New: Only scan files that are new or updated (#760)

* New: Only scan files that are new or updated

Pass through filter correctly

Add more tests

Add tests for migration 30

* Fix windows disk provider

* Don't publish deleted event for unmapped file

* Fix test on windows
pull/791/head
ta264 5 years ago committed by Qstick
parent 8fe8aec97c
commit 166fc90454

@ -1,5 +1,6 @@
using System;
using System.IO;
using System.IO.Abstractions;
using System.Linq;
using Nancy;
using NzbDrone.Common.Disk;
@ -62,9 +63,9 @@ namespace Lidarr.Api.V1.FileSystem
}
return _diskScanService.GetAudioFiles(path).Select(f => new {
Path = f,
RelativePath = path.GetRelativePath(f),
Name = Path.GetFileName(f)
Path = f.FullName,
RelativePath = path.GetRelativePath(f.FullName),
Name = f.Name
}).AsResponse();
}
}

@ -72,6 +72,9 @@
<Reference Include="NodaTime, Version=1.3.0.0, Culture=neutral, PublicKeyToken=4226afe0d9b296d1, processorArchitecture=MSIL">
<HintPath>..\packages\Ical.Net.2.2.32\lib\net46\NodaTime.dll</HintPath>
</Reference>
<Reference Include="System.IO.Abstractions, Version=4.0.0.0, Culture=neutral, processorArchitecture=MSIL">
<HintPath>..\packages\System.IO.Abstractions.4.0.11\lib\net40\System.IO.Abstractions.dll</HintPath>
</Reference>
<Reference Include="System" />
<Reference Include="System.Core" />
<Reference Include="Microsoft.CSharp" />

@ -7,6 +7,7 @@ using NzbDrone.Core.Music;
using NLog;
using Nancy;
using Lidarr.Http;
using NzbDrone.Core.MediaFiles;
namespace Lidarr.Api.V1.ManualImport
{
@ -43,10 +44,10 @@ namespace Lidarr.Api.V1.ManualImport
{
var folder = (string)Request.Query.folder;
var downloadId = (string)Request.Query.downloadId;
var filterExistingFiles = Request.GetBooleanQueryParameter("filterExistingFiles", true);
var filter = Request.GetBooleanQueryParameter("filterExistingFiles", true) ? FilterFilesType.Matched : FilterFilesType.None;
var replaceExistingFiles = Request.GetBooleanQueryParameter("replaceExistingFiles", true);
return _manualImportService.GetMediaFiles(folder, downloadId, filterExistingFiles, replaceExistingFiles).ToResource().Select(AddQualityWeight).ToList();
return _manualImportService.GetMediaFiles(folder, downloadId, filter, replaceExistingFiles).ToResource().Select(AddQualityWeight).ToList();
}
private ManualImportResource AddQualityWeight(ManualImportResource item)

@ -150,7 +150,6 @@ namespace Lidarr.Api.V1.TrackFiles
}
var artist = trackFile.Artist.Value;
var fullPath = Path.Combine(artist.Path, trackFile.RelativePath);
_mediaFileDeletionService.DeleteTrackFile(artist, trackFile);
}
@ -163,8 +162,6 @@ namespace Lidarr.Api.V1.TrackFiles
foreach (var trackFile in trackFiles)
{
var fullPath = Path.Combine(artist.Path, trackFile.RelativePath);
_mediaFileDeletionService.DeleteTrackFile(artist, trackFile);
}
@ -173,6 +170,12 @@ namespace Lidarr.Api.V1.TrackFiles
public void Handle(TrackFileAddedEvent message)
{
// don't process files that are added but not matched
if (message.TrackFile.AlbumId == 0)
{
return;
}
BroadcastResourceChange(ModelAction.Updated, message.TrackFile.ToResource(message.TrackFile.Artist.Value, _upgradableSpecification));
}

@ -1,10 +1,10 @@
using System;
using System.IO;
using NzbDrone.Core.DecisionEngine.Specifications;
using NzbDrone.Core.Languages;
using NzbDrone.Core.MediaFiles;
using NzbDrone.Core.Qualities;
using Lidarr.Http.REST;
using NzbDrone.Common.Extensions;
namespace Lidarr.Api.V1.TrackFiles
{
@ -38,8 +38,8 @@ namespace Lidarr.Api.V1.TrackFiles
ArtistId = model.Artist.Value.Id,
AlbumId = model.AlbumId,
RelativePath = model.RelativePath,
//Path
RelativePath = model.Artist.Value.Path.GetRelativePath(model.Path),
Path = model.Path,
Size = model.Size,
DateAdded = model.DateAdded,
// SceneName = model.SceneName,
@ -61,8 +61,8 @@ namespace Lidarr.Api.V1.TrackFiles
ArtistId = artist.Id,
AlbumId = model.AlbumId,
RelativePath = model.RelativePath,
Path = Path.Combine(artist.Path, model.RelativePath),
Path = model.Path,
RelativePath = artist.Path.GetRelativePath(model.Path),
Size = model.Size,
DateAdded = model.DateAdded,
//SceneName = model.SceneName,

@ -1,5 +1,6 @@
using System.Collections.Generic;
using System.IO;
using System.IO.Abstractions;
using System.Linq;
using FluentAssertions;
using Moq;
@ -15,7 +16,7 @@ namespace NzbDrone.Common.Test.DiskTests
private const string RECYCLING_BIN = "$Recycle.Bin";
private const string SYSTEM_VOLUME_INFORMATION = "System Volume Information";
private const string WINDOWS = "Windows";
private List<DirectoryInfo> _folders;
private List<IDirectoryInfo> _folders;
private void SetupFolders(string root)
{
@ -36,7 +37,7 @@ namespace NzbDrone.Common.Test.DiskTests
WINDOWS
};
_folders = folders.Select(f => new DirectoryInfo(Path.Combine(root, f))).ToList();
_folders = folders.Select(f => (DirectoryInfoBase)new DirectoryInfo(Path.Combine(root, f))).ToList<IDirectoryInfo>();
}
[Test]

@ -1,5 +1,6 @@
using System;
using System.IO;
using System.IO.Abstractions;
using FluentAssertions;
using NUnit.Framework;
using NzbDrone.Common.Disk;

@ -1,6 +1,7 @@
using System;
using System.Collections.Generic;
using System.IO;
using System.IO.Abstractions;
using System.Linq;
using FluentAssertions;
using Moq;
@ -981,48 +982,50 @@ namespace NzbDrone.Common.Test.DiskTests
// Note: never returns anything.
Mocker.GetMock<IDiskProvider>()
.Setup(v => v.GetDirectoryInfos(It.IsAny<string>()))
.Returns(new List<DirectoryInfo>());
.Setup(v => v.GetDirectoryInfos(It.IsAny<string>()))
.Returns(new List<IDirectoryInfo>());
// Note: never returns anything.
Mocker.GetMock<IDiskProvider>()
.Setup(v => v.GetFileInfos(It.IsAny<string>()))
.Returns(new List<FileInfo>());
.Setup(v => v.GetFileInfos(It.IsAny<string>(), It.IsAny<SearchOption>()))
.Returns(new List<IFileInfo>());
}
private void WithRealDiskProvider()
{
IFileSystem _fileSystem = new FileSystem();
Mocker.GetMock<IDiskProvider>()
.Setup(v => v.FolderExists(It.IsAny<string>()))
.Returns<string>(v => Directory.Exists(v));
.Returns<string>(v => _fileSystem.Directory.Exists(v));
Mocker.GetMock<IDiskProvider>()
.Setup(v => v.FileExists(It.IsAny<string>()))
.Returns<string>(v => File.Exists(v));
.Returns<string>(v => _fileSystem.File.Exists(v));
Mocker.GetMock<IDiskProvider>()
.Setup(v => v.CreateFolder(It.IsAny<string>()))
.Callback<string>(v => Directory.CreateDirectory(v));
.Callback<string>(v => _fileSystem.Directory.CreateDirectory(v));
Mocker.GetMock<IDiskProvider>()
.Setup(v => v.DeleteFolder(It.IsAny<string>(), It.IsAny<bool>()))
.Callback<string, bool>((v, r) => Directory.Delete(v, r));
.Callback<string, bool>((v, r) => _fileSystem.Directory.Delete(v, r));
Mocker.GetMock<IDiskProvider>()
.Setup(v => v.DeleteFile(It.IsAny<string>()))
.Callback<string>(v => File.Delete(v));
.Callback<string>(v => _fileSystem.File.Delete(v));
Mocker.GetMock<IDiskProvider>()
.Setup(v => v.GetDirectoryInfos(It.IsAny<string>()))
.Returns<string>(v => new DirectoryInfo(v).GetDirectories().ToList());
.Returns<string>(v => _fileSystem.DirectoryInfo.FromDirectoryName(v).GetDirectories().ToList());
Mocker.GetMock<IDiskProvider>()
.Setup(v => v.GetFileInfos(It.IsAny<string>()))
.Returns<string>(v => new DirectoryInfo(v).GetFiles().ToList());
.Setup(v => v.GetFileInfos(It.IsAny<string>(), It.IsAny<SearchOption>()))
.Returns((string v, SearchOption option) => _fileSystem.DirectoryInfo.FromDirectoryName(v).GetFiles("*", option).ToList());
Mocker.GetMock<IDiskProvider>()
.Setup(v => v.GetFileSize(It.IsAny<string>()))
.Returns<string>(v => new FileInfo(v).Length);
.Returns<string>(v => _fileSystem.FileInfo.FromFileName(v).Length);
Mocker.GetMock<IDiskProvider>()
.Setup(v => v.TryCreateHardLink(It.IsAny<string>(), It.IsAny<string>()))
@ -1030,13 +1033,13 @@ namespace NzbDrone.Common.Test.DiskTests
Mocker.GetMock<IDiskProvider>()
.Setup(v => v.CopyFile(It.IsAny<string>(), It.IsAny<string>(), It.IsAny<bool>()))
.Callback<string, string, bool>((s, d, o) => File.Copy(s, d, o));
.Callback<string, string, bool>((s, d, o) => _fileSystem.File.Copy(s, d, o));
Mocker.GetMock<IDiskProvider>()
.Setup(v => v.MoveFile(It.IsAny<string>(), It.IsAny<string>(), It.IsAny<bool>()))
.Callback<string, string, bool>((s, d, o) => {
if (File.Exists(d) && o) File.Delete(d);
File.Move(s, d);
if (_fileSystem.File.Exists(d) && o) _fileSystem.File.Delete(d);
_fileSystem.File.Move(s, d);
});
Mocker.GetMock<IDiskProvider>()

@ -55,6 +55,12 @@
<Reference Include="nunit.framework, Version=3.11.0.0, Culture=neutral, PublicKeyToken=2638cd05610744eb, processorArchitecture=MSIL">
<HintPath>..\packages\NUnit.3.11.0\lib\net45\nunit.framework.dll</HintPath>
</Reference>
<Reference Include="System.IO.Abstractions, Version=4.0.0.0, Culture=neutral, processorArchitecture=MSIL">
<HintPath>..\packages\System.IO.Abstractions.4.0.11\lib\net40\System.IO.Abstractions.dll</HintPath>
</Reference>
<Reference Include="Microsoft.Practices.Unity, Version=2.1.505.0, Culture=neutral, PublicKeyToken=31bf3856ad364e35, processorArchitecture=MSIL">
<HintPath>..\packages\Unity.2.1.505.2\lib\NET35\Microsoft.Practices.Unity.dll</HintPath>
</Reference>
<Reference Include="System" />
<Reference Include="System.Core" />
<Reference Include="System.Data" />

@ -43,6 +43,7 @@ namespace NzbDrone.Common.Test
result.Should().Be(clean);
}
[TestCase(@"/", @"/")]
[TestCase(@"/test/", @"/test")]
[TestCase(@"//test/", @"/test")]
[TestCase(@"//test//", @"/test")]

@ -4,4 +4,6 @@
<package id="Moq" version="4.0.10827" targetFramework="net461" />
<package id="NLog" version="4.5.4" targetFramework="net461" />
<package id="NUnit" version="3.11.0" targetFramework="net461" />
</packages>
<package id="Unity" version="2.1.505.2" targetFramework="net461" />
<package id="System.IO.Abstractions" version="4.0.11" targetFramework="net461" />
</packages>

@ -1,6 +1,7 @@
using System;
using System.Collections.Generic;
using System.IO;
using System.IO.Abstractions;
using System.Linq;
using System.Security.AccessControl;
using System.Security.Principal;
@ -15,6 +16,12 @@ namespace NzbDrone.Common.Disk
public abstract class DiskProviderBase : IDiskProvider
{
private static readonly Logger Logger = NzbDroneLogger.GetLogger(typeof(DiskProviderBase));
protected readonly IFileSystem _fileSystem;
public DiskProviderBase(IFileSystem fileSystem)
{
_fileSystem = fileSystem;
}
public static StringComparison PathStringComparison
{
@ -38,7 +45,7 @@ namespace NzbDrone.Common.Disk
{
CheckFolderExists(path);
return new DirectoryInfo(path).CreationTimeUtc;
return _fileSystem.DirectoryInfo.FromDirectoryName(path).CreationTimeUtc;
}
public DateTime FolderGetLastWrite(string path)
@ -49,17 +56,17 @@ namespace NzbDrone.Common.Disk
if (!dirFiles.Any())
{
return new DirectoryInfo(path).LastWriteTimeUtc;
return _fileSystem.DirectoryInfo.FromDirectoryName(path).LastWriteTimeUtc;
}
return dirFiles.Select(f => new FileInfo(f)).Max(c => c.LastWriteTimeUtc);
return dirFiles.Select(f => _fileSystem.FileInfo.FromFileName(f)).Max(c => c.LastWriteTimeUtc);
}
public DateTime FileGetLastWrite(string path)
{
CheckFileExists(path);
return new FileInfo(path).LastWriteTimeUtc;
return _fileSystem.FileInfo.FromFileName(path).LastWriteTimeUtc;
}
private void CheckFolderExists(string path)
@ -93,7 +100,7 @@ namespace NzbDrone.Common.Disk
public bool FolderExists(string path)
{
Ensure.That(path, () => path).IsValidPath();
return Directory.Exists(path);
return _fileSystem.Directory.Exists(path);
}
public bool FileExists(string path)
@ -112,11 +119,11 @@ namespace NzbDrone.Common.Disk
case StringComparison.InvariantCulture:
case StringComparison.Ordinal:
{
return File.Exists(path) && path == path.GetActualCasing();
return _fileSystem.File.Exists(path) && path == path.GetActualCasing();
}
default:
{
return File.Exists(path);
return _fileSystem.File.Exists(path);
}
}
}
@ -129,8 +136,8 @@ namespace NzbDrone.Common.Disk
{
var testPath = Path.Combine(path, "lidarr_write_test.txt");
var testContent = $"This file was created to verify if '{path}' is writable. It should've been automatically deleted. Feel free to delete it.";
File.WriteAllText(testPath, testContent);
File.Delete(testPath);
_fileSystem.File.WriteAllText(testPath, testContent);
_fileSystem.File.Delete(testPath);
return true;
}
catch (Exception e)
@ -144,21 +151,21 @@ namespace NzbDrone.Common.Disk
{
Ensure.That(path, () => path).IsValidPath();
return Directory.GetDirectories(path);
return _fileSystem.Directory.GetDirectories(path);
}
public string[] GetFiles(string path, SearchOption searchOption)
{
Ensure.That(path, () => path).IsValidPath();
return Directory.GetFiles(path, "*.*", searchOption);
return _fileSystem.Directory.GetFiles(path, "*.*", searchOption);
}
public long GetFolderSize(string path)
{
Ensure.That(path, () => path).IsValidPath();
return GetFiles(path, SearchOption.AllDirectories).Sum(e => new FileInfo(e).Length);
return GetFiles(path, SearchOption.AllDirectories).Sum(e => _fileSystem.FileInfo.FromFileName(e).Length);
}
public long GetFileSize(string path)
@ -170,14 +177,14 @@ namespace NzbDrone.Common.Disk
throw new FileNotFoundException("File doesn't exist: " + path);
}
var fi = new FileInfo(path);
var fi = _fileSystem.FileInfo.FromFileName(path);
return fi.Length;
}
public void CreateFolder(string path)
{
Ensure.That(path, () => path).IsValidPath();
Directory.CreateDirectory(path);
_fileSystem.Directory.CreateDirectory(path);
}
public void DeleteFile(string path)
@ -187,7 +194,7 @@ namespace NzbDrone.Common.Disk
RemoveReadOnly(path);
File.Delete(path);
_fileSystem.File.Delete(path);
}
public void CopyFile(string source, string destination, bool overwrite = false)
@ -205,7 +212,7 @@ namespace NzbDrone.Common.Disk
protected virtual void CopyFileInternal(string source, string destination, bool overwrite = false)
{
File.Copy(source, destination, overwrite);
_fileSystem.File.Copy(source, destination, overwrite);
}
public void MoveFile(string source, string destination, bool overwrite = false)
@ -237,7 +244,7 @@ namespace NzbDrone.Common.Disk
protected virtual void MoveFileInternal(string source, string destination)
{
File.Move(source, destination);
_fileSystem.File.Move(source, destination);
}
public abstract bool TryCreateHardLink(string source, string destination);
@ -246,45 +253,45 @@ namespace NzbDrone.Common.Disk
{
Ensure.That(path, () => path).IsValidPath();
var files = Directory.GetFiles(path, "*.*", recursive ? SearchOption.AllDirectories : SearchOption.TopDirectoryOnly);
var files = _fileSystem.Directory.GetFiles(path, "*.*", recursive ? SearchOption.AllDirectories : SearchOption.TopDirectoryOnly);
Array.ForEach(files, RemoveReadOnly);
Directory.Delete(path, recursive);
_fileSystem.Directory.Delete(path, recursive);
}
public string ReadAllText(string filePath)
{
Ensure.That(filePath, () => filePath).IsValidPath();
return File.ReadAllText(filePath);
return _fileSystem.File.ReadAllText(filePath);
}
public void WriteAllText(string filename, string contents)
{
Ensure.That(filename, () => filename).IsValidPath();
RemoveReadOnly(filename);
File.WriteAllText(filename, contents);
_fileSystem.File.WriteAllText(filename, contents);
}
public void FolderSetLastWriteTime(string path, DateTime dateTime)
{
Ensure.That(path, () => path).IsValidPath();
Directory.SetLastWriteTimeUtc(path, dateTime);
_fileSystem.Directory.SetLastWriteTimeUtc(path, dateTime);
}
public void FileSetLastWriteTime(string path, DateTime dateTime)
{
Ensure.That(path, () => path).IsValidPath();
File.SetLastWriteTime(path, dateTime);
_fileSystem.File.SetLastWriteTime(path, dateTime);
}
public bool IsFileLocked(string file)
{
try
{
using (File.Open(file, FileMode.Open, FileAccess.Read, FileShare.None))
using (_fileSystem.File.Open(file, FileMode.Open, FileAccess.Read, FileShare.None))
{
return false;
}
@ -306,7 +313,7 @@ namespace NzbDrone.Common.Disk
{
Ensure.That(path, () => path).IsValidPath();
var parent = Directory.GetParent(path.TrimEnd(Path.DirectorySeparatorChar));
var parent = _fileSystem.Directory.GetParent(path.TrimEnd(Path.DirectorySeparatorChar));
if (parent == null)
{
@ -322,7 +329,7 @@ namespace NzbDrone.Common.Disk
{
var sid = new SecurityIdentifier(accountSid, null);
var directoryInfo = new DirectoryInfo(filename);
var directoryInfo = _fileSystem.DirectoryInfo.FromDirectoryName(filename);
var directorySecurity = directoryInfo.GetAccessControl(AccessControlSections.Access);
var rules = directorySecurity.GetAccessRules(true, false, typeof(SecurityIdentifier));
@ -368,7 +375,7 @@ namespace NzbDrone.Common.Disk
public FileAttributes GetFileAttributes(string path)
{
return File.GetAttributes(path);
return _fileSystem.File.GetAttributes(path);
}
public void EmptyFolder(string path)
@ -410,12 +417,12 @@ namespace NzbDrone.Common.Disk
throw new FileNotFoundException("Unable to find file: " + path, path);
}
return new FileStream(path, FileMode.Open, FileAccess.Read);
return (FileStream) _fileSystem.FileStream.Create(path, FileMode.Open, FileAccess.Read);
}
public FileStream OpenWriteStream(string path)
{
return new FileStream(path, FileMode.Create);
return (FileStream) _fileSystem.FileStream.Create(path, FileMode.Create);
}
public List<IMount> GetMounts()
@ -454,29 +461,41 @@ namespace NzbDrone.Common.Disk
}
}
protected List<DriveInfo> GetDriveInfoMounts()
protected List<IDriveInfo> GetDriveInfoMounts()
{
return DriveInfo.GetDrives()
.Where(d => d.IsReady)
.ToList();
return _fileSystem.DriveInfo.GetDrives()
.Where(d => d.IsReady)
.ToList();
}
public List<DirectoryInfo> GetDirectoryInfos(string path)
public List<IDirectoryInfo> GetDirectoryInfos(string path)
{
Ensure.That(path, () => path).IsValidPath();
var di = new DirectoryInfo(path);
var di = _fileSystem.DirectoryInfo.FromDirectoryName(path);
return di.GetDirectories().ToList();
}
public List<FileInfo> GetFileInfos(string path)
public IDirectoryInfo GetDirectoryInfo(string path)
{
Ensure.That(path, () => path).IsValidPath();
return _fileSystem.DirectoryInfo.FromDirectoryName(path);
}
var di = new DirectoryInfo(path);
public List<IFileInfo> GetFileInfos(string path, SearchOption searchOption = SearchOption.TopDirectoryOnly)
{
Ensure.That(path, () => path).IsValidPath();
return di.GetFiles().ToList();
var di = _fileSystem.DirectoryInfo.FromDirectoryName(path);
return di.GetFiles("*", searchOption).ToList();
}
public IFileInfo GetFileInfo(string path)
{
Ensure.That(path, () => path).IsValidPath();
return _fileSystem.FileInfo.FromFileName(path);
}
public void RemoveEmptySubfolders(string path)

@ -1,5 +1,6 @@
using System;
using System.IO;
using System.IO.Abstractions;
using System.Linq;
using System.Threading;
using NLog;
@ -594,7 +595,7 @@ namespace NzbDrone.Common.Disk
}
}
private bool ShouldIgnore(DirectoryInfo folder)
private bool ShouldIgnore(IDirectoryInfo folder)
{
if (folder.Name.StartsWith(".nfs"))
{
@ -605,7 +606,7 @@ namespace NzbDrone.Common.Disk
return false;
}
private bool ShouldIgnore(FileInfo file)
private bool ShouldIgnore(IFileInfo file)
{
if (file.Name.StartsWith(".nfs") || file.Name == "debug.log" || file.Name.EndsWith(".socket"))
{

@ -1,15 +1,16 @@
using System.Collections.Generic;
using System.IO;
using System.IO.Abstractions;
using NzbDrone.Common.Extensions;
namespace NzbDrone.Common.Disk
{
public class DriveInfoMount : IMount
{
private readonly DriveInfo _driveInfo;
private readonly IDriveInfo _driveInfo;
private readonly DriveType _driveType;
public DriveInfoMount(DriveInfo driveInfo, DriveType driveType = DriveType.Unknown, MountOptions mountOptions = null)
public DriveInfoMount(IDriveInfo driveInfo, DriveType driveType = DriveType.Unknown, MountOptions mountOptions = null)
{
_driveInfo = driveInfo;
_driveType = driveType;

@ -1,6 +1,7 @@
using System;
using System.Collections.Generic;
using System.IO;
using System.IO.Abstractions;
using System.Security.AccessControl;
using System.Security.Principal;
@ -46,8 +47,10 @@ namespace NzbDrone.Common.Disk
FileStream OpenWriteStream(string path);
List<IMount> GetMounts();
IMount GetMount(string path);
List<DirectoryInfo> GetDirectoryInfos(string path);
List<FileInfo> GetFileInfos(string path);
IDirectoryInfo GetDirectoryInfo(string path);
List<IDirectoryInfo> GetDirectoryInfos(string path);
IFileInfo GetFileInfo(string path);
List<IFileInfo> GetFileInfos(string path, SearchOption searchOption = SearchOption.TopDirectoryOnly);
void RemoveEmptySubfolders(string path);
void SaveStream(Stream stream, string path);
}

@ -38,6 +38,11 @@ namespace NzbDrone.Common.Extensions
return info.FullName.TrimEnd('/', '\\', ' ');
}
if (OsInfo.IsNotWindows && info.FullName.TrimEnd('/').Length == 0)
{
return "/";
}
return info.FullName.TrimEnd('/').Trim('\\', ' ');
}

@ -66,6 +66,9 @@
<Reference Include="System.Collections.Immutable, Version=1.2.3.0, Culture=neutral, processorArchitecture=MSIL">
<HintPath>..\packages\System.Collections.Immutable.1.5.0\lib\netstandard2.0\System.Collections.Immutable.dll</HintPath>
</Reference>
<Reference Include="System.IO.Abstractions, Version=4.0.0.0, Culture=neutral, processorArchitecture=MSIL">
<HintPath>..\packages\System.IO.Abstractions.4.0.11\lib\net40\System.IO.Abstractions.dll</HintPath>
</Reference>
<Reference Include="SocksWebProxy, Version=1.3.4.0, Culture=neutral, processorArchitecture=MSIL">
<HintPath>..\packages\DotNet4.SocksProxy.1.3.4.0\lib\net40\SocksWebProxy.dll</HintPath>
<Private>True</Private>

@ -8,4 +8,5 @@
<package id="Sentry.PlatformAbstractions" version="1.0.0" targetFramework="net461" />
<package id="Sentry.Protocol" version="1.0.3" targetFramework="net461" />
<package id="System.Collections.Immutable" version="1.5.0" targetFramework="net461" />
<package id="System.IO.Abstractions" version="4.0.11" targetFramework="net461" />
</packages>

@ -0,0 +1,321 @@
using NUnit.Framework;
using NzbDrone.Core.Datastore.Migration;
using NzbDrone.Core.Test.Framework;
using System.Collections.Generic;
using System;
using NzbDrone.Core.Qualities;
using NzbDrone.Common.Serializer;
using NzbDrone.Test.Common;
using System.Linq;
using FluentAssertions;
using System.IO;
namespace NzbDrone.Core.Test.Datastore.Migration
{
[TestFixture]
public class add_mediafilerepository_mtimeFixture : MigrationTest<add_mediafilerepository_mtime>
{
private string _artistPath = null;
private void GivenArtist(add_mediafilerepository_mtime c, int id, string name)
{
_artistPath = $"/mnt/data/path/{name}".AsOsAgnostic();
c.Insert.IntoTable("Artists").Row(new
{
Id = id,
CleanName = name,
Path = _artistPath,
Monitored = 1,
AlbumFolder = 1,
LanguageProfileId = 1,
MetadataProfileId = 1,
ArtistMetadataId = id
});
}
private void GivenAlbum(add_mediafilerepository_mtime c, int id, int artistMetadataId, string title)
{
c.Insert.IntoTable("Albums").Row(new
{
Id = id,
ForeignAlbumId = id.ToString(),
ArtistMetadataId = artistMetadataId,
Title = title,
CleanTitle = title,
Images = "",
Monitored = 1,
AlbumType = "Studio",
AnyReleaseOk = 1
});
}
private void GivenAlbumRelease(add_mediafilerepository_mtime c, int id, int albumId, bool monitored)
{
c.Insert.IntoTable("AlbumReleases").Row(new
{
Id = id,
ForeignReleaseId = id.ToString(),
AlbumId = albumId,
Title = "Title",
Status = "Status",
Duration = 0,
Monitored = monitored
});
}
private void GivenTrackFiles(add_mediafilerepository_mtime c, List<string> tracks, int albumReleaseId, int albumId, int firstId = 1, bool addTracks = true)
{
int id = firstId;
foreach (var track in tracks)
{
c.Insert.IntoTable("TrackFiles").Row(new
{
Id = id,
RelativePath = track?.AsOsAgnostic(),
Size = 100,
DateAdded = DateTime.UtcNow,
Quality = new QualityModel(Quality.FLAC).ToJson(),
Language = 1,
AlbumId = albumId
});
if (addTracks)
{
c.Insert.IntoTable("Tracks").Row(new
{
Id = id,
ForeignTrackId = id.ToString(),
Explicit = 0,
TrackFileId = id,
Duration = 100,
MediumNumber = 1,
AbsoluteTrackNumber = 1,
ForeignRecordingId = id.ToString(),
AlbumReleaseId = albumReleaseId,
ArtistMetadataId = 0
});
}
id++;
}
}
private void VerifyTracksFiles(IDirectDataMapper db, int albumId, List<string> expectedPaths)
{
var tracks = db.Query("SELECT TrackFiles.* FROM TrackFiles " +
"WHERE TrackFiles.AlbumId = " + albumId);
TestLogger.Debug($"Got {tracks.Count} tracks");
tracks.Select(x => x["Path"]).Should().BeEquivalentTo(expectedPaths);
}
[Test]
public void migration_030_simple_case()
{
var tracks = new List<string> {
"folder/track1.mp3",
"folder/track2.mp3",
};
var db = WithMigrationTestDb(c => {
GivenArtist(c, 1, "TestArtist");
GivenAlbum(c, 1, 1, "TestAlbum");
GivenAlbumRelease(c, 1, 1, true);
GivenTrackFiles(c, tracks, 1, 1);
});
var expected = tracks.Select(x => Path.Combine(_artistPath, x)).ToList();
VerifyTracksFiles(db, 1, expected);
}
[Test]
public void migration_030_missing_path()
{
var tracks = new List<string> {
"folder/track1.mp3",
null,
};
var db = WithMigrationTestDb(c => {
GivenArtist(c, 1, "TestArtist");
GivenAlbum(c, 1, 1, "TestAlbum");
GivenAlbumRelease(c, 1, 1, true);
GivenTrackFiles(c, tracks, 1, 1);
});
var expected = tracks.GetRange(0, 1).Select(x => Path.Combine(_artistPath, x)).ToList();
VerifyTracksFiles(db, 1, expected);
}
[Test]
public void migration_030_bad_albumrelease_id()
{
var tracks = new List<string> {
"folder/track1.mp3",
"folder/track2.mp3"
};
var db = WithMigrationTestDb(c => {
GivenArtist(c, 1, "TestArtist");
GivenAlbum(c, 1, 1, "TestAlbum");
GivenAlbumRelease(c, 1, 1, true);
GivenTrackFiles(c, tracks, 2, 1);
});
VerifyTracksFiles(db, 1, new List<string>());
}
[Test]
public void migration_030_bad_album_id()
{
var tracks = new List<string> {
"folder/track1.mp3",
"folder/track2.mp3"
};
var db = WithMigrationTestDb(c => {
GivenArtist(c, 1, "TestArtist");
GivenAlbum(c, 1, 1, "TestAlbum");
GivenAlbumRelease(c, 1, 1, true);
GivenTrackFiles(c, tracks, 1, 2);
});
VerifyTracksFiles(db, 1, new List<string>());
}
[Test]
public void migration_030_bad_artist_metadata_id()
{
var tracks = new List<string> {
"folder/track1.mp3",
"folder/track2.mp3"
};
var db = WithMigrationTestDb(c => {
GivenArtist(c, 1, "TestArtist");
GivenAlbum(c, 1, 2, "TestAlbum");
GivenAlbumRelease(c, 1, 1, true);
GivenTrackFiles(c, tracks, 1, 1);
});
VerifyTracksFiles(db, 1, new List<string>());
}
[Test]
public void migration_030_missing_artist()
{
var tracks = new List<string> {
"folder/track1.mp3",
"folder/track2.mp3"
};
var db = WithMigrationTestDb(c => {
GivenAlbum(c, 1, 1, "TestAlbum");
GivenAlbumRelease(c, 1, 1, true);
GivenTrackFiles(c, tracks, 1, 1);
});
VerifyTracksFiles(db, 1, new List<string>());
}
[Test]
public void migration_030_missing_tracks()
{
var tracks = new List<string> {
"folder/track1.mp3",
"folder/track2.mp3"
};
var db = WithMigrationTestDb(c => {
GivenArtist(c, 1, "TestArtist");
GivenAlbum(c, 1, 1, "TestAlbum");
GivenAlbumRelease(c, 1, 1, true);
GivenTrackFiles(c, tracks, 1, 1, addTracks: false);
});
VerifyTracksFiles(db, 1, new List<string>());
}
[Test]
public void migration_030_duplicate_files()
{
var tracks = new List<string> {
"folder/track1.mp3",
"folder/track2.mp3",
"folder/track1.mp3",
};
var db = WithMigrationTestDb(c => {
GivenArtist(c, 1, "TestArtist");
GivenAlbum(c, 1, 1, "TestAlbum");
GivenAlbumRelease(c, 1, 1, true);
GivenTrackFiles(c, tracks, 1, 1);
});
var expected = tracks.GetRange(0, 2).Select(x => Path.Combine(_artistPath, x)).ToList();
VerifyTracksFiles(db, 1, expected);
}
[Test]
public void migration_030_unmonitored_release_duplicate()
{
var monitored_tracks = new List<string> {
"folder/track1.mp3",
"folder/track2.mp3",
};
var unmonitored_tracks = new List<string> {
"folder/track1.mp3",
"folder/track2.mp3",
};
var db = WithMigrationTestDb(c => {
GivenArtist(c, 1, "TestArtist");
GivenAlbum(c, 1, 1, "TestAlbum");
GivenAlbumRelease(c, 1, 1, true);
GivenTrackFiles(c, monitored_tracks, 1, 1);
GivenAlbumRelease(c, 2, 1, false);
GivenTrackFiles(c, unmonitored_tracks, 2, 1, firstId: 100);
});
var expected = monitored_tracks.Select(x => Path.Combine(_artistPath, x)).ToList();
VerifyTracksFiles(db, 1, expected);
}
[Test]
public void migration_030_unmonitored_release_distinct()
{
var monitored_tracks = new List<string> {
"folder/track1.mp3",
"folder/track2.mp3",
};
var unmonitored_tracks = new List<string> {
"folder/track3.mp3",
"folder/track4.mp3",
};
var db = WithMigrationTestDb(c => {
GivenArtist(c, 1, "TestArtist");
GivenAlbum(c, 1, 1, "TestAlbum");
GivenAlbumRelease(c, 1, 1, true);
GivenTrackFiles(c, monitored_tracks, 1, 1);
GivenAlbumRelease(c, 2, 1, false);
GivenTrackFiles(c, unmonitored_tracks, 2, 1, firstId: 100);
});
var expected = monitored_tracks.Select(x => Path.Combine(_artistPath, x)).ToList();
VerifyTracksFiles(db, 1, expected);
}
}
}

@ -34,7 +34,7 @@ namespace NzbDrone.Core.Test.DecisionEngineTests.RssSync
_firstFile =
new TrackFile{
Id = 1,
RelativePath = "My.Artist.S01E01.mp3",
Path = "/My.Artist.S01E01.mp3",
Quality = new QualityModel(Quality.FLAC, new Revision(version: 1)),
DateAdded = DateTime.Now,
AlbumId = 1
@ -43,7 +43,7 @@ namespace NzbDrone.Core.Test.DecisionEngineTests.RssSync
_secondFile =
new TrackFile{
Id = 2,
RelativePath = "My.Artist.S01E02.mp3",
Path = "/My.Artist.S01E02.mp3",
Quality = new QualityModel(Quality.FLAC, new Revision(version: 1)),
DateAdded = DateTime.Now,
AlbumId = 2
@ -97,7 +97,7 @@ namespace NzbDrone.Core.Test.DecisionEngineTests.RssSync
private void WithExistingFile(TrackFile trackFile)
{
var path = Path.Combine(@"C:\Music\My.Artist".AsOsAgnostic(), trackFile.RelativePath);
var path = trackFile.Path;
Mocker.GetMock<IDiskProvider>()
.Setup(v => v.FileExists(path))

@ -1,6 +1,7 @@
using System;
using System.Collections.Generic;
using System.IO;
using System.IO.Abstractions;
using System.Linq;
using System.Threading;
using FluentAssertions;
@ -37,7 +38,10 @@ namespace NzbDrone.Core.Test.Download.DownloadClientTests.Blackhole
.Returns(1000000);
Mocker.GetMock<IDiskScanService>().Setup(c => c.FilterFiles(It.IsAny<string>(), It.IsAny<IEnumerable<string>>()))
.Returns<string, IEnumerable<string>>((b, s) => s.ToList());
.Returns<string, IEnumerable<string>>((b, s) => s.ToList());
Mocker.GetMock<IDiskScanService>().Setup(c => c.FilterFiles(It.IsAny<string>(), It.IsAny<IEnumerable<IFileInfo>>()))
.Returns<string, IEnumerable<IFileInfo>>((b, s) => s.ToList());
}
protected void GivenChangedItem()

@ -1,6 +1,7 @@
using System;
using System.Collections.Generic;
using System.IO;
using System.IO.Abstractions;
using System.Linq;
using System.Net;
using FluentAssertions;
@ -52,6 +53,9 @@ namespace NzbDrone.Core.Test.Download.DownloadClientTests.Blackhole
Mocker.GetMock<IDiskScanService>().Setup(c => c.FilterFiles(It.IsAny<string>(), It.IsAny<IEnumerable<string>>()))
.Returns<string, IEnumerable<string>>((b, s) => s.ToList());
Mocker.GetMock<IDiskScanService>().Setup(c => c.FilterFiles(It.IsAny<string>(), It.IsAny<IEnumerable<IFileInfo>>()))
.Returns<string, IEnumerable<IFileInfo>>((b, s) => s.ToList());
}
protected void GivenFailedDownload()

@ -2,6 +2,7 @@
using System;
using System.Collections.Generic;
using System.IO;
using System.IO.Abstractions;
using System.Linq;
using System.Net;
using FluentAssertions;
@ -46,6 +47,9 @@ namespace NzbDrone.Core.Test.Download.DownloadClientTests.Blackhole
Mocker.GetMock<IDiskScanService>().Setup(c => c.FilterFiles(It.IsAny<string>(), It.IsAny<IEnumerable<string>>()))
.Returns<string, IEnumerable<string>>((b, s) => s.ToList());
Mocker.GetMock<IDiskScanService>().Setup(c => c.FilterFiles(It.IsAny<string>(), It.IsAny<IEnumerable<IFileInfo>>()))
.Returns<string, IEnumerable<IFileInfo>>((b, s) => s.ToList());
}
protected void GivenFailedDownload()

@ -88,7 +88,7 @@ namespace NzbDrone.Core.Test.Framework
value = dataRow.ItemArray[i];
}
item[columnName] = dataRow.ItemArray[i];
item[columnName] = value;
}
return item;

@ -0,0 +1,22 @@
using NUnit.Framework;
using System.IO.Abstractions.TestingHelpers;
using Microsoft.Practices.Unity;
using NzbDrone.Common.Disk;
namespace NzbDrone.Core.Test.Framework
{
public abstract class FileSystemTest<TSubject> : CoreTest<TSubject> where TSubject : class
{
protected MockFileSystem FileSystem { get; private set; }
protected IDiskProvider DiskProvider { get; private set; }
[SetUp]
public void FileSystemTestSetup()
{
FileSystem = new MockFileSystem();
DiskProvider = Mocker.Resolve<IDiskProvider>("ActualDiskProvider", new ResolverOverride[] {
new ParameterOverride("fileSystem", FileSystem)
});
}
}
}

@ -14,24 +14,26 @@ namespace NzbDrone.Core.Test.Housekeeping.Housekeepers
public class CleanupOrphanedTrackFilesFixture : DbTest<CleanupOrphanedTrackFiles, TrackFile>
{
[Test]
public void should_delete_orphaned_track_files()
public void should_unlink_orphaned_track_files()
{
var trackFile = Builder<TrackFile>.CreateNew()
.With(h => h.Quality = new QualityModel())
.BuildNew();
.With(h => h.Quality = new QualityModel())
.With(h => h.AlbumId = 1)
.BuildNew();
Db.Insert(trackFile);
Subject.Clean();
AllStoredModels.Should().BeEmpty();
AllStoredModels[0].AlbumId.Should().Be(0);
}
[Test]
public void should_not_delete_unorphaned_track_files()
public void should_not_unlink_unorphaned_track_files()
{
var trackFiles = Builder<TrackFile>.CreateListOfSize(2)
.All()
.With(h => h.Quality = new QualityModel())
.BuildListOfNew();
.All()
.With(h => h.Quality = new QualityModel())
.With(h => h.AlbumId = 1)
.BuildListOfNew();
Db.InsertMany(trackFiles);
@ -42,7 +44,8 @@ namespace NzbDrone.Core.Test.Housekeeping.Housekeepers
Db.Insert(track);
Subject.Clean();
AllStoredModels.Should().HaveCount(1);
AllStoredModels.Where(x => x.AlbumId == 1).Should().HaveCount(1);
Db.All<Track>().Should().Contain(e => e.TrackFileId == AllStoredModels.First().Id);
}
}

@ -119,11 +119,11 @@ namespace NzbDrone.Core.Test.Instrumentation
public void null_string_as_arg_should_not_fail()
{
var epFile = new TrackFile();
_logger.Debug("File {0} no longer exists on disk. removing from database.", epFile.RelativePath);
_logger.Debug("File {0} no longer exists on disk. removing from database.", epFile.Path);
Thread.Sleep(600);
epFile.RelativePath.Should().BeNull();
epFile.Path.Should().BeNull();
}

@ -186,10 +186,7 @@ namespace NzbDrone.Core.Test.MediaFiles.AudioTagServiceFixture
var path = copiedFile;
var track = new TrackFile {
Artist = new Artist {
Path = Path.GetDirectoryName(path)
},
RelativePath = Path.GetFileName(path)
Path = path
};
testTags.Write(path);

@ -1,5 +1,6 @@
using System.Collections.Generic;
using System.IO;
using System.IO.Abstractions;
using System.Linq;
using FizzWare.NBuilder;
using Moq;
@ -13,11 +14,16 @@ using NzbDrone.Core.Music;
using NzbDrone.Core.RootFolders;
using NzbDrone.Test.Common;
using NzbDrone.Core.Parser.Model;
using FluentAssertions;
using System.IO.Abstractions.TestingHelpers;
using NzbDrone.Core.DecisionEngine;
using System;
using NzbDrone.Core.Qualities;
namespace NzbDrone.Core.Test.MediaFiles.DiskScanServiceTests
{
[TestFixture]
public class ScanFixture : CoreTest<DiskScanService>
public class ScanFixture : FileSystemTest<DiskScanService>
{
private Artist _artist;
private string _rootFolder;
@ -34,42 +40,34 @@ namespace NzbDrone.Core.Test.MediaFiles.DiskScanServiceTests
.With(s => s.Path = artistFolder)
.Build();
Mocker.GetMock<IDiskProvider>()
.Setup(s => s.FolderExists(It.IsAny<string>()))
.Returns(false);
Mocker.GetMock<IDiskProvider>()
.Setup(s => s.GetParentFolder(It.IsAny<string>()))
.Returns((string path) => Directory.GetParent(path).FullName);
Mocker.GetMock<IRootFolderService>()
.Setup(s => s.GetBestRootFolderPath(It.IsAny<string>()))
.Returns(_rootFolder);
Mocker.GetMock<IMakeImportDecision>()
.Setup(v => v.GetImportDecisions(It.IsAny<List<string>>(), It.IsAny<Artist>(), It.IsAny<bool>()))
.Setup(v => v.GetImportDecisions(It.IsAny<List<IFileInfo>>(), It.IsAny<Artist>(), It.IsAny<FilterFilesType>(), It.IsAny<bool>()))
.Returns(new List<ImportDecision<LocalTrack>>());
Mocker.GetMock<IMediaFileService>()
.Setup(v => v.GetFilesByArtist(It.IsAny<int>()))
.Returns(new List<TrackFile>());
Mocker.GetMock<IMediaFileService>()
.Setup(v => v.GetFilesWithBasePath(It.IsAny<string>()))
.Returns(new List<TrackFile>());
Mocker.GetMock<IMediaFileService>()
.Setup(v => v.FilterUnchangedFiles(It.IsAny<List<IFileInfo>>(), It.IsAny<Artist>(), It.IsAny<FilterFilesType>()))
.Returns((List<IFileInfo> files, Artist artist) => files);
}
private void GivenRootFolder(params string[] subfolders)
{
Mocker.GetMock<IDiskProvider>()
.Setup(s => s.FolderExists(_rootFolder))
.Returns(true);
Mocker.GetMock<IDiskProvider>()
.Setup(s => s.GetDirectories(_rootFolder))
.Returns(subfolders);
FileSystem.AddDirectory(_rootFolder);
foreach (var folder in subfolders)
{
Mocker.GetMock<IDiskProvider>()
.Setup(s => s.FolderExists(folder))
.Returns(true);
FileSystem.AddDirectory(folder);
}
}
@ -78,11 +76,36 @@ namespace NzbDrone.Core.Test.MediaFiles.DiskScanServiceTests
GivenRootFolder(_artist.Path);
}
private void GivenFiles(IEnumerable<string> files)
private List<IFileInfo> GivenFiles(IEnumerable<string> files, DateTimeOffset? lastWrite = null)
{
Mocker.GetMock<IDiskProvider>()
.Setup(s => s.GetFiles(It.IsAny<string>(), SearchOption.AllDirectories))
.Returns(files.ToArray());
if (lastWrite == null)
{
TestLogger.Debug("Using default lastWrite");
lastWrite = new DateTime(2019, 1, 1, 0, 0, 0, DateTimeKind.Utc);
}
foreach (var file in files)
{
FileSystem.AddFile(file, new MockFileData(string.Empty) { LastWriteTime = lastWrite.Value });
}
return files.Select(x => DiskProvider.GetFileInfo(x)).ToList();
}
private void GivenKnownFiles(IEnumerable<string> files, DateTimeOffset? lastWrite = null)
{
if (lastWrite == null)
{
TestLogger.Debug("Using default lastWrite");
lastWrite = new DateTime(2019, 1, 1, 0, 0, 0, DateTimeKind.Utc);
}
Mocker.GetMock<IMediaFileService>()
.Setup(x => x.GetFilesWithBasePath(_artist.Path))
.Returns(files.Select(x => new TrackFile {
Path = x,
Modified = lastWrite.Value.UtcDateTime
}).ToList());
}
[Test]
@ -115,7 +138,7 @@ namespace NzbDrone.Core.Test.MediaFiles.DiskScanServiceTests
.Verify(v => v.Clean(It.IsAny<Artist>(), It.IsAny<List<string>>()), Times.Never());
Mocker.GetMock<IMakeImportDecision>()
.Verify(v => v.GetImportDecisions(It.IsAny<List<string>>(), _artist, false), Times.Never());
.Verify(v => v.GetImportDecisions(It.IsAny<List<IFileInfo>>(), _artist, FilterFilesType.Known, true), Times.Never());
}
[Test]
@ -129,8 +152,7 @@ namespace NzbDrone.Core.Test.MediaFiles.DiskScanServiceTests
Subject.Scan(_artist);
Mocker.GetMock<IDiskProvider>()
.Verify(v => v.CreateFolder(_artist.Path), Times.Once());
DiskProvider.FolderExists(_artist.Path).Should().BeTrue();
}
[Test]
@ -144,8 +166,7 @@ namespace NzbDrone.Core.Test.MediaFiles.DiskScanServiceTests
Subject.Scan(_artist);
Mocker.GetMock<IDiskProvider>()
.Verify(v => v.CreateFolder(_artist.Path), Times.Never());
DiskProvider.FolderExists(_artist.Path).Should().BeFalse();
}
[Test]
@ -155,14 +176,13 @@ namespace NzbDrone.Core.Test.MediaFiles.DiskScanServiceTests
Subject.Scan(_artist);
Mocker.GetMock<IDiskProvider>()
.Verify(v => v.FolderExists(_artist.Path), Times.Once());
DiskProvider.FolderExists(_artist.Path).Should().BeFalse();
Mocker.GetMock<IMediaFileTableCleanupService>()
.Verify(v => v.Clean(It.IsAny<Artist>(), It.IsAny<List<string>>()), Times.Once());
Mocker.GetMock<IMakeImportDecision>()
.Verify(v => v.GetImportDecisions(It.IsAny<List<string>>(), _artist, false), Times.Never());
.Verify(v => v.GetImportDecisions(It.IsAny<List<IFileInfo>>(), _artist, FilterFilesType.Known, true), Times.Never());
}
[Test]
@ -180,7 +200,7 @@ namespace NzbDrone.Core.Test.MediaFiles.DiskScanServiceTests
.Verify(v => v.Clean(It.IsAny<Artist>(), It.IsAny<List<string>>()), Times.Once());
Mocker.GetMock<IMakeImportDecision>()
.Verify(v => v.GetImportDecisions(It.IsAny<List<string>>(), _artist, false), Times.Never());
.Verify(v => v.GetImportDecisions(It.IsAny<List<IFileInfo>>(), _artist, FilterFilesType.Known, true), Times.Never());
}
[Test]
@ -190,14 +210,14 @@ namespace NzbDrone.Core.Test.MediaFiles.DiskScanServiceTests
GivenFiles(new List<string>
{
Path.Combine(_artist.Path, "file1.flac").AsOsAgnostic(),
Path.Combine(_artist.Path, "s01e01.flac").AsOsAgnostic()
Path.Combine(_artist.Path, "file1.flac"),
Path.Combine(_artist.Path, "s01e01.flac")
});
Subject.Scan(_artist);
Mocker.GetMock<IMakeImportDecision>()
.Verify(v => v.GetImportDecisions(It.Is<List<string>>(l => l.Count == 2), _artist, false), Times.Once());
.Verify(v => v.GetImportDecisions(It.Is<List<IFileInfo>>(l => l.Count == 2), _artist, FilterFilesType.Known, true), Times.Once());
}
[Test]
@ -207,20 +227,17 @@ namespace NzbDrone.Core.Test.MediaFiles.DiskScanServiceTests
GivenFiles(new List<string>
{
Path.Combine(_artist.Path, "EXTRAS", "file1.flac").AsOsAgnostic(),
Path.Combine(_artist.Path, "Extras", "file2.flac").AsOsAgnostic(),
Path.Combine(_artist.Path, "EXTRAs", "file3.flac").AsOsAgnostic(),
Path.Combine(_artist.Path, "ExTrAs", "file4.flac").AsOsAgnostic(),
Path.Combine(_artist.Path, "Season 1", "s01e01.flac").AsOsAgnostic()
Path.Combine(_artist.Path, "EXTRAS", "file1.flac"),
Path.Combine(_artist.Path, "Extras", "file2.flac"),
Path.Combine(_artist.Path, "EXTRAs", "file3.flac"),
Path.Combine(_artist.Path, "ExTrAs", "file4.flac"),
Path.Combine(_artist.Path, "Season 1", "s01e01.flac")
});
Subject.Scan(_artist);
Mocker.GetMock<IDiskProvider>()
.Verify(v => v.GetFiles(It.IsAny<string>(), It.IsAny<SearchOption>()), Times.Once());
Mocker.GetMock<IMakeImportDecision>()
.Verify(v => v.GetImportDecisions(It.Is<List<string>>(l => l.Count == 1), _artist, false), Times.Once());
.Verify(v => v.GetImportDecisions(It.Is<List<IFileInfo>>(l => l.Count == 1), _artist, FilterFilesType.Known, true), Times.Once());
}
[Test]
@ -230,15 +247,15 @@ namespace NzbDrone.Core.Test.MediaFiles.DiskScanServiceTests
GivenFiles(new List<string>
{
Path.Combine(_artist.Path, ".AppleDouble", "file1.flac").AsOsAgnostic(),
Path.Combine(_artist.Path, ".appledouble", "file2.flac").AsOsAgnostic(),
Path.Combine(_artist.Path, "Season 1", "s01e01.flac").AsOsAgnostic()
Path.Combine(_artist.Path, ".AppleDouble", "file1.flac"),
Path.Combine(_artist.Path, ".appledouble", "file2.flac"),
Path.Combine(_artist.Path, "Season 1", "s01e01.flac")
});
Subject.Scan(_artist);
Mocker.GetMock<IMakeImportDecision>()
.Verify(v => v.GetImportDecisions(It.Is<List<string>>(l => l.Count == 1), _artist, false), Times.Once());
.Verify(v => v.GetImportDecisions(It.Is<List<IFileInfo>>(l => l.Count == 1), _artist, FilterFilesType.Known, true), Times.Once());
}
[Test]
@ -250,18 +267,18 @@ namespace NzbDrone.Core.Test.MediaFiles.DiskScanServiceTests
GivenFiles(new List<string>
{
Path.Combine(_artist.Path, "Extras", "file1.flac").AsOsAgnostic(),
Path.Combine(_artist.Path, ".AppleDouble", "file2.flac").AsOsAgnostic(),
Path.Combine(_artist.Path, "Season 1", "s01e01.flac").AsOsAgnostic(),
Path.Combine(_artist.Path, "Season 1", "s01e02.flac").AsOsAgnostic(),
Path.Combine(_artist.Path, "Season 2", "s02e01.flac").AsOsAgnostic(),
Path.Combine(_artist.Path, "Season 2", "s02e02.flac").AsOsAgnostic(),
Path.Combine(_artist.Path, "Extras", "file1.flac"),
Path.Combine(_artist.Path, ".AppleDouble", "file2.flac"),
Path.Combine(_artist.Path, "Season 1", "s01e01.flac"),
Path.Combine(_artist.Path, "Season 1", "s01e02.flac"),
Path.Combine(_artist.Path, "Season 2", "s02e01.flac"),
Path.Combine(_artist.Path, "Season 2", "s02e02.flac"),
});
Subject.Scan(_artist);
Mocker.GetMock<IMakeImportDecision>()
.Verify(v => v.GetImportDecisions(It.Is<List<string>>(l => l.Count == 4), _artist, false), Times.Once());
.Verify(v => v.GetImportDecisions(It.Is<List<IFileInfo>>(l => l.Count == 4), _artist, FilterFilesType.Known, true), Times.Once());
}
[Test]
@ -271,13 +288,13 @@ namespace NzbDrone.Core.Test.MediaFiles.DiskScanServiceTests
GivenFiles(new List<string>
{
Path.Combine(_artist.Path, "Album 1", ".t01.mp3").AsOsAgnostic()
Path.Combine(_artist.Path, "Album 1", ".t01.mp3")
});
Subject.Scan(_artist);
Mocker.GetMock<IMakeImportDecision>()
.Verify(v => v.GetImportDecisions(It.Is<List<string>>(l => l.Count == 1), _artist, false), Times.Once());
.Verify(v => v.GetImportDecisions(It.Is<List<IFileInfo>>(l => l.Count == 1), _artist, FilterFilesType.Known, true), Times.Once());
}
[Test]
@ -287,16 +304,16 @@ namespace NzbDrone.Core.Test.MediaFiles.DiskScanServiceTests
GivenFiles(new List<string>
{
Path.Combine(_artist.Path, ".@__thumb", "file1.flac").AsOsAgnostic(),
Path.Combine(_artist.Path, ".@__THUMB", "file2.flac").AsOsAgnostic(),
Path.Combine(_artist.Path, ".hidden", "file2.flac").AsOsAgnostic(),
Path.Combine(_artist.Path, "Season 1", "s01e01.flac").AsOsAgnostic()
Path.Combine(_artist.Path, ".@__thumb", "file1.flac"),
Path.Combine(_artist.Path, ".@__THUMB", "file2.flac"),
Path.Combine(_artist.Path, ".hidden", "file2.flac"),
Path.Combine(_artist.Path, "Season 1", "s01e01.flac")
});
Subject.Scan(_artist);
Mocker.GetMock<IMakeImportDecision>()
.Verify(v => v.GetImportDecisions(It.Is<List<string>>(l => l.Count == 1), _artist, false), Times.Once());
.Verify(v => v.GetImportDecisions(It.Is<List<IFileInfo>>(l => l.Count == 1), _artist, FilterFilesType.Known, true), Times.Once());
}
[Test]
@ -306,17 +323,17 @@ namespace NzbDrone.Core.Test.MediaFiles.DiskScanServiceTests
GivenFiles(new List<string>
{
Path.Combine(_artist.Path, "Season 1", ".@__thumb", "file1.flac").AsOsAgnostic(),
Path.Combine(_artist.Path, "Season 1", ".@__THUMB", "file2.flac").AsOsAgnostic(),
Path.Combine(_artist.Path, "Season 1", ".hidden", "file2.flac").AsOsAgnostic(),
Path.Combine(_artist.Path, "Season 1", ".AppleDouble", "s01e01.flac").AsOsAgnostic(),
Path.Combine(_artist.Path, "Season 1", "s01e01.flac").AsOsAgnostic()
Path.Combine(_artist.Path, "Season 1", ".@__thumb", "file1.flac"),
Path.Combine(_artist.Path, "Season 1", ".@__THUMB", "file2.flac"),
Path.Combine(_artist.Path, "Season 1", ".hidden", "file2.flac"),
Path.Combine(_artist.Path, "Season 1", ".AppleDouble", "s01e01.flac"),
Path.Combine(_artist.Path, "Season 1", "s01e01.flac")
});
Subject.Scan(_artist);
Mocker.GetMock<IMakeImportDecision>()
.Verify(v => v.GetImportDecisions(It.Is<List<string>>(l => l.Count == 1), _artist, false), Times.Once());
.Verify(v => v.GetImportDecisions(It.Is<List<IFileInfo>>(l => l.Count == 1), _artist, FilterFilesType.Known, true), Times.Once());
}
[Test]
@ -326,14 +343,14 @@ namespace NzbDrone.Core.Test.MediaFiles.DiskScanServiceTests
GivenFiles(new List<string>
{
Path.Combine(_artist.Path, "@eaDir", "file1.flac").AsOsAgnostic(),
Path.Combine(_artist.Path, "Season 1", "s01e01.flac").AsOsAgnostic()
Path.Combine(_artist.Path, "@eaDir", "file1.flac"),
Path.Combine(_artist.Path, "Season 1", "s01e01.flac")
});
Subject.Scan(_artist);
Mocker.GetMock<IMakeImportDecision>()
.Verify(v => v.GetImportDecisions(It.Is<List<string>>(l => l.Count == 1), _artist, false), Times.Once());
.Verify(v => v.GetImportDecisions(It.Is<List<IFileInfo>>(l => l.Count == 1), _artist, FilterFilesType.Known, true), Times.Once());
}
[Test]
@ -343,14 +360,14 @@ namespace NzbDrone.Core.Test.MediaFiles.DiskScanServiceTests
GivenFiles(new List<string>
{
Path.Combine(_artist.Path, ".@__thumb", "file1.flac").AsOsAgnostic(),
Path.Combine(_artist.Path, "Season 1", "s01e01.flac").AsOsAgnostic()
Path.Combine(_artist.Path, ".@__thumb", "file1.flac"),
Path.Combine(_artist.Path, "Season 1", "s01e01.flac")
});
Subject.Scan(_artist);
Mocker.GetMock<IMakeImportDecision>()
.Verify(v => v.GetImportDecisions(It.Is<List<string>>(l => l.Count == 1), _artist, false), Times.Once());
.Verify(v => v.GetImportDecisions(It.Is<List<IFileInfo>>(l => l.Count == 1), _artist, FilterFilesType.Known, true), Times.Once());
}
[Test]
@ -362,14 +379,14 @@ namespace NzbDrone.Core.Test.MediaFiles.DiskScanServiceTests
GivenFiles(new List<string>
{
Path.Combine(_artist.Path, "Season 1", "file1.flac").AsOsAgnostic(),
Path.Combine(_artist.Path, "Season 1", "s01e01.flac").AsOsAgnostic()
Path.Combine(_artist.Path, "Season 1", "file1.flac"),
Path.Combine(_artist.Path, "Season 1", "s01e01.flac")
});
Subject.Scan(_artist);
Mocker.GetMock<IMakeImportDecision>()
.Verify(v => v.GetImportDecisions(It.Is<List<string>>(l => l.Count == 2), _artist, false), Times.Once());
.Verify(v => v.GetImportDecisions(It.Is<List<IFileInfo>>(l => l.Count == 2), _artist, FilterFilesType.Known, true), Times.Once());
}
[Test]
@ -379,15 +396,173 @@ namespace NzbDrone.Core.Test.MediaFiles.DiskScanServiceTests
GivenFiles(new List<string>
{
Path.Combine(_artist.Path, ".DS_STORE").AsOsAgnostic(),
Path.Combine(_artist.Path, "._24 The Status Quo Combustion.flac").AsOsAgnostic(),
Path.Combine(_artist.Path, "24 The Status Quo Combustion.flac").AsOsAgnostic()
Path.Combine(_artist.Path, ".DS_STORE"),
Path.Combine(_artist.Path, "._24 The Status Quo Combustion.flac"),
Path.Combine(_artist.Path, "24 The Status Quo Combustion.flac")
});
Subject.Scan(_artist);
Mocker.GetMock<IMakeImportDecision>()
.Verify(v => v.GetImportDecisions(It.Is<List<string>>(l => l.Count == 1), _artist, false), Times.Once());
.Verify(v => v.GetImportDecisions(It.Is<List<IFileInfo>>(l => l.Count == 1), _artist, FilterFilesType.Known, true), Times.Once());
}
private void GivenRejections()
{
Mocker.GetMock<IMakeImportDecision>()
.Setup(x => x.GetImportDecisions(It.IsAny<List<IFileInfo>>(), It.IsAny<Artist>(), It.IsAny<FilterFilesType>(), It.IsAny<bool>()))
.Returns((List<IFileInfo> fileList, Artist artist, FilterFilesType filter, bool includeExisting) =>
fileList.Select(x => new LocalTrack {
Artist = artist,
Path = x.FullName,
Modified = x.LastWriteTimeUtc,
FileTrackInfo = new ParsedTrackInfo()
})
.Select(x => new ImportDecision<LocalTrack>(x, new Rejection("Reject")))
.ToList());
}
[Test]
public void should_insert_new_unmatched_files_when_all_new()
{
var files = new List<string> {
Path.Combine(_artist.Path, "Season 1", "file1.flac"),
Path.Combine(_artist.Path, "Season 1", "s01e01.flac")
};
GivenFiles(files);
GivenKnownFiles(new List<string>());
GivenRejections();
Subject.Scan(_artist);
Mocker.GetMock<IMediaFileService>()
.Verify(x => x.AddMany(It.Is<List<TrackFile>>(l => l.Select(t => t.Path).SequenceEqual(files))),
Times.Once());
}
[Test]
public void should_insert_new_unmatched_files_when_some_known()
{
var files = new List<string> {
Path.Combine(_artist.Path, "Season 1", "file1.flac"),
Path.Combine(_artist.Path, "Season 1", "s01e01.flac")
};
GivenFiles(files);
GivenKnownFiles(files.GetRange(1, 1));
GivenRejections();
Subject.Scan(_artist);
Mocker.GetMock<IMediaFileService>()
.Verify(x => x.AddMany(It.Is<List<TrackFile>>(l => l.Select(t => t.Path).SequenceEqual(files.GetRange(0, 1)))),
Times.Once());
}
[Test]
public void should_not_insert_files_when_all_known()
{
var files = new List<string> {
Path.Combine(_artist.Path, "Season 1", "file1.flac"),
Path.Combine(_artist.Path, "Season 1", "s01e01.flac")
};
GivenFiles(files);
GivenKnownFiles(files);
GivenRejections();
Subject.Scan(_artist);
Mocker.GetMock<IMediaFileService>()
.Verify(x => x.AddMany(It.Is<List<TrackFile>>(l => l.Count == 0)),
Times.Once());
Mocker.GetMock<IMediaFileService>()
.Verify(x => x.AddMany(It.Is<List<TrackFile>>(l => l.Count > 0)),
Times.Never());
}
[Test]
public void should_not_update_info_for_unchanged_known_files()
{
var files = new List<string> {
Path.Combine(_artist.Path, "Season 1", "file1.flac"),
Path.Combine(_artist.Path, "Season 1", "s01e01.flac")
};
GivenFiles(files);
GivenKnownFiles(files);
GivenRejections();
Subject.Scan(_artist);
Mocker.GetMock<IMediaFileService>()
.Verify(x => x.Update(It.Is<List<TrackFile>>(l => l.Count == 0)),
Times.Once());
Mocker.GetMock<IMediaFileService>()
.Verify(x => x.Update(It.Is<List<TrackFile>>(l => l.Count > 0)),
Times.Never());
}
[Test]
public void should_update_info_for_changed_known_files()
{
var files = new List<string> {
Path.Combine(_artist.Path, "Season 1", "file1.flac"),
Path.Combine(_artist.Path, "Season 1", "s01e01.flac")
};
GivenFiles(files, new DateTime(2019, 2, 1));
GivenKnownFiles(files);
GivenRejections();
Subject.Scan(_artist);
Mocker.GetMock<IMediaFileService>()
.Verify(x => x.Update(It.Is<List<TrackFile>>(l => l.Count == 2)),
Times.Once());
}
[Test]
public void should_update_fields_for_updated_files()
{
var files = new List<string> {
Path.Combine(_artist.Path, "Season 1", "file1.flac"),
};
GivenKnownFiles(files);
FileSystem.AddFile(files[0], new MockFileData("".PadRight(100)) { LastWriteTime = new DateTime(2019, 2, 1) });
var localTrack = Builder<LocalTrack>.CreateNew()
.With(x => x.Path = files[0])
.With(x => x.Modified = new DateTime(2019, 2, 1))
.With(x => x.Size = 100)
.With(x => x.Quality = new QualityModel(Quality.FLAC))
.With(x => x.FileTrackInfo = new ParsedTrackInfo {
MediaInfo = Builder<MediaInfoModel>.CreateNew().Build()
})
.Build();
Mocker.GetMock<IMakeImportDecision>()
.Setup(x => x.GetImportDecisions(It.IsAny<List<IFileInfo>>(), It.IsAny<Artist>(), It.IsAny<FilterFilesType>(), It.IsAny<bool>()))
.Returns(new List<ImportDecision<LocalTrack>> { new ImportDecision<LocalTrack>(localTrack, new Rejection("Reject")) });
Subject.Scan(_artist);
Mocker.GetMock<IMediaFileService>()
.Verify(x => x.Update(It.Is<List<TrackFile>>(
l => l.Count == 1 &&
l[0].Path == localTrack.Path &&
l[0].Modified == localTrack.Modified &&
l[0].Size == localTrack.Size &&
l[0].Quality.Equals(localTrack.Quality) &&
l[0].MediaInfo.AudioFormat == localTrack.FileTrackInfo.MediaInfo.AudioFormat
)),
Times.Once());
}
}
}

@ -1,10 +1,9 @@
using System;
using System.Collections.Generic;
using System.IO;
using System.IO.Abstractions;
using FizzWare.NBuilder;
using Moq;
using NUnit.Framework;
using NzbDrone.Common.Disk;
using NzbDrone.Core.Download;
using NzbDrone.Core.Download.TrackedDownloads;
using NzbDrone.Core.MediaFiles;
@ -14,11 +13,12 @@ using NzbDrone.Core.Parser.Model;
using NzbDrone.Core.Test.Framework;
using NzbDrone.Core.Music;
using NzbDrone.Test.Common;
using System.IO.Abstractions.TestingHelpers;
namespace NzbDrone.Core.Test.MediaFiles
{
[TestFixture]
public class DownloadedAlbumsCommandServiceFixture : CoreTest<DownloadedAlbumsCommandService>
public class DownloadedAlbumsCommandServiceFixture : FileSystemTest<DownloadedAlbumsCommandService>
{
private string _downloadFolder = "c:\\drop_other\\Show.S01E01\\".AsOsAgnostic();
private string _downloadFile = "c:\\drop_other\\Show.S01E01.mkv".AsOsAgnostic();
@ -30,7 +30,7 @@ namespace NzbDrone.Core.Test.MediaFiles
{
Mocker.GetMock<IDownloadedTracksImportService>()
.Setup(v => v.ProcessRootFolder(It.IsAny<DirectoryInfo>()))
.Setup(v => v.ProcessRootFolder(It.IsAny<IDirectoryInfo>()))
.Returns(new List<ImportResult>());
Mocker.GetMock<IDownloadedTracksImportService>()
@ -56,14 +56,12 @@ namespace NzbDrone.Core.Test.MediaFiles
private void GivenExistingFolder(string path)
{
Mocker.GetMock<IDiskProvider>().Setup(c => c.FolderExists(It.IsAny<string>()))
.Returns(true);
FileSystem.AddDirectory(path);
}
private void GivenExistingFile(string path)
{
Mocker.GetMock<IDiskProvider>().Setup(c => c.FileExists(It.IsAny<string>()))
.Returns(true);
FileSystem.AddFile(path, new MockFileData(string.Empty));
}
private void GivenValidQueueItem()
@ -78,7 +76,7 @@ namespace NzbDrone.Core.Test.MediaFiles
{
Assert.Throws<ArgumentException>(() => Subject.Execute(new DownloadedAlbumsScanCommand()));
Mocker.GetMock<IDownloadedTracksImportService>().Verify(c => c.ProcessRootFolder(It.IsAny<DirectoryInfo>()), Times.Never());
Mocker.GetMock<IDownloadedTracksImportService>().Verify(c => c.ProcessRootFolder(It.IsAny<IDirectoryInfo>()), Times.Never());
}

@ -1,5 +1,5 @@
using System.Collections.Generic;
using System.IO;
using System.IO.Abstractions;
using System.Linq;
using FizzWare.NBuilder;
using FluentAssertions;
@ -12,36 +12,33 @@ using NzbDrone.Core.MediaFiles;
using NzbDrone.Core.MediaFiles.TrackImport;
using NzbDrone.Core.Parser;
using NzbDrone.Core.Parser.Model;
using NzbDrone.Core.Qualities;
using NzbDrone.Core.Test.Framework;
using NzbDrone.Core.Music;
using NzbDrone.Test.Common;
using System.IO.Abstractions.TestingHelpers;
using System.IO;
namespace NzbDrone.Core.Test.MediaFiles
{
[TestFixture]
public class DownloadedTracksImportServiceFixture : CoreTest<DownloadedTracksImportService>
public class DownloadedTracksImportServiceFixture : FileSystemTest<DownloadedTracksImportService>
{
private string _droneFactory = "c:\\drop\\".AsOsAgnostic();
private string[] _subFolders = new[] { "c:\\root\\foldername".AsOsAgnostic() };
private string[] _audioFiles = new[] { "c:\\root\\foldername\\01 the first track.ext".AsOsAgnostic() };
private string[] _subFolders = new[] { "c:\\drop\\foldername".AsOsAgnostic() };
private string[] _audioFiles = new[] { "c:\\drop\\foldername\\01 the first track.ext".AsOsAgnostic() };
private TrackedDownload _trackedDownload;
[SetUp]
public void Setup()
{
GivenAudioFiles(_audioFiles, 10);
Mocker.GetMock<IDiskScanService>().Setup(c => c.GetAudioFiles(It.IsAny<string>(), It.IsAny<bool>()))
.Returns(_audioFiles);
Mocker.GetMock<IDiskScanService>().Setup(c => c.FilterFiles(It.IsAny<string>(), It.IsAny<IEnumerable<string>>()))
.Returns<string, IEnumerable<string>>((b, s) => s.ToList());
Mocker.GetMock<IDiskProvider>().Setup(c => c.GetDirectories(It.IsAny<string>()))
.Returns(_subFolders);
.Returns(_audioFiles.Select(x => DiskProvider.GetFileInfo(x)).ToArray());
Mocker.GetMock<IDiskProvider>().Setup(c => c.FolderExists(It.IsAny<string>()))
.Returns(true);
Mocker.GetMock<IDiskScanService>().Setup(c => c.FilterFiles(It.IsAny<string>(), It.IsAny<IEnumerable<IFileInfo>>()))
.Returns<string, IEnumerable<IFileInfo>>((b, s) => s.ToList());
Mocker.GetMock<IImportApprovedTracks>()
.Setup(s => s.Import(It.IsAny<List<ImportDecision<LocalTrack>>>(), true, null, ImportMode.Auto))
@ -65,6 +62,14 @@ namespace NzbDrone.Core.Test.MediaFiles
};
}
private void GivenAudioFiles(string[] files, long filesize)
{
foreach (var file in files)
{
FileSystem.AddFile(file, new MockFileData("".PadRight((int)filesize)));
}
}
private void GivenValidArtist()
{
Mocker.GetMock<IParsingService>()
@ -80,7 +85,7 @@ namespace NzbDrone.Core.Test.MediaFiles
imported.Add(new ImportDecision<LocalTrack>(localTrack));
Mocker.GetMock<IMakeImportDecision>()
.Setup(s => s.GetImportDecisions(It.IsAny<List<string>>(), It.IsAny<Artist>(), null))
.Setup(s => s.GetImportDecisions(It.IsAny<List<IFileInfo>>(), It.IsAny<Artist>(), null))
.Returns(imported);
Mocker.GetMock<IImportApprovedTracks>()
@ -92,13 +97,13 @@ namespace NzbDrone.Core.Test.MediaFiles
private void WasImportedResponse()
{
Mocker.GetMock<IDiskScanService>().Setup(c => c.GetAudioFiles(It.IsAny<string>(), It.IsAny<bool>()))
.Returns(new string[0]);
.Returns(new IFileInfo[0]);
}
[Test]
public void should_search_for_artist_using_folder_name()
{
Subject.ProcessRootFolder(new DirectoryInfo(_droneFactory));
Subject.ProcessRootFolder(DiskProvider.GetDirectoryInfo(_droneFactory));
Mocker.GetMock<IParsingService>().Verify(c => c.GetArtist("foldername"), Times.Once());
}
@ -108,10 +113,12 @@ namespace NzbDrone.Core.Test.MediaFiles
{
GivenValidArtist();
Mocker.GetMock<IDiskProvider>().Setup(c => c.IsFileLocked(It.IsAny<string>()))
.Returns(true);
foreach (var file in _audioFiles)
{
FileSystem.AddFile(file, new MockFileData("".PadRight(10)) { AllowedFileShare = FileShare.None });
}
Subject.ProcessRootFolder(new DirectoryInfo(_droneFactory));
Subject.ProcessRootFolder(DiskProvider.GetDirectoryInfo(_droneFactory));
VerifyNoImport();
}
@ -121,10 +128,10 @@ namespace NzbDrone.Core.Test.MediaFiles
{
Mocker.GetMock<IParsingService>().Setup(c => c.GetArtist("foldername")).Returns((Artist)null);
Subject.ProcessRootFolder(new DirectoryInfo(_droneFactory));
Subject.ProcessRootFolder(DiskProvider.GetDirectoryInfo(_droneFactory));
Mocker.GetMock<IMakeImportDecision>()
.Verify(c => c.GetImportDecisions(It.IsAny<List<string>>(), It.IsAny<Artist>(), It.IsAny<ParsedTrackInfo>()),
.Verify(c => c.GetImportDecisions(It.IsAny<List<IFileInfo>>(), It.IsAny<Artist>(), It.IsAny<ParsedTrackInfo>()),
Times.Never());
VerifyNoImport();
@ -141,9 +148,9 @@ namespace NzbDrone.Core.Test.MediaFiles
Mocker.GetMock<IDiskScanService>()
.Setup(c => c.GetAudioFiles(It.IsAny<string>(), It.IsAny<bool>()))
.Returns(new string[0]);
.Returns(new IFileInfo[0]);
Subject.ProcessRootFolder(new DirectoryInfo(_droneFactory));
Subject.ProcessRootFolder(DiskProvider.GetDirectoryInfo(_droneFactory));
Mocker.GetMock<IDiskScanService>()
.Verify(v => v.GetAudioFiles(It.IsAny<string>(), true), Times.Never());
@ -158,7 +165,7 @@ namespace NzbDrone.Core.Test.MediaFiles
.Setup(s => s.Import(It.IsAny<List<ImportDecision<LocalTrack>>>(), false, null, ImportMode.Auto))
.Returns(new List<ImportResult>());
Subject.ProcessRootFolder(new DirectoryInfo(_droneFactory));
Subject.ProcessRootFolder(DiskProvider.GetDirectoryInfo(_droneFactory));
Mocker.GetMock<IDiskProvider>()
.Verify(v => v.GetFolderSize(It.IsAny<string>()), Times.Never());
@ -175,14 +182,14 @@ namespace NzbDrone.Core.Test.MediaFiles
imported.Add(new ImportDecision<LocalTrack>(localTrack));
Mocker.GetMock<IMakeImportDecision>()
.Setup(s => s.GetImportDecisions(It.IsAny<List<string>>(), It.IsAny<Artist>(), null))
.Setup(s => s.GetImportDecisions(It.IsAny<List<IFileInfo>>(), It.IsAny<Artist>(), null))
.Returns(imported);
Mocker.GetMock<IImportApprovedTracks>()
.Setup(s => s.Import(It.IsAny<List<ImportDecision<LocalTrack>>>(), true, null, ImportMode.Auto))
.Returns(imported.Select(i => new ImportResult(i)).ToList());
Subject.ProcessRootFolder(new DirectoryInfo(_droneFactory));
Subject.ProcessRootFolder(DiskProvider.GetDirectoryInfo(_droneFactory));
Mocker.GetMock<IDiskProvider>()
.Verify(v => v.DeleteFolder(It.IsAny<string>(), true), Times.Never());
@ -195,13 +202,9 @@ namespace NzbDrone.Core.Test.MediaFiles
public void should_remove_unpack_from_folder_name(string prefix)
{
var folderName = "Alien Ant Farm - Truant (2003)";
var folders = new[] { string.Format(@"C:\Test\Unsorted\{0}{1}", prefix, folderName).AsOsAgnostic() };
Mocker.GetMock<IDiskProvider>()
.Setup(c => c.GetDirectories(It.IsAny<string>()))
.Returns(folders);
FileSystem.AddDirectory(string.Format(@"C:\drop\{0}{1}", prefix, folderName).AsOsAgnostic());
Subject.ProcessRootFolder(new DirectoryInfo(_droneFactory));
Subject.ProcessRootFolder(DiskProvider.GetDirectoryInfo(_droneFactory));
Mocker.GetMock<IParsingService>()
.Verify(v => v.GetArtist(folderName), Times.Once());
@ -213,13 +216,8 @@ namespace NzbDrone.Core.Test.MediaFiles
[Test]
public void should_return_importresult_on_unknown_artist()
{
Mocker.GetMock<IDiskProvider>().Setup(c => c.FolderExists(It.IsAny<string>()))
.Returns(false);
Mocker.GetMock<IDiskProvider>().Setup(c => c.FileExists(It.IsAny<string>()))
.Returns(true);
var fileName = @"C:\folder\file.mkv".AsOsAgnostic();
FileSystem.AddFile(fileName, new MockFileData(string.Empty));
var result = Subject.ProcessPath(fileName);
@ -241,33 +239,18 @@ namespace NzbDrone.Core.Test.MediaFiles
imported.Add(new ImportDecision<LocalTrack>(localTrack));
Mocker.GetMock<IMakeImportDecision>()
.Setup(s => s.GetImportDecisions(It.IsAny<List<string>>(), It.IsAny<Artist>(), null))
.Setup(s => s.GetImportDecisions(It.IsAny<List<IFileInfo>>(), It.IsAny<Artist>(), null))
.Returns(imported);
Mocker.GetMock<IImportApprovedTracks>()
.Setup(s => s.Import(It.IsAny<List<ImportDecision<LocalTrack>>>(), true, null, ImportMode.Auto))
.Returns(imported.Select(i => new ImportResult(i)).ToList());
//Mocker.GetMock<IDetectSample>()
// .Setup(s => s.IsSample(It.IsAny<Artist>(),
// It.IsAny<QualityModel>(),
// It.IsAny<string>(),
// It.IsAny<long>(),
// It.IsAny<bool>()))
// .Returns(true);
GivenAudioFiles(new []{ _audioFiles.First().Replace(".ext", ".rar") }, 15.Megabytes());
Mocker.GetMock<IDiskProvider>()
.Setup(s => s.GetFiles(It.IsAny<string>(), SearchOption.AllDirectories))
.Returns(new []{ _audioFiles.First().Replace(".ext", ".rar") });
Mocker.GetMock<IDiskProvider>()
.Setup(s => s.GetFileSize(It.IsAny<string>()))
.Returns(15.Megabytes());
Subject.ProcessRootFolder(new DirectoryInfo(_droneFactory));
Subject.ProcessRootFolder(DiskProvider.GetDirectoryInfo(_droneFactory));
Mocker.GetMock<IDiskProvider>()
.Verify(v => v.DeleteFolder(It.IsAny<string>(), true), Times.Never());
DiskProvider.FolderExists(_subFolders[0]).Should().BeTrue();
ExceptionVerification.ExpectedWarns(1);
}
@ -277,12 +260,6 @@ namespace NzbDrone.Core.Test.MediaFiles
{
var folderName = @"C:\media\ba09030e-1234-1234-1234-123456789abc\[HorribleSubs] Maria the Virgin Witch - 09 [720p]".AsOsAgnostic();
Mocker.GetMock<IDiskProvider>().Setup(c => c.FolderExists(folderName))
.Returns(false);
Mocker.GetMock<IDiskProvider>().Setup(c => c.FileExists(folderName))
.Returns(false);
Subject.ProcessPath(folderName).Should().BeEmpty();
Mocker.GetMock<IParsingService>()
@ -302,26 +279,16 @@ namespace NzbDrone.Core.Test.MediaFiles
imported.Add(new ImportDecision<LocalTrack>(localTrack));
Mocker.GetMock<IMakeImportDecision>()
.Setup(s => s.GetImportDecisions(It.IsAny<List<string>>(), It.IsAny<Artist>(), null))
.Setup(s => s.GetImportDecisions(It.IsAny<List<IFileInfo>>(), It.IsAny<Artist>(), null))
.Returns(imported);
Mocker.GetMock<IImportApprovedTracks>()
.Setup(s => s.Import(It.IsAny<List<ImportDecision<LocalTrack>>>(), true, null, ImportMode.Auto))
.Returns(new List<ImportResult>());
//Mocker.GetMock<IDetectSample>()
// .Setup(s => s.IsSample(It.IsAny<Artist>(),
// It.IsAny<QualityModel>(),
// It.IsAny<string>(),
// It.IsAny<long>(),
// It.IsAny<bool>()))
// .Returns(true);
Subject.ProcessRootFolder(DiskProvider.GetDirectoryInfo(_droneFactory));
Mocker.GetMock<IDiskProvider>()
.Setup(s => s.GetFileSize(It.IsAny<string>()))
.Returns(15.Megabytes());
Subject.ProcessRootFolder(new DirectoryInfo(_droneFactory));
DiskProvider.FolderExists(_subFolders[0]).Should().BeTrue();
Mocker.GetMock<IDiskProvider>()
.Verify(v => v.DeleteFolder(It.IsAny<string>(), true), Times.Never());
@ -338,8 +305,7 @@ namespace NzbDrone.Core.Test.MediaFiles
Subject.ProcessPath(_droneFactory, ImportMode.Auto, _trackedDownload.RemoteAlbum.Artist, _trackedDownload.DownloadItem);
Mocker.GetMock<IDiskProvider>()
.Verify(v => v.DeleteFolder(It.IsAny<string>(), true), Times.Never());
DiskProvider.FolderExists(_subFolders[0]).Should().BeTrue();
}
[Test]
@ -353,8 +319,7 @@ namespace NzbDrone.Core.Test.MediaFiles
Subject.ProcessPath(_droneFactory, ImportMode.Move, _trackedDownload.RemoteAlbum.Artist, _trackedDownload.DownloadItem);
Mocker.GetMock<IDiskProvider>()
.Verify(v => v.DeleteFolder(It.IsAny<string>(), true), Times.Once());
DiskProvider.FolderExists(_subFolders[0]).Should().BeFalse();
}
[Test]
@ -368,8 +333,7 @@ namespace NzbDrone.Core.Test.MediaFiles
Subject.ProcessPath(_droneFactory, ImportMode.Copy, _trackedDownload.RemoteAlbum.Artist, _trackedDownload.DownloadItem);
Mocker.GetMock<IDiskProvider>()
.Verify(v => v.DeleteFolder(It.IsAny<string>(), true), Times.Never());
DiskProvider.FolderExists(_subFolders[0]).Should().BeTrue();
}
private void VerifyNoImport()

@ -89,10 +89,6 @@ namespace NzbDrone.Core.Test.MediaFiles
_downloadClientItem = Builder<DownloadClientItem>.CreateNew().Build();
Mocker.GetMock<IMediaFileService>()
.Setup(s => s.GetFilesWithRelativePath(It.IsAny<int>(), It.IsAny<string>()))
.Returns(new List<TrackFile>());
Mocker.GetMock<IMediaFileService>()
.Setup(s => s.GetFilesByAlbum(It.IsAny<int>()))
.Returns(new List<TrackFile>());
@ -220,8 +216,8 @@ namespace NzbDrone.Core.Test.MediaFiles
public void should_delete_existing_trackfiles_with_the_same_path()
{
Mocker.GetMock<IMediaFileService>()
.Setup(s => s.GetFilesWithRelativePath(It.IsAny<int>(), It.IsAny<string>()))
.Returns(Builder<TrackFile>.CreateListOfSize(1).BuildList());
.Setup(s => s.GetFileWithPath(It.IsAny<string>()))
.Returns(Builder<TrackFile>.CreateNew().Build());
var track = _approvedDecisions.First();
track.Item.ExistingFile = true;

@ -26,8 +26,7 @@ namespace NzbDrone.Core.Test.MediaFiles.MediaFileDeletionService
.Build();
_trackFile = Builder<TrackFile>.CreateNew()
.With(f => f.RelativePath = "Artist Name - Track01")
.With(f => f.Path = Path.Combine(_artist.Path, "Artist Name - Track01"))
.With(f => f.Path = "/Artist Name - Track01")
.Build();
Mocker.GetMock<IDiskProvider>()

@ -117,28 +117,6 @@ namespace NzbDrone.Core.Test.MediaFiles
files.Should().HaveCount(4);
}
[Test]
public void get_files_by_relative_path()
{
VerifyData();
var files = Subject.GetFilesWithRelativePath(artist.Id, "RelativePath2");
VerifyEagerLoaded(files);
files.Should().OnlyContain(c => c.AlbumId == album.Id);
files.Should().OnlyContain(c => c.RelativePath == "RelativePath2");
}
[Test]
public void get_files_by_relative_path_should_only_contain_monitored_releases()
{
VerifyData();
// file 5 is linked to an unmonitored release
var files = Subject.GetFilesWithRelativePath(artist.Id, "RelativePath5");
files.Should().BeEmpty();
}
private void VerifyData()
{
Db.All<Artist>().Should().HaveCount(1);

@ -1,5 +1,4 @@
using System.Collections.Generic;
using System.IO;
using System.Linq;
using FluentAssertions;
using Moq;
@ -8,13 +7,18 @@ using NzbDrone.Core.MediaFiles;
using NzbDrone.Core.Test.Framework;
using NzbDrone.Core.Music;
using NzbDrone.Test.Common;
using System.IO.Abstractions.TestingHelpers;
using System.IO.Abstractions;
using System;
using FizzWare.NBuilder;
namespace NzbDrone.Core.Test.MediaFiles.MediaFileServiceTests
{
[TestFixture]
public class FilterFixture : CoreTest<MediaFileService>
public class FilterFixture : FileSystemTest<MediaFileService>
{
private Artist _artist;
private DateTime _lastWrite = new DateTime(2019, 1, 1);
[SetUp]
public void Setup()
@ -26,125 +30,252 @@ namespace NzbDrone.Core.Test.MediaFiles.MediaFileServiceTests
};
}
[Test]
public void filter_should_return_all_files_if_no_existing_files()
private List<IFileInfo> GivenFiles(string[] files)
{
var files = new List<string>()
foreach (var file in files)
{
"C:\\file1.avi".AsOsAgnostic(),
"C:\\file2.avi".AsOsAgnostic(),
"C:\\file3.avi".AsOsAgnostic()
};
FileSystem.AddFile(file, new MockFileData(string.Empty) { LastWriteTime = _lastWrite });
}
return files.Select(x => DiskProvider.GetFileInfo(x)).ToList();
}
[TestCase(FilterFilesType.Known)]
[TestCase(FilterFilesType.Matched)]
public void filter_should_return_all_files_if_no_existing_files(FilterFilesType filter)
{
var files = GivenFiles(new []
{
"C:\\file1.avi".AsOsAgnostic(),
"C:\\file2.avi".AsOsAgnostic(),
"C:\\file3.avi".AsOsAgnostic()
});
Mocker.GetMock<IMediaFileRepository>()
.Setup(c => c.GetFilesByArtist(It.IsAny<int>()))
.Setup(c => c.GetFilesWithBasePath(It.IsAny<string>()))
.Returns(new List<TrackFile>());
Subject.FilterExistingFiles(files, _artist).Should().BeEquivalentTo(files);
Subject.FilterUnchangedFiles(files, _artist, filter).Should().BeEquivalentTo(files);
}
[Test]
public void filter_should_return_none_if_all_files_exist()
[TestCase(FilterFilesType.Known)]
[TestCase(FilterFilesType.Matched)]
public void filter_should_return_nothing_if_all_files_exist(FilterFilesType filter)
{
var files = new List<string>()
{
"C:\\file1.avi".AsOsAgnostic(),
"C:\\file2.avi".AsOsAgnostic(),
"C:\\file3.avi".AsOsAgnostic()
};
var files = GivenFiles(new []
{
"C:\\file1.avi".AsOsAgnostic(),
"C:\\file2.avi".AsOsAgnostic(),
"C:\\file3.avi".AsOsAgnostic()
});
Mocker.GetMock<IMediaFileRepository>()
.Setup(c => c.GetFilesByArtist(It.IsAny<int>()))
.Returns(files.Select(f => new TrackFile { RelativePath = Path.GetFileName(f) }).ToList());
.Setup(c => c.GetFilesWithBasePath(It.IsAny<string>()))
.Returns(files.Select(f => new TrackFile {
Path = f.FullName,
Modified = _lastWrite
}).ToList());
Subject.FilterExistingFiles(files, _artist).Should().BeEmpty();
Subject.FilterUnchangedFiles(files, _artist, filter).Should().BeEmpty();
}
[Test]
public void filter_should_return_none_existing_files()
[TestCase(FilterFilesType.Known)]
[TestCase(FilterFilesType.Matched)]
public void filter_should_not_return_existing_files(FilterFilesType filter)
{
var files = new List<string>()
{
"C:\\file1.avi".AsOsAgnostic(),
"C:\\file2.avi".AsOsAgnostic(),
"C:\\file3.avi".AsOsAgnostic()
};
var files = GivenFiles(new []
{
"C:\\file1.avi".AsOsAgnostic(),
"C:\\file2.avi".AsOsAgnostic(),
"C:\\file3.avi".AsOsAgnostic()
});
Mocker.GetMock<IMediaFileRepository>()
.Setup(c => c.GetFilesByArtist(It.IsAny<int>()))
.Setup(c => c.GetFilesWithBasePath(It.IsAny<string>()))
.Returns(new List<TrackFile>
{
new TrackFile{ RelativePath = "file2.avi".AsOsAgnostic()}
new TrackFile{
Path = "C:\\file2.avi".AsOsAgnostic(),
Modified = _lastWrite
}
});
Subject.FilterExistingFiles(files, _artist).Should().HaveCount(2);
Subject.FilterExistingFiles(files, _artist).Should().NotContain("C:\\file2.avi".AsOsAgnostic());
Subject.FilterUnchangedFiles(files, _artist, filter).Should().HaveCount(2);
Subject.FilterUnchangedFiles(files, _artist, filter).Select(x => x.FullName).Should().NotContain("C:\\file2.avi".AsOsAgnostic());
}
[Test]
public void filter_should_return_none_existing_files_ignoring_case()
[TestCase(FilterFilesType.Known)]
[TestCase(FilterFilesType.Matched)]
public void filter_should_return_none_existing_files_ignoring_case(FilterFilesType filter)
{
WindowsOnly();
var files = new List<string>()
{
"C:\\file1.avi".AsOsAgnostic(),
"C:\\FILE2.avi".AsOsAgnostic(),
"C:\\file3.avi".AsOsAgnostic()
};
var files = GivenFiles(new []
{
"C:\\file1.avi".AsOsAgnostic(),
"C:\\FILE2.avi".AsOsAgnostic(),
"C:\\file3.avi".AsOsAgnostic()
});
Mocker.GetMock<IMediaFileRepository>()
.Setup(c => c.GetFilesByArtist(It.IsAny<int>()))
.Setup(c => c.GetFilesWithBasePath(It.IsAny<string>()))
.Returns(new List<TrackFile>
{
new TrackFile{ RelativePath = "file2.avi".AsOsAgnostic()}
new TrackFile{
Path = "C:\\file2.avi".AsOsAgnostic(),
Modified = _lastWrite
}
});
Subject.FilterExistingFiles(files, _artist).Should().HaveCount(2);
Subject.FilterExistingFiles(files, _artist).Should().NotContain("C:\\file2.avi".AsOsAgnostic());
Subject.FilterUnchangedFiles(files, _artist, filter).Should().HaveCount(2);
Subject.FilterUnchangedFiles(files, _artist, filter).Select(x => x.FullName).Should().NotContain("C:\\file2.avi".AsOsAgnostic());
}
[Test]
public void filter_should_return_none_existing_files_not_ignoring_case()
[TestCase(FilterFilesType.Known)]
[TestCase(FilterFilesType.Matched)]
public void filter_should_return_none_existing_files_not_ignoring_case(FilterFilesType filter)
{
MonoOnly();
var files = new List<string>()
{
"C:\\file1.avi".AsOsAgnostic(),
"C:\\FILE2.avi".AsOsAgnostic(),
"C:\\file3.avi".AsOsAgnostic()
};
var files = GivenFiles(new []
{
"C:\\file1.avi".AsOsAgnostic(),
"C:\\FILE2.avi".AsOsAgnostic(),
"C:\\file3.avi".AsOsAgnostic()
});
Mocker.GetMock<IMediaFileRepository>()
.Setup(c => c.GetFilesByArtist(It.IsAny<int>()))
.Setup(c => c.GetFilesWithBasePath(It.IsAny<string>()))
.Returns(new List<TrackFile>
{
new TrackFile{ RelativePath = "file2.avi".AsOsAgnostic()}
new TrackFile{
Path = "C:\\file2.avi".AsOsAgnostic(),
Modified = _lastWrite
}
});
Subject.FilterExistingFiles(files, _artist).Should().HaveCount(3);
Subject.FilterUnchangedFiles(files, _artist, filter).Should().HaveCount(3);
}
[Test]
public void filter_should_not_change_casing()
[TestCase(FilterFilesType.Known)]
[TestCase(FilterFilesType.Matched)]
public void filter_should_not_change_casing(FilterFilesType filter)
{
var files = new List<string>()
{
"C:\\FILE1.avi".AsOsAgnostic()
};
var files = GivenFiles(new []
{
"C:\\FILE1.avi".AsOsAgnostic()
});
Mocker.GetMock<IMediaFileRepository>()
.Setup(c => c.GetFilesByArtist(It.IsAny<int>()))
.Setup(c => c.GetFilesWithBasePath(It.IsAny<string>()))
.Returns(new List<TrackFile>());
Subject.FilterExistingFiles(files, _artist).Should().HaveCount(1);
Subject.FilterExistingFiles(files, _artist).Should().NotContain(files.First().ToLower());
Subject.FilterExistingFiles(files, _artist).Should().Contain(files.First());
Subject.FilterUnchangedFiles(files, _artist, filter).Should().HaveCount(1);
Subject.FilterUnchangedFiles(files, _artist, filter).Select(x => x.FullName).Should().NotContain(files.First().FullName.ToLower());
Subject.FilterUnchangedFiles(files, _artist, filter).Should().Contain(files.First());
}
[TestCase(FilterFilesType.Known)]
[TestCase(FilterFilesType.Matched)]
public void filter_should_not_return_existing_file_if_size_unchanged(FilterFilesType filter)
{
FileSystem.AddFile("C:\\file1.avi".AsOsAgnostic(), new MockFileData("".PadRight(10)) { LastWriteTime = _lastWrite });
FileSystem.AddFile("C:\\file2.avi".AsOsAgnostic(), new MockFileData("".PadRight(10)) { LastWriteTime = _lastWrite });
FileSystem.AddFile("C:\\file3.avi".AsOsAgnostic(), new MockFileData("".PadRight(10)) { LastWriteTime = _lastWrite });
var files = FileSystem.AllFiles.Select(x => DiskProvider.GetFileInfo(x)).ToList();
Mocker.GetMock<IMediaFileRepository>()
.Setup(c => c.GetFilesWithBasePath(It.IsAny<string>()))
.Returns(new List<TrackFile>
{
new TrackFile{
Path = "C:\\file2.avi".AsOsAgnostic(),
Size = 10,
Modified = _lastWrite
}
});
Subject.FilterUnchangedFiles(files, _artist, filter).Should().HaveCount(2);
Subject.FilterUnchangedFiles(files, _artist, filter).Select(x => x.FullName).Should().NotContain("C:\\file2.avi".AsOsAgnostic());
}
[TestCase(FilterFilesType.Matched)]
public void filter_unmatched_should_return_existing_file_if_unmatched(FilterFilesType filter)
{
FileSystem.AddFile("C:\\file1.avi".AsOsAgnostic(), new MockFileData("".PadRight(10)) { LastWriteTime = _lastWrite });
FileSystem.AddFile("C:\\file2.avi".AsOsAgnostic(), new MockFileData("".PadRight(10)) { LastWriteTime = _lastWrite });
FileSystem.AddFile("C:\\file3.avi".AsOsAgnostic(), new MockFileData("".PadRight(10)) { LastWriteTime = _lastWrite });
var files = FileSystem.AllFiles.Select(x => DiskProvider.GetFileInfo(x)).ToList();
Mocker.GetMock<IMediaFileRepository>()
.Setup(c => c.GetFilesWithBasePath(It.IsAny<string>()))
.Returns(new List<TrackFile>
{
new TrackFile{
Path = "C:\\file2.avi".AsOsAgnostic(),
Size = 10,
Modified = _lastWrite,
Tracks = new List<Track>()
}
});
Subject.FilterUnchangedFiles(files, _artist, filter).Should().HaveCount(3);
Subject.FilterUnchangedFiles(files, _artist, filter).Select(x => x.FullName).Should().Contain("C:\\file2.avi".AsOsAgnostic());
}
[TestCase(FilterFilesType.Matched)]
public void filter_unmatched_should_not_return_existing_file_if_matched(FilterFilesType filter)
{
FileSystem.AddFile("C:\\file1.avi".AsOsAgnostic(), new MockFileData("".PadRight(10)) { LastWriteTime = _lastWrite });
FileSystem.AddFile("C:\\file2.avi".AsOsAgnostic(), new MockFileData("".PadRight(10)) { LastWriteTime = _lastWrite });
FileSystem.AddFile("C:\\file3.avi".AsOsAgnostic(), new MockFileData("".PadRight(10)) { LastWriteTime = _lastWrite });
var files = FileSystem.AllFiles.Select(x => DiskProvider.GetFileInfo(x)).ToList();
Mocker.GetMock<IMediaFileRepository>()
.Setup(c => c.GetFilesWithBasePath(It.IsAny<string>()))
.Returns(new List<TrackFile>
{
new TrackFile{
Path = "C:\\file2.avi".AsOsAgnostic(),
Size = 10,
Modified = _lastWrite,
Tracks = Builder<Track>.CreateListOfSize(1).Build() as List<Track>
}
});
Subject.FilterUnchangedFiles(files, _artist, filter).Should().HaveCount(2);
Subject.FilterUnchangedFiles(files, _artist, filter).Select(x => x.FullName).Should().NotContain("C:\\file2.avi".AsOsAgnostic());
}
[TestCase(FilterFilesType.Known)]
[TestCase(FilterFilesType.Matched)]
public void filter_should_return_existing_file_if_size_changed(FilterFilesType filter)
{
FileSystem.AddFile("C:\\file1.avi".AsOsAgnostic(), new MockFileData("".PadRight(10)) { LastWriteTime = _lastWrite });
FileSystem.AddFile("C:\\file2.avi".AsOsAgnostic(), new MockFileData("".PadRight(11)) { LastWriteTime = _lastWrite });
FileSystem.AddFile("C:\\file3.avi".AsOsAgnostic(), new MockFileData("".PadRight(10)) { LastWriteTime = _lastWrite });
var files = FileSystem.AllFiles.Select(x => DiskProvider.GetFileInfo(x)).ToList();
Mocker.GetMock<IMediaFileRepository>()
.Setup(c => c.GetFilesWithBasePath(It.IsAny<string>()))
.Returns(new List<TrackFile>
{
new TrackFile{
Path = "C:\\file2.avi".AsOsAgnostic(),
Size = 10,
Modified = _lastWrite
}
});
Subject.FilterUnchangedFiles(files, _artist, filter).Should().HaveCount(3);
Subject.FilterUnchangedFiles(files, _artist, filter).Select(x => x.FullName).Should().Contain("C:\\file2.avi".AsOsAgnostic());
}
}
}

@ -14,7 +14,7 @@ namespace NzbDrone.Core.Test.MediaFiles
{
public class MediaFileTableCleanupServiceFixture : CoreTest<MediaFileTableCleanupService>
{
private const string DELETED_PATH = "ANY FILE WITH THIS PATH IS CONSIDERED DELETED!";
private readonly string DELETED_PATH = @"c:\ANY FILE WITH THIS PATH IS CONSIDERED DELETED!".AsOsAgnostic();
private List<Track> _tracks;
private Artist _artist;
@ -56,13 +56,15 @@ namespace NzbDrone.Core.Test.MediaFiles
private List<string> FilesOnDisk(IEnumerable<TrackFile> trackFiles)
{
return trackFiles.Select(e => Path.Combine(_artist.Path, e.RelativePath)).ToList();
return trackFiles.Select(e => e.Path).ToList();
}
[Test]
public void should_skip_files_that_exist_in_disk()
public void should_skip_files_that_exist_on_disk()
{
var trackFiles = Builder<TrackFile>.CreateListOfSize(10)
.All()
.With(x => x.Path = Path.Combine(@"c:\test".AsOsAgnostic(), Path.GetRandomFileName()))
.Build();
GivenTrackFiles(trackFiles);
@ -76,31 +78,17 @@ namespace NzbDrone.Core.Test.MediaFiles
public void should_delete_non_existent_files()
{
var trackFiles = Builder<TrackFile>.CreateListOfSize(10)
.All()
.With(x => x.Path = Path.Combine(@"c:\test".AsOsAgnostic(), Path.GetRandomFileName()))
.Random(2)
.With(c => c.RelativePath = DELETED_PATH)
.With(c => c.Path = DELETED_PATH)
.Build();
GivenTrackFiles(trackFiles);
Subject.Clean(_artist, FilesOnDisk(trackFiles.Where(e => e.RelativePath != DELETED_PATH)));
Subject.Clean(_artist, FilesOnDisk(trackFiles.Where(e => e.Path != DELETED_PATH)));
Mocker.GetMock<IMediaFileService>().Verify(c => c.Delete(It.Is<TrackFile>(e => e.RelativePath == DELETED_PATH), DeleteMediaFileReason.MissingFromDisk), Times.Exactly(2));
}
[Test]
public void should_delete_files_that_dont_belong_to_any_tracks()
{
var trackFiles = Builder<TrackFile>.CreateListOfSize(10)
.Random(10)
.With(c => c.RelativePath = "ExistingPath")
.Build();
GivenTrackFiles(trackFiles);
GivenFilesAreNotAttachedToTrack();
Subject.Clean(_artist, FilesOnDisk(trackFiles));
Mocker.GetMock<IMediaFileService>().Verify(c => c.Delete(It.IsAny<TrackFile>(), DeleteMediaFileReason.NoLinkedEpisodes), Times.Exactly(10));
Mocker.GetMock<IMediaFileService>().Verify(c => c.Delete(It.Is<TrackFile>(e => e.Path == DELETED_PATH), DeleteMediaFileReason.MissingFromDisk), Times.Exactly(2));
}
[Test]
@ -118,7 +106,7 @@ namespace NzbDrone.Core.Test.MediaFiles
{
var trackFiles = Builder<TrackFile>.CreateListOfSize(10)
.Random(10)
.With(c => c.RelativePath = "ExistingPath")
.With(c => c.Path = "/ExistingPath".AsOsAgnostic())
.Build();
GivenTrackFiles(trackFiles);

@ -14,6 +14,7 @@ using NzbDrone.Core.Parser.Model;
using NzbDrone.Core.Test.Framework;
using NzbDrone.Core.Music;
using NzbDrone.Test.Common;
using System.IO;
namespace NzbDrone.Core.Test.MediaFiles.TrackFileMovingServiceTests
{
@ -33,7 +34,7 @@ namespace NzbDrone.Core.Test.MediaFiles.TrackFileMovingServiceTests
_trackFile = Builder<TrackFile>.CreateNew()
.With(f => f.Path = null)
.With(f => f.RelativePath = @"Album\File.mp3")
.With(f => f.Path = Path.Combine(_artist.Path, @"Album\File.mp3"))
.Build();
_localtrack = Builder<LocalTrack>.CreateNew()

@ -3,11 +3,11 @@ using System.Linq;
using FluentAssertions;
using Moq;
using NUnit.Framework;
using System.IO.Abstractions;
using NzbDrone.Core.DecisionEngine;
using NzbDrone.Core.MediaFiles;
using NzbDrone.Core.MediaFiles.TrackImport;
using NzbDrone.Core.Parser.Model;
using NzbDrone.Core.Profiles;
using NzbDrone.Core.Qualities;
using NzbDrone.Core.Test.Framework;
using NzbDrone.Core.Music;
@ -18,13 +18,14 @@ using NzbDrone.Core.MediaFiles.TrackImport.Aggregation;
using NzbDrone.Core.Profiles.Qualities;
using NzbDrone.Core.Profiles.Languages;
using NzbDrone.Core.MediaFiles.TrackImport.Identification;
using System.IO.Abstractions.TestingHelpers;
namespace NzbDrone.Core.Test.MediaFiles.TrackImport
{
[TestFixture]
public class ImportDecisionMakerFixture : CoreTest<ImportDecisionMaker>
public class ImportDecisionMakerFixture : FileSystemTest<ImportDecisionMaker>
{
private List<string> _audioFiles;
private List<IFileInfo> _fileInfos;
private LocalTrack _localTrack;
private Artist _artist;
private AlbumRelease _albumRelease;
@ -112,8 +113,8 @@ namespace NzbDrone.Core.Test.MediaFiles.TrackImport
});
Mocker.GetMock<IMediaFileService>()
.Setup(c => c.FilterExistingFiles(It.IsAny<List<string>>(), It.IsAny<Artist>()))
.Returns((List<string> files, Artist artist) => files);
.Setup(c => c.FilterUnchangedFiles(It.IsAny<List<IFileInfo>>(), It.IsAny<Artist>(), It.IsAny<FilterFilesType>()))
.Returns((List<IFileInfo> files, Artist artist) => files);
GivenSpecifications(_albumpass1);
}
@ -125,11 +126,12 @@ namespace NzbDrone.Core.Test.MediaFiles.TrackImport
private void GivenAudioFiles(IEnumerable<string> videoFiles)
{
_audioFiles = videoFiles.ToList();
foreach (var file in videoFiles)
{
FileSystem.AddFile(file, new MockFileData(string.Empty));
}
Mocker.GetMock<IMediaFileService>()
.Setup(c => c.FilterExistingFiles(_audioFiles, It.IsAny<Artist>()))
.Returns(_audioFiles);
_fileInfos = videoFiles.Select(x => DiskProvider.GetFileInfo(x)).ToList();
}
private void GivenAugmentationSuccess()
@ -149,7 +151,7 @@ namespace NzbDrone.Core.Test.MediaFiles.TrackImport
GivenAugmentationSuccess();
GivenSpecifications(_albumpass1, _albumpass2, _albumpass3, _albumfail1, _albumfail2, _albumfail3);
Subject.GetImportDecisions(_audioFiles, new Artist(), null, null, downloadClientItem, null, false, false, false, false);
Subject.GetImportDecisions(_fileInfos, new Artist(), null, null, downloadClientItem, null, FilterFilesType.None, false, false, false);
_albumfail1.Verify(c => c.IsSatisfiedBy(It.IsAny<LocalAlbumRelease>()), Times.Once());
_albumfail2.Verify(c => c.IsSatisfiedBy(It.IsAny<LocalAlbumRelease>()), Times.Once());
@ -166,7 +168,7 @@ namespace NzbDrone.Core.Test.MediaFiles.TrackImport
GivenAugmentationSuccess();
GivenSpecifications(_pass1, _pass2, _pass3, _fail1, _fail2, _fail3);
Subject.GetImportDecisions(_audioFiles, new Artist(), null, null, downloadClientItem, null, false, false, false, false);
Subject.GetImportDecisions(_fileInfos, new Artist(), null, null, downloadClientItem, null, FilterFilesType.None, false, false, false);
_fail1.Verify(c => c.IsSatisfiedBy(It.IsAny<LocalTrack>()), Times.Once());
_fail2.Verify(c => c.IsSatisfiedBy(It.IsAny<LocalTrack>()), Times.Once());
@ -184,7 +186,7 @@ namespace NzbDrone.Core.Test.MediaFiles.TrackImport
GivenSpecifications(_albumpass1, _albumpass2, _albumpass3, _albumfail1, _albumfail2, _albumfail3);
GivenSpecifications(_pass1, _pass2, _pass3, _fail1, _fail2, _fail3);
Subject.GetImportDecisions(_audioFiles, new Artist(), null, null, downloadClientItem, null, false, false, false, false);
Subject.GetImportDecisions(_fileInfos, new Artist(), null, null, downloadClientItem, null, FilterFilesType.None, false, false, false);
_fail1.Verify(c => c.IsSatisfiedBy(It.IsAny<LocalTrack>()), Times.Never());
_fail2.Verify(c => c.IsSatisfiedBy(It.IsAny<LocalTrack>()), Times.Never());
@ -200,7 +202,7 @@ namespace NzbDrone.Core.Test.MediaFiles.TrackImport
GivenSpecifications(_albumfail1);
GivenSpecifications(_pass1);
var result = Subject.GetImportDecisions(_audioFiles, new Artist(), false);
var result = Subject.GetImportDecisions(_fileInfos, new Artist(), FilterFilesType.None, false);
result.Single().Approved.Should().BeFalse();
}
@ -211,7 +213,7 @@ namespace NzbDrone.Core.Test.MediaFiles.TrackImport
GivenSpecifications(_albumpass1);
GivenSpecifications(_fail1);
var result = Subject.GetImportDecisions(_audioFiles, new Artist(), false);
var result = Subject.GetImportDecisions(_fileInfos, new Artist(), FilterFilesType.None, false);
result.Single().Approved.Should().BeFalse();
}
@ -222,7 +224,7 @@ namespace NzbDrone.Core.Test.MediaFiles.TrackImport
GivenSpecifications(_albumpass1, _albumfail1, _albumpass2, _albumpass3);
GivenSpecifications(_pass1, _pass2, _pass3);
var result = Subject.GetImportDecisions(_audioFiles, new Artist(), false);
var result = Subject.GetImportDecisions(_fileInfos, new Artist(), FilterFilesType.None, false);
result.Single().Approved.Should().BeFalse();
}
@ -233,7 +235,7 @@ namespace NzbDrone.Core.Test.MediaFiles.TrackImport
GivenSpecifications(_albumpass1, _albumpass2, _albumpass3);
GivenSpecifications(_pass1, _fail1, _pass2, _pass3);
var result = Subject.GetImportDecisions(_audioFiles, new Artist(), false);
var result = Subject.GetImportDecisions(_fileInfos, new Artist(), FilterFilesType.None, false);
result.Single().Approved.Should().BeFalse();
}
@ -245,7 +247,7 @@ namespace NzbDrone.Core.Test.MediaFiles.TrackImport
GivenSpecifications(_albumpass1, _albumpass2, _albumpass3);
GivenSpecifications(_pass1, _pass2, _pass3);
var result = Subject.GetImportDecisions(_audioFiles, new Artist(), false);
var result = Subject.GetImportDecisions(_fileInfos, new Artist(), FilterFilesType.None, false);
result.Single().Approved.Should().BeTrue();
}
@ -256,7 +258,7 @@ namespace NzbDrone.Core.Test.MediaFiles.TrackImport
GivenAugmentationSuccess();
GivenSpecifications(_pass1, _pass2, _pass3, _fail1, _fail2, _fail3);
var result = Subject.GetImportDecisions(_audioFiles, new Artist(), false);
var result = Subject.GetImportDecisions(_fileInfos, new Artist(), FilterFilesType.None, false);
result.Single().Rejections.Should().HaveCount(3);
}
@ -276,10 +278,10 @@ namespace NzbDrone.Core.Test.MediaFiles.TrackImport
@"C:\Test\Unsorted\The.Office.S03E115.DVDRip.XviD-OSiTV".AsOsAgnostic()
});
Subject.GetImportDecisions(_audioFiles, _artist, false);
Subject.GetImportDecisions(_fileInfos, _artist, FilterFilesType.None, false);
Mocker.GetMock<IAugmentingService>()
.Verify(c => c.Augment(It.IsAny<LocalTrack>(), It.IsAny<bool>()), Times.Exactly(_audioFiles.Count));
.Verify(c => c.Augment(It.IsAny<LocalTrack>(), It.IsAny<bool>()), Times.Exactly(_fileInfos.Count));
ExceptionVerification.ExpectedErrors(3);
}
@ -302,10 +304,10 @@ namespace NzbDrone.Core.Test.MediaFiles.TrackImport
return new List<LocalAlbumRelease> { new LocalAlbumRelease(tracks) };
});
var decisions = Subject.GetImportDecisions(_audioFiles, _artist, false);
var decisions = Subject.GetImportDecisions(_fileInfos, _artist, FilterFilesType.None, false);
Mocker.GetMock<IAugmentingService>()
.Verify(c => c.Augment(It.IsAny<LocalTrack>(), It.IsAny<bool>()), Times.Exactly(_audioFiles.Count));
.Verify(c => c.Augment(It.IsAny<LocalTrack>(), It.IsAny<bool>()), Times.Exactly(_fileInfos.Count));
decisions.Should().HaveCount(3);
decisions.First().Rejections.Should().NotBeEmpty();
@ -323,10 +325,10 @@ namespace NzbDrone.Core.Test.MediaFiles.TrackImport
@"C:\Test\Unsorted\The.Office.S03E115.DVDRip.XviD-OSiTV".AsOsAgnostic()
});
var decisions = Subject.GetImportDecisions(_audioFiles, _artist, false);
var decisions = Subject.GetImportDecisions(_fileInfos, _artist, FilterFilesType.None, false);
Mocker.GetMock<IAugmentingService>()
.Verify(c => c.Augment(It.IsAny<LocalTrack>(), It.IsAny<bool>()), Times.Exactly(_audioFiles.Count));
.Verify(c => c.Augment(It.IsAny<LocalTrack>(), It.IsAny<bool>()), Times.Exactly(_fileInfos.Count));
decisions.Should().HaveCount(3);
decisions.First().Rejections.Should().NotBeEmpty();
@ -344,7 +346,7 @@ namespace NzbDrone.Core.Test.MediaFiles.TrackImport
@"C:\Test\Unsorted\The.Office.S03E115.DVDRip.XviD-OSiTV".AsOsAgnostic()
});
Subject.GetImportDecisions(_audioFiles, _artist, false).Should().HaveCount(1);
Subject.GetImportDecisions(_fileInfos, _artist, FilterFilesType.None, false).Should().HaveCount(1);
ExceptionVerification.ExpectedErrors(1);
}

@ -18,6 +18,7 @@ namespace NzbDrone.Core.Test.MediaFiles
{
private TrackFile _trackFile;
private LocalTrack _localTrack;
private string rootPath = @"C:\Test\Music\Artist".AsOsAgnostic();
[SetUp]
public void Setup()
@ -25,7 +26,7 @@ namespace NzbDrone.Core.Test.MediaFiles
_localTrack = new LocalTrack();
_localTrack.Artist = new Artist
{
Path = @"C:\Test\Music\Artist".AsOsAgnostic()
Path = rootPath
};
_trackFile = Builder<TrackFile>
@ -55,7 +56,7 @@ namespace NzbDrone.Core.Test.MediaFiles
new TrackFile
{
Id = 1,
RelativePath = @"Season 01\30.rock.s01e01.avi",
Path = Path.Combine(rootPath, @"Season 01\30.rock.s01e01.avi"),
}))
.Build()
.ToList();
@ -70,7 +71,7 @@ namespace NzbDrone.Core.Test.MediaFiles
new TrackFile
{
Id = 1,
RelativePath = @"Season 01\30.rock.s01e01.avi",
Path = Path.Combine(rootPath, @"Season 01\30.rock.s01e01.avi"),
}))
.Build()
.ToList();
@ -84,14 +85,14 @@ namespace NzbDrone.Core.Test.MediaFiles
new TrackFile
{
Id = 1,
RelativePath = @"Season 01\30.rock.s01e01.avi",
Path = Path.Combine(rootPath, @"Season 01\30.rock.s01e01.avi"),
}))
.TheNext(1)
.With(e => e.TrackFile = new LazyLoaded<TrackFile>(
new TrackFile
{
Id = 2,
RelativePath = @"Season 01\30.rock.s01e02.avi",
Path = Path.Combine(rootPath, @"Season 01\30.rock.s01e02.avi"),
}))
.Build()
.ToList();

@ -7,6 +7,7 @@ using NzbDrone.Core.Notifications.Synology;
using NzbDrone.Core.Test.Framework;
using NzbDrone.Core.Music;
using NzbDrone.Test.Common;
using System.IO;
namespace NzbDrone.Core.Test.NotificationTests
{
@ -15,13 +16,14 @@ namespace NzbDrone.Core.Test.NotificationTests
{
private Artist _artist;
private AlbumDownloadMessage _upgrade;
private string rootPath = @"C:\Test\".AsOsAgnostic();
[SetUp]
public void SetUp()
{
_artist = new Artist()
{
Path = @"C:\Test\".AsOsAgnostic()
Path = rootPath,
};
_upgrade = new AlbumDownloadMessage()
@ -32,7 +34,7 @@ namespace NzbDrone.Core.Test.NotificationTests
{
new TrackFile
{
RelativePath = "file1.S01E01E02.mkv"
Path = Path.Combine(rootPath, "file1.S01E01E02.mkv")
}
},
@ -41,11 +43,11 @@ namespace NzbDrone.Core.Test.NotificationTests
{
new TrackFile
{
RelativePath = "file1.S01E01.mkv"
Path = Path.Combine(rootPath, "file1.S01E01.mkv")
},
new TrackFile
{
RelativePath = "file1.S01E02.mkv"
Path = Path.Combine(rootPath, "file1.S01E02.mkv")
}
}
};

@ -91,6 +91,12 @@
<HintPath>..\packages\TagLibSharp-Lidarr.2.2.0.19\lib\netstandard2.0\TagLibSharp.dll</HintPath>
<Private>True</Private>
</Reference>
<Reference Include="System.IO.Abstractions, Version=4.0.0.0, Culture=neutral, processorArchitecture=MSIL">
<HintPath>..\packages\System.IO.Abstractions.4.0.11\lib\net40\System.IO.Abstractions.dll</HintPath>
</Reference>
<Reference Include="System.IO.Abstractions.TestingHelpers, Version=4.0.0.0, Culture=neutral, processorArchitecture=MSIL">
<HintPath>..\packages\System.IO.Abstractions.TestingHelpers.4.0.11\lib\net40\System.IO.Abstractions.TestingHelpers.dll</HintPath>
</Reference>
<Reference Include="System" />
<Reference Include="System.Data" />
<Reference Include="System.Drawing" />
@ -127,6 +133,7 @@
<Compile Include="Datastore\MarrDataLazyLoadingFixture.cs" />
<Compile Include="Datastore\Migration\004_add_various_qualities_in_profileFixture.cs" />
<Compile Include="Datastore\Migration\023_add_release_groups_etcFixture.cs" />
<Compile Include="Datastore\Migration\030_add_mediafilerepository_mtimeFixture.cs" />
<Compile Include="Datastore\ObjectDatabaseFixture.cs" />
<Compile Include="Datastore\PagingSpecExtensionsTests\PagingOffsetFixture.cs" />
<Compile Include="Datastore\PagingSpecExtensionsTests\ToSortDirectionFixture.cs" />
@ -206,6 +213,7 @@
<Compile Include="Extras\Metadata\Consumers\Xbmc\FindMetadataFileFixture.cs" />
<Compile Include="FluentTest.cs" />
<Compile Include="Framework\CoreTest.cs" />
<Compile Include="Framework\FileSystemTest.cs" />
<Compile Include="Framework\DbTest.cs" />
<Compile Include="Framework\DirectDataMapper.cs" />
<Compile Include="Framework\MigrationTest.cs" />
@ -432,6 +440,14 @@
<Project>{CADDFCE0-7509-4430-8364-2074E1EEFCA2}</Project>
<Name>NzbDrone.Test.Common</Name>
</ProjectReference>
<ProjectReference Include="..\NzbDrone.Mono\NzbDrone.Mono.csproj">
<Project>{15AD7579-A314-4626-B556-663F51D97CD1}</Project>
<Name>NzbDrone.Mono</Name>
</ProjectReference>
<ProjectReference Include="..\NzbDrone.Windows\NzbDrone.Windows.csproj">
<Project>{911284D3-F130-459E-836C-2430B6FBF21D}</Project>
<Name>NzbDrone.Windows</Name>
</ProjectReference>
</ItemGroup>
<ItemGroup>
<Content Include="..\..\Logo\1024.png">

@ -391,28 +391,27 @@ namespace NzbDrone.Core.Test.OrganizerTests.FileNameBuilderTests
public void use_file_name_when_sceneName_is_null()
{
_namingConfig.RenameTracks = false;
_trackFile.RelativePath = "Linkin Park - 06 - Test";
_trackFile.Path = "Linkin Park - 06 - Test";
Subject.BuildTrackFileName(new List<Track> { _track1 }, _artist, _album, _trackFile)
.Should().Be(Path.GetFileNameWithoutExtension(_trackFile.RelativePath));
.Should().Be(Path.GetFileNameWithoutExtension(_trackFile.Path));
}
[Test]
public void use_file_name_when_sceneName_is_not_null()
{
_namingConfig.RenameTracks = false;
_trackFile.RelativePath = "Linkin Park - 06 - Test";
_trackFile.Path = "Linkin Park - 06 - Test";
_trackFile.SceneName = "SceneName";
Subject.BuildTrackFileName(new List<Track> { _track1 }, _artist, _album, _trackFile)
.Should().Be(Path.GetFileNameWithoutExtension(_trackFile.RelativePath));
.Should().Be(Path.GetFileNameWithoutExtension(_trackFile.Path));
}
[Test]
public void use_path_when_sceneName_and_relative_path_are_null()
{
_namingConfig.RenameTracks = false;
_trackFile.RelativePath = null;
_trackFile.Path = @"C:\Test\Unsorted\Artist - 01 - Test";
Subject.BuildTrackFileName(new List<Track> { _track1 }, _artist, _album, _trackFile)
@ -447,7 +446,7 @@ namespace NzbDrone.Core.Test.OrganizerTests.FileNameBuilderTests
_namingConfig.StandardTrackFormat = "{Artist Name} - {Original Title} - {Track Title}";
_trackFile.SceneName = "Linkin.Park.Meteora.320-LOL";
_trackFile.RelativePath = "30 Rock - 01 - Test";
_trackFile.Path = "30 Rock - 01 - Test";
Subject.BuildTrackFileName(new List<Track> { _track1 }, _artist, _album, _trackFile)
.Should().Be("Linkin Park - Linkin.Park.Meteora.320-LOL - City Sushi");
@ -520,10 +519,10 @@ namespace NzbDrone.Core.Test.OrganizerTests.FileNameBuilderTests
_namingConfig.StandardTrackFormat = "{Original Title}";
_trackFile.SceneName = null;
_trackFile.RelativePath = "existing.file.mkv";
_trackFile.Path = "existing.file.mkv";
Subject.BuildTrackFileName(new List<Track> { _track1 }, _artist, _album, _trackFile)
.Should().Be(Path.GetFileNameWithoutExtension(_trackFile.RelativePath));
.Should().Be(Path.GetFileNameWithoutExtension(_trackFile.Path));
}
[Test]
@ -533,7 +532,7 @@ namespace NzbDrone.Core.Test.OrganizerTests.FileNameBuilderTests
_namingConfig.StandardTrackFormat = "{Original Title}";
_trackFile.SceneName = "30.Rock.S01E01.xvid-LOL";
_trackFile.RelativePath = "30 Rock - S01E01 - Test";
_trackFile.Path = "30 Rock - S01E01 - Test";
Subject.BuildTrackFileName(new List<Track> { _track1 }, _artist, _album, _trackFile)
.Should().Be("30.Rock.S01E01.xvid-LOL");
@ -599,7 +598,7 @@ namespace NzbDrone.Core.Test.OrganizerTests.FileNameBuilderTests
_namingConfig.StandardTrackFormat = "{Artist Name} - {Original Filename}";
_trackFile.SceneName = "30.Rock.S01E01.xvid-LOL";
_trackFile.RelativePath = "30 Rock - S01E01 - Test";
_trackFile.Path = "30 Rock - S01E01 - Test";
Subject.BuildTrackFileName(new List<Track> { _track1 }, _artist, _album, _trackFile)
.Should().Be("30 Rock - 30 Rock - S01E01 - Test");
@ -612,7 +611,7 @@ namespace NzbDrone.Core.Test.OrganizerTests.FileNameBuilderTests
_namingConfig.StandardTrackFormat = "{Original Filename}";
_trackFile.SceneName = "30.Rock.S01E01.xvid-LOL";
_trackFile.RelativePath = "30 Rock - S01E01 - Test";
_trackFile.Path = "30 Rock - S01E01 - Test";
Subject.BuildTrackFileName(new List<Track> { _track1 }, _artist, _album, _trackFile)
.Should().Be("30 Rock - S01E01 - Test");

@ -8,6 +8,7 @@ using NzbDrone.Common.Disk;
using NzbDrone.Test.Common;
using NzbDrone.Core.MediaFiles;
using NzbDrone.Core.Test.Framework;
using System.IO.Abstractions;
namespace NzbDrone.Core.Test.ProviderTests.DiskScanProviderTests
{
@ -29,6 +30,10 @@ namespace NzbDrone.Core.Test.ProviderTests.DiskScanProviderTests
@"movie.exe",
@"movie"
};
Mocker.GetMock<IDiskProvider>()
.Setup(s => s.GetFileInfos(It.IsAny<string>(), It.IsAny<SearchOption>()))
.Returns(new List<IFileInfo>());
}
private IEnumerable<string> GetFiles(string folder, string subFolder = "")
@ -38,9 +43,15 @@ namespace NzbDrone.Core.Test.ProviderTests.DiskScanProviderTests
private void GivenFiles(IEnumerable<string> files)
{
var filesToReturn = files.ToArray();
var filesToReturn = files.Select(x => (FileInfoBase)new FileInfo(x)).ToList<IFileInfo>();
foreach (var file in filesToReturn)
{
TestLogger.Debug(file.Name);
}
Mocker.GetMock<IDiskProvider>()
.Setup(s => s.GetFiles(It.IsAny<string>(), SearchOption.AllDirectories))
.Setup(s => s.GetFileInfos(It.IsAny<string>(), SearchOption.AllDirectories))
.Returns(filesToReturn);
}
@ -49,8 +60,8 @@ namespace NzbDrone.Core.Test.ProviderTests.DiskScanProviderTests
{
Subject.GetAudioFiles(path);
Mocker.GetMock<IDiskProvider>().Verify(s => s.GetFiles(path, SearchOption.AllDirectories), Times.Once());
Mocker.GetMock<IDiskProvider>().Verify(s => s.GetFiles(path, SearchOption.TopDirectoryOnly), Times.Never());
Mocker.GetMock<IDiskProvider>().Verify(s => s.GetFileInfos(path, SearchOption.AllDirectories), Times.Once());
Mocker.GetMock<IDiskProvider>().Verify(s => s.GetFileInfos(path, SearchOption.TopDirectoryOnly), Times.Never());
}
[Test]
@ -58,8 +69,8 @@ namespace NzbDrone.Core.Test.ProviderTests.DiskScanProviderTests
{
Subject.GetAudioFiles(path, true);
Mocker.GetMock<IDiskProvider>().Verify(s => s.GetFiles(path, SearchOption.AllDirectories), Times.Once());
Mocker.GetMock<IDiskProvider>().Verify(s => s.GetFiles(path, SearchOption.TopDirectoryOnly), Times.Never());
Mocker.GetMock<IDiskProvider>().Verify(s => s.GetFileInfos(path, SearchOption.AllDirectories), Times.Once());
Mocker.GetMock<IDiskProvider>().Verify(s => s.GetFileInfos(path, SearchOption.TopDirectoryOnly), Times.Never());
}
[Test]
@ -67,8 +78,8 @@ namespace NzbDrone.Core.Test.ProviderTests.DiskScanProviderTests
{
Subject.GetAudioFiles(path, false);
Mocker.GetMock<IDiskProvider>().Verify(s => s.GetFiles(path, SearchOption.AllDirectories), Times.Never());
Mocker.GetMock<IDiskProvider>().Verify(s => s.GetFiles(path, SearchOption.TopDirectoryOnly), Times.Once());
Mocker.GetMock<IDiskProvider>().Verify(s => s.GetFileInfos(path, SearchOption.AllDirectories), Times.Never());
Mocker.GetMock<IDiskProvider>().Verify(s => s.GetFileInfos(path, SearchOption.TopDirectoryOnly), Times.Once());
}
[Test]

@ -15,4 +15,5 @@
<package id="Prowlin" version="0.9.4456.26422" targetFramework="net461" />
<package id="Unity" version="2.1.505.2" targetFramework="net461" />
<package id="TagLibSharp-Lidarr" version="2.2.0.19" targetFramework="net461" />
<package id="System.IO.Abstractions.TestingHelpers" version="4.0.11" targetFramework="net461" />
</packages>

@ -0,0 +1,64 @@
using System;
using FluentMigrator;
using NzbDrone.Core.Datastore.Migration.Framework;
namespace NzbDrone.Core.Datastore.Migration
{
[Migration(30)]
public class add_mediafilerepository_mtime : NzbDroneMigrationBase
{
protected override void MainDbUpgrade()
{
Alter.Table("TrackFiles").AddColumn("Modified").AsDateTime().WithDefaultValue(new DateTime(2000, 1, 1));
Alter.Table("TrackFiles").AddColumn("Path").AsString().Nullable();
// Remove anything where RelativePath is null
Execute.Sql(@"DELETE FROM TrackFiles WHERE RelativePath IS NULL");
// Remove anything not linked to a track (these shouldn't be present in version < 30)
Execute.Sql(@"DELETE FROM TrackFiles
WHERE Id IN (
SELECT TrackFiles.Id FROM TrackFiles
LEFT JOIN Tracks ON TrackFiles.Id = Tracks.TrackFileId
WHERE Tracks.Id IS NULL)");
// Remove anything where we can't get an artist path (i.e. we don't know where it is)
Execute.Sql(@"DELETE FROM TrackFiles
WHERE Id IN (
SELECT TrackFiles.Id FROM TrackFiles
LEFT JOIN Albums ON TrackFiles.AlbumId = Albums.Id
LEFT JOIN Artists on Artists.ArtistMetadataId = Albums.ArtistMetadataId
WHERE Artists.Path IS NULL)");
// Remove anything linked to unmonitored or unidentified releases. This should ensure uniqueness of track files.
Execute.Sql(@"DELETE FROM TrackFiles
WHERE Id IN (
SELECT TrackFiles.Id FROM TrackFiles
LEFT JOIN Tracks ON TrackFiles.Id = Tracks.TrackFileId
LEFT JOIN AlbumReleases ON Tracks.AlbumReleaseId = AlbumReleases.Id
WHERE AlbumReleases.Monitored = 0
OR AlbumReleases.Monitored IS NULL)");
// Populate the full paths
Execute.Sql(@"UPDATE TrackFiles
SET Path = (SELECT Artists.Path || '" + System.IO.Path.DirectorySeparatorChar + @"' || TrackFiles.RelativePath
FROM Artists
JOIN Albums ON Albums.ArtistMetadataId = Artists.ArtistMetadataId
WHERE TrackFiles.AlbumId = Albums.Id)");
// Belt and braces to ensure uniqueness
Execute.Sql(@"DELETE FROM TrackFiles
WHERE rowid NOT IN (
SELECT min(rowid)
FROM TrackFiles
GROUP BY Path
)");
// Now enforce the uniqueness constraint
Alter.Table("TrackFiles").AlterColumn("Path").AsString().NotNullable().Unique();
// Finally delete the relative path column
Delete.Column("RelativePath").FromTable("TrackFiles");
}
}
}

@ -147,7 +147,6 @@ namespace NzbDrone.Core.Datastore
.SingleOrDefault());
Mapper.Entity<TrackFile>().RegisterModel("TrackFiles")
.Ignore(f => f.Path)
.Relationship()
.HasOne(f => f.Album, f => f.AlbumId)
.For(f => f.Tracks)

@ -56,7 +56,7 @@ namespace NzbDrone.Core.DecisionEngine.Specifications.RssSync
{
foreach (var missingTrackFile in missingTrackFiles)
{
_logger.Trace("Track file {0} is missing from disk.", missingTrackFile.RelativePath);
_logger.Trace("Track file {0} is missing from disk.", missingTrackFile.Path);
}
_logger.Debug("Files for this album exist in the database but not on disk, will be unmonitored on next diskscan. skipping.");
@ -68,9 +68,7 @@ namespace NzbDrone.Core.DecisionEngine.Specifications.RssSync
private bool IsTrackFileMissing(Artist artist, TrackFile trackFile)
{
var fullPath = Path.Combine(artist.Path, trackFile.RelativePath);
return !_diskProvider.FileExists(fullPath);
return !_diskProvider.FileExists(trackFile.Path);
}
}
}

@ -88,14 +88,14 @@ namespace NzbDrone.Core.Download.Clients.Blackhole
foreach (var audioFile in _diskScanService.FilterFiles(watchFolder, _diskScanService.GetAudioFiles(watchFolder, false)))
{
var title = FileNameBuilder.CleanFileName(Path.GetFileName(audioFile));
var title = FileNameBuilder.CleanFileName(audioFile.Name);
var newWatchItem = new WatchFolderItem
{
DownloadId = Path.GetFileName(audioFile) + "_" + _diskProvider.FileGetLastWrite(audioFile).Ticks,
DownloadId = audioFile.Name + "_" + audioFile.LastWriteTimeUtc.Ticks,
Title = title,
OutputPath = new OsPath(audioFile),
OutputPath = new OsPath(audioFile.FullName),
Status = DownloadItemStatus.Completed,
RemainingTime = TimeSpan.Zero
@ -105,10 +105,10 @@ namespace NzbDrone.Core.Download.Clients.Blackhole
if (PreCheckWatchItemExpiry(newWatchItem, oldWatchItem))
{
newWatchItem.TotalSize = _diskProvider.GetFileSize(audioFile);
newWatchItem.Hash = GetHash(audioFile);
newWatchItem.TotalSize = audioFile.Length;
newWatchItem.Hash = GetHash(audioFile.FullName);
if (_diskProvider.IsFileLocked(audioFile))
if (_diskProvider.IsFileLocked(audioFile.FullName))
{
newWatchItem.Status = DownloadItemStatus.Downloading;
}

@ -50,8 +50,8 @@ namespace NzbDrone.Core.Extras.Files
protected TExtraFile ImportFile(Artist artist, TrackFile trackFile, string path, bool readOnly, string extension, string fileNameSuffix = null)
{
var newFolder = Path.GetDirectoryName(Path.Combine(artist.Path, trackFile.RelativePath));
var filenameBuilder = new StringBuilder(Path.GetFileNameWithoutExtension(trackFile.RelativePath));
var newFolder = Path.GetDirectoryName(trackFile.Path);
var filenameBuilder = new StringBuilder(Path.GetFileNameWithoutExtension(trackFile.Path));
if (fileNameSuffix.IsNotNullOrWhiteSpace())
{
@ -82,8 +82,8 @@ namespace NzbDrone.Core.Extras.Files
protected TExtraFile MoveFile(Artist artist, TrackFile trackFile, TExtraFile extraFile, string fileNameSuffix = null)
{
var newFolder = Path.GetDirectoryName(Path.Combine(artist.Path, trackFile.RelativePath));
var filenameBuilder = new StringBuilder(Path.GetFileNameWithoutExtension(trackFile.RelativePath));
var newFolder = Path.GetDirectoryName(trackFile.Path);
var filenameBuilder = new StringBuilder(Path.GetFileNameWithoutExtension(trackFile.Path));
if (fileNameSuffix.IsNotNullOrWhiteSpace())
{

@ -65,7 +65,7 @@ namespace NzbDrone.Core.Extras.Lyrics
if (groupCount > 1)
{
_logger.Warn("Multiple lyric files found with the same language and extension for {0}", Path.Combine(artist.Path, trackFile.RelativePath));
_logger.Warn("Multiple lyric files found with the same language and extension for {0}", trackFile.Path);
}
foreach (var subtitleFile in group)

@ -37,7 +37,7 @@ namespace NzbDrone.Core.Extras.Metadata.Consumers.Roksbox
public override string GetFilenameAfterMove(Artist artist, TrackFile trackFile, MetadataFile metadataFile)
{
var trackFilePath = Path.Combine(artist.Path, trackFile.RelativePath);
var trackFilePath = trackFile.Path;
if (metadataFile.Type == MetadataType.TrackMetadata)
{
@ -122,7 +122,7 @@ namespace NzbDrone.Core.Extras.Metadata.Consumers.Roksbox
return null;
}
_logger.Debug("Generating Track Metadata for: {0}", trackFile.RelativePath);
_logger.Debug("Generating Track Metadata for: {0}", trackFile.Path);
var xmlResult = string.Empty;
foreach (var track in trackFile.Tracks.Value)
@ -148,7 +148,7 @@ namespace NzbDrone.Core.Extras.Metadata.Consumers.Roksbox
}
}
return new MetadataFileResult(GetTrackMetadataFilename(trackFile.RelativePath), xmlResult.Trim(Environment.NewLine.ToCharArray()));
return new MetadataFileResult(GetTrackMetadataFilename(artist.Path.GetRelativePath(trackFile.Path)), xmlResult.Trim(Environment.NewLine.ToCharArray()));
}
public override List<ImageFileResult> ArtistImages(Artist artist)

@ -35,7 +35,7 @@ namespace NzbDrone.Core.Extras.Metadata.Consumers.Wdtv
public override string GetFilenameAfterMove(Artist artist, TrackFile trackFile, MetadataFile metadataFile)
{
var trackFilePath = Path.Combine(artist.Path, trackFile.RelativePath);
var trackFilePath = trackFile.Path;
if (metadataFile.Type == MetadataType.TrackMetadata)
{
@ -94,7 +94,7 @@ namespace NzbDrone.Core.Extras.Metadata.Consumers.Wdtv
return null;
}
_logger.Debug("Generating Track Metadata for: {0}", Path.Combine(artist.Path, trackFile.RelativePath));
_logger.Debug("Generating Track Metadata for: {0}", trackFile.Path);
var xmlResult = string.Empty;
foreach (var track in trackFile.Tracks.Value)
@ -124,7 +124,7 @@ namespace NzbDrone.Core.Extras.Metadata.Consumers.Wdtv
}
}
var filename = GetTrackMetadataFilename(trackFile.RelativePath);
var filename = GetTrackMetadataFilename(artist.Path.GetRelativePath(trackFile.Path));
return new MetadataFileResult(filename, xmlResult.Trim(Environment.NewLine.ToCharArray()));
}

@ -37,7 +37,7 @@ namespace NzbDrone.Core.Extras.Metadata.Consumers.Xbmc
public override string GetFilenameAfterMove(Artist artist, TrackFile trackFile, MetadataFile metadataFile)
{
var trackFilePath = Path.Combine(artist.Path, trackFile.RelativePath);
var trackFilePath = trackFile.Path;
if (metadataFile.Type == MetadataType.TrackMetadata)
{

@ -30,7 +30,7 @@ namespace NzbDrone.Core.Extras.Metadata
{
var existingFilename = Path.Combine(artist.Path, metadataFile.RelativePath);
var extension = Path.GetExtension(existingFilename).TrimStart('.');
var newFileName = Path.ChangeExtension(Path.Combine(artist.Path, trackFile.RelativePath), extension);
var newFileName = Path.ChangeExtension(trackFile.Path, extension);
return newFileName;
}

@ -80,7 +80,7 @@ namespace NzbDrone.Core.Extras.Metadata
files.AddIfNotNull(ProcessArtistMetadata(consumer, artist, consumerFiles));
files.AddRange(ProcessArtistImages(consumer, artist, consumerFiles));
var albumGroups = trackFiles.GroupBy(s => Path.GetDirectoryName(s.RelativePath)).ToList();
var albumGroups = trackFiles.GroupBy(s => Path.GetDirectoryName(s.Path)).ToList();
foreach (var group in albumGroups)
{
@ -146,7 +146,7 @@ namespace NzbDrone.Core.Extras.Metadata
{
var metadataFiles = _metadataFileService.GetFilesByArtist(artist.Id);
var movedFiles = new List<MetadataFile>();
var distinctTrackFilePaths = trackFiles.DistinctBy(s => Path.GetDirectoryName(s.RelativePath)).ToList();
var distinctTrackFilePaths = trackFiles.DistinctBy(s => Path.GetDirectoryName(s.Path)).ToList();
// TODO: Move EpisodeImage and EpisodeMetadata metadata files, instead of relying on consumers to do it
// (Xbmc's EpisodeImage is more than just the extension)
@ -162,7 +162,7 @@ namespace NzbDrone.Core.Extras.Metadata
foreach (var metadataFile in metadataFilesForConsumer)
{
var newFileName = consumer.GetFilenameAfterMove(artist, Path.GetDirectoryName(filePath.RelativePath), metadataFile);
var newFileName = consumer.GetFilenameAfterMove(artist, Path.GetDirectoryName(filePath.Path), metadataFile);
var existingFileName = Path.Combine(artist.Path, metadataFile.RelativePath);
if (newFileName.PathNotEquals(existingFileName))

@ -234,7 +234,7 @@ namespace NzbDrone.Core.History
//Won't have a value since we publish this event before saving to DB.
//history.Data.Add("FileId", message.ImportedEpisode.Id.ToString());
history.Data.Add("DroppedPath", message.TrackInfo.Path);
history.Data.Add("ImportedPath", Path.Combine(message.TrackInfo.Artist.Path, message.ImportedTrack.RelativePath));
history.Data.Add("ImportedPath", message.ImportedTrack.Path);
history.Data.Add("DownloadClient", message.DownloadClient);
_historyRepository.Insert(history);
@ -321,8 +321,7 @@ namespace NzbDrone.Core.History
{
var sourcePath = message.OriginalPath;
var sourceRelativePath = message.Artist.Path.GetRelativePath(message.OriginalPath);
var path = Path.Combine(message.Artist.Path, message.TrackFile.RelativePath);
var relativePath = message.TrackFile.RelativePath;
var path = message.TrackFile.Path;
foreach (var track in message.TrackFile.Tracks.Value)
{
@ -340,7 +339,6 @@ namespace NzbDrone.Core.History
history.Data.Add("SourcePath", sourcePath);
history.Data.Add("SourceRelativePath", sourceRelativePath);
history.Data.Add("Path", path);
history.Data.Add("RelativePath", relativePath);
_historyRepository.Insert(history);
}
@ -348,8 +346,7 @@ namespace NzbDrone.Core.History
public void Handle(TrackFileRetaggedEvent message)
{
var path = Path.Combine(message.Artist.Path, message.TrackFile.RelativePath);
var relativePath = message.TrackFile.RelativePath;
var path = message.TrackFile.Path;
foreach (var track in message.TrackFile.Tracks.Value)
{

@ -15,23 +15,15 @@ namespace NzbDrone.Core.Housekeeping.Housekeepers
{
var mapper = _database.GetDataMapper();
// Delete where track no longer exists
mapper.ExecuteNonQuery(@"DELETE FROM TrackFiles
// Unlink where track no longer exists
mapper.ExecuteNonQuery(@"UPDATE TrackFiles
SET AlbumId = 0
WHERE Id IN (
SELECT TrackFiles.Id FROM TrackFiles
LEFT OUTER JOIN Tracks
ON TrackFiles.Id = Tracks.TrackFileId
WHERE Tracks.Id IS NULL)");
// Delete trackfiles associated with releases that are not currently selected
mapper.ExecuteNonQuery(@"DELETE FROM TrackFiles
WHERE Id IN (
SELECT TrackFiles.Id FROM TrackFiles
JOIN Tracks ON TrackFiles.Id = Tracks.TrackFileId
JOIN AlbumReleases ON Tracks.AlbumReleaseId = AlbumReleases.Id
JOIN Albums ON AlbumReleases.AlbumId = Albums.Id
WHERE AlbumReleases.Monitored = 0)");
// Unlink Tracks where the Trackfiles entry no longer exists
mapper.ExecuteNonQuery(@"UPDATE Tracks
SET TrackFileId = 0

@ -15,6 +15,7 @@ using NLog.Fluent;
using NzbDrone.Core.MediaFiles.Events;
using NzbDrone.Core.Messaging.Events;
using TagLib;
using NzbDrone.Common.Extensions;
namespace NzbDrone.Core.MediaFiles
{
@ -176,7 +177,7 @@ namespace NzbDrone.Core.MediaFiles
}
var newTags = GetTrackMetadata(trackfile);
var path = Path.Combine(trackfile.Artist.Value.Path, trackfile.RelativePath);
var path = trackfile.Path;
var diff = ReadAudioTag(path).Diff(newTags);
@ -270,7 +271,7 @@ namespace NzbDrone.Core.MediaFiles
return;
}
var path = Path.Combine(trackfile.Artist.Value.Path, trackfile.RelativePath);
var path = trackfile.Path;
_logger.Debug($"Removing MusicBrainz tags for {path}");
RemoveMusicBrainzTags(path);
@ -312,7 +313,7 @@ namespace NzbDrone.Core.MediaFiles
continue;
}
var oldTags = ReadAudioTag(Path.Combine(f.Artist.Value.Path, f.RelativePath));
var oldTags = ReadAudioTag(f.Path);
var newTags = GetTrackMetadata(f);
var diff = oldTags.Diff(newTags);
@ -323,7 +324,7 @@ namespace NzbDrone.Core.MediaFiles
AlbumId = file.Album.Value.Id,
TrackNumbers = file.Tracks.Value.Select(e => e.AbsoluteTrackNumber).ToList(),
TrackFileId = file.Id,
RelativePath = file.RelativePath,
RelativePath = file.Artist.Value.Path.GetRelativePath(file.Path),
Changes = diff
};
}

@ -2,6 +2,7 @@ using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.IO;
using System.IO.Abstractions;
using System.Linq;
using System.Text.RegularExpressions;
using NLog;
@ -15,18 +16,17 @@ using NzbDrone.Core.Messaging.Commands;
using NzbDrone.Core.Messaging.Events;
using NzbDrone.Core.RootFolders;
using NzbDrone.Core.Music;
using NzbDrone.Core.Music.Events;
using NzbDrone.Core.MediaFiles.TrackImport;
using NzbDrone.Core.Parser.Model;
using NzbDrone.Common;
namespace NzbDrone.Core.MediaFiles
{
public interface IDiskScanService
{
void Scan(Artist artist);
string[] GetAudioFiles(string path, bool allDirectories = true);
void Scan(Artist artist, FilterFilesType filter = FilterFilesType.Known);
IFileInfo[] GetAudioFiles(string path, bool allDirectories = true);
string[] GetNonAudioFiles(string path, bool allDirectories = true);
List<IFileInfo> FilterFiles(string basePath, IEnumerable<IFileInfo> files);
List<string> FilterFiles(string basePath, IEnumerable<string> files);
}
@ -70,7 +70,7 @@ namespace NzbDrone.Core.MediaFiles
private static readonly Regex ExcludedSubFoldersRegex = new Regex(@"(?:\\|\/|^)(?:extras|@eadir|extrafanart|plex versions|\.[^\\/]+)(?:\\|\/)", RegexOptions.Compiled | RegexOptions.IgnoreCase);
private static readonly Regex ExcludedFilesRegex = new Regex(@"^\._|^Thumbs\.db$|^\.DS_store$|\.partial~$", RegexOptions.Compiled | RegexOptions.IgnoreCase);
public void Scan(Artist artist)
public void Scan(Artist artist, FilterFilesType filter = FilterFilesType.Known)
{
var rootFolder = _rootFolderService.GetBestRootFolderPath(artist.Path);
@ -113,44 +113,70 @@ namespace NzbDrone.Core.MediaFiles
var mediaFileList = FilterFiles(artist.Path, GetAudioFiles(artist.Path)).ToList();
musicFilesStopwatch.Stop();
_logger.Trace("Finished getting track files for: {0} [{1}]", artist, musicFilesStopwatch.Elapsed);
CleanMediaFiles(artist, mediaFileList);
CleanMediaFiles(artist, mediaFileList.Select(x => x.FullName).ToList());
var decisionsStopwatch = Stopwatch.StartNew();
var decisions = _importDecisionMaker.GetImportDecisions(mediaFileList, artist, false);
var decisions = _importDecisionMaker.GetImportDecisions(mediaFileList, artist, filter, true);
decisionsStopwatch.Stop();
_logger.Debug("Import decisions complete for: {0} [{1}]", artist, decisionsStopwatch.Elapsed);
var importStopwatch = Stopwatch.StartNew();
_importApprovedTracks.Import(decisions, false);
// decisions may have been filtered to just new files. Anything new and approved will have been inserted.
// Now we need to make sure anything new but not approved gets inserted
// Note that knownFiles will include anything imported just now
var knownFiles = _mediaFileService.GetFilesWithBasePath(artist.Path);
var newFiles = decisions
.ExceptBy(x => x.Item.Path, knownFiles, x => x.Path, PathEqualityComparer.Instance)
.Select(decision => new TrackFile {
Path = decision.Item.Path,
Size = decision.Item.Size,
Modified = decision.Item.Modified,
DateAdded = DateTime.UtcNow,
Quality = decision.Item.Quality,
MediaInfo = decision.Item.FileTrackInfo.MediaInfo,
Language = decision.Item.Language
})
.ToList();
_mediaFileService.AddMany(newFiles);
_logger.Debug($"Inserted {newFiles.Count} new unmatched trackfiles");
// finally update info on size/modified for existing files
var updatedFiles = knownFiles
.Join(decisions,
x => x.Path,
x => x.Item.Path,
(file, decision) => new {
File = file,
Item = decision.Item
},
PathEqualityComparer.Instance)
.Where(x => x.File.Size != x.Item.Size ||
Math.Abs((x.File.Modified - x.Item.Modified).TotalSeconds) > 1 )
.Select(x => {
x.File.Size = x.Item.Size;
x.File.Modified = x.Item.Modified;
x.File.MediaInfo = x.Item.FileTrackInfo.MediaInfo;
x.File.Quality = x.Item.Quality;
return x.File;
})
.ToList();
_mediaFileService.Update(updatedFiles);
_logger.Debug($"Updated info for {updatedFiles.Count} known files");
RemoveEmptyArtistFolder(artist.Path);
UpdateMediaInfo(artist, decisions.Select(x => x.Item).ToList());
CompletedScanning(artist);
importStopwatch.Stop();
_logger.Debug("Track import complete for: {0} [{1}]", artist, importStopwatch.Elapsed);
}
private void UpdateMediaInfo(Artist artist, List<LocalTrack> mediaFiles)
{
var existingFiles = _mediaFileService.GetFilesByArtist(artist.Id);
var toUpdate = new List<TrackFile>(existingFiles.Count);
foreach (var file in existingFiles)
{
var path = Path.Combine(artist.Path, file.RelativePath);
var scannedFile = mediaFiles.FirstOrDefault(x => PathEqualityComparer.Instance.Equals(path, x.Path));
if (scannedFile != null)
{
file.MediaInfo = scannedFile.FileTrackInfo.MediaInfo;
toUpdate.Add(file);
}
}
_logger.Debug($"Updating Media Info for:\n{string.Join("\n", toUpdate)}");
_mediaFileService.UpdateMediaInfo(toUpdate);
}
private void CleanMediaFiles(Artist artist, List<string> mediaFileList)
{
_logger.Debug("{0} Cleaning up media files in DB", artist);
@ -163,14 +189,14 @@ namespace NzbDrone.Core.MediaFiles
_eventAggregator.PublishEvent(new ArtistScannedEvent(artist));
}
public string[] GetAudioFiles(string path, bool allDirectories = true)
public IFileInfo[] GetAudioFiles(string path, bool allDirectories = true)
{
_logger.Debug("Scanning '{0}' for music files", path);
var searchOption = allDirectories ? SearchOption.AllDirectories : SearchOption.TopDirectoryOnly;
var filesOnDisk = _diskProvider.GetFiles(path, searchOption).ToList();
var filesOnDisk = _diskProvider.GetFileInfos(path, searchOption);
var mediaFileList = filesOnDisk.Where(file => MediaFileExtensions.Extensions.Contains(Path.GetExtension(file)))
var mediaFileList = filesOnDisk.Where(file => MediaFileExtensions.Extensions.Contains(file.Extension))
.ToList();
_logger.Trace("{0} files were found in {1}", filesOnDisk.Count, path);
@ -202,6 +228,13 @@ namespace NzbDrone.Core.MediaFiles
.ToList();
}
public List<IFileInfo> FilterFiles(string basePath, IEnumerable<IFileInfo> files)
{
return files.Where(file => !ExcludedSubFoldersRegex.IsMatch(basePath.GetRelativePath(file.FullName)))
.Where(file => !ExcludedFilesRegex.IsMatch(file.Name))
.ToList();
}
private void SetPermissions(string path)
{
if (!_configService.SetPermissionsLinux)

@ -1,6 +1,7 @@
using System;
using System.Collections.Generic;
using System.IO;
using System.IO.Abstractions;
using System.Linq;
using NLog;
using NzbDrone.Common.Disk;
@ -17,9 +18,9 @@ namespace NzbDrone.Core.MediaFiles
{
public interface IDownloadedTracksImportService
{
List<ImportResult> ProcessRootFolder(DirectoryInfo directoryInfo);
List<ImportResult> ProcessRootFolder(IDirectoryInfo directoryInfo);
List<ImportResult> ProcessPath(string path, ImportMode importMode = ImportMode.Auto, Artist artist = null, DownloadClientItem downloadClientItem = null);
bool ShouldDeleteFolder(DirectoryInfo directoryInfo, Artist artist);
bool ShouldDeleteFolder(IDirectoryInfo directoryInfo, Artist artist);
}
public class DownloadedTracksImportService : IDownloadedTracksImportService
@ -52,19 +53,19 @@ namespace NzbDrone.Core.MediaFiles
_logger = logger;
}
public List<ImportResult> ProcessRootFolder(DirectoryInfo directoryInfo)
public List<ImportResult> ProcessRootFolder(IDirectoryInfo directoryInfo)
{
var results = new List<ImportResult>();
foreach (var subFolder in _diskProvider.GetDirectories(directoryInfo.FullName))
foreach (var subFolder in _diskProvider.GetDirectoryInfos(directoryInfo.FullName))
{
var folderResults = ProcessFolder(new DirectoryInfo(subFolder), ImportMode.Auto, null);
var folderResults = ProcessFolder(subFolder, ImportMode.Auto, null);
results.AddRange(folderResults);
}
foreach (var audioFile in _diskScanService.GetAudioFiles(directoryInfo.FullName, false))
{
var fileResults = ProcessFile(new FileInfo(audioFile), ImportMode.Auto, null);
var fileResults = ProcessFile(audioFile, ImportMode.Auto, null);
results.AddRange(fileResults);
}
@ -75,7 +76,7 @@ namespace NzbDrone.Core.MediaFiles
{
if (_diskProvider.FolderExists(path))
{
var directoryInfo = new DirectoryInfo(path);
var directoryInfo = _diskProvider.GetDirectoryInfo(path);
if (artist == null)
{
@ -87,7 +88,7 @@ namespace NzbDrone.Core.MediaFiles
if (_diskProvider.FileExists(path))
{
var fileInfo = new FileInfo(path);
var fileInfo = _diskProvider.GetFileInfo(path);
if (artist == null)
{
@ -103,14 +104,14 @@ namespace NzbDrone.Core.MediaFiles
return new List<ImportResult>();
}
public bool ShouldDeleteFolder(DirectoryInfo directoryInfo, Artist artist)
public bool ShouldDeleteFolder(IDirectoryInfo directoryInfo, Artist artist)
{
var audioFiles = _diskScanService.GetAudioFiles(directoryInfo.FullName);
var rarFiles = _diskProvider.GetFiles(directoryInfo.FullName, SearchOption.AllDirectories).Where(f => Path.GetExtension(f).Equals(".rar", StringComparison.OrdinalIgnoreCase));
foreach (var audioFile in audioFiles)
{
var albumParseResult = Parser.Parser.ParseMusicTitle(Path.GetFileName(audioFile));
var albumParseResult = Parser.Parser.ParseMusicTitle(audioFile.Name);
if (albumParseResult == null)
{
@ -131,7 +132,7 @@ namespace NzbDrone.Core.MediaFiles
return true;
}
private List<ImportResult> ProcessFolder(DirectoryInfo directoryInfo, ImportMode importMode, DownloadClientItem downloadClientItem)
private List<ImportResult> ProcessFolder(IDirectoryInfo directoryInfo, ImportMode importMode, DownloadClientItem downloadClientItem)
{
var cleanedUpName = GetCleanedUpFolderName(directoryInfo.Name);
var artist = _parsingService.GetArtist(cleanedUpName);
@ -149,7 +150,7 @@ namespace NzbDrone.Core.MediaFiles
return ProcessFolder(directoryInfo, importMode, artist, downloadClientItem);
}
private List<ImportResult> ProcessFolder(DirectoryInfo directoryInfo, ImportMode importMode, Artist artist, DownloadClientItem downloadClientItem)
private List<ImportResult> ProcessFolder(IDirectoryInfo directoryInfo, ImportMode importMode, Artist artist, DownloadClientItem downloadClientItem)
{
if (_artistService.ArtistPathExists(directoryInfo.FullName))
{
@ -185,17 +186,17 @@ namespace NzbDrone.Core.MediaFiles
{
foreach (var audioFile in audioFiles)
{
if (_diskProvider.IsFileLocked(audioFile))
if (_diskProvider.IsFileLocked(audioFile.FullName))
{
return new List<ImportResult>
{
FileIsLockedResult(audioFile)
FileIsLockedResult(audioFile.FullName)
};
}
}
}
var decisions = _importDecisionMaker.GetImportDecisions(audioFiles.ToList(), artist, trackInfo);
var decisions = _importDecisionMaker.GetImportDecisions(audioFiles, artist, trackInfo);
var importResults = _importApprovedTracks.Import(decisions, true, downloadClientItem, importMode);
if (importMode == ImportMode.Auto)
@ -214,7 +215,7 @@ namespace NzbDrone.Core.MediaFiles
return importResults;
}
private List<ImportResult> ProcessFile(FileInfo fileInfo, ImportMode importMode, DownloadClientItem downloadClientItem)
private List<ImportResult> ProcessFile(IFileInfo fileInfo, ImportMode importMode, DownloadClientItem downloadClientItem)
{
var artist = _parsingService.GetArtist(Path.GetFileNameWithoutExtension(fileInfo.Name));
@ -231,7 +232,7 @@ namespace NzbDrone.Core.MediaFiles
return ProcessFile(fileInfo, importMode, artist, downloadClientItem);
}
private List<ImportResult> ProcessFile(FileInfo fileInfo, ImportMode importMode, Artist artist, DownloadClientItem downloadClientItem)
private List<ImportResult> ProcessFile(IFileInfo fileInfo, ImportMode importMode, Artist artist, DownloadClientItem downloadClientItem)
{
if (Path.GetFileNameWithoutExtension(fileInfo.Name).StartsWith("._"))
{
@ -254,7 +255,7 @@ namespace NzbDrone.Core.MediaFiles
}
}
var decisions = _importDecisionMaker.GetImportDecisions(new List<string>() { fileInfo.FullName }, artist, null);
var decisions = _importDecisionMaker.GetImportDecisions(new List<IFileInfo>() { fileInfo }, artist, null);
return _importApprovedTracks.Import(decisions, true, downloadClientItem, importMode);
}

@ -0,0 +1,9 @@
namespace NzbDrone.Core.MediaFiles
{
public enum FilterFilesType
{
None,
Matched,
Known
}
}

@ -47,7 +47,7 @@ namespace NzbDrone.Core.MediaFiles
public void DeleteTrackFile(Artist artist, TrackFile trackFile)
{
var fullPath = Path.Combine(artist.Path, trackFile.RelativePath);
var fullPath = trackFile.Path;
var rootFolder = _diskProvider.GetParentFolder(artist.Path);
if (!_diskProvider.FolderExists(rootFolder))

@ -1,4 +1,5 @@
using System.Collections.Generic;
using System.Linq;
using Marr.Data.QGen;
using NzbDrone.Core.Datastore;
using NzbDrone.Core.Messaging.Events;
@ -11,7 +12,8 @@ namespace NzbDrone.Core.MediaFiles
List<TrackFile> GetFilesByArtist(int artistId);
List<TrackFile> GetFilesByAlbum(int albumId);
List<TrackFile> GetFilesByRelease(int releaseId);
List<TrackFile> GetFilesWithRelativePath(int artistId, string relativePath);
List<TrackFile> GetFilesWithBasePath(string path);
TrackFile GetFileWithPath(string path);
}
@ -55,15 +57,17 @@ namespace NzbDrone.Core.MediaFiles
.Where<Track>(x => x.AlbumReleaseId == releaseId)
.ToList();
}
public List<TrackFile> GetFilesWithRelativePath(int artistId, string relativePath)
public List<TrackFile> GetFilesWithBasePath(string path)
{
return Query
.Join<Track, AlbumRelease>(JoinType.Inner, t => t.AlbumRelease, (t, r) => t.AlbumReleaseId == r.Id)
.Where<AlbumRelease>(r => r.Monitored == true)
.AndWhere(t => t.Artist.Value.Id == artistId && t.RelativePath == relativePath)
.Where(x => x.Path.StartsWith(path))
.ToList();
}
public TrackFile GetFileWithPath(string path)
{
return Query.Where(x => x.Path == path).SingleOrDefault();
}
}
}

@ -1,5 +1,6 @@
using System.Collections.Generic;
using System.IO;
using System.IO.Abstractions;
using System.Linq;
using NLog;
using NzbDrone.Core.MediaFiles.Events;
@ -8,6 +9,7 @@ using NzbDrone.Common;
using NzbDrone.Core.Music;
using System;
using NzbDrone.Core.Music.Events;
using NzbDrone.Common.Extensions;
namespace NzbDrone.Core.MediaFiles
{
@ -21,10 +23,11 @@ namespace NzbDrone.Core.MediaFiles
List<TrackFile> GetFilesByArtist(int artistId);
List<TrackFile> GetFilesByAlbum(int albumId);
List<TrackFile> GetFilesByRelease(int releaseId);
List<string> FilterExistingFiles(List<string> files, Artist artist);
List<IFileInfo> FilterUnchangedFiles(List<IFileInfo> files, Artist artist, FilterFilesType filter);
TrackFile Get(int id);
List<TrackFile> Get(IEnumerable<int> ids);
List<TrackFile> GetFilesWithRelativePath(int artistId, string relativePath);
List<TrackFile> GetFilesWithBasePath(string path);
TrackFile GetFileWithPath(string path);
void UpdateMediaInfo(List<TrackFile> trackFiles);
}
@ -70,19 +73,57 @@ namespace NzbDrone.Core.MediaFiles
public void Delete(TrackFile trackFile, DeleteMediaFileReason reason)
{
trackFile.Path = Path.Combine(trackFile.Artist.Value.Path, trackFile.RelativePath);
_mediaFileRepository.Delete(trackFile);
_eventAggregator.PublishEvent(new TrackFileDeletedEvent(trackFile, reason));
// If the trackfile wasn't mapped to a track, don't publish an event
if (trackFile.AlbumId > 0)
{
_eventAggregator.PublishEvent(new TrackFileDeletedEvent(trackFile, reason));
}
}
public List<string> FilterExistingFiles(List<string> files, Artist artist)
public List<IFileInfo> FilterUnchangedFiles(List<IFileInfo> files, Artist artist, FilterFilesType filter)
{
var artistFiles = GetFilesByArtist(artist.Id).Select(f => Path.Combine(artist.Path, f.RelativePath)).ToList();
_logger.Debug($"Filtering {files.Count} files for unchanged files");
var knownFiles = GetFilesWithBasePath(artist.Path);
_logger.Trace($"Got {knownFiles.Count} existing files");
if (!artistFiles.Any()) return files;
if (!knownFiles.Any()) return files;
return files.Except(artistFiles, PathEqualityComparer.Instance).ToList();
var combined = files
.Join(knownFiles,
f => f.FullName,
af => af.Path,
(f, af) => new { DiskFile = f, DbFile = af},
PathEqualityComparer.Instance)
.ToList();
List<IFileInfo> unwanted = null;
if (filter == FilterFilesType.Known)
{
unwanted = combined
.Where(x => x.DiskFile.Length == x.DbFile.Size &&
Math.Abs((x.DiskFile.LastWriteTimeUtc - x.DbFile.Modified).TotalSeconds) <= 1)
.Select(x => x.DiskFile)
.ToList();
_logger.Trace($"{unwanted.Count} unchanged existing files");
}
else if (filter == FilterFilesType.Matched)
{
unwanted = combined
.Where(x => x.DiskFile.Length == x.DbFile.Size &&
Math.Abs((x.DiskFile.LastWriteTimeUtc - x.DbFile.Modified).TotalSeconds) <= 1 &&
(x.DbFile.Tracks == null || (x.DbFile.Tracks.IsLoaded && x.DbFile.Tracks.Value.Any())))
.Select(x => x.DiskFile)
.ToList();
_logger.Trace($"{unwanted.Count} unchanged and matched files");
}
else
{
throw new ArgumentException("Unrecognised value of FilterFilesType filter");
}
return files.Except(unwanted).ToList();
}
public TrackFile Get(int id)
@ -95,9 +136,14 @@ namespace NzbDrone.Core.MediaFiles
return _mediaFileRepository.Get(ids).ToList();
}
public List<TrackFile> GetFilesWithRelativePath(int artistId, string relativePath)
public List<TrackFile> GetFilesWithBasePath(string path)
{
return _mediaFileRepository.GetFilesWithBasePath(path);
}
public TrackFile GetFileWithPath(string path)
{
return _mediaFileRepository.GetFilesWithRelativePath(artistId, relativePath);
return _mediaFileRepository.GetFileWithPath(path);
}
public void HandleAsync(AlbumDeletedEvent message)

@ -39,7 +39,7 @@ namespace NzbDrone.Core.MediaFiles
foreach (var artistFile in artistFiles)
{
var trackFile = artistFile;
var trackFilePath = Path.Combine(artist.Path, trackFile.RelativePath);
var trackFilePath = trackFile.Path;
try
{
@ -49,15 +49,7 @@ namespace NzbDrone.Core.MediaFiles
_mediaFileService.Delete(artistFile, DeleteMediaFileReason.MissingFromDisk);
continue;
}
if (tracks.None(e => e.TrackFileId == trackFile.Id))
{
_logger.Debug("File [{0}] is not assigned to any artist, removing from db", trackFilePath);
_mediaFileService.Delete(trackFile, DeleteMediaFileReason.NoLinkedEpisodes);
continue;
}
}
catch (Exception ex)
{
_logger.Error(ex, "Unable to cleanup TrackFile in DB: {0}", trackFile.Id);

@ -84,7 +84,7 @@ namespace NzbDrone.Core.MediaFiles
{
var file = f;
var tracksInFile = tracks.Where(e => e.TrackFileId == file.Id).ToList();
var trackFilePath = Path.Combine(artist.Path, file.RelativePath);
var trackFilePath = file.Path;
if (!tracksInFile.Any())
{
@ -105,7 +105,7 @@ namespace NzbDrone.Core.MediaFiles
AlbumId = album.Id,
TrackNumbers = tracksInFile.Select(e => e.AbsoluteTrackNumber).ToList(),
TrackFileId = file.Id,
ExistingPath = file.RelativePath,
ExistingPath = artist.Path.GetRelativePath(file.Path),
NewPath = artist.Path.GetRelativePath(newPath)
};
}
@ -118,7 +118,7 @@ namespace NzbDrone.Core.MediaFiles
foreach (var trackFile in trackFiles)
{
var trackFilePath = Path.Combine(artist.Path, trackFile.RelativePath);
var trackFilePath = trackFile.Path;
try
{

@ -13,9 +13,9 @@ namespace NzbDrone.Core.MediaFiles
public class TrackFile : ModelBase
{
// these are model properties
public string RelativePath { get; set; }
public string Path { get; set; }
public long Size { get; set; }
public DateTime Modified { get; set; }
public DateTime DateAdded { get; set; }
public string SceneName { get; set; }
public string ReleaseGroup { get; set; }
@ -31,7 +31,7 @@ namespace NzbDrone.Core.MediaFiles
public override string ToString()
{
return string.Format("[{0}] {1}", Id, RelativePath);
return string.Format("[{0}] {1}", Id, Path);
}
public string GetSceneOrFileName()
@ -41,11 +41,6 @@ namespace NzbDrone.Core.MediaFiles
return SceneName;
}
if (RelativePath.IsNotNullOrWhiteSpace())
{
return System.IO.Path.GetFileName(RelativePath);
}
if (Path.IsNotNullOrWhiteSpace())
{
return System.IO.Path.GetFileName(Path);

@ -66,7 +66,7 @@ namespace NzbDrone.Core.MediaFiles
var tracks = _trackService.GetTracksByFileId(trackFile.Id);
var album = _albumService.GetAlbum(trackFile.AlbumId);
var newFileName = _buildFileNames.BuildTrackFileName(tracks, artist, album, trackFile);
var filePath = _buildFileNames.BuildTrackFilePath(artist, album, newFileName, Path.GetExtension(trackFile.RelativePath));
var filePath = _buildFileNames.BuildTrackFilePath(artist, album, newFileName, Path.GetExtension(trackFile.Path));
EnsureTrackFolder(trackFile, artist, album, filePath);
@ -112,7 +112,7 @@ namespace NzbDrone.Core.MediaFiles
Ensure.That(artist, () => artist).IsNotNull();
Ensure.That(destinationFilePath, () => destinationFilePath).IsValidPath();
var trackFilePath = trackFile.Path ?? Path.Combine(artist.Path, trackFile.RelativePath);
var trackFilePath = trackFile.Path;
if (!_diskProvider.FileExists(trackFilePath))
{
@ -126,7 +126,7 @@ namespace NzbDrone.Core.MediaFiles
_diskTransferService.TransferFile(trackFilePath, destinationFilePath, mode);
trackFile.RelativePath = artist.Path.GetRelativePath(destinationFilePath);
trackFile.Path = destinationFilePath;
_updateTrackFileService.ChangeFileDateForFile(trackFile, artist, tracks);

@ -177,9 +177,12 @@ namespace NzbDrone.Core.MediaFiles.TrackImport.Identification
{
var localTracks = trackfiles.Select(x => new LocalTrack {
Path = x.Path,
Size = x.Size,
Modified = x.Modified,
FileTrackInfo = _audioTagService.ReadTags(x.Path),
ExistingFile = true,
AdditionalFile = true
AdditionalFile = true,
Quality = x.Quality
})
.ToList();
@ -335,15 +338,6 @@ namespace NzbDrone.Core.MediaFiles.TrackImport.Identification
.Distinct()
.ToDictionary(id => id, id => includeExisting ? _mediaFileService.GetFilesByAlbum(id) : new List<TrackFile>());
// populate the path. Artist will have been returned by mediaFileService
foreach (var trackfiles in albumTracks.Values)
{
foreach (var trackfile in trackfiles)
{
trackfile.Path = Path.Combine(trackfile.Artist.Value.Path, trackfile.RelativePath);
}
}
return releases.Select(x => new CandidateAlbumRelease {
AlbumRelease = x,
ExistingTracks = albumTracks[x.AlbumId]

@ -92,12 +92,11 @@ namespace NzbDrone.Core.MediaFiles.TrackImport
foreach (var previousFile in previousFiles)
{
var trackFilePath = Path.Combine(artist.Path, previousFile.RelativePath);
var subfolder = rootFolder.GetRelativePath(_diskProvider.GetParentFolder(trackFilePath));
if (_diskProvider.FileExists(trackFilePath))
var subfolder = rootFolder.GetRelativePath(_diskProvider.GetParentFolder(previousFile.Path));
if (_diskProvider.FileExists(previousFile.Path))
{
_logger.Debug("Removing existing track file: {0}", previousFile);
_recycleBinProvider.DeleteFile(trackFilePath, subfolder);
_recycleBinProvider.DeleteFile(previousFile.Path, subfolder);
}
_mediaFileService.Delete(previousFile, DeleteMediaFileReason.Upgrade);
}
@ -154,7 +153,8 @@ namespace NzbDrone.Core.MediaFiles.TrackImport
var trackFile = new TrackFile {
Path = localTrack.Path.CleanFilePath(),
Size = _diskProvider.GetFileSize(localTrack.Path),
Size = localTrack.Size,
Modified = localTrack.Modified,
DateAdded = DateTime.UtcNow,
ReleaseGroup = localTrack.ReleaseGroup,
Quality = localTrack.Quality,
@ -190,12 +190,10 @@ namespace NzbDrone.Core.MediaFiles.TrackImport
}
else
{
trackFile.RelativePath = localTrack.Artist.Path.GetRelativePath(trackFile.Path);
// Delete existing files from the DB mapped to this path
var previousFiles = _mediaFileService.GetFilesWithRelativePath(localTrack.Artist.Id, trackFile.RelativePath);
var previousFile = _mediaFileService.GetFileWithPath(trackFile.Path);
foreach (var previousFile in previousFiles)
if (previousFile != null)
{
_mediaFileService.Delete(previousFile, DeleteMediaFileReason.ManualOverride);
}

@ -11,14 +11,15 @@ using NzbDrone.Core.Messaging.Events;
using NzbDrone.Core.Download;
using NzbDrone.Core.MediaFiles.TrackImport.Aggregation;
using NzbDrone.Core.MediaFiles.TrackImport.Identification;
using System.IO.Abstractions;
namespace NzbDrone.Core.MediaFiles.TrackImport
{
public interface IMakeImportDecision
{
List<ImportDecision<LocalTrack>> GetImportDecisions(List<string> musicFiles, Artist artist, bool includeExisting);
List<ImportDecision<LocalTrack>> GetImportDecisions(List<string> musicFiles, Artist artist, ParsedTrackInfo folderInfo);
List<ImportDecision<LocalTrack>> GetImportDecisions(List<string> musicFiles, Artist artist, Album album, AlbumRelease albumRelease, DownloadClientItem downloadClientItem, ParsedTrackInfo folderInfo, bool filterExistingFiles, bool newDownload, bool singleRelease, bool includeExisting);
List<ImportDecision<LocalTrack>> GetImportDecisions(List<IFileInfo> musicFiles, Artist artist, FilterFilesType filter, bool includeExisting);
List<ImportDecision<LocalTrack>> GetImportDecisions(List<IFileInfo> musicFiles, Artist artist, ParsedTrackInfo folderInfo);
List<ImportDecision<LocalTrack>> GetImportDecisions(List<IFileInfo> musicFiles, Artist artist, Album album, AlbumRelease albumRelease, DownloadClientItem downloadClientItem, ParsedTrackInfo folderInfo, FilterFilesType filter, bool newDownload, bool singleRelease, bool includeExisting);
}
public class ImportDecisionMaker : IMakeImportDecision
@ -60,25 +61,33 @@ namespace NzbDrone.Core.MediaFiles.TrackImport
_logger = logger;
}
public List<ImportDecision<LocalTrack>> GetImportDecisions(List<string> musicFiles, Artist artist, bool includeExisting)
public List<ImportDecision<LocalTrack>> GetImportDecisions(List<IFileInfo> musicFiles, Artist artist, FilterFilesType filter, bool includeExisting)
{
return GetImportDecisions(musicFiles, artist, null, null, null, null, false, false, false, true);
return GetImportDecisions(musicFiles, artist, null, null, null, null, filter, false, false, true);
}
public List<ImportDecision<LocalTrack>> GetImportDecisions(List<string> musicFiles, Artist artist, ParsedTrackInfo folderInfo)
public List<ImportDecision<LocalTrack>> GetImportDecisions(List<IFileInfo> musicFiles, Artist artist, ParsedTrackInfo folderInfo)
{
return GetImportDecisions(musicFiles, artist, null, null, null, folderInfo, false, true, false, false);
return GetImportDecisions(musicFiles, artist, null, null, null, folderInfo, FilterFilesType.None, true, false, false);
}
public List<ImportDecision<LocalTrack>> GetImportDecisions(List<string> musicFiles, Artist artist, Album album, AlbumRelease albumRelease, DownloadClientItem downloadClientItem, ParsedTrackInfo folderInfo, bool filterExistingFiles, bool newDownload, bool singleRelease, bool includeExisting)
public List<ImportDecision<LocalTrack>> GetImportDecisions(List<IFileInfo> musicFiles, Artist artist, Album album, AlbumRelease albumRelease, DownloadClientItem downloadClientItem, ParsedTrackInfo folderInfo, FilterFilesType filter, bool newDownload, bool singleRelease, bool includeExisting)
{
var watch = new System.Diagnostics.Stopwatch();
watch.Start();
var files = filterExistingFiles && (artist != null) ? _mediaFileService.FilterExistingFiles(musicFiles, artist) : musicFiles;
var files = filter != FilterFilesType.None && (artist != null) ? _mediaFileService.FilterUnchangedFiles(musicFiles, artist, filter) : musicFiles;
var localTracks = new List<LocalTrack>();
var decisions = new List<ImportDecision<LocalTrack>>();
_logger.Debug("Analyzing {0}/{1} files.", files.Count, musicFiles.Count);
if (!files.Any())
{
return decisions;
}
ParsedAlbumInfo downloadClientItemInfo = null;
if (downloadClientItem != null)
@ -86,9 +95,6 @@ namespace NzbDrone.Core.MediaFiles.TrackImport
downloadClientItemInfo = Parser.Parser.ParseAlbumTitle(downloadClientItem.Title);
}
var localTracks = new List<LocalTrack>();
var decisions = new List<ImportDecision<LocalTrack>>();
foreach (var file in files)
{
var localTrack = new LocalTrack
@ -97,8 +103,10 @@ namespace NzbDrone.Core.MediaFiles.TrackImport
Album = album,
DownloadClientAlbumInfo = downloadClientItemInfo,
FolderTrackInfo = folderInfo,
Path = file,
FileTrackInfo = _audioTagService.ReadTags(file),
Path = file.FullName,
Size = file.Length,
Modified = file.LastWriteTimeUtc,
FileTrackInfo = _audioTagService.ReadTags(file.FullName),
ExistingFile = !newDownload,
AdditionalFile = false
};

@ -1,6 +1,7 @@
using System;
using System.Collections.Generic;
using System.IO;
using System.IO.Abstractions;
using System.Linq;
using NLog;
using NzbDrone.Common.Disk;
@ -20,7 +21,7 @@ namespace NzbDrone.Core.MediaFiles.TrackImport.Manual
{
public interface IManualImportService
{
List<ManualImportItem> GetMediaFiles(string path, string downloadId, bool filterExistingFiles, bool replaceExistingFiles);
List<ManualImportItem> GetMediaFiles(string path, string downloadId, FilterFilesType filter, bool replaceExistingFiles);
List<ManualImportItem> UpdateItems(List<ManualImportItem> item);
}
@ -72,7 +73,7 @@ namespace NzbDrone.Core.MediaFiles.TrackImport.Manual
_logger = logger;
}
public List<ManualImportItem> GetMediaFiles(string path, string downloadId, bool filterExistingFiles, bool replaceExistingFiles)
public List<ManualImportItem> GetMediaFiles(string path, string downloadId, FilterFilesType filter, bool replaceExistingFiles)
{
if (downloadId.IsNotNullOrWhiteSpace())
{
@ -93,16 +94,16 @@ namespace NzbDrone.Core.MediaFiles.TrackImport.Manual
return new List<ManualImportItem>();
}
var decision = _importDecisionMaker.GetImportDecisions(new List<string> { path }, null, null, null, null, null, false, true, false, !replaceExistingFiles);
var decision = _importDecisionMaker.GetImportDecisions(new List<IFileInfo> { _diskProvider.GetFileInfo(path) }, null, null, null, null, null, FilterFilesType.None, true, false, !replaceExistingFiles);
var result = MapItem(decision.First(), Path.GetDirectoryName(path), downloadId, replaceExistingFiles, false);
return new List<ManualImportItem> { result };
}
return ProcessFolder(path, downloadId, filterExistingFiles, replaceExistingFiles);
return ProcessFolder(path, downloadId, filter, replaceExistingFiles);
}
private List<ManualImportItem> ProcessFolder(string folder, string downloadId, bool filterExistingFiles, bool replaceExistingFiles)
private List<ManualImportItem> ProcessFolder(string folder, string downloadId, FilterFilesType filter, bool replaceExistingFiles)
{
var directoryInfo = new DirectoryInfo(folder);
var artist = _parsingService.GetArtist(directoryInfo.Name);
@ -115,11 +116,11 @@ namespace NzbDrone.Core.MediaFiles.TrackImport.Manual
var folderInfo = Parser.Parser.ParseMusicTitle(directoryInfo.Name);
var artistFiles = _diskScanService.GetAudioFiles(folder).ToList();
var decisions = _importDecisionMaker.GetImportDecisions(artistFiles, artist, null, null, null, folderInfo, filterExistingFiles, true, false, !replaceExistingFiles);
var decisions = _importDecisionMaker.GetImportDecisions(artistFiles, artist, null, null, null, folderInfo, filter, true, false, !replaceExistingFiles);
// paths will be different for new and old files which is why we need to map separately
var newFiles = artistFiles.Join(decisions,
f => f,
f => f.FullName,
d => d.Item.Path,
(f, d) => new { File = f, Decision = d },
PathEqualityComparer.Instance);
@ -145,7 +146,7 @@ namespace NzbDrone.Core.MediaFiles.TrackImport.Manual
var disableReleaseSwitching = group.First().DisableReleaseSwitching;
var decisions = _importDecisionMaker.GetImportDecisions(group.Select(x => x.Path).ToList(), group.First().Artist, group.First().Album, group.First().Release, null, null, false, true, true, !replaceExistingFiles);
var decisions = _importDecisionMaker.GetImportDecisions(group.Select(x => _diskProvider.GetFileInfo(x.Path)).ToList(), group.First().Artist, group.First().Album, group.First().Release, null, null, FilterFilesType.None, true, true, !replaceExistingFiles);
var existingItems = group.Join(decisions,
i => i.Path,
@ -260,7 +261,6 @@ namespace NzbDrone.Core.MediaFiles.TrackImport.Manual
ExistingFile = artist.Path.IsParentPath(file.Path),
Tracks = tracks,
FileTrackInfo = fileTrackInfo,
MediaInfo = fileTrackInfo.MediaInfo,
Path = file.Path,
Quality = file.Quality,
Language = file.Language,
@ -305,7 +305,7 @@ namespace NzbDrone.Core.MediaFiles.TrackImport.Manual
if (_diskProvider.FolderExists(trackedDownload.DownloadItem.OutputPath.FullPath))
{
if (_downloadedTracksImportService.ShouldDeleteFolder(
new DirectoryInfo(trackedDownload.DownloadItem.OutputPath.FullPath),
_diskProvider.GetDirectoryInfo(trackedDownload.DownloadItem.OutputPath.FullPath),
trackedDownload.RemoteAlbum.Artist) && trackedDownload.DownloadItem.CanMoveFiles)
{
_diskProvider.DeleteFolder(trackedDownload.DownloadItem.OutputPath.FullPath, true);

@ -49,7 +49,7 @@ namespace NzbDrone.Core.MediaFiles
private bool ChangeFileDate(TrackFile trackFile, Artist artist, List<Track> tracks)
{
var trackFilePath = Path.Combine(artist.Path, trackFile.RelativePath);
var trackFilePath = trackFile.Path;
switch (_configService.FileDate)
{

@ -57,7 +57,7 @@ namespace NzbDrone.Core.MediaFiles
foreach (var existingFile in existingFiles)
{
var file = existingFile.First();
var trackFilePath = Path.Combine(localTrack.Artist.Path, file.RelativePath);
var trackFilePath = file.Path;
var subfolder = rootFolder.GetRelativePath(_diskProvider.GetParentFolder(trackFilePath));
if (_diskProvider.FileExists(trackFilePath))

@ -17,8 +17,8 @@ namespace NzbDrone.Core.Music
{
public interface IRefreshAlbumService
{
void RefreshAlbumInfo(Album album, bool forceUpdateFileTags);
void RefreshAlbumInfo(List<Album> albums, bool forceAlbumRefresh, bool forceUpdateFileTags);
bool RefreshAlbumInfo(Album album, bool forceUpdateFileTags);
bool RefreshAlbumInfo(List<Album> albums, bool forceAlbumRefresh, bool forceUpdateFileTags);
}
public class RefreshAlbumService : IRefreshAlbumService, IExecute<RefreshAlbumCommand>
@ -57,20 +57,23 @@ namespace NzbDrone.Core.Music
_logger = logger;
}
public void RefreshAlbumInfo(List<Album> albums, bool forceAlbumRefresh, bool forceUpdateFileTags)
public bool RefreshAlbumInfo(List<Album> albums, bool forceAlbumRefresh, bool forceUpdateFileTags)
{
bool updated = false;
foreach (var album in albums)
{
if (forceAlbumRefresh || _checkIfAlbumShouldBeRefreshed.ShouldRefresh(album))
{
RefreshAlbumInfo(album, forceUpdateFileTags);
updated |= RefreshAlbumInfo(album, forceUpdateFileTags);
}
}
return updated;
}
public void RefreshAlbumInfo(Album album, bool forceUpdateFileTags)
public bool RefreshAlbumInfo(Album album, bool forceUpdateFileTags)
{
_logger.ProgressInfo("Updating Info for {0}", album.Title);
bool updated = false;
Tuple<string, Album, List<ArtistMetadata>> tuple;
@ -81,14 +84,14 @@ namespace NzbDrone.Core.Music
catch (AlbumNotFoundException)
{
_logger.Error($"{album} was not found, it may have been removed from Metadata sources.");
return;
return updated;
}
if (tuple.Item2.AlbumReleases.Value.Count == 0)
{
_logger.Debug($"{album} has no valid releases, removing.");
_albumService.DeleteMany(new List<Album> { album });
return;
return true;
}
var remoteMetadata = tuple.Item3.DistinctBy(x => x.ForeignArtistId).ToList();
@ -124,6 +127,7 @@ namespace NzbDrone.Core.Music
_artistMetadataRepository.InsertMany(addMetadataList);
forceUpdateFileTags |= updateMetadataList.Any();
updated |= updateMetadataList.Any() || addMetadataList.Any();
var albumInfo = tuple.Item2;
@ -137,6 +141,7 @@ namespace NzbDrone.Core.Music
// the only thing written to tags from the album object is the title
forceUpdateFileTags |= album.Title != (albumInfo.Title ?? "Unknown");
updated |= forceUpdateFileTags;
album.LastInfoSync = DateTime.UtcNow;
album.CleanTitle = albumInfo.CleanTitle;
@ -229,11 +234,14 @@ namespace NzbDrone.Core.Music
// if we have updated a monitored release, refresh all file tags
forceUpdateFileTags |= updateReleaseList.Any(x => x.Monitored);
updated |= existingReleases.Any() || updateReleaseList.Any() || newReleaseList.Any();
_refreshTrackService.RefreshTrackInfo(album, forceUpdateFileTags);
updated |= _refreshTrackService.RefreshTrackInfo(album, forceUpdateFileTags);
_albumService.UpdateMany(new List<Album>{album});
_logger.Debug("Finished album refresh for {0}", album.Title);
return updated;
}
public void Execute(RefreshAlbumCommand message)
@ -242,10 +250,12 @@ namespace NzbDrone.Core.Music
{
var album = _albumService.GetAlbum(message.AlbumId.Value);
var artist = _artistService.GetArtistByMetadataId(album.ArtistMetadataId);
RefreshAlbumInfo(album, false);
_eventAggregator.PublishEvent(new ArtistUpdatedEvent(artist));
var updated = RefreshAlbumInfo(album, false);
if (updated)
{
_eventAggregator.PublishEvent(new ArtistUpdatedEvent(artist));
}
}
}
}
}

@ -63,9 +63,10 @@ namespace NzbDrone.Core.Music
_logger = logger;
}
private void RefreshArtistInfo(Artist artist, bool forceAlbumRefresh)
private bool RefreshArtistInfo(Artist artist, bool forceAlbumRefresh)
{
_logger.ProgressInfo("Updating Info for {0}", artist.Name);
bool updated = false;
Artist artistInfo;
@ -76,10 +77,11 @@ namespace NzbDrone.Core.Music
catch (ArtistNotFoundException)
{
_logger.Error($"Artist {artist} was not found, it may have been removed from Metadata sources.");
return;
return updated;
}
var forceUpdateFileTags = artist.Name != artistInfo.Name;
updated |= forceUpdateFileTags;
if (artist.Metadata.Value.ForeignArtistId != artistInfo.Metadata.Value.ForeignArtistId)
{
@ -96,6 +98,7 @@ namespace NzbDrone.Core.Music
artist.Metadata.Value.ForeignArtistId = artistInfo.Metadata.Value.ForeignArtistId;
forceUpdateFileTags = true;
updated = true;
}
artist.Metadata.Value.ApplyChanges(artistInfo.Metadata.Value);
@ -153,14 +156,23 @@ namespace NzbDrone.Core.Music
newAlbumsList = UpdateAlbums(artist, newAlbumsList);
_addAlbumService.AddAlbums(newAlbumsList);
_refreshAlbumService.RefreshAlbumInfo(updateAlbumsList, forceAlbumRefresh, forceUpdateFileTags);
updated |= existingAlbums.Any() || newAlbumsList.Any();
updated |= _refreshAlbumService.RefreshAlbumInfo(updateAlbumsList, forceAlbumRefresh, forceUpdateFileTags);
// Do this last so artist only marked as refreshed if refresh of tracks / albums completed successfully
_artistService.UpdateArtist(artist);
_eventAggregator.PublishEvent(new AlbumInfoRefreshedEvent(artist, newAlbumsList, updateAlbumsList));
_eventAggregator.PublishEvent(new ArtistUpdatedEvent(artist));
if (updated)
{
_eventAggregator.PublishEvent(new ArtistUpdatedEvent(artist));
}
_logger.Debug("Finished artist refresh for {0}", artist.Name);
return updated;
}
private List<Album> UpdateAlbums(Artist artist, List<Album> albumsToUpdate)
@ -174,26 +186,26 @@ namespace NzbDrone.Core.Music
return albumsToUpdate;
}
private void RescanArtist(Artist artist, bool isNew, CommandTrigger trigger)
private void RescanArtist(Artist artist, bool isNew, CommandTrigger trigger, bool infoUpdated)
{
var rescanAfterRefresh = _configService.RescanAfterRefresh;
var shouldRescan = true;
if (isNew)
{
_logger.Trace("Forcing refresh of {0}. Reason: New artist", artist);
_logger.Trace("Forcing rescan of {0}. Reason: New artist", artist);
shouldRescan = true;
}
else if (rescanAfterRefresh == RescanAfterRefreshType.Never)
{
_logger.Trace("Skipping refresh of {0}. Reason: never recan after refresh", artist);
_logger.Trace("Skipping rescan of {0}. Reason: never recan after refresh", artist);
shouldRescan = false;
}
else if (rescanAfterRefresh == RescanAfterRefreshType.AfterManual && trigger != CommandTrigger.Manual)
{
_logger.Trace("Skipping refresh of {0}. Reason: not after automatic scans", artist);
_logger.Trace("Skipping rescan of {0}. Reason: not after automatic scans", artist);
shouldRescan = false;
}
@ -204,7 +216,10 @@ namespace NzbDrone.Core.Music
try
{
_diskScanService.Scan(artist);
// If some metadata has been updated then rescan unmatched files.
// Otherwise only scan files that haven't been seen before.
var filter = infoUpdated ? FilterFilesType.Matched : FilterFilesType.Known;
_diskScanService.Scan(artist, filter);
}
catch (Exception e)
{
@ -221,16 +236,16 @@ namespace NzbDrone.Core.Music
if (message.ArtistId.HasValue)
{
var artist = _artistService.GetArtist(message.ArtistId.Value);
bool updated = false;
try
{
RefreshArtistInfo(artist, true);
RescanArtist(artist, isNew, trigger);
updated = RefreshArtistInfo(artist, true);
RescanArtist(artist, isNew, trigger, updated);
}
catch (Exception e)
{
_logger.Error(e, "Couldn't refresh info for {0}", artist);
RescanArtist(artist, isNew, trigger);
RescanArtist(artist, isNew, trigger, updated);
throw;
}
}
@ -244,22 +259,23 @@ namespace NzbDrone.Core.Music
if (manualTrigger || _checkIfArtistShouldBeRefreshed.ShouldRefresh(artist))
{
bool updated = false;
try
{
RefreshArtistInfo(artist, manualTrigger);
updated = RefreshArtistInfo(artist, manualTrigger);
}
catch (Exception e)
{
_logger.Error(e, "Couldn't refresh info for {0}", artist);
}
RescanArtist(artist, false, trigger);
RescanArtist(artist, false, trigger, updated);
}
else
{
_logger.Info("Skipping refresh of artist: {0}", artist.Name);
RescanArtist(artist, false, trigger);
RescanArtist(artist, false, trigger, false);
}
}
}

@ -10,7 +10,7 @@ namespace NzbDrone.Core.Music
{
public interface IRefreshTrackService
{
void RefreshTrackInfo(Album rg, bool forceUpdateFileTags);
bool RefreshTrackInfo(Album rg, bool forceUpdateFileTags);
}
public class RefreshTrackService : IRefreshTrackService
@ -37,11 +37,12 @@ namespace NzbDrone.Core.Music
_logger = logger;
}
public void RefreshTrackInfo(Album album, bool forceUpdateFileTags)
public bool RefreshTrackInfo(Album album, bool forceUpdateFileTags)
{
_logger.Info("Starting track info refresh for: {0}", album);
var successCount = 0;
var failCount = 0;
bool updated = false;
foreach (var release in album.AlbumReleases.Value)
{
@ -115,6 +116,8 @@ namespace NzbDrone.Core.Music
_trackService.DeleteMany(existingTracks);
_trackService.UpdateMany(updateList);
_trackService.InsertMany(newList);
updated |= existingTracks.Any() || updateList.Any() || newList.Any();
}
if (failCount != 0)
@ -126,6 +129,8 @@ namespace NzbDrone.Core.Music
{
_logger.Info("Finished track refresh for album: {0}.", album);
}
return updated;
}
}
}

@ -79,14 +79,12 @@ namespace NzbDrone.Core.Notifications.CustomScript
if (message.TrackFiles.Any())
{
environmentVariables.Add("Lidarr_AddedTrackRelativePaths", string.Join("|", message.TrackFiles.Select(e => e.RelativePath)));
environmentVariables.Add("Lidarr_AddedTrackPaths", string.Join("|", message.TrackFiles.Select(e => Path.Combine(artist.Path, e.RelativePath))));
environmentVariables.Add("Lidarr_AddedTrackPaths", string.Join("|", message.TrackFiles.Select(e => e.Path)));
}
if (message.OldFiles.Any())
{
environmentVariables.Add("Lidarr_DeletedRelativePaths", string.Join("|", message.OldFiles.Select(e => e.RelativePath)));
environmentVariables.Add("Lidarr_DeletedPaths", string.Join("|", message.OldFiles.Select(e => Path.Combine(artist.Path, e.RelativePath))));
environmentVariables.Add("Lidarr_DeletedPaths", string.Join("|", message.OldFiles.Select(e => e.Path)));
}
ExecuteScript(environmentVariables);
@ -127,8 +125,7 @@ namespace NzbDrone.Core.Notifications.CustomScript
environmentVariables.Add("Lidarr_Album_ReleaseDate", album.ReleaseDate.ToString());
environmentVariables.Add("Lidarr_TrackFile_Id", trackFile.Id.ToString());
environmentVariables.Add("Lidarr_TrackFile_TrackCount", trackFile.Tracks.Value.Count.ToString());
environmentVariables.Add("Lidarr_TrackFile_RelativePath", trackFile.RelativePath);
environmentVariables.Add("Lidarr_TrackFile_Path", Path.Combine(artist.Path, trackFile.RelativePath));
environmentVariables.Add("Lidarr_TrackFile_Path", trackFile.Path);
environmentVariables.Add("Lidarr_TrackFile_TrackNumbers", string.Join(",", trackFile.Tracks.Value.Select(e => e.TrackNumber)));
environmentVariables.Add("Lidarr_TrackFile_TrackTitles", string.Join("|", trackFile.Tracks.Value.Select(e => e.Title)));
environmentVariables.Add("Lidarr_TrackFile_Quality", trackFile.Quality.Quality.Name);

@ -74,7 +74,7 @@ namespace NzbDrone.Core.Notifications
private string GetTrackRetagMessage(Artist artist, TrackFile trackFile, Dictionary<string, Tuple<string, string>> diff)
{
return string.Format("{0}:\n{1}",
Path.Combine(artist.Path, trackFile.RelativePath),
trackFile.Path,
string.Join("\n", diff.Select(x => $"{x.Key}: {FormatMissing(x.Value.Item1)} → {FormatMissing(x.Value.Item2)}")));
}

@ -26,14 +26,14 @@ namespace NzbDrone.Core.Notifications.Synology
{
foreach (var oldFile in message.OldFiles)
{
var fullPath = Path.Combine(message.Artist.Path, oldFile.RelativePath);
var fullPath = oldFile.Path;
_indexerProxy.DeleteFile(fullPath);
}
foreach (var newFile in message.TrackFiles)
{
var fullPath = Path.Combine(message.Artist.Path, newFile.RelativePath);
var fullPath = newFile.Path;
_indexerProxy.AddFile(fullPath);
}

@ -9,7 +9,6 @@ namespace NzbDrone.Core.Notifications.Webhook
public WebhookTrackFile(TrackFile trackFile)
{
Id = trackFile.Id;
RelativePath = trackFile.RelativePath;
Path = trackFile.Path;
Quality = trackFile.Quality.Quality.Name;
QualityVersion = trackFile.Quality.Revision.Version;
@ -18,7 +17,6 @@ namespace NzbDrone.Core.Notifications.Webhook
}
public int Id { get; set; }
public string RelativePath { get; set; }
public string Path { get; set; }
public string Quality { get; set; }
public int QualityVersion { get; set; }

@ -92,6 +92,9 @@
<Reference Include="RestSharp, Version=105.2.3.0, Culture=neutral, processorArchitecture=MSIL">
<HintPath>..\packages\RestSharp.105.2.3\lib\net46\RestSharp.dll</HintPath>
</Reference>
<Reference Include="System.IO.Abstractions, Version=4.0.0.0, Culture=neutral, processorArchitecture=MSIL">
<HintPath>..\packages\System.IO.Abstractions.4.0.11\lib\net40\System.IO.Abstractions.dll</HintPath>
</Reference>
<Reference Include="System" />
<Reference Include="System.Core" />
<Reference Include="System.Data" />
@ -204,6 +207,7 @@
<Compile Include="Datastore\Migration\025_rename_release_profiles.cs" />
<Compile Include="Datastore\Migration\026_rename_quality_profiles_add_upgrade_allowed.cs" />
<Compile Include="Datastore\Migration\028_clean_artistmetadata_table.cs" />
<Compile Include="Datastore\Migration\030_add_mediafilerepository_mtime.cs" />
<Compile Include="Datastore\Migration\Framework\MigrationContext.cs" />
<Compile Include="Datastore\Migration\Framework\MigrationController.cs" />
<Compile Include="Datastore\Migration\Framework\MigrationDbFactory.cs" />
@ -805,6 +809,7 @@
<Compile Include="MediaFiles\Events\TrackFileDeletedEvent.cs" />
<Compile Include="MediaFiles\Events\TrackImportedEvent.cs" />
<Compile Include="MediaFiles\FileDateType.cs" />
<Compile Include="MediaFiles\FilterFilesType.cs" />
<Compile Include="MediaFiles\MediaFileAttributeService.cs" />
<Compile Include="MediaFiles\MediaFileExtensions.cs" />
<Compile Include="MediaFiles\MediaFileRepository.cs" />

@ -549,12 +549,7 @@ namespace NzbDrone.Core.Organizer
private string GetOriginalFileName(TrackFile trackFile)
{
if (trackFile.RelativePath.IsNullOrWhiteSpace())
{
return Path.GetFileNameWithoutExtension(trackFile.Path);
}
return Path.GetFileNameWithoutExtension(trackFile.RelativePath);
return Path.GetFileNameWithoutExtension(trackFile.Path);
}
}

@ -86,7 +86,7 @@ namespace NzbDrone.Core.Organizer
_singleTrackFile = new TrackFile
{
Quality = new QualityModel(Quality.MP3_256, new Revision(2)),
RelativePath = "Artist.Name.Album.Name.TrackNum.Track.Title.MP3256.mp3",
Path = "/music/Artist.Name.Album.Name.TrackNum.Track.Title.MP3256.mp3",
SceneName = "Artist.Name.Album.Name.TrackNum.Track.Title.MP3256",
ReleaseGroup = "RlsGrp",
MediaInfo = mediaInfo

@ -3,6 +3,7 @@ using NzbDrone.Core.Qualities;
using System.Collections.Generic;
using NzbDrone.Core.Languages;
using NzbDrone.Core.MediaFiles.TrackImport.Identification;
using System;
namespace NzbDrone.Core.Parser.Model
{
@ -15,6 +16,7 @@ namespace NzbDrone.Core.Parser.Model
public string Path { get; set; }
public long Size { get; set; }
public DateTime Modified { get; set; }
public ParsedTrackInfo FileTrackInfo { get; set; }
public ParsedTrackInfo FolderTrackInfo { get; set; }
public ParsedAlbumInfo DownloadClientAlbumInfo { get; set; }
@ -26,7 +28,6 @@ namespace NzbDrone.Core.Parser.Model
public Distance Distance { get; set; }
public QualityModel Quality { get; set; }
public Language Language { get; set; }
public MediaInfoModel MediaInfo { get; set; }
public bool ExistingFile { get; set; }
public bool AdditionalFile { get; set; }
public bool SceneSource { get; set; }

@ -220,16 +220,13 @@ namespace NzbDrone.Core.Parser
public Album GetLocalAlbum(string filename, Artist artist)
{
if (Path.HasExtension(filename))
{
filename = Path.GetDirectoryName(filename);
}
filename = artist.Path.GetRelativePath(filename);
var tracksInAlbum = _mediaFileService.GetFilesByArtist(artist.Id)
.FindAll(s => Path.GetDirectoryName(s.RelativePath) == filename)
.FindAll(s => Path.GetDirectoryName(s.Path) == filename)
.DistinctBy(s => s.AlbumId)
.ToList();

@ -12,4 +12,5 @@
<package id="TagLibSharp-Lidarr" version="2.2.0.19" targetFramework="net461" />
<package id="TinyTwitter" version="1.1.2" targetFramework="net461" />
<package id="xmlrpcnet" version="2.5.0" targetFramework="net461" />
<package id="System.IO.Abstractions" version="4.0.11" targetFramework="net461" />
</packages>

@ -1,4 +1,7 @@
using NUnit.Framework;
using System.Collections.Generic;
using Moq;
using NUnit.Framework;
using NzbDrone.Common.Disk;
using NzbDrone.Common.Test.DiskTests;
using NzbDrone.Mono.Disk;

@ -1,6 +1,7 @@
using System;
using System.Collections.Generic;
using System.IO;
using System.IO.Abstractions;
using System.Linq;
using Mono.Unix;
using Mono.Unix.Native;
@ -23,7 +24,16 @@ namespace NzbDrone.Mono.Disk
// `unchecked((uint)-1)` and `uint.MaxValue` are the same thing.
private const uint UNCHANGED_ID = uint.MaxValue;
public DiskProvider(IProcMountProvider procMountProvider, ISymbolicLinkResolver symLinkResolver)
public DiskProvider(IProcMountProvider procMountProvider,
ISymbolicLinkResolver symLinkResolver)
: this(new FileSystem(), procMountProvider, symLinkResolver)
{
}
public DiskProvider(IFileSystem fileSystem,
IProcMountProvider procMountProvider,
ISymbolicLinkResolver symLinkResolver)
: base(fileSystem)
{
_procMountProvider = procMountProvider;
_symLinkResolver = symLinkResolver;
@ -40,6 +50,8 @@ namespace NzbDrone.Mono.Disk
{
Ensure.That(path, () => path).IsValidPath();
Logger.Debug($"path: {path}");
var mount = GetMount(path);
if (mount == null)
@ -57,9 +69,9 @@ namespace NzbDrone.Mono.Disk
try
{
var fs = File.GetAccessControl(filename);
var fs = _fileSystem.File.GetAccessControl(filename);
fs.SetAccessRuleProtection(false, false);
File.SetAccessControl(filename, fs);
_fileSystem.File.SetAccessControl(filename, fs);
}
catch (NotImplementedException)
{

@ -58,6 +58,9 @@
<Reference Include="NLog, Version=4.0.0.0, Culture=neutral, PublicKeyToken=5120e14c03d0593c, processorArchitecture=MSIL">
<HintPath>..\packages\NLog.4.5.4\lib\net45\NLog.dll</HintPath>
</Reference>
<Reference Include="System.IO.Abstractions, Version=4.0.0.0, Culture=neutral, processorArchitecture=MSIL">
<HintPath>..\packages\System.IO.Abstractions.4.0.11\lib\net40\System.IO.Abstractions.dll</HintPath>
</Reference>
<Reference Include="System" />
<Reference Include="System.Core" />
<Reference Include="System.Data" />
@ -102,4 +105,4 @@
<Target Name="AfterBuild">
</Target>
-->
</Project>
</Project>

@ -1,7 +1,7 @@
using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.IO;
using System.IO.Abstractions;
using System.Linq;
using System.Linq.Expressions;
using System.Reflection;
@ -9,6 +9,7 @@ using System.Runtime.CompilerServices;
using Microsoft.Practices.Unity;
using Moq;
using Moq.Language.Flow;
using NzbDrone.Common.Disk;
using NzbDrone.Common.EnvironmentInfo;
using NzbDrone.Test.Common.AutoMoq.Unity;
@ -50,6 +51,15 @@ namespace NzbDrone.Test.Common.AutoMoq
return result;
}
public virtual T Resolve<T>(string name, params ResolverOverride[] resolverOverrides)
{
ResolveType = typeof(T);
var result = _container.Resolve<T>(name, resolverOverrides);
SetConstant(result);
ResolveType = null;
return result;
}
public virtual Mock<T> GetMock<T>() where T : class
{
return GetMock<T>(DefaultBehavior);
@ -135,9 +145,9 @@ namespace NzbDrone.Test.Common.AutoMoq
_container = container;
container.RegisterInstance(this);
RegisterPlatformLibrary(container);
_registeredMocks = new Dictionary<Type, object>();
RegisterPlatformLibrary(container);
AddTheAutoMockingContainerExtensionToTheContainer(container);
}
@ -178,12 +188,15 @@ namespace NzbDrone.Test.Common.AutoMoq
assemblyName = "Lidarr.Mono";
}
if (!File.Exists(assemblyName + ".dll"))
{
return;
}
var assembly = Assembly.Load(assemblyName);
// This allows us to resolve the platform specific disk provider in FileSystemTest
var diskProvider = assembly.GetTypes().Where(x => x.Name == "DiskProvider").SingleOrDefault();
container.RegisterType(typeof(IDiskProvider), diskProvider, "ActualDiskProvider");
Assembly.Load(assemblyName);
// This tells the mocker to resolve IFileSystem using an actual filesystem (and not a mock)
// if not specified, giving the old behaviour before we switched to System.IO.Abstractions.
SetConstant<IFileSystem>(new FileSystem());
}
#endregion

@ -73,6 +73,9 @@
<Reference Include="RestSharp, Version=105.2.3.0, Culture=neutral, processorArchitecture=MSIL">
<HintPath>..\packages\RestSharp.105.2.3\lib\net46\RestSharp.dll</HintPath>
</Reference>
<Reference Include="System.IO.Abstractions, Version=4.0.0.0, Culture=neutral, processorArchitecture=MSIL">
<HintPath>..\packages\System.IO.Abstractions.4.0.11\lib\net40\System.IO.Abstractions.dll</HintPath>
</Reference>
<Reference Include="System" />
<Reference Include="System.Core" />
<Reference Include="System.Data" />
@ -80,15 +83,6 @@
<Reference Include="System.Xml" />
<Reference Include="System.Xml.Linq" />
<Reference Include="Microsoft.CSharp" />
<Reference Include="Microsoft.Practices.Unity">
<HintPath>..\packages\Unity.2.1.505.2\lib\NET35\Microsoft.Practices.Unity.dll</HintPath>
</Reference>
<Reference Include="Microsoft.Practices.Unity.Configuration">
<HintPath>..\packages\Unity.2.1.505.2\lib\NET35\Microsoft.Practices.Unity.Configuration.dll</HintPath>
</Reference>
<Reference Include="Moq">
<HintPath>..\packages\Moq.4.0.10827\lib\NET40\Moq.dll</HintPath>
</Reference>
</ItemGroup>
<ItemGroup>
<Compile Include="AutoMoq\AutoMoqer.cs" />
@ -138,4 +132,4 @@
<Target Name="AfterBuild">
</Target>
-->
</Project>
</Project>

@ -19,7 +19,7 @@ namespace NzbDrone.Test.Common
private TSubject _subject;
[SetUp]
public void CoreTestSetup()
public virtual void CoreTestSetup()
{
_subject = null;

@ -1,5 +1,6 @@
using System;
using System.IO;
using System.IO.Abstractions;
using System.Runtime.InteropServices;
using NLog;
using NzbDrone.Common.Disk;
@ -23,6 +24,16 @@ namespace NzbDrone.Windows.Disk
[return: MarshalAs(UnmanagedType.Bool)]
static extern bool CreateHardLink(string lpFileName, string lpExistingFileName, IntPtr lpSecurityAttributes);
public DiskProvider()
: this(new FileSystem())
{
}
public DiskProvider(IFileSystem fileSystem)
: base(fileSystem)
{
}
public override long? GetAvailableSpace(string path)
{
Ensure.That(path, () => path).IsValidPath();

@ -59,6 +59,9 @@
<Reference Include="NLog, Version=4.0.0.0, Culture=neutral, PublicKeyToken=5120e14c03d0593c, processorArchitecture=MSIL">
<HintPath>..\packages\NLog.4.5.4\lib\net45\NLog.dll</HintPath>
</Reference>
<Reference Include="System.IO.Abstractions, Version=4.0.0.0, Culture=neutral, processorArchitecture=MSIL">
<HintPath>..\packages\System.IO.Abstractions.4.0.11\lib\net40\System.IO.Abstractions.dll</HintPath>
</Reference>
<Reference Include="System" />
<Reference Include="System.Core" />
<Reference Include="System.Data" />
@ -91,4 +94,4 @@
<Target Name="AfterBuild">
</Target>
-->
</Project>
</Project>

Loading…
Cancel
Save