First attempt to add https syncing
This commit is contained in:
committed by
Koitharu
parent
c00614f17d
commit
065beb72e1
@@ -29,7 +29,7 @@ class SyncSettingsFragment : BasePreferenceFragment(R.string.sync_settings), Fra
|
|||||||
|
|
||||||
override fun onPreferenceTreeClick(preference: Preference): Boolean {
|
override fun onPreferenceTreeClick(preference: Preference): Boolean {
|
||||||
return when (preference.key) {
|
return when (preference.key) {
|
||||||
SyncSettings.KEY_HOST -> {
|
SyncSettings.KEY_SYNC_URL -> {
|
||||||
SyncHostDialogFragment.show(childFragmentManager, null)
|
SyncHostDialogFragment.show(childFragmentManager, null)
|
||||||
true
|
true
|
||||||
}
|
}
|
||||||
@@ -43,7 +43,7 @@ class SyncSettingsFragment : BasePreferenceFragment(R.string.sync_settings), Fra
|
|||||||
}
|
}
|
||||||
|
|
||||||
private fun bindHostSummary() {
|
private fun bindHostSummary() {
|
||||||
val preference = findPreference<Preference>(SyncSettings.KEY_HOST) ?: return
|
val preference = findPreference<Preference>(SyncSettings.KEY_SYNC_URL) ?: return
|
||||||
preference.summary = syncSettings.host
|
preference.summary = syncSettings.syncURL
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,5 +1,6 @@
|
|||||||
package org.koitharu.kotatsu.settings.utils.validation
|
package org.koitharu.kotatsu.settings.utils.validation
|
||||||
|
|
||||||
|
import android.webkit.URLUtil
|
||||||
import okhttp3.HttpUrl
|
import okhttp3.HttpUrl
|
||||||
import org.koitharu.kotatsu.R
|
import org.koitharu.kotatsu.R
|
||||||
import org.koitharu.kotatsu.core.util.EditTextValidator
|
import org.koitharu.kotatsu.core.util.EditTextValidator
|
||||||
@@ -20,15 +21,20 @@ class DomainValidator : EditTextValidator() {
|
|||||||
|
|
||||||
companion object {
|
companion object {
|
||||||
|
|
||||||
fun isValidDomain(value: String): Boolean = runCatching {
|
fun isValidDomain(value: String): Boolean {
|
||||||
require(value.isNotEmpty())
|
if ( ! URLUtil.isValidUrl(value) ) {
|
||||||
val parts = value.split(':')
|
return runCatching {
|
||||||
require(parts.size <= 2)
|
require(value.isNotEmpty())
|
||||||
val urlBuilder = HttpUrl.Builder()
|
val parts = value.split(':')
|
||||||
urlBuilder.host(parts.first())
|
require(parts.size <= 2)
|
||||||
if (parts.size == 2) {
|
val urlBuilder = HttpUrl.Builder()
|
||||||
urlBuilder.port(parts[1].toInt())
|
urlBuilder.host(parts.first())
|
||||||
|
if (parts.size == 2) {
|
||||||
|
urlBuilder.port(parts[1].toInt())
|
||||||
|
}
|
||||||
|
}.isSuccess
|
||||||
}
|
}
|
||||||
}.isSuccess
|
return true
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -17,13 +17,12 @@ class SyncAuthApi @Inject constructor(
|
|||||||
@BaseHttpClient private val okHttpClient: OkHttpClient,
|
@BaseHttpClient private val okHttpClient: OkHttpClient,
|
||||||
) {
|
) {
|
||||||
|
|
||||||
suspend fun authenticate(host: String, email: String, password: String): String {
|
suspend fun authenticate(syncURL: String, email: String, password: String): String {
|
||||||
val body = JSONObject(
|
val body = JSONObject(
|
||||||
mapOf("email" to email, "password" to password),
|
mapOf("email" to email, "password" to password),
|
||||||
).toRequestBody()
|
).toRequestBody()
|
||||||
val scheme = getScheme(host)
|
|
||||||
val request = Request.Builder()
|
val request = Request.Builder()
|
||||||
.url("$scheme://$host/auth")
|
.url("$syncURL/auth")
|
||||||
.post(body)
|
.post(body)
|
||||||
.build()
|
.build()
|
||||||
val response = okHttpClient.newCall(request).await()
|
val response = okHttpClient.newCall(request).await()
|
||||||
@@ -35,13 +34,4 @@ class SyncAuthApi @Inject constructor(
|
|||||||
throw SyncApiException(message, code)
|
throw SyncApiException(message, code)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private suspend fun getScheme(host: String): String {
|
|
||||||
val request = Request.Builder()
|
|
||||||
.url("http://$host/")
|
|
||||||
.head()
|
|
||||||
.build()
|
|
||||||
val response = okHttpClient.newCall(request).await()
|
|
||||||
return response.request.url.scheme
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -32,7 +32,7 @@ class SyncAuthenticator(
|
|||||||
private fun tryRefreshToken() = runCatching {
|
private fun tryRefreshToken() = runCatching {
|
||||||
runBlocking {
|
runBlocking {
|
||||||
authApi.authenticate(
|
authApi.authenticate(
|
||||||
syncSettings.host,
|
syncSettings.syncURL,
|
||||||
account.name,
|
account.name,
|
||||||
accountManager.getPassword(account),
|
accountManager.getPassword(account),
|
||||||
)
|
)
|
||||||
|
|||||||
@@ -24,22 +24,22 @@ class SyncSettings(
|
|||||||
)
|
)
|
||||||
|
|
||||||
private val accountManager = AccountManager.get(context)
|
private val accountManager = AccountManager.get(context)
|
||||||
private val defaultHost = context.getString(R.string.sync_host_default)
|
private val defaultSyncUrl = context.getString(R.string.sync_url_default)
|
||||||
|
|
||||||
@get:WorkerThread
|
@get:WorkerThread
|
||||||
@set:WorkerThread
|
@set:WorkerThread
|
||||||
var host: String
|
var syncURL: String
|
||||||
get() = account?.let {
|
get() = account?.let {
|
||||||
accountManager.getUserData(it, KEY_HOST)
|
accountManager.getUserData(it, KEY_SYNC_URL)
|
||||||
}.ifNullOrEmpty { defaultHost }
|
}.ifNullOrEmpty { defaultSyncUrl }
|
||||||
set(value) {
|
set(value) {
|
||||||
account?.let {
|
account?.let {
|
||||||
accountManager.setUserData(it, KEY_HOST, value)
|
accountManager.setUserData(it, KEY_SYNC_URL, value)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
companion object {
|
companion object {
|
||||||
|
|
||||||
const val KEY_HOST = "host"
|
const val KEY_SYNC_URL = "host"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
package org.koitharu.kotatsu.sync.domain
|
package org.koitharu.kotatsu.sync.domain
|
||||||
|
|
||||||
data class SyncAuthResult(
|
data class SyncAuthResult(
|
||||||
val host: String,
|
val syncURL: String,
|
||||||
val email: String,
|
val email: String,
|
||||||
val password: String,
|
val password: String,
|
||||||
val token: String,
|
val token: String,
|
||||||
|
|||||||
@@ -60,9 +60,7 @@ class SyncHelper @AssistedInject constructor(
|
|||||||
.addInterceptor(SyncInterceptor(context, account))
|
.addInterceptor(SyncInterceptor(context, account))
|
||||||
.build()
|
.build()
|
||||||
private val baseUrl: String by lazy {
|
private val baseUrl: String by lazy {
|
||||||
val host = settings.host
|
settings.syncURL
|
||||||
val scheme = getScheme(host)
|
|
||||||
"$scheme://$host"
|
|
||||||
}
|
}
|
||||||
private val defaultGcPeriod: Long // gc period if sync enabled
|
private val defaultGcPeriod: Long // gc period if sync enabled
|
||||||
get() = TimeUnit.DAYS.toMillis(4)
|
get() = TimeUnit.DAYS.toMillis(4)
|
||||||
@@ -273,15 +271,6 @@ class SyncHelper @AssistedInject constructor(
|
|||||||
return requireNotNull(tag)
|
return requireNotNull(tag)
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun getScheme(host: String): String {
|
|
||||||
val request = Request.Builder()
|
|
||||||
.url("http://$host/")
|
|
||||||
.head()
|
|
||||||
.build()
|
|
||||||
val response = httpClient.newCall(request).execute()
|
|
||||||
return response.request.url.scheme
|
|
||||||
}
|
|
||||||
|
|
||||||
private fun gcFavourites() {
|
private fun gcFavourites() {
|
||||||
val deletedAt = System.currentTimeMillis() - defaultGcPeriod
|
val deletedAt = System.currentTimeMillis() - defaultGcPeriod
|
||||||
val selection = "deleted_at != 0 AND deleted_at < ?"
|
val selection = "deleted_at != 0 AND deleted_at < ?"
|
||||||
|
|||||||
@@ -104,14 +104,14 @@ class SyncAuthActivity : BaseActivity<ActivitySyncAuthBinding>(), View.OnClickLi
|
|||||||
}
|
}
|
||||||
|
|
||||||
R.id.button_settings -> {
|
R.id.button_settings -> {
|
||||||
SyncHostDialogFragment.show(supportFragmentManager, viewModel.host.value)
|
SyncHostDialogFragment.show(supportFragmentManager, viewModel.syncURL.value)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun onFragmentResult(requestKey: String, result: Bundle) {
|
override fun onFragmentResult(requestKey: String, result: Bundle) {
|
||||||
val host = result.getString(SyncHostDialogFragment.KEY_HOST) ?: return
|
val syncURL = result.getString(SyncHostDialogFragment.KEY_SYNC_URL) ?: return
|
||||||
viewModel.host.value = host
|
viewModel.syncURL.value = syncURL
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun finish() {
|
override fun finish() {
|
||||||
@@ -144,7 +144,7 @@ class SyncAuthActivity : BaseActivity<ActivitySyncAuthBinding>(), View.OnClickLi
|
|||||||
val am = AccountManager.get(this)
|
val am = AccountManager.get(this)
|
||||||
val account = Account(authResult.email, getString(R.string.account_type_sync))
|
val account = Account(authResult.email, getString(R.string.account_type_sync))
|
||||||
val userdata = Bundle(1)
|
val userdata = Bundle(1)
|
||||||
userdata.putString(SyncSettings.KEY_HOST, authResult.host)
|
userdata.putString(SyncSettings.KEY_SYNC_URL, authResult.syncURL)
|
||||||
val result = Bundle()
|
val result = Bundle()
|
||||||
if (am.addAccountExplicitly(account, authResult.password, userdata)) {
|
if (am.addAccountExplicitly(account, authResult.password, userdata)) {
|
||||||
result.putString(AccountManager.KEY_ACCOUNT_NAME, account.name)
|
result.putString(AccountManager.KEY_ACCOUNT_NAME, account.name)
|
||||||
|
|||||||
@@ -22,7 +22,7 @@ class SyncAuthViewModel @Inject constructor(
|
|||||||
|
|
||||||
val onAccountAlreadyExists = MutableEventFlow<Unit>()
|
val onAccountAlreadyExists = MutableEventFlow<Unit>()
|
||||||
val onTokenObtained = MutableEventFlow<SyncAuthResult>()
|
val onTokenObtained = MutableEventFlow<SyncAuthResult>()
|
||||||
val host = MutableStateFlow(context.getString(R.string.sync_host_default))
|
val syncURL = MutableStateFlow(context.getString(R.string.sync_url_default))
|
||||||
|
|
||||||
init {
|
init {
|
||||||
launchJob(Dispatchers.Default) {
|
launchJob(Dispatchers.Default) {
|
||||||
@@ -35,10 +35,10 @@ class SyncAuthViewModel @Inject constructor(
|
|||||||
}
|
}
|
||||||
|
|
||||||
fun obtainToken(email: String, password: String) {
|
fun obtainToken(email: String, password: String) {
|
||||||
val hostValue = host.value
|
val urlValue = syncURL.value
|
||||||
launchLoadingJob(Dispatchers.Default) {
|
launchLoadingJob(Dispatchers.Default) {
|
||||||
val token = api.authenticate(hostValue, email, password)
|
val token = api.authenticate(urlValue, email, password)
|
||||||
val result = SyncAuthResult(host.value, email, password, token)
|
val result = SyncAuthResult(syncURL.value, email, password, token)
|
||||||
onTokenObtained.call(result)
|
onTokenObtained.call(result)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -49,10 +49,10 @@ class SyncHostDialogFragment : AlertDialogFragment<PreferenceDialogAutocompletet
|
|||||||
topMargin = binding.root.resources.getDimensionPixelOffset(R.dimen.screen_padding)
|
topMargin = binding.root.resources.getDimensionPixelOffset(R.dimen.screen_padding)
|
||||||
bottomMargin = topMargin
|
bottomMargin = topMargin
|
||||||
}
|
}
|
||||||
binding.message.setText(R.string.sync_host_description)
|
binding.message.setText(R.string.sync_url_description)
|
||||||
val entries = binding.root.resources.getStringArray(R.array.sync_host_list)
|
val entries = binding.root.resources.getStringArray(R.array.sync_url_list)
|
||||||
val editText = binding.edit
|
val editText = binding.edit
|
||||||
editText.setText(arguments?.getString(KEY_HOST).ifNullOrEmpty { syncSettings.host })
|
editText.setText(arguments?.getString(KEY_SYNC_URL).ifNullOrEmpty { syncSettings.syncURL })
|
||||||
editText.threshold = 0
|
editText.threshold = 0
|
||||||
editText.setAdapter(ArrayAdapter(binding.root.context, android.R.layout.simple_spinner_dropdown_item, entries))
|
editText.setAdapter(ArrayAdapter(binding.root.context, android.R.layout.simple_spinner_dropdown_item, entries))
|
||||||
binding.dropdown.setOnClickListener {
|
binding.dropdown.setOnClickListener {
|
||||||
@@ -65,8 +65,12 @@ class SyncHostDialogFragment : AlertDialogFragment<PreferenceDialogAutocompletet
|
|||||||
when (which) {
|
when (which) {
|
||||||
DialogInterface.BUTTON_POSITIVE -> {
|
DialogInterface.BUTTON_POSITIVE -> {
|
||||||
val result = requireViewBinding().edit.text?.toString().orEmpty()
|
val result = requireViewBinding().edit.text?.toString().orEmpty()
|
||||||
syncSettings.host = result
|
var scheme = ""
|
||||||
parentFragmentManager.setFragmentResult(REQUEST_KEY, bundleOf(KEY_HOST to result))
|
if ( ! result.startsWith("https://") && ! result.startsWith("http://")) {
|
||||||
|
scheme = "http://"
|
||||||
|
}
|
||||||
|
syncSettings.syncURL = "$scheme$result"
|
||||||
|
parentFragmentManager.setFragmentResult(REQUEST_KEY, bundleOf(KEY_SYNC_URL to "$scheme$result"))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
dialog.dismiss()
|
dialog.dismiss()
|
||||||
@@ -75,11 +79,11 @@ class SyncHostDialogFragment : AlertDialogFragment<PreferenceDialogAutocompletet
|
|||||||
companion object {
|
companion object {
|
||||||
|
|
||||||
private const val TAG = "SyncHostDialogFragment"
|
private const val TAG = "SyncHostDialogFragment"
|
||||||
const val REQUEST_KEY = "sync_host"
|
const val REQUEST_KEY = "host"
|
||||||
const val KEY_HOST = "host"
|
const val KEY_SYNC_URL = "host"
|
||||||
|
|
||||||
fun show(fm: FragmentManager, host: String?) = SyncHostDialogFragment().withArgs(1) {
|
fun show(fm: FragmentManager, syncURL: String?) = SyncHostDialogFragment().withArgs(1) {
|
||||||
putString(KEY_HOST, host)
|
putString(KEY_SYNC_URL, syncURL)
|
||||||
}.show(fm, TAG)
|
}.show(fm, TAG)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -6,7 +6,7 @@
|
|||||||
<string name="url_weblate" translatable="false">https://hosted.weblate.org/engage/kotatsu</string>
|
<string name="url_weblate" translatable="false">https://hosted.weblate.org/engage/kotatsu</string>
|
||||||
<string name="url_error_report" translatable="false">https://bugs.kotatsu.app/report</string>
|
<string name="url_error_report" translatable="false">https://bugs.kotatsu.app/report</string>
|
||||||
<string name="account_type_sync" translatable="false">org.kotatsu.sync</string>
|
<string name="account_type_sync" translatable="false">org.kotatsu.sync</string>
|
||||||
<string name="sync_host_default" translatable="false">sync.kotatsu.app</string>
|
<string name="sync_url_default" translatable="false">https://sync.kotatsu.app</string>
|
||||||
<string name="shikimori_clientId" translatable="false">Mw6F0tPEOgyV7F9U9Twg50Q8SndMY7hzIOfXg0AX_XU</string>
|
<string name="shikimori_clientId" translatable="false">Mw6F0tPEOgyV7F9U9Twg50Q8SndMY7hzIOfXg0AX_XU</string>
|
||||||
<string name="shikimori_clientSecret" translatable="false">euBMt1GGRSDpVIFQVPxZrO7Kh6X4gWyv0dABuj4B-M8</string>
|
<string name="shikimori_clientSecret" translatable="false">euBMt1GGRSDpVIFQVPxZrO7Kh6X4gWyv0dABuj4B-M8</string>
|
||||||
<string name="anilist_clientId" translatable="false">9887</string>
|
<string name="anilist_clientId" translatable="false">9887</string>
|
||||||
@@ -40,8 +40,8 @@
|
|||||||
<item>0</item>
|
<item>0</item>
|
||||||
<item>1</item>
|
<item>1</item>
|
||||||
</string-array>
|
</string-array>
|
||||||
<string-array name="sync_host_list" translatable="false">
|
<string-array name="sync_url_list" translatable="false">
|
||||||
<item>@string/sync_host_default</item>
|
<item>@string/sync_url_default</item>
|
||||||
<item>moe.shirizu.org</item>
|
<item>moe.shirizu.org</item>
|
||||||
<item>86.57.183.214:8081</item>
|
<item>86.57.183.214:8081</item>
|
||||||
</string-array>
|
</string-array>
|
||||||
|
|||||||
@@ -278,7 +278,7 @@
|
|||||||
<string name="exclude_nsfw_from_history_summary">Manga marked as NSFW will never be added to the history and your progress will not be saved</string>
|
<string name="exclude_nsfw_from_history_summary">Manga marked as NSFW will never be added to the history and your progress will not be saved</string>
|
||||||
<string name="clear_cookies_summary">Can help in case of some issues. All authorizations will be invalidated</string>
|
<string name="clear_cookies_summary">Can help in case of some issues. All authorizations will be invalidated</string>
|
||||||
<string name="show_all">Show all</string>
|
<string name="show_all">Show all</string>
|
||||||
<string name="invalid_domain_message">Invalid domain</string>
|
<string name="invalid_domain_message">Invalid server address</string>
|
||||||
<string name="select_range">Select range</string>
|
<string name="select_range">Select range</string>
|
||||||
<string name="clear_all_history">Clear all history</string>
|
<string name="clear_all_history">Clear all history</string>
|
||||||
<string name="last_2_hours">Last 2 hours</string>
|
<string name="last_2_hours">Last 2 hours</string>
|
||||||
@@ -375,7 +375,7 @@
|
|||||||
<string name="find_similar">Find similar</string>
|
<string name="find_similar">Find similar</string>
|
||||||
<string name="sync_settings">Synchronization settings</string>
|
<string name="sync_settings">Synchronization settings</string>
|
||||||
<string name="server_address">Server address</string>
|
<string name="server_address">Server address</string>
|
||||||
<string name="sync_host_description">You can use a self-hosted synchronization server or a default one. Don\'t change this if you\'re not sure what you\'re doing.</string>
|
<string name="sync_url_description">You can use a self-hosted synchronization server or a default one. Don\'t change this if you\'re not sure what you\'re doing.</string>
|
||||||
<string name="ignore_ssl_errors">Ignore SSL errors</string>
|
<string name="ignore_ssl_errors">Ignore SSL errors</string>
|
||||||
<string name="mirror_switching">Choose mirror automatically</string>
|
<string name="mirror_switching">Choose mirror automatically</string>
|
||||||
<string name="mirror_switching_summary">Automatically switch domains for manga sources on errors if mirrors are available</string>
|
<string name="mirror_switching_summary">Automatically switch domains for manga sources on errors if mirrors are available</string>
|
||||||
|
|||||||
Reference in New Issue
Block a user