Services: Move to a requery model instead of raw SQL and improve models.

Having it in raw sql was slowing down development, and was error-prone.
It's much cleaner now, easier to handle, and enables us to develop
faster.

In this change I also fixed the fetching of journals to be by service
and id, not just id, because just id is not guaranteed to be unique.
pull/14/head
Tom Hacohen 7 years ago
parent 8b79529a94
commit e2f206e02e

@ -13,17 +13,12 @@ import android.accounts.AccountManager;
import android.annotation.SuppressLint;
import android.app.Service;
import android.content.Intent;
import android.database.Cursor;
import android.database.DatabaseUtils;
import android.database.sqlite.SQLiteDatabase;
import android.os.Binder;
import android.os.IBinder;
import android.support.annotation.NonNull;
import android.text.TextUtils;
import com.etesync.syncadapter.model.JournalEntity;
import com.etesync.syncadapter.model.ServiceDB.OpenHelper;
import com.etesync.syncadapter.model.ServiceDB.Services;
import com.etesync.syncadapter.model.ServiceEntity;
import java.lang.ref.WeakReference;
import java.util.HashSet;
@ -105,31 +100,17 @@ public class AccountUpdateService extends Service {
void cleanupAccounts() {
App.log.info("Cleaning up orphaned accounts");
final OpenHelper dbHelper = new OpenHelper(this);
try {
SQLiteDatabase db = dbHelper.getWritableDatabase();
List<String> sqlAccountNames = new LinkedList<>();
AccountManager am = AccountManager.get(this);
for (Account account : am.getAccountsByType(Constants.ACCOUNT_TYPE))
sqlAccountNames.add(DatabaseUtils.sqlEscapeString(account.name));
EntityDataStore<Persistable> data = ((App) getApplication()).getData();
if (sqlAccountNames.isEmpty()) {
data.delete(JournalEntity.class).get().value();
db.delete(Services._TABLE, null, null);
} else {
Cursor cur = db.query(Services._TABLE, new String[]{Services.ID}, Services.ACCOUNT_NAME + " NOT IN (" + TextUtils.join(",", sqlAccountNames) + ")", null, null, null, null);
cur.moveToFirst();
while(!cur.isAfterLast()) {
data.delete(JournalEntity.class).where(JournalEntity.SERVICE.eq(cur.getLong(0))).get().value();
cur.moveToNext();
}
db.delete(Services._TABLE, Services.ACCOUNT_NAME + " NOT IN (" + TextUtils.join(",", sqlAccountNames) + ")", null);
}
} finally {
dbHelper.close();
List<String> sqlAccountNames = new LinkedList<>();
AccountManager am = AccountManager.get(this);
for (Account account : am.getAccountsByType(Constants.ACCOUNT_TYPE))
sqlAccountNames.add(account.name);
EntityDataStore<Persistable> data = ((App) getApplication()).getData();
if (sqlAccountNames.isEmpty()) {
data.delete(ServiceEntity.class).get().value();
} else {
data.delete(ServiceEntity.class).where(ServiceEntity.ACCOUNT.notIn(sqlAccountNames)).get().value();
}
}
}

@ -40,8 +40,10 @@ 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.JournalModel;
import com.etesync.syncadapter.model.Models;
import com.etesync.syncadapter.model.ServiceDB;
import com.etesync.syncadapter.model.ServiceEntity;
import com.etesync.syncadapter.model.Settings;
import com.etesync.syncadapter.resource.LocalAddressBook;
import com.etesync.syncadapter.resource.LocalCalendar;
@ -227,7 +229,7 @@ public class App extends Application {
public EntityDataStore<Persistable> getData() {
if (dataStore == null) {
// override onUpgrade to handle migrating to a new version
DatabaseSource source = new DatabaseSource(this, Models.DEFAULT, 2);
DatabaseSource source = new DatabaseSource(this, Models.DEFAULT, 3);
Configuration configuration = source.getConfiguration();
dataStore = new EntityDataStore<>(configuration);
}
@ -257,7 +259,7 @@ public class App extends Application {
List<CollectionInfo> collections = readCollections(dbHelper);
for (CollectionInfo info : collections) {
JournalEntity journalEntity = new JournalEntity(info);
JournalEntity journalEntity = new JournalEntity(data, info);
data.insert(journalEntity);
}
@ -291,6 +293,12 @@ public class App extends Application {
if (fromVersion < 10) {
HintManager.setHintSeen(this, AccountsActivity.HINT_ACCOUNT_ADD, true);
}
if (fromVersion < 11) {
ServiceDB.OpenHelper dbHelper = new ServiceDB.OpenHelper(this);
migrateServices(dbHelper);
}
}
public static class AppUpdatedReceiver extends BroadcastReceiver {
@ -321,4 +329,25 @@ public class App extends Application {
}
return collections;
}
public void migrateServices(ServiceDB.OpenHelper dbHelper) {
@Cleanup SQLiteDatabase db = dbHelper.getReadableDatabase();
EntityDataStore<Persistable> data = this.getData();
@Cleanup Cursor cursor = db.query(ServiceDB.Services._TABLE, null, null, null, null, null, null);
while (cursor.moveToNext()) {
ContentValues values = new ContentValues();
DatabaseUtils.cursorRowToContentValues(cursor, values);
ServiceEntity service = new ServiceEntity();
service.setAccount(values.getAsString(ServiceDB.Services.ACCOUNT_NAME));
service.setType(CollectionInfo.Type.valueOf(values.getAsString(ServiceDB.Services.SERVICE)));
data.insert(service);
for (JournalEntity journalEntity : data.select(JournalEntity.class).where(JournalEntity.SERVICE.eq(values.getAsLong(ServiceDB.Services.ID))).get()) {
journalEntity.setServiceModel(service);
data.update(journalEntity);
}
}
db.delete(ServiceDB.Services._TABLE, null, null);
}
}

@ -8,22 +8,13 @@
package com.etesync.syncadapter;
import android.accounts.Account;
import android.annotation.SuppressLint;
import android.content.BroadcastReceiver;
import android.content.ContentResolver;
import android.content.Context;
import android.content.Intent;
import android.database.Cursor;
import android.database.sqlite.SQLiteDatabase;
import android.os.Bundle;
import android.support.annotation.NonNull;
import com.etesync.syncadapter.model.ServiceDB;
import com.etesync.syncadapter.model.ServiceDB.Services;
import com.etesync.syncadapter.resource.LocalTaskList;
import at.bitfire.ical4android.TaskProvider;
import lombok.Cleanup;
public class PackageChangedReceiver extends BroadcastReceiver {
@ -40,24 +31,7 @@ public class PackageChangedReceiver extends BroadcastReceiver {
App.log.info("Package (un)installed; OpenTasks provider now available = " + tasksInstalled);
// check all accounts and (de)activate OpenTasks if a CalDAV service is defined
@Cleanup ServiceDB.OpenHelper dbHelper = new ServiceDB.OpenHelper(context);
SQLiteDatabase db = dbHelper.getReadableDatabase();
@Cleanup Cursor cursor = db.query(Services._TABLE, new String[] { Services.ACCOUNT_NAME },
Services.SERVICE + "=?", new String[] { Services.SERVICE_CALDAV }, null, null, null);
while (cursor.moveToNext()) {
Account account = new Account(cursor.getString(0), Constants.ACCOUNT_TYPE);
if (tasksInstalled) {
if (ContentResolver.getIsSyncable(account, TaskProvider.ProviderName.OpenTasks.authority) <= 0) {
ContentResolver.setIsSyncable(account, TaskProvider.ProviderName.OpenTasks.authority, 1);
ContentResolver.setSyncAutomatically(account, TaskProvider.ProviderName.OpenTasks.authority, true);
ContentResolver.addPeriodicSync(account, TaskProvider.ProviderName.OpenTasks.authority, new Bundle(), Constants.DEFAULT_SYNC_INTERVAL);
}
} else
ContentResolver.setIsSyncable(account, TaskProvider.ProviderName.OpenTasks.authority, 0);
}
// FIXME: Do something if we ever bring back tasks.
}
}

@ -18,6 +18,8 @@ import com.google.gson.annotations.Expose;
import java.io.Serializable;
import io.requery.Persistable;
import io.requery.sql.EntityDataStore;
import lombok.ToString;
@ToString(exclude = {"id"})
@ -25,7 +27,7 @@ public class CollectionInfo implements Serializable {
@Deprecated
public long id;
public Long serviceID;
public int serviceID;
public enum Type {
ADDRESS_BOOK,
@ -80,7 +82,7 @@ public class CollectionInfo implements Serializable {
public static CollectionInfo fromDB(ContentValues values) {
CollectionInfo info = new CollectionInfo();
info.id = values.getAsLong(Collections.ID);
info.serviceID = values.getAsLong(Collections.SERVICE_ID);
info.serviceID = values.getAsInteger(Collections.SERVICE_ID);
info.uid = values.getAsString(Collections.URL);
info.readOnly = values.getAsInteger(Collections.READ_ONLY) != 0;
@ -95,6 +97,10 @@ public class CollectionInfo implements Serializable {
return info;
}
public ServiceEntity getServiceEntity(EntityDataStore<Persistable> data) {
return data.findByKey(ServiceEntity.class, serviceID);
}
public static CollectionInfo fromJson(String json) {
return new GsonBuilder().excludeFieldsWithoutExposeAnnotation().create().fromJson(json, CollectionInfo.class);
}

@ -37,14 +37,19 @@ public class JournalModel {
byte[] encryptedKey;
@Index(value = "uid_unique")
@Deprecated
long service;
@ForeignKey(update = ReferentialAction.CASCADE)
@ManyToOne
Service serviceModel;
boolean deleted;
@PostLoad
void afterLoad() {
this.info.serviceID = service;
this.info.serviceID = this.serviceModel.id;
this.info.uid = uid;
}
@ -52,21 +57,21 @@ public class JournalModel {
this.deleted = false;
}
public Journal(CollectionInfo info) {
public Journal(EntityDataStore<Persistable> data, CollectionInfo info) {
this();
this.info = info;
this.uid = info.uid;
this.service = info.serviceID;
this.serviceModel = info.getServiceEntity(data);
}
public static List<JournalEntity> getJournals(EntityDataStore<Persistable> data, long service) {
return data.select(JournalEntity.class).where(JournalEntity.SERVICE.eq(service).and(JournalEntity.DELETED.eq(false))).get().toList();
public static List<JournalEntity> getJournals(EntityDataStore<Persistable> data, ServiceEntity serviceEntity) {
return data.select(JournalEntity.class).where(JournalEntity.SERVICE_MODEL.eq(serviceEntity).and(JournalEntity.DELETED.eq(false))).get().toList();
}
public static List<CollectionInfo> getCollections(EntityDataStore<Persistable> data, long service) {
public static List<CollectionInfo> getCollections(EntityDataStore<Persistable> data, ServiceEntity serviceEntity) {
List<CollectionInfo> ret = new LinkedList<>();
List<JournalEntity> journals = getJournals(data, service);
List<JournalEntity> journals = getJournals(data, serviceEntity);
for (JournalEntity journal : journals) {
// FIXME: For some reason this isn't always being called, manually do it here.
journal.afterLoad();
@ -76,8 +81,8 @@ public class JournalModel {
return ret;
}
public static JournalEntity fetch(EntityDataStore<Persistable> data, String url) {
JournalEntity ret = data.select(JournalEntity.class).where(JournalEntity.UID.eq(url)).limit(1).get().firstOrNull();
public static JournalEntity fetch(EntityDataStore<Persistable> data, ServiceEntity serviceEntity, String uid) {
JournalEntity ret = data.select(JournalEntity.class).where(JournalEntity.SERVICE_MODEL.eq(serviceEntity).and(JournalEntity.UID.eq(uid))).limit(1).get().firstOrNull();
if (ret != null) {
// FIXME: For some reason this isn't always being called, manually do it here.
ret.afterLoad();
@ -86,9 +91,9 @@ public class JournalModel {
}
public static JournalEntity fetchOrCreate(EntityDataStore<Persistable> data, CollectionInfo collection) {
JournalEntity journalEntity = fetch(data, collection.uid);
JournalEntity journalEntity = fetch(data, collection.getServiceEntity(data), collection.uid);
if (journalEntity == null) {
journalEntity = new JournalEntity(collection);
journalEntity = new JournalEntity(data, collection);
} else {
journalEntity.setInfo(collection);
}
@ -125,6 +130,26 @@ public class JournalModel {
Journal journal;
}
@Entity
@Table(name = "Service", uniqueIndexes = "service_unique_together")
public static abstract class Service {
@Key
@Generated
int id;
@Index(value = "service_unique_together")
@Column(nullable = false)
String account;
@Index(value = "service_unique_together")
CollectionInfo.Type type;
public static ServiceEntity fetch(EntityDataStore<Persistable> data, String account, CollectionInfo.Type type) {
return data.select(ServiceEntity.class).where(ServiceEntity.ACCOUNT.eq(account).and(ServiceEntity.TYPE.eq(type))).limit(1).get().firstOrNull();
}
}
static class CollectionInfoConverter implements Converter<CollectionInfo, String> {
@Override
public Class<CollectionInfo> getMappedType() {

@ -33,17 +33,13 @@ public class ServiceDB {
VALUE = "value";
}
@Deprecated
public static class Services {
public static final String
_TABLE = "services",
ID = "_id",
ACCOUNT_NAME = "accountName",
SERVICE = "service";
// allowed values for SERVICE column
public static final String
SERVICE_CALDAV = CollectionInfo.Type.CALENDAR.toString(),
SERVICE_CARDDAV = CollectionInfo.Type.ADDRESS_BOOK.toString();
}
@Deprecated
@ -90,15 +86,8 @@ public class ServiceDB {
db.execSQL("CREATE TABLE " + Settings._TABLE + "(" +
Settings.NAME + " TEXT NOT NULL," +
Settings.VALUE + " TEXT NOT NULL" +
")");
")");
db.execSQL("CREATE UNIQUE INDEX settings_name ON " + Settings._TABLE + " (" + Settings.NAME + ")");
db.execSQL("CREATE TABLE " + Services._TABLE + "(" +
Services.ID + " INTEGER PRIMARY KEY AUTOINCREMENT," +
Services.ACCOUNT_NAME + " TEXT NOT NULL," +
Services.SERVICE + " TEXT NOT NULL" +
")");
db.execSQL("CREATE UNIQUE INDEX services_account ON " + Services._TABLE + " (" + Services.ACCOUNT_NAME + "," + Services.SERVICE + ")");
}
@Override
@ -112,7 +101,7 @@ public class ServiceDB {
db.beginTransactionNonExclusive();
// iterate through all tables
@Cleanup Cursor cursorTables = db.query("sqlite_master", new String[] { "name" }, "type='table'", null, null, null, null);
@Cleanup Cursor cursorTables = db.query("sqlite_master", new String[]{"name"}, "type='table'", null, null, null, null);
while (cursorTables.moveToNext()) {
String table = cursorTables.getString(0);
sb.append(table).append("\n");
@ -153,47 +142,5 @@ public class ServiceDB {
}
db.endTransaction();
}
@NonNull
public Account getServiceAccount(SQLiteDatabase db, long service) {
@Cleanup Cursor cursor = db.query(Services._TABLE, new String[]{Services.ACCOUNT_NAME}, Services.ID + "=?", new String[]{String.valueOf(service)}, null, null, null);
if (cursor.moveToNext()) {
return new Account(cursor.getString(0), Constants.ACCOUNT_TYPE);
} else
throw new IllegalArgumentException("Service not found");
}
@NonNull
public String getServiceType(SQLiteDatabase db, long service) {
@Cleanup Cursor cursor = db.query(Services._TABLE, new String[]{Services.SERVICE}, Services.ID + "=?", new String[]{String.valueOf(service)}, null, null, null);
if (cursor.moveToNext())
return cursor.getString(0);
else
throw new IllegalArgumentException("Service not found");
}
@Nullable
public Long getService(@NonNull SQLiteDatabase db, @NonNull Account account, String service) {
@Cleanup Cursor c = db.query(Services._TABLE, new String[]{Services.ID},
Services.ACCOUNT_NAME + "=? AND " + Services.SERVICE + "=?", new String[]{account.name, service}, null, null, null);
if (c.moveToNext())
return c.getLong(0);
else
return null;
}
@Nullable
public Long getService(@NonNull Account account, String service) {
@Cleanup SQLiteDatabase db = getReadableDatabase();
return getService(db, account, service);
}
}
public static void onRenameAccount(@NonNull SQLiteDatabase db, @NonNull String oldName, @NonNull String newName) {
ContentValues values = new ContentValues(1);
values.put(Services.ACCOUNT_NAME, newName);
db.update(Services._TABLE, values, Services.ACCOUNT_NAME + "=?", new String[] { oldName });
}
}

@ -14,7 +14,6 @@ import android.content.ContentResolver;
import android.content.Context;
import android.content.Intent;
import android.content.SyncResult;
import android.database.sqlite.SQLiteDatabase;
import android.database.sqlite.SQLiteException;
import android.os.Bundle;
import android.provider.CalendarContract;
@ -27,8 +26,8 @@ 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.Services;
import com.etesync.syncadapter.model.JournalModel;
import com.etesync.syncadapter.model.ServiceEntity;
import com.etesync.syncadapter.resource.LocalCalendar;
import com.etesync.syncadapter.ui.DebugInfoActivity;
@ -109,47 +108,40 @@ public class CalendarsSyncAdapterService extends SyncAdapterService {
}
private void updateLocalCalendars(ContentProviderClient provider, Account account, AccountSettings settings) throws CalendarStorageException {
ServiceDB.OpenHelper dbHelper = new ServiceDB.OpenHelper(getContext());
try {
// enumerate remote and local calendars
SQLiteDatabase db = dbHelper.getReadableDatabase();
Long service = dbHelper.getService(db, account, Services.SERVICE_CALDAV);
EntityDataStore<Persistable> data = ((App) getContext().getApplicationContext()).getData();
Map<String, CollectionInfo> remote = new HashMap<>();
List<CollectionInfo> remoteCollections = JournalEntity.getCollections(data, service);
for (CollectionInfo info : remoteCollections) {
remote.put(info.uid, info);
}
EntityDataStore<Persistable> data = ((App) getContext().getApplicationContext()).getData();
ServiceEntity service = JournalModel.Service.fetch(data, account.name, CollectionInfo.Type.CALENDAR);
LocalCalendar[] local = (LocalCalendar[])LocalCalendar.find(account, provider, LocalCalendar.Factory.INSTANCE, null, null);
boolean updateColors = settings.getManageCalendarColors();
// delete obsolete local calendar
for (LocalCalendar calendar : local) {
String url = calendar.getName();
if (!remote.containsKey(url)) {
App.log.fine("Deleting obsolete local calendar " + url);
calendar.delete();
} else {
// remote CollectionInfo found for this local collection, update data
CollectionInfo info = remote.get(url);
App.log.fine("Updating local calendar " + url + " with " + info);
calendar.update(info, updateColors);
// we already have a local calendar for this remote collection, don't take into consideration anymore
remote.remove(url);
}
}
Map<String, CollectionInfo> remote = new HashMap<>();
List<CollectionInfo> remoteCollections = JournalEntity.getCollections(data, service);
for (CollectionInfo info : remoteCollections) {
remote.put(info.uid, info);
}
// create new local calendars
for (String url : remote.keySet()) {
LocalCalendar[] local = (LocalCalendar[]) LocalCalendar.find(account, provider, LocalCalendar.Factory.INSTANCE, null, null);
boolean updateColors = settings.getManageCalendarColors();
// delete obsolete local calendar
for (LocalCalendar calendar : local) {
String url = calendar.getName();
if (!remote.containsKey(url)) {
App.log.fine("Deleting obsolete local calendar " + url);
calendar.delete();
} else {
// remote CollectionInfo found for this local collection, update data
CollectionInfo info = remote.get(url);
App.log.info("Adding local calendar list " + info);
LocalCalendar.create(account, provider, info);
App.log.fine("Updating local calendar " + url + " with " + info);
calendar.update(info, updateColors);
// we already have a local calendar for this remote collection, don't take into consideration anymore
remote.remove(url);
}
} finally {
dbHelper.close();
}
// create new local calendars
for (String url : remote.keySet()) {
CollectionInfo info = remote.get(url);
App.log.info("Adding local calendar list " + info);
LocalCalendar.create(account, provider, info);
}
}
}

@ -14,7 +14,6 @@ import android.content.ContentResolver;
import android.content.Context;
import android.content.Intent;
import android.content.SyncResult;
import android.database.sqlite.SQLiteDatabase;
import android.os.Bundle;
import com.etesync.syncadapter.AccountSettings;
@ -26,7 +25,9 @@ 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.JournalModel;
import com.etesync.syncadapter.model.ServiceDB;
import com.etesync.syncadapter.model.ServiceEntity;
import com.etesync.syncadapter.ui.DebugInfoActivity;
import java.util.logging.Level;
@ -65,11 +66,12 @@ public class ContactsSyncAdapterService extends SyncAdapterService {
new RefreshCollections(account, CollectionInfo.Type.ADDRESS_BOOK).run();
SQLiteDatabase db = dbHelper.getReadableDatabase();
Long service = dbHelper.getService(db, account, ServiceDB.Services.SERVICE_CARDDAV);
EntityDataStore<Persistable> data = ((App) getContext().getApplicationContext()).getData();
ServiceEntity service = JournalModel.Service.fetch(data, account.name, CollectionInfo.Type.ADDRESS_BOOK);
if (service != null) {
HttpUrl principal = HttpUrl.get(settings.getUri());
EntityDataStore<Persistable> 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);

@ -47,7 +47,9 @@ 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.model.ServiceEntity;
import com.etesync.syncadapter.ui.PermissionsActivity;
import io.requery.Persistable;
@ -175,22 +177,16 @@ public abstract class SyncAdapterService extends Service {
journals.add(new Pair<>(journal, info));
}
db.beginTransactionNonExclusive();
try {
saveCollections(db, journals);
db.setTransactionSuccessful();
} finally {
db.endTransaction();
}
saveCollections(journals);
} finally {
dbHelper.close();
}
}
private void saveCollections(SQLiteDatabase db, Iterable<Pair<JournalManager.Journal, CollectionInfo>> journals) {
Long service = dbHelper.getService(db, account, serviceType.toString());
private void saveCollections(Iterable<Pair<JournalManager.Journal, CollectionInfo>> journals) {
EntityDataStore<Persistable> data = ((App) context.getApplicationContext()).getData();
ServiceEntity service = JournalModel.Service.fetch(data, account.name, serviceType);
Map<String, JournalEntity> existing = new HashMap<>();
for (JournalEntity journalEntity : JournalEntity.getJournals(data, service)) {
existing.put(journalEntity.getUid(), journalEntity);
@ -201,7 +197,7 @@ public abstract class SyncAdapterService extends Service {
CollectionInfo collection = pair.second;
App.log.log(Level.FINE, "Saving collection", journal.getUid());
collection.serviceID = service;
collection.serviceID = service.getId();
JournalEntity journalEntity = JournalEntity.fetchOrCreate(data, collection);
journalEntity.setOwner(journal.getOwner());
journalEntity.setEncryptedKey(journal.getKey());

@ -27,6 +27,8 @@ import com.etesync.syncadapter.journalmanager.JournalEntryManager;
import com.etesync.syncadapter.model.CollectionInfo;
import com.etesync.syncadapter.model.EntryEntity;
import com.etesync.syncadapter.model.JournalEntity;
import com.etesync.syncadapter.model.JournalModel;
import com.etesync.syncadapter.model.ServiceEntity;
import com.etesync.syncadapter.model.SyncEntry;
import com.etesync.syncadapter.resource.LocalCollection;
import com.etesync.syncadapter.resource.LocalResource;
@ -108,7 +110,8 @@ abstract public class SyncManager {
httpClient = HttpClient.create(context, account);
data = ((App) context.getApplicationContext()).getData();
info = JournalEntity.fetch(data, journalUid).getInfo();
ServiceEntity serviceEntity = JournalModel.Service.fetch(data, account.name, serviceType);
info = JournalEntity.fetch(data, serviceEntity, journalUid).getInfo();
// dismiss previous error notifications
notificationManager = new NotificationHelper(context, journalUid, notificationId());
@ -232,7 +235,7 @@ abstract public class SyncManager {
private JournalEntity getJournalEntity() {
if (_journalEntity == null)
_journalEntity = data.select(JournalEntity.class).where(JournalEntity.UID.eq(journal.getUid())).limit(1).get().first();
_journalEntity = JournalModel.Journal.fetch(data, info.getServiceEntity(data), journal.getUid());
return _journalEntity;
}

@ -24,8 +24,6 @@ import android.content.Intent;
import android.content.Loader;
import android.content.ServiceConnection;
import android.content.SyncStatusObserver;
import android.database.Cursor;
import android.database.sqlite.SQLiteDatabase;
import android.graphics.drawable.Drawable;
import android.os.Build;
import android.os.Bundle;
@ -57,8 +55,7 @@ import com.etesync.syncadapter.Constants;
import com.etesync.syncadapter.R;
import com.etesync.syncadapter.model.CollectionInfo;
import com.etesync.syncadapter.model.JournalEntity;
import com.etesync.syncadapter.model.ServiceDB.OpenHelper;
import com.etesync.syncadapter.model.ServiceDB.Services;
import com.etesync.syncadapter.model.ServiceEntity;
import com.etesync.syncadapter.resource.LocalCalendar;
import com.etesync.syncadapter.utils.HintManager;
import com.etesync.syncadapter.utils.ShowcaseBuilder;
@ -71,7 +68,6 @@ import at.bitfire.cert4android.CustomCertManager;
import at.bitfire.ical4android.TaskProvider;
import io.requery.Persistable;
import io.requery.sql.EntityDataStore;
import lombok.Cleanup;
import tourguide.tourguide.ToolTip;
import static android.content.ContentResolver.SYNC_OBSERVER_TYPE_ACTIVE;
@ -318,31 +314,23 @@ public class AccountActivity extends AppCompatActivity implements Toolbar.OnMenu
public AccountInfo loadInBackground() {
AccountInfo info = new AccountInfo();
@Cleanup OpenHelper dbHelper = new OpenHelper(getContext());
SQLiteDatabase db = dbHelper.getReadableDatabase();
EntityDataStore<Persistable> data = ((App) getContext().getApplicationContext()).getData();
@Cleanup Cursor cursor = db.query(
Services._TABLE,
new String[] { Services.ID, Services.SERVICE },
Services.ACCOUNT_NAME + "=?", new String[] { account.name },
null, null, null);
while (cursor.moveToNext()) {
long id = cursor.getLong(0);
String service = cursor.getString(1);
if (Services.SERVICE_CARDDAV.equals(service)) {
for (ServiceEntity serviceEntity : data.select(ServiceEntity.class).where(ServiceEntity.ACCOUNT.eq(account.name)).get()) {
long id = serviceEntity.getId();
CollectionInfo.Type service = serviceEntity.getType();
if (service.equals(CollectionInfo.Type.ADDRESS_BOOK)) {
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 = JournalEntity.getCollections(data, id);
} else if (Services.SERVICE_CALDAV.equals(service)) {
info.carddav.collections = JournalEntity.getCollections(data, serviceEntity);
} else if (service.equals(CollectionInfo.Type.CALENDAR)) {
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 = JournalEntity.getCollections(data, id);
info.caldav.collections = JournalEntity.getCollections(data, serviceEntity);
}
}
return info;

@ -35,7 +35,9 @@ 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.model.ServiceEntity;
import io.requery.Persistable;
import io.requery.sql.EntityDataStore;
@ -125,31 +127,21 @@ public class CreateCollectionFragment extends DialogFragment implements LoaderMa
@Override
public Exception loadInBackground() {
ServiceDB.OpenHelper dbHelper = new ServiceDB.OpenHelper(getContext());
try {
String authority = null;
// now insert collection into database:
SQLiteDatabase db = dbHelper.getWritableDatabase();
EntityDataStore<Persistable> data = ((App) getContext().getApplicationContext()).getData();
// 1. find service ID
String serviceType;
if (info.type == CollectionInfo.Type.ADDRESS_BOOK) {
serviceType = ServiceDB.Services.SERVICE_CARDDAV;
authority = ContactsContract.AUTHORITY;
} else if (info.type == CollectionInfo.Type.CALENDAR) {
serviceType = ServiceDB.Services.SERVICE_CALDAV;
authority = CalendarContract.AUTHORITY;
} else {
throw new IllegalArgumentException("Collection must be an address book or calendar");
}
@Cleanup Cursor c = db.query(ServiceDB.Services._TABLE, new String[]{ServiceDB.Services.ID},
ServiceDB.Services.ACCOUNT_NAME + "=? AND " + ServiceDB.Services.SERVICE + "=?",
new String[]{account.name, serviceType}, null, null, null
);
if (!c.moveToNext())
throw new IllegalStateException();
long serviceID = c.getLong(0);
ServiceEntity serviceEntity = JournalModel.Service.fetch(data, account.name, info.type);
AccountSettings settings = new AccountSettings(getContext(), account);
HttpUrl principal = HttpUrl.get(settings.getUri());
@ -167,8 +159,7 @@ public class CreateCollectionFragment extends DialogFragment implements LoaderMa
}
// 2. add collection to service
EntityDataStore<Persistable> data = ((App) getContext().getApplicationContext()).getData();
info.serviceID = serviceID;
info.serviceID = serviceEntity.getId();
JournalEntity journalEntity = JournalEntity.fetchOrCreate(data, info);
data.upsert(journalEntity);
@ -180,8 +171,6 @@ public class CreateCollectionFragment extends DialogFragment implements LoaderMa
return e;
} catch (Exceptions.IntegrityException|Exceptions.GenericCryptoException e) {
return e;
} finally {
dbHelper.close();
}
return null;

@ -49,6 +49,7 @@ import com.etesync.syncadapter.journalmanager.Exceptions.HttpException;
import com.etesync.syncadapter.model.EntryEntity;
import com.etesync.syncadapter.model.JournalEntity;
import com.etesync.syncadapter.model.ServiceDB;
import com.etesync.syncadapter.model.ServiceEntity;
import io.requery.Persistable;
import io.requery.sql.EntityDataStore;
@ -246,8 +247,14 @@ public class DebugInfoActivity extends AppCompatActivity implements LoaderManage
dbHelper.dump(report);
report.append("\n");
report.append("JOURNALS DUMP\n");
report.append("SERVICES DUMP\n");
EntityDataStore<Persistable> data = ((App) getContext().getApplicationContext()).getData();
for (ServiceEntity serviceEntity : data.select(ServiceEntity.class).get()) {
report.append(serviceEntity.toString() + "\n");
}
report.append("\n");
report.append("JOURNALS DUMP\n");
List<JournalEntity> journals = data.select(JournalEntity.class).where(JournalEntity.DELETED.eq(false)).get().toList();
for (JournalEntity journal : journals) {
report.append(journal.toString() + "\n");

@ -123,7 +123,7 @@ public class DeleteCollectionFragment extends DialogFragment implements LoaderMa
Crypto.CryptoManager crypto = new Crypto.CryptoManager(collectionInfo.version, settings.password(), collectionInfo.uid);
journalManager.deleteJournal(new JournalManager.Journal(crypto, collectionInfo.toJson(), collectionInfo.uid));
JournalEntity journalEntity = JournalEntity.fetch(data, collectionInfo.uid);
JournalEntity journalEntity = JournalEntity.fetch(data, collectionInfo.getServiceEntity(data), collectionInfo.uid);
journalEntity.setDeleted(true);
data.update(journalEntity);

@ -78,7 +78,7 @@ public class EditCollectionActivity extends CreateCollectionActivity {
public void onDeleteCollection(MenuItem item) {
EntityDataStore<Persistable> data = ((App) getApplication()).getData();
int journalCount = data.count(JournalEntity.class).where(JournalEntity.SERVICE.eq(info.serviceID)).get().value();
int journalCount = data.count(JournalEntity.class).where(JournalEntity.SERVICE_MODEL.eq(info.getServiceEntity(data))).get().value();
if (journalCount < 2) {
new AlertDialog.Builder(this)

@ -27,6 +27,8 @@ import com.etesync.syncadapter.R;
import com.etesync.syncadapter.model.CollectionInfo;
import com.etesync.syncadapter.model.EntryEntity;
import com.etesync.syncadapter.model.JournalEntity;
import com.etesync.syncadapter.model.JournalModel;
import com.etesync.syncadapter.model.ServiceEntity;
import com.etesync.syncadapter.resource.LocalAddressBook;
import com.etesync.syncadapter.resource.LocalCalendar;
import com.etesync.syncadapter.ui.importlocal.ImportActivity;
@ -63,7 +65,7 @@ public class ViewCollectionActivity extends AppCompatActivity implements Refresh
public void refresh() {
EntityDataStore<Persistable> data = ((App) getApplicationContext()).getData();
final JournalEntity journalEntity = JournalEntity.fetch(data, info.uid);
final JournalEntity journalEntity = JournalEntity.fetch(data, info.getServiceEntity(data), info.uid);
if ((journalEntity == null) || journalEntity.isDeleted()) {
finish();
return;
@ -173,7 +175,7 @@ public class ViewCollectionActivity extends AppCompatActivity implements Refresh
protected Long doInBackground(Void... aVoids) {
EntityDataStore<Persistable> data = ((App) getApplicationContext()).getData();
final JournalEntity journalEntity = JournalEntity.fetch(data, info.uid);
final JournalEntity journalEntity = JournalEntity.fetch(data, info.getServiceEntity(data), info.uid);
entryCount = data.count(EntryEntity.class).where(EntryEntity.JOURNAL.eq(journalEntity)).get().value();
long count;

@ -137,7 +137,7 @@ public class ListEntriesFragment extends ListFragment implements AdapterView.OnI
@Override
protected List<EntryEntity> doInBackground(Void... voids) {
journalEntity = JournalModel.Journal.fetch(data, info.uid);
journalEntity = JournalModel.Journal.fetch(data, info.getServiceEntity(data), info.uid);
return data.select(EntryEntity.class).where(EntryEntity.JOURNAL.eq(journalEntity)).orderBy(EntryEntity.ID.desc()).get().toList();
}

@ -14,7 +14,6 @@ import android.app.Activity;
import android.app.Dialog;
import android.app.ProgressDialog;
import android.content.ContentResolver;
import android.content.ContentValues;
import android.content.Context;
import android.database.sqlite.SQLiteDatabase;
import android.os.Bundle;
@ -26,8 +25,6 @@ import android.support.v4.app.LoaderManager;
import android.support.v4.content.AsyncTaskLoader;
import android.support.v4.content.Loader;
import java.util.logging.Level;
import com.etesync.syncadapter.AccountSettings;
import com.etesync.syncadapter.App;
import com.etesync.syncadapter.Constants;
@ -37,8 +34,12 @@ import com.etesync.syncadapter.journalmanager.Crypto;
import com.etesync.syncadapter.model.CollectionInfo;
import com.etesync.syncadapter.model.JournalEntity;
import com.etesync.syncadapter.model.ServiceDB;
import com.etesync.syncadapter.model.ServiceEntity;
import com.etesync.syncadapter.resource.LocalTaskList;
import com.etesync.syncadapter.ui.setup.BaseConfigurationFinder.Configuration;
import java.util.logging.Level;
import at.bitfire.ical4android.TaskProvider;
import io.requery.Persistable;
import io.requery.sql.EntityDataStore;
@ -140,7 +141,7 @@ public class SetupEncryptionFragment extends DialogFragment implements LoaderMan
if (config.cardDAV != null) {
// insert CardDAV service
insertService(db, accountName, ServiceDB.Services.SERVICE_CARDDAV, config.cardDAV);
insertService(db, accountName, CollectionInfo.Type.ADDRESS_BOOK, config.cardDAV);
// contact sync is automatically enabled by isAlwaysSyncable="true" in res/xml/sync_contacts.xml
settings.setSyncInterval(ContactsContract.AUTHORITY, Constants.DEFAULT_SYNC_INTERVAL);
@ -150,7 +151,7 @@ public class SetupEncryptionFragment extends DialogFragment implements LoaderMan
if (config.calDAV != null) {
// insert CalDAV service
insertService(db, accountName, ServiceDB.Services.SERVICE_CALDAV, config.calDAV);
insertService(db, accountName, CollectionInfo.Type.CALENDAR, config.calDAV);
// calendar sync is automatically enabled by isAlwaysSyncable="true" in res/xml/sync_contacts.xml
settings.setSyncInterval(CalendarContract.AUTHORITY, Constants.DEFAULT_SYNC_INTERVAL);
@ -172,22 +173,20 @@ public class SetupEncryptionFragment extends DialogFragment implements LoaderMan
return true;
}
protected long insertService(SQLiteDatabase db, String accountName, String service, BaseConfigurationFinder.Configuration.ServiceInfo info) {
ContentValues values = new ContentValues();
protected void insertService(SQLiteDatabase db, String accountName, CollectionInfo.Type serviceType, BaseConfigurationFinder.Configuration.ServiceInfo info) {
EntityDataStore<Persistable> data = ((App) getContext().getApplicationContext()).getData();
// insert service
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<Persistable> data = ((App) getContext().getApplicationContext()).getData();
ServiceEntity serviceEntity = new ServiceEntity();
serviceEntity.setAccount(accountName);
serviceEntity.setType(serviceType);
data.upsert(serviceEntity);
// insert collections
for (CollectionInfo collection : info.collections.values()) {
collection.serviceID = serviceID;
JournalEntity journalEntity = new JournalEntity(collection);
collection.serviceID = serviceEntity.getId();
JournalEntity journalEntity = new JournalEntity(data, collection);
data.insert(journalEntity);
}
return serviceID;
}
}

Loading…
Cancel
Save