mirror of
https://github.com/etesync/android
synced 2025-05-23 09:18:49 +00:00
Contact synchronization logic
* use VERSION_CODE and buildTime from BuildConfig * new HTTP User-Agent, VCard PRODID values * contact sync: store CTag in SyncState * sync logic: upload contacts, check CTag, multiget
This commit is contained in:
parent
4f7f3b851a
commit
d024cdb495
@ -17,6 +17,11 @@ android {
|
|||||||
applicationId "at.bitfire.davdroid"
|
applicationId "at.bitfire.davdroid"
|
||||||
minSdkVersion 14
|
minSdkVersion 14
|
||||||
targetSdkVersion 23
|
targetSdkVersion 23
|
||||||
|
|
||||||
|
versionCode 73
|
||||||
|
versionName "0.9-alpha1"
|
||||||
|
|
||||||
|
buildConfigField "java.util.Date", "buildTime", "new java.util.Date()"
|
||||||
}
|
}
|
||||||
|
|
||||||
buildTypes {
|
buildTypes {
|
||||||
|
@ -9,7 +9,6 @@
|
|||||||
|
|
||||||
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
|
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
|
||||||
package="at.bitfire.davdroid"
|
package="at.bitfire.davdroid"
|
||||||
android:versionCode="72" android:versionName="0.8.4.1"
|
|
||||||
android:installLocation="internalOnly">
|
android:installLocation="internalOnly">
|
||||||
|
|
||||||
<uses-permission android:name="android.permission.INTERNET" />
|
<uses-permission android:name="android.permission.INTERNET" />
|
||||||
|
@ -14,13 +14,12 @@ import org.slf4j.LoggerFactory;
|
|||||||
|
|
||||||
public class Constants {
|
public class Constants {
|
||||||
public static final String
|
public static final String
|
||||||
APP_VERSION = "0.8.4.1",
|
|
||||||
ACCOUNT_TYPE = "bitfire.at.davdroid",
|
ACCOUNT_TYPE = "bitfire.at.davdroid",
|
||||||
WEB_URL_MAIN = "https://davdroid.bitfire.at/?pk_campaign=davdroid-app",
|
WEB_URL_MAIN = "https://davdroid.bitfire.at/?pk_campaign=davdroid-app",
|
||||||
WEB_URL_HELP = "https://davdroid.bitfire.at/configuration?pk_campaign=davdroid-app",
|
WEB_URL_HELP = "https://davdroid.bitfire.at/configuration?pk_campaign=davdroid-app",
|
||||||
WEB_URL_VIEW_LOGS = "https://github.com/bitfireAT/davdroid/wiki/How-to-view-the-logs";
|
WEB_URL_VIEW_LOGS = "https://github.com/bitfireAT/davdroid/wiki/How-to-view-the-logs";
|
||||||
|
|
||||||
public static final ProdId ICAL_PRODID = new ProdId("-//bitfire web engineering//DAVdroid " + Constants.APP_VERSION + " (ical4j 2.0-beta1)//EN");
|
public static final ProdId ICAL_PRODID = new ProdId("-//bitfire web engineering//DAVdroid " + BuildConfig.VERSION_CODE + " (ical4j 2.0-beta1)//EN");
|
||||||
|
|
||||||
public static final Logger log = LoggerFactory.getLogger("DAVdroid");
|
public static final Logger log = LoggerFactory.getLogger("DAVdroid");
|
||||||
}
|
}
|
||||||
|
@ -8,6 +8,8 @@
|
|||||||
|
|
||||||
package at.bitfire.davdroid;
|
package at.bitfire.davdroid;
|
||||||
|
|
||||||
|
import android.os.Build;
|
||||||
|
|
||||||
import com.squareup.okhttp.Authenticator;
|
import com.squareup.okhttp.Authenticator;
|
||||||
import com.squareup.okhttp.Credentials;
|
import com.squareup.okhttp.Credentials;
|
||||||
import com.squareup.okhttp.Interceptor;
|
import com.squareup.okhttp.Interceptor;
|
||||||
@ -18,6 +20,7 @@ import com.squareup.okhttp.logging.HttpLoggingInterceptor;
|
|||||||
|
|
||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
import java.net.Proxy;
|
import java.net.Proxy;
|
||||||
|
import java.text.SimpleDateFormat;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
import java.util.concurrent.TimeUnit;
|
import java.util.concurrent.TimeUnit;
|
||||||
|
|
||||||
@ -26,8 +29,24 @@ import lombok.RequiredArgsConstructor;
|
|||||||
|
|
||||||
public class HttpClient extends OkHttpClient {
|
public class HttpClient extends OkHttpClient {
|
||||||
|
|
||||||
protected static final String
|
protected static final String HEADER_AUTHORIZATION = "Authorization";
|
||||||
HEADER_AUTHORIZATION = "Authorization";
|
|
||||||
|
final static UserAgentInterceptor userAgentInterceptor = new UserAgentInterceptor();
|
||||||
|
final static HttpLoggingInterceptor loggingInterceptor = new HttpLoggingInterceptor(new HttpLoggingInterceptor.Logger() {
|
||||||
|
@Override
|
||||||
|
public void log(String message) {
|
||||||
|
Constants.log.trace(message);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
static {
|
||||||
|
loggingInterceptor.setLevel(HttpLoggingInterceptor.Level.BODY);
|
||||||
|
}
|
||||||
|
|
||||||
|
static final String userAgent;
|
||||||
|
static {
|
||||||
|
String date = new SimpleDateFormat("yyyy/MM/dd").format(BuildConfig.buildTime);
|
||||||
|
userAgent = "DAVdroid/" + BuildConfig.VERSION_NAME + " (" + date + "; dav4android) Android/" + Build.VERSION.RELEASE;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
public HttpClient() {
|
public HttpClient() {
|
||||||
@ -40,13 +59,13 @@ public class HttpClient extends OkHttpClient {
|
|||||||
super();
|
super();
|
||||||
initialize();
|
initialize();
|
||||||
|
|
||||||
// authentication and User-Agent
|
enableLogs();
|
||||||
|
|
||||||
|
// authentication
|
||||||
if (preemptive)
|
if (preemptive)
|
||||||
networkInterceptors().add(new PreemptiveAuthenticationInterceptor(username, password));
|
networkInterceptors().add(new PreemptiveAuthenticationInterceptor(username, password));
|
||||||
else
|
else
|
||||||
setAuthenticator(new DavAuthenticator(username, password));
|
setAuthenticator(new DavAuthenticator(username, password));
|
||||||
|
|
||||||
enableLogs();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
@ -54,21 +73,27 @@ public class HttpClient extends OkHttpClient {
|
|||||||
// don't follow redirects automatically because this may rewrite DAV methods to GET
|
// don't follow redirects automatically because this may rewrite DAV methods to GET
|
||||||
setFollowRedirects(false);
|
setFollowRedirects(false);
|
||||||
|
|
||||||
// timeouts
|
|
||||||
setConnectTimeout(20, TimeUnit.SECONDS);
|
setConnectTimeout(20, TimeUnit.SECONDS);
|
||||||
setWriteTimeout(15, TimeUnit.SECONDS);
|
setWriteTimeout(15, TimeUnit.SECONDS);
|
||||||
setReadTimeout(45, TimeUnit.SECONDS);
|
setReadTimeout(45, TimeUnit.SECONDS);
|
||||||
|
|
||||||
|
// add User-Agent to every request
|
||||||
|
networkInterceptors().add(userAgentInterceptor);
|
||||||
}
|
}
|
||||||
|
|
||||||
protected void enableLogs() {
|
protected void enableLogs() {
|
||||||
HttpLoggingInterceptor logging = new HttpLoggingInterceptor(new HttpLoggingInterceptor.Logger() {
|
interceptors().add(loggingInterceptor);
|
||||||
@Override
|
}
|
||||||
public void log(String message) {
|
|
||||||
at.bitfire.dav4android.Constants.log.trace(message);
|
|
||||||
|
static class UserAgentInterceptor implements Interceptor {
|
||||||
|
@Override
|
||||||
|
public Response intercept(Chain chain) throws IOException {
|
||||||
|
Request request = chain.request().newBuilder()
|
||||||
|
.header("User-Agent", userAgent)
|
||||||
|
.build();
|
||||||
|
return chain.proceed(request);
|
||||||
}
|
}
|
||||||
});
|
|
||||||
logging.setLevel(HttpLoggingInterceptor.Level.BODY);
|
|
||||||
interceptors().add(logging);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
@ -78,8 +103,7 @@ public class HttpClient extends OkHttpClient {
|
|||||||
|
|
||||||
@Override
|
@Override
|
||||||
public Response intercept(Chain chain) throws IOException {
|
public Response intercept(Chain chain) throws IOException {
|
||||||
Request request = chain.request();
|
Request request = chain.request().newBuilder()
|
||||||
request = request.newBuilder()
|
|
||||||
.header("Authorization", Credentials.basic(username, password))
|
.header("Authorization", Credentials.basic(username, password))
|
||||||
.build();
|
.build();
|
||||||
return chain.proceed(request);
|
return chain.proceed(request);
|
||||||
|
@ -9,6 +9,8 @@ package at.bitfire.davdroid.resource;
|
|||||||
|
|
||||||
import android.accounts.Account;
|
import android.accounts.Account;
|
||||||
import android.content.ContentProviderClient;
|
import android.content.ContentProviderClient;
|
||||||
|
import android.os.Bundle;
|
||||||
|
import android.os.Parcel;
|
||||||
import android.provider.ContactsContract;
|
import android.provider.ContactsContract;
|
||||||
|
|
||||||
import at.bitfire.davdroid.Constants;
|
import at.bitfire.davdroid.Constants;
|
||||||
@ -18,14 +20,25 @@ import at.bitfire.vcard4android.AndroidContactFactory;
|
|||||||
import at.bitfire.vcard4android.AndroidGroupFactory;
|
import at.bitfire.vcard4android.AndroidGroupFactory;
|
||||||
import at.bitfire.vcard4android.Contact;
|
import at.bitfire.vcard4android.Contact;
|
||||||
import at.bitfire.vcard4android.ContactsStorageException;
|
import at.bitfire.vcard4android.ContactsStorageException;
|
||||||
|
import lombok.Cleanup;
|
||||||
|
import lombok.Synchronized;
|
||||||
|
|
||||||
|
|
||||||
public class LocalAddressBook extends AndroidAddressBook {
|
public class LocalAddressBook extends AndroidAddressBook {
|
||||||
|
|
||||||
|
protected static final String SYNC_STATE_CTAG = "ctag";
|
||||||
|
|
||||||
|
private Bundle syncState = new Bundle();
|
||||||
|
|
||||||
|
|
||||||
public LocalAddressBook(Account account, ContentProviderClient provider) {
|
public LocalAddressBook(Account account, ContentProviderClient provider) {
|
||||||
super(account, provider, AndroidGroupFactory.INSTANCE, LocalContact.Factory.INSTANCE);
|
super(account, provider, AndroidGroupFactory.INSTANCE, LocalContact.Factory.INSTANCE);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns an array of local contacts, excluding those which have been modified locally (and not uploaded yet).
|
||||||
|
*/
|
||||||
public LocalContact[] getAll() throws ContactsStorageException {
|
public LocalContact[] getAll() throws ContactsStorageException {
|
||||||
LocalContact contacts[] = (LocalContact[])queryContacts(null, null);
|
LocalContact contacts[] = (LocalContact[])queryContacts(null, null);
|
||||||
return contacts;
|
return contacts;
|
||||||
@ -35,14 +48,14 @@ public class LocalAddressBook extends AndroidAddressBook {
|
|||||||
* Returns an array of local contacts which have been deleted locally. (DELETED != 0).
|
* Returns an array of local contacts which have been deleted locally. (DELETED != 0).
|
||||||
*/
|
*/
|
||||||
public LocalContact[] getDeleted() throws ContactsStorageException {
|
public LocalContact[] getDeleted() throws ContactsStorageException {
|
||||||
return (LocalContact[])queryContacts(ContactsContract.RawContacts.DELETED + " != 0", null);
|
return (LocalContact[])queryContacts(ContactsContract.RawContacts.DELETED + "!=0", null);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Returns an array of local contacts which have been changed locally (DIRTY != 0).
|
* Returns an array of local contacts which have been changed locally (DIRTY != 0).
|
||||||
*/
|
*/
|
||||||
public LocalContact[] getDirty() throws ContactsStorageException {
|
public LocalContact[] getDirty() throws ContactsStorageException {
|
||||||
return (LocalContact[])queryContacts(ContactsContract.RawContacts.DIRTY + " != 0", null);
|
return (LocalContact[])queryContacts(ContactsContract.RawContacts.DIRTY + "!=0", null);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -52,4 +65,35 @@ public class LocalAddressBook extends AndroidAddressBook {
|
|||||||
return (LocalContact[])queryContacts(AndroidContact.COLUMN_FILENAME + " IS NULL", null);
|
return (LocalContact[])queryContacts(AndroidContact.COLUMN_FILENAME + " IS NULL", null);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
protected void readSyncState() throws ContactsStorageException {
|
||||||
|
@Cleanup("recycle") Parcel parcel = Parcel.obtain();
|
||||||
|
byte[] raw = getSyncState();
|
||||||
|
if (raw != null) {
|
||||||
|
parcel.unmarshall(raw, 0, raw.length);
|
||||||
|
parcel.setDataPosition(0);
|
||||||
|
syncState = parcel.readBundle();
|
||||||
|
} else
|
||||||
|
syncState.clear();
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getCTag() throws ContactsStorageException {
|
||||||
|
synchronized (syncState) {
|
||||||
|
readSyncState();
|
||||||
|
return syncState.getString(SYNC_STATE_CTAG);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setCTag(String cTag) throws ContactsStorageException {
|
||||||
|
synchronized (syncState) {
|
||||||
|
readSyncState();
|
||||||
|
syncState.putString(SYNC_STATE_CTAG, cTag);
|
||||||
|
|
||||||
|
// write sync state bundle
|
||||||
|
@Cleanup("recycle") Parcel parcel = Parcel.obtain();
|
||||||
|
parcel.writeBundle(syncState);
|
||||||
|
setSyncState(parcel.marshall());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
@ -12,13 +12,18 @@ import android.content.ContentValues;
|
|||||||
import android.os.RemoteException;
|
import android.os.RemoteException;
|
||||||
import android.provider.ContactsContract;
|
import android.provider.ContactsContract;
|
||||||
|
|
||||||
|
import at.bitfire.davdroid.BuildConfig;
|
||||||
import at.bitfire.vcard4android.AndroidAddressBook;
|
import at.bitfire.vcard4android.AndroidAddressBook;
|
||||||
import at.bitfire.vcard4android.AndroidContact;
|
import at.bitfire.vcard4android.AndroidContact;
|
||||||
import at.bitfire.vcard4android.AndroidContactFactory;
|
import at.bitfire.vcard4android.AndroidContactFactory;
|
||||||
import at.bitfire.vcard4android.Contact;
|
import at.bitfire.vcard4android.Contact;
|
||||||
import at.bitfire.vcard4android.ContactsStorageException;
|
import at.bitfire.vcard4android.ContactsStorageException;
|
||||||
|
import ezvcard.Ezvcard;
|
||||||
|
|
||||||
public class LocalContact extends AndroidContact {
|
public class LocalContact extends AndroidContact {
|
||||||
|
static {
|
||||||
|
Contact.productID = "+//IDN bitfire.at//DAVdroid/" + BuildConfig.VERSION_NAME + " ez-vcard/" + Ezvcard.VERSION;
|
||||||
|
}
|
||||||
|
|
||||||
protected LocalContact(AndroidAddressBook addressBook, long id, String fileName, String eTag) {
|
protected LocalContact(AndroidAddressBook addressBook, long id, String fileName, String eTag) {
|
||||||
super(addressBook, id, fileName, eTag);
|
super(addressBook, id, fileName, eTag);
|
||||||
@ -28,6 +33,17 @@ public class LocalContact extends AndroidContact {
|
|||||||
super(addressBook, contact, fileName, eTag);
|
super(addressBook, contact, fileName, eTag);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public void clearDirty(String eTag) throws ContactsStorageException {
|
||||||
|
try {
|
||||||
|
ContentValues values = new ContentValues(1);
|
||||||
|
values.put(COLUMN_ETAG, eTag);
|
||||||
|
values.put(ContactsContract.RawContacts.DIRTY, 0);
|
||||||
|
addressBook.provider.update(rawContactSyncURI(), values, null, null);
|
||||||
|
} catch (RemoteException e) {
|
||||||
|
throw new ContactsStorageException("Couldn't clear dirty flag", e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
public void updateUID(String uid) throws ContactsStorageException {
|
public void updateUID(String uid) throws ContactsStorageException {
|
||||||
try {
|
try {
|
||||||
ContentValues values = new ContentValues(1);
|
ContentValues values = new ContentValues(1);
|
||||||
|
@ -11,6 +11,7 @@ import android.accounts.Account;
|
|||||||
import android.app.Service;
|
import android.app.Service;
|
||||||
import android.content.AbstractThreadedSyncAdapter;
|
import android.content.AbstractThreadedSyncAdapter;
|
||||||
import android.content.ContentProviderClient;
|
import android.content.ContentProviderClient;
|
||||||
|
import android.content.ContentResolver;
|
||||||
import android.content.Context;
|
import android.content.Context;
|
||||||
import android.content.Intent;
|
import android.content.Intent;
|
||||||
import android.content.SyncResult;
|
import android.content.SyncResult;
|
||||||
@ -26,30 +27,43 @@ import com.squareup.okhttp.ResponseBody;
|
|||||||
|
|
||||||
import org.apache.commons.io.Charsets;
|
import org.apache.commons.io.Charsets;
|
||||||
|
|
||||||
|
import java.io.ByteArrayInputStream;
|
||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
|
import java.io.InputStream;
|
||||||
|
import java.nio.charset.Charset;
|
||||||
import java.util.HashMap;
|
import java.util.HashMap;
|
||||||
import java.util.HashSet;
|
import java.util.HashSet;
|
||||||
|
import java.util.LinkedList;
|
||||||
|
import java.util.List;
|
||||||
import java.util.Map;
|
import java.util.Map;
|
||||||
import java.util.Set;
|
import java.util.Set;
|
||||||
import java.util.UUID;
|
|
||||||
|
|
||||||
import at.bitfire.dav4android.DavAddressBook;
|
import at.bitfire.dav4android.DavAddressBook;
|
||||||
import at.bitfire.dav4android.DavResource;
|
import at.bitfire.dav4android.DavResource;
|
||||||
import at.bitfire.dav4android.exception.DavException;
|
import at.bitfire.dav4android.exception.DavException;
|
||||||
import at.bitfire.dav4android.exception.HttpException;
|
import at.bitfire.dav4android.exception.HttpException;
|
||||||
|
import at.bitfire.dav4android.property.AddressData;
|
||||||
|
import at.bitfire.dav4android.property.GetCTag;
|
||||||
|
import at.bitfire.dav4android.property.GetContentType;
|
||||||
import at.bitfire.dav4android.property.GetETag;
|
import at.bitfire.dav4android.property.GetETag;
|
||||||
import at.bitfire.dav4android.property.SupportedAddressData;
|
import at.bitfire.dav4android.property.SupportedAddressData;
|
||||||
|
import at.bitfire.davdroid.ArrayUtils;
|
||||||
import at.bitfire.davdroid.Constants;
|
import at.bitfire.davdroid.Constants;
|
||||||
import at.bitfire.davdroid.HttpClient;
|
import at.bitfire.davdroid.HttpClient;
|
||||||
import at.bitfire.davdroid.resource.LocalAddressBook;
|
import at.bitfire.davdroid.resource.LocalAddressBook;
|
||||||
import at.bitfire.davdroid.resource.LocalContact;
|
import at.bitfire.davdroid.resource.LocalContact;
|
||||||
import at.bitfire.vcard4android.Contact;
|
import at.bitfire.vcard4android.Contact;
|
||||||
|
import at.bitfire.vcard4android.ContactsStorageException;
|
||||||
import ezvcard.VCardVersion;
|
import ezvcard.VCardVersion;
|
||||||
import ezvcard.property.Uid;
|
import ezvcard.property.Uid;
|
||||||
|
import lombok.Cleanup;
|
||||||
|
|
||||||
public class ContactsSyncAdapterService extends Service {
|
public class ContactsSyncAdapterService extends Service {
|
||||||
private static ContactsSyncAdapter syncAdapter;
|
private static ContactsSyncAdapter syncAdapter;
|
||||||
|
|
||||||
|
protected static final int MAX_MULTIGET = 10;
|
||||||
|
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void onCreate() {
|
public void onCreate() {
|
||||||
if (syncAdapter == null)
|
if (syncAdapter == null)
|
||||||
@ -68,7 +82,6 @@ public class ContactsSyncAdapterService extends Service {
|
|||||||
|
|
||||||
|
|
||||||
private static class ContactsSyncAdapter extends AbstractThreadedSyncAdapter {
|
private static class ContactsSyncAdapter extends AbstractThreadedSyncAdapter {
|
||||||
|
|
||||||
public ContactsSyncAdapter(Context context) {
|
public ContactsSyncAdapter(Context context) {
|
||||||
super(context, false);
|
super(context, false);
|
||||||
}
|
}
|
||||||
@ -88,7 +101,7 @@ public class ContactsSyncAdapterService extends Service {
|
|||||||
|
|
||||||
// prepare remote address book
|
// prepare remote address book
|
||||||
boolean hasVCard4 = false;
|
boolean hasVCard4 = false;
|
||||||
dav.propfind(0, SupportedAddressData.NAME);
|
dav.propfind(0, SupportedAddressData.NAME, GetCTag.NAME);
|
||||||
SupportedAddressData supportedAddressData = (SupportedAddressData)dav.properties.get(SupportedAddressData.NAME);
|
SupportedAddressData supportedAddressData = (SupportedAddressData)dav.properties.get(SupportedAddressData.NAME);
|
||||||
if (supportedAddressData != null)
|
if (supportedAddressData != null)
|
||||||
for (MediaType type : supportedAddressData.types)
|
for (MediaType type : supportedAddressData.types)
|
||||||
@ -142,11 +155,24 @@ public class ContactsSyncAdapterService extends Service {
|
|||||||
remote.put(vCard, local.eTag, null);
|
remote.put(vCard, local.eTag, null);
|
||||||
}
|
}
|
||||||
|
|
||||||
// reset DIRTY
|
GetETag newETag = (GetETag) remote.properties.get(GetETag.NAME);
|
||||||
|
local.clearDirty(newETag != null ? newETag.eTag : null);
|
||||||
}
|
}
|
||||||
|
|
||||||
// check CTag (ignore on forced sync)
|
// check CTag (ignore on manual sync)
|
||||||
if (true) {
|
String currentCTag = null;
|
||||||
|
if (extras.containsKey(ContentResolver.SYNC_EXTRAS_MANUAL))
|
||||||
|
Constants.log.info("Manual sync, ignoring CTag");
|
||||||
|
else {
|
||||||
|
GetCTag getCTag = (GetCTag) dav.properties.get(GetCTag.NAME);
|
||||||
|
if (getCTag != null)
|
||||||
|
currentCTag = getCTag.cTag;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (currentCTag != null && !(currentCTag.equals(addressBook.getCTag()))) {
|
||||||
|
Constants.log.info("Remote address book didn't change (CTag=" + currentCTag + "), no need to list VCards");
|
||||||
|
|
||||||
|
} else {
|
||||||
// fetch list of local contacts and build hash table to index file name
|
// fetch list of local contacts and build hash table to index file name
|
||||||
localList = addressBook.getAll();
|
localList = addressBook.getAll();
|
||||||
Map<String, LocalContact> localContacts = new HashMap<>(localList.length);
|
Map<String, LocalContact> localContacts = new HashMap<>(localList.length);
|
||||||
@ -199,15 +225,73 @@ public class ContactsSyncAdapterService extends Service {
|
|||||||
toDownload.addAll(remoteContacts.values());
|
toDownload.addAll(remoteContacts.values());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Constants.log.info("Downloading " + toDownload.size() + " contacts (" + MAX_MULTIGET + " at once)");
|
||||||
|
|
||||||
// download new/updated VCards from server
|
// download new/updated VCards from server
|
||||||
for (DavResource remoteContact : toDownload) {
|
for (DavResource[] bunch : ArrayUtils.partition(toDownload.toArray(new DavResource[toDownload.size()]), MAX_MULTIGET)) {
|
||||||
Constants.log.info("Downloading " + remoteContact.location);
|
Constants.log.info("Downloading " + TextUtils.join(" + ", bunch));
|
||||||
String fileName = remoteContact.fileName();
|
if (bunch.length == 1) {
|
||||||
|
// only one contact, use GET
|
||||||
|
DavResource remote = bunch[0];
|
||||||
|
String fileName = remote.fileName();
|
||||||
|
|
||||||
ResponseBody body = remoteContact.get("text/vcard;q=0.5, text/vcard;charset=utf-8;q=0.8, text/vcard;version=4.0");
|
ResponseBody body = remote.get("text/vcard;q=0.5, text/vcard;charset=utf-8;q=0.8, text/vcard;version=4.0");
|
||||||
String remoteETag = ((GetETag)remoteContact.properties.get(GetETag.NAME)).eTag;
|
String eTag = ((GetETag)remote.properties.get(GetETag.NAME)).eTag;
|
||||||
|
|
||||||
Contact contacts[] = Contact.fromStream(body.byteStream(), body.contentType().charset(Charsets.UTF_8));
|
@Cleanup InputStream stream = body.byteStream();
|
||||||
|
processVCard(addressBook, localContacts, 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);
|
||||||
|
dav.multiget(urls.toArray(new HttpUrl[urls.size()]), hasVCard4);
|
||||||
|
|
||||||
|
// process multiget results
|
||||||
|
for (DavResource remote : dav.members) {
|
||||||
|
String eTag = null;
|
||||||
|
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);
|
||||||
|
}
|
||||||
|
|
||||||
|
AddressData addressData = (AddressData)remote.properties.get(AddressData.NAME);
|
||||||
|
if (addressData == null || addressData.vCard == null)
|
||||||
|
throw new DavException("Received multi-get response without address data");
|
||||||
|
|
||||||
|
@Cleanup InputStream stream = new ByteArrayInputStream(addressData.vCard.getBytes());
|
||||||
|
processVCard(addressBook, localContacts, remote.fileName(), eTag, stream, charset);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Save sync state (CTag). It doesn't matter if it has changed during the sync process
|
||||||
|
(for instance, because another client has uploaded changes), because this will simply
|
||||||
|
cause all remote entries to be listed at the next sync. */
|
||||||
|
Constants.log.info("Saving sync state: CTag=" + currentCTag);
|
||||||
|
addressBook.setCTag(currentCTag);
|
||||||
|
}
|
||||||
|
|
||||||
|
} catch (Exception e) {
|
||||||
|
Log.e("davdroid", "XXX", e);
|
||||||
|
}
|
||||||
|
|
||||||
|
Constants.log.info("Sync complete for authority " + authority);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
private void processVCard(LocalAddressBook addressBook, Map<String, LocalContact>localContacts, String fileName, String eTag, InputStream stream, Charset charset) throws IOException, ContactsStorageException {
|
||||||
|
Contact contacts[] = Contact.fromStream(stream, charset);
|
||||||
if (contacts.length == 1) {
|
if (contacts.length == 1) {
|
||||||
Contact newData = contacts[0];
|
Contact newData = contacts[0];
|
||||||
|
|
||||||
@ -215,25 +299,18 @@ public class ContactsSyncAdapterService extends Service {
|
|||||||
LocalContact localContact = localContacts.get(fileName);
|
LocalContact localContact = localContacts.get(fileName);
|
||||||
if (localContact != null) {
|
if (localContact != null) {
|
||||||
Constants.log.info("Updating " + fileName + " in local address book");
|
Constants.log.info("Updating " + fileName + " in local address book");
|
||||||
localContact.eTag = remoteETag;
|
localContact.eTag = eTag;
|
||||||
localContact.update(newData);
|
localContact.update(newData);
|
||||||
} else {
|
} else {
|
||||||
Constants.log.info("Adding " + fileName + " to local address book");
|
Constants.log.info("Adding " + fileName + " to local address book");
|
||||||
localContact = new LocalContact(addressBook, newData, fileName, remoteETag);
|
localContact = new LocalContact(addressBook, newData, fileName, eTag);
|
||||||
localContact.add();
|
localContact.add();
|
||||||
}
|
}
|
||||||
|
|
||||||
// add the new contact
|
|
||||||
} else
|
} else
|
||||||
Constants.log.error("Received VCard with not exactly one VCARD, ignoring " + fileName);
|
Constants.log.error("Received VCard with not exactly one VCARD, ignoring " + fileName);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
} catch (Exception e) {
|
|
||||||
Log.e("davdroid", "querying member etags", e);
|
|
||||||
}
|
|
||||||
|
|
||||||
Constants.log.info("Sync complete for authority " + authority);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
@ -20,6 +20,7 @@ import android.view.MenuItem;
|
|||||||
import android.view.View;
|
import android.view.View;
|
||||||
import android.widget.TextView;
|
import android.widget.TextView;
|
||||||
|
|
||||||
|
import at.bitfire.davdroid.BuildConfig;
|
||||||
import at.bitfire.davdroid.Constants;
|
import at.bitfire.davdroid.Constants;
|
||||||
import at.bitfire.davdroid.R;
|
import at.bitfire.davdroid.R;
|
||||||
import at.bitfire.davdroid.ui.settings.SettingsActivity;
|
import at.bitfire.davdroid.ui.settings.SettingsActivity;
|
||||||
@ -41,7 +42,7 @@ public class MainActivity extends Activity {
|
|||||||
}
|
}
|
||||||
|
|
||||||
TextView tvInfo = (TextView)findViewById(R.id.text_info);
|
TextView tvInfo = (TextView)findViewById(R.id.text_info);
|
||||||
tvInfo.setText(Html.fromHtml(getString(R.string.html_main_info, Constants.APP_VERSION)));
|
tvInfo.setText(Html.fromHtml(getString(R.string.html_main_info, BuildConfig.VERSION_NAME)));
|
||||||
tvInfo.setMovementMethod(LinkMovementMethod.getInstance());
|
tvInfo.setMovementMethod(LinkMovementMethod.getInstance());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -20,6 +20,7 @@ import org.apache.http.impl.client.HttpClientBuilder;
|
|||||||
import org.apache.http.impl.client.HttpClients;
|
import org.apache.http.impl.client.HttpClients;
|
||||||
import org.apache.http.impl.conn.PoolingHttpClientConnectionManager;
|
import org.apache.http.impl.conn.PoolingHttpClientConnectionManager;
|
||||||
|
|
||||||
|
import at.bitfire.davdroid.BuildConfig;
|
||||||
import at.bitfire.davdroid.Constants;
|
import at.bitfire.davdroid.Constants;
|
||||||
|
|
||||||
|
|
||||||
@ -57,7 +58,7 @@ public class DavHttpClient {
|
|||||||
.setDefaultRequestConfig(defaultRqConfig)
|
.setDefaultRequestConfig(defaultRqConfig)
|
||||||
.setRetryHandler(DavHttpRequestRetryHandler.INSTANCE)
|
.setRetryHandler(DavHttpRequestRetryHandler.INSTANCE)
|
||||||
.setRedirectStrategy(DavRedirectStrategy.INSTANCE)
|
.setRedirectStrategy(DavRedirectStrategy.INSTANCE)
|
||||||
.setUserAgent("DAVdroid/" + Constants.APP_VERSION);
|
.setUserAgent("DAVdroid/" + BuildConfig.VERSION_NAME);
|
||||||
|
|
||||||
if (Log.isLoggable("Wire", Log.DEBUG)) {
|
if (Log.isLoggable("Wire", Log.DEBUG)) {
|
||||||
Log.i(TAG, "Wire logging active, disabling HTTP compression");
|
Log.i(TAG, "Wire logging active, disabling HTTP compression");
|
||||||
|
@ -1 +1 @@
|
|||||||
Subproject commit 84a2cf0bbad257274e362851020da3822957449d
|
Subproject commit a6975918ed614eef93c222451fd0981c60ec3ad9
|
Loading…
Reference in New Issue
Block a user