public void Process(ExecutionContext context) { CmdStore cs = context.Store; StoreInfo storeInfo = cs.Store; string upLoc = storeInfo.UpstreamLocation; if (String.IsNullOrEmpty(upLoc)) { throw new ApplicationException("There is no upstream location"); } // Collate how much we already have in all remote branches. // We may have previously pushed some local branches to the remote // store, but nothing is supposed to mutate those remote copies. IdCount[] have = cs.Branches.Values .Where(x => x.IsRemote) .Select(x => new IdCount(x.Id, x.Info.CommandCount)) .ToArray(); // Open a channel to the upstream store IRemoteStore rs = context.GetRemoteStore(upLoc); // Determine what we are missing (including any new branches in the remote) IdRange[] toFetch = rs.GetMissingRanges(cs.Id, have, true); // How many commands do we need to fetch uint total = (uint)toFetch.Sum(x => x.Size); Log.Info($"To fetch {total} command`s from {toFetch.Length} branch`es".TrimExtras()); // Retrieve the command data from the remote, keeping new branches // apart from appends to existing branches. var newBranchData = new Dictionary <BranchInfo, CmdData[]>(); var moreBranchData = new Dictionary <BranchInfo, CmdData[]>(); foreach (IdRange idr in toFetch) { // Fetch the remote AC file BranchInfo ac = rs.GetBranchInfo(idr.Id); if (ac == null) { throw new ApplicationException("Could not locate remote branch " + idr.Id); } // And the relevant data Log.Info($"Fetch [{idr.Min},{idr.Max}] for {ac.BranchName} ({ac.BranchId})"); CmdData[] branchData = rs.GetData(idr).ToArray(); if (cs.FindBranch(ac.BranchId) == null) { newBranchData.Add(ac, branchData); } else { moreBranchData.Add(ac, branchData); } } // All done with the remote store // Copy any brand new branches (ensuring they get created in the // right order so that parent/child relationships can be formed // as we go). foreach (KeyValuePair <BranchInfo, CmdData[]> kvp in newBranchData.OrderBy(x => x.Key.CreatedAt)) { cs.CopyIn(kvp.Key, kvp.Value); } // Append command data for branches we previously had (the order // shouldn't matter) foreach (KeyValuePair <BranchInfo, CmdData[]> kvp in moreBranchData) { cs.CopyIn(kvp.Key, kvp.Value); } Log.Info("Fetch completed"); // Reload the current command stream (from scratch, kind of brute force, // not sure whether appending the new commands would really be sufficient) // TODO: Is this really necessary? Perhaps only if the current branch has // been fetched (the stuff we're fetching should only come from remotes, // but the current branch could be one of those remotes) //cs.Stream = new CmdStream(cs.Current); }
internal static FileStore Create(CmdData args) { // Expand the supplied name to include the current working directory (or // expand a relative path) string enteredName = args.GetValue <string>(nameof(ICreateStore.Name)); string name = Path.GetFileNameWithoutExtension(enteredName); string folderName = Path.GetFullPath(enteredName); // Disallow if the folder name already exists. // It may be worth relaxing this rule at some future date. The // reason for disallowing it is because an existing folder may // well contain sub-folders, but we also use sub-folders to // represent the branch hierarchy. So things would be a bit // mixed up. That said, it would be perfectly plausible to // place branch sub-folders under a separate ".ac" folder (in // much the same way as git). That might be worth considering // if we want to support a "working directory" like that // provided by git. if (Directory.Exists(folderName)) { throw new ApplicationException($"{folderName} already exists"); } // Confirm the folder is on a local drive if (!IsLocalDrive(folderName)) { throw new ApplicationException("Command stores can only be initialized on a local fixed drive"); } // Create the folder for the store (but if the folder already exists, // confirm that it does not already hold any AC files). if (Directory.Exists(folderName)) { if (GetAcFilePath(folderName) != null) { throw new ApplicationException($"{folderName} has already been initialized"); } } else { Log.Info("Creating " + folderName); Directory.CreateDirectory(folderName); } Guid storeId = args.GetGuid(nameof(ICreateStore.StoreId)); FileStore result = null; // If we're cloning, copy over the source data if (args.CmdName == nameof(ICloneStore)) { // TODO: When cloning from a real remote, using wget might be a // good choice (but that doesn't really fit with what's below) ICloneStore cs = (args as ICloneStore); IRemoteStore rs = GetRemoteStore(cs); // Retrieve metadata for all remote branches BranchInfo[] acs = rs.GetBranches() .OrderBy(x => x.CreatedAt) .ToArray(); var branchFolders = new Dictionary <Guid, string>(); // Copy over all branches foreach (BranchInfo ac in acs) { // Get the data for the branch (do this before we do any tweaking // of the folder path from the AC file -- if the command supplier is // a local file store, we won't be able to read the command data files // after changing the folder name recorded in the AC) IdRange range = new IdRange(ac.BranchId, 0, ac.CommandCount - 1); CmdData[] data = rs.GetData(range).ToArray(); // TODO: what follows is very similar to the CopyIn method. // Consider using that instead (means an empty FileStore needs // to be created up front) // Determine the output location for the AC file (relative to the // location that should have already been defined for the parent) if (!branchFolders.TryGetValue(ac.ParentId, out string parentFolder)) { Debug.Assert(ac.ParentId.Equals(Guid.Empty)); parentFolder = folderName; } string dataFolder = Path.Combine(parentFolder, ac.BranchName); branchFolders[ac.BranchId] = dataFolder; SaveBranchInfo(dataFolder, ac); // Copy over the command data foreach (CmdData cd in data) { FileStore.WriteData(dataFolder, cd); } } // If the origin is a folder on the local file system, ensure it's // saved as an absolute path (relative specs may confuse directory // navigation, depending on what the current directory is at the time) string origin = cs.Origin; if (Directory.Exists(origin)) { origin = Path.GetFullPath(origin); } // Save the store metadata var root = new StoreInfo(storeId, name, rs.Id, origin); SaveStoreInfo(folderName, root); // And suck it back up again string acSpec = Path.Combine(folderName, acs[0].BranchId + ".ac"); result = FileStore.Load(acSpec); } else { // Create the AC file that represents the store root branch var ac = new BranchInfo(storeId: storeId, parentId: Guid.Empty, branchId: storeId, branchName: name, createdAt: args.CreatedAt); // Create the store metadata var storeInfo = new StoreInfo(storeId, name, Guid.Empty); // Create the store and save it result = new FileStore(folderName, storeInfo, new BranchInfo[] { ac }, ac.BranchId); // Save the AC file plus the store metadata FileStore.SaveBranchInfo(folderName, ac); result.SaveStoreInfo(); } return(result); }