浏览代码

Merge pull request #1432 from softworkz/SeriesDetectionV2

Supersedes #1192: Auto-Organize - Added feature to remember/persist series matching in manual organization dialog
Luke 9 年之前
父节点
当前提交
b398b4eaab

+ 44 - 0
MediaBrowser.Api/Library/FileOrganizationService.cs

@@ -74,6 +74,34 @@ namespace MediaBrowser.Api.Library
         public bool RememberCorrection { get; set; }
     }
 
+    [Route("/Library/FileOrganizationSmartMatch", "GET", Summary = "Gets smart match entries")]
+    public class GetSmartMatchInfos : IReturn<QueryResult<SmartMatchInfo>>
+    {
+        /// <summary>
+        /// Skips over a given number of items within the results. Use for paging.
+        /// </summary>
+        /// <value>The start index.</value>
+        [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; }
+
+        /// <summary>
+        /// The maximum number of items to return
+        /// </summary>
+        /// <value>The limit.</value>
+        [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/FileOrganizationSmartMatch/{Id}/Delete", "POST", Summary = "Deletes a smart match entry")]
+    public class DeleteSmartMatchEntry
+    {
+        [ApiMember(Name = "Id", Description = "Item ID", IsRequired = true, DataType = "string", ParameterType = "path", Verb = "POST")]
+        public string Id { get; set; }
+
+        [ApiMember(Name = "MatchString", Description = "SmartMatch String", IsRequired = true, DataType = "string", ParameterType = "query", Verb = "POST")]
+        public string MatchString { get; set; }
+    }
+
     [Authenticated(Roles = "Admin")]
     public class FileOrganizationService : BaseApiService
     {
@@ -130,5 +158,21 @@ namespace MediaBrowser.Api.Library
 
             Task.WaitAll(task);
         }
+
+        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)
+        {
+            _iFileOrganizationService.DeleteSmartMatchEntry(request.Id, request.MatchString);
+        }
     }
 }

+ 14 - 0
MediaBrowser.Controller/FileOrganization/IFileOrganizationService.cs

@@ -67,5 +67,19 @@ namespace MediaBrowser.Controller.FileOrganization
         /// <param name="cancellationToken">The cancellation token.</param>
         /// <returns>Task.</returns>
         Task SaveResult(FileOrganizationResult result, CancellationToken cancellationToken);
+
+        /// <summary>
+        /// Returns a list of smart match entries
+        /// </summary>
+        /// <param name="query">The query.</param>
+        /// <returns>IEnumerable{SmartMatchInfo}.</returns>
+        QueryResult<SmartMatchInfo> GetSmartMatchInfos(FileOrganizationResultQuery query);
+
+        /// <summary>
+        /// Deletes a smart match entry.
+        /// </summary>
+        /// <param name="Id">Item Id.</param>
+        /// <param name="matchString">The match string to delete.</param>
+        void DeleteSmartMatchEntry(string Id, string matchString);
     }
 }

+ 3 - 0
MediaBrowser.Model.Portable/MediaBrowser.Model.Portable.csproj

@@ -668,6 +668,9 @@
     <Compile Include="..\MediaBrowser.Model\FileOrganization\FileSortingStatus.cs">
       <Link>FileOrganization\FileSortingStatus.cs</Link>
     </Compile>
+    <Compile Include="..\MediaBrowser.Model\FileOrganization\SmartMatchInfo.cs">
+      <Link>FileOrganization\SmartMatchInfo.cs</Link>
+    </Compile>
     <Compile Include="..\MediaBrowser.Model\FileOrganization\TvFileOrganizationOptions.cs">
       <Link>FileOrganization\TvFileOrganizationOptions.cs</Link>
     </Compile>

+ 3 - 0
MediaBrowser.Model.net35/MediaBrowser.Model.net35.csproj

@@ -633,6 +633,9 @@
     <Compile Include="..\MediaBrowser.Model\FileOrganization\FileSortingStatus.cs">
       <Link>FileOrganization\FileSortingStatus.cs</Link>
     </Compile>
+    <Compile Include="..\MediaBrowser.Model\FileOrganization\SmartMatchInfo.cs">
+      <Link>FileOrganization\SmartMatchInfo.cs</Link>
+    </Compile>
     <Compile Include="..\MediaBrowser.Model\FileOrganization\TvFileOrganizationOptions.cs">
       <Link>FileOrganization\TvFileOrganizationOptions.cs</Link>
     </Compile>

+ 8 - 0
MediaBrowser.Model/FileOrganization/AutoOrganizeOptions.cs

@@ -1,4 +1,5 @@
 
+using System.Collections.Generic;
 namespace MediaBrowser.Model.FileOrganization
 {
     public class AutoOrganizeOptions
@@ -9,9 +10,16 @@ namespace MediaBrowser.Model.FileOrganization
         /// <value>The tv options.</value>
         public TvFileOrganizationOptions TvOptions { get; set; }
 
+        /// <summary>
+        /// Gets or sets a list of smart match entries.
+        /// </summary>
+        /// <value>The smart match entries.</value>
+        public List<SmartMatchInfo> SmartMatchInfos { get; set; }
+
         public AutoOrganizeOptions()
         {
             TvOptions = new TvFileOrganizationOptions();
+            SmartMatchInfos = new List<SmartMatchInfo>();
         }
     }
 }

+ 19 - 0
MediaBrowser.Model/FileOrganization/SmartMatchInfo.cs

@@ -0,0 +1,19 @@
+
+using System;
+using System.Collections.Generic;
+
+namespace MediaBrowser.Model.FileOrganization
+{
+    public class SmartMatchInfo
+    {
+        public Guid Id { get; set; }
+        public string Name { get; set; }
+        public FileOrganizerType OrganizerType { get; set; }
+        public List<string> MatchStrings { get; set; }
+
+        public SmartMatchInfo()
+        {
+            MatchStrings = new List<string>();
+        }
+    }
+}

+ 1 - 0
MediaBrowser.Model/MediaBrowser.Model.csproj

@@ -137,6 +137,7 @@
     <Compile Include="Dto\MetadataEditorInfo.cs" />
     <Compile Include="Dto\NameIdPair.cs" />
     <Compile Include="Dto\NameValuePair.cs" />
+    <Compile Include="FileOrganization\SmartMatchInfo.cs" />
     <Compile Include="MediaInfo\LiveStreamRequest.cs" />
     <Compile Include="MediaInfo\LiveStreamResponse.cs" />
     <Compile Include="MediaInfo\PlaybackInfoRequest.cs" />

+ 58 - 11
MediaBrowser.Server.Implementations/FileOrganization/EpisodeFileOrganizer.cs

@@ -46,12 +46,12 @@ namespace MediaBrowser.Server.Implementations.FileOrganization
 
         public Task<FileOrganizationResult> OrganizeEpisodeFile(string path, CancellationToken cancellationToken)
         {
-            var options = _config.GetAutoOrganizeOptions().TvOptions;
+            var options = _config.GetAutoOrganizeOptions();
 
             return OrganizeEpisodeFile(path, options, false, cancellationToken);
         }
 
-        public async Task<FileOrganizationResult> OrganizeEpisodeFile(string path, TvFileOrganizationOptions options, bool overwriteExisting, CancellationToken cancellationToken)
+        public async Task<FileOrganizationResult> OrganizeEpisodeFile(string path, AutoOrganizeOptions options, bool overwriteExisting, CancellationToken cancellationToken)
         {
             _logger.Info("Sorting file {0}", path);
 
@@ -110,6 +110,7 @@ namespace MediaBrowser.Server.Implementations.FileOrganization
                         premiereDate,
                         options,
                         overwriteExisting,
+						false,
                         result,
                         cancellationToken).ConfigureAwait(false);
                 }
@@ -145,7 +146,7 @@ namespace MediaBrowser.Server.Implementations.FileOrganization
             return result;
         }
 
-        public async Task<FileOrganizationResult> OrganizeWithCorrection(EpisodeFileOrganizationRequest request, TvFileOrganizationOptions options, CancellationToken cancellationToken)
+        public async Task<FileOrganizationResult> OrganizeWithCorrection(EpisodeFileOrganizationRequest request, AutoOrganizeOptions options, CancellationToken cancellationToken)
         {
             var result = _organizationService.GetResult(request.ResultId);
 
@@ -159,6 +160,7 @@ namespace MediaBrowser.Server.Implementations.FileOrganization
                 null,
                 options,
                 true,
+				request.RememberCorrection,
                 result,
                 cancellationToken).ConfigureAwait(false);
 
@@ -173,12 +175,13 @@ namespace MediaBrowser.Server.Implementations.FileOrganization
             int? episodeNumber,
             int? endingEpiosdeNumber,
             DateTime? premiereDate,
-            TvFileOrganizationOptions options,
+            AutoOrganizeOptions options,
             bool overwriteExisting,
+			bool rememberCorrection,
             FileOrganizationResult result,
             CancellationToken cancellationToken)
         {
-            var series = GetMatchingSeries(seriesName, result);
+            var series = GetMatchingSeries(seriesName, result, options);
 
             if (series == null)
             {
@@ -197,6 +200,7 @@ namespace MediaBrowser.Server.Implementations.FileOrganization
                 premiereDate,
                 options,
                 overwriteExisting,
+				rememberCorrection,
                 result,
                 cancellationToken);
         }
@@ -207,15 +211,18 @@ namespace MediaBrowser.Server.Implementations.FileOrganization
             int? episodeNumber,
             int? endingEpiosdeNumber,
             DateTime? premiereDate,
-            TvFileOrganizationOptions options,
+            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;
+
             // Proceed to sort the file
-            var newPath = await GetNewPath(sourcePath, series, seasonNumber, episodeNumber, endingEpiosdeNumber, premiereDate, options, cancellationToken).ConfigureAwait(false);
+            var newPath = await GetNewPath(sourcePath, series, seasonNumber, episodeNumber, endingEpiosdeNumber, premiereDate, options.TvOptions, cancellationToken).ConfigureAwait(false);
 
             if (string.IsNullOrEmpty(newPath))
             {
@@ -234,7 +241,7 @@ namespace MediaBrowser.Server.Implementations.FileOrganization
 
             if (!overwriteExisting)
             {
-                if (options.CopyOriginalFile && fileExists && IsSameEpisode(sourcePath, newPath))
+                if (options.TvOptions.CopyOriginalFile && fileExists && IsSameEpisode(sourcePath, newPath))
                 {
                     _logger.Info("File {0} already copied to new path {1}, stopping organization", sourcePath, newPath);
                     result.Status = FileSortingStatus.SkippedExisting;
@@ -251,7 +258,7 @@ namespace MediaBrowser.Server.Implementations.FileOrganization
                 }
             }
 
-            PerformFileSorting(options, result);
+            PerformFileSorting(options.TvOptions, result);
 
             if (overwriteExisting)
             {
@@ -285,6 +292,31 @@ namespace MediaBrowser.Server.Implementations.FileOrganization
                     }
                 }
             }
+
+            if (rememberCorrection)
+            {
+                SaveSmartMatchString(originalExtractedSeriesString, series, options);
+            }
+        }
+
+        private void SaveSmartMatchString(string matchString, Series series, AutoOrganizeOptions options)
+        {
+            SmartMatchInfo info = options.SmartMatchInfos.Find(i => i.Id == series.Id);
+
+            if (info == null)
+            {
+                info = new SmartMatchInfo();
+                info.Id = series.Id;
+                info.OrganizerType = FileOrganizerType.Episode;
+                info.Name = series.Name;
+                options.SmartMatchInfos.Add(info);
+            }
+
+            if (!info.MatchStrings.Contains(matchString, StringComparer.OrdinalIgnoreCase))
+            {
+                info.MatchStrings.Add(matchString);
+                _config.SaveAutoOrganizeOptions(options);
+            }
         }
 
         private void DeleteLibraryFile(string path, bool renameRelatedFiles, string targetPath)
@@ -435,7 +467,7 @@ namespace MediaBrowser.Server.Implementations.FileOrganization
             }
         }
 
-        private Series GetMatchingSeries(string seriesName, FileOrganizationResult result)
+        private Series GetMatchingSeries(string seriesName, FileOrganizationResult result, AutoOrganizeOptions options)
         {
             var parsedName = _libraryManager.ParseName(seriesName);
 
@@ -445,13 +477,28 @@ namespace MediaBrowser.Server.Implementations.FileOrganization
             result.ExtractedName = nameWithoutYear;
             result.ExtractedYear = yearInName;
 
-            return _libraryManager.RootFolder.GetRecursiveChildren(i => i is Series)
+            var series = _libraryManager.RootFolder.GetRecursiveChildren(i => i is Series)
                 .Cast<Series>()
                 .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.Where(e => e.MatchStrings.Contains(seriesName, StringComparer.OrdinalIgnoreCase)).FirstOrDefault();
+
+                if (info != null)
+                {
+                    series = _libraryManager.RootFolder.GetRecursiveChildren(i => i is Series)
+                        .Cast<Series>()
+                        .Where(i => i.Id == info.Id)
+                        .FirstOrDefault();
+                }
+            }
+
+            return series ?? new Series();
         }
 
         /// <summary>

+ 4 - 0
MediaBrowser.Server.Implementations/FileOrganization/Extensions.cs

@@ -10,6 +10,10 @@ namespace MediaBrowser.Server.Implementations.FileOrganization
         {
             return manager.GetConfiguration<AutoOrganizeOptions>("autoorganize");
         }
+        public static void SaveAutoOrganizeOptions(this IConfigurationManager manager, AutoOrganizeOptions options)
+        {
+            manager.SaveConfiguration("autoorganize", options);
+        }
     }
 
     public class AutoOrganizeOptionsFactory : IConfigurationFactory

+ 53 - 4
MediaBrowser.Server.Implementations/FileOrganization/FileOrganizationService.cs

@@ -11,6 +11,7 @@ using MediaBrowser.Model.Logging;
 using MediaBrowser.Model.Querying;
 using System;
 using System.IO;
+using System.Linq;
 using System.Threading;
 using System.Threading.Tasks;
 using CommonIO;
@@ -96,9 +97,9 @@ namespace MediaBrowser.Server.Implementations.FileOrganization
             return _repo.Delete(resultId);
         }
 
-        private TvFileOrganizationOptions GetTvOptions()
+        private AutoOrganizeOptions GetAutoOrganizeptions()
         {
-            return _config.GetAutoOrganizeOptions().TvOptions;
+            return _config.GetAutoOrganizeOptions();
         }
 
         public async Task PerformOrganization(string resultId)
@@ -113,7 +114,7 @@ namespace MediaBrowser.Server.Implementations.FileOrganization
             var organizer = new EpisodeFileOrganizer(this, _config, _fileSystem, _logger, _libraryManager,
                 _libraryMonitor, _providerManager);
 
-            await organizer.OrganizeEpisodeFile(result.OriginalPath, GetTvOptions(), true, CancellationToken.None)
+            await organizer.OrganizeEpisodeFile(result.OriginalPath, GetAutoOrganizeptions(), true, CancellationToken.None)
                     .ConfigureAwait(false);
         }
 
@@ -127,7 +128,55 @@ namespace MediaBrowser.Server.Implementations.FileOrganization
             var organizer = new EpisodeFileOrganizer(this, _config, _fileSystem, _logger, _libraryManager,
                 _libraryMonitor, _providerManager);
 
-            await organizer.OrganizeWithCorrection(request, GetTvOptions(), CancellationToken.None).ConfigureAwait(false);
+            await organizer.OrganizeWithCorrection(request, GetAutoOrganizeptions(), CancellationToken.None).ConfigureAwait(false);
+        }
+
+        public QueryResult<SmartMatchInfo> GetSmartMatchInfos(FileOrganizationResultQuery query)
+        {
+            if (query == null)
+            {
+                throw new ArgumentNullException("query");
+            }
+
+            var options = GetAutoOrganizeptions();
+
+            var items = options.SmartMatchInfos.Skip(query.StartIndex ?? 0).Take(query.Limit ?? Int32.MaxValue);
+
+            return new QueryResult<SmartMatchInfo>()
+            {
+                Items = items.ToArray(),
+                TotalRecordCount = items.Count()
+            };
+        }
+
+        public void DeleteSmartMatchEntry(string IdString, string matchString)
+        {
+            Guid Id;
+
+            if (!Guid.TryParse(IdString, out Id))
+            {
+                throw new ArgumentNullException("Id");
+            }
+
+            if (string.IsNullOrEmpty(matchString))
+            {
+                throw new ArgumentNullException("matchString");
+            }
+
+            var options = GetAutoOrganizeptions();
+
+            SmartMatchInfo info = options.SmartMatchInfos.Find(i => i.Id == Id);
+
+            if (info != null && info.MatchStrings.Contains(matchString))
+            {
+                info.MatchStrings.Remove(matchString);
+                if (info.MatchStrings.Count == 0)
+                {
+                    options.SmartMatchInfos.Remove(info);
+                }
+
+                _config.SaveAutoOrganizeOptions(options);
+            }
         }
     }
 }

+ 6 - 6
MediaBrowser.Server.Implementations/FileOrganization/OrganizerScheduledTask.cs

@@ -50,17 +50,17 @@ namespace MediaBrowser.Server.Implementations.FileOrganization
             get { return "Library"; }
         }
 
-        private TvFileOrganizationOptions GetTvOptions()
+        private AutoOrganizeOptions GetAutoOrganizeOptions()
         {
-            return _config.GetAutoOrganizeOptions().TvOptions;
+            return _config.GetAutoOrganizeOptions();
         }
 
         public async Task Execute(CancellationToken cancellationToken, IProgress<double> progress)
         {
-            if (GetTvOptions().IsEnabled)
+            if (GetAutoOrganizeOptions().TvOptions.IsEnabled)
             {
                 await new TvFolderOrganizer(_libraryManager, _logger, _fileSystem, _libraryMonitor, _organizationService, _config, _providerManager)
-                    .Organize(GetTvOptions(), cancellationToken, progress).ConfigureAwait(false);
+                    .Organize(GetAutoOrganizeOptions(), cancellationToken, progress).ConfigureAwait(false);
             }
         }
 
@@ -74,12 +74,12 @@ namespace MediaBrowser.Server.Implementations.FileOrganization
 
         public bool IsHidden
         {
-            get { return !GetTvOptions().IsEnabled; }
+            get { return !GetAutoOrganizeOptions().TvOptions.IsEnabled; }
         }
 
         public bool IsEnabled
         {
-            get { return GetTvOptions().IsEnabled; }
+            get { return GetAutoOrganizeOptions().TvOptions.IsEnabled; }
         }
 
         public bool IsActivityLogged

+ 6 - 6
MediaBrowser.Server.Implementations/FileOrganization/TvFolderOrganizer.cs

@@ -52,13 +52,13 @@ namespace MediaBrowser.Server.Implementations.FileOrganization
             return false;
         }
 
-        public async Task Organize(TvFileOrganizationOptions options, CancellationToken cancellationToken, IProgress<double> progress)
+        public async Task Organize(AutoOrganizeOptions options, CancellationToken cancellationToken, IProgress<double> progress)
         {
-            var watchLocations = options.WatchLocations.ToList();
+            var watchLocations = options.TvOptions.WatchLocations.ToList();
 
             var eligibleFiles = watchLocations.SelectMany(GetFilesToOrganize)
                 .OrderBy(_fileSystem.GetCreationTimeUtc)
-                .Where(i => EnableOrganization(i, options))
+                .Where(i => EnableOrganization(i, options.TvOptions))
                 .ToList();
 
             var processedFolders = new HashSet<string>();
@@ -76,7 +76,7 @@ namespace MediaBrowser.Server.Implementations.FileOrganization
 
                     try
                     {
-                        var result = await organizer.OrganizeEpisodeFile(file.FullName, options, options.OverwriteExistingEpisodes, cancellationToken).ConfigureAwait(false);
+                        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);
@@ -100,7 +100,7 @@ namespace MediaBrowser.Server.Implementations.FileOrganization
 
             foreach (var path in processedFolders)
             {
-                var deleteExtensions = options.LeftOverFileExtensionsToDelete
+                var deleteExtensions = options.TvOptions.LeftOverFileExtensionsToDelete
                     .Select(i => i.Trim().TrimStart('.'))
                     .Where(i => !string.IsNullOrEmpty(i))
                     .Select(i => "." + i)
@@ -111,7 +111,7 @@ namespace MediaBrowser.Server.Implementations.FileOrganization
                     DeleteLeftOverFiles(path, deleteExtensions);
                 }
 
-                if (options.DeleteEmptyFolders)
+                if (options.TvOptions.DeleteEmptyFolders)
                 {
                     if (!IsWatchFolder(path, watchLocations))
                     {

+ 9 - 0
MediaBrowser.WebDashboard/MediaBrowser.WebDashboard.csproj

@@ -101,6 +101,12 @@
     <Content Include="dashboard-ui\components\chromecasthelpers.js">
       <CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
     </Content>
+    <Content Include="dashboard-ui\autoorganizesmart.html">
+      <CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
+    </Content>
+    <Content Include="dashboard-ui\bower_components\fastclick\lib\fastclick.js">
+      <CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
+    </Content>
     <Content Include="dashboard-ui\components\recordingcreator\recordingcreator.js">
       <CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
     </Content>
@@ -281,6 +287,9 @@
     <Content Include="dashboard-ui\scripts\mysyncsettings.js">
       <CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
     </Content>
+    <Content Include="dashboard-ui\scripts\autoorganizesmart.js">
+      <CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
+    </Content>
     <Content Include="dashboard-ui\scripts\searchmenu.js">
       <CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
     </Content>