github.com/rclone/rclone@v1.66.1-0.20240517100346-7b89735ae726/fs/accounting/transfer.go (about)

     1  package accounting
     2  
     3  import (
     4  	"context"
     5  	"encoding/json"
     6  	"io"
     7  	"sync"
     8  	"time"
     9  
    10  	"github.com/rclone/rclone/fs"
    11  	"github.com/rclone/rclone/fs/rc"
    12  )
    13  
    14  // TransferSnapshot represents state of an account at point in time.
    15  type TransferSnapshot struct {
    16  	Name        string    `json:"name"`
    17  	Size        int64     `json:"size"`
    18  	Bytes       int64     `json:"bytes"`
    19  	Checked     bool      `json:"checked"`
    20  	StartedAt   time.Time `json:"started_at"`
    21  	CompletedAt time.Time `json:"completed_at,omitempty"`
    22  	Error       error     `json:"-"`
    23  	Group       string    `json:"group"`
    24  	SrcFs       string    `json:"srcFs,omitempty"`
    25  	DstFs       string    `json:"dstFs,omitempty"`
    26  }
    27  
    28  // MarshalJSON implements json.Marshaler interface.
    29  func (as TransferSnapshot) MarshalJSON() ([]byte, error) {
    30  	err := ""
    31  	if as.Error != nil {
    32  		err = as.Error.Error()
    33  	}
    34  
    35  	type Alias TransferSnapshot
    36  	return json.Marshal(&struct {
    37  		Error string `json:"error"`
    38  		Alias
    39  	}{
    40  		Error: err,
    41  		Alias: (Alias)(as),
    42  	})
    43  }
    44  
    45  // Transfer keeps track of initiated transfers and provides access to
    46  // accounting functions.
    47  // Transfer needs to be closed on completion.
    48  type Transfer struct {
    49  	// these are initialised at creation and may be accessed without locking
    50  	stats     *StatsInfo
    51  	remote    string
    52  	size      int64
    53  	startedAt time.Time
    54  	checking  bool
    55  	what      string // what kind of transfer this is
    56  	srcFs     fs.Fs  // source Fs - may be nil
    57  	dstFs     fs.Fs  // destination Fs - may be nil
    58  
    59  	// Protects all below
    60  	//
    61  	// NB to avoid deadlocks we must release this lock before
    62  	// calling any methods on Transfer.stats.  This is because
    63  	// StatsInfo calls back into Transfer.
    64  	mu          sync.RWMutex
    65  	acc         *Account
    66  	err         error
    67  	completedAt time.Time
    68  }
    69  
    70  // newCheckingTransfer instantiates new checking of the object.
    71  func newCheckingTransfer(stats *StatsInfo, obj fs.DirEntry, what string) *Transfer {
    72  	return newTransferRemoteSize(stats, obj.Remote(), obj.Size(), true, what, nil, nil)
    73  }
    74  
    75  // newTransfer instantiates new transfer.
    76  func newTransfer(stats *StatsInfo, obj fs.DirEntry, srcFs, dstFs fs.Fs) *Transfer {
    77  	return newTransferRemoteSize(stats, obj.Remote(), obj.Size(), false, "", srcFs, dstFs)
    78  }
    79  
    80  func newTransferRemoteSize(stats *StatsInfo, remote string, size int64, checking bool, what string, srcFs, dstFs fs.Fs) *Transfer {
    81  	tr := &Transfer{
    82  		stats:     stats,
    83  		remote:    remote,
    84  		size:      size,
    85  		startedAt: time.Now(),
    86  		checking:  checking,
    87  		what:      what,
    88  		srcFs:     srcFs,
    89  		dstFs:     dstFs,
    90  	}
    91  	stats.AddTransfer(tr)
    92  	return tr
    93  }
    94  
    95  // Done ends the transfer.
    96  // Must be called after transfer is finished to run proper cleanups.
    97  func (tr *Transfer) Done(ctx context.Context, err error) {
    98  	if err != nil {
    99  		err = tr.stats.Error(err)
   100  
   101  		tr.mu.Lock()
   102  		tr.err = err
   103  		tr.mu.Unlock()
   104  	}
   105  
   106  	tr.mu.RLock()
   107  	acc := tr.acc
   108  	tr.mu.RUnlock()
   109  
   110  	ci := fs.GetConfig(ctx)
   111  	if acc != nil {
   112  		// Close the file if it is still open
   113  		if err := acc.Close(); err != nil {
   114  			fs.LogLevelPrintf(ci.StatsLogLevel, nil, "can't close account: %+v\n", err)
   115  		}
   116  		// Signal done with accounting
   117  		acc.Done()
   118  		// free the account since we may keep the transfer
   119  		acc = nil
   120  	}
   121  
   122  	tr.mu.Lock()
   123  	tr.completedAt = time.Now()
   124  	tr.mu.Unlock()
   125  
   126  	if tr.checking {
   127  		tr.stats.DoneChecking(tr.remote)
   128  	} else {
   129  		tr.stats.DoneTransferring(tr.remote, err == nil)
   130  	}
   131  	tr.stats.PruneTransfers()
   132  }
   133  
   134  // Reset allows to switch the Account to another transfer method.
   135  func (tr *Transfer) Reset(ctx context.Context) {
   136  	tr.mu.RLock()
   137  	acc := tr.acc
   138  	tr.acc = nil
   139  	tr.mu.RUnlock()
   140  	ci := fs.GetConfig(ctx)
   141  
   142  	if acc != nil {
   143  		acc.Done()
   144  		if err := acc.Close(); err != nil {
   145  			fs.LogLevelPrintf(ci.StatsLogLevel, nil, "can't close account: %+v\n", err)
   146  		}
   147  	}
   148  }
   149  
   150  // Account returns reader that knows how to keep track of transfer progress.
   151  func (tr *Transfer) Account(ctx context.Context, in io.ReadCloser) *Account {
   152  	tr.mu.Lock()
   153  	if tr.acc == nil {
   154  		tr.acc = newAccountSizeName(ctx, tr.stats, in, tr.size, tr.remote)
   155  	} else {
   156  		tr.acc.UpdateReader(ctx, in)
   157  	}
   158  	tr.acc.checking = tr.checking
   159  	tr.mu.Unlock()
   160  	return tr.acc
   161  }
   162  
   163  // TimeRange returns the time transfer started and ended at. If not completed
   164  // it will return zero time for end time.
   165  func (tr *Transfer) TimeRange() (time.Time, time.Time) {
   166  	tr.mu.RLock()
   167  	defer tr.mu.RUnlock()
   168  	return tr.startedAt, tr.completedAt
   169  }
   170  
   171  // IsDone returns true if transfer is completed.
   172  func (tr *Transfer) IsDone() bool {
   173  	tr.mu.RLock()
   174  	defer tr.mu.RUnlock()
   175  	return !tr.completedAt.IsZero()
   176  }
   177  
   178  // Snapshot produces stats for this account at point in time.
   179  func (tr *Transfer) Snapshot() TransferSnapshot {
   180  	tr.mu.RLock()
   181  	defer tr.mu.RUnlock()
   182  
   183  	var s, b int64 = tr.size, 0
   184  	if tr.acc != nil {
   185  		b, s = tr.acc.progress()
   186  	}
   187  	snapshot := TransferSnapshot{
   188  		Name:        tr.remote,
   189  		Checked:     tr.checking,
   190  		Size:        s,
   191  		Bytes:       b,
   192  		StartedAt:   tr.startedAt,
   193  		CompletedAt: tr.completedAt,
   194  		Error:       tr.err,
   195  		Group:       tr.stats.group,
   196  	}
   197  	if tr.srcFs != nil {
   198  		snapshot.SrcFs = fs.ConfigString(tr.srcFs)
   199  	}
   200  	if tr.dstFs != nil {
   201  		snapshot.DstFs = fs.ConfigString(tr.dstFs)
   202  	}
   203  	return snapshot
   204  }
   205  
   206  // rcStats returns stats for the transfer suitable for the rc
   207  func (tr *Transfer) rcStats() rc.Params {
   208  	out := rc.Params{
   209  		"name": tr.remote, // no locking needed to access this
   210  		"size": tr.size,
   211  	}
   212  	if tr.srcFs != nil {
   213  		out["srcFs"] = fs.ConfigString(tr.srcFs)
   214  	}
   215  	if tr.dstFs != nil {
   216  		out["dstFs"] = fs.ConfigString(tr.dstFs)
   217  	}
   218  	return out
   219  }