1
0
mirror of https://github.com/etesync/android synced 2025-08-05 05:15:17 +00:00

Retain unknown VCard properties, ez-vcard update, handle stale connections

* store unknown VCard properties in an extra column and load them when generating a new VCard (closes #118)
* upgrade to ez-vcard/0.9.3 (fixes sync error reported via Play Store)
* (re-)enable stale collection check, RetryHandler to retry idempotent CalDAV/CardDAV requests (hopefully fixes #225)
* always set FN/display name (take organization if no structured name is available) (hopefully fixes #227)
This commit is contained in:
rfc2822 2014-04-04 19:37:56 +02:00
parent 4c05dd1e45
commit 1e2051038c
12 changed files with 207 additions and 77 deletions

View File

@ -35,6 +35,7 @@ import ezvcard.property.Birthday;
import ezvcard.property.Email; import ezvcard.property.Email;
import ezvcard.property.FormattedName; import ezvcard.property.FormattedName;
import ezvcard.property.Impp; import ezvcard.property.Impp;
import ezvcard.property.Logo;
import ezvcard.property.Nickname; import ezvcard.property.Nickname;
import ezvcard.property.Note; import ezvcard.property.Note;
import ezvcard.property.Organization; import ezvcard.property.Organization;
@ -42,12 +43,15 @@ import ezvcard.property.Photo;
import ezvcard.property.RawProperty; import ezvcard.property.RawProperty;
import ezvcard.property.Revision; import ezvcard.property.Revision;
import ezvcard.property.Role; import ezvcard.property.Role;
import ezvcard.property.Sound;
import ezvcard.property.Source;
import ezvcard.property.StructuredName; import ezvcard.property.StructuredName;
import ezvcard.property.Telephone; import ezvcard.property.Telephone;
import ezvcard.property.Title; import ezvcard.property.Title;
import ezvcard.property.Uid; import ezvcard.property.Uid;
import ezvcard.property.Url; import ezvcard.property.Url;
@ToString(callSuper = true) @ToString(callSuper = true)
public class Contact extends Resource { public class Contact extends Resource {
private final static String TAG = "davdroid.Contact"; private final static String TAG = "davdroid.Contact";
@ -68,7 +72,9 @@ public class Contact extends Resource {
PHONE_TYPE_ASSISTANT = TelephoneType.get("X-ASSISTANT"), PHONE_TYPE_ASSISTANT = TelephoneType.get("X-ASSISTANT"),
PHONE_TYPE_MMS = TelephoneType.get("X-MMS"); PHONE_TYPE_MMS = TelephoneType.get("X-MMS");
@Getter @Setter boolean starred; @Getter @Setter private String unknownProperties;
@Getter @Setter private boolean starred;
@Getter @Setter private String displayName, nickName; @Getter @Setter private String displayName, nickName;
@Getter @Setter private String prefix, givenName, middleName, familyName, suffix; @Getter @Setter private String prefix, givenName, middleName, familyName, suffix;
@ -98,16 +104,16 @@ public class Contact extends Resource {
public Contact(long localID, String resourceName, String eTag) { public Contact(long localID, String resourceName, String eTag) {
super(localID, resourceName, eTag); super(localID, resourceName, eTag);
} }
@Override @Override
public void generateUID() { public void initialize() {
uid = UUID.randomUUID().toString(); generateUID();
name = uid + ".vcf";
} }
@Override protected void generateUID() {
public void generateName() { uid = UUID.randomUUID().toString();
name = uid + ".vcf";
} }
@ -119,20 +125,37 @@ public class Contact extends Resource {
if (vcard == null) if (vcard == null)
return; return;
Uid uid = vcard.getUid(); // now work through all supported properties
if (uid == null) { // supported properties are removed from the VCard after parsing
Log.w(TAG, "Received VCard without UID, generating new one"); // so that only unknown properties are left and can be stored separately
uid = new Uid(UUID.randomUUID().toString());
}
this.uid = uid.getValue();
RawProperty starred = vcard.getExtendedProperty(PROPERTY_STARRED);
this.starred = starred != null && starred.getValue().equals("1");
FormattedName fn = vcard.getFormattedName(); // UID
if (fn != null) Uid uid = vcard.getUid();
displayName = fn.getValue(); if (uid != null) {
this.uid = uid.getValue();
vcard.removeProperties(Uid.class);
} else {
Log.w(TAG, "Received VCard without UID, generating new one");
generateUID();
}
// X-DAVDROID-STARRED
RawProperty starred = vcard.getExtendedProperty(PROPERTY_STARRED);
if (starred != null && starred.getValue() != null) {
this.starred = starred.getValue().equals("1");
vcard.removeExtendedProperty(PROPERTY_STARRED);
} else
this.starred = false;
// FN
FormattedName fn = vcard.getFormattedName();
if (fn != null) {
displayName = fn.getValue();
vcard.removeProperties(FormattedName.class);
} else
Log.w(TAG, "Received invalid VCard without FN (formatted name) property");
// N
StructuredName n = vcard.getStructuredName(); StructuredName n = vcard.getStructuredName();
if (n != null) { if (n != null) {
prefix = StringUtils.join(n.getPrefixes(), " "); prefix = StringUtils.join(n.getPrefixes(), " ");
@ -140,77 +163,141 @@ public class Contact extends Resource {
middleName = StringUtils.join(n.getAdditional(), " "); middleName = StringUtils.join(n.getAdditional(), " ");
familyName = n.getFamily(); familyName = n.getFamily();
suffix = StringUtils.join(n.getSuffixes(), " "); suffix = StringUtils.join(n.getSuffixes(), " ");
vcard.removeProperties(StructuredName.class);
} }
// phonetic names
RawProperty RawProperty
phoneticFirstName = vcard.getExtendedProperty(PROPERTY_PHONETIC_FIRST_NAME), phoneticFirstName = vcard.getExtendedProperty(PROPERTY_PHONETIC_FIRST_NAME),
phoneticMiddleName = vcard.getExtendedProperty(PROPERTY_PHONETIC_MIDDLE_NAME), phoneticMiddleName = vcard.getExtendedProperty(PROPERTY_PHONETIC_MIDDLE_NAME),
phoneticLastName = vcard.getExtendedProperty(PROPERTY_PHONETIC_LAST_NAME); phoneticLastName = vcard.getExtendedProperty(PROPERTY_PHONETIC_LAST_NAME);
if (phoneticFirstName != null) if (phoneticFirstName != null) {
phoneticGivenName = phoneticFirstName.getValue(); phoneticGivenName = phoneticFirstName.getValue();
if (phoneticMiddleName != null) vcard.removeExtendedProperty(PROPERTY_PHONETIC_FIRST_NAME);
}
if (phoneticMiddleName != null) {
this.phoneticMiddleName = phoneticMiddleName.getValue(); this.phoneticMiddleName = phoneticMiddleName.getValue();
if (phoneticLastName != null) vcard.removeExtendedProperty(PROPERTY_PHONETIC_MIDDLE_NAME);
}
if (phoneticLastName != null) {
phoneticFamilyName = phoneticLastName.getValue(); phoneticFamilyName = phoneticLastName.getValue();
vcard.removeExtendedProperty(PROPERTY_PHONETIC_LAST_NAME);
}
// TEL
phoneNumbers = vcard.getTelephoneNumbers(); phoneNumbers = vcard.getTelephoneNumbers();
emails = vcard.getEmails(); vcard.removeProperties(Telephone.class);
// EMAIL
emails = vcard.getEmails();
vcard.removeProperties(Email.class);
// PHOTO
for (Photo photo : vcard.getPhotos()) { for (Photo photo : vcard.getPhotos()) {
this.photo = photo.getData(); this.photo = photo.getData();
vcard.removeProperties(Photo.class);
break; break;
} }
// ORG
organization = vcard.getOrganization(); organization = vcard.getOrganization();
vcard.removeProperties(Organization.class);
// TITLE
for (Title title : vcard.getTitles()) { for (Title title : vcard.getTitles()) {
jobTitle = title.getValue(); jobTitle = title.getValue();
vcard.removeProperties(Title.class);
break; break;
} }
// ROLE
for (Role role : vcard.getRoles()) { for (Role role : vcard.getRoles()) {
this.jobDescription = role.getValue(); this.jobDescription = role.getValue();
vcard.removeProperties(Role.class);
break; break;
} }
// IMPP
impps = vcard.getImpps(); impps = vcard.getImpps();
vcard.removeProperties(Impp.class);
// NICKNAME
Nickname nicknames = vcard.getNickname(); Nickname nicknames = vcard.getNickname();
if (nicknames != null && nicknames.getValues() != null) if (nicknames != null) {
nickName = StringUtils.join(nicknames.getValues(), ", "); if (nicknames.getValues() != null)
nickName = StringUtils.join(nicknames.getValues(), ", ");
vcard.removeProperties(Nickname.class);
}
// NOTE
List<String> notes = new LinkedList<String>(); List<String> notes = new LinkedList<String>();
for (Note note : vcard.getNotes()) for (Note note : vcard.getNotes())
notes.add(note.getValue()); notes.add(note.getValue());
if (!notes.isEmpty()) if (!notes.isEmpty())
note = StringUtils.join(notes, "\n---\n"); note = StringUtils.join(notes, "\n---\n");
vcard.removeProperties(Note.class);
// ADR
addresses = vcard.getAddresses(); addresses = vcard.getAddresses();
vcard.removeProperties(Address.class);
// URL
for (Url url : vcard.getUrls()) for (Url url : vcard.getUrls())
URLs.add(url.getValue()); URLs.add(url.getValue());
vcard.removeProperties(Url.class);
// BDAY
birthDay = vcard.getBirthday(); birthDay = vcard.getBirthday();
vcard.removeProperties(Birthday.class);
// ANNIVERSARY
anniversary = vcard.getAnniversary(); anniversary = vcard.getAnniversary();
vcard.removeProperties(Anniversary.class);
// get X-SIP and import as IMPP // X-SIP
for (RawProperty sip : vcard.getExtendedProperties(PROPERTY_SIP)) for (RawProperty sip : vcard.getExtendedProperties(PROPERTY_SIP))
impps.add(new Impp("sip", sip.getValue())); impps.add(new Impp("sip", sip.getValue()));
vcard.removeExtendedProperty(PROPERTY_SIP);
// remove binary properties because of potential OutOfMemory / TransactionTooLarge exceptions
vcard.removeProperties(Logo.class);
vcard.removeProperties(Sound.class);
// remove properties that don't apply anymore
vcard.removeProperties(Revision.class);
vcard.removeProperties(Source.class);
// store all remaining properties into unknownProperties
if (!vcard.getProperties().isEmpty() || !vcard.getExtendedProperties().isEmpty())
unknownProperties = vcard.write();
else
unknownProperties = null;
} }
@Override @Override
public ByteArrayOutputStream toEntity() throws IOException { public ByteArrayOutputStream toEntity() throws IOException {
VCard vcard = new VCard(); VCard vcard = null;
vcard.setProdId("DAVdroid/" + Constants.APP_VERSION + " (ez-vcard/" + Ezvcard.VERSION + ")"); try {
if (unknownProperties != null)
vcard = Ezvcard.parse(unknownProperties).first();
} catch (Exception e) {
Log.w(TAG, "Couldn't parse original property set, beginning from scratch");
}
if (vcard == null)
vcard = new VCard();
if (uid != null) if (uid != null)
vcard.setUid(new Uid(uid)); vcard.setUid(new Uid(uid));
else
Log.wtf(TAG, "Generating VCard without UID");
if (starred) if (starred)
vcard.setExtendedProperty(PROPERTY_STARRED, "1"); vcard.setExtendedProperty(PROPERTY_STARRED, "1");
if (displayName != null) if (displayName != null)
vcard.setFormattedName(displayName); vcard.setFormattedName(displayName);
else if (organization != null && organization.getValues() != null && organization.getValues().get(0) != null)
vcard.setFormattedName(organization.getValues().get(0));
else
Log.w(TAG, "No FN (formatted name) available to generate VCard");
// N
if (familyName != null || middleName != null || givenName != null) { if (familyName != null || middleName != null || givenName != null) {
StructuredName n = new StructuredName(); StructuredName n = new StructuredName();
if (prefix != null) if (prefix != null)
@ -227,6 +314,7 @@ public class Contact extends Resource {
vcard.setStructuredName(n); vcard.setStructuredName(n);
} }
// phonetic names
if (phoneticGivenName != null) if (phoneticGivenName != null)
vcard.addExtendedProperty(PROPERTY_PHONETIC_FIRST_NAME, phoneticGivenName); vcard.addExtendedProperty(PROPERTY_PHONETIC_FIRST_NAME, phoneticGivenName);
if (phoneticMiddleName != null) if (phoneticMiddleName != null)
@ -234,42 +322,55 @@ public class Contact extends Resource {
if (phoneticFamilyName != null) if (phoneticFamilyName != null)
vcard.addExtendedProperty(PROPERTY_PHONETIC_LAST_NAME, phoneticFamilyName); vcard.addExtendedProperty(PROPERTY_PHONETIC_LAST_NAME, phoneticFamilyName);
// TEL
for (Telephone phoneNumber : phoneNumbers) for (Telephone phoneNumber : phoneNumbers)
vcard.addTelephoneNumber(phoneNumber); vcard.addTelephoneNumber(phoneNumber);
// EMAIL
for (Email email : emails) for (Email email : emails)
vcard.addEmail(email); vcard.addEmail(email);
// ORG, TITLE, ROLE
if (organization != null) if (organization != null)
vcard.addOrganization(organization); vcard.setOrganization(organization);
if (jobTitle != null) if (jobTitle != null)
vcard.addTitle(jobTitle); vcard.addTitle(jobTitle);
if (jobDescription != null) if (jobDescription != null)
vcard.addRole(jobDescription); vcard.addRole(jobDescription);
// IMPP
for (Impp impp : impps) for (Impp impp : impps)
vcard.addImpp(impp); vcard.addImpp(impp);
if (nickName != null && !nickName.isEmpty()) // NICKNAME
if (!StringUtils.isBlank(nickName))
vcard.setNickname(nickName); vcard.setNickname(nickName);
if (note != null && !note.isEmpty()) // NOTE
if (!StringUtils.isBlank(note))
vcard.addNote(note); vcard.addNote(note);
// ADR
for (Address address : addresses) for (Address address : addresses)
vcard.addAddress(address); vcard.addAddress(address);
// URL
for (String url : URLs) for (String url : URLs)
vcard.addUrl(url); vcard.addUrl(url);
// ANNIVERSARY
if (anniversary != null) if (anniversary != null)
vcard.setAnniversary(anniversary); vcard.setAnniversary(anniversary);
// BDAY
if (birthDay != null) if (birthDay != null)
vcard.setBirthday(birthDay); vcard.setBirthday(birthDay);
// PHOTO
if (photo != null) if (photo != null)
vcard.addPhoto(new Photo(photo, ImageType.JPEG)); vcard.addPhoto(new Photo(photo, ImageType.JPEG));
// PRODID, REV
vcard.setProdId("DAVdroid/" + Constants.APP_VERSION + " (ez-vcard/" + Ezvcard.VERSION + ")");
vcard.setRevision(Revision.now()); vcard.setRevision(Revision.now());
ByteArrayOutputStream os = new ByteArrayOutputStream(); ByteArrayOutputStream os = new ByteArrayOutputStream();

View File

@ -109,14 +109,14 @@ public class Event extends Resource {
@Override @Override
public void generateUID() { public void initialize() {
UidGenerator generator = new UidGenerator(new SimpleHostInfo(DavSyncAdapter.getAndroidID()), String.valueOf(android.os.Process.myPid())); generateUID();
uid = generator.generateUid().getValue(); name = uid.replace("@", "_") + ".ics";
} }
@Override protected void generateUID() {
public void generateName() { UidGenerator generator = new UidGenerator(new SimpleHostInfo(DavSyncAdapter.getAndroidID()), String.valueOf(android.os.Process.myPid()));
name = uid.replace("@", "_") + ".ics"; uid = generator.generateUid().getValue();
} }

View File

@ -20,10 +20,6 @@ import java.util.Set;
import lombok.Cleanup; import lombok.Cleanup;
import org.apache.commons.io.IOUtils;
import org.apache.commons.lang.StringUtils;
import org.apache.commons.lang.WordUtils;
import android.accounts.Account; import android.accounts.Account;
import android.content.ContentProviderClient; import android.content.ContentProviderClient;
import android.content.ContentProviderOperation; import android.content.ContentProviderOperation;
@ -49,7 +45,11 @@ import android.provider.ContactsContract.CommonDataKinds.Website;
import android.provider.ContactsContract.Data; import android.provider.ContactsContract.Data;
import android.provider.ContactsContract.RawContacts; import android.provider.ContactsContract.RawContacts;
import android.util.Log; import android.util.Log;
import at.bitfire.davdroid.syncadapter.AccountSettings;
import org.apache.commons.io.IOUtils;
import org.apache.commons.lang.StringUtils;
import org.apache.commons.lang.WordUtils;
import ezvcard.parameter.AddressType; import ezvcard.parameter.AddressType;
import ezvcard.parameter.EmailType; import ezvcard.parameter.EmailType;
import ezvcard.parameter.ImppType; import ezvcard.parameter.ImppType;
@ -61,10 +61,15 @@ import ezvcard.property.DateOrTimeProperty;
import ezvcard.property.Impp; import ezvcard.property.Impp;
import ezvcard.property.Telephone; import ezvcard.property.Telephone;
import at.bitfire.davdroid.syncadapter.AccountSettings;
public class LocalAddressBook extends LocalCollection<Contact> { public class LocalAddressBook extends LocalCollection<Contact> {
private final static String TAG = "davdroid.LocalAddressBook"; private final static String TAG = "davdroid.LocalAddressBook";
protected final static String COLUMN_UNKNOWN_PROPERTIES = RawContacts.SYNC3;
protected AccountSettings accountSettings; protected AccountSettings accountSettings;
@ -146,10 +151,11 @@ public class LocalAddressBook extends LocalCollection<Contact> {
try { try {
@Cleanup Cursor cursor = providerClient.query(ContentUris.withAppendedId(entriesURI(), c.getLocalID()), @Cleanup Cursor cursor = providerClient.query(ContentUris.withAppendedId(entriesURI(), c.getLocalID()),
new String[] { entryColumnUID(), RawContacts.STARRED }, null, null, null); new String[] { entryColumnUID(), COLUMN_UNKNOWN_PROPERTIES, RawContacts.STARRED }, null, null, null);
if (cursor != null && cursor.moveToNext()) { if (cursor != null && cursor.moveToNext()) {
c.setUid(cursor.getString(0)); c.setUid(cursor.getString(0));
c.setStarred(cursor.getInt(1) != 0); c.setUnknownProperties(cursor.getString(1));
c.setStarred(cursor.getInt(2) != 0);
} else } else
throw new RecordNotFoundException(); throw new RecordNotFoundException();
@ -177,6 +183,7 @@ public class LocalAddressBook extends LocalCollection<Contact> {
/* 6 */ StructuredName.PHONETIC_GIVEN_NAME, StructuredName.PHONETIC_MIDDLE_NAME, StructuredName.PHONETIC_FAMILY_NAME /* 6 */ StructuredName.PHONETIC_GIVEN_NAME, StructuredName.PHONETIC_MIDDLE_NAME, StructuredName.PHONETIC_FAMILY_NAME
}, StructuredName.RAW_CONTACT_ID + "=? AND " + Data.MIMETYPE + "=?", }, StructuredName.RAW_CONTACT_ID + "=? AND " + Data.MIMETYPE + "=?",
new String[] { String.valueOf(c.getLocalID()), StructuredName.CONTENT_ITEM_TYPE }, null); new String[] { String.valueOf(c.getLocalID()), StructuredName.CONTENT_ITEM_TYPE }, null);
if (cursor != null && cursor.moveToNext()) { if (cursor != null && cursor.moveToNext()) {
c.setDisplayName(cursor.getString(0)); c.setDisplayName(cursor.getString(0));
@ -512,6 +519,7 @@ public class LocalAddressBook extends LocalCollection<Contact> {
.withValue(entryColumnRemoteName(), contact.getName()) .withValue(entryColumnRemoteName(), contact.getName())
.withValue(entryColumnUID(), contact.getUid()) .withValue(entryColumnUID(), contact.getUid())
.withValue(entryColumnETag(), contact.getETag()) .withValue(entryColumnETag(), contact.getETag())
.withValue(COLUMN_UNKNOWN_PROPERTIES, contact.getUnknownProperties())
.withValue(RawContacts.STARRED, contact.isStarred()); .withValue(RawContacts.STARRED, contact.isStarred());
} }
@ -519,8 +527,8 @@ public class LocalAddressBook extends LocalCollection<Contact> {
@Override @Override
protected void addDataRows(Resource resource, long localID, int backrefIdx) { protected void addDataRows(Resource resource, long localID, int backrefIdx) {
Contact contact = (Contact)resource; Contact contact = (Contact)resource;
pendingOperations.add(buildStructuredName(newDataInsertBuilder(localID, backrefIdx), contact).build()); queueOperation(buildStructuredName(newDataInsertBuilder(localID, backrefIdx), contact));
for (Telephone number : contact.getPhoneNumbers()) for (Telephone number : contact.getPhoneNumbers())
queueOperation(buildPhoneNumber(newDataInsertBuilder(localID, backrefIdx), number)); queueOperation(buildPhoneNumber(newDataInsertBuilder(localID, backrefIdx), number));
@ -531,9 +539,7 @@ public class LocalAddressBook extends LocalCollection<Contact> {
if (contact.getPhoto() != null) if (contact.getPhoto() != null)
queueOperation(buildPhoto(newDataInsertBuilder(localID, backrefIdx), contact.getPhoto())); queueOperation(buildPhoto(newDataInsertBuilder(localID, backrefIdx), contact.getPhoto()));
if (contact.getOrganization() != null || contact.getJobTitle() != null || contact.getJobDescription() != null) queueOperation(buildOrganization(newDataInsertBuilder(localID, backrefIdx), contact));
queueOperation(buildOrganization(newDataInsertBuilder(localID, backrefIdx),
contact.getOrganization(), contact.getJobTitle(), contact.getJobDescription()));
for (Impp impp : contact.getImpps()) for (Impp impp : contact.getImpps())
queueOperation(buildIMPP(newDataInsertBuilder(localID, backrefIdx), impp)); queueOperation(buildIMPP(newDataInsertBuilder(localID, backrefIdx), impp));
@ -560,7 +566,7 @@ public class LocalAddressBook extends LocalCollection<Contact> {
// TODO relations // TODO relations
// SIP addresses built by buildIMPP // SIP addresses are built by buildIMPP
} }
@Override @Override
@ -695,9 +701,12 @@ public class LocalAddressBook extends LocalCollection<Contact> {
.withValue(Photo.PHOTO, photo); .withValue(Photo.PHOTO, photo);
} }
protected Builder buildOrganization(Builder builder, ezvcard.property.Organization organization, String jobTitle, String jobDescription) { protected Builder buildOrganization(Builder builder, Contact contact) {
if (contact.getOrganization() == null && contact.getJobTitle() == null && contact.getJobDescription() == null)
return null;
ezvcard.property.Organization organization = contact.getOrganization();
String company = null, department = null; String company = null, department = null;
if (organization != null) { if (organization != null) {
Iterator<String> org = organization.getValues().iterator(); Iterator<String> org = organization.getValues().iterator();
if (org.hasNext()) if (org.hasNext())
@ -710,8 +719,8 @@ public class LocalAddressBook extends LocalCollection<Contact> {
.withValue(Data.MIMETYPE, Organization.CONTENT_ITEM_TYPE) .withValue(Data.MIMETYPE, Organization.CONTENT_ITEM_TYPE)
.withValue(Organization.COMPANY, company) .withValue(Organization.COMPANY, company)
.withValue(Organization.DEPARTMENT, department) .withValue(Organization.DEPARTMENT, department)
.withValue(Organization.TITLE, jobTitle) .withValue(Organization.TITLE, contact.getJobTitle())
.withValue(Organization.JOB_DESCRIPTION, jobDescription); .withValue(Organization.JOB_DESCRIPTION, contact.getJobDescription());
} }
protected Builder buildIMPP(Builder builder, Impp impp) { protected Builder buildIMPP(Builder builder, Impp impp) {

View File

@ -40,7 +40,7 @@ public abstract class LocalCollection<T extends Resource> {
abstract protected String entryColumnAccountType(); abstract protected String entryColumnAccountType();
abstract protected String entryColumnAccountName(); abstract protected String entryColumnAccountName();
abstract protected String entryColumnParentID(); abstract protected String entryColumnParentID();
abstract protected String entryColumnID(); abstract protected String entryColumnID();
abstract protected String entryColumnRemoteName(); abstract protected String entryColumnRemoteName();
@ -85,8 +85,7 @@ public abstract class LocalCollection<T extends Resource> {
// new record: generate UID + remote file name so that we can upload // new record: generate UID + remote file name so that we can upload
T resource = findById(id, false); T resource = findById(id, false);
resource.generateUID(); resource.initialize();
resource.generateName();
// write generated UID + remote file name into database // write generated UID + remote file name into database
ContentValues values = new ContentValues(2); ContentValues values = new ContentValues(2);
values.put(entryColumnUID(), resource.getUid()); values.put(entryColumnUID(), resource.getUid());

View File

@ -23,7 +23,7 @@ public abstract class Resource {
@Getter protected String name, ETag; @Getter protected String name, ETag;
@Getter @Setter protected String uid; @Getter @Setter protected String uid;
@Getter protected long localID; @Getter protected long localID;
public Resource(String name, String ETag) { public Resource(String name, String ETag) {
this.name = name; this.name = name;
@ -36,8 +36,7 @@ public abstract class Resource {
} }
// sets UID and resource name (= remote file name) // sets UID and resource name (= remote file name)
public abstract void generateUID(); public abstract void initialize();
public abstract void generateName();
public abstract void parseEntity(InputStream entity) throws IOException, InvalidResourceException; public abstract void parseEntity(InputStream entity) throws IOException, InvalidResourceException;
public abstract ByteArrayOutputStream toEntity() throws IOException; public abstract ByteArrayOutputStream toEntity() throws IOException;

View File

@ -14,7 +14,6 @@ import java.net.URISyntaxException;
import java.util.HashMap; import java.util.HashMap;
import java.util.Map; import java.util.Map;
import lombok.Synchronized;
import android.accounts.Account; import android.accounts.Account;
import android.app.Service; import android.app.Service;
import android.content.ContentProviderClient; import android.content.ContentProviderClient;
@ -32,13 +31,13 @@ public class CalendarsSyncAdapterService extends Service {
private static SyncAdapter syncAdapter; private static SyncAdapter syncAdapter;
@Override @Synchronized @Override
public void onCreate() { public void onCreate() {
if (syncAdapter == null) if (syncAdapter == null)
syncAdapter = new SyncAdapter(getApplicationContext()); syncAdapter = new SyncAdapter(getApplicationContext());
} }
@Override @Synchronized @Override
public void onDestroy() { public void onDestroy() {
syncAdapter.close(); syncAdapter.close();
syncAdapter = null; syncAdapter = null;

View File

@ -14,7 +14,6 @@ import java.net.URISyntaxException;
import java.util.HashMap; import java.util.HashMap;
import java.util.Map; import java.util.Map;
import lombok.Synchronized;
import android.accounts.Account; import android.accounts.Account;
import android.app.Service; import android.app.Service;
import android.content.ContentProviderClient; import android.content.ContentProviderClient;
@ -29,9 +28,9 @@ import at.bitfire.davdroid.resource.RemoteCollection;
public class ContactsSyncAdapterService extends Service { public class ContactsSyncAdapterService extends Service {
private static ContactsSyncAdapter syncAdapter; private static ContactsSyncAdapter syncAdapter;
@Override @Synchronized
@Override
public void onCreate() { public void onCreate() {
if (syncAdapter == null) if (syncAdapter == null)
syncAdapter = new ContactsSyncAdapter(getApplicationContext()); syncAdapter = new ContactsSyncAdapter(getApplicationContext());
@ -55,7 +54,6 @@ public class ContactsSyncAdapterService extends Service {
private ContactsSyncAdapter(Context context) { private ContactsSyncAdapter(Context context) {
super(context); super(context);
Log.i(TAG, "httpClient = " + httpClient);
} }
@Override @Override
@ -71,7 +69,6 @@ public class ContactsSyncAdapterService extends Service {
try { try {
LocalCollection<?> database = new LocalAddressBook(account, provider, settings); LocalCollection<?> database = new LocalAddressBook(account, provider, settings);
Log.i(TAG, "httpClient 2 = " + httpClient);
RemoteCollection<?> dav = new CardDavAddressBook(httpClient, addressBookURL, userName, password, preemptive); RemoteCollection<?> dav = new CardDavAddressBook(httpClient, addressBookURL, userName, password, preemptive);
Map<LocalCollection<?>, RemoteCollection<?>> map = new HashMap<LocalCollection<?>, RemoteCollection<?>>(); Map<LocalCollection<?>, RemoteCollection<?>> map = new HashMap<LocalCollection<?>, RemoteCollection<?>>();

View File

@ -57,7 +57,8 @@ public abstract class DavSyncAdapter extends AbstractThreadedSyncAdapter impleme
httpClient = DavHttpClient.create(); httpClient = DavHttpClient.create();
} }
@Override public void close() { @Override
public void close() {
// apparently may be called from a GUI thread // apparently may be called from a GUI thread
new AsyncTask<Void, Void, Void>() { new AsyncTask<Void, Void, Void>() {
@Override @Override
@ -104,7 +105,7 @@ public abstract class DavSyncAdapter extends AbstractThreadedSyncAdapter impleme
Log.e(TAG, "Hard HTTP error " + ex.getCode(), ex); Log.e(TAG, "Hard HTTP error " + ex.getCode(), ex);
syncResult.stats.numParseExceptions++; syncResult.stats.numParseExceptions++;
} else { } else {
Log.w(TAG, "Soft HTTP error" + ex.getCode(), ex); Log.w(TAG, "Soft HTTP error " + ex.getCode() + " (Android will try again later)", ex);
syncResult.stats.numIoExceptions++; syncResult.stats.numIoExceptions++;
} }
@ -113,7 +114,7 @@ public abstract class DavSyncAdapter extends AbstractThreadedSyncAdapter impleme
Log.e(TAG, "Local storage (content provider) exception", ex); Log.e(TAG, "Local storage (content provider) exception", ex);
} catch (IOException ex) { } catch (IOException ex) {
syncResult.stats.numIoExceptions++; syncResult.stats.numIoExceptions++;
Log.e(TAG, "I/O error", ex); Log.e(TAG, "I/O error (Android will try again later)", ex);
} }
} }
} }

View File

@ -36,7 +36,7 @@ public class DavHttpClient {
defaultRqConfig = RequestConfig.copy(RequestConfig.DEFAULT) defaultRqConfig = RequestConfig.copy(RequestConfig.DEFAULT)
.setConnectTimeout(20*1000) .setConnectTimeout(20*1000)
.setSocketTimeout(20*1000) .setSocketTimeout(20*1000)
.setStaleConnectionCheckEnabled(false) .setStaleConnectionCheckEnabled(true)
.build(); .build();
// enable logging // enable logging
@ -56,6 +56,7 @@ public class DavHttpClient {
.useSystemProperties() .useSystemProperties()
.setConnectionManager(connectionManager) .setConnectionManager(connectionManager)
.setDefaultRequestConfig(defaultRqConfig) .setDefaultRequestConfig(defaultRqConfig)
.setRetryHandler(DavHttpRequestRetryHandler.INSTANCE)
.setUserAgent("DAVdroid/" + Constants.APP_VERSION) .setUserAgent("DAVdroid/" + Constants.APP_VERSION)
.disableCookieManagement() .disableCookieManagement()
.build(); .build();

View File

@ -0,0 +1,28 @@
package at.bitfire.davdroid.webdav;
import java.util.Locale;
import org.apache.commons.lang.ArrayUtils;
import ch.boye.httpclientandroidlib.HttpRequest;
import ch.boye.httpclientandroidlib.impl.client.DefaultHttpRequestRetryHandler;
public class DavHttpRequestRetryHandler extends DefaultHttpRequestRetryHandler {
final static DavHttpRequestRetryHandler INSTANCE = new DavHttpRequestRetryHandler();
// see http://www.iana.org/assignments/http-methods/http-methods.xhtml
private final static String idempotentMethods[] = {
"DELETE", "GET", "HEAD", "MKCALENDAR", "MKCOL", "OPTIONS", "PROPFIND", "PROPPATCH",
"PUT", "REPORT", "SEARCH", "TRACE"
};
public DavHttpRequestRetryHandler() {
super(/* retry count */ 3, /* retry already sent requests? */ false);
}
@Override
protected boolean handleAsIdempotent(final HttpRequest request) {
final String method = request.getRequestLine().getMethod().toUpperCase(Locale.ROOT);
return ArrayUtils.contains(idempotentMethods, method);
}
}

View File

@ -14,9 +14,7 @@ import lombok.Cleanup;
import org.apache.commons.io.IOUtils; import org.apache.commons.io.IOUtils;
import android.content.res.AssetManager; import android.content.res.AssetManager;
import android.os.Build;
import android.test.InstrumentationTestCase; import android.test.InstrumentationTestCase;
import android.util.Log;
import at.bitfire.davdroid.webdav.DavException; import at.bitfire.davdroid.webdav.DavException;
import at.bitfire.davdroid.webdav.DavHttpClient; import at.bitfire.davdroid.webdav.DavHttpClient;
import at.bitfire.davdroid.webdav.DavMultiget; import at.bitfire.davdroid.webdav.DavMultiget;
@ -31,8 +29,6 @@ import ch.boye.httpclientandroidlib.impl.client.CloseableHttpClient;
// tests require running robohydra! // tests require running robohydra!
public class WebDavResourceTest extends InstrumentationTestCase { public class WebDavResourceTest extends InstrumentationTestCase {
private static final String TAG = "davdroidTest.WebDavResourceTest";
static final String ROBOHYDRA_BASE = "http://10.0.0.11:3000/"; static final String ROBOHYDRA_BASE = "http://10.0.0.11:3000/";
static byte[] SAMPLE_CONTENT = new byte[] { 1, 2, 3, 4, 5 }; static byte[] SAMPLE_CONTENT = new byte[] { 1, 2, 3, 4, 5 };