SteAp
SteAp

Reputation: 11999

Recommended way to copy arbitrary files using Cocoa

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.

Upvotes: 4

Views: 5107

Answers (2)

Parag Bafna
Parag Bafna

Reputation: 22930

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.

Upvotes: 6

Richard J. Ross III
Richard J. Ross III

Reputation: 55563

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
}

Upvotes: 11

Related Questions