From ad82c9f5e95e2b1f94ba7adda047dbfbc38004ea Mon Sep 17 00:00:00 2001 From: Luke Pulverenti Date: Tue, 28 Jan 2014 13:37:01 -0500 Subject: New provider system. Only for people right now --- MediaBrowser.ServerApplication/ApplicationHost.cs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) (limited to 'MediaBrowser.ServerApplication/ApplicationHost.cs') diff --git a/MediaBrowser.ServerApplication/ApplicationHost.cs b/MediaBrowser.ServerApplication/ApplicationHost.cs index b78a0f36d..3553996b0 100644 --- a/MediaBrowser.ServerApplication/ApplicationHost.cs +++ b/MediaBrowser.ServerApplication/ApplicationHost.cs @@ -33,6 +33,7 @@ using MediaBrowser.Model.MediaInfo; using MediaBrowser.Model.System; using MediaBrowser.Model.Updates; using MediaBrowser.Providers; +using MediaBrowser.Providers.Manager; using MediaBrowser.Server.Implementations; using MediaBrowser.Server.Implementations.BdInfo; using MediaBrowser.Server.Implementations.Configuration; @@ -47,7 +48,6 @@ using MediaBrowser.Server.Implementations.LiveTv; using MediaBrowser.Server.Implementations.Localization; using MediaBrowser.Server.Implementations.MediaEncoder; using MediaBrowser.Server.Implementations.Persistence; -using MediaBrowser.Server.Implementations.Providers; using MediaBrowser.Server.Implementations.ServerManager; using MediaBrowser.Server.Implementations.Session; using MediaBrowser.Server.Implementations.WebSocket; @@ -491,7 +491,7 @@ namespace MediaBrowser.ServerApplication GetExports(), GetExports()); - ProviderManager.AddParts(GetExports(), GetExports()); + ProviderManager.AddParts(GetExports(), GetExports(), GetExports(), GetExports()); ImageProcessor.AddParts(GetExports()); -- cgit v1.2.3 From 7c5b22246357ef6bcd6d74e61a8fe6a8a9d4df3e Mon Sep 17 00:00:00 2001 From: Luke Pulverenti Date: Tue, 28 Jan 2014 16:25:10 -0500 Subject: Created ILibraryMonitor to replace IDirectoryWatchers --- .../Library/LibraryStructureService.cs | 37 +- MediaBrowser.Controller/IO/IDirectoryWatchers.cs | 29 - MediaBrowser.Controller/Library/ILibraryMonitor.cs | 36 ++ .../MediaBrowser.Controller.csproj | 2 +- .../Configuration/ServerConfiguration.cs | 4 +- MediaBrowser.Providers/Manager/ImageSaver.cs | 22 +- MediaBrowser.Providers/Manager/MetadataService.cs | 18 +- MediaBrowser.Providers/Manager/ProviderManager.cs | 16 +- .../FileOrganization/EpisodeFileOrganizer.cs | 16 +- .../FileOrganization/FileOrganizationService.cs | 16 +- .../FileOrganization/OrganizerScheduledTask.cs | 8 +- .../FileOrganization/TvFolderOrganizer.cs | 8 +- .../IO/DirectoryWatchers.cs | 636 --------------------- .../IO/LibraryMonitor.cs | 569 ++++++++++++++++++ .../Library/LibraryManager.cs | 16 +- .../MediaBrowser.Server.Implementations.csproj | 2 +- MediaBrowser.ServerApplication/ApplicationHost.cs | 12 +- 17 files changed, 680 insertions(+), 767 deletions(-) delete mode 100644 MediaBrowser.Controller/IO/IDirectoryWatchers.cs create mode 100644 MediaBrowser.Controller/Library/ILibraryMonitor.cs delete mode 100644 MediaBrowser.Server.Implementations/IO/DirectoryWatchers.cs create mode 100644 MediaBrowser.Server.Implementations/IO/LibraryMonitor.cs (limited to 'MediaBrowser.ServerApplication/ApplicationHost.cs') diff --git a/MediaBrowser.Api/Library/LibraryStructureService.cs b/MediaBrowser.Api/Library/LibraryStructureService.cs index 775907379..56b0e01c3 100644 --- a/MediaBrowser.Api/Library/LibraryStructureService.cs +++ b/MediaBrowser.Api/Library/LibraryStructureService.cs @@ -187,7 +187,7 @@ namespace MediaBrowser.Api.Library /// private readonly ILibraryManager _libraryManager; - private readonly IDirectoryWatchers _directoryWatchers; + private readonly ILibraryMonitor _libraryMonitor; private readonly IFileSystem _fileSystem; private readonly ILogger _logger; @@ -199,7 +199,7 @@ namespace MediaBrowser.Api.Library /// The user manager. /// The library manager. /// appPaths - public LibraryStructureService(IServerApplicationPaths appPaths, IUserManager userManager, ILibraryManager libraryManager, IDirectoryWatchers directoryWatchers, IFileSystem fileSystem, ILogger logger) + public LibraryStructureService(IServerApplicationPaths appPaths, IUserManager userManager, ILibraryManager libraryManager, ILibraryMonitor libraryMonitor, IFileSystem fileSystem, ILogger logger) { if (appPaths == null) { @@ -209,7 +209,7 @@ namespace MediaBrowser.Api.Library _userManager = userManager; _appPaths = appPaths; _libraryManager = libraryManager; - _directoryWatchers = directoryWatchers; + _libraryMonitor = libraryMonitor; _fileSystem = fileSystem; _logger = logger; } @@ -270,8 +270,7 @@ namespace MediaBrowser.Api.Library throw new ArgumentException("There is already a media collection with the name " + name + "."); } - _directoryWatchers.Stop(); - _directoryWatchers.TemporarilyIgnore(virtualFolderPath); + _libraryMonitor.Stop(); try { @@ -294,10 +293,8 @@ namespace MediaBrowser.Api.Library // No need to start if scanning the library because it will handle it if (!request.RefreshLibrary) { - _directoryWatchers.Start(); + _libraryMonitor.Start(); } - - _directoryWatchers.RemoveTempIgnore(virtualFolderPath); } if (request.RefreshLibrary) @@ -348,9 +345,7 @@ namespace MediaBrowser.Api.Library throw new ArgumentException("There is already a media collection with the name " + newPath + "."); } - _directoryWatchers.Stop(); - _directoryWatchers.TemporarilyIgnore(currentPath); - _directoryWatchers.TemporarilyIgnore(newPath); + _libraryMonitor.Stop(); try { @@ -376,11 +371,8 @@ namespace MediaBrowser.Api.Library // No need to start if scanning the library because it will handle it if (!request.RefreshLibrary) { - _directoryWatchers.Start(); + _libraryMonitor.Start(); } - - _directoryWatchers.RemoveTempIgnore(currentPath); - _directoryWatchers.RemoveTempIgnore(newPath); } if (request.RefreshLibrary) @@ -420,8 +412,7 @@ namespace MediaBrowser.Api.Library throw new DirectoryNotFoundException("The media folder does not exist"); } - _directoryWatchers.Stop(); - _directoryWatchers.TemporarilyIgnore(path); + _libraryMonitor.Stop(); try { @@ -437,10 +428,8 @@ namespace MediaBrowser.Api.Library // No need to start if scanning the library because it will handle it if (!request.RefreshLibrary) { - _directoryWatchers.Start(); + _libraryMonitor.Start(); } - - _directoryWatchers.RemoveTempIgnore(path); } if (request.RefreshLibrary) @@ -460,7 +449,7 @@ namespace MediaBrowser.Api.Library throw new ArgumentNullException("request"); } - _directoryWatchers.Stop(); + _libraryMonitor.Stop(); try { @@ -485,7 +474,7 @@ namespace MediaBrowser.Api.Library // No need to start if scanning the library because it will handle it if (!request.RefreshLibrary) { - _directoryWatchers.Start(); + _libraryMonitor.Start(); } } @@ -506,7 +495,7 @@ namespace MediaBrowser.Api.Library throw new ArgumentNullException("request"); } - _directoryWatchers.Stop(); + _libraryMonitor.Stop(); try { @@ -531,7 +520,7 @@ namespace MediaBrowser.Api.Library // No need to start if scanning the library because it will handle it if (!request.RefreshLibrary) { - _directoryWatchers.Start(); + _libraryMonitor.Start(); } } diff --git a/MediaBrowser.Controller/IO/IDirectoryWatchers.cs b/MediaBrowser.Controller/IO/IDirectoryWatchers.cs deleted file mode 100644 index 9a43ee8ac..000000000 --- a/MediaBrowser.Controller/IO/IDirectoryWatchers.cs +++ /dev/null @@ -1,29 +0,0 @@ -using System; - -namespace MediaBrowser.Controller.IO -{ - public interface IDirectoryWatchers : IDisposable - { - /// - /// Add the path to our temporary ignore list. Use when writing to a path within our listening scope. - /// - /// The path. - void TemporarilyIgnore(string path); - - /// - /// Removes the temp ignore. - /// - /// The path. - void RemoveTempIgnore(string path); - - /// - /// Starts this instance. - /// - void Start(); - - /// - /// Stops this instance. - /// - void Stop(); - } -} \ No newline at end of file diff --git a/MediaBrowser.Controller/Library/ILibraryMonitor.cs b/MediaBrowser.Controller/Library/ILibraryMonitor.cs new file mode 100644 index 000000000..918382f04 --- /dev/null +++ b/MediaBrowser.Controller/Library/ILibraryMonitor.cs @@ -0,0 +1,36 @@ +using System; + +namespace MediaBrowser.Controller.Library +{ + public interface ILibraryMonitor : IDisposable + { + /// + /// Starts this instance. + /// + void Start(); + + /// + /// Stops this instance. + /// + void Stop(); + + /// + /// Reports the file system change beginning. + /// + /// The path. + void ReportFileSystemChangeBeginning(string path); + + /// + /// Reports the file system change complete. + /// + /// The path. + /// if set to true [refresh path]. + void ReportFileSystemChangeComplete(string path, bool refreshPath); + + /// + /// Reports the file system changed. + /// + /// The path. + void ReportFileSystemChanged(string path); + } +} \ No newline at end of file diff --git a/MediaBrowser.Controller/MediaBrowser.Controller.csproj b/MediaBrowser.Controller/MediaBrowser.Controller.csproj index ef87c30c7..45297ef3d 100644 --- a/MediaBrowser.Controller/MediaBrowser.Controller.csproj +++ b/MediaBrowser.Controller/MediaBrowser.Controller.csproj @@ -182,7 +182,7 @@ - + diff --git a/MediaBrowser.Model/Configuration/ServerConfiguration.cs b/MediaBrowser.Model/Configuration/ServerConfiguration.cs index f017fdf16..923c5ab74 100644 --- a/MediaBrowser.Model/Configuration/ServerConfiguration.cs +++ b/MediaBrowser.Model/Configuration/ServerConfiguration.cs @@ -165,7 +165,7 @@ namespace MediaBrowser.Model.Configuration /// different directories and files. /// /// The file watcher delay. - public int FileWatcherDelay { get; set; } + public int RealtimeWatcherDelay { get; set; } /// /// Gets or sets a value indicating whether [enable dashboard response caching]. @@ -250,7 +250,7 @@ namespace MediaBrowser.Model.Configuration MaxResumePct = 90; MinResumeDurationSeconds = Convert.ToInt32(TimeSpan.FromMinutes(5).TotalSeconds); - FileWatcherDelay = 8; + RealtimeWatcherDelay = 20; RecentItemDays = 10; diff --git a/MediaBrowser.Providers/Manager/ImageSaver.cs b/MediaBrowser.Providers/Manager/ImageSaver.cs index a75ce88ae..56a1a9d4f 100644 --- a/MediaBrowser.Providers/Manager/ImageSaver.cs +++ b/MediaBrowser.Providers/Manager/ImageSaver.cs @@ -3,7 +3,7 @@ using MediaBrowser.Controller.Configuration; using MediaBrowser.Controller.Entities; using MediaBrowser.Controller.Entities.Audio; using MediaBrowser.Controller.Entities.TV; -using MediaBrowser.Controller.IO; +using MediaBrowser.Controller.Library; using MediaBrowser.Model.Configuration; using MediaBrowser.Model.Entities; using MediaBrowser.Model.Logging; @@ -36,7 +36,7 @@ namespace MediaBrowser.Providers.Manager /// /// The _directory watchers /// - private readonly IDirectoryWatchers _directoryWatchers; + private readonly ILibraryMonitor _libraryMonitor; private readonly IFileSystem _fileSystem; private readonly ILogger _logger; @@ -44,11 +44,11 @@ namespace MediaBrowser.Providers.Manager /// Initializes a new instance of the class. /// /// The config. - /// The directory watchers. - public ImageSaver(IServerConfigurationManager config, IDirectoryWatchers directoryWatchers, IFileSystem fileSystem, ILogger logger) + /// The directory watchers. + public ImageSaver(IServerConfigurationManager config, ILibraryMonitor libraryMonitor, IFileSystem fileSystem, ILogger logger) { _config = config; - _directoryWatchers = directoryWatchers; + _libraryMonitor = libraryMonitor; _fileSystem = fileSystem; _logger = logger; _remoteImageCache = new FileSystemRepository(config.ApplicationPaths.DownloadedImagesDataPath); @@ -160,7 +160,7 @@ namespace MediaBrowser.Providers.Manager // Delete the current path if (!string.IsNullOrEmpty(currentPath) && !paths.Contains(currentPath, StringComparer.OrdinalIgnoreCase)) { - _directoryWatchers.TemporarilyIgnore(currentPath); + _libraryMonitor.ReportFileSystemChangeBeginning(currentPath); try { @@ -179,7 +179,7 @@ namespace MediaBrowser.Providers.Manager } finally { - _directoryWatchers.RemoveTempIgnore(currentPath); + _libraryMonitor.ReportFileSystemChangeComplete(currentPath, false); } } } @@ -197,8 +197,8 @@ namespace MediaBrowser.Providers.Manager var parentFolder = Path.GetDirectoryName(path); - _directoryWatchers.TemporarilyIgnore(path); - _directoryWatchers.TemporarilyIgnore(parentFolder); + _libraryMonitor.ReportFileSystemChangeBeginning(path); + _libraryMonitor.ReportFileSystemChangeBeginning(parentFolder); try { @@ -223,8 +223,8 @@ namespace MediaBrowser.Providers.Manager } finally { - _directoryWatchers.RemoveTempIgnore(path); - _directoryWatchers.RemoveTempIgnore(parentFolder); + _libraryMonitor.ReportFileSystemChangeComplete(path, false); + _libraryMonitor.ReportFileSystemChangeComplete(parentFolder, false); } } diff --git a/MediaBrowser.Providers/Manager/MetadataService.cs b/MediaBrowser.Providers/Manager/MetadataService.cs index dbe70b93d..5dde3098a 100644 --- a/MediaBrowser.Providers/Manager/MetadataService.cs +++ b/MediaBrowser.Providers/Manager/MetadataService.cs @@ -38,7 +38,6 @@ namespace MediaBrowser.Providers.Manager public void AddParts(IEnumerable providers, IEnumerable imageProviders) { _providers = providers.OfType>() - .OrderBy(GetSortOrder) .ToArray(); _imageProviders = imageProviders.OrderBy(i => i.Order).ToArray(); @@ -179,21 +178,6 @@ namespace MediaBrowser.Providers.Manager return providers; } - /// - /// Gets the sort order. - /// - /// The provider. - /// System.Int32. - protected virtual int GetSortOrder(IMetadataProvider provider) - { - if (provider is IRemoteMetadataProvider) - { - return 1; - } - - return 0; - } - /// /// Determines whether this instance can refresh the specified provider. /// @@ -217,7 +201,7 @@ namespace MediaBrowser.Providers.Manager protected abstract Task SaveItem(TItemType item, ItemUpdateType reason, CancellationToken cancellationToken); - protected virtual ItemId GetId(TItemType item) + protected virtual ItemId GetId(IHasMetadata item) { return new ItemId { diff --git a/MediaBrowser.Providers/Manager/ProviderManager.cs b/MediaBrowser.Providers/Manager/ProviderManager.cs index ebad2e6e0..faad15669 100644 --- a/MediaBrowser.Providers/Manager/ProviderManager.cs +++ b/MediaBrowser.Providers/Manager/ProviderManager.cs @@ -36,7 +36,7 @@ namespace MediaBrowser.Providers.Manager /// /// The _directory watchers /// - private readonly IDirectoryWatchers _directoryWatchers; + private readonly ILibraryMonitor _libraryMonitor; /// /// Gets or sets the configuration manager. @@ -57,23 +57,23 @@ namespace MediaBrowser.Providers.Manager private readonly IItemRepository _itemRepo; - private IMetadataService[] _metadataServices = {}; + private IMetadataService[] _metadataServices = { }; /// /// Initializes a new instance of the class. /// /// The HTTP client. /// The configuration manager. - /// The directory watchers. + /// The directory watchers. /// The log manager. /// The file system. /// The item repo. - public ProviderManager(IHttpClient httpClient, IServerConfigurationManager configurationManager, IDirectoryWatchers directoryWatchers, ILogManager logManager, IFileSystem fileSystem, IItemRepository itemRepo) + public ProviderManager(IHttpClient httpClient, IServerConfigurationManager configurationManager, ILibraryMonitor libraryMonitor, ILogManager logManager, IFileSystem fileSystem, IItemRepository itemRepo) { _logger = logManager.GetLogger("ProviderManager"); _httpClient = httpClient; ConfigurationManager = configurationManager; - _directoryWatchers = directoryWatchers; + _libraryMonitor = libraryMonitor; _fileSystem = fileSystem; _itemRepo = itemRepo; } @@ -315,7 +315,7 @@ namespace MediaBrowser.Providers.Manager } //Tell the watchers to ignore - _directoryWatchers.TemporarilyIgnore(path); + _libraryMonitor.ReportFileSystemChangeBeginning(path); if (dataToSave.CanSeek) { @@ -338,7 +338,7 @@ namespace MediaBrowser.Providers.Manager finally { //Remove the ignore - _directoryWatchers.RemoveTempIgnore(path); + _libraryMonitor.ReportFileSystemChangeComplete(path, false); } } @@ -380,7 +380,7 @@ namespace MediaBrowser.Providers.Manager /// Task. public Task SaveImage(BaseItem item, Stream source, string mimeType, ImageType type, int? imageIndex, string sourceUrl, CancellationToken cancellationToken) { - return new ImageSaver(ConfigurationManager, _directoryWatchers, _fileSystem, _logger).SaveImage(item, source, mimeType, type, imageIndex, sourceUrl, cancellationToken); + return new ImageSaver(ConfigurationManager, _libraryMonitor, _fileSystem, _logger).SaveImage(item, source, mimeType, type, imageIndex, sourceUrl, cancellationToken); } /// diff --git a/MediaBrowser.Server.Implementations/FileOrganization/EpisodeFileOrganizer.cs b/MediaBrowser.Server.Implementations/FileOrganization/EpisodeFileOrganizer.cs index 0a2dd5ae0..a2e094e9a 100644 --- a/MediaBrowser.Server.Implementations/FileOrganization/EpisodeFileOrganizer.cs +++ b/MediaBrowser.Server.Implementations/FileOrganization/EpisodeFileOrganizer.cs @@ -22,7 +22,7 @@ namespace MediaBrowser.Server.Implementations.FileOrganization { public class EpisodeFileOrganizer { - private readonly IDirectoryWatchers _directoryWatchers; + private readonly ILibraryMonitor _libraryMonitor; private readonly ILibraryManager _libraryManager; private readonly ILogger _logger; private readonly IFileSystem _fileSystem; @@ -31,14 +31,14 @@ namespace MediaBrowser.Server.Implementations.FileOrganization private readonly CultureInfo _usCulture = new CultureInfo("en-US"); - public EpisodeFileOrganizer(IFileOrganizationService organizationService, IServerConfigurationManager config, IFileSystem fileSystem, ILogger logger, ILibraryManager libraryManager, IDirectoryWatchers directoryWatchers) + public EpisodeFileOrganizer(IFileOrganizationService organizationService, IServerConfigurationManager config, IFileSystem fileSystem, ILogger logger, ILibraryManager libraryManager, ILibraryMonitor libraryMonitor) { _organizationService = organizationService; _config = config; _fileSystem = fileSystem; _logger = logger; _libraryManager = libraryManager; - _directoryWatchers = directoryWatchers; + _libraryMonitor = libraryMonitor; } public async Task OrganizeEpisodeFile(string path, TvFileOrganizationOptions options, bool overwriteExisting) @@ -174,6 +174,8 @@ namespace MediaBrowser.Server.Implementations.FileOrganization { _logger.Debug("Removing duplicate episode {0}", path); + _libraryMonitor.ReportFileSystemChangeBeginning(path); + try { File.Delete(path); @@ -182,6 +184,10 @@ namespace MediaBrowser.Server.Implementations.FileOrganization { _logger.ErrorException("Error removing duplicate episode", ex, path); } + finally + { + _libraryMonitor.ReportFileSystemChangeComplete(path, true); + } } } } @@ -232,7 +238,7 @@ namespace MediaBrowser.Server.Implementations.FileOrganization private void PerformFileSorting(TvFileOrganizationOptions options, FileOrganizationResult result) { - _directoryWatchers.TemporarilyIgnore(result.TargetPath); + _libraryMonitor.ReportFileSystemChangeBeginning(result.TargetPath); Directory.CreateDirectory(Path.GetDirectoryName(result.TargetPath)); @@ -264,7 +270,7 @@ namespace MediaBrowser.Server.Implementations.FileOrganization } finally { - _directoryWatchers.RemoveTempIgnore(result.TargetPath); + _libraryMonitor.ReportFileSystemChangeComplete(result.TargetPath, true); } if (copy) diff --git a/MediaBrowser.Server.Implementations/FileOrganization/FileOrganizationService.cs b/MediaBrowser.Server.Implementations/FileOrganization/FileOrganizationService.cs index bbd0f74e5..518a7bb48 100644 --- a/MediaBrowser.Server.Implementations/FileOrganization/FileOrganizationService.cs +++ b/MediaBrowser.Server.Implementations/FileOrganization/FileOrganizationService.cs @@ -21,17 +21,17 @@ namespace MediaBrowser.Server.Implementations.FileOrganization private readonly ITaskManager _taskManager; private readonly IFileOrganizationRepository _repo; private readonly ILogger _logger; - private readonly IDirectoryWatchers _directoryWatchers; + private readonly ILibraryMonitor _libraryMonitor; private readonly ILibraryManager _libraryManager; private readonly IServerConfigurationManager _config; private readonly IFileSystem _fileSystem; - public FileOrganizationService(ITaskManager taskManager, IFileOrganizationRepository repo, ILogger logger, IDirectoryWatchers directoryWatchers, ILibraryManager libraryManager, IServerConfigurationManager config, IFileSystem fileSystem) + public FileOrganizationService(ITaskManager taskManager, IFileOrganizationRepository repo, ILogger logger, ILibraryMonitor libraryMonitor, ILibraryManager libraryManager, IServerConfigurationManager config, IFileSystem fileSystem) { _taskManager = taskManager; _repo = repo; _logger = logger; - _directoryWatchers = directoryWatchers; + _libraryMonitor = libraryMonitor; _libraryManager = libraryManager; _config = config; _fileSystem = fileSystem; @@ -91,13 +91,10 @@ namespace MediaBrowser.Server.Implementations.FileOrganization } var organizer = new EpisodeFileOrganizer(this, _config, _fileSystem, _logger, _libraryManager, - _directoryWatchers); + _libraryMonitor); await organizer.OrganizeEpisodeFile(result.OriginalPath, _config.Configuration.TvFileOrganizationOptions, true) .ConfigureAwait(false); - - await _libraryManager.ValidateMediaLibrary(new Progress(), CancellationToken.None) - .ConfigureAwait(false); } public Task ClearLog() @@ -108,12 +105,9 @@ namespace MediaBrowser.Server.Implementations.FileOrganization public async Task PerformEpisodeOrganization(EpisodeFileOrganizationRequest request) { var organizer = new EpisodeFileOrganizer(this, _config, _fileSystem, _logger, _libraryManager, - _directoryWatchers); + _libraryMonitor); await organizer.OrganizeWithCorrection(request, _config.Configuration.TvFileOrganizationOptions).ConfigureAwait(false); - - await _libraryManager.ValidateMediaLibrary(new Progress(), CancellationToken.None) - .ConfigureAwait(false); } } } diff --git a/MediaBrowser.Server.Implementations/FileOrganization/OrganizerScheduledTask.cs b/MediaBrowser.Server.Implementations/FileOrganization/OrganizerScheduledTask.cs index 340038e4b..3c5e1ed0e 100644 --- a/MediaBrowser.Server.Implementations/FileOrganization/OrganizerScheduledTask.cs +++ b/MediaBrowser.Server.Implementations/FileOrganization/OrganizerScheduledTask.cs @@ -14,16 +14,16 @@ namespace MediaBrowser.Server.Implementations.FileOrganization { public class OrganizerScheduledTask : IScheduledTask, IConfigurableScheduledTask { - private readonly IDirectoryWatchers _directoryWatchers; + private readonly ILibraryMonitor _libraryMonitor; private readonly ILibraryManager _libraryManager; private readonly ILogger _logger; private readonly IFileSystem _fileSystem; private readonly IServerConfigurationManager _config; private readonly IFileOrganizationService _organizationService; - public OrganizerScheduledTask(IDirectoryWatchers directoryWatchers, ILibraryManager libraryManager, ILogger logger, IFileSystem fileSystem, IServerConfigurationManager config, IFileOrganizationService organizationService) + public OrganizerScheduledTask(ILibraryMonitor libraryMonitor, ILibraryManager libraryManager, ILogger logger, IFileSystem fileSystem, IServerConfigurationManager config, IFileOrganizationService organizationService) { - _directoryWatchers = directoryWatchers; + _libraryMonitor = libraryMonitor; _libraryManager = libraryManager; _logger = logger; _fileSystem = fileSystem; @@ -48,7 +48,7 @@ namespace MediaBrowser.Server.Implementations.FileOrganization public Task Execute(CancellationToken cancellationToken, IProgress progress) { - return new TvFolderOrganizer(_libraryManager, _logger, _fileSystem, _directoryWatchers, _organizationService, _config) + return new TvFolderOrganizer(_libraryManager, _logger, _fileSystem, _libraryMonitor, _organizationService, _config) .Organize(_config.Configuration.TvFileOrganizationOptions, cancellationToken, progress); } diff --git a/MediaBrowser.Server.Implementations/FileOrganization/TvFolderOrganizer.cs b/MediaBrowser.Server.Implementations/FileOrganization/TvFolderOrganizer.cs index 6a413f2f0..24f21e339 100644 --- a/MediaBrowser.Server.Implementations/FileOrganization/TvFolderOrganizer.cs +++ b/MediaBrowser.Server.Implementations/FileOrganization/TvFolderOrganizer.cs @@ -18,19 +18,19 @@ namespace MediaBrowser.Server.Implementations.FileOrganization { public class TvFolderOrganizer { - private readonly IDirectoryWatchers _directoryWatchers; + private readonly ILibraryMonitor _libraryMonitor; private readonly ILibraryManager _libraryManager; private readonly ILogger _logger; private readonly IFileSystem _fileSystem; private readonly IFileOrganizationService _organizationService; private readonly IServerConfigurationManager _config; - public TvFolderOrganizer(ILibraryManager libraryManager, ILogger logger, IFileSystem fileSystem, IDirectoryWatchers directoryWatchers, IFileOrganizationService organizationService, IServerConfigurationManager config) + public TvFolderOrganizer(ILibraryManager libraryManager, ILogger logger, IFileSystem fileSystem, ILibraryMonitor libraryMonitor, IFileOrganizationService organizationService, IServerConfigurationManager config) { _libraryManager = libraryManager; _logger = logger; _fileSystem = fileSystem; - _directoryWatchers = directoryWatchers; + _libraryMonitor = libraryMonitor; _organizationService = organizationService; _config = config; } @@ -57,7 +57,7 @@ namespace MediaBrowser.Server.Implementations.FileOrganization foreach (var file in eligibleFiles) { var organizer = new EpisodeFileOrganizer(_organizationService, _config, _fileSystem, _logger, _libraryManager, - _directoryWatchers); + _libraryMonitor); var result = await organizer.OrganizeEpisodeFile(file.FullName, options, false).ConfigureAwait(false); diff --git a/MediaBrowser.Server.Implementations/IO/DirectoryWatchers.cs b/MediaBrowser.Server.Implementations/IO/DirectoryWatchers.cs deleted file mode 100644 index 1efc3bc70..000000000 --- a/MediaBrowser.Server.Implementations/IO/DirectoryWatchers.cs +++ /dev/null @@ -1,636 +0,0 @@ -using MediaBrowser.Common.IO; -using MediaBrowser.Common.ScheduledTasks; -using MediaBrowser.Controller.Configuration; -using MediaBrowser.Controller.Entities; -using MediaBrowser.Controller.IO; -using MediaBrowser.Controller.Library; -using MediaBrowser.Model.Entities; -using MediaBrowser.Model.Logging; -using MediaBrowser.Server.Implementations.ScheduledTasks; -using Microsoft.Win32; -using System; -using System.Collections.Concurrent; -using System.Collections.Generic; -using System.IO; -using System.Linq; -using System.Threading; -using System.Threading.Tasks; - -namespace MediaBrowser.Server.Implementations.IO -{ - /// - /// Class DirectoryWatchers - /// - public class DirectoryWatchers : IDirectoryWatchers - { - /// - /// The file system watchers - /// - private readonly ConcurrentDictionary _fileSystemWatchers = new ConcurrentDictionary(StringComparer.OrdinalIgnoreCase); - /// - /// The update timer - /// - private Timer _updateTimer; - /// - /// The affected paths - /// - private readonly ConcurrentDictionary _affectedPaths = new ConcurrentDictionary(); - - /// - /// A dynamic list of paths that should be ignored. Added to during our own file sytem modifications. - /// - private readonly ConcurrentDictionary _tempIgnoredPaths = new ConcurrentDictionary(StringComparer.OrdinalIgnoreCase); - - /// - /// Any file name ending in any of these will be ignored by the watchers - /// - private readonly IReadOnlyList _alwaysIgnoreFiles = new List { "thumbs.db", "small.jpg", "albumart.jpg" }; - - /// - /// The timer lock - /// - private readonly object _timerLock = new object(); - - /// - /// Add the path to our temporary ignore list. Use when writing to a path within our listening scope. - /// - /// The path. - public void TemporarilyIgnore(string path) - { - _tempIgnoredPaths[path] = path; - } - - /// - /// Removes the temp ignore. - /// - /// The path. - public async void RemoveTempIgnore(string path) - { - // This is an arbitraty amount of time, but delay it because file system writes often trigger events after RemoveTempIgnore has been called. - // Seeing long delays in some situations, especially over the network. - // Seeing delays up to 40 seconds, but not going to ignore changes for that long. - await Task.Delay(1500).ConfigureAwait(false); - - string val; - _tempIgnoredPaths.TryRemove(path, out val); - } - - /// - /// Gets or sets the logger. - /// - /// The logger. - private ILogger Logger { get; set; } - - /// - /// Gets or sets the task manager. - /// - /// The task manager. - private ITaskManager TaskManager { get; set; } - - private ILibraryManager LibraryManager { get; set; } - private IServerConfigurationManager ConfigurationManager { get; set; } - - private readonly IFileSystem _fileSystem; - - /// - /// Initializes a new instance of the class. - /// - public DirectoryWatchers(ILogManager logManager, ITaskManager taskManager, ILibraryManager libraryManager, IServerConfigurationManager configurationManager, IFileSystem fileSystem) - { - if (taskManager == null) - { - throw new ArgumentNullException("taskManager"); - } - - LibraryManager = libraryManager; - TaskManager = taskManager; - Logger = logManager.GetLogger("DirectoryWatchers"); - ConfigurationManager = configurationManager; - _fileSystem = fileSystem; - - SystemEvents.PowerModeChanged += SystemEvents_PowerModeChanged; - } - - /// - /// Handles the PowerModeChanged event of the SystemEvents control. - /// - /// The source of the event. - /// The instance containing the event data. - void SystemEvents_PowerModeChanged(object sender, PowerModeChangedEventArgs e) - { - Stop(); - Start(); - } - - /// - /// Starts this instance. - /// - public void Start() - { - LibraryManager.ItemAdded += LibraryManager_ItemAdded; - LibraryManager.ItemRemoved += LibraryManager_ItemRemoved; - - var pathsToWatch = new List { LibraryManager.RootFolder.Path }; - - var paths = LibraryManager - .RootFolder - .Children - .OfType() - .Where(i => i.LocationType != LocationType.Remote && i.LocationType != LocationType.Virtual) - .SelectMany(f => - { - try - { - // Accessing ResolveArgs could involve file system access - return f.ResolveArgs.PhysicalLocations; - } - catch (IOException) - { - return new string[] { }; - } - - }) - .Distinct(StringComparer.OrdinalIgnoreCase) - .OrderBy(i => i) - .ToList(); - - foreach (var path in paths) - { - if (!ContainsParentFolder(pathsToWatch, path)) - { - pathsToWatch.Add(path); - } - } - - foreach (var path in pathsToWatch) - { - StartWatchingPath(path); - } - } - - /// - /// Handles the ItemRemoved event of the LibraryManager control. - /// - /// The source of the event. - /// The instance containing the event data. - void LibraryManager_ItemRemoved(object sender, ItemChangeEventArgs e) - { - if (e.Item.Parent is AggregateFolder) - { - StopWatchingPath(e.Item.Path); - } - } - - /// - /// Handles the ItemAdded event of the LibraryManager control. - /// - /// The source of the event. - /// The instance containing the event data. - void LibraryManager_ItemAdded(object sender, ItemChangeEventArgs e) - { - if (e.Item.Parent is AggregateFolder) - { - StartWatchingPath(e.Item.Path); - } - } - - /// - /// Examine a list of strings assumed to be file paths to see if it contains a parent of - /// the provided path. - /// - /// The LST. - /// The path. - /// true if [contains parent folder] [the specified LST]; otherwise, false. - /// path - private static bool ContainsParentFolder(IEnumerable lst, string path) - { - if (string.IsNullOrEmpty(path)) - { - throw new ArgumentNullException("path"); - } - - path = path.TrimEnd(Path.DirectorySeparatorChar); - - return lst.Any(str => - { - //this should be a little quicker than examining each actual parent folder... - var compare = str.TrimEnd(Path.DirectorySeparatorChar); - - return (path.Equals(compare, StringComparison.OrdinalIgnoreCase) || (path.StartsWith(compare, StringComparison.OrdinalIgnoreCase) && path[compare.Length] == Path.DirectorySeparatorChar)); - }); - } - - /// - /// Starts the watching path. - /// - /// The path. - private void StartWatchingPath(string path) - { - // Creating a FileSystemWatcher over the LAN can take hundreds of milliseconds, so wrap it in a Task to do them all in parallel - Task.Run(() => - { - var newWatcher = new FileSystemWatcher(path, "*") { IncludeSubdirectories = true, InternalBufferSize = 32767 }; - - newWatcher.Created += watcher_Changed; - newWatcher.Deleted += watcher_Changed; - newWatcher.Renamed += watcher_Changed; - newWatcher.Changed += watcher_Changed; - - newWatcher.Error += watcher_Error; - - try - { - if (_fileSystemWatchers.TryAdd(path, newWatcher)) - { - newWatcher.EnableRaisingEvents = true; - Logger.Info("Watching directory " + path); - } - else - { - Logger.Info("Unable to add directory watcher for {0}. It already exists in the dictionary." + path); - newWatcher.Dispose(); - } - - } - catch (IOException ex) - { - Logger.ErrorException("Error watching path: {0}", ex, path); - } - catch (PlatformNotSupportedException ex) - { - Logger.ErrorException("Error watching path: {0}", ex, path); - } - }); - } - - /// - /// Stops the watching path. - /// - /// The path. - private void StopWatchingPath(string path) - { - FileSystemWatcher watcher; - - if (_fileSystemWatchers.TryGetValue(path, out watcher)) - { - DisposeWatcher(watcher); - } - } - - /// - /// Disposes the watcher. - /// - /// The watcher. - private void DisposeWatcher(FileSystemWatcher watcher) - { - Logger.Info("Stopping directory watching for path {0}", watcher.Path); - - watcher.EnableRaisingEvents = false; - watcher.Dispose(); - - RemoveWatcherFromList(watcher); - } - - /// - /// Removes the watcher from list. - /// - /// The watcher. - private void RemoveWatcherFromList(FileSystemWatcher watcher) - { - FileSystemWatcher removed; - - _fileSystemWatchers.TryRemove(watcher.Path, out removed); - } - - /// - /// Handles the Error event of the watcher control. - /// - /// The source of the event. - /// The instance containing the event data. - void watcher_Error(object sender, ErrorEventArgs e) - { - var ex = e.GetException(); - var dw = (FileSystemWatcher)sender; - - Logger.ErrorException("Error in Directory watcher for: " + dw.Path, ex); - - DisposeWatcher(dw); - } - - /// - /// Handles the Changed event of the watcher control. - /// - /// The source of the event. - /// The instance containing the event data. - void watcher_Changed(object sender, FileSystemEventArgs e) - { - try - { - OnWatcherChanged(e); - } - catch (IOException ex) - { - Logger.ErrorException("IOException in watcher changed. Path: {0}", ex, e.FullPath); - } - } - - private void OnWatcherChanged(FileSystemEventArgs e) - { - var name = e.Name; - - // Ignore certain files - if (_alwaysIgnoreFiles.Contains(name, StringComparer.OrdinalIgnoreCase)) - { - return; - } - - var nameFromFullPath = Path.GetFileName(e.FullPath); - // Ignore certain files - if (!string.IsNullOrEmpty(nameFromFullPath) && _alwaysIgnoreFiles.Contains(nameFromFullPath, StringComparer.OrdinalIgnoreCase)) - { - return; - } - - // Ignore when someone manually creates a new folder - if (e.ChangeType == WatcherChangeTypes.Created && name == "New folder") - { - return; - } - - var tempIgnorePaths = _tempIgnoredPaths.Keys.ToList(); - - // If the parent of an ignored path has a change event, ignore that too - if (tempIgnorePaths.Any(i => - { - if (string.Equals(i, e.FullPath, StringComparison.OrdinalIgnoreCase)) - { - Logger.Debug("Watcher ignoring change to {0}", e.FullPath); - return true; - } - - // Go up a level - var parent = Path.GetDirectoryName(i); - if (string.Equals(parent, e.FullPath, StringComparison.OrdinalIgnoreCase)) - { - Logger.Debug("Watcher ignoring change to {0}", e.FullPath); - return true; - } - - // Go up another level - if (!string.IsNullOrEmpty(parent)) - { - parent = Path.GetDirectoryName(i); - if (string.Equals(parent, e.FullPath, StringComparison.OrdinalIgnoreCase)) - { - Logger.Debug("Watcher ignoring change to {0}", e.FullPath); - return true; - } - } - - if (i.StartsWith(e.FullPath, StringComparison.OrdinalIgnoreCase) || - e.FullPath.StartsWith(i, StringComparison.OrdinalIgnoreCase)) - { - Logger.Debug("Watcher ignoring change to {0}", e.FullPath); - return true; - } - - return false; - - })) - { - return; - } - - Logger.Info("Watcher sees change of type " + e.ChangeType + " to " + e.FullPath); - - //Since we're watching created, deleted and renamed we always want the parent of the item to be the affected path - var affectedPath = e.FullPath; - - _affectedPaths.AddOrUpdate(affectedPath, affectedPath, (key, oldValue) => affectedPath); - - lock (_timerLock) - { - if (_updateTimer == null) - { - _updateTimer = new Timer(TimerStopped, null, TimeSpan.FromSeconds(ConfigurationManager.Configuration.FileWatcherDelay), TimeSpan.FromMilliseconds(-1)); - } - else - { - _updateTimer.Change(TimeSpan.FromSeconds(ConfigurationManager.Configuration.FileWatcherDelay), TimeSpan.FromMilliseconds(-1)); - } - } - } - - /// - /// Timers the stopped. - /// - /// The state info. - private async void TimerStopped(object stateInfo) - { - lock (_timerLock) - { - // Extend the timer as long as any of the paths are still being written to. - if (_affectedPaths.Any(p => IsFileLocked(p.Key))) - { - Logger.Info("Timer extended."); - _updateTimer.Change(TimeSpan.FromSeconds(ConfigurationManager.Configuration.FileWatcherDelay), TimeSpan.FromMilliseconds(-1)); - return; - } - - Logger.Info("Timer stopped."); - - if (_updateTimer != null) - { - _updateTimer.Dispose(); - _updateTimer = null; - } - } - - var paths = _affectedPaths.Keys.ToList(); - _affectedPaths.Clear(); - - await ProcessPathChanges(paths).ConfigureAwait(false); - } - - /// - /// Try and determine if a file is locked - /// This is not perfect, and is subject to race conditions, so I'd rather not make this a re-usable library method. - /// - /// The path. - /// true if [is file locked] [the specified path]; otherwise, false. - private bool IsFileLocked(string path) - { - try - { - var data = _fileSystem.GetFileSystemInfo(path); - - if (!data.Exists - || data.Attributes.HasFlag(FileAttributes.Directory) - || data.Attributes.HasFlag(FileAttributes.ReadOnly)) - { - return false; - } - } - catch (IOException) - { - return false; - } - - try - { - using (_fileSystem.GetFileStream(path, FileMode.Open, FileAccess.ReadWrite, FileShare.ReadWrite)) - { - //file is not locked - return false; - } - } - catch (DirectoryNotFoundException) - { - return false; - } - catch (FileNotFoundException) - { - return false; - } - catch (IOException) - { - //the file is unavailable because it is: - //still being written to - //or being processed by another thread - //or does not exist (has already been processed) - Logger.Debug("{0} is locked.", path); - return true; - } - catch - { - return false; - } - } - - /// - /// Processes the path changes. - /// - /// The paths. - /// Task. - private async Task ProcessPathChanges(List paths) - { - var itemsToRefresh = paths.Select(Path.GetDirectoryName) - .Select(GetAffectedBaseItem) - .Where(item => item != null) - .Distinct() - .ToList(); - - foreach (var p in paths) Logger.Info(p + " reports change."); - - // If the root folder changed, run the library task so the user can see it - if (itemsToRefresh.Any(i => i is AggregateFolder)) - { - TaskManager.CancelIfRunningAndQueue(); - return; - } - - foreach (var item in itemsToRefresh) - { - Logger.Info(item.Name + " (" + item.Path + ") will be refreshed."); - - try - { - await item.ChangedExternally().ConfigureAwait(false); - } - catch (IOException ex) - { - // For now swallow and log. - // Research item: If an IOException occurs, the item may be in a disconnected state (media unavailable) - // Should we remove it from it's parent? - Logger.ErrorException("Error refreshing {0}", ex, item.Name); - } - catch (Exception ex) - { - Logger.ErrorException("Error refreshing {0}", ex, item.Name); - } - } - } - - /// - /// Gets the affected base item. - /// - /// The path. - /// BaseItem. - private BaseItem GetAffectedBaseItem(string path) - { - BaseItem item = null; - - while (item == null && !string.IsNullOrEmpty(path)) - { - item = LibraryManager.RootFolder.FindByPath(path); - - path = Path.GetDirectoryName(path); - } - - if (item != null) - { - // If the item has been deleted find the first valid parent that still exists - while (!Directory.Exists(item.Path) && !File.Exists(item.Path)) - { - item = item.Parent; - - if (item == null) - { - break; - } - } - } - - return item; - } - - /// - /// Stops this instance. - /// - public void Stop() - { - LibraryManager.ItemAdded -= LibraryManager_ItemAdded; - LibraryManager.ItemRemoved -= LibraryManager_ItemRemoved; - - foreach (var watcher in _fileSystemWatchers.Values.ToList()) - { - watcher.Changed -= watcher_Changed; - watcher.EnableRaisingEvents = false; - watcher.Dispose(); - } - - lock (_timerLock) - { - if (_updateTimer != null) - { - _updateTimer.Dispose(); - _updateTimer = null; - } - } - - _fileSystemWatchers.Clear(); - _affectedPaths.Clear(); - } - - /// - /// Performs application-defined tasks associated with freeing, releasing, or resetting unmanaged resources. - /// - public void Dispose() - { - Dispose(true); - GC.SuppressFinalize(this); - } - - /// - /// Releases unmanaged and - optionally - managed resources. - /// - /// true to release both managed and unmanaged resources; false to release only unmanaged resources. - protected virtual void Dispose(bool dispose) - { - if (dispose) - { - Stop(); - } - } - } -} diff --git a/MediaBrowser.Server.Implementations/IO/LibraryMonitor.cs b/MediaBrowser.Server.Implementations/IO/LibraryMonitor.cs new file mode 100644 index 000000000..e09e66765 --- /dev/null +++ b/MediaBrowser.Server.Implementations/IO/LibraryMonitor.cs @@ -0,0 +1,569 @@ +using MediaBrowser.Common.ScheduledTasks; +using MediaBrowser.Controller.Configuration; +using MediaBrowser.Controller.Entities; +using MediaBrowser.Controller.Library; +using MediaBrowser.Model.Entities; +using MediaBrowser.Model.Logging; +using MediaBrowser.Server.Implementations.ScheduledTasks; +using Microsoft.Win32; +using System; +using System.Collections.Concurrent; +using System.Collections.Generic; +using System.IO; +using System.Linq; +using System.Threading; +using System.Threading.Tasks; + +namespace MediaBrowser.Server.Implementations.IO +{ + public class LibraryMonitor : ILibraryMonitor + { + /// + /// The file system watchers + /// + private readonly ConcurrentDictionary _fileSystemWatchers = new ConcurrentDictionary(StringComparer.OrdinalIgnoreCase); + /// + /// The update timer + /// + private Timer _updateTimer; + /// + /// The affected paths + /// + private readonly ConcurrentDictionary _affectedPaths = new ConcurrentDictionary(); + + /// + /// A dynamic list of paths that should be ignored. Added to during our own file sytem modifications. + /// + private readonly ConcurrentDictionary _tempIgnoredPaths = new ConcurrentDictionary(StringComparer.OrdinalIgnoreCase); + + /// + /// Any file name ending in any of these will be ignored by the watchers + /// + private readonly IReadOnlyList _alwaysIgnoreFiles = new List { "thumbs.db", "small.jpg", "albumart.jpg" }; + + /// + /// The timer lock + /// + private readonly object _timerLock = new object(); + + /// + /// Add the path to our temporary ignore list. Use when writing to a path within our listening scope. + /// + /// The path. + private void TemporarilyIgnore(string path) + { + _tempIgnoredPaths[path] = path; + } + + /// + /// Removes the temp ignore. + /// + /// The path. + private async void RemoveTempIgnore(string path) + { + // This is an arbitraty amount of time, but delay it because file system writes often trigger events after RemoveTempIgnore has been called. + // Seeing long delays in some situations, especially over the network. + // Seeing delays up to 40 seconds, but not going to ignore changes for that long. + await Task.Delay(1500).ConfigureAwait(false); + + string val; + _tempIgnoredPaths.TryRemove(path, out val); + } + + public void ReportFileSystemChangeBeginning(string path) + { + TemporarilyIgnore(path); + } + + public void ReportFileSystemChangeComplete(string path, bool refreshPath) + { + RemoveTempIgnore(path); + + if (refreshPath) + { + ReportFileSystemChanged(path); + } + } + + /// + /// Gets or sets the logger. + /// + /// The logger. + private ILogger Logger { get; set; } + + /// + /// Gets or sets the task manager. + /// + /// The task manager. + private ITaskManager TaskManager { get; set; } + + private ILibraryManager LibraryManager { get; set; } + private IServerConfigurationManager ConfigurationManager { get; set; } + + /// + /// Initializes a new instance of the class. + /// + public LibraryMonitor(ILogManager logManager, ITaskManager taskManager, ILibraryManager libraryManager, IServerConfigurationManager configurationManager) + { + if (taskManager == null) + { + throw new ArgumentNullException("taskManager"); + } + + LibraryManager = libraryManager; + TaskManager = taskManager; + Logger = logManager.GetLogger(GetType().Name); + ConfigurationManager = configurationManager; + + SystemEvents.PowerModeChanged += SystemEvents_PowerModeChanged; + } + + /// + /// Handles the PowerModeChanged event of the SystemEvents control. + /// + /// The source of the event. + /// The instance containing the event data. + void SystemEvents_PowerModeChanged(object sender, PowerModeChangedEventArgs e) + { + Stop(); + Start(); + } + + /// + /// Starts this instance. + /// + public void Start() + { + LibraryManager.ItemAdded += LibraryManager_ItemAdded; + LibraryManager.ItemRemoved += LibraryManager_ItemRemoved; + + var pathsToWatch = new List { LibraryManager.RootFolder.Path }; + + var paths = LibraryManager + .RootFolder + .Children + .OfType() + .Where(i => i.LocationType != LocationType.Remote && i.LocationType != LocationType.Virtual) + .SelectMany(f => + { + try + { + // Accessing ResolveArgs could involve file system access + return f.ResolveArgs.PhysicalLocations; + } + catch (IOException) + { + return new string[] { }; + } + + }) + .Distinct(StringComparer.OrdinalIgnoreCase) + .OrderBy(i => i) + .ToList(); + + foreach (var path in paths) + { + if (!ContainsParentFolder(pathsToWatch, path)) + { + pathsToWatch.Add(path); + } + } + + foreach (var path in pathsToWatch) + { + StartWatchingPath(path); + } + } + + /// + /// Handles the ItemRemoved event of the LibraryManager control. + /// + /// The source of the event. + /// The instance containing the event data. + void LibraryManager_ItemRemoved(object sender, ItemChangeEventArgs e) + { + if (e.Item.Parent is AggregateFolder) + { + StopWatchingPath(e.Item.Path); + } + } + + /// + /// Handles the ItemAdded event of the LibraryManager control. + /// + /// The source of the event. + /// The instance containing the event data. + void LibraryManager_ItemAdded(object sender, ItemChangeEventArgs e) + { + if (e.Item.Parent is AggregateFolder) + { + StartWatchingPath(e.Item.Path); + } + } + + /// + /// Examine a list of strings assumed to be file paths to see if it contains a parent of + /// the provided path. + /// + /// The LST. + /// The path. + /// true if [contains parent folder] [the specified LST]; otherwise, false. + /// path + private static bool ContainsParentFolder(IEnumerable lst, string path) + { + if (string.IsNullOrEmpty(path)) + { + throw new ArgumentNullException("path"); + } + + path = path.TrimEnd(Path.DirectorySeparatorChar); + + return lst.Any(str => + { + //this should be a little quicker than examining each actual parent folder... + var compare = str.TrimEnd(Path.DirectorySeparatorChar); + + return (path.Equals(compare, StringComparison.OrdinalIgnoreCase) || (path.StartsWith(compare, StringComparison.OrdinalIgnoreCase) && path[compare.Length] == Path.DirectorySeparatorChar)); + }); + } + + /// + /// Starts the watching path. + /// + /// The path. + private void StartWatchingPath(string path) + { + // Creating a FileSystemWatcher over the LAN can take hundreds of milliseconds, so wrap it in a Task to do them all in parallel + Task.Run(() => + { + var newWatcher = new FileSystemWatcher(path, "*") { IncludeSubdirectories = true, InternalBufferSize = 32767 }; + + newWatcher.Created += watcher_Changed; + newWatcher.Deleted += watcher_Changed; + newWatcher.Renamed += watcher_Changed; + newWatcher.Changed += watcher_Changed; + + newWatcher.Error += watcher_Error; + + try + { + if (_fileSystemWatchers.TryAdd(path, newWatcher)) + { + newWatcher.EnableRaisingEvents = true; + Logger.Info("Watching directory " + path); + } + else + { + Logger.Info("Unable to add directory watcher for {0}. It already exists in the dictionary." + path); + newWatcher.Dispose(); + } + + } + catch (IOException ex) + { + Logger.ErrorException("Error watching path: {0}", ex, path); + } + catch (PlatformNotSupportedException ex) + { + Logger.ErrorException("Error watching path: {0}", ex, path); + } + }); + } + + /// + /// Stops the watching path. + /// + /// The path. + private void StopWatchingPath(string path) + { + FileSystemWatcher watcher; + + if (_fileSystemWatchers.TryGetValue(path, out watcher)) + { + DisposeWatcher(watcher); + } + } + + /// + /// Disposes the watcher. + /// + /// The watcher. + private void DisposeWatcher(FileSystemWatcher watcher) + { + Logger.Info("Stopping directory watching for path {0}", watcher.Path); + + watcher.EnableRaisingEvents = false; + watcher.Dispose(); + + RemoveWatcherFromList(watcher); + } + + /// + /// Removes the watcher from list. + /// + /// The watcher. + private void RemoveWatcherFromList(FileSystemWatcher watcher) + { + FileSystemWatcher removed; + + _fileSystemWatchers.TryRemove(watcher.Path, out removed); + } + + /// + /// Handles the Error event of the watcher control. + /// + /// The source of the event. + /// The instance containing the event data. + void watcher_Error(object sender, ErrorEventArgs e) + { + var ex = e.GetException(); + var dw = (FileSystemWatcher)sender; + + Logger.ErrorException("Error in Directory watcher for: " + dw.Path, ex); + + DisposeWatcher(dw); + } + + /// + /// Handles the Changed event of the watcher control. + /// + /// The source of the event. + /// The instance containing the event data. + void watcher_Changed(object sender, FileSystemEventArgs e) + { + try + { + OnWatcherChanged(e); + } + catch (Exception ex) + { + Logger.ErrorException("Exception in watcher changed. Path: {0}", ex, e.FullPath); + } + } + + private void OnWatcherChanged(FileSystemEventArgs e) + { + Logger.Debug("Watcher sees change of type " + e.ChangeType + " to " + e.FullPath); + + ReportFileSystemChanged(e.FullPath); + } + + public void ReportFileSystemChanged(string path) + { + var filename = Path.GetFileName(path); + + // Ignore certain files + if (!string.IsNullOrEmpty(filename) && _alwaysIgnoreFiles.Contains(filename, StringComparer.OrdinalIgnoreCase)) + { + return; + } + + var tempIgnorePaths = _tempIgnoredPaths.Keys.ToList(); + + // If the parent of an ignored path has a change event, ignore that too + if (tempIgnorePaths.Any(i => + { + if (string.Equals(i, path, StringComparison.OrdinalIgnoreCase)) + { + Logger.Debug("Ignoring change to {0}", path); + return true; + } + + // Go up a level + var parent = Path.GetDirectoryName(i); + if (string.Equals(parent, path, StringComparison.OrdinalIgnoreCase)) + { + Logger.Debug("Ignoring change to {0}", path); + return true; + } + + // Go up another level + if (!string.IsNullOrEmpty(parent)) + { + parent = Path.GetDirectoryName(i); + if (string.Equals(parent, path, StringComparison.OrdinalIgnoreCase)) + { + Logger.Debug("Ignoring change to {0}", path); + return true; + } + } + + if (i.StartsWith(path, StringComparison.OrdinalIgnoreCase) || + path.StartsWith(i, StringComparison.OrdinalIgnoreCase)) + { + Logger.Debug("Ignoring change to {0}", path); + return true; + } + + return false; + + })) + { + return; + } + + // Avoid implicitly captured closure + var affectedPath = path; + _affectedPaths.AddOrUpdate(path, path, (key, oldValue) => affectedPath); + + lock (_timerLock) + { + if (_updateTimer == null) + { + _updateTimer = new Timer(TimerStopped, null, TimeSpan.FromSeconds(ConfigurationManager.Configuration.RealtimeWatcherDelay), TimeSpan.FromMilliseconds(-1)); + } + else + { + _updateTimer.Change(TimeSpan.FromSeconds(ConfigurationManager.Configuration.RealtimeWatcherDelay), TimeSpan.FromMilliseconds(-1)); + } + } + } + + /// + /// Timers the stopped. + /// + /// The state info. + private async void TimerStopped(object stateInfo) + { + Logger.Debug("Timer stopped."); + + DisposeTimer(); + + var paths = _affectedPaths.Keys.ToList(); + _affectedPaths.Clear(); + + await ProcessPathChanges(paths).ConfigureAwait(false); + } + + private void DisposeTimer() + { + lock (_timerLock) + { + if (_updateTimer != null) + { + _updateTimer.Dispose(); + _updateTimer = null; + } + } + } + + /// + /// Processes the path changes. + /// + /// The paths. + /// Task. + private async Task ProcessPathChanges(List paths) + { + var itemsToRefresh = paths.Select(Path.GetDirectoryName) + .Select(GetAffectedBaseItem) + .Where(item => item != null) + .Distinct() + .ToList(); + + foreach (var p in paths) Logger.Info(p + " reports change."); + + // If the root folder changed, run the library task so the user can see it + if (itemsToRefresh.Any(i => i is AggregateFolder)) + { + TaskManager.CancelIfRunningAndQueue(); + return; + } + + foreach (var item in itemsToRefresh) + { + Logger.Info(item.Name + " (" + item.Path + ") will be refreshed."); + + try + { + await item.ChangedExternally().ConfigureAwait(false); + } + catch (IOException ex) + { + // For now swallow and log. + // Research item: If an IOException occurs, the item may be in a disconnected state (media unavailable) + // Should we remove it from it's parent? + Logger.ErrorException("Error refreshing {0}", ex, item.Name); + } + catch (Exception ex) + { + Logger.ErrorException("Error refreshing {0}", ex, item.Name); + } + } + } + + /// + /// Gets the affected base item. + /// + /// The path. + /// BaseItem. + private BaseItem GetAffectedBaseItem(string path) + { + BaseItem item = null; + + while (item == null && !string.IsNullOrEmpty(path)) + { + item = LibraryManager.RootFolder.FindByPath(path); + + path = Path.GetDirectoryName(path); + } + + if (item != null) + { + // If the item has been deleted find the first valid parent that still exists + while (!Directory.Exists(item.Path) && !File.Exists(item.Path)) + { + item = item.Parent; + + if (item == null) + { + break; + } + } + } + + return item; + } + + /// + /// Stops this instance. + /// + public void Stop() + { + LibraryManager.ItemAdded -= LibraryManager_ItemAdded; + LibraryManager.ItemRemoved -= LibraryManager_ItemRemoved; + + foreach (var watcher in _fileSystemWatchers.Values.ToList()) + { + watcher.Changed -= watcher_Changed; + watcher.EnableRaisingEvents = false; + watcher.Dispose(); + } + + DisposeTimer(); + + _fileSystemWatchers.Clear(); + _affectedPaths.Clear(); + } + + /// + /// Performs application-defined tasks associated with freeing, releasing, or resetting unmanaged resources. + /// + public void Dispose() + { + Dispose(true); + GC.SuppressFinalize(this); + } + + /// + /// Releases unmanaged and - optionally - managed resources. + /// + /// true to release both managed and unmanaged resources; false to release only unmanaged resources. + protected virtual void Dispose(bool dispose) + { + if (dispose) + { + Stop(); + } + } + } +} diff --git a/MediaBrowser.Server.Implementations/Library/LibraryManager.cs b/MediaBrowser.Server.Implementations/Library/LibraryManager.cs index 736c70ad5..04344553f 100644 --- a/MediaBrowser.Server.Implementations/Library/LibraryManager.cs +++ b/MediaBrowser.Server.Implementations/Library/LibraryManager.cs @@ -137,7 +137,7 @@ namespace MediaBrowser.Server.Implementations.Library private IEnumerable _savers; - private readonly Func _directoryWatchersFactory; + private readonly Func _libraryMonitorFactory; /// /// The _library items cache @@ -180,14 +180,14 @@ namespace MediaBrowser.Server.Implementations.Library /// The user manager. /// The configuration manager. /// The user data repository. - public LibraryManager(ILogger logger, ITaskManager taskManager, IUserManager userManager, IServerConfigurationManager configurationManager, IUserDataManager userDataRepository, Func directoryWatchersFactory, IFileSystem fileSystem) + public LibraryManager(ILogger logger, ITaskManager taskManager, IUserManager userManager, IServerConfigurationManager configurationManager, IUserDataManager userDataRepository, Func libraryMonitorFactory, IFileSystem fileSystem) { _logger = logger; _taskManager = taskManager; _userManager = userManager; ConfigurationManager = configurationManager; _userDataRepository = userDataRepository; - _directoryWatchersFactory = directoryWatchersFactory; + _libraryMonitorFactory = libraryMonitorFactory; _fileSystem = fileSystem; ByReferenceItems = new ConcurrentDictionary(); @@ -934,7 +934,7 @@ namespace MediaBrowser.Server.Implementations.Library /// Task. public async Task ValidateMediaLibraryInternal(IProgress progress, CancellationToken cancellationToken) { - _directoryWatchersFactory().Stop(); + _libraryMonitorFactory().Stop(); try { @@ -942,7 +942,7 @@ namespace MediaBrowser.Server.Implementations.Library } finally { - _directoryWatchersFactory().Start(); + _libraryMonitorFactory().Start(); } } @@ -1462,13 +1462,13 @@ namespace MediaBrowser.Server.Implementations.Library var semaphore = _fileLocks.GetOrAdd(path, key => new SemaphoreSlim(1, 1)); - var directoryWatchers = _directoryWatchersFactory(); + var directoryWatchers = _libraryMonitorFactory(); await semaphore.WaitAsync().ConfigureAwait(false); try { - directoryWatchers.TemporarilyIgnore(path); + directoryWatchers.ReportFileSystemChangeBeginning(path); saver.Save(item, CancellationToken.None); } catch (Exception ex) @@ -1477,7 +1477,7 @@ namespace MediaBrowser.Server.Implementations.Library } finally { - directoryWatchers.RemoveTempIgnore(path); + directoryWatchers.ReportFileSystemChangeComplete(path, false); semaphore.Release(); } } diff --git a/MediaBrowser.Server.Implementations/MediaBrowser.Server.Implementations.csproj b/MediaBrowser.Server.Implementations/MediaBrowser.Server.Implementations.csproj index a92ec29d5..fe4283368 100644 --- a/MediaBrowser.Server.Implementations/MediaBrowser.Server.Implementations.csproj +++ b/MediaBrowser.Server.Implementations/MediaBrowser.Server.Implementations.csproj @@ -137,7 +137,7 @@ - + diff --git a/MediaBrowser.ServerApplication/ApplicationHost.cs b/MediaBrowser.ServerApplication/ApplicationHost.cs index 3553996b0..0c8a6b923 100644 --- a/MediaBrowser.ServerApplication/ApplicationHost.cs +++ b/MediaBrowser.ServerApplication/ApplicationHost.cs @@ -137,7 +137,7 @@ namespace MediaBrowser.ServerApplication /// Gets or sets the directory watchers. /// /// The directory watchers. - private IDirectoryWatchers DirectoryWatchers { get; set; } + private ILibraryMonitor LibraryMonitor { get; set; } /// /// Gets or sets the provider manager. /// @@ -273,13 +273,13 @@ namespace MediaBrowser.ServerApplication UserManager = new UserManager(Logger, ServerConfigurationManager, UserRepository); RegisterSingleInstance(UserManager); - LibraryManager = new LibraryManager(Logger, TaskManager, UserManager, ServerConfigurationManager, UserDataManager, () => DirectoryWatchers, FileSystemManager); + LibraryManager = new LibraryManager(Logger, TaskManager, UserManager, ServerConfigurationManager, UserDataManager, () => LibraryMonitor, FileSystemManager); RegisterSingleInstance(LibraryManager); - DirectoryWatchers = new DirectoryWatchers(LogManager, TaskManager, LibraryManager, ServerConfigurationManager, FileSystemManager); - RegisterSingleInstance(DirectoryWatchers); + LibraryMonitor = new LibraryMonitor(LogManager, TaskManager, LibraryManager, ServerConfigurationManager); + RegisterSingleInstance(LibraryMonitor); - ProviderManager = new ProviderManager(HttpClient, ServerConfigurationManager, DirectoryWatchers, LogManager, FileSystemManager, ItemRepository); + ProviderManager = new ProviderManager(HttpClient, ServerConfigurationManager, LibraryMonitor, LogManager, FileSystemManager, ItemRepository); RegisterSingleInstance(ProviderManager); RegisterSingleInstance(() => new SearchEngine(LogManager, LibraryManager, UserManager)); @@ -306,7 +306,7 @@ namespace MediaBrowser.ServerApplication var newsService = new Server.Implementations.News.NewsService(ApplicationPaths, JsonSerializer); RegisterSingleInstance(newsService); - var fileOrganizationService = new FileOrganizationService(TaskManager, FileOrganizationRepository, Logger, DirectoryWatchers, LibraryManager, ServerConfigurationManager, FileSystemManager); + var fileOrganizationService = new FileOrganizationService(TaskManager, FileOrganizationRepository, Logger, LibraryMonitor, LibraryManager, ServerConfigurationManager, FileSystemManager); RegisterSingleInstance(fileOrganizationService); progress.Report(15); -- cgit v1.2.3 From e772b44ac73cf77b41072b7578a4014c6234ef1b Mon Sep 17 00:00:00 2001 From: Luke Pulverenti Date: Tue, 28 Jan 2014 20:46:04 -0500 Subject: fix library monitor reference --- MediaBrowser.Api/Library/LibraryStructureService.cs | 1 + MediaBrowser.Server.Implementations/IO/LibraryMonitor.cs | 5 +++-- MediaBrowser.ServerApplication/ApplicationHost.cs | 2 +- 3 files changed, 5 insertions(+), 3 deletions(-) (limited to 'MediaBrowser.ServerApplication/ApplicationHost.cs') diff --git a/MediaBrowser.Api/Library/LibraryStructureService.cs b/MediaBrowser.Api/Library/LibraryStructureService.cs index d0d56c07b..8ea472da3 100644 --- a/MediaBrowser.Api/Library/LibraryStructureService.cs +++ b/MediaBrowser.Api/Library/LibraryStructureService.cs @@ -174,6 +174,7 @@ namespace MediaBrowser.Api.Library /// Gets or sets the name. /// /// The name. + [ApiMember(Name = "Path", Description = "The path that was changed.", IsRequired = false, DataType = "string", ParameterType = "query", Verb = "POST")] public string Path { get; set; } } diff --git a/MediaBrowser.Server.Implementations/IO/LibraryMonitor.cs b/MediaBrowser.Server.Implementations/IO/LibraryMonitor.cs index bf90dc40e..22ea668f4 100644 --- a/MediaBrowser.Server.Implementations/IO/LibraryMonitor.cs +++ b/MediaBrowser.Server.Implementations/IO/LibraryMonitor.cs @@ -111,12 +111,12 @@ namespace MediaBrowser.Server.Implementations.IO private ILibraryManager LibraryManager { get; set; } private IServerConfigurationManager ConfigurationManager { get; set; } - private IFileSystem _fileSystem; + private readonly IFileSystem _fileSystem; /// /// Initializes a new instance of the class. /// - public LibraryMonitor(ILogManager logManager, ITaskManager taskManager, ILibraryManager libraryManager, IServerConfigurationManager configurationManager) + public LibraryMonitor(ILogManager logManager, ITaskManager taskManager, ILibraryManager libraryManager, IServerConfigurationManager configurationManager, IFileSystem fileSystem) { if (taskManager == null) { @@ -127,6 +127,7 @@ namespace MediaBrowser.Server.Implementations.IO TaskManager = taskManager; Logger = logManager.GetLogger(GetType().Name); ConfigurationManager = configurationManager; + _fileSystem = fileSystem; SystemEvents.PowerModeChanged += SystemEvents_PowerModeChanged; } diff --git a/MediaBrowser.ServerApplication/ApplicationHost.cs b/MediaBrowser.ServerApplication/ApplicationHost.cs index 0c8a6b923..bf652d8cb 100644 --- a/MediaBrowser.ServerApplication/ApplicationHost.cs +++ b/MediaBrowser.ServerApplication/ApplicationHost.cs @@ -276,7 +276,7 @@ namespace MediaBrowser.ServerApplication LibraryManager = new LibraryManager(Logger, TaskManager, UserManager, ServerConfigurationManager, UserDataManager, () => LibraryMonitor, FileSystemManager); RegisterSingleInstance(LibraryManager); - LibraryMonitor = new LibraryMonitor(LogManager, TaskManager, LibraryManager, ServerConfigurationManager); + LibraryMonitor = new LibraryMonitor(LogManager, TaskManager, LibraryManager, ServerConfigurationManager, FileSystemManager); RegisterSingleInstance(LibraryMonitor); ProviderManager = new ProviderManager(HttpClient, ServerConfigurationManager, LibraryMonitor, LogManager, FileSystemManager, ItemRepository); -- cgit v1.2.3 From 81d5e9f8087227591b2be068e822342b17ef3d7a Mon Sep 17 00:00:00 2001 From: Luke Pulverenti Date: Wed, 29 Jan 2014 00:17:58 -0500 Subject: persist provider results --- MediaBrowser.Controller/Entities/BaseItem.cs | 7 +- .../MediaBrowser.Controller.csproj | 4 +- .../Persistence/IItemRepository.cs | 17 -- .../Providers/IMetadataProvider.cs | 28 +-- .../Providers/IProviderRepository.cs | 48 +++++ MediaBrowser.Controller/Providers/ItemId.cs | 35 ++++ .../Providers/MetadataStatus.cs | 122 ++++++++++++ .../Providers/ProviderResult.cs | 60 ------ .../GameGenres/GameGenreMetadataService.cs | 4 +- .../Genres/GenreMetadataService.cs | 4 +- .../Manager/ItemImageProvider.cs | 11 +- MediaBrowser.Providers/Manager/MetadataService.cs | 51 ++--- MediaBrowser.Providers/Manager/ProviderManager.cs | 12 +- .../MusicGenres/MusicGenreMetadataService.cs | 4 +- .../People/PersonMetadataService.cs | 4 +- .../Studios/StudioMetadataService.cs | 4 +- .../IO/LibraryMonitor.cs | 25 +-- .../Persistence/SqliteItemRepository.cs | 22 --- .../Persistence/SqliteProviderInfoRepository.cs | 212 ++++++++++++++++++--- MediaBrowser.ServerApplication/ApplicationHost.cs | 9 +- 20 files changed, 477 insertions(+), 206 deletions(-) create mode 100644 MediaBrowser.Controller/Providers/IProviderRepository.cs create mode 100644 MediaBrowser.Controller/Providers/ItemId.cs create mode 100644 MediaBrowser.Controller/Providers/MetadataStatus.cs delete mode 100644 MediaBrowser.Controller/Providers/ProviderResult.cs (limited to 'MediaBrowser.ServerApplication/ApplicationHost.cs') diff --git a/MediaBrowser.Controller/Entities/BaseItem.cs b/MediaBrowser.Controller/Entities/BaseItem.cs index a50a0f467..06ebe8905 100644 --- a/MediaBrowser.Controller/Entities/BaseItem.cs +++ b/MediaBrowser.Controller/Entities/BaseItem.cs @@ -364,11 +364,16 @@ namespace MediaBrowser.Controller.Entities } } + private string _forcedSortName; /// /// Gets or sets the name of the forced sort. /// /// The name of the forced sort. - public string ForcedSortName { get; set; } + public string ForcedSortName + { + get { return _forcedSortName; } + set { _forcedSortName = value; _sortName = null; } + } private string _sortName; /// diff --git a/MediaBrowser.Controller/MediaBrowser.Controller.csproj b/MediaBrowser.Controller/MediaBrowser.Controller.csproj index 45297ef3d..ee8bb2761 100644 --- a/MediaBrowser.Controller/MediaBrowser.Controller.csproj +++ b/MediaBrowser.Controller/MediaBrowser.Controller.csproj @@ -146,13 +146,15 @@ + + - + diff --git a/MediaBrowser.Controller/Persistence/IItemRepository.cs b/MediaBrowser.Controller/Persistence/IItemRepository.cs index 3affe48e7..3a5cb4e87 100644 --- a/MediaBrowser.Controller/Persistence/IItemRepository.cs +++ b/MediaBrowser.Controller/Persistence/IItemRepository.cs @@ -1,5 +1,4 @@ using MediaBrowser.Controller.Entities; -using MediaBrowser.Controller.Providers; using MediaBrowser.Model.Entities; using System; using System.Collections.Generic; @@ -112,22 +111,6 @@ namespace MediaBrowser.Controller.Persistence /// The cancellation token. /// Task. Task SaveMediaStreams(Guid id, IEnumerable streams, CancellationToken cancellationToken); - - /// - /// Gets the provider history. - /// - /// The item identifier. - /// IEnumerable{BaseProviderInfo}. - IEnumerable GetProviderHistory(Guid itemId); - - /// - /// Saves the provider history. - /// - /// The identifier. - /// The history. - /// The cancellation token. - /// Task. - Task SaveProviderHistory(Guid id, IEnumerable history, CancellationToken cancellationToken); } } diff --git a/MediaBrowser.Controller/Providers/IMetadataProvider.cs b/MediaBrowser.Controller/Providers/IMetadataProvider.cs index 97249e26d..843ba263b 100644 --- a/MediaBrowser.Controller/Providers/IMetadataProvider.cs +++ b/MediaBrowser.Controller/Providers/IMetadataProvider.cs @@ -1,6 +1,4 @@ -using MediaBrowser.Model.Entities; -using System; -using System.Collections.Generic; +using System; using System.Threading; using System.Threading.Tasks; @@ -52,21 +50,14 @@ namespace MediaBrowser.Controller.Providers public interface IHasChangeMonitor { /// - /// Determines whether the specified date has changed. + /// Determines whether the specified item has changed. /// /// The item. /// The date. - /// true if the specified date has changed; otherwise, false. + /// true if the specified item has changed; otherwise, false. bool HasChanged(IHasMetadata item, DateTime date); } - public enum MetadataProviderType - { - Embedded = 0, - Local = 1, - Remote = 2 - } - public class MetadataResult where T : IHasMetadata { @@ -74,17 +65,4 @@ namespace MediaBrowser.Controller.Providers public T Item { get; set; } } - public class ItemId : IHasProviderIds - { - public string Name { get; set; } - public string MetadataLanguage { get; set; } - public string MetadataCountryCode { get; set; } - - public Dictionary ProviderIds { get; set; } - - public ItemId() - { - ProviderIds = new Dictionary(StringComparer.OrdinalIgnoreCase); - } - } } diff --git a/MediaBrowser.Controller/Providers/IProviderRepository.cs b/MediaBrowser.Controller/Providers/IProviderRepository.cs new file mode 100644 index 000000000..1c0ad2cd7 --- /dev/null +++ b/MediaBrowser.Controller/Providers/IProviderRepository.cs @@ -0,0 +1,48 @@ +using MediaBrowser.Controller.Persistence; +using System; +using System.Collections.Generic; +using System.Threading; +using System.Threading.Tasks; + +namespace MediaBrowser.Controller.Providers +{ + public interface IProviderRepository : IRepository + { + /// + /// Gets the provider history. + /// + /// The item identifier. + /// IEnumerable{BaseProviderInfo}. + IEnumerable GetProviderHistory(Guid itemId); + + /// + /// Saves the provider history. + /// + /// The identifier. + /// The history. + /// The cancellation token. + /// Task. + Task SaveProviderHistory(Guid id, IEnumerable history, CancellationToken cancellationToken); + + /// + /// Gets the metadata status. + /// + /// The item identifier. + /// MetadataStatus. + MetadataStatus GetMetadataStatus(Guid itemId); + + /// + /// Saves the metadata status. + /// + /// The status. + /// The cancellation token. + /// Task. + Task SaveMetadataStatus(MetadataStatus status, CancellationToken cancellationToken); + + /// + /// Initializes this instance. + /// + /// Task. + Task Initialize(); + } +} diff --git a/MediaBrowser.Controller/Providers/ItemId.cs b/MediaBrowser.Controller/Providers/ItemId.cs new file mode 100644 index 000000000..1116eb8b5 --- /dev/null +++ b/MediaBrowser.Controller/Providers/ItemId.cs @@ -0,0 +1,35 @@ +using MediaBrowser.Model.Entities; +using System; +using System.Collections.Generic; + +namespace MediaBrowser.Controller.Providers +{ + public class ItemId : IHasProviderIds + { + /// + /// Gets or sets the name. + /// + /// The name. + public string Name { get; set; } + /// + /// Gets or sets the metadata language. + /// + /// The metadata language. + public string MetadataLanguage { get; set; } + /// + /// Gets or sets the metadata country code. + /// + /// The metadata country code. + public string MetadataCountryCode { get; set; } + /// + /// Gets or sets the provider ids. + /// + /// The provider ids. + public Dictionary ProviderIds { get; set; } + + public ItemId() + { + ProviderIds = new Dictionary(StringComparer.OrdinalIgnoreCase); + } + } +} diff --git a/MediaBrowser.Controller/Providers/MetadataStatus.cs b/MediaBrowser.Controller/Providers/MetadataStatus.cs new file mode 100644 index 000000000..834d8ec35 --- /dev/null +++ b/MediaBrowser.Controller/Providers/MetadataStatus.cs @@ -0,0 +1,122 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using MediaBrowser.Common.Extensions; + +namespace MediaBrowser.Controller.Providers +{ + public class MetadataStatus + { + /// + /// Gets or sets the item identifier. + /// + /// The item identifier. + public Guid ItemId { get; set; } + + /// + /// Gets or sets the date last metadata refresh. + /// + /// The date last metadata refresh. + public DateTime? DateLastMetadataRefresh { get; set; } + + /// + /// Gets or sets the date last images refresh. + /// + /// The date last images refresh. + public DateTime? DateLastImagesRefresh { get; set; } + + /// + /// Gets or sets the last result. + /// + /// The last result. + public ProviderRefreshStatus LastStatus { get; set; } + + /// + /// Gets or sets the last result error message. + /// + /// The last result error message. + public string LastErrorMessage { get; set; } + + /// + /// Gets or sets the providers refreshed. + /// + /// The providers refreshed. + public List MetadataProvidersRefreshed { get; set; } + public List ImageProvidersRefreshed { get; set; } + + public void AddStatus(ProviderRefreshStatus status, string errorMessage) + { + if (LastStatus != status) + { + IsDirty = true; + } + + if (string.IsNullOrEmpty(LastErrorMessage)) + { + LastErrorMessage = errorMessage; + } + if (LastStatus == ProviderRefreshStatus.Success) + { + LastStatus = status; + } + } + + public MetadataStatus() + { + LastStatus = ProviderRefreshStatus.Success; + + MetadataProvidersRefreshed = new List(); + ImageProvidersRefreshed = new List(); + } + + public bool IsDirty { get; private set; } + + public void SetDateLastMetadataRefresh(DateTime date) + { + if (date != (DateLastMetadataRefresh ?? DateTime.MinValue)) + { + IsDirty = true; + } + + DateLastMetadataRefresh = date; + } + + public void SetDateLastImagesRefresh(DateTime date) + { + if (date != (DateLastImagesRefresh ?? DateTime.MinValue)) + { + IsDirty = true; + } + + DateLastImagesRefresh = date; + } + + public void AddImageProvidersRefreshed(List providerIds) + { + var count = ImageProvidersRefreshed.Count; + + providerIds.AddRange(ImageProvidersRefreshed); + + ImageProvidersRefreshed = providerIds.Distinct().ToList(); + + if (ImageProvidersRefreshed.Count != count) + { + IsDirty = true; + } + } + + public void AddMetadataProvidersRefreshed(List providerIds) + { + var count = MetadataProvidersRefreshed.Count; + + providerIds.AddRange(MetadataProvidersRefreshed); + + MetadataProvidersRefreshed = providerIds.Distinct().ToList(); + + if (MetadataProvidersRefreshed.Count != count) + { + IsDirty = true; + } + } + } +} diff --git a/MediaBrowser.Controller/Providers/ProviderResult.cs b/MediaBrowser.Controller/Providers/ProviderResult.cs deleted file mode 100644 index 2486faeed..000000000 --- a/MediaBrowser.Controller/Providers/ProviderResult.cs +++ /dev/null @@ -1,60 +0,0 @@ -using System; - -namespace MediaBrowser.Controller.Providers -{ - public class ProviderResult - { - /// - /// Gets or sets the item identifier. - /// - /// The item identifier. - public Guid ItemId { get; set; } - - /// - /// Gets or sets a value indicating whether this instance has refreshed metadata. - /// - /// true if this instance has refreshed metadata; otherwise, false. - public bool HasRefreshedMetadata { get; set; } - - /// - /// Gets or sets a value indicating whether this instance has refreshed images. - /// - /// true if this instance has refreshed images; otherwise, false. - public bool HasRefreshedImages { get; set; } - - /// - /// Gets or sets the date last refreshed. - /// - /// The date last refreshed. - public DateTime DateLastRefreshed { get; set; } - - /// - /// Gets or sets the last result. - /// - /// The last result. - public ProviderRefreshStatus Status { get; set; } - - /// - /// Gets or sets the last result error message. - /// - /// The last result error message. - public string ErrorMessage { get; set; } - - public void AddStatus(ProviderRefreshStatus status, string errorMessage) - { - if (string.IsNullOrEmpty(ErrorMessage)) - { - ErrorMessage = errorMessage; - } - if (Status == ProviderRefreshStatus.Success) - { - Status = status; - } - } - - public ProviderResult() - { - Status = ProviderRefreshStatus.Success; - } - } -} diff --git a/MediaBrowser.Providers/GameGenres/GameGenreMetadataService.cs b/MediaBrowser.Providers/GameGenres/GameGenreMetadataService.cs index 7625cdd5a..389e2a275 100644 --- a/MediaBrowser.Providers/GameGenres/GameGenreMetadataService.cs +++ b/MediaBrowser.Providers/GameGenres/GameGenreMetadataService.cs @@ -15,8 +15,8 @@ namespace MediaBrowser.Providers.GameGenres { private readonly ILibraryManager _libraryManager; - public GameGenreMetadataService(IServerConfigurationManager serverConfigurationManager, ILogger logger, IProviderManager providerManager, ILibraryManager libraryManager) - : base(serverConfigurationManager, logger, providerManager) + public GameGenreMetadataService(IServerConfigurationManager serverConfigurationManager, ILogger logger, IProviderManager providerManager, IProviderRepository providerRepo, ILibraryManager libraryManager) + : base(serverConfigurationManager, logger, providerManager, providerRepo) { _libraryManager = libraryManager; } diff --git a/MediaBrowser.Providers/Genres/GenreMetadataService.cs b/MediaBrowser.Providers/Genres/GenreMetadataService.cs index 7a8ffa5d4..83253a190 100644 --- a/MediaBrowser.Providers/Genres/GenreMetadataService.cs +++ b/MediaBrowser.Providers/Genres/GenreMetadataService.cs @@ -15,8 +15,8 @@ namespace MediaBrowser.Providers.Genres { private readonly ILibraryManager _libraryManager; - public GenreMetadataService(IServerConfigurationManager serverConfigurationManager, ILogger logger, IProviderManager providerManager, ILibraryManager libraryManager) - : base(serverConfigurationManager, logger, providerManager) + public GenreMetadataService(IServerConfigurationManager serverConfigurationManager, ILogger logger, IProviderManager providerManager, IProviderRepository providerRepo, ILibraryManager libraryManager) + : base(serverConfigurationManager, logger, providerManager, providerRepo) { _libraryManager = libraryManager; } diff --git a/MediaBrowser.Providers/Manager/ItemImageProvider.cs b/MediaBrowser.Providers/Manager/ItemImageProvider.cs index 5358506f0..d455a08db 100644 --- a/MediaBrowser.Providers/Manager/ItemImageProvider.cs +++ b/MediaBrowser.Providers/Manager/ItemImageProvider.cs @@ -1,4 +1,5 @@ -using MediaBrowser.Controller.Configuration; +using MediaBrowser.Common.Extensions; +using MediaBrowser.Controller.Configuration; using MediaBrowser.Controller.Entities; using MediaBrowser.Controller.Library; using MediaBrowser.Controller.Providers; @@ -51,16 +52,24 @@ namespace MediaBrowser.Providers.Manager var providers = GetImageProviders(item, imageProviders).ToList(); + var providerIds = new List(); + foreach (var provider in providers.OfType()) { await RefreshFromProvider(item, provider, options, result, cancellationToken).ConfigureAwait(false); + + providerIds.Add(provider.GetType().FullName.GetMD5()); } foreach (var provider in providers.OfType()) { await RefreshFromProvider(item, provider, result, cancellationToken).ConfigureAwait(false); + + providerIds.Add(provider.GetType().FullName.GetMD5()); } + result.Providers = providerIds; + return result; } diff --git a/MediaBrowser.Providers/Manager/MetadataService.cs b/MediaBrowser.Providers/Manager/MetadataService.cs index 5dde3098a..7916d7e86 100644 --- a/MediaBrowser.Providers/Manager/MetadataService.cs +++ b/MediaBrowser.Providers/Manager/MetadataService.cs @@ -1,4 +1,5 @@ -using MediaBrowser.Controller.Configuration; +using MediaBrowser.Common.Extensions; +using MediaBrowser.Controller.Configuration; using MediaBrowser.Controller.Entities; using MediaBrowser.Controller.Library; using MediaBrowser.Controller.Providers; @@ -18,16 +19,18 @@ namespace MediaBrowser.Providers.Manager protected readonly IServerConfigurationManager ServerConfigurationManager; protected readonly ILogger Logger; protected readonly IProviderManager ProviderManager; + private readonly IProviderRepository _providerRepo; private IMetadataProvider[] _providers = { }; private IImageProvider[] _imageProviders = { }; - protected MetadataService(IServerConfigurationManager serverConfigurationManager, ILogger logger, IProviderManager providerManager) + protected MetadataService(IServerConfigurationManager serverConfigurationManager, ILogger logger, IProviderManager providerManager, IProviderRepository providerRepo) { ServerConfigurationManager = serverConfigurationManager; Logger = logger; ProviderManager = providerManager; + _providerRepo = providerRepo; } /// @@ -48,9 +51,9 @@ namespace MediaBrowser.Providers.Manager /// /// The result. /// Task. - protected Task SaveProviderResult(ProviderResult result) + protected Task SaveProviderResult(MetadataStatus result) { - return Task.FromResult(true); + return _providerRepo.SaveMetadataStatus(result, CancellationToken.None); } /// @@ -58,12 +61,9 @@ namespace MediaBrowser.Providers.Manager /// /// The item identifier. /// ProviderResult. - protected ProviderResult GetLastResult(Guid itemId) + protected MetadataStatus GetLastResult(Guid itemId) { - return new ProviderResult - { - ItemId = itemId - }; + return _providerRepo.GetMetadataStatus(itemId) ?? new MetadataStatus { ItemId = itemId }; } public async Task RefreshMetadata(IHasMetadata item, MetadataRefreshOptions options, CancellationToken cancellationToken) @@ -72,7 +72,9 @@ namespace MediaBrowser.Providers.Manager var updateType = ItemUpdateType.Unspecified; var lastResult = GetLastResult(item.Id); - var refreshResult = new ProviderResult { ItemId = item.Id }; + var refreshResult = lastResult; + refreshResult.LastErrorMessage = string.Empty; + refreshResult.LastStatus = ProviderRefreshStatus.Success; var imageProviders = GetImageProviders(item).ToList(); var itemImageProvider = new ItemImageProvider(Logger, ProviderManager, ServerConfigurationManager); @@ -97,7 +99,7 @@ namespace MediaBrowser.Providers.Manager // Next run metadata providers if (options.MetadataRefreshMode != MetadataRefreshMode.None) { - var providers = GetProviders(item, lastResult.HasRefreshedMetadata, options).ToList(); + var providers = GetProviders(item, lastResult.DateLastMetadataRefresh.HasValue, options).ToList(); if (providers.Count > 0) { @@ -105,22 +107,23 @@ namespace MediaBrowser.Providers.Manager updateType = updateType | result.UpdateType; refreshResult.AddStatus(result.Status, result.ErrorMessage); + refreshResult.SetDateLastMetadataRefresh(DateTime.UtcNow); + refreshResult.AddImageProvidersRefreshed(result.Providers); } - - refreshResult.HasRefreshedMetadata = true; } // Next run remote image providers, but only if local image providers didn't throw an exception if (!localImagesFailed) { - if ((options.ImageRefreshMode == MetadataRefreshMode.EnsureMetadata && !lastResult.HasRefreshedImages) || + if ((options.ImageRefreshMode == MetadataRefreshMode.EnsureMetadata && !lastResult.DateLastImagesRefresh.HasValue) || options.ImageRefreshMode == MetadataRefreshMode.FullRefresh) { - var imagesReult = await itemImageProvider.RefreshImages(itemOfType, imageProviders, options, cancellationToken).ConfigureAwait(false); + var result = await itemImageProvider.RefreshImages(itemOfType, imageProviders, options, cancellationToken).ConfigureAwait(false); - updateType = updateType | imagesReult.UpdateType; - refreshResult.AddStatus(imagesReult.Status, imagesReult.ErrorMessage); - refreshResult.HasRefreshedImages = true; + updateType = updateType | result.UpdateType; + refreshResult.AddStatus(result.Status, result.ErrorMessage); + refreshResult.SetDateLastImagesRefresh(DateTime.UtcNow); + refreshResult.AddImageProvidersRefreshed(result.Providers); } } @@ -137,9 +140,8 @@ namespace MediaBrowser.Providers.Manager await SaveItem(itemOfType, updateType, cancellationToken); } - if (providersHadChanges) + if (providersHadChanges || refreshResult.IsDirty) { - refreshResult.DateLastRefreshed = DateTime.UtcNow; await SaveProviderResult(refreshResult).ConfigureAwait(false); } } @@ -165,7 +167,7 @@ namespace MediaBrowser.Providers.Manager var currentItem = item; var providersWithChanges = providers.OfType() - .Where(i => i.HasChanged(currentItem, item.DateLastSaved)) + .Where(i => i.HasChanged(currentItem, currentItem.DateLastSaved)) .ToList(); // If local providers are the only ones with changes, then just run those @@ -219,7 +221,11 @@ namespace MediaBrowser.Providers.Manager protected virtual async Task RefreshWithProviders(TItemType item, MetadataRefreshOptions options, List providers, CancellationToken cancellationToken) { - var refreshResult = new RefreshResult { UpdateType = ItemUpdateType.Unspecified }; + var refreshResult = new RefreshResult + { + UpdateType = ItemUpdateType.Unspecified, + Providers = providers.Select(i => i.GetType().FullName.GetMD5()).ToList() + }; var temp = new TItemType(); @@ -347,5 +353,6 @@ namespace MediaBrowser.Providers.Manager public ItemUpdateType UpdateType { get; set; } public ProviderRefreshStatus Status { get; set; } public string ErrorMessage { get; set; } + public List Providers { get; set; } } } diff --git a/MediaBrowser.Providers/Manager/ProviderManager.cs b/MediaBrowser.Providers/Manager/ProviderManager.cs index faad15669..3696bd02f 100644 --- a/MediaBrowser.Providers/Manager/ProviderManager.cs +++ b/MediaBrowser.Providers/Manager/ProviderManager.cs @@ -55,7 +55,7 @@ namespace MediaBrowser.Providers.Manager private readonly IFileSystem _fileSystem; - private readonly IItemRepository _itemRepo; + private readonly IProviderRepository _providerRepo; private IMetadataService[] _metadataServices = { }; @@ -67,15 +67,15 @@ namespace MediaBrowser.Providers.Manager /// The directory watchers. /// The log manager. /// The file system. - /// The item repo. - public ProviderManager(IHttpClient httpClient, IServerConfigurationManager configurationManager, ILibraryMonitor libraryMonitor, ILogManager logManager, IFileSystem fileSystem, IItemRepository itemRepo) + /// The provider repo. + public ProviderManager(IHttpClient httpClient, IServerConfigurationManager configurationManager, ILibraryMonitor libraryMonitor, ILogManager logManager, IFileSystem fileSystem, IProviderRepository providerRepo) { _logger = logManager.GetLogger("ProviderManager"); _httpClient = httpClient; ConfigurationManager = configurationManager; _libraryMonitor = libraryMonitor; _fileSystem = fileSystem; - _itemRepo = itemRepo; + _providerRepo = providerRepo; } /// @@ -136,7 +136,7 @@ namespace MediaBrowser.Providers.Manager var providerHistories = item.DateLastSaved == default(DateTime) ? new List() : - _itemRepo.GetProviderHistory(item.Id).ToList(); + _providerRepo.GetProviderHistory(item.Id).ToList(); // Run the normal providers sequentially in order of priority foreach (var provider in MetadataProviders) @@ -201,7 +201,7 @@ namespace MediaBrowser.Providers.Manager if (result.HasValue || force) { - await _itemRepo.SaveProviderHistory(item.Id, providerHistories, cancellationToken); + await _providerRepo.SaveProviderHistory(item.Id, providerHistories, cancellationToken); } return result; diff --git a/MediaBrowser.Providers/MusicGenres/MusicGenreMetadataService.cs b/MediaBrowser.Providers/MusicGenres/MusicGenreMetadataService.cs index bc4a99fc3..b88ca92bc 100644 --- a/MediaBrowser.Providers/MusicGenres/MusicGenreMetadataService.cs +++ b/MediaBrowser.Providers/MusicGenres/MusicGenreMetadataService.cs @@ -15,8 +15,8 @@ namespace MediaBrowser.Providers.MusicGenres { private readonly ILibraryManager _libraryManager; - public MusicGenreMetadataService(IServerConfigurationManager serverConfigurationManager, ILogger logger, IProviderManager providerManager, ILibraryManager libraryManager) - : base(serverConfigurationManager, logger, providerManager) + public MusicGenreMetadataService(IServerConfigurationManager serverConfigurationManager, ILogger logger, IProviderManager providerManager, IProviderRepository providerRepo, ILibraryManager libraryManager) + : base(serverConfigurationManager, logger, providerManager, providerRepo) { _libraryManager = libraryManager; } diff --git a/MediaBrowser.Providers/People/PersonMetadataService.cs b/MediaBrowser.Providers/People/PersonMetadataService.cs index 577158aa8..e04013934 100644 --- a/MediaBrowser.Providers/People/PersonMetadataService.cs +++ b/MediaBrowser.Providers/People/PersonMetadataService.cs @@ -15,8 +15,8 @@ namespace MediaBrowser.Providers.People { private readonly ILibraryManager _libraryManager; - public PersonMetadataService(IServerConfigurationManager serverConfigurationManager, ILogger logger, IProviderManager providerManager, ILibraryManager libraryManager) - : base(serverConfigurationManager, logger, providerManager) + public PersonMetadataService(IServerConfigurationManager serverConfigurationManager, ILogger logger, IProviderManager providerManager, IProviderRepository providerRepo, ILibraryManager libraryManager) + : base(serverConfigurationManager, logger, providerManager, providerRepo) { _libraryManager = libraryManager; } diff --git a/MediaBrowser.Providers/Studios/StudioMetadataService.cs b/MediaBrowser.Providers/Studios/StudioMetadataService.cs index 1a74d8317..1a35b94b3 100644 --- a/MediaBrowser.Providers/Studios/StudioMetadataService.cs +++ b/MediaBrowser.Providers/Studios/StudioMetadataService.cs @@ -15,8 +15,8 @@ namespace MediaBrowser.Providers.Studios { private readonly ILibraryManager _libraryManager; - public StudioMetadataService(IServerConfigurationManager serverConfigurationManager, ILogger logger, IProviderManager providerManager, ILibraryManager libraryManager) - : base(serverConfigurationManager, logger, providerManager) + public StudioMetadataService(IServerConfigurationManager serverConfigurationManager, ILogger logger, IProviderManager providerManager, IProviderRepository providerRepo, ILibraryManager libraryManager) + : base(serverConfigurationManager, logger, providerManager, providerRepo) { _libraryManager = libraryManager; } diff --git a/MediaBrowser.Server.Implementations/IO/LibraryMonitor.cs b/MediaBrowser.Server.Implementations/IO/LibraryMonitor.cs index 22ea668f4..0716a3d83 100644 --- a/MediaBrowser.Server.Implementations/IO/LibraryMonitor.cs +++ b/MediaBrowser.Server.Implementations/IO/LibraryMonitor.cs @@ -56,21 +56,6 @@ namespace MediaBrowser.Server.Implementations.IO _tempIgnoredPaths[path] = path; } - /// - /// Removes the temp ignore. - /// - /// The path. - private async void RemoveTempIgnore(string path) - { - // This is an arbitraty amount of time, but delay it because file system writes often trigger events after RemoveTempIgnore has been called. - // Seeing long delays in some situations, especially over the network. - // Seeing delays up to 40 seconds, but not going to ignore changes for that long. - await Task.Delay(1500).ConfigureAwait(false); - - string val; - _tempIgnoredPaths.TryRemove(path, out val); - } - public void ReportFileSystemChangeBeginning(string path) { if (string.IsNullOrEmpty(path)) @@ -81,14 +66,20 @@ namespace MediaBrowser.Server.Implementations.IO TemporarilyIgnore(path); } - public void ReportFileSystemChangeComplete(string path, bool refreshPath) + public async void ReportFileSystemChangeComplete(string path, bool refreshPath) { if (string.IsNullOrEmpty(path)) { throw new ArgumentNullException("path"); } - RemoveTempIgnore(path); + // This is an arbitraty amount of time, but delay it because file system writes often trigger events after RemoveTempIgnore has been called. + // Seeing long delays in some situations, especially over the network. + // Seeing delays up to 40 seconds, but not going to ignore changes for that long. + await Task.Delay(1500).ConfigureAwait(false); + + string val; + _tempIgnoredPaths.TryRemove(path, out val); if (refreshPath) { diff --git a/MediaBrowser.Server.Implementations/Persistence/SqliteItemRepository.cs b/MediaBrowser.Server.Implementations/Persistence/SqliteItemRepository.cs index 200898a62..6b463bbdf 100644 --- a/MediaBrowser.Server.Implementations/Persistence/SqliteItemRepository.cs +++ b/MediaBrowser.Server.Implementations/Persistence/SqliteItemRepository.cs @@ -58,7 +58,6 @@ namespace MediaBrowser.Server.Implementations.Persistence private SqliteChapterRepository _chapterRepository; private SqliteMediaStreamsRepository _mediaStreamsRepository; - private SqliteProviderInfoRepository _providerInfoRepository; private IDbCommand _deleteChildrenCommand; private IDbCommand _saveChildrenCommand; @@ -99,10 +98,6 @@ namespace MediaBrowser.Server.Implementations.Persistence var mediaStreamsDbFile = Path.Combine(_appPaths.DataPath, "mediainfo.db"); var mediaStreamsConnection = SqliteExtensions.ConnectToDb(mediaStreamsDbFile, _logger).Result; _mediaStreamsRepository = new SqliteMediaStreamsRepository(mediaStreamsConnection, logManager); - - var providerInfosDbFile = Path.Combine(_appPaths.DataPath, "providerinfo.db"); - var providerInfoConnection = SqliteExtensions.ConnectToDb(providerInfosDbFile, _logger).Result; - _providerInfoRepository = new SqliteProviderInfoRepository(providerInfoConnection, logManager); } /// @@ -134,7 +129,6 @@ namespace MediaBrowser.Server.Implementations.Persistence PrepareStatements(); _mediaStreamsRepository.Initialize(); - _providerInfoRepository.Initialize(); _chapterRepository.Initialize(); _shrinkMemoryTimer = new SqliteShrinkMemoryTimer(_connection, _writeLock, _logger); @@ -436,12 +430,6 @@ namespace MediaBrowser.Server.Implementations.Persistence _mediaStreamsRepository.Dispose(); _mediaStreamsRepository = null; } - - if (_providerInfoRepository != null) - { - _providerInfoRepository.Dispose(); - _providerInfoRepository = null; - } } } catch (Exception ex) @@ -556,15 +544,5 @@ namespace MediaBrowser.Server.Implementations.Persistence { return _mediaStreamsRepository.SaveMediaStreams(id, streams, cancellationToken); } - - public IEnumerable GetProviderHistory(Guid itemId) - { - return _providerInfoRepository.GetBaseProviderInfos(itemId); - } - - public Task SaveProviderHistory(Guid id, IEnumerable history, CancellationToken cancellationToken) - { - return _providerInfoRepository.SaveProviderInfos(id, history, cancellationToken); - } } } \ No newline at end of file diff --git a/MediaBrowser.Server.Implementations/Persistence/SqliteProviderInfoRepository.cs b/MediaBrowser.Server.Implementations/Persistence/SqliteProviderInfoRepository.cs index 9971c7460..8a82c062d 100644 --- a/MediaBrowser.Server.Implementations/Persistence/SqliteProviderInfoRepository.cs +++ b/MediaBrowser.Server.Implementations/Persistence/SqliteProviderInfoRepository.cs @@ -1,4 +1,6 @@ -using MediaBrowser.Controller.Providers; +using System.IO; +using MediaBrowser.Common.Configuration; +using MediaBrowser.Controller.Providers; using MediaBrowser.Model.Logging; using System; using System.Collections.Generic; @@ -9,7 +11,7 @@ using System.Threading.Tasks; namespace MediaBrowser.Server.Implementations.Persistence { - class SqliteProviderInfoRepository + public class SqliteProviderInfoRepository : IProviderRepository { private IDbConnection _connection; @@ -17,32 +19,47 @@ namespace MediaBrowser.Server.Implementations.Persistence private IDbCommand _deleteInfosCommand; private IDbCommand _saveInfoCommand; + private IDbCommand _saveStatusCommand; + private readonly IApplicationPaths _appPaths; - public SqliteProviderInfoRepository(IDbConnection connection, ILogManager logManager) + public SqliteProviderInfoRepository(IApplicationPaths appPaths, ILogManager logManager) { - _connection = connection; - + _appPaths = appPaths; _logger = logManager.GetLogger(GetType().Name); } private SqliteShrinkMemoryTimer _shrinkMemoryTimer; - + + /// + /// Gets the name of the repository + /// + /// The name. + public string Name + { + get + { + return "SQLite"; + } + } + /// /// Opens the connection to the database /// /// Task. - public void Initialize() + public async Task Initialize() { - var createTableCommand - = "create table if not exists providerinfos "; + var dbFile = Path.Combine(_appPaths.DataPath, "providerinfo.db"); - createTableCommand += "(ItemId GUID, ProviderId GUID, ProviderVersion TEXT, FileStamp GUID, LastRefreshStatus TEXT, LastRefreshed datetime, PRIMARY KEY (ItemId, ProviderId))"; + _connection = await SqliteExtensions.ConnectToDb(dbFile, _logger).ConfigureAwait(false); string[] queries = { - createTableCommand, + "create table if not exists providerinfos (ItemId GUID, ProviderId GUID, ProviderVersion TEXT, FileStamp GUID, LastRefreshStatus TEXT, LastRefreshed datetime, PRIMARY KEY (ItemId, ProviderId))", "create index if not exists idx_providerinfos on providerinfos(ItemId, ProviderId)", + "create table if not exists MetadataStatus (ItemId GUID PRIMARY KEY, DateLastMetadataRefresh datetime, DateLastImagesRefresh datetime, LastStatus TEXT, LastErrorMessage TEXT, MetadataProvidersRefreshed TEXT, ImageProvidersRefreshed TEXT)", + "create index if not exists idx_MetadataStatus on MetadataStatus(ItemId)", + //pragmas "pragma temp_store = memory", @@ -56,7 +73,7 @@ namespace MediaBrowser.Server.Implementations.Persistence _shrinkMemoryTimer = new SqliteShrinkMemoryTimer(_connection, _writeLock, _logger); } - private static readonly string[] SaveColumns = + private static readonly string[] SaveHistoryColumns = { "ItemId", "ProviderId", @@ -66,7 +83,18 @@ namespace MediaBrowser.Server.Implementations.Persistence "LastRefreshed" }; - private readonly string[] _selectColumns = SaveColumns.Skip(1).ToArray(); + private readonly string[] _historySelectColumns = SaveHistoryColumns.Skip(1).ToArray(); + + private static readonly string[] StatusColumns = + { + "ItemId", + "DateLastMetadataRefresh", + "DateLastImagesRefresh", + "LastStatus", + "LastErrorMessage", + "MetadataProvidersRefreshed", + "ImageProvidersRefreshed" + }; /// /// The _write lock @@ -85,16 +113,27 @@ namespace MediaBrowser.Server.Implementations.Persistence _saveInfoCommand = _connection.CreateCommand(); _saveInfoCommand.CommandText = string.Format("replace into providerinfos ({0}) values ({1})", - string.Join(",", SaveColumns), - string.Join(",", SaveColumns.Select(i => "@" + i).ToArray())); + string.Join(",", SaveHistoryColumns), + string.Join(",", SaveHistoryColumns.Select(i => "@" + i).ToArray())); - foreach (var col in SaveColumns) + foreach (var col in SaveHistoryColumns) { _saveInfoCommand.Parameters.Add(_saveInfoCommand, "@" + col); } + + _saveStatusCommand = _connection.CreateCommand(); + + _saveStatusCommand.CommandText = string.Format("replace into MetadataStatus ({0}) values ({1})", + string.Join(",", StatusColumns), + string.Join(",", StatusColumns.Select(i => "@" + i).ToArray())); + + foreach (var col in StatusColumns) + { + _saveStatusCommand.Parameters.Add(_saveStatusCommand, "@" + col); + } } - public IEnumerable GetBaseProviderInfos(Guid itemId) + public IEnumerable GetProviderHistory(Guid itemId) { if (itemId == Guid.Empty) { @@ -103,7 +142,7 @@ namespace MediaBrowser.Server.Implementations.Persistence using (var cmd = _connection.CreateCommand()) { - var cmdText = "select " + string.Join(",", _selectColumns) + " from providerinfos where"; + var cmdText = "select " + string.Join(",", _historySelectColumns) + " from providerinfos where"; cmdText += " ItemId=@ItemId"; cmd.Parameters.Add(cmd, "@ItemId", DbType.Guid).Value = itemId; @@ -121,10 +160,10 @@ namespace MediaBrowser.Server.Implementations.Persistence } /// - /// Gets the chapter. + /// Gets the base provider information. /// /// The reader. - /// ChapterInfo. + /// BaseProviderInfo. private BaseProviderInfo GetBaseProviderInfo(IDataReader reader) { var item = new BaseProviderInfo @@ -144,7 +183,7 @@ namespace MediaBrowser.Server.Implementations.Persistence return item; } - public async Task SaveProviderInfos(Guid id, IEnumerable infos, CancellationToken cancellationToken) + public async Task SaveProviderHistory(Guid id, IEnumerable infos, CancellationToken cancellationToken) { if (id == Guid.Empty) { @@ -166,7 +205,6 @@ namespace MediaBrowser.Server.Implementations.Persistence { transaction = _connection.BeginTransaction(); - // First delete chapters _deleteInfosCommand.GetParameter(0).Value = id; _deleteInfosCommand.Transaction = transaction; @@ -221,6 +259,136 @@ namespace MediaBrowser.Server.Implementations.Persistence } } + public MetadataStatus GetMetadataStatus(Guid itemId) + { + if (itemId == Guid.Empty) + { + throw new ArgumentNullException("itemId"); + } + + using (var cmd = _connection.CreateCommand()) + { + var cmdText = "select " + string.Join(",", StatusColumns) + " from MetadataStatus where"; + + cmdText += " ItemId=@ItemId"; + cmd.Parameters.Add(cmd, "@ItemId", DbType.Guid).Value = itemId; + + cmd.CommandText = cmdText; + + using (var reader = cmd.ExecuteReader(CommandBehavior.SequentialAccess | CommandBehavior.SingleResult | CommandBehavior.SingleRow)) + { + while (reader.Read()) + { + return GetStatus(reader); + } + + return null; + } + } + } + + private MetadataStatus GetStatus(IDataReader reader) + { + var result = new MetadataStatus + { + ItemId = reader.GetGuid(0) + }; + + if (!reader.IsDBNull(1)) + { + result.DateLastMetadataRefresh = reader.GetDateTime(1).ToUniversalTime(); + } + + if (!reader.IsDBNull(2)) + { + result.DateLastImagesRefresh = reader.GetDateTime(2).ToUniversalTime(); + } + + if (!reader.IsDBNull(3)) + { + result.LastStatus = (ProviderRefreshStatus)Enum.Parse(typeof(ProviderRefreshStatus), reader.GetString(3), true); + } + + if (!reader.IsDBNull(4)) + { + result.LastErrorMessage = reader.GetString(4); + } + + if (!reader.IsDBNull(5)) + { + result.MetadataProvidersRefreshed = reader.GetString(5).Split('|').Where(i => !string.IsNullOrEmpty(i)).Select(i => new Guid(i)).ToList(); + } + + if (!reader.IsDBNull(6)) + { + result.ImageProvidersRefreshed = reader.GetString(6).Split('|').Where(i => !string.IsNullOrEmpty(i)).Select(i => new Guid(i)).ToList(); + } + + return result; + } + + public async Task SaveMetadataStatus(MetadataStatus status, CancellationToken cancellationToken) + { + if (status == null) + { + throw new ArgumentNullException("status"); + } + + cancellationToken.ThrowIfCancellationRequested(); + + await _writeLock.WaitAsync(cancellationToken).ConfigureAwait(false); + + IDbTransaction transaction = null; + + try + { + transaction = _connection.BeginTransaction(); + + _saveStatusCommand.GetParameter(0).Value = status.ItemId; + _saveStatusCommand.GetParameter(1).Value = status.DateLastMetadataRefresh; + _saveStatusCommand.GetParameter(2).Value = status.DateLastImagesRefresh; + _saveStatusCommand.GetParameter(3).Value = status.LastStatus.ToString(); + _saveStatusCommand.GetParameter(4).Value = status.LastErrorMessage; + _saveStatusCommand.GetParameter(5).Value = string.Join("|", status.MetadataProvidersRefreshed.ToArray()); + _saveStatusCommand.GetParameter(6).Value = string.Join("|", status.ImageProvidersRefreshed.ToArray()); + + _saveStatusCommand.Transaction = transaction; + + _saveStatusCommand.ExecuteNonQuery(); + + transaction.Commit(); + } + catch (OperationCanceledException) + { + if (transaction != null) + { + transaction.Rollback(); + } + + throw; + } + catch (Exception e) + { + _logger.ErrorException("Failed to save provider info:", e); + + if (transaction != null) + { + transaction.Rollback(); + } + + throw; + } + finally + { + if (transaction != null) + { + transaction.Dispose(); + } + + _writeLock.Release(); + } + } + /// /// Performs application-defined tasks associated with freeing, releasing, or resetting unmanaged resources. /// diff --git a/MediaBrowser.ServerApplication/ApplicationHost.cs b/MediaBrowser.ServerApplication/ApplicationHost.cs index bf652d8cb..4b15ca8d0 100644 --- a/MediaBrowser.ServerApplication/ApplicationHost.cs +++ b/MediaBrowser.ServerApplication/ApplicationHost.cs @@ -14,7 +14,6 @@ using MediaBrowser.Controller.Drawing; using MediaBrowser.Controller.Dto; using MediaBrowser.Controller.Entities; using MediaBrowser.Controller.FileOrganization; -using MediaBrowser.Controller.IO; using MediaBrowser.Controller.Library; using MediaBrowser.Controller.LiveTv; using MediaBrowser.Controller.Localization; @@ -173,6 +172,7 @@ namespace MediaBrowser.ServerApplication internal IItemRepository ItemRepository { get; set; } private INotificationsRepository NotificationsRepository { get; set; } private IFileOrganizationRepository FileOrganizationRepository { get; set; } + private IProviderRepository ProviderRepository { get; set; } /// /// Initializes a new instance of the class. @@ -267,6 +267,9 @@ namespace MediaBrowser.ServerApplication ItemRepository = new SqliteItemRepository(ApplicationPaths, JsonSerializer, LogManager); RegisterSingleInstance(ItemRepository); + ProviderRepository = new SqliteProviderInfoRepository(ApplicationPaths, LogManager); + RegisterSingleInstance(ProviderRepository); + FileOrganizationRepository = await GetFileOrganizationRepository().ConfigureAwait(false); RegisterSingleInstance(FileOrganizationRepository); @@ -279,7 +282,7 @@ namespace MediaBrowser.ServerApplication LibraryMonitor = new LibraryMonitor(LogManager, TaskManager, LibraryManager, ServerConfigurationManager, FileSystemManager); RegisterSingleInstance(LibraryMonitor); - ProviderManager = new ProviderManager(HttpClient, ServerConfigurationManager, LibraryMonitor, LogManager, FileSystemManager, ItemRepository); + ProviderManager = new ProviderManager(HttpClient, ServerConfigurationManager, LibraryMonitor, LogManager, FileSystemManager, ProviderRepository); RegisterSingleInstance(ProviderManager); RegisterSingleInstance(() => new SearchEngine(LogManager, LibraryManager, UserManager)); @@ -427,6 +430,8 @@ namespace MediaBrowser.ServerApplication { await ItemRepository.Initialize().ConfigureAwait(false); + await ProviderRepository.Initialize().ConfigureAwait(false); + ((LibraryManager)LibraryManager).ItemRepository = ItemRepository; } -- cgit v1.2.3