diff --git a/src/NzbDrone.Common.Test/DiskTests/DiskTransferServiceFixture.cs b/src/NzbDrone.Common.Test/DiskTests/DiskTransferServiceFixture.cs index 8830ccea0..36300a2ee 100644 --- a/src/NzbDrone.Common.Test/DiskTests/DiskTransferServiceFixture.cs +++ b/src/NzbDrone.Common.Test/DiskTests/DiskTransferServiceFixture.cs @@ -129,6 +129,59 @@ public void should_remove_source_after_move() VerifyDeletedFile(_sourcePath); } + [Test] + public void should_not_remove_source_if_partial_still_exists() + { + MonoOnly(); + + var targetPath = Path.Combine(Path.GetDirectoryName(_targetPath), Path.GetFileName(_targetPath).ToUpper()); + var tempTargetPath = targetPath + ".partial~"; + + WithSuccessfulHardlink(_sourcePath, _backupPath); + + WithExistingFile(_targetPath); + + Mocker.GetMock() + .Setup(v => v.MoveFile(_backupPath, tempTargetPath, false)) + .Callback(() => WithExistingFile(tempTargetPath, true)); + + Mocker.GetMock() + .Setup(v => v.MoveFile(tempTargetPath, targetPath, false)) + .Callback(() => { }); + + Assert.Throws(() => Subject.TransferFile(_sourcePath, targetPath, TransferMode.Move)); + + Mocker.GetMock() + .Verify(v => v.DeleteFile(_sourcePath), Times.Never()); + } + + [Test] + public void should_rename_via_temp() + { + var targetPath = Path.Combine(Path.GetDirectoryName(_sourcePath), Path.GetFileName(_sourcePath).ToUpper()); + + Mocker.GetMock() + .Setup(v => v.MoveFile(_sourcePath, _backupPath, false)) + .Callback(() => + { + WithExistingFile(_backupPath, true); + WithExistingFile(_sourcePath, false); + }); + + Mocker.GetMock() + .Setup(v => v.MoveFile(_backupPath, targetPath, false)) + .Callback(() => + { + WithExistingFile(targetPath, true); + WithExistingFile(_backupPath, false); + }); + + var result = Subject.TransferFile(_sourcePath, targetPath, TransferMode.Move); + + Mocker.GetMock() + .Verify(v => v.MoveFile(_backupPath, targetPath, false), Times.Once()); + } + [Test] public void should_remove_backup_if_move_throws() { diff --git a/src/NzbDrone.Common/Disk/DiskTransferService.cs b/src/NzbDrone.Common/Disk/DiskTransferService.cs index e8e671c78..3feca3800 100644 --- a/src/NzbDrone.Common/Disk/DiskTransferService.cs +++ b/src/NzbDrone.Common/Disk/DiskTransferService.cs @@ -83,11 +83,35 @@ public TransferMode TransferFile(String sourcePath, String targetPath, TransferM _logger.Debug("{0} [{1}] > [{2}]", mode, sourcePath, targetPath); - if (sourcePath.PathEquals(targetPath)) + if (sourcePath == targetPath) { throw new IOException(string.Format("Source and destination can't be the same {0}", sourcePath)); } + if (sourcePath.PathEquals(targetPath, StringComparison.InvariantCultureIgnoreCase)) + { + if (mode.HasFlag(TransferMode.HardLink) || mode.HasFlag(TransferMode.Copy)) + { + throw new IOException(string.Format("Source and destination can't be the same {0}", sourcePath)); + } + + if (mode.HasFlag(TransferMode.Move)) + { + var tempPath = sourcePath + ".backup~"; + _diskProvider.MoveFile(sourcePath, tempPath); + + if (_diskProvider.FileExists(targetPath)) + { + _diskProvider.MoveFile(tempPath, sourcePath); + } + + _diskProvider.MoveFile(tempPath, targetPath); + return TransferMode.Move; + } + + return TransferMode.None; + } + if (sourcePath.IsParentPath(targetPath)) { throw new IOException(string.Format("Destination cannot be a child of the source [{0}] => [{1}]", sourcePath, targetPath)); @@ -220,6 +244,10 @@ private Boolean TryMoveFile(String sourcePath, String targetPath) if (targetSize == originalSize) { _diskProvider.MoveFile(tempTargetPath, targetPath); + if (_diskProvider.FileExists(tempTargetPath)) + { + throw new IOException(String.Format("Temporary file '{0}' still exists, aborting.", tempTargetPath)); + } _logger.Trace("Hardlink move succeeded, deleting source."); _diskProvider.DeleteFile(sourcePath); return true; diff --git a/src/NzbDrone.Core/MediaFiles/EpisodeFileMovingService.cs b/src/NzbDrone.Core/MediaFiles/EpisodeFileMovingService.cs index 27edea943..72f12fb3f 100644 --- a/src/NzbDrone.Core/MediaFiles/EpisodeFileMovingService.cs +++ b/src/NzbDrone.Core/MediaFiles/EpisodeFileMovingService.cs @@ -97,11 +97,11 @@ public EpisodeFile CopyEpisodeFile(EpisodeFile episodeFile, LocalEpisode localEp return TransferFile(episodeFile, localEpisode.Series, localEpisode.Episodes, filePath, TransferMode.Copy); } - private EpisodeFile TransferFile(EpisodeFile episodeFile, Series series, List episodes, string destinationFilename, TransferMode mode) + private EpisodeFile TransferFile(EpisodeFile episodeFile, Series series, List episodes, string destinationFilePath, TransferMode mode) { Ensure.That(episodeFile, () => episodeFile).IsNotNull(); Ensure.That(series,() => series).IsNotNull(); - Ensure.That(destinationFilename, () => destinationFilename).IsValidPath(); + Ensure.That(destinationFilePath, () => destinationFilePath).IsValidPath(); var episodeFilePath = episodeFile.Path ?? Path.Combine(series.Path, episodeFile.RelativePath); @@ -110,14 +110,14 @@ private EpisodeFile TransferFile(EpisodeFile episodeFile, Series series, List