github.com/jfrog/jfrog-cli-core/v2@v2.51.0/utils/reposnapshot/snapshotmanager.go (about) 1 package reposnapshot 2 3 import ( 4 "encoding/json" 5 "errors" 6 "fmt" 7 "strings" 8 9 "github.com/jfrog/gofrog/lru" 10 "github.com/jfrog/jfrog-client-go/utils/errorutils" 11 "github.com/jfrog/jfrog-client-go/utils/io/fileutils" 12 ) 13 14 // Represents a snapshot of a repository being traversed to do a certain action. 15 // Each directory in the repository is represented by a node. 16 // The snapshot is constructed as a linked prefix tree, where every node has pointers to its children and parent. 17 // While traversing over the repository, contents found are added to their respecting node. Later on, files handled are removed from their node. 18 // Each node has one of three states: 19 // 1. Unexplored / Partially explored - NOT all contents of the directory were found and added to its node (Marked by NodeStatus - Exploring). 20 // 2. Fully explored - All contents of the directory were found, but NOT all of them handled (Marked by NodeStatus - DoneExploring). 21 // 3. Completed - All contents found and handled (Marked by NodeStatus - Completed). 22 // 23 // In the event of a node reaching completion, a tree collapsing may occur in order to save space: 24 // The node will mark itself completed, and will then notify the parent to check completion as well. 25 // 26 // When resuming from a snapshot, all nodes that weren't completed are re-explored even if they were previously fully explored. 27 // Therefore, when persisting the snapshot to disk these fields are removed. 28 type RepoSnapshotManager struct { 29 repoKey string 30 // Pointer to the root node of the repository tree. 31 root *Node 32 lruCache *lru.Cache 33 // File path for saving the snapshot to and reading the snapshot from. 34 snapshotFilePath string 35 } 36 37 var cacheSize = 3000 38 39 // Loads a repo snapshot from the provided snapshotFilePath if such file exists. 40 // If successful, returns the snapshot and exists=true. 41 func LoadRepoSnapshotManager(repoKey, snapshotFilePath string) (RepoSnapshotManager, bool, error) { 42 exists, err := fileutils.IsFileExists(snapshotFilePath, false) 43 if err != nil || !exists { 44 return RepoSnapshotManager{}, false, err 45 } 46 47 root, err := loadAndConvertNodeTree(snapshotFilePath) 48 if err != nil { 49 return RepoSnapshotManager{}, false, err 50 } 51 return newRepoSnapshotManager(root, repoKey, snapshotFilePath), true, nil 52 } 53 54 func CreateRepoSnapshotManager(repoKey, snapshotFilePath string) RepoSnapshotManager { 55 return newRepoSnapshotManager(CreateNewNode(".", nil), repoKey, snapshotFilePath) 56 } 57 58 func newRepoSnapshotManager(root *Node, repoKey, snapshotFilePath string) RepoSnapshotManager { 59 return RepoSnapshotManager{ 60 root: root, 61 repoKey: repoKey, 62 snapshotFilePath: snapshotFilePath, 63 lruCache: lru.New(cacheSize, lru.WithoutSync()), 64 } 65 } 66 67 func loadAndConvertNodeTree(snapshotFilePath string) (root *Node, err error) { 68 content, err := fileutils.ReadFile(snapshotFilePath) 69 if err != nil { 70 return nil, err 71 } 72 73 var nodeWrapper NodeExportWrapper 74 err = json.Unmarshal(content, &nodeWrapper) 75 if err != nil { 76 return nil, errorutils.CheckError(err) 77 } 78 79 return nodeWrapper.convertToNode(), nil 80 } 81 82 func (sm *RepoSnapshotManager) PersistRepoSnapshot() error { 83 return sm.root.convertAndSaveToFile(sm.snapshotFilePath) 84 } 85 86 // Return the count and size of files that have been successfully transferred and their respective directories are marked as complete, 87 // ensuring they won't be transferred again. This data helps in estimating the remaining files for transfer after stopping. 88 func (sm *RepoSnapshotManager) CalculateTransferredFilesAndSize() (totalFilesCount uint32, totalFilesSize uint64, err error) { 89 return sm.root.CalculateTransferredFilesAndSize() 90 } 91 92 // Returns the node corresponding to the directory in the provided relative path. Path should be provided without the repository name. 93 func (sm *RepoSnapshotManager) LookUpNode(relativePath string) (requestedNode *Node, err error) { 94 if relativePath == "" { 95 return nil, errorutils.CheckErrorf(getLookUpNodeError(relativePath) + "- unexpected empty path provided to look up") 96 } 97 relativePath = strings.TrimSuffix(relativePath, "/") 98 if relativePath == "." { 99 requestedNode = sm.root 100 return 101 } 102 103 // Progress through the children maps till reaching the node that represents the requested path. 104 dirs := strings.Split(relativePath, "/") 105 requestedNode, err = sm.root.findMatchingNode(dirs) 106 if err != nil { 107 return nil, err 108 } 109 if requestedNode == nil { 110 return nil, errorutils.CheckErrorf(getLookUpNodeError(relativePath)) 111 } 112 return 113 } 114 115 // Returns the node that represents the directory from the repo state. Updates the lru cache. 116 // relativePath - relative path of the directory. 117 func (sm *RepoSnapshotManager) GetDirectorySnapshotNodeWithLru(relativePath string) (node *Node, err error) { 118 val, ok := sm.lruCache.Get(relativePath) 119 if ok { 120 if node, ok = val.(*Node); !ok { 121 return nil, errors.New("unexpected value in node lru cache") 122 } 123 return node, nil 124 } 125 126 // Otherwise, manually search for the node. 127 node, err = sm.LookUpNode(relativePath) 128 if err != nil { 129 return nil, err 130 } 131 132 // Add it to cache. 133 sm.lruCache.Add(relativePath, node) 134 return node, nil 135 } 136 137 func getLookUpNodeError(relativePath string) string { 138 return fmt.Sprintf("repo snapshot manager - could not reach the representing node for path '%s'", relativePath) 139 }