Speed up page loads — pre-fetch bookmarks/cache as sets, off-load PIL conversion to a worker
This commit is contained in:
parent
2b9bf22249
commit
74f948a3e8
@ -2,6 +2,7 @@
|
|||||||
|
|
||||||
from __future__ import annotations
|
from __future__ import annotations
|
||||||
|
|
||||||
|
import asyncio
|
||||||
import hashlib
|
import hashlib
|
||||||
import zipfile
|
import zipfile
|
||||||
from collections import OrderedDict
|
from collections import OrderedDict
|
||||||
@ -165,9 +166,11 @@ async def download_image(
|
|||||||
gif_path = local.with_suffix(".gif")
|
gif_path = local.with_suffix(".gif")
|
||||||
if gif_path.exists():
|
if gif_path.exists():
|
||||||
return gif_path
|
return gif_path
|
||||||
# If the zip is cached but not yet converted, convert it now
|
# If the zip is cached but not yet converted, convert it now.
|
||||||
|
# PIL frame iteration is CPU-bound and would block the asyncio
|
||||||
|
# loop for hundreds of ms — run it in a worker thread instead.
|
||||||
if local.exists() and zipfile.is_zipfile(local):
|
if local.exists() and zipfile.is_zipfile(local):
|
||||||
return _convert_ugoira_to_gif(local)
|
return await asyncio.to_thread(_convert_ugoira_to_gif, local)
|
||||||
|
|
||||||
# Check if animated PNG/WebP was already converted to gif
|
# Check if animated PNG/WebP was already converted to gif
|
||||||
if local.suffix.lower() in (".png", ".webp"):
|
if local.suffix.lower() in (".png", ".webp"):
|
||||||
@ -180,7 +183,7 @@ async def download_image(
|
|||||||
if _is_valid_media(local):
|
if _is_valid_media(local):
|
||||||
# Convert animated PNG/WebP on access if not yet converted
|
# Convert animated PNG/WebP on access if not yet converted
|
||||||
if local.suffix.lower() in (".png", ".webp"):
|
if local.suffix.lower() in (".png", ".webp"):
|
||||||
converted = _convert_animated_to_gif(local)
|
converted = await asyncio.to_thread(_convert_animated_to_gif, local)
|
||||||
if converted != local:
|
if converted != local:
|
||||||
return converted
|
return converted
|
||||||
return local
|
return local
|
||||||
@ -233,12 +236,13 @@ async def download_image(
|
|||||||
local.unlink()
|
local.unlink()
|
||||||
raise ValueError("Downloaded file is not valid media")
|
raise ValueError("Downloaded file is not valid media")
|
||||||
|
|
||||||
# Convert ugoira zip to animated GIF
|
# Convert ugoira zip to animated GIF (PIL is sync + CPU-bound;
|
||||||
|
# off-load to a worker so we don't block the asyncio loop).
|
||||||
if local.suffix.lower() == ".zip" and zipfile.is_zipfile(local):
|
if local.suffix.lower() == ".zip" and zipfile.is_zipfile(local):
|
||||||
local = _convert_ugoira_to_gif(local)
|
local = await asyncio.to_thread(_convert_ugoira_to_gif, local)
|
||||||
# Convert animated PNG/WebP to GIF for Qt playback
|
# Convert animated PNG/WebP to GIF for Qt playback
|
||||||
elif local.suffix.lower() in (".png", ".webp"):
|
elif local.suffix.lower() in (".png", ".webp"):
|
||||||
local = _convert_animated_to_gif(local)
|
local = await asyncio.to_thread(_convert_animated_to_gif, local)
|
||||||
finally:
|
finally:
|
||||||
pass # shared client stays open for connection reuse
|
pass # shared client stays open for connection reuse
|
||||||
return local
|
return local
|
||||||
|
|||||||
@ -905,6 +905,7 @@ class BooruApp(QMainWindow):
|
|||||||
QTimer.singleShot(100, self._clear_loading)
|
QTimer.singleShot(100, self._clear_loading)
|
||||||
|
|
||||||
from ..core.config import saved_dir, saved_folder_dir
|
from ..core.config import saved_dir, saved_folder_dir
|
||||||
|
from ..core.cache import cached_path_for, cache_dir
|
||||||
site_id = self._site_combo.currentData()
|
site_id = self._site_combo.currentData()
|
||||||
|
|
||||||
# Pre-scan saved directories once instead of per-post exists() calls
|
# Pre-scan saved directories once instead of per-post exists() calls
|
||||||
@ -918,12 +919,22 @@ class BooruApp(QMainWindow):
|
|||||||
if d.exists():
|
if d.exists():
|
||||||
_folder_saved[folder] = {int(f.stem) for f in d.iterdir() if f.is_file() and f.stem.isdigit()}
|
_folder_saved[folder] = {int(f.stem) for f in d.iterdir() if f.is_file() and f.stem.isdigit()}
|
||||||
|
|
||||||
# Pre-fetch bookmarks for the site once (used for folder checks)
|
# Pre-fetch bookmarks for the site once and project to a post-id set
|
||||||
|
# so the per-post check below is an O(1) membership test instead of
|
||||||
|
# a synchronous SQLite query (was N queries on the GUI thread).
|
||||||
_favs = self._db.get_bookmarks(site_id=site_id) if site_id else []
|
_favs = self._db.get_bookmarks(site_id=site_id) if site_id else []
|
||||||
|
_bookmarked_ids: set[int] = {f.post_id for f in _favs}
|
||||||
|
|
||||||
|
# Pre-scan the cache dir into a name set so the per-post drag-path
|
||||||
|
# lookup is one stat-equivalent (one iterdir) instead of N stat calls.
|
||||||
|
_cd = cache_dir()
|
||||||
|
_cached_names: set[str] = set()
|
||||||
|
if _cd.exists():
|
||||||
|
_cached_names = {f.name for f in _cd.iterdir() if f.is_file()}
|
||||||
|
|
||||||
for i, (post, thumb) in enumerate(zip(posts, thumbs)):
|
for i, (post, thumb) in enumerate(zip(posts, thumbs)):
|
||||||
# Bookmark status (DB)
|
# Bookmark status (DB)
|
||||||
if site_id and self._db.is_bookmarked(site_id, post.id):
|
if post.id in _bookmarked_ids:
|
||||||
thumb.set_bookmarked(True)
|
thumb.set_bookmarked(True)
|
||||||
# Saved status (filesystem) — independent of bookmark
|
# Saved status (filesystem) — independent of bookmark
|
||||||
saved = post.id in _saved_ids
|
saved = post.id in _saved_ids
|
||||||
@ -934,9 +945,8 @@ class BooruApp(QMainWindow):
|
|||||||
break
|
break
|
||||||
thumb.set_saved_locally(saved)
|
thumb.set_saved_locally(saved)
|
||||||
# Set drag path from cache
|
# Set drag path from cache
|
||||||
from ..core.cache import cached_path_for
|
|
||||||
cached = cached_path_for(post.file_url)
|
cached = cached_path_for(post.file_url)
|
||||||
if cached.exists():
|
if cached.name in _cached_names:
|
||||||
thumb._cached_path = str(cached)
|
thumb._cached_path = str(cached)
|
||||||
|
|
||||||
if post.preview_url:
|
if post.preview_url:
|
||||||
@ -1012,13 +1022,23 @@ class BooruApp(QMainWindow):
|
|||||||
return
|
return
|
||||||
|
|
||||||
from ..core.config import saved_dir
|
from ..core.config import saved_dir
|
||||||
from ..core.cache import cached_path_for
|
from ..core.cache import cached_path_for, cache_dir
|
||||||
site_id = self._site_combo.currentData()
|
site_id = self._site_combo.currentData()
|
||||||
_sd = saved_dir()
|
_sd = saved_dir()
|
||||||
_saved_ids: set[int] = set()
|
_saved_ids: set[int] = set()
|
||||||
if _sd.exists():
|
if _sd.exists():
|
||||||
_saved_ids = {int(f.stem) for f in _sd.iterdir() if f.is_file() and f.stem.isdigit()}
|
_saved_ids = {int(f.stem) for f in _sd.iterdir() if f.is_file() and f.stem.isdigit()}
|
||||||
|
|
||||||
|
# Pre-fetch bookmarks → set, and pre-scan cache dir → set, so the
|
||||||
|
# per-post checks below avoid N synchronous SQLite/stat calls on the
|
||||||
|
# GUI thread (matches the optimisation in _on_search_done).
|
||||||
|
_favs = self._db.get_bookmarks(site_id=site_id) if site_id else []
|
||||||
|
_bookmarked_ids: set[int] = {f.post_id for f in _favs}
|
||||||
|
_cd = cache_dir()
|
||||||
|
_cached_names: set[str] = set()
|
||||||
|
if _cd.exists():
|
||||||
|
_cached_names = {f.name for f in _cd.iterdir() if f.is_file()}
|
||||||
|
|
||||||
posts = ss.append_queue[:]
|
posts = ss.append_queue[:]
|
||||||
ss.append_queue.clear()
|
ss.append_queue.clear()
|
||||||
start_idx = len(self._posts)
|
start_idx = len(self._posts)
|
||||||
@ -1027,11 +1047,11 @@ class BooruApp(QMainWindow):
|
|||||||
|
|
||||||
for i, (post, thumb) in enumerate(zip(posts, thumbs)):
|
for i, (post, thumb) in enumerate(zip(posts, thumbs)):
|
||||||
idx = start_idx + i
|
idx = start_idx + i
|
||||||
if site_id and self._db.is_bookmarked(site_id, post.id):
|
if post.id in _bookmarked_ids:
|
||||||
thumb.set_bookmarked(True)
|
thumb.set_bookmarked(True)
|
||||||
thumb.set_saved_locally(post.id in _saved_ids)
|
thumb.set_saved_locally(post.id in _saved_ids)
|
||||||
cached = cached_path_for(post.file_url)
|
cached = cached_path_for(post.file_url)
|
||||||
if cached.exists():
|
if cached.name in _cached_names:
|
||||||
thumb._cached_path = str(cached)
|
thumb._cached_path = str(cached)
|
||||||
if post.preview_url:
|
if post.preview_url:
|
||||||
self._fetch_thumbnail(idx, post.preview_url)
|
self._fetch_thumbnail(idx, post.preview_url)
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user