« |
Main
| »
December 01, 2009
resumep3 is a ruby/iphone project whose
objective is to allow you to resume listening on one device to a song
your paused on another device. So, there are n programs
for n-many devices (iphone, laptop, etc), but for the most
part each does the same thing.
- Check to see if you are playing music
- If you are:
- store your players state -- song info, time, etc -- remotely
- pause the player
- If you are NOT playing music:
- retrieve the last known player state remotely
- resume the player with this state
That really is basically all there is. The idea is that this is
supposed to be anonymous,safe, but ultimately very easy to use. So at
first I stored player state info (album,artist,track,time) IDed off
the laptop's user name -- so, 'jeff' in my case. This worked, but
there are probably more jeffs in the world. So, we need a way to have
all your devices store locally a shared key. Of the only two programs
that run this are a desktop ruby script and an iphone app, only the
iphone has a unique id. So, the process of requesting a shared key differs between the two programs:
- For the iphone, if we don't have a shared key we will request one
from a remote URL, giving our unique id.
- For the desktop, if we don't have a shared key we need to wait
for an iphone to request one and then send it over the local
network.
When I started making this change I noticed that entire desktop app was basically one file containing code from these two files:
- common.rb
- resumep3
Also, I thought it would be useful to have a iphone simulator program,
too, so that I didn't have to run in XCode. I could also test the
remote code using this. Instead of going through the needs of this
program, let me point out the differences from the normal
desktop app.
- It doesn't actually play and pause itunes
- and, it doesn't wait for a shared key, when it doesn't have one
stored locally it requests one
from get_id.php
I began using regular abstraction to pull out the common parts -- into
common.rb -- and then have two subclasses -- one for the desktop app
and one for the iphone simulator. And found that each the class
implementating these programs would each essentially have three parts:
- The ability to pause the player and play tracks
- The ability to get the state of the player
- A means to retrieve a shared key, if one was not stored locally
But I could go further and take the command line aspect of the program
out, and the argument parsing, help printing. And, what I decided to
do was to completely decompose the program into one common
interface: common_interface.rb,
which had only seven methods that needed implementing, those were:
run(has_options)
find_track(info)
find_info(track_id)
read_info(user)
play_track(track_id)
pause_player
find_the_shared_key
These methods basically fall into three interfaces:
- A Player
implements:
read_info(user)
play_track(track_id)
pause_player
- A Library
implements
find_track(info)
find_info(track_id)
- A KeyFinder
implements
Then a common base class could implement this interface solely by
implementing run(has_options), because its implementation
would be the same for both classes, and then delegating to the
appropriate instance of Player, Library, or KeyFinder for the
remaining six methods. At the end, I did use composition entirely and
the two subclasses looked like this:
class DesktopComposedCommon < ComposedCommon
def initialize(config)
super(config,
AppleScriptPlayer.new,
ITunesBasedLibrary.new,
WaitsForKey.new(config))
end
end
class IPhoneComposedCommon < ComposedCommon
def initialize(config)
super(config,
ConsolePlayer.new,
ITunesBasedLibrary.new,
RetrievesKeyFromSever.new(config,self))
end
def unique_id
'asdkfjadflkjasdlfkjasdlfkjadsf02409234098234kjsdfksdf'
end
end
This was kind of a pain, but it was fun decomposing the whole system
and really maximizing both code and data reuse.
Posted by jeff at December 1, 2009 05:06 PM