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