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:
parent
4c05dd1e45
commit
1e2051038c
Binary file not shown.
@ -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;
|
||||||
@ -101,13 +107,13 @@ public class Contact extends Resource {
|
|||||||
|
|
||||||
|
|
||||||
@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;
|
||||||
|
|
||||||
|
// now work through all supported properties
|
||||||
|
// supported properties are removed from the VCard after parsing
|
||||||
|
// so that only unknown properties are left and can be stored separately
|
||||||
|
|
||||||
|
// UID
|
||||||
Uid uid = vcard.getUid();
|
Uid uid = vcard.getUid();
|
||||||
if (uid == null) {
|
if (uid != null) {
|
||||||
|
this.uid = uid.getValue();
|
||||||
|
vcard.removeProperties(Uid.class);
|
||||||
|
} else {
|
||||||
Log.w(TAG, "Received VCard without UID, generating new one");
|
Log.w(TAG, "Received VCard without UID, generating new one");
|
||||||
uid = new Uid(UUID.randomUUID().toString());
|
generateUID();
|
||||||
}
|
}
|
||||||
this.uid = uid.getValue();
|
|
||||||
|
|
||||||
|
// X-DAVDROID-STARRED
|
||||||
RawProperty starred = vcard.getExtendedProperty(PROPERTY_STARRED);
|
RawProperty starred = vcard.getExtendedProperty(PROPERTY_STARRED);
|
||||||
this.starred = starred != null && starred.getValue().equals("1");
|
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();
|
FormattedName fn = vcard.getFormattedName();
|
||||||
if (fn != null)
|
if (fn != null) {
|
||||||
displayName = fn.getValue();
|
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();
|
||||||
|
@ -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();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
@ -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());
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -520,7 +528,7 @@ public class LocalAddressBook extends LocalCollection<Contact> {
|
|||||||
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) {
|
||||||
String company = null, department = null;
|
if (contact.getOrganization() == null && contact.getJobTitle() == null && contact.getJobDescription() == null)
|
||||||
|
return null;
|
||||||
|
|
||||||
|
ezvcard.property.Organization organization = contact.getOrganization();
|
||||||
|
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) {
|
||||||
|
@ -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());
|
||||||
|
@ -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;
|
||||||
|
@ -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;
|
||||||
|
@ -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;
|
||||||
@ -31,7 +30,7 @@ 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<?>>();
|
||||||
|
@ -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);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -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();
|
||||||
|
@ -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);
|
||||||
|
}
|
||||||
|
}
|
@ -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 };
|
||||||
|
|
||||||
|
Loading…
Reference in New Issue
Block a user