github.com/Files-com/files-sdk-go/v2@v2.1.2/file/uploadio.go (about) 1 package file 2 3 import ( 4 "bytes" 5 "context" 6 "errors" 7 "io" 8 "net/http" 9 "net/url" 10 "os" 11 "strconv" 12 "strings" 13 "sync/atomic" 14 "time" 15 16 files_sdk "github.com/Files-com/files-sdk-go/v2" 17 "github.com/Files-com/files-sdk-go/v2/file/manager" 18 "github.com/Files-com/files-sdk-go/v2/file/status" 19 "github.com/Files-com/files-sdk-go/v2/lib" 20 ) 21 22 type Progress func(int64) 23 24 type Len interface { 25 Len() int 26 } 27 28 type UploadResumable struct { 29 files_sdk.FileUploadPart 30 Parts 31 files_sdk.File 32 } 33 34 type uploadIO struct { 35 ByteOffset 36 Path string 37 reader io.Reader 38 readerAt io.ReaderAt 39 Size *int64 40 Progress 41 Manager lib.ConcurrencyManagerWithSubWorker 42 ProvidedMtime time.Time 43 Parts 44 files_sdk.FileUploadPart 45 MkdirParents *bool 46 passedInContext context.Context 47 48 onComplete chan *Part 49 partsFinished chan struct{} 50 bytesWritten int64 51 *Client 52 etags []files_sdk.EtagsParam 53 file files_sdk.File 54 RewindAllProgressOnFailure bool 55 disableParallelParts bool 56 notResumable *atomic.Bool 57 } 58 59 func (u *uploadIO) Run(ctx context.Context) (UploadResumable, error) { 60 u.notResumable = &atomic.Bool{} 61 u.notResumable.Store(false) 62 if u.Path == "" { 63 return u.UploadResumable(), errors.New("UploadWithDestinationPath is required") 64 } 65 66 if u.reader == nil && u.readerAt == nil { 67 return u.UploadResumable(), errors.New("UploadWithReader or UploadWithReaderAt required") 68 } 69 70 u.onComplete = make(chan *Part) 71 u.partsFinished = make(chan struct{}) 72 u.etags = make([]files_sdk.EtagsParam, 0) 73 if u.Manager == nil { 74 u.Manager = manager.Sync().FilePartsManager 75 } 76 if u.Progress == nil { 77 u.Progress = func(i int64) {} 78 } 79 80 var defaultSize int64 81 if u.Size != nil { 82 defaultSize = *u.Size 83 } 84 85 var expires time.Time 86 var err error 87 if u.FileUploadPart.Expires != "" { 88 expires, _ = time.Parse(time.RFC3339, u.FileUploadPart.Expires) 89 } 90 if !time.Now().Before(expires) || !lib.UnWrapBool(u.FileUploadPart.ParallelParts) { 91 u.Parts = Parts{} // parts are invalidated 92 } 93 94 if u.MkdirParents == nil { 95 u.MkdirParents = lib.Bool(true) 96 } 97 98 if expires.IsZero() || !time.Now().Before(expires) { 99 if u.Manager.WaitWithContext(ctx) { 100 u.FileUploadPart, err = u.startUpload( 101 ctx, 102 files_sdk.FileBeginUploadParams{ 103 Size: defaultSize, 104 Path: u.Path, 105 MkdirParents: u.MkdirParents, 106 }, 107 ) 108 u.Manager.Done() 109 } else { 110 return u.UploadResumable(), ctx.Err() 111 } 112 if err != nil { 113 return u.UploadResumable(), err 114 } 115 } 116 117 partCtx, cancel := context.WithCancelCause(ctx) 118 119 u.FileUploadPart.Path = u.Path 120 go u.uploadParts(partCtx) 121 return u.waitOnParts(ctx, cancel) 122 } 123 124 func (u *uploadIO) UploadResumable() UploadResumable { 125 if u.notResumable.Load() { 126 return UploadResumable{File: u.file} 127 } 128 return UploadResumable{Parts: u.Parts, FileUploadPart: u.FileUploadPart, File: u.file} 129 } 130 131 func (u *uploadIO) rewindSuccessfulParts() { 132 if !u.RewindAllProgressOnFailure { 133 return 134 } 135 for _, part := range u.Parts { 136 if part.Successful() { 137 u.Progress(-part.bytes) 138 } 139 } 140 } 141 142 func (u *uploadIO) waitOnParts(ctx context.Context, cancelParts context.CancelCauseFunc) (UploadResumable, error) { 143 var allErrors error 144 for { 145 select { 146 case <-u.partsFinished: 147 close(u.onComplete) 148 if allErrors != nil { 149 u.rewindSuccessfulParts() 150 return u.UploadResumable(), allErrors 151 } 152 // Rate limit all outgoing connections 153 if u.Manager.WaitWithContext(ctx) { 154 var err error 155 u.file, err = u.completeUpload(ctx, &u.ProvidedMtime, u.etags, u.bytesWritten, u.Path, u.FileUploadPart.Ref) 156 u.Manager.Done() 157 if err != nil { 158 u.rewindSuccessfulParts() 159 } 160 return u.UploadResumable(), err 161 } else { 162 u.rewindSuccessfulParts() 163 return u.UploadResumable(), ctx.Err() 164 } 165 case part := <-u.onComplete: 166 if part.error == nil { 167 u.etags = append(u.etags, part.EtagsParam) 168 u.bytesWritten += part.bytes 169 } else { 170 u.Progress(-part.bytes) 171 allErrors = errors.Join(allErrors, part.error) 172 173 if strings.Contains(part.Error(), "File Upload Not Found") { 174 cancelParts(part.error) 175 176 u.notResumable.Store(true) 177 } 178 } 179 } 180 } 181 } 182 183 func (u *uploadIO) Reader() (io.Reader, bool) { 184 return u.reader, u.reader != nil 185 } 186 187 func (u *uploadIO) ReaderAt() (io.ReaderAt, bool) { 188 if u.readerAt != nil { 189 return u.readerAt, true 190 } 191 readerAt, ok := u.reader.(io.ReaderAt) 192 return readerAt, ok 193 } 194 195 func (u *uploadIO) partBuilder(offset OffSet, final bool, number int) *Part { 196 part := &Part{OffSet: offset, number: number, final: final, ProxyReader: u.buildReader(offset)} 197 // Parts are stored so a retry can pick up failed parts. Since io.Reader is a stream is better to just retry the whole file 198 if _, readerAtOk := u.ReaderAt(); readerAtOk && u.Size != nil { 199 u.Parts = append(u.Parts, part) 200 } 201 202 return part 203 } 204 205 func (u *uploadIO) buildReader(offset OffSet) ProxyReader { 206 var readerAt io.ReaderAt 207 var readerAtOk bool 208 209 if readerAt, readerAtOk = u.ReaderAt(); !readerAtOk { 210 u.disableParallelParts = true 211 } 212 213 if u.Size == nil { 214 if readerAtOk { 215 sectionReader := io.NewSectionReader(readerAt, offset.off, offset.len) 216 buf := new(bytes.Buffer) 217 218 // Use io.CopyN to copy up to fiveMB into buf 219 n, err := io.CopyN(buf, sectionReader, offset.len) 220 if err != nil && err != io.EOF { 221 // handle error 222 } 223 224 return &ProxyRead{ 225 Reader: buf, 226 len: n, 227 onRead: u.Progress, 228 } 229 } else { 230 buf := new(bytes.Buffer) 231 232 // Use io.CopyN to copy up to fiveMB into buf 233 reader, _ := u.Reader() 234 n, err := io.CopyN(buf, reader, offset.len) 235 if err != nil && err != io.EOF { 236 // handle error 237 } 238 239 return &ProxyRead{ 240 Reader: buf, 241 len: n, 242 onRead: u.Progress, 243 } 244 } 245 } 246 247 if readerAtOk { 248 return &ProxyReaderAt{ 249 ReaderAt: readerAt, 250 off: offset.off, 251 len: offset.len, 252 onRead: u.Progress, 253 } 254 } else { 255 reader, _ := u.Reader() 256 return &ProxyRead{ 257 Reader: reader, 258 len: offset.len, 259 onRead: u.Progress, 260 } 261 } 262 } 263 264 type PartRunnerReturn int 265 266 func (u *uploadIO) manageUpdatePart(ctx context.Context, part *Part, wait lib.ConcurrencyManager) bool { 267 if wait.WaitWithContext(ctx) { 268 if *u.FileUploadPart.ParallelParts && !u.disableParallelParts && u.Size != nil { 269 go func() { 270 u.runUploadPart(ctx, part) 271 wait.Done() 272 }() 273 } else { 274 u.runUploadPart(ctx, part) 275 wait.Done() 276 } 277 if part.ProxyReader.Len() != int(part.len) { 278 return true 279 } 280 } else { 281 return true 282 } 283 284 return false 285 } 286 287 func (u *uploadIO) runUploadPart(ctx context.Context, part *Part) { 288 fileUploadPart := u.FileUploadPart 289 fileUploadPart.PartNumber = int64(part.number) 290 maxRetries := 2 291 retryCount := 0 292 293 for retryCount <= maxRetries { 294 part.EtagsParam, part.error = u.createPart(ctx, part.ProxyReader, int64(part.ProxyReader.Len()), fileUploadPart, part.final) 295 296 if part.error == nil { 297 break 298 } 299 300 var pathErr *os.PathError 301 if errors.As(part.error, &pathErr) { 302 part.error = pathErr 303 } 304 305 var s3Err lib.S3Error 306 expires, _ := time.Parse(time.RFC3339, u.FileUploadPart.Expires) 307 if errors.As(part.error, &s3Err) && part.ProxyReader.Rewind() && time.Now().Before(expires) { 308 if s3Err.Code == "RequestTimeout" { 309 retryCount++ 310 if retryCount > maxRetries { 311 break 312 } 313 u.LogPath(u.Path, map[string]interface{}{"error": s3Err.Error(), "message": "retrying"}) 314 continue 315 } 316 } 317 break 318 } 319 320 part.bytes = int64(part.ProxyReader.BytesRead()) 321 part.Touch() 322 u.onComplete <- part 323 } 324 325 func (u *uploadIO) uploadParts(ctx context.Context) { 326 wait := u.Manager.NewSubWorker() 327 defer func() { 328 wait.WaitAllDone() 329 u.partsFinished <- struct{}{} 330 }() 331 332 for _, part := range u.Parts { 333 if part.Successful() { 334 u.Progress(part.bytes) 335 u.onComplete <- part 336 } else { 337 part.Clear() 338 part.ProxyReader = u.buildReader(part.OffSet) 339 340 if u.manageUpdatePart(ctx, part, wait) { 341 break 342 } 343 } 344 } 345 var ( 346 offset OffSet 347 iterator Iterator 348 index int 349 ) 350 if len(u.Parts) > 0 { 351 lastPart := u.Parts[len(u.Parts)-1] 352 iterator = u.ByteOffset.Resume(u.Size, lastPart.off, lastPart.number-1) 353 offset, iterator, index = iterator() 354 } else { 355 iterator = u.ByteOffset.BySize(u.Size) 356 } 357 358 for { 359 if iterator == nil { 360 break 361 } 362 363 offset, iterator, index = iterator() 364 365 if u.manageUpdatePart( 366 ctx, 367 u.partBuilder( 368 offset, 369 iterator == nil && u.Size != nil, 370 index+1, 371 ), 372 wait, 373 ) { 374 break 375 } 376 } 377 } 378 379 func (u *uploadIO) startUpload(ctx context.Context, beginUpload files_sdk.FileBeginUploadParams) (files_sdk.FileUploadPart, error) { 380 uploads, err := u.BeginUpload(beginUpload, files_sdk.WithContext(ctx)) 381 if err != nil { 382 return files_sdk.FileUploadPart{}, err 383 } 384 return uploads[0], err 385 } 386 387 func (u *uploadIO) completeUpload(ctx context.Context, providedMtime *time.Time, etags []files_sdk.EtagsParam, bytesWritten int64, path string, ref string) (files_sdk.File, error) { 388 if providedMtime.IsZero() { 389 providedMtime = nil 390 } 391 392 return u.Create(files_sdk.FileCreateParams{ 393 ProvidedMtime: providedMtime, 394 EtagsParam: etags, 395 Action: "end", 396 Path: path, 397 Ref: ref, 398 Size: bytesWritten, 399 MkdirParents: lib.Bool(true), 400 }, files_sdk.WithContext(ctx)) 401 } 402 403 func (u *uploadIO) createPart(ctx context.Context, reader io.ReadCloser, len int64, fileUploadPart files_sdk.FileUploadPart, lastPart bool) (files_sdk.EtagsParam, error) { 404 partNumber := fileUploadPart.PartNumber 405 var err error 406 if partNumber != 1 && *fileUploadPart.ParallelParts { // Remote Mounts use the same url 407 fileUploadPart, err = u.startUpload( 408 ctx, files_sdk.FileBeginUploadParams{Path: fileUploadPart.Path, Ref: fileUploadPart.Ref, Part: fileUploadPart.PartNumber, MkdirParents: lib.Bool(true)}, 409 ) 410 fileUploadPart.PartNumber = partNumber 411 if err != nil { 412 return files_sdk.EtagsParam{}, err 413 } 414 } 415 uri, err := url.Parse(fileUploadPart.UploadUri) 416 if err == nil { 417 q := uri.Query() 418 if q.Get("partNumber") == "" { 419 q.Add("part_number", strconv.FormatInt(partNumber, 10)) 420 uri.RawQuery = q.Encode() 421 fileUploadPart.UploadUri = uri.String() 422 } 423 } 424 425 headers := http.Header{} 426 headers.Add("Content-Length", strconv.FormatInt(len, 10)) 427 res, err := files_sdk.CallRaw( 428 &files_sdk.CallParams{ 429 Method: fileUploadPart.HttpMethod, 430 Config: u.Config, 431 Uri: fileUploadPart.UploadUri, 432 BodyIo: reader, 433 Headers: &headers, 434 Context: ctx, 435 StayOpen: !*fileUploadPart.ParallelParts && !lastPart, // Since Remote Mounts use the same url only close the connection on the last part. 436 }, 437 ) 438 if err != nil { 439 return files_sdk.EtagsParam{}, err 440 } 441 if err := lib.ResponseErrors(res, files_sdk.APIError(), lib.S3XMLError, lib.NonOkError); err != nil { 442 return files_sdk.EtagsParam{}, err 443 } 444 etag := strings.Trim(res.Header.Get("Etag"), "\"") 445 if etag == "" { 446 // With remote mounts this has no value, but the code strip the value causing a validation error. 447 etag = "null" 448 } 449 if res != nil { 450 err = res.Body.Close() 451 if err != nil { 452 return files_sdk.EtagsParam{}, err 453 } 454 } 455 return files_sdk.EtagsParam{ 456 Etag: etag, 457 Part: strconv.FormatInt(fileUploadPart.PartNumber, 10), 458 }, nil 459 } 460 461 func uploadProgress(uploadStatus *UploadStatus) func(bytesCount int64) { 462 return func(bytesCount int64) { 463 uploadStatus.Job().UpdateStatusWithBytes(status.Uploading, uploadStatus, bytesCount) 464 } 465 }