4

I need to copy file from one OS X volume to another OS X volume. While an *.app isn't strictly speaking a file but a folder, user expect them to be a unit. Thus, if user selects a file, the app should not show its folder's contents, but copy it as a unit.

Therefore I ask, if there exists a recommended way to copy files using pure Cocoa code.

Optional: Which command line tool provides help and could be utilized by a Cocoa application.

SteAp
  • 11,853
  • 10
  • 53
  • 88

2 Answers2

11

NSFileManager is your friend:

NSError *error = nil;
if ([[NSFileManager defaultManager] copyItemAtPath:@"path/to/source" toPath:@"path/to/destination" error:&error])
{
    // copy succeeded
}
else
{
    // copy failed, print error
}
Richard J. Ross III
  • 55,009
  • 24
  • 135
  • 201
  • Thank you! Except this fact: copyItemAtPath:toPath: error: aborts, if some target file exists. Should I delete identical target files first? Other method? – SteAp Feb 27 '12 at 19:32
  • @SteAp it depends. You can delete, move, etc. with NSFileManager, so it's up to you if what would be appropriate action for that scenario would be. – Richard J. Ross III Feb 27 '12 at 19:34
  • OK, then I didn't overlook something. Another version of copyItemAtPath: with options doesn't exist. Thx again! – SteAp Feb 27 '12 at 19:36
6

You can also use FSCopyObjectAsync function. You can display file copy progress and you can also cancel file copy using FSCopyObjectAsync().
Take a look at FSFileOperation example code.

This sample shows how to copy and move both files and folders. It shows both the synchronous and asynchronous (using CFRunLoop) use of the FSFileOperation APIs. In addition, it shows path and FSRef variants of the API and how to get status out of the callbacks. The API is conceptually similar to the FSVolumeOperation APIs introduced in Mac OS X 10.2.

Example of FSCopyObjectAsync:

#import <Cocoa/Cocoa.h>


@interface AsyncCopyController : NSObject {

}
-(OSStatus)copySource : (NSString *)aSource ToDestination: (NSString *)aDestDir setDelegate : (id)object;
//delegate method
-(void)didReceiveCurrentPath : (NSString *)curremtItemPath bytesCompleted : (unsigned long long)floatBytesCompleted currentStageOfFileOperation : (unsigned long)stage;
-(void)didCopyOperationComplete : (BOOL)boolean;
-(void)didReceiveCopyError : (NSString *)Error;
-(void)cancelAllAsyncCopyOperation;
@end

 #import "AsyncCopyController.h"

static Boolean copying= YES;
@implementation AsyncCopyController


static void statusCallback (FSFileOperationRef fileOp,
                            const FSRef *currentItem,
                            FSFileOperationStage stage,
                            OSStatus error,
                            CFDictionaryRef statusDictionary,
                            void *info )
{

    NSLog(@"Callback got called. %ld", error);

    id delegate;
    if (info)
        delegate = (id)info;
    if (error!=0) {
        if (error==-48) {
            [delegate didReceiveCopyError:@"Duplicate filename and version or Destination file already exists or File found instead of folder"];
        }   



    }
    CFURLRef theURL = CFURLCreateFromFSRef( kCFAllocatorDefault, currentItem );

    NSString* currentPath = [(NSURL *)theURL path];
//  NSLog(@"currentPath %@", currentPath);
    // If the status dictionary is valid, we can grab the current values to 
    // display status changes, or in our case to update the progress indicator.

    if (statusDictionary)
    {

        CFNumberRef bytesCompleted;

        bytesCompleted = (CFNumberRef) CFDictionaryGetValue(statusDictionary,
                                                            kFSOperationBytesCompleteKey);

        CGFloat floatBytesCompleted;
        CFNumberGetValue (bytesCompleted, kCFNumberMaxType, 
                          &floatBytesCompleted);

//        NSLog(@"Copied %d bytes so far.", 
//            (unsigned long long)floatBytesCompleted);

        if (info)
            [delegate didReceiveCurrentPath :currentPath bytesCompleted :floatBytesCompleted currentStageOfFileOperation:stage];

    }
    NSLog(@"stage  %d", stage);
    if (stage == kFSOperationStageComplete) {

        NSLog(@"Finished copying the file");
        if (info)
        [delegate didCopyOperationComplete:YES];

        // Would like to call a Cocoa Method here...
    }
    if (!copying) {
        FSFileOperationCancel(fileOp);
    }

} 


-(void)cancelAllAsyncCopyOperation
{
    copying = NO;
}



-(OSStatus)copySource : (NSString *)aSource ToDestination: (NSString *)aDestDir setDelegate : (id)object
{

    NSLog(@"copySource");
    copying = YES;
    CFRunLoopRef runLoop = CFRunLoopGetCurrent();
    NSLog(@"%@", runLoop);
    FSFileOperationRef fileOp = FSFileOperationCreate(kCFAllocatorDefault);
    require(fileOp, FSFileOperationCreateFailed);
    OSStatus status = FSFileOperationScheduleWithRunLoop(fileOp, 
                                                         runLoop, kCFRunLoopDefaultMode);
    if (status) {
        NSLog(@"Failed to schedule operation with run loop: %@", status);
        return status;
    }
    require_noerr(status, FSFileOperationScheduleWithRunLoopFailed);

    if (status) {
        NSLog(@"Failed to schedule operation with run loop: %@", status);
        //return NO;
    }

    // Create a filesystem ref structure for the source and destination and 
    // populate them with their respective paths from our NSTextFields.

    FSRef source;
    FSRef destination;

    // Used FSPathMakeRefWithOptions instead of FSPathMakeRef
    // because I needed to use the kFSPathMakeRefDefaultOptions
    // to deal with file paths to remote folders via a /Volume reference

    status = FSPathMakeRefWithOptions((const UInt8 *)[aSource fileSystemRepresentation],
                             kFSPathMakeRefDefaultOptions, 
                             &source, 
                             NULL);

    require_noerr(status, FSPathMakeRefWithOptionsaSourceFailed);
    Boolean isDir = true;

    status = FSPathMakeRefWithOptions((const UInt8 *)[aDestDir fileSystemRepresentation],
                             kFSPathMakeRefDefaultOptions, 
                             &destination, 
                             &isDir);
    require_noerr(status, FSPathMakeRefWithOptionsaDestDirFailed);
    // Needed to change from the original to use CFStringRef so I could convert
    // from an NSString (aDestFile) to a CFStringRef (targetFilename)

    FSFileOperationClientContext    clientContext;


    // The FSFileOperation will copy the data from the passed in clientContext so using
    // a stack based record that goes out of scope during the operation is fine.
    if (object)
    {
        clientContext.version = 0;
        clientContext.info = (void *) object;
        clientContext.retain = CFRetain;
        clientContext.release = CFRelease;
        clientContext.copyDescription = CFCopyDescription;
    }


    // Start the async copy.

    status = FSCopyObjectAsync (fileOp,
                                &source,
                                &destination, // Full path to destination dir
                                NULL,// Use the same filename as source
                                kFSFileOperationDefaultOptions,
                                statusCallback,
                                1.0,
                                object != NULL ? &clientContext : NULL);

    //CFRelease(fileOp);
    NSLog(@"Failed to begin asynchronous object copy: %d", status);

    if (status) {

        NSString * errMsg = [NSString stringWithFormat:@" - %@", status];

        NSLog(@"Failed to begin asynchronous object copy: %d", status);
    }
    if (object)
    {
        [object release];
    }
FSFileOperationScheduleWithRunLoopFailed:
    CFRelease(fileOp);
FSPathMakeRefWithOptionsaSourceFailed:
FSPathMakeRefWithOptionsaDestDirFailed:
FSFileOperationCreateFailed:
    return status;

}

@end  

FSCopyObjectAsync is Deprecated in OS X v10.8

copyfile(3) is alternative for FSCopyObjectAsync. Here is example of copyfile(3) with Progress Callback.

Parag Bafna
  • 22,812
  • 8
  • 71
  • 144
  • FSCopyObjectAsync is not deprecated. – Parag Bafna Feb 27 '12 at 19:37
  • No, definitely not. But haven't seen a reference with such an enormous amount of... – SteAp Feb 27 '12 at 19:38
  • 2
    Take a look at [this](http://stackoverflow.com/questions/3784230/displaying-file-copy-progress-using-fscopyobjectasync) for implementation. – Parag Bafna Feb 27 '12 at 19:40
  • Thanks for the great answer. What do you do in the other methods declared in the header? Like didReceiveCurrentPath? Looking to implement this just wanted to make sure I didn't miss anything. Thanks – Westley Apr 19 '12 at 17:34
  • If you want to display current item on progress indicatore, you can use didReceiveCurrentPath method. – Parag Bafna Apr 20 '12 at 05:29
  • Would you recommend handling this copy operation on the main thread? I am having difficulty copying multiple files one at a time as fast as Finder copies (to a remote Volume). If I opened up another SO question, would you be obliged to answer? :) – Westley May 25 '12 at 21:43
  • In 10.8, the `FS*` functions are now all deprecated. The alternative is to use `copyfile` if you want to implement a progress view or use NSFileManager if you don't care about progress. – DarkDust Jan 08 '13 at 11:18
  • Alternative for iOS? As far as I understand this is only for Mac OS... – Fabiosoft Feb 17 '20 at 11:23
  • 1
    @Fabiosoft maybe https://stackoverflow.com/questions/10414802/how-to-show-the-progress-of-copying-a-large-file-in-ios will help. – Parag Bafna Feb 18 '20 at 06:30