Rename CalDAV lists, sync CalDAV colors

pull/935/head
Alex Baker 6 years ago
parent 13d5df230b
commit 3e94a8c069

@ -127,7 +127,7 @@ public abstract class BaseCaldavCalendarSettingsActivity extends BaseListSetting
showProgressIndicator(); showProgressIndicator();
updateNameAndColor(caldavAccount, caldavCalendar, name, selectedColor); updateNameAndColor(caldavAccount, caldavCalendar, name, selectedColor);
} else if (iconChanged()) { } else if (iconChanged()) {
updateAccount(); updateCalendar();
} else { } else {
finish(); finish();
} }
@ -197,7 +197,7 @@ public abstract class BaseCaldavCalendarSettingsActivity extends BaseListSetting
finish(); finish();
} }
protected void updateAccount() { protected void updateCalendar() {
caldavCalendar.setName(getNewName()); caldavCalendar.setName(getNewName());
caldavCalendar.setColor(selectedColor); caldavCalendar.setColor(selectedColor);
caldavCalendar.setIcon(selectedIcon); caldavCalendar.setIcon(selectedIcon);

@ -1,7 +1,6 @@
package org.tasks.caldav; package org.tasks.caldav;
import android.os.Bundle; import android.os.Bundle;
import android.view.View;
import androidx.lifecycle.ViewModelProviders; import androidx.lifecycle.ViewModelProviders;
import javax.inject.Inject; import javax.inject.Inject;
import org.tasks.R; import org.tasks.R;
@ -15,6 +14,7 @@ public class CaldavCalendarSettingsActivity extends BaseCaldavCalendarSettingsAc
private CreateCalendarViewModel createCalendarViewModel; private CreateCalendarViewModel createCalendarViewModel;
private DeleteCalendarViewModel deleteCalendarViewModel; private DeleteCalendarViewModel deleteCalendarViewModel;
private UpdateCalendarViewModel updateCalendarViewModel;
@Override @Override
protected int getLayout() { protected int getLayout() {
@ -25,15 +25,13 @@ public class CaldavCalendarSettingsActivity extends BaseCaldavCalendarSettingsAc
protected void onCreate(Bundle savedInstanceState) { protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState); super.onCreate(savedInstanceState);
if (!isNew()) {
nameLayout.setVisibility(View.GONE);
}
createCalendarViewModel = ViewModelProviders.of(this).get(CreateCalendarViewModel.class); createCalendarViewModel = ViewModelProviders.of(this).get(CreateCalendarViewModel.class);
deleteCalendarViewModel = ViewModelProviders.of(this).get(DeleteCalendarViewModel.class); deleteCalendarViewModel = ViewModelProviders.of(this).get(DeleteCalendarViewModel.class);
updateCalendarViewModel = ViewModelProviders.of(this).get(UpdateCalendarViewModel.class);
createCalendarViewModel.observe(this, this::createSuccessful, this::requestFailed); createCalendarViewModel.observe(this, this::createSuccessful, this::requestFailed);
deleteCalendarViewModel.observe(this, this::onDeleted, this::requestFailed); deleteCalendarViewModel.observe(this, this::onDeleted, this::requestFailed);
updateCalendarViewModel.observe(this, ignored -> updateCalendar(), this::requestFailed);
} }
@Override @Override
@ -43,13 +41,13 @@ public class CaldavCalendarSettingsActivity extends BaseCaldavCalendarSettingsAc
@Override @Override
protected void createCalendar(CaldavAccount caldavAccount, String name, int color) { protected void createCalendar(CaldavAccount caldavAccount, String name, int color) {
createCalendarViewModel.createCalendar(client, caldavAccount, name); createCalendarViewModel.createCalendar(client, caldavAccount, name, color);
} }
@Override @Override
protected void updateNameAndColor( protected void updateNameAndColor(
CaldavAccount account, CaldavCalendar calendar, String name, int color) { CaldavAccount account, CaldavCalendar calendar, String name, int color) {
updateAccount(); updateCalendarViewModel.updateCalendar(client, account, calendar, name, color);
} }
@Override @Override

@ -16,6 +16,7 @@ import at.bitfire.dav4jvm.Response.HrefRelation;
import at.bitfire.dav4jvm.XmlUtils; import at.bitfire.dav4jvm.XmlUtils;
import at.bitfire.dav4jvm.exception.DavException; import at.bitfire.dav4jvm.exception.DavException;
import at.bitfire.dav4jvm.exception.HttpException; import at.bitfire.dav4jvm.exception.HttpException;
import at.bitfire.dav4jvm.property.CalendarColor;
import at.bitfire.dav4jvm.property.CalendarHomeSet; import at.bitfire.dav4jvm.property.CalendarHomeSet;
import at.bitfire.dav4jvm.property.CurrentUserPrincipal; import at.bitfire.dav4jvm.property.CurrentUserPrincipal;
import at.bitfire.dav4jvm.property.DisplayName; import at.bitfire.dav4jvm.property.DisplayName;
@ -207,6 +208,7 @@ public class CaldavClient {
DisplayName.NAME, DisplayName.NAME,
SupportedCalendarComponentSet.NAME, SupportedCalendarComponentSet.NAME,
GetCTag.NAME, GetCTag.NAME,
CalendarColor.NAME,
SyncToken.NAME SyncToken.NAME
}, },
responses); responses);
@ -234,16 +236,57 @@ public class CaldavClient {
new DavResource(httpClient, httpUrl).delete(null, response -> null); new DavResource(httpClient, httpUrl).delete(null, response -> null);
} }
String makeCollection(String displayName) String makeCollection(String displayName, int color)
throws IOException, XmlPullParserException, HttpException { throws IOException, XmlPullParserException, HttpException {
DavResource davResource = DavResource davResource =
new DavResource(httpClient, httpUrl.resolve(UUIDHelper.newUUID() + "/")); new DavResource(httpClient, httpUrl.resolve(UUIDHelper.newUUID() + "/"));
String mkcolString = getMkcolString(displayName); String mkcolString = getMkcolString(displayName, color);
davResource.mkCol(mkcolString, response -> null); davResource.mkCol(mkcolString, response -> null);
return davResource.getLocation().toString(); return davResource.getLocation().toString();
} }
private String getMkcolString(String displayName) throws IOException, XmlPullParserException { String updateCollection(String displayName, int color)
throws IOException, XmlPullParserException, HttpException {
PatchableDavResource davResource = new PatchableDavResource(httpClient, httpUrl);
davResource.propPatch(getPropPatchString(displayName, color), response -> null);
return davResource.getLocation().toString();
}
private String getPropPatchString(String displayName, int color)
throws IOException, XmlPullParserException {
XmlPullParserFactory xmlPullParserFactory = XmlPullParserFactory.newInstance();
XmlSerializer xml = xmlPullParserFactory.newSerializer();
StringWriter stringWriter = new StringWriter();
xml.setOutput(stringWriter);
xml.startDocument("UTF-8", null);
xml.setPrefix("", NS_WEBDAV);
xml.setPrefix("CAL", NS_CALDAV);
xml.setPrefix("CARD", NS_CARDDAV);
xml.startTag(NS_WEBDAV, "propertyupdate");
xml.startTag(XmlUtils.NS_WEBDAV, "set");
xml.startTag(XmlUtils.NS_WEBDAV, "prop");
setDisplayName(xml, displayName);
if (color != 0) {
setColor(xml, color);
}
xml.endTag(XmlUtils.NS_WEBDAV, "prop");
xml.endTag(XmlUtils.NS_WEBDAV, "set");
if (color == 0) {
xml.startTag(XmlUtils.NS_WEBDAV, "remove");
xml.startTag(XmlUtils.NS_WEBDAV, "prop");
xml.startTag(XmlUtils.NS_APPLE_ICAL, "calendar-color");
xml.endTag(XmlUtils.NS_APPLE_ICAL, "calendar-color");
xml.endTag(XmlUtils.NS_WEBDAV, "prop");
xml.endTag(XmlUtils.NS_WEBDAV, "remove");
}
xml.endTag(XmlUtils.NS_WEBDAV, "propertyupdate");
xml.endDocument();
xml.flush();
return stringWriter.toString();
}
private String getMkcolString(String displayName, int color) throws IOException, XmlPullParserException {
XmlPullParserFactory xmlPullParserFactory = XmlPullParserFactory.newInstance(); XmlPullParserFactory xmlPullParserFactory = XmlPullParserFactory.newInstance();
XmlSerializer xml = xmlPullParserFactory.newSerializer(); XmlSerializer xml = xmlPullParserFactory.newSerializer();
StringWriter stringWriter = new StringWriter(); StringWriter stringWriter = new StringWriter();
@ -261,9 +304,10 @@ public class CaldavClient {
xml.startTag(XmlUtils.NS_CALDAV, "calendar"); xml.startTag(XmlUtils.NS_CALDAV, "calendar");
xml.endTag(XmlUtils.NS_CALDAV, "calendar"); xml.endTag(XmlUtils.NS_CALDAV, "calendar");
xml.endTag(XmlUtils.NS_WEBDAV, "resourcetype"); xml.endTag(XmlUtils.NS_WEBDAV, "resourcetype");
xml.startTag(XmlUtils.NS_WEBDAV, "displayname"); setDisplayName(xml, displayName);
xml.text(displayName); if (color != 0) {
xml.endTag(XmlUtils.NS_WEBDAV, "displayname"); setColor(xml, color);
}
xml.startTag(XmlUtils.NS_CALDAV, "supported-calendar-component-set"); xml.startTag(XmlUtils.NS_CALDAV, "supported-calendar-component-set");
xml.startTag(XmlUtils.NS_CALDAV, "comp"); xml.startTag(XmlUtils.NS_CALDAV, "comp");
xml.attribute(null, "name", "VTODO"); xml.attribute(null, "name", "VTODO");
@ -277,6 +321,18 @@ public class CaldavClient {
return stringWriter.toString(); return stringWriter.toString();
} }
private void setDisplayName(XmlSerializer xml, String name) throws IOException {
xml.startTag(XmlUtils.NS_WEBDAV, "displayname");
xml.text(name);
xml.endTag(XmlUtils.NS_WEBDAV, "displayname");
}
private void setColor(XmlSerializer xml, int color) throws IOException {
xml.startTag(XmlUtils.NS_APPLE_ICAL, "calendar-color");
xml.text(String.format("#%06X%02X", color & 0xFFFFFF, color >>> 24));
xml.endTag(XmlUtils.NS_APPLE_ICAL, "calendar-color");
}
OkHttpClient getHttpClient() { OkHttpClient getHttpClient() {
return httpClient; return httpClient;
} }

@ -20,6 +20,7 @@ import at.bitfire.dav4jvm.exception.DavException;
import at.bitfire.dav4jvm.exception.HttpException; import at.bitfire.dav4jvm.exception.HttpException;
import at.bitfire.dav4jvm.exception.ServiceUnavailableException; import at.bitfire.dav4jvm.exception.ServiceUnavailableException;
import at.bitfire.dav4jvm.exception.UnauthorizedException; import at.bitfire.dav4jvm.exception.UnauthorizedException;
import at.bitfire.dav4jvm.property.CalendarColor;
import at.bitfire.dav4jvm.property.CalendarData; import at.bitfire.dav4jvm.property.CalendarData;
import at.bitfire.dav4jvm.property.DisplayName; import at.bitfire.dav4jvm.property.DisplayName;
import at.bitfire.dav4jvm.property.GetCTag; import at.bitfire.dav4jvm.property.GetCTag;
@ -155,13 +156,22 @@ public class CaldavSynchronizer {
String url = resource.getHref().toString(); String url = resource.getHref().toString();
CaldavCalendar calendar = caldavDao.getCalendarByUrl(account.getUuid(), url); CaldavCalendar calendar = caldavDao.getCalendarByUrl(account.getUuid(), url);
String remoteName = resource.get(DisplayName.class).getDisplayName();
CalendarColor calendarColor = resource.get(CalendarColor.class);
int color = calendarColor == null ? 0 : calendarColor.getColor();
if (calendar == null) { if (calendar == null) {
calendar = new CaldavCalendar(); calendar = new CaldavCalendar();
calendar.setName(resource.get(DisplayName.class).getDisplayName()); calendar.setName(remoteName);
calendar.setAccount(account.getUuid()); calendar.setAccount(account.getUuid());
calendar.setUrl(url); calendar.setUrl(url);
calendar.setUuid(UUIDHelper.newUUID()); calendar.setUuid(UUIDHelper.newUUID());
calendar.setColor(color);
caldavDao.insert(calendar); caldavDao.insert(calendar);
} else if (!calendar.getName().equals(remoteName) || calendar.getColor() != color) {
calendar.setColor(color);
calendar.setName(remoteName);
caldavDao.update(calendar);
localBroadcastManager.broadcastRefreshList();
} }
sync(calendar, resource, caldavClient.getHttpClient()); sync(calendar, resource, caldavClient.getHttpClient());
} }
@ -183,14 +193,6 @@ public class CaldavSynchronizer {
HttpUrl httpUrl = resource.getHref(); HttpUrl httpUrl = resource.getHref();
pushLocalChanges(caldavCalendar, httpClient, httpUrl); pushLocalChanges(caldavCalendar, httpClient, httpUrl);
String remoteName = resource.get(DisplayName.class).getDisplayName();
if (!caldavCalendar.getName().equals(remoteName)) {
Timber.d("%s -> %s", caldavCalendar.getName(), remoteName);
caldavCalendar.setName(remoteName);
caldavDao.update(caldavCalendar);
localBroadcastManager.broadcastRefreshList();
}
SyncToken syncToken = resource.get(SyncToken.class); SyncToken syncToken = resource.get(SyncToken.class);
GetCTag ctag = resource.get(GetCTag.class); GetCTag ctag = resource.get(GetCTag.class);
@Nullable String remoteCtag = null; @Nullable String remoteCtag = null;

@ -5,7 +5,7 @@ import org.tasks.ui.CompletableViewModel;
@SuppressWarnings("WeakerAccess") @SuppressWarnings("WeakerAccess")
public class CreateCalendarViewModel extends CompletableViewModel<String> { public class CreateCalendarViewModel extends CompletableViewModel<String> {
void createCalendar(CaldavClient client, CaldavAccount account, String name) { void createCalendar(CaldavClient client, CaldavAccount account, String name, int color) {
run(() -> client.forAccount(account).makeCollection(name)); run(() -> client.forAccount(account).makeCollection(name, color));
} }
} }

@ -0,0 +1,76 @@
package org.tasks.caldav
import at.bitfire.dav4jvm.DavResource
import at.bitfire.dav4jvm.DavResource.Companion.MAX_REDIRECTS
import at.bitfire.dav4jvm.exception.*
import okhttp3.HttpUrl
import okhttp3.OkHttpClient
import okhttp3.Request
import okhttp3.RequestBody
import okhttp3.Response
import java.io.IOException
import java.net.HttpURLConnection
import java.util.logging.Logger
class PatchableDavResource : DavResource {
constructor(client: OkHttpClient, url: HttpUrl, log: Logger) : super(client, url, log)
constructor(client: OkHttpClient, url: HttpUrl) : super(client, url)
/**
* Sends a PROPPATCH request to this resource. Follows up to [MAX_REDIRECTS] redirects.
*
* @throws IOException on I/O error
* @throws HttpException on HTTP error
*/
@Throws(IOException::class, HttpException::class)
fun propPatch(xmlBody: String?, callback: (response: Response) -> Unit) {
val rqBody = if (xmlBody != null) RequestBody.create(MIME_XML, xmlBody) else null
followRedirects {
httpClient.newCall(Request.Builder()
.method("PROPPATCH", rqBody)
.url(location)
.build()).execute()
}.use { response ->
checkStatus(response)
callback(response)
}
}
/**
* Checks the status from an HTTP response and throws an exception in case of an error.
*
* @throws HttpException in case of an HTTP error
*/
private fun checkStatus(response: Response) =
checkStatus(response.code(), response.message(), response)
/**
* Checks the status from an HTTP response and throws an exception in case of an error.
*
* @throws HttpException (with XML error names, if available) in case of an HTTP error
*/
private fun checkStatus(code: Int, message: String?, response: Response?) {
if (code / 100 == 2)
// everything OK
return
throw when (code) {
HttpURLConnection.HTTP_UNAUTHORIZED ->
if (response != null) UnauthorizedException(response) else UnauthorizedException(message)
HttpURLConnection.HTTP_FORBIDDEN ->
if (response != null) ForbiddenException(response) else ForbiddenException(message)
HttpURLConnection.HTTP_NOT_FOUND ->
if (response != null) NotFoundException(response) else NotFoundException(message)
HttpURLConnection.HTTP_CONFLICT ->
if (response != null) ConflictException(response) else ConflictException(message)
HttpURLConnection.HTTP_PRECON_FAILED ->
if (response != null) PreconditionFailedException(response) else PreconditionFailedException(message)
HttpURLConnection.HTTP_UNAVAILABLE ->
if (response != null) ServiceUnavailableException(response) else ServiceUnavailableException(message)
else ->
if (response != null) HttpException(response) else HttpException(code, message)
}
}
}

@ -0,0 +1,13 @@
package org.tasks.caldav;
import org.tasks.data.CaldavAccount;
import org.tasks.data.CaldavCalendar;
import org.tasks.ui.CompletableViewModel;
@SuppressWarnings("WeakerAccess")
public class UpdateCalendarViewModel extends CompletableViewModel<String> {
void updateCalendar(
CaldavClient client, CaldavAccount account, CaldavCalendar calendar, String name, int color) {
run(() -> client.forCalendar(account, calendar).updateCollection(name, color));
}
}

@ -25,7 +25,7 @@ public class EteSyncCalendarSettingsActivity extends BaseCaldavCalendarSettingsA
createCalendarViewModel.observe(this, this::createSuccessful, this::requestFailed); createCalendarViewModel.observe(this, this::createSuccessful, this::requestFailed);
deleteCalendarViewModel.observe(this, this::onDeleted, this::requestFailed); deleteCalendarViewModel.observe(this, this::onDeleted, this::requestFailed);
updateCalendarViewModel.observe(this, uid -> updateAccount(), this::requestFailed); updateCalendarViewModel.observe(this, uid -> updateCalendar(), this::requestFailed);
} }
@Override @Override

Loading…
Cancel
Save