< Summary

Information
Class: Services.EmailRoutingServices.DropboxFileRouter
Assembly: Services
File(s): /home/runner/work/Studio2Prod/Studio2Prod/AppRepository/EmailRoutingServices/DropboxFileRouter.cs
Line coverage
0%
Covered lines: 0
Uncovered lines: 74
Coverable lines: 74
Total lines: 144
Line coverage: 0%
Branch coverage
0%
Covered branches: 0
Total branches: 32
Branch coverage: 0%
Method coverage

Feature is only available for sponsors

Upgrade to PRO version

Metrics

MethodBranch coverage Crap Score Cyclomatic complexity Line coverage
.ctor(...)100%210%
RouteAsync()0%7280%
ResolveFileNameByPolicy()0%7280%
ResolveFolder(...)0%210140%
SanitizePathToken(...)0%620%

File(s)

/home/runner/work/Studio2Prod/Studio2Prod/AppRepository/EmailRoutingServices/DropboxFileRouter.cs

#LineLine coverage
 1using Services.ApiServices.DropBoxApi;
 2using OzonDomains.Models;
 3using System.Text.RegularExpressions;
 4using Services.SystemMonitoringServices;
 5
 6namespace Services.EmailRoutingServices;
 7
 8public class DropboxFileRouter : IDropboxFileRouter
 9{
 10    private readonly DropboxApiClient _dropboxApiClient;
 11    private readonly ISystemMonitoringSignalWriter? _signals;
 12
 013    public DropboxFileRouter(DropboxApiClient dropboxApiClient, ISystemMonitoringSignalWriter? signals = null)
 14    {
 015        _dropboxApiClient = dropboxApiClient;
 016        _signals = signals;
 017    }
 18
 19    public async Task<DropboxRouteResult> RouteAsync(
 20        EmailRoutingRule rule,
 21        InboxMessageDto message,
 22        InboxAttachmentDto attachment,
 23        string hash)
 24    {
 25        try
 26        {
 027            var folder = ResolveFolder(rule.DropboxFolderTemplate, message);
 028            var fileName = await ResolveFileNameByPolicy(rule, folder, attachment.FileName, hash);
 29
 030            await _dropboxApiClient.UploadFileFromBytesAsync(attachment.FileBytes, folder, fileName);
 031            var link = await _dropboxApiClient.GetDownloadLinkAsync(folder, fileName);
 032            await (_signals?.RecordAsync("dropbox-routing", "success") ?? Task.CompletedTask);
 33
 034            return new DropboxRouteResult
 035            {
 036                IsSuccess = true,
 037                FolderPath = folder,
 038                FileName = fileName,
 039                FilePath = $"{folder.TrimEnd('/')}/{fileName}",
 040                DownloadLink = link
 041            };
 42        }
 43        catch (Exception ex)
 44        {
 045            await (_signals?.RecordAsync("dropbox-routing", "failure", message: "Ошибка записи вложения в Dropbox") ?? T
 046            return new DropboxRouteResult
 047            {
 048                IsSuccess = false,
 049                Error = ex.Message
 050            };
 51        }
 052    }
 53
 54    private async Task<string> ResolveFileNameByPolicy(
 55        EmailRoutingRule rule,
 56        string folder,
 57        string fileName,
 58        string hash)
 59    {
 060        switch (rule.ConflictPolicy)
 61        {
 62            case EmailRoutingConflictPolicy.Overwrite:
 063                return fileName;
 64
 65            case EmailRoutingConflictPolicy.SkipIfExists:
 66            {
 067                var exists = await _dropboxApiClient.FileExistsAsync(folder, fileName);
 068                return exists ? $"skipped_{hash[..8]}_{fileName}" : fileName;
 69            }
 70
 71            case EmailRoutingConflictPolicy.VersionedName:
 72            {
 073                var exists = await _dropboxApiClient.FileExistsAsync(folder, fileName);
 074                if (!exists)
 75                {
 076                    return fileName;
 77                }
 78
 079                var ext = Path.GetExtension(fileName);
 080                var name = Path.GetFileNameWithoutExtension(fileName);
 081                return $"{name}_{DateTime.UtcNow:yyyyMMddHHmmss}{ext}";
 82            }
 83
 84            default:
 085                return fileName;
 86        }
 087    }
 88
 89    private static string ResolveFolder(string template, InboxMessageDto message)
 90    {
 091        var baseDate = message.ReceivedAtUtc?.ToUniversalTime() ?? DateTime.UtcNow;
 092        var safeSender = SanitizePathToken(message.SenderEmail ?? "unknown");
 093        var safeSubject = SanitizePathToken(message.Subject ?? "no-subject");
 094        var folder = (template ?? string.Empty).Trim();
 95
 96        // Support {date:yyyy-MM-dd} style token from rule UI.
 097        folder = Regex.Replace(
 098            folder,
 099            @"\{date:([^}]+)\}",
 0100            m =>
 0101            {
 0102                var dateFormat = m.Groups[1].Value;
 0103                try
 0104                {
 0105                    return baseDate.ToString(dateFormat);
 0106                }
 0107                catch
 0108                {
 0109                    return baseDate.ToString("yyyy-MM-dd");
 0110                }
 0111            },
 0112            RegexOptions.CultureInvariant);
 113
 0114        folder = folder
 0115            .Replace("{yyyy}", baseDate.ToString("yyyy"))
 0116            .Replace("{MM}", baseDate.ToString("MM"))
 0117            .Replace("{dd}", baseDate.ToString("dd"))
 0118            .Replace("{sender}", safeSender)
 0119            .Replace("{subject}", safeSubject);
 120
 0121        folder = folder.Replace("\\", "/");
 0122        while (folder.Contains("//"))
 123        {
 0124            folder = folder.Replace("//", "/");
 125        }
 126
 0127        if (!folder.StartsWith('/'))
 128        {
 0129            folder = "/" + folder;
 130        }
 131
 0132        folder = folder.TrimEnd('/');
 0133        return string.IsNullOrWhiteSpace(folder) ? "/" : folder;
 134    }
 135
 136    private static string SanitizePathToken(string value)
 137    {
 0138        var invalid = Path.GetInvalidFileNameChars().Concat(new[] { '/', '\\' }).ToHashSet();
 0139        return new string(value
 0140            .Trim()
 0141            .Select(ch => invalid.Contains(ch) ? '_' : ch)
 0142            .ToArray());
 143    }
 144}