rbpod 0.0.3 → 0.0.4

Sign up to get free protection for your applications and to get access to all the features.
data/README.md CHANGED
@@ -24,88 +24,110 @@ Functional for most read-only purposes. To get started, add `require 'rbpod'` to
24
24
 
25
25
  To load a database from the filesystem:
26
26
 
27
- database = RbPod::Database.new('/mnt/ipod/') # => #<RbPod::Database:0xdeadbeef>
27
+ ```ruby
28
+ database = RbPod::Database.new('/mnt/ipod/') # => #<RbPod::Database:0xdeadbeef>
28
29
 
29
- database.version # => 42
30
- database.mountpoint # => "/mnt/ipod/"
31
- database.filename # => "/mnt/ipod/iPod_Control/iTunes/iTunesDB"
32
- database.synchronized? # => true
30
+ database.version # => 42
31
+ database.mountpoint # => "/mnt/ipod/"
32
+ database.filename # => "/mnt/ipod/iPod_Control/iTunes/iTunesDB"
33
+ database.synchronized? # => true
34
+ ```
35
+ Loading a database is common, so you can also use a shortcut:
33
36
 
34
- If you'd like to create a blank database, you can do that too:
37
+ ```ruby
38
+ database = RbPod('/mnt/ipod/')
39
+ ```
35
40
 
36
- database = RbPod::Database.create!('/tmp/ipod-blank') # => #<RbPod::Database:0xdeadbeef>
41
+ If you'd like to create a new, blank database you can do that too:
42
+
43
+ ```ruby
44
+ database = RbPod::Database.create!('/tmp/ipod-blank') # => #<RbPod::Database:0xdeadbeef>
45
+ ```
37
46
 
38
47
  ### RbPod::Device
39
48
 
40
49
  The device (if any) that backs the database can be interrogated:
41
50
 
42
- device = database.device # => #<RbPod::Device:0xdeadbeef>
51
+ ```ruby
52
+ device = database.device # => #<RbPod::Device:0xdeadbeef>
43
53
 
44
- # Model name, number, and capacity.
45
- device.capacity # => 4.0
46
- device.generation # => "Nano Video (3rd Gen.)"
47
- device.model_name # => "Nano (Silver)"
48
- device.model_number # => "A978"
54
+ # Model name, number, and capacity.
55
+ device.capacity # => 4.0
56
+ device.generation # => "Nano Video (3rd Gen.)"
57
+ device.model_name # => "Nano (Silver)"
58
+ device.model_number # => "A978"
49
59
 
50
- # Feature support.
51
- device.supports_photos? # => true
52
- device.supports_videos? # => true
53
- device.supports_artwork? # => true
54
- device.supports_podcasts? # => true
55
- device.supports_chapter_images? # => true
60
+ # Feature support.
61
+ device.supports_photos? # => true
62
+ device.supports_videos? # => true
63
+ device.supports_artwork? # => true
64
+ device.supports_podcasts? # => true
65
+ device.supports_chapter_images? # => true
56
66
 
57
- # Reading/writing SysInfo. Set a key to nil to erase it.
58
- device['ModelNumStr'] # => "xA978"
59
- device['PotsdamConf45'] = "Kilroy Was Here"
60
- device.save!
67
+ # Reading/writing SysInfo. Set a key to nil to erase it.
68
+ device['ModelNumStr'] # => "xA978"
69
+ device['PotsdamConf45'] = "Kilroy Was Here"
70
+ device.save!
71
+ ```
61
72
 
62
73
  ### RbPod::Collection
63
74
 
64
75
  All methods that return lists return a `Collection` enumerator decorated depending on it's contents:
65
76
 
66
- database.playlists # => #<RbPod::Collection:0xdeadbeef>
77
+ ```ruby
78
+ database.playlists # => #<RbPod::Collection:0xdeadbeef>
67
79
 
68
- # For a list of all the names of playlists on the iPod:
69
- database.playlists.map(&:name) # => ["iPod", "Podcasts", "Recently Added"]
80
+ # For a list of all the names of playlists on the iPod:
81
+ database.playlists.map(&:name) # => ["iPod", "Podcasts", "Recently Added"]
70
82
 
71
- # For direct access to the master playlist:
72
- database.playlists.master # => #<RbPod::Playlist:0xdeadbeef>
83
+ # For direct access to the master playlist:
84
+ database.playlists.master # => #<RbPod::Playlist:0xdeadbeef>
73
85
 
74
- # For direct access to the podcasts playlist:
75
- database.playlists.podcasts # => #<RbPod::Playlist:0xdeadbeef>
86
+ # For direct access to the podcasts playlist:
87
+ database.playlists.podcasts # => #<RbPod::Playlist:0xdeadbeef>
88
+ ```
76
89
 
77
90
  ### RbPod::Playlist
78
91
 
79
92
  All playlists support a variety of methods:
80
93
 
81
- playlist = database.playlists.master
94
+ ```ruby
95
+ playlist = database.playlists.master
82
96
 
83
- playlist.name # => "iPod"
84
- playlist.length # => 400
85
- playlist.created_on # => 2008-04-05 08:47:46 -0700
97
+ playlist.name # => "iPod"
98
+ playlist.length # => 400
99
+ playlist.created_on # => 2008-04-05 08:47:46 -0700
86
100
 
87
- playlist.master_playlist? # => true
88
- playlist.smart_playlist? # => false
89
- playlist.podcast_playlist? # => false
101
+ playlist.master_playlist? # => true
102
+ playlist.smart_playlist? # => false
103
+ playlist.podcast_playlist? # => false
90
104
 
91
- playlist.tracks # => #<RbPod::Collection:0xdeadbeef>
105
+ playlist.tracks # => #<RbPod::Collection:0xdeadbeef>
106
+ ```
92
107
 
93
108
  ### RbPod::Track
94
109
 
95
110
  Tracks also can do a lot, but not complete:
96
111
 
97
- track = database.playlists.master.tracks.first
112
+ ```ruby
113
+ track = database.playlists.master.tracks.first
98
114
 
99
- track.artist # => "Steppenwolf"
100
- track.title # => "Born To Be Wild"
101
- track.album # => "All Time Greatest Hits Remastered"
102
- track.file_type # => "MPEG audio file"
103
- track.transferred? # => true
115
+ track.artist # => "Steppenwolf"
116
+ track.title # => "Born To Be Wild"
117
+ track.album # => "All Time Greatest Hits Remastered"
118
+ track.file_type # => "MPEG audio file"
119
+ track.transferred? # => true
120
+ ````
104
121
 
105
122
  ### RbPod::Error
106
123
 
107
124
  If anything goes belly up at run time, an `RbPod::Error` exception should be thrown with a detailed message.
108
125
 
126
+ ## Todo
127
+
128
+ * The RSpec tests need a lot of work and significantly more coverage, especially for integration.
129
+ * The documentation could be spiced up a bit. It's dry and doesn't provide too much useful information.
130
+
109
131
  ## Contributing
110
132
 
111
133
  1. Fork it
data/Rakefile CHANGED
@@ -1,5 +1,6 @@
1
1
  # Bring in rake with rake-compiler's support.
2
2
  require 'rake'
3
+ require 'rake/clean'
3
4
  require 'rake/extensiontask'
4
5
 
5
6
  # Bring in bundler and it's gem tasks.
@@ -15,6 +16,9 @@ require 'rdoc/task'
15
16
  # By default, clean, compile and then test.
16
17
  task :default => [ :compile, :test ]
17
18
 
19
+ # Let Rake know what is safe to remove.
20
+ CLEAN.include [ 'pkg/*', 'doc/*' ]
21
+
18
22
  desc "Compile the native extension."
19
23
  Rake::ExtensionTask.new do |extension|
20
24
  # Some basic configuration.
@@ -35,11 +39,11 @@ end
35
39
 
36
40
  desc "Build all RDoc documentation."
37
41
  RDoc::Task.new(:rdoc) do |task|
38
- task.main = 'README.md'
39
- task.markup = 'markdown'
40
- task.rdoc_files.include("README.md", 'lib/**/*.rb', 'ext/**/*.[ch]')
41
- task.options << "--all"
42
- task.rdoc_dir = 'doc'
42
+ task.rdoc_dir = 'doc/rdoc'
43
+ task.markup = 'markdown'
44
+ task.main = 'README.md'
45
+ task.title = 'RbPod: Lightweight Ruby bindings to libgpod'
46
+ task.rdoc_files.include('README.md', 'lib/**/*.rb', 'ext/**/*.[ch]')
43
47
  end
44
48
 
45
49
  desc "Open a console with rbpod preloaded."
@@ -16,6 +16,12 @@ inline VALUE rbpod_collection_create(GList *list, VALUE type)
16
16
  return Data_Wrap_Struct(cRbPodCollection, NULL, NULL, (void *) collection);
17
17
  }
18
18
 
19
+ /*
20
+ * call-seq:
21
+ * [](index) -> Object or nil
22
+ *
23
+ * Return the item located at the given position in the list.
24
+ */
19
25
  static VALUE rbpod_collection_get(VALUE self, VALUE key)
20
26
  {
21
27
  struct collection *collection = TYPED_DATA_PTR(self, struct collection);
@@ -30,6 +36,12 @@ static VALUE rbpod_collection_get(VALUE self, VALUE key)
30
36
  return Data_Wrap_Struct(collection->klass, NULL, NULL, (void *) current->data);
31
37
  }
32
38
 
39
+ /*
40
+ * call-seq:
41
+ * last() -> Object
42
+ *
43
+ * Return the last item of the collection.
44
+ */
33
45
  static VALUE rbpod_collection_last(VALUE self)
34
46
  {
35
47
  struct collection *collection = TYPED_DATA_PTR(self, struct collection);
@@ -42,6 +54,12 @@ static VALUE rbpod_collection_last(VALUE self)
42
54
  return Data_Wrap_Struct(collection->klass, NULL, NULL, (void *) current->data);
43
55
  }
44
56
 
57
+ /*
58
+ * call-seq:
59
+ * first() -> Object
60
+ *
61
+ * Return the first item of the collection.
62
+ */
45
63
  static VALUE rbpod_collection_first(VALUE self)
46
64
  {
47
65
  struct collection *collection = TYPED_DATA_PTR(self, struct collection);
@@ -54,33 +72,46 @@ static VALUE rbpod_collection_first(VALUE self)
54
72
  return Data_Wrap_Struct(collection->klass, NULL, NULL, (void *) current->data);
55
73
  }
56
74
 
75
+ /*
76
+ * call-seq:
77
+ * length() -> Integer
78
+ *
79
+ * Return the total length of all items in the collection.
80
+ */
57
81
  static VALUE rbpod_collection_length(VALUE self)
58
82
  {
59
83
  struct collection *collection = TYPED_DATA_PTR(self, struct collection);
60
84
  return INT2NUM(g_list_length(collection->list));
61
85
  }
62
86
 
63
- static VALUE rbpod_collection_each(VALUE self)
87
+ /*
88
+ * call-seq:
89
+ * each(*args) -> Enumerator
90
+ * each(*args) { |item| block } -> Enumerator
91
+ *
92
+ * Iterate over the collection, passing each item to a given block.
93
+ * If no block was supplied, return an enumerator for the collection.
94
+ *
95
+ */
96
+ static VALUE rbpod_collection_each(VALUE self, VALUE argv)
64
97
  {
65
98
  struct collection *collection = TYPED_DATA_PTR(self, struct collection);
99
+ VALUE item = Qnil, arguments = rb_ary_dup(argv);
66
100
  GList *current = NULL;
67
- VALUE item;
68
101
 
69
- if (rb_block_given_p() == FALSE) {
70
- return rb_funcall(self, rb_intern("enum_for"), 1, ID2SYM(rb_intern("each")));
71
- }
102
+ /* Return an enumerator if a block was not supplied. */
103
+ RETURN_ENUMERATOR(self, 0, 0);
104
+
105
+ /* Prepend an empty element as a placeholder. */
106
+ rb_ary_unshift(arguments, Qnil);
72
107
 
73
108
  /* If we were supplied a block, enumerate the entire list. */
74
109
  for (current = collection->list; current != NULL; current = current->next) {
75
110
  item = Data_Wrap_Struct(collection->klass, NULL, NULL, (void *) current->data);
76
- rb_yield(item);
111
+ rb_ary_store(arguments, 0, item);
112
+ rb_yield_splat(arguments);
77
113
  }
78
114
 
79
- return Qnil;
80
- }
81
-
82
- static VALUE rbpod_collection_initialize(VALUE self)
83
- {
84
115
  return self;
85
116
  }
86
117
 
@@ -114,14 +145,14 @@ void Init_rbpod_collection(void)
114
145
  rb_define_alloc_func(cRbPodCollection, rbpod_collection_allocate);
115
146
 
116
147
  rb_include_module(cRbPodCollection, rb_mEnumerable);
148
+ rb_include_module(cRbPodCollection, rb_mComparable);
117
149
 
118
- rb_define_private_method(cRbPodCollection, "initialize", rbpod_collection_initialize, 0);
119
-
120
- rb_define_method(cRbPodCollection, "each", rbpod_collection_each, 0);
150
+ rb_define_method(cRbPodCollection, "each", rbpod_collection_each, -2);
121
151
 
122
152
  rb_define_method(cRbPodCollection, "length", rbpod_collection_length, 0);
123
153
 
124
154
  rb_define_alias(cRbPodCollection, "size", "length");
155
+ rb_define_alias(cRbPodCollection, "count", "length");
125
156
 
126
157
  rb_define_method(cRbPodCollection, "first", rbpod_collection_first, 0);
127
158
  rb_define_method(cRbPodCollection, "last", rbpod_collection_last, 0);
data/ext/rbpod/database.c CHANGED
@@ -6,6 +6,12 @@
6
6
  #include "track_collection.h"
7
7
  #include "playlist_collection.h"
8
8
 
9
+ /*
10
+ * call-seq:
11
+ * save!() -> nil
12
+ *
13
+ * Saves any changes made to the database.
14
+ */
9
15
  static VALUE rbpod_database_save(VALUE self)
10
16
  {
11
17
  Itdb_iTunesDB *database = TYPED_DATA_PTR(self, Itdb_iTunesDB);
@@ -15,9 +21,15 @@ static VALUE rbpod_database_save(VALUE self)
15
21
  return rbpod_raise_error(error);
16
22
  }
17
23
 
18
- return self;
24
+ return Qnil;
19
25
  }
20
26
 
27
+ /*
28
+ * call-seq:
29
+ * synchronized?() -> Boolean
30
+ *
31
+ * Returns true or false depending if any changes have been made, but not saved.
32
+ */
21
33
  static VALUE rbpod_database_synchronized_p(VALUE self)
22
34
  {
23
35
  Itdb_iTunesDB *database = TYPED_DATA_PTR(self, Itdb_iTunesDB);
@@ -25,6 +37,12 @@ static VALUE rbpod_database_synchronized_p(VALUE self)
25
37
  return BooleanValue(nontransferred == 0);
26
38
  }
27
39
 
40
+ /*
41
+ * call-seq:
42
+ * playlists() -> RbPod::Collection
43
+ *
44
+ * Returns a collection of all playlists added to this database.
45
+ */
28
46
  static VALUE rbpod_database_playlists_get(VALUE self)
29
47
  {
30
48
  Itdb_iTunesDB *database = TYPED_DATA_PTR(self, Itdb_iTunesDB);
@@ -35,23 +53,40 @@ static VALUE rbpod_database_tracks_get(VALUE self)
35
53
  {
36
54
  Itdb_iTunesDB *database = TYPED_DATA_PTR(self, Itdb_iTunesDB);
37
55
  Itdb_Playlist *playlist = itdb_playlist_mpl(database);
38
- /* Use the master playlist as the parent for the master track list. */
39
56
  VALUE parent = Data_Wrap_Struct(cRbPodPlaylist, NULL, NULL, (void *) playlist);
40
57
  return rbpod_track_collection_create(parent, database->tracks);
41
58
  }
42
59
 
60
+ /*
61
+ * call-seq:
62
+ * device() -> RbPod::Device
63
+ *
64
+ * Returns the device backing this database.
65
+ */
43
66
  static VALUE rbpod_database_device_get(VALUE self)
44
67
  {
45
- Itdb_iTunesDB *database = TYPED_DATA_PTR(self, Itdb_iTunesDB);
46
- return rbpod_device_create(database->device);
68
+ return rb_class_new_instance(1, &self, cRbPodDevice);
47
69
  }
48
70
 
71
+ /*
72
+ * call-seq:
73
+ * filename() -> String
74
+ *
75
+ * Returns the path on the file system to the database.
76
+ */
49
77
  static VALUE rbpod_database_filename_get(VALUE self)
50
78
  {
51
79
  Itdb_iTunesDB *database = TYPED_DATA_PTR(self, Itdb_iTunesDB);
52
- return rb_str_new2(database->filename);
80
+ VALUE file_path = rb_str_new2(database->filename);
81
+ return rb_class_new_instance(1, &file_path, rb_cPathname);
53
82
  }
54
83
 
84
+ /*
85
+ * call-seq:
86
+ * version() -> Fixnum
87
+ *
88
+ * Returns the version number of the database.
89
+ */
55
90
  static VALUE rbpod_database_version_get(VALUE self)
56
91
  {
57
92
  Itdb_iTunesDB *database = TYPED_DATA_PTR(self, Itdb_iTunesDB);
@@ -64,17 +99,24 @@ static VALUE rbpod_database_id_get(VALUE self)
64
99
  return INT2NUM(database->id);
65
100
  }
66
101
 
102
+ /*
103
+ * call-seq:
104
+ * mountpoint() -> String
105
+ *
106
+ * Returns the location of the mount point this database was parsed from.
107
+ */
67
108
  static VALUE rbpod_database_mountpoint_get(VALUE self)
68
109
  {
69
110
  Itdb_iTunesDB *database = TYPED_DATA_PTR(self, Itdb_iTunesDB);
70
- return rb_str_new2(itdb_get_mountpoint(database));
111
+ VALUE mount_point = rb_str_new2(itdb_get_mountpoint(database));
112
+ return rb_class_new_instance(1, &mount_point, rb_cPathname);
71
113
  }
72
114
 
73
115
  static VALUE rbpod_database_mountpoint_set(VALUE self, VALUE path)
74
116
  {
75
117
  Itdb_iTunesDB *database = TYPED_DATA_PTR(self, Itdb_iTunesDB);
76
118
  itdb_set_mountpoint(database, StringValueCStr(path));
77
- return rbpod_database_mountpoint_get(self);
119
+ return Qnil;
78
120
  }
79
121
 
80
122
  static void rbpod_database_deallocate(void *handle)
@@ -83,6 +125,12 @@ static void rbpod_database_deallocate(void *handle)
83
125
  return;
84
126
  }
85
127
 
128
+ /*
129
+ * call-seq:
130
+ * initialize(mount_point) -> RbPod::Database
131
+ *
132
+ * Loads a new database parsed from the given mount point.
133
+ */
86
134
  static VALUE rbpod_database_initialize(VALUE self, VALUE mount_point)
87
135
  {
88
136
  Itdb_iTunesDB *database = TYPED_DATA_PTR(self, Itdb_iTunesDB);
@@ -111,11 +159,18 @@ static VALUE rbpod_database_allocate(VALUE self)
111
159
  return Data_Wrap_Struct(cRbPodDatabase, NULL, rbpod_database_deallocate, (void *) database);
112
160
  }
113
161
 
162
+ /*
163
+ * call-seq:
164
+ * create!(mount_point) -> RbPod::Database
165
+ * create!(mount_point, device_name) -> RbPod::Database
166
+ * create!(mount_point, device_name, model_number) -> RbPod::Database
167
+ *
168
+ * Creates a new database on the file system and loads it.
169
+ */
114
170
  static VALUE rbpod_database_create(int argc, VALUE *argv, VALUE self)
115
171
  {
116
- VALUE mount_point, device_name, model_number, instance;
117
172
  gchar *_mount_point, *_device_name, *_model_number;
118
- GDir *directory = NULL;
173
+ VALUE mount_point, device_name, model_number;
119
174
  GError *error = NULL;
120
175
 
121
176
  if (rb_scan_args(argc, argv, "12", &mount_point, &device_name, &model_number) < 1) {
@@ -123,21 +178,18 @@ static VALUE rbpod_database_create(int argc, VALUE *argv, VALUE self)
123
178
  return Qnil;
124
179
  }
125
180
 
126
- /* Reject the mount point immediately if it isn't a string. */
127
- if (TYPE(mount_point) != T_STRING && RSTRING_LEN(mount_point) > 0) {
128
- rb_raise(eRbPodError, "Mount point must be a non-empty string.");
181
+ /* Check if the mount point is a directory. */
182
+ if (rb_file_directory_p(rb_cFile, mount_point) != Qtrue) {
183
+ rb_raise(eRbPodError, "The mount point must be a directory!");
129
184
  return Qnil;
130
185
  }
131
186
 
132
187
  /* If we didn't specify a device name, default to 'iPod'. */
133
- if (NIL_P(device_name) == TRUE) {
134
- device_name = rb_str_new2("iPod");
135
- }
136
-
137
- /* If the specified device name isn't a string of at least three characters, bail now. */
138
- if (TYPE(device_name) != T_STRING || RSTRING_LEN(device_name) < 3) {
188
+ if (RTEST(device_name) && (TYPE(device_name) != T_STRING || RSTRING_LEN(device_name) < 4)) {
139
189
  rb_raise(eRbPodError, "Device name should be a string of at least three characters.");
140
190
  return Qnil;
191
+ } else {
192
+ device_name = rb_str_new2("iPod");
141
193
  }
142
194
 
143
195
  /* If the specified model number is specified but isn't a string, bail now. TODO: Use a regexp, stupid. */
@@ -153,25 +205,13 @@ static VALUE rbpod_database_create(int argc, VALUE *argv, VALUE self)
153
205
  /* GPod can function with a NULL model number, however, artwork may not function properly. */
154
206
  _model_number = !NIL_P(model_number) ? StringValueCStr(model_number) : NULL;
155
207
 
156
- /* Check if the mount point is a directory. */
157
- directory = g_dir_open(_mount_point, 0, &error);
158
-
159
- if (directory == NULL) {
160
- return rbpod_raise_error(error);
161
- }
162
-
163
- /* Glib seems to think so... */
164
- g_dir_close(directory);
165
-
166
208
  /* Initialize the iPod at this mount point, with this device name and model number. */
167
209
  if (itdb_init_ipod(_mount_point, _model_number, _device_name, &error) == FALSE) {
168
210
  return rbpod_raise_error(error);
169
211
  }
170
212
 
171
- instance = rbpod_database_allocate(cRbPodDatabase);
172
-
173
213
  /* Return an instance of this class using the newly created database. */
174
- return rbpod_database_initialize(instance, mount_point);
214
+ return rb_class_new_instance(1, &mount_point, cRbPodDatabase);
175
215
  }
176
216
 
177
217
  void Init_rbpod_database(void)
@@ -187,15 +227,16 @@ void Init_rbpod_database(void)
187
227
 
188
228
  rb_define_singleton_method(cRbPodDatabase, "create!", rbpod_database_create, -1);
189
229
 
230
+ rb_define_private_method(cRbPodDatabase, "id", rbpod_database_id_get, 0);
231
+ rb_define_private_method(cRbPodDatabase, "tracks", rbpod_database_tracks_get, 0);
232
+ rb_define_private_method(cRbPodDatabase, "mountpoint=", rbpod_database_mountpoint_set, 1);
233
+
190
234
  rb_define_method(cRbPodDatabase, "mountpoint", rbpod_database_mountpoint_get, 0);
191
- rb_define_method(cRbPodDatabase, "mountpoint=", rbpod_database_mountpoint_set, 1);
192
235
 
193
- rb_define_method(cRbPodDatabase, "id", rbpod_database_id_get, 0);
194
236
  rb_define_method(cRbPodDatabase, "version", rbpod_database_version_get, 0);
195
237
  rb_define_method(cRbPodDatabase, "filename", rbpod_database_filename_get, 0);
196
238
 
197
239
  rb_define_method(cRbPodDatabase, "device", rbpod_database_device_get, 0);
198
- rb_define_method(cRbPodDatabase, "tracks", rbpod_database_tracks_get, 0);
199
240
  rb_define_method(cRbPodDatabase, "playlists", rbpod_database_playlists_get, 0);
200
241
 
201
242
  rb_define_method(cRbPodDatabase, "synchronized?", rbpod_database_synchronized_p, 0);