From 339ce9db98c1e6dfb1c8d69f81806680b1efa666 Mon Sep 17 00:00:00 2001 From: Allan Wang Date: Fri, 4 Jan 2019 01:56:04 -0500 Subject: Convert global continuations to completable deferred --- .../frost/facebook/requests/FbRequest.kt | 16 ---------- .../pitchedapps/frost/facebook/requests/Images.kt | 2 +- .../frost/fragments/RecyclerFragments.kt | 2 +- .../com/pitchedapps/frost/kotlin/Flyweight.kt | 37 ++++++++++++++-------- .../frost/services/FrostRequestService.kt | 2 +- .../com/pitchedapps/frost/web/LoginWebView.kt | 30 +++++++----------- .../com/pitchedapps/frost/kotlin/FlyweightTest.kt | 20 ++++++------ 7 files changed, 48 insertions(+), 61 deletions(-) diff --git a/app/src/main/kotlin/com/pitchedapps/frost/facebook/requests/FbRequest.kt b/app/src/main/kotlin/com/pitchedapps/frost/facebook/requests/FbRequest.kt index 53ea6e67..3663f908 100644 --- a/app/src/main/kotlin/com/pitchedapps/frost/facebook/requests/FbRequest.kt +++ b/app/src/main/kotlin/com/pitchedapps/frost/facebook/requests/FbRequest.kt @@ -44,22 +44,6 @@ val fbAuth = Flyweight(GlobalScope, 100, 3600000 /* an hour it.getAuth() } -/** - * Synchronously fetch [RequestAuth] from cookie - * [action] will only be called if a valid auth is found. - * Otherwise, [fail] will be called - */ -fun String?.fbRequest(fail: () -> Unit = {}, action: RequestAuth.() -> Unit) { - if (this == null) return fail() - try { - val auth = runBlocking { fbAuth.fetch(this@fbRequest) } - auth.action() - } catch (e: Exception) { - L.e { "Failed auth for ${hashCode()}: ${e.message}" } - fail() - } -} - /** * Underlying container for all fb requests */ diff --git a/app/src/main/kotlin/com/pitchedapps/frost/facebook/requests/Images.kt b/app/src/main/kotlin/com/pitchedapps/frost/facebook/requests/Images.kt index ee18e15e..36dff6ff 100644 --- a/app/src/main/kotlin/com/pitchedapps/frost/facebook/requests/Images.kt +++ b/app/src/main/kotlin/com/pitchedapps/frost/facebook/requests/Images.kt @@ -133,7 +133,7 @@ class HdImageFetcher(private val model: HdImageMaybe) : DataFetcher val result: Result = runCatching { runBlocking { withTimeout(20000L) { - val auth = fbAuth.fetch(model.cookie) + val auth = fbAuth.fetch(model.cookie).await() if (cancelled) throw RuntimeException("Cancelled") val url = auth.getFullSizedImage(model.id).invoke() ?: throw RuntimeException("Null url") if (cancelled) throw RuntimeException("Cancelled") diff --git a/app/src/main/kotlin/com/pitchedapps/frost/fragments/RecyclerFragments.kt b/app/src/main/kotlin/com/pitchedapps/frost/fragments/RecyclerFragments.kt index f7ed9937..d9d518b1 100644 --- a/app/src/main/kotlin/com/pitchedapps/frost/fragments/RecyclerFragments.kt +++ b/app/src/main/kotlin/com/pitchedapps/frost/fragments/RecyclerFragments.kt @@ -74,7 +74,7 @@ class MenuFragment : GenericRecyclerFragment>() { override suspend fun reloadImpl(progress: (Int) -> Unit): List? = withContext(Dispatchers.IO) { val cookie = FbCookie.webCookie ?: return@withContext null progress(10) - val auth = fbAuth.fetch(cookie) + val auth = fbAuth.fetch(cookie).await() progress(30) val data = auth.getMenuData().invoke() ?: return@withContext null if (data.data.isEmpty()) return@withContext null diff --git a/app/src/main/kotlin/com/pitchedapps/frost/kotlin/Flyweight.kt b/app/src/main/kotlin/com/pitchedapps/frost/kotlin/Flyweight.kt index 7ac80147..a22022de 100644 --- a/app/src/main/kotlin/com/pitchedapps/frost/kotlin/Flyweight.kt +++ b/app/src/main/kotlin/com/pitchedapps/frost/kotlin/Flyweight.kt @@ -17,6 +17,7 @@ package com.pitchedapps.frost.kotlin import kotlinx.coroutines.CancellationException +import kotlinx.coroutines.CompletableDeferred import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.Job @@ -25,7 +26,6 @@ import kotlinx.coroutines.isActive import kotlinx.coroutines.launch import kotlinx.coroutines.selects.select import java.util.concurrent.ConcurrentHashMap -import kotlin.coroutines.Continuation import kotlin.coroutines.resumeWithException import kotlin.coroutines.suspendCoroutine @@ -44,7 +44,7 @@ class Flyweight( ) { // Receives a key and a pending request - private val actionChannel = Channel>>(capacity) + private val actionChannel = Channel>>(capacity) // Receives a key to invalidate the associated value private val invalidatorChannel = Channel(capacity) // Receives a key to fetch the value @@ -58,10 +58,17 @@ class Flyweight( private val resultMap: MutableMap> = mutableMapOf() // Keeps track of unfulfilled actions // Note that the explicit type is very important here. See https://youtrack.jetbrains.net/issue/KT-18053 - private val pendingMap: MutableMap>> = ConcurrentHashMap() + private val pendingMap: MutableMap>> = ConcurrentHashMap() private val job: Job + private fun CompletableDeferred.completeWith(result: Result) { + if (result.isSuccess) + complete(result.getOrNull()!!) + else + completeExceptionally(result.exceptionOrNull()!!) + } + init { job = scope.launch(Dispatchers.IO) { launch { @@ -70,15 +77,15 @@ class Flyweight( /* * New request received. Continuation should be fulfilled eventually */ - actionChannel.onReceive { (key, continuation) -> + actionChannel.onReceive { (key, completable) -> val lastUpdate = conditionMap[key] val lastResult = resultMap[key] // Valid value, retrieved within acceptable time if (lastResult != null && lastUpdate != null && System.currentTimeMillis() - lastUpdate < maxAge) { - continuation.resumeWith(lastResult) + completable.completeWith(lastResult) } else { val valueRequestPending = key in pendingMap - pendingMap.getOrPut(key) { mutableListOf() }.add(continuation) + pendingMap.getOrPut(key) { mutableListOf() }.add(completable) if (!valueRequestPending) requesterChannel.send(key) } @@ -106,7 +113,7 @@ class Flyweight( conditionMap[key] = System.currentTimeMillis() resultMap[key] = result pendingMap.remove(key)?.forEach { - it.resumeWith(result) + it.completeWith(result) } } } @@ -126,11 +133,15 @@ class Flyweight( } } - suspend fun fetch(key: K): V = suspendCoroutine { - if (!job.isActive) it.resumeWithException(IllegalStateException("Flyweight is not active")) - else scope.launch { - actionChannel.send(key to it) - } + /** + * Queues the request, and returns a completable once it is sent to a channel. + * The fetcher will only be suspended if the channels are full + */ + suspend fun fetch(key: K): CompletableDeferred { + val completable = CompletableDeferred(job) + if (!job.isActive) completable.completeExceptionally(IllegalStateException("Flyweight is not active")) + else actionChannel.send(key to completable) + return completable } suspend fun invalidate(key: K) { @@ -141,7 +152,7 @@ class Flyweight( job.cancel() if (pendingMap.isNotEmpty()) { val error = CancellationException("Flyweight cancelled") - pendingMap.values.flatten().forEach { it.resumeWithException(error) } + pendingMap.values.flatten().forEach { it.completeExceptionally(error) } pendingMap.clear() } actionChannel.close() diff --git a/app/src/main/kotlin/com/pitchedapps/frost/services/FrostRequestService.kt b/app/src/main/kotlin/com/pitchedapps/frost/services/FrostRequestService.kt index c88f3946..a8ecb27d 100644 --- a/app/src/main/kotlin/com/pitchedapps/frost/services/FrostRequestService.kt +++ b/app/src/main/kotlin/com/pitchedapps/frost/services/FrostRequestService.kt @@ -179,7 +179,7 @@ class FrostRequestService : BaseJobService() { } launch(Dispatchers.IO) { try { - val auth = fbAuth.fetch(cookie) + val auth = fbAuth.fetch(cookie).await() command.invoke(auth, bundle) L.d { "Finished frost service for ${command.name} in ${System.currentTimeMillis() - startTime} ms" diff --git a/app/src/main/kotlin/com/pitchedapps/frost/web/LoginWebView.kt b/app/src/main/kotlin/com/pitchedapps/frost/web/LoginWebView.kt index 8132382a..c21ce93b 100644 --- a/app/src/main/kotlin/com/pitchedapps/frost/web/LoginWebView.kt +++ b/app/src/main/kotlin/com/pitchedapps/frost/web/LoginWebView.kt @@ -28,7 +28,7 @@ import android.webkit.WebResourceRequest import android.webkit.WebView import ca.allanwang.kau.utils.fadeIn import ca.allanwang.kau.utils.isVisible -import ca.allanwang.kau.utils.withMainContext +import ca.allanwang.kau.utils.launchMain import com.pitchedapps.frost.dbflow.CookieModel import com.pitchedapps.frost.facebook.FB_LOGIN_URL import com.pitchedapps.frost.facebook.FB_USER_MATCHER @@ -40,10 +40,8 @@ import com.pitchedapps.frost.injectors.jsInject import com.pitchedapps.frost.utils.L import com.pitchedapps.frost.utils.Prefs import com.pitchedapps.frost.utils.isFacebookUrl +import kotlinx.coroutines.CompletableDeferred import kotlinx.coroutines.coroutineScope -import kotlinx.coroutines.launch -import kotlinx.coroutines.suspendCancellableCoroutine -import kotlin.coroutines.resume /** * Created by Allan Wang on 2017-05-29. @@ -54,7 +52,7 @@ class LoginWebView @JvmOverloads constructor( defStyleAttr: Int = 0 ) : WebView(context, attrs, defStyleAttr) { - private lateinit var loginCallback: (CookieModel) -> Unit + private val completable: CompletableDeferred = CompletableDeferred() private lateinit var progressCallback: (Int) -> Unit @SuppressLint("SetJavaScriptEnabled") @@ -65,19 +63,15 @@ class LoginWebView @JvmOverloads constructor( webChromeClient = LoginChromeClient() } - suspend fun loadLogin(progressCallback: (Int) -> Unit): CookieModel = withMainContext { - coroutineScope { - suspendCancellableCoroutine { cont -> - this@LoginWebView.progressCallback = progressCallback - this@LoginWebView.loginCallback = { cont.resume(it) } - L.d { "Begin loading login" } - launch { - FbCookie.reset() - setupWebview() - loadUrl(FB_LOGIN_URL) - } - } + suspend fun loadLogin(progressCallback: (Int) -> Unit): CompletableDeferred = coroutineScope { + this@LoginWebView.progressCallback = progressCallback + L.d { "Begin loading login" } + launchMain { + FbCookie.reset() + setupWebview() + loadUrl(FB_LOGIN_URL) } + completable } private inner class LoginClient : BaseWebViewClient() { @@ -86,7 +80,7 @@ class LoginWebView @JvmOverloads constructor( super.onPageFinished(view, url) val cookieModel = checkForLogin(url) if (cookieModel != null) - loginCallback(cookieModel) + completable.complete(cookieModel) if (!view.isVisible) view.fadeIn() } diff --git a/app/src/test/kotlin/com/pitchedapps/frost/kotlin/FlyweightTest.kt b/app/src/test/kotlin/com/pitchedapps/frost/kotlin/FlyweightTest.kt index 0eee530e..79f81002 100644 --- a/app/src/test/kotlin/com/pitchedapps/frost/kotlin/FlyweightTest.kt +++ b/app/src/test/kotlin/com/pitchedapps/frost/kotlin/FlyweightTest.kt @@ -54,7 +54,7 @@ class FlyweightTest { @Test fun basic() { - assertEquals(2, runBlocking { flyweight.fetch(1) }, "Invalid result") + assertEquals(2, runBlocking { flyweight.fetch(1).await() }, "Invalid result") assertEquals(1, callCount.get(), "1 call expected") } @@ -62,9 +62,7 @@ class FlyweightTest { fun multipleWithOneKey() { val results: List = runBlocking { (0..1000).map { - flyweight.scope.async { - flyweight.fetch(1) - } + flyweight.fetch(1) }.map { it.await() } } assertEquals(1, callCount.get(), "1 call expected") @@ -75,12 +73,12 @@ class FlyweightTest { @Test fun consecutiveReuse() { runBlocking { - flyweight.fetch(1) + flyweight.fetch(1).await() assertEquals(1, callCount.get(), "1 call expected") - flyweight.fetch(1) + flyweight.fetch(1).await() assertEquals(1, callCount.get(), "Reuse expected") Thread.sleep(300) - flyweight.fetch(1) + flyweight.fetch(1).await() assertEquals(2, callCount.get(), "Refetch expected") } } @@ -88,10 +86,10 @@ class FlyweightTest { @Test fun invalidate() { runBlocking { - flyweight.fetch(1) + flyweight.fetch(1).await() assertEquals(1, callCount.get(), "1 call expected") flyweight.invalidate(1) - flyweight.fetch(1) + flyweight.fetch(1).await() assertEquals(2, callCount.get(), "New call expected") } } @@ -100,10 +98,10 @@ class FlyweightTest { fun destroy() { runBlocking { val longRunningResult = async { flyweight.fetch(LONG_RUNNING_KEY) } - flyweight.fetch(1) + flyweight.fetch(1).await() flyweight.cancel() try { - flyweight.fetch(1) + flyweight.fetch(1).await() fail("Flyweight should not be fulfilled after it is destroyed") } catch (e: Exception) { assertEquals("Flyweight is not active", e.message, "Incorrect error found on fetch after destruction") -- cgit v1.2.3 From e0af746bc886515a06487c65f9bee12e2b0a1908 Mon Sep 17 00:00:00 2001 From: Allan Wang Date: Fri, 4 Jan 2019 02:20:27 -0500 Subject: Redact default cookiemodel string --- app/src/main/kotlin/com/pitchedapps/frost/StartActivity.kt | 3 +-- app/src/main/kotlin/com/pitchedapps/frost/dbflow/CookiesDb.kt | 7 ++++++- .../kotlin/com/pitchedapps/frost/facebook/requests/FbRequest.kt | 1 - app/src/main/kotlin/com/pitchedapps/frost/kotlin/Flyweight.kt | 2 -- 4 files changed, 7 insertions(+), 6 deletions(-) diff --git a/app/src/main/kotlin/com/pitchedapps/frost/StartActivity.kt b/app/src/main/kotlin/com/pitchedapps/frost/StartActivity.kt index aa6a0130..81407543 100644 --- a/app/src/main/kotlin/com/pitchedapps/frost/StartActivity.kt +++ b/app/src/main/kotlin/com/pitchedapps/frost/StartActivity.kt @@ -32,7 +32,6 @@ import com.mikepenz.google_material_typeface_library.GoogleMaterial import com.pitchedapps.frost.activities.LoginActivity import com.pitchedapps.frost.activities.MainActivity import com.pitchedapps.frost.activities.SelectorActivity -import com.pitchedapps.frost.dbflow.CookieModel import com.pitchedapps.frost.dbflow.loadFbCookiesSync import com.pitchedapps.frost.facebook.FbCookie import com.pitchedapps.frost.utils.EXTRA_COOKIES @@ -74,7 +73,7 @@ class StartActivity : KauBaseActivity() { }) L.i { "Cookies loaded at time ${System.currentTimeMillis()}" } L._d { "Cookies: ${cookies.joinToString("\t")}" } - loadAssets() + loadAssets() when { cookies.isEmpty() -> launchNewTask() // Has cookies but no selected account diff --git a/app/src/main/kotlin/com/pitchedapps/frost/dbflow/CookiesDb.kt b/app/src/main/kotlin/com/pitchedapps/frost/dbflow/CookiesDb.kt index e8fb5c54..f7d97833 100644 --- a/app/src/main/kotlin/com/pitchedapps/frost/dbflow/CookiesDb.kt +++ b/app/src/main/kotlin/com/pitchedapps/frost/dbflow/CookiesDb.kt @@ -54,7 +54,12 @@ object CookiesDb { @Parcelize @Table(database = CookiesDb::class, allFields = true, primaryKeyConflict = ConflictAction.REPLACE) data class CookieModel(@PrimaryKey var id: Long = -1L, var name: String? = null, var cookie: String? = null) : - BaseModel(), Parcelable + BaseModel(), Parcelable { + + override fun toString(): String = "CookieModel(${hashCode()})" + + fun toSensitiveString(): String = "CookieModel(id=$id, name=$name, cookie=$cookie)" +} fun loadFbCookie(id: Long): CookieModel? = (select from CookieModel::class where (CookieModel_Table.id eq id)).querySingle() diff --git a/app/src/main/kotlin/com/pitchedapps/frost/facebook/requests/FbRequest.kt b/app/src/main/kotlin/com/pitchedapps/frost/facebook/requests/FbRequest.kt index 3663f908..aa8dc58e 100644 --- a/app/src/main/kotlin/com/pitchedapps/frost/facebook/requests/FbRequest.kt +++ b/app/src/main/kotlin/com/pitchedapps/frost/facebook/requests/FbRequest.kt @@ -29,7 +29,6 @@ import com.pitchedapps.frost.utils.L import io.reactivex.Single import io.reactivex.schedulers.Schedulers import kotlinx.coroutines.GlobalScope -import kotlinx.coroutines.runBlocking import okhttp3.Call import okhttp3.FormBody import okhttp3.OkHttpClient diff --git a/app/src/main/kotlin/com/pitchedapps/frost/kotlin/Flyweight.kt b/app/src/main/kotlin/com/pitchedapps/frost/kotlin/Flyweight.kt index a22022de..e5edce24 100644 --- a/app/src/main/kotlin/com/pitchedapps/frost/kotlin/Flyweight.kt +++ b/app/src/main/kotlin/com/pitchedapps/frost/kotlin/Flyweight.kt @@ -26,8 +26,6 @@ import kotlinx.coroutines.isActive import kotlinx.coroutines.launch import kotlinx.coroutines.selects.select import java.util.concurrent.ConcurrentHashMap -import kotlin.coroutines.resumeWithException -import kotlin.coroutines.suspendCoroutine /** * Flyweight to keep track of values so long as they are valid. -- cgit v1.2.3 From 37cd65102f92334cf92a64637a354edeee8f1d25 Mon Sep 17 00:00:00 2001 From: Allan Wang Date: Fri, 4 Jan 2019 02:24:00 -0500 Subject: Apply sensitive string to start activity logger --- app/src/main/kotlin/com/pitchedapps/frost/StartActivity.kt | 3 ++- app/src/main/kotlin/com/pitchedapps/frost/glide/GlideUtils.kt | 1 - 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/app/src/main/kotlin/com/pitchedapps/frost/StartActivity.kt b/app/src/main/kotlin/com/pitchedapps/frost/StartActivity.kt index 81407543..7622ff5c 100644 --- a/app/src/main/kotlin/com/pitchedapps/frost/StartActivity.kt +++ b/app/src/main/kotlin/com/pitchedapps/frost/StartActivity.kt @@ -32,6 +32,7 @@ import com.mikepenz.google_material_typeface_library.GoogleMaterial import com.pitchedapps.frost.activities.LoginActivity import com.pitchedapps.frost.activities.MainActivity import com.pitchedapps.frost.activities.SelectorActivity +import com.pitchedapps.frost.dbflow.CookieModel import com.pitchedapps.frost.dbflow.loadFbCookiesSync import com.pitchedapps.frost.facebook.FbCookie import com.pitchedapps.frost.utils.EXTRA_COOKIES @@ -72,7 +73,7 @@ class StartActivity : KauBaseActivity() { loadFbCookiesSync() }) L.i { "Cookies loaded at time ${System.currentTimeMillis()}" } - L._d { "Cookies: ${cookies.joinToString("\t")}" } + L._d { "Cookies: ${cookies.joinToString("\t", transform = CookieModel::toSensitiveString)}" } loadAssets() when { cookies.isEmpty() -> launchNewTask() diff --git a/app/src/main/kotlin/com/pitchedapps/frost/glide/GlideUtils.kt b/app/src/main/kotlin/com/pitchedapps/frost/glide/GlideUtils.kt index 0c471c63..cceabcae 100644 --- a/app/src/main/kotlin/com/pitchedapps/frost/glide/GlideUtils.kt +++ b/app/src/main/kotlin/com/pitchedapps/frost/glide/GlideUtils.kt @@ -70,7 +70,6 @@ class FrostCookieInterceptor : Interceptor { override fun intercept(chain: Interceptor.Chain): Response { val origRequest = chain.request() val cookie = FbCookie.webCookie ?: return chain.proceed(origRequest) - L.v { "Add cookie to req $cookie" } val request = origRequest.newBuilder().addHeader("Cookie", cookie).build() return chain.proceed(request) } -- cgit v1.2.3 From 702503db4ef28caefefbaabc27ccf161f1c54ed2 Mon Sep 17 00:00:00 2001 From: Allan Wang Date: Fri, 4 Jan 2019 02:27:44 -0500 Subject: Try catch apache unescape, resolves #1300 --- .../kotlin/com/pitchedapps/frost/facebook/requests/FbRequest.kt | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/app/src/main/kotlin/com/pitchedapps/frost/facebook/requests/FbRequest.kt b/app/src/main/kotlin/com/pitchedapps/frost/facebook/requests/FbRequest.kt index aa8dc58e..16ddb7e1 100644 --- a/app/src/main/kotlin/com/pitchedapps/frost/facebook/requests/FbRequest.kt +++ b/app/src/main/kotlin/com/pitchedapps/frost/facebook/requests/FbRequest.kt @@ -35,6 +35,7 @@ import okhttp3.OkHttpClient import okhttp3.Request import okhttp3.logging.HttpLoggingInterceptor import org.apache.commons.text.StringEscapeUtils +import java.lang.Exception /** * Created by Allan Wang on 21/12/17. @@ -119,7 +120,11 @@ fun String.getAuth(): RequestAuth { .call() call.execute().body()?.charStream()?.useLines { lines -> lines.forEach { - val text = StringEscapeUtils.unescapeEcmaScript(it) + val text = try { + StringEscapeUtils.unescapeEcmaScript(it) + } catch (ignore: Exception) { + return@forEach + } val fb_dtsg = FB_DTSG_MATCHER.find(text)[1] if (fb_dtsg != null) { auth = auth.copy(fb_dtsg = fb_dtsg) -- cgit v1.2.3 From 9bd77fa983dda0054a1795b2c91f261a491a53c5 Mon Sep 17 00:00:00 2001 From: Allan Wang Date: Fri, 4 Jan 2019 02:33:15 -0500 Subject: Fix compilation error --- app/src/main/kotlin/com/pitchedapps/frost/activities/LoginActivity.kt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/src/main/kotlin/com/pitchedapps/frost/activities/LoginActivity.kt b/app/src/main/kotlin/com/pitchedapps/frost/activities/LoginActivity.kt index 150d29f4..27a488e3 100644 --- a/app/src/main/kotlin/com/pitchedapps/frost/activities/LoginActivity.kt +++ b/app/src/main/kotlin/com/pitchedapps/frost/activities/LoginActivity.kt @@ -88,7 +88,7 @@ class LoginActivity : BaseActivity() { } } launch { - val cookie = web.loadLogin { refresh(it != 100) } + val cookie = web.loadLogin { refresh(it != 100) }.await() L.d { "Login found" } FbCookie.save(cookie.id) webFadeOut() -- cgit v1.2.3 From 535004b8a28d1b227fa0673f80f6086ca8f4be41 Mon Sep 17 00:00:00 2001 From: Allan Wang Date: Fri, 4 Jan 2019 13:32:58 -0500 Subject: Fix flyweight tests --- .../frost/facebook/requests/FbRequest.kt | 2 +- .../com/pitchedapps/frost/kotlin/Flyweight.kt | 46 +++++++++++----------- .../com/pitchedapps/frost/kotlin/FlyweightTest.kt | 18 ++++----- 3 files changed, 31 insertions(+), 35 deletions(-) diff --git a/app/src/main/kotlin/com/pitchedapps/frost/facebook/requests/FbRequest.kt b/app/src/main/kotlin/com/pitchedapps/frost/facebook/requests/FbRequest.kt index 16ddb7e1..67a03ad4 100644 --- a/app/src/main/kotlin/com/pitchedapps/frost/facebook/requests/FbRequest.kt +++ b/app/src/main/kotlin/com/pitchedapps/frost/facebook/requests/FbRequest.kt @@ -40,7 +40,7 @@ import java.lang.Exception /** * Created by Allan Wang on 21/12/17. */ -val fbAuth = Flyweight(GlobalScope, 100, 3600000 /* an hour */) { +val fbAuth = Flyweight(GlobalScope, 3600000 /* an hour */) { it.getAuth() } diff --git a/app/src/main/kotlin/com/pitchedapps/frost/kotlin/Flyweight.kt b/app/src/main/kotlin/com/pitchedapps/frost/kotlin/Flyweight.kt index e5edce24..914ce151 100644 --- a/app/src/main/kotlin/com/pitchedapps/frost/kotlin/Flyweight.kt +++ b/app/src/main/kotlin/com/pitchedapps/frost/kotlin/Flyweight.kt @@ -36,19 +36,16 @@ import java.util.concurrent.ConcurrentHashMap */ class Flyweight( val scope: CoroutineScope, - capacity: Int, val maxAge: Long, private val fetcher: suspend (K) -> V ) { // Receives a key and a pending request - private val actionChannel = Channel>>(capacity) + private val actionChannel = Channel>>(Channel.UNLIMITED) // Receives a key to invalidate the associated value - private val invalidatorChannel = Channel(capacity) - // Receives a key to fetch the value - private val requesterChannel = Channel(capacity) + private val invalidatorChannel = Channel(Channel.UNLIMITED) // Receives a key and the resulting value - private val receiverChannel = Channel>>(capacity) + private val receiverChannel = Channel>>(Channel.UNLIMITED) // Keeps track of keys and associated update times private val conditionMap: MutableMap = mutableMapOf() @@ -85,7 +82,7 @@ class Flyweight( val valueRequestPending = key in pendingMap pendingMap.getOrPut(key) { mutableListOf() }.add(completable) if (!valueRequestPending) - requesterChannel.send(key) + fulfill(key) } } /* @@ -102,7 +99,7 @@ class Flyweight( resultMap.remove(key) if (pendingMap[key]?.isNotEmpty() == true) // Refetch value for pending requests - requesterChannel.send(key) + fulfill(key) } /* * Value request fulfilled. Should now fulfill pending requests @@ -117,28 +114,32 @@ class Flyweight( } } } - launch { - /* - * Value request received. Should fetch new value using supplied fetcher - */ - for (key in requesterChannel) { - val result = runCatching { - fetcher(key) - } - receiverChannel.send(key to result) - } + } + } + + /* + * Value request received. Should fetch new value using supplied fetcher + */ + private fun fulfill(key: K) { + scope.launch { + val result = runCatching { + fetcher(key) } + receiverChannel.send(key to result) } } /** * Queues the request, and returns a completable once it is sent to a channel. - * The fetcher will only be suspended if the channels are full + * The fetcher will only be suspended if the channels are full. + * + * Note that if the job is already inactive, a cancellation exception will be thrown. + * The message may default to the message for all completables under a cancelled job */ - suspend fun fetch(key: K): CompletableDeferred { + fun fetch(key: K): CompletableDeferred { val completable = CompletableDeferred(job) - if (!job.isActive) completable.completeExceptionally(IllegalStateException("Flyweight is not active")) - else actionChannel.send(key to completable) + if (!job.isActive) completable.completeExceptionally(CancellationException("Flyweight is not active")) + else actionChannel.offer(key to completable) return completable } @@ -155,7 +156,6 @@ class Flyweight( } actionChannel.close() invalidatorChannel.close() - requesterChannel.close() receiverChannel.close() conditionMap.clear() resultMap.clear() diff --git a/app/src/test/kotlin/com/pitchedapps/frost/kotlin/FlyweightTest.kt b/app/src/test/kotlin/com/pitchedapps/frost/kotlin/FlyweightTest.kt index 79f81002..d1d976b6 100644 --- a/app/src/test/kotlin/com/pitchedapps/frost/kotlin/FlyweightTest.kt +++ b/app/src/test/kotlin/com/pitchedapps/frost/kotlin/FlyweightTest.kt @@ -16,8 +16,8 @@ */ package com.pitchedapps.frost.kotlin +import kotlinx.coroutines.CancellationException import kotlinx.coroutines.GlobalScope -import kotlinx.coroutines.async import kotlinx.coroutines.runBlocking import org.junit.Rule import org.junit.rules.Timeout @@ -25,6 +25,7 @@ import java.util.concurrent.atomic.AtomicInteger import kotlin.test.BeforeTest import kotlin.test.Test import kotlin.test.assertEquals +import kotlin.test.assertFalse import kotlin.test.assertTrue import kotlin.test.fail @@ -42,7 +43,7 @@ class FlyweightTest { @BeforeTest fun before() { callCount = AtomicInteger(0) - flyweight = Flyweight(GlobalScope, 100, 200L) { + flyweight = Flyweight(GlobalScope, 200L) { callCount.incrementAndGet() when (it) { LONG_RUNNING_KEY -> Thread.sleep(100000) @@ -97,24 +98,19 @@ class FlyweightTest { @Test fun destroy() { runBlocking { - val longRunningResult = async { flyweight.fetch(LONG_RUNNING_KEY) } + val longRunningResult = flyweight.fetch(LONG_RUNNING_KEY) flyweight.fetch(1).await() flyweight.cancel() try { flyweight.fetch(1).await() fail("Flyweight should not be fulfilled after it is destroyed") - } catch (e: Exception) { - assertEquals("Flyweight is not active", e.message, "Incorrect error found on fetch after destruction") + } catch (ignore: CancellationException) { } try { + assertFalse(longRunningResult.isActive, "Long running result should no longer be active") longRunningResult.await() fail("Flyweight should have cancelled previously running requests") - } catch (e: Exception) { - assertEquals( - "Flyweight cancelled", - e.message, - "Incorrect error found on fetch cancelled by destruction" - ) + } catch (ignore: CancellationException) { } } } -- cgit v1.2.3 From 96418eb38691b634bb176435b72b49971dc07c27 Mon Sep 17 00:00:00 2001 From: Allan Wang Date: Fri, 4 Jan 2019 13:53:45 -0500 Subject: Prepare new test for unique only --- .../com/pitchedapps/frost/utils/CoroutineTest.kt | 39 ++++++++++++++++++++++ 1 file changed, 39 insertions(+) diff --git a/app/src/test/kotlin/com/pitchedapps/frost/utils/CoroutineTest.kt b/app/src/test/kotlin/com/pitchedapps/frost/utils/CoroutineTest.kt index 72eb6076..32a781df 100644 --- a/app/src/test/kotlin/com/pitchedapps/frost/utils/CoroutineTest.kt +++ b/app/src/test/kotlin/com/pitchedapps/frost/utils/CoroutineTest.kt @@ -16,8 +16,10 @@ */ package com.pitchedapps.frost.utils +import kotlinx.coroutines.CompletableDeferred import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.ExperimentalCoroutinesApi +import kotlinx.coroutines.GlobalScope import kotlinx.coroutines.asCoroutineDispatcher import kotlinx.coroutines.async import kotlinx.coroutines.channels.BroadcastChannel @@ -31,6 +33,7 @@ import kotlinx.coroutines.runBlocking import kotlinx.coroutines.withContext import java.util.concurrent.Executors import kotlin.coroutines.EmptyCoroutineContext +import kotlin.test.Ignore import kotlin.test.Test import kotlin.test.assertEquals import kotlin.test.assertFalse @@ -206,4 +209,40 @@ class CoroutineTest { ) } } + + /** + * When using [uniqueOnly] for channels with limited capacity, + * the duplicates should not count towards the actual capacity + */ + @Ignore("Not yet working as unique only buffered removes the capacity limitation of the channel") + @Test + fun uniqueOnlyBuffer() { + val completable = CompletableDeferred() + val channel = Channel(3) + runBlocking { + + val deferred = async { + listen(channel.uniqueOnly(GlobalScope)) { + // Throttle consumer + delay(50) + return@listen false + } + } + + listOf(0, 1, 1, 1, 1, 1, 2, 2, 2).forEach { + delay(10) + channel.offer(it) + } + + channel.close() + + val data = deferred.await() + + assertEquals( + listOf(0, 1, 2), + data, + "Unique receiver should not have two consecutive events that are equal" + ) + } + } } -- cgit v1.2.3 From a1cf58e0eee8d16576380e05a8d87e128242bf05 Mon Sep 17 00:00:00 2001 From: Allan Wang Date: Fri, 4 Jan 2019 13:56:12 -0500 Subject: Use coroutine version in kau --- app/build.gradle | 2 +- app/src/test/kotlin/com/pitchedapps/frost/utils/CoroutineTest.kt | 2 -- gradle.properties | 2 -- 3 files changed, 1 insertion(+), 5 deletions(-) diff --git a/app/build.gradle b/app/build.gradle index 562de936..0cb401fb 100644 --- a/app/build.gradle +++ b/app/build.gradle @@ -204,7 +204,7 @@ dependencies { // androidTestImplementation "io.mockk:mockk:${MOCKK}" - implementation "org.jetbrains.kotlinx:kotlinx-coroutines-android:${COROUTINES}" + implementation "org.jetbrains.kotlinx:kotlinx-coroutines-android:${kau.coroutines}" implementation "org.apache.commons:commons-text:${COMMONS_TEXT}" diff --git a/app/src/test/kotlin/com/pitchedapps/frost/utils/CoroutineTest.kt b/app/src/test/kotlin/com/pitchedapps/frost/utils/CoroutineTest.kt index 32a781df..e7520794 100644 --- a/app/src/test/kotlin/com/pitchedapps/frost/utils/CoroutineTest.kt +++ b/app/src/test/kotlin/com/pitchedapps/frost/utils/CoroutineTest.kt @@ -16,7 +16,6 @@ */ package com.pitchedapps.frost.utils -import kotlinx.coroutines.CompletableDeferred import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.ExperimentalCoroutinesApi import kotlinx.coroutines.GlobalScope @@ -217,7 +216,6 @@ class CoroutineTest { @Ignore("Not yet working as unique only buffered removes the capacity limitation of the channel") @Test fun uniqueOnlyBuffer() { - val completable = CompletableDeferred() val channel = Channel(3) runBlocking { diff --git a/gradle.properties b/gradle.properties index 11127a3e..dcdb5630 100644 --- a/gradle.properties +++ b/gradle.properties @@ -23,8 +23,6 @@ ANDROID_GRADLE=3.2.1 # https://github.com/diffplug/spotless/blob/master/plugin-gradle/CHANGES.md SPOTLESS=3.17.0 -# https://github.com/Kotlin/kotlinx.coroutines/releases -COROUTINES=1.0.1 # https://github.com/bugsnag/bugsnag-android/releases BUGSNAG=4.9.3 # https://github.com/bugsnag/bugsnag-android-gradle-plugin/releases -- cgit v1.2.3 From 5d2722b2205e404ee90cbb7e141ac63ca6570e1a Mon Sep 17 00:00:00 2001 From: Allan Wang Date: Fri, 4 Jan 2019 23:59:30 -0500 Subject: Remove unused dependencies and remove reactivex from username fetcher --- app/build.gradle | 4 +-- .../com/pitchedapps/frost/dbflow/CookiesDb.kt | 28 +++++++----------- .../frost/facebook/requests/FbRequestTest.kt | 2 +- .../com/pitchedapps/frost/internal/Internal.kt | 34 ---------------------- gradle.properties | 6 ++-- 5 files changed, 16 insertions(+), 58 deletions(-) diff --git a/app/build.gradle b/app/build.gradle index 0cb401fb..23d26fd8 100644 --- a/app/build.gradle +++ b/app/build.gradle @@ -253,8 +253,8 @@ dependencies { //Reactive Libs implementation "io.reactivex.rxjava2:rxjava:${RX_JAVA}" - implementation "io.reactivex.rxjava2:rxkotlin:${RX_KOTLIN}" - implementation "io.reactivex.rxjava2:rxandroid:${RX_ANDROID}" +// implementation "io.reactivex.rxjava2:rxkotlin:${RX_KOTLIN}" +// implementation "io.reactivex.rxjava2:rxandroid:${RX_ANDROID}" implementation "com.github.pwittchen:reactivenetwork-rx2:${RX_NETWORK}" } diff --git a/app/src/main/kotlin/com/pitchedapps/frost/dbflow/CookiesDb.kt b/app/src/main/kotlin/com/pitchedapps/frost/dbflow/CookiesDb.kt index f7d97833..d7dd71ed 100644 --- a/app/src/main/kotlin/com/pitchedapps/frost/dbflow/CookiesDb.kt +++ b/app/src/main/kotlin/com/pitchedapps/frost/dbflow/CookiesDb.kt @@ -17,7 +17,7 @@ package com.pitchedapps.frost.dbflow import android.os.Parcelable -import com.github.pwittchen.reactivenetwork.library.rx2.ReactiveNetwork +import com.pitchedapps.frost.dbflow.CookieModel_Table.cookie import com.pitchedapps.frost.facebook.FbItem import com.pitchedapps.frost.utils.L import com.pitchedapps.frost.utils.frostJsoup @@ -34,11 +34,10 @@ import com.raizlabs.android.dbflow.kotlinextensions.save import com.raizlabs.android.dbflow.kotlinextensions.select import com.raizlabs.android.dbflow.kotlinextensions.where import com.raizlabs.android.dbflow.structure.BaseModel -import io.reactivex.disposables.Disposable -import io.reactivex.schedulers.Schedulers import kotlinx.android.parcel.Parcelize import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.withContext +import kotlinx.coroutines.withTimeoutOrNull import java.net.UnknownHostException /** @@ -98,25 +97,20 @@ fun removeCookie(id: Long) { } } -inline fun CookieModel.fetchUsername(crossinline callback: (String) -> Unit): Disposable = - ReactiveNetwork.checkInternetConnectivity().subscribeOn(Schedulers.io()).subscribe { yes, _ -> - if (!yes) return@subscribe callback("") - var result = "" +suspend fun CookieModel.fetchUsername(): String? = withContext(Dispatchers.IO) { + withTimeoutOrNull(5000) { + var result: String? = null try { result = frostJsoup(cookie, FbItem.PROFILE.url).title() L.d { "Fetch username found" } } catch (e: Exception) { if (e !is UnknownHostException) e.logFrostEvent("Fetch username failed") - } finally { - if (result.isBlank() && (name?.isNotBlank() == true)) { - callback(name!!) - return@subscribe - } - if (name != result) { - name = result - saveFbCookie(this@fetchUsername) - } - callback(result) } + if (name?.isNotBlank() == false && result != null && result != name) { + name = result + saveFbCookie(this@fetchUsername) + } + result } +} \ No newline at end of file diff --git a/app/src/test/kotlin/com/pitchedapps/frost/facebook/requests/FbRequestTest.kt b/app/src/test/kotlin/com/pitchedapps/frost/facebook/requests/FbRequestTest.kt index ec765448..8610436a 100644 --- a/app/src/test/kotlin/com/pitchedapps/frost/facebook/requests/FbRequestTest.kt +++ b/app/src/test/kotlin/com/pitchedapps/frost/facebook/requests/FbRequestTest.kt @@ -84,7 +84,7 @@ class FbRequestTest { val data = AUTH.getMenuData().invoke() assertNotNull(data) println(ObjectMapper().writerWithDefaultPrettyPrinter().writeValueAsString(data)) - assertTrue(data!!.data.isNotEmpty()) + assertTrue(data.data.isNotEmpty()) assertTrue(data.footer.hasContent, "Footer may be badly parsed") val items = data.flatMapValid() assertTrue(items.size > 15, "Something may be badly parsed") diff --git a/app/src/test/kotlin/com/pitchedapps/frost/internal/Internal.kt b/app/src/test/kotlin/com/pitchedapps/frost/internal/Internal.kt index 061e7c38..b8d9635a 100644 --- a/app/src/test/kotlin/com/pitchedapps/frost/internal/Internal.kt +++ b/app/src/test/kotlin/com/pitchedapps/frost/internal/Internal.kt @@ -22,13 +22,10 @@ import com.pitchedapps.frost.facebook.get import com.pitchedapps.frost.facebook.requests.RequestAuth import com.pitchedapps.frost.facebook.requests.getAuth import com.pitchedapps.frost.utils.frostJsoup -import io.reactivex.Completable import org.junit.Assume -import org.junit.Test import java.io.File import java.io.FileInputStream import java.util.Properties -import java.util.concurrent.TimeUnit import kotlin.reflect.full.starProjectedType import kotlin.test.assertEquals import kotlin.test.assertTrue @@ -97,34 +94,3 @@ fun Any.assertComponentsNotEmpty() { fun > List.assertDescending(tag: String) { assertEquals(sortedDescending(), this, "$tag not sorted in descending order") } - -interface CompletableCallback { - fun onComplete() - fun onError(message: String) -} - -inline fun concurrentTest(crossinline caller: (callback: CompletableCallback) -> Unit) { - val result = Completable.create { emitter -> - caller(object : CompletableCallback { - override fun onComplete() = emitter.onComplete() - override fun onError(message: String) = emitter.onError(Throwable(message)) - }) - }.blockingGet(5, TimeUnit.SECONDS) - if (result != null) - throw RuntimeException("Concurrent fail: ${result.message}") -} - -class InternalTest { - @Test - fun concurrentTest() = try { - concurrentTest { result -> - Thread().run { - Thread.sleep(100) - result.onError("Intentional fail") - } - } - fail("Did not throw exception") - } catch (e: Exception) { - // pass - } -} diff --git a/gradle.properties b/gradle.properties index dcdb5630..691544bc 100644 --- a/gradle.properties +++ b/gradle.properties @@ -14,7 +14,7 @@ org.gradle.jvmargs=-Xmx2048m -XX:MaxPermSize=512m -XX:+HeapDumpOnOutOfMemoryErro APP_ID=Frost APP_GROUP=com.pitchedapps -KAU=af43e82 +KAU=72d6461 KOTLIN=1.3.11 # https://mvnrepository.com/artifact/com.android.tools.build/gradle?repo=google @@ -28,7 +28,7 @@ BUGSNAG=4.9.3 # https://github.com/bugsnag/bugsnag-android-gradle-plugin/releases BUGSNAG_PLUGIN=3.6.0 # https://github.com/KeepSafe/dexcount-gradle-plugin/releases -DEX_PLUGIN=0.8.4 +DEX_PLUGIN=0.8.5 # https://github.com/gladed/gradle-android-git-version/releases GIT_PLUGIN=0.4.7 # https://mvnrepository.com/artifact/org.apache.commons/commons-text @@ -59,8 +59,6 @@ OKHTTP=3.12.1 ROBOELECTRIC=4.1 # https://github.com/ReactiveX/RxAndroid/releases RX_ANDROID=2.1.0 -# https://github.com/JakeWharton/RxBinding/releases -RX_BINDING=2.2.0 # https://github.com/ReactiveX/RxJava/releases RX_JAVA=2.2.4 # https://github.com/ReactiveX/RxKotlin/releases -- cgit v1.2.3 From 765c74196042430bb2c5b1a0d522da20a4485dc4 Mon Sep 17 00:00:00 2001 From: Allan Wang Date: Fri, 4 Jan 2019 23:59:55 -0500 Subject: Remove unnecessary zip function with reactivex --- .../test/kotlin/com/pitchedapps/frost/MiscTest.kt | 48 ---------------------- 1 file changed, 48 deletions(-) delete mode 100644 app/src/test/kotlin/com/pitchedapps/frost/MiscTest.kt diff --git a/app/src/test/kotlin/com/pitchedapps/frost/MiscTest.kt b/app/src/test/kotlin/com/pitchedapps/frost/MiscTest.kt deleted file mode 100644 index 20610b2a..00000000 --- a/app/src/test/kotlin/com/pitchedapps/frost/MiscTest.kt +++ /dev/null @@ -1,48 +0,0 @@ -/* - * Copyright 2018 Allan Wang - * - * 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 . - */ -package com.pitchedapps.frost - -import com.pitchedapps.frost.facebook.requests.zip -import org.junit.Test -import kotlin.test.assertTrue - -/** - * Created by Allan Wang on 2017-06-14. - */ -class MiscTest { - - /** - * Spin off 15 threads - * Pause each for 1 - 2s - * Ensure that total zipped process does not take over 5s - */ - @Test - fun zip() { - val now = System.currentTimeMillis() - val base = 1 - val data: LongArray = (0..15).map { Math.random() + base } - .toTypedArray().zip(List::toLongArray) { - Thread.sleep((it * 1000).toLong()) - System.currentTimeMillis() - now - }.blockingGet() - println(data.contentToString()) - assertTrue( - data.all { it >= base * 1000 && it < base * 1000 * 5 }, - "zip did not seem to work on different threads" - ) - } -} -- cgit v1.2.3 From 495df6c84bb7a0daf8cea789f03396b6dabcdf2d Mon Sep 17 00:00:00 2001 From: Allan Wang Date: Sat, 5 Jan 2019 00:10:25 -0500 Subject: Remove reactivex from debugger --- .../pitchedapps/frost/activities/DebugActivity.kt | 59 ++++++++++------------ .../pitchedapps/frost/activities/LoginActivity.kt | 23 +++++++-- .../com/pitchedapps/frost/dbflow/CookiesDb.kt | 24 --------- .../frost/facebook/requests/FbRequest.kt | 16 ------ .../com/pitchedapps/frost/web/DebugWebView.kt | 13 +++-- 5 files changed, 55 insertions(+), 80 deletions(-) diff --git a/app/src/main/kotlin/com/pitchedapps/frost/activities/DebugActivity.kt b/app/src/main/kotlin/com/pitchedapps/frost/activities/DebugActivity.kt index 6257e6f1..a1b41830 100644 --- a/app/src/main/kotlin/com/pitchedapps/frost/activities/DebugActivity.kt +++ b/app/src/main/kotlin/com/pitchedapps/frost/activities/DebugActivity.kt @@ -22,6 +22,7 @@ import android.content.Intent import android.content.res.ColorStateList import android.os.Bundle import ca.allanwang.kau.internal.KauBaseActivity +import ca.allanwang.kau.utils.launchMain import ca.allanwang.kau.utils.setIcon import ca.allanwang.kau.utils.visible import com.mikepenz.google_material_typeface_library.GoogleMaterial @@ -32,12 +33,12 @@ import com.pitchedapps.frost.utils.L import com.pitchedapps.frost.utils.Prefs import com.pitchedapps.frost.utils.createFreshDir import com.pitchedapps.frost.utils.setFrostColors -import io.reactivex.Single -import io.reactivex.android.schedulers.AndroidSchedulers -import io.reactivex.schedulers.Schedulers import kotlinx.android.synthetic.main.activity_debug.* import kotlinx.android.synthetic.main.view_main_fab.* +import kotlinx.coroutines.CoroutineExceptionHandler import java.io.File +import kotlin.coroutines.resume +import kotlin.coroutines.suspendCoroutine /** * Created by Allan Wang on 05/01/18. @@ -74,36 +75,32 @@ class DebugActivity : KauBaseActivity() { fab.setOnClickListener { _ -> fab.hide() - val parent = baseDir(this) - parent.createFreshDir() - val rxScreenshot = Single.fromCallable { - debug_webview.getScreenshot(File(parent, "screenshot.png")) - }.subscribeOn(Schedulers.io()) - val rxBody = Single.create { emitter -> - debug_webview.evaluateJavascript(JsActions.RETURN_BODY.function) { - emitter.onSuccess(it) - } - }.subscribeOn(AndroidSchedulers.mainThread()) - Single.zip(listOf(rxScreenshot, rxBody)) { - val screenshot = it[0] == true - val body = it[1] as? String - screenshot to body - }.observeOn(AndroidSchedulers.mainThread()) - .subscribe { (screenshot, body), err -> - if (err != null) { - L.e { "DebugActivity error ${err.message}" } - setResult(Activity.RESULT_CANCELED) - finish() - return@subscribe + val errorHandler = CoroutineExceptionHandler { _, throwable -> + L.e { "DebugActivity error ${throwable.message}" } + setResult(Activity.RESULT_CANCELED) + finish() + } + + launchMain(errorHandler) { + val parent = baseDir(this@DebugActivity) + parent.createFreshDir() + + val body: String? = suspendCoroutine { cont -> + debug_webview.evaluateJavascript(JsActions.RETURN_BODY.function) { + cont.resume(it) } - val intent = Intent() - intent.putExtra(RESULT_URL, debug_webview.url) - intent.putExtra(RESULT_SCREENSHOT, screenshot) - if (body != null) - intent.putExtra(RESULT_BODY, body) - setResult(Activity.RESULT_OK, intent) - finish() } + + val hasScreenshot: Boolean = debug_webview.getScreenshot(File(parent, "screenshot.png")) + + val intent = Intent() + intent.putExtra(RESULT_URL, debug_webview.url) + intent.putExtra(RESULT_SCREENSHOT, hasScreenshot) + if (body != null) + intent.putExtra(RESULT_BODY, body) + setResult(Activity.RESULT_OK, intent) + finish() + } } } diff --git a/app/src/main/kotlin/com/pitchedapps/frost/activities/LoginActivity.kt b/app/src/main/kotlin/com/pitchedapps/frost/activities/LoginActivity.kt index 27a488e3..f3eb8fe6 100644 --- a/app/src/main/kotlin/com/pitchedapps/frost/activities/LoginActivity.kt +++ b/app/src/main/kotlin/com/pitchedapps/frost/activities/LoginActivity.kt @@ -33,9 +33,10 @@ import com.bumptech.glide.request.RequestListener import com.bumptech.glide.request.target.Target import com.pitchedapps.frost.R import com.pitchedapps.frost.dbflow.CookieModel -import com.pitchedapps.frost.dbflow.fetchUsername import com.pitchedapps.frost.dbflow.loadFbCookiesSuspend +import com.pitchedapps.frost.dbflow.saveFbCookie import com.pitchedapps.frost.facebook.FbCookie +import com.pitchedapps.frost.facebook.FbItem import com.pitchedapps.frost.facebook.profilePictureUrl import com.pitchedapps.frost.glide.FrostGlide import com.pitchedapps.frost.glide.GlideApp @@ -43,6 +44,7 @@ import com.pitchedapps.frost.glide.transform import com.pitchedapps.frost.utils.L import com.pitchedapps.frost.utils.Showcase import com.pitchedapps.frost.utils.frostEvent +import com.pitchedapps.frost.utils.frostJsoup import com.pitchedapps.frost.utils.launchNewTask import com.pitchedapps.frost.utils.logFrostEvent import com.pitchedapps.frost.utils.setFrostColors @@ -55,6 +57,8 @@ import kotlinx.coroutines.delay import kotlinx.coroutines.launch import kotlinx.coroutines.suspendCancellableCoroutine import kotlinx.coroutines.withContext +import kotlinx.coroutines.withTimeout +import java.net.UnknownHostException import kotlin.coroutines.resume /** @@ -168,11 +172,22 @@ class LoginActivity : BaseActivity() { } private suspend fun loadUsername(cookie: CookieModel): String = withContext(Dispatchers.IO) { - suspendCancellableCoroutine { cont -> - cookie.fetchUsername { - cont.resume(it) + val result: String = try { + withTimeout(5000) { + frostJsoup(cookie.cookie, FbItem.PROFILE.url).title() } + } catch (e: Exception) { + if (e !is UnknownHostException) + e.logFrostEvent("Fetch username failed") + "" } + + if (cookie.name?.isNotBlank() == false && result != cookie.name) { + cookie.name = result + saveFbCookie(cookie) + } + + cookie.name ?: "" } override fun backConsumer(): Boolean { diff --git a/app/src/main/kotlin/com/pitchedapps/frost/dbflow/CookiesDb.kt b/app/src/main/kotlin/com/pitchedapps/frost/dbflow/CookiesDb.kt index d7dd71ed..10746b75 100644 --- a/app/src/main/kotlin/com/pitchedapps/frost/dbflow/CookiesDb.kt +++ b/app/src/main/kotlin/com/pitchedapps/frost/dbflow/CookiesDb.kt @@ -17,11 +17,7 @@ package com.pitchedapps.frost.dbflow import android.os.Parcelable -import com.pitchedapps.frost.dbflow.CookieModel_Table.cookie -import com.pitchedapps.frost.facebook.FbItem import com.pitchedapps.frost.utils.L -import com.pitchedapps.frost.utils.frostJsoup -import com.pitchedapps.frost.utils.logFrostEvent import com.raizlabs.android.dbflow.annotation.ConflictAction import com.raizlabs.android.dbflow.annotation.Database import com.raizlabs.android.dbflow.annotation.PrimaryKey @@ -37,8 +33,6 @@ import com.raizlabs.android.dbflow.structure.BaseModel import kotlinx.android.parcel.Parcelize import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.withContext -import kotlinx.coroutines.withTimeoutOrNull -import java.net.UnknownHostException /** * Created by Allan Wang on 2017-05-30. @@ -95,22 +89,4 @@ fun removeCookie(id: Long) { L.d { "Fb cookie deleted" } L._d { id } } -} - -suspend fun CookieModel.fetchUsername(): String? = withContext(Dispatchers.IO) { - withTimeoutOrNull(5000) { - var result: String? = null - try { - result = frostJsoup(cookie, FbItem.PROFILE.url).title() - L.d { "Fetch username found" } - } catch (e: Exception) { - if (e !is UnknownHostException) - e.logFrostEvent("Fetch username failed") - } - if (name?.isNotBlank() == false && result != null && result != name) { - name = result - saveFbCookie(this@fetchUsername) - } - result - } } \ No newline at end of file diff --git a/app/src/main/kotlin/com/pitchedapps/frost/facebook/requests/FbRequest.kt b/app/src/main/kotlin/com/pitchedapps/frost/facebook/requests/FbRequest.kt index 67a03ad4..b49fd970 100644 --- a/app/src/main/kotlin/com/pitchedapps/frost/facebook/requests/FbRequest.kt +++ b/app/src/main/kotlin/com/pitchedapps/frost/facebook/requests/FbRequest.kt @@ -26,8 +26,6 @@ import com.pitchedapps.frost.facebook.USER_AGENT_BASIC import com.pitchedapps.frost.facebook.get import com.pitchedapps.frost.kotlin.Flyweight import com.pitchedapps.frost.utils.L -import io.reactivex.Single -import io.reactivex.schedulers.Schedulers import kotlinx.coroutines.GlobalScope import okhttp3.Call import okhttp3.FormBody @@ -35,7 +33,6 @@ import okhttp3.OkHttpClient import okhttp3.Request import okhttp3.logging.HttpLoggingInterceptor import org.apache.commons.text.StringEscapeUtils -import java.lang.Exception /** * Created by Allan Wang on 21/12/17. @@ -142,19 +139,6 @@ fun String.getAuth(): RequestAuth { return auth } -inline fun Array.zip( - crossinline mapper: (List) -> O, - crossinline caller: (T) -> R -): Single { - if (isEmpty()) - return Single.just(mapper(emptyList())) - val singles = map { Single.fromCallable { caller(it) }.subscribeOn(Schedulers.io()) } - return Single.zip(singles) { - val results = it.mapNotNull { it as? R } - mapper(results) - } -} - /** * Execute the call and attempt to check validity * Valid = not blank & no "error" instance diff --git a/app/src/main/kotlin/com/pitchedapps/frost/web/DebugWebView.kt b/app/src/main/kotlin/com/pitchedapps/frost/web/DebugWebView.kt index 22668309..d2b53ab5 100644 --- a/app/src/main/kotlin/com/pitchedapps/frost/web/DebugWebView.kt +++ b/app/src/main/kotlin/com/pitchedapps/frost/web/DebugWebView.kt @@ -23,7 +23,6 @@ import android.graphics.Color import android.util.AttributeSet import android.view.View import android.webkit.WebView -import androidx.annotation.WorkerThread import ca.allanwang.kau.utils.withAlpha import com.pitchedapps.frost.facebook.USER_AGENT_BASIC import com.pitchedapps.frost.injectors.CssAssets @@ -33,6 +32,8 @@ import com.pitchedapps.frost.utils.L import com.pitchedapps.frost.utils.Prefs import com.pitchedapps.frost.utils.createFreshFile import com.pitchedapps.frost.utils.isFacebookUrl +import kotlinx.coroutines.Dispatchers +import kotlinx.coroutines.withContext import java.io.File /** @@ -61,14 +62,16 @@ class DebugWebView @JvmOverloads constructor( isDrawingCacheEnabled = true } - @WorkerThread - fun getScreenshot(output: File): Boolean { + /** + * Fetches a screenshot of the current webview, returning true if successful, false otherwise. + */ + suspend fun getScreenshot(output: File): Boolean = withContext(Dispatchers.IO) { if (!output.createFreshFile()) { L.e { "Failed to create ${output.absolutePath} for debug screenshot" } - return false + return@withContext false } - return try { + try { output.outputStream().use { drawingCache.compress(Bitmap.CompressFormat.PNG, 100, it) } -- cgit v1.2.3 From c00aab03622a5045cf01b4b3883c7f7b835c841e Mon Sep 17 00:00:00 2001 From: Allan Wang Date: Sat, 5 Jan 2019 00:11:15 -0500 Subject: Remove remaining reactivex components --- .../main/kotlin/com/pitchedapps/frost/FrostApp.kt | 12 ------- .../pitchedapps/frost/activities/BaseActivity.kt | 38 ---------------------- 2 files changed, 50 deletions(-) diff --git a/app/src/main/kotlin/com/pitchedapps/frost/FrostApp.kt b/app/src/main/kotlin/com/pitchedapps/frost/FrostApp.kt index 20ad46d9..5b62afad 100644 --- a/app/src/main/kotlin/com/pitchedapps/frost/FrostApp.kt +++ b/app/src/main/kotlin/com/pitchedapps/frost/FrostApp.kt @@ -44,9 +44,6 @@ import com.raizlabs.android.dbflow.config.DatabaseConfig import com.raizlabs.android.dbflow.config.FlowConfig import com.raizlabs.android.dbflow.config.FlowManager import com.raizlabs.android.dbflow.runtime.ContentResolverNotifier -import io.reactivex.exceptions.UndeliverableException -import io.reactivex.plugins.RxJavaPlugins -import java.net.SocketTimeoutException import java.util.Random import kotlin.reflect.KClass @@ -135,15 +132,6 @@ class FrostApp : Application() { L.d { "Activity ${activity.localClassName} created" } } }) - - RxJavaPlugins.setErrorHandler { - when (it) { - is SocketTimeoutException, is UndeliverableException -> - L.e { "RxJava common error ${it.message}" } - else -> - L.e(it) { "RxJava error" } - } - } } private fun initBugsnag() { diff --git a/app/src/main/kotlin/com/pitchedapps/frost/activities/BaseActivity.kt b/app/src/main/kotlin/com/pitchedapps/frost/activities/BaseActivity.kt index 5965e5cf..e514fa14 100644 --- a/app/src/main/kotlin/com/pitchedapps/frost/activities/BaseActivity.kt +++ b/app/src/main/kotlin/com/pitchedapps/frost/activities/BaseActivity.kt @@ -45,44 +45,6 @@ abstract class BaseActivity : KauBaseActivity() { if (this !is WebOverlayActivityBase) setFrostTheme() } - // -// private var networkDisposable: Disposable? = null -// private var networkConsumer: ((Connectivity) -> Unit)? = null -// -// fun setNetworkObserver(consumer: (connectivity: Connectivity) -> Unit) { -// this.networkConsumer = consumer -// } -// -// private fun observeNetworkConnectivity() { -// val consumer = networkConsumer ?: return -// networkDisposable = ReactiveNetwork.observeNetworkConnectivity(applicationContext) -// .subscribeOn(Schedulers.io()) -// .observeOn(AndroidSchedulers.mainThread()) -// .subscribe { connectivity: Connectivity -> -// connectivity.apply { -// L.d{"Network connectivity changed: isAvailable: $isAvailable isRoaming: $isRoaming"} -// consumer(connectivity) -// } -// } -// } -// -// private fun disposeNetworkConnectivity() { -// if (networkDisposable?.isDisposed == false) -// networkDisposable?.dispose() -// networkDisposable = null -// } -// -// override fun onResume() { -// super.onResume() -//// disposeNetworkConnectivity() -//// observeNetworkConnectivity() -// } -// -// override fun onPause() { -// super.onPause() -//// disposeNetworkConnectivity() -// } - override fun onStop() { if (this is VideoViewHolder) videoOnStop() super.onStop() -- cgit v1.2.3 From 635bdddebbc52ec67cfb157830c3fc8b32f9a6e7 Mon Sep 17 00:00:00 2001 From: Allan Wang Date: Sat, 5 Jan 2019 00:12:45 -0500 Subject: Remove all rx libraries --- app/build.gradle | 6 ------ .../main/kotlin/com/pitchedapps/frost/activities/AboutActivity.kt | 3 --- app/src/main/kotlin/com/pitchedapps/frost/dbflow/CookiesDb.kt | 2 +- app/src/main/kotlin/com/pitchedapps/frost/glide/GlideUtils.kt | 1 - gradle.properties | 8 -------- 5 files changed, 1 insertion(+), 19 deletions(-) diff --git a/app/build.gradle b/app/build.gradle index 23d26fd8..5fc249bf 100644 --- a/app/build.gradle +++ b/app/build.gradle @@ -251,12 +251,6 @@ dependencies { implementation "com.sothree.slidinguppanel:library:${SLIDING_PANEL}" - //Reactive Libs - implementation "io.reactivex.rxjava2:rxjava:${RX_JAVA}" -// implementation "io.reactivex.rxjava2:rxkotlin:${RX_KOTLIN}" -// implementation "io.reactivex.rxjava2:rxandroid:${RX_ANDROID}" - implementation "com.github.pwittchen:reactivenetwork-rx2:${RX_NETWORK}" - } // Validates code and generates apk diff --git a/app/src/main/kotlin/com/pitchedapps/frost/activities/AboutActivity.kt b/app/src/main/kotlin/com/pitchedapps/frost/activities/AboutActivity.kt index 283477d7..8d849bff 100644 --- a/app/src/main/kotlin/com/pitchedapps/frost/activities/AboutActivity.kt +++ b/app/src/main/kotlin/com/pitchedapps/frost/activities/AboutActivity.kt @@ -66,9 +66,7 @@ class AboutActivity : AboutActivityBase(null, { val include = arrayOf( "AboutLibraries", "AndroidIconics", - "androidin_appbillingv3", "androidslidinguppanel", - "Crashlytics", "dbflow", "fastadapter", "glide", @@ -77,7 +75,6 @@ class AboutActivity : AboutActivityBase(null, { "kotterknife", "materialdialogs", "materialdrawer", - "rxjava", "subsamplingscaleimageview" ) diff --git a/app/src/main/kotlin/com/pitchedapps/frost/dbflow/CookiesDb.kt b/app/src/main/kotlin/com/pitchedapps/frost/dbflow/CookiesDb.kt index 10746b75..67953144 100644 --- a/app/src/main/kotlin/com/pitchedapps/frost/dbflow/CookiesDb.kt +++ b/app/src/main/kotlin/com/pitchedapps/frost/dbflow/CookiesDb.kt @@ -89,4 +89,4 @@ fun removeCookie(id: Long) { L.d { "Fb cookie deleted" } L._d { id } } -} \ No newline at end of file +} diff --git a/app/src/main/kotlin/com/pitchedapps/frost/glide/GlideUtils.kt b/app/src/main/kotlin/com/pitchedapps/frost/glide/GlideUtils.kt index cceabcae..870e2ccd 100644 --- a/app/src/main/kotlin/com/pitchedapps/frost/glide/GlideUtils.kt +++ b/app/src/main/kotlin/com/pitchedapps/frost/glide/GlideUtils.kt @@ -27,7 +27,6 @@ import com.bumptech.glide.load.resource.bitmap.CircleCrop import com.bumptech.glide.module.AppGlideModule import com.bumptech.glide.request.RequestOptions import com.pitchedapps.frost.facebook.FbCookie -import com.pitchedapps.frost.utils.L import okhttp3.Interceptor import okhttp3.OkHttpClient import okhttp3.Response diff --git a/gradle.properties b/gradle.properties index 691544bc..aabfd255 100644 --- a/gradle.properties +++ b/gradle.properties @@ -57,14 +57,6 @@ MATERIAL_DRAWER_KT=2.0.1 OKHTTP=3.12.1 # http://robolectric.org/getting-started/ ROBOELECTRIC=4.1 -# https://github.com/ReactiveX/RxAndroid/releases -RX_ANDROID=2.1.0 -# https://github.com/ReactiveX/RxJava/releases -RX_JAVA=2.2.4 -# https://github.com/ReactiveX/RxKotlin/releases -RX_KOTLIN=2.3.0 -# https://github.com/pwittchen/ReactiveNetwork/releases -RX_NETWORK=2.1.0 # https://github.com/davemorrissey/subsampling-scale-image-view#quick-start SCALE_IMAGE_VIEW=3.10.0 # https://github.com/umano/AndroidSlidingUpPanel#importing-the-library -- cgit v1.2.3