diff options
author | yorhel <yorhel@1fe2e327-d9db-4752-bcf7-ef0cb4a1748b> | 2008-08-14 15:19:18 +0000 |
---|---|---|
committer | yorhel <yorhel@1fe2e327-d9db-4752-bcf7-ef0cb4a1748b> | 2008-08-14 15:19:18 +0000 |
commit | 889e9b9c481170c94f0b5e773e7c39d27b921a43 (patch) | |
tree | 7515e85ee690916f7549fd27bd0a2879657d913b | |
parent | 99214c46848b3e4706552cfb87043861c1b5f140 (diff) |
Added screenshots (not entirely finished, still needs some tweaking and polishing)
git-svn-id: svn://vndb.org/vndb@83 1fe2e327-d9db-4752-bcf7-ef0cb4a1748b
-rw-r--r-- | data/tpl/defs.pl | 3 | ||||
-rw-r--r-- | data/tpl/vnedit | 4 | ||||
-rw-r--r-- | data/tpl/vnpage | 31 | ||||
-rw-r--r-- | data/tpl/vnpage_scr | 34 | ||||
-rw-r--r-- | lib/ChangeLog | 2 | ||||
-rw-r--r-- | lib/Multi/Image.pm | 205 | ||||
-rw-r--r-- | lib/VNDB.pm | 2 | ||||
-rw-r--r-- | lib/VNDB/Util/DB.pm | 23 | ||||
-rw-r--r-- | lib/VNDB/VN.pm | 57 | ||||
-rw-r--r-- | lib/global.pl | 6 | ||||
-rw-r--r-- | static/files/blank.css | 0 | ||||
-rw-r--r-- | static/files/def.js | 110 | ||||
-rw-r--r-- | static/files/dyna.js | 196 | ||||
-rw-r--r-- | static/files/style.css | 47 | ||||
-rw-r--r-- | util/dump.sql | 12 | ||||
-rw-r--r-- | util/fcgi.pl | 3 | ||||
-rwxr-xr-x | util/updates/update_1.21.pl | 15 | ||||
-rw-r--r-- | util/updates/update_1.21.sql | 12 |
18 files changed, 687 insertions, 75 deletions
diff --git a/data/tpl/defs.pl b/data/tpl/defs.pl index e16da538..9135adb1 100644 --- a/data/tpl/defs.pl +++ b/data/tpl/defs.pl @@ -241,7 +241,8 @@ sub ttabs { # [vrpu], obj, sel <li><a href="/v$$o{id}/edit?fh=info" rel="nofollow">General info</a></li> <li><a href="/v$$o{id}/edit?fh=cat" rel="nofollow">Categories</a></li> <li><a href="/v$$o{id}/edit?fh=rel" rel="nofollow">Relations</a></li> - <li><a href="/v$$o{id}/edit?fh=img" rel="nofollow">Upload image</a></li> + <li><a href="/v$$o{id}/edit?fh=img" rel="nofollow">Image</a></li> + <li><a href="/v$$o{id}/edit?fh=scr" rel="nofollow">Screenshots</a></li> <li><a href="/v$$o{id}/add" rel="nofollow">Add release</a></li> </ul> </div>| : $t eq 'r' ? qq| diff --git a/data/tpl/vnedit b/data/tpl/vnedit index 6412ad8f..c3573d7b 100644 --- a/data/tpl/vnedit +++ b/data/tpl/vnedit @@ -93,6 +93,10 @@ Kagetsu Tohya will automatically be added as a sequel for Tsukihime. |}, + { type => 'sub', title => 'Screenshots', short => 'scr' }, + { type => 'hidden', short => 'screenshots' }, + { type => 'static', raw => 1, text => '<div id="scrfrm" class="'.$p{st}.'">...make sure to enable Javascript...</div>' }, + { type => 'sub', title => 'Edit summary', short => 'com' }, { type => 'textarea', name => 'Edit summary', short => 'comm', rows => 3, cols => 60 }, { type => 'static', text => 'Please explain your modifications and cite all sources.' }, diff --git a/data/tpl/vnpage b/data/tpl/vnpage index 39374ed8..d0651992 100644 --- a/data/tpl/vnpage +++ b/data/tpl/vnpage @@ -23,19 +23,20 @@ [[ if($d{change}) { ]] [[= cdiff($d{prev}, $d{vn}, - [ title => 'Title', 1 ], - [ alias => 'Alias', 1, 1 ], - [ desc => 'Description', 1, 1 ], - [ length => 'Length', sub { $VNDB::VNLEN->[$_[0] ][0] } ], - [ l_wp => 'Wikipedia link', sub { $_[0] ? '<a href="http://en.wikipedia.org/wiki/'.$_[0].'">'.$_[0].'</a>' : 'No link' } ], - [ l_encubed => 'Encubed tag', sub { $_[0] ? '<a href="http://novelnews.net/tag/'._huri($_[0]).'/">'.$_[0].'</a>' : 'No link' } ], - [ l_renai => 'Renai.us link', sub { $_[0] ? '<a href="http://renai.us/game/'._huri($_[0]).'.shtml">'.$_[0].'</a>' : 'No link' } ], - [ l_vnn => 'V-N.net link', sub { $_[0] ? '<a href="http://visual-novels.net/vn/index.php?option=com_content&task=view&id='.$_[0].'">'.$_[0].'</a>' : 'No link' } ], - [ anime => 'Related anime', sub { join(' ', map qq|<a href="http://anidb.net/a$$_{id}">$$_{id}</a>|, sort { $a->{id} <=> $b->{id} } @{$_[0]}) } ], - [ categories => 'Categories', sub { join(' ', map { my $l=$VNDB::CAT->{substr($_->[0],0,1)}[1]{substr($_->[0],1,2)}; $l?$l.'('.$_->[1].')':() } sort { $a->[0] cmp $b->[0] } @{$_[0]}) || 'No categories selected' }, 1 ], - [ relations => 'Relations', sub { join("<br />\n", map { $VNDB::VREL->[$_->{relation}].': '._hchar($_->{title}) } sort { $a->{id} <=> $b->{id} } @{$_[0]}) } ], - [ image => 'Image', sub { $_[0] > 0 ? sprintf '<img src="%s/cv/%02d/%d.jpg" />', $p{st}, $_[0]%100, $_[0] : $_[0] < 0 ? '[processing]' : 'No image'; } ], - [ img_nsfw => 'NSFW', sub { $_[0] ? 'Not safe' : 'Safe' } ] + [ title => 'Title', 1 ], + [ alias => 'Alias', 1, 1 ], + [ desc => 'Description', 1, 1 ], + [ length => 'Length', sub { $VNDB::VNLEN->[$_[0] ][0] } ], + [ l_wp => 'Wikipedia link', sub { $_[0] ? '<a href="http://en.wikipedia.org/wiki/'.$_[0].'">'.$_[0].'</a>' : 'No link' } ], + [ l_encubed => 'Encubed tag', sub { $_[0] ? '<a href="http://novelnews.net/tag/'._huri($_[0]).'/">'.$_[0].'</a>' : 'No link' } ], + [ l_renai => 'Renai.us link', sub { $_[0] ? '<a href="http://renai.us/game/'._huri($_[0]).'.shtml">'.$_[0].'</a>' : 'No link' } ], + [ l_vnn => 'V-N.net link', sub { $_[0] ? '<a href="http://visual-novels.net/vn/index.php?option=com_content&task=view&id='.$_[0].'">'.$_[0].'</a>' : 'No link' } ], + [ anime => 'Related anime', sub { join(' ', map qq|<a href="http://anidb.net/a$$_{id}">$$_{id}</a>|, sort { $a->{id} <=> $b->{id} } @{$_[0]}) } ], + [ categories => 'Categories', sub { join(' ', map { my $l=$VNDB::CAT->{substr($_->[0],0,1)}[1]{substr($_->[0],1,2)}; $l?$l.'('.$_->[1].')':() } sort { $a->[0] cmp $b->[0] } @{$_[0]}) || 'No categories selected' }, 1 ], + [ relations => 'Relations', sub { join("<br />\n", map { $VNDB::VREL->[$_->{relation}].': '._hchar($_->{title}) } sort { $a->{id} <=> $b->{id} } @{$_[0]}) } ], + [ image => 'Image', sub { $_[0] > 0 ? sprintf '<img src="%s/cv/%02d/%d.jpg" />', $p{st}, $_[0]%100, $_[0] : $_[0] < 0 ? '[processing]' : 'No image'; } ], + [ screenshots => 'Screenshots', sub { join "<br />\n", map sprintf('<a href="%s/sf/%02d/%d.jpg">%2$d</a> (%s)',$p{st},$$_[0]%100,$$_[0],$$_[1]?'NSFW':'Safe'), @{$_[0]} } ], + [ img_nsfw => 'NSFW', sub { $_[0] ? 'Not safe' : 'Safe' } ] ) ]] [[ } ]]- @@ -157,6 +158,9 @@ if($d{vn}{length} || $d{vn}{alias} || @links || $prod) { ]] my @lnks = ( !$d{page} ? '<b>description & releases</b>' : '<a href="/v'.$d{vn}{id}.'">description & releases</a>', $d{page} eq 'stats' ? '<b>stats</b>' : '<a href="/v'.$d{vn}{id}.'/stats">stats</a>', + @{$d{vn}{screenshots}} ? ( + $d{page} eq 'scr' ? '<b>screenshots</b>' : '<a href="/v'.$d{vn}{id}.'/scr">screenshots</a>', + ) : (), $d{vn}{rgraph} ? ( $d{page} eq 'rg' ? '<b>relations</b>' : '<a href="/v'.$d{vn}{id}.'/rg">relations</a>', ) : (), @@ -167,6 +171,7 @@ if($d{vn}{length} || $d{vn}{alias} || @links || $prod) { ]] [[ if(!$d{page}) { ]][[+ vnpage_rel ]][[ } ]] [[ if($d{page} eq 'stats') { ]][[+ vnpage_stats ]][[ } ]] [[ if($d{page} eq 'rg') { ]][[+ vnpage_rg ]][[ } ]] +[[ if($d{page} eq 'scr') { ]][[+ vnpage_scr ]][[ } ]] [[ if($p{AuthLoggedin}) { ]]- <div class="dropdown" id="voteDD"> diff --git a/data/tpl/vnpage_scr b/data/tpl/vnpage_scr new file mode 100644 index 00000000..b12bbb14 --- /dev/null +++ b/data/tpl/vnpage_scr @@ -0,0 +1,34 @@ +<h3>Screenshots +[[ if((!$d{vn}{locked} && $p{Authedit}) || $p{Authlock}) { ]]- <p class="actions">(<a href="/v[[= $d{vn}{id} ]]/edit?fh=scr">manage screenshots</a>)</p>[[ } ]]</h3> + +[[ if(@{$d{vn}{screenshots}}) { + my $tot = @{$d{vn}{screenshots}}; + my $nsfw = grep $$_[1], @{$d{vn}{screenshots}}; +]]- + +<div id="screenshots"> +[[ for(@{$d{vn}{screenshots}}) { ]] + <a href="[[= sprintf '%s/sf/%02d/%d.jpg', $p{st}, $$_[0]%100, $$_[0] ]]"[[= $$_[1] ? ' class="scr_nsfw"'.(!$p{AuthNsfw}?' style="display: none"':'') : '' ]] + ><img src="[[= sprintf '%s/st/%02d/%d.jpg', $p{st}, $$_[0]%100, $$_[0] ]]" + /><b>[[= $$_[1] ? 'x' : ' ' ]]</b></a> +[[ } ]]- +</div> +[[ if($nsfw) { ]]- +<br style="clear: left" /><br /> +<p id="scrNsfwHid"> </p> +<i style="font-size: 10px;">Items marked with a red X are flagged as NSFW.</i> +[[ } ]]- + +<div id="scrView"> +<b id="scrimg"></b><br /> +<a href="#" onclick="return scrClose()" id="scrclose">close</a> +<a href="#" id="scrprev"><- previous</a> +<a href="#" id="scrnext">next -></a> +</div> +<img id="preload" onload="scrPosition()" /> + +[[ } else { ]]- +<p> + No screenshots have been uploaded yet for this visual novel. +</p> +[[ } ]] diff --git a/lib/ChangeLog b/lib/ChangeLog index 344b720a..688629d7 100644 --- a/lib/ChangeLog +++ b/lib/ChangeLog @@ -9,6 +9,8 @@ TODO: 1.21 - ? - Added !vn and !uptime commands to Multi::IRC - Added realtime IRC notifications for actions on the site + - Added screenshots to VNs + - Rewrote Multi::Image 1.20 - 2008-08-06 (r79) - Admins can change someone's username diff --git a/lib/Multi/Image.pm b/lib/Multi/Image.pm index b615ef4e..14ffc94b 100644 --- a/lib/Multi/Image.pm +++ b/lib/Multi/Image.pm @@ -10,16 +10,21 @@ use warnings; use POE; use Image::Magick; use Image::MetaData::JPEG; +use Time::HiRes 'time'; sub spawn { my $p = shift; POE::Session->create( package_states => [ - $p => [qw| _start cmd_coverimage format compress update finish |], + $p => [qw| _start + cmd_coverimage cv_process cv_update cv_finish + cmd_screenshot scr_process scr_clean scr_finish + |], ], heap => { - imgpath => '/www/vndb/static/cv' + cvsize => [ 256, 400 ], + scrsize => [ 136, 102 ], }, ); } @@ -29,9 +34,10 @@ sub _start { $_[KERNEL]->alias_set('image'); $_[KERNEL]->sig(shutdown => 'shutdown'); $_[KERNEL]->call(core => register => qr/^coverimage(?: ([0-9]+)|)$/, 'cmd_coverimage'); + $_[KERNEL]->call(core => register => qr/^screenshot ([0-9]+|all|clean)$/, 'cmd_screenshot'); - # check for unprocessed cover images every day on 0:00 and 12:00 local time - $_[KERNEL]->post(core => addcron => '0 0,12 * * *', 'coverimage'); + # daily check for unprocessed cover images + $_[KERNEL]->post(core => addcron => '0 0 * * *', 'coverimage'); } @@ -46,80 +52,183 @@ sub cmd_coverimage { $_[HEAP]{todo} = [ map { -1*$_->[0]} @{$q->fetchall_arrayref([])} ]; if(!@{$_[HEAP]{todo}}) { $_[KERNEL]->call(core => log => 2, 'No images to process'); - $_[KERNEL]->yield('finish'); + $_[KERNEL]->yield('cv_finish'); return; } } - $_[KERNEL]->yield(format => $_[HEAP]{todo}[0]); + $_[KERNEL]->yield(cv_process => $_[HEAP]{todo}[0]); } -sub format { # imgid - $_[HEAP]{imgid} = $_[ARG0]; - $_[HEAP]{img} = sprintf '%s/%02d/%d.jpg', $_[HEAP]{imgpath}, $_[ARG0]%100, $_[ARG0]; - $_[KERNEL]->call(core => log => 3, 'Processing image %d', $_[HEAP]{imgid}); - - $_[HEAP]{im} = Image::Magick->new; - $_[HEAP]{im}->Read($_[HEAP]{img}); - $_[HEAP]{im}->Set(magick => 'JPEG'); - my($w, $h) = ($_[HEAP]{im}->Get('width'), $_[HEAP]{im}->Get('height')); - if($w > 256 || $h > 400) { - $_[KERNEL]->call(core => log => 3, 'Image too large (%dx%d), resizing', $w, $h); - if($w/$h > 256/400) { # width is the limiting factor - $h *= 256/$w; - $w = 256; +sub cv_process { # id + my $start = time; + + my $img = sprintf '%s/%02d/%d.jpg', $VNDB::VNDBopts{imgpath}, $_[ARG0]%100, $_[ARG0]; + + my $os = -s $img; + my $im = Image::Magick->new; + $im->Read($img); + $im->Set(magick => 'JPEG'); + my($w, $h) = ($im->Get('width'), $im->Get('height')); + my($ow, $oh) = ($w, $h); + if($w > $_[HEAP]{cvsize}[0] || $h > $_[HEAP]{cvsize}[1]) { + if($w/$h > $_[HEAP]{cvsize}[0]/$_[HEAP]{cvsize}[1]) { # width is the limiting factor + $h *= $_[HEAP]{cvsize}[0]/$w; + $w = $_[HEAP]{cvsize}[0]; } else { - $w *= 400/$h; - $h = 400; + $w *= $_[HEAP]{cvsize}[1]/$h; + $h = $_[HEAP]{cvsize}[1]; } - $_[HEAP]{im}->Thumbnail(width => $w, height => $h); + $im->Thumbnail(width => $w, height => $h); } + $im->Set(quality => 80); + $im->Write($img); + undef $im; - $_[KERNEL]->yield('compress'); + my $md = Image::MetaData::JPEG->new($img); + $md->drop_segments('METADATA'); + $md->save($img); + + $_[KERNEL]->call(core => log => 2, 'Processed cover image %d in %.2fs: %.2fkB (%dx%d) -> %.2fkB (%dx%d)', + $_[ARG0], time-$start, $os/1024, $ow, $oh, (-s $img)/1024, $w, $h); + $_[KERNEL]->yield(cv_update => $_[ARG0]); } -sub compress { - $_[HEAP]{im}->Set(quality => 80); - $_[HEAP]{im}->Write($_[HEAP]{img}); - undef $_[HEAP]{im}; +sub cv_update { # id + if($Multi::SQL->do('UPDATE vn_rev SET image = ? WHERE image = ?', undef, $_[ARG0], -1*$_[ARG0])) { + $_[KERNEL]->yield(cv_finish => $_[ARG0]); + } elsif(!$_[ARG0]) { + $_[KERNEL]->delay(cv_update => 5 => $_[ARG0]); + } else { + $_[KERNEL]->call(core => log => 1, 'Image %d not present in the database!', $_[ARG0]); + $_[KERNEL]->yield(cv_finish => $_[ARG0]); + } +} + - $_[HEAP]{md} = Image::MetaData::JPEG->new($_[HEAP]{img}); - $_[HEAP]{md}->drop_segments('METADATA'); - $_[HEAP]{md}->save($_[HEAP]{img}); - undef $_[HEAP]{md}; +sub cv_finish { # [id] + if($_[ARG0]) { + $_[HEAP]{todo} = [ grep $_[ARG0]!=$_, @{$_[HEAP]{todo}} ]; + return $_[KERNEL]->yield(cv_process => $_[HEAP]{todo}[0]) + if @{$_[HEAP]{todo}}; + } - $_[KERNEL]->call(core => log => 3, 'Compressed image %d to %.2fkB', $_[HEAP]{imgid}, (-s $_[HEAP]{img})/1024); - $_[KERNEL]->yield('update'); + $_[KERNEL]->post(core => finish => $_[HEAP]{curcmd}); + delete @{$_[HEAP]}{qw| curcmd todo |}; } -sub update { - if($Multi::SQL->do('UPDATE vn_rev SET image = ? WHERE image = ?', undef, $_[HEAP]{imgid}, -1*$_[HEAP]{imgid})) { - $_[KERNEL]->yield('finish'); - } elsif(!$_[ARG0]) { - $_[KERNEL]->delay(update => 3, 3); + + +sub cmd_screenshot { + my($cmd, $id) = @_[ARG0, ARG1]; + $_[HEAP]{curcmd} = $_[ARG0]; + $_[HEAP]{id} = $_[ARG1]; + + if($id eq 'clean') { + return $_[KERNEL]->yield('scr_clean'); + } elsif($id eq 'all') { + my $q = $Multi::SQL->prepare('SELECT DISTINCT scr FROM vn_screenshots'); + $q->execute(); + $_[HEAP]{todo} = [ map $_->[0], @{$q->fetchall_arrayref([])} ]; } else { - $_[KERNEL]->call(core => log => 1, 'Image %d not present in the database!', $_[HEAP]{imgid}); - $_[KERNEL]->yield('finish'); + $_[HEAP]{todo} = [ $_[ARG1] ]; } + $_[KERNEL]->yield(scr_process => $_[HEAP]{todo}[0]); } -sub finish { - if($_[HEAP]{imgid}) { - $_[HEAP]{todo} = [ grep { $_[HEAP]{imgid} != $_ } @{$_[HEAP]{todo}} ]; - if(@{$_[HEAP]{todo}}) { - $_[KERNEL]->yield(format => $_[HEAP]{todo}[0]); - return; +sub scr_process { # id + my $start = time; + + my $sf = sprintf '%s/%02d/%d.jpg', $VNDB::VNDBopts{sfpath}, $_[ARG0]%100, $_[ARG0]; + my $st = sprintf '%s/%02d/%d.jpg', $VNDB::VNDBopts{stpath}, $_[ARG0]%100, $_[ARG0]; + + # convert/compress full-size image + my $os = -s $sf; + my $im = Image::Magick->new; + $im->Read($sf); + $im->Set(magick => 'JPEG'); + $im->Set(quality => 80); + $im->Write($sf); + + # create thumbnail + my($w, $h) = ($im->Get('width'), $im->Get('height')); + my($ow, $oh) = ($w, $h); + if($w/$h > $_[HEAP]{scrsize}[0]/$_[HEAP]{scrsize}[1]) { # width is the limiting factor + $h *= $_[HEAP]{scrsize}[0]/$w; + $w = $_[HEAP]{scrsize}[0]; + } else { + $w *= $_[HEAP]{scrsize}[1]/$h; + $h = $_[HEAP]{scrsize}[1]; + } + $im->Thumbnail(width => $w, height => $h); + $im->Set(quality => 90); + $im->Write($st); + undef $im; + + # remove metadata in both files + my $md = Image::MetaData::JPEG->new($sf); + $md->drop_segments('METADATA'); + $md->save($sf); + $md = Image::MetaData::JPEG->new($st); + $md->drop_segments('METADATA'); + $md->save($st); + undef $md; + + $_[KERNEL]->call(core => log => 2, 'Processed screenshot #%d in %.2fs: %.1fkB -> %.1fkB (%dx%d), thumb: %.1fkB (%dx%d)', + $_[ARG0], time-$start, $os/1024, (-s $sf)/1024, $ow, $oh, (-s $st)/1024, $w, $h); + $_[KERNEL]->yield(scr_finish => $_[ARG0]); +} + + +sub scr_clean { + # not very efficient... + my $q = $Multi::SQL->prepare('SELECT DISTINCT scr FROM vn_screenshots'); + $q->execute(); + my @exists = map $_->[0], @{$q->fetchall_arrayref([])}; + + # not very efficient either... + my @files = map /\/([0-9]+)\.jpg$/?$1:(), glob "$VNDB::VNDBopts{sfpath}/*/*.jpg"; + + my($files, $thumbs, $bytes) = (0,0,0); + for my $id (@files) { + if(!grep $_==$id, @exists) { + my $f = sprintf '%s/%02d/%d.jpg', $VNDB::VNDBopts{stpath}, $id%100, $id; + my $t = sprintf '%s/%02d/%d.jpg', $VNDB::VNDBopts{stpath}, $id%100, $id; + $bytes += -s $f; + $files++; + unlink $f; + if(-f $t) { + $bytes += -s $t; + $thumbs++; + unlink $t; + } + $_[KERNEL]->call(core => log => 3, 'Removing screenshot #%d', $id); } } + $_[KERNEL]->call(core => log => 2, 'Removed %d + %d unused files, total of %.2fMB freed.', + $files, $thumbs, $bytes/1024/1024) if $files; + $_[KERNEL]->call(core => log => 2, 'No unused screenshots found') if !$files; + $_[KERNEL]->yield('scr_finish'); +} + + +sub scr_finish { # [id] + if($_[ARG0]) { + $_[HEAP]{todo} = [ grep $_!=$_[ARG0], @{$_[HEAP]{todo}} ]; + return $_[KERNEL]->yield(scr_process => $_[HEAP]{todo}[0]) + if @{$_[HEAP]{todo}}; + } + $_[KERNEL]->post(core => finish => $_[HEAP]{curcmd}); - delete @{$_[HEAP]}{qw| curcmd imgid img im md todo |}; + delete @{$_[HEAP]}{qw| curcmd todo |}; } + 1; diff --git a/lib/VNDB.pm b/lib/VNDB.pm index be5446e0..441175cb 100644 --- a/lib/VNDB.pm +++ b/lib/VNDB.pm @@ -59,6 +59,7 @@ my %VNDBuris = ( # wildcards: * -> (.+), + -> ([0-9]+) '/' => sub { shift->VNPage(shift) }, stats => sub { shift->VNPage(shift, shift) }, rg => sub { shift->VNPage(shift, shift) }, + scr => sub { shift->VNPage(shift, shift) }, edit => sub { shift->VNEdit(shift) }, vote => sub { shift->VNVote(shift) }, wish => sub { shift->WListMod(shift) }, @@ -113,6 +114,7 @@ my %VNDBuris = ( # wildcards: * -> (.+), + -> ([0-9]+) xml => { 'producers.xml' => sub { shift->PXML }, 'vn.xml' => sub { shift->VNXML }, + 'screenshots.xml' => sub { shift->VNScrXML }, }, ); diff --git a/lib/VNDB/Util/DB.pm b/lib/VNDB/Util/DB.pm index 34ca9ca0..a2f3efe3 100644 --- a/lib/VNDB/Util/DB.pm +++ b/lib/VNDB/Util/DB.pm @@ -782,11 +782,12 @@ sub DBGetVN { # %options->{ id rev char search order results page what cati cate ); $_->{c_released} = sprintf '%08d', $_->{c_released} for @$r; - if($o{what} =~ /(?:relations|categories|anime)/ && $#$r >= 0) { + if($o{what} =~ /(?:relations|categories|anime|screenshots)/ && $#$r >= 0) { my %r = map { $r->[$_]{relations} = []; $r->[$_]{categories} = []; $r->[$_]{anime} = []; + $r->[$_]{screenshots} = []; ($r->[$_]{cid}, $_) } 0..$#$r; @@ -809,6 +810,16 @@ sub DBGetVN { # %options->{ id rev char search order results page what cati cate )}); } + if($o{what} =~ /screenshots/) { + push(@{$r->[$r{$_->{vid}}]{screenshots}}, [ $_->{scr}, $_->{nsfw} ]) for (@{$s->DBAll(q| + SELECT vid, scr, nsfw + FROM vn_screenshots + WHERE vid IN(!l) + ORDER BY scr|, + [ keys %r ] + )}); + } + if($o{what} =~ /relations/) { my $rel = $s->DBAll(q| SELECT rel.vid1, rel.vid2, rel.relation, vr.title @@ -832,7 +843,7 @@ sub DBGetVN { # %options->{ id rev char search order results page what cati cate } -sub DBAddVN { # %options->{ columns in vn_rev + comm + relations + categories + anime } +sub DBAddVN { # %options->{ columns in vn_rev + comm + relations + categories + anime + screenshots } my($s, %o) = @_; my $id = $s->DBRow(q| @@ -854,7 +865,7 @@ sub DBAddVN { # %options->{ columns in vn_rev + comm + relations + categories + } -sub DBEditVN { # id, %options->( columns in vn_rev + comm + relations + categories + anime + uid + causedby } +sub DBEditVN { # id, %options->( columns in vn_rev + comm + relations + categories + anime + screenshots + uid + causedby } my($s, $vid, %o) = @_; my $c = $s->DBRow(q| @@ -892,6 +903,12 @@ sub _insert_vn_rev { ) for (@{$o->{categories}}); $s->DBExec(q| + INSERT INTO vn_screenshots (vid, scr, nsfw) + VALUES (%d, %d, %d)|, + $cid, $_->[0], $_->[1]?1:0 + ) for (@{$o->{screenshots}}); + + $s->DBExec(q| INSERT INTO vn_relations (vid1, vid2, relation) VALUES (%d, %d, %d)|, $cid, $_->[1], $_->[0] diff --git a/lib/VNDB/VN.pm b/lib/VNDB/VN.pm index 30ec94b1..a6e5e735 100644 --- a/lib/VNDB/VN.pm +++ b/lib/VNDB/VN.pm @@ -9,7 +9,7 @@ require bytes; use vars ('$VERSION', '@EXPORT'); $VERSION = $VNDB::VERSION; -@EXPORT = qw| VNPage VNEdit VNLock VNHide VNBrowse VNXML VNUpdReverse |; +@EXPORT = qw| VNPage VNEdit VNLock VNHide VNBrowse VNXML VNScrXML VNUpdReverse |; sub VNPage { @@ -22,12 +22,12 @@ sub VNPage { my $v = $self->DBGetVN( id => $id, - what => 'extended relations categories anime'.($rev ? ' changes' : ''), + what => 'extended relations categories anime screenshots'.($rev ? ' changes' : ''), $rev ? ( rev => $rev ) : () )->[0]; return $self->ResNotFound if !$v->{id}; - my $c = $rev && $rev > 1 && $self->DBGetVN(id => $id, rev => $rev-1, what => 'extended changes relations categories anime')->[0]; + my $c = $rev && $rev > 1 && $self->DBGetVN(id => $id, rev => $rev-1, what => 'extended changes relations categories anime screenshots')->[0]; $v->{next} = $rev && $v->{latest} > $v->{cid} ? $rev+1 : 0; my $rel = $self->DBGetRelease(vid => $id, what => 'producers platforms'); @@ -74,7 +74,7 @@ sub VNEdit { return $self->ResNotFound if $rev->{_err}; $rev = $rev->{rev}; - my $v = $self->DBGetVN(id => $id, what => 'extended changes relations categories anime', $rev ? ( rev => $rev ) : ())->[0] if $id; + my $v = $self->DBGetVN(id => $id, what => 'extended changes relations categories anime screenshots', $rev ? ( rev => $rev ) : ())->[0] if $id; return $self->ResNotFound() if $id && !$v; return $self->ResDenied if !$self->AuthCan('edit') || ($v->{locked} && !$self->AuthCan('lock')); @@ -84,6 +84,7 @@ sub VNEdit { relations => join('|||', map { $_->{relation}.','.$_->{id}.','.$_->{title} } @{$v->{relations}}), categories => join(',', map { $_->[0].$_->[1] } sort { $a->[0] cmp $b->[0] } @{$v->{categories}}), anime => join(' ', sort { $a <=> $b } map $_->{id}, @{$v->{anime}}), + screenshots => join(' ', map "$$_[0],$$_[1]", @{$v->{screenshots}}), ) : (); my $frm = {}; @@ -101,17 +102,19 @@ sub VNEdit { { name => 'img_nsfw', required => 0 }, { name => 'categories', required => 0, default => '' }, { name => 'relations', required => 0, default => '' }, + { name => 'screenshots', required => 0, default => '' }, { name => 'comm', required => 0, default => '' }, ); $frm->{img_nsfw} = $frm->{img_nsfw} ? 1 : 0; $frm->{anime} = join(' ', sort { $a <=> $b } grep /^[0-9]+$/, split(/\s+/, $frm->{anime})); # re-sort return $self->ResRedirect('/v'.$id, 'post') - if $id && !$self->ReqParam('img') && 12 == scalar grep { $b4{$_} eq $frm->{$_} } keys %b4; + if $id && !$self->ReqParam('img') && 13 == scalar grep { $b4{$_} eq $frm->{$_} } keys %b4; my $relations = [ map { /^([0-9]+),([0-9]+)/ && $2 != $id ? ( [ $1, $2 ] ) : () } split /\|\|\|/, $frm->{relations} ]; my $cat = [ map { [ substr($_,0,3), substr($_,3,1) ] } split /,/, $frm->{categories} ]; - my $anime = [ split / /, $frm->{anime} ]; + my $anime = [ split / +/, $frm->{anime} ]; + my $screenshots = [ map [split /,/], grep /^[0-9]+,[01]$/, split / +/, $frm->{screenshots} ]; # upload image my $imgid = 0; @@ -149,6 +152,7 @@ sub VNEdit { anime => $anime, relations => $relations, categories => $cat, + screenshots => $screenshots, ); if(!$frm->{_err}) { @@ -317,6 +321,47 @@ sub VNXML { } +sub VNScrXML { + my $self = shift; + + return $self->ResNotFound if $self->ReqMethod ne 'POST'; + return $self->ResDenied if !$self->AuthCan('edit'); + + my $i = $self->FormCheck( + { name => 'itemnumber', required => 1, template => 'int' } + ); + return $self->ResNotFound if $i->{_err}; + $i = $i->{itemnumber}; + + my $tmp = sprintf '%s/00/tmp.%d.jpg', $self->{sfpath}, $$*int(rand(1000)+1); + $self->ReqSaveUpload('scrAddFile'.$i, $tmp); + + my $id = 0; + $id = -2 if !-s $tmp; + if(!$id) { + my $l; + open(my $T, '<:raw:bytes', $tmp) || die $1; + read $T, $l, 2; + close($T); + $id = -1 if $l ne pack('H*', 'ffd8') && $l ne pack('H*', '8950'); + } + + if($id) { + unlink $tmp; + } else { + $id = $self->DBIncId('screenshots_seq'); + my $new = sprintf '%s/%02d/%d.jpg', $self->{sfpath}, $id%100, $id; + rename $tmp, $new or die $!; + chmod 0666, $new; + $self->RunCmd(sprintf 'screenshot %d', $id); + } + + my $x = $self->ResStartXML; + $x->pi('xml-stylesheet', 'href="'.$self->{static_url}.'/files/blank.css" type="text/css"'); + $x->emptyTag('image', id => $id); +} + + # Update reverse relations sub VNUpdReverse { # old, new, id, cid, rev my($self, $old, $new, $id, $cid, $rev) = @_; diff --git a/lib/global.pl b/lib/global.pl index e0c44016..ba5607d2 100644 --- a/lib/global.pl +++ b/lib/global.pl @@ -38,8 +38,10 @@ our %VNDBopts = ( {map{$_,1}qw| hist board boardmod edit mod lock del usermod |}, # 4 - admin ], postsperpage => 25, - imgpath => '/www/vndb/static/cv', - mappath => '/www/vndb/data/rg', + imgpath => '/www/vndb/static/cv', # cover images + sfpath => '/www/vndb/static/sf', # full-size screenshots + stpath => '/www/vndb/static/st', # screenshot thumbnails + mappath => '/www/vndb/data/rg', # image maps for the relation graphs docpath => '/www/vndb/data/docs', ); $VNDBopts{ranks}[0][1] = { (map{$_,1} map { keys %{$VNDBopts{ranks}[$_]} } 1..5) }; diff --git a/static/files/blank.css b/static/files/blank.css new file mode 100644 index 00000000..e69de29b --- /dev/null +++ b/static/files/blank.css diff --git a/static/files/def.js b/static/files/def.js index d377bf09..7e7e46d9 100644 --- a/static/files/def.js +++ b/static/files/def.js @@ -43,9 +43,14 @@ function formhid() { formsubs[i] = !formsubs[i]; } } + if(x('screenshots') && !formsubs['scr']) + scrLoad(); } function formtoggle(n) { formsubs[n] = !formsubs[n]; + if(x('screenshots') && !formsubs['scr'] && !x('scrTbl')) + scrLoad(); + var i; var l = document.forms[1].getElementsByTagName('a'); for(i=0; i<l.length; i++) @@ -208,6 +213,102 @@ function ad_dosearch() { +/* S C R E E N S H O T S */ + +var scrNsfwEnabled = null; +function scrNsfwHid() { + var l=x('screenshots').getElementsByTagName('a'); + var i; + if(scrNsfwEnabled == null) + scrNsfwEnabled = 0; + else { + for(i=0;i<l.length;i++) + if(l[i].className.indexOf('scr_nsfw')>=0) + l[i].style.display = scrNsfwEnabled ? 'block' : 'none'; + scrNsfwEnabled = scrNsfwEnabled ? 0 : 1; + } + + var t=0;var n=0; + for(i=0;i<l.length;i++) { + t++; + if(l[i].className.indexOf('scr_nsfw')>=0) + n++; + if(l[i].style.display == 'none') + scrNsfwEnabled = 1; + } + x('scrNsfwHid').innerHTML = 'Showing '+(t-(scrNsfwEnabled?n:0))+' out of '+t+' items. ' + +(scrNsfwEnabled ? '<a href="javascript:scrNsfwHid()">Show</a> / hide' : 'Show / <a href="javascript:scrNsfwHid()">hide</a>') + +' nsfw.'; +} +var scrInt; +function scrView() { + var u=this.href; + var ol=x('screenshots').getElementsByTagName('a'); + var l=[]; + + // remove NSFW + for(i=0;i<ol.length;i++) + if(!scrNsfwEnabled || ol[i].className.indexOf('scr_nsfw')<0) + l[l.length] = ol[i]; + + // fix prev/next links + for(i=0;i<l.length;i++) + if(l[i].href == u) { + x('scrnext').style.visibility = l[i+1] ? 'visible' : 'hidden'; + x('scrnext').href = l[i+1] ? l[i+1].href : '#'; + x('scrprev').style.visibility = l[i-1] ? 'visible' : 'hidden'; + x('scrprev').href = l[i-1] ? l[i-1].href : '#'; + } + + // show image + x('preload').src = u; + x('scrimg').innerHTML = 'Loading...'; + x('scrView').style.display = 'block'; + scrPosition(1); + return false; +} +function scrPosition(act) { + d = x('scrView'); + m = x('preload'); + // why don't all browsers use the same DOM... + var ww = typeof(window.innerWidth) == 'number' ? window.innerWidth : document.documentElement.clientWidth; + var wh = typeof(window.innerHeight) == 'number' ? window.innerHeight : document.documentElement.clientHeight; + var st = typeof(window.pageYOffset) == 'number' ? window.pageYOffset : document.body && document.body.scrollTop ? document.body.scrollTop : document.documentElement.scrollTop; + var h=20;var w=200; + if(act != 1) { + w = m.offsetWidth; + h = m.offsetHeight; + if(w+100 > ww || h+100 > wh) { + if(w/h > ww/wh) { // width++ + h *= (ww-100)/w; + w = ww-100; + } else { // height++ + w *= (wh-100)/h; + h = wh-100; + } + } + } + var dw = w; + var dh = h+20; + dw = dw < 200 ? 200 : dw; + d.style.width = dw+'px'; + d.style.height = dh+'px'; + d.style.left = ((ww - dw) / 2 - 10)+'px'; + d.style.top = ((wh - dh) / 2 + st)+'px'; + if(act != 1) + x('scrimg').innerHTML = '<img src="'+x('preload').src+'" onclick="scrClose()" style="width: '+w+'px; height: '+h+'px" />'; +} +function scrClose() { + clearInterval(scrInt); + scrInt = null; + x('scrView').style.display = 'none'; + x('scrView').style.top = '-5000px'; + x('scrimg').innerHTML = ''; + return false; +} + + + /* O N L O A D */ @@ -301,6 +402,15 @@ DOMLoad(function() { } } + // screenshots + if(x('scrNsfwHid')) + scrNsfwHid(); + if(x('scrView')) { + l=x('screenshots').getElementsByTagName('a'); + for(i=0;i<l.length;i++) + l[i].onclick=scrView; + x('scrnext').onclick = x('scrprev').onclick = scrView; + } // spam protection on all forms if(document.forms.length > 1) diff --git a/static/files/dyna.js b/static/files/dyna.js index 1c9c262d..639acfa9 100644 --- a/static/files/dyna.js +++ b/static/files/dyna.js @@ -59,6 +59,10 @@ function dInit() { ct = x('categories'); if(ct) catLoad(); + +/* scrLoad() is called by the form sub functions in def.js + if(x('scrfrm')) + scrLoad();*/ } function qq(v) { @@ -579,3 +583,195 @@ function catSet(id, rnk) { } + + + + + + /***************************\ + * S C R E E N S H O T S * + \***************************/ + + +var scrL = []; // id, load, nsfw, obj +function scrLoad() { + // 'screenshots' format: id,nsfw id,nsfw .. + var l=x('screenshots').value.split(' '); + for(var i=0;i<l.length;i++) + if(l[i].length > 2) + scrL[i] = { load: 0, id: l[i].split(',')[0], nsfw: l[i].split(',')[1]>0?1:0 }; + + // <tbody> because IE can't operate on <table> + x('scrfrm').innerHTML = '<table><tbody id="scrTbl"></tbody></table>'; + for(i=0;i<scrL.length;i++) + scrGenerateTR(i); + scrGenerateTR(i); + + setTimeout(scrSetSubmit, 1000); +} + +// give an error when submitting the form while still uploading an image +function scrSetSubmit() { + var o=document.forms[1].onsubmit; + document.forms[1].onsubmit = function() { + var c=0; + for(var i=0;i<scrL.length;i++) + if(scrL[i] && scrL[i].load) + c=1; + if(!c) + return o(); + alert('Please wait for the screenshots to be uploaded before submitting the form.'); + return false; + }; +} + +function scrURL(id, t) { + return x('scrfrm').className+'/s'+t+'/'+(id%100<10?'0':'')+(id%100)+'/'+id+'.jpg'; +} + +function scrGenerateTR(i) { + if(!scrL[i]) + scrL[i] = { id: 0, load: 0 }; + var r = '<b style="width: auto; float: none;margin: 0; padding: 0; font-weight: bold">'; + if(!scrL[i].id && !scrL[i].load) { + var c=0; + for(var j=0,c=0; j<scrL.length; j++) + if(scrL[j] && (scrL[j].load || scrL[j].id)) + c++; + if(c >= 10) + r += 'Enough screenshots</b>' + +'The limit of 10 screenshots per visual novel has been reached. ' + +'If you want to add a new screenshot, please remove an existing one first.'; + else + r += 'Add screenshot</b>' + +'<input type="file" name="scrAddFile'+i+'" id="scrAddFile'+i+'" style="float: none; height: auto; width: auto;" />' + +'<input type="button" value="Upload!" style="float: none; height: auto; width: auto; display: inline;" onclick="scrUpload('+i+')" />'; + } + if(scrL[i].load && scrL[i].load == 1) + r += 'Uploading...</b>This could take a while, depending on the file size and your upload speed.<br />' + +'<a href="javascript:scrDel('+i+')">cancel</a>'; + if(scrL[i].load && scrL[i].load == 2) + r += 'Generating thumbnail...</b>Note: if this takes longer than 30 seconds, there\'s probably something wrong on our side.' + +'Please try again later or report a bug if that is the case.'; + if(scrL[i].id && !scrL[i].load) + r += 'Screenshot #'+scrL[i].id+'</b>' + +'<input type="checkbox" name="scrNSFW'+i+'" id="scrNSFW'+i+'"'+(scrL[i].nsfw?' checked="checked"':'')+' style="float: left" onclick="scrSer()" /> ' + +'<label for="scrNSFW'+i+'" class="checkbox"> This screenshot is NSFW.</label>' + +'<input type="button" value="remove" onclick="scrDel('+i+')" style="float: right; width: auto; height: auto" />'; + + if(scrL[i].obj) { + x('scrTr'+i).getElementsByTagName('td')[1].innerHTML = r; + return; + } + + // the slow and tedious way, because we need to use DOM functions to manipulate the table contents... + var o = document.createElement('tr'); + o.setAttribute('id', 'scrTr'+i); + o.style.cssText = 'border-top: 1px solid #ccc'; + var d = document.createElement('td'); + d.style.cssText = 'width: 141px; height: 102px; padding: 0;'; + d.innerHTML = scrL[i].id && !scrL[i].load ? '<img src="'+scrURL(scrL[i].id, 't')+'" style="margin: 0; padding: 0; border: 0" />' : ' '; + var e = document.createElement('td'); + e.innerHTML = r; + o.appendChild(d); + o.appendChild(e); + x('scrTbl').appendChild(o); + scrL[i].obj = o; + scrStripe(); +} + +function scrUpload(i) { + scrL[i].load = 1; + // move the file selection box into a temporary form and post it into a temporary iframe + var d = document.createElement('div'); + d.id = 'scrUpl'+i; + d.style.cssText = 'visibility: hidden; overflow: hidden; width: 1px; height: 1px; position: absolute; left: -500px; top: -500px'; + d.innerHTML = '<iframe name="scrIframe'+i+'" id="scrIframe'+i+'" style="height: 0px; width: 0px; visibility: hidden"' + +' src="about:blank" onload="scrUploadComplete('+i+')"></iframe>' + +'<form method="post" action="/xml/screenshots.xml" target="scrIframe'+i+'" enctype="multipart/form-data" id="scrUplFrm'+i+'" name="scrUplFrm'+i+'">' + +'<input type="hidden" name="itemnumber" value="'+i+'" />' + +'</form>'; + document.body.appendChild(d); + x('scrUplFrm'+i).appendChild(x('scrAddFile'+i)); + x('scrUplFrm'+i).submit(); + scrGenerateTR(i); + scrGenerateTR(i+1); + return false; +} + +function scrStripe() { + var l = x('scrTbl').getElementsByTagName('tr'); + for(var j=0; j<l.length; j++) + l[j].style.backgroundColor = j%2==0 ? '#fff' : '#f5f5f5'; +} + +function scrUploadComplete(i) { + if(window.frames['scrIframe'+i].location.href.indexOf('screenshots') > 0) { + try { + scrL[i].id = window.frames['scrIframe'+i].window.document.getElementsByTagName('image')[0].getAttribute('id'); + } catch(e) { + scrL[i].id = -10; + } + if(scrL[i].id < 0) { + alert( + scrL[i].id == -10 ? + 'Oops! Seems like something went wrong...\n' + +'Make sure the file you\'re uploading doesn\'t exceed 5MB in size.\n' + +'If that isn\'t the problem, then please report a bug.' : + scrL[i].id == -1 ? + 'Upload failed!\nOnly JPEG or PNG images are accepted.' : + 'Upload failed!\nNo file selected, or an empty file?'); + return scrDel(i); + } + scrL[i].load = 2; + scrGenerateTR(i); + scrImageFail(i); + } +} + +function scrImageFail(i) { + if(scrL[i].timer) + clearTimeout(scrL[i].timer); + if(!scrL[i].load) + return; + scrL[i].timer = setTimeout(function() { + if(!scrL[i].load) + return; + x('scrTr'+i).getElementsByTagName('td')[0].innerHTML = + '<img src="'+scrURL(scrL[i].id, 't')+'?'+(Math.floor(Math.random()*999)+1)+'" onload="scrImageSuccess('+i+')"' + +' onerror="scrImageFail('+i+')" style="visibility: hidden; width: 0px; height: 0px;" id="scrImage'+i+'" />'; + setTimeout('scrImageFail('+i+')', 7000); + }, 2000); +} + +function scrImageSuccess(i) { + scrL[i].load = 0; + x('scrImage'+i).style.cssText = 'margin: 0; padding: 0; border: 0;'; + scrGenerateTR(i); + scrSer(); +} + +function scrDel(i) { + x('scrTbl').removeChild(x('scrTr'+i)); + if(scrL[i].load) + document.body.removeChild(x('scrUpl'+i)); + scrL[i]=null; + scrGenerateTR(scrL.length-1); + scrSer(); + scrStripe(); +} + +function scrSer() { + var r=''; + for(var i=0;i<scrL.length;i++) { + if(scrL[i] && scrL[i].id && !scrL[i].load) { + scrL[i].nsfw = x('scrNSFW'+i).checked ? '1' : '0'; + r += ' '+scrL[i].id+','+scrL[i].nsfw; + } + } + x('screenshots').value = r; +} + + + + diff --git a/static/files/style.css b/static/files/style.css index ded407bd..65e02102 100644 --- a/static/files/style.css +++ b/static/files/style.css @@ -606,6 +606,53 @@ i.crgn3 { font-style: normal; font-weight: bold; } #dpage .retired { text-decoration: line-through; } #dpage dt b { color: #999; font-weight: normal; font-style: normal; font-size: 12px; } +/* Screenshots */ +div#screenshots { + display: block; + width: 700px; + margin: 0; + padding: 0; +} +div#screenshots a { + display: block; + float: left; + width: 136px; + height: 102px; + margin: 0; + padding: 2px; + text-decoration: none; + text-align: center; +} +div#screenshots a:hover { + background-color: #ccc; +} +div#screenshots a b { + display: block; + margin: -16px 0 0 0; + padding: 0; + height: 14px; + width: 136px; + text-align: right; + font-size: 12px; + color: #f00; +} +div#screenshots img { border: 0; padding: 0; } +div#scrView { + position: absolute; + display: none; + top: -5000px; + left: -5000px; + background-color: #fff; + border: 2px solid #ccc; + padding: 10px; + text-align: center; +} +#scrView img { cursor: pointer } +#scrclose { float: right; padding-left: 10px; } +#scrnext { padding-left: 5px; } +#scrprev { padding-right: 5px; } +#preload { visibility: hidden; position: absolute; left: -9000px; top: -9000px } + #content input.right, #content select.right { diff --git a/util/dump.sql b/util/dump.sql index 884c06e8..f2db8bc2 100644 --- a/util/dump.sql +++ b/util/dump.sql @@ -214,6 +214,14 @@ CREATE TABLE vn_rev ( l_renai varchar(100) NOT NULL DEFAULT '' ); +-- vn_screenshots +CREATE TABLE vn_screenshots ( + vid integer NOT NULL DEFAULT 0, + scr integer NOT NULL DEFAULT 0, + nsfw smallint NOT NULL DEFAULT 0, + PRIMARY KEY(vid, scr) +); + -- vnlists CREATE TABLE vnlists ( uid integer DEFAULT 0, @@ -278,6 +286,7 @@ ALTER TABLE vn_relations ADD FOREIGN KEY (vid1) REFERENCES vn_rev ALTER TABLE vn_relations ADD FOREIGN KEY (vid2) REFERENCES vn (id) DEFERRABLE INITIALLY DEFERRED; ALTER TABLE vn_rev ADD FOREIGN KEY (id) REFERENCES changes (id) DEFERRABLE INITIALLY DEFERRED; ALTER TABLE vn_rev ADD FOREIGN KEY (vid) REFERENCES vn (id) DEFERRABLE INITIALLY DEFERRED; +ALTER TABLE vn_screenshots ADD FOREIGN KEY (vid) REFERENCES vn (id) DEFERRABLE INITIALLY DEFERRED; ALTER TABLE vnlists ADD FOREIGN KEY (uid) REFERENCES users (id) DEFERRABLE INITIALLY DEFERRED; ALTER TABLE vnlists ADD FOREIGN KEY (vid) REFERENCES vn (id) DEFERRABLE INITIALLY DEFERRED; ALTER TABLE votes ADD FOREIGN KEY (uid) REFERENCES users (id) DEFERRABLE INITIALLY DEFERRED; @@ -379,9 +388,10 @@ $$ LANGUAGE plpgsql; --------------------------------- --- Two sequences used for cover and relation graph ID numbers +-- Sequences used for cover, relation graph and screenshot ID numbers CREATE SEQUENCE covers_seq; CREATE SEQUENCE relgraph_seq; +CREATE SEQUENCE screenshots_seq; INSERT INTO users (id, username, mail, rank) VALUES (0, 'deleted', 'del@vndb.org', 0); diff --git a/util/fcgi.pl b/util/fcgi.pl index 60f3c324..a1fb1d92 100644 --- a/util/fcgi.pl +++ b/util/fcgi.pl @@ -34,6 +34,7 @@ while($req->Accept() >= 0) { eval { CGI::Minimal::reset_globals; CGI::Minimal::allow_hybrid_post_get(1); + CGI::Minimal::max_read_size(5*1024*1024); # allow 5MB of POST data $c = CGI::Minimal->new(); }; if($@) { @@ -116,7 +117,7 @@ while($req->Accept() >= 0) { } sub send500 { - print "HTTP/1.0 500 Internal Server Error\n"; + print "Status: 500 Internal Server Error\n"; print "Content-Type: text/html\n"; print "X-Sendfile: /www/vndb/www/files/err.html\n\n"; } diff --git a/util/updates/update_1.21.pl b/util/updates/update_1.21.pl new file mode 100755 index 00000000..d59d3603 --- /dev/null +++ b/util/updates/update_1.21.pl @@ -0,0 +1,15 @@ +#!/usr/bin/perl + +# create static/sf and static/st with subdirectories +chdir '/www/vndb/static'; + +sub mk { + for (@_) { + mkdir $_ or die "mkdir: $_: $!"; + chmod 0777, $_ or die "chmod: $_: $!"; + } +} + +mk 'sf', 'st'; +mk sprintf('sf/%02d',$_), sprintf('st/%02d',$_) for (0..99); + diff --git a/util/updates/update_1.21.sql b/util/updates/update_1.21.sql new file mode 100644 index 00000000..c8457498 --- /dev/null +++ b/util/updates/update_1.21.sql @@ -0,0 +1,12 @@ + +-- screenshots +--CREATE SEQUENCE screenshots_seq; + +CREATE TABLE vn_screenshots ( + vid integer NOT NULL DEFAULT 0 REFERENCES vn_rev (id) DEFERRABLE INITIALLY DEFERRED, + scr integer NOT NULL DEFAULT 0, + nsfw smallint NOT NULL DEFAULT 0, + PRIMARY KEY(vid, scr) +) WITHOUT OIDS; + + |