{"id":1190,"date":"2015-06-25T21:13:03","date_gmt":"2015-06-25T21:13:03","guid":{"rendered":"https:\/\/zogspat.tk\/blog\/?p=1190"},"modified":"2016-06-12T13:29:32","modified_gmt":"2016-06-12T13:29:32","slug":"building-an-electronic-programme-guide-part-1","status":"publish","type":"post","link":"https:\/\/the-plot.com\/blog\/?p=1190","title":{"rendered":"Building an Electronic Programme Guide [Part 1]"},"content":{"rendered":"<p>[Edit: 12\/06\/2016. The source I used for the programme data, called Radio Times XML TV, is just about to be shut down and replaced with another service which restricts the amount of data that can be hauled down to 24 hours. The whole premise of this app was to facilitate offline access. If you landed here via Google note that, while you might find some utility\u00a0in the various postings on the UI, the app itself is useless without the data source. If I find another suitable data source I may update the app.]<\/p>\n<p>This is the first in a series of posts about a new TV guide app that I\u2019ve been working on in my spare time for about the last 6 weeks or so. The motivation for this comes from a number of quarters. I\u2019d been scratching around for ideas for a reasonably useful Apple Watch app. I\u2019d also been finding the guide that I got off the shelf from the app store is getting more reliant on in app advertising. While I\u2019m sure there are premium, ad-free variants out there, I thought it was an interesting area for development, and one that I could extend to the watch with a \u2018what\u2019s on now \/ later\u2019 mini view.<\/p>\n<div>I now have a working app for my phone, but without all of the UI frills I\u2019m aiming for. The main guide looks like this&#8230;<\/div>\n<div>\u00a0<a href=\"https:\/\/the-plot.com\/blog\/wp-content\/uploads\/2015\/06\/epg.png\"><img loading=\"lazy\" decoding=\"async\" class=\"alignnone size-full wp-image-1191\" src=\"https:\/\/the-plot.com\/blog\/wp-content\/uploads\/2015\/06\/epg.png\" alt=\"epg\" width=\"720\" height=\"1190\" srcset=\"https:\/\/the-plot.com\/blog\/wp-content\/uploads\/2015\/06\/epg.png 720w, https:\/\/the-plot.com\/blog\/wp-content\/uploads\/2015\/06\/epg-181x300.png 181w, https:\/\/the-plot.com\/blog\/wp-content\/uploads\/2015\/06\/epg-619x1024.png 619w\" sizes=\"auto, (max-width: 720px) 100vw, 720px\" \/><\/a><\/div>\n<div><\/div>\n<div>\u2026 and with programme previews that look like this:<\/div>\n<div><\/div>\n<div><a href=\"https:\/\/the-plot.com\/blog\/wp-content\/uploads\/2015\/06\/preview.png\"><img loading=\"lazy\" decoding=\"async\" class=\"alignnone size-full wp-image-1192\" src=\"https:\/\/the-plot.com\/blog\/wp-content\/uploads\/2015\/06\/preview.png\" alt=\"preview\" width=\"713\" height=\"1197\" srcset=\"https:\/\/the-plot.com\/blog\/wp-content\/uploads\/2015\/06\/preview.png 713w, https:\/\/the-plot.com\/blog\/wp-content\/uploads\/2015\/06\/preview-178x300.png 178w\" sizes=\"auto, (max-width: 713px) 100vw, 713px\" \/><\/a><\/div>\n<div><\/div>\n<div>I\u2019m continuing the development, and it\u2019s already a pretty large codebase. It\u2019s not one that\u2019ll end up on GitHub [for reasons I\u2019ll get on to shortly], but I think there are some pretty interesting challenges that I didn\u2019t come across anything approaching kitform answers for, so I\u2019m going to pluck out the interesting parts for a deeper dive.<\/div>\n<div><\/div>\n<div>The first and obvious question is where to get the Electronic Programme Guide data and, all importantly, data that is free. After some research, I settled on a service offered by the Radio Times, which uses a standard format called XMLTV. As I\u2019m not into hacking PVRs, I\u2019d never come across this standard before. The first thing to say is that the Radio Times variant isn\u2019t actually in XML, it\u2019s character delimited. The next is that it\u2019s very clearly identified as being for personal use only &#8211; hence I\u2019m not going to publish the app in full.<\/div>\n<div><\/div>\n<div>First, let\u2019s deal with the data download from the per-channel URLs. Currently, I\u2019ve hard-wired the app to download 12 channels\u2019 worth of data, something that I ultimately plan on linking back to the UI so that the user can self select them. Each channel download contains two weeks of programme data, so over a mobile network, the overriding UX consideration is progress.<\/div>\n<div><\/div>\n<div>Having previously lost a couple of hours to this in the past, trying to hook up a fancy UI component showing progress graphically, it\u2019s one to put front and centre when you\u2019re designing the networking component. Quite commonly, servers will be generating responses dynamically, in which case, there\u2019s a very good chance the Content-Length header won\u2019t be set.<\/div>\n<div><\/div>\n<div>The progress indicator must have the total of bytes available to be able to calculate progress. The Radio Times site isn\u2019t setting the required header, so no fancy progress meters &#8211; sad face. This is actually a more complex use case anyway, because we\u2019d have needed to calculate the progress for a dozen downloads, and the progress of each is going to be interlaced.<\/div>\n<div><\/div>\n<div>What I\u2019ve done is to initiate each download in a for loop using AFNetworking, and then in the\u00a0setCompletionBlockWithSuccess\u00a0block:<\/div>\n<div><code><br \/>\nNSData *channelData = (NSData *)responseObject;<br \/>\nChannelDataParser *channelProgs = [[ChannelDataParser alloc] init];<br \/>\nNSMutableArray *asProgObjects = [channelProgs programmeChunker:channelData andChannelName:thisPhoneChannel.channelName];<br \/>\n[self storeChannelProgData: asProgObjects forThisChannelName:thisPhoneChannel.channelName];<br \/>\n<\/code><\/p>\n<div><\/div>\n<div>I\u2019ll come back to the parser in a second, but one point to focus on is the method to store the parsed results. As the network responses are interleaved, and they\u2019re all ultimately going to be written to the same Core Data entity, I\u2019ve wrapped the method in an @synchronized(managedObjectContext).<\/div>\n<div><\/div>\n<div>In that same @synchronized method, I increment a completed download counter, which I use to set a label in the UI. Pretty crude, but given the progress limitations, it seems like a reasonable compromise. I\u2019ve actually spent the vast majority of the time on the UI for the programme guide, so I might take another look at this later.<\/div>\n<div><\/div>\n<div>Back to the parser itself. First up, for the NSData that contains the AFNetworking response for the channel, the first step is to convert it into a string, and then an array of strings divided by line breaks:<\/div>\n<div><code><code><br \/>\nNSString *stringFromData = [[NSString alloc] initWithData:channelData encoding:NSUTF8StringEncoding];<br \/>\n\/\/ The line break vs carriage return: done through experimentation:<br \/>\nNSArray *eachLineOfString = [stringFromData componentsSeparatedByString:@\"\\n\"];<br \/>\n<code><\/code><\/code><\/code><\/p>\n<div><\/div>\n<\/div>\n<div>The parsing was something that required trial and error, as the file contained some copyright info at the start, and then some carriage returns at the end:<\/div>\n<div><\/div>\n<div><code> for (int progCount = 2; progCount &lt;= [eachLineOfString count] -2 ; progCount++)<br \/>\n{<br \/>\nNSString *oneLine = eachLineOfString[progCount];<br \/>\nif (![oneLine isEqualToString:@\"\\r\"])<br \/>\n{<br \/>\n[.....]<br \/>\n<\/code><\/div>\n<div><\/div>\n<div>Within the loop, I then parse out the carriage returns, and finally split the data into an array, based on the separator character [a tilde]r:<\/div>\n<div><code><br \/>\nNSString *oneLineNoBreak = [oneLine stringByReplacingOccurrencesOfString:@\"\\r\" withString:@\"\"];<br \/>\n\/\/NSLog(@\"oneLineNoBreak is %lu long\", (unsigned long)oneLineNoBreak.length);<br \/>\nNSArray *oneProgrammeData = [oneLineNoBreak componentsSeparatedByString:@\"~\"];<br \/>\n<\/code><\/div>\n<div><\/div>\n<div>I think I would have struggled with the whole \\r versus \\n thing if I hadn\u2019t been bitten with it way back in the old days of the \u00a0mid 1990s. I can\u2019t recall if it was FTPing files with the wrong transfer type or using Samba between Windows and Unix machines, but either way, you were prone to getting oddities with the control characters in text files. Anyway&#8230;<\/div>\n<div><\/div>\n<div>At this point we are ready to assign values, based on the content of the array conforming to the XMLTV standard. So the 0th element of the array is the programme title, then comes a subtitle, etc. I assign these to an instance of a custom class. I found it useful to add an additional three fields, all related to the UI. The first is the running order that the programmes appear in: once you save them to Core Data as individual records representing the programmes, they become unordered &#8211; standard database stuff. Next is a GUID: I\u2019ll get into this in more detail in a subsequent posting on the UI, but the summary is that it\u2019s to associate programme data with a button action. Finally, I also include the channel name for the programme: this is to do with a limitation in the UI design &#8211; again, I\u2019ll come back to this later.<\/div>\n<div><\/div>\n<div>I cycle through parsing each channel, ultimately saving them to the core data in the @synchronised method I mentioned earlier.<\/div>\n<div><\/div>\n<div>One final point on the networking side. I do a simple check in a Core Data entity with a single value to see if it\u2019s the first run. If it is, I haul down the data before presenting the UI. I also haven\u2019t finished the part of the UI where the user can self select the channels. For now, it\u2019s hardwired. This list of channels and the corresponding URLs also gets written to another simple entity during that first run.<\/div>\n<div><\/div>\n<div>I\u2019ll follow up with a description of the UI design\u2026.<\/div>\n<\/div>\n","protected":false},"excerpt":{"rendered":"<p>[Edit: 12\/06\/2016. The source I used for the programme data, called Radio Times XML TV, is just about to be shut down and replaced with another service which restricts the amount of data that can be hauled down to 24 &hellip; <a href=\"https:\/\/the-plot.com\/blog\/?p=1190\">Continue reading <span class=\"meta-nav\">&rarr;<\/span><\/a><\/p>\n","protected":false},"author":1,"featured_media":0,"comment_status":"open","ping_status":"open","sticky":false,"template":"","format":"standard","meta":{"footnotes":""},"categories":[3],"tags":[],"class_list":["post-1190","post","type-post","status-publish","format-standard","hentry","category-ios-development"],"_links":{"self":[{"href":"https:\/\/the-plot.com\/blog\/index.php?rest_route=\/wp\/v2\/posts\/1190","targetHints":{"allow":["GET"]}}],"collection":[{"href":"https:\/\/the-plot.com\/blog\/index.php?rest_route=\/wp\/v2\/posts"}],"about":[{"href":"https:\/\/the-plot.com\/blog\/index.php?rest_route=\/wp\/v2\/types\/post"}],"author":[{"embeddable":true,"href":"https:\/\/the-plot.com\/blog\/index.php?rest_route=\/wp\/v2\/users\/1"}],"replies":[{"embeddable":true,"href":"https:\/\/the-plot.com\/blog\/index.php?rest_route=%2Fwp%2Fv2%2Fcomments&post=1190"}],"version-history":[{"count":11,"href":"https:\/\/the-plot.com\/blog\/index.php?rest_route=\/wp\/v2\/posts\/1190\/revisions"}],"predecessor-version":[{"id":1259,"href":"https:\/\/the-plot.com\/blog\/index.php?rest_route=\/wp\/v2\/posts\/1190\/revisions\/1259"}],"wp:attachment":[{"href":"https:\/\/the-plot.com\/blog\/index.php?rest_route=%2Fwp%2Fv2%2Fmedia&parent=1190"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"https:\/\/the-plot.com\/blog\/index.php?rest_route=%2Fwp%2Fv2%2Fcategories&post=1190"},{"taxonomy":"post_tag","embeddable":true,"href":"https:\/\/the-plot.com\/blog\/index.php?rest_route=%2Fwp%2Fv2%2Ftags&post=1190"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}