Skip to content
This repository

HTTPS clone URL

Subversion checkout URL

You can clone with HTTPS or Subversion.

Download ZIP
Browse code

First portion of the Bundle tutorial for the MacRuby website

git-svn-id: http://svn.macosforge.org/repository/ruby/MacRubyWebsite/trunk@4456 23306eb0-4c56-4727-a40e-e92c0eb68959
  • Loading branch information...
commit 2338f77771e06f3a333581b68477758ea8f27efe 1 parent ca021d5
Matt Aimonetti mattetti authored

Showing 1 changed file with 195 additions and 0 deletions. Show diff stats Hide diff stats

  1. +195 0 content/realworld-dynamic-bundles.txt
195 content/realworld-dynamic-bundles.txt
... ... @@ -0,0 +1,195 @@
  1 +---
  2 +title: Real-world dynamic bundle creation
  3 +created_at: 2010-08-08 23:41:08.125402 +01:00
  4 +updated_at: 2010-08-08 23:41:08.125777 +01:00
  5 +tutorial: true
  6 +author: Nick Ludlam
  7 +filter:
  8 + - erb
  9 + - textile
  10 +---
  11 +h1(title). <%= h(@page.title) %>
  12 +
  13 +<div class="author">
  14 + By <%= member_name(@page.author) %>
  15 +</div>
  16 +
  17 +<div class='tutorial'>
  18 +
  19 +In this tutorial we will examine a real-world case of wanting to read ID3 tags from MP3 files in a quick and efficient manner from MacRuby. The solution must also be packagable, so we can build a stand-alone version of the application, free of any specific system dependencies.
  20 +
  21 +While there are some existing gems to read ID3 tags, the native Ruby implentation, <em>id3lib-ruby</em>, does not compile with the current version (0.6 as of this tutorial creation) of MacRuby, and the other gem (id3) complicates portability by wrapping a C library.
  22 +
  23 +To bring about a cleaner solution, we can take advantage of MacRuby's ability to load Objective-C bundles at runtime. The Objective-C layer of these bundles can be a very thin wrapper around native C or C++ calls, giving us a powerful flexibility to pull in any kind of compiled code we choose. In doing this, we can also ensure that the library is built as a fat binary, ensuring maximum compatibility with end users.
  24 +
  25 +h3. Preparation
  26 +
  27 +The framework chosen was one called TagLib (<a href="http://developer.kde.org/~wheeler/taglib.html">developer.kde.org/~wheeler/taglib.html</a>), which is a nicely clean and portable C++ implementation.
  28 +
  29 +Conveniently, a user on GitHub has already done some of the work for us in making an XCode project which builds a Framework of this library. After forking and cloning this Git project, we can start to build our Obj-C wrapper class around the library's functionality. We can get the basic Framework project set up with the following:
  30 +
  31 +<pre class="commands">
  32 +git clone http://github.com/rahvin/TagLib.framework.git
  33 +cd TagLib.framework
  34 +wget http://developer.kde.org/~wheeler/files/src/taglib-1.6.3.tar.gz
  35 +tar zxvf taglib-1.6.3.tar.gz
  36 +mv taglib-1.6.3 taglib-src
  37 +</pre>
  38 +
  39 +You can use the latest revision of TagLib, but this example will reference the 1.6.3 release.
  40 +
  41 +The last thing we need to do to the existing Framework target is add in the C wrapper functionality, which is an optional part of TagLib. We will utilise these C bindings later on, as they simplify the amount of code we need to write, and handle much of the UTF-8 string conversion for us.
  42 +
  43 +Select "Add to Project..." from the Project menu, and navigate to "taglib-src/bindings/c/", and add both "tag_c.cpp" and "tag_c.h". If you build the project at this stage, there should be no errors. So far, so good.
  44 +
  45 +h3. Adding our Bundle target
  46 +
  47 +The first thing we need to do is add a new Target to our project. Select "New Target..." from the Project menu, and select "Loadable Bundle". Call this <code>TagLibBundle</code>.
  48 +
  49 +Open up the "Compile Sources" folder inside the "TagLib" Framework target. Shift-select every file in this folder, and open "Get Info" in the File menu. Open up the Targets tab at the top, and check the tickbox next to <code>TagLibBundle</code>. This ensures that every file used to compile the framework is also used to compile our new bundle target.
  50 +
  51 +Now open the <code>TagLibBundle</code> target info pane, and search for "Preprocessor Macros". Add <code>HAVE_CONFIG_H</code> to this entry. This is related to the way the project has been ported from the Autoconf setup of the original source code.
  52 +
  53 +!/images/realworld-dynamic-bundles/preprocessor_macros.png!
  54 +
  55 +While this info view is open, we also need to remove the entries under "Prefix Header"
  56 +
  57 +!/images/realworld-dynamic-bundles/remove_prefix_header.png!
  58 +
  59 +and "Other Linker Flags". These are set up by default, and are not required for our bundle.
  60 +
  61 +!/images/realworld-dynamic-bundles/remove_other_linker_flags.png!
  62 +
  63 +
  64 +Now swap to the "General" tab and ensure that we are linking the project against the "Foundation" framework and "libz" shared library. Check that the project builds OK at this stage.
  65 +
  66 +h3. Adding our Objective-C wrapper class
  67 +
  68 +Now comes the fun part. We write a very simple Objective-C class to wrap the C functionality of the <code>TagLib</code> code.
  69 +
  70 +We will take a very simplistic approach in buidling the wrapper, as this ensures easy memory management. Our class will have an <code>initWithFileAtPath:</code> method, which we will use to initialise our class, and perform the scan of the file for tags. The tags themselves will be placed inside an <code>NSDictionary</code> to be read at a later point. Lastly the <code>dealloc</code> method will release the <code>NSDictionary</code>. All of the potentially tricky memory management is contained only within the <code>initWithFileAtPath:</code> method, and occurs within a single method invocation.
  71 +
  72 +Now we need to add an Objective-C class which will perform our wrapping duties. Add a new Objective-C file named "TagLib.m" to the project, subclassed from <code>NSObject</code>. Ensure that is is only part of the TagLibBundle target.
  73 +
  74 +!/images/realworld-dynamic-bundles/new_wrapper_class.png!
  75 +
  76 +
  77 +Add a new Objective-C file to the <code>TagLibBundle</code> target called <code>TagLib.m</code>. Once we synthesize a simple <code>NSDictionary</code> to contain our tag, our header looks like:
  78 +
  79 +<% coderay :lang => 'c' do -%>
  80 +@interface TagLib : NSObject {
  81 + NSDictionary *tags;
  82 +}
  83 +
  84 +@property (nonatomic, retain) NSDictionary *tags;
  85 +
  86 +@end
  87 +<% end %>
  88 +
  89 +and our <code>TagLib.m</code> file:
  90 +
  91 +<% coderay :lang => 'c' do -%>
  92 +@implementation TagLib
  93 +
  94 +@synthesize tags;
  95 +
  96 +- (id)init {
  97 + if (self = [super init]) {
  98 + self.tags = [NSDictionary dictionary];
  99 + }
  100 +
  101 + return self;
  102 +}
  103 +
  104 +- (void)dealloc {
  105 + [tags release]; tags = nil;
  106 + [super dealloc];
  107 +}
  108 +
  109 +@end
  110 +<% end %>
  111 +
  112 +<% coderay :lang => 'c' do -%>
  113 +
  114 +- (id)initWithFileAtPath:(NSString *)filePath {
  115 + if (self = [super init]) {
  116 +
  117 + // Our mutable dictionary for accumulation
  118 + NSMutableDictionary *tempDictionary = [NSMutableDictionary dictionary];
  119 +
  120 + // Initialisation as per the TagLib example C code
  121 + TagLib_File *file;
  122 + TagLib_Tag *tag;
  123 +
  124 + // We want UTF8 strings out of TagLib
  125 + taglib_set_strings_unicode(TRUE);
  126 +
  127 + file = taglib_file_new([filePath cStringUsingEncoding:NSUTF8StringEncoding]);
  128 +
  129 + if (file != NULL) {
  130 + tag = taglib_file_tag(file);
  131 +
  132 + // Collect title, artist, album, comment, genre, track and year in turn.
  133 + // Sanity check them for presence, and length
  134 + if (taglib_tag_title(tag) != NULL &&
  135 + strlen(taglib_tag_title(tag)) > 0) {
  136 + NSString *title = [NSString stringWithCString:taglib_tag_title(tag)
  137 + encoding:NSUTF8StringEncoding];
  138 + [tempDictionary setObject:title forKey:@"title"];
  139 + }
  140 +
  141 + if (taglib_tag_artist(tag) != NULL &&
  142 + strlen(taglib_tag_artist(tag)) > 0) {
  143 + NSString *artist = [NSString stringWithCString:taglib_tag_artist(tag)
  144 + encoding:NSUTF8StringEncoding];
  145 + [tempDictionary setObject:artist forKey:@"artist"];
  146 + }
  147 +
  148 + if (taglib_tag_album(tag) != NULL &&
  149 + strlen(taglib_tag_album(tag)) > 0) {
  150 + NSString *album = [NSString stringWithCString:taglib_tag_album(tag)
  151 + encoding:NSUTF8StringEncoding];
  152 + [tempDictionary setObject:album forKey:@"album"];
  153 + }
  154 +
  155 + if (taglib_tag_comment(tag) != NULL &&
  156 + strlen(taglib_tag_comment(tag)) > 0) {
  157 + NSString *comment = [NSString stringWithCString:taglib_tag_comment(tag)
  158 + encoding:NSUTF8StringEncoding];
  159 + [tempDictionary setObject:comment forKey:@"comment"];
  160 + }
  161 +
  162 + if (taglib_tag_genre(tag) != NULL &&
  163 + strlen(taglib_tag_genre(tag)) > 0) {
  164 + NSString *genre = [NSString stringWithCString:taglib_tag_genre(tag)
  165 + encoding:NSUTF8StringEncoding];
  166 + [tempDictionary setObject:genre forKey:@"genre"];
  167 + }
  168 +
  169 + // Year and track are uints
  170 + if (taglib_tag_year(tag) > 0) {
  171 + NSNumber *year = [NSNumber numberWithUnsignedInt:taglib_tag_year(tag)];
  172 + [tempDictionary setObject:year forKey:@"year"];
  173 + }
  174 +
  175 + if (taglib_tag_track(tag) > 0) {
  176 + NSNumber *track = [NSNumber numberWithUnsignedInt:taglib_tag_track(tag)];
  177 + [tempDictionary setObject:track forKey:@"track"];
  178 + }
  179 +
  180 + // Free up our used memory so far
  181 + taglib_tag_free_strings();
  182 + taglib_file_free(file);
  183 +
  184 + }
  185 +
  186 + // Make immutable
  187 + self.tags = [NSDictionary dictionaryWithDictionary:tempDictionary];
  188 + [tempDictionary release];
  189 + }
  190 +
  191 + return self;
  192 +}
  193 +<% end %>
  194 +
  195 +</div>

0 comments on commit 2338f77

Please sign in to comment.
Something went wrong with that request. Please try again.