mirror of
https://github.com/Radarr/Radarr.git
synced 2024-09-11 12:02:35 +02:00
New: Now checks the file size of moved episodes to verify if the transfer was completed successfully to be able to detect errors with mounted network storage.
This commit is contained in:
parent
aa4fca7177
commit
5effca92b8
@ -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));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
390
src/NzbDrone.Common.Test/DiskTests/DiskTransferServiceFixture.cs
Normal file
390
src/NzbDrone.Common.Test/DiskTests/DiskTransferServiceFixture.cs
Normal file
@ -0,0 +1,390 @@
|
||||
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_retry_if_partial_copy()
|
||||
{
|
||||
WithSuccessfulHardlink(_sourcePath, _backupPath);
|
||||
|
||||
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()
|
||||
{
|
||||
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()
|
||||
{
|
||||
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()
|
||||
{
|
||||
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()
|
||||
{
|
||||
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()
|
||||
{
|
||||
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()
|
||||
{
|
||||
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);
|
||||
|
239
src/NzbDrone.Common/Disk/DiskTransferService.cs
Normal file
239
src/NzbDrone.Common/Disk/DiskTransferService.cs
Normal file
@ -0,0 +1,239 @@
|
||||
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.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 (!_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();
|
||||
|
||||
_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