/// <summary> /// Saves the supplied store metadata. /// </summary> static void SaveStoreInfo(string rootDirectoryName, StoreInfo storeInfo) { if (rootDirectoryName == null) { throw new ArgumentNullException("Cannot save store info because location is undefined"); } Directory.CreateDirectory(rootDirectoryName); string data = JsonConvert.SerializeObject(storeInfo, Formatting.Indented); string fileName = Path.Combine(rootDirectoryName, StoreInfoName); File.WriteAllText(fileName, data); }
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 the number of commands we have in local branches IdCount[] have = cs.Branches.Values .Where(x => !x.IsRemote) .Select(x => new IdCount(x.Id, x.Info.CommandCount)) .ToArray(); IRemoteStore rs = context.GetRemoteStore(upLoc); // We don't care about new branches in the origin, but we do care // about local branches that have been created since the last push IdRange[] toPush = rs.GetMissingRanges(cs.Id, have, false).ToArray(); // How many commands do we need to push uint total = (uint)toPush.Sum(x => x.Size); Log.Info($"To push {total} command`s in {toPush.Length} branch`es".TrimExtras()); foreach (IdRange idr in toPush) { Branch b = cs.FindBranch(idr.Id); if (b == null) { throw new ApplicationException("Cannot locate branch " + idr.Id); } Log.Info($"Push [{idr.Min},{idr.Max}] from {b}"); CmdData[] data = b.TakeRange(idr.Min, idr.Max).ToArray(); rs.Push(cs.Name, b.Info, data); // Remember how much we were ahead at the time of the push b.Info.LastPush = idr.Max + 1; b.Store.SaveBranchInfo(b); } Log.Info("Push completed"); }
/// <summary> /// Initializes a new instance of the <see cref="CmdStore"/> class. /// </summary> /// <param name="home">Some sort of identifier relating to the place where /// the store resides</param> /// <param name="storeInfo">Store metadata.</param> /// <param name="acInfo">Metadata for the branches in the store</param> /// <param name="currentId">The ID of the currently checked out branch</param> protected CmdStore(string home, StoreInfo storeInfo, BranchInfo[] acInfo, Guid currentId) { if (acInfo == null || acInfo.Length == 0) { throw new ArgumentException(nameof(acInfo)); } Home = home; // Obtain the store name from the root folder Store = storeInfo; Log.Trace($"Initializing store {Id}, Name={Name}"); Branches = acInfo.ToDictionary(x => x.BranchId, x => new Branch(this, x)); // Create branch objects and form parent/child relationships foreach (Branch b in Branches.Values) { Guid parentId = b.Info.ParentId; if (Branches.TryGetValue(parentId, out Branch p)) { b.Parent = p; p.Children.Add(b); } else { // We should have located the parent for all branches // except for the root branch if (!Guid.Empty.Equals(parentId)) { throw new ApplicationException($"Could not locate parent for {b.Info.BranchId}"); } } } if (!Branches.TryGetValue(currentId, out Branch current)) { throw new ApplicationException($"Cannot locate current branch"); } Current = current; }
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); }
internal static SQLiteStore Create(CmdData args) { // Disallow store names that correspond to tables in the database (bear // in mind that the entered name may or may not include a directory path) string enteredName = args.GetValue <string>(nameof(ICreateStore.Name)); string name = Path.GetFileNameWithoutExtension(enteredName); if (IsReservedName(name)) { throw new ApplicationException("Store name not allowed"); } // Expand the supplied name to include the current working directory (or // expand a relative path) string fullSpec = Path.GetFullPath(enteredName); string fileType = Path.GetExtension(fullSpec); if (String.IsNullOrEmpty(fileType)) { fullSpec += ".ac-sqlite"; } // Disallow if the database file already exists if (File.Exists(fullSpec)) { throw new ApplicationException($"{fullSpec} already exists"); } // Confirm the file is on a local drive if (!IsLocalDrive(fullSpec)) { throw new ApplicationException("Command stores can only be initialized on a local fixed drive"); } // Ensure the folder exists string folderName = Path.GetDirectoryName(fullSpec); if (!Directory.Exists(folderName)) { Log.Trace("Creating " + folderName); Directory.CreateDirectory(folderName); } Guid storeId = args.GetGuid(nameof(ICreateStore.StoreId)); SQLiteStore result = null; if (args.CmdName == nameof(ICloneStore)) { // Copy the SQLite database // TODO: To handle database files on remote machines ICloneStore cs = (args as ICloneStore); Log.Info($"Copying {cs.Origin} to {fullSpec}"); File.Copy(cs.Origin, fullSpec); // Load the copied store result = SQLiteStore.Load(fullSpec, cs); } else { Log.Info("Creating " + fullSpec); SQLiteDatabase db = CreateDatabase(fullSpec); // 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 SQLiteStore(storeInfo, new BranchInfo[] { ac }, ac.BranchId, db); // Save the info for the master branch plus the store metadata result.SaveBranchInfo(ac); result.SaveStoreInfo(); // The last branch is the root of the new database result.SaveProperty(PropertyNaming.LastBranch, storeId.ToString()); } return(result); }