< Summary

Information
Class: MyNet.IO.FileHistory.RecentFilesService
Assembly: MyNet.IO
File(s): https://raw.githubusercontent.com/sandre58/MyNet/85372080fe102cd9ee155ceab49ae000e7f66103/src/MyNet.IO/FileHistory/RecentFilesService.cs
Tag: 323_28699572109
Line coverage
78%
Covered lines: 95
Uncovered lines: 26
Coverable lines: 121
Total lines: 263
Line coverage: 78.5%
Branch coverage
90%
Covered branches: 20
Total branches: 22
Branch coverage: 90.9%
Method coverage

Feature is only available for sponsors

Upgrade to PRO version

Metrics

MethodBranch coverage Crap Score Cyclomatic complexity Line coverage
.ctor(...)100%11100%
GetAll()100%210%
GetAllAsync()100%11100%
GetLast()100%210%
GetLastAsync()100%11100%
Add(...)100%210%
AddAsync()100%66100%
Remove(...)100%210%
RemoveAsync()100%22100%
Pin(...)100%210%
PinAsync()75%4493.33%
Contains(...)100%210%
ContainsAsync()100%11100%
Clear()100%210%
ClearAsync()100%22100%
GetLastModified(...)100%22100%
EnsureInitializedAsync()83.33%6693.75%
GetOrderedFiles()100%11100%
GetLastCore()100%11100%
Dispose()100%11100%

File(s)

https://raw.githubusercontent.com/sandre58/MyNet/85372080fe102cd9ee155ceab49ae000e7f66103/src/MyNet.IO/FileHistory/RecentFilesService.cs

#LineLine coverage
 1// -----------------------------------------------------------------------
 2// <copyright file="RecentFilesService.cs" company="Stéphane ANDRE">
 3// Copyright (c) Stéphane ANDRE. All rights reserved.
 4// </copyright>
 5// -----------------------------------------------------------------------
 6
 7using System;
 8using System.Collections.Concurrent;
 9using System.Collections.Generic;
 10using System.IO;
 11using System.Linq;
 12using System.Threading;
 13using System.Threading.Tasks;
 14
 15namespace MyNet.IO.FileHistory;
 16
 17/// <summary>
 18/// Service responsible for managing recent files, including adding, retrieving, pinning, and removing entries.
 19/// </summary>
 20/// <param name="repository">The repository used for storing and retrieving recent files.</param>
 21public sealed class RecentFilesService(IRecentFileRepository repository) : IRecentFilesService, IDisposable
 22{
 2123    private readonly ConcurrentDictionary<string, RecentFile> _cache = new(StringComparer.OrdinalIgnoreCase);
 2124    private readonly SemaphoreSlim _initializationLock = new(1, 1);
 25    private volatile bool _initialized;
 26
 27    /// <inheritdoc/>
 28    public IReadOnlyList<RecentFile> GetAll()
 29    {
 030        EnsureInitializedAsync()
 031            .GetAwaiter()
 032            .GetResult();
 33
 034        return GetOrderedFiles();
 35    }
 36
 37    /// <inheritdoc/>
 38    public async Task<IReadOnlyList<RecentFile>> GetAllAsync()
 39    {
 940        await EnsureInitializedAsync().ConfigureAwait(false);
 41
 942        return GetOrderedFiles();
 943    }
 44
 45    /// <inheritdoc/>
 46    public RecentFile? GetLast()
 47    {
 048        EnsureInitializedAsync()
 049            .GetAwaiter()
 050            .GetResult();
 51
 052        return GetLastCore();
 53    }
 54
 55    /// <inheritdoc/>
 56    public async Task<RecentFile?> GetLastAsync()
 57    {
 358        await EnsureInitializedAsync().ConfigureAwait(false);
 59
 360        return GetLastCore();
 361    }
 62
 63    /// <inheritdoc/>
 064    public RecentFile? Add(string name, string path, bool isRecovered = false) => AddAsync(name, path, isRecovered)
 065        .GetAwaiter()
 066        .GetResult();
 67
 68    /// <inheritdoc/>
 69    public async Task<RecentFile?> AddAsync(string name, string path, bool isRecovered = false)
 70    {
 671        ArgumentException.ThrowIfNullOrWhiteSpace(name);
 672        ArgumentException.ThrowIfNullOrWhiteSpace(path);
 73
 674        await EnsureInitializedAsync().ConfigureAwait(false);
 75
 676        if (_cache.TryGetValue(path, out var existing))
 77        {
 378            var refreshed = existing with
 379            {
 380                LastAccessedAt = DateTimeOffset.UtcNow,
 381                LastModifiedAt = GetLastModified(path)
 382            };
 83
 384            var updated = await repository
 385                .UpdateAsync(refreshed)
 386                .ConfigureAwait(false);
 87
 388            if (updated is not null)
 389                _cache[path] = updated;
 90
 391            return updated;
 92        }
 93
 394        var recentFile = new RecentFile
 395        {
 396            Name = name,
 397            Path = path,
 398            LastAccessedAt = DateTimeOffset.UtcNow,
 399            LastModifiedAt = GetLastModified(path),
 3100            IsRecovered = isRecovered
 3101        };
 102
 3103        var created = await repository
 3104            .AddAsync(recentFile)
 3105            .ConfigureAwait(false);
 106
 3107        if (created is not null)
 3108            _cache[path] = created;
 109
 3110        return created;
 6111    }
 112
 113    /// <inheritdoc/>
 0114    public bool Remove(string path) => RemoveAsync(path)
 0115        .GetAwaiter()
 0116        .GetResult();
 117
 118    /// <inheritdoc/>
 119    public async Task<bool> RemoveAsync(string path)
 120    {
 3121        ArgumentException.ThrowIfNullOrWhiteSpace(path);
 122
 3123        await EnsureInitializedAsync().ConfigureAwait(false);
 124
 3125        var removed = await repository
 3126            .RemoveAsync(path)
 3127            .ConfigureAwait(false);
 128
 3129        if (removed)
 3130            _cache.TryRemove(path, out _);
 131
 3132        return removed;
 3133    }
 134
 135    /// <inheritdoc/>
 136    public RecentFile? Pin(string path, bool isPinned)
 0137        => PinAsync(path, isPinned)
 0138            .GetAwaiter()
 0139            .GetResult();
 140
 141    /// <inheritdoc/>
 142    public async Task<RecentFile?> PinAsync(string path, bool isPinned)
 143    {
 3144        ArgumentException.ThrowIfNullOrWhiteSpace(path);
 145
 3146        await EnsureInitializedAsync().ConfigureAwait(false);
 147
 3148        if (!_cache.TryGetValue(path, out var existing))
 0149            return null;
 150
 3151        var updatedFile = existing with
 3152        {
 3153            IsPinned = isPinned
 3154        };
 155
 3156        var updated = await repository
 3157            .UpdateAsync(updatedFile)
 3158            .ConfigureAwait(false);
 159
 3160        if (updated is not null)
 3161            _cache[path] = updated;
 162
 3163        return updated;
 3164    }
 165
 166    /// <inheritdoc/>
 167    public bool Contains(string path)
 168    {
 0169        EnsureInitializedAsync()
 0170            .GetAwaiter()
 0171            .GetResult();
 172
 0173        return _cache.ContainsKey(path);
 174    }
 175
 176    /// <inheritdoc/>
 177    public async Task<bool> ContainsAsync(string path)
 178    {
 6179        await EnsureInitializedAsync().ConfigureAwait(false);
 180
 6181        return _cache.ContainsKey(path);
 6182    }
 183
 184    /// <inheritdoc/>
 0185    public void Clear() => ClearAsync()
 0186        .GetAwaiter()
 0187        .GetResult();
 188
 189    /// <inheritdoc/>
 190    public async Task ClearAsync()
 191    {
 3192        await EnsureInitializedAsync().ConfigureAwait(false);
 193
 18194        foreach (var file in _cache.Keys.ToArray())
 6195            await repository.RemoveAsync(file).ConfigureAwait(false);
 196
 3197        _cache.Clear();
 3198    }
 199
 200    /// <summary>
 201    /// Gets the last modified time of the file at the specified path. If the file does not exist, returns null.
 202    /// </summary>
 203    /// <param name="path">The path of the file.</param>
 204    /// <returns>The last modified time of the file, or null if the file does not exist.</returns>
 6205    private static DateTimeOffset? GetLastModified(string path) => File.Exists(path)
 6206        ? File.GetLastWriteTimeUtc(path)
 6207        : null;
 208
 209    /// <summary>
 210    /// Ensures that the recent files data is loaded into the in-memory cache. This method checks if the cache has alrea
 211    /// </summary>
 212    private async Task EnsureInitializedAsync()
 213    {
 33214        if (_initialized)
 12215            return;
 216
 21217        await _initializationLock
 21218            .WaitAsync()
 21219            .ConfigureAwait(false);
 220
 221        try
 222        {
 21223            if (_initialized)
 0224                return;
 225
 21226            var files = await repository
 21227                .GetAllAsync()
 21228                .ConfigureAwait(false);
 229
 102230            foreach (var file in files)
 30231                _cache[file.Path] = file;
 232
 21233            _initialized = true;
 21234        }
 235        finally
 236        {
 21237            _initializationLock.Release();
 238        }
 33239    }
 240
 241    /// <summary>
 242    /// Retrieves the list of recent files from the in-memory cache, ordered first by pinned status (pinned files appear
 243    /// </summary>
 244    /// <returns>A read-only list of recent files, ordered by pinned status and last accessed time.</returns>
 9245    private IReadOnlyList<RecentFile> GetOrderedFiles() => [.. _cache.Values
 9246        .OrderByDescending(x => x.IsPinned)
 9247        .ThenByDescending(x => x.LastAccessedAt)];
 248
 249    /// <summary>
 250    /// Retrieves the most recently accessed file from the in-memory cache that is not marked as recovered. The method f
 251    /// </summary>
 252    /// <returns>The most recently accessed non-recovered file, or null if none exist.</returns>
 3253    private RecentFile? GetLastCore() => _cache.Values
 3254        .Where(x => !x.IsRecovered)
 3255        .OrderByDescending(x => x.LastAccessedAt)
 3256        .FirstOrDefault();
 257
 258    /// <summary>
 259    /// Disposes of the resources used by the <see cref="RecentFilesService"/>. This method is responsible for releasing
 260    /// </summary>
 21261    public void Dispose() => _initializationLock.Dispose();
 262}
 263