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 }