github.com/mutagen-io/mutagen@v0.18.0-rc1/pkg/synchronization/rsync/transmit.go (about) 1 package rsync 2 3 import ( 4 "errors" 5 "fmt" 6 7 "github.com/mutagen-io/mutagen/pkg/filesystem" 8 ) 9 10 // Transmit performs streaming transmission of files (in rsync deltified form) 11 // to the specified receiver. It is the responsibility of the caller to ensure 12 // that the provided signatures are valid by invoking their EnsureValid method. 13 // In order for this function to perform efficiently, paths should be passed in 14 // depth-first traversal order. 15 func Transmit(root string, paths []string, signatures []*Signature, receiver Receiver) error { 16 // Ensure that the transmission request is sane. 17 if len(paths) != len(signatures) { 18 receiver.finalize() 19 return errors.New("number of paths does not match number of signatures") 20 } 21 22 // Create a file opener that we can use to safely open files, and defer its 23 // closure. 24 opener := filesystem.NewOpener(root) 25 defer opener.Close() 26 27 // Create an rsync engine. 28 engine := NewEngine() 29 30 // Create a transmission object that we can re-use to avoid allocating. 31 transmission := &Transmission{} 32 33 // Handle the requested files. 34 for i, p := range paths { 35 // Open the file and extract its size. Failure here is non-terminal, but 36 // we need to inform the receiver. If sending the message fails, that is 37 // a terminal error. 38 file, metadata, err := opener.OpenFile(p) 39 if err != nil { 40 *transmission = Transmission{ 41 Done: true, 42 Error: fmt.Errorf("unable to open file: %w", err).Error(), 43 } 44 if err = receiver.Receive(transmission); err != nil { 45 receiver.finalize() 46 return fmt.Errorf("unable to send error transmission: %w", err) 47 } 48 continue 49 } 50 fileSize := metadata.Size 51 52 // Create an operation transmitter for deltification and track reception 53 // errors. We can safely set transmitError on each call because as soon 54 // as it's returned non-nil, the transmit function won't be called 55 // again. 56 var transmitError error 57 transmit := func(o *Operation) error { 58 *transmission = Transmission{ExpectedSize: fileSize, Operation: o} 59 transmitError = receiver.Receive(transmission) 60 fileSize = 0 61 return transmitError 62 } 63 64 // Perform deltification. 65 err = engine.Deltify(file, signatures[i], 0, transmit) 66 67 // Close the file. 68 file.Close() 69 70 // Handle any transmission errors. These are terminal. 71 if transmitError != nil { 72 receiver.finalize() 73 return fmt.Errorf("unable to transmit delta: %w", transmitError) 74 } 75 76 // Inform the client the operation stream for this file is complete. Any 77 // internal (non-transmission) errors are non-terminal but should be 78 // reported to the receiver. 79 *transmission = Transmission{Done: true} 80 if err != nil { 81 transmission.Error = fmt.Errorf("engine error: %w", err).Error() 82 } 83 if err = receiver.Receive(transmission); err != nil { 84 receiver.finalize() 85 return fmt.Errorf("unable to send done message: %w", err) 86 } 87 } 88 89 // Ensure that the receiver is finalized. 90 if err := receiver.finalize(); err != nil { 91 return fmt.Errorf("unable to finalize receiver: %w", err) 92 } 93 94 // Success. 95 return nil 96 }