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 @@
+
+
+
+
+
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