diff --git a/app/Http/Controllers/ComicController.php b/app/Http/Controllers/ComicController.php
index e99d766..c6c7f2b 100644
--- a/app/Http/Controllers/ComicController.php
+++ b/app/Http/Controllers/ComicController.php
@@ -3,10 +3,11 @@
namespace App\Http\Controllers;
use App\Helper\ZhConversion;
+use App\Jobs\ImageUpsert;
+use App\Jobs\RemotePrefetch;
use App\Models\Author;
use App\Models\Chapter;
use App\Models\Comic;
-use App\Models\Image;
use App\Remote\CopyManga;
use App\Remote\ImageFetcher;
use GuzzleHttp\Exception\GuzzleException;
@@ -22,13 +23,26 @@ use Illuminate\Support\Facades\Cache;
use Inertia\Inertia;
use Inertia\Response;
+/**
+ * Comic Controller
+ */
class ComicController extends Controller
{
+ /**
+ * @param CopyManga $copyManga
+ * @param ZhConversion $zhConversion
+ */
public function __construct(private readonly CopyManga $copyManga, private readonly ZhConversion $zhConversion)
{
}
+ /**
+ * Convert SC to ZH
+ *
+ * @param mixed $string
+ * @return mixed
+ */
protected function scToZh(mixed $string): mixed
{
if (gettype($string) !== 'string') {
@@ -323,24 +337,8 @@ class ComicController extends Controller
$chapterObj = Chapter::where('chapter_uuid', $chapter['chapter']['uuid'])->first();
$comicObj = Comic::where('pathword', $pathword)->first();
- $arrayForUpsert = [];
-
- foreach ($chapter['sorted'] as $k => $image) {
- $metadata = $chapter;
- unset($metadata['sorted']);
- unset($metadata['chapter']['contents'], $metadata['chapter']['words']);
-
- $arrayForUpsert[] = [
- 'comic_id' => $comicObj->id,
- 'chapter_id' => $chapterObj->id,
- 'order' => $k,
- 'url' => $image['url'],
- 'metadata' => json_encode($metadata),
- ];
- }
-
- // Do an upsert
- Image::upsert($arrayForUpsert, uniqueBy: 'url');
+ // Image Upsert
+ ImageUpsert::dispatch($comicObj->id, $chapterObj->id, $chapter);
// Update history
$request->user()->readingHistories()->attach($chapterObj->id, ['comic_id' => $comicObj->id]);
@@ -348,6 +346,15 @@ class ComicController extends Controller
// Get chapters from DB
$chapters = $comicObj->chapters()->where('metadata->group_path_word', $chapter['chapter']['group_path_word'])->orderBy('order')->get(['name', 'chapter_uuid']);
+ // Do remote prefetch if needed
+ if ($chapter['chapter']['next'] !== null) {
+ try {
+ Chapter::where('chapter_uuid', $chapter['chapter']['next'])->firstOrFail()->images()->firstOrFail();
+ } catch (ModelNotFoundException $e) {
+ RemotePrefetch::dispatch('chapter', ['pathword' => $pathword, 'uuid' => $chapter['chapter']['next']]);
+ }
+ }
+
return Inertia::render('Comic/Read', [
'comic' => $this->scToZh($comic),
'chapter' => $this->scToZh($chapter),
diff --git a/app/Jobs/ImageUpsert.php b/app/Jobs/ImageUpsert.php
new file mode 100644
index 0000000..c708f8f
--- /dev/null
+++ b/app/Jobs/ImageUpsert.php
@@ -0,0 +1,51 @@
+comicId}, chapterId: {$this->chapterId}");
+
+ $arrayForUpsert = [];
+
+ foreach ($this->chapter['sorted'] as $k => $image) {
+ $metadata = $this->chapter;
+ unset($metadata['sorted']);
+ unset($metadata['chapter']['contents'], $metadata['chapter']['words']);
+
+ $arrayForUpsert[] = [
+ 'comic_id' => $this->comicId,
+ 'chapter_id' => $this->chapterId,
+ 'order' => $k,
+ 'url' => $image['url'],
+ 'metadata' => json_encode($metadata),
+ ];
+ }
+
+ // Do an upsert
+ Image::upsert($arrayForUpsert, uniqueBy: 'url');
+
+ Log::info('JOB ImageUpsert END');
+ }
+}
diff --git a/app/Jobs/RemotePrefetch.php b/app/Jobs/RemotePrefetch.php
new file mode 100644
index 0000000..70da749
--- /dev/null
+++ b/app/Jobs/RemotePrefetch.php
@@ -0,0 +1,51 @@
+action) {
+ case 'chapter':
+ Log::info("JOB RemotePrefetch START, action '{$this->action}', Pathword: {$this->parameters['pathword']}, UUID: {$this->parameters['uuid']}");
+
+ $copyManga->chapter($this->parameters['pathword'], $this->parameters['uuid']);
+
+ Log::info("JOB RemotePrefetch END, action '{$this->action}'");
+ break;
+ case 'chapters':
+ // TODO:
+ break;
+ case 'index':
+ // TODO
+ break;
+ case 'tags':
+ // TODO
+ break;
+ default:
+ Log::info("JOB RemotePrefetch Unknown action '{$this->action}'");
+ break;
+ }
+ }
+}
diff --git a/app/Remote/CopyManga.php b/app/Remote/CopyManga.php
index 9b626c9..4ee88d6 100644
--- a/app/Remote/CopyManga.php
+++ b/app/Remote/CopyManga.php
@@ -17,6 +17,7 @@ class CopyManga
/**
* @var array Caching options
+ * @deprecated
*/
protected array $options = [
'caching' => true,
@@ -101,17 +102,17 @@ class CopyManga
*
* @param string $url
* @param array $value
+ * @param int $ttl
* @return void
*/
- protected function writeToCache(string $url, array $value): void
+ protected function writeToCache(string $url, array $value, int $ttl = 0): void
{
if ($this->options['caching']) {
Cache::add("URL_{$url}", array_merge($value, ['CACHE' => [
'CACHE' => true,
'CACHED_AT' => Date::now(),
- 'EXPIRE_AT' => Date::now()->addSeconds($this->options['cachingTimeout'])]
- ]),
- $this->options['cachingTimeout']);
+ 'EXPIRE_AT' => Date::now()->addSeconds(($ttl !== 0) ? $ttl : $this->options['cachingTimeout'])]
+ ]), $this->options['cachingTimeout']);
}
}
@@ -121,10 +122,11 @@ class CopyManga
* @param string $url
* @param string $method
* @param string $userAgent
+ * @param int $ttl
* @return mixed|string
* @throws GuzzleException
*/
- protected function execute(string $url, string $method = 'GET', string $userAgent = ""): mixed
+ protected function execute(string $url, string $method = 'GET', string $userAgent = "", int $ttl = 0): mixed
{
if ($this->options['caching']) {
// Check cache exist
@@ -156,7 +158,7 @@ class CopyManga
if ($userAgent !== "") {
// Directly send html to method to process
$html = $response->getBody()->getContents();
- $this->writeToCache($url, ['response' => $html, 'type' => 'HTML']);
+ $this->writeToCache($url, ['response' => $html, 'type' => 'HTML'], $ttl);
return $html;
}
@@ -166,7 +168,7 @@ class CopyManga
if (json_last_error() === JSON_ERROR_NONE) {
// Save to cache if needed
- $this->writeToCache($url, $json['results']);
+ $this->writeToCache($url, $json['results'], $ttl);
return $json['results'];
} else {
throw new Exception($json['code']);
@@ -209,11 +211,8 @@ class CopyManga
$parameters['limit'] = $limit;
$parameters['offset'] = $offset;
$parameters['top'] = $top;
- //OPTIONS
- // https://api.mangacopy.com/api/v3/comics?format=json&platform=1&q=x&limit=30&offset=0&top=all"
- // https://api.mangacopy.com/api/v3/search/comic?platform=1&q=x&limit=20&offset=0&q_type=&_update=true
- return $this->execute($this->buildUrl("comics", $parameters));
+ return $this->execute($this->buildUrl("comics", $parameters), ttl: 15 * 60);
}
/**
@@ -233,7 +232,7 @@ class CopyManga
$parameters['limit'] = $limit;
$parameters['offset'] = $offset;
- return $this->execute($this->buildUrl("search/comic", $parameters));
+ return $this->execute($this->buildUrl("search/comic", $parameters), ttl: 15 * 60);
}
/**
@@ -246,7 +245,7 @@ class CopyManga
*/
public function comic(string $comic, array $parameters = []): mixed
{
- return $this->execute($this->buildUrl("comic2/{$comic}", $parameters));
+ return $this->execute($this->buildUrl("comic2/{$comic}", $parameters), ttl: 24 * 60 * 60);
}
/**
@@ -266,7 +265,7 @@ class CopyManga
$parameters['offset'] = $offset;
$options = $this->execute($this->buildUrl("comic/{$comic}/group/{$group}/chapters", $parameters, false), 'OPTIONS');
- return $this->execute($this->buildUrl("comic/{$comic}/group/{$group}/chapters", $parameters));
+ return $this->execute($this->buildUrl("comic/{$comic}/group/{$group}/chapters", $parameters), ttl: 24 * 60 * 60);
}
/**
@@ -297,7 +296,7 @@ class CopyManga
public function legacyChapter(string $comic, string $chapter): array
{
$userAgent = "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/108.0.0.0 Safari/537.36";
- $responses = $this->execute($this->legacyBuildUrl("comic/{$comic}/chapter/{$chapter}"), "GET", $userAgent);
+ $responses = $this->execute($this->legacyBuildUrl("comic/{$comic}/chapter/{$chapter}"), "GET", $userAgent, ttl: 24 * 60 * 60);
// Get Content Key
$dom = new DOMDocument();
@@ -347,11 +346,12 @@ class CopyManga
*/
public function chapter(string $comic, string $chapter, array $parameters = []): array
{
- $responses = $this->execute($this->buildUrl("comic/{$comic}/chapter2/{$chapter}", $parameters));
- $responses['sorted'] = $this->sort($responses['chapter']['contents'], $responses['chapter']['words']);
+ $responses = $this->execute($this->buildUrl("comic/{$comic}/chapter2/{$chapter}", $parameters), ttl: 24 * 60 * 60);
if ($this->legacyImagesFetch) {
$responses['sorted'] = $this->legacyChapter($comic, $chapter);
+ } else {
+ $responses['sorted'] = $this->sort($responses['chapter']['contents'], $responses['chapter']['words']);
}
return $responses;
diff --git a/bun.lockb b/bun.lockb
index 4c59ecf..d95ba2f 100755
Binary files a/bun.lockb and b/bun.lockb differ
diff --git a/composer.json b/composer.json
index 0bafdf0..2f82cfa 100644
--- a/composer.json
+++ b/composer.json
@@ -13,6 +13,7 @@
"laravel/framework": "^11.31",
"laravel/sanctum": "^4.0",
"laravel/tinker": "^2.9",
+ "plesk/ext-laravel-integration": "^7.0",
"predis/predis": "^2.0",
"sentry/sentry-laravel": "^4.10",
"tightenco/ziggy": "^2.0"
diff --git a/composer.lock b/composer.lock
index e39bfe4..ba3b1e5 100644
--- a/composer.lock
+++ b/composer.lock
@@ -4,7 +4,7 @@
"Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies",
"This file is @generated automatically"
],
- "content-hash": "b4f2ee211728714c2fa05fbad3914d2e",
+ "content-hash": "641b7b21cd3231c89ab305cd615eeb6f",
"packages": [
{
"name": "brick/math",
@@ -2310,12 +2310,12 @@
"version": "3.8.4",
"source": {
"type": "git",
- "url": "https://github.com/briannesbitt/Carbon.git",
+ "url": "https://github.com/CarbonPHP/carbon.git",
"reference": "129700ed449b1f02d70272d2ac802357c8c30c58"
},
"dist": {
"type": "zip",
- "url": "https://api.github.com/repos/briannesbitt/Carbon/zipball/129700ed449b1f02d70272d2ac802357c8c30c58",
+ "url": "https://api.github.com/repos/CarbonPHP/carbon/zipball/129700ed449b1f02d70272d2ac802357c8c30c58",
"reference": "129700ed449b1f02d70272d2ac802357c8c30c58",
"shasum": ""
},
@@ -2857,6 +2857,53 @@
],
"time": "2024-07-20T21:41:07+00:00"
},
+ {
+ "name": "plesk/ext-laravel-integration",
+ "version": "7.0.0",
+ "source": {
+ "type": "git",
+ "url": "https://github.com/plesk/ext-laravel-integration.git",
+ "reference": "73dc4ea3f99e033396497f147aa033eb0602f943"
+ },
+ "dist": {
+ "type": "zip",
+ "url": "https://api.github.com/repos/plesk/ext-laravel-integration/zipball/73dc4ea3f99e033396497f147aa033eb0602f943",
+ "reference": "73dc4ea3f99e033396497f147aa033eb0602f943",
+ "shasum": ""
+ },
+ "require": {
+ "laravel/framework": ">=7.0.0",
+ "php": ">=7.4"
+ },
+ "type": "library",
+ "extra": {
+ "laravel": {
+ "providers": [
+ "PleskExtLaravel\\Providers\\ConsoleServiceProvider"
+ ]
+ },
+ "component": "package"
+ },
+ "autoload": {
+ "psr-4": {
+ "PleskExtLaravel\\": "src/"
+ }
+ },
+ "notification-url": "https://packagist.org/downloads/",
+ "license": [
+ "Apache-2.0"
+ ],
+ "description": "Plesk Laravel Toolkit integration with Laravel applications.",
+ "keywords": [
+ "laravel",
+ "plesk"
+ ],
+ "support": {
+ "issues": "https://github.com/plesk/ext-laravel-integration/issues",
+ "source": "https://github.com/plesk/ext-laravel-integration/tree/7.0.0"
+ },
+ "time": "2023-01-20T09:40:57+00:00"
+ },
{
"name": "predis/predis",
"version": "v2.3.0",
diff --git a/package.json b/package.json
index b0fad8b..632efc7 100644
--- a/package.json
+++ b/package.json
@@ -8,7 +8,7 @@
"devDependencies": {
"@headlessui/react": "^2.2.0",
"@inertiajs/react": "^2.0.0",
- "@tailwindcss/forms": "^0.5.9",
+ "@tailwindcss/forms": "^0.5.10",
"@vitejs/plugin-react": "^4.3.4",
"autoprefixer": "^10.4.20",
"axios": "^1.7.9",
@@ -21,7 +21,7 @@
"vite": "^6.0.7"
},
"dependencies": {
- "@hookform/resolvers": "^3.9.1",
+ "@hookform/resolvers": "^3.10.0",
"@radix-ui/react-avatar": "^1.1.2",
"@radix-ui/react-checkbox": "^1.1.3",
"@radix-ui/react-collapsible": "^1.1.2",
@@ -35,8 +35,8 @@
"@radix-ui/react-tabs": "^1.1.2",
"@radix-ui/react-toast": "^1.2.4",
"@radix-ui/react-tooltip": "^1.1.6",
- "@sentry/react": "^8.47.0",
- "@sentry/vite-plugin": "^2.22.7",
+ "@sentry/react": "^8.48.0",
+ "@sentry/vite-plugin": "^2.23.0",
"@tanstack/react-table": "^8.20.6",
"class-variance-authority": "^0.7.1",
"clsx": "^2.1.1",
diff --git a/resources/js/Layouts/AppLayout.jsx b/resources/js/Layouts/AppLayout.jsx
index 64359fb..e6184ce 100644
--- a/resources/js/Layouts/AppLayout.jsx
+++ b/resources/js/Layouts/AppLayout.jsx
@@ -35,8 +35,7 @@ export default function AppLayout({ auth, header, children, toolbar }) {
const [theme, setTheme] = useState(getTheme());
useEffect(() => {
- getTheme();
- setTheme(theme);
+ setTheme(getTheme());
}, []);
return (
diff --git a/resources/js/Pages/Comic/Chapters.jsx b/resources/js/Pages/Comic/Chapters.jsx
index 88cf7b6..4b732c8 100644
--- a/resources/js/Pages/Comic/Chapters.jsx
+++ b/resources/js/Pages/Comic/Chapters.jsx
@@ -215,7 +215,7 @@ export default function Chapters({ auth, comic, chapters, histories, offset }) {
{ chapters.list.sort((a, b) => ascending ? (a.index - b.index) : (b.index - a.index)).map(c => (
) ) }
- { (chapters.total > chapters.limit && chapters.offset === 0) && (
+ { (chapters.total > chapters.limit && chapters.total > chapters.offset) && (