diff --git a/app/src/main/java/jp/juggler/subwaytooter/ActMain.kt b/app/src/main/java/jp/juggler/subwaytooter/ActMain.kt index 2b32e696..91f9f2f7 100644 --- a/app/src/main/java/jp/juggler/subwaytooter/ActMain.kt +++ b/app/src/main/java/jp/juggler/subwaytooter/ActMain.kt @@ -1954,8 +1954,8 @@ class ActMain : AsyncActivity(), View.OnClickListener, )?.also { this.ta = parser.account(it.jsonObject) if( ta != null){ - val (ti, r2) = TootInstance.get(client, forceAccessToken = refToken.get()) - this.ti = ti ?: return r2 + val (ti, ri) = TootInstance.getEx(client, forceAccessToken = refToken.get()) + this.ti = ti ?: return ri } } } @@ -2124,15 +2124,10 @@ class ActMain : AsyncActivity(), View.OnClickListener, override suspend fun background(client: TootApiClient): TootApiResult? { - val (instance, instanceResult) = TootInstance.get( - client, - apiHost, - forceAccessToken = access_token - ) - instance ?: return instanceResult - this.ti = instance + val (ti,ri) = TootInstance.getEx(client,forceAccessToken = access_token) + this.ti = ti ?: return ri - val misskeyVersion = instance.misskeyVersion + val misskeyVersion = ti.misskeyVersion val result = client.getUserCredential(access_token, misskeyVersion = misskeyVersion) @@ -2140,7 +2135,7 @@ class ActMain : AsyncActivity(), View.OnClickListener, this@ActMain, LinkHelper.create( apiHost, - apDomainArg = instance.uri?.let { Host.parse(it) }, + apDomainArg = ti.uri?.let { Host.parse(it) }, misskeyVersion = misskeyVersion ) ).account(result?.jsonObject) diff --git a/app/src/main/java/jp/juggler/subwaytooter/ColumnType.kt b/app/src/main/java/jp/juggler/subwaytooter/ColumnType.kt index bd85c2c9..38835e4e 100644 --- a/app/src/main/java/jp/juggler/subwaytooter/ColumnType.kt +++ b/app/src/main/java/jp/juggler/subwaytooter/ColumnType.kt @@ -1215,31 +1215,17 @@ enum class ColumnType( headerType = HeaderType.Instance, loading = { client -> - val (instance, instanceResult) = TootInstance.get( + val (ti,ri) = TootInstance.getEx( client, Host.parse(column.instance_uri), allowPixelfed = true, forceUpdate = true ) - if (instance != null) { - column.instance_information = instance - column.handshake = instanceResult?.response?.handshake + if (ti != null) { + column.instance_information = ti + column.handshake = ri?.response?.handshake } - instanceResult - // - // // 「インスタンス情報」カラムをNAアカウントで開く場合 - // instance_name != null -> client.instance = instance_name - // - // val (result, ti) = client.parseInstanceInformation(client.getInstanceInformation()) - // instance_tmp = ti - // return result - // } - // - // val result = getInstanceInformation(client, column.instance_uri) - // if(instance_tmp != null) { - // - // } - // result + ri } ), diff --git a/app/src/main/java/jp/juggler/subwaytooter/action/Action_Instance.kt b/app/src/main/java/jp/juggler/subwaytooter/action/Action_Instance.kt index 0c09f8d0..281102dd 100644 --- a/app/src/main/java/jp/juggler/subwaytooter/action/Action_Instance.kt +++ b/app/src/main/java/jp/juggler/subwaytooter/action/Action_Instance.kt @@ -25,7 +25,7 @@ object Action_Instance { instance == null -> TootTaskRunner(activity).run(host, object : TootTask { var targetInstance : TootInstance? = null override suspend fun background(client : TootApiClient) : TootApiResult? { - val (ti, ri) = TootInstance.get(client, host, allowPixelfed = true) + val (ti, ri) = TootInstance.getEx(client, host, allowPixelfed = true) targetInstance = ti return ri } diff --git a/app/src/main/java/jp/juggler/subwaytooter/api/TootApiClient.kt b/app/src/main/java/jp/juggler/subwaytooter/api/TootApiClient.kt index 6663f011..a9e1abb5 100644 --- a/app/src/main/java/jp/juggler/subwaytooter/api/TootApiClient.kt +++ b/app/src/main/java/jp/juggler/subwaytooter/api/TootApiClient.kt @@ -120,7 +120,7 @@ class TootApiClient( fun getScopeString(ti: TootInstance?) = when { // 古いサーバ - ti?.versionGE(TootInstance.VERSION_2_4_0_rc1) == false ->"read+write+follow" + ti?.versionGE(TootInstance.VERSION_2_4_0_rc1) == false -> "read+write+follow" // 新しいサーバか、AUTHORIZED_FETCH(3.0.0以降)によりサーバ情報を取得できなかった else -> "read+write+follow+push" @@ -551,8 +551,7 @@ class TootApiClient( suspend fun request( path: String, request_builder: Request.Builder = Request.Builder(), - withoutToken: Boolean = false, - forceAccessToken:String? =null, + forceAccessToken: String? = null, ): TootApiResult? { val result = TootApiResult.makeWithCaption(apiHost?.pretty) if (result.error != null) return result @@ -566,12 +565,8 @@ class TootApiClient( request_builder.url(url) - if (!withoutToken) { - val access_token = forceAccessToken ?: account?.getAccessToken() - if (access_token?.isNotEmpty() == true) { - request_builder.header("Authorization", "Bearer $access_token") - } - } + (forceAccessToken ?: account?.getAccessToken()) + ?.notEmpty()?.let { request_builder.header("Authorization", "Bearer $it") } request_builder.build() .also { log.d("request: ${it.method} $url") } diff --git a/app/src/main/java/jp/juggler/subwaytooter/api/entity/TootInstance.kt b/app/src/main/java/jp/juggler/subwaytooter/api/entity/TootInstance.kt index 8cdea4aa..d84b4794 100644 --- a/app/src/main/java/jp/juggler/subwaytooter/api/entity/TootInstance.kt +++ b/app/src/main/java/jp/juggler/subwaytooter/api/entity/TootInstance.kt @@ -9,14 +9,12 @@ import jp.juggler.subwaytooter.api.TootParser import jp.juggler.subwaytooter.table.SavedAccount import jp.juggler.subwaytooter.util.LinkHelper import jp.juggler.subwaytooter.util.VersionString -import jp.juggler.subwaytooter.util.matchHost import jp.juggler.util.* import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.GlobalScope import kotlinx.coroutines.channels.Channel import kotlinx.coroutines.launch import okhttp3.Request -import java.lang.NullPointerException import java.util.* import java.util.regex.Pattern import kotlin.collections.ArrayList @@ -266,33 +264,43 @@ class TootInstance(parser: TootParser, src: JsonObject) { } // 疑似アカウントの追加時に、インスタンスの検証を行う - private suspend fun TootApiClient.getInstanceInformationMastodon(): TootApiResult? { + private suspend fun TootApiClient.getInstanceInformationMastodon( + forceAccessToken: String? = null + ): TootApiResult? { val result = TootApiResult.makeWithCaption(apiHost?.pretty) if (result.error != null) return result if (sendRequest(result) { - Request.Builder().url("https://${apiHost?.ascii}/api/v1/instance").build() + val builder = Request.Builder().url("https://${apiHost?.ascii}/api/v1/instance") + + (forceAccessToken ?: account?.getAccessToken() ) + ?.notEmpty()?.let { builder.header("Authorization", "Bearer $it") } + builder.build() } ) { parseJson(result) ?: return null } - // misskeyの事は忘れて本来のエラー情報を返す return result } // 疑似アカウントの追加時に、インスタンスの検証を行う - private suspend fun TootApiClient.getInstanceInformationMisskey(): TootApiResult? { + private suspend fun TootApiClient.getInstanceInformationMisskey( + forceAccessToken: String? = null + ): TootApiResult? { val result = TootApiResult.makeWithCaption(apiHost?.pretty) if (result.error != null) return result + if (sendRequest(result) { - JsonObject().apply { + jsonObject { put("dummy", 1) - } - .toPostRequestBuilder() + (forceAccessToken ?: account?.misskeyApiToken ) + ?.notEmpty()?.let { put("i", it) } + }.toPostRequestBuilder() .url("https://${apiHost?.ascii}/api/meta") .build() - }) { + } + ) { parseJson(result) ?: return null result.jsonObject?.apply { @@ -306,136 +314,71 @@ class TootInstance(parser: TootParser, src: JsonObject) { } // 疑似アカウントの追加時に、インスタンスの検証を行う - private suspend fun TootApiClient.getInstanceInformation(): TootApiResult? { + private suspend fun TootApiClient.getInstanceInformation( + forceAccessToken: String? = null + ): TootApiResult? { // misskeyのインスタンス情報を読めたら、それはmisskeyのインスタンス - val r2 = getInstanceInformationMisskey() ?: return null + val r2 = getInstanceInformationMisskey(forceAccessToken) ?: return null if (r2.jsonObject != null) return r2 // マストドンのインスタンス情報を読めたら、それはマストドンのインスタンス - val r1 = getInstanceInformationMastodon() ?: return null + val r1 = getInstanceInformationMastodon(forceAccessToken) ?: return null if (r1.jsonObject != null) return r1 - return r1 // 通信エラーの表示ならr1でもr2でも構わないはず + return r1 // ホワイトリストモードの問題があるのでマストドン側のエラーを返す } - class RequestInfo( - val client: TootApiClient, - val account: SavedAccount?, + class QueuedRequest( val allowPixelfed: Boolean, - val forceUpdate: Boolean, - val forceAccessToken: String?, + val get: suspend (cached: TootInstance?) -> Pair, ) { val result = Channel>() } + fun queuedRequest( + allowPixelfed: Boolean, + get: suspend (cached: TootInstance?) -> Pair + ) = QueuedRequest(allowPixelfed, get) + // インスタンス情報のキャッシュ。同期オブジェクトを兼ねる class CacheEntry { // インスタンス情報のキャッシュ var cacheData: TootInstance? = null - private suspend fun getImpl(ri: RequestInfo): Pair { - - var item: TootInstance? - - if (!ri.forceUpdate && ri.forceAccessToken == null) { - // re-use cached item. - val now = SystemClock.elapsedRealtime() - item = cacheData - if (item != null && now - item.time_parse <= EXPIRE) { - if (item.instanceType == InstanceType.Pixelfed && - !Pref.bpEnablePixelfed(App1.pref) && - !ri.allowPixelfed - ) { - return Pair( - null, - TootApiResult("currently Pixelfed instance is not supported.") - ) - } - return Pair(item, TootApiResult()) - } - } - - // get new information - val result = when { - // マストドンのホワイトリストモード用 - ri.forceAccessToken != null -> - ri.client.request( - "/api/v1/instance", - forceAccessToken = ri.forceAccessToken - ) - - ri.account == null -> - ri.client.getInstanceInformation() - - ri.account.isMisskey -> - ri.client.request( - "/api/meta", - JsonObject().apply { put("dummy", 1) }.toPostRequestBuilder(), - withoutToken = true - ) - - else -> - ri.client.request( - "/api/v1/instance", - withoutToken = true - ) - } - - val json = result?.jsonObject ?: return Pair(null, result) - - item = parseItem( - ::TootInstance, - TootParser( - ri.client.context, - linkHelper = ri.account ?: LinkHelper.create( - ri.client.apiHost!!, - misskeyVersion = parseMisskeyVersion(json) - ) - ), - json - ) - - return when { - item == null -> Pair( - null, - result.setError("instance information parse error.") - ) - - item.instanceType == InstanceType.Pixelfed && - !Pref.bpEnablePixelfed(App1.pref) && - !ri.allowPixelfed -> - Pair( - null, - result.setError("currently Pixelfed instance is not supported.") - ) - - else -> Pair(item.also { cacheData = it }, result) - } - } - // ホストごとに同時に1つしか実行しない、インスタンス情報更新キュー - val requestQueue = Channel(capacity = Channel.UNLIMITED) + val requestQueue = Channel(capacity = Channel.UNLIMITED) - private suspend fun loop() { - while (true) { - requestQueue.receive().let { req -> - req.result.send( - try { - getImpl(req) - } catch (ex: Throwable) { - Pair( - null, - TootApiResult(ex.withCaption("can't get server information.")) - ) - } + private suspend fun handleRequest(req: QueuedRequest) = try { + val pair = req.get(cacheData) + + pair.first?.let { cacheData = it } + + when { + + pair.first?.instanceType == InstanceType.Pixelfed && + !Pref.bpEnablePixelfed(App1.pref) && + !req.allowPixelfed -> + Pair( + null, TootApiResult("currently Pixelfed instance is not supported.") ) - } + + else -> pair } + + } catch (ex: Throwable) { + Pair( + null, + TootApiResult(ex.withCaption("can't get server information.")) + ) } init { - GlobalScope.launch(Dispatchers.IO) { loop() } + GlobalScope.launch(Dispatchers.IO) { + while (true) { + requestQueue.receive().let { it.result.send(handleRequest(it)) } + } + } } } @@ -456,50 +399,95 @@ class TootInstance(parser: TootParser, src: JsonObject) { // no request, no expiration check fun getCached(host: String) = Host.parse(host).getCacheEntry().cacheData - suspend fun get( - client: TootApiClient, - host: String, - account: SavedAccount? = client.account?.takeIf { it.matchHost(host) }, - allowPixelfed: Boolean = false, - forceUpdate: Boolean = false - ): Pair = - get(client, Host.parse(host), account, allowPixelfed, forceUpdate) + suspend fun get(client: TootApiClient): Pair = getEx(client) - suspend fun get( + suspend fun getEx( client: TootApiClient, hostArg: Host? = null, - account: SavedAccount? = if (hostArg == client.apiHost) client.account else null, + account: SavedAccount? = null, allowPixelfed: Boolean = false, forceUpdate: Boolean = false, forceAccessToken: String? = null, // マストドンのwhitelist modeでアカウント追加時に必要 ): Pair { - val tmpInstance = client.apiHost - val tmpAccount = client.account - try { - // this may write client.apiHost - if (account != null) client.account = account - // update client.apiHost - if (hostArg != null) client.apiHost = hostArg + val cacheEntry = (hostArg ?: account?.apiHost ?: client.apiHost)?.getCacheEntry() + ?: return Pair(null, TootApiResult("missing host.")) - val host = client.apiHost - ?: throw NullPointerException("missing host to get server information.") + // ホスト名ごとに用意したオブジェクトで同期する + return queuedRequest(allowPixelfed) { cached -> - // ホスト名ごとに用意したオブジェクトで同期する - return RequestInfo( - client = client, - account = account, - allowPixelfed = allowPixelfed, - forceUpdate = forceUpdate, - forceAccessToken = forceAccessToken + // may use cached item. + if (!forceUpdate && forceAccessToken == null && cached!=null) { + val now = SystemClock.elapsedRealtime() + if ( now - cached.time_parse <= EXPIRE) + return@queuedRequest Pair(cached, TootApiResult()) + } + + val tmpInstance = client.apiHost + val tmpAccount = client.account + + val linkHelper: LinkHelper? + + // get new information + val result = when { + + // ストリームマネジャから呼ばれる + account != null -> try { + linkHelper = account + client.account = account // this may change client.apiHost + if (account.isMisskey) { + client.getInstanceInformationMisskey() + } else { + client.getInstanceInformationMastodon() + } + } finally { + client.account = tmpAccount + client.apiHost = tmpInstance // must be last. + } + + // サーバ情報カラムやProfileDirectoryを開く場合 + hostArg != null && hostArg != tmpInstance -> try { + linkHelper = null + client.account = null // don't use access token. + client.apiHost = hostArg + client.getInstanceInformation() + } finally { + client.account = tmpAccount + client.apiHost = tmpInstance // must be last. + } + + // client にすでにあるアクセス情報でサーバ情報を取得する + // マストドンのホワイトリストモード用にアクセストークンを指定できる + else -> { + linkHelper = client.account // may null + client.getInstanceInformation( + forceAccessToken = forceAccessToken + ) + } + } + + val json = result?.jsonObject + ?: return@queuedRequest Pair(null, result) + + val item = parseItem( + ::TootInstance, + TootParser( + client.context, + linkHelper = linkHelper ?: LinkHelper.create( + hostArg!!, + misskeyVersion = parseMisskeyVersion(json) + ) + ), + json + ) ?: return@queuedRequest Pair( + null, + result.setError("instance information parse error.") ) - .also { host.getCacheEntry().requestQueue.send(it) } - .result.receive() - } finally { - client.account = tmpAccount - client.apiHost = tmpInstance // must be last. + Pair(item, result) } + .also { cacheEntry.requestQueue.send(it) } + .result.receive() } } } diff --git a/app/src/main/java/jp/juggler/subwaytooter/streaming/StreamManager.kt b/app/src/main/java/jp/juggler/subwaytooter/streaming/StreamManager.kt index 24a1b581..786ffa8f 100644 --- a/app/src/main/java/jp/juggler/subwaytooter/streaming/StreamManager.kt +++ b/app/src/main/java/jp/juggler/subwaytooter/streaming/StreamManager.kt @@ -56,7 +56,7 @@ class StreamManager(val appState: AppState) { if (errorAcct.contains(acct)) return null var acctGroup = newMap[acct] if (acctGroup == null) { - var (ti, ri) = TootInstance.get(client, account = accessInfo) + var (ti, ri) = TootInstance.getEx(client, account = accessInfo) if (ti == null) { log.d("can't get server info. ${ri?.error}") val tiOld = acctGroups[acct]?.ti