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  }