mirror of
https://github.com/Radarr/Radarr.git
synced 2024-09-11 12:02:35 +02:00
Merge pull request #139 from Sonarr/verified-file-transfer
Verified file transfer
This commit is contained in:
commit
51f705d89a
@ -10,22 +10,6 @@ namespace NzbDrone.Common.Test.DiskTests
|
||||
{
|
||||
public abstract class DiskProviderFixtureBase<TSubject> : TestBase<TSubject> where TSubject : class, IDiskProvider
|
||||
{
|
||||
public DirectoryInfo GetFilledTempFolder()
|
||||
{
|
||||
var tempFolder = GetTempFilePath();
|
||||
Directory.CreateDirectory(tempFolder);
|
||||
|
||||
File.WriteAllText(Path.Combine(tempFolder, Path.GetRandomFileName()), "RootFile");
|
||||
|
||||
var subDir = Path.Combine(tempFolder, Path.GetRandomFileName());
|
||||
Directory.CreateDirectory(subDir);
|
||||
|
||||
File.WriteAllText(Path.Combine(subDir, Path.GetRandomFileName()), "SubFile1");
|
||||
File.WriteAllText(Path.Combine(subDir, Path.GetRandomFileName()), "SubFile2");
|
||||
|
||||
return new DirectoryInfo(tempFolder);
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void directory_exist_should_be_able_to_find_existing_folder()
|
||||
{
|
||||
@ -101,65 +85,9 @@ public void MoveFile_should_not_move_overwrite_itself()
|
||||
|
||||
File.WriteAllText(source, "SourceFile1");
|
||||
|
||||
Subject.MoveFile(source, source, true);
|
||||
Assert.Throws<IOException>(() => Subject.MoveFile(source, source, true));
|
||||
|
||||
File.Exists(source).Should().BeTrue();
|
||||
ExceptionVerification.ExpectedWarns(1);
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void CopyFolder_should_copy_folder()
|
||||
{
|
||||
var source = GetFilledTempFolder();
|
||||
var destination = new DirectoryInfo(GetTempFilePath());
|
||||
|
||||
Subject.CopyFolder(source.FullName, destination.FullName);
|
||||
|
||||
VerifyCopy(source.FullName, destination.FullName);
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void CopyFolder_should_overwrite_existing_folder()
|
||||
{
|
||||
var source = GetFilledTempFolder();
|
||||
var destination = new DirectoryInfo(GetTempFilePath());
|
||||
Subject.CopyFolder(source.FullName, destination.FullName);
|
||||
|
||||
//Delete Random File
|
||||
destination.GetFiles("*.*", SearchOption.AllDirectories).First().Delete();
|
||||
|
||||
Subject.CopyFolder(source.FullName, destination.FullName);
|
||||
|
||||
VerifyCopy(source.FullName, destination.FullName);
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void MoveFolder_should_move_folder()
|
||||
{
|
||||
var original = GetFilledTempFolder();
|
||||
var source = new DirectoryInfo(GetTempFilePath());
|
||||
var destination = new DirectoryInfo(GetTempFilePath());
|
||||
|
||||
Subject.CopyFolder(original.FullName, source.FullName);
|
||||
|
||||
Subject.MoveFolder(source.FullName, destination.FullName);
|
||||
|
||||
VerifyMove(original.FullName, source.FullName, destination.FullName);
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void MoveFolder_should_overwrite_existing_folder()
|
||||
{
|
||||
var original = GetFilledTempFolder();
|
||||
var source = new DirectoryInfo(GetTempFilePath());
|
||||
var destination = new DirectoryInfo(GetTempFilePath());
|
||||
|
||||
Subject.CopyFolder(original.FullName, source.FullName);
|
||||
Subject.CopyFolder(original.FullName, destination.FullName);
|
||||
|
||||
Subject.MoveFolder(source.FullName, destination.FullName);
|
||||
|
||||
VerifyMove(original.FullName, source.FullName, destination.FullName);
|
||||
}
|
||||
|
||||
[Test]
|
||||
@ -194,72 +122,6 @@ public void should_be_able_to_delete_directory_with_read_only_file()
|
||||
Directory.Exists(sourceDir).Should().BeFalse();
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void should_be_able_to_hardlink_file()
|
||||
{
|
||||
var sourceDir = GetTempFilePath();
|
||||
var source = Path.Combine(sourceDir, "test.txt");
|
||||
var destination = Path.Combine(sourceDir, "destination.txt");
|
||||
|
||||
Directory.CreateDirectory(sourceDir);
|
||||
|
||||
Subject.WriteAllText(source, "SourceFile");
|
||||
|
||||
var result = Subject.TransferFile(source, destination, TransferMode.HardLink);
|
||||
|
||||
result.Should().Be(TransferMode.HardLink);
|
||||
|
||||
File.AppendAllText(source, "Test");
|
||||
File.ReadAllText(destination).Should().Be("SourceFileTest");
|
||||
}
|
||||
|
||||
private void DoHardLinkRename(FileShare fileShare)
|
||||
{
|
||||
var sourceDir = GetTempFilePath();
|
||||
var source = Path.Combine(sourceDir, "test.txt");
|
||||
var destination = Path.Combine(sourceDir, "destination.txt");
|
||||
var rename = Path.Combine(sourceDir, "rename.txt");
|
||||
|
||||
Directory.CreateDirectory(sourceDir);
|
||||
|
||||
Subject.WriteAllText(source, "SourceFile");
|
||||
|
||||
Subject.TransferFile(source, destination, TransferMode.HardLink);
|
||||
|
||||
using (var stream = new FileStream(source, FileMode.Open, FileAccess.Read, fileShare))
|
||||
{
|
||||
stream.ReadByte();
|
||||
|
||||
Subject.MoveFile(destination, rename);
|
||||
|
||||
stream.ReadByte();
|
||||
}
|
||||
|
||||
File.Exists(rename).Should().BeTrue();
|
||||
File.Exists(destination).Should().BeFalse();
|
||||
|
||||
File.AppendAllText(source, "Test");
|
||||
File.ReadAllText(rename).Should().Be("SourceFileTest");
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void should_be_able_to_rename_open_hardlinks_with_fileshare_delete()
|
||||
{
|
||||
DoHardLinkRename(FileShare.Delete);
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void should_not_be_able_to_rename_open_hardlinks_with_fileshare_none()
|
||||
{
|
||||
Assert.Throws<IOException>(() => DoHardLinkRename(FileShare.None));
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void should_not_be_able_to_rename_open_hardlinks_with_fileshare_write()
|
||||
{
|
||||
Assert.Throws<IOException>(() => DoHardLinkRename(FileShare.Read));
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void empty_folder_should_return_folder_modified_date()
|
||||
{
|
||||
@ -338,14 +200,6 @@ public void should_be_set_last_file_write()
|
||||
Subject.FileGetLastWrite(testFile).Should().Be(lastWriteTime);
|
||||
}
|
||||
|
||||
[Test]
|
||||
[Explicit]
|
||||
public void check_last_write()
|
||||
{
|
||||
Console.WriteLine(Subject.FolderGetLastWrite(GetFilledTempFolder().FullName));
|
||||
Console.WriteLine(GetFilledTempFolder().LastWriteTimeUtc);
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void GetParentFolder_should_remove_trailing_slash_before_getting_parent_folder()
|
||||
{
|
||||
@ -355,22 +209,51 @@ public void GetParentFolder_should_remove_trailing_slash_before_getting_parent_f
|
||||
Subject.GetParentFolder(path).Should().Be(parent);
|
||||
}
|
||||
|
||||
private void VerifyCopy(string source, string destination)
|
||||
private void DoHardLinkRename(FileShare fileShare)
|
||||
{
|
||||
var sourceFiles = Directory.GetFileSystemEntries(source, "*", SearchOption.AllDirectories).Select(v => v.Substring(source.Length + 1)).ToArray();
|
||||
var destFiles = Directory.GetFileSystemEntries(destination, "*", SearchOption.AllDirectories).Select(v => v.Substring(destination.Length + 1)).ToArray();
|
||||
var sourceDir = GetTempFilePath();
|
||||
var source = Path.Combine(sourceDir, "test.txt");
|
||||
var destination = Path.Combine(sourceDir, "destination.txt");
|
||||
var rename = Path.Combine(sourceDir, "rename.txt");
|
||||
|
||||
CollectionAssert.AreEquivalent(sourceFiles, destFiles);
|
||||
Directory.CreateDirectory(sourceDir);
|
||||
|
||||
File.WriteAllText(source, "SourceFile");
|
||||
|
||||
Subject.TryCreateHardLink(source, destination).Should().BeTrue();
|
||||
|
||||
using (var stream = new FileStream(source, FileMode.Open, FileAccess.Read, fileShare))
|
||||
{
|
||||
stream.ReadByte();
|
||||
|
||||
Subject.MoveFile(destination, rename);
|
||||
|
||||
stream.ReadByte();
|
||||
}
|
||||
|
||||
File.Exists(rename).Should().BeTrue();
|
||||
File.Exists(destination).Should().BeFalse();
|
||||
|
||||
File.AppendAllText(source, "Test");
|
||||
File.ReadAllText(rename).Should().Be("SourceFileTest");
|
||||
}
|
||||
|
||||
private void VerifyMove(string source, string from, string destination)
|
||||
[Test]
|
||||
public void should_be_able_to_rename_open_hardlinks_with_fileshare_delete()
|
||||
{
|
||||
Directory.Exists(from).Should().BeFalse();
|
||||
DoHardLinkRename(FileShare.Delete);
|
||||
}
|
||||
|
||||
var sourceFiles = Directory.GetFileSystemEntries(source, "*", SearchOption.AllDirectories).Select(v => v.Substring(source.Length + 1)).ToArray();
|
||||
var destFiles = Directory.GetFileSystemEntries(destination, "*", SearchOption.AllDirectories).Select(v => v.Substring(destination.Length + 1)).ToArray();
|
||||
[Test]
|
||||
public void should_not_be_able_to_rename_open_hardlinks_with_fileshare_none()
|
||||
{
|
||||
Assert.Throws<IOException>(() => DoHardLinkRename(FileShare.None));
|
||||
}
|
||||
|
||||
CollectionAssert.AreEquivalent(sourceFiles, destFiles);
|
||||
[Test]
|
||||
public void should_not_be_able_to_rename_open_hardlinks_with_fileshare_write()
|
||||
{
|
||||
Assert.Throws<IOException>(() => DoHardLinkRename(FileShare.Read));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
416
src/NzbDrone.Common.Test/DiskTests/DiskTransferServiceFixture.cs
Normal file
416
src/NzbDrone.Common.Test/DiskTests/DiskTransferServiceFixture.cs
Normal file
@ -0,0 +1,416 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using Moq;
|
||||
using NUnit.Framework;
|
||||
using NzbDrone.Common.Disk;
|
||||
using NzbDrone.Test.Common;
|
||||
using FluentAssertions;
|
||||
|
||||
namespace NzbDrone.Common.Test.DiskTests
|
||||
{
|
||||
[TestFixture]
|
||||
public class DiskTransferServiceFixture : TestBase<DiskTransferService>
|
||||
{
|
||||
private readonly String _sourcePath = @"C:\source\my.video.mkv".AsOsAgnostic();
|
||||
private readonly String _targetPath = @"C:\target\my.video.mkv".AsOsAgnostic();
|
||||
private readonly String _backupPath = @"C:\source\my.video.mkv.backup~".AsOsAgnostic();
|
||||
private readonly String _tempTargetPath = @"C:\target\my.video.mkv.partial~".AsOsAgnostic();
|
||||
|
||||
[SetUp]
|
||||
public void SetUp()
|
||||
{
|
||||
Mocker.GetMock<IDiskProvider>(MockBehavior.Strict);
|
||||
|
||||
WithEmulatedDiskProvider();
|
||||
|
||||
WithExistingFile(_sourcePath);
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void should_hardlink_only()
|
||||
{
|
||||
WithSuccessfulHardlink(_sourcePath, _targetPath);
|
||||
|
||||
var result = Subject.TransferFile(_sourcePath, _targetPath, TransferMode.HardLink);
|
||||
|
||||
result.Should().Be(TransferMode.HardLink);
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void should_throw_if_hardlink_only_failed()
|
||||
{
|
||||
WithFailedHardlink();
|
||||
|
||||
Assert.Throws<IOException>(() => Subject.TransferFile(_sourcePath, _targetPath, TransferMode.HardLink));
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void should_not_use_verified_transfer_on_windows()
|
||||
{
|
||||
WindowsOnly();
|
||||
|
||||
var result = Subject.TransferFile(_sourcePath, _targetPath, TransferMode.Move);
|
||||
|
||||
Mocker.GetMock<IDiskProvider>()
|
||||
.Verify(v => v.TryCreateHardLink(_sourcePath, _backupPath), Times.Never());
|
||||
|
||||
Mocker.GetMock<IDiskProvider>()
|
||||
.Verify(v => v.MoveFile(_sourcePath, _targetPath, false), Times.Once());
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void should_retry_if_partial_copy()
|
||||
{
|
||||
MonoOnly();
|
||||
|
||||
var retry = 0;
|
||||
Mocker.GetMock<IDiskProvider>()
|
||||
.Setup(v => v.CopyFile(_sourcePath, _tempTargetPath, false))
|
||||
.Callback(() =>
|
||||
{
|
||||
WithExistingFile(_tempTargetPath, true, 900);
|
||||
if (retry++ == 1) WithExistingFile(_tempTargetPath, true, 1000);
|
||||
});
|
||||
|
||||
var result = Subject.TransferFile(_sourcePath, _targetPath, TransferMode.Copy);
|
||||
|
||||
ExceptionVerification.ExpectedWarns(1);
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void should_retry_twice_if_partial_copy()
|
||||
{
|
||||
MonoOnly();
|
||||
|
||||
var retry = 0;
|
||||
Mocker.GetMock<IDiskProvider>()
|
||||
.Setup(v => v.CopyFile(_sourcePath, _tempTargetPath, false))
|
||||
.Callback(() =>
|
||||
{
|
||||
WithExistingFile(_tempTargetPath, true, 900);
|
||||
if (retry++ == 3) throw new Exception("Test Failed, retried too many times.");
|
||||
});
|
||||
|
||||
Assert.Throws<IOException>(() => Subject.TransferFile(_sourcePath, _targetPath, TransferMode.Copy));
|
||||
|
||||
ExceptionVerification.ExpectedWarns(2);
|
||||
ExceptionVerification.ExpectedErrors(1);
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void should_hardlink_before_move()
|
||||
{
|
||||
MonoOnly();
|
||||
|
||||
WithSuccessfulHardlink(_sourcePath, _backupPath);
|
||||
|
||||
var result = Subject.TransferFile(_sourcePath, _targetPath, TransferMode.Move);
|
||||
|
||||
Mocker.GetMock<IDiskProvider>()
|
||||
.Verify(v => v.TryCreateHardLink(_sourcePath, _backupPath), Times.Once());
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void should_remove_source_after_move()
|
||||
{
|
||||
MonoOnly();
|
||||
|
||||
WithSuccessfulHardlink(_sourcePath, _backupPath);
|
||||
|
||||
Mocker.GetMock<IDiskProvider>()
|
||||
.Setup(v => v.MoveFile(_backupPath, _tempTargetPath, false))
|
||||
.Callback(() => WithExistingFile(_tempTargetPath, true));
|
||||
|
||||
var result = Subject.TransferFile(_sourcePath, _targetPath, TransferMode.Move);
|
||||
|
||||
VerifyDeletedFile(_sourcePath);
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void should_remove_backup_if_move_throws()
|
||||
{
|
||||
MonoOnly();
|
||||
|
||||
WithSuccessfulHardlink(_sourcePath, _backupPath);
|
||||
|
||||
Mocker.GetMock<IDiskProvider>()
|
||||
.Setup(v => v.MoveFile(_backupPath, _tempTargetPath, false))
|
||||
.Throws(new IOException("Blackbox IO error"));
|
||||
|
||||
Assert.Throws<IOException>(() => Subject.TransferFile(_sourcePath, _targetPath, TransferMode.Move));
|
||||
|
||||
VerifyDeletedFile(_backupPath);
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void should_remove_partial_if_move_fails()
|
||||
{
|
||||
MonoOnly();
|
||||
|
||||
WithSuccessfulHardlink(_sourcePath, _backupPath);
|
||||
|
||||
Mocker.GetMock<IDiskProvider>()
|
||||
.Setup(v => v.MoveFile(_backupPath, _tempTargetPath, false))
|
||||
.Callback(() =>
|
||||
{
|
||||
WithExistingFile(_backupPath, false);
|
||||
WithExistingFile(_tempTargetPath, true, 900);
|
||||
});
|
||||
|
||||
Subject.TransferFile(_sourcePath, _targetPath, TransferMode.Move);
|
||||
|
||||
VerifyDeletedFile(_tempTargetPath);
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void should_fallback_to_copy_if_hardlink_failed()
|
||||
{
|
||||
MonoOnly();
|
||||
|
||||
WithFailedHardlink();
|
||||
|
||||
var result = Subject.TransferFile(_sourcePath, _targetPath, TransferMode.Move);
|
||||
|
||||
Mocker.GetMock<IDiskProvider>()
|
||||
.Verify(v => v.CopyFile(_sourcePath, _tempTargetPath, false), Times.Once());
|
||||
|
||||
Mocker.GetMock<IDiskProvider>()
|
||||
.Verify(v => v.MoveFile(_tempTargetPath, _targetPath, false), Times.Once());
|
||||
|
||||
VerifyDeletedFile(_sourcePath);
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void CopyFolder_should_copy_folder()
|
||||
{
|
||||
WithRealDiskProvider();
|
||||
|
||||
var source = GetFilledTempFolder();
|
||||
var destination = new DirectoryInfo(GetTempFilePath());
|
||||
|
||||
Subject.TransferFolder(source.FullName, destination.FullName, TransferMode.Copy);
|
||||
|
||||
VerifyCopyFolder(source.FullName, destination.FullName);
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void CopyFolder_should_overwrite_existing_folder()
|
||||
{
|
||||
WithRealDiskProvider();
|
||||
|
||||
var source = GetFilledTempFolder();
|
||||
var destination = new DirectoryInfo(GetTempFilePath());
|
||||
Subject.TransferFolder(source.FullName, destination.FullName, TransferMode.Copy);
|
||||
|
||||
//Delete Random File
|
||||
destination.GetFiles("*.*", SearchOption.AllDirectories).First().Delete();
|
||||
|
||||
Subject.TransferFolder(source.FullName, destination.FullName, TransferMode.Copy);
|
||||
|
||||
VerifyCopyFolder(source.FullName, destination.FullName);
|
||||
}
|
||||
|
||||
|
||||
[Test]
|
||||
public void MoveFolder_should_move_folder()
|
||||
{
|
||||
WithRealDiskProvider();
|
||||
|
||||
var original = GetFilledTempFolder();
|
||||
var source = new DirectoryInfo(GetTempFilePath());
|
||||
var destination = new DirectoryInfo(GetTempFilePath());
|
||||
|
||||
Subject.TransferFolder(original.FullName, source.FullName, TransferMode.Copy);
|
||||
|
||||
Subject.TransferFolder(source.FullName, destination.FullName, TransferMode.Move);
|
||||
|
||||
VerifyMoveFolder(original.FullName, source.FullName, destination.FullName);
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void MoveFolder_should_overwrite_existing_folder()
|
||||
{
|
||||
WithRealDiskProvider();
|
||||
|
||||
var original = GetFilledTempFolder();
|
||||
var source = new DirectoryInfo(GetTempFilePath());
|
||||
var destination = new DirectoryInfo(GetTempFilePath());
|
||||
|
||||
Subject.TransferFolder(original.FullName, source.FullName, TransferMode.Copy);
|
||||
Subject.TransferFolder(original.FullName, destination.FullName, TransferMode.Copy);
|
||||
|
||||
Subject.TransferFolder(source.FullName, destination.FullName, TransferMode.Move);
|
||||
|
||||
VerifyMoveFolder(original.FullName, source.FullName, destination.FullName);
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void should_throw_if_destination_is_readonly()
|
||||
{
|
||||
Mocker.GetMock<IDiskProvider>()
|
||||
.Setup(v => v.CopyFile(It.IsAny<string>(), It.IsAny<string>(), false))
|
||||
.Throws(new IOException("Access denied"));
|
||||
|
||||
Assert.Throws<IOException>(() => Subject.TransferFile(_sourcePath, _targetPath, TransferMode.Copy));
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void should_throw_if_destination_is_child_of_source()
|
||||
{
|
||||
var childPath = Path.Combine(_sourcePath, "child");
|
||||
|
||||
Assert.Throws<IOException>(() => Subject.TransferFile(_sourcePath, childPath, TransferMode.Move));
|
||||
}
|
||||
|
||||
public DirectoryInfo GetFilledTempFolder()
|
||||
{
|
||||
var tempFolder = GetTempFilePath();
|
||||
Directory.CreateDirectory(tempFolder);
|
||||
|
||||
File.WriteAllText(Path.Combine(tempFolder, Path.GetRandomFileName()), "RootFile");
|
||||
|
||||
var subDir = Path.Combine(tempFolder, Path.GetRandomFileName());
|
||||
Directory.CreateDirectory(subDir);
|
||||
|
||||
File.WriteAllText(Path.Combine(subDir, Path.GetRandomFileName()), "SubFile1");
|
||||
File.WriteAllText(Path.Combine(subDir, Path.GetRandomFileName()), "SubFile2");
|
||||
|
||||
return new DirectoryInfo(tempFolder);
|
||||
}
|
||||
|
||||
private void WithExistingFile(string path, bool exists = true, int size = 1000)
|
||||
{
|
||||
Mocker.GetMock<IDiskProvider>()
|
||||
.Setup(v => v.FileExists(path))
|
||||
.Returns(exists);
|
||||
|
||||
Mocker.GetMock<IDiskProvider>()
|
||||
.Setup(v => v.GetFileSize(path))
|
||||
.Returns(size);
|
||||
}
|
||||
|
||||
private void WithSuccessfulHardlink(string source, string target)
|
||||
{
|
||||
Mocker.GetMock<IDiskProvider>()
|
||||
.Setup(v => v.TryCreateHardLink(source, target))
|
||||
.Callback(() => WithExistingFile(target))
|
||||
.Returns(true);
|
||||
}
|
||||
|
||||
private void WithFailedHardlink()
|
||||
{
|
||||
Mocker.GetMock<IDiskProvider>()
|
||||
.Setup(v => v.TryCreateHardLink(It.IsAny<String>(), It.IsAny<String>()))
|
||||
.Returns(false);
|
||||
}
|
||||
|
||||
private void WithEmulatedDiskProvider()
|
||||
{
|
||||
Mocker.GetMock<IDiskProvider>()
|
||||
.Setup(v => v.FileExists(It.IsAny<string>()))
|
||||
.Returns(false);
|
||||
|
||||
Mocker.GetMock<IDiskProvider>()
|
||||
.Setup(v => v.CopyFile(It.IsAny<string>(), It.IsAny<string>(), false))
|
||||
.Callback<string, string, bool>((s, d, o) =>
|
||||
{
|
||||
WithExistingFile(d);
|
||||
});
|
||||
|
||||
Mocker.GetMock<IDiskProvider>()
|
||||
.Setup(v => v.MoveFile(It.IsAny<string>(), It.IsAny<string>(), false))
|
||||
.Callback<string, string, bool>((s, d, o) =>
|
||||
{
|
||||
WithExistingFile(s, false);
|
||||
WithExistingFile(d);
|
||||
});
|
||||
|
||||
Mocker.GetMock<IDiskProvider>()
|
||||
.Setup(v => v.DeleteFile(It.IsAny<string>()))
|
||||
.Callback<string>(v =>
|
||||
{
|
||||
WithExistingFile(v, false);
|
||||
});
|
||||
}
|
||||
|
||||
private void WithRealDiskProvider()
|
||||
{
|
||||
Mocker.GetMock<IDiskProvider>()
|
||||
.Setup(v => v.FolderExists(It.IsAny<string>()))
|
||||
.Returns<string>(v => Directory.Exists(v));
|
||||
|
||||
Mocker.GetMock<IDiskProvider>()
|
||||
.Setup(v => v.FileExists(It.IsAny<string>()))
|
||||
.Returns<string>(v => File.Exists(v));
|
||||
|
||||
Mocker.GetMock<IDiskProvider>()
|
||||
.Setup(v => v.CreateFolder(It.IsAny<string>()))
|
||||
.Callback<string>(v => 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));
|
||||
|
||||
Mocker.GetMock<IDiskProvider>()
|
||||
.Setup(v => v.DeleteFile(It.IsAny<string>()))
|
||||
.Callback<string>(v => File.Delete(v));
|
||||
|
||||
Mocker.GetMock<IDiskProvider>()
|
||||
.Setup(v => v.GetDirectoryInfos(It.IsAny<string>()))
|
||||
.Returns<string>(v => new DirectoryInfo(v).GetDirectories().ToList());
|
||||
|
||||
Mocker.GetMock<IDiskProvider>()
|
||||
.Setup(v => v.GetFileInfos(It.IsAny<string>()))
|
||||
.Returns<string>(v => new DirectoryInfo(v).GetFiles().ToList());
|
||||
|
||||
Mocker.GetMock<IDiskProvider>()
|
||||
.Setup(v => v.GetFileSize(It.IsAny<string>()))
|
||||
.Returns<string>(v => new FileInfo(v).Length);
|
||||
|
||||
Mocker.GetMock<IDiskProvider>()
|
||||
.Setup(v => v.TryCreateHardLink(It.IsAny<string>(), It.IsAny<string>()))
|
||||
.Returns(false);
|
||||
|
||||
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));
|
||||
|
||||
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);
|
||||
});
|
||||
|
||||
}
|
||||
|
||||
private void VerifyCopyFolder(string source, string destination)
|
||||
{
|
||||
var sourceFiles = Directory.GetFileSystemEntries(source, "*", SearchOption.AllDirectories).Select(v => v.Substring(source.Length + 1)).ToArray();
|
||||
var destFiles = Directory.GetFileSystemEntries(destination, "*", SearchOption.AllDirectories).Select(v => v.Substring(destination.Length + 1)).ToArray();
|
||||
|
||||
CollectionAssert.AreEquivalent(sourceFiles, destFiles);
|
||||
}
|
||||
|
||||
private void VerifyMoveFolder(string source, string from, string destination)
|
||||
{
|
||||
Directory.Exists(from).Should().BeFalse();
|
||||
|
||||
var sourceFiles = Directory.GetFileSystemEntries(source, "*", SearchOption.AllDirectories).Select(v => v.Substring(source.Length + 1)).ToArray();
|
||||
var destFiles = Directory.GetFileSystemEntries(destination, "*", SearchOption.AllDirectories).Select(v => v.Substring(destination.Length + 1)).ToArray();
|
||||
|
||||
CollectionAssert.AreEquivalent(sourceFiles, destFiles);
|
||||
}
|
||||
|
||||
private void VerifyDeletedFile(String filePath)
|
||||
{
|
||||
var path = filePath;
|
||||
|
||||
Mocker.GetMock<IDiskProvider>()
|
||||
.Verify(v => v.DeleteFile(path), Times.Once());
|
||||
}
|
||||
}
|
||||
}
|
@ -72,6 +72,7 @@
|
||||
<Compile Include="DiskTests\DiskProviderFixtureBase.cs" />
|
||||
<Compile Include="DiskTests\FreeSpaceFixtureBase.cs" />
|
||||
<Compile Include="DiskTests\IsParentFixtureBase.cs" />
|
||||
<Compile Include="DiskTests\DiskTransferServiceFixture.cs" />
|
||||
<Compile Include="EnsureTest\PathExtensionFixture.cs" />
|
||||
<Compile Include="EnvironmentProviderTest.cs" />
|
||||
<Compile Include="EnvironmentTests\EnvironmentProviderTest.cs" />
|
||||
|
@ -168,62 +168,6 @@ public void CreateFolder(string path)
|
||||
Directory.CreateDirectory(path);
|
||||
}
|
||||
|
||||
public void CopyFolder(string source, string destination)
|
||||
{
|
||||
Ensure.That(source, () => source).IsValidPath();
|
||||
Ensure.That(destination, () => destination).IsValidPath();
|
||||
|
||||
TransferFolder(source, destination, TransferMode.Copy);
|
||||
}
|
||||
|
||||
public void MoveFolder(string source, string destination)
|
||||
{
|
||||
Ensure.That(source, () => source).IsValidPath();
|
||||
Ensure.That(destination, () => destination).IsValidPath();
|
||||
|
||||
try
|
||||
{
|
||||
TransferFolder(source, destination, TransferMode.Move);
|
||||
DeleteFolder(source, true);
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
e.Data.Add("Source", source);
|
||||
e.Data.Add("Destination", destination);
|
||||
throw;
|
||||
}
|
||||
}
|
||||
|
||||
public void TransferFolder(string source, string destination, TransferMode mode)
|
||||
{
|
||||
Ensure.That(source, () => source).IsValidPath();
|
||||
Ensure.That(destination, () => destination).IsValidPath();
|
||||
|
||||
Logger.ProgressDebug("{0} {1} -> {2}", mode, source, destination);
|
||||
|
||||
var sourceFolder = new DirectoryInfo(source);
|
||||
var targetFolder = new DirectoryInfo(destination);
|
||||
|
||||
if (!targetFolder.Exists)
|
||||
{
|
||||
targetFolder.Create();
|
||||
}
|
||||
|
||||
foreach (var subDir in sourceFolder.GetDirectories())
|
||||
{
|
||||
TransferFolder(subDir.FullName, Path.Combine(destination, subDir.Name), mode);
|
||||
}
|
||||
|
||||
foreach (var sourceFile in sourceFolder.GetFiles("*.*", SearchOption.TopDirectoryOnly))
|
||||
{
|
||||
var destFile = Path.Combine(destination, sourceFile.Name);
|
||||
|
||||
Logger.ProgressDebug("{0} {1} -> {2}", mode, sourceFile, destFile);
|
||||
|
||||
TransferFile(sourceFile.FullName, destFile, mode, true);
|
||||
}
|
||||
}
|
||||
|
||||
public void DeleteFile(string path)
|
||||
{
|
||||
Ensure.That(path, () => path).IsValidPath();
|
||||
@ -236,23 +180,25 @@ public void DeleteFile(string path)
|
||||
|
||||
public void CopyFile(string source, string destination, bool overwrite = false)
|
||||
{
|
||||
TransferFile(source, destination, TransferMode.Copy, overwrite);
|
||||
Ensure.That(source, () => source).IsValidPath();
|
||||
Ensure.That(destination, () => destination).IsValidPath();
|
||||
|
||||
if (source.PathEquals(destination))
|
||||
{
|
||||
throw new IOException(string.Format("Source and destination can't be the same {0}", source));
|
||||
}
|
||||
|
||||
File.Copy(source, destination, overwrite);
|
||||
}
|
||||
|
||||
public void MoveFile(string source, string destination, bool overwrite = false)
|
||||
{
|
||||
TransferFile(source, destination, TransferMode.Move, overwrite);
|
||||
}
|
||||
|
||||
public TransferMode TransferFile(string source, string destination, TransferMode mode, bool overwrite)
|
||||
{
|
||||
Ensure.That(source, () => source).IsValidPath();
|
||||
Ensure.That(destination, () => destination).IsValidPath();
|
||||
|
||||
if (source.PathEquals(destination))
|
||||
{
|
||||
Logger.Warn("Source and destination can't be the same {0}", source);
|
||||
return TransferMode.None;
|
||||
throw new IOException(string.Format("Source and destination can't be the same {0}", source));
|
||||
}
|
||||
|
||||
if (FileExists(destination) && overwrite)
|
||||
@ -260,33 +206,8 @@ public TransferMode TransferFile(string source, string destination, TransferMode
|
||||
DeleteFile(destination);
|
||||
}
|
||||
|
||||
if (mode.HasFlag(TransferMode.HardLink))
|
||||
{
|
||||
bool createdHardlink = TryCreateHardLink(source, destination);
|
||||
if (createdHardlink)
|
||||
{
|
||||
return TransferMode.HardLink;
|
||||
}
|
||||
if (!mode.HasFlag(TransferMode.Copy))
|
||||
{
|
||||
throw new IOException("Hardlinking from '" + source + "' to '" + destination + "' failed.");
|
||||
}
|
||||
}
|
||||
|
||||
if (mode.HasFlag(TransferMode.Copy))
|
||||
{
|
||||
File.Copy(source, destination, overwrite);
|
||||
return TransferMode.Copy;
|
||||
}
|
||||
|
||||
if (mode.HasFlag(TransferMode.Move))
|
||||
{
|
||||
RemoveReadOnly(source);
|
||||
File.Move(source, destination);
|
||||
return TransferMode.Move;
|
||||
}
|
||||
|
||||
return TransferMode.None;
|
||||
RemoveReadOnly(source);
|
||||
File.Move(source, destination);
|
||||
}
|
||||
|
||||
public abstract bool TryCreateHardLink(string source, string destination);
|
||||
|
254
src/NzbDrone.Common/Disk/DiskTransferService.cs
Normal file
254
src/NzbDrone.Common/Disk/DiskTransferService.cs
Normal file
@ -0,0 +1,254 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using System.Threading;
|
||||
using NLog;
|
||||
using NzbDrone.Common.EnsureThat;
|
||||
using NzbDrone.Common.EnvironmentInfo;
|
||||
using NzbDrone.Common.Extensions;
|
||||
|
||||
namespace NzbDrone.Common.Disk
|
||||
{
|
||||
public interface IDiskTransferService
|
||||
{
|
||||
TransferMode TransferFolder(String sourcePath, String targetPath, TransferMode mode, bool verified = true);
|
||||
TransferMode TransferFile(String sourcePath, String targetPath, TransferMode mode, bool overwrite = false, bool verified = true);
|
||||
}
|
||||
|
||||
public class DiskTransferService : IDiskTransferService
|
||||
{
|
||||
private const Int32 RetryCount = 2;
|
||||
|
||||
private readonly IDiskProvider _diskProvider;
|
||||
private readonly Logger _logger;
|
||||
|
||||
public DiskTransferService(IDiskProvider diskProvider, Logger logger)
|
||||
{
|
||||
_diskProvider = diskProvider;
|
||||
_logger = logger;
|
||||
}
|
||||
|
||||
public TransferMode TransferFolder(String sourcePath, String targetPath, TransferMode mode, bool verified = true)
|
||||
{
|
||||
Ensure.That(sourcePath, () => sourcePath).IsValidPath();
|
||||
Ensure.That(targetPath, () => targetPath).IsValidPath();
|
||||
|
||||
if (OsInfo.IsWindows)
|
||||
{
|
||||
// TODO: Atm we haven't seen partial transfers on windows so we disable verified transfer.
|
||||
// (If enabled in the future, be sure to check specifically for ReFS, which doesn't support hardlinks.)
|
||||
verified = false;
|
||||
}
|
||||
|
||||
if (!_diskProvider.FolderExists(targetPath))
|
||||
{
|
||||
_diskProvider.CreateFolder(targetPath);
|
||||
}
|
||||
|
||||
var result = mode;
|
||||
|
||||
foreach (var subDir in _diskProvider.GetDirectoryInfos(sourcePath))
|
||||
{
|
||||
result &= TransferFolder(subDir.FullName, Path.Combine(targetPath, subDir.Name), mode, verified);
|
||||
}
|
||||
|
||||
foreach (var sourceFile in _diskProvider.GetFileInfos(sourcePath))
|
||||
{
|
||||
var destFile = Path.Combine(targetPath, sourceFile.Name);
|
||||
|
||||
result &= TransferFile(sourceFile.FullName, destFile, mode, true, verified);
|
||||
}
|
||||
|
||||
if (mode.HasFlag(TransferMode.Move))
|
||||
{
|
||||
_diskProvider.DeleteFolder(sourcePath, true);
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
public TransferMode TransferFile(String sourcePath, String targetPath, TransferMode mode, bool overwrite = false, bool verified = true)
|
||||
{
|
||||
Ensure.That(sourcePath, () => sourcePath).IsValidPath();
|
||||
Ensure.That(targetPath, () => targetPath).IsValidPath();
|
||||
|
||||
if (OsInfo.IsWindows)
|
||||
{
|
||||
// TODO: Atm we haven't seen partial transfers on windows so we disable verified transfer.
|
||||
// (If enabled in the future, be sure to check specifically for ReFS, which doesn't support hardlinks.)
|
||||
verified = false;
|
||||
}
|
||||
|
||||
_logger.Debug("{0} [{1}] > [{2}]", mode, sourcePath, targetPath);
|
||||
|
||||
if (sourcePath.PathEquals(targetPath))
|
||||
{
|
||||
throw new IOException(string.Format("Source and destination can't be the same {0}", sourcePath));
|
||||
}
|
||||
|
||||
if (sourcePath.IsParentPath(targetPath))
|
||||
{
|
||||
throw new IOException(string.Format("Destination cannot be a child of the source [{0}] => [{1}]", sourcePath, targetPath));
|
||||
}
|
||||
|
||||
if (_diskProvider.FileExists(targetPath) && overwrite)
|
||||
{
|
||||
_diskProvider.DeleteFile(targetPath);
|
||||
}
|
||||
|
||||
if (mode.HasFlag(TransferMode.HardLink))
|
||||
{
|
||||
var createdHardlink = _diskProvider.TryCreateHardLink(sourcePath, targetPath);
|
||||
if (createdHardlink)
|
||||
{
|
||||
return TransferMode.HardLink;
|
||||
}
|
||||
if (!mode.HasFlag(TransferMode.Copy))
|
||||
{
|
||||
throw new IOException("Hardlinking from '" + sourcePath + "' to '" + targetPath + "' failed.");
|
||||
}
|
||||
}
|
||||
|
||||
if (verified)
|
||||
{
|
||||
if (mode.HasFlag(TransferMode.Copy))
|
||||
{
|
||||
if (TryCopyFile(sourcePath, targetPath))
|
||||
{
|
||||
return TransferMode.Copy;
|
||||
}
|
||||
}
|
||||
|
||||
if (mode.HasFlag(TransferMode.Move))
|
||||
{
|
||||
if (TryMoveFile(sourcePath, targetPath))
|
||||
{
|
||||
return TransferMode.Move;
|
||||
}
|
||||
}
|
||||
|
||||
throw new IOException(String.Format("Failed to completely transfer [{0}] to [{1}], aborting.", sourcePath, targetPath));
|
||||
}
|
||||
else
|
||||
{
|
||||
if (mode.HasFlag(TransferMode.Copy))
|
||||
{
|
||||
_diskProvider.CopyFile(sourcePath, targetPath);
|
||||
return TransferMode.Copy;
|
||||
}
|
||||
|
||||
if (mode.HasFlag(TransferMode.Move))
|
||||
{
|
||||
_diskProvider.MoveFile(sourcePath, targetPath);
|
||||
return TransferMode.Move;
|
||||
}
|
||||
}
|
||||
|
||||
return TransferMode.None;
|
||||
}
|
||||
|
||||
private Boolean TryCopyFile(String sourcePath, String targetPath)
|
||||
{
|
||||
var originalSize = _diskProvider.GetFileSize(sourcePath);
|
||||
|
||||
var tempTargetPath = targetPath + ".partial~";
|
||||
|
||||
for (var i = 0; i <= RetryCount; i++)
|
||||
{
|
||||
_diskProvider.CopyFile(sourcePath, tempTargetPath);
|
||||
|
||||
if (_diskProvider.FileExists(tempTargetPath))
|
||||
{
|
||||
var targetSize = _diskProvider.GetFileSize(tempTargetPath);
|
||||
|
||||
if (targetSize == originalSize)
|
||||
{
|
||||
_diskProvider.MoveFile(tempTargetPath, targetPath);
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
Thread.Sleep(5000);
|
||||
|
||||
_diskProvider.DeleteFile(tempTargetPath);
|
||||
|
||||
if (i == RetryCount)
|
||||
{
|
||||
_logger.Error("Failed to completely transfer [{0}] to [{1}], aborting.", sourcePath, targetPath, i + 1, RetryCount);
|
||||
}
|
||||
else
|
||||
{
|
||||
_logger.Warn("Failed to completely transfer [{0}] to [{1}], retrying [{2}/{3}].", sourcePath, targetPath, i + 1, RetryCount);
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
private Boolean TryMoveFile(String sourcePath, String targetPath)
|
||||
{
|
||||
var originalSize = _diskProvider.GetFileSize(sourcePath);
|
||||
|
||||
var backupPath = sourcePath + ".backup~";
|
||||
var tempTargetPath = targetPath + ".partial~";
|
||||
|
||||
if (_diskProvider.FileExists(backupPath))
|
||||
{
|
||||
_logger.Trace("Removing old backup.");
|
||||
_diskProvider.DeleteFile(backupPath);
|
||||
}
|
||||
|
||||
if (_diskProvider.FileExists(tempTargetPath))
|
||||
{
|
||||
_logger.Trace("Removing old partial.");
|
||||
_diskProvider.DeleteFile(tempTargetPath);
|
||||
}
|
||||
|
||||
try
|
||||
{
|
||||
_logger.Trace("Attempting to move hardlinked backup.");
|
||||
if (_diskProvider.TryCreateHardLink(sourcePath, backupPath))
|
||||
{
|
||||
_diskProvider.MoveFile(backupPath, tempTargetPath);
|
||||
|
||||
if (_diskProvider.FileExists(tempTargetPath))
|
||||
{
|
||||
var targetSize = _diskProvider.GetFileSize(tempTargetPath);
|
||||
|
||||
if (targetSize == originalSize)
|
||||
{
|
||||
_diskProvider.MoveFile(tempTargetPath, targetPath);
|
||||
_logger.Trace("Hardlink move succeeded, deleting source.");
|
||||
_diskProvider.DeleteFile(sourcePath);
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
Thread.Sleep(5000);
|
||||
|
||||
_diskProvider.DeleteFile(tempTargetPath);
|
||||
}
|
||||
}
|
||||
finally
|
||||
{
|
||||
if (_diskProvider.FileExists(backupPath))
|
||||
{
|
||||
_diskProvider.DeleteFile(backupPath);
|
||||
}
|
||||
}
|
||||
|
||||
_logger.Trace("Hardlink move failed, reverting to copy.");
|
||||
if (TryCopyFile(sourcePath, targetPath))
|
||||
{
|
||||
_logger.Trace("Copy succeeded, deleting source.");
|
||||
_diskProvider.DeleteFile(sourcePath);
|
||||
return true;
|
||||
}
|
||||
|
||||
_logger.Trace("Copy failed.");
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
@ -25,13 +25,9 @@ public interface IDiskProvider
|
||||
long GetFolderSize(string path);
|
||||
long GetFileSize(string path);
|
||||
void CreateFolder(string path);
|
||||
void CopyFolder(string source, string destination);
|
||||
void MoveFolder(string source, string destination);
|
||||
void TransferFolder(string source, string destination, TransferMode transferMode);
|
||||
void DeleteFile(string path);
|
||||
void CopyFile(string source, string destination, bool overwrite = false);
|
||||
void MoveFile(string source, string destination, bool overwrite = false);
|
||||
TransferMode TransferFile(string source, string destination, TransferMode transferMode, bool overwrite = false);
|
||||
bool TryCreateHardLink(string source, string destination);
|
||||
void DeleteFolder(string path, bool recursive);
|
||||
string ReadAllText(string filePath);
|
||||
|
@ -79,6 +79,7 @@
|
||||
<Compile Include="Disk\OsPath.cs" />
|
||||
<Compile Include="Disk\DiskProviderBase.cs" />
|
||||
<Compile Include="Disk\IDiskProvider.cs" />
|
||||
<Compile Include="Disk\DiskTransferService.cs" />
|
||||
<Compile Include="Disk\TransferMode.cs" />
|
||||
<Compile Include="EnsureThat\Ensure.cs" />
|
||||
<Compile Include="EnsureThat\EnsureBoolExtensions.cs" />
|
||||
|
@ -45,7 +45,8 @@ public void should_use_move_when_recycleBin_is_configured()
|
||||
|
||||
Mocker.Resolve<RecycleBinProvider>().DeleteFolder(path);
|
||||
|
||||
Mocker.GetMock<IDiskProvider>().Verify(v => v.MoveFolder(path, @"C:\Test\Recycle Bin\30 Rock".AsOsAgnostic()), Times.Once());
|
||||
Mocker.GetMock<IDiskTransferService>()
|
||||
.Verify(v => v.TransferFolder(path, @"C:\Test\Recycle Bin\30 Rock".AsOsAgnostic(), TransferMode.Move, true), Times.Once());
|
||||
}
|
||||
|
||||
[Test]
|
||||
@ -68,7 +69,7 @@ public void should_call_fileSetLastWriteTime_for_each_file()
|
||||
var path = @"C:\Test\TV\30 Rock".AsOsAgnostic();
|
||||
|
||||
Mocker.GetMock<IDiskProvider>().Setup(s => s.GetFiles(@"C:\Test\Recycle Bin\30 Rock".AsOsAgnostic(), SearchOption.AllDirectories))
|
||||
.Returns(new[] { "File1", "File2", "File3" });
|
||||
.Returns(new[] { "File1", "File2", "File3" });
|
||||
|
||||
Mocker.Resolve<RecycleBinProvider>().DeleteFolder(path);
|
||||
|
||||
|
@ -44,7 +44,7 @@ public void should_use_move_when_recycleBin_is_configured()
|
||||
|
||||
Mocker.Resolve<RecycleBinProvider>().DeleteFile(path);
|
||||
|
||||
Mocker.GetMock<IDiskProvider>().Verify(v => v.MoveFile(path, @"C:\Test\Recycle Bin\S01E01.avi".AsOsAgnostic(), true), Times.Once());
|
||||
Mocker.GetMock<IDiskTransferService>().Verify(v => v.TransferFile(path, @"C:\Test\Recycle Bin\S01E01.avi".AsOsAgnostic(), TransferMode.Move, false, true), Times.Once());
|
||||
}
|
||||
|
||||
[Test]
|
||||
@ -60,7 +60,7 @@ public void should_use_alternative_name_if_already_exists()
|
||||
|
||||
Mocker.Resolve<RecycleBinProvider>().DeleteFile(path);
|
||||
|
||||
Mocker.GetMock<IDiskProvider>().Verify(v => v.MoveFile(path, @"C:\Test\Recycle Bin\S01E01_2.avi".AsOsAgnostic(), true), Times.Once());
|
||||
Mocker.GetMock<IDiskTransferService>().Verify(v => v.TransferFile(path, @"C:\Test\Recycle Bin\S01E01_2.avi".AsOsAgnostic(), TransferMode.Move, false, true), Times.Once());
|
||||
}
|
||||
|
||||
[Test]
|
||||
|
@ -39,8 +39,8 @@ public void Setup()
|
||||
|
||||
private void GivenFailedMove()
|
||||
{
|
||||
Mocker.GetMock<IDiskProvider>()
|
||||
.Setup(s => s.MoveFolder(It.IsAny<String>(), It.IsAny<String>()))
|
||||
Mocker.GetMock<IDiskTransferService>()
|
||||
.Setup(s => s.TransferFolder(It.IsAny<String>(), It.IsAny<String>(), TransferMode.Move, true))
|
||||
.Throws<IOException>();
|
||||
}
|
||||
|
||||
|
@ -135,7 +135,8 @@ public void Should_copy_update_client_to_root_of_sandbox()
|
||||
|
||||
Subject.Execute(new ApplicationUpdateCommand());
|
||||
|
||||
Mocker.GetMock<IDiskProvider>().Verify(c => c.MoveFolder(updateClientFolder, _sandboxFolder));
|
||||
Mocker.GetMock<IDiskTransferService>()
|
||||
.Verify(c => c.TransferFolder(updateClientFolder, _sandboxFolder, TransferMode.Move, false));
|
||||
}
|
||||
|
||||
[Test]
|
||||
|
@ -25,6 +25,7 @@ public interface IBackupService
|
||||
public class BackupService : IBackupService, IExecute<BackupCommand>
|
||||
{
|
||||
private readonly IMainDatabase _maindDb;
|
||||
private readonly IDiskTransferService _diskTransferService;
|
||||
private readonly IDiskProvider _diskProvider;
|
||||
private readonly IAppFolderInfo _appFolderInfo;
|
||||
private readonly IArchiveService _archiveService;
|
||||
@ -35,12 +36,14 @@ public class BackupService : IBackupService, IExecute<BackupCommand>
|
||||
private static readonly Regex BackupFileRegex = new Regex(@"nzbdrone_backup_[._0-9]+\.zip", RegexOptions.Compiled | RegexOptions.IgnoreCase);
|
||||
|
||||
public BackupService(IMainDatabase maindDb,
|
||||
IDiskProvider diskProvider,
|
||||
IAppFolderInfo appFolderInfo,
|
||||
IArchiveService archiveService,
|
||||
IDiskTransferService diskTransferService,
|
||||
IDiskProvider diskProvider,
|
||||
IAppFolderInfo appFolderInfo,
|
||||
IArchiveService archiveService,
|
||||
Logger logger)
|
||||
{
|
||||
_maindDb = maindDb;
|
||||
_diskTransferService = diskTransferService;
|
||||
_diskProvider = diskProvider;
|
||||
_appFolderInfo = appFolderInfo;
|
||||
_archiveService = archiveService;
|
||||
@ -115,7 +118,7 @@ private void BackupDatabase()
|
||||
var databaseFile = _appFolderInfo.GetNzbDroneDatabase();
|
||||
var tempDatabaseFile = Path.Combine(_backupTempFolder, Path.GetFileName(databaseFile));
|
||||
|
||||
_diskProvider.CopyFile(databaseFile, tempDatabaseFile, true);
|
||||
_diskTransferService.TransferFile(databaseFile, tempDatabaseFile, TransferMode.Copy);
|
||||
|
||||
unitOfWork.Commit();
|
||||
}
|
||||
@ -128,7 +131,7 @@ private void BackupConfigFile()
|
||||
var configFile = _appFolderInfo.GetConfigPath();
|
||||
var tempConfigFile = Path.Combine(_backupTempFolder, Path.GetFileName(configFile));
|
||||
|
||||
_diskProvider.CopyFile(configFile, tempConfigFile, true);
|
||||
_diskTransferService.TransferFile(configFile, tempConfigFile, TransferMode.Copy);
|
||||
}
|
||||
|
||||
private void CleanupOldBackups(BackupType backupType)
|
||||
|
@ -27,6 +27,7 @@ public class EpisodeFileMovingService : IMoveEpisodeFiles
|
||||
private readonly IEpisodeService _episodeService;
|
||||
private readonly IUpdateEpisodeFileService _updateEpisodeFileService;
|
||||
private readonly IBuildFileNames _buildFileNames;
|
||||
private readonly IDiskTransferService _diskTransferService;
|
||||
private readonly IDiskProvider _diskProvider;
|
||||
private readonly IMediaFileAttributeService _mediaFileAttributeService;
|
||||
private readonly IEventAggregator _eventAggregator;
|
||||
@ -36,6 +37,7 @@ public class EpisodeFileMovingService : IMoveEpisodeFiles
|
||||
public EpisodeFileMovingService(IEpisodeService episodeService,
|
||||
IUpdateEpisodeFileService updateEpisodeFileService,
|
||||
IBuildFileNames buildFileNames,
|
||||
IDiskTransferService diskTransferService,
|
||||
IDiskProvider diskProvider,
|
||||
IMediaFileAttributeService mediaFileAttributeService,
|
||||
IEventAggregator eventAggregator,
|
||||
@ -45,6 +47,7 @@ public EpisodeFileMovingService(IEpisodeService episodeService,
|
||||
_episodeService = episodeService;
|
||||
_updateEpisodeFileService = updateEpisodeFileService;
|
||||
_buildFileNames = buildFileNames;
|
||||
_diskTransferService = diskTransferService;
|
||||
_diskProvider = diskProvider;
|
||||
_mediaFileAttributeService = mediaFileAttributeService;
|
||||
_eventAggregator = eventAggregator;
|
||||
@ -112,8 +115,7 @@ private EpisodeFile TransferFile(EpisodeFile episodeFile, Series series, List<Ep
|
||||
throw new SameFilenameException("File not moved, source and destination are the same", episodeFilePath);
|
||||
}
|
||||
|
||||
_logger.Debug("{0} [{1}] > [{2}]", mode, episodeFilePath, destinationFilename);
|
||||
_diskProvider.TransferFile(episodeFilePath, destinationFilename, mode);
|
||||
_diskTransferService.TransferFile(episodeFilePath, destinationFilename, mode);
|
||||
|
||||
episodeFile.RelativePath = series.Path.GetRelativePath(destinationFilename);
|
||||
|
||||
|
@ -22,13 +22,18 @@ public interface IRecycleBinProvider
|
||||
|
||||
public class RecycleBinProvider : IHandleAsync<SeriesDeletedEvent>, IExecute<CleanUpRecycleBinCommand>, IRecycleBinProvider
|
||||
{
|
||||
private readonly IDiskTransferService _diskTransferService;
|
||||
private readonly IDiskProvider _diskProvider;
|
||||
private readonly IConfigService _configService;
|
||||
private readonly Logger _logger;
|
||||
|
||||
|
||||
public RecycleBinProvider(IDiskProvider diskProvider, IConfigService configService, Logger logger)
|
||||
public RecycleBinProvider(IDiskTransferService diskTransferService,
|
||||
IDiskProvider diskProvider,
|
||||
IConfigService configService,
|
||||
Logger logger)
|
||||
{
|
||||
_diskTransferService = diskTransferService;
|
||||
_diskProvider = diskProvider;
|
||||
_configService = configService;
|
||||
_logger = logger;
|
||||
@ -51,7 +56,7 @@ public void DeleteFolder(string path)
|
||||
var destination = Path.Combine(recyclingBin, new DirectoryInfo(path).Name);
|
||||
|
||||
_logger.Debug("Moving '{0}' to '{1}'", path, destination);
|
||||
_diskProvider.MoveFolder(path, destination);
|
||||
_diskTransferService.TransferFolder(path, destination, TransferMode.Move);
|
||||
|
||||
_logger.Debug("Setting last accessed: {0}", path);
|
||||
_diskProvider.FolderSetLastWriteTime(destination, DateTime.UtcNow);
|
||||
@ -106,7 +111,7 @@ public void DeleteFile(string path)
|
||||
}
|
||||
|
||||
_logger.Debug("Moving '{0}' to '{1}'", path, destination);
|
||||
_diskProvider.MoveFile(path, destination, true);
|
||||
_diskTransferService.TransferFile(path, destination, TransferMode.Move);
|
||||
|
||||
//TODO: Better fix than this for non-Windows?
|
||||
if (OsInfo.IsWindows)
|
||||
|
@ -27,6 +27,7 @@ public class MetadataService : IHandle<MediaCoversUpdatedEvent>,
|
||||
private readonly ICleanMetadataService _cleanMetadataService;
|
||||
private readonly IMediaFileService _mediaFileService;
|
||||
private readonly IEpisodeService _episodeService;
|
||||
private readonly IDiskTransferService _diskTransferService;
|
||||
private readonly IDiskProvider _diskProvider;
|
||||
private readonly IHttpClient _httpClient;
|
||||
private readonly IMediaFileAttributeService _mediaFileAttributeService;
|
||||
@ -38,6 +39,7 @@ public MetadataService(IMetadataFactory metadataFactory,
|
||||
ICleanMetadataService cleanMetadataService,
|
||||
IMediaFileService mediaFileService,
|
||||
IEpisodeService episodeService,
|
||||
IDiskTransferService diskTransferService,
|
||||
IDiskProvider diskProvider,
|
||||
IHttpClient httpClient,
|
||||
IMediaFileAttributeService mediaFileAttributeService,
|
||||
@ -49,6 +51,7 @@ public MetadataService(IMetadataFactory metadataFactory,
|
||||
_cleanMetadataService = cleanMetadataService;
|
||||
_mediaFileService = mediaFileService;
|
||||
_episodeService = episodeService;
|
||||
_diskTransferService = diskTransferService;
|
||||
_diskProvider = diskProvider;
|
||||
_httpClient = httpClient;
|
||||
_mediaFileAttributeService = mediaFileAttributeService;
|
||||
@ -218,7 +221,7 @@ private MetadataFile ProcessEpisodeMetadata(IMetadata consumer, Series series, E
|
||||
var existingFullPath = Path.Combine(series.Path, existingMetadata.RelativePath);
|
||||
if (!fullPath.PathEquals(existingFullPath))
|
||||
{
|
||||
_diskProvider.MoveFile(existingFullPath, fullPath);
|
||||
_diskTransferService.TransferFile(existingFullPath, fullPath, TransferMode.Move);
|
||||
existingMetadata.RelativePath = episodeMetadata.RelativePath;
|
||||
}
|
||||
}
|
||||
@ -339,7 +342,7 @@ private List<MetadataFile> ProcessEpisodeImages(IMetadata consumer, Series serie
|
||||
var existingFullPath = Path.Combine(series.Path, existingMetadata.RelativePath);
|
||||
if (!fullPath.PathEquals(existingFullPath))
|
||||
{
|
||||
_diskProvider.MoveFile(fullPath, fullPath);
|
||||
_diskTransferService.TransferFile(existingFullPath, fullPath, TransferMode.Move);
|
||||
existingMetadata.RelativePath = image.RelativePath;
|
||||
|
||||
return new List<MetadataFile>{ existingMetadata };
|
||||
|
@ -16,19 +16,19 @@ public class MoveSeriesService : IExecute<MoveSeriesCommand>
|
||||
{
|
||||
private readonly ISeriesService _seriesService;
|
||||
private readonly IBuildFileNames _filenameBuilder;
|
||||
private readonly IDiskProvider _diskProvider;
|
||||
private readonly IDiskTransferService _diskTransferService;
|
||||
private readonly IEventAggregator _eventAggregator;
|
||||
private readonly Logger _logger;
|
||||
|
||||
public MoveSeriesService(ISeriesService seriesService,
|
||||
IBuildFileNames filenameBuilder,
|
||||
IDiskProvider diskProvider,
|
||||
IDiskTransferService diskTransferService,
|
||||
IEventAggregator eventAggregator,
|
||||
Logger logger)
|
||||
{
|
||||
_seriesService = seriesService;
|
||||
_filenameBuilder = filenameBuilder;
|
||||
_diskProvider = diskProvider;
|
||||
_diskTransferService = diskTransferService;
|
||||
_eventAggregator = eventAggregator;
|
||||
_logger = logger;
|
||||
}
|
||||
@ -50,7 +50,7 @@ public void Execute(MoveSeriesCommand message)
|
||||
//TODO: Move to transactional disk operations
|
||||
try
|
||||
{
|
||||
_diskProvider.MoveFolder(source, destination);
|
||||
_diskTransferService.TransferFolder(source, destination, TransferMode.Move);
|
||||
}
|
||||
catch (IOException ex)
|
||||
{
|
||||
|
@ -24,6 +24,7 @@ public class InstallUpdateService : IExecute<ApplicationUpdateCommand>
|
||||
private readonly IAppFolderInfo _appFolderInfo;
|
||||
|
||||
private readonly IDiskProvider _diskProvider;
|
||||
private readonly IDiskTransferService _diskTransferService;
|
||||
private readonly IHttpClient _httpClient;
|
||||
private readonly IArchiveService _archiveService;
|
||||
private readonly IProcessProvider _processProvider;
|
||||
@ -34,9 +35,13 @@ public class InstallUpdateService : IExecute<ApplicationUpdateCommand>
|
||||
private readonly IBackupService _backupService;
|
||||
|
||||
|
||||
public InstallUpdateService(ICheckUpdateService checkUpdateService, IAppFolderInfo appFolderInfo,
|
||||
IDiskProvider diskProvider, IHttpClient httpClient,
|
||||
IArchiveService archiveService, IProcessProvider processProvider,
|
||||
public InstallUpdateService(ICheckUpdateService checkUpdateService,
|
||||
IAppFolderInfo appFolderInfo,
|
||||
IDiskProvider diskProvider,
|
||||
IDiskTransferService diskTransferService,
|
||||
IHttpClient httpClient,
|
||||
IArchiveService archiveService,
|
||||
IProcessProvider processProvider,
|
||||
IVerifyUpdates updateVerifier,
|
||||
IStartupContext startupContext,
|
||||
IConfigFileProvider configFileProvider,
|
||||
@ -51,6 +56,7 @@ public InstallUpdateService(ICheckUpdateService checkUpdateService, IAppFolderIn
|
||||
_checkUpdateService = checkUpdateService;
|
||||
_appFolderInfo = appFolderInfo;
|
||||
_diskProvider = diskProvider;
|
||||
_diskTransferService = diskTransferService;
|
||||
_httpClient = httpClient;
|
||||
_archiveService = archiveService;
|
||||
_processProvider = processProvider;
|
||||
@ -113,8 +119,7 @@ private void InstallUpdate(UpdatePackage updatePackage)
|
||||
}
|
||||
|
||||
_logger.Info("Preparing client");
|
||||
_diskProvider.MoveFolder(_appFolderInfo.GetUpdateClientFolder(),
|
||||
updateSandboxFolder);
|
||||
_diskTransferService.TransferFolder(_appFolderInfo.GetUpdateClientFolder(), updateSandboxFolder, TransferMode.Move, false);
|
||||
|
||||
_logger.Info("Starting update client {0}", _appFolderInfo.GetUpdateClientExePath());
|
||||
_logger.ProgressInfo("Sonarr will restart shortly.");
|
||||
|
@ -13,13 +13,13 @@ public interface IBackupAndRestore
|
||||
|
||||
public class BackupAndRestore : IBackupAndRestore
|
||||
{
|
||||
private readonly IDiskProvider _diskProvider;
|
||||
private readonly IDiskTransferService _diskTransferService;
|
||||
private readonly IAppFolderInfo _appFolderInfo;
|
||||
private readonly Logger _logger;
|
||||
|
||||
public BackupAndRestore(IDiskProvider diskProvider, IAppFolderInfo appFolderInfo, Logger logger)
|
||||
public BackupAndRestore(IDiskTransferService diskTransferService, IAppFolderInfo appFolderInfo, Logger logger)
|
||||
{
|
||||
_diskProvider = diskProvider;
|
||||
_diskTransferService = diskTransferService;
|
||||
_appFolderInfo = appFolderInfo;
|
||||
_logger = logger;
|
||||
}
|
||||
@ -27,13 +27,13 @@ public BackupAndRestore(IDiskProvider diskProvider, IAppFolderInfo appFolderInfo
|
||||
public void Backup(string source)
|
||||
{
|
||||
_logger.Info("Creating backup of existing installation");
|
||||
_diskProvider.CopyFolder(source, _appFolderInfo.GetUpdateBackUpFolder());
|
||||
_diskTransferService.TransferFolder(source, _appFolderInfo.GetUpdateBackUpFolder(), TransferMode.Copy, false);
|
||||
}
|
||||
|
||||
public void Restore(string target)
|
||||
{
|
||||
_logger.Info("Attempting to rollback upgrade");
|
||||
_diskProvider.CopyFolder(_appFolderInfo.GetUpdateBackUpFolder(), target);
|
||||
_diskTransferService.TransferFolder(_appFolderInfo.GetUpdateBackUpFolder(), target, TransferMode.Copy, false);
|
||||
}
|
||||
}
|
||||
}
|
@ -14,13 +14,18 @@ public interface IBackupAppData
|
||||
public class BackupAppData : IBackupAppData
|
||||
{
|
||||
private readonly IAppFolderInfo _appFolderInfo;
|
||||
private readonly IDiskTransferService _diskTransferService;
|
||||
private readonly IDiskProvider _diskProvider;
|
||||
private readonly Logger _logger;
|
||||
|
||||
public BackupAppData(IAppFolderInfo appFolderInfo, IDiskProvider diskProvider, Logger logger)
|
||||
public BackupAppData(IAppFolderInfo appFolderInfo,
|
||||
IDiskProvider diskProvider,
|
||||
IDiskTransferService diskTransferService,
|
||||
Logger logger)
|
||||
{
|
||||
_appFolderInfo = appFolderInfo;
|
||||
_diskProvider = diskProvider;
|
||||
_diskTransferService = diskTransferService;
|
||||
_logger = logger;
|
||||
}
|
||||
|
||||
@ -33,9 +38,8 @@ public void Backup()
|
||||
|
||||
try
|
||||
{
|
||||
_diskProvider.CopyFile(_appFolderInfo.GetConfigPath(), _appFolderInfo.GetUpdateBackupConfigFile(), true);
|
||||
_diskProvider.CopyFile(_appFolderInfo.GetNzbDroneDatabase(), _appFolderInfo.GetUpdateBackupDatabase(),
|
||||
true);
|
||||
_diskTransferService.TransferFile(_appFolderInfo.GetConfigPath(), _appFolderInfo.GetUpdateBackupConfigFile(), TransferMode.Copy);
|
||||
_diskTransferService.TransferFile(_appFolderInfo.GetNzbDroneDatabase(), _appFolderInfo.GetUpdateBackupDatabase(), TransferMode.Copy);
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
|
@ -16,6 +16,7 @@ public interface IInstallUpdateService
|
||||
public class InstallUpdateService : IInstallUpdateService
|
||||
{
|
||||
private readonly IDiskProvider _diskProvider;
|
||||
private readonly IDiskTransferService _diskTransferService;
|
||||
private readonly IDetectApplicationType _detectApplicationType;
|
||||
private readonly ITerminateNzbDrone _terminateNzbDrone;
|
||||
private readonly IAppFolderInfo _appFolderInfo;
|
||||
@ -26,6 +27,7 @@ public class InstallUpdateService : IInstallUpdateService
|
||||
private readonly Logger _logger;
|
||||
|
||||
public InstallUpdateService(IDiskProvider diskProvider,
|
||||
IDiskTransferService diskTransferService,
|
||||
IDetectApplicationType detectApplicationType,
|
||||
ITerminateNzbDrone terminateNzbDrone,
|
||||
IAppFolderInfo appFolderInfo,
|
||||
@ -36,6 +38,7 @@ public InstallUpdateService(IDiskProvider diskProvider,
|
||||
Logger logger)
|
||||
{
|
||||
_diskProvider = diskProvider;
|
||||
_diskTransferService = diskTransferService;
|
||||
_detectApplicationType = detectApplicationType;
|
||||
_terminateNzbDrone = terminateNzbDrone;
|
||||
_appFolderInfo = appFolderInfo;
|
||||
@ -93,7 +96,7 @@ public void Start(string installationFolder, int processId)
|
||||
_diskProvider.EmptyFolder(installationFolder);
|
||||
|
||||
_logger.Info("Copying new files to target folder");
|
||||
_diskProvider.CopyFolder(_appFolderInfo.GetUpdatePackageFolder(), installationFolder);
|
||||
_diskTransferService.TransferFolder(_appFolderInfo.GetUpdatePackageFolder(), installationFolder, TransferMode.Copy, false);
|
||||
|
||||
// Set executable flag on Sonarr app
|
||||
if (OsInfo.IsOsx)
|
||||
|
Loading…
Reference in New Issue
Block a user