From 9970f8017f3b9c04cc3b50cee1eb3d8c78a6196c Mon Sep 17 00:00:00 2001 From: Andreas Shimokawa Date: Sun, 16 Apr 2017 19:37:43 +0200 Subject: [PATCH] Calendar sync: save sync status to db to avoid leakage of deleted events We now have a per device database that tracks sync states for calendar entries We still cannot track changed calendar entries that where changed while we were disconnected --- .../gadgetbridge/daogen/GBDaoGenerator.java | 19 +- .../externalevents/CalendarReceiver.java | 165 ++++++++++++------ .../service/DeviceCommunicationService.java | 4 +- 3 files changed, 136 insertions(+), 52 deletions(-) diff --git a/GBDaoGenerator/src/nodomain/freeyourgadget/gadgetbridge/daogen/GBDaoGenerator.java b/GBDaoGenerator/src/nodomain/freeyourgadget/gadgetbridge/daogen/GBDaoGenerator.java index d70679a1..9927a60f 100644 --- a/GBDaoGenerator/src/nodomain/freeyourgadget/gadgetbridge/daogen/GBDaoGenerator.java +++ b/GBDaoGenerator/src/nodomain/freeyourgadget/gadgetbridge/daogen/GBDaoGenerator.java @@ -17,6 +17,7 @@ package nodomain.freeyourgadget.gadgetbridge.daogen; import de.greenrobot.daogenerator.DaoGenerator; import de.greenrobot.daogenerator.Entity; +import de.greenrobot.daogenerator.Index; import de.greenrobot.daogenerator.Property; import de.greenrobot.daogenerator.Schema; @@ -39,8 +40,9 @@ public class GBDaoGenerator { private static final String TIMESTAMP_FROM = "timestampFrom"; private static final String TIMESTAMP_TO = "timestampTo"; + public static void main(String[] args) throws Exception { - Schema schema = new Schema(15, MAIN_PACKAGE + ".entities"); + Schema schema = new Schema(17, MAIN_PACKAGE + ".entities"); Entity userAttributes = addUserAttributes(schema); Entity user = addUserInfo(schema, userAttributes); @@ -63,6 +65,8 @@ public class GBDaoGenerator { addHPlusHealthActivityKindOverlay(schema, user, device); addHPlusHealthActivitySample(schema, user, device); + addCalendarSyncState(schema, device); + new DaoGenerator().generateAll(schema, "app/src/main/java"); } @@ -267,6 +271,19 @@ public class GBDaoGenerator { activitySample.addToOne(user, userId); } + private static void addCalendarSyncState(Schema schema, Entity device) { + Entity calendarSyncState = addEntity(schema, "CalendarSyncState"); + Property deviceId = calendarSyncState.addLongProperty("deviceId").notNull().getProperty(); + Property calendarEntryId = calendarSyncState.addLongProperty("calendarEntryId").notNull().getProperty(); + Index indexUnique = new Index(); + indexUnique.addProperty(deviceId); + indexUnique.addProperty(calendarEntryId); + indexUnique.makeUnique(); + calendarSyncState.addIndex(indexUnique); + calendarSyncState.addToOne(device, deviceId); + calendarSyncState.addIntProperty("syncState").notNull(); + } + private static Property findProperty(Entity entity, String propertyName) { for (Property prop : entity.getProperties()) { if (propertyName.equals(prop.getPropertyName())) { diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/externalevents/CalendarReceiver.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/externalevents/CalendarReceiver.java index b09435a0..b81b417e 100644 --- a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/externalevents/CalendarReceiver.java +++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/externalevents/CalendarReceiver.java @@ -22,39 +22,48 @@ import android.app.PendingIntent; import android.content.BroadcastReceiver; import android.content.Context; import android.content.Intent; +import android.widget.Toast; import org.slf4j.Logger; import org.slf4j.LoggerFactory; -import java.util.ArrayList; import java.util.Calendar; import java.util.Enumeration; import java.util.Hashtable; import java.util.List; -import nodomain.freeyourgadget.gadgetbridge.BuildConfig; +import de.greenrobot.dao.query.QueryBuilder; import nodomain.freeyourgadget.gadgetbridge.GBApplication; +import nodomain.freeyourgadget.gadgetbridge.database.DBHandler; +import nodomain.freeyourgadget.gadgetbridge.database.DBHelper; +import nodomain.freeyourgadget.gadgetbridge.entities.CalendarSyncState; +import nodomain.freeyourgadget.gadgetbridge.entities.CalendarSyncStateDao; +import nodomain.freeyourgadget.gadgetbridge.entities.DaoSession; +import nodomain.freeyourgadget.gadgetbridge.impl.GBDevice; import nodomain.freeyourgadget.gadgetbridge.model.CalendarEventSpec; import nodomain.freeyourgadget.gadgetbridge.model.CalendarEvents; +import nodomain.freeyourgadget.gadgetbridge.util.GB; public class CalendarReceiver extends BroadcastReceiver { private static final Logger LOG = LoggerFactory.getLogger(CalendarReceiver.class); - private static Hashtable eventState = new Hashtable<>(); + private Hashtable eventState = new Hashtable<>(); + + private GBDevice mGBDevice; private class EventSyncState { - private EventState state; + private int state; private CalendarEvents.CalendarEvent event; - public EventSyncState(CalendarEvents.CalendarEvent event, EventState state) { + EventSyncState(CalendarEvents.CalendarEvent event, int state) { this.state = state; this.event = event; } - public EventState getState() { + public int getState() { return state; } - public void setState(EventState state) { + public void setState(int state) { this.state = state; } @@ -67,76 +76,134 @@ public class CalendarReceiver extends BroadcastReceiver { } } - private enum EventState { - NOT_SYNCED, SYNCED, NEEDS_UPDATE, NEEDS_DELETE + private static class EventState { + private static final int NOT_SYNCED = 0; + private static final int SYNCED = 1; + private static final int NEEDS_UPDATE = 2; + private static final int NEEDS_DELETE = 3; } - public CalendarReceiver() { + public CalendarReceiver(GBDevice gbDevice) { LOG.info("Created calendar receiver."); + mGBDevice = gbDevice; Context context = GBApplication.getContext(); - Intent intent = new Intent("CALENDAR_SYNC"); - intent.setPackage(BuildConfig.APPLICATION_ID); PendingIntent pendingIntent = PendingIntent.getBroadcast(context, 0, new Intent("CALENDAR_SYNC"), 0); AlarmManager am = (AlarmManager) (context.getSystemService(Context.ALARM_SERVICE)); - am.setInexactRepeating(AlarmManager.RTC_WAKEUP, Calendar.getInstance().getTimeInMillis() + 10000, AlarmManager.INTERVAL_HALF_HOUR, pendingIntent); + + //FIXME: 30 sec interval is only for debugging + am.setInexactRepeating(AlarmManager.RTC_WAKEUP, Calendar.getInstance().getTimeInMillis() + 10000, 30000, pendingIntent); + + //am.setInexactRepeating(AlarmManager.RTC_WAKEUP, Calendar.getInstance().getTimeInMillis() + 10000, AlarmManager.INTERVAL_HALF_HOUR, pendingIntent); + //syncCalendar(); - does not work here (device not yet initialized) } @Override public void onReceive(Context context, Intent intent) { - LOG.info("Syncing with calendar."); - List eventList = (new CalendarEvents()).getCalendarEventList(GBApplication.getContext()); - Hashtable eventTable = new Hashtable<>(); - - for (CalendarEvents.CalendarEvent e: eventList) { - eventTable.put(e.getId(), e); - if (!eventState.containsKey(e.getId())) { - eventState.put(e.getId(), new EventSyncState(e, EventState.NOT_SYNCED)); - } - } - - Enumeration ids = eventState.keys(); - while (ids.hasMoreElements()) { - Long i = ids.nextElement(); - EventSyncState es = eventState.get(i); - if (eventTable.containsKey(i)) { - if (es.getState() == EventState.SYNCED) { - if (!es.getEvent().equals(eventTable.get(i))) { - eventState.put(i, new EventSyncState(eventTable.get(i), EventState.NEEDS_UPDATE)); - } - } - } else { - if (es.getState() == EventState.NOT_SYNCED) { - eventState.remove(i); - } else { - es.setState(EventState.NEEDS_DELETE); - eventState.put(i, es); - } - } - } - - updateEvents(); + syncCalendar(); } - private void updateEvents() { + public void syncCalendar() { + LOG.info("Syncing with calendar."); + List eventList = (new CalendarEvents()).getCalendarEventList(GBApplication.getContext()); + Hashtable eventTable = new Hashtable<>(); + + try (DBHandler dbHandler = GBApplication.acquireDB()) { + DaoSession session = dbHandler.getDaoSession(); + Long deviceId = DBHelper.getDevice(mGBDevice, session).getId(); + + QueryBuilder qb = session.getCalendarSyncStateDao().queryBuilder(); + + + for (CalendarEvents.CalendarEvent e : eventList) { + eventTable.put(e.getId(), e); + if (!eventState.containsKey(e.getId())) { + qb = session.getCalendarSyncStateDao().queryBuilder(); + + CalendarSyncState calendarSyncState = qb.where(qb.and(CalendarSyncStateDao.Properties.DeviceId.eq(deviceId), CalendarSyncStateDao.Properties.CalendarEntryId.eq(e.getId()))) + .build().unique(); + if (calendarSyncState == null) { + eventState.put(e.getId(), new EventSyncState(e, EventState.NOT_SYNCED)); + } else { + eventState.put(e.getId(), new EventSyncState(e, calendarSyncState.getSyncState())); + } + } + } + + // add all missing calendar ids on the device to sync status (so that they are deleted later) + List CalendarSyncStateList = qb.where(CalendarSyncStateDao.Properties.DeviceId.eq(deviceId)).build().list(); + for (CalendarSyncState CalendarSyncState : CalendarSyncStateList) { + if (!eventState.containsKey(CalendarSyncState.getCalendarEntryId())) { + eventState.put(CalendarSyncState.getCalendarEntryId(), new EventSyncState(null, CalendarSyncState.getSyncState())); + LOG.info("insert null event for orphanded calendar id=" + CalendarSyncState.getCalendarEntryId() + " for device=" + mGBDevice.getName()); + } + } + + Enumeration ids = eventState.keys(); + while (ids.hasMoreElements()) { + qb = session.getCalendarSyncStateDao().queryBuilder(); + Long i = ids.nextElement(); + EventSyncState es = eventState.get(i); + if (eventTable.containsKey(i)) { + if (es.getState() == EventState.SYNCED) { + if (!es.getEvent().equals(eventTable.get(i))) { + eventState.put(i, new EventSyncState(eventTable.get(i), EventState.NEEDS_UPDATE)); + // update sync status of that Calendar entry in DB for all devices + CalendarSyncStateList = qb.where(CalendarSyncStateDao.Properties.CalendarEntryId.eq(i)).build().list(); + for (CalendarSyncState CalendarSyncState : CalendarSyncStateList) { + CalendarSyncState.setSyncState(EventState.NEEDS_UPDATE); + CalendarSyncState.update(); + } + } + } + } else { + if (es.getState() == EventState.NOT_SYNCED) { + // delete for current device only + qb.where(qb.and(CalendarSyncStateDao.Properties.DeviceId.eq(deviceId), CalendarSyncStateDao.Properties.CalendarEntryId.eq(i))) + .buildDelete().executeDeleteWithoutDetachingEntities(); + eventState.remove(i); + } else { + es.setState(EventState.NEEDS_DELETE); + eventState.put(i, es); + } + } + updateEvents(deviceId, session); + } + } catch (Exception e) { + e.printStackTrace(); + GB.toast("Datebase Error while syncing Calendar", Toast.LENGTH_SHORT, GB.ERROR); + } + + } + + private void updateEvents(Long deviceId, DaoSession session) { Enumeration ids = eventState.keys(); while (ids.hasMoreElements()) { Long i = ids.nextElement(); EventSyncState es = eventState.get(i); - if ((es.getState() == EventState.NOT_SYNCED) || (es.getState() == EventState.NEEDS_UPDATE)) { + int syncState = es.getState(); + if (syncState == EventState.NOT_SYNCED || syncState == EventState.NEEDS_UPDATE) { CalendarEventSpec calendarEventSpec = new CalendarEventSpec(); calendarEventSpec.title = es.getEvent().getTitle(); calendarEventSpec.id = i; calendarEventSpec.timestamp = es.getEvent().getBeginSeconds(); calendarEventSpec.description = es.getEvent().getDescription(); calendarEventSpec.type = CalendarEventSpec.TYPE_UNKNOWN; - GBApplication.deviceService().onDeleteCalendarEvent(CalendarEventSpec.TYPE_UNKNOWN, i); + if (syncState == EventState.NEEDS_UPDATE) { + GBApplication.deviceService().onDeleteCalendarEvent(CalendarEventSpec.TYPE_UNKNOWN, i); + } GBApplication.deviceService().onAddCalendarEvent(calendarEventSpec); es.setState(EventState.SYNCED); eventState.put(i, es); - } else if (es.getState() == EventState.NEEDS_DELETE) { + // update db + session.insertOrReplace(new CalendarSyncState(deviceId, i, EventState.SYNCED)); + } else if (syncState == EventState.NEEDS_DELETE) { GBApplication.deviceService().onDeleteCalendarEvent(CalendarEventSpec.TYPE_UNKNOWN, i); eventState.remove(i); + // delete from db for current device only + QueryBuilder qb = session.getCalendarSyncStateDao().queryBuilder(); + qb.where(qb.and(CalendarSyncStateDao.Properties.DeviceId.eq(deviceId), CalendarSyncStateDao.Properties.CalendarEntryId.eq(i))) + .buildDelete().executeDeleteWithoutDetachingEntities(); } } } diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/DeviceCommunicationService.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/DeviceCommunicationService.java index 9a3f7d63..48bdc780 100644 --- a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/DeviceCommunicationService.java +++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/DeviceCommunicationService.java @@ -114,6 +114,7 @@ import static nodomain.freeyourgadget.gadgetbridge.model.DeviceService.EXTRA_CAL import static nodomain.freeyourgadget.gadgetbridge.model.DeviceService.EXTRA_CANNEDMESSAGES; import static nodomain.freeyourgadget.gadgetbridge.model.DeviceService.EXTRA_CANNEDMESSAGES_TYPE; import static nodomain.freeyourgadget.gadgetbridge.model.DeviceService.EXTRA_CONFIG; +import static nodomain.freeyourgadget.gadgetbridge.model.DeviceService.EXTRA_CONNECT_FIRST_TIME; import static nodomain.freeyourgadget.gadgetbridge.model.DeviceService.EXTRA_FIND_START; import static nodomain.freeyourgadget.gadgetbridge.model.DeviceService.EXTRA_MUSIC_ALBUM; import static nodomain.freeyourgadget.gadgetbridge.model.DeviceService.EXTRA_MUSIC_ARTIST; @@ -135,7 +136,6 @@ import static nodomain.freeyourgadget.gadgetbridge.model.DeviceService.EXTRA_NOT import static nodomain.freeyourgadget.gadgetbridge.model.DeviceService.EXTRA_NOTIFICATION_SUBJECT; import static nodomain.freeyourgadget.gadgetbridge.model.DeviceService.EXTRA_NOTIFICATION_TITLE; import static nodomain.freeyourgadget.gadgetbridge.model.DeviceService.EXTRA_NOTIFICATION_TYPE; -import static nodomain.freeyourgadget.gadgetbridge.model.DeviceService.EXTRA_CONNECT_FIRST_TIME; import static nodomain.freeyourgadget.gadgetbridge.model.DeviceService.EXTRA_URI; import static nodomain.freeyourgadget.gadgetbridge.model.DeviceService.EXTRA_VIBRATION_INTENSITY; import static nodomain.freeyourgadget.gadgetbridge.model.DeviceService.EXTRA_WEATHER_CURRENTCONDITION; @@ -616,7 +616,7 @@ public class DeviceCommunicationService extends Service implements SharedPrefere registerReceiver(mAlarmReceiver, new IntentFilter("DAILY_ALARM")); } if (mCalendarReceiver == null) { - mCalendarReceiver = new CalendarReceiver(); + mCalendarReceiver = new CalendarReceiver(mGBDevice); registerReceiver(mCalendarReceiver, new IntentFilter("CALENDAR_SYNC")); } if (mAlarmClockReceiver == null) {