diff --git a/src/Lidarr.Api.V1/FileSystem/FileSystemModule.cs b/src/Lidarr.Api.V1/FileSystem/FileSystemModule.cs
index 5fb003601..fe368b671 100644
--- a/src/Lidarr.Api.V1/FileSystem/FileSystemModule.cs
+++ b/src/Lidarr.Api.V1/FileSystem/FileSystemModule.cs
@@ -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();
}
}
diff --git a/src/Lidarr.Api.V1/Lidarr.Api.V1.csproj b/src/Lidarr.Api.V1/Lidarr.Api.V1.csproj
index f102ae7ce..e6f2a8644 100644
--- a/src/Lidarr.Api.V1/Lidarr.Api.V1.csproj
+++ b/src/Lidarr.Api.V1/Lidarr.Api.V1.csproj
@@ -72,6 +72,9 @@
..\packages\Ical.Net.2.2.32\lib\net46\NodaTime.dll
+
+ ..\packages\System.IO.Abstractions.4.0.11\lib\net40\System.IO.Abstractions.dll
+
diff --git a/src/Lidarr.Api.V1/ManualImport/ManualImportModule.cs b/src/Lidarr.Api.V1/ManualImport/ManualImportModule.cs
index e0540fc22..3c41232c5 100644
--- a/src/Lidarr.Api.V1/ManualImport/ManualImportModule.cs
+++ b/src/Lidarr.Api.V1/ManualImport/ManualImportModule.cs
@@ -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)
diff --git a/src/Lidarr.Api.V1/TrackFiles/TrackFileModule.cs b/src/Lidarr.Api.V1/TrackFiles/TrackFileModule.cs
index 1d0203242..a7be00e44 100644
--- a/src/Lidarr.Api.V1/TrackFiles/TrackFileModule.cs
+++ b/src/Lidarr.Api.V1/TrackFiles/TrackFileModule.cs
@@ -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));
}
diff --git a/src/Lidarr.Api.V1/TrackFiles/TrackFileResource.cs b/src/Lidarr.Api.V1/TrackFiles/TrackFileResource.cs
index 38e57ff14..4470d01bf 100644
--- a/src/Lidarr.Api.V1/TrackFiles/TrackFileResource.cs
+++ b/src/Lidarr.Api.V1/TrackFiles/TrackFileResource.cs
@@ -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,
diff --git a/src/NzbDrone.Common.Test/DiskTests/DirectoryLookupServiceFixture.cs b/src/NzbDrone.Common.Test/DiskTests/DirectoryLookupServiceFixture.cs
index 78aa99f7d..48afe91c3 100644
--- a/src/NzbDrone.Common.Test/DiskTests/DirectoryLookupServiceFixture.cs
+++ b/src/NzbDrone.Common.Test/DiskTests/DirectoryLookupServiceFixture.cs
@@ -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 _folders;
+ private List _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();
}
[Test]
diff --git a/src/NzbDrone.Common.Test/DiskTests/DiskProviderFixtureBase.cs b/src/NzbDrone.Common.Test/DiskTests/DiskProviderFixtureBase.cs
index 266a89ba3..1d750e6c6 100644
--- a/src/NzbDrone.Common.Test/DiskTests/DiskProviderFixtureBase.cs
+++ b/src/NzbDrone.Common.Test/DiskTests/DiskProviderFixtureBase.cs
@@ -1,5 +1,6 @@
using System;
using System.IO;
+using System.IO.Abstractions;
using FluentAssertions;
using NUnit.Framework;
using NzbDrone.Common.Disk;
diff --git a/src/NzbDrone.Common.Test/DiskTests/DiskTransferServiceFixture.cs b/src/NzbDrone.Common.Test/DiskTests/DiskTransferServiceFixture.cs
index ca6e539f5..5a216c719 100644
--- a/src/NzbDrone.Common.Test/DiskTests/DiskTransferServiceFixture.cs
+++ b/src/NzbDrone.Common.Test/DiskTests/DiskTransferServiceFixture.cs
@@ -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()
- .Setup(v => v.GetDirectoryInfos(It.IsAny()))
- .Returns(new List());
+ .Setup(v => v.GetDirectoryInfos(It.IsAny()))
+ .Returns(new List());
// Note: never returns anything.
Mocker.GetMock()
- .Setup(v => v.GetFileInfos(It.IsAny()))
- .Returns(new List());
+ .Setup(v => v.GetFileInfos(It.IsAny(), It.IsAny()))
+ .Returns(new List());
}
private void WithRealDiskProvider()
{
+ IFileSystem _fileSystem = new FileSystem();
+
Mocker.GetMock()
.Setup(v => v.FolderExists(It.IsAny()))
- .Returns(v => Directory.Exists(v));
+ .Returns(v => _fileSystem.Directory.Exists(v));
Mocker.GetMock()
.Setup(v => v.FileExists(It.IsAny()))
- .Returns(v => File.Exists(v));
+ .Returns(v => _fileSystem.File.Exists(v));
Mocker.GetMock()
.Setup(v => v.CreateFolder(It.IsAny()))
- .Callback(v => Directory.CreateDirectory(v));
+ .Callback(v => _fileSystem.Directory.CreateDirectory(v));
Mocker.GetMock()
.Setup(v => v.DeleteFolder(It.IsAny(), It.IsAny()))
- .Callback((v, r) => Directory.Delete(v, r));
+ .Callback((v, r) => _fileSystem.Directory.Delete(v, r));
Mocker.GetMock()
.Setup(v => v.DeleteFile(It.IsAny()))
- .Callback(v => File.Delete(v));
+ .Callback(v => _fileSystem.File.Delete(v));
Mocker.GetMock()
.Setup(v => v.GetDirectoryInfos(It.IsAny()))
- .Returns(v => new DirectoryInfo(v).GetDirectories().ToList());
+ .Returns(v => _fileSystem.DirectoryInfo.FromDirectoryName(v).GetDirectories().ToList());
Mocker.GetMock()
- .Setup(v => v.GetFileInfos(It.IsAny()))
- .Returns(v => new DirectoryInfo(v).GetFiles().ToList());
+ .Setup(v => v.GetFileInfos(It.IsAny(), It.IsAny()))
+ .Returns((string v, SearchOption option) => _fileSystem.DirectoryInfo.FromDirectoryName(v).GetFiles("*", option).ToList());
Mocker.GetMock()
.Setup(v => v.GetFileSize(It.IsAny()))
- .Returns(v => new FileInfo(v).Length);
+ .Returns(v => _fileSystem.FileInfo.FromFileName(v).Length);
Mocker.GetMock()
.Setup(v => v.TryCreateHardLink(It.IsAny(), It.IsAny()))
@@ -1030,13 +1033,13 @@ namespace NzbDrone.Common.Test.DiskTests
Mocker.GetMock()
.Setup(v => v.CopyFile(It.IsAny(), It.IsAny(), It.IsAny()))
- .Callback((s, d, o) => File.Copy(s, d, o));
+ .Callback((s, d, o) => _fileSystem.File.Copy(s, d, o));
Mocker.GetMock()
.Setup(v => v.MoveFile(It.IsAny(), It.IsAny(), It.IsAny()))
.Callback((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()
diff --git a/src/NzbDrone.Common.Test/NzbDrone.Common.Test.csproj b/src/NzbDrone.Common.Test/NzbDrone.Common.Test.csproj
index 412384a51..f5c852b3a 100644
--- a/src/NzbDrone.Common.Test/NzbDrone.Common.Test.csproj
+++ b/src/NzbDrone.Common.Test/NzbDrone.Common.Test.csproj
@@ -55,6 +55,12 @@
..\packages\NUnit.3.11.0\lib\net45\nunit.framework.dll
+
+ ..\packages\System.IO.Abstractions.4.0.11\lib\net40\System.IO.Abstractions.dll
+
+
+ ..\packages\Unity.2.1.505.2\lib\NET35\Microsoft.Practices.Unity.dll
+
diff --git a/src/NzbDrone.Common.Test/PathExtensionFixture.cs b/src/NzbDrone.Common.Test/PathExtensionFixture.cs
index 42ad67aac..269e4145c 100644
--- a/src/NzbDrone.Common.Test/PathExtensionFixture.cs
+++ b/src/NzbDrone.Common.Test/PathExtensionFixture.cs
@@ -43,6 +43,7 @@ namespace NzbDrone.Common.Test
result.Should().Be(clean);
}
+ [TestCase(@"/", @"/")]
[TestCase(@"/test/", @"/test")]
[TestCase(@"//test/", @"/test")]
[TestCase(@"//test//", @"/test")]
diff --git a/src/NzbDrone.Common.Test/packages.config b/src/NzbDrone.Common.Test/packages.config
index 37ac7a8c1..dd34c366c 100644
--- a/src/NzbDrone.Common.Test/packages.config
+++ b/src/NzbDrone.Common.Test/packages.config
@@ -4,4 +4,6 @@
-
\ No newline at end of file
+
+
+
diff --git a/src/NzbDrone.Common/Disk/DiskProviderBase.cs b/src/NzbDrone.Common/Disk/DiskProviderBase.cs
index 29b37fb26..de37a7ac3 100644
--- a/src/NzbDrone.Common/Disk/DiskProviderBase.cs
+++ b/src/NzbDrone.Common/Disk/DiskProviderBase.cs
@@ -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 GetMounts()
@@ -454,29 +461,41 @@ namespace NzbDrone.Common.Disk
}
}
- protected List GetDriveInfoMounts()
+ protected List GetDriveInfoMounts()
{
- return DriveInfo.GetDrives()
- .Where(d => d.IsReady)
- .ToList();
+ return _fileSystem.DriveInfo.GetDrives()
+ .Where(d => d.IsReady)
+ .ToList();
}
- public List GetDirectoryInfos(string path)
+ public List 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 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 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)
diff --git a/src/NzbDrone.Common/Disk/DiskTransferService.cs b/src/NzbDrone.Common/Disk/DiskTransferService.cs
index 1f210b977..947ad16d3 100644
--- a/src/NzbDrone.Common/Disk/DiskTransferService.cs
+++ b/src/NzbDrone.Common/Disk/DiskTransferService.cs
@@ -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"))
{
diff --git a/src/NzbDrone.Common/Disk/DriveInfoMount.cs b/src/NzbDrone.Common/Disk/DriveInfoMount.cs
index 1ddd9f43f..d80513666 100644
--- a/src/NzbDrone.Common/Disk/DriveInfoMount.cs
+++ b/src/NzbDrone.Common/Disk/DriveInfoMount.cs
@@ -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;
diff --git a/src/NzbDrone.Common/Disk/IDiskProvider.cs b/src/NzbDrone.Common/Disk/IDiskProvider.cs
index 20f36c67d..c4cec3fc0 100644
--- a/src/NzbDrone.Common/Disk/IDiskProvider.cs
+++ b/src/NzbDrone.Common/Disk/IDiskProvider.cs
@@ -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 GetMounts();
IMount GetMount(string path);
- List GetDirectoryInfos(string path);
- List GetFileInfos(string path);
+ IDirectoryInfo GetDirectoryInfo(string path);
+ List GetDirectoryInfos(string path);
+ IFileInfo GetFileInfo(string path);
+ List GetFileInfos(string path, SearchOption searchOption = SearchOption.TopDirectoryOnly);
void RemoveEmptySubfolders(string path);
void SaveStream(Stream stream, string path);
}
diff --git a/src/NzbDrone.Common/Extensions/PathExtensions.cs b/src/NzbDrone.Common/Extensions/PathExtensions.cs
index 68d1eea33..a6a62042d 100644
--- a/src/NzbDrone.Common/Extensions/PathExtensions.cs
+++ b/src/NzbDrone.Common/Extensions/PathExtensions.cs
@@ -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('\\', ' ');
}
diff --git a/src/NzbDrone.Common/NzbDrone.Common.csproj b/src/NzbDrone.Common/NzbDrone.Common.csproj
index 05e2a0eaf..1441389c6 100644
--- a/src/NzbDrone.Common/NzbDrone.Common.csproj
+++ b/src/NzbDrone.Common/NzbDrone.Common.csproj
@@ -66,6 +66,9 @@
..\packages\System.Collections.Immutable.1.5.0\lib\netstandard2.0\System.Collections.Immutable.dll
+
+ ..\packages\System.IO.Abstractions.4.0.11\lib\net40\System.IO.Abstractions.dll
+
..\packages\DotNet4.SocksProxy.1.3.4.0\lib\net40\SocksWebProxy.dll
True
diff --git a/src/NzbDrone.Common/packages.config b/src/NzbDrone.Common/packages.config
index 56cd6e4e1..f23ec5743 100644
--- a/src/NzbDrone.Common/packages.config
+++ b/src/NzbDrone.Common/packages.config
@@ -8,4 +8,5 @@
+
diff --git a/src/NzbDrone.Core.Test/Datastore/Migration/030_add_mediafilerepository_mtimeFixture.cs b/src/NzbDrone.Core.Test/Datastore/Migration/030_add_mediafilerepository_mtimeFixture.cs
new file mode 100644
index 000000000..b79248a61
--- /dev/null
+++ b/src/NzbDrone.Core.Test/Datastore/Migration/030_add_mediafilerepository_mtimeFixture.cs
@@ -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
+ {
+ 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 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 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 {
+ "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 {
+ "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 {
+ "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());
+ }
+
+ [Test]
+ public void migration_030_bad_album_id()
+ {
+ var tracks = new List {
+ "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());
+ }
+
+ [Test]
+ public void migration_030_bad_artist_metadata_id()
+ {
+ var tracks = new List {
+ "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());
+ }
+
+ [Test]
+ public void migration_030_missing_artist()
+ {
+ var tracks = new List {
+ "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());
+ }
+
+ [Test]
+ public void migration_030_missing_tracks()
+ {
+ var tracks = new List {
+ "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());
+ }
+
+ [Test]
+ public void migration_030_duplicate_files()
+ {
+ var tracks = new List {
+ "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 {
+ "folder/track1.mp3",
+ "folder/track2.mp3",
+ };
+
+ var unmonitored_tracks = new List {
+ "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 {
+ "folder/track1.mp3",
+ "folder/track2.mp3",
+ };
+
+ var unmonitored_tracks = new List {
+ "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);
+ }
+ }
+}
diff --git a/src/NzbDrone.Core.Test/DecisionEngineTests/RssSync/DeletedTrackFileSpecificationFixture.cs b/src/NzbDrone.Core.Test/DecisionEngineTests/RssSync/DeletedTrackFileSpecificationFixture.cs
index 698eafce2..d581c5194 100644
--- a/src/NzbDrone.Core.Test/DecisionEngineTests/RssSync/DeletedTrackFileSpecificationFixture.cs
+++ b/src/NzbDrone.Core.Test/DecisionEngineTests/RssSync/DeletedTrackFileSpecificationFixture.cs
@@ -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()
.Setup(v => v.FileExists(path))
diff --git a/src/NzbDrone.Core.Test/Download/DownloadClientTests/Blackhole/ScanWatchFolderFixture.cs b/src/NzbDrone.Core.Test/Download/DownloadClientTests/Blackhole/ScanWatchFolderFixture.cs
index a63bfba6a..227102792 100644
--- a/src/NzbDrone.Core.Test/Download/DownloadClientTests/Blackhole/ScanWatchFolderFixture.cs
+++ b/src/NzbDrone.Core.Test/Download/DownloadClientTests/Blackhole/ScanWatchFolderFixture.cs
@@ -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().Setup(c => c.FilterFiles(It.IsAny(), It.IsAny>()))
- .Returns>((b, s) => s.ToList());
+ .Returns>((b, s) => s.ToList());
+
+ Mocker.GetMock().Setup(c => c.FilterFiles(It.IsAny(), It.IsAny>()))
+ .Returns>((b, s) => s.ToList());
}
protected void GivenChangedItem()
diff --git a/src/NzbDrone.Core.Test/Download/DownloadClientTests/Blackhole/TorrentBlackholeFixture.cs b/src/NzbDrone.Core.Test/Download/DownloadClientTests/Blackhole/TorrentBlackholeFixture.cs
index e972fc474..6fd6c6042 100644
--- a/src/NzbDrone.Core.Test/Download/DownloadClientTests/Blackhole/TorrentBlackholeFixture.cs
+++ b/src/NzbDrone.Core.Test/Download/DownloadClientTests/Blackhole/TorrentBlackholeFixture.cs
@@ -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().Setup(c => c.FilterFiles(It.IsAny(), It.IsAny>()))
.Returns>((b, s) => s.ToList());
+
+ Mocker.GetMock().Setup(c => c.FilterFiles(It.IsAny(), It.IsAny>()))
+ .Returns>((b, s) => s.ToList());
}
protected void GivenFailedDownload()
diff --git a/src/NzbDrone.Core.Test/Download/DownloadClientTests/Blackhole/UsenetBlackholeFixture.cs b/src/NzbDrone.Core.Test/Download/DownloadClientTests/Blackhole/UsenetBlackholeFixture.cs
index 291846222..04786acbc 100644
--- a/src/NzbDrone.Core.Test/Download/DownloadClientTests/Blackhole/UsenetBlackholeFixture.cs
+++ b/src/NzbDrone.Core.Test/Download/DownloadClientTests/Blackhole/UsenetBlackholeFixture.cs
@@ -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().Setup(c => c.FilterFiles(It.IsAny(), It.IsAny>()))
.Returns>((b, s) => s.ToList());
+
+ Mocker.GetMock().Setup(c => c.FilterFiles(It.IsAny(), It.IsAny>()))
+ .Returns>((b, s) => s.ToList());
}
protected void GivenFailedDownload()
diff --git a/src/NzbDrone.Core.Test/Framework/DirectDataMapper.cs b/src/NzbDrone.Core.Test/Framework/DirectDataMapper.cs
index a75daf167..ccc469e21 100644
--- a/src/NzbDrone.Core.Test/Framework/DirectDataMapper.cs
+++ b/src/NzbDrone.Core.Test/Framework/DirectDataMapper.cs
@@ -88,7 +88,7 @@ namespace NzbDrone.Core.Test.Framework
value = dataRow.ItemArray[i];
}
- item[columnName] = dataRow.ItemArray[i];
+ item[columnName] = value;
}
return item;
diff --git a/src/NzbDrone.Core.Test/Framework/FileSystemTest.cs b/src/NzbDrone.Core.Test/Framework/FileSystemTest.cs
new file mode 100644
index 000000000..8e43591a2
--- /dev/null
+++ b/src/NzbDrone.Core.Test/Framework/FileSystemTest.cs
@@ -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 : CoreTest 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("ActualDiskProvider", new ResolverOverride[] {
+ new ParameterOverride("fileSystem", FileSystem)
+ });
+ }
+ }
+}
diff --git a/src/NzbDrone.Core.Test/Housekeeping/Housekeepers/CleanupOrphanedTrackFilesFixture.cs b/src/NzbDrone.Core.Test/Housekeeping/Housekeepers/CleanupOrphanedTrackFilesFixture.cs
index 95c824745..fa3fbcae9 100644
--- a/src/NzbDrone.Core.Test/Housekeeping/Housekeepers/CleanupOrphanedTrackFilesFixture.cs
+++ b/src/NzbDrone.Core.Test/Housekeeping/Housekeepers/CleanupOrphanedTrackFilesFixture.cs
@@ -14,24 +14,26 @@ namespace NzbDrone.Core.Test.Housekeeping.Housekeepers
public class CleanupOrphanedTrackFilesFixture : DbTest
{
[Test]
- public void should_delete_orphaned_track_files()
+ public void should_unlink_orphaned_track_files()
{
var trackFile = Builder.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.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