From dcc5aebf4ee31cc2911d040f6c692646d01976f9 Mon Sep 17 00:00:00 2001 From: rdbende Date: Mon, 18 Dec 2023 16:39:41 +0100 Subject: [PATCH 01/20] Huhh --- cozy/app_controller.py | 2 + cozy/model/settings.py | 17 +-- cozy/model/storage.py | 14 +-- cozy/ui/main_view.py | 6 +- cozy/ui/preferences_view.py | 95 ++------------ cozy/ui/widgets/storage_list_box_row.py | 100 --------------- cozy/ui/widgets/storages.py | 161 ++++++++++++++++++++++++ cozy/view_model/settings_view_model.py | 88 +------------ cozy/view_model/storages_view_model.py | 119 ++++++++++++++++++ data/ui/gresource.xml | 2 + data/ui/preferences.ui | 130 +------------------ data/ui/storage_locations.ui | 54 ++++++++ data/ui/storage_row.ui | 32 +++++ 13 files changed, 399 insertions(+), 421 deletions(-) delete mode 100644 cozy/ui/widgets/storage_list_box_row.py create mode 100644 cozy/ui/widgets/storages.py create mode 100644 cozy/view_model/storages_view_model.py create mode 100644 data/ui/storage_locations.ui create mode 100644 data/ui/storage_row.ui diff --git a/cozy/app_controller.py b/cozy/app_controller.py index ec1fbd53..003f53db 100644 --- a/cozy/app_controller.py +++ b/cozy/app_controller.py @@ -37,6 +37,7 @@ from cozy.view_model.search_view_model import SearchViewModel from cozy.view_model.settings_view_model import SettingsViewModel from cozy.view_model.sleep_timer_view_model import SleepTimerViewModel +from cozy.view_model.storages_view_model import StoragesViewModel class AppController(metaclass=Singleton): @@ -108,6 +109,7 @@ def configure_inject(self, binder): binder.bind_to_constructor(ToastNotifier, lambda: ToastNotifier()) binder.bind_to_constructor(AppViewModel, lambda: AppViewModel()) binder.bind_to_constructor(SettingsViewModel, lambda: SettingsViewModel()) + binder.bind_to_constructor(StoragesViewModel, lambda: StoragesViewModel()) def open_author(self, author: str): self.library_view_model.library_view_mode = LibraryViewMode.AUTHOR diff --git a/cozy/model/settings.py b/cozy/model/settings.py index 432415bb..c56dd913 100644 --- a/cozy/model/settings.py +++ b/cozy/model/settings.py @@ -16,7 +16,7 @@ class Settings: - _storages: List[Storage] = [] + _storages: list[Storage] = [] _db = cache = inject.attr(SqliteDatabase) def __init__(self): @@ -49,10 +49,7 @@ def last_played_book(self, new_value): @property def default_location(self): - return next(location - for location - in self.storage_locations - if location.default) + return next(location for location in self.storage_locations if location.default) @property def storage_locations(self): @@ -69,11 +66,9 @@ def external_storage_locations(self): return [storage for storage in self._storages if storage.external] def invalidate(self): - self._storages = [] + self._storages.clear() def _load_all_storage_locations(self): - self._storages = [] - for storage_db_obj in StorageModel.select(StorageModel.id): try: self._storages.append(Storage(self._db, storage_db_obj.id)) @@ -83,9 +78,5 @@ def _load_all_storage_locations(self): self._ensure_default_storage_present() def _ensure_default_storage_present(self): - default_storage_present = any(storage.default - for storage - in self._storages) - - if not default_storage_present and len(self._storages) > 0: + if self._storages and not any(storage.default for storage in self._storages): self._storages[0].default = True diff --git a/cozy/model/storage.py b/cozy/model/storage.py index cd5a8fc9..b09a3309 100644 --- a/cozy/model/storage.py +++ b/cozy/model/storage.py @@ -1,4 +1,4 @@ -import os +from pathlib import Path from peewee import SqliteDatabase @@ -17,8 +17,8 @@ def __init__(self, db: SqliteDatabase, db_id: int): self._get_db_object() @staticmethod - def new(db: SqliteDatabase): - db_obj = StorageModel.create(path="") + def new(db: SqliteDatabase, path: str): + db_obj = StorageModel.create(path=path) return Storage(db, db_obj.id) def _get_db_object(self): @@ -33,11 +33,11 @@ def path(self): return self._db_object.path @path.setter - def path(self, new_path: str): - if not os.path.isabs(new_path): + def path(self, path: str): + if not Path(path).is_absolute(): raise InvalidPath - self._db_object.path = new_path + self._db_object.path = path self._db_object.save(only=self._db_object.dirty_fields) @property @@ -68,4 +68,4 @@ def external(self, new_external: bool): self._db_object.save(only=self._db_object.dirty_fields) def delete(self): - self._db_object.delete_instance(recursive=True, delete_nullable=False) \ No newline at end of file + self._db_object.delete_instance(recursive=True, delete_nullable=False) diff --git a/cozy/ui/main_view.py b/cozy/ui/main_view.py index e0b2e20a..39801517 100644 --- a/cozy/ui/main_view.py +++ b/cozy/ui/main_view.py @@ -17,7 +17,7 @@ from cozy.media.importer import Importer, ScanStatus from cozy.media.player import Player from cozy.model.settings import Settings as SettingsModel -from cozy.view_model.settings_view_model import SettingsViewModel +from cozy.view_model.storages_view_model import StoragesViewModel from cozy.open_view import OpenView from cozy.ui.library_view import LibraryView from cozy.ui.preferences_view import PreferencesView @@ -38,7 +38,7 @@ class CozyUI(EventSender, metaclass=Singleton): _settings: SettingsModel = inject.attr(SettingsModel) _files: Files = inject.attr(Files) _player: Player = inject.attr(Player) - _settings_view_model: SettingsViewModel = inject.attr(SettingsViewModel) + _storages_view_model: StoragesViewModel = inject.attr(StoragesViewModel) def __init__(self, pkgdatadir, app, version): super().__init__() @@ -309,7 +309,7 @@ def _on_drag_data_received(self, widget, value, *_): return True def _set_audiobook_path(self, path): - self._settings_view_model.add_first_storage_location(path) + self._storages_view_model.add_first_storage_location(path) self.main_stack.props.visible_child_name = "import" self.scan(None, None) self.fs_monitor.init_offline_mode() diff --git a/cozy/ui/preferences_view.py b/cozy/ui/preferences_view.py index 709488a1..a00c1266 100644 --- a/cozy/ui/preferences_view.py +++ b/cozy/ui/preferences_view.py @@ -1,11 +1,11 @@ from gi.repository import Gtk from cozy.view_model.settings_view_model import SettingsViewModel -import gi from gi.repository import Adw, Gio +from typing import Callable from cozy.ext import inject from cozy.ui.widgets.error_reporting import ErrorReporting -from cozy.ui.widgets.storage_list_box_row import StorageListBoxRow +from cozy.ui.widgets.storages import StorageLocations @Gtk.Template.from_resource('/com/github/geigi/cozy/preferences.ui') @@ -17,6 +17,8 @@ class PreferencesView(Adw.PreferencesWindow): _glib_settings: Gio.Settings = inject.attr(Gio.Settings) _view_model: SettingsViewModel = inject.attr(SettingsViewModel) + storages_page: Adw.PreferencesPage = Gtk.Template.Child() + dark_mode_switch: Gtk.Switch = Gtk.Template.Child() swap_author_reader_switch: Gtk.Switch = Gtk.Template.Child() replay_switch: Gtk.Switch = Gtk.Template.Child() @@ -28,44 +30,25 @@ class PreferencesView(Adw.PreferencesWindow): forward_duration_adjustment: Gtk.Adjustment = Gtk.Template.Child() fadeout_duration_adjustment: Gtk.Adjustment = Gtk.Template.Child() - storage_list_box: Gtk.ListBox = Gtk.Template.Child() - add_storage_button: Gtk.Button = Gtk.Template.Child() - remove_storage_button: Gtk.Button = Gtk.Template.Child() - external_storage_toggle_button: Gtk.ToggleButton = Gtk.Template.Child() - default_storage_button: Gtk.ToggleButton = Gtk.Template.Child() - user_feedback_preference_group: Adw.PreferencesRow = Gtk.Template.Child() def __init__(self, **kwargs): - super().__init__(**kwargs) + super().__init__(transient_for=self.main_window.window, **kwargs) error_reporting = ErrorReporting() error_reporting.show_header(False) self.user_feedback_preference_group.add(error_reporting) + storage_locations = StorageLocations() + self.storages_page.add(storage_locations) + self._bind_settings() - self._bind_view_model() self.connect("close-request", self._hide_window) self.sleep_timer_fadeout_switch.connect("notify::active", self._on_sleep_fadeout_switch_changed) self.fadeout_duration_spin_button.set_sensitive(self.sleep_timer_fadeout_switch.props.active) - self.storage_list_box.connect("row-selected", self._on_storage_box_changed) - - self.add_storage_button.connect("clicked", self._on_add_storage_clicked) - self.remove_storage_button.connect("clicked", self._on_remove_storage_clicked) - self.external_button_handle_id = self.external_storage_toggle_button.connect("clicked", self._on_external_clicked) - self.default_storage_button.connect("clicked", self._on_default_storage_clicked) - - self.set_transient_for(self.main_window.window) - - self._init_storage_box() - - def _bind_view_model(self): - self._view_model.bind_to("storage_locations", self._init_storage_box) - self._view_model.bind_to("storage_attributes", self._refresh_storage_rows) - def _bind_settings(self): self._glib_settings.bind("dark-mode", self.dark_mode_switch, "active", Gio.SettingsBindFlags.DEFAULT) @@ -91,74 +74,16 @@ def _bind_settings(self): def _on_sleep_fadeout_switch_changed(self, widget, param): state = widget.get_property(param.name) self.fadeout_duration_spin_button.set_sensitive(state) - - def _init_storage_box(self): - self.storage_list_box.remove_all_children() - - for storage in self._view_model.storage_locations: - row = StorageListBoxRow(storage) - row.connect("location-changed", self._on_storage_location_changed) - self.storage_list_box.append(row) - - def _on_add_storage_clicked(self, _): - self._view_model.add_storage_location() - - def _on_remove_storage_clicked(self, _): - row = self.storage_list_box.get_selected_row() - self._view_model.remove_storage_location(row.model) - - def _on_default_storage_clicked(self, _): - row = self.storage_list_box.get_selected_row() - self._view_model.set_default_storage(row.model) - self._on_storage_box_changed(None, row) - - def _on_storage_box_changed(self, _, row): - row = self.storage_list_box.get_selected_row() - if row is None: - sensitive = False - default_sensitive = False - remove_sensitive = False - else: - sensitive = True - remove_sensitive = True - if row.model.default or not row.model.path: - default_sensitive = remove_sensitive = False - else: - default_sensitive = True - - if not row.model.path: - remove_sensitive = True - - self.external_storage_toggle_button.handler_block(self.external_button_handle_id) - self.external_storage_toggle_button.set_active(row.model.external) - self.external_storage_toggle_button.handler_unblock(self.external_button_handle_id) - - self.remove_storage_button.set_sensitive(remove_sensitive) - self.external_storage_toggle_button.set_sensitive(sensitive) - self.default_storage_button.set_sensitive(default_sensitive) - - def _on_external_clicked(self, _): - external = self.external_storage_toggle_button.get_active() - row = self.storage_list_box.get_selected_row() - self._view_model.set_storage_external(row.model, external) - - def _on_storage_location_changed(self, widget, new_location): - self._view_model.change_storage_location(widget.model, new_location) - - def _refresh_storage_rows(self): - self._init_storage_box() - - self._on_storage_box_changed(None, self.storage_list_box.get_selected_row()) def _on_lock_ui_changed(self): sensitive = not self._view_model.lock_ui - self.storage_list_box.set_sensitive(sensitive) + self.storage_locations_list.set_sensitive(sensitive) self.add_storage_button.set_sensitive(sensitive) self.remove_storage_button.set_sensitive(sensitive) self.external_storage_toggle_button.set_sensitive(sensitive) self.default_storage_button.set_sensitive(sensitive) - self._on_storage_box_changed(None, self.storage_list_box.get_selected_row()) + self._on_storage_box_changed(None, self.storage_locations_list.get_selected_row()) def _hide_window(self, *_): self.hide() diff --git a/cozy/ui/widgets/storage_list_box_row.py b/cozy/ui/widgets/storage_list_box_row.py deleted file mode 100644 index fcf97bd9..00000000 --- a/cozy/ui/widgets/storage_list_box_row.py +++ /dev/null @@ -1,100 +0,0 @@ -import logging -from threading import Thread - -from cozy.control.filesystem_monitor import FilesystemMonitor -from cozy.model.storage import Storage -from cozy.ext import inject -from cozy.model.library import Library -from cozy.model.settings import Settings -from gi.repository import Gtk, GObject, Gio, GLib - -log = logging.getLogger("settings") - - -class StorageListBoxRow(Gtk.ListBoxRow): - """ - This class represents a listboxitem for a storage location. - """ - - main_window = inject.attr("MainWindow") - - def __init__(self, model: Storage): - self._model = model - - super(Gtk.ListBoxRow, self).__init__() - box = Gtk.Box() - box.set_orientation(Gtk.Orientation.HORIZONTAL) - box.set_spacing(3) - box.set_halign(Gtk.Align.FILL) - box.set_valign(Gtk.Align.CENTER) - box.set_margin_start(6) - box.set_margin_end(6) - box.set_margin_top(12) - box.set_margin_bottom(12) - - self.default_image = Gtk.Image() - self.default_image.set_from_icon_name("emblem-default-symbolic") - self.default_image.set_margin_end(5) - - self.type_image = Gtk.Image() - self._set_drive_icon() - self.location_chooser = Gtk.Button() - self.location_label = Gtk.Label() - self.location_chooser.set_child(self.location_label) - self.location_chooser.set_margin_end(6) - self.location_chooser.connect("clicked", self._on_location_chooser_clicked) - - self.location_label.set_text(model.path) - - box.append(self.type_image) - box.append(self.location_chooser) - box.append(self.default_image) - self.set_child(box) - self._set_default_icon() - - @property - def model(self) -> Storage: - return self._model - - def refresh(self): - self._set_drive_icon() - self._set_default_icon() - - def __on_folder_changed(self, new_path): - self.emit("location-changed", new_path) - - def _set_drive_icon(self): - if self._model.external: - icon_name = "network-server-symbolic" - self.type_image.set_tooltip_text(_("External drive")) - else: - icon_name = "drive-harddisk-symbolic" - self.type_image.set_tooltip_text(_("Internal drive")) - - self.type_image.set_from_icon_name(icon_name) - self.type_image.set_margin_end(5) - - def _set_default_icon(self): - self.default_image.set_visible(self._model.default) - - def _on_location_chooser_clicked(self, *junk): - location_chooser = Gtk.FileDialog(title=_("Set Audiobooks Directory")) - - if self._model.path != "": - folder = Gio.File.new_for_path(self._model.path) - location_chooser.set_initial_folder(folder) - - location_chooser.select_folder(self.main_window.window, None, self._location_chooser_open_callback) - - def _location_chooser_open_callback(self, dialog, result): - try: - file = dialog.select_folder_finish(result) - except GLib.GError: - pass - else: - if file is not None: - self.__on_folder_changed(file.get_path()) - -GObject.signal_new('location-changed', StorageListBoxRow, GObject.SIGNAL_RUN_LAST, GObject.TYPE_PYOBJECT, - (GObject.TYPE_PYOBJECT,)) - diff --git a/cozy/ui/widgets/storages.py b/cozy/ui/widgets/storages.py new file mode 100644 index 00000000..0f6e59c0 --- /dev/null +++ b/cozy/ui/widgets/storages.py @@ -0,0 +1,161 @@ +import logging +from threading import Thread +from typing import Callable + +from cozy.control.filesystem_monitor import FilesystemMonitor +from cozy.model.storage import Storage +from cozy.ext import inject +from cozy.model.library import Library +from cozy.model.settings import Settings +from gi.repository import Gtk, GObject, Gio, GLib, Adw +from cozy.view_model.storages_view_model import StoragesViewModel + +log = logging.getLogger("settings") + + +def ask_storage_location(callback: Callable[[str], None], *junk): + location_chooser = Gtk.FileDialog(title=_("Set Audiobooks Directory")) + + # if path: + # folder = Gio.File.new_for_path(path) + # location_chooser.set_initial_folder(folder) + + def finish_callback(dialog, result): + try: + file = dialog.select_folder_finish(result) + except GLib.GError: + pass + else: + if file is not None: + callback(file.get_path()) + + location_chooser.select_folder(inject.instance("MainWindow").window, None, finish_callback) + + +@Gtk.Template.from_resource("/com/github/geigi/cozy/storage_row.ui") +class StorageRow(Adw.ActionRow): + __gtype_name__ = "StorageRow" + + icon: Gtk.Image = Gtk.Template.Child() + default_icon: Gtk.Image = Gtk.Template.Child() + menu_button: Gtk.MenuButton = Gtk.Template.Child() + + def __init__(self, model: Storage, menu_model: Gio.Menu) -> None: + self._model = model + + super().__init__(title=model.path) + self.connect("activated", self.ask_for_new_location) + + self.menu_button.set_menu_model(menu_model) + self.menu_button.connect("notify::active", self._on_menu_opened) + + self._set_default_icon() + self._set_drive_icon() + + @property + def model(self) -> Storage: + return self._model + + def ask_for_new_location(self, *_): + ask_storage_location(self._on_folder_changed) + + def _on_folder_changed(self, new_path): + self.emit("location-changed", new_path) + + def _on_menu_opened(self, *_): + self.emit("menu-opened") + + def _set_drive_icon(self): + if self._model.external: + self.icon.set_from_icon_name("network-server-symbolic") + self.icon.set_tooltip_text(_("External drive")) + else: + self.icon.set_from_icon_name("folder-open-symbolic") + self.icon.set_tooltip_text(_("Internal drive")) + + def _set_default_icon(self): + self.default_icon.set_visible(self._model.default) + + +GObject.signal_new('location-changed', StorageRow, GObject.SIGNAL_RUN_LAST, GObject.TYPE_PYOBJECT, + (GObject.TYPE_PYOBJECT,)) +GObject.signal_new('menu-opened', StorageRow, GObject.SIGNAL_RUN_LAST, GObject.TYPE_PYOBJECT, ()) + + +@Gtk.Template.from_resource("/com/github/geigi/cozy/storage_locations.ui") +class StorageLocations(Adw.PreferencesGroup): + __gtype_name__ = "StorageLocations" + + _view_model: StoragesViewModel = inject.attr(StoragesViewModel) + + storage_locations_list: Gtk.ListBox = Gtk.Template.Child() + new_storage_button: Adw.ActionRow = Gtk.Template.Child() + storage_menu: Gio.Menu = Gtk.Template.Child() + + def __init__(self) -> None: + super().__init__() + + self.new_storage_button.connect("activated", self._on_new_storage_clicked) + + self._view_model.bind_to("storage_locations", self._reload_storage_list) + self._view_model.bind_to("storage_attributes", self._reload_storage_list) + + self._create_actions() + + self._reload_storage_list() + + def _create_actions(self): + self.action_group = Gio.SimpleActionGroup.new() + self.insert_action_group("storage", self.action_group) + + self.set_external_action = Gio.SimpleAction.new_stateful( + "mark-external", + None, + GLib.Variant.new_boolean(False), + ) + self.set_external_signal_handler = self.set_external_action.connect( + "notify::state", self._mark_storage_location_external + ) + self.action_group.add_action(self.set_external_action) + + self.remove_action = Gio.SimpleAction.new("remove", None) + self.remove_action.connect("activate", self._remove_storage_location) + self.action_group.add_action(self.remove_action) + + self.make_default_action = Gio.SimpleAction.new("make-default", None) + self.make_default_action.connect("activate", self._set_default_storage_location) + self.action_group.add_action(self.make_default_action) + + def _reload_storage_list(self): + self.storage_locations_list.remove_all() + + for storage in self._view_model.storages: + row = StorageRow(storage, menu_model=self.storage_menu) + row.connect("location-changed", self._on_storage_location_changed) + row.connect("menu-opened", self._on_storage_menu_opened) + self.storage_locations_list.append(row) + + def _remove_storage_location(self, *_): + self._view_model.remove(self._view_model.selected_storage) + + def _set_default_storage_location(self, *_): + self._view_model.set_default(self._view_model.selected_storage) + + def _mark_storage_location_external(self, action, value): + value = action.get_property(value.name) + self._view_model.set_external(self._view_model.selected_storage, value) + + def _on_new_storage_clicked(self, *junk): + ask_storage_location(self._view_model.add_storage_location) + + def _on_storage_location_changed(self, widget, new_location): + self._view_model.change_storage_location(widget.model, new_location) + + def _on_storage_menu_opened(self, widget: StorageRow): + with self.set_external_action.handler_block(self.set_external_signal_handler): + self.set_external_action.props.state = GLib.Variant.new_boolean(widget.model.external) + + self.remove_action.props.enabled = not widget.model.default and len(self._view_model.storages) > 1 + self.make_default_action.props.enabled = widget.model is not self._view_model.default + self._view_model.selected_storage = widget.model + diff --git a/cozy/view_model/settings_view_model.py b/cozy/view_model/settings_view_model.py index 28251f8a..f5333529 100644 --- a/cozy/view_model/settings_view_model.py +++ b/cozy/view_model/settings_view_model.py @@ -1,31 +1,21 @@ import logging -from threading import Thread -from typing import List -from peewee import SqliteDatabase +from gi.repository import Adw, Gtk + from cozy.application_settings import ApplicationSettings from cozy.architecture.event_sender import EventSender from cozy.architecture.observable import Observable -from cozy.control.filesystem_monitor import FilesystemMonitor -from cozy.model.library import Library -from cozy.model.storage import Storage from cozy.ext import inject from cozy.media.importer import Importer from cozy.model.settings import Settings -from cozy.report import reporter -from gi.repository import Gtk, Adw - - log = logging.getLogger("settings_view_model") + class SettingsViewModel(Observable, EventSender): - _library: Library = inject.attr(Library) _importer: Importer = inject.attr(Importer) _model: Settings = inject.attr(Settings) _app_settings: ApplicationSettings = inject.attr(ApplicationSettings) - _db = inject.attr(SqliteDatabase) - _fs_monitor = inject.attr(FilesystemMonitor) def __init__(self): super().__init__() @@ -33,7 +23,6 @@ def __init__(self): self._lock_ui: bool = False - self._gtk_settings = Gtk.Settings.get_default() self.style_manager = Adw.StyleManager.get_default() self._set_dark_mode() @@ -42,84 +31,15 @@ def __init__(self): if self._model.first_start: self._importer.scan() - @property - def storage_locations(self) -> List[Storage]: - return self._model.storage_locations - @property def lock_ui(self) -> bool: return self._lock_ui - + @lock_ui.setter def lock_ui(self, new_value: bool): self._lock_ui = new_value self._notify("lock_ui") - def add_storage_location(self): - Storage.new(self._db) - self._model.invalidate() - self._notify("storage_locations") - - def remove_storage_location(self, model: Storage): - if model.default: - log.error("deleting the default storage location {} is not possible".format(model.path)) - reporter.error("settings_view_model", "deleting the default storage location is not possible") - return - - model.delete() - self._model.invalidate() - self._notify("storage_locations") - self.emit_event("storage-removed", model) - - def set_storage_external(self, model: Storage, external: bool): - model.external = external - - if external: - self.emit_event("external-storage-added", model) - else: - self.emit_event("external-storage-removed", model) - - self._notify("storage_attributes") - - def set_default_storage(self, model: Storage): - if model.default: - return - - for storage in self._model.storage_locations: - storage.default = False - - model.default = True - - self._notify("storage_attributes") - - def change_storage_location(self, model: Storage, new_path: str): - old_path = model.path - model.path = new_path - model.external = self._fs_monitor.is_external(new_path) - - if old_path == "": - self.emit_event("storage-added", model) - log.info("New audiobook location added. Starting import scan.") - thread = Thread(target=self._importer.scan, name="ImportThread") - thread.start() - else: - self.emit_event("storage-changed", model) - log.info("Audio book location changed, rebasing the location in Cozy.") - thread = Thread(target=self._library.rebase_path, args=(old_path, new_path), name="RebaseStorageLocationThread") - thread.start() - - self._notify("storage_attributes") - - def add_first_storage_location(self, path: str): - storage = self._model.storage_locations[0] - - storage.path = path - storage.default = True - storage.external = self._fs_monitor.is_external(path) - - self._model.invalidate() - self._notify("storage_locations") - def _set_dark_mode(self): if self._app_settings.dark_mode: self.style_manager.set_color_scheme(Adw.ColorScheme.PREFER_DARK) diff --git a/cozy/view_model/storages_view_model.py b/cozy/view_model/storages_view_model.py new file mode 100644 index 00000000..34229c33 --- /dev/null +++ b/cozy/view_model/storages_view_model.py @@ -0,0 +1,119 @@ +import logging +from threading import Thread + +from peewee import SqliteDatabase +from cozy.application_settings import ApplicationSettings +from cozy.architecture.event_sender import EventSender +from cozy.architecture.observable import Observable +from cozy.control.filesystem_monitor import FilesystemMonitor +from cozy.model.library import Library +from cozy.model.storage import Storage +from cozy.ext import inject +from cozy.media.importer import Importer +from cozy.model.settings import Settings +from cozy.report import reporter +from gi.repository import Gtk, Adw + + + +log = logging.getLogger("storages_view_model") + +class StoragesViewModel(Observable, EventSender): + _library: Library = inject.attr(Library) + _importer: Importer = inject.attr(Importer) + _model: Settings = inject.attr(Settings) + _app_settings: ApplicationSettings = inject.attr(ApplicationSettings) + _db = inject.attr(SqliteDatabase) + _fs_monitor = inject.attr(FilesystemMonitor) + + def __init__(self): + super().__init__() + super(Observable, self).__init__() + + self._selected_storage = None + + def _scan_new_storage(self, model: Storage): + self.emit_event("storage-added", model) + log.info("New audiobook location added. Starting import scan.") + thread = Thread(target=self._importer.scan, name="ImportThread") + thread.start() + + def _rebase_storage_location(self, model: Storage, old_path: str): + self.emit_event("storage-changed", model) + log.info("Audio book location changed, rebasing the location in Cozy.") + thread = Thread(target=self._library.rebase_path, args=(old_path, model.path), name="RebaseStorageLocationThread") + thread.start() + + def add_storage_location(self, path: str) -> None: + model = Storage.new(self._db, path) + + self._model.invalidate() + self._scan_new_storage(model) + self._notify("storage_locations") + + def add_first_storage_location(self, path: str): + storage = self.storages[0] + + storage.path = path + storage.default = True + storage.external = self._fs_monitor.is_external(path) + + self._model.invalidate() + self._notify("storage_locations") + + def change_storage_location(self, model: Storage, new_path: str) -> None: + old_path = model.path + model.path = new_path + model.external = self._fs_monitor.is_external(new_path) + + self._rebase_storage_location(model, old_path) + self._notify("storage_attributes") + + @property + def storages(self) -> list[Storage]: + return self._model.storage_locations + + @property + def default(self) -> Storage | None: + for item in self.storages: + if item.default: + return item + + @property + def selected_storage(self) -> Storage | None: + return self._selected_storage + + @selected_storage.setter + def selected_storage(self, value) -> None: + self._selected_storage = value + + def remove(self, model: Storage) -> None: + if model.default: + return + + model.delete() + self._model.invalidate() + self.emit_event("storage-removed", model) + + self._notify("storage_locations") + + def set_default(self, model: Storage): + if model.default: + return + + for storage in self.storages: + storage.default = False + + model.default = True + + self._notify("storage_attributes") + + def set_external(self, model: Storage, external: bool): + model.external = external + + if external: + self.emit_event("external-storage-added", model) + else: + self.emit_event("external-storage-removed", model) + + self._notify("storage_attributes") diff --git a/data/ui/gresource.xml b/data/ui/gresource.xml index 05828d20..fbb091fe 100644 --- a/data/ui/gresource.xml +++ b/data/ui/gresource.xml @@ -16,6 +16,8 @@ progress_popover.ui search_popover.ui seek_bar.ui + storage_locations.ui + storage_row.ui timer_popover.ui welcome.ui whats_new.ui diff --git a/data/ui/preferences.ui b/data/ui/preferences.ui index 20045d83..10235bbc 100644 --- a/data/ui/preferences.ui +++ b/data/ui/preferences.ui @@ -97,7 +97,7 @@ - + harddisk-symbolic Storage @@ -111,134 +111,6 @@ - - - Storage locations - - - Storage locations - - - 13 - 13 - 13 - 13 - true - vertical - - - 250 - - - true - never - - - - - 1 - - - - - - - - - - - - - - 24 - - - - 24 - 24 - Add location - - - list-add-symbolic - - - - - - - - 24 - 24 - false - Remove location - - - list-remove-symbolic - - - - - - - - vertical - True - - - - - - External drive - 24 - 24 - false - Toggle this storage location to be internal/external. - - - network-server-symbolic - - - - - - - - Set as default - 24 - 24 - false - Set as default storage location for new audiobooks - - - checkmark-symbolic - - - - - - - - - - - - - - diff --git a/data/ui/storage_locations.ui b/data/ui/storage_locations.ui new file mode 100644 index 00000000..a4922c55 --- /dev/null +++ b/data/ui/storage_locations.ui @@ -0,0 +1,54 @@ + + + + + +
+ + External drive + storage.mark-external + +
+
+ + Set as default + storage.make-default + + + Remove + storage.remove + +
+
+
+ diff --git a/data/ui/storage_row.ui b/data/ui/storage_row.ui new file mode 100644 index 00000000..0669bcf9 --- /dev/null +++ b/data/ui/storage_row.ui @@ -0,0 +1,32 @@ + + + + + From fcc0fe88f80dcff087e43daa90c1c91d0e9837be Mon Sep 17 00:00:00 2001 From: rdbende Date: Wed, 20 Dec 2023 18:44:11 +0100 Subject: [PATCH 02/20] Did I delete the ("working", True) event emission? --- cozy/ui/preferences_view.py | 31 ++++++++++--------------------- 1 file changed, 10 insertions(+), 21 deletions(-) diff --git a/cozy/ui/preferences_view.py b/cozy/ui/preferences_view.py index a00c1266..d5e7f7b5 100644 --- a/cozy/ui/preferences_view.py +++ b/cozy/ui/preferences_view.py @@ -18,20 +18,19 @@ class PreferencesView(Adw.PreferencesWindow): _view_model: SettingsViewModel = inject.attr(SettingsViewModel) storages_page: Adw.PreferencesPage = Gtk.Template.Child() + user_feedback_preference_group: Adw.PreferencesGroup = Gtk.Template.Child() - dark_mode_switch: Gtk.Switch = Gtk.Template.Child() - swap_author_reader_switch: Gtk.Switch = Gtk.Template.Child() - replay_switch: Gtk.Switch = Gtk.Template.Child() + dark_mode_switch: Adw.SwitchRow = Gtk.Template.Child() + swap_author_reader_switch: Adw.SwitchRow = Gtk.Template.Child() + replay_switch: Adw.SwitchRow = Gtk.Template.Child() sleep_timer_fadeout_switch: Adw.SwitchRow = Gtk.Template.Child() fadeout_duration_spin_button: Adw.SpinRow = Gtk.Template.Child() - artwork_prefer_external_switch: Gtk.Switch = Gtk.Template.Child() + artwork_prefer_external_switch: Adw.SwitchRow = Gtk.Template.Child() rewind_duration_adjustment: Gtk.Adjustment = Gtk.Template.Child() forward_duration_adjustment: Gtk.Adjustment = Gtk.Template.Child() fadeout_duration_adjustment: Gtk.Adjustment = Gtk.Template.Child() - user_feedback_preference_group: Adw.PreferencesRow = Gtk.Template.Child() - def __init__(self, **kwargs): super().__init__(transient_for=self.main_window.window, **kwargs) @@ -39,15 +38,14 @@ def __init__(self, **kwargs): error_reporting.show_header(False) self.user_feedback_preference_group.add(error_reporting) - storage_locations = StorageLocations() - self.storages_page.add(storage_locations) + self.storage_locations_view = StorageLocations() + self.storages_page.add(self.storage_locations_view) self._bind_settings() - self.connect("close-request", self._hide_window) + self._view_model.bind_to("lock_ui", self._on_lock_ui_changed) - self.sleep_timer_fadeout_switch.connect("notify::active", self._on_sleep_fadeout_switch_changed) - self.fadeout_duration_spin_button.set_sensitive(self.sleep_timer_fadeout_switch.props.active) + self.connect("close-request", self._hide_window) def _bind_settings(self): self._glib_settings.bind("dark-mode", self.dark_mode_switch, "active", @@ -71,19 +69,10 @@ def _bind_settings(self): self._glib_settings.bind("prefer-external-cover", self.artwork_prefer_external_switch, "active", Gio.SettingsBindFlags.DEFAULT) - def _on_sleep_fadeout_switch_changed(self, widget, param): - state = widget.get_property(param.name) - self.fadeout_duration_spin_button.set_sensitive(state) - def _on_lock_ui_changed(self): sensitive = not self._view_model.lock_ui - self.storage_locations_list.set_sensitive(sensitive) - self.add_storage_button.set_sensitive(sensitive) - self.remove_storage_button.set_sensitive(sensitive) - self.external_storage_toggle_button.set_sensitive(sensitive) - self.default_storage_button.set_sensitive(sensitive) - self._on_storage_box_changed(None, self.storage_locations_list.get_selected_row()) + self.storage_locations_view.set_sensitive(sensitive) def _hide_window(self, *_): self.hide() From 9693858a960b85b7c2b3b5b7ff59de73ea33030e Mon Sep 17 00:00:00 2001 From: rdbende Date: Wed, 20 Dec 2023 19:00:48 +0100 Subject: [PATCH 03/20] Use AdwExpanderRow --- cozy/ui/preferences_view.py | 3 +-- data/ui/preferences.ui | 22 ++++++++++++---------- 2 files changed, 13 insertions(+), 12 deletions(-) diff --git a/cozy/ui/preferences_view.py b/cozy/ui/preferences_view.py index d5e7f7b5..bf587d71 100644 --- a/cozy/ui/preferences_view.py +++ b/cozy/ui/preferences_view.py @@ -24,7 +24,6 @@ class PreferencesView(Adw.PreferencesWindow): swap_author_reader_switch: Adw.SwitchRow = Gtk.Template.Child() replay_switch: Adw.SwitchRow = Gtk.Template.Child() sleep_timer_fadeout_switch: Adw.SwitchRow = Gtk.Template.Child() - fadeout_duration_spin_button: Adw.SpinRow = Gtk.Template.Child() artwork_prefer_external_switch: Adw.SwitchRow = Gtk.Template.Child() rewind_duration_adjustment: Gtk.Adjustment = Gtk.Template.Child() @@ -60,7 +59,7 @@ def _bind_settings(self): self._glib_settings.bind("forward-duration", self.forward_duration_adjustment, "value", Gio.SettingsBindFlags.DEFAULT) - self._glib_settings.bind("sleep-timer-fadeout", self.sleep_timer_fadeout_switch, "active", + self._glib_settings.bind("sleep-timer-fadeout", self.sleep_timer_fadeout_switch, "enable-expansion", Gio.SettingsBindFlags.DEFAULT) self._glib_settings.bind("sleep-timer-fadeout-duration", self.fadeout_duration_adjustment, diff --git a/data/ui/preferences.ui b/data/ui/preferences.ui index 10235bbc..a38b3219 100644 --- a/data/ui/preferences.ui +++ b/data/ui/preferences.ui @@ -79,17 +79,19 @@ Sleep Timer - + Fadeout - - - - - Fadeout duration - true - fadeout_duration_adjustment - true - true + true + true + + + Fadeout duration + true + fadeout_duration_adjustment + true + true + + From 5e7a13bd0bacebc3c574f42bb2e5e70a3c8c542c Mon Sep 17 00:00:00 2001 From: rdbende Date: Thu, 21 Dec 2023 15:52:45 +0100 Subject: [PATCH 04/20] Wrong order of functions. Yikes! --- cozy/view_model/storages_view_model.py | 15 +++++++++------ 1 file changed, 9 insertions(+), 6 deletions(-) diff --git a/cozy/view_model/storages_view_model.py b/cozy/view_model/storages_view_model.py index 34229c33..22aeee3a 100644 --- a/cozy/view_model/storages_view_model.py +++ b/cozy/view_model/storages_view_model.py @@ -35,25 +35,28 @@ def __init__(self): def _scan_new_storage(self, model: Storage): self.emit_event("storage-added", model) log.info("New audiobook location added. Starting import scan.") - thread = Thread(target=self._importer.scan, name="ImportThread") - thread.start() + Thread(target=self._importer.scan, name="ImportThread").start() def _rebase_storage_location(self, model: Storage, old_path: str): self.emit_event("storage-changed", model) log.info("Audio book location changed, rebasing the location in Cozy.") - thread = Thread(target=self._library.rebase_path, args=(old_path, model.path), name="RebaseStorageLocationThread") - thread.start() + Thread( + target=self._library.rebase_path, + args=(old_path, model.path), + name="RebaseStorageLocationThread" + ).start() def add_storage_location(self, path: str) -> None: model = Storage.new(self._db, path) + model.external = self._fs_monitor.is_external(path) self._model.invalidate() - self._scan_new_storage(model) self._notify("storage_locations") + self._scan_new_storage(model) + def add_first_storage_location(self, path: str): storage = self.storages[0] - storage.path = path storage.default = True storage.external = self._fs_monitor.is_external(path) From 242bd2401f33d7c9e3b646a2157d482d13afef5b Mon Sep 17 00:00:00 2001 From: rdbende Date: Thu, 21 Dec 2023 16:03:22 +0100 Subject: [PATCH 05/20] Add missing type annotations and format the code --- cozy/ui/preferences_view.py | 89 +++++++++++++++++--------- cozy/ui/widgets/storages.py | 69 ++++++++++++-------- cozy/view_model/storages_view_model.py | 24 ++++--- 3 files changed, 112 insertions(+), 70 deletions(-) diff --git a/cozy/ui/preferences_view.py b/cozy/ui/preferences_view.py index bf587d71..ce871893 100644 --- a/cozy/ui/preferences_view.py +++ b/cozy/ui/preferences_view.py @@ -1,14 +1,14 @@ -from gi.repository import Gtk -from cozy.view_model.settings_view_model import SettingsViewModel -from gi.repository import Adw, Gio -from typing import Callable +from typing import Any + +from gi.repository import Adw, Gio, Gtk from cozy.ext import inject from cozy.ui.widgets.error_reporting import ErrorReporting from cozy.ui.widgets.storages import StorageLocations +from cozy.view_model.settings_view_model import SettingsViewModel -@Gtk.Template.from_resource('/com/github/geigi/cozy/preferences.ui') +@Gtk.Template.from_resource("/com/github/geigi/cozy/preferences.ui") class PreferencesView(Adw.PreferencesWindow): __gtype_name__ = "PreferencesWindow" @@ -30,7 +30,7 @@ class PreferencesView(Adw.PreferencesWindow): forward_duration_adjustment: Gtk.Adjustment = Gtk.Template.Child() fadeout_duration_adjustment: Gtk.Adjustment = Gtk.Template.Child() - def __init__(self, **kwargs): + def __init__(self, **kwargs: Any) -> None: super().__init__(transient_for=self.main_window.window, **kwargs) error_reporting = ErrorReporting() @@ -46,33 +46,62 @@ def __init__(self, **kwargs): self.connect("close-request", self._hide_window) - def _bind_settings(self): - self._glib_settings.bind("dark-mode", self.dark_mode_switch, "active", - Gio.SettingsBindFlags.DEFAULT) - - self._glib_settings.bind("swap-author-reader", self.swap_author_reader_switch, "active", - Gio.SettingsBindFlags.DEFAULT) - - self._glib_settings.bind("replay", self.replay_switch, "active", Gio.SettingsBindFlags.DEFAULT) - self._glib_settings.bind("rewind-duration", self.rewind_duration_adjustment, "value", - Gio.SettingsBindFlags.DEFAULT) - self._glib_settings.bind("forward-duration", self.forward_duration_adjustment, "value", - Gio.SettingsBindFlags.DEFAULT) - - self._glib_settings.bind("sleep-timer-fadeout", self.sleep_timer_fadeout_switch, "enable-expansion", - Gio.SettingsBindFlags.DEFAULT) - - self._glib_settings.bind("sleep-timer-fadeout-duration", self.fadeout_duration_adjustment, - "value", Gio.SettingsBindFlags.DEFAULT) - - self._glib_settings.bind("prefer-external-cover", self.artwork_prefer_external_switch, "active", - Gio.SettingsBindFlags.DEFAULT) - - def _on_lock_ui_changed(self): + def _bind_settings(self) -> None: + self._glib_settings.bind( + "dark-mode", self.dark_mode_switch, "active", Gio.SettingsBindFlags.DEFAULT + ) + + self._glib_settings.bind( + "swap-author-reader", + self.swap_author_reader_switch, + "active", + Gio.SettingsBindFlags.DEFAULT, + ) + + self._glib_settings.bind( + "replay", self.replay_switch, "active", Gio.SettingsBindFlags.DEFAULT + ) + self._glib_settings.bind( + "rewind-duration", + self.rewind_duration_adjustment, + "value", + Gio.SettingsBindFlags.DEFAULT, + ) + self._glib_settings.bind( + "forward-duration", + self.forward_duration_adjustment, + "value", + Gio.SettingsBindFlags.DEFAULT, + ) + + self._glib_settings.bind( + "sleep-timer-fadeout", + self.sleep_timer_fadeout_switch, + "enable-expansion", + Gio.SettingsBindFlags.DEFAULT, + ) + + self._glib_settings.bind( + "sleep-timer-fadeout-duration", + self.fadeout_duration_adjustment, + "value", + Gio.SettingsBindFlags.DEFAULT, + ) + + self._glib_settings.bind( + "prefer-external-cover", + self.artwork_prefer_external_switch, + "active", + Gio.SettingsBindFlags.DEFAULT, + ) + + self.storage_locations_view.set_sensitive(False) + + def _on_lock_ui_changed(self) -> None: sensitive = not self._view_model.lock_ui self.storage_locations_view.set_sensitive(sensitive) - + def _hide_window(self, *_): self.hide() return True diff --git a/cozy/ui/widgets/storages.py b/cozy/ui/widgets/storages.py index 0f6e59c0..55d5280c 100644 --- a/cozy/ui/widgets/storages.py +++ b/cozy/ui/widgets/storages.py @@ -1,13 +1,10 @@ import logging -from threading import Thread from typing import Callable -from cozy.control.filesystem_monitor import FilesystemMonitor -from cozy.model.storage import Storage +from gi.repository import Adw, Gio, GLib, GObject, Gtk + from cozy.ext import inject -from cozy.model.library import Library -from cozy.model.settings import Settings -from gi.repository import Gtk, GObject, Gio, GLib, Adw +from cozy.model.storage import Storage from cozy.view_model.storages_view_model import StoragesViewModel log = logging.getLogger("settings") @@ -29,7 +26,9 @@ def finish_callback(dialog, result): if file is not None: callback(file.get_path()) - location_chooser.select_folder(inject.instance("MainWindow").window, None, finish_callback) + location_chooser.select_folder( + inject.instance("MainWindow").window, None, finish_callback + ) @Gtk.Template.from_resource("/com/github/geigi/cozy/storage_row.ui") @@ -56,16 +55,16 @@ def __init__(self, model: Storage, menu_model: Gio.Menu) -> None: def model(self) -> Storage: return self._model - def ask_for_new_location(self, *_): + def ask_for_new_location(self, *_) -> None: ask_storage_location(self._on_folder_changed) - def _on_folder_changed(self, new_path): + def _on_folder_changed(self, new_path: str) -> None: self.emit("location-changed", new_path) - def _on_menu_opened(self, *_): + def _on_menu_opened(self, *_) -> None: self.emit("menu-opened") - def _set_drive_icon(self): + def _set_drive_icon(self) -> None: if self._model.external: self.icon.set_from_icon_name("network-server-symbolic") self.icon.set_tooltip_text(_("External drive")) @@ -73,13 +72,20 @@ def _set_drive_icon(self): self.icon.set_from_icon_name("folder-open-symbolic") self.icon.set_tooltip_text(_("Internal drive")) - def _set_default_icon(self): + def _set_default_icon(self) -> None: self.default_icon.set_visible(self._model.default) -GObject.signal_new('location-changed', StorageRow, GObject.SIGNAL_RUN_LAST, GObject.TYPE_PYOBJECT, - (GObject.TYPE_PYOBJECT,)) -GObject.signal_new('menu-opened', StorageRow, GObject.SIGNAL_RUN_LAST, GObject.TYPE_PYOBJECT, ()) +GObject.signal_new( + "location-changed", + StorageRow, + GObject.SIGNAL_RUN_LAST, + GObject.TYPE_PYOBJECT, + (GObject.TYPE_PYOBJECT,), +) +GObject.signal_new( + "menu-opened", StorageRow, GObject.SIGNAL_RUN_LAST, GObject.TYPE_PYOBJECT, () +) @Gtk.Template.from_resource("/com/github/geigi/cozy/storage_locations.ui") @@ -104,7 +110,7 @@ def __init__(self) -> None: self._reload_storage_list() - def _create_actions(self): + def _create_actions(self) -> None: self.action_group = Gio.SimpleActionGroup.new() self.insert_action_group("storage", self.action_group) @@ -126,7 +132,7 @@ def _create_actions(self): self.make_default_action.connect("activate", self._set_default_storage_location) self.action_group.add_action(self.make_default_action) - def _reload_storage_list(self): + def _reload_storage_list(self) -> None: self.storage_locations_list.remove_all() for storage in self._view_model.storages: @@ -135,27 +141,36 @@ def _reload_storage_list(self): row.connect("menu-opened", self._on_storage_menu_opened) self.storage_locations_list.append(row) - def _remove_storage_location(self, *_): + def _remove_storage_location(self, *_) -> None: self._view_model.remove(self._view_model.selected_storage) - def _set_default_storage_location(self, *_): + def _set_default_storage_location(self, *_) -> None: self._view_model.set_default(self._view_model.selected_storage) - def _mark_storage_location_external(self, action, value): + def _mark_storage_location_external( + self, action: Gio.SimpleAction, value: GObject.ParamSpec + ) -> None: value = action.get_property(value.name) self._view_model.set_external(self._view_model.selected_storage, value) - def _on_new_storage_clicked(self, *junk): + def _on_new_storage_clicked(self, *junk) -> None: ask_storage_location(self._view_model.add_storage_location) - def _on_storage_location_changed(self, widget, new_location): + def _on_storage_location_changed( + self, widget: StorageRow, new_location: str + ) -> None: self._view_model.change_storage_location(widget.model, new_location) - def _on_storage_menu_opened(self, widget: StorageRow): + def _on_storage_menu_opened(self, widget: StorageRow) -> None: with self.set_external_action.handler_block(self.set_external_signal_handler): - self.set_external_action.props.state = GLib.Variant.new_boolean(widget.model.external) + self.set_external_action.props.state = GLib.Variant.new_boolean( + widget.model.external + ) - self.remove_action.props.enabled = not widget.model.default and len(self._view_model.storages) > 1 - self.make_default_action.props.enabled = widget.model is not self._view_model.default + self.remove_action.props.enabled = ( + not widget.model.default and len(self._view_model.storages) > 1 + ) + self.make_default_action.props.enabled = ( + widget.model is not self._view_model.default + ) self._view_model.selected_storage = widget.model - diff --git a/cozy/view_model/storages_view_model.py b/cozy/view_model/storages_view_model.py index 22aeee3a..c9463fe3 100644 --- a/cozy/view_model/storages_view_model.py +++ b/cozy/view_model/storages_view_model.py @@ -2,22 +2,20 @@ from threading import Thread from peewee import SqliteDatabase + from cozy.application_settings import ApplicationSettings from cozy.architecture.event_sender import EventSender from cozy.architecture.observable import Observable from cozy.control.filesystem_monitor import FilesystemMonitor -from cozy.model.library import Library -from cozy.model.storage import Storage from cozy.ext import inject from cozy.media.importer import Importer +from cozy.model.library import Library from cozy.model.settings import Settings -from cozy.report import reporter -from gi.repository import Gtk, Adw - - +from cozy.model.storage import Storage log = logging.getLogger("storages_view_model") + class StoragesViewModel(Observable, EventSender): _library: Library = inject.attr(Library) _importer: Importer = inject.attr(Importer) @@ -26,24 +24,24 @@ class StoragesViewModel(Observable, EventSender): _db = inject.attr(SqliteDatabase) _fs_monitor = inject.attr(FilesystemMonitor) - def __init__(self): + def __init__(self) -> None: super().__init__() super(Observable, self).__init__() self._selected_storage = None - def _scan_new_storage(self, model: Storage): + def _scan_new_storage(self, model: Storage) -> None: self.emit_event("storage-added", model) log.info("New audiobook location added. Starting import scan.") Thread(target=self._importer.scan, name="ImportThread").start() - def _rebase_storage_location(self, model: Storage, old_path: str): + def _rebase_storage_location(self, model: Storage, old_path: str) -> None: self.emit_event("storage-changed", model) log.info("Audio book location changed, rebasing the location in Cozy.") Thread( target=self._library.rebase_path, args=(old_path, model.path), - name="RebaseStorageLocationThread" + name="RebaseStorageLocationThread", ).start() def add_storage_location(self, path: str) -> None: @@ -55,7 +53,7 @@ def add_storage_location(self, path: str) -> None: self._scan_new_storage(model) - def add_first_storage_location(self, path: str): + def add_first_storage_location(self, path: str) -> None: storage = self.storages[0] storage.path = path storage.default = True @@ -100,7 +98,7 @@ def remove(self, model: Storage) -> None: self._notify("storage_locations") - def set_default(self, model: Storage): + def set_default(self, model: Storage) -> None: if model.default: return @@ -111,7 +109,7 @@ def set_default(self, model: Storage): self._notify("storage_attributes") - def set_external(self, model: Storage, external: bool): + def set_external(self, model: Storage, external: bool) -> None: model.external = external if external: From df9dcef7e27b06a6ce2921688e6ca6cb7c495cda Mon Sep 17 00:00:00 2001 From: rdbende Date: Thu, 21 Dec 2023 16:03:44 +0100 Subject: [PATCH 06/20] This was a test lol --- cozy/ui/preferences_view.py | 2 -- 1 file changed, 2 deletions(-) diff --git a/cozy/ui/preferences_view.py b/cozy/ui/preferences_view.py index ce871893..6fd02e57 100644 --- a/cozy/ui/preferences_view.py +++ b/cozy/ui/preferences_view.py @@ -95,8 +95,6 @@ def _bind_settings(self) -> None: Gio.SettingsBindFlags.DEFAULT, ) - self.storage_locations_view.set_sensitive(False) - def _on_lock_ui_changed(self) -> None: sensitive = not self._view_model.lock_ui From fa9eac4218faa3f4298c4c3595c0087661352f80 Mon Sep 17 00:00:00 2001 From: rdbende Date: Thu, 21 Dec 2023 16:09:16 +0100 Subject: [PATCH 07/20] New storage button as part of the same list --- cozy/ui/widgets/storages.py | 13 ++++++++++--- data/ui/storage_locations.ui | 20 -------------------- 2 files changed, 10 insertions(+), 23 deletions(-) diff --git a/cozy/ui/widgets/storages.py b/cozy/ui/widgets/storages.py index 55d5280c..4fb70068 100644 --- a/cozy/ui/widgets/storages.py +++ b/cozy/ui/widgets/storages.py @@ -95,18 +95,16 @@ class StorageLocations(Adw.PreferencesGroup): _view_model: StoragesViewModel = inject.attr(StoragesViewModel) storage_locations_list: Gtk.ListBox = Gtk.Template.Child() - new_storage_button: Adw.ActionRow = Gtk.Template.Child() storage_menu: Gio.Menu = Gtk.Template.Child() def __init__(self) -> None: super().__init__() - self.new_storage_button.connect("activated", self._on_new_storage_clicked) - self._view_model.bind_to("storage_locations", self._reload_storage_list) self._view_model.bind_to("storage_attributes", self._reload_storage_list) self._create_actions() + self.new_storage_button = self._create_new_storage_button() self._reload_storage_list() @@ -132,6 +130,13 @@ def _create_actions(self) -> None: self.make_default_action.connect("activate", self._set_default_storage_location) self.action_group.add_action(self.make_default_action) + def _create_new_storage_button(self) -> Adw.ActionRow: + icon = Gtk.Image(icon_name="list-add-symbolic", margin_top=18, margin_bottom=18) + row = Adw.ActionRow(selectable=False, activatable=True) + row.connect("activated", self._on_new_storage_clicked) + row.set_child(icon) + return row + def _reload_storage_list(self) -> None: self.storage_locations_list.remove_all() @@ -141,6 +146,8 @@ def _reload_storage_list(self) -> None: row.connect("menu-opened", self._on_storage_menu_opened) self.storage_locations_list.append(row) + self.storage_locations_list.append(self.new_storage_button) + def _remove_storage_location(self, *_) -> None: self._view_model.remove(self._view_model.selected_storage) diff --git a/data/ui/storage_locations.ui b/data/ui/storage_locations.ui index a4922c55..afb6dce7 100644 --- a/data/ui/storage_locations.ui +++ b/data/ui/storage_locations.ui @@ -11,26 +11,6 @@
- - - - - false - true - - - list-add-symbolic - 18 - 18 - - - - - - -
From ae2def337c387d6d614f140f44624c5710d12503 Mon Sep 17 00:00:00 2001 From: rdbende Date: Thu, 21 Dec 2023 16:40:13 +0100 Subject: [PATCH 08/20] I didn't think about the tests before removing that unnecessary code --- cozy/model/settings.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/cozy/model/settings.py b/cozy/model/settings.py index c56dd913..6d5a2fc3 100644 --- a/cozy/model/settings.py +++ b/cozy/model/settings.py @@ -69,6 +69,8 @@ def invalidate(self): self._storages.clear() def _load_all_storage_locations(self): + self.invalidate() + for storage_db_obj in StorageModel.select(StorageModel.id): try: self._storages.append(Storage(self._db, storage_db_obj.id)) From d5fda9e288ffac06c3fcabe1d679b3378c4c8342 Mon Sep 17 00:00:00 2001 From: rdbende Date: Thu, 21 Dec 2023 19:01:04 +0100 Subject: [PATCH 09/20] Set initial folder when changing location --- cozy/ui/widgets/storages.py | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/cozy/ui/widgets/storages.py b/cozy/ui/widgets/storages.py index 4fb70068..abed1da6 100644 --- a/cozy/ui/widgets/storages.py +++ b/cozy/ui/widgets/storages.py @@ -10,12 +10,12 @@ log = logging.getLogger("settings") -def ask_storage_location(callback: Callable[[str], None], *junk): +def ask_storage_location(callback: Callable[[str], None], *junk, initial_folder: str | None = None): location_chooser = Gtk.FileDialog(title=_("Set Audiobooks Directory")) - # if path: - # folder = Gio.File.new_for_path(path) - # location_chooser.set_initial_folder(folder) + if initial_folder: + gfile = Gio.File.new_for_path(initial_folder) + location_chooser.set_initial_folder(gfile) def finish_callback(dialog, result): try: @@ -56,7 +56,7 @@ def model(self) -> Storage: return self._model def ask_for_new_location(self, *_) -> None: - ask_storage_location(self._on_folder_changed) + ask_storage_location(self._on_folder_changed, initial_folder=self._model.path) def _on_folder_changed(self, new_path: str) -> None: self.emit("location-changed", new_path) From 96f50905f30e1a2c715a86456659e22ec858dcb2 Mon Sep 17 00:00:00 2001 From: rdbende Date: Thu, 21 Dec 2023 19:17:18 +0100 Subject: [PATCH 10/20] Nah, this shouldn't be entirely public --- cozy/ui/widgets/storages.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/cozy/ui/widgets/storages.py b/cozy/ui/widgets/storages.py index abed1da6..12a8a745 100644 --- a/cozy/ui/widgets/storages.py +++ b/cozy/ui/widgets/storages.py @@ -117,7 +117,7 @@ def _create_actions(self) -> None: None, GLib.Variant.new_boolean(False), ) - self.set_external_signal_handler = self.set_external_action.connect( + self._set_external_signal_handler = self.set_external_action.connect( "notify::state", self._mark_storage_location_external ) self.action_group.add_action(self.set_external_action) @@ -169,7 +169,7 @@ def _on_storage_location_changed( self._view_model.change_storage_location(widget.model, new_location) def _on_storage_menu_opened(self, widget: StorageRow) -> None: - with self.set_external_action.handler_block(self.set_external_signal_handler): + with self.set_external_action.handler_block(self._set_external_signal_handler): self.set_external_action.props.state = GLib.Variant.new_boolean( widget.model.external ) From 3f1fb744ae6ece5430e8de71c0951fa6ad2793cb Mon Sep 17 00:00:00 2001 From: rdbende Date: Thu, 21 Dec 2023 23:34:17 +0100 Subject: [PATCH 11/20] Disable storage list while work is being done --- cozy/app_controller.py | 6 ++++-- cozy/ui/main_view.py | 2 +- cozy/view_model/headerbar_view_model.py | 2 ++ 3 files changed, 7 insertions(+), 3 deletions(-) diff --git a/cozy/app_controller.py b/cozy/app_controller.py index 003f53db..b504eb2d 100644 --- a/cozy/app_controller.py +++ b/cozy/app_controller.py @@ -76,7 +76,7 @@ def __init__(self, gtk_app, main_window_builder, main_window): self.library_view_model.add_listener(self._on_open_view) self.library_view_model.add_listener(self._on_library_view_event) self.playback_control_view_model.add_listener(self._on_open_view) - self.headerbar_view_model.add_listener(self._on_open_view) + self.headerbar_view_model.add_listener(self._on_working_event) self.app_view_model.add_listener(self._on_app_view_event) self.main_window.add_listener(self._on_main_window_event) @@ -148,10 +148,12 @@ def _on_app_view_event(self, event: str, data): if event == "view": self.headerbar_view_model.set_view(data) - def _on_main_window_event(self, event: str, data): + def _on_working_event(self, event: str, data) -> None: if event == "working": self.book_detail_view_model.lock_ui = data self.settings_view_model.lock_ui = data + + def _on_main_window_event(self, event: str, data): if event == "open_view": self._on_open_view(data, None) diff --git a/cozy/ui/main_view.py b/cozy/ui/main_view.py index 39801517..2fdf606e 100644 --- a/cozy/ui/main_view.py +++ b/cozy/ui/main_view.py @@ -249,9 +249,9 @@ def switch_to_playing(self): self.block_ui_buttons(False, True) else: # we want to only block the player controls + # TODO: rework. this is messy self.block_ui_buttons(False, True) self.block_ui_buttons(True, False) - self.emit_event_main_thread("working", False) def check_for_tracks(self): """ diff --git a/cozy/view_model/headerbar_view_model.py b/cozy/view_model/headerbar_view_model.py index dc20d622..9b5e2223 100644 --- a/cozy/view_model/headerbar_view_model.py +++ b/cozy/view_model/headerbar_view_model.py @@ -63,10 +63,12 @@ def _start_working(self, message: str): self._notify("work_message") self._notify("work_progress") self._notify("state") + self.emit_event_main_thread("working", True) def _stop_working(self): self._state = HeaderBarState.PLAYING self._notify("state") + self.emit_event_main_thread("working", False) def _on_importer_event(self, event: str, message): if event == "scan-progress" and isinstance(message, float): From 79dd5e54a7fb368dc4834a34367ac71108c52a66 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Benedek=20D=C3=A9v=C3=A9nyi?= Date: Fri, 22 Dec 2023 01:05:42 +0100 Subject: [PATCH 12/20] Update cozy/view_model/storages_view_model.py Co-authored-by: Naglis Jonaitis <827324+naglis@users.noreply.github.com> --- cozy/view_model/storages_view_model.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/cozy/view_model/storages_view_model.py b/cozy/view_model/storages_view_model.py index c9463fe3..0ae64381 100644 --- a/cozy/view_model/storages_view_model.py +++ b/cozy/view_model/storages_view_model.py @@ -37,7 +37,7 @@ def _scan_new_storage(self, model: Storage) -> None: def _rebase_storage_location(self, model: Storage, old_path: str) -> None: self.emit_event("storage-changed", model) - log.info("Audio book location changed, rebasing the location in Cozy.") + log.info("Audiobook location changed, rebasing the location in Cozy.") Thread( target=self._library.rebase_path, args=(old_path, model.path), From 3c8bef4d285e5bafeb86f22ae3519991f1a49b55 Mon Sep 17 00:00:00 2001 From: rdbende Date: Fri, 22 Dec 2023 12:54:52 +0100 Subject: [PATCH 13/20] Remove unused logging, format code with proper line-length --- cozy/model/settings.py | 14 ++++++++++---- cozy/ui/widgets/storages.py | 27 ++++++--------------------- 2 files changed, 16 insertions(+), 25 deletions(-) diff --git a/cozy/model/settings.py b/cozy/model/settings.py index 6d5a2fc3..41ea8e0d 100644 --- a/cozy/model/settings.py +++ b/cozy/model/settings.py @@ -31,9 +31,13 @@ def last_played_book(self) -> Optional[Book]: try: return self._db_object.last_played_book except peewee.DoesNotExist: - log.warning("last_played_book references an non existent object. Setting last_played_book to None.") - reporter.warning("settings_model", - "last_played_book references an non existent object. Setting last_played_book to None.") + log.warning( + "last_played_book references an non existent object. Setting last_played_book to None." + ) + reporter.warning( + "settings_model", + "last_played_book references an non existent object. Setting last_played_book to None.", + ) self.last_played_book = None return None @@ -75,7 +79,9 @@ def _load_all_storage_locations(self): try: self._storages.append(Storage(self._db, storage_db_obj.id)) except InvalidPath: - log.error("Invalid path found in database, skipping: {}".format(storage_db_obj.path)) + log.error( + "Invalid path found in database, skipping: {}".format(storage_db_obj.path) + ) self._ensure_default_storage_present() diff --git a/cozy/ui/widgets/storages.py b/cozy/ui/widgets/storages.py index 12a8a745..aba283e0 100644 --- a/cozy/ui/widgets/storages.py +++ b/cozy/ui/widgets/storages.py @@ -1,4 +1,3 @@ -import logging from typing import Callable from gi.repository import Adw, Gio, GLib, GObject, Gtk @@ -7,8 +6,6 @@ from cozy.model.storage import Storage from cozy.view_model.storages_view_model import StoragesViewModel -log = logging.getLogger("settings") - def ask_storage_location(callback: Callable[[str], None], *junk, initial_folder: str | None = None): location_chooser = Gtk.FileDialog(title=_("Set Audiobooks Directory")) @@ -26,9 +23,7 @@ def finish_callback(dialog, result): if file is not None: callback(file.get_path()) - location_chooser.select_folder( - inject.instance("MainWindow").window, None, finish_callback - ) + location_chooser.select_folder(inject.instance("MainWindow").window, None, finish_callback) @Gtk.Template.from_resource("/com/github/geigi/cozy/storage_row.ui") @@ -83,9 +78,7 @@ def _set_default_icon(self) -> None: GObject.TYPE_PYOBJECT, (GObject.TYPE_PYOBJECT,), ) -GObject.signal_new( - "menu-opened", StorageRow, GObject.SIGNAL_RUN_LAST, GObject.TYPE_PYOBJECT, () -) +GObject.signal_new("menu-opened", StorageRow, GObject.SIGNAL_RUN_LAST, GObject.TYPE_PYOBJECT, ()) @Gtk.Template.from_resource("/com/github/geigi/cozy/storage_locations.ui") @@ -113,9 +106,7 @@ def _create_actions(self) -> None: self.insert_action_group("storage", self.action_group) self.set_external_action = Gio.SimpleAction.new_stateful( - "mark-external", - None, - GLib.Variant.new_boolean(False), + "mark-external", None, GLib.Variant.new_boolean(False) ) self._set_external_signal_handler = self.set_external_action.connect( "notify::state", self._mark_storage_location_external @@ -163,21 +154,15 @@ def _mark_storage_location_external( def _on_new_storage_clicked(self, *junk) -> None: ask_storage_location(self._view_model.add_storage_location) - def _on_storage_location_changed( - self, widget: StorageRow, new_location: str - ) -> None: + def _on_storage_location_changed(self, widget: StorageRow, new_location: str) -> None: self._view_model.change_storage_location(widget.model, new_location) def _on_storage_menu_opened(self, widget: StorageRow) -> None: with self.set_external_action.handler_block(self._set_external_signal_handler): - self.set_external_action.props.state = GLib.Variant.new_boolean( - widget.model.external - ) + self.set_external_action.props.state = GLib.Variant.new_boolean(widget.model.external) self.remove_action.props.enabled = ( not widget.model.default and len(self._view_model.storages) > 1 ) - self.make_default_action.props.enabled = ( - widget.model is not self._view_model.default - ) + self.make_default_action.props.enabled = widget.model is not self._view_model.default self._view_model.selected_storage = widget.model From 7cead39ba2d33aab84bc79ccc49675f472c02d0c Mon Sep 17 00:00:00 2001 From: rdbende Date: Fri, 22 Dec 2023 13:04:50 +0100 Subject: [PATCH 14/20] It should already be the default --- cozy/view_model/storages_view_model.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/cozy/view_model/storages_view_model.py b/cozy/view_model/storages_view_model.py index 0ae64381..c564d6f4 100644 --- a/cozy/view_model/storages_view_model.py +++ b/cozy/view_model/storages_view_model.py @@ -56,8 +56,8 @@ def add_storage_location(self, path: str) -> None: def add_first_storage_location(self, path: str) -> None: storage = self.storages[0] storage.path = path - storage.default = True storage.external = self._fs_monitor.is_external(path) + assert storage.default self._model.invalidate() self._notify("storage_locations") From 9217a546ae7732dd2d2be30d3018ddd7fefeac1c Mon Sep 17 00:00:00 2001 From: rdbende Date: Fri, 22 Dec 2023 13:07:23 +0100 Subject: [PATCH 15/20] Better function name --- cozy/model/settings.py | 4 ++-- test/cozy/model/test_settings.py | 8 ++++---- 2 files changed, 6 insertions(+), 6 deletions(-) diff --git a/cozy/model/settings.py b/cozy/model/settings.py index 41ea8e0d..c1018da3 100644 --- a/cozy/model/settings.py +++ b/cozy/model/settings.py @@ -83,8 +83,8 @@ def _load_all_storage_locations(self): "Invalid path found in database, skipping: {}".format(storage_db_obj.path) ) - self._ensure_default_storage_present() + self._ensure_default_storage_is_present() - def _ensure_default_storage_present(self): + def _ensure_default_storage_is_present(self): if self._storages and not any(storage.default for storage in self._storages): self._storages[0].default = True diff --git a/test/cozy/model/test_settings.py b/test/cozy/model/test_settings.py index 6bba3875..8cf0639b 100644 --- a/test/cozy/model/test_settings.py +++ b/test/cozy/model/test_settings.py @@ -86,7 +86,7 @@ def test_fetching_non_existent_last_played_book_sets_it_to_none(peewee_database) assert SettingsModel.get().last_played_book is None -def test_ensure_default_storage_present_adds_default_if_not_present(peewee_database): +def test_ensure_default_storage_is_present_adds_default_if_not_present(peewee_database): from cozy.model.settings import Settings from cozy.db.storage import Storage @@ -94,17 +94,17 @@ def test_ensure_default_storage_present_adds_default_if_not_present(peewee_datab settings = Settings() settings._load_all_storage_locations() - settings._ensure_default_storage_present() + settings._ensure_default_storage_is_present() assert Storage.get(1).default assert not Storage.get(2).default -def test_ensure_default_storage_present_does_nothing_if_default_is_present(peewee_database): +def test_ensure_default_storage_is_present_does_nothing_if_default_is_present(peewee_database): from cozy.model.settings import Settings from cozy.db.storage import Storage settings = Settings() settings._load_all_storage_locations() - settings._ensure_default_storage_present() + settings._ensure_default_storage_is_present() assert not Storage.get(1).default assert Storage.get(2).default From e8fb0a717ca45657d14eeabe2c468b93e54a6208 Mon Sep 17 00:00:00 2001 From: rdbende Date: Fri, 22 Dec 2023 13:12:30 +0100 Subject: [PATCH 16/20] Add type annotations, and an assert --- cozy/model/settings.py | 18 +++++++++--------- 1 file changed, 9 insertions(+), 9 deletions(-) diff --git a/cozy/model/settings.py b/cozy/model/settings.py index c1018da3..a1fd3dac 100644 --- a/cozy/model/settings.py +++ b/cozy/model/settings.py @@ -1,5 +1,4 @@ import logging -from typing import List, Optional import peewee @@ -27,7 +26,7 @@ def first_start(self) -> bool: return self._db_object.first_start @property - def last_played_book(self) -> Optional[Book]: + def last_played_book(self) -> Book | None: try: return self._db_object.last_played_book except peewee.DoesNotExist: @@ -43,7 +42,7 @@ def last_played_book(self) -> Optional[Book]: return None @last_played_book.setter - def last_played_book(self, new_value): + def last_played_book(self, new_value) -> None: if new_value: self._db_object.last_played_book = new_value._db_object else: @@ -52,27 +51,28 @@ def last_played_book(self, new_value): self._db_object.save(only=self._db_object.dirty_fields) @property - def default_location(self): + def default_location(self) -> Storage: return next(location for location in self.storage_locations if location.default) @property - def storage_locations(self): + def storage_locations(self) -> list[Storage]: if not self._storages: self._load_all_storage_locations() + assert self._storages return self._storages @property - def external_storage_locations(self): + def external_storage_locations(self) -> list[Storage]: if not self._storages: self._load_all_storage_locations() return [storage for storage in self._storages if storage.external] - def invalidate(self): + def invalidate(self) -> None: self._storages.clear() - def _load_all_storage_locations(self): + def _load_all_storage_locations(self) -> None: self.invalidate() for storage_db_obj in StorageModel.select(StorageModel.id): @@ -85,6 +85,6 @@ def _load_all_storage_locations(self): self._ensure_default_storage_is_present() - def _ensure_default_storage_is_present(self): + def _ensure_default_storage_is_present(self) -> None: if self._storages and not any(storage.default for storage in self._storages): self._storages[0].default = True From 9f87b45101e9ede4a5125a022b2f01717e42bc89 Mon Sep 17 00:00:00 2001 From: rdbende Date: Fri, 22 Dec 2023 13:15:22 +0100 Subject: [PATCH 17/20] Turns out that *junk was not even necessary anymore --- cozy/ui/widgets/storages.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/cozy/ui/widgets/storages.py b/cozy/ui/widgets/storages.py index aba283e0..2cf21ab4 100644 --- a/cozy/ui/widgets/storages.py +++ b/cozy/ui/widgets/storages.py @@ -7,7 +7,7 @@ from cozy.view_model.storages_view_model import StoragesViewModel -def ask_storage_location(callback: Callable[[str], None], *junk, initial_folder: str | None = None): +def ask_storage_location(callback: Callable[[str], None], initial_folder: str | None = None): location_chooser = Gtk.FileDialog(title=_("Set Audiobooks Directory")) if initial_folder: @@ -151,7 +151,7 @@ def _mark_storage_location_external( value = action.get_property(value.name) self._view_model.set_external(self._view_model.selected_storage, value) - def _on_new_storage_clicked(self, *junk) -> None: + def _on_new_storage_clicked(self, *_) -> None: ask_storage_location(self._view_model.add_storage_location) def _on_storage_location_changed(self, widget: StorageRow, new_location: str) -> None: From d31b0ee55fac869b61af0f57b52f0fe7d17d01ad Mon Sep 17 00:00:00 2001 From: rdbende Date: Fri, 22 Dec 2023 13:52:23 +0100 Subject: [PATCH 18/20] This wasn't a good idea --- cozy/model/settings.py | 1 - 1 file changed, 1 deletion(-) diff --git a/cozy/model/settings.py b/cozy/model/settings.py index a1fd3dac..8afa96dc 100644 --- a/cozy/model/settings.py +++ b/cozy/model/settings.py @@ -59,7 +59,6 @@ def storage_locations(self) -> list[Storage]: if not self._storages: self._load_all_storage_locations() - assert self._storages return self._storages @property From 68e2924b7f2d191c2b945cee2457e178b6fbedb9 Mon Sep 17 00:00:00 2001 From: rdbende Date: Fri, 19 Jan 2024 22:47:42 +0100 Subject: [PATCH 19/20] Delete books when removing a storage location --- cozy/view_model/library_view_model.py | 33 ++++++++++---------------- cozy/view_model/storages_view_model.py | 11 ++++++++- 2 files changed, 23 insertions(+), 21 deletions(-) diff --git a/cozy/view_model/library_view_model.py b/cozy/view_model/library_view_model.py index 612724e8..a29e51ff 100644 --- a/cozy/view_model/library_view_model.py +++ b/cozy/view_model/library_view_model.py @@ -20,7 +20,7 @@ from cozy.report import reporter from cozy.ui.widgets.book_element import BookElement from cozy.ui.import_failed_dialog import ImportFailedDialog -from cozy.view_model.settings_view_model import SettingsViewModel +from cozy.view_model.storages_view_model import StoragesViewModel log = logging.getLogger("library_view_model") @@ -43,7 +43,7 @@ class LibraryViewModel(Observable, EventSender): _model = inject.attr(Library) _importer: Importer = inject.attr(Importer) _player: Player = inject.attr(Player) - _settings: SettingsViewModel = inject.attr(SettingsViewModel) + _storages: StoragesViewModel = inject.attr(StoragesViewModel) def __init__(self): super().__init__() @@ -60,7 +60,7 @@ def _connect(self): self._importer.add_listener(self._on_importer_event) self._player.add_listener(self._on_player_event) self._model.add_listener(self._on_model_event) - self._settings.add_listener(self._on_settings_event) + self._storages.add_listener(self._on_storages_event) @property def books(self): @@ -222,24 +222,17 @@ def _on_player_event(self, event, message): elif event == "position" or event == "book-finished": self._notify("book-progress") - def _on_settings_event(self, event: str, message): + def _on_storages_event(self, event: str, message): if event == "storage-removed": - self._on_external_storage_removed(message) - - def _on_external_storage_removed(self, storage: Storage): - books = self.books.copy() - for book in books: - chapters_to_remove = [c for c in book.chapters if c.file.startswith(str(storage.path))] - - for chapter in chapters_to_remove: - chapter.delete() - - self._notify("authors") - self._notify("readers") - self._notify("books") - self._notify("books-filter") - self._notify("current_book_in_playback") - self._notify("playing") + for property in ( + "authors", + "readers", + "books", + "books-filter", + "current_book_in_playback", + "playing", + ): + self._notify(property) def _on_model_event(self, event: str, message): if event == "rebase-finished": diff --git a/cozy/view_model/storages_view_model.py b/cozy/view_model/storages_view_model.py index c564d6f4..9316c012 100644 --- a/cozy/view_model/storages_view_model.py +++ b/cozy/view_model/storages_view_model.py @@ -94,8 +94,17 @@ def remove(self, model: Storage) -> None: model.delete() self._model.invalidate() - self.emit_event("storage-removed", model) + storage_path = str(model.path) + for book in self._library.books: + chapters_to_remove = [ + c for c in book.chapters if c.file.startswith(storage_path) + ] + + for chapter in chapters_to_remove: + chapter.delete() + + self.emit_event("storage-removed", model) self._notify("storage_locations") def set_default(self, model: Storage) -> None: From 6d33b2b8e351b4312d521d97dc28365e18c6331d Mon Sep 17 00:00:00 2001 From: rdbende Date: Fri, 19 Jan 2024 22:47:55 +0100 Subject: [PATCH 20/20] Blacking --- cozy/view_model/library_view_model.py | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/cozy/view_model/library_view_model.py b/cozy/view_model/library_view_model.py index a29e51ff..a2cb0cb2 100644 --- a/cozy/view_model/library_view_model.py +++ b/cozy/view_model/library_view_model.py @@ -96,8 +96,7 @@ def authors(self): authors = { book.author - for book - in self._model.books + for book in self._model.books if is_book_online(book) or show_offline_books or book.downloaded } @@ -110,8 +109,7 @@ def readers(self): readers = { book.reader - for book - in self._model.books + for book in self._model.books if is_book_online(book) or show_offline_books or book.downloaded }