From c9f720885ec7b37c23680a79feb8c77d883c6f87 Mon Sep 17 00:00:00 2001 From: Taloth Saldono Date: Sat, 27 Jun 2015 01:01:33 +0200 Subject: [PATCH] Fixed: Renaming episodes on OSX with case-insensitive filesystem. --- .../DiskTests/DiskTransferServiceFixture.cs | 53 +++++++++++++++++++ .../Disk/DiskTransferService.cs | 30 ++++++++++- .../MediaFiles/EpisodeFileMovingService.cs | 14 ++--- 3 files changed, 89 insertions(+), 8 deletions(-) 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 @@ namespace NzbDrone.Common.Test.DiskTests 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 @@ namespace NzbDrone.Common.Disk _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 @@ namespace NzbDrone.Common.Disk 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 @@ namespace NzbDrone.Core.MediaFiles 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 @@ namespace NzbDrone.Core.MediaFiles throw new FileNotFoundException("Episode file path does not exist", episodeFilePath); } - if (episodeFilePath.PathEquals(destinationFilename)) + if (episodeFilePath == destinationFilePath) { throw new SameFilenameException("File not moved, source and destination are the same", episodeFilePath); } - _diskTransferService.TransferFile(episodeFilePath, destinationFilename, mode); + _diskTransferService.TransferFile(episodeFilePath, destinationFilePath, mode); - episodeFile.RelativePath = series.Path.GetRelativePath(destinationFilename); + episodeFile.RelativePath = series.Path.GetRelativePath(destinationFilePath); _updateEpisodeFileService.ChangeFileDateForFile(episodeFile, series, episodes); @@ -127,7 +127,7 @@ namespace NzbDrone.Core.MediaFiles if (series.SeasonFolder) { - var seasonFolder = Path.GetDirectoryName(destinationFilename); + var seasonFolder = Path.GetDirectoryName(destinationFilePath); _mediaFileAttributeService.SetFolderLastWriteTime(seasonFolder, episodeFile.DateAdded); } @@ -138,7 +138,7 @@ namespace NzbDrone.Core.MediaFiles _logger.WarnException("Unable to set last write time", ex); } - _mediaFileAttributeService.SetFilePermissions(destinationFilename); + _mediaFileAttributeService.SetFilePermissions(destinationFilePath); return episodeFile; }