rbpod 0.0.2 → 0.0.3

Sign up to get free protection for your applications and to get access to all the features.
data/.rspec ADDED
@@ -0,0 +1 @@
1
+ --require spec_helper
data/README.md CHANGED
@@ -18,47 +18,93 @@ Or install it yourself as:
18
18
 
19
19
  ## Usage
20
20
 
21
- Functional for most read-only purposes.
21
+ Functional for most read-only purposes. To get started, add `require 'rbpod'` to your script.
22
22
 
23
- #!/usr/bin/env ruby
23
+ ### RbPod::Database
24
24
 
25
- require 'rbpod'
25
+ To load a database from the filesystem:
26
26
 
27
- # Explicitly load a database from the filesystem -- can be a mount point to an iPod.
28
- database = RbPod::Database.new('/mnt/ipod/') # => #<RbPod::Database:0x8733fa4>
27
+ database = RbPod::Database.new('/mnt/ipod/') # => #<RbPod::Database:0xdeadbeef>
29
28
 
30
29
  database.version # => 42
31
30
  database.mountpoint # => "/mnt/ipod/"
32
31
  database.filename # => "/mnt/ipod/iPod_Control/iTunes/iTunesDB"
33
32
  database.synchronized? # => true
34
33
 
35
- # Device information, including model name, number, and capacity.
36
- database.device.capacity # => 4.0
37
- database.device.generation # => "Nano Video (3rd Gen.)"
38
- database.device.model_name # => "Nano (Silver)"
39
- database.device.model_number # => "A978"
40
-
41
- # Device feature support detection.
42
- database.device.supports_photos? # => true
43
- database.device.supports_videos? # => true
44
- database.device.supports_artwork? # => true
45
- database.device.supports_podcasts? # => true
46
- database.device.supports_chapter_images? # => true
47
-
48
- # Device SysInfo read/write.
49
- database.device['ModelNumStr'] # => "xA978"
50
- database.device['PotsdamConf45'] = "Kilroy Was Here"
51
- database.device.save!
52
-
53
- # Playlists (includes enumerable)
34
+ If you'd like to create a blank database, you can do that too:
35
+
36
+ database = RbPod::Database.create!('/tmp/ipod-blank') # => #<RbPod::Database:0xdeadbeef>
37
+
38
+ ### RbPod::Device
39
+
40
+ The device (if any) that backs the database can be interrogated:
41
+
42
+ device = database.device # => #<RbPod::Device:0xdeadbeef>
43
+
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"
49
+
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
56
+
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!
61
+
62
+ ### RbPod::Collection
63
+
64
+ All methods that return lists return a `Collection` enumerator decorated depending on it's contents:
65
+
66
+ database.playlists # => #<RbPod::Collection:0xdeadbeef>
67
+
68
+ # For a list of all the names of playlists on the iPod:
54
69
  database.playlists.map(&:name) # => ["iPod", "Podcasts", "Recently Added"]
55
70
 
56
- # Track Listing (also enumerable)
57
- database.tracks.length # => 400
58
- database.tracks.first.artist # => "Steppenwolf"
59
- database.tracks.first.title # => "Born To Be Wild"
71
+ # For direct access to the master playlist:
72
+ database.playlists.master # => #<RbPod::Playlist:0xdeadbeef>
73
+
74
+ # For direct access to the podcasts playlist:
75
+ database.playlists.podcasts # => #<RbPod::Playlist:0xdeadbeef>
76
+
77
+ ### RbPod::Playlist
78
+
79
+ All playlists support a variety of methods:
80
+
81
+ playlist = database.playlists.master
82
+
83
+ playlist.name # => "iPod"
84
+ playlist.length # => 400
85
+ playlist.created_on # => 2008-04-05 08:47:46 -0700
86
+
87
+ playlist.master_playlist? # => true
88
+ playlist.smart_playlist? # => false
89
+ playlist.podcast_playlist? # => false
90
+
91
+ playlist.tracks # => #<RbPod::Collection:0xdeadbeef>
92
+
93
+ ### RbPod::Track
94
+
95
+ Tracks also can do a lot, but not complete:
96
+
97
+ track = database.playlists.master.tracks.first
98
+
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
104
+
105
+ ### RbPod::Error
60
106
 
61
- It's alpha quality -- many pieces still need to be implemented. Patches welcome.
107
+ If anything goes belly up at run time, an `RbPod::Error` exception should be thrown with a detailed message.
62
108
 
63
109
  ## Contributing
64
110
 
data/Rakefile CHANGED
@@ -9,6 +9,9 @@ require 'bundler/gem_tasks'
9
9
  # Bring in RSpec's built-in rake task.
10
10
  require 'rspec/core/rake_task'
11
11
 
12
+ # Bring in RDoc's built-in rake task.
13
+ require 'rdoc/task'
14
+
12
15
  # By default, clean, compile and then test.
13
16
  task :default => [ :compile, :test ]
14
17
 
@@ -30,6 +33,15 @@ RSpec::Core::RakeTask.new(:test) do |task|
30
33
  task.ruby_opts = '-w'
31
34
  end
32
35
 
36
+ desc "Build all RDoc documentation."
37
+ 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'
43
+ end
44
+
33
45
  desc "Open a console with rbpod preloaded."
34
46
  task :console => [ :compile ] do
35
47
  sh 'bundle console'
@@ -3,53 +3,72 @@
3
3
  #include "rbpod.h"
4
4
  #include "collection.h"
5
5
 
6
- VALUE cRbPodCollection;
6
+ struct collection {
7
+ VALUE klass;
8
+ GList *list;
9
+ };
7
10
 
8
- struct collection { VALUE klass; GList *list; };
9
-
10
- inline VALUE rbpod_collection_create(GList *list, VALUE type) {
11
+ inline VALUE rbpod_collection_create(GList *list, VALUE type)
12
+ {
11
13
  struct collection *collection = ALLOC(struct collection);
12
14
  collection->list = list;
13
15
  collection->klass = type;
14
16
  return Data_Wrap_Struct(cRbPodCollection, NULL, NULL, (void *) collection);
15
17
  }
16
18
 
17
- static VALUE rbpod_collection_get(VALUE self, VALUE key) {
19
+ static VALUE rbpod_collection_get(VALUE self, VALUE key)
20
+ {
18
21
  struct collection *collection = TYPED_DATA_PTR(self, struct collection);
19
22
  GList *current = NULL;
20
23
 
21
- if (FIXNUM_P(key) == FALSE)
24
+ if (FIXNUM_P(key) == FALSE) {
22
25
  return Qnil;
26
+ }
23
27
 
24
28
  current = g_list_nth(collection->list, FIX2INT(key));
25
29
 
26
30
  return Data_Wrap_Struct(collection->klass, NULL, NULL, (void *) current->data);
27
31
  }
28
32
 
29
- static VALUE rbpod_collection_last(VALUE self) {
33
+ static VALUE rbpod_collection_last(VALUE self)
34
+ {
30
35
  struct collection *collection = TYPED_DATA_PTR(self, struct collection);
31
36
  GList *current = g_list_last(collection->list);
37
+
38
+ if (current == NULL) {
39
+ return Qnil;
40
+ }
41
+
32
42
  return Data_Wrap_Struct(collection->klass, NULL, NULL, (void *) current->data);
33
43
  }
34
44
 
35
- static VALUE rbpod_collection_first(VALUE self) {
45
+ static VALUE rbpod_collection_first(VALUE self)
46
+ {
36
47
  struct collection *collection = TYPED_DATA_PTR(self, struct collection);
37
48
  GList *current = g_list_first(collection->list);
49
+
50
+ if (current == NULL) {
51
+ return Qnil;
52
+ }
53
+
38
54
  return Data_Wrap_Struct(collection->klass, NULL, NULL, (void *) current->data);
39
55
  }
40
56
 
41
- static VALUE rbpod_collection_length(VALUE self) {
57
+ static VALUE rbpod_collection_length(VALUE self)
58
+ {
42
59
  struct collection *collection = TYPED_DATA_PTR(self, struct collection);
43
60
  return INT2NUM(g_list_length(collection->list));
44
61
  }
45
62
 
46
- static VALUE rbpod_collection_each(VALUE self) {
63
+ static VALUE rbpod_collection_each(VALUE self)
64
+ {
47
65
  struct collection *collection = TYPED_DATA_PTR(self, struct collection);
48
66
  GList *current = NULL;
49
67
  VALUE item;
50
68
 
51
- if (rb_block_given_p() == FALSE)
69
+ if (rb_block_given_p() == FALSE) {
52
70
  return rb_funcall(self, rb_intern("enum_for"), 1, ID2SYM(rb_intern("each")));
71
+ }
53
72
 
54
73
  /* If we were supplied a block, enumerate the entire list. */
55
74
  for (current = collection->list; current != NULL; current = current->next) {
@@ -60,35 +79,43 @@ static VALUE rbpod_collection_each(VALUE self) {
60
79
  return Qnil;
61
80
  }
62
81
 
63
- static VALUE rbpod_collection_initialize(VALUE self) {
82
+ static VALUE rbpod_collection_initialize(VALUE self)
83
+ {
64
84
  return self;
65
85
  }
66
86
 
67
- static void rbpod_collection_deallocate(void *handle) {
87
+ static void rbpod_collection_deallocate(void *handle)
88
+ {
68
89
  struct collection *collection = (struct collection *) handle;
69
90
 
70
- if (collection->list != NULL)
91
+ if (collection->list != NULL) {
71
92
  g_list_free(collection->list);
93
+ }
72
94
 
73
95
  xfree(handle);
74
96
  return;
75
97
  }
76
98
 
77
- static VALUE rbpod_collection_allocate(VALUE self) {
99
+ static VALUE rbpod_collection_allocate(VALUE self)
100
+ {
78
101
  struct collection *collection = ALLOC(struct collection);
79
102
  collection->list = NULL;
80
103
  collection->klass = Qnil;
81
104
  return Data_Wrap_Struct(cRbPodCollection, NULL, rbpod_collection_deallocate, (void *) collection);
82
105
  }
83
106
 
84
- void Init_rbpod_collection(void) {
107
+ void Init_rbpod_collection(void)
108
+ {
109
+ #if RDOC_CAN_PARSE_DOCUMENTATION
110
+ mRbPod = rb_define_module("RbPod");
111
+ #endif
85
112
  cRbPodCollection = rb_define_class_under(mRbPod, "Collection", rb_cObject);
86
113
 
87
114
  rb_define_alloc_func(cRbPodCollection, rbpod_collection_allocate);
88
115
 
89
116
  rb_include_module(cRbPodCollection, rb_mEnumerable);
90
117
 
91
- rb_define_method(cRbPodCollection, "initialize", rbpod_collection_initialize, 0);
118
+ rb_define_private_method(cRbPodCollection, "initialize", rbpod_collection_initialize, 0);
92
119
 
93
120
  rb_define_method(cRbPodCollection, "each", rbpod_collection_each, 0);
94
121
 
@@ -3,7 +3,7 @@
3
3
  #ifndef RBPOD_COLLECTION_H
4
4
  #define RBPOD_COLLECTION_H
5
5
 
6
- extern VALUE cRbPodCollection;
6
+ RUBY_EXTERN VALUE cRbPodCollection;
7
7
 
8
8
  void Init_rbpod_collection(void);
9
9
 
data/ext/rbpod/database.c CHANGED
@@ -1,32 +1,38 @@
1
1
  /* database.c */
2
2
 
3
3
  #include "rbpod.h"
4
- #include "track.h"
5
4
  #include "device.h"
6
- #include "playlist.h"
7
5
  #include "database.h"
8
- #include "collection.h"
6
+ #include "track_collection.h"
7
+ #include "playlist_collection.h"
9
8
 
10
- VALUE cRbPodDatabase;
11
-
12
- static VALUE rbpod_database_write(VALUE self) {
9
+ static VALUE rbpod_database_save(VALUE self)
10
+ {
13
11
  Itdb_iTunesDB *database = TYPED_DATA_PTR(self, Itdb_iTunesDB);
14
- gboolean success = itdb_write(database, NULL); /* TODO: Improve error handling. */
15
- return BooleanValue(success);
12
+ GError *error = NULL;
13
+
14
+ if (itdb_write(database, &error) == FALSE) {
15
+ return rbpod_raise_error(error);
16
+ }
17
+
18
+ return self;
16
19
  }
17
20
 
18
- static VALUE rbpod_database_is_synchronized(VALUE self) {
21
+ static VALUE rbpod_database_synchronized_p(VALUE self)
22
+ {
19
23
  Itdb_iTunesDB *database = TYPED_DATA_PTR(self, Itdb_iTunesDB);
20
24
  guint32 nontransferred = itdb_tracks_number_nontransferred(database);
21
25
  return BooleanValue(nontransferred == 0);
22
26
  }
23
27
 
24
- static VALUE rbpod_database_playlists_get(VALUE self) {
28
+ static VALUE rbpod_database_playlists_get(VALUE self)
29
+ {
25
30
  Itdb_iTunesDB *database = TYPED_DATA_PTR(self, Itdb_iTunesDB);
26
31
  return rbpod_playlist_collection_create(self, database->playlists);
27
32
  }
28
33
 
29
- static VALUE rbpod_database_tracks_get(VALUE self) {
34
+ static VALUE rbpod_database_tracks_get(VALUE self)
35
+ {
30
36
  Itdb_iTunesDB *database = TYPED_DATA_PTR(self, Itdb_iTunesDB);
31
37
  Itdb_Playlist *playlist = itdb_playlist_mpl(database);
32
38
  /* Use the master playlist as the parent for the master track list. */
@@ -34,103 +40,152 @@ static VALUE rbpod_database_tracks_get(VALUE self) {
34
40
  return rbpod_track_collection_create(parent, database->tracks);
35
41
  }
36
42
 
37
- static VALUE rbpod_database_device_get(VALUE self) {
43
+ static VALUE rbpod_database_device_get(VALUE self)
44
+ {
38
45
  Itdb_iTunesDB *database = TYPED_DATA_PTR(self, Itdb_iTunesDB);
39
46
  return rbpod_device_create(database->device);
40
47
  }
41
48
 
42
- static VALUE rbpod_database_filename_get(VALUE self) {
49
+ static VALUE rbpod_database_filename_get(VALUE self)
50
+ {
43
51
  Itdb_iTunesDB *database = TYPED_DATA_PTR(self, Itdb_iTunesDB);
44
52
  return rb_str_new2(database->filename);
45
53
  }
46
54
 
47
- static VALUE rbpod_database_version_get(VALUE self) {
55
+ static VALUE rbpod_database_version_get(VALUE self)
56
+ {
48
57
  Itdb_iTunesDB *database = TYPED_DATA_PTR(self, Itdb_iTunesDB);
49
58
  return INT2NUM(database->version);
50
59
  }
51
60
 
52
- static VALUE rbpod_database_id_get(VALUE self) {
61
+ static VALUE rbpod_database_id_get(VALUE self)
62
+ {
53
63
  Itdb_iTunesDB *database = TYPED_DATA_PTR(self, Itdb_iTunesDB);
54
64
  return INT2NUM(database->id);
55
65
  }
56
66
 
57
- static VALUE rbpod_database_mountpoint_get(VALUE self) {
67
+ static VALUE rbpod_database_mountpoint_get(VALUE self)
68
+ {
58
69
  Itdb_iTunesDB *database = TYPED_DATA_PTR(self, Itdb_iTunesDB);
59
70
  return rb_str_new2(itdb_get_mountpoint(database));
60
71
  }
61
72
 
62
- static VALUE rbpod_database_mountpoint_set(VALUE self, VALUE path) {
73
+ static VALUE rbpod_database_mountpoint_set(VALUE self, VALUE path)
74
+ {
63
75
  Itdb_iTunesDB *database = TYPED_DATA_PTR(self, Itdb_iTunesDB);
64
76
  itdb_set_mountpoint(database, StringValueCStr(path));
65
77
  return rbpod_database_mountpoint_get(self);
66
78
  }
67
79
 
68
- static void rbpod_database_deallocate(void *handle) {
80
+ static void rbpod_database_deallocate(void *handle)
81
+ {
69
82
  itdb_free((Itdb_iTunesDB *) handle);
70
83
  return;
71
84
  }
72
85
 
73
- static VALUE rbpod_database_create(VALUE self, VALUE device_name, VALUE mount_point, VALUE model_number) {
74
- gchar *_mount_point, *_model_number, *_device_name;
75
- Itdb_iTunesDB *database = NULL;
76
-
77
- _device_name = StringValueCStr(device_name);
78
- _mount_point = StringValueCStr(mount_point);
86
+ static VALUE rbpod_database_initialize(VALUE self, VALUE mount_point)
87
+ {
88
+ Itdb_iTunesDB *database = TYPED_DATA_PTR(self, Itdb_iTunesDB);
89
+ Itdb_iTunesDB *previous = database;
90
+ GError *error = NULL;
79
91
 
80
- /* GPod can function with a NULL model number, however, artwork will not function. */
81
- _model_number = !NIL_P(model_number) ? StringValueCStr(model_number) : NULL;
92
+ /* Try to parse the database from the given mount point. */
93
+ database = itdb_parse(StringValueCStr(mount_point), &error);
82
94
 
83
- /* Initialize the iPod at this mount point, with this device name and model number. */
84
- /* TODO: Improve error handling. */
85
- if (itdb_init_ipod(_mount_point, _model_number, _device_name, NULL) == FALSE) {
86
- rb_raise(eRbPodError, "Unable to format this iPod.");
87
- return Qfalse;
95
+ if (database == NULL) {
96
+ return rbpod_raise_error(error);
88
97
  }
89
98
 
90
- /* Parse the newly created database. */
91
- /* TODO: Improve error handling. */
92
- database = itdb_parse(_mount_point, NULL);
99
+ /* Overwrite our old database with the new one. */
100
+ DATA_PTR(self) = database;
93
101
 
94
- if (database == NULL) {
95
- rb_raise(eRbPodError, "Not an iPod mount point.");
96
- return Qnil;
97
- }
102
+ /* Free the old one. */
103
+ itdb_free(previous);
98
104
 
99
- /* Return an instance of this class using the newly created database. */
105
+ return self;
106
+ }
107
+
108
+ static VALUE rbpod_database_allocate(VALUE self)
109
+ {
110
+ Itdb_iTunesDB *database = itdb_new();
100
111
  return Data_Wrap_Struct(cRbPodDatabase, NULL, rbpod_database_deallocate, (void *) database);
101
112
  }
102
113
 
103
- static VALUE rbpod_database_initialize(VALUE self, VALUE mount_point) {
104
- Itdb_iTunesDB *database = TYPED_DATA_PTR(self, Itdb_iTunesDB);
114
+ static VALUE rbpod_database_create(int argc, VALUE *argv, VALUE self)
115
+ {
116
+ VALUE mount_point, device_name, model_number, instance;
117
+ gchar *_mount_point, *_device_name, *_model_number;
118
+ GDir *directory = NULL;
119
+ GError *error = NULL;
105
120
 
106
- /* Try to parse the database from the given mount point. */
107
- /* TODO: Improve error handling. */
108
- database = itdb_parse(StringValueCStr(mount_point), NULL);
121
+ if (rb_scan_args(argc, argv, "12", &mount_point, &device_name, &model_number) < 1) {
122
+ rb_raise(eRbPodError, "Invalid arguments.");
123
+ return Qnil;
124
+ }
109
125
 
110
- /* The given mount point was not an iPod mount point. */
111
- if (database == NULL) {
112
- rb_raise(eRbPodError, "This is not an iPod mount point.");
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.");
113
129
  return Qnil;
114
130
  }
115
131
 
116
- DATA_PTR(self) = database;
132
+ /* 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
+ }
117
136
 
118
- return self;
119
- }
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) {
139
+ rb_raise(eRbPodError, "Device name should be a string of at least three characters.");
140
+ return Qnil;
141
+ }
120
142
 
121
- static VALUE rbpod_database_allocate(VALUE self) {
122
- Itdb_iTunesDB *database = itdb_new();
123
- return Data_Wrap_Struct(cRbPodDatabase, NULL, rbpod_database_deallocate, (void *) database);
143
+ /* If the specified model number is specified but isn't a string, bail now. TODO: Use a regexp, stupid. */
144
+ if (NIL_P(model_number) == FALSE && (TYPE(model_number) != T_STRING || RSTRING_LEN(model_number) < 4)) {
145
+ rb_raise(eRbPodError, "Model number should be a string of at least four characters.");
146
+ return Qnil;
147
+ }
148
+
149
+ /* Extract pointers for glib use. */
150
+ _mount_point = StringValueCStr(mount_point);
151
+ _device_name = StringValueCStr(device_name);
152
+
153
+ /* GPod can function with a NULL model number, however, artwork may not function properly. */
154
+ _model_number = !NIL_P(model_number) ? StringValueCStr(model_number) : NULL;
155
+
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
+ /* Initialize the iPod at this mount point, with this device name and model number. */
167
+ if (itdb_init_ipod(_mount_point, _model_number, _device_name, &error) == FALSE) {
168
+ return rbpod_raise_error(error);
169
+ }
170
+
171
+ instance = rbpod_database_allocate(cRbPodDatabase);
172
+
173
+ /* Return an instance of this class using the newly created database. */
174
+ return rbpod_database_initialize(instance, mount_point);
124
175
  }
125
176
 
126
- void Init_rbpod_database(void) {
177
+ void Init_rbpod_database(void)
178
+ {
179
+ #if RDOC_CAN_PARSE_DOCUMENTATION
180
+ mRbPod = rb_define_module("RbPod");
181
+ #endif
127
182
  cRbPodDatabase = rb_define_class_under(mRbPod, "Database", rb_cObject);
128
183
 
129
184
  rb_define_alloc_func(cRbPodDatabase, rbpod_database_allocate);
130
185
 
131
186
  rb_define_method(cRbPodDatabase, "initialize", rbpod_database_initialize, 1);
132
187
 
133
- rb_define_singleton_method(cRbPodDatabase, "create!", rbpod_database_create, 3);
188
+ rb_define_singleton_method(cRbPodDatabase, "create!", rbpod_database_create, -1);
134
189
 
135
190
  rb_define_method(cRbPodDatabase, "mountpoint", rbpod_database_mountpoint_get, 0);
136
191
  rb_define_method(cRbPodDatabase, "mountpoint=", rbpod_database_mountpoint_set, 1);
@@ -143,8 +198,8 @@ void Init_rbpod_database(void) {
143
198
  rb_define_method(cRbPodDatabase, "tracks", rbpod_database_tracks_get, 0);
144
199
  rb_define_method(cRbPodDatabase, "playlists", rbpod_database_playlists_get, 0);
145
200
 
146
- rb_define_method(cRbPodDatabase, "synchronized?", rbpod_database_is_synchronized, 0);
201
+ rb_define_method(cRbPodDatabase, "synchronized?", rbpod_database_synchronized_p, 0);
147
202
 
148
- rb_define_method(cRbPodDatabase, "write!", rbpod_database_write, 0);
203
+ rb_define_method(cRbPodDatabase, "save!", rbpod_database_save, 0);
149
204
  }
150
205
 
data/ext/rbpod/database.h CHANGED
@@ -3,7 +3,7 @@
3
3
  #ifndef RBPOD_DATABASE_H
4
4
  #define RBPOD_DATABASE_H
5
5
 
6
- extern VALUE cRbPodDatabase;
6
+ RUBY_EXTERN VALUE cRbPodDatabase;
7
7
 
8
8
  void Init_rbpod_database(void);
9
9