diff --git a/fastanime/Utility/user_data_helper.py b/fastanime/Utility/user_data_helper.py index 08aa673..27582d5 100644 --- a/fastanime/Utility/user_data_helper.py +++ b/fastanime/Utility/user_data_helper.py @@ -2,29 +2,19 @@ Contains Helper functions to read and write the user data files """ -import os from datetime import date, datetime from kivy.logger import Logger -from kivy.storage.jsonstore import JsonStore - -from .. import data_folder today = date.today() now = datetime.now() -# TODO:confirm data integrity -if os.path.exists(os.path.join(data_folder, "user_data.json")): - user_data = JsonStore(os.path.join(data_folder, "user_data.json")) -else: - user_data_path = os.path.join(data_folder, "user_data.json") - user_data = JsonStore(user_data_path) - - # Get the user data def get_user_anime_list() -> list: + from .. import user_data + try: return user_data.get("user_anime_list")[ "user_anime_list" @@ -35,6 +25,8 @@ def get_user_anime_list() -> list: def update_user_anime_list(updated_list: list): + from .. import user_data + try: updated_list_ = list(set(updated_list)) user_data.put("user_anime_list", user_anime_list=updated_list_) diff --git a/fastanime/View/base_screen.py b/fastanime/View/base_screen.py index 344f4a2..879969b 100644 --- a/fastanime/View/base_screen.py +++ b/fastanime/View/base_screen.py @@ -69,7 +69,7 @@ class BaseScreenView(MDScreen, Observer): super().__init__(**kw) # Often you need to get access to the application object from the view # class. You can do this using this attribute. - from ..__main__ import FastAnime + from .. import FastAnime self.app: FastAnime = MDApp.get_running_app() # type: ignore # Adding a view class as observer. diff --git a/fastanime/__init__.py b/fastanime/__init__.py index 660d9f2..113df46 100644 --- a/fastanime/__init__.py +++ b/fastanime/__init__.py @@ -1,6 +1,35 @@ import os +import random + import plyer -from kivy.resources import resource_add_path +from kivy.config import Config +from kivy.loader import Loader +from kivy.logger import Logger +from kivy.resources import resource_add_path, resource_find +from kivy.storage.jsonstore import JsonStore +from kivy.uix.screenmanager import FadeTransition, ScreenManager +from kivy.uix.settings import Settings, SettingsWithSidebar +from kivymd.app import MDApp + +from .libs.mpv.player import mpv_player +from .Utility import ( + themes_available, +) +from .Utility.show_notification import show_notification +from .View.components.media_card.components.media_popup import MediaPopup +from .View.screens import screens + +os.environ["KIVY_VIDEO"] = "ffpyplayer" # noqa: E402 + +Config.set("graphics", "width", "1000") # noqa: E402 +Config.set("graphics", "minimum_width", "1000") # noqa: E402 +Config.set("kivy", "window_icon", resource_find("logo.ico")) # noqa: E402 +Config.write() # noqa: E402 + + +Loader.num_workers = 5 +Loader.max_upload_per_frame = 10 + # print(plyer.storagepath.get_application_dir(), plyer.storagepath.get_home_dir()) app_dir = os.path.abspath(os.path.dirname(__file__)) @@ -21,7 +50,142 @@ else: os.mkdir(downloads_dir) +# TODO:confirm data integrity +if os.path.exists(os.path.join(data_folder, "user_data.json")): + user_data = JsonStore(os.path.join(data_folder, "user_data.json")) +else: + user_data_path = os.path.join(data_folder, "user_data.json") + user_data = JsonStore(user_data_path) + + assets_folder = os.path.join(app_dir, "assets") resource_add_path(assets_folder) conigs_folder = os.path.join(app_dir, "configs") resource_add_path(conigs_folder) + + +from .Utility import user_data_helper + + +from .Utility.downloader.downloader import downloader + + +class FastAnime(MDApp): + # Ensure the user data fields exist + if not (user_data.exists("user_anime_list")): + user_data_helper.update_user_anime_list([]) + + def __init__(self, **kwargs): + self.default_banner_image = resource_find( + random.choice(["banner_1.jpg", "banner.jpg"]) + ) + self.default_anime_image = resource_find( + random.choice(["default_1.jpg", "default.jpg"]) + ) + super().__init__(**kwargs) + self.icon = resource_find("logo.png") + + self.load_all_kv_files(self.directory) + self.theme_cls.theme_style = "Dark" + self.theme_cls.primary_palette = "Lightcoral" + self.manager_screens = ScreenManager() + self.manager_screens.transition = FadeTransition() + + def build(self) -> ScreenManager: + self.settings_cls = SettingsWithSidebar + + self.generate_application_screens() + + if config := self.config: + if theme_color := config.get("Preferences", "theme_color"): + self.theme_cls.primary_palette = theme_color + if theme_style := config.get("Preferences", "theme_style"): + self.theme_cls.theme_style = theme_style + + self.anime_screen = self.manager_screens.get_screen("anime screen") + self.search_screen = self.manager_screens.get_screen("search screen") + self.download_screen = self.manager_screens.get_screen("downloads screen") + self.home_screen = self.manager_screens.get_screen("home screen") + return self.manager_screens + + def on_start(self, *args): + self.media_card_popup = MediaPopup() + + def generate_application_screens(self) -> None: + for i, name_screen in enumerate(screens.keys()): + model = screens[name_screen]["model"]() + controller = screens[name_screen]["controller"](model) + view = controller.get_view() + view.manager_screens = self.manager_screens + view.name = name_screen + self.manager_screens.add_widget(view) + + def build_config(self, config): + # General settings setup + config.setdefaults( + "Preferences", + { + "theme_color": "Cyan", + "theme_style": "Dark", + "downloads_dir": downloads_dir, + }, + ) + + def build_settings(self, settings: Settings): + settings.add_json_panel( + "Settings", self.config, resource_find("general_settings_panel.json") + ) + + def on_config_change(self, config, section, key, value): + # TODO: Change to match case + if section == "Preferences": + match key: + case "theme_color": + if value in themes_available: + self.theme_cls.primary_palette = value + else: + Logger.warning( + "AniXStream Settings: An invalid theme has been entered and will be ignored" + ) + config.set("Preferences", "theme_color", "Cyan") + config.write() + case "theme_style": + self.theme_cls.theme_style = value + + def on_stop(self): + pass + + def search_for_anime(self, search_field, **kwargs): + if self.manager_screens.current != "search screen": + self.manager_screens.current = "search screen" + self.search_screen.handle_search_for_anime(search_field, **kwargs) + + def add_anime_to_user_anime_list(self, id: int): + updated_list = user_data_helper.get_user_anime_list() + updated_list.append(id) + user_data_helper.update_user_anime_list(updated_list) + + def remove_anime_from_user_anime_list(self, id: int): + updated_list = user_data_helper.get_user_anime_list() + if updated_list.count(id): + updated_list.remove(id) + user_data_helper.update_user_anime_list(updated_list) + + def show_anime_screen(self, id: int, title, caller_screen_name: str): + self.manager_screens.current = "anime screen" + self.anime_screen.controller.update_anime_view(id, title, caller_screen_name) + + def play_on_mpv(self, anime_video_url: str): + if mpv_player.mpv_process: + mpv_player.stop_mpv() + mpv_player.run_mpv(anime_video_url) + + def download_anime_video(self, url: str, anime_title: tuple): + self.download_screen.new_download_task(anime_title) + show_notification("New Download", f"{anime_title[0]} episode: {anime_title[1]}") + progress_hook = self.download_screen.on_episode_download_progress + downloader.download_file(url, anime_title, progress_hook) + + +def main(): + FastAnime().run() diff --git a/fastanime/__init__f.py b/fastanime/__init__f.py new file mode 100644 index 0000000..c60bcaf --- /dev/null +++ b/fastanime/__init__f.py @@ -0,0 +1,33 @@ +import os +import plyer +from kivy.resources import resource_add_path +from . import app + + +# print(plyer.storagepath.get_application_dir(), plyer.storagepath.get_home_dir()) +app_dir = os.path.abspath(os.path.dirname(__file__)) + + +data_folder = os.path.join(app_dir, "data") +if not os.path.exists(data_folder): + os.mkdir(data_folder) + + +if vid_path := plyer.storagepath.get_videos_dir(): # type: ignore + downloads_dir = os.path.join(vid_path, "FastAnime") + if not os.path.exists(downloads_dir): + os.mkdir(downloads_dir) +else: + downloads_dir = os.path.join(app_dir, "videos") + if not os.path.exists(downloads_dir): + os.mkdir(downloads_dir) + + +assets_folder = os.path.join(app_dir, "assets") +resource_add_path(assets_folder) +conigs_folder = os.path.join(app_dir, "configs") +resource_add_path(conigs_folder) + + +def main(): + app.FastAnime(downloads_dir).run() diff --git a/fastanime/__main__.py b/fastanime/__main__.py index 1f6baf6..e740e2d 100644 --- a/fastanime/__main__.py +++ b/fastanime/__main__.py @@ -1,164 +1,24 @@ +import sys import os -import random -from kivy.config import Config -from kivy.loader import Loader -from kivy.logger import Logger -from kivy.resources import resource_find -from kivy.uix.screenmanager import FadeTransition, ScreenManager -from kivy.uix.settings import Settings, SettingsWithSidebar -from kivymd.app import MDApp +if __package__ is None and not getattr(sys, "frozen", False): + # direct call of __main__.py + import os.path -from fastanime.Utility.show_notification import show_notification - -from . import downloads_dir -from .libs.mpv.player import mpv_player -from .Utility import ( - themes_available, - user_data_helper, -) -from .Utility.downloader.downloader import downloader -from .Utility.utils import write_crash -from .View.components.media_card.components.media_popup import MediaPopup -from .View.screens import screens - -os.environ["KIVY_VIDEO"] = "ffpyplayer" # noqa: E402 - -Config.set("graphics", "width", "1000") # noqa: E402 -Config.set("graphics", "minimum_width", "1000") # noqa: E402 -Config.set("kivy", "window_icon", resource_find("logo.ico")) # noqa: E402 -Config.write() # noqa: E402 - - -Loader.num_workers = 5 -Loader.max_upload_per_frame = 10 - - -# Ensure the user data fields exist -if not (user_data_helper.user_data.exists("user_anime_list")): - user_data_helper.update_user_anime_list([]) - - -class FastAnime(MDApp): - default_anime_image = resource_find(random.choice(["default_1.jpg", "default.jpg"])) - default_banner_image = resource_find(random.choice(["banner_1.jpg", "banner.jpg"])) - - def __init__(self, **kwargs): - super().__init__(**kwargs) - self.icon = resource_find("logo.png") - - self.load_all_kv_files(self.directory) - self.theme_cls.theme_style = "Dark" - self.theme_cls.primary_palette = "Lightcoral" - self.manager_screens = ScreenManager() - self.manager_screens.transition = FadeTransition() - - def build(self) -> ScreenManager: - self.settings_cls = SettingsWithSidebar - - self.generate_application_screens() - - if config := self.config: - if theme_color := config.get("Preferences", "theme_color"): - self.theme_cls.primary_palette = theme_color - if theme_style := config.get("Preferences", "theme_style"): - self.theme_cls.theme_style = theme_style - - self.anime_screen = self.manager_screens.get_screen("anime screen") - self.search_screen = self.manager_screens.get_screen("search screen") - self.download_screen = self.manager_screens.get_screen("downloads screen") - self.home_screen = self.manager_screens.get_screen("home screen") - return self.manager_screens - - def on_start(self, *args): - self.media_card_popup = MediaPopup() - - def generate_application_screens(self) -> None: - for i, name_screen in enumerate(screens.keys()): - model = screens[name_screen]["model"]() - controller = screens[name_screen]["controller"](model) - view = controller.get_view() - view.manager_screens = self.manager_screens - view.name = name_screen - self.manager_screens.add_widget(view) - - def build_config(self, config): - # General settings setup - config.setdefaults( - "Preferences", - { - "theme_color": "Cyan", - "theme_style": "Dark", - "downloads_dir": downloads_dir, - }, - ) - - def build_settings(self, settings: Settings): - settings.add_json_panel( - "Settings", self.config, resource_find("general_settings_panel.json") - ) - - def on_config_change(self, config, section, key, value): - # TODO: Change to match case - if section == "Preferences": - match key: - case "theme_color": - if value in themes_available: - self.theme_cls.primary_palette = value - else: - Logger.warning( - "AniXStream Settings: An invalid theme has been entered and will be ignored" - ) - config.set("Preferences", "theme_color", "Cyan") - config.write() - case "theme_style": - self.theme_cls.theme_style = value - - def on_stop(self): - pass - - def search_for_anime(self, search_field, **kwargs): - if self.manager_screens.current != "search screen": - self.manager_screens.current = "search screen" - self.search_screen.handle_search_for_anime(search_field, **kwargs) - - def add_anime_to_user_anime_list(self, id: int): - updated_list = user_data_helper.get_user_anime_list() - updated_list.append(id) - user_data_helper.update_user_anime_list(updated_list) - - def remove_anime_from_user_anime_list(self, id: int): - updated_list = user_data_helper.get_user_anime_list() - if updated_list.count(id): - updated_list.remove(id) - user_data_helper.update_user_anime_list(updated_list) - - def show_anime_screen(self, id: int, title, caller_screen_name: str): - self.manager_screens.current = "anime screen" - self.anime_screen.controller.update_anime_view(id, title, caller_screen_name) - - def play_on_mpv(self, anime_video_url: str): - if mpv_player.mpv_process: - mpv_player.stop_mpv() - mpv_player.run_mpv(anime_video_url) - - def download_anime_video(self, url: str, anime_title: tuple): - self.download_screen.new_download_task(anime_title) - show_notification("New Download", f"{anime_title[0]} episode: {anime_title[1]}") - progress_hook = self.download_screen.on_episode_download_progress - downloader.download_file(url, anime_title, progress_hook) - - -def run_app(): - FastAnime().run() + path = os.path.realpath(os.path.abspath(__file__)) + sys.path.insert(0, os.path.dirname(os.path.dirname(path))) if __name__ == "__main__": in_development = bool(os.environ.get("IN_DEVELOPMENT", False)) + import fastanime + if in_development: - run_app() + fastanime.main() else: try: - run_app() + fastanime.main() except Exception as e: + from .Utility.utils import write_crash + write_crash(e) diff --git a/fastanime/fastanime.py b/fastanime/fastanime.py new file mode 100644 index 0000000..46b50b3 --- /dev/null +++ b/fastanime/fastanime.py @@ -0,0 +1,153 @@ +import os +import random + +from kivy.config import Config +from kivy.loader import Loader +from kivy.logger import Logger +from kivy.resources import resource_find +from kivy.uix.screenmanager import FadeTransition, ScreenManager +from kivy.uix.settings import Settings, SettingsWithSidebar +from kivymd.app import MDApp + +from fastanime.Utility.show_notification import show_notification + +from . import downloads_dir +from .libs.mpv.player import mpv_player +from .Utility import ( + themes_available, + user_data_helper, +) +from .Utility.downloader.downloader import downloader +from .Utility.utils import write_crash +from .View.components.media_card.components.media_popup import MediaPopup +from .View.screens import screens + +os.environ["KIVY_VIDEO"] = "ffpyplayer" # noqa: E402 + +Config.set("graphics", "width", "1000") # noqa: E402 +Config.set("graphics", "minimum_width", "1000") # noqa: E402 +Config.set("kivy", "window_icon", resource_find("logo.ico")) # noqa: E402 +Config.write() # noqa: E402 + + +Loader.num_workers = 5 +Loader.max_upload_per_frame = 10 + + +# Ensure the user data fields exist +if not (user_data_helper.user_data.exists("user_anime_list")): + user_data_helper.update_user_anime_list([]) + + +class FastAnime(MDApp): + default_anime_image = resource_find(random.choice(["default_1.jpg", "default.jpg"])) + default_banner_image = resource_find(random.choice(["banner_1.jpg", "banner.jpg"])) + + def __init__(self, **kwargs): + super().__init__(**kwargs) + self.icon = resource_find("logo.png") + + self.load_all_kv_files(self.directory) + self.theme_cls.theme_style = "Dark" + self.theme_cls.primary_palette = "Lightcoral" + self.manager_screens = ScreenManager() + self.manager_screens.transition = FadeTransition() + + def build(self) -> ScreenManager: + self.settings_cls = SettingsWithSidebar + + self.generate_application_screens() + + if config := self.config: + if theme_color := config.get("Preferences", "theme_color"): + self.theme_cls.primary_palette = theme_color + if theme_style := config.get("Preferences", "theme_style"): + self.theme_cls.theme_style = theme_style + + self.anime_screen = self.manager_screens.get_screen("anime screen") + self.search_screen = self.manager_screens.get_screen("search screen") + self.download_screen = self.manager_screens.get_screen("downloads screen") + self.home_screen = self.manager_screens.get_screen("home screen") + return self.manager_screens + + def on_start(self, *args): + self.media_card_popup = MediaPopup() + + def generate_application_screens(self) -> None: + for i, name_screen in enumerate(screens.keys()): + model = screens[name_screen]["model"]() + controller = screens[name_screen]["controller"](model) + view = controller.get_view() + view.manager_screens = self.manager_screens + view.name = name_screen + self.manager_screens.add_widget(view) + + def build_config(self, config): + # General settings setup + config.setdefaults( + "Preferences", + { + "theme_color": "Cyan", + "theme_style": "Dark", + "downloads_dir": downloads_dir, + }, + ) + + def build_settings(self, settings: Settings): + settings.add_json_panel( + "Settings", self.config, resource_find("general_settings_panel.json") + ) + + def on_config_change(self, config, section, key, value): + # TODO: Change to match case + if section == "Preferences": + match key: + case "theme_color": + if value in themes_available: + self.theme_cls.primary_palette = value + else: + Logger.warning( + "AniXStream Settings: An invalid theme has been entered and will be ignored" + ) + config.set("Preferences", "theme_color", "Cyan") + config.write() + case "theme_style": + self.theme_cls.theme_style = value + + def on_stop(self): + pass + + def search_for_anime(self, search_field, **kwargs): + if self.manager_screens.current != "search screen": + self.manager_screens.current = "search screen" + self.search_screen.handle_search_for_anime(search_field, **kwargs) + + def add_anime_to_user_anime_list(self, id: int): + updated_list = user_data_helper.get_user_anime_list() + updated_list.append(id) + user_data_helper.update_user_anime_list(updated_list) + + def remove_anime_from_user_anime_list(self, id: int): + updated_list = user_data_helper.get_user_anime_list() + if updated_list.count(id): + updated_list.remove(id) + user_data_helper.update_user_anime_list(updated_list) + + def show_anime_screen(self, id: int, title, caller_screen_name: str): + self.manager_screens.current = "anime screen" + self.anime_screen.controller.update_anime_view(id, title, caller_screen_name) + + def play_on_mpv(self, anime_video_url: str): + if mpv_player.mpv_process: + mpv_player.stop_mpv() + mpv_player.run_mpv(anime_video_url) + + def download_anime_video(self, url: str, anime_title: tuple): + self.download_screen.new_download_task(anime_title) + show_notification("New Download", f"{anime_title[0]} episode: {anime_title[1]}") + progress_hook = self.download_screen.on_episode_download_progress + downloader.download_file(url, anime_title, progress_hook) + + +def run_app(): + FastAnime().run() diff --git a/setup.py b/setup.py index 3531ab5..7f3f07d 100644 --- a/setup.py +++ b/setup.py @@ -56,7 +56,7 @@ if __name__ == "__main__": python_requires=">=3.10", entry_points={ "gui_scripts": [ - "fastanime = fastanime.__main__:run_app", + "fastanime = fastanime:main", ], }, )