github.com/jfrog/jfrog-cli-core/v2@v2.51.0/artifactory/commands/transferfiles/state/statemanager.go (about) 1 package state 2 3 import ( 4 "path/filepath" 5 "time" 6 7 "github.com/jfrog/gofrog/datastructures" 8 "github.com/jfrog/jfrog-cli-core/v2/artifactory/commands/transferfiles/api" 9 "github.com/jfrog/jfrog-cli-core/v2/utils/coreutils" 10 "github.com/jfrog/jfrog-cli-core/v2/utils/lock" 11 "github.com/jfrog/jfrog-client-go/utils/errorutils" 12 "github.com/jfrog/jfrog-client-go/utils/io/fileutils" 13 "github.com/jfrog/jfrog-client-go/utils/log" 14 ) 15 16 // The interval in which to save the state and run transfer files to the file system. 17 // Every change made will be held in memory till the saving time comes. 18 const stateAndStatusSaveIntervalSecsDefault = 10 19 20 var stateAndStatusSaveIntervalSecs = stateAndStatusSaveIntervalSecsDefault 21 22 type ProgressState struct { 23 TotalSizeBytes int64 `json:"total_size_bytes,omitempty"` 24 TransferredSizeBytes int64 `json:"transferred_size_bytes,omitempty"` 25 ProgressStateUnits 26 } 27 28 type ProgressStateUnits struct { 29 TotalUnits int64 `json:"total_units,omitempty"` 30 TransferredUnits int64 `json:"transferred_units,omitempty"` 31 } 32 33 type TransferStateManager struct { 34 TransferState 35 TransferRunStatus 36 repoTransferSnapshot *RepoTransferSnapshot 37 // This function unlocks the state manager after the transfer-files command is finished 38 unlockStateManager func() error 39 } 40 41 func NewTransferStateManager(loadRunStatus bool) (*TransferStateManager, error) { 42 stateManager := TransferStateManager{} 43 if loadRunStatus { 44 transferRunStatus, _, err := loadTransferRunStatus() 45 if err != nil { 46 return nil, err 47 } 48 stateManager.TransferRunStatus = transferRunStatus 49 } 50 stateManager.TimeEstimationManager.stateManager = &stateManager 51 return &stateManager, nil 52 } 53 54 // Try to lock the transfer state manager. 55 // If file-transfer is already running, return "Already locked" error. 56 func (ts *TransferStateManager) TryLockTransferStateManager() error { 57 return ts.tryLockStateManager() 58 } 59 60 func (ts *TransferStateManager) UnlockTransferStateManager() error { 61 return ts.unlockStateManager() 62 } 63 64 func (ts *TransferStateManager) SetStartTimestamp(startTimestamp time.Time) { 65 ts.startTimestamp = startTimestamp 66 } 67 68 func (ts *TransferStateManager) GetStartTimestamp() time.Time { 69 return ts.startTimestamp 70 } 71 72 // Set the repository state. 73 // repoKey - Repository key 74 // totalSizeBytes - Repository size in bytes 75 // totalFiles - Total files in the repository 76 // buildInfoRepo - True if build info repository 77 // reset - Delete the repository's previous transfer info 78 func (ts *TransferStateManager) SetRepoState(repoKey string, totalSizeBytes, totalFiles int64, buildInfoRepo, reset bool) error { 79 var transferredFiles uint32 = 0 80 var transferredSizeBytes uint64 = 0 81 err := ts.Action(func(*TransferState) error { 82 transferState, repoTransferSnapshot, err := getTransferStateAndSnapshot(repoKey, reset) 83 if err != nil { 84 return err 85 } 86 transferState.CurrentRepo.Phase1Info.TotalSizeBytes = totalSizeBytes 87 transferState.CurrentRepo.Phase1Info.TotalUnits = totalFiles 88 89 if repoTransferSnapshot != nil && repoTransferSnapshot.loadedFromSnapshot { 90 transferredFiles, transferredSizeBytes, err = repoTransferSnapshot.snapshotManager.CalculateTransferredFilesAndSize() 91 if err != nil { 92 return err 93 } 94 log.Info("Calculated transferred files from previous run:", transferredFiles) 95 log.Info("Calculated transferred bytes from previous run:", transferredSizeBytes) 96 transferState.CurrentRepo.Phase1Info.TransferredUnits = int64(transferredFiles) 97 transferState.CurrentRepo.Phase1Info.TransferredSizeBytes = int64(transferredSizeBytes) 98 } 99 100 ts.TransferState = transferState 101 ts.repoTransferSnapshot = repoTransferSnapshot 102 return nil 103 }) 104 if err != nil { 105 return err 106 } 107 return ts.action(func(transferRunStatus *TransferRunStatus) error { 108 transferRunStatus.CurrentRepoKey = repoKey 109 transferRunStatus.BuildInfoRepo = buildInfoRepo 110 transferRunStatus.VisitedFolders = 0 111 112 transferRunStatus.OverallTransfer.TransferredUnits += int64(transferredFiles) 113 transferRunStatus.OverallTransfer.TransferredSizeBytes += int64(transferredSizeBytes) 114 return nil 115 }) 116 } 117 118 func (ts *TransferStateManager) SetRepoFullTransferStarted(startTime time.Time) error { 119 // We do not want to change the start time if it already exists, because it means we continue transferring from a snapshot. 120 // Some dirs may not be searched again (if done exploring or completed), so handling their diffs from the original time is required. 121 return ts.TransferState.Action(func(state *TransferState) error { 122 if state.CurrentRepo.FullTransfer.Started == "" { 123 state.CurrentRepo.FullTransfer.Started = ConvertTimeToRFC3339(startTime) 124 } 125 return nil 126 }) 127 } 128 129 func (ts *TransferStateManager) SetRepoFullTransferCompleted() error { 130 return ts.TransferState.Action(func(state *TransferState) error { 131 state.CurrentRepo.FullTransfer.Ended = ConvertTimeToRFC3339(time.Now()) 132 return nil 133 }) 134 } 135 136 // Increasing Transferred Diff files (modified files) and SizeByBytes value in suitable repository progress state 137 func (ts *TransferStateManager) IncTransferredSizeAndFilesPhase1(chunkTotalFiles, chunkTotalSizeInBytes int64) error { 138 err := ts.TransferState.Action(func(state *TransferState) error { 139 atomicallyAddInt64(&state.CurrentRepo.Phase1Info.TransferredSizeBytes, chunkTotalSizeInBytes) 140 atomicallyAddInt64(&state.CurrentRepo.Phase1Info.TransferredUnits, chunkTotalFiles) 141 return nil 142 }) 143 if err != nil { 144 return err 145 } 146 return ts.TransferRunStatus.action(func(transferRunStatus *TransferRunStatus) error { 147 atomicallyAddInt64(&transferRunStatus.OverallTransfer.TransferredSizeBytes, chunkTotalSizeInBytes) 148 atomicallyAddInt64(&transferRunStatus.OverallTransfer.TransferredUnits, chunkTotalFiles) 149 150 if transferRunStatus.BuildInfoRepo { 151 atomicallyAddInt64(&transferRunStatus.OverallBiFiles.TransferredUnits, chunkTotalFiles) 152 } 153 return nil 154 }) 155 } 156 157 func (ts *TransferStateManager) IncTransferredSizeAndFilesPhase2(chunkTotalFiles, chunkTotalSizeInBytes int64) error { 158 return ts.TransferState.Action(func(state *TransferState) error { 159 atomicallyAddInt64(&state.CurrentRepo.Phase2Info.TransferredSizeBytes, chunkTotalSizeInBytes) 160 atomicallyAddInt64(&state.CurrentRepo.Phase2Info.TransferredUnits, chunkTotalFiles) 161 return nil 162 }) 163 } 164 165 func (ts *TransferStateManager) IncTotalSizeAndFilesPhase2(filesNumber, totalSize int64) error { 166 return ts.TransferState.Action(func(state *TransferState) error { 167 atomicallyAddInt64(&state.CurrentRepo.Phase2Info.TotalSizeBytes, totalSize) 168 atomicallyAddInt64(&state.CurrentRepo.Phase2Info.TotalUnits, filesNumber) 169 return nil 170 }) 171 } 172 173 // Set relevant information of files and storage we need to transfer in phase3 174 func (ts *TransferStateManager) SetTotalSizeAndFilesPhase3(filesNumber, totalSize int64) error { 175 return ts.TransferState.Action(func(state *TransferState) error { 176 state.CurrentRepo.Phase3Info.TransferredUnits = 0 177 state.CurrentRepo.Phase3Info.TransferredSizeBytes = 0 178 atomicallyAddInt64(&state.CurrentRepo.Phase3Info.TotalSizeBytes, totalSize) 179 atomicallyAddInt64(&state.CurrentRepo.Phase3Info.TotalUnits, filesNumber) 180 return nil 181 }) 182 } 183 184 // Increase transferred storage and files in phase 3 185 func (ts *TransferStateManager) IncTransferredSizeAndFilesPhase3(chunkTotalFiles, chunkTotalSizeInBytes int64) error { 186 return ts.TransferState.Action(func(state *TransferState) error { 187 atomicallyAddInt64(&state.CurrentRepo.Phase3Info.TransferredSizeBytes, chunkTotalSizeInBytes) 188 atomicallyAddInt64(&state.CurrentRepo.Phase3Info.TransferredUnits, chunkTotalFiles) 189 return nil 190 }) 191 } 192 193 // Returns pointers to TotalStorage, TotalFiles, TransferredFiles and TransferredStorage from progressState of a specific Repository. 194 func (ts *TransferStateManager) GetStorageAndFilesRepoPointers(phase int) (totalFailedStorage, totalUploadedFailedStorage, totalFailedFiles, totalUploadedFailedFiles *int64, err error) { 195 err = ts.TransferState.Action(func(state *TransferState) error { 196 switch phase { 197 case api.Phase1: 198 totalFailedStorage = &ts.CurrentRepo.Phase1Info.TotalSizeBytes 199 totalUploadedFailedStorage = &ts.CurrentRepo.Phase1Info.TransferredSizeBytes 200 totalFailedFiles = &ts.CurrentRepo.Phase1Info.TotalUnits 201 totalUploadedFailedFiles = &ts.CurrentRepo.Phase1Info.TransferredUnits 202 case api.Phase2: 203 totalFailedStorage = &ts.CurrentRepo.Phase2Info.TotalSizeBytes 204 totalUploadedFailedStorage = &ts.CurrentRepo.Phase2Info.TransferredSizeBytes 205 totalFailedFiles = &ts.CurrentRepo.Phase2Info.TotalUnits 206 totalUploadedFailedFiles = &ts.CurrentRepo.Phase2Info.TransferredUnits 207 case api.Phase3: 208 totalFailedStorage = &ts.CurrentRepo.Phase3Info.TotalSizeBytes 209 totalUploadedFailedStorage = &ts.CurrentRepo.Phase3Info.TransferredSizeBytes 210 totalFailedFiles = &ts.CurrentRepo.Phase3Info.TotalUnits 211 totalUploadedFailedFiles = &ts.CurrentRepo.Phase3Info.TransferredUnits 212 } 213 return nil 214 }) 215 return 216 } 217 218 // Adds new diff details to the repo's diff array in state. 219 // Marks files handling as started, and sets the handling range. 220 func (ts *TransferStateManager) AddNewDiffToState(startTime time.Time) error { 221 return ts.TransferState.Action(func(state *TransferState) error { 222 223 newDiff := DiffDetails{} 224 225 // Set Files Diff Handling started. 226 newDiff.FilesDiffRunTime.Started = ConvertTimeToRFC3339(startTime) 227 228 // Determines the range on which files diffs should be handled. 229 // If the repo previously completed files diff phases, we will continue handling diffs from where the last phase finished handling. 230 // Otherwise, we will start handling diffs from the start time of the full transfer. 231 for i := len(state.CurrentRepo.Diffs) - 1; i >= 0; i-- { 232 if state.CurrentRepo.Diffs[i].Completed { 233 newDiff.HandledRange.Started = state.CurrentRepo.Diffs[i].HandledRange.Ended 234 break 235 } 236 } 237 if newDiff.HandledRange.Started == "" { 238 newDiff.HandledRange.Started = state.CurrentRepo.FullTransfer.Started 239 } 240 newDiff.HandledRange.Ended = ConvertTimeToRFC3339(startTime) 241 state.CurrentRepo.Diffs = append(state.CurrentRepo.Diffs, newDiff) 242 return nil 243 }) 244 } 245 246 func (ts *TransferStateManager) SetFilesDiffHandlingCompleted() error { 247 return ts.TransferState.Action(func(state *TransferState) error { 248 state.CurrentRepo.Diffs[len(state.CurrentRepo.Diffs)-1].FilesDiffRunTime.Ended = ConvertTimeToRFC3339(time.Now()) 249 state.CurrentRepo.Diffs[len(state.CurrentRepo.Diffs)-1].Completed = true 250 return nil 251 }) 252 } 253 254 func (ts *TransferStateManager) GetReposTransferredSizeBytes(repoKeys ...string) (transferredSizeBytes int64, err error) { 255 reposSet := datastructures.MakeSet[string]() 256 for _, repoKey := range repoKeys { 257 if reposSet.Exists(repoKey) { 258 continue 259 } 260 reposSet.Add(repoKey) 261 transferState, exists, err := LoadTransferState(repoKey, false) 262 if err != nil { 263 return transferredSizeBytes, err 264 } 265 if !exists { 266 continue 267 } 268 transferredSizeBytes += transferState.CurrentRepo.Phase1Info.TransferredSizeBytes 269 } 270 return 271 } 272 273 func (ts *TransferStateManager) GetTransferredSizeBytes() (transferredSizeBytes int64, err error) { 274 return transferredSizeBytes, ts.TransferRunStatus.action(func(transferRunStatus *TransferRunStatus) error { 275 transferredSizeBytes = transferRunStatus.OverallTransfer.TransferredSizeBytes 276 return nil 277 }) 278 } 279 280 func (ts *TransferStateManager) GetDiffHandlingRange() (start, end time.Time, err error) { 281 return start, end, ts.TransferState.Action(func(state *TransferState) error { 282 var inErr error 283 start, inErr = ConvertRFC3339ToTime(state.CurrentRepo.Diffs[len(state.CurrentRepo.Diffs)-1].HandledRange.Started) 284 if inErr != nil { 285 return inErr 286 } 287 end, inErr = ConvertRFC3339ToTime(state.CurrentRepo.Diffs[len(state.CurrentRepo.Diffs)-1].HandledRange.Ended) 288 return inErr 289 }) 290 } 291 292 func (ts *TransferStateManager) IncVisitedFolders() error { 293 return ts.action(func(transferRunStatus *TransferRunStatus) error { 294 atomicallyAddUint64(&transferRunStatus.VisitedFolders, 1, true) 295 return nil 296 }) 297 } 298 299 func (ts *TransferStateManager) ChangeDelayedFilesCountBy(count uint64, increase bool) error { 300 return ts.TransferRunStatus.action(func(transferRunStatus *TransferRunStatus) error { 301 atomicallyAddUint64(&transferRunStatus.DelayedFiles, count, increase) 302 return nil 303 }) 304 } 305 306 func (ts *TransferStateManager) ChangeTransferFailureCountBy(count uint64, increase bool) error { 307 return ts.TransferRunStatus.action(func(transferRunStatus *TransferRunStatus) error { 308 atomicallyAddUint64(&transferRunStatus.TransferFailures, count, increase) 309 return nil 310 }) 311 } 312 313 func (ts *TransferStateManager) IncRepositoriesTransferred() error { 314 return ts.TransferRunStatus.action(func(transferRunStatus *TransferRunStatus) error { 315 transferRunStatus.TotalRepositories.TransferredUnits++ 316 return nil 317 }) 318 } 319 320 func (ts *TransferStateManager) SetRepoPhase(phaseId int) error { 321 return ts.TransferRunStatus.action(func(transferRunStatus *TransferRunStatus) error { 322 transferRunStatus.CurrentRepoPhase = phaseId 323 return nil 324 }) 325 } 326 327 func (ts *TransferStateManager) SetWorkingThreads(workingThreads int) error { 328 return ts.TransferRunStatus.action(func(transferRunStatus *TransferRunStatus) error { 329 transferRunStatus.WorkingThreads = workingThreads 330 return nil 331 }) 332 } 333 334 func (ts *TransferStateManager) GetWorkingThreads() (workingThreads int, err error) { 335 return workingThreads, ts.TransferRunStatus.action(func(transferRunStatus *TransferRunStatus) error { 336 workingThreads = transferRunStatus.WorkingThreads 337 return nil 338 }) 339 } 340 341 func (ts *TransferStateManager) SetStaleChunks(staleChunks []StaleChunks) error { 342 return ts.action(func(transferRunStatus *TransferRunStatus) error { 343 transferRunStatus.StaleChunks = staleChunks 344 return nil 345 }) 346 } 347 348 func (ts *TransferStateManager) GetStaleChunks() (staleChunks []StaleChunks, err error) { 349 return staleChunks, ts.action(func(transferRunStatus *TransferRunStatus) error { 350 staleChunks = transferRunStatus.StaleChunks 351 return nil 352 }) 353 } 354 355 func (ts *TransferStateManager) SaveStateAndSnapshots() error { 356 ts.TransferState.lastSaveTimestamp = time.Now() 357 if err := ts.persistTransferState(false); err != nil { 358 return err 359 } 360 // Save snapshots if needed. 361 if ts.repoTransferSnapshot == nil { 362 return nil 363 } 364 ts.repoTransferSnapshot.lastSaveTimestamp = time.Now() 365 if err := ts.repoTransferSnapshot.snapshotManager.PersistRepoSnapshot(); err != nil { 366 return err 367 } 368 return ts.persistTransferState(true) 369 } 370 371 type AlreadyLockedError struct{} 372 373 func (m *AlreadyLockedError) Error() string { 374 return "Files transfer is already running" 375 } 376 377 // Lock the state manager. We use the File Lock to acquire two purposes: 378 // 1. Make sure that only one transfer-files process is running 379 // 2. Check whether there is an active transfer-file process when providing the --status flag. We also extract the start timestamp (See 'GetStartTimestamp'). 380 func (ts *TransferStateManager) tryLockStateManager() error { 381 lockDirPath, err := coreutils.GetJfrogTransferLockDir() 382 if err != nil { 383 return err 384 } 385 startTimestamp, err := lock.GetLastLockTimestamp(lockDirPath) 386 if err != nil { 387 return err 388 } 389 if startTimestamp != 0 { 390 return errorutils.CheckError(new(AlreadyLockedError)) 391 } 392 unlockFunc, err := lock.CreateLock(lockDirPath) 393 if err != nil { 394 return err 395 } 396 ts.unlockStateManager = unlockFunc 397 return nil 398 } 399 400 func (ts *TransferStateManager) Running() (running bool, err error) { 401 lockDirPath, err := coreutils.GetJfrogTransferLockDir() 402 if err != nil { 403 return false, err 404 } 405 var startTimestamp int64 406 startTimestamp, err = lock.GetLastLockTimestamp(lockDirPath) 407 return err == nil && startTimestamp != 0, err 408 } 409 410 func (ts *TransferStateManager) InitStartTimestamp() (running bool, err error) { 411 if !ts.startTimestamp.IsZero() { 412 return true, nil 413 } 414 lockDirPath, err := coreutils.GetJfrogTransferLockDir() 415 if err != nil { 416 return false, err 417 } 418 var startTimestamp int64 419 startTimestamp, err = lock.GetLastLockTimestamp(lockDirPath) 420 if err != nil || startTimestamp == 0 { 421 return false, err 422 } 423 ts.startTimestamp = time.Unix(0, startTimestamp) 424 return true, nil 425 } 426 427 func (ts *TransferStateManager) GetRunningTimeString() (runningTime string) { 428 if ts.startTimestamp.IsZero() { 429 return "" 430 } 431 runningSecs := int64(time.Since(ts.startTimestamp).Seconds()) 432 return SecondsToLiteralTime(runningSecs, "") 433 } 434 435 func UpdateChunkInState(stateManager *TransferStateManager, chunk *api.ChunkStatus) (err error) { 436 var chunkTotalSizeInBytes int64 = 0 437 var chunkTotalFiles int64 = 0 438 for _, file := range chunk.Files { 439 if file.Status != api.Fail && file.Name != "" { 440 // Count only successfully transferred files 441 chunkTotalSizeInBytes += file.SizeBytes 442 chunkTotalFiles++ 443 } 444 } 445 switch stateManager.CurrentRepoPhase { 446 case api.Phase1: 447 err = stateManager.IncTransferredSizeAndFilesPhase1(chunkTotalFiles, chunkTotalSizeInBytes) 448 case api.Phase2: 449 err = stateManager.IncTransferredSizeAndFilesPhase2(chunkTotalFiles, chunkTotalSizeInBytes) 450 case api.Phase3: 451 err = stateManager.IncTransferredSizeAndFilesPhase3(chunkTotalFiles, chunkTotalSizeInBytes) 452 } 453 return err 454 } 455 456 func GetJfrogTransferRepoSnapshotDir(repoKey string) (string, error) { 457 return GetJfrogTransferRepoSubDir(repoKey, coreutils.JfrogTransferSnapshotDirName) 458 } 459 460 func GetRepoSnapshotFilePath(repoKey string) (string, error) { 461 snapshotDir, err := GetJfrogTransferRepoSnapshotDir(repoKey) 462 if err != nil { 463 return "", err 464 } 465 err = fileutils.CreateDirIfNotExist(snapshotDir) 466 if err != nil { 467 return "", err 468 } 469 return filepath.Join(snapshotDir, coreutils.JfrogTransferRepoSnapshotFileName), nil 470 }