/*
* *
* Copyright (C) 2014 Open Whisper Systems
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
* /
*/
package org.anhonesteffort.flock.sync.calendar;
import android.accounts.Account;
import android.content.ContentProviderClient;
import android.content.ContentProviderOperation;
import android.content.ContentUris;
import android.content.ContentValues;
import android.content.OperationApplicationException;
import android.database.Cursor;
import android.net.Uri;
import android.os.RemoteException;
import android.provider.CalendarContract;
import android.util.Log;
import org.anhonesteffort.flock.util.guava.Optional;
import org.anhonesteffort.flock.sync.InvalidRemoteComponentException;
import org.anhonesteffort.flock.webdav.PropertyParseException;
import org.anhonesteffort.flock.webdav.caldav.CalDavConstants;
import net.fortuna.ical4j.model.Calendar;
import net.fortuna.ical4j.model.ConstraintViolationException;
import net.fortuna.ical4j.model.PropertyList;
import net.fortuna.ical4j.model.TimeZoneRegistry;
import net.fortuna.ical4j.model.TimeZoneRegistryFactory;
import net.fortuna.ical4j.model.component.VEvent;
import net.fortuna.ical4j.model.component.VTimeZone;
import net.fortuna.ical4j.model.property.Attendee;
import net.fortuna.ical4j.model.property.Organizer;
import net.fortuna.ical4j.model.property.Uid;
import net.fortuna.ical4j.model.property.Version;
import net.fortuna.ical4j.util.Calendars;
import org.anhonesteffort.flock.sync.AbstractLocalComponentCollection;
import org.anhonesteffort.flock.sync.InvalidLocalComponentException;
import org.anhonesteffort.flock.webdav.ComponentETagPair;
import org.anhonesteffort.flock.webdav.InvalidComponentException;
import java.net.URI;
import java.net.URISyntaxException;
import java.util.LinkedList;
import java.util.List;
import java.util.UUID;
/**
* Programmer: rhodey
*/
public class LocalEventCollection extends AbstractLocalComponentCollection<Calendar> {
private static final String TAG = "org.anhonesteffort.flock.sync.calendar.LocalEventCollection";
private static final String COLUMN_NAME_COLLECTION_C_TAG = CalendarContract.Calendars.CAL_SYNC2;
private static final String COLUMN_NAME_COLLECTION_ORDER = CalendarContract.Calendars.CAL_SYNC3;
protected static final String COLUMN_NAME_COLLECTION_COPIED = CalendarContract.Calendars.CAL_SYNC4;
public LocalEventCollection(ContentProviderClient client,
Account account,
Long localId,
String remotePath)
{
super(client, account, remotePath, localId);
}
public static Uri getSyncAdapterUri(Uri base, Account account) {
return base.buildUpon()
.appendQueryParameter(CalendarContract.Calendars.ACCOUNT_NAME, account.name)
.appendQueryParameter(CalendarContract.Calendars.ACCOUNT_TYPE, account.type)
.appendQueryParameter(CalendarContract.CALLER_IS_SYNCADAPTER, "true")
.build();
}
@Override
protected Uri getSyncAdapterUri(Uri base) {
return getSyncAdapterUri(base, account);
}
@Override
protected Uri handleAddAccountQueryParams(Uri uri) {
return CalendarContract.Events.CONTENT_URI.buildUpon()
.appendQueryParameter(CalendarContract.Calendars.ACCOUNT_NAME, account.name)
.appendQueryParameter(CalendarContract.Calendars.ACCOUNT_TYPE, account.type)
.build();
}
protected static Uri getCollectionsUri(Account account) {
return getSyncAdapterUri(CalendarContract.Calendars.CONTENT_URI, account);
}
private Uri getCollectionUri() {
return ContentUris.withAppendedId(getCollectionsUri(account), localId);
}
@Override
protected Uri getUriForComponents() {
if (account != null)
return getSyncAdapterUri(CalendarContract.Events.CONTENT_URI);
return CalendarContract.Events.CONTENT_URI.buildUpon()
.appendQueryParameter(CalendarContract.CALLER_IS_SYNCADAPTER, "true")
.build();
}
private Uri getUriForAttendees() {
return getSyncAdapterUri(CalendarContract.Attendees.CONTENT_URI);
}
private Uri getUriForReminders() {
return getSyncAdapterUri(CalendarContract.Reminders.CONTENT_URI);
}
@Override
protected String getColumnNameCollectionLocalId() {
return CalendarContract.Events.CALENDAR_ID;
}
@Override
protected String getColumnNameComponentLocalId() {
return CalendarContract.Events._ID;
}
@Override
protected String getColumnNameComponentUid() {
return EventFactory.COLUMN_NAME_EVENT_UID;
}
@Override
protected String getColumnNameComponentETag() {
return EventFactory.COLUMN_NAME_EVENT_ETAG;
}
@Override
protected String getColumnNameDirty() {
return CalendarContract.Events.DIRTY;
}
@Override
protected String getColumnNameDeleted() {
return CalendarContract.Events.DELETED;
}
@Override
protected String getColumnNameAccountType() {
return CalendarContract.Events.ACCOUNT_TYPE;
}
@Override
public List<Long> getNewComponentIds() throws RemoteException {
final String[] PROJECTION = new String[]{getColumnNameComponentLocalId()};
final String SELECTION = "(" + getColumnNameComponentUid() + " IS NULL OR " +
EventFactory.COLUMN_NAME_COPIED_EVENT_ID + " > 0) AND " +
getColumnNameCollectionLocalId() + "=" + localId;
Cursor cursor = client.query(getUriForComponents(), PROJECTION, SELECTION, null, null);
List<Long> newIds = new LinkedList<Long>();
if (cursor == null)
throw new RemoteException("Content provider client gave us a null cursor!");
while (cursor.moveToNext()) {
if (!newIds.contains(cursor.getLong(0))) // android gets weird sometimes :(
newIds.add(cursor.getLong(0));
}
cursor.close();
return newIds;
}
@Override
public boolean hasChanges() throws RemoteException {
final String[] PROJECTION = new String[]{};
final String SELECTION = "(" + getColumnNameComponentUid() + " IS NULL OR " +
getColumnNameDirty() + "=1 OR " +
getColumnNameDeleted() + "=1 OR " +
EventFactory.COLUMN_NAME_COPIED_EVENT_ID + "> 0) AND " +
getColumnNameCollectionLocalId() + "=" + localId;
Cursor cursor = client.query(getUriForComponents(), PROJECTION, SELECTION, null, null);
boolean hasChanges = cursor.moveToNext();
cursor.close();
return hasChanges;
}
@Override
public void cleanComponent(Long localId) {
Log.d(TAG, "cleanComponent() localId " + localId);
operationQueue.queue(ContentProviderOperation
.newUpdate(ContentUris.withAppendedId(getUriForComponents(), localId))
.withValue(getColumnNameDirty(), 0)
.withValue(EventFactory.COLUMN_NAME_COPIED_EVENT_ID, null)
.build());
}
@Override
public Optional<String> getDisplayName() throws RemoteException {
final String[] PROJECTION = new String[]{CalendarContract.Calendars.CALENDAR_DISPLAY_NAME};
Cursor cursor = client.query(getCollectionUri(), PROJECTION, null, null, null);
String displayName = null;
if (cursor == null)
throw new RemoteException("Content provider client gave us a null cursor!");
if (cursor.moveToNext())
displayName = cursor.getString(0);
cursor.close();
return Optional.fromNullable(displayName);
}
public void setVisible(Boolean isVisible) {
operationQueue.queue(ContentProviderOperation.newUpdate(getCollectionUri())
.withValue(CalendarContract.Calendars.VISIBLE, isVisible ? 1 : 0)
.build());
}
@Override
public void setDisplayName(String displayName) {
operationQueue.queue(
ContentProviderOperation.newUpdate(getCollectionUri())
.withValue(CalendarContract.Calendars.CALENDAR_DISPLAY_NAME, displayName)
.build(),
displayName.getBytes().length);
}
public Optional<Integer> getColor() throws RemoteException {
final String[] PROJECTION = new String[]{CalendarContract.Calendars.CALENDAR_COLOR};
Cursor cursor = client.query(getCollectionUri(), PROJECTION, null, null, null);
Integer color = null;
if (cursor == null)
throw new RemoteException("Content provider client gave us a null cursor!");
if (cursor.moveToNext())
color = cursor.getInt(0);
cursor.close();
if (color == null)
return Optional.absent();
return Optional.of(color);
}
public void setColor(int color) {
operationQueue.queue(ContentProviderOperation.newUpdate(getCollectionUri())
.withValue(CalendarContract.Calendars.CALENDAR_COLOR, color)
.build());
}
@Override
public Optional<String> getCTag() throws RemoteException {
final String[] PROJECTION = new String[]{COLUMN_NAME_COLLECTION_C_TAG};
Cursor cursor = client.query(getCollectionUri(), PROJECTION, null, null, null);
String cTag = null;
if (cursor == null)
throw new RemoteException("Content provider client gave us a null cursor!");
if (cursor.moveToNext())
cTag = cursor.getString(0);
cursor.close();
return Optional.fromNullable(cTag);
}
@Override
public void setCTag(String cTag) throws RemoteException {
operationQueue.queue(
ContentProviderOperation.newUpdate(getCollectionUri())
.withValue(COLUMN_NAME_COLLECTION_C_TAG, cTag)
.build(),
cTag.getBytes().length);
}
public Optional<Calendar> getTimeZone() throws RemoteException {
final String[] PROJECTION = new String[]{CalendarContract.Calendars.CALENDAR_TIME_ZONE};
Cursor cursor = client.query(getCollectionUri(), PROJECTION, null, null, null);
String timeZoneId = null;
if (cursor == null)
throw new RemoteException("Content provider client gave us a null cursor!");
if (cursor.moveToNext())
timeZoneId = cursor.getString(0);
cursor.close();
if (timeZoneId != null) {
Calendar calendar = new Calendar();
TimeZoneRegistry registry = TimeZoneRegistryFactory.getInstance().createRegistry();
VTimeZone vTimeZone = registry.getTimeZone(timeZoneId).getVTimeZone();
calendar.getProperties().add(Version.VERSION_2_0);
calendar.getComponents().add(vTimeZone);
return Optional.of(calendar);
}
return Optional.absent();
}
public void setTimeZone(Calendar timezone) throws PropertyParseException {
VTimeZone vTimeZone = (VTimeZone) timezone.getComponent(VTimeZone.VTIMEZONE);
if (vTimeZone != null && vTimeZone.getTimeZoneId() != null) {
operationQueue.queue(ContentProviderOperation.newUpdate(getCollectionUri())
.withValue(CalendarContract.Calendars.CALENDAR_TIME_ZONE, vTimeZone.getTimeZoneId().getValue())
.build());
}
else
throw new PropertyParseException("Calendar object must contain a valid VTimeZone component.",
getPath(), CalDavConstants.PROPERTY_NAME_CALENDAR_TIMEZONE);
}
public Optional<Integer> getOrder() throws RemoteException {
final String[] PROJECTION = new String[]{COLUMN_NAME_COLLECTION_ORDER};
Cursor cursor = client.query(getCollectionUri(), PROJECTION, null, null, null);
Integer order = null;
if (cursor == null)
throw new RemoteException("Content provider client gave us a null cursor!");
if (cursor.moveToNext())
order = cursor.getInt(0);
cursor.close();
return Optional.fromNullable(order);
}
public void setOrder(Integer order) {
operationQueue.queue(ContentProviderOperation.newUpdate(getCollectionUri())
.withValue(COLUMN_NAME_COLLECTION_ORDER, order)
.build());
}
private void addAttendees(Long eventId, Calendar component)
throws InvalidLocalComponentException, RemoteException
{
String SELECTION = CalendarContract.Attendees.EVENT_ID + "=?";
String[] SELECTION_ARGS = new String[]{eventId.toString()};
Cursor cursor = client.query(getUriForAttendees(),
EventFactory.getProjectionForAttendee(),
SELECTION,
SELECTION_ARGS,
null);
while (cursor.moveToNext()) {
ContentValues attendeeValues = EventFactory.getValuesForAttendee(cursor);
EventFactory.addAttendee(getPath(), component, attendeeValues);
}
cursor.close();
}
private void addReminders(Long eventId, Calendar component)
throws InvalidLocalComponentException, RemoteException
{
String SELECTION = CalendarContract.Reminders.EVENT_ID + "=?";
String[] SELECTION_ARGS = new String[]{eventId.toString()};
Cursor cursor = client.query(getUriForReminders(),
EventFactory.getProjectionForReminder(),
SELECTION,
SELECTION_ARGS,
null);
while (cursor.moveToNext()) {
Optional<ContentValues> reminderValues = EventFactory.getValuesForReminder(cursor);
if (reminderValues.isPresent())
EventFactory.addReminder(component, reminderValues.get());
}
cursor.close();
}
private void buildEvent(Long eventId, Calendar component)
throws InvalidLocalComponentException, RemoteException
{
addAttendees(eventId, component);
addReminders(eventId, component);
}
@Override
public Optional<Calendar> getComponent(Long eventId)
throws RemoteException, InvalidLocalComponentException
{
Cursor cursor = client.query(ContentUris.withAppendedId(getUriForComponents(), eventId),
EventFactory.getProjectionForEvent(),
null,
null,
null);
if (cursor == null)
throw new RemoteException("Content provider client gave us a null cursor!");
if (cursor.moveToNext()) {
ContentValues eventValues = EventFactory.getValuesForEvent(cursor);
ComponentETagPair<Calendar> component = EventFactory.getEventComponent(getPath(), eventValues);
buildEvent(eventId, component.getComponent());
cursor.close();
return Optional.of(component.getComponent());
}
cursor.close();
return Optional.absent();
}
@Override
public Optional<ComponentETagPair<Calendar>> getComponent(String uid)
throws RemoteException, InvalidLocalComponentException
{
String SELECTION = getColumnNameComponentUid() + "=?";
String[] SELECTION_ARGS = new String[]{uid};
Cursor cursor = client.query(getUriForComponents(),
EventFactory.getProjectionForEvent(),
SELECTION,
SELECTION_ARGS,
null);
if (cursor == null)
throw new RemoteException("Content provider client gave us a null cursor!");
if (cursor.moveToNext()) {
ContentValues eventValues = EventFactory.getValuesForEvent(cursor);
ComponentETagPair<Calendar> component = EventFactory.getEventComponent(getPath(), eventValues);
buildEvent(eventValues.getAsLong(CalendarContract.Events._ID), component.getComponent());
cursor.close();
return Optional.of(component);
}
cursor.close();
return Optional.absent();
}
@Override
public List<ComponentETagPair<Calendar>> getComponents()
throws RemoteException, InvalidLocalComponentException
{
String SELECTION = getColumnNameCollectionLocalId() + "=?";
String[] SELECTION_ARGS = new String[]{localId.toString()};
List<ComponentETagPair<Calendar>> components = new LinkedList<ComponentETagPair<Calendar>>();
Cursor cursor = client.query(getUriForComponents(),
EventFactory.getProjectionForEvent(),
SELECTION,
SELECTION_ARGS, null);
if (cursor == null)
throw new RemoteException("Content provider client gave us a null cursor!");
while (cursor.moveToNext()) {
ContentValues eventValues = EventFactory.getValuesForEvent(cursor);
Long eventId = eventValues.getAsLong(getColumnNameComponentLocalId());
try {
ComponentETagPair<Calendar> component = EventFactory.getEventComponent(getPath(), eventValues);
buildEvent(eventValues.getAsLong(CalendarContract.Events._ID), component.getComponent());
components.add(component);
} catch (InvalidLocalComponentException e) {
if (e.getUid().isPresent())
throw new InvalidLocalComponentException(e.getMessage(), CalDavConstants.CALDAV_NAMESPACE,
getPath(), e.getUid().get(), eventId, e);
throw new InvalidLocalComponentException(e.getMessage(), CalDavConstants.CALDAV_NAMESPACE,
getPath(), eventId, e);
}
}
cursor.close();
return components;
}
@Override
public int addComponent(ComponentETagPair<Calendar> component)
throws RemoteException, InvalidRemoteComponentException
{
int eventOpIndex = operationQueue.size();
ContentValues eventValues = EventFactory.getValuesForEvent(this, localId, component);
operationQueue.queue(
ContentProviderOperation.newInsert(getUriForComponents())
.withValues(eventValues)
.build(),
512);
List<ContentValues> attendeeValues = EventFactory.getValuesForAttendees(component.getComponent());
for (ContentValues attendee : attendeeValues) {
operationQueue.queue(
ContentProviderOperation.newInsert(getUriForAttendees())
.withValues(attendee)
.withValueBackReference(CalendarContract.Attendees.EVENT_ID, eventOpIndex)
.build(),
256);
}
List<ContentValues> reminderValues = EventFactory.getValuesForReminders(component.getComponent());
for (ContentValues reminder : reminderValues) {
operationQueue.queue(
ContentProviderOperation.newInsert(getUriForReminders())
.withValues(reminder)
.withValueBackReference(CalendarContract.Reminders.EVENT_ID, eventOpIndex)
.build(),
256);
}
return operationQueue.size() - eventOpIndex;
}
@Override
public void removeComponent(String remoteUId) throws RemoteException {
final String SELECTION = getColumnNameComponentUid() + "=?";
final String[] SELECTION_ARGS = new String[]{remoteUId};
final Optional<Long> LOCAL_ID = getLocalIdForUid(remoteUId);
operationQueue.queue(ContentProviderOperation
.newDelete(getUriForComponents())
.withSelection(SELECTION, SELECTION_ARGS)
.withYieldAllowed(true)
.build());
if (LOCAL_ID.isPresent()) {
operationQueue.queue(ContentProviderOperation
.newDelete(ContentUris.withAppendedId(getUriForAttendees(), LOCAL_ID.get()))
.withYieldAllowed(true)
.build());
operationQueue.queue(ContentProviderOperation
.newDelete(ContentUris.withAppendedId(getUriForReminders(), LOCAL_ID.get()))
.withYieldAllowed(true)
.build());
}
}
@Override
public int updateComponent(ComponentETagPair<Calendar> component)
throws RemoteException, InvalidRemoteComponentException
{
try {
String componentUid = Calendars.getUid(component.getComponent()).getValue();
removeComponent(componentUid);
return addComponent(component);
} catch (ConstraintViolationException e) {
Log.d(TAG, "caught exception while updating component ", e);
throw new InvalidRemoteComponentException("Caught exception while parsing UID from calendar",
CalDavConstants.CALDAV_NAMESPACE, getPath(), e);
}
}
public void handleCorrectEventReminders() throws RemoteException {
final ContentValues UPDATE_VALUES = new ContentValues(1);
final String SELECTION = CalendarContract.Reminders.METHOD + "=?";
final String[] SELECTION_ARGS = new String[] {
Integer.toString(CalendarContract.Reminders.METHOD_DEFAULT)
};
UPDATE_VALUES.put(CalendarContract.Reminders.METHOD, CalendarContract.Reminders.METHOD_ALERT);
int updateCount = client.update(getUriForReminders(), UPDATE_VALUES, SELECTION, SELECTION_ARGS);
Log.d(TAG, "corrected " + updateCount + " event reminders.");
}
private boolean hasRecurrenceExceptions(Long eventId) throws RemoteException {
final String[] PROJECTION = new String[]{getColumnNameComponentLocalId(), CalendarContract.Events.ORIGINAL_ID};
final String SELECTION = CalendarContract.Events.ORIGINAL_ID + "=" + eventId;
Cursor cursor = client.query(getUriForComponents(), PROJECTION, SELECTION, null, null);
if (cursor == null)
throw new RemoteException("Content provider client gave us a null cursor!");
if (cursor.moveToNext()) {
cursor.close();
return true;
}
cursor.close();
return false;
}
private Optional<Long> getOriginalIdForRecurrenceException(Long recurrenceExceptionId)
throws RemoteException
{
final String[] PROJECTION = new String[]{CalendarContract.Events.ORIGINAL_ID};
final String SELECTION = getColumnNameComponentLocalId() + "=" + recurrenceExceptionId;
Cursor cursor = client.query(getUriForComponents(), PROJECTION, SELECTION, null, null);
if (cursor == null)
throw new RemoteException("Content provider client gave us a null cursor!");
Optional<Long> originalId = Optional.absent();
if (cursor.moveToNext())
originalId = Optional.of(cursor.getLong(0));
cursor.close();
return originalId;
}
private Optional<String> getUidForCopiedEventLocalId(Long copiedEventId) throws RemoteException {
final String[] PROJECTION = new String[]{getColumnNameComponentUid()};
final String SELECTION = EventFactory.COLUMN_NAME_COPIED_EVENT_ID + "=" + copiedEventId;
Cursor cursor = client.query(getUriForComponents(), PROJECTION, SELECTION, null, null);
if (cursor == null)
throw new RemoteException("Content provider client gave us a null cursor!");
Optional<String> uid = Optional.absent();
if (cursor.moveToNext())
uid = Optional.fromNullable(cursor.getString(0));
cursor.close();
return uid;
}
private void handleCorrectOrganizersAndAttendees(VEvent vEvent, Account toAccount)
throws InvalidLocalComponentException
{
Organizer oldOrganizer = vEvent.getOrganizer();
if (oldOrganizer != null)
vEvent.getProperties().remove(oldOrganizer);
try {
URI newOrganizerEmail = new URI("mailto", toAccount.name, null);
Organizer newOrganizer = new Organizer(newOrganizerEmail);
vEvent.getProperties().add(newOrganizer);
PropertyList attendeeList = vEvent.getProperties(Attendee.ATTENDEE);
for (int i = 0; i < attendeeList.size(); i++) {
Attendee attendee = (Attendee) attendeeList.get(i);
if (attendee != null) {
String attendeeEmail = Uri.parse(attendee.getValue()).getSchemeSpecificPart();
if (attendeeEmail != null && attendeeEmail.equals(account.name))
attendee.setValue(new URI("mailto", toAccount.name, null).toString());
}
}
} catch (URISyntaxException e) {
Log.e(TAG, "caught exception while copying collection to account ", e);
throw new InvalidLocalComponentException("caught exception while copying collection to account",
CalDavConstants.CALDAV_NAMESPACE, getPath(), e);
}
}
private boolean handleCommitPendingIfFull(LocalEventCollection toCollection,
List<Integer> eventOperationCounts,
CalendarCopiedListener listener,
boolean forceFull)
{
if (toCollection.operationQueue.hasSpace() && !forceFull)
return false;
try {
int pendingCount = toCollection.operationQueue.size();
int successCount = toCollection.commitPendingOperations();
int failCount = pendingCount - successCount;
Log.d(TAG, pendingCount + " were pending " + successCount + " were committed");
int eventCount = 0;
while (eventCount++ < eventOperationCounts.size())
listener.onEventCopied(account, toCollection.getAccount(), localId);
if (failCount > 0)
Log.e(TAG, "failed to commit " + failCount + "" +
"operations but no idea which events they're from!");
} catch (OperationApplicationException e) {
int eventCount = 0;
while (eventCount++ < eventOperationCounts.size())
listener.onEventCopyFailed(e, account, toCollection.getAccount(), localId);
} catch (RemoteException e) {
int eventCount = 0;
while (eventCount++ < eventOperationCounts.size())
listener.onEventCopyFailed(e, account, toCollection.getAccount(), localId);
}
return true;
}
private void handleCopyRecurrenceExceptions(Account toAccount,
LocalEventCollection toCollection,
CalendarCopiedListener listener)
throws RemoteException
{
List<Long> componentIds = getComponentIds();
List<Integer> eventOperationCounts = new LinkedList<Integer>();
for (Long eventId : componentIds) {
try {
Optional<Calendar> copyComponent = getComponent(eventId);
if (!copyComponent.isPresent())
throw new InvalidLocalComponentException("absent returned on copy of " + eventId + " from " + localId,
CalDavConstants.CALDAV_NAMESPACE, getPath(), eventId);
VEvent vEvent = (VEvent) copyComponent.get().getComponent(VEvent.VEVENT);
if (vEvent != null) {
Uid uid = vEvent.getUid();
if (uid != null)
uid.setValue(null);
handleCorrectOrganizersAndAttendees(vEvent, toAccount);
ComponentETagPair<Calendar> correctedComponent =
new ComponentETagPair<Calendar>(copyComponent.get(), Optional.<String>absent());
if (EventFactory.isRecurrenceException(vEvent)) {
Log.d(TAG, "found recurrence exception (" + eventId + ") during copy, will copy over now");
Optional<Long> originalId = getOriginalIdForRecurrenceException(eventId);
if (!originalId.isPresent()) {
throw new InvalidLocalComponentException("could not get original ID for recurrence exception",
CalDavConstants.CALDAV_NAMESPACE, getPath(), eventId);
}
Optional<String> parentUid = toCollection.getUidForCopiedEventLocalId(originalId.get());
if (!parentUid.isPresent()) {
throw new InvalidLocalComponentException("could not get uid for copied event local id",
CalDavConstants.CALDAV_NAMESPACE, getPath(), eventId);
}
EventFactory.handleReplaceOriginalSyncId(getPath(), parentUid.get(), vEvent);
eventOperationCounts.add(toCollection.addComponent(correctedComponent));
if (handleCommitPendingIfFull(toCollection, eventOperationCounts, listener, false))
eventOperationCounts.clear();
}
}
else
throw new InvalidLocalComponentException("could not parse VEvent from calendar component.",
CalDavConstants.CALDAV_NAMESPACE, getPath(), eventId);
} catch (InvalidComponentException e) {
listener.onEventCopyFailed(e, getAccount(), toAccount, localId);
}
}
if (toCollection.operationQueue.size() > 0)
handleCommitPendingIfFull(toCollection, eventOperationCounts, listener, true);
}
public void copyToAccount(Account toAccount,
String newCalendarName,
int newCalendarColor,
CalendarCopiedListener listener)
throws RemoteException
{
LocalCalendarStore toStore = new LocalCalendarStore(client, toAccount);
String tempRemotePath = UUID.randomUUID().toString();
Optional<LocalEventCollection> toCollection = Optional.absent();
List<Long> componentIds = getComponentIds();
List<Integer> eventOperationCounts = new LinkedList<Integer>();
Log.d(TAG, "copy my " + componentIds.size() + " events to account " + toAccount.name);
try {
toStore.addCollection(tempRemotePath, newCalendarName, newCalendarColor);
toCollection = toStore.getCollection(tempRemotePath);
if (!toCollection.isPresent()) {
Log.e(TAG, "local calendar store for " + toAccount.name +
" returned absent for the collection we just copied");
throw new RemoteException("LocalCalendarStore does not have a copy of our collection!");
}
toStore.setCollectionCopied(toCollection.get().getLocalId(), true);
setVisible(false);
commitPendingOperations();
listener.onCalendarCopied(getAccount(), toAccount, localId);
} catch (RemoteException e) {
listener.onCalendarCopyFailed(e, getAccount(), toAccount, localId);
return;
} catch (OperationApplicationException e) {
listener.onCalendarCopyFailed(e, getAccount(), toAccount, localId);
return;
}
for (Long eventId : componentIds) {
try {
Optional<Calendar> copyComponent = getComponent(eventId);
if (!copyComponent.isPresent())
throw new InvalidLocalComponentException("absent returned on copy of " + eventId + " from " + localId,
CalDavConstants.CALDAV_NAMESPACE, getPath(), eventId);
VEvent vEvent = (VEvent) copyComponent.get().getComponent(VEvent.VEVENT);
if (vEvent != null) {
Uid uid = vEvent.getUid();
if (uid != null)
uid.setValue(null);
handleCorrectOrganizersAndAttendees(vEvent, toAccount);
ComponentETagPair<Calendar> correctedComponent =
new ComponentETagPair<Calendar>(copyComponent.get(), Optional.<String>absent());
if (EventFactory.isRecurrenceException(vEvent))
Log.d(TAG, "found recurrence exception (" + eventId + ") during copy, will copy over next");
else if (hasRecurrenceExceptions(eventId)) {
EventFactory.handleAttachPropertiesForCopiedRecurrenceWithExceptions(vEvent, eventId);
eventOperationCounts.add(toCollection.get().addComponent(correctedComponent));
if (handleCommitPendingIfFull(toCollection.get(), eventOperationCounts, listener, false))
eventOperationCounts.clear();
}
else {
eventOperationCounts.add(toCollection.get().addComponent(correctedComponent));
if (handleCommitPendingIfFull(toCollection.get(), eventOperationCounts, listener, false))
eventOperationCounts.clear();
}
}
else
throw new InvalidLocalComponentException("could not parse VEvent from calendar component.",
CalDavConstants.CALDAV_NAMESPACE, getPath(), eventId);
} catch (InvalidComponentException e) {
listener.onEventCopyFailed(e, getAccount(), toAccount, localId);
}
}
if (toCollection.get().operationQueue.size() > 0)
handleCommitPendingIfFull(toCollection.get(), eventOperationCounts, listener, true);
handleCopyRecurrenceExceptions(toAccount, toCollection.get(), listener);
}
}