from collections import OrderedDict from dataclasses import dataclass from pathlib import Path from typing import Union from django.conf import settings from django.contrib.auth.models import User from django.db.models import Count, Q, F, Case, When, PositiveSmallIntegerField from django.utils.http import urlsafe_base64_encode from .models import ComicBook, Directory, ComicStatus def generate_title_from_path(file_path: Path): if file_path == "Home": return "CBWebReader" return f'CBWebReader - {" - ".join(p for p in file_path.parts)}' class Menu: def __init__(self, user, page=""): """ :type page: str """ self.menu_items = OrderedDict() self.menu_items["Browse"] = "/comic/" self.menu_items["Recent"] = "/comic/recent/" self.menu_items["Account"] = "/comic/account/" if user.is_superuser: self.menu_items["Users"] = "/comic/settings/users/" self.menu_items["Logout"] = "/logout/" self.current_page = page class Breadcrumb: def __init__(self): self.name = "Home" self.url = "/comic/" def __str__(self): return self.name def __unicode__(self): return self.name def generate_breadcrumbs_from_path(directory=False, book=False): """ :type directory: Directory :type book: ComicBook """ output = [Breadcrumb()] if directory: folders = directory.get_path_objects() else: folders = [] for item in folders[::-1]: bc = Breadcrumb() bc.name = item.name bc.url = "/comic/" + urlsafe_base64_encode(item.selector.bytes) output.append(bc) if book: bc = Breadcrumb() bc.name = book.file_name bc.url = "/read/" + urlsafe_base64_encode(book.selector.bytes) output.append(bc) return output def generate_breadcrumbs_from_menu(paths): output = [Breadcrumb()] for item in paths: bc = Breadcrumb() bc.name = item[0] bc.url = item[1] output.append(bc) return output @dataclass class DirFile: obj: Union[Directory, ComicBook] name: str = '' item_type: str = '' percent: int = 0 selector: str = '' total: int = None total_read: int = None total_unread: int = None def __post_init__(self): self.item_type = type(self.obj).__name__ if hasattr(self.obj, 'total') and hasattr(self.obj, 'total_read'): # because pages count from zero. total_adjustment = 1 if isinstance(self.obj, Directory): total_adjustment = 0 self.total = self.obj.total - total_adjustment self.total_read = self.obj.total_read self.total_unread = self.total - self.total_read try: self.percent = int((self.obj.total_read / self.total) * 100) except ZeroDivisionError: self.percent = 0 self.selector = self.obj.url_safe_selector if isinstance(self.obj, Directory): self.name = self.obj.name elif isinstance(self.obj, ComicBook): self.name = self.obj.file_name def generate_directory(user: User, directory=False): """ :type user: User :type directory: Directory """ base_dir = settings.COMIC_BOOK_VOLUME files = [] if directory: dir_path = Path(base_dir, directory.path) dir_list = [x for x in sorted(dir_path.glob('*')) if Path(base_dir, directory.path, x).is_dir()] else: dir_path = base_dir dir_list = [x for x in sorted(dir_path.glob('*')) if Path(base_dir, x).is_dir()] file_list = [x for x in sorted(dir_path.glob('*')) if x.is_file()] if directory: dir_list_obj = Directory.objects.filter(name__in=[x.name for x in dir_list], parent=directory) file_list_obj = ComicBook.objects.filter(file_name__in=[x.name for x in file_list], directory=directory) else: dir_list_obj = Directory.objects.filter(name__in=[x.name for x in dir_list], parent__isnull=True) file_list_obj = ComicBook.objects.filter(file_name__in=[x.name for x in file_list], directory__isnull=True) dir_list_obj = dir_list_obj.annotate( total=Count('comicbook', distinct=True), total_read=Count('comicbook__comicstatus', Q(comicbook__comicstatus__finished=True, comicbook__comicstatus__user=user), distinct=True) ) # Create Missing Status status_list = [x.comic for x in ComicStatus.objects.filter(comic__in=file_list_obj, user=user).select_related('comic')] new_status = [ComicStatus(comic=file, user=user) for file in file_list_obj if file not in status_list] ComicStatus.objects.bulk_create(new_status) file_list_obj = file_list_obj.annotate( total=Count('comicpage', distinct=True), total_read=F('comicstatus__last_read_page'), finished=F('comicstatus__finished'), unread=F('comicstatus__unread'), user=F('comicstatus__user'), classification=Case( When(directory__isnull=True, then=Directory.Classification.C_18), default=F('directory__classification'), output_field=PositiveSmallIntegerField(choices=Directory.Classification.choices) ) ).filter(Q(user__isnull=True) | Q(user=user.id)) for directory_obj in dir_list_obj: files.append(DirFile(directory_obj)) dir_list.remove(Path(dir_path, directory_obj.name)) for file_obj in file_list_obj: files.append(DirFile(file_obj)) file_list.remove(Path(dir_path, file_obj.file_name)) for directory_name in dir_list: if directory: directory_obj = Directory(name=directory_name.name, parent=directory) else: directory_obj = Directory(name=directory_name.name) directory_obj.save() directory_obj.total = 0 directory_obj.total_read = 0 files.append(DirFile(directory_obj)) files = [file for file in files if file.obj.classification <= user.usermisc.allowed_to_read] for file_name in file_list: if file_name.suffix.lower() in [".rar", ".zip", ".cbr", ".cbz", ".pdf"]: book = ComicBook.process_comic_book(file_name, directory) files.append(DirFile(book)) files.sort(key=lambda x: x.name) files.sort(key=lambda x: x.item_type, reverse=True) return files def generate_label(book): """ book need to be annotated with the following from ComicStatus * unread * finished * last_read_page * total_pages :param book: ComicBook :return: str """ unread_text = '