#!/usr/bin/perl # # music.pl, by Anthony DiSante # www.nodivisions.com/tech/systems/musicbox/ # # 20040322: # # Lots of small adjustments. # # Made it so that when kismet isn't running, and when it's running # but we're not displaying new nets as they're found, we don't # constantly grep the kismet log. This was hogging lots of CPU. # # Moved the temperature display and the disk_io display into a new # "extra stats" category, because they too use enough CPU that when # we don't want to see them, they should be disabled. # # $pausepoint is now reset when skipping tracks, and when we get # to the end of the current playlist and stop. # # 20031020: # # fixed a bug in the display of kismet log data (I hadn't realized # that ssids could have spaces in them). # # rewrote shell-prompt code. previously, any key that had been # assigned to some function in the script (about half of the # capital letters and may other keys) would execute that function # even while in shell mode, instead of printing that char to # the shell. this has been fixed; when in shell mode, we don't # even give the keys a chance to get through to the other logic. # # also made the typed-text wrap around to the next line at the # prompt, if it's longer than $NUMLCDCOLS. # # added a key (currently '&') to display the full kismet log, # useful in case you think the (highly buggy) driver has taken # a crap. print " --------------------------------------------- musicbox mobile MP3 player http://nodivisions.com/tech/systems/musicbox/ --------------------------------------------- "; use strict; use Term::ReadKey; use Time::HiRes qw(usleep gettimeofday); use POSIX; #use warnings; my %action; # temp: oss stuff: my $use_oss = 0; my $oss_timeout; my $oss_timer = time(); for(@ARGV) { if(/--use-oss/) { $use_oss = 1; } } if($use_oss) { $oss_timeout = (60*60*3) - 120; # 2 minutes less than 3 hours. $oss_timeout = $ARGV[0] if $ARGV[0] =~ /^\d+$/; system("/tmp/oss/soundoff"); system("/tmp/oss/soundon"); } ##### ##### ##### User-definable variables: ##### my $rootpath = '/mp3s/music'; # Where your mp3s are at. my $listpath = '/tmp'; # A directory that's writeable by you. my $ramdisk = $listpath . '/ramdisk0'; my $lcdwriter = $ramdisk . '/lcd'; my $player = $ramdisk . '/mpg321'; # Your mp3 player, program name only. my $play = $ramdisk . '/mpg321'; # Your mp3 player, with path and args. my $mixer = 'rexima'; # For volume control. my $getvol = "$mixer -v|grep -P \"^vol\""; my $input_timeout = 3; # So the user can input more than one char at a time. my $sleeptimeout = 10; # Stop hogging the CPU after this many seconds. my $sleeppause = 40000;# useconds. my $NUMLCDROWS = 4; # Number of rows on your lcd. my $NUMLCDCOLS = 40; # Number of columns on your lcd. my $scrollpagesize = 4; # How many bands/folders we should skip on PageUp/Down. my $statrefreshrate = 1; # How often (seconds) to update the stats on LCD, when we're idling. my $num_random_songs = 99; # How many songs to pick & play when set to random. my $report_temperature = 1; # set to zero unless you have the lmsensors package installed and # working (run "sensors" and see if you get any output) my $report_disk_io = 0; # Prints a R, W, or B for reading, writing, or both. my $kismet_log = $ramdisk . '/antlog-kismet*'; # This is a list of folders that you wish to be excluded from # selection when you press the "play random folder" key. my @folders_to_exclude = ("$rootpath/Nonsuch", "$rootpath/#nonmusic", "$rootpath/#other", "$rootpath/#downloaded/#new_and_unknown", "$rootpath/#downloaded/#only_a_few_songs"); ##### ##### ##### User-definable key mappings: edit the values on the right to change the key mappings. ##### Everything is lowercase, except where you want to use a capital letter for an action ##### (ex., to use shift-c as the key for clear_playlist, you'd type a capital C as the value). ##### Note that some keys are unavailable for use (Caps Lock, Shift (by itself), Ctrl, Alt, ##### WinKey, Insert, Delete, Home, End, Print Screen, Scroll Lock, Pause, NumLock). And the ##### script is designed to let you type a band's name to go directly to that band's folder, ##### so it doesn't really make sense to assign any of these functions to lowercase letter ##### values. ##### $action{'display_help'} = 'F1'; $action{'stop_music'} = 'F2'; $action{'play_playlist'} = 'F3'; $action{'pause_music_normal'} = 'F4'; $action{'kismet_start'} = 'F5'; $action{'kismet_stop'} = 'F6'; $action{'show_kismet_log_unique'} = 'F7'; $action{'show_kismet_log'} = 'F8'; $action{'start_wireless_card'} = 'F9'; $action{'stop_wlan0'} = 'F10'; $action{'try_dhcp_and_website'} = 'F11'; $action{'reset_usb'} = 'F12'; $action{'reset_sound'} = '"'; $action{'play_song'} = '$'; $action{'add_song_to_playlist'} = '%'; $action{'add_folder_to_playlist'} = '^'; $action{'rw'} = '&'; $action{'ff'} = '*'; $action{'skip_forward_in_playlist'} = 'RightArrow'; $action{'skip_backwards_in_playlist'} = 'LeftArrow'; $action{'pause_lowquality_file'} = '{'; $action{'ff_lowqual'} = 'K'; $action{'rw_lowqual'} = 'L'; $action{'go_up_one_level'} = 'Backspace'; $action{'enter_or_play_folder'} = 'Enter'; $action{'scroll_up'} = 'UpArrow'; $action{'scroll_down'} = 'DownArrow'; $action{'scroll_up_4'} = 'PageUp'; $action{'scroll_down_4'} = 'PageDown'; $action{'scroll_to_top'} = 'T'; $action{'scroll_to_bottom'} = 'B'; $action{'cycle_display'} = 'Tab'; $action{'clear_playlist'} = 'C'; $action{'remove_song_from_playlist'} = 'R'; $action{'move_song_up_in_playlist'} = 'U'; $action{'move_song_down_in_playlist'} = 'D'; $action{'switch_editormode'} = 'S'; $action{'toggle_stats'} = 'Z'; $action{'toggle_extra_stats'} = 'E'; $action{'toggle_exclude_unknown'} = 'N'; $action{'show_current_song_filename'} = 'I'; $action{'volume_down'} = '('; $action{'volume_up'} = ')'; $action{'mute'} = 'M'; $action{'reboot'} = 'Q'; $action{'halt'} = '!'; $action{'run_command'} = '?'; $action{'play_random_folder'} = '`'; $action{'play_random_songs'} = '|'; $action{'count_files'} = 'P'; $action{'free_ram'} = 'F'; $action{'update_music_library'} = 'U'; $action{'update_randomselection_albumlist'} = 'A'; $action{'enable_readonly_mode_for_disk'}= '<'; $action{'disable_readonly_mode_for_disk'}= '>'; $action{'toggle_ramdisk_usage'} = 'O'; $action{'exit'} = 'X'; ##### ##### ##### End of user-definable section. ##### ##### ##### ##### Non-user-definable variables: ##### my %options; # Anything passed on the command line becomes an item in %options: for(@ARGV) { $options{$_} = 1; } my $showstats = 1 unless($options{'--nostats'}); my $print_to_crt = 1 if($options{'--crt'}); my $play_on_startup = 1 unless($options{'--dontplay'}); my $use_ramdisk = 1 unless($options{'--noramdisk'}); my $update_albumlist = 1 if($options{'--update-albumlist'} || $options{'--u'}); my $playlist = $listpath . '/playlist.m3u'; my $currentsongfile = $listpath . '/currentsong.txt'; my $currentvolumefile = $listpath . '/currentvolume.txt'; my $pausefile = $listpath . '/pausefile.txt'; my $readonly_mode_file = $listpath . '/readonly_mode_file.txt'; my $albumlist_file = $listpath . '/albumlist.txt'; if(!(-e $playlist)) { `touch $playlist`; } if(!(-e $currentsongfile)) { `touch $currentsongfile`; } if(!(-e $currentvolumefile)) { `touch $currentvolumefile`; } if(!(-e $pausefile)) { `touch $pausefile`; } if(!(-e $readonly_mode_file)) { `touch $readonly_mode_file`; } if(!(-d $ramdisk)) { `mkdir $ramdisk`; } my (%displayoffset, %playlist, %rootoffset, %top, @help, $command_to_run, @command_output, @command_history, %random_folders_already_chosen, @all_folders, @mp3_folders, $player_instances, @player_instances, $seconds, $useconds, $utime, $last_utime, %conn_checker_born_at, %lcd_line_2, %lcd_line_3, %lcd_line_4); my $path = $rootpath; my $now = 0; my $numsongs = 0; my $currentsong = 1; my $origppid = $$; my $parent = $$; my $currentsongpid = 0; my $song_is_playing = 0; my $song_is_paused = 0; my $keep_playing = 0; my $first_loop = 1; my $userTimeLast = 0; my $niceTimeLast = 0; my $systemTimeLast = 0; my $lastNow = 0; my $idleTimeLast = 0; my $cpuidle = 0; my $cpuidle2 = 0; my $cpu_utilization_1 = 0; my $cpu_utilization_2 = 0; my $cpu_utilization_avg = 0; my $elapsedtime = 0; my $song_start_time = 0; my $lcd_in_use = 0; my $status = ''; my $mode = 'library'; my $editor_mode = 1; my $previous_volume = 0; my $just_starting_up = 1; my $last_random_folder = 0; my $exclude_unknown = 1; my $time_of_day = get_time_of_day(); my $readonly_mode = 0; my $num_read_io_ops = 0; my $num_write_io_ops = 0; my $volume_before_fading= 0; my $num_networks = `cat $kismet_log|grep "new network"|wc -l`; my $last_num_networks = $num_networks; my $exit_timer = 0; my $do_not_disturb_display = 0; my $living_conn_checkers=0; my $online_status = ' '; my $playlist_file_needs_syncing = 0; my $currentsong_file_needs_syncing = 0; my $pausepoint = 0; my $get_pausetime_from_file = 1; my $kismet_running = 0; my $shutting_down = 0; my %monthnum; $monthnum{'Jan'} = '01'; $monthnum{'Feb'} = '02'; $monthnum{'Mar'} = '03'; $monthnum{'Apr'} = '04'; $monthnum{'May'} = '05'; $monthnum{'Jun'} = '06'; $monthnum{'Jul'} = '07'; $monthnum{'Aug'} = '08'; $monthnum{'Sep'} = '09'; $monthnum{'Oct'} = '10'; $monthnum{'Nov'} = '11'; $monthnum{'Dec'} = '12'; $rootoffset{'library'} = 0; $rootoffset{'playlist'} = 0; $rootoffset{'help'} = 0; $displayoffset{'library'} = 0; $displayoffset{'playlist'} = 0; $displayoffset{'help'} = 0; my ($k, $z, $key, $thekey, $keyval, @dirlist, $pid, $start, $end, @mp3s, $count, @toLCD, $_2, $ignorekey, $last_key, @dirlistMaster, $elapsedkeytime); my @temp = `df --block-size=1 $ramdisk`; my @temp2 = split /\s+/, $temp[1]; my $ramdisk_size = $temp2[3]; if($update_albumlist || !(-e $albumlist_file)) { update_albumlist(); } sub update_albumlist() { print "Updating random-selection albumlist...\n"; `$lcdwriter clear rowcol 0 0 "Updating random-selection albumlist..."`; load_albumlist_for_random_selection(); purge_nonmp3_and_excluded_folders_from_albumlist(); remount_partition_rw(); print "update_albumlist() writing to disk ($albumlist_file)\n"; open(OUT,">$albumlist_file") or die "$0: couldn't open $albumlist_file for writing: $!\n"; flock OUT, 2; seek OUT, 0, 0; print OUT join "\n", @mp3_folders; print OUT "\n\n"; close OUT or die "$0: couldn't close $albumlist_file: $!\n"; remount_partition_rw(); print "Albumlist updated; restarting..."; prepare_for_shutdown('restart'); `$lcdwriter noclear rowcol 2 10 "Albumlist updated; restarting..."`; sleep 1; exec($0); } sub new_network_notification() { return if( ($mode =~ /shell|library|playlist/) || $do_not_disturb_display || $editor_mode); my @temp = `cat $kismet_log|grep "new network"`; my ($date,$ssid,$mac,$wep,$rate,$channel) = parse_kismet_logline($temp[$#temp]); my $numnets = count_nets_found_today(); my $opennets = count_open_nets_found_today(); @command_output = (); $command_output[0] = "# Found net: $ssid"; $command_output[1] = "# Timestamp: $date WEP:$wep"; $command_output[2] = "# MAC: $mac Ch:$channel $rate "; $command_output[3] = "# Today: $numnets nets / $opennets open."; display_something(); $showstats = 1 unless $options{'--nostats'}; } sub count_nets_found_today() { my ($date,$mac,%nets_today,@temp); my $today = strftime("%Y%m%d",localtime(time)); my @nets = `cat $kismet_log|grep "new network"`; for(@nets) { @temp = parse_kismet_logline($_); $date = $temp[0]; $mac = $temp[2]; if($date =~ /^$today/) { $nets_today{$mac} = 1; } } return scalar keys %nets_today; } sub count_open_nets_found_today() { my ($date,$mac,%open_nets_today,@temp); my $today = strftime("%Y%m%d",localtime(time)); my @open_nets = `cat $kismet_log|grep "new network"|grep "WEP N Ch"`; for(@open_nets) { @temp = parse_kismet_logline($_); $date = $temp[0]; $mac = $temp[2]; if($date =~ /^$today/) { $open_nets_today{$mac} = 1; } } return scalar keys %open_nets_today; } sub parse_kismet_logline($) { $_ = shift; my ($month, $day, $hour, $min, $year, $ssid, $idstring, $mac, $wep, $channel, $rate); ($month,$day,$hour,$min,$year,$ssid,$idstring,$mac,$wep,$channel,$rate) = (/^\S+\s+(\S+)\s+(\d+)\s+(\d+):(\d+):\d+\s+(\d+)\s+Found new network\s+"(.+?)"\s+(\S+)\s+(\S+)\s+WEP\s+(\S+)\s+Ch\s+(\d+)\s+\@\s+(\S+)\s+mbit/); $day = "0$day" if $day < 10; $mac =~ s/://g; $rate =~ s/\.0+$//; $wep = lc($wep); return ("$year$monthnum{$month}$day-$hour$min", $ssid, $mac, $wep, "${rate}mb/s", $channel); } sub start_kismet() { `/kismet-start.sh 1>/dev/null 2>/dev/null &`; $kismet_running = 1; #my @temp = `cat /tmp/.kismet/status-log*`; #my ($i,@lastlines); #for(@temp) #{ # push (@lastlines, $_) if (($#temp-$i)<=10); # $i++; #} # #for(@lastlines) #{ ##s/^\s+//; ##s/\s+$//; ##while(length($_) > $NUMLCDCOLS) ##{ ## $_ =~ s/^(.{$NUMLCDCOLS})//; ## push @command_output, $1; ##} ##push @command_output, $_; # Since we're running the kismet-start script in the # background, execution jumps down to here immediately # and kismet hasn't had a chance to write to the log # yet. So our @lastlines are the lastlines from one # the previous executions, and we get false repots that # it failed to start because of that. This could # probably be worked-around by sleeping for 2 or 3 # seconds after starting kismet, but that's kludgey, # and plus that adds 2 or 3 seconds to our startup # delay. # #if(/(FATAL|[Nn]o such device)/) #{ # @toLCD = (); # $toLCD[1] = "Kismet failed to start."; # printLCD(@toLCD); #} #} ##display_something(); } sub stop_kismet() { `/kismet-stop.sh &`; $kismet_running = 0; } sub start_wireless_nic { my $ssid = shift; print "starting wireless card...\n"; print "trying to connect to $ssid..." if $ssid =~ /\w/; `/wlan-start.sh "$ssid"`; my @temp = `cat /tmp/.kismet/antlog-wlan-ng*`; my ($i,@lastlines); for(@temp) { push (@lastlines, $_) if (($#temp-$i)<=7); $i++; } for(@lastlines) { s/^\s+//; s/\s+$//; while(length($_) > $NUMLCDCOLS) { $_ =~ s/^(.{$NUMLCDCOLS})//; push @command_output, $1; } push @command_output, $_; } $do_not_disturb_display = 1; display_something(); } sub try_dhcp_server_and_website() { print "trying dhcp and website...\n"; `/try-dhcp.sh`; my @temp = `cat /tmp/.kismet/antlog-dhcp*`; my ($i,@lastlines); for(@temp) { push (@lastlines, $_) if (($#temp-$i)<=8); $i++; } for(@lastlines) { s/^\s+//; s/\s+$//; while(length($_) > $NUMLCDCOLS) { $_ =~ s/^(.{$NUMLCDCOLS})//; push @command_output, $1; } push @command_output, $_; } $do_not_disturb_display = 1; display_something(); } sub try_to_logon_to_network($) { my $ssid = shift; start_wireless_nic($ssid); try_dhcp_server_and_website(); } sub get_readonly_mode_from_file() { print "diskread: get_readonly_mode_from_file() reading $readonly_mode_file\n"; open(IN,"<$readonly_mode_file") or die "$0: couldn't open $readonly_mode_file for reading: $!\n"; flock IN, 1; seek IN, 0, 0; $readonly_mode = ; chomp $readonly_mode; close IN or die "$0: couldn't close $readonly_mode_file: $!\n"; } sub write_readonly_mode_to_file() { print "write_readonly_mode_to_file() writing to disk ($readonly_mode_file)\n"; open(OUT,">$readonly_mode_file") or die "$0: couldn't open $readonly_mode_file for writing: $!\n"; flock OUT, 2; seek OUT, 0, 0; print OUT $readonly_mode; close OUT or die "$0: couldn't close $readonly_mode_file: $!\n"; } sub enable_readonly_mode_for_disk() { `mount -o remount,rw $listpath`; usleep(300000); `mount -o remount,rw $listpath`; usleep(300000); `mount -o remount,rw $listpath`; $readonly_mode = 1; write_readonly_mode_to_file(); `sync`; usleep(300000); `sync`; usleep(300000); `mount -o remount,ro $listpath`; usleep(300000); `mount -o remount,ro $listpath`; usleep(300000); `mount -o remount,ro $listpath`; } sub disable_readonly_mode_for_disk() { `mount -o remount,rw $listpath`; usleep(300000); `mount -o remount,rw $listpath`; usleep(300000); `mount -o remount,rw $listpath`; $readonly_mode = 0; write_readonly_mode_to_file(); } sub print_crt($) { my $string = shift; if($print_to_crt) { print $string; } } sub get_time_of_day() { my $tod = `date`; my ($hour, $min) = ($tod =~ /^\S+\s+\S+\s+\S+\s+(\d+):(\d+)/); if($hour > 12) { $hour -= 12; } if($hour =~ /^0+$/) { $hour = '12'; } #if($hour < 10) { $hour = chr(3) . $hour; } $hour =~ s/^0//; $time_of_day = $hour . ':' . $min; return $time_of_day; } sub printLCD { my $i = 0; my @allLines=(); for(@_) { # write to the LCD if we run out of rows: if($i > ($NUMLCDROWS-1)) { last; } my $__ = $_; chomp $__; $allLines[$i]=$__; $i++; } if($showstats) { ### ### Report used memory percentage: ### my $usedmem = get_usedmem(); if($usedmem < 10) { $usedmem = " $usedmem"; } # Num of chars will be cols minus the space for '75%' at the end. my $len = $NUMLCDCOLS - (length($usedmem) + 2); # Pad the line with enough extra spaces to account for the smallest # possible line, rather than calculating anything here: $allLines[2] .= (' ' x $NUMLCDCOLS); # Save only the first $len chars, and then put the used memory count on the end: $allLines[2] = substr($allLines[2], 0, $len) . " $usedmem" . '%'; # ### # ### Report cpu utilization: # ### # # # Num of chars will be cols minus the space for ' 83%' at the end. # $len = $NUMLCDCOLS - (length($cpu_utilization_1) + 2); # # # Pad the line with enough extra spaces to account for the smallest # # possible line, rather than calculating anything here: # $allLines[0] .= (' ' x $NUMLCDCOLS); # # # Save only the first $len chars, and then put the cpuidle percent on the end: # $allLines[0] = substr($allLines[0], 0, $len) . " $cpu_utilization_1" . '%'; # # ### # ### Report cpu utilization (2): # ### # # # Num of chars will be cols minus the space for ' 83%' at the end. # $len = $NUMLCDCOLS - (length($cpu_utilization_2) + 2); # # # Pad the line with enough extra spaces to account for the smallest # # possible line, rather than calculating anything here: # $allLines[1] .= (' ' x $NUMLCDCOLS); # # # If we're on the displaysong() screen, then there are 2 escaped # # quotes in $allLines[1] (which is the song title), and they don't # # count in the count, so we have to manually add them. # if($allLines[1] =~ /\\\"/) { $len = $len+2; } # # # Save only the first $len chars, and then put the cpuidle percent on the end: # $allLines[1] = substr($allLines[1], 0, $len) . " $cpu_utilization_2" . '%'; # ### ### Report cpu utilization (avg of the 2 methods): ### # Num of chars will be cols minus the space for ' 83%' at the end. $len = $NUMLCDCOLS - (length($cpu_utilization_avg) + 2); # Pad the line with enough extra spaces to account for the smallest # possible line, rather than calculating anything here: $allLines[1] .= (' ' x $NUMLCDCOLS); # If we're on the displaysong() screen, then there are 2 escaped # quotes in $allLines[1] (which is the song title), and they don't # count in the count, so we have to manually add them. if($allLines[1] =~ /\\\"/) { $len = $len+2; } # Save only the first $len chars, and then put the cpuidle percent on the end: $allLines[1] = substr($allLines[1], 0, $len) . " $cpu_utilization_avg" . '%'; ### ### Show the time: ### $time_of_day = get_time_of_day(); # Num of chars will be cols minus the space for '10:35' at the end. $len = $NUMLCDCOLS - (length($time_of_day) + 1); # Pad the line with enough extra spaces to account for the smallest # possible line, rather than calculating anything here: $allLines[0] .= (' ' x $NUMLCDCOLS); # Save only the first $len chars, and then put the time of day on the end: $allLines[0] = substr($allLines[0], 0, $len) . " $time_of_day"; ### ### Show RO if we're in readonly mode. ### if($readonly_mode) { if(length($allLines[0]) < ($NUMLCDCOLS - 8)) { my $spaces_to_pad_with = ($NUMLCDCOLS - 2) - length($allLines[0]); $allLines[0] = $allLines[0] . ' ' x $spaces_to_pad_with; } $allLines[0] = substr($allLines[0], 0, ($NUMLCDCOLS - 8)) . 'RO'; } ### ### Show the CPU temperature. ### my $temperature; my $cpu_temp_pos = 0; if($report_temperature) { $temperature = ' ' . get_cpu_temperature(); $cpu_temp_pos = $NUMLCDCOLS - length($temperature); if(length($allLines[3]) < ($NUMLCDCOLS - 5)) { my $spaces_to_pad_with = ($NUMLCDCOLS - 5) - length($allLines[3]); $allLines[3] = $allLines[3] . ' ' x $spaces_to_pad_with; } $allLines[3] = substr($allLines[3], 0, ($NUMLCDCOLS - 5)) . $temperature; } } # Print it: `$lcdwriter clear all4 "$allLines[0]" "$allLines[1]" "$allLines[2]" "$allLines[3]"`; if($print_to_crt) { system('clear'); my $zero = substr($allLines[0], 0, $NUMLCDCOLS); my $one = substr($allLines[1], 0, $NUMLCDCOLS); my $two = substr($allLines[2], 0, $NUMLCDCOLS); my $three = substr($allLines[3], 0, $NUMLCDCOLS); for($zero,$one,$two,$three) { s!\\"!"!g; } print $zero . "\n" . $one . "\n" . $two . "\n" . $three . "\n"; } } sub print_playlist() { $mode = 'playlist' unless $numsongs == 0; if($print_to_crt) { print "Playlist:\n"; print "diskread: print_playlist() reading $playlist\n"; open(IN,"<$playlist") or die "$0: couldn't open $playlist for reading: $!\n"; flock IN, 2; seek IN, 0, 0; while() { print $_; } close IN or die "$0: couldn't close $playlist: $!\n"; } } sub add_song_to_playlist($$) { my $song = shift; my $position = shift; $playlist{$position} = $song; #add_song_to_playlist_file($song); } sub add_song_to_playlist_file($) { return if $readonly_mode; my $song = shift; chomp $song; print "add_song_to_playlist_file() writing to disk ($playlist)\n"; open(OUT,">>$playlist") or die "$0: couldn't open $playlist: $!\n"; flock OUT, 2; print OUT "$song\n"; close OUT or die "$0: couldn't close $playlist: $!\n"; } sub remove_song_from_playlist($) { my $song_to_remove = shift; chomp $song_to_remove; my ($deleted_song_num, @new_playlist); # First, find the key of the song to delete: foreach $_ (sort keys(%playlist)) { if($playlist{$_} eq $song_to_remove) { $deleted_song_num = $_; } } # Delete the song. delete $playlist{$deleted_song_num}; # Now the keys are still in order, but are missing a number (1, 2, 3, 5, 6, 7... # if the 4th entry was deleted), so we'll put the remaining songs into an array # (in the correct playlist order) and then re-populate the hash from that to fill # the missing number: foreach $key (sort keys %playlist) { push @new_playlist, $playlist{$key}; } %playlist = (); my $i = 0; for(@new_playlist) { $i++; $playlist{$i} = $_; } # The currentsong number needs to be adjusted if the deleted song # was lower than it in the playlist: if($currentsong >= $deleted_song_num) { $currentsong-- unless $currentsong == 1; $currentsong_file_needs_syncing = 1; } # Now update the playlist file. #write_playlist_to_file(); $playlist_file_needs_syncing = 1; } sub write_playlist_to_file() { return if $readonly_mode; $playlist_file_needs_syncing = 0; print "write_playlist_to_file() writing to disk ($playlist)\n"; open(OUT,">$playlist") or die "$0: couldn't open $playlist for writing: $!\n"; flock OUT, 2; seek OUT, 0, 0; foreach $_ (sort { $a <=> $b } (keys(%playlist))) { print OUT $playlist{$_} . "\n"; } close OUT or die "$0: couldn't close $playlist: $!\n"; } sub load_playlist_from_file() { print "diskread: load_playlist_from_file() reading $playlist\n"; open(IN,"<$playlist") or die "$0: couldn't open $playlist for reading: $!\n"; flock IN, 1; seek IN, 0, 0; while() { chomp $_; $playlist{$.} = $_; $numsongs = $.; } close IN or die "$0: couldn't close $playlist: $!\n"; print "diskread: load_playlist_from_file() reading $currentsongfile\n"; open(IN,"<$currentsongfile") or die "$0: couldn't open $currentsongfile for reading: $!\n"; flock IN, 2; seek IN, 0, 0; $currentsong = ; close IN or die "$0: couldn't close $currentsongfile: $!\n"; } sub clear_playlist() { my $temp = $playlist{$currentsong}; %playlist = (); if(!$song_is_playing) { $numsongs = $currentsong = 0; } # If there's a song playing, then 'clearing the playlist' # will make the playlist contain only that song. else { $numsongs = $currentsong = 1; add_song_to_playlist($temp, $currentsong); } $playlist_file_needs_syncing = 1; $currentsong_file_needs_syncing = 1; } sub clear_playlist_file() { return if $readonly_mode; print "clear_playlist_file() writing to disk ($playlist)\n"; open(CLEAR,">$playlist") or die "$0: couldn't open $playlist for writing: $!\n"; close CLEAR or die "$0: couldn't close $playlist: $!\n"; } sub get_usedmem() { # Note: this is a percentage, not a MB amount. It's the ratio # of usedmem to totalmem. my @temp = `free`; my ($total, $used, $used_percent); ($total, $used) = ($temp[1] =~ /^Mem:\s+(\d+)\s+(\d+)/); $used_percent = ($used / $total) * 100; $used_percent = sprintf("%.0f",$used_percent); # %.0f rounds the number. if( ($used_percent > 70) && ($elapsedtime > 10) ) { print "usedmem: $used_percent\nfreeing some ram...\n"; `/freeram 100000 &`; } return $used_percent; } sub get_cpu_temperature() { my $output = `sensors -f|grep "Proc Temp:"`; my ($temp,$decimal) = ($output =~ /Proc Temp:\s*\+(\d+)\.(\d+)/); if($decimal > 4) {$temp++;} return "$temp" . chr(7); } sub get_cpu_utilization() { my @stats = `cat /proc/stat`; # $stats[0] will contain 'cpu 1050265 0 933317 3683857' # which is cpu centiseconds in: user nice system idle my (undef, undef, $userTime, $niceTime, $systemTime, $idleTime) = split(/ /, $stats[0]); # Method 1: my $user = $userTime - $userTimeLast; my $nice = $niceTime - $niceTimeLast; my $system = $systemTime - $systemTimeLast; $userTimeLast = $userTime; $niceTimeLast = $niceTime; $systemTimeLast = $systemTime; $cpu_utilization_1 = $user + $nice + $system; my $idlePercent = 101 - $cpu_utilization_1; $cpuidle = $idlePercent; # Method 2: my $idle = $idleTime - $idleTimeLast; $idleTimeLast = $idleTime; my $idlePercentTwo = $idle; $cpuidle2 = $idlePercentTwo; $cpu_utilization_2 = 101 - $cpuidle2; #if($cpuidle < 0) { $cpuidle = 0; } #if($cpuidle2 < 0) { $cpuidle2 = 0; } #if($cpuidle > 99) { $cpuidle = 99; } #if($cpuidle2 > 99) { $cpuidle2 = 99; } if($cpu_utilization_1 < 0 ) { $cpu_utilization_1 = 0; } if($cpu_utilization_2 < 0 ) { $cpu_utilization_2 = 0; } if($cpu_utilization_1 > 99 ) { $cpu_utilization_1 = 99; } if($cpu_utilization_2 > 99 ) { $cpu_utilization_2 = 99; } $cpu_utilization_avg = ($cpu_utilization_1 + $cpu_utilization_2) / 2; $cpu_utilization_avg =~ s/\..*//; } sub get_disk_io() { my $output = `cat /proc/stat|grep disk`; my ($num_read_io_ops_new, $num_write_io_ops_new) = ($output =~ /\d+,(\d+),\d+,(\d+),\d+/); #print "in_sub: new is $num_read_io_ops_new,$num_write_io_ops_new\n"; # Subtract the old value from the new value to get the delta. my $delta_read_io_ops = $num_read_io_ops_new - $num_read_io_ops; my $delta_write_io_ops = $num_write_io_ops_new - $num_write_io_ops; my $return_1 = $delta_read_io_ops; my $return_2 = $delta_write_io_ops; #print "in_sub: returns are $return_1,$return_2\n"; # Update the "old" values for next time through: $num_read_io_ops = $num_read_io_ops_new; $num_write_io_ops = $num_write_io_ops_new; # Return the deltas: read_io_ops, then write_io_ops. return ($return_1, $return_2); } sub overlay_stats() { my ($cpu_util_out_1, $cpu_util_out_2, $cpu_util_out_avg, $len); # Note: this chr(3) nonsense is a workaround for a bug in the lcd_printstring() function # in the lcdwriter program. For some reason, if you tell it to write a string to a certain # location on the LCD screen, it doesn't write the first character if that character # is a space. So I created a custom character (that's just a space anyway, grrr...) and put # it in character 3 on the LCD. #my $cpurow = 0; #$cpu_util_out_1 = chr(3) . $cpu_utilization_1 . '%'; #my $cpucol = $NUMLCDCOLS - (length($cpu_utilization_1) + 2); #my $cpurow2 = 1; #$cpu_util_out_2 = chr(3) . $cpu_utilization_2 . '%'; #my $cpucol2 = $NUMLCDCOLS - (length($cpu_utilization_2) + 2); my $cpurow_avg = 1; $cpu_util_out_avg = chr(3) . $cpu_utilization_avg . '%'; my $cpucol_avg = $NUMLCDCOLS - (length($cpu_utilization_avg) + 2); my $tod_row = 0; get_time_of_day(); my $tod_col = $NUMLCDCOLS - length($time_of_day); my $usedmem = get_usedmem(); my $memrow = 2; $usedmem = chr(3) . $usedmem . '%'; my $memcol = $NUMLCDCOLS - (length($usedmem)); #one space before + one % after =+2 chars. my $temperature; my $cpu_temp_pos = 0; if($report_temperature) { $temperature = ' ' . get_cpu_temperature(); $cpu_temp_pos = $NUMLCDCOLS - length($temperature); } # Note: with my current setup, the elapsed time seems completely accurate until you get to # the end of the song. At that point, the counter goes on for 1 second longer than the actual # length of the song. I think this is due to the 1 second timeout while we wait on the child # process in the main while() loop. my $elapsedtimeDisplay; if($song_is_playing && ($mode eq 'song')) { $elapsedtimeDisplay = (sprintf("%.1u", $elapsedtime / 60)) . ':' . sprintf("%.2u",($elapsedtime % 60)); } # Display RO if we're in readonly mode. my $readonly_display; my $readonly_display_pos = $NUMLCDCOLS - 8; if($readonly_mode) { $readonly_display = ' RO'; } # Display an R, a W, or a B to indicate whether there is disk activity (read, write, or both): my $disk_access = ' '; if($report_disk_io) { my ($read_io_ops, $write_io_ops) = get_disk_io(); if($read_io_ops && $write_io_ops) { $disk_access = 'B'; } elsif($read_io_ops) { $disk_access = 'R'; } elsif($write_io_ops) { $disk_access = 'W'; } } # on the top row, where the volume indicator sometimes is, we're going # to have single-letter status indicators. the first of these will be # a 'K' to indicate that kismet is running. my $status_indicators; my $kismet_status = ' '; my $kismets = `ps -ef|grep kismet_server|grep -v grep|wc -l`; chomp $kismets; $kismets =~ s/^\s+//; $kismets =~ s/\s+$//; if($kismets > 0) { $kismet_status = 'K'; $kismet_running = 1; } else { $kismet_running = 0; } $status_indicators = '[' . $kismet_status . $online_status . ' ' . $disk_access . ']'; my $si_posx = 28; #`$lcdwriter noclear all4rc $cpurow $cpucol $cpu_util_out_1 $cpurow2 $cpucol2 $cpu_util_out_2 $memrow $memcol $usedmem 0 18 $elapsedtimeDisplay ''`; `$lcdwriter noclear all7rc 3 $cpu_temp_pos "$temperature" $tod_row $tod_col $time_of_day $cpurow_avg $cpucol_avg $cpu_util_out_avg $memrow $memcol $usedmem 0 18 "$elapsedtimeDisplay" 0 $readonly_display_pos "$readonly_display" 0 $si_posx "$status_indicators"`; } sub displaycurrentsong() { @toLCD = (); my @temp = split(/\//, $playlist{$currentsong}); my $song = $temp[$#temp]; my $album = $temp[$#temp-1]; my $band = $temp[$#temp-2]; my $track = $temp[$#temp-3]; # This will never actually be a tracknum; # it's only used if track doesn't get filled # in with a real value below. $track =~ s/^0+//; $song =~ s/\.mp3$//; if($song =~ /^(\d{1,3})( | - )(.+)$/) { $song = $3; $track = $1; $track =~ s/^0+//; $track = "#$track on $album"; } elsif($song =~ /^(.+?) - (\d{1,3}) (- |)(.+?)$/) { $band = $1; $track = $2; $song = $4; $track =~ s/^0+//; $track = "#$track on $album"; } elsif($song =~ /^(.+?)( - |-+)(.+)$/) { $band = $1; $song = $3; } else { # If we didn't match any of those formats, then # just display the filename and the 2 folders above it: push @toLCD, "$status $currentsong/$numsongs:"; push @toLCD, "\\\"$song\\\""; push @toLCD, "$album"; push @toLCD, "$band"; unless( ($currentsong == 0) && ($numsongs == 0) ) { printLCD(@toLCD); $mode = 'song'; } return; } # If $track is 'music' or 'mp3s' then just blank it: if($rootpath =~ /$track/) { $track = ''; } push @toLCD, "$status $currentsong/$numsongs:"; push @toLCD, "\\\"$song\\\""; push @toLCD, "By $band"; push @toLCD, $track; unless( ($currentsong == 0) && ($numsongs == 0) ) { printLCD(@toLCD); $mode = 'song'; } } sub save_currentsong_to_file() { return if $readonly_mode; print "save_currentsong_to_file() writing to disk ($currentsongfile)\n"; open(OUT,">$currentsongfile") or die "$0: couldn't open $currentsongfile for writing: $!\n"; flock OUT, 2; seek OUT, 0, 0; print OUT $currentsong; close OUT or die "$0: couldn't close $currentsongfile: $!\n"; } sub save_volume_to_file() { return if $readonly_mode; my $vol = getvolume(); print "save_volume_to_file() writing to disk ($currentvolumefile)\n"; open(OUT,">$currentvolumefile") or die "$0: couldn't open $currentvolumefile for writing: $!\n"; flock OUT, 2; seek OUT, 0, 0; print OUT $vol; close OUT or die "$0: couldn't close $currentvolumefile: $!\n"; } sub set_volume_from_file() { print "diskread: set_volume_from_file() reading $currentvolumefile\n"; open(IN,"<$currentvolumefile") or die "$0: couldn't open $currentvolumefile for reading: $!\n"; flock IN, 1; seek IN, 0, 0; my $vol = ; close IN or die "$0: couldn't close $currentvolumefile: $!\n"; #print_crt "in set_volume_from_file(), setting volume to $vol\n"; setvolume($vol); return $vol; } sub displayvolume() { my $vol = getvolume(); `$lcdwriter noclear rowcol 0 25 "Vol:$vol"`; if($print_to_crt) { print "Vol:$vol\n"; } } sub getvolume() { my $vol = `$getvol`; # output looks like 'vol 76, 76' or 'vol 100,100' $vol =~ /(\d+), ?(\d+)/; $vol = $1; if($vol >= 100) { $vol = 99; } if( ($vol < 0) || ($vol !~ /\d+/) ) { $vol = 1; } return $vol; } sub setvolume($) { my $vol = shift; my $sign; if($vol =~ /^(-|\+)?(\d+)$/) { $sign = $1; $vol = $2; #print_crt "in setvolume(), setting volume to $sign$vol\n"; `$mixer vol $sign$vol pcm $sign$vol`; } } my $chr1 = chr(32); my $chr2 = chr(5); # chr(35); # Framed box. # chr(255); # Filled box. # chr(32); # Space. sub animate() { `$lcdwriter noclear all4rc 3 39 "$chr2" 2 39 "$chr2" 1 39 "$chr2" 0 39 "$chr2"`; `$lcdwriter noclear all4rc 0 39 "$chr1" 1 39 "$chr1" 2 39 "$chr1" 3 39 "$chr1"`; } sub animate2() { `$lcdwriter noclear all4rc 3 39 "$chr2" 2 39 "$chr2" 1 39 "$chr2" 0 39 "$chr2"`; `$lcdwriter noclear all4rc 0 39 "$chr1" 1 39 "$chr1" 2 39 "$chr1" 3 39 "$chr1"`; } sub display_help($) { $mode = 'help'; $showstats = 0; $displayoffset{'help'} = 0; @help = (); my $option = shift; if($option eq 'run_command') { @help = @command_output; return; } push @help, "[NoDivisions.com/tech/systems/musicbox/]"; push @help, ' '; push @help, 'Scroll down for instructions and the'; push @help, 'list of keys and their functions.'; push @help, ' '; push @help, 'To open/select an item, type or scroll'; push @help, 'until it\'s at the top of the screen.'; push @help, 'Then press Enter.'; push @help, ' '; push @help, "Open selected (topmost) folder.....$action{'enter_or_play_folder'}"; push @help, "Close folder (go back/up)......$action{'go_up_one_level'}"; push @help, ' '; push @help, "Play selected song...................$action{'play_song'}"; push @help, "Play all songs in open folder......$action{'enter_or_play_folder'}"; push @help, ' '; push @help, "Enqueue selected song................$action{'add_song_to_playlist'}"; push @help, "Enqueue all songs in open folder.....$action{'add_folder_to_playlist'}"; push @help, "Clear playlist.......................$action{'clear_playlist'}"; push @help, ' '; push @help, "Rewind 10 sec........................$action{'rw'}"; push @help, "Play.................................$action{'play_playlist'}"; push @help, "Pause...................spacebar, or $action{'pause_music_normal'}"; push @help, "Stop.................................$action{'stop_music'}"; push @help, "FastFwd 10 sec.......................$action{'ff'}"; push @help, ' '; push @help, "Pause low-quality audio..............$action{'pause_lowquality_file'}"; push @help, "Rewind 10 sec (low-quality audio)....$action{'rw_lowqual'}"; push @help, "FastFwd 10 sec (low-quality audio)...$action{'ff_lowqual'}"; push @help, ' '; push @help, "Next song.....................$action{'skip_forward_in_playlist'}"; push @help, "Previous song..................$action{'skip_backwards_in_playlist'}"; push @help, ' '; push @help, "Play random folder.................tick"; push @help, "Play random songs....................$action{'play_random_songs'}"; push @help, "Toggle the inclusion of the 'new and"; push @help, "unknown' folder on random............$action{'toggle_exclude_unknown'}"; push @help, ' '; push @help, "Scroll up........................$action{'scroll_up'}"; push @help, "Scroll down....................$action{'scroll_down'}"; push @help, "Scroll up 4 lines.................$action{'scroll_up_4'}"; push @help, "Scroll down 4 lines.............$action{'scroll_down_4'}"; push @help, "Scroll to top........................$action{'scroll_to_top'}"; push @help, "Scroll to bottom.....................$action{'scroll_to_bottom'}"; push @help, ' '; push @help, "Cycle through display modes..........$action{'cycle_display'}"; push @help, "Remove selected song from playlist...$action{'remove_song_from_playlist'}"; push @help, ' '; push @help, "Volume up............................$action{'volume_up'}"; push @help, "Volume down..........................$action{'volume_down'}"; push @help, "Mute.................................$action{'mute'}"; push @help, ' '; push @help, "Start kismet logging.................$action{'kismet_start'}"; push @help, "Stop kismet logging..................$action{'kismet_stop'}"; push @help, "Show kismet log (unique entries only)$action{'show_kismet_log_unique'}"; push @help, "Show kismet log (with debug info)....$action{'show_kismet_log'}"; push @help, ' '; push @help, "Start wireless nic...................$action{'start_wireless_card'}"; push @help, "Stop wireless nic....................$action{'stop_wlan0'}"; push @help, "Run dhcpcp and try a website.........$action{'try_dhcp_and_website'}"; push @help, "Reset USB............................$action{'reset_usb'}"; push @help, "Reset sound..........................$action{'reset_sound'}"; push @help, ' '; push @help, "Get shell prompt.....................$action{'run_command'}"; push @help, "Display this help text...............$action{'display_help'}"; push @help, "Toggle display of songtime/cpu/mem...$action{'toggle_stats'}"; push @help, "Toggle display of temperature/diskio.$action{'toggle_extra_stats'}"; push @help, "Count mp3 files......................$action{'count_files'}"; push @help, ' '; push @help, "Show filename of current song........$action{'show_current_song_filename'}"; push @help, ' '; push @help, "Toggle display mode between full and"; push @help, "condensed (for various screens, like"; push @help, "song-info and playlist editor).......$action{'switch_editormode'}"; push @help, ' '; push @help, "Put disk into readonly mode..........$action{'enable_readonly_mode_for_disk'}"; push @help, "Put disk into readwrite mode.........$action{'disable_readonly_mode_for_disk'}"; push @help, "Toggle the use of the ramdisk........$action{'toggle_ramdisk_usage'}"; push @help, "Update music library (sync with home)$action{'update_music_library'}"; push @help, ' '; push @help, "Free some ram........................$action{'free_ram'}"; push @help, "Exit the program (don't!)............$action{'exit'}"; push @help, "Reboot system........................$action{'reboot'}"; push @help, "Stop system (before turning car off).$action{'halt'}"; #push @help, "Confirm reboot/stop..................$action{'reboot_confirm'}"; push @help, ' '; } sub display_something() { $mode = 'help'; # not really help mode, but this is how we display text and keep it on the LCD. $showstats = 0; $displayoffset{'help'} = 0; @help = (); @help = @command_output; @command_output = (); browse(); } sub signal_handler { return if $shutting_down; my $sig = shift; my $more_songs_to_play = ($currentsong < $numsongs); print "I'm $$ (parent is $parent) and I just got a $sig\n"; print "currentsong is $currentsong / $numsongs\n"; if( $more_songs_to_play && $keep_playing ) { $currentsong++; #print "in signal_handler(): just caught an exit, playing next song ($currentsong)\n"; fork_player(''); } else { $song_is_playing = 0; # If we're showing the current song, and it's the last song, # then when we get here, the last song has just finished, so # display the current song to update the status to 'Stopped.' if( ($mode eq 'song') && ($currentsong == $numsongs) && !$song_is_paused ) { $status = 'Stopped.' unless $more_songs_to_play; $pausepoint = 0; displaycurrentsong(); } } } $SIG{CHLD} = 'IGNORE'; $SIG{PIPE} = \&signal_handler; $SIG{POLL} = \&online_handler; $SIG{PROF} = \&offline_handler; $SIG{USR1} = \&halt; sub fork_player($) { my $arg = shift; my $start_frame = ''; my $resume = 0; if($arg =~ /^(\d+|\d+\.\d+)$/) # ^$ disallows negative values. { $start_frame = "--skip $arg"; $resume = 1; } # Stop any currently playing songs: if(already_playing()) { stop_player(); } #die unless $$ == $parent; # Fork a process to play the current song: if($currentsongpid = fork) { # this is the parent. $song_is_playing = 1; $status = 'Playing'; displaycurrentsong() if $mode eq 'song'; $keep_playing = 1; } else { # this is the child. defined($currentsongpid) or die "fork: $!"; $SIG{PIPE} = 'IGNORE'; $SIG{POLL} = 'IGNORE'; $SIG{PROF} = 'IGNORE'; $SIG{USR1} = 'IGNORE'; setsid(); # These are necessary sometimes when you fork. open(STDIN,'/dev/null') or die "dup: $!"; open(STDERR,'>/dev/null') or die "dup: $!"; if($use_ramdisk) { my $song_is_in_ramdisk = copy_song_to_ramdisk_if_it_fits_and_DNE($playlist{$currentsong}); if($song_is_in_ramdisk =~ /[1-2]/) { my ($just_filename) = ($playlist{$currentsong} =~ /.*\/(.+)/); #print "Playing $just_filename from ramdisk.\n"; if($song_is_in_ramdisk == 2) { #print "just copied the song, so sleeping for 2 before playing....\n"; #`sleep 2; $play $start_frame "$ramdisk/$just_filename" &`; `$play $start_frame "$ramdisk/$just_filename"`; } else { `$play $start_frame "$ramdisk/$just_filename"`; } `rm -f "$ramdisk/$just_filename" &`; } else { #print "Playing $playlist{$currentsong} from hard drive.\n"; `$play $start_frame "$playlist{$currentsong}"`; } } else { `$play $start_frame "$playlist{$currentsong}"`; } `kill -s PIPE $parent`; exit; # exit the child. } # end child, resume parent: # If we're resuming from being paused, then the start time # is actually in the past, not the current time(): $song_start_time = $resume ? (time() - $elapsedtime) : time(); } sub online_handler { my $sig = shift; $living_conn_checkers--; $online_status = 'I'; } sub offline_handler { my $sig = shift; $living_conn_checkers--; $online_status = ' '; } use POSIX; use IO::Socket; sub fork_connectivity_checker() { my $time = time(); return if ( ($living_conn_checkers > 5) || ($conn_checker_born_at{$time}) ); $conn_checker_born_at{$time} = 1; my $kidpid; if($kidpid = fork) { # this is the parent. $living_conn_checkers++; } else { # this is the child. defined($kidpid) or die "$0: fork error: $!"; $SIG{PIPE} = 'IGNORE'; $SIG{POLL} = 'IGNORE'; $SIG{PROF} = 'IGNORE'; $SIG{USR1} = 'IGNORE'; setsid(); # These are necessary sometimes when you fork. open(STDIN,'/dev/null') or die "dup: $!"; open(STDERR,'>/dev/null') or die "dup: $!"; my $sock = IO::Socket::INET->new(PeerAddr => 'google.com', PeerPort => 80, Proto => 'tcp', Timeout => 10); if($sock) { $sock->autoflush(1); close($sock); `kill -s POLL $parent`; } else { `kill -s PROF $parent`; } exit; # exit the child. } } sub fork_acc_line_monitor() { my $kidpid; if($kidpid = fork) { # this is the parent. } else { # this is the child. defined($kidpid) or die "$0: fork error in fork_acc_line_monitor(): $!"; $SIG{PIPE} = 'IGNORE'; $SIG{POLL} = 'IGNORE'; $SIG{PROF} = 'IGNORE'; $SIG{USR1} = 'IGNORE'; setsid(); # These are necessary sometimes when you fork. open(STDIN,'/dev/null') or die "dup: $!"; open(STDERR,'>/dev/null') or die "dup: $!"; # dsrmon is a c program that monitors the DSR signal (pin 6) # of a serial port. It just sits there doing nothing, polling # that pin, until the pin goes low after having been high. At # that point, dsrmon exits. Control returns to this forked # process, which sends the signal below (USR1) to the parent # musicbox script. That script interprets that signal as a # sign that the car's ACC line has gone low, thus the car # has been shut off, and it halts the computer. system("/dsrmon /dev/ttyS0"); `kill -s USR1 $parent`; exit; # exit the child. } } sub already_playing() { my $is = `/sbin/pidof $player`; if($is =~ /\d/) { return 1; } else { return 0; } } sub stop_player { my $s = shift; if($s =~ /paused/i) { $status = $s; } else { $status = 'Stopped.'; } $keep_playing = 0; my $pids = `/sbin/pidof $player`; chomp $pids; if($pids) { `kill $pids`; #print "in stop_player(): just killed $pids\n"; } $pids = `/sbin/pidof $player`; chomp $pids; if($pids) { `kill $pids`; #print "in stop_player(): just killed $pids\n"; } $song_is_playing = 0; $elapsedtime = 0; } sub pause_resume($) { my $pause_mode = shift; my $fps; if($pause_mode eq 'normal') { $fps = 39.4; } elsif($pause_mode eq 'lowquality_file') { # For spoken audio like Kent Hovind's stuff: $fps = 19.2; } if($song_is_playing) { # Figure out where we're at in the song, # and set the resume variables: fade_out(); $song_is_paused = 1; $pausepoint = $elapsedtime; stop_player('Paused.'); #update_pausefile($elapsedtime); #setvolume($volume_before_fading); fade_in(); if($mode eq 'song') { displaycurrentsong(); } } else { if($get_pausetime_from_file) { $get_pausetime_from_file = 0; print "diskread: pause_resume() reading $pausefile\n"; open(IN,"<$pausefile") or die "$0: couldn't open $pausefile for reading: $!\n"; flock IN, 1; seek IN, 0, 0; $elapsedtime = ; chomp $elapsedtime; close IN or die "$0: couldn't close $pausefile: $!\n"; } else { #print "pause_resume(): setting elapsedtime to pausepoint ($pausepoint)\n"; $elapsedtime = $pausepoint; $pausepoint = 0; } setvolume('0'); fork_player($elapsedtime * $fps); if($just_starting_up) { sleep 1; } else { usleep 40000; } fade_in(); $song_is_paused = 0; } } sub fade_out() { $volume_before_fading = getvolume(); my $i; for($i = 0; $i < 30; $i++) { setvolume('-2'); usleep(500); } } sub fade_in() { setvolume('25'); my $i; for($i = 0; $i < 25; $i++) { setvolume('+2'); usleep(500); } } sub update_pausefile($) { return if $readonly_mode; my $pausetime = shift; print "update_pausefile() writing to disk ($pausefile)\n"; open(OUT,">$pausefile") or die "$0: couldn't open $pausefile: $!\n"; flock OUT, 2; seek OUT, 0, 0; print OUT $pausetime . "\n"; close OUT or die "$0: couldn't close $pausefile: $!\n"; } sub songseek($) { my $mode = shift; my $seektime; stop_player(); if($mode eq 'ff') { $elapsedtime += 10; $seektime = $elapsedtime * 39.4; } if($mode eq 'ff_lowqual') { $elapsedtime += 10; $seektime = $elapsedtime * 19.2; } if($mode eq 'rw') { $elapsedtime -= 10; $seektime = $elapsedtime * 39.4; } if($mode eq 'rw_lowqual') { $elapsedtime -= 10; $seektime = $elapsedtime * 19.2; } fork_player($seektime); } sub getakey() { ReadMode('cbreak'); while(not defined($z = ReadKey(-1))) { }; # $z = ReadKey(-1); my $zvalue = ord($z); ReadMode('normal'); return $zvalue; } sub determine_the_key($) { my $rawkey = shift; my $kval = ord($rawkey); my $thekey; # Function keys send >1 signal, like 3 or 4 or 5 for one keypress, and # they all start with 27 (ESC): # F1: 27 91 91 65 # F2: 27 91 91 66 # F3: 27 91 91 67 # F4: 27 91 91 68 # F5: 27 91 91 69 # F6: 27 91 49 55 126 # F7: 27 91 49 56 126 # F8: 27 91 49 57 126 # F9: 27 91 50 126|48 (this last one varies... I don't get it) # F10: 27 91 50 49 126 # F11: 27 91 50 51 126 # F12: 27 91 50 52 126 # # And the arrow keys are freaky too... # # Up: 27 91 65 # Down: 27 91 66 # Right: 27 91 67 # Left: 27 91 68 # PageUp: 27 91 53 126 # PageDown: 27 91 54 126 if($kval == 27) { $kval = getakey(); # Get rid of the next one, which is 91. $kval = getakey(); # Get the next one, which we need to check. if($kval == 91) # F1-F5 { $kval = getakey(); if ($kval == 65) { $thekey = 'F1'; } elsif($kval == 66) { $thekey = 'F2'; } elsif($kval == 67) { $thekey = 'F3'; } elsif($kval == 68) { $thekey = 'F4'; } elsif($kval == 69) { $thekey = 'F5'; } } elsif($kval == 49) # F6-F8 { $kval = getakey(); if ($kval == 53) { $thekey = 'F5'; } elsif($kval == 55) { $thekey = 'F6'; } elsif($kval == 56) { $thekey = 'F7'; } elsif($kval == 57) { $thekey = 'F8'; } $kval = getakey(); # Because of the extra 126 sent with these. } elsif($kval == 50) # F9-F12 { $kval = getakey(); if ($kval == 48) { $thekey = 'F9'; } elsif($kval == 49) { $thekey = 'F10'; } elsif($kval == 51) { $thekey = 'F11'; } elsif($kval == 52) { $thekey = 'F12'; } $kval = getakey(); # Because of the extra 126 sent with these. } elsif($kval == 53) { $thekey = 'PageUp'; getakey(); } elsif($kval == 54) { $thekey = 'PageDown'; getakey(); } elsif($kval == 65) { $thekey = 'UpArrow'; } elsif($kval == 66) { $thekey = 'DownArrow'; } elsif($kval == 67) { $thekey = 'RightArrow'; } elsif($kval == 68) { $thekey = 'LeftArrow'; } } elsif($kval == 127) { $thekey = 'Backspace'; } elsif($kval == 9) { $thekey = 'Tab'; } elsif($kval == 10) { $thekey = 'Enter'; } else { $thekey = $rawkey; if( ($thekey =~ /[0-9a-z_#-]/) && ($mode ne 'shell') ) { # for [a-z],etc automatically go to library mode: # But note that spacebar is excluded here, because we # use that for pause, and that's ok because there aren't # any bandnames, albumnames, or tracknames whose first # character is a space, so we don't need to automatically # switch to library mode upon receiving a space. $mode = 'library'; } } return $thekey; } sub browse { my $hide_output = shift; my @dirlist; $path =~ s/\/{2,}$/\//; # turn multiple trailing slashes into a single one. if( ($mode eq 'library') || ($hide_output eq 'hide') ) { opendir(DIR,"$path") or die "$0: (1) couldn't open directory $path: $!\n"; @dirlist = grep(!/(^\.\.?$)|musicbox-ignore/, readdir(DIR)); closedir DIR or die "$0: couldn't close directory $path: $!\n"; @dirlist = sort {lc($a) cmp lc($b)} @dirlist; } elsif($mode eq 'playlist') { foreach $_ (sort { $a <=> $b } (keys(%playlist))) { push @dirlist, $playlist{$_}; } } elsif($mode eq 'help') { @dirlist = @help; } my $i = 0; $count = 0; my $keylocal = $key; # Only print the path if we're in a subfolder and in library mode. if( ($path !~ /^$rootpath$/) && ($mode eq 'library') ) { my ($subpath) = ($path =~ /^$rootpath(.*)/); push @toLCD, $subpath; } # Prevent scrolling past the end of the list: if($displayoffset{$mode} > $#dirlist) { $displayoffset{$mode} = $#dirlist; } my @lastscreen = @toLCD; @toLCD = (); for(@dirlist) { chomp $_; # We're good if it's an mp3 or directory, but if # it's anything else, skip it: unless( ($_ =~ /\.mp3$/) || (-d "$path/$_") || ($mode eq 'help') ) { next; } # If the user typed a key or a few, then update the offset: if(($keylocal ne 'done') && ($key ne '')) { if(eval{$_ =~ /^$keylocal/i}) { $displayoffset{$mode} = $i; $keylocal = 'done'; $top{$mode} = $_; if($mode eq 'playlist') { if($editor_mode) { $_ =~ /.*\/(.*mp3$)/; push @toLCD, ($i + 1) . "/$numsongs $1"; } else { push @toLCD, substr($_, (length($rootpath)+1)); } } else { push @toLCD, $_; } $count++; } } # Else just display from the current offset: elsif( ($i >= $displayoffset{$mode}) ) { # If the user asked to scroll to the bottom of # this list, then skip ahead until we get to the # last item (the bottom) of the list. if($thekey eq $action{'scroll_to_bottom'}) { if($i < $#dirlist) { $i++; next; } else { # Once we've reached the end, update # the offset to point there. $displayoffset{$mode} = $i; } } # Save the topmost one in case the user selects it, so # we can add it to the path and move to that folder. if($count == 0) { $top{$mode} = $_; } elsif($count == 1) { $lcd_line_2{$mode} = $_; } elsif($count == 2) { $lcd_line_3{$mode} = $_; } elsif($count == 3) { $lcd_line_4{$mode} = $_; } # Print the folders that match the user's input. if($mode eq 'playlist') { if($editor_mode) { if(/.+ - (\d\d .+\.mp3)$/) { push @toLCD, ($i + 1) . "/$numsongs $1"; } elsif(/.+ - (.+\.mp3)$/) { push @toLCD, ($i + 1) . "/$numsongs $1"; } elsif(/.+\/(.+\.mp3)$/) { push @toLCD, ($i + 1) . "/$numsongs $1"; } } else { if(/.+\/(.+\.mp3)$/) { push @toLCD, ($i + 1) . "/$numsongs $1"; } } } elsif($mode eq 'library') { if($editor_mode && /\.mp3$/) { if(/.+ - (\d\d .+\.mp3)$/) { push @toLCD, $1; } elsif(/.+ - (.+\.mp3)$/) { push @toLCD, $1; } #elsif(/.+\/(.+\.mp3)$/) #{ # push @toLCD, $1; #} else { push @toLCD, $_; } } else { push @toLCD, $_; } } else { push @toLCD, $_; } $count++; } $i++; } # If they typed something that doesn't match anything in the dirlist, # then nothing will be put into @toLCD. In that case, instead of # blanking the screen, display the last screen. if($#toLCD < 0) { @toLCD = @lastscreen; } unless($hide_output eq 'hide') { printLCD(@toLCD); } } sub save_vol_and_elapsed_time() { if($song_is_playing) { update_pausefile($elapsedtime); stop_player(); } #setvolume($volume_before_fading); fade_in(); save_volume_to_file(); } sub remount_partition_rw() { print "mounting $listpath rw...\n"; `/sbin/mount -o remount,rw $listpath`; } sub remount_partition_ro() { print "mounting $listpath ro...\n"; `/sbin/mount -o remount,ro $listpath`; } sub reboot() { prepare_for_shutdown('reboot'); `$lcdwriter noclear rowcol 2 15 "Rebooting..."`; `/sbin/reboot`; exit; } sub halt() { prepare_for_shutdown('halt'); `/sbin/halt`; `$lcdwriter noclear rowcol 2 17 "Halted."`; exit; } sub prepare_for_shutdown($) { $shutting_down = 1; my $action = shift; stop_kismet() if $kismet_running; if($song_is_playing) { fade_out(); stop_player(); } print "about to clear the lcd...\n"; `$lcdwriter clear rowcol 1 10 "Preparing to $action..."`; remount_partition_rw(); save_vol_and_elapsed_time(); write_playlist_to_file(); save_currentsong_to_file(); `cp $ramdisk/*.txt $listpath`; remount_partition_ro(); # # Flush any cached writes. # `sync`; # # `/sbin/mount -o remount,ro /tmp`; } sub get_free_space_on_ramdisk() { my $df_output = `df --block-size=1 $ramdisk|grep $ramdisk`; my @temp = split(/\s+/, $df_output); return $temp[3]; } sub get_filesize($) { my $file = shift; return ( (stat($file))[7] ); } # return 0 if the song isn't in the ramdisk and couldn't be copied. # return 1 if it was already there, and return 2 if we copied it there. sub copy_song_to_ramdisk_if_it_fits_and_DNE($) { my $file = shift; my ($just_filename) = ($file =~ /.*\/(.+)/); if(!-e "$ramdisk/$just_filename") { #print "song DNE in ramdisk...\n"; my $space_on_ramdisk = get_free_space_on_ramdisk(); my $song_size = get_filesize($file); if($song_size < $space_on_ramdisk) { #print "song will fit in ramdisk...\n"; } else { #print "song is bigger than free space on ramdisk; trying to free more space...\n"; `rm $ramdisk/*.mp3`; } `cp "$file" "$ramdisk"`; #print "just attempted to copy $just_filename to $ramdisk... "; if(-e "$ramdisk/$just_filename") { #print "copy was successful.\n"; return 2; } else { #print "copy failed.\n"; return 0; } } else { #print "song already exists in ramdisk; will not copy it again.\n"; return 1; } } get_readonly_mode_from_file(); set_volume_from_file(); load_albumlist_from_file(); fork_acc_line_monitor(); #print @mp3_folders; if($showstats) { get_disk_io(); get_cpu_utilization(); } load_playlist_from_file(); if($play_on_startup && $numsongs) { # The first thing we want to do is get music playing. pause_resume('normal'); $showstats = 1 unless($options{'--nostats'}); $mode = 'song'; displaycurrentsong(); } elsif($readonly_mode) { enqueue_random_folder(); fork_player(''); } start_kismet() unless($options{'--nokismet'}); #`$lcdwriter clear all4 "Musicbox. Press F1 for help." "" " Seven seconds until keyboard is ready. " ""`; # We want to call browse() immediately after the music starts, # so that the main dirlist can get loaded into memory, because # this takes about 6 or 7 seconds. After it's done once, the # response in library mode is realtime, but if you don't do it # here, then that 6 or 7 second pause happens the first time # you try to browse the library, and that's annoying. # # And 'hide' just refers to the fact that we're calling browse, # but we don't actually want to display the output of the dir # listing (because we're showing the current song at this # point). So we detect that inside the browse() function. # # And as of Oct 2003, I decided that it probably makes sense # to fork this into a separate process. It doesn't need to # return any data to us (it just needs to read the library one # time to get the disk data it into cache), and this way the # script isn't frozen for the 8-10 seconds that this takes. my $temppid; if($temppid = fork) { # parent; } else { # child: defined($temppid) or die "$0: fork error: $!"; $SIG{PIPE} = 'IGNORE'; $SIG{POLL} = 'IGNORE'; $SIG{PROF} = 'IGNORE'; setsid(); # These are necessary sometimes when you fork. open(STDIN,'/dev/null') or die "dup: $!"; open(STDERR,'>/dev/null') or die "dup: $!"; browse('hide'); exit; } #if($numsongs > 0) #{ # $mode = 'playlist'; #} #else #{ # $mode = 'library'; #} #`$lcdwriter noclear all4 "" "" "Keyboard ready. Type, scroll, & use Tab." "Enter = select/open/play. $action{'stop_music'} = stop." ""`; #displaycurrentsong(); while(1) { dontbrowse: $last_key = $thekey; $start = time(); ($seconds, $useconds) = gettimeofday; $utime = $seconds . $useconds; ReadMode('cbreak'); # Main control loop: while( not defined ($k = ReadKey(-1)) ) { # Sometimes bad things happen, like multiple songs playing simultaneously. # So every time we go through this loop (about a million times per second), # we'll check if there's >1 instance of the player (>0 in the code, since # the array indices start at -1). If there is, we'll kill them all, and # re-start the current song. $player_instances = `pidof $player`; @player_instances = split /\s+/, $player_instances; if($#player_instances > 0) { $keep_playing = 0; `killall $player`; if( ($currentsong <= $numsongs) && ($currentsong != 0) ) { fork_player(''); } } elsif($#player_instances == 0) { $song_is_playing = 1; } # No key yet. $now = time(); $elapsedkeytime = $now - $start; # Sleep (to keep the CPU utilization down) unless we're in # library, playlist, or shell mode, OR if we're in one of those # modes but there hasn't been a keypress in a while. if( !($mode =~ /library|playlist|shell/) || ($elapsedkeytime > $sleeptimeout) ) { if( $use_ramdisk && $song_is_playing && ($currentsong < $numsongs) ) { copy_song_to_ramdisk_if_it_fits_and_DNE($playlist{$currentsong + 1}); } usleep($sleeppause); } if($elapsedkeytime >= $input_timeout) { $key = ''; } if( ($elapsedkeytime < ($sleeptimeout - 2)) && !$song_is_playing ) { if( ($thekey eq $action{'skip_forward_in_playlist'}) || ($thekey eq $action{'skip_backwards_in_playlist'}) || ($thekey eq $action{'play_random_folder'}) ) { if($elapsedkeytime > 4) { fork_player(''); } if( ($elapsedkeytime > 1) && $use_ramdisk ) { if($elapsedtime > 5) # give the current playing song a few seconds to get playing stably { copy_song_to_ramdisk_if_it_fits_and_DNE($playlist{$currentsong}); } } } } # Once we've been playing this song for a while, check if # the playlist has changed and needs to be written to disk. if($playlist_file_needs_syncing && ($elapsedtime > 20)) { #print "elapsedtime > 20 so writing playlist and currentsong files to disk\n"; remount_partition_rw(); write_playlist_to_file(); save_currentsong_to_file(); remount_partition_ro(); } $elapsedtime = time() - $song_start_time - 1; # -1 is a tempfix because copying to ramdisk is taking too long and messing this up. $elapsedtime = 0 if $elapsedtime < 0; if(($showstats) && ($now != $lastNow)) { # This must be called exactly once per second, # or the value won't be accurate: get_cpu_utilization(); # That's why this next line is here... then # $now != $lastNow is only true once/sec: $lastNow = $now; if(($now % $statrefreshrate) == 0) { overlay_stats(); } } if(!$editor_mode && $kismet_running) { $num_networks = `cat $kismet_log|grep "new network"|wc -l`; new_network_notification() if ($num_networks > $last_num_networks); $last_num_networks = $num_networks; } if( ($elapsedkeytime == 0) || (($elapsedkeytime % 5) == 0) ) { fork_connectivity_checker(); } #if($use_oss) #{ # # the demo of the oss driver only runs for 3 hours, after which you must restart it. # if((time() - $oss_timer) > $oss_timeout) # { # print "oss_timeout=$oss_timeout; restarting oss driver at " . time() . "\n"; # pause_resume('normal'); # system("/tmp/oss/soundoff;/tmp/oss/soundon"); # pause_resume('normal'); # $oss_timer = time(); # } #} } ReadMode('normal'); $end = time(); ## If the user holds a key down, it overflows the keyboard buffer faster than we can pull ## stuff out of it, and things get all screwed up. This if() which checks the utime vars ## will cause us to only process ~2 keys per second (depending on CPU speed and the constant ## in the if() statement). #if(($utime - $last_utime) < 40000) #{ # # If not enough time has passed since the last keypress, then we'll skip # # this one. But we still need to run the determine_the_key function on # # it first, to remove any junk left on the buffer (from those keys which # # have longer-than-one escape sequences, like arrow keys, etc.). # $thekey = determine_the_key($k); # next; #} $last_utime = $utime; # The rest of the main loop is one big if/elsif chain to respond to # keypresses. But before that chain begins, we have a few single # if()s for things that we need to check every time through, in # addition to / before doing the actual processing in the if/elsif # chain. # On startup, we always disable showstats so that we can # display the splash screen. Once a key is pressed, the # splash screen is exited, so check and reset it here. if( $just_starting_up && !($options{'--nostats'}) ) { $showstats = 1; $just_starting_up = 0; } #print "mode is $mode\n"; if($ARGV[0] eq 'debug') { # Debug mode to find key values... run the script with # an argument of 'debug' to enable: print 'keyval is ' . ord($k) . " ($k)\n"; $exit_timer++; die if $exit_timer > 20; goto dontbrowse unless ($k eq $action{'exit'}); } $thekey = determine_the_key($k); # now '$k' should NOT be tested! use $thekey! if($ARGV[0] eq 'debug2') { print "thekey is $thekey\n"; usleep(100000); $exit_timer++; die if $exit_timer > 20; goto dontbrowse unless ($thekey eq $action{'exit'}); } if($thekey =~ /^$action{'scroll_down'}|$action{'scroll_up'}|$action{'scroll_down_4'}|$action{'scroll_up_4'}$/) { # If mode is 'song' then scrolling has no meaning, # so set the mode to 'library' in that case: if($mode eq 'song') { $mode = 'library'; } # We DO want to clear the typedchar-string once the user presses # a scroll key: $key = ''; } #if($thekey ne $action{'play_random_folder'}) #{ # %random_folders_already_chosen = (); #} if($mode eq 'shell') { if($thekey eq $action{'scroll_down'}) { $displayoffset{'shell'}++ unless ($displayoffset{'shell'} > $#command_history); $command_to_run = $command_history[$displayoffset{'shell'}]; } elsif($thekey eq $action{'scroll_up'}) { $displayoffset{'shell'}-- unless ($displayoffset{'shell'} <= 0); $command_to_run = $command_history[$displayoffset{'shell'}]; } elsif($thekey eq $action{'enter_or_play_folder'}) { # Run a command based on the typed input, and display # the command's output through the help function's # interface. push @command_history, $command_to_run; $displayoffset{'shell'} = $#command_history; my $chdir_target = ''; if($command_to_run =~ /^cd (.*)/) { chdir $1; $chdir_target = $1; } else { my @temp = `$command_to_run 2>&1`; my $chars_wide = $NUMLCDCOLS - 1; for(@temp) { chomp; while(length($_) > $chars_wide) { $_ =~ s/^(.{$chars_wide})//; push @command_output, $1; } push @command_output, $_; } } $command_to_run = ''; $showstats = 0; # temporarily disable it. switching modes re-enables it if it was enabled before. # In case the command had no output (including the case # that the output was written to STDERR instead of # STDOUT), give a special message. if($#command_output < 0) { if($chdir_target) { my $cwd = `pwd`; $command_output[1] = "cwd: $cwd"; } else { $command_output[1] = 'That command gave no output.'; } } display_something(); next; } elsif($thekey eq $action{'go_up_one_level'}) { # Delete one char from the command: $command_to_run = substr($command_to_run, 0, -1); } elsif(length($thekey) <= 1) { $command_to_run .= $thekey; } @toLCD = (); $toLCD[0] = "\$ " . $command_to_run; if($toLCD[0] =~ /^.{$NUMLCDCOLS}(.+)/) { $toLCD[1] = $1; } if($toLCD[1] =~ /^.{$NUMLCDCOLS}(.+)/) { $toLCD[2] = $1; } if($toLCD[2] =~ /^.{$NUMLCDCOLS}(.+)/) { $toLCD[3] = $1; } printLCD(@toLCD); goto dontbrowse; #next; } ### ### Now the main if/elsif chain begins. ### if($thekey eq $action{'scroll_down'}) { $displayoffset{$mode}++; } elsif($thekey eq $action{'scroll_up'}) { $displayoffset{$mode}--; if($displayoffset{$mode} < 0) { $displayoffset{$mode} = 0; } } elsif($thekey eq $action{'scroll_up_4'}) { $displayoffset{$mode} -= $scrollpagesize; } elsif($thekey eq $action{'scroll_down_4'}) { $displayoffset{$mode} += $scrollpagesize; if($displayoffset{$mode} < 0) { $displayoffset{$mode} = 0; } } elsif($thekey eq $action{'scroll_to_top'}) { $displayoffset{$mode} = 0; } elsif($thekey eq $action{'scroll_to_bottom'}) { $displayoffset{$mode} = 0; } ###### Enter a folder, or play the folder: # Press this key to select the topmost folder/play the topmost file shown. elsif($thekey eq $action{'enter_or_play_folder'}) { $key = ''; if($mode eq 'song') { goto dontbrowse; } # Only change to paths that exist. #if(-e "$path/$top{$mode}" && !($top{$mode} =~ /\.(mp3|wav|txt)$/)) # Enter the folder. elsif(-d "$path/$top{$mode}") { # Remember where we were in the root folder: if($path eq $rootpath) { $rootoffset{$mode} = $displayoffset{$mode}; } $path = "$path/$top{$mode}"; $displayoffset{$mode} = 0; } elsif($top{$mode} =~ /\.(mp3)$/) # Play the whole folder. { if($mode eq 'library') { if($song_is_playing) { # If a song is currently playing, then pressing Enter # will enqueue the selected tracks, rather than stopping # and playing them. add_folder_to_playlist($path); next; } else { clear_playlist(); add_folder_to_playlist($path); $currentsong = 1; } } else # mode is playlist, so just change the current song number: { $currentsong = $displayoffset{'playlist'} + 1; } $mode = 'song'; fork_player(''); goto dontbrowse; } elsif( ($top{$mode} =~ /\s*(.+) \(WEP:[yn]\)/) || ($top{$mode} =~ /Found net: (.+)/) ) { my $ssid = $1; $ssid =~ s/\s+$//; try_to_logon_to_network($ssid); } } elsif($thekey eq $action{'play_song'}) { if($top{$mode} =~ /\.(mp3)$/) { if($mode eq 'library') { # If there's a song playing, stop it: if($song_is_playing) { stop_player(); } clear_playlist(); $numsongs = $currentsong = 1; add_song_to_playlist("$path/$top{$mode}", $currentsong); } else # mode is playlist, so just change the current song number: { $currentsong = $displayoffset{'playlist'} + 1; } $mode = 'song'; fork_player(''); goto dontbrowse; } } # Press this key to move up one directory level. elsif($thekey eq $action{'go_up_one_level'}) { $key = ''; $displayoffset{$mode} = 0; # Don't cd higher than the root path. if($path =~ /(^$rootpath.*)\//) { $path = $1; } # Remember where we were at in the root folder: if($path eq $rootpath) { $displayoffset{$mode} = $rootoffset{$mode}; } } # Press this key to alternate between displaying the playlist, # the music library, and the current song. But only switch # to the playlist or current song if there are songs in the # playlist. elsif($thekey eq $action{'cycle_display'}) { $key = ''; print "mode before switching: $mode\n"; if($mode eq 'help') { # Re-enable the stats display since it gets turned # off when entering help mode. $showstats = 1 unless($options{'--nostats'}); $do_not_disturb_display = 0; $mode = 'song'; displaycurrentsong(); goto dontbrowse; } elsif( ($mode eq 'playlist') && ($numsongs > 0) ) { $do_not_disturb_display = 0; $mode = 'song'; displaycurrentsong(); goto dontbrowse; } elsif($mode eq 'song') { $do_not_disturb_display = 1; $mode = 'library'; } elsif( ($mode eq 'library') && ($numsongs > 0) ) { print "in library mode, numsongs=$numsongs\n"; $do_not_disturb_display = 1; $mode = 'playlist'; } $path = $rootpath; } ###### Enqueue folder. # Press this key to enque all mp3 files in the current directory. elsif($thekey eq $action{'add_folder_to_playlist'}) { add_folder_to_playlist($path); } ###### Enqueue one song. # Press this key to enque the currently selected song (the topmost one on the lcd). elsif($thekey eq $action{'add_song_to_playlist'}) { if($top{$mode} =~ /\.(mp3)$/) { $numsongs++; add_song_to_playlist("$path/$top{$mode}", $numsongs); if($currentsong == 0) { $currentsong = 1; } } } # Play. elsif($thekey eq $action{'play_playlist'}) { #if($song_is_playing) #{ # stop_player(); #} if($currentsong == 0) { $currentsong = 1; } $pausepoint = 0; $mode = 'song'; fork_player(''); goto dontbrowse; } # Skip to next song. elsif($thekey eq $action{'skip_forward_in_playlist'}) { stop_player() if $song_is_playing; $pausepoint = 0; if($numsongs > 0) { if($currentsong == $numsongs) { $currentsong = 1; } else { $currentsong++; } $currentsong_file_needs_syncing = 1; $mode = 'song'; #displaycurrentsong(); fork_player(''); } goto dontbrowse; } # Skip to previous song. elsif($thekey eq $action{'skip_backwards_in_playlist'}) { stop_player() if $song_is_playing; $pausepoint = 0; if($numsongs > 0) { if($currentsong == 1) { $currentsong = $numsongs; } else { $currentsong--; } $currentsong_file_needs_syncing = 1; $mode = 'song'; #displaycurrentsong(); fork_player(''); } goto dontbrowse; } # Stop playback. elsif($thekey eq $action{'stop_music'}) { fade_out(); stop_player(); #setvolume($volume_before_fading); fade_in(); if($mode eq 'song') { displaycurrentsong(); } $pausepoint = 0; $song_is_playing = 0; goto dontbrowse; } elsif($thekey eq $action{'show_playlist'}) { print_playlist(); } elsif($thekey eq $action{'clear_playlist'}) { clear_playlist(); } elsif( ($thekey eq $action{'remove_song_from_playlist'}) && ($mode eq 'playlist') ) { remove_song_from_playlist($top{'playlist'}); } elsif( ($thekey eq $action{'move_song_up_in_playlist'}) && ($mode eq 'playlist') ) { my $myPos = $displayoffset{'playlist'} + 1; my $prevPos = $displayoffset{'playlist'}; unless($myPos == 1) # Can't move it up if it's at the top. { my $temp = $playlist{$myPos}; $playlist{$myPos} = $playlist{$prevPos}; $playlist{$prevPos} = $temp; } write_playlist_to_file(); $displayoffset{'playlist'}-- unless $displayoffset{'playlist'} eq 0; } elsif( ($thekey eq $action{'move_song_down_in_playlist'}) && ($mode eq 'playlist') ) { my $myPos = $displayoffset{'playlist'} + 1; my $nextPos = $displayoffset{'playlist'} + 2; unless($myPos == $numsongs) # Can't move it down if it's at the bottom. { my $temp = $playlist{$myPos}; $playlist{$myPos} = $playlist{$nextPos}; $playlist{$nextPos} = $temp; } write_playlist_to_file(); $displayoffset{'playlist'}++ unless $displayoffset{'playlist'} eq ($numsongs - 1); } elsif($thekey eq $action{'volume_up'}) { setvolume('+3'); displayvolume(); goto dontbrowse; } elsif($thekey eq $action{'volume_down'}) { setvolume('-3'); displayvolume(); goto dontbrowse; } elsif($thekey eq $action{'mute'}) { my $vol = getvolume(); if($vol == 0) { setvolume($previous_volume); } else { setvolume('0'); $previous_volume = $vol; } displayvolume(); goto dontbrowse; } elsif($thekey eq $action{'reboot'}) { reboot(); } elsif($thekey eq $action{'halt'}) { halt(); } elsif($thekey eq $action{'exit'}) { prepare_for_shutdown('exit'); `$lcdwriter noclear rowcol 2 17 "Exited."`; die; } elsif($thekey eq $action{'free_ram'}) { `/freeram 80000`; $key = $thekey = ''; goto dontbrowse; } elsif($thekey eq $action{'display_help'}) { display_help(''); } elsif($thekey eq $action{'pause_music_normal'}) { pause_resume('normal'); goto dontbrowse; } elsif($thekey eq ' ') { # We're going to use spacebar as a special pause key... if the user is # typing text, then it works as the space character, but if the user # isn't typing text, then it works as a pause key. if($key eq '') { pause_resume('normal'); next; } } elsif($thekey eq $action{'pause_lowquality_file'}) { pause_resume('lowquality_file'); goto dontbrowse; } elsif($thekey eq $action{'ff'}) { songseek('ff'); goto dontbrowse; } elsif($thekey eq $action{'rw'}) { songseek('rw'); goto dontbrowse; } elsif($thekey eq $action{'ff_lowqual'}) { songseek('ff_loqual'); goto dontbrowse; } elsif($thekey eq $action{'rw_lowqual'}) { songseek('rw_lowqual'); goto dontbrowse; } elsif($thekey eq $action{'switch_editormode'}) { $editor_mode = $editor_mode ? 0 : 1; } elsif($thekey eq $action{'toggle_ramdisk_usage'}) { $use_ramdisk = $use_ramdisk ? 0 : 1; } elsif($thekey eq $action{'run_command'}) { $showstats = 0; $mode = 'shell'; $displayoffset{'shell'} = $#command_history + 1; @toLCD = (); @toLCD[0] = "\$ "; printLCD(@toLCD); goto dontbrowse; } elsif($thekey eq $action{'toggle_stats'}) { $showstats = $showstats ? 0 : 1; # If we're displaying the currently playing song, then # we need to refresh the display to make the changes # take effect: if($mode eq 'song') { displaycurrentsong(); goto dontbrowse; } } elsif($thekey eq $action{'toggle_extra_stats'}) { $report_disk_io = $report_disk_io ? 0 : 1; # I decided that the temperature display is neato enough # that it's worth the extra 4-5% CPU usage. #$report_temperature = $report_temperature ? 0 : 1; # If we're displaying the currently playing song, then # we need to refresh the display to make the changes # take effect: if($mode eq 'song') { displaycurrentsong(); goto dontbrowse; } } elsif($thekey eq $action{'toggle_exclude_unknown'}) { my $inc_or_exc; if($exclude_unknown) { $exclude_unknown = 0; $inc_or_exc = 'include'; } else { $exclude_unknown = 1; $inc_or_exc = 'exclude'; } @command_output = (); $command_output[0] = ' '; $command_output[1] = 'Random song/folder selection will now'; $command_output[2] = "$inc_or_exc the 'new and unknown' folder."; display_something(); } elsif($thekey eq $action{'play_random_folder'}) { stop_player() if $song_is_playing; enqueue_random_folder(); fork_player(''); #displaycurrentsong(); goto dontbrowse; } elsif($thekey eq $action{'update_randomselection_albumlist'}) { update_albumlist(); } elsif($thekey eq $action{'enable_readonly_mode_for_disk'}) { enable_readonly_mode_for_disk(); goto dontbrowse; } elsif($thekey eq $action{'kismet_start'}) { start_kismet(); $key = $thekey = ''; goto dontbrowse; } elsif($thekey eq $action{'kismet_stop'}) { stop_kismet(); $key = $thekey = ''; goto dontbrowse; } elsif($thekey eq $action{'start_wireless_card'}) { start_wireless_nic(); $key = $thekey = ''; goto dontbrowse; } elsif($thekey eq $action{'stop_wlan0'}) { print "stopping wlan0...\n"; `/wlan-stop.sh`; $key = $thekey = ''; goto dontbrowse; } elsif($thekey eq $action{'reset_sound'}) { pause_resume('normal'); print "removing and reinserting sound modules...\n"; `/sound-stop.sh`; sleep 1; `/sound-stop.sh`; sleep 2; `/sound-start.sh`; sleep 2; `/sound-start.sh`; $key = $thekey = ''; pause_resume('normal'); goto dontbrowse; } elsif($thekey eq $action{'reset_usb'}) { print "removing and reinserting usb modules...\n"; `/usb-stop.sh`; sleep 1; `/usb-stop.sh`; sleep 2; `/usb-start.sh`; sleep 2; `/usb-start.sh`; $key = $thekey = ''; goto dontbrowse; } elsif($thekey eq $action{'try_dhcp_and_website'}) { try_dhcp_server_and_website(); $key = $thekey = ''; goto dontbrowse; } elsif($thekey eq $action{'show_kismet_log'}) { @command_output = (); my $num_nets = count_nets_found_today(); my $num_open_nets = count_open_nets_found_today(); $command_output[0] = "[$num_nets nets / $num_open_nets open found today.]"; my @temp = `cat $kismet_log`; for(@temp) { chomp; while(length($_) > $NUMLCDCOLS) { $_ =~ s/^(.{$NUMLCDCOLS})//; push @command_output, $1; } push @command_output, $_; } $do_not_disturb_display = 1; display_something(); } elsif($thekey eq $action{'show_kismet_log_unique'}) { my (%entries,$key,$date,$ssid,$mac,$wep,$rate,$channel); @command_output = (); my @temp = `cat $kismet_log|grep "new network"`; my $chars_wide = $NUMLCDCOLS; for(@temp) { chomp; ($date,$ssid,$mac,$wep,$rate,$channel) = parse_kismet_logline($_); $entries{$mac} = "$date $mac $rate Ch$channel $ssid (WEP:$wep)"; } foreach $key (sort { $entries{$b} cmp $entries{$a} } (keys %entries)) { $_ = $entries{$key}; while(length($_) > $chars_wide) { $_ =~ s/^(.{$chars_wide})//; push @command_output, $1; } push @command_output, $_; } $do_not_disturb_display = 1; display_something(); } elsif($thekey eq $action{'disable_readonly_mode_for_disk'}) { disable_readonly_mode_for_disk(); goto dontbrowse; } elsif($thekey eq $action{'play_random_songs'}) { play_random_songs(); goto dontbrowse; } elsif($thekey eq $action{'show_current_song_filename'}) { @command_output = (); $command_output[0] = $playlist{$currentsong}; $command_output[0] =~ s/^$rootpath//; $command_output[0] =~ s/.*\/(.+)/$1/ if $editor_mode; if($command_output[0] =~ /^.{$NUMLCDCOLS}(.+)/) { $command_output[1] = $1; } if($command_output[1] =~ /^.{$NUMLCDCOLS}(.+)/) { $command_output[2] = $1; } if($command_output[2] =~ /^.{$NUMLCDCOLS}(.+)/) { $command_output[3] = $1; } display_something(); $key = ''; # reset the accumulating-input-string. } elsif($thekey eq $action{'update_music_library'}) { my $prev_mode = $mode; my $prev_stat_state = $showstats; @command_output = (); $command_output[0] = "Attempting to connect to home system"; $command_output[1] = "to sync my music library to it."; $command_output[2] = "Please wait a minute or two..."; display_something(); `/etc/rc.d/rc.inet1`; # try to connect via wired network `/go-home.sh`; # try to connect via wireless sleep 2; my $count_mobile = `ls -R1 $rootpath|grep -P "\\.mp3\$"|wc -l`; my $size_mobile = `du -sm $rootpath`; $size_mobile =~ s/(\d+)\s+.+/$1/g; my $home_stats = `lynx -dump potassium:88/cgi-bin/reportsize.cgi`; chomp $home_stats; my ($count_home,$size_home) = ($home_stats =~ /^(\d+) (\d+)$/); if( ($count_home =~ /^\d+$/) && ($size_home =~ /^\d+$/) ) { for($count_mobile,$size_mobile) { s/^\s+//; s/\s+$//; } @command_output = (); $command_output[0] = "Before: home : $count_home mp3s, $size_home MB"; $command_output[1] = " mobile: $count_mobile mp3s, $size_mobile MB"; $command_output[2] = "Press y to begin rsync,"; $command_output[3] = " or n to abort."; display_something(); ReadMode('normal'); my $choice = ; ReadMode('cbreak'); chomp $choice; if($choice =~ /^y$/i) { $command_output[2] = "rsyncing my library with home library..."; $command_output[3] = ''; display_something(); `/netstatlive.pl 1>/dev/null 2>/dev/null &`; `mount -o remount,rw /mp3s`; `rsync -a --delete potassium::mastermusic /mp3s/music`; `mount -o remount,ro /mp3s`; `killall netstatlive.pl`; $count_mobile = `ls -R1 $rootpath|grep -P "\\.mp3\$"|wc -l`; $size_mobile = `du -sm $rootpath`; $size_mobile =~ s/(\d+)\s+.+/$1/g; for($count_mobile,$size_mobile) { s/^\s+//; s/\s+$//; } $home_stats = `lynx -dump potassium:88/cgi-bin/reportsize.cgi`; chomp $home_stats; ($count_home,$size_home) = ($home_stats =~ /^(\d+) (\d+)$/); $command_output[2] = "rsync complete."; $command_output[3] = "After: home : $count_home items, $size_home MB"; $command_output[4] = " mobile: $count_mobile items, $size_mobile MB"; $command_output[5] = "Don't forget to update the random-selection"; $command_output[6] = "albumlist now (press $action{'update_randomselection_albumlist'})."; display_something(); `/wlan-stop.sh`; } else { $mode = $prev_mode; $showstats = $prev_stat_state; if($mode eq 'library') { browse(); } elsif($mode eq 'song') { displaycurrentsong(); } } } else { @command_output = (); $command_output[0] = ""; $command_output[1] = "Could not get size of music library from"; $command_output[2] = "home system. Will not attempt sync."; display_something(); } } elsif($thekey eq $action{'count_files'}) { @command_output = (); my $num_albums = 0; opendir(DIR,"$rootpath") or die "$0: (2) couldn't open directory $rootpath: $!\n"; my @rootfolders = grep( (-d "$rootpath/$_" && !/^\.\.?$/) , readdir(DIR)); #print join "\n", @rootfolders; my $folder; foreach $folder (@rootfolders) { next if $folder =~ /^(#downloaded|#nonmusic|#other|nonsuch)$| \(DL\)$/; opendir(DIR2,"$rootpath/$folder") or die "$0: (3) couldn't open directory $rootpath/$folder: $!\n"; my @albums = grep( (-d "$rootpath/$folder/$_" && !/^\.\.?$| \(DL\)$/), readdir(DIR2)); $num_albums = $num_albums + $#albums + 1; #extra 1 because $#albums==0 means 1 album exists. closedir DIR2 or die "$0: couldn't close directory $rootpath/$folder: $!\n"; } closedir DIR or die "$0: couldn't close $rootpath: $!\n"; my $num_songs = `ls -R1 $rootpath|grep -P "\\.mp3\$"|wc -l`; chomp $num_songs; $num_songs =~ s/\s//g; my $space_used = `du -sm $rootpath`; chomp $space_used; ($space_used) = ($space_used =~ /(\d+)/); $space_used /= 1000; # in gigs. $command_output[0] = ' '; $command_output[1] = " MP3 count: $num_songs (includes downloads)"; $command_output[2] = " Album count: $num_albums (excludes downloads)"; $command_output[3] = " Space used: $space_used gigabytes"; $showstats = 0; display_help('run_command'); @command_output = (); # This loads the system pretty bad, messing up the count # and stuff, do refresh the display if necessary: if($mode eq 'song') { displaycurrentsong(); } print "num_songs: $num_songs\nnum_albums: $num_albums\nspace_used: $space_used\n\n"; } # This allows the user to type more-than-one letter at a time, to match # more precisely the band/album they want. If they take longer than 2 # seconds to finish typing, we reset the count and their input. #unless($ignorekey) # # This '$ignorekey' stuff is nonsense... just test if it's a valid # character for a bandname or albumname or songname (and remember that # uppercase letters are NOT valid for our purposes): if($thekey =~ /^[0-9a-z_# -]{1}$/) { if(($end - $start) >= $input_timeout) { $key = $thekey; } else { $key .= $thekey; } } browse(''); }#end main loop sub add_folder_to_playlist($) { my $folder = shift; opendir(DIR,"$folder") or die "$0: (4) couldn't open directory $folder: $!\n"; @mp3s = grep(/\.mp3$/, readdir(DIR)); closedir DIR or die "$0: couldn't close directory $folder: $!\n"; for(sort(@mp3s)) { chomp $_; $numsongs++; add_song_to_playlist("$folder/$_", $numsongs); } } sub play_folder($) { my $folder = shift; if($song_is_playing) { stop_player(); } clear_playlist(); $currentsong = 1; add_folder_to_playlist($folder); $mode = 'song'; fork_player(''); } # Recursive sub... pass it the directory to start at, or else it # will start at the root path defined at the top of this script: sub play_random_folder_old($) { my $path = shift; if(!$path || $path eq '') { # Since the downloadedmusic folder contains tons of bands, # treat it like another root folder in the random selection # (well, almost... give it half as good of a chance): my $random = int(rand(4)); if($random == 0) { $path = $rootpath; } elsif($random == 1) { $path = $rootpath; } elsif($random == 2) { $path = $rootpath; } elsif($random == 3) { $path = $rootpath . '/' . '#downloaded'; } } my @mp3s; my $more_subfolders = 0; opendir(DIR,"$path") or die "$0: couldn't open dir $path: $!\n"; # Get the root dir list, excluding . and ..: my @dirlist = grep( (!/^\.\.?$/) , readdir(DIR)); closedir DIR or die "$0: couldn't close directory $path: $!\n"; # Append the root path to each one: for(@dirlist) { $_ = $path . '/' . $_; if(/\.mp3$/) { push @mp3s, $_; } elsif(-d) { $more_subfolders = 1; } } # Once we find a folder containing mp3s, we might think to pick one randomly # and use that as our random song. However, if we do that, then any folder # that contains mp3s AND other subfolders will always return a "random" song # from the mp3s, never from the subfolders. So we need a random bool here # to make sure that we sometimes (ideally, half the time) check subfolders # even when there are already mp3s at the current level. Of course, if there # aren't any subfolders present, then we don't need to bother and we'll just # take one of the mp3s. my $keep_looking; if(!$more_subfolders) { $keep_looking = 0; } else { $keep_looking = int(rand(2)); } if( ($#mp3s > -1) && (!$keep_looking) ) { # The %random_folders_already_chosen hash contains every album already # chosen to play (whether we played through the entire thing or not is # irrelevant; as soon as it's chosen it gets added to the hash). It # prevents any album from being chosen more than once as a "random" # album. This hash is cleared whenever the script is restarted (so # reboot, halt, etc. all clear it). if( !($path eq $last_random_folder) && !($random_folders_already_chosen{$path}) ) { $last_random_folder = $path; $random_folders_already_chosen{$path} = 1; play_folder($path); } else { # Start over if we picked the same one twice in a row. play_random_folder(); } } else { # Remove any items that aren't directories: @dirlist = grep { -d } @dirlist; my $which = int(rand($#dirlist)); # random int between 0 and $#dirlist. # If the listing is empty, then we've gotten into a folder with # no mp3s and no more subfolders. If the selected folder is my # nonmusic folder, we don't want to include that. And if we're # currently excluding my new_and_unknown folder, and that is the # one that got selected, that's bad too. In any of these 3 # cases, we want to select a new random folder, starting at the # root. if( ($#dirlist == -1) || ($dirlist[$which] =~ /\/#nonmusic/i) || ($exclude_unknown && ($dirlist[$which] =~ /\/#new_and_unknown/i)) ) { play_random_folder(); } else { play_random_folder($dirlist[$which]); } } } sub load_albumlist_for_random_selection { my $path = shift; $path = $rootpath unless defined $path; #print "path is $path\n"; opendir(DIR,"$path") or die "$0: couldn't open dir $path: $!\n"; my @dirlist = grep { !/^\.\.?$/ && (-d "$path/$_") } readdir(DIR); closedir DIR or die "$0: couldn't close dir $path: $!\n"; for(@dirlist) { push @all_folders, "$path/$_"; load_albumlist_for_random_selection("$path/$_"); } } sub purge_nonmp3_and_excluded_folders_from_albumlist { my $exclude_this_folder; my $folder; for(@all_folders) { opendir(DIR,"$_") or die "$0: couldn't open dir \"$_\": $!\n"; my @mp3s = grep { /\.mp3$/ } readdir(DIR); closedir DIR or die "$0: couldn't close dir \"$_\": $!\n"; # If there are mp3s in this folder... # note: >0 really means >1 mp3 file; I want to ignore single-file folders for the random-album selection. if($#mp3s > 0) { $folder = $_; $exclude_this_folder = 0; for(@folders_to_exclude) { if($folder =~ /$_/i) { $exclude_this_folder = 1; } } unless($exclude_this_folder || ($folder =~ /musicbox-ignore/)) { push @mp3_folders, $folder; } } } } sub load_albumlist_from_file() { print "diskread: load_albumlist_from_file() reading $albumlist_file\n"; open(IN,"<$albumlist_file") or die "$0: couldn't open $albumlist_file for reading: $!\n"; flock IN, 1; seek IN, 0, 0; while() { chomp; push @mp3_folders, $_; } close IN or die "$0: couldn't close $albumlist_file: $!\n"; } sub enqueue_random_folder { my $which = int(rand($#mp3_folders)); if($random_folders_already_chosen{$which}) { enqueue_random_folder(); } else { $random_folders_already_chosen{$which} = 1; #print "Playing $which: $mp3_folders[$which]\n"; clear_playlist(); $currentsong = 1; #print "in enqueue_random_folder(): song_is_playing=$song_is_playing\n"; add_folder_to_playlist($mp3_folders[$which]); } } sub play_random_songs() { my %songs; my $song; for(my $i = 1; $i <= $num_random_songs; $i++) { $song = get_random_song(); if($songs{$song} == 1) { # We already had this one so get another: $i--; } else { $songs{$song} = 1; } } if($song_is_playing) { stop_player(); } clear_playlist(); $numsongs = 0; for(keys %songs) { $numsongs++; $playlist{$numsongs} = $_; } write_playlist_to_file(); $currentsong = 1; $mode = 'song'; fork_player(''); } # Recursive sub... pass it the directory to start at, or else it # will start at the root path defined at the top of this script: sub get_random_song($) { my $path = shift; if(!$path || $path eq '') { # Since the downloadedmusic folder contains tons of bands, # treat it like another root folder in the random selection # (and the #downloaded/singles folder, while we're at it). # We want the #downloaded folder to have as good a chance # as the main folder, but the singles folder is much more # limited in capacity, so we only give that half the chance. my $random = int(rand(6)); if($random == 0) { $path = $rootpath; } elsif($random == 1) { $path = $rootpath; } elsif($random == 2) { $path = $rootpath; } elsif($random == 3) { $path = $rootpath . '/' . '#downloaded'; } elsif($random == 4) { $path = $rootpath . '/' . '#downloaded'; } elsif($random == 5) { $path = $rootpath . '/' . '#downloaded/#singles'; } } my @mp3s; my $more_subfolders = 0; opendir(DIR,"$path") or die "$0: couldn't open dir $path: $!\n"; # Get the root dir list, excluding . and ..: my @dirlist = grep( (!/^\.\.?$/) , readdir(DIR)); closedir DIR or die "$0: couldn't close directory $path: $!\n"; # Append the root path to each one: for(@dirlist) { $_ = $path . '/' . $_; if(/\.mp3$/) { push @mp3s, $_; } elsif(-d) { $more_subfolders = 1; } } # Once we find a folder containing mp3s, we might think to pick one randomly # and use that as our random song. However, if we do that, then any folder # that contains mp3s AND other subfolders will always return a "random" song # from the mp3s, never from the subfolders. So we need a random bool here # to make sure that we sometimes (ideally, half the time) check subfolders # even when there are already mp3s at the current level. Of course, if there # aren't any subfolders present, then we don't need to bother and we'll just # take one of the mp3s. my $keep_looking; if(!$more_subfolders) { $keep_looking = 0; } else { $keep_looking = int(rand(2)); } if( ($#mp3s > -1) && (!$keep_looking) ) { my $rand = int(rand($#mp3s)); return $mp3s[$rand]; } else { # Remove any items that aren't directories: @dirlist = grep( -d && !/^\.\.?$/, @dirlist); my $which = int(rand($#dirlist)); # random int between 0 and $#dirlist. # If the listing is empty, then we've gotten into a folder with # no mp3s and no more subfolders. If the selected folder is my # nonmusic folder, we don't want to include that. And if we're # currently excluding my new_and_unknown folder, and that is the # one that got selected, that's bad too. In any of these 3 # cases, we want to select a new random song, starting at the # root. if( ($#dirlist == -1) || ($dirlist[$which] =~ /\/#nonmusic/i) || ($exclude_unknown && ($dirlist[$which] =~ /\/#new_and_unknown/i)) ) { get_random_song(); } else { get_random_song($dirlist[$which]); } } }