【问题标题】:Displaying file copy progress using FSCopyObjectAsync使用 FSCopyObjectAsync 显示文件复制进度
【发布时间】:2011-04-16 14:26:21
【问题描述】:

经过大量搜索后,在尝试进行文件复制并显示相对于已复制文件数量的进度指示器时似乎存在一个常见问题。在花费了相当长的时间试图解决这个问题之后,我发现自己再次受到 StackOverflow 之神的摆布 :-) - 希望有一天我也能成为帮助新手的人之一!

我正在尝试获取一个进度条来显示复制过程的状态,一旦复制过程完成,调用 Cocoa 方法。挑战 - 我需要使用文件管理器 Carbon 调用,因为 NSFileManager 没有提供我需要的全部功能。

我开始尝试使用 Matt Long 的网站 Cocoa Is My Girlfriend 上的代码。代码让我距离很远。我设法使文件复制进度正常工作。栏更新并且(在 Apple 文档中进行了一些额外的搜索)我发现了如何判断文件复制过程是否已经完成......

if (stage == kFSOperationStageComplete)

但是,我还有一个比我现在的飞跃大一点的障碍。我不知道如何将对象引用传递给回调,也不知道如何在完成后从回调中调用 Cocoa 方法。这是我对 Carbon -> Cocoa -> Carbon 理解的限制。博客上的一位cmet说

“您可以使用 FSFileOperationClientContext 结构的 void *info 字段,并传递 AppDelegate 或进度指示器本身,而不是通过静态指针访问进度指示器。”

听起来是个好主意。不知道该怎么做。为了其他似乎遇到这个问题并且来自非碳背景的人,主要基于马特示例中的代码,这里有一些简化的代码作为问题的示例......

在普通的可可方法中:

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);
}

然后是回调(在同一个文件中)

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...
}

} 

所以底线是我该怎么做:

  1. 将指向进度条实例的指针从调用方法传递给回调
  2. 完成后,回调到正常的 Cocoa 方法

与往常一样,非常感谢您的帮助(希望答案能解决我在许多线程中看到的许多问题和投诉!!)

【问题讨论】:

    标签: objective-c cocoa macos-carbon nsfilemanager nsprogressindicator


    【解决方案1】:

    您可以通过使用FSCopyObjectAsync() 的最后一个参数来做到这一点,这是一个FSFileOperationClientContext 类型的结构。该结构的字段之一是info,它是一个void* 参数,您基本上可以根据需要使用它。无论您分配给传递给FSCopyObjectAsync() 的结构的该字段的任何内容,都将作为最后一个info 函数参数依次传递给您的回调函数。 void* 可以是任何东西,包括指向对象的指针,因此您可以使用它来传递要处理回调的对象实例。

    设置代码如下所示:

    FSFileOperationClientContext clientContext = {0}; //zero out the struct to begin with
    
    clientContext.info = myProgressIndicator;
    //All the other setup code
    status = FSCopyObjectAsync (fileOp,
             &source,
             &destination, // Full path to destination dir
             targetFilename,
             kFSFileOperationDefaultOptions,
             statusCallback,
             1.0,
             &clientContext);
    

    然后,在你的回调函数中:

    static void statusCallback (FSFileOperationRef fileOp,
           const FSRef *currentItem,
           FSFileOperationStage stage,
           OSStatus error,
           CFDictionaryRef statusDictionary,
           void *info )
    {
        NSProgressIndicator* fileProgressIndicator = (NSProgressIndicator*)info;
        [fileProgressIndicator setDoubleValue:(double)floatBytesCompleted];
        [fileProgressIndicator displayIfNeeded];
    }
    

    【讨论】:

    • 布莱恩谢谢!!!现在我明白了,解决方案似乎很简单!我最终初始化了一个类实例以完全处理进度面板并将该类实例传递给 clientContext。我能够更新进度条并对类进行方法调用。当我有时间时,我会为那些没有完全关注我最后一次比赛的人发布一个完整的解决方案。再次感谢布赖恩!
    • @Hooligancat 你能发布你的完整解决方案吗?我有同样的问题:我想将类实例传递给 clientContext 但我不知道如何。
    • @FR6 - 我已经有一段时间没有接触过这段代码了,但我会深入研究一下,看看能否为你找到它并发布整个代码。敬请期待……
    • @FR6 - 我知道已经有一段时间了,但我从来没有设法挖掘出这段代码。我们制作了这个项目,我已经转储了代码库。抱歉 :-( 如果我遇到它,我仍然会发布它,即使它可能对你来说有点晚了。
    • @Hooligancat 没问题,别担心。
    猜你喜欢
    • 2017-12-21
    • 1970-01-01
    • 2012-09-24
    • 1970-01-01
    • 1970-01-01
    • 2010-09-16
    • 2014-08-06
    • 2012-12-23
    相关资源
    最近更新 更多