mirror of https://github.com/tasks/tasks
Convert EteSynchronizer to Kotlin
parent
5658fca41f
commit
378580b1e8
@ -1,288 +0,0 @@
|
||||
package org.tasks.etesync;
|
||||
|
||||
import static com.google.common.collect.Lists.partition;
|
||||
import static com.google.common.collect.Lists.transform;
|
||||
|
||||
import android.content.Context;
|
||||
import androidx.annotation.Nullable;
|
||||
import androidx.core.util.Pair;
|
||||
import at.bitfire.cert4android.CustomCertManager;
|
||||
import at.bitfire.cert4android.CustomCertManager.CustomHostnameVerifier;
|
||||
import com.etesync.journalmanager.Constants;
|
||||
import com.etesync.journalmanager.Crypto;
|
||||
import com.etesync.journalmanager.Crypto.CryptoManager;
|
||||
import com.etesync.journalmanager.Exceptions;
|
||||
import com.etesync.journalmanager.Exceptions.HttpException;
|
||||
import com.etesync.journalmanager.Exceptions.IntegrityException;
|
||||
import com.etesync.journalmanager.Exceptions.VersionTooNewException;
|
||||
import com.etesync.journalmanager.JournalAuthenticator;
|
||||
import com.etesync.journalmanager.JournalEntryManager;
|
||||
import com.etesync.journalmanager.JournalEntryManager.Entry;
|
||||
import com.etesync.journalmanager.JournalManager;
|
||||
import com.etesync.journalmanager.JournalManager.Journal;
|
||||
import com.etesync.journalmanager.UserInfoManager;
|
||||
import com.etesync.journalmanager.UserInfoManager.UserInfo;
|
||||
import com.etesync.journalmanager.model.CollectionInfo;
|
||||
import com.etesync.journalmanager.model.SyncEntry;
|
||||
import com.etesync.journalmanager.util.TokenAuthenticator;
|
||||
import dagger.hilt.android.qualifiers.ApplicationContext;
|
||||
import java.io.IOException;
|
||||
import java.security.KeyManagementException;
|
||||
import java.security.NoSuchAlgorithmException;
|
||||
import java.util.HashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
import javax.inject.Inject;
|
||||
import javax.net.ssl.SSLContext;
|
||||
import javax.net.ssl.TrustManager;
|
||||
import okhttp3.HttpUrl;
|
||||
import okhttp3.OkHttpClient;
|
||||
import okhttp3.OkHttpClient.Builder;
|
||||
import okhttp3.internal.tls.OkHostnameVerifier;
|
||||
import org.tasks.Callback;
|
||||
import org.tasks.DebugNetworkInterceptor;
|
||||
import org.tasks.caldav.MemoryCookieStore;
|
||||
import org.tasks.data.CaldavAccount;
|
||||
import org.tasks.data.CaldavCalendar;
|
||||
import org.tasks.preferences.Preferences;
|
||||
import org.tasks.security.KeyStoreEncryption;
|
||||
import timber.log.Timber;
|
||||
|
||||
public class EteSyncClient {
|
||||
|
||||
private static final String TYPE_TASKS = "TASKS";
|
||||
private static final int MAX_FETCH = 50;
|
||||
private static final int MAX_PUSH = 30;
|
||||
|
||||
private final KeyStoreEncryption encryption;
|
||||
private final Preferences preferences;
|
||||
private final DebugNetworkInterceptor interceptor;
|
||||
private final String username;
|
||||
private final String token;
|
||||
private final String encryptionPassword;
|
||||
private final OkHttpClient httpClient;
|
||||
private final HttpUrl httpUrl;
|
||||
private final Context context;
|
||||
private final JournalManager journalManager;
|
||||
private boolean foreground;
|
||||
|
||||
@Inject
|
||||
public EteSyncClient(
|
||||
@ApplicationContext Context context,
|
||||
KeyStoreEncryption encryption,
|
||||
Preferences preferences,
|
||||
DebugNetworkInterceptor interceptor) {
|
||||
this.context = context;
|
||||
this.encryption = encryption;
|
||||
this.preferences = preferences;
|
||||
this.interceptor = interceptor;
|
||||
username = null;
|
||||
token = null;
|
||||
encryptionPassword = null;
|
||||
httpClient = null;
|
||||
httpUrl = null;
|
||||
journalManager = null;
|
||||
}
|
||||
|
||||
private EteSyncClient(
|
||||
Context context,
|
||||
KeyStoreEncryption encryption,
|
||||
Preferences preferences,
|
||||
DebugNetworkInterceptor interceptor,
|
||||
String url,
|
||||
String username,
|
||||
String encryptionPassword,
|
||||
String token,
|
||||
boolean foreground)
|
||||
throws NoSuchAlgorithmException, KeyManagementException {
|
||||
this.context = context;
|
||||
this.encryption = encryption;
|
||||
this.preferences = preferences;
|
||||
this.interceptor = interceptor;
|
||||
this.username = username;
|
||||
this.encryptionPassword = encryptionPassword;
|
||||
this.token = token;
|
||||
this.foreground = foreground;
|
||||
|
||||
CustomCertManager customCertManager = new CustomCertManager(context);
|
||||
customCertManager.setAppInForeground(foreground);
|
||||
CustomHostnameVerifier hostnameVerifier =
|
||||
customCertManager.hostnameVerifier(OkHostnameVerifier.INSTANCE);
|
||||
SSLContext sslContext = SSLContext.getInstance("TLS");
|
||||
sslContext.init(null, new TrustManager[] {customCertManager}, null);
|
||||
|
||||
Builder builder =
|
||||
new OkHttpClient()
|
||||
.newBuilder()
|
||||
.addNetworkInterceptor(new TokenAuthenticator(null, token))
|
||||
.cookieJar(new MemoryCookieStore())
|
||||
.followRedirects(false)
|
||||
.followSslRedirects(true)
|
||||
.sslSocketFactory(sslContext.getSocketFactory(), customCertManager)
|
||||
.hostnameVerifier(hostnameVerifier)
|
||||
.connectTimeout(15, TimeUnit.SECONDS)
|
||||
.writeTimeout(30, TimeUnit.SECONDS)
|
||||
.readTimeout(120, TimeUnit.SECONDS);
|
||||
if (preferences.isFlipperEnabled()) {
|
||||
interceptor.add(builder);
|
||||
}
|
||||
httpClient = builder.build();
|
||||
httpUrl = HttpUrl.parse(url);
|
||||
journalManager = new JournalManager(httpClient, httpUrl);
|
||||
}
|
||||
|
||||
public EteSyncClient forAccount(CaldavAccount account)
|
||||
throws NoSuchAlgorithmException, KeyManagementException {
|
||||
return forUrl(
|
||||
account.getUrl(),
|
||||
account.getUsername(),
|
||||
account.getEncryptionPassword(encryption),
|
||||
account.getPassword(encryption));
|
||||
}
|
||||
|
||||
EteSyncClient forUrl(String url, String username, String encryptionPassword, String token)
|
||||
throws KeyManagementException, NoSuchAlgorithmException {
|
||||
return new EteSyncClient(
|
||||
context,
|
||||
encryption,
|
||||
preferences,
|
||||
interceptor,
|
||||
url,
|
||||
username,
|
||||
encryptionPassword,
|
||||
token,
|
||||
foreground);
|
||||
}
|
||||
|
||||
String getToken(String password) throws IOException, HttpException {
|
||||
return new JournalAuthenticator(httpClient, httpUrl).getAuthToken(username, password);
|
||||
}
|
||||
|
||||
UserInfo getUserInfo() throws HttpException {
|
||||
UserInfoManager userInfoManager = new UserInfoManager(httpClient, httpUrl);
|
||||
return userInfoManager.fetch(username);
|
||||
}
|
||||
|
||||
CryptoManager getCrypto(UserInfo userInfo, Journal journal)
|
||||
throws VersionTooNewException, IntegrityException {
|
||||
if (journal.getKey() == null) {
|
||||
return new CryptoManager(journal.getVersion(), encryptionPassword, journal.getUid());
|
||||
}
|
||||
if (userInfo == null) {
|
||||
throw new RuntimeException("Missing userInfo");
|
||||
}
|
||||
CryptoManager cryptoManager = new CryptoManager(userInfo.getVersion(), encryptionPassword, "userInfo");
|
||||
Crypto.AsymmetricKeyPair keyPair =
|
||||
new Crypto.AsymmetricKeyPair(userInfo.getContent(cryptoManager), userInfo.getPubkey());
|
||||
return new CryptoManager(journal.getVersion(), keyPair, journal.getKey());
|
||||
}
|
||||
|
||||
private @Nullable CollectionInfo convertJournalToCollection(UserInfo userInfo, Journal journal) {
|
||||
try {
|
||||
CryptoManager cryptoManager = getCrypto(userInfo, journal);
|
||||
journal.verify(cryptoManager);
|
||||
CollectionInfo collection =
|
||||
CollectionInfo.Companion.fromJson(journal.getContent(cryptoManager));
|
||||
collection.updateFromJournal(journal);
|
||||
return collection;
|
||||
} catch (IntegrityException | VersionTooNewException e) {
|
||||
Timber.e(e);
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
public Map<Journal, CollectionInfo> getCalendars(UserInfo userInfo)
|
||||
throws Exceptions.HttpException {
|
||||
Map<Journal, CollectionInfo> result = new HashMap<>();
|
||||
for (Journal journal : journalManager.list()) {
|
||||
CollectionInfo collection = convertJournalToCollection(userInfo, journal);
|
||||
if (collection != null) {
|
||||
if (TYPE_TASKS.equals(collection.getType())) {
|
||||
Timber.v("Found %s", collection);
|
||||
result.put(journal, collection);
|
||||
} else {
|
||||
Timber.v("Ignoring %s", collection);
|
||||
}
|
||||
}
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
void getSyncEntries(
|
||||
UserInfo userInfo,
|
||||
Journal journal,
|
||||
CaldavCalendar calendar,
|
||||
Callback<List<Pair<Entry, SyncEntry>>> callback)
|
||||
throws IntegrityException, Exceptions.HttpException, VersionTooNewException {
|
||||
JournalEntryManager journalEntryManager =
|
||||
new JournalEntryManager(httpClient, httpUrl, journal.getUid());
|
||||
CryptoManager crypto = getCrypto(userInfo, journal);
|
||||
List<Entry> journalEntries;
|
||||
do {
|
||||
journalEntries = journalEntryManager.list(crypto, calendar.getCtag(), MAX_FETCH);
|
||||
callback.call(
|
||||
transform(journalEntries, e -> Pair.create(e, SyncEntry.fromJournalEntry(crypto, e))));
|
||||
} while (journalEntries.size() >= MAX_FETCH);
|
||||
}
|
||||
|
||||
void pushEntries(Journal journal, List<Entry> entries, String remoteCtag) throws HttpException {
|
||||
JournalEntryManager journalEntryManager =
|
||||
new JournalEntryManager(httpClient, httpUrl, journal.getUid());
|
||||
for (List<Entry> partition : partition(entries, MAX_PUSH)) {
|
||||
journalEntryManager.create(partition, remoteCtag);
|
||||
remoteCtag = partition.get(partition.size() - 1).getUid();
|
||||
}
|
||||
}
|
||||
|
||||
void setForeground() {
|
||||
foreground = true;
|
||||
}
|
||||
|
||||
void invalidateToken() {
|
||||
try {
|
||||
new JournalAuthenticator(httpClient, httpUrl).invalidateAuthToken(token);
|
||||
} catch (Exception e) {
|
||||
Timber.e(e);
|
||||
}
|
||||
}
|
||||
|
||||
String makeCollection(String name, int color)
|
||||
throws VersionTooNewException, IntegrityException, HttpException {
|
||||
String uid = Journal.genUid();
|
||||
CollectionInfo collectionInfo = new CollectionInfo();
|
||||
collectionInfo.setDisplayName(name);
|
||||
collectionInfo.setType(TYPE_TASKS);
|
||||
collectionInfo.setUid(uid);
|
||||
collectionInfo.setSelected(true);
|
||||
collectionInfo.setColor(color == 0 ? null : color);
|
||||
CryptoManager crypto = new CryptoManager(collectionInfo.getVersion(), encryptionPassword, uid);
|
||||
journalManager.create(new Journal(crypto, collectionInfo.toJson(), uid));
|
||||
return uid;
|
||||
}
|
||||
|
||||
String updateCollection(CaldavCalendar calendar, String name, int color)
|
||||
throws VersionTooNewException, IntegrityException, HttpException {
|
||||
String uid = calendar.getUrl();
|
||||
Journal journal = journalManager.fetch(uid);
|
||||
UserInfo userInfo = getUserInfo();
|
||||
CryptoManager crypto = getCrypto(userInfo, journal);
|
||||
CollectionInfo collectionInfo = convertJournalToCollection(userInfo, journal);
|
||||
collectionInfo.setDisplayName(name);
|
||||
collectionInfo.setColor(color == 0 ? null : color);
|
||||
journalManager.update(new Journal(crypto, collectionInfo.toJson(), uid));
|
||||
return uid;
|
||||
}
|
||||
|
||||
void deleteCollection(CaldavCalendar calendar) throws HttpException {
|
||||
journalManager.delete(Journal.fakeWithUid(calendar.getUrl()));
|
||||
}
|
||||
|
||||
void createUserInfo(String derivedKey)
|
||||
throws HttpException, VersionTooNewException, IntegrityException, IOException {
|
||||
CryptoManager cryptoManager =
|
||||
new CryptoManager(Constants.CURRENT_VERSION, derivedKey, "userInfo");
|
||||
UserInfo userInfo = UserInfo.generate(cryptoManager, username);
|
||||
new UserInfoManager(httpClient, httpUrl).create(userInfo);
|
||||
}
|
||||
}
|
@ -0,0 +1,276 @@
|
||||
package org.tasks.etesync
|
||||
|
||||
import android.content.Context
|
||||
import androidx.core.util.Pair
|
||||
import at.bitfire.cert4android.CustomCertManager
|
||||
import com.etesync.journalmanager.*
|
||||
import com.etesync.journalmanager.Constants.Companion.CURRENT_VERSION
|
||||
import com.etesync.journalmanager.Crypto.AsymmetricKeyPair
|
||||
import com.etesync.journalmanager.Crypto.CryptoManager
|
||||
import com.etesync.journalmanager.Exceptions.IntegrityException
|
||||
import com.etesync.journalmanager.Exceptions.VersionTooNewException
|
||||
import com.etesync.journalmanager.JournalManager.Journal
|
||||
import com.etesync.journalmanager.UserInfoManager.UserInfo.Companion.generate
|
||||
import com.etesync.journalmanager.model.CollectionInfo
|
||||
import com.etesync.journalmanager.model.CollectionInfo.Companion.fromJson
|
||||
import com.etesync.journalmanager.model.SyncEntry
|
||||
import com.etesync.journalmanager.util.TokenAuthenticator
|
||||
import com.google.common.collect.Lists
|
||||
import dagger.hilt.android.qualifiers.ApplicationContext
|
||||
import okhttp3.HttpUrl
|
||||
import okhttp3.HttpUrl.Companion.toHttpUrlOrNull
|
||||
import okhttp3.OkHttpClient
|
||||
import org.tasks.DebugNetworkInterceptor
|
||||
import org.tasks.caldav.MemoryCookieStore
|
||||
import org.tasks.data.CaldavAccount
|
||||
import org.tasks.data.CaldavCalendar
|
||||
import org.tasks.preferences.Preferences
|
||||
import org.tasks.security.KeyStoreEncryption
|
||||
import timber.log.Timber
|
||||
import java.io.IOException
|
||||
import java.security.KeyManagementException
|
||||
import java.security.NoSuchAlgorithmException
|
||||
import java.util.*
|
||||
import java.util.concurrent.TimeUnit
|
||||
import javax.inject.Inject
|
||||
import javax.net.ssl.SSLContext
|
||||
import javax.net.ssl.TrustManager
|
||||
|
||||
class EteSyncClient {
|
||||
private val encryption: KeyStoreEncryption
|
||||
private val preferences: Preferences
|
||||
private val interceptor: DebugNetworkInterceptor
|
||||
private val username: String?
|
||||
private val token: String?
|
||||
private val encryptionPassword: String?
|
||||
private val httpClient: OkHttpClient?
|
||||
private val httpUrl: HttpUrl?
|
||||
private val context: Context
|
||||
private val journalManager: JournalManager?
|
||||
private var foreground = false
|
||||
|
||||
@Inject
|
||||
constructor(
|
||||
@ApplicationContext context: Context,
|
||||
encryption: KeyStoreEncryption,
|
||||
preferences: Preferences,
|
||||
interceptor: DebugNetworkInterceptor) {
|
||||
this.context = context
|
||||
this.encryption = encryption
|
||||
this.preferences = preferences
|
||||
this.interceptor = interceptor
|
||||
username = null
|
||||
token = null
|
||||
encryptionPassword = null
|
||||
httpClient = null
|
||||
httpUrl = null
|
||||
journalManager = null
|
||||
}
|
||||
|
||||
private constructor(
|
||||
context: Context,
|
||||
encryption: KeyStoreEncryption,
|
||||
preferences: Preferences,
|
||||
interceptor: DebugNetworkInterceptor,
|
||||
url: String?,
|
||||
username: String?,
|
||||
encryptionPassword: String,
|
||||
token: String,
|
||||
foreground: Boolean) {
|
||||
this.context = context
|
||||
this.encryption = encryption
|
||||
this.preferences = preferences
|
||||
this.interceptor = interceptor
|
||||
this.username = username
|
||||
this.encryptionPassword = encryptionPassword
|
||||
this.token = token
|
||||
this.foreground = foreground
|
||||
val customCertManager = CustomCertManager(context)
|
||||
customCertManager.appInForeground = foreground
|
||||
val hostnameVerifier = customCertManager.hostnameVerifier(null)
|
||||
val sslContext = SSLContext.getInstance("TLS")
|
||||
sslContext.init(null, arrayOf<TrustManager>(customCertManager), null)
|
||||
val builder = OkHttpClient()
|
||||
.newBuilder()
|
||||
.addNetworkInterceptor(TokenAuthenticator(null, token))
|
||||
.cookieJar(MemoryCookieStore())
|
||||
.followRedirects(false)
|
||||
.followSslRedirects(true)
|
||||
.sslSocketFactory(sslContext.socketFactory, customCertManager)
|
||||
.hostnameVerifier(hostnameVerifier)
|
||||
.connectTimeout(15, TimeUnit.SECONDS)
|
||||
.writeTimeout(30, TimeUnit.SECONDS)
|
||||
.readTimeout(120, TimeUnit.SECONDS)
|
||||
if (preferences.isFlipperEnabled) {
|
||||
interceptor.add(builder)
|
||||
}
|
||||
httpClient = builder.build()
|
||||
httpUrl = url?.toHttpUrlOrNull()
|
||||
journalManager = JournalManager(httpClient, httpUrl!!)
|
||||
}
|
||||
|
||||
@Throws(NoSuchAlgorithmException::class, KeyManagementException::class)
|
||||
fun forAccount(account: CaldavAccount): EteSyncClient {
|
||||
return forUrl(
|
||||
account.url,
|
||||
account.username,
|
||||
account.getEncryptionPassword(encryption),
|
||||
account.getPassword(encryption))
|
||||
}
|
||||
|
||||
@Throws(KeyManagementException::class, NoSuchAlgorithmException::class)
|
||||
fun forUrl(url: String?, username: String?, encryptionPassword: String, token: String): EteSyncClient {
|
||||
return EteSyncClient(
|
||||
context,
|
||||
encryption,
|
||||
preferences,
|
||||
interceptor,
|
||||
url,
|
||||
username,
|
||||
encryptionPassword,
|
||||
token,
|
||||
foreground)
|
||||
}
|
||||
|
||||
@Throws(IOException::class, Exceptions.HttpException::class)
|
||||
fun getToken(password: String?): String? {
|
||||
return JournalAuthenticator(httpClient!!, httpUrl!!).getAuthToken(username!!, password!!)
|
||||
}
|
||||
|
||||
@get:Throws(Exceptions.HttpException::class)
|
||||
val userInfo: UserInfoManager.UserInfo?
|
||||
get() {
|
||||
val userInfoManager = UserInfoManager(httpClient!!, httpUrl!!)
|
||||
return userInfoManager.fetch(username!!)
|
||||
}
|
||||
|
||||
@Throws(VersionTooNewException::class, IntegrityException::class)
|
||||
fun getCrypto(userInfo: UserInfoManager.UserInfo?, journal: Journal): CryptoManager {
|
||||
if (journal.key == null) {
|
||||
return CryptoManager(journal.version, encryptionPassword!!, journal.uid!!)
|
||||
}
|
||||
if (userInfo == null) {
|
||||
throw RuntimeException("Missing userInfo")
|
||||
}
|
||||
val cryptoManager = CryptoManager(userInfo.version!!.toInt(), encryptionPassword!!, "userInfo")
|
||||
val keyPair = AsymmetricKeyPair(userInfo.getContent(cryptoManager)!!, userInfo.pubkey!!)
|
||||
return CryptoManager(journal.version, keyPair, journal.key!!)
|
||||
}
|
||||
|
||||
private fun convertJournalToCollection(userInfo: UserInfoManager.UserInfo?, journal: Journal): CollectionInfo? {
|
||||
return try {
|
||||
val cryptoManager = getCrypto(userInfo, journal)
|
||||
journal.verify(cryptoManager)
|
||||
val collection = fromJson(journal.getContent(cryptoManager))
|
||||
collection.updateFromJournal(journal)
|
||||
collection
|
||||
} catch (e: IntegrityException) {
|
||||
Timber.e(e)
|
||||
null
|
||||
} catch (e: VersionTooNewException) {
|
||||
Timber.e(e)
|
||||
null
|
||||
}
|
||||
}
|
||||
|
||||
@Throws(Exceptions.HttpException::class)
|
||||
fun getCalendars(userInfo: UserInfoManager.UserInfo?): Map<Journal, CollectionInfo> {
|
||||
val result: MutableMap<Journal, CollectionInfo> = HashMap()
|
||||
for (journal in journalManager!!.list()) {
|
||||
val collection = convertJournalToCollection(userInfo, journal)
|
||||
if (collection != null) {
|
||||
if (TYPE_TASKS == collection.type) {
|
||||
Timber.v("Found %s", collection)
|
||||
result[journal] = collection
|
||||
} else {
|
||||
Timber.v("Ignoring %s", collection)
|
||||
}
|
||||
}
|
||||
}
|
||||
return result
|
||||
}
|
||||
|
||||
@Throws(IntegrityException::class, Exceptions.HttpException::class, VersionTooNewException::class)
|
||||
fun getSyncEntries(
|
||||
userInfo: UserInfoManager.UserInfo?,
|
||||
journal: Journal,
|
||||
calendar: CaldavCalendar,
|
||||
callback: (List<Pair<JournalEntryManager.Entry, SyncEntry>>) -> Unit) {
|
||||
val journalEntryManager = JournalEntryManager(httpClient!!, httpUrl!!, journal.uid!!)
|
||||
val crypto = getCrypto(userInfo, journal)
|
||||
var journalEntries: List<JournalEntryManager.Entry>
|
||||
do {
|
||||
journalEntries = journalEntryManager.list(crypto, calendar.ctag, MAX_FETCH)
|
||||
callback.invoke(journalEntries.map {
|
||||
Pair.create(it, SyncEntry.fromJournalEntry(crypto, it))
|
||||
})
|
||||
} while (journalEntries.size >= MAX_FETCH)
|
||||
}
|
||||
|
||||
@Throws(Exceptions.HttpException::class)
|
||||
fun pushEntries(journal: Journal, entries: List<JournalEntryManager.Entry>?, remoteCtag: String?) {
|
||||
var remoteCtag = remoteCtag
|
||||
val journalEntryManager = JournalEntryManager(httpClient!!, httpUrl!!, journal.uid!!)
|
||||
for (partition in Lists.partition(entries!!, MAX_PUSH)) {
|
||||
journalEntryManager.create(partition, remoteCtag)
|
||||
remoteCtag = partition[partition.size - 1].uid
|
||||
}
|
||||
}
|
||||
|
||||
fun setForeground() {
|
||||
foreground = true
|
||||
}
|
||||
|
||||
fun invalidateToken() {
|
||||
try {
|
||||
JournalAuthenticator(httpClient!!, httpUrl!!).invalidateAuthToken(token!!)
|
||||
} catch (e: Exception) {
|
||||
Timber.e(e)
|
||||
}
|
||||
}
|
||||
|
||||
@Throws(VersionTooNewException::class, IntegrityException::class, Exceptions.HttpException::class)
|
||||
fun makeCollection(name: String?, color: Int): String {
|
||||
val uid = Journal.genUid()
|
||||
val collectionInfo = CollectionInfo()
|
||||
collectionInfo.displayName = name
|
||||
collectionInfo.type = TYPE_TASKS
|
||||
collectionInfo.uid = uid
|
||||
collectionInfo.selected = true
|
||||
collectionInfo.color = if (color == 0) null else color
|
||||
val crypto = CryptoManager(collectionInfo.version, encryptionPassword!!, uid)
|
||||
journalManager!!.create(Journal(crypto, collectionInfo.toJson(), uid))
|
||||
return uid
|
||||
}
|
||||
|
||||
@Throws(VersionTooNewException::class, IntegrityException::class, Exceptions.HttpException::class)
|
||||
fun updateCollection(calendar: CaldavCalendar, name: String?, color: Int): String? {
|
||||
val uid = calendar.url
|
||||
val journal = journalManager!!.fetch(uid!!)
|
||||
val userInfo = userInfo
|
||||
val crypto = getCrypto(userInfo, journal)
|
||||
val collectionInfo = convertJournalToCollection(userInfo, journal)
|
||||
collectionInfo!!.displayName = name
|
||||
collectionInfo.color = if (color == 0) null else color
|
||||
journalManager.update(Journal(crypto, collectionInfo.toJson(), uid))
|
||||
return uid
|
||||
}
|
||||
|
||||
@Throws(Exceptions.HttpException::class)
|
||||
fun deleteCollection(calendar: CaldavCalendar) {
|
||||
journalManager!!.delete(Journal.fakeWithUid(calendar.url!!))
|
||||
}
|
||||
|
||||
@Throws(Exceptions.HttpException::class, VersionTooNewException::class, IntegrityException::class, IOException::class)
|
||||
fun createUserInfo(derivedKey: String?) {
|
||||
val cryptoManager = CryptoManager(CURRENT_VERSION, derivedKey!!, "userInfo")
|
||||
val userInfo: UserInfoManager.UserInfo = generate(cryptoManager, username!!)
|
||||
UserInfoManager(httpClient!!, httpUrl!!).create(userInfo)
|
||||
}
|
||||
|
||||
companion object {
|
||||
private const val TYPE_TASKS = "TASKS"
|
||||
private const val MAX_FETCH = 50
|
||||
private const val MAX_PUSH = 30
|
||||
}
|
||||
}
|
@ -1,268 +0,0 @@
|
||||
package org.tasks.etesync;
|
||||
|
||||
import static com.google.common.collect.FluentIterable.from;
|
||||
import static com.google.common.collect.Maps.newHashMap;
|
||||
import static com.google.common.collect.Sets.newHashSet;
|
||||
import static java.util.Collections.emptySet;
|
||||
import static org.tasks.Strings.isNullOrEmpty;
|
||||
|
||||
import android.content.Context;
|
||||
import androidx.core.util.Pair;
|
||||
import at.bitfire.ical4android.ICalendar;
|
||||
import com.etesync.journalmanager.Crypto.CryptoManager;
|
||||
import com.etesync.journalmanager.Exceptions;
|
||||
import com.etesync.journalmanager.Exceptions.HttpException;
|
||||
import com.etesync.journalmanager.Exceptions.IntegrityException;
|
||||
import com.etesync.journalmanager.Exceptions.VersionTooNewException;
|
||||
import com.etesync.journalmanager.JournalEntryManager;
|
||||
import com.etesync.journalmanager.JournalEntryManager.Entry;
|
||||
import com.etesync.journalmanager.JournalManager.Journal;
|
||||
import com.etesync.journalmanager.UserInfoManager.UserInfo;
|
||||
import com.etesync.journalmanager.model.CollectionInfo;
|
||||
import com.etesync.journalmanager.model.SyncEntry;
|
||||
import com.etesync.journalmanager.model.SyncEntry.Actions;
|
||||
import com.google.common.collect.Iterables;
|
||||
import com.todoroo.astrid.helper.UUIDHelper;
|
||||
import com.todoroo.astrid.service.TaskDeleter;
|
||||
import dagger.hilt.android.qualifiers.ApplicationContext;
|
||||
import java.security.KeyManagementException;
|
||||
import java.security.NoSuchAlgorithmException;
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Set;
|
||||
import javax.inject.Inject;
|
||||
import net.fortuna.ical4j.model.property.ProdId;
|
||||
import org.tasks.BuildConfig;
|
||||
import org.tasks.LocalBroadcastManager;
|
||||
import org.tasks.R;
|
||||
import org.tasks.billing.Inventory;
|
||||
import org.tasks.caldav.iCalendar;
|
||||
import org.tasks.data.CaldavAccount;
|
||||
import org.tasks.data.CaldavCalendar;
|
||||
import org.tasks.data.CaldavDaoBlocking;
|
||||
import org.tasks.data.CaldavTask;
|
||||
import org.tasks.data.CaldavTaskContainer;
|
||||
import timber.log.Timber;
|
||||
|
||||
public class EteSynchronizer {
|
||||
|
||||
static {
|
||||
ICalendar.Companion.setProdId(
|
||||
new ProdId("+//IDN tasks.org//android-" + BuildConfig.VERSION_CODE + "//EN"));
|
||||
}
|
||||
|
||||
private final CaldavDaoBlocking caldavDao;
|
||||
private final LocalBroadcastManager localBroadcastManager;
|
||||
private final TaskDeleter taskDeleter;
|
||||
private final Inventory inventory;
|
||||
private final EteSyncClient client;
|
||||
private final iCalendar iCal;
|
||||
private final Context context;
|
||||
|
||||
@Inject
|
||||
public EteSynchronizer(
|
||||
@ApplicationContext Context context,
|
||||
CaldavDaoBlocking caldavDao,
|
||||
LocalBroadcastManager localBroadcastManager,
|
||||
TaskDeleter taskDeleter,
|
||||
Inventory inventory,
|
||||
EteSyncClient client,
|
||||
iCalendar iCal) {
|
||||
this.context = context;
|
||||
this.caldavDao = caldavDao;
|
||||
this.localBroadcastManager = localBroadcastManager;
|
||||
this.taskDeleter = taskDeleter;
|
||||
this.inventory = inventory;
|
||||
this.client = client;
|
||||
this.iCal = iCal;
|
||||
}
|
||||
|
||||
public void sync(CaldavAccount account) {
|
||||
if (!inventory.hasPro()) {
|
||||
setError(account, context.getString(R.string.requires_pro_subscription));
|
||||
return;
|
||||
}
|
||||
if (isNullOrEmpty(account.getPassword())) {
|
||||
setError(account, context.getString(R.string.password_required));
|
||||
return;
|
||||
}
|
||||
if (isNullOrEmpty(account.getEncryptionKey())) {
|
||||
setError(account, context.getString(R.string.encryption_password_required));
|
||||
return;
|
||||
}
|
||||
try {
|
||||
synchronize(account);
|
||||
} catch (KeyManagementException
|
||||
| NoSuchAlgorithmException
|
||||
| HttpException
|
||||
| IntegrityException
|
||||
| VersionTooNewException e) {
|
||||
setError(account, e.getMessage());
|
||||
}
|
||||
}
|
||||
|
||||
private void synchronize(CaldavAccount account)
|
||||
throws KeyManagementException, NoSuchAlgorithmException, Exceptions.HttpException,
|
||||
IntegrityException, VersionTooNewException {
|
||||
EteSyncClient client = this.client.forAccount(account);
|
||||
UserInfo userInfo = client.getUserInfo();
|
||||
Map<Journal, CollectionInfo> resources = client.getCalendars(userInfo);
|
||||
|
||||
Set<String> uids = newHashSet(Iterables.transform(resources.values(), CollectionInfo::getUid));
|
||||
Timber.d("Found uids: %s", uids);
|
||||
for (CaldavCalendar calendar :
|
||||
caldavDao.findDeletedCalendars(account.getUuid(), new ArrayList<>(uids))) {
|
||||
taskDeleter.delete(calendar);
|
||||
}
|
||||
|
||||
for (Map.Entry<Journal, CollectionInfo> entry : resources.entrySet()) {
|
||||
CollectionInfo collection = entry.getValue();
|
||||
String uid = collection.getUid();
|
||||
|
||||
CaldavCalendar calendar = caldavDao.getCalendarByUrl(account.getUuid(), uid);
|
||||
Integer colorInt = collection.getColor();
|
||||
int color = colorInt == null ? 0 : colorInt;
|
||||
if (calendar == null) {
|
||||
calendar = new CaldavCalendar();
|
||||
calendar.setName(collection.getDisplayName());
|
||||
calendar.setAccount(account.getUuid());
|
||||
calendar.setUrl(collection.getUid());
|
||||
calendar.setUuid(UUIDHelper.newUUID());
|
||||
calendar.setColor(color);
|
||||
caldavDao.insert(calendar);
|
||||
} else {
|
||||
if (!calendar.getName().equals(collection.getDisplayName())
|
||||
|| calendar.getColor() != color) {
|
||||
calendar.setName(collection.getDisplayName());
|
||||
calendar.setColor(color);
|
||||
caldavDao.update(calendar);
|
||||
localBroadcastManager.broadcastRefreshList();
|
||||
}
|
||||
}
|
||||
sync(client, userInfo, calendar, entry.getKey());
|
||||
}
|
||||
setError(account, "");
|
||||
}
|
||||
|
||||
private void setError(CaldavAccount account, String message) {
|
||||
account.setError(message);
|
||||
caldavDao.update(account);
|
||||
localBroadcastManager.broadcastRefreshList();
|
||||
if (!isNullOrEmpty(message)) {
|
||||
Timber.e(message);
|
||||
}
|
||||
}
|
||||
|
||||
private void sync(
|
||||
EteSyncClient client, UserInfo userInfo, CaldavCalendar caldavCalendar, Journal journal)
|
||||
throws IntegrityException, Exceptions.HttpException, VersionTooNewException {
|
||||
Timber.d("sync(%s)", caldavCalendar);
|
||||
|
||||
Map<String, CaldavTaskContainer> localChanges = newHashMap();
|
||||
for (CaldavTaskContainer task : caldavDao.getCaldavTasksToPush(caldavCalendar.getUuid())) {
|
||||
localChanges.put(task.getRemoteId(), task);
|
||||
}
|
||||
|
||||
String remoteCtag = journal.getLastUid();
|
||||
if (isNullOrEmpty(remoteCtag) || !remoteCtag.equals(caldavCalendar.getCtag())) {
|
||||
Timber.v("Applying remote changes");
|
||||
client.getSyncEntries(
|
||||
userInfo,
|
||||
journal,
|
||||
caldavCalendar,
|
||||
syncEntries -> applyEntries(caldavCalendar, syncEntries, localChanges.keySet()));
|
||||
} else {
|
||||
Timber.d("%s up to date", caldavCalendar.getName());
|
||||
}
|
||||
|
||||
List<SyncEntry> changes = new ArrayList<>();
|
||||
for (CaldavTask task : caldavDao.getDeleted(caldavCalendar.getUuid())) {
|
||||
String vtodo = task.getVtodo();
|
||||
if (!isNullOrEmpty(vtodo)) {
|
||||
changes.add(new SyncEntry(vtodo, Actions.DELETE));
|
||||
}
|
||||
}
|
||||
|
||||
for (CaldavTaskContainer task : localChanges.values()) {
|
||||
String vtodo = task.getVtodo();
|
||||
boolean existingTask = !isNullOrEmpty(vtodo);
|
||||
|
||||
if (task.isDeleted()) {
|
||||
if (existingTask) {
|
||||
changes.add(new SyncEntry(vtodo, Actions.DELETE));
|
||||
}
|
||||
} else {
|
||||
changes.add(
|
||||
new SyncEntry(
|
||||
new String(iCal.toVtodo(task.getCaldavTask(), task.getTask())),
|
||||
existingTask ? Actions.CHANGE : Actions.ADD));
|
||||
}
|
||||
}
|
||||
|
||||
remoteCtag = caldavCalendar.getCtag();
|
||||
CryptoManager crypto = client.getCrypto(userInfo, journal);
|
||||
List<Pair<Entry, SyncEntry>> updates = new ArrayList<>();
|
||||
JournalEntryManager.Entry previous =
|
||||
isNullOrEmpty(remoteCtag) ? null : Entry.getFakeWithUid(remoteCtag);
|
||||
|
||||
for (SyncEntry syncEntry : changes) {
|
||||
Entry entry = new Entry();
|
||||
entry.update(crypto, syncEntry.toJson(), previous);
|
||||
updates.add(Pair.create(entry, syncEntry));
|
||||
previous = entry;
|
||||
}
|
||||
if (updates.size() > 0) {
|
||||
Timber.v("Pushing local changes");
|
||||
client.pushEntries(journal, from(updates).transform(p -> p.first).toList(), remoteCtag);
|
||||
Timber.v("Applying local changes");
|
||||
applyEntries(caldavCalendar, updates, emptySet());
|
||||
}
|
||||
|
||||
Timber.d("UPDATE %s", caldavCalendar);
|
||||
|
||||
caldavDao.update(caldavCalendar);
|
||||
caldavDao.updateParents(caldavCalendar.getUuid());
|
||||
localBroadcastManager.broadcastRefresh();
|
||||
}
|
||||
|
||||
private void applyEntries(
|
||||
CaldavCalendar caldavCalendar, List<Pair<Entry, SyncEntry>> syncEntries, Set<String> dirty) {
|
||||
for (Pair<Entry, SyncEntry> entry : syncEntries) {
|
||||
Entry journalEntry = entry.first;
|
||||
SyncEntry syncEntry = entry.second;
|
||||
Actions action = syncEntry.getAction();
|
||||
String vtodo = syncEntry.getContent();
|
||||
Timber.v("%s: %s", action, vtodo);
|
||||
at.bitfire.ical4android.Task task = iCalendar.Companion.fromVtodo(vtodo);
|
||||
if (task == null) {
|
||||
continue;
|
||||
}
|
||||
String remoteId = task.getUid();
|
||||
CaldavTask caldavTask = caldavDao.getTaskByRemoteId(caldavCalendar.getUuid(), remoteId);
|
||||
switch (action) {
|
||||
case ADD:
|
||||
case CHANGE:
|
||||
if (dirty.contains(remoteId)) {
|
||||
caldavTask.setVtodo(vtodo);
|
||||
caldavDao.update(caldavTask);
|
||||
} else {
|
||||
iCal.fromVtodo(caldavCalendar, caldavTask, task, vtodo, null, null);
|
||||
}
|
||||
break;
|
||||
case DELETE:
|
||||
dirty.remove(remoteId);
|
||||
if (caldavTask != null) {
|
||||
if (caldavTask.isDeleted()) {
|
||||
caldavDao.delete(caldavTask);
|
||||
} else {
|
||||
taskDeleter.delete(caldavTask.getTask());
|
||||
}
|
||||
}
|
||||
break;
|
||||
}
|
||||
caldavCalendar.setCtag(journalEntry.getUid());
|
||||
caldavDao.update(caldavCalendar);
|
||||
}
|
||||
}
|
||||
}
|
@ -0,0 +1,221 @@
|
||||
package org.tasks.etesync
|
||||
|
||||
import android.content.Context
|
||||
import androidx.core.util.Pair
|
||||
import at.bitfire.ical4android.ICalendar.Companion.prodId
|
||||
import com.etesync.journalmanager.Exceptions
|
||||
import com.etesync.journalmanager.Exceptions.IntegrityException
|
||||
import com.etesync.journalmanager.Exceptions.VersionTooNewException
|
||||
import com.etesync.journalmanager.JournalEntryManager
|
||||
import com.etesync.journalmanager.JournalEntryManager.Entry.Companion.getFakeWithUid
|
||||
import com.etesync.journalmanager.JournalManager.Journal
|
||||
import com.etesync.journalmanager.UserInfoManager
|
||||
import com.etesync.journalmanager.model.SyncEntry
|
||||
import com.todoroo.astrid.helper.UUIDHelper
|
||||
import com.todoroo.astrid.service.TaskDeleter
|
||||
import dagger.hilt.android.qualifiers.ApplicationContext
|
||||
import net.fortuna.ical4j.model.property.ProdId
|
||||
import org.tasks.BuildConfig
|
||||
import org.tasks.LocalBroadcastManager
|
||||
import org.tasks.R
|
||||
import org.tasks.Strings.isNullOrEmpty
|
||||
import org.tasks.billing.Inventory
|
||||
import org.tasks.caldav.iCalendar
|
||||
import org.tasks.caldav.iCalendar.Companion.fromVtodo
|
||||
import org.tasks.data.CaldavAccount
|
||||
import org.tasks.data.CaldavCalendar
|
||||
import org.tasks.data.CaldavDaoBlocking
|
||||
import org.tasks.data.CaldavTaskContainer
|
||||
import timber.log.Timber
|
||||
import java.security.KeyManagementException
|
||||
import java.security.NoSuchAlgorithmException
|
||||
import java.util.*
|
||||
import javax.inject.Inject
|
||||
import kotlin.collections.HashSet
|
||||
|
||||
class EteSynchronizer @Inject constructor(
|
||||
@param:ApplicationContext private val context: Context,
|
||||
private val caldavDao: CaldavDaoBlocking,
|
||||
private val localBroadcastManager: LocalBroadcastManager,
|
||||
private val taskDeleter: TaskDeleter,
|
||||
private val inventory: Inventory,
|
||||
private val client: EteSyncClient,
|
||||
private val iCal: iCalendar) {
|
||||
companion object {
|
||||
init {
|
||||
prodId = ProdId("+//IDN tasks.org//android-" + BuildConfig.VERSION_CODE + "//EN")
|
||||
}
|
||||
}
|
||||
|
||||
fun sync(account: CaldavAccount) {
|
||||
if (!inventory.hasPro()) {
|
||||
setError(account, context.getString(R.string.requires_pro_subscription))
|
||||
return
|
||||
}
|
||||
if (isNullOrEmpty(account.password)) {
|
||||
setError(account, context.getString(R.string.password_required))
|
||||
return
|
||||
}
|
||||
if (isNullOrEmpty(account.encryptionKey)) {
|
||||
setError(account, context.getString(R.string.encryption_password_required))
|
||||
return
|
||||
}
|
||||
try {
|
||||
synchronize(account)
|
||||
} catch (e: KeyManagementException) {
|
||||
setError(account, e.message)
|
||||
} catch (e: NoSuchAlgorithmException) {
|
||||
setError(account, e.message)
|
||||
} catch (e: Exceptions.HttpException) {
|
||||
setError(account, e.message)
|
||||
} catch (e: IntegrityException) {
|
||||
setError(account, e.message)
|
||||
} catch (e: VersionTooNewException) {
|
||||
setError(account, e.message)
|
||||
}
|
||||
}
|
||||
|
||||
@Throws(KeyManagementException::class, NoSuchAlgorithmException::class, Exceptions.HttpException::class, IntegrityException::class, VersionTooNewException::class)
|
||||
private fun synchronize(account: CaldavAccount) {
|
||||
val client = client.forAccount(account)
|
||||
val userInfo = client.userInfo
|
||||
val resources = client.getCalendars(userInfo)
|
||||
val uids: Set<String> = resources.values.mapNotNull { it.uid }.toHashSet()
|
||||
Timber.d("Found uids: %s", uids)
|
||||
for (calendar in caldavDao.findDeletedCalendars(account.uuid!!, uids.toList())) {
|
||||
taskDeleter.delete(calendar)
|
||||
}
|
||||
for ((key, collection) in resources) {
|
||||
val uid = collection.uid
|
||||
var calendar = caldavDao.getCalendarByUrl(account.uuid!!, uid!!)
|
||||
val colorInt = collection.color
|
||||
val color = colorInt ?: 0
|
||||
if (calendar == null) {
|
||||
calendar = CaldavCalendar()
|
||||
calendar.name = collection.displayName
|
||||
calendar.account = account.uuid
|
||||
calendar.url = collection.uid
|
||||
calendar.uuid = UUIDHelper.newUUID()
|
||||
calendar.color = color
|
||||
caldavDao.insert(calendar)
|
||||
} else {
|
||||
if (calendar.name != collection.displayName
|
||||
|| calendar.color != color) {
|
||||
calendar.name = collection.displayName
|
||||
calendar.color = color
|
||||
caldavDao.update(calendar)
|
||||
localBroadcastManager.broadcastRefreshList()
|
||||
}
|
||||
}
|
||||
sync(client, userInfo, calendar, key)
|
||||
}
|
||||
setError(account, "")
|
||||
}
|
||||
|
||||
private fun setError(account: CaldavAccount, message: String?) {
|
||||
account.error = message
|
||||
caldavDao.update(account)
|
||||
localBroadcastManager.broadcastRefreshList()
|
||||
if (!isNullOrEmpty(message)) {
|
||||
Timber.e(message)
|
||||
}
|
||||
}
|
||||
|
||||
@Throws(IntegrityException::class, Exceptions.HttpException::class, VersionTooNewException::class)
|
||||
private fun sync(
|
||||
client: EteSyncClient, userInfo: UserInfoManager.UserInfo, caldavCalendar: CaldavCalendar, journal: Journal) {
|
||||
Timber.d("sync(%s)", caldavCalendar)
|
||||
val localChanges = HashMap<String?, CaldavTaskContainer>()
|
||||
for (task in caldavDao.getCaldavTasksToPush(caldavCalendar.uuid!!)) {
|
||||
localChanges[task.remoteId] = task
|
||||
}
|
||||
var remoteCtag = journal.lastUid
|
||||
if (isNullOrEmpty(remoteCtag) || remoteCtag != caldavCalendar.ctag) {
|
||||
Timber.v("Applying remote changes")
|
||||
client.getSyncEntries(
|
||||
userInfo,
|
||||
journal,
|
||||
caldavCalendar
|
||||
) { syncEntries: List<Pair<JournalEntryManager.Entry, SyncEntry>> -> applyEntries(caldavCalendar, syncEntries, localChanges.keys) }
|
||||
} else {
|
||||
Timber.d("%s up to date", caldavCalendar.name)
|
||||
}
|
||||
val changes: MutableList<SyncEntry> = ArrayList()
|
||||
for (task in caldavDao.getDeleted(caldavCalendar.uuid!!)) {
|
||||
val vtodo = task.vtodo
|
||||
if (!isNullOrEmpty(vtodo)) {
|
||||
changes.add(SyncEntry(vtodo!!, SyncEntry.Actions.DELETE))
|
||||
}
|
||||
}
|
||||
for (task in localChanges.values) {
|
||||
val vtodo = task.vtodo
|
||||
val existingTask = !isNullOrEmpty(vtodo)
|
||||
if (task.isDeleted) {
|
||||
if (existingTask) {
|
||||
changes.add(SyncEntry(vtodo!!, SyncEntry.Actions.DELETE))
|
||||
}
|
||||
} else {
|
||||
changes.add(
|
||||
SyncEntry(
|
||||
String(iCal.toVtodo(task.caldavTask, task.task)),
|
||||
if (existingTask) SyncEntry.Actions.CHANGE else SyncEntry.Actions.ADD))
|
||||
}
|
||||
}
|
||||
remoteCtag = caldavCalendar.ctag
|
||||
val crypto = client.getCrypto(userInfo, journal)
|
||||
val updates: MutableList<Pair<JournalEntryManager.Entry, SyncEntry>> = ArrayList()
|
||||
var previous: JournalEntryManager.Entry? = if (isNullOrEmpty(remoteCtag)) null else getFakeWithUid(remoteCtag!!)
|
||||
for (syncEntry in changes) {
|
||||
val entry = JournalEntryManager.Entry()
|
||||
entry.update(crypto, syncEntry.toJson(), previous)
|
||||
updates.add(Pair.create(entry, syncEntry))
|
||||
previous = entry
|
||||
}
|
||||
if (updates.size > 0) {
|
||||
Timber.v("Pushing local changes")
|
||||
client.pushEntries(journal, updates.map { it.first }, remoteCtag)
|
||||
Timber.v("Applying local changes")
|
||||
applyEntries(caldavCalendar, updates, HashSet())
|
||||
}
|
||||
Timber.d("UPDATE %s", caldavCalendar)
|
||||
caldavDao.update(caldavCalendar)
|
||||
caldavDao.updateParents(caldavCalendar.uuid!!)
|
||||
localBroadcastManager.broadcastRefresh()
|
||||
}
|
||||
|
||||
private fun applyEntries(
|
||||
caldavCalendar: CaldavCalendar,
|
||||
syncEntries: List<Pair<JournalEntryManager.Entry, SyncEntry>>,
|
||||
dirty: MutableSet<String?>) {
|
||||
for (entry in syncEntries) {
|
||||
val journalEntry = entry.first
|
||||
val syncEntry = entry.second
|
||||
val action = syncEntry!!.action
|
||||
val vtodo = syncEntry.content
|
||||
Timber.v("%s: %s", action, vtodo)
|
||||
val task = fromVtodo(vtodo) ?: continue
|
||||
val remoteId = task.uid
|
||||
val caldavTask = caldavDao.getTaskByRemoteId(caldavCalendar.uuid!!, remoteId!!)
|
||||
when (action) {
|
||||
SyncEntry.Actions.ADD, SyncEntry.Actions.CHANGE -> if (dirty.contains(remoteId)) {
|
||||
caldavTask!!.vtodo = vtodo
|
||||
caldavDao.update(caldavTask)
|
||||
} else {
|
||||
iCal.fromVtodo(caldavCalendar, caldavTask, task, vtodo, null, null)
|
||||
}
|
||||
SyncEntry.Actions.DELETE -> {
|
||||
dirty.remove(remoteId)
|
||||
if (caldavTask != null) {
|
||||
if (caldavTask.isDeleted()) {
|
||||
caldavDao.delete(caldavTask)
|
||||
} else {
|
||||
taskDeleter.delete(caldavTask.task)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
caldavCalendar.ctag = journalEntry!!.uid
|
||||
caldavDao.update(caldavCalendar)
|
||||
}
|
||||
}
|
||||
}
|
Loading…
Reference in New Issue