I'm developing an app extension for open mode for my document management application. I have already implemented the import mode which is working fine. But in the open mode , when a third party application tries to open any documents from my storage provider, the following methods of file provider is executing multiple times,kind of an inifinite execution and in turn resulting in a memory warning exception.
- (instancetype)init
- (void)startProvidingItemAtURL:(NSURL *)url completionHandler:(void (^)(NSError *))completionHandler
And also for your reference the complete code fo file provider as follows
- (NSFileCoordinator *)fileCoordinator {
NSFileCoordinator *fileCoordinator = [[NSFileCoordinator alloc] init];
[fileCoordinator setPurposeIdentifier:[self providerIdentifier]];
return fileCoordinator;
}
- (instancetype)init {
self = [super init];
if (self) {
[self.fileCoordinator coordinateWritingItemAtURL:[self documentStorageURL] options:0 error:nil byAccessor:^(NSURL *newURL) {
// ensure the documentStorageURL actually exists
NSError *error = nil;
[[NSFileManager defaultManager] createDirectoryAtURL:newURL withIntermediateDirectories:YES attributes:nil error:&error];
}];
}
return self;
}
- (void)providePlaceholderAtURL:(NSURL *)url completionHandler:(void (^)(NSError *error))completionHandler {
// Should call + writePlaceholderAtURL:withMetadata:error: with the placeholder URL, then call the completion handler with the error if applicable.
NSString* fileName = [url lastPathComponent];
NSURL *placeholderURL = [NSFileProviderExtension placeholderURLForURL:[self.documentStorageURL URLByAppendingPathComponent:fileName]];
NSUInteger fileSize = 0;
// TODO: get file size for file at <url> from model
[self.fileCoordinator coordinateWritingItemAtURL:placeholderURL options:0 error:NULL byAccessor:^(NSURL *newURL) {
NSDictionary* metadata = #{ NSURLFileSizeKey : #(fileSize)};
[NSFileProviderExtension writePlaceholderAtURL:placeholderURL withMetadata:metadata error:NULL];
}];
if (completionHandler) {
completionHandler(nil);
}
}
- (void)startProvidingItemAtURL:(NSURL *)url completionHandler:(void (^)(NSError *))completionHandler {
// Should ensure that the actual file is in the position returned by URLForItemWithIdentifier:, then call the completion handler
NSError* error = nil;
__block NSError* fileError = nil;
//getting the actual fiile from the shared container
NSURL *storeURL = [[NSFileManager defaultManager] containerURLForSecurityApplicationGroupIdentifier:#"group.company.test.NBox"];
storeURL = [storeURL URLByAppendingPathComponent:[url.path lastPathComponent]];
NSData* fileData = [NSData dataWithContentsOfFile:[storeURL path]];
// TODO: get the contents of file at <url> from model
//Writing the file data to the documentStorage location
//[self.fileCoordinator coordinateWritingItemAtURL:url options:0 error:&error byAccessor:^(NSURL *newURL) {
[fileData writeToURL:url options:0 error:&fileError];
//}];
if (error!=nil) {
completionHandler(error);
} else {
completionHandler(fileError);
}
}
- (void)itemChangedAtURL:(NSURL *)url {
// Called at some point after the file has changed; the provider may then trigger an upload
// TODO: mark file at <url> as needing an update in the model; kick off update process
NSLog(#"Item changed at URL %#", url);
}
- (void)stopProvidingItemAtURL:(NSURL *)url {
// Called after the last claim to the file has been released. At this point, it is safe for the file provider to remove the content file.
// Care should be taken that the corresponding placeholder file stays behind after the content file has been deleted.
[self.fileCoordinator coordinateWritingItemAtURL:url options:NSFileCoordinatorWritingForDeleting error:NULL byAccessor:^(NSURL *newURL) {
[[NSFileManager defaultManager] removeItemAtURL:newURL error:NULL];
}];
[self providePlaceholderAtURL:url completionHandler:NULL];
}
Thanks,
Vsh
I'm also trying to develop an app extension for open mode. I haven't been successful yet but I don't get the infinite execution. Looking at your code, it's possible that storeURL in startProvidingItemAtURL: points to something inside your container. If so, then the assignment to fileData would trigger an infinite recursion.
As a test, try setting fileData with a test message like this:
NSString *message = [NSString stringWithFormat:#"This is a test."];
NSData *fileData = [NSKeyedArchiver archivedDataWithRootObject:message];
If that works, then it's a problem with storeURL and you'll have to figure out some different location to get the data.
(Incidentally, I noticed that you commented out the file coordinator in startProvidingItemAtURL:. I also ended up doing that to prevent deadlocks and because there's a note in the documentation that says "Do not use file coordination inside this method." But it's very confusing because the template code for file providers puts the file coordinator in that method!)
Related
I'm developing an app that can receive messages/files while it's on the background via webRTC.
When a file is received, I write it to disk. But when trying to access it later (even between app launches) that file doesn't exist.
User's document folder has NSFileProtectionCompleteUntilFirstUserAuthentication attribute.
I've tried to create the file on disk using NSData's [writeToURL:options:error:], [writeToFile:options:error:]; NSFileManager's [createFileAtPath:contents:attributes:] and also NSFileHandle's methods.
All of them successfully create the file at the designated path/url. Right after creation I check whether file exists with NSFileManager's [attributesOfItemAtPath:error:] which shows me the following:
attributes: {
NSFileCreationDate = "2018-05-07 18:47:50 +0000";
NSFileExtensionHidden = 0;
NSFileGroupOwnerAccountID = 501;
NSFileGroupOwnerAccountName = mobile;
NSFileModificationDate = "2018-05-07 18:47:50 +0000";
NSFileOwnerAccountID = 501;
NSFileOwnerAccountName = mobile;
NSFilePosixPermissions = 420;
NSFileProtectionKey = NSFileProtectionCompleteUntilFirstUserAuthentication;
NSFileReferenceCount = 1;
NSFileSize = 92156;
NSFileSystemFileNumber = 695101;
NSFileSystemNumber = 16777219;
NSFileType = NSFileTypeRegular;
}
[[NSFileManager defaultManager] fileExistsAtPath:[fileURL path]] also shows me that the file exists after write.
Considering it could be a threading problem, I've also tried to write that file putting it on a main thread block, but result is the same. First file seems to be written but, when trying to access it afterwards it's like it never was.
Is there anything I could be missing?
edit: Added function I use to write.
- (void) saveFileData:(NSData *)fileData completionHandler:(void(^)(BOOL success))completionHandler {
NSURL *fileURL = [self fileURL];
NSError *error = nil;
[fileData writeToURL:fileURL options:NSDataWritingAtomic error:&error];
if (error) {
ZLogError(ZLogTypeFile, #"[%#] could not be saved: %#", self.fileKey, error);
completionHandler(NO);
return;
}
ZLogDebug(ZLogTypeFile, #"<file: %#> exists after write:%d", fileURL, [[NSFileManager defaultManager] fileExistsAtPath:[fileURL path]]);
NSDictionary *attributes = [[NSFileManager defaultManager] attributesOfItemAtPath:[fileURL path] error:&error];
ZLogDebug(ZLogTypeFile, #"attributes: %#", attributes);
completionHandler(YES);
}
output comes as (where Documents is the users NSDocumentDirectory in the app)
[file: /Documents/57/Downloads/Images/9d1687ab5f4374a2c00429a24316b5ccd3fb0a67.png] exists after write:1
and getting the file (an image):
- (UIImage *) imageFromURL:(NSURL *)imageURL {
BOOL fileExists = [[NSFileManager defaultManager] fileExistsAtPath:[imageURL path]];
if (!fileExists) {
ZLogDebug(ZLogTypeFile, #"[file: %#] exists: %d", imageURL, fileExists);
return nil;
}
return [UIImage imageWithData:[NSData dataWithContentsOfURL:imageURL]];
}
in log (note I've taken out the long path before Documents directory just here):
[file: /Documents/57/Downloads/Images/9d1687ab5f4374a2c00429a24316b5ccd3fb0a67.png] exists: 1
I am trying to download only image and text(probably HTML string) of a Evernote's note in my iOS app. I have successfully downloaded image from a note . But I did not find any method or process which help me to get text which are written on the note . I have used
ENSDK.framework
-(void)findAllNotes {
NSLog(#"finding all notes..");
[self.session findNotesWithSearch:nil
inNotebook:nil
orScope:ENSessionSearchScopeAll
sortOrder:ENSessionSortOrderNormal
maxResults:255
completion:^(NSArray* findNotesResults,
NSError* findNotesError) {
if (findNotesError) {
[self.session unauthenticate];
NSAssert(NO, #"Could not find notes with error %#", findNotesError);
} else {
[self processFindNotesResults:findNotesResults];
}
}];
}
- (void)processFindNotesResults:(NSArray*)results {
NSParameterAssert(results);
NSLog(#"processing find notes results..");
for (ENSessionFindNotesResult* result in results) {
[self.session downloadNote:result.noteRef
progress:NULL
completion:^(ENNote* note,
NSError* downloadNoteError) {
NSAssert(!downloadNoteError, #"Could not download note with error %#",
downloadNoteError);
[self getDataFromNote:note];
}];
}
}
-(void)getDataFromNote:(ENNote*)note {
for (ENResource* resource in note.resources) {
if ([resource.mimeType hasPrefix:#"image"]) {
UIImage* image = [[UIImage alloc] initWithData:resource.data];
NSArray *paths = NSSearchPathForDirectoriesInDomains(NSDocumentDirectory,NSUserDomainMask, YES);
NSString *docs = [paths objectAtIndex:0];
NSString* path = [docs stringByAppendingFormat:#"/image1.jpg"];
NSData* imageData = [NSData dataWithData:UIImageJPEGRepresentation(image, .8)];
NSError *writeError = nil;
if(![imageData writeToFile:path options:NSDataWritingAtomic error:&writeError]) {
NSLog(#"%#: Error saving image: %#", [self class], [writeError localizedDescription]);
}
}
}
}
The content of the note is available to you in the content property of your variable note; i.e. it's in the content property of an ENNote object.
Also note that in addition to accessing the content directly, the Evernote iOS SDK also includes a special method that makes it easy to display a note's content in a UIWebView:
We've made this easy-- rather than serializing it to HTML and fussing with attached image resources, we've provided a method to generate a single Safari "web archive" from the note; this is a bundled data type which UIWebView natively knows how to load directly.
In my host App I am downloading custom emojis images folder after unzipping successfully saving by below url.
NSURL* shareContainerURL = [[NSFileManager defaultManager] containerURLForSecurityApplicationGroupIdentifier:#"group.company.app.PushServiceExtn"];
And without any issue whenever user tapping on emojis icon all the custom emojis shows in grid in place of keyboard by shareContainerURL.
I have created PushNotification Service Extension where I need to show the custom emojis image by fetching emoji name from payload whenever push comes. using below code.
- (void)didReceiveNotificationRequest:(UNNotificationRequest *)request withContentHandler:(void (^)(UNNotificationContent * _Nonnull))contentHandler {
self.contentHandler = contentHandler;
self.bestAttemptContent = [request.content mutableCopy];
NSDictionary* mediaAttachment = [self.bestAttemptContent.userInfo objectForKey:#"media-attachment"];
NSString* attachType = [mediaAttachment objectForKey:#"attachType"];
if ([attachType isEqualToString:#"emoji"]) {
NSString* strEmojiURL = [mediaAttachment objectForKey:#"url"];
self.bestAttemptContent.title = strEmojiURL;
NSString* emojiName = [[strEmojiURL stringByRemovingPercentEncoding] lastPathComponent];
NSString* strUnpresseedEmojiPath = [self getFullPath:#"emoji/Pressed"];
NSString* strImagePath = [NSString stringWithFormat:#"%#/%# Pressed.png",strUnpresseedEmojiPath, emojiName];
NSURL* fileURL = [NSURL fileURLWithPath:strImagePath];
NSData *imageData = [NSData dataWithContentsOfURL:fileURL];
UIImage *image = [UIImage imageWithData:imageData];
if (image) {
NSError* error;
// CGRect rect = CGRectMake(0,0,50,50);
// #{UNNotificationAttachmentOptionsThumbnailClippingRectKey:(__bridge NSDictionary*)CGRectCreateDictionaryRepresentation(rect)} option dict;
UNNotificationAttachment * attachement = [UNNotificationAttachment attachmentWithIdentifier:strImagePath.lastPathComponent URL:fileURL options:nil error:&error];
if (error == nil) {
self.bestAttemptContent.attachments = #[attachement];
}
}
}
self.contentHandler(self.bestAttemptContent);
}
- (NSString *)getFullPath:(NSString *)file {
NSURL* shareContainerURL = [[NSFileManager defaultManager] containerURLForSecurityApplicationGroupIdentifier:#"group.company.app.PushServiceExtn"];
return [shareContainerURL.path stringByAppendingPathComponent: file];
}
I am always getting valid url but second time I get image nil but first time of every image it works. Couldn't get the root cause. Any help would appreciated.
Below is the error that occurred second time for every image.
2016-10-27 17:34:59.081026 pushNotificationServiceExtension[651:34632] Attachement Error = Error Domain=UNErrorDomain Code=100 "Invalid attachment file URL" UserInfo={NSLocalizedDescription=Invalid attachment file URL}
Also please let me know how to view App Group shared container, Couldn't find way to view the files contained inside.
*Update = * File is getting deleted after showing in push notification.
From apple "UNNotificationAttachment Once validated, attached files are moved into the attachment data store so that they can be accessed by the appropriate processes. Attachments located inside an app’s bundle are copied instead of moved."
So I copy my emoji image to duplicate URL and assign it to UNNotificationAttachment.
if (imageFileURL) {
NSURL* duplicateImageURL = [self getFullPath:#"EmojiAttachment"];
if (![fileManager fileExistsAtPath:duplicateImageURL.path]) {
[fileManager createDirectoryAtPath:duplicateImageURL.path withIntermediateDirectories:NO attributes:nil error:&error];
}
emojiName = [NSString stringWithFormat:#"%# Unpressed.png", emojiName];
duplicateImageURL = [duplicateImageURL URLByAppendingPathComponent:emojiName];
[[NSFileManager defaultManager]copyItemAtURL:imageFileURL toURL:duplicateImageURL error:&error];
UNNotificationAttachment * attachement = [UNNotificationAttachment attachmentWithIdentifier:emojiName URL:[duplicateImageURL filePathURL] options:nil error:&error];
if (error == nil) {
self.bestAttemptContent.attachments = #[attachement];
}
else{
NSLog(#"Attachement Error = %#",error);
}
}
I am downloading a file using NSURLSessionDownloadTask. it get downloaded and saved. and I can display data normally.
Unfortunitly I only have the iOS simulator to test. What is happenning if I close the app with the stop button on Xcode. then I relaunch, the file is no longer exists.
But, If I closed it by removing it from apps. running list by clicking cmd + shift + H twice. and reluanch it by tapping app. on simulator. I find the file.
BOOL found = [[NSFileManager defaultManager] fileExistsAtPath:path];
Is this a simulator problem? and I shouldn't worry about it in a real device?
Actually, I just managed to test on iPhone. exactly the same behaviour!! Any explanation.
I call this on the NSURLSessionDownloadTask which I sent a destination block that returns the destination to save:
- (NSURLSessionDownloadTask *)downloadTaskWithRequest:(NSURLRequest *)request
progress:(NSProgress * __autoreleasing *)progress
destination:(NSURL * (^)(NSURL *targetPath, NSURLResponse *response))destination
completionHandler:(void (^)(NSURLResponse *response, NSURL *filePath, NSError *error))completionHandler
Destination block code:
NSURL *documentsDirectoryURL = [[NSFileManager defaultManager] URLForDirectory:NSLibraryDirectory inDomain:NSUserDomainMask appropriateForURL:nil create:NO error:nil];
return [documentsDirectoryURL URLByAppendingPathComponent:[response suggestedFilename]];
Try to log documentsDirectoryURL and you will see that it is different each time you launch the app. Solution is to save between launches not an absolute URL, but a path relative to NSLibraryDirectory directory. I had the same problem and I have solved it with two methods:
// [self uploadFolder] returns a folder in NSLibraryDirectory
+ (NSString*)relativePathForURL:(NSURL*)url
{
NSURL *uploadFolderURL = [self uploadFolder];
if ([url.baseURL isEqual:uploadFolderURL])
{
return url.relativeString;
}
else if ([url.absoluteString hasPrefix:uploadFolderURL.absoluteString])
{
return [url.absoluteString substringFromIndex:uploadFolderURL.absoluteString.length];
}
return nil;
}
+ (NSURL*)urlForRelativePath:(NSString*)path
{
return [NSURL URLWithString:path relativeToURL:[self uploadFolder]];
}
They should be used following way:
Download file and move it to NSLibraryDirectory folder with some URL
savedURL.
Call relativePath = [self relativePathForURL:savedURL] and save it.
When app is relaunched call savedURL = [self urlForRelativePath:relativePath]
savedURL is valid now.
Heres the solution;
- (void)URLSession:(NSURLSession *)session downloadTask:(NSURLSessionDownloadTask *)downloadTask didFinishDownloadingToURL:(NSURL *)location{
//location is the temporary "destination" which you returned (can be Temp directory) and it now contains the downloaded file. Now copy the file to a new location (eg documents directory) for future use.
BOOL fileCopied = [[[NSFileManager defaultManager] copyItemAtURL:location toURL:documentsDirectoryURLForSavingFileForFutureUe error:&error];
}
What is the right way to move a core data model that allows external storage into a UIManagedDocument? I have a core data store that I am trying to move into a UIManagedDocument. I have users with lots of data. Some of it is 2 - 3 minute audio clips. I am subclassing UIManaged document and overriding the configurePersistentStoreCoordinatorForURL. Then copying the files over into the UIManagedDocument bundle. It all seems to work great accept for the Audio files that are stored externally. In my Core Data Model, my audio files are set up to allow external storage. These files are no longer connected after the move and when I try to play them int the app after the move, I get an audio session error. Thanks for any help you can offer on the topic. Here is my code that I am using to override the UIMD…
- (BOOL)configurePersistentStoreCoordinatorForURL:(NSURL *)storeURL
ofType:(NSString *)fileType
modelConfiguration:(NSString *)configuration
storeOptions:(NSDictionary *)storeOptions
error:(NSError *__autoreleasing *)error{
[self printFileDir];
// If legacy store exists, create a UIManaged Document and store it there
NSURL *docsDir = [[[NSFileManager defaultManager] URLsForDirectory:NSDocumentDirectory inDomains:NSUserDomainMask] lastObject];
NSURL *legacyStoreURL = [docsDir URLByAppendingPathComponent:#"RRLevelBook.sqlite"];
NSFileManager* fileManager = [NSFileManager defaultManager];
if ([fileManager fileExistsAtPath:legacyStoreURL.path])
{
NSLog(#"Old db exists");
//swap files
NSURL *storeURLshm = [NSURL URLWithString:[[storeURL absoluteString] stringByAppendingString:#"-shm"]];
NSURL *storeURLwal = [NSURL URLWithString:[[storeURL absoluteString] stringByAppendingString:#"-wal"]];
NSURL *supportFiles = [[storeURL URLByDeletingLastPathComponent] URLByAppendingPathComponent:#".persistenStore_SUPPORT"];
NSURL *legacyStoreURLshm = [NSURL URLWithString:[[legacyStoreURL absoluteString] stringByAppendingString:#"-shm"]];
NSURL *legacyStoreURLwal = [NSURL URLWithString:[[legacyStoreURL absoluteString] stringByAppendingString:#"-wal"]];
NSURL *legacySupportFiles = [[legacyStoreURL URLByDeletingLastPathComponent] URLByAppendingPathComponent:#".RRLevelBook_SUPPORT"];
NSError* thisError = nil;
//swap the sqlite file
[fileManager replaceItemAtURL:storeURL
withItemAtURL:legacyStoreURL
backupItemName:nil
options:NSFileManagerItemReplacementUsingNewMetadataOnly
resultingItemURL:nil
error:&thisError];
//swap the -shm file
[fileManager replaceItemAtURL:storeURLshm
withItemAtURL:legacyStoreURLshm
backupItemName:nil
options:NSFileManagerItemReplacementUsingNewMetadataOnly
resultingItemURL:nil
error:&thisError];
//swap the -wal file
[fileManager replaceItemAtURL:storeURLwal
withItemAtURL:legacyStoreURLwal
backupItemName:nil
options:NSFileManagerItemReplacementUsingNewMetadataOnly
resultingItemURL:nil
error:&thisError];
//Move in the Support files
[fileManager moveItemAtURL:legacySupportFiles toURL:supportFiles error:nil];
//delete old files that have been swapped
[fileManager removeItemAtURL:legacyStoreURL error:nil];
[fileManager removeItemAtURL:legacyStoreURLwal error:nil];
[fileManager removeItemAtURL:legacyStoreURLshm error:nil];
[fileManager removeItemAtURL:legacySupportFiles error:nil];
NSLog(#"%#",[thisError localizedDescription]);
}
[self printFileDir];
return [super configurePersistentStoreCoordinatorForURL:storeURL ofType:fileType modelConfiguration:configuration storeOptions:storeOptions error:error];
}
Well, here is what I ended up doing - for better or worse:
Open the new UIManagedDocument.
Open up the legacy Core Data Model.
Copy each audio file (NSData) from Legacy CoreData Context to the UIManagedDocument Context.
Reconnect all relationships based on the Legacy CoreData Context.
NSManagedObjectContext *legacyMOC = [[NSManagedObjectContext alloc]init];
[legacyMOC setPersistentStoreCoordinator:psc];
//fetch all audio recordings from legacyStore
NSArray *legacyRecordingArray = [self fetchAudioRecordingsfrom:legacyMOC];
//fetch all audio recordings form UIMDStore
NSArray *uimdRecordingArray = [self fetchAudioRecordingsfrom:self.managedObjectContext];
//for each audio recording, copy the audio object from legacy and save it to UIMDStore
for (int i = 0; i < legacyRecordingArray.count; i++) {
//save audio to core data
RunningRecord *legacyRR = (RunningRecord *)legacyRecordingArray[i];
RunningRecord *uimdRR = (RunningRecord *)uimdRecordingArray[i];
uimdRR.audioData = [NSData dataWithData:legacyRR.audio.file];
uimdRR.audio.file = nil;
}
if (![self.managedObjectContext save:&error]) {
NSLog(#"Whoops, couldn't save: %#", [error localizedDescription]);
}