At a point where we can build. Many TODOs and existing Series-based APIs need to be removed. No track code actually works.

pull/7/head
Joseph Milazzo 8 years ago
parent 235e753b93
commit 1024555f75

@ -11,6 +11,7 @@ using NzbDrone.Core.Messaging.Events;
using NzbDrone.Core.Tv;
using NzbDrone.Core.DecisionEngine;
using NzbDrone.SignalR;
using System;
namespace NzbDrone.Api.EpisodeFiles
{
@ -47,24 +48,26 @@ namespace NzbDrone.Api.EpisodeFiles
private EpisodeFileResource GetEpisodeFile(int id)
{
var episodeFile = _mediaFileService.Get(id);
var series = _seriesService.GetSeries(episodeFile.SeriesId);
throw new NotImplementedException();
//var episodeFile = _mediaFileService.Get(id);
//var series = _seriesService.GetSeries(episodeFile.SeriesId);
return episodeFile.ToResource(series, _qualityUpgradableSpecification);
//return episodeFile.ToResource(series, _qualityUpgradableSpecification);
}
private List<EpisodeFileResource> GetEpisodeFiles()
{
if (!Request.Query.SeriesId.HasValue)
{
throw new BadRequestException("seriesId is missing");
}
throw new NotImplementedException();
//if (!Request.Query.SeriesId.HasValue)
//{
// throw new BadRequestException("seriesId is missing");
//}
var seriesId = (int)Request.Query.SeriesId;
//var seriesId = (int)Request.Query.SeriesId;
var series = _seriesService.GetSeries(seriesId);
//var series = _seriesService.GetSeries(seriesId);
return _mediaFileService.GetFilesBySeries(seriesId).ConvertAll(f => f.ToResource(series, _qualityUpgradableSpecification));
//return _mediaFileService.GetFilesBySeries(seriesId).ConvertAll(f => f.ToResource(series, _qualityUpgradableSpecification));
}
private void SetQuality(EpisodeFileResource episodeFileResource)
@ -76,14 +79,15 @@ namespace NzbDrone.Api.EpisodeFiles
private void DeleteEpisodeFile(int id)
{
var episodeFile = _mediaFileService.Get(id);
var series = _seriesService.GetSeries(episodeFile.SeriesId);
var fullPath = Path.Combine(series.Path, episodeFile.RelativePath);
var subfolder = _diskProvider.GetParentFolder(series.Path).GetRelativePath(_diskProvider.GetParentFolder(fullPath));
throw new NotImplementedException();
//var episodeFile = _mediaFileService.Get(id);
//var series = _seriesService.GetSeries(episodeFile.SeriesId);
//var fullPath = Path.Combine(series.Path, episodeFile.RelativePath);
//var subfolder = _diskProvider.GetParentFolder(series.Path).GetRelativePath(_diskProvider.GetParentFolder(fullPath));
_logger.Info("Deleting episode file: {0}", fullPath);
_recycleBinProvider.DeleteFile(fullPath, subfolder);
_mediaFileService.Delete(episodeFile, DeleteMediaFileReason.Manual);
//_logger.Info("Deleting episode file: {0}", fullPath);
//_recycleBinProvider.DeleteFile(fullPath, subfolder);
//_mediaFileService.Delete(episodeFile, DeleteMediaFileReason.Manual);
}
public void Handle(EpisodeFileAddedEvent message)

@ -144,14 +144,14 @@ namespace NzbDrone.Api.Music
public void Handle(TrackImportedEvent message)
{
BroadcastResourceChange(ModelAction.Updated, message.ImportedTrack.ItunesTrackId);
BroadcastResourceChange(ModelAction.Updated, message.ImportedTrack.Id); // TODO: Ensure we can pass DB ID instead of Metadata ID (SpotifyID)
}
public void Handle(TrackFileDeletedEvent message)
{
if (message.Reason == DeleteMediaFileReason.Upgrade) return;
BroadcastResourceChange(ModelAction.Updated, message.TrackFile.ItunesTrackId);
BroadcastResourceChange(ModelAction.Updated, message.TrackFile.Id); // TODO: Ensure we can pass DB ID instead of Metadata ID (SpotifyID)
}
public void Handle(ArtistUpdatedEvent message)

@ -13,6 +13,7 @@ using NzbDrone.Core.MediaFiles.EpisodeImport;
using NzbDrone.Core.Messaging.Events;
using NzbDrone.Core.Parser;
using NzbDrone.Core.Tv;
using NzbDrone.Core.MediaFiles.TrackImport;
namespace NzbDrone.Core.Download
{
@ -130,7 +131,7 @@ namespace NzbDrone.Core.Download
{
var statusMessages = importResults
.Where(v => v.Result != ImportResultType.Imported)
.Select(v => new TrackedDownloadStatusMessage(Path.GetFileName(v.ImportDecision.LocalEpisode.Path), v.Errors))
.Select(v => new TrackedDownloadStatusMessage(Path.GetFileName(v.ImportDecision.LocalTrack.Path), v.Errors))
.ToArray();
trackedDownload.Warn(statusMessages);

@ -16,6 +16,7 @@ using NzbDrone.Core.Tv;
namespace NzbDrone.Core.Extras
{
// NOTE: Majora: ExtraService can be reserved for Music Videos, lyric files, etc for Plex. TODO: Implement Extras for Music
public interface IExtraService
{
void ImportExtraFiles(LocalEpisode localEpisode, EpisodeFile episodeFile, bool isReadOnly);
@ -136,16 +137,17 @@ namespace NzbDrone.Core.Extras
private List<EpisodeFile> GetEpisodeFiles(int seriesId)
{
var episodeFiles = _mediaFileService.GetFilesBySeries(seriesId);
var episodes = _episodeService.GetEpisodeBySeries(seriesId);
//var episodeFiles = _mediaFileService.GetFilesBySeries(seriesId);
//var episodes = _episodeService.GetEpisodeBySeries(seriesId);
foreach (var episodeFile in episodeFiles)
{
var localEpisodeFile = episodeFile;
episodeFile.Episodes = new LazyList<Episode>(episodes.Where(e => e.EpisodeFileId == localEpisodeFile.Id));
}
//foreach (var episodeFile in episodeFiles)
//{
// var localEpisodeFile = episodeFile;
// episodeFile.Episodes = new LazyList<Episode>(episodes.Where(e => e.EpisodeFileId == localEpisodeFile.Id));
//}
return episodeFiles;
//return episodeFiles;
return new List<EpisodeFile>();
}
}
}

@ -1,4 +1,5 @@
using NzbDrone.Core.MediaFiles.EpisodeImport;
using NzbDrone.Core.MediaFiles.TrackImport;
using NzbDrone.Core.Messaging.Commands;
namespace NzbDrone.Core.MediaFiles.Commands

@ -18,12 +18,12 @@ using NzbDrone.Core.Tv;
using NzbDrone.Core.Tv.Events;
using NzbDrone.Core.Music;
using NzbDrone.Core.Music.Events;
using NzbDrone.Core.MediaFiles.TrackImport;
namespace NzbDrone.Core.MediaFiles
{
public interface IDiskScanService
{
void Scan(Series series);
void Scan(Artist artist);
string[] GetVideoFiles(string path, bool allDirectories = true);
string[] GetNonVideoFiles(string path, bool allDirectories = true);
@ -32,14 +32,12 @@ namespace NzbDrone.Core.MediaFiles
public class DiskScanService :
IDiskScanService,
IHandle<SeriesUpdatedEvent>,
IExecute<RescanSeriesCommand>,
IHandle<ArtistUpdatedEvent>,
IExecute<RescanArtistCommand>
{
private readonly IDiskProvider _diskProvider;
private readonly IMakeImportDecision _importDecisionMaker;
private readonly IImportApprovedEpisodes _importApprovedEpisodes;
private readonly IImportApprovedTracks _importApprovedTracks;
private readonly IConfigService _configService;
private readonly ISeriesService _seriesService;
private readonly IArtistService _artistService;
@ -49,7 +47,7 @@ namespace NzbDrone.Core.MediaFiles
public DiskScanService(IDiskProvider diskProvider,
IMakeImportDecision importDecisionMaker,
IImportApprovedEpisodes importApprovedEpisodes,
IImportApprovedTracks importApprovedTracks,
IConfigService configService,
ISeriesService seriesService,
IArtistService artistService,
@ -59,7 +57,7 @@ namespace NzbDrone.Core.MediaFiles
{
_diskProvider = diskProvider;
_importDecisionMaker = importDecisionMaker;
_importApprovedEpisodes = importApprovedEpisodes;
_importApprovedTracks = importApprovedTracks;
_configService = configService;
_seriesService = seriesService;
_artistService = artistService;
@ -123,76 +121,18 @@ namespace NzbDrone.Core.MediaFiles
CompletedScanning(artist);
}
public void Scan(Series series)
{
var rootFolder = _diskProvider.GetParentFolder(series.Path);
if (!_diskProvider.FolderExists(rootFolder))
{
_logger.Warn("Series' root folder ({0}) doesn't exist.", rootFolder);
_eventAggregator.PublishEvent(new SeriesScanSkippedEvent(series, SeriesScanSkippedReason.RootFolderDoesNotExist));
return;
}
if (_diskProvider.GetDirectories(rootFolder).Empty())
{
_logger.Warn("Series' root folder ({0}) is empty.", rootFolder);
_eventAggregator.PublishEvent(new SeriesScanSkippedEvent(series, SeriesScanSkippedReason.RootFolderIsEmpty));
return;
}
_logger.ProgressInfo("Scanning disk for {0}", series.Title);
if (!_diskProvider.FolderExists(series.Path))
{
if (_configService.CreateEmptySeriesFolders)
{
_logger.Debug("Creating missing series folder: {0}", series.Path);
_diskProvider.CreateFolder(series.Path);
SetPermissions(series.Path);
}
else
{
_logger.Debug("Series folder doesn't exist: {0}", series.Path);
}
CleanMediaFiles(series, new List<string>());
CompletedScanning(series);
return;
}
var videoFilesStopwatch = Stopwatch.StartNew();
var mediaFileList = FilterFiles(series, GetVideoFiles(series.Path)).ToList();
videoFilesStopwatch.Stop();
_logger.Trace("Finished getting episode files for: {0} [{1}]", series, videoFilesStopwatch.Elapsed);
CleanMediaFiles(series, mediaFileList);
var decisionsStopwatch = Stopwatch.StartNew();
var decisions = _importDecisionMaker.GetImportDecisions(mediaFileList, series);
decisionsStopwatch.Stop();
_logger.Trace("Import decisions complete for: {0} [{1}]", series, decisionsStopwatch.Elapsed);
_importApprovedEpisodes.Import(decisions, false);
CompletedScanning(series);
}
private void CleanMediaFiles(Series series, List<string> mediaFileList)
{
_logger.Debug("{0} Cleaning up media files in DB", series);
_mediaFileTableCleanupService.Clean(series, mediaFileList);
}
private void CleanMediaFiles(Artist artist, List<string> mediaFileList)
{
_logger.Debug("{0} Cleaning up media files in DB", artist);
_mediaFileTableCleanupService.Clean(artist, mediaFileList);
}
private void CompletedScanning(Series series)
{
_logger.Info("Completed scanning disk for {0}", series.Title);
_eventAggregator.PublishEvent(new SeriesScannedEvent(series));
}
//private void CompletedScanning(Series series)
//{
// _logger.Info("Completed scanning disk for {0}", series.Title);
// _eventAggregator.PublishEvent(new SeriesScannedEvent(series));
//}
private void CompletedScanning(Artist artist)
{
@ -280,35 +220,11 @@ namespace NzbDrone.Core.MediaFiles
}
}
public void Handle(SeriesUpdatedEvent message)
{
Scan(message.Series);
}
public void Handle(ArtistUpdatedEvent message)
{
Scan(message.Artist);
}
public void Execute(RescanSeriesCommand message)
{
if (message.SeriesId.HasValue)
{
var series = _seriesService.GetSeries(message.SeriesId.Value);
Scan(series);
}
else
{
var allSeries = _seriesService.GetAllSeries();
foreach (var series in allSeries)
{
Scan(series);
}
}
}
public void Execute(RescanArtistCommand message)
{
if (message.ArtistId.IsNotNullOrWhiteSpace())

@ -9,6 +9,7 @@ using NzbDrone.Core.Download.TrackedDownloads;
using NzbDrone.Core.MediaFiles.Commands;
using NzbDrone.Core.MediaFiles.EpisodeImport;
using NzbDrone.Core.Messaging.Commands;
using NzbDrone.Core.MediaFiles.TrackImport;
namespace NzbDrone.Core.MediaFiles
{

@ -9,6 +9,7 @@ using NzbDrone.Core.Parser;
using NzbDrone.Core.Tv;
using NzbDrone.Core.Download;
using NzbDrone.Core.Parser.Model;
using NzbDrone.Core.MediaFiles.TrackImport;
namespace NzbDrone.Core.MediaFiles
{
@ -59,7 +60,7 @@ namespace NzbDrone.Core.MediaFiles
results.AddRange(folderResults);
}
foreach (var videoFile in _diskScanService.GetVideoFiles(directoryInfo.FullName, false))
foreach (var videoFile in _diskScanService.GetNonVideoFiles(directoryInfo.FullName, false))
{
var fileResults = ProcessFile(new FileInfo(videoFile), ImportMode.Auto, null);
results.AddRange(fileResults);
@ -100,7 +101,7 @@ namespace NzbDrone.Core.MediaFiles
public bool ShouldDeleteFolder(DirectoryInfo directoryInfo, Series series)
{
var videoFiles = _diskScanService.GetVideoFiles(directoryInfo.FullName);
var videoFiles = _diskScanService.GetNonVideoFiles(directoryInfo.FullName);
var rarFiles = _diskProvider.GetFiles(directoryInfo.FullName, SearchOption.AllDirectories).Where(f => Path.GetExtension(f) == ".rar");
foreach (var videoFile in videoFiles)
@ -152,48 +153,50 @@ namespace NzbDrone.Core.MediaFiles
private List<ImportResult> ProcessFolder(DirectoryInfo directoryInfo, ImportMode importMode, Series series, DownloadClientItem downloadClientItem)
{
if (_seriesService.SeriesPathExists(directoryInfo.FullName))
{
_logger.Warn("Unable to process folder that is mapped to an existing show");
return new List<ImportResult>();
}
var cleanedUpName = GetCleanedUpFolderName(directoryInfo.Name);
var folderInfo = Parser.Parser.ParseTitle(directoryInfo.Name);
if (folderInfo != null)
{
_logger.Debug("{0} folder quality: {1}", cleanedUpName, folderInfo.Quality);
}
var videoFiles = _diskScanService.GetVideoFiles(directoryInfo.FullName);
if (downloadClientItem == null)
{
foreach (var videoFile in videoFiles)
{
if (_diskProvider.IsFileLocked(videoFile))
{
return new List<ImportResult>
{
FileIsLockedResult(videoFile)
};
}
}
}
var decisions = _importDecisionMaker.GetImportDecisions(videoFiles.ToList(), series, folderInfo, true);
var importResults = _importApprovedEpisodes.Import(decisions, true, downloadClientItem, importMode);
if ((downloadClientItem == null || !downloadClientItem.IsReadOnly) &&
importResults.Any(i => i.Result == ImportResultType.Imported) &&
ShouldDeleteFolder(directoryInfo, series))
{
_logger.Debug("Deleting folder after importing valid files");
_diskProvider.DeleteFolder(directoryInfo.FullName, true);
}
return importResults;
throw new System.NotImplementedException("Will be removed");
//if (_seriesService.SeriesPathExists(directoryInfo.FullName))
//{
// _logger.Warn("Unable to process folder that is mapped to an existing show");
// return new List<ImportResult>();
//}
//var cleanedUpName = GetCleanedUpFolderName(directoryInfo.Name);
//var folderInfo = Parser.Parser.ParseTitle(directoryInfo.Name);
//if (folderInfo != null)
//{
// _logger.Debug("{0} folder quality: {1}", cleanedUpName, folderInfo.Quality);
//}
//var videoFiles = _diskScanService.GetVideoFiles(directoryInfo.FullName);
//if (downloadClientItem == null)
//{
// foreach (var videoFile in videoFiles)
// {
// if (_diskProvider.IsFileLocked(videoFile))
// {
// return new List<ImportResult>
// {
// FileIsLockedResult(videoFile)
// };
// }
// }
//}
//var decisions = _importDecisionMaker.GetImportDecisions(videoFiles.ToList(), series, folderInfo, true);
//var importResults = _importApprovedEpisodes.Import(decisions, true, downloadClientItem, importMode);
//if ((downloadClientItem == null || !downloadClientItem.IsReadOnly) &&
// importResults.Any(i => i.Result == ImportResultType.Imported) &&
// ShouldDeleteFolder(directoryInfo, series))
//{
// _logger.Debug("Deleting folder after importing valid files");
// _diskProvider.DeleteFolder(directoryInfo.FullName, true);
//}
//return importResults;
}
private List<ImportResult> ProcessFile(FileInfo fileInfo, ImportMode importMode, DownloadClientItem downloadClientItem)
@ -215,30 +218,31 @@ namespace NzbDrone.Core.MediaFiles
private List<ImportResult> ProcessFile(FileInfo fileInfo, ImportMode importMode, Series series, DownloadClientItem downloadClientItem)
{
if (Path.GetFileNameWithoutExtension(fileInfo.Name).StartsWith("._"))
{
_logger.Debug("[{0}] starts with '._', skipping", fileInfo.FullName);
return new List<ImportResult>
{
new ImportResult(new ImportDecision(new LocalEpisode { Path = fileInfo.FullName }, new Rejection("Invalid video file, filename starts with '._'")), "Invalid video file, filename starts with '._'")
};
}
if (downloadClientItem == null)
{
if (_diskProvider.IsFileLocked(fileInfo.FullName))
{
return new List<ImportResult>
{
FileIsLockedResult(fileInfo.FullName)
};
}
}
var decisions = _importDecisionMaker.GetImportDecisions(new List<string>() { fileInfo.FullName }, series, null, true);
return _importApprovedEpisodes.Import(decisions, true, downloadClientItem, importMode);
throw new System.NotImplementedException("Will be removed");
//if (Path.GetFileNameWithoutExtension(fileInfo.Name).StartsWith("._"))
//{
// _logger.Debug("[{0}] starts with '._', skipping", fileInfo.FullName);
// return new List<ImportResult>
// {
// new ImportResult(new ImportDecision(new LocalTrack { Path = fileInfo.FullName }, new Rejection("Invalid music file, filename starts with '._'")), "Invalid music file, filename starts with '._'")
// };
//}
//if (downloadClientItem == null)
//{
// if (_diskProvider.IsFileLocked(fileInfo.FullName))
// {
// return new List<ImportResult>
// {
// FileIsLockedResult(fileInfo.FullName)
// };
// }
//}
//var decisions = _importDecisionMaker.GetImportDecisions(new List<string>() { fileInfo.FullName }, series, null, true);
//return _importApprovedEpisodes.Import(decisions, true, downloadClientItem, importMode);
}
private string GetCleanedUpFolderName(string folder)
@ -251,15 +255,17 @@ namespace NzbDrone.Core.MediaFiles
private ImportResult FileIsLockedResult(string videoFile)
{
_logger.Debug("[{0}] is currently locked by another process, skipping", videoFile);
return new ImportResult(new ImportDecision(new LocalEpisode { Path = videoFile }, new Rejection("Locked file, try again later")), "Locked file, try again later");
throw new System.NotImplementedException("Will be removed");
//_logger.Debug("[{0}] is currently locked by another process, skipping", videoFile);
//return new ImportResult(new ImportDecision(new LocalEpisode { Path = videoFile }, new Rejection("Locked file, try again later")), "Locked file, try again later");
}
private ImportResult UnknownSeriesResult(string message, string videoFile = null)
{
var localEpisode = videoFile == null ? null : new LocalEpisode { Path = videoFile };
throw new System.NotImplementedException("Will be removed");
//var localEpisode = videoFile == null ? null : new LocalEpisode { Path = videoFile };
return new ImportResult(new ImportDecision(localEpisode, new Rejection("Unknown Series")), message);
//return new ImportResult(new ImportDecision(localEpisode, new Rejection("Unknown Series")), message);
}
}
}

@ -6,5 +6,6 @@ namespace NzbDrone.Core.MediaFiles.EpisodeImport
public interface IImportDecisionEngineSpecification
{
Decision IsSatisfiedBy(LocalEpisode localEpisode);
Decision IsSatisfiedBy(LocalTrack localTrack);
}
}

@ -12,7 +12,7 @@ using NzbDrone.Core.Parser.Model;
using NzbDrone.Core.Qualities;
using NzbDrone.Core.Download;
using NzbDrone.Core.Extras;
using NzbDrone.Core.MediaFiles.TrackImport;
namespace NzbDrone.Core.MediaFiles.EpisodeImport
{
@ -47,105 +47,7 @@ namespace NzbDrone.Core.MediaFiles.EpisodeImport
public List<ImportResult> Import(List<ImportDecision> decisions, bool newDownload, DownloadClientItem downloadClientItem = null, ImportMode importMode = ImportMode.Auto)
{
var qualifiedImports = decisions.Where(c => c.Approved)
.GroupBy(c => c.LocalEpisode.Series.Id, (i, s) => s
.OrderByDescending(c => c.LocalEpisode.Quality, new QualityModelComparer(s.First().LocalEpisode.Series.Profile))
.ThenByDescending(c => c.LocalEpisode.Size))
.SelectMany(c => c)
.ToList();
var importResults = new List<ImportResult>();
foreach (var importDecision in qualifiedImports.OrderBy(e => e.LocalEpisode.Episodes.Select(episode => episode.EpisodeNumber).MinOrDefault())
.ThenByDescending(e => e.LocalEpisode.Size))
{
var localEpisode = importDecision.LocalEpisode;
var oldFiles = new List<EpisodeFile>();
try
{
//check if already imported
if (importResults.SelectMany(r => r.ImportDecision.LocalEpisode.Episodes)
.Select(e => e.Id)
.Intersect(localEpisode.Episodes.Select(e => e.Id))
.Any())
{
importResults.Add(new ImportResult(importDecision, "Episode has already been imported"));
continue;
}
var episodeFile = new EpisodeFile();
episodeFile.DateAdded = DateTime.UtcNow;
episodeFile.SeriesId = localEpisode.Series.Id;
episodeFile.Path = localEpisode.Path.CleanFilePath();
episodeFile.Size = _diskProvider.GetFileSize(localEpisode.Path);
episodeFile.Quality = localEpisode.Quality;
episodeFile.MediaInfo = localEpisode.MediaInfo;
episodeFile.SeasonNumber = localEpisode.SeasonNumber;
episodeFile.Episodes = localEpisode.Episodes;
episodeFile.ReleaseGroup = localEpisode.ParsedEpisodeInfo.ReleaseGroup;
bool copyOnly;
switch (importMode)
{
default:
case ImportMode.Auto:
copyOnly = downloadClientItem != null && downloadClientItem.IsReadOnly;
break;
case ImportMode.Move:
copyOnly = false;
break;
case ImportMode.Copy:
copyOnly = true;
break;
}
if (newDownload)
{
episodeFile.SceneName = GetSceneName(downloadClientItem, localEpisode);
var moveResult = _episodeFileUpgrader.UpgradeEpisodeFile(episodeFile, localEpisode, copyOnly);
oldFiles = moveResult.OldFiles;
}
else
{
episodeFile.RelativePath = localEpisode.Series.Path.GetRelativePath(episodeFile.Path);
}
_mediaFileService.Add(episodeFile);
importResults.Add(new ImportResult(importDecision));
if (newDownload)
{
_extraService.ImportExtraFiles(localEpisode, episodeFile, copyOnly);
}
if (downloadClientItem != null)
{
_eventAggregator.PublishEvent(new EpisodeImportedEvent(localEpisode, episodeFile, newDownload, downloadClientItem.DownloadClient, downloadClientItem.DownloadId, downloadClientItem.IsReadOnly));
}
else
{
_eventAggregator.PublishEvent(new EpisodeImportedEvent(localEpisode, episodeFile, newDownload));
}
if (newDownload)
{
_eventAggregator.PublishEvent(new EpisodeDownloadedEvent(localEpisode, episodeFile, oldFiles));
}
}
catch (Exception e)
{
_logger.Warn(e, "Couldn't import episode " + localEpisode);
importResults.Add(new ImportResult(importDecision, "Failed to import episode"));
}
}
//Adding all the rejected decisions
importResults.AddRange(decisions.Where(c => !c.Approved)
.Select(d => new ImportResult(d, d.Rejections.Select(r => r.Reason).ToArray())));
return importResults;
throw new NotImplementedException("This will be removed");
}
private string GetSceneName(DownloadClientItem downloadClientItem, LocalEpisode localEpisode)

@ -1,22 +0,0 @@
using System.Collections.Generic;
using System.Linq;
using NzbDrone.Common.Extensions;
using NzbDrone.Core.DecisionEngine;
using NzbDrone.Core.Parser.Model;
namespace NzbDrone.Core.MediaFiles.EpisodeImport
{
public class ImportDecision
{
public LocalEpisode LocalEpisode { get; private set; }
public IEnumerable<Rejection> Rejections { get; private set; }
public bool Approved => Rejections.Empty();
public ImportDecision(LocalEpisode localEpisode, params Rejection[] rejections)
{
LocalEpisode = localEpisode;
Rejections = rejections.ToList();
}
}
}

@ -12,6 +12,7 @@ using NzbDrone.Core.Qualities;
using NzbDrone.Core.Tv;
using NzbDrone.Core.MediaFiles.MediaInfo;
using NzbDrone.Core.Music;
using NzbDrone.Core.MediaFiles.TrackImport;
namespace NzbDrone.Core.MediaFiles.EpisodeImport
{
@ -94,7 +95,7 @@ namespace NzbDrone.Core.MediaFiles.EpisodeImport
return decisions;
}
private ImportDecision GetDecision(string file, Artist artist, ParsedTrackInfo folderInfo, bool sceneSource, bool shouldUseFolderName)
private ImportDecision GetDecision(string file, Artist artist, ParsedTrackInfo folderInfo, bool shouldUseFolderName)
{
ImportDecision decision = null;
@ -109,11 +110,7 @@ namespace NzbDrone.Core.MediaFiles.EpisodeImport
_logger.Debug("Size: {0}", localTrack.Size);
//TODO: make it so media info doesn't ruin the import process of a new series
if (sceneSource)
{
localTrack.MediaInfo = _videoFileInfoReader.GetMediaInfo(file);
}
//TODO: make it so media info doesn't ruin the import process of a new artist
if (localTrack.Tracks.Empty())
{
@ -137,19 +134,41 @@ namespace NzbDrone.Core.MediaFiles.EpisodeImport
{
_logger.Error(e, "Couldn't import file. {0}", file);
var localEpisode = new LocalEpisode { Path = file };
decision = new ImportDecision(localEpisode, new Rejection("Unexpected error processing file"));
var localTrack = new LocalTrack { Path = file };
decision = new ImportDecision(localTrack, new Rejection("Unexpected error processing file"));
}
return decision;
}
private ImportDecision GetDecision(LocalEpisode localEpisode)
private ImportDecision GetDecision(LocalTrack localTrack)
{
var reasons = _specifications.Select(c => EvaluateSpec(c, localEpisode))
var reasons = _specifications.Select(c => EvaluateSpec(c, localTrack))
.Where(c => c != null);
return new ImportDecision(localEpisode, reasons.ToArray());
return new ImportDecision(localTrack, reasons.ToArray());
}
private Rejection EvaluateSpec(IImportDecisionEngineSpecification spec, LocalTrack localTrack)
{
try
{
var result = spec.IsSatisfiedBy(localTrack);
if (!result.Accepted)
{
return new Rejection(result.Reason);
}
}
catch (Exception e)
{
//e.Data.Add("report", remoteEpisode.Report.ToJson());
//e.Data.Add("parsed", remoteEpisode.ParsedEpisodeInfo.ToJson());
_logger.Error(e, "Couldn't evaluate decision on {0}", localTrack.Path);
return new Rejection($"{spec.GetType().Name}: {e.Message}");
}
return null;
}
private Rejection EvaluateSpec(IImportDecisionEngineSpecification spec, LocalEpisode localEpisode)

@ -1,5 +1,6 @@
using System.Collections.Generic;
using NzbDrone.Core.Messaging.Commands;
using NzbDrone.Core.MediaFiles.TrackImport;
namespace NzbDrone.Core.MediaFiles.EpisodeImport.Manual
{

@ -15,6 +15,7 @@ using NzbDrone.Core.Messaging.Events;
using NzbDrone.Core.Parser;
using NzbDrone.Core.Parser.Model;
using NzbDrone.Core.Tv;
using NzbDrone.Core.MediaFiles.TrackImport;
namespace NzbDrone.Core.MediaFiles.EpisodeImport.Manual
{
@ -94,65 +95,67 @@ namespace NzbDrone.Core.MediaFiles.EpisodeImport.Manual
private List<ManualImportItem> ProcessFolder(string folder, string downloadId)
{
var directoryInfo = new DirectoryInfo(folder);
var series = _parsingService.GetSeries(directoryInfo.Name);
throw new System.NotImplementedException("TODO: This will be rewritten for Music");
//var directoryInfo = new DirectoryInfo(folder);
//var series = _parsingService.GetSeries(directoryInfo.Name);
if (series == null && downloadId.IsNotNullOrWhiteSpace())
{
var trackedDownload = _trackedDownloadService.Find(downloadId);
series = trackedDownload.RemoteEpisode.Series;
}
//if (series == null && downloadId.IsNotNullOrWhiteSpace())
//{
// var trackedDownload = _trackedDownloadService.Find(downloadId);
// series = trackedDownload.RemoteEpisode.Series;
//}
if (series == null)
{
var files = _diskScanService.GetVideoFiles(folder);
//if (series == null)
//{
// var files = _diskScanService.GetVideoFiles(folder);
return files.Select(file => ProcessFile(file, downloadId, folder)).Where(i => i != null).ToList();
}
// return files.Select(file => ProcessFile(file, downloadId, folder)).Where(i => i != null).ToList();
//}
var folderInfo = Parser.Parser.ParseTitle(directoryInfo.Name);
var seriesFiles = _diskScanService.GetVideoFiles(folder).ToList();
var decisions = _importDecisionMaker.GetImportDecisions(seriesFiles, series, folderInfo, SceneSource(series, folder));
//var folderInfo = Parser.Parser.ParseTitle(directoryInfo.Name);
//var seriesFiles = _diskScanService.GetVideoFiles(folder).ToList();
//var decisions = _importDecisionMaker.GetImportDecisions(seriesFiles, series, folderInfo, SceneSource(series, folder));
return decisions.Select(decision => MapItem(decision, folder, downloadId)).ToList();
//return decisions.Select(decision => MapItem(decision, folder, downloadId)).ToList();
}
private ManualImportItem ProcessFile(string file, string downloadId, string folder = null)
{
if (folder.IsNullOrWhiteSpace())
{
folder = new FileInfo(file).Directory.FullName;
}
throw new System.NotImplementedException("TODO: This will be rewritten for Music");
//if (folder.IsNullOrWhiteSpace())
//{
// folder = new FileInfo(file).Directory.FullName;
//}
var relativeFile = folder.GetRelativePath(file);
//var relativeFile = folder.GetRelativePath(file);
var series = _parsingService.GetSeries(relativeFile.Split('\\', '/')[0]);
//var series = _parsingService.GetSeries(relativeFile.Split('\\', '/')[0]);
if (series == null)
{
series = _parsingService.GetSeries(relativeFile);
}
//if (series == null)
//{
// series = _parsingService.GetSeries(relativeFile);
//}
if (series == null && downloadId.IsNotNullOrWhiteSpace())
{
var trackedDownload = _trackedDownloadService.Find(downloadId);
series = trackedDownload.RemoteEpisode.Series;
}
//if (series == null && downloadId.IsNotNullOrWhiteSpace())
//{
// var trackedDownload = _trackedDownloadService.Find(downloadId);
// series = trackedDownload.RemoteEpisode.Series;
//}
if (series == null)
{
var localEpisode = new LocalEpisode();
localEpisode.Path = file;
localEpisode.Quality = QualityParser.ParseQuality(file);
localEpisode.Size = _diskProvider.GetFileSize(file);
//if (series == null)
//{
// var localEpisode = new LocalEpisode();
// localEpisode.Path = file;
// localEpisode.Quality = QualityParser.ParseQuality(file);
// localEpisode.Size = _diskProvider.GetFileSize(file);
return MapItem(new ImportDecision(localEpisode, new Rejection("Unknown Series")), folder, downloadId);
}
// return MapItem(new ImportDecision(localEpisode, new Rejection("Unknown Series")), folder, downloadId);
//}
var importDecisions = _importDecisionMaker.GetImportDecisions(new List<string> {file},
series, null, SceneSource(series, folder));
//var importDecisions = _importDecisionMaker.GetImportDecisions(new List<string> {file},
// series, null, SceneSource(series, folder));
return importDecisions.Any() ? MapItem(importDecisions.First(), folder, downloadId) : null;
//return importDecisions.Any() ? MapItem(importDecisions.First(), folder, downloadId) : null;
}
private bool SceneSource(Series series, string folder)
@ -162,107 +165,109 @@ namespace NzbDrone.Core.MediaFiles.EpisodeImport.Manual
private ManualImportItem MapItem(ImportDecision decision, string folder, string downloadId)
{
var item = new ManualImportItem();
item.Path = decision.LocalEpisode.Path;
item.RelativePath = folder.GetRelativePath(decision.LocalEpisode.Path);
item.Name = Path.GetFileNameWithoutExtension(decision.LocalEpisode.Path);
item.DownloadId = downloadId;
if (decision.LocalEpisode.Series != null)
{
item.Series = decision.LocalEpisode.Series;
}
if (decision.LocalEpisode.Episodes.Any())
{
item.SeasonNumber = decision.LocalEpisode.SeasonNumber;
item.Episodes = decision.LocalEpisode.Episodes;
}
item.Quality = decision.LocalEpisode.Quality;
item.Size = _diskProvider.GetFileSize(decision.LocalEpisode.Path);
item.Rejections = decision.Rejections;
return item;
throw new System.NotImplementedException("TODO: This will be rewritten for Music");
//var item = new ManualImportItem();
//item.Path = decision.LocalEpisode.Path;
//item.RelativePath = folder.GetRelativePath(decision.LocalEpisode.Path);
//item.Name = Path.GetFileNameWithoutExtension(decision.LocalEpisode.Path);
//item.DownloadId = downloadId;
//if (decision.LocalEpisode.Series != null)
//{
// item.Series = decision.LocalEpisode.Series;
//}
//if (decision.LocalEpisode.Episodes.Any())
//{
// item.SeasonNumber = decision.LocalEpisode.SeasonNumber;
// item.Episodes = decision.LocalEpisode.Episodes;
//}
//item.Quality = decision.LocalEpisode.Quality;
//item.Size = _diskProvider.GetFileSize(decision.LocalEpisode.Path);
//item.Rejections = decision.Rejections;
//return item;
}
public void Execute(ManualImportCommand message)
{
_logger.ProgressTrace("Manually importing {0} files using mode {1}", message.Files.Count, message.ImportMode);
var imported = new List<ImportResult>();
var importedTrackedDownload = new List<ManuallyImportedFile>();
for (int i = 0; i < message.Files.Count; i++)
{
_logger.ProgressTrace("Processing file {0} of {1}", i + 1, message.Files.Count);
var file = message.Files[i];
var series = _seriesService.GetSeries(file.SeriesId);
var episodes = _episodeService.GetEpisodes(file.EpisodeIds);
var parsedEpisodeInfo = Parser.Parser.ParsePath(file.Path) ?? new ParsedEpisodeInfo();
var mediaInfo = _videoFileInfoReader.GetMediaInfo(file.Path);
var existingFile = series.Path.IsParentPath(file.Path);
var localEpisode = new LocalEpisode
{
ExistingFile = false,
Episodes = episodes,
MediaInfo = mediaInfo,
ParsedEpisodeInfo = parsedEpisodeInfo,
Path = file.Path,
Quality = file.Quality,
Series = series,
Size = 0
};
//TODO: Cleanup non-tracked downloads
var importDecision = new ImportDecision(localEpisode);
if (file.DownloadId.IsNullOrWhiteSpace())
{
imported.AddRange(_importApprovedEpisodes.Import(new List<ImportDecision> { importDecision }, !existingFile, null, message.ImportMode));
}
else
{
var trackedDownload = _trackedDownloadService.Find(file.DownloadId);
var importResult = _importApprovedEpisodes.Import(new List<ImportDecision> { importDecision }, true, trackedDownload.DownloadItem, message.ImportMode).First();
imported.Add(importResult);
importedTrackedDownload.Add(new ManuallyImportedFile
{
TrackedDownload = trackedDownload,
ImportResult = importResult
});
}
}
_logger.ProgressTrace("Manually imported {0} files", imported.Count);
foreach (var groupedTrackedDownload in importedTrackedDownload.GroupBy(i => i.TrackedDownload.DownloadItem.DownloadId).ToList())
{
var trackedDownload = groupedTrackedDownload.First().TrackedDownload;
if (_diskProvider.FolderExists(trackedDownload.DownloadItem.OutputPath.FullPath))
{
if (_downloadedEpisodesImportService.ShouldDeleteFolder(
new DirectoryInfo(trackedDownload.DownloadItem.OutputPath.FullPath),
trackedDownload.RemoteEpisode.Series) && !trackedDownload.DownloadItem.IsReadOnly)
{
_diskProvider.DeleteFolder(trackedDownload.DownloadItem.OutputPath.FullPath, true);
}
}
if (groupedTrackedDownload.Select(c => c.ImportResult).Count(c => c.Result == ImportResultType.Imported) >= Math.Max(1, trackedDownload.RemoteEpisode.Episodes.Count))
{
trackedDownload.State = TrackedDownloadStage.Imported;
_eventAggregator.PublishEvent(new DownloadCompletedEvent(trackedDownload));
}
}
throw new System.NotImplementedException("TODO: This will be rewritten for Music");
//var imported = new List<ImportResult>();
//var importedTrackedDownload = new List<ManuallyImportedFile>();
//for (int i = 0; i < message.Files.Count; i++)
//{
// _logger.ProgressTrace("Processing file {0} of {1}", i + 1, message.Files.Count);
// var file = message.Files[i];
// var series = _seriesService.GetSeries(file.SeriesId);
// var episodes = _episodeService.GetEpisodes(file.EpisodeIds);
// var parsedEpisodeInfo = Parser.Parser.ParsePath(file.Path) ?? new ParsedEpisodeInfo();
// var mediaInfo = _videoFileInfoReader.GetMediaInfo(file.Path);
// var existingFile = series.Path.IsParentPath(file.Path);
// var localEpisode = new LocalEpisode
// {
// ExistingFile = false,
// Episodes = episodes,
// MediaInfo = mediaInfo,
// ParsedEpisodeInfo = parsedEpisodeInfo,
// Path = file.Path,
// Quality = file.Quality,
// Series = series,
// Size = 0
// };
// //TODO: Cleanup non-tracked downloads
// var importDecision = new ImportDecision(localEpisode);
// if (file.DownloadId.IsNullOrWhiteSpace())
// {
// imported.AddRange(_importApprovedEpisodes.Import(new List<ImportDecision> { importDecision }, !existingFile, null, message.ImportMode));
// }
// else
// {
// var trackedDownload = _trackedDownloadService.Find(file.DownloadId);
// var importResult = _importApprovedEpisodes.Import(new List<ImportDecision> { importDecision }, true, trackedDownload.DownloadItem, message.ImportMode).First();
// imported.Add(importResult);
// importedTrackedDownload.Add(new ManuallyImportedFile
// {
// TrackedDownload = trackedDownload,
// ImportResult = importResult
// });
// }
//}
//_logger.ProgressTrace("Manually imported {0} files", imported.Count);
//foreach (var groupedTrackedDownload in importedTrackedDownload.GroupBy(i => i.TrackedDownload.DownloadItem.DownloadId).ToList())
//{
// var trackedDownload = groupedTrackedDownload.First().TrackedDownload;
// if (_diskProvider.FolderExists(trackedDownload.DownloadItem.OutputPath.FullPath))
// {
// if (_downloadedEpisodesImportService.ShouldDeleteFolder(
// new DirectoryInfo(trackedDownload.DownloadItem.OutputPath.FullPath),
// trackedDownload.RemoteEpisode.Series) && !trackedDownload.DownloadItem.IsReadOnly)
// {
// _diskProvider.DeleteFolder(trackedDownload.DownloadItem.OutputPath.FullPath, true);
// }
// }
// if (groupedTrackedDownload.Select(c => c.ImportResult).Count(c => c.Result == ImportResultType.Imported) >= Math.Max(1, trackedDownload.RemoteEpisode.Episodes.Count))
// {
// trackedDownload.State = TrackedDownloadStage.Imported;
// _eventAggregator.PublishEvent(new DownloadCompletedEvent(trackedDownload));
// }
//}
}
}
}

@ -1,4 +1,5 @@
using NzbDrone.Core.Download.TrackedDownloads;
using NzbDrone.Core.MediaFiles.TrackImport;
namespace NzbDrone.Core.MediaFiles.EpisodeImport.Manual
{

@ -63,5 +63,48 @@ namespace NzbDrone.Core.MediaFiles.EpisodeImport.Specifications
return Decision.Accept();
}
public Decision IsSatisfiedBy(LocalTrack localTrack)
{
if (_configService.SkipFreeSpaceCheckWhenImporting)
{
_logger.Debug("Skipping free space check when importing");
return Decision.Accept();
}
try
{
if (localTrack.ExistingFile)
{
_logger.Debug("Skipping free space check for existing episode");
return Decision.Accept();
}
var path = Directory.GetParent(localTrack.Artist.Path);
var freeSpace = _diskProvider.GetAvailableSpace(path.FullName);
if (!freeSpace.HasValue)
{
_logger.Debug("Free space check returned an invalid result for: {0}", path);
return Decision.Accept();
}
if (freeSpace < localTrack.Size + 100.Megabytes())
{
_logger.Warn("Not enough free space ({0}) to import: {1} ({2})", freeSpace, localTrack, localTrack.Size);
return Decision.Reject("Not enough free space");
}
}
catch (DirectoryNotFoundException ex)
{
_logger.Error(ex, "Unable to check free disk space while importing.");
}
catch (Exception ex)
{
_logger.Error(ex, "Unable to check free disk space while importing. {0}", localTrack.Path);
}
return Decision.Accept();
}
}
}

@ -1,4 +1,5 @@
using NLog;
using System;
using NLog;
using NzbDrone.Core.DecisionEngine;
using NzbDrone.Core.Parser.Model;
@ -13,6 +14,11 @@ namespace NzbDrone.Core.MediaFiles.EpisodeImport.Specifications
_logger = logger;
}
public Decision IsSatisfiedBy(LocalTrack localTrack)
{
throw new NotImplementedException("Interface will be removed");
}
public Decision IsSatisfiedBy(LocalEpisode localEpisode)
{
if (localEpisode.ParsedEpisodeInfo.FullSeason)

@ -1,4 +1,5 @@
using System.IO;
using System;
using System.IO;
using System.Linq;
using NLog;
using NzbDrone.Core.DecisionEngine;
@ -14,6 +15,53 @@ namespace NzbDrone.Core.MediaFiles.EpisodeImport.Specifications
{
_logger = logger;
}
public Decision IsSatisfiedBy(LocalTrack localTrack)
{
if (localTrack.ExistingFile)
{
return Decision.Accept();
}
var dirInfo = new FileInfo(localTrack.Path).Directory;
if (dirInfo == null)
{
return Decision.Accept();
}
throw new System.NotImplementedException("Needs to be implemented");
//var folderInfo = Parser.Parser.ParseTitle(dirInfo.Name);
//if (folderInfo == null)
//{
// return Decision.Accept();
//}
//if (!folderInfo.TrackNumbers.Any())
//{
// return Decision.Accept();
//}
//var unexpected = localTrack.ParsedTrackInfo.TrackNumbers.Where(f => !folderInfo.TrackNumbers.Contains(f)).ToList();
//// TODO: Implement MatchesFolderSpecification
//if (unexpected.Any())
//{
// _logger.Debug("Unexpected track number(s) in file: {0}", string.Join(", ", unexpected));
// if (unexpected.Count == 1)
// {
// return Decision.Reject("Track Number {0} was unexpected considering the {1} folder name", unexpected.First(), dirInfo.Name);
// }
// return Decision.Reject("Episode Numbers {0} were unexpected considering the {1} folder name", string.Join(", ", unexpected), dirInfo.Name);
//}
return Decision.Accept();
}
public Decision IsSatisfiedBy(LocalEpisode localEpisode)
{
if (localEpisode.ExistingFile)

@ -1,4 +1,5 @@
using NLog;
using System;
using NLog;
using NzbDrone.Core.DecisionEngine;
using NzbDrone.Core.Parser.Model;
@ -16,6 +17,11 @@ namespace NzbDrone.Core.MediaFiles.EpisodeImport.Specifications
_logger = logger;
}
public Decision IsSatisfiedBy(LocalTrack localTrack)
{
throw new NotImplementedException();
}
public Decision IsSatisfiedBy(LocalEpisode localEpisode)
{
if (localEpisode.ExistingFile)

@ -56,5 +56,10 @@ namespace NzbDrone.Core.MediaFiles.EpisodeImport.Specifications
return Decision.Accept();
}
public Decision IsSatisfiedBy(LocalTrack localTrack)
{
throw new NotImplementedException();
}
}
}

@ -1,3 +1,4 @@
using System;
using NLog;
using NzbDrone.Core.DecisionEngine;
using NzbDrone.Core.Parser.Model;
@ -27,5 +28,10 @@ namespace NzbDrone.Core.MediaFiles.EpisodeImport.Specifications
_logger.Debug("Episode file on disk contains more episodes than this file contains");
return Decision.Reject("Episode file on disk contains more episodes than this file contains");
}
public Decision IsSatisfiedBy(LocalTrack localTrack)
{
throw new NotImplementedException();
}
}
}

@ -1,4 +1,5 @@
using System.Linq;
using System;
using System.Linq;
using NLog;
using NzbDrone.Core.DecisionEngine;
using NzbDrone.Core.Parser.Model;
@ -13,6 +14,11 @@ namespace NzbDrone.Core.MediaFiles.EpisodeImport.Specifications
_logger = logger;
}
public Decision IsSatisfiedBy(LocalTrack localTrack)
{
throw new NotImplementedException("This is not needed, Interface will be removed");
}
public Decision IsSatisfiedBy(LocalEpisode localEpisode)
{
if (localEpisode.ExistingFile)

@ -1,4 +1,5 @@
using System.Linq;
using System;
using System.Linq;
using NLog;
using NzbDrone.Core.DecisionEngine;
using NzbDrone.Core.Parser.Model;
@ -15,6 +16,18 @@ namespace NzbDrone.Core.MediaFiles.EpisodeImport.Specifications
_logger = logger;
}
public Decision IsSatisfiedBy(LocalTrack localTrack)
{
var qualityComparer = new QualityModelComparer(localTrack.Artist.Profile);
if (localTrack.Tracks.Any(e => e.TrackFileId != 0 && qualityComparer.Compare(e.TrackFile.Value.Quality, localTrack.Quality) > 0))
{
_logger.Debug("This file isn't an upgrade for all tracks. Skipping {0}", localTrack.Path);
return Decision.Reject("Not an upgrade for existing track file(s)");
}
return Decision.Accept();
}
public Decision IsSatisfiedBy(LocalEpisode localEpisode)
{
var qualityComparer = new QualityModelComparer(localEpisode.Series.Profile);

@ -0,0 +1,23 @@
using NzbDrone.Common.Messaging;
using NzbDrone.Core.Parser.Model;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
namespace NzbDrone.Core.MediaFiles.Events
{
public class TrackDownloadedEvent : IEvent
{
public LocalTrack Track { get; private set; }
public TrackFile TrackFile { get; private set; }
public List<TrackFile> OldFiles { get; private set; }
public TrackDownloadedEvent(LocalTrack track, TrackFile trackFile, List<TrackFile> oldFiles)
{
Track = track;
TrackFile = trackFile;
OldFiles = oldFiles;
}
}
}

@ -5,81 +5,73 @@ using NLog;
using NzbDrone.Common;
using NzbDrone.Common.Extensions;
using NzbDrone.Core.Tv;
using NzbDrone.Core.Music;
namespace NzbDrone.Core.MediaFiles
{
public interface IMediaFileTableCleanupService
{
void Clean(Series series, List<string> filesOnDisk);
void Clean(Artist artist, List<string> filesOnDisk);
}
public class MediaFileTableCleanupService : IMediaFileTableCleanupService
{
private readonly IMediaFileService _mediaFileService;
private readonly IEpisodeService _episodeService;
private readonly ITrackService _trackService;
private readonly Logger _logger;
public MediaFileTableCleanupService(IMediaFileService mediaFileService,
IEpisodeService episodeService,
ITrackService trackService,
Logger logger)
{
_mediaFileService = mediaFileService;
_episodeService = episodeService;
_trackService = trackService;
_logger = logger;
}
public void Clean(Series series, List<string> filesOnDisk)
public void Clean(Artist artist, List<string> filesOnDisk)
{
var seriesFiles = _mediaFileService.GetFilesBySeries(series.Id);
var episodes = _episodeService.GetEpisodeBySeries(series.Id);
var artistFiles = _mediaFileService.GetFilesByArtist(artist.SpotifyId);
var tracks = _trackService.GetTracksByArtist(artist.SpotifyId);
var filesOnDiskKeys = new HashSet<string>(filesOnDisk, PathEqualityComparer.Instance);
foreach (var seriesFile in seriesFiles)
foreach (var artistFile in artistFiles)
{
var episodeFile = seriesFile;
var episodeFilePath = Path.Combine(series.Path, episodeFile.RelativePath);
var trackFile = artistFile;
var trackFilePath = Path.Combine(artist.Path, trackFile.RelativePath);
try
{
if (!filesOnDiskKeys.Contains(episodeFilePath))
if (!filesOnDiskKeys.Contains(trackFilePath))
{
_logger.Debug("File [{0}] no longer exists on disk, removing from db", episodeFilePath);
_mediaFileService.Delete(seriesFile, DeleteMediaFileReason.MissingFromDisk);
_logger.Debug("File [{0}] no longer exists on disk, removing from db", trackFilePath);
_mediaFileService.Delete(artistFile, DeleteMediaFileReason.MissingFromDisk);
continue;
}
if (episodes.None(e => e.EpisodeFileId == episodeFile.Id))
if (tracks.None(e => e.TrackFileId == trackFile.Id))
{
_logger.Debug("File [{0}] is not assigned to any episodes, removing from db", episodeFilePath);
_mediaFileService.Delete(episodeFile, DeleteMediaFileReason.NoLinkedEpisodes);
_logger.Debug("File [{0}] is not assigned to any artist, removing from db", trackFilePath);
_mediaFileService.Delete(trackFile, DeleteMediaFileReason.NoLinkedEpisodes);
continue;
}
// var localEpsiode = _parsingService.GetLocalEpisode(episodeFile.Path, series);
//
// if (localEpsiode == null || episodes.Count != localEpsiode.Episodes.Count)
// {
// _logger.Debug("File [{0}] parsed episodes has changed, removing from db", episodeFile.Path);
// _mediaFileService.Delete(episodeFile);
// continue;
// }
}
catch (Exception ex)
{
_logger.Error(ex, "Unable to cleanup EpisodeFile in DB: {0}", episodeFile.Id);
_logger.Error(ex, "Unable to cleanup EpisodeFile in DB: {0}", trackFile.Id);
}
}
foreach (var e in episodes)
foreach (var t in tracks)
{
var episode = e;
var track = t;
if (episode.EpisodeFileId > 0 && seriesFiles.None(f => f.Id == episode.EpisodeFileId))
if (track.TrackFileId > 0 && artistFiles.None(f => f.Id == track.TrackFileId))
{
episode.EpisodeFileId = 0;
_episodeService.UpdateEpisode(episode);
track.TrackFileId = 0;
_trackService.UpdateTrack(track);
}
}
}

@ -7,10 +7,11 @@ using NzbDrone.Core.Tv;
using System.Collections.Generic;
using System.Linq;
using NzbDrone.Core.Configuration;
using NzbDrone.Core.Music;
namespace NzbDrone.Core.MediaFiles.MediaInfo
{
public class UpdateMediaInfoService : IHandle<SeriesScannedEvent>
public class UpdateMediaInfoService : IHandle<ArtistScannedEvent>
{
private readonly IDiskProvider _diskProvider;
private readonly IMediaFileService _mediaFileService;
@ -33,11 +34,11 @@ namespace NzbDrone.Core.MediaFiles.MediaInfo
_logger = logger;
}
private void UpdateMediaInfo(Series series, List<EpisodeFile> mediaFiles)
private void UpdateMediaInfo(Artist artist, List<TrackFile> mediaFiles)
{
foreach (var mediaFile in mediaFiles)
{
var path = Path.Combine(series.Path, mediaFile.RelativePath);
var path = Path.Combine(artist.Path, mediaFile.RelativePath);
if (!_diskProvider.FileExists(path))
{
@ -56,7 +57,7 @@ namespace NzbDrone.Core.MediaFiles.MediaInfo
}
}
public void Handle(SeriesScannedEvent message)
public void Handle(ArtistScannedEvent message)
{
if (!_configService.EnableMediaInfo)
{
@ -64,10 +65,10 @@ namespace NzbDrone.Core.MediaFiles.MediaInfo
return;
}
var allMediaFiles = _mediaFileService.GetFilesBySeries(message.Series.Id);
var allMediaFiles = _mediaFileService.GetFilesByArtist(message.Artist.SpotifyId);
var filteredMediaFiles = allMediaFiles.Where(c => c.MediaInfo == null || c.MediaInfo.SchemaRevision < CURRENT_MEDIA_INFO_SCHEMA_REVISION).ToList();
UpdateMediaInfo(message.Series, filteredMediaFiles);
UpdateMediaInfo(message.Artist, filteredMediaFiles);
}
}
}

@ -55,24 +55,28 @@ namespace NzbDrone.Core.MediaFiles
public List<RenameEpisodeFilePreview> GetRenamePreviews(int seriesId)
{
var series = _seriesService.GetSeries(seriesId);
var episodes = _episodeService.GetEpisodeBySeries(seriesId);
var files = _mediaFileService.GetFilesBySeries(seriesId);
return GetPreviews(series, episodes, files)
.OrderByDescending(e => e.SeasonNumber)
.ThenByDescending(e => e.EpisodeNumbers.First())
.ToList();
// TODO
throw new NotImplementedException();
//var series = _seriesService.GetSeries(seriesId);
//var episodes = _episodeService.GetEpisodeBySeries(seriesId);
//var files = _mediaFileService.GetFilesBySeries(seriesId);
//return GetPreviews(series, episodes, files)
// .OrderByDescending(e => e.SeasonNumber)
// .ThenByDescending(e => e.EpisodeNumbers.First())
// .ToList();
}
public List<RenameEpisodeFilePreview> GetRenamePreviews(int seriesId, int seasonNumber)
{
var series = _seriesService.GetSeries(seriesId);
var episodes = _episodeService.GetEpisodesBySeason(seriesId, seasonNumber);
var files = _mediaFileService.GetFilesBySeason(seriesId, seasonNumber);
return GetPreviews(series, episodes, files)
.OrderByDescending(e => e.EpisodeNumbers.First()).ToList();
// TODO
throw new NotImplementedException();
//var series = _seriesService.GetSeries(seriesId);
//var episodes = _episodeService.GetEpisodesBySeason(seriesId, seasonNumber);
//var files = _mediaFileService.GetFilesBySeason(seriesId, seasonNumber);
//return GetPreviews(series, episodes, files)
// .OrderByDescending(e => e.EpisodeNumbers.First()).ToList();
}
private IEnumerable<RenameEpisodeFilePreview> GetPreviews(Series series, List<Episode> episodes, List<EpisodeFile> files)
@ -110,62 +114,68 @@ namespace NzbDrone.Core.MediaFiles
private void RenameFiles(List<EpisodeFile> episodeFiles, Series series)
{
var renamed = new List<EpisodeFile>();
foreach (var episodeFile in episodeFiles)
{
var episodeFilePath = Path.Combine(series.Path, episodeFile.RelativePath);
try
{
_logger.Debug("Renaming episode file: {0}", episodeFile);
_episodeFileMover.MoveEpisodeFile(episodeFile, series);
_mediaFileService.Update(episodeFile);
renamed.Add(episodeFile);
_logger.Debug("Renamed episode file: {0}", episodeFile);
}
catch (SameFilenameException ex)
{
_logger.Debug("File not renamed, source and destination are the same: {0}", ex.Filename);
}
catch (Exception ex)
{
_logger.Error(ex, "Failed to rename file {0}", episodeFilePath);
}
}
if (renamed.Any())
{
_diskProvider.RemoveEmptySubfolders(series.Path);
_eventAggregator.PublishEvent(new SeriesRenamedEvent(series));
}
// TODO
throw new NotImplementedException();
//var renamed = new List<EpisodeFile>();
//foreach (var episodeFile in episodeFiles)
//{
// var episodeFilePath = Path.Combine(series.Path, episodeFile.RelativePath);
// try
// {
// _logger.Debug("Renaming episode file: {0}", episodeFile);
// _episodeFileMover.MoveEpisodeFile(episodeFile, series);
// _mediaFileService.Update(episodeFile);
// renamed.Add(episodeFile);
// _logger.Debug("Renamed episode file: {0}", episodeFile);
// }
// catch (SameFilenameException ex)
// {
// _logger.Debug("File not renamed, source and destination are the same: {0}", ex.Filename);
// }
// catch (Exception ex)
// {
// _logger.Error(ex, "Failed to rename file {0}", episodeFilePath);
// }
//}
//if (renamed.Any())
//{
// _diskProvider.RemoveEmptySubfolders(series.Path);
// _eventAggregator.PublishEvent(new SeriesRenamedEvent(series));
//}
}
public void Execute(RenameFilesCommand message)
{
var series = _seriesService.GetSeries(message.SeriesId);
var episodeFiles = _mediaFileService.Get(message.Files);
_logger.ProgressInfo("Renaming {0} files for {1}", episodeFiles.Count, series.Title);
RenameFiles(episodeFiles, series);
_logger.ProgressInfo("Selected episode files renamed for {0}", series.Title);
// TODO
throw new NotImplementedException();
//var series = _seriesService.GetSeries(message.SeriesId);
//var episodeFiles = _mediaFileService.Get(message.Files);
//_logger.ProgressInfo("Renaming {0} files for {1}", episodeFiles.Count, series.Title);
//RenameFiles(episodeFiles, series);
//_logger.ProgressInfo("Selected episode files renamed for {0}", series.Title);
}
public void Execute(RenameSeriesCommand message)
{
_logger.Debug("Renaming all files for selected series");
var seriesToRename = _seriesService.GetSeries(message.SeriesIds);
foreach (var series in seriesToRename)
{
var episodeFiles = _mediaFileService.GetFilesBySeries(series.Id);
_logger.ProgressInfo("Renaming all files in series: {0}", series.Title);
RenameFiles(episodeFiles, series);
_logger.ProgressInfo("All episode files renamed for {0}", series.Title);
}
// TODO
throw new NotImplementedException();
//_logger.Debug("Renaming all files for selected series");
//var seriesToRename = _seriesService.GetSeries(message.SeriesIds);
//foreach (var series in seriesToRename)
//{
// var episodeFiles = _mediaFileService.GetFilesBySeries(series.Id);
// _logger.ProgressInfo("Renaming all files in series: {0}", series.Title);
// RenameFiles(episodeFiles, series);
// _logger.ProgressInfo("All episode files renamed for {0}", series.Title);
//}
}
}
}

@ -0,0 +1,18 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
namespace NzbDrone.Core.MediaFiles
{
public class TrackFileMoveResult
{
public TrackFileMoveResult()
{
OldFiles = new List<TrackFile>();
}
public TrackFile TrackFile { get; set; }
public List<TrackFile> OldFiles { get; set; }
}
}

@ -0,0 +1,226 @@
using NLog;
using NzbDrone.Common.Disk;
using NzbDrone.Common.EnsureThat;
using NzbDrone.Common.Extensions;
using NzbDrone.Core.Configuration;
using NzbDrone.Core.MediaFiles.Events;
using NzbDrone.Core.Messaging.Events;
using NzbDrone.Core.Music;
using NzbDrone.Core.Organizer;
using NzbDrone.Core.Parser.Model;
using NzbDrone.Core.Tv;
using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Text;
namespace NzbDrone.Core.MediaFiles
{
public interface IMoveTrackFiles
{
TrackFile MoveTrackFile(TrackFile trackFile, Artist artist);
TrackFile MoveTrackFile(TrackFile trackFile, LocalTrack localTrack);
TrackFile CopyTrackFile(TrackFile trackFile, LocalTrack localTrack);
}
public class TrackFileMovingService : IMoveTrackFiles
{
private readonly ITrackService _trackService;
//private readonly IUpdateTrackFileService _updateTrackFileService;
private readonly IBuildFileNames _buildFileNames;
private readonly IDiskTransferService _diskTransferService;
private readonly IDiskProvider _diskProvider;
private readonly IMediaFileAttributeService _mediaFileAttributeService;
private readonly IEventAggregator _eventAggregator;
private readonly IConfigService _configService;
private readonly Logger _logger;
public TrackFileMovingService(ITrackService episodeService,
//IUpdateEpisodeFileService updateEpisodeFileService,
IBuildFileNames buildFileNames,
IDiskTransferService diskTransferService,
IDiskProvider diskProvider,
IMediaFileAttributeService mediaFileAttributeService,
IEventAggregator eventAggregator,
IConfigService configService,
Logger logger)
{
_trackService = episodeService;
//_updateTrackFileService = updateEpisodeFileService;
_buildFileNames = buildFileNames;
_diskTransferService = diskTransferService;
_diskProvider = diskProvider;
_mediaFileAttributeService = mediaFileAttributeService;
_eventAggregator = eventAggregator;
_configService = configService;
_logger = logger;
}
public TrackFile MoveTrackFile(TrackFile trackFile, Artist artist)
{
throw new System.NotImplementedException();
// TODO
//var tracks = _trackService.GetTracksByFileId(trackFile.Id);
//var newFileName = _buildFileNames.BuildFileName(tracks, artist, trackFile);
//var filePath = _buildFileNames.BuildFilePath(artist, tracks.First(), trackFile.AlbumId, newFileName, Path.GetExtension(trackFile.RelativePath));
//EnsureAlbumFolder(trackFile, artist, tracks.Select(v => v.Album).First(), filePath);
//_logger.Debug("Renaming track file: {0} to {1}", trackFile, filePath);
//return TransferFile(trackFile, artist, tracks, filePath, TransferMode.Move);
}
public TrackFile MoveTrackFile(TrackFile trackFile, LocalTrack localTrack)
{
// TODO
throw new System.NotImplementedException();
//var newFileName = _buildFileNames.BuildFileName(localEpisode.Episodes, localEpisode.Series, episodeFile);
//var filePath = _buildFileNames.BuildFilePath(localEpisode.Series, localEpisode.SeasonNumber, newFileName, Path.GetExtension(localEpisode.Path));
//EnsureEpisodeFolder(episodeFile, localEpisode, filePath);
//_logger.Debug("Moving episode file: {0} to {1}", episodeFile.Path, filePath);
//return TransferFile(episodeFile, localEpisode.Series, localEpisode.Episodes, filePath, TransferMode.Move);
}
public TrackFile CopyTrackFile(TrackFile trackFile, LocalTrack localTrack)
{
// TODO
throw new System.NotImplementedException();
//var newFileName = _buildFileNames.BuildFileName(localEpisode.Episodes, localEpisode.Series, episodeFile);
//var filePath = _buildFileNames.BuildFilePath(localEpisode.Series, localEpisode.SeasonNumber, newFileName, Path.GetExtension(localEpisode.Path));
//EnsureEpisodeFolder(episodeFile, localEpisode, filePath);
//if (_configService.CopyUsingHardlinks)
//{
// _logger.Debug("Hardlinking episode file: {0} to {1}", episodeFile.Path, filePath);
// return TransferFile(episodeFile, localEpisode.Series, localEpisode.Episodes, filePath, TransferMode.HardLinkOrCopy);
//}
//_logger.Debug("Copying episode file: {0} to {1}", episodeFile.Path, filePath);
//return TransferFile(episodeFile, localEpisode.Series, localEpisode.Episodes, filePath, TransferMode.Copy);
}
private EpisodeFile TransferFile(EpisodeFile episodeFile, Series series, List<Episode> episodes, string destinationFilePath, TransferMode mode)
{
// TODO
throw new System.NotImplementedException();
//Ensure.That(episodeFile, () => episodeFile).IsNotNull();
//Ensure.That(series, () => series).IsNotNull();
//Ensure.That(destinationFilePath, () => destinationFilePath).IsValidPath();
//var episodeFilePath = episodeFile.Path ?? Path.Combine(series.Path, episodeFile.RelativePath);
//if (!_diskProvider.FileExists(episodeFilePath))
//{
// throw new FileNotFoundException("Episode file path does not exist", episodeFilePath);
//}
//if (episodeFilePath == destinationFilePath)
//{
// throw new SameFilenameException("File not moved, source and destination are the same", episodeFilePath);
//}
//_diskTransferService.TransferFile(episodeFilePath, destinationFilePath, mode);
//episodeFile.RelativePath = series.Path.GetRelativePath(destinationFilePath);
//_updateTrackFileService.ChangeFileDateForFile(episodeFile, series, episodes);
//try
//{
// _mediaFileAttributeService.SetFolderLastWriteTime(series.Path, episodeFile.DateAdded);
// if (series.SeasonFolder)
// {
// var seasonFolder = Path.GetDirectoryName(destinationFilePath);
// _mediaFileAttributeService.SetFolderLastWriteTime(seasonFolder, episodeFile.DateAdded);
// }
//}
//catch (Exception ex)
//{
// _logger.Warn(ex, "Unable to set last write time");
//}
//_mediaFileAttributeService.SetFilePermissions(destinationFilePath);
//return episodeFile;
}
private void EnsureEpisodeFolder(EpisodeFile episodeFile, LocalEpisode localEpisode, string filePath)
{
EnsureEpisodeFolder(episodeFile, localEpisode.Series, localEpisode.SeasonNumber, filePath);
}
private void EnsureEpisodeFolder(EpisodeFile episodeFile, Series series, int seasonNumber, string filePath)
{
var episodeFolder = Path.GetDirectoryName(filePath);
var seasonFolder = _buildFileNames.BuildSeasonPath(series, seasonNumber);
var seriesFolder = series.Path;
var rootFolder = new OsPath(seriesFolder).Directory.FullPath;
if (!_diskProvider.FolderExists(rootFolder))
{
throw new DirectoryNotFoundException(string.Format("Root folder '{0}' was not found.", rootFolder));
}
var changed = false;
var newEvent = new EpisodeFolderCreatedEvent(series, episodeFile);
if (!_diskProvider.FolderExists(seriesFolder))
{
CreateFolder(seriesFolder);
newEvent.SeriesFolder = seriesFolder;
changed = true;
}
if (seriesFolder != seasonFolder && !_diskProvider.FolderExists(seasonFolder))
{
CreateFolder(seasonFolder);
newEvent.SeasonFolder = seasonFolder;
changed = true;
}
if (seasonFolder != episodeFolder && !_diskProvider.FolderExists(episodeFolder))
{
CreateFolder(episodeFolder);
newEvent.EpisodeFolder = episodeFolder;
changed = true;
}
if (changed)
{
_eventAggregator.PublishEvent(newEvent);
}
}
private void CreateFolder(string directoryName)
{
Ensure.That(directoryName, () => directoryName).IsNotNullOrWhiteSpace();
var parentFolder = new OsPath(directoryName).Directory.FullPath;
if (!_diskProvider.FolderExists(parentFolder))
{
CreateFolder(parentFolder);
}
try
{
_diskProvider.CreateFolder(directoryName);
}
catch (IOException ex)
{
_logger.Error(ex, "Unable to create directory: {0}", directoryName);
}
_mediaFileAttributeService.SetFolderPermissions(directoryName);
}
}
}

@ -0,0 +1,172 @@
using NLog;
using NzbDrone.Common.Disk;
using NzbDrone.Common.Extensions;
using NzbDrone.Core.Download;
using NzbDrone.Core.MediaFiles.EpisodeImport;
using NzbDrone.Core.MediaFiles.Events;
using NzbDrone.Core.Messaging.Events;
using NzbDrone.Core.Qualities;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
namespace NzbDrone.Core.MediaFiles.TrackImport
{
public interface IImportApprovedTracks
{
List<ImportResult> Import(List<ImportDecision> decisions, bool newDownload, DownloadClientItem downloadClientItem = null, ImportMode importMode = ImportMode.Auto);
}
public class ImportApprovedTracks : IImportApprovedTracks
{
private readonly IUpgradeMediaFiles _trackFileUpgrader;
private readonly IMediaFileService _mediaFileService;
//private readonly IExtraService _extraService;
private readonly IDiskProvider _diskProvider;
private readonly IEventAggregator _eventAggregator;
private readonly Logger _logger;
public ImportApprovedTracks(IUpgradeMediaFiles episodeFileUpgrader,
IMediaFileService mediaFileService,
//IExtraService extraService,
IDiskProvider diskProvider,
IEventAggregator eventAggregator,
Logger logger)
{
_trackFileUpgrader = episodeFileUpgrader;
_mediaFileService = mediaFileService;
// _extraService = extraService;
_diskProvider = diskProvider;
_eventAggregator = eventAggregator;
_logger = logger;
}
public List<ImportResult> Import(List<ImportDecision> decisions, bool newDownload, DownloadClientItem downloadClientItem = null, ImportMode importMode = ImportMode.Auto)
{
var qualifiedImports = decisions.Where(c => c.Approved)
.GroupBy(c => c.LocalTrack.Artist.Id, (i, s) => s
.OrderByDescending(c => c.LocalTrack.Quality, new QualityModelComparer(s.First().LocalTrack.Artist.Profile))
.ThenByDescending(c => c.LocalTrack.Size))
.SelectMany(c => c)
.ToList();
var importResults = new List<ImportResult>();
foreach (var importDecision in qualifiedImports.OrderBy(e => e.LocalTrack.Tracks.Select(track => track.TrackNumber).MinOrDefault())
.ThenByDescending(e => e.LocalTrack.Size))
{
var localTrack = importDecision.LocalTrack;
var oldFiles = new List<TrackFile>();
try
{
//check if already imported
if (importResults.SelectMany(r => r.ImportDecision.LocalTrack.Tracks)
.Select(e => e.Id)
.Intersect(localTrack.Tracks.Select(e => e.Id))
.Any())
{
importResults.Add(new ImportResult(importDecision, "Episode has already been imported"));
continue;
}
var trackFile = new TrackFile();
trackFile.DateAdded = DateTime.UtcNow;
trackFile.SpotifyTrackId = localTrack.Artist.SpotifyId;
trackFile.Path = localTrack.Path.CleanFilePath();
trackFile.Size = _diskProvider.GetFileSize(localTrack.Path);
trackFile.Quality = localTrack.Quality;
trackFile.MediaInfo = localTrack.MediaInfo;
//trackFile.AlbumId = localTrack.Album.ElementAt(0); // TODO: Implement ImportApprovedTracks Album Id
trackFile.Tracks = localTrack.Tracks;
trackFile.ReleaseGroup = localTrack.ParsedTrackInfo.ReleaseGroup;
bool copyOnly;
switch (importMode)
{
default:
case ImportMode.Auto:
copyOnly = downloadClientItem != null && downloadClientItem.IsReadOnly;
break;
case ImportMode.Move:
copyOnly = false;
break;
case ImportMode.Copy:
copyOnly = true;
break;
}
if (newDownload)
{
//trackFile.SceneName = GetSceneName(downloadClientItem, localTrack);
var moveResult = _trackFileUpgrader.UpgradeTrackFile(trackFile, localTrack, copyOnly);
oldFiles = moveResult.OldFiles;
}
else
{
trackFile.RelativePath = localTrack.Artist.Path.GetRelativePath(trackFile.Path);
}
_mediaFileService.Add(trackFile);
importResults.Add(new ImportResult(importDecision));
//if (newDownload)
//{
// _extraService.ImportExtraFiles(localTrack, trackFile, copyOnly); // TODO: Import Music Extras
//}
if (downloadClientItem != null)
{
_eventAggregator.PublishEvent(new TrackImportedEvent(localTrack, trackFile, newDownload, downloadClientItem.DownloadClient, downloadClientItem.DownloadId, downloadClientItem.IsReadOnly));
}
else
{
_eventAggregator.PublishEvent(new TrackImportedEvent(localTrack, trackFile, newDownload));
}
if (newDownload)
{
_eventAggregator.PublishEvent(new TrackDownloadedEvent(localTrack, trackFile, oldFiles));
}
}
catch (Exception e)
{
_logger.Warn(e, "Couldn't import track " + localTrack);
importResults.Add(new ImportResult(importDecision, "Failed to import episode"));
}
}
//Adding all the rejected decisions
importResults.AddRange(decisions.Where(c => !c.Approved)
.Select(d => new ImportResult(d, d.Rejections.Select(r => r.Reason).ToArray())));
return importResults;
}
//private string GetSceneName(DownloadClientItem downloadClientItem, LocalEpisode localEpisode)
//{
// if (downloadClientItem != null)
// {
// var title = Parser.Parser.RemoveFileExtension(downloadClientItem.Title);
// var parsedTitle = Parser.Parser.ParseTitle(title);
// if (parsedTitle != null && !parsedTitle.FullSeason)
// {
// return title;
// }
// }
// var fileName = Path.GetFileNameWithoutExtension(localEpisode.Path.CleanFilePath());
// if (SceneChecker.IsSceneTitle(fileName))
// {
// return fileName;
// }
// return null;
//}
}
}

@ -0,0 +1,26 @@
using NzbDrone.Common.Extensions;
using NzbDrone.Core.DecisionEngine;
using NzbDrone.Core.Parser.Model;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
namespace NzbDrone.Core.MediaFiles.TrackImport
{
public class ImportDecision
{
public LocalTrack LocalTrack { get; private set; }
public IEnumerable<Rejection> Rejections { get; private set; }
public bool Approved => Rejections.Empty();
public object LocalEpisode { get; internal set; }
public ImportDecision(LocalTrack localTrack, params Rejection[] rejections)
{
LocalTrack = localTrack;
Rejections = rejections.ToList();
}
}
}

@ -1,4 +1,4 @@
namespace NzbDrone.Core.MediaFiles.EpisodeImport
namespace NzbDrone.Core.MediaFiles.TrackImport
{
public enum ImportMode
{

@ -1,8 +1,10 @@
using System.Collections.Generic;
using NzbDrone.Common.EnsureThat;
using System;
using System.Collections.Generic;
using System.Linq;
using NzbDrone.Common.EnsureThat;
using System.Text;
namespace NzbDrone.Core.MediaFiles.EpisodeImport
namespace NzbDrone.Core.MediaFiles.TrackImport
{
public class ImportResult
{

@ -1,4 +1,4 @@
namespace NzbDrone.Core.MediaFiles.EpisodeImport
namespace NzbDrone.Core.MediaFiles.TrackImport
{
public enum ImportResultType
{

@ -9,7 +9,8 @@ namespace NzbDrone.Core.MediaFiles
{
public interface IUpgradeMediaFiles
{
EpisodeFileMoveResult UpgradeEpisodeFile(EpisodeFile episodeFile, LocalEpisode localEpisode, bool copyOnly = false);
//EpisodeFileMoveResult UpgradeEpisodeFile(EpisodeFile episodeFile, LocalEpisode localEpisode, bool copyOnly = false);
TrackFileMoveResult UpgradeTrackFile(TrackFile trackFile, LocalTrack localTrack, bool copyOnly = false);
}
public class UpgradeMediaFileService : IUpgradeMediaFiles
@ -17,35 +18,36 @@ namespace NzbDrone.Core.MediaFiles
private readonly IRecycleBinProvider _recycleBinProvider;
private readonly IMediaFileService _mediaFileService;
private readonly IMoveEpisodeFiles _episodeFileMover;
private readonly IMoveTrackFiles _trackFileMover;
private readonly IDiskProvider _diskProvider;
private readonly Logger _logger;
public UpgradeMediaFileService(IRecycleBinProvider recycleBinProvider,
IMediaFileService mediaFileService,
IMoveEpisodeFiles episodeFileMover,
IMoveTrackFiles trackFileMover,
IDiskProvider diskProvider,
Logger logger)
{
_recycleBinProvider = recycleBinProvider;
_mediaFileService = mediaFileService;
_episodeFileMover = episodeFileMover;
_trackFileMover = trackFileMover;
_diskProvider = diskProvider;
_logger = logger;
}
public EpisodeFileMoveResult UpgradeEpisodeFile(EpisodeFile episodeFile, LocalEpisode localEpisode, bool copyOnly = false)
public TrackFileMoveResult UpgradeTrackFile(TrackFile trackFile, LocalTrack localTrack, bool copyOnly = false)
{
var moveFileResult = new EpisodeFileMoveResult();
var existingFiles = localEpisode.Episodes
.Where(e => e.EpisodeFileId > 0)
.Select(e => e.EpisodeFile.Value)
var moveFileResult = new TrackFileMoveResult();
var existingFiles = localTrack.Tracks
.Where(e => e.TrackFileId > 0)
.Select(e => e.TrackFile.Value)
.GroupBy(e => e.Id);
foreach (var existingFile in existingFiles)
{
var file = existingFile.First();
var episodeFilePath = Path.Combine(localEpisode.Series.Path, file.RelativePath);
var subfolder = _diskProvider.GetParentFolder(localEpisode.Series.Path).GetRelativePath(_diskProvider.GetParentFolder(episodeFilePath));
var episodeFilePath = Path.Combine(localTrack.Artist.Path, file.RelativePath);
var subfolder = _diskProvider.GetParentFolder(localTrack.Artist.Path).GetRelativePath(_diskProvider.GetParentFolder(episodeFilePath));
if (_diskProvider.FileExists(episodeFilePath))
{
@ -59,11 +61,11 @@ namespace NzbDrone.Core.MediaFiles
if (copyOnly)
{
moveFileResult.EpisodeFile = _episodeFileMover.CopyEpisodeFile(episodeFile, localEpisode);
moveFileResult.TrackFile = _trackFileMover.CopyTrackFile(trackFile, localTrack);
}
else
{
moveFileResult.EpisodeFile = _episodeFileMover.MoveEpisodeFile(episodeFile, localEpisode);
moveFileResult.TrackFile = _trackFileMover.MoveTrackFile(trackFile, localTrack);
}
return moveFileResult;

@ -205,7 +205,7 @@ namespace NzbDrone.Core.MetadataSource.SkyHook
var lowerTitle = title.ToLowerInvariant();
Console.WriteLine("Searching for " + lowerTitle);
if (lowerTitle.StartsWith("itunes:") || lowerTitle.StartsWith("itunesid:"))
if (lowerTitle.StartsWith("spotify:") || lowerTitle.StartsWith("spotifyid:"))
{
var slug = lowerTitle.Split(':')[1].Trim();

@ -55,11 +55,16 @@ namespace NzbDrone.Core.Music
return _trackRepository.Get(ids).ToList();
}
public Track FindTrack(string artistId, string albumId, int episodeNumber)
public Track FindTrack(string artistId, string albumId, int trackNumber)
{
return _trackRepository.Find(artistId, albumId, episodeNumber);
return _trackRepository.Find(artistId, albumId, trackNumber);
}
//public Track FindTrack(string artistId, int trackNumber)
//{
// return _trackRepository.Find(artistId, trackNumber);
//}
public List<Track> GetTracksByArtist(string artistId)
{
return _trackRepository.GetTracks(artistId).ToList();
@ -132,7 +137,7 @@ namespace NzbDrone.Core.Music
_trackRepository.SetMonitoredByAlbum(artistId, albumId, monitored);
}
public void UpdateEpisodes(List<Track> tracks)
public void UpdateTracks(List<Track> tracks)
{
_trackRepository.UpdateMany(tracks);
}
@ -182,10 +187,5 @@ namespace NzbDrone.Core.Music
_logger.Debug("Linking [{0}] > [{1}]", message.TrackFile.RelativePath, track);
}
}
public void UpdateTracks(List<Track> tracks)
{
_trackRepository.UpdateMany(tracks);
}
}
}

@ -725,7 +725,10 @@
<Compile Include="MediaFiles\Commands\BackendCommandAttribute.cs" />
<Compile Include="MediaFiles\Commands\CleanUpRecycleBinCommand.cs" />
<Compile Include="MediaFiles\Commands\DownloadedEpisodesScanCommand.cs" />
<Compile Include="MediaFiles\EpisodeImport\ImportMode.cs" />
<Compile Include="MediaFiles\Events\TrackDownloadedEvent.cs" />
<Compile Include="MediaFiles\TrackFileMovingService.cs" />
<Compile Include="MediaFiles\TrackFileMoveResult.cs" />
<Compile Include="MediaFiles\TrackImport\ImportMode.cs" />
<Compile Include="MediaFiles\Commands\RenameFilesCommand.cs" />
<Compile Include="MediaFiles\Commands\RenameSeriesCommand.cs" />
<Compile Include="MediaFiles\Commands\RescanSeriesCommand.cs" />
@ -740,12 +743,10 @@
<Compile Include="MediaFiles\EpisodeFile.cs" />
<Compile Include="MediaFiles\EpisodeFileMoveResult.cs" />
<Compile Include="MediaFiles\EpisodeFileMovingService.cs" />
<Compile Include="MediaFiles\EpisodeImport\ImportResult.cs" />
<Compile Include="MediaFiles\EpisodeImport\IImportDecisionEngineSpecification.cs" />
<Compile Include="MediaFiles\EpisodeImport\ImportApprovedEpisodes.cs" />
<Compile Include="MediaFiles\EpisodeImport\ImportDecision.cs" />
<Compile Include="MediaFiles\EpisodeImport\ImportDecisionMaker.cs" />
<Compile Include="MediaFiles\EpisodeImport\ImportResultType.cs" />
<Compile Include="MediaFiles\TrackImport\ImportResultType.cs" />
<Compile Include="MediaFiles\EpisodeImport\Manual\ManualImportFile.cs" />
<Compile Include="MediaFiles\EpisodeImport\Manual\ManualImportCommand.cs" />
<Compile Include="MediaFiles\EpisodeImport\Manual\ManualImportItem.cs" />
@ -761,11 +762,14 @@
<Compile Include="MediaFiles\EpisodeImport\Specifications\UnverifiedSceneNumberingSpecification.cs" />
<Compile Include="MediaFiles\EpisodeImport\Specifications\UpgradeSpecification.cs" />
<Compile Include="MediaFiles\Events\ArtistRenamedEvent.cs" />
<Compile Include="MediaFiles\Events\ArtistScannedEvent.cs" />
<Compile Include="MediaFiles\Events\ArtistScanSkippedEvent.cs" />
<Compile Include="MediaFiles\Events\EpisodeDownloadedEvent.cs" />
<Compile Include="MediaFiles\Events\EpisodeFileAddedEvent.cs" />
<Compile Include="MediaFiles\Events\EpisodeFileDeletedEvent.cs" />
<Compile Include="MediaFiles\Events\EpisodeFolderCreatedEvent.cs" />
<Compile Include="MediaFiles\Events\EpisodeImportedEvent.cs" />
<Compile Include="MediaFiles\Commands\RescanArtistCommand.cs" />
<Compile Include="MediaFiles\Events\SeriesRenamedEvent.cs" />
<Compile Include="MediaFiles\Events\SeriesScanSkippedEvent.cs" />
<Compile Include="MediaFiles\Events\SeriesScannedEvent.cs" />
@ -789,6 +793,9 @@
<Compile Include="MediaFiles\RenameEpisodeFileService.cs" />
<Compile Include="MediaFiles\SameFilenameException.cs" />
<Compile Include="MediaFiles\TrackFile.cs" />
<Compile Include="MediaFiles\TrackImport\ImportApprovedTracks.cs" />
<Compile Include="MediaFiles\TrackImport\ImportDecision.cs" />
<Compile Include="MediaFiles\TrackImport\ImportResult.cs" />
<Compile Include="MediaFiles\UpdateEpisodeFileService.cs" />
<Compile Include="MediaFiles\UpgradeMediaFileService.cs" />
<Compile Include="Messaging\Commands\BackendCommandAttribute.cs" />
@ -928,6 +935,7 @@
<Compile Include="Parser\IsoLanguage.cs" />
<Compile Include="Parser\IsoLanguages.cs" />
<Compile Include="Parser\LanguageParser.cs" />
<Compile Include="Parser\Model\ArtistTitleInfo.cs" />
<Compile Include="Parser\Model\LocalTrack.cs" />
<Compile Include="Parser\Model\ParsedTrackInfo.cs" />
<Compile Include="Profiles\Delay\DelayProfile.cs" />

@ -380,33 +380,34 @@ namespace NzbDrone.Core.Parser
if (result != null)
{
if (result.FullSeason && title.ContainsIgnoreCase("Special"))
{
result.FullSeason = false;
result.Special = true;
}
//if (result.FullSeason && title.ContainsIgnoreCase("Special"))
//{
// result.FullSeason = false;
// result.Special = true;
//}
result.Language = LanguageParser.ParseLanguage(title);
Logger.Debug("Language parsed: {0}", result.Language);
//result.Language = LanguageParser.ParseLanguage(title);
//Logger.Debug("Language parsed: {0}", result.Language);
result.Quality = QualityParser.ParseQuality(title);
Logger.Debug("Quality parsed: {0}", result.Quality);
result.ReleaseGroup = ParseReleaseGroup(title);
// Majora: We don't currently need Release Group for Music.
//result.ReleaseGroup = ParseReleaseGroup(title);
var subGroup = GetSubGroup(match);
if (!subGroup.IsNullOrWhiteSpace())
{
result.ReleaseGroup = subGroup;
}
//var subGroup = GetSubGroup(match);
//if (!subGroup.IsNullOrWhiteSpace())
//{
// result.ReleaseGroup = subGroup;
//}
Logger.Debug("Release Group parsed: {0}", result.ReleaseGroup);
//Logger.Debug("Release Group parsed: {0}", result.ReleaseGroup);
result.ReleaseHash = GetReleaseHash(match);
if (!result.ReleaseHash.IsNullOrWhiteSpace())
{
Logger.Debug("Release Hash parsed: {0}", result.ReleaseHash);
}
//result.ReleaseHash = GetReleaseHash(match);
//if (!result.ReleaseHash.IsNullOrWhiteSpace())
//{
// Logger.Debug("Release Hash parsed: {0}", result.ReleaseHash);
//}
return result;
}

@ -457,7 +457,9 @@ namespace NzbDrone.Core.Parser
if (trackInfo == null)
{
trackInfo = _trackService.FindTrack(artist.SpotifyId, trackNumber);
// TODO: [ParsingService]: FindTrack by artistID and trackNumber (or albumID and trackNumber if we change db schema to album as base)
_logger.Debug("TrackInfo is null, we will not add as FindTrack(artistId, trackNumber) is not implemented");
//trackInfo = _trackService.FindTrack(artist.SpotifyId, trackNumber);
}
if (trackInfo != null)
@ -467,7 +469,7 @@ namespace NzbDrone.Core.Parser
else
{
_logger.Debug("Unable to find {0}", parsedEpisodeInfo);
_logger.Debug("Unable to find {0}", parsedTrackInfo);
}
}
@ -553,6 +555,7 @@ namespace NzbDrone.Core.Parser
return result;
}
public LocalTrack GetLocalTrack(string filename, Artist artist)
{
return GetLocalTrack(filename, artist, null);
@ -635,7 +638,60 @@ namespace NzbDrone.Core.Parser
}
return GetStandardEpisodes(artist, parsedTrackInfo, sceneSource, searchCriteria);*/
return GetStandardTracks(artist, parsedTrackInfo, searchCriteria);
return GetStandardTracks(artist, parsedTrackInfo);
}
private List<Track> GetStandardTracks(Artist artist, ParsedTrackInfo parsedTrackInfo)
{
var result = new List<Track>();
//var seasonNumber = parsedEpisodeInfo.SeasonNumber;
//if (sceneSource)
//{
// var sceneMapping = _sceneMappingService.FindSceneMapping(parsedEpisodeInfo.SeriesTitle);
// if (sceneMapping != null && sceneMapping.SeasonNumber.HasValue && sceneMapping.SeasonNumber.Value >= 0 &&
// sceneMapping.SceneSeasonNumber == seasonNumber)
// {
// seasonNumber = sceneMapping.SeasonNumber.Value;
// }
//}
if (parsedTrackInfo.TrackNumbers == null)
{
return new List<Track>();
}
foreach (var trackNumber in parsedTrackInfo.TrackNumbers)
{
Track trackInfo = null;
//if (searchCriteria != null)
//{
// trackInfo = searchCriteria.Episodes.SingleOrDefault(e => e.SeasonNumber == seasonNumber && e.EpisodeNumber == trackNumber);
//}
if (trackInfo == null)
{
// TODO: [ParsingService]: FindTrack by artistID and trackNumber (or albumID and trackNumber if we change db schema to album as base)
_logger.Debug("TrackInfo is null, we will not add as FindTrack(artistId, trackNumber) is not implemented");
//trackInfo = _trackService.FindTrack(artist.SpotifyId, trackNumber); //series.Id, seasonNumber, trackNumber
}
if (trackInfo != null)
{
result.Add(trackInfo);
}
else
{
_logger.Debug("Unable to find {0}", parsedTrackInfo);
}
}
return result;
}
}
}

@ -176,7 +176,7 @@ namespace NzbDrone.Core.Tv
try
{
_logger.Info("Skipping refresh of series: {0}", series.Title);
_diskScanService.Scan(series);
//_diskScanService.Scan(series);
}
catch (Exception e)
{

Loading…
Cancel
Save