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  }