From d26fa8c1e0886c813de5364ee6570c860c6ce12c Mon Sep 17 00:00:00 2001 From: Othmar Gsenger Date: Thu, 31 Mar 2011 19:05:23 +0000 Subject: hopfully repaired this after backup loss --- cancel.pl | 36 ------------- config.pm.example | 4 +- cron.sh | 5 ++ cron/clean_database.pl | 11 ++++ cron/read_filesize_from_filesystem.pl | 16 ++++++ cron/regen_quota.pl | 17 +++++++ enqueue.pl | 40 --------------- examples/cron.d-nzbget | 12 +++++ groups.pl | 77 ---------------------------- history.pl | 32 ------------ local/db.pm | 2 +- local/menu.pm | 38 ++++++++++++-- local/nzbget.pm | 24 +++++++-- local/nzbindex.pm | 26 ++++++++-- log.pl | 31 ------------ mark_completed.pl | 8 --- menu.pl | 10 ---- mod_perl_startup.pl | 6 +++ mydl.pl | 37 -------------- nzbget/cancel.pm | 43 ++++++++++++++++ nzbget/download.pm | 95 +++++++++++++++++++++++++++++++++++ nzbget/enqueue.pm | 59 ++++++++++++++++++++++ nzbget/list.pm | 49 ++++++++++++++++++ nzbget/log.pm | 29 +++++++++++ nzbget/main.pm | 56 +++++++++++++++++++++ nzbget/menu.pm | 15 ++++++ nzbget/pre_enqueue.pm | 36 +++++++++++++ nzbget/read.pm | 26 ++++++++++ nzbget/remove.pm | 31 ++++++++++++ nzbget/search.pm | 56 +++++++++++++++++++++ nzbget/search_upload.pm | 37 ++++++++++++++ pre_enqueue.pl | 30 ----------- process.pl | 27 ++++++++++ process.sh | 4 ++ read.pl | 18 ------- remove.pl | 25 --------- search.pl | 51 ------------------- status.pl | 23 --------- styles/0xff.css | 35 ++++++++++++- 39 files changed, 744 insertions(+), 433 deletions(-) delete mode 100755 cancel.pl create mode 100755 cron.sh create mode 100755 cron/clean_database.pl create mode 100755 cron/read_filesize_from_filesystem.pl create mode 100755 cron/regen_quota.pl delete mode 100755 enqueue.pl create mode 100644 examples/cron.d-nzbget delete mode 100755 groups.pl delete mode 100755 history.pl delete mode 100755 log.pl delete mode 100755 mark_completed.pl delete mode 100755 menu.pl create mode 100755 mod_perl_startup.pl delete mode 100755 mydl.pl create mode 100644 nzbget/cancel.pm create mode 100644 nzbget/download.pm create mode 100644 nzbget/enqueue.pm create mode 100644 nzbget/list.pm create mode 100644 nzbget/log.pm create mode 100644 nzbget/main.pm create mode 100644 nzbget/menu.pm create mode 100644 nzbget/pre_enqueue.pm create mode 100644 nzbget/read.pm create mode 100644 nzbget/remove.pm create mode 100644 nzbget/search.pm create mode 100644 nzbget/search_upload.pm delete mode 100755 pre_enqueue.pl create mode 100755 process.pl create mode 100755 process.sh delete mode 100755 read.pl delete mode 100755 remove.pl delete mode 100755 search.pl delete mode 100755 status.pl diff --git a/cancel.pl b/cancel.pl deleted file mode 100755 index 613612e..0000000 --- a/cancel.pl +++ /dev/null @@ -1,36 +0,0 @@ -#!/usr/bin/perl - -use strict; -use CGI qw(:standard); -use CGI::Carp 'fatalsToBrowser'; -use local::nzbget; -use local::db; -use local::user; -use File::Path; -use config; - -my $user = new local::user(%ENV); -my $del_id = param('id'); - -my ($download) = local::db::download->retrieve($del_id) or die 'Error in cancel'; -die "Not allowed" if not $download->owner->owner_id() eq $user->get_id(); -my $basedir = $config::config{files_dir} or die 'Missing files_dir in config'; -my $cli = new local::nzbget; -my $row = $cli->send_request('listgroups') or die "Can't connect to nubget"; -foreach my $value ( @$row) -{ - my ($dl) = local::db::download->retrieve($value->{NZBNicename}->value) or die ' Error in groups'; - if ($value->{NZBNicename}->value == $del_id) - { - my $status = $cli->send_request('editqueue','GroupDelete',0,"",[$value->{LastID}->value]) or die "Can't connect to nzbget"; - sleep 1; - File::Path::rmtree($basedir.'/'.$dl->category->name.'/'.$dl->download_id); - $user->obj->quota_used($user->obj->quota_used - $dl->size); - $user->obj->update; - my @seens = local::db::seen->search(download=>$del_id); - map {$_->delete()} @seens; - $dl->delete(); - } -} - -print CGI::redirect('/cgi-bin/nzbget/groups.pl'); diff --git a/config.pm.example b/config.pm.example index e8abd09..4764dd5 100644 --- a/config.pm.example +++ b/config.pm.example @@ -2,7 +2,9 @@ package config; our %config = ( db => ['dbi:SQLite:/home/nzbget/datenbank.sqlite', '', ''], - page_name => 'anymur binary usenet', + page_name => 'anymur binary usenet', files_dir => '/media/wechsel/otti/done/usenet', web_dir => '/done/usenet', + rpc_uri => 'http://nzbget:tegbzn6789@127.0.0.1:6789/xmlrpc', + process => '/etc/nzbget/postprocess-example.sh', ); diff --git a/cron.sh b/cron.sh new file mode 100755 index 0000000..c29d228 --- /dev/null +++ b/cron.sh @@ -0,0 +1,5 @@ +#!/bin/bash +export MOD_PATH=`dirname $0` +perl -I $MOD_PATH $MOD_PATH/cron/clean_database.pl +perl -I $MOD_PATH $MOD_PATH/cron/read_filesize_from_filesystem.pl +perl -I $MOD_PATH $MOD_PATH/cron/regen_quota.pl diff --git a/cron/clean_database.pl b/cron/clean_database.pl new file mode 100755 index 0000000..f553515 --- /dev/null +++ b/cron/clean_database.pl @@ -0,0 +1,11 @@ +#!/usr/bin/perl +use strict; +use Cwd 'abs_path'; +use File::Basename; +use local::db; +use config; + +for my $dl (local::db::download->retrieve_all) +{ + $dl->delete if not -d $config::config{files_dir}.'/'.$dl->category->name.'/'.$dl->download_id; +} diff --git a/cron/read_filesize_from_filesystem.pl b/cron/read_filesize_from_filesystem.pl new file mode 100755 index 0000000..702ddb4 --- /dev/null +++ b/cron/read_filesize_from_filesystem.pl @@ -0,0 +1,16 @@ +#!/usr/bin/perl +use strict; +use Cwd 'abs_path'; +use File::Basename; +use local::db; +use config; + +for my $dl (local::db::download->retrieve_all) +{ + my $du = "du -sm ".$config::config{files_dir}.'/'.$dl->category->name.'/'.$dl->download_id; + my $du_resp = `$du`; + my ($size) = $du_resp =~ /^(\d+)/; + # warn $size; + $dl->size($size); + $dl->update; +} diff --git a/cron/regen_quota.pl b/cron/regen_quota.pl new file mode 100755 index 0000000..e582b30 --- /dev/null +++ b/cron/regen_quota.pl @@ -0,0 +1,17 @@ +#!/usr/bin/perl +use strict; +use Cwd 'abs_path'; +use File::Basename; +use local::db; +use config; + +for my $owner (local::db::owner->retrieve_all) +{ + my $sum =0; + for my $dl ($owner->download) + { + $sum+=$dl->size + } + $owner->quota_used($sum); + $owner->update +} diff --git a/enqueue.pl b/enqueue.pl deleted file mode 100755 index 27c13ae..0000000 --- a/enqueue.pl +++ /dev/null @@ -1,40 +0,0 @@ -#!/usr/bin/perl -use strict; -use CGI qw(:standard); -use CGI::Carp 'fatalsToBrowser'; -use local::db; -use local::nzbget; -use utf8; - -use local::user; -my $user = new local::user(%ENV); -my $nzbget = new local::nzbget; -die "Kein name angegeben" if not param('name'); - -die 'Kein freier Speicher' if $user->obj->quota_used>$user->obj->quoata; - -my $cat = local::db::category->retrieve(name=>param('cat')); -die "Ungültige category" if not $cat; - -my $category = $cat->name(); - -my ($size) = param('description') =~ m/(.*)<\/b>/; -$size = 0; -my $download = local::db::download->insert({owner=>$user->get_id(),name=>param('name'),category=>$cat->category_id(),description=>param('title'),size=>$size}); -use LWP::Simple; -my $nzb = get(param('url')); -die "Couldn't get ".param('url') unless defined $nzb; -use MIME::Base64; -$nzb = encode_base64($nzb); -my $resp = $nzbget->send_request('append',RPC::XML::string->new($download->download_id()),RPC::XML::string->new($category),RPC::XML::boolean->new(0),RPC::XML::string->new($nzb)); - -if ($resp->value) -{ - print CGI::redirect('/cgi-bin/nzbget/groups.pl'); -} else { - print header; - print start_html('nzbget enqueue error'); - print 'Error'; - print end_html(); -} - diff --git a/examples/cron.d-nzbget b/examples/cron.d-nzbget new file mode 100644 index 0000000..358f457 --- /dev/null +++ b/examples/cron.d-nzbget @@ -0,0 +1,12 @@ +# +# cron-jobs for munin-node +# + +MAILTO=root + +# If the APT plugin is enabled, update packages databases approx. once +# an hour (12 invokations an hour, 1 in 12 chance that the update will +# happen), but ensure that there will never be more than two hour (7200 +# seconds) interval between updates.. +5 9 * * * www-data /srv/cgi-bin/nzbget/cron.sh + diff --git a/groups.pl b/groups.pl deleted file mode 100755 index 908b2d5..0000000 --- a/groups.pl +++ /dev/null @@ -1,77 +0,0 @@ -#!/usr/bin/perl - -use strict; -use CGI qw(:standard); -use CGI::Carp 'fatalsToBrowser'; -use local::nzbget; -use local::db; -use local::user; -use utf8; -my $user = new local::user(%ENV); - -use CGI::Ajax; - -my $cgi = new CGI; -my $pjx = new CGI::Ajax( 'get_download_speed' => \&get_download_speed, - 'get_download_table' => \&get_download_table - ); -print $pjx->build_html( $cgi, \&Show_HTML); - -sub get_download_speed -{ - my $cli = new local::nzbget; - my $status = $cli->send_request('status') or die "Can't connect to nzbget"; - my $speed = int($status->{DownloadRate}->value/(1024*1024)); - return $speed?$speed.'MB/s':int($status->{DownloadRate}->value/1024).'KB/s'; -} - -sub get_download_table -{ - my $html; - my $cli = new local::nzbget; - my $row = $cli->send_request('listgroups') or die "Can't connect to nzbget"; - $html.= CGI::start_table(); - $html.= Tr(th([qw/Kategorie Name Besitzer Größe(MB) Fortschritt Abbrechen/])); - foreach my $value ( @$row) - { - my ($download) = local::db::download->retrieve($value->{NZBNicename}->value) or die ' Error in groups'; - my $remaining=$value->{RemainingSizeMB}->value; - my $total=$value->{FileSizeMB}->value; - my $percent = int(100-100*$remaining/$total); - if (not $download->size) - { - $download->size($total); - $download->update(); - $user->obj->quota_used($user->obj->quota_used+$total); - $user->obj->update(); - } - my $cancel; - $cancel=a({href=>'/cgi-bin/nzbget/cancel.pl?id='.$value->{NZBNicename}->value},'Abbrechen') if ($download->owner->owner_id() eq $user->get_id()); - $html.= Tr(td([$download->category->name,$download->name,$download->owner->name,$total,$percent.'%',$cancel])); - #foreach my $key (keys %$value) - #{ - # $html.= Tr(td([$key,$value->{$key}->value])); - #} - } - $html.= CGI::end_table(); - return $html; -} - -sub Show_HTML -{ - use local::menu; - my $menu= new local::menu(%ENV); - my $html= $menu->start_html('nzbget status'); - - $html.= < -LALALA - ; - $html.= table({class=>'nohigh'},Tr(td['download speed   '. span({id=>'speed'},get_download_speed())]),Tr(td([' ']))); - $html.= div({id=>'dls'},get_download_table()); - - $html.= $menu->end_html(); - return $html -} diff --git a/history.pl b/history.pl deleted file mode 100755 index 204329e..0000000 --- a/history.pl +++ /dev/null @@ -1,32 +0,0 @@ -#!/usr/bin/perl -use strict; - -require RPC::XML; -require RPC::XML::Client; -use CGI qw(:standard); -use CGI::Carp 'fatalsToBrowser'; -use URI::Escape; - -print header; -print CGI::start_html('nzbget status'); - -my $cli = RPC::XML::Client->new('http://nzbget:tegbzn6789@127.0.0.1:6789/xmlrpc'); -my $resp = $cli->send_request('history') or die "Can't connect to nubget"; -my $i=0; -print CGI::start_table(); -foreach my $row (@{ $resp->value}) -{ - $i++; - foreach my $key (keys %$row) - { - if (ref $row->{$key} eq 'ARRAY') - { - print Tr(td([$key,(join ',',@{$row->{$key}})])); - } else { - print Tr(td([$key,$row->{$key}])); - } - } -} -print CGI::end_table(); -; -print end_html; diff --git a/local/db.pm b/local/db.pm index db20856..60c72b3 100644 --- a/local/db.pm +++ b/local/db.pm @@ -6,7 +6,7 @@ local::db::dbi->connection(@{$config::config{db}}); package local::db::download; use base 'local::db::dbi'; local::db::download->table('download'); -local::db::download->columns(All => qw/download_id owner size time category name description completed/); +local::db::download->columns(All => qw/download_id owner size time category name description completed processing/); local::db::download->has_a(owner => 'local::db::owner'); local::db::download->has_a(category => 'local::db::category'); local::db::download->has_many(seen => 'local::db::seen'); diff --git a/local/menu.pm b/local/menu.pm index d5078ff..714e06d 100644 --- a/local/menu.pm +++ b/local/menu.pm @@ -8,11 +8,11 @@ use config; my %menus = ( - '/cgi-bin/nzbget/groups.pl'=> [10 , 'Aktive Downloads'], - '/cgi-bin/nzbget/mydl.pl?only_me=1'=>[20 , 'Meine Downloads'], - '/cgi-bin/nzbget/mydl.pl'=> [30,'Alle Downloads'], - '/cgi-bin/nzbget/log.pl'=> [50,'Protokoll'], - '/cgi-bin/nzbget/search.pl'=> [5, 'Suche'], + 'download'=> [10 , 'Downloads','aktive','fertige','meine'], +# 'list?only_me=1'=>[20 , 'Meine Downloads'], +# 'list'=> [30,'Alle Downloads'], + 'log'=> [50,'Protokoll'], + 'search'=> [5, 'Suche', 'nzbindex.nl','NZB Datei hochladen'], ); @@ -30,6 +30,11 @@ sub start_html my $self=shift; my ($title)=@_; my $active = $self->{REQUEST_URI}; + $active =~ s'.*/''; + $active =~ s'\?.*''; + my $active_sub; + ($active,$active_sub)= split /_/,$active; + $active_sub+=2; my $html= CGI::start_html(-style=>{'src'=>'/style.css'},-title=>$title); my $user = new local::user(%ENV); $html.= table({class=>'nohigh'},Tr({class=>'nohigh'},td({style=>'font-weight: bold'},[$config::config{page_name} ]),td({class=>'login',align=>'right'},[''.b('Angemeldet als: ').$user->get_name.'']))); @@ -37,6 +42,7 @@ sub start_html my @elements; foreach my $url (sort {$menus{$a}->[0] <=> $menus{$b}->[0] } keys %menus) { + next if not $menus{$url}->[1]; if ($url eq $active) { push @elements,th({class=>'active'},[$menus{$url}->[1]]); @@ -48,6 +54,28 @@ sub start_html Tr({class=>'menu'},join '',@elements ), ); + my @submens; + if ($#{$menus{$active}}>1) + { + foreach my $i (2..$#{$menus{$active}}) + { + if ($i == $active_sub) + { + push @submens,th({class=>'active'},[$menus{$active}->[$i]]); + } else { + if ($i > 2) + { + push @submens,th([a({href=>"${active}_".($i-2)},$menus{$active}->[$i])]); + } else { + push @submens,th([a({href=>"${active}"},$menus{$active}->[$i])]); + } + } + } + } + $html.= table({class=>'submenu'}, + Tr({class=>'submenu'},join '',@submens + ), + ); return $html; } diff --git a/local/nzbget.pm b/local/nzbget.pm index ea3b47a..5f7a509 100644 --- a/local/nzbget.pm +++ b/local/nzbget.pm @@ -1,9 +1,11 @@ package local::nzbget; require Exporter; -require RPC::XML; -require RPC::XML::Client; use strict; use base "Exporter"; +use config; +require RPC::XML; +require RPC::XML::Client; + sub new { @@ -11,12 +13,26 @@ sub new my $class = ref($invocant) || $invocant; # my $self = $class->SUPER::new(@_); my $self=bless {@_}, $class; - $self->{cli} = RPC::XML::Client->new('http://nzbget:tegbzn6789@127.0.0.1:6789/xmlrpc'); + $self->{cli} = RPC::XML::Client->new($config::config{rpc_uri}); return $self; } sub send_request { my $self = shift; - return $self->{cli}->send_request(@_) or die "Can't connect to nzbget"; + my $res = $self->{cli}->send_request( @_ ) or die "Can't connect to nzbget"; + my %result; + if (ref $res eq 'RPC::XML::hash') { + my %result; + foreach my $key (%$res) + { + $result{$key}=$res->{$key}->value; + } + return \%result; + } elsif (ref $res eq 'RPC::XML::array') { + my @array = map {$_->value} @$res; + return \@array; + } else { + return $res->value; + } } diff --git a/local/nzbindex.pm b/local/nzbindex.pm index 492ebb3..70256fa 100755 --- a/local/nzbindex.pm +++ b/local/nzbindex.pm @@ -5,7 +5,8 @@ use base "Exporter"; use URI::Escape; use XML::Parser; use XML::SimpleObject; -use LWP::Simple; +use LWP::UserAgent; +use HTTP::Cookies; sub new { @@ -16,6 +17,25 @@ sub new return $self; } +sub http_fetch +{ + my $self=shift; + my ($request)=@_; + my $ua = LWP::UserAgent->new; + $ua->timeout(10); + my $cookie_jar = HTTP::Cookies->new({}); + $ua->cookie_jar($cookie_jar); + my $response = $ua->get("http://www.nzbindex.nl/?lang=2"); + $response = $ua->get('http://www.nzbindex.nl/rss/?'.$request); + if ($response->is_success) { + return $response->decoded_content; + } + else { + return; + die $response->status_line; + } +} + sub search { my $self=shift; @@ -23,10 +43,10 @@ sub search my @result; my $request=''; for my $key (keys %params) { - $request.= uri_escape($key)."=".$params{$key}.'&'; + $request.= uri_escape_utf8($key)."=".$params{$key}.'&'; } $request =~ s/\&$//; - my $feed = get('http://nzbindex.com/rss/?'.$request); + my $feed = $self->http_fetch($request); die "Couldn't get connect to search site" unless defined $feed; my $p1 = new XML::Parser(Style => 'Tree'); my $xso = XML::SimpleObject->new($p1->parse($feed)); diff --git a/log.pl b/log.pl deleted file mode 100755 index f4904c7..0000000 --- a/log.pl +++ /dev/null @@ -1,31 +0,0 @@ -#!/usr/bin/perl -use strict; -use local::nzbget; -use CGI qw(:standard); -use CGI::Carp 'fatalsToBrowser'; -use URI::Escape; - -print header; -use local::menu; -my $menu=new local::menu(%ENV); -print $menu->start_html('log'); - -my $cli = new local::nzbget; -print CGI::start_table(); -my $resp = $cli->send_request('log',0,10) or die "Can't connect to nubget"; -die $resp if not ref $resp; -foreach my $row (@{ $resp->value}) -{ - print Tr(td[$row->{Text}]); -# foreach my $key (keys %$row) -# { -# if (ref $row->{$key} eq 'ARRAY') -# { -# print Tr(td([$key,join(',',@{$row->{$key}})])); -# } else { -# print Tr(td([$key,$row->{$key}])); -# } -# } -} -print CGI::end_table(); -print $menu->end_html; diff --git a/mark_completed.pl b/mark_completed.pl deleted file mode 100755 index f456011..0000000 --- a/mark_completed.pl +++ /dev/null @@ -1,8 +0,0 @@ -#!/usr/bin/perl -use strict; -use local::db; -my $dl_id = $ARGV[0] or die "missing argument download id"; -my $dl = local::db::download->retrieve($dl_id) or die 'cannont find download'; -$dl->completed(1); -$dl->update; -exit 0; diff --git a/menu.pl b/menu.pl deleted file mode 100755 index ebd777f..0000000 --- a/menu.pl +++ /dev/null @@ -1,10 +0,0 @@ -#!/usr/bin/perl -use strict; -use CGI qw(:standard); -use CGI::Carp 'fatalsToBrowser'; -use local::menu; - -print header; -my $menu= new local::menu(%ENV); -print $menu->start_html('nzbget status'); -print $menu->end_html; diff --git a/mod_perl_startup.pl b/mod_perl_startup.pl new file mode 100755 index 0000000..f83e06e --- /dev/null +++ b/mod_perl_startup.pl @@ -0,0 +1,6 @@ +#!/usr/bin/perl +use strict; +use utf8; +use lib '/home/web/public_html/cgi/nzbget'; +use nzbget::main; +1; diff --git a/mydl.pl b/mydl.pl deleted file mode 100755 index c90a40c..0000000 --- a/mydl.pl +++ /dev/null @@ -1,37 +0,0 @@ -#!/usr/bin/perl -use strict; -use CGI qw(:standard); -use CGI::Carp 'fatalsToBrowser'; -use utf8; -use local::nzbget; -use local::db; -use local::user; -my $user = new local::user(%ENV); - - -print header; -use local::menu; -my $menu=new local::menu(%ENV); -print $menu->start_html('my downloads'); -print table({class=>'nohigh'},Tr(td({align=>'right'},['benutzter Speicherplatz   '. $user->obj->quota_used().' / '.$user->obj->quoata().' MB']))) if (param('only_me')); - -print CGI::start_table(); -print Tr(th([qw/Neu Kategorie Name Größe Löschen /])); -my @downloads = local::db::download->search( - param('only_me') ? (owner=>$user->get_id()) : () , - param('category') ? (category => param('category')) : (), - completed=>1,{ order_by => 'time DESC'}); -foreach my $dl ( @downloads) -{ - my $dl_remove; - $dl_remove = a({href=>'/cgi-bin/nzbget/remove.pl?id='.$dl->download_id},"löschen") if $dl->owner->owner_id == $user->get_id; - print Tr(td([ (local::db::seen->search(user=>$user->get_id(),download=>$dl->download_id))?'':'x', - a({href=>'mydl.pl?only_me='.param('only_me').'&category='.$dl->category->category_id},$dl->category->name),a({title=>$dl->description,href=>'/cgi-bin/nzbget/read.pl?id='.$dl->download_id},$dl->name),$dl->size.'MB',$dl_remove - - ])); -} -; -print CGI::end_table(); - -print $menu->end_html(); - diff --git a/nzbget/cancel.pm b/nzbget/cancel.pm new file mode 100644 index 0000000..556cc22 --- /dev/null +++ b/nzbget/cancel.pm @@ -0,0 +1,43 @@ +package nzbget::cancel; + +use strict; +use Apache2::Const -compile => qw(OK REDIRECT); +use CGI qw(:standard); +use CGI::Carp 'fatalsToBrowser'; +use local::nzbget; +use local::db; +use local::user; +use File::Path; +use config; + +sub handler +{ + my $user = new local::user(%ENV); + my $del_id = param('id'); + + my ($download) = local::db::download->retrieve($del_id) or die 'Error in cancel'; + die "Not allowed" if not $download->owner->owner_id() eq $user->get_id(); + my $basedir = $config::config{files_dir} or die 'Missing files_dir in config'; + my $cli = new local::nzbget; + my $row = $cli->send_request('listgroups') or die "Can't connect to nubget"; + foreach my $value ( @$row) + { + my ($dl) = local::db::download->retrieve($value->{NZBNicename}) or die ' Error in groups'; + if ($value->{NZBNicename} == $del_id) + { + my $status = $cli->send_request('editqueue','GroupDelete',0,"",[$value->{LastID}]) or die "Can't connect to nzbget"; + sleep 1; + File::Path::rmtree($basedir.'/'.$dl->category->name.'/'.$dl->download_id); + $user->obj->quota_used($user->obj->quota_used - $dl->size); + $user->obj->update; + my @seens = local::db::seen->search(download=>$del_id); + map {$_->delete()} @seens; + $dl->delete(); + } + } + + print CGI::redirect('download'); + return Apache2::Const::REDIRECT; +} + +1; diff --git a/nzbget/download.pm b/nzbget/download.pm new file mode 100644 index 0000000..39394e7 --- /dev/null +++ b/nzbget/download.pm @@ -0,0 +1,95 @@ +package nzbget::download; +use strict; +use Apache2::Const -compile => qw(OK REDIRECT); +use CGI qw(:standard); +use CGI::Carp 'fatalsToBrowser'; +use local::nzbget; +use local::db; +use local::user; +use utf8; +#use CGI::Ajax; + +sub handler +{ + print header(); + print Show_HTML(); +# my $cgi = new CGI; +# my $pjx = new CGI::Ajax( 'get_download_speed' => \&get_download_speed, +# 'get_download_table' => \&get_download_table +# ); +# print $pjx->build_html( $cgi, \&Show_HTML); + return Apache2::Const::OK; +} + +sub get_download_speed +{ + my $cli = new local::nzbget; + my $status = $cli->send_request('status') or die "Can't connect to nzbget"; + my $speed = int($status->{DownloadRate}/(1024*1024)); + return $speed?$speed.'MB/s':int($status->{DownloadRate}/1024).'KB/s'; +} + +sub get_download_table +{ + my $user = new local::user(%ENV); + my $html; + my $cli = new local::nzbget; + my $row = $cli->send_request('listgroups') or die "Can't connect to nzbget"; + $html.= CGI::start_table(); + $html.= Tr(th([qw/Kategorie Name Besitzer Größe(MB) Fortschritt Abbrechen/])); + foreach my $value ( @$row) + { + my ($download) = local::db::download->retrieve($value->{NZBNicename}) or die ' Error in groups'; + my $remaining=$value->{RemainingSizeMB}; + my $total=$value->{FileSizeMB}; + my $percent = int(100-100*$remaining/$total); + if (not $download->size) + { + $download->size($total); + $download->update(); + $user->obj->quota_used($user->obj->quota_used+$total); + $user->obj->update(); + } + my $cancel; + if ($download->processing == 1) + { + $html.=Tr(td([$download->category->name,$download->name,$download->owner->name,$total,'unpacking',''])); + } else { + $cancel=a({href=>'cancel?id='.$value->{NZBNicename}},'Abbrechen') if ($download->owner->owner_id() eq $user->get_id()); + $html.= Tr(td([$download->category->name,$download->name,$download->owner->name,$total,$percent.'%',$cancel])); + } + #foreach my $key (keys %$value) + #{ + # $html.= Tr(td([$key,$value->{$key}])); + #} + } + $html.= CGI::end_table(); + return $html; +} + +sub Show_HTML +{ + use local::menu; + my $menu= new local::menu(%ENV); + my $html= $menu->start_html('nzbget status'); + + $html.= < +LALALA + ; +# $html.= < +#LALALA +# ; + $html.= table({class=>'nohigh'},Tr(td['download speed   '. span({id=>'speed'},get_download_speed())]),Tr(td([' ']))); + $html.= div({id=>'dls'},get_download_table()); + + $html.= $menu->end_html(); + return $html +} + +1; diff --git a/nzbget/enqueue.pm b/nzbget/enqueue.pm new file mode 100644 index 0000000..e786f38 --- /dev/null +++ b/nzbget/enqueue.pm @@ -0,0 +1,59 @@ +package nzbget::enqueue; +use strict; +use Apache2::Const -compile => qw(OK REDIRECT); +use CGI qw(:standard); +use CGI::Carp 'fatalsToBrowser'; +use local::db; +use local::nzbget; +use utf8; +use LWP::Simple; + +use local::user; + +sub handler +{ + my $user = new local::user(%ENV); + my $nzbget = new local::nzbget; + die "Kein name angegeben" if not param('name'); + + die 'Kein freier Speicher' if $user->obj->quota_used>$user->obj->quoata; + + my $cat = local::db::category->retrieve(name=>param('cat')); + die "Ungültige category" if not $cat; + + my $category = $cat->name(); + + # my ($size) = param('description') =~ m/(.*)<\/b>/; + my $size = 0; + my $nzb; + my $title = param('title'); + if (param('url')) + { + $nzb = get(param('url')); + die "Couldn't get ".param('url') unless $nzb; + } elsif ($title = param('file')) { + my $upload_filehandle = upload("file"); + while ( <$upload_filehandle> ) + { + $nzb.=$_; + } + } + die "Could not get nzb" if not $nzb; + use MIME::Base64; + $nzb = encode_base64($nzb); + my $download = local::db::download->insert({owner=>$user->get_id(),name=>param('name'),category=>$cat->category_id(),description=>$title,size=>$size}); + my $resp = $nzbget->send_request('append',RPC::XML::string->new($download->download_id()),RPC::XML::string->new($category),RPC::XML::boolean->new(0),RPC::XML::string->new($nzb)); + + if ($resp) + { + print CGI::redirect('download'); + return Apache2::Const::REDIRECT; + } else { + print header; + print start_html('nzbget enqueue error'); + print 'Error'; + print end_html(); + return Apache2::Const::OK; + } +} +1; diff --git a/nzbget/list.pm b/nzbget/list.pm new file mode 100644 index 0000000..fc30069 --- /dev/null +++ b/nzbget/list.pm @@ -0,0 +1,49 @@ +#!/usr/bin/perl +package nzbget::list; +use strict; +use Apache2::Const -compile => qw(OK REDIRECT); +use CGI qw(:standard); +use CGI::Carp 'fatalsToBrowser'; +use utf8; +use local::nzbget; +use local::db; +use local::user; +use local::menu; + + +sub handler +{ + my $user = new local::user(%ENV); + print header; + my $menu=new local::menu(%ENV); + print $menu->start_html('my downloads'); + my $module = $ENV{REQUEST_URI}; + $module =~ s'.*/''; + $module =~ s'\?.*''; + my $only_me = $module eq 'download_2' ? 1:0; + + print table({class=>'nohigh'},Tr(td({align=>'right'},['benutzter Speicherplatz   '. $user->obj->quota_used().' / '.$user->obj->quoata().' MB']))) if ($only_me); + + print CGI::start_table(); + print Tr(th([qw/Neu Kategorie Name Größe Löschen /])); + my @downloads = local::db::download->search( + $only_me ? (owner=>$user->get_id()) : () , + param('category') ? (category => param('category')) : (), + completed=>1,{ order_by => 'time DESC'}); + foreach my $dl ( @downloads) + { + my $dl_remove; + $dl_remove = a({href=>'remove?id='.$dl->download_id},"löschen") if $dl->owner->owner_id == $user->get_id; + print Tr(td([ (local::db::seen->search(user=>$user->get_id(),download=>$dl->download_id))?'':'x', + a({href=>"$module?category=".$dl->category->category_id},$dl->category->name),a({title=>$dl->description,href=>'read?id='.$dl->download_id},$dl->name),$dl->size.'MB',$dl_remove + + ])); + } + ; + print CGI::end_table(); + + print $menu->end_html(); + return Apache2::Const::OK; +} + +1; diff --git a/nzbget/log.pm b/nzbget/log.pm new file mode 100644 index 0000000..05670ec --- /dev/null +++ b/nzbget/log.pm @@ -0,0 +1,29 @@ +package nzbget::log; +use strict; +use Apache2::Const -compile => qw(OK); +use local::nzbget; +use CGI qw(:standard); +use CGI::Carp 'fatalsToBrowser'; +use URI::Escape; + + +sub handler { + print header; + use local::menu; + my $menu=new local::menu(%ENV); + print $menu->start_html('log'); + + my $cli = new local::nzbget; + print CGI::start_table(); + my $resp = $cli->send_request('log',0,10) or die "Can't connect to nubget"; + die $resp if not ref $resp; + foreach my $row (@$resp) + { + print Tr(td[$row->{Text}]); + } + print CGI::end_table(); + print $menu->end_html; + return Apache2::Const::OK; +} + +1; diff --git a/nzbget/main.pm b/nzbget/main.pm new file mode 100644 index 0000000..1ff34c5 --- /dev/null +++ b/nzbget/main.pm @@ -0,0 +1,56 @@ +package nzbget::main; + +use strict; +use warnings FATAL => 'all'; +no warnings 'redefine'; + +use Apache2::RequestRec (); +use Apache2::RequestIO (); + +use Apache2::Const -compile => qw(OK); + +use nzbget::list; +use nzbget::download; +use nzbget::cancel; +use nzbget::enqueue; +use nzbget::log; +use nzbget::pre_enqueue; +use nzbget::read; +use nzbget::remove; +use nzbget::search; +use nzbget::menu; +use nzbget::search_upload; + +my %modules = +( + download => \&nzbget::download::handler, + download_1 => \&nzbget::list::handler, + download_2 => \&nzbget::list::handler, + cancel=> \&nzbget::cancel::handler, + enqueue=> \&nzbget::enqueue::handler, + log=> \&nzbget::log::handler, + pre_enqueue=> \&nzbget::pre_enqueue::handler, + read=> \&nzbget::read::handler, + remove=> \&nzbget::remove::handler, + search=> \&nzbget::search::handler, + search_1=> \&nzbget::search_upload::handler, + menu=> \&nzbget::menu::handler, +); + + +sub handler { + my $r = shift; + + my $module = $ENV{REQUEST_URI}; + $module =~ s'.*/''; + $module =~ s'\?.*''; + if ($modules{$module}) + { + return $modules{$module}->($r); + } else { + $r->content_type('text/plain'); + print "Unknown action $module ($ENV{REQUEST_URI})\n"; + return Apache2::Const::OK; + } +} +1; diff --git a/nzbget/menu.pm b/nzbget/menu.pm new file mode 100644 index 0000000..e368d21 --- /dev/null +++ b/nzbget/menu.pm @@ -0,0 +1,15 @@ +package nzbget::menu; +use strict; +use Apache2::Const -compile => qw(OK REDIRECT); +use CGI qw(:standard); +use CGI::Carp 'fatalsToBrowser'; +use local::menu; + +sub handler { + print header; + my $menu= new local::menu(%ENV); + print $menu->start_html('nzbget status'); + print $menu->end_html; + return Apache2::Const::OK; +} +1; diff --git a/nzbget/pre_enqueue.pm b/nzbget/pre_enqueue.pm new file mode 100644 index 0000000..dda8967 --- /dev/null +++ b/nzbget/pre_enqueue.pm @@ -0,0 +1,36 @@ +package nzbget::pre_enqueue; +use strict; +use Apache2::Const -compile => qw(OK); +use CGI qw(:standard); +use CGI::Carp 'fatalsToBrowser'; +use local::db; +use local::menu; + +sub handler +{ + my @cat = local::db::category->retrieve_all(); + die "Ungültige category" if not @cat; + + print header; + my $menu = new local::menu(%ENV); + print $menu->start_html('nzbget pre_enqueue'); + print CGI::start_form(-action=>'enqueue'); + print table({class=>'nohigh'}, + Tr(td(param('title'))), + Tr(td(param('description'))), + ), + table({class=>'nohigh'}, + Tr(td(['name',textfield('name').hidden('description',param('description')).hidden('url',param('url')).hidden('title',param('title'))])), + Tr(td(['category', + CGI::popup_menu( + -name=>'cat', + -values=>[0,map {$_->name()} @cat], + -labels => {0=>'---', map { ($_->category_id() => $_->name() ) } @cat} + )])), + Tr(td([' ',submit("Download starten")])), + ); + print CGI::end_form; + print $menu->end_html(); + return Apache2::Const::OK; +} +1; diff --git a/nzbget/read.pm b/nzbget/read.pm new file mode 100644 index 0000000..3cc30ec --- /dev/null +++ b/nzbget/read.pm @@ -0,0 +1,26 @@ +package nzbget::read; +use strict; +use Apache2::Const -compile => qw(OK REDIRECT); +use CGI qw(:standard); +use CGI::Carp 'fatalsToBrowser'; +use utf8; +use local::db; +use local::user; +use config; + + +sub handler +{ + my $user = new local::user(%ENV); + + my $dl = local::db::download->retrieve(param('id')) or die "Ungültige download id"; + + if (not local::db::seen->search(user=>$user->get_id,download=>$dl->download_id)) + { + local::db::seen->insert({user=>$user->get_id,download=>$dl->download_id}); + } + + print CGI::redirect($config::config{web_dir}.'/'.$dl->category->name.'/'.$dl->download_id); + return Apache2::Const::REDIRECT; +} +1; diff --git a/nzbget/remove.pm b/nzbget/remove.pm new file mode 100644 index 0000000..71334bd --- /dev/null +++ b/nzbget/remove.pm @@ -0,0 +1,31 @@ +package nzbget::remove; +use strict; +use Apache2::Const -compile => qw(OK REDIRECT); +use CGI qw(:standard); +use CGI::Carp 'fatalsToBrowser'; +use local::db; +use local::user; +use File::Path; +use config; + + +sub handler +{ + my $user = new local::user(%ENV); + my $del_id = param('id'); + + my ($dl) = local::db::download->retrieve($del_id) or die 'Error in cancel'; + die "Not allowed" if not $dl->owner->owner_id() eq $user->get_id(); + + my $basedir = $config::config{files_dir} or die 'Missing files_dir in config'; + File::Path::rmtree($basedir.'/'.$dl->category->name.'/'.$dl->download_id); + $user->obj->quota_used($user->obj->quota_used - $dl->size); + $user->obj->update; + my @seens = local::db::seen->search(download=>$del_id); + map {$_->delete()} @seens; + $dl->delete(); + + print CGI::redirect('download_2'); + return Apache2::Const::REDIRECT; +} +1; diff --git a/nzbget/search.pm b/nzbget/search.pm new file mode 100644 index 0000000..e1c181d --- /dev/null +++ b/nzbget/search.pm @@ -0,0 +1,56 @@ +package nzbget::search; +use strict; +use Apache2::Const -compile => qw(OK); +use CGI qw(:standard); +use CGI::Carp 'fatalsToBrowser'; +use URI::Escape; +use local::params; +use local::nzbindex; +use local::menu; + +sub handler +{ + print header; + my $menu= new local::menu(%ENV); + print $menu->start_html('nzbget search'); + + print CGI::start_table(); + + print CGI::start_form(-action=>'search'); + print + table({class=>'nohigh'}, + Tr(td(['Search',textfield({size=>100,name=>'q'}),])), + Tr(td(['min Size',textfield({size=>5,name=>'minsize'}).' MB'])), + Tr(td(['max Size',textfield({size=>5, name=>'maxsize'}).' MB'.hidden('more',1)])), + Tr(td(['results',CGI::popup_menu( + -name => 'max', + -values => ['25','100','500'], + -default => '25', + -labels => {25=>'25',100=>'100',500=>500} + ) ])), + Tr(td(['',submit()])), + Tr(td([qw/   /])), + ); + print CGI::end_form; + my $param_obj=new local::params; + if ($param_obj->has_params()) + { + my $nzbindex=new local::nzbindex; + my @search = $nzbindex->search($param_obj->get_as_hash()); + if (@search) + { + print CGI::start_table(); + foreach my $item (@search) { + print Tr(td([ + a({href=>"pre_enqueue?url=".uri_escape_utf8($item->{url})."&name=".uri_escape_utf8(param('q'))."&description=".uri_escape_utf8($item->{description})."&title=".uri_escape_utf8($item->{title})},$item->{title}).' '. + $item->{description}])); + } + print CGI::end_table(); + } else { + print hr().'nothing found'; + } + } + print $menu->end_html; + return Apache2::Const::OK; +} +1; diff --git a/nzbget/search_upload.pm b/nzbget/search_upload.pm new file mode 100644 index 0000000..de3e10f --- /dev/null +++ b/nzbget/search_upload.pm @@ -0,0 +1,37 @@ +package nzbget::search_upload; +use strict; +use Apache2::Const -compile => qw(OK); +use CGI qw(:standard); +use CGI::Carp 'fatalsToBrowser'; +use local::db; +use local::menu; + +sub handler +{ + my @cat = local::db::category->retrieve_all(); + die "Ungültige category" if not @cat; + + print header; + my $menu = new local::menu(%ENV); + print $menu->start_html('nzbget pre_enqueue'); + print CGI::start_form(-action=>'enqueue'); + print table({class=>'nohigh'}, + Tr(td(param('title'))), + Tr(td(param('description'))), + ), + table({class=>'nohigh'}, + Tr(td(['nzb file',filefield('file')])), + Tr(td(['name',textfield('name')])), + Tr(td(['category', + CGI::popup_menu( + -name=>'cat', + -values=>[0,map {$_->name()} @cat], + -labels => {0=>'---', map { ($_->category_id() => $_->name() ) } @cat} + )])), + Tr(td([' ',submit("Download starten")])), + ); + print CGI::end_form; + print $menu->end_html(); + return Apache2::Const::OK; +} +1; diff --git a/pre_enqueue.pl b/pre_enqueue.pl deleted file mode 100755 index fea581e..0000000 --- a/pre_enqueue.pl +++ /dev/null @@ -1,30 +0,0 @@ -#!/usr/bin/perl -use strict; -use CGI qw(:standard); -use CGI::Carp 'fatalsToBrowser'; -use local::db; -use local::menu; - -my @cat = local::db::category->retrieve_all(); -die "Ungültige category" if not @cat; - -print header; -my $menu = new local::menu(%ENV); -print $menu->start_html('nzbget pre_enqueue'); -print CGI::start_form(-action=>'/cgi-bin/nzbget/enqueue.pl'); -print table({class=>'nohigh'}, - Tr(td(param('title'))), - Tr(td(param('description'))), -), -table({class=>'nohigh'}, - Tr(td(['name',textfield('name').hidden('description',param('description')).hidden('url',param('url')).hidden('title',param('title'))])), - Tr(td(['category', - CGI::popup_menu( - -name=>'cat', - -values=>[0,map {$_->name()} @cat], - -labels => {0=>'---', map { ($_->category_id() => $_->name() ) } @cat} - )])), - Tr(td([' ',submit("Download starten")])), -); -print CGI::end_form; -print $menu->end_html(); diff --git a/process.pl b/process.pl new file mode 100755 index 0000000..33a6279 --- /dev/null +++ b/process.pl @@ -0,0 +1,27 @@ +#!/usr/bin/perl +use strict; +use Cwd 'abs_path'; +use File::Basename; +use local::db; +use config; +my $process_path = dirname(abs_path($config::config{process})) or die "Couldn\'t determine process_path"; +#print "$process_path\n"; + +$ENV{NZBPP_DIRECTORY} or die "missing argument download id"; +my $dl_id = basename($ENV{NZBPP_DIRECTORY}) or die "missing argument download id"; +my $dl = local::db::download->retrieve($dl_id) or die 'cannont find download'; +$dl->processing(1); +$dl->update; +chdir($process_path); +my $return = system($config::config{process}); +$return = $return >> 8 if $return; +$dl->completed(1); + +my $du = "du -sm ".$config::config{files_dir}.'/'.$dl->category->name.'/'.$dl_id; +my $du_resp = `$du`; +my ($size) = $du_resp =~ /^(\d+)/; +#warn $size; +$dl->size($size); +$dl->update; + +exit $return; diff --git a/process.sh b/process.sh new file mode 100755 index 0000000..5aac9e5 --- /dev/null +++ b/process.sh @@ -0,0 +1,4 @@ +#!/bin/bash +set -e +export MOD_PATH=`dirname $0` +perl -I $MOD_PATH $MOD_PATH/process.pl diff --git a/read.pl b/read.pl deleted file mode 100755 index 45a428c..0000000 --- a/read.pl +++ /dev/null @@ -1,18 +0,0 @@ -#!/usr/bin/perl -use strict; -use CGI qw(:standard); -use CGI::Carp 'fatalsToBrowser'; -use utf8; -use local::db; -use local::user; -use config; -my $user = new local::user(%ENV); - -my $dl = local::db::download->retrieve(param('id')) or die "Ungültige download id"; - -if (not local::db::seen->search(user=>$user->get_id,download=>$dl->download_id)) -{ - local::db::seen->insert({user=>$user->get_id,download=>$dl->download_id}); -} - -print CGI::redirect($config::config{web_dir}.'/'.$dl->category->name.'/'.$dl->download_id); diff --git a/remove.pl b/remove.pl deleted file mode 100755 index fe690ed..0000000 --- a/remove.pl +++ /dev/null @@ -1,25 +0,0 @@ -#!/usr/bin/perl - -use strict; -use CGI qw(:standard); -use CGI::Carp 'fatalsToBrowser'; -use local::db; -use local::user; -use File::Path; -use config; - -my $user = new local::user(%ENV); -my $del_id = param('id'); - -my ($dl) = local::db::download->retrieve($del_id) or die 'Error in cancel'; -die "Not allowed" if not $dl->owner->owner_id() eq $user->get_id(); - -my $basedir = $config::config{files_dir} or die 'Missing files_dir in config'; -File::Path::rmtree($basedir.'/'.$dl->category->name.'/'.$dl->download_id); -$user->obj->quota_used($user->obj->quota_used - $dl->size); -$user->obj->update; -my @seens = local::db::seen->search(download=>$del_id); -map {$_->delete()} @seens; -$dl->delete(); - -print CGI::redirect('/cgi-bin/nzbget/mydl.pl?only_me=1'); diff --git a/search.pl b/search.pl deleted file mode 100755 index d63d944..0000000 --- a/search.pl +++ /dev/null @@ -1,51 +0,0 @@ -#!/usr/bin/perl -use strict; -use CGI qw(:standard); -use CGI::Carp 'fatalsToBrowser'; -use URI::Escape; -use local::params; -use local::nzbindex; -use local::menu; - -print header; -my $menu= new local::menu(%ENV); -print $menu->start_html('nzbget status'); - -print CGI::start_table(); - -print CGI::start_form(-action=>'/cgi-bin/nzbget/search.pl'); -print - table({class=>'nohigh'}, - Tr(td(['Search',textfield({size=>100,name=>'q'}),])), - Tr(td(['min Size',textfield({size=>5,name=>'minsize'}).' MB'])), - Tr(td(['max Size',textfield({size=>5, name=>'maxsize'}).' MB'.hidden('more',1)])), - Tr(td(['results',CGI::popup_menu( - -name => 'max', - -values => ['25','100','500'], - -default => '25', - -labels => {25=>'25',100=>'100',500=>500} - ) ])), - Tr(td(['',submit()])), - Tr(td([qw/   /])), -); -print CGI::end_form; -my $param_obj=new local::params; -if ($param_obj->has_params()) -{ - my $nzbindex=new local::nzbindex; - my @search = $nzbindex->search($param_obj->get_as_hash()); - if (@search) - { - print CGI::start_table(); - foreach my $item (@search) { - print Tr(td([ - a({href=>"./pre_enqueue.pl?url=".uri_escape($item->{url})."&name=".uri_escape(param('q'))."&description=".uri_escape($item->{description})."&title=".uri_escape($item->{title})},$item->{title}).' '. - $item->{description}])); - } - print CGI::end_table(); - } else { - print hr().'nothing found'; - } -} -print $menu->end_html; - diff --git a/status.pl b/status.pl deleted file mode 100755 index 3877f4b..0000000 --- a/status.pl +++ /dev/null @@ -1,23 +0,0 @@ -#!/usr/bin/perl -use strict; -use CGI qw(:standard); -use CGI::Carp 'fatalsToBrowser'; -print header; -print start_html('nzbget status'); - -require RPC::XML; -require RPC::XML::Client; - -my $cli = RPC::XML::Client->new('http://nzbget:tegbzn6789@127.0.0.1:6789/xmlrpc'); -my $row = $cli->send_request('status') or die "Can't connect to nubget"; - -print CGI::start_table(); -foreach my $key (keys %$row) -{ - print Tr(td([$key,$row->{$key}->value])); -} -; -print CGI::end_table(); - -print end_html; - diff --git a/styles/0xff.css b/styles/0xff.css index 5492ec3..29e710e 100644 --- a/styles/0xff.css +++ b/styles/0xff.css @@ -84,10 +84,43 @@ tr { // border:0px; //} -table.menu { +table.submenu { margin-bottom: 1em; } +tr.submenu th { + background-color:#fff200; + color:#000000; + padding-left:1em; + margin:0px; + border:3px solid #fff200; +} +tr.submenu a{ + background-color:#fff200; + color:#000000; + margin:0px; +} + +tr.submenu th.active { + background-color:#000000; + color:#fff200; + padding-left:1em; + margin:0px; + border:3px solid #fff200; +} + + +tr.submenu:hover th a { + color:#000000; + background-color:#fff200; + text-decoration:none; +} + +tr.submenu:hover th a:hover { + color:#999000; + background-color:#fff200; + text-decoration:none; +} tr.menu th { background-color:#fff200; color:#000000; -- cgit v1.2.3