From d3f823734e2c96fd3a89dae06f6b7dbda4993b2c Mon Sep 17 00:00:00 2001 From: "kay.one" Date: Sun, 19 Jun 2011 18:59:31 -0700 Subject: [PATCH] Cleanedup get GetNewFilename --- NzbDrone.Core/Providers/DiskScanProvider.cs | 428 ++++++++++++++++++ NzbDrone.Core/Providers/EpisodeProvider.cs | 2 +- .../Providers/Jobs/RenameEpisodeJob.cs | 3 +- NzbDrone.Core/Providers/MediaFileProvider.cs | 158 +++---- 4 files changed, 484 insertions(+), 107 deletions(-) create mode 100644 NzbDrone.Core/Providers/DiskScanProvider.cs diff --git a/NzbDrone.Core/Providers/DiskScanProvider.cs b/NzbDrone.Core/Providers/DiskScanProvider.cs new file mode 100644 index 000000000..0483d14d2 --- /dev/null +++ b/NzbDrone.Core/Providers/DiskScanProvider.cs @@ -0,0 +1,428 @@ +using System; +using System.Collections.Generic; +using System.IO; +using System.Linq; +using System.Text; +using Ninject; +using NLog; +using NzbDrone.Core.Helpers; +using NzbDrone.Core.Model.Notification; +using NzbDrone.Core.Providers.Core; +using NzbDrone.Core.Repository; +using NzbDrone.Core.Repository.Quality; +using PetaPoco; + +namespace NzbDrone.Core.Providers +{ + class DiskScanProvider + { + + private static readonly Logger Logger = LogManager.GetCurrentClassLogger(); + private static readonly string[] MediaExtentions = new[] { ".mkv", ".avi", ".wmv", ".mp4" }; + private readonly DiskProvider _diskProvider; + private readonly EpisodeProvider _episodeProvider; + private readonly SeriesProvider _seriesProvider; + private readonly ConfigProvider _configProvider; + private readonly IDatabase _database; + + [Inject] + public DiskScanProvider(DiskProvider diskProvider, EpisodeProvider episodeProvider, + SeriesProvider seriesProvider, ConfigProvider configProvider, + IDatabase database) + { + _diskProvider = diskProvider; + _episodeProvider = episodeProvider; + _seriesProvider = seriesProvider; + _configProvider = configProvider; + _database = database; + } + + + public DiskScanProvider() + { + + } + + + /// + /// Scans the specified series folder for media files + /// + /// The series to be scanned + public virtual List Scan(Series series) + { + if (_episodeProvider.GetEpisodeBySeries(series.SeriesId).Count == 0) + { + Logger.Debug("Series {0} has no episodes. skipping", series.Title); + return new List(); + } + + var mediaFileList = GetVideoFiles(series.Path); + var fileList = new List(); + + foreach (var filePath in mediaFileList) + { + var file = ImportFile(series, filePath); + if (file != null) + fileList.Add(file); + } + + series.LastDiskSync = DateTime.Now; + _seriesProvider.UpdateSeries(series); + + return fileList; + } + + public virtual EpisodeFile ImportFile(Series series, string filePath) + { + Logger.Trace("Importing file to database [{0}]", filePath); + + try + { + var size = _diskProvider.GetSize(filePath); + + //If Size is less than 50MB and contains sample. Check for Size to ensure its not an episode with sample in the title + if (size < 40000000 && filePath.ToLower().Contains("sample")) + { + Logger.Trace("[{0}] appears to be a sample. skipping.", filePath); + return null; + } + + //Check to see if file already exists in the database + if (!_database.Exists("Path =@0", Parser.NormalizePath(filePath))) + { + var parseResult = Parser.ParseEpisodeInfo(filePath); + + if (parseResult == null) + return null; + + parseResult.CleanTitle = series.Title;//replaces the nasty path as title to help with logging + + //Stores the list of episodes to add to the EpisodeFile + var episodes = new List(); + + //Check for daily shows + if (parseResult.EpisodeNumbers == null) + { + var episode = _episodeProvider.GetEpisode(series.SeriesId, parseResult.AirDate.Date); + + if (episode != null) + { + episodes.Add(episode); + } + else + { + Logger.Warn("Unable to find [{0}] in the database.[{1}]", parseResult, filePath); + } + } + else + { + foreach (var episodeNumber in parseResult.EpisodeNumbers) + { + var episode = _episodeProvider.GetEpisode(series.SeriesId, parseResult.SeasonNumber, + episodeNumber); + + if (episode != null) + { + episodes.Add(episode); + } + else + { + Logger.Warn("Unable to find [{0}] in the database.[{1}]", parseResult, filePath); + } + } + } + + //Return null if no Episodes exist in the DB for the parsed episodes from file + if (episodes.Count <= 0) + return null; + + var episodeFile = new EpisodeFile(); + episodeFile.DateAdded = DateTime.Now; + episodeFile.SeriesId = series.SeriesId; + episodeFile.Path = Parser.NormalizePath(filePath); + episodeFile.Size = size; + episodeFile.Quality = parseResult.Quality.QualityType; + episodeFile.Proper = parseResult.Quality.Proper; + episodeFile.SeasonNumber = parseResult.SeasonNumber; + var fileId = Convert.ToInt32(_database.Insert(episodeFile)); + + //This is for logging + updating the episodes that are linked to this EpisodeFile + string episodeList = String.Empty; + foreach (var ep in episodes) + { + ep.EpisodeFileId = fileId; + _episodeProvider.UpdateEpisode(ep); + episodeList += String.Format(", {0}", ep.EpisodeId).Trim(' ', ','); + } + Logger.Trace("File {0}:{1} attached to episode(s): '{2}'", episodeFile.EpisodeFileId, filePath, + episodeList); + + return episodeFile; + } + + Logger.Trace("[{0}] already exists in the database. skipping.", filePath); + } + catch (Exception ex) + { + Logger.ErrorException("An error has occurred while importing file " + filePath, ex); + throw; + } + return null; + } + + /// + /// Removes files that no longer exist from the database + /// + /// list of files to verify + public virtual void CleanUp(List files) + { + //TODO: remove orphaned files. in files table but not linked to from episode table. + foreach (var episodeFile in files) + { + if (!_diskProvider.FileExists(episodeFile.Path)) + { + Logger.Trace("File {0} no longer exists on disk. removing from database.", episodeFile.Path); + + //Set the EpisodeFileId for each episode attached to this file to 0 + foreach (var episode in episodeFile.Episodes) + { + episode.EpisodeFileId = 0; + _episodeProvider.UpdateEpisode(episode); + } + + //Delete it from the DB + _database.Delete(episodeFile.EpisodeFileId); + } + } + } + + + + private List GetVideoFiles(string path) + { + Logger.Debug("Scanning '{0}' for episodes", path); + + var filesOnDisk = _diskProvider.GetFiles(path, "*.*", SearchOption.AllDirectories); + + var mediaFileList = filesOnDisk.Where(c => MediaExtentions.Contains(Path.GetExtension(c).ToLower())).ToList(); + + Logger.Debug("{0} media files were found in {1}", mediaFileList.Count, path); + return mediaFileList; + } + + public virtual List ImportNewFiles(string path, Series series) + { + var result = new List(); + + //Get all the files except those that are considered samples + var files = GetVideoFiles(path).Where(f => _diskProvider.GetSize(f) > 40000000 || !f.ToLower().Contains("sample")).ToList(); + + foreach (var file in files) + { + try + { + //Parse the filename + var parseResult = Parser.ParseEpisodeInfo(Path.GetFileName(file)); + parseResult.Series = series; + parseResult.Episodes = _episodeProvider.GetEpisodes(parseResult); + + if (parseResult.Episodes.Count == 0) + { + Logger.Error("File '{0}' contains invalid episode information, skipping import", file); + continue; + } + + var ext = _diskProvider.GetExtension(file); + var filename = GetNewFilename(parseResult.Episodes, series.Title, parseResult.Quality.QualityType) + ext; + var folder = series.Path + Path.DirectorySeparatorChar; + if (_configProvider.UseSeasonFolder) + folder += _configProvider.SeasonFolderFormat + .Replace("%0s", parseResult.SeasonNumber.ToString("00")) + .Replace("%s", parseResult.SeasonNumber.ToString()) + + Path.DirectorySeparatorChar; + + _diskProvider.CreateDirectory(folder); + + //Get a list of episodeFiles that we need to delete and cleanup + var episodeFilesToClean = new List(); + + foreach (var episode in parseResult.Episodes) + { + if (episode.EpisodeFileId > 0) + episodeFilesToClean.Add(episode.EpisodeFile); + } + + if (episodeFilesToClean.Count != episodeFilesToClean.Where(e => parseResult.Quality.QualityType >= e.Quality).Count()) + { + Logger.Debug("Episode isn't an upgrade for all episodes in file: [{0}]. Skipping.", file); + continue; + } + + //Delete the files and then cleanup! + foreach (var e in episodeFilesToClean) + { + if (_diskProvider.FileExists(e.Path)) + _diskProvider.DeleteFile(e.Path); + } + + CleanUp(episodeFilesToClean); + + //Move the file + _diskProvider.RenameFile(file, folder + filename); + + //Import into DB + result.Add(ImportFile(series, folder + filename)); + } + + catch (Exception ex) + { + Logger.WarnException("Error importing new download: " + file, ex); + } + } + + //If we have imported all the non-sample files, delete the folder, requires a minimum of 1 file to be imported. + if (files.Count() > 0 && files.Count() == result.Count) + { + Logger.Debug("All non-sample files have been processed, deleting folder: {0}", path); + _diskProvider.DeleteFolder(path, true); + } + + return result; + } + + public virtual string GetNewFilename(IList episodes, string seriesName, QualityTypes quality) + { + var separatorStyle = EpisodeSortingHelper.GetSeparatorStyle(_configProvider.SeparatorStyle); + var numberStyle = EpisodeSortingHelper.GetNumberStyle(_configProvider.NumberStyle); + var useSeriesName = _configProvider.SeriesName; + var useEpisodeName = _configProvider.EpisodeName; + var replaceSpaces = _configProvider.ReplaceSpaces; + var appendQuality = _configProvider.AppendQuality; + + var title = String.Empty; + + if (episodes.Count == 1) + { + if (useSeriesName) + { + title += seriesName; + title += separatorStyle.Pattern; + } + + title += numberStyle.Pattern.Replace("%s", String.Format("{0}", episodes[0].SeasonNumber)) + .Replace("%0s", String.Format("{0:00}", episodes[0].SeasonNumber)) + .Replace("%0e", String.Format("{0:00}", episodes[0].EpisodeNumber)); + + if (useEpisodeName) + { + title += separatorStyle.Pattern; + title += episodes[0].Title; + } + + if (appendQuality) + title += String.Format(" [{0}]", quality); + + if (replaceSpaces) + title = title.Replace(' ', '.'); + + Logger.Debug("New File Name is: {0}", title); + return title; + } + + var multiEpisodeStyle = EpisodeSortingHelper.GetMultiEpisodeStyle(_configProvider.MultiEpisodeStyle); + + if (useSeriesName) + { + title += seriesName; + title += separatorStyle.Pattern; + } + + title += numberStyle.Pattern.Replace("%s", String.Format("{0}", episodes[0].SeasonNumber)) + .Replace("%0s", String.Format("{0:00}", episodes[0].SeasonNumber)) + .Replace("%0e", String.Format("{0:00}", episodes[0].EpisodeNumber)); + + var numbers = String.Empty; + var episodeNames = episodes[0].Title; + + for (int i = 1; i < episodes.Count; i++) + { + var episode = episodes[i]; + + if (multiEpisodeStyle.Name == "Duplicate") + { + numbers += separatorStyle.Pattern + numberStyle.Pattern.Replace("%s", String.Format("{0}", episode.SeasonNumber)) + .Replace("%0s", String.Format("{0:00}", episode.SeasonNumber)) + .Replace("%0e", String.Format("{0:00}", episode.EpisodeNumber)); + } + else + { + numbers += multiEpisodeStyle.Pattern.Replace("%s", String.Format("{0}", episode.SeasonNumber)) + .Replace("%0s", String.Format("{0:00}", episode.SeasonNumber)) + .Replace("%0e", String.Format("{0:00}", episode.EpisodeNumber)) + .Replace("%x", numberStyle.EpisodeSeparator) + .Replace("%p", separatorStyle.Pattern); + } + + episodeNames += String.Format(" + {0}", episode.Title); + } + + title += numbers; + + if (useEpisodeName) + { + episodeNames = episodeNames.TrimEnd(' ', '+'); + + title += separatorStyle.Pattern; + title += episodeNames; + } + + if (appendQuality) + title += String.Format(" [{0}]", quality); + + if (replaceSpaces) + title = title.Replace(' ', '.'); + + Logger.Debug("New File Name is: {0}", title); + return title; + } + + public virtual bool RenameEpisodeFile(int episodeFileId, ProgressNotification notification) + { + var episodeFile = GetEpisodeFile(episodeFileId); + + if (episodeFile == null) + return false; + + try + { + notification.CurrentMessage = String.Format("Renaming '{0}'", episodeFile.Path); + + var series = _seriesProvider.GetSeries(episodeFile.SeriesId); + var folder = new FileInfo(episodeFile.Path).DirectoryName; + var episodes = _episodeProvider.EpisodesByFileId(episodeFileId); + var ext = _diskProvider.GetExtension(episodeFile.Path); + + var newFileName = GetNewFilename(episodes, series.Title, episodeFile.Quality); + + var newFile = folder + Path.DirectorySeparatorChar + newFileName + ext; + + //Do the rename + _diskProvider.RenameFile(episodeFile.Path, newFile); + + //Update the filename in the DB + episodeFile.Path = newFile; + Update(episodeFile); + + notification.CurrentMessage = String.Format("Finished Renaming '{0}'", newFile); + } + + catch (Exception e) + { + notification.CurrentMessage = String.Format("Failed to Rename '{0}'", episodeFile.Path); + Logger.ErrorException("An error has occurred while renaming episode: " + episodeFile.Path, e); + throw; + } + return true; + } + } +} diff --git a/NzbDrone.Core/Providers/EpisodeProvider.cs b/NzbDrone.Core/Providers/EpisodeProvider.cs index 10825b9a2..006d1d8e9 100644 --- a/NzbDrone.Core/Providers/EpisodeProvider.cs +++ b/NzbDrone.Core/Providers/EpisodeProvider.cs @@ -91,7 +91,7 @@ namespace NzbDrone.Core.Providers return AttachSeries(episodes.ToList()); } - public virtual IList EpisodesByFileId(int episodeFileId) + public virtual IList GetEpisodesByFileId(int episodeFileId) { return AttachSeries(_database.Fetch("WHERE EpisodeFileId = @0", episodeFileId)); } diff --git a/NzbDrone.Core/Providers/Jobs/RenameEpisodeJob.cs b/NzbDrone.Core/Providers/Jobs/RenameEpisodeJob.cs index e8184750e..e9f4373d9 100644 --- a/NzbDrone.Core/Providers/Jobs/RenameEpisodeJob.cs +++ b/NzbDrone.Core/Providers/Jobs/RenameEpisodeJob.cs @@ -36,7 +36,8 @@ namespace NzbDrone.Core.Providers.Jobs public void Start(ProgressNotification notification, int targetId) { - _mediaFileProvider.RenameEpisodeFile(targetId, notification); + var episode = _mediaFileProvider.GetEpisodeFile(targetId); + _mediaFileProvider.RenameEpisodeFile(episode); } } } \ No newline at end of file diff --git a/NzbDrone.Core/Providers/MediaFileProvider.cs b/NzbDrone.Core/Providers/MediaFileProvider.cs index 8a25655df..34c6d81fe 100644 --- a/NzbDrone.Core/Providers/MediaFileProvider.cs +++ b/NzbDrone.Core/Providers/MediaFileProvider.cs @@ -192,7 +192,7 @@ namespace NzbDrone.Core.Providers public virtual void Update(EpisodeFile episodeFile) { _database.Update(episodeFile); - } + } public virtual EpisodeFile GetEpisodeFile(int episodeFileId) { @@ -315,134 +315,82 @@ namespace NzbDrone.Core.Providers { var separatorStyle = EpisodeSortingHelper.GetSeparatorStyle(_configProvider.SeparatorStyle); var numberStyle = EpisodeSortingHelper.GetNumberStyle(_configProvider.NumberStyle); - var useSeriesName = _configProvider.SeriesName; - var useEpisodeName = _configProvider.EpisodeName; - var replaceSpaces = _configProvider.ReplaceSpaces; - var appendQuality = _configProvider.AppendQuality; - var title = String.Empty; - - if (episodes.Count == 1) - { - if (useSeriesName) - { - title += seriesName; - title += separatorStyle.Pattern; - } - - title += numberStyle.Pattern.Replace("%s", String.Format("{0}", episodes[0].SeasonNumber)) - .Replace("%0s", String.Format("{0:00}", episodes[0].SeasonNumber)) - .Replace("%0e", String.Format("{0:00}", episodes[0].EpisodeNumber)); - - if (useEpisodeName) - { - title += separatorStyle.Pattern; - title += episodes[0].Title; - } - - if (appendQuality) - title += String.Format(" [{0}]", quality); - - if (replaceSpaces) - title = title.Replace(' ', '.'); - - Logger.Debug("New File Name is: {0}", title); - return title; - } - - var multiEpisodeStyle = EpisodeSortingHelper.GetMultiEpisodeStyle(_configProvider.MultiEpisodeStyle); - - if (useSeriesName) - { - title += seriesName; - title += separatorStyle.Pattern; - } - - title += numberStyle.Pattern.Replace("%s", String.Format("{0}", episodes[0].SeasonNumber)) - .Replace("%0s", String.Format("{0:00}", episodes[0].SeasonNumber)) - .Replace("%0e", String.Format("{0:00}", episodes[0].EpisodeNumber)); - - var numbers = String.Empty; var episodeNames = episodes[0].Title; - for (int i = 1; i < episodes.Count; i++) + var result = String.Empty; + + if (_configProvider.SeriesName) { - var episode = episodes[i]; - - if (multiEpisodeStyle.Name == "Duplicate") - { - numbers += separatorStyle.Pattern + numberStyle.Pattern.Replace("%s", String.Format("{0}", episode.SeasonNumber)) - .Replace("%0s", String.Format("{0:00}", episode.SeasonNumber)) - .Replace("%0e", String.Format("{0:00}", episode.EpisodeNumber)); - } - else - { - numbers += multiEpisodeStyle.Pattern.Replace("%s", String.Format("{0}", episode.SeasonNumber)) - .Replace("%0s", String.Format("{0:00}", episode.SeasonNumber)) - .Replace("%0e", String.Format("{0:00}", episode.EpisodeNumber)) - .Replace("%x", numberStyle.EpisodeSeparator) - .Replace("%p", separatorStyle.Pattern); - } - - episodeNames += String.Format(" + {0}", episode.Title); + result += seriesName + separatorStyle.Pattern; } - title += numbers; + result += numberStyle.Pattern.Replace("%0e", String.Format("{0:00}", episodes[0].EpisodeNumber)); - if (useEpisodeName) + if (episodes.Count > 1) + { + var multiEpisodeStyle = EpisodeSortingHelper.GetMultiEpisodeStyle(_configProvider.MultiEpisodeStyle); + + foreach (var episode in episodes.OrderBy(e => e.EpisodeNumber).Skip(1)) + { + if (multiEpisodeStyle.Name == "Duplicate") + { + result += separatorStyle.Pattern + numberStyle.Pattern; + } + else + { + result += multiEpisodeStyle.Pattern; + } + + result = result.Replace("%0e", String.Format("{0:00}", episode.EpisodeNumber)); + episodeNames += String.Format(" + {0}", episode.Title); + } + } + + result = result + .Replace("%s", String.Format("{0}", episodes.First().SeasonNumber)) + .Replace("%0s", String.Format("{0:00}", episodes.First().SeasonNumber)) + .Replace("%x", numberStyle.EpisodeSeparator) + .Replace("%p", separatorStyle.Pattern); + + if (_configProvider.EpisodeName) { episodeNames = episodeNames.TrimEnd(' ', '+'); - - title += separatorStyle.Pattern; - title += episodeNames; + result += separatorStyle.Pattern + episodeNames; } - if (appendQuality) - title += String.Format(" [{0}]", quality); + if (_configProvider.AppendQuality) + result += String.Format(" [{0}]", quality); - if (replaceSpaces) - title = title.Replace(' ', '.'); + if (_configProvider.ReplaceSpaces) + result = result.Replace(' ', '.'); - Logger.Debug("New File Name is: {0}", title); - return title; + Logger.Debug("New File Name is: {0}", result.Trim()); + return result.Trim(); } - public virtual bool RenameEpisodeFile(int episodeFileId, ProgressNotification notification) + public virtual bool RenameEpisodeFile(EpisodeFile episodeFile) { - var episodeFile = GetEpisodeFile(episodeFileId); - if (episodeFile == null) - return false; + throw new ArgumentNullException("episodeFile"); - try - { - notification.CurrentMessage = String.Format("Renaming '{0}'", episodeFile.Path); + var series = _seriesProvider.GetSeries(episodeFile.SeriesId); + var folder = new FileInfo(episodeFile.Path).DirectoryName; + var ext = _diskProvider.GetExtension(episodeFile.Path); + var episodes = _episodeProvider.GetEpisodesByFileId(episodeFile.EpisodeFileId); - var series = _seriesProvider.GetSeries(episodeFile.SeriesId); - var folder = new FileInfo(episodeFile.Path).DirectoryName; - var episodes = _episodeProvider.EpisodesByFileId(episodeFileId); - var ext = _diskProvider.GetExtension(episodeFile.Path); + var newFileName = GetNewFilename(episodes, series.Title, episodeFile.Quality); - var newFileName = GetNewFilename(episodes, series.Title, episodeFile.Quality); + var newFile = folder + Path.DirectorySeparatorChar + newFileName + ext; - var newFile = folder + Path.DirectorySeparatorChar + newFileName + ext; + //Do the rename + _diskProvider.RenameFile(episodeFile.Path, newFile); - //Do the rename - _diskProvider.RenameFile(episodeFile.Path, newFile); + //Update the filename in the DB + episodeFile.Path = newFile; + Update(episodeFile); - //Update the filename in the DB - episodeFile.Path = newFile; - Update(episodeFile); - notification.CurrentMessage = String.Format("Finished Renaming '{0}'", newFile); - } - - catch (Exception e) - { - notification.CurrentMessage = String.Format("Failed to Rename '{0}'", episodeFile.Path); - Logger.ErrorException("An error has occurred while renaming episode: " + episodeFile.Path, e); - throw; - } return true; } }