Example #1
0
        async Task pushFileToMeta(List <MetaSegment.Command.FileOrigin> metaSegments, long fileSize, string remotePath, bool ignoreFile = false)
        {
            await metaSem.WaitAsync();

            try
            {
                // validate and resolve remotePath
                // MAYBE: move remotePath conversion out of here and replace here with validation after conversion
                if (Path.DirectorySeparatorChar != '/' && Path.AltDirectorySeparatorChar != '/')
                {
                    throw new NotImplementedException("remotePath separator must be '/'");
                }
                if (remotePath.Substring(0, 1) != "/")
                {
                    throw new ArgumentException("Invalid path. Must be absolute file path.");
                }
                remotePath = Path.GetFullPath(remotePath);
                if (remotePath.Substring(0, 1) != "/" || !Path.IsPathRooted(remotePath) || Path.GetFileName(remotePath) == "")
                {
                    throw new ArgumentException("Invalid path. Must be absolute file path.");
                }

                // convert to actual remote path
                // split path into partial paths: root (empty), directories (without leading or trailing slash) and file name
                var root          = "";
                var candidateDirs = ((Func <string, string[]>)(path =>
                {
                    var pp = Path.GetDirectoryName(path).Split(new char[] { '/' }, StringSplitOptions.RemoveEmptyEntries);
                    if (pp.Length == 0)
                    {
                        return(pp);
                    }
                    pp[0] = $"{pp[0]}";
                    for (var i = 1; i < pp.Length; i++)
                    {
                        pp[i] = $"{pp[i - 1]}/{pp[i]}";
                    }
                    return(pp);
                }))(remotePath);
                var file = remotePath.Substring(1);
                var allDirs = new string[] { root }.Concat(candidateDirs).ToArray();
                var all = allDirs.Concat(new[] { file }).ToArray();

                // check if we can push to meta at that remotePath.
                // if we want deletion or modification later, we need to iterate over the index here and aggregate meta
                foreach (var dir in candidateDirs)
                {
                    if (DB.SQLMap.CommandMetaType.File == db.MetaTypeAtPathInTransientCache(dir) ||
                        null != db.FindMatchingSegmentInAssurancesByIndexId(generator.GenerateMetaFileID(0, dir)))
                    {
                        throw new MetaEntryOverwriteException($"Directory '{dir}' would overwrite file at the same path.");
                    }
                }
                var metaTypeFile = db.MetaTypeAtPathInTransientCache(file);
                if (DB.SQLMap.CommandMetaType.Folder == metaTypeFile ||
                    null != db.FindMatchingSegmentInAssurancesByIndexId(generator.GenerateMetaFolderID(0, file)))
                {
                    throw new MetaEntryOverwriteException($"File '{file}' would overwrite folder at the same path.");
                }
                if (DB.SQLMap.CommandMetaType.File == metaTypeFile ||
                    null != db.FindMatchingSegmentInAssurancesByIndexId(generator.GenerateMetaFileID(0, file)))
                {
                    throw new MetaEntryOverwriteException($"File '{file}' would overwrite file at the same path.");
                }

                Constants.Logger.Log($"Pushing to meta: {remotePath}");

                var allDirsEx = allDirs.Select((d, i) => new { last = Path.GetFileName(d), full = d, i }).ToArray();

                var pushList = new List <DB.SQLMap.Command>();
                foreach (var dir in allDirsEx)
                {
                    var commands = new List <DB.SQLMap.Command>();
                    var i        = 0;
                    for (; ; i++)
                    {
                        var indexId = generator.GenerateMetaFolderID((uint)i, dir.full);
                        var seg     = db.FindMatchingSegmentInAssurancesByIndexId(indexId);
                        if (seg == null)
                        {
                            break;
                        }
                        var chunk = await DownloadChunk(indexId);                         // TODO cache

                        commands.AddRange(MetaSegment.FromByteArray(chunk).Commands
                                          .Select(c => c.ToDBObject())
                                          .Select(c =>
                        {
                            c.IsNew    = false;
                            c.Path     = dir.full;
                            c.MetaType = DB.SQLMap.CommandMetaType.Folder;
                            return(c);
                        })
                                          );
                    }
                    var commands2 = db.CommandsInTransientCache(dir.full);
                    if (commands2.Where(c => c.Index < i).FirstOrDefault() != null)
                    {
                        throw new InvalidDataException($"too small index in meta db cache for dir '{dir}'");
                    }

                    i += commands2.Count;

                    var allCommands = commands.Concat(commands2).ToArray();

                    if (dir.i != allDirsEx.Length - 1)
                    {
                        var next      = allDirsEx[dir.i + 1];
                        var hasFolder = null != allCommands.Where(c => c.MetaType == DB.SQLMap.CommandMetaType.Folder && c.FolderOrigin_Name == next.last).FirstOrDefault();
                        if (hasFolder)
                        {
                            continue;
                        }

                        // add folder to folder
                        pushList.Add(new DB.SQLMap.Command
                        {
                            IsNew             = true,
                            Path              = dir.full,
                            Index             = i,
                            MetaType          = DB.SQLMap.CommandMetaType.Folder,
                            CMD               = DB.SQLMap.Command.CMDV.ADD,
                            TYPE              = DB.SQLMap.Command.TYPEV.FOLDER,
                            FolderOrigin_Name = next.last,
                        });
                    }
                    else
                    {
                        if (!ignoreFile)
                        {
                            var fileName = Path.GetFileName(file);
                            var hasFile  = null != allCommands.Where(c => c.MetaType == DB.SQLMap.CommandMetaType.Folder && c.FolderOrigin_Name == fileName).FirstOrDefault();
                            if (hasFile)
                            {
                                throw new MetaEntryOverwriteException($"File '{file}' would overwrite file in parent folder.");
                            }

                            // add file to folder
                            pushList.Add(new DB.SQLMap.Command
                            {
                                IsNew                 = true,
                                Path                  = dir.full,
                                Index                 = i,
                                MetaType              = DB.SQLMap.CommandMetaType.Folder,
                                CMD                   = DB.SQLMap.Command.CMDV.ADD,
                                TYPE                  = DB.SQLMap.Command.TYPEV.FILE,
                                FolderOrigin_Name     = fileName,
                                FolderOrigin_FileSize = fileSize,
                            });
                        }
                    }
                }

                if (!ignoreFile)
                {
                    // add blocks to file
                    pushList.AddRange(metaSegments.Select((ms, i) => new DB.SQLMap.Command
                    {
                        IsNew            = true,
                        Path             = file,
                        Index            = i,
                        MetaType         = DB.SQLMap.CommandMetaType.File,
                        CMD              = DB.SQLMap.Command.CMDV.ADD,
                        TYPE             = DB.SQLMap.Command.TYPEV.BLOCK,
                        FileOrigin_Hash  = ms.Hash,
                        FileOrigin_Size  = ms.Size,
                        FileOrigin_Start = ms.Start,
                    }));
                }

                db.AddCommandsToTransientCache(pushList);
            }
            finally
            {
                metaSem.Release();
            }
        }
Example #2
0
        public async Task <Meta> DownloadMetaForPath(string path)
        {
            int  maxConcurrentTasks = 10;
            uint metaIndex          = 0;
            var  tasks = new List <Task <byte[]> >();

            var cmdsInTransientCache = db.CommandsInTransientCache(path);
            var first = cmdsInTransientCache.FirstOrDefault();

            var isFile = first?.MetaType == DB.SQLMap.CommandMetaType.File ||
                         null != db.FindMatchingSegmentInAssurancesByIndexId(generator.GenerateMetaFileID(0, path));

            if (!isFile)
            {
                var isFolder = first?.MetaType == DB.SQLMap.CommandMetaType.Folder ||
                               null != db.FindMatchingSegmentInAssurancesByIndexId(generator.GenerateMetaFolderID(0, path));
                if (!isFolder)
                {
                    return(null);
                }
            }

            for (; ; metaIndex++)
            {
                var indexId = isFile
                                        ? generator.GenerateMetaFileID(metaIndex, path)
                                        : generator.GenerateMetaFolderID(metaIndex, path);
                var seg = db.FindMatchingSegmentInAssurancesByIndexId(indexId);
                if (seg == null)
                {
                    break;
                }

                var task = DownloadChunk(indexId);
                tasks.Add(task);

                if (tasks.Count == maxConcurrentTasks)
                {
                    var t = await Task.WhenAny(tasks);

                    if (t.IsFaulted)
                    {
                        throw t.Exception;                                  // catch earlier by capping maxConcurrentTasks to connection limit?
                    }
                }
            }

            var combinedMeta = new Meta {
                Path = path, IsFile = isFile
            };

            var values = await Task.WhenAll(tasks);

            foreach (var cmds1 in values.Select(v => MetaSegment.FromByteArray(v).Commands))
            {
                combinedMeta.Commands.AddRange(cmds1);
            }

            var cmds2 = cmdsInTransientCache.OrderBy(c => c.Index).Select(x => x.ToProtoObject());

            combinedMeta.Commands.AddRange(cmds2);

            return(combinedMeta);
        }