Exemple #1
0
        /// <summary>
        /// Saves the metadata for a branch that is part of this store.
        /// </summary>
        /// <param name="folderName">The path for the folder that holds branch data.</param>
        /// <param name="ac">The branch metadata to be saved.</param>
        internal static void SaveBranchInfo(string folderName, BranchInfo ac)
        {
            Directory.CreateDirectory(folderName);
            string data     = JsonConvert.SerializeObject(ac, Formatting.Indented);
            string fileName = Path.Combine(folderName, ac.BranchId + ".ac");

            File.WriteAllText(fileName, data);
        }
Exemple #2
0
 /// <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>
        /// Accepts data from another command store.
        /// </summary>
        /// <param name="source">A name that identifies the command store that is
        /// the source of the data.</param>
        /// <param name="ac">The metadata for the branch the commands are part of.</param>
        /// <param name="data">The command data to be appended to the remote branch.</param>
        void IRemoteStore.Push(string source, BranchInfo ac, CmdData[] data)
        {
            // Clone the supplied metadata (if the call actually comes from
            // the current application, we don't want to mutate the metadata)
            BranchInfo acCopy = ac.CreateCopy();

            string altName = acCopy.BranchName == "+" ? source : null;

            CopyIn(acCopy, data, altName);
        }
Exemple #4
0
        /// <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>
        /// Initializes a new instance of the <see cref="MemoryStore"/> class.
        /// </summary>
        /// <param name="rootAc">Metadata for the root branch</param>
        MemoryStore(BranchInfo rootAc)
            : base(String.Empty,
                   new StoreInfo(rootAc),
                   new BranchInfo[] { rootAc },
                   rootAc.BranchId)
        {
            AcFiles = new Dictionary <Guid, string>();
            Data    = new Dictionary <string, string>();

            SaveBranchInfo(rootAc);
        }
Exemple #6
0
        /// <summary>
        /// Prepares an instance of <see cref="CmdData"/> for
        /// the next command that will be appended to the
        /// current branch.
        /// </summary>
        /// <param name="commandName">A name that identifies
        /// the command type (a class name is one of potentially
        /// many choices)</param>
        /// <returns>An instance of command data that can
        /// be appended to the current branch.</returns>
        /// <remarks>
        /// The caller will usually append additional properties
        /// that are specific to the command.
        /// </remarks>
        public CmdData CreateCmdData(string commandName)
        {
            BranchInfo ac = Store.Current.Info;

            // Never return command data for a completed branch
            if (ac.IsCompleted)
            {
                throw new InvalidOperationException("Branch has already been completed");
            }

            // And never for a remote
            // May be doing a push (which you can do from any branch)
            //if (Store.Current.IsRemote)
            //    throw new InvalidOperationException("Unexpected attempt to append to a remote branch");

            return(new CmdData(
                       cmdName: commandName,
                       sequence: ac.CommandCount,
                       createdAt: DateTime.UtcNow));
        }
        void SaveBranchInfo(BranchInfo ac)
        {
            string data = JsonConvert.SerializeObject(ac, Formatting.Indented);
            string sql;
            int    nRows = 0;

            if (ac.CommandCount == 0)
            {
                Database.ExecuteTransaction(() =>
                {
                    // Create the data table for the new branch
                    sql = $"CREATE TABLE [{ac.BranchId}] " +
                          "(Sequence INTEGER NOT NULL PRIMARY KEY" +
                          ",Data JSON NOT NULL)";

                    Database.ExecuteNonQuery(sql);

                    // Create a view with a matching name (this is not needed by the
                    // software, but may make it easier to debug things)
                    string viewName = GetDataViewName(ac.BranchName);
                    sql             = $"CREATE VIEW [{viewName}] AS SELECT * FROM [{ac.BranchId}]";
                    Database.ExecuteNonQuery(sql);

                    // Record branch metadata
                    sql = $"INSERT INTO Branches (Id,Name,CreatedAt,Data) VALUES " +
                          $"('{ac.BranchId}', '{viewName}', '{ac.CreatedAt:o}', '{data}')";

                    nRows = Database.ExecuteNonQuery(sql);
                });
            }
            else
            {
                sql   = $"UPDATE Branches SET Data='{data}' WHERE Id='{ac.BranchId}'";
                nRows = Database.ExecuteNonQuery(sql);
            }

            if (nRows != 1)
            {
                throw new ApplicationException($"Update for branch changed {nRows} row`s".TrimExtras());
            }
        }
        internal static MemoryStore Create(CmdData args)
        {
            // Disallow an attempt to clone another memory store
            // TODO: How should the ICloneStore input reference another memory store?
            if (args.CmdName == nameof(ICloneStore))
            {
                throw new NotImplementedException(nameof(MemoryStore));
            }

            // Create the AC file that represents the store root branch
            Guid   storeId = args.GetGuid(nameof(ICreateStore.StoreId));
            string name    = args.GetValue <string>(nameof(ICreateStore.Name));

            var ac = new BranchInfo(storeId: storeId,
                                    parentId: Guid.Empty,
                                    branchId: storeId,
                                    branchName: name,
                                    createdAt: args.CreatedAt);

            return(new MemoryStore(ac));
        }
Exemple #9
0
        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);
        }
Exemple #10
0
        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);
        }
Exemple #11
0
        /// <summary>
        /// Copies in data from a remote branch.
        /// </summary>
        /// <param name="ac">The branch metadata received from a remote store
        /// (this will be used to replace any metadata already held locally).</param>
        /// <param name="data">The command data to append to the local branch.</param>
        /// <param name="altName">An alternative name to assign to the branch.</param>
        /// <exception cref="ApplicationException">
        /// Attempt to import remote data into a local branch</exception>
        public override void CopyIn(BranchInfo ac, CmdData[] data, string altName = null)
        {
            // The incoming data can only come from a remote store
            Debug.Assert(!ac.StoreId.Equals(this.Id));

            // Define the local location for the AC file (relative to the
            // location that should have already been defined for the parent).
            // There could be no parent if we're copying in command data from
            // the root branch.

            string dataFolder;
            Branch parent;

            if (ac.ParentId.Equals(Guid.Empty))
            {
                parent     = null;
                dataFolder = RootDirectoryName;
            }
            else
            {
                parent = FindBranch(ac.ParentId);
                if (parent == null)
                {
                    throw new ApplicationException("Cannot find parent branch " + ac.ParentId);
                }

                string parentDir = GetBranchDirectoryName(parent);
                dataFolder = Path.Combine(parentDir, altName ?? ac.BranchName);
            }

            // Save the supplied AC in its new location (if the branch already
            // exists locally, this will overwrite the AC)
            SaveBranchInfo(dataFolder, ac);

            Branch branch = FindBranch(ac.BranchId);

            if (branch == null)
            {
                Log.Trace($"Copying {data.Length} commands to {dataFolder}");

                Debug.Assert(data[0].Sequence == 0);
                Debug.Assert(data[0].CmdName == nameof(ICreateBranch));
                Debug.Assert(parent != null);

                // Create the new branch
                branch = new Branch(this, ac)
                {
                    Parent = parent
                };
                parent.Children.Add(branch);
                Branches.Add(ac.BranchId, branch);
            }
            else
            {
                Log.Trace($"Appending {data.Length} commands to {dataFolder}");

                if (!branch.IsRemote)
                {
                    throw new ApplicationException("Attempt to import remote data into a local branch");
                }

                // Replace the cached metadata
                branch.Info = ac;
            }

            // Copy over the command data
            foreach (CmdData cd in data)
            {
                WriteData(dataFolder, cd);
            }
        }
        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);
        }
        /// <summary>
        /// Saves the metadata for a branch that is part of this store.
        /// </summary>
        /// <param name="ac">The branch metadata to be saved.</param>
        void SaveBranchInfo(BranchInfo ac)
        {
            string data = JsonConvert.SerializeObject(ac, Formatting.Indented);

            AcFiles[ac.BranchId] = data;
        }
Exemple #14
0
 /// <summary>
 /// Copies in data from a new remote branch.
 /// </summary>
 /// <param name="ac">The branch metadata received from a remote store.</param>
 /// <param name="data">The command data for the branch</param>
 /// <param name="altName">An alternative name to assign to the branch.</param>
 public virtual void CopyIn(BranchInfo ac, CmdData[] data, string altName = null)
 {
     // Currently implemented only by FileStore
     throw new NotImplementedException();
 }
 /// <summary>
 /// Creates a new instance of <see cref="StoreInfo"/> for
 /// a brand new command store.
 /// </summary>
 /// <param name="ac">The branch metadata for the root branch</param>
 internal StoreInfo(BranchInfo ac)
     : this(ac.StoreId, ac.BranchName, Guid.Empty)
 {
 }