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.
Related
I'm trying to access a file to pull a copy into my app so that users can associate it with relevant information. It used to work just fine up until recently, and now I suddenly am getting the following message:
Failed to read file, error Error Domain=NSCocoaErrorDomain Code=257 "The file “[File name]” couldn’t be opened because you don’t have permission to view it." UserInfo={NSFilePath=/private/var/mobile/Library/Mobile Documents/com~apple~CloudDocs/[File name], NSUnderlyingError=0x281b88690 {Error Domain=NSPOSIXErrorDomain Code=1 "Operation not permitted"}}
This is the code that's throwing the error:
//AppDelegate.m
-(BOOL)application:(UIApplication *)app openURL:(NSURL *)url options:(NSDictionary<UIApplicationOpenURLOptionsKey,id> *)options
{
if (![url.pathExtension isEqualToString:#"pdf"] && ![url.pathExtension isEqualToString:#"png"] && ![url.pathExtension isEqualToString:#"jpg"] && ![url.pathExtension isEqualToString:#"jpeg"]){
return false;
}
NSError* error = nil;
NSString *path = [url path];
NSData *data = [NSData dataWithContentsOfFile:path options: 0 error: &error];
if(data == nil) {
NSLog(#"Failed to read file, error %#", error);
}
//Do stuff with the file
return true;
}
I did update to xcode 11 and iOS 13, so there may have been a change there that I wasn't aware of.
It turns out there's a "using" function that tells the app its accessing files outside of it's sandbox. The methods startAccessingSecurityScopedResource and stopAccessingSecurityScopedResource on NSURL need to be wrapped around the code using the url, like so:
BOOL isAcccessing = [url startAccessingSecurityScopedResource];
NSError* error = nil;
NSString *path = [url path];
NSData *data = [NSData dataWithContentsOfFile:path options: 0 error: &error];
if(data == nil) {
NSLog(#"Failed to read file, error %#", error);
}
if (isAccessing) {
[url stopAccessingSecurityScopedResource];
}
I'm not sure if there's anything specific to iOS 13 that requires this when it didn't previously, but that is the only real change between it working and not working.
Jordan has a great answer! Here's the version translated to Swift
let isAccessing = url.startAccessingSecurityScopedResource()
// Here you're processing your url
if isAccessing {
url.stopAccessingSecurityScopedResource()
}
As I encountered this myself and the comment to Jordan's answer confirmed this happens only on the real device. Simulator has no such an issue
Some background for this issue, I'm trying to include what I think may be relevant to help understand the context.
I am currently adding an linked library which used Core Data to save some user information and a feature which adds an Entity to the pre-existing Core Data model already in the app. Each managedObjectContext has its own instance when created (verified) as well as its own PSC and MOM and neither interact with the other's entities(thus seem to be independent).
The entirety of the following code, errors, and (I believe issue) is in the Main Target of the app. (Hopefully) not the newly added linked library.
The saveContext method is:
- (void)saveContext {
dispatch_async(dispatch_get_main_queue(), ^{
NSError *error = nil;
// Register
[[NSNotificationCenter defaultCenter] addObserver:self selector:#selector(myManagedObjectContextDidSaveNotificationHandler:) name:NSManagedObjectContextDidSaveNotification object:self.managedObjectContext];
if (self.managedObjectContext != nil) {
if ([self.managedObjectContext hasChanges]) {
BOOL success = [self.managedObjectContext save:&error];
if (!success) {
[Error showErrorByAppendingString:NSLocalizedString(#"UnableToSaveChanges", nil) withError:error];
} else {
//
}
}
}
// Unregister
[[NSNotificationCenter defaultCenter] removeObserver:self name:NSManagedObjectContextDidSaveNotification object:self.managedObjectContext];
});
}
When called, error = nil, success = NO and by forcing the compiler past the exception I get the following:
CoreData: error: exception during obtainPermenantIDsForObjects: Updating max pk failed: attempt to write a readonly database with userInfo of { NSSQLiteErrorDomain = 1032;
}
I have googled, "NSSQLiteErrorDomain = 1032", "obtainPermenantIDsForObjects", and "CoreData readonly database". It does appear that the key primary key for each object is the same, but I am setting that value, I believe sqlite is. I have not found any solutions to help with this. I do have the argument passed on launch, "Concurrency Debug 1" set to on.
I have not implemented obtainPermenantIDsForObjects and I've searched the whole project and cant find its implementation so I think CoreData is using this.
The saveContext method is called on the main queue because thats how my predecessors rolled out the code and I don't have time at the moment to deal with it.
The method calling saveContext (from a background thread):
- (NSMutableArray *)convertRawStepDataTo:(NSMutableArray*)steps
withDates:(NSMutableArray*)dates
inManagedObjectContext:(NSManagedObjectContext*)theMOC {
NSMutableArray *theStepsArray = [[NSMutableArray alloc] init];
// prepare values for chart
AppDelegate *delegate = (AppDelegate *)[UIApplication sharedApplication].delegate;
StepSelector *theSelector = [[StepSelector alloc] init];
NSString* apiSelectionForStep = [theSelector getCurrentSelectionString];
for (int iter = 0; iter < steps.count; iter++) {
NSNumber *currStepValue = [steps objectAtIndex:iter];
// NSNumber *stepCountforIter = [NSNumber numberWithLong:[[steps objectAtIndex:iter] longValue]];
NSNumber* dateForIter = [NSNumber numberWithLong:[[dates objectAtIndex:iter] longLongValue]];
Step *step = [delegate addStepObjectToPersistentStorewithAPI:apiSelectionForStep
andStep:stepCountforIter
andDate:dateForIter
forMOC:theMOC];
[theStepsArray addObject:step];
if (VERBOSE) {
NSLog(#"This is step number %d, with object ID: %#", count, [theMOC objectWithID:step.objectID]);
count++;
}
}
[delegate saveContext];
return theStepsArray;
}
Thats all I can think that might help. The source for the MOC in the main target is the appDelegate which is where all the core data code was written initially.
EDIT Here is the requested PSC code. The store is located in the documents directory. I discovered that these objects are being saved to the Persistent Store.. but the error is still occurs. Se below for PSC code:
- (NSPersistentStoreCoordinator *)persistentStoreCoordinator {
if (persistentStoreCoordinator != nil) {
return persistentStoreCoordinator;
}
NSURL *storeUrl = [self getStoreURL];
// Rollback journalling mode...
NSDictionary *options = [NSDictionary dictionaryWithObjectsAndKeys:
[NSNumber numberWithBool:YES], NSMigratePersistentStoresAutomaticallyOption,
[NSNumber numberWithBool:YES],NSInferMappingModelAutomaticallyOption,
NSFileProtectionComplete, NSFileProtectionKey,
#{#"journal_mode": #"TRUNCATE"}, NSSQLitePragmasOption, nil];
persistentStoreCoordinator = [[NSPersistentStoreCoordinator alloc] initWithManagedObjectModel: [self managedObjectModel]];
NSError *error = nil;
self.persistentStore = [persistentStoreCoordinator addPersistentStoreWithType:NSSQLiteStoreType configuration:nil URL:storeUrl options:options error:&error];
if (!self.persistentStore) {
NSLog(#"Error: %#",error);
[Error showErrorByAppendingString:NSLocalizedString(#"UnableToFindDatabaseFile", nil) withError:error];
}
return persistentStoreCoordinator;
}
-(NSURL *)getStoreURL {
NSString *storePath = [[self applicationDocumentsDirectory] stringByAppendingPathComponent: kSQLFILENAME];
/*
Set up the store.
For the sake of illustration, provide a pre-populated default store.
*/
NSFileManager *fileManager = [NSFileManager defaultManager];
// If the expected store doesn't exist, copy the default store.
if (![fileManager fileExistsAtPath:storePath]) {
NSString *defaultStorePath = [[NSBundle mainBundle] pathForResource:SQLFILEPATHRESOURCE ofType:#"sqlite"];
if (defaultStorePath) {
[fileManager copyItemAtPath:defaultStorePath toPath:storePath error:NULL];
}
}
NSURL *storeUrl = [NSURL fileURLWithPath:storePath];
return storeUrl;
}
The NSSQLiteErrorDomain key means that this error came from SQLite, and that Core Data is passing it back to you. SQLite defines error 1032 as follows:
The SQLITE_READONLY_DBMOVED error code is an extended error code for SQLITE_READONLY. The SQLITE_READONLY_DBMOVED error code indicates that a database cannot be modified because the database file has been moved since it was opened, and so any attempt to modify the database might result in database corruption if the processes crashes because the rollback journal would not be correctly named.
...which appears to mean that SQLite is making the persistent store file read only because something has happened to it since it was opened, and SQLite is trying to prevent data corruption.
I don't see anything in the code you've posted that is obviously at fault, at least as far as the error code description goes. So I wonder, are you doing anything anywhere else that would directly affect the persistent store file (i.e. touching the file in any way at all instead of going through Core Data fetch/save calls)?
The mention of the rollback journal in the error code description makes me wonder if setting journal_mode to TRUNCATE is related. If it were me, I'd remove that (I don't know what it's intended to accomplish here) or set it to DELETE. At least for testing purposes, anyway, in the hope of understanding the problem better.
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.
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)
I am running into an error when I try to turn iCloud sync off for a UIDocument file. Wondering if anyone else has run into this. Here's the scenario:
I create a UIDocument file locally in the app sandbox and then make the following call to begin syncing the file with iCloud:
[[NSFileManager defaultManager] setUbiquitous:YES itemAtURL:localPathURL destinationURL:cloudPathURL error:&error];
Everything's going swimmingly.
Now I want to stop iCloud syncing for this file.
I first make sure that the file has at least been synced with iCloud by calling the following:
- (BOOL) isDataFileSyncedWithCloud
{
if (![self isICloudSupported] || ![self isUsingICloudForFiles])
return NO;
NSURL* file = [self getFileURLToCloudDatafile];
NSNumber* isInCloudNum = nil;
if ([file getResourceValue:&isInCloudNum forKey:NSURLIsUbiquitousItemKey error:nil])
{
// If the item is in iCloud, see if it is downloaded and uploaded.
if ([isInCloudNum boolValue])
{
NSNumber* isDownloadedNum = nil;
if ([file getResourceValue:&isDownloadedNum forKey:NSURLUbiquitousItemIsDownloadedKey error:nil])
{
NSNumber* isUploadedNum = nil;
if ([file getResourceValue:&isUploadedNum forKey:NSURLUbiquitousItemIsUploadedKey error:nil])
{
return ([isDownloadedNum boolValue] && [isUploadedNum boolValue]);
}
}
}
}
return NO;
}
The above returns YES, indicating the file has been synced (or so I thought...)
So, now I go ahead and make the call below to stop iCloud syncing for this file:
[[NSFileManager defaultManager] setUbiquitous:NO itemAtURL:localPathURL destinationURL:cloudPathURL error:&error];
and I get the following error: "The operation couldn't be completed. (LibrarianErrorDomain error 2 - Cannot disable syncing on a unsynced item.)"
Any idea why this error is occurring and how I can get rid of it? I would have thought that my file was fully synced...
Thanks in advance!
I figured it out. To disable iCloud syncing, I was accidentally calling:
[[NSFileManager defaultManager] setUbiquitous:NO itemAtURL:localPathURL destinationURL:cloudPathURL error:&error];
instead of
[[NSFileManager defaultManager] setUbiquitous:NO itemAtURL:cloudPathURL destinationURL:localPathURL error:&error];