summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--data/tpl/defs.pl3
-rw-r--r--data/tpl/vnedit4
-rw-r--r--data/tpl/vnpage31
-rw-r--r--data/tpl/vnpage_scr34
-rw-r--r--lib/ChangeLog2
-rw-r--r--lib/Multi/Image.pm205
-rw-r--r--lib/VNDB.pm2
-rw-r--r--lib/VNDB/Util/DB.pm23
-rw-r--r--lib/VNDB/VN.pm57
-rw-r--r--lib/global.pl6
-rw-r--r--static/files/blank.css0
-rw-r--r--static/files/def.js110
-rw-r--r--static/files/dyna.js196
-rw-r--r--static/files/style.css47
-rw-r--r--util/dump.sql12
-rw-r--r--util/fcgi.pl3
-rwxr-xr-xutil/updates/update_1.21.pl15
-rw-r--r--util/updates/update_1.21.sql12
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&amp;task=view&amp;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&amp;task=view&amp;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 &amp; releases</b>' : '<a href="/v'.$d{vn}{id}.'">description &amp; 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' : '&nbsp;' ]]</b></a>
+[[ } ]]-
+</div>
+[[ if($nsfw) { ]]-
+<br style="clear: left" /><br />
+<p id="scrNsfwHid">&nbsp;</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">&lt;- previous</a>
+<a href="#" id="scrnext">next -&gt;</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">&nbsp;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" />' : '&nbsp;';
+ 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;
+
+