2015-10-14 22:49:15 +00:00
|
|
|
|
/*
|
|
|
|
|
* Copyright © 2013 – 2015 Ricki Hirner (bitfire web engineering).
|
|
|
|
|
* All rights reserved. This program and the accompanying materials
|
|
|
|
|
* are made available under the terms of the GNU Public License v3.0
|
|
|
|
|
* which accompanies this distribution, and is available at
|
|
|
|
|
* http://www.gnu.org/licenses/gpl.html
|
|
|
|
|
*/
|
|
|
|
|
|
|
|
|
|
package at.bitfire.davdroid.syncadapter;
|
|
|
|
|
|
|
|
|
|
import android.accounts.Account;
|
|
|
|
|
import android.content.ContentValues;
|
|
|
|
|
import android.content.Context;
|
|
|
|
|
import android.content.SyncResult;
|
|
|
|
|
import android.os.Bundle;
|
|
|
|
|
import android.text.TextUtils;
|
|
|
|
|
|
2015-10-15 13:36:55 +00:00
|
|
|
|
import org.apache.commons.codec.Charsets;
|
|
|
|
|
import org.apache.commons.lang3.StringUtils;
|
2015-10-14 22:49:15 +00:00
|
|
|
|
import org.dmfs.provider.tasks.TaskContract.TaskLists;
|
|
|
|
|
|
|
|
|
|
import java.io.ByteArrayInputStream;
|
|
|
|
|
import java.io.IOException;
|
|
|
|
|
import java.io.InputStream;
|
|
|
|
|
import java.nio.charset.Charset;
|
2016-03-16 17:23:52 +00:00
|
|
|
|
import java.util.Calendar;
|
|
|
|
|
import java.util.Date;
|
2015-10-14 22:49:15 +00:00
|
|
|
|
import java.util.HashMap;
|
|
|
|
|
import java.util.LinkedList;
|
|
|
|
|
import java.util.List;
|
2016-02-24 22:08:19 +00:00
|
|
|
|
import java.util.logging.Level;
|
2015-10-14 22:49:15 +00:00
|
|
|
|
|
|
|
|
|
import at.bitfire.dav4android.DavCalendar;
|
|
|
|
|
import at.bitfire.dav4android.DavResource;
|
|
|
|
|
import at.bitfire.dav4android.exception.DavException;
|
|
|
|
|
import at.bitfire.dav4android.exception.HttpException;
|
|
|
|
|
import at.bitfire.dav4android.property.CalendarColor;
|
|
|
|
|
import at.bitfire.dav4android.property.CalendarData;
|
|
|
|
|
import at.bitfire.dav4android.property.DisplayName;
|
|
|
|
|
import at.bitfire.dav4android.property.GetCTag;
|
|
|
|
|
import at.bitfire.dav4android.property.GetContentType;
|
|
|
|
|
import at.bitfire.dav4android.property.GetETag;
|
2016-02-24 22:08:19 +00:00
|
|
|
|
import at.bitfire.davdroid.App;
|
2015-10-14 22:49:15 +00:00
|
|
|
|
import at.bitfire.davdroid.ArrayUtils;
|
|
|
|
|
import at.bitfire.davdroid.Constants;
|
2015-10-16 21:06:35 +00:00
|
|
|
|
import at.bitfire.davdroid.R;
|
2015-10-14 22:49:15 +00:00
|
|
|
|
import at.bitfire.davdroid.resource.LocalResource;
|
|
|
|
|
import at.bitfire.davdroid.resource.LocalTask;
|
|
|
|
|
import at.bitfire.davdroid.resource.LocalTaskList;
|
|
|
|
|
import at.bitfire.ical4android.CalendarStorageException;
|
|
|
|
|
import at.bitfire.ical4android.InvalidCalendarException;
|
|
|
|
|
import at.bitfire.ical4android.Task;
|
|
|
|
|
import at.bitfire.ical4android.TaskProvider;
|
|
|
|
|
import lombok.Cleanup;
|
2016-02-24 22:08:19 +00:00
|
|
|
|
import okhttp3.HttpUrl;
|
|
|
|
|
import okhttp3.MediaType;
|
|
|
|
|
import okhttp3.RequestBody;
|
|
|
|
|
import okhttp3.ResponseBody;
|
2015-10-14 22:49:15 +00:00
|
|
|
|
|
|
|
|
|
public class TasksSyncManager extends SyncManager {
|
|
|
|
|
|
2015-10-15 13:36:55 +00:00
|
|
|
|
protected static final int MAX_MULTIGET = 30;
|
2015-10-14 22:49:15 +00:00
|
|
|
|
|
|
|
|
|
final protected TaskProvider provider;
|
|
|
|
|
|
|
|
|
|
|
2015-10-17 09:33:35 +00:00
|
|
|
|
public TasksSyncManager(Context context, Account account, Bundle extras, String authority, TaskProvider provider, SyncResult result, LocalTaskList taskList) {
|
|
|
|
|
super(Constants.NOTIFICATION_TASK_SYNC, context, account, extras, authority, result);
|
2015-10-14 22:49:15 +00:00
|
|
|
|
this.provider = provider;
|
|
|
|
|
localCollection = taskList;
|
|
|
|
|
}
|
|
|
|
|
|
2015-10-16 21:06:35 +00:00
|
|
|
|
@Override
|
|
|
|
|
protected String getSyncErrorTitle() {
|
|
|
|
|
return context.getString(R.string.sync_error_tasks, account.name);
|
|
|
|
|
}
|
|
|
|
|
|
2015-10-14 22:49:15 +00:00
|
|
|
|
|
|
|
|
|
@Override
|
|
|
|
|
protected void prepare() {
|
|
|
|
|
collectionURL = HttpUrl.parse(localTaskList().getSyncId());
|
2016-02-24 22:08:19 +00:00
|
|
|
|
davCollection = new DavCalendar(httpClient, collectionURL);
|
2015-10-14 22:49:15 +00:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
@Override
|
2016-03-20 16:41:05 +00:00
|
|
|
|
protected void queryCapabilities() throws DavException, IOException, HttpException {
|
|
|
|
|
davCollection.propfind(0, GetCTag.NAME);
|
2015-10-14 22:49:15 +00:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
@Override
|
|
|
|
|
protected RequestBody prepareUpload(LocalResource resource) throws IOException, CalendarStorageException {
|
|
|
|
|
LocalTask local = (LocalTask)resource;
|
|
|
|
|
return RequestBody.create(
|
|
|
|
|
DavCalendar.MIME_ICALENDAR,
|
|
|
|
|
local.getTask().toStream().toByteArray()
|
|
|
|
|
);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
@Override
|
|
|
|
|
protected void listRemote() throws IOException, HttpException, DavException {
|
|
|
|
|
// fetch list of remote VTODOs and build hash table to index file name
|
2016-03-16 17:23:52 +00:00
|
|
|
|
davCalendar().calendarQuery("VTODO", null, null);
|
2015-10-14 22:49:15 +00:00
|
|
|
|
remoteResources = new HashMap<>(davCollection.members.size());
|
|
|
|
|
for (DavResource vCard : davCollection.members) {
|
|
|
|
|
String fileName = vCard.fileName();
|
2016-02-24 22:08:19 +00:00
|
|
|
|
App.log.fine("Found remote VTODO: " + fileName);
|
2015-10-14 22:49:15 +00:00
|
|
|
|
remoteResources.put(fileName, vCard);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
@Override
|
|
|
|
|
protected void downloadRemote() throws IOException, HttpException, DavException, CalendarStorageException {
|
2016-02-24 22:08:19 +00:00
|
|
|
|
App.log.info("Downloading " + toDownload.size() + " tasks (" + MAX_MULTIGET + " at once)");
|
2015-10-14 22:49:15 +00:00
|
|
|
|
|
|
|
|
|
// download new/updated iCalendars from server
|
|
|
|
|
for (DavResource[] bunch : ArrayUtils.partition(toDownload.toArray(new DavResource[toDownload.size()]), MAX_MULTIGET)) {
|
2015-10-17 09:33:35 +00:00
|
|
|
|
if (Thread.interrupted())
|
|
|
|
|
return;
|
|
|
|
|
|
2016-02-24 22:08:19 +00:00
|
|
|
|
App.log.info("Downloading " + StringUtils.join(bunch, ", "));
|
2015-10-14 22:49:15 +00:00
|
|
|
|
|
|
|
|
|
if (bunch.length == 1) {
|
|
|
|
|
// only one contact, use GET
|
|
|
|
|
DavResource remote = bunch[0];
|
|
|
|
|
|
|
|
|
|
ResponseBody body = remote.get("text/calendar");
|
|
|
|
|
String eTag = ((GetETag)remote.properties.get(GetETag.NAME)).eTag;
|
|
|
|
|
|
2015-10-23 22:36:22 +00:00
|
|
|
|
Charset charset = Charsets.UTF_8;
|
|
|
|
|
MediaType contentType = body.contentType();
|
|
|
|
|
if (contentType != null)
|
|
|
|
|
charset = contentType.charset(Charsets.UTF_8);
|
|
|
|
|
|
2015-10-14 22:49:15 +00:00
|
|
|
|
@Cleanup InputStream stream = body.byteStream();
|
2015-10-23 22:36:22 +00:00
|
|
|
|
processVTodo(remote.fileName(), eTag, stream, charset);
|
2015-10-14 22:49:15 +00:00
|
|
|
|
|
|
|
|
|
} else {
|
|
|
|
|
// multiple contacts, use multi-get
|
|
|
|
|
List<HttpUrl> urls = new LinkedList<>();
|
|
|
|
|
for (DavResource remote : bunch)
|
|
|
|
|
urls.add(remote.location);
|
|
|
|
|
davCalendar().multiget(urls.toArray(new HttpUrl[urls.size()]));
|
|
|
|
|
|
|
|
|
|
// process multiget results
|
|
|
|
|
for (DavResource remote : davCollection.members) {
|
|
|
|
|
String eTag;
|
|
|
|
|
GetETag getETag = (GetETag)remote.properties.get(GetETag.NAME);
|
|
|
|
|
if (getETag != null)
|
|
|
|
|
eTag = getETag.eTag;
|
|
|
|
|
else
|
|
|
|
|
throw new DavException("Received multi-get response without ETag");
|
|
|
|
|
|
|
|
|
|
Charset charset = Charsets.UTF_8;
|
|
|
|
|
GetContentType getContentType = (GetContentType)remote.properties.get(GetContentType.NAME);
|
|
|
|
|
if (getContentType != null && getContentType.type != null) {
|
|
|
|
|
MediaType type = MediaType.parse(getContentType.type);
|
|
|
|
|
if (type != null)
|
|
|
|
|
charset = type.charset(Charsets.UTF_8);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
CalendarData calendarData = (CalendarData)remote.properties.get(CalendarData.NAME);
|
|
|
|
|
if (calendarData == null || calendarData.iCalendar == null)
|
|
|
|
|
throw new DavException("Received multi-get response without address data");
|
|
|
|
|
|
|
|
|
|
@Cleanup InputStream stream = new ByteArrayInputStream(calendarData.iCalendar.getBytes());
|
|
|
|
|
processVTodo(remote.fileName(), eTag, stream, charset);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
// helpers
|
|
|
|
|
|
|
|
|
|
private LocalTaskList localTaskList() { return ((LocalTaskList)localCollection); }
|
|
|
|
|
private DavCalendar davCalendar() { return (DavCalendar)davCollection; }
|
|
|
|
|
|
|
|
|
|
private void processVTodo(String fileName, String eTag, InputStream stream, Charset charset) throws IOException, CalendarStorageException {
|
2016-02-24 22:08:19 +00:00
|
|
|
|
Task[] tasks;
|
2015-10-14 22:49:15 +00:00
|
|
|
|
try {
|
|
|
|
|
tasks = Task.fromStream(stream, charset);
|
|
|
|
|
} catch (InvalidCalendarException e) {
|
2016-02-24 22:08:19 +00:00
|
|
|
|
App.log.log(Level.SEVERE, "Received invalid iCalendar, ignoring", e);
|
2015-10-14 22:49:15 +00:00
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
|
2016-02-24 22:08:19 +00:00
|
|
|
|
if (tasks.length == 1) {
|
2015-10-14 22:49:15 +00:00
|
|
|
|
Task newData = tasks[0];
|
|
|
|
|
|
|
|
|
|
// update local task, if it exists
|
|
|
|
|
LocalTask localTask = (LocalTask)localResources.get(fileName);
|
|
|
|
|
if (localTask != null) {
|
2016-02-24 22:08:19 +00:00
|
|
|
|
App.log.info("Updating " + fileName + " in local tasklist");
|
2015-10-14 22:49:15 +00:00
|
|
|
|
localTask.setETag(eTag);
|
|
|
|
|
localTask.update(newData);
|
|
|
|
|
syncResult.stats.numUpdates++;
|
|
|
|
|
} else {
|
2016-02-24 22:08:19 +00:00
|
|
|
|
App.log.info("Adding " + fileName + " to local task list");
|
2015-10-14 22:49:15 +00:00
|
|
|
|
localTask = new LocalTask(localTaskList(), newData, fileName, eTag);
|
|
|
|
|
localTask.add();
|
|
|
|
|
syncResult.stats.numInserts++;
|
|
|
|
|
}
|
|
|
|
|
} else
|
2016-02-24 22:08:19 +00:00
|
|
|
|
App.log.severe("Received VCALENDAR with not exactly one VTODO; ignoring " + fileName);
|
2015-10-14 22:49:15 +00:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
}
|