It appears after much searching that there seems to be a common problem when trying to do a file copy and show a progress indicator relative to the amount of the file that has been copied. After spending some considerable time trying to resolve this issue, I find myself at the mercy of the StackOverflow Gods once again :-) - Hopefully one day I'll be among those that can help out the rookies too!
I am trying to get a progress bar to show the status of a copy process and once the copy process has finished, call a Cocoa method. The challenge - I need to make use of File Manager Carbon calls because NSFileManager does not give me the full ability I need.
I started out by trying to utilize the code on Matt Long's site Cocoa Is My Girlfriend. The code got me some good distance. I managed to get the file copy progress working. The bar updates and (with some additional searching within Apple docs) I found out how to tell if the file copy process has finished...
if (stage == kFSOperationStageComplete)
However, I have one last hurdle that is a little larger than my leap right now. I don't know how to pass an object reference into the callback and I don't know how to call a Cocoa method from the callback once finished. This is a limit of my Carbon -> Cocoa -> Carbon understanding. One of the comments on the blog said
"Instead of accessing the progress indicator via a static pointer, you can just use the void *info field of the FSFileOperationClientContext struct, and passing either the AppDelegate or the progress indicator itself."
Sounds like a great idea. Not sure how to do this. For the sake of everyone else that appears to bump into this issue and is coming from a non-Carbon background, based mostly upon the code from Matt's example, here is some simplified code as an example of the problem...
In a normal cocoa method:
CFRunLoopRef runLoop = CFRunLoopGetCurrent();
FSFileOperationRef fileOp = FSFileOperationCreate(kCFAllocatorDefault);
OSStatus status = FSFileOperationScheduleWithRunLoop(fileOp,
runLoop, kCFRunLoopDefaultMode);
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 which is in the
// original example because I needed to use the kFSPathMakeRefDefaultOptions
// to deal with file paths to remote folders via a /Volume reference
FSPathMakeRefWithOptions((const UInt8 *)[aSource fileSystemRepresentation],
kFSPathMakeRefDefaultOptions,
&source,
NULL);
Boolean isDir = true;
FSPathMakeRefWithOptions((const UInt8 *)[aDestDir fileSystemRepresentation],
kFSPathMakeRefDefaultOptions,
&destination,
&isDir);
// Needed to change from the original to use CFStringRef so I could convert
// from an NSString (aDestFile) to a CFStringRef (targetFilename)
CFStringRef targetFilename = (CFStringRef)aDestFile;
// Start the async copy.
status = FSCopyObjectAsync (fileOp,
&source,
&destination, // Full path to destination dir
targetFilename,
kFSFileOperationDefaultOptions,
statusCallback,
1.0,
NULL);
CFRelease(fileOp);
if (status) {
NSString * errMsg = [NSString stringWithFormat:@"%@ - %@",
[self class], status];
NSLog(@"Failed to begin asynchronous object copy: %@", status);
}
Then the callback (in the same file)
static void statusCallback (FSFileOperationRef fileOp,
const FSRef *currentItem,
FSFileOperationStage stage,
OSStatus error,
CFDictionaryRef statusDictionary,
void *info )
{
NSLog(@"Callback got called.");
// 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);
// fileProgressIndicator is currently declared as a pointer to a
// static progress bar - but this needs to change so that it is a
// pointer passed in via the controller. Would like to have a
// pointer to an instance of a progress bar
[fileProgressIndicator setDoubleValue:(double)floatBytesCompleted];
[fileProgressIndicator displayIfNeeded];
}
if (stage == kFSOperationStageComplete) {
NSLog(@"Finished copying the file");
// Would like to call a Cocoa Method here...
}
}
So the bottom line is how can I:
- Pass a pointer to an instance of a progress bar from the calling method to the callback
- Upon completion, call back out to a normal Cocoa method
And as always, help is much appreciated (and hopefully the answer will solve many of the issues and complaints I have seen in many threads!!)
See Question&Answers more detail:os