ruby-freedb 0.6
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +7 -0
- data/ext/freedb_cdrom/extconf.rb +9 -0
- data/ext/freedb_cdrom/freedb_cdrom.c +207 -0
- data/lib/freedb.rb +638 -0
- metadata +50 -0
checksums.yaml
ADDED
@@ -0,0 +1,7 @@
|
|
1
|
+
---
|
2
|
+
SHA1:
|
3
|
+
metadata.gz: ac0470c2d99f0522088f36660bbc98a407674bea
|
4
|
+
data.tar.gz: 05b89f019dfa43587b5c49a82f59e83db746e859
|
5
|
+
SHA512:
|
6
|
+
metadata.gz: d779275beaf433f832ac0af4fb6bb531cbd36fa0da8fc4a6882ab7d37ff0eac1a9f43e15ef7f90170a3d5b7860ed9dad6902edc75712e7aa3ade66bad3255f90
|
7
|
+
data.tar.gz: 1987cfe6889b95dac68a282e3799040741425a003b55496e148856a75a273928314062e1da233bf65cd82bc8334f81e71fec01915990485ecc3ed788b2dc433e
|
@@ -0,0 +1,207 @@
|
|
1
|
+
/* $Id: freedb_cdrom.c,v 1.5 2003/02/07 14:16:16 moumar Exp $ */
|
2
|
+
/*
|
3
|
+
* Copyright (c) 1999,2001 Robert Woodcock <rcw@debian.org>
|
4
|
+
* This code is hereby licensed for public consumption under either the
|
5
|
+
* GNU GPL v2 or greater, or Larry Wall's Artistic license - your choice.
|
6
|
+
|
7
|
+
* You should have received a copy of the GNU General Public License
|
8
|
+
* along with this program; if not, write to the Free Software
|
9
|
+
* Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
|
10
|
+
*/
|
11
|
+
#include <stdio.h>
|
12
|
+
#include <stdlib.h>
|
13
|
+
#include <fcntl.h>
|
14
|
+
#include <sys/ioctl.h>
|
15
|
+
#include <unistd.h>
|
16
|
+
|
17
|
+
#include <ruby.h>
|
18
|
+
|
19
|
+
/* Porting credits:
|
20
|
+
* Solaris: David Champion <dgc@uchicago.edu>
|
21
|
+
* FreeBSD: Niels Bakker <niels@bakker.net>
|
22
|
+
* OpenBSD: Marcus Daniel <danielm@uni-muenster.de>
|
23
|
+
* NetBSD: Chris Gilbert <chris@NetBSD.org>
|
24
|
+
*/
|
25
|
+
|
26
|
+
/* Modified for ruby/freedb by Guillaume Pierronnet <moumar@netcourrier.com> */
|
27
|
+
|
28
|
+
#if defined(OS_LINUX)
|
29
|
+
# include <linux/cdrom.h>
|
30
|
+
|
31
|
+
#elif defined(OS_SOLARIS)
|
32
|
+
# include <sys/cdio.h>
|
33
|
+
# define CD_MSF_OFFSET 150
|
34
|
+
# define CD_FRAMES 75
|
35
|
+
|
36
|
+
#elif defined(OS_FREEBSD)
|
37
|
+
#include <sys/cdio.h>
|
38
|
+
#define CDROM_LBA CD_LBA_FORMAT /* first frame is 0 */
|
39
|
+
#define CD_MSF_OFFSET 150 /* MSF offset of first frame */
|
40
|
+
#define CD_FRAMES 75 /* per second */
|
41
|
+
#define CDROM_LEADOUT 0xAA /* leadout track */
|
42
|
+
#define CDROMREADTOCHDR CDIOREADTOCHEADER
|
43
|
+
#define CDROMREADTOCENTRY CDIOREADTOCENTRY
|
44
|
+
#define cdrom_tochdr ioc_toc_header
|
45
|
+
#define cdth_trk0 starting_track
|
46
|
+
#define cdth_trk1 ending_track
|
47
|
+
#define cdrom_tocentry ioc_read_toc_single_entry
|
48
|
+
#define cdte_track track
|
49
|
+
#define cdte_format address_format
|
50
|
+
#define cdte_addr entry.addr
|
51
|
+
|
52
|
+
#elif defined(OS_OPENBSD) || defined(OS_NETBSD)
|
53
|
+
#include <sys/cdio.h>
|
54
|
+
#define CDROM_LBA CD_LBA_FORMAT /* first frame is 0 */
|
55
|
+
#define CD_MSF_OFFSET 150 /* MSF offset of first frame */
|
56
|
+
#define CD_FRAMES 75 /* per second */
|
57
|
+
#define CDROM_LEADOUT 0xAA /* leadout track */
|
58
|
+
#define CDROMREADTOCHDR CDIOREADTOCHEADER
|
59
|
+
#define cdrom_tochdr ioc_toc_header
|
60
|
+
#define cdth_trk0 starting_track
|
61
|
+
#define cdth_trk1 ending_track
|
62
|
+
#define cdrom_tocentry cd_toc_entry
|
63
|
+
#define cdte_track track
|
64
|
+
#define cdte_addr addr
|
65
|
+
|
66
|
+
#endif
|
67
|
+
|
68
|
+
VALUE cFreedb;
|
69
|
+
|
70
|
+
int cddb_sum (int n)
|
71
|
+
{
|
72
|
+
/* a number like 2344 becomes 2+3+4+4 (13) */
|
73
|
+
int ret=0;
|
74
|
+
|
75
|
+
while (n > 0) {
|
76
|
+
ret = ret + (n % 10);
|
77
|
+
n = n / 10;
|
78
|
+
}
|
79
|
+
|
80
|
+
return ret;
|
81
|
+
}
|
82
|
+
|
83
|
+
/*
|
84
|
+
* Returns a valid DISCID for the CD in (device)
|
85
|
+
*/
|
86
|
+
static VALUE fdb_get_cdrom(VALUE self, VALUE device) {
|
87
|
+
char str[1201];
|
88
|
+
#if defined(OS_OPENBSD) || defined(OS_NETBSD)
|
89
|
+
struct ioc_read_toc_entry t;
|
90
|
+
#endif
|
91
|
+
int len;
|
92
|
+
int drive, i, totaltime;
|
93
|
+
long int cksum=0;
|
94
|
+
unsigned char last=1;
|
95
|
+
struct cdrom_tochdr hdr;
|
96
|
+
struct cdrom_tocentry *TocEntry;
|
97
|
+
|
98
|
+
|
99
|
+
char offsets[1089] = "", buff[255];
|
100
|
+
|
101
|
+
SafeStringValue(device);
|
102
|
+
drive = open(RSTRING_PTR(device), O_RDONLY | O_NONBLOCK);
|
103
|
+
|
104
|
+
if (drive < 0) {
|
105
|
+
rb_sys_fail(RSTRING_PTR(device));
|
106
|
+
}
|
107
|
+
|
108
|
+
if (ioctl(drive,CDROMREADTOCHDR,&hdr) < 0) {
|
109
|
+
close(drive);
|
110
|
+
rb_sys_fail("Failed to read TOC entry");
|
111
|
+
}
|
112
|
+
|
113
|
+
last=hdr.cdth_trk1;
|
114
|
+
len = (last + 1) * sizeof (struct cdrom_tocentry);
|
115
|
+
/*
|
116
|
+
if (TocEntry) {
|
117
|
+
free(TocEntry);
|
118
|
+
TocEntry = 0;
|
119
|
+
}
|
120
|
+
*/
|
121
|
+
TocEntry = malloc(len);
|
122
|
+
if (!TocEntry) {
|
123
|
+
close(drive);
|
124
|
+
rb_sys_fail("Can't allocate memory for TOC entries");
|
125
|
+
}
|
126
|
+
#if defined(OS_OPENBSD)
|
127
|
+
t.address_format = CDROM_LBA;
|
128
|
+
t.starting_track = 0;
|
129
|
+
t.data_len = len;
|
130
|
+
t.data = TocEntry;
|
131
|
+
|
132
|
+
if (ioctl(drive, CDIOREADTOCENTRYS, (char *) &t) < 0)
|
133
|
+
free(TocEntry);
|
134
|
+
close(drive);
|
135
|
+
rb_sys_fail("Failed to read TOC entry");
|
136
|
+
}
|
137
|
+
#elif defined(OS_NETBSD)
|
138
|
+
t.address_format = CDROM_LBA;
|
139
|
+
t.starting_track = 1;
|
140
|
+
t.data_len = len;
|
141
|
+
t.data = TocEntry;
|
142
|
+
memset(TocEntry, 0, len);
|
143
|
+
|
144
|
+
if(ioctl(drive, CDIOREADTOCENTRYS, (char *) &t) < 0) {
|
145
|
+
free(TocEntry);
|
146
|
+
close(drive);
|
147
|
+
rb_sys_fail("Failed to read TOC entry");
|
148
|
+
}
|
149
|
+
#else
|
150
|
+
|
151
|
+
for (i=0; i < last; i++) {
|
152
|
+
TocEntry[i].cdte_track = i + 1; /* tracks start with 1, but i must start with 0 on OpenBSD */
|
153
|
+
TocEntry[i].cdte_format = CDROM_LBA;
|
154
|
+
if (ioctl(drive, CDROMREADTOCENTRY, &TocEntry[i]) < 0) {
|
155
|
+
free(TocEntry);
|
156
|
+
close(drive);
|
157
|
+
rb_sys_fail("Failed to read TOC entry");
|
158
|
+
}
|
159
|
+
}
|
160
|
+
|
161
|
+
TocEntry[last].cdte_track = CDROM_LEADOUT;
|
162
|
+
TocEntry[last].cdte_format = CDROM_LBA;
|
163
|
+
if (ioctl(drive, CDROMREADTOCENTRY, &TocEntry[i]) < 0) {
|
164
|
+
free(TocEntry);
|
165
|
+
close(drive);
|
166
|
+
rb_sys_fail("Failed to read TOC entry");
|
167
|
+
}
|
168
|
+
#endif
|
169
|
+
close(drive);
|
170
|
+
|
171
|
+
#if defined(OS_FREEBSD)
|
172
|
+
TocEntry[i].cdte_addr.lba = ntohl(TocEntry[i].cdte_addr.lba);
|
173
|
+
#endif
|
174
|
+
|
175
|
+
for (i=0; i < last; i++) {
|
176
|
+
#if defined(OS_FREEBSD)
|
177
|
+
TocEntry[i].cdte_addr.lba = ntohl(TocEntry[i].cdte_addr.lba);
|
178
|
+
#endif
|
179
|
+
cksum += cddb_sum((TocEntry[i].cdte_addr.lba + CD_MSF_OFFSET) / CD_FRAMES);
|
180
|
+
}
|
181
|
+
|
182
|
+
totaltime = ((TocEntry[last].cdte_addr.lba + CD_MSF_OFFSET) / CD_FRAMES) -
|
183
|
+
((TocEntry[0].cdte_addr.lba + CD_MSF_OFFSET) / CD_FRAMES);
|
184
|
+
|
185
|
+
|
186
|
+
for (i = 0; i < last; i++) { /* write offsets */
|
187
|
+
sprintf(buff, "%d ", TocEntry[i].cdte_addr.lba + CD_MSF_OFFSET);
|
188
|
+
strcat(offsets, buff);
|
189
|
+
}
|
190
|
+
|
191
|
+
sprintf(buff,"%d", (TocEntry[last].cdte_addr.lba + CD_MSF_OFFSET) / CD_FRAMES);
|
192
|
+
strcat(offsets, buff);
|
193
|
+
|
194
|
+
sprintf(str, "%08lx %d %s", (cksum % 0xff) << 24 | totaltime << 8 | last, last, offsets);
|
195
|
+
|
196
|
+
free(TocEntry);
|
197
|
+
|
198
|
+
return rb_str_new2(str);
|
199
|
+
}
|
200
|
+
|
201
|
+
void Init_freedb_cdrom() {
|
202
|
+
|
203
|
+
cFreedb = rb_define_class("Freedb", rb_cObject);
|
204
|
+
rb_define_private_method(cFreedb, "get_cdrom", fdb_get_cdrom, 1);
|
205
|
+
//rb_require("freedb_misc.rb");
|
206
|
+
|
207
|
+
}
|
data/lib/freedb.rb
ADDED
@@ -0,0 +1,638 @@
|
|
1
|
+
# $Id: freedb.rb,v 1.22 2003/02/13 15:52:04 moumar Exp $
|
2
|
+
# = Description
|
3
|
+
#
|
4
|
+
# ruby-freedb is a Ruby library who provide access to cddb/freedb servers as
|
5
|
+
# well as local database, can dump the "discid" from a CD and submit new
|
6
|
+
# entries to the freedb database.
|
7
|
+
#
|
8
|
+
#
|
9
|
+
# = Download
|
10
|
+
#
|
11
|
+
# get tar.gz and debian packages at
|
12
|
+
# http://davedd.free.fr/ruby-freedb/download/
|
13
|
+
#
|
14
|
+
#
|
15
|
+
# = Installation
|
16
|
+
#
|
17
|
+
# <b>CAUTION</b>: Some files have changed since 0.4, please clean up your old ruby-freedb
|
18
|
+
# (0.3.1 and older) installation before installing this one by deleting our
|
19
|
+
# freedb_misc.rb and freedb.so.
|
20
|
+
#
|
21
|
+
# $ ruby extconf.rb
|
22
|
+
# $ make
|
23
|
+
# $ make install
|
24
|
+
#
|
25
|
+
#
|
26
|
+
# = Examples
|
27
|
+
# see examples/ directory for more advanced examples
|
28
|
+
#
|
29
|
+
# === get all possible matches for CD in "/dev/cdrom"
|
30
|
+
#
|
31
|
+
# freedb = Freedb.new("/dev/cdrom")
|
32
|
+
# freedb.fetch
|
33
|
+
# freedb.results.each { |r| puts r }
|
34
|
+
#
|
35
|
+
# === getting full description
|
36
|
+
# # get "rock" match for this cd
|
37
|
+
# freedb.get_result("rock")
|
38
|
+
#
|
39
|
+
# === make something with your freedb object
|
40
|
+
# puts freedb.title # disc's title
|
41
|
+
# puts freedb.artist # disc's artist
|
42
|
+
# puts freedb.length # disc's length in seconds
|
43
|
+
# puts freedb.tracks.size # number of tracks on the CD
|
44
|
+
# puts freedb.tracks[3]["title"] # title of the track 4
|
45
|
+
# # (indexing begin at 0)
|
46
|
+
# puts freedb.tracks[5]["length"] # length of track 6 in seconds
|
47
|
+
#
|
48
|
+
#
|
49
|
+
# = Testing
|
50
|
+
#
|
51
|
+
# In order to run all tests, you have to burn the "freedb CD Test" at
|
52
|
+
# http://ftp.freedb.org/pub/freedb/misc/freedb_testcd.zip
|
53
|
+
# and you must be connected to internet.
|
54
|
+
#
|
55
|
+
# Test::Unit library is used for tests. see http://testunit.talbott.ws/
|
56
|
+
#
|
57
|
+
# $ cd test/
|
58
|
+
# $ ruby test_all.rb
|
59
|
+
#
|
60
|
+
# = ToDo
|
61
|
+
# * CD-ROM access under Win32
|
62
|
+
#
|
63
|
+
# = Changelog
|
64
|
+
#
|
65
|
+
# [0.5 07/02/2003]
|
66
|
+
#
|
67
|
+
# * submission (http or mail) added
|
68
|
+
# * fetching from disk in Unix or Windows format added
|
69
|
+
# * "raw_response" attribute added (raw response from the server) [Fernando Arbeiza <arbeizaf@ono.com>]
|
70
|
+
# * "tracks" removed (however it can be redefined with 'tracks.collect { |h| h["title"] }'
|
71
|
+
# * "tracks_ext" renamed to "tracks"
|
72
|
+
# * "genre" renamed to "category"
|
73
|
+
# * "exact_genre" renamed to "genre"
|
74
|
+
# * "get_result(index)": index can be a String that represents the freedb category
|
75
|
+
# * FetchCGI: does not rely on cgi.rb anymore
|
76
|
+
# * documentation written with "rdoc"
|
77
|
+
#
|
78
|
+
#
|
79
|
+
# [0.4.2 10/01/2003]
|
80
|
+
#
|
81
|
+
# * Fixed a bug in track length computation [Fernando Arbeiza <arbeizaf@ono.com>]
|
82
|
+
#
|
83
|
+
#
|
84
|
+
# [0.4.1 13/10/2002]
|
85
|
+
#
|
86
|
+
# * Improved cddb parser [Akinori MUSHA <knu@iDaemons.org>]
|
87
|
+
# * Many bugs fixed in freedb_cdrom.c [Akinori MUSHA <knu@iDaemons.org>]
|
88
|
+
#
|
89
|
+
#
|
90
|
+
# [0.4 28/09/2002]
|
91
|
+
#
|
92
|
+
# * length attribute added
|
93
|
+
# * tracks_ext attribute added
|
94
|
+
# * fixed a bug in discid computation [Akinori MUSHA <knu@iDaemons.org>]
|
95
|
+
# * protocol level handling
|
96
|
+
# * test suite
|
97
|
+
# * code refactoring
|
98
|
+
# * file renaming (change nothing for end users)
|
99
|
+
#
|
100
|
+
#
|
101
|
+
# [0.3.1 30/08/2002]
|
102
|
+
#
|
103
|
+
# * genre read-only attribute added,
|
104
|
+
# * fixes syntax error due to a change in the Ruby interpreter. [Akinori MUSHA <knu@iDaemons.org>]
|
105
|
+
# * debianization
|
106
|
+
#
|
107
|
+
#
|
108
|
+
# [0.3 07/04/2002]
|
109
|
+
#
|
110
|
+
# * fetch() replaced by fetch_net() however i created an alias to fetch()
|
111
|
+
# * fetch_cgi() added
|
112
|
+
# * discid read-only attribute added
|
113
|
+
# * free() bug on FreeBSD fixed in get_cdrom() [Stephane D'Alu <sdalu@loria.fr>]
|
114
|
+
# * get_cdrom() buffer overrun fixed [OGAWA Takaya <t-ogawa@triaez.kaisei.org>]
|
115
|
+
#
|
116
|
+
#
|
117
|
+
# [0.2 19/01/2002]
|
118
|
+
#
|
119
|
+
# * Big cleaning of code.
|
120
|
+
# * Minimum code ( just the CDROM access ) written in C. Other is in pure Ruby.
|
121
|
+
# * Module now called 'freedb' instead of 'Freedb'.
|
122
|
+
# * Deleted specific exceptions. There is only one now (FreedbError).
|
123
|
+
#
|
124
|
+
#
|
125
|
+
# [0.1 18/12/2001]
|
126
|
+
#
|
127
|
+
# * Initial version
|
128
|
+
#
|
129
|
+
# License:: GPL
|
130
|
+
# Author:: Guillaume Pierronnet (mailto:moumar@netcourrier.com)
|
131
|
+
# Website:: http://davedd.free.fr/ruby-freedb/
|
132
|
+
|
133
|
+
# Raised on any kind of error related to ruby-freedb (cd-rom, network, protocol)
|
134
|
+
class FreedbError < StandardError ; end
|
135
|
+
|
136
|
+
class Freedb
|
137
|
+
|
138
|
+
VERSION = "0.6"
|
139
|
+
PROTO_LEVEL = 5
|
140
|
+
CD_FRAME = 75
|
141
|
+
VALID_CATEGORIES = [ "blues", "classical", "country", "data", "folk", "jazz", "misc", "newage", "reggae", "rock", "soundtrack" ]
|
142
|
+
|
143
|
+
# cddbid of the CD
|
144
|
+
attr_reader(:discid)
|
145
|
+
|
146
|
+
# the complete string used to query the database
|
147
|
+
attr_reader(:query)
|
148
|
+
|
149
|
+
# total length of the CD
|
150
|
+
attr_reader(:length)
|
151
|
+
|
152
|
+
# an array with all possible results for this CD
|
153
|
+
attr_reader(:results)
|
154
|
+
|
155
|
+
# string containing raw entry from freedb database
|
156
|
+
attr_reader(:raw_response)
|
157
|
+
|
158
|
+
# artist of the CD, must not be empty
|
159
|
+
attr_accessor(:artist)
|
160
|
+
|
161
|
+
# title of the CD, must not be empty
|
162
|
+
attr_accessor(:title)
|
163
|
+
|
164
|
+
# freedb category, must be one of +Freedb::VALID_CATEGORIES+
|
165
|
+
attr_accessor(:category)
|
166
|
+
|
167
|
+
# arbitraty string for the genre
|
168
|
+
attr_accessor(:genre)
|
169
|
+
|
170
|
+
# year of the cd (0 if not known)
|
171
|
+
attr_accessor(:year)
|
172
|
+
|
173
|
+
# an array of hashs containing following keys:
|
174
|
+
# "title" (must not be empty), "length", "ext" (for extended infos)
|
175
|
+
attr_accessor(:tracks)
|
176
|
+
|
177
|
+
# extended infos of the CD
|
178
|
+
attr_accessor(:ext_infos)
|
179
|
+
|
180
|
+
# If +is_query+ is false, the discid of the CD in +param+ is dumped.
|
181
|
+
# Else +param+ is considered as a valid freedb query string and is used directly.
|
182
|
+
def initialize(param = "/dev/cdrom", is_query = false)
|
183
|
+
@query =
|
184
|
+
if is_query
|
185
|
+
param
|
186
|
+
else
|
187
|
+
require "freedb_cdrom/freedb_cdrom"
|
188
|
+
get_cdrom(param)
|
189
|
+
end
|
190
|
+
q = @query.split(" ")
|
191
|
+
@discid = q[0]
|
192
|
+
nb_tracks = q[1].to_i
|
193
|
+
@length = q[-1].to_i
|
194
|
+
@offsets = q[2...-1] << @length*CD_FRAME
|
195
|
+
@offsets.collect! { |x| x.to_i }
|
196
|
+
|
197
|
+
@tracks = Array.new
|
198
|
+
|
199
|
+
nb_tracks.times { |i|
|
200
|
+
t = Hash.new
|
201
|
+
t["length"] = ((@offsets[i+1]-@offsets[i]).to_f/CD_FRAME).round
|
202
|
+
@tracks << t
|
203
|
+
}
|
204
|
+
@revision = 0
|
205
|
+
@raw_response = ""
|
206
|
+
end
|
207
|
+
|
208
|
+
|
209
|
+
# Query database using network
|
210
|
+
# Fill the +results+ array with multiple results.
|
211
|
+
# return nil if no match found
|
212
|
+
def fetch_net(server = "freedb.org", port = 8880)
|
213
|
+
@handler = FetchNet.new(server, port)
|
214
|
+
_fetch
|
215
|
+
end
|
216
|
+
|
217
|
+
alias :fetch :fetch_net
|
218
|
+
|
219
|
+
# Query database using CGI (HTTP) method.
|
220
|
+
# Fill the +results+ array with multiple results.
|
221
|
+
# return nil if no match found
|
222
|
+
def fetch_cgi(server = "www.freedb.org", port = 80, proxy = nil, proxy_port = nil, path = "/~cddb/cddb.cgi")
|
223
|
+
@handler = FetchCGI.new(server, port, proxy, proxy_port, path)
|
224
|
+
_fetch
|
225
|
+
end
|
226
|
+
|
227
|
+
# Query database using local directory. Set +win_format+ to true
|
228
|
+
# if the database has windows format (see freedb howto in "misc/" for details)
|
229
|
+
# return nil if no match found
|
230
|
+
def fetch_disk(directory, win_format = false)
|
231
|
+
@handler = FetchDisk.new(directory, win_format)
|
232
|
+
_fetch
|
233
|
+
end
|
234
|
+
|
235
|
+
# submit the current Freedb object using http
|
236
|
+
# +from+ is an email adress used to return submissions errors
|
237
|
+
# +submit_mode+ can be set to "test" to check submission validity (for developpers)
|
238
|
+
# return nil
|
239
|
+
def submit_http(from = "user@localhost", server = "freedb.org", port = 80, path = "/~cddb/submit.cgi", submit_mode = "submit")
|
240
|
+
require "net/http"
|
241
|
+
headers = {
|
242
|
+
"Category" => @category,
|
243
|
+
"Discid" => @discid,
|
244
|
+
"User-Email" => from,
|
245
|
+
"Submit-Mode" => submit_mode,
|
246
|
+
"Charset" => "ISO-8859-1",
|
247
|
+
"X-Cddbd-Note" => "Sent by ruby-freedb #{VERSION}"
|
248
|
+
}
|
249
|
+
Net::HTTP.start(server, port) { |http|
|
250
|
+
reply, body = http.post(path, submit_body(), headers)
|
251
|
+
if reply.code != 200
|
252
|
+
raise(FreedbError, "Bad response from server: '#{body.chop}'")
|
253
|
+
end
|
254
|
+
}
|
255
|
+
nil
|
256
|
+
end
|
257
|
+
alias :submit :submit_http
|
258
|
+
|
259
|
+
# submit the current Freedb object using smtp
|
260
|
+
# return +nil+
|
261
|
+
def submit_mail(smtp_server, from = "localuser@localhost", port = 25, to = "freedb-submit@freedb.org")
|
262
|
+
# +to+ can be set to "test-submit@freedb.org" to check validity (for
|
263
|
+
# developpers)
|
264
|
+
require "net/smtp"
|
265
|
+
header = {
|
266
|
+
"From" => from,
|
267
|
+
"To" => to,
|
268
|
+
"Subject" => "cddb #{@category} #{@discid}",
|
269
|
+
"MIME-Version" => "1.0",
|
270
|
+
"Content-Type" => "text/plain",
|
271
|
+
"Content-Transfer-Encoding" => "quoted-printable",
|
272
|
+
"X-Cddbd-Note" => "Sent by ruby-freedb #{VERSION}"
|
273
|
+
}
|
274
|
+
msg = ""
|
275
|
+
header.each { |k, v|
|
276
|
+
msg << "#{k}: #{v}\r\n"
|
277
|
+
}
|
278
|
+
msg << "\r\n"
|
279
|
+
msg << submit_body
|
280
|
+
Net::SMTP.start(smtp_server, port) { |smtp| smtp.send_mail(msg, from, to) }
|
281
|
+
nil
|
282
|
+
end
|
283
|
+
|
284
|
+
# Retrieve full result from the database.
|
285
|
+
# If +index+ is a Fixnum, get the +index+'th result in the +result+ array
|
286
|
+
# If +index+ is a String, +index+ is the freedb category
|
287
|
+
def get_result(index)
|
288
|
+
|
289
|
+
if index.is_a?(String)
|
290
|
+
idx = nil
|
291
|
+
@results.each_with_index { |r, i|
|
292
|
+
if r =~ /^#{index}/
|
293
|
+
idx = i
|
294
|
+
end
|
295
|
+
}
|
296
|
+
else
|
297
|
+
idx = index
|
298
|
+
end
|
299
|
+
|
300
|
+
md = /^\S+ [0-9a-fA-F]{8}/.match(@results[idx])
|
301
|
+
@handler.send_cmd("read", md[0])
|
302
|
+
|
303
|
+
# swallow the whole response into a hash
|
304
|
+
response = Hash.new
|
305
|
+
|
306
|
+
each_line(@handler) { |line|
|
307
|
+
@raw_response << line + "\n"
|
308
|
+
case line
|
309
|
+
when /^(\d+) (\S+)/, /^([A-Za-z0-9_]+)=(.*)/
|
310
|
+
key = $1.upcase
|
311
|
+
|
312
|
+
val = $2.gsub(/\\(.)/) {
|
313
|
+
case $1
|
314
|
+
when "t"
|
315
|
+
"\t"
|
316
|
+
when "n"
|
317
|
+
"\n"
|
318
|
+
else
|
319
|
+
$1
|
320
|
+
end
|
321
|
+
}
|
322
|
+
|
323
|
+
(response[key] ||= '') << val
|
324
|
+
when /^# Revision: (\d+)/
|
325
|
+
@revision = $1.to_i
|
326
|
+
end
|
327
|
+
}
|
328
|
+
@category = response['210']
|
329
|
+
@genre = response['DGENRE']
|
330
|
+
@year = response['DYEAR'].to_i
|
331
|
+
@ext_infos = response['EXTD']
|
332
|
+
|
333
|
+
# Use a regexp instead of a bare string to avoid ruby >= 1.7 warning
|
334
|
+
@artist, @title = response['DTITLE'].split(/ \/ /, 2)
|
335
|
+
# A self-titled album may not have a title part
|
336
|
+
@title ||= @artist
|
337
|
+
|
338
|
+
response.each { |key, val|
|
339
|
+
case key
|
340
|
+
when /^4\d\d$/
|
341
|
+
raise(FreedbError, val)
|
342
|
+
when /^TTITLE(\d+)$/
|
343
|
+
i = $1.to_i
|
344
|
+
@tracks[i]["title"] = val
|
345
|
+
when /^EXTT(\d+)$/
|
346
|
+
i = $1.to_i
|
347
|
+
@tracks[i]["ext"] = val
|
348
|
+
end
|
349
|
+
}
|
350
|
+
self
|
351
|
+
end
|
352
|
+
|
353
|
+
# close all pending connections
|
354
|
+
def close
|
355
|
+
@handler.close if @handler
|
356
|
+
@handler = nil
|
357
|
+
end
|
358
|
+
|
359
|
+
private
|
360
|
+
|
361
|
+
def _fetch
|
362
|
+
@handler.gets #banner
|
363
|
+
#@handler.send_cmd("hello", "#{ENV['USER']} #{`hostname`.chop} ruby-freedb #{VERSION}")
|
364
|
+
@handler.send_cmd("hello", "user localhost ruby-freedb #{VERSION}")
|
365
|
+
if @handler.gets.chop =~ /^4\d\d (.+)/ #welcome
|
366
|
+
raise(FreedbError, $1)
|
367
|
+
end
|
368
|
+
set_proto_level(PROTO_LEVEL)
|
369
|
+
@handler.send_cmd("query", @query)
|
370
|
+
resp = @handler.gets.chop
|
371
|
+
@results = []
|
372
|
+
case resp
|
373
|
+
when /^200 (.+)/ #single result
|
374
|
+
@results << $1
|
375
|
+
when /^211/ #multiple results
|
376
|
+
each_line(@handler) { |l|
|
377
|
+
@results << l
|
378
|
+
}
|
379
|
+
when /^202/ #no match found
|
380
|
+
return nil
|
381
|
+
end
|
382
|
+
self
|
383
|
+
end
|
384
|
+
|
385
|
+
def set_proto_level(l)
|
386
|
+
if l < 1
|
387
|
+
raise(FreedbError, "Server doesn't support level 1!")
|
388
|
+
end
|
389
|
+
@handler.send_cmd("proto", l.to_s)
|
390
|
+
if @handler.gets =~ /^501/
|
391
|
+
set_proto_level(l-1)
|
392
|
+
end
|
393
|
+
end
|
394
|
+
|
395
|
+
def each_line(handler)
|
396
|
+
until (l = handler.gets) =~ /^\./
|
397
|
+
yield l.chop
|
398
|
+
end
|
399
|
+
end
|
400
|
+
|
401
|
+
def submit_body
|
402
|
+
if @tracks.detect { |h| h["title"].empty? }
|
403
|
+
raise(FreedbError, "Some tracks title are empty")
|
404
|
+
elsif not VALID_CATEGORIES.include?(@category)
|
405
|
+
raise(FreedbError, "Category is not valid")
|
406
|
+
elsif @artist.empty?
|
407
|
+
raise(FreedbError, "Artist field must not be empty")
|
408
|
+
elsif @title.empty?
|
409
|
+
raise(FreedbError, "Title field must not be empty")
|
410
|
+
end
|
411
|
+
body = <<EOF
|
412
|
+
# xmcd CD database file
|
413
|
+
#
|
414
|
+
# Track frame offsets:
|
415
|
+
EOF
|
416
|
+
@offsets[0..-2].each { |o|
|
417
|
+
body << "# #{o}\n"
|
418
|
+
}
|
419
|
+
body << <<EOF
|
420
|
+
#
|
421
|
+
# Disc length: #{@length} seconds
|
422
|
+
#
|
423
|
+
# Revision: #{@revision}
|
424
|
+
# Submitted via: ruby-freedb #{VERSION}
|
425
|
+
#
|
426
|
+
DISCID=#{discid}
|
427
|
+
DTITLE=#{artist.gsub(/ \/ /, "/")} / #{title.gsub(/ \/ /, "/")}
|
428
|
+
DYEAR=#{@year.to_i == 0 ? "" : "%04d" % @year}
|
429
|
+
DGENRE=#{(@genre || "").split(" ").collect do |w| w.capitalize end.join(" ")}
|
430
|
+
EOF
|
431
|
+
@tracks.each_with_index { |t, i|
|
432
|
+
body << "TTITLE#{i}=#{escape(t["title"])}\n"
|
433
|
+
}
|
434
|
+
body << "EXTD=#{escape(@ext_infos)}\n"
|
435
|
+
@tracks.each_with_index { |t, i|
|
436
|
+
body << "EXTT#{i}=#{escape(t["ext"])}\n"
|
437
|
+
}
|
438
|
+
body << "PLAYORDER=\n"
|
439
|
+
body
|
440
|
+
end
|
441
|
+
|
442
|
+
#FIXME optimize that, this is UGLY!
|
443
|
+
def escape(str)
|
444
|
+
str.gsub(/\t/, '\t').gsub(/\n/, '\n').gsub(/\\/, "\\\\\\")
|
445
|
+
end
|
446
|
+
|
447
|
+
class FetchCGI #:nodoc:
|
448
|
+
|
449
|
+
def initialize(server, port, proxy, proxy_port, path)
|
450
|
+
require "net/http"
|
451
|
+
@session = Net::HTTP.new(server, port, proxy, proxy_port)
|
452
|
+
@path = path
|
453
|
+
@proto_level = 1
|
454
|
+
@res = []
|
455
|
+
end
|
456
|
+
|
457
|
+
def send_cmd(cmd, args)
|
458
|
+
if cmd == "hello"
|
459
|
+
@hello_str = "hello=" + cgi_escape(args)
|
460
|
+
@res << "201" #necessary for the next call to gets
|
461
|
+
elsif cmd == "proto"
|
462
|
+
@proto_level = args
|
463
|
+
@res << "200" #necessary for the next call to gets
|
464
|
+
else
|
465
|
+
request = "?cmd=cddb+#{cmd}+#{cgi_escape(args)}&#{@hello_str}&proto=#{@proto_level}"
|
466
|
+
resp, data = @session.get(@path + request)
|
467
|
+
raise(FreedbError, "Bad HTTP response: #{resp.code} #{resp.message} while querying server") unless resp.code == "200"
|
468
|
+
@res.concat( data.split("\n") )
|
469
|
+
end
|
470
|
+
end
|
471
|
+
|
472
|
+
def gets
|
473
|
+
#(@res.nil? ? "" : @res.shift)
|
474
|
+
@res.shift
|
475
|
+
end
|
476
|
+
|
477
|
+
def close; end
|
478
|
+
|
479
|
+
private
|
480
|
+
|
481
|
+
#stolen from cgi.rb
|
482
|
+
def cgi_escape(str)
|
483
|
+
str.gsub(/([^ a-zA-Z0-9_.-]+)/n) do
|
484
|
+
'%' + $1.unpack('H2' * $1.size).join('%').upcase
|
485
|
+
end.tr(' ', '+')
|
486
|
+
end
|
487
|
+
|
488
|
+
end
|
489
|
+
|
490
|
+
class FetchNet #:nodoc:
|
491
|
+
|
492
|
+
def initialize(server, port)
|
493
|
+
require "socket"
|
494
|
+
@socket = TCPSocket.new(server, port)
|
495
|
+
end
|
496
|
+
|
497
|
+
def send_cmd(cmd, args)
|
498
|
+
@socket.puts("cddb #{cmd} #{args}")
|
499
|
+
end
|
500
|
+
|
501
|
+
def gets
|
502
|
+
@socket.gets
|
503
|
+
end
|
504
|
+
|
505
|
+
def close
|
506
|
+
@socket.close
|
507
|
+
end
|
508
|
+
end
|
509
|
+
|
510
|
+
class FetchDisk #:nodoc:
|
511
|
+
def initialize(basedir, win_format)
|
512
|
+
raise(FreedbError, "#{basedir} is not a directory") if not File.directory?(basedir)
|
513
|
+
@basedir = File.expand_path(basedir)
|
514
|
+
@win = win_format
|
515
|
+
@res = ["201"]
|
516
|
+
|
517
|
+
# used to store results to avoid rescanning files when getting result
|
518
|
+
# hash key if the name of the category
|
519
|
+
@temp_results = {}
|
520
|
+
|
521
|
+
# storing full directory and name of differents categories ( each
|
522
|
+
# represented by a directory)
|
523
|
+
@categs = Dir.entries(@basedir).collect do |d|
|
524
|
+
dir = File.join(@basedir, d)
|
525
|
+
if File.directory?(dir) and d !~ /^\.\.?$/
|
526
|
+
@temp_results[d] = []
|
527
|
+
[dir, d]
|
528
|
+
end
|
529
|
+
end
|
530
|
+
@categs.compact!
|
531
|
+
end
|
532
|
+
|
533
|
+
def send_cmd(cmd, args)
|
534
|
+
case cmd
|
535
|
+
when "hello"
|
536
|
+
a = args.split(" ")
|
537
|
+
@res << "200 Hello and welcome #{a[0]}@#{a[1]} running #{a[2]} #{a[3]}."
|
538
|
+
when "query"
|
539
|
+
discid = args.split(" ")[0]
|
540
|
+
match = []
|
541
|
+
####################
|
542
|
+
#WINDOWS DB FORMAT
|
543
|
+
####################
|
544
|
+
if @win
|
545
|
+
good_line = "#FILENAME=#{discid}\n"
|
546
|
+
find_files_win(discid).each do |f, categ|
|
547
|
+
content = []
|
548
|
+
catch(:finish) do
|
549
|
+
started = false
|
550
|
+
File.foreach(f) do |line|
|
551
|
+
if line == good_line
|
552
|
+
started = true
|
553
|
+
elsif started
|
554
|
+
if line =~ /^#FILENAME=/
|
555
|
+
match << categ + " " + discid + " " + disc_name(content)
|
556
|
+
@temp_results[categ] = content
|
557
|
+
throw(:finish)
|
558
|
+
end
|
559
|
+
content << line
|
560
|
+
end
|
561
|
+
end
|
562
|
+
end
|
563
|
+
end
|
564
|
+
####################
|
565
|
+
#CLASSIC DB FORMAT
|
566
|
+
####################
|
567
|
+
else
|
568
|
+
@categs.each do |dir, categ|
|
569
|
+
filename = File.join(dir, discid)
|
570
|
+
#if file exists, we've got a match
|
571
|
+
if File.file?(filename)
|
572
|
+
@temp_results[categ] = File.readlines(filename)
|
573
|
+
match << categ + " " + discid + " " + disc_name(@temp_results[categ])
|
574
|
+
end
|
575
|
+
end
|
576
|
+
end
|
577
|
+
if match.size == 1
|
578
|
+
@res << "200 #{match[0]}"
|
579
|
+
elsif match.size > 1
|
580
|
+
@res << "211 Multiple match found"
|
581
|
+
match.each { |m|
|
582
|
+
@res << m
|
583
|
+
}
|
584
|
+
@res << "."
|
585
|
+
else
|
586
|
+
@res << "202 No match found"
|
587
|
+
end
|
588
|
+
when "read"
|
589
|
+
categ, discid = args.split(" ")
|
590
|
+
@res << "210 #{categ} #{discid}"
|
591
|
+
if @win
|
592
|
+
lines = @temp_results[categ]
|
593
|
+
else
|
594
|
+
filename = File.join(@basedir, categ, discid)
|
595
|
+
lines = File.readlines(filename)
|
596
|
+
end
|
597
|
+
@res.concat(lines.collect { |l| l.chop })
|
598
|
+
@res << "."
|
599
|
+
when "proto"
|
600
|
+
@res << "200 CDDB protocol level: current #{PROTO_LEVEL}, supported #{PROTO_LEVEL}"
|
601
|
+
else
|
602
|
+
@res << "501"
|
603
|
+
#$stderr.puts "#{self.class} unsupported command #{cmd}"
|
604
|
+
end
|
605
|
+
end
|
606
|
+
|
607
|
+
def gets
|
608
|
+
@res.shift + "\n"
|
609
|
+
end
|
610
|
+
|
611
|
+
def close; end
|
612
|
+
|
613
|
+
private
|
614
|
+
|
615
|
+
def disc_name(content)
|
616
|
+
disc_name = nil
|
617
|
+
content.each { |line|
|
618
|
+
if md = /DTITLE=(.+)/.match(line)
|
619
|
+
disc_name = $1
|
620
|
+
end
|
621
|
+
}
|
622
|
+
disc_name
|
623
|
+
end
|
624
|
+
|
625
|
+
def find_files_win(discid)
|
626
|
+
ret = []
|
627
|
+
head = discid[0, 2]
|
628
|
+
@categs.each do |dir, categ|
|
629
|
+
Dir.foreach(dir) do |filename|
|
630
|
+
if filename =~ /^([0-9a-fA-f]{2})to([0-9a-fA-F]{2})$/ and head >= $1 and head <= $2
|
631
|
+
ret << [File.join(dir, filename), categ]
|
632
|
+
end
|
633
|
+
end
|
634
|
+
end
|
635
|
+
ret
|
636
|
+
end
|
637
|
+
end
|
638
|
+
end
|
metadata
ADDED
@@ -0,0 +1,50 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: ruby-freedb
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
version: '0.6'
|
5
|
+
platform: ruby
|
6
|
+
authors:
|
7
|
+
- Guillaume Pierronnet
|
8
|
+
- Patric Mueller
|
9
|
+
autorequire:
|
10
|
+
bindir: bin
|
11
|
+
cert_chain: []
|
12
|
+
date: 2014-07-10 00:00:00.000000000 Z
|
13
|
+
dependencies: []
|
14
|
+
description: A Ruby library which provides dumping CD info, fetching from and submitting
|
15
|
+
data to cddb/freedb servers.
|
16
|
+
email: bhaak@gmx.net
|
17
|
+
executables: []
|
18
|
+
extensions:
|
19
|
+
- ext/freedb_cdrom/extconf.rb
|
20
|
+
extra_rdoc_files: []
|
21
|
+
files:
|
22
|
+
- ext/freedb_cdrom/extconf.rb
|
23
|
+
- ext/freedb_cdrom/freedb_cdrom.c
|
24
|
+
- lib/freedb.rb
|
25
|
+
homepage: http://ruby-freedb.rubyforge.org/
|
26
|
+
licenses:
|
27
|
+
- Artistic
|
28
|
+
- GPL-2
|
29
|
+
metadata: {}
|
30
|
+
post_install_message:
|
31
|
+
rdoc_options: []
|
32
|
+
require_paths:
|
33
|
+
- lib
|
34
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
35
|
+
requirements:
|
36
|
+
- - ">="
|
37
|
+
- !ruby/object:Gem::Version
|
38
|
+
version: '0'
|
39
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
40
|
+
requirements:
|
41
|
+
- - ">="
|
42
|
+
- !ruby/object:Gem::Version
|
43
|
+
version: '0'
|
44
|
+
requirements: []
|
45
|
+
rubyforge_project:
|
46
|
+
rubygems_version: 2.2.2
|
47
|
+
signing_key:
|
48
|
+
specification_version: 4
|
49
|
+
summary: A Ruby library for accessing cddb/freedb servers.
|
50
|
+
test_files: []
|