Donate to e Foundation | Murena handsets with /e/OS | Own a part of Murena! Learn more

Commit fa766906 authored by Ricki Hirner's avatar Ricki Hirner
Browse files

Tasks with new sync logic

parent 65c15258
Loading
Loading
Loading
Loading
+2 −3
Original line number Diff line number Diff line
@@ -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()"
    }
+9 −3
Original line number Diff line number Diff line
@@ -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 −0
Original line number Diff line number Diff line
/*
 * 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];
        }
    }
}
+146 −0
Original line number Diff line number Diff line
/*
 * 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];
        }
    }
}
+5 −14
Original line number Diff line number Diff line
@@ -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
Loading