/// <summary> /// Creates a new instance of <see cref="Branch"/> (but does /// not load it). /// </summary> /// <param name="store">The command store that this branch is part of (not null)</param> /// <param name="ac">Metadata relating to the branch (not null)</param> /// <exception cref="ArgumentNullException"> /// The supplied command store or metadata is undefined</exception> internal Branch(CmdStore store, BranchInfo ac) { Store = store ?? throw new ArgumentNullException(nameof(store)); Info = ac ?? throw new ArgumentNullException(nameof(ac)); Commands = new List <CmdData>(); Parent = null; Children = new List <Branch>(); }
/// <summary> /// Performs the data processing associated with a command. /// </summary> public void Process(ExecutionContext context) { CmdStore cs = context?.Store ?? throw new ApplicationException("Undefined store"); uint numCmd = (Input as ICreateBranch).CommandCount; if (numCmd == 0) { throw new ApplicationException(nameof(numCmd)); } // Confirm that the name for the new branch is not a // duplicate (considering just the children of the // current branch) string name = (Input as ICreateBranch).Name; Branch parent = cs.Current; Branch oldBranch = parent.GetChild(name); if (oldBranch != null) { throw new ArgumentException( $"Branch {name} previously created at {oldBranch.Info.CreatedAt}"); } // Confirm that the new branch name is acceptable to the command store if (!cs.IsValidBranchName(name)) { throw new ArgumentException($"Branch name '{name}' is not allowed"); } var ac = new BranchInfo( storeId: cs.Id, parentId: parent.Id, branchId: Guid.NewGuid(), branchName: name, createdAt: Input.CreatedAt, updatedAt: Input.CreatedAt, commandDiscount: 1, refreshCount: numCmd); // Update internal structure to include the new branch var newBranch = new Branch(cs, ac) { Parent = parent }; parent.Children.Add(newBranch); cs.Branches.Add(ac.BranchId, newBranch); // Save the metadata for the new branch cs.SaveBranchInfo(newBranch); // And save the CreateBranch command itself newBranch.SaveData(Input); }
/// <summary> /// Performs the data processing associated with a command. /// </summary> /// <remarks>If a call to this method completes without any exception, /// a reference to the new store can be obtained using the /// <see cref="ExecutionContext.Store"/> property. /// </remarks> public void Process(ExecutionContext context) { if (context == null) { throw new ArgumentNullException(nameof(context)); } // Create the physical manifestation (if any) for the new store var store = CmdStore.CreateStore(Input); // Write the command data store.Current.SaveData(Input); context.Store = store; }
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"); }
public void Process(ExecutionContext context) { if (context == null) { throw new ArgumentNullException(nameof(context)); } // Create the new store var store = CmdStore.CreateStore(Input); // Write the command data // TODO: Does it need to be written anywhere? Perhaps to root metadata? // We shouldn't really touch any of the branches that have just been copied. //store.Current.SaveData(Input); context.Store = store; }
/// <summary> /// Initializes a new instance of the <see cref="ExecutionContext"/> class. /// </summary> /// <param name="store">The command store (not null).</param> /// <exception cref="ArgumentNullException"> /// The specified store is undefined.</exception> public ExecutionContext(CmdStore store) : this() { Store = store ?? throw new ArgumentNullException(); }
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); }
public void Process(ExecutionContext context) { Guid sourceId = (Input as IMerge).FromId; CmdStore cs = context.Store; Branch target = cs.Current; Branch source = cs.FindBranch(sourceId); if (source == null) { throw new ApplicationException($"Cannot locate branch {sourceId}"); } // Merges can only be done between branches that are part // of the local store (fetches from remote stores should // involve a totally different set of branches) //if (target.Info.StoreId != source.Info.StoreId) // throw new NotSupportedException("Attempt to merge with a remote store"); // Determine the first command that needs to be merged from // the source branch. The source branch doesn't know anything // about merges that have already been done, so figuring this // out involves scanning back from the end of the target branch // to locate the last merge (if any). // How far we go back depends on whether we're merging from // a child branch to its parent, or vice versa. If the target // is the child we go back as far as command 0. If the target // is the parent, we go back as far the command that followed // the branch start point. bool targetIsParent = target.Id.Equals(source.Info.ParentId); // Define the range of commands to be merged from the source uint minCmd = 0; if (targetIsParent) { // Merging into the parent, so start from the command immediately // after the last command that was previously merged (or 0 if this // is the first time the parent has merged from the child). if (target.Info.LastMerge.TryGetValue(sourceId, out MergeInfo mi)) { minCmd = mi.ChildCount; } } else { // Merging from the parent into the child, so start from the command // immediately after the last command that was previously merged (it // could potentially be 0) minCmd = target.Info.RefreshCount; } uint maxCmd = (uint)source.Info.CommandCount - 1; // TODO: Round about here we need to actually include the new stuff // as part of the current stream. Replaying the commands may then // lead to some sort of conflict (things are not guaranteed to work), // so there needs to be some way to preview the results. // Write the command data Input.Add(nameof(IMerge.MinCmd), minCmd); Input.Add(nameof(IMerge.MaxCmd), maxCmd); target.SaveData(Input); }