Reputation: 11999
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
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
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