< Summary - Jellyfin

Information
Class: Emby.Server.Implementations.IO.FileRefresher
Assembly: Emby.Server.Implementations
File(s): /srv/git/jellyfin/Emby.Server.Implementations/IO/FileRefresher.cs
Line coverage
74%
Covered lines: 67
Uncovered lines: 23
Coverable lines: 90
Total lines: 234
Line coverage: 74.4%
Branch coverage
63%
Covered branches: 24
Total branches: 38
Branch coverage: 63.1%
Method coverage

Feature is only available for sponsors

Upgrade to PRO version

Coverage history

Coverage history 0 25 50 75 100 5/16/2026 - 12:15:55 AM Line coverage: 0% (0/82) Branch coverage: 0% (0/36) Total lines: 21510/29/2026 - 12:17:11 AM Line coverage: 74.4% (67/90) Branch coverage: 63.1% (24/38) Total lines: 234 5/16/2026 - 12:15:55 AM Line coverage: 0% (0/82) Branch coverage: 0% (0/36) Total lines: 21510/29/2026 - 12:17:11 AM Line coverage: 74.4% (67/90) Branch coverage: 63.1% (24/38) Total lines: 234

Coverage delta

Coverage delta 75 -75

Metrics

MethodBranch coverage Crap Score Cyclomatic complexity Line coverage
.ctor(...)100%11100%
AddAffectedPath(...)50%22100%
AddPath(...)100%11100%
RestartTimer()66.66%6677.77%
ResetPath(...)0%620%
OnTimerCallback(...)50%4471.42%
ProcessPathChanges(...)50%6453.33%
TryGetAffectedBaseItem(...)100%11100%
GetAffectedBaseItem(...)75%1616100%
DisposeTimer()100%22100%
Dispose()50%2280%

File(s)

/srv/git/jellyfin/Emby.Server.Implementations/IO/FileRefresher.cs

#LineLine coverage
 1#pragma warning disable CS1591
 2
 3using System;
 4using System.Collections.Generic;
 5using System.IO;
 6using System.Linq;
 7using System.Threading;
 8using MediaBrowser.Controller.Configuration;
 9using MediaBrowser.Controller.Entities;
 10using MediaBrowser.Controller.Library;
 11using Microsoft.Extensions.Logging;
 12
 13namespace Emby.Server.Implementations.IO
 14{
 15    public sealed class FileRefresher : IDisposable
 16    {
 17        private readonly ILogger _logger;
 18        private readonly ILibraryManager _libraryManager;
 19        private readonly IServerConfigurationManager _configurationManager;
 20
 121        private readonly List<string> _affectedPaths = new();
 122        private readonly Lock _timerLock = new();
 23        private Timer? _timer;
 24        private bool _disposed;
 25
 26        public FileRefresher(string path, IServerConfigurationManager configurationManager, ILibraryManager libraryManag
 27        {
 128            logger.LogDebug("New file refresher created for {0}", path);
 129            Path = path;
 30
 131            _configurationManager = configurationManager;
 132            _libraryManager = libraryManager;
 133            _logger = logger;
 134            AddPath(path);
 135        }
 36
 37        public event EventHandler<EventArgs>? Completed;
 38
 39        public string Path { get; private set; }
 40
 41        private void AddAffectedPath(string path)
 42        {
 243            ArgumentException.ThrowIfNullOrEmpty(path);
 44
 245            if (!_affectedPaths.Contains(path, StringComparer.Ordinal))
 46            {
 247                _affectedPaths.Add(path);
 48            }
 249        }
 50
 51        public void AddPath(string path)
 52        {
 253            ArgumentException.ThrowIfNullOrEmpty(path);
 54
 55            lock (_timerLock)
 56            {
 257                AddAffectedPath(path);
 258            }
 59
 260            RestartTimer();
 261        }
 62
 63        public void RestartTimer()
 64        {
 265            if (_disposed)
 66            {
 067                return;
 68            }
 69
 70            lock (_timerLock)
 71            {
 272                if (_disposed)
 73                {
 074                    return;
 75                }
 76
 277                if (_timer is null)
 78                {
 179                    _timer = new Timer(OnTimerCallback, null, TimeSpan.FromSeconds(_configurationManager.Configuration.L
 80                }
 81                else
 82                {
 183                    _timer.Change(TimeSpan.FromSeconds(_configurationManager.Configuration.LibraryMonitorDelay), TimeSpa
 84                }
 185            }
 286        }
 87
 88        public void ResetPath(string path, string? affectedFile)
 089        {
 90            lock (_timerLock)
 91            {
 092                _logger.LogDebug("Resetting file refresher from {0} to {1}", Path, path);
 93
 094                Path = path;
 095                AddAffectedPath(path);
 96
 097                if (!string.IsNullOrEmpty(affectedFile))
 98                {
 099                    AddAffectedPath(affectedFile);
 100                }
 0101            }
 102
 0103            RestartTimer();
 0104        }
 105
 106        private void OnTimerCallback(object? state)
 1107        {
 108            List<string> paths;
 109
 110            lock (_timerLock)
 111            {
 1112                if (_disposed)
 113                {
 0114                    return;
 115                }
 116
 1117                paths = _affectedPaths.ToList();
 1118            }
 119
 1120            _logger.LogDebug("Timer stopped.");
 121
 1122            DisposeTimer();
 1123            Completed?.Invoke(this, EventArgs.Empty);
 124
 125            try
 126            {
 1127                ProcessPathChanges(paths);
 1128            }
 0129            catch (Exception ex)
 130            {
 0131                _logger.LogError(ex, "Error processing directory changes");
 0132            }
 1133        }
 134
 135        private void ProcessPathChanges(List<string> paths)
 136        {
 1137            var itemsToRefresh = paths
 1138                .Distinct()
 1139                .Select(TryGetAffectedBaseItem)
 1140                .OfType<BaseItem>()
 1141                .DistinctBy(x => x.Id)
 1142                .ToList();
 143
 2144            foreach (var item in itemsToRefresh)
 145            {
 0146                if (item is AggregateFolder)
 147                {
 148                    continue;
 149                }
 150
 0151                _logger.LogInformation("{Name} ({Path}) will be refreshed.", item.Name, item.Path);
 152
 153                try
 154                {
 0155                    item.ChangedExternally();
 0156                }
 0157                catch (Exception ex)
 158                {
 0159                    _logger.LogError(ex, "Error refreshing {Name}", item.Name);
 0160                }
 161            }
 1162        }
 163
 164        private BaseItem? TryGetAffectedBaseItem(string path)
 165        {
 166            try
 167            {
 2168                return GetAffectedBaseItem(path);
 169            }
 1170            catch (Exception ex)
 171            {
 1172                _logger.LogError(ex, "Error finding the item affected by changes to {Path}", path);
 1173                return null;
 174            }
 2175        }
 176
 177        /// <summary>
 178        /// Gets the affected base item.
 179        /// </summary>
 180        /// <param name="path">The path.</param>
 181        /// <returns>BaseItem.</returns>
 182        private BaseItem? GetAffectedBaseItem(string path)
 183        {
 2184            BaseItem? item = null;
 185
 3186            while (item is null && !string.IsNullOrEmpty(path))
 187            {
 2188                item = _libraryManager.FindByPath(path, null);
 189
 1190                path = System.IO.Path.GetDirectoryName(path) ?? string.Empty;
 191            }
 192
 1193            if (item is not null)
 194            {
 195                // If the item has been deleted find the first valid parent that still exists
 1196                while (!Directory.Exists(item.Path) && !File.Exists(item.Path))
 197                {
 1198                    item = item.GetOwner() ?? item.GetParent();
 199
 1200                    if (item is null)
 201                    {
 202                        break;
 203                    }
 204                }
 205            }
 206
 1207            return item;
 208        }
 209
 210        private void DisposeTimer()
 2211        {
 212            lock (_timerLock)
 213            {
 2214                if (_timer is not null)
 215                {
 1216                    _timer.Dispose();
 1217                    _timer = null;
 218                }
 2219            }
 2220        }
 221
 222        /// <inheritdoc />
 223        public void Dispose()
 224        {
 1225            if (_disposed)
 226            {
 0227                return;
 228            }
 229
 1230            DisposeTimer();
 1231            _disposed = true;
 1232        }
 233    }
 234}