// transferDetachmentConn transfers as much of a detachment as possible on a // single connection. It calls sendStatus repeatedly with the current state of // the transfer and watches killChan for an abort signal. It returns an error // and an indication of whether the error is fatal. If not fatatl then another // connection can be attempted in order to resume the transfer. func (c *client) transferDetachmentConn(sendStatus func(s string, done, total int64), conn *transport.Conn, transfer detachmentTransfer, killChan chan bool) (err error, fatal bool) { defer conn.Close() // transferred is the number of bytes that *this connection* has transferred. // total is the full length of the file. var startingOffset, transferred, total int64 sendStatus("Requesting transfer", 0, 0) if err := conn.WriteProto(transfer.Request()); err != nil { return fmt.Errorf("failed to write request: %s", err), false } reply := new(pond.Reply) if err := conn.ReadProto(reply); err != nil { return fmt.Errorf("failed to read reply: %s", err), false } if reply.Status != nil && *reply.Status == pond.Reply_RESUME_PAST_END_OF_FILE { return nil, false } if err := replyToError(reply); err != nil { if reply.GetStatus() == pond.Reply_OVER_QUOTA { return fmt.Errorf("server reports that the upload would exceed allowed quota"), true } return fmt.Errorf("request failed: %s", err), false } var file *os.File var isUpload, isComplete bool if file, isUpload, startingOffset, total, isComplete, err = transfer.ProcessReply(reply); err != nil { return fmt.Errorf("request failed: %s", err), false } if isComplete { return nil, false } todo := total - startingOffset var in io.Reader var out io.Writer if isUpload { out = conn in = file } else { out = file in = conn } buf := make([]byte, 16*1024) var lastUpdate time.Time for transferred < todo { select { case <-killChan: return backgroundCanceledError, true default: break } conn.SetDeadline(time.Now().Add(30 * time.Second)) n, err := in.Read(buf) if err != nil { if isUpload { return fmt.Errorf("failed to read from disk: %s", err), true } return err, false } n, err = out.Write(buf[:n]) if err != nil { if !isUpload { return fmt.Errorf("failed to write to disk: %s", err), true } return err, false } transferred += int64(n) if transferred > todo { return errors.New("transferred more than the expected amount"), true } now := time.Now() if lastUpdate.IsZero() || now.Sub(lastUpdate) > 10*time.Millisecond { lastUpdate = now sendStatus("", startingOffset+transferred, total) } } sendStatus("", startingOffset+transferred, total) if transferred < todo { return errors.New("incomplete transfer"), false } if !transfer.Complete(conn) { return errors.New("didn't receive confirmation from server"), false } return nil, false }