diff --git a/src/preferences.py b/src/preferences.py index 2b63d0a5..61675d5f 100644 --- a/src/preferences.py +++ b/src/preferences.py @@ -20,6 +20,7 @@ # OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE # SOFTWARE. from gi.repository import Adw, Gtk +from .sync import Sync from .utils import GSettings @@ -36,6 +37,7 @@ class PreferencesWindow(Adw.PreferencesWindow): sync_username: Adw.EntryRow = Gtk.Template.Child() sync_password: Adw.EntryRow = Gtk.Template.Child() sync_token: Adw.EntryRow = Gtk.Template.Child() + test_connection_row: Adw.ActionRow = Gtk.Template.Child() selected_provider = 0 @@ -65,13 +67,22 @@ def setup_sync(self): self.sync_url.set_visible(selected != 3 and selected != 0) self.sync_username.set_visible(selected != 3 and selected != 0) self.sync_password.set_visible(selected != 3 and selected != 0) - self.window.sync_btn.set_visible(selected > 0) + + self.test_connection_row.set_visible(selected > 0) + + # --- Template handlers --- # @Gtk.Template.Callback() def on_sync_provider_selected(self, *_) -> None: self.setup_sync() - # --- Template handlers --- # + @Gtk.Template.Callback() + def on_test_connection_btn_clicked(self, btn): + res: bool = Sync.test_connection() + msg = _("Connected") if res else _("Can't connect") # pyright:ignore + toast: Adw.Toast = Adw.Toast(title=msg, timeout=2) + self.add_toast(toast) + self.window.sync_btn.set_visible(res) @Gtk.Template.Callback() def on_theme_change(self, btn: Gtk.Button) -> None: diff --git a/src/res/ui/preferences.ui b/src/res/ui/preferences.ui index b3c5bdd9..7968ec4e 100644 --- a/src/res/ui/preferences.ui +++ b/src/res/ui/preferences.ui @@ -133,6 +133,20 @@ + + + + Test Connection + + + Test + center + + + + + + diff --git a/src/res/ui/window.ui b/src/res/ui/window.ui index 438767f1..c778c737 100644 --- a/src/res/ui/window.ui +++ b/src/res/ui/window.ui @@ -257,6 +257,7 @@ emblem-synchronizing-symbolic + false Sync/Fetch Tasks diff --git a/src/sync.py b/src/sync.py index 8d71ea04..9f73ad37 100644 --- a/src/sync.py +++ b/src/sync.py @@ -1,5 +1,5 @@ from gi.repository import Adw, GLib -from caldav import Calendar, DAVClient +from caldav import Calendar, DAVClient, Principal from .utils import GSettings, Log, TaskUtils, UserData, threaded @@ -8,7 +8,7 @@ class Sync: window: Adw.ApplicationWindow = None @classmethod - def init(self, window: Adw.ApplicationWindow = None) -> None: + def init(self, window: Adw.ApplicationWindow = None, testing: bool = False) -> None: Log.debug("Initialize sync provider") if window: self.window = window @@ -17,8 +17,7 @@ def init(self, window: Adw.ApplicationWindow = None) -> None: Log.info("Sync disabled") self.window.sync_btn.set_visible(False) case 1: - self.provider = SyncProviderCalDAV("Nextcloud", self.window) - self.window.sync_btn.set_visible(True) + self.provider = SyncProviderCalDAV("Nextcloud", self.window, testing) @classmethod @threaded @@ -33,76 +32,75 @@ def sync(self, fetch: bool = False) -> None: if self.provider and self.provider.can_sync: self.provider.sync(fetch) + @classmethod + def test_connection(self) -> bool: + self.init(testing=True) + return self.provider.can_sync + class SyncProviderCalDAV: can_sync: bool = False calendar: Calendar = None window = None - def __init__(self, name: str, window: Adw.ApplicationWindow) -> None: + def __init__(self, name: str, window: Adw.ApplicationWindow, testing: bool) -> None: Log.info(f"Initialize {name} sync provider") self.name = name self.window = window + self.testing = testing # Only for connection test + + if not self._check_credentials(): + return + + self._check_url() + return self._connect() + + def _check_credentials(self) -> bool: self.url = GSettings.get("sync-url") self.username = GSettings.get("sync-username") self.password = GSettings.get("sync-password") if self.url == "" or self.username == "" or self.password == "": Log.error(f"Not all {self.name} credentials provided") - self.window.add_toast( - _( # pyright:ignore - "Not all sync credentials provided. Please check settings." + if not self.testing: + self.window.add_toast( + _( # pyright:ignore + "Not all sync credentials provided. Please check settings." + ) ) - ) self.window.sync_btn.set_visible(False) - return + return False - self._set_url() + self.window.sync_btn.set_visible(True) + return True + + def _check_url(self) -> None: + if not self.url.startswith("http"): + self.url = "http://" + self.url + GSettings.set("sync-url", "s", self.url) + if self.name == "Nextcloud": + self.url = f"{self.url}/remote.php/dav/" + def _connect(self) -> bool: with DAVClient( url=self.url, username=self.username, password=self.password ) as client: try: - supports_caldav = client.check_cdav_support() - if not supports_caldav: - Log.error(f"Server doesn't support CalDAV. Maybe wrong adress?") - self.window.add_toast( - _( # pyright:ignore - "Server doesn't support CalDAV. Maybe wrong adress?" - ) - ) - return - principal = client.principal() Log.info(f"Connected to {self.name} CalDAV server at '{self.url}'") self.can_sync = True + self._setup_calendar(principal) + self.window.sync_btn.set_visible(True) except: Log.error(f"Can't connect to {self.name} CalDAV server at '{self.url}'") - self.window.add_toast(_("Sync is Disabled")) # pyright:ignore + if not self.testing: + self.window.add_toast( + _("Can't connect to CalDAV server at:") # pyright:ignore + + " " + + self.url + ) self.window.sync_btn.set_visible(False) - return - # Get calendars - calendars = principal.calendars() - # Check if Errands calendar exists - errands_cal_exists: bool = False - for cal in calendars: - if cal.name == "Errands": - self.calendar = cal - errands_cal_exists = True - # Create one if not - if not errands_cal_exists: - Log.debug(f"Create new calendar 'Errands' on {self.name}") - self.calendar = principal.make_calendar( - "Errands", supported_calendar_component_set=["VTODO"] - ) - - def _set_url(self): - if not self.url.startswith("http"): - self.url = "http://" + self.url - GSettings.set("sync-url", "s", self.url) - if self.name == "Nextcloud": - self.url = f"{self.url}/remote.php/dav/" def _get_tasks(self) -> list[dict]: """ @@ -182,6 +180,22 @@ def _fetch(self): UserData.set(data) + def _setup_calendar(self, principal: Principal) -> None: + # Get calendars + calendars: list[Calendar] = principal.calendars() + # Check if Errands calendar exists + errands_cal_exists: bool = False + for cal in calendars: + if cal.name == "Errands": + self.calendar = cal + errands_cal_exists = True + # Create one if not + if not errands_cal_exists: + Log.debug(f"Create new calendar 'Errands' on {self.name}") + self.calendar = principal.make_calendar( + "Errands", supported_calendar_component_set=["VTODO"] + ) + def sync(self, fetch: bool) -> None: """ Sync local tasks with provider