// uploadFileContents does its best to upload the local file stored at // localPath to the given *drive.File on Google Drive. (It assumes that // the *drive.File has already been created.) func uploadFileContents(localPath string, driveFile *gdrive.File, encrypt bool, pb *pb.ProgressBar) error { var iv []byte var err error if encrypt { iv, err = getInitializationVector(driveFile) if err != nil { return fmt.Errorf("unable to get IV: %v", err) } } for try := 0; ; try++ { contentsReader, length, err := getFileContentsReaderForUpload(localPath, encrypt, iv) if contentsReader != nil { defer contentsReader.Close() } if err != nil { return err } // Keep track of how many bytes are uploaded in case we fail // part-way through and need to roll back the progress bar. countingReader := &byteCountingReader{R: contentsReader} // Also tee reads to the progress bar as they are done so that it // stays in sync with how much data has been transmitted. var uploadReader io.Reader if pb != nil { uploadReader = io.TeeReader(countingReader, pb) } else { uploadReader = countingReader } if length >= resumableUploadMinSize { err = gd.UploadFileContentsResumable(driveFile, uploadReader, length) } else { err = gd.UploadFileContents(driveFile, uploadReader, length, try) } atomic.AddInt64(&stats.DiskReadBytes, countingReader.bytesRead) if err == nil { // Success! atomic.AddInt64(&stats.DriveFilesUpdated, 1) atomic.AddInt64(&stats.UploadBytes, length) return nil } // The "progress" made so far on this file should be rolled back; // if we don't do this, when retries happen, we end up going over // 100% progress... if pb != nil { pb.Add64(-countingReader.bytesRead) } if re, ok := err.(gdrive.RetryHTTPTransmitError); ok && try < 5 { debug.Printf("%s: got retry http error--retrying: %s", localPath, re.Error()) } else { debug.Printf("%s: giving up due to error: %v", localPath, err) // We're giving up on this file, so subtract its length from // what the progress bar is expecting. if pb != nil { pb.Total -= length } return err } } }