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 }