< Summary

Information
Class: Services.ApiServices.DropBoxApi.DropboxFileInfo
Assembly: Services
File(s): /home/runner/work/Studio2Prod/Studio2Prod/AppRepository/ApiServices/DropBoxApi/DropboxApiClient.cs
Line coverage
0%
Covered lines: 0
Uncovered lines: 4
Coverable lines: 4
Total lines: 361
Line coverage: 0%
Branch coverage
N/A
Covered branches: 0
Total branches: 0
Branch coverage: N/A
Method coverage

Feature is only available for sponsors

Upgrade to PRO version

Metrics

MethodBranch coverage Crap Score Cyclomatic complexity Line coverage
get_Name()100%210%
get_Path()100%210%
get_Size()100%210%
get_Modified()100%210%

File(s)

/home/runner/work/Studio2Prod/Studio2Prod/AppRepository/ApiServices/DropBoxApi/DropboxApiClient.cs

#LineLine coverage
 1using Dropbox.Api.Files;
 2using Dropbox.Api;
 3using Newtonsoft.Json;
 4using System.IO;
 5
 6
 7namespace Services.ApiServices.DropBoxApi
 8{
 9    public class DropboxApiClient
 10    {
 11        private static readonly HttpClient _tokenClient = new(new SocketsHttpHandler
 12        {
 13            PooledConnectionLifetime = TimeSpan.FromMinutes(5)
 14        });
 15
 16        private string _accessToken = null!;
 17        private readonly string _refreshToken;
 18        private readonly string _appKey;
 19        private readonly string _appSecret;
 20
 21        public DropboxApiClient(string refreshToken, string appKey, string appSecret)
 22        {
 23            _refreshToken = refreshToken;
 24            _appKey = appKey;
 25            _appSecret = appSecret;
 26        }
 27
 28        private async Task<string> RefreshAccessTokenAsync()
 29        {
 30            var client = _tokenClient;
 31            var requestContent = new FormUrlEncodedContent(new[]
 32            {
 33                new KeyValuePair<string, string>("grant_type", "refresh_token"),
 34                new KeyValuePair<string, string>("refresh_token", _refreshToken),
 35                new KeyValuePair<string, string>("client_id", _appKey),
 36                new KeyValuePair<string, string>("client_secret", _appSecret)
 37            });
 38
 39            var response = await client.PostAsync("https://api.dropbox.com/oauth2/token", requestContent);
 40            response.EnsureSuccessStatusCode();
 41
 42            var responseContent = await response.Content.ReadAsStringAsync();
 43            var tokenResponse = JsonConvert.DeserializeObject<Dictionary<string, string>>(responseContent);
 44            if (tokenResponse == null || !tokenResponse.TryGetValue("access_token", out var accessToken))
 45                throw new InvalidOperationException("Dropbox token response missing access_token");
 46
 47            _accessToken = accessToken;
 48            return _accessToken;
 49        }
 50
 51        public async Task<string> GetViewLinkAsync(string filePath, string fileName)
 52        {
 53            await Task.CompletedTask;
 54            var baseSegments = new[] { "Приложения", "Studio2", "Orders" }
 55                .Select(Uri.EscapeDataString);
 56            var pathSegments = filePath.Split('/', StringSplitOptions.RemoveEmptyEntries)
 57                .Select(Uri.EscapeDataString);
 58            var fileSegment = Uri.EscapeDataString(fileName);
 59            var allSegments = baseSegments.Concat(pathSegments).Concat(new[] { fileSegment });
 60            var encodedPath = string.Join('/', allSegments);
 61            return $"https://www.dropbox.com/home/{encodedPath}";
 62        }
 63
 64        public async Task UploadFileAsync(string localFilePath, string dropboxFolderPath, string dropboxFileName)
 65        {
 66            await RefreshAccessTokenAsync();
 67
 68            using (var dropboxClient = new DropboxClient(_accessToken))
 69            {
 70                using (var fileStream = new FileStream(localFilePath, FileMode.Open, FileAccess.Read))
 71                {
 72                    await dropboxClient.Files.UploadAsync(
 73                        path: $"{dropboxFolderPath}/{dropboxFileName}",
 74                        WriteMode.Overwrite.Instance,
 75                        body: fileStream
 76                    );
 77                }
 78            }
 79        }
 80
 81        /// <summary>
 82        /// Получение ссылки для скачивания файла из Dropbox
 83        /// </summary>
 84        public async Task<string> GetDownloadLinkAsync(string dropboxFolderPath, string fileName)
 85        {
 86            await RefreshAccessTokenAsync();
 87
 88            using (var dropboxClient = new DropboxClient(_accessToken))
 89            {
 90                var fullPath = $"{dropboxFolderPath.TrimEnd('/')}/{fileName}";
 91                var tempLinkResult = await dropboxClient.Files.GetTemporaryLinkAsync(fullPath);
 92                return tempLinkResult.Link;
 93            }
 94        }
 95
 96        /// <summary>
 97        /// Возвращает файлы из подпапок указанной папки Dropbox вместе с временными ссылками.
 98        /// </summary>
 99        /// <param name="folderPath">Путь к папке с подпапками.</param>
 100        /// <param name="maxFilesPerFolder">
 101        /// Максимум файлов на подпапку (по дате изменения, новые сначала). null — без ограничения (все файлы).
 102        /// </param>
 103        /// <param name="filterKnownFileNames">
 104        /// Необязательный колбэк: получает список имён файлов подпапки, возвращает набор уже известных —
 105        /// для них временная ссылка не запрашивается (экономит дорогие вызовы Dropbox API).
 106        /// </param>
 107        public async Task<Dictionary<string, Dictionary<string, string>>> GetFolderContentsAsync(
 108            string folderPath,
 109            int? maxFilesPerFolder = 10,
 110            Func<IReadOnlyCollection<string>, Task<HashSet<string>>>? filterKnownFileNames = null)
 111        {
 112            var result = new Dictionary<string, Dictionary<string, string>>();
 113            try
 114            {
 115                await RefreshAccessTokenAsync();
 116                using (var client = new DropboxClient(_accessToken))
 117                {
 118                    // Получаем список файлов и папок в указанной папке
 119                    var list = await client.Files.ListFolderAsync(folderPath);
 120
 121                    // Проходим по всем элементам в папке
 122                    foreach (var item in list.Entries.Where(i => i.IsFolder))
 123                    {
 124                        var folderName = item.AsFolder.Name;
 125                        var folderPathLower = item.AsFolder.PathLower;
 126
 127                        // Получаем список файлов в подпапке (с пагинацией, если лимит не задан —
 128                        // иначе часть файлов Dropbox не покажет за один вызов ListFolderAsync)
 129                        var folderFiles = await client.Files.ListFolderAsync(folderPathLower);
 130                        var entries = folderFiles.Entries.ToList();
 131                        if (maxFilesPerFolder == null)
 132                        {
 133                            var hasMore = folderFiles.HasMore;
 134                            var cursor = folderFiles.Cursor;
 135                            while (hasMore)
 136                            {
 137                                var next = await client.Files.ListFolderContinueAsync(cursor);
 138                                entries.AddRange(next.Entries);
 139                                hasMore = next.HasMore;
 140                                cursor = next.Cursor;
 141                            }
 142                        }
 143
 144                        // Сортируем файлы по дате изменения (от новых к старым), при заданном лимите — берём верхушку
 145                        var candidateFiles = entries
 146                            .Where(i => i.IsFile)
 147                            .OrderByDescending(f => f.AsFile.ClientModified) // Сначала новые файлы
 148                            .ToList();
 149                        if (maxFilesPerFolder.HasValue)
 150                        {
 151                            candidateFiles = candidateFiles.Take(maxFilesPerFolder.Value).ToList();
 152                        }
 153
 154                        // Отсеиваем уже известные файлы ДО генерации временных ссылок
 155                        var known = filterKnownFileNames == null
 156                            ? new HashSet<string>()
 157                            : await filterKnownFileNames(candidateFiles.Select(f => f.AsFile.Name).ToList());
 158
 159                        // Создаем словарь для хранения названий файлов и ссылок
 160                        var fileLinks = new Dictionary<string, string>();
 161                        foreach (var file in candidateFiles)
 162                        {
 163                            var fileName = file.AsFile.Name;
 164                            if (known.Contains(fileName))
 165                                continue;
 166
 167                            var filePath = file.AsFile.PathLower;
 168                            var link = await client.Files.GetTemporaryLinkAsync(filePath);
 169                            fileLinks[fileName] = link.Link;
 170                        }
 171
 172                        // Добавляем в результат
 173                        result[folderName] = fileLinks;
 174                    }
 175                }
 176            }
 177            catch (Exception e)
 178            {
 179                Console.WriteLine(e);
 180            }
 181
 182            return result;
 183        }
 184
 185        /// <summary>
 186        /// Проверяет существование файла в Dropbox
 187        /// </summary>
 188        public async Task<bool> FileExistsAsync(string dropboxFolderPath, string fileName)
 189        {
 190            try
 191            {
 192                await RefreshAccessTokenAsync();
 193                using (var client = new DropboxClient(_accessToken))
 194                {
 195                    var fullPath = $"{dropboxFolderPath.TrimEnd('/')}/{fileName}";
 196                    try
 197                    {
 198                        var metadata = await client.Files.GetMetadataAsync(fullPath);
 199                        return metadata != null && metadata.IsFile;
 200                    }
 201                    catch (Exception ex)
 202                    {
 203                        // Проверяем, является ли это ошибкой "файл не найден"
 204                        // В Dropbox API это обычно HttpException с кодом 409 или 404
 205                        if (ex.Message.Contains("not_found") ||
 206                            ex.Message.Contains("path") ||
 207                            ex.Message.Contains("not found") ||
 208                            ex.GetType().Name.Contains("HttpException"))
 209                        {
 210                            return false;
 211                        }
 212
 213                        // Другие ошибки - считаем что файла нет
 214                        return false;
 215                    }
 216                }
 217            }
 218            catch
 219            {
 220                return false;
 221            }
 222        }
 223
 224        /// <summary>
 225        /// Загружает файл из байтов в Dropbox
 226        /// </summary>
 227        public async Task UploadFileFromBytesAsync(byte[] fileBytes, string dropboxFolderPath, string dropboxFileName)
 228        {
 229            await RefreshAccessTokenAsync();
 230
 231            using (var dropboxClient = new DropboxClient(_accessToken))
 232            {
 233                using (var memoryStream = new MemoryStream(fileBytes))
 234                {
 235                    var fullPath = $"{dropboxFolderPath.TrimEnd('/')}/{dropboxFileName}";
 236                    await dropboxClient.Files.UploadAsync(
 237                        path: fullPath,
 238                        WriteMode.Overwrite.Instance,
 239                        body: memoryStream
 240                    );
 241                }
 242            }
 243        }
 244
 245        /// <summary>
 246        /// Загружает файл из байтов и возвращает ссылку для скачивания
 247        /// </summary>
 248        public async Task<string> UploadFileAndGetLinkAsync(byte[] fileBytes, string dropboxFolderPath,
 249            string dropboxFileName)
 250        {
 251            await UploadFileFromBytesAsync(fileBytes, dropboxFolderPath, dropboxFileName);
 252            return await GetDownloadLinkAsync(dropboxFolderPath, dropboxFileName);
 253        }
 254
 255        /// <summary>
 256        /// Получает список всех файлов в папке с их метаданными
 257        /// </summary>
 258        public async Task<List<DropboxFileInfo>> GetFilesListAsync(string dropboxFolderPath)
 259        {
 260            var filesList = new List<DropboxFileInfo>();
 261            try
 262            {
 263                await RefreshAccessTokenAsync();
 264                using (var client = new DropboxClient(_accessToken))
 265                {
 266                    var list = await client.Files.ListFolderAsync(dropboxFolderPath);
 267
 268                    foreach (var item in list.Entries.Where(i => i.IsFile))
 269                    {
 270                        var file = item.AsFile;
 271                        filesList.Add(new DropboxFileInfo
 272                        {
 273                            Name = file.Name,
 274                            Path = file.PathLower,
 275                            Size = file.Size,
 276                            Modified = file.ServerModified
 277                        });
 278                    }
 279                }
 280            }
 281            catch (Exception e)
 282            {
 283                Console.WriteLine($"Ошибка при получении списка файлов: {e.Message}");
 284            }
 285
 286            return filesList.OrderByDescending(f => f.Modified).ToList();
 287        }
 288
 289        /// <summary>
 290        /// Индекс имён PDF в папке (без расширения) — один list_folder вместо N get_metadata.
 291        /// </summary>
 292        public async Task<HashSet<string>> GetPdfShipmentNumberIndexAsync(string dropboxFolderPath)
 293        {
 294            var index = new HashSet<string>(StringComparer.OrdinalIgnoreCase);
 295            try
 296            {
 297                await RefreshAccessTokenAsync();
 298                using var client = new DropboxClient(_accessToken);
 299                var list = await client.Files.ListFolderAsync(dropboxFolderPath);
 300                AddPdfNamesToIndex(list, index);
 301                while (list.HasMore)
 302                {
 303                    list = await client.Files.ListFolderContinueAsync(list.Cursor);
 304                    AddPdfNamesToIndex(list, index);
 305                }
 306            }
 307            catch (Exception e)
 308            {
 309                Console.WriteLine($"Ошибка при построении индекса этикеток: {e.Message}");
 310            }
 311
 312            return index;
 313        }
 314
 315        private static void AddPdfNamesToIndex(ListFolderResult list, HashSet<string> index)
 316        {
 317            foreach (var item in list.Entries.Where(i => i.IsFile))
 318            {
 319                var name = item.AsFile.Name;
 320                if (!name.EndsWith(".pdf", StringComparison.OrdinalIgnoreCase))
 321                {
 322                    continue;
 323                }
 324
 325                index.Add(name[..^4]);
 326            }
 327        }
 328
 329        /// <summary>
 330        /// Получает файл из Dropbox в виде байтов
 331        /// </summary>
 332        public async Task<byte[]> DownloadFileAsync(string dropboxFolderPath, string fileName)
 333        {
 334            await RefreshAccessTokenAsync();
 335
 336            using (var dropboxClient = new DropboxClient(_accessToken))
 337            {
 338                var fullPath = $"{dropboxFolderPath.TrimEnd('/')}/{fileName}";
 339                var downloadResult = await dropboxClient.Files.DownloadAsync(fullPath);
 340
 341                using (var contentStream = await downloadResult.GetContentAsStreamAsync())
 342                using (var memoryStream = new MemoryStream())
 343                {
 344                    await contentStream.CopyToAsync(memoryStream);
 345                    return memoryStream.ToArray();
 346                }
 347            }
 348        }
 349    }
 350
 351    /// <summary>
 352    /// Информация о файле в Dropbox
 353    /// </summary>
 354    public class DropboxFileInfo
 355    {
 0356        public string Name { get; set; } = null!;
 0357        public string Path { get; set; } = null!;
 0358        public ulong Size { get; set; }
 0359        public DateTime Modified { get; set; }
 360    }
 361}