From 9911df11e8a96d5d7fffcd8618b63a6adb27701f Mon Sep 17 00:00:00 2001 From: LukePulverenti Date: Fri, 8 Mar 2013 00:08:27 -0500 Subject: extracted provider manager. took more off the kernel --- MediaBrowser.Api/Playback/BaseStreamingService.cs | 2 +- .../BaseApplicationHost.cs | 5 +- MediaBrowser.Controller/Entities/BaseItem.cs | 9 +- MediaBrowser.Controller/Entities/User.cs | 6 +- MediaBrowser.Controller/IO/DirectoryWatchers.cs | 542 -------------------- MediaBrowser.Controller/IO/FileSystemManager.cs | 120 ----- MediaBrowser.Controller/IO/IDirectoryWatchers.cs | 29 ++ MediaBrowser.Controller/IServerApplicationPaths.cs | 8 +- MediaBrowser.Controller/Kernel.cs | 174 +------ .../Localization/LocalizedStrings.cs | 2 +- MediaBrowser.Controller/Localization/Ratings.cs | 2 +- .../MediaBrowser.Controller.csproj | 6 +- .../Providers/BaseMetadataProvider.cs | 137 +----- .../Providers/FolderProviderFromXml.cs | 2 +- .../Providers/IProviderManager.cs | 51 ++ .../Providers/ImageFromMediaLocationProvider.cs | 7 +- .../Providers/MediaInfo/BaseFFProbeProvider.cs | 11 +- .../MediaInfo/FFMpegAudioImageProvider.cs | 7 +- .../MediaInfo/FFMpegVideoImageProvider.cs | 7 +- .../Providers/MetadataProviderPriority.cs | 33 ++ .../Providers/Movies/FanArtMovieProvider.cs | 27 +- .../Providers/Movies/MovieDbProvider.cs | 30 +- .../Providers/Movies/MovieProviderFromJson.cs | 6 +- .../Providers/Movies/MovieProviderFromXml.cs | 2 +- .../Providers/Movies/PersonProviderFromJson.cs | 6 +- .../Providers/Movies/TmdbPersonProvider.cs | 13 +- .../Providers/Music/FanArtAlbumProvider.cs | 10 +- .../Providers/Music/FanArtArtistProvider.cs | 17 +- .../Providers/Music/LastfmAlbumProvider.cs | 7 +- .../Providers/Music/LastfmArtistProvider.cs | 8 +- .../Providers/Music/LastfmBaseProvider.cs | 2 +- .../Providers/Music/MusicArtistProviderFromJson.cs | 2 +- .../Providers/ProviderManager.cs | 372 -------------- .../Providers/SortNameProvider.cs | 13 +- .../TV/EpisodeImageFromMediaLocationProvider.cs | 7 +- .../Providers/TV/EpisodeProviderFromXml.cs | 2 +- .../Providers/TV/FanArtTVProvider.cs | 13 +- .../Providers/TV/RemoteEpisodeProvider.cs | 11 +- .../Providers/TV/RemoteSeasonProvider.cs | 17 +- .../Providers/TV/RemoteSeriesProvider.cs | 28 +- .../Providers/TV/SeriesProviderFromXml.cs | 2 +- .../IO/DirectoryWatchers.cs | 543 +++++++++++++++++++++ .../MediaBrowser.Server.Implementations.csproj | 2 + .../Providers/ProviderManager.cs | 487 ++++++++++++++++++ .../ScheduledTasks/ImageCleanupTask.cs | 8 +- .../ServerApplicationPaths.cs | 28 +- .../ServerManager/ServerManager.cs | 4 +- MediaBrowser.ServerApplication/App.xaml.cs | 8 +- MediaBrowser.ServerApplication/ApplicationHost.cs | 143 +++++- .../EntryPoints/NewItemNotifier.cs | 148 ++++++ .../EntryPoints/RefreshUsersMetadata.cs | 41 ++ .../EntryPoints/StartupWizard.cs | 65 +++ .../EntryPoints/WebSocketEvents.cs | 221 +++++++++ MediaBrowser.ServerApplication/MainWindow.xaml.cs | 19 +- .../MediaBrowser.ServerApplication.csproj | 15 +- MediaBrowser.ServerApplication/NewItemNotifier.cs | 148 ------ MediaBrowser.ServerApplication/StartupWizard.cs | 65 --- MediaBrowser.ServerApplication/WebSocketEvents.cs | 221 --------- MediaBrowser.sln | 3 + 59 files changed, 2022 insertions(+), 1902 deletions(-) delete mode 100644 MediaBrowser.Controller/IO/DirectoryWatchers.cs delete mode 100644 MediaBrowser.Controller/IO/FileSystemManager.cs create mode 100644 MediaBrowser.Controller/IO/IDirectoryWatchers.cs create mode 100644 MediaBrowser.Controller/Providers/IProviderManager.cs create mode 100644 MediaBrowser.Controller/Providers/MetadataProviderPriority.cs delete mode 100644 MediaBrowser.Controller/Providers/ProviderManager.cs create mode 100644 MediaBrowser.Server.Implementations/IO/DirectoryWatchers.cs create mode 100644 MediaBrowser.Server.Implementations/Providers/ProviderManager.cs create mode 100644 MediaBrowser.ServerApplication/EntryPoints/NewItemNotifier.cs create mode 100644 MediaBrowser.ServerApplication/EntryPoints/RefreshUsersMetadata.cs create mode 100644 MediaBrowser.ServerApplication/EntryPoints/StartupWizard.cs create mode 100644 MediaBrowser.ServerApplication/EntryPoints/WebSocketEvents.cs delete mode 100644 MediaBrowser.ServerApplication/NewItemNotifier.cs delete mode 100644 MediaBrowser.ServerApplication/StartupWizard.cs delete mode 100644 MediaBrowser.ServerApplication/WebSocketEvents.cs diff --git a/MediaBrowser.Api/Playback/BaseStreamingService.cs b/MediaBrowser.Api/Playback/BaseStreamingService.cs index fb5f56693..012a55363 100644 --- a/MediaBrowser.Api/Playback/BaseStreamingService.cs +++ b/MediaBrowser.Api/Playback/BaseStreamingService.cs @@ -93,7 +93,7 @@ namespace MediaBrowser.Api.Playback /// System.String. protected string GetOutputFilePath(StreamState state) { - var folder = ApplicationPaths.FFMpegStreamCachePath; + var folder = ApplicationPaths.EncodedMediaCachePath; return Path.Combine(folder, GetCommandLineArguments("dummy\\dummy", state).GetMD5() + GetOutputFileExtension(state).ToLower()); } diff --git a/MediaBrowser.Common.Implementations/BaseApplicationHost.cs b/MediaBrowser.Common.Implementations/BaseApplicationHost.cs index a5af2ff66..a4d191148 100644 --- a/MediaBrowser.Common.Implementations/BaseApplicationHost.cs +++ b/MediaBrowser.Common.Implementations/BaseApplicationHost.cs @@ -77,7 +77,7 @@ namespace MediaBrowser.Common.Implementations /// /// The json serializer /// - protected readonly IJsonSerializer JsonSerializer = new JsonSerializer(); + public readonly IJsonSerializer JsonSerializer = new JsonSerializer(); /// /// The _XML serializer @@ -524,8 +524,7 @@ namespace MediaBrowser.Common.Implementations /// Task. public async Task UpdateApplication(PackageVersionInfo package, CancellationToken cancellationToken, IProgress progress) { - var pkgManager = Resolve(); - await pkgManager.InstallPackage(progress, package, cancellationToken).ConfigureAwait(false); + await PackageManager.InstallPackage(progress, package, cancellationToken).ConfigureAwait(false); EventHelper.QueueEventIfNotNull(ApplicationUpdated, this, new GenericEventArgs { Argument = package.version }, Logger); } diff --git a/MediaBrowser.Controller/Entities/BaseItem.cs b/MediaBrowser.Controller/Entities/BaseItem.cs index e80c3d71f..f90044d8b 100644 --- a/MediaBrowser.Controller/Entities/BaseItem.cs +++ b/MediaBrowser.Controller/Entities/BaseItem.cs @@ -96,9 +96,10 @@ namespace MediaBrowser.Controller.Entities /// /// The logger /// - protected static internal ILogger Logger { get; internal set; } - protected static internal ILibraryManager LibraryManager { get; internal set; } - protected static internal IServerConfigurationManager ConfigurationManager { get; internal set; } + public static ILogger Logger { get; set; } + public static ILibraryManager LibraryManager { get; set; } + public static IServerConfigurationManager ConfigurationManager { get; set; } + public static IProviderManager ProviderManager { get; set; } /// /// Returns a that represents this instance. @@ -652,7 +653,7 @@ namespace MediaBrowser.Controller.Entities LocalTrailers = null; // Refresh for the item - var itemRefreshTask = Kernel.Instance.ProviderManager.ExecuteMetadataProviders(this, cancellationToken, forceRefresh, allowSlowProviders); + var itemRefreshTask = ProviderManager.ExecuteMetadataProviders(this, cancellationToken, forceRefresh, allowSlowProviders); cancellationToken.ThrowIfCancellationRequested(); diff --git a/MediaBrowser.Controller/Entities/User.cs b/MediaBrowser.Controller/Entities/User.cs index ef50a0867..7e50b1ea5 100644 --- a/MediaBrowser.Controller/Entities/User.cs +++ b/MediaBrowser.Controller/Entities/User.cs @@ -18,8 +18,8 @@ namespace MediaBrowser.Controller.Entities /// public class User : BaseItem { - internal static IUserManager UserManager { get; set; } - internal static IXmlSerializer XmlSerializer { get; set; } + public static IUserManager UserManager { get; set; } + public static IXmlSerializer XmlSerializer { get; set; } /// /// The _root folder path @@ -363,7 +363,7 @@ namespace MediaBrowser.Controller.Entities ResolveArgs = null; } - var changed = await Kernel.Instance.ProviderManager.ExecuteMetadataProviders(this, cancellationToken, forceRefresh, allowSlowProviders).ConfigureAwait(false); + var changed = await ProviderManager.ExecuteMetadataProviders(this, cancellationToken, forceRefresh, allowSlowProviders).ConfigureAwait(false); if (changed || forceSave) { diff --git a/MediaBrowser.Controller/IO/DirectoryWatchers.cs b/MediaBrowser.Controller/IO/DirectoryWatchers.cs deleted file mode 100644 index 5789c0240..000000000 --- a/MediaBrowser.Controller/IO/DirectoryWatchers.cs +++ /dev/null @@ -1,542 +0,0 @@ -using MediaBrowser.Common.ScheduledTasks; -using MediaBrowser.Controller.Configuration; -using MediaBrowser.Controller.Entities; -using MediaBrowser.Controller.Library; -using MediaBrowser.Controller.ScheduledTasks; -using MediaBrowser.Model.Logging; -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.Controller.IO -{ - /// - /// Class DirectoryWatchers - /// - public class DirectoryWatchers : IDisposable - { - /// - /// The file system watchers - /// - private ConcurrentBag FileSystemWatchers = new ConcurrentBag(); - /// - /// 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); - - /// - /// 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 void RemoveTempIgnore(string path) - { - 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; } - - /// - /// Initializes a new instance of the class. - /// - public DirectoryWatchers(ILogManager logManager, ITaskManager taskManager, ILibraryManager libraryManager, IServerConfigurationManager configurationManager) - { - if (taskManager == null) - { - throw new ArgumentNullException("taskManager"); - } - - LibraryManager = libraryManager; - TaskManager = taskManager; - Logger = logManager.GetLogger("DirectoryWatchers"); - ConfigurationManager = configurationManager; - } - - /// - /// Starts this instance. - /// - internal void Start() - { - LibraryManager.LibraryChanged += Instance_LibraryChanged; - - var pathsToWatch = new List { LibraryManager.RootFolder.Path }; - - var paths = LibraryManager.RootFolder.Children.OfType() - .SelectMany(f => - { - try - { - // Accessing ResolveArgs could involve file system access - return f.ResolveArgs.PhysicalLocations; - } - catch (IOException) - { - return new string[] {}; - } - - }) - .Where(Path.IsPathRooted); - - foreach (var path in paths) - { - if (!ContainsParentFolder(pathsToWatch, path)) - { - pathsToWatch.Add(path); - } - } - - foreach (var path in pathsToWatch) - { - StartWatchingPath(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 - { - newWatcher.EnableRaisingEvents = true; - FileSystemWatchers.Add(newWatcher); - - Logger.Info("Watching directory " + path); - } - 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) - { - var watcher = FileSystemWatchers.FirstOrDefault(f => f.Path.Equals(path, StringComparison.OrdinalIgnoreCase)); - - if (watcher != null) - { - 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(); - - var watchers = FileSystemWatchers.ToList(); - - watchers.Remove(watcher); - - FileSystemWatchers = new ConcurrentBag(watchers); - } - - /// - /// Handles the LibraryChanged event of the Kernel - /// - /// The source of the event. - /// The instance containing the event data. - void Instance_LibraryChanged(object sender, ChildrenChangedEventArgs e) - { - if (e.Folder is AggregateFolder && e.HasAddOrRemoveChange) - { - if (e.ItemsRemoved != null) - { - foreach (var item in e.ItemsRemoved.OfType()) - { - StopWatchingPath(item.Path); - } - } - if (e.ItemsAdded != null) - { - foreach (var item in e.ItemsAdded.OfType()) - { - StartWatchingPath(item.Path); - } - } - } - } - - /// - /// Handles the Error event of the watcher control. - /// - /// The source of the event. - /// The instance containing the event data. - async 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); - - if (ex.Message.Contains("network name is no longer available")) - { - //Network either dropped or, we are coming out of sleep and it hasn't reconnected yet - wait and retry - Logger.Warn("Network connection lost - will retry..."); - var retries = 0; - var success = false; - while (!success && retries < 10) - { - await Task.Delay(500).ConfigureAwait(false); - - try - { - dw.EnableRaisingEvents = false; - dw.EnableRaisingEvents = true; - success = true; - } - catch (IOException) - { - Logger.Warn("Network still unavailable..."); - retries++; - } - } - if (!success) - { - Logger.Warn("Unable to access network. Giving up."); - DisposeWatcher(dw); - } - - } - else - { - if (!ex.Message.Contains("BIOS command limit")) - { - Logger.Info("Attempting to re-start watcher."); - - dw.EnableRaisingEvents = false; - dw.EnableRaisingEvents = true; - } - - } - } - - /// - /// 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) - { - if (e.ChangeType == WatcherChangeTypes.Created && e.Name == "New folder") - { - return; - } - if (TempIgnoredPaths.ContainsKey(e.FullPath)) - { - Logger.Info("Watcher requested to ignore change to " + e.FullPath); - return; - } - - Logger.Info("Watcher sees change of type " + e.ChangeType.ToString() + " 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."); - - 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.GetFileData(path); - - if (!data.HasValue || data.Value.IsDirectory) - { - return false; - } - } - catch (IOException) - { - return false; - } - - FileStream stream = null; - - try - { - stream = new FileStream(path, FileMode.Open, FileAccess.ReadWrite, FileShare.ReadWrite); - } - catch - { - //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) - return true; - } - finally - { - if (stream != null) - stream.Close(); - } - - //file is not locked - 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; - } - - await Task.WhenAll(itemsToRefresh.Select(i => Task.Run(async () => - { - Logger.Info(i.Name + " (" + i.Path + ") will be refreshed."); - - try - { - await i.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, i.Name); - } - - }))).ConfigureAwait(false); - } - - /// - /// 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. - /// - private void Stop() - { - LibraryManager.LibraryChanged -= Instance_LibraryChanged; - - FileSystemWatcher watcher; - - while (FileSystemWatchers.TryTake(out watcher)) - { - watcher.Changed -= watcher_Changed; - watcher.EnableRaisingEvents = false; - watcher.Dispose(); - } - - lock (timerLock) - { - if (updateTimer != null) - { - updateTimer.Dispose(); - updateTimer = null; - } - } - - 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.Controller/IO/FileSystemManager.cs b/MediaBrowser.Controller/IO/FileSystemManager.cs deleted file mode 100644 index 463672ece..000000000 --- a/MediaBrowser.Controller/IO/FileSystemManager.cs +++ /dev/null @@ -1,120 +0,0 @@ -using MediaBrowser.Common.IO; -using MediaBrowser.Common.ScheduledTasks; -using MediaBrowser.Controller.Configuration; -using MediaBrowser.Controller.Entities; -using MediaBrowser.Controller.Library; -using MediaBrowser.Model.Logging; -using System; -using System.IO; -using System.Threading; -using System.Threading.Tasks; - -namespace MediaBrowser.Controller.IO -{ - /// - /// This class will manage our file system watching and modifications. Any process that needs to - /// modify the directories that the system is watching for changes should use the methods of - /// this class to do so. This way we can have the watchers correctly respond to only external changes. - /// - public class FileSystemManager : IDisposable - { - /// - /// Gets or sets the directory watchers. - /// - /// The directory watchers. - private DirectoryWatchers DirectoryWatchers { get; set; } - - /// - /// Initializes a new instance of the class. - /// - /// The log manager. - /// The task manager. - /// The library manager. - /// The configuration manager. - public FileSystemManager(ILogManager logManager, ITaskManager taskManager, ILibraryManager libraryManager, IServerConfigurationManager configurationManager) - { - DirectoryWatchers = new DirectoryWatchers(logManager, taskManager, libraryManager, configurationManager); - } - - /// - /// Start the directory watchers on our library folders - /// - public void StartWatchers() - { - DirectoryWatchers.Start(); - } - - /// - /// Saves to library filesystem. - /// - /// The item. - /// The path. - /// The data to save. - /// The cancellation token. - /// Task. - /// - public async Task SaveToLibraryFilesystem(BaseItem item, string path, Stream dataToSave, CancellationToken cancellationToken) - { - if (item == null) - { - throw new ArgumentNullException(); - } - if (string.IsNullOrEmpty(path)) - { - throw new ArgumentNullException(); - } - if (dataToSave == null) - { - throw new ArgumentNullException(); - } - if (cancellationToken == null) - { - throw new ArgumentNullException(); - } - - cancellationToken.ThrowIfCancellationRequested(); - - //Tell the watchers to ignore - DirectoryWatchers.TemporarilyIgnore(path); - - //Make the mod - - dataToSave.Position = 0; - - try - { - using (var fs = new FileStream(path, FileMode.Create, FileAccess.Write, FileShare.Read, StreamDefaults.DefaultFileStreamBufferSize, FileOptions.Asynchronous)) - { - await dataToSave.CopyToAsync(fs, StreamDefaults.DefaultCopyToBufferSize, cancellationToken).ConfigureAwait(false); - - dataToSave.Dispose(); - - // If this is ever used for something other than metadata we can add a file type param - item.ResolveArgs.AddMetadataFile(path); - } - } - finally - { - //Remove the ignore - DirectoryWatchers.RemoveTempIgnore(path); - } - } - - /// - /// 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) - { - DirectoryWatchers.Dispose(); - } - } - - public void Dispose() - { - Dispose(true); - } - } -} diff --git a/MediaBrowser.Controller/IO/IDirectoryWatchers.cs b/MediaBrowser.Controller/IO/IDirectoryWatchers.cs new file mode 100644 index 000000000..9a43ee8ac --- /dev/null +++ b/MediaBrowser.Controller/IO/IDirectoryWatchers.cs @@ -0,0 +1,29 @@ +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/IServerApplicationPaths.cs b/MediaBrowser.Controller/IServerApplicationPaths.cs index c9bac4dd2..f88d7a5f6 100644 --- a/MediaBrowser.Controller/IServerApplicationPaths.cs +++ b/MediaBrowser.Controller/IServerApplicationPaths.cs @@ -74,12 +74,18 @@ namespace MediaBrowser.Controller /// Gets the FF MPEG stream cache path. /// /// The FF MPEG stream cache path. - string FFMpegStreamCachePath { get; } + string EncodedMediaCachePath { get; } /// /// Gets the folder path to tools /// /// The media tools path. string MediaToolsPath { get; } + + /// + /// Gets the downloaded images data path. + /// + /// The downloaded images data path. + string DownloadedImagesDataPath { get; } } } \ No newline at end of file diff --git a/MediaBrowser.Controller/Kernel.cs b/MediaBrowser.Controller/Kernel.cs index 40c59c8cf..d43c505a5 100644 --- a/MediaBrowser.Controller/Kernel.cs +++ b/MediaBrowser.Controller/Kernel.cs @@ -1,22 +1,13 @@ -using MediaBrowser.Common; -using MediaBrowser.Common.ScheduledTasks; -using MediaBrowser.Controller.Configuration; +using MediaBrowser.Controller.Configuration; using MediaBrowser.Controller.Drawing; -using MediaBrowser.Controller.Entities; -using MediaBrowser.Controller.IO; -using MediaBrowser.Controller.Library; using MediaBrowser.Controller.Localization; using MediaBrowser.Controller.MediaInfo; using MediaBrowser.Controller.Persistence; -using MediaBrowser.Controller.Plugins; using MediaBrowser.Controller.Providers; using MediaBrowser.Controller.Weather; -using MediaBrowser.Model.Logging; -using MediaBrowser.Model.Serialization; using System; using System.Collections.Generic; using System.Linq; -using System.Threading; using System.Threading.Tasks; namespace MediaBrowser.Controller @@ -24,7 +15,7 @@ namespace MediaBrowser.Controller /// /// Class Kernel /// - public class Kernel : IDisposable + public class Kernel { /// /// Gets the instance. @@ -36,25 +27,13 @@ namespace MediaBrowser.Controller /// Gets the image manager. /// /// The image manager. - public ImageManager ImageManager { get; private set; } + public ImageManager ImageManager { get; set; } /// /// Gets the FFMPEG controller. /// /// The FFMPEG controller. - public FFMpegManager FFMpegManager { get; private set; } - - /// - /// Gets or sets the file system manager. - /// - /// The file system manager. - public FileSystemManager FileSystemManager { get; private set; } - - /// - /// Gets the provider manager. - /// - /// The provider manager. - public ProviderManager ProviderManager { get; private set; } + public FFMpegManager FFMpegManager { get; set; } /// /// Gets the name of the web application that can be used for url building. @@ -82,74 +61,68 @@ namespace MediaBrowser.Controller /// Gets the list of Localized string files /// /// The string files. - public IEnumerable StringFiles { get; private set; } + public IEnumerable StringFiles { get; set; } /// /// Gets the list of currently registered weather prvoiders /// /// The weather providers. - public IEnumerable WeatherProviders { get; private set; } - - /// - /// Gets the list of currently registered metadata prvoiders - /// - /// The metadata providers enumerable. - public BaseMetadataProvider[] MetadataProviders { get; private set; } + public IEnumerable WeatherProviders { get; set; } /// /// Gets the list of currently registered image processors /// Image processors are specialized metadata providers that run after the normal ones /// /// The image enhancers. - public IEnumerable ImageEnhancers { get; private set; } + public IEnumerable ImageEnhancers { get; set; } /// /// Gets the list of available user repositories /// /// The user repositories. - private IEnumerable UserRepositories { get; set; } + public IEnumerable UserRepositories { get; set; } /// /// Gets the active user repository /// /// The user repository. - public IUserRepository UserRepository { get; private set; } + public IUserRepository UserRepository { get; set; } /// /// Gets the active user repository /// /// The display preferences repository. - public IDisplayPreferencesRepository DisplayPreferencesRepository { get; private set; } + public IDisplayPreferencesRepository DisplayPreferencesRepository { get; set; } /// /// Gets the list of available item repositories /// /// The item repositories. - private IEnumerable ItemRepositories { get; set; } + public IEnumerable ItemRepositories { get; set; } /// /// Gets the active item repository /// /// The item repository. - public IItemRepository ItemRepository { get; private set; } + public IItemRepository ItemRepository { get; set; } /// /// Gets the list of available DisplayPreferencesRepositories /// /// The display preferences repositories. - private IEnumerable DisplayPreferencesRepositories { get; set; } + public IEnumerable DisplayPreferencesRepositories { get; set; } /// /// Gets the list of available item repositories /// /// The user data repositories. - private IEnumerable UserDataRepositories { get; set; } + public IEnumerable UserDataRepositories { get; set; } /// /// Gets the active user data repository /// /// The user data repository. - public IUserDataRepository UserDataRepository { get; private set; } + public IUserDataRepository UserDataRepository { get; set; } /// /// Gets the UDP server port number. @@ -160,121 +133,39 @@ namespace MediaBrowser.Controller get { return 7359; } } - private readonly IXmlSerializer _xmlSerializer; - private readonly IServerConfigurationManager _configurationManager; - private readonly ILogManager _logManager; - private IApplicationHost ApplicationHost { get; set; } /// /// Creates a kernel based on a Data path, which is akin to our current programdata path /// - /// The app host. - /// The XML serializer. - /// The log manager. /// The configuration manager. - /// isoManager - public Kernel(IApplicationHost appHost, IXmlSerializer xmlSerializer, ILogManager logManager, IServerConfigurationManager configurationManager) + public Kernel(IServerConfigurationManager configurationManager) { Instance = this; - ApplicationHost = appHost; _configurationManager = configurationManager; - _xmlSerializer = xmlSerializer; - _logManager = logManager; - - // For now there's no real way to inject these properly - BaseItem.Logger = logManager.GetLogger("BaseItem"); - User.XmlSerializer = _xmlSerializer; - Ratings.ConfigurationManager = _configurationManager; - LocalizedStrings.ApplicationPaths = _configurationManager.ApplicationPaths; - BaseItem.ConfigurationManager = configurationManager; - } - - /// - /// Composes the parts with ioc container. - /// - protected void FindParts() - { - // For now there's no real way to inject these properly - BaseItem.LibraryManager = ApplicationHost.Resolve(); - User.UserManager = ApplicationHost.Resolve(); - - FFMpegManager = (FFMpegManager)ApplicationHost.CreateInstance(typeof(FFMpegManager)); - ImageManager = (ImageManager)ApplicationHost.CreateInstance(typeof(ImageManager)); - ProviderManager = (ProviderManager)ApplicationHost.CreateInstance(typeof(ProviderManager)); - - UserDataRepositories = ApplicationHost.GetExports(); - UserRepositories = ApplicationHost.GetExports(); - DisplayPreferencesRepositories = ApplicationHost.GetExports(); - ItemRepositories = ApplicationHost.GetExports(); - WeatherProviders = ApplicationHost.GetExports(); - ImageEnhancers = ApplicationHost.GetExports().OrderBy(e => e.Priority).ToArray(); - StringFiles = ApplicationHost.GetExports(); - MetadataProviders = ApplicationHost.GetExports().OrderBy(e => e.Priority).ToArray(); - } - - /// - /// Performs initializations that can be reloaded at anytime - /// - /// Task. - public async Task Init() - { - FindParts(); - - await LoadRepositories().ConfigureAwait(false); - - await ApplicationHost.Resolve().RefreshUsersMetadata(CancellationToken.None).ConfigureAwait(false); - - foreach (var entryPoint in ApplicationHost.GetExports()) - { - entryPoint.Run(); - } - - ReloadFileSystemManager(); - } - - /// - /// 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) - { - DisposeFileSystemManager(); - } } /// /// Called when [composable parts loaded]. /// /// Task. - protected Task LoadRepositories() + public Task LoadRepositories(IServerConfigurationManager configurationManager) { // Get the current item repository - ItemRepository = GetRepository(ItemRepositories, _configurationManager.Configuration.ItemRepository); + ItemRepository = GetRepository(ItemRepositories, configurationManager.Configuration.ItemRepository); var itemRepoTask = ItemRepository.Initialize(); // Get the current user repository - UserRepository = GetRepository(UserRepositories, _configurationManager.Configuration.UserRepository); + UserRepository = GetRepository(UserRepositories, configurationManager.Configuration.UserRepository); var userRepoTask = UserRepository.Initialize(); // Get the current item repository - UserDataRepository = GetRepository(UserDataRepositories, _configurationManager.Configuration.UserDataRepository); + UserDataRepository = GetRepository(UserDataRepositories, configurationManager.Configuration.UserDataRepository); var userDataRepoTask = UserDataRepository.Initialize(); // Get the current display preferences repository - DisplayPreferencesRepository = GetRepository(DisplayPreferencesRepositories, _configurationManager.Configuration.DisplayPreferencesRepository); + DisplayPreferencesRepository = GetRepository(DisplayPreferencesRepositories, configurationManager.Configuration.DisplayPreferencesRepository); var displayPreferencesRepoTask = DisplayPreferencesRepository.Initialize(); return Task.WhenAll(itemRepoTask, userRepoTask, userDataRepoTask, displayPreferencesRepoTask); @@ -295,28 +186,5 @@ namespace MediaBrowser.Controller return enumerable.FirstOrDefault(r => string.Equals(r.Name, name, StringComparison.OrdinalIgnoreCase)) ?? enumerable.FirstOrDefault(); } - - /// - /// Disposes the file system manager. - /// - private void DisposeFileSystemManager() - { - if (FileSystemManager != null) - { - FileSystemManager.Dispose(); - FileSystemManager = null; - } - } - - /// - /// Reloads the file system manager. - /// - private void ReloadFileSystemManager() - { - DisposeFileSystemManager(); - - FileSystemManager = new FileSystemManager(_logManager, ApplicationHost.Resolve(), ApplicationHost.Resolve(), _configurationManager); - FileSystemManager.StartWatchers(); - } } } diff --git a/MediaBrowser.Controller/Localization/LocalizedStrings.cs b/MediaBrowser.Controller/Localization/LocalizedStrings.cs index c7f4755b7..ea065b17a 100644 --- a/MediaBrowser.Controller/Localization/LocalizedStrings.cs +++ b/MediaBrowser.Controller/Localization/LocalizedStrings.cs @@ -15,7 +15,7 @@ namespace MediaBrowser.Controller.Localization /// public class LocalizedStrings { - internal static IServerApplicationPaths ApplicationPaths; + public static IServerApplicationPaths ApplicationPaths; /// /// The base prefix diff --git a/MediaBrowser.Controller/Localization/Ratings.cs b/MediaBrowser.Controller/Localization/Ratings.cs index bee7b6e55..a8fbdbc80 100644 --- a/MediaBrowser.Controller/Localization/Ratings.cs +++ b/MediaBrowser.Controller/Localization/Ratings.cs @@ -12,7 +12,7 @@ namespace MediaBrowser.Controller.Localization /// public static class Ratings { - internal static IServerConfigurationManager ConfigurationManager; + public static IServerConfigurationManager ConfigurationManager; /// /// The ratings def diff --git a/MediaBrowser.Controller/MediaBrowser.Controller.csproj b/MediaBrowser.Controller/MediaBrowser.Controller.csproj index c07713d7d..5ae6cccac 100644 --- a/MediaBrowser.Controller/MediaBrowser.Controller.csproj +++ b/MediaBrowser.Controller/MediaBrowser.Controller.csproj @@ -102,12 +102,14 @@ - + + + @@ -154,7 +156,6 @@ - @@ -176,7 +177,6 @@ - diff --git a/MediaBrowser.Controller/Providers/BaseMetadataProvider.cs b/MediaBrowser.Controller/Providers/BaseMetadataProvider.cs index a5823c60e..aaf3fe6bf 100644 --- a/MediaBrowser.Controller/Providers/BaseMetadataProvider.cs +++ b/MediaBrowser.Controller/Providers/BaseMetadataProvider.cs @@ -1,5 +1,4 @@ -using MediaBrowser.Common.Configuration; -using MediaBrowser.Common.Extensions; +using MediaBrowser.Common.Extensions; using MediaBrowser.Controller.Configuration; using MediaBrowser.Controller.Entities; using MediaBrowser.Model.Logging; @@ -12,7 +11,7 @@ namespace MediaBrowser.Controller.Providers /// /// Class BaseMetadataProvider /// - public abstract class BaseMetadataProvider : IDisposable + public abstract class BaseMetadataProvider { /// /// Gets the logger. @@ -27,16 +26,6 @@ namespace MediaBrowser.Controller.Providers /// The configuration manager. protected IServerConfigurationManager ConfigurationManager { get; private set; } - // Cache these since they will be used a lot - /// - /// The false task result - /// - protected static readonly Task FalseTaskResult = Task.FromResult(false); - /// - /// The true task result - /// - protected static readonly Task TrueTaskResult = Task.FromResult(true); - /// /// The _id /// @@ -135,7 +124,7 @@ namespace MediaBrowser.Controller.Providers /// The provider version. /// The status. /// item - protected virtual void SetLastRefreshed(BaseItem item, DateTime value, string providerVersion, ProviderRefreshStatus status = ProviderRefreshStatus.Success) + public virtual void SetLastRefreshed(BaseItem item, DateTime value, string providerVersion, ProviderRefreshStatus status = ProviderRefreshStatus.Success) { if (item == null) { @@ -162,7 +151,7 @@ namespace MediaBrowser.Controller.Providers /// The item. /// The value. /// The status. - protected virtual void SetLastRefreshed(BaseItem item, DateTime value, ProviderRefreshStatus status = ProviderRefreshStatus.Success) + public void SetLastRefreshed(BaseItem item, DateTime value, ProviderRefreshStatus status = ProviderRefreshStatus.Success) { SetLastRefreshed(item, value, ProviderVersion, status); } @@ -254,76 +243,7 @@ namespace MediaBrowser.Controller.Providers /// The cancellation token. /// Task{System.Boolean}. /// - public async Task FetchAsync(BaseItem item, bool force, CancellationToken cancellationToken) - { - if (item == null) - { - throw new ArgumentNullException(); - } - - cancellationToken.ThrowIfCancellationRequested(); - - Logger.Info("Running for {0}", item.Path ?? item.Name ?? "--Unknown--"); - - // This provides the ability to cancel just this one provider - var innerCancellationTokenSource = new CancellationTokenSource(); - - Kernel.Instance.ProviderManager.OnProviderRefreshBeginning(this, item, innerCancellationTokenSource); - - try - { - var task = FetchAsyncInternal(item, force, CancellationTokenSource.CreateLinkedTokenSource(cancellationToken, innerCancellationTokenSource.Token).Token); - - await task.ConfigureAwait(false); - - if (task.IsFaulted) - { - // Log the AggregateException - if (task.Exception != null) - { - Logger.ErrorException("AggregateException:", task.Exception); - } - - return false; - } - - return task.Result; - } - catch (OperationCanceledException ex) - { - Logger.Info("{0} cancelled for {1}", GetType().Name, item.Name); - - // If the outer cancellation token is the one that caused the cancellation, throw it - if (cancellationToken.IsCancellationRequested && ex.CancellationToken == cancellationToken) - { - throw; - } - - return false; - } - catch (Exception ex) - { - Logger.ErrorException("failed refreshing {0}", ex, item.Name); - - SetLastRefreshed(item, DateTime.UtcNow, ProviderRefreshStatus.Failure); - return true; - } - finally - { - innerCancellationTokenSource.Dispose(); - - Kernel.Instance.ProviderManager.OnProviderRefreshCompleted(this, item); - } - } - - /// - /// Fetches metadata and returns true or false indicating if any work that requires persistence was done - /// - /// The item. - /// if set to true [force]. - /// The cancellation token. - /// Task{System.Boolean}. - protected abstract Task FetchAsyncInternal(BaseItem item, bool force, CancellationToken cancellationToken); + public abstract Task FetchAsync(BaseItem item, bool force, CancellationToken cancellationToken); /// /// Gets the priority. @@ -331,23 +251,6 @@ namespace MediaBrowser.Controller.Providers /// The priority. public abstract MetadataProviderPriority Priority { get; } - /// - /// 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) - { - } - /// /// Returns true or false indicating if the provider should refresh when the contents of it's directory changes /// @@ -386,34 +289,4 @@ namespace MediaBrowser.Controller.Providers return item.FileSystemStamp; } } - - /// - /// Determines when a provider should execute, relative to others - /// - public enum MetadataProviderPriority - { - // Run this provider at the beginning - /// - /// The first - /// - First = 1, - - // Run this provider after all first priority providers - /// - /// The second - /// - Second = 2, - - // Run this provider after all second priority providers - /// - /// The third - /// - Third = 3, - - // Run this provider last - /// - /// The last - /// - Last = 4 - } } diff --git a/MediaBrowser.Controller/Providers/FolderProviderFromXml.cs b/MediaBrowser.Controller/Providers/FolderProviderFromXml.cs index 45a47a58d..d2c2d5b15 100644 --- a/MediaBrowser.Controller/Providers/FolderProviderFromXml.cs +++ b/MediaBrowser.Controller/Providers/FolderProviderFromXml.cs @@ -56,7 +56,7 @@ namespace MediaBrowser.Controller.Providers /// if set to true [force]. /// The cancellation token. /// Task{System.Boolean}. - protected override Task FetchAsyncInternal(BaseItem item, bool force, CancellationToken cancellationToken) + public override Task FetchAsync(BaseItem item, bool force, CancellationToken cancellationToken) { return Task.Run(() => Fetch(item, cancellationToken)); } diff --git a/MediaBrowser.Controller/Providers/IProviderManager.cs b/MediaBrowser.Controller/Providers/IProviderManager.cs new file mode 100644 index 000000000..902ed21bc --- /dev/null +++ b/MediaBrowser.Controller/Providers/IProviderManager.cs @@ -0,0 +1,51 @@ +using System.Collections.Generic; +using MediaBrowser.Controller.Entities; +using System; +using System.IO; +using System.Threading; +using System.Threading.Tasks; + +namespace MediaBrowser.Controller.Providers +{ + public interface IProviderManager : IDisposable + { + /// + /// Downloads the and save image. + /// + /// The item. + /// The source. + /// Name of the target. + /// The resource pool. + /// The cancellation token. + /// Task{System.String}. + /// item + Task DownloadAndSaveImage(BaseItem item, string source, string targetName, SemaphoreSlim resourcePool, CancellationToken cancellationToken); + + /// + /// Saves to library filesystem. + /// + /// The item. + /// The path. + /// The data to save. + /// The cancellation token. + /// Task. + /// + Task SaveToLibraryFilesystem(BaseItem item, string path, Stream dataToSave, CancellationToken cancellationToken); + + /// + /// Executes the metadata providers. + /// + /// The item. + /// The cancellation token. + /// if set to true [force]. + /// if set to true [allow slow providers]. + /// Task{System.Boolean}. + Task ExecuteMetadataProviders(BaseItem item, CancellationToken cancellationToken, bool force = false, bool allowSlowProviders = true); + + /// + /// Adds the metadata providers. + /// + /// The providers. + void AddMetadataProviders(IEnumerable providers); + } +} \ No newline at end of file diff --git a/MediaBrowser.Controller/Providers/ImageFromMediaLocationProvider.cs b/MediaBrowser.Controller/Providers/ImageFromMediaLocationProvider.cs index 4b337aadc..70de38743 100644 --- a/MediaBrowser.Controller/Providers/ImageFromMediaLocationProvider.cs +++ b/MediaBrowser.Controller/Providers/ImageFromMediaLocationProvider.cs @@ -52,6 +52,11 @@ namespace MediaBrowser.Controller.Providers } } + /// + /// The true task result + /// + protected static readonly Task TrueTaskResult = Task.FromResult(true); + /// /// Fetches metadata and returns true or false indicating if any work that requires persistence was done /// @@ -59,7 +64,7 @@ namespace MediaBrowser.Controller.Providers /// if set to true [force]. /// The cancellation token. /// Task{System.Boolean}. - protected override Task FetchAsyncInternal(BaseItem item, bool force, CancellationToken cancellationToken) + public override Task FetchAsync(BaseItem item, bool force, CancellationToken cancellationToken) { cancellationToken.ThrowIfCancellationRequested(); diff --git a/MediaBrowser.Controller/Providers/MediaInfo/BaseFFProbeProvider.cs b/MediaBrowser.Controller/Providers/MediaInfo/BaseFFProbeProvider.cs index 9fe2a6c01..253d4dfed 100644 --- a/MediaBrowser.Controller/Providers/MediaInfo/BaseFFProbeProvider.cs +++ b/MediaBrowser.Controller/Providers/MediaInfo/BaseFFProbeProvider.cs @@ -17,7 +17,7 @@ namespace MediaBrowser.Controller.Providers.MediaInfo /// Provides a base class for extracting media information through ffprobe /// /// - public abstract class BaseFFProbeProvider : BaseFFMpegProvider + public abstract class BaseFFProbeProvider : BaseFFMpegProvider, IDisposable where T : BaseItem { protected BaseFFProbeProvider(ILogManager logManager, IServerConfigurationManager configurationManager) : base(logManager, configurationManager) @@ -69,7 +69,7 @@ namespace MediaBrowser.Controller.Providers.MediaInfo /// if set to true [force]. /// The cancellation token. /// Task{System.Boolean}. - protected override async Task FetchAsyncInternal(BaseItem item, bool force, CancellationToken cancellationToken) + public override async Task FetchAsync(BaseItem item, bool force, CancellationToken cancellationToken) { var myItem = (T)item; @@ -351,14 +351,17 @@ namespace MediaBrowser.Controller.Providers.MediaInfo /// Releases unmanaged and - optionally - managed resources. /// /// true to release both managed and unmanaged resources; false to release only unmanaged resources. - protected override void Dispose(bool dispose) + protected virtual void Dispose(bool dispose) { if (dispose) { FFProbeCache.Dispose(); } + } - base.Dispose(dispose); + public void Dispose() + { + Dispose(true); } } } diff --git a/MediaBrowser.Controller/Providers/MediaInfo/FFMpegAudioImageProvider.cs b/MediaBrowser.Controller/Providers/MediaInfo/FFMpegAudioImageProvider.cs index aedd03537..e752a863d 100644 --- a/MediaBrowser.Controller/Providers/MediaInfo/FFMpegAudioImageProvider.cs +++ b/MediaBrowser.Controller/Providers/MediaInfo/FFMpegAudioImageProvider.cs @@ -19,6 +19,11 @@ namespace MediaBrowser.Controller.Providers.MediaInfo { } + /// + /// The true task result + /// + protected static readonly Task TrueTaskResult = Task.FromResult(true); + /// /// Fetches metadata and returns true or false indicating if any work that requires persistence was done /// @@ -26,7 +31,7 @@ namespace MediaBrowser.Controller.Providers.MediaInfo /// if set to true [force]. /// The cancellation token. /// Task{System.Boolean}. - protected override Task FetchAsyncInternal(BaseItem item, bool force, CancellationToken cancellationToken) + public override Task FetchAsync(BaseItem item, bool force, CancellationToken cancellationToken) { var audio = (Audio)item; diff --git a/MediaBrowser.Controller/Providers/MediaInfo/FFMpegVideoImageProvider.cs b/MediaBrowser.Controller/Providers/MediaInfo/FFMpegVideoImageProvider.cs index a44acb8f6..5a8157ebf 100644 --- a/MediaBrowser.Controller/Providers/MediaInfo/FFMpegVideoImageProvider.cs +++ b/MediaBrowser.Controller/Providers/MediaInfo/FFMpegVideoImageProvider.cs @@ -59,6 +59,11 @@ namespace MediaBrowser.Controller.Providers.MediaInfo return false; } + /// + /// The true task result + /// + protected static readonly Task TrueTaskResult = Task.FromResult(true); + /// /// Fetches metadata and returns true or false indicating if any work that requires persistence was done /// @@ -66,7 +71,7 @@ namespace MediaBrowser.Controller.Providers.MediaInfo /// if set to true [force]. /// The cancellation token. /// Task{System.Boolean}. - protected override Task FetchAsyncInternal(BaseItem item, bool force, CancellationToken cancellationToken) + public override Task FetchAsync(BaseItem item, bool force, CancellationToken cancellationToken) { if (string.IsNullOrEmpty(item.PrimaryImagePath)) { diff --git a/MediaBrowser.Controller/Providers/MetadataProviderPriority.cs b/MediaBrowser.Controller/Providers/MetadataProviderPriority.cs new file mode 100644 index 000000000..9ddc0a33f --- /dev/null +++ b/MediaBrowser.Controller/Providers/MetadataProviderPriority.cs @@ -0,0 +1,33 @@ + +namespace MediaBrowser.Controller.Providers +{ + /// + /// Determines when a provider should execute, relative to others + /// + public enum MetadataProviderPriority + { + // Run this provider at the beginning + /// + /// The first + /// + First = 1, + + // Run this provider after all first priority providers + /// + /// The second + /// + Second = 2, + + // Run this provider after all second priority providers + /// + /// The third + /// + Third = 3, + + // Run this provider last + /// + /// The last + /// + Last = 4 + } +} diff --git a/MediaBrowser.Controller/Providers/Movies/FanArtMovieProvider.cs b/MediaBrowser.Controller/Providers/Movies/FanArtMovieProvider.cs index 9c71642e9..af613f023 100644 --- a/MediaBrowser.Controller/Providers/Movies/FanArtMovieProvider.cs +++ b/MediaBrowser.Controller/Providers/Movies/FanArtMovieProvider.cs @@ -17,7 +17,7 @@ namespace MediaBrowser.Controller.Providers.Movies /// /// Class FanArtMovieProvider /// - class FanArtMovieProvider : FanartBaseProvider + class FanArtMovieProvider : FanartBaseProvider, IDisposable { /// /// The fan art @@ -32,6 +32,8 @@ namespace MediaBrowser.Controller.Providers.Movies /// The HTTP client. protected IHttpClient HttpClient { get; private set; } + private readonly IProviderManager _providerManager; + /// /// Initializes a new instance of the class. /// @@ -39,7 +41,7 @@ namespace MediaBrowser.Controller.Providers.Movies /// The log manager. /// The configuration manager. /// httpClient - public FanArtMovieProvider(IHttpClient httpClient, ILogManager logManager, IServerConfigurationManager configurationManager) + public FanArtMovieProvider(IHttpClient httpClient, ILogManager logManager, IServerConfigurationManager configurationManager, IProviderManager providerManager) : base(logManager, configurationManager) { if (httpClient == null) @@ -47,19 +49,19 @@ namespace MediaBrowser.Controller.Providers.Movies throw new ArgumentNullException("httpClient"); } HttpClient = httpClient; + _providerManager = providerManager; } /// /// Releases unmanaged and - optionally - managed resources. /// /// true to release both managed and unmanaged resources; false to release only unmanaged resources. - protected override void Dispose(bool dispose) + protected virtual void Dispose(bool dispose) { if (dispose) { FanArtResourcePool.Dispose(); } - base.Dispose(dispose); } /// @@ -103,7 +105,7 @@ namespace MediaBrowser.Controller.Providers.Movies /// if set to true [force]. /// The cancellation token. /// Task{System.Boolean}. - protected override async Task FetchAsyncInternal(BaseItem item, bool force, CancellationToken cancellationToken) + public override async Task FetchAsync(BaseItem item, bool force, CancellationToken cancellationToken) { cancellationToken.ThrowIfCancellationRequested(); @@ -147,7 +149,7 @@ namespace MediaBrowser.Controller.Providers.Movies Logger.Debug("FanArtProvider getting ClearLogo for " + movie.Name); try { - movie.SetImage(ImageType.Logo, await Kernel.Instance.ProviderManager.DownloadAndSaveImage(movie, path, LOGO_FILE, FanArtResourcePool, cancellationToken).ConfigureAwait(false)); + movie.SetImage(ImageType.Logo, await _providerManager.DownloadAndSaveImage(movie, path, LOGO_FILE, FanArtResourcePool, cancellationToken).ConfigureAwait(false)); } catch (HttpException) { @@ -173,7 +175,7 @@ namespace MediaBrowser.Controller.Providers.Movies Logger.Debug("FanArtProvider getting ClearArt for " + movie.Name); try { - movie.SetImage(ImageType.Art, await Kernel.Instance.ProviderManager.DownloadAndSaveImage(movie, path, ART_FILE, FanArtResourcePool, cancellationToken).ConfigureAwait(false)); + movie.SetImage(ImageType.Art, await _providerManager.DownloadAndSaveImage(movie, path, ART_FILE, FanArtResourcePool, cancellationToken).ConfigureAwait(false)); } catch (HttpException) { @@ -196,7 +198,7 @@ namespace MediaBrowser.Controller.Providers.Movies Logger.Debug("FanArtProvider getting DiscArt for " + movie.Name); try { - movie.SetImage(ImageType.Disc, await Kernel.Instance.ProviderManager.DownloadAndSaveImage(movie, path, DISC_FILE, FanArtResourcePool, cancellationToken).ConfigureAwait(false)); + movie.SetImage(ImageType.Disc, await _providerManager.DownloadAndSaveImage(movie, path, DISC_FILE, FanArtResourcePool, cancellationToken).ConfigureAwait(false)); } catch (HttpException) { @@ -220,7 +222,7 @@ namespace MediaBrowser.Controller.Providers.Movies Logger.Debug("FanArtProvider getting Banner for " + movie.Name); try { - movie.SetImage(ImageType.Banner, await Kernel.Instance.ProviderManager.DownloadAndSaveImage(movie, path, BANNER_FILE, FanArtResourcePool, cancellationToken).ConfigureAwait(false)); + movie.SetImage(ImageType.Banner, await _providerManager.DownloadAndSaveImage(movie, path, BANNER_FILE, FanArtResourcePool, cancellationToken).ConfigureAwait(false)); } catch (HttpException) { @@ -244,7 +246,7 @@ namespace MediaBrowser.Controller.Providers.Movies Logger.Debug("FanArtProvider getting Banner for " + movie.Name); try { - movie.SetImage(ImageType.Thumb, await Kernel.Instance.ProviderManager.DownloadAndSaveImage(movie, path, THUMB_FILE, FanArtResourcePool, cancellationToken).ConfigureAwait(false)); + movie.SetImage(ImageType.Thumb, await _providerManager.DownloadAndSaveImage(movie, path, THUMB_FILE, FanArtResourcePool, cancellationToken).ConfigureAwait(false)); } catch (HttpException) { @@ -260,5 +262,10 @@ namespace MediaBrowser.Controller.Providers.Movies SetLastRefreshed(movie, DateTime.UtcNow); return true; } + + public void Dispose() + { + Dispose(true); + } } } diff --git a/MediaBrowser.Controller/Providers/Movies/MovieDbProvider.cs b/MediaBrowser.Controller/Providers/Movies/MovieDbProvider.cs index 8d004a574..3eea0979c 100644 --- a/MediaBrowser.Controller/Providers/Movies/MovieDbProvider.cs +++ b/MediaBrowser.Controller/Providers/Movies/MovieDbProvider.cs @@ -30,8 +30,10 @@ namespace MediaBrowser.Controller.Providers.Movies /// /// Class MovieDbProvider /// - public class MovieDbProvider : BaseMetadataProvider + public class MovieDbProvider : BaseMetadataProvider, IDisposable { + protected readonly IProviderManager ProviderManager; + /// /// The movie db /// @@ -58,11 +60,12 @@ namespace MediaBrowser.Controller.Providers.Movies /// The configuration manager. /// The json serializer. /// The HTTP client. - public MovieDbProvider(ILogManager logManager, IServerConfigurationManager configurationManager, IJsonSerializer jsonSerializer, IHttpClient httpClient) + public MovieDbProvider(ILogManager logManager, IServerConfigurationManager configurationManager, IJsonSerializer jsonSerializer, IHttpClient httpClient, IProviderManager providerManager) : base(logManager, configurationManager) { JsonSerializer = jsonSerializer; HttpClient = httpClient; + ProviderManager = providerManager; Current = this; } @@ -70,13 +73,12 @@ namespace MediaBrowser.Controller.Providers.Movies /// Releases unmanaged and - optionally - managed resources. /// /// true to release both managed and unmanaged resources; false to release only unmanaged resources. - protected override void Dispose(bool dispose) + protected virtual void Dispose(bool dispose) { if (dispose) { MovieDbResourcePool.Dispose(); } - base.Dispose(dispose); } /// @@ -209,16 +211,17 @@ namespace MediaBrowser.Controller.Providers.Movies /// /// The item. /// The value. + /// The provider version. /// The status. - protected override void SetLastRefreshed(BaseItem item, DateTime value, ProviderRefreshStatus status = ProviderRefreshStatus.Success) + public override void SetLastRefreshed(BaseItem item, DateTime value, string providerVersion, ProviderRefreshStatus status = ProviderRefreshStatus.Success) { - base.SetLastRefreshed(item, value, status); + base.SetLastRefreshed(item, value, providerVersion, status); if (ConfigurationManager.Configuration.SaveLocalMeta) { //in addition to ours, we need to set the last refreshed time for the local data provider //so it won't see the new files we download and process them all over again - if (JsonProvider == null) JsonProvider = new MovieProviderFromJson(LogManager, ConfigurationManager, JsonSerializer, HttpClient); + if (JsonProvider == null) JsonProvider = new MovieProviderFromJson(LogManager, ConfigurationManager, JsonSerializer, HttpClient, ProviderManager); var data = item.ProviderData.GetValueOrDefault(JsonProvider.Id, new BaseProviderInfo { ProviderId = JsonProvider.Id }); data.LastRefreshed = value; item.ProviderData[JsonProvider.Id] = data; @@ -291,7 +294,7 @@ namespace MediaBrowser.Controller.Providers.Movies /// if set to true [force]. /// The cancellation token /// Task{System.Boolean}. - protected override async Task FetchAsyncInternal(BaseItem item, bool force, CancellationToken cancellationToken) + public override async Task FetchAsync(BaseItem item, bool force, CancellationToken cancellationToken) { if (HasAltMeta(item)) { @@ -724,7 +727,7 @@ namespace MediaBrowser.Controller.Providers.Movies cancellationToken.ThrowIfCancellationRequested(); - await Kernel.Instance.FileSystemManager.SaveToLibraryFilesystem(item, Path.Combine(item.MetaLocation, LOCAL_META_FILE_NAME), ms, cancellationToken).ConfigureAwait(false); + await ProviderManager.SaveToLibraryFilesystem(item, Path.Combine(item.MetaLocation, LOCAL_META_FILE_NAME), ms, cancellationToken).ConfigureAwait(false); } } @@ -1018,7 +1021,7 @@ namespace MediaBrowser.Controller.Providers.Movies { try { - item.PrimaryImagePath = await Kernel.Instance.ProviderManager.DownloadAndSaveImage(item, tmdbImageUrl + poster.file_path, "folder" + Path.GetExtension(poster.file_path), MovieDbResourcePool, cancellationToken).ConfigureAwait(false); + item.PrimaryImagePath = await ProviderManager.DownloadAndSaveImage(item, tmdbImageUrl + poster.file_path, "folder" + Path.GetExtension(poster.file_path), MovieDbResourcePool, cancellationToken).ConfigureAwait(false); } catch (HttpException) { @@ -1050,7 +1053,7 @@ namespace MediaBrowser.Controller.Providers.Movies { try { - item.BackdropImagePaths.Add(await Kernel.Instance.ProviderManager.DownloadAndSaveImage(item, tmdbImageUrl + images.backdrops[i].file_path, bdName + Path.GetExtension(images.backdrops[i].file_path), MovieDbResourcePool, cancellationToken).ConfigureAwait(false)); + item.BackdropImagePaths.Add(await ProviderManager.DownloadAndSaveImage(item, tmdbImageUrl + images.backdrops[i].file_path, bdName + Path.GetExtension(images.backdrops[i].file_path), MovieDbResourcePool, cancellationToken).ConfigureAwait(false)); } catch (HttpException) { @@ -1661,5 +1664,10 @@ namespace MediaBrowser.Controller.Providers.Movies public TmdbImageSettings images { get; set; } } #endregion + + public void Dispose() + { + Dispose(true); + } } } diff --git a/MediaBrowser.Controller/Providers/Movies/MovieProviderFromJson.cs b/MediaBrowser.Controller/Providers/Movies/MovieProviderFromJson.cs index 2a7a09625..b81e0c51d 100644 --- a/MediaBrowser.Controller/Providers/Movies/MovieProviderFromJson.cs +++ b/MediaBrowser.Controller/Providers/Movies/MovieProviderFromJson.cs @@ -15,8 +15,8 @@ namespace MediaBrowser.Controller.Providers.Movies /// public class MovieProviderFromJson : MovieDbProvider { - public MovieProviderFromJson(ILogManager logManager, IServerConfigurationManager configurationManager, IJsonSerializer jsonSerializer, IHttpClient httpClient) : - base(logManager, configurationManager, jsonSerializer, httpClient) + public MovieProviderFromJson(ILogManager logManager, IServerConfigurationManager configurationManager, IJsonSerializer jsonSerializer, IHttpClient httpClient, IProviderManager providerManager) + : base(logManager, configurationManager, jsonSerializer, httpClient, providerManager) { } @@ -79,7 +79,7 @@ namespace MediaBrowser.Controller.Providers.Movies /// if set to true [force]. /// The cancellation token. /// Task{System.Boolean}. - protected override Task FetchAsyncInternal(BaseItem item, bool force, CancellationToken cancellationToken) + public override Task FetchAsync(BaseItem item, bool force, CancellationToken cancellationToken) { // Since we don't have anything truly async, and since deserializing can be expensive, create a task to force parallelism return Task.Run(() => diff --git a/MediaBrowser.Controller/Providers/Movies/MovieProviderFromXml.cs b/MediaBrowser.Controller/Providers/Movies/MovieProviderFromXml.cs index 3e5a6fe8d..279533636 100644 --- a/MediaBrowser.Controller/Providers/Movies/MovieProviderFromXml.cs +++ b/MediaBrowser.Controller/Providers/Movies/MovieProviderFromXml.cs @@ -56,7 +56,7 @@ namespace MediaBrowser.Controller.Providers.Movies /// if set to true [force]. /// The cancellation token. /// Task{System.Boolean}. - protected override Task FetchAsyncInternal(BaseItem item, bool force, CancellationToken cancellationToken) + public override Task FetchAsync(BaseItem item, bool force, CancellationToken cancellationToken) { return Task.Run(() => Fetch(item, cancellationToken)); } diff --git a/MediaBrowser.Controller/Providers/Movies/PersonProviderFromJson.cs b/MediaBrowser.Controller/Providers/Movies/PersonProviderFromJson.cs index 278d67261..e89f1a044 100644 --- a/MediaBrowser.Controller/Providers/Movies/PersonProviderFromJson.cs +++ b/MediaBrowser.Controller/Providers/Movies/PersonProviderFromJson.cs @@ -15,8 +15,8 @@ namespace MediaBrowser.Controller.Providers.Movies /// class PersonProviderFromJson : TmdbPersonProvider { - public PersonProviderFromJson(IHttpClient httpClient, IJsonSerializer jsonSerializer, ILogManager logManager, IServerConfigurationManager configurationManager) : - base(httpClient, jsonSerializer, logManager, configurationManager) + public PersonProviderFromJson(IHttpClient httpClient, IJsonSerializer jsonSerializer, ILogManager logManager, IServerConfigurationManager configurationManager, IProviderManager providerManager) + : base(httpClient, jsonSerializer, logManager, configurationManager, providerManager) { } @@ -90,7 +90,7 @@ namespace MediaBrowser.Controller.Providers.Movies /// if set to true [force]. /// The cancellation token. /// Task{System.Boolean}. - protected override Task FetchAsyncInternal(BaseItem item, bool force, CancellationToken cancellationToken) + public override Task FetchAsync(BaseItem item, bool force, CancellationToken cancellationToken) { return Task.Run(() => { diff --git a/MediaBrowser.Controller/Providers/Movies/TmdbPersonProvider.cs b/MediaBrowser.Controller/Providers/Movies/TmdbPersonProvider.cs index 5c977a928..f721a04eb 100644 --- a/MediaBrowser.Controller/Providers/Movies/TmdbPersonProvider.cs +++ b/MediaBrowser.Controller/Providers/Movies/TmdbPersonProvider.cs @@ -26,7 +26,9 @@ namespace MediaBrowser.Controller.Providers.Movies /// protected const string MetaFileName = "MBPerson.json"; - public TmdbPersonProvider(IHttpClient httpClient, IJsonSerializer jsonSerializer, ILogManager logManager, IServerConfigurationManager configurationManager) + protected readonly IProviderManager ProviderManager; + + public TmdbPersonProvider(IHttpClient httpClient, IJsonSerializer jsonSerializer, ILogManager logManager, IServerConfigurationManager configurationManager, IProviderManager providerManager) : base(logManager, configurationManager) { if (jsonSerializer == null) @@ -39,6 +41,7 @@ namespace MediaBrowser.Controller.Providers.Movies } HttpClient = httpClient; JsonSerializer = jsonSerializer; + ProviderManager = providerManager; } /// @@ -83,7 +86,7 @@ namespace MediaBrowser.Controller.Providers.Movies /// if set to true [force]. /// The cancellation token. /// Task{System.Boolean}. - protected override async Task FetchAsyncInternal(BaseItem item, bool force, CancellationToken cancellationToken) + public override async Task FetchAsync(BaseItem item, bool force, CancellationToken cancellationToken) { cancellationToken.ThrowIfCancellationRequested(); @@ -209,7 +212,7 @@ namespace MediaBrowser.Controller.Providers.Movies JsonSerializer.SerializeToStream(searchResult, memoryStream); - await Kernel.Instance.FileSystemManager.SaveToLibraryFilesystem(person, Path.Combine(person.MetaLocation, MetaFileName), memoryStream, cancellationToken); + await ProviderManager.SaveToLibraryFilesystem(person, Path.Combine(person.MetaLocation, MetaFileName), memoryStream, cancellationToken); Logger.Debug("TmdbPersonProvider downloaded and saved information for {0}", person.Name); } @@ -288,7 +291,7 @@ namespace MediaBrowser.Controller.Providers.Movies } if (profile != null) { - var tmdbSettings = await Kernel.Instance.MetadataProviders.OfType().First().TmdbSettings.ConfigureAwait(false); + var tmdbSettings = await MovieDbProvider.Current.TmdbSettings.ConfigureAwait(false); var img = await DownloadAndSaveImage(person, tmdbSettings.images.base_url + ConfigurationManager.Configuration.TmdbFetchedProfileSize + profile.File_Path, "folder" + Path.GetExtension(profile.File_Path), cancellationToken).ConfigureAwait(false); @@ -319,7 +322,7 @@ namespace MediaBrowser.Controller.Providers.Movies { using (var sourceStream = await HttpClient.GetMemoryStream(source, MovieDbProvider.Current.MovieDbResourcePool, cancellationToken).ConfigureAwait(false)) { - await Kernel.Instance.FileSystemManager.SaveToLibraryFilesystem(item, localPath, sourceStream, cancellationToken).ConfigureAwait(false); + await ProviderManager.SaveToLibraryFilesystem(item, localPath, sourceStream, cancellationToken).ConfigureAwait(false); Logger.Debug("TmdbPersonProvider downloaded and saved image for {0}", item.Name); } diff --git a/MediaBrowser.Controller/Providers/Music/FanArtAlbumProvider.cs b/MediaBrowser.Controller/Providers/Music/FanArtAlbumProvider.cs index 2d7284c80..331bd00da 100644 --- a/MediaBrowser.Controller/Providers/Music/FanArtAlbumProvider.cs +++ b/MediaBrowser.Controller/Providers/Music/FanArtAlbumProvider.cs @@ -15,8 +15,12 @@ namespace MediaBrowser.Controller.Providers.Music { public class FanArtAlbumProvider : FanartBaseProvider { - public FanArtAlbumProvider(ILogManager logManager, IServerConfigurationManager configurationManager) : base(logManager, configurationManager) + private readonly IProviderManager _providerManager; + + public FanArtAlbumProvider(ILogManager logManager, IServerConfigurationManager configurationManager, IProviderManager providerManager) + : base(logManager, configurationManager) { + _providerManager = providerManager; } public override bool Supports(BaseItem item) @@ -37,7 +41,7 @@ namespace MediaBrowser.Controller.Providers.Music DateTime.Today.Subtract(providerInfo.LastRefreshed).TotalDays > ConfigurationManager.Configuration.MetadataRefreshDays; } - protected override async Task FetchAsyncInternal(BaseItem item, bool force, CancellationToken cancellationToken) + public override async Task FetchAsync(BaseItem item, bool force, CancellationToken cancellationToken) { var mbid = item.GetProviderId(MetadataProviders.Musicbrainz); if (mbid == null) @@ -67,7 +71,7 @@ namespace MediaBrowser.Controller.Providers.Music return false; } - item.SetImage(ImageType.Primary, await Kernel.Instance.ProviderManager.DownloadAndSaveImage(item, cover, "folder.jpg", FanArtResourcePool, cancellationToken).ConfigureAwait(false)); + item.SetImage(ImageType.Primary, await _providerManager.DownloadAndSaveImage(item, cover, "folder.jpg", FanArtResourcePool, cancellationToken).ConfigureAwait(false)); return true; } } diff --git a/MediaBrowser.Controller/Providers/Music/FanArtArtistProvider.cs b/MediaBrowser.Controller/Providers/Music/FanArtArtistProvider.cs index 3834bff91..4846c8fdc 100644 --- a/MediaBrowser.Controller/Providers/Music/FanArtArtistProvider.cs +++ b/MediaBrowser.Controller/Providers/Music/FanArtArtistProvider.cs @@ -28,7 +28,9 @@ namespace MediaBrowser.Controller.Providers.Music /// The HTTP client. protected IHttpClient HttpClient { get; private set; } - public FanArtArtistProvider(IHttpClient httpClient, ILogManager logManager, IServerConfigurationManager configurationManager) + private readonly IProviderManager _providerManager; + + public FanArtArtistProvider(IHttpClient httpClient, ILogManager logManager, IServerConfigurationManager configurationManager, IProviderManager providerManager) : base(logManager, configurationManager) { if (httpClient == null) @@ -36,6 +38,7 @@ namespace MediaBrowser.Controller.Providers.Music throw new ArgumentNullException("httpClient"); } HttpClient = httpClient; + _providerManager = providerManager; } /// @@ -80,7 +83,7 @@ namespace MediaBrowser.Controller.Providers.Music /// if set to true [force]. /// The cancellation token. /// Task{System.Boolean}. - protected override async Task FetchAsyncInternal(BaseItem item, bool force, CancellationToken cancellationToken) + public override async Task FetchAsync(BaseItem item, bool force, CancellationToken cancellationToken) { cancellationToken.ThrowIfCancellationRequested(); @@ -118,7 +121,7 @@ namespace MediaBrowser.Controller.Providers.Music Logger.Debug("FanArtProvider getting ClearLogo for " + artist.Name); try { - artist.SetImage(ImageType.Logo, await Kernel.Instance.ProviderManager.DownloadAndSaveImage(artist, path, LOGO_FILE, FanArtResourcePool, cancellationToken).ConfigureAwait(false)); + artist.SetImage(ImageType.Logo, await _providerManager.DownloadAndSaveImage(artist, path, LOGO_FILE, FanArtResourcePool, cancellationToken).ConfigureAwait(false)); } catch (HttpException) { @@ -146,7 +149,7 @@ namespace MediaBrowser.Controller.Providers.Music Logger.Debug("FanArtProvider getting Backdrop for " + artist.Name); try { - artist.BackdropImagePaths.Add(await Kernel.Instance.ProviderManager.DownloadAndSaveImage(artist, path, ("Backdrop"+(numBackdrops > 0 ? numBackdrops.ToString() : "")+".jpg"), FanArtResourcePool, cancellationToken).ConfigureAwait(false)); + artist.BackdropImagePaths.Add(await _providerManager.DownloadAndSaveImage(artist, path, ("Backdrop" + (numBackdrops > 0 ? numBackdrops.ToString() : "") + ".jpg"), FanArtResourcePool, cancellationToken).ConfigureAwait(false)); numBackdrops++; if (numBackdrops >= ConfigurationManager.Configuration.MaxBackdrops) break; } @@ -203,7 +206,7 @@ namespace MediaBrowser.Controller.Providers.Music Logger.Debug("FanArtProvider getting ClearArt for " + artist.Name); try { - artist.SetImage(ImageType.Art, await Kernel.Instance.ProviderManager.DownloadAndSaveImage(artist, path, ART_FILE, FanArtResourcePool, cancellationToken).ConfigureAwait(false)); + artist.SetImage(ImageType.Art, await _providerManager.DownloadAndSaveImage(artist, path, ART_FILE, FanArtResourcePool, cancellationToken).ConfigureAwait(false)); } catch (HttpException) { @@ -226,7 +229,7 @@ namespace MediaBrowser.Controller.Providers.Music Logger.Debug("FanArtProvider getting Banner for " + artist.Name); try { - artist.SetImage(ImageType.Banner, await Kernel.Instance.ProviderManager.DownloadAndSaveImage(artist, path, BANNER_FILE, FanArtResourcePool, cancellationToken).ConfigureAwait(false)); + artist.SetImage(ImageType.Banner, await _providerManager.DownloadAndSaveImage(artist, path, BANNER_FILE, FanArtResourcePool, cancellationToken).ConfigureAwait(false)); } catch (HttpException) { @@ -250,7 +253,7 @@ namespace MediaBrowser.Controller.Providers.Music Logger.Debug("FanArtProvider getting Primary image for " + artist.Name); try { - artist.SetImage(ImageType.Primary, await Kernel.Instance.ProviderManager.DownloadAndSaveImage(artist, path, PRIMARY_FILE, FanArtResourcePool, cancellationToken).ConfigureAwait(false)); + artist.SetImage(ImageType.Primary, await _providerManager.DownloadAndSaveImage(artist, path, PRIMARY_FILE, FanArtResourcePool, cancellationToken).ConfigureAwait(false)); } catch (HttpException) { diff --git a/MediaBrowser.Controller/Providers/Music/LastfmAlbumProvider.cs b/MediaBrowser.Controller/Providers/Music/LastfmAlbumProvider.cs index 70de42f4d..7f2952c66 100644 --- a/MediaBrowser.Controller/Providers/Music/LastfmAlbumProvider.cs +++ b/MediaBrowser.Controller/Providers/Music/LastfmAlbumProvider.cs @@ -16,9 +16,12 @@ namespace MediaBrowser.Controller.Providers.Music { private static readonly Task BlankId = Task.FromResult("0000"); - public LastfmAlbumProvider(IJsonSerializer jsonSerializer, IHttpClient httpClient, ILogManager logManager, IServerConfigurationManager configurationManager) + private readonly IProviderManager _providerManager; + + public LastfmAlbumProvider(IJsonSerializer jsonSerializer, IHttpClient httpClient, ILogManager logManager, IServerConfigurationManager configurationManager, IProviderManager providerManager) : base(jsonSerializer, httpClient, logManager, configurationManager) { + _providerManager = providerManager; LocalMetaFileName = LastfmHelper.LocalAlbumMetaFileName; } @@ -62,7 +65,7 @@ namespace MediaBrowser.Controller.Providers.Music cancellationToken.ThrowIfCancellationRequested(); - await Kernel.Instance.FileSystemManager.SaveToLibraryFilesystem(item, Path.Combine(item.MetaLocation, LocalMetaFileName), ms, cancellationToken).ConfigureAwait(false); + await _providerManager.SaveToLibraryFilesystem(item, Path.Combine(item.MetaLocation, LocalMetaFileName), ms, cancellationToken).ConfigureAwait(false); } } diff --git a/MediaBrowser.Controller/Providers/Music/LastfmArtistProvider.cs b/MediaBrowser.Controller/Providers/Music/LastfmArtistProvider.cs index 29cb35cdc..016650071 100644 --- a/MediaBrowser.Controller/Providers/Music/LastfmArtistProvider.cs +++ b/MediaBrowser.Controller/Providers/Music/LastfmArtistProvider.cs @@ -19,10 +19,12 @@ namespace MediaBrowser.Controller.Providers.Music { public class LastfmArtistProvider : LastfmBaseProvider { - - public LastfmArtistProvider(IJsonSerializer jsonSerializer, IHttpClient httpClient, ILogManager logManager, IServerConfigurationManager configurationManager) + private readonly IProviderManager _providerManager; + + public LastfmArtistProvider(IJsonSerializer jsonSerializer, IHttpClient httpClient, ILogManager logManager, IServerConfigurationManager configurationManager, IProviderManager providerManager) : base(jsonSerializer, httpClient, logManager, configurationManager) { + _providerManager = providerManager; LocalMetaFileName = LastfmHelper.LocalArtistMetaFileName; } @@ -91,7 +93,7 @@ namespace MediaBrowser.Controller.Providers.Music cancellationToken.ThrowIfCancellationRequested(); - await Kernel.Instance.FileSystemManager.SaveToLibraryFilesystem(item, Path.Combine(item.MetaLocation, LocalMetaFileName), ms, cancellationToken).ConfigureAwait(false); + await _providerManager.SaveToLibraryFilesystem(item, Path.Combine(item.MetaLocation, LocalMetaFileName), ms, cancellationToken).ConfigureAwait(false); } } diff --git a/MediaBrowser.Controller/Providers/Music/LastfmBaseProvider.cs b/MediaBrowser.Controller/Providers/Music/LastfmBaseProvider.cs index d8b4c387f..26817a41c 100644 --- a/MediaBrowser.Controller/Providers/Music/LastfmBaseProvider.cs +++ b/MediaBrowser.Controller/Providers/Music/LastfmBaseProvider.cs @@ -196,7 +196,7 @@ namespace MediaBrowser.Controller.Providers.Music /// if set to true [force]. /// The cancellation token /// Task{System.Boolean}. - protected override async Task FetchAsyncInternal(BaseItem item, bool force, CancellationToken cancellationToken) + public override async Task FetchAsync(BaseItem item, bool force, CancellationToken cancellationToken) { if (item.DontFetchMeta) { diff --git a/MediaBrowser.Controller/Providers/Music/MusicArtistProviderFromJson.cs b/MediaBrowser.Controller/Providers/Music/MusicArtistProviderFromJson.cs index fab468492..8b9b07bce 100644 --- a/MediaBrowser.Controller/Providers/Music/MusicArtistProviderFromJson.cs +++ b/MediaBrowser.Controller/Providers/Music/MusicArtistProviderFromJson.cs @@ -34,7 +34,7 @@ namespace MediaBrowser.Controller.Providers.Music } - protected override Task FetchAsyncInternal(BaseItem item, bool force, CancellationToken cancellationToken) + public override Task FetchAsync(BaseItem item, bool force, CancellationToken cancellationToken) { return Task.Run(() => { diff --git a/MediaBrowser.Controller/Providers/ProviderManager.cs b/MediaBrowser.Controller/Providers/ProviderManager.cs deleted file mode 100644 index 0df5f64d3..000000000 --- a/MediaBrowser.Controller/Providers/ProviderManager.cs +++ /dev/null @@ -1,372 +0,0 @@ -using MediaBrowser.Common.Configuration; -using MediaBrowser.Common.Extensions; -using MediaBrowser.Common.IO; -using MediaBrowser.Common.Net; -using MediaBrowser.Controller.Configuration; -using MediaBrowser.Controller.Entities; -using MediaBrowser.Model.Logging; -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.Controller.Providers -{ - /// - /// Class ProviderManager - /// - public class ProviderManager : IDisposable - { - /// - /// The remote image cache - /// - private readonly FileSystemRepository _remoteImageCache; - - /// - /// The currently running metadata providers - /// - private readonly ConcurrentDictionary> _currentlyRunningProviders = - new ConcurrentDictionary>(); - - /// - /// The _logger - /// - private readonly ILogger _logger; - - /// - /// The _HTTP client - /// - private readonly IHttpClient _httpClient; - - private IServerConfigurationManager ConfigurationManager { get; set; } - - private Kernel Kernel { get; set; } - - /// - /// Initializes a new instance of the class. - /// - /// The kernel. - /// The HTTP client. - /// The logger. - public ProviderManager(Kernel kernel, IHttpClient httpClient, ILogger logger, IServerConfigurationManager configurationManager) - { - _logger = logger; - Kernel = kernel; - _httpClient = httpClient; - ConfigurationManager = configurationManager; - _remoteImageCache = new FileSystemRepository(ImagesDataPath); - - configurationManager.ConfigurationUpdated += configurationManager_ConfigurationUpdated; - } - - /// - /// Handles the ConfigurationUpdated event of the configurationManager control. - /// - /// The source of the event. - /// The instance containing the event data. - void configurationManager_ConfigurationUpdated(object sender, EventArgs e) - { - // Validate currently executing providers, in the background - Task.Run(() => - { - ValidateCurrentlyRunningProviders(); - }); - } - - /// - /// The _images data path - /// - private string _imagesDataPath; - /// - /// Gets the images data path. - /// - /// The images data path. - public string ImagesDataPath - { - get - { - if (_imagesDataPath == null) - { - _imagesDataPath = Path.Combine(ConfigurationManager.ApplicationPaths.DataPath, "remote-images"); - - if (!Directory.Exists(_imagesDataPath)) - { - Directory.CreateDirectory(_imagesDataPath); - } - } - - return _imagesDataPath; - } - } - - /// - /// Gets or sets the supported providers key. - /// - /// The supported providers key. - private Guid SupportedProvidersKey { get; set; } - - /// - /// Runs all metadata providers for an entity, and returns true or false indicating if at least one was refreshed and requires persistence - /// - /// The item. - /// The cancellation token. - /// if set to true [force]. - /// if set to true [allow slow providers]. - /// Task{System.Boolean}. - internal async Task ExecuteMetadataProviders(BaseItem item, CancellationToken cancellationToken, bool force = false, bool allowSlowProviders = true) - { - // Allow providers of the same priority to execute in parallel - MetadataProviderPriority? currentPriority = null; - var currentTasks = new List>(); - - var result = false; - - cancellationToken.ThrowIfCancellationRequested(); - - // Determine if supported providers have changed - var supportedProviders = Kernel.MetadataProviders.Where(p => p.Supports(item)).ToList(); - - BaseProviderInfo supportedProvidersInfo; - - if (SupportedProvidersKey == Guid.Empty) - { - SupportedProvidersKey = "SupportedProviders".GetMD5(); - } - - var supportedProvidersHash = string.Join("+", supportedProviders.Select(i => i.GetType().Name)).GetMD5(); - bool providersChanged; - - item.ProviderData.TryGetValue(SupportedProvidersKey, out supportedProvidersInfo); - if (supportedProvidersInfo == null) - { - // First time - supportedProvidersInfo = new BaseProviderInfo { ProviderId = SupportedProvidersKey, FileSystemStamp = supportedProvidersHash }; - providersChanged = force = true; - } - else - { - // Force refresh if the supported providers have changed - providersChanged = force = force || supportedProvidersInfo.FileSystemStamp != supportedProvidersHash; - } - - // If providers have changed, clear provider info and update the supported providers hash - if (providersChanged) - { - _logger.Debug("Providers changed for {0}. Clearing and forcing refresh.", item.Name); - item.ProviderData.Clear(); - supportedProvidersInfo.FileSystemStamp = supportedProvidersHash; - } - - if (force) item.ClearMetaValues(); - - // Run the normal providers sequentially in order of priority - foreach (var provider in supportedProviders) - { - cancellationToken.ThrowIfCancellationRequested(); - - // Skip if internet providers are currently disabled - if (provider.RequiresInternet && !ConfigurationManager.Configuration.EnableInternetProviders) - { - continue; - } - - // Skip if is slow and we aren't allowing slow ones - if (provider.IsSlow && !allowSlowProviders) - { - continue; - } - - // Skip if internet provider and this type is not allowed - if (provider.RequiresInternet && ConfigurationManager.Configuration.EnableInternetProviders && ConfigurationManager.Configuration.InternetProviderExcludeTypes.Contains(item.GetType().Name, StringComparer.OrdinalIgnoreCase)) - { - continue; - } - - // When a new priority is reached, await the ones that are currently running and clear the list - if (currentPriority.HasValue && currentPriority.Value != provider.Priority && currentTasks.Count > 0) - { - var results = await Task.WhenAll(currentTasks).ConfigureAwait(false); - result |= results.Contains(true); - - currentTasks.Clear(); - } - - // Put this check below the await because the needs refresh of the next tier of providers may depend on the previous ones running - // This is the case for the fan art provider which depends on the movie and tv providers having run before them - if (!force && !provider.NeedsRefresh(item)) - { - continue; - } - - currentTasks.Add(provider.FetchAsync(item, force, cancellationToken)); - currentPriority = provider.Priority; - } - - if (currentTasks.Count > 0) - { - var results = await Task.WhenAll(currentTasks).ConfigureAwait(false); - result |= results.Contains(true); - } - - if (providersChanged) - { - item.ProviderData[SupportedProvidersKey] = supportedProvidersInfo; - } - - return result || providersChanged; - } - - /// - /// Notifies the kernal that a provider has begun refreshing - /// - /// The provider. - /// The item. - /// The cancellation token source. - internal void OnProviderRefreshBeginning(BaseMetadataProvider provider, BaseItem item, CancellationTokenSource cancellationTokenSource) - { - var key = item.Id + provider.GetType().Name; - - Tuple current; - - if (_currentlyRunningProviders.TryGetValue(key, out current)) - { - try - { - current.Item3.Cancel(); - } - catch (ObjectDisposedException) - { - - } - } - - var tuple = new Tuple(provider, item, cancellationTokenSource); - - _currentlyRunningProviders.AddOrUpdate(key, tuple, (k, v) => tuple); - } - - /// - /// Notifies the kernal that a provider has completed refreshing - /// - /// The provider. - /// The item. - internal void OnProviderRefreshCompleted(BaseMetadataProvider provider, BaseItem item) - { - var key = item.Id + provider.GetType().Name; - - Tuple current; - - if (_currentlyRunningProviders.TryRemove(key, out current)) - { - current.Item3.Dispose(); - } - } - - /// - /// Validates the currently running providers and cancels any that should not be run due to configuration changes - /// - internal void ValidateCurrentlyRunningProviders() - { - _logger.Info("Validing currently running providers"); - - var enableInternetProviders = ConfigurationManager.Configuration.EnableInternetProviders; - var internetProviderExcludeTypes = ConfigurationManager.Configuration.InternetProviderExcludeTypes; - - foreach (var tuple in _currentlyRunningProviders.Values - .Where(p => p.Item1.RequiresInternet && (!enableInternetProviders || internetProviderExcludeTypes.Contains(p.Item2.GetType().Name, StringComparer.OrdinalIgnoreCase))) - .ToList()) - { - tuple.Item3.Cancel(); - } - } - - /// - /// Downloads the and save image. - /// - /// The item. - /// The source. - /// Name of the target. - /// The resource pool. - /// The cancellation token. - /// Task{System.String}. - /// item - public async Task DownloadAndSaveImage(BaseItem item, string source, string targetName, SemaphoreSlim resourcePool, CancellationToken cancellationToken) - { - if (item == null) - { - throw new ArgumentNullException("item"); - } - if (string.IsNullOrEmpty(source)) - { - throw new ArgumentNullException("source"); - } - if (string.IsNullOrEmpty(targetName)) - { - throw new ArgumentNullException("targetName"); - } - if (resourcePool == null) - { - throw new ArgumentNullException("resourcePool"); - } - - //download and save locally - var localPath = ConfigurationManager.Configuration.SaveLocalMeta ? - Path.Combine(item.MetaLocation, targetName) : - _remoteImageCache.GetResourcePath(item.GetType().FullName + item.Path.ToLower(), targetName); - - var img = await _httpClient.GetMemoryStream(source, resourcePool, cancellationToken).ConfigureAwait(false); - - if (ConfigurationManager.Configuration.SaveLocalMeta) // queue to media directories - { - await Kernel.FileSystemManager.SaveToLibraryFilesystem(item, localPath, img, cancellationToken).ConfigureAwait(false); - } - else - { - // we can write directly here because it won't affect the watchers - - try - { - using (var fs = new FileStream(localPath, FileMode.Create, FileAccess.Write, FileShare.Read, StreamDefaults.DefaultFileStreamBufferSize, FileOptions.Asynchronous)) - { - await img.CopyToAsync(fs, StreamDefaults.DefaultCopyToBufferSize, cancellationToken).ConfigureAwait(false); - } - } - catch (OperationCanceledException) - { - throw; - } - catch (Exception e) - { - _logger.ErrorException("Error downloading and saving image " + localPath, e); - throw; - } - finally - { - img.Dispose(); - } - - } - return localPath; - } - - /// - /// 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) - { - _remoteImageCache.Dispose(); - } - } - - public void Dispose() - { - Dispose(true); - } - } -} diff --git a/MediaBrowser.Controller/Providers/SortNameProvider.cs b/MediaBrowser.Controller/Providers/SortNameProvider.cs index 2214d8a08..c36bcb656 100644 --- a/MediaBrowser.Controller/Providers/SortNameProvider.cs +++ b/MediaBrowser.Controller/Providers/SortNameProvider.cs @@ -49,6 +49,17 @@ namespace MediaBrowser.Controller.Providers return !string.IsNullOrEmpty(item.Name) && string.IsNullOrEmpty(item.SortName); } + // Cache these since they will be used a lot + /// + /// The false task result + /// + protected static readonly Task FalseTaskResult = Task.FromResult(false); + + /// + /// The true task result + /// + protected static readonly Task TrueTaskResult = Task.FromResult(true); + /// /// Fetches metadata and returns true or false indicating if any work that requires persistence was done /// @@ -56,7 +67,7 @@ namespace MediaBrowser.Controller.Providers /// if set to true [force]. /// The cancellation token. /// Task{System.Boolean}. - protected override Task FetchAsyncInternal(BaseItem item, bool force, CancellationToken cancellationToken) + public override Task FetchAsync(BaseItem item, bool force, CancellationToken cancellationToken) { return SetSortName(item, cancellationToken) ? TrueTaskResult : FalseTaskResult; } diff --git a/MediaBrowser.Controller/Providers/TV/EpisodeImageFromMediaLocationProvider.cs b/MediaBrowser.Controller/Providers/TV/EpisodeImageFromMediaLocationProvider.cs index e4ef73892..ac22b43bf 100644 --- a/MediaBrowser.Controller/Providers/TV/EpisodeImageFromMediaLocationProvider.cs +++ b/MediaBrowser.Controller/Providers/TV/EpisodeImageFromMediaLocationProvider.cs @@ -50,6 +50,11 @@ namespace MediaBrowser.Controller.Providers.TV } } + /// + /// The true task result + /// + protected static readonly Task TrueTaskResult = Task.FromResult(true); + /// /// Fetches metadata and returns true or false indicating if any work that requires persistence was done /// @@ -57,7 +62,7 @@ namespace MediaBrowser.Controller.Providers.TV /// if set to true [force]. /// The cancellation token. /// Task{System.Boolean}. - protected override Task FetchAsyncInternal(BaseItem item, bool force, CancellationToken cancellationToken) + public override Task FetchAsync(BaseItem item, bool force, CancellationToken cancellationToken) { cancellationToken.ThrowIfCancellationRequested(); diff --git a/MediaBrowser.Controller/Providers/TV/EpisodeProviderFromXml.cs b/MediaBrowser.Controller/Providers/TV/EpisodeProviderFromXml.cs index 1913397d4..483d1ddce 100644 --- a/MediaBrowser.Controller/Providers/TV/EpisodeProviderFromXml.cs +++ b/MediaBrowser.Controller/Providers/TV/EpisodeProviderFromXml.cs @@ -53,7 +53,7 @@ namespace MediaBrowser.Controller.Providers.TV /// if set to true [force]. /// The cancellation token. /// Task{System.Boolean}. - protected override Task FetchAsyncInternal(BaseItem item, bool force, CancellationToken cancellationToken) + public override Task FetchAsync(BaseItem item, bool force, CancellationToken cancellationToken) { return Task.Run(() => Fetch(item, cancellationToken)); } diff --git a/MediaBrowser.Controller/Providers/TV/FanArtTVProvider.cs b/MediaBrowser.Controller/Providers/TV/FanArtTVProvider.cs index 5dcead7b1..5690ba47f 100644 --- a/MediaBrowser.Controller/Providers/TV/FanArtTVProvider.cs +++ b/MediaBrowser.Controller/Providers/TV/FanArtTVProvider.cs @@ -25,7 +25,9 @@ namespace MediaBrowser.Controller.Providers.TV /// The HTTP client. protected IHttpClient HttpClient { get; private set; } - public FanArtTvProvider(IHttpClient httpClient, ILogManager logManager, IServerConfigurationManager configurationManager) + private readonly IProviderManager _providerManager; + + public FanArtTvProvider(IHttpClient httpClient, ILogManager logManager, IServerConfigurationManager configurationManager, IProviderManager providerManager) : base(logManager, configurationManager) { if (httpClient == null) @@ -33,6 +35,7 @@ namespace MediaBrowser.Controller.Providers.TV throw new ArgumentNullException("httpClient"); } HttpClient = httpClient; + _providerManager = providerManager; } public override bool Supports(BaseItem item) @@ -53,7 +56,7 @@ namespace MediaBrowser.Controller.Providers.TV || (!thumbExists && ConfigurationManager.Configuration.DownloadSeriesImages.Thumb); } - protected override async Task FetchAsyncInternal(BaseItem item, bool force, CancellationToken cancellationToken) + public override async Task FetchAsync(BaseItem item, bool force, CancellationToken cancellationToken) { cancellationToken.ThrowIfCancellationRequested(); @@ -90,7 +93,7 @@ namespace MediaBrowser.Controller.Providers.TV Logger.Debug("FanArtProvider getting ClearLogo for " + series.Name); try { - series.SetImage(ImageType.Logo, await Kernel.Instance.ProviderManager.DownloadAndSaveImage(series, path, LOGO_FILE, FanArtMovieProvider.Current.FanArtResourcePool, cancellationToken).ConfigureAwait(false)); + series.SetImage(ImageType.Logo, await _providerManager.DownloadAndSaveImage(series, path, LOGO_FILE, FanArtMovieProvider.Current.FanArtResourcePool, cancellationToken).ConfigureAwait(false)); } catch (HttpException) { @@ -114,7 +117,7 @@ namespace MediaBrowser.Controller.Providers.TV Logger.Debug("FanArtProvider getting ClearArt for " + series.Name); try { - series.SetImage(ImageType.Art, await Kernel.Instance.ProviderManager.DownloadAndSaveImage(series, path, ART_FILE, FanArtMovieProvider.Current.FanArtResourcePool, cancellationToken).ConfigureAwait(false)); + series.SetImage(ImageType.Art, await _providerManager.DownloadAndSaveImage(series, path, ART_FILE, FanArtMovieProvider.Current.FanArtResourcePool, cancellationToken).ConfigureAwait(false)); } catch (HttpException) { @@ -138,7 +141,7 @@ namespace MediaBrowser.Controller.Providers.TV Logger.Debug("FanArtProvider getting ThumbArt for " + series.Name); try { - series.SetImage(ImageType.Disc, await Kernel.Instance.ProviderManager.DownloadAndSaveImage(series, path, THUMB_FILE, FanArtMovieProvider.Current.FanArtResourcePool, cancellationToken).ConfigureAwait(false)); + series.SetImage(ImageType.Disc, await _providerManager.DownloadAndSaveImage(series, path, THUMB_FILE, FanArtMovieProvider.Current.FanArtResourcePool, cancellationToken).ConfigureAwait(false)); } catch (HttpException) { diff --git a/MediaBrowser.Controller/Providers/TV/RemoteEpisodeProvider.cs b/MediaBrowser.Controller/Providers/TV/RemoteEpisodeProvider.cs index eee43016c..46bb21c48 100644 --- a/MediaBrowser.Controller/Providers/TV/RemoteEpisodeProvider.cs +++ b/MediaBrowser.Controller/Providers/TV/RemoteEpisodeProvider.cs @@ -22,6 +22,8 @@ namespace MediaBrowser.Controller.Providers.TV /// class RemoteEpisodeProvider : BaseMetadataProvider { + private readonly IProviderManager _providerManager; + /// /// Gets the HTTP client. /// @@ -34,10 +36,11 @@ namespace MediaBrowser.Controller.Providers.TV /// The HTTP client. /// The log manager. /// The configuration manager. - public RemoteEpisodeProvider(IHttpClient httpClient, ILogManager logManager, IServerConfigurationManager configurationManager) + public RemoteEpisodeProvider(IHttpClient httpClient, ILogManager logManager, IServerConfigurationManager configurationManager, IProviderManager providerManager) : base(logManager, configurationManager) { HttpClient = httpClient; + _providerManager = providerManager; } /// @@ -117,7 +120,7 @@ namespace MediaBrowser.Controller.Providers.TV /// The item. /// if set to true [force]. /// Task{System.Boolean}. - protected override async Task FetchAsyncInternal(BaseItem item, bool force, CancellationToken cancellationToken) + public override async Task FetchAsync(BaseItem item, bool force, CancellationToken cancellationToken) { cancellationToken.ThrowIfCancellationRequested(); @@ -229,7 +232,7 @@ namespace MediaBrowser.Controller.Providers.TV try { - episode.PrimaryImagePath = await Kernel.Instance.ProviderManager.DownloadAndSaveImage(episode, TVUtils.BannerUrl + p, Path.GetFileName(p), RemoteSeriesProvider.Current.TvDbResourcePool, cancellationToken); + episode.PrimaryImagePath = await _providerManager.DownloadAndSaveImage(episode, TVUtils.BannerUrl + p, Path.GetFileName(p), RemoteSeriesProvider.Current.TvDbResourcePool, cancellationToken); } catch (HttpException) { @@ -282,7 +285,7 @@ namespace MediaBrowser.Controller.Providers.TV var ms = new MemoryStream(); doc.Save(ms); - await Kernel.Instance.FileSystemManager.SaveToLibraryFilesystem(episode, Path.Combine(episode.MetaLocation, Path.GetFileNameWithoutExtension(episode.Path) + ".xml"), ms, cancellationToken).ConfigureAwait(false); + await _providerManager.SaveToLibraryFilesystem(episode, Path.Combine(episode.MetaLocation, Path.GetFileNameWithoutExtension(episode.Path) + ".xml"), ms, cancellationToken).ConfigureAwait(false); } return true; diff --git a/MediaBrowser.Controller/Providers/TV/RemoteSeasonProvider.cs b/MediaBrowser.Controller/Providers/TV/RemoteSeasonProvider.cs index 03d7d3353..f39e91834 100644 --- a/MediaBrowser.Controller/Providers/TV/RemoteSeasonProvider.cs +++ b/MediaBrowser.Controller/Providers/TV/RemoteSeasonProvider.cs @@ -25,8 +25,10 @@ namespace MediaBrowser.Controller.Providers.TV /// /// The HTTP client. protected IHttpClient HttpClient { get; private set; } + + private readonly IProviderManager _providerManager; - public RemoteSeasonProvider(IHttpClient httpClient, ILogManager logManager, IServerConfigurationManager configurationManager) + public RemoteSeasonProvider(IHttpClient httpClient, ILogManager logManager, IServerConfigurationManager configurationManager, IProviderManager providerManager) : base(logManager, configurationManager) { if (httpClient == null) @@ -34,6 +36,7 @@ namespace MediaBrowser.Controller.Providers.TV throw new ArgumentNullException("httpClient"); } HttpClient = httpClient; + _providerManager = providerManager; } /// @@ -97,7 +100,7 @@ namespace MediaBrowser.Controller.Providers.TV /// if set to true [force]. /// The cancellation token. /// Task{System.Boolean}. - protected override async Task FetchAsyncInternal(BaseItem item, bool force, CancellationToken cancellationToken) + public override async Task FetchAsync(BaseItem item, bool force, CancellationToken cancellationToken) { cancellationToken.ThrowIfCancellationRequested(); @@ -174,7 +177,7 @@ namespace MediaBrowser.Controller.Providers.TV try { if (n != null) - season.PrimaryImagePath = await Kernel.Instance.ProviderManager.DownloadAndSaveImage(season, TVUtils.BannerUrl + n.InnerText, "folder" + Path.GetExtension(n.InnerText), RemoteSeriesProvider.Current.TvDbResourcePool, cancellationToken).ConfigureAwait(false); + season.PrimaryImagePath = await _providerManager.DownloadAndSaveImage(season, TVUtils.BannerUrl + n.InnerText, "folder" + Path.GetExtension(n.InnerText), RemoteSeriesProvider.Current.TvDbResourcePool, cancellationToken).ConfigureAwait(false); } catch (HttpException) { @@ -197,8 +200,7 @@ namespace MediaBrowser.Controller.Providers.TV try { var bannerImagePath = - await - Kernel.Instance.ProviderManager.DownloadAndSaveImage(season, + await _providerManager.DownloadAndSaveImage(season, TVUtils.BannerUrl + n.InnerText, "banner" + Path.GetExtension(n.InnerText), @@ -229,7 +231,7 @@ namespace MediaBrowser.Controller.Providers.TV try { if (season.BackdropImagePaths == null) season.BackdropImagePaths = new List(); - season.BackdropImagePaths.Add(await Kernel.Instance.ProviderManager.DownloadAndSaveImage(season, TVUtils.BannerUrl + n.InnerText, "backdrop" + Path.GetExtension(n.InnerText), RemoteSeriesProvider.Current.TvDbResourcePool, cancellationToken).ConfigureAwait(false)); + season.BackdropImagePaths.Add(await _providerManager.DownloadAndSaveImage(season, TVUtils.BannerUrl + n.InnerText, "backdrop" + Path.GetExtension(n.InnerText), RemoteSeriesProvider.Current.TvDbResourcePool, cancellationToken).ConfigureAwait(false)); } catch (HttpException) { @@ -257,8 +259,7 @@ namespace MediaBrowser.Controller.Providers.TV try { season.BackdropImagePaths.Add( - await - Kernel.Instance.ProviderManager.DownloadAndSaveImage(season, + await _providerManager.DownloadAndSaveImage(season, TVUtils.BannerUrl + n.InnerText, "backdrop" + diff --git a/MediaBrowser.Controller/Providers/TV/RemoteSeriesProvider.cs b/MediaBrowser.Controller/Providers/TV/RemoteSeriesProvider.cs index efb158b1e..ddc4e7fca 100644 --- a/MediaBrowser.Controller/Providers/TV/RemoteSeriesProvider.cs +++ b/MediaBrowser.Controller/Providers/TV/RemoteSeriesProvider.cs @@ -22,8 +22,10 @@ namespace MediaBrowser.Controller.Providers.TV /// /// Class RemoteSeriesProvider /// - class RemoteSeriesProvider : BaseMetadataProvider + class RemoteSeriesProvider : BaseMetadataProvider, IDisposable { + private readonly IProviderManager _providerManager; + /// /// The tv db /// @@ -44,7 +46,7 @@ namespace MediaBrowser.Controller.Providers.TV /// The log manager. /// The configuration manager. /// httpClient - public RemoteSeriesProvider(IHttpClient httpClient, ILogManager logManager, IServerConfigurationManager configurationManager) + public RemoteSeriesProvider(IHttpClient httpClient, ILogManager logManager, IServerConfigurationManager configurationManager, IProviderManager providerManager) : base(logManager, configurationManager) { if (httpClient == null) @@ -52,6 +54,7 @@ namespace MediaBrowser.Controller.Providers.TV throw new ArgumentNullException("httpClient"); } HttpClient = httpClient; + _providerManager = providerManager; Current = this; } @@ -59,13 +62,12 @@ namespace MediaBrowser.Controller.Providers.TV /// Releases unmanaged and - optionally - managed resources. /// /// true to release both managed and unmanaged resources; false to release only unmanaged resources. - protected override void Dispose(bool dispose) + protected virtual void Dispose(bool dispose) { if (dispose) { TvDbResourcePool.Dispose(); } - base.Dispose(dispose); } /// @@ -149,7 +151,7 @@ namespace MediaBrowser.Controller.Providers.TV /// if set to true [force]. /// The cancellation token. /// Task{System.Boolean}. - protected override async Task FetchAsyncInternal(BaseItem item, bool force, CancellationToken cancellationToken) + public override async Task FetchAsync(BaseItem item, bool force, CancellationToken cancellationToken) { cancellationToken.ThrowIfCancellationRequested(); @@ -252,8 +254,8 @@ namespace MediaBrowser.Controller.Providers.TV { var ms = new MemoryStream(); doc.Save(ms); - - await Kernel.Instance.FileSystemManager.SaveToLibraryFilesystem(series, Path.Combine(series.MetaLocation, LOCAL_META_FILE_NAME), ms, cancellationToken).ConfigureAwait(false); + + await _providerManager.SaveToLibraryFilesystem(series, Path.Combine(series.MetaLocation, LOCAL_META_FILE_NAME), ms, cancellationToken).ConfigureAwait(false); } } } @@ -366,7 +368,7 @@ namespace MediaBrowser.Controller.Providers.TV { try { - series.PrimaryImagePath = await Kernel.Instance.ProviderManager.DownloadAndSaveImage(series, TVUtils.BannerUrl + n.InnerText, "folder" + Path.GetExtension(n.InnerText), TvDbResourcePool, cancellationToken).ConfigureAwait(false); + series.PrimaryImagePath = await _providerManager.DownloadAndSaveImage(series, TVUtils.BannerUrl + n.InnerText, "folder" + Path.GetExtension(n.InnerText), TvDbResourcePool, cancellationToken).ConfigureAwait(false); } catch (HttpException) { @@ -389,7 +391,7 @@ namespace MediaBrowser.Controller.Providers.TV { try { - var bannerImagePath = await Kernel.Instance.ProviderManager.DownloadAndSaveImage(series, TVUtils.BannerUrl + n.InnerText, "banner" + Path.GetExtension(n.InnerText), TvDbResourcePool, cancellationToken); + var bannerImagePath = await _providerManager.DownloadAndSaveImage(series, TVUtils.BannerUrl + n.InnerText, "banner" + Path.GetExtension(n.InnerText), TvDbResourcePool, cancellationToken); series.SetImage(ImageType.Banner, bannerImagePath); } @@ -418,7 +420,7 @@ namespace MediaBrowser.Controller.Providers.TV { try { - series.BackdropImagePaths.Add(await Kernel.Instance.ProviderManager.DownloadAndSaveImage(series, TVUtils.BannerUrl + p.InnerText, bdName + Path.GetExtension(p.InnerText), TvDbResourcePool, cancellationToken).ConfigureAwait(false)); + series.BackdropImagePaths.Add(await _providerManager.DownloadAndSaveImage(series, TVUtils.BannerUrl + p.InnerText, bdName + Path.GetExtension(p.InnerText), TvDbResourcePool, cancellationToken).ConfigureAwait(false)); } catch (HttpException) { @@ -584,7 +586,9 @@ namespace MediaBrowser.Controller.Providers.TV return name.Trim(); } - - + public void Dispose() + { + Dispose(true); + } } } diff --git a/MediaBrowser.Controller/Providers/TV/SeriesProviderFromXml.cs b/MediaBrowser.Controller/Providers/TV/SeriesProviderFromXml.cs index f9d409c18..8e42e9d55 100644 --- a/MediaBrowser.Controller/Providers/TV/SeriesProviderFromXml.cs +++ b/MediaBrowser.Controller/Providers/TV/SeriesProviderFromXml.cs @@ -57,7 +57,7 @@ namespace MediaBrowser.Controller.Providers.TV /// if set to true [force]. /// The cancellation token. /// Task{System.Boolean}. - protected override Task FetchAsyncInternal(BaseItem item, bool force, CancellationToken cancellationToken) + public override Task FetchAsync(BaseItem item, bool force, CancellationToken cancellationToken) { return Task.Run(() => Fetch(item, cancellationToken)); } diff --git a/MediaBrowser.Server.Implementations/IO/DirectoryWatchers.cs b/MediaBrowser.Server.Implementations/IO/DirectoryWatchers.cs new file mode 100644 index 000000000..c1371a32b --- /dev/null +++ b/MediaBrowser.Server.Implementations/IO/DirectoryWatchers.cs @@ -0,0 +1,543 @@ +using MediaBrowser.Common.ScheduledTasks; +using MediaBrowser.Controller.Configuration; +using MediaBrowser.Controller.Entities; +using MediaBrowser.Controller.IO; +using MediaBrowser.Controller.Library; +using MediaBrowser.Controller.ScheduledTasks; +using MediaBrowser.Model.Logging; +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 ConcurrentBag _fileSystemWatchers = new ConcurrentBag(); + /// + /// 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); + + /// + /// 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 void RemoveTempIgnore(string path) + { + 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; } + + /// + /// Initializes a new instance of the class. + /// + public DirectoryWatchers(ILogManager logManager, ITaskManager taskManager, ILibraryManager libraryManager, IServerConfigurationManager configurationManager) + { + if (taskManager == null) + { + throw new ArgumentNullException("taskManager"); + } + + LibraryManager = libraryManager; + TaskManager = taskManager; + Logger = logManager.GetLogger("DirectoryWatchers"); + ConfigurationManager = configurationManager; + } + + /// + /// Starts this instance. + /// + public void Start() + { + LibraryManager.LibraryChanged += Instance_LibraryChanged; + + var pathsToWatch = new List { LibraryManager.RootFolder.Path }; + + var paths = LibraryManager.RootFolder.Children.OfType() + .SelectMany(f => + { + try + { + // Accessing ResolveArgs could involve file system access + return f.ResolveArgs.PhysicalLocations; + } + catch (IOException) + { + return new string[] {}; + } + + }) + .Where(Path.IsPathRooted); + + foreach (var path in paths) + { + if (!ContainsParentFolder(pathsToWatch, path)) + { + pathsToWatch.Add(path); + } + } + + foreach (var path in pathsToWatch) + { + StartWatchingPath(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 + { + newWatcher.EnableRaisingEvents = true; + _fileSystemWatchers.Add(newWatcher); + + Logger.Info("Watching directory " + path); + } + 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) + { + var watcher = _fileSystemWatchers.FirstOrDefault(f => f.Path.Equals(path, StringComparison.OrdinalIgnoreCase)); + + if (watcher != null) + { + 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(); + + var watchers = _fileSystemWatchers.ToList(); + + watchers.Remove(watcher); + + _fileSystemWatchers = new ConcurrentBag(watchers); + } + + /// + /// Handles the LibraryChanged event of the Kernel + /// + /// The source of the event. + /// The instance containing the event data. + void Instance_LibraryChanged(object sender, ChildrenChangedEventArgs e) + { + if (e.Folder is AggregateFolder && e.HasAddOrRemoveChange) + { + if (e.ItemsRemoved != null) + { + foreach (var item in e.ItemsRemoved.OfType()) + { + StopWatchingPath(item.Path); + } + } + if (e.ItemsAdded != null) + { + foreach (var item in e.ItemsAdded.OfType()) + { + StartWatchingPath(item.Path); + } + } + } + } + + /// + /// Handles the Error event of the watcher control. + /// + /// The source of the event. + /// The instance containing the event data. + async 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); + + if (ex.Message.Contains("network name is no longer available")) + { + //Network either dropped or, we are coming out of sleep and it hasn't reconnected yet - wait and retry + Logger.Warn("Network connection lost - will retry..."); + var retries = 0; + var success = false; + while (!success && retries < 10) + { + await Task.Delay(500).ConfigureAwait(false); + + try + { + dw.EnableRaisingEvents = false; + dw.EnableRaisingEvents = true; + success = true; + } + catch (IOException) + { + Logger.Warn("Network still unavailable..."); + retries++; + } + } + if (!success) + { + Logger.Warn("Unable to access network. Giving up."); + DisposeWatcher(dw); + } + + } + else + { + if (!ex.Message.Contains("BIOS command limit")) + { + Logger.Info("Attempting to re-start watcher."); + + dw.EnableRaisingEvents = false; + dw.EnableRaisingEvents = true; + } + + } + } + + /// + /// 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) + { + if (e.ChangeType == WatcherChangeTypes.Created && e.Name == "New folder") + { + return; + } + if (_tempIgnoredPaths.ContainsKey(e.FullPath)) + { + Logger.Info("Watcher requested to ignore change to " + e.FullPath); + return; + } + + Logger.Info("Watcher sees change of type " + e.ChangeType.ToString() + " 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."); + + _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.GetFileData(path); + + if (!data.HasValue || data.Value.IsDirectory) + { + return false; + } + } + catch (IOException) + { + return false; + } + + FileStream stream = null; + + try + { + stream = new FileStream(path, FileMode.Open, FileAccess.ReadWrite, FileShare.ReadWrite); + } + catch + { + //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) + return true; + } + finally + { + if (stream != null) + stream.Close(); + } + + //file is not locked + 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; + } + + await Task.WhenAll(itemsToRefresh.Select(i => Task.Run(async () => + { + Logger.Info(i.Name + " (" + i.Path + ") will be refreshed."); + + try + { + await i.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, i.Name); + } + + }))).ConfigureAwait(false); + } + + /// + /// 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.LibraryChanged -= Instance_LibraryChanged; + + FileSystemWatcher watcher; + + while (_fileSystemWatchers.TryTake(out watcher)) + { + watcher.Changed -= watcher_Changed; + watcher.EnableRaisingEvents = false; + watcher.Dispose(); + } + + lock (_timerLock) + { + if (_updateTimer != null) + { + _updateTimer.Dispose(); + _updateTimer = null; + } + } + + _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/MediaBrowser.Server.Implementations.csproj b/MediaBrowser.Server.Implementations/MediaBrowser.Server.Implementations.csproj index e56c79ded..781088672 100644 --- a/MediaBrowser.Server.Implementations/MediaBrowser.Server.Implementations.csproj +++ b/MediaBrowser.Server.Implementations/MediaBrowser.Server.Implementations.csproj @@ -111,6 +111,7 @@ + @@ -128,6 +129,7 @@ + diff --git a/MediaBrowser.Server.Implementations/Providers/ProviderManager.cs b/MediaBrowser.Server.Implementations/Providers/ProviderManager.cs new file mode 100644 index 000000000..5accc06d7 --- /dev/null +++ b/MediaBrowser.Server.Implementations/Providers/ProviderManager.cs @@ -0,0 +1,487 @@ +using MediaBrowser.Common.Extensions; +using MediaBrowser.Common.IO; +using MediaBrowser.Common.Net; +using MediaBrowser.Controller; +using MediaBrowser.Controller.Configuration; +using MediaBrowser.Controller.Entities; +using MediaBrowser.Controller.IO; +using MediaBrowser.Controller.Providers; +using MediaBrowser.Model.Logging; +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.Providers +{ + /// + /// Class ProviderManager + /// + public class ProviderManager : IProviderManager + { + /// + /// The remote image cache + /// + private readonly FileSystemRepository _remoteImageCache; + + /// + /// The currently running metadata providers + /// + private readonly ConcurrentDictionary> _currentlyRunningProviders = + new ConcurrentDictionary>(); + + /// + /// The _logger + /// + private readonly ILogger _logger; + + /// + /// The _HTTP client + /// + private readonly IHttpClient _httpClient; + + /// + /// The _directory watchers + /// + private readonly IDirectoryWatchers _directoryWatchers; + + /// + /// Gets or sets the configuration manager. + /// + /// The configuration manager. + private IServerConfigurationManager ConfigurationManager { get; set; } + + /// + /// Gets the list of currently registered metadata prvoiders + /// + /// The metadata providers enumerable. + private BaseMetadataProvider[] MetadataProviders { get; set; } + + /// + /// Initializes a new instance of the class. + /// + /// The HTTP client. + /// The configuration manager. + /// The directory watchers. + /// The log manager. + public ProviderManager(IHttpClient httpClient, IServerConfigurationManager configurationManager, IDirectoryWatchers directoryWatchers, ILogManager logManager) + { + _logger = logManager.GetLogger("ProviderManager"); + _httpClient = httpClient; + ConfigurationManager = configurationManager; + _directoryWatchers = directoryWatchers; + _remoteImageCache = new FileSystemRepository(configurationManager.ApplicationPaths.DownloadedImagesDataPath); + + configurationManager.ConfigurationUpdated += configurationManager_ConfigurationUpdated; + } + + /// + /// Handles the ConfigurationUpdated event of the configurationManager control. + /// + /// The source of the event. + /// The instance containing the event data. + void configurationManager_ConfigurationUpdated(object sender, EventArgs e) + { + // Validate currently executing providers, in the background + Task.Run(() => + { + ValidateCurrentlyRunningProviders(); + }); + } + + /// + /// Gets or sets the supported providers key. + /// + /// The supported providers key. + private Guid SupportedProvidersKey { get; set; } + + /// + /// Adds the metadata providers. + /// + /// The providers. + public void AddMetadataProviders(IEnumerable providers) + { + MetadataProviders = providers.ToArray(); + } + + /// + /// Runs all metadata providers for an entity, and returns true or false indicating if at least one was refreshed and requires persistence + /// + /// The item. + /// The cancellation token. + /// if set to true [force]. + /// if set to true [allow slow providers]. + /// Task{System.Boolean}. + public async Task ExecuteMetadataProviders(BaseItem item, CancellationToken cancellationToken, bool force = false, bool allowSlowProviders = true) + { + // Allow providers of the same priority to execute in parallel + MetadataProviderPriority? currentPriority = null; + var currentTasks = new List>(); + + var result = false; + + cancellationToken.ThrowIfCancellationRequested(); + + // Determine if supported providers have changed + var supportedProviders = MetadataProviders.Where(p => p.Supports(item)).ToList(); + + BaseProviderInfo supportedProvidersInfo; + + if (SupportedProvidersKey == Guid.Empty) + { + SupportedProvidersKey = "SupportedProviders".GetMD5(); + } + + var supportedProvidersHash = string.Join("+", supportedProviders.Select(i => i.GetType().Name)).GetMD5(); + bool providersChanged; + + item.ProviderData.TryGetValue(SupportedProvidersKey, out supportedProvidersInfo); + if (supportedProvidersInfo == null) + { + // First time + supportedProvidersInfo = new BaseProviderInfo { ProviderId = SupportedProvidersKey, FileSystemStamp = supportedProvidersHash }; + providersChanged = force = true; + } + else + { + // Force refresh if the supported providers have changed + providersChanged = force = force || supportedProvidersInfo.FileSystemStamp != supportedProvidersHash; + } + + // If providers have changed, clear provider info and update the supported providers hash + if (providersChanged) + { + _logger.Debug("Providers changed for {0}. Clearing and forcing refresh.", item.Name); + item.ProviderData.Clear(); + supportedProvidersInfo.FileSystemStamp = supportedProvidersHash; + } + + if (force) item.ClearMetaValues(); + + // Run the normal providers sequentially in order of priority + foreach (var provider in supportedProviders) + { + cancellationToken.ThrowIfCancellationRequested(); + + // Skip if internet providers are currently disabled + if (provider.RequiresInternet && !ConfigurationManager.Configuration.EnableInternetProviders) + { + continue; + } + + // Skip if is slow and we aren't allowing slow ones + if (provider.IsSlow && !allowSlowProviders) + { + continue; + } + + // Skip if internet provider and this type is not allowed + if (provider.RequiresInternet && ConfigurationManager.Configuration.EnableInternetProviders && ConfigurationManager.Configuration.InternetProviderExcludeTypes.Contains(item.GetType().Name, StringComparer.OrdinalIgnoreCase)) + { + continue; + } + + // When a new priority is reached, await the ones that are currently running and clear the list + if (currentPriority.HasValue && currentPriority.Value != provider.Priority && currentTasks.Count > 0) + { + var results = await Task.WhenAll(currentTasks).ConfigureAwait(false); + result |= results.Contains(true); + + currentTasks.Clear(); + } + + // Put this check below the await because the needs refresh of the next tier of providers may depend on the previous ones running + // This is the case for the fan art provider which depends on the movie and tv providers having run before them + if (!force && !provider.NeedsRefresh(item)) + { + continue; + } + + currentTasks.Add(FetchAsync(provider, item, force, cancellationToken)); + currentPriority = provider.Priority; + } + + if (currentTasks.Count > 0) + { + var results = await Task.WhenAll(currentTasks).ConfigureAwait(false); + result |= results.Contains(true); + } + + if (providersChanged) + { + item.ProviderData[SupportedProvidersKey] = supportedProvidersInfo; + } + + return result || providersChanged; + } + + /// + /// Fetches metadata and returns true or false indicating if any work that requires persistence was done + /// + /// The provider. + /// The item. + /// if set to true [force]. + /// The cancellation token. + /// Task{System.Boolean}. + /// + private async Task FetchAsync(BaseMetadataProvider provider, BaseItem item, bool force, CancellationToken cancellationToken) + { + if (item == null) + { + throw new ArgumentNullException(); + } + + cancellationToken.ThrowIfCancellationRequested(); + + _logger.Info("Running {0} for {1}", provider.GetType().Name, item.Path ?? item.Name ?? "--Unknown--"); + + // This provides the ability to cancel just this one provider + var innerCancellationTokenSource = new CancellationTokenSource(); + + OnProviderRefreshBeginning(provider, item, innerCancellationTokenSource); + + try + { + return await provider.FetchAsync(item, force, CancellationTokenSource.CreateLinkedTokenSource(cancellationToken, innerCancellationTokenSource.Token).Token).ConfigureAwait(false); + } + catch (OperationCanceledException ex) + { + _logger.Info("{0} cancelled for {1}", provider.GetType().Name, item.Name); + + // If the outer cancellation token is the one that caused the cancellation, throw it + if (cancellationToken.IsCancellationRequested && ex.CancellationToken == cancellationToken) + { + throw; + } + + return false; + } + catch (Exception ex) + { + _logger.ErrorException("{0} failed refreshing {1}", ex, provider.GetType().Name, item.Name); + + provider.SetLastRefreshed(item, DateTime.UtcNow, ProviderRefreshStatus.Failure); + return true; + } + finally + { + innerCancellationTokenSource.Dispose(); + + OnProviderRefreshCompleted(provider, item); + } + } + + /// + /// Notifies the kernal that a provider has begun refreshing + /// + /// The provider. + /// The item. + /// The cancellation token source. + public void OnProviderRefreshBeginning(BaseMetadataProvider provider, BaseItem item, CancellationTokenSource cancellationTokenSource) + { + var key = item.Id + provider.GetType().Name; + + Tuple current; + + if (_currentlyRunningProviders.TryGetValue(key, out current)) + { + try + { + current.Item3.Cancel(); + } + catch (ObjectDisposedException) + { + + } + } + + var tuple = new Tuple(provider, item, cancellationTokenSource); + + _currentlyRunningProviders.AddOrUpdate(key, tuple, (k, v) => tuple); + } + + /// + /// Notifies the kernal that a provider has completed refreshing + /// + /// The provider. + /// The item. + public void OnProviderRefreshCompleted(BaseMetadataProvider provider, BaseItem item) + { + var key = item.Id + provider.GetType().Name; + + Tuple current; + + if (_currentlyRunningProviders.TryRemove(key, out current)) + { + current.Item3.Dispose(); + } + } + + /// + /// Validates the currently running providers and cancels any that should not be run due to configuration changes + /// + private void ValidateCurrentlyRunningProviders() + { + _logger.Info("Validing currently running providers"); + + var enableInternetProviders = ConfigurationManager.Configuration.EnableInternetProviders; + var internetProviderExcludeTypes = ConfigurationManager.Configuration.InternetProviderExcludeTypes; + + foreach (var tuple in _currentlyRunningProviders.Values + .Where(p => p.Item1.RequiresInternet && (!enableInternetProviders || internetProviderExcludeTypes.Contains(p.Item2.GetType().Name, StringComparer.OrdinalIgnoreCase))) + .ToList()) + { + tuple.Item3.Cancel(); + } + } + + /// + /// Downloads the and save image. + /// + /// The item. + /// The source. + /// Name of the target. + /// The resource pool. + /// The cancellation token. + /// Task{System.String}. + /// item + public async Task DownloadAndSaveImage(BaseItem item, string source, string targetName, SemaphoreSlim resourcePool, CancellationToken cancellationToken) + { + if (item == null) + { + throw new ArgumentNullException("item"); + } + if (string.IsNullOrEmpty(source)) + { + throw new ArgumentNullException("source"); + } + if (string.IsNullOrEmpty(targetName)) + { + throw new ArgumentNullException("targetName"); + } + if (resourcePool == null) + { + throw new ArgumentNullException("resourcePool"); + } + + //download and save locally + var localPath = ConfigurationManager.Configuration.SaveLocalMeta ? + Path.Combine(item.MetaLocation, targetName) : + _remoteImageCache.GetResourcePath(item.GetType().FullName + item.Path.ToLower(), targetName); + + var img = await _httpClient.GetMemoryStream(source, resourcePool, cancellationToken).ConfigureAwait(false); + + if (ConfigurationManager.Configuration.SaveLocalMeta) // queue to media directories + { + await SaveToLibraryFilesystem(item, localPath, img, cancellationToken).ConfigureAwait(false); + } + else + { + // we can write directly here because it won't affect the watchers + + try + { + using (var fs = new FileStream(localPath, FileMode.Create, FileAccess.Write, FileShare.Read, StreamDefaults.DefaultFileStreamBufferSize, FileOptions.Asynchronous)) + { + await img.CopyToAsync(fs, StreamDefaults.DefaultCopyToBufferSize, cancellationToken).ConfigureAwait(false); + } + } + catch (OperationCanceledException) + { + throw; + } + catch (Exception e) + { + _logger.ErrorException("Error downloading and saving image " + localPath, e); + throw; + } + finally + { + img.Dispose(); + } + + } + return localPath; + } + + + /// + /// Saves to library filesystem. + /// + /// The item. + /// The path. + /// The data to save. + /// The cancellation token. + /// Task. + /// + public async Task SaveToLibraryFilesystem(BaseItem item, string path, Stream dataToSave, CancellationToken cancellationToken) + { + if (item == null) + { + throw new ArgumentNullException(); + } + if (string.IsNullOrEmpty(path)) + { + throw new ArgumentNullException(); + } + if (dataToSave == null) + { + throw new ArgumentNullException(); + } + if (cancellationToken == null) + { + throw new ArgumentNullException(); + } + + cancellationToken.ThrowIfCancellationRequested(); + + //Tell the watchers to ignore + _directoryWatchers.TemporarilyIgnore(path); + + //Make the mod + + dataToSave.Position = 0; + + try + { + using (var fs = new FileStream(path, FileMode.Create, FileAccess.Write, FileShare.Read, StreamDefaults.DefaultFileStreamBufferSize, FileOptions.Asynchronous)) + { + await dataToSave.CopyToAsync(fs, StreamDefaults.DefaultCopyToBufferSize, cancellationToken).ConfigureAwait(false); + + dataToSave.Dispose(); + + // If this is ever used for something other than metadata we can add a file type param + item.ResolveArgs.AddMetadataFile(path); + } + } + finally + { + //Remove the ignore + _directoryWatchers.RemoveTempIgnore(path); + } + } + + /// + /// 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) + { + _remoteImageCache.Dispose(); + } + } + + /// + /// Performs application-defined tasks associated with freeing, releasing, or resetting unmanaged resources. + /// + public void Dispose() + { + Dispose(true); + } + } +} diff --git a/MediaBrowser.Server.Implementations/ScheduledTasks/ImageCleanupTask.cs b/MediaBrowser.Server.Implementations/ScheduledTasks/ImageCleanupTask.cs index 8306e094b..b20630edb 100644 --- a/MediaBrowser.Server.Implementations/ScheduledTasks/ImageCleanupTask.cs +++ b/MediaBrowser.Server.Implementations/ScheduledTasks/ImageCleanupTask.cs @@ -27,17 +27,21 @@ namespace MediaBrowser.Server.Implementations.ScheduledTasks /// private readonly ILogger _logger; private readonly ILibraryManager _libraryManager; + private readonly IServerApplicationPaths _appPaths; /// /// Initializes a new instance of the class. /// /// The kernel. /// The logger. - public ImageCleanupTask(Kernel kernel, ILogger logger, ILibraryManager libraryManager) + /// The library manager. + /// The app paths. + public ImageCleanupTask(Kernel kernel, ILogger logger, ILibraryManager libraryManager, IServerApplicationPaths appPaths) { _kernel = kernel; _logger = logger; _libraryManager = libraryManager; + _appPaths = appPaths; } /// @@ -65,7 +69,7 @@ namespace MediaBrowser.Server.Implementations.ScheduledTasks // First gather all image files var files = GetFiles(_kernel.FFMpegManager.AudioImagesDataPath) .Concat(GetFiles(_kernel.FFMpegManager.VideoImagesDataPath)) - .Concat(GetFiles(_kernel.ProviderManager.ImagesDataPath)) + .Concat(GetFiles(_appPaths.DownloadedImagesDataPath)) .ToList(); // Now gather all items diff --git a/MediaBrowser.Server.Implementations/ServerApplicationPaths.cs b/MediaBrowser.Server.Implementations/ServerApplicationPaths.cs index ff29badff..429c893ca 100644 --- a/MediaBrowser.Server.Implementations/ServerApplicationPaths.cs +++ b/MediaBrowser.Server.Implementations/ServerApplicationPaths.cs @@ -302,7 +302,7 @@ namespace MediaBrowser.Server.Implementations /// Gets the FF MPEG stream cache path. /// /// The FF MPEG stream cache path. - public string FFMpegStreamCachePath + public string EncodedMediaCachePath { get { @@ -345,5 +345,31 @@ namespace MediaBrowser.Server.Implementations return _mediaToolsPath; } } + + /// + /// The _images data path + /// + private string _downloadedImagesDataPath; + /// + /// Gets the images data path. + /// + /// The images data path. + public string DownloadedImagesDataPath + { + get + { + if (_downloadedImagesDataPath == null) + { + _downloadedImagesDataPath = Path.Combine(DataPath, "remote-images"); + + if (!Directory.Exists(_downloadedImagesDataPath)) + { + Directory.CreateDirectory(_downloadedImagesDataPath); + } + } + + return _downloadedImagesDataPath; + } + } } } diff --git a/MediaBrowser.Server.Implementations/ServerManager/ServerManager.cs b/MediaBrowser.Server.Implementations/ServerManager/ServerManager.cs index e4cb83e96..60628853e 100644 --- a/MediaBrowser.Server.Implementations/ServerManager/ServerManager.cs +++ b/MediaBrowser.Server.Implementations/ServerManager/ServerManager.cs @@ -104,8 +104,8 @@ namespace MediaBrowser.Server.Implementations.ServerManager /// The web socket listeners. private readonly List _webSocketListeners = new List(); - private Kernel _kernel; - + private readonly Kernel _kernel; + /// /// Initializes a new instance of the class. /// diff --git a/MediaBrowser.ServerApplication/App.xaml.cs b/MediaBrowser.ServerApplication/App.xaml.cs index a3e273438..0155d0c90 100644 --- a/MediaBrowser.ServerApplication/App.xaml.cs +++ b/MediaBrowser.ServerApplication/App.xaml.cs @@ -156,13 +156,13 @@ namespace MediaBrowser.ServerApplication { CompositionRoot = new ApplicationHost(); - var win = new MainWindow(CompositionRoot.LogManager, CompositionRoot, CompositionRoot.ServerConfigurationManager); - Logger = CompositionRoot.LogManager.GetLogger("App"); - win.Show(); - await CompositionRoot.Init(); + + var win = new MainWindow(CompositionRoot.LogManager, CompositionRoot, CompositionRoot.ServerConfigurationManager, CompositionRoot.UserManager, CompositionRoot.LibraryManager, CompositionRoot.JsonSerializer); + + win.Show(); } catch (Exception ex) { diff --git a/MediaBrowser.ServerApplication/ApplicationHost.cs b/MediaBrowser.ServerApplication/ApplicationHost.cs index df3765818..2eccee066 100644 --- a/MediaBrowser.ServerApplication/ApplicationHost.cs +++ b/MediaBrowser.ServerApplication/ApplicationHost.cs @@ -2,18 +2,24 @@ using MediaBrowser.Common; using MediaBrowser.Common.Configuration; using MediaBrowser.Common.Constants; -using MediaBrowser.Common.Events; using MediaBrowser.Common.Implementations; using MediaBrowser.Common.Implementations.ScheduledTasks; using MediaBrowser.Common.IO; using MediaBrowser.Common.Net; -using MediaBrowser.Common.Updates; using MediaBrowser.Controller; using MediaBrowser.Controller.Configuration; +using MediaBrowser.Controller.Drawing; using MediaBrowser.Controller.Entities; +using MediaBrowser.Controller.IO; using MediaBrowser.Controller.Library; +using MediaBrowser.Controller.Localization; +using MediaBrowser.Controller.MediaInfo; +using MediaBrowser.Controller.Persistence; +using MediaBrowser.Controller.Plugins; +using MediaBrowser.Controller.Providers; using MediaBrowser.Controller.Resolvers; using MediaBrowser.Controller.Updates; +using MediaBrowser.Controller.Weather; using MediaBrowser.IsoMounter; using MediaBrowser.Model.IO; using MediaBrowser.Model.MediaInfo; @@ -23,7 +29,9 @@ using MediaBrowser.Server.Implementations; using MediaBrowser.Server.Implementations.BdInfo; using MediaBrowser.Server.Implementations.Configuration; using MediaBrowser.Server.Implementations.HttpServer; +using MediaBrowser.Server.Implementations.IO; using MediaBrowser.Server.Implementations.Library; +using MediaBrowser.Server.Implementations.Providers; using MediaBrowser.Server.Implementations.ServerManager; using MediaBrowser.Server.Implementations.Udp; using MediaBrowser.Server.Implementations.Updates; @@ -78,23 +86,73 @@ namespace MediaBrowser.ServerApplication return new ServerConfigurationManager(ApplicationPaths, LogManager, XmlSerializer); } + /// + /// Gets or sets the installation manager. + /// + /// The installation manager. private IInstallationManager InstallationManager { get; set; } + /// + /// Gets or sets the server manager. + /// + /// The server manager. private IServerManager ServerManager { get; set; } - + /// + /// Gets or sets the user manager. + /// + /// The user manager. + public IUserManager UserManager { get; set; } + /// + /// Gets or sets the library manager. + /// + /// The library manager. + internal ILibraryManager LibraryManager { get; set; } + /// + /// Gets or sets the directory watchers. + /// + /// The directory watchers. + private IDirectoryWatchers DirectoryWatchers { get; set; } + /// + /// Gets or sets the provider manager. + /// + /// The provider manager. + private IProviderManager ProviderManager { get; set; } + /// + /// Gets or sets the zip client. + /// + /// The zip client. + private IZipClient ZipClient { get; set; } + /// + /// Gets or sets the HTTP server. + /// + /// The HTTP server. + private IHttpServer HttpServer { get; set; } + + /// + /// Inits this instance. + /// + /// Task. public override async Task Init() { await base.Init().ConfigureAwait(false); - await ServerKernel.Init().ConfigureAwait(false); + Task.Run(async () => + { + await ServerKernel.LoadRepositories(ServerConfigurationManager).ConfigureAwait(false); + + DirectoryWatchers.Start(); + + Parallel.ForEach(GetExports(), entryPoint => entryPoint.Run()); + }); } /// /// Registers resources that classes will depend on /// + /// Task. protected override async Task RegisterResources() { - ServerKernel = new Kernel(this, XmlSerializer, LogManager, ServerConfigurationManager); - + ServerKernel = new Kernel(ServerConfigurationManager); + await base.RegisterResources().ConfigureAwait(false); RegisterSingleInstance(this); @@ -108,20 +166,66 @@ namespace MediaBrowser.ServerApplication RegisterSingleInstance(new PismoIsoManager(Logger)); RegisterSingleInstance(new BdInfoExaminer()); - RegisterSingleInstance(new DotNetZipClient()); - RegisterSingleInstance(ServerFactory.CreateServer(this, ProtobufSerializer, Logger, "Media Browser", "index.html"), false); + + ZipClient = new DotNetZipClient(); + RegisterSingleInstance(ZipClient); + + HttpServer = ServerFactory.CreateServer(this, ProtobufSerializer, Logger, "Media Browser", "index.html"); + RegisterSingleInstance(HttpServer, false); ServerManager = new ServerManager(this, NetworkManager, JsonSerializer, Logger, ServerConfigurationManager, ServerKernel); RegisterSingleInstance(ServerManager); - var userManager = new UserManager(ServerKernel, Logger, ServerConfigurationManager); + UserManager = new UserManager(ServerKernel, Logger, ServerConfigurationManager); + RegisterSingleInstance(UserManager); - RegisterSingleInstance(userManager); - - RegisterSingleInstance(new LibraryManager(ServerKernel, Logger, TaskManager, userManager, ServerConfigurationManager)); + LibraryManager = new LibraryManager(ServerKernel, Logger, TaskManager, UserManager, ServerConfigurationManager); + RegisterSingleInstance(LibraryManager); InstallationManager = new InstallationManager(HttpClient, PackageManager, JsonSerializer, Logger, this); RegisterSingleInstance(InstallationManager); + + DirectoryWatchers = new DirectoryWatchers(LogManager, TaskManager, LibraryManager, ServerConfigurationManager); + RegisterSingleInstance(DirectoryWatchers); + + ProviderManager = new ProviderManager(HttpClient, ServerConfigurationManager, DirectoryWatchers, LogManager); + RegisterSingleInstance(ProviderManager); + + SetKernelProperties(); + SetStaticProperties(); + } + + /// + /// Sets the kernel properties. + /// + private void SetKernelProperties() + { + ServerKernel.FFMpegManager = new FFMpegManager(ServerKernel, ZipClient, JsonSerializer, ProtobufSerializer, LogManager, ApplicationPaths); + ServerKernel.ImageManager = new ImageManager(ServerKernel, ProtobufSerializer, LogManager.GetLogger("ImageManager"), ApplicationPaths); + + ServerKernel.UserDataRepositories = GetExports(); + ServerKernel.UserRepositories = GetExports(); + ServerKernel.DisplayPreferencesRepositories = GetExports(); + ServerKernel.ItemRepositories = GetExports(); + ServerKernel.WeatherProviders = GetExports(); + ServerKernel.ImageEnhancers = GetExports().OrderBy(e => e.Priority).ToArray(); + ServerKernel.StringFiles = GetExports(); + } + + /// + /// Dirty hacks + /// + private void SetStaticProperties() + { + // For now there's no real way to inject these properly + BaseItem.Logger = LogManager.GetLogger("BaseItem"); + BaseItem.ConfigurationManager = ServerConfigurationManager; + BaseItem.LibraryManager = LibraryManager; + BaseItem.ProviderManager = ProviderManager; + User.XmlSerializer = XmlSerializer; + User.UserManager = UserManager; + Ratings.ConfigurationManager = ServerConfigurationManager; + LocalizedStrings.ApplicationPaths = ApplicationPaths; } /// @@ -131,12 +235,14 @@ namespace MediaBrowser.ServerApplication { base.FindParts(); - Resolve().Init(GetExports(false)); - Resolve().AddWebSocketListeners(GetExports(false)); + HttpServer.Init(GetExports(false)); + + ServerManager.AddWebSocketListeners(GetExports(false)); + ServerManager.Start(); - Resolve().Start(); + LibraryManager.AddParts(GetExports(), GetExports(), GetExports(), GetExports()); - Resolve().AddParts(GetExports(), GetExports(), GetExports(), GetExports()); + ProviderManager.AddMetadataProviders(GetExports().OrderBy(e => e.Priority).ToArray()); } /// @@ -164,9 +270,8 @@ namespace MediaBrowser.ServerApplication /// Task{CheckForUpdateResult}. public async override Task CheckForApplicationUpdate(CancellationToken cancellationToken, IProgress progress) { - var pkgManager = Resolve(); - var availablePackages = await pkgManager.GetAvailablePackages(CancellationToken.None).ConfigureAwait(false); - var version = Resolve().GetLatestCompatibleVersion(availablePackages, Constants.MBServerPkgName, ConfigurationManager.CommonConfiguration.SystemUpdateLevel); + var availablePackages = await PackageManager.GetAvailablePackages(CancellationToken.None).ConfigureAwait(false); + var version = InstallationManager.GetLatestCompatibleVersion(availablePackages, Constants.MBServerPkgName, ConfigurationManager.CommonConfiguration.SystemUpdateLevel); return version != null ? new CheckForUpdateResult { AvailableVersion = version.version, IsUpdateAvailable = version.version > ApplicationVersion, Package = version } : new CheckForUpdateResult { AvailableVersion = ApplicationVersion, IsUpdateAvailable = false }; diff --git a/MediaBrowser.ServerApplication/EntryPoints/NewItemNotifier.cs b/MediaBrowser.ServerApplication/EntryPoints/NewItemNotifier.cs new file mode 100644 index 000000000..4a1f0ac74 --- /dev/null +++ b/MediaBrowser.ServerApplication/EntryPoints/NewItemNotifier.cs @@ -0,0 +1,148 @@ +using System.Windows; +using MediaBrowser.Controller.Entities; +using MediaBrowser.Controller.Library; +using MediaBrowser.Controller.Plugins; +using MediaBrowser.Model.Logging; +using MediaBrowser.ServerApplication.Controls; +using System.Collections.Generic; +using System.Linq; +using System.Threading; +using System.Windows.Controls.Primitives; + +namespace MediaBrowser.ServerApplication.EntryPoints +{ + /// + /// Class NewItemNotifier + /// + public class NewItemNotifier : IServerEntryPoint + { + /// + /// Holds the list of new items to display when the NewItemTimer expires + /// + private readonly List _newlyAddedItems = new List(); + + /// + /// The amount of time to wait before showing a new item notification + /// This allows us to group items together into one notification + /// + private const int NewItemDelay = 60000; + + /// + /// The current new item timer + /// + /// The new item timer. + private Timer NewItemTimer { get; set; } + + /// + /// The _library manager + /// + private readonly ILibraryManager _libraryManager; + + /// + /// The _logger + /// + private readonly ILogger _logger; + + /// + /// Initializes a new instance of the class. + /// + /// The library manager. + /// The log manager. + public NewItemNotifier(ILibraryManager libraryManager, ILogManager logManager) + { + _logger = logManager.GetLogger("NewItemNotifier"); + _libraryManager = libraryManager; + } + + /// + /// Runs this instance. + /// + public void Run() + { + _libraryManager.LibraryChanged += libraryManager_LibraryChanged; + } + + /// + /// Handles the LibraryChanged event of the libraryManager control. + /// + /// The source of the event. + /// The instance containing the event data. + void libraryManager_LibraryChanged(object sender, ChildrenChangedEventArgs e) + { + var newItems = e.ItemsAdded.Where(i => !i.IsFolder).ToList(); + + // Use a timer to prevent lots of these notifications from showing in a short period of time + if (newItems.Count > 0) + { + lock (_newlyAddedItems) + { + _newlyAddedItems.AddRange(newItems); + + if (NewItemTimer == null) + { + NewItemTimer = new Timer(NewItemTimerCallback, null, NewItemDelay, Timeout.Infinite); + } + else + { + NewItemTimer.Change(NewItemDelay, Timeout.Infinite); + } + } + } + } + + /// + /// Called when the new item timer expires + /// + /// The state. + private void NewItemTimerCallback(object state) + { + List newItems; + + // Lock the list and release all resources + lock (_newlyAddedItems) + { + newItems = _newlyAddedItems.ToList(); + _newlyAddedItems.Clear(); + + NewItemTimer.Dispose(); + NewItemTimer = null; + } + + // Show the notification + if (newItems.Count == 1) + { + Application.Current.Dispatcher.InvokeAsync(() => + { + var window = (MainWindow)Application.Current.MainWindow; + + window.Dispatcher.InvokeAsync(() => window.MbTaskbarIcon.ShowCustomBalloon(new ItemUpdateNotification(_logger) + { + DataContext = newItems[0] + + }, PopupAnimation.Slide, 6000)); + }); + } + else if (newItems.Count > 1) + { + Application.Current.Dispatcher.InvokeAsync(() => + { + var window = (MainWindow)Application.Current.MainWindow; + + window.Dispatcher.InvokeAsync(() => window.MbTaskbarIcon.ShowCustomBalloon(new MultiItemUpdateNotification(_logger) + { + DataContext = newItems + + }, PopupAnimation.Slide, 6000)); + }); + } + } + + /// + /// Performs application-defined tasks associated with freeing, releasing, or resetting unmanaged resources. + /// + public void Dispose() + { + _libraryManager.LibraryChanged -= libraryManager_LibraryChanged; + } + } +} diff --git a/MediaBrowser.ServerApplication/EntryPoints/RefreshUsersMetadata.cs b/MediaBrowser.ServerApplication/EntryPoints/RefreshUsersMetadata.cs new file mode 100644 index 000000000..735a1f655 --- /dev/null +++ b/MediaBrowser.ServerApplication/EntryPoints/RefreshUsersMetadata.cs @@ -0,0 +1,41 @@ +using MediaBrowser.Controller.Library; +using MediaBrowser.Controller.Plugins; +using System.Threading; + +namespace MediaBrowser.ServerApplication.EntryPoints +{ + /// + /// Class RefreshUsersMetadata + /// + public class RefreshUsersMetadata : IServerEntryPoint + { + /// + /// The _user manager + /// + private readonly IUserManager _userManager; + + /// + /// Initializes a new instance of the class. + /// + /// The user manager. + public RefreshUsersMetadata(IUserManager userManager) + { + _userManager = userManager; + } + + /// + /// Runs this instance. + /// + public async void Run() + { + await _userManager.RefreshUsersMetadata(CancellationToken.None).ConfigureAwait(false); + } + + /// + /// Performs application-defined tasks associated with freeing, releasing, or resetting unmanaged resources. + /// + public void Dispose() + { + } + } +} diff --git a/MediaBrowser.ServerApplication/EntryPoints/StartupWizard.cs b/MediaBrowser.ServerApplication/EntryPoints/StartupWizard.cs new file mode 100644 index 000000000..43d9018a1 --- /dev/null +++ b/MediaBrowser.ServerApplication/EntryPoints/StartupWizard.cs @@ -0,0 +1,65 @@ +using MediaBrowser.Common; +using MediaBrowser.Controller.Configuration; +using MediaBrowser.Controller.Library; +using MediaBrowser.Controller.Plugins; +using System.Linq; + +namespace MediaBrowser.ServerApplication.EntryPoints +{ + /// + /// Class StartupWizard + /// + public class StartupWizard : IServerEntryPoint + { + /// + /// The _app host + /// + private readonly IApplicationHost _appHost; + /// + /// The _user manager + /// + private readonly IUserManager _userManager; + + private readonly IServerConfigurationManager _configurationManager; + + /// + /// Initializes a new instance of the class. + /// + /// The app host. + /// The user manager. + public StartupWizard(IApplicationHost appHost, IUserManager userManager, IServerConfigurationManager configurationManager) + { + _appHost = appHost; + _userManager = userManager; + _configurationManager = configurationManager; + } + + /// + /// Runs this instance. + /// + public void Run() + { + if (_appHost.IsFirstRun) + { + LaunchStartupWizard(); + } + } + + /// + /// Launches the startup wizard. + /// + private void LaunchStartupWizard() + { + var user = _userManager.Users.FirstOrDefault(u => u.Configuration.IsAdministrator); + + App.OpenDashboardPage("wizardStart.html", user, _configurationManager); + } + + /// + /// Performs application-defined tasks associated with freeing, releasing, or resetting unmanaged resources. + /// + public void Dispose() + { + } + } +} diff --git a/MediaBrowser.ServerApplication/EntryPoints/WebSocketEvents.cs b/MediaBrowser.ServerApplication/EntryPoints/WebSocketEvents.cs new file mode 100644 index 000000000..13359fced --- /dev/null +++ b/MediaBrowser.ServerApplication/EntryPoints/WebSocketEvents.cs @@ -0,0 +1,221 @@ +using MediaBrowser.Common.Events; +using MediaBrowser.Common.Net; +using MediaBrowser.Common.Plugins; +using MediaBrowser.Common.ScheduledTasks; +using MediaBrowser.Controller; +using MediaBrowser.Controller.Entities; +using MediaBrowser.Controller.Library; +using MediaBrowser.Controller.Plugins; +using MediaBrowser.Controller.Updates; +using MediaBrowser.Model.Logging; +using MediaBrowser.Model.Tasks; +using MediaBrowser.Model.Updates; +using System; + +namespace MediaBrowser.ServerApplication.EntryPoints +{ + /// + /// Class WebSocketEvents + /// + public class WebSocketEvents : IServerEntryPoint + { + /// + /// The _server manager + /// + private readonly IServerManager _serverManager; + /// + /// The _logger + /// + private readonly ILogger _logger; + + /// + /// The _user manager + /// + private readonly IUserManager _userManager; + + /// + /// The _library manager + /// + private readonly ILibraryManager _libraryManager; + + /// + /// The _installation manager + /// + private readonly IInstallationManager _installationManager; + + /// + /// The _kernel + /// + private readonly IServerApplicationHost _appHost; + + private readonly ITaskManager _taskManager; + + /// + /// Initializes a new instance of the class. + /// + /// The server manager. + /// The logger. + /// The user manager. + public WebSocketEvents(IServerManager serverManager, IServerApplicationHost appHost, ILogger logger, IUserManager userManager, ILibraryManager libraryManager, IInstallationManager installationManager, ITaskManager taskManager) + { + _serverManager = serverManager; + _logger = logger; + _userManager = userManager; + _libraryManager = libraryManager; + _installationManager = installationManager; + _appHost = appHost; + _taskManager = taskManager; + } + + public void Run() + { + _userManager.UserDeleted += userManager_UserDeleted; + _userManager.UserUpdated += userManager_UserUpdated; + + _libraryManager.LibraryChanged += libraryManager_LibraryChanged; + + _appHost.HasPendingRestartChanged += kernel_HasPendingRestartChanged; + + _installationManager.PluginUninstalled += InstallationManager_PluginUninstalled; + _installationManager.PackageInstalling += installationManager_PackageInstalling; + _installationManager.PackageInstallationCancelled += installationManager_PackageInstallationCancelled; + _installationManager.PackageInstallationCompleted += installationManager_PackageInstallationCompleted; + _installationManager.PackageInstallationFailed += installationManager_PackageInstallationFailed; + + _taskManager.TaskExecuting += _taskManager_TaskExecuting; + _taskManager.TaskCompleted += _taskManager_TaskCompleted; + } + + void _taskManager_TaskCompleted(object sender, GenericEventArgs e) + { + _serverManager.SendWebSocketMessage("ScheduledTaskEndExecute", e.Argument); + } + + void _taskManager_TaskExecuting(object sender, EventArgs e) + { + var task = (IScheduledTask) sender; + _serverManager.SendWebSocketMessage("ScheduledTaskBeginExecute", task.Name); + } + + /// + /// Installations the manager_ package installation failed. + /// + /// The sender. + /// The e. + void installationManager_PackageInstallationFailed(object sender, GenericEventArgs e) + { + _serverManager.SendWebSocketMessage("PackageInstallationFailed", e.Argument); + } + + /// + /// Installations the manager_ package installation completed. + /// + /// The sender. + /// The e. + void installationManager_PackageInstallationCompleted(object sender, GenericEventArgs e) + { + _serverManager.SendWebSocketMessage("PackageInstallationCompleted", e.Argument); + } + + /// + /// Installations the manager_ package installation cancelled. + /// + /// The sender. + /// The e. + void installationManager_PackageInstallationCancelled(object sender, GenericEventArgs e) + { + _serverManager.SendWebSocketMessage("PackageInstallationCancelled", e.Argument); + } + + /// + /// Installations the manager_ package installing. + /// + /// The sender. + /// The e. + void installationManager_PackageInstalling(object sender, GenericEventArgs e) + { + _serverManager.SendWebSocketMessage("PackageInstalling", e.Argument); + } + + /// + /// Handles the LibraryChanged event of the libraryManager control. + /// + /// The source of the event. + /// The instance containing the event data. + void libraryManager_LibraryChanged(object sender, ChildrenChangedEventArgs e) + { + _serverManager.SendWebSocketMessage("LibraryChanged", () => DtoBuilder.GetLibraryUpdateInfo(e)); + } + + /// + /// Installations the manager_ plugin uninstalled. + /// + /// The sender. + /// The e. + void InstallationManager_PluginUninstalled(object sender, GenericEventArgs e) + { + _serverManager.SendWebSocketMessage("PluginUninstalled", e.Argument.GetPluginInfo()); + } + + /// + /// Handles the HasPendingRestartChanged event of the kernel control. + /// + /// The source of the event. + /// The instance containing the event data. + void kernel_HasPendingRestartChanged(object sender, EventArgs e) + { + _serverManager.SendWebSocketMessage("HasPendingRestartChanged", _appHost.GetSystemInfo()); + } + + /// + /// Users the manager_ user updated. + /// + /// The sender. + /// The e. + void userManager_UserUpdated(object sender, GenericEventArgs e) + { + _serverManager.SendWebSocketMessage("UserUpdated", new DtoBuilder(_logger).GetDtoUser(e.Argument)); + } + + /// + /// Users the manager_ user deleted. + /// + /// The sender. + /// The e. + void userManager_UserDeleted(object sender, GenericEventArgs e) + { + _serverManager.SendWebSocketMessage("UserDeleted", e.Argument.Id.ToString()); + } + + /// + /// Performs application-defined tasks associated with freeing, releasing, or resetting unmanaged resources. + /// + public void Dispose() + { + Dispose(true); + } + + /// + /// 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) + { + _userManager.UserDeleted -= userManager_UserDeleted; + _userManager.UserUpdated -= userManager_UserUpdated; + + _libraryManager.LibraryChanged -= libraryManager_LibraryChanged; + + _installationManager.PluginUninstalled -= InstallationManager_PluginUninstalled; + _installationManager.PackageInstalling -= installationManager_PackageInstalling; + _installationManager.PackageInstallationCancelled -= installationManager_PackageInstallationCancelled; + _installationManager.PackageInstallationCompleted -= installationManager_PackageInstallationCompleted; + _installationManager.PackageInstallationFailed -= installationManager_PackageInstallationFailed; + + _appHost.HasPendingRestartChanged -= kernel_HasPendingRestartChanged; + } + } + } +} diff --git a/MediaBrowser.ServerApplication/MainWindow.xaml.cs b/MediaBrowser.ServerApplication/MainWindow.xaml.cs index 80f959b8b..364dd9da6 100644 --- a/MediaBrowser.ServerApplication/MainWindow.xaml.cs +++ b/MediaBrowser.ServerApplication/MainWindow.xaml.cs @@ -4,6 +4,7 @@ using MediaBrowser.Controller.Configuration; using MediaBrowser.Controller.Entities; using MediaBrowser.Controller.Library; using MediaBrowser.Model.Logging; +using MediaBrowser.Model.Serialization; using MediaBrowser.ServerApplication.Logging; using System; using System.ComponentModel; @@ -38,7 +39,11 @@ namespace MediaBrowser.ServerApplication /// The _configuration manager /// private readonly IServerConfigurationManager _configurationManager; - + + private readonly IUserManager _userManager; + private readonly ILibraryManager _libraryManager; + private readonly IJsonSerializer _jsonSerializer; + /// /// Initializes a new instance of the class. /// @@ -46,7 +51,7 @@ namespace MediaBrowser.ServerApplication /// The logger. /// The app host. /// logger - public MainWindow(ILogManager logManager, IApplicationHost appHost, IServerConfigurationManager configurationManager) + public MainWindow(ILogManager logManager, IApplicationHost appHost, IServerConfigurationManager configurationManager, IUserManager userManager, ILibraryManager libraryManager, IJsonSerializer jsonSerializer) { if (logManager == null) { @@ -65,6 +70,9 @@ namespace MediaBrowser.ServerApplication _appHost = appHost; _logManager = logManager; _configurationManager = configurationManager; + _userManager = userManager; + _libraryManager = libraryManager; + _jsonSerializer = jsonSerializer; InitializeComponent(); @@ -209,8 +217,7 @@ namespace MediaBrowser.ServerApplication /// The instance containing the event data. private void cmOpenExplorer_click(object sender, RoutedEventArgs e) { - var explorer = (LibraryExplorer)_appHost.CreateInstance(typeof(LibraryExplorer)); - explorer.Show(); + new LibraryExplorer(_jsonSerializer, _logger, _appHost, _userManager, _libraryManager).Show(); } /// @@ -220,7 +227,7 @@ namespace MediaBrowser.ServerApplication /// The instance containing the event data. private void cmOpenDashboard_click(object sender, RoutedEventArgs e) { - var user = _appHost.Resolve().Users.FirstOrDefault(u => u.Configuration.IsAdministrator); + var user = _userManager.Users.FirstOrDefault(u => u.Configuration.IsAdministrator); OpenDashboard(user); } @@ -249,7 +256,7 @@ namespace MediaBrowser.ServerApplication /// The instance containing the event data. private void cmdBrowseLibrary_click(object sender, RoutedEventArgs e) { - var user = _appHost.Resolve().Users.FirstOrDefault(u => u.Configuration.IsAdministrator); + var user = _userManager.Users.FirstOrDefault(u => u.Configuration.IsAdministrator); App.OpenDashboardPage("index.html", user, _configurationManager); } diff --git a/MediaBrowser.ServerApplication/MediaBrowser.ServerApplication.csproj b/MediaBrowser.ServerApplication/MediaBrowser.ServerApplication.csproj index deec2d67f..a67d61bfd 100644 --- a/MediaBrowser.ServerApplication/MediaBrowser.ServerApplication.csproj +++ b/MediaBrowser.ServerApplication/MediaBrowser.ServerApplication.csproj @@ -158,9 +158,10 @@ - - - + + + + MSBuild:Compile Designer @@ -403,9 +404,9 @@ del "$(SolutionDir)..\Deploy\MBServer.zip" --> - - - - + + + + \ No newline at end of file diff --git a/MediaBrowser.ServerApplication/NewItemNotifier.cs b/MediaBrowser.ServerApplication/NewItemNotifier.cs deleted file mode 100644 index 4864cef62..000000000 --- a/MediaBrowser.ServerApplication/NewItemNotifier.cs +++ /dev/null @@ -1,148 +0,0 @@ -using System.Windows; -using MediaBrowser.Controller.Entities; -using MediaBrowser.Controller.Library; -using MediaBrowser.Controller.Plugins; -using MediaBrowser.Model.Logging; -using MediaBrowser.ServerApplication.Controls; -using System.Collections.Generic; -using System.Linq; -using System.Threading; -using System.Windows.Controls.Primitives; - -namespace MediaBrowser.ServerApplication -{ - /// - /// Class NewItemNotifier - /// - public class NewItemNotifier : IServerEntryPoint - { - /// - /// Holds the list of new items to display when the NewItemTimer expires - /// - private readonly List _newlyAddedItems = new List(); - - /// - /// The amount of time to wait before showing a new item notification - /// This allows us to group items together into one notification - /// - private const int NewItemDelay = 60000; - - /// - /// The current new item timer - /// - /// The new item timer. - private Timer NewItemTimer { get; set; } - - /// - /// The _library manager - /// - private readonly ILibraryManager _libraryManager; - - /// - /// The _logger - /// - private readonly ILogger _logger; - - /// - /// Initializes a new instance of the class. - /// - /// The library manager. - /// The log manager. - public NewItemNotifier(ILibraryManager libraryManager, ILogManager logManager) - { - _logger = logManager.GetLogger("NewItemNotifier"); - _libraryManager = libraryManager; - } - - /// - /// Runs this instance. - /// - public void Run() - { - _libraryManager.LibraryChanged += libraryManager_LibraryChanged; - } - - /// - /// Handles the LibraryChanged event of the libraryManager control. - /// - /// The source of the event. - /// The instance containing the event data. - void libraryManager_LibraryChanged(object sender, ChildrenChangedEventArgs e) - { - var newItems = e.ItemsAdded.Where(i => !i.IsFolder).ToList(); - - // Use a timer to prevent lots of these notifications from showing in a short period of time - if (newItems.Count > 0) - { - lock (_newlyAddedItems) - { - _newlyAddedItems.AddRange(newItems); - - if (NewItemTimer == null) - { - NewItemTimer = new Timer(NewItemTimerCallback, null, NewItemDelay, Timeout.Infinite); - } - else - { - NewItemTimer.Change(NewItemDelay, Timeout.Infinite); - } - } - } - } - - /// - /// Called when the new item timer expires - /// - /// The state. - private void NewItemTimerCallback(object state) - { - List newItems; - - // Lock the list and release all resources - lock (_newlyAddedItems) - { - newItems = _newlyAddedItems.ToList(); - _newlyAddedItems.Clear(); - - NewItemTimer.Dispose(); - NewItemTimer = null; - } - - // Show the notification - if (newItems.Count == 1) - { - Application.Current.Dispatcher.InvokeAsync(() => - { - var window = (MainWindow)Application.Current.MainWindow; - - window.Dispatcher.InvokeAsync(() => window.MbTaskbarIcon.ShowCustomBalloon(new ItemUpdateNotification(_logger) - { - DataContext = newItems[0] - - }, PopupAnimation.Slide, 6000)); - }); - } - else if (newItems.Count > 1) - { - Application.Current.Dispatcher.InvokeAsync(() => - { - var window = (MainWindow)Application.Current.MainWindow; - - window.Dispatcher.InvokeAsync(() => window.MbTaskbarIcon.ShowCustomBalloon(new MultiItemUpdateNotification(_logger) - { - DataContext = newItems - - }, PopupAnimation.Slide, 6000)); - }); - } - } - - /// - /// Performs application-defined tasks associated with freeing, releasing, or resetting unmanaged resources. - /// - public void Dispose() - { - _libraryManager.LibraryChanged -= libraryManager_LibraryChanged; - } - } -} diff --git a/MediaBrowser.ServerApplication/StartupWizard.cs b/MediaBrowser.ServerApplication/StartupWizard.cs deleted file mode 100644 index f7946c711..000000000 --- a/MediaBrowser.ServerApplication/StartupWizard.cs +++ /dev/null @@ -1,65 +0,0 @@ -using MediaBrowser.Common; -using MediaBrowser.Controller.Configuration; -using MediaBrowser.Controller.Library; -using MediaBrowser.Controller.Plugins; -using System.Linq; - -namespace MediaBrowser.ServerApplication -{ - /// - /// Class StartupWizard - /// - public class StartupWizard : IServerEntryPoint - { - /// - /// The _app host - /// - private readonly IApplicationHost _appHost; - /// - /// The _user manager - /// - private readonly IUserManager _userManager; - - private readonly IServerConfigurationManager _configurationManager; - - /// - /// Initializes a new instance of the class. - /// - /// The app host. - /// The user manager. - public StartupWizard(IApplicationHost appHost, IUserManager userManager, IServerConfigurationManager configurationManager) - { - _appHost = appHost; - _userManager = userManager; - _configurationManager = configurationManager; - } - - /// - /// Runs this instance. - /// - public void Run() - { - if (_appHost.IsFirstRun) - { - LaunchStartupWizard(); - } - } - - /// - /// Launches the startup wizard. - /// - private void LaunchStartupWizard() - { - var user = _userManager.Users.FirstOrDefault(u => u.Configuration.IsAdministrator); - - App.OpenDashboardPage("wizardStart.html", user, _configurationManager); - } - - /// - /// Performs application-defined tasks associated with freeing, releasing, or resetting unmanaged resources. - /// - public void Dispose() - { - } - } -} diff --git a/MediaBrowser.ServerApplication/WebSocketEvents.cs b/MediaBrowser.ServerApplication/WebSocketEvents.cs deleted file mode 100644 index 43972bafc..000000000 --- a/MediaBrowser.ServerApplication/WebSocketEvents.cs +++ /dev/null @@ -1,221 +0,0 @@ -using MediaBrowser.Common.Events; -using MediaBrowser.Common.Net; -using MediaBrowser.Common.Plugins; -using MediaBrowser.Common.ScheduledTasks; -using MediaBrowser.Controller; -using MediaBrowser.Controller.Entities; -using MediaBrowser.Controller.Library; -using MediaBrowser.Controller.Plugins; -using MediaBrowser.Controller.Updates; -using MediaBrowser.Model.Logging; -using MediaBrowser.Model.Tasks; -using MediaBrowser.Model.Updates; -using System; - -namespace MediaBrowser.ServerApplication -{ - /// - /// Class WebSocketEvents - /// - public class WebSocketEvents : IServerEntryPoint - { - /// - /// The _server manager - /// - private readonly IServerManager _serverManager; - /// - /// The _logger - /// - private readonly ILogger _logger; - - /// - /// The _user manager - /// - private readonly IUserManager _userManager; - - /// - /// The _library manager - /// - private readonly ILibraryManager _libraryManager; - - /// - /// The _installation manager - /// - private readonly IInstallationManager _installationManager; - - /// - /// The _kernel - /// - private readonly IServerApplicationHost _appHost; - - private readonly ITaskManager _taskManager; - - /// - /// Initializes a new instance of the class. - /// - /// The server manager. - /// The logger. - /// The user manager. - public WebSocketEvents(IServerManager serverManager, IServerApplicationHost appHost, ILogger logger, IUserManager userManager, ILibraryManager libraryManager, IInstallationManager installationManager, ITaskManager taskManager) - { - _serverManager = serverManager; - _logger = logger; - _userManager = userManager; - _libraryManager = libraryManager; - _installationManager = installationManager; - _appHost = appHost; - _taskManager = taskManager; - } - - public void Run() - { - _userManager.UserDeleted += userManager_UserDeleted; - _userManager.UserUpdated += userManager_UserUpdated; - - _libraryManager.LibraryChanged += libraryManager_LibraryChanged; - - _appHost.HasPendingRestartChanged += kernel_HasPendingRestartChanged; - - _installationManager.PluginUninstalled += InstallationManager_PluginUninstalled; - _installationManager.PackageInstalling += installationManager_PackageInstalling; - _installationManager.PackageInstallationCancelled += installationManager_PackageInstallationCancelled; - _installationManager.PackageInstallationCompleted += installationManager_PackageInstallationCompleted; - _installationManager.PackageInstallationFailed += installationManager_PackageInstallationFailed; - - _taskManager.TaskExecuting += _taskManager_TaskExecuting; - _taskManager.TaskCompleted += _taskManager_TaskCompleted; - } - - void _taskManager_TaskCompleted(object sender, GenericEventArgs e) - { - _serverManager.SendWebSocketMessage("ScheduledTaskEndExecute", e.Argument); - } - - void _taskManager_TaskExecuting(object sender, EventArgs e) - { - var task = (IScheduledTask) sender; - _serverManager.SendWebSocketMessage("ScheduledTaskBeginExecute", task.Name); - } - - /// - /// Installations the manager_ package installation failed. - /// - /// The sender. - /// The e. - void installationManager_PackageInstallationFailed(object sender, GenericEventArgs e) - { - _serverManager.SendWebSocketMessage("PackageInstallationFailed", e.Argument); - } - - /// - /// Installations the manager_ package installation completed. - /// - /// The sender. - /// The e. - void installationManager_PackageInstallationCompleted(object sender, GenericEventArgs e) - { - _serverManager.SendWebSocketMessage("PackageInstallationCompleted", e.Argument); - } - - /// - /// Installations the manager_ package installation cancelled. - /// - /// The sender. - /// The e. - void installationManager_PackageInstallationCancelled(object sender, GenericEventArgs e) - { - _serverManager.SendWebSocketMessage("PackageInstallationCancelled", e.Argument); - } - - /// - /// Installations the manager_ package installing. - /// - /// The sender. - /// The e. - void installationManager_PackageInstalling(object sender, GenericEventArgs e) - { - _serverManager.SendWebSocketMessage("PackageInstalling", e.Argument); - } - - /// - /// Handles the LibraryChanged event of the libraryManager control. - /// - /// The source of the event. - /// The instance containing the event data. - void libraryManager_LibraryChanged(object sender, ChildrenChangedEventArgs e) - { - _serverManager.SendWebSocketMessage("LibraryChanged", () => DtoBuilder.GetLibraryUpdateInfo(e)); - } - - /// - /// Installations the manager_ plugin uninstalled. - /// - /// The sender. - /// The e. - void InstallationManager_PluginUninstalled(object sender, GenericEventArgs e) - { - _serverManager.SendWebSocketMessage("PluginUninstalled", e.Argument.GetPluginInfo()); - } - - /// - /// Handles the HasPendingRestartChanged event of the kernel control. - /// - /// The source of the event. - /// The instance containing the event data. - void kernel_HasPendingRestartChanged(object sender, EventArgs e) - { - _serverManager.SendWebSocketMessage("HasPendingRestartChanged", _appHost.GetSystemInfo()); - } - - /// - /// Users the manager_ user updated. - /// - /// The sender. - /// The e. - void userManager_UserUpdated(object sender, GenericEventArgs e) - { - _serverManager.SendWebSocketMessage("UserUpdated", new DtoBuilder(_logger).GetDtoUser(e.Argument)); - } - - /// - /// Users the manager_ user deleted. - /// - /// The sender. - /// The e. - void userManager_UserDeleted(object sender, GenericEventArgs e) - { - _serverManager.SendWebSocketMessage("UserDeleted", e.Argument.Id.ToString()); - } - - /// - /// Performs application-defined tasks associated with freeing, releasing, or resetting unmanaged resources. - /// - public void Dispose() - { - Dispose(true); - } - - /// - /// 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) - { - _userManager.UserDeleted -= userManager_UserDeleted; - _userManager.UserUpdated -= userManager_UserUpdated; - - _libraryManager.LibraryChanged -= libraryManager_LibraryChanged; - - _installationManager.PluginUninstalled -= InstallationManager_PluginUninstalled; - _installationManager.PackageInstalling -= installationManager_PackageInstalling; - _installationManager.PackageInstallationCancelled -= installationManager_PackageInstallationCancelled; - _installationManager.PackageInstallationCompleted -= installationManager_PackageInstallationCompleted; - _installationManager.PackageInstallationFailed -= installationManager_PackageInstallationFailed; - - _appHost.HasPendingRestartChanged -= kernel_HasPendingRestartChanged; - } - } - } -} diff --git a/MediaBrowser.sln b/MediaBrowser.sln index 16caa617d..c13ad6f81 100644 --- a/MediaBrowser.sln +++ b/MediaBrowser.sln @@ -237,4 +237,7 @@ Global GlobalSection(SolutionProperties) = preSolution HideSolutionNode = FALSE EndGlobalSection + GlobalSection(Performance) = preSolution + HasPerformanceSessions = true + EndGlobalSection EndGlobal -- cgit v1.2.3