2016-12-31 18:13:21 +01:00
|
|
|
package org.mariotaku.twidere.util.sync
|
|
|
|
|
|
|
|
import android.content.Context
|
|
|
|
import android.support.v4.util.LongSparseArray
|
|
|
|
import org.mariotaku.ktextension.map
|
|
|
|
import org.mariotaku.ktextension.set
|
2017-03-05 09:08:09 +01:00
|
|
|
import org.mariotaku.library.objectcursor.ObjectCursor
|
2016-12-31 18:13:21 +01:00
|
|
|
import org.mariotaku.sqliteqb.library.Expression
|
|
|
|
import org.mariotaku.twidere.extension.model.filename
|
|
|
|
import org.mariotaku.twidere.extension.model.unique_id_non_null
|
|
|
|
import org.mariotaku.twidere.model.Draft
|
|
|
|
import org.mariotaku.twidere.provider.TwidereDataStore.Drafts
|
2017-01-26 16:15:05 +01:00
|
|
|
import org.mariotaku.twidere.util.DebugLog
|
2016-12-31 18:13:21 +01:00
|
|
|
import org.mariotaku.twidere.util.content.ContentResolverUtils
|
|
|
|
import java.io.File
|
|
|
|
import java.io.FileNotFoundException
|
|
|
|
import java.io.IOException
|
|
|
|
import java.util.*
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Created by mariotaku on 2016/12/31.
|
|
|
|
*/
|
|
|
|
|
2017-01-06 14:38:05 +01:00
|
|
|
abstract class FileBasedDraftsSyncAction<RemoteFileInfo>(val context: Context) : ISyncAction {
|
2017-01-03 16:37:24 +01:00
|
|
|
|
|
|
|
@Throws(IOException::class)
|
2017-01-06 14:38:05 +01:00
|
|
|
override fun execute(): Boolean {
|
2017-01-26 16:15:05 +01:00
|
|
|
DebugLog.d(LOGTAG_SYNC, "Begin syncing drafts")
|
2017-01-21 17:33:20 +01:00
|
|
|
|
|
|
|
if (!setup()) {
|
|
|
|
return false
|
|
|
|
}
|
|
|
|
|
2016-12-31 18:13:21 +01:00
|
|
|
val syncDataDir: File = context.syncDataDir.mkdirIfNotExists() ?: return false
|
|
|
|
val snapshotsListFile = File(syncDataDir, "draft_ids.list")
|
|
|
|
|
|
|
|
// Read last synced id
|
|
|
|
val snapshotIds: List<String> = try {
|
|
|
|
snapshotsListFile.readLines()
|
|
|
|
} catch (e: FileNotFoundException) {
|
|
|
|
emptyList<String>()
|
|
|
|
}
|
|
|
|
|
|
|
|
val localDrafts = run {
|
|
|
|
val cur = context.contentResolver.query(Drafts.CONTENT_URI, Drafts.COLUMNS, null, null, null)!!
|
|
|
|
try {
|
2017-03-05 09:08:09 +01:00
|
|
|
val indices = ObjectCursor.indicesFrom(cur, Draft::class.java)
|
|
|
|
return@run cur.map(indices)
|
2016-12-31 18:13:21 +01:00
|
|
|
} finally {
|
|
|
|
cur.close()
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
// Ids of draft removed locally, we will delete these from remote storage
|
|
|
|
val localRemovedIds = snapshotIds.filter { id -> localDrafts.none { draft -> draft.unique_id_non_null == id } }
|
|
|
|
|
|
|
|
|
|
|
|
val remoteDrafts = listRemoteDrafts()
|
|
|
|
// Download remote items
|
|
|
|
val downloadRemoteInfoList = ArrayList<RemoteFileInfo>()
|
|
|
|
// Remote remote items: snapshot has but database doesn't have
|
|
|
|
val removeRemoteInfoList = ArrayList<RemoteFileInfo>()
|
|
|
|
// Update local items using remote
|
|
|
|
val updateLocalInfoList = LongSparseArray<RemoteFileInfo>()
|
|
|
|
// Remove local items: snapshot has but remote doesn't have
|
|
|
|
val removeLocalIdsList = snapshotIds.filter { snapshotId ->
|
|
|
|
remoteDrafts.none { it.draftFileName == "$snapshotId.eml" }
|
|
|
|
}
|
|
|
|
|
|
|
|
val uploadLocalList = ArrayList<Draft>()
|
|
|
|
|
|
|
|
remoteDrafts.forEach { remoteDraft ->
|
|
|
|
val localDraft = localDrafts.find { it.filename == remoteDraft.draftFileName }
|
|
|
|
if (remoteDraft.draftFileName.substringBefore(".eml") in localRemovedIds) {
|
|
|
|
// Local removed, remove remote
|
|
|
|
removeRemoteInfoList.add(remoteDraft)
|
|
|
|
} else if (localDraft == null) {
|
|
|
|
// Local doesn't exist, download remote
|
|
|
|
downloadRemoteInfoList.add(remoteDraft)
|
|
|
|
} else if (remoteDraft.draftTimestamp - localDraft.timestamp > 1000) {
|
|
|
|
// Local is older, update from remote
|
2017-01-22 07:53:06 +01:00
|
|
|
localDraft.remote_extras = remoteDraft.draftRemoteExtras
|
2016-12-31 18:13:21 +01:00
|
|
|
updateLocalInfoList[localDraft._id] = remoteDraft
|
|
|
|
} else if (localDraft.timestamp - remoteDraft.draftTimestamp > 1000) {
|
|
|
|
// Local is newer, upload local
|
2017-01-22 07:53:06 +01:00
|
|
|
localDraft.remote_extras = remoteDraft.draftRemoteExtras
|
2016-12-31 18:13:21 +01:00
|
|
|
uploadLocalList.add(localDraft)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
// Deal with local drafts that remote doesn't have
|
|
|
|
localDrafts.filterTo(uploadLocalList) { localDraft ->
|
|
|
|
if (remoteDrafts.any { it.draftFileName == localDraft.filename }) {
|
|
|
|
return@filterTo false
|
|
|
|
}
|
|
|
|
if (downloadRemoteInfoList.any { it.draftFileName == localDraft.filename }) {
|
|
|
|
return@filterTo false
|
|
|
|
}
|
|
|
|
if (removeRemoteInfoList.any { it.draftFileName == localDraft.filename }) {
|
|
|
|
return@filterTo false
|
|
|
|
}
|
|
|
|
if (localDraft.unique_id_non_null in removeLocalIdsList) {
|
|
|
|
return@filterTo false
|
|
|
|
}
|
|
|
|
if ((0 until updateLocalInfoList.size()).any { updateLocalInfoList.valueAt(it).draftFileName == localDraft.filename }) {
|
|
|
|
return@filterTo false
|
|
|
|
}
|
|
|
|
return@filterTo true
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// Upload local items
|
2017-01-22 07:53:06 +01:00
|
|
|
if (uploadLocalList.isNotEmpty()) {
|
2017-01-26 16:15:05 +01:00
|
|
|
val fileList = uploadLocalList.joinToString(",") { it.filename }
|
|
|
|
DebugLog.d(LOGTAG_SYNC, "Uploading local drafts $fileList")
|
2017-01-22 07:53:06 +01:00
|
|
|
uploadDrafts(uploadLocalList)
|
2016-12-31 18:13:21 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
// Download remote items
|
2017-01-22 07:53:06 +01:00
|
|
|
if (downloadRemoteInfoList.isNotEmpty()) {
|
2017-01-26 16:15:05 +01:00
|
|
|
val fileList = downloadRemoteInfoList.joinToString(",") { it.draftFileName }
|
|
|
|
DebugLog.d(LOGTAG_SYNC, "Downloading remote drafts $fileList")
|
2017-01-22 07:53:06 +01:00
|
|
|
ContentResolverUtils.bulkInsert(context.contentResolver, Drafts.CONTENT_URI,
|
2017-03-05 09:08:09 +01:00
|
|
|
downloadDrafts(downloadRemoteInfoList).map { ObjectCursor.valuesCreatorFrom(Draft::class.java).create(it) })
|
2016-12-31 18:13:21 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
// Update local items
|
2017-01-22 07:53:06 +01:00
|
|
|
if (updateLocalInfoList.size() > 0) {
|
2017-01-26 16:15:05 +01:00
|
|
|
val fileList = (0 until updateLocalInfoList.size()).joinToString(",") { updateLocalInfoList.valueAt(it).draftFileName }
|
|
|
|
DebugLog.d(LOGTAG_SYNC, "Updating local drafts $fileList")
|
2017-03-05 09:08:09 +01:00
|
|
|
val creator = ObjectCursor.valuesCreatorFrom(Draft::class.java)
|
2017-01-22 07:53:06 +01:00
|
|
|
for (index in 0 until updateLocalInfoList.size()) {
|
|
|
|
val draft = Draft()
|
|
|
|
if (draft.loadFromRemote(updateLocalInfoList.valueAt(index))) {
|
|
|
|
val where = Expression.equalsArgs(Drafts._ID).sql
|
|
|
|
val whereArgs = arrayOf(updateLocalInfoList.keyAt(index).toString())
|
2017-03-05 09:08:09 +01:00
|
|
|
context.contentResolver.update(Drafts.CONTENT_URI, creator.create(draft), where, whereArgs)
|
2017-01-22 07:53:06 +01:00
|
|
|
}
|
2016-12-31 18:13:21 +01:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
// Remove local items
|
2017-01-22 07:53:06 +01:00
|
|
|
if (removeLocalIdsList.isNotEmpty()) {
|
2017-01-26 16:15:05 +01:00
|
|
|
val fileList = removeLocalIdsList.joinToString(",") { "$it.eml" }
|
|
|
|
DebugLog.d(LOGTAG_SYNC, "Removing local drafts $fileList")
|
2017-01-22 07:53:06 +01:00
|
|
|
ContentResolverUtils.bulkDelete(context.contentResolver, Drafts.CONTENT_URI,
|
2017-02-13 14:33:45 +01:00
|
|
|
Drafts.UNIQUE_ID, false, removeLocalIdsList, null, null)
|
2016-12-31 18:13:21 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
// Remove remote items
|
2017-01-22 07:53:06 +01:00
|
|
|
if (removeRemoteInfoList.isNotEmpty()) {
|
2017-01-26 16:15:05 +01:00
|
|
|
val fileList = removeRemoteInfoList.joinToString(",") { it.draftFileName }
|
|
|
|
DebugLog.d(LOGTAG_SYNC, "Removing remote drafts $fileList")
|
2017-01-22 07:53:06 +01:00
|
|
|
removeDrafts(removeRemoteInfoList)
|
2016-12-31 18:13:21 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
snapshotsListFile.writer().use { writer ->
|
|
|
|
val cur = context.contentResolver.query(Drafts.CONTENT_URI, Drafts.COLUMNS, null, null, null)!!
|
|
|
|
try {
|
2017-03-05 09:08:09 +01:00
|
|
|
val indices = ObjectCursor.indicesFrom(cur, Draft::class.java)
|
|
|
|
cur.map(indices).map { it.unique_id_non_null }.forEach { line ->
|
2016-12-31 18:13:21 +01:00
|
|
|
writer.write(line)
|
|
|
|
writer.write("\n")
|
|
|
|
}
|
|
|
|
} finally {
|
|
|
|
cur.close()
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2017-01-26 16:15:05 +01:00
|
|
|
DebugLog.d(LOGTAG_SYNC, "Finished syncing drafts")
|
2016-12-31 18:13:21 +01:00
|
|
|
return true
|
|
|
|
}
|
|
|
|
|
|
|
|
@Throws(IOException::class)
|
|
|
|
abstract fun listRemoteDrafts(): List<RemoteFileInfo>
|
|
|
|
|
|
|
|
@Throws(IOException::class)
|
|
|
|
open fun downloadDrafts(list: List<RemoteFileInfo>): List<Draft> {
|
|
|
|
val result = ArrayList<Draft>()
|
|
|
|
list.forEach {
|
|
|
|
val draft = Draft()
|
|
|
|
if (draft.loadFromRemote(it)) {
|
|
|
|
result.add(draft)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
return result
|
|
|
|
}
|
|
|
|
|
|
|
|
@Throws(IOException::class)
|
|
|
|
open fun removeDrafts(list: List<RemoteFileInfo>): Boolean {
|
|
|
|
var result = false
|
|
|
|
list.forEach { item ->
|
|
|
|
result = result or removeDraft(item)
|
|
|
|
}
|
|
|
|
return result
|
|
|
|
}
|
|
|
|
|
|
|
|
@Throws(IOException::class)
|
|
|
|
open fun uploadDrafts(list: List<Draft>): Boolean {
|
|
|
|
var result = false
|
|
|
|
list.forEach { item ->
|
|
|
|
result = result or (item.saveToRemote() != null)
|
|
|
|
}
|
|
|
|
return result
|
|
|
|
}
|
|
|
|
|
|
|
|
@Throws(IOException::class)
|
|
|
|
abstract fun Draft.loadFromRemote(info: RemoteFileInfo): Boolean
|
|
|
|
|
|
|
|
@Throws(IOException::class)
|
|
|
|
abstract fun removeDraft(info: RemoteFileInfo): Boolean
|
|
|
|
|
|
|
|
@Throws(IOException::class)
|
|
|
|
abstract fun Draft.saveToRemote(): RemoteFileInfo?
|
|
|
|
|
|
|
|
abstract val RemoteFileInfo.draftFileName: String
|
|
|
|
abstract val RemoteFileInfo.draftTimestamp: Long
|
2017-01-22 07:53:06 +01:00
|
|
|
open val RemoteFileInfo.draftRemoteExtras: String? get() = null
|
2017-01-21 17:33:20 +01:00
|
|
|
|
|
|
|
@Throws(IOException::class)
|
|
|
|
open fun setup(): Boolean = true
|
2016-12-31 18:13:21 +01:00
|
|
|
}
|