mirror of
https://github.com/etesync/android
synced 2024-11-22 07:58:09 +00:00
Tasks with new sync logic
This commit is contained in:
parent
419d732195
commit
f344bd3c28
@ -11,15 +11,14 @@ apply plugin: 'com.android.application'
|
||||
android {
|
||||
compileSdkVersion 23
|
||||
buildToolsVersion '23.0.1'
|
||||
useLibrary 'org.apache.http.legacy'
|
||||
|
||||
defaultConfig {
|
||||
applicationId "at.bitfire.davdroid"
|
||||
minSdkVersion 14
|
||||
targetSdkVersion 23
|
||||
|
||||
versionCode 74
|
||||
versionName "0.9-alpha2"
|
||||
versionCode 75
|
||||
versionName "0.9-alpha3"
|
||||
|
||||
buildConfigField "java.util.Date", "buildTime", "new java.util.Date()"
|
||||
}
|
||||
|
@ -22,9 +22,13 @@ import android.provider.CalendarContract;
|
||||
import android.provider.CalendarContract.Calendars;
|
||||
import android.provider.CalendarContract.Events;
|
||||
import android.provider.CalendarContract.Reminders;
|
||||
import android.text.TextUtils;
|
||||
|
||||
import com.google.common.base.Joiner;
|
||||
|
||||
import net.fortuna.ical4j.data.CalendarBuilder;
|
||||
import net.fortuna.ical4j.model.component.VTimeZone;
|
||||
|
||||
import java.io.FileNotFoundException;
|
||||
import java.util.LinkedList;
|
||||
import java.util.List;
|
||||
@ -34,6 +38,7 @@ import at.bitfire.ical4android.AndroidCalendar;
|
||||
import at.bitfire.ical4android.AndroidCalendarFactory;
|
||||
import at.bitfire.ical4android.BatchOperation;
|
||||
import at.bitfire.ical4android.CalendarStorageException;
|
||||
import at.bitfire.ical4android.DateUtils;
|
||||
import at.bitfire.vcard4android.ContactsStorageException;
|
||||
import lombok.Cleanup;
|
||||
|
||||
@ -84,9 +89,10 @@ public class LocalCalendar extends AndroidCalendar implements LocalCollection {
|
||||
values.put(Calendars.OWNER_ACCOUNT, account.name);
|
||||
values.put(Calendars.SYNC_EVENTS, 1);
|
||||
values.put(Calendars.VISIBLE, 1);
|
||||
if (info.timezone != null) {
|
||||
// TODO parse VTIMEZONE
|
||||
// values.put(Calendars.CALENDAR_TIME_ZONE, DateUtils.findAndroidTimezoneID(info.timezone));
|
||||
if (!TextUtils.isEmpty(info.timezone)) {
|
||||
VTimeZone timeZone = DateUtils.parseVTimeZone(info.timezone);
|
||||
if (timeZone != null && timeZone.getTimeZoneId() != null)
|
||||
values.put(Calendars.CALENDAR_TIME_ZONE, DateUtils.findAndroidTimezoneID(timeZone.getTimeZoneId().getValue()));
|
||||
}
|
||||
values.put(Calendars.ALLOWED_REMINDERS, Reminders.METHOD_ALERT);
|
||||
if (Build.VERSION.SDK_INT >= 15) {
|
||||
|
139
app/src/main/java/at/bitfire/davdroid/resource/LocalTask.java
Normal file
139
app/src/main/java/at/bitfire/davdroid/resource/LocalTask.java
Normal file
@ -0,0 +1,139 @@
|
||||
/*
|
||||
* 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.resource;
|
||||
|
||||
import android.content.ContentProviderOperation;
|
||||
import android.content.ContentValues;
|
||||
import android.os.RemoteException;
|
||||
import android.provider.CalendarContract.Events;
|
||||
|
||||
import net.fortuna.ical4j.model.property.ProdId;
|
||||
|
||||
import org.dmfs.provider.tasks.TaskContract.Tasks;
|
||||
|
||||
import java.io.FileNotFoundException;
|
||||
import java.text.ParseException;
|
||||
|
||||
import at.bitfire.davdroid.BuildConfig;
|
||||
import at.bitfire.ical4android.AndroidTask;
|
||||
import at.bitfire.ical4android.AndroidTaskFactory;
|
||||
import at.bitfire.ical4android.AndroidTaskList;
|
||||
import at.bitfire.ical4android.CalendarStorageException;
|
||||
import at.bitfire.ical4android.Task;
|
||||
import lombok.Getter;
|
||||
import lombok.NonNull;
|
||||
import lombok.Setter;
|
||||
|
||||
public class LocalTask extends AndroidTask implements LocalResource {
|
||||
static {
|
||||
Task.prodId = new ProdId("+//IDN bitfire.at//DAVdroid/" + BuildConfig.VERSION_NAME + " ical4android ical4j/2.x");
|
||||
}
|
||||
|
||||
static final String COLUMN_ETAG = Tasks.SYNC1,
|
||||
COLUMN_UID = Tasks._UID,
|
||||
COLUMN_SEQUENCE = Tasks.SYNC2;
|
||||
|
||||
@Getter protected String fileName;
|
||||
@Getter @Setter protected String eTag;
|
||||
|
||||
public LocalTask(@NonNull AndroidTaskList taskList, Task task, String fileName, String eTag) {
|
||||
super(taskList, task);
|
||||
this.fileName = fileName;
|
||||
this.eTag = eTag;
|
||||
}
|
||||
|
||||
protected LocalTask(@NonNull AndroidTaskList taskList, long id, ContentValues baseInfo) {
|
||||
super(taskList, id);
|
||||
if (baseInfo != null) {
|
||||
fileName = baseInfo.getAsString(Events._SYNC_ID);
|
||||
eTag = baseInfo.getAsString(COLUMN_ETAG);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/* process LocalTask-specific fields */
|
||||
|
||||
@Override
|
||||
protected void populateTask(ContentValues values) throws FileNotFoundException, RemoteException, ParseException {
|
||||
super.populateTask(values);
|
||||
|
||||
fileName = values.getAsString(Events._SYNC_ID);
|
||||
eTag = values.getAsString(COLUMN_ETAG);
|
||||
task.uid = values.getAsString(COLUMN_UID);
|
||||
|
||||
if (values.containsKey(COLUMN_SEQUENCE))
|
||||
task.sequence = values.getAsInteger(COLUMN_SEQUENCE);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void buildTask(ContentProviderOperation.Builder builder, boolean update) {
|
||||
super.buildTask(builder, update);
|
||||
builder .withValue(Tasks._SYNC_ID, fileName)
|
||||
.withValue(COLUMN_UID, task.uid)
|
||||
.withValue(COLUMN_SEQUENCE, task.sequence)
|
||||
.withValue(COLUMN_ETAG, eTag);
|
||||
}
|
||||
|
||||
|
||||
/* custom queries */
|
||||
|
||||
public void updateFileNameAndUID(String uid) throws CalendarStorageException {
|
||||
try {
|
||||
String newFileName = uid + ".ics";
|
||||
|
||||
ContentValues values = new ContentValues(2);
|
||||
values.put(Tasks._SYNC_ID, newFileName);
|
||||
values.put(COLUMN_UID, uid);
|
||||
taskList.provider.client.update(taskSyncURI(), values, null, null);
|
||||
|
||||
fileName = newFileName;
|
||||
if (task != null)
|
||||
task.uid = uid;
|
||||
|
||||
} catch (RemoteException e) {
|
||||
throw new CalendarStorageException("Couldn't update UID", e);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void clearDirty(String eTag) throws CalendarStorageException {
|
||||
try {
|
||||
ContentValues values = new ContentValues(2);
|
||||
values.put(Tasks._DIRTY, 0);
|
||||
values.put(COLUMN_ETAG, eTag);
|
||||
if (task != null)
|
||||
values.put(COLUMN_SEQUENCE, task.sequence);
|
||||
taskList.provider.client.update(taskSyncURI(), values, null, null);
|
||||
|
||||
this.eTag = eTag;
|
||||
} catch (RemoteException e) {
|
||||
throw new CalendarStorageException("Couldn't update _DIRTY/ETag/SEQUENCE", e);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
static class Factory implements AndroidTaskFactory {
|
||||
static final Factory INSTANCE = new Factory();
|
||||
|
||||
@Override
|
||||
public LocalTask newInstance(AndroidTaskList taskList, long id, ContentValues baseInfo) {
|
||||
return new LocalTask(taskList, id, baseInfo);
|
||||
}
|
||||
|
||||
@Override
|
||||
public LocalTask newInstance(AndroidTaskList taskList, Task task) {
|
||||
return new LocalTask(taskList, task, null, null);
|
||||
}
|
||||
|
||||
@Override
|
||||
public LocalTask[] newArray(int size) {
|
||||
return new LocalTask[size];
|
||||
}
|
||||
}
|
||||
}
|
@ -0,0 +1,146 @@
|
||||
/*
|
||||
* 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.resource;
|
||||
|
||||
import android.accounts.Account;
|
||||
import android.content.ContentResolver;
|
||||
import android.content.ContentValues;
|
||||
import android.database.Cursor;
|
||||
import android.net.Uri;
|
||||
import android.os.RemoteException;
|
||||
|
||||
import org.dmfs.provider.tasks.TaskContract.TaskLists;
|
||||
import org.dmfs.provider.tasks.TaskContract.Tasks;
|
||||
|
||||
import java.io.FileNotFoundException;
|
||||
|
||||
import at.bitfire.ical4android.AndroidTaskList;
|
||||
import at.bitfire.ical4android.AndroidTaskListFactory;
|
||||
import at.bitfire.ical4android.CalendarStorageException;
|
||||
import at.bitfire.ical4android.TaskProvider;
|
||||
import lombok.Cleanup;
|
||||
import lombok.NonNull;
|
||||
|
||||
public class LocalTaskList extends AndroidTaskList implements LocalCollection {
|
||||
|
||||
public static final int defaultColor = 0xFFC3EA6E; // "DAVdroid green"
|
||||
|
||||
public static final String COLUMN_CTAG = TaskLists.SYNC_VERSION;
|
||||
|
||||
static String[] BASE_INFO_COLUMNS = new String[] {
|
||||
Tasks._ID,
|
||||
Tasks._SYNC_ID,
|
||||
LocalTask.COLUMN_ETAG
|
||||
};
|
||||
|
||||
private static Boolean tasksProviderAvailable;
|
||||
|
||||
|
||||
@Override
|
||||
protected String[] taskBaseInfoColumns() {
|
||||
return BASE_INFO_COLUMNS;
|
||||
}
|
||||
|
||||
|
||||
protected LocalTaskList(Account account, TaskProvider provider, long id) {
|
||||
super(account, provider, LocalTask.Factory.INSTANCE, id);
|
||||
}
|
||||
|
||||
public static Uri create(Account account, ContentResolver resolver, ServerInfo.ResourceInfo info) throws CalendarStorageException {
|
||||
TaskProvider provider = TaskProvider.acquire(resolver, TaskProvider.ProviderName.OpenTasks);
|
||||
if (provider == null)
|
||||
throw new CalendarStorageException("Couldn't access OpenTasks provider");
|
||||
|
||||
ContentValues values = new ContentValues();
|
||||
values.put(TaskLists._SYNC_ID, info.getURL());
|
||||
values.put(TaskLists.LIST_NAME, info.getTitle());
|
||||
values.put(TaskLists.LIST_COLOR, info.color != null ? info.color : defaultColor);
|
||||
values.put(TaskLists.OWNER, account.name);
|
||||
values.put(TaskLists.SYNC_ENABLED, 1);
|
||||
values.put(TaskLists.VISIBLE, 1);
|
||||
|
||||
return create(account, provider, values);
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
public LocalTask[] getAll() throws CalendarStorageException {
|
||||
return (LocalTask[])queryTasks(null, null);
|
||||
}
|
||||
|
||||
@Override
|
||||
public LocalTask[] getDeleted() throws CalendarStorageException {
|
||||
return (LocalTask[])queryTasks(Tasks._DELETED + "!=0", null);
|
||||
}
|
||||
|
||||
@Override
|
||||
public LocalTask[] getWithoutFileName() throws CalendarStorageException {
|
||||
return (LocalTask[])queryTasks(Tasks._SYNC_ID + " IS NULL", null);
|
||||
}
|
||||
|
||||
@Override
|
||||
public LocalResource[] getDirty() throws CalendarStorageException, FileNotFoundException {
|
||||
LocalTask[] tasks = (LocalTask[])queryTasks(Tasks._DIRTY + "!=0", null);
|
||||
if (tasks != null)
|
||||
for (LocalTask task : tasks)
|
||||
task.getTask().sequence++;
|
||||
return tasks;
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
public String getCTag() throws CalendarStorageException {
|
||||
try {
|
||||
@Cleanup Cursor cursor = provider.client.query(taskListSyncUri(), new String[] { COLUMN_CTAG }, null, null, null);
|
||||
if (cursor != null && cursor.moveToNext())
|
||||
return cursor.getString(0);
|
||||
} catch (RemoteException e) {
|
||||
throw new CalendarStorageException("Couldn't read local (last known) CTag", e);
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setCTag(String cTag) throws CalendarStorageException {
|
||||
try {
|
||||
ContentValues values = new ContentValues(1);
|
||||
values.put(COLUMN_CTAG, cTag);
|
||||
provider.client.update(taskListSyncUri(), values, null, null);
|
||||
} catch (RemoteException e) {
|
||||
throw new CalendarStorageException("Couldn't write local (last known) CTag", e);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
// helpers
|
||||
|
||||
public static boolean tasksProviderAvailable(@NonNull ContentResolver resolver) {
|
||||
if (tasksProviderAvailable != null)
|
||||
return tasksProviderAvailable;
|
||||
else {
|
||||
TaskProvider provider = TaskProvider.acquire(resolver, TaskProvider.ProviderName.OpenTasks);
|
||||
return tasksProviderAvailable = (provider != null);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
public static class Factory implements AndroidTaskListFactory {
|
||||
public static final Factory INSTANCE = new Factory();
|
||||
|
||||
@Override
|
||||
public AndroidTaskList newInstance(Account account, TaskProvider provider, long id) {
|
||||
return new LocalTaskList(account, provider, id);
|
||||
}
|
||||
|
||||
@Override
|
||||
public AndroidTaskList[] newArray(int size) {
|
||||
return new LocalTaskList[size];
|
||||
}
|
||||
}
|
||||
}
|
@ -9,7 +9,6 @@
|
||||
package at.bitfire.davdroid.syncadapter;
|
||||
|
||||
import android.accounts.Account;
|
||||
import android.content.ContentProviderClient;
|
||||
import android.content.ContentValues;
|
||||
import android.content.Context;
|
||||
import android.content.SyncResult;
|
||||
@ -36,7 +35,6 @@ 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.AddressData;
|
||||
import at.bitfire.dav4android.property.CalendarColor;
|
||||
import at.bitfire.dav4android.property.CalendarData;
|
||||
import at.bitfire.dav4android.property.DisplayName;
|
||||
@ -46,14 +44,11 @@ import at.bitfire.dav4android.property.GetETag;
|
||||
import at.bitfire.davdroid.ArrayUtils;
|
||||
import at.bitfire.davdroid.Constants;
|
||||
import at.bitfire.davdroid.resource.LocalCalendar;
|
||||
import at.bitfire.davdroid.resource.LocalContact;
|
||||
import at.bitfire.davdroid.resource.LocalEvent;
|
||||
import at.bitfire.davdroid.resource.LocalResource;
|
||||
import at.bitfire.ical4android.AndroidHostInfo;
|
||||
import at.bitfire.ical4android.CalendarStorageException;
|
||||
import at.bitfire.ical4android.Event;
|
||||
import at.bitfire.ical4android.InvalidCalendarException;
|
||||
import at.bitfire.vcard4android.Contact;
|
||||
import at.bitfire.vcard4android.ContactsStorageException;
|
||||
import lombok.Cleanup;
|
||||
|
||||
@ -63,20 +58,16 @@ public class CalendarSyncManager extends SyncManager {
|
||||
MAX_MULTIGET = 30,
|
||||
NOTIFICATION_ID = 2;
|
||||
|
||||
protected AndroidHostInfo hostInfo;
|
||||
|
||||
|
||||
public CalendarSyncManager(Context context, Account account, Bundle extras, ContentProviderClient provider, SyncResult result, LocalCalendar calendar) {
|
||||
super(NOTIFICATION_ID, context, account, extras, provider, result);
|
||||
public CalendarSyncManager(Context context, Account account, Bundle extras, SyncResult result, LocalCalendar calendar) {
|
||||
super(NOTIFICATION_ID, context, account, extras, result);
|
||||
localCollection = calendar;
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
protected void prepare() {
|
||||
Thread.currentThread().setContextClassLoader(context.getClassLoader());
|
||||
|
||||
hostInfo = new AndroidHostInfo(context.getContentResolver());
|
||||
Thread.currentThread().setContextClassLoader(context.getClassLoader()); // required for ical4j
|
||||
|
||||
collectionURL = HttpUrl.parse(localCalendar().getName());
|
||||
davCollection = new DavCalendar(httpClient, collectionURL);
|
||||
@ -191,13 +182,13 @@ public class CalendarSyncManager extends SyncManager {
|
||||
private void processVEvent(String fileName, String eTag, InputStream stream, Charset charset) throws IOException, CalendarStorageException {
|
||||
Event[] events;
|
||||
try {
|
||||
events = Event.fromStream(stream, charset, hostInfo);
|
||||
events = Event.fromStream(stream, charset);
|
||||
} catch (InvalidCalendarException e) {
|
||||
Constants.log.error("Received invalid iCalendar, ignoring");
|
||||
return;
|
||||
}
|
||||
|
||||
if (events.length == 1) {
|
||||
if (events != null && events.length == 1) {
|
||||
Event newData = events[0];
|
||||
|
||||
// delete local event, if it exists
|
||||
|
@ -55,11 +55,11 @@ public class CalendarsSyncAdapterService extends Service {
|
||||
try {
|
||||
for (LocalCalendar calendar : (LocalCalendar[])LocalCalendar.find(account, provider, LocalCalendar.Factory.INSTANCE, CalendarContract.Calendars.SYNC_EVENTS + "!=0", null)) {
|
||||
Constants.log.info("Synchronizing calendar #" + calendar.getId() + ", URL: " + calendar.getName());
|
||||
CalendarSyncManager syncManager = new CalendarSyncManager(getContext(), account, extras, provider, syncResult, calendar);
|
||||
CalendarSyncManager syncManager = new CalendarSyncManager(getContext(), account, extras, syncResult, calendar);
|
||||
syncManager.performSync();
|
||||
}
|
||||
} catch (CalendarStorageException e) {
|
||||
Constants.log.error("Couldn't get list of local calendars", e);
|
||||
Constants.log.error("Couldn't enumerate local calendars", e);
|
||||
}
|
||||
|
||||
Constants.log.info("Calendar sync complete");
|
||||
|
@ -60,11 +60,13 @@ public class ContactsSyncManager extends SyncManager {
|
||||
MAX_MULTIGET = 10,
|
||||
NOTIFICATION_ID = 1;
|
||||
|
||||
final protected ContentProviderClient provider;
|
||||
protected boolean hasVCard4;
|
||||
|
||||
|
||||
public ContactsSyncManager(Context context, Account account, Bundle extras, ContentProviderClient provider, SyncResult result) {
|
||||
super(NOTIFICATION_ID, context, account, extras, provider, result);
|
||||
super(NOTIFICATION_ID, context, account, extras, result);
|
||||
this.provider = provider;
|
||||
}
|
||||
|
||||
|
||||
@ -175,10 +177,10 @@ public class ContactsSyncManager extends SyncManager {
|
||||
|
||||
private void processVCard(String fileName, String eTag, InputStream stream, Charset charset, Contact.Downloader downloader) throws IOException, ContactsStorageException {
|
||||
Contact contacts[] = Contact.fromStream(stream, charset, downloader);
|
||||
if (contacts.length == 1) {
|
||||
if (contacts != null && contacts.length == 1) {
|
||||
Contact newData = contacts[0];
|
||||
|
||||
// delete local contact, if it exists
|
||||
// update local contact, if it exists
|
||||
LocalContact localContact = (LocalContact)localResources.get(fileName);
|
||||
if (localContact != null) {
|
||||
Constants.log.info("Updating " + fileName + " in local address book");
|
||||
|
@ -11,7 +11,6 @@ import android.accounts.Account;
|
||||
import android.app.Notification;
|
||||
import android.app.NotificationManager;
|
||||
import android.app.PendingIntent;
|
||||
import android.content.ContentProviderClient;
|
||||
import android.content.ContentResolver;
|
||||
import android.content.Context;
|
||||
import android.content.Intent;
|
||||
@ -67,7 +66,6 @@ abstract public class SyncManager {
|
||||
protected final Context context;
|
||||
protected final Account account;
|
||||
protected final Bundle extras;
|
||||
protected final ContentProviderClient provider;
|
||||
protected final SyncResult syncResult;
|
||||
|
||||
protected final AccountSettings settings;
|
||||
@ -92,11 +90,10 @@ abstract public class SyncManager {
|
||||
|
||||
|
||||
|
||||
public SyncManager(int notificationId, Context context, Account account, Bundle extras, ContentProviderClient provider, SyncResult syncResult) {
|
||||
public SyncManager(int notificationId, Context context, Account account, Bundle extras, SyncResult syncResult) {
|
||||
this.context = context;
|
||||
this.account = account;
|
||||
this.extras = extras;
|
||||
this.provider = provider;
|
||||
this.syncResult = syncResult;
|
||||
|
||||
// get account settings and generate httpClient
|
||||
@ -167,9 +164,14 @@ abstract public class SyncManager {
|
||||
}
|
||||
}
|
||||
|
||||
} catch(HttpException|DavException e) {
|
||||
Constants.log.error("HTTP/DAV Exception during sync", e);
|
||||
syncResult.stats.numParseExceptions++;
|
||||
} catch(Exception e) {
|
||||
if (e instanceof HttpException || e instanceof DavException) {
|
||||
Constants.log.error("HTTP/DAV Exception during sync", e);
|
||||
syncResult.stats.numParseExceptions++;
|
||||
} else if (e instanceof CalendarStorageException || e instanceof ContactsStorageException) {
|
||||
Constants.log.error("Couldn't access local storage", e);
|
||||
syncResult.databaseError = true;
|
||||
}
|
||||
|
||||
Intent detailsIntent = new Intent(context, DebugInfoActivity.class);
|
||||
detailsIntent.putExtra(DebugInfoActivity.KEY_EXCEPTION, e);
|
||||
@ -195,9 +197,6 @@ abstract public class SyncManager {
|
||||
}
|
||||
notificationManager.notify(account.name, notificationId, notification);
|
||||
|
||||
} catch(CalendarStorageException|ContactsStorageException e) {
|
||||
Constants.log.error("Couldn't access local storage", e);
|
||||
syncResult.databaseError = true;
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -16,6 +16,14 @@ import android.content.Intent;
|
||||
import android.content.SyncResult;
|
||||
import android.os.Bundle;
|
||||
import android.os.IBinder;
|
||||
import android.provider.CalendarContract;
|
||||
|
||||
import at.bitfire.davdroid.Constants;
|
||||
import at.bitfire.davdroid.resource.LocalCalendar;
|
||||
import at.bitfire.davdroid.resource.LocalTaskList;
|
||||
import at.bitfire.ical4android.CalendarStorageException;
|
||||
import at.bitfire.ical4android.TaskProvider;
|
||||
import lombok.Cleanup;
|
||||
|
||||
public class TasksSyncAdapterService extends Service {
|
||||
private static SyncAdapter syncAdapter;
|
||||
@ -43,8 +51,24 @@ public class TasksSyncAdapterService extends Service {
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onPerformSync(Account account, Bundle extras, String authority, ContentProviderClient provider, SyncResult syncResult) {
|
||||
public void onPerformSync(Account account, Bundle extras, String authority, ContentProviderClient providerClient, SyncResult syncResult) {
|
||||
Constants.log.info("Starting task sync (" + authority + ")");
|
||||
|
||||
try {
|
||||
@Cleanup TaskProvider provider = TaskProvider.acquire(getContext().getContentResolver(), TaskProvider.ProviderName.OpenTasks);
|
||||
if (provider == null)
|
||||
throw new CalendarStorageException("Couldn't access OpenTasks provider");
|
||||
|
||||
for (LocalTaskList taskList : (LocalTaskList[])LocalTaskList.find(account, provider, LocalTaskList.Factory.INSTANCE, null, null)) {
|
||||
Constants.log.info("Synchronizing task list #" + taskList.getId() + ", URL: " + taskList.getName());
|
||||
TasksSyncManager syncManager = new TasksSyncManager(getContext(), account, extras, provider, syncResult, taskList);
|
||||
syncManager.performSync();
|
||||
}
|
||||
} catch (CalendarStorageException e) {
|
||||
Constants.log.error("Couldn't enumerate local task lists", e);
|
||||
}
|
||||
|
||||
Constants.log.info("Calendar sync complete");
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -0,0 +1,209 @@
|
||||
/*
|
||||
* 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;
|
||||
|
||||
import com.google.common.base.Charsets;
|
||||
import com.google.common.base.Joiner;
|
||||
import com.squareup.okhttp.HttpUrl;
|
||||
import com.squareup.okhttp.MediaType;
|
||||
import com.squareup.okhttp.RequestBody;
|
||||
import com.squareup.okhttp.ResponseBody;
|
||||
|
||||
import org.dmfs.provider.tasks.TaskContract.TaskLists;
|
||||
|
||||
import java.io.ByteArrayInputStream;
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
import java.nio.charset.Charset;
|
||||
import java.util.HashMap;
|
||||
import java.util.LinkedList;
|
||||
import java.util.List;
|
||||
|
||||
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;
|
||||
import at.bitfire.davdroid.ArrayUtils;
|
||||
import at.bitfire.davdroid.Constants;
|
||||
import at.bitfire.davdroid.resource.LocalCalendar;
|
||||
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;
|
||||
|
||||
public class TasksSyncManager extends SyncManager {
|
||||
|
||||
protected static final int
|
||||
MAX_MULTIGET = 30,
|
||||
NOTIFICATION_ID = 3;
|
||||
|
||||
final protected TaskProvider provider;
|
||||
|
||||
|
||||
public TasksSyncManager(Context context, Account account, Bundle extras, TaskProvider provider, SyncResult result, LocalTaskList taskList) {
|
||||
super(NOTIFICATION_ID, context, account, extras, result);
|
||||
this.provider = provider;
|
||||
localCollection = taskList;
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
protected void prepare() {
|
||||
Thread.currentThread().setContextClassLoader(context.getClassLoader()); // required for ical4j
|
||||
|
||||
collectionURL = HttpUrl.parse(localTaskList().getSyncId());
|
||||
davCollection = new DavCalendar(httpClient, collectionURL);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void queryCapabilities() throws DavException, IOException, HttpException, CalendarStorageException {
|
||||
davCollection.propfind(0, DisplayName.NAME, CalendarColor.NAME, GetCTag.NAME);
|
||||
|
||||
// update name and color
|
||||
DisplayName pDisplayName = (DisplayName)davCollection.properties.get(DisplayName.NAME);
|
||||
String displayName = (pDisplayName != null && !TextUtils.isEmpty(pDisplayName.displayName)) ?
|
||||
pDisplayName.displayName : collectionURL.toString();
|
||||
|
||||
CalendarColor pColor = (CalendarColor)davCollection.properties.get(CalendarColor.NAME);
|
||||
int color = (pColor != null && pColor.color != null) ? pColor.color : LocalCalendar.defaultColor;
|
||||
|
||||
ContentValues values = new ContentValues(2);
|
||||
Constants.log.info("Setting new calendar name \"" + displayName + "\" and color 0x" + Integer.toHexString(color));
|
||||
values.put(TaskLists.LIST_NAME, displayName);
|
||||
values.put(TaskLists.LIST_COLOR, color);
|
||||
localTaskList().update(values);
|
||||
}
|
||||
|
||||
@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
|
||||
davCalendar().calendarQuery("VTODO");
|
||||
remoteResources = new HashMap<>(davCollection.members.size());
|
||||
for (DavResource vCard : davCollection.members) {
|
||||
String fileName = vCard.fileName();
|
||||
Constants.log.debug("Found remote VTODO: " + fileName);
|
||||
remoteResources.put(fileName, vCard);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void downloadRemote() throws IOException, HttpException, DavException, CalendarStorageException {
|
||||
Constants.log.info("Downloading " + toDownload.size() + " tasks (" + MAX_MULTIGET + " at once)");
|
||||
|
||||
// download new/updated iCalendars from server
|
||||
for (DavResource[] bunch : ArrayUtils.partition(toDownload.toArray(new DavResource[toDownload.size()]), MAX_MULTIGET)) {
|
||||
Constants.log.info("Downloading " + Joiner.on(" + ").join(bunch));
|
||||
|
||||
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;
|
||||
|
||||
@Cleanup InputStream stream = body.byteStream();
|
||||
processVTodo(remote.fileName(), eTag, stream, body.contentType().charset(Charsets.UTF_8));
|
||||
|
||||
} 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 {
|
||||
Task[] tasks = null;
|
||||
try {
|
||||
tasks = Task.fromStream(stream, charset);
|
||||
} catch (InvalidCalendarException e) {
|
||||
Constants.log.error("Received invalid iCalendar, ignoring");
|
||||
return;
|
||||
}
|
||||
|
||||
if (tasks != null && tasks.length == 1) {
|
||||
Task newData = tasks[0];
|
||||
|
||||
// update local task, if it exists
|
||||
LocalTask localTask = (LocalTask)localResources.get(fileName);
|
||||
if (localTask != null) {
|
||||
Constants.log.info("Updating " + fileName + " in local tasklist");
|
||||
localTask.setETag(eTag);
|
||||
localTask.update(newData);
|
||||
syncResult.stats.numUpdates++;
|
||||
} else {
|
||||
Constants.log.info("Adding " + fileName + " to local task list");
|
||||
localTask = new LocalTask(localTaskList(), newData, fileName, eTag);
|
||||
localTask.add();
|
||||
syncResult.stats.numInserts++;
|
||||
}
|
||||
} else
|
||||
Constants.log.error("Received VCALENDAR with not exactly one VEVENT with UID, but without RECURRENCE-ID; ignoring " + fileName);
|
||||
}
|
||||
|
||||
}
|
@ -21,6 +21,7 @@ import android.provider.ContactsContract;
|
||||
|
||||
import at.bitfire.davdroid.R;
|
||||
import at.bitfire.davdroid.syncadapter.AccountSettings;
|
||||
import at.bitfire.ical4android.TaskProvider;
|
||||
import ezvcard.VCardVersion;
|
||||
|
||||
public class AccountFragment extends PreferenceFragment {
|
||||
@ -119,8 +120,8 @@ public class AccountFragment extends PreferenceFragment {
|
||||
prefSyncCalendars.setSummary(R.string.settings_sync_summary_not_available);
|
||||
}
|
||||
|
||||
/*final ListPreference prefSyncTasks = (ListPreference)findPreference("sync_interval_tasks");
|
||||
final Long syncIntervalTasks = settings.getSyncInterval(LocalTaskList.TASKS_AUTHORITY);
|
||||
final ListPreference prefSyncTasks = (ListPreference)findPreference("sync_interval_tasks");
|
||||
final Long syncIntervalTasks = settings.getSyncInterval(TaskProvider.ProviderName.OpenTasks.authority);
|
||||
if (syncIntervalTasks != null) {
|
||||
prefSyncTasks.setValue(syncIntervalTasks.toString());
|
||||
if (syncIntervalTasks == AccountSettings.SYNC_INTERVAL_MANUALLY)
|
||||
@ -130,7 +131,7 @@ public class AccountFragment extends PreferenceFragment {
|
||||
prefSyncTasks.setOnPreferenceChangeListener(new Preference.OnPreferenceChangeListener() {
|
||||
@Override
|
||||
public boolean onPreferenceChange(Preference preference, Object newValue) {
|
||||
settings.setSyncInterval(LocalTaskList.TASKS_AUTHORITY, Long.parseLong((String) newValue));
|
||||
settings.setSyncInterval(TaskProvider.ProviderName.OpenTasks.authority, Long.parseLong((String) newValue));
|
||||
readFromAccount();
|
||||
return true;
|
||||
}
|
||||
@ -138,7 +139,7 @@ public class AccountFragment extends PreferenceFragment {
|
||||
} else {
|
||||
prefSyncTasks.setEnabled(false);
|
||||
prefSyncTasks.setSummary(R.string.settings_sync_summary_not_available);
|
||||
}*/
|
||||
}
|
||||
|
||||
// category: address book
|
||||
final CheckBoxPreference prefVCard4 = (CheckBoxPreference) findPreference("vcard4_support");
|
||||
|
@ -36,9 +36,11 @@ import at.bitfire.davdroid.Constants;
|
||||
import at.bitfire.davdroid.R;
|
||||
import at.bitfire.davdroid.resource.LocalAddressBook;
|
||||
import at.bitfire.davdroid.resource.LocalCalendar;
|
||||
import at.bitfire.davdroid.resource.LocalTaskList;
|
||||
import at.bitfire.davdroid.resource.ServerInfo;
|
||||
import at.bitfire.davdroid.syncadapter.AccountSettings;
|
||||
import at.bitfire.ical4android.CalendarStorageException;
|
||||
import at.bitfire.ical4android.TaskProvider;
|
||||
import at.bitfire.vcard4android.ContactsStorageException;
|
||||
import lombok.Cleanup;
|
||||
|
||||
@ -120,12 +122,16 @@ public class AccountDetailsFragment extends Fragment implements TextWatcher {
|
||||
}
|
||||
});
|
||||
|
||||
/*addSync(account, LocalTaskList.TASKS_AUTHORITY, serverInfo.getTaskLists(), new AddSyncCallback() {
|
||||
addSync(account, TaskProvider.ProviderName.OpenTasks.authority, serverInfo.getTaskLists(), new AddSyncCallback() {
|
||||
@Override
|
||||
public void createLocalCollection(Account account, ServerInfo.ResourceInfo todoList) throws LocalStorageException {
|
||||
LocalTaskList.create(account, getActivity().getContentResolver(), todoList);
|
||||
}
|
||||
});*/
|
||||
public void createLocalCollection(Account account, ServerInfo.ResourceInfo todoList) {
|
||||
try {
|
||||
LocalTaskList.create(account, getActivity().getContentResolver(), todoList);
|
||||
} catch (CalendarStorageException e) {
|
||||
Constants.log.error("Couldn't create local task list", e);
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
getActivity().finish();
|
||||
} else
|
||||
|
@ -26,8 +26,10 @@ import java.net.URISyntaxException;
|
||||
|
||||
import at.bitfire.dav4android.exception.DavException;
|
||||
import at.bitfire.dav4android.exception.HttpException;
|
||||
import at.bitfire.davdroid.Constants;
|
||||
import at.bitfire.davdroid.R;
|
||||
import at.bitfire.davdroid.resource.DavResourceFinder;
|
||||
import at.bitfire.davdroid.resource.LocalTaskList;
|
||||
import at.bitfire.davdroid.resource.ServerInfo;
|
||||
|
||||
public class QueryServerDialogFragment extends DialogFragment implements LoaderCallbacks<ServerInfo> {
|
||||
@ -68,7 +70,7 @@ public class QueryServerDialogFragment extends DialogFragment implements LoaderC
|
||||
((AddAccountActivity)getActivity()).serverInfo = serverInfo;
|
||||
|
||||
Fragment nextFragment;
|
||||
if (!serverInfo.getTaskLists().isEmpty() /*&& !LocalTaskList.isAvailable(getActivity())*/)
|
||||
if (!serverInfo.getTaskLists().isEmpty() && !LocalTaskList.tasksProviderAvailable(getActivity().getContentResolver()))
|
||||
nextFragment = new InstallAppsFragment();
|
||||
else
|
||||
nextFragment = new SelectCollectionsFragment();
|
||||
@ -119,10 +121,10 @@ public class QueryServerDialogFragment extends DialogFragment implements LoaderC
|
||||
/*if (ExceptionUtils.indexOfType(e, CertPathValidatorException.class) != -1)
|
||||
serverInfo.setErrorMessage(getContext().getString(R.string.exception_cert_path_validation, e.getMessage()));*/
|
||||
} catch (HttpException e) {
|
||||
Log.e(TAG, "HTTP error while querying server info", e);
|
||||
Constants.log.error("HTTP error while querying server info", e);
|
||||
serverInfo.setErrorMessage(getContext().getString(R.string.exception_http, e.getLocalizedMessage()));
|
||||
} catch (DavException e) {
|
||||
Log.e(TAG, "DAV error while querying server info", e);
|
||||
Constants.log.error("DAV error while querying server info", e);
|
||||
serverInfo.setErrorMessage(getContext().getString(R.string.exception_incapable_resource, e.getLocalizedMessage()));
|
||||
}
|
||||
|
||||
|
@ -18,6 +18,7 @@ import android.widget.CheckedTextView;
|
||||
import android.widget.ListAdapter;
|
||||
|
||||
import at.bitfire.davdroid.R;
|
||||
import at.bitfire.davdroid.resource.LocalTaskList;
|
||||
import at.bitfire.davdroid.resource.ServerInfo;
|
||||
import lombok.Getter;
|
||||
|
||||
@ -171,7 +172,7 @@ public class SelectCollectionsAdapter extends BaseAdapter implements ListAdapter
|
||||
}
|
||||
|
||||
// disable task list selection if there's no local task provider
|
||||
if (viewType == TYPE_TASK_LISTS_ROW /*&& !LocalTaskList.isAvailable(context)*/) {
|
||||
if (viewType == TYPE_TASK_LISTS_ROW && !LocalTaskList.tasksProviderAvailable(context.getContentResolver())) {
|
||||
final CheckedTextView check = (CheckedTextView)v;
|
||||
check.setEnabled(false);
|
||||
check.setOnClickListener(new View.OnClickListener() {
|
||||
|
@ -1 +1 @@
|
||||
Subproject commit 7530deb497c7c0ee78a583e7371ef9bfc4458a2e
|
||||
Subproject commit 6fc7f30b614ecb23b6fa66b6fd848fd6ddc4bafe
|
@ -1 +1 @@
|
||||
Subproject commit ea504f2512ad5e9a85391797bdbdeb6c92871cdf
|
||||
Subproject commit 624287708eb7f093d2ddf2012e2c2c124cd6e206
|
@ -1 +1 @@
|
||||
Subproject commit 53c1695db02cc371369e05cb02a0f1e537ac9eec
|
||||
Subproject commit 83ba3128913ba6e57d021ad003bf992100baae43
|
Loading…
Reference in New Issue
Block a user