rbpod 0.0.3 → 0.0.4

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
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);