2019-11-01 13:38:54 -04:00
|
|
|
#pragma warning disable CS1591
|
|
|
|
|
2019-01-13 14:54:44 -05:00
|
|
|
using System;
|
2016-05-26 00:11:27 -04:00
|
|
|
using System.Collections.Generic;
|
|
|
|
using System.IO;
|
|
|
|
using System.Linq;
|
2019-02-05 03:49:46 -05:00
|
|
|
using System.Threading;
|
2016-05-26 00:11:27 -04:00
|
|
|
using MediaBrowser.Controller.Configuration;
|
|
|
|
using MediaBrowser.Controller.Entities;
|
|
|
|
using MediaBrowser.Controller.Library;
|
2019-01-13 14:21:32 -05:00
|
|
|
using Microsoft.Extensions.Logging;
|
2016-05-26 00:11:27 -04:00
|
|
|
|
2016-11-04 14:56:47 -04:00
|
|
|
namespace Emby.Server.Implementations.IO
|
2016-05-26 00:11:27 -04:00
|
|
|
{
|
2021-11-15 09:57:07 -05:00
|
|
|
public sealed class FileRefresher : IDisposable
|
2016-05-26 00:11:27 -04:00
|
|
|
{
|
2020-02-06 09:20:23 -05:00
|
|
|
private readonly ILogger _logger;
|
|
|
|
private readonly ILibraryManager _libraryManager;
|
|
|
|
private readonly IServerConfigurationManager _configurationManager;
|
|
|
|
|
2016-05-26 00:11:27 -04:00
|
|
|
private readonly List<string> _affectedPaths = new List<string>();
|
|
|
|
private readonly object _timerLock = new object();
|
2021-11-15 09:57:07 -05:00
|
|
|
private Timer? _timer;
|
2020-07-24 10:37:54 -04:00
|
|
|
private bool _disposed;
|
2016-05-26 00:11:27 -04:00
|
|
|
|
2019-02-06 14:38:42 -05:00
|
|
|
public FileRefresher(string path, IServerConfigurationManager configurationManager, ILibraryManager libraryManager, ILogger logger)
|
2016-05-26 00:11:27 -04:00
|
|
|
{
|
2018-12-13 08:18:25 -05:00
|
|
|
logger.LogDebug("New file refresher created for {0}", path);
|
2016-05-26 16:54:05 -04:00
|
|
|
Path = path;
|
2016-05-26 00:11:27 -04:00
|
|
|
|
2020-02-06 09:20:23 -05:00
|
|
|
_configurationManager = configurationManager;
|
|
|
|
_libraryManager = libraryManager;
|
|
|
|
_logger = logger;
|
2016-07-02 22:47:39 -04:00
|
|
|
AddPath(path);
|
2016-05-26 00:11:27 -04:00
|
|
|
}
|
|
|
|
|
2021-11-15 09:57:07 -05:00
|
|
|
public event EventHandler<EventArgs>? Completed;
|
2020-02-06 09:20:23 -05:00
|
|
|
|
|
|
|
public string Path { get; private set; }
|
|
|
|
|
2016-05-26 16:54:05 -04:00
|
|
|
private void AddAffectedPath(string path)
|
|
|
|
{
|
2018-09-12 13:26:21 -04:00
|
|
|
if (string.IsNullOrEmpty(path))
|
2016-10-17 12:41:08 -04:00
|
|
|
{
|
2019-01-06 15:50:43 -05:00
|
|
|
throw new ArgumentNullException(nameof(path));
|
2016-10-17 12:41:08 -04:00
|
|
|
}
|
|
|
|
|
2016-05-26 16:54:05 -04:00
|
|
|
if (!_affectedPaths.Contains(path, StringComparer.Ordinal))
|
|
|
|
{
|
|
|
|
_affectedPaths.Add(path);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
public void AddPath(string path)
|
|
|
|
{
|
2018-09-12 13:26:21 -04:00
|
|
|
if (string.IsNullOrEmpty(path))
|
2016-10-17 12:41:08 -04:00
|
|
|
{
|
2019-01-06 15:50:43 -05:00
|
|
|
throw new ArgumentNullException(nameof(path));
|
2016-10-17 12:41:08 -04:00
|
|
|
}
|
|
|
|
|
2016-05-26 16:54:05 -04:00
|
|
|
lock (_timerLock)
|
|
|
|
{
|
|
|
|
AddAffectedPath(path);
|
|
|
|
}
|
2019-03-28 18:19:56 -04:00
|
|
|
|
2016-05-26 16:54:05 -04:00
|
|
|
RestartTimer();
|
|
|
|
}
|
|
|
|
|
|
|
|
public void RestartTimer()
|
2016-05-26 00:11:27 -04:00
|
|
|
{
|
2016-08-10 23:56:01 -04:00
|
|
|
if (_disposed)
|
|
|
|
{
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
2016-05-26 00:11:27 -04:00
|
|
|
lock (_timerLock)
|
|
|
|
{
|
2016-09-17 02:08:38 -04:00
|
|
|
if (_disposed)
|
|
|
|
{
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
2022-12-05 09:00:20 -05:00
|
|
|
if (_timer is null)
|
2016-05-26 00:11:27 -04:00
|
|
|
{
|
2020-02-06 09:20:23 -05:00
|
|
|
_timer = new Timer(OnTimerCallback, null, TimeSpan.FromSeconds(_configurationManager.Configuration.LibraryMonitorDelay), TimeSpan.FromMilliseconds(-1));
|
2016-05-26 00:11:27 -04:00
|
|
|
}
|
|
|
|
else
|
|
|
|
{
|
2020-02-06 09:20:23 -05:00
|
|
|
_timer.Change(TimeSpan.FromSeconds(_configurationManager.Configuration.LibraryMonitorDelay), TimeSpan.FromMilliseconds(-1));
|
2016-05-26 00:11:27 -04:00
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2016-05-26 16:54:05 -04:00
|
|
|
public void ResetPath(string path, string affectedFile)
|
|
|
|
{
|
|
|
|
lock (_timerLock)
|
|
|
|
{
|
2020-02-06 09:20:23 -05:00
|
|
|
_logger.LogDebug("Resetting file refresher from {0} to {1}", Path, path);
|
2016-05-26 16:54:05 -04:00
|
|
|
|
|
|
|
Path = path;
|
|
|
|
AddAffectedPath(path);
|
|
|
|
|
2018-09-12 13:26:21 -04:00
|
|
|
if (!string.IsNullOrEmpty(affectedFile))
|
2016-05-26 16:54:05 -04:00
|
|
|
{
|
|
|
|
AddAffectedPath(affectedFile);
|
|
|
|
}
|
|
|
|
}
|
2019-03-28 18:19:56 -04:00
|
|
|
|
2016-05-26 16:54:05 -04:00
|
|
|
RestartTimer();
|
|
|
|
}
|
|
|
|
|
2021-11-15 09:57:07 -05:00
|
|
|
private void OnTimerCallback(object? state)
|
2016-05-26 00:11:27 -04:00
|
|
|
{
|
2016-06-11 11:56:15 -04:00
|
|
|
List<string> paths;
|
|
|
|
|
|
|
|
lock (_timerLock)
|
|
|
|
{
|
|
|
|
paths = _affectedPaths.ToList();
|
|
|
|
}
|
|
|
|
|
2020-02-06 09:20:23 -05:00
|
|
|
_logger.LogDebug("Timer stopped.");
|
2016-05-26 00:11:27 -04:00
|
|
|
|
|
|
|
DisposeTimer();
|
2018-12-28 09:21:02 -05:00
|
|
|
Completed?.Invoke(this, EventArgs.Empty);
|
2016-05-26 00:11:27 -04:00
|
|
|
|
|
|
|
try
|
|
|
|
{
|
2021-11-15 09:57:07 -05:00
|
|
|
ProcessPathChanges(paths);
|
2016-05-26 00:11:27 -04:00
|
|
|
}
|
|
|
|
catch (Exception ex)
|
|
|
|
{
|
2020-02-06 09:20:23 -05:00
|
|
|
_logger.LogError(ex, "Error processing directory changes");
|
2016-05-26 00:11:27 -04:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2017-08-16 13:30:16 -04:00
|
|
|
private void ProcessPathChanges(List<string> paths)
|
2016-05-26 00:11:27 -04:00
|
|
|
{
|
2021-11-15 09:57:07 -05:00
|
|
|
IEnumerable<BaseItem> itemsToRefresh = paths
|
2016-09-20 15:43:27 -04:00
|
|
|
.Distinct(StringComparer.OrdinalIgnoreCase)
|
2016-05-26 00:11:27 -04:00
|
|
|
.Select(GetAffectedBaseItem)
|
2022-12-05 09:01:13 -05:00
|
|
|
.Where(item => item is not null)
|
2021-11-15 09:57:07 -05:00
|
|
|
.GroupBy(x => x!.Id) // Removed null values in the previous .Where()
|
|
|
|
.Select(x => x.First())!;
|
2016-05-26 00:11:27 -04:00
|
|
|
|
|
|
|
foreach (var item in itemsToRefresh)
|
|
|
|
{
|
2017-10-03 14:39:37 -04:00
|
|
|
if (item is AggregateFolder)
|
|
|
|
{
|
|
|
|
continue;
|
|
|
|
}
|
|
|
|
|
2020-08-31 16:20:19 -04:00
|
|
|
_logger.LogInformation("{Name} ({Path}) will be refreshed.", item.Name, item.Path);
|
2016-05-26 00:11:27 -04:00
|
|
|
|
|
|
|
try
|
|
|
|
{
|
2017-05-27 03:19:09 -04:00
|
|
|
item.ChangedExternally();
|
2016-05-26 00:11:27 -04:00
|
|
|
}
|
|
|
|
catch (IOException ex)
|
|
|
|
{
|
2019-01-07 18:27:46 -05:00
|
|
|
// For now swallow and log.
|
2016-05-26 00:11:27 -04:00
|
|
|
// Research item: If an IOException occurs, the item may be in a disconnected state (media unavailable)
|
|
|
|
// Should we remove it from it's parent?
|
2020-08-31 16:20:19 -04:00
|
|
|
_logger.LogError(ex, "Error refreshing {Name}", item.Name);
|
2016-05-26 00:11:27 -04:00
|
|
|
}
|
|
|
|
catch (Exception ex)
|
|
|
|
{
|
2020-08-31 16:20:19 -04:00
|
|
|
_logger.LogError(ex, "Error refreshing {Name}", item.Name);
|
2016-05-26 00:11:27 -04:00
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
/// <summary>
|
|
|
|
/// Gets the affected base item.
|
|
|
|
/// </summary>
|
|
|
|
/// <param name="path">The path.</param>
|
|
|
|
/// <returns>BaseItem.</returns>
|
2021-11-15 09:57:07 -05:00
|
|
|
private BaseItem? GetAffectedBaseItem(string path)
|
2016-05-26 00:11:27 -04:00
|
|
|
{
|
2021-11-15 09:57:07 -05:00
|
|
|
BaseItem? item = null;
|
2016-05-26 00:11:27 -04:00
|
|
|
|
2022-12-05 09:00:20 -05:00
|
|
|
while (item is null && !string.IsNullOrEmpty(path))
|
2016-05-26 00:11:27 -04:00
|
|
|
{
|
2020-02-06 09:20:23 -05:00
|
|
|
item = _libraryManager.FindByPath(path, null);
|
2016-05-26 00:11:27 -04:00
|
|
|
|
2021-11-15 09:57:07 -05:00
|
|
|
path = System.IO.Path.GetDirectoryName(path) ?? string.Empty;
|
2016-05-26 00:11:27 -04:00
|
|
|
}
|
|
|
|
|
2022-12-05 09:01:13 -05:00
|
|
|
if (item is not null)
|
2016-05-26 00:11:27 -04:00
|
|
|
{
|
|
|
|
// If the item has been deleted find the first valid parent that still exists
|
2019-01-26 16:59:53 -05:00
|
|
|
while (!Directory.Exists(item.Path) && !File.Exists(item.Path))
|
2016-05-26 00:11:27 -04:00
|
|
|
{
|
2018-09-12 13:26:21 -04:00
|
|
|
item = item.GetOwner() ?? item.GetParent();
|
2016-05-26 00:11:27 -04:00
|
|
|
|
2022-12-05 09:00:20 -05:00
|
|
|
if (item is null)
|
2016-05-26 00:11:27 -04:00
|
|
|
{
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
return item;
|
|
|
|
}
|
|
|
|
|
2016-05-26 16:54:05 -04:00
|
|
|
private void DisposeTimer()
|
2016-05-26 00:11:27 -04:00
|
|
|
{
|
|
|
|
lock (_timerLock)
|
|
|
|
{
|
2022-12-05 09:01:13 -05:00
|
|
|
if (_timer is not null)
|
2016-05-26 00:11:27 -04:00
|
|
|
{
|
|
|
|
_timer.Dispose();
|
2016-09-17 02:08:38 -04:00
|
|
|
_timer = null;
|
2016-05-26 00:11:27 -04:00
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2020-08-31 16:20:19 -04:00
|
|
|
/// <inheritdoc />
|
2016-05-26 00:11:27 -04:00
|
|
|
public void Dispose()
|
|
|
|
{
|
2021-12-15 12:25:36 -05:00
|
|
|
if (_disposed)
|
|
|
|
{
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
2016-05-26 00:11:27 -04:00
|
|
|
DisposeTimer();
|
2021-12-15 12:25:36 -05:00
|
|
|
_disposed = true;
|
2020-07-24 10:37:54 -04:00
|
|
|
GC.SuppressFinalize(this);
|
2016-05-26 00:11:27 -04:00
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|