Compare commits
2 Commits
develop
...
phantom-fo
Author | SHA1 | Date |
---|---|---|
Taloth Saldono | 0f4e699b57 | |
Taloth Saldono | 6134aa38eb |
|
@ -451,6 +451,38 @@ namespace NzbDrone.Common.Test.DiskTests
|
||||||
VerifyMoveFolder(original.FullName, source.FullName, destination.FullName);
|
VerifyMoveFolder(original.FullName, source.FullName, destination.FullName);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
[Test]
|
||||||
|
public void MoveFolder_should_detect_caseinsensitive_parents()
|
||||||
|
{
|
||||||
|
WithRealDiskProvider();
|
||||||
|
|
||||||
|
var original = GetFilledTempFolder();
|
||||||
|
var root = new DirectoryInfo(GetTempFilePath());
|
||||||
|
var source = new DirectoryInfo(root.FullName + "A/series");
|
||||||
|
var destination = new DirectoryInfo(root.FullName + "a/series");
|
||||||
|
|
||||||
|
Subject.TransferFolder(original.FullName, source.FullName, TransferMode.Copy);
|
||||||
|
|
||||||
|
Assert.Throws<IOException>(() => Subject.TransferFolder(source.FullName, destination.FullName, TransferMode.Move));
|
||||||
|
}
|
||||||
|
|
||||||
|
[Test]
|
||||||
|
public void MoveFolder_should_rename_caseinsensitive_folder()
|
||||||
|
{
|
||||||
|
WithRealDiskProvider();
|
||||||
|
|
||||||
|
var original = GetFilledTempFolder();
|
||||||
|
var root = new DirectoryInfo(GetTempFilePath());
|
||||||
|
var source = new DirectoryInfo(root.FullName + "A/series");
|
||||||
|
var destination = new DirectoryInfo(root.FullName + "A/Series");
|
||||||
|
|
||||||
|
Subject.TransferFolder(original.FullName, source.FullName, TransferMode.Copy);
|
||||||
|
|
||||||
|
Subject.TransferFolder(source.FullName, destination.FullName, TransferMode.Move);
|
||||||
|
|
||||||
|
source.FullName.GetActualCasing().Should().Be(destination.FullName);
|
||||||
|
}
|
||||||
|
|
||||||
[Test]
|
[Test]
|
||||||
public void should_throw_if_destination_is_readonly()
|
public void should_throw_if_destination_is_readonly()
|
||||||
{
|
{
|
||||||
|
@ -747,9 +779,13 @@ namespace NzbDrone.Common.Test.DiskTests
|
||||||
.Setup(v => v.CreateFolder(It.IsAny<string>()))
|
.Setup(v => v.CreateFolder(It.IsAny<string>()))
|
||||||
.Callback<string>(v => Directory.CreateDirectory(v));
|
.Callback<string>(v => Directory.CreateDirectory(v));
|
||||||
|
|
||||||
|
Mocker.GetMock<IDiskProvider>()
|
||||||
|
.Setup(v => v.MoveFolder(It.IsAny<string>(), It.IsAny<string>()))
|
||||||
|
.Callback<string, string>((v, r) => Directory.Move(v, r));
|
||||||
|
|
||||||
Mocker.GetMock<IDiskProvider>()
|
Mocker.GetMock<IDiskProvider>()
|
||||||
.Setup(v => v.DeleteFolder(It.IsAny<string>(), It.IsAny<bool>()))
|
.Setup(v => v.DeleteFolder(It.IsAny<string>(), It.IsAny<bool>()))
|
||||||
.Callback<string, bool>((v,r) => Directory.Delete(v, r));
|
.Callback<string, bool>((v, r) => Directory.Delete(v, r));
|
||||||
|
|
||||||
Mocker.GetMock<IDiskProvider>()
|
Mocker.GetMock<IDiskProvider>()
|
||||||
.Setup(v => v.DeleteFile(It.IsAny<string>()))
|
.Setup(v => v.DeleteFile(It.IsAny<string>()))
|
||||||
|
|
|
@ -1,6 +1,7 @@
|
||||||
using System;
|
using System;
|
||||||
using System.IO;
|
using System.IO;
|
||||||
using System.Linq;
|
using System.Linq;
|
||||||
|
using System.Runtime.InteropServices;
|
||||||
using System.Threading;
|
using System.Threading;
|
||||||
using NLog;
|
using NLog;
|
||||||
using NzbDrone.Common.EnsureThat;
|
using NzbDrone.Common.EnsureThat;
|
||||||
|
@ -28,11 +29,53 @@ namespace NzbDrone.Common.Disk
|
||||||
_logger = logger;
|
_logger = logger;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private string ResolveRealParentPath(string path)
|
||||||
|
{
|
||||||
|
var parentPath = path.GetParentPath();
|
||||||
|
if (!_diskProvider.FolderExists(path))
|
||||||
|
{
|
||||||
|
return path;
|
||||||
|
}
|
||||||
|
|
||||||
|
parentPath = parentPath.GetActualCasing();
|
||||||
|
return parentPath + Path.DirectorySeparatorChar + Path.GetFileName(path);
|
||||||
|
}
|
||||||
|
|
||||||
public TransferMode TransferFolder(string sourcePath, string targetPath, TransferMode mode)
|
public TransferMode TransferFolder(string sourcePath, string targetPath, TransferMode mode)
|
||||||
{
|
{
|
||||||
Ensure.That(sourcePath, () => sourcePath).IsValidPath();
|
Ensure.That(sourcePath, () => sourcePath).IsValidPath();
|
||||||
Ensure.That(targetPath, () => targetPath).IsValidPath();
|
Ensure.That(targetPath, () => targetPath).IsValidPath();
|
||||||
|
|
||||||
|
sourcePath = ResolveRealParentPath(sourcePath);
|
||||||
|
targetPath = ResolveRealParentPath(targetPath);
|
||||||
|
|
||||||
|
_logger.Debug("{0} Directory [{1}] > [{2}]", mode, sourcePath, targetPath);
|
||||||
|
|
||||||
|
if (sourcePath == targetPath)
|
||||||
|
{
|
||||||
|
throw new IOException(string.Format("Source and destination can't be the same {0}", sourcePath));
|
||||||
|
}
|
||||||
|
|
||||||
|
if (mode == TransferMode.Move && sourcePath.PathEquals(targetPath, StringComparison.InvariantCultureIgnoreCase) && _diskProvider.FolderExists(targetPath))
|
||||||
|
{
|
||||||
|
// Move folder out of the way to allow case-insensitive renames
|
||||||
|
var tempPath = sourcePath + ".backup~";
|
||||||
|
_logger.Trace("Rename Intermediate Directory [{0}] > [{1}]", sourcePath, tempPath);
|
||||||
|
_diskProvider.MoveFolder(sourcePath, tempPath);
|
||||||
|
|
||||||
|
if (!_diskProvider.FolderExists(targetPath))
|
||||||
|
{
|
||||||
|
_logger.Trace("Rename Intermediate Directory [{0}] > [{1}]", tempPath, targetPath);
|
||||||
|
_logger.Debug("Rename Directory [{0}] > [{1}]", sourcePath, targetPath);
|
||||||
|
_diskProvider.MoveFolder(tempPath, targetPath);
|
||||||
|
return mode;
|
||||||
|
}
|
||||||
|
|
||||||
|
// There were two separate folders, revert the intermediate rename and let the recursion deal with it
|
||||||
|
_logger.Trace("Rename Intermediate Directory [{0}] > [{1}]", tempPath, sourcePath);
|
||||||
|
_diskProvider.MoveFolder(tempPath, sourcePath);
|
||||||
|
}
|
||||||
|
|
||||||
if (mode == TransferMode.Move && !_diskProvider.FolderExists(targetPath))
|
if (mode == TransferMode.Move && !_diskProvider.FolderExists(targetPath))
|
||||||
{
|
{
|
||||||
var sourceMount = _diskProvider.GetMount(sourcePath);
|
var sourceMount = _diskProvider.GetMount(sourcePath);
|
||||||
|
@ -41,7 +84,7 @@ namespace NzbDrone.Common.Disk
|
||||||
// If we're on the same mount, do a simple folder move.
|
// If we're on the same mount, do a simple folder move.
|
||||||
if (sourceMount != null && targetMount != null && sourceMount.RootDirectory == targetMount.RootDirectory)
|
if (sourceMount != null && targetMount != null && sourceMount.RootDirectory == targetMount.RootDirectory)
|
||||||
{
|
{
|
||||||
_logger.Debug("Move Directory [{0}] > [{1}]", sourcePath, targetPath);
|
_logger.Debug("Rename Directory [{0}] > [{1}]", sourcePath, targetPath);
|
||||||
_diskProvider.MoveFolder(sourcePath, targetPath);
|
_diskProvider.MoveFolder(sourcePath, targetPath);
|
||||||
return mode;
|
return mode;
|
||||||
}
|
}
|
||||||
|
@ -74,6 +117,13 @@ namespace NzbDrone.Common.Disk
|
||||||
|
|
||||||
if (mode.HasFlag(TransferMode.Move))
|
if (mode.HasFlag(TransferMode.Move))
|
||||||
{
|
{
|
||||||
|
var totalSize = _diskProvider.GetFileInfos(sourcePath).Sum(v => v.Length);
|
||||||
|
|
||||||
|
if (totalSize > (100 * 1024L * 1024L))
|
||||||
|
{
|
||||||
|
throw new IOException($"Large files still exist in {sourcePath} after folder move, not deleting source folder");
|
||||||
|
}
|
||||||
|
|
||||||
_diskProvider.DeleteFolder(sourcePath, true);
|
_diskProvider.DeleteFolder(sourcePath, true);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -87,7 +137,10 @@ namespace NzbDrone.Common.Disk
|
||||||
Ensure.That(sourcePath, () => sourcePath).IsValidPath();
|
Ensure.That(sourcePath, () => sourcePath).IsValidPath();
|
||||||
Ensure.That(targetPath, () => targetPath).IsValidPath();
|
Ensure.That(targetPath, () => targetPath).IsValidPath();
|
||||||
|
|
||||||
_logger.Debug("Mirror [{0}] > [{1}]", sourcePath, targetPath);
|
sourcePath = ResolveRealParentPath(sourcePath);
|
||||||
|
targetPath = ResolveRealParentPath(targetPath);
|
||||||
|
|
||||||
|
_logger.Debug("Mirror Folder [{0}] > [{1}]", sourcePath, targetPath);
|
||||||
|
|
||||||
if (!_diskProvider.FolderExists(targetPath))
|
if (!_diskProvider.FolderExists(targetPath))
|
||||||
{
|
{
|
||||||
|
@ -187,17 +240,22 @@ namespace NzbDrone.Common.Disk
|
||||||
Ensure.That(sourcePath, () => sourcePath).IsValidPath();
|
Ensure.That(sourcePath, () => sourcePath).IsValidPath();
|
||||||
Ensure.That(targetPath, () => targetPath).IsValidPath();
|
Ensure.That(targetPath, () => targetPath).IsValidPath();
|
||||||
|
|
||||||
_logger.Debug("{0} [{1}] > [{2}]", mode, sourcePath, targetPath);
|
sourcePath = ResolveRealParentPath(sourcePath);
|
||||||
|
targetPath = ResolveRealParentPath(targetPath);
|
||||||
|
|
||||||
var originalSize = _diskProvider.GetFileSize(sourcePath);
|
_logger.Debug("{0} [{1}] > [{2}]", mode, sourcePath, targetPath);
|
||||||
|
|
||||||
if (sourcePath == targetPath)
|
if (sourcePath == targetPath)
|
||||||
{
|
{
|
||||||
throw new IOException(string.Format("Source and destination can't be the same {0}", sourcePath));
|
throw new IOException(string.Format("Source and destination can't be the same {0}", sourcePath));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
var originalSize = _diskProvider.GetFileSize(sourcePath);
|
||||||
|
|
||||||
if (sourcePath.PathEquals(targetPath, StringComparison.InvariantCultureIgnoreCase))
|
if (sourcePath.PathEquals(targetPath, StringComparison.InvariantCultureIgnoreCase))
|
||||||
{
|
{
|
||||||
|
// Shortcut for dealing with inplace rename
|
||||||
|
|
||||||
if (mode.HasFlag(TransferMode.HardLink) || mode.HasFlag(TransferMode.Copy))
|
if (mode.HasFlag(TransferMode.HardLink) || mode.HasFlag(TransferMode.Copy))
|
||||||
{
|
{
|
||||||
throw new IOException(string.Format("Source and destination can't be the same {0}", sourcePath));
|
throw new IOException(string.Format("Source and destination can't be the same {0}", sourcePath));
|
||||||
|
@ -210,7 +268,7 @@ namespace NzbDrone.Common.Disk
|
||||||
_diskProvider.MoveFile(sourcePath, tempPath, true);
|
_diskProvider.MoveFile(sourcePath, tempPath, true);
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
ClearTargetPath(sourcePath, targetPath, overwrite);
|
ClearTargetPath(targetPath, overwrite);
|
||||||
|
|
||||||
_diskProvider.MoveFile(tempPath, targetPath);
|
_diskProvider.MoveFile(tempPath, targetPath);
|
||||||
|
|
||||||
|
@ -240,7 +298,77 @@ namespace NzbDrone.Common.Disk
|
||||||
throw new IOException(string.Format("Destination cannot be a child of the source [{0}] => [{1}]", sourcePath, targetPath));
|
throw new IOException(string.Format("Destination cannot be a child of the source [{0}] => [{1}]", sourcePath, targetPath));
|
||||||
}
|
}
|
||||||
|
|
||||||
ClearTargetPath(sourcePath, targetPath, overwrite);
|
var targetFileExists = _diskProvider.FileExists(targetPath);
|
||||||
|
var targetFilePotentiallySame = targetFileExists && Path.GetFileName(sourcePath).EqualsIgnoreCase(Path.GetFileName(targetPath)) && _diskProvider.GetFileSize(targetPath) == originalSize;
|
||||||
|
|
||||||
|
// If the target file exists and has the same name ans size then it _could_ be that they're actually pointing to the same actual file via softlinks.
|
||||||
|
// This is reasonably easy to determine in linux, but on windows it's more tricky.
|
||||||
|
// Depending on 'overwrite' and Move/Copy we have to take different actions.
|
||||||
|
|
||||||
|
if (targetFileExists)
|
||||||
|
{
|
||||||
|
if (targetFilePotentiallySame)
|
||||||
|
{
|
||||||
|
var targetBackupPath = targetPath + ".backup~";
|
||||||
|
|
||||||
|
if (mode.HasFlag(TransferMode.HardLink) || mode.HasFlag(TransferMode.Copy))
|
||||||
|
{
|
||||||
|
// Copy doesn't allow renames, only overwrite
|
||||||
|
if (!overwrite)
|
||||||
|
{
|
||||||
|
throw new DestinationAlreadyExistsException($"Destination {targetPath} already exists.");
|
||||||
|
}
|
||||||
|
|
||||||
|
_diskProvider.MoveFile(targetPath, targetBackupPath, true);
|
||||||
|
if (!_diskProvider.FileExists(sourcePath))
|
||||||
|
{
|
||||||
|
// They were the same file, Copy/Hardlink can't handle that, so revert and throw
|
||||||
|
// Note that on windows this can actually cause the casing to change
|
||||||
|
_diskProvider.MoveFile(targetBackupPath, targetPath);
|
||||||
|
|
||||||
|
throw new IOException(string.Format("Source and destination can't be the same {0}", sourcePath));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else if (mode.HasFlag(TransferMode.Move))
|
||||||
|
{
|
||||||
|
// Move allows a rename of same file, or overwrite different file
|
||||||
|
_diskProvider.MoveFile(targetPath, targetBackupPath, true);
|
||||||
|
if (!_diskProvider.FileExists(sourcePath))
|
||||||
|
{
|
||||||
|
// They were the same file, treat this as if it's a rename in place
|
||||||
|
_diskProvider.MoveFile(targetBackupPath, targetPath);
|
||||||
|
|
||||||
|
return TransferMode.Move;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
// They were different files, only allow the move if overwrite is enabled, otherwise revert and throw
|
||||||
|
if (overwrite)
|
||||||
|
{
|
||||||
|
_diskProvider.DeleteFile(targetBackupPath);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
_diskProvider.MoveFile(targetBackupPath, targetPath);
|
||||||
|
throw new DestinationAlreadyExistsException($"Destination {targetPath} already exists.");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
return TransferMode.None;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
if (!overwrite)
|
||||||
|
{
|
||||||
|
throw new DestinationAlreadyExistsException($"Destination {targetPath} already exists.");
|
||||||
|
}
|
||||||
|
|
||||||
|
_diskProvider.DeleteFile(targetPath);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
if (mode.HasFlag(TransferMode.HardLink))
|
if (mode.HasFlag(TransferMode.HardLink))
|
||||||
{
|
{
|
||||||
|
@ -315,7 +443,7 @@ namespace NzbDrone.Common.Disk
|
||||||
return TransferMode.None;
|
return TransferMode.None;
|
||||||
}
|
}
|
||||||
|
|
||||||
private void ClearTargetPath(string sourcePath, string targetPath, bool overwrite)
|
private void ClearTargetPath(string targetPath, bool overwrite)
|
||||||
{
|
{
|
||||||
if (_diskProvider.FileExists(targetPath))
|
if (_diskProvider.FileExists(targetPath))
|
||||||
{
|
{
|
||||||
|
|
Loading…
Reference in New Issue