Refactoring

* move common code to super-classes
* new icons
pull/2/head
rfc2822 11 years ago
parent 3729951b52
commit 495b854bf2

Binary file not shown.

Before

Width:  |  Height:  |  Size: 129 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 6.0 KiB

After

Width:  |  Height:  |  Size: 4.9 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 3.6 KiB

After

Width:  |  Height:  |  Size: 3.0 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 9.0 KiB

After

Width:  |  Height:  |  Size: 7.1 KiB

Binary file not shown.

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
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 MultigetType multiGetType() {
return MultigetType.CALENDAR;
}
@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]);
protected Event newResourceSkeleton(String name, String ETag) {
return new Event(name, ETag);
}
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,73 +38,41 @@ 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;
public LocalAddressBook(Account account, ContentProviderClient providerClient, AccountManager accountManager) {
super(account, providerClient);
this.accountManager = accountManager;
}
/* find multiple rows */
/* database fields */
@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]);
protected Uri entriesURI() {
return syncAdapterURI(RawContacts.CONTENT_URI);
}
@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]);
}
protected String entryColumnAccountType() { return RawContacts.ACCOUNT_TYPE; }
protected String entryColumnAccountName() { return RawContacts.ACCOUNT_NAME; }
@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);
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; }
// 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]);
protected String entryColumnUID() { return RawContacts.SYNC1; }
public LocalAddressBook(Account account, ContentProviderClient providerClient, AccountManager accountManager) {
super(account, providerClient);
this.accountManager = accountManager;
}
/* collection operations */
@Override
public String getCTag() {
return accountManager.getUserData(account, Constants.ACCOUNT_KEY_ADDRESSBOOK_CTAG);
@ -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();
private Builder newDataInsertBuilder(long raw_contact_id, Integer backrefIdx) {
return newDataInsertBuilder(dataURI(), Data.RAW_CONTACT_ID, raw_contact_id, backrefIdx);
}
/* content builder methods */
@Override
public void clearDirty(Resource resource) {
pendingOperations.add(ContentProviderOperation
.newUpdate(ContentUris.withAppendedId(entriesURI(), resource.getLocalID()))
.withValue(RawContacts.DIRTY, 0).build());
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());
}
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);
}
@Override
protected void addDataRows(Contact contact, long localID, int backrefIdx) {
pendingOperations.add(buildStructuredName(newInsertBuilder(localID, backrefIdx), contact).build());
pendingOperations.add(buildStructuredName(newDataInsertBuilder(localID, backrefIdx), contact).build());
if (contact.getNickNames() != null)
for (String nick : contact.getNickNames().getValues())
pendingOperations.add(buildNickName(newInsertBuilder(localID, backrefIdx), nick).build());
pendingOperations.add(buildNickName(newDataInsertBuilder(localID, backrefIdx), nick).build());
for (PhotoType photo : contact.getPhotos())
pendingOperations.add(buildPhoto(newInsertBuilder(localID, backrefIdx), photo).build());
pendingOperations.add(buildPhoto(newDataInsertBuilder(localID, backrefIdx), photo).build());
for (TelephoneType number : contact.getPhoneNumbers())
pendingOperations.add(buildPhoneNumber(newInsertBuilder(localID, backrefIdx), number).build());
pendingOperations.add(buildPhoneNumber(newDataInsertBuilder(localID, backrefIdx), number).build());
for (EmailType email : contact.getEmails())
pendingOperations.add(buildEmail(newInsertBuilder(localID, backrefIdx), email).build());
pendingOperations.add(buildEmail(newDataInsertBuilder(localID, backrefIdx), email).build());
for (UrlType url : contact.getURLs())
pendingOperations.add(buildURL(newInsertBuilder(localID, backrefIdx), url).build());
pendingOperations.add(buildURL(newDataInsertBuilder(localID, backrefIdx), url).build());
for (NoteType note : contact.getNotes())
pendingOperations.add(buildNote(newInsertBuilder(localID, backrefIdx), note).build());
pendingOperations.add(buildNote(newDataInsertBuilder(localID, backrefIdx), note).build());
}
/* content builder methods */
@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;
/* 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; }
/* class methods */
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;
// collection operations
// cache management
abstract public String getCTag();
abstract public void setCTag(String cTag);
// content provider (= database) querying
// fetch
public abstract Resource getByRemoteName(String name) throws RemoteException;
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]);
}
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;
// modify
public abstract void add(Resource resource);
public abstract void updateByRemoteName(Resource remoteResource) throws RemoteException;
// create/update/delete
public void delete(Resource resource) {
pendingOperations.add(ContentProviderOperation.newDelete(
ContentUris.withAppendedId(entriesURI(), resource.getLocalID()))
.build());
public void add(ResourceType resource) {
int idx = pendingOperations.size();
pendingOperations.add(
buildEntry(ContentProviderOperation.newInsert(entriesURI()), resource)
.withYieldAllowed(true)
.build());
addDataRows(resource, -1, idx);
}
public abstract void deleteAllExceptRemoteNames(Resource[] remoteRecords);
// database operations
protected abstract Uri syncAdapterURI(Uri baseURI);
protected abstract Uri entriesURI();
public abstract void clearDirty(Resource resource);
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;

Loading…
Cancel
Save