From cee9576155b9638cffda87de4ec7b4eaa5b475f2 Mon Sep 17 00:00:00 2001 From: Tom Hacohen Date: Thu, 9 Mar 2017 14:45:34 +0000 Subject: [PATCH] JournalModel: persist collection info inside the model instead of sqlite This change moves the collection info away from raw sqlite in favour of the requery ORM. --- .../syncadapter/model/CollectionInfoTest.java | 54 ----------- app/src/main/AndroidManifest.xml | 9 ++ .../java/com/etesync/syncadapter/App.java | 64 +++++++++++++ .../syncadapter/model/CollectionInfo.java | 20 ----- .../syncadapter/model/JournalModel.java | 89 ++++++++++++++++++- .../etesync/syncadapter/model/ServiceDB.java | 15 ---- .../CalendarsSyncAdapterService.java | 43 ++++----- .../ContactsSyncAdapterService.java | 30 ++----- .../syncadapter/SyncAdapterService.java | 51 ++++++----- .../syncadapter/ui/AccountActivity.java | 26 ++---- .../ui/CreateCollectionFragment.java | 13 ++- .../syncadapter/ui/DebugInfoActivity.java | 13 +++ .../ui/DeleteCollectionFragment.java | 16 ++-- .../ui/setup/SetupEncryptionFragment.java | 10 ++- 14 files changed, 250 insertions(+), 203 deletions(-) delete mode 100644 app/src/androidTest/java/com/etesync/syncadapter/model/CollectionInfoTest.java diff --git a/app/src/androidTest/java/com/etesync/syncadapter/model/CollectionInfoTest.java b/app/src/androidTest/java/com/etesync/syncadapter/model/CollectionInfoTest.java deleted file mode 100644 index 0edcaaa9..00000000 --- a/app/src/androidTest/java/com/etesync/syncadapter/model/CollectionInfoTest.java +++ /dev/null @@ -1,54 +0,0 @@ -/* - * Copyright © 2013 – 2016 Ricki 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 com.etesync.syncadapter.model; - -import android.content.ContentValues; - -import org.junit.Test; - -import com.etesync.syncadapter.model.ServiceDB.Collections; -import okhttp3.mockwebserver.MockWebServer; - -import static org.junit.Assert.assertEquals; -import static org.junit.Assert.assertTrue; - -public class CollectionInfoTest { - - MockWebServer server = new MockWebServer(); - - @Test - public void testFromDB() { - ContentValues values = new ContentValues(); - values.put(Collections.ID, 1); - values.put(Collections.SERVICE_ID, 1); - values.put(Collections.URL, "http://example.com"); - values.put(Collections.READ_ONLY, 1); - values.put(Collections.DISPLAY_NAME, "display name"); - values.put(Collections.DESCRIPTION, "description"); - values.put(Collections.COLOR, 0xFFFF0000); - values.put(Collections.TIME_ZONE, "tzdata"); - values.put(Collections.SUPPORTS_VEVENT, 1); - values.put(Collections.SUPPORTS_VTODO, 1); - values.put(Collections.SYNC, 1); - - CollectionInfo info = CollectionInfo.fromDB(values); - assertEquals(1, info.id); - assertEquals(1, (long)info.serviceID); - assertEquals("http://example.com", info.url); - assertTrue(info.readOnly); - assertEquals("display name", info.displayName); - assertEquals("description", info.description); - assertEquals(0xFFFF0000, (int)info.color); - assertEquals("tzdata", info.timeZone); - assertTrue(info.supportsVEVENT); - assertTrue(info.supportsVTODO); - assertTrue(info.selected); - } - -} diff --git a/app/src/main/AndroidManifest.xml b/app/src/main/AndroidManifest.xml index 33ecec35..c47e5f92 100644 --- a/app/src/main/AndroidManifest.xml +++ b/app/src/main/AndroidManifest.xml @@ -60,6 +60,15 @@ + + + + + + + diff --git a/app/src/main/java/com/etesync/syncadapter/App.java b/app/src/main/java/com/etesync/syncadapter/App.java index aeaae5f1..1efee55d 100644 --- a/app/src/main/java/com/etesync/syncadapter/App.java +++ b/app/src/main/java/com/etesync/syncadapter/App.java @@ -8,11 +8,19 @@ package com.etesync.syncadapter; +import android.accounts.Account; +import android.accounts.AccountManager; +import android.annotation.SuppressLint; import android.annotation.TargetApi; import android.app.Application; import android.content.BroadcastReceiver; +import android.content.ContentValues; import android.content.Context; import android.content.Intent; +import android.content.SharedPreferences; +import android.database.Cursor; +import android.database.DatabaseUtils; +import android.database.sqlite.SQLiteDatabase; import android.graphics.Bitmap; import android.graphics.drawable.BitmapDrawable; import android.graphics.drawable.Drawable; @@ -27,6 +35,8 @@ import android.util.Log; import com.etesync.syncadapter.log.LogcatHandler; import com.etesync.syncadapter.log.PlainTextFormatter; +import com.etesync.syncadapter.model.CollectionInfo; +import com.etesync.syncadapter.model.JournalEntity; import com.etesync.syncadapter.model.Models; import com.etesync.syncadapter.model.ServiceDB; import com.etesync.syncadapter.model.Settings; @@ -35,6 +45,8 @@ import org.apache.commons.lang3.time.DateFormatUtils; import java.io.File; import java.io.IOException; +import java.util.LinkedList; +import java.util.List; import java.util.logging.FileHandler; import java.util.logging.Handler; import java.util.logging.Level; @@ -207,4 +219,56 @@ public class App extends Application { } return dataStore; } + + // update from previous account settings + + private final static String PREF_VERSION = "version"; + + private void update(int fromVersion) { + App.log.info("Updating from version " + fromVersion + " to " + BuildConfig.VERSION_CODE); + + if (fromVersion < 6) { + EntityDataStore data = this.getData(); + + ServiceDB.OpenHelper dbHelper = new ServiceDB.OpenHelper(this); + + List collections = readCollections(dbHelper); + for (CollectionInfo info : collections) { + JournalEntity journalEntity = new JournalEntity(info); + data.insert(journalEntity); + } + + @Cleanup SQLiteDatabase db = dbHelper.getWritableDatabase(); + db.delete(ServiceDB.Collections._TABLE, null, null); + } + } + + public static class AppUpdatedReceiver extends BroadcastReceiver { + + @Override + @SuppressLint("UnsafeProtectedBroadcastReceiver,MissingPermission") + public void onReceive(Context context, Intent intent) { + App.log.info("EteSync was updated, checking for app version"); + + App app = (App) context.getApplicationContext(); + SharedPreferences prefs = app.getSharedPreferences("app", Context.MODE_PRIVATE); + int fromVersion = prefs.getInt(PREF_VERSION, 1); + app.update(fromVersion); + prefs.edit().putInt(PREF_VERSION, BuildConfig.VERSION_CODE).apply(); + } + + } + + @NonNull + private List readCollections(ServiceDB.OpenHelper dbHelper) { + @Cleanup SQLiteDatabase db = dbHelper.getWritableDatabase(); + List collections = new LinkedList<>(); + @Cleanup Cursor cursor = db.query(ServiceDB.Collections._TABLE, null, null, null, null, null, null); + while (cursor.moveToNext()) { + ContentValues values = new ContentValues(); + DatabaseUtils.cursorRowToContentValues(cursor, values); + collections.add(CollectionInfo.fromDB(values)); + } + return collections; + } } diff --git a/app/src/main/java/com/etesync/syncadapter/model/CollectionInfo.java b/app/src/main/java/com/etesync/syncadapter/model/CollectionInfo.java index 2d7aa500..13326a98 100644 --- a/app/src/main/java/com/etesync/syncadapter/model/CollectionInfo.java +++ b/app/src/main/java/com/etesync/syncadapter/model/CollectionInfo.java @@ -93,26 +93,6 @@ public class CollectionInfo implements Serializable { return info; } - public ContentValues toDB() { - ContentValues values = new ContentValues(); - // Collections.SERVICE_ID is never changed - - values.put(Collections.URL, url); - values.put(Collections.READ_ONLY, readOnly ? 1 : 0); - values.put(Collections.DISPLAY_NAME, displayName); - values.put(Collections.DESCRIPTION, description); - values.put(Collections.COLOR, color); - - values.put(Collections.TIME_ZONE, timeZone); - if (supportsVEVENT != null) - values.put(Collections.SUPPORTS_VEVENT, supportsVEVENT ? 1 : 0); - if (supportsVTODO != null) - values.put(Collections.SUPPORTS_VTODO, supportsVTODO ? 1 : 0); - - values.put(Collections.SYNC, selected ? 1 : 0); - return values; - } - public static CollectionInfo fromJson(String json) { return new GsonBuilder().excludeFieldsWithoutExposeAnnotation().create().fromJson(json, CollectionInfo.class); } diff --git a/app/src/main/java/com/etesync/syncadapter/model/JournalModel.java b/app/src/main/java/com/etesync/syncadapter/model/JournalModel.java index 78d0cd89..9ce224cf 100644 --- a/app/src/main/java/com/etesync/syncadapter/model/JournalModel.java +++ b/app/src/main/java/com/etesync/syncadapter/model/JournalModel.java @@ -1,16 +1,21 @@ package com.etesync.syncadapter.model; -import io.requery.CascadeAction; +import java.util.LinkedList; +import java.util.List; + import io.requery.Column; +import io.requery.Convert; +import io.requery.Converter; import io.requery.Entity; import io.requery.ForeignKey; import io.requery.Generated; import io.requery.Index; import io.requery.Key; import io.requery.ManyToOne; -import io.requery.OneToMany; -import io.requery.OneToOne; +import io.requery.Persistable; +import io.requery.PostLoad; import io.requery.ReferentialAction; +import io.requery.sql.EntityDataStore; public class JournalModel { @Entity @@ -21,6 +26,57 @@ public class JournalModel { @Column(length = 64, unique = true, nullable = false) String uid; + + @Convert(CollectionInfoConverter.class) + CollectionInfo info; + + long service; + + boolean deleted; + + @PostLoad + void afterLoad() { + this.info.serviceID = service; + this.info.url = uid; + } + + public Journal() { + this.deleted = false; + } + + public Journal(CollectionInfo info) { + this(); + this.info = info; + this.uid = info.url; + this.service = info.serviceID; + } + + public static List getCollections(EntityDataStore data, long service) { + List ret = new LinkedList<>(); + + List journals = data.select(JournalEntity.class).where(JournalEntity.SERVICE.eq(service).and(JournalEntity.DELETED.eq(false))).get().toList(); + for (JournalEntity journal : journals) { + // FIXME: For some reason this isn't always being called, manually do it here. + journal.afterLoad(); + ret.add(journal.getInfo()); + } + + return ret; + } + + public static JournalEntity fetch(EntityDataStore data, String url) { + return data.select(JournalEntity.class).where(JournalEntity.UID.eq(url)).limit(1).get().firstOrNull(); + } + + public static JournalEntity fetchOrCreate(EntityDataStore data, CollectionInfo collection) { + JournalEntity journalEntity = fetch(data, collection.url); + if (journalEntity == null) { + journalEntity = new JournalEntity(collection); + } else { + journalEntity.setInfo(collection); + } + return journalEntity; + } } @Entity @@ -39,4 +95,31 @@ public class JournalModel { @ManyToOne Journal journal; } + + public static class CollectionInfoConverter implements Converter { + @Override + public Class getMappedType() { + return CollectionInfo.class; + } + + @Override + public Class getPersistedType() { + return String.class; + } + + @Override + public Integer getPersistedSize() { + return null; + } + + @Override + public String convertToPersisted(CollectionInfo value) { + return value == null ? null : value.toJson(); + } + + @Override + public CollectionInfo convertToMapped(Class type, String value) { + return value == null ? null : CollectionInfo.fromJson(value); + } + } } diff --git a/app/src/main/java/com/etesync/syncadapter/model/ServiceDB.java b/app/src/main/java/com/etesync/syncadapter/model/ServiceDB.java index 56923a25..b9dceea8 100644 --- a/app/src/main/java/com/etesync/syncadapter/model/ServiceDB.java +++ b/app/src/main/java/com/etesync/syncadapter/model/ServiceDB.java @@ -100,21 +100,6 @@ public class ServiceDB { Services.SERVICE + " TEXT NOT NULL" + ")"); db.execSQL("CREATE UNIQUE INDEX services_account ON " + Services._TABLE + " (" + Services.ACCOUNT_NAME + "," + Services.SERVICE + ")"); - - db.execSQL("CREATE TABLE " + Collections._TABLE + "(" + - Collections.ID + " INTEGER PRIMARY KEY AUTOINCREMENT," + - Collections.SERVICE_ID + " INTEGER NOT NULL REFERENCES " + Services._TABLE +" ON DELETE CASCADE," + - Collections.URL + " TEXT NOT NULL," + - Collections.READ_ONLY + " INTEGER DEFAULT 0 NOT NULL," + - Collections.DISPLAY_NAME + " TEXT NULL," + - Collections.DESCRIPTION + " TEXT NULL," + - Collections.COLOR + " INTEGER NULL," + - Collections.TIME_ZONE + " TEXT NULL," + - Collections.SUPPORTS_VEVENT + " INTEGER NULL," + - Collections.SUPPORTS_VTODO + " INTEGER NULL," + - Collections.SYNC + " INTEGER DEFAULT 0 NOT NULL" + - ")"); - db.execSQL("CREATE UNIQUE INDEX collections_service_url ON " + Collections._TABLE + "(" + Collections.SERVICE_ID + "," + Collections.URL + ")"); } @Override diff --git a/app/src/main/java/com/etesync/syncadapter/syncadapter/CalendarsSyncAdapterService.java b/app/src/main/java/com/etesync/syncadapter/syncadapter/CalendarsSyncAdapterService.java index 46a869b3..28d23c91 100644 --- a/app/src/main/java/com/etesync/syncadapter/syncadapter/CalendarsSyncAdapterService.java +++ b/app/src/main/java/com/etesync/syncadapter/syncadapter/CalendarsSyncAdapterService.java @@ -11,21 +11,13 @@ import android.accounts.Account; import android.content.AbstractThreadedSyncAdapter; import android.content.ContentProviderClient; import android.content.ContentResolver; -import android.content.ContentValues; import android.content.Context; import android.content.Intent; import android.content.SyncResult; -import android.database.Cursor; -import android.database.DatabaseUtils; import android.database.sqlite.SQLiteDatabase; import android.database.sqlite.SQLiteException; import android.os.Bundle; import android.provider.CalendarContract; -import android.support.annotation.NonNull; - -import java.util.LinkedHashMap; -import java.util.Map; -import java.util.logging.Level; import com.etesync.syncadapter.AccountSettings; import com.etesync.syncadapter.App; @@ -34,13 +26,20 @@ import com.etesync.syncadapter.NotificationHelper; import com.etesync.syncadapter.R; import com.etesync.syncadapter.journalmanager.Exceptions; import com.etesync.syncadapter.model.CollectionInfo; +import com.etesync.syncadapter.model.JournalEntity; import com.etesync.syncadapter.model.ServiceDB; -import com.etesync.syncadapter.model.ServiceDB.Collections; import com.etesync.syncadapter.model.ServiceDB.Services; import com.etesync.syncadapter.resource.LocalCalendar; import com.etesync.syncadapter.ui.DebugInfoActivity; + +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import java.util.logging.Level; + import at.bitfire.ical4android.CalendarStorageException; -import lombok.Cleanup; +import io.requery.Persistable; +import io.requery.sql.EntityDataStore; import okhttp3.HttpUrl; import static com.etesync.syncadapter.Constants.KEY_ACCOUNT; @@ -117,7 +116,12 @@ public class CalendarsSyncAdapterService extends SyncAdapterService { ret = HttpUrl.get(settings.getUri()); - Map remote = remoteCalendars(db, service); + EntityDataStore data = ((App) getContext().getApplicationContext()).getData(); + Map remote = new HashMap<>(); + List remoteCollections = JournalEntity.getCollections(data, service); + for (CollectionInfo info : remoteCollections) { + remote.put(info.url, info); + } LocalCalendar[] local = (LocalCalendar[])LocalCalendar.find(account, provider, LocalCalendar.Factory.INSTANCE, null, null); @@ -151,23 +155,6 @@ public class CalendarsSyncAdapterService extends SyncAdapterService { return ret; } - - @NonNull - private Map remoteCalendars(@NonNull SQLiteDatabase db, Long service) { - Map collections = new LinkedHashMap<>(); - if (service != null) { - @Cleanup Cursor cursor = db.query(Collections._TABLE, null, - Collections.SERVICE_ID + "=? AND " + Collections.SUPPORTS_VEVENT + "!=0 AND " + Collections.SYNC, - new String[]{String.valueOf(service)}, null, null, null); - while (cursor.moveToNext()) { - ContentValues values = new ContentValues(); - DatabaseUtils.cursorRowToContentValues(cursor, values); - CollectionInfo info = CollectionInfo.fromDB(values); - collections.put(info.url, info); - } - } - return collections; - } } } diff --git a/app/src/main/java/com/etesync/syncadapter/syncadapter/ContactsSyncAdapterService.java b/app/src/main/java/com/etesync/syncadapter/syncadapter/ContactsSyncAdapterService.java index c0ab4be1..fc2f55cf 100644 --- a/app/src/main/java/com/etesync/syncadapter/syncadapter/ContactsSyncAdapterService.java +++ b/app/src/main/java/com/etesync/syncadapter/syncadapter/ContactsSyncAdapterService.java @@ -11,18 +11,11 @@ import android.accounts.Account; import android.content.AbstractThreadedSyncAdapter; import android.content.ContentProviderClient; import android.content.ContentResolver; -import android.content.ContentValues; import android.content.Context; import android.content.Intent; import android.content.SyncResult; -import android.database.Cursor; -import android.database.DatabaseUtils; import android.database.sqlite.SQLiteDatabase; import android.os.Bundle; -import android.support.annotation.NonNull; -import android.support.annotation.Nullable; - -import java.util.logging.Level; import com.etesync.syncadapter.AccountSettings; import com.etesync.syncadapter.App; @@ -32,10 +25,14 @@ import com.etesync.syncadapter.NotificationHelper; import com.etesync.syncadapter.R; import com.etesync.syncadapter.journalmanager.Exceptions; import com.etesync.syncadapter.model.CollectionInfo; +import com.etesync.syncadapter.model.JournalEntity; import com.etesync.syncadapter.model.ServiceDB; -import com.etesync.syncadapter.model.ServiceDB.Collections; import com.etesync.syncadapter.ui.DebugInfoActivity; -import lombok.Cleanup; + +import java.util.logging.Level; + +import io.requery.Persistable; +import io.requery.sql.EntityDataStore; import okhttp3.HttpUrl; import static com.etesync.syncadapter.Constants.KEY_ACCOUNT; @@ -72,7 +69,8 @@ public class ContactsSyncAdapterService extends SyncAdapterService { Long service = dbHelper.getService(db, account, ServiceDB.Services.SERVICE_CARDDAV); if (service != null) { HttpUrl principal = HttpUrl.get(settings.getUri()); - CollectionInfo info = remoteAddressBook(db, service); + EntityDataStore data = ((App) getContext().getApplicationContext()).getData(); + CollectionInfo info = JournalEntity.getCollections(data, service).get(0); try { ContactsSyncManager syncManager = new ContactsSyncManager(getContext(), account, settings, extras, authority, provider, syncResult, principal, info); syncManager.performSync(); @@ -103,18 +101,6 @@ public class ContactsSyncAdapterService extends SyncAdapterService { App.log.info("Address book sync complete"); } - - @Nullable - private CollectionInfo remoteAddressBook(@NonNull SQLiteDatabase db, long service) { - @Cleanup Cursor c = db.query(Collections._TABLE, null, - Collections.SERVICE_ID + "=? AND " + Collections.SYNC, new String[]{String.valueOf(service)}, null, null, null); - if (c.moveToNext()) { - ContentValues values = new ContentValues(); - DatabaseUtils.cursorRowToContentValues(c, values); - return CollectionInfo.fromDB(values); - } else - return null; - } } } diff --git a/app/src/main/java/com/etesync/syncadapter/syncadapter/SyncAdapterService.java b/app/src/main/java/com/etesync/syncadapter/syncadapter/SyncAdapterService.java index 8962a635..694b5481 100644 --- a/app/src/main/java/com/etesync/syncadapter/syncadapter/SyncAdapterService.java +++ b/app/src/main/java/com/etesync/syncadapter/syncadapter/SyncAdapterService.java @@ -32,6 +32,7 @@ import android.support.annotation.Nullable; import android.support.v4.app.NotificationCompat; import android.support.v4.app.NotificationManagerCompat; +import java.util.HashMap; import java.util.LinkedHashMap; import java.util.LinkedList; import java.util.List; @@ -48,6 +49,7 @@ import com.etesync.syncadapter.journalmanager.Exceptions; import com.etesync.syncadapter.journalmanager.JournalManager; import com.etesync.syncadapter.model.CollectionInfo; import com.etesync.syncadapter.model.JournalEntity; +import com.etesync.syncadapter.model.JournalModel; import com.etesync.syncadapter.model.ServiceDB; import com.etesync.syncadapter.ui.PermissionsActivity; @@ -185,35 +187,32 @@ public abstract class SyncAdapterService extends Service { } } - @NonNull - private Map readCollections(SQLiteDatabase db) { - Long service = dbHelper.getService(db, account, serviceType.toString()); - Map collections = new LinkedHashMap<>(); - @Cleanup Cursor cursor = db.query(ServiceDB.Collections._TABLE, null, ServiceDB.Collections.SERVICE_ID + "=?", new String[]{String.valueOf(service)}, null, null, null); - while (cursor.moveToNext()) { - ContentValues values = new ContentValues(); - DatabaseUtils.cursorRowToContentValues(cursor, values); - collections.put(values.getAsString(ServiceDB.Collections.URL), CollectionInfo.fromDB(values)); - } - return collections; - } - private void saveCollections(SQLiteDatabase db, Iterable collections) { - EntityDataStore data = ((App) context.getApplicationContext()).getData(); Long service = dbHelper.getService(db, account, serviceType.toString()); - db.delete(ServiceDB.Collections._TABLE, ServiceDB.Collections.SERVICE_ID + "=?", new String[]{String.valueOf(service)}); - for (CollectionInfo collection : collections) { - ContentValues values = collection.toDB(); - App.log.log(Level.FINE, "Saving collection", values); - values.put(ServiceDB.Collections.SERVICE_ID, service); - db.insertWithOnConflict(ServiceDB.Collections._TABLE, null, values, SQLiteDatabase.CONFLICT_REPLACE); - JournalEntity journalEntity = data.select(JournalEntity.class).where(JournalEntity.UID.eq(collection.url)).limit(1).get().firstOrNull(); - if (journalEntity == null) { - journalEntity = new JournalEntity(); - journalEntity.setUid(collection.url); - data.insert(journalEntity); - } + EntityDataStore data = ((App) context.getApplicationContext()).getData(); + Map existing = new HashMap<>(); + List existingList = JournalEntity.getCollections(data, service); + for (CollectionInfo info : existingList) { + existing.put(info.url, info); + } + + for (CollectionInfo collection : collections) { + App.log.log(Level.FINE, "Saving collection", collection.url); + + collection.serviceID = service; + JournalEntity journalEntity = JournalEntity.fetchOrCreate(data, collection); + data.upsert(journalEntity); + + existing.remove(collection.url); + } + + for (CollectionInfo collection : existing.values()) { + App.log.log(Level.FINE, "Deleting collection", collection.url); + + JournalEntity journalEntity = data.select(JournalEntity.class).where(JournalEntity.UID.eq(collection.url)).limit(1).get().first(); + journalEntity.setDeleted(true); + data.update(journalEntity); } } } diff --git a/app/src/main/java/com/etesync/syncadapter/ui/AccountActivity.java b/app/src/main/java/com/etesync/syncadapter/ui/AccountActivity.java index f06459f4..f87930e0 100644 --- a/app/src/main/java/com/etesync/syncadapter/ui/AccountActivity.java +++ b/app/src/main/java/com/etesync/syncadapter/ui/AccountActivity.java @@ -18,7 +18,6 @@ import android.app.LoaderManager; import android.content.AsyncTaskLoader; import android.content.ComponentName; import android.content.ContentResolver; -import android.content.ContentValues; import android.content.Context; import android.content.DialogInterface; import android.content.Intent; @@ -26,7 +25,6 @@ import android.content.Loader; import android.content.ServiceConnection; import android.content.SyncStatusObserver; import android.database.Cursor; -import android.database.DatabaseUtils; import android.database.sqlite.SQLiteDatabase; import android.graphics.drawable.Drawable; import android.os.Build; @@ -34,7 +32,6 @@ import android.os.Bundle; import android.os.IBinder; import android.provider.CalendarContract; import android.provider.ContactsContract; -import android.support.annotation.NonNull; import android.support.design.widget.Snackbar; import android.support.v7.app.AlertDialog; import android.support.v7.app.AppCompatActivity; @@ -64,11 +61,13 @@ import com.etesync.syncadapter.App; import com.etesync.syncadapter.Constants; import com.etesync.syncadapter.R; import com.etesync.syncadapter.model.CollectionInfo; -import com.etesync.syncadapter.model.ServiceDB.Collections; +import com.etesync.syncadapter.model.JournalEntity; import com.etesync.syncadapter.model.ServiceDB.OpenHelper; import com.etesync.syncadapter.model.ServiceDB.Services; import com.etesync.syncadapter.resource.LocalCalendar; import at.bitfire.ical4android.TaskProvider; +import io.requery.Persistable; +import io.requery.sql.EntityDataStore; import lombok.Cleanup; import static android.content.ContentResolver.SYNC_OBSERVER_TYPE_ACTIVE; @@ -331,6 +330,7 @@ public class AccountActivity extends AppCompatActivity implements Toolbar.OnMenu @Cleanup OpenHelper dbHelper = new OpenHelper(getContext()); SQLiteDatabase db = dbHelper.getReadableDatabase(); + EntityDataStore data = ((App) getContext().getApplicationContext()).getData(); @Cleanup Cursor cursor = db.query( Services._TABLE, @@ -345,32 +345,18 @@ public class AccountActivity extends AppCompatActivity implements Toolbar.OnMenu info.carddav = new AccountInfo.ServiceInfo(); info.carddav.id = id; info.carddav.refreshing = (davService != null && davService.isRefreshing(id)) || ContentResolver.isSyncActive(account, ContactsContract.AUTHORITY); - info.carddav.collections = readCollections(db, id); - + info.carddav.collections = JournalEntity.getCollections(data, id); } else if (Services.SERVICE_CALDAV.equals(service)) { info.caldav = new AccountInfo.ServiceInfo(); info.caldav.id = id; info.caldav.refreshing = (davService != null && davService.isRefreshing(id)) || ContentResolver.isSyncActive(account, CalendarContract.AUTHORITY) || ContentResolver.isSyncActive(account, TaskProvider.ProviderName.OpenTasks.authority); - info.caldav.collections = readCollections(db, id); + info.caldav.collections = JournalEntity.getCollections(data, id); } } return info; } - - private List readCollections(@NonNull SQLiteDatabase db, long service) { - List collections = new LinkedList<>(); - @Cleanup Cursor cursor = db.query(Collections._TABLE, null, Collections.SERVICE_ID + "=?", - new String[] { String.valueOf(service) }, null, null, Collections.SUPPORTS_VEVENT + " DESC," + Collections.DISPLAY_NAME); - while (cursor.moveToNext()) { - ContentValues values = new ContentValues(); - DatabaseUtils.cursorRowToContentValues(cursor, values); - collections.add(CollectionInfo.fromDB(values)); - } - return collections; - } - } diff --git a/app/src/main/java/com/etesync/syncadapter/ui/CreateCollectionFragment.java b/app/src/main/java/com/etesync/syncadapter/ui/CreateCollectionFragment.java index 3927faf1..20686255 100644 --- a/app/src/main/java/com/etesync/syncadapter/ui/CreateCollectionFragment.java +++ b/app/src/main/java/com/etesync/syncadapter/ui/CreateCollectionFragment.java @@ -27,13 +27,18 @@ import android.support.v4.content.AsyncTaskLoader; import android.support.v4.content.Loader; import com.etesync.syncadapter.AccountSettings; +import com.etesync.syncadapter.App; import com.etesync.syncadapter.HttpClient; import com.etesync.syncadapter.InvalidAccountException; import com.etesync.syncadapter.R; import com.etesync.syncadapter.journalmanager.Exceptions; import com.etesync.syncadapter.journalmanager.JournalManager; import com.etesync.syncadapter.model.CollectionInfo; +import com.etesync.syncadapter.model.JournalEntity; import com.etesync.syncadapter.model.ServiceDB; + +import io.requery.Persistable; +import io.requery.sql.EntityDataStore; import lombok.Cleanup; import okhttp3.HttpUrl; @@ -161,9 +166,11 @@ public class CreateCollectionFragment extends DialogFragment implements LoaderMa } // 2. add collection to service - ContentValues values = info.toDB(); - values.put(ServiceDB.Collections.SERVICE_ID, serviceID); - db.insertWithOnConflict(ServiceDB.Collections._TABLE, null, values, SQLiteDatabase.CONFLICT_REPLACE); + EntityDataStore data = ((App) getContext().getApplicationContext()).getData(); + info.serviceID = serviceID; + JournalEntity journalEntity = JournalEntity.fetchOrCreate(data, info); + data.upsert(journalEntity); + requestSync(authority); } catch (IllegalStateException | Exceptions.HttpException e) { diff --git a/app/src/main/java/com/etesync/syncadapter/ui/DebugInfoActivity.java b/app/src/main/java/com/etesync/syncadapter/ui/DebugInfoActivity.java index 347eab1c..306db17a 100644 --- a/app/src/main/java/com/etesync/syncadapter/ui/DebugInfoActivity.java +++ b/app/src/main/java/com/etesync/syncadapter/ui/DebugInfoActivity.java @@ -36,6 +36,7 @@ import java.io.File; import java.io.FileWriter; import java.io.IOException; import java.util.Date; +import java.util.List; import java.util.logging.Level; import com.etesync.syncadapter.AccountSettings; @@ -45,7 +46,11 @@ import com.etesync.syncadapter.Constants; import com.etesync.syncadapter.InvalidAccountException; import com.etesync.syncadapter.R; import com.etesync.syncadapter.journalmanager.Exceptions.HttpException; +import com.etesync.syncadapter.model.JournalEntity; import com.etesync.syncadapter.model.ServiceDB; + +import io.requery.Persistable; +import io.requery.sql.EntityDataStore; import lombok.Cleanup; import static com.etesync.syncadapter.Constants.KEY_ACCOUNT; @@ -240,6 +245,14 @@ public class DebugInfoActivity extends AppCompatActivity implements LoaderManage dbHelper.dump(report); report.append("\n"); + report.append("JOURNALS DUMP\n"); + EntityDataStore data = ((App) getContext().getApplicationContext()).getData(); + List journals = data.select(JournalEntity.class).where(JournalEntity.DELETED.eq(false)).get().toList(); + for (JournalEntity journal : journals) { + report.append(journal.toString() + "\n"); + } + report.append("\n"); + try { report.append( "SYSTEM INFORMATION\n" + diff --git a/app/src/main/java/com/etesync/syncadapter/ui/DeleteCollectionFragment.java b/app/src/main/java/com/etesync/syncadapter/ui/DeleteCollectionFragment.java index d574e008..b53c5cdd 100644 --- a/app/src/main/java/com/etesync/syncadapter/ui/DeleteCollectionFragment.java +++ b/app/src/main/java/com/etesync/syncadapter/ui/DeleteCollectionFragment.java @@ -14,7 +14,6 @@ import android.app.Dialog; import android.app.ProgressDialog; import android.content.Context; import android.content.DialogInterface; -import android.database.sqlite.SQLiteDatabase; import android.os.Bundle; import android.support.annotation.NonNull; import android.support.v4.app.DialogFragment; @@ -25,13 +24,17 @@ import android.support.v7.app.AlertDialog; import android.text.TextUtils; import com.etesync.syncadapter.AccountSettings; +import com.etesync.syncadapter.App; import com.etesync.syncadapter.HttpClient; import com.etesync.syncadapter.InvalidAccountException; import com.etesync.syncadapter.R; import com.etesync.syncadapter.journalmanager.Exceptions; import com.etesync.syncadapter.journalmanager.JournalManager; import com.etesync.syncadapter.model.CollectionInfo; -import com.etesync.syncadapter.model.ServiceDB; +import com.etesync.syncadapter.model.JournalEntity; + +import io.requery.Persistable; +import io.requery.sql.EntityDataStore; import okhttp3.HttpUrl; public class DeleteCollectionFragment extends DialogFragment implements LoaderManager.LoaderCallbacks { @@ -94,13 +97,11 @@ public class DeleteCollectionFragment extends DialogFragment implements LoaderMa protected static class DeleteCollectionLoader extends AsyncTaskLoader { final Account account; final CollectionInfo collectionInfo; - final ServiceDB.OpenHelper dbHelper; public DeleteCollectionLoader(Context context, Account account, CollectionInfo collectionInfo) { super(context); this.account = account; this.collectionInfo = collectionInfo; - dbHelper = new ServiceDB.OpenHelper(getContext()); } @Override @@ -112,23 +113,20 @@ public class DeleteCollectionFragment extends DialogFragment implements LoaderMa public Exception loadInBackground() { try { // delete collection locally - SQLiteDatabase db = dbHelper.getWritableDatabase(); + EntityDataStore data = ((App) getContext().getApplicationContext()).getData(); AccountSettings settings = new AccountSettings(getContext(), account); HttpUrl principal = HttpUrl.get(settings.getUri()); JournalManager journalManager = new JournalManager(HttpClient.create(getContext(), account), principal); journalManager.deleteJournal(new JournalManager.Journal(settings.password(), collectionInfo.toJson(), collectionInfo.url)); - - db.delete(ServiceDB.Collections._TABLE, ServiceDB.Collections.ID + "=?", new String[]{String.valueOf(collectionInfo.id)}); + data.delete(JournalEntity.fetch(data, collectionInfo.url)); return null; } catch (Exceptions.HttpException e) { return e; } catch (InvalidAccountException e) { return e; - } finally { - dbHelper.close(); } } } diff --git a/app/src/main/java/com/etesync/syncadapter/ui/setup/SetupEncryptionFragment.java b/app/src/main/java/com/etesync/syncadapter/ui/setup/SetupEncryptionFragment.java index 784ef20e..21796b74 100644 --- a/app/src/main/java/com/etesync/syncadapter/ui/setup/SetupEncryptionFragment.java +++ b/app/src/main/java/com/etesync/syncadapter/ui/setup/SetupEncryptionFragment.java @@ -35,10 +35,13 @@ import com.etesync.syncadapter.InvalidAccountException; import com.etesync.syncadapter.R; import com.etesync.syncadapter.journalmanager.Helpers; import com.etesync.syncadapter.model.CollectionInfo; +import com.etesync.syncadapter.model.JournalEntity; import com.etesync.syncadapter.model.ServiceDB; import com.etesync.syncadapter.resource.LocalTaskList; import com.etesync.syncadapter.ui.setup.BaseConfigurationFinder.Configuration; import at.bitfire.ical4android.TaskProvider; +import io.requery.Persistable; +import io.requery.sql.EntityDataStore; import lombok.Cleanup; public class SetupEncryptionFragment extends DialogFragment implements LoaderManager.LoaderCallbacks { @@ -176,12 +179,13 @@ public class SetupEncryptionFragment extends DialogFragment implements LoaderMan values.put(ServiceDB.Services.ACCOUNT_NAME, accountName); values.put(ServiceDB.Services.SERVICE, service); long serviceID = db.insertWithOnConflict(ServiceDB.Services._TABLE, null, values, SQLiteDatabase.CONFLICT_REPLACE); + EntityDataStore data = ((App) getContext().getApplicationContext()).getData(); // insert collections for (CollectionInfo collection : info.collections.values()) { - values = collection.toDB(); - values.put(ServiceDB.Collections.SERVICE_ID, serviceID); - db.insertWithOnConflict(ServiceDB.Collections._TABLE, null, values, SQLiteDatabase.CONFLICT_REPLACE); + collection.serviceID = serviceID; + JournalEntity journalEntity = new JournalEntity(collection); + data.insert(journalEntity); } return serviceID;