private async Task <IFile> CreateFileAsync(AdapterHolder adapterHolder, string key, CancellationToken cancellationToken)
        {
            if (key == null)
            {
                throw new ArgumentNullException(nameof(key));
            }

            var file =
                await FileRegister.GetOrDefaultAsync(adapterHolder.Scheme, key, cancellationToken);

            if (file != null)
            {
                return(file);
            }

            var adapter = await adapterHolder.GetAdapterAsync();

            if (adapter is IFileFactory fileFactory)
            {
                file = await fileFactory.CreateFileAsync(key, this, cancellationToken);
            }
            else
            {
                file = new File(adapterHolder.Scheme, key, this);
            }

            await FileRegister.StoreAsync(adapterHolder.Scheme, key, file, cancellationToken);

            return(file);
        }
        private Task WriteAsync(AdapterHolder adapterHolder, string key, byte[] bytes, bool overwrite, CancellationToken cancellationToken)
        {
            if (key == null)
            {
                throw new ArgumentNullException(nameof(key));
            }

            return(WriteAsyncImpl());

            async Task WriteAsyncImpl()
            {
                if (!overwrite)
                {
                    var exists = await ExistsAsync(adapterHolder, key, cancellationToken);

                    if (exists)
                    {
                        throw new FileAlreadyExistsException(key); //TODO Message
                    }
                }

                var adapter = await adapterHolder.GetAdapterAsync();

                await adapter.WriteAsync(key, bytes, overwrite, cancellationToken);
            }
        }
        private Task <byte[]> ReadAllBytesAsync(AdapterHolder adapterHolder, string key, CancellationToken cancellationToken)
        {
            if (key == null)
            {
                throw new ArgumentNullException(nameof(key));
            }

            return(AdapterReadAllBytesAsync());

            async Task <byte[]> AdapterReadAllBytesAsync()
            {
                await ThrowIfNotExistsAsync(adapterHolder, key, cancellationToken);

                var adapter = await adapterHolder.GetAdapterAsync();

                var content = await adapter.ReadAllBytesAsync(key, cancellationToken);

                if (content == null)
                {
                    throw new IOException($"Could not read the '{key} key content'");
                }

                return(content);
            }
        }
        private Task ReadToStreamAsync(AdapterHolder adapterHolder, string key, Stream toStream, CancellationToken cancellationToken)
        {
            if (key == null)
            {
                throw new ArgumentNullException(nameof(key));
            }

            return(ReadAllBytesAndCreateStreamAsync());

            async Task ReadAllBytesAndCreateStreamAsync()
            {
                var adapter = await adapterHolder.GetAdapterAsync();

                if (adapter is IStreamFactory streamFactory)
                {
                    await streamFactory.ReadToStreamAsync(key, toStream, cancellationToken);
                }
                else
                {
                    var bytes = await ReadAllBytesAsync(adapter, key, cancellationToken);

                    using (var memoryBytes = new MemoryStream(bytes))
                        await memoryBytes.CopyToAsync(toStream, cancellationToken);
                }
            }
        }
        private Task <string> GetChecksumAsync(AdapterHolder adapterHolder, string key, CancellationToken cancellationToken)
        {
            if (key == null)
            {
                throw new ArgumentNullException(nameof(key));
            }

            return(GetChecksumAsyncImpl());

            async Task <string> GetChecksumAsyncImpl()
            {
                var adapter = await adapterHolder.GetAdapterAsync();

                await ThrowIfNotExistsAsync(adapter, key, cancellationToken);

                if (adapter is IChecksumCalculator checksumCalculator)
                {
                    return(await checksumCalculator.GetChecksumAsync(key, cancellationToken));
                }

                var bytes = await ReadAllBytesAsync(adapter, key, cancellationToken);

                return(ChecksumHelper.FromContent(bytes));
            }
        }
        private Task <long> GetSizeAsync(AdapterHolder adapterHolder, string key, CancellationToken cancellationToken)
        {
            if (key == null)
            {
                throw new ArgumentNullException(nameof(key));
            }

            return(ReadAllBytesAndGetLengthAsync());

            async Task <long> ReadAllBytesAndGetLengthAsync()
            {
                var adapter = await adapterHolder.GetAdapterAsync();

                await ThrowIfNotExistsAsync(adapter, key, cancellationToken);

                if (adapter is ISizeCalculator sizeCalculator)
                {
                    return(await sizeCalculator.GetSizeAsync(key, cancellationToken));
                }

                var bytes = await ReadAllBytesAsync(adapter, key, cancellationToken);

                return(bytes.LongLength);
            }
        }
        private async Task ThrowIfNotExistsAsync(AdapterHolder adapterHolder, string key, CancellationToken cancellationToken)
        {
            var exists = await ExistsAsync(adapterHolder, key, cancellationToken);

            if (!exists)
            {
                var adapter = await adapterHolder.GetAdapterAsync();

                throw new FileNotFoundException($"FileSystem '{adapter.Scheme.Name}' could not find a file with key '{key}'", key);
            }
        }
        private Task <bool> ExistsAsync(AdapterHolder adapterHolder, string key, CancellationToken cancellationToken)
        {
            if (key == null)
            {
                throw new ArgumentNullException(nameof(key));
            }

            return(ExistsAsyncImpl());

            async Task <bool> ExistsAsyncImpl()
            {
                var adapter = await adapterHolder.GetAdapterAsync();

                var exists = await adapter.ExistsAsync(key, cancellationToken);

                return(exists);
            }
        }
        private Task <IFile> GetAsync(AdapterHolder adapterHolder, string key, bool create, CancellationToken cancellationToken)
        {
            if (key == null)
            {
                throw new ArgumentNullException(nameof(key));
            }

            return(GetFileOrCreateOrThrowAsync());

            async Task <IFile> GetFileOrCreateOrThrowAsync()
            {
                if (!create)
                {
                    await ThrowIfNotExistsAsync(adapterHolder, key, cancellationToken);
                }

                return(await CreateFileAsync(adapterHolder, key, cancellationToken));
            }
        }
        private Task RenameAsync(AdapterHolder sourceAdapterHolder, string sourceKey, AdapterHolder targetAdapterHolder, string targetKey, CancellationToken cancellationToken)
        {
            if (sourceKey == null)
            {
                throw new ArgumentNullException(nameof(sourceKey));
            }
            if (targetKey == null)
            {
                throw new ArgumentNullException(nameof(targetKey));
            }

            return(CheckTargetExistenceAndRenameAsync());

            async Task CheckTargetExistenceAndRenameAsync()
            {
                await ThrowIfNotExistsAsync(sourceAdapterHolder, sourceKey, cancellationToken);

                var exists = await ExistsAsync(targetAdapterHolder, targetKey, cancellationToken);

                if (exists)
                {
                    throw new UnexpectedFileException(targetKey); //TODO Message
                }
                var adapter = await targetAdapterHolder.GetAdapterAsync();

                var renamedSuccess = await adapter.RenameAsync(sourceKey, targetKey, cancellationToken);

                if (!renamedSuccess)
                {
                    throw new IOException($"Could not rename the '{sourceKey}' key to '{targetKey}'");
                }

                var sourceFile =
                    await FileRegister.GetOrDefaultAsync(sourceAdapterHolder.Scheme, sourceKey, cancellationToken);

                if (sourceFile != null)
                {
                    await FileRegister.StoreAsync(targetAdapterHolder.Scheme, targetKey, sourceFile, cancellationToken);

                    await FileRegister.RemoveAsync(sourceAdapterHolder.Scheme, sourceKey, cancellationToken);
                }
            }
        }
        private Task DeleteAsync(AdapterHolder adapterHolder, string key, CancellationToken cancellationToken)
        {
            if (key == null)
            {
                throw new ArgumentNullException(nameof(key));
            }

            return(AdapterDeleteAndRemoveFromFileRegisterAsync());

            async Task AdapterDeleteAndRemoveFromFileRegisterAsync()
            {
                var adapter = await adapterHolder.GetAdapterAsync();

                await ThrowIfNotExistsAsync(adapter, key, cancellationToken);

                await adapter.DeleteAsync(key, cancellationToken);

                await FileRegister.RemoveAsync(adapterHolder.Scheme, key, cancellationToken);
            }
        }
        private Task <DateTime> GetLastModificationDateAsync(AdapterHolder adapterHolder, string key, CancellationToken cancellationToken)
        {
            if (key == null)
            {
                throw new ArgumentNullException(nameof(key));
            }

            return(GetLastModificationDateAsyncImpl());

            async Task <DateTime> GetLastModificationDateAsyncImpl()
            {
                var adapter = await adapterHolder.GetAdapterAsync();

                await ThrowIfNotExistsAsync(adapter, key, cancellationToken);

                var date = await adapter.GetLastModificationDateAsync(key, cancellationToken);

                return(date);
            }
        }
        private Task <string> GetMimeTypeAsync(AdapterHolder adapterHolder, string key, CancellationToken cancellationToken)
        {
            if (key == null)
            {
                throw new ArgumentNullException(nameof(key));
            }

            return(GetMimeTypeAsyncImpl());

            async Task <string> GetMimeTypeAsyncImpl()
            {
                var adapter = await adapterHolder.GetAdapterAsync();

                await ThrowIfNotExistsAsync(adapter, key, cancellationToken);

                if (adapter is IMimeTypeProvider mimeTypeProvider)
                {
                    return(await mimeTypeProvider.GetMimeTypeAsync(key, cancellationToken));
                }

                throw new InvalidOperationException($"FileSystem Adapter '{adapter.GetType().Name}' cannot provide MIME type");
            }
        }
        private async Task <ListKeysResult> ListKeysAsync(AdapterHolder adapterHolder, string prefix, CancellationToken cancellationToken)
        {
            var adapter = await adapterHolder.GetAdapterAsync();

            if (adapter is IListKeysAware listKeysAware)
            {
                return(await listKeysAware.ListKeysAsync(prefix, cancellationToken));
            }

            var result = new ListKeysResult();

            var checkPrefix = !string.IsNullOrWhiteSpace(prefix);

            var keys = await GetKeysAsync(adapter, cancellationToken);

            foreach (var key in keys)
            {
                cancellationToken.ThrowIfCancellationRequested();

                if (!checkPrefix || key.StartsWith(prefix))
                {
                    var isDirectory = await adapter.IsDirectoryAsync(key, cancellationToken);

                    if (isDirectory)
                    {
                        result.Directories.Add(key);
                    }
                    else
                    {
                        result.Keys.Add(key);
                    }
                }
            }

            return(result);
        }
        private async Task <List <string> > GetKeysAsync(AdapterHolder adapterHolder, CancellationToken cancellationToken)
        {
            var adapter = await adapterHolder.GetAdapterAsync();

            return(await adapter.GetKeysAsync(cancellationToken));
        }