The problem
What I have so far is code that creates a new local file and deletes the iCloud file.
Is it possible to rename an iCloud document, so that it stays in iCloud?
GarageBand can do it. It's possible to rename an iCloud song. After the rename is done, the song is still in iCloud. However GarageBand is an Apple app, so it may use private apis.
My current code:
- (void)moveFrom:(NSURL*)sourceURL
moveTo:(NSString*)destinationName
completion:(void (^)())completion
{
MyDocument *document = [[MyDocument alloc] initWithFileURL:sourceURL];
[document openWithCompletionHandler:^(BOOL success)
{
NSURL *fileURL = [self.localRoot URLByAppendingPathComponent:destinationName];
DLog(#"Create %#", fileURL);
[document saveToURL:fileURL
forSaveOperation:UIDocumentSaveForCreating
completionHandler:^(BOOL success)
{
NSLog(#"Saved %#", fileURL);
[document closeWithCompletionHandler:^(BOOL success) {
// Delete the old document from a secondary thread
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^()
{
NSFileCoordinator* fileCoordinator = [[NSFileCoordinator alloc] initWithFilePresenter:nil];
[fileCoordinator coordinateWritingItemAtURL:sourceURL
options:NSFileCoordinatorWritingForDeleting
error:nil
byAccessor:^(NSURL* writingURL) {
NSFileManager* fileManager = [[NSFileManager alloc] init];
[fileManager removeItemAtURL:writingURL error:nil];
DLog(#"Deleted %#", sourceURL);
completion();
}];
});
}];
}];
}];
}
Update: Still no luck
I have found out that -setUbiquitous:itemAtURL:destinationURL:error: cannot be used for renaming documents.
If I invoke [setUbiquitous:NO itemAtURL:oldLocalURL destinationURL:newLocalURL error:&error] on an already local file, then:
Error Domain=NSCocoaErrorDomain Code=512 "The operation couldn’t be
completed. (Cocoa error 512.)" UserInfo=0x1fdf6730
{NSURL=file://localhost/var/mobile/Applications/4BABA000-B100-49FC-B928-B0F403FC75FF/Documents/LocalDrawing.td2/,
NSUnderlyingError=0x20940e80 "The operation couldn’t be completed.
(LibrarianErrorDomain error 2 - Cannot disable syncing on a unsynced
item.)"}
If I invoke [setUbiquitous:YES itemAtURL:oldCloudURL destinationURL:newCloudURL error:&error] on an already cloud file, then:
Error Domain=NSCocoaErrorDomain Code=512 "The operation couldn’t be
completed. (Cocoa error 512.)" UserInfo=0x208e9820
{NSURL=file://localhost/var/mobile/Library/Mobile%20Documents/22DR89XVRF~com~opcoders~triangle-draw/Documents/CloudDrawing.td2/,
NSUnderlyingError=0x208d45b0 "The operation couldn’t be completed.
(LibrarianErrorDomain error 2 - Cannot enable syncing on a synced
item.)"}
Thus -setUbiquitous:itemAtURL:destinationURL:error: cannot be used for renaming documents.
I have finally solved it. This is my code so far:
- (void)_moveURL:(NSURL*)sourceURL
destURL:(NSURL*)destinationURL
success:(void (^)())successBlock
failure:(void (^)(NSError *))failureBlock
{
NSParameterAssert(sourceURL);
NSParameterAssert(destinationURL);
NSParameterAssert(successBlock);
NSParameterAssert(failureBlock);
// Do the actual renaming
__block NSError *moveError = nil;
__block BOOL moveSuccess = NO;
void (^accessor)(NSURL*, NSURL*) = ^(NSURL *newURL1, NSURL *newURL2) {
NSFileManager *fileManager = [[NSFileManager alloc] init];
moveSuccess = [fileManager moveItemAtURL:sourceURL toURL:destinationURL error:&moveError];
};
// Coordinate renaming
NSError *coordinatorError = nil;
NSFileCoordinator *coordinator = [[NSFileCoordinator alloc] initWithFilePresenter:nil];
[coordinator coordinateWritingItemAtURL:sourceURL
options:NSFileCoordinatorWritingForMoving
writingItemAtURL:destinationURL
options:NSFileCoordinatorWritingForReplacing
error:&coordinatorError
byAccessor:accessor];
if (moveSuccess) {
successBlock();
return;
}
if (moveError) {
failureBlock(moveError);
return;
}
if (coordinatorError) {
failureBlock(coordinatorError);
return;
}
NSAssert(NO, #"should not happen");
}
There is an easier way to rename item at any given URL, including iCloud URLs.
url.setResourceValue(newName, forKey: NSURLNameKey)
I am using this in my cocoa app's production code.
Edit:
In Swift 5, this looks a little different, but still works well:
var resourceValues = URLResourceValues()
resourceValues.name = newName
try previousFileURL.setResourceValues(resourceValues)
Related
I've got an old app that uses UIManagedDocument to interact with Core Data. However on iOS 11.2 (and possibly earlier iOS 11 point releases) the saveToURL:forSaveOperation:completionHandler: method seems to have stopped working both on-device and in the simulator (however it does still work in the iOS 10.3.1 simulator). Specifically, in the code below the completionHandler inside the first if statement is never executed (as NSLog messages indicate).
- (void)useDemoDocument {
NSURL *url = [[[NSFileManager defaultManager] URLsForDirectory:NSDocumentDirectory inDomains:NSUserDomainMask] lastObject];
url = [url URLByAppendingPathComponent:#"TWL_Document"];
UIManagedDocument *document = [[UIManagedDocument alloc] initWithFileURL:url];
if (![[NSFileManager defaultManager] fileExistsAtPath:[url path]]) {
NSLog(#"This Code Executes");
[document saveToURL:url
forSaveOperation:UIDocumentSaveForCreating
completionHandler:^(BOOL success) {
if (success) {
NSLog(#"But this is never called");
self.managedObjectContext = document.managedObjectContext;
} else {
NSLog(#"This also is not called");
}
}];
} else if (document.documentState == UIDocumentStateClosed) {
[document openWithCompletionHandler:^(BOOL success) {
if (success) {
self.managedObjectContext = document.managedObjectContext;
}
}];
} else {
self.managedObjectContext = document.managedObjectContext;
}
}
Instead I get an error message that The reader is not permitted to access the URL.:
2017-12-17 12:38:14.258936-0800 ToWatchList[1864:542434] [default] [ERROR] Could not get attribute values for item /var/mobile/Containers/Data/Application/2[UUID]/Documents/TWL_Document (n). Error: Error Domain=NSFileProviderInternalErrorDomain Code=1 "The reader is not permitted to access the URL." UserInfo={NSLocalizedDescription=The reader is not permitted to access the URL.}
What's going on here? Any suggestions on how to get this working again in iOS 11?
when trying to save a managed object with the following configuration in the persistent store coordinator:
NSURL *storeURL = [[self applicationDocumentsDirectory] URLByAppendingPathComponent:#"ParallelPhotosModel.sqlite"];
NSError *error = nil;
NSString *failureReason = #"There was an error creating or loading the application's saved data.";
if (![_persistentStoreCoordinator addPersistentStoreWithType:NSSQLiteStoreType configuration:nil URL:storeURL options:nil error:&error]) {
i get the following error:
Unresolved error Error Domain=NSCocoaErrorDomain Code=134030 "An error occurred while saving." UserInfo={NSFilePath=/var/mobile/Containers/Data/Application/9FCED2FF-F976-4780-8192-208519C8CD11/Documents/ParallelPhotosModel.sqlite, NSAffectedStoresErrorKey=(
" (URL: file:///var/mobile/Containers/Data/Application/9FCED2FF-F976-4780-8192-208519C8CD11/Documents/ParallelPhotosModel.sqlite)"
), NSUnderlyingError=0x127335c50 {Error Domain=NSCocoaErrorDomain Code=4 "The file doesn’t exist." UserInfo={NSUnderlyingError=0x12732a670 {Error Domain=NSPOSIXErrorDomain Code=2 "No such file or directory" UserInfo={NSFilePath=/var/mobile/Containers/Data/Application/9FCED2FF-F976-4780-8192-208519C8CD11/Documents/ParallelPhotosModel.sqlite}}}}}, {
NSAffectedStoresErrorKey = (
" (URL: file:///var/mobile/Containers/Data/Application/9FCED2FF-F976-4780-8192-208519C8CD11/Documents/ParallelPhotosModel.sqlite)"
);
NSFilePath = "/var/mobile/Containers/Data/Application/9FCED2FF-F976-4780-8192-208519C8CD11/Documents/ParallelPhotosModel.sqlite";
NSUnderlyingError = "Error Domain=NSCocoaErrorDomain Code=4 \"The file doesn\U2019t exist.\" UserInfo={NSUnderlyingError=0x12732a670 {Error Domain=NSPOSIXErrorDomain Code=2 \"No such file or directory\" UserInfo={NSFilePath=/var/mobile/Containers/Data/Application/9FCED2FF-F976-4780-8192-208519C8CD11/Documents/ParallelPhotosModel.sqlite}}}";
}
what is the underlying problem here and how do i avoid it? I ve seen somewhere that a similar error pops up when trying to save inside the App bundle but the storeURL i m using was taken from a tutorial (that is the documents directory).
UPDATE:
to be more specific, I paste where exactly NSManagedObjectContext should allow me to save but it does not:
NSManagedObjectContext *context = [appDelegate managedObjectContext];
for (DBMetadata *file in metadataContents) {
NSString *photo_info = [file photo_info];
if (photo_info != nil) {
NSNumber *day_taken = [self extractDayFromString: photo_info];
NSNumber *month_taken = [self extractMonthFromString: photo_info];
NSNumber *year_taken = [self extractYearFromString: photo_info];
NSString *db_path = [file path];
NSManagedObject *parallelPhoto = [NSEntityDescription
insertNewObjectForEntityForName:#"ParallelPhotos"
inManagedObjectContext:context];
[parallelPhoto setValue:db_path forKey:#"pathInDropbox"];
[parallelPhoto setValue:day_taken forKey:#"day"];
[parallelPhoto setValue:month_taken forKey:#"month"];
[parallelPhoto setValue:year_taken forKey:#"year"];
NSLog(#"ADDED TO DB: %# %# %#", day_taken, month_taken, year_taken);
}
}
NSError *error = nil;
if(![context save:&error]){
NSLog(#"error: %#", error);
You may want to make sure the directory exists before adding the URL to the persistent store.
[[NSFileManager defaultManager] createDirectoryAtURL:[storeURL URLByDeletingLastPathComponent]
withIntermediateDirectories:YES
attributes:nil
error:NULL];
Actually, you may want to add this code to your applicationDocumentsDirectory method before returning the value. If you do that, make sure you properly handle the potential error.
EDIT
Put a breakpoint on the line
NSManagedObjectContext *context = [appDelegate managedObjectContext];
and step through that. Inspect the attributes of the MOC and PSC. You should also inspect the URL held by the persistent stores. Then you should examine the file system to see what is in that directory.
If none of that works, put a breakpoint at the point where the MOC/PSC are initially created and make sure it is created properly. Examine the file system to make sure the store is there.
Then, install an observer on the file and/or file system to be notified anytime it changes, and see if you can catch the file being deleted or moved.
So, I ve changed several things in my implementation but what fixed it for me I think is the following line:
[newAddress.managedObjectContext save:nil]
instead of
[context save:&error]
which is a managedObjectContext that seems to have been the wrong one.
Probably it was some duplicate. So, I conclude it is a good strategy to always use the managedObjectContext owned by the Object that you ve just made changes to it just to be in the safe side.
I have UIDocument in iCloud ubiquitous container and I need to append data to file while saving document. I override readFromURL:: and writeContents::::: method according UIDocument documentation:
-(BOOL) writeContents:(id)contents toURL:(NSURL*)url forSaveOperation:(UIDocumentSaveOperation)saveOperation originalContentsURL:(NSURL*)originalContentsURL error:(NSError *__autoreleasing *)outError
{
NSFileCoordinator* coordinator = [[NSFileCoordinator alloc] initWithFilePresenter:self];
NSError* error = nil;
[coordinator coordinateWritingItemAtURL:url options:0 error:&error byAccessor:^(NSURL *newURL) {
NSData* data = contents; //correct, non-empty NSData
///[data writeToFile:newURL :] works, but overwrite original file
NSOutputStream* stream =[[NSOutputStream alloc] initWithURL:newURL append:YES];
if (stream)
{
NSInteger written = [stream write:data.bytes maxLength:data.length];
if (written != data.length)
{
//failed here, written == -1
NSLog(#"Write data to UIDocument failed: %#, error: %#", newURL, stream.streamError);
}
}
else
{
NSLog(#"Write data to iCloudDocument failed: %#", newURL);
}
}];
if (error)
{
NSLog(#"Coordinated write failed %#, error: %#", url, error);
*outError = error;
}
return error == nil;
}
Accessor block has different newURL, for example:
url: file:///private/var/mobile/Library/Mobile%20Documents/XXXXXXX~com~test~test/test.doc
newURL: file:///private/var/mobile/Applications/5631D484-7661-4E9E-A342-B25297FC0E18/tmp/(A%20Document%20Being%20Saved%20By%20test%20)/test.doc.
[stream write::] failed, because newURL file dosn't exists and I can't append data, only create file with all document's content.
Document editing code:
NSURL* url = [self.containerURL URLByAppendingPathComponent:kCloudDocumentName];
MyDocument* document = [[MyDocument alloc] initWithFileURL:url];
[document openWithCompletionHandler:^(BOOL success) {
if (success)
{
//update some document data
[self updateData:document completion:nil];
[document closeWithCompletionHandler:^(BOOL success) {
//failed here!
}];
}
}];
MyDocument exists in ubiquitous container at url and document has Normal state.
How I can do incremental writing in this case? Whats wrong?
After changing data in your UIDocumnet you can use:
-[UIDocumnet saveToURL:UIDocumnet.fileURL forSaveOperation:UIDocumentSaveForOverwriting completionHandler:nil];
Not sure, it's right or not but it works in my app. If it's wrong, correct me please somebody.
You may need to overwrite this method in your subclass of UIDocumnet:
- (id) contentsForType:(NSString *)typeName error:(NSError **)outError
I have been struggling with this for two days now, but I cannot get it to work. Part of the reason I'm fighting with this is that the official Dropbox Sync API tutorial does a very poor job explaining how to get this done.
So far I can write anything using the Dropbox API, but getting anything is being a whole different beast on its own.
I have the following code (apologies if it is chaotic - I have been testing a lot and it's just natural it will clutter up sooner or later) in my app delegate's - (void)applicationDidBecomeActive:(UIApplication *)application method:
if([[[DBAccountManager sharedManager] linkedAccount] isLinked])
{
DBError *erri = nil;
if(!self.collectionsFile.open)
{
DBPath *newPath = [[DBPath root] childPath:[NSString stringWithFormat:#"metadata/%#", #"user_collections.json"]];
//self.collectionsFile = [[DBFilesystem sharedFilesystem] createFile:newPath error:nil];
self.collectionsFile = [[DBFilesystem sharedFilesystem] openFile:newPath error:&erri];
}
DBFileStatus *newerStatus = self.collectionsFile.newerStatus;
DBFileStatus *status = self.collectionsFile.status;
NSLog(#"%# erri", erri.localizedDescription);
__block NSString *contents = [self.collectionsFile readString:nil];
NSLog(#"Contents %#", contents);
[self.collectionsFile addObserver:self block:^(){
NSLog(#"Observer called");
if([[DBFilesystem sharedFilesystem] completedFirstSync])
{
NSLog(#"First sync done");
if(newerStatus != nil)
{
NSLog(#"File is downloading.");
}else
{
NSLog(#"Im here dude %#", contents);
NSString *metadata = [(FTIBAppDelegate *)[[UIApplication sharedApplication] delegate] getMetadataPath];
NSString *collectionsFile = [NSString stringWithFormat:#"%#/%#", metadata, #"user_collections.json", nil];
[contents writeToFile:collectionsFile atomically:NO encoding:NSUTF8StringEncoding error:nil];
}
if(status.cached)
{
NSString *metadata = [(FTIBAppDelegate *)[[UIApplication sharedApplication] delegate] getMetadataPath];
NSString *collectionsFile = [NSString stringWithFormat:#"%#/%#", metadata, #"user_collections.json", nil];
[contents writeToFile:collectionsFile atomically:NO encoding:NSUTF8StringEncoding error:nil];
}
}
}];
}
Whenever I reinstall my app and link Dropbox again, I get this beautiful output:
2014-01-21 15:58:24.171 Mignori[913:60b] App linked successfully!
2014-01-21 15:58:24.301 Mignori[913:60b] [WARNING] ERR:
DROPBOX_ERROR_USAGE: sync.cpp:210: Checking file path before file
types info has been fetched. Wait for first sync to avoid creating a
file which may fail to upload later. 2014-01-21 15:58:24.473
Mignori[913:60b] [ERROR] ERR: DROPBOX_ERROR_ALREADYOPEN: file.cpp:188:
p(/v1/t5.json) already open (1) 2014-01-21 15:58:24.528
Mignori[913:60b] DropboxSync error - Error Domain=dropbox.com
Code=2004 "The operation couldn’t be completed. (dropbox.com error
2004.)" UserInfo=0x17028ba0 {desc=file.cpp:188: p(/v1/t5.json) already open (1)} 2014-01-21 15:58:24.531 Mignori[913:60b] The operation
couldn’t be completed. (dropbox.com error 2004.) erri 2014-01-21
15:58:24.532 Mignori[913:60b] Contents (null)
A couple of things to keep in mind:
The file (user_collections.json) does exist in my Dropbox - I'm updating it with a different device.
My File is a strong property of the app delegate.
I will appreciate any help to get this to work. Even a (better) sample code on how to get this done. I can update files in Dropbox with no problem at all, but downloading them is being much more complicated than I expected.
The problem was that I was trying to open the file when the initial sync wasn't completed. You have to wait until it's done. Set an NSThread to constantly check if your filesystem's completeFirstSync is completed.
I have an app that I'm making and it uses a UIManagedDocument to handle core data and iCloud. At the start of my app I Open the file and initialize the UIManagedDocument with the iCloud directory.
This works fine the first time I load the program and subsequent times but It is my understanding that because I use iCloud I should be able to delete the app and reload it and have it reload the data from the cloud.
When I delete the app and reload it its able to get the url and initialize the UIManagedDocument but once it gets to [document openWithCompletionHandler:^(BOOL success){}] the thread that was supposed to access the document comes back with EXC_BAD_ACCESS.
This is the code I use to setup the document with core data and the iCloud url is good. This is my first experience with iCloud. What am I doing wrong?
- (void)setupDocument
{
NSURL *url = [self iCloudDocumentsDirectory];
url = [url URLByAppendingPathComponent:#"ZJournalData"];
UIManagedDocument *document = [[ZManagedDocument alloc] initWithFileURL:url];
NSLog(#"%#", document);
[document.persistentStoreOptions setValue:[document.fileURL lastPathComponent] forKey:NSPersistentStoreUbiquitousContentNameKey];
[document.persistentStoreOptions setValue:[[self iCloudDirectory] URLByAppendingPathComponent:#"CoreDataLogs"] forKey:NSPersistentStoreUbiquitousContentURLKey];
if (![[NSFileManager defaultManager] fileExistsAtPath:[url path]]) {
[document saveToURL:url
forSaveOperation:UIDocumentSaveForCreating
completionHandler:^(BOOL success) {
if (success) {
self.managedObjectContext = document.managedObjectContext;
}
}];
} else if (document.documentState == UIDocumentStateClosed) {
[document openWithCompletionHandler:^(BOOL success) {
if (success) {
self.managedObjectContext = document.managedObjectContext;
}
}];
} else {
NSLog(#"%#", document);
self.managedObjectContext = document.managedObjectContext;
}
}