1 ############################################################################
2 # soepkiptng (c) copyright 2000 Eric Lammerts <eric@lammerts.org>.
3 ############################################################################
4 # This program is free software; you can redistribute it and/or modify
5 # it under the terms of the GNU General Public License, version 2, as
6 # published by the Free Software Foundation.
8 # This program is distributed in the hope that it will be useful,
9 # but WITHOUT ANY WARRANTY; without even the implied warranty of
10 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11 # GNU General Public License for more details.
13 # A copy of the GNU General Public License is available on the World Wide Web
14 # at `http://www.gnu.org/copyleft/gpl.html'. You can also obtain it by
15 # writing to the Free Software Foundation, Inc., 59 Temple Place - Suite 330,
16 # Boston, MA 02111-1307, USA.
17 ############################################################################
21 use POSIX qw(locale_h);
23 setlocale(LC_CTYPE, "en_US.ISO-8859-1");
25 my @globalconfigfiles = ($ENV{SOEPKIPTNG_CONFIGFILE},
26 "$ENV{HOME}/.soepkiptng.conf", "/etc/soepkiptng.conf");
28 our @playmodes = ("all", "requested", "recent", "never played");
30 sub read_configfile1($$);
32 sub read_configfile1($$) {
36 open F, $cf or return undef;
41 if(/^include\s+(.*\S)/) {
42 read_configfile1($conf, $1);
45 if(/^(\w[-.\w]*)\s*=\s*(.*?)\s*$/) {
48 } elsif(/^\s+(.*?)\s*$/) {
50 $conf->{$f} .= "\n$1";
52 die "$cf line $.: invalid format\n";
59 sub read_configfile($;@) {
60 my ($conf, @extraconfigfiles) = @_;
63 foreach $cf ((@extraconfigfiles, @globalconfigfiles)) {
64 defined($cf) and read_configfile1($conf, $cf) and return;
66 die sprintf "no configuration file found (tried %s)\n",
67 join(" ", @extraconfigfiles, @globalconfigfiles);
70 sub connect_to_db($) {
72 my $dbh = DBI->connect("DBI:$conf->{db_type}:$conf->{db_name}:$conf->{db_host}", $conf->{db_user}, $conf->{db_pass})
73 or die "can't connect to database";
74 $dbh->{mysql_auto_reconnect} = 1;
79 128 => '_', 129 => '_', 130 => '_', 131 => '_',
80 132 => '_', 133 => '_', 134 => '_', 135 => '_',
81 136 => '_', 137 => '_', 138 => '_', 139 => '_',
82 140 => '_', 141 => '_', 142 => '_', 143 => '_',
83 144 => '_', 145 => '_', 146 => '_', 147 => '_',
84 148 => '_', 149 => '_', 150 => '_', 151 => '_',
85 152 => '_', 153 => '_', 154 => '_', 155 => '_',
86 156 => '_', 157 => '_', 158 => '_', 159 => '_',
87 160 => '_', # no-break space
88 161 => '_', # ¡ inverted exclamation mark
89 162 => 'c', # ¢ cent sign
90 163 => 'L', # £ pound sign
91 164 => 'EUR', # ¤ euro sign
92 165 => 'Y', # ¥ yen sign
93 166 => 'S', # ¦ latin capital letter s with caron
94 167 => '_', # § section sign
95 168 => 's', # ¨ latin small letter s with caron
96 169 => 'C', # © copyright sign
97 170 => 'a', # ª feminine ordinal indicator
98 171 => '_', # « left-pointing double angle quotation mark
99 172 => '_', # ¬ not sign
100 173 => '_', # soft hyphen
101 174 => 'R', # ® registered sign
102 175 => '_', # ¯ macron
103 176 => 'o', # ° degree sign
104 177 => '_', # ± plus-minus sign
105 178 => '2', # ² superscript two
106 179 => '3', # ³ superscript three
107 180 => 'Z', # ´ latin capital letter z with caron
108 181 => 'u', # µ micro sign
109 182 => '_', # ¶ pilcrow sign
110 183 => '_', # · middle dot
111 184 => 'z', # ¸ latin small letter z with caron
112 185 => '1', # ¹ superscript one
113 186 => 'o', # º masculine ordinal indicator
114 187 => '_', # » right-pointing double angle quotation mark
115 188 => 'OE', # ¼ latin capital ligature oe
116 189 => 'oe', # ½ latin small ligature oe
117 190 => 'IJ', # ¾ latin capital letter y with diaeresis
118 191 => '_', # ¿ inverted question mark
119 192 => 'A', # capital A, grave accent
120 193 => 'A', # capital A, acute accent
121 194 => 'A', # capital A, circumflex accent
122 195 => 'A', # capital A, tilde
123 196 => 'A', # capital A, dieresis or umlaut mark
124 197 => 'A', # capital A, ring
125 198 => 'AE', # capital AE diphthong (ligature)
126 199 => 'C', # capital C, cedilla
127 200 => 'E', # capital E, grave accent
128 201 => 'E', # capital E, acute accent
129 202 => 'E', # capital E, circumflex accent
130 203 => 'E', # capital E, dieresis or umlaut mark
131 204 => 'I', # capital I, grave accent
132 205 => 'I', # capital I, acute accent
133 206 => 'I', # capital I, circumflex accent
134 207 => 'I', # capital I, dieresis or umlaut mark
135 208 => '_', # Ð latin capital letter eth
136 209 => 'N', # capital N, tilde
137 210 => 'O', # capital O, grave accent
138 211 => 'O', # capital O, acute accent
139 212 => 'O', # capital O, circumflex accent
140 213 => 'O', # capital O, tilde
141 214 => 'O', # capital O, dieresis or umlaut mark
142 215 => 'x', # ×, multiplication sign
143 216 => 'O', # capital O, slash
144 217 => 'U', # capital U, grave accent
145 218 => 'U', # capital U, acute accent
146 219 => 'U', # capital U, circumflex accent
147 220 => 'U', # capital U, dieresis or umlaut mark
148 221 => 'Y', # capital Y, acute accent
149 222 => '_', # Þ latin capital letter thorn
150 223 => 'ss', # small sharp s, German (sz ligature)
151 224 => 'a', # small a, grave accent
152 225 => 'a', # small a, acute accent
153 226 => 'a', # small a, circumflex accent
154 227 => 'a', # small a, tilde
155 228 => 'a', # small a, dieresis or umlaut mark
156 229 => 'a', # small a, ring
157 230 => 'ae', # small ae diphthong (ligature)
158 231 => 'c', # small c, cedilla
159 232 => 'e', # small e, grave accent
160 233 => 'e', # small e, acute accent
161 234 => 'e', # small e, circumflex accent
162 235 => 'e', # small e, dieresis or umlaut mark
163 236 => 'i', # small i, grave accent
164 237 => 'i', # small i, acute accent
165 238 => 'i', # small i, circumflex accent
166 239 => 'i', # small i, dieresis or umlaut mark
167 240 => '_', # ð latin small letter eth
168 241 => 'n', # small n, tilde
169 242 => 'o', # small o, grave accent
170 243 => 'o', # small o, acute accent
171 244 => 'o', # small o, circumflex accent
172 245 => 'o', # small o, tilde
173 246 => 'o', # small o, dieresis or umlaut mark
174 247 => '_', # ÷ division sign
175 248 => 'o', # small o, slash
176 249 => 'u', # small u, grave accent
177 250 => 'u', # small u, acute accent
178 251 => 'u', # small u, circumflex accent
179 252 => 'u', # small u, dieresis or umlaut mark
180 253 => 'y', # small y, acute accent
181 254 => '_', # þ latin small letter thorn
182 255 => 'ij', # small y, dieresis or umlaut mark
185 sub string_to_filename($;$) {
188 my $ampr = $lang eq "dut"? "en" : $lang eq "fre"? "et" : $lang eq "ger"? "und" : "and";
191 $a =~ s/[\[(](.*)/-$1/;
192 $a =~ s/[ _]?&[_ ]?/_${ampr}_/g;
193 $a =~ s/([\x80-\xff])/lc($latin9_to_ascii{ord($1)}) || $1/ge;
194 $a =~ s/[^-A-Za-z0-9]+/_/g;
203 $a =~ s|([^-./\w])|sprintf "%%%02x", ord($1)|ge;
209 my ($a, $do_nbsp) = @_;
215 $a =~ s| | |g if $do_nbsp;
220 my ($db, $tbl, @ids) = @_;
222 $db->do("DELETE FROM $tbl WHERE song_id=" . join(" OR song_id=", @ids));
225 sub add_song_nolock($$$$@) {
226 my ($db, $tbl, $order, $user, @ids) = @_;
229 my $firstid = shift @ids;
230 my $q = "INSERT INTO $tbl (song_order, song_id, user) " .
231 "VALUES ($order, $firstid, \"$user\")";
234 $q .= ",($order,$_,\"$user\")";
236 $db->do($q) or return undef;
240 sub get_table_ids($$$) {
241 my ($db, $tbl, $rest) = @_;
243 my $res = $db->selectcol_arrayref("SELECT song_id FROM $tbl $rest");
249 my ($db, $tbl, $user, @ids) = @_;
252 $db->do("LOCK TABLES $tbl WRITE");
253 del_song($db, $tbl, @ids);
254 my ($order) = $db->selectrow_array("SELECT MAX(song_order) FROM $tbl");
255 $order = 0 if $order < 0;
256 my $retval = add_song_nolock($db, $tbl, $order + 1, $user, @ids);
257 $db->do("UNLOCK TABLES");
261 sub reorder_table($$$@) {
262 my ($db, $tbl, $order, @ids) = @_;
265 $db->do("UPDATE $tbl SET song_order=$order WHERE song_id=$_");
270 sub move_song_to_top($$@) {
271 my ($db, $tbl, @ids) = @_;
274 $db->do("LOCK TABLES $tbl WRITE");
275 reorder_table($db, $tbl, $#ids + 2, get_table_ids($db, $tbl, "ORDER BY song_order"));
276 reorder_table($db, $tbl, 1, @ids);
277 $db->do("UNLOCK TABLES");
280 sub move_song_to_bottom($$@) {
281 my ($db, $tbl, @ids) = @_;
284 foreach(@ids) { $ids{$_} = 1; };
286 $db->do("LOCK TABLES $tbl WRITE");
287 my @q = get_table_ids($db, $tbl, "ORDER BY song_order");
290 if($ids{$_}) { push @q2, $_; } else { push @q1, $_; }
292 reorder_table($db, $tbl, 1, @q1, @q2);
293 $db->do("UNLOCK TABLES");
296 sub shuffle_table($$) {
300 $db->do("LOCK TABLES $tbl WRITE");
301 reorder_table($db, $tbl, 1, get_table_ids($db, $tbl, "ORDER BY rand()"));
302 $db->do("UNLOCK TABLES");
306 my ($user, $id) = @_;
307 my ($plid, $host, $port);
310 open F, $conf{statusfile}
311 or die "$conf{statusfile}: $!\n";
318 if(defined($id) && $id != $plid) { return undef; }
320 if($conf{kill_song_external}) {
321 system $conf{kill_song_external}, $user || '';
323 open F, $conf{statusfile}
324 or die "$conf{statusfile}: $!\n";
330 return kill_song_internal($host, $port);
333 sub kill_song_internal($$) {
334 my ($host, $port) = @_;
337 $host && $port or return undef;
339 socket(F, PF_INET, SOCK_STREAM, getprotobyname('tcp'))
340 or die "socket: $!\n";
341 connect(F, sockaddr_in($port, inet_aton($host)))
342 or die "connect $host:$port: $!\n";
354 open F, $conf{statusfile}
355 or die "$conf{statusfile}: $!\n";
360 my $sock = IO::Socket::INET->new("$host:2222") or return undef;
362 $response = <$sock>; #greeting
364 $sock->print("$_\n");
365 $res = 0 if <$sock> !~ /^\+/;
372 sub get_player_pid() {
376 open F, $conf{statusfile}
377 or die "$conf{statusfile}: $!\n";
385 my ($db, $table, $value) = @_;
387 my $sth = $db->prepare("SELECT id,name FROM $table WHERE binary name=?");
388 if($sth->execute($value || "") >= 1) {
389 my ($id, $v) = $sth->fetchrow_array;
392 $db->do("REPLACE INTO $table SET name=?", undef, $value)
394 return $db->{'mysql_insertid'};
398 sub get_playlist_contents($$) {
399 my ($dbh, $list) = @_;
401 my (%artistids, %albumsids, %listids);
402 my (%artistids_done, %albumsids_done, %listids_done);
404 my $sth_list = $dbh->prepare("SELECT type,entity_id" .
405 " FROM list_contents WHERE list_id=?");
406 my $sth_artist = $dbh->prepare("SELECT id FROM song WHERE artist_id=?");
407 my $sth_album = $dbh->prepare("SELECT id FROM song WHERE album_id=?");
410 while(%artistids || %albumsids || %listids) {
411 foreach(keys %listids) {
412 $listids_done{$_} = 1;
415 $sth_list->execute($_);
416 while($_ = $sth_list->fetchrow_hashref) {
417 if($_->{'type'} eq 'list') {
418 next if $listids_done{$_->{'entity_id'}};
419 $listids{$_->{'entity_id'}} = 1;
420 } elsif($_->{'type'} eq 'artist') {
421 next if $artistids_done{$_->{'entity_id'}};
422 $artistids{$_->{'entity_id'}} = 1;
423 } elsif($_->{'type'} eq 'album') {
424 next if $albumids_done{$_->{'entity_id'}};
425 $albumids{$_->{'entity_id'}} = 1;
426 } elsif($_->{'type'} eq 'song') {
427 $songids{$_->{'entity_id'}} = 1;
431 foreach(keys %artistids) {
432 $artistids_done{$_} = 1;
433 delete $artistids{$_};
435 $sth_artist->execute($_);
436 while($_ = $sth_artist->fetchrow_hashref) {
437 $songids{$_->{'id'}} = 1;
440 foreach(keys %albumids) {
441 $albumids_done{$_} = 1;
442 delete $albumids{$_};
444 $sth_album->execute($_);
445 while($_ = $sth_album->fetchrow_hashref) {
446 $songids{$_->{'id'}} = 1;
450 return keys %songids;
453 sub get_nowplaying(;$) {
458 open F, $conf{statusfile} or return undef;
459 chop (($s->{id}, $s->{filename}, $s->{pid}, $s->{cdrplaypid},
460 $s->{killhost}, $s->{killport}, $s->{type},
461 $s->{user}, $s->{artist}, $s->{title}, $s->{album},
462 $s->{track}, $s->{length}, $s->{encoding},
463 $s->{artisturl}, $s->{albumurl}, $s->{titleurl}) = <F>);
466 if($dbh && $s->{id}) {
467 my $sth = $dbh->prepare(
468 "SELECT artist.name as artist,album.name as album," .
469 " song.artist_id as arid,song.album_id as alid, song.*" .
470 " FROM song,artist,album WHERE song.artist_id=artist.id" .
471 " AND song.album_id=album.id AND song.id=?");
472 $sth->execute($s->{id});
473 if(my $d = $sth->fetchrow_hashref) {
474 $s->{alid} = $d->{alid};
475 $s->{arid} = $d->{arid};
476 $s->{filename} = $d->{filename};
477 $s->{artist} = $d->{artist};
478 $s->{title} = $d->{title};
479 $s->{album} = $d->{album};
480 $s->{track} = $d->{track};
481 $s->{length} = $d->{length};
482 $s->{encoding} = $d->{encoding};
488 sub construct_url($@) {
489 my ($baseurl, $argsref) = @_;
491 foreach(keys %$argsref) {
492 $baseurl .= "$sep$_=" . encurl($$argsref{$_});
501 $s =~ s/\.mp3\s*$//i;
502 $s =~ s/%([0-9a-f][0-9a-f])/chr(hex($1))/eig;
503 $s =~ s/([a-zA-Z])([A-Z][a-z])/$1 $2/g;
506 if($s !~ / /) { $s =~ s/\./ /g; }
507 $s =~ s/^\s+|\s+$//g;
508 $s =~ s/([^'\w\xc0-\xff]|^)(\w)/$1\U$2/g;
509 $s =~ s/\b(o'\w)/\U$1/ig;
512 $s =~ s/\bI M\b/I'm/g;
513 $s =~ s/ ll\b/'ll/ig;
514 $s =~ s/\b(i+)\b/\U$1/ig;
515 $s =~ s/([eiuy]) ([rv]e)\b/$1'\L$2/ig;
516 $s =~ s/\s*-\s*/ - /g;
520 sub add_song_next($$$;$) {
521 my ($dbh, $table, $currentsong, $user) = @_;
523 my $ids = $dbh->selectcol_arrayref("SELECT song_id FROM $table,song ".
524 "WHERE $table.song_id=song.id AND song.present");
526 foreach(@$ids) { $idsq{$_} = 1; }
528 my $sth = $dbh->prepare("SELECT song2.* FROM song AS song1,".
529 " song AS song2 WHERE song1.id=$currentsong AND".
530 " song1.artist_id = song2.artist_id AND".
531 " song1.album_id = song2.album_id AND".
532 " song2.track > song1.track AND".
533 " song2.present ORDER BY song2.track");
535 while($_ = $sth->fetchrow_hashref) {
536 if(!$idsq{$_->{id}}) {
537 add_song($dbh, $table, $user, $_->{id})
538 or warn "can't add song.\n";
546 'abk' => 'Abkhazian',
550 'afa' => 'Afro-Asiatic',
552 'afr' => 'Afrikaans',
557 'alg' => 'Algonquian Languages',
559 'ang' => 'English, Old',
560 'apa' => 'Apache Languages',
564 'arn' => 'Araucanian',
566 'art' => 'Artificial',
569 'ath' => 'Athapascan Languages',
574 'aze' => 'Azerbaijani',
576 'bai' => 'Bamileke Languages',
585 'bel' => 'Byelorussian',
601 'bul' => 'Bulgarian',
604 'cai' => 'Central American Indian',
607 'cau' => 'Caucasian',
617 'chn' => 'Chinook jargon',
620 'chu' => 'Church Slavic',
626 'cpe' => 'Creoles and Pidgins, English-based',
627 'cpf' => 'Creoles and Pidgins, French-based',
628 'cpp' => 'Creoles and Pidgins, Portuguese-based',
630 'crp' => 'Creoles and Pidgins',
641 'dra' => 'Dravidian',
643 'dum' => 'Dutch, Middle',
648 'egy' => 'Egyptian (Ancient)',
650 'ell' => 'Greek, Modern',
653 'enm' => 'English, Middle',
654 'epo' => 'Esperanto',
667 'fiu' => 'Finno-Ugrian',
671 'frm' => 'French, Middle',
672 'fro' => 'French, Old',
676 'gae' => 'Gaelic (Scots)',
679 'gdh' => 'Gaelic (Scots)',
684 'gil' => 'Gilbertese',
686 'gmh' => 'German, Middle High',
687 'goh' => 'German, Old High',
691 'grc' => 'Greek, Ancient',
692 'gre' => 'Greek, Modern',
700 'hil' => 'Hiligaynon',
701 'him' => 'Himachali',
703 'hmo' => 'Hiri Motu',
704 'hun' => 'Hungarian',
709 'ice' => 'Icelandic',
711 'iku' => 'Inuktitut',
713 'ina' => 'Interlingua',
715 'ind' => 'Indonesian',
716 'ine' => 'Indo-European',
717 'ine' => 'Interlingue',
721 'iro' => 'Iroquoian uages',
722 'isl' => 'Icelandic',
727 'jpr' => 'Judeo-Persian',
728 'jrb' => 'Judeo-Arabic',
729 'kaa' => 'Kara-Kalpak',
732 'kal' => 'Greenlandic',
744 'kho' => 'Khotanese',
746 'kin' => 'Kinyarwanda',
768 'lit' => 'Lithuanian',
771 'ltz' => 'Letzeburgesch',
772 'lub' => 'Luba-Katanga',
776 'luo' => 'Luo (Kenya and Tanzania)',
777 'mac' => 'Macedonian',
782 'mak' => 'Macedonian',
784 'mal' => 'Malayalam',
787 'map' => 'Austronesian',
793 'mga' => 'Irish, Middle',
795 'min' => 'Minangkabau',
796 'mis' => 'Miscellaneous',
801 'mno' => 'Manobo Languages',
803 'mol' => 'Moldavian',
804 'mon' => 'Mongolian',
808 'mul' => 'Multiple Languages',
809 'mun' => 'Munda Languages',
813 'myn' => 'Mayan Languages',
815 'nai' => 'North American Indian',
818 'nbl' => 'Ndebele, South',
819 'nde' => 'Ndebele, North',
823 'nic' => 'Niger-Kordofanian',
826 'nno' => 'Norwegian (Nynorsk)',
827 'non' => 'Norse, Old',
828 'nor' => 'Norwegian',
829 'nso' => 'Sotho, Northern',
830 'nub' => 'Nubian Languages',
836 'oci' => 'Langue d\'Oc',
842 'ota' => 'Turkish, Ottoman',
843 'oto' => 'Otomian Languages',
844 'paa' => 'Papuan-Australian',
845 'pag' => 'Pangasinan',
849 'pap' => 'Papiamento',
851 'peo' => 'Persian, Old',
853 'phn' => 'Phoenician',
857 'por' => 'Portuguese',
858 'pra' => 'Prakrit uages',
859 'pro' => 'Provencal, Old',
862 'raj' => 'Rajasthani',
863 'rar' => 'Rarotongan',
865 'roh' => 'Rhaeto-Romance',
874 'sai' => 'South American Indian',
875 'sal' => 'Salishan Languages',
876 'sam' => 'Samaritan Aramaic',
879 'scr' => 'Serbo-Croatian',
882 'sga' => 'Irish, Old',
885 'sin' => 'Singhalese',
886 'sio' => 'Siouan Languages',
887 'sit' => 'Sino-Tibetan',
891 'slv' => 'Slovenian',
892 'smi' => 'Sami Languages',
899 'sot' => 'Sotho, Southern',
902 'srd' => 'Sardinian',
904 'ssa' => 'Nilo-Saharan',
930 'tog' => 'Tonga (Nyasa)',
931 'ton' => 'Tonga (Tonga Islands)',
933 'tsi' => 'Tsimshian',
944 'ukr' => 'Ukrainian',
946 'und' => 'Undetermined',
951 'vie' => 'Vietnamese',
954 'wak' => 'Wakashan Languages',
959 'wen' => 'Sorbian Languages',