mirror of
https://github.com/Radarr/Radarr.git
synced 2024-09-17 15:02:34 +02:00
Fixed: Dataloss when moving series folder to root folder with only different casing
Fixes #5190 Fixes #5184
This commit is contained in:
parent
c270a53eab
commit
d00f3abbf0
@ -402,6 +402,58 @@ public void CopyFolder_should_overwrite_existing_folder()
|
|||||||
VerifyCopyFolder(source.FullName, destination.FullName);
|
VerifyCopyFolder(source.FullName, destination.FullName);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
[Test]
|
||||||
|
public void CopyFolder_should_detect_caseinsensitive_parents()
|
||||||
|
{
|
||||||
|
WindowsOnly();
|
||||||
|
|
||||||
|
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.Copy));
|
||||||
|
}
|
||||||
|
|
||||||
|
[Test]
|
||||||
|
public void CopyFolder_should_detect_caseinsensitive_folder()
|
||||||
|
{
|
||||||
|
WindowsOnly();
|
||||||
|
|
||||||
|
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.Copy));
|
||||||
|
}
|
||||||
|
|
||||||
|
[Test]
|
||||||
|
public void CopyFolder_should_not_copy_casesensitive_folder()
|
||||||
|
{
|
||||||
|
MonoOnly();
|
||||||
|
|
||||||
|
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);
|
||||||
|
|
||||||
|
// Note: Although technically possible top copy to different case, we're not allowing it
|
||||||
|
Assert.Throws<IOException>(() => Subject.TransferFolder(source.FullName, destination.FullName, TransferMode.Copy));
|
||||||
|
}
|
||||||
|
|
||||||
[Test]
|
[Test]
|
||||||
public void CopyFolder_should_ignore_nfs_temp_file()
|
public void CopyFolder_should_ignore_nfs_temp_file()
|
||||||
{
|
{
|
||||||
@ -451,6 +503,62 @@ public void MoveFolder_should_overwrite_existing_folder()
|
|||||||
VerifyMoveFolder(original.FullName, source.FullName, destination.FullName);
|
VerifyMoveFolder(original.FullName, source.FullName, destination.FullName);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
[Test]
|
||||||
|
public void MoveFolder_should_detect_caseinsensitive_parents()
|
||||||
|
{
|
||||||
|
WindowsOnly();
|
||||||
|
|
||||||
|
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()
|
||||||
|
{
|
||||||
|
WindowsOnly();
|
||||||
|
|
||||||
|
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]
|
||||||
|
public void MoveFolder_should_rename_casesensitive_folder()
|
||||||
|
{
|
||||||
|
MonoOnly();
|
||||||
|
|
||||||
|
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);
|
||||||
|
|
||||||
|
Directory.Exists(source.FullName).Should().Be(false);
|
||||||
|
Directory.Exists(destination.FullName).Should().Be(true);
|
||||||
|
}
|
||||||
|
|
||||||
[Test]
|
[Test]
|
||||||
public void should_throw_if_destination_is_readonly()
|
public void should_throw_if_destination_is_readonly()
|
||||||
{
|
{
|
||||||
@ -553,6 +661,23 @@ public void MirrorFolder_should_not_touch_equivalent_files()
|
|||||||
VerifyCopyFolder(original.FullName, destination.FullName);
|
VerifyCopyFolder(original.FullName, destination.FullName);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
[Test]
|
||||||
|
public void MirrorFolder_should_handle_trailing_slash()
|
||||||
|
{
|
||||||
|
WithRealDiskProvider();
|
||||||
|
|
||||||
|
var original = GetFilledTempFolder();
|
||||||
|
var source = new DirectoryInfo(GetTempFilePath());
|
||||||
|
var destination = new DirectoryInfo(GetTempFilePath());
|
||||||
|
|
||||||
|
Subject.TransferFolder(original.FullName, source.FullName, TransferMode.Copy);
|
||||||
|
|
||||||
|
var count = Subject.MirrorFolder(source.FullName + Path.DirectorySeparatorChar, destination.FullName);
|
||||||
|
|
||||||
|
count.Should().Equals(3);
|
||||||
|
VerifyCopyFolder(original.FullName, destination.FullName);
|
||||||
|
}
|
||||||
|
|
||||||
[Test]
|
[Test]
|
||||||
public void TransferFolder_should_use_movefolder_if_on_same_mount()
|
public void TransferFolder_should_use_movefolder_if_on_same_mount()
|
||||||
{
|
{
|
||||||
@ -752,6 +877,10 @@ private void WithRealDiskProvider()
|
|||||||
.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>(), It.IsAny<bool>()))
|
||||||
|
.Callback<string, string, bool>((v, r, b) => 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));
|
||||||
|
@ -258,17 +258,6 @@ public void MoveFolder(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))
|
|
||||||
{
|
|
||||||
throw new IOException(string.Format("Source and destination can't be the same {0}", source));
|
|
||||||
}
|
|
||||||
|
|
||||||
if (FolderExists(destination) && overwrite)
|
|
||||||
{
|
|
||||||
DeleteFolder(destination, true);
|
|
||||||
}
|
|
||||||
|
|
||||||
RemoveReadOnlyFolder(source);
|
|
||||||
Directory.Move(source, destination);
|
Directory.Move(source, destination);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -4,7 +4,6 @@
|
|||||||
using System.Threading;
|
using System.Threading;
|
||||||
using NLog;
|
using NLog;
|
||||||
using NzbDrone.Common.EnsureThat;
|
using NzbDrone.Common.EnsureThat;
|
||||||
using NzbDrone.Common.EnvironmentInfo;
|
|
||||||
using NzbDrone.Common.Extensions;
|
using NzbDrone.Common.Extensions;
|
||||||
|
|
||||||
namespace NzbDrone.Common.Disk
|
namespace NzbDrone.Common.Disk
|
||||||
@ -27,11 +26,56 @@ public DiskTransferService(IDiskProvider diskProvider, Logger logger)
|
|||||||
_logger = logger;
|
_logger = logger;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private string ResolveRealParentPath(string path)
|
||||||
|
{
|
||||||
|
var parentPath = path.GetParentPath();
|
||||||
|
if (!_diskProvider.FolderExists(parentPath))
|
||||||
|
{
|
||||||
|
return path;
|
||||||
|
}
|
||||||
|
|
||||||
|
var realParentPath = parentPath.GetActualCasing();
|
||||||
|
|
||||||
|
var partialChildPath = path.Substring(parentPath.Length);
|
||||||
|
|
||||||
|
return realParentPath + partialChildPath;
|
||||||
|
}
|
||||||
|
|
||||||
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);
|
||||||
@ -40,7 +84,7 @@ public TransferMode TransferFolder(string sourcePath, string targetPath, Transfe
|
|||||||
// 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;
|
||||||
}
|
}
|
||||||
@ -79,6 +123,13 @@ public TransferMode TransferFolder(string sourcePath, string targetPath, Transfe
|
|||||||
|
|
||||||
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);
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -92,7 +143,10 @@ public int MirrorFolder(string sourcePath, string targetPath)
|
|||||||
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))
|
||||||
{
|
{
|
||||||
@ -204,6 +258,9 @@ public TransferMode TransferFile(string sourcePath, string targetPath, TransferM
|
|||||||
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} [{1}] > [{2}]", mode, sourcePath, targetPath);
|
_logger.Debug("{0} [{1}] > [{2}]", mode, sourcePath, targetPath);
|
||||||
|
|
||||||
var originalSize = _diskProvider.GetFileSize(sourcePath);
|
var originalSize = _diskProvider.GetFileSize(sourcePath);
|
||||||
|
@ -90,6 +90,12 @@ public void Start(string installationFolder, int processId)
|
|||||||
|
|
||||||
Verify(installationFolder, processId);
|
Verify(installationFolder, processId);
|
||||||
|
|
||||||
|
if (installationFolder.EndsWith(@"\bin\Radarr") || installationFolder.EndsWith(@"/bin/Radarr"))
|
||||||
|
{
|
||||||
|
installationFolder = installationFolder.GetParentPath();
|
||||||
|
_logger.Info("Fixed Installation Folder: {0}", installationFolder);
|
||||||
|
}
|
||||||
|
|
||||||
var appType = _detectApplicationType.GetAppType();
|
var appType = _detectApplicationType.GetAppType();
|
||||||
|
|
||||||
_processProvider.FindProcessByName(ProcessProvider.RADARR_CONSOLE_PROCESS_NAME);
|
_processProvider.FindProcessByName(ProcessProvider.RADARR_CONSOLE_PROCESS_NAME);
|
||||||
|
Loading…
Reference in New Issue
Block a user