* Philip Meyer shaped the electrons to say...

I usually just run the exe as a service, but since I started investigating
the problem, I've been running with ActiveState.

The problem also occurs with .m3u files, most of which I have now removed
from my music folders.

Ok - I've attached a replacement Slim/Utils/Misc.pm - can you drop it in and 
run with that?

-D
--
"They that can give up essential liberty to obtain a little temporary safety
deserve neither liberty nor safety." - Benjamin Franklin
package Slim::Utils::Misc;

# $Id: Misc.pm 3847 2005-08-02 22:57:47Z dsully $

# SlimServer Copyright (c) 2001-2004 Sean Adams, Slim Devices Inc.
# This program is free software; you can redistribute it and/or
# modify it under the terms of the GNU General Public License, 
# version 2.

use strict;
use File::Spec::Functions qw(:ALL);
use File::Which ();
use FindBin qw($Bin);
use Fcntl qw(:seek);
use POSIX qw(strftime setlocale LC_TIME LC_CTYPE);
use Sys::Hostname;
use Socket qw(inet_ntoa inet_aton);
use Symbol qw(qualify_to_ref);
use URI;
use URI::file;

use Slim::Music::Info;
use Slim::Utils::OSDetect;
use Slim::Utils::Strings qw(string);

if ($] > 5.007) {
        require Encode;
        require File::BOM;
}

use base qw(Exporter);

our @EXPORT = qw(assert bt msg msgf watchDog);
our $log    = "";
our $watch  = 0;

BEGIN {
        if ($^O =~ /Win32/) {
                *EWOULDBLOCK = sub () { 10035 };
                *EINPROGRESS = sub () { 10036 };

                require Win32::Shortcut;
                require Win32::OLE::NLS;
                require Win32;

        } else {
                require Errno;
                import Errno qw(EWOULDBLOCK EINPROGRESS);
        }
}

# Cache our user agent string.
my $userAgentString;

# Find out what code page we're in, so we can properly translate file/directory 
encodings.
our $locale = '';
our $utf8_re_bits;

{
        if ($^O =~ /Win32/) {

                my $langid = Win32::OLE::NLS::GetUserDefaultLangID();
                my $lcid   = Win32::OLE::NLS::MAKELCID($langid);
                my $linfo  = Win32::OLE::NLS::GetLocaleInfo($lcid, 
Win32::OLE::NLS::LOCALE_IDEFAULTANSICODEPAGE());

                $locale = "cp$linfo";

        } elsif ($^O =~ /darwin/) {

                # I believe this is correct from reading:
                # 
http://developer.apple.com/documentation/MacOSX/Conceptual/SystemOverview/FileSystem/chapter_8_section_6.html
                $locale = 'utf8';

        } else {

                my $lc = POSIX::setlocale(LC_CTYPE) || 'C';

                # If the locale is C or POSIX, that's ASCII - we'll set to 
iso-8859-1
                # Otherwise, normalize the codeset part of the locale.
                if ($lc eq 'C' || $lc eq 'POSIX') {
                        $lc = 'iso-8859-1';
                } else {
                        $lc = lc((split(/\./, $lc))[1]);
                }

                # Locale can end up with nothing, if it's invalid, such as 
"en_US"
                if (!defined $lc || $lc =~ /^\s*$/) {
                        $lc = 'iso-8859-1';
                }

                # Sometimes underscores can be aliases - Solaris
                $lc =~ s/_/-/g;

                # ISO encodings with 4 or more digits use a hyphen after "ISO"
                $lc =~ s/^iso(\d{4})/iso-$1/;

                # Special case ISO 2022 and 8859 to be nice
                $lc =~ s/^iso-(2022|8859)([^-])/iso-$1-$2/;

                $lc =~ s/utf-8/utf8/gi;

                $locale = $lc;
        }

        # Create a regex for looks_like_utf8()
        $utf8_re_bits = join "|", map { latin1toUTF8(chr($_)) } (127..255);
}

sub blocking {   
        my $sock = shift;
        return $sock->blocking(@_) unless $^O =~ /Win32/;
        my $nonblocking = $_[0] ? "0" : "1";
        my $retval = ioctl($sock, 0x8004667e, \$nonblocking);
        if (!defined($retval) && $] >= 5.008) {
                $retval = "0 but true";
        }
        return $retval;
}

sub findbin {
        my $executable = shift;

        # Reduce all the x86 architectures down to i386, so we only need one
        # directory per *nix OS.
        my $arch = $Config::Config{'archname'};

           $arch =~ s/^i[3456]86-([^-]+).*$/i386-$1/;

        my $path;
        my @paths = (
                catdir($Bin, 'Bin', $arch),
                catdir($Bin, 'Bin', $^O),
                catdir($Bin, 'Bin'),
        );

        if (Slim::Utils::OSDetect::OS() eq 'mac') {
                push @paths, $ENV{'HOME'} . "/Library/SlimDevices/bin/";
                push @paths, "/Library/SlimDevices/bin/";
                push @paths, $ENV{'HOME'} . 
"/Library/iTunes/Scripts/iTunes-LAME.app/Contents/Resources/";
        }

        if (Slim::Utils::OSDetect::OS() ne "win") {
                push @paths, (split(/:/, 
$ENV{'PATH'}),'/usr/bin','/usr/local/bin','/sw/bin');
        } else {
                $executable .= '.exe';
        }

        foreach my $path (@paths) {
                $path = catdir($path, $executable);

                $::d_paths && msg("Checking for $executable in $path\n");

                if (-x $path) {
                        $::d_paths && msg("Found binary $path for 
$executable\n");
                        return $path;
                }
        }

        if (Slim::Utils::OSDetect::OS() eq "win") {
                $path =  File::Which::which($executable);
        } else {
                $path = undef;
        }

        $::d_paths && msgf("Found binary %s for %s\n", defined $path ? $path : 
'undef', $executable);

        return $path;   
}

sub pathFromWinShortcut {
        my $fullpath = pathFromFileURL(shift);

        my $path = "";

        if (Slim::Utils::OSDetect::OS() ne "win") {
                $::d_files && msg("Windows shortcuts not supported on 
non-windows platforms\n");
                return $path;
        }

        my $shortcut = Win32::Shortcut->new($fullpath);
        if (defined($shortcut)) {

                $path = $shortcut->Path();
                # the following pattern match throws out the path returned from 
the
                # shortcut if the shortcut is contained in a child directory of 
the path
                # to avoid simple loops, loops involving more than one shortcut 
are still
                # possible and should be dealt with somewhere, just not here.
                if (defined($path) && !$path eq "" && $fullpath !~ 
/^\Q$path\E/i) {

                        $path = fileURLFromPath($path);

                        #collapse shortcuts to shortcuts into a single hop
                        if (Slim::Music::Info::isWinShortcut($path)) {
                                $path = pathFromWinShortcut($path);
                        }

                } else {
                        $::d_files && msg("Bad path in $fullpath\n");
                        $::d_files && defined($path) && msg("Path was $path\n");
                }

        } else {
                $::d_files && msg("Shortcut $fullpath is invalid\n");
        }

        $::d_files && msg("pathFromWinShortcut: path $path from shortcut 
$fullpath\n"); 

        return $path;
}

sub fileURLFromWinShortcut {
        my $shortcut = shift;

        return fixPath(pathFromWinShortcut($shortcut));
}

sub pathFromFileURL {
        my $url = shift;
        my $file;
        
        assert(Slim::Music::Info::isFileURL($url), "Path isn't a file URL: 
$url\n");

        my $uri = URI->new($url);

        # TODO - FIXME - this isn't mac or dos friendly with the path...
        # Use File::Spec::rel2abs ? or something along those lines?
        #
        # file URLs must start with file:/// or file://localhost/ or 
file://\\uncpath
        if ($uri->scheme() && $uri->scheme() eq 'file') {

                my $path = $uri->path();

                $::d_files && msg("Got $path from file url $url\n");

                # only allow absolute file URLs and don't allow .. in files...
                if ($path !~ /[\/\\]\.\.[\/\\]/) {
                        $file = $uri->file();
                } 

        } else {
                msg("pathFromFileURL: $url isn't a file URL...\n");
                bt();
        }

        if (!defined($file))  {
                $::d_files && msg("bad file: url $url\n");
        } else {
                $::d_files && msg("extracted: $file from $url\n");
        }

        return $file;
}

sub fileURLFromPath {
        my $path = shift;
        
        return $path if (Slim::Music::Info::isURL($path));

        my $uri  = URI::file->new($path);
        $uri->host('');
        return $uri->as_string;
}

# Unicode / Encoding functions.

sub utf8decode {
        my $string = shift;

        # Bail early if it's just ascii
        if (looks_like_ascii($string)) {
                return $string;
        }

        my $orig = $string;

        if ($string && $] > 5.007 && !Encode::is_utf8($string)) {

                $string = Encode::decode('utf8', $string, Encode::FB_QUIET());

        } elsif ($string && $] > 5.007) {

                Encode::_utf8_on($string);
        }

        if ($string && $] > 5.007 && !looks_like_utf8($string)) {

                $string = $orig;
        }

        return $string;
}

sub utf8encode {
        my $string = shift;

        # Bail early if it's just ascii
        if (looks_like_ascii($string)) {
                return $string;
        }

        my $orig = $string;

        # Don't try to encode a string which isn't utf8
        # 
        # If the incoming string already is utf8, turn off the utf8 flag.
        if ($string && $] > 5.007 && !Encode::is_utf8($string)) {

                $string = Encode::encode('utf8', $string, Encode::FB_QUIET());

        } elsif ($string && $] > 5.007) {

                Encode::_utf8_off($string);
        }

        # Check for doubly encoded strings - and revert back to our original
        # string if that's the case.
        if ($string && $] > 5.007 && !looks_like_utf8($string)) {

                $string = $orig;
        }

        return $string;
}

sub utf8off {
        my $string = shift;

        if ($string && $] > 5.007) {
                Encode::_utf8_off($string);
        }

        return $string;
}

sub utf8on {
        my $string = shift;

        if ($string && $] > 5.007 && looks_like_utf8($string)) {
                Encode::_utf8_on($string);
        }

        return $string;
}

sub looks_like_ascii {
        use bytes;

        return 1 if $_[0] !~ /([^\x00-\x7F])/;
        return 0;
}

sub looks_like_latin1 {
        use bytes;

        return 1 if $_[0] !~ /([^\x00-\xFF])/;
        return 0;
}

sub looks_like_utf8 {
        use bytes;

        return 1 if $_[0] =~ /($utf8_re_bits)/o;
        return 0;
}

sub latin1toUTF8 {
        my $data = shift;

        if ($] > 5.007) {

                $data = eval { Encode::encode('utf8', $data, 
Encode::FB_QUIET()) } || $data;

        } else {

                $data =~ 
s/([\x80-\xFF])/chr(0xC0|ord($1)>>6).chr(0x80|ord($1)&0x3F)/eg;
        }

        return $data;
}

sub utf8toLatin1 {
        my $data = shift;

        if ($] > 5.007) {

                $data = eval { Encode::encode('iso-8859-1', $data, 
Encode::FB_QUIET()) } || $data;

        } else {

                $data =~ 
s/([\xC0-\xDF])([\x80-\xBF])/chr(ord($1)<<6&0xC0|ord($2)&0x3F)/eg; 
                $data =~ s/[\xE2][\x80][\x99]/'/g;
        }

        return $data;
}

sub encodingFromString {

        my $encoding = 'raw';

        # Don't copy a potentially large string - just read it from the stack.
        if (looks_like_ascii($_[0])) {

                $encoding = 'ascii';

        } elsif (looks_like_utf8($_[0])) {
        
                $encoding = 'utf8';

        } elsif (looks_like_latin1($_[0])) {
        
                $encoding = 'iso-8859-1';
        }

        return $encoding;
}

sub encodingFromFileHandle {
        my $fh = shift;

        # If we didn't get a filehandle, not much we can do.
        if (!ref($fh) || !$fh->can('seek')) {

                msg("Warning: Not a filehandle in encodingFromFileHandle()\n");
                bt();

                return;
        }

        local $/ = undef;

        # Save the old position (if any)
        # And find the file size.
        #
        # These must be seek() and not sysseek(), as File::BOM uses seek(),
        # and they'll get confused otherwise.
        my $pos  = tell($fh);
        my $size = seek($fh, 0, SEEK_END);

        # Don't do any translation.
        binmode($fh, ":raw");

        # Try to find a BOM on the file - otherwise check the string
        #
        # Although get_encoding_from_filehandle tries to determine if
        # the handle is seekable or not - the Protocol handlers don't
        # implement a seek() method, and even if they did, File::BOM
        # internally would try to read(), which doesn't mix with
        # sysread(). So skip those m3u files entirely.
        my $enc = '';

        # Explitly check for IO::String - as it does have a seek() method!
        if ($] > 5.007 && ref($fh) && ref($fh) ne 'IO::String' && 
$fh->can('seek')) {
                $enc = File::BOM::get_encoding_from_filehandle($fh);
        }

        # File::BOM got something - let's get out of here.
        return $enc if $enc;

        # Seek to the beginning of the file.
        seek($fh, 0, SEEK_SET);

        #
        read($fh, my $string, $size);

        # Seek back to where we started.
        seek($fh, $pos, SEEK_SET);

        return encodingFromString($string);
}

# Handle either a filename or filehandle
sub encodingFromFile {
        my $file = shift;

        my $encoding = $locale;

        if (ref($file) && $file->can('seek')) {

                $encoding = encodingFromFileHandle($file);

        } elsif (-r $file) {

                my $fh = new FileHandle;
                $fh->open($file) or do {
                        msg("Couldn't open file: [$file] : $!\n");
                        return $encoding;
                };

                $encoding = encodingFromFileHandle($fh);

                $fh->close();

        } else {

                msg("Warning: Not a filename or filehandle encodingFromFile( 
$file )\n");
                bt();
        }

        return $encoding;
}

########

sub anchorFromURL {
        my $url = shift;

        if ($url =~ /#(.*)$/) {
                return $1;
        }
        return undef;
}

sub stripAnchorFromURL {
        my $url = shift;

        if ($url =~ /^(.*)#[\d\.]+-.*$/) {
                return $1;
        }

        return $url;
}

#################################################################################
#
# split a URL into (host, port, path)
#
sub crackURL {
        my ($string) = @_;

        $string =~ m|http://(?:([^\@:]+):?([EMAIL 
PROTECTED])\@)?([^:/]+):*(\d*)(\S*)|i;
        
        my $user = $1;
        my $password = $2;
        my $host = $3;
        my $port = $4;
        my $path = $5;
        
        $path = '/' unless $path;

        $port = 80 unless $port;
        
        $::d_files && msg("cracked: $string with [$host],[$port],[$path]\n");
        $::d_files && $user && msg("   user: [$user]\n");
        $::d_files && $password && msg("   password: [$password]\n");
        
        return ($host, $port, $path, $user, $password);
}

# fixPathCase makes sure that we are using the actual casing of paths in
# a case-insensitive but case preserving filesystem.
# currently only implemented for Win32

sub fixPathCase {
        my $path = shift;
        my $orig = $path;

        if ($^O =~ /Win32/) {
                $path = Win32::GetLongPathName($path);
        }

        # Use the original path if we didn't get anything back from
        # GetLongPathName - this can happen if a cuesheet references a
        # non-existant .wav file, which is often the case.
        #
        # At that point, we'd return a bogus value, and start crawling at the
        # top of the directory tree, which isn't what we want.
        $path = $orig unless $path;

        return canonpath($path);
}
                
# there's not really a better way to do this..
# fixPath takes relative file paths and puts the base path in the beginning
# to make them full paths, if possible.
# URLs are left alone
        
sub fixPath {
        my $file = shift || return;
        my $base = shift;

        my $fixed;

        if (Slim::Music::Info::isURL($file)) { 

                my $uri = URI->new($file);

                if ($uri->scheme() && $uri->scheme() eq 'file') {

                        $uri->host('');
                }

                return $uri->as_string;
        }

        if (Slim::Music::Info::isFileURL($base)) {
                $base = pathFromFileURL($base);
        } 

        # the only kind of absolute file we like is one in 
        # the music directory or the playlist directory...
        my $audiodir = Slim::Utils::Prefs::get("audiodir");
        my $savedplaylistdir = Slim::Utils::Prefs::get("playlistdir");

        if ($audiodir && $file =~ /^\Q$audiodir\E/) {

                $fixed = $file;

        } elsif ($savedplaylistdir && $file =~ /^\Q$savedplaylistdir\E/) {

                $fixed = $file;

        } elsif (Slim::Music::Info::isURL($file) && (!defined($audiodir) || ! 
-r catfile($audiodir, $file))) {

                $fixed = $file;

        } elsif ($base) {

                if (file_name_is_absolute($file)) {
                        if (Slim::Utils::OSDetect::OS() eq "win") {
                                my ($volume) = splitpath($file);
                                if (!$volume) {
                                        ($volume) = splitpath($base);
                                        $file = $volume . $file;
                                }
                        }
                        $fixed = fixPath($file);

                } else {

                        if (Slim::Utils::OSDetect::OS() eq "win") {

                                # rel2abs will convert ../../ paths correctly 
only for windows
                                $fixed = fixPath(rel2abs($file,$base));

                        } else {

                                $fixed = fixPath(stripRel(catfile($base, 
$file)));
                        }
                }
        } elsif (file_name_is_absolute($file)) {
                $fixed = $file;
        } else {
                $file =~ s/\Q$audiodir\E//;
                $fixed = catfile($audiodir, $file);
        }

        # I hate Windows.
        # A playlist or the like can have a completely different case than
        # what we get from the filesystem. Fix that all up so we don't create
        # duplicate entries in the database.
        if (Slim::Utils::OSDetect::OS() eq "win" && 
!Slim::Music::Info::isFileURL($fixed)) {

                $fixed = fixPathCase($fixed);
        }

        $::d_paths && ($file ne $fixed) && msg("*****fixed: " . $file . " to " 
. $fixed . "\n");
        $::d_paths && ($file ne $fixed) && ($base) && msg("*****base: " . $base 
. "\n");

        if (Slim::Music::Info::isFileURL($fixed)) {
                return $fixed;
        } else {
                return fileURLFromPath($fixed);
        }
}

sub stripRel {
        my $file = shift;
        
        while ($file =~ m#[\/\\]\.\.[\/\\]#) {
                $file =~ s#\w+[\/\\]\.\.[\///]##isg;
        }
        
        $::d_paths && msg("stripRel result: $file\n");
        return $file;
}

sub virtualToAbsolute {
        my ($virtual, $recursion) = @_;

        my $curdir  = Slim::Utils::Prefs::get('audiodir') || return $virtual;

        if (!defined $virtual) {
                $virtual = ""
        }
        
        if (Slim::Music::Info::isURL($virtual)) {
                return $virtual;
        }
        
        if (file_name_is_absolute($virtual)) {
                $::d_paths && msg("virtualToAbsolute: $virtual is already 
absolute.\n");
                return $virtual;
        }

        # Always unescape ourselves
        $virtual = Slim::Web::HTTP::unescape($virtual);

        $curdir = fileURLFromPath($curdir);     

        my @levels = ();

        if (defined($virtual)) {
                @levels = splitdir($virtual);
        }

        my $level;

        if ($::d_paths) {
                foreach $level (@levels) {
                        msg("    $level\n");
                }
        }

        my @items;
        foreach $level (@levels) {

                next if $level eq "";

                # this was breaking songinfo and other pages when using windows 
.lnk files.
                #last if $level eq "..";

                # optimization for pre-cached imported playlists.
                if (Slim::Music::Info::isPlaylistURL($curdir)) {
                        my $listref = 
Slim::Music::Info::cachedPlaylist(fileURLFromPath($curdir));
                        if ($listref) {
                                return @{$listref}[$level];
                        }
                } 
                
                if (Slim::Music::Info::isPlaylist(fileURLFromPath($curdir))) {
                        @items = ();
                        Slim::Utils::Scan::addToList([EMAIL PROTECTED],$curdir, 
0, 0);
                        if (scalar(@items)) {
                                if (defined $items[$level]) {
                                        $curdir = $items[$level];
                                } else {
                                        last;
                                }
                                #continue traversing if the item was found in 
the list
                                #and the item found is itself a list
                                next if 
(Slim::Music::Info::isList(fileURLFromPath($curdir)));
                                #otherwise stop traversing, curdir is either 
the playlist
                                #if no entry found or the located entry in the 
playlist
                                last;
                        }
                } else {
                        if (Slim::Music::Info::isURL($curdir)) {
                                #URLs always use / as separator
                                $curdir .= '/' . 
Slim::Web::HTTP::escape($level);
                        } else {
                                $curdir = catdir($curdir,$level);
                        }
                }

                next if (Slim::Music::Info::isDir(fileURLFromPath($curdir)));

                if (Slim::Music::Info::isWinShortcut(fileURLFromPath($curdir))) 
{
                        if 
(defined($Slim::Utils::Scan::playlistCache{fileURLFromPath($curdir)})) {
                                $curdir = 
$Slim::Utils::Scan::playlistCache{fileURLFromPath($curdir)}
                        } else {
                                $curdir = 
pathFromWinShortcut(fileURLFromPath($curdir));
                        }
                }
                #continue traversing if curdir is a list
                next if (Slim::Music::Info::isList(fileURLFromPath($curdir)));
                #otherwise stop traversing, non-list items cannot be traversed
                last;
        }
        
        $::d_paths && msg("became: $curdir\n");
        
        if (Slim::Music::Info::isFileURL($curdir)) {
                return $curdir;
        } else {
                return fileURLFromPath($curdir);  
        }
}

sub inPlaylistFolder {
        my $path = shift || return;

        # Fully qualify the path - and strip out any url prefix.
        $path = fixPath($path) || return 0;
        $path = virtualToAbsolute($path) || return 0;
        $path = pathFromFileURL($path) || return 0;

        my $playlistdir = Slim::Utils::Prefs::get("playlistdir");

        if ($playlistdir && $path =~ /^\Q$playlistdir\E/) {
                return 1;
        } else {
                return 0;
        }
}

my %_ignoredItems = (

        # always ignore . and ..
        '.' => 1,
        '..' => 1,

        # Items we should ignore on a mac volume
        'Icon' => 1,
        'TheVolumeSettingsFolder' => 1,
        'TheFindByContentFolder' => 1,
        'Network Trash Folder' => 1,
        'Desktop' => 1,
        'Desktop Folder' => 1,
        'Temporary Items' => 1,
        '.Trashes' => 1,
        '.AppleDB' => 1,
        '.AppleDouble' => 1,
        '.Metadata' => 1,
        '.DS_Store' => 1,

        # Items we should ignore on a linux vlume
        'lost+found' => 1,

        # Items we should ignore  on a Windows volume
        'System Volume Information' => 1,
        'RECYCLER' => 1,
        'Recycled' => 1,
);

sub readDirectory {
        my $dirname  = shift;
        my $validRE  = shift || Slim::Music::Info::validTypeExtensions();
        my @diritems = ();
        
        $::d_files && msg("reading directory: $dirname\n");

        if (!-d $dirname) { 
                $::d_files && msg("no such dir: $dirname\n");
                return @diritems;
        }

        my $ignore = Slim::Utils::Prefs::get('ignoreDirRE') || '';

        opendir(DIR, $dirname) || do {
                warn "opendir failed: " . $dirname . ": $!\n";
                return @diritems;
        };

        for my $item (readdir(DIR)) {

                next if exists $_ignoredItems{$item};

                # Ignore our special named files and directories
                next if $item =~ /^__/;  

                if ($ignore ne '') {
                        next if $item =~ /$ignore/;
                }

                my $fullpath = catdir($dirname, $item);

                # We only want files, directories and symlinks Bug #441
                # Otherwise we'll try and read them, and bad things will happen.
                # symlink must come first so an lstat() is done.
                unless (-l $fullpath || -d _ || -f _) {
                        next;
                }

                # Don't bother with file types we don't understand.
                if ($validRE && -f _) {
                        next unless $item =~ $validRE;
                }
                elsif ($validRE && -l _ && defined(my $target = 
readlink($fullpath))) {
                        # fix relative/absolute path
                        $target = ($target =~ /^\// ? $target : 
catdir($dirname, $target));

                        if (-f $target) {
                                next unless $target =~ $validRE;
                        }
                }

                push @diritems, $item;
        }

        closedir(DIR);
        
        $::d_files && msg("directory: $dirname contains " . scalar(@diritems) . 
" items\n");
        
        return sort(@diritems);
}

sub findAndScanDirectoryTree {
        my $levels   = shift;
        my $urlOrObj = shift || 
Slim::Utils::Misc::fileURLFromPath(Slim::Utils::Prefs::get('audiodir'));

        # Find the db entry that corresponds to the requested directory.
        # If we don't have one - that means we're starting out from the root 
audiodir.
        my $topLevelObj;
        my $ds = Slim::Music::Info::getCurrentDataStore();

        if (ref $urlOrObj) {

                $topLevelObj = $urlOrObj;

        } elsif (scalar @$levels) {

                $topLevelObj = $ds->objectForId('track', $levels->[-1]);

        } else {

                $topLevelObj = $ds->objectForUrl($urlOrObj, 1, 1, 1) || return;

                push @$levels, $topLevelObj->id;
        }

        if (!defined $topLevelObj || !ref $topLevelObj) {

                msg("Error: Couldn't find a topLevelObj for 
findAndScanDirectoryTree()\n");

                if (scalar @$levels) {
                        msgf("Passed in value was: [%s]\n", $levels->[-1]);
                } else {
                        msg("Starting from audiodir! Is it not set?\n");
                }

                return ();
        }

        # Check for changes - these can be adds or deletes.
        # Do a realtime scan - don't send anything to the scheduler.
        my $path    = $topLevelObj->path;
        my $fsMTime = (stat($path))[9] || 0;
        my $dbMTime = $topLevelObj->timestamp || 0;

        if ($fsMTime != $dbMTime) {

                if ($::d_scan) {
                        msg("mtime db: $dbMTime : " . localtime($dbMTime) . 
"\n");
                        msg("mtime fs: $fsMTime : " . localtime($fsMTime) . 
"\n");
                }

                # Update the mtime in the db.
                $topLevelObj->timestamp($fsMTime);

                # Do a quick directory scan.
                Slim::Utils::Scan::addToList([], $path, 0, undef, sub {});
        }

        # Now read the raw directory and return it. This should always be 
really fast.
        my $items = [ Slim::Music::Info::sortFilename( readDirectory( 
$topLevelObj->path ) ) ];
        my $count = scalar @$items;

        return ($topLevelObj, $items, $count);
}

# the following functions cleanup the date and time, specifically:
# remove the leading zeros for single digit dates and hours
# where a | is specified in the format

sub longDateF {
        my $time = shift || time();
        my $date = localeStrftime(Slim::Utils::Prefs::get('longdateFormat'), 
$time);
        $date =~ s/\|0*//;
        return $date;
}

sub shortDateF {
        my $time = shift || time();
        my $date = localeStrftime(Slim::Utils::Prefs::get('shortdateFormat'),  
$time);
        $date =~ s/\|0*//;
        return $date;
}

sub timeF {
        my $ltime = shift || time();
        my $time = localeStrftime(Slim::Utils::Prefs::get('timeFormat'),  
$ltime);
        # remove leading zero if another digit follows
        $time =~ s/\|0?(\d+)/$1/;
        return $time;
}

sub localeStrftime {
        my $format = shift;
        my $ltime = shift;
        
        (my $language = Slim::Utils::Prefs::get('language')) =~ tr/A-Z/a-z/;

        # we can't display japanese or chinese, etc right now.
        unless ($Slim::Player::Client::validClientLanguages{$language}) {
                $language = $Slim::Player::Client::failsafeLanguage;
        }

        (my $country = $language) =~ tr/a-z/A-Z/;

        # This is for when we can display japanese on the display.
        # We might want to consider changing s/JP/JA/ in strings.txt ?
        if ($language eq 'jp') {
                $language = 'ja';
        }
        
        my $serverlocale = $language . "_" . $country;

        my $saved_locale = setlocale(LC_TIME, $serverlocale);
        my $time = strftime $format, localtime($ltime);
        
        # XXX - we display in utf8 now
        # these strings may come back as utf8, make sure they are latin1 when 
we display them
        # $time = utf8toLatin1($time);
        
        setlocale(LC_TIME, "");
        return $time;
}

sub fracSecToMinSec {
        my $seconds = shift;

        my ($min, $sec, $frac, $fracrounded);

        $min = int($seconds/60);
        $sec = $seconds%60;
        $sec = "0$sec" if length($sec) < 2;
        
        # We want to round the last two decimals but we
        # always round down to avoid overshooting EOF on last track
        $fracrounded = int($seconds * 100) + 100;
        $frac = substr($fracrounded, -2, 2);
                                                                        
        return "$min:$sec.$frac";
}

# Utility functions for strings we send out to the world.
sub userAgentString {

        if (defined $userAgentString) {
                return $userAgentString;
        }

        my $osDetails = Slim::Utils::OSDetect::details();

        # We masquerade as iTunes for radio stations that really want it.
        $userAgentString = sprintf("iTunes/4.7.1 (%s; N; %s; %s; %s; %s) 
SlimServer/$::VERSION/$::REVISION",

                $osDetails->{'os'},
                $osDetails->{'osName'},
                ($osDetails->{'osArch'} || 'Unknown'),
                Slim::Utils::Prefs::get('language'),
                $locale,
        );

        return $userAgentString;
}

sub settingsDiagString {

        my $osDetails = Slim::Utils::OSDetect::details();

        # We masquerade as iTunes for radio stations that really want it.
        my $diagString = sprintf("%s%s %s - %s - %s - %s - %s",

                string('SERVER_VERSION'),
                string('COLON'),
                $::VERSION,
                $::REVISION,
                $osDetails->{'osName'},
                Slim::Utils::Prefs::get('language'),
                $Slim::Utils::Misc::locale,
        );

        return $diagString;
}

sub assert {
        my $exp = shift;
        my $msg = shift;
        
        defined($exp) && $exp && return;
        
        msg($msg) if $msg;
        
        bt();
}

sub bt {
        my $frame = 1;

        my $msg = "Backtrace:\n\n";

        my $assertfile = '';
        my $assertline = 0;

        while (my ($filename, $line, $subroutine) = (caller($frame++))[1,2,3]) {

                $msg .= sprintf("   frame %d: $subroutine ($filename line 
$line)\n", $frame - 2);

                if ($subroutine=~/assert$/) {
                        $assertfile = $filename;
                        $assertline = $line;                    
                }
        }
        
        if ($assertfile) {
                open SRC, $assertfile;
                my $line;
                my $line_n=0;
                $msg.="\nHere's the problem. $assertfile, line 
$assertline:\n\n";
                while ($line=<SRC>) {
                        $line_n++;
                        if (abs($assertline-$line_n) <=10) {
                                $msg.="$line_n\t$line";
                        }
                }
        }
        
        $msg.="\n";

        &msg($msg);
}

sub watchDog {
        if (!$::d_perf) {return;}
        
        my $lapse = shift;
        my $warn = shift;
        my $now = Time::HiRes::time();
        
        if (!defined($lapse)) { return $now; };
        
        my $delay = $now - $lapse;
        
        if (($delay) > 0.5) {
                msg("*****Watchpup: $warn took too long: $delay (now: $now)\n");
        }
        
        return $now;
}

sub msg {
        use bytes;

        my $entry = strftime "%Y-%m-%d %H:%M:%S.", localtime;
        my $now = int(Time::HiRes::time() * 10000);
        $entry .= (substr $now, -4) . " ";
        $entry .= shift;

        print STDERR $entry;
        
        if (Slim::Utils::Prefs::get('livelog')) {
                 $Slim::Utils::Misc::log .= $entry;
                 $Slim::Utils::Misc::log = substr($Slim::Utils::Misc::log, 
-Slim::Utils::Prefs::get('livelog'));
        }
}

sub msgf {
        my $format = shift;

        msg(sprintf($format, @_));
}

sub delimitThousands {
        my $len = shift || return 0; 

        my $sep = Slim::Utils::Strings::string('THOUSANDS_SEP');

        0 while $len =~ s/^(-?\d+)(\d{3})/$1$sep$2/;
        return $len;
}

# Check for allowed source IPs, called via CLI.pm and HTTP.pm
sub isAllowedHost {
        my $host = shift;
        my @rules = split /\,/, Slim::Utils::Prefs::get('allowedHosts');

        foreach my $item (@rules)
        {
                if ($item eq $host)
                {
                #If the host matches a specific IP, return valid
                        return 1;
                } else {
                        my @matched = (0,0,0,0);
                        
                        #Get each octet
                        my @allowedoctets = split /\./, $item;
                        my @hostoctets = split /\./, $host;
                        for (my $i = 0; $i < 4; ++$i)
                        {
                                $allowedoctets[$i] =~ s/\s+//g;
                                #if the octet is * or a specific match, pass 
octet match
                                if (($allowedoctets[$i] eq "*") || 
($allowedoctets[$i] eq $hostoctets[$i]))
                                {
                                        $matched[$i] = 1;
                                } elsif ($allowedoctets[$i] =~ /-/) {   #Look 
for a range formatted octet rule
                                        my ($low, $high) = split 
/-/,$allowedoctets[$i];
                                        if (($hostoctets[$i] >= $low) && 
($hostoctets[$i] <= $high))
                                        {
                                                #if it matches the range, pass 
octet match
                                                $matched[$i] = 1;
                                        }
                                } 
                        }
                        #check if all octets passed
                        if (($matched[0] eq '1') && ($matched[1] eq '1') &&
                            ($matched[2] eq '1') && ($matched[3] eq '1'))
                        {
                                return 1;
                        }
                }
        }
        
        # No rules matched, return invalid source
        return 0;
}

sub hostaddr {
        my @hostaddr = ();

        my @hostnames = ('localhost', hostname());
        
        foreach my $hostname (@hostnames) {

                next if !$hostname;

                if ($hostname =~ /^\d+(?:\.\d+(?:\.\d+(?:\.\d+)?)?)?$/) {
                        push @hostaddr, addrToHost($hostname);
                } else {
                        push @hostaddr, hostToAddr($hostname);
                }
        }

        return @hostaddr;
}

sub hostToAddr {
        my $host  = shift;
        my @addrs = (gethostbyname($host))[4];

        my $addr  = defined $addrs[0] ? inet_ntoa($addrs[0]) : $host;

        return $addr;
}

sub addrToHost {
        my $addr = shift;
        my $aton = inet_aton($addr);

        return $addr unless defined $aton;

        my $host = (gethostbyaddr($aton, Socket::AF_INET()))[0];

        return $host if defined $host;
        return $addr;
}

sub stillScanning {
        return Slim::Music::Import::stillScanning();
}

# this function based on a posting by Tom Christiansen: 
http://www.mail-archive.com/[email protected]/msg71350.html
sub at_eol($) { $_[0] =~ /\n\z/ }
sub sysreadline(*;$) { 
        my($handle, $maxnap) = @_;
        $handle = qualify_to_ref($handle, caller());

        return undef unless $handle;

        my $infinitely_patient = @_ == 1;

        my $start_time = Time::HiRes::time();

        # Try to use an existing IO::Select object if we have one.
        my $selector = ${*$handle}{'_sel'} || IO::Select->new($handle);

        my $line = '';
        my $result;

SLEEP:
        until (at_eol($line)) {

                unless ($infinitely_patient) {

                        if (Time::HiRes::time() > $start_time + $maxnap) {
                                return $line;
                        } 
                } 

                my @ready_handles;

                unless (@ready_handles = $selector->can_read(.1)) {  # seconds

                        unless ($infinitely_patient) {
                                my $time_left = $start_time + $maxnap - 
Time::HiRes::time();
                        } 

                        next SLEEP;
                }

INPUT_READY:
                while (() = $selector->can_read(0.0)) {

                        my $was_blocking = blocking($handle,0);

CHAR:
                        while ($result = sysread($handle, my $char, 1)) {
                                $line .= $char;
                                last CHAR if $char eq "\n";
                        } 

                        my $err = $!;

                        blocking($handle, $was_blocking);

                        unless (at_eol($line)) {

                                if (!defined($result) && $err != EWOULDBLOCK) { 
                                        return undef;                           
        
                                }
                                next SLEEP;
                        } 

                        last INPUT_READY;
                }
        } 

        return $line;
}

# Use Tie::Watch to keep track of a variable, and report when it changes.
sub watchVariable {
        my $var = shift;

        unless ($watch) {
                eval "use Tie::Watch";

                if ($@) {
                        return;
                } else {
                        $watch = 1;
                }
        }

        # See the Tie::Watch manpage for more info.
        Tie::Watch->new(
                -variable => $var,
                -shadow   => 0,

                -clear    => sub {
                        msg("In clear callback for $var!\n");
                        bt();
                },

                -destroy  => sub {
                        msg("In destroy callback for $var!\n");
                        bt();
                },

                -fetch   => sub {
                        my ($self, $key) = @_;

                        my $val  = $self->Fetch($key);
                        my $args = $self->Args(-fetch);

                        bt();
                        msgf("In fetch callback, key=$key, val=%s, 
args=('%s')\n",
                                $self->Say($val), ($args ? join("', '",  
@$args) : 'undef')
                        );

                        return $val;
                },

                -store    => sub {
                        my ($self, $key, $new_val) = @_;

                        my $val  = $self->Fetch($key);
                        my $args = $self->Args(-store);

                        $self->Store($key, $new_val);

                        bt();
                        msgf("In store callback, key=$key, val=%s, new_val=%s, 
args=('%s')\n",
                                $self->Say($val), $self->Say($new_val), ($args 
? join("', '",  @$args) : 'undef')
                        );

                        return $new_val;
                },
        );
}

sub deparseCoderef {
        my $coderef = shift;

        eval "use B::Deparse ()";
        my $deparse = B::Deparse->new('-si8T') unless $@ =~ /Can't locate/;

        eval "use Devel::Peek ()";
        my $peek = 1 unless $@ =~ /Can't locate/;

        return 0 unless $deparse;
                
        my $body = $deparse->coderef2text($coderef) || return 0;
        my $name;

        if ($peek) {
                my $gv = Devel::Peek::CvGV($coderef);
                $name  = join('::', *$gv{'PACKAGE'}, *$gv{'NAME'});
        }

        $name ||= 'ANON';

        return "sub $name $body";
}

1;
__END__

# Local Variables:
# tab-width:4
# indent-tabs-mode:t
# End:
_______________________________________________
beta mailing list
[email protected]
http://lists.slimdevices.com/lists/listinfo/beta

Reply via email to