1
0
mirror of https://github.com/etesync/android synced 2024-11-22 07:58:09 +00:00

Bring back and adjust the tasks sync manager.

This commit is contained in:
Tom Hacohen 2017-12-10 19:17:23 +00:00
parent e9b09453f8
commit 23ab80acc0
9 changed files with 370 additions and 3 deletions

View File

@ -106,6 +106,19 @@
android:name="android.content.SyncAdapter"
android:resource="@xml/sync_calendars" />
</service>
<service
android:name=".syncadapter.TasksSyncAdapterService"
android:exported="true"
android:process=":sync"
tools:ignore="ExportedService">
<intent-filter>
<action android:name="android.content.SyncAdapter"/>
</intent-filter>
<meta-data
android:name="android.content.SyncAdapter"
android:resource="@xml/sync_tasks"/>
</service>
<!-- Address book account -->
<service

View File

@ -8,14 +8,20 @@
package com.etesync.syncadapter;
import android.accounts.Account;
import android.accounts.AccountManager;
import android.annotation.SuppressLint;
import android.content.BroadcastReceiver;
import android.content.ContentResolver;
import android.content.Context;
import android.content.Intent;
import android.os.Bundle;
import android.support.annotation.NonNull;
import com.etesync.syncadapter.resource.LocalTaskList;
import at.bitfire.ical4android.TaskProvider;
public class PackageChangedReceiver extends BroadcastReceiver {
@Override
@ -31,7 +37,17 @@ public class PackageChangedReceiver extends BroadcastReceiver {
App.log.info("Package (un)installed; OpenTasks provider now available = " + tasksInstalled);
// check all accounts and (de)activate OpenTasks if a CalDAV service is defined
// FIXME: Do something if we ever bring back tasks.
AccountManager am = AccountManager.get(context);
for (Account account : am.getAccountsByType(App.getAccountType())) {
if (tasksInstalled) {
if (ContentResolver.getIsSyncable(account, TaskProvider.ProviderName.OpenTasks.authority) <= 0) {
ContentResolver.setIsSyncable(account, TaskProvider.ProviderName.OpenTasks.authority, 1);
ContentResolver.setSyncAutomatically(account, TaskProvider.ProviderName.OpenTasks.authority, true);
ContentResolver.addPeriodicSync(account, TaskProvider.ProviderName.OpenTasks.authority, new Bundle(), Constants.DEFAULT_SYNC_INTERVAL);
}
} else {
ContentResolver.setIsSyncable(account, TaskProvider.ProviderName.OpenTasks.authority, 0);
}
}
}
}

View File

@ -31,7 +31,8 @@ public class CollectionInfo implements Serializable {
public enum Type {
ADDRESS_BOOK,
CALENDAR
CALENDAR,
TASK_LIST,
}
// FIXME: Shouldn't be exposed, as it's already saved in the journal. We just expose it for when we save for db.

View File

@ -0,0 +1,159 @@
/*
* 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 com.etesync.syncadapter.syncadapter;
import android.accounts.Account;
import android.content.AbstractThreadedSyncAdapter;
import android.content.ContentProviderClient;
import android.content.ContentResolver;
import android.content.Context;
import android.content.Intent;
import android.content.SyncResult;
import android.database.sqlite.SQLiteException;
import android.os.Bundle;
import com.etesync.syncadapter.AccountSettings;
import com.etesync.syncadapter.App;
import com.etesync.syncadapter.Constants;
import com.etesync.syncadapter.NotificationHelper;
import com.etesync.syncadapter.R;
import com.etesync.syncadapter.journalmanager.Exceptions;
import com.etesync.syncadapter.model.CollectionInfo;
import com.etesync.syncadapter.model.JournalEntity;
import com.etesync.syncadapter.model.JournalModel;
import com.etesync.syncadapter.model.ServiceEntity;
import com.etesync.syncadapter.resource.LocalTaskList;
import com.etesync.syncadapter.ui.DebugInfoActivity;
import org.dmfs.provider.tasks.TaskContract;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.logging.Level;
import at.bitfire.ical4android.CalendarStorageException;
import at.bitfire.ical4android.TaskProvider;
import io.requery.Persistable;
import io.requery.sql.EntityDataStore;
import lombok.Cleanup;
import okhttp3.HttpUrl;
import static com.etesync.syncadapter.Constants.KEY_ACCOUNT;
/**
* Synchronization manager for CalDAV collections; handles tasks ({@code VTODO}).
*/
public class TasksSyncAdapterService extends SyncAdapterService {
@Override
protected AbstractThreadedSyncAdapter syncAdapter() {
return new SyncAdapter(this);
}
private static class SyncAdapter extends SyncAdapterService.SyncAdapter {
public SyncAdapter(Context context) {
super(context);
}
@Override
public void onPerformSync(Account account, Bundle extras, String authority, ContentProviderClient providerClient, SyncResult syncResult) {
super.onPerformSync(account, extras, authority, providerClient, syncResult);
NotificationHelper notificationManager = new NotificationHelper(getContext(), "journals-tasks", Constants.NOTIFICATION_CALENDAR_SYNC);
notificationManager.cancel();
try {
@Cleanup TaskProvider provider = TaskProvider.acquire(getContext().getContentResolver(), TaskProvider.ProviderName.OpenTasks);
if (provider == null)
throw new CalendarStorageException("Couldn't access OpenTasks provider");
AccountSettings settings = new AccountSettings(getContext(), account);
if (!extras.containsKey(ContentResolver.SYNC_EXTRAS_MANUAL) && !checkSyncConditions(settings))
return;
new RefreshCollections(account, CollectionInfo.Type.CALENDAR).run();
updateLocalTaskLists(provider, account, settings);
HttpUrl principal = HttpUrl.get(settings.getUri());
for (LocalTaskList taskList : (LocalTaskList[]) LocalTaskList.find(account, provider, LocalTaskList.Factory.INSTANCE, TaskContract.TaskLists.SYNC_ENABLED + "!=0", null)) {
App.log.info("Synchronizing task list #" + taskList.getId() + " [" + taskList.getSyncId() + "]");
TasksSyncManager syncManager = new TasksSyncManager(getContext(), account, settings, extras, authority, provider, syncResult, taskList, principal);
syncManager.performSync();
}
} catch (Exceptions.ServiceUnavailableException e) {
syncResult.stats.numIoExceptions++;
syncResult.delayUntil = (e.retryAfter > 0) ? e.retryAfter : Constants.DEFAULT_RETRY_DELAY;
} catch (Exception | OutOfMemoryError e) {
if (e instanceof CalendarStorageException || e instanceof SQLiteException) {
App.log.log(Level.SEVERE, "Couldn't prepare local calendars", e);
syncResult.databaseError = true;
}
int syncPhase = R.string.sync_phase_journals;
String title = getContext().getString(R.string.sync_error_calendar, account.name);
notificationManager.setThrowable(e);
final Intent detailsIntent = notificationManager.getDetailsIntent();
detailsIntent.putExtra(KEY_ACCOUNT, account);
if (!(e instanceof Exceptions.UnauthorizedException)) {
detailsIntent.putExtra(DebugInfoActivity.KEY_AUTHORITY, authority);
detailsIntent.putExtra(DebugInfoActivity.KEY_PHASE, syncPhase);
}
notificationManager.notify(title, getContext().getString(syncPhase));
}
App.log.info("Task sync complete");
}
private void updateLocalTaskLists(TaskProvider provider, Account account, AccountSettings settings) throws CalendarStorageException {
EntityDataStore<Persistable> data = ((App) getContext().getApplicationContext()).getData();
ServiceEntity service = JournalModel.Service.fetch(data, account.name, CollectionInfo.Type.TASK_LIST);
Map<String, JournalEntity> remote = new HashMap<>();
List<JournalEntity> remoteJournals = JournalEntity.getJournals(data, service);
for (JournalEntity journalEntity : remoteJournals) {
remote.put(journalEntity.getUid(), journalEntity);
}
LocalTaskList[] local = (LocalTaskList[])LocalTaskList.find(account, provider, LocalTaskList.Factory.INSTANCE, null, null);
boolean updateColors = settings.getManageCalendarColors();
// delete obsolete local task lists
for (LocalTaskList list : local) {
String url = list.getSyncId();
if (!remote.containsKey(url)) {
App.log.fine("Deleting obsolete local task list" + url);
list.delete();
} else {
// remote CollectionInfo found for this local collection, update data
JournalEntity journalEntity = remote.get(url);
App.log.fine("Updating local task list " + url + " with " + journalEntity);
list.update(journalEntity, updateColors);
// we already have a local task list for this remote collection, don't take into consideration anymore
remote.remove(url);
}
}
// create new local task lists
for (String url : remote.keySet()) {
JournalEntity journalEntity = remote.get(url);
App.log.info("Adding local task list " + journalEntity);
LocalTaskList.create(account, provider, journalEntity);
}
}
}
}

View File

@ -0,0 +1,130 @@
/*
* 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 com.etesync.syncadapter.syncadapter;
import android.accounts.Account;
import android.content.Context;
import android.content.SyncResult;
import android.os.Bundle;
import com.etesync.syncadapter.AccountSettings;
import com.etesync.syncadapter.App;
import com.etesync.syncadapter.Constants;
import com.etesync.syncadapter.InvalidAccountException;
import com.etesync.syncadapter.R;
import com.etesync.syncadapter.journalmanager.Exceptions;
import com.etesync.syncadapter.journalmanager.JournalEntryManager;
import com.etesync.syncadapter.model.CollectionInfo;
import com.etesync.syncadapter.model.SyncEntry;
import com.etesync.syncadapter.resource.LocalResource;
import com.etesync.syncadapter.resource.LocalTask;
import com.etesync.syncadapter.resource.LocalTaskList;
import org.apache.commons.codec.Charsets;
import java.io.ByteArrayInputStream;
import java.io.IOException;
import java.io.InputStream;
import java.nio.charset.Charset;
import java.util.logging.Level;
import at.bitfire.ical4android.CalendarStorageException;
import at.bitfire.ical4android.InvalidCalendarException;
import at.bitfire.ical4android.Task;
import at.bitfire.ical4android.TaskProvider;
import at.bitfire.vcard4android.ContactsStorageException;
import okhttp3.HttpUrl;
public class TasksSyncManager extends SyncManager {
final private HttpUrl remote;
protected static final int MAX_MULTIGET = 30;
final protected TaskProvider provider;
public TasksSyncManager(Context context, Account account, AccountSettings settings, Bundle extras, String authority, TaskProvider provider, SyncResult result, LocalTaskList taskList, HttpUrl remote) throws InvalidAccountException, Exceptions.IntegrityException, Exceptions.GenericCryptoException {
super(context, account, settings, extras, authority, result, taskList.getName(), CollectionInfo.Type.TASK_LIST, account.name);
this.provider = provider;
localCollection = taskList;
this.remote = remote;
}
@Override
protected int notificationId() {
return Constants.NOTIFICATION_TASK_SYNC;
}
@Override
protected String getSyncErrorTitle() {
return context.getString(R.string.sync_error_tasks, account.name);
}
@Override
protected String getSyncSuccessfullyTitle() {
return context.getString(R.string.sync_successfully_tasks, info.displayName,
account.name);
}
@Override
protected boolean prepare() throws CalendarStorageException, ContactsStorageException {
if (!super.prepare())
return false;
journal = new JournalEntryManager(httpClient, remote, localTaskList().getName());
return true;
}
@Override
protected void prepareDirty() throws CalendarStorageException, ContactsStorageException {
super.prepareDirty();
}
// helpers
private LocalTaskList localTaskList() { return ((LocalTaskList)localCollection); }
protected void processSyncEntry(SyncEntry cEntry) throws IOException, ContactsStorageException, CalendarStorageException, InvalidCalendarException {
InputStream is = new ByteArrayInputStream(cEntry.getContent().getBytes(Charsets.UTF_8));
Task[] tasks = Task.fromStream(is, Charsets.UTF_8);
if (tasks.length == 0) {
App.log.warning("Received VCard without data, ignoring");
return;
} else if (tasks.length > 1)
App.log.warning("Received multiple VCALs, using first one");
Task task = tasks[0];
LocalTask local = (LocalTask) localCollection.getByUid(task.uid);
if (cEntry.isAction(SyncEntry.Actions.ADD) || cEntry.isAction(SyncEntry.Actions.CHANGE)) {
processTask(task, local);
} else {
App.log.info("Removing local record #" + local.getId() + " which has been deleted on the server");
local.delete();
}
}
private LocalResource processTask(final Task newData, LocalTask localTask) throws IOException, ContactsStorageException, CalendarStorageException {
// delete local event, if it exists
if (localTask != null) {
App.log.info("Updating " + newData.uid + " in local calendar");
localTask.setETag(newData.uid);
localTask.update(newData);
syncResult.stats.numUpdates++;
} else {
App.log.info("Adding " + newData.uid + " to local calendar");
localTask = new LocalTask(localTaskList(), newData, newData.uid, newData.uid);
localTask.add();
syncResult.stats.numInserts++;
}
return localTask;
}
}

View File

@ -34,6 +34,8 @@ import com.etesync.syncadapter.R;
import com.etesync.syncadapter.ui.setup.LoginCredentials;
import com.etesync.syncadapter.ui.setup.LoginCredentialsChangeFragment;
import at.bitfire.ical4android.TaskProvider;
import static com.etesync.syncadapter.Constants.KEY_ACCOUNT;
public class AccountSettingsActivity extends BaseActivity {
@ -150,6 +152,27 @@ public class AccountSettingsActivity extends BaseActivity {
prefSyncCalendars.setSummary(R.string.settings_sync_summary_not_available);
}
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)
prefSyncTasks.setSummary(R.string.settings_sync_summary_manually);
else
prefSyncTasks.setSummary(getString(R.string.settings_sync_summary_periodically, prefSyncTasks.getEntry()));
prefSyncTasks.setOnPreferenceChangeListener(new Preference.OnPreferenceChangeListener() {
@Override
public boolean onPreferenceChange(Preference preference, Object newValue) {
settings.setSyncInterval(TaskProvider.ProviderName.OpenTasks.authority, Long.parseLong((String)newValue));
getLoaderManager().restartLoader(0, getArguments(), AccountSettingsFragment.this);
return false;
}
});
} else {
prefSyncTasks.setEnabled(false);
prefSyncTasks.setSummary(R.string.settings_sync_summary_not_available);
}
final SwitchPreferenceCompat prefWifiOnly = (SwitchPreferenceCompat)findPreference("sync_wifi_only");
prefWifiOnly.setChecked(settings.getSyncWifiOnly());
prefWifiOnly.setOnPreferenceChangeListener(new Preference.OnPreferenceChangeListener() {

View File

@ -224,6 +224,7 @@
<string name="settings_sync_summary_periodically" tools:ignore="PluralsCandidate">%s and immediately on local changes</string>
<string name="settings_sync_summary_not_available">Not available</string>
<string name="settings_sync_interval_calendars">Calendars sync. interval</string>
<string name="settings_sync_interval_tasks">Tasks sync. interval</string>
<string-array name="settings_sync_interval_seconds" translateable="false">
<item>-1</item>
<item>300</item>
@ -291,6 +292,7 @@
<string name="sync_error_permissions_text">Additional permissions required</string>
<string name="sync_error_calendar">Calendar sync failed (%s)</string>
<string name="sync_error_contacts">Contacts sync failed (%s)</string>
<string name="sync_error_tasks">Tasks sync failed (%s)</string>
<string name="sync_error">Error while %s</string>
<string name="sync_error_integrity">Integrity error while %s</string>
<string name="sync_error_http_dav">Server error while %s</string>
@ -308,6 +310,7 @@
<string name="sync_phase_post_processing">post processing</string>
<string name="sync_error_unauthorized">Authentication failed</string>
<string name="sync_error_user_inactive">User is inactive</string>
<string name="sync_successfully_tasks" formatted="false">Task list \"%s\" modified (%s)</string>
<string name="sync_successfully_calendar" formatted="false">Calendar \"%s\" modified (%s)</string>
<string name="sync_successfully_contacts" formatted="false">Contacts modified (%s)</string>
<string name="sync_successfully_modified" formatted="false">%s modified.</string>

View File

@ -50,6 +50,13 @@
android:entries="@array/settings_sync_interval_names"
android:entryValues="@array/settings_sync_interval_seconds" />
<ListPreference
android:key="sync_interval_tasks"
android:persistent="false"
android:title="@string/settings_sync_interval_tasks"
android:entries="@array/settings_sync_interval_names"
android:entryValues="@array/settings_sync_interval_seconds" />
<SwitchPreferenceCompat
android:key="sync_wifi_only"
android:persistent="false"

View File

@ -0,0 +1,15 @@
<!--
~ Copyright (c) 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
-->
<sync-adapter xmlns:android="http://schemas.android.com/apk/res/android"
android:accountType="com.etesync.syncadapter"
android:contentAuthority="org.dmfs.tasks"
android:allowParallelSyncs="true"
android:supportsUploading="true"
android:isAlwaysSyncable="true"
android:userVisible="true" />