From 5effca92b8e09c4c1ccf11f2e028c7316225ad19 Mon Sep 17 00:00:00 2001 From: Taloth Saldono Date: Sat, 15 Nov 2014 01:42:22 +0100 Subject: [PATCH] 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. --- .../DiskTests/DiskProviderFixtureBase.cs | 195 ++------- .../DiskTests/DiskTransferServiceFixture.cs | 390 ++++++++++++++++++ .../NzbDrone.Common.Test.csproj | 1 + src/NzbDrone.Common/Disk/DiskProviderBase.cs | 103 +---- .../Disk/DiskTransferService.cs | 239 +++++++++++ src/NzbDrone.Common/Disk/IDiskProvider.cs | 4 - src/NzbDrone.Common/NzbDrone.Common.csproj | 1 + .../DeleteDirectoryFixture.cs | 5 +- .../DeleteFileFixture.cs | 4 +- .../TvTests/MoveSeriesServiceFixture.cs | 4 +- .../UpdateTests/UpdateServiceFixture.cs | 3 +- src/NzbDrone.Core/Backup/BackupService.cs | 13 +- .../MediaFiles/EpisodeFileMovingService.cs | 6 +- .../MediaFiles/RecycleBinProvider.cs | 11 +- src/NzbDrone.Core/Metadata/MetadataService.cs | 7 +- src/NzbDrone.Core/Tv/MoveSeriesService.cs | 8 +- .../Update/InstallUpdateService.cs | 15 +- .../UpdateEngine/BackupAndRestore.cs | 10 +- .../UpdateEngine/BackupAppData.cs | 12 +- .../UpdateEngine/InstallUpdateService.cs | 5 +- 20 files changed, 747 insertions(+), 289 deletions(-) create mode 100644 src/NzbDrone.Common.Test/DiskTests/DiskTransferServiceFixture.cs create mode 100644 src/NzbDrone.Common/Disk/DiskTransferService.cs diff --git a/src/NzbDrone.Common.Test/DiskTests/DiskProviderFixtureBase.cs b/src/NzbDrone.Common.Test/DiskTests/DiskProviderFixtureBase.cs index c4930d4d1..1d72536d8 100644 --- a/src/NzbDrone.Common.Test/DiskTests/DiskProviderFixtureBase.cs +++ b/src/NzbDrone.Common.Test/DiskTests/DiskProviderFixtureBase.cs @@ -10,22 +10,6 @@ namespace NzbDrone.Common.Test.DiskTests { public abstract class DiskProviderFixtureBase : TestBase 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(() => 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(() => DoHardLinkRename(FileShare.None)); - } - - [Test] - public void should_not_be_able_to_rename_open_hardlinks_with_fileshare_write() - { - Assert.Throws(() => 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(() => DoHardLinkRename(FileShare.None)); + } - CollectionAssert.AreEquivalent(sourceFiles, destFiles); + [Test] + public void should_not_be_able_to_rename_open_hardlinks_with_fileshare_write() + { + Assert.Throws(() => DoHardLinkRename(FileShare.Read)); } } } diff --git a/src/NzbDrone.Common.Test/DiskTests/DiskTransferServiceFixture.cs b/src/NzbDrone.Common.Test/DiskTests/DiskTransferServiceFixture.cs new file mode 100644 index 000000000..000fb3577 --- /dev/null +++ b/src/NzbDrone.Common.Test/DiskTests/DiskTransferServiceFixture.cs @@ -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 + { + 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(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(() => Subject.TransferFile(_sourcePath, _targetPath, TransferMode.HardLink)); + } + + [Test] + public void should_retry_if_partial_copy() + { + WithSuccessfulHardlink(_sourcePath, _backupPath); + + var retry = 0; + Mocker.GetMock() + .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() + .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(() => 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() + .Verify(v => v.TryCreateHardLink(_sourcePath, _backupPath), Times.Once()); + } + + [Test] + public void should_remove_source_after_move() + { + WithSuccessfulHardlink(_sourcePath, _backupPath); + + Mocker.GetMock() + .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() + .Setup(v => v.MoveFile(_backupPath, _tempTargetPath, false)) + .Throws(new IOException("Blackbox IO error")); + + Assert.Throws(() => Subject.TransferFile(_sourcePath, _targetPath, TransferMode.Move)); + + VerifyDeletedFile(_backupPath); + } + + [Test] + public void should_remove_partial_if_move_fails() + { + WithSuccessfulHardlink(_sourcePath, _backupPath); + + Mocker.GetMock() + .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() + .Verify(v => v.CopyFile(_sourcePath, _tempTargetPath, false), Times.Once()); + + Mocker.GetMock() + .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() + .Setup(v => v.CopyFile(It.IsAny(), It.IsAny(), false)) + .Throws(new IOException("Access denied")); + + Assert.Throws(() => 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(() => 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() + .Setup(v => v.FileExists(path)) + .Returns(exists); + + Mocker.GetMock() + .Setup(v => v.GetFileSize(path)) + .Returns(size); + } + + private void WithSuccessfulHardlink(string source, string target) + { + Mocker.GetMock() + .Setup(v => v.TryCreateHardLink(source, target)) + .Callback(() => WithExistingFile(target)) + .Returns(true); + } + + private void WithFailedHardlink() + { + Mocker.GetMock() + .Setup(v => v.TryCreateHardLink(It.IsAny(), It.IsAny())) + .Returns(false); + } + + private void WithEmulatedDiskProvider() + { + Mocker.GetMock() + .Setup(v => v.FileExists(It.IsAny())) + .Returns(false); + + Mocker.GetMock() + .Setup(v => v.CopyFile(It.IsAny(), It.IsAny(), false)) + .Callback((s, d, o) => + { + WithExistingFile(d); + }); + + Mocker.GetMock() + .Setup(v => v.MoveFile(It.IsAny(), It.IsAny(), false)) + .Callback((s, d, o) => + { + WithExistingFile(s, false); + WithExistingFile(d); + }); + + Mocker.GetMock() + .Setup(v => v.DeleteFile(It.IsAny())) + .Callback(v => + { + WithExistingFile(v, false); + }); + } + + private void WithRealDiskProvider() + { + Mocker.GetMock() + .Setup(v => v.FolderExists(It.IsAny())) + .Returns(v => Directory.Exists(v)); + + Mocker.GetMock() + .Setup(v => v.FileExists(It.IsAny())) + .Returns(v => File.Exists(v)); + + Mocker.GetMock() + .Setup(v => v.CreateFolder(It.IsAny())) + .Callback(v => Directory.CreateDirectory(v)); + + Mocker.GetMock() + .Setup(v => v.DeleteFolder(It.IsAny(), It.IsAny())) + .Callback((v,r) => Directory.Delete(v, r)); + + Mocker.GetMock() + .Setup(v => v.DeleteFile(It.IsAny())) + .Callback(v => File.Delete(v)); + + Mocker.GetMock() + .Setup(v => v.GetDirectoryInfos(It.IsAny())) + .Returns(v => new DirectoryInfo(v).GetDirectories().ToList()); + + Mocker.GetMock() + .Setup(v => v.GetFileInfos(It.IsAny())) + .Returns(v => new DirectoryInfo(v).GetFiles().ToList()); + + Mocker.GetMock() + .Setup(v => v.GetFileSize(It.IsAny())) + .Returns(v => new FileInfo(v).Length); + + Mocker.GetMock() + .Setup(v => v.TryCreateHardLink(It.IsAny(), It.IsAny())) + .Returns(false); + + Mocker.GetMock() + .Setup(v => v.CopyFile(It.IsAny(), It.IsAny(), It.IsAny())) + .Callback((s, d, o) => File.Copy(s, d, o)); + + Mocker.GetMock() + .Setup(v => v.MoveFile(It.IsAny(), It.IsAny(), It.IsAny())) + .Callback((s,d,o) => { + if (File.Exists(d) && o) File.Delete(d); + File.Move(s, d); + }); + + } + + 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() + .Verify(v => v.DeleteFile(path), Times.Once()); + } + } +} diff --git a/src/NzbDrone.Common.Test/NzbDrone.Common.Test.csproj b/src/NzbDrone.Common.Test/NzbDrone.Common.Test.csproj index 4ef24b6e2..dc0c26e97 100644 --- a/src/NzbDrone.Common.Test/NzbDrone.Common.Test.csproj +++ b/src/NzbDrone.Common.Test/NzbDrone.Common.Test.csproj @@ -72,6 +72,7 @@ + diff --git a/src/NzbDrone.Common/Disk/DiskProviderBase.cs b/src/NzbDrone.Common/Disk/DiskProviderBase.cs index 20855d6b3..61d023b3f 100644 --- a/src/NzbDrone.Common/Disk/DiskProviderBase.cs +++ b/src/NzbDrone.Common/Disk/DiskProviderBase.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); diff --git a/src/NzbDrone.Common/Disk/DiskTransferService.cs b/src/NzbDrone.Common/Disk/DiskTransferService.cs new file mode 100644 index 000000000..52da91460 --- /dev/null +++ b/src/NzbDrone.Common/Disk/DiskTransferService.cs @@ -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; + } + } +} diff --git a/src/NzbDrone.Common/Disk/IDiskProvider.cs b/src/NzbDrone.Common/Disk/IDiskProvider.cs index 7374080e5..ad4cba263 100644 --- a/src/NzbDrone.Common/Disk/IDiskProvider.cs +++ b/src/NzbDrone.Common/Disk/IDiskProvider.cs @@ -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); diff --git a/src/NzbDrone.Common/NzbDrone.Common.csproj b/src/NzbDrone.Common/NzbDrone.Common.csproj index 2dc9bb8e0..e430f4f34 100644 --- a/src/NzbDrone.Common/NzbDrone.Common.csproj +++ b/src/NzbDrone.Common/NzbDrone.Common.csproj @@ -79,6 +79,7 @@ + diff --git a/src/NzbDrone.Core.Test/ProviderTests/RecycleBinProviderTests/DeleteDirectoryFixture.cs b/src/NzbDrone.Core.Test/ProviderTests/RecycleBinProviderTests/DeleteDirectoryFixture.cs index 3eff2891a..35130a6ea 100644 --- a/src/NzbDrone.Core.Test/ProviderTests/RecycleBinProviderTests/DeleteDirectoryFixture.cs +++ b/src/NzbDrone.Core.Test/ProviderTests/RecycleBinProviderTests/DeleteDirectoryFixture.cs @@ -45,7 +45,8 @@ public void should_use_move_when_recycleBin_is_configured() Mocker.Resolve().DeleteFolder(path); - Mocker.GetMock().Verify(v => v.MoveFolder(path, @"C:\Test\Recycle Bin\30 Rock".AsOsAgnostic()), Times.Once()); + Mocker.GetMock() + .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().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().DeleteFolder(path); diff --git a/src/NzbDrone.Core.Test/ProviderTests/RecycleBinProviderTests/DeleteFileFixture.cs b/src/NzbDrone.Core.Test/ProviderTests/RecycleBinProviderTests/DeleteFileFixture.cs index c20b3c772..47cd20876 100644 --- a/src/NzbDrone.Core.Test/ProviderTests/RecycleBinProviderTests/DeleteFileFixture.cs +++ b/src/NzbDrone.Core.Test/ProviderTests/RecycleBinProviderTests/DeleteFileFixture.cs @@ -44,7 +44,7 @@ public void should_use_move_when_recycleBin_is_configured() Mocker.Resolve().DeleteFile(path); - Mocker.GetMock().Verify(v => v.MoveFile(path, @"C:\Test\Recycle Bin\S01E01.avi".AsOsAgnostic(), true), Times.Once()); + Mocker.GetMock().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().DeleteFile(path); - Mocker.GetMock().Verify(v => v.MoveFile(path, @"C:\Test\Recycle Bin\S01E01_2.avi".AsOsAgnostic(), true), Times.Once()); + Mocker.GetMock().Verify(v => v.TransferFile(path, @"C:\Test\Recycle Bin\S01E01_2.avi".AsOsAgnostic(), TransferMode.Move, false, true), Times.Once()); } [Test] diff --git a/src/NzbDrone.Core.Test/TvTests/MoveSeriesServiceFixture.cs b/src/NzbDrone.Core.Test/TvTests/MoveSeriesServiceFixture.cs index 0470c0b16..795bcadb6 100644 --- a/src/NzbDrone.Core.Test/TvTests/MoveSeriesServiceFixture.cs +++ b/src/NzbDrone.Core.Test/TvTests/MoveSeriesServiceFixture.cs @@ -39,8 +39,8 @@ public void Setup() private void GivenFailedMove() { - Mocker.GetMock() - .Setup(s => s.MoveFolder(It.IsAny(), It.IsAny())) + Mocker.GetMock() + .Setup(s => s.TransferFolder(It.IsAny(), It.IsAny(), TransferMode.Move, true)) .Throws(); } diff --git a/src/NzbDrone.Core.Test/UpdateTests/UpdateServiceFixture.cs b/src/NzbDrone.Core.Test/UpdateTests/UpdateServiceFixture.cs index 888a41b96..404837749 100644 --- a/src/NzbDrone.Core.Test/UpdateTests/UpdateServiceFixture.cs +++ b/src/NzbDrone.Core.Test/UpdateTests/UpdateServiceFixture.cs @@ -135,7 +135,8 @@ public void Should_copy_update_client_to_root_of_sandbox() Subject.Execute(new ApplicationUpdateCommand()); - Mocker.GetMock().Verify(c => c.MoveFolder(updateClientFolder, _sandboxFolder)); + Mocker.GetMock() + .Verify(c => c.TransferFolder(updateClientFolder, _sandboxFolder, TransferMode.Move, false)); } [Test] diff --git a/src/NzbDrone.Core/Backup/BackupService.cs b/src/NzbDrone.Core/Backup/BackupService.cs index 19db79527..c87c49cc5 100644 --- a/src/NzbDrone.Core/Backup/BackupService.cs +++ b/src/NzbDrone.Core/Backup/BackupService.cs @@ -25,6 +25,7 @@ public interface IBackupService public class BackupService : IBackupService, IExecute { 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 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) diff --git a/src/NzbDrone.Core/MediaFiles/EpisodeFileMovingService.cs b/src/NzbDrone.Core/MediaFiles/EpisodeFileMovingService.cs index d7760a7a9..27edea943 100644 --- a/src/NzbDrone.Core/MediaFiles/EpisodeFileMovingService.cs +++ b/src/NzbDrone.Core/MediaFiles/EpisodeFileMovingService.cs @@ -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 [{2}]", mode, episodeFilePath, destinationFilename); - _diskProvider.TransferFile(episodeFilePath, destinationFilename, mode); + _diskTransferService.TransferFile(episodeFilePath, destinationFilename, mode); episodeFile.RelativePath = series.Path.GetRelativePath(destinationFilename); diff --git a/src/NzbDrone.Core/MediaFiles/RecycleBinProvider.cs b/src/NzbDrone.Core/MediaFiles/RecycleBinProvider.cs index 1dd5e5dff..d4c814bbe 100644 --- a/src/NzbDrone.Core/MediaFiles/RecycleBinProvider.cs +++ b/src/NzbDrone.Core/MediaFiles/RecycleBinProvider.cs @@ -22,13 +22,18 @@ public interface IRecycleBinProvider public class RecycleBinProvider : IHandleAsync, IExecute, 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) diff --git a/src/NzbDrone.Core/Metadata/MetadataService.cs b/src/NzbDrone.Core/Metadata/MetadataService.cs index cf1e1f325..15847acd6 100644 --- a/src/NzbDrone.Core/Metadata/MetadataService.cs +++ b/src/NzbDrone.Core/Metadata/MetadataService.cs @@ -27,6 +27,7 @@ public class MetadataService : IHandle, 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 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{ existingMetadata }; diff --git a/src/NzbDrone.Core/Tv/MoveSeriesService.cs b/src/NzbDrone.Core/Tv/MoveSeriesService.cs index 2713d6ddd..57726157b 100644 --- a/src/NzbDrone.Core/Tv/MoveSeriesService.cs +++ b/src/NzbDrone.Core/Tv/MoveSeriesService.cs @@ -16,19 +16,19 @@ public class MoveSeriesService : IExecute { 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) { diff --git a/src/NzbDrone.Core/Update/InstallUpdateService.cs b/src/NzbDrone.Core/Update/InstallUpdateService.cs index 690e8bc21..376565ad0 100644 --- a/src/NzbDrone.Core/Update/InstallUpdateService.cs +++ b/src/NzbDrone.Core/Update/InstallUpdateService.cs @@ -24,6 +24,7 @@ public class InstallUpdateService : IExecute 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 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."); diff --git a/src/NzbDrone.Update/UpdateEngine/BackupAndRestore.cs b/src/NzbDrone.Update/UpdateEngine/BackupAndRestore.cs index 37142b2ad..6e800e0be 100644 --- a/src/NzbDrone.Update/UpdateEngine/BackupAndRestore.cs +++ b/src/NzbDrone.Update/UpdateEngine/BackupAndRestore.cs @@ -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); } } } \ No newline at end of file diff --git a/src/NzbDrone.Update/UpdateEngine/BackupAppData.cs b/src/NzbDrone.Update/UpdateEngine/BackupAppData.cs index 29cd8b1af..6732907a7 100644 --- a/src/NzbDrone.Update/UpdateEngine/BackupAppData.cs +++ b/src/NzbDrone.Update/UpdateEngine/BackupAppData.cs @@ -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) { diff --git a/src/NzbDrone.Update/UpdateEngine/InstallUpdateService.cs b/src/NzbDrone.Update/UpdateEngine/InstallUpdateService.cs index cbad5d493..ef511227f 100644 --- a/src/NzbDrone.Update/UpdateEngine/InstallUpdateService.cs +++ b/src/NzbDrone.Update/UpdateEngine/InstallUpdateService.cs @@ -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)