Sandboxing und "Related Items"

Diese Seite verwendet Cookies. Durch die Nutzung unserer Seite erklären Sie sich damit einverstanden, dass wir Cookies setzen. Weitere Informationen

  • Sandboxing und "Related Items"

    Hi!

    Es ist wieder soweit: Die Sommerpause nutze ich mal wieder für "konzeptionelle" Gedanken:

    Ich möchte als nächstes Projekt eine NSDocument-basierte App für den Mac App Store vorbereiten. In dieser lese / schreibe ich parallel zu den Dokumenten, die ein Benutzer in NSOpenPanel / NSSavePanel auswählt, Dateien mit Meta-Daten, die den gleichen Namen, aber eine andere Erweiterung haben ... gemäß Apple sogenannte "related items":

    Apple schrieb:


    • Related items. With the appropriate entitlement, your app can access a file with the same name as a user-specified file, but a different extension. This can be used for accessing files that are functionally related (such as a subtitle file associated with a movie) or for saving modified files in a different format (such as re-saving an RTF flat file as an RTFD container after the user added a picture).


    Cool, das ist genau, was ich brauche.

    Allerdings habe ich Dateizugriffe bisher nicht mittels NSFilePresenter / NSFileCoordinator koordiniert ... sicherlich ein Fehler, aber bisher hatte meine App eben "Glück". Für die Nutzung in der Sandbox ist ein NSFilePresenter aber obligatorisch, u. a. zum Registrieren der "related items".

    Könnt Ihr mich zu einer Dokumentation schicken, welche die Verwendung beider Klassen idealerweise an einem Beispiel erläutert? Ich habe mir zwar schon "The Role of File Coordinators and Presenters" durchgelesen, stehe aber noch auf'm Schlauch ... Vielleicht sollte ich die o. g. Datei auch in einer eigenen NSDocument-Klasse behandeln?

    Ciao, Mattes
    Diese Seite bleibt aus technischen Gründen unbedruckt.
  • Moin,

    mache das auch, habe es einfach ohne viel Nachdenken umgesetzt und es klappt. Kann gerne mal nächste Woche nachsehen und Code posten. Habe es zwar in Swift gemacht, aber das ändert nichts an der Sache. Aber irgendwie dunkel erinnere ich mich an 3 oder 4 Zeilen... es gab dabei ein Problem damit, dass manchmal der security scope limitiert war, sprich, plötzlich nix mehr aufzumachen war.

    Bei mir werden Messwerte jedoch nur ausgelesen, falls die Datei existiert. Die stehen in zwei verschiedenen Dateien mit selben Namen und eben anderen Extensions. Ohne die related items Geschichte wurden die Dateien NICHT gelesen, insofern also schon wichtig, das zu nutzen.

    volker
  • Moin,

    also ohne Gewähr, dass es die optimale/beste Lösung ist, aber seit Monaten erfolgreich im Einsatz beim Endkunden.

    Hoffe es hilft weiter! Bei Fragen melden ;)

    grüße,
    volker

    Als Variable gibt es in der NSDocument Subklasse

    Quellcode

    1. var bcCallsPresenter: BCFilePresenter?
    2. var bcCSVPresenter: BCCSVPresenter?

    Im deinit/dealloc mache ich einen teardown wenn nötig

    Quellcode

    1. if bcCallsPresenter != nil {
    2. NSFileCoordinator.removeFilePresenter(bcCallsPresenter!)
    3. }
    4. if bcCSVPresenter != nil {
    5. NSFileCoordinator.removeFilePresenter(bcCSVPresenter!)
    6. }

    Beim Lesen eines Dokuments

    Quellcode

    1. override func readFromURL(url: NSURL, ofType typeName: String) throws {
    2. // andere sachen ...
    3. if NSFileManager.defaultManager().fileExistsAtPath(self.fileURL!.URLByDeletingPathExtension!.URLByAppendingPathExtension("bcCalls").path!) {
    4. bcCallsPresenter = BCFilePresenter()
    5. bcCallsPresenter!.pFileURL = self.fileURL!
    6. NSFileCoordinator.addFilePresenter(bcCallsPresenter!)
    7. }
    8. // andere sachen ...
    9. }


    Wenn ich das related Item Speichern will...

    Quellcode

    1. @IBAction func savebcCalls(_: AnyObject) {
    2. let bcCallsContent = NSMutableArray()
    3. for aCall in callMeasures! {
    4. let callDictionary = aCall.bcCallsRepresentation
    5. bcCallsContent.addObject(callDictionary)
    6. }
    7. if bcCallsContent.count > 0 {
    8. if let fileURL = self.fileURL!.URLByDeletingPathExtension?.URLByAppendingPathExtension("bcCalls") {
    9. if self.bcCallsPresenter == nil {
    10. bcCallsPresenter = BCFilePresenter()
    11. bcCallsPresenter!.pFileURL = self.fileURL!
    12. NSFileCoordinator.addFilePresenter(bcCallsPresenter!)
    13. }
    14. let coord = NSFileCoordinator(filePresenter: self.bcCallsPresenter)
    15. let error: NSErrorPointer = NSErrorPointer()
    16. coord.coordinateWritingItemAtURL(bcCallsPresenter!.presentedItemURL!, options: NSFileCoordinatorWritingOptions(), error: error, byAccessor: { (anURL) -> Void in
    17. let result = bcCallsContent.writeToURL(fileURL, atomically: true)
    18. if !result {
    19. Swift.print(error)
    20. NSBeep()
    21. }
    22. else {
    23. self.callListController.edited = false
    24. }
    25. })
    26. }
    27. }
    28. }
    Alles anzeigen

    Und dann hab ich noch je eine Klasse für die FilePresenter


    Quellcode

    1. //
    2. // BCFilePresenter.swift
    3. // bcAnalyze3
    4. //
    5. // Created by Volker Runkel on 06.07.15.
    6. // Copyright (c) 2015 ecoObs GmbH. All rights reserved.
    7. //
    8. import Cocoa
    9. class BCFilePresenter: NSObject, NSFilePresenter {
    10. var queue: NSOperationQueue = NSOperationQueue()
    11. var pFileURL: NSURL? {
    12. didSet {
    13. tFileURL=pFileURL?.URLByDeletingPathExtension?.URLByAppendingPathExtension("bcCalls")
    14. }
    15. }
    16. var tFileURL: NSURL?
    17. var presentedItemURL: NSURL? {
    18. get {
    19. return tFileURL
    20. }
    21. }
    22. var presentedItemOperationQueue: NSOperationQueue {
    23. get {
    24. return queue
    25. }
    26. }
    27. var primaryPresentedItemURL: NSURL? {
    28. get {
    29. return pFileURL
    30. }
    31. }
    32. deinit {
    33. print("file presenter bcCalls deinitialized")
    34. }
    35. override init() {
    36. super.init()
    37. }
    38. }
    39. class BCCSVPresenter: NSObject, NSFilePresenter {
    40. var queue: NSOperationQueue = NSOperationQueue()
    41. var pFileURL: NSURL? {
    42. didSet {
    43. tFileURL=pFileURL?.URLByDeletingPathExtension?.URLByAppendingPathExtension("csv")
    44. }
    45. }
    46. var tFileURL: NSURL?
    47. var presentedItemURL: NSURL? {
    48. get {
    49. return tFileURL
    50. }
    51. }
    52. var presentedItemOperationQueue: NSOperationQueue {
    53. get {
    54. return queue
    55. }
    56. }
    57. var primaryPresentedItemURL: NSURL? {
    58. get {
    59. return pFileURL
    60. }
    61. }
    62. deinit {
    63. print("file presenter csv deinitialized")
    64. }
    65. override init() {
    66. super.init()
    67. }
    68. }
    Alles anzeigen
  • volker schrieb:


    Hoffe es hilft weiter! Bei Fragen melden ;)
    Hi Volker,

    Danke, das tut es auf jeden Fall! Ich habe jetzt nur theoretisch durch den Code geschaut und behaupte mal "Jupp, Prinzip verstanden!" :D

    Bis ich die Sache praktisch bei mir umsetze, wird es voraussichtlich Herbst werden ... momentan fallen mir keine Fragen ein, aber das mag sich dann noch ändern. Obwohl es ja eigentlich recht simpel scheint, vielleicht habe ich mich durch die Apple-Doku unnötig verwirren lassen. Ein kleines Beispiel in der Doku (oder ein Sample-Code) hätten es mir garantiert leichter gemacht, aber vielleicht habe ich den auch übersehen.

    CU, Mattes
    Diese Seite bleibt aus technischen Gründen unbedruckt.
  • Hi zusammen (insbesondere Volker),

    ich habe das Speichern des "related item" nun wie oben umgesetzt, und die Sandbox wird wie gewünscht erweitert. Etwas tricky war hierbei, dass ich das Erzeugen der NSFilePresenter / NSFileCoordinator und den Aufruf für das Schreiben des "related items" in die saveToURL:-Methode von NSDocument packen musste: Die writeToURL:-Methode arbeitet z. B. beim Speichern unter einem anderen Dateinamen mit einem temporären Verzeichnis, dann scheitert der FileCoordinator...

    Hier für den interessierten Leser die notwendigen Schritte:
    1. Definition des CFBundleDocumentTypes in der Info.plist mit NSIsRelatedItemType=true
    2. Eine eigene Subclass von NSFilePresenter, die drei Properties für die Ursprungsdatei, die "related item"-Datei und eine NSOperationQueue definiert.

      Quellcode: STBThreadFilePresenter.m

      1. #import "STBThreadFilePresenter.h"
      2. @implementation STBThreadFilePresenter
      3. - (id)initWithDesignURL:(NSURL *)designURL
      4. {
      5. if (self = [super init])
      6. {
      7. self.presentedItemURL = [[designURL URLByDeletingPathExtension] URLByAppendingPathExtension:@"threads"];
      8. self.primaryPresentedItemURL = designURL;
      9. self.presentedItemOperationQueue = [NSOperationQueue mainQueue];
      10. }
      11. return self;
      12. }
      13. - (void)dealloc
      14. {
      15. self.presentedItemURL = nil;
      16. self.primaryPresentedItemURL = nil;
      17. self.presentedItemOperationQueue = nil;
      18. [super dealloc];
      19. }
      20. @end
      Alles anzeigen



    3. Manipulationen der "related item"-Datei im Rahmen von entsprechenden NSFileCoordinator-Aufrufen, z. B. zum Schreiben:

      Quellcode

      1. STBThreadFilePresenter *filePresenter = [[STBThreadFilePresenter alloc] initWithDesignURL:designURL];
      2. [NSFileCoordinator addFilePresenter:filePresenter];
      3. NSError *coordinatorError = nil;
      4. NSFileCoordinator *fileCoordinator = [[NSFileCoordinator alloc] initWithFilePresenter:filePresenter];
      5. [fileCoordinator coordinateWritingItemAtURL:filePresenter.presentedItemURL options:NSFileCoordinatorWritingForMerging error:&coordinatorError byAccessor:^(NSURL *colorURL)
      6. {
      7. [colors writeToURL:colorURL atomically:YES];
      8. }];


    Nochmals danke für' auf Pferd helfen :D

    Mattes
    Diese Seite bleibt aus technischen Gründen unbedruckt.