/// <summary>
        /// Sets the app Id
        /// </summary>
        /// <param name="appId"></param>
        public void SetApp(int appId)
        {
            if (appId == 0)
            {
                throw new ArgumentException(nameof(appId));
            }

            //Check if app exists
            var        bytesAppIds = KeyValueStore.Get(KeyValueStore.DefaultType, KeyGenerator.AppsKey, null);
            List <int> appIds      = null;

            if (bytesAppIds != null)
            {
                appIds = (List <int>)BinarySerializerHelper.DeserializeObject(bytesAppIds);
            }

            if (appIds == null || !appIds.Contains(appId))
            {
                throw new ArgumentException($"{nameof(appId)} - doesn't exist");
            }

            AppId = appId;

            ForkProvider = new ForkProvider <TDataTypesEnum>(KeyValueStore, AppId);
        }
        /// <summary>
        /// Create new master fork with the data from an exsiting fork
        /// This is used to prune unused forks
        /// The data is trasferred to a new master fork to allow checking the data before deleting the old fork tree.
        /// </summary>
        /// <param name="forkId">Fork to prune</param>
        /// <returns>New master fork id with the data from the old fork</returns>
        public int PruneForks(int forkId)
        {
            var usedKeys    = new HashSet <string>();
            var deletedKeys = new HashSet <string>();
            var valuesToSet = new List <Tuple <string, TDataTypesEnum, byte[], object> >();

            var fork        = ForkProvider.GetFork(forkId);
            var currentFork = fork;

            while (currentFork != null)
            {
                var forkPattern = KeyGenerator.GenerateForkValuePattern(AppId, currentFork.Id);
                var keys        = KeyValueStore.Keys($"{forkPattern}*");

                foreach (var key in keys)
                {
                    var originalKey = key.Substring(forkPattern.Length);

                    // If the key was used/deleted in a lower fork, this key is not relevant
                    if (usedKeys.Contains(originalKey) || deletedKeys.Contains(originalKey))
                    {
                        continue;
                    }

                    if (originalKey.EndsWith(KeyGenerator.NullKeyPostFix))
                    {
                        deletedKeys.Add(originalKey.Substring(0, originalKey.Length - KeyGenerator.NullKeyPostFix.Length));
                    }
                    else
                    {
                        var keyDataCollection = KeyValueStore.GetKeyData(key);

                        foreach (var keyData in keyDataCollection)
                        {
                            valuesToSet.Add(Tuple.Create(originalKey, keyData.Item1, keyData.Item2, keyData.Item3));
                        }

                        usedKeys.Add(originalKey);
                    }
                }

                currentFork = currentFork.Parent;
            }

            var newForkId = CreateFork("master", $"Pruned from {fork.Id}:{fork.Name}");

            var wrapper = GetWrapper(newForkId);

            foreach (var value in valuesToSet)
            {
                wrapper.Set(value.Item2, value.Item1, value.Item3, value.Item4);
            }

            return(newForkId);
        }
        public ForksWrapper(IKeyValueStore <TDataTypesEnum> keyValueStore,
                            int appId, int forkId, ForkProvider <TDataTypesEnum> forkProvider)
        {
            _keyValueStore = keyValueStore ?? throw new ArgumentNullException(nameof(keyValueStore));
            _appId         = appId;

            if (!typeof(TDataTypesEnum).IsEnum)
            {
                throw new ArgumentException($"{nameof(TDataTypesEnum)} must be an enumerated type");
            }

            _forkProvider              = forkProvider;
            Fork                       = _forkProvider.GetFork(forkId);
            _forkProvider.ForkChanged += _forkProvider_ForkChanged;
        }
 public List <DTOs.Fork> GetMasterForks()
 {
     return(ForkProvider.GetMasterForks().Select(x => MapToDto(x)).ToList());
 }
        /// <summary>
        /// Merge all the keys from one fork to another fork, a new fork is created from the target with the new data
        /// </summary>
        /// <param name="originForkId">Origin fork id</param>
        /// <param name="targetForkId">Target fork id</param>
        /// <returns>New fork id, Target fork id is the parent</returns>
        public int MergeFork(int originForkId, int targetForkId)
        {
            var usedKeys     = new HashSet <string>();
            var valuesToSet  = new List <Tuple <string, TDataTypesEnum, byte[], object> >();
            var keysToDelete = new Dictionary <string, TDataTypesEnum>();

            var currentFork = ForkProvider.GetFork(originForkId);
            var targetFork  = ForkProvider.GetFork(targetForkId);

            while (!IsCommonParent(currentFork, targetFork))
            {
                if (currentFork == null)
                {
                    throw new ArgumentException($"No common parents between {originForkId} and {targetForkId}");
                }

                var forkPattern = KeyGenerator.GenerateForkValuePattern(AppId, currentFork.Id);
                var keys        = KeyValueStore.Keys($"{forkPattern}*");

                foreach (var key in keys)
                {
                    var originalKey = key.Substring(forkPattern.Length);

                    // If the key was used/deleted in a lower fork, this key is not relevant
                    if (usedKeys.Contains(originalKey) || keysToDelete.ContainsKey(originalKey))
                    {
                        continue;
                    }

                    if (originalKey.EndsWith(KeyGenerator.NullKeyPostFix))
                    {
                        var type = (TDataTypesEnum)BinarySerializerHelper.DeserializeObject(KeyValueStore.Get(KeyValueStore.DefaultType, key, null));
                        keysToDelete.Add(originalKey.Substring(0, originalKey.Length - KeyGenerator.NullKeyPostFix.Length), type);
                    }
                    else
                    {
                        var keyDataCollection = KeyValueStore.GetKeyData(key);

                        foreach (var keyData in keyDataCollection)
                        {
                            valuesToSet.Add(Tuple.Create(originalKey, keyData.Item1, keyData.Item2, keyData.Item3));
                        }

                        usedKeys.Add(originalKey);
                    }
                }

                currentFork = currentFork.Parent;
            }

            var newForkId = CreateFork($"{targetFork.Name} Merge", "", targetFork.Id);

            var wrapper = GetWrapper(newForkId);

            foreach (var keyToDelete in keysToDelete)
            {
                wrapper.Delete(keyToDelete.Value, keyToDelete.Key);
            }

            foreach (var value in valuesToSet)
            {
                wrapper.Set(value.Item2, value.Item1, value.Item3, value.Item4);
            }

            return(newForkId);
        }