7

Il apparaît après de nombreuses recherches qu'il semble y avoir un problème courant lors d'une tentative de copie de fichier et affiche un indicateur de progression relatif à la quantité de fichier copiée. Après avoir passé beaucoup de temps à essayer de résoudre ce problème, je me retrouve une fois de plus à la merci des dieux StackOverflow :-) - J'espère qu'un jour je serai parmi ceux qui pourront aussi aider les novices!Affichage de la progression de la copie de fichiers à l'aide de FSCopyObjectAsync

J'essaie d'obtenir une barre de progression pour montrer l'état d'un processus de copie et une fois le processus de copie terminé, appeler une méthode Cocoa. Le défi - Je dois utiliser les appels Carbon du gestionnaire de fichiers parce que NSFileManager ne me donne pas la pleine capacité dont j'ai besoin.

J'ai commencé en essayant d'utiliser le code sur le site de Matt Long Cocoa Is My Girlfriend. Le code m'a eu une bonne distance. J'ai réussi à faire fonctionner le fichier copy progress. Les mises à jour de bar et (avec quelques recherches supplémentaires dans les documents d'Apple), j'ai découvert comment savoir si le processus de copie de fichier est terminé ...

if (stage == kFSOperationStageComplete)

Cependant, j'ai un dernier obstacle qui est un peu plus grand que mon saut en ce moment. Je ne sais pas comment passer une référence d'objet dans le rappel et je ne sais pas comment appeler une méthode Cocoa à partir du rappel une fois terminé. C'est une limite de ma compréhension du carbone -> cacao -> carbone. L'un des commentaires sur le blog dit

« Au lieu d'accéder à l'indicateur de progression via un pointeur statique, vous pouvez simplement utiliser le vide * champ d'information de la struct FSFileOperationClientContext, et en passant soit le AppDelegate ou l'indicateur de progression lui-même "

Cela semble être une excellente idée. Je ne sais pas comment faire cela. Pour le bien de tout le monde qui semble bousculer ce problème et qui provient d'un contexte non-carbone, basé principalement sur le code de l'exemple de Matt, voici un exemple de code simplifié.

dans un procédé de cacao normal:

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

Ensuite, le rappel (dans le même fichier)

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

} 

Ainsi, la ligne de fond est de savoir comment puis-je:

  1. passer un pointeur vers une instance d'une barre de progression de la méthode d'appel à la fonction de rappel
  2. À la fin, rappellerons vers une méthode normale de cacao

Et comme toujours, l'aide est très appréciée (et, espérons la réponse résoudra la plupart des problèmes et des plaintes que j'ai vu dans beaucoup de threads !!)

Répondre

7

Vous pouvez le faire en utilisant le dernier paramètre à FSCopyObjectAsync(), qui est une structure de type FSFileOperationClientContext. L'un des champs de cette structure est info, qui est un paramètre void * que vous pouvez utiliser comme bon vous semble. Tout ce que vous affectez à ce champ de la structure que vous passez en FSCopyObjectAsync() sera passé successivement à votre fonction de rappel en tant que dernier paramètre de la fonction info. Un void * peut être n'importe quoi, y compris un pointeur sur un objet, de sorte que vous pouvez l'utiliser pour passer l'instance de votre objet que vous voulez gérer le callback.

Le code de configuration ressemblerait à ceci:

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

Ensuite, dans votre fonction de rappel:

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]; 
} 
+1

Brian merci !!! Maintenant je le vois, la solution semble si simple! J'ai fini par initialiser une instance de classe pour gérer complètement le panneau de progression et j'ai passé cette instance de classe dans clientContext. J'ai été en mesure de mettre à jour la barre de progression et de faire des appels de méthode contre la classe. Je posterai une solution complète pour tous ceux qui n'auront pas suivi mes derniers commentaires quand j'aurai un moment. Merci encore Brian! – Hooligancat

+0

@Hooligancat Pouvez-vous poster votre solution complète? J'ai le même problème: je voudrais passer l'instance de classe dans le clientContext mais je ne sais pas comment. – FR6

+0

@ FR6 - Je n'ai pas touché à ce code depuis un moment, mais je vais creuser un peu et voir si je peux le trouver pour vous et afficher le code entier. Restez à l'écoute ... – Hooligancat

Questions connexes