From 464ed889d69fb68253fc5e1c144a3ca6afadc142 Mon Sep 17 00:00:00 2001 From: rdbende Date: Sat, 9 Dec 2023 23:03:03 +0100 Subject: [PATCH 01/41] Standard grid gaps --- data/ui/book_element.ui | 4 ---- data/ui/main_window.ui | 14 +++++++------- 2 files changed, 7 insertions(+), 11 deletions(-) diff --git a/data/ui/book_element.ui b/data/ui/book_element.ui index 402d96c7..ad157685 100644 --- a/data/ui/book_element.ui +++ b/data/ui/book_element.ui @@ -8,10 +8,6 @@ true Open book overview - 6 - 6 - 12 - 12 vertical 1 start diff --git a/data/ui/main_window.ui b/data/ui/main_window.ui index b6ad6048..38be7b56 100644 --- a/data/ui/main_window.ui +++ b/data/ui/main_window.ui @@ -154,15 +154,15 @@ minimum - 6 - 6 - 6 - 6 + 18 + 18 + 18 + 18 true - true + start true - 6 - 3 + 18 + 18 1 10 none From dcc5aebf4ee31cc2911d040f6c692646d01976f9 Mon Sep 17 00:00:00 2001 From: rdbende Date: Mon, 18 Dec 2023 16:39:41 +0100 Subject: [PATCH 02/41] 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 03/41] 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 04/41] 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 05/41] 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 06/41] 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 07/41] 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 08/41] 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 09/41] 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 10/41] 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 11/41] 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 12/41] 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 13/41] 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 14/41] 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 15/41] 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 16/41] 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 17/41] 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 18/41] 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 19/41] 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 d9a76e1fe86003d14643c085233b45f716ad42b8 Mon Sep 17 00:00:00 2001 From: rdbende Date: Sat, 23 Dec 2023 12:04:00 +0100 Subject: [PATCH 20/41] Hide it instead of disabling --- cozy/ui/media_controller.py | 12 +++--------- data/ui/main_window.ui | 6 +----- data/ui/media_controller.ui | 5 ----- data/ui/seek_bar.ui | 1 - 4 files changed, 4 insertions(+), 20 deletions(-) diff --git a/cozy/ui/media_controller.py b/cozy/ui/media_controller.py index b5c9a506..fe3e3e05 100644 --- a/cozy/ui/media_controller.py +++ b/cozy/ui/media_controller.py @@ -40,8 +40,8 @@ class MediaController(Adw.BreakpointBin): def __init__(self, main_window_builder: Gtk.Builder): super().__init__() - media_control_box: Gtk.Box = main_window_builder.get_object("media_control_box") - media_control_box.append(self) + self.container_bar: Gtk.Revealer = main_window_builder.get_object("media_control_box") + self.container_bar.set_child(self) self.seek_bar = SeekBar() self.seek_bar_container.append(self.seek_bar) @@ -127,13 +127,7 @@ def _on_length_changed(self): def _on_lock_ui_changed(self): sensitive = not self._playback_control_view_model.lock_ui - self.seek_bar.sensitive = sensitive - self.prev_button.set_sensitive(sensitive) - self.next_button.set_sensitive(sensitive) - self.play_button.set_sensitive(sensitive) - self.volume_button.set_sensitive(sensitive) - self.playback_speed_button.set_sensitive(sensitive) - self.timer_button.set_sensitive(sensitive) + self.container_bar.set_reveal_child(sensitive) def _on_volume_changed(self): self.volume_button.set_value(self._playback_control_view_model.volume) diff --git a/data/ui/main_window.ui b/data/ui/main_window.ui index b6ad6048..d8b541cd 100644 --- a/data/ui/main_window.ui +++ b/data/ui/main_window.ui @@ -220,11 +220,7 @@ vertical - - - + diff --git a/data/ui/media_controller.ui b/data/ui/media_controller.ui index fac4dd60..46318de5 100644 --- a/data/ui/media_controller.ui +++ b/data/ui/media_controller.ui @@ -106,7 +106,6 @@ 6 - false true true Rewind @@ -126,7 +125,6 @@ 42 42 - false true true Start playback @@ -145,7 +143,6 @@ - false true true Forward @@ -200,7 +197,6 @@ audio-volume-medium-symbolic - false true true Playback speed @@ -212,7 +208,6 @@ audio-volume-medium-symbolic - false true true Sleep timer diff --git a/data/ui/seek_bar.ui b/data/ui/seek_bar.ui index 6873bf27..2254819d 100644 --- a/data/ui/seek_bar.ui +++ b/data/ui/seek_bar.ui @@ -26,7 +26,6 @@ 150 - false true Jump to position in current chapter center From ddb8a27a1ea13825c837b63cd8381bdd13aa809c Mon Sep 17 00:00:00 2001 From: rdbende Date: Wed, 27 Dec 2023 12:51:37 +0100 Subject: [PATCH 21/41] Update things, add new release notes --- data/com.github.geigi.cozy.appdata.xml | 80 ++++++++------------------ 1 file changed, 25 insertions(+), 55 deletions(-) diff --git a/data/com.github.geigi.cozy.appdata.xml b/data/com.github.geigi.cozy.appdata.xml index 889fd80a..a8d5ff23 100644 --- a/data/com.github.geigi.cozy.appdata.xml +++ b/data/com.github.geigi.cozy.appdata.xml @@ -1,18 +1,21 @@ - com.github.geigi.cozy + @APP_ID@ + @APP_ID@.desktop + @APP_ID@ + + @APP_ID@ + + Cozy + Julian Geywitz + cozy@geigi.de CC0 GPL-3.0+ - Cozy Listen to audio books -

- Do you like audio books? Then lets get cozy! -

-

- Cozy is a audio book player. Here are some of the features: -

-
    +

    Do you like audio books? Then lets get cozy!

    +

    Cozy is a audio book player. Here are some of the features:

    +
    • Import all your audio books into Cozy to browse them comfortably
    • Listen to your DRM free mp3, m4b, m4a (aac, ALAC, …), flac, ogg and wav audio books
    • Remembers your playback position
    • @@ -23,13 +26,15 @@
    • Offline Mode! This allows you to keep an audio book on your internal storage if you store your audio books on an external or network drive. Perfect to listen to on the go!
    • Drag and Drop to import new audio books
    • Sort your audio books by author, reader and name
    • -
    +
- - com.github.geigi.cozy - - com.github.geigi.cozy.desktop - com.github.geigi.cozy + https://cozy.sh + https://github.com/geigi/cozy/issues + https://matrix.to/#/#cozy:gnome.org + https://www.patreon.com/geigi + https://github.com/geigi/cozy/ + https://www.transifex.com/geigi/cozy/ + https://raw.githubusercontent.com/geigi/cozy/img/img/screenshot1.png @@ -44,19 +49,15 @@ https://raw.githubusercontent.com/geigi/cozy/img/img/screenshot4.png - Julian Geywitz - https://cozy.sh - https://github.com/geigi/cozy/issues - https://matrix.to/#/#cozy:gnome.org?via=matrix.org&via=gnome.org - https://www.patreon.com/geigi - https://github.com/geigi/cozy/ - https://www.transifex.com/geigi/cozy/ - cozy@geigi.de
    -
  • Update to GTK 4 and libadwaita (thank you rdbende and grahamvh!)
  • +
  • Update the UI to GTK4 and libadwaita. This allows Cozy to take advantage of the new stylesheet, automatic dark mode and utilize the latest and greatest UI elements throughout the application. Many thanks to Benedek Dévényi and grahamvh!
  • +
  • Smaller visual refinements to match the state of the art of GNOME apps
  • +
  • Significant cleanup and improvements to the codebase
  • +
  • Dozens of bug fixes
  • +
  • As always, updated translations thanks to all translators!
@@ -220,37 +221,6 @@
- - - none - none - none - none - none - none - none - none - none - none - none - none - none - none - none - none - none - none - none - none - none - none - none - none - none - none - none - - #FB6542 #FFFFFF From ab0723a7800f9c56216525c92a1f14aa2d77ff42 Mon Sep 17 00:00:00 2001 From: rdbende Date: Wed, 27 Dec 2023 13:05:20 +0100 Subject: [PATCH 22/41] Supports mobile --- data/com.github.geigi.cozy.appdata.xml | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/data/com.github.geigi.cozy.appdata.xml b/data/com.github.geigi.cozy.appdata.xml index a8d5ff23..32258e20 100644 --- a/data/com.github.geigi.cozy.appdata.xml +++ b/data/com.github.geigi.cozy.appdata.xml @@ -54,6 +54,7 @@
  • Update the UI to GTK4 and libadwaita. This allows Cozy to take advantage of the new stylesheet, automatic dark mode and utilize the latest and greatest UI elements throughout the application. Many thanks to Benedek Dévényi and grahamvh!
  • +
  • Much improved mobile support
  • Smaller visual refinements to match the state of the art of GNOME apps
  • Significant cleanup and improvements to the codebase
  • Dozens of bug fixes
  • @@ -221,6 +222,14 @@ + + pointing + keyboard + touch + + + 280 + #FB6542 #FFFFFF From b6b5d4be201a55ce10d01d77e49f6743f4be7171 Mon Sep 17 00:00:00 2001 From: rdbende Date: Wed, 27 Dec 2023 13:06:11 +0100 Subject: [PATCH 23/41] Let's not do this now --- data/com.github.geigi.cozy.appdata.xml | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/data/com.github.geigi.cozy.appdata.xml b/data/com.github.geigi.cozy.appdata.xml index 32258e20..0c24cbe4 100644 --- a/data/com.github.geigi.cozy.appdata.xml +++ b/data/com.github.geigi.cozy.appdata.xml @@ -1,10 +1,10 @@ - @APP_ID@ - @APP_ID@.desktop - @APP_ID@ + com.github.geigi.cozy + com.github.geigi.cozy.desktop + com.github.geigi.cozy - @APP_ID@ + com.github.geigi.cozy Cozy Julian Geywitz From b1372bb3fa065d0f59c7d04c2b7d2148d1796e8b Mon Sep 17 00:00:00 2001 From: rdbende Date: Wed, 27 Dec 2023 13:34:53 +0100 Subject: [PATCH 24/41] Timestamp is deprecated --- data/com.github.geigi.cozy.appdata.xml | 24 ++++++++++++------------ 1 file changed, 12 insertions(+), 12 deletions(-) diff --git a/data/com.github.geigi.cozy.appdata.xml b/data/com.github.geigi.cozy.appdata.xml index 0c24cbe4..e840b7e8 100644 --- a/data/com.github.geigi.cozy.appdata.xml +++ b/data/com.github.geigi.cozy.appdata.xml @@ -50,7 +50,7 @@ - +
    • Update the UI to GTK4 and libadwaita. This allows Cozy to take advantage of the new stylesheet, automatic dark mode and utilize the latest and greatest UI elements throughout the application. Many thanks to Benedek Dévényi and grahamvh!
    • @@ -62,7 +62,7 @@
    - +
    • Support for GTK style manager (thanks A6GibKm)
    • @@ -71,7 +71,7 @@
    - +

    This release features a redesigned preference window. All settings can now be searched. Good news for mobile users too: the redesign should work a lot better on mobile devices now. @@ -86,7 +86,7 @@

- +

A small bugfix release which makes Cozy more reliable. @@ -99,7 +99,7 @@ - +

A small bugfix release which makes Cozy more reliable. @@ -115,7 +115,7 @@ - +

A small bugfix release which makes Cozy more reliable. @@ -131,7 +131,7 @@ - +

This release features a redesigned library with responsiveness in mind. @@ -148,7 +148,7 @@ - +

Performance improvements for the book detail view and some bugfixes. @@ -163,7 +163,7 @@ - +

A small bugfix release which makes Cozy more reliable. @@ -178,7 +178,7 @@ - +

A small bugfix release which makes Cozy more reliable. @@ -193,7 +193,7 @@ - +

This release features chapter support for m4b files. @@ -207,7 +207,7 @@ - +

This release features chapter support for m4b files. From 9bde4b74b1e07337c7d7bbd9191f52536b9ae3e5 Mon Sep 17 00:00:00 2001 From: rdbende Date: Wed, 27 Dec 2023 13:35:09 +0100 Subject: [PATCH 25/41] Whoops, a bit wider --- data/com.github.geigi.cozy.appdata.xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/data/com.github.geigi.cozy.appdata.xml b/data/com.github.geigi.cozy.appdata.xml index e840b7e8..d873dd78 100644 --- a/data/com.github.geigi.cozy.appdata.xml +++ b/data/com.github.geigi.cozy.appdata.xml @@ -228,7 +228,7 @@ touch - 280 + 360 #FB6542 From 549aef9ccc16e0fed05144c5284a85bf6fc2c43f Mon Sep 17 00:00:00 2001 From: rdbende Date: Wed, 27 Dec 2023 13:35:37 +0100 Subject: [PATCH 26/41] Add new branding color tags --- data/com.github.geigi.cozy.appdata.xml | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/data/com.github.geigi.cozy.appdata.xml b/data/com.github.geigi.cozy.appdata.xml index d873dd78..cf7b634e 100644 --- a/data/com.github.geigi.cozy.appdata.xml +++ b/data/com.github.geigi.cozy.appdata.xml @@ -230,9 +230,11 @@ 360 + + #ffa348 + #ffa348 + - #FB6542 - #FFFFFF 2 pk_live_XJocNlICBfLqHpdZXN0LxlyV00xrhZTbDe From 36e0657d88670ad3c5b62a7b22af78c2c8d787cc Mon Sep 17 00:00:00 2001 From: rdbende Date: Wed, 27 Dec 2023 14:41:42 +0100 Subject: [PATCH 27/41] Tag screenshots with language --- data/com.github.geigi.cozy.appdata.xml | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/data/com.github.geigi.cozy.appdata.xml b/data/com.github.geigi.cozy.appdata.xml index cf7b634e..6c9b19a9 100644 --- a/data/com.github.geigi.cozy.appdata.xml +++ b/data/com.github.geigi.cozy.appdata.xml @@ -37,16 +37,16 @@ - https://raw.githubusercontent.com/geigi/cozy/img/img/screenshot1.png + https://raw.githubusercontent.com/geigi/cozy/img/img/screenshot1.png - https://raw.githubusercontent.com/geigi/cozy/img/img/screenshot2.png + https://raw.githubusercontent.com/geigi/cozy/img/img/screenshot2.png - https://raw.githubusercontent.com/geigi/cozy/img/img/screenshot3.png + https://raw.githubusercontent.com/geigi/cozy/img/img/screenshot3.png - https://raw.githubusercontent.com/geigi/cozy/img/img/screenshot4.png + https://raw.githubusercontent.com/geigi/cozy/img/img/screenshot4.png From 31f668ea8aa32378617e5d46ddb2f2321217abc7 Mon Sep 17 00:00:00 2001 From: rdbende Date: Wed, 27 Dec 2023 15:05:57 +0100 Subject: [PATCH 28/41] Better release notes --- data/com.github.geigi.cozy.appdata.xml | 10 ++++++++-- 1 file changed, 8 insertions(+), 2 deletions(-) diff --git a/data/com.github.geigi.cozy.appdata.xml b/data/com.github.geigi.cozy.appdata.xml index 6c9b19a9..e43e48a7 100644 --- a/data/com.github.geigi.cozy.appdata.xml +++ b/data/com.github.geigi.cozy.appdata.xml @@ -52,12 +52,18 @@ +

+ After almost two years, a new version of Cozy is finally here! This release brings an updated user interface along with numerous bug fixes and improvemed performance. +

+

+ The user interface has been ported to GTK4 and Libadwaita. Thus, Cozy benefits from the new style sheet, automatic dark mode, and utilizes the latest and greatest UI elements throughout the application. Many thanks to Benedek Dévényi and grahamvh! +

+

Other changes include:

    -
  • Update the UI to GTK4 and libadwaita. This allows Cozy to take advantage of the new stylesheet, automatic dark mode and utilize the latest and greatest UI elements throughout the application. Many thanks to Benedek Dévényi and grahamvh!
  • Much improved mobile support
  • Smaller visual refinements to match the state of the art of GNOME apps
  • +
  • Dozens of bug fixes and performance improvements
  • Significant cleanup and improvements to the codebase
  • -
  • Dozens of bug fixes
  • As always, updated translations thanks to all translators!
From 95f134b3238d24ff33b6948910f50d4179b33540 Mon Sep 17 00:00:00 2001 From: rdbende Date: Thu, 28 Dec 2023 11:24:09 +0100 Subject: [PATCH 29/41] Add insane hack to workaround the problem --- cozy/ui/widgets/seek_bar.py | 29 +++++++++++++++++++---------- 1 file changed, 19 insertions(+), 10 deletions(-) diff --git a/cozy/ui/widgets/seek_bar.py b/cozy/ui/widgets/seek_bar.py index 31c9e5c6..38114297 100644 --- a/cozy/ui/widgets/seek_bar.py +++ b/cozy/ui/widgets/seek_bar.py @@ -19,15 +19,25 @@ def __init__(self, **kwargs): self.progress_scale.connect("value-changed", self._on_progress_scale_changed) - self._progress_scale_gesture = Gtk.GestureClick() - self._progress_scale_gesture.connect("pressed", self._on_progress_scale_press) - self._progress_scale_gesture.connect("end", self._on_progress_scale_release) - self.progress_scale.add_controller(self._progress_scale_gesture) - - self._progress_scale_key = Gtk.EventControllerKey() - self._progress_scale_key.connect("key-pressed", self._on_progress_scale_press) - self._progress_scale_key.connect("key-released", self._on_progress_scale_release) - self.progress_scale.add_controller(self._progress_scale_key) + # HACK: Using a GtkGestureClick here is not possible, as GtkRange's internal + # gesture controller claims the button press event, and thus the released signal doesn't get emitted. + # Therefore we get its internal GtkGestureClick, and add our handlers to that. + # Hacky workaround from: https://gitlab.gnome.org/GNOME/gtk/-/issues/4939 + # Ideally GtkRange would forward these signals, so we wouldn't need this hack + # TODO: Add these signals to Gtk and make a MR? + for controller in self.progress_scale.observe_controllers(): + if isinstance(controller, Gtk.GestureClick): + click_gesture = controller + break + + click_gesture.set_button(0) # Enable all mouse buttons + click_gesture.connect("pressed", self._on_progress_scale_press) + click_gesture.connect("released", self._on_progress_scale_release) + + keyboard_controller = Gtk.EventControllerKey() + keyboard_controller.connect("key-pressed", self._on_progress_scale_press) + keyboard_controller.connect("key-released", self._on_progress_scale_release) + self.progress_scale.add_controller(keyboard_controller) @property def position(self) -> float: @@ -91,7 +101,6 @@ def _on_progress_key_pressed(self, _, event): def _on_progress_scale_press(self, *_): self._progress_scale_pressed = True - return False GObject.signal_new('position-changed', SeekBar, GObject.SIGNAL_RUN_LAST, GObject.TYPE_PYOBJECT, From dc3f631376eaeee187c455072278483a2d84957f Mon Sep 17 00:00:00 2001 From: rdbende Date: Thu, 4 Jan 2024 21:51:59 +0100 Subject: [PATCH 30/41] We use only two spaces for indentation --- data/com.github.geigi.cozy.appdata.xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/data/com.github.geigi.cozy.appdata.xml b/data/com.github.geigi.cozy.appdata.xml index 7fde1257..bb673468 100644 --- a/data/com.github.geigi.cozy.appdata.xml +++ b/data/com.github.geigi.cozy.appdata.xml @@ -9,7 +9,7 @@ Julian Geywitz - Julian Geywitz + Julian Geywitz cozy@geigi.de Listen to audio books From c7def89952d92ef58b9681148ea51a027c195a74 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Benedek=20D=C3=A9v=C3=A9nyi?= Date: Fri, 5 Jan 2024 13:01:24 +0100 Subject: [PATCH 31/41] Fix typo Co-authored-by: K.B.Dharun Krishna --- data/com.github.geigi.cozy.appdata.xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/data/com.github.geigi.cozy.appdata.xml b/data/com.github.geigi.cozy.appdata.xml index bb673468..3452e6dd 100644 --- a/data/com.github.geigi.cozy.appdata.xml +++ b/data/com.github.geigi.cozy.appdata.xml @@ -54,7 +54,7 @@

- After almost two years, a new version of Cozy is finally here! This release brings an updated user interface along with numerous bug fixes and improvemed performance. + After almost two years, a new version of Cozy is finally here! This release brings an updated user interface along with numerous bug fixes and improved performance.

The user interface has been ported to GTK4 and Libadwaita. Thus, Cozy benefits from the new style sheet, automatic dark mode, and utilizes the latest and greatest UI elements throughout the application. Many thanks to Benedek Dévényi and grahamvh! From e137f40657deaf22c6563d17d152789b537bb122 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Benedek=20D=C3=A9v=C3=A9nyi?= Date: Fri, 5 Jan 2024 13:01:38 +0100 Subject: [PATCH 32/41] Improve wording Co-authored-by: K.B.Dharun Krishna --- data/com.github.geigi.cozy.appdata.xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/data/com.github.geigi.cozy.appdata.xml b/data/com.github.geigi.cozy.appdata.xml index 3452e6dd..2aaf4dbe 100644 --- a/data/com.github.geigi.cozy.appdata.xml +++ b/data/com.github.geigi.cozy.appdata.xml @@ -61,7 +61,7 @@

Other changes include:

    -
  • Much improved mobile support
  • +
  • Improved mobile support
  • Smaller visual refinements to match the state of the art of GNOME apps
  • Dozens of bug fixes and performance improvements
  • Significant cleanup and improvements to the codebase
  • From 76e2d0914cede0a8ac11e4c74b9837aad312de1b Mon Sep 17 00:00:00 2001 From: rdbende Date: Sun, 7 Jan 2024 13:25:31 +0100 Subject: [PATCH 33/41] Always show the filter sidebar on desktop --- cozy/ui/headerbar.py | 32 +++++++++++++++++++++++++++----- 1 file changed, 27 insertions(+), 5 deletions(-) diff --git a/cozy/ui/headerbar.py b/cozy/ui/headerbar.py index f5a01e21..7bc71f24 100644 --- a/cozy/ui/headerbar.py +++ b/cozy/ui/headerbar.py @@ -6,7 +6,7 @@ from cozy.ui.widgets.progress_popover import ProgressPopover from cozy.view_model.headerbar_view_model import HeaderbarViewModel, HeaderBarState -from gi.repository import Adw, Gtk +from gi.repository import Adw, Gtk, GObject log = logging.getLogger("Headerbar") @@ -48,6 +48,8 @@ def __init__(self, main_window_builder: Gtk.Builder): self._connect_view_model() self._connect_widgets() + self._set_show_sidebar_button_visible() + def _connect_view_model(self): self._headerbar_view_model.bind_to("state", self._on_state_changed) self._headerbar_view_model.bind_to("work_progress", self._on_work_progress_changed) @@ -58,19 +60,39 @@ def _connect_widgets(self): self.split_view.connect("notify::show-sidebar", self._on_sidebar_toggle) self.show_sidebar_button.connect("notify::active", self._on_sidebar_toggle) self.mobile_view_switcher.connect("notify::reveal", self._on_mobile_view) - self.sort_stack.connect("notify::visible-child", self._on_sort_stack_changed) + self.sort_stack.connect("notify::visible-child", self._set_show_sidebar_button_visible) + + self.mobile_view_switcher.bind_property( + "reveal", self.split_view, "collapsed", GObject.BindingFlags.SYNC_CREATE + ) - def _on_sort_stack_changed(self, widget, _): - page = widget.props.visible_child_name + def _on_mobile(self) -> bool: + return self.mobile_view_switcher.props.reveal - self.show_sidebar_button.set_visible(page != "recent") + def _set_show_sidebar_button_visible(self, *_): + page = self.sort_stack.props.visible_child_name + + if not self._on_mobile(): + self.show_sidebar_button.set_visible(False) + self.split_view.set_collapsed(False) + self.split_view.set_show_sidebar(page != "recent") + return + + if self.mobile_view_switcher.props.reveal: + self.show_sidebar_button.set_visible(page != "recent") + else: + self.show_sidebar_button.set_visible(False) def _on_mobile_view(self, widget, _): + page = self.sort_stack.props.visible_child_name + if widget.props.reveal: self.headerbar.set_title_widget(Adw.WindowTitle(title="Cozy")) else: self.headerbar.set_title_widget(self.view_switcher) + self._set_show_sidebar_button_visible() + def _on_sidebar_toggle(self, widget, param): show_sidebar = widget.get_property(param.name) From d95cae0e0abace2324bc616913888ac462d6f847 Mon Sep 17 00:00:00 2001 From: rdbende Date: Sun, 7 Jan 2024 18:54:03 +0100 Subject: [PATCH 34/41] This was ported incorrectly --- cozy/ui/widgets/seek_bar.py | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/cozy/ui/widgets/seek_bar.py b/cozy/ui/widgets/seek_bar.py index 38114297..f901d510 100644 --- a/cozy/ui/widgets/seek_bar.py +++ b/cozy/ui/widgets/seek_bar.py @@ -35,8 +35,7 @@ def __init__(self, **kwargs): click_gesture.connect("released", self._on_progress_scale_release) keyboard_controller = Gtk.EventControllerKey() - keyboard_controller.connect("key-pressed", self._on_progress_scale_press) - keyboard_controller.connect("key-released", self._on_progress_scale_release) + keyboard_controller.connect("key-pressed", self._on_progress_key_pressed) self.progress_scale.add_controller(keyboard_controller) @property From 68e2924b7f2d191c2b945cee2457e178b6fbedb9 Mon Sep 17 00:00:00 2001 From: rdbende Date: Fri, 19 Jan 2024 22:47:42 +0100 Subject: [PATCH 35/41] 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 36/41] 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 } From d639fa44439a8e38049e056cf98f78863c539b6c Mon Sep 17 00:00:00 2001 From: rdbende Date: Sat, 20 Jan 2024 18:05:09 +0100 Subject: [PATCH 37/41] Replace flake8 checks with Ruff, and make it fail on error --- .github/workflows/build.yml | 24 ------------------------ .github/workflows/checks.yml | 16 ++++++++++++++++ .github/workflows/tests.yml | 18 ++++++++++++++++++ pyproject.toml | 6 +++++- 4 files changed, 39 insertions(+), 25 deletions(-) delete mode 100644 .github/workflows/build.yml create mode 100644 .github/workflows/checks.yml create mode 100644 .github/workflows/tests.yml diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml deleted file mode 100644 index 40af8faa..00000000 --- a/.github/workflows/build.yml +++ /dev/null @@ -1,24 +0,0 @@ -name: Unit Testing - -on: [push, pull_request] - -jobs: - test: - - runs-on: ubuntu-latest - container: - image: ghcr.io/geigi/cozy-ci:main - - steps: - - uses: actions/checkout@v4 - - - name: Lint with flake8 - run: | - # stop the build if there are Python syntax errors or undefined names - flake8 . --count --select=E9,F63,F7,F82 --show-source --statistics --builtins="_" - # exit-zero treats all errors as warnings. The GitHub editor is 127 chars wide - flake8 . --count --exit-zero --max-complexity=10 --max-line-length=127 --statistics --builtins="_" - - - name: Test with pytest - run: | - pytest diff --git a/.github/workflows/checks.yml b/.github/workflows/checks.yml new file mode 100644 index 00000000..a7c4f284 --- /dev/null +++ b/.github/workflows/checks.yml @@ -0,0 +1,16 @@ +name: Checks + +on: + push: + branches: + - "main" + pull_request: + +jobs: + ruff: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v4 + - uses: chartboost/ruff-action@v1 + with: + args: --output-format github \ No newline at end of file diff --git a/.github/workflows/tests.yml b/.github/workflows/tests.yml new file mode 100644 index 00000000..9f7d14b4 --- /dev/null +++ b/.github/workflows/tests.yml @@ -0,0 +1,18 @@ +name: Tests + +on: + push: + branches: + - "main" + pull_request: + +jobs: + test: + runs-on: ubuntu-latest + container: + image: ghcr.io/geigi/cozy-ci:main + + steps: + - uses: actions/checkout@v4 + run: | + pytest diff --git a/pyproject.toml b/pyproject.toml index cd6386ac..a019a68d 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -5,4 +5,8 @@ skip-magic-trailing-comma = true [tool.isort] line_length = 100 profile = "black" -multi_line_output = 3 \ No newline at end of file +multi_line_output = 3 + +[tool.ruff] +line-length = 100 +builtins = ["_"] \ No newline at end of file From 782f0b9969d38fb547cf15ac8dec2abd9575f1bc Mon Sep 17 00:00:00 2001 From: rdbende Date: Sat, 20 Jan 2024 18:37:37 +0100 Subject: [PATCH 38/41] Select some useful checks --- pyproject.toml | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/pyproject.toml b/pyproject.toml index a019a68d..88a50fdb 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -9,4 +9,6 @@ multi_line_output = 3 [tool.ruff] line-length = 100 -builtins = ["_"] \ No newline at end of file +builtins = ["_"] +output-format = "github" +extend-select = ["B", "SIM", "PIE", "C4", "GT", "LOG"] From 5fbd483b26aa41ff8c43a7b5e948e3e89acfbf54 Mon Sep 17 00:00:00 2001 From: rdbende Date: Sat, 20 Jan 2024 18:37:55 +0100 Subject: [PATCH 39/41] Don't make the check fails for now --- .github/workflows/checks.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/checks.yml b/.github/workflows/checks.yml index a7c4f284..d9d9b846 100644 --- a/.github/workflows/checks.yml +++ b/.github/workflows/checks.yml @@ -13,4 +13,4 @@ jobs: - uses: actions/checkout@v4 - uses: chartboost/ruff-action@v1 with: - args: --output-format github \ No newline at end of file + args: --exit-zero \ No newline at end of file From aab9d1fca533cf7f2bd94d749c654d98560eda22 Mon Sep 17 00:00:00 2001 From: rdbende Date: Sat, 20 Jan 2024 18:43:14 +0100 Subject: [PATCH 40/41] It's INT in Ruff --- pyproject.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pyproject.toml b/pyproject.toml index 88a50fdb..10591275 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -11,4 +11,4 @@ multi_line_output = 3 line-length = 100 builtins = ["_"] output-format = "github" -extend-select = ["B", "SIM", "PIE", "C4", "GT", "LOG"] +extend-select = ["B", "SIM", "PIE", "C4", "INT", "LOG"] From d628e27e699fd39bf6aef75cf381dc8d30839ba0 Mon Sep 17 00:00:00 2001 From: geigi Date: Sat, 3 Feb 2024 10:41:03 +0100 Subject: [PATCH 41/41] fix pytest ci integration --- .github/workflows/tests.yml | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/.github/workflows/tests.yml b/.github/workflows/tests.yml index 9f7d14b4..3bd89da0 100644 --- a/.github/workflows/tests.yml +++ b/.github/workflows/tests.yml @@ -4,6 +4,7 @@ on: push: branches: - "main" + - "master" pull_request: jobs: @@ -14,5 +15,6 @@ jobs: steps: - uses: actions/checkout@v4 - run: | - pytest + + - name: Run pytest + run: pytest