improved filter data sync

This commit is contained in:
Mariotaku Lee 2017-09-16 23:33:00 +08:00
parent 6cc278d667
commit db272c33db
No known key found for this signature in database
GPG Key ID: 15C10F89D7C33535
9 changed files with 232 additions and 109 deletions

View File

@ -1 +1 @@
3dc89e56116e8ce97933fcc36164cd73f7901ad8
1cd45204bcb489d29b7d8d5a9b30f522d1f61ba1

View File

@ -7,28 +7,22 @@ import java.util.*
* Created by mariotaku on 16/7/30.
*/
fun String?.toLongOr(def: Long): Long {
try {
return this?.toLong() ?: def
} catch (e: NumberFormatException) {
return def
}
fun String?.toLongOr(def: Long) = try {
this?.toLong() ?: def
} catch (e: NumberFormatException) {
def
}
fun String?.toIntOr(def: Int): Int {
try {
return this?.toInt() ?: def
} catch (e: NumberFormatException) {
return def
}
fun String?.toIntOr(def: Int) = try {
this?.toInt() ?: def
} catch (e: NumberFormatException) {
def
}
fun String?.toDoubleOr(def: Double): Double {
try {
return this?.toDouble() ?: def
} catch (e: NumberFormatException) {
return def
}
fun String?.toDoubleOr(def: Double) = try {
this?.toDouble() ?: def
} catch (e: NumberFormatException) {
def
}
fun Int.coerceInOr(range: ClosedRange<Int>, def: Int): Int {

View File

@ -9,6 +9,7 @@ import org.mariotaku.sqliteqb.library.Expression
import org.mariotaku.twidere.extension.queryAll
import org.mariotaku.twidere.model.FiltersData
import org.mariotaku.twidere.model.UserKey
import org.mariotaku.twidere.model.filter.FilterScopeStringMap
import org.mariotaku.twidere.provider.TwidereDataStore.Filters
import org.mariotaku.twidere.util.content.ContentResolverUtils
import org.xmlpull.v1.XmlPullParser
@ -74,27 +75,39 @@ private const val TAG_USER = "user"
private const val ATTR_SCREEN_NAME = "screenName"
private const val ATTR_NAME = "name"
private const val ATTR_KEY = "key"
private const val ATTR_SCOPE = "scope"
private const val ATTR_USER_KEY = "userKey"
@Throws(IOException::class)
fun FiltersData.serialize(serializer: XmlSerializer) {
@Throws(IOException::class)
fun FiltersData.BaseItem.serialize(name: String, writer: XmlSerializer) {
writer.startTag(null, name)
if (scope != 0) {
writer.attribute(null, ATTR_SCOPE, FilterScopeStringMap.toString(scope))
}
if (userKey != null) {
writer.attribute(null, ATTR_USER_KEY, userKey.toString())
}
writer.text(value)
writer.endTag(null, name)
}
serializer.startDocument("utf-8", true)
serializer.startTag(null, TAG_FILTERS)
this.users?.forEach { user ->
fun FiltersData.UserItem.serialize(serializer: XmlSerializer) {
serializer.startTag(null, TAG_USER)
serializer.attribute(null, ATTR_KEY, user.userKey.toString())
serializer.attribute(null, ATTR_NAME, user.name)
serializer.attribute(null, ATTR_SCREEN_NAME, user.screenName)
serializer.attribute(null, ATTR_KEY, userKey.toString())
serializer.attribute(null, ATTR_NAME, name)
serializer.attribute(null, ATTR_SCREEN_NAME, screenName)
if (scope != 0) {
serializer.attribute(null, ATTR_SCOPE, FilterScopeStringMap.toString(scope))
}
serializer.endTag(null, TAG_USER)
}
serializer.startDocument("utf-8", true)
serializer.startTag(null, TAG_FILTERS)
this.users?.forEach { it.serialize(serializer) }
this.keywords?.forEach { it.serialize(TAG_KEYWORD, serializer) }
this.sources?.forEach { it.serialize(TAG_SOURCE, serializer) }
this.links?.forEach { it.serialize(TAG_LINK, serializer) }
@ -102,13 +115,23 @@ fun FiltersData.serialize(serializer: XmlSerializer) {
serializer.endDocument()
}
@Throws(IOException::class)
fun FiltersData.parse(parser: XmlPullParser) {
fun parseBaseItem(parser: XmlPullParser): FiltersData.BaseItem {
val item = FiltersData.BaseItem()
item.userKey = parser.getAttributeValue(null, ATTR_USER_KEY)?.let(UserKey::valueOf)
item.scope = parser.getAttributeValue(null, ATTR_SCOPE)?.let(FilterScopeStringMap::fromString) ?: 0
return item
}
fun parseUserItem(parser: XmlPullParser): FiltersData.UserItem? {
val item = FiltersData.UserItem()
item.name = parser.getAttributeValue(null, ATTR_NAME) ?: return null
item.screenName = parser.getAttributeValue(null, ATTR_SCREEN_NAME) ?: return null
item.userKey = parser.getAttributeValue(null, ATTR_KEY)?.let(UserKey::valueOf) ?: return null
item.scope = parser.getAttributeValue(null, ATTR_SCOPE)?.let(FilterScopeStringMap::fromString) ?: 0
return item
}
@ -122,7 +145,7 @@ fun FiltersData.parse(parser: XmlPullParser) {
XmlPullParser.START_TAG -> {
stack.push(when (parser.name) {
TAG_USER -> parseUserItem(parser)
TAG_KEYWORD, TAG_SOURCE, TAG_LINK -> FiltersData.BaseItem()
TAG_KEYWORD, TAG_SOURCE, TAG_LINK -> parseBaseItem(parser)
else -> null
})
}

View File

@ -24,8 +24,6 @@ import android.app.Dialog
import android.content.DialogInterface
import android.net.Uri
import android.os.Bundle
import android.os.Parcel
import android.os.Parcelable
import android.support.v7.app.AlertDialog
import android.view.View
import android.view.WindowManager
@ -33,7 +31,6 @@ import android.widget.CheckBox
import android.widget.Toast
import kotlinx.android.synthetic.main.dialog_filter_rule_editor.*
import org.mariotaku.ktextension.ContentValues
import org.mariotaku.ktextension.contains
import org.mariotaku.ktextension.set
import org.mariotaku.ktextension.string
import org.mariotaku.sqliteqb.library.Expression
@ -46,6 +43,7 @@ import org.mariotaku.twidere.extension.*
import org.mariotaku.twidere.extension.util.isAdvancedFiltersEnabled
import org.mariotaku.twidere.fragment.BaseDialogFragment
import org.mariotaku.twidere.fragment.ExtraFeaturesIntroductionDialogFragment
import org.mariotaku.twidere.model.filter.FilterScopesHolder
import org.mariotaku.twidere.model.util.AccountUtils
import org.mariotaku.twidere.provider.TwidereDataStore.Filters
import org.mariotaku.twidere.util.premium.ExtraFeaturesService
@ -61,8 +59,8 @@ class AddEditItemFragment : BaseDialogFragment(), DialogInterface.OnClickListene
private val defaultValue: String?
get() = arguments.getString(EXTRA_VALUE)
private val defaultScope: FilterScopes
get() = FilterScopes(filterMasks, arguments.getInt(EXTRA_SCOPE, FilterScope.DEFAULT))
private val defaultScopes: FilterScopesHolder
get() = FilterScopesHolder(filterMasks, arguments.getInt(EXTRA_SCOPE, FilterScope.DEFAULT))
private val filterMasks: Int
get() = when (contentUri) {
@ -82,14 +80,14 @@ class AddEditItemFragment : BaseDialogFragment(), DialogInterface.OnClickListene
editText.string = value
}
private var Dialog.scope: FilterScopes?
get() = defaultScope.also {
private var Dialog.scopes: FilterScopesHolder?
get() = defaultScopes.also {
if (extraFeaturesService.isAdvancedFiltersEnabled) {
saveScopes(it)
}
}
set(value) {
loadScopes(value ?: defaultScope)
loadScopes(value ?: defaultScopes)
}
private var Dialog.advancedExpanded: Boolean
@ -103,7 +101,7 @@ class AddEditItemFragment : BaseDialogFragment(), DialogInterface.OnClickListene
dialog as AlertDialog
when (which) {
DialogInterface.BUTTON_POSITIVE -> {
val scope = dialog.scope ?: return
val scope = dialog.scopes ?: return
if (!canEditValue) {
saveScopeOnly(scope)
} else {
@ -160,12 +158,12 @@ class AddEditItemFragment : BaseDialogFragment(), DialogInterface.OnClickListene
if (savedInstanceState == null) {
value = defaultValue
scope = defaultScope
scopes = defaultScopes
advancedExpanded = false
editText.setSelection(editText.length().coerceAtLeast(0))
} else {
value = savedInstanceState.getString(EXTRA_VALUE)
scope = savedInstanceState.getParcelable(EXTRA_SCOPE)
scopes = savedInstanceState.getParcelable(EXTRA_SCOPE)
}
}
return dialog
@ -174,10 +172,10 @@ class AddEditItemFragment : BaseDialogFragment(), DialogInterface.OnClickListene
override fun onSaveInstanceState(outState: Bundle) {
super.onSaveInstanceState(outState)
outState.putString(EXTRA_VALUE, dialog.value)
outState.putParcelable(EXTRA_SCOPE, dialog.scope)
outState.putParcelable(EXTRA_SCOPE, dialog.scopes)
}
private fun Dialog.saveScopes(scopes: FilterScopes) {
private fun Dialog.saveScopes(scopes: FilterScopesHolder) {
targetText.saveScope(scopes, FilterScope.TARGET_TEXT)
targetName.saveScope(scopes, FilterScope.TARGET_NAME)
targetDescription.saveScope(scopes, FilterScope.TARGET_DESCRIPTION)
@ -188,7 +186,7 @@ class AddEditItemFragment : BaseDialogFragment(), DialogInterface.OnClickListene
scopeOther.saveScope(scopes, FilterScope.UGC_TIMELINE)
}
private fun Dialog.loadScopes(scopes: FilterScopes) {
private fun Dialog.loadScopes(scopes: FilterScopesHolder) {
labelTarget.setVisible(scopes.hasMask(FilterScope.MASK_TARGET))
targetText.loadScope(scopes, FilterScope.TARGET_TEXT)
targetName.loadScope(scopes, FilterScope.TARGET_NAME)
@ -202,12 +200,12 @@ class AddEditItemFragment : BaseDialogFragment(), DialogInterface.OnClickListene
scopeOther.loadScope(scopes, FilterScope.UGC_TIMELINE)
}
private fun CheckBox.saveScope(scopes: FilterScopes, scope: Int) {
private fun CheckBox.saveScope(scopes: FilterScopesHolder, scope: Int) {
if (!isEnabled || visibility != View.VISIBLE) return
scopes[scope] = isChecked
}
private fun CheckBox.loadScope(scopes: FilterScopes, scope: Int) {
private fun CheckBox.loadScope(scopes: FilterScopesHolder, scope: Int) {
if (scope in scopes) {
isEnabled = true
visibility = View.VISIBLE
@ -219,7 +217,7 @@ class AddEditItemFragment : BaseDialogFragment(), DialogInterface.OnClickListene
isChecked = scopes[scope]
}
private fun saveScopeOnly(scopes: FilterScopes) {
private fun saveScopeOnly(scopes: FilterScopesHolder) {
val resolver = context.contentResolver
val contentUri = contentUri
val rowId = rowId
@ -233,7 +231,7 @@ class AddEditItemFragment : BaseDialogFragment(), DialogInterface.OnClickListene
resolver.update(contentUri, values, idWhere, null)
}
private fun saveItem(value: String, scopes: FilterScopes) {
private fun saveItem(value: String, scopes: FilterScopesHolder) {
val resolver = context.contentResolver
val uri = contentUri
val rowId = rowId
@ -258,59 +256,6 @@ class AddEditItemFragment : BaseDialogFragment(), DialogInterface.OnClickListene
}
}
class FilterScopes(val masks: Int, value: Int = 0) : Parcelable {
var value: Int = 0
get() = field and masks
private set(v) {
field = v and masks
}
constructor(parcel: Parcel) : this(parcel.readInt(), parcel.readInt())
init {
this.value = value
}
operator fun set(scope: Int, enabled: Boolean) {
value = if (enabled) {
value or scope
} else {
value and scope.inv()
}
}
operator fun get(scope: Int): Boolean {
return scope in value
}
operator fun contains(scope: Int): Boolean {
return scope in masks
}
override fun writeToParcel(parcel: Parcel, flags: Int) {
parcel.writeInt(masks)
parcel.writeInt(value)
}
override fun describeContents(): Int {
return 0
}
fun hasMask(mask: Int): Boolean = masks and mask != 0
companion object CREATOR : Parcelable.Creator<FilterScopes> {
override fun createFromParcel(parcel: Parcel): FilterScopes {
return FilterScopes(parcel)
}
override fun newArray(size: Int): Array<FilterScopes?> {
return arrayOfNulls(size)
}
}
}
companion object {
private const val REQUEST_CHANGE_SCOPE_PURCHASE = 101
}

View File

@ -7,11 +7,16 @@ import android.view.Menu
import android.view.MenuInflater
import android.view.MenuItem
import com.squareup.otto.Subscribe
import nl.komponents.kovenant.combine.and
import nl.komponents.kovenant.ui.alwaysUi
import org.mariotaku.ktextension.weak
import org.mariotaku.twidere.R
import org.mariotaku.twidere.TwidereConstants.SYNC_PREFERENCES_NAME
import org.mariotaku.twidere.constant.dataSyncProviderInfoKey
import org.mariotaku.twidere.extension.applyTheme
import org.mariotaku.twidere.extension.dismissProgressDialog
import org.mariotaku.twidere.extension.onShow
import org.mariotaku.twidere.extension.showProgressDialog
import org.mariotaku.twidere.fragment.BaseDialogFragment
import org.mariotaku.twidere.fragment.BasePreferenceFragment
import org.mariotaku.twidere.util.TaskServiceRunner
@ -75,10 +80,16 @@ class SyncSettingsFragment : BasePreferenceFragment() {
private fun cleanupAndDisconnect() {
val providerInfo = kPreferences[dataSyncProviderInfoKey] ?: return
syncController.cleanupSyncCache(providerInfo)
kPreferences[dataSyncProviderInfoKey] = null
DataSyncProvider.Factory.notifyUpdate(context)
activity?.finish()
val weakThis = weak()
val task = showProgressDialog("cleanup_sync_cache").
and(syncController.cleanupSyncCache(providerInfo))
task.alwaysUi {
val f = weakThis.get() ?: return@alwaysUi
f.dismissProgressDialog("cleanup_sync_cache")
f.kPreferences[dataSyncProviderInfoKey] = null
DataSyncProvider.Factory.notifyUpdate(f.context)
f.activity?.finish()
}
}
class DisconnectSyncConfirmDialogFragment : BaseDialogFragment() {

View File

@ -0,0 +1,71 @@
/*
* Twidere - Twitter client for Android
*
* Copyright (C) 2012-2017 Mariotaku Lee <mariotaku.lee@gmail.com>
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
package org.mariotaku.twidere.model.filter
import org.mariotaku.ktextension.contains
import org.mariotaku.twidere.annotation.FilterScope
/**
* Created by mariotaku on 2017/9/16.
*/
object FilterScopeStringMap {
private val mappings: Array<Mapping>
init {
mappings = arrayOf(
Mapping(FilterScope.ALL, "all"),
Mapping(FilterScope.DEFAULT, "default"),
Mapping(FilterScope.TARGET_NAME, "target_name"),
Mapping(FilterScope.TARGET_DESCRIPTION, "target_description"),
Mapping(FilterScope.TARGET_TEXT, "target_text"),
Mapping(FilterScope.HOME, "home"),
Mapping(FilterScope.INTERACTIONS, "interactions"),
Mapping(FilterScope.MESSAGES, "messages"),
Mapping(FilterScope.SEARCH_RESULTS, "search_results"),
Mapping(FilterScope.LIST_GROUP_TIMELINE, "list_group_timeline"),
Mapping(FilterScope.FAVORITES, "favorites"),
Mapping(FilterScope.USER_TIMELINE, "user_timeline"),
Mapping(FilterScope.PUBLIC_TIMELINE, "public_timeline")
)
}
fun toString(scope: Int): String {
val result = StringBuilder()
var tmp = scope
while (tmp != 0) {
val mapping = mappings.firstOrNull { (v, _) -> v in tmp } ?: break
if (!result.isEmpty()) {
result.append('|')
}
result.append(mapping.name)
tmp = tmp and mapping.scope.inv()
}
return result.toString()
}
fun fromString(str: String): Int {
return str.split('|').mapNotNull { seg ->
mappings.firstOrNull { (_, n) -> n == seg }
}.fold(0) { acc, mapping -> acc or mapping.scope }
}
private data class Mapping(val scope: Int, val name: String)
}

View File

@ -0,0 +1,77 @@
/*
* Twidere - Twitter client for Android
*
* Copyright (C) 2012-2017 Mariotaku Lee <mariotaku.lee@gmail.com>
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
package org.mariotaku.twidere.model.filter
import android.os.Parcel
import android.os.Parcelable
import org.mariotaku.ktextension.contains
class FilterScopesHolder(val masks: Int, value: Int = 0) : Parcelable {
var value: Int = 0
get() = field and masks
private set(v) {
field = v and masks
}
constructor(parcel: Parcel) : this(parcel.readInt(), parcel.readInt())
init {
this.value = value
}
operator fun set(scope: Int, enabled: Boolean) {
value = if (enabled) {
value or scope
} else {
value and scope.inv()
}
}
operator fun get(scope: Int): Boolean {
return scope in value
}
operator fun contains(scope: Int): Boolean {
return scope in masks
}
override fun writeToParcel(parcel: Parcel, flags: Int) {
parcel.writeInt(masks)
parcel.writeInt(value)
}
override fun describeContents(): Int {
return 0
}
fun hasMask(mask: Int): Boolean = masks and mask != 0
companion object CREATOR : Parcelable.Creator<FilterScopesHolder> {
override fun createFromParcel(parcel: Parcel): FilterScopesHolder {
return FilterScopesHolder(parcel)
}
override fun newArray(size: Int): Array<FilterScopesHolder?> {
return arrayOfNulls(size)
}
}
}

View File

@ -1,6 +1,8 @@
package org.mariotaku.twidere.util.sync
import android.content.Context
import nl.komponents.kovenant.Promise
import java.lang.Exception
/**
* Created by mariotaku on 2017/1/3.
@ -13,7 +15,7 @@ abstract class SyncController(val context: Context) {
syncProvider.newSyncTaskRunner(context).performSync()
}
fun cleanupSyncCache(syncProvider: DataSyncProvider) {
syncProvider.newSyncTaskRunner(context).cleanupSyncCache()
fun cleanupSyncCache(syncProvider: DataSyncProvider): Promise<Boolean, Exception> {
return syncProvider.newSyncTaskRunner(context).cleanupSyncCache()
}
}

View File

@ -2,10 +2,12 @@ package org.mariotaku.twidere.util.sync
import android.content.Context
import com.squareup.otto.Bus
import nl.komponents.kovenant.Promise
import nl.komponents.kovenant.task
import org.mariotaku.twidere.util.TaskServiceRunner
import org.mariotaku.twidere.util.UserColorNameManager
import org.mariotaku.twidere.util.dagger.GeneralComponent
import java.lang.Exception
import java.util.*
import javax.inject.Inject
@ -46,11 +48,9 @@ abstract class SyncTaskRunner(val context: Context) {
}
fun cleanupSyncCache() {
task {
context.syncDataDir.listFiles { file, _ -> file.isFile }?.forEach { file ->
file.delete()
}
fun cleanupSyncCache(): Promise<Boolean, Exception> {
return task {
context.syncDataDir.deleteRecursively()
}
}