Commit 557e69f1 authored by Ricki Hirner's avatar Ricki Hirner 🐑

Sync manager optimization

* allow cancellation of synchronization within appropriate time
* sync error notification: use loader, show all accounts, show whether JB Workaround is installed, reorder
parent a68717de
......@@ -62,47 +62,10 @@ public class HttpClient extends OkHttpClient {
protected String username, password;
public HttpClient() {
super();
context = null;
initialize();
}
public HttpClient(Context context, String username, String password, boolean preemptive) {
protected HttpClient(Context context) {
super();
this.context = context;
initialize();
// authentication
this.username = username;
this.password = password;
if (preemptive)
networkInterceptors().add(new PreemptiveAuthenticationInterceptor(username, password));
else
setAuthenticator(new BasicDigestAuthenticator(null, username, password));
}
/**
* Creates a new HttpClient (based on another one) which can be used to download external resources:
* 1. it does not use preemptive authentication
* 2. it only authenticates against a given host
* @param client user name and password from this client will be used
* @param host authentication will be restricted to this host
*/
public HttpClient(HttpClient client, String host) {
super();
context = client.context;
initialize();
username = client.username;
password = client.password;
setAuthenticator(new BasicDigestAuthenticator(host, username, password));
}
protected void initialize() {
if (context != null) {
// use MemorizingTrustManager to manage self-signed certificates
MemorizingTrustManager mtm = new MemorizingTrustManager(context);
......@@ -131,6 +94,39 @@ public class HttpClient extends OkHttpClient {
enableLogs();
}
public HttpClient(Context context, String username, String password, boolean preemptive) {
this(context);
// authentication
this.username = username;
this.password = password;
if (preemptive)
networkInterceptors().add(new PreemptiveAuthenticationInterceptor(username, password));
else
setAuthenticator(new BasicDigestAuthenticator(null, username, password));
}
/**
* Creates a new HttpClient (based on another one) which can be used to download external resources:
* 1. it does not use preemptive authentication
* 2. it only authenticates against a given host
* @param client user name and password from this client will be used
* @param host authentication will be restricted to this host
*/
public HttpClient(HttpClient client, String host) {
this(client.context);
username = client.username;
password = client.password;
setAuthenticator(new BasicDigestAuthenticator(host, username, password));
}
// for testing (mock server doesn't need auth)
protected HttpClient() {
this(null, null, null, false);
}
protected void enableLogs() {
interceptors().add(loggingInterceptor);
}
......
......@@ -59,8 +59,8 @@ public class CalendarSyncManager extends SyncManager {
protected static final int MAX_MULTIGET = 20;
public CalendarSyncManager(Context context, Account account, Bundle extras, SyncResult result, LocalCalendar calendar) {
super(Constants.NOTIFICATION_CALENDAR_SYNC, context, account, extras, result);
public CalendarSyncManager(Context context, Account account, Bundle extras, String authority, SyncResult result, LocalCalendar calendar) {
super(Constants.NOTIFICATION_CALENDAR_SYNC, context, account, extras, authority, result);
localCollection = calendar;
}
......@@ -130,6 +130,8 @@ public class CalendarSyncManager extends SyncManager {
// download new/updated iCalendars from server
for (DavResource[] bunch : ArrayUtils.partition(toDownload.toArray(new DavResource[toDownload.size()]), MAX_MULTIGET)) {
if (Thread.interrupted())
return;
Constants.log.info("Downloading " + StringUtils.join(bunch, ", "));
if (bunch.length == 1) {
......
......@@ -55,7 +55,7 @@ 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, syncResult, calendar);
CalendarSyncManager syncManager = new CalendarSyncManager(getContext(), account, extras, authority, syncResult, calendar);
syncManager.performSync();
}
} catch (CalendarStorageException e) {
......
......@@ -48,7 +48,7 @@ public class ContactsSyncAdapterService extends Service {
public void onPerformSync(Account account, Bundle extras, String authority, ContentProviderClient provider, SyncResult syncResult) {
Constants.log.info("Starting address book sync (" + authority + ")");
ContactsSyncManager syncManager = new ContactsSyncManager(getContext(), account, extras, provider, syncResult);
ContactsSyncManager syncManager = new ContactsSyncManager(getContext(), account, extras, authority, provider, syncResult);
syncManager.performSync();
Constants.log.info("Address book sync complete");
......
......@@ -63,8 +63,8 @@ public class ContactsSyncManager extends SyncManager {
protected boolean hasVCard4;
public ContactsSyncManager(Context context, Account account, Bundle extras, ContentProviderClient provider, SyncResult result) {
super(Constants.NOTIFICATION_CONTACTS_SYNC, context, account, extras, result);
public ContactsSyncManager(Context context, Account account, Bundle extras, String authority, ContentProviderClient provider, SyncResult result) {
super(Constants.NOTIFICATION_CONTACTS_SYNC, context, account, extras, authority, result);
this.provider = provider;
}
......@@ -140,6 +140,9 @@ public class ContactsSyncManager extends SyncManager {
// download new/updated VCards from server
for (DavResource[] bunch : ArrayUtils.partition(toDownload.toArray(new DavResource[toDownload.size()]), MAX_MULTIGET)) {
if (Thread.interrupted())
return;
Constants.log.info("Downloading " + StringUtils.join(bunch, ", "));
if (bunch.length == 1) {
......
......@@ -31,6 +31,7 @@ import java.util.Set;
import java.util.UUID;
import at.bitfire.dav4android.DavResource;
import at.bitfire.dav4android.exception.ConflictException;
import at.bitfire.dav4android.exception.DavException;
import at.bitfire.dav4android.exception.HttpException;
import at.bitfire.dav4android.exception.UnauthorizedException;
......@@ -68,6 +69,7 @@ abstract public class SyncManager {
protected final Context context;
protected final Account account;
protected final Bundle extras;
protected final String authority;
protected final SyncResult syncResult;
protected final AccountSettings settings;
......@@ -92,10 +94,11 @@ abstract public class SyncManager {
public SyncManager(int notificationId, Context context, Account account, Bundle extras, SyncResult syncResult) {
public SyncManager(int notificationId, Context context, Account account, Bundle extras, String authority, SyncResult syncResult) {
this.context = context;
this.account = account;
this.extras = extras;
this.authority = authority;
this.syncResult = syncResult;
// get account settings and generate httpClient
......@@ -115,6 +118,8 @@ abstract public class SyncManager {
Constants.log.info("Preparing synchronization");
prepare();
if (Thread.interrupted())
return;
syncPhase = SYNC_PHASE_QUERY_CAPABILITIES;
Constants.log.info("Querying capabilities");
queryCapabilities();
......@@ -123,6 +128,8 @@ abstract public class SyncManager {
Constants.log.info("Processing locally deleted entries");
processLocallyDeleted();
if (Thread.interrupted())
return;
syncPhase = SYNC_PHASE_PREPARE_DIRTY;
Constants.log.info("Locally preparing dirty entries");
prepareDirty();
......@@ -138,10 +145,14 @@ abstract public class SyncManager {
Constants.log.info("Listing local entries");
listLocal();
if (Thread.interrupted())
return;
syncPhase = SYNC_PHASE_LIST_REMOTE;
Constants.log.info("Listing remote entries");
listRemote();
if (Thread.interrupted())
return;
syncPhase = SYNC_PHASE_COMPARE_LOCAL_REMOTE;
Constants.log.info("Comparing local/remote entries");
compareLocalRemote();
......@@ -197,6 +208,7 @@ abstract public class SyncManager {
detailsIntent = new Intent(context, DebugInfoActivity.class);
detailsIntent.putExtra(DebugInfoActivity.KEY_EXCEPTION, e);
detailsIntent.putExtra(DebugInfoActivity.KEY_ACCOUNT, account);
detailsIntent.putExtra(DebugInfoActivity.KEY_AUTHORITY, authority);
detailsIntent.putExtra(DebugInfoActivity.KEY_PHASE, syncPhase);
}
......@@ -225,7 +237,6 @@ abstract public class SyncManager {
notification = builder.getNotification();
}
notificationManager.notify(account.name, notificationId, notification);
}
}
......@@ -234,11 +245,18 @@ abstract public class SyncManager {
abstract protected void queryCapabilities() throws IOException, HttpException, DavException, CalendarStorageException, ContactsStorageException;
/**
* Process locally deleted entries (DELETE them on the server as well).
* Checks Thread.interrupted() before each request to allow quick sync cancellation.
*/
protected void processLocallyDeleted() throws CalendarStorageException, ContactsStorageException {
// Remove locally deleted entries from server (if they have a name, i.e. if they were uploaded before),
// but only if they don't have changed on the server. Then finally remove them from the local address book.
LocalResource[] localList = localCollection.getDeleted();
for (LocalResource local : localList) {
if (Thread.interrupted())
return;
final String fileName = local.getFileName();
if (!TextUtils.isEmpty(fileName)) {
Constants.log.info(fileName + " has been deleted locally -> deleting from server");
......@@ -246,7 +264,7 @@ abstract public class SyncManager {
new DavResource(httpClient, collectionURL.newBuilder().addPathSegment(fileName).build())
.delete(local.getETag());
} catch (IOException|HttpException e) {
Constants.log.warn("Couldn't delete " + fileName + " from server");
Constants.log.warn("Couldn't delete " + fileName + " from server; ignoring (may be downloaded again)");
}
} else
Constants.log.info("Removing local record #" + local.getId() + " which has been deleted locally and was never uploaded");
......@@ -266,9 +284,16 @@ abstract public class SyncManager {
abstract protected RequestBody prepareUpload(LocalResource resource) throws IOException, CalendarStorageException, ContactsStorageException;
/**
* Uploads dirty records to the server, using a PUT request for each record.
* Checks Thread.interrupted() before each request to allow quick sync cancellation.
*/
protected void uploadDirty() throws IOException, HttpException, CalendarStorageException, ContactsStorageException {
// upload dirty contacts
for (LocalResource local : localCollection.getDirty()) {
if (Thread.interrupted())
return;
final String fileName = local.getFileName();
DavResource remote = new DavResource(httpClient, collectionURL.newBuilder().addPathSegment(fileName).build());
......@@ -281,14 +306,13 @@ abstract public class SyncManager {
if (local.getETag() == null) {
Constants.log.info("Uploading new record " + fileName);
remote.put(body, null, true);
// TODO handle 30x
} else {
Constants.log.info("Uploading locally modified record " + fileName);
remote.put(body, local.getETag(), false);
// TODO handle 30x
}
} catch (PreconditionFailedException e) {
} catch (ConflictException|PreconditionFailedException e) {
// we can't interact with the user to resolve the conflict, so we treat 409 like 412
Constants.log.info("Resource has been modified on the server before upload, ignoring", e);
}
......@@ -396,6 +420,7 @@ abstract public class SyncManager {
/**
* Downloads the remote resources in {@link #toDownload} and stores them locally.
* Must check Thread.interrupted() periodically to allow quick sync cancellation.
*/
abstract protected void downloadRemote() throws IOException, HttpException, DavException, ContactsStorageException, CalendarStorageException;
......
......@@ -61,7 +61,7 @@ public class TasksSyncAdapterService extends Service {
for (LocalTaskList taskList : (LocalTaskList[])LocalTaskList.find(account, provider, LocalTaskList.Factory.INSTANCE, null, null)) {
Constants.log.info("Synchronizing task list #" + taskList.getId() + ", URL: " + taskList.getSyncId());
TasksSyncManager syncManager = new TasksSyncManager(getContext(), account, extras, provider, syncResult, taskList);
TasksSyncManager syncManager = new TasksSyncManager(getContext(), account, extras, authority, provider, syncResult, taskList);
syncManager.performSync();
}
} catch (CalendarStorageException e) {
......
......@@ -62,8 +62,8 @@ public class TasksSyncManager extends SyncManager {
final protected TaskProvider provider;
public TasksSyncManager(Context context, Account account, Bundle extras, TaskProvider provider, SyncResult result, LocalTaskList taskList) {
super(Constants.NOTIFICATION_TASK_SYNC, context, account, extras, result);
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);
this.provider = provider;
localCollection = taskList;
}
......@@ -128,6 +128,9 @@ public class TasksSyncManager extends SyncManager {
// download new/updated iCalendars from server
for (DavResource[] bunch : ArrayUtils.partition(toDownload.toArray(new DavResource[toDownload.size()]), MAX_MULTIGET)) {
if (Thread.interrupted())
return;
Constants.log.info("Downloading " + StringUtils.join(bunch, ", "));
if (bunch.length == 1) {
......
Subproject commit 2083d075d3b4a4b9ac0a930af1d019547d7dcf07
Subproject commit a22eb4eb193c8f22180369791df3671e1cab6f1c
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment