< Summary

Information
Class: Services.LabelServices.LabelExistenceService
Assembly: Services
File(s): /home/runner/work/Studio2Prod/Studio2Prod/AppRepository/LabelServices/LabelExistenceService.cs
Line coverage
87%
Covered lines: 61
Uncovered lines: 9
Coverable lines: 70
Total lines: 142
Line coverage: 87.1%
Branch coverage
91%
Covered branches: 22
Total branches: 24
Branch coverage: 91.6%
Method coverage

Feature is only available for sponsors

Upgrade to PRO version

Metrics

MethodBranch coverage Crap Score Cyclomatic complexity Line coverage
.cctor()100%11100%
.ctor(...)100%11100%
CheckBatchAsync()100%1010100%
InvalidateShipment(...)50%2280%
SetShipmentExists(...)50%2290.9%
RefreshFolderIndexAsync()100%210%
GetFolderIndexAsync()100%44100%
LoadFolderIndexAsync()100%7675%

File(s)

/home/runner/work/Studio2Prod/Studio2Prod/AppRepository/LabelServices/LabelExistenceService.cs

#LineLine coverage
 1using Microsoft.Extensions.Caching.Memory;
 2
 3namespace Services.LabelServices;
 4
 5public class LabelExistenceService : ILabelExistenceService
 6{
 7    private const string PerLabelCacheKeyPrefix = "label_exists_";
 8    private const string FolderIndexCacheKey = "labels_folder_index_v1";
 9
 110    private static readonly TimeSpan PerLabelCacheExpiration = TimeSpan.FromHours(1);
 111    private static readonly TimeSpan FolderIndexCacheExpiration = TimeSpan.FromMinutes(10);
 12
 13    private readonly ILabelFileStorage _labelFileStorage;
 14    private readonly IMemoryCache _cache;
 15
 716    public LabelExistenceService(ILabelFileStorage labelFileStorage, IMemoryCache cache)
 17    {
 718        _labelFileStorage = labelFileStorage;
 719        _cache = cache;
 720    }
 21
 22    public async Task<IReadOnlyDictionary<string, bool>> CheckBatchAsync(IReadOnlyList<string> shipmentNumbers)
 23    {
 624        var results = new Dictionary<string, bool>(StringComparer.OrdinalIgnoreCase);
 625        if (shipmentNumbers.Count == 0)
 26        {
 127            return results;
 28        }
 29
 530        var distinctNumbers = shipmentNumbers
 10831            .Where(s => !string.IsNullOrWhiteSpace(s))
 532            .Distinct(StringComparer.OrdinalIgnoreCase)
 533            .Take(100)
 534            .ToList();
 35
 536        var toResolve = new List<string>();
 22637        foreach (var shipmentNumber in distinctNumbers)
 38        {
 10839            var cacheKey = $"{PerLabelCacheKeyPrefix}{shipmentNumber}";
 10840            if (_cache.TryGetValue(cacheKey, out bool cachedExists))
 41            {
 242                results[shipmentNumber] = cachedExists;
 43            }
 44            else
 45            {
 10646                toResolve.Add(shipmentNumber);
 47            }
 48        }
 49
 550        if (toResolve.Count == 0)
 51        {
 152            return results;
 53        }
 54
 455        var folderIndex = await GetFolderIndexAsync();
 456        var cacheOptions = new MemoryCacheEntryOptions
 457        {
 458            AbsoluteExpirationRelativeToNow = PerLabelCacheExpiration,
 459            SlidingExpiration = TimeSpan.FromMinutes(30)
 460        };
 61
 22062        foreach (var shipmentNumber in toResolve)
 63        {
 10664            var exists = folderIndex.Contains(shipmentNumber);
 10665            results[shipmentNumber] = exists;
 10666            _cache.Set($"{PerLabelCacheKeyPrefix}{shipmentNumber}", exists, cacheOptions);
 67        }
 68
 469        return results;
 670    }
 71
 72    public void InvalidateShipment(string shipmentNumber)
 73    {
 174        if (string.IsNullOrWhiteSpace(shipmentNumber))
 75        {
 076            return;
 77        }
 78
 179        _cache.Remove($"{PerLabelCacheKeyPrefix}{shipmentNumber}");
 180        _cache.Remove(FolderIndexCacheKey);
 181    }
 82
 83    public void SetShipmentExists(string shipmentNumber, bool exists)
 84    {
 185        if (string.IsNullOrWhiteSpace(shipmentNumber))
 86        {
 087            return;
 88        }
 89
 190        _cache.Set(
 191            $"{PerLabelCacheKeyPrefix}{shipmentNumber}",
 192            exists,
 193            new MemoryCacheEntryOptions
 194            {
 195                AbsoluteExpirationRelativeToNow = PerLabelCacheExpiration,
 196                SlidingExpiration = TimeSpan.FromMinutes(30)
 197            });
 198    }
 99
 100    public async Task<int> RefreshFolderIndexAsync(CancellationToken cancellationToken = default)
 101    {
 102        _ = cancellationToken;
 0103        _cache.Remove(FolderIndexCacheKey);
 0104        var index = await LoadFolderIndexAsync();
 0105        return index.Count;
 0106    }
 107
 108    private async Task<HashSet<string>> GetFolderIndexAsync()
 109    {
 4110        if (_cache.TryGetValue(FolderIndexCacheKey, out HashSet<string>? cached) && cached is not null)
 111        {
 3112            return cached;
 113        }
 114
 1115        return await LoadFolderIndexAsync();
 4116    }
 117
 118    private async Task<HashSet<string>> LoadFolderIndexAsync()
 119    {
 1120        var index = new HashSet<string>(StringComparer.OrdinalIgnoreCase);
 121        try
 122        {
 1123            var files = await _labelFileStorage.ListAsync();
 6124            foreach (var file in files)
 125            {
 2126                if (file.IsDirectory || !file.Name.EndsWith(".pdf", StringComparison.OrdinalIgnoreCase))
 127                {
 128                    continue;
 129                }
 130
 1131                index.Add(file.Name[..^4]);
 132            }
 1133        }
 0134        catch (Exception ex)
 135        {
 0136            Console.WriteLine($"Ошибка при построении индекса этикеток: {ex.Message}");
 0137        }
 138
 1139        _cache.Set(FolderIndexCacheKey, index, FolderIndexCacheExpiration);
 1140        return index;
 1141    }
 142}