diff --git a/.github/workflows/push-continous-delivery.yml b/.github/workflows/push-continous-delivery.yml
index 5faf7bcd8..559409a60 100644
--- a/.github/workflows/push-continous-delivery.yml
+++ b/.github/workflows/push-continous-delivery.yml
@@ -65,8 +65,11 @@ jobs:
- name: Build MSI Installer
shell: powershell
run: |
- Import-Module "C:\Program Files (x86)\Microsoft Visual Studio\2019\Enterprise\Common7\Tools\Microsoft.VisualStudio.DevShell.dll";
- Enter-VsDevShell -VsInstallPath "C:\Program Files (x86)\Microsoft Visual Studio\2019\Enterprise" -SkipAutomaticLocation
+ [array]$installPath = &"C:\Program Files (x86)\Microsoft Visual Studio\Installer\vswhere.exe" -property installationpath
+ # Get first line of installPath in case we have multiple VS installs
+ Import-Module (Join-Path $installPath[0] "Common7\Tools\Microsoft.VisualStudio.DevShell.dll")
+ # Import the VS shell module
+ Enter-VsDevShell -VsInstallPath $installPath[0] -SkipAutomaticLocation
$ErrorActionPreference = 'Continue'
git submodule init
git submodule update
diff --git a/.github/workflows/release-delivery.yml b/.github/workflows/release-delivery.yml
index 09dbdc5cc..fcefa3e20 100644
--- a/.github/workflows/release-delivery.yml
+++ b/.github/workflows/release-delivery.yml
@@ -32,8 +32,11 @@ jobs:
- name: Build MSI Installer
shell: powershell
run: |
- Import-Module "C:\Program Files (x86)\Microsoft Visual Studio\2019\Enterprise\Common7\Tools\Microsoft.VisualStudio.DevShell.dll";
- Enter-VsDevShell -VsInstallPath "C:\Program Files (x86)\Microsoft Visual Studio\2019\Enterprise" -SkipAutomaticLocation
+ [array]$installPath = &"C:\Program Files (x86)\Microsoft Visual Studio\Installer\vswhere.exe" -property installationpath
+ # Get first line of installPath in case we have multiple VS installs
+ Import-Module (Join-Path $installPath[0] "Common7\Tools\Microsoft.VisualStudio.DevShell.dll")
+ # Import the VS shell module
+ Enter-VsDevShell -VsInstallPath $installPath[0] -SkipAutomaticLocation
$ErrorActionPreference = 'Continue'
git submodule init
git submodule update
@@ -63,7 +66,7 @@ jobs:
env:
DISCORD_WEBHOOK: ${{ secrets.DISCORD_WEBHOOK }}
with:
- args: 'Windows Installer built and available on the Release page! :logo:🪟'
+ args: 'Windows Installer built and available on the Release page! <:logo:821516019179978772>🪟'
buildLatestDocker:
name: Build Latest Docker image
diff --git a/lib/LANraragi/Controller/Api/Minion.pm b/lib/LANraragi/Controller/Api/Minion.pm
new file mode 100644
index 000000000..2be6845c5
--- /dev/null
+++ b/lib/LANraragi/Controller/Api/Minion.pm
@@ -0,0 +1,69 @@
+package LANraragi::Controller::Api::Minion;
+use Mojo::Base 'Mojolicious::Controller';
+
+use Mojo::JSON qw(encode_json decode_json);
+use Redis;
+
+use LANraragi::Model::Stats;
+use LANraragi::Utils::TempFolder qw(get_tempsize clean_temp_full);
+use LANraragi::Utils::Generic qw(render_api_response);
+use LANraragi::Utils::Plugins qw(get_plugin get_plugins get_plugin_parameters use_plugin);
+
+# Returns basic info for the given Minion job id.
+sub minion_job_status {
+ my $self = shift;
+ my $id = $self->stash('jobid');
+ my $job = $self->minion->job($id);
+
+ if ($job) {
+
+ my %info = %{ $job->info };
+
+ # Render a basic json containing only task, state and error
+ $self->render(
+ json => {
+ task => $info{task},
+ state => $info{state},
+ error => $info{error}
+ }
+ );
+
+ } else {
+ render_api_response( $self, "minion_job_status", "No job with this ID." );
+ }
+}
+
+# Returns the full info for the given Minion job id.
+sub minion_job_detail {
+ my $self = shift;
+ my $id = $self->stash('jobid');
+ my $job = $self->minion->job($id);
+
+ if ($job) {
+ $self->render( json => $job->info );
+ } else {
+ render_api_response( $self, "minion_job_detail", "No job with this ID." );
+ }
+}
+
+# Queues a job into Minion.
+sub queue_minion_job {
+
+ my ($self) = shift;
+ my $jobname = $self->stash('jobname');
+ my @jobargs = decode_json( $self->req->param('args') );
+ my $priority = $self->req->param('priority') || 0;
+
+ my $jobid = $self->minion->enqueue( $jobname => @jobargs => { priority => $priority } );
+
+ $self->render(
+ json => {
+ operation => "queue_minion_job",
+ success => 1,
+ job => $jobid
+ }
+ );
+}
+
+1;
+
diff --git a/lib/LANraragi/Controller/Api/Other.pm b/lib/LANraragi/Controller/Api/Other.pm
index 0650e44f9..35f7b76aa 100644
--- a/lib/LANraragi/Controller/Api/Other.pm
+++ b/lib/LANraragi/Controller/Api/Other.pm
@@ -70,20 +70,6 @@ sub list_plugins {
$self->render( json => \@plugins );
}
-# Returns the info for the given Minion job id.
-sub minion_job_status {
- my $self = shift;
- my $id = $self->stash('jobid');
-
- my $job = $self->minion->job($id);
-
- if ($job) {
- $self->render( json => $job->info );
- } else {
- render_api_response( $self, "minion_job_status", "No job with this ID." );
- }
-}
-
# Queue the regen_all_thumbnails Minion job.
sub regen_thumbnails {
my $self = shift;
@@ -102,7 +88,6 @@ sub regen_thumbnails {
}
sub download_url {
-
my ($self) = shift;
my $url = $self->req->param('url');
my $catid = $self->req->param('catid');
@@ -130,8 +115,7 @@ sub download_url {
# Uses a plugin, with the standard global arguments and a provided oneshot argument.
sub use_plugin_sync {
-
- my ($self) = shift;
+ my ($self) = shift;
my $id = $self->req->param('id') || 0;
my $plugname = $self->req->param('plugin');
my $input = $self->req->param('arg');
@@ -153,7 +137,6 @@ sub use_plugin_sync {
# Queues a plugin execution into Minion.
sub use_plugin_async {
-
my ($self) = shift;
my $id = $self->req->param('id') || 0;
my $priority = $self->req->param('priority') || 0;
@@ -171,24 +154,5 @@ sub use_plugin_async {
);
}
-# Queues a job into Minion.
-sub queue_minion_job {
-
- my ($self) = shift;
- my $jobname = $self->stash('jobname');
- my @jobargs = decode_json( $self->req->param('args') );
- my $priority = $self->req->param('priority') || 0;
-
- my $jobid = $self->minion->enqueue( $jobname => @jobargs => { priority => $priority } );
-
- $self->render(
- json => {
- operation => "queue_minion_job",
- success => 1,
- job => $jobid
- }
- );
-}
-
1;
diff --git a/lib/LANraragi/Controller/Api/Shinobu.pm b/lib/LANraragi/Controller/Api/Shinobu.pm
index 9fa6f77fb..0001fb7aa 100644
--- a/lib/LANraragi/Controller/Api/Shinobu.pm
+++ b/lib/LANraragi/Controller/Api/Shinobu.pm
@@ -19,6 +19,32 @@ sub shinobu_status {
);
}
+sub reset_filemap {
+ my $self = shift;
+
+ # This is a shinobu endpoint even though we're deleting stuff in redis
+ # since we'll have to restart shinobu anyway to proc filemap re-creation.
+
+ my $redis = $self->LRR_CONF->get_redis;
+ $redis->del("LRR_FILEMAP");
+
+ my $shinobu = ${ retrieve( get_temp . "/shinobu.pid" ) };
+
+ #commit sudoku
+ $shinobu->kill();
+
+ # Create a new Process, automatically stored in TEMP_FOLDER/shinobu.pid
+ my $proc = start_shinobu($self);
+
+ $self->render(
+ json => {
+ operation => "shinobu_rescan",
+ success => $proc->poll(),
+ new_pid => $proc->pid
+ }
+ );
+}
+
sub stop_shinobu {
my $self = shift;
my $shinobu = ${ retrieve( get_temp . "/shinobu.pid" ) };
diff --git a/lib/LANraragi/Controller/Upload.pm b/lib/LANraragi/Controller/Upload.pm
index c144419c0..30d70975e 100644
--- a/lib/LANraragi/Controller/Upload.pm
+++ b/lib/LANraragi/Controller/Upload.pm
@@ -2,7 +2,7 @@ package LANraragi::Controller::Upload;
use Mojo::Base 'Mojolicious::Controller';
use Redis;
-use File::Temp qw/ tempfile tempdir /;
+use File::Temp qw(tempdir);
use File::Copy;
use File::Find;
use File::Basename;
diff --git a/lib/LANraragi/Model/Archive.pm b/lib/LANraragi/Model/Archive.pm
index 52c0ae033..c2f6dfe7c 100644
--- a/lib/LANraragi/Model/Archive.pm
+++ b/lib/LANraragi/Model/Archive.pm
@@ -8,8 +8,8 @@ use Cwd 'abs_path';
use Redis;
use Time::HiRes qw(usleep);
use File::Basename;
-use File::Temp qw(tempfile);
use File::Copy "cp";
+use File::Path qw(make_path);
use Mojo::Util qw(xml_escape);
use LANraragi::Utils::Generic qw(get_tag_with_namespace remove_spaces remove_newlines render_api_response);
@@ -54,11 +54,11 @@ sub generate_opds_catalog {
my $tags = $arcdata->{tags};
# Infer a few OPDS-related fields from the tags
- $arcdata->{dateadded} = get_tag_with_namespace( "dateadded", $tags, "2010-01-10T10:01:11Z" );
- $arcdata->{author} = get_tag_with_namespace( "artist", $tags, "" );
- $arcdata->{language} = get_tag_with_namespace( "language", $tags, "" );
- $arcdata->{circle} = get_tag_with_namespace( "group", $tags, "" );
- $arcdata->{event} = get_tag_with_namespace( "event", $tags, "" );
+ $arcdata->{dateadded} = get_tag_with_namespace( "date_added", $tags, "2010-01-10T10:01:11Z" );
+ $arcdata->{author} = get_tag_with_namespace( "artist", $tags, "" );
+ $arcdata->{language} = get_tag_with_namespace( "language", $tags, "" );
+ $arcdata->{circle} = get_tag_with_namespace( "group", $tags, "" );
+ $arcdata->{event} = get_tag_with_namespace( "event", $tags, "" );
# Application/zip is universally hated by all readers so it's better to use x-cbz and x-cbr here.
if ( $file =~ /^(.*\/)*.+\.(pdf)$/ ) {
@@ -124,7 +124,7 @@ sub find_untagged_archives {
remove_newlines($t);
# The following are basic and therefore don't count as "tagged"
- $nondefaulttags += 1 unless $t =~ /(artist|parody|series|language|event|group|date_added):.*/;
+ $nondefaulttags += 1 unless $t =~ /(artist|parody|series|language|event|group|date_added|timestamp):.*/;
}
#If the archive has no tags, or the tags namespaces are only from
@@ -287,17 +287,21 @@ sub serve_page {
# Apply resizing transformation if set in Settings
if ( LANraragi::Model::Config->enable_resize ) {
- # Use File::Temp to copy the extracted file and resize it
- my ( $fh, $filename ) = tempfile();
- cp( $file, $fh );
+ # Store resized files in a subfolder of the ID's temp folder
+ my $resized_file = "$tempfldr/$id/resized/$path";
+ my ( $n, $resized_folder, $e ) = fileparse( $resized_file, qr/\.[^.]*/ );
+ make_path($resized_folder);
+
+ $logger->debug("Copying file to $resized_folder for resize transformation");
+ cp( $file, $resized_file );
my $threshold = LANraragi::Model::Config->get_threshold;
my $quality = LANraragi::Model::Config->get_readquality;
- LANraragi::Model::Reader::resize_image( $filename, $quality, $threshold );
+ LANraragi::Model::Reader::resize_image( $resized_file, $quality, $threshold );
# resize_image always converts the image to jpg
$self->render_file(
- filepath => $filename,
+ filepath => $resized_file,
content_disposition => "inline",
format => "jpg"
);
diff --git a/lib/LANraragi/Model/Upload.pm b/lib/LANraragi/Model/Upload.pm
index c00c4c63f..f63474792 100644
--- a/lib/LANraragi/Model/Upload.pm
+++ b/lib/LANraragi/Model/Upload.pm
@@ -6,7 +6,7 @@ use warnings;
use Redis;
use URI::Escape;
use File::Basename;
-use File::Temp qw/ tempfile tempdir /;
+use File::Temp qw(tempdir);
use File::Find qw(find);
use File::Copy qw(move);
@@ -107,9 +107,10 @@ sub handle_incoming_file {
return ( 0, $id, $name, "The file couldn't be moved to your content folder!" );
}
- # Now that the file has been copied, we can add the timestamp tag.
+ # Now that the file has been copied, we can add the timestamp tag and calculate pagecount.
# (The file being physically present is necessary in case last modified time is used)
LANraragi::Utils::Database::add_timestamp_tag( $redis, $id );
+ LANraragi::Utils::Database::add_pagecount( $redis, $id );
$redis->quit();
$logger->debug("Running autoplugin on newly uploaded file $id...");
diff --git a/lib/LANraragi/Plugin/Metadata/EHentai.pm b/lib/LANraragi/Plugin/Metadata/EHentai.pm
index e9282c54c..77d2dba2e 100644
--- a/lib/LANraragi/Plugin/Metadata/EHentai.pm
+++ b/lib/LANraragi/Plugin/Metadata/EHentai.pm
@@ -35,7 +35,7 @@ sub plugin_info {
{ type => "bool", desc => "Fetch using thumbnail first (falls back to title)" },
{ type => "bool", desc => "Use ExHentai (enable to search for fjorded content without star cookie)" },
{ type => "bool",
- desc => "Save the original Japanese title when available instead of the English or " . "romanised title"
+ desc => "Save the original title when available instead of the English or romanised title"
},
{ type => "bool", desc => "Fetch additional timestamp (time posted) and uploader metadata" },
{ type => "bool", desc => "Search expunged galleries as well" },
@@ -69,7 +69,7 @@ sub get_tags {
$gID = $1;
$gToken = $2;
$logger->debug("Skipping search and using gallery $gID / $gToken from oneshot args");
- } elsif ( $lrr_info->{existing_tags} =~ /.*source:e(?:x|-)hentai\.org\/g\/([0-9]*)\/([0-z]*)\/*.*/gi ) {
+ } elsif ( $lrr_info->{existing_tags} =~ /.*source:\s*e(?:x|-)hentai\.org\/g\/([0-9]*)\/([0-z]*)\/*.*/gi ) {
$gID = $1;
$gToken = $2;
$hasSrc = 1;
diff --git a/lib/LANraragi/Plugin/Metadata/Eze.pm b/lib/LANraragi/Plugin/Metadata/Eze.pm
index bb78c373d..d17a1768d 100644
--- a/lib/LANraragi/Plugin/Metadata/Eze.pm
+++ b/lib/LANraragi/Plugin/Metadata/Eze.pm
@@ -6,6 +6,8 @@ use warnings;
#Plugins can freely use all Perl packages already installed on the system
#Try however to restrain yourself to the ones already installed for LRR (see tools/cpanfile) to avoid extra installations by the end-user.
use Mojo::JSON qw(from_json);
+use File::Basename;
+use Time::Local qw(timegm_modern);
#You can also use the LRR Internal API when fitting.
use LANraragi::Model::Plugins;
@@ -23,12 +25,18 @@ sub plugin_info {
type => "metadata",
namespace => "ezeplugin",
author => "Difegue",
- version => "2.2",
+ version => "2.3",
description =>
- "Collects metadata embedded into your archives as eze-style info.json files. ({'gallery_info': {xxx} } syntax)",
+ "Collects metadata from eze-style info.json files ({'gallery_info': {xxx} } syntax), either embedded in your archive or in the same folder with the same name. ({archive_name}.json)",
icon =>
"data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABQAAAAUCAYAAACNiR0NAAAACXBIWXMAAAsTAAALEwEAmpwYAAAA\nB3RJTUUH4wYCFDYBnHlU6AAAAB1pVFh0Q29tbWVudAAAAAAAQ3JlYXRlZCB3aXRoIEdJTVBkLmUH\nAAAETUlEQVQ4y22UTWhTWRTHf/d9JHmNJLFpShMcKoRIqxXE4sKpjgthYLCLggU/wI1CUWRUxlmU\nWblw20WZMlJc1yKKKCjCdDdYuqgRiygq2mL8aJpmQot5uabv3XdnUftG0bu593AOv3M45/yvGBgY\n4OrVqwRBgG3bGIaBbduhDSClxPM8tNZMTEwwMTGB53lYloXWmkgkwqdPnygUCljZbJbW1lYqlQqG\nYYRBjuNw9+5dHj16RD6fJ51O09bWxt69e5mammJ5eZm1tTXi8Tiu6xKNRrlx4wZWNBqlXq8Tj8cx\nTRMhBJZlMT4+zuXLlxFCEIvFqFarBEFAKpXCcRzq9TrpdJparcbIyAiHDh1icXERyzAMhBB4nofv\n+5imiWmavHr1inQ6jeM4ZLNZDMMglUqxuLiIlBLXdfn48SNKKXp6eqhUKiQSCaxkMsna2hqe52Hb\nNsMdec3n8+Pn2+vpETt37qSlpYVyucz8/DzT09Ns3bqVYrEIgOM4RCIRrI1MiUQCz/P43vE8jxcv\nXqCUwvM8Zmdn2bJlC6lUitHRUdrb2zFNE9/3sd6/f4/jOLiuSzKZDCH1wV/EzMwM3d3dNN69o729\nnXK5jFKKPXv2sLS0RF9fHydOnMD3fZRSaK0xtNYEQYBpmtTr9RC4b98+LMsCwLZtHj9+TCwWI5/P\nI6Xk5MmTXLhwAaUUG3MA4M6dOzQaDd68eYOUkqHIZj0U2ay11mzfvp1du3YhhGBgYIDjx4/T3d1N\nvV4nCAKklCilcF2XZrOJlBIBcOnSJc6ePYsQgj9yBf1l//7OJcXPH1Y1wK/Ff8SfvT995R9d/SA8\nzyMaja5Xq7Xm1q1bLCwssLS09M1Atm3bFr67urq+8W8oRUqJlBJLCMHNmze5d+8e2Ww2DPyrsSxq\ntRqZTAattZibm6PZbHJFVoUQgtOxtAbwfR8A13WJxWIYANVqFd/36e/v/ypzIpEgCAKEEMzNzYXN\n34CN/FsSvu+jtSaTyeC67jrw4cOHdHZ2kslkQmCz2SQSiYT269evMU0zhF2RVaH1ejt932dlZYXh\n4eF14MLCArZtI6UMAb+1/qBPx9L6jNOmAY4dO/b/agBnnDb9e1un3vhQzp8/z/Xr19eBQgjevn3L\n1NTUd5WilKJQKGAYxje+lpYWrl27xuTk5PqKARSLRfr6+hgaGiKbzfLy5UvGx8dRSqGUwnEcDMNA\nKYUQIlRGNBplZmaGw4cPE4/HOXDgAMbs7Cy9vb1cvHiR+fl5Hjx4QC6XwzAMYrEYz549Y3p6mufP\nn4d6NU0Tx3GYnJzk6NGjNJtNduzYQUdHB+LL8mu1Gv39/WitGRsb4/79+3R1dbF7925yuVw4/Uaj\nwalTpzhy5AhjY2P4vs/BgwdJp9OYG7ByuUwmk6FUKgFw7tw5SqUSlUqFp0+fkkgk2LRpEysrKzx5\n8oTBwUG01ty+fZv9+/eTz+dZXV3lP31rAEu+yXjEAAAAAElFTkSuQmCC",
- parameters => [ { type => "bool", desc => "Save archive title" } ]
+ parameters => [
+ { type => "bool", desc => "Save archive title" },
+ { type => "bool",
+ desc => "Save the original title when available instead of the English or romanised title"
+ },
+ { type => "bool", desc => "Fetch additional timestamp (time posted) and uploader metadata" },
+ ]
);
}
@@ -38,49 +46,63 @@ sub get_tags {
shift;
my $lrr_info = shift; # Global info hash
- my ($save_title) = @_; # Plugin parameters
+ my ($save_title, $origin_title, $additional_tags) = @_; # Plugin parameters
my $logger = get_plugin_logger();
+
my $path_in_archive = is_file_in_archive( $lrr_info->{file_path}, "info.json" );
- if ($path_in_archive) {
- #Extract info.json
- my $filepath = extract_file_from_archive( $lrr_info->{file_path}, $path_in_archive );
+ my ($name, $path, $suffix) = fileparse($lrr_info->{file_path}, qr/\.[^.]*/);
+ my $path_nearby_json = $path . $name . '.json';
+
+ my $filepath;
+ my $delete_after_parse;
+
+ #Extract info.json
+ if($path_in_archive) {
+ $filepath = extract_file_from_archive( $lrr_info->{file_path}, $path_in_archive );
+ $logger->debug("Found file in archive at $filepath");
+ $delete_after_parse = 1;
+ } elsif (-e $path_nearby_json) {
+ $filepath = $path_nearby_json;
+ $logger->debug("Found file nearby at $filepath");
+ $delete_after_parse = 0;
+ } else {
+ return ( error => "No in-archive info.json or {archive_name}.json file found!" );
+ }
- #Open it
- my $stringjson = "";
+ #Open it
+ my $stringjson = "";
- open( my $fh, '<:encoding(UTF-8)', $filepath )
- or return ( error => "Could not open $filepath!" );
+ open( my $fh, '<:encoding(UTF-8)', $filepath )
+ or return ( error => "Could not open $filepath!" );
- while ( my $row = <$fh> ) {
- chomp $row;
- $stringjson .= $row;
- }
+ while ( my $row = <$fh> ) {
+ chomp $row;
+ $stringjson .= $row;
+ }
- #Use Mojo::JSON to decode the string into a hash
- my $hashjson = from_json $stringjson;
+ #Use Mojo::JSON to decode the string into a hash
+ my $hashjson = from_json $stringjson;
- $logger->debug("Found and loaded the following JSON: $stringjson");
+ $logger->debug("Loaded the following JSON: $stringjson");
- #Parse it
- my ( $tags, $title ) = tags_from_eze_json($hashjson);
+ #Parse it
+ my ( $tags, $title ) = tags_from_eze_json($origin_title, $additional_tags, $hashjson);
+ if ($delete_after_parse){
#Delete it
unlink $filepath;
+ }
- #Return tags
- $logger->info("Sending the following tags to LRR: $tags");
-
- if ( $save_title && $title ) {
- $logger->info("Parsed title is $title");
- return ( tags => $tags, title => $title );
- } else {
- return ( tags => $tags );
- }
+ #Return tags
+ $logger->info("Sending the following tags to LRR: $tags");
+ if ( $save_title && $title ) {
+ $logger->info("Parsed title is $title");
+ return ( tags => $tags, title => $title );
} else {
- return ( error => "No eze info.json file found in this archive!" );
+ return ( tags => $tags );
}
}
@@ -89,7 +111,7 @@ sub get_tags {
#Goes through the JSON hash obtained from an info.json file and return the contained tags.
sub tags_from_eze_json {
- my $hash = $_[0];
+ my ($origin_title, $additional_tags, $hash) = @_;
my $return = "";
#Tags are in gallery_info -> tags -> one array per namespace
@@ -97,6 +119,11 @@ sub tags_from_eze_json {
# Titles returned by eze are in complete E-H notation.
my $title = $hash->{"gallery_info"}->{"title"};
+
+ if ($origin_title && $hash->{"gallery_info"}->{"title_original"} ) {
+ $title = $hash->{"gallery_info"}->{"title_original"};
+ }
+
remove_spaces($title);
foreach my $namespace ( sort keys %$tags ) {
@@ -115,9 +142,33 @@ sub tags_from_eze_json {
my $site = $hash->{"gallery_info"}->{"source"}->{"site"};
my $gid = $hash->{"gallery_info"}->{"source"}->{"gid"};
my $gtoken = $hash->{"gallery_info"}->{"source"}->{"token"};
+ my $category = $hash->{"gallery_info"}->{"category"};
+ my $uploader = $hash->{"gallery_info_full"}->{"uploader"};
+ my $timestamp = $hash->{"gallery_info_full"}->{"date_uploaded"};
+
+ if ( $timestamp ) {
+ # convert microsecond to second
+ $timestamp = $timestamp / 1000;
+ } else {
+ my $upload_date = $hash->{"gallery_info"}->{"upload_date"};
+ my $time = timegm_modern($$upload_date[5],$$upload_date[4],$$upload_date[3],$$upload_date[2],$$upload_date[1]-1,$$upload_date[0]);
+ $timestamp = $time;
+ }
+
+ if ( $category ) {
+ $return .= ", category:$category";
+ }
+
+ if ( $additional_tags && $uploader ) {
+ $return .= ", uploader:$uploader";
+ }
+
+ if ( $additional_tags && $timestamp ) {
+ $return .= ", timestamp:$timestamp";
+ }
if ( $site && $gid && $gtoken ) {
- $return .= ", source: $site.org/g/$gid/$gtoken";
+ $return .= ", source:$site.org/g/$gid/$gtoken";
}
#Done-o
diff --git a/lib/LANraragi/Plugin/Metadata/Koromo.pm b/lib/LANraragi/Plugin/Metadata/Koromo.pm
index a46c369f8..edb9a68cb 100644
--- a/lib/LANraragi/Plugin/Metadata/Koromo.pm
+++ b/lib/LANraragi/Plugin/Metadata/Koromo.pm
@@ -41,6 +41,13 @@ sub get_tags {
my $file = $lrr_info->{file_path};
my $path_in_archive = is_file_in_archive( $file, "Info.json" );
+
+ unless ($path_in_archive) {
+
+ # Try for the lowercase variant as well
+ $path_in_archive = is_file_in_archive( $file, "info.json" );
+ }
+
if ($path_in_archive) {
#Extract info.json
diff --git a/lib/LANraragi/Plugin/Metadata/nHentai.pm b/lib/LANraragi/Plugin/Metadata/nHentai.pm
index 40776e177..861cd37be 100644
--- a/lib/LANraragi/Plugin/Metadata/nHentai.pm
+++ b/lib/LANraragi/Plugin/Metadata/nHentai.pm
@@ -44,11 +44,15 @@ sub get_tags {
# Work your magic here - You can create subs below to organize the code better
my $galleryID = "";
- # Quick regex to get the nh gallery id from the provided url.
+ # Quick regex to get the nh gallery id from the provided url or source tag.
if ( $lrr_info->{oneshot_param} =~ /.*\/g\/([0-9]+).*/ ) {
$galleryID = $1;
+ $logger->debug("Skipping search and using gallery $galleryID from oneshot args");
+ } elsif ( $lrr_info->{existing_tags} =~ /.*source:\s*(?:https?:\/\/)?nhentai\.net\/g\/([0-9]*).*/gi ) {
+ # Matching URL Scheme like 'https://' is only for backward compatible purpose.
+ $galleryID = $1;
+ $logger->debug("Skipping search and using gallery $galleryID from source tag")
} else {
-
#Get Gallery ID by hand if the user didn't specify a URL
$galleryID = get_gallery_id_from_title( $lrr_info->{archive_title} );
}
@@ -210,7 +214,7 @@ sub get_tags_from_NH {
if ( $json ) {
my @tags = get_tags_from_json($json);
- push( @tags, "source:https://nhentai.net/g/$gID" ) if ( @tags > 0 );
+ push( @tags, "source:nhentai.net/g/$gID" ) if ( @tags > 0 );
# Use NH's "pretty" names (romaji titles without extraneous data we already have like (Event)[Artist], etc)
$hashdata{tags} = join(', ', @tags);
diff --git a/lib/LANraragi/Plugin/Scripts/FolderToCat.pm b/lib/LANraragi/Plugin/Scripts/FolderToCat.pm
index 7817890ae..b4c45faa5 100644
--- a/lib/LANraragi/Plugin/Scripts/FolderToCat.pm
+++ b/lib/LANraragi/Plugin/Scripts/FolderToCat.pm
@@ -82,8 +82,10 @@ sub run_script {
push @created_categories, $catID;
for my $file ( @{ $subfolders{$folder} } ) {
- my $id = compute_id($file) || next;
- LANraragi::Model::Category::add_to_category( $catID, $id );
+ eval {
+ my $id = compute_id($file) || next;
+ LANraragi::Model::Category::add_to_category( $catID, $id );
+ };
}
}
diff --git a/lib/LANraragi/Plugin/Scripts/nHentaiSourceConverter.pm b/lib/LANraragi/Plugin/Scripts/nHentaiSourceConverter.pm
index fcb66e446..a11db37c7 100644
--- a/lib/LANraragi/Plugin/Scripts/nHentaiSourceConverter.pm
+++ b/lib/LANraragi/Plugin/Scripts/nHentaiSourceConverter.pm
@@ -19,7 +19,7 @@ sub plugin_info {
author => "Guerra24",
version => "1.0",
description =>
- "Converts \"source:id\" tags with 6 or less digits into \"source:https://nhentai.net/g/id\""
+ "Converts \"source:{id}\" tags with 6 or less digits into \"source:nhentai.net/g/{id}\""
);
}
@@ -41,7 +41,7 @@ sub run_script {
my %hash = $redis->hgetall($id);
my ( $tags ) = @hash{qw(tags)};
- if ( $tags =~ s/source:(\d{1,6})/source:https:\/\/nhentai\.net\/g\/$1/igm ) {
+ if ( $tags =~ s/source:(\d{1,6})/source:nhentai\.net\/g\/$1/igm ) {
$count++;
}
diff --git a/lib/LANraragi/Utils/Archive.pm b/lib/LANraragi/Utils/Archive.pm
index ed2a905f3..9aa9d6ea9 100644
--- a/lib/LANraragi/Utils/Archive.pm
+++ b/lib/LANraragi/Utils/Archive.pm
@@ -19,7 +19,7 @@ use Image::Magick;
use Archive::Libarchive qw( ARCHIVE_OK );
use Archive::Libarchive::Extract;
use Archive::Libarchive::Peek;
-use File::Temp qw(tempfile tempdir);
+use File::Temp qw(tempdir);
use LANraragi::Utils::TempFolder qw(get_temp);
use LANraragi::Utils::Logging qw(get_logger);
diff --git a/lib/LANraragi/Utils/Database.pm b/lib/LANraragi/Utils/Database.pm
index 7c70cbc65..544d66987 100644
--- a/lib/LANraragi/Utils/Database.pm
+++ b/lib/LANraragi/Utils/Database.pm
@@ -15,6 +15,7 @@ use Unicode::Normalize;
use LANraragi::Model::Plugins;
use LANraragi::Utils::Generic qw(flat remove_spaces);
use LANraragi::Utils::Tags qw(unflat_tagrules tags_rules_to_array restore_CRLF);
+use LANraragi::Utils::Archive qw(get_filelist);
use LANraragi::Utils::Logging qw(get_logger);
# Functions for interacting with the DB Model.
@@ -59,18 +60,32 @@ sub add_timestamp_tag {
if ( LANraragi::Model::Config->enable_dateadded eq "1" ) {
$logger->debug("Adding timestamp tag...");
+ my $date;
if ( LANraragi::Model::Config->use_lastmodified eq "1" ) {
$logger->info("Using file date");
- my $date = ( stat( $redis->hget( $id, "file" ) ) )[9]; #9 is the unix time stamp for date modified.
- $redis->hset( $id, "tags", "date_added:$date" );
+ $date = ( stat( $redis->hget( $id, "file" ) ) )[9]; #9 is the unix time stamp for date modified.
} else {
$logger->info("Using current date");
- $redis->hset( $id, "tags", "date_added:" . time() );
+ $date = time();
}
+
+ add_tags( $id, "date_added:$date" );
}
}
+# add_pagecount(redis,id)
+# Calculates and adds pagecount to the given ID.
+sub add_pagecount {
+ my ( $redis, $id ) = @_;
+ my $logger = get_logger( "Archive", "lanraragi" );
+
+ my $file = $redis->hget( $id, "file" );
+ my ( $images, $sizes ) = get_filelist($file);
+ my @images = @$images;
+ $redis->hset( $id, "pagecount", scalar @images );
+}
+
# get_archive_json(redis, id)
# Builds a JSON object for an archive registered in the database and returns it.
# If you need to get many JSONs at once, use the multi variant.
diff --git a/lib/LANraragi/Utils/Routing.pm b/lib/LANraragi/Utils/Routing.pm
index c6d1b28a9..4d078fbcc 100644
--- a/lib/LANraragi/Utils/Routing.pm
+++ b/lib/LANraragi/Utils/Routing.pm
@@ -93,8 +93,6 @@ sub apply_routes {
$logged_in_api->post('/api/plugins/use')->to('api-other#use_plugin_sync');
$logged_in_api->post('/api/plugins/queue')->to('api-other#use_plugin_async');
$logged_in_api->delete('/api/tempfolder')->to('api-other#clean_tempfolder');
- $logged_in_api->get('/api/minion/:jobid')->to('api-other#minion_job_status');
- $logged_in_api->post('/api/minion/:jobname/queue')->to('api-other#queue_minion_job');
$logged_in_api->post('/api/download_url')->to('api-other#download_url');
$logged_in_api->post('/api/regen_thumbs')->to('api-other#regen_thumbnails');
@@ -132,6 +130,12 @@ sub apply_routes {
$logged_in_api->get('/api/shinobu')->to('api-shinobu#shinobu_status');
$logged_in_api->post('/api/shinobu/stop')->to('api-shinobu#stop_shinobu');
$logged_in_api->post('/api/shinobu/restart')->to('api-shinobu#restart_shinobu');
+ $logged_in_api->post('/api/shinobu/rescan')->to('api-shinobu#reset_filemap');
+
+ # Minion API
+ $public_api->get('/api/minion/:jobid')->to('api-minion#minion_job_status');
+ $logged_in_api->get('/api/minion/:jobid/detail')->to('api-minion#minion_job_detail');
+ $logged_in_api->post('/api/minion/:jobname/queue')->to('api-minion#queue_minion_job'); # unused for now
# Category API
$public_api->get('/api/categories')->to('api-category#get_category_list');
diff --git a/lib/Shinobu.pm b/lib/Shinobu.pm
index a8aba5e3e..34d9c3e07 100644
--- a/lib/Shinobu.pm
+++ b/lib/Shinobu.pm
@@ -246,6 +246,13 @@ sub add_to_filemap {
$redis->wait_all_responses;
invalidate_cache();
}
+
+ # Set pagecount in case it's not already there
+ unless ( $redis->hget( $id, "pagecount" ) ) {
+ $logger->debug("Pagecount not calculated for $id, doing it now!");
+ LANraragi::Utils::Database::add_pagecount( $redis, $id );
+ }
+
} else {
# Add to Redis if not present beforehand
@@ -302,6 +309,7 @@ sub add_new_file {
eval {
LANraragi::Utils::Database::add_archive_to_redis( $id, $file, $redis );
LANraragi::Utils::Database::add_timestamp_tag( $redis, $id );
+ LANraragi::Utils::Database::add_pagecount( $redis, $id );
#AutoTagging using enabled plugins goes here!
LANraragi::Model::Plugins::exec_enabled_plugins_on_file($id);
diff --git a/package.json b/package.json
index 74baf9dab..cfc6ade76 100644
--- a/package.json
+++ b/package.json
@@ -1,7 +1,7 @@
{
"name": "lanraragi",
- "version": "0.8.4",
- "version_name": "Real Cool World",
+ "version": "0.8.5",
+ "version_name": "Sex and the Church",
"description": "I'm under Japanese influence and my honor's at stake!",
"scripts": {
"test": "prove -r -l -v tests/",
@@ -34,7 +34,7 @@
"jquery": "^3.6.0",
"jquery-contextmenu": "^2.9.2",
"jquery-toast-plugin": "^1.3.2",
- "marked": "^3.0.4",
+ "marked": "^4.0.10",
"open-sans-fontface": "^1.4.0",
"roboto-fontface": "^0.8.0",
"swiper": "^7.2.0",
diff --git a/public/css/lrr.css b/public/css/lrr.css
index e6851ec27..4ef206ca5 100644
--- a/public/css/lrr.css
+++ b/public/css/lrr.css
@@ -36,14 +36,14 @@ td.tags {
span.tags {
display: block;
- overflow:hidden;
- text-overflow:ellipsis;
- min-height:20px;
+ overflow: hidden;
+ text-overflow: ellipsis;
+ min-height: 20px;
}
.tagger {
- min-height:125px;
- cursor:text;
+ min-height: 125px;
+ cursor: text;
}
@@ -75,7 +75,7 @@ div.id4 {
}
table.itg {
- table-layout:fixed;
+ table-layout: fixed;
}
/*Archive list Sorting*/
@@ -148,7 +148,7 @@ table thead .sorting_disabled a:after {
margin-bottom: 36px;
}
-.index-carousel > .option-flyout {
+.index-carousel>.option-flyout {
margin: 0 4%;
}
@@ -184,9 +184,9 @@ p#nb {
.caption-namespace {
width: 100px !important;
- font-size:10pt;
- padding: 3px 0px 7px;
- vertical-align:top;
+ font-size: 10pt;
+ padding: 3px 0px 7px;
+ vertical-align: top;
}
.caption-tags {
@@ -275,28 +275,30 @@ p#nb {
color: lightskyblue;
}
-.quick-thumbnail:hover > .page-number {
+.quick-thumbnail:hover>.page-number {
z-index: 300;
background-color: #000000;
}
.reader-image {
- max-width:100%;
+ max-width: 100%;
+ min-width: 0;
height: auto;
width: auto;
- min-height:300px;
user-select: none;
+ align-self: center;
+ cursor: pointer;
}
.caption-reader {
- margin-left: auto;
- margin-right: auto;
- padding:16px;
+ margin-left: auto;
+ margin-right: auto;
+ padding: 16px;
min-width: 50% !important;
}
.reader-thumbnail {
- display: inline-block;
+ display: inline-block;
vertical-align: middle;
margin-right: 48px !important;
}
@@ -318,7 +320,7 @@ p#nb {
}
.collapsible-right {
- float: right;
+ float: right;
padding: 1.2rem 1.2rem 0 0;
}
@@ -326,12 +328,12 @@ p#nb {
border: none !important;
}
-.option-flyout > .collapsible-title {
+.option-flyout>.collapsible-title {
font-size: 18px;
font-weight: bold;
}
-.option-flyout > .collapsible-body {
+.option-flyout>.collapsible-body {
padding: 10px !important;
}
@@ -367,7 +369,7 @@ li {
font-size: 9pt;
}
-.checklist > li {
+.checklist>li {
text-align: left;
text-overflow: ellipsis;
white-space: nowrap;
@@ -406,22 +408,24 @@ li {
background-color: transparent !important;
}
-.artist-tag{
+.artist-tag {
color: #22a7f0;
}
-.series-tag, .parody-tag {
+.series-tag,
+.parody-tag {
color: #d2527f;
}
-.circle-tag, .group-tag {
+.circle-tag,
+.group-tag {
color: #36d7b7;
}
.theme-preview {
- height: 64px;
- margin-top:4px;
- margin-right:8px;
+ height: 64px;
+ margin-top: 4px;
+ margin-right: 8px;
border-radius: 4px;
}
@@ -439,6 +443,7 @@ li {
margin: auto;
padding-bottom: 6px;
}
+
.indeterminate .bar-container {
position: absolute;
margin: 0;
@@ -459,27 +464,36 @@ li {
0% {
-moz-transform: translateX(100%);
}
+
100% {
-moz-transform: translateX(-100%);
}
}
+
@-webkit-keyframes scroll-left {
- 0% {
+ 0% {
-webkit-transform: translateX(100%);
}
+
100% {
-webkit-transform: translateX(-100%);
}
}
+
@keyframes scroll-left {
- 0% {
- -moz-transform: translateX(100%); /* Browser bug fix */
- -webkit-transform: translateX(100%); /* Browser bug fix */
+ 0% {
+ -moz-transform: translateX(100%);
+ /* Browser bug fix */
+ -webkit-transform: translateX(100%);
+ /* Browser bug fix */
transform: translateX(100%);
}
+
100% {
- -moz-transform: translateX(-100%); /* Browser bug fix */
- -webkit-transform: translateX(-100%); /* Browser bug fix */
+ -moz-transform: translateX(-100%);
+ /* Browser bug fix */
+ -webkit-transform: translateX(-100%);
+ /* Browser bug fix */
transform: translateX(-100%);
}
}
@@ -488,11 +502,12 @@ li {
.jq-toast-single a {
padding-bottom: 0 !important;
}
+
.jq-toast-single h2 {
margin: 0 !important;
}
-.awesomplete > ul {
+.awesomplete>ul {
z-index: 99 !important;
}
@@ -525,6 +540,7 @@ li {
.ie7 .page-overlay {
min-height: 100% !important;
}
+
.left-column,
.right-column {
width: 100% !important;
@@ -554,7 +570,7 @@ li {
@media all and (max-width: 560px) {
div.id1 {
- width:164px;
+ width: 164px;
height: 256px !important;
}
@@ -598,6 +614,7 @@ li {
white-space: nowrap;
text-overflow: ellipsis;
}
+
.option-td {
width: 150px !important;
}
@@ -643,9 +660,9 @@ li {
height: unset;
}
-#settingsOverlay > div {
- margin: auto;
- font-size: 8pt;
+#settingsOverlay>div {
+ margin: auto;
+ font-size: 8pt;
}
#changelog {
@@ -653,7 +670,7 @@ li {
text-align: left;
}
-#changelog > * > img {
+#changelog>*>img {
max-width: 400px;
}
@@ -690,4 +707,29 @@ body.infinite-scroll #display img {
body.infinite-scroll .sb {
margin-top: 10px;
+}
+
+/* Double Page & Fullscreen */
+
+div.sni img[src=""] {
+ display: none;
+}
+
+div.fullscreen img {
+ height: unset !important;
+ max-height: 100vh !important;
+ margin: 0px !important;
+}
+
+#display {
+ display: flex;
+ justify-content: center;
+}
+
+.fullscreen-infinite {
+ overflow-y: scroll;
+}
+
+body.infinite-scroll .fullscreen-infinite img {
+ margin: 0 auto !important;
}
\ No newline at end of file
diff --git a/public/js/common.js b/public/js/common.js
index d5238f968..bdb3cf497 100644
--- a/public/js/common.js
+++ b/public/js/common.js
@@ -38,7 +38,13 @@ LRR.isNullOrWhitespace = function (input) {
*/
LRR.getTagSearchURL = function (namespace, tag) {
const namespacedTag = this.buildNamespacedTag(namespace, tag);
- return (namespace !== "source") ? `/?q=${encodeURIComponent(namespacedTag)}` : `http://${tag}`;
+ if (namespace !== "source"){
+ return `/?q=${encodeURIComponent(namespacedTag)}`;
+ } else if (/https?:\/\//.test(tag)) {
+ return `${tag}`;
+ } else {
+ return `https://${tag}`;
+ }
};
/**
@@ -259,3 +265,21 @@ LRR.showErrorToast = function (header, error) {
icon: "error",
});
};
+
+/**
+ * Fires a HEAD request to get filesize of a given URL.
+ * return target img size.
+ * @param {*} target Target URL String
+ */
+LRR.getImgSize = function (target) {
+ let imgSize = 0;
+ $.ajax({
+ async: false,
+ url: target,
+ type: "HEAD",
+ success: (data, textStatus, request) => {
+ imgSize = parseInt(request.getResponseHeader("Content-Length") / 1024, 10);
+ },
+ });
+ return imgSize;
+};
diff --git a/public/js/index.js b/public/js/index.js
index 4bb66a90c..2d5f547e4 100644
--- a/public/js/index.js
+++ b/public/js/index.js
@@ -383,7 +383,7 @@ Index.fetchChangelog = function () {
throw new Error(data.result);
}
- marked(data.body, {
+ marked.parse(data.body, {
gfm: true,
breaks: true,
sanitize: true,
diff --git a/public/js/reader.js b/public/js/reader.js
index 7d0f0f69e..bcbcf4695 100644
--- a/public/js/reader.js
+++ b/public/js/reader.js
@@ -9,6 +9,7 @@ Reader.force = false;
Reader.previousPage = -1;
Reader.currentPage = -1;
Reader.showingSinglePage = true;
+Reader.isFullscreen = false;
Reader.preloadedImg = {};
Reader.preloadedSizes = {};
@@ -19,6 +20,7 @@ Reader.initializeAll = function () {
// Bind events to DOM
$(document).on("keyup", Reader.handleShortcuts);
+ $(document).on("wheel", Reader.handleWheel);
$(document).on("click.toggle_fit_mode", "#fit-mode input", Reader.toggleFitMode);
$(document).on("click.toggle_double_mode", "#toggle-double-mode input", Reader.toggleDoublePageMode);
@@ -34,6 +36,7 @@ Reader.initializeAll = function () {
$(document).on("click.pagination_change_pages", ".page-link", Reader.handlePaginator);
$(document).on("click.close_overlay", "#overlay-shade", LRR.closeOverlay);
+ $(document).on("click.toggle_full_screen", "#toggle-full-screen", Reader.toggleFullScreen);
$(document).on("click.toggle_archive_overlay", "#toggle-archive-overlay", Reader.toggleArchiveOverlay);
$(document).on("click.toggle_settings_overlay", "#toggle-settings-overlay", Reader.toggleSettingsOverlay);
$(document).on("click.toggle_help", "#toggle-help", Reader.toggleHelp);
@@ -109,8 +112,18 @@ Reader.loadImages = function () {
if (Reader.infiniteScroll) {
Reader.initInfiniteScrollView();
} else {
- $(document).on("click.imagemap_change_pages", "#Map area", Reader.handlePaginator);
- $(window).on("resize", Reader.updateImagemap);
+ // when click left or right img area change page
+ $(document).on("click", (event) => {
+ // check click Y position is in img Y area
+ if ($(event.target).closest("#i3").length && !$("#overlay-shade").is(":visible")) {
+ // is click X position is left on screen or right
+ if (event.pageX < $(window).width() / 2) {
+ Reader.changePage(-1);
+ } else {
+ Reader.changePage(1);
+ }
+ }
+ });
// when there's no parameter, null is coerced to 0 so it becomes -1
Reader.currentPage = Reader.currentPage || (
@@ -131,7 +144,7 @@ Reader.loadImages = function () {
if (Reader.showOverlayByDefault) { Reader.toggleArchiveOverlay(); }
// Wait for the extraction job to conclude before getting thumbnails
- Server.checkJobStatus(data.job,
+ Server.checkJobStatus(data.job, false,
() => Reader.initializeArchiveOverlay(),
() => LRR.showErrorToast("The extraction job didn't conclude properly. Your archive might be corrupted."));
}).finally(() => {
@@ -234,6 +247,9 @@ Reader.handleShortcuts = function (e) {
case 68: // d
Reader.changePage(1);
break;
+ case 70: // f
+ Reader.toggleFullScreen();
+ break;
case 72: // h
Reader.toggleHelp();
break;
@@ -258,6 +274,17 @@ Reader.handleShortcuts = function (e) {
}
};
+Reader.handleWheel = function (e) {
+ if (Reader.isFullscreen && !Reader.infiniteScroll) {
+ let changePage = 1;
+ if (e.originalEvent.deltaY > 0) changePage = -1;
+ // In Manga mode, reverse the changePage variable
+ // so that we always move forward
+ if (!Reader.mangaMode) changePage *= -1;
+ Reader.changePage(changePage);
+ }
+};
+
Reader.checkFiletypeSupport = function (extension) {
if ((extension === "rar" || extension === "cbr") && !localStorage.rarWarningShown) {
localStorage.rarWarningShown = true;
@@ -308,6 +335,11 @@ Reader.updateMetadata = function () {
const img = $("#img")[0];
const imageUrl = new URL(img.src);
const filename = imageUrl.searchParams.get("path");
+
+ const imgDoublePage = $("#img_doublepage")[0];
+ const imageUrlDoublePage = new URL(imgDoublePage.src);
+ const filenameDoublePage = imageUrlDoublePage.searchParams.get("path");
+
if (!filename && Reader.showingSinglePage) {
Reader.currentPageLoaded = true;
$("#i3").removeClass("loading");
@@ -316,22 +348,36 @@ Reader.updateMetadata = function () {
const width = img.naturalWidth;
const height = img.naturalHeight;
+ const widthDoublePage = imgDoublePage.naturalWidth;
+ const heightDoublePage = imgDoublePage.naturalHeight;
+ const widthView = width + widthDoublePage;
if (Reader.showingSinglePage) {
- // HEAD request to get filesize
let size = Reader.preloadedSizes[Reader.currentPage];
if (!size) {
- $.ajax({
- url: Reader.pages[Reader.currentPage],
- type: "HEAD",
- success: (data, textStatus, request) => {
- size = parseInt(request.getResponseHeader("Content-Length") / 1024, 10);
- Reader.preloadedSizes[Reader.currentPage] = size;
- $(".file-info").text(`${filename} :: ${width} x ${height} :: ${size} KB`);
- },
- });
- } else { $(".file-info").text(`${filename} :: ${width} x ${height} :: ${size} KB`); }
- } else { $(".file-info").text(`Double-Page View :: ${width} x ${height}`); }
+ size = LRR.getImgSize(Reader.pages[Reader.currentPage]);
+ Reader.preloadedSizes[Reader.currentPage] = size;
+ $(".file-info").text(`${filename} :: ${width} x ${height} :: ${size} KB`);
+ $(".file-info").attr("title", `${filename} :: ${width} x ${height} :: ${size} KB`);
+ } else {
+ $(".file-info").text(`${filename} :: ${width} x ${height} :: ${size} KB`);
+ $(".file-info").attr("title", `${filename} :: ${width} x ${height} :: ${size} KB`);
+ }
+ } else {
+ let size = Reader.preloadedSizes[Reader.currentPage];
+ let sizePre = Reader.preloadedSizes[Reader.currentPage + 1];
+
+ if (!size || !sizePre) {
+ size = LRR.getImgSize(Reader.pages[Reader.currentPage]);
+ sizePre = LRR.getImgSize(Reader.pages[Reader.currentPage + 1]);
+ Reader.preloadedSizes[Reader.currentPage] = size;
+ Reader.preloadedSizes[Reader.currentPage + 1] = sizePre;
+ }
+
+ const sizeView = size + sizePre;
+ $(".file-info").text(`${filename} - ${filenameDoublePage} :: ${widthView} x ${height} :: ${sizeView} KB`);
+ $(".file-info").attr("title", `${filename} :: ${width} x ${height} :: ${size} KB - ${filenameDoublePage} :: ${widthDoublePage} x ${heightDoublePage} :: ${sizePre} KB`);
+ }
// Update page numbers in the paginator
const newVal = Reader.showingSinglePage
@@ -339,37 +385,37 @@ Reader.updateMetadata = function () {
: `${Reader.currentPage + 1} + ${Reader.currentPage + 2}`;
$(".current-page").each((_i, el) => $(el).html(newVal));
- Reader.updateImageMap();
Reader.currentPageLoaded = true;
$("#i3").removeClass("loading");
};
-Reader.updateImageMap = function () {
- // update imagemap with the w/h parameters we obtained
- const img = $("#img")[0];
- const mapWidth = img.width / 2;
- const mapHeight = img.height;
-
- $("#leftmap").attr("coords", `0,0,${mapWidth},${mapHeight}`);
- $("#rightmap").attr("coords", `${mapWidth + 1},0,${img.width},${mapHeight}`);
-};
-
Reader.goToPage = function (page) {
Reader.previousPage = Reader.currentPage;
Reader.currentPage = Math.min(Reader.maxPage, Math.max(0, +page));
Reader.showingSinglePage = false;
+ $("#img_doublepage").attr("src", "");
+ $("#display").removeClass("double-mode");
if (Reader.doublePageMode && Reader.currentPage > 0 && Reader.currentPage < Reader.maxPage) {
- // composite an image and use that as the source
+ // Composite an image and use that as the source
const img1 = Reader.loadImage(Reader.currentPage);
const img2 = Reader.loadImage(Reader.currentPage + 1);
- let imagesLoaded = 0;
- const loadHandler = () => { (imagesLoaded += 1) === 2 && Reader.drawCanvas(img1, img2); };
- $([img1, img2]).each((_i, img) => {
- img.onload = loadHandler;
- // If the image is preloaded it does not trigger onload, so we have to call it manually
- if (img.complete) { loadHandler(); }
- });
+ // If w > h on one of the images(widespread), set canvasdata to the first image only
+ if (img1.naturalWidth > img1.naturalHeight || img2.naturalWidth > img2.naturalHeight) {
+ // Depending on whether we were going forward or backward, display img1 or img2
+ const wideSrc = Reader.previousPage > Reader.currentPage ? img2.src : img1.src;
+ $("#img").attr("src", wideSrc);
+ Reader.showingSinglePage = true;
+ } else {
+ if (Reader.mangaMode) {
+ $("#img").attr("src", img2.src);
+ $("#img_doublepage").attr("src", img1.src);
+ } else {
+ $("#img").attr("src", img1.src);
+ $("#img_doublepage").attr("src", img2.src);
+ }
+ $("#display").addClass("double-mode");
+ }
} else {
const img = Reader.loadImage(Reader.currentPage);
$("#img").attr("src", img.src);
@@ -515,6 +561,7 @@ Reader.toggleHeader = function () {
$("#toggle-header input").toggleClass("toggled");
$("#i2").toggle();
Reader.applyContainerWidth();
+ return false;
};
Reader.toggleProgressTracking = function () {
@@ -541,6 +588,50 @@ Reader.toggleArchiveOverlay = function () {
return LRR.toggleOverlay("#archivePagesOverlay");
};
+Reader.toggleFullScreen = function () {
+ // if already full screen; exit
+ // else go fullscreen
+ if (
+ document.fullscreenElement
+ || document.webkitFullscreenElement
+ || document.mozFullScreenElement
+ || document.msFullscreenElement
+ ) {
+ if ($("body").hasClass("infinite-scroll")) {
+ $("div#i3").removeClass("fullscreen-infinite");
+ } else {
+ $("div#i3").removeClass("fullscreen");
+ }
+ if (document.exitFullscreen) {
+ document.exitFullscreen();
+ } else if (document.mozCancelFullScreen) {
+ document.mozCancelFullScreen();
+ } else if (document.webkitExitFullscreen) {
+ document.webkitExitFullscreen();
+ } else if (document.msExitFullscreen) {
+ document.msExitFullscreen();
+ }
+ Reader.isFullscreen = false;
+ } else {
+ if ($("body").hasClass("infinite-scroll")) {
+ $("div#i3").addClass("fullscreen-infinite");
+ } else {
+ $("div#i3").addClass("fullscreen");
+ }
+ const element = $("div#i3").get(0);
+ if (element.requestFullscreen) {
+ element.requestFullscreen();
+ } else if (element.mozRequestFullScreen) {
+ element.mozRequestFullScreen();
+ } else if (element.webkitRequestFullscreen) {
+ element.webkitRequestFullscreen(Element.ALLOW_KEYBOARD_INPUT);
+ } else if (element.msRequestFullscreen) {
+ element.msRequestFullscreen();
+ }
+ Reader.isFullscreen = true;
+ }
+};
+
Reader.initializeArchiveOverlay = function () {
if ($("#archivePagesOverlay").attr("loaded") === "true") {
return;
@@ -581,7 +672,7 @@ Reader.initializeArchiveOverlay = function () {
thumbSuccess();
} else if (response.status === 202) {
// Wait for Minion job to finish
- response.json().then((data) => Server.checkJobStatus(data.job,
+ response.json().then((data) => Server.checkJobStatus(data.job, false,
() => thumbSuccess(),
() => thumbFail()));
} else {
@@ -595,33 +686,6 @@ Reader.initializeArchiveOverlay = function () {
$("#archivePagesOverlay").attr("loaded", "true");
};
-Reader.drawCanvas = function (img1, img2) {
- // If w > h on one of the images(widespread), set canvasdata to the first image only
- if (img1.naturalWidth > img1.naturalHeight || img2.naturalWidth > img2.naturalHeight) {
- // Depending on whether we were going forward or backward, display img1 or img2
- $("#img").attr("src", Reader.previousPage > Reader.currentPage ? img2.src : img1.src);
- Reader.showingSinglePage = true;
- return;
- }
-
- // Create an adequately-sized canvas
- const canvas = $("#dpcanvas")[0];
- canvas.width = img1.naturalWidth + img2.naturalWidth;
- canvas.height = Math.max(img1.naturalHeight, img2.naturalHeight);
-
- // Draw both images on it
- const ctx = canvas.getContext("2d");
- if (Reader.mangaMode) {
- ctx.drawImage(img2, 0, 0);
- ctx.drawImage(img1, img2.naturalWidth + 1, 0);
- } else {
- ctx.drawImage(img1, 0, 0);
- ctx.drawImage(img2, img1.naturalWidth + 1, 0);
- }
-
- $("#img").attr("src", canvas.toDataURL("image/jpeg"));
-};
-
Reader.changePage = function (targetPage) {
let destination;
if (Reader.infiniteScroll) {
diff --git a/public/js/server.js b/public/js/server.js
index 2c2549632..efc9c09c6 100644
--- a/public/js/server.js
+++ b/public/js/server.js
@@ -43,11 +43,13 @@ Server.callAPI = function (endpoint, method, successMessage, errorMessage, succe
/**
* Check the status of a Minion job until it's completed.
* @param {*} jobId Job ID to check
+ * @param {*} useDetail Whether to get full details or the job or not.
+ * This requires the user to be logged in.
* @param {*} callback Execute a callback on successful job completion.
* @param {*} failureCallback Execute a callback on unsuccessful job completion.
*/
-Server.checkJobStatus = function (jobId, callback, failureCallback) {
- fetch(`/api/minion/${jobId}`, { method: "GET" })
+Server.checkJobStatus = function (jobId, useDetail, callback, failureCallback) {
+ fetch(useDetail ? `/api/minion/${jobId}/detail` : `/api/minion/${jobId}`, { method: "GET" })
.then((response) => (response.ok ? response.json() : { success: 0, error: "Response was not OK" }))
.then((data) => {
if (data.error) throw new Error(data.error);
@@ -59,7 +61,7 @@ Server.checkJobStatus = function (jobId, callback, failureCallback) {
if (data.state !== "finished") {
// Wait and retry, job isn't done yet
setTimeout(() => {
- Server.checkJobStatus(jobId, callback, failureCallback);
+ Server.checkJobStatus(jobId, useDetail, callback, failureCallback);
}, 1000);
} else {
// Update UI with info
@@ -113,7 +115,7 @@ Server.triggerScript = function (namespace) {
.then(Server.callAPI(`/api/plugins/queue?plugin=${namespace}&arg=${scriptArg}`, "POST", null, "Error while executing Script :",
(data) => {
// Check minion job state periodically while we're on this page
- Server.checkJobStatus(data.job,
+ Server.checkJobStatus(data.job, true,
(d) => {
Server.isScriptRunning = false;
$(".script-running").hide();
@@ -197,7 +199,7 @@ Server.regenerateThumbnails = function (force) {
$("#forcethumb-button").prop("disabled", true);
// Check minion job state periodically while we're on this page
- Server.checkJobStatus(data.job,
+ Server.checkJobStatus(data.job, true,
(d) => {
$("#genthumb-button").prop("disabled", false);
$("#forcethumb-button").prop("disabled", false);
diff --git a/public/js/upload.js b/public/js/upload.js
index 6ec7767a8..94ac2eefd 100644
--- a/public/js/upload.js
+++ b/public/js/upload.js
@@ -1,29 +1,26 @@
// Scripting for the Upload page.
-var processingArchives = 0;
-var completedArchives = 0;
-var failedArchives = 0;
-var totalUploads = 0;
+let processingArchives = 0;
+let completedArchives = 0;
+let failedArchives = 0;
+let totalUploads = 0;
// Handle updating the upload counters.
function updateUploadCounters() {
-
$("#progressCount").html(`🤔 Processing: ${processingArchives} 🙌 Completed: ${completedArchives} 👹 Failed: ${failedArchives}`);
- var icon = (completedArchives == totalUploads) ? "fas fa-check-circle" :
- failedArchives > 0 ? "fas fa-exclamation-circle" :
- "fa fa-spinner fa-spin";
+ const icon = (completedArchives == totalUploads) ? "fas fa-check-circle"
+ : failedArchives > 0 ? "fas fa-exclamation-circle"
+ : "fa fa-spinner fa-spin";
$("#progressTotal").html(` Total:${completedArchives + failedArchives}/${totalUploads}`);
// At the end of the upload job, dump the search cache!
- if (processingArchives === 0)
- Server.invalidateCache();
+ if (processingArchives === 0) { Server.invalidateCache(); }
}
// Handle a completed job from minion. Update the line in upload results with the title, ID, message.
function handleCompletedUpload(jobID, d) {
-
$(`#${jobID}-name`).html(d.result.title);
if (d.result.id) {
@@ -32,11 +29,11 @@ function handleCompletedUpload(jobID, d) {
}
if (d.result.success) {
- $(`#${jobID}-link`).html("Click here to edit metadata.
(" + d.result.message + ")")
+ $(`#${jobID}-link`).html(`Click here to edit metadata.
(${d.result.message})`);
$(`#${jobID}-icon`).attr("class", "fa fa-check-circle");
completedArchives++;
} else {
- $(`#${jobID}-link`).html("Error while processing archive.
(" + d.result.message + ")");
+ $(`#${jobID}-link`).html(`Error while processing archive.
(${d.result.message})`);
$(`#${jobID}-icon`).attr("class", "fa fa-exclamation-circle");
failedArchives++;
}
@@ -46,8 +43,7 @@ function handleCompletedUpload(jobID, d) {
}
function handleFailedUpload(jobID, d) {
-
- $(`#${jobID}-link`).html("Error while processing file.
(" + d + ")");
+ $(`#${jobID}-link`).html(`Error while processing file.
(${d})`);
$(`#${jobID}-icon`).attr("class", "fa fa-exclamation-circle");
failedArchives++;
@@ -57,26 +53,24 @@ function handleFailedUpload(jobID, d) {
// Send URLs to the Download API and add a Server.checkJobStatus to track its progress.
function downloadUrl() {
-
const categoryID = document.getElementById("category").value;
// One fetch job per non-empty line of the form
- $('#urlForm').val().split(/\r|\n/).forEach(url => {
-
+ $("#urlForm").val().split(/\r|\n/).forEach((url) => {
if (url === "") return;
- let formData = new FormData();
- formData.append('url', url);
+ const formData = new FormData();
+ formData.append("url", url);
if (categoryID !== "") {
- formData.append('catid', categoryID);
+ formData.append("catid", categoryID);
}
fetch("/api/download_url", {
method: "POST",
- body: formData
+ body: formData,
})
- .then(response => response.json())
+ .then((response) => response.json())
.then((data) => {
if (data.success) {
result = `