diff options
| author | Eric Reed <ebr@mediabrowser3.com> | 2014-01-29 12:22:53 -0500 |
|---|---|---|
| committer | Eric Reed <ebr@mediabrowser3.com> | 2014-01-29 12:22:53 -0500 |
| commit | ab5145bcd794ee579e0064c9c0f9d15b13f72fb4 (patch) | |
| tree | d68cdabe4302cc4dda8194ee840060f48d9fa714 /MediaBrowser.Server.Implementations | |
| parent | 1f31c8dbfca9ca9134d9ee779256c435f913689b (diff) | |
| parent | c0f606683a045e463f518ec466b9fc9a85f8d4fd (diff) | |
Merge branch 'master' of https://github.com/MediaBrowser/MediaBrowser
Diffstat (limited to 'MediaBrowser.Server.Implementations')
21 files changed, 428 insertions, 1447 deletions
diff --git a/MediaBrowser.Server.Implementations/Drawing/ImageProcessor.cs b/MediaBrowser.Server.Implementations/Drawing/ImageProcessor.cs index 6378cef52..06a03ba1c 100644 --- a/MediaBrowser.Server.Implementations/Drawing/ImageProcessor.cs +++ b/MediaBrowser.Server.Implementations/Drawing/ImageProcessor.cs @@ -388,18 +388,18 @@ namespace MediaBrowser.Server.Implementations.Drawing /// <param name="image">The image.</param> /// <param name="outputFormat">The output format.</param> /// <returns>ImageFormat.</returns> - private ImageFormat GetOutputFormat(Image image, ImageOutputFormat outputFormat) + private System.Drawing.Imaging.ImageFormat GetOutputFormat(Image image, ImageOutputFormat outputFormat) { switch (outputFormat) { case ImageOutputFormat.Bmp: - return ImageFormat.Bmp; + return System.Drawing.Imaging.ImageFormat.Bmp; case ImageOutputFormat.Gif: - return ImageFormat.Gif; + return System.Drawing.Imaging.ImageFormat.Gif; case ImageOutputFormat.Jpg: - return ImageFormat.Jpeg; + return System.Drawing.Imaging.ImageFormat.Jpeg; case ImageOutputFormat.Png: - return ImageFormat.Png; + return System.Drawing.Imaging.ImageFormat.Png; default: return image.RawFormat; } @@ -787,7 +787,7 @@ namespace MediaBrowser.Server.Implementations.Drawing //And then save it in the cache using (var outputStream = _fileSystem.GetFileStream(enhancedImagePath, FileMode.Create, FileAccess.Write, FileShare.Read, false)) { - newImage.Save(ImageFormat.Png, outputStream, 100); + newImage.Save(System.Drawing.Imaging.ImageFormat.Png, outputStream, 100); } } } diff --git a/MediaBrowser.Server.Implementations/Dto/DtoService.cs b/MediaBrowser.Server.Implementations/Dto/DtoService.cs index 7a9735e0e..b427b0c45 100644 --- a/MediaBrowser.Server.Implementations/Dto/DtoService.cs +++ b/MediaBrowser.Server.Implementations/Dto/DtoService.cs @@ -1024,6 +1024,11 @@ namespace MediaBrowser.Server.Implementations.Dto { dto.SpecialFeatureCount = specialFeatureCount; } + + if (fields.Contains(ItemFields.TmdbCollectionName)) + { + dto.TmdbCollectionName = movie.TmdbCollectionName; + } } // Add EpisodeInfo diff --git a/MediaBrowser.Server.Implementations/FileOrganization/EpisodeFileOrganizer.cs b/MediaBrowser.Server.Implementations/FileOrganization/EpisodeFileOrganizer.cs index ece21df7a..a2e094e9a 100644 --- a/MediaBrowser.Server.Implementations/FileOrganization/EpisodeFileOrganizer.cs +++ b/MediaBrowser.Server.Implementations/FileOrganization/EpisodeFileOrganizer.cs @@ -22,7 +22,7 @@ namespace MediaBrowser.Server.Implementations.FileOrganization { public class EpisodeFileOrganizer { - private readonly IDirectoryWatchers _directoryWatchers; + private readonly ILibraryMonitor _libraryMonitor; private readonly ILibraryManager _libraryManager; private readonly ILogger _logger; private readonly IFileSystem _fileSystem; @@ -31,14 +31,14 @@ namespace MediaBrowser.Server.Implementations.FileOrganization private readonly CultureInfo _usCulture = new CultureInfo("en-US"); - public EpisodeFileOrganizer(IFileOrganizationService organizationService, IServerConfigurationManager config, IFileSystem fileSystem, ILogger logger, ILibraryManager libraryManager, IDirectoryWatchers directoryWatchers) + public EpisodeFileOrganizer(IFileOrganizationService organizationService, IServerConfigurationManager config, IFileSystem fileSystem, ILogger logger, ILibraryManager libraryManager, ILibraryMonitor libraryMonitor) { _organizationService = organizationService; _config = config; _fileSystem = fileSystem; _logger = logger; _libraryManager = libraryManager; - _directoryWatchers = directoryWatchers; + _libraryMonitor = libraryMonitor; } public async Task<FileOrganizationResult> OrganizeEpisodeFile(string path, TvFileOrganizationOptions options, bool overwriteExisting) @@ -174,6 +174,8 @@ namespace MediaBrowser.Server.Implementations.FileOrganization { _logger.Debug("Removing duplicate episode {0}", path); + _libraryMonitor.ReportFileSystemChangeBeginning(path); + try { File.Delete(path); @@ -182,6 +184,10 @@ namespace MediaBrowser.Server.Implementations.FileOrganization { _logger.ErrorException("Error removing duplicate episode", ex, path); } + finally + { + _libraryMonitor.ReportFileSystemChangeComplete(path, true); + } } } } @@ -232,7 +238,7 @@ namespace MediaBrowser.Server.Implementations.FileOrganization private void PerformFileSorting(TvFileOrganizationOptions options, FileOrganizationResult result) { - _directoryWatchers.TemporarilyIgnore(result.TargetPath); + _libraryMonitor.ReportFileSystemChangeBeginning(result.TargetPath); Directory.CreateDirectory(Path.GetDirectoryName(result.TargetPath)); @@ -264,7 +270,7 @@ namespace MediaBrowser.Server.Implementations.FileOrganization } finally { - _directoryWatchers.RemoveTempIgnore(result.TargetPath); + _libraryMonitor.ReportFileSystemChangeComplete(result.TargetPath, true); } if (copy) @@ -376,8 +382,8 @@ namespace MediaBrowser.Server.Implementations.FileOrganization private string GetEpisodeFileName(string sourcePath, string seriesName, int seasonNumber, int episodeNumber, int? endingEpisodeNumber, string episodeTitle, TvFileOrganizationOptions options) { - seriesName = _fileSystem.GetValidFilename(seriesName); - episodeTitle = _fileSystem.GetValidFilename(episodeTitle); + seriesName = _fileSystem.GetValidFilename(seriesName).Trim(); + episodeTitle = _fileSystem.GetValidFilename(episodeTitle).Trim(); var sourceExtension = (Path.GetExtension(sourcePath) ?? string.Empty).TrimStart('.'); diff --git a/MediaBrowser.Server.Implementations/FileOrganization/FileOrganizationService.cs b/MediaBrowser.Server.Implementations/FileOrganization/FileOrganizationService.cs index bbd0f74e5..518a7bb48 100644 --- a/MediaBrowser.Server.Implementations/FileOrganization/FileOrganizationService.cs +++ b/MediaBrowser.Server.Implementations/FileOrganization/FileOrganizationService.cs @@ -21,17 +21,17 @@ namespace MediaBrowser.Server.Implementations.FileOrganization private readonly ITaskManager _taskManager; private readonly IFileOrganizationRepository _repo; private readonly ILogger _logger; - private readonly IDirectoryWatchers _directoryWatchers; + private readonly ILibraryMonitor _libraryMonitor; private readonly ILibraryManager _libraryManager; private readonly IServerConfigurationManager _config; private readonly IFileSystem _fileSystem; - public FileOrganizationService(ITaskManager taskManager, IFileOrganizationRepository repo, ILogger logger, IDirectoryWatchers directoryWatchers, ILibraryManager libraryManager, IServerConfigurationManager config, IFileSystem fileSystem) + public FileOrganizationService(ITaskManager taskManager, IFileOrganizationRepository repo, ILogger logger, ILibraryMonitor libraryMonitor, ILibraryManager libraryManager, IServerConfigurationManager config, IFileSystem fileSystem) { _taskManager = taskManager; _repo = repo; _logger = logger; - _directoryWatchers = directoryWatchers; + _libraryMonitor = libraryMonitor; _libraryManager = libraryManager; _config = config; _fileSystem = fileSystem; @@ -91,13 +91,10 @@ namespace MediaBrowser.Server.Implementations.FileOrganization } var organizer = new EpisodeFileOrganizer(this, _config, _fileSystem, _logger, _libraryManager, - _directoryWatchers); + _libraryMonitor); await organizer.OrganizeEpisodeFile(result.OriginalPath, _config.Configuration.TvFileOrganizationOptions, true) .ConfigureAwait(false); - - await _libraryManager.ValidateMediaLibrary(new Progress<double>(), CancellationToken.None) - .ConfigureAwait(false); } public Task ClearLog() @@ -108,12 +105,9 @@ namespace MediaBrowser.Server.Implementations.FileOrganization public async Task PerformEpisodeOrganization(EpisodeFileOrganizationRequest request) { var organizer = new EpisodeFileOrganizer(this, _config, _fileSystem, _logger, _libraryManager, - _directoryWatchers); + _libraryMonitor); await organizer.OrganizeWithCorrection(request, _config.Configuration.TvFileOrganizationOptions).ConfigureAwait(false); - - await _libraryManager.ValidateMediaLibrary(new Progress<double>(), CancellationToken.None) - .ConfigureAwait(false); } } } diff --git a/MediaBrowser.Server.Implementations/FileOrganization/OrganizerScheduledTask.cs b/MediaBrowser.Server.Implementations/FileOrganization/OrganizerScheduledTask.cs index 340038e4b..3c5e1ed0e 100644 --- a/MediaBrowser.Server.Implementations/FileOrganization/OrganizerScheduledTask.cs +++ b/MediaBrowser.Server.Implementations/FileOrganization/OrganizerScheduledTask.cs @@ -14,16 +14,16 @@ namespace MediaBrowser.Server.Implementations.FileOrganization { public class OrganizerScheduledTask : IScheduledTask, IConfigurableScheduledTask { - private readonly IDirectoryWatchers _directoryWatchers; + private readonly ILibraryMonitor _libraryMonitor; private readonly ILibraryManager _libraryManager; private readonly ILogger _logger; private readonly IFileSystem _fileSystem; private readonly IServerConfigurationManager _config; private readonly IFileOrganizationService _organizationService; - public OrganizerScheduledTask(IDirectoryWatchers directoryWatchers, ILibraryManager libraryManager, ILogger logger, IFileSystem fileSystem, IServerConfigurationManager config, IFileOrganizationService organizationService) + public OrganizerScheduledTask(ILibraryMonitor libraryMonitor, ILibraryManager libraryManager, ILogger logger, IFileSystem fileSystem, IServerConfigurationManager config, IFileOrganizationService organizationService) { - _directoryWatchers = directoryWatchers; + _libraryMonitor = libraryMonitor; _libraryManager = libraryManager; _logger = logger; _fileSystem = fileSystem; @@ -48,7 +48,7 @@ namespace MediaBrowser.Server.Implementations.FileOrganization public Task Execute(CancellationToken cancellationToken, IProgress<double> progress) { - return new TvFolderOrganizer(_libraryManager, _logger, _fileSystem, _directoryWatchers, _organizationService, _config) + return new TvFolderOrganizer(_libraryManager, _logger, _fileSystem, _libraryMonitor, _organizationService, _config) .Organize(_config.Configuration.TvFileOrganizationOptions, cancellationToken, progress); } diff --git a/MediaBrowser.Server.Implementations/FileOrganization/TvFolderOrganizer.cs b/MediaBrowser.Server.Implementations/FileOrganization/TvFolderOrganizer.cs index 6a413f2f0..24f21e339 100644 --- a/MediaBrowser.Server.Implementations/FileOrganization/TvFolderOrganizer.cs +++ b/MediaBrowser.Server.Implementations/FileOrganization/TvFolderOrganizer.cs @@ -18,19 +18,19 @@ namespace MediaBrowser.Server.Implementations.FileOrganization { public class TvFolderOrganizer { - private readonly IDirectoryWatchers _directoryWatchers; + private readonly ILibraryMonitor _libraryMonitor; private readonly ILibraryManager _libraryManager; private readonly ILogger _logger; private readonly IFileSystem _fileSystem; private readonly IFileOrganizationService _organizationService; private readonly IServerConfigurationManager _config; - public TvFolderOrganizer(ILibraryManager libraryManager, ILogger logger, IFileSystem fileSystem, IDirectoryWatchers directoryWatchers, IFileOrganizationService organizationService, IServerConfigurationManager config) + public TvFolderOrganizer(ILibraryManager libraryManager, ILogger logger, IFileSystem fileSystem, ILibraryMonitor libraryMonitor, IFileOrganizationService organizationService, IServerConfigurationManager config) { _libraryManager = libraryManager; _logger = logger; _fileSystem = fileSystem; - _directoryWatchers = directoryWatchers; + _libraryMonitor = libraryMonitor; _organizationService = organizationService; _config = config; } @@ -57,7 +57,7 @@ namespace MediaBrowser.Server.Implementations.FileOrganization foreach (var file in eligibleFiles) { var organizer = new EpisodeFileOrganizer(_organizationService, _config, _fileSystem, _logger, _libraryManager, - _directoryWatchers); + _libraryMonitor); var result = await organizer.OrganizeEpisodeFile(file.FullName, options, false).ConfigureAwait(false); diff --git a/MediaBrowser.Server.Implementations/IO/DirectoryWatchers.cs b/MediaBrowser.Server.Implementations/IO/LibraryMonitor.cs index 1efc3bc70..0716a3d83 100644 --- a/MediaBrowser.Server.Implementations/IO/DirectoryWatchers.cs +++ b/MediaBrowser.Server.Implementations/IO/LibraryMonitor.cs @@ -2,7 +2,6 @@ using MediaBrowser.Common.ScheduledTasks; using MediaBrowser.Controller.Configuration; using MediaBrowser.Controller.Entities; -using MediaBrowser.Controller.IO; using MediaBrowser.Controller.Library; using MediaBrowser.Model.Entities; using MediaBrowser.Model.Logging; @@ -18,10 +17,7 @@ using System.Threading.Tasks; namespace MediaBrowser.Server.Implementations.IO { - /// <summary> - /// Class DirectoryWatchers - /// </summary> - public class DirectoryWatchers : IDirectoryWatchers + public class LibraryMonitor : ILibraryMonitor { /// <summary> /// The file system watchers @@ -55,17 +51,28 @@ namespace MediaBrowser.Server.Implementations.IO /// Add the path to our temporary ignore list. Use when writing to a path within our listening scope. /// </summary> /// <param name="path">The path.</param> - public void TemporarilyIgnore(string path) + private void TemporarilyIgnore(string path) { _tempIgnoredPaths[path] = path; } - /// <summary> - /// Removes the temp ignore. - /// </summary> - /// <param name="path">The path.</param> - public async void RemoveTempIgnore(string path) + public void ReportFileSystemChangeBeginning(string path) + { + if (string.IsNullOrEmpty(path)) + { + throw new ArgumentNullException("path"); + } + + TemporarilyIgnore(path); + } + + public async void ReportFileSystemChangeComplete(string path, bool refreshPath) { + if (string.IsNullOrEmpty(path)) + { + throw new ArgumentNullException("path"); + } + // This is an arbitraty amount of time, but delay it because file system writes often trigger events after RemoveTempIgnore has been called. // Seeing long delays in some situations, especially over the network. // Seeing delays up to 40 seconds, but not going to ignore changes for that long. @@ -73,6 +80,11 @@ namespace MediaBrowser.Server.Implementations.IO string val; _tempIgnoredPaths.TryRemove(path, out val); + + if (refreshPath) + { + ReportFileSystemChanged(path); + } } /// <summary> @@ -91,11 +103,11 @@ namespace MediaBrowser.Server.Implementations.IO private IServerConfigurationManager ConfigurationManager { get; set; } private readonly IFileSystem _fileSystem; - + /// <summary> - /// Initializes a new instance of the <see cref="DirectoryWatchers" /> class. + /// Initializes a new instance of the <see cref="LibraryMonitor" /> class. /// </summary> - public DirectoryWatchers(ILogManager logManager, ITaskManager taskManager, ILibraryManager libraryManager, IServerConfigurationManager configurationManager, IFileSystem fileSystem) + public LibraryMonitor(ILogManager logManager, ITaskManager taskManager, ILibraryManager libraryManager, IServerConfigurationManager configurationManager, IFileSystem fileSystem) { if (taskManager == null) { @@ -104,7 +116,7 @@ namespace MediaBrowser.Server.Implementations.IO LibraryManager = libraryManager; TaskManager = taskManager; - Logger = logManager.GetLogger("DirectoryWatchers"); + Logger = logManager.GetLogger(GetType().Name); ConfigurationManager = configurationManager; _fileSystem = fileSystem; @@ -328,31 +340,30 @@ namespace MediaBrowser.Server.Implementations.IO { OnWatcherChanged(e); } - catch (IOException ex) + catch (Exception ex) { - Logger.ErrorException("IOException in watcher changed. Path: {0}", ex, e.FullPath); + Logger.ErrorException("Exception in watcher changed. Path: {0}", ex, e.FullPath); } } private void OnWatcherChanged(FileSystemEventArgs e) { - var name = e.Name; + Logger.Debug("Watcher sees change of type " + e.ChangeType + " to " + e.FullPath); - // Ignore certain files - if (_alwaysIgnoreFiles.Contains(name, StringComparer.OrdinalIgnoreCase)) - { - return; - } + ReportFileSystemChanged(e.FullPath); + } - var nameFromFullPath = Path.GetFileName(e.FullPath); - // Ignore certain files - if (!string.IsNullOrEmpty(nameFromFullPath) && _alwaysIgnoreFiles.Contains(nameFromFullPath, StringComparer.OrdinalIgnoreCase)) + public void ReportFileSystemChanged(string path) + { + if (string.IsNullOrEmpty(path)) { - return; + throw new ArgumentNullException("path"); } + + var filename = Path.GetFileName(path); - // Ignore when someone manually creates a new folder - if (e.ChangeType == WatcherChangeTypes.Created && name == "New folder") + // Ignore certain files + if (!string.IsNullOrEmpty(filename) && _alwaysIgnoreFiles.Contains(filename, StringComparer.OrdinalIgnoreCase)) { return; } @@ -362,36 +373,35 @@ namespace MediaBrowser.Server.Implementations.IO // If the parent of an ignored path has a change event, ignore that too if (tempIgnorePaths.Any(i => { - if (string.Equals(i, e.FullPath, StringComparison.OrdinalIgnoreCase)) + if (string.Equals(i, path, StringComparison.OrdinalIgnoreCase)) { - Logger.Debug("Watcher ignoring change to {0}", e.FullPath); + Logger.Debug("Ignoring change to {0}", path); return true; } - // Go up a level - var parent = Path.GetDirectoryName(i); - if (string.Equals(parent, e.FullPath, StringComparison.OrdinalIgnoreCase)) + if (_fileSystem.ContainsSubPath(i, path)) { - Logger.Debug("Watcher ignoring change to {0}", e.FullPath); + Logger.Debug("Ignoring change to {0}", path); return true; } - // Go up another level + // Go up a level + var parent = Path.GetDirectoryName(i); if (!string.IsNullOrEmpty(parent)) { - parent = Path.GetDirectoryName(i); - if (string.Equals(parent, e.FullPath, StringComparison.OrdinalIgnoreCase)) + if (string.Equals(parent, path, StringComparison.OrdinalIgnoreCase)) { - Logger.Debug("Watcher ignoring change to {0}", e.FullPath); + Logger.Debug("Ignoring change to {0}", path); return true; } - } - if (i.StartsWith(e.FullPath, StringComparison.OrdinalIgnoreCase) || - e.FullPath.StartsWith(i, StringComparison.OrdinalIgnoreCase)) - { - Logger.Debug("Watcher ignoring change to {0}", e.FullPath); - return true; + // Go up another level + parent = Path.GetDirectoryName(i); + if (string.Equals(parent, path, StringComparison.OrdinalIgnoreCase)) + { + Logger.Debug("Ignoring change to {0}", path); + return true; + } } return false; @@ -401,22 +411,19 @@ namespace MediaBrowser.Server.Implementations.IO return; } - Logger.Info("Watcher sees change of type " + e.ChangeType + " to " + e.FullPath); - - //Since we're watching created, deleted and renamed we always want the parent of the item to be the affected path - var affectedPath = e.FullPath; - - _affectedPaths.AddOrUpdate(affectedPath, affectedPath, (key, oldValue) => affectedPath); + // Avoid implicitly captured closure + var affectedPath = path; + _affectedPaths.AddOrUpdate(path, path, (key, oldValue) => affectedPath); lock (_timerLock) { if (_updateTimer == null) { - _updateTimer = new Timer(TimerStopped, null, TimeSpan.FromSeconds(ConfigurationManager.Configuration.FileWatcherDelay), TimeSpan.FromMilliseconds(-1)); + _updateTimer = new Timer(TimerStopped, null, TimeSpan.FromSeconds(ConfigurationManager.Configuration.RealtimeWatcherDelay), TimeSpan.FromMilliseconds(-1)); } else { - _updateTimer.Change(TimeSpan.FromSeconds(ConfigurationManager.Configuration.FileWatcherDelay), TimeSpan.FromMilliseconds(-1)); + _updateTimer.Change(TimeSpan.FromSeconds(ConfigurationManager.Configuration.RealtimeWatcherDelay), TimeSpan.FromMilliseconds(-1)); } } } @@ -427,24 +434,9 @@ namespace MediaBrowser.Server.Implementations.IO /// <param name="stateInfo">The state info.</param> 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."); + Logger.Debug("Timer stopped."); - if (_updateTimer != null) - { - _updateTimer.Dispose(); - _updateTimer = null; - } - } + DisposeTimer(); var paths = _affectedPaths.Keys.ToList(); _affectedPaths.Clear(); @@ -452,59 +444,16 @@ namespace MediaBrowser.Server.Implementations.IO await ProcessPathChanges(paths).ConfigureAwait(false); } - /// <summary> - /// 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. - /// </summary> - /// <param name="path">The path.</param> - /// <returns><c>true</c> if [is file locked] [the specified path]; otherwise, <c>false</c>.</returns> - private bool IsFileLocked(string path) + private void DisposeTimer() { - try - { - var data = _fileSystem.GetFileSystemInfo(path); - - if (!data.Exists - || data.Attributes.HasFlag(FileAttributes.Directory) - || data.Attributes.HasFlag(FileAttributes.ReadOnly)) - { - return false; - } - } - catch (IOException) - { - return false; - } - - try + lock (_timerLock) { - using (_fileSystem.GetFileStream(path, FileMode.Open, FileAccess.ReadWrite, FileShare.ReadWrite)) + if (_updateTimer != null) { - //file is not locked - return false; + _updateTimer.Dispose(); + _updateTimer = null; } } - catch (DirectoryNotFoundException) - { - return false; - } - catch (FileNotFoundException) - { - return false; - } - catch (IOException) - { - //the file is unavailable because it is: - //still being written to - //or being processed by another thread - //or does not exist (has already been processed) - Logger.Debug("{0} is locked.", path); - return true; - } - catch - { - return false; - } } /// <summary> @@ -599,14 +548,7 @@ namespace MediaBrowser.Server.Implementations.IO watcher.Dispose(); } - lock (_timerLock) - { - if (_updateTimer != null) - { - _updateTimer.Dispose(); - _updateTimer = null; - } - } + DisposeTimer(); _fileSystemWatchers.Clear(); _affectedPaths.Clear(); diff --git a/MediaBrowser.Server.Implementations/Library/LibraryManager.cs b/MediaBrowser.Server.Implementations/Library/LibraryManager.cs index 736c70ad5..17b5ea424 100644 --- a/MediaBrowser.Server.Implementations/Library/LibraryManager.cs +++ b/MediaBrowser.Server.Implementations/Library/LibraryManager.cs @@ -137,7 +137,7 @@ namespace MediaBrowser.Server.Implementations.Library private IEnumerable<IMetadataSaver> _savers; - private readonly Func<IDirectoryWatchers> _directoryWatchersFactory; + private readonly Func<ILibraryMonitor> _libraryMonitorFactory; /// <summary> /// The _library items cache @@ -180,14 +180,14 @@ namespace MediaBrowser.Server.Implementations.Library /// <param name="userManager">The user manager.</param> /// <param name="configurationManager">The configuration manager.</param> /// <param name="userDataRepository">The user data repository.</param> - public LibraryManager(ILogger logger, ITaskManager taskManager, IUserManager userManager, IServerConfigurationManager configurationManager, IUserDataManager userDataRepository, Func<IDirectoryWatchers> directoryWatchersFactory, IFileSystem fileSystem) + public LibraryManager(ILogger logger, ITaskManager taskManager, IUserManager userManager, IServerConfigurationManager configurationManager, IUserDataManager userDataRepository, Func<ILibraryMonitor> libraryMonitorFactory, IFileSystem fileSystem) { _logger = logger; _taskManager = taskManager; _userManager = userManager; ConfigurationManager = configurationManager; _userDataRepository = userDataRepository; - _directoryWatchersFactory = directoryWatchersFactory; + _libraryMonitorFactory = libraryMonitorFactory; _fileSystem = fileSystem; ByReferenceItems = new ConcurrentDictionary<Guid, BaseItem>(); @@ -934,7 +934,7 @@ namespace MediaBrowser.Server.Implementations.Library /// <returns>Task.</returns> public async Task ValidateMediaLibraryInternal(IProgress<double> progress, CancellationToken cancellationToken) { - _directoryWatchersFactory().Stop(); + _libraryMonitorFactory().Stop(); try { @@ -942,7 +942,7 @@ namespace MediaBrowser.Server.Implementations.Library } finally { - _directoryWatchersFactory().Start(); + _libraryMonitorFactory().Start(); } } @@ -1462,13 +1462,13 @@ namespace MediaBrowser.Server.Implementations.Library var semaphore = _fileLocks.GetOrAdd(path, key => new SemaphoreSlim(1, 1)); - var directoryWatchers = _directoryWatchersFactory(); + var libraryMonitor = _libraryMonitorFactory(); await semaphore.WaitAsync().ConfigureAwait(false); try { - directoryWatchers.TemporarilyIgnore(path); + libraryMonitor.ReportFileSystemChangeBeginning(path); saver.Save(item, CancellationToken.None); } catch (Exception ex) @@ -1477,7 +1477,7 @@ namespace MediaBrowser.Server.Implementations.Library } finally { - directoryWatchers.RemoveTempIgnore(path); + libraryMonitor.ReportFileSystemChangeComplete(path, false); semaphore.Release(); } } diff --git a/MediaBrowser.Server.Implementations/Library/ResolverHelper.cs b/MediaBrowser.Server.Implementations/Library/ResolverHelper.cs index e32fcd627..4ce5f11d4 100644 --- a/MediaBrowser.Server.Implementations/Library/ResolverHelper.cs +++ b/MediaBrowser.Server.Implementations/Library/ResolverHelper.cs @@ -46,7 +46,7 @@ namespace MediaBrowser.Server.Implementations.Library } // Make sure the item has a name - EnsureName(item); + EnsureName(item, args); item.DontFetchMeta = item.Path.IndexOf("[dontfetchmeta]", StringComparison.OrdinalIgnoreCase) != -1 || item.Parents.Any(i => i.DontFetchMeta); @@ -59,13 +59,13 @@ namespace MediaBrowser.Server.Implementations.Library /// Ensures the name. /// </summary> /// <param name="item">The item.</param> - private static void EnsureName(BaseItem item) + private static void EnsureName(BaseItem item, ItemResolveArgs args) { // If the subclass didn't supply a name, add it here if (string.IsNullOrEmpty(item.Name) && !string.IsNullOrEmpty(item.Path)) { //we use our resolve args name here to get the name of the containg folder, not actual video file - item.Name = GetMBName(item.ResolveArgs.FileInfo.Name, (item.ResolveArgs.FileInfo.Attributes & FileAttributes.Directory) == FileAttributes.Directory); + item.Name = GetMBName(args.FileInfo.Name, (args.FileInfo.Attributes & FileAttributes.Directory) == FileAttributes.Directory); } } diff --git a/MediaBrowser.Server.Implementations/Library/UserManager.cs b/MediaBrowser.Server.Implementations/Library/UserManager.cs index d4a74f2b6..ce76dd21b 100644 --- a/MediaBrowser.Server.Implementations/Library/UserManager.cs +++ b/MediaBrowser.Server.Implementations/Library/UserManager.cs @@ -4,6 +4,7 @@ using MediaBrowser.Controller.Configuration; using MediaBrowser.Controller.Entities; using MediaBrowser.Controller.Library; using MediaBrowser.Controller.Persistence; +using MediaBrowser.Controller.Providers; using MediaBrowser.Model.Logging; using System; using System.Collections.Generic; @@ -192,7 +193,11 @@ namespace MediaBrowser.Server.Implementations.Library /// <returns>Task.</returns> public Task RefreshUsersMetadata(CancellationToken cancellationToken, bool force = false) { - var tasks = Users.Select(user => user.RefreshMetadata(cancellationToken, forceRefresh: force)).ToList(); + var tasks = Users.Select(user => user.RefreshMetadata(new MetadataRefreshOptions + { + ReplaceAllMetadata = force + + }, cancellationToken)).ToList(); return Task.WhenAll(tasks); } diff --git a/MediaBrowser.Server.Implementations/Library/Validators/GenresPostScanTask.cs b/MediaBrowser.Server.Implementations/Library/Validators/GenresPostScanTask.cs index b4907a70c..d7add8574 100644 --- a/MediaBrowser.Server.Implementations/Library/Validators/GenresPostScanTask.cs +++ b/MediaBrowser.Server.Implementations/Library/Validators/GenresPostScanTask.cs @@ -16,7 +16,6 @@ namespace MediaBrowser.Server.Implementations.Library.Validators /// Initializes a new instance of the <see cref="ArtistsPostScanTask" /> class. /// </summary> /// <param name="libraryManager">The library manager.</param> - /// <param name="userManager">The user manager.</param> public GenresPostScanTask(ILibraryManager libraryManager) { _libraryManager = libraryManager; diff --git a/MediaBrowser.Server.Implementations/Library/Validators/PeoplePostScanTask.cs b/MediaBrowser.Server.Implementations/Library/Validators/PeoplePostScanTask.cs index 0104b2b7e..c8094302c 100644 --- a/MediaBrowser.Server.Implementations/Library/Validators/PeoplePostScanTask.cs +++ b/MediaBrowser.Server.Implementations/Library/Validators/PeoplePostScanTask.cs @@ -1,5 +1,6 @@ using MediaBrowser.Controller.Entities; using MediaBrowser.Controller.Library; +using MediaBrowser.Controller.Providers; using MediaBrowser.Model.Logging; using System; using System.Collections.Generic; @@ -88,7 +89,14 @@ namespace MediaBrowser.Server.Implementations.Library.Validators var itemByName = _libraryManager.GetPerson(name); - await itemByName.RefreshMetadata(cancellationToken, allowSlowProviders: false).ConfigureAwait(false); + // The only purpose here is to be able to react to image changes without running the people task. + // All other metadata can wait for that. + await itemByName.RefreshMetadata(new MetadataRefreshOptions + { + ImageRefreshMode = MetadataRefreshMode.None, + MetadataRefreshMode = MetadataRefreshMode.None + + }, cancellationToken).ConfigureAwait(false); foreach (var libraryId in counts.Keys) { diff --git a/MediaBrowser.Server.Implementations/LiveTv/ChannelImageProvider.cs b/MediaBrowser.Server.Implementations/LiveTv/ChannelImageProvider.cs index f1e10e175..9fdca568e 100644 --- a/MediaBrowser.Server.Implementations/LiveTv/ChannelImageProvider.cs +++ b/MediaBrowser.Server.Implementations/LiveTv/ChannelImageProvider.cs @@ -1,154 +1,111 @@ -using MediaBrowser.Common.IO; -using MediaBrowser.Common.Net; -using MediaBrowser.Controller.Configuration; +using MediaBrowser.Common.Net; using MediaBrowser.Controller.Entities; -using MediaBrowser.Controller.Library; using MediaBrowser.Controller.LiveTv; using MediaBrowser.Controller.Providers; using MediaBrowser.Model.Entities; using MediaBrowser.Model.Logging; -using MediaBrowser.Model.Net; using System; -using System.IO; +using System.Collections.Generic; using System.Linq; -using System.Net; using System.Threading; using System.Threading.Tasks; namespace MediaBrowser.Server.Implementations.LiveTv { - public class ChannelImageProvider : BaseMetadataProvider + public class ChannelImageProvider : IDynamicImageProvider, IHasChangeMonitor { private readonly ILiveTvManager _liveTvManager; - private readonly IProviderManager _providerManager; - private readonly IFileSystem _fileSystem; private readonly IHttpClient _httpClient; + private readonly ILogger _logger; - public ChannelImageProvider(ILogManager logManager, IServerConfigurationManager configurationManager, ILiveTvManager liveTvManager, IProviderManager providerManager, IFileSystem fileSystem, IHttpClient httpClient) - : base(logManager, configurationManager) + public ChannelImageProvider(ILiveTvManager liveTvManager, IHttpClient httpClient, ILogger logger) { _liveTvManager = liveTvManager; - _providerManager = providerManager; - _fileSystem = fileSystem; _httpClient = httpClient; + _logger = logger; } - public override bool Supports(BaseItem item) + public IEnumerable<ImageType> GetSupportedImages(IHasImages item) { - return item is LiveTvChannel; - } - - protected override bool NeedsRefreshInternal(BaseItem item, BaseProviderInfo providerInfo) - { - return !item.HasImage(ImageType.Primary); + return new[] { ImageType.Primary }; } - public override async Task<bool> FetchAsync(BaseItem item, bool force, BaseProviderInfo providerInfo, CancellationToken cancellationToken) + public async Task<DynamicImageResponse> GetImage(IHasImages item, ImageType type, CancellationToken cancellationToken) { - if (item.HasImage(ImageType.Primary)) - { - SetLastRefreshed(item, DateTime.UtcNow, providerInfo); - return true; - } - - var changed = true; - - try - { - changed = await DownloadImage((LiveTvChannel)item, cancellationToken).ConfigureAwait(false); - } - catch (HttpException ex) - { - // Don't fail the provider on a 404 - if (!ex.StatusCode.HasValue || ex.StatusCode.Value != HttpStatusCode.NotFound) - { - throw; - } - } - - if (changed) - { - SetLastRefreshed(item, DateTime.UtcNow, providerInfo); - } - - return changed; - } + var liveTvItem = (LiveTvChannel)item; - private async Task<bool> DownloadImage(LiveTvChannel item, CancellationToken cancellationToken) - { - Stream imageStream = null; - string contentType = null; + var imageResponse = new DynamicImageResponse(); - if (!string.IsNullOrEmpty(item.ProviderImagePath)) + if (!string.IsNullOrEmpty(liveTvItem.ProviderImagePath)) { - contentType = "image/" + Path.GetExtension(item.ProviderImagePath).ToLower(); - imageStream = _fileSystem.GetFileStream(item.ProviderImagePath, FileMode.Open, FileAccess.Read, FileShare.Read, true); + imageResponse.Path = liveTvItem.ProviderImagePath; + imageResponse.HasImage = true; } - else if (!string.IsNullOrEmpty(item.ProviderImageUrl)) + else if (!string.IsNullOrEmpty(liveTvItem.ProviderImageUrl)) { var options = new HttpRequestOptions { CancellationToken = cancellationToken, - Url = item.ProviderImageUrl + Url = liveTvItem.ProviderImageUrl }; var response = await _httpClient.GetResponse(options).ConfigureAwait(false); - if (!response.ContentType.StartsWith("image/", StringComparison.OrdinalIgnoreCase)) + if (response.ContentType.StartsWith("image/", StringComparison.OrdinalIgnoreCase)) { - Logger.Error("Provider did not return an image content type."); - return false; + imageResponse.HasImage = true; + imageResponse.Stream = response.Content; + imageResponse.SetFormatFromMimeType(response.ContentType); + } + else + { + _logger.Error("Provider did not return an image content type."); } - - imageStream = response.Content; - contentType = response.ContentType; } - else if (item.HasProviderImage ?? true) + else if (liveTvItem.HasProviderImage ?? true) { - var service = _liveTvManager.Services.FirstOrDefault(i => string.Equals(i.Name, item.ServiceName, StringComparison.OrdinalIgnoreCase)); + var service = _liveTvManager.Services.FirstOrDefault(i => string.Equals(i.Name, liveTvItem.ServiceName, StringComparison.OrdinalIgnoreCase)); if (service != null) { try { - var response = await service.GetChannelImageAsync(item.ExternalId, cancellationToken).ConfigureAwait(false); + var response = await service.GetChannelImageAsync(liveTvItem.ExternalId, cancellationToken).ConfigureAwait(false); if (response != null) { - imageStream = response.Stream; - contentType = response.MimeType; + imageResponse.HasImage = true; + imageResponse.Stream = response.Stream; + imageResponse.Format = response.Format; } } catch (NotImplementedException) { - return false; } } } - if (imageStream != null) - { - // Dummy up the original url - var url = item.ServiceName + item.ExternalId; + return imageResponse; + } - await _providerManager.SaveImage(item, imageStream, contentType, ImageType.Primary, null, url, cancellationToken).ConfigureAwait(false); - return true; - } + public string Name + { + get { return "Live TV Service Provider"; } + } - return false; + public bool Supports(IHasImages item) + { + return item is LiveTvChannel; } - public override MetadataProviderPriority Priority + public int Order { - get { return MetadataProviderPriority.Second; } + get { return 0; } } - public override ItemUpdateType ItemUpdateType + public bool HasChanged(IHasMetadata item, DateTime date) { - get - { - return ItemUpdateType.ImageUpdate; - } + return !item.HasImage(ImageType.Primary) && (DateTime.UtcNow - date).TotalDays >= 1; } } } diff --git a/MediaBrowser.Server.Implementations/LiveTv/LiveTvManager.cs b/MediaBrowser.Server.Implementations/LiveTv/LiveTvManager.cs index e256d7da5..9501d2d12 100644 --- a/MediaBrowser.Server.Implementations/LiveTv/LiveTvManager.cs +++ b/MediaBrowser.Server.Implementations/LiveTv/LiveTvManager.cs @@ -9,6 +9,7 @@ using MediaBrowser.Controller.Library; using MediaBrowser.Controller.LiveTv; using MediaBrowser.Controller.MediaInfo; using MediaBrowser.Controller.Persistence; +using MediaBrowser.Controller.Providers; using MediaBrowser.Controller.Sorting; using MediaBrowser.Model.Entities; using MediaBrowser.Model.LiveTv; @@ -328,7 +329,12 @@ namespace MediaBrowser.Server.Implementations.LiveTv // Set this now so we don't cause additional file system access during provider executions item.ResetResolveArgs(fileInfo); - await item.RefreshMetadata(cancellationToken, forceSave: isNew, resetResolveArgs: false); + await item.RefreshMetadata(new MetadataRefreshOptions + { + ForceSave = isNew, + ResetResolveArgs = false + + }, cancellationToken); return item; } @@ -383,7 +389,12 @@ namespace MediaBrowser.Server.Implementations.LiveTv item.RunTimeTicks = (info.EndDate - info.StartDate).Ticks; item.StartDate = info.StartDate; - await item.RefreshMetadata(cancellationToken, forceSave: isNew, resetResolveArgs: false); + await item.RefreshMetadata(new MetadataRefreshOptions + { + ForceSave = isNew, + ResetResolveArgs = false + + }, cancellationToken); return item; } @@ -435,7 +446,12 @@ namespace MediaBrowser.Server.Implementations.LiveTv item.RecordingInfo = info; item.ServiceName = serviceName; - await item.RefreshMetadata(cancellationToken, forceSave: isNew, resetResolveArgs: false); + await item.RefreshMetadata(new MetadataRefreshOptions + { + ForceSave = isNew, + ResetResolveArgs = false + + }, cancellationToken); _libraryManager.RegisterItem((BaseItem)item); diff --git a/MediaBrowser.Server.Implementations/LiveTv/ProgramImageProvider.cs b/MediaBrowser.Server.Implementations/LiveTv/ProgramImageProvider.cs index 041925cdd..117cb1da7 100644 --- a/MediaBrowser.Server.Implementations/LiveTv/ProgramImageProvider.cs +++ b/MediaBrowser.Server.Implementations/LiveTv/ProgramImageProvider.cs @@ -1,154 +1,111 @@ -using MediaBrowser.Common.IO; -using MediaBrowser.Common.Net; -using MediaBrowser.Controller.Configuration; +using MediaBrowser.Common.Net; using MediaBrowser.Controller.Entities; -using MediaBrowser.Controller.Library; using MediaBrowser.Controller.LiveTv; using MediaBrowser.Controller.Providers; using MediaBrowser.Model.Entities; using MediaBrowser.Model.Logging; -using MediaBrowser.Model.Net; using System; -using System.IO; +using System.Collections.Generic; using System.Linq; -using System.Net; using System.Threading; using System.Threading.Tasks; namespace MediaBrowser.Server.Implementations.LiveTv { - public class ProgramImageProvider : BaseMetadataProvider + public class ProgramImageProvider : IDynamicImageProvider, IHasChangeMonitor { private readonly ILiveTvManager _liveTvManager; - private readonly IProviderManager _providerManager; - private readonly IFileSystem _fileSystem; private readonly IHttpClient _httpClient; + private readonly ILogger _logger; - public ProgramImageProvider(ILogManager logManager, IServerConfigurationManager configurationManager, ILiveTvManager liveTvManager, IProviderManager providerManager, IFileSystem fileSystem, IHttpClient httpClient) - : base(logManager, configurationManager) + public ProgramImageProvider(ILiveTvManager liveTvManager, IHttpClient httpClient, ILogger logger) { _liveTvManager = liveTvManager; - _providerManager = providerManager; - _fileSystem = fileSystem; _httpClient = httpClient; + _logger = logger; } - public override bool Supports(BaseItem item) + public IEnumerable<ImageType> GetSupportedImages(IHasImages item) { - return item is LiveTvProgram; - } - - protected override bool NeedsRefreshInternal(BaseItem item, BaseProviderInfo providerInfo) - { - return !item.HasImage(ImageType.Primary); + return new[] { ImageType.Primary }; } - public override async Task<bool> FetchAsync(BaseItem item, bool force, BaseProviderInfo providerInfo, CancellationToken cancellationToken) + public async Task<DynamicImageResponse> GetImage(IHasImages item, ImageType type, CancellationToken cancellationToken) { - if (item.HasImage(ImageType.Primary)) - { - SetLastRefreshed(item, DateTime.UtcNow, providerInfo); - return true; - } - - var changed = true; - - try - { - changed = await DownloadImage((LiveTvProgram)item, cancellationToken).ConfigureAwait(false); - } - catch (HttpException ex) - { - // Don't fail the provider on a 404 - if (!ex.StatusCode.HasValue || ex.StatusCode.Value != HttpStatusCode.NotFound) - { - throw; - } - } - - if (changed) - { - SetLastRefreshed(item, DateTime.UtcNow, providerInfo); - } - - return changed; - } + var liveTvItem = (LiveTvProgram)item; - private async Task<bool> DownloadImage(LiveTvProgram item, CancellationToken cancellationToken) - { - Stream imageStream = null; - string contentType = null; + var imageResponse = new DynamicImageResponse(); - if (!string.IsNullOrEmpty(item.ProviderImagePath)) + if (!string.IsNullOrEmpty(liveTvItem.ProviderImagePath)) { - contentType = "image/" + Path.GetExtension(item.ProviderImagePath).ToLower(); - imageStream = _fileSystem.GetFileStream(item.ProviderImagePath, FileMode.Open, FileAccess.Read, FileShare.Read, true); + imageResponse.Path = liveTvItem.ProviderImagePath; + imageResponse.HasImage = true; } - else if (!string.IsNullOrEmpty(item.ProviderImageUrl)) + else if (!string.IsNullOrEmpty(liveTvItem.ProviderImageUrl)) { var options = new HttpRequestOptions { CancellationToken = cancellationToken, - Url = item.ProviderImageUrl + Url = liveTvItem.ProviderImageUrl }; var response = await _httpClient.GetResponse(options).ConfigureAwait(false); - if (!response.ContentType.StartsWith("image/", StringComparison.OrdinalIgnoreCase)) + if (response.ContentType.StartsWith("image/", StringComparison.OrdinalIgnoreCase)) { - Logger.Error("Provider did not return an image content type."); - return false; + imageResponse.HasImage = true; + imageResponse.Stream = response.Content; + imageResponse.SetFormatFromMimeType(response.ContentType); + } + else + { + _logger.Error("Provider did not return an image content type."); } - - imageStream = response.Content; - contentType = response.ContentType; } - else if (item.HasProviderImage ?? true) + else if (liveTvItem.HasProviderImage ?? true) { - var service = _liveTvManager.Services.FirstOrDefault(i => string.Equals(i.Name, item.ServiceName, StringComparison.OrdinalIgnoreCase)); + var service = _liveTvManager.Services.FirstOrDefault(i => string.Equals(i.Name, liveTvItem.ServiceName, StringComparison.OrdinalIgnoreCase)); if (service != null) { try { - var response = await service.GetProgramImageAsync(item.ExternalId, item.ExternalChannelId, cancellationToken).ConfigureAwait(false); + var response = await service.GetProgramImageAsync(liveTvItem.ExternalId, liveTvItem.ExternalChannelId, cancellationToken).ConfigureAwait(false); if (response != null) { - imageStream = response.Stream; - contentType = response.MimeType; + imageResponse.HasImage = true; + imageResponse.Stream = response.Stream; + imageResponse.Format = response.Format; } } catch (NotImplementedException) { - return false; } } } - if (imageStream != null) - { - // Dummy up the original url - var url = item.ServiceName + item.ExternalId; + return imageResponse; + } - await _providerManager.SaveImage(item, imageStream, contentType, ImageType.Primary, null, url, cancellationToken).ConfigureAwait(false); - return true; - } + public string Name + { + get { return "Live TV Service Provider"; } + } - return false; + public bool Supports(IHasImages item) + { + return item is LiveTvProgram; } - public override MetadataProviderPriority Priority + public int Order { - get { return MetadataProviderPriority.Second; } + get { return 0; } } - public override ItemUpdateType ItemUpdateType + public bool HasChanged(IHasMetadata item, DateTime date) { - get - { - return ItemUpdateType.ImageUpdate; - } + return !item.HasImage(ImageType.Primary) && (DateTime.UtcNow - date).TotalHours >= 12; } } } diff --git a/MediaBrowser.Server.Implementations/LiveTv/RecordingImageProvider.cs b/MediaBrowser.Server.Implementations/LiveTv/RecordingImageProvider.cs index 9f6ab85a4..ce7c1286b 100644 --- a/MediaBrowser.Server.Implementations/LiveTv/RecordingImageProvider.cs +++ b/MediaBrowser.Server.Implementations/LiveTv/RecordingImageProvider.cs @@ -118,7 +118,7 @@ namespace MediaBrowser.Server.Implementations.LiveTv if (response != null) { imageStream = response.Stream; - contentType = response.MimeType; + contentType = "image/" + response.Format.ToString().ToLower(); } } catch (NotImplementedException) diff --git a/MediaBrowser.Server.Implementations/MediaBrowser.Server.Implementations.csproj b/MediaBrowser.Server.Implementations/MediaBrowser.Server.Implementations.csproj index 314e7a458..fe4283368 100644 --- a/MediaBrowser.Server.Implementations/MediaBrowser.Server.Implementations.csproj +++ b/MediaBrowser.Server.Implementations/MediaBrowser.Server.Implementations.csproj @@ -137,7 +137,7 @@ <Compile Include="HttpServer\StreamWriter.cs" /> <Compile Include="HttpServer\SwaggerService.cs" /> <Compile Include="Drawing\ImageProcessor.cs" /> - <Compile Include="IO\DirectoryWatchers.cs" /> + <Compile Include="IO\LibraryMonitor.cs" /> <Compile Include="Library\CoreResolutionIgnoreRule.cs" /> <Compile Include="Library\LibraryManager.cs" /> <Compile Include="Library\SearchEngine.cs" /> @@ -189,8 +189,6 @@ <Compile Include="Persistence\SqliteShrinkMemoryTimer.cs" /> <Compile Include="Persistence\TypeMapper.cs" /> <Compile Include="Properties\AssemblyInfo.cs" /> - <Compile Include="Providers\ImageSaver.cs" /> - <Compile Include="Providers\ProviderManager.cs" /> <Compile Include="Roku\RokuControllerFactory.cs" /> <Compile Include="ScheduledTasks\PeopleValidationTask.cs" /> <Compile Include="ScheduledTasks\ChapterImagesTask.cs" /> diff --git a/MediaBrowser.Server.Implementations/Persistence/SqliteItemRepository.cs b/MediaBrowser.Server.Implementations/Persistence/SqliteItemRepository.cs index 200898a62..6b463bbdf 100644 --- a/MediaBrowser.Server.Implementations/Persistence/SqliteItemRepository.cs +++ b/MediaBrowser.Server.Implementations/Persistence/SqliteItemRepository.cs @@ -58,7 +58,6 @@ namespace MediaBrowser.Server.Implementations.Persistence private SqliteChapterRepository _chapterRepository; private SqliteMediaStreamsRepository _mediaStreamsRepository; - private SqliteProviderInfoRepository _providerInfoRepository; private IDbCommand _deleteChildrenCommand; private IDbCommand _saveChildrenCommand; @@ -99,10 +98,6 @@ namespace MediaBrowser.Server.Implementations.Persistence var mediaStreamsDbFile = Path.Combine(_appPaths.DataPath, "mediainfo.db"); var mediaStreamsConnection = SqliteExtensions.ConnectToDb(mediaStreamsDbFile, _logger).Result; _mediaStreamsRepository = new SqliteMediaStreamsRepository(mediaStreamsConnection, logManager); - - var providerInfosDbFile = Path.Combine(_appPaths.DataPath, "providerinfo.db"); - var providerInfoConnection = SqliteExtensions.ConnectToDb(providerInfosDbFile, _logger).Result; - _providerInfoRepository = new SqliteProviderInfoRepository(providerInfoConnection, logManager); } /// <summary> @@ -134,7 +129,6 @@ namespace MediaBrowser.Server.Implementations.Persistence PrepareStatements(); _mediaStreamsRepository.Initialize(); - _providerInfoRepository.Initialize(); _chapterRepository.Initialize(); _shrinkMemoryTimer = new SqliteShrinkMemoryTimer(_connection, _writeLock, _logger); @@ -436,12 +430,6 @@ namespace MediaBrowser.Server.Implementations.Persistence _mediaStreamsRepository.Dispose(); _mediaStreamsRepository = null; } - - if (_providerInfoRepository != null) - { - _providerInfoRepository.Dispose(); - _providerInfoRepository = null; - } } } catch (Exception ex) @@ -556,15 +544,5 @@ namespace MediaBrowser.Server.Implementations.Persistence { return _mediaStreamsRepository.SaveMediaStreams(id, streams, cancellationToken); } - - public IEnumerable<BaseProviderInfo> GetProviderHistory(Guid itemId) - { - return _providerInfoRepository.GetBaseProviderInfos(itemId); - } - - public Task SaveProviderHistory(Guid id, IEnumerable<BaseProviderInfo> history, CancellationToken cancellationToken) - { - return _providerInfoRepository.SaveProviderInfos(id, history, cancellationToken); - } } }
\ No newline at end of file diff --git a/MediaBrowser.Server.Implementations/Persistence/SqliteProviderInfoRepository.cs b/MediaBrowser.Server.Implementations/Persistence/SqliteProviderInfoRepository.cs index 9971c7460..8a82c062d 100644 --- a/MediaBrowser.Server.Implementations/Persistence/SqliteProviderInfoRepository.cs +++ b/MediaBrowser.Server.Implementations/Persistence/SqliteProviderInfoRepository.cs @@ -1,4 +1,6 @@ -using MediaBrowser.Controller.Providers; +using System.IO; +using MediaBrowser.Common.Configuration; +using MediaBrowser.Controller.Providers; using MediaBrowser.Model.Logging; using System; using System.Collections.Generic; @@ -9,7 +11,7 @@ using System.Threading.Tasks; namespace MediaBrowser.Server.Implementations.Persistence { - class SqliteProviderInfoRepository + public class SqliteProviderInfoRepository : IProviderRepository { private IDbConnection _connection; @@ -17,32 +19,47 @@ namespace MediaBrowser.Server.Implementations.Persistence private IDbCommand _deleteInfosCommand; private IDbCommand _saveInfoCommand; + private IDbCommand _saveStatusCommand; + private readonly IApplicationPaths _appPaths; - public SqliteProviderInfoRepository(IDbConnection connection, ILogManager logManager) + public SqliteProviderInfoRepository(IApplicationPaths appPaths, ILogManager logManager) { - _connection = connection; - + _appPaths = appPaths; _logger = logManager.GetLogger(GetType().Name); } private SqliteShrinkMemoryTimer _shrinkMemoryTimer; - + + /// <summary> + /// Gets the name of the repository + /// </summary> + /// <value>The name.</value> + public string Name + { + get + { + return "SQLite"; + } + } + /// <summary> /// Opens the connection to the database /// </summary> /// <returns>Task.</returns> - public void Initialize() + public async Task Initialize() { - var createTableCommand - = "create table if not exists providerinfos "; + var dbFile = Path.Combine(_appPaths.DataPath, "providerinfo.db"); - createTableCommand += "(ItemId GUID, ProviderId GUID, ProviderVersion TEXT, FileStamp GUID, LastRefreshStatus TEXT, LastRefreshed datetime, PRIMARY KEY (ItemId, ProviderId))"; + _connection = await SqliteExtensions.ConnectToDb(dbFile, _logger).ConfigureAwait(false); string[] queries = { - createTableCommand, + "create table if not exists providerinfos (ItemId GUID, ProviderId GUID, ProviderVersion TEXT, FileStamp GUID, LastRefreshStatus TEXT, LastRefreshed datetime, PRIMARY KEY (ItemId, ProviderId))", "create index if not exists idx_providerinfos on providerinfos(ItemId, ProviderId)", + "create table if not exists MetadataStatus (ItemId GUID PRIMARY KEY, DateLastMetadataRefresh datetime, DateLastImagesRefresh datetime, LastStatus TEXT, LastErrorMessage TEXT, MetadataProvidersRefreshed TEXT, ImageProvidersRefreshed TEXT)", + "create index if not exists idx_MetadataStatus on MetadataStatus(ItemId)", + //pragmas "pragma temp_store = memory", @@ -56,7 +73,7 @@ namespace MediaBrowser.Server.Implementations.Persistence _shrinkMemoryTimer = new SqliteShrinkMemoryTimer(_connection, _writeLock, _logger); } - private static readonly string[] SaveColumns = + private static readonly string[] SaveHistoryColumns = { "ItemId", "ProviderId", @@ -66,7 +83,18 @@ namespace MediaBrowser.Server.Implementations.Persistence "LastRefreshed" }; - private readonly string[] _selectColumns = SaveColumns.Skip(1).ToArray(); + private readonly string[] _historySelectColumns = SaveHistoryColumns.Skip(1).ToArray(); + + private static readonly string[] StatusColumns = + { + "ItemId", + "DateLastMetadataRefresh", + "DateLastImagesRefresh", + "LastStatus", + "LastErrorMessage", + "MetadataProvidersRefreshed", + "ImageProvidersRefreshed" + }; /// <summary> /// The _write lock @@ -85,16 +113,27 @@ namespace MediaBrowser.Server.Implementations.Persistence _saveInfoCommand = _connection.CreateCommand(); _saveInfoCommand.CommandText = string.Format("replace into providerinfos ({0}) values ({1})", - string.Join(",", SaveColumns), - string.Join(",", SaveColumns.Select(i => "@" + i).ToArray())); + string.Join(",", SaveHistoryColumns), + string.Join(",", SaveHistoryColumns.Select(i => "@" + i).ToArray())); - foreach (var col in SaveColumns) + foreach (var col in SaveHistoryColumns) { _saveInfoCommand.Parameters.Add(_saveInfoCommand, "@" + col); } + + _saveStatusCommand = _connection.CreateCommand(); + + _saveStatusCommand.CommandText = string.Format("replace into MetadataStatus ({0}) values ({1})", + string.Join(",", StatusColumns), + string.Join(",", StatusColumns.Select(i => "@" + i).ToArray())); + + foreach (var col in StatusColumns) + { + _saveStatusCommand.Parameters.Add(_saveStatusCommand, "@" + col); + } } - public IEnumerable<BaseProviderInfo> GetBaseProviderInfos(Guid itemId) + public IEnumerable<BaseProviderInfo> GetProviderHistory(Guid itemId) { if (itemId == Guid.Empty) { @@ -103,7 +142,7 @@ namespace MediaBrowser.Server.Implementations.Persistence using (var cmd = _connection.CreateCommand()) { - var cmdText = "select " + string.Join(",", _selectColumns) + " from providerinfos where"; + var cmdText = "select " + string.Join(",", _historySelectColumns) + " from providerinfos where"; cmdText += " ItemId=@ItemId"; cmd.Parameters.Add(cmd, "@ItemId", DbType.Guid).Value = itemId; @@ -121,10 +160,10 @@ namespace MediaBrowser.Server.Implementations.Persistence } /// <summary> - /// Gets the chapter. + /// Gets the base provider information. /// </summary> /// <param name="reader">The reader.</param> - /// <returns>ChapterInfo.</returns> + /// <returns>BaseProviderInfo.</returns> private BaseProviderInfo GetBaseProviderInfo(IDataReader reader) { var item = new BaseProviderInfo @@ -144,7 +183,7 @@ namespace MediaBrowser.Server.Implementations.Persistence return item; } - public async Task SaveProviderInfos(Guid id, IEnumerable<BaseProviderInfo> infos, CancellationToken cancellationToken) + public async Task SaveProviderHistory(Guid id, IEnumerable<BaseProviderInfo> infos, CancellationToken cancellationToken) { if (id == Guid.Empty) { @@ -166,7 +205,6 @@ namespace MediaBrowser.Server.Implementations.Persistence { transaction = _connection.BeginTransaction(); - // First delete chapters _deleteInfosCommand.GetParameter(0).Value = id; _deleteInfosCommand.Transaction = transaction; @@ -221,6 +259,136 @@ namespace MediaBrowser.Server.Implementations.Persistence } } + public MetadataStatus GetMetadataStatus(Guid itemId) + { + if (itemId == Guid.Empty) + { + throw new ArgumentNullException("itemId"); + } + + using (var cmd = _connection.CreateCommand()) + { + var cmdText = "select " + string.Join(",", StatusColumns) + " from MetadataStatus where"; + + cmdText += " ItemId=@ItemId"; + cmd.Parameters.Add(cmd, "@ItemId", DbType.Guid).Value = itemId; + + cmd.CommandText = cmdText; + + using (var reader = cmd.ExecuteReader(CommandBehavior.SequentialAccess | CommandBehavior.SingleResult | CommandBehavior.SingleRow)) + { + while (reader.Read()) + { + return GetStatus(reader); + } + + return null; + } + } + } + + private MetadataStatus GetStatus(IDataReader reader) + { + var result = new MetadataStatus + { + ItemId = reader.GetGuid(0) + }; + + if (!reader.IsDBNull(1)) + { + result.DateLastMetadataRefresh = reader.GetDateTime(1).ToUniversalTime(); + } + + if (!reader.IsDBNull(2)) + { + result.DateLastImagesRefresh = reader.GetDateTime(2).ToUniversalTime(); + } + + if (!reader.IsDBNull(3)) + { + result.LastStatus = (ProviderRefreshStatus)Enum.Parse(typeof(ProviderRefreshStatus), reader.GetString(3), true); + } + + if (!reader.IsDBNull(4)) + { + result.LastErrorMessage = reader.GetString(4); + } + + if (!reader.IsDBNull(5)) + { + result.MetadataProvidersRefreshed = reader.GetString(5).Split('|').Where(i => !string.IsNullOrEmpty(i)).Select(i => new Guid(i)).ToList(); + } + + if (!reader.IsDBNull(6)) + { + result.ImageProvidersRefreshed = reader.GetString(6).Split('|').Where(i => !string.IsNullOrEmpty(i)).Select(i => new Guid(i)).ToList(); + } + + return result; + } + + public async Task SaveMetadataStatus(MetadataStatus status, CancellationToken cancellationToken) + { + if (status == null) + { + throw new ArgumentNullException("status"); + } + + cancellationToken.ThrowIfCancellationRequested(); + + await _writeLock.WaitAsync(cancellationToken).ConfigureAwait(false); + + IDbTransaction transaction = null; + + try + { + transaction = _connection.BeginTransaction(); + + _saveStatusCommand.GetParameter(0).Value = status.ItemId; + _saveStatusCommand.GetParameter(1).Value = status.DateLastMetadataRefresh; + _saveStatusCommand.GetParameter(2).Value = status.DateLastImagesRefresh; + _saveStatusCommand.GetParameter(3).Value = status.LastStatus.ToString(); + _saveStatusCommand.GetParameter(4).Value = status.LastErrorMessage; + _saveStatusCommand.GetParameter(5).Value = string.Join("|", status.MetadataProvidersRefreshed.ToArray()); + _saveStatusCommand.GetParameter(6).Value = string.Join("|", status.ImageProvidersRefreshed.ToArray()); + + _saveStatusCommand.Transaction = transaction; + + _saveStatusCommand.ExecuteNonQuery(); + + transaction.Commit(); + } + catch (OperationCanceledException) + { + if (transaction != null) + { + transaction.Rollback(); + } + + throw; + } + catch (Exception e) + { + _logger.ErrorException("Failed to save provider info:", e); + + if (transaction != null) + { + transaction.Rollback(); + } + + throw; + } + finally + { + if (transaction != null) + { + transaction.Dispose(); + } + + _writeLock.Release(); + } + } + /// <summary> /// Performs application-defined tasks associated with freeing, releasing, or resetting unmanaged resources. /// </summary> diff --git a/MediaBrowser.Server.Implementations/Providers/ImageSaver.cs b/MediaBrowser.Server.Implementations/Providers/ImageSaver.cs deleted file mode 100644 index ec797b688..000000000 --- a/MediaBrowser.Server.Implementations/Providers/ImageSaver.cs +++ /dev/null @@ -1,598 +0,0 @@ -using MediaBrowser.Common.IO; -using MediaBrowser.Controller.Configuration; -using MediaBrowser.Controller.Entities; -using MediaBrowser.Controller.Entities.Audio; -using MediaBrowser.Controller.Entities.TV; -using MediaBrowser.Controller.IO; -using MediaBrowser.Model.Configuration; -using MediaBrowser.Model.Entities; -using MediaBrowser.Model.Logging; -using System; -using System.Collections.Generic; -using System.Globalization; -using System.IO; -using System.Linq; -using System.Threading; -using System.Threading.Tasks; - -namespace MediaBrowser.Server.Implementations.Providers -{ - /// <summary> - /// Class ImageSaver - /// </summary> - public class ImageSaver - { - private static readonly CultureInfo UsCulture = new CultureInfo("en-US"); - - /// <summary> - /// The _config - /// </summary> - private readonly IServerConfigurationManager _config; - - /// <summary> - /// The remote image cache - /// </summary> - private readonly FileSystemRepository _remoteImageCache; - /// <summary> - /// The _directory watchers - /// </summary> - private readonly IDirectoryWatchers _directoryWatchers; - private readonly IFileSystem _fileSystem; - private readonly ILogger _logger; - - /// <summary> - /// Initializes a new instance of the <see cref="ImageSaver"/> class. - /// </summary> - /// <param name="config">The config.</param> - /// <param name="directoryWatchers">The directory watchers.</param> - public ImageSaver(IServerConfigurationManager config, IDirectoryWatchers directoryWatchers, IFileSystem fileSystem, ILogger logger) - { - _config = config; - _directoryWatchers = directoryWatchers; - _fileSystem = fileSystem; - _logger = logger; - _remoteImageCache = new FileSystemRepository(config.ApplicationPaths.DownloadedImagesDataPath); - } - - /// <summary> - /// Saves the image. - /// </summary> - /// <param name="item">The item.</param> - /// <param name="source">The source.</param> - /// <param name="mimeType">Type of the MIME.</param> - /// <param name="type">The type.</param> - /// <param name="imageIndex">Index of the image.</param> - /// <param name="sourceUrl">The source URL.</param> - /// <param name="cancellationToken">The cancellation token.</param> - /// <returns>Task.</returns> - /// <exception cref="System.ArgumentNullException">mimeType</exception> - public async Task SaveImage(BaseItem item, Stream source, string mimeType, ImageType type, int? imageIndex, string sourceUrl, CancellationToken cancellationToken) - { - if (string.IsNullOrEmpty(mimeType)) - { - throw new ArgumentNullException("mimeType"); - } - - var saveLocally = item.IsSaveLocalMetadataEnabled() && item.Parent != null && !(item is Audio); - - if (item is IItemByName || item is User) - { - saveLocally = true; - } - - if (type != ImageType.Primary && item is Episode) - { - saveLocally = false; - } - - var locationType = item.LocationType; - if (locationType == LocationType.Remote || locationType == LocationType.Virtual) - { - saveLocally = false; - - var season = item as Season; - - // If season is virtual under a physical series, save locally if using compatible convention - if (season != null && _config.Configuration.ImageSavingConvention == ImageSavingConvention.Compatible) - { - var series = season.Series; - - if (series != null) - { - var seriesLocationType = series.LocationType; - if (seriesLocationType == LocationType.FileSystem || seriesLocationType == LocationType.Offline) - { - saveLocally = true; - } - } - } - } - - if (type == ImageType.Backdrop && imageIndex == null) - { - imageIndex = item.BackdropImagePaths.Count; - } - else if (type == ImageType.Screenshot && imageIndex == null) - { - var hasScreenshots = (IHasScreenshots)item; - imageIndex = hasScreenshots.ScreenshotImagePaths.Count; - } - - var index = imageIndex ?? 0; - - var paths = GetSavePaths(item, type, imageIndex, mimeType, saveLocally); - - // If there are more than one output paths, the stream will need to be seekable - if (paths.Length > 1 && !source.CanSeek) - { - var memoryStream = new MemoryStream(); - using (source) - { - await source.CopyToAsync(memoryStream).ConfigureAwait(false); - } - memoryStream.Position = 0; - source = memoryStream; - } - - var currentPath = GetCurrentImagePath(item, type, index); - - using (source) - { - var isFirst = true; - - foreach (var path in paths) - { - // Seek back to the beginning - if (!isFirst) - { - source.Position = 0; - } - - await SaveImageToLocation(source, path, cancellationToken).ConfigureAwait(false); - - isFirst = false; - } - } - - // Set the path into the item - SetImagePath(item, type, imageIndex, paths[0], sourceUrl); - - // Delete the current path - if (!string.IsNullOrEmpty(currentPath) && !paths.Contains(currentPath, StringComparer.OrdinalIgnoreCase)) - { - _directoryWatchers.TemporarilyIgnore(currentPath); - - try - { - var currentFile = new FileInfo(currentPath); - - // This will fail if the file is hidden - if (currentFile.Exists) - { - if ((currentFile.Attributes & FileAttributes.Hidden) == FileAttributes.Hidden) - { - currentFile.Attributes &= ~FileAttributes.Hidden; - } - - currentFile.Delete(); - } - } - finally - { - _directoryWatchers.RemoveTempIgnore(currentPath); - } - } - } - - /// <summary> - /// Saves the image to location. - /// </summary> - /// <param name="source">The source.</param> - /// <param name="path">The path.</param> - /// <param name="cancellationToken">The cancellation token.</param> - /// <returns>Task.</returns> - private async Task SaveImageToLocation(Stream source, string path, CancellationToken cancellationToken) - { - _logger.Debug("Saving image to {0}", path); - - var parentFolder = Path.GetDirectoryName(path); - - _directoryWatchers.TemporarilyIgnore(path); - _directoryWatchers.TemporarilyIgnore(parentFolder); - - try - { - Directory.CreateDirectory(Path.GetDirectoryName(path)); - - // If the file is currently hidden we'll have to remove that or the save will fail - var file = new FileInfo(path); - - // This will fail if the file is hidden - if (file.Exists) - { - if ((file.Attributes & FileAttributes.Hidden) == FileAttributes.Hidden) - { - file.Attributes &= ~FileAttributes.Hidden; - } - } - - using (var fs = _fileSystem.GetFileStream(path, FileMode.Create, FileAccess.Write, FileShare.Read, true)) - { - await source.CopyToAsync(fs, StreamDefaults.DefaultCopyToBufferSize, cancellationToken).ConfigureAwait(false); - } - } - finally - { - _directoryWatchers.RemoveTempIgnore(path); - _directoryWatchers.RemoveTempIgnore(parentFolder); - } - } - - /// <summary> - /// Gets the save paths. - /// </summary> - /// <param name="item">The item.</param> - /// <param name="type">The type.</param> - /// <param name="imageIndex">Index of the image.</param> - /// <param name="mimeType">Type of the MIME.</param> - /// <param name="saveLocally">if set to <c>true</c> [save locally].</param> - /// <returns>IEnumerable{System.String}.</returns> - private string[] GetSavePaths(BaseItem item, ImageType type, int? imageIndex, string mimeType, bool saveLocally) - { - if (_config.Configuration.ImageSavingConvention == ImageSavingConvention.Legacy || !saveLocally) - { - return new[] { GetStandardSavePath(item, type, imageIndex, mimeType, saveLocally) }; - } - - return GetCompatibleSavePaths(item, type, imageIndex, mimeType); - } - - /// <summary> - /// Gets the current image path. - /// </summary> - /// <param name="item">The item.</param> - /// <param name="type">The type.</param> - /// <param name="imageIndex">Index of the image.</param> - /// <returns>System.String.</returns> - /// <exception cref="System.ArgumentNullException"> - /// imageIndex - /// or - /// imageIndex - /// </exception> - private string GetCurrentImagePath(IHasImages item, ImageType type, int imageIndex) - { - return item.GetImagePath(type, imageIndex); - } - - /// <summary> - /// Sets the image path. - /// </summary> - /// <param name="item">The item.</param> - /// <param name="type">The type.</param> - /// <param name="imageIndex">Index of the image.</param> - /// <param name="path">The path.</param> - /// <param name="sourceUrl">The source URL.</param> - /// <exception cref="System.ArgumentNullException">imageIndex - /// or - /// imageIndex</exception> - private void SetImagePath(BaseItem item, ImageType type, int? imageIndex, string path, string sourceUrl) - { - switch (type) - { - case ImageType.Screenshot: - - if (!imageIndex.HasValue) - { - throw new ArgumentNullException("imageIndex"); - } - - var hasScreenshots = (IHasScreenshots)item; - if (hasScreenshots.ScreenshotImagePaths.Count > imageIndex.Value) - { - hasScreenshots.ScreenshotImagePaths[imageIndex.Value] = path; - } - else if (!hasScreenshots.ScreenshotImagePaths.Contains(path, StringComparer.OrdinalIgnoreCase)) - { - hasScreenshots.ScreenshotImagePaths.Add(path); - } - break; - case ImageType.Backdrop: - if (!imageIndex.HasValue) - { - throw new ArgumentNullException("imageIndex"); - } - if (item.BackdropImagePaths.Count > imageIndex.Value) - { - item.BackdropImagePaths[imageIndex.Value] = path; - } - else if (!item.BackdropImagePaths.Contains(path, StringComparer.OrdinalIgnoreCase)) - { - item.BackdropImagePaths.Add(path); - } - - if (string.IsNullOrEmpty(sourceUrl)) - { - item.RemoveImageSourceForPath(path); - } - else - { - item.AddImageSource(path, sourceUrl); - } - break; - default: - item.SetImagePath(type, path); - break; - } - } - - /// <summary> - /// Gets the save path. - /// </summary> - /// <param name="item">The item.</param> - /// <param name="type">The type.</param> - /// <param name="imageIndex">Index of the image.</param> - /// <param name="mimeType">Type of the MIME.</param> - /// <param name="saveLocally">if set to <c>true</c> [save locally].</param> - /// <returns>System.String.</returns> - /// <exception cref="System.ArgumentNullException"> - /// imageIndex - /// or - /// imageIndex - /// </exception> - private string GetStandardSavePath(BaseItem item, ImageType type, int? imageIndex, string mimeType, bool saveLocally) - { - string filename; - - switch (type) - { - case ImageType.Art: - filename = "clearart"; - break; - case ImageType.Disc: - filename = item is MusicAlbum ? "cdart" : "disc"; - break; - case ImageType.Primary: - filename = item is Episode ? Path.GetFileNameWithoutExtension(item.Path) : "folder"; - break; - case ImageType.Backdrop: - if (!imageIndex.HasValue) - { - throw new ArgumentNullException("imageIndex"); - } - filename = GetBackdropSaveFilename(item.BackdropImagePaths, "backdrop", "backdrop", imageIndex.Value); - break; - case ImageType.Screenshot: - if (!imageIndex.HasValue) - { - throw new ArgumentNullException("imageIndex"); - } - var hasScreenshots = (IHasScreenshots)item; - filename = GetBackdropSaveFilename(hasScreenshots.ScreenshotImagePaths, "screenshot", "screenshot", imageIndex.Value); - break; - default: - filename = type.ToString().ToLower(); - break; - } - - var extension = mimeType.Split('/').Last(); - - if (string.Equals(extension, "jpeg", StringComparison.OrdinalIgnoreCase)) - { - extension = "jpg"; - } - - extension = "." + extension.ToLower(); - - string path = null; - - if (saveLocally) - { - if (item.IsInMixedFolder && !(item is Episode)) - { - path = GetSavePathForItemInMixedFolder(item, type, filename, extension); - } - - if (string.IsNullOrEmpty(path)) - { - path = Path.Combine(item.MetaLocation, filename + extension); - } - } - - // None of the save local conditions passed, so store it in our internal folders - if (string.IsNullOrEmpty(path)) - { - path = _remoteImageCache.GetResourcePath(item.GetType().FullName + item.Id, filename + extension); - } - - return path; - } - - private string GetBackdropSaveFilename(IEnumerable<string> images, string zeroIndexFilename, string numberedIndexPrefix, int index) - { - if (index == 0) - { - return zeroIndexFilename; - } - - var filenames = images.Select(Path.GetFileNameWithoutExtension).ToList(); - - var current = index; - while (filenames.Contains(numberedIndexPrefix + current.ToString(UsCulture), StringComparer.OrdinalIgnoreCase)) - { - current++; - } - - return numberedIndexPrefix + current.ToString(UsCulture); - } - - /// <summary> - /// Gets the compatible save paths. - /// </summary> - /// <param name="item">The item.</param> - /// <param name="type">The type.</param> - /// <param name="imageIndex">Index of the image.</param> - /// <param name="mimeType">Type of the MIME.</param> - /// <returns>IEnumerable{System.String}.</returns> - /// <exception cref="System.ArgumentNullException">imageIndex</exception> - private string[] GetCompatibleSavePaths(BaseItem item, ImageType type, int? imageIndex, string mimeType) - { - var season = item as Season; - - var extension = mimeType.Split('/').Last(); - - if (string.Equals(extension, "jpeg", StringComparison.OrdinalIgnoreCase)) - { - extension = "jpg"; - } - extension = "." + extension.ToLower(); - - // Backdrop paths - if (type == ImageType.Backdrop) - { - if (!imageIndex.HasValue) - { - throw new ArgumentNullException("imageIndex"); - } - - if (imageIndex.Value == 0) - { - if (item.IsInMixedFolder) - { - return new[] { GetSavePathForItemInMixedFolder(item, type, "fanart", extension) }; - } - - if (season != null && item.IndexNumber.HasValue) - { - var seriesFolder = season.SeriesPath; - - var seasonMarker = item.IndexNumber.Value == 0 - ? "-specials" - : item.IndexNumber.Value.ToString("00", UsCulture); - - var imageFilename = "season" + seasonMarker + "-fanart" + extension; - - return new[] { Path.Combine(seriesFolder, imageFilename) }; - } - - return new[] - { - Path.Combine(item.MetaLocation, "fanart" + extension) - }; - } - - var outputIndex = imageIndex.Value; - - if (item.IsInMixedFolder) - { - return new[] { GetSavePathForItemInMixedFolder(item, type, "fanart" + outputIndex.ToString(UsCulture), extension) }; - } - - var extraFanartFilename = GetBackdropSaveFilename(item.BackdropImagePaths, "fanart", "fanart", outputIndex); - - return new[] - { - Path.Combine(item.MetaLocation, "extrafanart", extraFanartFilename + extension), - Path.Combine(item.MetaLocation, "extrathumbs", "thumb" + outputIndex.ToString(UsCulture) + extension) - }; - } - - if (type == ImageType.Primary) - { - if (season != null && item.IndexNumber.HasValue) - { - var seriesFolder = season.SeriesPath; - - var seasonMarker = item.IndexNumber.Value == 0 - ? "-specials" - : item.IndexNumber.Value.ToString("00", UsCulture); - - var imageFilename = "season" + seasonMarker + "-poster" + extension; - - return new[] { Path.Combine(seriesFolder, imageFilename) }; - } - - if (item is Episode) - { - var seasonFolder = Path.GetDirectoryName(item.Path); - - var imageFilename = Path.GetFileNameWithoutExtension(item.Path) + "-thumb" + extension; - - return new[] { Path.Combine(seasonFolder, imageFilename) }; - } - - if (item.IsInMixedFolder || item is MusicVideo) - { - return new[] { GetSavePathForItemInMixedFolder(item, type, string.Empty, extension) }; - } - - if (item is MusicAlbum || item is MusicArtist) - { - return new[] { Path.Combine(item.MetaLocation, "folder" + extension) }; - } - - return new[] { Path.Combine(item.MetaLocation, "poster" + extension) }; - } - - if (type == ImageType.Banner) - { - if (season != null && item.IndexNumber.HasValue) - { - var seriesFolder = season.SeriesPath; - - var seasonMarker = item.IndexNumber.Value == 0 - ? "-specials" - : item.IndexNumber.Value.ToString("00", UsCulture); - - var imageFilename = "season" + seasonMarker + "-banner" + extension; - - return new[] { Path.Combine(seriesFolder, imageFilename) }; - } - } - - if (type == ImageType.Thumb) - { - if (season != null && item.IndexNumber.HasValue) - { - var seriesFolder = season.SeriesPath; - - var seasonMarker = item.IndexNumber.Value == 0 - ? "-specials" - : item.IndexNumber.Value.ToString("00", UsCulture); - - var imageFilename = "season" + seasonMarker + "-landscape" + extension; - - return new[] { Path.Combine(seriesFolder, imageFilename) }; - } - - if (item.IsInMixedFolder) - { - return new[] { GetSavePathForItemInMixedFolder(item, type, "landscape", extension) }; - } - - return new[] { Path.Combine(item.MetaLocation, "landscape" + extension) }; - } - - // All other paths are the same - return new[] { GetStandardSavePath(item, type, imageIndex, mimeType, true) }; - } - - /// <summary> - /// Gets the save path for item in mixed folder. - /// </summary> - /// <param name="item">The item.</param> - /// <param name="type">The type.</param> - /// <param name="imageFilename">The image filename.</param> - /// <param name="extension">The extension.</param> - /// <returns>System.String.</returns> - private string GetSavePathForItemInMixedFolder(IHasImages item, ImageType type, string imageFilename, string extension) - { - if (type == ImageType.Primary) - { - imageFilename = "poster"; - } - var folder = Path.GetDirectoryName(item.Path); - - return Path.Combine(folder, Path.GetFileNameWithoutExtension(item.Path) + "-" + imageFilename + extension); - } - } -} diff --git a/MediaBrowser.Server.Implementations/Providers/ProviderManager.cs b/MediaBrowser.Server.Implementations/Providers/ProviderManager.cs deleted file mode 100644 index cbfd7d74d..000000000 --- a/MediaBrowser.Server.Implementations/Providers/ProviderManager.cs +++ /dev/null @@ -1,454 +0,0 @@ -using MediaBrowser.Common.IO; -using MediaBrowser.Common.Net; -using MediaBrowser.Controller.Configuration; -using MediaBrowser.Controller.Entities; -using MediaBrowser.Controller.Entities.Movies; -using MediaBrowser.Controller.IO; -using MediaBrowser.Controller.Library; -using MediaBrowser.Controller.Persistence; -using MediaBrowser.Controller.Providers; -using MediaBrowser.Model.Entities; -using MediaBrowser.Model.Logging; -using MediaBrowser.Model.Providers; -using System; -using System.Collections.Generic; -using System.IO; -using System.Linq; -using System.Threading; -using System.Threading.Tasks; - -namespace MediaBrowser.Server.Implementations.Providers -{ - /// <summary> - /// Class ProviderManager - /// </summary> - public class ProviderManager : IProviderManager - { - /// <summary> - /// The _logger - /// </summary> - private readonly ILogger _logger; - - /// <summary> - /// The _HTTP client - /// </summary> - private readonly IHttpClient _httpClient; - - /// <summary> - /// The _directory watchers - /// </summary> - private readonly IDirectoryWatchers _directoryWatchers; - - /// <summary> - /// Gets or sets the configuration manager. - /// </summary> - /// <value>The configuration manager.</value> - private IServerConfigurationManager ConfigurationManager { get; set; } - - /// <summary> - /// Gets the list of currently registered metadata prvoiders - /// </summary> - /// <value>The metadata providers enumerable.</value> - private BaseMetadataProvider[] MetadataProviders { get; set; } - - private IImageProvider[] ImageProviders { get; set; } - private readonly IFileSystem _fileSystem; - - private readonly IItemRepository _itemRepo; - - /// <summary> - /// Initializes a new instance of the <see cref="ProviderManager" /> class. - /// </summary> - /// <param name="httpClient">The HTTP client.</param> - /// <param name="configurationManager">The configuration manager.</param> - /// <param name="directoryWatchers">The directory watchers.</param> - /// <param name="logManager">The log manager.</param> - public ProviderManager(IHttpClient httpClient, IServerConfigurationManager configurationManager, IDirectoryWatchers directoryWatchers, ILogManager logManager, IFileSystem fileSystem, IItemRepository itemRepo) - { - _logger = logManager.GetLogger("ProviderManager"); - _httpClient = httpClient; - ConfigurationManager = configurationManager; - _directoryWatchers = directoryWatchers; - _fileSystem = fileSystem; - _itemRepo = itemRepo; - } - - /// <summary> - /// Adds the metadata providers. - /// </summary> - /// <param name="providers">The providers.</param> - /// <param name="imageProviders">The image providers.</param> - public void AddParts(IEnumerable<BaseMetadataProvider> providers, IEnumerable<IImageProvider> imageProviders) - { - MetadataProviders = providers.OrderBy(e => e.Priority).ToArray(); - - ImageProviders = imageProviders.OrderByDescending(i => i.Priority).ToArray(); - } - - /// <summary> - /// Runs all metadata providers for an entity, and returns true or false indicating if at least one was refreshed and requires persistence - /// </summary> - /// <param name="item">The item.</param> - /// <param name="cancellationToken">The cancellation token.</param> - /// <param name="force">if set to <c>true</c> [force].</param> - /// <param name="allowSlowProviders">if set to <c>true</c> [allow slow providers].</param> - /// <returns>Task{System.Boolean}.</returns> - public async Task<ItemUpdateType?> ExecuteMetadataProviders(BaseItem item, CancellationToken cancellationToken, bool force = false, bool allowSlowProviders = true) - { - if (item == null) - { - throw new ArgumentNullException("item"); - } - - ItemUpdateType? result = null; - - cancellationToken.ThrowIfCancellationRequested(); - - var enableInternetProviders = ConfigurationManager.Configuration.EnableInternetProviders; - - var providerHistories = item.DateLastSaved == default(DateTime) ? - new List<BaseProviderInfo>() : - _itemRepo.GetProviderHistory(item.Id).ToList(); - - // Run the normal providers sequentially in order of priority - foreach (var provider in MetadataProviders) - { - cancellationToken.ThrowIfCancellationRequested(); - - if (!ProviderSupportsItem(provider, item)) - { - continue; - } - - // Skip if internet providers are currently disabled - if (provider.RequiresInternet && !enableInternetProviders) - { - continue; - } - - // Skip if is slow and we aren't allowing slow ones - if (provider.IsSlow && !allowSlowProviders) - { - continue; - } - - // 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 (provider.RequiresInternet && item.DontFetchMeta && provider.EnforceDontFetchMetadata) - { - continue; - } - - var providerInfo = providerHistories.FirstOrDefault(i => i.ProviderId == provider.Id); - - if (providerInfo == null) - { - providerInfo = new BaseProviderInfo - { - ProviderId = provider.Id - }; - providerHistories.Add(providerInfo); - } - - try - { - if (!force && !provider.NeedsRefresh(item, providerInfo)) - { - continue; - } - } - catch (Exception ex) - { - _logger.Error("Error determining NeedsRefresh for {0}", ex, item.Path); - } - - var updateType = await FetchAsync(provider, item, providerInfo, force, cancellationToken).ConfigureAwait(false); - - if (updateType.HasValue) - { - if (result.HasValue) - { - result = result.Value | updateType.Value; - } - else - { - result = updateType; - } - } - } - - if (result.HasValue || force) - { - await _itemRepo.SaveProviderHistory(item.Id, providerHistories, cancellationToken); - } - - return result; - } - - /// <summary> - /// Providers the supports item. - /// </summary> - /// <param name="provider">The provider.</param> - /// <param name="item">The item.</param> - /// <returns><c>true</c> if XXXX, <c>false</c> otherwise</returns> - private bool ProviderSupportsItem(BaseMetadataProvider provider, BaseItem item) - { - try - { - return provider.Supports(item); - } - catch (Exception ex) - { - _logger.ErrorException("{0} failed in Supports for type {1}", ex, provider.GetType().Name, item.GetType().Name); - return false; - } - } - - /// <summary> - /// Fetches metadata and returns true or false indicating if any work that requires persistence was done - /// </summary> - /// <param name="provider">The provider.</param> - /// <param name="item">The item.</param> - /// <param name="providerInfo">The provider information.</param> - /// <param name="force">if set to <c>true</c> [force].</param> - /// <param name="cancellationToken">The cancellation token.</param> - /// <returns>Task{System.Boolean}.</returns> - /// <exception cref="System.ArgumentNullException">item</exception> - private async Task<ItemUpdateType?> FetchAsync(BaseMetadataProvider provider, BaseItem item, BaseProviderInfo providerInfo, bool force, CancellationToken cancellationToken) - { - if (item == null) - { - throw new ArgumentNullException("item"); - } - - cancellationToken.ThrowIfCancellationRequested(); - - // Don't clog up the log with these providers - if (!(provider is IDynamicInfoProvider)) - { - _logger.Debug("Running {0} for {1}", provider.GetType().Name, item.Path ?? item.Name ?? "--Unknown--"); - } - - try - { - var changed = await provider.FetchAsync(item, force, providerInfo, cancellationToken).ConfigureAwait(false); - - if (changed) - { - return provider.ItemUpdateType; - } - - return null; - } - catch (OperationCanceledException ex) - { - _logger.Debug("{0} canceled 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 null; - } - catch (Exception ex) - { - _logger.ErrorException("{0} failed refreshing {1} {2}", ex, provider.GetType().Name, item.Name, item.Path ?? string.Empty); - - provider.SetLastRefreshed(item, DateTime.UtcNow, providerInfo, ProviderRefreshStatus.Failure); - - return ItemUpdateType.Unspecified; - } - } - - /// <summary> - /// Saves to library filesystem. - /// </summary> - /// <param name="item">The item.</param> - /// <param name="path">The path.</param> - /// <param name="dataToSave">The data to save.</param> - /// <param name="cancellationToken">The cancellation token.</param> - /// <returns>Task.</returns> - /// <exception cref="System.ArgumentNullException"></exception> - 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.IsCancellationRequested) - { - dataToSave.Dispose(); - cancellationToken.ThrowIfCancellationRequested(); - } - - //Tell the watchers to ignore - _directoryWatchers.TemporarilyIgnore(path); - - if (dataToSave.CanSeek) - { - dataToSave.Position = 0; - } - - try - { - using (dataToSave) - { - using (var fs = _fileSystem.GetFileStream(path, FileMode.Create, FileAccess.Write, FileShare.Read, true)) - { - await dataToSave.CopyToAsync(fs, StreamDefaults.DefaultCopyToBufferSize, cancellationToken).ConfigureAwait(false); - } - } - - // 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); - } - } - - - /// <summary> - /// Saves the image. - /// </summary> - /// <param name="item">The item.</param> - /// <param name="url">The URL.</param> - /// <param name="resourcePool">The resource pool.</param> - /// <param name="type">The type.</param> - /// <param name="imageIndex">Index of the image.</param> - /// <param name="cancellationToken">The cancellation token.</param> - /// <returns>Task.</returns> - public async Task SaveImage(BaseItem item, string url, SemaphoreSlim resourcePool, ImageType type, int? imageIndex, CancellationToken cancellationToken) - { - var response = await _httpClient.GetResponse(new HttpRequestOptions - { - CancellationToken = cancellationToken, - ResourcePool = resourcePool, - Url = url - - }).ConfigureAwait(false); - - await SaveImage(item, response.Content, response.ContentType, type, imageIndex, url, cancellationToken) - .ConfigureAwait(false); - } - - /// <summary> - /// Saves the image. - /// </summary> - /// <param name="item">The item.</param> - /// <param name="source">The source.</param> - /// <param name="mimeType">Type of the MIME.</param> - /// <param name="type">The type.</param> - /// <param name="imageIndex">Index of the image.</param> - /// <param name="sourceUrl">The source URL.</param> - /// <param name="cancellationToken">The cancellation token.</param> - /// <returns>Task.</returns> - public Task SaveImage(BaseItem item, Stream source, string mimeType, ImageType type, int? imageIndex, string sourceUrl, CancellationToken cancellationToken) - { - return new ImageSaver(ConfigurationManager, _directoryWatchers, _fileSystem, _logger).SaveImage(item, source, mimeType, type, imageIndex, sourceUrl, cancellationToken); - } - - /// <summary> - /// Gets the available remote images. - /// </summary> - /// <param name="item">The item.</param> - /// <param name="cancellationToken">The cancellation token.</param> - /// <param name="providerName">Name of the provider.</param> - /// <param name="type">The type.</param> - /// <returns>Task{IEnumerable{RemoteImageInfo}}.</returns> - public async Task<IEnumerable<RemoteImageInfo>> GetAvailableRemoteImages(BaseItem item, CancellationToken cancellationToken, string providerName = null, ImageType? type = null) - { - var providers = GetImageProviders(item); - - if (!string.IsNullOrEmpty(providerName)) - { - providers = providers.Where(i => string.Equals(i.Name, providerName, StringComparison.OrdinalIgnoreCase)); - } - - var preferredLanguage = item.GetPreferredMetadataLanguage(); - - var tasks = providers.Select(i => GetImages(item, cancellationToken, i, preferredLanguage, type)); - - var results = await Task.WhenAll(tasks).ConfigureAwait(false); - - return results.SelectMany(i => i); - } - - /// <summary> - /// Gets the images. - /// </summary> - /// <param name="item">The item.</param> - /// <param name="cancellationToken">The cancellation token.</param> - /// <param name="i">The i.</param> - /// <param name="preferredLanguage">The preferred language.</param> - /// <param name="type">The type.</param> - /// <returns>Task{IEnumerable{RemoteImageInfo}}.</returns> - private async Task<IEnumerable<RemoteImageInfo>> GetImages(BaseItem item, CancellationToken cancellationToken, IImageProvider i, string preferredLanguage, ImageType? type = null) - { - try - { - if (type.HasValue) - { - var result = await i.GetImages(item, type.Value, cancellationToken).ConfigureAwait(false); - - return FilterImages(result, preferredLanguage); - } - else - { - var result = await i.GetAllImages(item, cancellationToken).ConfigureAwait(false); - return FilterImages(result, preferredLanguage); - } - } - catch (Exception ex) - { - _logger.ErrorException("{0} failed in GetImages for type {1}", ex, i.GetType().Name, item.GetType().Name); - return new List<RemoteImageInfo>(); - } - } - - private IEnumerable<RemoteImageInfo> FilterImages(IEnumerable<RemoteImageInfo> images, string preferredLanguage) - { - if (string.Equals(preferredLanguage, "en", StringComparison.OrdinalIgnoreCase)) - { - images = images.Where(i => string.IsNullOrEmpty(i.Language) || - string.Equals(i.Language, "en", StringComparison.OrdinalIgnoreCase)); - } - - return images; - } - - /// <summary> - /// Gets the supported image providers. - /// </summary> - /// <param name="item">The item.</param> - /// <returns>IEnumerable{IImageProvider}.</returns> - public IEnumerable<IImageProvider> GetImageProviders(BaseItem item) - { - return ImageProviders.Where(i => - { - try - { - return i.Supports(item); - } - catch (Exception ex) - { - _logger.ErrorException("{0} failed in Supports for type {1}", ex, i.GetType().Name, item.GetType().Name); - return false; - } - }); - } - } -} |
