I am downloading a file revision status from Dropbox and basically I compare downloaded revison number in Dropbox with revision number in my local plist.
After comparing them I want to change local revision number with Dropbox's revision number. But it is not working I am about to loss my mind.
I put some flags and NSlogs it seems it replace the value but after I call same function or launch the app again I see that value is not replaced. It gives the same output over and over again
NSString* revisionLocal = [dicInner objectForKey:#"revision"];
NSString* statusLocal = [dicInner objectForKey:#"status"];
NSLog(#"revision value before %#",revisionLocal);
NSLog(#"status value before %#",statusLocal);
//If revision has changed on dropbox, flag it as outdated on the local revision
if(![revisionLocal isEqualToString: dropBoxRevision] ){
[dicInner setValue:#"outdated" forKey:#"status"];
//But the revision is the latest
[dicInner setValue:dropBoxRevision forKey:#"revision"];
//[dicInner setValue:#"outdated" forKey:#"revision"];
NSLog(#"revision value %#",[dicInner objectForKey:#"revision"]);
NSLog(#"status value %#",[dicInner objectForKey:#"status"]);
so this give me the output of:
revision value before 4309efbbb7
status value before updated
revision value 4409efbbb7
status value outdated
And the full code is:
- (void)restClient:(DBRestClient *)client loadedMetadata:(DBMetadata *)metadata {
//get the local revision
NSDictionary * localRevisionDic = [FileUtils readPlistIntoDictionary:#"revision.plist"];
NSString *fileRevString = [NSString alloc];
//get the revision from Dropbox
//NSString * dropboxRevision;
if (metadata.isDirectory) {
NSLog(#"Folder '%#' contains:", metadata.path);
for (DBMetadata *file in metadata.contents) {
NSLog(#"\t%#", file.filename);
//NSLog(#"\t%#", file.lastModifiedDate);
NSLog(#"\t%#", file.rev );
//Assign dropbox revision for this file
//dropboxRevision = file.rev;
//if no local revision.plist, entry will be added. Hence init here
if (localRevisionDic==nil){
localRevisionDic = [[NSMutableDictionary alloc]init];
}
//Otherwise go through each from dropbox and campare with local
//From Dropbox
NSString * dropBoxFileName = file.filename;
NSString * dropBoxRevision = file.rev;
fileRevString = file.rev;
//if no local revision.plist entry is added for all other files
//with status need_downloaded, and no revision
if ([localRevisionDic count]==0){
//Creating revision dictionary entry for agenda.plist
NSDictionary * localRevisionDicDic = [[NSMutableDictionary alloc]init];
//when agenda.plist revision entry is added update the revision while leaving status as "new" before downloading
//will be updated accordingly if download fails
[localRevisionDicDic setValue:#"new" forKey:#"status"];
//Status is new but the revision is the latest
[localRevisionDicDic setValue:dropBoxRevision forKey:#"revision"];
[localRevisionDic setValue:localRevisionDicDic forKey:dropBoxFileName];
}else{//If there is local revision.plist compare and update accordingly
NSDictionary * dicInner = [localRevisionDic objectForKey:dropBoxFileName];
//File name Found locally
if (dicInner!=nil){
NSString* revisionLocal = [dicInner objectForKey:#"revision"];
NSString* statusLocal = [dicInner objectForKey:#"status"];
NSLog(#"revision value before %#",revisionLocal);
NSLog(#"status value before %#",statusLocal);
//If revision has changed on dropbox, flag it as outdated on the local revision
if(![revisionLocal isEqualToString: dropBoxRevision] ){
[dicInner setValue:#"outdated" forKey:#"status"];
//But the revision is the latest
[dicInner setValue:dropBoxRevision forKey:#"revision"];
//[dicInner setValue:#"outdated" forKey:#"revision"];
NSLog(#"revision value %#",[dicInner objectForKey:#"revision"]);
NSLog(#"status value %#",[dicInner objectForKey:#"status"]);
}
}else{//File name not found locally newly added on dropbox
NSDictionary * localRevisionDicDic = [[NSMutableDictionary alloc]init];
//when agenda.plist revision entry is added update the revision while leaving status as "new" before downloading
//will be updated accordingly if download fails
[localRevisionDicDic setValue:#"new" forKey:#"status"];
//But the revision is the latest
[localRevisionDicDic setValue:dropBoxRevision forKey:#"revision"];
[localRevisionDic setValue:localRevisionDicDic forKey:dropBoxFileName];
}
}
}
}
//At this point agendaRevisionDicTemp contains all the files in dropbox entered/updated.
[[self agenda] setRevision:localRevisionDic];
//*****The following block is needed to determine is new agenda is needed or not.
BOOL newAgendaNeeded = false;
NSMutableDictionary * agendaRevisionDicLocal = [localRevisionDic objectForKey:#"agenda.plist"];
//NSString * localRevision = [agendaRevisionDicLocal objectForKey:#"revision"]; //what is this value?
NSString * localStatus = [agendaRevisionDicLocal objectForKey:#"status"];
NSLog(#"Local Status= %#",agendaRevisionDicLocal);
if ([localStatus isEqualToString:#"new"] ||[localStatus isEqualToString:#"outdated"]){
newAgendaNeeded = true;
//when agenda.plist is added update the revision while leaving status as "new" before downloading
//will be updated accordingly if download fails
NSDictionary * agendaDic = [[[self agenda]revision] objectForKey:#"agenda.plist"];
[agendaDic setValue:#"updated" forKey:#"status"];
NSLog(#"agendaDic where update %#",agendaDic);
}
//*****The above block is needed to determine is new agenda is needed or not.
//If new agenda is needed download
if (newAgendaNeeded){
//Download agenda.plist
NSString *documentsDirectory = FileUtils.getDocumentsDirectory;
[[self restClient] loadFile:#"/agenda.plist" intoPath: [ NSString stringWithFormat:#"%#/%#",documentsDirectory,#"agenda.plist"] ];
} else{//Else display the scene
[self populateSceneFromAgenda];
}
[restOfView reloadData];
// Add at start of requestFinished AND requestFailed
[MBProgressHUD hideHUDForView:self.view animated:YES];
}
Any Idea How Can I replace that value?
You don't respect immutability of your NSDictionary objects. You declare localRevisionDic as NSDictionary*, but later it could be assigned value of NSMutableDictionary. localRevisionDicDic declared as NSDictionary* but initialised with value of NSMutableDictionary. Following two assignments should be warned at compile time. At line
[localRevisionDic setValue:localRevisionDicDic forKey:dropBoxFileName];
who knows for sure is localRevisionDic mutable or immutable one?
Then, again, you declare dicInner as NSDictionary*, but try to setValue twice later. Make mutableCopy of dictionary first.
I guess your dictionary changes correctly whats wrong with your code should be that you do not save your dictionary to the plist you edit
In fact there is deffinetly something wrong with that method[[self agenda] setRevision:localRevisionDic];
Try to add following code below from [[self agenda] setRevision:localRevisionDic];
//write dictionary to plist
NSArray *paths = NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES);
NSString *documentsDirectory = [paths objectAtIndex:0];
NSString *path = [documentsDirectory stringByAppendingPathComponent:#"revision.plist"];
// write plist to disk
[localRevisionDic writeToFile:path atomically:YES];
// read it back in with different dictionary variable
NSMutableDictionary *savedStock = [NSMutableDictionary dictionaryWithContentsOfFile:path];
if( savedStock==nil ){
NSLog(#"failed to retrieve dictionary from disk");
}
Related
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.
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!
Hi together I have a Problem:
I read out the actual Values of the Playing media song, and add it to a dictionary.
And then into an Array. If I add a new entry pressing the Button again, all Values of all entrys changing to the same.
Do you have an Idea why?
Here my Code:
- (IBAction)ActionButtonLU:(id)sender
{
MPMediaItem *currentItem = [musicPlayer nowPlayingItem];
NSString *titleString = [currentItem valueForProperty:MPMediaItemPropertyTitle];
NSString *artistString = [currentItem valueForProperty:MPMediaItemPropertyArtist];
NSString *albumString = [currentItem valueForProperty:MPMediaItemPropertyAlbumTitle];
if (titleString == nil) {
titleString = #"";
}
if (artistString == nil) {
artistString = #"";
}
` `if (albumString == nil) {
albumString = #"";
}
[_dictCat1 setObject:titleString forKey:#"Titel"];
[_dictCat1 setObject:artistString forKey:#"Artist"];
[_dictCat1 setObject:albumString forKey:#"Album"];
[_countCat1 addObject:_dictCat1];
[musicPlayer skipToNextItem];
}
You are modifying the same dictionary, _dictCat1, in every call. You need to create a dictionary locally and add that to _countCat1.
[_countCat1 addObject:#{ #"Title": titleString,
#"Artist": artistString,
#"Album": albumString }];
When you add an object to a collection (NSArray, NSDictionary, NSSet etc, including mutable counterparts), the collection doesn't copy it. With [_countCat1 addObject:_dictCat1]; you keep adding the same object to _countCat1 over and over. You need to create new NSDictionary and add that to _countCat1 collection.
I'm trying to get a list of all ejectable disks using the Disk Arbitration framework. The problem is that the Ejectable property is always false (even when diskutil info says Ejectable: Yes). What's going on? Do I need to do a DADiskClaim first or something?
- (NSArray *)disks {
NSMutableArray *disks = #[].mutableCopy;
// Get a list of everything in /Volumes/
NSArray *volumes = [[NSFileManager defaultManager] contentsOfDirectoryAtPath:#"/Volumes/" error:NULL];
DASessionRef session = DASessionCreate(kCFAllocatorDefault);
// Use NSSet for easy object finding
NSMutableSet *bsdNamesOfDisks = #[].mutableCopy;
for (NSString *volume in volumes) {
// Create the URL for the volume mount point
NSURL *volumeURL = [NSURL URLWithString:[[NSString stringWithFormat:#"/Volumes/%#", volume] stringByAddingPercentEscapesUsingEncoding:NSUTF8StringEncoding]];
// Create DADisk for the volume
DADiskRef volumeDisk = DADiskCreateFromVolumePath(kCFAllocatorDefault, session, (__bridge CFURLRef)volumeURL);
// Filter out files/directories that aren't volumes
if (volumeDisk) {
// Get disk description
NSDictionary *description = (__bridge_transfer NSDictionary *)DADiskCopyDescription(volumeDisk);
// Get BSD name and ejectable property
NSString *bsdName = description[(__bridge id)kDADiskDescriptionMediaBSDNameKey];
CFBooleanRef ejectable = (__bridge CFBooleanRef)description[(__bridge id)kDADiskDescriptionMediaEjectableKey];
// Check to see if this is a volume we're interested in
if (bsdName && ejectable && CFBooleanGetValue(ejectable)) {
// Deletes "disk" in "disk0s1" and separates the numbers
NSArray *numbersInName = [[bsdName substringFromIndex:4] componentsSeparatedByCharactersInSet:[NSCharacterSet letterCharacterSet]];
// Scan the first number
NSScanner *scanner = [NSScanner scannerWithString:numbersInName[0]];
NSInteger diskNumber;
[scanner scanInteger:&diskNumber];
// Construct BSD name for the whole disk
NSString *bsdDiskName = [NSString stringWithFormat:#"disk%ld", diskNumber];
// Check to see if we already added this disk to our array (e.g. disk0s1 and disk0s2 both give disk0)
if (![bsdNamesOfDisks containsObject:bsdDiskName]) {
// Create DADisk for the whole disk
DADiskRef disk = DADiskCreateFromBSDName(kCFAllocatorDefault, session, bsdDiskName.UTF8String);
// Add DADisk to disk array and BSD name to name array
[disks addObject:(__bridge_transfer id)disk];
[bsdNamesOfDisks addObject:bsdDiskName];
}
}
CFRelease(volumeDisk);
}
}
CFRelease(session);
return disks.copy;
}
I want to create an Application that connects to the iPhoto Library. So now I would like to read the Events and the pictures themselves from the library.
Is there an elegant / easy way to do this or do I have to manually read the Bundle Structure of the iPhoto User Data?
So far I have only found a picture taker: Is there a UIImagePicker for the Mac Desktop
Update: I found another relevant SO post: Selecting iPhoto images within a cocoa application
You can do it with NSAppleScript. This is some copy/paste from my app, hacked up a bit just to show the idea.
NSAppleEventDescriptor d = .. compile this script ..
#"tell application \"iPhoto\" to properties of albums"
for (int i = 0; i < [d numberOfItems]; i++)
{
NSAppleEventDescriptor *albumDesc = [d descriptorAtIndex:i];
// <NSAppleEventDescriptor: 'ipal'{
// 'ID ':4.265e+09,
// 'purl':'utxt'("http://www.flickr.com/photos/..."),
// 'pnam':'utxt'("Vacation"),
// 'alTy':'pubs',
// 'alCh':[ ],
// 'alPx':'msng' }>
NSString *albumName = [[albumDesc descriptorForKeyword:'pnam'] stringValue];
NSString *albumId = [[albumDesc descriptorForKeyword:'ID '] stringValue];
You can do the same thing to find the images
NSString *scp =
[NSString stringWithFormat:#"tell application \"iPhoto\" to properties of photos of album id %#",
[album objectForKey:#"id"]];
NSAppleEventDescriptor *d = ... compile scp ...
// 1 based!?
for (int i = 1; i <= [d numberOfItems]; i++)
{
NSAppleEventDescriptor *photoDesc = [d descriptorAtIndex:i];
// Yes.. this happens. Not sure why?!
if (!photoDesc)
continue;
// <NSAppleEventDescriptor: 'ipmr'{
// 'pnam':'utxt'("IMG_0058.JPG"),
// 'pwid':768,
// 'pdim':[ 768, 1024 ],
// 'alti':1.79769e+308,
// 'filn':'utxt'("3133889525_10975ba071_b.jpg"),
// 'ipth':'utxt'("/Users/lagnat/Pictures/iPhoto Library/Masters/2010/11/10/20101110-002341/3133889525_10975ba071_b.jpg"),
// 'idat':'ldt '($F57C69C500000000$),
// 'rate':0,
// 'titl':'utxt'("IMG_0058.JPG"),
// 'phit':1024,
// 'itpt':'utxt'("/Users/lagnat/Pictures/iPhoto Library/Thumbnails/2010/11/10/20101110-002341/3133889525_10975ba071_b.jpg.jpg"),
// 'ID ':4.295e+09,
// 'lati':'msng',
// 'pcom':'utxt'(""),
// 'opth':'utxt'("/Users/lagnat/Pictures/iPhoto Library/Masters/2010/11/10/20101110-002341/3133889525_10975ba071_b.jpg"),
// 'lngt':'msng',
// 'tiln':'utxt'("3133889525_10975ba071_b.jpg.jpg") }>
NSString *path = [[photoDesc descriptorForKeyword:'ipth'] stringValue];
NSString *imgname = [[photoDesc descriptorForKeyword:'pnam'] stringValue];
If releasing apps on the App Store you are now required now required to use the Sandbox, this stops the previous AppleScript method from working (the iPhoto app launches but an empty set is returned).
iPhoto libraries consist of a directory structure containing photos, databases and XML files. The contents changes with each version of iPhoto so be careful if manually accessing these files.
If you just want the album details you can parse the file AlbumData.xml
If you would like photos you can browse the Masters folder. The files structure follows date rather than by the sets configured in iPhoto.
More information can be found on the internals of the iPhoto library here:
http://www.fatcatsoftware.com/iplm/Help/iphoto%20library%20internals.html
The majority of the databases are in SQLite format and so can be programmatically accessed through Objective C, though again you can expect schema changes between different versions of iPhoto. The main databases of interest are Library.apdb and Properties.apdb in Database/apdb.
If you still want to use the Apple Script method, here's a version of the previous answer with the Apple script execution part included:
NSAppleScript *script = [[NSAppleScript alloc] initWithSource:#"tell application \"iPhoto\" to properties of albums"];
NSAppleEventDescriptor *d = [script executeAndReturnError:nil];
NSLog(#"photo library count: %ld", (long)[d numberOfItems]);
for (int i = 0; i < [d numberOfItems]; i++)
{
NSAppleEventDescriptor *albumDesc = [d descriptorAtIndex:i];
NSString *albumName = [[albumDesc descriptorForKeyword:'pnam'] stringValue];
NSLog(#"%#", albumName);
}