Index: src/OFFileManager.h ================================================================== --- src/OFFileManager.h +++ src/OFFileManager.h @@ -439,13 +439,13 @@ * @brief Moves an item. * * The destination URL must have a full path, which means it must include the * name of the item. * - * If the destination is on a different logical device, the source will be - * copied to the destination using @ref copyItemAtURL:toURL: and the source - * removed using @ref removeItemAtURL:. + * If the destination is on a different logical device or uses a different + * scheme, the source will be copied to the destination using + * @ref copyItemAtURL:toURL: and the source removed using @ref removeItemAtURL:. * * @param source The item to rename * @param destination The new name for the item */ - (void)moveItemAtURL: (OFURL *)source Index: src/OFFileManager.m ================================================================== --- src/OFFileManager.m +++ src/OFFileManager.m @@ -472,17 +472,26 @@ - (void)copyItemAtURL: (OFURL *)source toURL: (OFURL *)destination { void *pool; + OF_KINDOF(OFURLHandler *) URLHandler; of_file_attributes_t attributes; of_file_type_t type; if (source == nil || destination == nil) @throw [OFInvalidArgumentException exception]; pool = objc_autoreleasePoolPush(); + + if ((URLHandler = [OFURLHandler handlerForURL: source]) == nil) + @throw [OFUnsupportedProtocolException + exceptionWithURL: source]; + + if ([URLHandler copyItemAtURL: source + toURL: destination]) + return; if ([self fileExistsAtURL: destination]) @throw [OFCopyItemFailedException exceptionWithSourceURL: source destinationURL: destination @@ -640,108 +649,68 @@ } - (void)moveItemAtPath: (OFString *)source toPath: (OFString *)destination { - if (source == nil || destination == nil) - @throw [OFInvalidArgumentException exception]; - - if ([self fileExistsAtPath: destination]) - @throw [OFMoveItemFailedException - exceptionWithSourcePath: source - destinationPath: destination - errNo: EEXIST]; - -#if defined(OF_WINDOWS) void *pool = objc_autoreleasePoolPush(); - if (_wrename([source UTF16String], [destination UTF16String]) != 0) { - if (errno != EXDEV) - @throw [OFMoveItemFailedException - exceptionWithSourcePath: source - destinationPath: destination - errNo: errno]; -#elif defined(OF_MORPHOS) - of_string_encoding_t encoding = [OFLocalization encoding]; - - if (!Rename([source cStringWithEncoding: encoding], - [destination cStringWithEncoding: encoding]) != 0) { - LONG err; - - if ((err = IoErr()) != ERROR_RENAME_ACROSS_DEVICES) { - int errNo; - - switch (err) { - case ERROR_OBJECT_IN_USE: - case ERROR_DISK_NOT_VALIDATED: - errNo = EBUSY; - break; - case ERROR_OBJECT_EXISTS: - errNo = EEXIST; - break; - case ERROR_OBJECT_NOT_FOUND: - errNo = ENOENT; - break; - case ERROR_DISK_WRITE_PROTECTED: - errNo = EROFS; - break; - default: - errNo = 0; - break; - } - - @throw [OFMoveItemFailedException - exceptionWithSourcePath: source - destinationPath: destination - errNo: errNo]; - } -#else - of_string_encoding_t encoding = [OFLocalization encoding]; - - if (rename([source cStringWithEncoding: encoding], - [destination cStringWithEncoding: encoding]) != 0) { - if (errno != EXDEV) - @throw [OFMoveItemFailedException - exceptionWithSourcePath: source - destinationPath: destination - errNo: errno]; -#endif - - @try { - [self copyItemAtPath: source - toPath: destination]; - } @catch (OFCopyItemFailedException *e) { - [self removeItemAtPath: destination]; - - @throw [OFMoveItemFailedException - exceptionWithSourcePath: source - destinationPath: destination - errNo: [e errNo]]; - } - - @try { - [self removeItemAtPath: source]; - } @catch (OFRemoveItemFailedException *e) { - @throw [OFMoveItemFailedException - exceptionWithSourcePath: source - destinationPath: destination - errNo: [e errNo]]; - } - } - -#ifdef OF_WINDOWS - objc_autoreleasePoolPop(pool); -#endif + [self moveItemAtURL: [OFURL fileURLWithPath: source] + toURL: [OFURL fileURLWithPath: destination]]; + + objc_autoreleasePoolPop(pool); } - (void)moveItemAtURL: (OFURL *)source toURL: (OFURL *)destination { - void *pool = objc_autoreleasePoolPush(); + void *pool; + OF_KINDOF(OFURLHandler *) URLHandler; + + if (source == nil || destination == nil) + @throw [OFInvalidArgumentException exception]; + + pool = objc_autoreleasePoolPush(); + + if ((URLHandler = [OFURLHandler handlerForURL: source]) == nil) + @throw [OFUnsupportedProtocolException + exceptionWithURL: source]; + + @try { + if ([URLHandler moveItemAtURL: source + toURL: destination]) + return; + } @catch (OFMoveItemFailedException *e) { + if ([e errNo] != EXDEV) + @throw e; + } + + if ([self fileExistsAtURL: destination]) + @throw [OFMoveItemFailedException + exceptionWithSourceURL: source + destinationURL: destination + errNo: EEXIST]; + + @try { + [self copyItemAtURL: source + toURL: destination]; + } @catch (OFCopyItemFailedException *e) { + [self removeItemAtURL: destination]; + + @throw [OFMoveItemFailedException + exceptionWithSourceURL: source + destinationURL: destination + errNo: [e errNo]]; + } - [self moveItemAtPath: [source fileSystemRepresentation] - toPath: [destination fileSystemRepresentation]]; + @try { + [self removeItemAtURL: source]; + } @catch (OFRemoveItemFailedException *e) { + @throw [OFMoveItemFailedException + exceptionWithSourceURL: source + destinationURL: destination + errNo: [e errNo]]; + } objc_autoreleasePoolPop(pool); } - (void)removeItemAtURL: (OFURL *)URL Index: src/OFURLHandler.h ================================================================== --- src/OFURLHandler.h +++ src/OFURLHandler.h @@ -182,8 +182,47 @@ * @param URL The URL to the item which should symbolically link to the target * @param target The target of the symbolic link */ - (void)createSymbolicLinkAtURL: (OFURL *)URL withDestinationPath: (OFString *)target; + +/*! + * @brief Tries to efficiently copy an item. If a copy would only be possible + * by reading the entire item and then writing it, it returns false. + * + * The destination URL must have a full path, which means it must include the + * name of the item. + * + * If an item already exists, the copy operation fails. This is also the case + * if a directory is copied and an item already exists in the destination + * directory. + * + * @param source The file, directory or symbolic link to copy + * @param destination The destination URL + * @return True if an efficient copy was performed, false if an efficient copy + * was not possible. Note that errors while performing a copy are + * reported via exceptions and not by returning false! + */ +- (bool)copyItemAtURL: (OFURL *)source + toURL: (OFURL *)destination; + +/*! + * @brief Tries to efficiently move an item. If a move would only be possible + * by copying the source and deleting it, it returns false. + * + * The destination URL must have a full path, which means it must include the + * name of the item. + * + * If the destination is on a different logical device or uses a different + * scheme, an efficient move is not possible and false is returned. + * + * @param source The item to rename + * @param destination The new name for the item + * @return True if an efficient move was performed, false if an efficient move + * was not possible. Note that errors while performing a move are + * reported via exceptions and not by returning false! + */ +- (bool)moveItemAtURL: (OFURL *)source + toURL: (OFURL *)destination; @end OF_ASSUME_NONNULL_END Index: src/OFURLHandler.m ================================================================== --- src/OFURLHandler.m +++ src/OFURLHandler.m @@ -170,6 +170,18 @@ - (void)createSymbolicLinkAtURL: (OFURL *)destination withDestinationPath: (OFString *)source { OF_UNRECOGNIZED_SELECTOR } + +- (bool)copyItemAtURL: (OFURL *)source + toURL: (OFURL *)destination +{ + return false; +} + +- (bool)moveItemAtURL: (OFURL *)source + toURL: (OFURL *)destination +{ + return false; +} @end Index: src/OFURLHandler_file.m ================================================================== --- src/OFURLHandler_file.m +++ src/OFURLHandler_file.m @@ -48,10 +48,11 @@ #import "OFCreateDirectoryFailedException.h" #import "OFCreateSymbolicLinkFailedException.h" #import "OFInvalidArgumentException.h" #import "OFLinkFailedException.h" +#import "OFMoveItemFailedException.h" #import "OFNotImplementedException.h" #import "OFOpenItemFailedException.h" #import "OFOutOfRangeException.h" #import "OFReadFailedException.h" #import "OFRemoveItemFailedException.h" @@ -1072,6 +1073,84 @@ # endif objc_autoreleasePoolPop(pool); } #endif + +- (bool)moveItemAtURL: (OFURL *)source + toURL: (OFURL *)destination +{ + void *pool; + + if (![[source scheme] isEqual: _scheme] || + ![[destination scheme] isEqual: _scheme]) + return false; + + if ([self fileExistsAtURL: destination]) + @throw [OFMoveItemFailedException + exceptionWithSourceURL: source + destinationURL: destination + errNo: EEXIST]; + + pool = objc_autoreleasePoolPush(); + +#if defined(OF_WINDOWS) + if (_wrename([[source fileSystemRepresentation] UTF16String], + [[destination fileSystemRepresentation] UTF16String]) != 0) + @throw [OFMoveItemFailedException + exceptionWithSourceURL: source + destinationURL: destination + errNo: errno]; +#elif defined(OF_MORPHOS) + of_string_encoding_t encoding = [OFLocalization encoding]; + + if (!Rename([[source fileSystemRepresentation] + cStringWithEncoding: encoding], + [[destination fileSystemRepresentation] + cStringWithEncoding: encoding]) != 0) { + int errNo; + + switch (IoErr()) { + case ERROR_RENAME_ACROSS_DEVICES: + errNo = EXDEV; + break; + case ERROR_OBJECT_IN_USE: + case ERROR_DISK_NOT_VALIDATED: + errNo = EBUSY; + break; + case ERROR_OBJECT_EXISTS: + errNo = EEXIST; + break; + case ERROR_OBJECT_NOT_FOUND: + errNo = ENOENT; + break; + case ERROR_DISK_WRITE_PROTECTED: + errNo = EROFS; + break; + default: + errNo = 0; + break; + } + + @throw [OFMoveItemFailedException + exceptionWithSourceURL: source + destinationURL: destination + errNo: errNo]; + } +#else + of_string_encoding_t encoding = [OFLocalization encoding]; + + if (rename([[source fileSystemRepresentation] + cStringWithEncoding: encoding], + [[destination fileSystemRepresentation] + cStringWithEncoding: encoding]) != 0) + @throw [OFMoveItemFailedException + exceptionWithSourceURL: source + destinationURL: destination + errNo: errno]; +#endif + + objc_autoreleasePoolPop(pool); + + return true; +} @end Index: src/exceptions/OFCopyItemFailedException.m ================================================================== --- src/exceptions/OFCopyItemFailedException.m +++ src/exceptions/OFCopyItemFailedException.m @@ -16,10 +16,11 @@ #include "config.h" #import "OFCopyItemFailedException.h" #import "OFString.h" +#import "OFURL.h" @implementation OFCopyItemFailedException @synthesize sourceURL = _sourceURL, destinationURL = _destinationURL; @synthesize errNo = _errNo; Index: src/exceptions/OFMoveItemFailedException.h ================================================================== --- src/exceptions/OFMoveItemFailedException.h +++ src/exceptions/OFMoveItemFailedException.h @@ -16,31 +16,33 @@ #import "OFException.h" OF_ASSUME_NONNULL_BEGIN +@class OFURL; + /*! * @class OFMoveItemFailedException \ * OFMoveItemFailedException.h ObjFW/OFMoveItemFailedException.h * * @brief An exception indicating that moving an item failed. */ @interface OFMoveItemFailedException: OFException { - OFString *_sourcePath, *_destinationPath; + OFURL *_sourceURL, *_destinationURL; int _errNo; } /*! - * @brief The original path. + * @brief The original URL. */ -@property (readonly, nonatomic) OFString *sourcePath; +@property (readonly, nonatomic) OFURL *sourceURL; /*! - * @brief The new path. + * @brief The new URL. */ -@property (readonly, nonatomic) OFString *destinationPath; +@property (readonly, nonatomic) OFURL *destinationURL; /*! * @brief The errno of the error that occurred. */ @property (readonly, nonatomic) int errNo; @@ -48,30 +50,30 @@ + (instancetype)exception OF_UNAVAILABLE; /*! * @brief Creates a new, autoreleased move item failed exception. * - * @param sourcePath The original path - * @param destinationPath The new path + * @param sourceURL The original URL + * @param destinationURL The new URL * @param errNo The errno of the error that occurred * @return A new, autoreleased move item failed exception */ -+ (instancetype)exceptionWithSourcePath: (OFString *)sourcePath - destinationPath: (OFString *)destinationPath - errNo: (int)errNo; ++ (instancetype)exceptionWithSourceURL: (OFURL *)sourceURL + destinationURL: (OFURL *)destinationURL + errNo: (int)errNo; - (instancetype)init OF_UNAVAILABLE; /*! * @brief Initializes an already allocated move item failed exception. * - * @param sourcePath The original path - * @param destinationPath The new path + * @param sourceURL The original URL + * @param destinationURL The new URL * @param errNo The errno of the error that occurred * @return An initialized move item failed exception */ -- (instancetype)initWithSourcePath: (OFString *)sourcePath - destinationPath: (OFString *)destinationPath - errNo: (int)errNo OF_DESIGNATED_INITIALIZER; +- (instancetype)initWithSourceURL: (OFURL *)sourceURL + destinationURL: (OFURL *)destinationURL + errNo: (int)errNo OF_DESIGNATED_INITIALIZER; @end OF_ASSUME_NONNULL_END Index: src/exceptions/OFMoveItemFailedException.m ================================================================== --- src/exceptions/OFMoveItemFailedException.m +++ src/exceptions/OFMoveItemFailedException.m @@ -16,43 +16,44 @@ #include "config.h" #import "OFMoveItemFailedException.h" #import "OFString.h" +#import "OFURL.h" @implementation OFMoveItemFailedException -@synthesize sourcePath = _sourcePath, destinationPath = _destinationPath; +@synthesize sourceURL = _sourceURL, destinationURL = _destinationURL; @synthesize errNo = _errNo; + (instancetype)exception { OF_UNRECOGNIZED_SELECTOR } -+ (instancetype)exceptionWithSourcePath: (OFString *)sourcePath - destinationPath: (OFString *)destinationPath ++ (instancetype)exceptionWithSourceURL: (OFURL *)sourceURL + destinationURL: (OFURL *)destinationURL errNo: (int)errNo { - return [[[self alloc] initWithSourcePath: sourcePath - destinationPath: destinationPath - errNo: errNo] autorelease]; + return [[[self alloc] initWithSourceURL: sourceURL + destinationURL: destinationURL + errNo: errNo] autorelease]; } - (instancetype)init { OF_INVALID_INIT_METHOD } -- (instancetype)initWithSourcePath: (OFString *)sourcePath - destinationPath: (OFString *)destinationPath - errNo: (int)errNo +- (instancetype)initWithSourceURL: (OFURL *)sourceURL + destinationURL: (OFURL *)destinationURL + errNo: (int)errNo { self = [super init]; @try { - _sourcePath = [sourcePath copy]; - _destinationPath = [destinationPath copy]; + _sourceURL = [sourceURL copy]; + _destinationURL = [destinationURL copy]; _errNo = errNo; } @catch (id e) { [self release]; @throw e; } @@ -60,18 +61,18 @@ return self; } - (void)dealloc { - [_sourcePath release]; - [_destinationPath release]; + [_sourceURL release]; + [_destinationURL release]; [super dealloc]; } - (OFString *)description { return [OFString stringWithFormat: - @"Failed to move item at path %@ to %@: %@", - _sourcePath, _destinationPath, of_strerror(_errNo)]; + @"Failed to move item at %@ to %@: %@", + _sourceURL, _destinationURL, of_strerror(_errNo)]; } @end