Merge pull request #139 from Sonarr/verified-file-transfer

Verified file transfer
pull/4/head
Taloth 10 years ago
commit 51f705d89a

@ -10,22 +10,6 @@ namespace NzbDrone.Common.Test.DiskTests
{ {
public abstract class DiskProviderFixtureBase<TSubject> : TestBase<TSubject> where TSubject : class, IDiskProvider 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] [Test]
public void directory_exist_should_be_able_to_find_existing_folder() public void directory_exist_should_be_able_to_find_existing_folder()
{ {
@ -101,65 +85,9 @@ namespace NzbDrone.Common.Test.DiskTests
File.WriteAllText(source, "SourceFile1"); File.WriteAllText(source, "SourceFile1");
Subject.MoveFile(source, source, true); Assert.Throws<IOException>(() => Subject.MoveFile(source, source, true));
File.Exists(source).Should().BeTrue(); 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] [Test]
@ -194,72 +122,6 @@ namespace NzbDrone.Common.Test.DiskTests
Directory.Exists(sourceDir).Should().BeFalse(); 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] [Test]
public void empty_folder_should_return_folder_modified_date() public void empty_folder_should_return_folder_modified_date()
{ {
@ -338,14 +200,6 @@ namespace NzbDrone.Common.Test.DiskTests
Subject.FileGetLastWrite(testFile).Should().Be(lastWriteTime); Subject.FileGetLastWrite(testFile).Should().Be(lastWriteTime);
} }
[Test]
[Explicit]
public void check_last_write()
{
Console.WriteLine(Subject.FolderGetLastWrite(GetFilledTempFolder().FullName));
Console.WriteLine(GetFilledTempFolder().LastWriteTimeUtc);
}
[Test] [Test]
public void GetParentFolder_should_remove_trailing_slash_before_getting_parent_folder() public void GetParentFolder_should_remove_trailing_slash_before_getting_parent_folder()
{ {
@ -355,22 +209,51 @@ namespace NzbDrone.Common.Test.DiskTests
Subject.GetParentFolder(path).Should().Be(parent); 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 sourceDir = GetTempFilePath();
var destFiles = Directory.GetFileSystemEntries(destination, "*", SearchOption.AllDirectories).Select(v => v.Substring(destination.Length + 1)).ToArray(); var source = Path.Combine(sourceDir, "test.txt");
var destination = Path.Combine(sourceDir, "destination.txt");
var rename = Path.Combine(sourceDir, "rename.txt");
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();
CollectionAssert.AreEquivalent(sourceFiles, destFiles); 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(); [Test]
var destFiles = Directory.GetFileSystemEntries(destination, "*", SearchOption.AllDirectories).Select(v => v.Substring(destination.Length + 1)).ToArray(); 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));
} }
} }
} }

@ -0,0 +1,416 @@
using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Text;
using Moq;
using NUnit.Framework;
using NzbDrone.Common.Disk;
using NzbDrone.Test.Common;
using FluentAssertions;
namespace NzbDrone.Common.Test.DiskTests
{
[TestFixture]
public class DiskTransferServiceFixture : TestBase<DiskTransferService>
{
private readonly String _sourcePath = @"C:\source\my.video.mkv".AsOsAgnostic();
private readonly String _targetPath = @"C:\target\my.video.mkv".AsOsAgnostic();
private readonly String _backupPath = @"C:\source\my.video.mkv.backup~".AsOsAgnostic();
private readonly String _tempTargetPath = @"C:\target\my.video.mkv.partial~".AsOsAgnostic();
[SetUp]
public void SetUp()
{
Mocker.GetMock<IDiskProvider>(MockBehavior.Strict);
WithEmulatedDiskProvider();
WithExistingFile(_sourcePath);
}
[Test]
public void should_hardlink_only()
{
WithSuccessfulHardlink(_sourcePath, _targetPath);
var result = Subject.TransferFile(_sourcePath, _targetPath, TransferMode.HardLink);
result.Should().Be(TransferMode.HardLink);
}
[Test]
public void should_throw_if_hardlink_only_failed()
{
WithFailedHardlink();
Assert.Throws<IOException>(() => Subject.TransferFile(_sourcePath, _targetPath, TransferMode.HardLink));
}
[Test]
public void should_not_use_verified_transfer_on_windows()
{
WindowsOnly();
var result = Subject.TransferFile(_sourcePath, _targetPath, TransferMode.Move);
Mocker.GetMock<IDiskProvider>()
.Verify(v => v.TryCreateHardLink(_sourcePath, _backupPath), Times.Never());
Mocker.GetMock<IDiskProvider>()
.Verify(v => v.MoveFile(_sourcePath, _targetPath, false), Times.Once());
}
[Test]
public void should_retry_if_partial_copy()
{
MonoOnly();
var retry = 0;
Mocker.GetMock<IDiskProvider>()
.Setup(v => v.CopyFile(_sourcePath, _tempTargetPath, false))
.Callback(() =>
{
WithExistingFile(_tempTargetPath, true, 900);
if (retry++ == 1) WithExistingFile(_tempTargetPath, true, 1000);
});
var result = Subject.TransferFile(_sourcePath, _targetPath, TransferMode.Copy);
ExceptionVerification.ExpectedWarns(1);
}
[Test]
public void should_retry_twice_if_partial_copy()
{
MonoOnly();
var retry = 0;
Mocker.GetMock<IDiskProvider>()
.Setup(v => v.CopyFile(_sourcePath, _tempTargetPath, false))
.Callback(() =>
{
WithExistingFile(_tempTargetPath, true, 900);
if (retry++ == 3) throw new Exception("Test Failed, retried too many times.");
});
Assert.Throws<IOException>(() => Subject.TransferFile(_sourcePath, _targetPath, TransferMode.Copy));
ExceptionVerification.ExpectedWarns(2);
ExceptionVerification.ExpectedErrors(1);
}
[Test]
public void should_hardlink_before_move()
{
MonoOnly();
WithSuccessfulHardlink(_sourcePath, _backupPath);
var result = Subject.TransferFile(_sourcePath, _targetPath, TransferMode.Move);
Mocker.GetMock<IDiskProvider>()
.Verify(v => v.TryCreateHardLink(_sourcePath, _backupPath), Times.Once());
}
[Test]
public void should_remove_source_after_move()
{
MonoOnly();
WithSuccessfulHardlink(_sourcePath, _backupPath);
Mocker.GetMock<IDiskProvider>()
.Setup(v => v.MoveFile(_backupPath, _tempTargetPath, false))
.Callback(() => WithExistingFile(_tempTargetPath, true));
var result = Subject.TransferFile(_sourcePath, _targetPath, TransferMode.Move);
VerifyDeletedFile(_sourcePath);
}
[Test]
public void should_remove_backup_if_move_throws()
{
MonoOnly();
WithSuccessfulHardlink(_sourcePath, _backupPath);
Mocker.GetMock<IDiskProvider>()
.Setup(v => v.MoveFile(_backupPath, _tempTargetPath, false))
.Throws(new IOException("Blackbox IO error"));
Assert.Throws<IOException>(() => Subject.TransferFile(_sourcePath, _targetPath, TransferMode.Move));
VerifyDeletedFile(_backupPath);
}
[Test]
public void should_remove_partial_if_move_fails()
{
MonoOnly();
WithSuccessfulHardlink(_sourcePath, _backupPath);
Mocker.GetMock<IDiskProvider>()
.Setup(v => v.MoveFile(_backupPath, _tempTargetPath, false))
.Callback(() =>
{
WithExistingFile(_backupPath, false);
WithExistingFile(_tempTargetPath, true, 900);
});
Subject.TransferFile(_sourcePath, _targetPath, TransferMode.Move);
VerifyDeletedFile(_tempTargetPath);
}
[Test]
public void should_fallback_to_copy_if_hardlink_failed()
{
MonoOnly();
WithFailedHardlink();
var result = Subject.TransferFile(_sourcePath, _targetPath, TransferMode.Move);
Mocker.GetMock<IDiskProvider>()
.Verify(v => v.CopyFile(_sourcePath, _tempTargetPath, false), Times.Once());
Mocker.GetMock<IDiskProvider>()
.Verify(v => v.MoveFile(_tempTargetPath, _targetPath, false), Times.Once());
VerifyDeletedFile(_sourcePath);
}
[Test]
public void CopyFolder_should_copy_folder()
{
WithRealDiskProvider();
var source = GetFilledTempFolder();
var destination = new DirectoryInfo(GetTempFilePath());
Subject.TransferFolder(source.FullName, destination.FullName, TransferMode.Copy);
VerifyCopyFolder(source.FullName, destination.FullName);
}
[Test]
public void CopyFolder_should_overwrite_existing_folder()
{
WithRealDiskProvider();
var source = GetFilledTempFolder();
var destination = new DirectoryInfo(GetTempFilePath());
Subject.TransferFolder(source.FullName, destination.FullName, TransferMode.Copy);
//Delete Random File
destination.GetFiles("*.*", SearchOption.AllDirectories).First().Delete();
Subject.TransferFolder(source.FullName, destination.FullName, TransferMode.Copy);
VerifyCopyFolder(source.FullName, destination.FullName);
}
[Test]
public void MoveFolder_should_move_folder()
{
WithRealDiskProvider();
var original = GetFilledTempFolder();
var source = new DirectoryInfo(GetTempFilePath());
var destination = new DirectoryInfo(GetTempFilePath());
Subject.TransferFolder(original.FullName, source.FullName, TransferMode.Copy);
Subject.TransferFolder(source.FullName, destination.FullName, TransferMode.Move);
VerifyMoveFolder(original.FullName, source.FullName, destination.FullName);
}
[Test]
public void MoveFolder_should_overwrite_existing_folder()
{
WithRealDiskProvider();
var original = GetFilledTempFolder();
var source = new DirectoryInfo(GetTempFilePath());
var destination = new DirectoryInfo(GetTempFilePath());
Subject.TransferFolder(original.FullName, source.FullName, TransferMode.Copy);
Subject.TransferFolder(original.FullName, destination.FullName, TransferMode.Copy);
Subject.TransferFolder(source.FullName, destination.FullName, TransferMode.Move);
VerifyMoveFolder(original.FullName, source.FullName, destination.FullName);
}
[Test]
public void should_throw_if_destination_is_readonly()
{
Mocker.GetMock<IDiskProvider>()
.Setup(v => v.CopyFile(It.IsAny<string>(), It.IsAny<string>(), false))
.Throws(new IOException("Access denied"));
Assert.Throws<IOException>(() => Subject.TransferFile(_sourcePath, _targetPath, TransferMode.Copy));
}
[Test]
public void should_throw_if_destination_is_child_of_source()
{
var childPath = Path.Combine(_sourcePath, "child");
Assert.Throws<IOException>(() => Subject.TransferFile(_sourcePath, childPath, TransferMode.Move));
}
public DirectoryInfo GetFilledTempFolder()
{
var tempFolder = GetTempFilePath();
Directory.CreateDirectory(tempFolder);
File.WriteAllText(Path.Combine(tempFolder, Path.GetRandomFileName()), "RootFile");
var subDir = Path.Combine(tempFolder, Path.GetRandomFileName());
Directory.CreateDirectory(subDir);
File.WriteAllText(Path.Combine(subDir, Path.GetRandomFileName()), "SubFile1");
File.WriteAllText(Path.Combine(subDir, Path.GetRandomFileName()), "SubFile2");
return new DirectoryInfo(tempFolder);
}
private void WithExistingFile(string path, bool exists = true, int size = 1000)
{
Mocker.GetMock<IDiskProvider>()
.Setup(v => v.FileExists(path))
.Returns(exists);
Mocker.GetMock<IDiskProvider>()
.Setup(v => v.GetFileSize(path))
.Returns(size);
}
private void WithSuccessfulHardlink(string source, string target)
{
Mocker.GetMock<IDiskProvider>()
.Setup(v => v.TryCreateHardLink(source, target))
.Callback(() => WithExistingFile(target))
.Returns(true);
}
private void WithFailedHardlink()
{
Mocker.GetMock<IDiskProvider>()
.Setup(v => v.TryCreateHardLink(It.IsAny<String>(), It.IsAny<String>()))
.Returns(false);
}
private void WithEmulatedDiskProvider()
{
Mocker.GetMock<IDiskProvider>()
.Setup(v => v.FileExists(It.IsAny<string>()))
.Returns(false);
Mocker.GetMock<IDiskProvider>()
.Setup(v => v.CopyFile(It.IsAny<string>(), It.IsAny<string>(), false))
.Callback<string, string, bool>((s, d, o) =>
{
WithExistingFile(d);
});
Mocker.GetMock<IDiskProvider>()
.Setup(v => v.MoveFile(It.IsAny<string>(), It.IsAny<string>(), false))
.Callback<string, string, bool>((s, d, o) =>
{
WithExistingFile(s, false);
WithExistingFile(d);
});
Mocker.GetMock<IDiskProvider>()
.Setup(v => v.DeleteFile(It.IsAny<string>()))
.Callback<string>(v =>
{
WithExistingFile(v, false);
});
}
private void WithRealDiskProvider()
{
Mocker.GetMock<IDiskProvider>()
.Setup(v => v.FolderExists(It.IsAny<string>()))
.Returns<string>(v => Directory.Exists(v));
Mocker.GetMock<IDiskProvider>()
.Setup(v => v.FileExists(It.IsAny<string>()))
.Returns<string>(v => File.Exists(v));
Mocker.GetMock<IDiskProvider>()
.Setup(v => v.CreateFolder(It.IsAny<string>()))
.Callback<string>(v => Directory.CreateDirectory(v));
Mocker.GetMock<IDiskProvider>()
.Setup(v => v.DeleteFolder(It.IsAny<string>(), It.IsAny<bool>()))
.Callback<string, bool>((v,r) => Directory.Delete(v, r));
Mocker.GetMock<IDiskProvider>()
.Setup(v => v.DeleteFile(It.IsAny<string>()))
.Callback<string>(v => File.Delete(v));
Mocker.GetMock<IDiskProvider>()
.Setup(v => v.GetDirectoryInfos(It.IsAny<string>()))
.Returns<string>(v => new DirectoryInfo(v).GetDirectories().ToList());
Mocker.GetMock<IDiskProvider>()
.Setup(v => v.GetFileInfos(It.IsAny<string>()))
.Returns<string>(v => new DirectoryInfo(v).GetFiles().ToList());
Mocker.GetMock<IDiskProvider>()
.Setup(v => v.GetFileSize(It.IsAny<string>()))
.Returns<string>(v => new FileInfo(v).Length);
Mocker.GetMock<IDiskProvider>()
.Setup(v => v.TryCreateHardLink(It.IsAny<string>(), It.IsAny<string>()))
.Returns(false);
Mocker.GetMock<IDiskProvider>()
.Setup(v => v.CopyFile(It.IsAny<string>(), It.IsAny<string>(), It.IsAny<bool>()))
.Callback<string, string, bool>((s, d, o) => File.Copy(s, d, o));
Mocker.GetMock<IDiskProvider>()
.Setup(v => v.MoveFile(It.IsAny<string>(), It.IsAny<string>(), It.IsAny<bool>()))
.Callback<string, string, bool>((s,d,o) => {
if (File.Exists(d) && o) File.Delete(d);
File.Move(s, d);
});
}
private void VerifyCopyFolder(string source, string destination)
{
var sourceFiles = Directory.GetFileSystemEntries(source, "*", SearchOption.AllDirectories).Select(v => v.Substring(source.Length + 1)).ToArray();
var destFiles = Directory.GetFileSystemEntries(destination, "*", SearchOption.AllDirectories).Select(v => v.Substring(destination.Length + 1)).ToArray();
CollectionAssert.AreEquivalent(sourceFiles, destFiles);
}
private void VerifyMoveFolder(string source, string from, string destination)
{
Directory.Exists(from).Should().BeFalse();
var sourceFiles = Directory.GetFileSystemEntries(source, "*", SearchOption.AllDirectories).Select(v => v.Substring(source.Length + 1)).ToArray();
var destFiles = Directory.GetFileSystemEntries(destination, "*", SearchOption.AllDirectories).Select(v => v.Substring(destination.Length + 1)).ToArray();
CollectionAssert.AreEquivalent(sourceFiles, destFiles);
}
private void VerifyDeletedFile(String filePath)
{
var path = filePath;
Mocker.GetMock<IDiskProvider>()
.Verify(v => v.DeleteFile(path), Times.Once());
}
}
}

@ -72,6 +72,7 @@
<Compile Include="DiskTests\DiskProviderFixtureBase.cs" /> <Compile Include="DiskTests\DiskProviderFixtureBase.cs" />
<Compile Include="DiskTests\FreeSpaceFixtureBase.cs" /> <Compile Include="DiskTests\FreeSpaceFixtureBase.cs" />
<Compile Include="DiskTests\IsParentFixtureBase.cs" /> <Compile Include="DiskTests\IsParentFixtureBase.cs" />
<Compile Include="DiskTests\DiskTransferServiceFixture.cs" />
<Compile Include="EnsureTest\PathExtensionFixture.cs" /> <Compile Include="EnsureTest\PathExtensionFixture.cs" />
<Compile Include="EnvironmentProviderTest.cs" /> <Compile Include="EnvironmentProviderTest.cs" />
<Compile Include="EnvironmentTests\EnvironmentProviderTest.cs" /> <Compile Include="EnvironmentTests\EnvironmentProviderTest.cs" />

@ -168,62 +168,6 @@ namespace NzbDrone.Common.Disk
Directory.CreateDirectory(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) public void DeleteFile(string path)
{ {
Ensure.That(path, () => path).IsValidPath(); Ensure.That(path, () => path).IsValidPath();
@ -236,23 +180,25 @@ namespace NzbDrone.Common.Disk
public void CopyFile(string source, string destination, bool overwrite = false) 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();
public void MoveFile(string source, string destination, bool overwrite = false) if (source.PathEquals(destination))
{ {
TransferFile(source, destination, TransferMode.Move, overwrite); throw new IOException(string.Format("Source and destination can't be the same {0}", source));
}
File.Copy(source, destination, overwrite);
} }
public TransferMode TransferFile(string source, string destination, TransferMode mode, bool overwrite) public void MoveFile(string source, string destination, bool overwrite = false)
{ {
Ensure.That(source, () => source).IsValidPath(); Ensure.That(source, () => source).IsValidPath();
Ensure.That(destination, () => destination).IsValidPath(); Ensure.That(destination, () => destination).IsValidPath();
if (source.PathEquals(destination)) if (source.PathEquals(destination))
{ {
Logger.Warn("Source and destination can't be the same {0}", source); throw new IOException(string.Format("Source and destination can't be the same {0}", source));
return TransferMode.None;
} }
if (FileExists(destination) && overwrite) if (FileExists(destination) && overwrite)
@ -260,33 +206,8 @@ namespace NzbDrone.Common.Disk
DeleteFile(destination); DeleteFile(destination);
} }
if (mode.HasFlag(TransferMode.HardLink)) RemoveReadOnly(source);
{ File.Move(source, destination);
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;
} }
public abstract bool TryCreateHardLink(string source, string destination); public abstract bool TryCreateHardLink(string source, string destination);

@ -0,0 +1,254 @@
using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Text;
using System.Threading;
using NLog;
using NzbDrone.Common.EnsureThat;
using NzbDrone.Common.EnvironmentInfo;
using NzbDrone.Common.Extensions;
namespace NzbDrone.Common.Disk
{
public interface IDiskTransferService
{
TransferMode TransferFolder(String sourcePath, String targetPath, TransferMode mode, bool verified = true);
TransferMode TransferFile(String sourcePath, String targetPath, TransferMode mode, bool overwrite = false, bool verified = true);
}
public class DiskTransferService : IDiskTransferService
{
private const Int32 RetryCount = 2;
private readonly IDiskProvider _diskProvider;
private readonly Logger _logger;
public DiskTransferService(IDiskProvider diskProvider, Logger logger)
{
_diskProvider = diskProvider;
_logger = logger;
}
public TransferMode TransferFolder(String sourcePath, String targetPath, TransferMode mode, bool verified = true)
{
Ensure.That(sourcePath, () => sourcePath).IsValidPath();
Ensure.That(targetPath, () => targetPath).IsValidPath();
if (OsInfo.IsWindows)
{
// TODO: Atm we haven't seen partial transfers on windows so we disable verified transfer.
// (If enabled in the future, be sure to check specifically for ReFS, which doesn't support hardlinks.)
verified = false;
}
if (!_diskProvider.FolderExists(targetPath))
{
_diskProvider.CreateFolder(targetPath);
}
var result = mode;
foreach (var subDir in _diskProvider.GetDirectoryInfos(sourcePath))
{
result &= TransferFolder(subDir.FullName, Path.Combine(targetPath, subDir.Name), mode, verified);
}
foreach (var sourceFile in _diskProvider.GetFileInfos(sourcePath))
{
var destFile = Path.Combine(targetPath, sourceFile.Name);
result &= TransferFile(sourceFile.FullName, destFile, mode, true, verified);
}
if (mode.HasFlag(TransferMode.Move))
{
_diskProvider.DeleteFolder(sourcePath, true);
}
return result;
}
public TransferMode TransferFile(String sourcePath, String targetPath, TransferMode mode, bool overwrite = false, bool verified = true)
{
Ensure.That(sourcePath, () => sourcePath).IsValidPath();
Ensure.That(targetPath, () => targetPath).IsValidPath();
if (OsInfo.IsWindows)
{
// TODO: Atm we haven't seen partial transfers on windows so we disable verified transfer.
// (If enabled in the future, be sure to check specifically for ReFS, which doesn't support hardlinks.)
verified = false;
}
_logger.Debug("{0} [{1}] > [{2}]", mode, sourcePath, targetPath);
if (sourcePath.PathEquals(targetPath))
{
throw new IOException(string.Format("Source and destination can't be the same {0}", sourcePath));
}
if (sourcePath.IsParentPath(targetPath))
{
throw new IOException(string.Format("Destination cannot be a child of the source [{0}] => [{1}]", sourcePath, targetPath));
}
if (_diskProvider.FileExists(targetPath) && overwrite)
{
_diskProvider.DeleteFile(targetPath);
}
if (mode.HasFlag(TransferMode.HardLink))
{
var createdHardlink = _diskProvider.TryCreateHardLink(sourcePath, targetPath);
if (createdHardlink)
{
return TransferMode.HardLink;
}
if (!mode.HasFlag(TransferMode.Copy))
{
throw new IOException("Hardlinking from '" + sourcePath + "' to '" + targetPath + "' failed.");
}
}
if (verified)
{
if (mode.HasFlag(TransferMode.Copy))
{
if (TryCopyFile(sourcePath, targetPath))
{
return TransferMode.Copy;
}
}
if (mode.HasFlag(TransferMode.Move))
{
if (TryMoveFile(sourcePath, targetPath))
{
return TransferMode.Move;
}
}
throw new IOException(String.Format("Failed to completely transfer [{0}] to [{1}], aborting.", sourcePath, targetPath));
}
else
{
if (mode.HasFlag(TransferMode.Copy))
{
_diskProvider.CopyFile(sourcePath, targetPath);
return TransferMode.Copy;
}
if (mode.HasFlag(TransferMode.Move))
{
_diskProvider.MoveFile(sourcePath, targetPath);
return TransferMode.Move;
}
}
return TransferMode.None;
}
private Boolean TryCopyFile(String sourcePath, String targetPath)
{
var originalSize = _diskProvider.GetFileSize(sourcePath);
var tempTargetPath = targetPath + ".partial~";
for (var i = 0; i <= RetryCount; i++)
{
_diskProvider.CopyFile(sourcePath, tempTargetPath);
if (_diskProvider.FileExists(tempTargetPath))
{
var targetSize = _diskProvider.GetFileSize(tempTargetPath);
if (targetSize == originalSize)
{
_diskProvider.MoveFile(tempTargetPath, targetPath);
return true;
}
}
Thread.Sleep(5000);
_diskProvider.DeleteFile(tempTargetPath);
if (i == RetryCount)
{
_logger.Error("Failed to completely transfer [{0}] to [{1}], aborting.", sourcePath, targetPath, i + 1, RetryCount);
}
else
{
_logger.Warn("Failed to completely transfer [{0}] to [{1}], retrying [{2}/{3}].", sourcePath, targetPath, i + 1, RetryCount);
}
}
return false;
}
private Boolean TryMoveFile(String sourcePath, String targetPath)
{
var originalSize = _diskProvider.GetFileSize(sourcePath);
var backupPath = sourcePath + ".backup~";
var tempTargetPath = targetPath + ".partial~";
if (_diskProvider.FileExists(backupPath))
{
_logger.Trace("Removing old backup.");
_diskProvider.DeleteFile(backupPath);
}
if (_diskProvider.FileExists(tempTargetPath))
{
_logger.Trace("Removing old partial.");
_diskProvider.DeleteFile(tempTargetPath);
}
try
{
_logger.Trace("Attempting to move hardlinked backup.");
if (_diskProvider.TryCreateHardLink(sourcePath, backupPath))
{
_diskProvider.MoveFile(backupPath, tempTargetPath);
if (_diskProvider.FileExists(tempTargetPath))
{
var targetSize = _diskProvider.GetFileSize(tempTargetPath);
if (targetSize == originalSize)
{
_diskProvider.MoveFile(tempTargetPath, targetPath);
_logger.Trace("Hardlink move succeeded, deleting source.");
_diskProvider.DeleteFile(sourcePath);
return true;
}
}
Thread.Sleep(5000);
_diskProvider.DeleteFile(tempTargetPath);
}
}
finally
{
if (_diskProvider.FileExists(backupPath))
{
_diskProvider.DeleteFile(backupPath);
}
}
_logger.Trace("Hardlink move failed, reverting to copy.");
if (TryCopyFile(sourcePath, targetPath))
{
_logger.Trace("Copy succeeded, deleting source.");
_diskProvider.DeleteFile(sourcePath);
return true;
}
_logger.Trace("Copy failed.");
return false;
}
}
}

@ -25,13 +25,9 @@ namespace NzbDrone.Common.Disk
long GetFolderSize(string path); long GetFolderSize(string path);
long GetFileSize(string path); long GetFileSize(string path);
void CreateFolder(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 DeleteFile(string path);
void CopyFile(string source, string destination, bool overwrite = false); void CopyFile(string source, string destination, bool overwrite = false);
void MoveFile(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); bool TryCreateHardLink(string source, string destination);
void DeleteFolder(string path, bool recursive); void DeleteFolder(string path, bool recursive);
string ReadAllText(string filePath); string ReadAllText(string filePath);

@ -79,6 +79,7 @@
<Compile Include="Disk\OsPath.cs" /> <Compile Include="Disk\OsPath.cs" />
<Compile Include="Disk\DiskProviderBase.cs" /> <Compile Include="Disk\DiskProviderBase.cs" />
<Compile Include="Disk\IDiskProvider.cs" /> <Compile Include="Disk\IDiskProvider.cs" />
<Compile Include="Disk\DiskTransferService.cs" />
<Compile Include="Disk\TransferMode.cs" /> <Compile Include="Disk\TransferMode.cs" />
<Compile Include="EnsureThat\Ensure.cs" /> <Compile Include="EnsureThat\Ensure.cs" />
<Compile Include="EnsureThat\EnsureBoolExtensions.cs" /> <Compile Include="EnsureThat\EnsureBoolExtensions.cs" />

@ -45,7 +45,8 @@ namespace NzbDrone.Core.Test.ProviderTests.RecycleBinProviderTests
Mocker.Resolve<RecycleBinProvider>().DeleteFolder(path); 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] [Test]
@ -68,7 +69,7 @@ namespace NzbDrone.Core.Test.ProviderTests.RecycleBinProviderTests
var path = @"C:\Test\TV\30 Rock".AsOsAgnostic(); var path = @"C:\Test\TV\30 Rock".AsOsAgnostic();
Mocker.GetMock<IDiskProvider>().Setup(s => s.GetFiles(@"C:\Test\Recycle Bin\30 Rock".AsOsAgnostic(), SearchOption.AllDirectories)) 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); Mocker.Resolve<RecycleBinProvider>().DeleteFolder(path);

@ -44,7 +44,7 @@ namespace NzbDrone.Core.Test.ProviderTests.RecycleBinProviderTests
Mocker.Resolve<RecycleBinProvider>().DeleteFile(path); 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] [Test]
@ -60,7 +60,7 @@ namespace NzbDrone.Core.Test.ProviderTests.RecycleBinProviderTests
Mocker.Resolve<RecycleBinProvider>().DeleteFile(path); 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] [Test]

@ -39,8 +39,8 @@ namespace NzbDrone.Core.Test.TvTests
private void GivenFailedMove() private void GivenFailedMove()
{ {
Mocker.GetMock<IDiskProvider>() Mocker.GetMock<IDiskTransferService>()
.Setup(s => s.MoveFolder(It.IsAny<String>(), It.IsAny<String>())) .Setup(s => s.TransferFolder(It.IsAny<String>(), It.IsAny<String>(), TransferMode.Move, true))
.Throws<IOException>(); .Throws<IOException>();
} }

@ -135,7 +135,8 @@ namespace NzbDrone.Core.Test.UpdateTests
Subject.Execute(new ApplicationUpdateCommand()); 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] [Test]

@ -25,6 +25,7 @@ namespace NzbDrone.Core.Backup
public class BackupService : IBackupService, IExecute<BackupCommand> public class BackupService : IBackupService, IExecute<BackupCommand>
{ {
private readonly IMainDatabase _maindDb; private readonly IMainDatabase _maindDb;
private readonly IDiskTransferService _diskTransferService;
private readonly IDiskProvider _diskProvider; private readonly IDiskProvider _diskProvider;
private readonly IAppFolderInfo _appFolderInfo; private readonly IAppFolderInfo _appFolderInfo;
private readonly IArchiveService _archiveService; private readonly IArchiveService _archiveService;
@ -35,12 +36,14 @@ namespace NzbDrone.Core.Backup
private static readonly Regex BackupFileRegex = new Regex(@"nzbdrone_backup_[._0-9]+\.zip", RegexOptions.Compiled | RegexOptions.IgnoreCase); private static readonly Regex BackupFileRegex = new Regex(@"nzbdrone_backup_[._0-9]+\.zip", RegexOptions.Compiled | RegexOptions.IgnoreCase);
public BackupService(IMainDatabase maindDb, public BackupService(IMainDatabase maindDb,
IDiskProvider diskProvider, IDiskTransferService diskTransferService,
IAppFolderInfo appFolderInfo, IDiskProvider diskProvider,
IArchiveService archiveService, IAppFolderInfo appFolderInfo,
IArchiveService archiveService,
Logger logger) Logger logger)
{ {
_maindDb = maindDb; _maindDb = maindDb;
_diskTransferService = diskTransferService;
_diskProvider = diskProvider; _diskProvider = diskProvider;
_appFolderInfo = appFolderInfo; _appFolderInfo = appFolderInfo;
_archiveService = archiveService; _archiveService = archiveService;
@ -115,7 +118,7 @@ namespace NzbDrone.Core.Backup
var databaseFile = _appFolderInfo.GetNzbDroneDatabase(); var databaseFile = _appFolderInfo.GetNzbDroneDatabase();
var tempDatabaseFile = Path.Combine(_backupTempFolder, Path.GetFileName(databaseFile)); var tempDatabaseFile = Path.Combine(_backupTempFolder, Path.GetFileName(databaseFile));
_diskProvider.CopyFile(databaseFile, tempDatabaseFile, true); _diskTransferService.TransferFile(databaseFile, tempDatabaseFile, TransferMode.Copy);
unitOfWork.Commit(); unitOfWork.Commit();
} }
@ -128,7 +131,7 @@ namespace NzbDrone.Core.Backup
var configFile = _appFolderInfo.GetConfigPath(); var configFile = _appFolderInfo.GetConfigPath();
var tempConfigFile = Path.Combine(_backupTempFolder, Path.GetFileName(configFile)); var tempConfigFile = Path.Combine(_backupTempFolder, Path.GetFileName(configFile));
_diskProvider.CopyFile(configFile, tempConfigFile, true); _diskTransferService.TransferFile(configFile, tempConfigFile, TransferMode.Copy);
} }
private void CleanupOldBackups(BackupType backupType) private void CleanupOldBackups(BackupType backupType)

@ -27,6 +27,7 @@ namespace NzbDrone.Core.MediaFiles
private readonly IEpisodeService _episodeService; private readonly IEpisodeService _episodeService;
private readonly IUpdateEpisodeFileService _updateEpisodeFileService; private readonly IUpdateEpisodeFileService _updateEpisodeFileService;
private readonly IBuildFileNames _buildFileNames; private readonly IBuildFileNames _buildFileNames;
private readonly IDiskTransferService _diskTransferService;
private readonly IDiskProvider _diskProvider; private readonly IDiskProvider _diskProvider;
private readonly IMediaFileAttributeService _mediaFileAttributeService; private readonly IMediaFileAttributeService _mediaFileAttributeService;
private readonly IEventAggregator _eventAggregator; private readonly IEventAggregator _eventAggregator;
@ -36,6 +37,7 @@ namespace NzbDrone.Core.MediaFiles
public EpisodeFileMovingService(IEpisodeService episodeService, public EpisodeFileMovingService(IEpisodeService episodeService,
IUpdateEpisodeFileService updateEpisodeFileService, IUpdateEpisodeFileService updateEpisodeFileService,
IBuildFileNames buildFileNames, IBuildFileNames buildFileNames,
IDiskTransferService diskTransferService,
IDiskProvider diskProvider, IDiskProvider diskProvider,
IMediaFileAttributeService mediaFileAttributeService, IMediaFileAttributeService mediaFileAttributeService,
IEventAggregator eventAggregator, IEventAggregator eventAggregator,
@ -45,6 +47,7 @@ namespace NzbDrone.Core.MediaFiles
_episodeService = episodeService; _episodeService = episodeService;
_updateEpisodeFileService = updateEpisodeFileService; _updateEpisodeFileService = updateEpisodeFileService;
_buildFileNames = buildFileNames; _buildFileNames = buildFileNames;
_diskTransferService = diskTransferService;
_diskProvider = diskProvider; _diskProvider = diskProvider;
_mediaFileAttributeService = mediaFileAttributeService; _mediaFileAttributeService = mediaFileAttributeService;
_eventAggregator = eventAggregator; _eventAggregator = eventAggregator;
@ -112,8 +115,7 @@ namespace NzbDrone.Core.MediaFiles
throw new SameFilenameException("File not moved, source and destination are the same", episodeFilePath); throw new SameFilenameException("File not moved, source and destination are the same", episodeFilePath);
} }
_logger.Debug("{0} [{1}] > [{2}]", mode, episodeFilePath, destinationFilename); _diskTransferService.TransferFile(episodeFilePath, destinationFilename, mode);
_diskProvider.TransferFile(episodeFilePath, destinationFilename, mode);
episodeFile.RelativePath = series.Path.GetRelativePath(destinationFilename); episodeFile.RelativePath = series.Path.GetRelativePath(destinationFilename);

@ -22,13 +22,18 @@ namespace NzbDrone.Core.MediaFiles
public class RecycleBinProvider : IHandleAsync<SeriesDeletedEvent>, IExecute<CleanUpRecycleBinCommand>, IRecycleBinProvider public class RecycleBinProvider : IHandleAsync<SeriesDeletedEvent>, IExecute<CleanUpRecycleBinCommand>, IRecycleBinProvider
{ {
private readonly IDiskTransferService _diskTransferService;
private readonly IDiskProvider _diskProvider; private readonly IDiskProvider _diskProvider;
private readonly IConfigService _configService; private readonly IConfigService _configService;
private readonly Logger _logger; 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; _diskProvider = diskProvider;
_configService = configService; _configService = configService;
_logger = logger; _logger = logger;
@ -51,7 +56,7 @@ namespace NzbDrone.Core.MediaFiles
var destination = Path.Combine(recyclingBin, new DirectoryInfo(path).Name); var destination = Path.Combine(recyclingBin, new DirectoryInfo(path).Name);
_logger.Debug("Moving '{0}' to '{1}'", path, destination); _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); _logger.Debug("Setting last accessed: {0}", path);
_diskProvider.FolderSetLastWriteTime(destination, DateTime.UtcNow); _diskProvider.FolderSetLastWriteTime(destination, DateTime.UtcNow);
@ -106,7 +111,7 @@ namespace NzbDrone.Core.MediaFiles
} }
_logger.Debug("Moving '{0}' to '{1}'", path, destination); _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? //TODO: Better fix than this for non-Windows?
if (OsInfo.IsWindows) if (OsInfo.IsWindows)

@ -27,6 +27,7 @@ namespace NzbDrone.Core.Metadata
private readonly ICleanMetadataService _cleanMetadataService; private readonly ICleanMetadataService _cleanMetadataService;
private readonly IMediaFileService _mediaFileService; private readonly IMediaFileService _mediaFileService;
private readonly IEpisodeService _episodeService; private readonly IEpisodeService _episodeService;
private readonly IDiskTransferService _diskTransferService;
private readonly IDiskProvider _diskProvider; private readonly IDiskProvider _diskProvider;
private readonly IHttpClient _httpClient; private readonly IHttpClient _httpClient;
private readonly IMediaFileAttributeService _mediaFileAttributeService; private readonly IMediaFileAttributeService _mediaFileAttributeService;
@ -38,6 +39,7 @@ namespace NzbDrone.Core.Metadata
ICleanMetadataService cleanMetadataService, ICleanMetadataService cleanMetadataService,
IMediaFileService mediaFileService, IMediaFileService mediaFileService,
IEpisodeService episodeService, IEpisodeService episodeService,
IDiskTransferService diskTransferService,
IDiskProvider diskProvider, IDiskProvider diskProvider,
IHttpClient httpClient, IHttpClient httpClient,
IMediaFileAttributeService mediaFileAttributeService, IMediaFileAttributeService mediaFileAttributeService,
@ -49,6 +51,7 @@ namespace NzbDrone.Core.Metadata
_cleanMetadataService = cleanMetadataService; _cleanMetadataService = cleanMetadataService;
_mediaFileService = mediaFileService; _mediaFileService = mediaFileService;
_episodeService = episodeService; _episodeService = episodeService;
_diskTransferService = diskTransferService;
_diskProvider = diskProvider; _diskProvider = diskProvider;
_httpClient = httpClient; _httpClient = httpClient;
_mediaFileAttributeService = mediaFileAttributeService; _mediaFileAttributeService = mediaFileAttributeService;
@ -218,7 +221,7 @@ namespace NzbDrone.Core.Metadata
var existingFullPath = Path.Combine(series.Path, existingMetadata.RelativePath); var existingFullPath = Path.Combine(series.Path, existingMetadata.RelativePath);
if (!fullPath.PathEquals(existingFullPath)) if (!fullPath.PathEquals(existingFullPath))
{ {
_diskProvider.MoveFile(existingFullPath, fullPath); _diskTransferService.TransferFile(existingFullPath, fullPath, TransferMode.Move);
existingMetadata.RelativePath = episodeMetadata.RelativePath; existingMetadata.RelativePath = episodeMetadata.RelativePath;
} }
} }
@ -339,7 +342,7 @@ namespace NzbDrone.Core.Metadata
var existingFullPath = Path.Combine(series.Path, existingMetadata.RelativePath); var existingFullPath = Path.Combine(series.Path, existingMetadata.RelativePath);
if (!fullPath.PathEquals(existingFullPath)) if (!fullPath.PathEquals(existingFullPath))
{ {
_diskProvider.MoveFile(fullPath, fullPath); _diskTransferService.TransferFile(existingFullPath, fullPath, TransferMode.Move);
existingMetadata.RelativePath = image.RelativePath; existingMetadata.RelativePath = image.RelativePath;
return new List<MetadataFile>{ existingMetadata }; return new List<MetadataFile>{ existingMetadata };

@ -16,19 +16,19 @@ namespace NzbDrone.Core.Tv
{ {
private readonly ISeriesService _seriesService; private readonly ISeriesService _seriesService;
private readonly IBuildFileNames _filenameBuilder; private readonly IBuildFileNames _filenameBuilder;
private readonly IDiskProvider _diskProvider; private readonly IDiskTransferService _diskTransferService;
private readonly IEventAggregator _eventAggregator; private readonly IEventAggregator _eventAggregator;
private readonly Logger _logger; private readonly Logger _logger;
public MoveSeriesService(ISeriesService seriesService, public MoveSeriesService(ISeriesService seriesService,
IBuildFileNames filenameBuilder, IBuildFileNames filenameBuilder,
IDiskProvider diskProvider, IDiskTransferService diskTransferService,
IEventAggregator eventAggregator, IEventAggregator eventAggregator,
Logger logger) Logger logger)
{ {
_seriesService = seriesService; _seriesService = seriesService;
_filenameBuilder = filenameBuilder; _filenameBuilder = filenameBuilder;
_diskProvider = diskProvider; _diskTransferService = diskTransferService;
_eventAggregator = eventAggregator; _eventAggregator = eventAggregator;
_logger = logger; _logger = logger;
} }
@ -50,7 +50,7 @@ namespace NzbDrone.Core.Tv
//TODO: Move to transactional disk operations //TODO: Move to transactional disk operations
try try
{ {
_diskProvider.MoveFolder(source, destination); _diskTransferService.TransferFolder(source, destination, TransferMode.Move);
} }
catch (IOException ex) catch (IOException ex)
{ {

@ -24,6 +24,7 @@ namespace NzbDrone.Core.Update
private readonly IAppFolderInfo _appFolderInfo; private readonly IAppFolderInfo _appFolderInfo;
private readonly IDiskProvider _diskProvider; private readonly IDiskProvider _diskProvider;
private readonly IDiskTransferService _diskTransferService;
private readonly IHttpClient _httpClient; private readonly IHttpClient _httpClient;
private readonly IArchiveService _archiveService; private readonly IArchiveService _archiveService;
private readonly IProcessProvider _processProvider; private readonly IProcessProvider _processProvider;
@ -34,9 +35,13 @@ namespace NzbDrone.Core.Update
private readonly IBackupService _backupService; private readonly IBackupService _backupService;
public InstallUpdateService(ICheckUpdateService checkUpdateService, IAppFolderInfo appFolderInfo, public InstallUpdateService(ICheckUpdateService checkUpdateService,
IDiskProvider diskProvider, IHttpClient httpClient, IAppFolderInfo appFolderInfo,
IArchiveService archiveService, IProcessProvider processProvider, IDiskProvider diskProvider,
IDiskTransferService diskTransferService,
IHttpClient httpClient,
IArchiveService archiveService,
IProcessProvider processProvider,
IVerifyUpdates updateVerifier, IVerifyUpdates updateVerifier,
IStartupContext startupContext, IStartupContext startupContext,
IConfigFileProvider configFileProvider, IConfigFileProvider configFileProvider,
@ -51,6 +56,7 @@ namespace NzbDrone.Core.Update
_checkUpdateService = checkUpdateService; _checkUpdateService = checkUpdateService;
_appFolderInfo = appFolderInfo; _appFolderInfo = appFolderInfo;
_diskProvider = diskProvider; _diskProvider = diskProvider;
_diskTransferService = diskTransferService;
_httpClient = httpClient; _httpClient = httpClient;
_archiveService = archiveService; _archiveService = archiveService;
_processProvider = processProvider; _processProvider = processProvider;
@ -113,8 +119,7 @@ namespace NzbDrone.Core.Update
} }
_logger.Info("Preparing client"); _logger.Info("Preparing client");
_diskProvider.MoveFolder(_appFolderInfo.GetUpdateClientFolder(), _diskTransferService.TransferFolder(_appFolderInfo.GetUpdateClientFolder(), updateSandboxFolder, TransferMode.Move, false);
updateSandboxFolder);
_logger.Info("Starting update client {0}", _appFolderInfo.GetUpdateClientExePath()); _logger.Info("Starting update client {0}", _appFolderInfo.GetUpdateClientExePath());
_logger.ProgressInfo("Sonarr will restart shortly."); _logger.ProgressInfo("Sonarr will restart shortly.");

@ -13,13 +13,13 @@ namespace NzbDrone.Update.UpdateEngine
public class BackupAndRestore : IBackupAndRestore public class BackupAndRestore : IBackupAndRestore
{ {
private readonly IDiskProvider _diskProvider; private readonly IDiskTransferService _diskTransferService;
private readonly IAppFolderInfo _appFolderInfo; private readonly IAppFolderInfo _appFolderInfo;
private readonly Logger _logger; 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; _appFolderInfo = appFolderInfo;
_logger = logger; _logger = logger;
} }
@ -27,13 +27,13 @@ namespace NzbDrone.Update.UpdateEngine
public void Backup(string source) public void Backup(string source)
{ {
_logger.Info("Creating backup of existing installation"); _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) public void Restore(string target)
{ {
_logger.Info("Attempting to rollback upgrade"); _logger.Info("Attempting to rollback upgrade");
_diskProvider.CopyFolder(_appFolderInfo.GetUpdateBackUpFolder(), target); _diskTransferService.TransferFolder(_appFolderInfo.GetUpdateBackUpFolder(), target, TransferMode.Copy, false);
} }
} }
} }

@ -14,13 +14,18 @@ namespace NzbDrone.Update.UpdateEngine
public class BackupAppData : IBackupAppData public class BackupAppData : IBackupAppData
{ {
private readonly IAppFolderInfo _appFolderInfo; private readonly IAppFolderInfo _appFolderInfo;
private readonly IDiskTransferService _diskTransferService;
private readonly IDiskProvider _diskProvider; private readonly IDiskProvider _diskProvider;
private readonly Logger _logger; private readonly Logger _logger;
public BackupAppData(IAppFolderInfo appFolderInfo, IDiskProvider diskProvider, Logger logger) public BackupAppData(IAppFolderInfo appFolderInfo,
IDiskProvider diskProvider,
IDiskTransferService diskTransferService,
Logger logger)
{ {
_appFolderInfo = appFolderInfo; _appFolderInfo = appFolderInfo;
_diskProvider = diskProvider; _diskProvider = diskProvider;
_diskTransferService = diskTransferService;
_logger = logger; _logger = logger;
} }
@ -33,9 +38,8 @@ namespace NzbDrone.Update.UpdateEngine
try try
{ {
_diskProvider.CopyFile(_appFolderInfo.GetConfigPath(), _appFolderInfo.GetUpdateBackupConfigFile(), true); _diskTransferService.TransferFile(_appFolderInfo.GetConfigPath(), _appFolderInfo.GetUpdateBackupConfigFile(), TransferMode.Copy);
_diskProvider.CopyFile(_appFolderInfo.GetNzbDroneDatabase(), _appFolderInfo.GetUpdateBackupDatabase(), _diskTransferService.TransferFile(_appFolderInfo.GetNzbDroneDatabase(), _appFolderInfo.GetUpdateBackupDatabase(), TransferMode.Copy);
true);
} }
catch (Exception e) catch (Exception e)
{ {

@ -16,6 +16,7 @@ namespace NzbDrone.Update.UpdateEngine
public class InstallUpdateService : IInstallUpdateService public class InstallUpdateService : IInstallUpdateService
{ {
private readonly IDiskProvider _diskProvider; private readonly IDiskProvider _diskProvider;
private readonly IDiskTransferService _diskTransferService;
private readonly IDetectApplicationType _detectApplicationType; private readonly IDetectApplicationType _detectApplicationType;
private readonly ITerminateNzbDrone _terminateNzbDrone; private readonly ITerminateNzbDrone _terminateNzbDrone;
private readonly IAppFolderInfo _appFolderInfo; private readonly IAppFolderInfo _appFolderInfo;
@ -26,6 +27,7 @@ namespace NzbDrone.Update.UpdateEngine
private readonly Logger _logger; private readonly Logger _logger;
public InstallUpdateService(IDiskProvider diskProvider, public InstallUpdateService(IDiskProvider diskProvider,
IDiskTransferService diskTransferService,
IDetectApplicationType detectApplicationType, IDetectApplicationType detectApplicationType,
ITerminateNzbDrone terminateNzbDrone, ITerminateNzbDrone terminateNzbDrone,
IAppFolderInfo appFolderInfo, IAppFolderInfo appFolderInfo,
@ -36,6 +38,7 @@ namespace NzbDrone.Update.UpdateEngine
Logger logger) Logger logger)
{ {
_diskProvider = diskProvider; _diskProvider = diskProvider;
_diskTransferService = diskTransferService;
_detectApplicationType = detectApplicationType; _detectApplicationType = detectApplicationType;
_terminateNzbDrone = terminateNzbDrone; _terminateNzbDrone = terminateNzbDrone;
_appFolderInfo = appFolderInfo; _appFolderInfo = appFolderInfo;
@ -93,7 +96,7 @@ namespace NzbDrone.Update.UpdateEngine
_diskProvider.EmptyFolder(installationFolder); _diskProvider.EmptyFolder(installationFolder);
_logger.Info("Copying new files to target folder"); _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 // Set executable flag on Sonarr app
if (OsInfo.IsOsx) if (OsInfo.IsOsx)

Loading…
Cancel
Save