Persisting bookmark in core-data - objective-c

I have an OSX application that is supposed to have a list of files from anywhere in the user's disk.
The first version of the app saves the path to these files in a core-data model.
However, if the file is moved or renamed, the tool loses its purpose and the app can crash.
So I decided to use bookmarks. It seems to be working, but every time I try to recover the data, I get the old path of the files. Why is that? What am I missing?
My core-data entity uses a binary data field to persist the bookmark.
The bookmark itself is done like this:
NSData * bookmark = [filePath bookmarkDataWithOptions:NSURLBookmarkCreationMinimalBookmark
includingResourceValuesForKeys:NULL
relativeToURL:NULL
error:NULL];
And on loading the application, I have a loop to iterate all the table and recover the bookmark like this:
while (object = [rowEnumerator nextObject]) {
NSError * error = noErr;
NSURL * bookmark = [NSURL URLByResolvingBookmarkData:[object fileBookmark]
options:NSURLBookmarkResolutionWithoutUI
relativeToURL:NULL
bookmarkDataIsStale:NO
error:&error];
if (error != noErr)
DDLogCError(#"%#", [error description]);
DDLogCInfo(#"File Path: %#", [bookmark fileReferenceURL]);
}
If I rename the file, the path is null. I see no difference between storing this NSData object and a string with the path. So I am obviously missing something.
Edit:
I also often get an error like this: CFURLSetTemporaryResourcePropertyForKey failed because it was passed this URL which has no scheme.
I appreciate any help, thanks!

I can't find any issues in my code, so I changed it.
After looking for the reason of the "no scheme" message, I came to the conclusion some third-party application is required for this code to work, and that's undesirable.
I am now using aliases. This is how I create them:
FSRef fsFile, fsOriginal;
AliasHandle aliasHandle;
NSString * fileOriginalPath = [[filePath absoluteString] stringByReplacingPercentEscapesUsingEncoding:NSUTF8StringEncoding];
OSStatus status = FSPathMakeRef((unsigned char*)[fileOriginalPath cStringUsingEncoding: NSUTF8StringEncoding], &fsOriginal, NULL);
status = FSPathMakeRef((unsigned char*)[fileOriginalPath cStringUsingEncoding: NSUTF8StringEncoding], &fsFile, NULL);
OSErr err = FSNewAlias(&fsOriginal, &fsFile, &aliasHandle);
NSData * aliasData = [NSData dataWithBytes: *aliasHandle length: GetAliasSize(aliasHandle)];
And now I recover the path like this:
while (object = [rowEnumerator nextObject]) {
NSData * aliasData = [object fileBookmark];
NSUInteger aliasLen = [aliasData length];
if (aliasLen > 0) {
FSRef fsFile, fsOriginal;
AliasHandle aliasHandle;
OSErr err = PtrToHand([aliasData bytes], (Handle*)&aliasHandle, aliasLen);
Boolean changed;
err = FSResolveAlias(&fsOriginal, aliasHandle, &fsFile, &changed);
if (err == noErr) {
char pathC[2*1024];
OSStatus status = FSRefMakePath(&fsFile, (UInt8*) &pathC, sizeof(pathC));
NSAssert(status == 0, #"FSRefMakePath failed");
NSLog(#"%#", [NSString stringWithCString: pathC encoding: NSUTF8StringEncoding]);
} else {
NSLog(#"The file disappeared!");
}
} else {
NSLog(#"CardCollectionUserDefault was zero length");
}
}
However, I am still curious on why my previous code failed. I appreciate any thoughts on that. Thanks!

Related

Upgrading an Xcode Objective-C IOS game with Unity3d based project

I have an old Xcode/ObC quiz game that I launched quite a few years ago, pre-swift, that has been, and still is quite successful for me. At least the local version.
I am now at the finish line rewriting this game in Unity3d c#.
Something I have been thinking about lately is how to to maintain the "old" statistics that is saved in a plist file in IOS. I have been google this but I would need some more information to really understand how to proceed.
What will happen with the stats-plist file when I upgrade the current Xcode/ObC with the new Unity-based project, will it still be there and is it possible to easily find it? This particular plist is added when the first player is added and then updated with stats and new players.
Is there a good, and easy, way reading plist from Unity and convert to a normal text file?
To be able to find the file from Unity I am thinking of launching a maintenance release of the ObC based game and only copy this plist file to another directory (Document) to prepare for the new big release. When starting the Unity-based game for the first time I could then read the copied file and process so the player do not lose his/her stats.
The problem I have is that the only time I have updated the actual ObC code the last 5 - 6 years is when I updated the app from 32 to 64 bit so my skills on ObC is very limited at the moment.
I have been thinking of using something like this for the plist:
NSFileManager *filemgr;
filemgr = [NSFileManager defaultManager];
if ([filemgr copyItemAtPath: #"/tmp/myfile.txt" toPath: #"/Users/demo/newfile.txt" error: NULL] == YES)
NSLog (#"Copy successful");
else
NSLog (#"Copy failed");
I would really appreciate some advise how I should process this.
Here is some code you can use to list the doc and app support contents. I guard this with the #define as I do not want this in the final app. I also use it to perform some cleanup (the commented out stuff) that you can use if you need to delete something.
#ifdef DEBUG
// Auxiliary function to list directory contents
+ (void) docList
{
NSFileManager * fileManager = NSFileManager.defaultManager;
NSURL * docUrl = [NSFileManager.defaultManager URLsForDirectory:NSDocumentDirectory inDomains:NSUserDomainMask].firstObject;
NSString * docPath = docUrl.path;
NSLog( #"User document directory" );
NSLog( #"%# listing follows", docPath );
[fmUtil traverse:docPath tab:#"\t" fileManager:fileManager];
}
// Auxiliary function to list application support path
+ (void) supList
{
NSFileManager * fileManager = NSFileManager.defaultManager;
NSURL * docUrl = [NSFileManager.defaultManager URLsForDirectory:NSApplicationSupportDirectory inDomains:NSUserDomainMask].firstObject;
NSString * docPath = docUrl.path;
NSLog( #"Application support directory" );
NSLog( #"\t%#", docPath );
NSLog( #"\tAppending bundle identifier" );
docPath = [docPath stringByAppendingPathComponent:NSBundle.mainBundle.bundleIdentifier];
NSLog( #"\t%#", docPath );
NSLog( #"%# listing follows", docPath );
[fmUtil traverse:docPath tab:#"\t" fileManager:fileManager];
}
+ (void) traverse:(NSString *)root tab:(NSString *)tab fileManager:(NSFileManager *)fileManager
{
NSArray * dir = [fileManager contentsOfDirectoryAtPath:root error:NULL];
for ( NSString * s in dir )
{
// See if this is a directory or not
NSString * t = [root stringByAppendingPathComponent:s];
BOOL isDir = NO;
[fileManager fileExistsAtPath:t isDirectory:& isDir];
if ( isDir )
{
// Report
NSLog(#"%#%#/",tab,s);
// Traverse
[fmUtil traverse:t tab:[tab stringByAppendingString:#"\t"]
fileManager:fileManager];
}
else
{
// Get size of normal file
NSDictionary * fa = [fileManager attributesOfItemAtPath:t error:NULL];
NSNumber * fz = [fa objectForKey:NSFileSize];
NSDate * fm = [fa objectForKey:NSFileModificationDate];
unsigned long long n = fz.unsignedLongLongValue;
// Some formatting
NSString * f = s;
while ( f.length < 50 )
{
f = [f stringByAppendingString:#" "];
}
NSLog(#"%#%# %15llu bytes (%#)", tab, f, n, [fm descriptionWithLocale:NSLocale.currentLocale] );
// To delete something for test purposes ...
/*
if ( [t.lastPathComponent isEqualToString:#"P5041-1-BuildingStatistics"] && [fileManager removeItemAtPath:t error:NULL] )
{
NSLog( #"%#%# now xxxxxxxxxxxxxxxxxx", tab, f );
}
if ( [t.lastPathComponent isEqualToString:#"index"] && [fileManager removeItemAtPath:t error:NULL] )
{
NSLog( #"%#%# now xxxxxxxxxxxxxxxxxx", tab, f );
}
*/
}
}
}
#endif
Put this in your app delegate e.g. inside application:didFinishLaunchingWithOptions:.
Well, today I started to look back on this "problem" and after testing it turned out not to be a problem at all. I tried initially to find the path via my Objective-C code (this post) but when testing I realised that the files were stored in the (IOS game) Document directory, the same as "Application.persistentDataPath". In Unity I used the following path's:
filePathPlayerData = Application.persistentDataPath + "/playerdata.plist";
filePathPlayerStats = Application.persistentDataPath + "/playerstatistics.plist";
Reading and process the file is easy as well as it is XML structure. However due to the simple format I opened the files as text files wrote my own simple parser in c# (unity), rather than using an XML parser. The whole "problem" turned out not to be any problem.

JSON data has "bad" characters that causes NSJSONSerialization to die

I am using the ATV version of TVH Client - if you haven't looked at this it's worth looking at TVH to glimpse madness in the face. It has a JSON API that sends back data, including the electronic program guide. Sometimes the channels put accented characters in their data. Here is an example, this is the result from Postman, note the ? char in the description:
{
"eventId": 14277,
"episodeId": 14278,
"channelName": "49.3 CometTV",
"channelUuid": "02fe96403d58d53d71fde60649bf2b9a",
"channelNumber": "49.3",
"start": 1480266000,
"stop": 1480273200,
"title": "The Brain That Wouldn't Die",
"description": "Dr. Bill Cortner and his fianc�e, Jan Compton , are driving to his lab when they get into a horrible car accident. Compton is decapitated. But Cortner is not fazed by this seemingly insurmountable hurdle. His expertise is in transplants, and he is excited to perform the first head transplant. Keeping Compton's head alive in his lab, Cortner plans the groundbreaking yet unorthodox surgery. First, however, he needs a body."
},
If this data is fed into NSJSONSerialization, it returns an error. So to avoid this, the data is first fed into this function:
+ (NSDictionary*)convertFromJsonToObjectFixUtf8:(NSData*)responseData error:(__autoreleasing NSError**)error {
NSMutableData *FileData = [NSMutableData dataWithLength:[responseData length]];
for (int i = 0; i < [responseData length]; ++i) {
char *a = &((char*)[responseData bytes])[i];
if ( (int)*a >0 && (int)*a < 0x20 ) {
((char*)[FileData mutableBytes])[i] = 0x20;
} else {
((char*)[FileData mutableBytes])[i] = ((char*)[responseData bytes])[i];
}
}
NSDictionary* json = [NSJSONSerialization JSONObjectWithData:FileData //1
options:kNilOptions
error:error];
if( *error ) {
NSLog(#"[JSON Error (2nd)] output - %#", [[NSString alloc] initWithData:responseData encoding:NSUTF8StringEncoding]);
NSDictionary *userInfo = #{ NSLocalizedDescriptionKey:[NSString stringWithFormat:NSLocalizedString(#"Tvheadend returned malformed JSON - check your Tvheadend's Character Set for each mux and choose the correct one!", nil)] };
*error = [[NSError alloc] initWithDomain:#"Not ready" code:NSURLErrorBadServerResponse userInfo:userInfo];
return nil;
}
return json;
}
This cleans up the case when there is a control character in the data, but not an accent like the case above. When I feed in that data I get the "Tvheadend returned malformed JSON" error.
One problem is that the user can change the character set among a limited number of selections, and the server does not tell the client what it is. So one channel might use UTF8 and another ISO-8891-1, and there is no way to know which to use on the client side.
So: can anyone offer a suggestion on how to process this data so we feed clean strings into NSJSONSerialization?
I still do not know the root cause of the problem I am seeing - the server is sending not only high-bit characters like the ones I noted above, but I also found that it contained control characters too! Looking over other threads it appears I am not the only one seeing this problem, so hopefully others will find this useful...
The basic trick is to convert the original data from the server to a string using UTF8. If there are any of these "bad" chars in it, the conversion will fail. So you check if the resulting string is empty, and try another charset. Eventually you'll get data back. Now you take that string and strip out any control chars. Now you take that result, which is now UTF8 "clean", and convert it back to UTF8 NSData. That will pass through the JSON conversion without error. Phew!
Here is the solution I finally used:
// ... the original data from the URL is in responseData
NSString *str = [[NSString alloc] initWithData:responseData encoding:NSUTF8StringEncoding];
if ( str == nil ) {
str = [[NSString alloc] initWithData:responseData encoding:NSISOLatin1StringEncoding];
}
if ( str == nil ) {
str = [[NSString alloc] initWithData:responseData encoding:NSASCIIStringEncoding];
}
NSCharacterSet *controls = [NSCharacterSet controlCharacterSet];
NSString *stripped = [[str componentsSeparatedByCharactersInSet:controls] componentsJoinedByString:#""];
NSData *data = [stripped dataUsingEncoding:NSUTF8StringEncoding];
NSDictionary* json = [NSJSONSerialization JSONObjectWithData:data options:kNilOptions error:&error];
I hope someone finds this useful!

Sitecore Mobile SDK: how to read the value from a linkedItem in a DropLink

I am creating a native IOS app by using the Sitecore Mobile SDK. So far I am able to read the items I need but I got stuck on reading the fieldvalue from a linked item in a Droplink field.
I use this code:
SCApiContext* context = [SCApiContext contextWithHost: #"http://<myhost>/-/item"];
SCItemsReaderRequest* request = [ SCItemsReaderRequest new ];
request.requestType = SCItemReaderRequestQuery;
request.request = #"/sitecore/content/Home/descendant::*[##templatename='Content item']";
request.flags = SCItemReaderRequestReadFieldsValues;
request.fieldNames = [ NSSet setWithObjects: #"Content title", #"Content author", #"Content introduction", #"Content date", #"Content body" , nil ];
[context itemsReaderWithRequest: request]( ^(id result, NSError* error)
{
NSArray* items = result;
for (SCItem* item in result)
{
// get the author
__block NSString *author = #"empty";
SCField *dropLinkField = [item fieldWithName: #"Content author"];
[dropLinkField fieldValueReader]( ^(id result, NSError *error)
{
if (!error)
{
SCItem *linkedItem = result;
// TODO: author is not yet filled
NSSet *fieldsSet = [NSSet setWithObjects:#"Firstname", nil];
// this method seems to be skipped
[linkedItem fieldsReaderForFieldsNames:fieldsSet]( ^(id result2, NSError *error2)
{
if (!error2)
{
NSDictionary *fields = result2;
SCField *field_ = [fields objectForKey: #"Firstname"];
author = field_.rawValue;
}
});
}
});
}
}
The original item is read and I can read the field values of the droplink field. It also seems that I can read the linked Item, because I can write it's itempath to the log. But when I try to read a field from the linked item, it fails and the "fieldsReaderForFieldsNames" method seems to be skipped.
I'm obviously doing something wrong here, but seem to overlook the issue...
EDIT:
I forgot to mention that I use Sitecore 7, not sure if it makes a difference.
I have added the lines above that creates the SCApiContext and SCItemReaderRequest.
I use anonymous access and in the "site settings" I use
itemwebapi.mode="StandardSecurity"
itemwebapi.access="ReadOnly"
itemwebapi.allowanonymousaccess="true"
I just thought that I found the issue, because I did not set the Field Remote Read rights on several fields. However, setting that permission did not resolve it and other fields without the Field Remote Read set, did return in the API.
Sitecore iOS SDK operations (from the list below) are executed asynchronously on the background operation queue.
* fieldValueReader
* fieldsReaderForFieldsNames
This does not guarantee that author data is downloaded at the moment you are accessing it.
Please use downloaded items and fields in the completion callback block to ensure they exist on your iPhone.
[linkedItem fieldsReaderForFieldsNames:fieldsSet]( ^(id result2, NSError *error2)
{
NSLog(#"Read author field");
if (!error2)
{
NSLog(#"No error");
NSDictionary *fields = result2;
SCField *field_ = [fields objectForKey: #"Firstname"];
author = field_.rawValue;
// Now all required fields will
// definitely be downloaded by the time you create a blog item
NSLog(#"voornaam: %#", author);
ParTechBlogItem *blogItem;
blogItem = [[ParTechBlogItem alloc] initWithTitle:[item fieldValueWithName:#"Content title"]
date:[item fieldValueWithName:#"Content date"]
intro:[item fieldValueWithName:#"Content introduction"]
author:author
text:[item fieldValueWithName:#"Content body" ]];
[weakSelf addBlogItem:blogItem];
}

dataWithContentsOfURL - What is expected from the server?

I am trying to create NSData with the contents of an URL:
NSString *theUrl = [NSString stringWithString:#"http://127.0.0.1:8090"];
NSError *connectionError = nil;
NSData *inData = [NSData dataWithContentsOfURL:[NSURL URLWithString:theUrl] options:NSDataReadingUncached error:&connectionError];
NSInteger code = [connectionError code];
if (code != 0)
{
NSString *locDesc = [NSString stringWithString:[connectionError localizedDescription]];
NSString *locFail = [NSString stringWithString:[connectionError localizedFailureReason]];
NSLog(#"Error: %d %# %#", code, locDesc, locFail);
}
else if ([inData length] == 0)
{
NSLog(#"No data");
}
I have a super simple Java http server running on the local host that returns Hello World to a client:
DataOutputStream os = new DataOutputStream(s.getOutputStream()); // s is the socket
os.writeBytes(new String("Hello World\0"));
os.flush();
os.close();
s.close();
When pointing Google Chrome to http://127.0.0.1:8090 it displays Hello World as expected so data is sent back. When I run the objective-c code the inData is empty (0x0, data length is 0), and the error code is 0 so I don't have an error to inspect. If I change theUrl to "http://www.google.com" it seems works fine as the data length becomes > 0.
So my question is why inData is empty when I go the to local http-server. Does the stream have to be terminated with a specific data sequence?
Is the server outputting an HTTP status code like it's supposed to? If the response doesn't contain a 200 status indicating that the request was completed successfully, that might be causing dataWithContentsOfURL:options:error: to fail.
A little more context would be useful, but a wild guess is that your "super simple" HTTP server does not send any headers or not the ones expected by NSURL.
Have you tried curl -i http://127.0.0.1:8090 to see what the output actually looks like?

Weird IF THAN not working with Requested data from URL text problem

Hey all, i am checking for an internet connection by checking for a file on my server. The file only has the word LIVE displayed on the page. No HTML or anything else is there, just the word LIVE.
When i run this code, i do get the NSLog as saying "LIVE" but once i go to check it with the IF statement, it fails and i just do not know why???
NSString* myFile = [NSString stringWithFormat:#"http://www.xxx.com/iPodTouchPing.html"];
NSString* myFileURLString = [myFile stringByReplacingPercentEscapesUsingEncoding:NSASCIIStringEncoding];
NSData *myFileData = [[NSData alloc] initWithContentsOfURL:[NSURL URLWithString:myFileURLString]];
NSString *returnedMyFileContents=[[[NSString alloc] initWithData:myFileData encoding:NSASCIIStringEncoding] autorelease];
NSLog(#"%#", returnedMyFileContents);
if (returnedMyFileContents == #"LIVE") {
NSLog(#"LIVE!");
}else{
NSLog(#"Not Live");
}
What am i doing wrong? I can not seem to find the reason??
David
You can't compare strings like that in Objective C - you're just comparing their addresses, not their contents. Change your code to this:
if ([returnedMyFileContents isEqualToString:#"LIVE"]) {
NSLog(#"LIVE!");
} else {
NSLog(#"Not Live");
}