also check gaps at end
[soepkiptng.git] / soepkiptngd_mobile
blob859e478f66a8e02af4c00b890434149ce74c2db9
1 #!/usr/bin/perl
2 ############################################################################
3 # soepkiptngd_mobile (Soepkip The Next Generation daemon, Mobile version)
5 # (c) copyright 2001 Eric Lammerts <eric@lammerts.org>
7 ############################################################################
8 # This program is free software; you can redistribute it and/or modify
9 # it under the terms of the GNU General Public License, version 2, as
10 # published by the Free Software Foundation.
12 # This program is distributed in the hope that it will be useful,
13 # but WITHOUT ANY WARRANTY; without even the implied warranty of
14 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
15 # GNU General Public License for more details.
17 # A copy of the GNU General Public License is available on the World Wide Web
18 # at `http://www.gnu.org/copyleft/gpl.html'. You can also obtain it by
19 # writing to the Free Software Foundation, Inc., 59 Temple Place - Suite 330,
20 # Boston, MA 02111-1307, USA.
21 ############################################################################
23 # fork, and start soepkiptngd
24 # fork, and start funkey handler
25 # wait until soepkip is playing stuff, or timeout (timeout is needed when no preload is available)
26 # exec /sbin/init
28 use integer;
29 use Audio::Mixer;
30 use Cwd 'abs_path';
31 use DBI;
32 use Getopt::Std;
33 use Socket;
35 # find program directory
36 $_ = $0;
37 while(-l) {
38 my $l = readlink or die "readlink $_: $!\n";
39 if($l =~ m|^/|) { $_ = $l; } else { s|[^/]*$|/$l|; }
41 m|(.*)/|;
42 my $progdir = abs_path($1);
44 require "$progdir/soepkiptng.lib";
46 getopts('c:');
48 read_configfile(\%conf, $opt_c);
50 $mixerchannel = 'pcm';
52 sub numtext($)
54 return join(" ", split(//, $_[0]));
57 sub open_db() {
58 # (re)open database connection if necessary
59 if(!$dbh) {
60 $dbh = DBI->connect("DBI:mysql:$conf{db_name}:$conf{db_host}",
61 $conf{db_user}, $conf{db_pass}) or do {
63 warn "Can't connect to database $conf{db_name}" .
64 "\@$conf{db_host} as user $conf{db_user}\n";
65 return undef;
68 return 1;
71 sub playlist_clear() {
72 open_db() or return;
73 $dbh->do("DELETE FROM queue");
74 warn "playlist cleared.\n";
77 sub open_db_getnow() {
78 open_db() or return;
79 my $now = get_nowplaying($dbh) or do {
80 warn "no now!\n";
81 return;
83 warn "now->{id}=$now->{id}\n";
84 return $now;
87 sub playlist_addall_artist() {
88 my $now = open_db_getnow() or return;
90 my $songsref = $dbh->selectcol_arrayref(
91 "SELECT id FROM song WHERE present AND filename LIKE '/%' AND ".
92 "artist_id=$now->{arid} AND id != $now->{id} AND ".
93 "unix_timestamp(now()) - unix_timestamp(last_played) > ".
94 "$conf{min_random_time} ORDER BY RAND()");
95 warn "adding songs: " . join(",", @{$songsref}) . ".\n";
96 return if @{$songsref} == 0;
97 add_song($dbh, "queue", '', @{$songsref});
100 sub playlist_addall_album($) {
101 my ($alltracks) = @_;
103 my $now = open_db_getnow() or return;
104 $now->{track} = -1 if $alltracks;
106 my $songsref = $dbh->selectcol_arrayref(
107 "SELECT song.id FROM song,album WHERE present AND filename LIKE '/%' AND ".
108 "song.album_id=album.id AND album.name <> '' AND ".
109 "album_id=$now->{alid} AND song.id != $now->{id} AND ".
110 "track > $now->{track} AND ".
111 "unix_timestamp(now()) - unix_timestamp(last_played) > ".
112 "$conf{min_random_time} ORDER BY track");
113 warn "adding songs: " . join(",", @{$songsref}) . ".\n";
114 return if @{$songsref} == 0;
115 add_song($dbh, "queue", '', @{$songsref});
118 sub shuffle() {
119 open_db() or return;
120 shuffle_table($dbh, "queue");
121 warn "shuffle\n";
124 # return higest of (left, right) volume
125 sub getvol() {
126 my ($l, $r) = Audio::Mixer::get_cval($mixerchannel);
127 return $l > $r? $l : $r;
130 sub setvol($;$) {
131 my ($incr, $abs) = @_;
133 if($muted) {
134 if($incr > 0) {
135 $muted = 0;
136 } elsif($incr < 0) {
137 $volume += $incr;
138 return;
140 } else {
141 $volume = getvol() + $incr;
142 warn "volume=$volume\n";
145 if($incr == 0) { $muted = !$muted; }
146 if($abs) { $volume = $incr; $muted = 0; }
148 if($volume > 100) { $volume = 100; }
149 if($volume < 0) { $volume = 0; }
151 warn "setting volume, volume=$volume, muted=$muted\n";
152 say("volume " . numtext($volume)) if $debug;
153 Audio::Mixer::set_cval($mixerchannel, ($muted? 0 : $volume));
156 sub killsong() {
157 my $iaddr = inet_aton($killhost) or return;
158 $paddr = sockaddr_in($killport, $iaddr);
160 say("kill song $keypadvalue") if $debug;
161 if($keypadvalue && open_db()) {
162 $dbh->do("LOCK TABLES queue WRITE, song READ");
163 my $res = $dbh->selectall_arrayref(
164 "SELECT song_id,song.track FROM queue " .
165 "LEFT JOIN song ON queue.song_id=song.id " .
166 "ORDER BY song_order");
167 my @goners;
168 foreach(@$res) {
169 last if $_->[1] == $keypadvalue;
170 push @goners, $_->[0];
172 warn sprintf "deleting songs up to track %d: %s\n", $keypadvalue, join(",", @goners);
173 del_song($dbh, "queue", @goners);
174 $dbh->do("UNLOCK TABLES");
175 digit_reset();
178 socket SOCK, PF_INET, SOCK_STREAM, getprotobyname('tcp')
179 or return;
180 connect SOCK, $paddr
181 or return;
182 warn "killed song.\n";
183 close SOCK;
186 sub killdelete() {
187 local *ST;
188 my $filename;
190 open ST, $conf{statusfile} or return;
191 chop((undef, $filename) = <ST>);
192 close ST;
194 unlink $filename and killsong();
197 sub digit($) {
198 $keypadvalue *= 10;
199 $keypadvalue += $_[0];
200 push @keypadvalues, $_[0];
201 warn "keypadvalue=$keypadvalue\n";
204 sub digit_reset() {
205 $keypadvalue = 0;
206 @keypadvalues = ();
207 warn "digit_reset\n";
210 sub playlist() {
211 my $reboots = 0;
212 my $killdelete = 0;
213 foreach(@keypadvalues) {
214 say("dot command $_") if $debug;
215 if($_ == 0) {
217 elsif($_ == 1) {
218 shuffle();
220 elsif($_ == 2) {
221 playlist_addall_album(1);
223 elsif($_ == 3) {
224 playlist_addall_album(0);
226 elsif($_ == 4) {
227 playlist_addall_artist();
229 elsif($_ == 5) {
230 $debug++;
231 if($debug == 3) { $debug = 0; }
232 say("deebug $debug");
234 elsif($_ == 6) {
235 $killdelete++;
237 elsif($_ == 7) {
238 $reboots++;
240 elsif($_ == 8) {
241 sayit();
243 elsif($_ == 9) {
244 playlist_clear();
247 digit_reset();
248 if($reboots >= 3) {
249 say("reboot") if $debug;
250 warn "sync\n";
251 system "sync";
252 sleep 2;
253 warn "reboot -nf\n";
254 system "reboot -nf";
256 if($killdelete >= 3) {
257 say("kill and delete") if $debug;
258 killdelete();
262 sub prepare_word($) {
263 my ($t) = @_;
265 my $wavfile = "/tmp/soepkiptngd_mobile_say_$t.wav";
266 warn "say wavfile=$wavfile\n";
268 my $tmpfile = "/tmp/soepkiptngd_mobile_say.wav";
269 unlink $tmpfile;
270 symlink $wavfile, $tmpfile;
272 if(! -f $wavfile) {
273 local *F;
274 open F, "| text2wave -scale 5 -F 44100 -o $tmpfile";
275 print F "$t\n";
276 close F;
278 warn "wavfile size=" . (-s $wavfile) . " bytes.\n";
279 return $wavfile;
282 sub say($;$) {
283 my ($t, $dontsplit) = @_;
284 my @words;
285 if($dontsplit) {
286 @words = ($t);
287 } else {
288 @words = split /\s+/, $t;
291 # pause _before_ doing text2speech (to speed it up)
292 warn "kill -USR2 $cdrplaypid\n";
293 kill 'USR2', $cdrplaypid; # pause: make it close /dev/dsp
295 my @wavs;
296 foreach(@words) {
297 push @wavs, prepare_word($_);
299 open PL, "|cdrplay -w";
300 foreach(@wavs) {
301 my $pid;
302 if(($pid = fork) == 0) {
303 open STDOUT, ">&PL" or die;
304 exec "wav2raw", $_;
306 waitpid $pid, 0;
308 close PL;
310 warn "kill -HUP $cdrplaypid\n";
311 kill 'HUP', $cdrplaypid; # resume
312 unlink $tmpfile;
315 sub sayit() {
316 my $now = get_nowplaying($dbh) or do {
317 warn "no now!\n";
318 return;
321 my $t = "$now->{artist} ... $now->{title}";
322 $t =~ s|/| |g;
323 say($t, 1);
326 sub request() {
327 open_db() or return;
329 say("request song " . numtext($keypadvalue))
330 if $debug;
331 add_song($dbh, "queue", '', $keypadvalue);
332 warn "adding song: $keypadvalue.\n";
333 digit_reset();
336 sub request_album() {
337 my $alid = $keypadvalue;
338 open_db() or return;
340 my $songsref = $dbh->selectcol_arrayref(
341 "SELECT song.id FROM song,album WHERE present AND filename LIKE '/%' AND ".
342 "song.album_id=album.id AND album.id=$alid ".
343 "ORDER BY track");
344 digit_reset();
345 warn "adding songs: " . join(",", @{$songsref}) . ".\n";
346 return if @{$songsref} == 0;
347 say("request album " . numtext($alid) .
348 " found " . numtext(scalar @{$songsref}))
349 if $debug;
350 add_song($dbh, "queue", '', @{$songsref});
351 warn "adding song: @{$songsref}.\n";
354 sub request_artist() {
355 my $arid = $keypadvalue;
356 open_db() or return;
358 my $songsref = $dbh->selectcol_arrayref(
359 "SELECT song.id FROM song,artist WHERE present AND filename LIKE '/%' AND ".
360 "song.artist_id=artist.id AND artist.id=$arid ".
361 "ORDER BY track");
362 digit_reset();
363 warn "adding songs: " . join(",", @{$songsref}) . ".\n";
364 say("request artist " . numtext($arid) .
365 " found " . numtext(scalar @{$songsref}))
366 if $debug;
367 return if @{$songsref} == 0;
368 add_song($dbh, "queue", '', @{$songsref});
369 warn "adding song: @{$songsref}.\n";
372 sub funkeyd() {
373 warn "funkeyd cdrplaypid=$cdrplaypid k=$killhost:$killport\n";
374 local *F;
376 my $keynum = 89;
377 print STDERR "settings keycodes:";
378 my $loadkeys_input;
379 foreach(keys %conf) {
380 next unless /^funkey_(\d+)$/;
381 $keynum < 128 or die "too many funkeys defined!\n";
382 $loadkeys_input .= sprintf " keycode %d = U+%04x\n", $1, 0xfe00 + $keynum;
383 print STDERR " $1=>$keynum";
384 $scancode{$keynum} = $1;
385 $funkey_handle{$keynum} = $conf{$_};
386 $keynum++;
388 warn "---\n$loadkeys_input---\n";
389 warn "loadkeys\n";
390 if(open(F, "|-") == 0) {
391 $ENV{'PATH'} = $mypath;
392 setpgrp;
393 exec 'loadkeys', '-';
394 die "loadkeys: $!\n";
396 print F $loadkeys_input;
397 warn "\nloadkeys wait\n";
398 close F;
400 warn "funkey fork\n";
401 if(fork == 0) {
402 setpgrp;
403 $ENV{'PATH'} = $mypath;
404 open LOGERR, ">>/tmp/soepkiptngd_mobile.out";
405 $SIG{__WARN__} = sub {
406 my $msg = $_[0];
407 $msg =~ s/\n/\r\n/g;
408 print STDERR $msg;
409 printf LOGERR "%s %s",
410 scalar localtime, $msg;
413 Audio::Mixer::init_mixer();
414 open F, "/dev/funkey" or die "/dev/funkey: $!\n";
415 for(;;) {
416 sysread F, $key, 1;
417 $key = unpack "C", $key;
419 # skip autorepeated keys
420 next if $key == $prevkey;
421 $prevkey = $key;
423 # don't act on releasing a key
424 next if $key & 0x80;
426 if(time - $lastkeytime > 10) {
427 # timeout
428 warn "timeout -> digit_reset()\n";
429 digit_reset();
431 $lastkeytime = time;
433 warn "scancode $scancode{$key} keynum $key action $funkey_handle{$key}!\n";
434 eval $funkey_handle{$key};
439 sub wait_for_soepkiptng() {
440 local *ST;
442 $starttime = time;
443 while((time - $starttime) < 15) {
444 if(open ST, $conf{statusfile}) {
445 chop((undef, undef, undef, $cdrplaypid, $killhost, $killport) = <ST>);
446 close ST;
447 last if $cdrplaypid && $killhost && $killport;
449 sleep 1;
454 ### MAIN ###
456 $SIG{__WARN__} = sub {
457 my $msg = $_[0];
458 $msg =~ s/\n/\r\n/g;
459 print STDERR $msg;
462 $mypath = "/bin:/usr/bin:/usr/local/bin:/sbin:/usr/sbin:/usr/local/sbin:$progdir:$progdir/bin";
463 warn "path=$mypath\n";
465 warn "volume\n";
466 setvol($conf{volume_init}, 1) if $conf{volume_init};
468 if($$ == 1) {
469 warn "fork\n";
470 if(fork == 0) {
471 setpgrp;
472 $ENV{'PATH'} = $mypath;
473 open LOGERR, ">>/tmp/soepkiptngd_mobile.out";
474 $SIG{__WARN__} = sub {
475 print STDERR $_[0];
476 printf LOGERR "%s %s",
477 scalar localtime, $_[0];
480 warn "hdparm -u1 -m16 -X66 -d1 /dev/hda\n";
481 system "hdparm -u1 -m16 -X66 -d1 /dev/hda";
482 warn "ifconfig lo\n";
483 system "ifconfig lo 127.0.0.1";
484 warn "hostname -F /etc/hostname\n";
485 system "hostname -F /etc/hostname";
487 warn "unlink $conf{statusfile}\n";
488 unlink $conf{statusfile};
490 warn "starting soepkiptngd\n";
491 exec 'sched', 'soepkiptngd', '-d';
492 warn "sched: $!\n";
493 exec 'soepkiptngd', '-d';
494 warn "soepkiptngd: $!\n";
495 exec "$progdir/soepkiptngd", '-d';
496 die "$progdir/soepkiptngd: $!\n";
499 warn "wait_for_soepkiptng\n";
500 wait_for_soepkiptng;
501 } else {
502 local *ST;
503 if(open ST, $conf{statusfile}) {
504 chop((undef, undef, undef, $cdrplaypid, $killhost, $killport) = <ST>);
505 close ST;
508 warn "killhost=$killhost:$killport\n";
510 warn "funkeyd\n";
511 funkeyd;
513 # for testing
514 exit if $$ != 1;
516 sleep 1;
518 exec "/sbin/init";
519 warn "/sbin/init: $!\n";
521 exec "/bin/sh";
522 warn "/bin/sh: $!\n";
524 # nothing else we can do
525 for(;;) { }