From d270b10db67eeaa16a35920d5d86b1975dfffd16 Mon Sep 17 00:00:00 2001 From: Luke Pulverenti Date: Thu, 27 Jul 2017 01:18:39 -0400 Subject: [PATCH 1/2] move auto-organize to plugin --- Emby.Server.Core/ApplicationHost.cs | 22 - .../Data/SqliteFileOrganizationRepository.cs | 284 ------ .../Emby.Server.Implementations.csproj | 8 - .../FileOrganization/EpisodeFileOrganizer.cs | 813 ------------------ .../FileOrganization/Extensions.cs | 33 - .../FileOrganizationNotifier.cs | 80 -- .../FileOrganizationService.cs | 283 ------ .../FileOrganization/NameUtils.cs | 81 -- .../OrganizerScheduledTask.cs | 101 --- .../FileOrganization/TvFolderOrganizer.cs | 236 ----- .../LiveTv/EmbyTV/EmbyTV.cs | 6 +- .../Library/FileOrganizationService.cs | 213 ----- MediaBrowser.Api/MediaBrowser.Api.csproj | 1 - .../IFileOrganizationService.cs | 107 --- .../MediaBrowser.Controller.csproj | 2 - .../IFileOrganizationRepository.cs | 45 - .../Encoder/MediaEncoder.cs | 33 +- .../FileOrganization/AutoOrganizeOptions.cs | 24 - .../EpisodeFileOrganizationRequest.cs | 26 - .../FileOrganizationResult.cs | 109 --- .../FileOrganizationResultQuery.cs | 18 - .../FileOrganization/FileOrganizerType.cs | 9 - .../FileOrganization/FileSortingStatus.cs | 9 - .../FileOrganization/SmartMatchInfo.cs | 16 - .../TvFileOrganizationOptions.cs | 40 - MediaBrowser.Model/MediaBrowser.Model.csproj | 8 - .../Api/DashboardService.cs | 15 + Nuget/MediaBrowser.Common.nuspec | 2 +- Nuget/MediaBrowser.Server.Core.nuspec | 4 +- 29 files changed, 20 insertions(+), 2608 deletions(-) delete mode 100644 Emby.Server.Implementations/Data/SqliteFileOrganizationRepository.cs delete mode 100644 Emby.Server.Implementations/FileOrganization/EpisodeFileOrganizer.cs delete mode 100644 Emby.Server.Implementations/FileOrganization/Extensions.cs delete mode 100644 Emby.Server.Implementations/FileOrganization/FileOrganizationNotifier.cs delete mode 100644 Emby.Server.Implementations/FileOrganization/FileOrganizationService.cs delete mode 100644 Emby.Server.Implementations/FileOrganization/NameUtils.cs delete mode 100644 Emby.Server.Implementations/FileOrganization/OrganizerScheduledTask.cs delete mode 100644 Emby.Server.Implementations/FileOrganization/TvFolderOrganizer.cs delete mode 100644 MediaBrowser.Api/Library/FileOrganizationService.cs delete mode 100644 MediaBrowser.Controller/FileOrganization/IFileOrganizationService.cs delete mode 100644 MediaBrowser.Controller/Persistence/IFileOrganizationRepository.cs delete mode 100644 MediaBrowser.Model/FileOrganization/AutoOrganizeOptions.cs delete mode 100644 MediaBrowser.Model/FileOrganization/EpisodeFileOrganizationRequest.cs delete mode 100644 MediaBrowser.Model/FileOrganization/FileOrganizationResult.cs delete mode 100644 MediaBrowser.Model/FileOrganization/FileOrganizationResultQuery.cs delete mode 100644 MediaBrowser.Model/FileOrganization/FileOrganizerType.cs delete mode 100644 MediaBrowser.Model/FileOrganization/FileSortingStatus.cs delete mode 100644 MediaBrowser.Model/FileOrganization/SmartMatchInfo.cs delete mode 100644 MediaBrowser.Model/FileOrganization/TvFileOrganizationOptions.cs diff --git a/Emby.Server.Core/ApplicationHost.cs b/Emby.Server.Core/ApplicationHost.cs index 584706e4f0..67c9364374 100644 --- a/Emby.Server.Core/ApplicationHost.cs +++ b/Emby.Server.Core/ApplicationHost.cs @@ -17,7 +17,6 @@ using MediaBrowser.Controller.Dlna; using MediaBrowser.Controller.Drawing; using MediaBrowser.Controller.Dto; using MediaBrowser.Controller.Entities; -using MediaBrowser.Controller.FileOrganization; using MediaBrowser.Controller.Library; using MediaBrowser.Controller.LiveTv; using MediaBrowser.Controller.MediaEncoding; @@ -94,7 +93,6 @@ using Emby.Server.Implementations.Channels; using Emby.Server.Implementations.Collections; using Emby.Server.Implementations.Dto; using Emby.Server.Implementations.IO; -using Emby.Server.Implementations.FileOrganization; using Emby.Server.Implementations.HttpServer; using Emby.Server.Implementations.HttpServer.Security; using Emby.Server.Implementations.Library; @@ -216,7 +214,6 @@ namespace Emby.Server.Core internal IDisplayPreferencesRepository DisplayPreferencesRepository { get; set; } internal IItemRepository ItemRepository { get; set; } private INotificationsRepository NotificationsRepository { get; set; } - private IFileOrganizationRepository FileOrganizationRepository { get; set; } private INotificationManager NotificationManager { get; set; } private ISubtitleManager SubtitleManager { get; set; } @@ -583,9 +580,6 @@ namespace Emby.Server.Core ItemRepository = itemRepo; RegisterSingleInstance(ItemRepository); - FileOrganizationRepository = GetFileOrganizationRepository(); - RegisterSingleInstance(FileOrganizationRepository); - AuthenticationRepository = GetAuthenticationRepository(); RegisterSingleInstance(AuthenticationRepository); @@ -644,9 +638,6 @@ namespace Emby.Server.Core var newsService = new Emby.Server.Implementations.News.NewsService(ApplicationPaths, JsonSerializer); RegisterSingleInstance(newsService); - var fileOrganizationService = new FileOrganizationService(TaskManager, FileOrganizationRepository, LogManager.GetLogger("FileOrganizationService"), LibraryMonitor, LibraryManager, ServerConfigurationManager, FileSystemManager, ProviderManager); - RegisterSingleInstance(fileOrganizationService); - progress.Report(15); ChannelManager = new ChannelManager(UserManager, DtoService, LibraryManager, LogManager.GetLogger("ChannelManager"), ServerConfigurationManager, FileSystemManager, UserDataManager, JsonSerializer, LocalizationManager, HttpClient, ProviderManager); @@ -932,19 +923,6 @@ namespace Emby.Server.Core return repo; } - /// - /// Gets the file organization repository. - /// - /// Task{IUserRepository}. - private IFileOrganizationRepository GetFileOrganizationRepository() - { - var repo = new SqliteFileOrganizationRepository(LogManager.GetLogger("SqliteFileOrganizationRepository"), ServerConfigurationManager.ApplicationPaths); - - repo.Initialize(); - - return repo; - } - private IAuthenticationRepository GetAuthenticationRepository() { var repo = new AuthenticationRepository(LogManager.GetLogger("AuthenticationRepository"), ServerConfigurationManager.ApplicationPaths); diff --git a/Emby.Server.Implementations/Data/SqliteFileOrganizationRepository.cs b/Emby.Server.Implementations/Data/SqliteFileOrganizationRepository.cs deleted file mode 100644 index a254962c94..0000000000 --- a/Emby.Server.Implementations/Data/SqliteFileOrganizationRepository.cs +++ /dev/null @@ -1,284 +0,0 @@ -using System; -using System.Collections.Generic; -using System.Globalization; -using System.IO; -using System.Linq; -using System.Threading; -using System.Threading.Tasks; -using MediaBrowser.Controller; -using MediaBrowser.Controller.Persistence; -using MediaBrowser.Model.FileOrganization; -using MediaBrowser.Model.Logging; -using MediaBrowser.Model.Querying; -using SQLitePCL.pretty; - -namespace Emby.Server.Implementations.Data -{ - public class SqliteFileOrganizationRepository : BaseSqliteRepository, IFileOrganizationRepository, IDisposable - { - private readonly CultureInfo _usCulture = new CultureInfo("en-US"); - - public SqliteFileOrganizationRepository(ILogger logger, IServerApplicationPaths appPaths) : base(logger) - { - DbFilePath = Path.Combine(appPaths.DataPath, "fileorganization.db"); - } - - /// - /// Opens the connection to the database - /// - /// Task. - public void Initialize() - { - using (var connection = CreateConnection()) - { - RunDefaultInitialization(connection); - - string[] queries = { - - "create table if not exists FileOrganizerResults (ResultId GUID PRIMARY KEY, OriginalPath TEXT, TargetPath TEXT, FileLength INT, OrganizationDate datetime, Status TEXT, OrganizationType TEXT, StatusMessage TEXT, ExtractedName TEXT, ExtractedYear int null, ExtractedSeasonNumber int null, ExtractedEpisodeNumber int null, ExtractedEndingEpisodeNumber, DuplicatePaths TEXT int null)", - "create index if not exists idx_FileOrganizerResults on FileOrganizerResults(ResultId)" - }; - - connection.RunQueries(queries); - } - } - - public async Task SaveResult(FileOrganizationResult result, CancellationToken cancellationToken) - { - if (result == null) - { - throw new ArgumentNullException("result"); - } - - cancellationToken.ThrowIfCancellationRequested(); - - using (WriteLock.Write()) - { - using (var connection = CreateConnection()) - { - connection.RunInTransaction(db => - { - var commandText = "replace into FileOrganizerResults (ResultId, OriginalPath, TargetPath, FileLength, OrganizationDate, Status, OrganizationType, StatusMessage, ExtractedName, ExtractedYear, ExtractedSeasonNumber, ExtractedEpisodeNumber, ExtractedEndingEpisodeNumber, DuplicatePaths) values (@ResultId, @OriginalPath, @TargetPath, @FileLength, @OrganizationDate, @Status, @OrganizationType, @StatusMessage, @ExtractedName, @ExtractedYear, @ExtractedSeasonNumber, @ExtractedEpisodeNumber, @ExtractedEndingEpisodeNumber, @DuplicatePaths)"; - - using (var statement = db.PrepareStatement(commandText)) - { - statement.TryBind("@ResultId", result.Id.ToGuidBlob()); - statement.TryBind("@OriginalPath", result.OriginalPath); - - statement.TryBind("@TargetPath", result.TargetPath); - statement.TryBind("@FileLength", result.FileSize); - statement.TryBind("@OrganizationDate", result.Date.ToDateTimeParamValue()); - statement.TryBind("@Status", result.Status.ToString()); - statement.TryBind("@OrganizationType", result.Type.ToString()); - statement.TryBind("@StatusMessage", result.StatusMessage); - statement.TryBind("@ExtractedName", result.ExtractedName); - statement.TryBind("@ExtractedYear", result.ExtractedYear); - statement.TryBind("@ExtractedSeasonNumber", result.ExtractedSeasonNumber); - statement.TryBind("@ExtractedEpisodeNumber", result.ExtractedEpisodeNumber); - statement.TryBind("@ExtractedEndingEpisodeNumber", result.ExtractedEndingEpisodeNumber); - statement.TryBind("@DuplicatePaths", string.Join("|", result.DuplicatePaths.ToArray())); - - statement.MoveNext(); - } - }, TransactionMode); - } - } - } - - public async Task Delete(string id) - { - if (string.IsNullOrEmpty(id)) - { - throw new ArgumentNullException("id"); - } - - using (WriteLock.Write()) - { - using (var connection = CreateConnection()) - { - connection.RunInTransaction(db => - { - using (var statement = db.PrepareStatement("delete from FileOrganizerResults where ResultId = @ResultId")) - { - statement.TryBind("@ResultId", id.ToGuidBlob()); - statement.MoveNext(); - } - }, TransactionMode); - } - } - } - - public async Task DeleteAll() - { - using (WriteLock.Write()) - { - using (var connection = CreateConnection()) - { - connection.RunInTransaction(db => - { - var commandText = "delete from FileOrganizerResults"; - - db.Execute(commandText); - }, TransactionMode); - } - } - } - - public QueryResult GetResults(FileOrganizationResultQuery query) - { - if (query == null) - { - throw new ArgumentNullException("query"); - } - - using (WriteLock.Read()) - { - using (var connection = CreateConnection(true)) - { - var commandText = "SELECT ResultId, OriginalPath, TargetPath, FileLength, OrganizationDate, Status, OrganizationType, StatusMessage, ExtractedName, ExtractedYear, ExtractedSeasonNumber, ExtractedEpisodeNumber, ExtractedEndingEpisodeNumber, DuplicatePaths from FileOrganizerResults"; - - if (query.StartIndex.HasValue && query.StartIndex.Value > 0) - { - commandText += string.Format(" WHERE ResultId NOT IN (SELECT ResultId FROM FileOrganizerResults ORDER BY OrganizationDate desc LIMIT {0})", - query.StartIndex.Value.ToString(_usCulture)); - } - - commandText += " ORDER BY OrganizationDate desc"; - - if (query.Limit.HasValue) - { - commandText += " LIMIT " + query.Limit.Value.ToString(_usCulture); - } - - var list = new List(); - - using (var statement = connection.PrepareStatement(commandText)) - { - foreach (var row in statement.ExecuteQuery()) - { - list.Add(GetResult(row)); - } - } - - int count; - using (var statement = connection.PrepareStatement("select count (ResultId) from FileOrganizerResults")) - { - count = statement.ExecuteQuery().SelectScalarInt().First(); - } - - return new QueryResult() - { - Items = list.ToArray(), - TotalRecordCount = count - }; - } - } - } - - public FileOrganizationResult GetResult(string id) - { - if (string.IsNullOrEmpty(id)) - { - throw new ArgumentNullException("id"); - } - - using (WriteLock.Read()) - { - using (var connection = CreateConnection(true)) - { - using (var statement = connection.PrepareStatement("select ResultId, OriginalPath, TargetPath, FileLength, OrganizationDate, Status, OrganizationType, StatusMessage, ExtractedName, ExtractedYear, ExtractedSeasonNumber, ExtractedEpisodeNumber, ExtractedEndingEpisodeNumber, DuplicatePaths from FileOrganizerResults where ResultId=@ResultId")) - { - statement.TryBind("@ResultId", id.ToGuidBlob()); - - foreach (var row in statement.ExecuteQuery()) - { - return GetResult(row); - } - } - - return null; - } - } - } - - public FileOrganizationResult GetResult(IReadOnlyList reader) - { - var index = 0; - - var result = new FileOrganizationResult - { - Id = reader[0].ReadGuidFromBlob().ToString("N") - }; - - index++; - if (reader[index].SQLiteType != SQLiteType.Null) - { - result.OriginalPath = reader[index].ToString(); - } - - index++; - if (reader[index].SQLiteType != SQLiteType.Null) - { - result.TargetPath = reader[index].ToString(); - } - - index++; - result.FileSize = reader[index].ToInt64(); - - index++; - result.Date = reader[index].ReadDateTime(); - - index++; - result.Status = (FileSortingStatus)Enum.Parse(typeof(FileSortingStatus), reader[index].ToString(), true); - - index++; - result.Type = (FileOrganizerType)Enum.Parse(typeof(FileOrganizerType), reader[index].ToString(), true); - - index++; - if (reader[index].SQLiteType != SQLiteType.Null) - { - result.StatusMessage = reader[index].ToString(); - } - - result.OriginalFileName = Path.GetFileName(result.OriginalPath); - - index++; - if (reader[index].SQLiteType != SQLiteType.Null) - { - result.ExtractedName = reader[index].ToString(); - } - - index++; - if (reader[index].SQLiteType != SQLiteType.Null) - { - result.ExtractedYear = reader[index].ToInt(); - } - - index++; - if (reader[index].SQLiteType != SQLiteType.Null) - { - result.ExtractedSeasonNumber = reader[index].ToInt(); - } - - index++; - if (reader[index].SQLiteType != SQLiteType.Null) - { - result.ExtractedEpisodeNumber = reader[index].ToInt(); - } - - index++; - if (reader[index].SQLiteType != SQLiteType.Null) - { - result.ExtractedEndingEpisodeNumber = reader[index].ToInt(); - } - - index++; - if (reader[index].SQLiteType != SQLiteType.Null) - { - result.DuplicatePaths = reader[index].ToString().Split('|').Where(i => !string.IsNullOrEmpty(i)).ToList(); - } - - return result; - } - } -} diff --git a/Emby.Server.Implementations/Emby.Server.Implementations.csproj b/Emby.Server.Implementations/Emby.Server.Implementations.csproj index 1b0cbb936a..3d6e02816e 100644 --- a/Emby.Server.Implementations/Emby.Server.Implementations.csproj +++ b/Emby.Server.Implementations/Emby.Server.Implementations.csproj @@ -54,7 +54,6 @@ - @@ -79,13 +78,6 @@ - - - - - - - diff --git a/Emby.Server.Implementations/FileOrganization/EpisodeFileOrganizer.cs b/Emby.Server.Implementations/FileOrganization/EpisodeFileOrganizer.cs deleted file mode 100644 index cf9fdbb161..0000000000 --- a/Emby.Server.Implementations/FileOrganization/EpisodeFileOrganizer.cs +++ /dev/null @@ -1,813 +0,0 @@ -using MediaBrowser.Controller.Configuration; -using MediaBrowser.Controller.Entities.TV; -using MediaBrowser.Controller.FileOrganization; -using MediaBrowser.Controller.Library; -using MediaBrowser.Controller.Providers; -using MediaBrowser.Model.Entities; -using MediaBrowser.Model.Extensions; -using MediaBrowser.Model.FileOrganization; -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; -using Emby.Server.Implementations.Library; -using MediaBrowser.Controller.Dto; -using MediaBrowser.Controller.Entities; -using MediaBrowser.Controller.IO; -using MediaBrowser.Model.IO; -using MediaBrowser.Naming.TV; -using EpisodeInfo = MediaBrowser.Controller.Providers.EpisodeInfo; - -namespace Emby.Server.Implementations.FileOrganization -{ - public class EpisodeFileOrganizer - { - private readonly ILibraryMonitor _libraryMonitor; - private readonly ILibraryManager _libraryManager; - private readonly ILogger _logger; - private readonly IFileSystem _fileSystem; - private readonly IFileOrganizationService _organizationService; - private readonly IServerConfigurationManager _config; - private readonly IProviderManager _providerManager; - - private readonly CultureInfo _usCulture = new CultureInfo("en-US"); - - public EpisodeFileOrganizer(IFileOrganizationService organizationService, IServerConfigurationManager config, IFileSystem fileSystem, ILogger logger, ILibraryManager libraryManager, ILibraryMonitor libraryMonitor, IProviderManager providerManager) - { - _organizationService = organizationService; - _config = config; - _fileSystem = fileSystem; - _logger = logger; - _libraryManager = libraryManager; - _libraryMonitor = libraryMonitor; - _providerManager = providerManager; - } - - public async Task OrganizeEpisodeFile(string path, AutoOrganizeOptions options, bool overwriteExisting, CancellationToken cancellationToken) - { - _logger.Info("Sorting file {0}", path); - - var result = new FileOrganizationResult - { - Date = DateTime.UtcNow, - OriginalPath = path, - OriginalFileName = Path.GetFileName(path), - Type = FileOrganizerType.Episode, - FileSize = _fileSystem.GetFileInfo(path).Length - }; - - try - { - if (_libraryMonitor.IsPathLocked(path)) - { - result.Status = FileSortingStatus.Failure; - result.StatusMessage = "Path is locked by other processes. Please try again later."; - return result; - } - - var namingOptions = ((LibraryManager)_libraryManager).GetNamingOptions(); - var resolver = new EpisodeResolver(namingOptions, new NullLogger()); - - var episodeInfo = resolver.Resolve(path, false) ?? - new MediaBrowser.Naming.TV.EpisodeInfo(); - - var seriesName = episodeInfo.SeriesName; - - if (!string.IsNullOrEmpty(seriesName)) - { - var seasonNumber = episodeInfo.SeasonNumber; - - result.ExtractedSeasonNumber = seasonNumber; - - // Passing in true will include a few extra regex's - var episodeNumber = episodeInfo.EpisodeNumber; - - result.ExtractedEpisodeNumber = episodeNumber; - - var premiereDate = episodeInfo.IsByDate ? - new DateTime(episodeInfo.Year.Value, episodeInfo.Month.Value, episodeInfo.Day.Value) : - (DateTime?)null; - - if (episodeInfo.IsByDate || (seasonNumber.HasValue && episodeNumber.HasValue)) - { - if (episodeInfo.IsByDate) - { - _logger.Debug("Extracted information from {0}. Series name {1}, Date {2}", path, seriesName, premiereDate.Value); - } - else - { - _logger.Debug("Extracted information from {0}. Series name {1}, Season {2}, Episode {3}", path, seriesName, seasonNumber, episodeNumber); - } - - var endingEpisodeNumber = episodeInfo.EndingEpsiodeNumber; - - result.ExtractedEndingEpisodeNumber = endingEpisodeNumber; - - await OrganizeEpisode(path, - seriesName, - seasonNumber, - episodeNumber, - endingEpisodeNumber, - premiereDate, - options, - overwriteExisting, - false, - result, - cancellationToken).ConfigureAwait(false); - } - else - { - var msg = string.Format("Unable to determine episode number from {0}", path); - result.Status = FileSortingStatus.Failure; - result.StatusMessage = msg; - _logger.Warn(msg); - } - } - else - { - var msg = string.Format("Unable to determine series name from {0}", path); - result.Status = FileSortingStatus.Failure; - result.StatusMessage = msg; - _logger.Warn(msg); - } - - var previousResult = _organizationService.GetResultBySourcePath(path); - - if (previousResult != null) - { - // Don't keep saving the same result over and over if nothing has changed - if (previousResult.Status == result.Status && previousResult.StatusMessage == result.StatusMessage && result.Status != FileSortingStatus.Success) - { - return previousResult; - } - } - - await _organizationService.SaveResult(result, CancellationToken.None).ConfigureAwait(false); - } - catch (Exception ex) - { - result.Status = FileSortingStatus.Failure; - result.StatusMessage = ex.Message; - } - - return result; - } - - public async Task OrganizeWithCorrection(EpisodeFileOrganizationRequest request, AutoOrganizeOptions options, CancellationToken cancellationToken) - { - var result = _organizationService.GetResult(request.ResultId); - - try - { - Series series = null; - - if (request.NewSeriesProviderIds.Count > 0) - { - // We're having a new series here - SeriesInfo seriesRequest = new SeriesInfo(); - seriesRequest.ProviderIds = request.NewSeriesProviderIds; - - var refreshOptions = new MetadataRefreshOptions(_fileSystem); - series = new Series(); - series.Id = Guid.NewGuid(); - series.Name = request.NewSeriesName; - - int year; - if (int.TryParse(request.NewSeriesYear, out year)) - { - series.ProductionYear = year; - } - - var seriesFolderName = series.Name; - if (series.ProductionYear.HasValue) - { - seriesFolderName = string.Format("{0} ({1})", seriesFolderName, series.ProductionYear); - } - - seriesFolderName = _fileSystem.GetValidFilename(seriesFolderName); - - series.Path = Path.Combine(request.TargetFolder, seriesFolderName); - - series.ProviderIds = request.NewSeriesProviderIds; - - await series.RefreshMetadata(refreshOptions, cancellationToken).ConfigureAwait(false); - } - - if (series == null) - { - // Existing Series - series = (Series)_libraryManager.GetItemById(new Guid(request.SeriesId)); - } - - await OrganizeEpisode(result.OriginalPath, - series, - request.SeasonNumber, - request.EpisodeNumber, - request.EndingEpisodeNumber, - null, - options, - true, - request.RememberCorrection, - result, - cancellationToken).ConfigureAwait(false); - - await _organizationService.SaveResult(result, CancellationToken.None).ConfigureAwait(false); - } - catch (Exception ex) - { - result.Status = FileSortingStatus.Failure; - result.StatusMessage = ex.Message; - } - - return result; - } - - private Task OrganizeEpisode(string sourcePath, - string seriesName, - int? seasonNumber, - int? episodeNumber, - int? endingEpiosdeNumber, - DateTime? premiereDate, - AutoOrganizeOptions options, - bool overwriteExisting, - bool rememberCorrection, - FileOrganizationResult result, - CancellationToken cancellationToken) - { - var series = GetMatchingSeries(seriesName, result, options); - - if (series == null) - { - var msg = string.Format("Unable to find series in library matching name {0}", seriesName); - result.Status = FileSortingStatus.Failure; - result.StatusMessage = msg; - _logger.Warn(msg); - return Task.FromResult(true); - } - - return OrganizeEpisode(sourcePath, - series, - seasonNumber, - episodeNumber, - endingEpiosdeNumber, - premiereDate, - options, - overwriteExisting, - rememberCorrection, - result, - cancellationToken); - } - - private async Task OrganizeEpisode(string sourcePath, - Series series, - int? seasonNumber, - int? episodeNumber, - int? endingEpiosdeNumber, - DateTime? premiereDate, - AutoOrganizeOptions options, - bool overwriteExisting, - bool rememberCorrection, - FileOrganizationResult result, - CancellationToken cancellationToken) - { - _logger.Info("Sorting file {0} into series {1}", sourcePath, series.Path); - - var originalExtractedSeriesString = result.ExtractedName; - - bool isNew = string.IsNullOrWhiteSpace(result.Id); - - if (isNew) - { - await _organizationService.SaveResult(result, cancellationToken); - } - - if (!_organizationService.AddToInProgressList(result, isNew)) - { - throw new Exception("File is currently processed otherwise. Please try again later."); - } - - try - { - // Proceed to sort the file - var newPath = await GetNewPath(sourcePath, series, seasonNumber, episodeNumber, endingEpiosdeNumber, premiereDate, options.TvOptions, cancellationToken).ConfigureAwait(false); - - if (string.IsNullOrEmpty(newPath)) - { - var msg = string.Format("Unable to sort {0} because target path could not be determined.", sourcePath); - throw new Exception(msg); - } - - _logger.Info("Sorting file {0} to new path {1}", sourcePath, newPath); - result.TargetPath = newPath; - - var fileExists = _fileSystem.FileExists(result.TargetPath); - var otherDuplicatePaths = GetOtherDuplicatePaths(result.TargetPath, series, seasonNumber, episodeNumber, endingEpiosdeNumber); - - if (!overwriteExisting) - { - if (options.TvOptions.CopyOriginalFile && fileExists && IsSameEpisode(sourcePath, newPath)) - { - var msg = string.Format("File '{0}' already copied to new path '{1}', stopping organization", sourcePath, newPath); - _logger.Info(msg); - result.Status = FileSortingStatus.SkippedExisting; - result.StatusMessage = msg; - return; - } - - if (fileExists) - { - var msg = string.Format("File '{0}' already exists as '{1}', stopping organization", sourcePath, newPath); - _logger.Info(msg); - result.Status = FileSortingStatus.SkippedExisting; - result.StatusMessage = msg; - result.TargetPath = newPath; - return; - } - - if (otherDuplicatePaths.Count > 0) - { - var msg = string.Format("File '{0}' already exists as these:'{1}'. Stopping organization", sourcePath, string.Join("', '", otherDuplicatePaths)); - _logger.Info(msg); - result.Status = FileSortingStatus.SkippedExisting; - result.StatusMessage = msg; - result.DuplicatePaths = otherDuplicatePaths; - return; - } - } - - PerformFileSorting(options.TvOptions, result); - - if (overwriteExisting) - { - var hasRenamedFiles = false; - - foreach (var path in otherDuplicatePaths) - { - _logger.Debug("Removing duplicate episode {0}", path); - - _libraryMonitor.ReportFileSystemChangeBeginning(path); - - var renameRelatedFiles = !hasRenamedFiles && - string.Equals(_fileSystem.GetDirectoryName(path), _fileSystem.GetDirectoryName(result.TargetPath), StringComparison.OrdinalIgnoreCase); - - if (renameRelatedFiles) - { - hasRenamedFiles = true; - } - - try - { - DeleteLibraryFile(path, renameRelatedFiles, result.TargetPath); - } - catch (IOException ex) - { - _logger.ErrorException("Error removing duplicate episode", ex, path); - } - finally - { - _libraryMonitor.ReportFileSystemChangeComplete(path, true); - } - } - } - } - catch (Exception ex) - { - result.Status = FileSortingStatus.Failure; - result.StatusMessage = ex.Message; - _logger.Warn(ex.Message); - return; - } - finally - { - _organizationService.RemoveFromInprogressList(result); - } - - if (rememberCorrection) - { - SaveSmartMatchString(originalExtractedSeriesString, series, options); - } - } - - private void SaveSmartMatchString(string matchString, Series series, AutoOrganizeOptions options) - { - if (string.IsNullOrEmpty(matchString) || matchString.Length < 3) - { - return; - } - - SmartMatchInfo info = options.SmartMatchInfos.FirstOrDefault(i => string.Equals(i.ItemName, series.Name, StringComparison.OrdinalIgnoreCase)); - - if (info == null) - { - info = new SmartMatchInfo(); - info.ItemName = series.Name; - info.OrganizerType = FileOrganizerType.Episode; - info.DisplayName = series.Name; - var list = options.SmartMatchInfos.ToList(); - list.Add(info); - options.SmartMatchInfos = list.ToArray(); - } - - if (!info.MatchStrings.Contains(matchString, StringComparer.OrdinalIgnoreCase)) - { - var list = info.MatchStrings.ToList(); - list.Add(matchString); - info.MatchStrings = list.ToArray(); - _config.SaveAutoOrganizeOptions(options); - } - } - - private void DeleteLibraryFile(string path, bool renameRelatedFiles, string targetPath) - { - _fileSystem.DeleteFile(path); - - if (!renameRelatedFiles) - { - return; - } - - // Now find other files - var originalFilenameWithoutExtension = Path.GetFileNameWithoutExtension(path); - var directory = _fileSystem.GetDirectoryName(path); - - if (!string.IsNullOrWhiteSpace(originalFilenameWithoutExtension) && !string.IsNullOrWhiteSpace(directory)) - { - // Get all related files, e.g. metadata, images, etc - var files = _fileSystem.GetFilePaths(directory) - .Where(i => (Path.GetFileNameWithoutExtension(i) ?? string.Empty).StartsWith(originalFilenameWithoutExtension, StringComparison.OrdinalIgnoreCase)) - .ToList(); - - var targetFilenameWithoutExtension = Path.GetFileNameWithoutExtension(targetPath); - - foreach (var file in files) - { - directory = _fileSystem.GetDirectoryName(file); - var filename = Path.GetFileName(file); - - filename = filename.Replace(originalFilenameWithoutExtension, targetFilenameWithoutExtension, - StringComparison.OrdinalIgnoreCase); - - var destination = Path.Combine(directory, filename); - - _fileSystem.MoveFile(file, destination); - } - } - } - - private List GetOtherDuplicatePaths(string targetPath, - Series series, - int? seasonNumber, - int? episodeNumber, - int? endingEpisodeNumber) - { - // TODO: Support date-naming? - if (!seasonNumber.HasValue || !episodeNumber.HasValue) - { - return new List(); - } - - var episodePaths = series.GetRecursiveChildren(i => i is Episode) - .OfType() - .Where(i => - { - var locationType = i.LocationType; - - // Must be file system based and match exactly - if (locationType != LocationType.Remote && - locationType != LocationType.Virtual && - i.ParentIndexNumber.HasValue && - i.ParentIndexNumber.Value == seasonNumber && - i.IndexNumber.HasValue && - i.IndexNumber.Value == episodeNumber) - { - - if (endingEpisodeNumber.HasValue || i.IndexNumberEnd.HasValue) - { - return endingEpisodeNumber.HasValue && i.IndexNumberEnd.HasValue && - endingEpisodeNumber.Value == i.IndexNumberEnd.Value; - } - - return true; - } - - return false; - }) - .Select(i => i.Path) - .ToList(); - - var folder = _fileSystem.GetDirectoryName(targetPath); - var targetFileNameWithoutExtension = _fileSystem.GetFileNameWithoutExtension(targetPath); - - try - { - var filesOfOtherExtensions = _fileSystem.GetFilePaths(folder) - .Where(i => _libraryManager.IsVideoFile(i) && string.Equals(_fileSystem.GetFileNameWithoutExtension(i), targetFileNameWithoutExtension, StringComparison.OrdinalIgnoreCase)); - - episodePaths.AddRange(filesOfOtherExtensions); - } - catch (IOException) - { - // No big deal. Maybe the season folder doesn't already exist. - } - - return episodePaths.Where(i => !string.Equals(i, targetPath, StringComparison.OrdinalIgnoreCase)) - .Distinct(StringComparer.OrdinalIgnoreCase) - .ToList(); - } - - private void PerformFileSorting(TvFileOrganizationOptions options, FileOrganizationResult result) - { - // We should probably handle this earlier so that we never even make it this far - if (string.Equals(result.OriginalPath, result.TargetPath, StringComparison.OrdinalIgnoreCase)) - { - return; - } - - _libraryMonitor.ReportFileSystemChangeBeginning(result.TargetPath); - - _fileSystem.CreateDirectory(_fileSystem.GetDirectoryName(result.TargetPath)); - - var targetAlreadyExists = _fileSystem.FileExists(result.TargetPath); - - try - { - if (targetAlreadyExists || options.CopyOriginalFile) - { - _fileSystem.CopyFile(result.OriginalPath, result.TargetPath, true); - } - else - { - _fileSystem.MoveFile(result.OriginalPath, result.TargetPath); - } - - result.Status = FileSortingStatus.Success; - result.StatusMessage = string.Empty; - } - catch (Exception ex) - { - var errorMsg = string.Format("Failed to move file from {0} to {1}: {2}", result.OriginalPath, result.TargetPath, ex.Message); - - result.Status = FileSortingStatus.Failure; - result.StatusMessage = errorMsg; - _logger.ErrorException(errorMsg, ex); - - return; - } - finally - { - _libraryMonitor.ReportFileSystemChangeComplete(result.TargetPath, true); - } - - if (targetAlreadyExists && !options.CopyOriginalFile) - { - try - { - _fileSystem.DeleteFile(result.OriginalPath); - } - catch (Exception ex) - { - _logger.ErrorException("Error deleting {0}", ex, result.OriginalPath); - } - } - } - - private Series GetMatchingSeries(string seriesName, FileOrganizationResult result, AutoOrganizeOptions options) - { - var parsedName = _libraryManager.ParseName(seriesName); - - var yearInName = parsedName.Year; - var nameWithoutYear = parsedName.Name; - - result.ExtractedName = nameWithoutYear; - result.ExtractedYear = yearInName; - - var series = _libraryManager.GetItemList(new InternalItemsQuery - { - IncludeItemTypes = new[] { typeof(Series).Name }, - Recursive = true, - DtoOptions = new DtoOptions(true) - }) - .Cast() - .Select(i => NameUtils.GetMatchScore(nameWithoutYear, yearInName, i)) - .Where(i => i.Item2 > 0) - .OrderByDescending(i => i.Item2) - .Select(i => i.Item1) - .FirstOrDefault(); - - if (series == null) - { - SmartMatchInfo info = options.SmartMatchInfos.FirstOrDefault(e => e.MatchStrings.Contains(nameWithoutYear, StringComparer.OrdinalIgnoreCase)); - - if (info != null) - { - series = _libraryManager.GetItemList(new InternalItemsQuery - { - IncludeItemTypes = new[] { typeof(Series).Name }, - Recursive = true, - Name = info.ItemName, - DtoOptions = new DtoOptions(true) - - }).Cast().FirstOrDefault(); - } - } - - return series; - } - - /// - /// Gets the new path. - /// - /// The source path. - /// The series. - /// The season number. - /// The episode number. - /// The ending episode number. - /// The premiere date. - /// The options. - /// The cancellation token. - /// System.String. - private async Task GetNewPath(string sourcePath, - Series series, - int? seasonNumber, - int? episodeNumber, - int? endingEpisodeNumber, - DateTime? premiereDate, - TvFileOrganizationOptions options, - CancellationToken cancellationToken) - { - var episodeInfo = new EpisodeInfo - { - IndexNumber = episodeNumber, - IndexNumberEnd = endingEpisodeNumber, - MetadataCountryCode = series.GetPreferredMetadataCountryCode(), - MetadataLanguage = series.GetPreferredMetadataLanguage(), - ParentIndexNumber = seasonNumber, - SeriesProviderIds = series.ProviderIds, - PremiereDate = premiereDate - }; - - var searchResults = await _providerManager.GetRemoteSearchResults(new RemoteSearchQuery - { - SearchInfo = episodeInfo - - }, cancellationToken).ConfigureAwait(false); - - var episode = searchResults.FirstOrDefault(); - - if (episode == null) - { - var msg = string.Format("No provider metadata found for {0} season {1} episode {2}", series.Name, seasonNumber, episodeNumber); - _logger.Warn(msg); - throw new Exception(msg); - } - - var episodeName = episode.Name; - - //if (string.IsNullOrWhiteSpace(episodeName)) - //{ - // var msg = string.Format("No provider metadata found for {0} season {1} episode {2}", series.Name, seasonNumber, episodeNumber); - // _logger.Warn(msg); - // return null; - //} - - seasonNumber = seasonNumber ?? episode.ParentIndexNumber; - episodeNumber = episodeNumber ?? episode.IndexNumber; - - var newPath = GetSeasonFolderPath(series, seasonNumber.Value, options); - - var episodeFileName = GetEpisodeFileName(sourcePath, series.Name, seasonNumber.Value, episodeNumber.Value, endingEpisodeNumber, episodeName, options); - - if (string.IsNullOrEmpty(episodeFileName)) - { - // cause failure - return string.Empty; - } - - newPath = Path.Combine(newPath, episodeFileName); - - return newPath; - } - - /// - /// Gets the season folder path. - /// - /// The series. - /// The season number. - /// The options. - /// System.String. - private string GetSeasonFolderPath(Series series, int seasonNumber, TvFileOrganizationOptions options) - { - // If there's already a season folder, use that - var season = series - .GetRecursiveChildren(i => i is Season && i.LocationType == LocationType.FileSystem && i.IndexNumber.HasValue && i.IndexNumber.Value == seasonNumber) - .FirstOrDefault(); - - if (season != null) - { - return season.Path; - } - - var path = series.Path; - - if (series.ContainsEpisodesWithoutSeasonFolders) - { - return path; - } - - if (seasonNumber == 0) - { - return Path.Combine(path, _fileSystem.GetValidFilename(options.SeasonZeroFolderName)); - } - - var seasonFolderName = options.SeasonFolderPattern - .Replace("%s", seasonNumber.ToString(_usCulture)) - .Replace("%0s", seasonNumber.ToString("00", _usCulture)) - .Replace("%00s", seasonNumber.ToString("000", _usCulture)); - - return Path.Combine(path, _fileSystem.GetValidFilename(seasonFolderName)); - } - - private string GetEpisodeFileName(string sourcePath, string seriesName, int seasonNumber, int episodeNumber, int? endingEpisodeNumber, string episodeTitle, TvFileOrganizationOptions options) - { - seriesName = _fileSystem.GetValidFilename(seriesName).Trim(); - - if (string.IsNullOrWhiteSpace(episodeTitle)) - { - episodeTitle = string.Empty; - } - else - { - episodeTitle = _fileSystem.GetValidFilename(episodeTitle).Trim(); - } - - var sourceExtension = (Path.GetExtension(sourcePath) ?? string.Empty).TrimStart('.'); - - var pattern = endingEpisodeNumber.HasValue ? options.MultiEpisodeNamePattern : options.EpisodeNamePattern; - - if (string.IsNullOrWhiteSpace(pattern)) - { - throw new Exception("GetEpisodeFileName: Configured episode name pattern is empty!"); - } - - var result = pattern.Replace("%sn", seriesName) - .Replace("%s.n", seriesName.Replace(" ", ".")) - .Replace("%s_n", seriesName.Replace(" ", "_")) - .Replace("%s", seasonNumber.ToString(_usCulture)) - .Replace("%0s", seasonNumber.ToString("00", _usCulture)) - .Replace("%00s", seasonNumber.ToString("000", _usCulture)) - .Replace("%ext", sourceExtension) - .Replace("%en", "%#1") - .Replace("%e.n", "%#2") - .Replace("%e_n", "%#3"); - - if (endingEpisodeNumber.HasValue) - { - result = result.Replace("%ed", endingEpisodeNumber.Value.ToString(_usCulture)) - .Replace("%0ed", endingEpisodeNumber.Value.ToString("00", _usCulture)) - .Replace("%00ed", endingEpisodeNumber.Value.ToString("000", _usCulture)); - } - - result = result.Replace("%e", episodeNumber.ToString(_usCulture)) - .Replace("%0e", episodeNumber.ToString("00", _usCulture)) - .Replace("%00e", episodeNumber.ToString("000", _usCulture)); - - if (result.Contains("%#")) - { - result = result.Replace("%#1", episodeTitle) - .Replace("%#2", episodeTitle.Replace(" ", ".")) - .Replace("%#3", episodeTitle.Replace(" ", "_")); - } - - // Finally, call GetValidFilename again in case user customized the episode expression with any invalid filename characters - return _fileSystem.GetValidFilename(result).Trim(); - } - - private bool IsSameEpisode(string sourcePath, string newPath) - { - try - { - var sourceFileInfo = _fileSystem.GetFileInfo(sourcePath); - var destinationFileInfo = _fileSystem.GetFileInfo(newPath); - - if (sourceFileInfo.Length == destinationFileInfo.Length) - { - return true; - } - } - catch (FileNotFoundException) - { - return false; - } - catch (IOException) - { - return false; - } - - return false; - } - } -} diff --git a/Emby.Server.Implementations/FileOrganization/Extensions.cs b/Emby.Server.Implementations/FileOrganization/Extensions.cs deleted file mode 100644 index 506bc0327e..0000000000 --- a/Emby.Server.Implementations/FileOrganization/Extensions.cs +++ /dev/null @@ -1,33 +0,0 @@ -using MediaBrowser.Common.Configuration; -using MediaBrowser.Model.FileOrganization; -using System.Collections.Generic; - -namespace Emby.Server.Implementations.FileOrganization -{ - public static class ConfigurationExtension - { - public static AutoOrganizeOptions GetAutoOrganizeOptions(this IConfigurationManager manager) - { - return manager.GetConfiguration("autoorganize"); - } - public static void SaveAutoOrganizeOptions(this IConfigurationManager manager, AutoOrganizeOptions options) - { - manager.SaveConfiguration("autoorganize", options); - } - } - - public class AutoOrganizeOptionsFactory : IConfigurationFactory - { - public IEnumerable GetConfigurations() - { - return new List - { - new ConfigurationStore - { - Key = "autoorganize", - ConfigurationType = typeof (AutoOrganizeOptions) - } - }; - } - } -} diff --git a/Emby.Server.Implementations/FileOrganization/FileOrganizationNotifier.cs b/Emby.Server.Implementations/FileOrganization/FileOrganizationNotifier.cs deleted file mode 100644 index 2a01765472..0000000000 --- a/Emby.Server.Implementations/FileOrganization/FileOrganizationNotifier.cs +++ /dev/null @@ -1,80 +0,0 @@ -using MediaBrowser.Controller.FileOrganization; -using MediaBrowser.Controller.Plugins; -using MediaBrowser.Controller.Session; -using MediaBrowser.Model.Events; -using MediaBrowser.Model.FileOrganization; -using MediaBrowser.Model.Logging; -using System; -using System.Threading; -using MediaBrowser.Model.Tasks; - -namespace Emby.Server.Implementations.FileOrganization -{ - /// - /// Class SessionInfoWebSocketListener - /// - class FileOrganizationNotifier : IServerEntryPoint - { - private readonly IFileOrganizationService _organizationService; - private readonly ISessionManager _sessionManager; - private readonly ITaskManager _taskManager; - - public FileOrganizationNotifier(ILogger logger, IFileOrganizationService organizationService, ISessionManager sessionManager, ITaskManager taskManager) - { - _organizationService = organizationService; - _sessionManager = sessionManager; - _taskManager = taskManager; - } - - public void Run() - { - _organizationService.ItemAdded += _organizationService_ItemAdded; - _organizationService.ItemRemoved += _organizationService_ItemRemoved; - _organizationService.ItemUpdated += _organizationService_ItemUpdated; - _organizationService.LogReset += _organizationService_LogReset; - - //_taskManager.TaskCompleted += _taskManager_TaskCompleted; - } - - private void _organizationService_LogReset(object sender, EventArgs e) - { - _sessionManager.SendMessageToAdminSessions("AutoOrganize_LogReset", (FileOrganizationResult)null, CancellationToken.None); - } - - private void _organizationService_ItemUpdated(object sender, GenericEventArgs e) - { - _sessionManager.SendMessageToAdminSessions("AutoOrganize_ItemUpdated", e.Argument, CancellationToken.None); - } - - private void _organizationService_ItemRemoved(object sender, GenericEventArgs e) - { - _sessionManager.SendMessageToAdminSessions("AutoOrganize_ItemRemoved", e.Argument, CancellationToken.None); - } - - private void _organizationService_ItemAdded(object sender, GenericEventArgs e) - { - _sessionManager.SendMessageToAdminSessions("AutoOrganize_ItemAdded", e.Argument, CancellationToken.None); - } - - //private void _taskManager_TaskCompleted(object sender, TaskCompletionEventArgs e) - //{ - // var taskWithKey = e.Task.ScheduledTask as IHasKey; - // if (taskWithKey != null && taskWithKey.Key == "AutoOrganize") - // { - // _sessionManager.SendMessageToAdminSessions("AutoOrganize_TaskCompleted", (FileOrganizationResult)null, CancellationToken.None); - // } - //} - - public void Dispose() - { - _organizationService.ItemAdded -= _organizationService_ItemAdded; - _organizationService.ItemRemoved -= _organizationService_ItemRemoved; - _organizationService.ItemUpdated -= _organizationService_ItemUpdated; - _organizationService.LogReset -= _organizationService_LogReset; - - //_taskManager.TaskCompleted -= _taskManager_TaskCompleted; - } - - - } -} diff --git a/Emby.Server.Implementations/FileOrganization/FileOrganizationService.cs b/Emby.Server.Implementations/FileOrganization/FileOrganizationService.cs deleted file mode 100644 index d95bd87346..0000000000 --- a/Emby.Server.Implementations/FileOrganization/FileOrganizationService.cs +++ /dev/null @@ -1,283 +0,0 @@ -using MediaBrowser.Common.Extensions; -using MediaBrowser.Controller.Configuration; -using MediaBrowser.Controller.FileOrganization; -using MediaBrowser.Controller.Library; -using MediaBrowser.Controller.Net; -using MediaBrowser.Controller.Persistence; -using MediaBrowser.Controller.Providers; -using MediaBrowser.Model.FileOrganization; -using MediaBrowser.Model.Logging; -using MediaBrowser.Model.Querying; -using System; -using System.Collections.Concurrent; -using System.Linq; -using System.Threading; -using System.Threading.Tasks; -using MediaBrowser.Model.IO; -using MediaBrowser.Controller.Session; -using MediaBrowser.Model.Events; -using MediaBrowser.Common.Events; - -using MediaBrowser.Controller.IO; -using MediaBrowser.Model.Tasks; - -namespace Emby.Server.Implementations.FileOrganization -{ - public class FileOrganizationService : IFileOrganizationService - { - private readonly ITaskManager _taskManager; - private readonly IFileOrganizationRepository _repo; - private readonly ILogger _logger; - private readonly ILibraryMonitor _libraryMonitor; - private readonly ILibraryManager _libraryManager; - private readonly IServerConfigurationManager _config; - private readonly IFileSystem _fileSystem; - private readonly IProviderManager _providerManager; - private readonly ConcurrentDictionary _inProgressItemIds = new ConcurrentDictionary(); - - public event EventHandler> ItemAdded; - public event EventHandler> ItemUpdated; - public event EventHandler> ItemRemoved; - public event EventHandler LogReset; - - public FileOrganizationService(ITaskManager taskManager, IFileOrganizationRepository repo, ILogger logger, ILibraryMonitor libraryMonitor, ILibraryManager libraryManager, IServerConfigurationManager config, IFileSystem fileSystem, IProviderManager providerManager) - { - _taskManager = taskManager; - _repo = repo; - _logger = logger; - _libraryMonitor = libraryMonitor; - _libraryManager = libraryManager; - _config = config; - _fileSystem = fileSystem; - _providerManager = providerManager; - } - - public void BeginProcessNewFiles() - { - _taskManager.CancelIfRunningAndQueue(); - } - - public Task SaveResult(FileOrganizationResult result, CancellationToken cancellationToken) - { - if (result == null || string.IsNullOrEmpty(result.OriginalPath)) - { - throw new ArgumentNullException("result"); - } - - result.Id = result.OriginalPath.GetMD5().ToString("N"); - - return _repo.SaveResult(result, cancellationToken); - } - - public QueryResult GetResults(FileOrganizationResultQuery query) - { - var results = _repo.GetResults(query); - - foreach (var result in results.Items) - { - result.IsInProgress = _inProgressItemIds.ContainsKey(result.Id); - } - - return results; - } - - public FileOrganizationResult GetResult(string id) - { - var result = _repo.GetResult(id); - - if (result != null) - { - result.IsInProgress = _inProgressItemIds.ContainsKey(result.Id); - } - - return result; - } - - public FileOrganizationResult GetResultBySourcePath(string path) - { - if (string.IsNullOrEmpty(path)) - { - throw new ArgumentNullException("path"); - } - - var id = path.GetMD5().ToString("N"); - - return GetResult(id); - } - - public async Task DeleteOriginalFile(string resultId) - { - var result = _repo.GetResult(resultId); - - _logger.Info("Requested to delete {0}", result.OriginalPath); - - if (!AddToInProgressList(result, false)) - { - throw new Exception("Path is currently processed otherwise. Please try again later."); - } - - try - { - _fileSystem.DeleteFile(result.OriginalPath); - } - catch (Exception ex) - { - _logger.ErrorException("Error deleting {0}", ex, result.OriginalPath); - } - finally - { - RemoveFromInprogressList(result); - } - - await _repo.Delete(resultId); - - EventHelper.FireEventIfNotNull(ItemRemoved, this, new GenericEventArgs(result), _logger); - } - - private AutoOrganizeOptions GetAutoOrganizeOptions() - { - return _config.GetAutoOrganizeOptions(); - } - - public async Task PerformOrganization(string resultId) - { - var result = _repo.GetResult(resultId); - - if (string.IsNullOrEmpty(result.TargetPath)) - { - throw new ArgumentException("No target path available."); - } - - var organizer = new EpisodeFileOrganizer(this, _config, _fileSystem, _logger, _libraryManager, - _libraryMonitor, _providerManager); - - var organizeResult = await organizer.OrganizeEpisodeFile(result.OriginalPath, GetAutoOrganizeOptions(), true, CancellationToken.None) - .ConfigureAwait(false); - - if (organizeResult.Status != FileSortingStatus.Success) - { - throw new Exception(result.StatusMessage); - } - } - - public async Task ClearLog() - { - await _repo.DeleteAll(); - EventHelper.FireEventIfNotNull(LogReset, this, EventArgs.Empty, _logger); - } - - public async Task PerformEpisodeOrganization(EpisodeFileOrganizationRequest request) - { - var organizer = new EpisodeFileOrganizer(this, _config, _fileSystem, _logger, _libraryManager, - _libraryMonitor, _providerManager); - - var result = await organizer.OrganizeWithCorrection(request, GetAutoOrganizeOptions(), CancellationToken.None).ConfigureAwait(false); - - if (result.Status != FileSortingStatus.Success) - { - throw new Exception(result.StatusMessage); - } - } - - public QueryResult GetSmartMatchInfos(FileOrganizationResultQuery query) - { - if (query == null) - { - throw new ArgumentNullException("query"); - } - - var options = GetAutoOrganizeOptions(); - - var items = options.SmartMatchInfos.Skip(query.StartIndex ?? 0).Take(query.Limit ?? Int32.MaxValue).ToArray(); - - return new QueryResult() - { - Items = items, - TotalRecordCount = options.SmartMatchInfos.Length - }; - } - - public void DeleteSmartMatchEntry(string itemName, string matchString) - { - if (string.IsNullOrEmpty(itemName)) - { - throw new ArgumentNullException("itemName"); - } - - if (string.IsNullOrEmpty(matchString)) - { - throw new ArgumentNullException("matchString"); - } - - var options = GetAutoOrganizeOptions(); - - SmartMatchInfo info = options.SmartMatchInfos.FirstOrDefault(i => string.Equals(i.ItemName, itemName)); - - if (info != null && info.MatchStrings.Contains(matchString)) - { - var list = info.MatchStrings.ToList(); - list.Remove(matchString); - info.MatchStrings = list.ToArray(); - - if (info.MatchStrings.Length == 0) - { - var infos = options.SmartMatchInfos.ToList(); - infos.Remove(info); - options.SmartMatchInfos = infos.ToArray(); - } - - _config.SaveAutoOrganizeOptions(options); - } - } - - /// - /// Attempts to add a an item to the list of currently processed items. - /// - /// The result item. - /// Passing true will notify the client to reload all items, otherwise only a single item will be refreshed. - /// True if the item was added, False if the item is already contained in the list. - public bool AddToInProgressList(FileOrganizationResult result, bool isNewItem) - { - if (string.IsNullOrWhiteSpace(result.Id)) - { - result.Id = result.OriginalPath.GetMD5().ToString("N"); - } - - if (!_inProgressItemIds.TryAdd(result.Id, false)) - { - return false; - } - - result.IsInProgress = true; - - if (isNewItem) - { - EventHelper.FireEventIfNotNull(ItemAdded, this, new GenericEventArgs(result), _logger); - } - else - { - EventHelper.FireEventIfNotNull(ItemUpdated, this, new GenericEventArgs(result), _logger); - } - - return true; - } - - /// - /// Removes an item from the list of currently processed items. - /// - /// The result item. - /// True if the item was removed, False if the item was not contained in the list. - public bool RemoveFromInprogressList(FileOrganizationResult result) - { - bool itemValue; - var retval = _inProgressItemIds.TryRemove(result.Id, out itemValue); - - result.IsInProgress = false; - - EventHelper.FireEventIfNotNull(ItemUpdated, this, new GenericEventArgs(result), _logger); - - return retval; - } - - } -} diff --git a/Emby.Server.Implementations/FileOrganization/NameUtils.cs b/Emby.Server.Implementations/FileOrganization/NameUtils.cs deleted file mode 100644 index eb22ca4ea8..0000000000 --- a/Emby.Server.Implementations/FileOrganization/NameUtils.cs +++ /dev/null @@ -1,81 +0,0 @@ -using MediaBrowser.Model.Extensions; -using MediaBrowser.Controller.Entities; -using System; -using System.Globalization; -using MediaBrowser.Controller.Extensions; - -namespace Emby.Server.Implementations.FileOrganization -{ - public static class NameUtils - { - private static readonly CultureInfo UsCulture = new CultureInfo("en-US"); - - internal static Tuple GetMatchScore(string sortedName, int? year, T series) - where T : BaseItem - { - var score = 0; - - var seriesNameWithoutYear = series.Name; - if (series.ProductionYear.HasValue) - { - seriesNameWithoutYear = seriesNameWithoutYear.Replace(series.ProductionYear.Value.ToString(UsCulture), String.Empty); - } - - if (IsNameMatch(sortedName, seriesNameWithoutYear)) - { - score++; - - if (year.HasValue && series.ProductionYear.HasValue) - { - if (year.Value == series.ProductionYear.Value) - { - score++; - } - else - { - // Regardless of name, return a 0 score if the years don't match - return new Tuple(series, 0); - } - } - } - - return new Tuple(series, score); - } - - - private static bool IsNameMatch(string name1, string name2) - { - name1 = GetComparableName(name1); - name2 = GetComparableName(name2); - - return String.Equals(name1, name2, StringComparison.OrdinalIgnoreCase); - } - - private static string GetComparableName(string name) - { - name = name.RemoveDiacritics(); - - name = " " + name + " "; - - name = name.Replace(".", " ") - .Replace("_", " ") - .Replace(" and ", " ") - .Replace(".and.", " ") - .Replace("&", " ") - .Replace("!", " ") - .Replace("(", " ") - .Replace(")", " ") - .Replace(":", " ") - .Replace(",", " ") - .Replace("-", " ") - .Replace("'", " ") - .Replace("[", " ") - .Replace("]", " ") - .Replace(" a ", String.Empty, StringComparison.OrdinalIgnoreCase) - .Replace(" the ", String.Empty, StringComparison.OrdinalIgnoreCase) - .Replace(" ", String.Empty); - - return name.Trim(); - } - } -} diff --git a/Emby.Server.Implementations/FileOrganization/OrganizerScheduledTask.cs b/Emby.Server.Implementations/FileOrganization/OrganizerScheduledTask.cs deleted file mode 100644 index b71a3975fb..0000000000 --- a/Emby.Server.Implementations/FileOrganization/OrganizerScheduledTask.cs +++ /dev/null @@ -1,101 +0,0 @@ -using MediaBrowser.Controller.Configuration; -using MediaBrowser.Controller.FileOrganization; -using MediaBrowser.Controller.Library; -using MediaBrowser.Controller.Providers; -using MediaBrowser.Model.FileOrganization; -using MediaBrowser.Model.Logging; -using System; -using System.Collections.Generic; -using System.Threading; -using System.Threading.Tasks; - -using MediaBrowser.Controller.IO; -using MediaBrowser.Model.IO; -using MediaBrowser.Model.Tasks; - -namespace Emby.Server.Implementations.FileOrganization -{ - public class OrganizerScheduledTask : IScheduledTask, IConfigurableScheduledTask - { - private readonly ILibraryMonitor _libraryMonitor; - private readonly ILibraryManager _libraryManager; - private readonly ILogger _logger; - private readonly IFileSystem _fileSystem; - private readonly IServerConfigurationManager _config; - private readonly IFileOrganizationService _organizationService; - private readonly IProviderManager _providerManager; - - public OrganizerScheduledTask(ILibraryMonitor libraryMonitor, ILibraryManager libraryManager, ILogger logger, IFileSystem fileSystem, IServerConfigurationManager config, IFileOrganizationService organizationService, IProviderManager providerManager) - { - _libraryMonitor = libraryMonitor; - _libraryManager = libraryManager; - _logger = logger; - _fileSystem = fileSystem; - _config = config; - _organizationService = organizationService; - _providerManager = providerManager; - } - - public string Name - { - get { return "Organize new media files"; } - } - - public string Description - { - get { return "Processes new files available in the configured watch folder."; } - } - - public string Category - { - get { return "Library"; } - } - - private AutoOrganizeOptions GetAutoOrganizeOptions() - { - return _config.GetAutoOrganizeOptions(); - } - - public async Task Execute(CancellationToken cancellationToken, IProgress progress) - { - if (GetAutoOrganizeOptions().TvOptions.IsEnabled) - { - await new TvFolderOrganizer(_libraryManager, _logger, _fileSystem, _libraryMonitor, _organizationService, _config, _providerManager) - .Organize(GetAutoOrganizeOptions(), cancellationToken, progress).ConfigureAwait(false); - } - } - - /// - /// Creates the triggers that define when the task will run - /// - /// IEnumerable{BaseTaskTrigger}. - public IEnumerable GetDefaultTriggers() - { - return new[] { - - // Every so often - new TaskTriggerInfo { Type = TaskTriggerInfo.TriggerInterval, IntervalTicks = TimeSpan.FromMinutes(5).Ticks} - }; - } - - public bool IsHidden - { - get { return !GetAutoOrganizeOptions().TvOptions.IsEnabled; } - } - - public bool IsEnabled - { - get { return GetAutoOrganizeOptions().TvOptions.IsEnabled; } - } - - public bool IsLogged - { - get { return false; } - } - - public string Key - { - get { return "AutoOrganize"; } - } - } -} diff --git a/Emby.Server.Implementations/FileOrganization/TvFolderOrganizer.cs b/Emby.Server.Implementations/FileOrganization/TvFolderOrganizer.cs deleted file mode 100644 index 0dbd6f8375..0000000000 --- a/Emby.Server.Implementations/FileOrganization/TvFolderOrganizer.cs +++ /dev/null @@ -1,236 +0,0 @@ -using MediaBrowser.Controller.Configuration; -using MediaBrowser.Controller.FileOrganization; -using MediaBrowser.Controller.Library; -using MediaBrowser.Controller.Providers; -using MediaBrowser.Model.FileOrganization; -using MediaBrowser.Model.Logging; -using System; -using System.Collections.Generic; -using System.IO; -using System.Linq; -using System.Threading; -using System.Threading.Tasks; - -using MediaBrowser.Controller.IO; -using MediaBrowser.Model.IO; - -namespace Emby.Server.Implementations.FileOrganization -{ - public class TvFolderOrganizer - { - private readonly ILibraryMonitor _libraryMonitor; - private readonly ILibraryManager _libraryManager; - private readonly ILogger _logger; - private readonly IFileSystem _fileSystem; - private readonly IFileOrganizationService _organizationService; - private readonly IServerConfigurationManager _config; - private readonly IProviderManager _providerManager; - - public TvFolderOrganizer(ILibraryManager libraryManager, ILogger logger, IFileSystem fileSystem, ILibraryMonitor libraryMonitor, IFileOrganizationService organizationService, IServerConfigurationManager config, IProviderManager providerManager) - { - _libraryManager = libraryManager; - _logger = logger; - _fileSystem = fileSystem; - _libraryMonitor = libraryMonitor; - _organizationService = organizationService; - _config = config; - _providerManager = providerManager; - } - - private bool EnableOrganization(FileSystemMetadata fileInfo, TvFileOrganizationOptions options) - { - var minFileBytes = options.MinFileSizeMb * 1024 * 1024; - - try - { - return _libraryManager.IsVideoFile(fileInfo.FullName) && fileInfo.Length >= minFileBytes; - } - catch (Exception ex) - { - _logger.ErrorException("Error organizing file {0}", ex, fileInfo.Name); - } - - return false; - } - - private bool IsValidWatchLocation(string path, List libraryFolderPaths) - { - if (IsPathAlreadyInMediaLibrary(path, libraryFolderPaths)) - { - _logger.Info("Folder {0} is not eligible for auto-organize because it is also part of an Emby library", path); - return false; - } - - return true; - } - - private bool IsPathAlreadyInMediaLibrary(string path, List libraryFolderPaths) - { - return libraryFolderPaths.Any(i => string.Equals(i, path, StringComparison.Ordinal) || _fileSystem.ContainsSubPath(i, path)); - } - - public async Task Organize(AutoOrganizeOptions options, CancellationToken cancellationToken, IProgress progress) - { - var libraryFolderPaths = _libraryManager.GetVirtualFolders().SelectMany(i => i.Locations).ToList(); - - var watchLocations = options.TvOptions.WatchLocations - .Where(i => IsValidWatchLocation(i, libraryFolderPaths)) - .ToList(); - - var eligibleFiles = watchLocations.SelectMany(GetFilesToOrganize) - .OrderBy(_fileSystem.GetCreationTimeUtc) - .Where(i => EnableOrganization(i, options.TvOptions)) - .ToList(); - - var processedFolders = new HashSet(); - - progress.Report(10); - - if (eligibleFiles.Count > 0) - { - var numComplete = 0; - - foreach (var file in eligibleFiles) - { - cancellationToken.ThrowIfCancellationRequested(); - - var organizer = new EpisodeFileOrganizer(_organizationService, _config, _fileSystem, _logger, _libraryManager, - _libraryMonitor, _providerManager); - - try - { - var result = await organizer.OrganizeEpisodeFile(file.FullName, options, options.TvOptions.OverwriteExistingEpisodes, cancellationToken).ConfigureAwait(false); - - if (result.Status == FileSortingStatus.Success && !processedFolders.Contains(file.DirectoryName, StringComparer.OrdinalIgnoreCase)) - { - processedFolders.Add(file.DirectoryName); - } - } - catch (OperationCanceledException) - { - break; - } - catch (Exception ex) - { - _logger.ErrorException("Error organizing episode {0}", ex, file.FullName); - } - - numComplete++; - double percent = numComplete; - percent /= eligibleFiles.Count; - - progress.Report(10 + 89 * percent); - } - } - - cancellationToken.ThrowIfCancellationRequested(); - progress.Report(99); - - foreach (var path in processedFolders) - { - var deleteExtensions = options.TvOptions.LeftOverFileExtensionsToDelete - .Select(i => i.Trim().TrimStart('.')) - .Where(i => !string.IsNullOrEmpty(i)) - .Select(i => "." + i) - .ToList(); - - if (deleteExtensions.Count > 0) - { - DeleteLeftOverFiles(path, deleteExtensions); - } - - if (options.TvOptions.DeleteEmptyFolders) - { - if (!IsWatchFolder(path, watchLocations)) - { - DeleteEmptyFolders(path); - } - } - } - - progress.Report(100); - } - - /// - /// Gets the files to organize. - /// - /// The path. - /// IEnumerable{FileInfo}. - private List GetFilesToOrganize(string path) - { - try - { - return _fileSystem.GetFiles(path, true) - .ToList(); - } - catch (IOException ex) - { - _logger.ErrorException("Error getting files from {0}", ex, path); - - return new List(); - } - } - - /// - /// Deletes the left over files. - /// - /// The path. - /// The extensions. - private void DeleteLeftOverFiles(string path, IEnumerable extensions) - { - var eligibleFiles = _fileSystem.GetFilePaths(path, extensions.ToArray(), false, true) - .ToList(); - - foreach (var file in eligibleFiles) - { - try - { - _fileSystem.DeleteFile(file); - } - catch (Exception ex) - { - _logger.ErrorException("Error deleting file {0}", ex, file); - } - } - } - - /// - /// Deletes the empty folders. - /// - /// The path. - private void DeleteEmptyFolders(string path) - { - try - { - foreach (var d in _fileSystem.GetDirectoryPaths(path)) - { - DeleteEmptyFolders(d); - } - - var entries = _fileSystem.GetFileSystemEntryPaths(path); - - if (!entries.Any()) - { - try - { - _logger.Debug("Deleting empty directory {0}", path); - _fileSystem.DeleteDirectory(path, false); - } - catch (UnauthorizedAccessException) { } - catch (IOException) { } - } - } - catch (UnauthorizedAccessException) { } - } - - /// - /// Determines if a given folder path is contained in a folder list - /// - /// The folder path to check. - /// A list of folders. - private bool IsWatchFolder(string path, IEnumerable watchLocations) - { - return watchLocations.Contains(path, StringComparer.OrdinalIgnoreCase); - } - } -} \ No newline at end of file diff --git a/Emby.Server.Implementations/LiveTv/EmbyTV/EmbyTV.cs b/Emby.Server.Implementations/LiveTv/EmbyTV/EmbyTV.cs index b55e4412be..9ac599846b 100644 --- a/Emby.Server.Implementations/LiveTv/EmbyTV/EmbyTV.cs +++ b/Emby.Server.Implementations/LiveTv/EmbyTV/EmbyTV.cs @@ -4,7 +4,6 @@ using MediaBrowser.Common.Net; using MediaBrowser.Common.Security; using MediaBrowser.Controller.Configuration; using MediaBrowser.Controller.Drawing; -using MediaBrowser.Controller.FileOrganization; using MediaBrowser.Controller.Library; using MediaBrowser.Controller.LiveTv; using MediaBrowser.Controller.MediaEncoding; @@ -36,7 +35,6 @@ using MediaBrowser.Controller.Entities.TV; using MediaBrowser.Controller.IO; using MediaBrowser.Model.Configuration; using MediaBrowser.Model.Diagnostics; -using MediaBrowser.Model.FileOrganization; using MediaBrowser.Model.System; using MediaBrowser.Model.Threading; using MediaBrowser.Model.Extensions; @@ -61,7 +59,6 @@ namespace Emby.Server.Implementations.LiveTv.EmbyTV private readonly ILibraryMonitor _libraryMonitor; private readonly ILibraryManager _libraryManager; private readonly IProviderManager _providerManager; - private readonly IFileOrganizationService _organizationService; private readonly IMediaEncoder _mediaEncoder; private readonly IProcessFactory _processFactory; private readonly ISystemEvents _systemEvents; @@ -74,7 +71,7 @@ namespace Emby.Server.Implementations.LiveTv.EmbyTV private readonly ConcurrentDictionary _activeRecordings = new ConcurrentDictionary(StringComparer.OrdinalIgnoreCase); - public EmbyTV(IServerApplicationHost appHost, ILogger logger, IJsonSerializer jsonSerializer, IHttpClient httpClient, IServerConfigurationManager config, ILiveTvManager liveTvManager, IFileSystem fileSystem, ILibraryManager libraryManager, ILibraryMonitor libraryMonitor, IProviderManager providerManager, IFileOrganizationService organizationService, IMediaEncoder mediaEncoder, ITimerFactory timerFactory, IProcessFactory processFactory, ISystemEvents systemEvents) + public EmbyTV(IServerApplicationHost appHost, ILogger logger, IJsonSerializer jsonSerializer, IHttpClient httpClient, IServerConfigurationManager config, ILiveTvManager liveTvManager, IFileSystem fileSystem, ILibraryManager libraryManager, ILibraryMonitor libraryMonitor, IProviderManager providerManager, IMediaEncoder mediaEncoder, ITimerFactory timerFactory, IProcessFactory processFactory, ISystemEvents systemEvents) { Current = this; @@ -86,7 +83,6 @@ namespace Emby.Server.Implementations.LiveTv.EmbyTV _libraryManager = libraryManager; _libraryMonitor = libraryMonitor; _providerManager = providerManager; - _organizationService = organizationService; _mediaEncoder = mediaEncoder; _processFactory = processFactory; _systemEvents = systemEvents; diff --git a/MediaBrowser.Api/Library/FileOrganizationService.cs b/MediaBrowser.Api/Library/FileOrganizationService.cs deleted file mode 100644 index ea610ac5c1..0000000000 --- a/MediaBrowser.Api/Library/FileOrganizationService.cs +++ /dev/null @@ -1,213 +0,0 @@ -using System.Collections.Generic; -using MediaBrowser.Controller.FileOrganization; -using MediaBrowser.Controller.Net; -using MediaBrowser.Model.FileOrganization; -using MediaBrowser.Model.Querying; -using System.Threading.Tasks; -using MediaBrowser.Model.Dto; -using MediaBrowser.Model.Serialization; -using MediaBrowser.Model.Services; - -namespace MediaBrowser.Api.Library -{ - [Route("/Library/FileOrganization", "GET", Summary = "Gets file organization results")] - public class GetFileOrganizationActivity : IReturn> - { - /// - /// Skips over a given number of items within the results. Use for paging. - /// - /// The start index. - [ApiMember(Name = "StartIndex", Description = "Optional. The record index to start at. All items with a lower index will be dropped from the results.", IsRequired = false, DataType = "int", ParameterType = "query", Verb = "GET")] - public int? StartIndex { get; set; } - - /// - /// The maximum number of items to return - /// - /// The limit. - [ApiMember(Name = "Limit", Description = "Optional. The maximum number of records to return", IsRequired = false, DataType = "int", ParameterType = "query", Verb = "GET")] - public int? Limit { get; set; } - } - - [Route("/Library/FileOrganizations", "DELETE", Summary = "Clears the activity log")] - public class ClearOrganizationLog : IReturnVoid - { - } - - [Route("/Library/FileOrganizations/{Id}/File", "DELETE", Summary = "Deletes the original file of a organizer result")] - public class DeleteOriginalFile : IReturnVoid - { - /// - /// Gets or sets the id. - /// - /// The id. - [ApiMember(Name = "Id", Description = "Result Id", IsRequired = true, DataType = "string", ParameterType = "path", Verb = "DELETE")] - public string Id { get; set; } - } - - [Route("/Library/FileOrganizations/{Id}/Organize", "POST", Summary = "Performs an organization")] - public class PerformOrganization : IReturn> - { - /// - /// Gets or sets the id. - /// - /// The id. - [ApiMember(Name = "Id", Description = "Result Id", IsRequired = true, DataType = "string", ParameterType = "path", Verb = "POST")] - public string Id { get; set; } - } - - [Route("/Library/FileOrganizations/{Id}/Episode/Organize", "POST", Summary = "Performs organization of a tv episode")] - public class OrganizeEpisode - { - [ApiMember(Name = "Id", Description = "Result Id", IsRequired = true, DataType = "string", ParameterType = "path", Verb = "POST")] - public string Id { get; set; } - - [ApiMember(Name = "SeriesId", Description = "Series Id", IsRequired = true, DataType = "string", ParameterType = "query", Verb = "POST")] - public string SeriesId { get; set; } - - [ApiMember(Name = "SeasonNumber", IsRequired = true, DataType = "int", ParameterType = "query", Verb = "POST")] - public int SeasonNumber { get; set; } - - [ApiMember(Name = "EpisodeNumber", IsRequired = true, DataType = "int", ParameterType = "query", Verb = "POST")] - public int EpisodeNumber { get; set; } - - [ApiMember(Name = "EndingEpisodeNumber", IsRequired = false, DataType = "int", ParameterType = "query", Verb = "POST")] - public int? EndingEpisodeNumber { get; set; } - - [ApiMember(Name = "RememberCorrection", Description = "Whether or not to apply the same correction to future episodes of the same series.", IsRequired = false, DataType = "bool", ParameterType = "query", Verb = "POST")] - public bool RememberCorrection { get; set; } - - [ApiMember(Name = "NewSeriesProviderIds", Description = "A list of provider IDs identifying a new series.", IsRequired = false, DataType = "Dictionary", ParameterType = "query", Verb = "POST")] - public Dictionary NewSeriesProviderIds { get; set; } - - [ApiMember(Name = "NewSeriesName", Description = "Name of a series to add.", IsRequired = false, DataType = "string", ParameterType = "query", Verb = "POST")] - public string NewSeriesName { get; set; } - - [ApiMember(Name = "NewSeriesYear", Description = "Year of a series to add.", IsRequired = false, DataType = "string", ParameterType = "query", Verb = "POST")] - public string NewSeriesYear { get; set; } - - [ApiMember(Name = "TargetFolder", Description = "Target Folder", IsRequired = false, DataType = "string", ParameterType = "query", Verb = "POST")] - public string TargetFolder { get; set; } - } - - [Route("/Library/FileOrganizations/SmartMatches", "GET", Summary = "Gets smart match entries")] - public class GetSmartMatchInfos : IReturn> - { - /// - /// Skips over a given number of items within the results. Use for paging. - /// - /// The start index. - [ApiMember(Name = "StartIndex", Description = "Optional. The record index to start at. All items with a lower index will be dropped from the results.", IsRequired = false, DataType = "int", ParameterType = "query", Verb = "GET")] - public int? StartIndex { get; set; } - - /// - /// The maximum number of items to return - /// - /// The limit. - [ApiMember(Name = "Limit", Description = "Optional. The maximum number of records to return", IsRequired = false, DataType = "int", ParameterType = "query", Verb = "GET")] - public int? Limit { get; set; } - } - - [Route("/Library/FileOrganizations/SmartMatches/Delete", "POST", Summary = "Deletes a smart match entry")] - public class DeleteSmartMatchEntry - { - [ApiMember(Name = "Entries", Description = "SmartMatch Entry", IsRequired = true, DataType = "string", ParameterType = "query", Verb = "POST")] - public List Entries { get; set; } - } - - [Authenticated(Roles = "Admin")] - public class FileOrganizationService : BaseApiService - { - private readonly IFileOrganizationService _iFileOrganizationService; - - private readonly IJsonSerializer _jsonSerializer; - - public FileOrganizationService(IFileOrganizationService iFileOrganizationService, IJsonSerializer jsonSerializer) - { - _iFileOrganizationService = iFileOrganizationService; - _jsonSerializer = jsonSerializer; - } - - public object Get(GetFileOrganizationActivity request) - { - var result = _iFileOrganizationService.GetResults(new FileOrganizationResultQuery - { - Limit = request.Limit, - StartIndex = request.StartIndex - }); - - return ToOptimizedSerializedResultUsingCache(result); - } - - public void Delete(DeleteOriginalFile request) - { - var task = _iFileOrganizationService.DeleteOriginalFile(request.Id); - - Task.WaitAll(task); - } - - public void Delete(ClearOrganizationLog request) - { - var task = _iFileOrganizationService.ClearLog(); - - Task.WaitAll(task); - } - - public void Post(PerformOrganization request) - { - // Don't await this - var task = _iFileOrganizationService.PerformOrganization(request.Id); - - // Async processing (close dialog early instead of waiting until the file has been copied) - // Wait 2s for exceptions that may occur to have them forwarded to the client for immediate error display - task.Wait(2000); - } - - public void Post(OrganizeEpisode request) - { - var dicNewProviderIds = new Dictionary(); - - if (request.NewSeriesProviderIds != null) - { - dicNewProviderIds = request.NewSeriesProviderIds; - } - - // Don't await this - var task = _iFileOrganizationService.PerformEpisodeOrganization(new EpisodeFileOrganizationRequest - { - EndingEpisodeNumber = request.EndingEpisodeNumber, - EpisodeNumber = request.EpisodeNumber, - RememberCorrection = request.RememberCorrection, - ResultId = request.Id, - SeasonNumber = request.SeasonNumber, - SeriesId = request.SeriesId, - NewSeriesName = request.NewSeriesName, - NewSeriesYear = request.NewSeriesYear, - NewSeriesProviderIds = dicNewProviderIds, - TargetFolder = request.TargetFolder - }); - - // Async processing (close dialog early instead of waiting until the file has been copied) - // Wait 2s for exceptions that may occur to have them forwarded to the client for immediate error display - task.Wait(2000); - } - - public object Get(GetSmartMatchInfos request) - { - var result = _iFileOrganizationService.GetSmartMatchInfos(new FileOrganizationResultQuery - { - Limit = request.Limit, - StartIndex = request.StartIndex - }); - - return ToOptimizedSerializedResultUsingCache(result); - } - - public void Post(DeleteSmartMatchEntry request) - { - foreach (var entry in request.Entries) - { - _iFileOrganizationService.DeleteSmartMatchEntry(entry.Name, entry.Value); - } - } - } -} diff --git a/MediaBrowser.Api/MediaBrowser.Api.csproj b/MediaBrowser.Api/MediaBrowser.Api.csproj index a798ab5ff5..88889e5e76 100644 --- a/MediaBrowser.Api/MediaBrowser.Api.csproj +++ b/MediaBrowser.Api/MediaBrowser.Api.csproj @@ -93,7 +93,6 @@ - diff --git a/MediaBrowser.Controller/FileOrganization/IFileOrganizationService.cs b/MediaBrowser.Controller/FileOrganization/IFileOrganizationService.cs deleted file mode 100644 index 9a5b96a241..0000000000 --- a/MediaBrowser.Controller/FileOrganization/IFileOrganizationService.cs +++ /dev/null @@ -1,107 +0,0 @@ -using MediaBrowser.Model.Events; -using MediaBrowser.Model.FileOrganization; -using MediaBrowser.Model.Querying; -using System; -using System.Threading; -using System.Threading.Tasks; - -namespace MediaBrowser.Controller.FileOrganization -{ - public interface IFileOrganizationService - { - event EventHandler> ItemAdded; - event EventHandler> ItemUpdated; - event EventHandler> ItemRemoved; - event EventHandler LogReset; - - /// - /// Processes the new files. - /// - void BeginProcessNewFiles(); - - /// - /// Deletes the original file. - /// - /// The result identifier. - /// Task. - Task DeleteOriginalFile(string resultId); - - /// - /// Clears the log. - /// - /// Task. - Task ClearLog(); - - /// - /// Performs the organization. - /// - /// The result identifier. - /// Task. - Task PerformOrganization(string resultId); - - /// - /// Performs the episode organization. - /// - /// The request. - /// Task. - Task PerformEpisodeOrganization(EpisodeFileOrganizationRequest request); - - /// - /// Gets the results. - /// - /// The query. - /// IEnumerable{FileOrganizationResult}. - QueryResult GetResults(FileOrganizationResultQuery query); - - /// - /// Gets the result. - /// - /// The identifier. - /// FileOrganizationResult. - FileOrganizationResult GetResult(string id); - - /// - /// Gets the result by source path. - /// - /// The path. - /// FileOrganizationResult. - FileOrganizationResult GetResultBySourcePath(string path); - - /// - /// Saves the result. - /// - /// The result. - /// The cancellation token. - /// Task. - Task SaveResult(FileOrganizationResult result, CancellationToken cancellationToken); - - /// - /// Returns a list of smart match entries - /// - /// The query. - /// IEnumerable{SmartMatchInfo}. - QueryResult GetSmartMatchInfos(FileOrganizationResultQuery query); - - /// - /// Deletes a smart match entry. - /// - /// Item name. - /// The match string to delete. - void DeleteSmartMatchEntry(string ItemName, string matchString); - - /// - /// Attempts to add a an item to the list of currently processed items. - /// - /// The result item. - /// Passing true will notify the client to reload all items, otherwise only a single item will be refreshed. - /// True if the item was added, False if the item is already contained in the list. - bool AddToInProgressList(FileOrganizationResult result, bool fullClientRefresh); - - /// - /// Removes an item from the list of currently processed items. - /// - /// The result item. - /// True if the item was removed, False if the item was not contained in the list. - bool RemoveFromInprogressList(FileOrganizationResult result); - } -} diff --git a/MediaBrowser.Controller/MediaBrowser.Controller.csproj b/MediaBrowser.Controller/MediaBrowser.Controller.csproj index e28e1761ec..9cba48b749 100644 --- a/MediaBrowser.Controller/MediaBrowser.Controller.csproj +++ b/MediaBrowser.Controller/MediaBrowser.Controller.csproj @@ -128,7 +128,6 @@ - @@ -205,7 +204,6 @@ - diff --git a/MediaBrowser.Controller/Persistence/IFileOrganizationRepository.cs b/MediaBrowser.Controller/Persistence/IFileOrganizationRepository.cs deleted file mode 100644 index f71784d823..0000000000 --- a/MediaBrowser.Controller/Persistence/IFileOrganizationRepository.cs +++ /dev/null @@ -1,45 +0,0 @@ -using MediaBrowser.Model.FileOrganization; -using MediaBrowser.Model.Querying; -using System.Threading; -using System.Threading.Tasks; - -namespace MediaBrowser.Controller.Persistence -{ - public interface IFileOrganizationRepository - { - /// - /// Saves the result. - /// - /// The result. - /// The cancellation token. - /// Task. - Task SaveResult(FileOrganizationResult result, CancellationToken cancellationToken); - - /// - /// Deletes the specified identifier. - /// - /// The identifier. - /// Task. - Task Delete(string id); - - /// - /// Gets the result. - /// - /// The identifier. - /// FileOrganizationResult. - FileOrganizationResult GetResult(string id); - - /// - /// Gets the results. - /// - /// The query. - /// IEnumerable{FileOrganizationResult}. - QueryResult GetResults(FileOrganizationResultQuery query); - - /// - /// Deletes all. - /// - /// Task. - Task DeleteAll(); - } -} diff --git a/MediaBrowser.MediaEncoding/Encoder/MediaEncoder.cs b/MediaBrowser.MediaEncoding/Encoder/MediaEncoder.cs index f1bf29d92b..f416ea4178 100644 --- a/MediaBrowser.MediaEncoding/Encoder/MediaEncoder.cs +++ b/MediaBrowser.MediaEncoding/Encoder/MediaEncoder.cs @@ -597,21 +597,7 @@ namespace MediaBrowser.MediaEncoding.Encoder } } - var mediaInfo = new ProbeResultNormalizer(_logger, FileSystem, _memoryStreamProvider).GetMediaInfo(result, videoType, isAudio, primaryPath, protocol); - - var videoStream = mediaInfo.MediaStreams.FirstOrDefault(i => i.Type == MediaStreamType.Video); - - if (videoStream != null && !videoStream.IsInterlaced) - { - var isInterlaced = DetectInterlaced(mediaInfo, videoStream); - - if (isInterlaced) - { - videoStream.IsInterlaced = true; - } - } - - return mediaInfo; + return new ProbeResultNormalizer(_logger, FileSystem, _memoryStreamProvider).GetMediaInfo(result, videoType, isAudio, primaryPath, protocol); } catch { @@ -622,23 +608,6 @@ namespace MediaBrowser.MediaEncoding.Encoder } } - private bool DetectInterlaced(MediaSourceInfo video, MediaStream videoStream) - { - // If it's mpeg based, assume true - if ((videoStream.Codec ?? string.Empty).IndexOf("mpeg", StringComparison.OrdinalIgnoreCase) != -1) - { - var formats = (video.Container ?? string.Empty).Split(',').ToList(); - return formats.Contains("vob", StringComparer.OrdinalIgnoreCase) || - formats.Contains("m2ts", StringComparer.OrdinalIgnoreCase) || - formats.Contains("ts", StringComparer.OrdinalIgnoreCase) || - formats.Contains("mpegts", StringComparer.OrdinalIgnoreCase) || - formats.Contains("wtv", StringComparer.OrdinalIgnoreCase); - - } - - return false; - } - /// /// The us culture /// diff --git a/MediaBrowser.Model/FileOrganization/AutoOrganizeOptions.cs b/MediaBrowser.Model/FileOrganization/AutoOrganizeOptions.cs deleted file mode 100644 index 830d55bf54..0000000000 --- a/MediaBrowser.Model/FileOrganization/AutoOrganizeOptions.cs +++ /dev/null @@ -1,24 +0,0 @@ - -namespace MediaBrowser.Model.FileOrganization -{ - public class AutoOrganizeOptions - { - /// - /// Gets or sets the tv options. - /// - /// The tv options. - public TvFileOrganizationOptions TvOptions { get; set; } - - /// - /// Gets or sets a list of smart match entries. - /// - /// The smart match entries. - public SmartMatchInfo[] SmartMatchInfos { get; set; } - - public AutoOrganizeOptions() - { - TvOptions = new TvFileOrganizationOptions(); - SmartMatchInfos = new SmartMatchInfo[]{}; - } - } -} diff --git a/MediaBrowser.Model/FileOrganization/EpisodeFileOrganizationRequest.cs b/MediaBrowser.Model/FileOrganization/EpisodeFileOrganizationRequest.cs deleted file mode 100644 index b20e43e544..0000000000 --- a/MediaBrowser.Model/FileOrganization/EpisodeFileOrganizationRequest.cs +++ /dev/null @@ -1,26 +0,0 @@ -using System.Collections.Generic; - -namespace MediaBrowser.Model.FileOrganization -{ - public class EpisodeFileOrganizationRequest - { - public string ResultId { get; set; } - - public string SeriesId { get; set; } - - public int SeasonNumber { get; set; } - - public int EpisodeNumber { get; set; } - - public int? EndingEpisodeNumber { get; set; } - - public bool RememberCorrection { get; set; } - public string NewSeriesName { get; set; } - - public string NewSeriesYear { get; set; } - - public string TargetFolder { get; set; } - - public Dictionary NewSeriesProviderIds { get; set; } - } -} \ No newline at end of file diff --git a/MediaBrowser.Model/FileOrganization/FileOrganizationResult.cs b/MediaBrowser.Model/FileOrganization/FileOrganizationResult.cs deleted file mode 100644 index caf99183dc..0000000000 --- a/MediaBrowser.Model/FileOrganization/FileOrganizationResult.cs +++ /dev/null @@ -1,109 +0,0 @@ -using System; -using System.Collections.Generic; - -namespace MediaBrowser.Model.FileOrganization -{ - public class FileOrganizationResult - { - /// - /// Gets or sets the result identifier. - /// - /// The result identifier. - public string Id { get; set; } - - /// - /// Gets or sets the original path. - /// - /// The original path. - public string OriginalPath { get; set; } - - /// - /// Gets or sets the name of the original file. - /// - /// The name of the original file. - public string OriginalFileName { get; set; } - - /// - /// Gets or sets the name of the extracted. - /// - /// The name of the extracted. - public string ExtractedName { get; set; } - - /// - /// Gets or sets the extracted year. - /// - /// The extracted year. - public int? ExtractedYear { get; set; } - - /// - /// Gets or sets the extracted season number. - /// - /// The extracted season number. - public int? ExtractedSeasonNumber { get; set; } - - /// - /// Gets or sets the extracted episode number. - /// - /// The extracted episode number. - public int? ExtractedEpisodeNumber { get; set; } - - /// - /// Gets or sets the extracted ending episode number. - /// - /// The extracted ending episode number. - public int? ExtractedEndingEpisodeNumber { get; set; } - - /// - /// Gets or sets the target path. - /// - /// The target path. - public string TargetPath { get; set; } - - /// - /// Gets or sets the date. - /// - /// The date. - public DateTime Date { get; set; } - - /// - /// Gets or sets the error message. - /// - /// The error message. - public string StatusMessage { get; set; } - - /// - /// Gets or sets the status. - /// - /// The status. - public FileSortingStatus Status { get; set; } - - /// - /// Gets or sets the type. - /// - /// The type. - public FileOrganizerType Type { get; set; } - - /// - /// Gets or sets the duplicate paths. - /// - /// The duplicate paths. - public List DuplicatePaths { get; set; } - - /// - /// Gets or sets the size of the file. - /// - /// The size of the file. - public long FileSize { get; set; } - - /// - /// Indicates if the item is currently being processed. - /// - /// Runtime property not persisted to the store. - public bool IsInProgress { get; set; } - - public FileOrganizationResult() - { - DuplicatePaths = new List(); - } - } -} diff --git a/MediaBrowser.Model/FileOrganization/FileOrganizationResultQuery.cs b/MediaBrowser.Model/FileOrganization/FileOrganizationResultQuery.cs deleted file mode 100644 index 18287534ea..0000000000 --- a/MediaBrowser.Model/FileOrganization/FileOrganizationResultQuery.cs +++ /dev/null @@ -1,18 +0,0 @@ - -namespace MediaBrowser.Model.FileOrganization -{ - public class FileOrganizationResultQuery - { - /// - /// Skips over a given number of items within the results. Use for paging. - /// - /// The start index. - public int? StartIndex { get; set; } - - /// - /// The maximum number of items to return - /// - /// The limit. - public int? Limit { get; set; } - } -} diff --git a/MediaBrowser.Model/FileOrganization/FileOrganizerType.cs b/MediaBrowser.Model/FileOrganization/FileOrganizerType.cs deleted file mode 100644 index cbbeb9ce23..0000000000 --- a/MediaBrowser.Model/FileOrganization/FileOrganizerType.cs +++ /dev/null @@ -1,9 +0,0 @@ -namespace MediaBrowser.Model.FileOrganization -{ - public enum FileOrganizerType - { - Movie, - Episode, - Song - } -} \ No newline at end of file diff --git a/MediaBrowser.Model/FileOrganization/FileSortingStatus.cs b/MediaBrowser.Model/FileOrganization/FileSortingStatus.cs deleted file mode 100644 index 8a467c05fb..0000000000 --- a/MediaBrowser.Model/FileOrganization/FileSortingStatus.cs +++ /dev/null @@ -1,9 +0,0 @@ -namespace MediaBrowser.Model.FileOrganization -{ - public enum FileSortingStatus - { - Success, - Failure, - SkippedExisting - } -} \ No newline at end of file diff --git a/MediaBrowser.Model/FileOrganization/SmartMatchInfo.cs b/MediaBrowser.Model/FileOrganization/SmartMatchInfo.cs deleted file mode 100644 index 28c99b89bb..0000000000 --- a/MediaBrowser.Model/FileOrganization/SmartMatchInfo.cs +++ /dev/null @@ -1,16 +0,0 @@ - -namespace MediaBrowser.Model.FileOrganization -{ - public class SmartMatchInfo - { - public string ItemName { get; set; } - public string DisplayName { get; set; } - public FileOrganizerType OrganizerType { get; set; } - public string[] MatchStrings { get; set; } - - public SmartMatchInfo() - { - MatchStrings = new string[] { }; - } - } -} diff --git a/MediaBrowser.Model/FileOrganization/TvFileOrganizationOptions.cs b/MediaBrowser.Model/FileOrganization/TvFileOrganizationOptions.cs deleted file mode 100644 index 973ecf6e71..0000000000 --- a/MediaBrowser.Model/FileOrganization/TvFileOrganizationOptions.cs +++ /dev/null @@ -1,40 +0,0 @@ - -namespace MediaBrowser.Model.FileOrganization -{ - public class TvFileOrganizationOptions - { - public bool IsEnabled { get; set; } - public int MinFileSizeMb { get; set; } - public string[] LeftOverFileExtensionsToDelete { get; set; } - public string[] WatchLocations { get; set; } - - public string SeasonFolderPattern { get; set; } - - public string SeasonZeroFolderName { get; set; } - - public string EpisodeNamePattern { get; set; } - public string MultiEpisodeNamePattern { get; set; } - - public bool OverwriteExistingEpisodes { get; set; } - - public bool DeleteEmptyFolders { get; set; } - - public bool CopyOriginalFile { get; set; } - - public TvFileOrganizationOptions() - { - MinFileSizeMb = 50; - - LeftOverFileExtensionsToDelete = new string[] { }; - - WatchLocations = new string[] { }; - - EpisodeNamePattern = "%sn - %sx%0e - %en.%ext"; - MultiEpisodeNamePattern = "%sn - %sx%0e-x%0ed - %en.%ext"; - SeasonFolderPattern = "Season %s"; - SeasonZeroFolderName = "Season 0"; - - CopyOriginalFile = false; - } - } -} diff --git a/MediaBrowser.Model/MediaBrowser.Model.csproj b/MediaBrowser.Model/MediaBrowser.Model.csproj index e6cc58868b..249e970dd4 100644 --- a/MediaBrowser.Model/MediaBrowser.Model.csproj +++ b/MediaBrowser.Model/MediaBrowser.Model.csproj @@ -143,7 +143,6 @@ - @@ -160,8 +159,6 @@ - - @@ -248,11 +245,6 @@ - - - - - diff --git a/MediaBrowser.WebDashboard/Api/DashboardService.cs b/MediaBrowser.WebDashboard/Api/DashboardService.cs index cf3e221b33..c6bbca6728 100644 --- a/MediaBrowser.WebDashboard/Api/DashboardService.cs +++ b/MediaBrowser.WebDashboard/Api/DashboardService.cs @@ -174,6 +174,9 @@ namespace MediaBrowser.WebDashboard.Api IPlugin plugin = null; Stream stream = null; + var isJs = false; + var isTemplate = false; + var page = ServerEntryPoint.Instance.PluginConfigurationPages.FirstOrDefault(p => string.Equals(p.Name, request.Name, StringComparison.OrdinalIgnoreCase)); if (page != null) { @@ -188,11 +191,23 @@ namespace MediaBrowser.WebDashboard.Api { plugin = altPage.Item2; stream = _assemblyInfo.GetManifestResourceStream(plugin.GetType(), altPage.Item1.EmbeddedResourcePath); + + isJs = string.Equals(Path.GetExtension(altPage.Item1.EmbeddedResourcePath), ".js", StringComparison.OrdinalIgnoreCase); + isTemplate = altPage.Item1.EmbeddedResourcePath.EndsWith(".template.html"); } } if (plugin != null && stream != null) { + if (isJs) + { + return _resultFactory.GetStaticResult(Request, plugin.Version.ToString().GetMD5(), null, null, MimeTypes.GetMimeType("page.js"), () => Task.FromResult(stream)); + } + if (isTemplate) + { + return _resultFactory.GetStaticResult(Request, plugin.Version.ToString().GetMD5(), null, null, MimeTypes.GetMimeType("page.html"), () => Task.FromResult(stream)); + } + return _resultFactory.GetStaticResult(Request, plugin.Version.ToString().GetMD5(), null, null, MimeTypes.GetMimeType("page.html"), () => GetPackageCreator(DashboardUIPath).ModifyHtml("dummy.html", stream, null, _appHost.ApplicationVersion.ToString(), null)); } diff --git a/Nuget/MediaBrowser.Common.nuspec b/Nuget/MediaBrowser.Common.nuspec index 26c19c79a8..ff0973b0f2 100644 --- a/Nuget/MediaBrowser.Common.nuspec +++ b/Nuget/MediaBrowser.Common.nuspec @@ -2,7 +2,7 @@ MediaBrowser.Common - 3.0.708 + 3.0.709 Emby.Common Emby Team ebr,Luke,scottisafool diff --git a/Nuget/MediaBrowser.Server.Core.nuspec b/Nuget/MediaBrowser.Server.Core.nuspec index 641db709a2..31b8ee0264 100644 --- a/Nuget/MediaBrowser.Server.Core.nuspec +++ b/Nuget/MediaBrowser.Server.Core.nuspec @@ -2,7 +2,7 @@ MediaBrowser.Server.Core - 3.0.708 + 3.0.709 Emby.Server.Core Emby Team ebr,Luke,scottisafool @@ -12,7 +12,7 @@ Contains core components required to build plugins for Emby Server. Copyright © Emby 2013 - + From d0aa25f0295580303cffddb9539b50a05a22b41f Mon Sep 17 00:00:00 2001 From: Luke Pulverenti Date: Thu, 27 Jul 2017 01:21:09 -0400 Subject: [PATCH 2/2] 3.2.26.5 --- SharedVersion.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/SharedVersion.cs b/SharedVersion.cs index 636bb7b965..e799ddfeb6 100644 --- a/SharedVersion.cs +++ b/SharedVersion.cs @@ -1,3 +1,3 @@ using System.Reflection; -[assembly: AssemblyVersion("3.2.26.4")] +[assembly: AssemblyVersion("3.2.26.5")]