Rebuild web caches immediately on ebook updates

This commit is contained in:
Alex Cabal 2022-07-14 15:28:57 -05:00
parent 60a58e9a95
commit e857e4e9e6
9 changed files with 115 additions and 70 deletions

View file

@ -281,13 +281,13 @@ Define webroot /standardebooks.org/web
RewriteCond %{QUERY_STRING} \bquery= RewriteCond %{QUERY_STRING} \bquery=
RewriteRule ^/feeds/(opds|atom|rss)/all.xml$ /feeds/$1/search.php [QSA] RewriteRule ^/feeds/(opds|atom|rss)/all.xml$ /feeds/$1/search.php [QSA]
RewriteRule ^/feeds/(atom|rss)/([^/\.]+)$ /feeds/collection.php?type=$1&name=$2 RewriteRule ^/feeds/(atom|rss)/([^/\.]+)$ /feeds/collection.php?type=$1&class=$2
RewriteRule ^/feeds/(.+\.xml)$ /feeds/download.php?path=$1 RewriteRule ^/feeds/(.+\.xml)$ /feeds/download.php?path=$1
# Rewrite rules for bulk downloads # Rewrite rules for bulk downloads
RewriteRule ^/bulk-downloads/(.+\.zip)$ /bulk-downloads/download.php?path=$1 RewriteRule ^/bulk-downloads/(.+\.zip)$ /bulk-downloads/download.php?path=$1
RewriteRule ^/bulk-downloads/([^/\.]+)$ /bulk-downloads/collection.php?name=$1 RewriteRule ^/bulk-downloads/([^/\.]+)$ /bulk-downloads/collection.php?class=$1
# Specific config for /bulk-downloads # Specific config for /bulk-downloads
<DirectoryMatch "${webroot}/www/bulk-downloads"> <DirectoryMatch "${webroot}/www/bulk-downloads">

View file

@ -263,13 +263,13 @@ Define webroot /standardebooks.org/web
RewriteCond %{QUERY_STRING} \bquery= RewriteCond %{QUERY_STRING} \bquery=
RewriteRule ^/feeds/(opds|atom|rss)/all.xml$ /feeds/$1/search.php [QSA] RewriteRule ^/feeds/(opds|atom|rss)/all.xml$ /feeds/$1/search.php [QSA]
RewriteRule ^/feeds/(atom|rss)/([^/\.]+)$ /feeds/collection.php?type=$1&name=$2 RewriteRule ^/feeds/(atom|rss)/([^/\.]+)$ /feeds/collection.php?type=$1&class=$2
RewriteRule ^/feeds/(.+\.xml)$ /feeds/download.php?path=$1 RewriteRule ^/feeds/(.+\.xml)$ /feeds/download.php?path=$1
# Rewrite rules for bulk downloads # Rewrite rules for bulk downloads
RewriteRule ^/bulk-downloads/(.+\.zip)$ /bulk-downloads/download.php?path=$1 RewriteRule ^/bulk-downloads/(.+\.zip)$ /bulk-downloads/download.php?path=$1
RewriteRule ^/bulk-downloads/([^/\.]+)$ /bulk-downloads/collection.php?name=$1 RewriteRule ^/bulk-downloads/([^/\.]+)$ /bulk-downloads/collection.php?class=$1
# Specific config for /bulk-downloads # Specific config for /bulk-downloads
<DirectoryMatch "${webroot}/www/bulk-downloads"> <DirectoryMatch "${webroot}/www/bulk-downloads">

View file

@ -380,6 +380,54 @@ class Library{
return ['months' => $months, 'subjects' => $subjects, 'collections' => $collections, 'authors' => $authors]; return ['months' => $months, 'subjects' => $subjects, 'collections' => $collections, 'authors' => $authors];
} }
/**
* @return array<string, array<int|string, array<int|string, mixed>>>
*/
public static function RebuildFeedsCache(?string $returnType = null, ?string $returnClass = null): ?array{
$feedTypes = ['opds', 'atom', 'rss'];
$feedClasses = ['authors', 'collections', 'subjects'];
$retval = null;
$collator = Collator::create('en_US'); // Used for sorting letters with diacritics like in author names
if($collator === null){
throw new Exceptions\SeException('Couldn\'t create collator object when rebuilding feeds cache.');
}
foreach($feedTypes as $type){
foreach($feedClasses as $class){
$files = glob(WEB_ROOT . '/feeds/' . $type . '/' . $class . '/*.xml');
$feeds = [];
foreach($files as $file){
$obj = new stdClass();
$obj->Url = '/feeds/' . $type . '/' . $class . '/' . basename($file, '.xml');
$obj->Label = exec('attr -g se-label ' . escapeshellarg($file)) ?: null;
if($obj->Label == null){
$obj->Label = basename($file, '.xml');
}
$obj->LabelSort = exec('attr -g se-label-sort ' . escapeshellarg($file)) ?: null;
if($obj->LabelSort == null){
$obj->LabelSort = basename($file, '.xml');
}
$feeds[] = $obj;
}
usort($feeds, function($a, $b) use($collator){ return $collator->compare($a->LabelSort, $b->LabelSort); });
if($type == $returnType && $class == $returnClass){
$retval = $feeds;
}
apcu_store('feeds-index-' . $type . '-' . $class, $feeds);
}
}
return $retval;
}
public static function RebuildCache(): void{ public static function RebuildCache(): void{
// We check a lockfile because this can be a long-running command. // We check a lockfile because this can be a long-running command.
// We don't want to queue up a bunch of these in case someone is refreshing the index constantly. // We don't want to queue up a bunch of these in case someone is refreshing the index constantly.

View file

@ -67,7 +67,7 @@ class Session extends PropertiesBase{
return null; return null;
} }
public static function SetSessionCookie($sessionId): void{ public static function SetSessionCookie(string $sessionId): void{
setcookie('sessionid', $sessionId, time() + 60 * 60 * 24 * 14 * 1, '/', SITE_DOMAIN, true, false); // Expires in two weeks setcookie('sessionid', $sessionId, time() + 60 * 60 * 24 * 14 * 1, '/', SITE_DOMAIN, true, false); // Expires in two weeks
} }

View file

@ -143,6 +143,10 @@ if ! [ -f "${scriptsDir}"/generate-feeds ]; then
die "\"${scriptsDir}\"/generate-feeds\" is not a file or could not be found." die "\"${scriptsDir}\"/generate-feeds\" is not a file or could not be found."
fi fi
if ! [ -f "${scriptsDir}"/generate-bulk-downloads ]; then
die "\"${scriptsDir}\"/generate-bulk-downloads\" is not a file or could not be found."
fi
mkdir -p "${webRoot}"/images/covers/ mkdir -p "${webRoot}"/images/covers/
for dir in "$@" for dir in "$@"
@ -391,7 +395,7 @@ if [ "${queuedTasks}" = "false" ]; then
printf "Rebuilding web library cache ... " printf "Rebuilding web library cache ... "
fi fi
"${scriptsDir}"/rebuild-library-cache "${scriptsDir}"/rebuild-cache library
if [ "${verbose}" = "true" ]; then if [ "${verbose}" = "true" ]; then
printf "Done.\n" printf "Done.\n"
@ -410,6 +414,7 @@ if [ "${feeds}" = "true" ]; then
fi fi
"${scriptsDir}/generate-feeds" --webroot "${webRoot}" --weburl "${webUrl}" "${scriptsDir}/generate-feeds" --webroot "${webRoot}" --weburl "${webUrl}"
"${scriptsDir}"/rebuild-cache feeds
if [ "${verbose}" = "true" ]; then if [ "${verbose}" = "true" ]; then
printf "Done.\n" printf "Done.\n"
@ -429,6 +434,7 @@ if [ "${bulkDownloads}" = "true" ]; then
fi fi
"${scriptsDir}/generate-bulk-downloads" --webroot "${webRoot}" "${scriptsDir}/generate-bulk-downloads" --webroot "${webRoot}"
"${scriptsDir}"/rebuild-cache bulk-downloads
if [ "${verbose}" = "true" ]; then if [ "${verbose}" = "true" ]; then
printf "Done.\n" printf "Done.\n"

43
scripts/rebuild-cache Executable file
View file

@ -0,0 +1,43 @@
#!/bin/bash
usage(){
echo -n
fmt <<EOF
DESCRIPTION
Rebuild one of three caches stored in APCu for the standardebooks.org FPM pool.
USAGE
rebuild-cache {library,bulk-downloads,feeds}
EOF
exit 1
}
if [ $# -eq 1 ]; then
if [ "$1" = "-h" ] || [ "$1" = "--help" ]; then
usage
fi
fi
if [ $# -ne 1 ]; then
usage
fi
# If this script is run by a user without sudo powers, they can be given permission to run this command by creating a file in sudoers.d with:
# MY_USERNAME ALL=(www-data) NOPASSWD: /usr/bin/env SCRIPT_FILENAME=/tmp/rebuild-cache.php REQUEST_METHOD=GET cgi-fcgi -bind -connect *
type="$1"
if [ "${type}" = "library" ]; then
echo "<?php require_once('Core.php'); Library::RebuildCache(); ?>" > /tmp/rebuild-cache.php
fi
if [ "${type}" = "bulk-downloads" ]; then
echo "<?php require_once('Core.php'); Library::RebuildBulkDownloadsCache(); ?>" > /tmp/rebuild-cache.php
fi
if [ "${type}" = "feeds" ]; then
echo "<?php require_once('Core.php'); Library::RebuildFeedsCache(); ?>" > /tmp/rebuild-cache.php
fi
sudo -u www-data env SCRIPT_FILENAME=/tmp/rebuild-cache.php REQUEST_METHOD=GET cgi-fcgi -bind -connect "/run/php/standardebooks.org.sock" &> /dev/null
rm /tmp/rebuild-cache.php

View file

@ -1,26 +0,0 @@
#!/bin/bash
usage(){
echo -n
fmt <<EOF
DESCRIPTION
Rebuild the library cache stored in APCu for the standardebooks.org FPM pool.
USAGE
rebuild-library-cache
EOF
exit 1
}
if [ $# -eq 1 ]; then
if [ "$1" = "-h" ] || [ "$1" = "--help" ]; then
usage
fi
fi
# If this script is run by a user without sudo powers, they can be given for this command by creating a file in sudoers.d with:
# MY_USERNAME ALL=(www-data) NOPASSWD: /usr/bin/env SCRIPT_FILENAME=/tmp/rebuild-library-cache.php REQUEST_METHOD=GET cgi-fcgi -bind -connect *
echo "<?php require_once('Core.php'); Library::RebuildCache(); ?>" > /tmp/rebuild-library-cache.php
sudo -u www-data env SCRIPT_FILENAME=/tmp/rebuild-library-cache.php REQUEST_METHOD=GET cgi-fcgi -bind -connect "/run/php/standardebooks.org.sock" &> /dev/null
rm /tmp/rebuild-library-cache.php

View file

@ -5,9 +5,9 @@ use function Safe\apcu_fetch;
use function Safe\preg_replace; use function Safe\preg_replace;
$canDownload = false; $canDownload = false;
$name = HttpInput::Str(GET, 'name', false) ?? ''; $class = HttpInput::Str(GET, 'class', false) ?? '';
if($name != 'authors' && $name != 'collections' && $name != 'subjects' && $name != 'months'){ if($class != 'authors' && $class != 'collections' && $class != 'subjects' && $class != 'months'){
Template::Emit404(); Template::Emit404();
} }
@ -18,14 +18,14 @@ if($GLOBALS['User'] !== null && $GLOBALS['User']->Benefits->CanBulkDownload){
$collection = []; $collection = [];
try{ try{
$collection = apcu_fetch('bulk-downloads-' . $name); $collection = apcu_fetch('bulk-downloads-' . $class);
} }
catch(Safe\Exceptions\ApcuException $ex){ catch(Safe\Exceptions\ApcuException $ex){
$result = Library::RebuildBulkDownloadsCache(); $result = Library::RebuildBulkDownloadsCache();
$collection = $result[$name]; $collection = $result[$class];
} }
$title = preg_replace('/s$/', '', ucfirst($name)); $title = preg_replace('/s$/', '', ucfirst($class));
?><?= Template::Header(['title' => 'Downloads by ' . $title, 'highlight' => '', 'description' => 'Download zip files containing all of the Standard Ebooks in a given collection.']) ?> ?><?= Template::Header(['title' => 'Downloads by ' . $title, 'highlight' => '', 'description' => 'Download zip files containing all of the Standard Ebooks in a given collection.']) ?>
<main> <main>
@ -34,8 +34,8 @@ $title = preg_replace('/s$/', '', ucfirst($name));
<? if(!$canDownload){ ?> <? if(!$canDownload){ ?>
<p><a href="/about#patrons-circle">Patrons circle members</a> get convenient access to zip files containing collections of different categories of ebooks. You can <a href="/donate#patrons-circle">join the Patrons Circle</a> with a small donation in support of our continuing mission to create free, beautiful digital literature, and download these collections files too.</p> <p><a href="/about#patrons-circle">Patrons circle members</a> get convenient access to zip files containing collections of different categories of ebooks. You can <a href="/donate#patrons-circle">join the Patrons Circle</a> with a small donation in support of our continuing mission to create free, beautiful digital literature, and download these collections files too.</p>
<? } ?> <? } ?>
<p>These zip files contain each ebook in every format we offer, and are updated once daily with the latest versions of each ebook. Read about <a href="/help/how-to-use-our-ebooks#which-file-to-download">which file format to download</a>.</p> <p>These zip files contain each ebook in every format we offer, and are kept updated with the latest versions of each ebook. Read about <a href="/help/how-to-use-our-ebooks#which-file-to-download">which file format to download</a>.</p>
<? if($name == 'months'){ ?> <? if($class == 'months'){ ?>
<table class="download-list"> <table class="download-list">
<caption aria-hidden="hidden">Scroll right </caption> <caption aria-hidden="hidden">Scroll right </caption>
<tbody> <tbody>

View file

@ -6,10 +6,10 @@ use function Safe\glob;
use function Safe\preg_replace; use function Safe\preg_replace;
use function Safe\usort; use function Safe\usort;
$name = HttpInput::Str(GET, 'name', false) ?? ''; $class = HttpInput::Str(GET, 'class', false) ?? '';
$type = HttpInput::Str(GET, 'type', false) ?? ''; $type = HttpInput::Str(GET, 'type', false) ?? '';
if($name != 'authors' && $name != 'collections' && $name != 'subjects'){ if($class != 'authors' && $class != 'collections' && $class != 'subjects'){
Template::Emit404(); Template::Emit404();
} }
@ -19,7 +19,7 @@ if($type != 'rss' && $type != 'atom'){
$feeds = []; $feeds = [];
$lcTitle = preg_replace('/s$/', '', $name); $lcTitle = preg_replace('/s$/', '', $class);
$ucTitle = ucfirst($lcTitle); $ucTitle = ucfirst($lcTitle);
$ucType = 'RSS 2.0'; $ucType = 'RSS 2.0';
if($type === 'atom'){ if($type === 'atom'){
@ -27,36 +27,10 @@ if($type === 'atom'){
} }
try{ try{
$feeds = apcu_fetch('feeds-index-' . $type . '-' . $name); $feeds = apcu_fetch('feeds-index-' . $type . '-' . $class);
} }
catch(Safe\Exceptions\ApcuException $ex){ catch(Safe\Exceptions\ApcuException $ex){
$files = glob(WEB_ROOT . '/feeds/' . $type . '/' . $name . '/*.xml'); $feeds = Library::RebuildFeedsCache($type, $class);
$feeds = [];
foreach($files as $file){
$obj = new stdClass();
$obj->Url = '/feeds/' . $type . '/' . $name . '/' . basename($file, '.xml');
$obj->Label = exec('attr -g se-label ' . escapeshellarg($file)) ?: null;
if($obj->Label == null){
$obj->Label = basename($file, '.xml');
}
$obj->LabelSort = exec('attr -g se-label-sort ' . escapeshellarg($file)) ?: null;
if($obj->LabelSort == null){
$obj->LabelSort = basename($file, '.xml');
}
$feeds[] = $obj;
}
$collator = Collator::create('en_US'); // Used for sorting letters with diacritics like in author names
if($collator !== null){
usort($feeds, function($a, $b) use($collator){ return $collator->compare($a->LabelSort, $b->LabelSort); });
}
apcu_store('feeds-index-' . $type . '-' . $name, $feeds, 43200); // 12 hours
} }
?><?= Template::Header(['title' => $ucType . ' Ebook Feeds by ' . $ucTitle, 'description' => 'A list of available ' . $ucType . ' feeds of Standard Ebooks ebooks by ' . $lcTitle . '.']) ?> ?><?= Template::Header(['title' => $ucType . ' Ebook Feeds by ' . $ucTitle, 'description' => 'A list of available ' . $ucType . ' feeds of Standard Ebooks ebooks by ' . $lcTitle . '.']) ?>
<main> <main>