/// <inheritdoc/>
        public async Task <Skylink> UploadFiles(IReadOnlyCollection <UploadItem> items, MultiFileUploadOptions options = default)
        {
            if (items is null)
            {
                throw new ArgumentNullException(nameof(items));
            }
            if (items.Count == 0)
            {
                throw new ArgumentException("Sequence must not be empty", nameof(items));
            }
            options ??= MultiFileUploadOptions._default;

            using var multiPartContent = new MultipartFormDataContent();
            foreach (var item in items)
            {
                multiPartContent.Add(CreateFileContent("files[]", item));
            }

            var response = await _httpClient.PostAsync($"/skynet/skyfile{options.ToQueryString()}", multiPartContent).ConfigureAwait(false);

            response.EnsureSuccessStatusCode();

            var contentStream = await response.Content.ReadAsStreamAsync().ConfigureAwait(false);

            var uploadResponse = await JsonSerializer.DeserializeAsync <UploadResponse>(contentStream, _jsonSerializerOptions).ConfigureAwait(false);

            return(uploadResponse.ParseAndValidate());
        }
        /// <inheritdoc/>
        public Task <Skylink> UploadDirectory(IFileProvider fileProvider, string directoryPath, bool recurse = false, MultiFileUploadOptions options = default)
        {
            if (fileProvider is null)
            {
                throw new ArgumentNullException(nameof(fileProvider));
            }
            if (directoryPath is null)
            {
                throw new ArgumentNullException(nameof(directoryPath));
            }
            if (fileProvider is NullFileProvider)
            {
                throw new ArgumentException("Cannot access files from NullFileProvider", nameof(fileProvider));
            }

            // stores FileInfo and relative path
            var files = new List <(IFileInfo, string)>();

            SearchFiles(directoryPath, files);
            var uploadItems = files.Select(file => new UploadItem(file.Item1, file.Item2)).ToArray();

            return(UploadFiles(uploadItems, options));

            void SearchFiles(string path, ICollection <(IFileInfo, string)> fileList)
            {
                var directoryContents = fileProvider.GetDirectoryContents(path);

                if (!directoryContents.Exists)
                {
                    throw new DirectoryNotFoundException($"Cannot find directory at path: {path}");
                }

                foreach (var fileInfo in directoryContents.OrderBy(file => file.Name))
                {
                    var filePath = Path.Combine(path, fileInfo.Name);
                    if (!fileInfo.IsDirectory)
                    {
                        var relativePath = filePath.Substring(directoryPath.Length, filePath.Length - directoryPath.Length);
                        // format as unix-style path
                        fileList.Add((fileInfo, relativePath.Replace(Path.DirectorySeparatorChar, '/')));
                    }
                    else if (recurse)
                    {
                        SearchFiles(filePath, fileList);
                    }
                }
            }
        }
 /// <inheritdoc/>
 public Task <Skylink> UploadFiles(IEnumerable <IFileInfo> files, MultiFileUploadOptions options = default)
 => UploadFiles(files.Select(file => new UploadItem(file)).ToArray(), options);