mirror of
https://github.com/ajurna/cbwebreader.git
synced 2025-12-06 06:17:17 +00:00
V1 cleanup (#73)
* removed obsolete code * removed obsolete code * removed obsolete code and added type annotations * removed obsolete code and added type annotations * version bump Co-authored-by: Peter Dwyer <peter.dwyer@clanwilliamhealth.com>
This commit is contained in:
285
comic/models.py
285
comic/models.py
@@ -3,21 +3,21 @@ import mimetypes
|
||||
import uuid
|
||||
import zipfile
|
||||
from functools import reduce
|
||||
from itertools import zip_longest, chain
|
||||
from itertools import zip_longest
|
||||
from pathlib import Path
|
||||
from typing import Optional, List, Union, Tuple
|
||||
from typing import Optional, List, Union, Tuple, Final
|
||||
|
||||
# noinspection PyPackageRequirements
|
||||
import fitz
|
||||
import rarfile
|
||||
from PIL import Image, UnidentifiedImageError
|
||||
from PIL.Image import Image as Image_type
|
||||
from django.conf import settings
|
||||
from django.contrib.auth.models import User, AbstractUser
|
||||
from django.core.files.uploadedfile import InMemoryUploadedFile
|
||||
from django.db import models
|
||||
from django.db.models import UniqueConstraint
|
||||
from django.db.transaction import atomic
|
||||
from django.templatetags.static import static
|
||||
from django.utils.http import urlsafe_base64_encode
|
||||
from django_boost.models.fields import AutoOneToOneField
|
||||
from imagekit.models import ProcessedImageField
|
||||
from imagekit.processors import ResizeToFill
|
||||
@@ -57,34 +57,14 @@ class Directory(models.Model):
|
||||
return "Directory: {0}; {1}".format(self.name, self.parent)
|
||||
|
||||
@property
|
||||
def title(self):
|
||||
def title(self) -> str:
|
||||
return self.name
|
||||
|
||||
@property
|
||||
def type(self):
|
||||
def type(self) -> str:
|
||||
return 'Directory'
|
||||
|
||||
def mark_read(self, user):
|
||||
books = ComicBook.objects.filter(directory=self)
|
||||
for book in books:
|
||||
book.mark_read(user)
|
||||
|
||||
def mark_unread(self, user):
|
||||
books = ComicBook.objects.filter(directory=self)
|
||||
for book in books:
|
||||
book.mark_unread(user)
|
||||
|
||||
def get_thumbnail_url(self):
|
||||
if self.thumbnail:
|
||||
return self.thumbnail.url
|
||||
else:
|
||||
self.generate_thumbnail()
|
||||
if self.thumbnail:
|
||||
return self.thumbnail.url
|
||||
else:
|
||||
return static('img/placeholder.png')
|
||||
|
||||
def generate_thumbnail(self):
|
||||
def generate_thumbnail(self) -> None:
|
||||
book: ComicBook = ComicBook.objects.filter(directory=self).order_by('file_name').first()
|
||||
if not book:
|
||||
return
|
||||
@@ -105,7 +85,7 @@ class Directory(models.Model):
|
||||
else:
|
||||
return Path(path_items[0])
|
||||
|
||||
def get_path_items(self, p: Optional[List] = None) -> List[str]:
|
||||
def get_path_items(self, p: Optional[List] = None) -> List[Path]:
|
||||
if p is None:
|
||||
p = []
|
||||
p.append(self.name)
|
||||
@@ -113,7 +93,7 @@ class Directory(models.Model):
|
||||
self.parent.get_path_items(p)
|
||||
return p
|
||||
|
||||
def get_path_objects(self, p=None):
|
||||
def get_path_objects(self, p=None) -> List["Directory"]:
|
||||
if p is None:
|
||||
p = []
|
||||
p.append(self)
|
||||
@@ -121,14 +101,6 @@ class Directory(models.Model):
|
||||
self.parent.get_path_objects(p)
|
||||
return p
|
||||
|
||||
@property
|
||||
def url_safe_selector(self):
|
||||
return urlsafe_base64_encode(self.selector.bytes)
|
||||
|
||||
def set_classification(self, form_data):
|
||||
self.classification = form_data['classification']
|
||||
self.save()
|
||||
|
||||
|
||||
class ComicBook(models.Model):
|
||||
file_name = models.TextField()
|
||||
@@ -148,36 +120,17 @@ class ComicBook(models.Model):
|
||||
UniqueConstraint(fields=['directory', 'file_name'], name='one_comic_name_per_directory')
|
||||
]
|
||||
|
||||
def __str__(self):
|
||||
def __str__(self) -> str:
|
||||
return self.file_name
|
||||
|
||||
@property
|
||||
def title(self):
|
||||
def title(self) -> str:
|
||||
return self.file_name
|
||||
|
||||
@property
|
||||
def type(self):
|
||||
def type(self) -> str:
|
||||
return 'ComicBook'
|
||||
|
||||
def mark_read(self, user: User):
|
||||
status, _ = ComicStatus.objects.get_or_create(comic=self, user=user)
|
||||
status.mark_read()
|
||||
|
||||
def mark_unread(self, user: User):
|
||||
status, _ = ComicStatus.objects.get_or_create(comic=self, user=user)
|
||||
status.mark_unread()
|
||||
|
||||
def mark_previous(self, user):
|
||||
books = ComicBook.objects.filter(directory=self.directory).order_by('file_name')
|
||||
for book in books:
|
||||
if book == self:
|
||||
break
|
||||
book.mark_read(user)
|
||||
|
||||
@property
|
||||
def url_safe_selector(self):
|
||||
return urlsafe_base64_encode(self.selector.bytes)
|
||||
|
||||
def get_pdf(self) -> Path:
|
||||
base_dir = settings.COMIC_BOOK_VOLUME
|
||||
if self.directory:
|
||||
@@ -185,7 +138,7 @@ class ComicBook(models.Model):
|
||||
else:
|
||||
return Path(base_dir, self.file_name)
|
||||
|
||||
def get_image(self, page: int):
|
||||
def get_image(self, page: int) -> Union[Tuple[io.BytesIO, Image_type], Tuple[bool, bool]]:
|
||||
base_dir = settings.COMIC_BOOK_VOLUME
|
||||
if self.directory:
|
||||
archive_path = Path(base_dir, self.directory.path, self.file_name)
|
||||
@@ -196,7 +149,7 @@ class ComicBook(models.Model):
|
||||
except rarfile.NotRarFile:
|
||||
archive = zipfile.ZipFile(archive_path)
|
||||
except zipfile.BadZipfile:
|
||||
return False
|
||||
return False, False
|
||||
|
||||
page_obj = ComicPage.objects.get(Comic=self, index=page)
|
||||
try:
|
||||
@@ -207,39 +160,34 @@ class ComicBook(models.Model):
|
||||
out = (archive.open(page_obj.page_file_name), page_obj.content_type)
|
||||
return out
|
||||
|
||||
def get_thumbnail_url(self):
|
||||
if self.thumbnail:
|
||||
return self.thumbnail.url
|
||||
else:
|
||||
self.generate_thumbnail()
|
||||
return self.thumbnail.url
|
||||
def generate_thumbnail_pdf(self, page_index: int = 0) -> Tuple[io.BytesIO, Image_type, str]:
|
||||
img, pil_data = self._get_pdf_image(page_index if page_index else 0)
|
||||
return img, pil_data, 'Image/JPEG'
|
||||
|
||||
def generate_thumbnail(self, page_index: int = None):
|
||||
def generate_thumbnail_archive(self, page_index: int = 0) -> Union[Tuple[io.BytesIO, Image_type, str],
|
||||
Tuple[bool, bool, bool]]:
|
||||
img, content_type, pil_data = False, False, False
|
||||
if page_index:
|
||||
img, content_type = self.get_image(page_index)
|
||||
pil_data = Image.open(img)
|
||||
else:
|
||||
for x in range(ComicPage.objects.filter(Comic=self).count()):
|
||||
try:
|
||||
img, content_type = self.get_image(x)
|
||||
pil_data = Image.open(img)
|
||||
break
|
||||
except UnidentifiedImageError:
|
||||
continue
|
||||
return img, pil_data, content_type
|
||||
|
||||
def generate_thumbnail(self, page_index: int = None) -> None:
|
||||
|
||||
if Path(self.file_name).suffix.lower() == '.pdf':
|
||||
if page_index:
|
||||
img, pil_data = self._get_pdf_image(page_index)
|
||||
else:
|
||||
img, pil_data = self._get_pdf_image(0)
|
||||
content_type = 'Image/JPEG'
|
||||
img, pil_data, content_type = self.generate_thumbnail_pdf(page_index if page_index else 0)
|
||||
else:
|
||||
if page_index:
|
||||
img, content_type = self.get_image(page_index)
|
||||
pil_data = Image.open(img)
|
||||
else:
|
||||
for x in range(ComicPage.objects.filter(Comic=self).count()):
|
||||
try:
|
||||
img, content_type = self.get_image(x)
|
||||
pil_data = Image.open(img)
|
||||
break
|
||||
except UnidentifiedImageError:
|
||||
continue
|
||||
try:
|
||||
img
|
||||
content_type
|
||||
pil_data
|
||||
except NameError:
|
||||
return
|
||||
img, pil_data, content_type = self.generate_thumbnail_archive(page_index)
|
||||
if not img:
|
||||
return
|
||||
self.thumbnail = InMemoryUploadedFile(
|
||||
img,
|
||||
None,
|
||||
@@ -250,133 +198,25 @@ class ComicBook(models.Model):
|
||||
)
|
||||
self.save()
|
||||
|
||||
def _get_pdf_image(self, page_index: int):
|
||||
# noinspection PyTypeChecker
|
||||
def _get_pdf_image(self, page_index: int) -> Tuple[io.BytesIO, Image_type]:
|
||||
# noinspection PyUnresolvedReferences
|
||||
doc = fitz.open(self.get_pdf())
|
||||
page = doc[page_index]
|
||||
pix = page.get_pixmap()
|
||||
mode = "RGBA" if pix.alpha else "RGB"
|
||||
pil_data = Image.frombytes(mode, [pix.width, pix.height], pix.samples)
|
||||
mode: Final = "RGBA" if pix.alpha else "RGB"
|
||||
# noinspection PyTypeChecker
|
||||
pil_data = Image.frombytes(mode, (pix.width, pix.height), pix.samples)
|
||||
img = io.BytesIO()
|
||||
pil_data.save(img, format="JPEG")
|
||||
img.seek(0)
|
||||
return img, pil_data
|
||||
|
||||
def is_last_page(self, page):
|
||||
if (self.page_count - 1) == page:
|
||||
return True
|
||||
return False
|
||||
|
||||
@property
|
||||
def page_count(self):
|
||||
def page_count(self) -> int:
|
||||
return ComicPage.objects.filter(Comic=self).count()
|
||||
|
||||
def nav(self, user):
|
||||
next_path, next_type = self.nav_get_next_comic(user)
|
||||
prev_path, prev_type = self.nav_get_prev_comic(user)
|
||||
return {
|
||||
"next_path": next_path,
|
||||
"next_type": next_type,
|
||||
"prev_path": prev_path,
|
||||
"prev_type": prev_type,
|
||||
"cur_path": urlsafe_base64_encode(self.selector.bytes)
|
||||
}
|
||||
|
||||
def nav_get_prev_comic(self, user) -> str:
|
||||
base_dir = settings.COMIC_BOOK_VOLUME
|
||||
if self.directory:
|
||||
folder = Path(base_dir, self.directory.path)
|
||||
else:
|
||||
folder = base_dir
|
||||
dir_list = ComicBook.get_ordered_dir_list(folder)
|
||||
comic_index = dir_list.index(self.file_name)
|
||||
if comic_index == 0:
|
||||
if self.directory:
|
||||
comic_path = urlsafe_base64_encode(self.directory.selector.bytes), type(self.directory).__name__
|
||||
else:
|
||||
comic_path = "", None
|
||||
else:
|
||||
prev_comic = dir_list[comic_index - 1]
|
||||
|
||||
if Path(folder, prev_comic).is_dir():
|
||||
if self.directory:
|
||||
comic_path = urlsafe_base64_encode(self.directory.selector.bytes), type(self.directory).__name__
|
||||
else:
|
||||
comic_path = "", None
|
||||
else:
|
||||
try:
|
||||
if self.directory:
|
||||
book = ComicBook.objects.get(file_name=prev_comic, directory=self.directory)
|
||||
else:
|
||||
book = ComicBook.objects.get(file_name=prev_comic, directory__isnull=True)
|
||||
except ComicBook.DoesNotExist:
|
||||
if self.directory:
|
||||
book = ComicBook.process_comic_book(Path(prev_comic), self.directory)
|
||||
else:
|
||||
book = ComicBook.process_comic_book(Path(prev_comic))
|
||||
cs, _ = ComicStatus.objects.get_or_create(comic=book, user=user)
|
||||
comic_path = urlsafe_base64_encode(book.selector.bytes), type(book).__name__
|
||||
|
||||
return comic_path
|
||||
|
||||
def nav_get_next_comic(self, user):
|
||||
base_dir = settings.COMIC_BOOK_VOLUME
|
||||
if self.directory:
|
||||
folder = Path(base_dir, self.directory.path)
|
||||
else:
|
||||
folder = base_dir
|
||||
dir_list = ComicBook.get_ordered_dir_list(folder)
|
||||
comic_index = dir_list.index(self.file_name)
|
||||
try:
|
||||
next_comic = dir_list[comic_index + 1]
|
||||
try:
|
||||
if self.directory:
|
||||
book = ComicBook.objects.get(file_name=next_comic, directory=self.directory)
|
||||
else:
|
||||
book = ComicBook.objects.get(file_name=next_comic, directory__isnull=True)
|
||||
except ComicBook.DoesNotExist:
|
||||
if self.directory:
|
||||
book = ComicBook.process_comic_book(Path(next_comic), self.directory)
|
||||
else:
|
||||
book = ComicBook.process_comic_book(Path(next_comic))
|
||||
except ComicBook.MultipleObjectsReturned:
|
||||
if self.directory:
|
||||
books = ComicBook.objects.filter(file_name=next_comic, directory=self.directory).order_by('id')
|
||||
else:
|
||||
books = ComicBook.objects.get(file_name=next_comic, directory__isnull=True).order_by('id')
|
||||
book = books.first()
|
||||
books = books.exclude(id=book.id)
|
||||
books.delete()
|
||||
if type(book) is str:
|
||||
raise IndexError
|
||||
comic_path = urlsafe_base64_encode(book.selector.bytes), type(book).__name__
|
||||
except IndexError:
|
||||
if self.directory:
|
||||
comic_path = urlsafe_base64_encode(self.directory.selector.bytes), type(self.directory).__name__
|
||||
else:
|
||||
comic_path = "", None
|
||||
return comic_path
|
||||
|
||||
class DirFile:
|
||||
def __init__(self):
|
||||
self.name = ""
|
||||
self.isdir = False
|
||||
self.icon = ""
|
||||
self.iscb = False
|
||||
self.location = ""
|
||||
self.label = ""
|
||||
self.cur_page = 0
|
||||
|
||||
def __str__(self):
|
||||
return self.name
|
||||
|
||||
@staticmethod
|
||||
def process_comic_book(comic_file_path: Path, directory: "Directory" = False) -> Union["ComicBook", Path]:
|
||||
"""
|
||||
|
||||
:type comic_file_path: str
|
||||
:type directory: Directory
|
||||
"""
|
||||
try:
|
||||
book = ComicBook.objects.get(file_name=comic_file_path.name, version=0)
|
||||
book.directory = directory
|
||||
@@ -404,19 +244,8 @@ class ComicBook(models.Model):
|
||||
page.save()
|
||||
return book
|
||||
|
||||
@staticmethod
|
||||
def get_ordered_dir_list(folder: Path) -> List[str]:
|
||||
directories = []
|
||||
files = []
|
||||
for item in folder.glob('*'):
|
||||
if item.is_dir():
|
||||
directories.append(item)
|
||||
else:
|
||||
files.append(item)
|
||||
return [x.name for x in chain(sorted(directories), sorted(files))]
|
||||
|
||||
@property
|
||||
def get_archive_path(self):
|
||||
def get_archive_path(self) -> Path:
|
||||
if self.directory:
|
||||
return Path(settings.COMIC_BOOK_VOLUME, self.directory.get_path(), self.file_name)
|
||||
else:
|
||||
@@ -441,13 +270,13 @@ class ComicBook(models.Model):
|
||||
raise NotCompatibleArchive
|
||||
|
||||
@staticmethod
|
||||
def get_archive_files(archive):
|
||||
def get_archive_files(archive) -> List[Tuple[str, str]]:
|
||||
return [
|
||||
(x, mimetypes.guess_type(x)[0]) for x in sorted(archive.namelist())
|
||||
if not x.endswith('/') and mimetypes.guess_type(x)[0]
|
||||
]
|
||||
|
||||
def verify_pages(self, pages: Optional["ComicPage"] = None):
|
||||
def verify_pages(self, pages: Optional["ComicPage"] = None) -> None:
|
||||
if not pages:
|
||||
pages = ComicPage.objects.filter(Comic=self)
|
||||
|
||||
@@ -493,7 +322,8 @@ class ComicPage(models.Model):
|
||||
|
||||
class ComicStatus(models.Model):
|
||||
user = models.ForeignKey(User, unique=False, null=False, on_delete=models.CASCADE)
|
||||
comic = models.ForeignKey(ComicBook, unique=False, blank=False, null=False, on_delete=models.CASCADE, to_field="selector")
|
||||
comic = models.ForeignKey(ComicBook, unique=False, blank=False, null=False, on_delete=models.CASCADE,
|
||||
to_field="selector")
|
||||
last_read_page = models.IntegerField(default=0)
|
||||
unread = models.BooleanField(default=True)
|
||||
finished = models.BooleanField(default=False)
|
||||
@@ -503,23 +333,10 @@ class ComicStatus(models.Model):
|
||||
UniqueConstraint(fields=['user', 'comic'], name='one_per_user_per_comic')
|
||||
]
|
||||
|
||||
def mark_read(self):
|
||||
page_count = ComicPage.objects.filter(Comic=self.comic).count()
|
||||
self.unread = False
|
||||
self.finished = True
|
||||
self.last_read_page = page_count - 1
|
||||
self.save()
|
||||
|
||||
def mark_unread(self):
|
||||
self.unread = True
|
||||
self.finished = False
|
||||
self.last_read_page = 0
|
||||
self.save()
|
||||
|
||||
def __str__(self):
|
||||
def __str__(self) -> str:
|
||||
return self.__repr__()
|
||||
|
||||
def __repr__(self):
|
||||
def __repr__(self) -> str:
|
||||
return (
|
||||
f"<ComicStatus:{self.user.username}:{self.comic.file_name}:{self.last_read_page}:"
|
||||
f"{self.unread}:{self.finished}"
|
||||
|
||||
Reference in New Issue
Block a user