package XXV::MODULES::MUSIC;
use strict;

use Tools;
use Locale::gettext;
use File::Basename;
use File::Path;

$SIG{CHLD} = 'IGNORE';

# This module method must exist for XXV
# ------------------
sub module {
# ------------------
    my $obj = shift || return error ('No Object!' );
    my $args = {
        Name => 'MUSIC',
        Prereq => {
            'DBI'          => 'Database independent interface for Perl ',
            'DBD::mysql'   => 'MySQL driver for the Perl5 Database Interface (DBI)',
            'MP3::Icecast' => 'Generate Icecast streams, as well as M3U and PLSv2 playlists',
            'MP3::Info'    => 'Manipulate / fetch info from MP3 audio files ',
            'CGI'          => 'Simple Common Gateway Interface Class',
            'LWP::Simple'  => 'get, head, getprint, getstore, mirror - Procedural LWP interface',
            'Net::Amazon'  => 'Framework for accessing amazon.com via SOAP and XML/HTTP',
            'Net::Amazon::Request::Artist' =>
                              'Class for submitting Artist requests',
        },
        Description => gettext('This module managed music files.'),
        Version => '0.45',
        Date => '04.09.2005',
        Author => 'xpix',
        Status => sub{ $obj->status(@_) },
        Preferences => {
            active => {
                description => gettext('Activate this service'),
                default     => 'y',
                type        => 'confirm',
                required    => gettext('This is required!'),
            },
            path => {
                description => gettext('Directory with the music files'),
                default     => '/music',
                type        => 'dir',
                required    => gettext('This is required!'),
            },
            port => {
                description => gettext('Port to listen for icecast clients.'),
                default     => 8100,
                type        => 'integer',
                required    => gettext('This is required!'),
            },
            proxy => {
                description => gettext('Proxy URL to music server. e.g. (http://vdr/xxv) Please remember you must write the Port to icecast server in your Proxy Konfiguration!'),
                default     => '',
                type        => 'string',
            },
            clients => {
                description => gettext('Maximum Clients to connect at the same time.'),
                default     => 5,
                type        => 'integer',
                required    => gettext('This is required!'),
            },
            coverimages => {
                description => gettext('common directory for cover images'),
                default     => '/var/cache/xxv/cover',
                type        => 'dir',
                required    => gettext('This is required!'),
            },
            muggle => {
                description => gettext('DSN for muggle Database'),
                default     => 'DBI:mysql:database=GiantDisc;host=localhost;port=3306',
                type        => 'string',
                check       => sub{
                    my $value = shift;
                    $obj->{mdbh} = $obj->ConnectToMuggleDB($value);
                    return $value;
                },
            },
            mugglei => {
                description => gettext('Path to the binary from mugglei.'),
                default     => '/usr/local/bin/mugglei',
                type        => 'file',
            },
        },
        Commands => {
            mrefresh => {
                description => gettext('Rereading of the music directory.'),
                short       => 'mr',
                callback    => sub{ $obj->refresh(@_) },
                DenyClass   => 'mlist',
            },
            mcovers => {
                description => gettext('Download album covers.'),
                short       => 'mc',
                callback    => sub{ $obj->getcovers(@_) },
                DenyClass   => 'mlist',
            },
            mplay => {
                description => gettext("play music file 'fid'"),
                short       => 'mp',
                callback    => sub{ $obj->play(@_) },
                DenyClass   => 'mlist',
            },
            mplaylist => {
                description => gettext("get a m3u playlist for 'fid'"),
                short       => 'm3',
                callback    => sub{ $obj->playlist(@_) },
                DenyClass   => 'mlist',
            },
            mlist => {
                description => gettext("list music 'dir'"),
                short       => 'ml',
                callback    => sub{ $obj->list(@_) },
                DenyClass   => 'mlist',
            },
            msearch => {
                description => gettext("search music 'txt'"),
                short       => 'mf',
                callback    => sub{ $obj->search(@_) },
                DenyClass   => 'mlist',
            },
        },
    };
    return $args;
}

# ------------------
sub new {
# ------------------
	my($class, %attr) = @_;
	my $self = {};
	bless($self, $class);

    # paths
    $self->{paths} = delete $attr{'-paths'};

	# who am I
    $self->{MOD} = $self->module;

    # all configvalues to $self without parents (important for ConfigModule)
    map {
        $self->{$_} = $attr{'-config'}->{$self->{MOD}->{Name}}->{$_};
        $self->{$_} = $self->{MOD}->{Preferences}->{$_}->{default} unless($self->{$_});
    } keys %{$self->{MOD}->{Preferences}};

    # Try to use the Requirments
    map {
        eval "use $_";
        return panic("\nCan not load Module: $_\nPlease install this module on your System:\nperl -MCPAN -e 'install $_'") if($@);
    } keys %{$self->{MOD}->{Prereq}};

    # read the DB Handle
    $self->{dbh} = delete $attr{'-dbh'};

    # The Initprocess
    my $erg = $self->_init or return error('Problem to initialze Module');

	return $self;
}

# ------------------
sub _init {
# ------------------
    my $obj = shift || return error ('No Object!' );

    $obj->{Amazon} = Net::Amazon->new(
        token       => '1CCSPM94SQW5RNWY6682',
    );

    $obj->{mdbh} = $obj->ConnectToMuggleDB($obj->{muggle});

    #create an instance to find all files below /usr/local/mp3
    $obj->{ICE} = MP3::Icecast->new();
    $obj->{ICE}->recursive(1);
    $obj->{ICE}->add_directory($obj->{path});

    $obj->{SOCK} = IO::Socket::INET->new(
        LocalPort => $obj->{port}, #standard Icecast port
        Listen    => $obj->{clients},
        Proto     => 'tcp',
        Reuse     => 1,
        Timeout   => 3600
    );

    my $channels;

    Event->io(
        fd => $obj->{SOCK},
        prio => -1,  # -1 very hard ... 6 very low
        cb => sub {
            # accept client
            my $client = $obj->{SOCK}->accept;
            panic "Can't connect to new icecast client." and return unless $client;
            $client->autoflush;

            # make "channel" number
            my $channel=++$channels;

            # install a communicator
            Event->io(
                fd => $client,
                prio => -1,  # -1 very hard ... 6 very low
                poll => 'r',
                cb => sub {
                    my $watcher = shift;
                    # report
                    lg(sprintf("Talking on icecast channel %d", $channel));

                    # read new line and report it
                    my $handle=$watcher->w->fd;
                    my $data = $obj->parseRequest($handle);
                    my $files = $obj->handleInput($data);
                    unless(ref $files eq 'ARRAY') {
                        $watcher->w->cancel;
                        $client->close();
                        undef $watcher;
                        return 1;
                    }

                    $obj->stream($files, $client);

                    $watcher->w->cancel;
                    undef $watcher;
                    $client->close;
                },
            );

            # report
            lg(sprintf("Open new icecast channel %d", $channel));
        },
    ) if($obj->{active} eq 'y');


    # Look for table or create this table
    my $version = main::getVersion;

    unless($obj->{mdbh}) {
        $obj->{dbh}->do(qq|
          CREATE TABLE IF NOT EXISTS MUSIC (
              Id int(11) unsigned auto_increment NOT NULL,
              FILE text NOT NULL,
              ARTIST varchar(128) default 'unknown',
              ALBUM varchar(128) default 'unknown',
              TITLE varchar(128) default 'unknown',
              COMMENT varchar(128),
              TRACKNUM varchar(10) default '0',
              YEAR tinyint(4),
              GENRE varchar(128),
              BITRATE tinyint(4),
              FREQUENCY varchar(4),
              SECS int (11) NOT NULL,
              PRIMARY KEY  (ID)
            ) COMMENT = '$version'
        |);

        $obj->{fields} = fields($obj->{dbh}, 'select * from MUSIC');

        # Read File to Database, if the DB empty and Musicdir exists
        $obj->refresh()
            unless($obj->{dbh}->selectall_arrayref("select count(*) from MUSIC")->[0][0]);
    }

    return 1;

}

# ------------------
sub refresh {
# ------------------
    my $obj = shift || return error ('No Object!' );
    my $watcher = shift;
    my $console = shift;

    my $dbh = ($obj->{mdbh} ? $obj->{mdbh} : $obj->{dbh});

    if( ref $console and not -d $obj->{path} ) {
        my $errmsg = sprintf(gettext("Directory of the music files '%s' not found"), $obj->{path});
        error($errmsg);
        $console->err($errmsg);
        $console->link({
            text => gettext("Back to music list"),
            url => "?cmd=mlist",
        }) if($console->typ eq 'HTML');
        return;
    }

    if($obj->{mugglei} and $obj->{mdbh}) {
        my $usr = main::getGeneralConfig->{USR};
        my $pwd = main::getGeneralConfig->{PWD};
        my $host = (split(/ /, $dbh->{'mysql_hostinfo'}))[0];
        # /usr/local/bin/mugglei -h 127.0.0.1 -c -u xpix -w xpix97 -t /NAS/Music .
        my $command = sprintf('%s -h %s -c -u %s -w %s -t %s . 2>&1',
            $obj->{mugglei}, lc($host), $usr, $pwd, $obj->{path});
        chdir($obj->{path});
        my @erg = (`$command`);

        if( ref $console) {
            $console->message(gettext("Reread the music files ..."));
            $console->link({
                text => gettext("Back to music list"),
                url => "?cmd=mlist",
            }) if($console->typ eq 'HTML');
        }
        return 1;
    }

    my $waiter;
    # Show waiter, early as is possible
    if(ref $console && $console->typ eq 'HTML') {
        $waiter = $console->wait(gettext("Get information from music files ..."), 0, 1000, 'no');
    }

    lg('Please wait! I search for new Musicfiles!');

    #create an instance to find all files below /usr/local/mp3
    $obj->{ICE} = MP3::Icecast->new();
    $obj->{ICE}->recursive(1);
    $obj->{ICE}->add_directory($obj->{path});

    $obj->{CACHE} = {};

    my $data = $dbh->selectall_hashref("select ID, FILE from MUSIC", 'FILE');
    my @files = $obj->{ICE}->files;

    lg sprintf('Found %d music files !', scalar @files);

    return unless(scalar @files);

    if( ref $console and not scalar @files ) {

        # last call of waiter
        $waiter->end() if(ref $waiter);

        $console->start() if(ref $waiter);

        $console->err(gettext("No music files found!"));
        $console->link({
            text => gettext("Back to music list"),
            url => "?cmd=mlist",
        }) if($console->typ eq 'HTML');

        return;
    }

    # Adjust waiter max value now.
    $waiter->max(scalar @files)
        if(ref $waiter);

    my $c = 0;
    my $new = 0;
    foreach my $file (@files) {
        ++$c;
        $waiter->next($c)
            if(ref $waiter);
        next if(delete $data->{$file});
        my $info = MP3::Info->new($file);
        $new++
            if($obj->insert($info));
    }

    foreach my $f (sort keys %$data) {
        unless(-e $f) {
            $dbh->do(sprintf('DELETE FROM MUSIC WHERE ID = %lu', $data->{$f}->{ID}));
        }
    }

    # last call of waiter
    $waiter->end() if(ref $waiter);

    if(ref $console) {
        $console->start()
            if(ref $waiter);
        my $msg = sprintf(gettext("%d new music files in database saved and %d non exists entries deleted!"), $new, scalar keys %$data);
        $console->message($msg);
        lg $msg;
        $console->link({
            text => gettext("Back to music list"),
            url => "?cmd=mlist",
        }) if($console->typ eq 'HTML');
    }
}

# ------------------
sub play {
# ------------------
    my $obj = shift || return error ('No Object!' );
    my $watcher = shift || return error ('No Watcher!');
    my $console = shift || return error ('No Console');
    my $data = shift || return error ('No data');

    lg sprintf('Call play from User %s',
        $console->{USER}->{Name}
        );

    $console->player("?cmd=mplaylist&data=${data}&binary=1");
}

# ------------------
sub playlist {
# ------------------
    my $obj = shift || return error ('No Object!' );
    my $watcher = shift || return error ('No Watcher!');
    my $console = shift || return error ('No Console');
    my $data = shift || return error ('No data');

    my $dbh = ($obj->{mdbh} ? $obj->{mdbh} : $obj->{dbh});

    my $host = main::getModule('STREAM')->{host} || main::getModule('STATUS')->IP;
    my $output = "#EXTM3U\r\n";

    foreach my $id (split('_', $data)) {
        my $data;
        if($obj->{mdbh}) {
            $data = $dbh->selectrow_hashref("select * from tracks where id = '$id'");
        } else {
            $data = $dbh->selectrow_hashref("select * from MUSIC where ID = '$id'");
        }
        my $file;
        if($obj->{proxy}) {
            $file = sprintf('%s/?cmd=play&data=%s&field=id', $obj->{proxy}, $id);
        } else {
            $file = sprintf('http://%s:%lu/?cmd=play&data=%s&field=%s', $host, $obj->{port}, $id, ($obj->{mdbh} ? 'id' : 'ID'));
        }
        if($obj->{mdbh}) {
            $output .= sprintf("#EXTINF:%d,%s - %s (%s)\r\n",$data->{'length'},$data->{title},$data->{artist},$data->{sourceid});
        } else {
            $output .= sprintf("#EXTINF:%d,%s - %s (%s)\r\n",$data->{SECS},$data->{TITLE},$data->{ARTIST},$data->{ALBUM});
        }
        $output .= sprintf("%s\r\n", $file);
    }
    #dumper($output);
    if($console->typ eq 'HTML') {
        $console->{noFooter} = 1;
        $console->{nopack} = 1;
        $console->out($output, "audio/x-mpegurl");
    }
}

# ------------------
sub search {
# ------------------
    my $obj = shift || return error ('No Object!' );
    my $watcher = shift || return error ('No Watcher!');
    my $console = shift || return error ('No Console');
    my $text   = shift || return $console->message(gettext("No Text to search! Please use msearch 'text'"));

    return $obj->list($watcher,$console,"search:".$text);
}

# ------------------
sub list {
# ------------------
    my $obj = shift || return error ('No Object!' );
    my $watcher = shift || return error ('No Watcher!');
    my $console = shift || return error ('No Console');
    my $search  = shift;

    my $dbh = ($obj->{mdbh} ? $obj->{mdbh} : $obj->{dbh});

    # Genres cachen
    $obj->{GENRES} = $dbh->selectall_hashref('select * from genre', 'id')
        if($obj->{mdbh});

    if($obj->{mdbh} && ! $search) {
        my $eg = $dbh->selectrow_arrayref('select title from album limit 1')
            || return error($obj->{mdbh}->errstr);
        $search = sprintf('title:%s', $eg->[0]);
    } elsif(! $search) {
        $search = sprintf('album:%s', $dbh->selectrow_arrayref('select ALBUM from MUSIC limit 1')->[0]);
    }

    my @field = split(':',$search);
    my $typ = $field[0];

    # Muggleübersetzer ;)
    my $translate = {
        artist  => 'artist',
        album   => 'title',
        genre   => 'genre1',
        title   => 'title',
        year   => 'year',
    };

    shift @field;
    my $text = join(':',@field);

    my $t;
    if($typ eq 'genre') {
        $t = ($obj->{mdbh} ? 'tracks.'.$translate->{$typ} : uc($typ));
        $text = $obj->{GENRES}->{$text}->{id};
    } elsif($typ eq 'year') {
        $t = ($obj->{muggle} ? 'tracks.'.$translate->{$typ} : uc($typ));
    } else {
        $t = ($obj->{mdbh} ? 'album.'.$translate->{$typ} : uc($typ));
    }

    my $where;
    if($typ eq 'search') {
        if($obj->{mdbh}) {
            $where = buildsearch("album.artist,album.title,tracks.title",$text);
        } else {
            $where = buildsearch("ALBUM,ARTIST,TITLE,COMMENT",$text);
        }
    } else {
        $text =~ s/\'/\\'/sg;#'
        $where = sprintf("%s LIKE '%%%s%%'", $t, $text);
    }

    my %f = (
        'Id' => umlaute(gettext('Sv')),
        'Artist' => umlaute(gettext('Artist')),
        'Album' => umlaute(gettext('Album')),
        'Title' => umlaute(gettext('Title')),
        'Tracknum' => umlaute(gettext('Tracknum')),
        'Year' => umlaute(gettext('Year')),
        'Length' => umlaute(gettext('Length')),
    );

    my $sql;
    if($obj->{mdbh}) {

        $sql = qq|
        SELECT
        	tracks.id as $f{'Id'},
        	tracks.artist as $f{'Artist'},
        	album.title as $f{'Album'},
        	tracks.title as $f{'Title'},
        	tracks.tracknb as $f{'Tracknum'},
        	tracks.year as $f{'Year'},
            DATE_FORMAT(FROM_UNIXTIME(tracks.length), '%i:%s') as $f{'Length'},
        	album.covertxt as __COMMENT
        FROM
        	tracks, album
        WHERE
            tracks.sourceid = album.cddbid and
        	( $where )
        ORDER BY
                tracks.sourceid,
                tracks.tracknb
        |;

    } else {

        $sql = qq|
        SELECT
        	ID as $f{'Id'},
        	ARTIST as $f{'Artist'},
        	ALBUM as $f{'Album'},
        	TITLE as $f{'Title'},
        	TRACKNUM as $f{'Tracknum'},
        	YEAR as $f{'Year'},
            IF(SECS >= 3600,SEC_TO_TIME(SECS),DATE_FORMAT(FROM_UNIXTIME(SECS), '%i:%s')) as $f{'Length'},
        	GENRE as __GENRE,
        	COMMENT as __COMMENT
        FROM
        	MUSIC
        WHERE
        	( $where )
        ORDER BY
        	FILE
        |;
    }

    my $fields = fields($dbh, $sql);
    my $erg = $dbh->selectall_arrayref($sql);

    unshift(@$erg, $fields);

    my $params = {
        albums =>  ($obj->{mdbh} ? $obj->GroupArray('title', 'album', 'cddbid') : $obj->GroupArray('ALBUM')),
        artists => ($obj->{mdbh} ? $obj->GroupArray('artist', 'album', 'cddbid'): $obj->GroupArray('ARTIST')),
        genres =>  $obj->GenreArray(),
        getCover => sub{ return $obj->getCoverFile(@_, 'relative') },
        proxy => $obj->{proxy},
    };

    $console->table($erg, $params);
}

# ------------------
sub handleInput {
# ------------------
    my $obj     = shift || return error ('No Object!' );
    my $data    = shift || return error ('No Request!' );
    my $cgi = CGI->new( $data->{Query} );

    my $ucmd = $cgi->param('cmd')   || 'play';
    my $ufield = $cgi->param('field') || ($obj->{mdbh} ? 'id' : 'ID');
    my $udata = $cgi->param('data') || '*';

    my $files;
    if($ucmd eq 'play' and $ufield and my @search = split(',',$udata)) {
        $files = $obj->field2path($ufield, \@search);
    } else {
        return error "I don't understand this command '$ucmd'";
    }
    return $files;
}

# ------------------
sub field2path {
# ------------------
    my $obj = shift || return error ('No Object!' );
    my $field = shift || return error ('No Field!' );
    my $data = shift || return error ('No ids!' );

    my $dbh = ($obj->{mdbh} ? $obj->{mdbh} : $obj->{dbh});

    my $pathfield = ($obj->{mdbh} ? 'mp3file' : 'FILE');

    map {$_ = $dbh->quote($_)} @$data;
    my $sql = sprintf "select %s, %s from tracks", $pathfield, $field;
    $sql .= sprintf " where %s in (%s)", $field, join(',', @$data)
        if($data->[0] ne '*');

    my $ret = $dbh->selectall_hashref($sql, $pathfield);
    my @files = sort keys %$ret;
    return \@files;
}

# ------------------
sub insert {
# ------------------
    my $obj = shift || return error ('No Object!' );
    my $data = shift || return 0;

    my @setdata;
    foreach my $name (keys %$data) {
        next unless(grep($name eq $_, @{$obj->{fields}}));
        push(@setdata, sprintf("%s=%s", $name, $obj->{dbh}->quote($data->{$name})));
    }

    # MD5(File) as ID
    my $sql = sprintf('INSERT INTO MUSIC SET %s', join(', ', @setdata));
    # dumper($sql);
    $obj->{dbh}->do( $sql );
    return 1;
}

# ------------------
sub stream {
# ------------------
    my $obj = shift || return error ('No Object!' );
    my $files = shift || return error ('No Files!' );
    my $client = shift || return error ('No Client!' );

    my %seen = ();
    my @uniqu = grep { ! $seen{$_} ++ } @$files;

    defined(my $child = fork()) or die "Can't fork: $!";
    if($child == 0) {
        $obj->{SOCK}->close;
        $obj->{dbh}->{InactiveDestroy} = 1;

        foreach my $file (@uniqu) {
            debug('Stream file "%s" to Client: %s',
                $file,$client);
            my $erg = $obj->{ICE}->stream($file,0,$client)
                || last;
        }
        exit 0;
    }
}

# ------------------
sub parseRequest {
# ------------------
    my $obj = shift || return error ('No Object!' );
    my $hdl = shift || return error ('No Request!' );

    my ($Req, $size) = getFromSocket($hdl);

	if(ref $Req eq 'ARRAY' and $Req->[0] =~ /^GET (\/[\w\.\/-\:]*)([\?[\w=&\.\+\%-\:\!]*]*)[\#\d ]+HTTP\/1.\d$/) {
        my $data = {};
		($data->{Request}, $data->{Query}) = ($1, $2 ? substr($2, 1, length($2)) : undef);

    	# parse header
    	foreach my $line (@$Req) {
    		if($line =~ /Referer: (.*)/) {
    			$data->{Referer} = $1;
    		}
    		if($line =~ /Host: (.*)/) {
    			$data->{HOST} = $1;
    		}
    		if($line =~ /Authorization: basic (.*)/i) {
    			($data->{username}, $data->{password}) = split(":", MIME::Base64::decode_base64($1), 2);
    		}
    		if($line =~ /User-Agent: (.*)/i) {
    			$data->{http_useragent} = $1;
    		}
    	}

lg sprintf(qq|
-----------------------------------------
New Request from User: %s at Host: %s!
Query: %s
-----------------------------------------
|, $data->{username}, $data->{HOST}, $data->{Query});

        return $data;
	} else {

error sprintf(qq|
-----------------------------------------
Unknown Request :
%s
-----------------------------------------
|, join("\n", @$Req));

		return;
	}
}

# ------------------
sub GroupArray {
# ------------------
    my $obj = shift || return error ('No Object!' );
    my $field = shift || return undef;
    my $table = shift;
    my $idfield = shift;

    my $dbh = ($obj->{mdbh} ? $obj->{mdbh} : $obj->{dbh});

    my $sql;
    if($obj->{mdbh}) {
        $sql = sprintf('select %s, %s from %s group by %s order by %s', $field, $idfield, $table, $field, $field);
    } else {
        $sql = sprintf('select %s, ID from MUSIC group by %s order by %s', $field, $field, $field);
    }
    my $erg = $dbh->selectall_arrayref($sql);

    return $erg;
}

# ------------------
sub GenreArray {
# ------------------
    my $obj = shift || return error ('No Object!' );

    my $dbh = ($obj->{mdbh} ? $obj->{mdbh} : $obj->{dbh});

    my $sql;
    if($obj->{mdbh}) {
        $sql = "select genre, genre.id as id from genre,tracks where genre.id = tracks.genre1 group by id order by id";
    } else {
        my $field = 'genre';
        $sql = sprintf('select %s, ID from MUSIC group by %s order by %s', $field, $field, $field);
    }
    my $erg = $dbh->selectall_arrayref($sql);

    return $erg;
}

# ------------------
sub status {
# ------------------
    my $obj = shift || return error ('No Object!' );
    my $watcher = shift;
    my $console = shift;
    my $lastReportTime = shift || 0;

    my $report = {};
    if($obj->{mdbh}) {
        $report->{FILE} = $obj->{mdbh}->selectrow_arrayref('select count(*) from tracks')->[0];
        $report->{ALBUM} = $obj->{mdbh}->selectrow_arrayref('select count(*) from album')->[0];
        my $d = $obj->{mdbh}->selectall_arrayref('select artist from tracks group by artist');
        $report->{ARTIST} = scalar @$d;
        $report->{GENRE} = $obj->{mdbh}->selectrow_arrayref('select count(*) from genre')->[0];
    } else {
        foreach my $field (qw/FILE ALBUM ARTIST GENRE/) {
            my $data = $obj->GroupArray($field);
            $report->{$field} = scalar @$data;
        }
    }


    return {
        message => sprintf(gettext('Music database contains %d entries with %d albums from %d artists in %d genres'),
            $report->{FILE}, $report->{ALBUM},$report->{ARTIST}, $report->{GENRE}),
    };
}

# ------------------
sub getcovers {
# ------------------
    my $obj = shift || return error ('No Object!' );
    my $watcher = shift;
    my $console = shift;
    my $force = shift;

    my $dbh = ($obj->{mdbh} ? $obj->{mdbh} : $obj->{dbh});

    return error('No valid Amazon token exists. Please sign up at http://amazon.com/soap!')
        unless($obj->{Amazon});

    lg sprintf('Call getcovers from User: %s',
            $console->{USER}->{Name}
        );

    my $waiter = $console->wait(gettext("Please wait, search for new covers ..."),0,1000,'no')
        if(ref $console);

    unless(-d $obj->{coverimages}) {
        mkpath($obj->{coverimages}) or error "Can't mkpath $obj->{coverimages} : $!";
        lg sprintf('mkdir path "%s" from User: %s',
                $obj->{coverimages},
                $console->{USER}->{Name}
            );
    }

    my $rob = main::getModule('ROBOT')
        or return error('No ROBOT Module installed!');

    $rob->saveRobot('coverimage', sub{
        my $artist = shift || return;
        my $album = shift || return;
        my $year = shift || 0;
        my $target = shift || return;
        my $current = shift || 0;

        # Anzeige der ProcessBar
        $waiter->next($current) if(ref $waiter);

        my $req = Net::Amazon::Request::Artist->new(
            artist  => $artist,
        );
        my $resp = $obj->{Amazon}->request($req);

        foreach my $item ($resp->properties) {
            if($item->album() =~ /$album/i or
                $item->year() == $year) {
                    getstore($item->ImageUrlMedium(), $target);
                    last;
                }
        }

        return 1;
    });

    my $erg;
    if($obj->{mdbh}) {
        $erg = $dbh->selectall_hashref('select DISTINCT t.id as ID,a.artist as ARTIST, a.title as ALBUM, t.year as YEAR from album as a, tracks as t where a.cddbid = t.sourceid group by a.title', 'ID');
    } else {
        $erg = $dbh->selectall_hashref('select DISTINCT Id as ID, ARTIST, ALBUM, YEAR from MUSIC group by ALBUM', 'ID');
    }

    my $current = 0;
    foreach my $id (sort keys %$erg) {
        my $e = $erg->{$id};
        my $target = $obj->getCoverFile($e->{ALBUM},$e->{ARTIST});

        next if(-e $target and not $force);

        $rob->register('coverimage', $e->{ARTIST}, $e->{ALBUM}, $e->{YEAR}, $target, ++$current);
    }

    # Adjust waiter max value now.
    $waiter->max($current || 1)
        if(ref $waiter);

    if(ref $waiter and $current) {
        $waiter->endcallback(
            sub{
                if(ref $console) {
                    $console->start();
                    $console->message(my $msg = gettext("New covers search was successfully!"));
                    lg sprintf($msg);

                    $console->link({
                        text => gettext("Back to music list"),
                        url => "?cmd=mlist",
                    }) if($console->typ eq 'HTML');
                    $console->footer();
                }
            }
        );
    }

    if(ref $waiter and not $current) {
        $waiter->endcallback(
            sub{
                if(ref $console) {
                    $console->start();
                    $console->message(gettext("It is not necessary to look for new covers because already all albums possess cover!"));

                    $console->link({
                        text => gettext("Back to music list"),
                        url => "?cmd=mlist",
                    }) if($console->typ eq 'HTML');
                    $console->footer();
                }
            }
        );
        lg sprintf('All covers exists!');
    }

    # Start Robots
    $rob->start( 'coverimage', $watcher, $console, sub{ $waiter->end if(ref $waiter and $current); } );

    return $erg;
}

# ------------------
sub getCoverFile {
# ------------------
    my $obj = shift  || return error ('No Object!' );
    my $album = shift || return error ('No Album!' );
    my $artist = shift || 0;
    my $typ = shift || 'absolute';

    my $absolute;
    my $relative;

    if($artist) {
        $absolute = sprintf('%s/%s-%s.jpg', $obj->{coverimages}, $obj->unique($artist), $obj->unique($album));
        $relative = sprintf('/coverimages/%s-%s.jpg', $obj->unique($artist), $obj->unique($album));
    } else {
        $absolute = sprintf('%s/%s.jpg', $obj->{coverimages}, $obj->unique($album));
        $relative = sprintf('/coverimages/%s.jpg', $obj->unique($album));
    }
    return $absolute
        if($typ eq 'absolute');
    return $relative
        if(-r $absolute);

    lg sprintf("Don't find cover for %s - %s, as file %s",$artist,$album,$absolute);
    return undef;
}

# ------------------
sub unique {
# ------------------
    my $obj = shift  || return error ('No Object!' );
    my $text = shift || return '';

    $text =~ s/[^0-9a-z]//sig;
    return $text;
}

# ------------------
sub ConnectToMuggleDB {
# ------------------
    my $obj = shift  || return error ('No Object!' );
    my $dsn = shift  || return 0;

    #try to connect to muggle database
    if($dsn) {
        my $usr = main::getGeneralConfig->{USR};
        my $pwd = main::getGeneralConfig->{PWD};

        my $mdbh = DBI->connect(
                $dsn, $usr, $pwd,
                {   PrintError => 1,
                    AutoCommit => 1,
                }) || error($DBI::errstr);
        if($mdbh) {
            $mdbh->{InactiveDestroy} = 1;
            $mdbh->{mysql_auto_reconnect} = 1;
            debug('Successfully connect to: %s', $dsn);
            return $mdbh;
        } else {
            debug('No GiantDisc DB. Use standart DB!');
            return 0;
        }
    } else {
        return 0;
    }
}


1;
