@ -21,6 +21,7 @@ namespace NzbDrone.Common.Disk
{
None ,
VerifyOnly ,
TryTransactional ,
Transactional
}
@ -40,7 +41,7 @@ namespace NzbDrone.Common.Disk
// 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.)
VerificationMode = OsInfo . IsWindows ? DiskTransferVerificationMode . VerifyOnly : DiskTransferVerificationMode . Tr ansactional;
VerificationMode = OsInfo . IsWindows ? DiskTransferVerificationMode . VerifyOnly : DiskTransferVerificationMode . Tr yTr ansactional;
}
public TransferMode TransferFolder ( string sourcePath , string targetPath , TransferMode mode , bool verified = true )
@ -48,11 +49,6 @@ namespace NzbDrone.Common.Disk
Ensure . That ( sourcePath , ( ) = > sourcePath ) . IsValidPath ( ) ;
Ensure . That ( targetPath , ( ) = > targetPath ) . IsValidPath ( ) ;
if ( VerificationMode ! = DiskTransferVerificationMode . Transactional )
{
verified = false ;
}
if ( ! _diskProvider . FolderExists ( targetPath ) )
{
_diskProvider . CreateFolder ( targetPath ) ;
@ -85,13 +81,15 @@ namespace NzbDrone.Common.Disk
Ensure . That ( sourcePath , ( ) = > sourcePath ) . IsValidPath ( ) ;
Ensure . That ( targetPath , ( ) = > targetPath ) . IsValidPath ( ) ;
if ( VerificationMode ! = DiskTransferVerificationMode . Transactional )
if ( VerificationMode ! = DiskTransferVerificationMode . Transactional & & VerificationMode ! = DiskTransferVerificationMode . TryTransactional )
{
verified = false ;
}
_logger . Debug ( "{0} [{1}] > [{2}]" , mode , sourcePath , targetPath ) ;
var originalSize = _diskProvider . GetFileSize ( sourcePath ) ;
if ( sourcePath = = targetPath )
{
throw new IOException ( string . Format ( "Source and destination can't be the same {0}" , sourcePath ) ) ;
@ -127,6 +125,15 @@ namespace NzbDrone.Common.Disk
return TransferMode . None ;
}
if ( sourcePath . GetParentPath ( ) = = targetPath . GetParentPath ( ) )
{
if ( mode . HasFlag ( TransferMode . Move ) )
{
TryMoveFileVerified ( sourcePath , targetPath , originalSize ) ;
return TransferMode . Move ;
}
}
if ( sourcePath . IsParentPath ( targetPath ) )
{
throw new IOException ( string . Format ( "Destination cannot be a child of the source [{0}] => [{1}]" , sourcePath , targetPath ) ) ;
@ -151,7 +158,7 @@ namespace NzbDrone.Common.Disk
{
if ( mode . HasFlag ( TransferMode . Copy ) )
{
if ( TryCopyFile ( sourcePath , targetPath ) )
if ( TryCopyFile Transactional ( sourcePath , targetPath , originalSize ) )
{
return TransferMode . Copy ;
}
@ -159,7 +166,7 @@ namespace NzbDrone.Common.Disk
if ( mode . HasFlag ( TransferMode . Move ) )
{
if ( TryMoveFile ( sourcePath , targetPath ) )
if ( TryMoveFile Transactional ( sourcePath , targetPath , originalSize ) )
{
return TransferMode . Move ;
}
@ -167,50 +174,18 @@ namespace NzbDrone.Common.Disk
throw new IOException ( string . Format ( "Failed to completely transfer [{0}] to [{1}], aborting." , sourcePath , targetPath ) ) ;
}
else if ( VerificationMode == DiskTransferVerificationMode . VerifyOnly )
else if ( VerificationMode != DiskTransferVerificationMode . None )
{
var originalSize = _diskProvider . GetFileSize ( sourcePath ) ;
if ( mode . HasFlag ( TransferMode . Copy ) )
{
try
{
_diskProvider . CopyFile ( sourcePath , targetPath ) ;
var targetSize = _diskProvider . GetFileSize ( targetPath ) ;
if ( targetSize ! = originalSize )
{
throw new IOException ( string . Format ( "File copy incomplete. [{0}] was {1} bytes long instead of {2} bytes." , targetPath , targetSize , originalSize ) ) ;
}
return TransferMode . Copy ;
}
catch
{
RollbackCopy ( sourcePath , targetPath ) ;
throw ;
}
TryCopyFileVerified ( sourcePath , targetPath , originalSize ) ;
return TransferMode . Copy ;
}
if ( mode . HasFlag ( TransferMode . Move ) )
{
try
{
_diskProvider . MoveFile ( sourcePath , targetPath ) ;
var targetSize = _diskProvider . GetFileSize ( targetPath ) ;
if ( targetSize ! = originalSize )
{
throw new IOException ( string . Format ( "File copy incomplete, data loss may have occured. [{0}] was {1} bytes long instead of the expected {2}." , targetPath , targetSize , originalSize ) ) ;
}
return TransferMode . Move ;
}
catch
{
RollbackPartialMove ( sourcePath , targetPath ) ;
throw ;
}
TryMoveFileVerified ( sourcePath , targetPath , originalSize ) ;
return TransferMode . Move ;
}
}
else
@ -310,10 +285,8 @@ namespace NzbDrone.Common.Disk
Thread . Sleep ( 3000 ) ;
}
private bool TryCopyFile ( string sourcePath , string targetPath )
private bool TryCopyFile Transactional ( string sourcePath , string targetPath , long originalSize )
{
var originalSize = _diskProvider . GetFileSize ( sourcePath ) ;
var tempTargetPath = targetPath + ".partial~" ;
if ( _diskProvider . FileExists ( tempTargetPath ) )
@ -367,10 +340,8 @@ namespace NzbDrone.Common.Disk
return false ;
}
private bool TryMoveFile ( string sourcePath , string targetPath )
private bool TryMoveFile Transactional ( string sourcePath , string targetPath , long originalSize )
{
var originalSize = _diskProvider . GetFileSize ( sourcePath ) ;
var backupPath = sourcePath + ".backup~" ;
var tempTargetPath = targetPath + ".partial~" ;
@ -423,16 +394,63 @@ namespace NzbDrone.Common.Disk
}
}
_logger . Trace ( "Hardlink move failed, reverting to copy." ) ;
if ( TryCopyFile ( sourcePath , targetPath ) )
if ( VerificationMode = = DiskTransferVerificationMode . Transactional )
{
_logger . Trace ( "Copy succeeded, deleting source." ) ;
_diskProvider . DeleteFile ( sourcePath ) ;
_logger . Trace ( "Hardlink move failed, reverting to copy." ) ;
if ( TryCopyFileTransactional ( sourcePath , targetPath , originalSize ) )
{
_logger . Trace ( "Copy succeeded, deleting source." ) ;
_diskProvider . DeleteFile ( sourcePath ) ;
return true ;
}
}
else
{
_logger . Trace ( "Hardlink move failed, reverting to move." ) ;
TryMoveFileVerified ( sourcePath , targetPath , originalSize ) ;
return true ;
}
_logger . Trace ( "Copy failed." ) ;
_logger . Trace ( " Move failed.") ;
return false ;
}
private void TryCopyFileVerified ( string sourcePath , string targetPath , long originalSize )
{
try
{
_diskProvider . CopyFile ( sourcePath , targetPath ) ;
var targetSize = _diskProvider . GetFileSize ( targetPath ) ;
if ( targetSize ! = originalSize )
{
throw new IOException ( string . Format ( "File copy incomplete. [{0}] was {1} bytes long instead of {2} bytes." , targetPath , targetSize , originalSize ) ) ;
}
}
catch
{
RollbackCopy ( sourcePath , targetPath ) ;
throw ;
}
}
private void TryMoveFileVerified ( string sourcePath , string targetPath , long originalSize )
{
try
{
_diskProvider . MoveFile ( sourcePath , targetPath ) ;
var targetSize = _diskProvider . GetFileSize ( targetPath ) ;
if ( targetSize ! = originalSize )
{
throw new IOException ( string . Format ( "File move incomplete, data loss may have occurred. [{0}] was {1} bytes long instead of the expected {2}." , targetPath , targetSize , originalSize ) ) ;
}
}
catch
{
RollbackPartialMove ( sourcePath , targetPath ) ;
throw ;
}
}
}
}