From ad82c9f5e95e2b1f94ba7adda047dbfbc38004ea Mon Sep 17 00:00:00 2001 From: Luke Pulverenti Date: Tue, 28 Jan 2014 13:37:01 -0500 Subject: New provider system. Only for people right now --- MediaBrowser.Providers/Manager/ImageSaver.cs | 598 +++++++++++++++++++++++++++ 1 file changed, 598 insertions(+) create mode 100644 MediaBrowser.Providers/Manager/ImageSaver.cs (limited to 'MediaBrowser.Providers/Manager/ImageSaver.cs') diff --git a/MediaBrowser.Providers/Manager/ImageSaver.cs b/MediaBrowser.Providers/Manager/ImageSaver.cs new file mode 100644 index 0000000000..a75ce88aeb --- /dev/null +++ b/MediaBrowser.Providers/Manager/ImageSaver.cs @@ -0,0 +1,598 @@ +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.Providers.Manager +{ + /// + /// Class ImageSaver + /// + public class ImageSaver + { + private static readonly CultureInfo UsCulture = new CultureInfo("en-US"); + + /// + /// The _config + /// + private readonly IServerConfigurationManager _config; + + /// + /// The remote image cache + /// + private readonly FileSystemRepository _remoteImageCache; + /// + /// The _directory watchers + /// + private readonly IDirectoryWatchers _directoryWatchers; + private readonly IFileSystem _fileSystem; + private readonly ILogger _logger; + + /// + /// Initializes a new instance of the class. + /// + /// The config. + /// The directory watchers. + public ImageSaver(IServerConfigurationManager config, IDirectoryWatchers directoryWatchers, IFileSystem fileSystem, ILogger logger) + { + _config = config; + _directoryWatchers = directoryWatchers; + _fileSystem = fileSystem; + _logger = logger; + _remoteImageCache = new FileSystemRepository(config.ApplicationPaths.DownloadedImagesDataPath); + } + + /// + /// Saves the image. + /// + /// The item. + /// The source. + /// Type of the MIME. + /// The type. + /// Index of the image. + /// The source URL. + /// The cancellation token. + /// Task. + /// mimeType + 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); + } + } + } + + /// + /// Saves the image to location. + /// + /// The source. + /// The path. + /// The cancellation token. + /// Task. + 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); + } + } + + /// + /// Gets the save paths. + /// + /// The item. + /// The type. + /// Index of the image. + /// Type of the MIME. + /// if set to true [save locally]. + /// IEnumerable{System.String}. + 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); + } + + /// + /// Gets the current image path. + /// + /// The item. + /// The type. + /// Index of the image. + /// System.String. + /// + /// imageIndex + /// or + /// imageIndex + /// + private string GetCurrentImagePath(IHasImages item, ImageType type, int imageIndex) + { + return item.GetImagePath(type, imageIndex); + } + + /// + /// Sets the image path. + /// + /// The item. + /// The type. + /// Index of the image. + /// The path. + /// The source URL. + /// imageIndex + /// or + /// imageIndex + 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; + } + } + + /// + /// Gets the save path. + /// + /// The item. + /// The type. + /// Index of the image. + /// Type of the MIME. + /// if set to true [save locally]. + /// System.String. + /// + /// imageIndex + /// or + /// imageIndex + /// + 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 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); + } + + /// + /// Gets the compatible save paths. + /// + /// The item. + /// The type. + /// Index of the image. + /// Type of the MIME. + /// IEnumerable{System.String}. + /// imageIndex + 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) }; + } + + /// + /// Gets the save path for item in mixed folder. + /// + /// The item. + /// The type. + /// The image filename. + /// The extension. + /// System.String. + 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); + } + } +} -- cgit v1.2.3 From 7c5b22246357ef6bcd6d74e61a8fe6a8a9d4df3e Mon Sep 17 00:00:00 2001 From: Luke Pulverenti Date: Tue, 28 Jan 2014 16:25:10 -0500 Subject: Created ILibraryMonitor to replace IDirectoryWatchers --- .../Library/LibraryStructureService.cs | 37 +- MediaBrowser.Controller/IO/IDirectoryWatchers.cs | 29 - MediaBrowser.Controller/Library/ILibraryMonitor.cs | 36 ++ .../MediaBrowser.Controller.csproj | 2 +- .../Configuration/ServerConfiguration.cs | 4 +- MediaBrowser.Providers/Manager/ImageSaver.cs | 22 +- MediaBrowser.Providers/Manager/MetadataService.cs | 18 +- MediaBrowser.Providers/Manager/ProviderManager.cs | 16 +- .../FileOrganization/EpisodeFileOrganizer.cs | 16 +- .../FileOrganization/FileOrganizationService.cs | 16 +- .../FileOrganization/OrganizerScheduledTask.cs | 8 +- .../FileOrganization/TvFolderOrganizer.cs | 8 +- .../IO/DirectoryWatchers.cs | 636 --------------------- .../IO/LibraryMonitor.cs | 569 ++++++++++++++++++ .../Library/LibraryManager.cs | 16 +- .../MediaBrowser.Server.Implementations.csproj | 2 +- MediaBrowser.ServerApplication/ApplicationHost.cs | 12 +- 17 files changed, 680 insertions(+), 767 deletions(-) delete mode 100644 MediaBrowser.Controller/IO/IDirectoryWatchers.cs create mode 100644 MediaBrowser.Controller/Library/ILibraryMonitor.cs delete mode 100644 MediaBrowser.Server.Implementations/IO/DirectoryWatchers.cs create mode 100644 MediaBrowser.Server.Implementations/IO/LibraryMonitor.cs (limited to 'MediaBrowser.Providers/Manager/ImageSaver.cs') diff --git a/MediaBrowser.Api/Library/LibraryStructureService.cs b/MediaBrowser.Api/Library/LibraryStructureService.cs index 7759073798..56b0e01c3f 100644 --- a/MediaBrowser.Api/Library/LibraryStructureService.cs +++ b/MediaBrowser.Api/Library/LibraryStructureService.cs @@ -187,7 +187,7 @@ namespace MediaBrowser.Api.Library /// private readonly ILibraryManager _libraryManager; - private readonly IDirectoryWatchers _directoryWatchers; + private readonly ILibraryMonitor _libraryMonitor; private readonly IFileSystem _fileSystem; private readonly ILogger _logger; @@ -199,7 +199,7 @@ namespace MediaBrowser.Api.Library /// The user manager. /// The library manager. /// appPaths - public LibraryStructureService(IServerApplicationPaths appPaths, IUserManager userManager, ILibraryManager libraryManager, IDirectoryWatchers directoryWatchers, IFileSystem fileSystem, ILogger logger) + public LibraryStructureService(IServerApplicationPaths appPaths, IUserManager userManager, ILibraryManager libraryManager, ILibraryMonitor libraryMonitor, IFileSystem fileSystem, ILogger logger) { if (appPaths == null) { @@ -209,7 +209,7 @@ namespace MediaBrowser.Api.Library _userManager = userManager; _appPaths = appPaths; _libraryManager = libraryManager; - _directoryWatchers = directoryWatchers; + _libraryMonitor = libraryMonitor; _fileSystem = fileSystem; _logger = logger; } @@ -270,8 +270,7 @@ namespace MediaBrowser.Api.Library throw new ArgumentException("There is already a media collection with the name " + name + "."); } - _directoryWatchers.Stop(); - _directoryWatchers.TemporarilyIgnore(virtualFolderPath); + _libraryMonitor.Stop(); try { @@ -294,10 +293,8 @@ namespace MediaBrowser.Api.Library // No need to start if scanning the library because it will handle it if (!request.RefreshLibrary) { - _directoryWatchers.Start(); + _libraryMonitor.Start(); } - - _directoryWatchers.RemoveTempIgnore(virtualFolderPath); } if (request.RefreshLibrary) @@ -348,9 +345,7 @@ namespace MediaBrowser.Api.Library throw new ArgumentException("There is already a media collection with the name " + newPath + "."); } - _directoryWatchers.Stop(); - _directoryWatchers.TemporarilyIgnore(currentPath); - _directoryWatchers.TemporarilyIgnore(newPath); + _libraryMonitor.Stop(); try { @@ -376,11 +371,8 @@ namespace MediaBrowser.Api.Library // No need to start if scanning the library because it will handle it if (!request.RefreshLibrary) { - _directoryWatchers.Start(); + _libraryMonitor.Start(); } - - _directoryWatchers.RemoveTempIgnore(currentPath); - _directoryWatchers.RemoveTempIgnore(newPath); } if (request.RefreshLibrary) @@ -420,8 +412,7 @@ namespace MediaBrowser.Api.Library throw new DirectoryNotFoundException("The media folder does not exist"); } - _directoryWatchers.Stop(); - _directoryWatchers.TemporarilyIgnore(path); + _libraryMonitor.Stop(); try { @@ -437,10 +428,8 @@ namespace MediaBrowser.Api.Library // No need to start if scanning the library because it will handle it if (!request.RefreshLibrary) { - _directoryWatchers.Start(); + _libraryMonitor.Start(); } - - _directoryWatchers.RemoveTempIgnore(path); } if (request.RefreshLibrary) @@ -460,7 +449,7 @@ namespace MediaBrowser.Api.Library throw new ArgumentNullException("request"); } - _directoryWatchers.Stop(); + _libraryMonitor.Stop(); try { @@ -485,7 +474,7 @@ namespace MediaBrowser.Api.Library // No need to start if scanning the library because it will handle it if (!request.RefreshLibrary) { - _directoryWatchers.Start(); + _libraryMonitor.Start(); } } @@ -506,7 +495,7 @@ namespace MediaBrowser.Api.Library throw new ArgumentNullException("request"); } - _directoryWatchers.Stop(); + _libraryMonitor.Stop(); try { @@ -531,7 +520,7 @@ namespace MediaBrowser.Api.Library // No need to start if scanning the library because it will handle it if (!request.RefreshLibrary) { - _directoryWatchers.Start(); + _libraryMonitor.Start(); } } diff --git a/MediaBrowser.Controller/IO/IDirectoryWatchers.cs b/MediaBrowser.Controller/IO/IDirectoryWatchers.cs deleted file mode 100644 index 9a43ee8acf..0000000000 --- a/MediaBrowser.Controller/IO/IDirectoryWatchers.cs +++ /dev/null @@ -1,29 +0,0 @@ -using System; - -namespace MediaBrowser.Controller.IO -{ - public interface IDirectoryWatchers : IDisposable - { - /// - /// Add the path to our temporary ignore list. Use when writing to a path within our listening scope. - /// - /// The path. - void TemporarilyIgnore(string path); - - /// - /// Removes the temp ignore. - /// - /// The path. - void RemoveTempIgnore(string path); - - /// - /// Starts this instance. - /// - void Start(); - - /// - /// Stops this instance. - /// - void Stop(); - } -} \ No newline at end of file diff --git a/MediaBrowser.Controller/Library/ILibraryMonitor.cs b/MediaBrowser.Controller/Library/ILibraryMonitor.cs new file mode 100644 index 0000000000..918382f049 --- /dev/null +++ b/MediaBrowser.Controller/Library/ILibraryMonitor.cs @@ -0,0 +1,36 @@ +using System; + +namespace MediaBrowser.Controller.Library +{ + public interface ILibraryMonitor : IDisposable + { + /// + /// Starts this instance. + /// + void Start(); + + /// + /// Stops this instance. + /// + void Stop(); + + /// + /// Reports the file system change beginning. + /// + /// The path. + void ReportFileSystemChangeBeginning(string path); + + /// + /// Reports the file system change complete. + /// + /// The path. + /// if set to true [refresh path]. + void ReportFileSystemChangeComplete(string path, bool refreshPath); + + /// + /// Reports the file system changed. + /// + /// The path. + void ReportFileSystemChanged(string path); + } +} \ No newline at end of file diff --git a/MediaBrowser.Controller/MediaBrowser.Controller.csproj b/MediaBrowser.Controller/MediaBrowser.Controller.csproj index ef87c30c78..45297ef3d9 100644 --- a/MediaBrowser.Controller/MediaBrowser.Controller.csproj +++ b/MediaBrowser.Controller/MediaBrowser.Controller.csproj @@ -182,7 +182,7 @@ - + diff --git a/MediaBrowser.Model/Configuration/ServerConfiguration.cs b/MediaBrowser.Model/Configuration/ServerConfiguration.cs index f017fdf16f..923c5ab748 100644 --- a/MediaBrowser.Model/Configuration/ServerConfiguration.cs +++ b/MediaBrowser.Model/Configuration/ServerConfiguration.cs @@ -165,7 +165,7 @@ namespace MediaBrowser.Model.Configuration /// different directories and files. /// /// The file watcher delay. - public int FileWatcherDelay { get; set; } + public int RealtimeWatcherDelay { get; set; } /// /// Gets or sets a value indicating whether [enable dashboard response caching]. @@ -250,7 +250,7 @@ namespace MediaBrowser.Model.Configuration MaxResumePct = 90; MinResumeDurationSeconds = Convert.ToInt32(TimeSpan.FromMinutes(5).TotalSeconds); - FileWatcherDelay = 8; + RealtimeWatcherDelay = 20; RecentItemDays = 10; diff --git a/MediaBrowser.Providers/Manager/ImageSaver.cs b/MediaBrowser.Providers/Manager/ImageSaver.cs index a75ce88aeb..56a1a9d4fe 100644 --- a/MediaBrowser.Providers/Manager/ImageSaver.cs +++ b/MediaBrowser.Providers/Manager/ImageSaver.cs @@ -3,7 +3,7 @@ using MediaBrowser.Controller.Configuration; using MediaBrowser.Controller.Entities; using MediaBrowser.Controller.Entities.Audio; using MediaBrowser.Controller.Entities.TV; -using MediaBrowser.Controller.IO; +using MediaBrowser.Controller.Library; using MediaBrowser.Model.Configuration; using MediaBrowser.Model.Entities; using MediaBrowser.Model.Logging; @@ -36,7 +36,7 @@ namespace MediaBrowser.Providers.Manager /// /// The _directory watchers /// - private readonly IDirectoryWatchers _directoryWatchers; + private readonly ILibraryMonitor _libraryMonitor; private readonly IFileSystem _fileSystem; private readonly ILogger _logger; @@ -44,11 +44,11 @@ namespace MediaBrowser.Providers.Manager /// Initializes a new instance of the class. /// /// The config. - /// The directory watchers. - public ImageSaver(IServerConfigurationManager config, IDirectoryWatchers directoryWatchers, IFileSystem fileSystem, ILogger logger) + /// The directory watchers. + public ImageSaver(IServerConfigurationManager config, ILibraryMonitor libraryMonitor, IFileSystem fileSystem, ILogger logger) { _config = config; - _directoryWatchers = directoryWatchers; + _libraryMonitor = libraryMonitor; _fileSystem = fileSystem; _logger = logger; _remoteImageCache = new FileSystemRepository(config.ApplicationPaths.DownloadedImagesDataPath); @@ -160,7 +160,7 @@ namespace MediaBrowser.Providers.Manager // Delete the current path if (!string.IsNullOrEmpty(currentPath) && !paths.Contains(currentPath, StringComparer.OrdinalIgnoreCase)) { - _directoryWatchers.TemporarilyIgnore(currentPath); + _libraryMonitor.ReportFileSystemChangeBeginning(currentPath); try { @@ -179,7 +179,7 @@ namespace MediaBrowser.Providers.Manager } finally { - _directoryWatchers.RemoveTempIgnore(currentPath); + _libraryMonitor.ReportFileSystemChangeComplete(currentPath, false); } } } @@ -197,8 +197,8 @@ namespace MediaBrowser.Providers.Manager var parentFolder = Path.GetDirectoryName(path); - _directoryWatchers.TemporarilyIgnore(path); - _directoryWatchers.TemporarilyIgnore(parentFolder); + _libraryMonitor.ReportFileSystemChangeBeginning(path); + _libraryMonitor.ReportFileSystemChangeBeginning(parentFolder); try { @@ -223,8 +223,8 @@ namespace MediaBrowser.Providers.Manager } finally { - _directoryWatchers.RemoveTempIgnore(path); - _directoryWatchers.RemoveTempIgnore(parentFolder); + _libraryMonitor.ReportFileSystemChangeComplete(path, false); + _libraryMonitor.ReportFileSystemChangeComplete(parentFolder, false); } } diff --git a/MediaBrowser.Providers/Manager/MetadataService.cs b/MediaBrowser.Providers/Manager/MetadataService.cs index dbe70b93d7..5dde3098ac 100644 --- a/MediaBrowser.Providers/Manager/MetadataService.cs +++ b/MediaBrowser.Providers/Manager/MetadataService.cs @@ -38,7 +38,6 @@ namespace MediaBrowser.Providers.Manager public void AddParts(IEnumerable providers, IEnumerable imageProviders) { _providers = providers.OfType>() - .OrderBy(GetSortOrder) .ToArray(); _imageProviders = imageProviders.OrderBy(i => i.Order).ToArray(); @@ -179,21 +178,6 @@ namespace MediaBrowser.Providers.Manager return providers; } - /// - /// Gets the sort order. - /// - /// The provider. - /// System.Int32. - protected virtual int GetSortOrder(IMetadataProvider provider) - { - if (provider is IRemoteMetadataProvider) - { - return 1; - } - - return 0; - } - /// /// Determines whether this instance can refresh the specified provider. /// @@ -217,7 +201,7 @@ namespace MediaBrowser.Providers.Manager protected abstract Task SaveItem(TItemType item, ItemUpdateType reason, CancellationToken cancellationToken); - protected virtual ItemId GetId(TItemType item) + protected virtual ItemId GetId(IHasMetadata item) { return new ItemId { diff --git a/MediaBrowser.Providers/Manager/ProviderManager.cs b/MediaBrowser.Providers/Manager/ProviderManager.cs index ebad2e6e05..faad156698 100644 --- a/MediaBrowser.Providers/Manager/ProviderManager.cs +++ b/MediaBrowser.Providers/Manager/ProviderManager.cs @@ -36,7 +36,7 @@ namespace MediaBrowser.Providers.Manager /// /// The _directory watchers /// - private readonly IDirectoryWatchers _directoryWatchers; + private readonly ILibraryMonitor _libraryMonitor; /// /// Gets or sets the configuration manager. @@ -57,23 +57,23 @@ namespace MediaBrowser.Providers.Manager private readonly IItemRepository _itemRepo; - private IMetadataService[] _metadataServices = {}; + private IMetadataService[] _metadataServices = { }; /// /// Initializes a new instance of the class. /// /// The HTTP client. /// The configuration manager. - /// The directory watchers. + /// The directory watchers. /// The log manager. /// The file system. /// The item repo. - public ProviderManager(IHttpClient httpClient, IServerConfigurationManager configurationManager, IDirectoryWatchers directoryWatchers, ILogManager logManager, IFileSystem fileSystem, IItemRepository itemRepo) + public ProviderManager(IHttpClient httpClient, IServerConfigurationManager configurationManager, ILibraryMonitor libraryMonitor, ILogManager logManager, IFileSystem fileSystem, IItemRepository itemRepo) { _logger = logManager.GetLogger("ProviderManager"); _httpClient = httpClient; ConfigurationManager = configurationManager; - _directoryWatchers = directoryWatchers; + _libraryMonitor = libraryMonitor; _fileSystem = fileSystem; _itemRepo = itemRepo; } @@ -315,7 +315,7 @@ namespace MediaBrowser.Providers.Manager } //Tell the watchers to ignore - _directoryWatchers.TemporarilyIgnore(path); + _libraryMonitor.ReportFileSystemChangeBeginning(path); if (dataToSave.CanSeek) { @@ -338,7 +338,7 @@ namespace MediaBrowser.Providers.Manager finally { //Remove the ignore - _directoryWatchers.RemoveTempIgnore(path); + _libraryMonitor.ReportFileSystemChangeComplete(path, false); } } @@ -380,7 +380,7 @@ namespace MediaBrowser.Providers.Manager /// Task. public Task SaveImage(BaseItem item, Stream source, string mimeType, ImageType type, int? imageIndex, string sourceUrl, CancellationToken cancellationToken) { - return new ImageSaver(ConfigurationManager, _directoryWatchers, _fileSystem, _logger).SaveImage(item, source, mimeType, type, imageIndex, sourceUrl, cancellationToken); + return new ImageSaver(ConfigurationManager, _libraryMonitor, _fileSystem, _logger).SaveImage(item, source, mimeType, type, imageIndex, sourceUrl, cancellationToken); } /// diff --git a/MediaBrowser.Server.Implementations/FileOrganization/EpisodeFileOrganizer.cs b/MediaBrowser.Server.Implementations/FileOrganization/EpisodeFileOrganizer.cs index 0a2dd5ae04..a2e094e9aa 100644 --- a/MediaBrowser.Server.Implementations/FileOrganization/EpisodeFileOrganizer.cs +++ b/MediaBrowser.Server.Implementations/FileOrganization/EpisodeFileOrganizer.cs @@ -22,7 +22,7 @@ namespace MediaBrowser.Server.Implementations.FileOrganization { public class EpisodeFileOrganizer { - private readonly IDirectoryWatchers _directoryWatchers; + private readonly ILibraryMonitor _libraryMonitor; private readonly ILibraryManager _libraryManager; private readonly ILogger _logger; private readonly IFileSystem _fileSystem; @@ -31,14 +31,14 @@ namespace MediaBrowser.Server.Implementations.FileOrganization private readonly CultureInfo _usCulture = new CultureInfo("en-US"); - public EpisodeFileOrganizer(IFileOrganizationService organizationService, IServerConfigurationManager config, IFileSystem fileSystem, ILogger logger, ILibraryManager libraryManager, IDirectoryWatchers directoryWatchers) + public EpisodeFileOrganizer(IFileOrganizationService organizationService, IServerConfigurationManager config, IFileSystem fileSystem, ILogger logger, ILibraryManager libraryManager, ILibraryMonitor libraryMonitor) { _organizationService = organizationService; _config = config; _fileSystem = fileSystem; _logger = logger; _libraryManager = libraryManager; - _directoryWatchers = directoryWatchers; + _libraryMonitor = libraryMonitor; } public async Task OrganizeEpisodeFile(string path, TvFileOrganizationOptions options, bool overwriteExisting) @@ -174,6 +174,8 @@ namespace MediaBrowser.Server.Implementations.FileOrganization { _logger.Debug("Removing duplicate episode {0}", path); + _libraryMonitor.ReportFileSystemChangeBeginning(path); + try { File.Delete(path); @@ -182,6 +184,10 @@ namespace MediaBrowser.Server.Implementations.FileOrganization { _logger.ErrorException("Error removing duplicate episode", ex, path); } + finally + { + _libraryMonitor.ReportFileSystemChangeComplete(path, true); + } } } } @@ -232,7 +238,7 @@ namespace MediaBrowser.Server.Implementations.FileOrganization private void PerformFileSorting(TvFileOrganizationOptions options, FileOrganizationResult result) { - _directoryWatchers.TemporarilyIgnore(result.TargetPath); + _libraryMonitor.ReportFileSystemChangeBeginning(result.TargetPath); Directory.CreateDirectory(Path.GetDirectoryName(result.TargetPath)); @@ -264,7 +270,7 @@ namespace MediaBrowser.Server.Implementations.FileOrganization } finally { - _directoryWatchers.RemoveTempIgnore(result.TargetPath); + _libraryMonitor.ReportFileSystemChangeComplete(result.TargetPath, true); } if (copy) diff --git a/MediaBrowser.Server.Implementations/FileOrganization/FileOrganizationService.cs b/MediaBrowser.Server.Implementations/FileOrganization/FileOrganizationService.cs index bbd0f74e5b..518a7bb48f 100644 --- a/MediaBrowser.Server.Implementations/FileOrganization/FileOrganizationService.cs +++ b/MediaBrowser.Server.Implementations/FileOrganization/FileOrganizationService.cs @@ -21,17 +21,17 @@ namespace MediaBrowser.Server.Implementations.FileOrganization private readonly ITaskManager _taskManager; private readonly IFileOrganizationRepository _repo; private readonly ILogger _logger; - private readonly IDirectoryWatchers _directoryWatchers; + private readonly ILibraryMonitor _libraryMonitor; private readonly ILibraryManager _libraryManager; private readonly IServerConfigurationManager _config; private readonly IFileSystem _fileSystem; - public FileOrganizationService(ITaskManager taskManager, IFileOrganizationRepository repo, ILogger logger, IDirectoryWatchers directoryWatchers, ILibraryManager libraryManager, IServerConfigurationManager config, IFileSystem fileSystem) + public FileOrganizationService(ITaskManager taskManager, IFileOrganizationRepository repo, ILogger logger, ILibraryMonitor libraryMonitor, ILibraryManager libraryManager, IServerConfigurationManager config, IFileSystem fileSystem) { _taskManager = taskManager; _repo = repo; _logger = logger; - _directoryWatchers = directoryWatchers; + _libraryMonitor = libraryMonitor; _libraryManager = libraryManager; _config = config; _fileSystem = fileSystem; @@ -91,13 +91,10 @@ namespace MediaBrowser.Server.Implementations.FileOrganization } var organizer = new EpisodeFileOrganizer(this, _config, _fileSystem, _logger, _libraryManager, - _directoryWatchers); + _libraryMonitor); await organizer.OrganizeEpisodeFile(result.OriginalPath, _config.Configuration.TvFileOrganizationOptions, true) .ConfigureAwait(false); - - await _libraryManager.ValidateMediaLibrary(new Progress(), CancellationToken.None) - .ConfigureAwait(false); } public Task ClearLog() @@ -108,12 +105,9 @@ namespace MediaBrowser.Server.Implementations.FileOrganization public async Task PerformEpisodeOrganization(EpisodeFileOrganizationRequest request) { var organizer = new EpisodeFileOrganizer(this, _config, _fileSystem, _logger, _libraryManager, - _directoryWatchers); + _libraryMonitor); await organizer.OrganizeWithCorrection(request, _config.Configuration.TvFileOrganizationOptions).ConfigureAwait(false); - - await _libraryManager.ValidateMediaLibrary(new Progress(), CancellationToken.None) - .ConfigureAwait(false); } } } diff --git a/MediaBrowser.Server.Implementations/FileOrganization/OrganizerScheduledTask.cs b/MediaBrowser.Server.Implementations/FileOrganization/OrganizerScheduledTask.cs index 340038e4b0..3c5e1ed0ed 100644 --- a/MediaBrowser.Server.Implementations/FileOrganization/OrganizerScheduledTask.cs +++ b/MediaBrowser.Server.Implementations/FileOrganization/OrganizerScheduledTask.cs @@ -14,16 +14,16 @@ namespace MediaBrowser.Server.Implementations.FileOrganization { public class OrganizerScheduledTask : IScheduledTask, IConfigurableScheduledTask { - private readonly IDirectoryWatchers _directoryWatchers; + private readonly ILibraryMonitor _libraryMonitor; private readonly ILibraryManager _libraryManager; private readonly ILogger _logger; private readonly IFileSystem _fileSystem; private readonly IServerConfigurationManager _config; private readonly IFileOrganizationService _organizationService; - public OrganizerScheduledTask(IDirectoryWatchers directoryWatchers, ILibraryManager libraryManager, ILogger logger, IFileSystem fileSystem, IServerConfigurationManager config, IFileOrganizationService organizationService) + public OrganizerScheduledTask(ILibraryMonitor libraryMonitor, ILibraryManager libraryManager, ILogger logger, IFileSystem fileSystem, IServerConfigurationManager config, IFileOrganizationService organizationService) { - _directoryWatchers = directoryWatchers; + _libraryMonitor = libraryMonitor; _libraryManager = libraryManager; _logger = logger; _fileSystem = fileSystem; @@ -48,7 +48,7 @@ namespace MediaBrowser.Server.Implementations.FileOrganization public Task Execute(CancellationToken cancellationToken, IProgress progress) { - return new TvFolderOrganizer(_libraryManager, _logger, _fileSystem, _directoryWatchers, _organizationService, _config) + return new TvFolderOrganizer(_libraryManager, _logger, _fileSystem, _libraryMonitor, _organizationService, _config) .Organize(_config.Configuration.TvFileOrganizationOptions, cancellationToken, progress); } diff --git a/MediaBrowser.Server.Implementations/FileOrganization/TvFolderOrganizer.cs b/MediaBrowser.Server.Implementations/FileOrganization/TvFolderOrganizer.cs index 6a413f2f04..24f21e3398 100644 --- a/MediaBrowser.Server.Implementations/FileOrganization/TvFolderOrganizer.cs +++ b/MediaBrowser.Server.Implementations/FileOrganization/TvFolderOrganizer.cs @@ -18,19 +18,19 @@ namespace MediaBrowser.Server.Implementations.FileOrganization { public class TvFolderOrganizer { - private readonly IDirectoryWatchers _directoryWatchers; + private readonly ILibraryMonitor _libraryMonitor; private readonly ILibraryManager _libraryManager; private readonly ILogger _logger; private readonly IFileSystem _fileSystem; private readonly IFileOrganizationService _organizationService; private readonly IServerConfigurationManager _config; - public TvFolderOrganizer(ILibraryManager libraryManager, ILogger logger, IFileSystem fileSystem, IDirectoryWatchers directoryWatchers, IFileOrganizationService organizationService, IServerConfigurationManager config) + public TvFolderOrganizer(ILibraryManager libraryManager, ILogger logger, IFileSystem fileSystem, ILibraryMonitor libraryMonitor, IFileOrganizationService organizationService, IServerConfigurationManager config) { _libraryManager = libraryManager; _logger = logger; _fileSystem = fileSystem; - _directoryWatchers = directoryWatchers; + _libraryMonitor = libraryMonitor; _organizationService = organizationService; _config = config; } @@ -57,7 +57,7 @@ namespace MediaBrowser.Server.Implementations.FileOrganization foreach (var file in eligibleFiles) { var organizer = new EpisodeFileOrganizer(_organizationService, _config, _fileSystem, _logger, _libraryManager, - _directoryWatchers); + _libraryMonitor); var result = await organizer.OrganizeEpisodeFile(file.FullName, options, false).ConfigureAwait(false); diff --git a/MediaBrowser.Server.Implementations/IO/DirectoryWatchers.cs b/MediaBrowser.Server.Implementations/IO/DirectoryWatchers.cs deleted file mode 100644 index 1efc3bc70f..0000000000 --- a/MediaBrowser.Server.Implementations/IO/DirectoryWatchers.cs +++ /dev/null @@ -1,636 +0,0 @@ -using MediaBrowser.Common.IO; -using MediaBrowser.Common.ScheduledTasks; -using MediaBrowser.Controller.Configuration; -using MediaBrowser.Controller.Entities; -using MediaBrowser.Controller.IO; -using MediaBrowser.Controller.Library; -using MediaBrowser.Model.Entities; -using MediaBrowser.Model.Logging; -using MediaBrowser.Server.Implementations.ScheduledTasks; -using Microsoft.Win32; -using System; -using System.Collections.Concurrent; -using System.Collections.Generic; -using System.IO; -using System.Linq; -using System.Threading; -using System.Threading.Tasks; - -namespace MediaBrowser.Server.Implementations.IO -{ - /// - /// Class DirectoryWatchers - /// - public class DirectoryWatchers : IDirectoryWatchers - { - /// - /// The file system watchers - /// - private readonly ConcurrentDictionary _fileSystemWatchers = new ConcurrentDictionary(StringComparer.OrdinalIgnoreCase); - /// - /// The update timer - /// - private Timer _updateTimer; - /// - /// The affected paths - /// - private readonly ConcurrentDictionary _affectedPaths = new ConcurrentDictionary(); - - /// - /// A dynamic list of paths that should be ignored. Added to during our own file sytem modifications. - /// - private readonly ConcurrentDictionary _tempIgnoredPaths = new ConcurrentDictionary(StringComparer.OrdinalIgnoreCase); - - /// - /// Any file name ending in any of these will be ignored by the watchers - /// - private readonly IReadOnlyList _alwaysIgnoreFiles = new List { "thumbs.db", "small.jpg", "albumart.jpg" }; - - /// - /// The timer lock - /// - private readonly object _timerLock = new object(); - - /// - /// Add the path to our temporary ignore list. Use when writing to a path within our listening scope. - /// - /// The path. - public void TemporarilyIgnore(string path) - { - _tempIgnoredPaths[path] = path; - } - - /// - /// Removes the temp ignore. - /// - /// The path. - public async void RemoveTempIgnore(string path) - { - // This is an arbitraty amount of time, but delay it because file system writes often trigger events after RemoveTempIgnore has been called. - // Seeing long delays in some situations, especially over the network. - // Seeing delays up to 40 seconds, but not going to ignore changes for that long. - await Task.Delay(1500).ConfigureAwait(false); - - string val; - _tempIgnoredPaths.TryRemove(path, out val); - } - - /// - /// Gets or sets the logger. - /// - /// The logger. - private ILogger Logger { get; set; } - - /// - /// Gets or sets the task manager. - /// - /// The task manager. - private ITaskManager TaskManager { get; set; } - - private ILibraryManager LibraryManager { get; set; } - private IServerConfigurationManager ConfigurationManager { get; set; } - - private readonly IFileSystem _fileSystem; - - /// - /// Initializes a new instance of the class. - /// - public DirectoryWatchers(ILogManager logManager, ITaskManager taskManager, ILibraryManager libraryManager, IServerConfigurationManager configurationManager, IFileSystem fileSystem) - { - if (taskManager == null) - { - throw new ArgumentNullException("taskManager"); - } - - LibraryManager = libraryManager; - TaskManager = taskManager; - Logger = logManager.GetLogger("DirectoryWatchers"); - ConfigurationManager = configurationManager; - _fileSystem = fileSystem; - - SystemEvents.PowerModeChanged += SystemEvents_PowerModeChanged; - } - - /// - /// Handles the PowerModeChanged event of the SystemEvents control. - /// - /// The source of the event. - /// The instance containing the event data. - void SystemEvents_PowerModeChanged(object sender, PowerModeChangedEventArgs e) - { - Stop(); - Start(); - } - - /// - /// Starts this instance. - /// - public void Start() - { - LibraryManager.ItemAdded += LibraryManager_ItemAdded; - LibraryManager.ItemRemoved += LibraryManager_ItemRemoved; - - var pathsToWatch = new List { LibraryManager.RootFolder.Path }; - - var paths = LibraryManager - .RootFolder - .Children - .OfType() - .Where(i => i.LocationType != LocationType.Remote && i.LocationType != LocationType.Virtual) - .SelectMany(f => - { - try - { - // Accessing ResolveArgs could involve file system access - return f.ResolveArgs.PhysicalLocations; - } - catch (IOException) - { - return new string[] { }; - } - - }) - .Distinct(StringComparer.OrdinalIgnoreCase) - .OrderBy(i => i) - .ToList(); - - foreach (var path in paths) - { - if (!ContainsParentFolder(pathsToWatch, path)) - { - pathsToWatch.Add(path); - } - } - - foreach (var path in pathsToWatch) - { - StartWatchingPath(path); - } - } - - /// - /// Handles the ItemRemoved event of the LibraryManager control. - /// - /// The source of the event. - /// The instance containing the event data. - void LibraryManager_ItemRemoved(object sender, ItemChangeEventArgs e) - { - if (e.Item.Parent is AggregateFolder) - { - StopWatchingPath(e.Item.Path); - } - } - - /// - /// Handles the ItemAdded event of the LibraryManager control. - /// - /// The source of the event. - /// The instance containing the event data. - void LibraryManager_ItemAdded(object sender, ItemChangeEventArgs e) - { - if (e.Item.Parent is AggregateFolder) - { - StartWatchingPath(e.Item.Path); - } - } - - /// - /// Examine a list of strings assumed to be file paths to see if it contains a parent of - /// the provided path. - /// - /// The LST. - /// The path. - /// true if [contains parent folder] [the specified LST]; otherwise, false. - /// path - private static bool ContainsParentFolder(IEnumerable lst, string path) - { - if (string.IsNullOrEmpty(path)) - { - throw new ArgumentNullException("path"); - } - - path = path.TrimEnd(Path.DirectorySeparatorChar); - - return lst.Any(str => - { - //this should be a little quicker than examining each actual parent folder... - var compare = str.TrimEnd(Path.DirectorySeparatorChar); - - return (path.Equals(compare, StringComparison.OrdinalIgnoreCase) || (path.StartsWith(compare, StringComparison.OrdinalIgnoreCase) && path[compare.Length] == Path.DirectorySeparatorChar)); - }); - } - - /// - /// Starts the watching path. - /// - /// The path. - private void StartWatchingPath(string path) - { - // Creating a FileSystemWatcher over the LAN can take hundreds of milliseconds, so wrap it in a Task to do them all in parallel - Task.Run(() => - { - var newWatcher = new FileSystemWatcher(path, "*") { IncludeSubdirectories = true, InternalBufferSize = 32767 }; - - newWatcher.Created += watcher_Changed; - newWatcher.Deleted += watcher_Changed; - newWatcher.Renamed += watcher_Changed; - newWatcher.Changed += watcher_Changed; - - newWatcher.Error += watcher_Error; - - try - { - if (_fileSystemWatchers.TryAdd(path, newWatcher)) - { - newWatcher.EnableRaisingEvents = true; - Logger.Info("Watching directory " + path); - } - else - { - Logger.Info("Unable to add directory watcher for {0}. It already exists in the dictionary." + path); - newWatcher.Dispose(); - } - - } - catch (IOException ex) - { - Logger.ErrorException("Error watching path: {0}", ex, path); - } - catch (PlatformNotSupportedException ex) - { - Logger.ErrorException("Error watching path: {0}", ex, path); - } - }); - } - - /// - /// Stops the watching path. - /// - /// The path. - private void StopWatchingPath(string path) - { - FileSystemWatcher watcher; - - if (_fileSystemWatchers.TryGetValue(path, out watcher)) - { - DisposeWatcher(watcher); - } - } - - /// - /// Disposes the watcher. - /// - /// The watcher. - private void DisposeWatcher(FileSystemWatcher watcher) - { - Logger.Info("Stopping directory watching for path {0}", watcher.Path); - - watcher.EnableRaisingEvents = false; - watcher.Dispose(); - - RemoveWatcherFromList(watcher); - } - - /// - /// Removes the watcher from list. - /// - /// The watcher. - private void RemoveWatcherFromList(FileSystemWatcher watcher) - { - FileSystemWatcher removed; - - _fileSystemWatchers.TryRemove(watcher.Path, out removed); - } - - /// - /// Handles the Error event of the watcher control. - /// - /// The source of the event. - /// The instance containing the event data. - void watcher_Error(object sender, ErrorEventArgs e) - { - var ex = e.GetException(); - var dw = (FileSystemWatcher)sender; - - Logger.ErrorException("Error in Directory watcher for: " + dw.Path, ex); - - DisposeWatcher(dw); - } - - /// - /// Handles the Changed event of the watcher control. - /// - /// The source of the event. - /// The instance containing the event data. - void watcher_Changed(object sender, FileSystemEventArgs e) - { - try - { - OnWatcherChanged(e); - } - catch (IOException ex) - { - Logger.ErrorException("IOException in watcher changed. Path: {0}", ex, e.FullPath); - } - } - - private void OnWatcherChanged(FileSystemEventArgs e) - { - var name = e.Name; - - // Ignore certain files - if (_alwaysIgnoreFiles.Contains(name, StringComparer.OrdinalIgnoreCase)) - { - return; - } - - var nameFromFullPath = Path.GetFileName(e.FullPath); - // Ignore certain files - if (!string.IsNullOrEmpty(nameFromFullPath) && _alwaysIgnoreFiles.Contains(nameFromFullPath, StringComparer.OrdinalIgnoreCase)) - { - return; - } - - // Ignore when someone manually creates a new folder - if (e.ChangeType == WatcherChangeTypes.Created && name == "New folder") - { - return; - } - - var tempIgnorePaths = _tempIgnoredPaths.Keys.ToList(); - - // If the parent of an ignored path has a change event, ignore that too - if (tempIgnorePaths.Any(i => - { - if (string.Equals(i, e.FullPath, StringComparison.OrdinalIgnoreCase)) - { - Logger.Debug("Watcher ignoring change to {0}", e.FullPath); - return true; - } - - // Go up a level - var parent = Path.GetDirectoryName(i); - if (string.Equals(parent, e.FullPath, StringComparison.OrdinalIgnoreCase)) - { - Logger.Debug("Watcher ignoring change to {0}", e.FullPath); - return true; - } - - // Go up another level - if (!string.IsNullOrEmpty(parent)) - { - parent = Path.GetDirectoryName(i); - if (string.Equals(parent, e.FullPath, StringComparison.OrdinalIgnoreCase)) - { - Logger.Debug("Watcher ignoring change to {0}", e.FullPath); - return true; - } - } - - if (i.StartsWith(e.FullPath, StringComparison.OrdinalIgnoreCase) || - e.FullPath.StartsWith(i, StringComparison.OrdinalIgnoreCase)) - { - Logger.Debug("Watcher ignoring change to {0}", e.FullPath); - return true; - } - - return false; - - })) - { - return; - } - - Logger.Info("Watcher sees change of type " + e.ChangeType + " to " + e.FullPath); - - //Since we're watching created, deleted and renamed we always want the parent of the item to be the affected path - var affectedPath = e.FullPath; - - _affectedPaths.AddOrUpdate(affectedPath, affectedPath, (key, oldValue) => affectedPath); - - lock (_timerLock) - { - if (_updateTimer == null) - { - _updateTimer = new Timer(TimerStopped, null, TimeSpan.FromSeconds(ConfigurationManager.Configuration.FileWatcherDelay), TimeSpan.FromMilliseconds(-1)); - } - else - { - _updateTimer.Change(TimeSpan.FromSeconds(ConfigurationManager.Configuration.FileWatcherDelay), TimeSpan.FromMilliseconds(-1)); - } - } - } - - /// - /// Timers the stopped. - /// - /// The state info. - private async void TimerStopped(object stateInfo) - { - lock (_timerLock) - { - // Extend the timer as long as any of the paths are still being written to. - if (_affectedPaths.Any(p => IsFileLocked(p.Key))) - { - Logger.Info("Timer extended."); - _updateTimer.Change(TimeSpan.FromSeconds(ConfigurationManager.Configuration.FileWatcherDelay), TimeSpan.FromMilliseconds(-1)); - return; - } - - Logger.Info("Timer stopped."); - - if (_updateTimer != null) - { - _updateTimer.Dispose(); - _updateTimer = null; - } - } - - var paths = _affectedPaths.Keys.ToList(); - _affectedPaths.Clear(); - - await ProcessPathChanges(paths).ConfigureAwait(false); - } - - /// - /// Try and determine if a file is locked - /// This is not perfect, and is subject to race conditions, so I'd rather not make this a re-usable library method. - /// - /// The path. - /// true if [is file locked] [the specified path]; otherwise, false. - private bool IsFileLocked(string path) - { - try - { - var data = _fileSystem.GetFileSystemInfo(path); - - if (!data.Exists - || data.Attributes.HasFlag(FileAttributes.Directory) - || data.Attributes.HasFlag(FileAttributes.ReadOnly)) - { - return false; - } - } - catch (IOException) - { - return false; - } - - try - { - using (_fileSystem.GetFileStream(path, FileMode.Open, FileAccess.ReadWrite, FileShare.ReadWrite)) - { - //file is not locked - return false; - } - } - catch (DirectoryNotFoundException) - { - return false; - } - catch (FileNotFoundException) - { - return false; - } - catch (IOException) - { - //the file is unavailable because it is: - //still being written to - //or being processed by another thread - //or does not exist (has already been processed) - Logger.Debug("{0} is locked.", path); - return true; - } - catch - { - return false; - } - } - - /// - /// Processes the path changes. - /// - /// The paths. - /// Task. - private async Task ProcessPathChanges(List paths) - { - var itemsToRefresh = paths.Select(Path.GetDirectoryName) - .Select(GetAffectedBaseItem) - .Where(item => item != null) - .Distinct() - .ToList(); - - foreach (var p in paths) Logger.Info(p + " reports change."); - - // If the root folder changed, run the library task so the user can see it - if (itemsToRefresh.Any(i => i is AggregateFolder)) - { - TaskManager.CancelIfRunningAndQueue(); - return; - } - - foreach (var item in itemsToRefresh) - { - Logger.Info(item.Name + " (" + item.Path + ") will be refreshed."); - - try - { - await item.ChangedExternally().ConfigureAwait(false); - } - catch (IOException ex) - { - // For now swallow and log. - // Research item: If an IOException occurs, the item may be in a disconnected state (media unavailable) - // Should we remove it from it's parent? - Logger.ErrorException("Error refreshing {0}", ex, item.Name); - } - catch (Exception ex) - { - Logger.ErrorException("Error refreshing {0}", ex, item.Name); - } - } - } - - /// - /// Gets the affected base item. - /// - /// The path. - /// BaseItem. - private BaseItem GetAffectedBaseItem(string path) - { - BaseItem item = null; - - while (item == null && !string.IsNullOrEmpty(path)) - { - item = LibraryManager.RootFolder.FindByPath(path); - - path = Path.GetDirectoryName(path); - } - - if (item != null) - { - // If the item has been deleted find the first valid parent that still exists - while (!Directory.Exists(item.Path) && !File.Exists(item.Path)) - { - item = item.Parent; - - if (item == null) - { - break; - } - } - } - - return item; - } - - /// - /// Stops this instance. - /// - public void Stop() - { - LibraryManager.ItemAdded -= LibraryManager_ItemAdded; - LibraryManager.ItemRemoved -= LibraryManager_ItemRemoved; - - foreach (var watcher in _fileSystemWatchers.Values.ToList()) - { - watcher.Changed -= watcher_Changed; - watcher.EnableRaisingEvents = false; - watcher.Dispose(); - } - - lock (_timerLock) - { - if (_updateTimer != null) - { - _updateTimer.Dispose(); - _updateTimer = null; - } - } - - _fileSystemWatchers.Clear(); - _affectedPaths.Clear(); - } - - /// - /// Performs application-defined tasks associated with freeing, releasing, or resetting unmanaged resources. - /// - public void Dispose() - { - Dispose(true); - GC.SuppressFinalize(this); - } - - /// - /// Releases unmanaged and - optionally - managed resources. - /// - /// true to release both managed and unmanaged resources; false to release only unmanaged resources. - protected virtual void Dispose(bool dispose) - { - if (dispose) - { - Stop(); - } - } - } -} diff --git a/MediaBrowser.Server.Implementations/IO/LibraryMonitor.cs b/MediaBrowser.Server.Implementations/IO/LibraryMonitor.cs new file mode 100644 index 0000000000..e09e667651 --- /dev/null +++ b/MediaBrowser.Server.Implementations/IO/LibraryMonitor.cs @@ -0,0 +1,569 @@ +using MediaBrowser.Common.ScheduledTasks; +using MediaBrowser.Controller.Configuration; +using MediaBrowser.Controller.Entities; +using MediaBrowser.Controller.Library; +using MediaBrowser.Model.Entities; +using MediaBrowser.Model.Logging; +using MediaBrowser.Server.Implementations.ScheduledTasks; +using Microsoft.Win32; +using System; +using System.Collections.Concurrent; +using System.Collections.Generic; +using System.IO; +using System.Linq; +using System.Threading; +using System.Threading.Tasks; + +namespace MediaBrowser.Server.Implementations.IO +{ + public class LibraryMonitor : ILibraryMonitor + { + /// + /// The file system watchers + /// + private readonly ConcurrentDictionary _fileSystemWatchers = new ConcurrentDictionary(StringComparer.OrdinalIgnoreCase); + /// + /// The update timer + /// + private Timer _updateTimer; + /// + /// The affected paths + /// + private readonly ConcurrentDictionary _affectedPaths = new ConcurrentDictionary(); + + /// + /// A dynamic list of paths that should be ignored. Added to during our own file sytem modifications. + /// + private readonly ConcurrentDictionary _tempIgnoredPaths = new ConcurrentDictionary(StringComparer.OrdinalIgnoreCase); + + /// + /// Any file name ending in any of these will be ignored by the watchers + /// + private readonly IReadOnlyList _alwaysIgnoreFiles = new List { "thumbs.db", "small.jpg", "albumart.jpg" }; + + /// + /// The timer lock + /// + private readonly object _timerLock = new object(); + + /// + /// Add the path to our temporary ignore list. Use when writing to a path within our listening scope. + /// + /// The path. + private void TemporarilyIgnore(string path) + { + _tempIgnoredPaths[path] = path; + } + + /// + /// Removes the temp ignore. + /// + /// The path. + private async void RemoveTempIgnore(string path) + { + // This is an arbitraty amount of time, but delay it because file system writes often trigger events after RemoveTempIgnore has been called. + // Seeing long delays in some situations, especially over the network. + // Seeing delays up to 40 seconds, but not going to ignore changes for that long. + await Task.Delay(1500).ConfigureAwait(false); + + string val; + _tempIgnoredPaths.TryRemove(path, out val); + } + + public void ReportFileSystemChangeBeginning(string path) + { + TemporarilyIgnore(path); + } + + public void ReportFileSystemChangeComplete(string path, bool refreshPath) + { + RemoveTempIgnore(path); + + if (refreshPath) + { + ReportFileSystemChanged(path); + } + } + + /// + /// Gets or sets the logger. + /// + /// The logger. + private ILogger Logger { get; set; } + + /// + /// Gets or sets the task manager. + /// + /// The task manager. + private ITaskManager TaskManager { get; set; } + + private ILibraryManager LibraryManager { get; set; } + private IServerConfigurationManager ConfigurationManager { get; set; } + + /// + /// Initializes a new instance of the class. + /// + public LibraryMonitor(ILogManager logManager, ITaskManager taskManager, ILibraryManager libraryManager, IServerConfigurationManager configurationManager) + { + if (taskManager == null) + { + throw new ArgumentNullException("taskManager"); + } + + LibraryManager = libraryManager; + TaskManager = taskManager; + Logger = logManager.GetLogger(GetType().Name); + ConfigurationManager = configurationManager; + + SystemEvents.PowerModeChanged += SystemEvents_PowerModeChanged; + } + + /// + /// Handles the PowerModeChanged event of the SystemEvents control. + /// + /// The source of the event. + /// The instance containing the event data. + void SystemEvents_PowerModeChanged(object sender, PowerModeChangedEventArgs e) + { + Stop(); + Start(); + } + + /// + /// Starts this instance. + /// + public void Start() + { + LibraryManager.ItemAdded += LibraryManager_ItemAdded; + LibraryManager.ItemRemoved += LibraryManager_ItemRemoved; + + var pathsToWatch = new List { LibraryManager.RootFolder.Path }; + + var paths = LibraryManager + .RootFolder + .Children + .OfType() + .Where(i => i.LocationType != LocationType.Remote && i.LocationType != LocationType.Virtual) + .SelectMany(f => + { + try + { + // Accessing ResolveArgs could involve file system access + return f.ResolveArgs.PhysicalLocations; + } + catch (IOException) + { + return new string[] { }; + } + + }) + .Distinct(StringComparer.OrdinalIgnoreCase) + .OrderBy(i => i) + .ToList(); + + foreach (var path in paths) + { + if (!ContainsParentFolder(pathsToWatch, path)) + { + pathsToWatch.Add(path); + } + } + + foreach (var path in pathsToWatch) + { + StartWatchingPath(path); + } + } + + /// + /// Handles the ItemRemoved event of the LibraryManager control. + /// + /// The source of the event. + /// The instance containing the event data. + void LibraryManager_ItemRemoved(object sender, ItemChangeEventArgs e) + { + if (e.Item.Parent is AggregateFolder) + { + StopWatchingPath(e.Item.Path); + } + } + + /// + /// Handles the ItemAdded event of the LibraryManager control. + /// + /// The source of the event. + /// The instance containing the event data. + void LibraryManager_ItemAdded(object sender, ItemChangeEventArgs e) + { + if (e.Item.Parent is AggregateFolder) + { + StartWatchingPath(e.Item.Path); + } + } + + /// + /// Examine a list of strings assumed to be file paths to see if it contains a parent of + /// the provided path. + /// + /// The LST. + /// The path. + /// true if [contains parent folder] [the specified LST]; otherwise, false. + /// path + private static bool ContainsParentFolder(IEnumerable lst, string path) + { + if (string.IsNullOrEmpty(path)) + { + throw new ArgumentNullException("path"); + } + + path = path.TrimEnd(Path.DirectorySeparatorChar); + + return lst.Any(str => + { + //this should be a little quicker than examining each actual parent folder... + var compare = str.TrimEnd(Path.DirectorySeparatorChar); + + return (path.Equals(compare, StringComparison.OrdinalIgnoreCase) || (path.StartsWith(compare, StringComparison.OrdinalIgnoreCase) && path[compare.Length] == Path.DirectorySeparatorChar)); + }); + } + + /// + /// Starts the watching path. + /// + /// The path. + private void StartWatchingPath(string path) + { + // Creating a FileSystemWatcher over the LAN can take hundreds of milliseconds, so wrap it in a Task to do them all in parallel + Task.Run(() => + { + var newWatcher = new FileSystemWatcher(path, "*") { IncludeSubdirectories = true, InternalBufferSize = 32767 }; + + newWatcher.Created += watcher_Changed; + newWatcher.Deleted += watcher_Changed; + newWatcher.Renamed += watcher_Changed; + newWatcher.Changed += watcher_Changed; + + newWatcher.Error += watcher_Error; + + try + { + if (_fileSystemWatchers.TryAdd(path, newWatcher)) + { + newWatcher.EnableRaisingEvents = true; + Logger.Info("Watching directory " + path); + } + else + { + Logger.Info("Unable to add directory watcher for {0}. It already exists in the dictionary." + path); + newWatcher.Dispose(); + } + + } + catch (IOException ex) + { + Logger.ErrorException("Error watching path: {0}", ex, path); + } + catch (PlatformNotSupportedException ex) + { + Logger.ErrorException("Error watching path: {0}", ex, path); + } + }); + } + + /// + /// Stops the watching path. + /// + /// The path. + private void StopWatchingPath(string path) + { + FileSystemWatcher watcher; + + if (_fileSystemWatchers.TryGetValue(path, out watcher)) + { + DisposeWatcher(watcher); + } + } + + /// + /// Disposes the watcher. + /// + /// The watcher. + private void DisposeWatcher(FileSystemWatcher watcher) + { + Logger.Info("Stopping directory watching for path {0}", watcher.Path); + + watcher.EnableRaisingEvents = false; + watcher.Dispose(); + + RemoveWatcherFromList(watcher); + } + + /// + /// Removes the watcher from list. + /// + /// The watcher. + private void RemoveWatcherFromList(FileSystemWatcher watcher) + { + FileSystemWatcher removed; + + _fileSystemWatchers.TryRemove(watcher.Path, out removed); + } + + /// + /// Handles the Error event of the watcher control. + /// + /// The source of the event. + /// The instance containing the event data. + void watcher_Error(object sender, ErrorEventArgs e) + { + var ex = e.GetException(); + var dw = (FileSystemWatcher)sender; + + Logger.ErrorException("Error in Directory watcher for: " + dw.Path, ex); + + DisposeWatcher(dw); + } + + /// + /// Handles the Changed event of the watcher control. + /// + /// The source of the event. + /// The instance containing the event data. + void watcher_Changed(object sender, FileSystemEventArgs e) + { + try + { + OnWatcherChanged(e); + } + catch (Exception ex) + { + Logger.ErrorException("Exception in watcher changed. Path: {0}", ex, e.FullPath); + } + } + + private void OnWatcherChanged(FileSystemEventArgs e) + { + Logger.Debug("Watcher sees change of type " + e.ChangeType + " to " + e.FullPath); + + ReportFileSystemChanged(e.FullPath); + } + + public void ReportFileSystemChanged(string path) + { + var filename = Path.GetFileName(path); + + // Ignore certain files + if (!string.IsNullOrEmpty(filename) && _alwaysIgnoreFiles.Contains(filename, StringComparer.OrdinalIgnoreCase)) + { + return; + } + + var tempIgnorePaths = _tempIgnoredPaths.Keys.ToList(); + + // If the parent of an ignored path has a change event, ignore that too + if (tempIgnorePaths.Any(i => + { + if (string.Equals(i, path, StringComparison.OrdinalIgnoreCase)) + { + Logger.Debug("Ignoring change to {0}", path); + return true; + } + + // Go up a level + var parent = Path.GetDirectoryName(i); + if (string.Equals(parent, path, StringComparison.OrdinalIgnoreCase)) + { + Logger.Debug("Ignoring change to {0}", path); + return true; + } + + // Go up another level + if (!string.IsNullOrEmpty(parent)) + { + parent = Path.GetDirectoryName(i); + if (string.Equals(parent, path, StringComparison.OrdinalIgnoreCase)) + { + Logger.Debug("Ignoring change to {0}", path); + return true; + } + } + + if (i.StartsWith(path, StringComparison.OrdinalIgnoreCase) || + path.StartsWith(i, StringComparison.OrdinalIgnoreCase)) + { + Logger.Debug("Ignoring change to {0}", path); + return true; + } + + return false; + + })) + { + return; + } + + // Avoid implicitly captured closure + var affectedPath = path; + _affectedPaths.AddOrUpdate(path, path, (key, oldValue) => affectedPath); + + lock (_timerLock) + { + if (_updateTimer == null) + { + _updateTimer = new Timer(TimerStopped, null, TimeSpan.FromSeconds(ConfigurationManager.Configuration.RealtimeWatcherDelay), TimeSpan.FromMilliseconds(-1)); + } + else + { + _updateTimer.Change(TimeSpan.FromSeconds(ConfigurationManager.Configuration.RealtimeWatcherDelay), TimeSpan.FromMilliseconds(-1)); + } + } + } + + /// + /// Timers the stopped. + /// + /// The state info. + private async void TimerStopped(object stateInfo) + { + Logger.Debug("Timer stopped."); + + DisposeTimer(); + + var paths = _affectedPaths.Keys.ToList(); + _affectedPaths.Clear(); + + await ProcessPathChanges(paths).ConfigureAwait(false); + } + + private void DisposeTimer() + { + lock (_timerLock) + { + if (_updateTimer != null) + { + _updateTimer.Dispose(); + _updateTimer = null; + } + } + } + + /// + /// Processes the path changes. + /// + /// The paths. + /// Task. + private async Task ProcessPathChanges(List paths) + { + var itemsToRefresh = paths.Select(Path.GetDirectoryName) + .Select(GetAffectedBaseItem) + .Where(item => item != null) + .Distinct() + .ToList(); + + foreach (var p in paths) Logger.Info(p + " reports change."); + + // If the root folder changed, run the library task so the user can see it + if (itemsToRefresh.Any(i => i is AggregateFolder)) + { + TaskManager.CancelIfRunningAndQueue(); + return; + } + + foreach (var item in itemsToRefresh) + { + Logger.Info(item.Name + " (" + item.Path + ") will be refreshed."); + + try + { + await item.ChangedExternally().ConfigureAwait(false); + } + catch (IOException ex) + { + // For now swallow and log. + // Research item: If an IOException occurs, the item may be in a disconnected state (media unavailable) + // Should we remove it from it's parent? + Logger.ErrorException("Error refreshing {0}", ex, item.Name); + } + catch (Exception ex) + { + Logger.ErrorException("Error refreshing {0}", ex, item.Name); + } + } + } + + /// + /// Gets the affected base item. + /// + /// The path. + /// BaseItem. + private BaseItem GetAffectedBaseItem(string path) + { + BaseItem item = null; + + while (item == null && !string.IsNullOrEmpty(path)) + { + item = LibraryManager.RootFolder.FindByPath(path); + + path = Path.GetDirectoryName(path); + } + + if (item != null) + { + // If the item has been deleted find the first valid parent that still exists + while (!Directory.Exists(item.Path) && !File.Exists(item.Path)) + { + item = item.Parent; + + if (item == null) + { + break; + } + } + } + + return item; + } + + /// + /// Stops this instance. + /// + public void Stop() + { + LibraryManager.ItemAdded -= LibraryManager_ItemAdded; + LibraryManager.ItemRemoved -= LibraryManager_ItemRemoved; + + foreach (var watcher in _fileSystemWatchers.Values.ToList()) + { + watcher.Changed -= watcher_Changed; + watcher.EnableRaisingEvents = false; + watcher.Dispose(); + } + + DisposeTimer(); + + _fileSystemWatchers.Clear(); + _affectedPaths.Clear(); + } + + /// + /// Performs application-defined tasks associated with freeing, releasing, or resetting unmanaged resources. + /// + public void Dispose() + { + Dispose(true); + GC.SuppressFinalize(this); + } + + /// + /// Releases unmanaged and - optionally - managed resources. + /// + /// true to release both managed and unmanaged resources; false to release only unmanaged resources. + protected virtual void Dispose(bool dispose) + { + if (dispose) + { + Stop(); + } + } + } +} diff --git a/MediaBrowser.Server.Implementations/Library/LibraryManager.cs b/MediaBrowser.Server.Implementations/Library/LibraryManager.cs index 736c70ad59..04344553fc 100644 --- a/MediaBrowser.Server.Implementations/Library/LibraryManager.cs +++ b/MediaBrowser.Server.Implementations/Library/LibraryManager.cs @@ -137,7 +137,7 @@ namespace MediaBrowser.Server.Implementations.Library private IEnumerable _savers; - private readonly Func _directoryWatchersFactory; + private readonly Func _libraryMonitorFactory; /// /// The _library items cache @@ -180,14 +180,14 @@ namespace MediaBrowser.Server.Implementations.Library /// The user manager. /// The configuration manager. /// The user data repository. - public LibraryManager(ILogger logger, ITaskManager taskManager, IUserManager userManager, IServerConfigurationManager configurationManager, IUserDataManager userDataRepository, Func directoryWatchersFactory, IFileSystem fileSystem) + public LibraryManager(ILogger logger, ITaskManager taskManager, IUserManager userManager, IServerConfigurationManager configurationManager, IUserDataManager userDataRepository, Func libraryMonitorFactory, IFileSystem fileSystem) { _logger = logger; _taskManager = taskManager; _userManager = userManager; ConfigurationManager = configurationManager; _userDataRepository = userDataRepository; - _directoryWatchersFactory = directoryWatchersFactory; + _libraryMonitorFactory = libraryMonitorFactory; _fileSystem = fileSystem; ByReferenceItems = new ConcurrentDictionary(); @@ -934,7 +934,7 @@ namespace MediaBrowser.Server.Implementations.Library /// Task. public async Task ValidateMediaLibraryInternal(IProgress progress, CancellationToken cancellationToken) { - _directoryWatchersFactory().Stop(); + _libraryMonitorFactory().Stop(); try { @@ -942,7 +942,7 @@ namespace MediaBrowser.Server.Implementations.Library } finally { - _directoryWatchersFactory().Start(); + _libraryMonitorFactory().Start(); } } @@ -1462,13 +1462,13 @@ namespace MediaBrowser.Server.Implementations.Library var semaphore = _fileLocks.GetOrAdd(path, key => new SemaphoreSlim(1, 1)); - var directoryWatchers = _directoryWatchersFactory(); + var directoryWatchers = _libraryMonitorFactory(); await semaphore.WaitAsync().ConfigureAwait(false); try { - directoryWatchers.TemporarilyIgnore(path); + directoryWatchers.ReportFileSystemChangeBeginning(path); saver.Save(item, CancellationToken.None); } catch (Exception ex) @@ -1477,7 +1477,7 @@ namespace MediaBrowser.Server.Implementations.Library } finally { - directoryWatchers.RemoveTempIgnore(path); + directoryWatchers.ReportFileSystemChangeComplete(path, false); semaphore.Release(); } } diff --git a/MediaBrowser.Server.Implementations/MediaBrowser.Server.Implementations.csproj b/MediaBrowser.Server.Implementations/MediaBrowser.Server.Implementations.csproj index a92ec29d51..fe4283368c 100644 --- a/MediaBrowser.Server.Implementations/MediaBrowser.Server.Implementations.csproj +++ b/MediaBrowser.Server.Implementations/MediaBrowser.Server.Implementations.csproj @@ -137,7 +137,7 @@ - + diff --git a/MediaBrowser.ServerApplication/ApplicationHost.cs b/MediaBrowser.ServerApplication/ApplicationHost.cs index 3553996b06..0c8a6b923b 100644 --- a/MediaBrowser.ServerApplication/ApplicationHost.cs +++ b/MediaBrowser.ServerApplication/ApplicationHost.cs @@ -137,7 +137,7 @@ namespace MediaBrowser.ServerApplication /// Gets or sets the directory watchers. /// /// The directory watchers. - private IDirectoryWatchers DirectoryWatchers { get; set; } + private ILibraryMonitor LibraryMonitor { get; set; } /// /// Gets or sets the provider manager. /// @@ -273,13 +273,13 @@ namespace MediaBrowser.ServerApplication UserManager = new UserManager(Logger, ServerConfigurationManager, UserRepository); RegisterSingleInstance(UserManager); - LibraryManager = new LibraryManager(Logger, TaskManager, UserManager, ServerConfigurationManager, UserDataManager, () => DirectoryWatchers, FileSystemManager); + LibraryManager = new LibraryManager(Logger, TaskManager, UserManager, ServerConfigurationManager, UserDataManager, () => LibraryMonitor, FileSystemManager); RegisterSingleInstance(LibraryManager); - DirectoryWatchers = new DirectoryWatchers(LogManager, TaskManager, LibraryManager, ServerConfigurationManager, FileSystemManager); - RegisterSingleInstance(DirectoryWatchers); + LibraryMonitor = new LibraryMonitor(LogManager, TaskManager, LibraryManager, ServerConfigurationManager); + RegisterSingleInstance(LibraryMonitor); - ProviderManager = new ProviderManager(HttpClient, ServerConfigurationManager, DirectoryWatchers, LogManager, FileSystemManager, ItemRepository); + ProviderManager = new ProviderManager(HttpClient, ServerConfigurationManager, LibraryMonitor, LogManager, FileSystemManager, ItemRepository); RegisterSingleInstance(ProviderManager); RegisterSingleInstance(() => new SearchEngine(LogManager, LibraryManager, UserManager)); @@ -306,7 +306,7 @@ namespace MediaBrowser.ServerApplication var newsService = new Server.Implementations.News.NewsService(ApplicationPaths, JsonSerializer); RegisterSingleInstance(newsService); - var fileOrganizationService = new FileOrganizationService(TaskManager, FileOrganizationRepository, Logger, DirectoryWatchers, LibraryManager, ServerConfigurationManager, FileSystemManager); + var fileOrganizationService = new FileOrganizationService(TaskManager, FileOrganizationRepository, Logger, LibraryMonitor, LibraryManager, ServerConfigurationManager, FileSystemManager); RegisterSingleInstance(fileOrganizationService); progress.Report(15); -- cgit v1.2.3 From d864ed87f5d2ec1ba19aaae580defdd63c7959d4 Mon Sep 17 00:00:00 2001 From: Luke Pulverenti Date: Tue, 28 Jan 2014 20:47:47 -0500 Subject: fixes #677 - Support back images --- MediaBrowser.Providers/Manager/ImageSaver.cs | 3 +++ 1 file changed, 3 insertions(+) (limited to 'MediaBrowser.Providers/Manager/ImageSaver.cs') diff --git a/MediaBrowser.Providers/Manager/ImageSaver.cs b/MediaBrowser.Providers/Manager/ImageSaver.cs index 56a1a9d4fe..2decba1610 100644 --- a/MediaBrowser.Providers/Manager/ImageSaver.cs +++ b/MediaBrowser.Providers/Manager/ImageSaver.cs @@ -348,6 +348,9 @@ namespace MediaBrowser.Providers.Manager case ImageType.Art: filename = "clearart"; break; + case ImageType.BoxRear: + filename = "back"; + break; case ImageType.Disc: filename = item is MusicAlbum ? "cdart" : "disc"; break; -- cgit v1.2.3