Pretty Reckless – Going to Hell Wallpaper

Couldn’t find a decent sized version of their album cover anywhere in a wallpaper style design, so here’s a quick’n’dirty one at 1920×1200.

ThePrettyReckless-GoingToHell-1920x1200

The album is pretty damn fine too.

Posted in Art, Miscellany, Music | Leave a comment

CEX stock monitoring

It’s been ages since I posted anything, I’ve been playing with all sorts of stuff, like Cocos2D and SpriteKit… as well as wicked little things like this DIY Gamer kit. Maybe I’ll do some more long form ish posts soon about those. In the meantime though here’s a silly little bash script that I just put in cron:


#!/bin/bash

# for i in `wget -O - -o /dev/null https://uk.webuy.com/product.php?sku=5060327531071 | grep -oE "branchId=([[:digit:]]{1,3})" - | cut -d'=' -f2`; do if [ $i -eq 100 ] then; echo "Found" ; fi ; done

DECEPTION_VITA="https://uk.webuy.com/product.php?sku=5060327531071"
DECEPTION_PS3="https://uk.webuy.com/product.php?sku=5060327531002"

ST_ALBANS=100
WATFORD=19
HEMEL=56
STRATFORD=120

for store in `wget -O - -o /dev/null $DECEPTION_VITA | grep -oE "branchId=([[:digit:]]{1,3})" - | cut -d'=' -f2`; do
if [ $store -eq $ST_ALBANS ]
then
echo "Vita Deception in stock St. Albans"
fi
if [ $store -eq $WATFORD ]
then
echo "Vita Deception in stock Watford"
fi
if [ $store -eq $HEMEL ]
then
echo "Vita Deception in stock Hemel"
fi
# if [ $store -eq $STRATFORD ]
# then
# echo "Test confirmed, PS3 Deception in store Stratford."
# fi
done

for store in `wget -O - -o /dev/null $DECEPTION_PS3 | grep -oE "branchId=([[:digit:]]{1,3})" - | cut -d'=' -f2`; do
if [ $store -eq $ST_ALBANS ]
then
echo "PS3 Deception in stock St. Albans"
fi
if [ $store -eq $WATFORD ]
then
echo "PS3 Deception in stock Watford"
fi
if [ $store -eq $HEMEL ]
then
echo "PS3 Deception in stock Hemel"
fi
done

It takes urls for products and store id’s then via crons MAILTO environment variable it will email me if it finds those games in stock. It’s an experiment, and the code is a bit ‘dumb’. It should really loop around URLS and store IDs but for my purposes it’s fine.

Posted in Games, Programming | Leave a comment

Using a UITextField like an ATM keypad.

A small snippet of code that uses a UITextField like an ATM keypad. E.g. You start typing in a sequence like this:

£0.00 -> £0.02 -> £0.20 -> £2.05 -> £20.50

Elsewhere define and store a reference to a NSNumberFormatter like so:

        self.en_GB = [[NSLocale alloc] initWithLocaleIdentifier:@"en_GB"];
        self.paymentFormatter = [[NSNumberFormatter alloc] init];
        [self.paymentFormatter setNumberStyle:NSNumberFormatterCurrencyStyle];
        [self.paymentFormatter setLocale:self.en_GB];
        [self.paymentFormatter setGeneratesDecimalNumbers:YES];        

Then as a delegate method to the UITextField:

- (BOOL)textField:(UITextField *)textField shouldChangeCharactersInRange:(NSRange)range
replacementString:(NSString *)string {
    
    NSMutableString *mutableString = [[textField text] mutableCopy];
    
    if ([mutableString length] == 0) {
        [mutableString setString:[self.en_GB objectForKey:NSLocaleCurrencySymbol]];
        [mutableString appendString:string];
    } else {
        if ([string length] > 0) {
            [mutableString insertString:string atIndex:range.location];
        } else {
            [mutableString deleteCharactersInRange:range];
        }
    }
    
    NSString *penceString = [[[mutableString stringByReplacingOccurrencesOfString:
                               [self.en_GB objectForKey:NSLocaleDecimalSeparator] withString:@""]
                              stringByReplacingOccurrencesOfString:
                               [self.en_GB objectForKey:NSLocaleCurrencySymbol] withString:@""]
                              stringByReplacingOccurrencesOfString:
                               [self.en_GB objectForKey:NSLocaleGroupingSeparator] withString:@""];
    
    self.paymentPence = [NSDecimalNumber decimalNumberWithString:penceString];
    
    [textField setText:[self.paymentFormatter stringFromNumber:[self.paymentPence decimalNumberByMultiplyingByPowerOf10:-2]]];
    
    return NO;
}

I want to keep the amount in pence, hence the self.paymentPence property. On the off-chance that the UK moves to the € I also use a NSLocale instance to make sure Decimal Separators, Currency Separators and Symbols are correct rather than hard coded.

Posted in Programming | Tagged , , , , | 2 Comments

Thoughts on The Secret World (Work in Progress)

Thoughts on The Secret World
============================

Around the birth of Massively Multiplayer Online Role Playing Games (MMORPGs) there was a game called Ultima Online, an online variation of the popular Ultima series of RPGs. Ultima Online had, to this day, a unique skill system within mainstream MMOs. Although you selected a starting class or custom made one your skills and thus role in the game were never fixed. Skills improved as you used them be they magic, swordsmanship, parrying or meditation right through to trade skills such as mining and armour smithing. For some reason, which to me remains opaque, in the mainstream every MMO that followed abandoned such a system forcing you to create multiple characters should you wish to experiment. Your skill set essentially set in stone. Imagine being born a miner knowing full well you could never write a line of code or solve a crossword?
There are, of course, less well known games that use such a system but none quite as elegantly as UO did. You could lock a skill, set it only to raise or force it to decrease. Thus with careful management I could forgo my meditation and wrestling skills by marking them down and concentrate on raising my parrying skills with a foil to better protect my mage in combat.

The Secret World although not directly emulating this path also gives you free reign. You earn skill points and can invest them in whatever branch of the skill set you see fit. Thus respeccing (is that shorthand for re-specialising?) is a matter of inputting the time to earn the skill points. As a departure from the UO method you thus don’t lose skills, you simply can’t take advantage of them without the requisite item equipped, say a shotgun or a magic ward or totem.

That’s what drew me to the game but now it is primarily Ragnar Tornquist’s excellent writing. Having cut his teeth on some of the best point and click adventure games in recent times, The Longest Journey and its sequel Dreamfall, the writing is top notch. Suprisingly by and large so is the voice acting, even if the character animation during dialogue is somewhat off.

This coupled with quests liberally scattered around the games areas rarely leaves you feeling bored, though you will find yourself rapidly outclassed even doing the side quests. On that note it’s worth talking about the quests and their associated cut scenes.

The game uses a completely silent protagonist, a raised eyebrow is about as expressive as your avatar becomes in these scenes. After the bountiful dialogue used in Star Wars the Old Republic this is initially jarring but some self conscious nods in the game lead to acceptance of this odd fact by and large. Is my sword wielding chaos magic avatar a mute?

The quests are pretty much of the standard MMO variety. Escort, Kill, Retrieve, Follow, Assist etc. etc. However the presentation of these quests sells them well. Gone for the most part is plain text and following arrows to the next waypoint (although waypoints are very much present, they generally lead you to the area no the spot). In are snippets of newspapers and maps shown as handwritten notes and scrawled stained maps and photos. The main quest chain appears devoid of the more exotic puzzles, I assume only so as not to hinder progress for those that have neither the time or inclination to solve them.

It’s in the side quests for me that the game therefore shines, being optional the designers appear to have had a bloody field day. So, as an example: (SPOILERS, I’ll put this behind a cut later)

=======================================================

One quest wants you to track down four satellite dishes in a site and repoint them to a different satellite. You do this not by just clicking on them but entering the err azimuth/elevation data. A simple change but an important one as you need to refer to your notes if you don’t have it memorised. It is at this point things get better. The first satellite displays this:

29 21 68 6f 24 28 23 23 2c 20 63 28 23 20 5f 6f
23 20 68 24 21 29 20 24 23 3f 20 43 2b 3d 2a 28
25 20 40 2b 72 20 29 5e 78 20 24 21 79 24 20 40
28 21 20 23 24 2a 25 6e 20 21 2a 67 23 29 23 2e
20 21 63 21 28 23 73 20 74 2b 24 20 25 5e 26 40
20 6f 2a 20 24 69 2b 40 2e 20 46 5f 24 2a 21 79
2d 28 5e 75 29 20 21 65 61 23 2b 23 20 5e 26 5e
76 21 20 21 68 5f 25 20 2a 20 21 28 40 23 79 2c
20 62 40 25 20 2a 20 63 24 69 29 40 20 28 2b 20
69 40 20 5e 2a 20 23 28 65 20 5e 2a 26 21 65 20
2b 7e 20 21 65 21 5f 24 21 20 48 25 2a 40 20 6d
2a 20 5f 6f 3d 24 40 21

The second this:

83 33 61 42 108 97 35 35 44 32 33 40 110 32
95 61 35 32 64 101 33 41 32 109 35 63 32 64
104 61 42 40 100 32 102 43 37 32 41 94 40 32
100 33 42 36 32 97 40 33 32 115 101 42 37 35
32 110 105 36 35 41 35 46 32 65 41 33 40 35 61
32 64 104 36 32 37 94 38 115 32 33 42 32 36 94
114 64 46 32 64 95 117 114 33 42 45 40 94 40
114 32 98 38 42 35 43 35 32 94 38 94 64 101
32 33 64 97 37 32 42 32 99 40 64 114 94 44 32
42 117 37 32 42 32 33 36 37 110 64 32 40 111
32 36 64 32 94 110 32 35 40 36 32 104 42 38
33 42 32 43 102 32 33 42 97 95 36 33 32 40 37
42 100 32 36 42 32 119 33 61 36 64 33

The third this:

KWM9KiQociMsICFhIyBfPXUgQCQhciAkIz8gQCthKiglIEArJSBzXiggJCEqcyBAbiEg<
IyR2JSMgISokaCkjLiAhKSEocz0gQCtlICVedEAgIWYgJF4rZS4gQG8kKiEqLWZeKCkg
ISYqI3RzIF4mYUAhIHdAXyUgSSAhYUAjXiwgKkB0ICogIWwlKWcgKCsgJEAgaSogdCgk
IF5vdSEqICt+IGQqIXQkISAoJWVAICR5IF8hciRzIQ==

and the fourth:

00101001 00100001 00111101 00101010 01101100
00101000 00100011 01110011 00101100 00100000
00100001 00101000 00100011 00100000 01111001
00111101 00100011 00100000 01000000 00100100
01100001 00101001 00100000 00100100 01100101
00111111 00100000 01000000 00101011 00111101
01110011 01100101 00100101 00100000 01000000
01101111 00100101 00100000 00101001 01101001
00101000 00100000 00100100 01100001 00101010
00100100 00100000 01000000 00101000 01100100
00100000 00100011 00100100 00101010 01100101
00100011 00100000 00100001 00101010 00100100
00100011 01110100 01110011 00101110 00100000
00100001 00101001 01110010 01101111 00100011
00111101 00100000 01000000 00101011 00100100
00100000 01110000 01101001 00100110 01000000
00100000 00100001 00101010 00100000 01100110
01011110 00101011 01000000 00101110 00100000
01000000 01011111 00100100 00101010 01110100
00101010 00101101 00100001 01101111 00101000
00101001 00100000 00100001
00100110 00101010 01110011 00101011 00100011
00100000 01100011 01110010 01011110 01000000
00100001 00100000 00100001 01000000 01011111
01110100 00100000 00101010 00100000 00101100
00100000 00101010 01000000 00100101 00100000
01001001 00100000 00100001 00100100 00100101
00101001 01000000 00100000 01110100 00101011
00100000 00100100 01110100 00100000 01011110
00101010 00100000 00100011 01101000 00100100
00100000 01011110 00101010 00100110 01110011
00101010 00100000 01101111 01111110 00100000
00100001 00101010 00100001 01011111 01101000
00100001 00100000 00101000 01100101 00101010
01000000 00100000 00100100 00101010 00100000
01011111 00100001 00111101 01100100 01000000
00100001

As a programmer this quest seemed tailor made for me. They are all ASCII. The first is displayed as Hex, the second the base-10, the third is Base64 encoded and the fourth is pure binary (essentially the same as 1 and 2. 1 is Base16, 2 is Base10 and 4 is Base2).

Decoding these and putting them underneath each other gives, with a few line breaks for clarity:

)!ho$(##, c(# _o# h$!) $#? C+=*(% @+r )^x $!y$ @(! #$*%n !*g#)#. !c!(#s
S!=*la##, !(n _=# @e!) m#? @h=*(d f+% )^( d!*$ a(! se*%# ni$#)#. A)!(#=
)c=*$(r#, !a# _=u @$!r $#? @+a*(% @+% s^( $!*s @n! #$v%# !*$h)#. !)!(s=
)!=*l(#s, !(# y=# @$a) $e? @+=se% @o% )i( $a*$ @(d #$*e# !*$#ts. !)ro#=

t+$ %^&@ o* $i+@. F_$*!y-(^u) !ea#+# ^&^v! !h_% * !(@#y, b@% * c$i)@ (+
@h$ %^&s !* $^r@. @_ur!*-(^(r b&*#+# ^&^@e !@a% * c(@r^, *u% * !$%n@ (o
@+e %^t@ !f $^+e. @o$*!*-f^() !&*#ts ^&a@! w@_% I !a@#^, *@t * !l%)g (+
@+$ pi&@ !* f^+@. @_$*t*-!o() !&*s+# cr^@! !@_t *      , *@% I !$%)@ t+

i@ ^* #(e ^*&!e +~ !e!_$! H%*@ m* _o=$@!
$@ ^n #($ h*&!* +f !*a_$! (%*d $* w!=$@!
$@ i* t($ ^ou!* +~ d*!t$! (%e@ $y _!r$s!
$t ^* #h$ ^*&s* o~ !*!_h! (e*@ $* _!=d@!

Reading down each column and picking only a valid letter from each gives you this:

Scholars, can you hear me? Chased for six days and seven nights. Across
the pits of fire. Fourty-four beasts crave what I carry, but I cling to it
in the house of death! Heed my words!

Instructions, heed the words indeed. Travel across the pits of fire (a huge burning scar opened up in the egyptian desert outside a town) and follow it north to a tomb. In there against the pillar is a body clutching a box, the pin to unlock it is 6744.

=========================================================================

End of spoilers. It's not the only example of this in the game but it's by far the most complicated I've encountered and in fact I quit out of the game to solve it, not having ready access to base64 and other command line tools and not wanting to use the in game web browser for fear of spoilers. This sets me thinking, why has no other online game done this? Sadly within the TSW comes the solution. The chat is commonly flooded with "I can't be bothered." and "What's the password for the doctors computer?" (a simple enough clue about the four seasons, vivaldi). Oh well.

In other news, the combat is initially solid but unenspiring with no real feel of 'connection' with your attacks. A common problem in such games and certainly not unique to TSW. However as you progress the range of skills becomes broader and the limitation of only having 6 active and 6 passive skills can lead to some judicious skill set building working out what of your array of skills will let you chain attacks to the most effect or perform efficient crowd control should the need arise.

This, coupled with underlying artifice on which the game is built (that every conspiracy theory is true) is why I love The Secret World. It really does deserve more subscribers, sadly it's not the most immediately fulfilling MMO. Either way, I'll be there as I have no monthly fees in the game. What I do need to find is a guild; those dungeons are calling me and without a group I would die screaming.

P.S. Free three day trial here, clicky you know you want to.

Posted in Games | Tagged , , , , | Leave a comment

Wow it’s been sometime huh? Here’s a brief rundown…

It’s all been rather hectic. Lots has been happening and unhappening. The most obvious being that the box that runs this website went a bit mental as the hard disks succumbed to age; and I’ve been too busy to fix it until this rather wet and dull bank holiday.

Anyway it’s more alive now than it has been. However because I’m a muppet I lost the www.chefs-book.com website. I have a very very old copy but it’s gonna take me sometime to massage it back to life. I suspect I’ll start from scratch. In fact I know I’ll start from scratch!

In other news I’ve been playing a lot of The Secret World and been having a nose around Guild Wars 2 this morning. All looks good there. I bought a K90 Corsair keyboard, which was gorgeous until it stopped working OK. Thankfully Overclockers and Corsair rock and a new one is in the post as I type this. Should arrive tomorrow at work.

On the subject of work that’s been going really well… everyones cool and rather supportive; even when I’m doing stuff I don’t quite understand (re: PHP/Zend/Security/Forms/Payment Systems etc.).

All the PHP coding at work however has given me the impetus to revisit my own Objective-C code. I’ve spend the last week polishing my own Last.fm scrobbling app which may not ever see the light of day in the app store. It does what I want, it’s very minimal and it does musicbrainz lookups to supplement the data when last.fm rejects the scrobble. Normally because it falls foul of a fubared filter, as Alanis Morissette’s “Incomplete” track did a few months ago. The downside is background notifications of song plays appear to be limited to three, so the only two ways to have it as a background app are:

- Hacks
- Check play counts on tracks and scrobble plays that have occurred in the backgrounded period (iffy but I’m looking into it)
- Integrate a music player into the app itself. Not keen on this idea and have no idea how much other processing I can do with the app running like that.

My idea to have my Delicious Library database on my iPhone has reached fruition. This is a piece of software that will definitely never be released. Mostly because I’m not allowed, though I do have permission for it on a personal learning basis. It’s actually a project I started doing whilst learning to code for iOS but quickly got back burnered when I’d learnt what I needed from it and moved onto Chef’s Book as a project that could see the light of day. I’ve still got some Core Data wizardry to do so I can search it etc. That should hopefully stop me buying books/cds/games and movies I already own when I’m out shopping!

As for Chef’s Book I have the compound predicates needed for recipe searching working. They need spit and polish and need to have a bit more natural language processing put in. Either by removing words such as and/or/with or actually processing them into their respective meanings and modifying the search accordingly.

I’ve taken lots of photos but haven’t yet punted them up to Flickr.

I took a step I’ve been thinking of taking for several years, I shut down my Facebook account. It feels good, although it would appear that many people missed the posting where I said “Hey I’m shutting down my account, you’ve got my number please call me if you want to chat”. Essentially I’d rather speak to my friends and meet them for a coffee once in a while than exchange banalities electronically!

Also there’s less photos in my face of things I don’t care about or wish to see! }:-)

What else? Not a lot. I keep a proper hand written daily journal now, having finally found a pen I can write with that doesn’t cause muscle cramps/carpal tunnel whatever the hell is wrong with my hands to flare up. I’m still going to the gym twice a week. Errr… I’m single. Again, but there’s a nice lady I talk to once a week about that crap. **grin**

I might post the odd journal type entry here now. Seeing as I can excerpt them from my diary. My pre-daily journal diary has now become my personal to-do list appointment diary, the paper that Moleskine use is crap with fountain pens compare to these lovely creations Paper Blanks. These particularly are nice Paper Blanks Mucha but too small for journal needs so I don’t know what I’m going to use them for. This is the one I’m using for journal stuff Scott Fitzgerald The Great Gatsby Ultra. The papers nice and thick and they look lovely. I’d hunted and hunted for the Mucha ones, trying to find somewhere that had them in stock. Just because I like Mucha. They are a lot more gold in person than they look in photos on the web. Having ordered them online I found that the Waterstones in St. Albans has a tonne of them in stock upstairs. They also have a decent enough selection of the much larger ones, as does Smiths in Hemel Hempstead (upstairs).

Posted in Art, Chef's Book, Computer, Cooking, Drawing, Exercise, Games, Literature, Miscellany, Music, Photography, Programming | Tagged , , , , , , , | Leave a comment

Diablo 3 – Launch Day Blues

21:20 – Diziet Embless Sma: Meh.. let’s put it this way dude. You’re simply playing a slightly different version of Diablo 3.
21:21 – Diziet Embless Sma: Last night I made Level 10 at Error 37, and Level 3 at Error 12. Today I’ve got level 50 Entering Password and Level 45 Retrieving Character List.
21:21 – Diziet Embless Sma: You made Level 7 Can’t Fucking Install.

Posted in Games | 1 Comment

Backing up Steam with CWRsync

This may or may not be of use to other people, and seeing as I’ve not written a blog post for ages, I thought I’d post it here. Before demolishing my PC last year and building a new one once I found myself gainfully employed once again I was working on something to back up Steam (and indeed image my PC). I never got the hang of using windows ghost copies from batch scripts to image the windows drive out but I did come up with a bash script to copy steam off to a USB or eSATA drive:


@ECHO OFF

SETLOCAL

REM ** CUSTOMIZE ** Specify where to find rsync and related files (C:\CWRSYNC)
SET CWRSYNCHOME="D:\Program Files (x86)\cwRsync"

REM Set CYGWIN variable to 'nontsec'. That makes sure that permissions
REM on your windows machine are not updated as a side effect of cygwin
REM operations.
SET CYGWIN=nontsec

REM Set HOME variable to your windows home directory. That makes sure
REM that ssh command creates known_hosts in a directory you have access.
SET HOME=%HOMEDRIVE%%HOMEPATH%

REM Make cwRsync home as a part of system PATH to find required DLLs
SET CWOLDPATH=%PATH%
SET PATH=%CWRSYNCHOME%\BIN;%PATH%

REM rsync
REM -r recursive
REM -v verbose
REM -P progress
REM -k transform hard link to dir on dest.
REM -L transform symlink to dir on dest.
REM -u update, probably a winblows timestamp fix to stop it copying every file every time.
sc stop MsMpSvc
taskkill /f /im msmpeng.exe
REM rsync --delete -rvuP --exclude='Google' -exclude='Temporary Internet Files' --exclude='Local Low' --exclude='Backup' --exclude='Temp' --exclude='*Cache*' /cygdrive/c/Users /cygdrive/h/
rsync -rvukPL --exclude='httpcache' --exclude='htmlcache' --exclude='overlayhtmlcache' /cygdrive/d/Games/Steam /cygdrive/g/Games/PC/
sc start MsMpSvc

Hopefully it’s of use to someone. If you’re not comfortable with the sc stops and taskkill commands then get rid of them, I just found (especially when copying the windows users directory) that it interfered and made the rsync slow to a crawl).

In other news there’s err not a lot. If there was I’d post it here. :P

Actually that’s not true. I’ve written tonnes and tonnes of code over the last six months some of which I’d love to post but never seemed to get around to it. I’ll try and post more some. Some of things I’ve been wrestling with have been:

- Clustering Algorithms for geo location data.
- MKMapKit with transparent overlays and mad exc_bad_access errors buried deep within Map Kit/iOS. Which are mostly solved, but it still crashes occassionally even with only 1 overlay. Notably on zooming out.
- OpenGL ES, briefly last week. I’m hoping to do more fun coding in this area and have even asked my nephew to design a game. That should be amusing. :D It will be very very weird.
- Last.fms API, I want a basic no frills scrobbling app for the iPhone that lets me use the including Music.app. It turns out it’s non trivial.

There will definitely be a post on the latter. NSNotifications from the music app are particularly interesting when your app is in the background. They go missing and there’s not hard and fast rule it would appear on how long they are kept for or under what conditions they are dropped. A rule of thumb would appear to be that you receive about 3 track change notifications and that’s it. So you can’t receive information for a whole album with the app backgrounded. Whilst tackling this supposedly obvious ‘why the hell hasn’t anyone done this already?’ app I’ve realised why the apps that are out there either:

- Don’t work.
- Use a built in player.
- Build horrible caches of your play history each time (iScrobble I’m looking at you).

Sadly the latter was particularly good when it worked but now appears to not receive updates and recent changes to iOS have rendered it useless. I can only assume because it caches it’s data in a directory that is now cleared out by the OS.

On the Chef’s Book count I’ve been wanting to sneak the time in to polish off the recipe search and update the graphics and UI. It just never seems to happen. :( I’ll do it, I just don’t know when.

Posted in Games, Programming | Tagged , , , , , , | Leave a comment

Progress with adding iCloud to Chef’s Book.

There are some interesting challenges with incorporating iCloud support, especially into an existing application. One, which I’ve been working through, is that you can no longer copy in a pre-existing store for the purposes of loading data. Such a move appears to work but does not upload anything to the cloud, the cloud store will only see future changes and not have the initial data set.
The solution to this is to use NSPersistentStoreCordinator’s migratePersistentStore:toURL:options:withType:error method. However I found the documentation somewhat lacking in clarity so here is the solution that now appears to be working for me.

There are some things I wanted to guarantee, which are probably not applicable to many so I’ll document them here:

- The application has gone through a few object model changes and I wanted to support people that may have skipped versions.
- The application’s data store location has changed on one occasion, when I moved the store out of Documents and into the Library directory due to adding iTunes document sharing.

Before we look at the code we’ll have a look at a rough outline of the procedure, in handy bullet points, step is 4 is the one that took some figuring out:

  1. Run everything following this asynchronously.
  2. Determine location of any existing stores, if not default to initial data set.
  3. Check to see if application supports iCloud, e.g. is running iOS 5.
  4. Configure store options if it does support iCloud.
  5. Lock the persistent store coordinator.
  6. If there is a cloud data set already, simply add it.
  7. If there isn’t DON’T add it, the migrate function adds it.
  8. Unlock persistent store coordinator.
  9. Send messages out so that view controllers that need to can redo their fetches.

A few caveats about the following, it’s not tested aside from ensuring it does migrate data into the store. I’ve not yet verified that the data then careers across the cloud to another device but I’ll update the post as soon as I do.

The code isn’t optimal, but that should be obvious! ;) The method should be called where in your code you’d normally add the store. A lot of the framework, minus the migrations stuff, is based on some example iCloud code.
Also note that the if block with the “//Migrate old store to new AND migrate into cloud” comment is devoid of code. In that block I would need to update the old store to match the new one and THEN migrate the store as I’m using my own migrations code which also allows importing of new data and a few other tweaks, which I think I’ve previously blogged.

CLOUD_NAME,STORE_NAME are #defines. DLog is a #define macro to NSLog that is blank in production environments.

- (void)aSynchronouslyAddPersistentStore {
  NSString *cloudPath = [[self applicationLibraryDirectory] stringByAppendingPathComponent:CLOUD_NAME];
  NSString *preCloudPath = [[self applicationLibraryDirectory] stringByAppendingPathComponent:STORE_NAME];
  NSString *defaultStorePath = [[NSBundle mainBundle] 
                                pathForResource:@"InitialData" ofType:@"sqlite"];
  NSString *oldStorePath = [[self applicationDocumentsDirectory] stringByAppendingPathComponent:STORE_NAME];
  
  
  // do this asynchronously since if this is the first time this particular device is syncing with preexisting
  // iCloud content it may take a long long time to download
  dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
    NSFileManager *fileManager = [NSFileManager defaultManager];
    NSURL *storeUrl = [NSURL fileURLWithPath:cloudPath];
    NSURL *migrateUrl = nil;
    
    /** 
     Find out which store has the data we need to migrate,
     if any.
     */
    if (![fileManager fileExistsAtPath:cloudPath]) {
      if ([fileManager fileExistsAtPath:preCloudPath]) {
        // Migrate old application data.
        migrateUrl = [NSURL fileURLWithPath:preCloudPath];
      } else if ([fileManager fileExistsAtPath:oldStorePath]) {
        // Migrate old store to new AND migrate into cloud.
      } else if ([fileManager fileExistsAtPath:defaultStorePath]) {
        // Migrate (copy) in initial recipe data set.
        migrateUrl = [NSURL fileURLWithPath:defaultStorePath];
      }
    }

    // this needs to match the entitlements and provisioning profile
    NSURL *cloudURL = nil;
    if([fileManager respondsToSelector:@selector(URLForUbiquityContainerIdentifier:)])
    {
      cloudURL = [fileManager URLForUbiquityContainerIdentifier:nil];
      NSString* coreDataCloudContent = [[cloudURL path] stringByAppendingPathComponent:@"chefsbook_v14b3"];
      cloudURL = [NSURL fileURLWithPath:coreDataCloudContent];
      DLog(@"cloudURL : %@", cloudURL);
    }
    
    //  The API to turn on Core Data iCloud support here.
    NSDictionary *options = nil;
    if (cloudURL) {
      options = [NSDictionary dictionaryWithObjectsAndKeys:
                 @"com.thelostsouls.chefsbook", NSPersistentStoreUbiquitousContentNameKey, 
                 cloudURL, NSPersistentStoreUbiquitousContentURLKey, 
                 [NSNumber numberWithBool:YES], NSMigratePersistentStoresAutomaticallyOption, 
                 [NSNumber numberWithBool:YES], NSInferMappingModelAutomaticallyOption
                 ,nil];
    } 
    
    NSError *error = nil;
    [persistentStoreCoordinator_ lock];
    DLog(@"Persistent Store ****LOCKED****");
    
    /**
     If I migrate url is set don't just add, migrate!
     Otherwise proceed as planned.
     */
    if (migrateUrl) {
      NSPersistentStore *srcPS = [persistentStoreCoordinator_ addPersistentStoreWithType:NSSQLiteStoreType
                                                configuration:nil
                                                          URL:migrateUrl
                                                      options:nil
                                                        error:&error];
      if (![persistentStoreCoordinator_ migratePersistentStore:srcPS
                                                         toURL:storeUrl
                                                       options:options
                                                      withType:NSSQLiteStoreType 
                                                         error:&error]) {
        DLog(@"Error migrating data: %@, %@", error, [error userInfo]);
        abort();
      }
    }
    else
    {
      if (![persistentStoreCoordinator_ addPersistentStoreWithType:NSSQLiteStoreType 
                                                     configuration:nil 
                                                               URL:storeUrl 
                                                           options:options 
                                                             error:&error]) {
        DLog(@"Error adding persistent store: %@, %@", error, [error userInfo]);
        abort();
      }    
    }
    [persistentStoreCoordinator_ unlock];
    DLog(@"Persistent Store ****UNLOCKED****");
    
    // tell the UI on the main thread we finally added the store and then
    // post a custom notification to make your views do whatever they need to such as tell their
    // NSFetchedResultsController to -performFetch again now there is a real store
    dispatch_async(dispatch_get_main_queue(), ^{
      DLog(@"asynchronously added persistent store!");
      [[NSNotificationCenter defaultCenter] 
       postNotificationName:AppDelegateSharedPSCAddedStore
       object:self userInfo:nil];
    });
  });

}

So there you go.

Still a worry are scenarios where iCloud support is enabled/disabled and/or the data deleted from the cloud via the settings menu. Hopefully nobody would do that and leave iCloud enabled or they’d end up with an empty application and no recovery. That’s also the reason I’m still supporting and improving the code that saves the docs out to iTunes.

Another thought just occurred to me. The following scenario needs serious consideration:

  1. User starts app with iCloud support, initial data set loaded.
  2. User proceeds to add a handful of data.
  3. User installs Chef’s Book on second device with iCloud enabled for app.
  4. App starts and copies in default data PLUS receiving iCloud data.

Will it fail, magically know it’s the same (eminently probable), or duplicate data?

Time to find out!

-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=

Well I found out, it hangs permanently. Thanks to some helpful people on the apple forums I know why. So the above code is only partially correct. What needs to be done is to also query data on the cloud using NSMetadataQuery and examine the results, if there is cloud data you should use that and not add a persistent store in the normal way (I guess?). What I now need to find out, having successfully been able to query the cloud and find LOTS of data on there from my previous tests, is how you should add that data.

Then I’m going to rewrite the entire core data start up code from the bottom up.

Posted in Chef's Book, Programming | Tagged , , , , , , | 15 Comments

Fun with bash and old photo libraries.

It recently dawned on me I had huge swathes of my photo collection entirely gone. Something like 3 years that were nowhere to be found on flickr or my main PC or Mac. I’m not sure how this gap appeared but I eventually found the files in a backup of my Mac from quite some time ago (I keep everything until I really need the space). Problem was it was in an old Aperture directory structure and I really just wanted to get the pictures out and sort through them from scratch. I’d used different cameras, some of the pictures weren’t mine etc. etc.

So to sort through it all I wrote a small bash script. First argument in quotes is the backup/source and the second argument in quotes is the destination. It uses imagemagicks identify command and will copy the files to:

destination/camera model/date taken/name.count.jpg

count will iterate up if the file exists, if something goes wrong it will not copy the file but will print an F instead of a dot and write a log to extractPhotos.err.


#!/bin/bash
SOURCE="$1"
DEST="$2"
cd "$SOURCE"

function getSafeFilename {
    local SAFE="$1"
    local COUNT=1
    while [ -e "$SAFE" ] 
    do
        local DIR=$(dirname "$1")
        local BASE=$(basename "$1")
        local NAME=${BASE%.*}
        SAFE="$DIR/$NAME.$COUNT.JPG"
        ((COUNT++))
    done

    echo $SAFE
}

# Identify all JPG images that are not thumbnails.
find . -iname *.jpg | grep -iv thumb | while read -r jpeg 
do
    # Identify Model of Camera
    MODEL=$(identify -format "%[EXIF:Model]" "$jpeg" | sed 's/ *$//g')
    # Identify Date taken YYYY-MM-DD
    DATETAKEN=$(identify -format "%[EXIF:DateTime]" "$jpeg")
    DATEDAY=$(echo "$DATETAKEN" | cut -d' ' -f1)
    # Get base photo name
    PHOTO=$(basename "$jpeg")

    # Set camera model if undefined
    len=${#MODEL}
    if [[ "$len" -lt "1" ]]; then
        MODEL="Unknown"
    fi

    # Make destination directory
    DESTDIR="$DEST/$MODEL/$DATEDAY"
    if [ ! -d "$DESTDIR" ]; then
        mkdir -p "$DESTDIR"
    fi

    # Check for existing picture in that directory, if so, modify destination name, iterate if nec. (move to function)
    SAFEFILE=$(getSafeFilename "$DEST/$MODEL/$DATEDAY/$PHOTO")

    # Copy file!
    cp -n "$jpeg" "$SAFEFILE" && echo -n "." || (echo -n "F"; echo "Failed: $jpeg to $SAFEFILE" >> extractPhotos.err)

    # End
    unset MODEL
    unset DATETAKEN
    unset DATEDAY
    unset DESTDIR
    unset SAFEFILE
    unset len
# End
done
Posted in Computer, Photography, Programming | Tagged , , , , , | Leave a comment

Has it been nearly a month?

It’s odd, I can’t believe it’s been that long since the last post. There hasn’t really been anything to write about, no trials with code, no revelations, just fairly steady progress. In other news I’ve finished one playthrough of The Witcher 2, which is absolutely blinding. A more recent discovery is the ancient but still utterly amazing Jagged Alliance 2. Go buy, gog have it on sale this weekend!

Posted in Miscellany | Tagged , , , , | Leave a comment