mirror of
https://github.com/etesync/android
synced 2025-04-20 00:59:05 +00:00
Cache journals locally.
This is useful both as an anti-tampering mechanism, and will be used later when interacting with the journal is implemented.
This commit is contained in:
parent
afcb00e4f1
commit
29fd177a95
@ -127,6 +127,10 @@ dependencies {
|
||||
|
||||
compile 'com.github.yukuku:ambilwarna:2.0.1'
|
||||
|
||||
compile 'io.requery:requery:1.2.0'
|
||||
compile 'io.requery:requery-android:1.2.0'
|
||||
annotationProcessor 'io.requery:requery-processor:1.2.0'
|
||||
|
||||
compile group: 'com.madgag.spongycastle', name: 'core', version: '1.54.0.0'
|
||||
compile group: 'com.madgag.spongycastle', name: 'prov', version: '1.54.0.0'
|
||||
compile group: 'com.google.code.gson', name: 'gson', version: '1.7.2'
|
||||
|
@ -18,12 +18,19 @@ import android.graphics.drawable.BitmapDrawable;
|
||||
import android.graphics.drawable.Drawable;
|
||||
import android.os.Build;
|
||||
import android.os.Process;
|
||||
import android.os.StrictMode;
|
||||
import android.support.annotation.NonNull;
|
||||
import android.support.annotation.Nullable;
|
||||
import android.support.v4.app.NotificationManagerCompat;
|
||||
import android.support.v7.app.NotificationCompat;
|
||||
import android.util.Log;
|
||||
|
||||
import com.etesync.syncadapter.log.LogcatHandler;
|
||||
import com.etesync.syncadapter.log.PlainTextFormatter;
|
||||
import com.etesync.syncadapter.model.Models;
|
||||
import com.etesync.syncadapter.model.ServiceDB;
|
||||
import com.etesync.syncadapter.model.Settings;
|
||||
|
||||
import org.apache.commons.lang3.time.DateFormatUtils;
|
||||
|
||||
import java.io.File;
|
||||
@ -36,10 +43,11 @@ import java.util.logging.Logger;
|
||||
import javax.net.ssl.HostnameVerifier;
|
||||
|
||||
import at.bitfire.cert4android.CustomCertManager;
|
||||
import com.etesync.syncadapter.log.LogcatHandler;
|
||||
import com.etesync.syncadapter.log.PlainTextFormatter;
|
||||
import com.etesync.syncadapter.model.ServiceDB;
|
||||
import com.etesync.syncadapter.model.Settings;
|
||||
import io.requery.Persistable;
|
||||
import io.requery.android.sqlite.DatabaseSource;
|
||||
import io.requery.sql.Configuration;
|
||||
import io.requery.sql.EntityDataStore;
|
||||
import io.requery.sql.TableCreationMode;
|
||||
import lombok.Cleanup;
|
||||
import lombok.Getter;
|
||||
import okhttp3.internal.tls.OkHostnameVerifier;
|
||||
@ -76,6 +84,7 @@ public class App extends Application {
|
||||
super.onCreate();
|
||||
reinitCertManager();
|
||||
reinitLogger();
|
||||
StrictMode.enableDefaults();
|
||||
}
|
||||
|
||||
public void reinitCertManager() {
|
||||
@ -176,4 +185,26 @@ public class App extends Application {
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
private EntityDataStore<Persistable> dataStore;
|
||||
|
||||
/**
|
||||
* @return {@link EntityDataStore} single instance for the application.
|
||||
* <p/>
|
||||
* Note if you're using Dagger you can make this part of your application level module returning
|
||||
* {@code @Provides @Singleton}.
|
||||
*/
|
||||
public EntityDataStore<Persistable> getData() {
|
||||
if (dataStore == null) {
|
||||
// override onUpgrade to handle migrating to a new version
|
||||
DatabaseSource source = new DatabaseSource(this, Models.DEFAULT, 1);
|
||||
if (BuildConfig.DEBUG) {
|
||||
// use this in development mode to drop and recreate the tables on every upgrade
|
||||
source.setTableCreationMode(TableCreationMode.DROP_CREATE);
|
||||
}
|
||||
Configuration configuration = source.getConfiguration();
|
||||
dataStore = new EntityDataStore<>(configuration);
|
||||
}
|
||||
return dataStore;
|
||||
}
|
||||
}
|
||||
|
@ -7,6 +7,8 @@ import java.util.List;
|
||||
|
||||
import com.etesync.syncadapter.App;
|
||||
import com.etesync.syncadapter.GsonHelper;
|
||||
|
||||
import lombok.Getter;
|
||||
import okhttp3.HttpUrl;
|
||||
import okhttp3.OkHttpClient;
|
||||
import okhttp3.Request;
|
||||
@ -15,11 +17,14 @@ import okhttp3.Response;
|
||||
import okhttp3.ResponseBody;
|
||||
|
||||
public class JournalEntryManager extends BaseManager {
|
||||
@Getter
|
||||
final String uid;
|
||||
final static private Type entryType = new TypeToken<List<Entry>>() {
|
||||
}.getType();
|
||||
|
||||
|
||||
public JournalEntryManager(OkHttpClient httpClient, HttpUrl remote, String journal) {
|
||||
this.uid = journal;
|
||||
this.remote = remote.newBuilder()
|
||||
.addPathSegments("api/v1/journal")
|
||||
.addPathSegments(journal)
|
||||
|
@ -0,0 +1,42 @@
|
||||
package com.etesync.syncadapter.model;
|
||||
|
||||
import io.requery.CascadeAction;
|
||||
import io.requery.Column;
|
||||
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.ReferentialAction;
|
||||
|
||||
public class JournalModel {
|
||||
@Entity
|
||||
public static abstract class Journal {
|
||||
@Key
|
||||
@Generated
|
||||
int id;
|
||||
|
||||
@Column(length = 64, unique = true, nullable = false)
|
||||
String uid;
|
||||
}
|
||||
|
||||
@Entity
|
||||
public static abstract class Entry {
|
||||
@Key
|
||||
@Generated
|
||||
int id;
|
||||
|
||||
@Column(length = 64, unique = true, nullable = false)
|
||||
String uid;
|
||||
|
||||
String content;
|
||||
|
||||
@Index("journal_index")
|
||||
@ForeignKey(update = ReferentialAction.CASCADE)
|
||||
@ManyToOne
|
||||
Journal journal;
|
||||
}
|
||||
}
|
@ -60,7 +60,10 @@ public class CalendarSyncManager extends SyncManager {
|
||||
}
|
||||
|
||||
@Override
|
||||
protected boolean prepare() throws ContactsStorageException {
|
||||
protected boolean prepare() throws ContactsStorageException, CalendarStorageException {
|
||||
if (!super.prepare())
|
||||
return false;
|
||||
|
||||
journal = new JournalEntryManager(httpClient, remote, localCalendar().getName());
|
||||
return true;
|
||||
}
|
||||
|
@ -78,6 +78,8 @@ public class ContactsSyncManager extends SyncManager {
|
||||
|
||||
@Override
|
||||
protected boolean prepare() throws ContactsStorageException, CalendarStorageException {
|
||||
if (!super.prepare())
|
||||
return false;
|
||||
// prepare local address book
|
||||
localCollection = new LocalAddressBook(account, provider);
|
||||
LocalAddressBook localAddressBook = localAddressBook();
|
||||
|
@ -47,8 +47,12 @@ 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 com.etesync.syncadapter.ui.PermissionsActivity;
|
||||
|
||||
import io.requery.Persistable;
|
||||
import io.requery.sql.EntityDataStore;
|
||||
import lombok.Cleanup;
|
||||
import okhttp3.HttpUrl;
|
||||
import okhttp3.OkHttpClient;
|
||||
@ -195,6 +199,7 @@ public abstract class SyncAdapterService extends Service {
|
||||
}
|
||||
|
||||
private void saveCollections(SQLiteDatabase db, Iterable<CollectionInfo> collections) {
|
||||
EntityDataStore<Persistable> 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) {
|
||||
@ -202,6 +207,13 @@ public abstract class SyncAdapterService extends Service {
|
||||
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);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -14,16 +14,6 @@ import android.content.Intent;
|
||||
import android.content.SyncResult;
|
||||
import android.os.Bundle;
|
||||
|
||||
import org.apache.commons.collections4.ListUtils;
|
||||
|
||||
import java.io.FileNotFoundException;
|
||||
import java.io.IOException;
|
||||
import java.util.ArrayList;
|
||||
import java.util.LinkedList;
|
||||
import java.util.List;
|
||||
import java.util.UUID;
|
||||
import java.util.logging.Level;
|
||||
|
||||
import com.etesync.syncadapter.AccountSettings;
|
||||
import com.etesync.syncadapter.App;
|
||||
import com.etesync.syncadapter.Constants;
|
||||
@ -35,12 +25,27 @@ import com.etesync.syncadapter.R;
|
||||
import com.etesync.syncadapter.journalmanager.Exceptions;
|
||||
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.resource.LocalCollection;
|
||||
import com.etesync.syncadapter.resource.LocalResource;
|
||||
import com.etesync.syncadapter.ui.DebugInfoActivity;
|
||||
|
||||
import org.apache.commons.collections4.ListUtils;
|
||||
|
||||
import java.io.FileNotFoundException;
|
||||
import java.io.IOException;
|
||||
import java.util.ArrayList;
|
||||
import java.util.LinkedList;
|
||||
import java.util.List;
|
||||
import java.util.UUID;
|
||||
import java.util.logging.Level;
|
||||
|
||||
import at.bitfire.ical4android.CalendarStorageException;
|
||||
import at.bitfire.ical4android.InvalidCalendarException;
|
||||
import at.bitfire.vcard4android.ContactsStorageException;
|
||||
import io.requery.Persistable;
|
||||
import io.requery.sql.EntityDataStore;
|
||||
import lombok.Getter;
|
||||
import okhttp3.OkHttpClient;
|
||||
|
||||
@ -63,6 +68,9 @@ abstract public class SyncManager {
|
||||
protected OkHttpClient httpClient;
|
||||
|
||||
protected JournalEntryManager journal;
|
||||
private JournalEntity _journalEntity;
|
||||
|
||||
private EntityDataStore<Persistable> data;
|
||||
|
||||
/**
|
||||
* remote CTag (uuid of the last entry on the server). We update it when we fetch/push and save when everything works.
|
||||
@ -101,6 +109,8 @@ abstract public class SyncManager {
|
||||
this.uniqueCollectionId = uniqueCollectionId;
|
||||
notificationManager = new NotificationHelper(context, uniqueCollectionId, notificationId());
|
||||
notificationManager.cancel();
|
||||
|
||||
data = ((App) context.getApplicationContext()).getData();
|
||||
}
|
||||
|
||||
protected abstract int notificationId();
|
||||
@ -207,10 +217,26 @@ abstract public class SyncManager {
|
||||
* @return whether actual synchronization is required / can be made. true = synchronization
|
||||
* shall be continued, false = synchronization can be skipped
|
||||
*/
|
||||
abstract protected boolean prepare() throws ContactsStorageException, CalendarStorageException;
|
||||
protected boolean prepare() throws ContactsStorageException, CalendarStorageException {
|
||||
return true;
|
||||
}
|
||||
|
||||
abstract protected void processSyncEntry(SyncEntry cEntry) throws IOException, ContactsStorageException, CalendarStorageException, InvalidCalendarException;
|
||||
|
||||
private JournalEntity getJournalEntity() {
|
||||
if (_journalEntity == null)
|
||||
_journalEntity = data.select(JournalEntity.class).where(JournalEntity.UID.eq(journal.getUid())).limit(1).get().first();
|
||||
return _journalEntity;
|
||||
}
|
||||
|
||||
private void persistSyncEntry(String uid, SyncEntry syncEntry) {
|
||||
EntryEntity entry = new EntryEntity();
|
||||
entry.setUid(uid);
|
||||
entry.setContent(syncEntry.toJson());
|
||||
entry.setJournal(getJournalEntity());
|
||||
data.insert(entry);
|
||||
}
|
||||
|
||||
protected void applyLocalEntries() throws IOException, ContactsStorageException, CalendarStorageException, Exceptions.HttpException, InvalidCalendarException, InterruptedException {
|
||||
// FIXME: Need a better strategy
|
||||
// We re-apply local entries so our changes override whatever was written in the remote.
|
||||
@ -225,6 +251,7 @@ abstract public class SyncManager {
|
||||
App.log.info("Processing (" + String.valueOf(i) + "/" + strTotal + ") " + entry.toString());
|
||||
|
||||
SyncEntry cEntry = SyncEntry.fromJournalEntry(settings.password(), entry);
|
||||
persistSyncEntry(entry.getUuid(), cEntry);
|
||||
if (cEntry.isAction(SyncEntry.Actions.DELETE)) {
|
||||
continue;
|
||||
}
|
||||
@ -237,7 +264,23 @@ abstract public class SyncManager {
|
||||
}
|
||||
|
||||
protected void fetchEntries() throws Exceptions.HttpException, ContactsStorageException, CalendarStorageException, Exceptions.IntegrityException {
|
||||
remoteEntries = journal.getEntries(settings.password(), remoteCTag);
|
||||
int count = data.count(EntryEntity.class).where(EntryEntity.JOURNAL.eq(getJournalEntity())).get().value();
|
||||
if ((remoteCTag != null) && (count == 0)) {
|
||||
// If we are updating an existing installation with no saved journal, we need to add
|
||||
remoteEntries = journal.getEntries(settings.password(), null);
|
||||
int i = 0;
|
||||
for (JournalEntryManager.Entry entry : remoteEntries){
|
||||
SyncEntry cEntry = SyncEntry.fromJournalEntry(settings.password(), entry);
|
||||
persistSyncEntry(entry.getUuid(), cEntry);
|
||||
i++;
|
||||
if (remoteCTag.equals(entry.getUuid())) {
|
||||
remoteEntries.subList(0, i).clear();
|
||||
break;
|
||||
}
|
||||
}
|
||||
} else {
|
||||
remoteEntries = journal.getEntries(settings.password(), remoteCTag);
|
||||
}
|
||||
|
||||
App.log.info("Fetched " + String.valueOf(remoteEntries.size()) + " entries");
|
||||
}
|
||||
@ -256,6 +299,7 @@ abstract public class SyncManager {
|
||||
App.log.info("Processing (" + String.valueOf(i) + "/" + strTotal + ") " + entry.toString());
|
||||
|
||||
SyncEntry cEntry = SyncEntry.fromJournalEntry(settings.password(), entry);
|
||||
persistSyncEntry(entry.getUuid(), cEntry);
|
||||
App.log.info("Processing resource for journal entry");
|
||||
processSyncEntry(cEntry);
|
||||
|
||||
|
Loading…
Reference in New Issue
Block a user