github.com/jfrog/jfrog-cli-core/v2@v2.52.0/artifactory/commands/transferfiles/state/state.go (about) 1 package state 2 3 import ( 4 "encoding/json" 5 "github.com/jfrog/jfrog-cli-core/v2/utils/coreutils" 6 "github.com/jfrog/jfrog-client-go/utils/errorutils" 7 "github.com/jfrog/jfrog-client-go/utils/io/fileutils" 8 "github.com/jfrog/jfrog-client-go/utils/log" 9 "os" 10 "path/filepath" 11 "sync" 12 "time" 13 ) 14 15 var saveStateMutex sync.Mutex 16 17 type ActionOnStateFunc func(state *TransferState) error 18 19 const ( 20 transferStateFileVersion = 0 21 transferStateFileInvalidVersionErrorMsg = "unexpected repository state file found" 22 ) 23 24 // This struct holds the state of the current repository being transferred from the source Artifactory instance. 25 // It is saved to a file located in a directory named with the sha of the repository, under the transfer directory. 26 // The transfer-files command uses this state to determine which phases should be executed for the repository, 27 // as well as other decisions related to the process execution. 28 type TransferState struct { 29 lastSaveTimestamp time.Time 30 // The Version of the state.json file of a repository. 31 Version int `json:"state_version,omitempty"` 32 CurrentRepo Repository `json:"repository,omitempty"` 33 } 34 35 type Repository struct { 36 Phase1Info ProgressState `json:"phase1_info,omitempty"` 37 Phase2Info ProgressState `json:"phase2_info,omitempty"` 38 Phase3Info ProgressState `json:"phase3_info,omitempty"` 39 Name string `json:"name,omitempty"` 40 FullTransfer PhaseDetails `json:"full_transfer,omitempty"` 41 Diffs []DiffDetails `json:"diffs,omitempty"` 42 } 43 44 type PhaseDetails struct { 45 Started string `json:"started,omitempty"` 46 Ended string `json:"ended,omitempty"` 47 } 48 49 type DiffDetails struct { 50 // The start and end time of a complete transferring Files Diff phase 51 FilesDiffRunTime PhaseDetails `json:"files_diff,omitempty"` 52 // The start and end time of the last handled range 53 HandledRange PhaseDetails `json:"handled_range,omitempty"` 54 // If false, start the Diff phase from the start time of the full transfer 55 Completed bool `json:"completed,omitempty"` 56 } 57 58 func newRepositoryTransferState(repoKey string) TransferState { 59 return TransferState{ 60 Version: transferStateFileVersion, 61 CurrentRepo: Repository{Name: repoKey}, 62 } 63 } 64 65 func (ts *TransferState) Action(action ActionOnStateFunc) error { 66 if err := action(ts); err != nil { 67 return err 68 } 69 70 now := time.Now() 71 if now.Sub(ts.lastSaveTimestamp).Seconds() < float64(stateAndStatusSaveIntervalSecs) { 72 return nil 73 } 74 75 if !saveStateMutex.TryLock() { 76 return nil 77 } 78 defer saveStateMutex.Unlock() 79 80 ts.lastSaveTimestamp = now 81 return ts.persistTransferState(false) 82 } 83 84 // Persist TransferState to file, at the repository dir. If snapshot requested, persist it to the repository's snapshot dir. 85 func (ts *TransferState) persistTransferState(snapshot bool) (err error) { 86 repoStateFilePath, err := GetRepoStateFilepath(ts.CurrentRepo.Name, snapshot) 87 if err != nil { 88 return err 89 } 90 91 content, err := json.Marshal(ts) 92 if err != nil { 93 return errorutils.CheckError(err) 94 } 95 96 err = os.WriteFile(repoStateFilePath, content, 0600) 97 if err != nil { 98 return errorutils.CheckError(err) 99 } 100 return nil 101 } 102 103 func (ts *TransferState) IsRepoTransferred() (isTransferred bool, err error) { 104 return isTransferred, ts.Action(func(state *TransferState) error { 105 isTransferred = state.CurrentRepo.FullTransfer.Ended != "" 106 return nil 107 }) 108 } 109 110 func LoadTransferState(repoKey string, snapshot bool) (transferState TransferState, exists bool, err error) { 111 stateFilePath, err := GetRepoStateFilepath(repoKey, snapshot) 112 if err != nil { 113 return 114 } 115 exists, err = fileutils.IsFileExists(stateFilePath, false) 116 if err != nil || !exists { 117 return 118 } 119 120 content, err := fileutils.ReadFile(stateFilePath) 121 if err != nil { 122 return 123 } 124 125 if err = json.Unmarshal(content, &transferState); errorutils.CheckError(err) != nil { 126 return 127 } 128 if transferState.Version != transferStateFileVersion { 129 return TransferState{}, false, errorutils.CheckErrorf(transferStateFileInvalidVersionErrorMsg) 130 } 131 return 132 } 133 134 func GetRepoStateFilepath(repoKey string, snapshot bool) (string, error) { 135 var dirPath string 136 var err error 137 if snapshot { 138 dirPath, err = GetJfrogTransferRepoSnapshotDir(repoKey) 139 } else { 140 dirPath, err = GetRepositoryTransferDir(repoKey) 141 } 142 if err != nil { 143 return "", err 144 } 145 return filepath.Join(dirPath, coreutils.JfrogTransferRepoStateFileName), nil 146 } 147 148 // Returns a transfer state and repo transfer snapshot according to the state of the repository as found in the repository transfer directory. 149 // A repo transfer snapshot is only returned if running phase 1 is required. 150 // The state and snapshot will be loaded from snapshot dir if a previous run of phase 1 was interrupted, and reset was not required. 151 func getTransferStateAndSnapshot(repoKey string, reset bool) (transferState TransferState, repoTransferSnapshot *RepoTransferSnapshot, err error) { 152 if reset { 153 return getCleanStateAndSnapshot(repoKey) 154 } 155 156 // Check if repo state exists. If not, start clean. 157 transferState, exists, err := LoadTransferState(repoKey, false) 158 if err != nil { 159 return 160 } 161 if !exists { 162 return getCleanStateAndSnapshot(repoKey) 163 } 164 165 // If it exists and repo already fully completed phase 1, load current state. 166 transferred, err := transferState.IsRepoTransferred() 167 if err != nil || transferred { 168 return transferState, nil, err 169 } 170 171 // Phase 1 was started and not completed. Try loading snapshots to continue from the same point. 172 return loadRepoSnapshots(repoKey) 173 } 174 175 // Loads the state and repo snapshots from the repository's snapshot directory. 176 func loadRepoSnapshots(repoKey string) (transferState TransferState, repoTransferSnapshot *RepoTransferSnapshot, err error) { 177 transferState, stateExists, err := LoadTransferState(repoKey, true) 178 if err != nil { 179 return 180 } 181 snapshotPath, err := GetRepoSnapshotFilePath(repoKey) 182 if err != nil { 183 return 184 } 185 repoTransferSnapshot, snapshotExists, err := loadRepoTransferSnapshot(repoKey, snapshotPath) 186 if !stateExists || !snapshotExists { 187 log.Info("An attempt to transfer repository '" + repoKey + "' was previously stopped but no snapshot was found to continue from. " + 188 "Starting to transfer from scratch...") 189 return getCleanStateAndSnapshot(repoKey) 190 } 191 return 192 } 193 194 func getCleanStateAndSnapshot(repoKey string) (transferState TransferState, repoTransferSnapshot *RepoTransferSnapshot, err error) { 195 snapshotFilePath, err := GetRepoSnapshotFilePath(repoKey) 196 if err != nil { 197 return 198 } 199 return newRepositoryTransferState(repoKey), createRepoTransferSnapshot(repoKey, snapshotFilePath), nil 200 }