Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 2 additions & 1 deletion core/Enum.vala
Original file line number Diff line number Diff line change
Expand Up @@ -753,7 +753,8 @@ public enum Appearance {
public enum SyncErrorType {
AUTH_EXPIRED,
SERVER_ERROR,
NETWORK_ERROR
NETWORK_ERROR,
CERTIFICATE_ERROR
}

public class SyncStatus : Object {
Expand Down
12 changes: 3 additions & 9 deletions core/Objects/Source.vala
Original file line number Diff line number Diff line change
Expand Up @@ -176,7 +176,9 @@ public class Objects.Source : Objects.BaseObject {

public async void delete_source () {
// Remove server_timeout
remove_sync_server ();
if (sync_server) {
remove_sync_server ();
}

// Remove DB
yield Services.Store.instance ().delete_source (this);
Expand Down Expand Up @@ -374,7 +376,6 @@ public class Objects.SourceCalDAVData : Objects.SourceData {
}

public CalDAVType caldav_type { get; set; default = CalDAVType.GENERIC; }
public bool ignore_ssl { get; set; default = false; }

public SourceCalDAVData.from_json (string json) {
Json.Parser parser = new Json.Parser ();
Expand Down Expand Up @@ -411,10 +412,6 @@ public class Objects.SourceCalDAVData : Objects.SourceData {
caldav_type = CalDAVType.parse (object.get_string_member ("caldav_type"));
}

if (object.has_member ("ignore_ssl")) {
ignore_ssl = object.get_boolean_member ("ignore_ssl");
}

if (object.has_member ("credentials")) {
var decoded = (string) Base64.decode (object.get_string_member ("credentials"));

Expand Down Expand Up @@ -454,9 +451,6 @@ public class Objects.SourceCalDAVData : Objects.SourceData {
builder.set_member_name ("calendar_home_url");
builder.add_string_value (calendar_home_url);

builder.set_member_name ("ignore_ssl");
builder.add_boolean_value (ignore_ssl);

builder.end_object ();

Json.Generator generator = new Json.Generator ();
Expand Down
149 changes: 77 additions & 72 deletions core/Services/CalDAV/CalDAVClient.vala
Original file line number Diff line number Diff line change
Expand Up @@ -22,8 +22,8 @@

public class Services.CalDAV.CalDAVClient : Services.CalDAV.WebDAVClient {

public CalDAVClient (Soup.Session session, string base_url, string username, string password, bool ignore_ssl = false) {
base (session, base_url, username, password, ignore_ssl);
public CalDAVClient (Soup.Session session, string base_url, string username, string password, string source_id) {
base (session, base_url, username, password, source_id);
}


Expand Down Expand Up @@ -120,6 +120,10 @@ public class Services.CalDAV.CalDAVClient : Services.CalDAV.WebDAVClient {
source.display_name = source.caldav_data.user_displayname;
return;
}

if (source.caldav_data.user_displayname == null) {
source.caldav_data.user_displayname = source.caldav_data.username;
}

source.display_name = _ ("CalDAV");
}
Expand Down Expand Up @@ -422,97 +426,98 @@ public class Services.CalDAV.CalDAVClient : Services.CalDAV.WebDAVClient {
project.loading = true;
project.sync_started ();

yield fetch_project_details (project, cancellable);

Services.LogService.get_default ().debug ("CalDAV", "sync_id after fetch_project_details: '%s'".printf (project.sync_id ?? "(null)"));

if (project.sync_id == null || project.sync_id == "") {
Services.LogService.get_default ().warn ("CalDAV", "No sync-token from server, falling back to etag-based sync for '%s'".printf (project.name));
project.loading = false;
project.freeze_update = false;
yield etag_sync_project (project, cancellable);
project.sync_finished ();
return;
}

WebDAVMultiStatus multi_status;
try {
multi_status = yield report (project.calendar_url, xml, "1", cancellable);
} catch (Error e) {
if (e is GLib.IOError.CANCELLED) {
throw e;
}
// sync-collection fails with a 412 Precondition Failed on Vikunja (but it sends a sync-token?)
yield fetch_project_details (project, cancellable);

Services.LogService.get_default ().warn ("CalDAV", "sync-collection failed, falling back to ETag sync: %s".printf (e.message));
project.loading = false;
project.freeze_update = false;
yield etag_sync_project (project, cancellable);
project.sync_finished ();
return;
}
project.freeze_update = true;
Services.LogService.get_default ().debug ("CalDAV", "sync_id after fetch_project_details: '%s'".printf (project.sync_id ?? "(null)"));

foreach (WebDAVResponse response in multi_status.responses ()) {
string? href = response.href;
var url = get_absolute_url (href);
if (project.sync_id == null || project.sync_id == "") {
Services.LogService.get_default ().warn ("CalDAV", "No sync-token from server, falling back to etag-based sync for '%s'".printf (project.name));
yield etag_sync_project (project, cancellable);
project.sync_finished ();
return;
}

if (response.status == Soup.Status.NOT_FOUND) {
Objects.Item ? item = Services.Store.instance ().get_item_by_ical_url (url);
if (item != null) {
Services.Store.instance ().delete_item (item);
WebDAVMultiStatus multi_status;
try {
multi_status = yield report (project.calendar_url, xml, "1", cancellable);
} catch (Error e) {
if (e is GLib.IOError.CANCELLED) {
throw e;
}
// sync-collection fails with a 412 Precondition Failed on Vikunja (but it sends a sync-token?)

continue;
Services.LogService.get_default ().warn ("CalDAV", "sync-collection failed, falling back to ETag sync: %s".printf (e.message));
yield etag_sync_project (project, cancellable);
project.sync_finished ();
return;
}
project.freeze_update = true;

foreach (WebDAVResponse response in multi_status.responses ()) {
string? href = response.href;
var url = get_absolute_url (href);

foreach (WebDAVPropStat propstat in response.propstats ()) {
if (propstat.status == Soup.Status.NOT_FOUND) {
if (response.status == Soup.Status.NOT_FOUND) {
Objects.Item ? item = Services.Store.instance ().get_item_by_ical_url (url);
if (item != null) {
Services.Store.instance ().delete_item (item);
}
} else {
bool has_component_parameter = false;
bool is_vtodo = false;

var getcontenttype = propstat.get_first_prop_with_tagname ("getcontenttype");
if (getcontenttype != null) {
has_component_parameter = getcontenttype.text_content.down ().contains ("component");
is_vtodo = getcontenttype.text_content.down ().contains ("vtodo");
}
continue;
}

if (!has_component_parameter) {
Services.LogService.get_default ().debug ("CalDAV", "No 'component' parameter present in getcontenttype.");
// See https://datatracker.ietf.org/doc/html/rfc5545#section-8.1 -> The component parameter is optional. If it is not present, the iCal data must always be fetched and parsed.
}
foreach (WebDAVPropStat propstat in response.propstats ()) {
if (propstat.status == Soup.Status.NOT_FOUND) {
Objects.Item ? item = Services.Store.instance ().get_item_by_ical_url (url);
if (item != null) {
Services.Store.instance ().delete_item (item);
}
} else {
bool has_component_parameter = false;
bool is_vtodo = false;

var getcontenttype = propstat.get_first_prop_with_tagname ("getcontenttype");
if (getcontenttype != null) {
has_component_parameter = getcontenttype.text_content.down ().contains ("component");
is_vtodo = getcontenttype.text_content.down ().contains ("vtodo");
}

if (!has_component_parameter || is_vtodo) {
var getetag = propstat.get_first_prop_with_tagname ("getetag");
string etag = getetag != null ? getetag.text_content.strip () : "";
if (!has_component_parameter) {
Services.LogService.get_default ().debug ("CalDAV", "No 'component' parameter present in getcontenttype.");
// See https://datatracker.ietf.org/doc/html/rfc5545#section-8.1 -> The component parameter is optional. If it is not present, the iCal data must always be fetched and parsed.
}

if (!has_component_parameter || is_vtodo) {
var getetag = propstat.get_first_prop_with_tagname ("getetag");
string etag = getetag != null ? getetag.text_content.strip () : "";

string vtodo_content = yield get_vtodo_by_url (url, cancellable);
upsert_vtodo_content (project, url, etag, vtodo_content);
string vtodo_content = yield get_vtodo_by_url (url, cancellable);
upsert_vtodo_content (project, url, etag, vtodo_content);
}
}
}
}
}

var sync_token = multi_status.get_first_text_content_by_tag_name ("sync-token");
if (sync_token != null && sync_token != project.sync_id) {
project.sync_id = sync_token;
project.update_local ();
} else if (sync_token == null) {
// Some CalDAV providers do not support sync-token. Keep token empty
// so subsequent syncs always take the ETag fallback path.
project.sync_id = "";
project.update_local ();
}
var sync_token = multi_status.get_first_text_content_by_tag_name ("sync-token");
if (sync_token != null && sync_token != project.sync_id) {
project.sync_id = sync_token;
project.update_local ();
} else if (sync_token == null) {
// Some CalDAV providers do not support sync-token. Keep token empty
// so subsequent syncs always take the ETag fallback path.
project.sync_id = "";
project.update_local ();
}

project.loading = false;
project.freeze_update = false;
project.count_update ();
Services.Store.instance ().update_project (project);
project.count_update ();
Services.Store.instance ().update_project (project);

project.sync_finished ();
} finally {
project.loading = false;
project.freeze_update = false;
}
}


Expand Down
Loading