Document Manager Methods

OSDocumentManager.h

#import "OSManagedDocument.h"
#import "Utilities.h"

@interface FileRepresentation : NSObject

@property (nonatomic, readonly) NSString* fileName;
@property (nonatomic, readonly) NSURL* url;
@property (nonatomic, retain) NSURL* previewURL;
@property (nonatomic, readonly) NSString* fileDate;
@property (nonatomic, readonly) NSNumber* percentDownloaded;
@property (retain) NSNumber* isDownloaded;
@property  bool ready;

- (id)initWithFileName:(NSString*)fileName url:(NSURL*)url;
- (id)initWithFileName:(NSString*)fileName url:(NSURL*)url date:(NSString*)fileDate;
- (id)initWithFileName:(NSString*)fileName url:(NSURL*)url percentDownloaded:(NSNumber*)percent;
- (NSString*)modifiedDate;

@end

// CloudManager performs file management asynchronously so we need callbacks to let the requester know when we are done
@protocol OSFileManagerProtocol <NSObject>
@required
-(void)fileHasBeenOpened:(NSURL*)file;
-(void)fileHasBeenCreated:(NSURL*)file;
-(void)fileHasBeenDeleted:(NSURL*)file;
-(void)fileHasBeenClosed:(NSURL*)file;
@end

@interface OSDocumentManager : NSObject <OSManagedDocumentDelegate> {
    
    OSManagedDocument *_document;
    
    BOOL _useICloudStorage;

    NSArray * _localURLs;
    NSArray * _iCloudURLs;
    NSArray* _localDocuments;

    BOOL _isBusy;
    BOOL _isMigratingToICloud;
    BOOL _isMigratingToLocal;
    NSURL *_migratingFileURL;
    
    bool _deletingDocument;
    bool _creatingDocument;
    bool _creatingNewFile;

    int _migrationCounter;
    
    bool _clearFiles;  // set this to delete all files on the device on startup
    
    bool _fileIsOpen;
    
    bool _storesChanging;
    
    UIAlertView* _storesUpdatingAlert;
    UIAlertView* _cloudChoiceAlert;
    UIAlertView* _cloudChangedAlert;
    UIAlertView* _migratingAlert;

    NSManagedObjectModel *_managedObjectModel;
    
    NSMetadataQuery* _query;

}

@property (nonatomic, readonly) NSManagedObjectContext* managedObjectContext;

@property (readwrite, retain)   NSTimer *iCloudUpdateTimer;
@property (nonatomic, retain)   NSURL *storeURL;

@property (nonatomic)           BOOL isCloudEnabled;
@property (nonatomic)           BOOL deleteICloudFiles;
@property (nonatomic, readonly) NSURL* dataDirectoryURL;
@property (nonatomic, readonly) NSURL* documentsDirectoryURL;
@property (nonatomic)           NSString* ubiquityID;
@property (nonatomic, strong)   NSString *cloudPreferenceKey;
@property (nonatomic, strong)   NSString *cloudPreferenceSet;
@property (nonatomic, strong)   NSString *ubiquityContainerKey;
@property (nonatomic, strong)   NSString *ubiquityIDToken;


+ (OSDocumentManager*)sharedManager;
- (void)setUbiquityID:(NSString*)ubiquityID;
- (void)checkUserICloudPreferenceAndSetupIfNecessary;
- (void)performApplicationWillEnterForegroundCheck;

- (NSString*)getDocFilename:(NSURL *)filename local:(BOOL)local;
- (NSURL*)iCloudCoreDataURL;
- (void)listAllDocuments;
- (void)listAllFiles;
- (bool)canUseICloud;
- (bool)isICloudAvailable;
- (bool)iCloudFilesExist;
- (bool)isBusy;
- (void)fileMigrated:(NSURL*)migratedURL success:(bool)success;
- (void)deleteDocumentAtURL:(NSURL*)fileURL;
- (void)deleteLocalCopyOfiCloudDocumentAtURL:(NSURL*)fileURL;

- (void)createNewFile:(NSURL*)fileURL requester:(NSObject<OSFileManagerProtocol>*) requester;
- (void)saveDocument;
- (void)closeDocument;
- (NSMutableArray *)getFileList;

- (NSString *)getFileDate:(NSURL*)file;
- (void)postUIUpdateNotification;
- (NSString*)userFilenameForDoc;
- (NSString*)fileUUIDForDoc;

@end

NSString* const ICloudStateUpdatedNotification;

NSString* const OSFileDeletedNotification;
NSString* const OSFileCreatedNotification;
NSString* const OSFileClosedNotification;
NSString* const OSFilesUpdatedNotification;
NSString* const OSDataUpdatedNotification;

OSDocumentManager.m

#import "OSDocumentManager.h"
#import "OSManagedDocument.h"


NSString* const ICloudStateUpdatedNotification = @"ICloudStateUpdatedNotification";

NSString* const OSFileDeletedNotification = @"OSFileDeletedNotification";
NSString* const OSFileCreatedNotification = @"OSFileCreatedNotification";
NSString* const OSFileClosedNotification = @"OSFileClosedNotification";
NSString* const OSFilesUpdatedNotification = @"OSFilesUpdatedNotification";
NSString* const OSDataUpdatedNotification = @"OSCoreDataUpdated";

// The name of the file that contains the store identifier.
static NSString *DocumentMetadataFileName = @"DocumentMetadata.plist";

// The name of the file package subdirectory that contains the Core Data store when local.
static NSString *StoreDirectoryComponentLocal = @"StoreContent";

// The name of the file package subdirectory that contains the Core Data store when in the cloud. The Core Data store itself should not be synced directly, so it is placed in a .nosync directory.
static NSString *StoreDirectoryComponentCloud = @"StoreContent.nosync";

static NSString *StoreFileName = @"persistentStore";


static OSDocumentManager* __sharedManager;

// Just a class to store details of a file.
@implementation FileRepresentation

- (id)initWithFileName:(NSString *)fileName url:(NSURL *)url
{
    self = [super init];
    if (self) {
        _fileName = fileName;
        _url = url;
        _fileDate = @"";
        _percentDownloaded = nil;
    }
    
    return self;
}
- (id)initWithFileName:(NSString *)fileName url:(NSURL *)url date:(NSString*)fileDate
{
    self = [super init];
    if (self) {
        _fileName = fileName;
        _url = url;
        _fileDate = fileDate;
        _percentDownloaded = nil;
    }
    
    return self;
}
- (id)initWithFileName:(NSString *)fileName url:(NSURL *)url percentDownloaded:(NSNumber*)percent
{
    self = [super init];
    if (self) {
        _fileName = fileName;
        _url = url;
        _fileDate = @"";
        _percentDownloaded = percent;
        _fileDate = [self modifiedDate];
    }
    
    return self;
}
- (BOOL)isEqual:(FileRepresentation*)object
{
    return [object isKindOfClass:[FileRepresentation class]] && [_fileName isEqual:object.fileName];
}
- (NSString*)modifiedDate {
    NSError *er = nil;
    NSDate *date = nil;
    if ([_url getResourceValue:&date forKey:NSURLContentModificationDateKey error:&er] == YES)
    {
        NSDateFormatter *dateFormatter = [[NSDateFormatter alloc] init];
        [dateFormatter setDateStyle:NSDateFormatterMediumStyle];
        [dateFormatter setTimeStyle:NSDateFormatterMediumStyle];
        return [dateFormatter stringFromDate:date];
    } else {
        return @"" ;
    }
}
@end



@implementation OSDocumentManager
{
    BOOL _isCloudEnabled;
    NSURL* _dataDirectoryURL;
}

@synthesize isCloudEnabled = _isCloudEnabled;
@synthesize dataDirectoryURL = _dataDirectoryURL;
@synthesize ubiquityID = _ubiquityID;

+ (OSDocumentManager*)sharedManager
{
    if (!__sharedManager) {
        __sharedManager = [[OSDocumentManager alloc] init];
    }
    
    return __sharedManager;
}

- (id)init
{
    if ((self = [super init])) {
        
        [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(checkUserICloudPreferenceAndSetupIfNecessary) name:NSUbiquityIdentityDidChangeNotification object:nil];
        
        // We store everything locally
        [self updateFileStorageContainerURL];
        
        // Used during development to delete all files off a device
        //if (_clearFiles)
        //    [self clearAllFiles];
    }
    _isBusy = NO;
    return self;
}
- (void)setUbiquityID:(NSString*)ubiquityID
{
    _ubiquityID = ubiquityID;
}
/*! Posts a notification that files have been changed
 */
- (void)postFileUpdateNotification {
    [[NSNotificationCenter defaultCenter] postNotificationName:OSFilesUpdatedNotification
                                                        object:self];
}
/*! Posts a notification that file has been deleted
 */
- (void)postFileDeletedNotification {
    [[NSNotificationCenter defaultCenter] postNotificationName:OSFileDeletedNotification
                                                        object:self];
}
/*! Posts a notification that file has been deleted
 */
- (void)postFileCreatedNotification {
    [[NSNotificationCenter defaultCenter] postNotificationName:OSFileCreatedNotification
                                                        object:self];
}
/*! Posts a notification that file has been deleted
 */
- (void)postFileClosedNotification {
    [[NSNotificationCenter defaultCenter] postNotificationName:OSFileClosedNotification
                                                        object:self];
}
/*!  This method is called when the user selects whether they want to delete any files from the device in response to disabling iCloud
    for the app
 
    @param deleteICloudFiles A bool value YES or NO.  Used in combination with setIsCloudEnabled to take actions to copy files to/from iCloud
 */
- (void)setDeleteICloudFiles:(BOOL)deleteICloudFiles
{
    _deleteICloudFiles = deleteICloudFiles;
}
/*! This gets called to enable or disable use of iCloud.  This is only called when the App specific setting is changed
    because if its the global setting we don't do anything anyway.  If the global iCloud setting is turned OFF then the app 
    would already have lost access to iCloud so we can't migrate files to local storage.
    If the global iCloud setting is set to ON then we need to check if the local setting is set to ON or OFF before we do anything.
 
    When iCloud is enabled then this function triggers the sharing of any local files to iCloud by migrating the files using [PSC migratePersistentStore]
    At the same time this function will trigger the metadata scan for iCloud files so that any files in iCloud appear locally.  For these iCloud files to 
    appear locally we have to build local stores from the iCloud transaction logs using standard [PSC addPersistentStore].
 
    When iCloud option is disabled we need to migrate the iCloud stores to local stores and then remove the stores from iCloud.
 
    @param  isCloudEnabled A bool value YES or NO indicating whether iCloud is enabled or disabled.
 */
- (void)setIsCloudEnabled:(BOOL)isCloudEnabled
{
    _isCloudEnabled = isCloudEnabled;
    
    [self migrateFilesIfRequired];

}
/*! Migrates any files based on the current settings.
 
 */
- (void)migrateFilesIfRequired {
    FLOG(@"migrateFilesIfRequired called...");
    // Setting has changed to take the appropriate action
    if (_isCloudEnabled) {
        // iCloud has been enabled so migrate local files to iCloud
        //FLOG(@" iCloud has been enabled so migrate local files to iCloud");
        [self migrateLocalFilesToICloud];
    } else {
        // iCloud has been disabled so check whether to keep or delete them
        //FLOG(@" iCloud has been disabled so check if there are any iCloud files and delete or migrate them");
        
            //FLOG(@" iCloud files do exist");
            if ([self iCloudFilesExist] && _deleteICloudFiles) {
                [self deleteAllICloudFiles];
            } else {
                [self migrateICloudFilesToLocal];
            }
            

    }

}
/*! Gets the list of iCloud files and moves them to local files.
 
 */
- (void)migrateICloudFilesToLocal {
    //FLOG(@"migrateICloudFilesToLocal called");
    NSArray *docs = [self listAllICloudDocuments];
    
    if (self.isBusy) return;
    
    // Do them one at a time...
    int count = [docs count];

    if (count > 0) {
        _isMigratingToLocal = YES;
        _isMigratingToICloud = NO;
        _migrationCounter = 0;
        [self showMigratingAlert:@"from"];
        
        NSURL *fileURL = [docs objectAtIndex:0];
        FLOG(@" migrating %@ to local", [fileURL lastPathComponent]);
        _isBusy = YES;
        _migrationCounter ++;
        //FLOG(@" migration counter %d", _migrationCounter);
        
        _migratingFileURL = fileURL;
        
        [self migrateICloudFileToLocal:fileURL];
    } else {
        _isMigratingToLocal = NO;
        [self dismissMigratingAlert];
    }
}
/*! Migrates and iCloud file to a Local file by creating a OSManagedDocument
    and calling the moveDocumentToLocal method.  The document knows how to move itself.
 
    @param fileURL The URL of the file to be moved
 */
- (void)migrateICloudFileToLocal:(NSURL*)fileURL {
    //FLOG(@" migrateICloudFileToLocal called with %@", [fileURL lastPathComponent]);
        
        NSString *fileName = [[fileURL URLByDeletingPathExtension] lastPathComponent];
        
        // Now check if the file is in iCloud
        if ([fileName rangeOfString:@"_UUID_"].location != NSNotFound) {
            
            [self moveDocumentToLocal:fileURL];
        }
}

/*! This method starts the migration of all local files to iCloud, usually because the user has
    selected 'Use iCloud' in the Settings app's application specific settings page.
 
 */
- (void)migrateLocalFilesToICloud {
    //FLOG(@"migrateLocalFilesToICloud called");
    NSArray *docs = [self listAllLocalDocuments];
    
    if (self.isBusy) return;
    
    // Do them one at a time...
    int count = [docs count];
    
    if (count > 0) {
        _isMigratingToLocal = NO;
        _isMigratingToICloud = YES;
        [self showMigratingAlert:@"to"];
        
        _migrationCounter = 0;
        NSURL *fileURL = [docs objectAtIndex:0];
        FLOG(@" migrating %@ to iCloud", [fileURL lastPathComponent]);
        _isBusy = YES;
        _migrationCounter ++;
        //FLOG(@" migration counter %d", _migrationCounter);

        _migratingFileURL = fileURL;
        [self migrateLocalFileToiCloud:fileURL];
        
    } else {
        _isMigratingToICloud = NO;
        [self dismissMigratingAlert];
    }

}
/*! Migrates a Local file to iCloud file by creating a OSManagedDocument
 and calling the moveDocumentToICloud method.  The document knows how to move itself.
 
 @param fileURL The URL of the file to be moved
 */
- (void)migrateLocalFileToiCloud:(NSURL*)fileURL {
    //FLOG(@" migrateLocalFileToiCloud called with %@", [fileURL lastPathComponent]);
    
    NSString *fileName = [[fileURL URLByDeletingPathExtension] lastPathComponent];
    
    // Now check if the file is in iCloud
    if ([fileName rangeOfString:@"_UUID_"].location == NSNotFound) {
        
        [self moveDocumentToICloud:fileURL];
    }
}
/*! This is a callback called by the async process started by OSManagedDocument.moveDocumentToiCloud 
    to migrate the store to iCloud if the migration was successful.  This method then removes the local file
    and unblocks the FileListViewController file scanner by setting _isBusy to NO.
 
    @param migratedURL The URL that was to be migrated
    @param success YES if the file was successfully migrated or NO if migration failed
 */
- (void)fileMigrated:(NSURL*)migratedURL success:(bool)success {
    //FLOG(@"fileMigrated:success: called with %@", (success ? @"YES": @"NO"));
    
    if ([[migratedURL path] isEqualToString:[_migratingFileURL path]]) {
    
        FLOG(@"fileMigrated:success: called with %@", (success ? @"YES": @"NO"));
        FLOG(@" file %@", [[migratedURL URLByDeletingPathExtension] lastPathComponent]);
        
        _migrationCounter --;
        
        //FLOG(@" migration counter %d", _migrationCounter);
    
    }

    if (!success) {

        if (_migrationCounter == 0)
            _isBusy = NO;

        return;
        
    }
    
    NSString *fileName = [[migratedURL URLByDeletingPathExtension] lastPathComponent];
    
    // Check if it's a local file or an iCloud file and remove it
    if ([fileName rangeOfString:@"_UUID_"].location == NSNotFound) {
        //FLOG(@"  local file migrated");
        
        //FLOG(@"  removing local file %@", migratedURL);
        NSFileManager* fileManager = [[NSFileManager alloc] init];
        NSError *er;
        bool res = [fileManager removeItemAtURL:migratedURL error:&er];
        if (res) {
            if ([[migratedURL path] isEqualToString:[_migratingFileURL path]]) {
                FLOG(@" migrated document removed %@", migratedURL);
                if (_migrationCounter == 0)
                    _isBusy = NO;
            
                _migratingFileURL = nil;
            }
            [self postFileUpdateNotification];
            // Check if there are more
            [self migrateFilesIfRequired];
        } else {
            FLOG(@" local document could not be removed, %@", er.userInfo);
            FLOG(@" local document is %@", migratedURL);
            if (_migrationCounter == 0)
                _isBusy = NO;
        }
        
    } else {
        FLOG(@"  iCloud file migrated now remove the iCloud store");
        
        // It's an iCloud file so remove it
        [self deleteDocumentAtURL:migratedURL];

    }

}
/*! This gets called to delete a document in response to the
    user selecting DELETE or if file was migrated to local store.
    If its an iCloud document then we must call
    removeUbiquitousContentAndPersistentStoreAtURL
    otherwise just delete everything local
 
    @param fileURL The URL of the document to delete
 */
- (void)deleteDocumentAtURL:(NSURL*)fileURL {
    FLOG(@"deleteDocumentAtURL called");
    
    
    NSString *fileName = [[fileURL URLByDeletingPathExtension] lastPathComponent];
    
    // Check if its a local store and delete it
    if ([fileName rangeOfString:@"_UUID_"].location == NSNotFound) {
        //FLOG(@" Removing local store at %@", fileURL);
        NSFileManager* fileManager = [[NSFileManager alloc] init];
        NSError *er;
        bool res = [fileManager removeItemAtURL:fileURL error:&er];
        if (res) {
            //LOG(@" local document removed");
            [self postFileUpdateNotification];
            
            [self migrateFilesIfRequired];
        } else {
            //FLOG(@" error local document could not be removed, %@", er.userInfo);
        }
        
        return;
    } else {
        
        // We need to get the URL to the store
        FLOG(@" Removing iCloud store at %@", fileURL);
        
        UIManagedDocument *doc = [[UIManagedDocument alloc] initWithFileURL:fileURL];
        
        [doc setPersistentStoreOptions:@{NSPersistentStoreUbiquitousContentNameKey:fileName,
                                         NSMigratePersistentStoresAutomaticallyOption:@YES,
                                         NSInferMappingModelAutomaticallyOption:@YES,
                                         NSSQLitePragmasOption:@{ @"journal_mode" : @"DELETE" }}];
        
        if ([[NSFileManager defaultManager] fileExistsAtPath:[fileURL path]]) {
            //LOG(@" file exists so open it");
            [doc openWithCompletionHandler:^(BOOL success){
                if (!success) {
                    // Handle the error.
                    LOG(@" error opening file");
                    //return;
                }
                [self removeDocStoresAndClose:doc]; //DG
            }];
        }
    }
}
// Delete an iCloud file by doing the following:
// 1.  Call removeUbiquitousContentAndPersistentStoreAtURL
// 2.  Then just wait - someone else deletes the fileURL !!
//
// Check all instances of the file has been removed from these directories:
//  /Documents
//  /Documents/CoreDataUbiquitySupport - This is now inside UIManagedDocument
//  /Mobile Documents/CoreData
//
- (void)removeDocStoresAndClose:(UIManagedDocument *)aDoc {
    
    LOG(@"removeDocStoresAndClose called");
    
    //NSError *error;
    bool result;
    
    NSURL* fileURL = aDoc.fileURL;
    
    // get any persistentStores
    NSArray * stores =  [[[aDoc managedObjectContext] persistentStoreCoordinator] persistentStores];
    
    for (NSPersistentStore *store in stores) {
        //LOG(@" store exists so remove it");
        
        NSURL *storeURL = [[[aDoc managedObjectContext] persistentStoreCoordinator] URLForPersistentStore:store];
        //FLOG(@" store URL is %@", storeURL);
        
        NSError *error;
        NSString *fileName = [[fileURL URLByDeletingPathExtension] lastPathComponent];
        
        FLOG(@" removeUbiquitousContentAndPersistentStoreAtURL called for %@", storeURL);
        result = [NSPersistentStoreCoordinator removeUbiquitousContentAndPersistentStoreAtURL:storeURL
                                                                                      options:@{NSPersistentStoreUbiquitousContentNameKey:fileName,
                                                                                                NSMigratePersistentStoresAutomaticallyOption:@YES,
                                                                                                NSInferMappingModelAutomaticallyOption:@YES,
                                                                                                NSSQLitePragmasOption:@{ @"journal_mode" : @"DELETE" }}
                                                                                        error:&error];
        if (!result) {
            LOG(@" error removing store");
            FLOG(@" error %@, %@", error, error.userInfo);
            return ;
        } else {
            LOG(@" Core Data store removed.");
            //[self listAllDocuments];
        }
        
    }
    //LOG(@" now close the Document and remove it");
    [aDoc closeWithCompletionHandler:^(BOOL success){
        if (!success) {
            // Handle the error.
            LOG(@" error closing Document before removal");
        } else
            LOG(@" Document closed while awaiting notice to remove local copy");
        
        if ([[aDoc.fileURL path] isEqualToString:[_migratingFileURL path]]) {
            FLOG(@" migrated document removed from iCloud %@", [aDoc.fileURL lastPathComponent]);
            //if (_migrationCounter == 0)
            //    _isBusy = NO;
            //_migratingFileURL = nil;
        }
        // Just return and let our file observer see the deleted iCloud file and take action
        [self postFileUpdateNotification];
        
    }];
    
    
    return;
}
// In this case we are deleting the local copy of an iCloud document in response
// to detecting that the iCloud file has been removed. We need to do
// the following:
// 1.  Delete the local file.
//
// Unfortunately its not clear what Core Data does about removing documents
// from peer devices. The 207 video seems to indicate that Core Data does
// do something about propogating the delete but exactly what that is is
// uncertain.  So for now we will just remove any local stuff in the
// sandbox and in /CoreDataUbiquitySupport.
//
- (void)deleteLocalCopyOfiCloudDocumentAtURL:(NSURL*)fileURL {
    // We need to get the URL to the store
    FLOG(@"deleteLocalCopyOfiCloudDocumentAtURL: called with %@", fileURL);
    
    //Check is this is removing the file we are currently migrating
    
    if ([[fileURL path] isEqualToString:[_migratingFileURL path]]) {
        FLOG(@"  Completing migration");
        FLOG(@"    of file %@", fileURL);
        
    }
    
    NSString *fileName = [[fileURL URLByDeletingPathExtension] lastPathComponent];
    NSURL* documentsDirectoryURL = [[OSDocumentManager sharedManager] documentsDirectoryURL] ;
    NSURL *coreDataSupportFiles = [[documentsDirectoryURL URLByAppendingPathComponent:@"CoreDataUbiquitySupport"]
                                   URLByAppendingPathComponent:fileName];
    
    // Check if the CoreDataUbiquitySupport files exist
    if (![[NSFileManager defaultManager] fileExistsAtPath:[coreDataSupportFiles path]]) {
        LOG(@" CoreDataUbiquitySupport files do not exist");
        coreDataSupportFiles = nil;
    }
    //NSArray *files = [NSArray arrayWithObjects:fileURL, coreDataSupportFiles, nil];
    
    
    //LOG(@" remove Document");
    dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^(void) {
        NSFileCoordinator* fileCoordinator = [[NSFileCoordinator alloc] initWithFilePresenter:nil];
        
        if (coreDataSupportFiles) {
            [fileCoordinator coordinateWritingItemAtURL:coreDataSupportFiles options:NSFileCoordinatorWritingForDeleting
                                                  error:nil byAccessor:^(NSURL* writingURL) {
                                                      NSFileManager* fileManager = [[NSFileManager alloc] init];
                                                      NSError *er;
                                                      //FLOG(@" deleting %@", writingURL);
                                                      bool res = [fileManager removeItemAtURL:writingURL error:&er];
                                                      if (res) {
                                                          LOG(@"   CoreDataSupport files removed");
                                                      }
                                                      else {
                                                          LOG(@"   document NOT removed");
                                                          FLOG(@"   error %@, %@", er, er.userInfo);
                                                      }
                                                  }];
        }
        
        [fileCoordinator coordinateWritingItemAtURL:fileURL options:NSFileCoordinatorWritingForDeleting
                                              error:nil byAccessor:^(NSURL* writingURL) {
                                                  NSFileManager* fileManager = [[NSFileManager alloc] init];
                                                  NSError *er;
                                                  //FLOG(@" deleting %@", writingURL);
                                                  bool res = [fileManager removeItemAtURL:writingURL error:&er];
                                                  if (res) {
                                                      LOG(@"   document removed");
                                                      [[NSOperationQueue mainQueue] addOperationWithBlock:^ {
                                                          
                                                          if ([[fileURL path] isEqualToString:[_migratingFileURL path]]) {
                                                              FLOG(@"  Completing migration");
                                                              _migratingFileURL = nil;
                                                              _isBusy = NO;
                                                              
                                                          }
                                                          
                                                          [self postFileUpdateNotification];
                                                          
                                                          // If there are more to migrate then migrate them
                                                          // We get to this point once a file has been migrated from iCloud to local
                                                          // and the FileListViewController has detected the removal of the iCloud file, and
                                                          // deleted the local copy.
                                                          [self migrateFilesIfRequired];
                                                      }];
                                                  }
                                                  else {
                                                      LOG(@"   document NOT removed");
                                                      FLOG(@"   error %@, %@", er, er.userInfo);
                                                  }
                                              }];
        
    });
    
    return;
}

- (void)deleteAllICloudFiles {
    //FLOG(@"deleteAllICloudFiles called");
    [self listAllICloudDocuments];
}
/*! Checks for the existence of any documents with _UUID_ in the filename.  These documents are documents shared in
    iCloud.  Returns YES if any are found.
 
    @return Returns YES of any documents are found or NO if none are found.
 */
- (bool)iCloudFilesExist {
    NSURL* documentsDirectoryURL = [self documentsDirectoryURL];
    NSArray *docs = [[NSFileManager defaultManager] contentsOfDirectoryAtURL:documentsDirectoryURL includingPropertiesForKeys:nil options:0 error:nil];
    for (NSURL* document in docs) {
        NSString *name = [document lastPathComponent];
        
        // If its not an iCloud file ignore it
        if ([name rangeOfString:@"_UUID_"].location != NSNotFound) {
            //FLOG(@"  %@", [document lastPathComponent]);
            return YES;
        }
    }
    
    return NO;
}
/*! Checks for the existence of any documents without _UUID_ in the filename.  These documents are local documents only.  Returns YES if any are found.
 
 @return Returns YES of any documents are found or NO if none are found.
 */
- (bool)localFilesExist {
    NSURL* documentsDirectoryURL = [self documentsDirectoryURL];
    NSArray *docs = [[NSFileManager defaultManager] contentsOfDirectoryAtURL:documentsDirectoryURL includingPropertiesForKeys:nil options:0 error:nil];
    for (NSURL* document in docs) {
        NSString *name = [document lastPathComponent];
        
        // If its not an iCloud file ignore it
        if ([name rangeOfString:@"_UUID_"].location == NSNotFound) {
            //FLOG(@"  %@", [document lastPathComponent]);
            return YES;
        }
    }
    
    return NO;
}
/*! Lists all LOCAL DOCUMENTS on a device in the local /Document directory. LOCAL DOCUMENTS are documents that have
    filenames with no _UUID_ appended to the filename
 
    @return Returns an array of local document URLs
 */
- (NSArray*)listAllLocalDocuments {
    NSURL* documentsDirectoryURL = [self documentsDirectoryURL];
    NSArray *docs = [[NSFileManager defaultManager] contentsOfDirectoryAtURL:documentsDirectoryURL includingPropertiesForKeys:nil options:0 error:nil];
    //FLOG(@"   ");
    //FLOG(@"   ");
    //FLOG(@"  ALL LOCAL DOCUMENTS");
    //FLOG(@"  ===================");
    
    NSMutableArray *array = [[NSMutableArray alloc] init];
    
    for (NSURL* document in docs) {
        NSString *name = [document lastPathComponent];
        
        if ([name rangeOfString:@"_UUID_"].location == NSNotFound && ![name isEqualToString:@".DS_Store"]) {
            //FLOG(@"  %@", name);
            [array addObject:document];
        }
    }
    //FLOG(@"   ");
    //FLOG(@"   ");
    return array;
}
/*! Lists all ICLOUD DOCUMENTS on a device in the local /Document directory. ICLOUD DOCUMENTS are documents that have
 filenames with _UUID_ appended to the filename
 
 @return Returns an array of iCloud document URLs
 */
- (NSArray*)listAllICloudDocuments {
    NSURL* documentsDirectoryURL = [self documentsDirectoryURL];
    NSArray *docs = [[NSFileManager defaultManager] contentsOfDirectoryAtURL:documentsDirectoryURL includingPropertiesForKeys:nil options:0 error:nil];
    //FLOG(@"   ");
    //FLOG(@"   ");
    //FLOG(@"  ALL ICLOUD DOCUMENTS");
    //FLOG(@"  ====================");
    
    NSMutableArray *array = [[NSMutableArray alloc] init];
    
    for (NSURL* document in docs) {
        NSString *name = [document lastPathComponent];
        
        if ([name rangeOfString:@"_UUID_"].location != NSNotFound) {
            //FLOG(@"  %@", name);
            [array addObject:document];
        }
    }
    //FLOG(@"   ");
    //FLOG(@"   ");
    return array;
}
/*! Lists all DOCUMENTS on a device in both the local /Document directory and in the iCloud container.
    Note that DOCUMENTS are the user created UIManagedDocuments and not all the file system files. The list
    should correspond with the NSPersistentStoreUbiquitousContentNameKey used when opening the Core Data store.
 
 */
- (void)listAllDocuments {
    NSURL* documentsDirectoryURL = [self documentsDirectoryURL];
    NSArray *docs = [[NSFileManager defaultManager] contentsOfDirectoryAtURL:documentsDirectoryURL includingPropertiesForKeys:nil options:0 error:nil];
    //FLOG(@"   ");
    //FLOG(@"   ");
    //FLOG(@"  ALL LOCAL DOCUMENTS (%d)", [docs count]);
    //FLOG(@"  ====================");
    //FLOG(@" localDocuments:");
    for (NSURL* document in docs) {
        //FLOG(@"  %@", [document lastPathComponent]);
    }
    
    
    NSURL *iCloudDirectory = [self iCloudCoreDataURL];
    
    //FLOG(@"  iCloudDirectory is %@", iCloudDirectory);
    
    // If iCloud is not available then just return
    if (iCloudDirectory == nil) return;
    
    docs = [[NSFileManager defaultManager] contentsOfDirectoryAtURL:iCloudDirectory includingPropertiesForKeys:nil options:0 error:nil];
    //FLOG(@"   ");
    //FLOG(@"  ICLOUD DOCUMENTS (%d)", [docs count]);
    //FLOG(@"  =====================");
    //FLOG(@" localDocuments:");
    for (NSURL* document in docs) {
        //FLOG(@"  %@", [document lastPathComponent]);
    }
    //FLOG(@"   ");
    //FLOG(@"   ");
}
/*! Lists all FILES on a device in the apps DATA directory excluding those in the *.app directory and the DOCUMENTS in the iCloud container.
 Note that DOCUMENTS are the user created UIManagedDocuments not all the file system files.
 
 */
- (void)listAllFiles {
    NSURL* dataDirectoryURL = [NSURL fileURLWithPath:NSHomeDirectory() isDirectory:YES];
    NSArray *docs = [[NSFileManager defaultManager] contentsOfDirectoryAtURL:dataDirectoryURL includingPropertiesForKeys:nil options:0 error:nil];
    //FLOG(@"   ");
    //FLOG(@"   ");
    //FLOG(@"  ALL LOCAL DOCUMENTS (%d)", [docs count]);
    //FLOG(@"  ====================");
    //FLOG(@" localDocuments:");
    for (NSURL* document in docs) {
        //FLOG(@"  %@", [document lastPathComponent]);
        
        // Ignore the .app folder
        if (![[document lastPathComponent] isEqualToString:@"iProject2iOS.app"])
            [self listAllFilesInDirectory:document padding:@"  "];
    }
    
    // returns the iCloud container /CoreData directory URL whose root directories are the UbiquityNameKeys for all Core Data documents
    NSURL *iCloudDirectory = [[OSDocumentManager sharedManager] iCloudCoreDataURL];
    
    //FLOG(@"  iCloudDirectory is %@", iCloudDirectory);
    
    // If iCloud is not available then just return
    if (iCloudDirectory == nil) return;
    
    
    docs = [[NSFileManager defaultManager] contentsOfDirectoryAtURL:iCloudDirectory includingPropertiesForKeys:nil options:0 error:nil];
    //FLOG(@"   ");
    //FLOG(@"  ICLOUD DOCUMENTS (%d)", [docs count]);
    //FLOG(@"  =====================");
    //FLOG(@" localDocuments:");
    for (NSURL* document in docs) {
        //FLOG(@"  %@", [document lastPathComponent]);
    }
    //FLOG(@"   ");
    //FLOG(@"   ");
}
/*! Recursively lists all files
 
 @param dir The directory to list
 @param padding A string padding to indent the output depending on the level of recursion
 */
- (void)listAllFilesInDirectory:(NSURL*)dir padding:(NSString*)padding {
    
    NSArray *docs = [[NSFileManager defaultManager] contentsOfDirectoryAtURL:dir includingPropertiesForKeys:nil options:0 error:nil];
    
    for (NSURL* document in docs) {
        
        //FLOG(@" %@ %@", padding, [document lastPathComponent]);
        
        BOOL isDir;
        BOOL fileExists = [[NSFileManager defaultManager] fileExistsAtPath:document.path isDirectory:&isDir];
        
        if (fileExists && isDir) {
            [self listAllFilesInDirectory:document padding:[NSString stringWithFormat:@"  %@", padding]];
        }
        
    }
}
/*! Returns the CoreData directory in the ubiquity container
 
    @returns The URL for the CoreData directory in ubiquity container
 */
- (NSURL*)iCloudCoreDataURL {
    NSURL *iCloudURL = [[NSFileManager defaultManager] URLForUbiquityContainerIdentifier:_ubiquityID];
    return [iCloudURL URLByAppendingPathComponent:@"CoreData"];
}
/*! Checks for the existence of a document in the iCloud directory
 
 @params docName The document name.
 @returns Returns YES if a document with the same name is found and NO if not.
 */
- (BOOL)docNameExistsInICloudURLs:(NSString *)docName {
    BOOL nameExists = NO;
    //FLOG(@" checking for %@", docName);
    NSURL *iCloudURL = [[NSFileManager defaultManager] URLForUbiquityContainerIdentifier:_ubiquityID];
     NSURL *iCloudDocumentsURL = [iCloudURL URLByAppendingPathComponent:@"Documents"];
    _iCloudURLs = [[NSFileManager defaultManager] contentsOfDirectoryAtURL:iCloudDocumentsURL includingPropertiesForKeys:nil options:0 error:nil];
    for (NSURL * fileURL in _iCloudURLs) {
        //FLOG(@" iCloud file: %@", [fileURL lastPathComponent]);
        if ([[fileURL lastPathComponent] isEqualToString:docName]) {
            nameExists = YES;
            break;
        }
    }
    return nameExists;
}
/*! Checks for the existence of a document in the local directory
 
    @params docName The document name.
    @returns Returns YES if a document with the same name is found and NO if not.
 */
- (BOOL)docNameExistsInLocalURLs:(NSString *)docName {
    BOOL nameExists = NO;
    for (NSURL * fileURL in _localURLs) {
        if ([[fileURL lastPathComponent] isEqualToString:docName]) {
            nameExists = YES;
            break;
        }
    }
    return nameExists;
}

/*! Check if filename exists locally or in iCloud and produce a unique name
 
    @params filename The filename to check
    @params local YES if the search is to be in the local directory or NO if to search in the iCloud directory
 
    @returns Returns a new filename
 */
- (NSString*)getDocFilename:(NSURL *)filename local:(BOOL)local {
    //FLOG(@"getDocFilename:local: called with %@", filename);
    NSInteger docCount = 0;
    NSString* newDocName = nil;
    NSString * extension = [filename pathExtension];
    NSString * prefix = [[filename lastPathComponent] stringByDeletingPathExtension];
    
    // At this point, the document list should be up-to-date.
    BOOL done = NO;
    BOOL first = YES;
    while (!done) {
        if (first) {
            first = NO;
            newDocName = [NSString stringWithFormat:@"%@.%@",
                          prefix, extension];
        } else {
            newDocName = [NSString stringWithFormat:@"%@ %d.%@",
                          prefix, docCount, extension];
        }
        //FLOG(@" newDocName is %@", newDocName);
        
        // Look for an existing document with the same name. If one is
        // found, increment the docCount value and try again.
        BOOL nameExists;
        if (local) {
            nameExists = [self docNameExistsInLocalURLs:newDocName];
        } else {
            nameExists = [self docNameExistsInICloudURLs:newDocName];
        }
        if (!nameExists) {
            break;
        } else {
            docCount++;
        }
        
    }
    
    return newDocName;
}
/*! Returns the /Documents directory for the app
 
    @returns Returns the URL for the apps /Documents directory
 */
- (NSURL*)documentsDirectoryURL
{
    _dataDirectoryURL = [NSURL fileURLWithPath:NSHomeDirectory() isDirectory:YES];
    return [_dataDirectoryURL URLByAppendingPathComponent:@"Documents"];
}

- (void)updateFileStorageContainerURL
{
    // Perform the asynchronous update of the data directory and document directory URLs
    _dataDirectoryURL = nil;
    // We always store files locally
        
    // Otherwise just get the local documents directory
    _dataDirectoryURL = [NSURL fileURLWithPath:NSHomeDirectory() isDirectory:YES];
}

/*! Called to determine whether the user preference "Use iCloud" has been set to YES and iCloud
    is available (user logged in and Documents & Data ON).  If this returns YES then create new files
    with UUID and iCloud options.  If this returns NO create new files without UUID or iCloud options.
 
    @return Returns YES if both app iCloud "Use iCloud" preferences is YES and iCloud is available.
 */
- (bool)canUseICloud {
    
    // Get the user preference setting
    NSUserDefaults* userDefaults = [NSUserDefaults standardUserDefaults];
    //NSString* userICloudChoiceSet = [userDefaults stringForKey:self.cloudPreferenceSet];
    
    bool userICloudChoice = [userDefaults boolForKey:self.cloudPreferenceKey];
    //FLOG(@" User preference for %@ is %@", self.cloudPreferenceSet, (userICloudChoice ? @"YES" : @"NO"));
    
    if ([[NSFileManager defaultManager] ubiquityIdentityToken] && userICloudChoice)
        return YES;
    else
        return NO;
    
}
/*! Called to determine whether iCloud is available (user logged in and Documents & Data ON).
 
 @return Returns YES if iCloud account is active and Data & Documents is ON
 */
- (bool)isICloudAvailable {
    
    
    if ([[NSFileManager defaultManager] ubiquityIdentityToken])
        return YES;
    else
        return NO;
    
}
- (bool)isBusy {
    return _isBusy;
}
/*! Gets the list of files in the iCloud directory and compares against files we have locally
 and then creates new ones if required.  WARNING: files appear in iCloud before they appear
 locally when we are creating new documents so we must set a flag so we know to ignore any
 new files that appear if we are busy creating a new document already.  Only applies if we
 have initiated creating a new document on this device!
 
 @return Returns true if any have been found
 */
- (bool)getNewiCloudDocs {
    FLOG(@"getNewiCloudDocs called");
    bool found = NO;
    if (self.isBusy) {
        FLOG(@"  CloudManager is busy");
        return NO;
    }
    NSURL *iCloudDirectory = [[OSDocumentManager sharedManager] iCloudCoreDataURL];
    
    //FLOG(@"  iCloudDirectory is %@", iCloudDirectory);
    
    // If iCloud is not available then just return
    if (iCloudDirectory == nil) return NO;
    
    if (![[OSDocumentManager sharedManager] canUseICloud]) return NO;
    
    // Get the top level directory names are these will represent all the iCloud documents
    NSArray* cloudDocuments = [[NSFileManager defaultManager] contentsOfDirectoryAtURL:iCloudDirectory includingPropertiesForKeys:nil options:0 error:nil];
    
    //Now get an array of the local document names including any UUID because we want then to be unique
    NSMutableArray *localFileIDs = [[NSMutableArray alloc] init];
    
    for (NSURL *doc in _localDocuments) {
        [localFileIDs addObject:[doc lastPathComponent]];
    }
    
    // Check if there are any we don't already have
    //FLOG(@"  iCloud Documents are:");
    for (NSURL* document in cloudDocuments) {
        //FLOG(@"   %@", [document lastPathComponent]);
        
        NSString *name = [document lastPathComponent];
        
        if (![localFileIDs containsObject:name] && ![name isEqualToString:@".DS_Store"]) { // .DS_Store appears in simulator if Filemanager gets used!
            FLOG(@"  new file found: %@", name);
            found = YES;
            [self createLocalCopyOfICloudFile: name];
            
        }
    }
    return found;
}
/*! Looks for files we have that are not in iCloud and are local.
 These have probably been deleted by some other device so remove them.
 Except for the case when we have just turned iCloud option ON, in which case local files
 were probably created while iCloud was off so leave these for Core Data to handle, but how
 do we know which are which?
 
 @return Returns true if any have been found
 */
- (bool)getDeletediCloudDocs {
    
    bool found = NO;
    
    NSURL *iCloudDirectory = [[OSDocumentManager sharedManager] iCloudCoreDataURL];
    
    if (_isMigratingToICloud) return NO;
    
    // If iCloud is not available then just return
    if (iCloudDirectory == nil) {
        return NO;
    }
    
    // If the user has set Use iCloud to NO and iCloud is available
    // then we still check for iCloud files that have been removed
    // Change this to return NO if necessary
    if (![[OSDocumentManager sharedManager] canUseICloud]) {
        //FLOG(@" canUseICloud is NO so skip the check");
        //return NO;
    }
    
    
    //Now get an array of the iCloud document names including any UUID because we want then to be unique
    NSMutableArray *cloudFileIDs = [[NSMutableArray alloc] init];
    
    // Get the top level directory names as these will represent all the iCloud documents
    NSArray* cloudDocuments = [[NSFileManager defaultManager] contentsOfDirectoryAtURL:iCloudDirectory includingPropertiesForKeys:nil options:0 error:nil];
    
    if ([cloudDocuments count]) {
        
        //FLOG(@" %d documents found in iCloud", [cloudDocuments count]);
        //FLOG(@"   docs are: %@", cloudDocuments);
        
        for (NSURL* doc in cloudDocuments) {
            [cloudFileIDs addObject:[doc lastPathComponent]];
        }
        
        // Check if there are documents that we have that are not in iCloud
        //FLOG(@"  Deleted iCloud Documents are:");
        for (NSURL* document in _localDocuments) {
            NSString *name = [document lastPathComponent];
            
            // If its not an iCloud file ignore it
            if ([name rangeOfString:@"_UUID_"].location != NSNotFound) {
                
                if (![cloudFileIDs containsObject:name]) {
                    FLOG(@"  REMOVED iCloud file detected: %@", name);
                    found = YES;
                    
                    [self removeLocalCopyOfICloudFile: name];
                    
                }
            }
        }
    } else {
        
        //FLOG(@" no documents found in iCloud");
        //FLOG(@" removing all local iCloud documents");
        
        for (NSURL* document in _localDocuments) {
            NSString *name = [document lastPathComponent];
            //FLOG(@"  removing local file %@", name);
            found = YES;
            
            // If its an iCloud file remove it
            if ([name rangeOfString:@"_UUID_"].location != NSNotFound) {
                [self removeLocalCopyOfICloudFile: name];
            }
        }
        
    }
    
    return found;
}
/*! This gets called when we find an iCloud document that does not exist locally. Check if
 we are already busy creating a document and skip until free.  We get an async callback from
 CloudManager when a creation request is completed.  We want to avoid creating multiple
 files at once because Core Data seems to struggle.  Maybe we could use queue's to queue
 these requests but for now we just wait until the next scan is done.  Another scan will be
 triggered in the callback and by the metadata query that is running,
 
 @param fileName The fileName for the document to be created
 */
- (void)createLocalCopyOfICloudFile:(NSString *)fileName {
    
    if (!_creatingDocument) {
        LOG(@"  Not busy creating document so create the document!");
        NSURL* fileURL = [[[OSDocumentManager sharedManager] documentsDirectoryURL] URLByAppendingPathComponent:fileName];
        
        [self createLocalCopyOfCloudFile:fileURL];
        
    } else {
        LOG(@"  Busy creating document so don't do anything!");
    }
}

/*! Called when a deleted iCloud file is detected to remove the local copy. Checks if a delete request is currently busy
 and if not forwards the delete request to CloudManager
 
 @param fileName The filename of the file that has been removed
 */
- (void)removeLocalCopyOfICloudFile:(NSString *)fileName {
    //LOG(@"");
    if (!_deletingDocument) {
        FLOG(@"  REMOVING local copy of DELETED iCloud file: %@", fileName);
        NSURL* fileURL = [[self documentsDirectoryURL] URLByAppendingPathComponent:fileName];
        [self deleteLocalCopyOfiCloudDocumentAtURL:fileURL];
        //LOG(@"");
    } else {
        //LOG(@"Busy deleting document so don't do anything!");
    }
}
- (void)createLocalCopyOfCloudFile:(NSURL*)fileName {
    // Don't do this if we are busy because
    // creating a new file results in us seeing the
    // iCloud files before we have created our local copies
    // Ideally we want to block this creation in a single
    // atomic transaction so we don't see the iCloud
    // file before the local file
    if (!_creatingNewFile) {
        LOG(@" _creatingNewFile is NO");
        _creatingNewFile = YES;
        [self createNewFileFromCloud:fileName];
    } else {
        LOG(@" _creatingNewFile is YES");
    }
}
/*! This gets called when the user has done one of the following:
 1.  Created a new file and entered a new file name.
 a) If iCloud is ON the we have then created the fileURL
 using the /Documents directory, the filename and appending '_UUID_'+uuid to ensure that we
 avoid duplicate file names in case the user used the same file name on another device.
 b) If iCloud is off then we create the fileURL using the /Documents directory and the filename
 only. This is so that if the use later turns iCloud on then we can move these files to iCloud
 by creating a new file with UUID and using migratePersistentStore API.
 2.  Selected an existing file from the file browser
 */
- (void)createNewFile:(NSURL*)fileURL requester:(NSObject<OSFileManagerProtocol>*) requester {
    
    //FLOG(@"createNewFile called with url %@", fileURL);
    _fileIsOpen = NO;
    _creatingNewFile = YES; // Ignore any file metadata scan events coming in while we do this because some iCloud
    // files get created by Core Data before the local files are created and our scanning
    // picks up new iCloud files and attempts to create local copies and we don't want this
    // if this devices is busy creating the new iCloud file
    
    
    _document = [[OSManagedDocument alloc] initWithFileURL:fileURL];
    
    // Set oberving on this file to monitor the state (we don't use it for anything other than debugging)
    NSNotificationCenter *center = [NSNotificationCenter defaultCenter];
    [center addObserver:self
               selector:@selector(documentStateChanged:)
                   name:UIDocumentStateChangedNotification
                 object:_document];
    
    //_openedVersion = [NSFileVersion currentVersionOfItemAtURL:fileURL];
    //_openedVersionDate = _openedVersion.modificationDate;
    //_openedVersionDevice = _openedVersion.localizedNameOfSavingComputer;
    //FLOG(@" file  version date: %@", _openedVersionDate);
    //FLOG(@" file  version device: %@", _openedVersionDevice);
    
    NSString *fileName = [[fileURL URLByDeletingPathExtension] lastPathComponent];
    
    // Now check if the file is in iCloud
    if ([fileName rangeOfString:@"_UUID_"].location != NSNotFound) {
        
        // Set iCloud options
        [_document setPersistentStoreOptions:@{NSPersistentStoreUbiquitousContentNameKey:fileName,
                                               NSMigratePersistentStoresAutomaticallyOption:@YES,
                                               NSInferMappingModelAutomaticallyOption:@YES,
                                               NSSQLitePragmasOption:@{ @"journal_mode" : @"DELETE" }}];
        
    } else {
        
        // Local only options
        [_document setPersistentStoreOptions:@{NSMigratePersistentStoresAutomaticallyOption:@YES,
                                               NSInferMappingModelAutomaticallyOption:@YES,
                                               NSSQLitePragmasOption:@{ @"journal_mode" : @"DELETE" }}];
        
    }
    
    if (_document.managedObjectContext) {
        FLOG(@" setting merge policy");
        // Set the MergePolicy to prioritise external inputs
        NSMergePolicy * mergePolicy = [[NSMergePolicy alloc] initWithMergeType:NSMergeByPropertyStoreTrumpMergePolicyType];
        [_document.managedObjectContext setMergePolicy:mergePolicy];
    }
    [self registerForStoreChanges:nil];
    
    
    if ([[NSFileManager defaultManager] fileExistsAtPath:[fileURL path]]) {
        //FLOG(@" file exists so open it: %@",fileURL);
        [_document openWithCompletionHandler:^(BOOL success){
            if (!success) {
                // Handle the error.
                LOG(@" error creating file");
            } else {
                //LOG(@" file opened");
                _fileIsOpen = YES;
                _creatingNewFile = NO;  // OK we are done, so let metaData scanning go ahead as normal
                
                if (requester)
                    [requester fileHasBeenOpened:fileURL];  // Now initialise the UI and let the user continue...
            }
        }];
        
    }
    else {
        // File does not exist so that means the user has created a new one and we need to
        // load some initialisation data into the Core Data store (codes tables, etc.)
        //
        // At this stage we have a database in memory so we can just use the _document.managedObjectContext
        // to add objects prior to attempting to write to disk.
        
        //LOG(@" file DOES NOT exist so add initial data");
        [self addInitialData];
        
        // Just checking if anything has been written to disk, nothing should not exist on disk yet.
        // Debugging use only
        if ([[NSFileManager defaultManager] fileExistsAtPath:[fileURL path]])
            LOG(@" file exists but keep going anyway :-(");
        else
            LOG(@" file still does not exist :-)");
        
        
        // OK now save a copy to disk using UIManagedDocument
        // NOTE: the iCloud files are written before the UIManagedDocument.fileURL, presumably because Core Data does this setup
        // in response to the [moc save:].  Make sure we don't pick this up in our iCloud metaData scan and attempt to create
        // it as if it were a new iCloud file created by some other device.
        //
        [_document saveToURL:_document.fileURL forSaveOperation:UIDocumentSaveForCreating completionHandler:^(BOOL success){
            if (!success) {
                // Handle the error.
                LOG(@" error saving file :-(");
            }
            
            // We close the file and wait for it to appear in the file browser and then
            // let the user select it from the browser to open it and start using it.
            // Skip this if you want to open it directly and [self fileopened] but
            // bear in mind the fileListController will not be correctly set up when we return to it.
            
            [_document closeWithCompletionHandler:^(BOOL success){
                if (!success) {
                    // Handle the error.
                    LOG(@" error closing file after creation :-(");
                    FLOG(@" file URL is %@", [fileURL path]);
                } else {
                    FLOG(@" file closed %@", fileName);
                    
                    _creatingNewFile = NO;  // OK we are done, so let metaData scanning go ahead as normal
                    _fileIsOpen = NO;
                    // Tell our UITableView file list that we are done and trigger scanning of local and iCloud files
                    // The fileListController will the add the new file itself and the user will then pick the
                    // file from this list in order to open it.
                    
                    // To open the file automatically use the callback in the fileListController
                    // to select and then open the file so it looks seamless to the user.
                    if (requester)
                        [requester fileHasBeenCreated:fileURL];
                    
                    
                    // Stop observing now
                    [center removeObserver:self
                                      name:UIDocumentStateChangedNotification
                                    object:_document];
                    
                }
            }];
        }];
    }
}
// This method gets called when we detect a new file in iCloud
// that does not exist locally (from metaData scans).
// Here we do the following:
// 1.  Create a UIManagedDocument in the local sandbox (see WWDC2013 video 207 recommendation)
// 2.  Set up the persistent store options
// 3.  Check if the file exists (it should not or we would'nt be here)
// 4.  Save the UIManagedDocument with the SaveForCreating option
// 5.  And then clean up by closing the document...
//
// Not 100% sure what activity Core Data gets up to, we should also consider
// opening the document to get the stores built in the background, before the
// user even tries to open the document.  But it seems Core Data does this
// even when we close the document.  Maybe just run this whole thing on a background
// thread and monitor for store changes and then activate after the store is switched
// to the iCloud store (well the local store that is synced with iCloud)
//
- (void)createNewFileFromCloud:(NSURL*)fileURL {
    FLOG(@"createNewFileFromCloud called with url %@", fileURL);
    
    // Create a new Document
    OSManagedDocument *doc = [[OSManagedDocument alloc] initWithFileURL:fileURL];
    NSNotificationCenter *center = [NSNotificationCenter defaultCenter];
    [center addObserver:self
               selector:@selector(documentStateChanged:)
                   name:UIDocumentStateChangedNotification
                 object:doc];
    
    // If its from iCloud then the fileName is the NSPersistentStoreUbiquitousContentNameKey
    // The fileName is = userEnteredFilename + '_UUID_' + uuid to guarantee it is unique in iCloud
    NSString *fileName = [[fileURL URLByDeletingPathExtension] lastPathComponent];
    
    // Set up persistentStore so Core Data can find the iCloud stuff
    // The only thing we need is NSPersistentStoreUbiquitousContentNameKey
    // We don't set the log directory because Core Data will do all of this
    // better than we can.  May be required for legacy apps
    [doc setPersistentStoreOptions:@{NSPersistentStoreUbiquitousContentNameKey:fileName,
                                     NSMigratePersistentStoresAutomaticallyOption:@YES,
                                     NSInferMappingModelAutomaticallyOption:@YES,
                                     NSSQLitePragmasOption:@{ @"journal_mode" : @"DELETE" }}];
    
    // Check if the file exists, it should'nt so if it does what are we going to do?
    if ([[NSFileManager defaultManager] fileExistsAtPath:[fileURL path]]) {
        LOG(@" WARNING: creating a local copy of iCloud file and file exists when it should not :-(");
        FLOG(@" file URL is %@", [fileURL path]);
        // We should probably delete it first!! and then continue below - for later, if we ever see this
    }
    else {
        // Create the file BUT do NOT initialise with any data because if it comes from iCloud then
        // some other device must have done this already.
        // Just create it and when we open it Core Data will get transaction logs from iCloud and
        // import them.
        LOG(@" file DOES NOT exist so we create it");
        
        [doc saveToURL:doc.fileURL forSaveOperation:UIDocumentSaveForCreating completionHandler:^(BOOL success){
            if (!success) {
                // Handle the error.
                LOG(@" error creating local copy of iCloud file");
                FLOG(@" file URL is %@", [fileURL path]);
            } else {
                LOG(@" file created");
                // Now lets close and quite.  Note that even though we do this
                // Core Data may continue importing data from iCloud if the App remains active.
                // Strange behaviour indeed, but not unwelcome because this reduces the
                // setup time when the user does open the file.
                // Can't seem to find any hooks to be able to monitor Core Data's actual progress
                // so we can disable the item until the import process has finished.  We don't really
                // want the user to access the file when it is in an uninitialised state.
                // Remember Core Data will return immediately with the fallback store.
                [doc closeWithCompletionHandler:^(BOOL success){
                    if (!success) {
                        // Handle the error.
                        LOG(@" error closing file after creation");
                        FLOG(@" file URL is %@", [fileURL path]);
                    } else {
                        FLOG(@" file closed %@", fileName);
                        _creatingNewFile = NO;
                        [center removeObserver:self
                                          name:UIDocumentStateChangedNotification
                                        object:doc];
                    }
                }];
            }
        }];
    }
}
// We only care if the one we have open is changing
- (void)registerForStoreChanges:(NSPersistentStoreCoordinator*)storeCoordinator {
    //LOG(@"registerForStoreChanges called");
    [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(storesWillChange:) name:NSPersistentStoreCoordinatorStoresWillChangeNotification object:storeCoordinator];
    [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(storesDidChange:) name:NSPersistentStoreCoordinatorStoresDidChangeNotification object:storeCoordinator];
    [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(storesDidImport:) name:NSPersistentStoreDidImportUbiquitousContentChangesNotification object:storeCoordinator];
    
}
- (void)deregisterForStoreChanges {
    //LOG(@"degisterForStoreChanges called");
    [[NSNotificationCenter defaultCenter] removeObserver:self  name:NSPersistentStoreCoordinatorStoresWillChangeNotification object:nil];
    [[NSNotificationCenter defaultCenter] removeObserver:self name:NSPersistentStoreCoordinatorStoresDidChangeNotification object:nil];
    [[NSNotificationCenter defaultCenter] removeObserver:self name:NSPersistentStoreDidImportUbiquitousContentChangesNotification object:nil];
    
}
// NB - this may be called from a background thread so make sure we run on the main thread !!
// This is when store files are being switched from fallback to iCloud store
- (void)storesWillChange:(NSNotification*)n {
    
    [[NSOperationQueue mainQueue] addOperationWithBlock:^ {
        
        FLOG(@"storesWillChange called - >>>>>>>>>>>>>>>>>>>>>>>>>>>>");
        
        NSArray *addedStores = [n.userInfo objectForKey:NSAddedPersistentStoresKey];
        NSArray *removedStores = [n.userInfo objectForKey:NSRemovedPersistentStoresKey];
        NSArray *changedStores = [n.userInfo objectForKey:NSUUIDChangedPersistentStoresKey];
        
        FLOG(@" added stores are %@", addedStores);
        FLOG(@" removed stores are %@", removedStores);
        FLOG(@" changed stores are %@", changedStores);
        
        NSError *error;
        if ([self.managedObjectContext hasChanges]) {
            [self.managedObjectContext save:&error];
        }
        
        [self.managedObjectContext reset];
        // Reset user Interface - i.e. lock the user out!
        _storesChanging = YES;
        
        //DG Turn this off for now
        //[self showStoresChangingAlert];
    }];
    
}
// NB - this may be called from a background thread so make sure we run on the main thread !!
// This is when store files are being switched from fallback to iCloud store
- (void)storesDidChange:(NSNotification*)n {
    [[NSOperationQueue mainQueue] addOperationWithBlock:^ {
        
        FLOG(@"storesDidChange called - >>>>>>>>>>>>>>>>>>>>>>>>>>>>");
        _storesChanging = NO;
        
        // Refresh user Interface
        [self createTimer];
        
        if (_storesUpdatingAlert)
            [_storesUpdatingAlert dismissWithClickedButtonIndex:0 animated:YES];
    }];
}
// NB - this may be called from a background thread so make sure we run on the main thread !!
// This is when transactoin logs are loaded
- (void)storesDidImport:(NSNotification*)notification {
    
    [[NSOperationQueue mainQueue] addOperationWithBlock:^ {
        FLOG(@"storesDidImport ");
        [self createTimer];
        /* Process new/changed objects here and remove any unwanted items or duplicates prior to merging with current context
         //
         NSSet *updatedObjectIDs = [[notification userInfo] objectForKey:NSUpdatedObjectsKey];
         NSSet *insertedObjectIDs = [[notification userInfo] objectForKey:NSInsertedObjectsKey];
         NSSet *deletedObjectIDs = [[notification userInfo] objectForKey:NSDeletedObjectsKey];
         
         // Iterate over all the new, changed or deleted ManagedObjectIDs and get the NSManagedObject for the corresponding ID:
         // These come from another thread so we can't reference the objects directly
         
         for(NSManagedObjectID *managedObjectID in updatedObjectIDs){
         NSManagedObject *managedObject = [_managedObjectContext objectWithID:managedObjectID];
         }
         
         // Check is some object is equal to an inserted or updated object
         if([myEntity.objectID isEqual:managedObject.objectID]){}
         */
        if (_document) {
            [_document.managedObjectContext mergeChangesFromContextDidSaveNotification:notification];
        }
        
    }];
}
- (void)createTimer {
    if (self.iCloudUpdateTimer == nil) {
        self.iCloudUpdateTimer = [NSTimer scheduledTimerWithTimeInterval:1.0
                                                                  target:self
                                                                selector:@selector(notifyOfCoreDataUpdates)
                                                                userInfo:nil
                                                                 repeats:NO];
    } else {
        [self.iCloudUpdateTimer setFireDate:[NSDate dateWithTimeIntervalSinceNow:1.0]];
    }
}
- (void)notifyOfCoreDataUpdates {
    [self.iCloudUpdateTimer invalidate];
    self.iCloudUpdateTimer = nil;
    if (_fileIsOpen)
        [self postUIUpdateNotification];
}
- (void)postUIUpdateNotification {
    [[NSNotificationCenter defaultCenter] postNotificationName:OSDataUpdatedNotification
                                                        object:self];
}
- (void)documentStateChanged:(NSNotification*)notification
{
    switch ([[notification object] documentState]) {
        case UIDocumentStateNormal:
            //FLOG(@"UIDocumentStateNormal");
            break;
        case UIDocumentStateClosed:
            FLOG(@"UIDocumentStateClosed %@", notification);
            break;
        case UIDocumentStateInConflict:
            FLOG(@"UIDocumentStateInConflict %@", notification);
            break;
        case UIDocumentStateSavingError:
            FLOG(@"UIDocumentStateSavingError %@", notification);
            break;
        case UIDocumentStateEditingDisabled:
            FLOG(@"UIDocumentStateEditingDisabled %@", notification);
            break;
    }
}
- (void)addInitialData {
    LOG(@"addInitialData called");
    if (_document) {
        Utilities *utility = [[Utilities alloc] initWithManagedObjectContext:_document.managedObjectContext];
        [utility initialiseDetails];
    }
}

- (NSString *)getFileDate:(NSURL*)file
{
    NSError *er = nil;
    NSDate *date = nil;
    [file getResourceValue:&date forKey:NSURLContentModificationDateKey error:&er];
    NSDateFormatter *dateFormatter = [[NSDateFormatter alloc] init];
    [dateFormatter setDateStyle:NSDateFormatterMediumStyle];
    [dateFormatter setTimeStyle:NSDateFormatterMediumStyle];
    
    return [dateFormatter stringFromDate:date];
}

// It seems we can't do this check because with UIManagedDocument we don't know what the store URL actually is
// Perhaps this is contained in the metadata somewhere?

- (bool)checkVersion:(NSURL*)fileURL {
    //FLOG(@" checkVersion called with %@", fileURL);
    
    // We are using UIManagedDocument so lets get the store file name
    
    // File must exist
    if ([[NSFileManager defaultManager] fileExistsAtPath:[fileURL path]]) {
        //LOG(@" file exists :-)");
    }
    else {
        //LOG(@" file does not exist :-(");
        return NO;
    }
    
    NSURL *metadataURL = [fileURL URLByAppendingPathComponent:@"DocumentMetadata.plist"];
    //FLOG(@" metadataURL is %@", metadataURL);
    NSDictionary *dict = [[NSDictionary alloc] initWithContentsOfURL:metadataURL];
    //FLOG(@" metadata is %@", dict);
    
    
    [dict enumerateKeysAndObjectsUsingBlock:^(id key, id obj, BOOL *stop) {
        //FLOG(@" %@: %@", key, obj);
        
    }];
    
    NSURL *storeURL = [fileURL URLByAppendingPathComponent:[OSManagedDocument persistentStoreName]];
    
    //FLOG(@" store path is %@ ", storeURL.path);
    
    
    NSManagedObjectModel *model = [self managedObjectModel];
    
    //FLOG(@" app model is %@", model);
    //FLOG(@" app model entity version hashes are %@", [model entityVersionHashesByName]);
    
    
    return YES;
    
    NSError *error;
    NSDictionary *metaData = [NSPersistentStoreCoordinator metadataForPersistentStoreOfType:NSSQLiteStoreType URL:storeURL error:&error];
    
    if (!metaData) {
        FLOG(@" problem getting metaData");
        FLOG(@"  - error is %@, %@", error, error.userInfo);
        return NO;
    }
    
    bool result = [model isConfiguration:nil compatibleWithStoreMetadata:metaData];
    if (!result) {
        FLOG(@" file is not compatible!");
        FLOG(@" metadata is %@", metaData);
        
    }
    
    return result;
    
}
// Returns the managed object context for the application.
// If the context doesn't already exist, it is created and bound to the persistent store coordinator for the application.
- (NSManagedObjectContext *)managedObjectContext
{
    return _document.managedObjectContext;
    
}

// Returns the managed object model for the application.
// If the model doesn't already exist, it is created from the application's model.
- (NSManagedObjectModel *)managedObjectModel
{
    FLOG(@"managedObjectModel called.");
    if (_managedObjectModel != nil) {
        return _managedObjectModel;
    }
    NSURL *modelURL = [[NSBundle mainBundle] URLForResource:@"iProject" withExtension:@"momd"];
    _managedObjectModel = [[NSManagedObjectModel alloc] initWithContentsOfURL:modelURL];
    return _managedObjectModel;
}

/*! Checks whether the ubiquity token has changed and if so it means the iCloud login has changed since the application was last
 active.  If the user has signed out then they will loose access to their iCloud documents so tell them to log back in to
 access those documents.
 
 @param currenToken The current ubiquity identity.
 */
- (void)checkUbiquitousTokenFromPreviousLaunch:(id)currentToken
{
    // Fetch a previously stored value for the ubiquity identity token from NSUserDefaults.
    // That value can be compared to the current token to determine if the iCloud login has changed since the last launch of our application
    
    NSData* oldTokenData = [[NSUserDefaults standardUserDefaults] objectForKey:_ubiquityIDToken];
    id oldToken = oldTokenData ? [NSKeyedUnarchiver unarchiveObjectWithData:oldTokenData] : nil;
    if (oldTokenData && ![oldToken isEqual:currentToken]) {
        // If we had a token, we were signed in before.
        // If the token has change, a signout has occurred - either switching to another account or deleting iCloud entirely.
        
        UIAlertView* alert = [[UIAlertView alloc] initWithTitle:@"iCloud Sign-Out" message:@"You have signed out of the iCloud account previously used to store documents. Sign back in to access those documents" delegate:nil cancelButtonTitle:@"OK" otherButtonTitles:nil];
        [alert show];
    }
}
- (void)showMigratingAlert:(NSString*)str {
    
    if (_migratingAlert == nil) {
        NSString * message = [NSString stringWithFormat:@"Please wait while your files get migrated %@ iCloud", str];
        _migratingAlert = [[UIAlertView alloc] initWithTitle:@"File migration in progress" message:message delegate:nil cancelButtonTitle:nil otherButtonTitles:nil];
        FLOG(@" XXX showMigratingAlert %@", message);
        [_migratingAlert show];
    }

}
- (void)dismissMigratingAlert {
    if (_migratingAlert != nil) {
        FLOG(@" XXX dismissMigratingAlert");
        [_migratingAlert dismissWithClickedButtonIndex:0 animated:YES];
        _migratingAlert = nil;
    }
}

- (void)alertView:(UIAlertView*)alertView didDismissWithButtonIndex:(NSInteger)buttonIndex
{
    if (alertView == _cloudChoiceAlert)
    {
        LOG(@" _cloudChoiceAlert being processed");
        if (buttonIndex == 1) {
            LOG(@" user selected iCloud files");
            [[NSUserDefaults standardUserDefaults] setBool:YES forKey:_cloudPreferenceKey];
            [[NSUserDefaults standardUserDefaults] setValue:@"YES" forKey:_cloudPreferenceSet];
            _useICloudStorage = YES;
            [[NSUserDefaults standardUserDefaults] synchronize];
            
            [self setIsCloudEnabled:YES];
            [self postFileUpdateNotification];
        }
        else {
            LOG(@" user selected local files");
            [[NSUserDefaults standardUserDefaults] setBool:NO forKey:_cloudPreferenceKey];
            [[NSUserDefaults standardUserDefaults] setValue:@"YES" forKey:_cloudPreferenceSet];
            _useICloudStorage = NO;
            [[NSUserDefaults standardUserDefaults] synchronize];
            
            [self setIsCloudEnabled:NO];
        }
    }
    if (alertView == _cloudChangedAlert)
    {   LOG(@" _cloudChangedAlert being processed");
        if (buttonIndex == 0) {
            LOG(@" 'Keep using iCloud' selected");
            LOG(@" turn Use iCloud back ON");
            [[NSUserDefaults standardUserDefaults] setBool:YES forKey:_cloudPreferenceKey];
            [[NSUserDefaults standardUserDefaults] synchronize];
            _useICloudStorage = YES;
            [self setIsCloudEnabled:YES];
            
        }
        else if (buttonIndex == 1) {
            //LOG(@" 'Keep on My iPhone' selected");
            //LOG(@" copy to local storage");
            _useICloudStorage = NO;
            [self setDeleteICloudFiles:NO];
            [self setIsCloudEnabled:NO];
            
        }else if (buttonIndex == 2) {
            //LOG(@" 'Delete from My iPhone' selected");
            //LOG(@" delete copies from iPhone");
            _useICloudStorage = NO;
            [self setDeleteICloudFiles:YES];
            [self setIsCloudEnabled:NO];
        }
        [self postFileUpdateNotification];
    }
    
}
- (void)storeCurrentUbiquityToken:(id)currentToken
{
    // Write the ubquity identity token to NSUserDefaults if it exists.
    // Otherwise, remove the key.
    
    if (currentToken) {
        NSData* newTokenData = [NSKeyedArchiver archivedDataWithRootObject:currentToken];
        [[NSUserDefaults standardUserDefaults] setObject:newTokenData forKey:_ubiquityIDToken];
    }
    else {
        [[NSUserDefaults standardUserDefaults] removeObjectForKey:_ubiquityIDToken];
    }
}
/*! Checks to see whether the user has previously selected the iCloud storage option, and if so then check
 whether the iCloud identity has changed (i.e. different iCloud account being used or logged out of iCloud).
 
 If the user has previously chosen to use iCloud and we're still signed in, setup the CloudManager
 with cloud storage enabled.
 
 If iCloud is available AND if no user choice is recorded, use a UIAlert to fetch the user's preference.
 
 If iCloud is available AND if user has selected to Use iCloud then check if any local files need to be
 migrated.
 
 if iCloud is available AND if user has selected to NO Use iCloud then check if any iCloud files need to
 be migrated to local storage.
 
 */
- (void)checkUserICloudPreferenceAndSetupIfNecessary
{
    FLOG(@"checkUserICloudPreferenceAndSetupIfNecessary called");
    
    [[OSDocumentManager sharedManager] setUbiquityID:_ubiquityContainerKey ];
    
    if (![[NSFileManager defaultManager] ubiquityIdentityToken]) {
        // If there is no token now, set our state to NO
        self.isCloudEnabled = NO;
    } else {
        if (self.isCloudEnabled) {
            [[NSNotificationCenter defaultCenter] postNotificationName:ICloudStateUpdatedNotification object:nil];
        }
    }

    
    id currentToken = [[NSFileManager defaultManager] ubiquityIdentityToken];
    
    NSUserDefaults* userDefaults = [NSUserDefaults standardUserDefaults];
    NSString* userICloudChoiceSet = [userDefaults stringForKey:_cloudPreferenceSet];
    
    bool userICloudChoice = [userDefaults boolForKey:_cloudPreferenceKey];
    
    userICloudChoice = [userDefaults boolForKey:_cloudPreferenceKey];
    
    FLOG(@" User preference for %@ is %@", _cloudPreferenceKey, (userICloudChoice ? @"YES" : @"NO"));
    
    if (userICloudChoice) {
        
        LOG(@" User selected iCloud");
        _useICloudStorage = YES;
        
        // Display notice if previous iCloud account is not available
        [self checkUbiquitousTokenFromPreviousLaunch:currentToken];
        
        
    } else {
        
        LOG(@" User disabled iCloud");
        _useICloudStorage = NO;
        
    }
    
    // iCloud is active
    if (currentToken) {
        
        LOG(@" iCloud is active");
        
        // If user has not yet set preference then prompt for them to select a preference
        if ([userICloudChoiceSet length] == 0) {
            LOG(@" userICloudChoiceSet has not been set yet, so prompt the user.");
            _cloudChoiceAlert = [[UIAlertView alloc] initWithTitle:@"Choose Storage Option" message:@"Should documents be stored in iCloud or on just this device?" delegate:self cancelButtonTitle:@"Local only" otherButtonTitles:@"iCloud", nil];
            [_cloudChoiceAlert show];
            
        }
        else {
            
            if (userICloudChoice) {
                // iCloud is available and user has selected to use it
                // Check if any local files need to be migrated to iCloud
                // and migrate them
                [self setIsCloudEnabled:YES];
                
            } else  {
                
                // iCloud is avalable but user has chosen to NOT Use iCloud
                // Check if any iCloud files exist and ask the user what to do if there are any
                // otherwise just set isCloudEnabled = NO on the CloudManager
                if ([[OSDocumentManager sharedManager] iCloudFilesExist]) {
                    
                    [self promptUserAboutICloudDocumentStorage];
                    
                } else {
                    [self setIsCloudEnabled:NO];
                }
                
            }
            
        }
    }
    else {
        LOG(@" iCloud is not active");
        [[OSDocumentManager sharedManager] setIsCloudEnabled:NO];
        _useICloudStorage = NO;
        [[NSUserDefaults standardUserDefaults] setBool:NO forKey:_cloudPreferenceKey];
        [[NSUserDefaults standardUserDefaults] synchronize];
        
        // Since the user is signed out of iCloud, reset the preference to not use iCloud, so if they sign in again we will prompt them to move data
        [userDefaults removeObjectForKey:_cloudPreferenceSet];
    }
    
    [self storeCurrentUbiquityToken:currentToken];
}
/*! This method is called when Use iCloud preference has been turned OFF to ask the user whether they want to keep iCloud files, delete them or keep using iCloud
 
 */
- (void)promptUserAboutICloudDocumentStorage {
    
    if ([[UIDevice currentDevice] userInterfaceIdiom] == UIUserInterfaceIdiomPhone) {
        _cloudChangedAlert = [[UIAlertView alloc] initWithTitle:@"You're not using iCloud" message:@"What would you like to do with documents currently on this phone?" delegate:self cancelButtonTitle:@"Keep using iCloud" otherButtonTitles:@"Keep on My iPhone", @"Delete from My iPhone", nil];
    } else {
        _cloudChangedAlert = [[UIAlertView alloc] initWithTitle:@"You're not using iCloud" message:@"What would you like to do with documents currently on this phone?" delegate:self cancelButtonTitle:@"Keep using iCloud" otherButtonTitles:@"Keep on My iPad", @"Delete from My iPad", nil];
        
    }
    
    [_cloudChangedAlert show];
}
- (void)performApplicationWillEnterForegroundCheck
{
    LOG(@"applicationWillEnterForegroundCheck called");
    
    // Check if the app settings have been changed in the Settings Bundle (we use a Settings Bundle which
    // shows settings in the Devices Settings app, along with all the other device settings).
    [[NSUserDefaults standardUserDefaults] synchronize];
    NSUserDefaults* userDefaults = [NSUserDefaults standardUserDefaults];
    bool userICloudChoice = [userDefaults boolForKey:_cloudPreferenceKey];
    
    
    // Check if iCloud is available, if its not then we can't do anything anyway
    
    if ([self isICloudAvailable]) {
        
        // iCloud option has been turned off
        if (!userICloudChoice) {
            
            // If iCloud files still exist then ask what to do
            if ([self iCloudFilesExist]) {
                [self promptUserAboutICloudDocumentStorage];
            } else {
                [self setIsCloudEnabled:NO];
            }
            // Handle the users response in the alert callback
            
        } else {
            
            // iCloud is turned on so just copy them across... including the one we may have open
            
            //LOG(@" iCloud turned on so copy any created files across");
            [self setIsCloudEnabled:YES];  // This does all the work for us
            
            _useICloudStorage = YES;
            
        }
    }
    // Update the list 
    [self postFileUpdateNotification];
}
- (void)closeDocument
{
    //LOG(@"closeDocument called.");
    
    [self deregisterForStoreChanges];
    
    if (!_document) {
        return;
    }
    
    
    [_document saveToURL:_document.fileURL forSaveOperation:UIDocumentSaveForOverwriting completionHandler:^(BOOL success){
        if (!success) {
            // Handle the error.
            LOG(@" error saving file");
        }
    }];
    [_document closeWithCompletionHandler:^(BOOL success){
        if (!success) {
            // Handle the error.
            LOG(@" error closing file");
        }
        [[NSNotificationCenter defaultCenter] removeObserver:self name:UIDocumentStateChangedNotification  object:_document];
        _document = nil;
        _storeURL = nil;
        [self postFileClosedNotification];
    }];
    
}
/*! Gets called by the metadata query any time files change.  We need to be able to flag files that
 we have created so as to not think it has been deleted from iCloud.
 
 */
- (void)fileListReceived {
    LOG(@"fileListReceived called.");
    bool found = NO;
    
    FLOG(@" is %@", (self.isBusy ? @"BUSY" : @"NOT BUSY"));
    
    // If iCloud is available then check iCloud files
    if ([self canUseICloud]) {
        
        if (!self.isBusy) {
            //LOG(@"  iCloud is available and we are NOT busy so check iCloud for files");
            if ([self getNewiCloudDocs]) {
                //LOG(@"  New iCloud documents found!");
                found = YES;
            }
        }
        
        if ([self getDeletediCloudDocs]) {
            
            //LOG(@"  Deleted iCloud documents found!");
            found = YES;
        }
    } else {
        
        //LOG(@"  iCloud is NOT available so skip checking iCloud for files");
        
    }
    
    // Just check the local files again...
    if (found)
        [self postFileUpdateNotification];
    
}
/*!  Creates and starts a metadata query for iCloud files
 
 */
- (void)createFileQuery {
    [_query stopQuery];
    
    
    if ([[OSDocumentManager sharedManager] isCloudEnabled]) {
        //LOG(@"  getting Cloud files.");
        //cloudFiles = YES;
        if (_query) {
            [_query startQuery];
        }
        else {
            _query = [[NSMetadataQuery alloc] init];
            
            [_query setSearchScopes:[NSArray arrayWithObjects:NSMetadataQueryUbiquitousDocumentsScope, NSMetadataQueryUbiquitousDataScope, nil]];
            // NSString * str = [NSString stringWithFormat:@"*.%@",_fileExtension];
            NSString *str = @"*";
            [_query setPredicate:[NSPredicate predicateWithFormat:@"%K LIKE %@", NSMetadataItemFSNameKey, str]];
            
            NSNotificationCenter* notificationCenter = [NSNotificationCenter defaultCenter];
            [notificationCenter addObserver:self selector:@selector(fileListReceived) name:NSMetadataQueryDidFinishGatheringNotification object:_query];
            [notificationCenter addObserver:self selector:@selector(fileListReceived) name:NSMetadataQueryDidUpdateNotification object:_query];
            [_query startQuery];
        }
    }
    
}
- (void)saveDocument {
    [_document saveToURL:_document.fileURL forSaveOperation:UIDocumentSaveForOverwriting completionHandler:^(BOOL success){
        if (!success) {
            // Handle the error.
            LOG(@" error saving file");
        }
        LOG(@" file saved");
    }];
}
- (NSMutableArray *)getFileList
{
    //LOG(@"getFileList called.");
    
    NSMutableArray *fileList = [[NSMutableArray alloc] init];
    
    _localDocuments = [[NSFileManager defaultManager] contentsOfDirectoryAtURL:self.documentsDirectoryURL includingPropertiesForKeys:nil options:0 error:nil];
    
    bool found = [self getNewiCloudDocs];
    bool created = NO;
    int count = 0;
    
    // Delay things to let new iCloud files get created
    if (found) {
        while (!created) {
            _localDocuments = [[NSFileManager defaultManager] contentsOfDirectoryAtURL:self.documentsDirectoryURL includingPropertiesForKeys:nil options:0 error:nil];
            created = [_localDocuments count];
            //FLOG(@"  %d local documents found AFTER iCloud scan", [_localDocuments count]);
            if (count > 5) {
                break;
            }
            sleep(1);
            count++;
        }
    }
    _localDocuments = [[NSFileManager defaultManager] contentsOfDirectoryAtURL:self.documentsDirectoryURL includingPropertiesForKeys:nil options:0 error:nil];
    //FLOG(@"  %d local documents found AFTER iCloud scan", [_localDocuments count]);
    
    if ([self getDeletediCloudDocs]) {
        //LOG(@"  Deleted iCloud documents found!");
        found = YES;
    }
    
    
    //FLOG(@" localDocuments:");
    for (NSURL* document in _localDocuments) {
        
        NSString *filename = [document lastPathComponent];
        
        if (![filename isEqualToString:@"CoreDataUbiquitySupport"] && ![filename isEqualToString:@".DS_Store"]) { // .DS_Store for simulator
            
            // If iCloud has been turned off then don't show any iCloud files because ONLY the fallback store will be used
            // and we don't want that !!
            if (!self.isICloudAvailable ) {
                
                if ([filename rangeOfString:@"_UUID_"].location == NSNotFound) {
                    
                    //FLOG(@" adding document: %@", [document lastPathComponent]);
                    FileRepresentation* fileRepresentation = [[FileRepresentation alloc] initWithFileName:[document lastPathComponent]  url:document percentDownloaded:[NSNumber numberWithFloat:100.0]];
                    
                    [fileList addObject:fileRepresentation];
                    
                }
            } else {
                //FLOG(@" adding document: %@", [document lastPathComponent]);
                FileRepresentation* fileRepresentation = [[FileRepresentation alloc] initWithFileName:[document lastPathComponent]  url:document percentDownloaded:[NSNumber numberWithFloat:100.0]];
                
                [fileList addObject:fileRepresentation];
                
            }
        }
    }
    
    [self createFileQuery];
    
    return fileList;
}
/*! Use this to clean up during testing or to reset everything in production
 
 */
- (void)clearAllFiles {
    
    [self clearLocalFiles];
    [self clearICloudFiles];
}
/*! Removes all files from the apps /Document directory
 
 */
- (void)clearLocalFiles {
    
    NSURL* documentsDirectoryURL = [[OSDocumentManager sharedManager] documentsDirectoryURL] ;
    
    [self clearDirectory:documentsDirectoryURL];
    
}
/*! Removes all files from the ubiquity container's /CoreData directory which is the only one
 used by us.
 
 */
- (void)clearICloudFiles {
    //LOG(@"  clearICloudFiles called");
    
    NSURL *iCloudDirectory = [[OSDocumentManager sharedManager] iCloudCoreDataURL];
    
    [self clearDirectory:iCloudDirectory];
    
}
- (void)clearDirectory:(NSURL*)url {
    NSFileManager *fileManager = [[NSFileManager alloc] init];
    NSError *error;
    NSArray* documents = [[NSFileManager defaultManager] contentsOfDirectoryAtURL:url includingPropertiesForKeys:nil options:0 error:nil];
    
    for (NSURL *file in documents) {
        //FLOG(@" deleting file %@", file);
        [fileManager removeItemAtURL:file error:&error];
    }
}
/*! Returns the user entered filename because we have appended a UUID to guarantee uniqueness in iCloud
 
 @param filename The iCloud filename which is 'userFilename'_UUID_'uuid'
 @return The userFilename component
 */
- (NSString*)userFilename:(NSString *)filename {
    NSArray *components = [filename componentsSeparatedByString:@"_UUID_"];
    return [components objectAtIndex:0];
}
/*! Returns the files UUID which is extracted from the filename of the format 'userFilename'_UUID_'uuid'
 
 @param filename The iCloud filename which is 'userFilename'_UUID_'uuid'
 @return The UUID string component
 */
- (NSString*)fileUUID:(NSString *)filename {
    NSArray *components = [filename componentsSeparatedByString:@"_UUID_"];
    if ([components count] > 1)
        return [components objectAtIndex:1];
    else
        return @"";
}
- (NSString*)userFilenameForDoc {
    return [self userFilename:[[_document.fileURL URLByDeletingPathExtension] lastPathComponent]];
}
- (NSString*)fileUUIDForDoc {
    return [self fileUUID:[[_document.fileURL URLByDeletingPathExtension] lastPathComponent]];
}





//////////
/*! Moves a local document to iCloud by creating a new UIManagedDocument using the local filename+"_UUID_"+uuid
 in the same directory and then migrating the core data store to this document with iCloud options. When completed
 makes a callback to the cloudManager so the cloudManager can delete the local file.
 
 @param  cloudManager The cloud file manager that get the callback when the file migration has completed successfully.
 */
- (void)moveDocumentToICloud:(NSURL*)fileURL {
    FLOG(@"moveDocumentToICloud called");
    
    //OSManagedDocument *document = [[OSManagedDocument alloc] initWithFileURL:fileURL];
    //document.delegate = self;

    NSURL * sourceDocumentURL = fileURL;
    NSURL * destination = [sourceDocumentURL URLByDeletingLastPathComponent];
    
    // Create the new iCloud filename
    NSString *documentName = [NSString stringWithFormat:@"%@_UUID_%@",[sourceDocumentURL lastPathComponent], [[NSUUID UUID] UUIDString]];
    
    NSURL *destinationURL = [destination URLByAppendingPathComponent:documentName];
    
    NSURL *sourceStoreURL = [[sourceDocumentURL URLByAppendingPathComponent:StoreDirectoryComponentLocal isDirectory:YES] URLByAppendingPathComponent:StoreFileName isDirectory:NO];
    
    
    NSDictionary *localOptions = @{NSMigratePersistentStoresAutomaticallyOption:@YES,
                                   NSInferMappingModelAutomaticallyOption:@YES,
                                   NSSQLitePragmasOption:@{ @"journal_mode" : @"DELETE" }};
    
    NSDictionary *ubiquityOptions = @{NSPersistentStoreUbiquitousContentNameKey:documentName,
                                      NSMigratePersistentStoresAutomaticallyOption:@YES,
                                      NSInferMappingModelAutomaticallyOption:@YES,
                                      NSSQLitePragmasOption:@{ @"journal_mode" : @"DELETE" }};
    
    // Delete any file at the destination
    NSFileManager *fileManager = [[NSFileManager alloc] init];
    [fileManager removeItemAtURL:destinationURL error:nil];
    
    __block bool moveSuccess = NO;
    
    // Now create a new UIManagedDocument which will use the model from the application bundle
    LOG(@" Creating new OSManagedDocument...");
    OSManagedDocument *newDocument = [[OSManagedDocument alloc] initWithFileURL:destinationURL];
    newDocument.persistentStoreOptions = ubiquityOptions;
    [newDocument saveToURL:destinationURL forSaveOperation:UIDocumentSaveForCreating completionHandler:^(BOOL success){
        if (!success) {
            
            // Handle the error.
            LOG(@" error saving file :-(");
            
        } else {
            LOG(@" Successfully created new OSManagedDocument");
            
            // Now that we have created a new OSManagedDocument remove the store and migrate the local store
            
            NSPersistentStoreCoordinator *pscForSave = newDocument.managedObjectContext.persistentStoreCoordinator;
            
            NSPersistentStore *store = [[pscForSave persistentStores] objectAtIndex:0];
            NSURL *storesURL = store.URL;
            
            // Now remove the existing store
            NSError *removeStoreError;
            bool result = [pscForSave removePersistentStore:store error:&removeStoreError];
            
            if (!result) {
                
                FLOG(@"  Failed to remove store: %@, %@", removeStoreError, removeStoreError.userInfo);
                
            } else {
                LOG(@" Successfully removed old store");
                
                id sourceStore = [pscForSave addPersistentStoreWithType:NSSQLiteStoreType configuration:nil URL:sourceStoreURL options:localOptions error:nil];
                
                if (!sourceStore) {
                    
                    FLOG(@" failed to add old store");
                    
                } else {
                    LOG(@" Successfully added store to migrate");
                    
                    NSError *error;
                    LOG(@" About to migrate the store...");
                    id migrationSuccess = [pscForSave migratePersistentStore:sourceStore toURL:storesURL options:ubiquityOptions withType:NSSQLiteStoreType error:&error];
                    
                    if (migrationSuccess) {
                        moveSuccess = YES;
                        FLOG(@"store successfully migrated");
                    }
                    else {
                        FLOG(@"Failed to migrate store: %@, %@", error, error.userInfo);
                        moveSuccess = NO;
                    }
                }
            }
        }
        [newDocument closeWithCompletionHandler:^(BOOL success){
            if (!success) {
                // Handle the error.
                LOG(@" error closing file after creation :-(");
            } else {
                FLOG(@" file closed");
            }
            // Async callback to CloudManager to tell it we are done migrating
            [self fileMigrated:sourceDocumentURL success:moveSuccess];
        }];
    }];
    
    return;
}
/*! Moves an iCloud document to local by creating a new UIManagedDocument using the filename component of the iCloud ubiquity name (filename+"_UUID_"+uuid)
 in the same directory and then migrating the core data store to this document without iCloud options and removes the store from iCloud and finally
 deletes the local copy.
 
 Note that even if it fails to remove the iCloud files it deletes the local copy.  User may need to clean up orphaned iCloud files using a Mac!
 
 @return Returns YES of file was migrated or NO if not.
 */
- (void)moveDocumentToLocal:(NSURL*)fileURL {
    
    NSURL * sourceDocumentURL = fileURL;
    NSString *ubiquityNameKey = [sourceDocumentURL lastPathComponent];
    NSURL * destination = [sourceDocumentURL URLByDeletingLastPathComponent];
    
    // Create the new local filename
    NSString *documentName = [[[sourceDocumentURL lastPathComponent] componentsSeparatedByString:@"_UUID_"] objectAtIndex:0];
    
    NSURL *destinationURL = [destination URLByAppendingPathComponent:documentName];
    
    NSURL *sourceStoreURL = [self storeFileURL:fileURL];
    
    if (sourceStoreURL == nil) {
        FLOG(@" error unable to find sourceStoreURL");
        return;
    }
    
    NSDictionary *localOptions = @{NSPersistentStoreRemoveUbiquitousMetadataOption:@YES,
                                   NSMigratePersistentStoresAutomaticallyOption:@YES,
                                   NSInferMappingModelAutomaticallyOption:@YES,
                                   NSSQLitePragmasOption:@{ @"journal_mode" : @"DELETE" }};
    
    NSDictionary *ubiquityOptions = @{NSPersistentStoreUbiquitousContentNameKey:ubiquityNameKey,
                                      NSMigratePersistentStoresAutomaticallyOption:@YES,
                                      NSInferMappingModelAutomaticallyOption:@YES,
                                      NSSQLitePragmasOption:@{ @"journal_mode" : @"DELETE" }};
    
    // Remove any local file
    // We should check if a filename exists and then create a new filename using a counter...
    // because its possible to have more than one file with the same user filename in iCloud
    NSFileManager *fileManager = [[NSFileManager alloc] init];
    [fileManager removeItemAtURL:destinationURL error:nil];
    
    __block bool moveSuccess = NO;
    
    // Now create a new UIManagedDocument which will use the model from the application bundle
    OSManagedDocument *newDocument = [[OSManagedDocument alloc] initWithFileURL:destinationURL];
    newDocument.persistentStoreOptions = localOptions;
    [newDocument saveToURL:destinationURL forSaveOperation:UIDocumentSaveForCreating completionHandler:^(BOOL success){
        if (!success) {
            // Handle the error.
            LOG(@" error saving file :-(");
        } else {
            // Now that we have created a new OSManagedDocument remove the store and migrate the local store
            
            NSPersistentStoreCoordinator *pscForSave = newDocument.managedObjectContext.persistentStoreCoordinator;
            
            NSPersistentStore *store = [[pscForSave persistentStores] objectAtIndex:0];
            NSURL *storesURL = store.URL;
            
            // Now remove the existing store
            NSError *removeStoreError;
            bool result = [pscForSave removePersistentStore:store error:&removeStoreError];
            
            if (!result) {
                
                FLOG(@"  Failed to remove store: %@, %@", removeStoreError, removeStoreError.userInfo);
                
            } else {
                
                id sourceStore = [pscForSave addPersistentStoreWithType:NSSQLiteStoreType configuration:nil URL:sourceStoreURL options:ubiquityOptions error:nil];
                
                if (!sourceStore) {
                    
                    FLOG(@" failed to add old store");
                    
                } else {
                    
                    NSError *error;
                    
                    id migrationSuccess = [pscForSave migratePersistentStore:sourceStore toURL:storesURL options:localOptions withType:NSSQLiteStoreType error:&error];
                    
                    if (migrationSuccess) {
                        FLOG(@"  Store successfuly migrated to %@", documentName);
                        moveSuccess = YES;
                    }
                    else {
                        FLOG(@"  Failed to migrate store: %@", error);
                        moveSuccess = NO;
                    }
                }
            }
        }
        [newDocument closeWithCompletionHandler:^(BOOL success){
            if (!success) {
                // Handle the error.
                LOG(@" error closing file after creation :-(");
            } else {
                FLOG(@" %@ document closed ", documentName);
            }
            // Async callback to CloudManager to tell it we are done migrating
            [self fileMigrated:sourceDocumentURL success:moveSuccess];
            
        }];
    }];
    
    return;
}
/*! Finds the URL for the store file.  We have to search for it because there appears to be no way to
 get the store file path from a UIManagedDocument API if iCloud options have been used. It's stored somewhere
 in ~/DOCUMENTNAME/StoreContent.nosync/CoreDataUbiquitySupport/iCloudAccountID~DeviceID/DOCUMENTUBIQUITYNAME/SomeRandomNumber/store/
 
 WARNING: Its not clear how Core Data stores things if another iCloud account is used.  Is a new structure created and are old files removed ?
 If not then additional information may be required to identify the correct store to use based on iCloud ID or something.
 
 As an alternative use - (void)storeFileURL2;
 
 @return Returns the URL to the store file.
 */
- (NSURL*)storeFileURL:(NSURL*)fileURL {
    FLOG(@"storeFileURL called");
    
    NSString *storeFileName = [OSManagedDocument persistentStoreName];
    //FLOG(@"  looking for %@", storeFileName);
    
    return [self searchDirectory:fileURL for:storeFileName];
}
- (NSURL*)searchDirectory:(NSURL*)directory for:(NSString*)searchFilename {
    
    NSArray *docs = [[NSFileManager defaultManager] contentsOfDirectoryAtURL:directory includingPropertiesForKeys:nil options:0 error:nil];
    
    for (NSURL* document in docs) {
        NSString *filename = [document lastPathComponent];
        BOOL isDir;
        BOOL fileExists = [[NSFileManager defaultManager] fileExistsAtPath:document.path isDirectory:&isDir];
        
        if (fileExists && !isDir && [filename isEqualToString:searchFilename]) {
            return document;
        }
        if (fileExists && isDir) {
            NSURL *url = [self searchDirectory:document for:searchFilename];
            if (url != nil) return url;
        }
        
    }
    return nil;
    
}
/*! Gets the URL for the store file.  If iCloud is being used then the store file is in some weird
 location so this is the safest way to get it.  Can only be used once the UIManagedDocument has been
 opened!
 
 @return Returns the store file URL
 */
- (NSURL*)storeFileURL2:(NSURL*)fileURL {
    
    OSManagedDocument *doc = [[OSManagedDocument alloc] initWithFileURL:fileURL];
    
    NSPersistentStoreCoordinator *psc = doc.managedObjectContext.persistentStoreCoordinator;
    NSArray *stores = [psc persistentStores];
    if(stores && stores.count>0) {
        NSPersistentStore *store = [[psc persistentStores] objectAtIndex:0];
        return store.URL;
    } else {
        return [self storeFileURL:fileURL];
    }
    
}
@end

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out / Change )

Twitter picture

You are commenting using your Twitter account. Log Out / Change )

Facebook photo

You are commenting using your Facebook account. Log Out / Change )

Google+ photo

You are commenting using your Google+ account. Log Out / Change )

Connecting to %s