Refactoring
* move common code to super-classes * new icons
Before Width: | Height: | Size: 129 KiB |
Before Width: | Height: | Size: 6.0 KiB After Width: | Height: | Size: 4.9 KiB |
Before Width: | Height: | Size: 3.6 KiB After Width: | Height: | Size: 3.0 KiB |
Before Width: | Height: | Size: 9.0 KiB After Width: | Height: | Size: 7.1 KiB |
Before Width: | Height: | Size: 13 KiB After Width: | Height: | Size: 12 KiB |
@ -8,66 +8,30 @@
|
||||
package at.bitfire.davdroid.resource;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.net.URI;
|
||||
import java.net.URISyntaxException;
|
||||
import java.util.LinkedList;
|
||||
|
||||
import net.fortuna.ical4j.data.ParserException;
|
||||
|
||||
import org.apache.http.HttpException;
|
||||
|
||||
import at.bitfire.davdroid.webdav.WebDavCollection;
|
||||
import at.bitfire.davdroid.webdav.WebDavCollection.MultigetType;
|
||||
import at.bitfire.davdroid.webdav.WebDavResource;
|
||||
|
||||
public class CalDavCalendar extends RemoteCollection {
|
||||
public class CalDavCalendar extends RemoteCollection<Event> {
|
||||
//private final static String TAG = "davdroid.CalDavCalendar";
|
||||
|
||||
public CalDavCalendar(String baseURL, String user, String password) throws IOException {
|
||||
try {
|
||||
collection = new WebDavCollection(new URI(baseURL), user, password);
|
||||
} catch (URISyntaxException e) {
|
||||
throw new IOException();
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
protected String memberContentType() {
|
||||
return "text/calendar";
|
||||
}
|
||||
|
||||
@Override
|
||||
protected MultigetType multiGetType() {
|
||||
return MultigetType.CALENDAR;
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
public Event[] getMemberETags() throws IOException, IncapableResourceException, HttpException {
|
||||
super.getMemberETags();
|
||||
|
||||
LinkedList<Event> resources = new LinkedList<Event>();
|
||||
for (WebDavResource member : collection.getMembers()) {
|
||||
Event e = new Event(member.getName(), member.getETag());
|
||||
resources.add(e);
|
||||
}
|
||||
return resources.toArray(new Event[0]);
|
||||
protected Event newResourceSkeleton(String name, String ETag) {
|
||||
return new Event(name, ETag);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Event[] multiGet(Resource[] resources) throws IOException, IncapableResourceException, HttpException, ParserException {
|
||||
if (resources.length == 1)
|
||||
return new Event[] { (Event)get(resources[0]) };
|
||||
|
||||
LinkedList<String> names = new LinkedList<String>();
|
||||
for (Resource c : resources)
|
||||
names.add(c.getName());
|
||||
|
||||
collection.multiGet(names.toArray(new String[0]), MultigetType.CALENDAR);
|
||||
|
||||
LinkedList<Event> foundEvents = new LinkedList<Event>();
|
||||
for (WebDavResource member : collection.getMembers()) {
|
||||
Event e = new Event(member.getName(), member.getETag());
|
||||
e.parseEntity(member.getContent());
|
||||
foundEvents.add(e);
|
||||
}
|
||||
return foundEvents.toArray(new Event[0]);
|
||||
|
||||
|
||||
public CalDavCalendar(String baseURL, String user, String password) throws IOException, URISyntaxException {
|
||||
super(baseURL, user, password);
|
||||
}
|
||||
|
||||
}
|
||||
|
@ -8,70 +8,30 @@
|
||||
package at.bitfire.davdroid.resource;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.net.URI;
|
||||
import java.net.URISyntaxException;
|
||||
import java.util.LinkedList;
|
||||
|
||||
import net.fortuna.ical4j.data.ParserException;
|
||||
|
||||
import org.apache.http.HttpException;
|
||||
|
||||
import at.bitfire.davdroid.webdav.WebDavCollection;
|
||||
import at.bitfire.davdroid.webdav.WebDavCollection.MultigetType;
|
||||
import at.bitfire.davdroid.webdav.WebDavResource;
|
||||
|
||||
public class CardDavAddressBook extends RemoteCollection {
|
||||
public class CardDavAddressBook extends RemoteCollection<Contact> {
|
||||
//private final static String TAG = "davdroid.CardDavAddressBook";
|
||||
|
||||
public CardDavAddressBook(String baseURL, String user, String password) throws IOException {
|
||||
try {
|
||||
collection = new WebDavCollection(new URI(baseURL), user, password);
|
||||
} catch (URISyntaxException e) {
|
||||
throw new IOException();
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
protected String memberContentType() {
|
||||
return "text/vcard";
|
||||
}
|
||||
|
||||
@Override
|
||||
public Contact[] getMemberETags() throws IOException, IncapableResourceException, HttpException {
|
||||
super.getMemberETags();
|
||||
|
||||
LinkedList<Contact> resources = new LinkedList<Contact>();
|
||||
for (WebDavResource member : collection.getMembers()) {
|
||||
Contact c = new Contact(member.getName(), member.getETag());
|
||||
resources.add(c);
|
||||
}
|
||||
|
||||
return resources.toArray(new Contact[0]);
|
||||
protected MultigetType multiGetType() {
|
||||
return MultigetType.ADDRESS_BOOK;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Contact[] multiGet(Resource[] resources) throws IOException, IncapableResourceException, HttpException, ParserException {
|
||||
if (resources.length == 1) {
|
||||
Resource resource = get(resources[0]);
|
||||
if (resource != null)
|
||||
return new Contact[] { (Contact)resource };
|
||||
else
|
||||
return null;
|
||||
}
|
||||
|
||||
LinkedList<String> names = new LinkedList<String>();
|
||||
for (Resource c : resources)
|
||||
names.add(c.getName());
|
||||
|
||||
collection.multiGet(names.toArray(new String[0]), MultigetType.ADDRESS_BOOK);
|
||||
|
||||
LinkedList<Contact> foundContacts = new LinkedList<Contact>();
|
||||
for (WebDavResource member : collection.getMembers()) {
|
||||
Contact c = new Contact(member.getName(), member.getETag());
|
||||
c.parseEntity(member.getContent());
|
||||
foundContacts.add(c);
|
||||
}
|
||||
return foundContacts.toArray(new Contact[0]);
|
||||
protected Contact newResourceSkeleton(String name, String ETag) {
|
||||
return new Contact(name, ETag);
|
||||
}
|
||||
|
||||
|
||||
public CardDavAddressBook(String baseURL, String user, String password) throws IOException, URISyntaxException {
|
||||
super(baseURL, user, password);
|
||||
}
|
||||
}
|
||||
|
@ -34,9 +34,6 @@ import ezvcard.types.UrlType;
|
||||
public class Contact extends Resource {
|
||||
public final String VCARD_STARRED = "X-DAVDROID-STARRED";
|
||||
|
||||
@Getter @Setter private String uid;
|
||||
|
||||
// VCard data
|
||||
@Getter @Setter boolean starred;
|
||||
|
||||
@Getter @Setter private String displayName;
|
||||
|
@ -15,7 +15,6 @@ import java.util.List;
|
||||
import java.util.SimpleTimeZone;
|
||||
import java.util.TimeZone;
|
||||
|
||||
import ezvcard.types.PhotoType;
|
||||
import lombok.Getter;
|
||||
import lombok.Setter;
|
||||
import net.fortuna.ical4j.data.CalendarBuilder;
|
||||
@ -56,7 +55,7 @@ public class Event extends Resource {
|
||||
|
||||
private TimeZoneRegistry tzRegistry;
|
||||
|
||||
@Getter @Setter private String uid, summary, location, description;
|
||||
@Getter @Setter private String summary, location, description;
|
||||
|
||||
@Getter private DtStart dtStart;
|
||||
@Getter private DtEnd dtEnd;
|
||||
|
@ -7,10 +7,7 @@
|
||||
******************************************************************************/
|
||||
package at.bitfire.davdroid.resource;
|
||||
|
||||
import java.util.LinkedList;
|
||||
import java.util.List;
|
||||
import java.util.Set;
|
||||
import java.util.UUID;
|
||||
|
||||
import android.accounts.Account;
|
||||
import android.accounts.AccountManager;
|
||||
@ -19,12 +16,8 @@ import android.content.ContentProviderOperation;
|
||||
import android.content.ContentProviderOperation.Builder;
|
||||
import android.content.ContentUris;
|
||||
import android.database.Cursor;
|
||||
import android.database.DatabaseUtils;
|
||||
import android.net.Uri;
|
||||
import android.os.RemoteException;
|
||||
import android.provider.CalendarContract;
|
||||
import android.provider.ContactsContract;
|
||||
import android.provider.CalendarContract.Events;
|
||||
import android.provider.ContactsContract.CommonDataKinds.Email;
|
||||
import android.provider.ContactsContract.CommonDataKinds.Nickname;
|
||||
import android.provider.ContactsContract.CommonDataKinds.Note;
|
||||
@ -35,9 +28,6 @@ import android.provider.ContactsContract.CommonDataKinds.Website;
|
||||
import android.provider.ContactsContract.Data;
|
||||
import android.provider.ContactsContract.RawContacts;
|
||||
import at.bitfire.davdroid.Constants;
|
||||
|
||||
import com.google.common.base.Joiner;
|
||||
|
||||
import ezvcard.parameters.EmailTypeParameter;
|
||||
import ezvcard.parameters.ImageTypeParameter;
|
||||
import ezvcard.parameters.TelephoneTypeParameter;
|
||||
@ -48,15 +38,31 @@ import ezvcard.types.PhotoType;
|
||||
import ezvcard.types.TelephoneType;
|
||||
import ezvcard.types.UrlType;
|
||||
|
||||
public class LocalAddressBook extends LocalCollection {
|
||||
public class LocalAddressBook extends LocalCollection<Contact> {
|
||||
//private final static String TAG = "davdroid.LocalAddressBook";
|
||||
|
||||
protected final static String
|
||||
COLUMN_REMOTE_NAME = RawContacts.SOURCE_ID,
|
||||
COLUMN_UID = RawContacts.SYNC1,
|
||||
COLUMN_ETAG = RawContacts.SYNC2;
|
||||
|
||||
protected AccountManager accountManager;
|
||||
|
||||
|
||||
/* database fields */
|
||||
|
||||
@Override
|
||||
protected Uri entriesURI() {
|
||||
return syncAdapterURI(RawContacts.CONTENT_URI);
|
||||
}
|
||||
|
||||
protected String entryColumnAccountType() { return RawContacts.ACCOUNT_TYPE; }
|
||||
protected String entryColumnAccountName() { return RawContacts.ACCOUNT_NAME; }
|
||||
|
||||
protected String entryColumnID() { return RawContacts._ID; }
|
||||
protected String entryColumnRemoteName() { return RawContacts.SOURCE_ID; }
|
||||
protected String entryColumnETag() { return RawContacts.SYNC2; }
|
||||
|
||||
protected String entryColumnDirty() { return RawContacts.DIRTY; }
|
||||
protected String entryColumnDeleted() { return RawContacts.DELETED; }
|
||||
|
||||
protected String entryColumnUID() { return RawContacts.SYNC1; }
|
||||
|
||||
|
||||
|
||||
public LocalAddressBook(Account account, ContentProviderClient providerClient, AccountManager accountManager) {
|
||||
@ -65,55 +71,7 @@ public class LocalAddressBook extends LocalCollection {
|
||||
}
|
||||
|
||||
|
||||
/* find multiple rows */
|
||||
|
||||
@Override
|
||||
public Contact[] findDeleted() throws RemoteException {
|
||||
Cursor cursor = providerClient.query(entriesURI(),
|
||||
new String[] { RawContacts._ID, COLUMN_REMOTE_NAME, COLUMN_ETAG },
|
||||
RawContacts.DELETED + "=1", null, null);
|
||||
LinkedList<Contact> contacts = new LinkedList<Contact>();
|
||||
while (cursor.moveToNext())
|
||||
contacts.add(new Contact(cursor.getLong(0), cursor.getString(1), cursor.getString(2)));
|
||||
return contacts.toArray(new Contact[0]);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Contact[] findDirty() throws RemoteException {
|
||||
Cursor cursor = providerClient.query(entriesURI(),
|
||||
new String[] { RawContacts._ID, COLUMN_REMOTE_NAME, COLUMN_ETAG },
|
||||
RawContacts.DIRTY + "=1", null, null);
|
||||
LinkedList<Contact> contacts = new LinkedList<Contact>();
|
||||
while (cursor.moveToNext()) {
|
||||
Contact c = new Contact(cursor.getLong(0), cursor.getString(1), cursor.getString(2));
|
||||
populate(c);
|
||||
contacts.add(c);
|
||||
}
|
||||
return contacts.toArray(new Contact[0]);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Contact[] findNew() throws RemoteException {
|
||||
Cursor cursor = providerClient.query(entriesURI(), new String[] { RawContacts._ID },
|
||||
RawContacts.DIRTY + "=1 AND " + COLUMN_REMOTE_NAME + " IS NULL", null, null);
|
||||
LinkedList<Contact> contacts = new LinkedList<Contact>();
|
||||
while (cursor.moveToNext()) {
|
||||
String uid = UUID.randomUUID().toString(),
|
||||
resourceName = uid + ".vcf";
|
||||
Contact c = new Contact(cursor.getLong(0), resourceName, null);
|
||||
c.setUid(uid);
|
||||
populate(c);
|
||||
|
||||
// new record: set resource name and UID in database
|
||||
pendingOperations.add(ContentProviderOperation
|
||||
.newUpdate(ContentUris.withAppendedId(entriesURI(), c.getLocalID()))
|
||||
.withValue(COLUMN_REMOTE_NAME, resourceName)
|
||||
.build());
|
||||
|
||||
contacts.add(c);
|
||||
}
|
||||
return contacts.toArray(new Contact[0]);
|
||||
}
|
||||
/* collection operations */
|
||||
|
||||
@Override
|
||||
public String getCTag() {
|
||||
@ -126,13 +84,21 @@ public class LocalAddressBook extends LocalCollection {
|
||||
}
|
||||
|
||||
|
||||
/* get data */
|
||||
/* content provider (= database) querying */
|
||||
|
||||
@Override
|
||||
public Contact findById(long localID, String remoteName, String eTag, boolean populate) throws RemoteException {
|
||||
Contact c = new Contact(localID, remoteName, eTag);
|
||||
if (populate)
|
||||
populate(c);
|
||||
return c;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Contact getByRemoteName(String remoteName) throws RemoteException {
|
||||
public Contact findByRemoteName(String remoteName) throws RemoteException {
|
||||
Cursor cursor = providerClient.query(entriesURI(),
|
||||
new String[] { RawContacts._ID, COLUMN_REMOTE_NAME, COLUMN_ETAG },
|
||||
COLUMN_REMOTE_NAME + "=?", new String[] { remoteName }, null);
|
||||
new String[] { RawContacts._ID, entryColumnRemoteName(), entryColumnETag() },
|
||||
entryColumnRemoteName() + "=?", new String[] { remoteName }, null);
|
||||
if (cursor.moveToNext())
|
||||
return new Contact(cursor.getLong(0), cursor.getString(1), cursor.getString(2));
|
||||
return null;
|
||||
@ -145,7 +111,7 @@ public class LocalAddressBook extends LocalCollection {
|
||||
return;
|
||||
|
||||
Cursor cursor = providerClient.query(ContentUris.withAppendedId(entriesURI(), c.getLocalID()),
|
||||
new String[] { COLUMN_UID, RawContacts.STARRED }, null, null, null);
|
||||
new String[] { entryColumnUID(), RawContacts.STARRED }, null, null, null);
|
||||
if (cursor.moveToNext()) {
|
||||
c.setUid(cursor.getString(0));
|
||||
c.setStarred(cursor.getInt(1) != 0);
|
||||
@ -260,133 +226,70 @@ public class LocalAddressBook extends LocalCollection {
|
||||
c.populated = true;
|
||||
return;
|
||||
}
|
||||
|
||||
|
||||
/* create/update */
|
||||
|
||||
@Override
|
||||
public void add(Resource resource) {
|
||||
Contact contact = (Contact)resource;
|
||||
|
||||
int idx = pendingOperations.size();
|
||||
pendingOperations.add(ContentProviderOperation.newInsert(entriesURI())
|
||||
.withValue(RawContacts.ACCOUNT_NAME, account.name)
|
||||
.withValue(RawContacts.ACCOUNT_TYPE, account.type)
|
||||
.withValue(COLUMN_REMOTE_NAME, contact.getName())
|
||||
.withValue(COLUMN_UID, contact.getUid())
|
||||
.withValue(COLUMN_ETAG, contact.getETag())
|
||||
.withValue(RawContacts.STARRED, contact.isStarred())
|
||||
.withYieldAllowed(true)
|
||||
.build());
|
||||
|
||||
addDataRows(contact, -1, idx);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void updateByRemoteName(Resource resource) throws RemoteException {
|
||||
Contact remoteContact = (Contact)resource,
|
||||
localContact = getByRemoteName(remoteContact.getName());
|
||||
|
||||
pendingOperations.add(ContentProviderOperation
|
||||
.newUpdate(ContentUris.withAppendedId(entriesURI(), localContact.getLocalID()))
|
||||
.withValue(COLUMN_ETAG, remoteContact.getETag())
|
||||
.withValue(RawContacts.STARRED, remoteContact.isStarred())
|
||||
.withYieldAllowed(true)
|
||||
.build());
|
||||
|
||||
// remove all data rows ...
|
||||
pendingOperations.add(ContentProviderOperation.newDelete(dataURI())
|
||||
.withSelection(Data.RAW_CONTACT_ID + "=?",
|
||||
new String[] { String.valueOf(localContact.getLocalID()) }).build());
|
||||
|
||||
// ... and insert new ones
|
||||
addDataRows(remoteContact, localContact.getLocalID(), -1);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void deleteAllExceptRemoteNames(Resource[] remoteResources) {
|
||||
Builder builder = ContentProviderOperation.newDelete(entriesURI());
|
||||
|
||||
if (remoteResources.length != 0) {
|
||||
List<String> terms = new LinkedList<String>();
|
||||
for (Resource res : remoteResources)
|
||||
terms.add(COLUMN_REMOTE_NAME + "<>" + DatabaseUtils.sqlEscapeString(res.getName()));
|
||||
String where = Joiner.on(" AND ").join(terms);
|
||||
builder = builder.withSelection(where, new String[] {});
|
||||
} else
|
||||
builder = builder.withSelection(COLUMN_REMOTE_NAME + " IS NOT NULL", null);
|
||||
|
||||
pendingOperations.add(builder.build());
|
||||
}
|
||||
|
||||
|
||||
|
||||
/* private helper methods */
|
||||
|
||||
@Override
|
||||
protected Uri syncAdapterURI(Uri baseURI) {
|
||||
return baseURI.buildUpon()
|
||||
.appendQueryParameter(RawContacts.ACCOUNT_NAME, account.name)
|
||||
.appendQueryParameter(RawContacts.ACCOUNT_TYPE, account.type)
|
||||
.appendQueryParameter(ContactsContract.CALLER_IS_SYNCADAPTER, "true")
|
||||
.build();
|
||||
protected String fileExtension() {
|
||||
return ".vcf";
|
||||
}
|
||||
|
||||
protected Uri dataURI() {
|
||||
return syncAdapterURI(Data.CONTENT_URI);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected Uri entriesURI() {
|
||||
return RawContacts.CONTENT_URI.buildUpon()
|
||||
.appendQueryParameter(RawContacts.ACCOUNT_NAME, account.name)
|
||||
.appendQueryParameter(RawContacts.ACCOUNT_TYPE, account.type)
|
||||
.appendQueryParameter(ContactsContract.CALLER_IS_SYNCADAPTER, "true")
|
||||
.build();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void clearDirty(Resource resource) {
|
||||
pendingOperations.add(ContentProviderOperation
|
||||
.newUpdate(ContentUris.withAppendedId(entriesURI(), resource.getLocalID()))
|
||||
.withValue(RawContacts.DIRTY, 0).build());
|
||||
private Builder newDataInsertBuilder(long raw_contact_id, Integer backrefIdx) {
|
||||
return newDataInsertBuilder(dataURI(), Data.RAW_CONTACT_ID, raw_contact_id, backrefIdx);
|
||||
}
|
||||
|
||||
private Builder newInsertBuilder(long raw_contact_id, Integer backrefIdx) {
|
||||
Builder builder = ContentProviderOperation.newInsert(dataURI());
|
||||
if (backrefIdx != -1)
|
||||
return builder.withValueBackReference(Data.RAW_CONTACT_ID, backrefIdx);
|
||||
else
|
||||
return builder.withValue(Data.RAW_CONTACT_ID, raw_contact_id);
|
||||
}
|
||||
|
||||
protected void addDataRows(Contact contact, long localID, int backrefIdx) {
|
||||
pendingOperations.add(buildStructuredName(newInsertBuilder(localID, backrefIdx), contact).build());
|
||||
|
||||
if (contact.getNickNames() != null)
|
||||
for (String nick : contact.getNickNames().getValues())
|
||||
pendingOperations.add(buildNickName(newInsertBuilder(localID, backrefIdx), nick).build());
|
||||
|
||||
for (PhotoType photo : contact.getPhotos())
|
||||
pendingOperations.add(buildPhoto(newInsertBuilder(localID, backrefIdx), photo).build());
|
||||
|
||||
for (TelephoneType number : contact.getPhoneNumbers())
|
||||
pendingOperations.add(buildPhoneNumber(newInsertBuilder(localID, backrefIdx), number).build());
|
||||
|
||||
for (EmailType email : contact.getEmails())
|
||||
pendingOperations.add(buildEmail(newInsertBuilder(localID, backrefIdx), email).build());
|
||||
|
||||
for (UrlType url : contact.getURLs())
|
||||
pendingOperations.add(buildURL(newInsertBuilder(localID, backrefIdx), url).build());
|
||||
|
||||
for (NoteType note : contact.getNotes())
|
||||
pendingOperations.add(buildNote(newInsertBuilder(localID, backrefIdx), note).build());
|
||||
}
|
||||
|
||||
|
||||
|
||||
/* content builder methods */
|
||||
|
||||
@Override
|
||||
protected Builder buildEntry(Builder builder, Contact contact) {
|
||||
return builder
|
||||
.withValue(RawContacts.ACCOUNT_NAME, account.name)
|
||||
.withValue(RawContacts.ACCOUNT_TYPE, account.type)
|
||||
.withValue(entryColumnRemoteName(), contact.getName())
|
||||
.withValue(entryColumnUID(), contact.getUid())
|
||||
.withValue(entryColumnETag(), contact.getETag())
|
||||
.withValue(RawContacts.STARRED, contact.isStarred());
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
protected void addDataRows(Contact contact, long localID, int backrefIdx) {
|
||||
pendingOperations.add(buildStructuredName(newDataInsertBuilder(localID, backrefIdx), contact).build());
|
||||
|
||||
if (contact.getNickNames() != null)
|
||||
for (String nick : contact.getNickNames().getValues())
|
||||
pendingOperations.add(buildNickName(newDataInsertBuilder(localID, backrefIdx), nick).build());
|
||||
|
||||
for (PhotoType photo : contact.getPhotos())
|
||||
pendingOperations.add(buildPhoto(newDataInsertBuilder(localID, backrefIdx), photo).build());
|
||||
|
||||
for (TelephoneType number : contact.getPhoneNumbers())
|
||||
pendingOperations.add(buildPhoneNumber(newDataInsertBuilder(localID, backrefIdx), number).build());
|
||||
|
||||
for (EmailType email : contact.getEmails())
|
||||
pendingOperations.add(buildEmail(newDataInsertBuilder(localID, backrefIdx), email).build());
|
||||
|
||||
for (UrlType url : contact.getURLs())
|
||||
pendingOperations.add(buildURL(newDataInsertBuilder(localID, backrefIdx), url).build());
|
||||
|
||||
for (NoteType note : contact.getNotes())
|
||||
pendingOperations.add(buildNote(newDataInsertBuilder(localID, backrefIdx), note).build());
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void removeDataRows(Contact contact) {
|
||||
pendingOperations.add(ContentProviderOperation.newDelete(dataURI())
|
||||
.withSelection(Data.RAW_CONTACT_ID + "=?",
|
||||
new String[] { String.valueOf(contact.getLocalID()) }).build());
|
||||
}
|
||||
|
||||
|
||||
protected Builder buildStructuredName(Builder builder, Contact contact) {
|
||||
return builder
|
||||
.withValue(Data.MIMETYPE, StructuredName.CONTENT_ITEM_TYPE)
|
||||
|
@ -10,8 +10,6 @@ package at.bitfire.davdroid.resource;
|
||||
import java.net.URISyntaxException;
|
||||
import java.text.ParseException;
|
||||
import java.util.LinkedList;
|
||||
import java.util.List;
|
||||
import java.util.UUID;
|
||||
|
||||
import lombok.Getter;
|
||||
import net.fortuna.ical4j.model.Parameter;
|
||||
@ -36,33 +34,44 @@ import android.content.ContentResolver;
|
||||
import android.content.ContentUris;
|
||||
import android.content.ContentValues;
|
||||
import android.database.Cursor;
|
||||
import android.database.DatabaseUtils;
|
||||
import android.net.Uri;
|
||||
import android.os.RemoteException;
|
||||
import android.provider.CalendarContract;
|
||||
import android.provider.CalendarContract.Attendees;
|
||||
import android.provider.CalendarContract.Calendars;
|
||||
import android.provider.CalendarContract.Events;
|
||||
import android.provider.ContactsContract.RawContacts;
|
||||
import android.provider.ContactsContract;
|
||||
import android.util.Log;
|
||||
import at.bitfire.davdroid.syncadapter.ServerInfo;
|
||||
|
||||
import com.google.common.base.Joiner;
|
||||
|
||||
public class LocalCalendar extends LocalCollection {
|
||||
public class LocalCalendar extends LocalCollection<Event> {
|
||||
private static final String TAG = "davdroid.LocalCalendar";
|
||||
|
||||
protected final static String
|
||||
CALENDARS_COLUMN_CTAG = Calendars.CAL_SYNC1,
|
||||
EVENTS_COLUMN_REMOTE_NAME = Events._SYNC_ID,
|
||||
EVENTS_COLUMN_ETAG = Events.SYNC_DATA1;
|
||||
|
||||
protected long id;
|
||||
@Getter protected String path, cTag;
|
||||
|
||||
protected static String COLLECTION_COLUMN_CTAG = Calendars.CAL_SYNC1;
|
||||
|
||||
|
||||
/* class methods */
|
||||
/* database fields */
|
||||
|
||||
@Override
|
||||
protected Uri entriesURI() {
|
||||
return syncAdapterURI(Events.CONTENT_URI);
|
||||
}
|
||||
|
||||
protected String entryColumnAccountType() { return Events.ACCOUNT_TYPE; }
|
||||
protected String entryColumnAccountName() { return Events.ACCOUNT_NAME; }
|
||||
|
||||
protected String entryColumnID() { return Events._ID; }
|
||||
protected String entryColumnRemoteName() { return Events._SYNC_ID; }
|
||||
protected String entryColumnETag() { return Events.SYNC_DATA1; }
|
||||
|
||||
protected String entryColumnDirty() { return Events.DIRTY; }
|
||||
protected String entryColumnDeleted() { return Events.DELETED; }
|
||||
|
||||
|
||||
/* class methods, constructor */
|
||||
|
||||
@SuppressLint("InlinedApi")
|
||||
public static void create(Account account, ContentResolver resolver, ServerInfo.ResourceInfo info) throws RemoteException {
|
||||
@ -86,7 +95,7 @@ public class LocalCalendar extends LocalCollection {
|
||||
|
||||
public static LocalCalendar[] findAll(Account account, ContentProviderClient providerClient) throws RemoteException {
|
||||
Cursor cursor = providerClient.query(calendarsURI(account),
|
||||
new String[] { Calendars._ID, Calendars.NAME, CALENDARS_COLUMN_CTAG },
|
||||
new String[] { Calendars._ID, Calendars.NAME, COLLECTION_COLUMN_CTAG },
|
||||
Calendars.DELETED + "=0 AND " + Calendars.SYNC_EVENTS + "=1", null, null);
|
||||
LinkedList<LocalCalendar> calendars = new LinkedList<LocalCalendar>();
|
||||
while (cursor.moveToNext())
|
||||
@ -100,72 +109,34 @@ public class LocalCalendar extends LocalCollection {
|
||||
this.path = path;
|
||||
this.cTag = cTag;
|
||||
}
|
||||
|
||||
|
||||
|
||||
/* find multiple rows */
|
||||
|
||||
@Override
|
||||
public Resource[] findDeleted() throws RemoteException {
|
||||
Cursor cursor = providerClient.query(entriesURI(),
|
||||
new String[] { Events._ID, EVENTS_COLUMN_REMOTE_NAME, EVENTS_COLUMN_ETAG },
|
||||
Events.CALENDAR_ID + "=? AND " + Events.DELETED + "=1", new String[] { String.valueOf(id) }, null);
|
||||
LinkedList<Event> events = new LinkedList<Event>();
|
||||
while (cursor.moveToNext())
|
||||
events.add(new Event(cursor.getLong(0), cursor.getString(1), cursor.getString(2)));
|
||||
return events.toArray(new Event[0]);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Resource[] findDirty() throws RemoteException {
|
||||
Cursor cursor = providerClient.query(entriesURI(),
|
||||
new String[] { Events._ID, EVENTS_COLUMN_REMOTE_NAME, EVENTS_COLUMN_ETAG },
|
||||
Events.DIRTY + "=1", null, null);
|
||||
LinkedList<Event> events = new LinkedList<Event>();
|
||||
while (cursor.moveToNext()) {
|
||||
Event e = new Event(cursor.getLong(0), cursor.getString(1), cursor.getString(2));
|
||||
populate(e);
|
||||
events.add(e);
|
||||
}
|
||||
return events.toArray(new Event[0]);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Resource[] findNew() throws RemoteException {
|
||||
Cursor cursor = providerClient.query(entriesURI(), new String[] { Events._ID },
|
||||
Events.DIRTY + "=1 AND " + EVENTS_COLUMN_REMOTE_NAME + " IS NULL", null, null);
|
||||
LinkedList<Event> events = new LinkedList<Event>();
|
||||
while (cursor.moveToNext()) {
|
||||
String uid = UUID.randomUUID().toString(),
|
||||
resourceName = uid + ".ics";
|
||||
Event e = new Event(cursor.getLong(0), resourceName, null);
|
||||
e.setUid(uid);
|
||||
populate(e);
|
||||
|
||||
// new record: set resource name and UID in database
|
||||
pendingOperations.add(ContentProviderOperation
|
||||
.newUpdate(ContentUris.withAppendedId(entriesURI(), e.getLocalID()))
|
||||
.withValue(EVENTS_COLUMN_REMOTE_NAME, resourceName)
|
||||
.build());
|
||||
|
||||
events.add(e);
|
||||
}
|
||||
return events.toArray(new Event[0]);
|
||||
}
|
||||
/* collection operations */
|
||||
|
||||
@Override
|
||||
public void setCTag(String cTag) {
|
||||
pendingOperations.add(ContentProviderOperation.newUpdate(ContentUris.withAppendedId(calendarsURI(), id))
|
||||
.withValue(CALENDARS_COLUMN_CTAG, cTag)
|
||||
.withValue(COLLECTION_COLUMN_CTAG, cTag)
|
||||
.build());
|
||||
}
|
||||
|
||||
|
||||
/* get data */
|
||||
/* content provider (= database) querying */
|
||||
|
||||
@Override
|
||||
public Event getByRemoteName(String remoteName) throws RemoteException {
|
||||
Cursor cursor = providerClient.query(entriesURI(), new String[] { Events._ID, EVENTS_COLUMN_REMOTE_NAME, EVENTS_COLUMN_ETAG },
|
||||
Events.CALENDAR_ID + "=? AND " + EVENTS_COLUMN_REMOTE_NAME + "=?", new String[] { String.valueOf(id), remoteName }, null);
|
||||
public Event findById(long localID, String remoteName, String eTag, boolean populate) throws RemoteException {
|
||||
Event e = new Event(localID, remoteName, eTag);
|
||||
if (populate)
|
||||
populate(e);
|
||||
return e;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Event findByRemoteName(String remoteName) throws RemoteException {
|
||||
Cursor cursor = providerClient.query(entriesURI(),
|
||||
new String[] { entryColumnID(), entryColumnRemoteName(), entryColumnETag() },
|
||||
Events.CALENDAR_ID + "=? AND " + entryColumnRemoteName() + "=?",
|
||||
new String[] { String.valueOf(id), remoteName }, null);
|
||||
if (cursor.moveToNext())
|
||||
return new Event(cursor.getLong(0), cursor.getString(1), cursor.getString(2));
|
||||
return null;
|
||||
@ -335,62 +306,14 @@ public class LocalCalendar extends LocalCollection {
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/* create/update */
|
||||
|
||||
@Override
|
||||
public void add(Resource resource) {
|
||||
Event event = (Event) resource;
|
||||
|
||||
int idx = pendingOperations.size();
|
||||
pendingOperations.add(buildEvent(ContentProviderOperation.newInsert(entriesURI()), event)
|
||||
.withYieldAllowed(true)
|
||||
.build());
|
||||
|
||||
addDataRows(event, -1, idx);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void updateByRemoteName(Resource remoteResource) throws RemoteException {
|
||||
Event remoteEvent = (Event) remoteResource,
|
||||
localEvent = (Event) getByRemoteName(remoteResource.getName());
|
||||
|
||||
pendingOperations.add(buildEvent(
|
||||
ContentProviderOperation.newUpdate(ContentUris.withAppendedId(entriesURI(), localEvent.getLocalID())), remoteEvent)
|
||||
.withYieldAllowed(true).build());
|
||||
|
||||
pendingOperations.add(ContentProviderOperation.newDelete(syncAdapterURI(Attendees.CONTENT_URI))
|
||||
.withSelection(Attendees.EVENT_ID + "=?",
|
||||
new String[] { String.valueOf(localEvent.getLocalID()) }).build());
|
||||
|
||||
addDataRows(remoteEvent, localEvent.getLocalID(), -1);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void delete(Resource event) {
|
||||
pendingOperations.add(ContentProviderOperation.newDelete(
|
||||
ContentUris.withAppendedId(entriesURI(), event.getLocalID())).build());
|
||||
}
|
||||
|
||||
@Override
|
||||
public void deleteAllExceptRemoteNames(Resource[] remoteResources) {
|
||||
Builder builder = ContentProviderOperation.newDelete(entriesURI());
|
||||
|
||||
if (remoteResources.length != 0) {
|
||||
List<String> terms = new LinkedList<String>();
|
||||
for (Resource res : remoteResources)
|
||||
terms.add(EVENTS_COLUMN_REMOTE_NAME + "<>" + DatabaseUtils.sqlEscapeString(res.getName()));
|
||||
String where = Joiner.on(" AND ").join(terms);
|
||||
builder = builder.withSelection(where, null);
|
||||
} else
|
||||
builder = builder.withSelection(EVENTS_COLUMN_REMOTE_NAME + " IS NOT NULL", null);
|
||||
|
||||
pendingOperations.add(builder.build());
|
||||
}
|
||||
|
||||
|
||||
/* private helper methods */
|
||||
|
||||
@Override
|
||||
protected String fileExtension() {
|
||||
return ".ics";
|
||||
}
|
||||
|
||||
protected static Uri calendarsURI(Account account) {
|
||||
return Calendars.CONTENT_URI.buildUpon().appendQueryParameter(Calendars.ACCOUNT_NAME, account.name)
|
||||
.appendQueryParameter(Calendars.ACCOUNT_TYPE, account.type)
|
||||
@ -401,47 +324,16 @@ public class LocalCalendar extends LocalCollection {
|
||||
return calendarsURI(account);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected Uri syncAdapterURI(Uri baseURI) {
|
||||
return baseURI.buildUpon()
|
||||
.appendQueryParameter(Events.ACCOUNT_NAME, account.name)
|
||||
.appendQueryParameter(Events.ACCOUNT_TYPE, account.type)
|
||||
.appendQueryParameter(CalendarContract.CALLER_IS_SYNCADAPTER, "true")
|
||||
.build();
|
||||
}
|
||||
|
||||
@Override
|
||||
protected Uri entriesURI() {
|
||||
return syncAdapterURI(Events.CONTENT_URI);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void clearDirty(Resource resource) {
|
||||
pendingOperations.add(ContentProviderOperation
|
||||
.newUpdate(ContentUris.withAppendedId(entriesURI(), resource.getLocalID()))
|
||||
.withValue(Events.DIRTY, 0).build());
|
||||
}
|
||||
|
||||
private Builder newInsertBuilder(Uri dataUri, String refFieldName, long raw_ref_id, Integer backrefIdx) {
|
||||
Builder builder = ContentProviderOperation.newInsert(syncAdapterURI(dataUri));
|
||||
if (backrefIdx != -1)
|
||||
return builder.withValueBackReference(refFieldName, backrefIdx);
|
||||
else
|
||||
return builder.withValue(refFieldName, raw_ref_id);
|
||||
}
|
||||
|
||||
protected void addDataRows(Event event, long localID, int backrefIdx) {
|
||||
for (Attendee attendee : event.getAttendees())
|
||||
pendingOperations.add(buildAttendee(newInsertBuilder(Attendees.CONTENT_URI, Attendees.EVENT_ID, localID, backrefIdx), attendee).build());
|
||||
}
|
||||
|
||||
|
||||
/* content builder methods */
|
||||
|
||||
protected Builder buildEvent(Builder builder, Event event) {
|
||||
builder = builder.withValue(Events.CALENDAR_ID, id)
|
||||
.withValue(EVENTS_COLUMN_REMOTE_NAME, event.getName())
|
||||
.withValue(EVENTS_COLUMN_ETAG, event.getETag())
|
||||
@Override
|
||||
protected Builder buildEntry(Builder builder, Event event) {
|
||||
builder = builder
|
||||
.withValue(Events.CALENDAR_ID, id)
|
||||
.withValue(entryColumnRemoteName(), event.getName())
|
||||
.withValue(entryColumnETag(), event.getETag())
|
||||
.withValue(Events.ALL_DAY, event.isAllDay() ? 1 : 0)
|
||||
.withValue(Events.DTSTART, event.getDtStartInMillis())
|
||||
.withValue(Events.DTEND, event.getDtEndInMillis())
|
||||
@ -482,6 +374,21 @@ public class LocalCalendar extends LocalCollection {
|
||||
|
||||
return builder;
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
protected void addDataRows(Event event, long localID, int backrefIdx) {
|
||||
for (Attendee attendee : event.getAttendees())
|
||||
pendingOperations.add(buildAttendee(newDataInsertBuilder(Attendees.CONTENT_URI, Attendees.EVENT_ID, localID, backrefIdx), attendee).build());
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void removeDataRows(Event event) {
|
||||
pendingOperations.add(ContentProviderOperation.newDelete(syncAdapterURI(Attendees.CONTENT_URI))
|
||||
.withSelection(Attendees.EVENT_ID + "=?",
|
||||
new String[] { String.valueOf(event.getLocalID()) }).build());
|
||||
}
|
||||
|
||||
|
||||
@SuppressLint("InlinedApi")
|
||||
protected Builder buildAttendee(Builder builder, Attendee attendee) {
|
||||
|
@ -8,61 +8,195 @@
|
||||
package at.bitfire.davdroid.resource;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.LinkedList;
|
||||
import java.util.List;
|
||||
import java.util.UUID;
|
||||
|
||||
import android.accounts.Account;
|
||||
import android.content.ContentProviderClient;
|
||||
import android.content.ContentProviderOperation;
|
||||
import android.content.ContentProviderOperation.Builder;
|
||||
import android.content.ContentUris;
|
||||
import android.content.OperationApplicationException;
|
||||
import android.database.Cursor;
|
||||
import android.database.DatabaseUtils;
|
||||
import android.net.Uri;
|
||||
import android.os.RemoteException;
|
||||
import android.provider.ContactsContract;
|
||||
import android.provider.CalendarContract.Events;
|
||||
import android.provider.ContactsContract.RawContacts;
|
||||
import android.provider.CalendarContract;
|
||||
|
||||
public abstract class LocalCollection {
|
||||
import com.google.common.base.Joiner;
|
||||
|
||||
public abstract class LocalCollection<ResourceType extends Resource> {
|
||||
protected Account account;
|
||||
protected ContentProviderClient providerClient;
|
||||
protected ArrayList<ContentProviderOperation> pendingOperations = new ArrayList<ContentProviderOperation>();;
|
||||
protected ArrayList<ContentProviderOperation> pendingOperations = new ArrayList<ContentProviderOperation>();
|
||||
|
||||
|
||||
// database fields
|
||||
|
||||
abstract protected Uri entriesURI();
|
||||
|
||||
abstract protected String entryColumnAccountType();
|
||||
abstract protected String entryColumnAccountName();
|
||||
|
||||
abstract protected String entryColumnID();
|
||||
abstract protected String entryColumnRemoteName();
|
||||
abstract protected String entryColumnETag();
|
||||
|
||||
abstract protected String entryColumnDirty();
|
||||
abstract protected String entryColumnDeleted();
|
||||
|
||||
|
||||
LocalCollection(Account account, ContentProviderClient providerClient) {
|
||||
this.account = account;
|
||||
this.providerClient = providerClient;
|
||||
}
|
||||
|
||||
// query
|
||||
abstract public Resource[] findDeleted() throws RemoteException;
|
||||
abstract public Resource[] findDirty() throws RemoteException;
|
||||
abstract public Resource[] findNew() throws RemoteException;
|
||||
|
||||
// cache management
|
||||
|
||||
// collection operations
|
||||
|
||||
abstract public String getCTag();
|
||||
abstract public void setCTag(String cTag);
|
||||
|
||||
|
||||
// fetch
|
||||
public abstract Resource getByRemoteName(String name) throws RemoteException;
|
||||
public abstract void populate(Resource record) throws RemoteException;
|
||||
// content provider (= database) querying
|
||||
|
||||
// modify
|
||||
public abstract void add(Resource resource);
|
||||
public abstract void updateByRemoteName(Resource remoteResource) throws RemoteException;
|
||||
|
||||
public void delete(Resource resource) {
|
||||
pendingOperations.add(ContentProviderOperation.newDelete(
|
||||
ContentUris.withAppendedId(entriesURI(), resource.getLocalID()))
|
||||
.build());
|
||||
public Resource[] findDirty() throws RemoteException {
|
||||
Cursor cursor = providerClient.query(entriesURI(),
|
||||
new String[] { entryColumnID(), entryColumnRemoteName(), entryColumnETag() },
|
||||
entryColumnDirty() + "=1", null, null);
|
||||
LinkedList<Resource> dirty = new LinkedList<Resource>();
|
||||
while (cursor.moveToNext())
|
||||
dirty.add(findById(cursor.getLong(0), cursor.getString(1), cursor.getString(2), true));
|
||||
return dirty.toArray(new Resource[0]);
|
||||
}
|
||||
|
||||
public Resource[] findDeleted() throws RemoteException {
|
||||
Cursor cursor = providerClient.query(entriesURI(),
|
||||
new String[] { entryColumnID(), entryColumnRemoteName(), entryColumnETag() },
|
||||
entryColumnDeleted() + "=1", null, null);
|
||||
LinkedList<Resource> deleted = new LinkedList<Resource>();
|
||||
while (cursor.moveToNext())
|
||||
deleted.add(findById(cursor.getLong(0), cursor.getString(1), cursor.getString(2), false));
|
||||
return deleted.toArray(new Resource[0]);
|
||||
}
|
||||
|
||||
public Resource[] findNew() throws RemoteException {
|
||||
Cursor cursor = providerClient.query(entriesURI(),
|
||||
new String[] { entryColumnID() },
|
||||
entryColumnDirty() + "=1 AND " + entryColumnRemoteName() + " IS NULL", null, null);
|
||||
LinkedList<Resource> fresh = new LinkedList<Resource>();
|
||||
while (cursor.moveToNext()) {
|
||||
String uid = UUID.randomUUID().toString(),
|
||||
resourceName = uid + fileExtension();
|
||||
Resource resource = findById(cursor.getLong(0), resourceName, null, true); //new Event(cursor.getLong(0), resourceName, null);
|
||||
resource.setUid(uid);
|
||||
|
||||
// new record: set generated resource name in database
|
||||
pendingOperations.add(ContentProviderOperation
|
||||
.newUpdate(ContentUris.withAppendedId(entriesURI(), resource.getLocalID()))
|
||||
.withValue(entryColumnRemoteName(), resourceName)
|
||||
.build());
|
||||
|
||||
fresh.add(resource);
|
||||
}
|
||||
return fresh.toArray(new Event[0]);
|
||||
}
|
||||
|
||||
public abstract void deleteAllExceptRemoteNames(Resource[] remoteRecords);
|
||||
abstract public Resource findById(long localID, String resourceName, String eTag, boolean populate) throws RemoteException;
|
||||
abstract public ResourceType findByRemoteName(String name) throws RemoteException;
|
||||
|
||||
public abstract void populate(Resource record) throws RemoteException;
|
||||
|
||||
|
||||
// database operations
|
||||
protected abstract Uri syncAdapterURI(Uri baseURI);
|
||||
protected abstract Uri entriesURI();
|
||||
public abstract void clearDirty(Resource resource);
|
||||
// create/update/delete
|
||||
|
||||
public void add(ResourceType resource) {
|
||||
int idx = pendingOperations.size();
|
||||
pendingOperations.add(
|
||||
buildEntry(ContentProviderOperation.newInsert(entriesURI()), resource)
|
||||
.withYieldAllowed(true)
|
||||
.build());
|
||||
|
||||
addDataRows(resource, -1, idx);
|
||||
}
|
||||
|
||||
public void updateByRemoteName(ResourceType remoteResource) throws RemoteException {
|
||||
ResourceType localResource = findByRemoteName(remoteResource.getName());
|
||||
|
||||
pendingOperations.add(
|
||||
buildEntry(ContentProviderOperation.newUpdate(ContentUris.withAppendedId(entriesURI(), localResource.getLocalID())), remoteResource)
|
||||
.withValue(entryColumnETag(), remoteResource.getETag())
|
||||
.withYieldAllowed(true)
|
||||
.build());
|
||||
|
||||
removeDataRows(localResource);
|
||||
addDataRows(remoteResource, localResource.getLocalID(), -1);
|
||||
}
|
||||
|
||||
public void delete(Resource resource) {
|
||||
pendingOperations.add(ContentProviderOperation
|
||||
.newDelete(ContentUris.withAppendedId(entriesURI(), resource.getLocalID()))
|
||||
.withYieldAllowed(true)
|
||||
.build());
|
||||
}
|
||||
|
||||
public void deleteAllExceptRemoteNames(Resource[] remoteResources) {
|
||||
Builder builder = ContentProviderOperation.newDelete(entriesURI());
|
||||
|
||||
if (remoteResources.length != 0) {
|
||||
List<String> terms = new LinkedList<String>();
|
||||
for (Resource res : remoteResources)
|
||||
terms.add(entryColumnRemoteName() + "<>" + DatabaseUtils.sqlEscapeString(res.getName()));
|
||||
String where = Joiner.on(" AND ").join(terms);
|
||||
builder = builder.withSelection(where, new String[] {});
|
||||
} else
|
||||
builder = builder.withSelection(entryColumnRemoteName() + " IS NOT NULL", null);
|
||||
|
||||
pendingOperations.add(builder
|
||||
.withYieldAllowed(true)
|
||||
.build());
|
||||
}
|
||||
|
||||
public void clearDirty(Resource resource) {
|
||||
pendingOperations.add(ContentProviderOperation
|
||||
.newUpdate(ContentUris.withAppendedId(entriesURI(), resource.getLocalID()))
|
||||
.withValue(entryColumnDirty(), 0).build());
|
||||
}
|
||||
|
||||
public void commit() throws RemoteException, OperationApplicationException {
|
||||
if (!pendingOperations.isEmpty())
|
||||
providerClient.applyBatch(pendingOperations);
|
||||
|
||||
pendingOperations.clear();
|
||||
}
|
||||
|
||||
|
||||
// helpers
|
||||
|
||||
protected abstract String fileExtension();
|
||||
|
||||
protected Uri syncAdapterURI(Uri baseURI) {
|
||||
return baseURI.buildUpon()
|
||||
.appendQueryParameter(entryColumnAccountType(), account.type)
|
||||
.appendQueryParameter(entryColumnAccountName(), account.name)
|
||||
.appendQueryParameter(CalendarContract.CALLER_IS_SYNCADAPTER, "true")
|
||||
.build();
|
||||
}
|
||||
|
||||
protected Builder newDataInsertBuilder(Uri dataUri, String refFieldName, long raw_ref_id, Integer backrefIdx) {
|
||||
Builder builder = ContentProviderOperation.newInsert(syncAdapterURI(dataUri));
|
||||
if (backrefIdx != -1)
|
||||
return builder.withValueBackReference(refFieldName, backrefIdx);
|
||||
else
|
||||
return builder.withValue(refFieldName, raw_ref_id);
|
||||
}
|
||||
|
||||
|
||||
// content builders
|
||||
|
||||
protected abstract Builder buildEntry(Builder builder, ResourceType resource);
|
||||
|
||||
protected abstract void addDataRows(ResourceType resource, long localID, int backrefIdx);
|
||||
protected abstract void removeDataRows(ResourceType resource);
|
||||
}
|
||||
|
@ -8,24 +8,35 @@
|
||||
package at.bitfire.davdroid.resource;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.net.URI;
|
||||
import java.net.URISyntaxException;
|
||||
import java.util.LinkedList;
|
||||
import java.util.List;
|
||||
|
||||
import lombok.Getter;
|
||||
import net.fortuna.ical4j.data.ParserException;
|
||||
|
||||
import org.apache.http.HttpException;
|
||||
|
||||
import lombok.Getter;
|
||||
import at.bitfire.davdroid.webdav.HttpPropfind;
|
||||
import at.bitfire.davdroid.webdav.WebDavCollection;
|
||||
import at.bitfire.davdroid.webdav.WebDavCollection.MultigetType;
|
||||
import at.bitfire.davdroid.webdav.WebDavResource;
|
||||
import at.bitfire.davdroid.webdav.WebDavResource.PutMode;
|
||||
|
||||
public abstract class RemoteCollection {
|
||||
public abstract class RemoteCollection<ResourceType extends Resource> {
|
||||
@Getter WebDavCollection collection;
|
||||
|
||||
protected abstract String memberContentType();
|
||||
abstract protected String memberContentType();
|
||||
abstract protected MultigetType multiGetType();
|
||||
abstract protected ResourceType newResourceSkeleton(String name, String ETag);
|
||||
|
||||
public RemoteCollection(String baseURL, String user, String password) throws IOException, URISyntaxException {
|
||||
collection = new WebDavCollection(new URI(baseURL), user, password);
|
||||
}
|
||||
|
||||
|
||||
/* collection methods */
|
||||
/* collection operations */
|
||||
|
||||
public String getCTag() throws IOException, HttpException {
|
||||
try {
|
||||
@ -39,33 +50,57 @@ public abstract class RemoteCollection {
|
||||
|
||||
public Resource[] getMemberETags() throws IOException, IncapableResourceException, HttpException {
|
||||
collection.propfind(HttpPropfind.Mode.MEMBERS_ETAG);
|
||||
return null;
|
||||
|
||||
List<ResourceType> resources = new LinkedList<ResourceType>();
|
||||
for (WebDavResource member : collection.getMembers())
|
||||
resources.add(newResourceSkeleton(member.getName(), member.getETag()));
|
||||
return resources.toArray(new Resource[0]);
|
||||
}
|
||||
|
||||
public abstract Resource[] multiGet(Resource[] resource) throws IOException, IncapableResourceException, HttpException, ParserException;
|
||||
@SuppressWarnings("unchecked")
|
||||
public Resource[] multiGet(ResourceType[] resources) throws IOException, IncapableResourceException, HttpException, ParserException {
|
||||
if (resources.length == 1) {
|
||||
Resource resource = get(resources[0]);
|
||||
return (resource != null) ? (ResourceType[]) new Resource[] { resource } : null;
|
||||
}
|
||||
|
||||
LinkedList<String> names = new LinkedList<String>();
|
||||
for (ResourceType resource : resources)
|
||||
names.add(resource.getName());
|
||||
|
||||
collection.multiGet(names.toArray(new String[0]), multiGetType());
|
||||
|
||||
LinkedList<ResourceType> foundResources = new LinkedList<ResourceType>();
|
||||
for (WebDavResource member : collection.getMembers()) {
|
||||
ResourceType resource = newResourceSkeleton(member.getName(), member.getETag());
|
||||
resource.parseEntity(member.getContent());
|
||||
foundResources.add(resource);
|
||||
}
|
||||
return foundResources.toArray(new Resource[0]);
|
||||
}
|
||||
|
||||
|
||||
/* internal member methods */
|
||||
/* internal member operations */
|
||||
|
||||
public Resource get(Resource resource) throws IOException, HttpException, ParserException {
|
||||
public ResourceType get(ResourceType resource) throws IOException, HttpException, ParserException {
|
||||
WebDavResource member = new WebDavResource(collection, resource.getName());
|
||||
member.get();
|
||||
resource.parseEntity(member.getContent());
|
||||
return resource;
|
||||
}
|
||||
|
||||
public void add(Resource resource) throws IOException, HttpException {
|
||||
public void add(ResourceType resource) throws IOException, HttpException {
|
||||
WebDavResource member = new WebDavResource(collection, resource.getName(), resource.getETag());
|
||||
member.setContentType(memberContentType());
|
||||
member.put(resource.toEntity().getBytes("UTF-8"), PutMode.ADD_DONT_OVERWRITE);
|
||||
}
|
||||
|
||||
public void delete(Resource resource) throws IOException, HttpException {
|
||||
public void delete(ResourceType resource) throws IOException, HttpException {
|
||||
WebDavResource member = new WebDavResource(collection, resource.getName(), resource.getETag());
|
||||
member.delete();
|
||||
}
|
||||
|
||||
public void update(Resource resource) throws IOException, HttpException {
|
||||
public void update(ResourceType resource) throws IOException, HttpException {
|
||||
WebDavResource member = new WebDavResource(collection, resource.getName(), resource.getETag());
|
||||
member.setContentType(memberContentType());
|
||||
member.put(resource.toEntity().getBytes("UTF-8"), PutMode.UPDATE_DONT_OVERWRITE);
|
||||
|
@ -12,11 +12,13 @@ import java.io.InputStream;
|
||||
|
||||
import net.fortuna.ical4j.data.ParserException;
|
||||
import lombok.Getter;
|
||||
import lombok.Setter;
|
||||
import lombok.ToString;
|
||||
|
||||
@ToString
|
||||
public abstract class Resource {
|
||||
@Getter protected String name, ETag;
|
||||
@Getter @Setter protected String uid;
|
||||
@Getter protected long localID;
|
||||
|
||||
@Getter protected boolean populated = false;
|
||||
|
@ -120,7 +120,7 @@ public class SyncManager {
|
||||
return;
|
||||
|
||||
for (Resource remoteResource : remoteResources) {
|
||||
Resource localResource = local.getByRemoteName(remoteResource.getName());
|
||||
Resource localResource = local.findByRemoteName(remoteResource.getName());
|
||||
if (localResource == null)
|
||||
resourcesToAdd.add(remoteResource);
|
||||
else if (localResource.getETag() == null || !localResource.getETag().equals(remoteResource.getETag()))
|
||||
|
@ -1,16 +1,19 @@
|
||||
/*******************************************************************************
|
||||
* Copyright (c) 2013 Richard Hirner (bitfire web engineering).
|
||||
* All rights reserved. This program and the accompanying materials
|
||||
* are made available under the terms of the GNU Public License v3.0
|
||||
* which accompanies this distribution, and is available at
|
||||
* http://www.gnu.org/licenses/gpl.html
|
||||
******************************************************************************/
|
||||
package at.bitfire.davdroid.test;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
import java.util.Calendar;
|
||||
import java.util.TimeZone;
|
||||
|
||||
import junit.framework.Assert;
|
||||
import net.fortuna.ical4j.data.ParserException;
|
||||
import net.fortuna.ical4j.model.Date;
|
||||
import android.content.res.AssetManager;
|
||||
import android.test.InstrumentationTestCase;
|
||||
import android.text.format.Time;
|
||||
import at.bitfire.davdroid.resource.Event;
|
||||
|
||||
public class CalendarTest extends InstrumentationTestCase {
|
||||
|
@ -1,3 +1,10 @@
|
||||
/*******************************************************************************
|
||||
* Copyright (c) 2013 Richard Hirner (bitfire web engineering).
|
||||
* All rights reserved. This program and the accompanying materials
|
||||
* are made available under the terms of the GNU Public License v3.0
|
||||
* which accompanies this distribution, and is available at
|
||||
* http://www.gnu.org/licenses/gpl.html
|
||||
******************************************************************************/
|
||||
package at.bitfire.davdroid.test;
|
||||
|
||||
import java.io.IOException;
|
||||
|