• Home
  • Features
  • Pricing
  • Docs
  • Announcements
  • Sign In

abatar1 / CyclicalFileWatcher / 14564676608

21 Apr 2025 12:18AM UTC coverage: 15.556% (-0.05%) from 15.605%
14564676608

push

github

abatar1
fix content disposal bug

0 of 56 branches covered (0.0%)

Branch coverage included in aggregate %.

0 of 2 new or added lines in 1 file covered. (0.0%)

2 existing lines in 1 file now uncovered.

49 of 259 relevant lines covered (18.92%)

3191.33 hits per line

Source File
Press 'n' to go to next uncovered line, 'b' for previous

0.0
/src/CyclicalFileWatcher/Internals/FileStateStorage.cs
1
using System;
2
using System.Collections.Concurrent;
3
using System.Collections.Generic;
4
using System.IO;
5
using System.Threading;
6
using System.Threading.Tasks;
7
using FileWatcher.Base;
8
using Nito.AsyncEx;
9

10
namespace FileWatcher.Internals;
11

12
internal sealed class FileStateStorage<TFileStateContent> : IAsyncDisposable
13
    where TFileStateContent : IFileStateContent
14
{
15
    private readonly AsyncLazy<FileWatcherParameters<TFileStateContent>> _fileWatcherParameters;
16
    private readonly ConcurrentDictionary<string, FileState<TFileStateContent>> _filesStatesByKeys = new();
×
17
    private readonly LinkedList<string> _fileStateKeysOrder = new();
×
18

19
    public FileStateStorage(FileWatcherParameters<TFileStateContent> fileWatcherParameters)
×
20
    {
21
        _fileWatcherParameters = new AsyncLazy<FileWatcherParameters<TFileStateContent>>(async () =>
×
22
        {
×
23
            await AppendFileStateAsync(fileWatcherParameters);
×
24
            return fileWatcherParameters;
×
25
        });
×
26
        _fileWatcherParameters.Start();
×
27
    }
×
28

29
    public async Task<FileState<TFileStateContent>> GetFileStateAsync(string key, CancellationToken cancellationToken)
30
    {
31
        var parameters = await _fileWatcherParameters.Task.WaitAsync(cancellationToken);
×
32
        
33
        if (!_filesStatesByKeys.TryGetValue(key, out var file))
×
34
            throw new KeyNotFoundException($"File {parameters.FilePath} with key {key} not found or already expired");
×
35
        return file;
×
36
    }
×
37
    
38
    public async Task<FileState<TFileStateContent>> GetLatestFileStateAsync(CancellationToken cancellationToken)
39
    {
40
        var parameters = await _fileWatcherParameters.Task.WaitAsync(cancellationToken);
×
41
        
42
        var latestKey = _fileStateKeysOrder.Last?.Value;
×
43
        if (latestKey == null)
×
44
            throw new InvalidOperationException($"No files added yet, could not retrieve latest file from filepath {parameters.FilePath}, seems like a bug");
×
45
        return await GetFileStateAsync(latestKey, cancellationToken);
×
46
    } 
×
47
    
48
    public async Task<bool> TryUpdateFileStateAsync(CancellationToken cancellationToken)
49
    {
50
        var parameters = await _fileWatcherParameters.Task.WaitAsync(cancellationToken);
×
51
        
52
        FileState<TFileStateContent> latestFileState;
53
        try
54
        {
55
            latestFileState = await GetLatestFileStateAsync(cancellationToken);
×
56
        }
×
57
        catch (Exception)
×
58
        {
59
            return false;
×
60
        }
61
        
62
        var lastModificationDateTime = GetLastModificationDateTime(parameters.FilePath);
×
63
        
64
        if (latestFileState.ModifiedAtUtc == lastModificationDateTime)
×
65
            return false;
×
66
        
67
        while (!cancellationToken.IsCancellationRequested)
×
68
        {
NEW
69
            if (await CheckFileCanBeLoadedAsync(parameters.FilePath))
×
70
                break;
71
            await Task.Delay(TimeSpan.FromSeconds(1), cancellationToken);
×
72
        }
73

74
        await AppendFileStateAsync(parameters);
×
75
        
76
        return true;
×
77
    }
×
78
    
79
    private async Task AppendFileStateAsync(FileWatcherParameters<TFileStateContent> fileWatcherParameters)
80
    {
81
        var fileStateContent = await fileWatcherParameters.FileStateContentFactory.Invoke();
×
82
        var key = await fileWatcherParameters.FileStateKeyFactory.Invoke(fileStateContent);
×
83

84
        var fileState = new FileState<TFileStateContent>
×
85
        {
×
86
            Identifier = new FileStateIdentifier(fileWatcherParameters.FilePath),
×
87
            ModifiedAtUtc = GetLastModificationDateTime(fileWatcherParameters.FilePath),
×
88
            Content = fileStateContent,
×
89
            Key = key
×
90
        };
×
91

92
        _filesStatesByKeys[key] = fileState;
×
93
        _fileStateKeysOrder.AddLast(key);
×
94

95
        if (_fileStateKeysOrder.Count > fileWatcherParameters.Depth + 1)
×
96
        {
97
            var oldestKey = _fileStateKeysOrder.First.Value;
×
98
            _fileStateKeysOrder.RemoveFirst();
×
NEW
99
            await _filesStatesByKeys[oldestKey].Content.DisposeAsync();
×
UNCOV
100
            _filesStatesByKeys.Remove(oldestKey, out _);
×
UNCOV
101
        }
×
102
    }
×
103
    
104
    private static async Task<bool> CheckFileCanBeLoadedAsync(string filePath)
105
    {
106
        long streamLength;
107
        try
108
        {
109
            await using FileStream inputStream = File.Open(filePath, FileMode.Open, FileAccess.Read, FileShare.None);
×
110
            streamLength = inputStream.Length;
×
111
        }
×
112
        catch (SystemException)
×
113
        {
114
            return false;
×
115
        }
116

117
        if (streamLength <= 0)
×
118
            return false;
×
119
        
120
        return true;
×
121
    }
×
122
    
123
    private static DateTime GetLastModificationDateTime(string certificatePath)
124
    {
125
        var fileInfo = new FileInfo(certificatePath);
×
126
        return fileInfo.LastWriteTime;
×
127
    }
128

129
    public async ValueTask DisposeAsync()
130
    {
131
        foreach (var storedCertificate in _filesStatesByKeys.Values)
×
132
            await storedCertificate.DisposeAsync();
×
133
    }
×
134
}
STATUS · Troubleshooting · Open an Issue · Sales · Support · CAREERS · ENTERPRISE · START FREE · SCHEDULE DEMO
ANNOUNCEMENTS · TWITTER · TOS & SLA · Supported CI Services · What's a CI service? · Automated Testing

© 2026 Coveralls, Inc