private void ProcessRemoteFile(
            StorageFile remoteFile,
            List <StorageItem> localItems,
            EncryptedFolder folder,
            SyncConcurrentQueue queue,
            IStorage localStorage,
            string localFolderPath)
        {
            var encryptedFile = folder.Files.FirstOrDefault(f => f.EncryptedName == remoteFile.Name);

            if (encryptedFile == null)
            {
                // remote file does not exists in db, we can't recrypt it
                // todo add sync error
                return;
            }

            var localPath  = Path.Combine(localFolderPath, encryptedFile.Name);
            var localItem  = localItems.OfType <StorageFile>().FirstOrDefault(l => l.Name == encryptedFile.Name);
            var itemStatus = GetItemStatus(localItem, remoteFile, encryptedFile);

            if (itemStatus != null)
            {
                queue.Enqueue(
                    new QueueItem
                {
                    Status        = itemStatus.Value,
                    LocalStorage  = localStorage,
                    LocalPath     = localPath,
                    RemotePath    = remoteFile.FullPath,
                    RemoteStorage = this.remoteStorage,
                    EncryptedFile = encryptedFile
                });
            }
        }
        public async Task FullBackup(CancellationToken cancellationToken)
        {
            var queue           = new SyncConcurrentQueue();
            var readFilesTask   = this.syncFoldersReader.Sync(queue, cancellationToken);
            var uploadItemsTask = this.syncQueueProcessor.ProcessQueue(queue, cancellationToken);

            await readFilesTask;

            this.syncQueueProcessor.ReaderFinished();
            await uploadItemsTask;
        }
        private void ProcessLocalFile(
            StorageFile localFile,
            List <StorageItem> remoteItems,
            EncryptedFolder folder,
            SyncConcurrentQueue queue,
            IStorage localStorage,
            string remoteFolderPath)
        {
            var encryptedFile = folder.Files.FirstOrDefault(f => f.Name == localFile.Name);

            if (encryptedFile == null)
            {
                // file doesn't exists in remote
                encryptedFile = new EncryptedFile
                {
                    Name            = localFile.Name,
                    Created         = localFile.Modified,
                    Modified        = localFile.Modified,
                    EncryptedName   = Guid.NewGuid().ToString(),
                    UnencryptedSize = localFile.Size,
                    // todo Hash =
                    FolderId = folder.Id,
                    Folder   = folder
                };
                encryptedFile = this.db.Files.Add(encryptedFile).Entity;
                this.db.SaveChangesAsync();
            }
            else
            {
                var remoteExists = remoteItems.Any(x => x.Name == encryptedFile.EncryptedName);
                if (remoteExists)
                {
                    // already processed at remote processing
                    return;
                }
            }

            queue.Enqueue(
                new QueueItem
            {
                Status        = QueueItemStatus.WaitingForUpload,
                LocalStorage  = localStorage,
                LocalPath     = localFile.FullPath,
                RemotePath    = Path.Combine(remoteFolderPath, encryptedFile.EncryptedName),
                RemoteStorage = this.remoteStorage,
                EncryptedFile = encryptedFile
            });
        }
        public async Task Sync(SyncConcurrentQueue queue, CancellationToken cancellationToken)
        {
            var device = await this.db.Devices
                         .Include(d => d.DeviceRootFolders)
                         .ThenInclude(d => d.Folder)
                         .Where(d => d.DeviceName == this.deviceName)
                         .FirstOrDefaultAsync(cancellationToken);

            if (device == null)
            {
                this.db.Devices.Add(new Device {
                    DeviceName = this.deviceName
                });
                await this.db.SaveChangesAsync(cancellationToken);

                return;
            }

            if (device.DeviceRootFolders.Count == 0)
            {
                return;
            }

            foreach (var deviceDeviceRootFolder in device.DeviceRootFolders)
            {
                await this.SyncFolder(
                    queue,
                    cancellationToken,
                    deviceDeviceRootFolder.Folder,
                    this.storageFactory.CreateLocalStorage(deviceDeviceRootFolder.RootPath));

                if (cancellationToken.IsCancellationRequested)
                {
                    return;
                }
            }
        }
        private async Task SyncFolder(
            SyncConcurrentQueue queue,
            CancellationToken cancellationToken,
            EncryptedFolder folder,
            IStorage localStorage)
        {
            var remoteFolderPath = folder.GetRelativeEncryptedPath();
            var localFolderPath  = folder.GetRelativePath();
            var remoteItems      = await this.remoteStorage.GetFolderChilds(remoteFolderPath);

            var localItems = await localStorage.GetFolderChilds(localFolderPath);

            foreach (var remoteItem in remoteItems)
            {
                if (cancellationToken.IsCancellationRequested)
                {
                    return;
                }

                switch (remoteItem)
                {
                case StorageFile remoteFile:
                    this.ProcessRemoteFile(
                        remoteFile,
                        localItems,
                        folder,
                        queue,
                        localStorage,
                        localFolderPath);
                    break;

                case StorageFolder remoteFolder:
                    var encryptedFolder = folder.Folders.FirstOrDefault(f => f.EncryptedName == remoteFolder.Name);
                    if (encryptedFolder == null)
                    {
                        // todo add sync error
                        continue;
                    }

                    await this.SyncFolder(queue, cancellationToken, encryptedFolder, localStorage);

                    break;

                default:
                    throw new NotImplementedException($"Failed to process emelent of type {remoteItem.GetType()}");
                }
            }

            foreach (var localItem in localItems)
            {
                if (cancellationToken.IsCancellationRequested)
                {
                    return;
                }

                switch (localItem)
                {
                case StorageFile localFile:
                    this.ProcessLocalFile(
                        localFile,
                        remoteItems,
                        folder,
                        queue,
                        localStorage,
                        remoteFolderPath);
                    break;

                case StorageFolder localFolder:
                    var encryptedFolder = folder.Folders.FirstOrDefault(f => f.Name == localFolder.Name);
                    if (encryptedFolder == null)
                    {
                        // new local folder
                        encryptedFolder = new EncryptedFolder
                        {
                            Name          = localFolder.Name,
                            EncryptedName = Guid.NewGuid().ToString(),
                            RootId        = folder.RootId ?? folder.Id,
                            Root          = folder.Root ?? folder,
                            ParentId      = folder.Id,
                            Parent        = folder,
                            Created       = DateTime.UtcNow,
                            Modified      = DateTime.UtcNow
                        };
                        encryptedFolder = this.db.Add(encryptedFolder).Entity;
                        await this.db.SaveChangesAsync(cancellationToken);
                    }
                    else
                    {
                        var remoteFolderExists = remoteItems.OfType <StorageFolder>()
                                                 .Any(f => f.Name == encryptedFolder.EncryptedName);
                        if (remoteFolderExists)
                        {
                            // skip existing remote folders
                            continue;
                        }
                    }

                    await SyncFolder(queue, cancellationToken, encryptedFolder, localStorage);

                    break;

                default:
                    throw new NotImplementedException($"Failed to process emelent of type {localItem.GetType()}");
                }
            }
        }