trying to implement timeline sync
This commit is contained in:
parent
fc4626f948
commit
8626109153
|
@ -58,6 +58,7 @@ public interface TwidereConstants extends SharedPreferenceConstants, IntentConst
|
|||
String PERMISSION_PREFERENCES_NAME = "app_permissions";
|
||||
String SYNC_PREFERENCES_NAME = "sync_preferences";
|
||||
String TIMELINE_POSITIONS_PREFERENCES_NAME = "timeline_positions";
|
||||
String TIMELINE_SYNC_CACHE_PREFERENCES_NAME = "timeline_sync_cache";
|
||||
String KEYBOARD_SHORTCUTS_PREFERENCES_NAME = "keyboard_shortcuts_preferences";
|
||||
String ETAG_CACHE_PREFERENCES_NAME = "etag_cache";
|
||||
String ETAG_MASTODON_APPS_PREFERENCES_NAME = "mastodon_apps";
|
||||
|
|
|
@ -30,7 +30,6 @@ import org.mariotaku.twidere.extension.model.hasInvalidAccount
|
|||
import org.mariotaku.twidere.model.util.AccountUtils
|
||||
import org.mariotaku.twidere.util.DeviceUtils
|
||||
import org.mariotaku.twidere.util.StrictModeUtils
|
||||
import org.mariotaku.twidere.util.Utils
|
||||
|
||||
open class MainActivity : BaseActivity() {
|
||||
|
||||
|
|
|
@ -32,7 +32,10 @@ import org.mariotaku.twidere.constant.profileImageStyleKey
|
|||
import org.mariotaku.twidere.constant.showAbsoluteTimeKey
|
||||
import org.mariotaku.twidere.constant.textSizeKey
|
||||
import org.mariotaku.twidere.model.DefaultFeatures
|
||||
import org.mariotaku.twidere.util.*
|
||||
import org.mariotaku.twidere.util.AsyncTwitterWrapper
|
||||
import org.mariotaku.twidere.util.MultiSelectManager
|
||||
import org.mariotaku.twidere.util.ReadStateManager
|
||||
import org.mariotaku.twidere.util.UserColorNameManager
|
||||
import org.mariotaku.twidere.util.dagger.GeneralComponent
|
||||
import javax.inject.Inject
|
||||
|
||||
|
|
|
@ -15,8 +15,6 @@ import org.mariotaku.twidere.annotation.AccountType
|
|||
import org.mariotaku.twidere.model.ParcelableUser
|
||||
import org.mariotaku.twidere.model.UserKey
|
||||
import org.mariotaku.twidere.model.account.AccountExtras
|
||||
import org.mariotaku.twidere.model.account.MastodonAccountExtras
|
||||
import org.mariotaku.twidere.model.account.StatusNetAccountExtras
|
||||
import org.mariotaku.twidere.model.account.TwitterAccountExtras
|
||||
import org.mariotaku.twidere.model.account.cred.*
|
||||
import org.mariotaku.twidere.model.util.AccountUtils
|
||||
|
|
|
@ -240,8 +240,17 @@ abstract class AbsActivitiesFragment protected constructor() :
|
|||
lastReadViewTop = layoutManager.findViewByPosition(lastReadPosition)?.top ?: 0
|
||||
loadMore = activityRange.endInclusive in 0..lastVisibleItemPosition
|
||||
} else if (rememberPosition && readPositionTag != null) {
|
||||
lastReadId = readStateManager.getPosition(readPositionTag)
|
||||
lastReadViewTop = 0
|
||||
val syncManager = timelineSyncManager
|
||||
val positionTag = this.readPositionTag
|
||||
val syncTag = this.timelineSyncTag
|
||||
val currentTag = this.currentReadPositionTag
|
||||
|
||||
if (syncManager != null && positionTag != null && syncTag != null) {
|
||||
lastReadId = syncManager.peekPosition(positionTag, syncTag)
|
||||
}
|
||||
if (lastReadId <= 0 && currentTag != null) {
|
||||
lastReadId = readStateManager.getPosition(currentTag)
|
||||
}
|
||||
}
|
||||
|
||||
adapter.setData(data)
|
||||
|
|
|
@ -263,7 +263,7 @@ abstract class AbsStatusesFragment : AbsContentListRecyclerViewFragment<Parcelab
|
|||
*/
|
||||
override fun onLoadFinished(loader: Loader<List<ParcelableStatus>?>, data: List<ParcelableStatus>?) {
|
||||
val rememberPosition = preferences[rememberPositionKey]
|
||||
val readPositionTag = currentReadPositionTag
|
||||
val currentReadPositionTag = currentReadPositionTag
|
||||
val readFromBottom = preferences[readFromBottomKey]
|
||||
val firstLoad = adapterData.isNullOrEmpty()
|
||||
|
||||
|
@ -292,8 +292,19 @@ abstract class AbsStatusesFragment : AbsContentListRecyclerViewFragment<Parcelab
|
|||
}
|
||||
lastReadViewTop = layoutManager.findViewByPosition(lastReadPosition)?.top ?: 0
|
||||
loadMore = statusRange.endInclusive in 0..lastVisibleItemPosition
|
||||
} else if (rememberPosition && readPositionTag != null) {
|
||||
lastReadId = readStateManager.getPosition(readPositionTag)
|
||||
} else if (rememberPosition) {
|
||||
val syncManager = timelineSyncManager
|
||||
val positionTag = this.readPositionTag
|
||||
val syncTag = this.timelineSyncTag
|
||||
val currentTag = this.currentReadPositionTag
|
||||
|
||||
if (syncManager != null && positionTag != null && syncTag != null) {
|
||||
lastReadId = syncManager.peekPosition(positionTag, syncTag)
|
||||
}
|
||||
if (lastReadId <= 0 && currentTag != null) {
|
||||
lastReadId = readStateManager.getPosition(currentTag)
|
||||
}
|
||||
|
||||
lastReadViewTop = 0
|
||||
}
|
||||
// 2. Change adapter data
|
||||
|
|
|
@ -26,7 +26,10 @@ import com.squareup.otto.Bus
|
|||
import com.twitter.Validator
|
||||
import org.mariotaku.kpreferences.KPreferences
|
||||
import org.mariotaku.restfu.http.RestHttpClient
|
||||
import org.mariotaku.twidere.util.*
|
||||
import org.mariotaku.twidere.util.AsyncTwitterWrapper
|
||||
import org.mariotaku.twidere.util.DebugModeUtils
|
||||
import org.mariotaku.twidere.util.KeyboardShortcutsHandler
|
||||
import org.mariotaku.twidere.util.UserColorNameManager
|
||||
import org.mariotaku.twidere.util.dagger.GeneralComponent
|
||||
import org.mariotaku.twidere.util.premium.ExtraFeaturesService
|
||||
import javax.inject.Inject
|
||||
|
|
|
@ -35,13 +35,11 @@ import android.widget.*
|
|||
import android.widget.AbsListView.MultiChoiceModeListener
|
||||
import android.widget.CompoundButton.OnCheckedChangeListener
|
||||
import kotlinx.android.synthetic.main.fragment_content_listview.*
|
||||
import org.mariotaku.twidere.Constants
|
||||
import org.mariotaku.twidere.R
|
||||
import org.mariotaku.twidere.TwidereConstants.HOST_MAPPING_PREFERENCES_NAME
|
||||
import org.mariotaku.twidere.adapter.ArrayAdapter
|
||||
import org.mariotaku.twidere.extension.applyTheme
|
||||
import org.mariotaku.twidere.util.ParseUtils
|
||||
import org.mariotaku.twidere.util.SharedPreferencesWrapper
|
||||
|
||||
class HostMappingsListFragment : AbsContentListViewFragment<HostMappingsListFragment.HostMappingAdapter>(),
|
||||
AdapterView.OnItemClickListener, MultiChoiceModeListener, OnSharedPreferenceChangeListener {
|
||||
|
|
|
@ -20,8 +20,6 @@
|
|||
package org.mariotaku.twidere.loader.statuses
|
||||
|
||||
import android.content.Context
|
||||
import org.mariotaku.twidere.loader.statuses.ParcelableStatusesLoader
|
||||
|
||||
import org.mariotaku.twidere.model.ListResponse
|
||||
import org.mariotaku.twidere.model.ParcelableStatus
|
||||
|
||||
|
|
|
@ -22,7 +22,6 @@ package org.mariotaku.twidere.loader.statuses
|
|||
import android.content.Context
|
||||
import android.os.Bundle
|
||||
import org.mariotaku.twidere.constant.IntentConstants.EXTRA_STATUSES
|
||||
import org.mariotaku.twidere.loader.statuses.ParcelableStatusesLoader
|
||||
import org.mariotaku.twidere.model.ListResponse
|
||||
import org.mariotaku.twidere.model.ParcelableStatus
|
||||
import java.util.*
|
||||
|
|
|
@ -20,13 +20,11 @@
|
|||
package org.mariotaku.twidere.loader.userlists
|
||||
|
||||
import android.content.Context
|
||||
|
||||
import org.mariotaku.microblog.library.MicroBlog
|
||||
import org.mariotaku.microblog.library.MicroBlogException
|
||||
import org.mariotaku.microblog.library.twitter.model.PageableResponseList
|
||||
import org.mariotaku.microblog.library.twitter.model.Paging
|
||||
import org.mariotaku.microblog.library.twitter.model.UserList
|
||||
import org.mariotaku.twidere.loader.userlists.BaseUserListsLoader
|
||||
import org.mariotaku.twidere.model.ParcelableUserList
|
||||
import org.mariotaku.twidere.model.UserKey
|
||||
|
||||
|
|
|
@ -24,7 +24,6 @@ import org.mariotaku.microblog.library.MicroBlog
|
|||
import org.mariotaku.microblog.library.MicroBlogException
|
||||
import org.mariotaku.microblog.library.twitter.model.Paging
|
||||
import org.mariotaku.microblog.library.twitter.model.UserList
|
||||
import org.mariotaku.twidere.loader.userlists.BaseUserListsLoader
|
||||
import org.mariotaku.twidere.model.ParcelableUserList
|
||||
import org.mariotaku.twidere.model.UserKey
|
||||
|
||||
|
|
|
@ -20,13 +20,11 @@
|
|||
package org.mariotaku.twidere.loader.userlists
|
||||
|
||||
import android.content.Context
|
||||
|
||||
import org.mariotaku.microblog.library.MicroBlog
|
||||
import org.mariotaku.microblog.library.MicroBlogException
|
||||
import org.mariotaku.microblog.library.twitter.model.PageableResponseList
|
||||
import org.mariotaku.microblog.library.twitter.model.Paging
|
||||
import org.mariotaku.microblog.library.twitter.model.UserList
|
||||
import org.mariotaku.twidere.loader.userlists.BaseUserListsLoader
|
||||
import org.mariotaku.twidere.model.ParcelableUserList
|
||||
import org.mariotaku.twidere.model.UserKey
|
||||
|
||||
|
|
|
@ -16,4 +16,6 @@ interface RefreshTaskParam {
|
|||
|
||||
val shouldAbort: Boolean get() = false
|
||||
|
||||
val isBackground: Boolean get() = false
|
||||
|
||||
}
|
||||
|
|
|
@ -10,12 +10,10 @@ import org.mariotaku.library.objectcursor.ObjectCursor
|
|||
import org.mariotaku.microblog.library.twitter.model.User
|
||||
import org.mariotaku.sqliteqb.library.Expression
|
||||
import org.mariotaku.twidere.extension.model.api.toParcelable
|
||||
import org.mariotaku.twidere.extension.model.api.toParcelable
|
||||
import org.mariotaku.twidere.model.ParcelableRelationship
|
||||
import org.mariotaku.twidere.model.ParcelableUser
|
||||
import org.mariotaku.twidere.model.UserKey
|
||||
import org.mariotaku.twidere.model.util.ParcelableRelationshipUtils
|
||||
import org.mariotaku.twidere.model.util.ParcelableUserUtils
|
||||
import org.mariotaku.twidere.model.util.UserKeyUtils
|
||||
import org.mariotaku.twidere.provider.TwidereDataStore.CachedRelationships
|
||||
import org.mariotaku.twidere.provider.TwidereDataStore.CachedUsers
|
||||
|
|
|
@ -94,7 +94,7 @@ class GetActivitiesAboutMeTask(context: Context) : GetActivitiesTask(context) {
|
|||
}
|
||||
|
||||
|
||||
override fun setLocalReadPosition(accountKeys: Array<UserKey>, saveReadPosition: BooleanArray) {
|
||||
override fun syncFetchReadPosition(accountKeys: Array<UserKey>) {
|
||||
val manager = timelineSyncManagerFactory.get() ?: return
|
||||
val tag = InteractionsTimelineFragment.getTimelineSyncTag(accountKeys)
|
||||
try {
|
||||
|
|
|
@ -9,7 +9,6 @@ import org.mariotaku.kpreferences.get
|
|||
import org.mariotaku.microblog.library.MicroBlogException
|
||||
import org.mariotaku.microblog.library.twitter.model.Paging
|
||||
import org.mariotaku.sqliteqb.library.Expression
|
||||
import org.mariotaku.twidere.R
|
||||
import org.mariotaku.twidere.TwidereConstants.LOGTAG
|
||||
import org.mariotaku.twidere.TwidereConstants.QUERY_PARAM_NOTIFY_CHANGE
|
||||
import org.mariotaku.twidere.constant.loadItemLimitKey
|
||||
|
@ -42,9 +41,8 @@ abstract class GetActivitiesTask(
|
|||
|
||||
override fun doLongOperation(param: RefreshTaskParam): List<GetTimelineResult?> {
|
||||
if (param.shouldAbort) return emptyList()
|
||||
val accountKeys = param.accountKeys
|
||||
val accountKeys = param.accountKeys.takeIf { it.isNotEmpty() } ?: return emptyList()
|
||||
val loadItemLimit = preferences[loadItemLimitKey]
|
||||
val saveReadPosition = BooleanArray(accountKeys.size)
|
||||
val result = accountKeys.mapIndexed { i, accountKey ->
|
||||
val noItemsBefore = DataStoreUtils.getActivitiesCount(context, contentUri, accountKey) <= 0
|
||||
val credentials = AccountUtils.getAccountDetails(AccountManager.get(context), accountKey,
|
||||
|
@ -61,7 +59,6 @@ abstract class GetActivitiesTask(
|
|||
paging.sinceId(sinceId)
|
||||
if (maxId == null) {
|
||||
paging.setLatestResults(true)
|
||||
saveReadPosition[i] = true
|
||||
}
|
||||
}
|
||||
// We should delete old activities has intersection with new items
|
||||
|
@ -69,9 +66,6 @@ abstract class GetActivitiesTask(
|
|||
val activities = getActivities(credentials, paging)
|
||||
val storeResult = storeActivities(credentials, activities, sinceId, maxId,
|
||||
loadItemLimit, noItemsBefore, false)
|
||||
if (saveReadPosition[i]) {
|
||||
|
||||
}
|
||||
errorInfoStore.remove(errorInfoKey, accountKey)
|
||||
if (storeResult != 0) {
|
||||
throw GetStatusesTask.GetTimelineException(storeResult)
|
||||
|
@ -89,7 +83,9 @@ abstract class GetActivitiesTask(
|
|||
}
|
||||
return@mapIndexed GetTimelineResult(null)
|
||||
}
|
||||
setLocalReadPosition(accountKeys, saveReadPosition)
|
||||
if (param.isBackground) {
|
||||
syncFetchReadPosition(accountKeys)
|
||||
}
|
||||
return result
|
||||
}
|
||||
|
||||
|
@ -108,7 +104,7 @@ abstract class GetActivitiesTask(
|
|||
@Throws(MicroBlogException::class)
|
||||
protected abstract fun getActivities(account: AccountDetails, paging: Paging): List<ParcelableActivity>
|
||||
|
||||
protected abstract fun setLocalReadPosition(accountKeys: Array<UserKey>, saveReadPosition: BooleanArray)
|
||||
protected abstract fun syncFetchReadPosition(accountKeys: Array<UserKey>)
|
||||
|
||||
private fun storeActivities(details: AccountDetails, activities: List<ParcelableActivity>,
|
||||
sinceId: String?, maxId: String?, loadItemLimit: Int, noItemsBefore: Boolean,
|
||||
|
|
|
@ -31,12 +31,12 @@ import org.mariotaku.twidere.annotation.ReadPositionTag
|
|||
import org.mariotaku.twidere.extension.model.api.mastodon.toParcelable
|
||||
import org.mariotaku.twidere.extension.model.api.toParcelable
|
||||
import org.mariotaku.twidere.extension.model.newMicroBlogInstance
|
||||
import org.mariotaku.twidere.fragment.HomeTimelineFragment
|
||||
import org.mariotaku.twidere.model.AccountDetails
|
||||
import org.mariotaku.twidere.model.ParcelableStatus
|
||||
import org.mariotaku.twidere.model.UserKey
|
||||
import org.mariotaku.twidere.provider.TwidereDataStore.Statuses
|
||||
import org.mariotaku.twidere.util.ErrorInfoStore
|
||||
import org.mariotaku.twidere.util.Utils
|
||||
import java.io.IOException
|
||||
|
||||
/**
|
||||
|
@ -70,13 +70,13 @@ class GetHomeTimelineTask(context: Context) : GetStatusesTask(context) {
|
|||
}
|
||||
}
|
||||
|
||||
override fun setLocalReadPosition(accountKey: UserKey, details: AccountDetails) {
|
||||
val syncManager = timelineSyncManagerFactory.get() ?: return
|
||||
override fun syncFetchReadPosition(accountKeys: Array<UserKey>) {
|
||||
val manager = timelineSyncManagerFactory.get() ?: return
|
||||
val tag = HomeTimelineFragment.getTimelineSyncTag(accountKeys)
|
||||
try {
|
||||
val tag = Utils.getReadPositionTagWithAccount(ReadPositionTag.HOME_TIMELINE, accountKey)
|
||||
syncManager.blockingGetPosition(ReadPositionTag.HOME_TIMELINE, tag)
|
||||
}catch (e: IOException) {
|
||||
|
||||
manager.blockingGetPosition(ReadPositionTag.ACTIVITIES_ABOUT_ME, tag)
|
||||
} catch (e: IOException) {
|
||||
return
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -49,10 +49,9 @@ abstract class GetStatusesTask(
|
|||
|
||||
override fun doLongOperation(param: RefreshTaskParam): List<GetTimelineResult?> {
|
||||
if (param.shouldAbort) return emptyList()
|
||||
val accountKeys = param.accountKeys
|
||||
val accountKeys = param.accountKeys.takeIf { it.isNotEmpty() } ?: return emptyList()
|
||||
val loadItemLimit = preferences[loadItemLimitKey]
|
||||
var saveReadPosition = false
|
||||
return accountKeys.mapIndexed { i, accountKey ->
|
||||
val result = accountKeys.mapIndexed { i, accountKey ->
|
||||
val account = AccountUtils.getAccountDetails(AccountManager.get(context),
|
||||
accountKey, true) ?: return@mapIndexed null
|
||||
try {
|
||||
|
@ -77,14 +76,10 @@ abstract class GetStatusesTask(
|
|||
if (maxId == null) {
|
||||
paging.setLatestResults(true)
|
||||
}
|
||||
saveReadPosition = true
|
||||
}
|
||||
val statuses = getStatuses(account, paging)
|
||||
val storeResult = storeStatus(account, statuses, sinceId, maxId, sinceSortId,
|
||||
maxSortId, loadItemLimit, false)
|
||||
if (saveReadPosition) {
|
||||
setLocalReadPosition(accountKey, account)
|
||||
}
|
||||
// TODO cache related data and preload
|
||||
errorInfoStore.remove(errorInfoKey, accountKey.id)
|
||||
if (storeResult != 0) {
|
||||
|
@ -103,6 +98,10 @@ abstract class GetStatusesTask(
|
|||
return@mapIndexed GetTimelineResult(e)
|
||||
}
|
||||
}
|
||||
if (param.isBackground) {
|
||||
syncFetchReadPosition(accountKeys)
|
||||
}
|
||||
return result
|
||||
}
|
||||
|
||||
override fun afterExecute(handler: ((Boolean) -> Unit)?, result: List<GetTimelineResult?>) {
|
||||
|
@ -119,7 +118,7 @@ abstract class GetStatusesTask(
|
|||
@Throws(MicroBlogException::class)
|
||||
protected abstract fun getStatuses(account: AccountDetails, paging: Paging): List<ParcelableStatus>
|
||||
|
||||
protected abstract fun setLocalReadPosition(accountKey: UserKey, details: AccountDetails)
|
||||
protected abstract fun syncFetchReadPosition(accountKeys: Array<UserKey>)
|
||||
|
||||
private fun storeStatus(account: AccountDetails, statuses: List<ParcelableStatus>,
|
||||
sinceId: String?, maxId: String?, sinceSortId: Long, maxSortId: Long,
|
||||
|
|
|
@ -30,7 +30,6 @@ import org.mariotaku.sqliteqb.library.Expression
|
|||
import org.mariotaku.twidere.R
|
||||
import org.mariotaku.twidere.annotation.AccountType
|
||||
import org.mariotaku.twidere.extension.model.api.toParcelable
|
||||
import org.mariotaku.twidere.extension.model.api.toParcelable
|
||||
import org.mariotaku.twidere.extension.model.isOfficial
|
||||
import org.mariotaku.twidere.extension.model.newMicroBlogInstance
|
||||
import org.mariotaku.twidere.model.AccountDetails
|
||||
|
|
|
@ -42,7 +42,6 @@ import org.mariotaku.twidere.constant.homeRefreshDirectMessagesKey
|
|||
import org.mariotaku.twidere.constant.homeRefreshMentionsKey
|
||||
import org.mariotaku.twidere.constant.homeRefreshSavedSearchesKey
|
||||
import org.mariotaku.twidere.constant.nameFirstKey
|
||||
import org.mariotaku.twidere.extension.model.api.mastodon.toParcelable
|
||||
import org.mariotaku.twidere.extension.model.api.microblog.toParcelable
|
||||
import org.mariotaku.twidere.extension.model.newMicroBlogInstance
|
||||
import org.mariotaku.twidere.model.*
|
||||
|
|
|
@ -3,7 +3,9 @@ package org.mariotaku.twidere.util
|
|||
import android.content.Context
|
||||
import android.content.SharedPreferences
|
||||
import android.net.Uri
|
||||
import android.os.Build
|
||||
import android.util.Base64
|
||||
import android.util.Log
|
||||
import okhttp3.*
|
||||
import org.mariotaku.kpreferences.get
|
||||
import org.mariotaku.ktextension.toIntOr
|
||||
|
@ -15,16 +17,10 @@ import org.mariotaku.twidere.util.dagger.DependencyHolder
|
|||
import java.io.IOException
|
||||
import java.net.InetSocketAddress
|
||||
import java.net.Proxy
|
||||
import java.util.concurrent.TimeUnit
|
||||
import okhttp3.ConnectionSpec
|
||||
import java.util.ArrayList
|
||||
import android.util.Log
|
||||
import java.security.NoSuchAlgorithmException
|
||||
import java.util.*
|
||||
import java.util.concurrent.TimeUnit
|
||||
import javax.net.ssl.SSLContext
|
||||
import android.os.Build
|
||||
|
||||
|
||||
|
||||
|
||||
/**
|
||||
* Created by mariotaku on 16/1/27.
|
||||
|
@ -45,102 +41,6 @@ object HttpClientFactory {
|
|||
DebugModeUtils.initForOkHttpClient(builder)
|
||||
}
|
||||
|
||||
internal fun nougatECCFix(specList: ArrayList<ConnectionSpec>) {
|
||||
// Shamelessly stolen from Tusky
|
||||
if (Build.VERSION.SDK_INT != Build.VERSION_CODES.N) {
|
||||
return
|
||||
}
|
||||
val sslContext: SSLContext
|
||||
try {
|
||||
sslContext = SSLContext.getInstance("TLS")
|
||||
} catch (e: NoSuchAlgorithmException) {
|
||||
Log.e("HttpClientFactory", "Failed obtaining TLS Context.")
|
||||
return
|
||||
}
|
||||
|
||||
sslContext.init(null, null, null)
|
||||
val cipherSuites = sslContext.socketFactory.defaultCipherSuites
|
||||
val allowedList = cipherSuites.filterNotTo(ArrayList<String>()) { it.contains("ECDH") }
|
||||
val spec = ConnectionSpec.Builder(ConnectionSpec.MODERN_TLS)
|
||||
.cipherSuites(*allowedList.toTypedArray())
|
||||
.supportsTlsExtensions(true)
|
||||
.build()
|
||||
specList.add(spec)
|
||||
}
|
||||
|
||||
internal fun updateHttpClientConfiguration(builder: OkHttpClient.Builder, conf: HttpClientConfiguration,
|
||||
dns: Dns, connectionPool: ConnectionPool, cache: Cache) {
|
||||
conf.applyTo(builder)
|
||||
builder.dns(dns)
|
||||
builder.connectionPool(connectionPool)
|
||||
builder.cache(cache)
|
||||
}
|
||||
|
||||
internal fun updateTLSConnectionSpecs(builder: OkHttpClient.Builder) {
|
||||
//Default spec list from OkHttpClient.DEFAULT_CONNECTION_SPECS
|
||||
var specList: ArrayList<ConnectionSpec> = ArrayList()
|
||||
specList.add(ConnectionSpec.MODERN_TLS)
|
||||
nougatECCFix(specList)
|
||||
specList.add(ConnectionSpec.CLEARTEXT)
|
||||
builder.connectionSpecs(specList)
|
||||
}
|
||||
|
||||
class HttpClientConfiguration(val prefs: SharedPreferences) {
|
||||
|
||||
var readTimeoutSecs: Long = -1
|
||||
var writeTimeoutSecs: Long = -1
|
||||
var connectionTimeoutSecs: Long = prefs.getInt(KEY_CONNECTION_TIMEOUT, 10).toLong()
|
||||
var cacheSize: Int = prefs[cacheSizeLimitKey]
|
||||
|
||||
internal fun applyTo(builder: OkHttpClient.Builder) {
|
||||
if (connectionTimeoutSecs >= 0) {
|
||||
builder.connectTimeout(connectionTimeoutSecs, TimeUnit.SECONDS)
|
||||
}
|
||||
if (writeTimeoutSecs >= 0) {
|
||||
builder.writeTimeout(writeTimeoutSecs, TimeUnit.SECONDS)
|
||||
}
|
||||
if (readTimeoutSecs >= 0) {
|
||||
builder.readTimeout(readTimeoutSecs, TimeUnit.SECONDS)
|
||||
}
|
||||
if (prefs.getBoolean(KEY_ENABLE_PROXY, false)) {
|
||||
configProxy(builder)
|
||||
}
|
||||
}
|
||||
|
||||
private fun configProxy(builder: OkHttpClient.Builder) {
|
||||
val proxyType = prefs.getString(KEY_PROXY_TYPE, null) ?: return
|
||||
val proxyHost = prefs.getString(KEY_PROXY_HOST, null)?.takeIf(String::isNotEmpty) ?: return
|
||||
val proxyPort = prefs.getString(KEY_PROXY_PORT, null).toIntOr(-1)
|
||||
val username = prefs.getString(KEY_PROXY_USERNAME, null)?.takeIf(String::isNotEmpty)
|
||||
val password = prefs.getString(KEY_PROXY_PASSWORD, null)?.takeIf(String::isNotEmpty)
|
||||
when (proxyType) {
|
||||
"http" -> {
|
||||
if (proxyPort !in (0..65535)) {
|
||||
return
|
||||
}
|
||||
val address = InetSocketAddress.createUnresolved(proxyHost, proxyPort)
|
||||
builder.proxy(Proxy(Proxy.Type.HTTP, address))
|
||||
|
||||
builder.authenticator { _, response ->
|
||||
val b = response.request().newBuilder()
|
||||
if (response.code() == 407) {
|
||||
if (username != null && password != null) {
|
||||
val credential = Credentials.basic(username, password)
|
||||
b.header("Proxy-Authorization", credential)
|
||||
}
|
||||
}
|
||||
b.build()
|
||||
}
|
||||
}
|
||||
"reverse" -> {
|
||||
builder.addInterceptor(ReverseProxyInterceptor(proxyHost, username, password))
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
fun reloadConnectivitySettings(context: Context) {
|
||||
val holder = DependencyHolder.get(context)
|
||||
val client = holder.restHttpClient as? OkHttpRestClient ?: return
|
||||
|
@ -150,30 +50,6 @@ object HttpClientFactory {
|
|||
client.client = builder.build()
|
||||
}
|
||||
|
||||
/**
|
||||
* Intercept and replace proxy patterns to real URL
|
||||
*/
|
||||
class ReverseProxyInterceptor(val proxyFormat: String, val proxyUsername: String?,
|
||||
val proxyPassword: String?) : Interceptor {
|
||||
|
||||
override fun intercept(chain: Interceptor.Chain): Response {
|
||||
val request = chain.request()
|
||||
val url = request.url()
|
||||
val builder = request.newBuilder()
|
||||
val replacedUrl = HttpUrl.parse(replaceUrl(url, proxyFormat)) ?: run {
|
||||
throw IOException("Invalid reverse proxy format")
|
||||
}
|
||||
builder.url(replacedUrl)
|
||||
if (proxyUsername != null && proxyPassword != null) {
|
||||
val headerValue = Base64.encodeToString("$proxyUsername:$proxyPassword".toByteArray(Charsets.UTF_8),
|
||||
Base64.URL_SAFE)
|
||||
builder.addHeader("Proxy-Authorization", headerValue)
|
||||
}
|
||||
return chain.proceed(builder.build())
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* # Supported patterns
|
||||
*
|
||||
|
@ -241,6 +117,45 @@ object HttpClientFactory {
|
|||
return sb.toString()
|
||||
}
|
||||
|
||||
private fun updateHttpClientConfiguration(builder: OkHttpClient.Builder, conf: HttpClientConfiguration,
|
||||
dns: Dns, connectionPool: ConnectionPool, cache: Cache) {
|
||||
conf.applyTo(builder)
|
||||
builder.dns(dns)
|
||||
builder.connectionPool(connectionPool)
|
||||
builder.cache(cache)
|
||||
}
|
||||
|
||||
private fun updateTLSConnectionSpecs(builder: OkHttpClient.Builder) {
|
||||
//Default spec list from OkHttpClient.DEFAULT_CONNECTION_SPECS
|
||||
val specList: ArrayList<ConnectionSpec> = ArrayList()
|
||||
specList.add(ConnectionSpec.MODERN_TLS)
|
||||
nougatECCFix(specList)
|
||||
specList.add(ConnectionSpec.CLEARTEXT)
|
||||
builder.connectionSpecs(specList)
|
||||
}
|
||||
|
||||
private fun nougatECCFix(specList: ArrayList<ConnectionSpec>) {
|
||||
// Shamelessly stolen from Tusky
|
||||
if (Build.VERSION.SDK_INT != Build.VERSION_CODES.N) {
|
||||
return
|
||||
}
|
||||
val sslContext = try {
|
||||
SSLContext.getInstance("TLS")
|
||||
} catch (e: NoSuchAlgorithmException) {
|
||||
Log.e("HttpClientFactory", "Failed obtaining TLS Context.")
|
||||
return
|
||||
}
|
||||
|
||||
sslContext.init(null, null, null)
|
||||
val cipherSuites = sslContext.socketFactory.defaultCipherSuites
|
||||
val allowedList = cipherSuites.filterNotTo(ArrayList<String>()) { it.contains("ECDH") }
|
||||
val spec = ConnectionSpec.Builder(ConnectionSpec.MODERN_TLS)
|
||||
.cipherSuites(*allowedList.toTypedArray())
|
||||
.supportsTlsExtensions(true)
|
||||
.build()
|
||||
specList.add(spec)
|
||||
}
|
||||
|
||||
private fun HttpUrl.authority(): String {
|
||||
val host = host()
|
||||
val port = port()
|
||||
|
@ -255,4 +170,84 @@ object HttpClientFactory {
|
|||
private val urlSupportedPatterns = listOf("[SCHEME]", "[HOST]", "[PORT]", "[AUTHORITY]",
|
||||
"[PATH]", "[/PATH]", "[PATH_ENCODED]", "[QUERY]", "[?QUERY]", "[QUERY_ENCODED]",
|
||||
"[FRAGMENT]", "[#FRAGMENT]", "[FRAGMENT_ENCODED]", "[URL_ENCODED]", "[URL_BASE64]")
|
||||
|
||||
class HttpClientConfiguration(val prefs: SharedPreferences) {
|
||||
|
||||
var readTimeoutSecs: Long = -1
|
||||
var writeTimeoutSecs: Long = -1
|
||||
var connectionTimeoutSecs: Long = prefs.getInt(KEY_CONNECTION_TIMEOUT, 10).toLong()
|
||||
var cacheSize: Int = prefs[cacheSizeLimitKey]
|
||||
|
||||
internal fun applyTo(builder: OkHttpClient.Builder) {
|
||||
if (connectionTimeoutSecs >= 0) {
|
||||
builder.connectTimeout(connectionTimeoutSecs, TimeUnit.SECONDS)
|
||||
}
|
||||
if (writeTimeoutSecs >= 0) {
|
||||
builder.writeTimeout(writeTimeoutSecs, TimeUnit.SECONDS)
|
||||
}
|
||||
if (readTimeoutSecs >= 0) {
|
||||
builder.readTimeout(readTimeoutSecs, TimeUnit.SECONDS)
|
||||
}
|
||||
if (prefs.getBoolean(KEY_ENABLE_PROXY, false)) {
|
||||
configProxy(builder)
|
||||
}
|
||||
}
|
||||
|
||||
private fun configProxy(builder: OkHttpClient.Builder) {
|
||||
val proxyType = prefs.getString(KEY_PROXY_TYPE, null) ?: return
|
||||
val proxyHost = prefs.getString(KEY_PROXY_HOST, null)?.takeIf(String::isNotEmpty) ?: return
|
||||
val proxyPort = prefs.getString(KEY_PROXY_PORT, null).toIntOr(-1)
|
||||
val username = prefs.getString(KEY_PROXY_USERNAME, null)?.takeIf(String::isNotEmpty)
|
||||
val password = prefs.getString(KEY_PROXY_PASSWORD, null)?.takeIf(String::isNotEmpty)
|
||||
when (proxyType) {
|
||||
"http" -> {
|
||||
if (proxyPort !in (0..65535)) {
|
||||
return
|
||||
}
|
||||
val address = InetSocketAddress.createUnresolved(proxyHost, proxyPort)
|
||||
builder.proxy(Proxy(Proxy.Type.HTTP, address))
|
||||
|
||||
builder.authenticator { _, response ->
|
||||
val b = response.request().newBuilder()
|
||||
if (response.code() == 407) {
|
||||
if (username != null && password != null) {
|
||||
val credential = Credentials.basic(username, password)
|
||||
b.header("Proxy-Authorization", credential)
|
||||
}
|
||||
}
|
||||
b.build()
|
||||
}
|
||||
}
|
||||
"reverse" -> {
|
||||
builder.addInterceptor(ReverseProxyInterceptor(proxyHost, username, password))
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* Intercept and replace proxy patterns to real URL
|
||||
*/
|
||||
class ReverseProxyInterceptor(val proxyFormat: String, val proxyUsername: String?,
|
||||
val proxyPassword: String?) : Interceptor {
|
||||
|
||||
override fun intercept(chain: Interceptor.Chain): Response {
|
||||
val request = chain.request()
|
||||
val url = request.url()
|
||||
val builder = request.newBuilder()
|
||||
val replacedUrl = HttpUrl.parse(replaceUrl(url, proxyFormat)) ?: run {
|
||||
throw IOException("Invalid reverse proxy format")
|
||||
}
|
||||
builder.url(replacedUrl)
|
||||
if (proxyUsername != null && proxyPassword != null) {
|
||||
val headerValue = Base64.encodeToString("$proxyUsername:$proxyPassword".toByteArray(Charsets.UTF_8),
|
||||
Base64.URL_SAFE)
|
||||
builder.addHeader("Proxy-Authorization", headerValue)
|
||||
}
|
||||
return chain.proceed(builder.build())
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
|
|
@ -80,6 +80,9 @@ class TaskServiceRunner(
|
|||
ACTION_REFRESH_DIRECT_MESSAGES -> {
|
||||
val task = GetMessagesTask(context)
|
||||
task.params = object : GetMessagesTask.RefreshNewTaskParam(context) {
|
||||
|
||||
override val isBackground: Boolean = true
|
||||
|
||||
override val accountKeys: Array<UserKey> by lazy {
|
||||
AccountPreferences.getAccountPreferences(context, preferences,
|
||||
DataStoreUtils.getAccountKeys(context)).filter {
|
||||
|
@ -121,6 +124,7 @@ class TaskServiceRunner(
|
|||
SinceMaxPagination().also { it.sinceId = sinceId }
|
||||
}
|
||||
|
||||
override val isBackground: Boolean = true
|
||||
}
|
||||
|
||||
companion object {
|
||||
|
|
|
@ -34,7 +34,10 @@ import org.mariotaku.twidere.fragment.BasePreferenceFragment
|
|||
import org.mariotaku.twidere.fragment.ThemedPreferenceDialogFragmentCompat
|
||||
import org.mariotaku.twidere.fragment.filter.FilteredUsersFragment
|
||||
import org.mariotaku.twidere.fragment.media.ExoPlayerPageFragment
|
||||
import org.mariotaku.twidere.loader.*
|
||||
import org.mariotaku.twidere.loader.CacheUserSearchLoader
|
||||
import org.mariotaku.twidere.loader.DefaultAPIConfigLoader
|
||||
import org.mariotaku.twidere.loader.ParcelableStatusLoader
|
||||
import org.mariotaku.twidere.loader.ParcelableUserLoader
|
||||
import org.mariotaku.twidere.loader.statuses.AbsRequestStatusesLoader
|
||||
import org.mariotaku.twidere.loader.userlists.BaseUserListsLoader
|
||||
import org.mariotaku.twidere.preference.AccountsListPreference
|
||||
|
|
|
@ -20,9 +20,12 @@
|
|||
package org.mariotaku.twidere.util.sync
|
||||
|
||||
import android.content.Context
|
||||
import android.net.Uri
|
||||
import android.support.annotation.UiThread
|
||||
import android.support.annotation.WorkerThread
|
||||
import android.support.v4.util.ArrayMap
|
||||
import okio.ByteString
|
||||
import org.mariotaku.twidere.TwidereConstants.TIMELINE_SYNC_CACHE_PREFERENCES_NAME
|
||||
import org.mariotaku.twidere.annotation.ReadPositionTag
|
||||
import java.io.IOException
|
||||
import java.util.*
|
||||
|
@ -34,7 +37,8 @@ import java.util.*
|
|||
abstract class TimelineSyncManager(val context: Context) {
|
||||
|
||||
private val stagedCommits = ArrayMap<TimelineKey, Long>()
|
||||
private val cachedPositions = ArrayMap<TimelineKey, Long>()
|
||||
private val cachedPositions = context.getSharedPreferences(TIMELINE_SYNC_CACHE_PREFERENCES_NAME,
|
||||
Context.MODE_PRIVATE)
|
||||
|
||||
fun setPosition(@ReadPositionTag positionTag: String, currentTag: String?, positionKey: Long) {
|
||||
stagedCommits[TimelineKey(positionTag, currentTag)] = positionKey
|
||||
|
@ -45,22 +49,21 @@ abstract class TimelineSyncManager(val context: Context) {
|
|||
PositionData(key.positionTag, key.currentTag, value)
|
||||
}.toTypedArray()
|
||||
stagedCommits.clear()
|
||||
if (data.isEmpty()) return
|
||||
performSync(data)
|
||||
}
|
||||
|
||||
|
||||
fun blockingGetPosition(@ReadPositionTag positionTag: String, currentTag: String?): Long {
|
||||
val position = fetchPosition(positionTag, currentTag)
|
||||
synchronized(cachedPositions) {
|
||||
cachedPositions[TimelineKey(positionTag, currentTag)] = position
|
||||
}
|
||||
cachedPositions.edit().putLong(cacheKey(positionTag, currentTag), position).apply()
|
||||
return position
|
||||
}
|
||||
|
||||
fun peekPosition(@ReadPositionTag positionTag: String, currentTag: String?): Long {
|
||||
synchronized(cachedPositions) {
|
||||
return cachedPositions[TimelineKey(positionTag, currentTag)] ?: -1
|
||||
}
|
||||
val cacheKey = cacheKey(positionTag, currentTag)
|
||||
val position = cachedPositions.getLong(cacheKey, -1)
|
||||
cachedPositions.edit().remove(cacheKey).apply()
|
||||
return position
|
||||
}
|
||||
|
||||
|
||||
|
@ -93,6 +96,11 @@ abstract class TimelineSyncManager(val context: Context) {
|
|||
|
||||
companion object {
|
||||
fun newFactory(): Factory = ServiceLoader.load(Factory::class.java).firstOrNull() ?: DummyFactory
|
||||
|
||||
private fun cacheKey(@ReadPositionTag positionTag: String, currentTag: String?): String {
|
||||
if (currentTag == null) return positionTag
|
||||
return ByteString.encodeUtf8("$positionTag:${Uri.encode(currentTag)}").sha1().hex()
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
|
Loading…
Reference in New Issue