From 7c7dff743bec641d8df7ca8e8f06249ad6314f08 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Kuba=20Szczodrzy=C5=84ski?= Date: Fri, 13 Mar 2020 16:22:43 +0100 Subject: [PATCH] [API] Optimize App Sync a bit. --- .../pl/szczodrzynski/edziennik/Extensions.kt | 2 + .../szczodrzynski/edziennik/MainActivity.kt | 2 + .../szczodrzynski/edziennik/config/Config.kt | 5 -- .../edziennik/config/ConfigSync.kt | 10 ++++ .../edziennik/data/api/ApiService.kt | 3 +- .../edziennik/data/api/szkolny/SzkolnyApi.kt | 47 +++++++++---------- .../api/szkolny/request/ServerSyncRequest.kt | 2 + .../szkolny/response/ServerSyncResponse.kt | 5 +- .../edziennik/data/api/task/AppSync.kt | 37 +++++++-------- .../edziennik/data/api/task/SzkolnyTask.kt | 15 ++++-- .../ui/modules/webpush/WebPushFragment.kt | 2 + 11 files changed, 75 insertions(+), 55 deletions(-) diff --git a/app/src/main/java/pl/szczodrzynski/edziennik/Extensions.kt b/app/src/main/java/pl/szczodrzynski/edziennik/Extensions.kt index df60318f..cff0645e 100644 --- a/app/src/main/java/pl/szczodrzynski/edziennik/Extensions.kt +++ b/app/src/main/java/pl/szczodrzynski/edziennik/Extensions.kt @@ -51,6 +51,7 @@ import okhttp3.RequestBody import okhttp3.TlsVersion import okio.Buffer import pl.szczodrzynski.edziennik.data.api.* +import pl.szczodrzynski.edziennik.data.api.models.ApiError import pl.szczodrzynski.edziennik.data.api.szkolny.SzkolnyApiException import pl.szczodrzynski.edziennik.data.api.szkolny.response.ApiResponse import pl.szczodrzynski.edziennik.data.db.entity.Notification @@ -1080,6 +1081,7 @@ fun Throwable.toErrorCode() = when (this) { private fun ApiResponse.Error.toErrorCode() = when (this.code) { else -> ERROR_API_EXCEPTION } +fun Throwable.toApiError(tag: String) = ApiError.fromThrowable(tag, this) inline fun ifNotNull(a: A?, b: B?, code: (A, B) -> R): R? { if (a != null && b != null) { diff --git a/app/src/main/java/pl/szczodrzynski/edziennik/MainActivity.kt b/app/src/main/java/pl/szczodrzynski/edziennik/MainActivity.kt index ce850b9a..99a631af 100644 --- a/app/src/main/java/pl/szczodrzynski/edziennik/MainActivity.kt +++ b/app/src/main/java/pl/szczodrzynski/edziennik/MainActivity.kt @@ -444,6 +444,8 @@ class MainActivity : AppCompatActivity(), CoroutineScope { // WHAT'S NEW DIALOG if (app.config.appVersion < BuildConfig.VERSION_CODE) { + // force an AppSync after update + app.config.sync.lastAppSync = 0L ChangelogDialog(this) if (app.config.appVersion < 170) { //Intent intent = new Intent(this, ChangelogIntroActivity.class); diff --git a/app/src/main/java/pl/szczodrzynski/edziennik/config/Config.kt b/app/src/main/java/pl/szczodrzynski/edziennik/config/Config.kt index 0b10c35b..bf4f743f 100644 --- a/app/src/main/java/pl/szczodrzynski/edziennik/config/Config.kt +++ b/app/src/main/java/pl/szczodrzynski/edziennik/config/Config.kt @@ -105,11 +105,6 @@ class Config(val db: AppDb) : CoroutineScope, AbstractConfig { get() { mWidgetConfigs = mWidgetConfigs ?: values.get("widgetConfigs", JsonObject()); return mWidgetConfigs ?: JsonObject() } set(value) { set("widgetConfigs", value); mWidgetConfigs = value } - private var mLastAppSync: Long? = null - var lastAppSync: Long - get() { mLastAppSync = mLastAppSync ?: values.get("lastAppSync", 0L); return mLastAppSync ?: 0L } - set(value) { set("lastAppSync", value); mLastAppSync = value } - private var rawEntries: List = db.configDao().getAllNow() private val profileConfigs: HashMap = hashMapOf() init { diff --git a/app/src/main/java/pl/szczodrzynski/edziennik/config/ConfigSync.kt b/app/src/main/java/pl/szczodrzynski/edziennik/config/ConfigSync.kt index d8d5ccb5..a6bb68df 100644 --- a/app/src/main/java/pl/szczodrzynski/edziennik/config/ConfigSync.kt +++ b/app/src/main/java/pl/szczodrzynski/edziennik/config/ConfigSync.kt @@ -20,6 +20,11 @@ class ConfigSync(private val config: Config) { get() { mSyncEnabled = mSyncEnabled ?: config.values.get("syncEnabled", true); return mSyncEnabled ?: true } set(value) { config.set("syncEnabled", value); mSyncEnabled = value } + private var mWebPushEnabled: Boolean? = null + var webPushEnabled: Boolean + get() { mWebPushEnabled = mWebPushEnabled ?: config.values.get("webPushEnabled", true); return mWebPushEnabled ?: true } + set(value) { config.set("webPushEnabled", value); mWebPushEnabled = value } + private var mSyncOnlyWifi: Boolean? = null var onlyWifi: Boolean get() { mSyncOnlyWifi = mSyncOnlyWifi ?: config.values.get("syncOnlyWifi", false); return mSyncOnlyWifi ?: notifyAboutUpdates } @@ -35,6 +40,11 @@ class ConfigSync(private val config: Config) { get() { mNotifyAboutUpdates = mNotifyAboutUpdates ?: config.values.get("notifyAboutUpdates", true); return mNotifyAboutUpdates ?: true } set(value) { config.set("notifyAboutUpdates", value); mNotifyAboutUpdates = value } + private var mLastAppSync: Long? = null + var lastAppSync: Long + get() { mLastAppSync = mLastAppSync ?: config.values.get("lastAppSync", 0L); return mLastAppSync ?: 0L } + set(value) { config.set("lastAppSync", value); mLastAppSync = value } + /* ____ _ _ _ / __ \ (_) | | | | | | | |_ _ _ ___| |_ | |__ ___ _ _ _ __ ___ diff --git a/app/src/main/java/pl/szczodrzynski/edziennik/data/api/ApiService.kt b/app/src/main/java/pl/szczodrzynski/edziennik/data/api/ApiService.kt index 7a8a3972..2b0b63f2 100644 --- a/app/src/main/java/pl/szczodrzynski/edziennik/data/api/ApiService.kt +++ b/app/src/main/java/pl/szczodrzynski/edziennik/data/api/ApiService.kt @@ -22,6 +22,7 @@ import pl.szczodrzynski.edziennik.data.api.task.ErrorReportTask import pl.szczodrzynski.edziennik.data.api.task.IApiTask import pl.szczodrzynski.edziennik.data.api.task.SzkolnyTask import pl.szczodrzynski.edziennik.data.db.entity.Profile +import pl.szczodrzynski.edziennik.toApiError import pl.szczodrzynski.edziennik.utils.Utils.d import kotlin.math.min import kotlin.math.roundToInt @@ -181,7 +182,7 @@ class ApiService : Service() { is SzkolnyTask -> task.run(taskCallback) } } catch (e: Exception) { - taskCallback.onError(ApiError(TAG, EXCEPTION_API_TASK).withThrowable(e)) + taskCallback.onError(e.toApiError(TAG)) } } diff --git a/app/src/main/java/pl/szczodrzynski/edziennik/data/api/szkolny/SzkolnyApi.kt b/app/src/main/java/pl/szczodrzynski/edziennik/data/api/szkolny/SzkolnyApi.kt index 13aaf807..eb70ab9a 100644 --- a/app/src/main/java/pl/szczodrzynski/edziennik/data/api/szkolny/SzkolnyApi.kt +++ b/app/src/main/java/pl/szczodrzynski/edziennik/data/api/szkolny/SzkolnyApi.kt @@ -12,10 +12,7 @@ import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.Job import kotlinx.coroutines.withContext import okhttp3.OkHttpClient -import pl.szczodrzynski.edziennik.App -import pl.szczodrzynski.edziennik.BuildConfig -import pl.szczodrzynski.edziennik.R -import pl.szczodrzynski.edziennik.data.api.models.ApiError +import pl.szczodrzynski.edziennik.* import pl.szczodrzynski.edziennik.data.api.szkolny.adapter.DateAdapter import pl.szczodrzynski.edziennik.data.api.szkolny.adapter.TimeAdapter import pl.szczodrzynski.edziennik.data.api.szkolny.interceptor.SignatureInterceptor @@ -28,7 +25,6 @@ import pl.szczodrzynski.edziennik.data.db.entity.FeedbackMessage import pl.szczodrzynski.edziennik.data.db.entity.Notification import pl.szczodrzynski.edziennik.data.db.entity.Profile import pl.szczodrzynski.edziennik.data.db.full.EventFull -import pl.szczodrzynski.edziennik.md5 import pl.szczodrzynski.edziennik.ui.modules.error.ErrorDetailsDialog import pl.szczodrzynski.edziennik.ui.modules.error.ErrorSnackbar import pl.szczodrzynski.edziennik.utils.models.Date @@ -80,7 +76,7 @@ class SzkolnyApi(val app: App) : CoroutineScope { withContext(Dispatchers.Default) { block() } } catch (e: Exception) { - errorSnackbar.addError(ApiError.fromThrowable(TAG, e)).show() + errorSnackbar.addError(e.toApiError(TAG)).show() null } } @@ -91,7 +87,7 @@ class SzkolnyApi(val app: App) : CoroutineScope { catch (e: Exception) { ErrorDetailsDialog( activity, - listOf(ApiError.fromThrowable(TAG, e)), + listOf(e.toApiError(TAG)), R.string.error_occured ) null @@ -160,7 +156,7 @@ class SzkolnyApi(val app: App) : CoroutineScope { } @Throws(Exception::class) - fun getEvents(profiles: List, notifications: List, blacklistedIds: List): List { + fun getEvents(profiles: List, notifications: List, blacklistedIds: List, lastSyncTime: Long): List { val teams = app.db.teamDao().allNow val response = api.serverSync(ServerSyncRequest( @@ -185,19 +181,26 @@ class SzkolnyApi(val app: App) : CoroutineScope { } } }, + lastSync = lastSyncTime, notifications = notifications.map { ServerSyncRequest.Notification(it.profileName ?: "", it.type, it.text) } )).execute() - parseResponse(response) + val (events, hasBrowsers) = parseResponse(response) - val events = mutableListOf() + hasBrowsers?.let { + app.config.sync.webPushEnabled = it + } - response.body()?.data?.events?.forEach { event -> + val eventList = mutableListOf() + + events.forEach { event -> + // skip blacklisted events if (event.id in blacklistedIds) return@forEach + // create the event for every matching team and profile teams.filter { it.code == event.teamCode }.onEach { team -> val profile = profiles.firstOrNull { it.id == team.profileId } ?: return@onEach - events.add(EventFull(event).apply { + eventList += EventFull(event).apply { profileId = team.profileId teamId = team.id addedManually = true @@ -205,11 +208,11 @@ class SzkolnyApi(val app: App) : CoroutineScope { notified = profile.empty if (profile.userCode == event.sharedBy) sharedBy = "self" - }) + } } } - return events + return eventList } @Throws(Exception::class) @@ -253,9 +256,8 @@ class SzkolnyApi(val app: App) : CoroutineScope { browserId = browserId, pairToken = pairToken )).execute() - parseResponse(response) - return response.body()?.data?.browsers ?: emptyList() + return parseResponse(response).browsers } @Throws(Exception::class) @@ -265,9 +267,8 @@ class SzkolnyApi(val app: App) : CoroutineScope { device = getDevice(), action = "listBrowsers" )).execute() - parseResponse(response) - return response.body()?.data?.browsers ?: emptyList() + return parseResponse(response).browsers } @Throws(Exception::class) @@ -278,9 +279,8 @@ class SzkolnyApi(val app: App) : CoroutineScope { action = "unpairBrowser", browserId = browserId )).execute() - parseResponse(response) - return response.body()?.data?.browsers ?: emptyList() + return parseResponse(response).browsers } @Throws(Exception::class) @@ -307,9 +307,7 @@ class SzkolnyApi(val app: App) : CoroutineScope { @Throws(Exception::class) fun getUpdate(channel: String): List { val response = api.updates(channel).execute() - parseResponse(response) - - return response.body()?.data ?: emptyList() + return parseResponse(response) } @Throws(Exception::class) @@ -321,8 +319,7 @@ class SzkolnyApi(val app: App) : CoroutineScope { targetDeviceId = targetDeviceId, text = text )).execute() - val data = parseResponse(response) - return data.message + return parseResponse(response).message } } diff --git a/app/src/main/java/pl/szczodrzynski/edziennik/data/api/szkolny/request/ServerSyncRequest.kt b/app/src/main/java/pl/szczodrzynski/edziennik/data/api/szkolny/request/ServerSyncRequest.kt index a60742e0..e287df06 100644 --- a/app/src/main/java/pl/szczodrzynski/edziennik/data/api/szkolny/request/ServerSyncRequest.kt +++ b/app/src/main/java/pl/szczodrzynski/edziennik/data/api/szkolny/request/ServerSyncRequest.kt @@ -11,6 +11,8 @@ data class ServerSyncRequest( val userCodes: List, val users: List? = null, + val lastSync: Long, + val notifications: List? = null ) { data class User( diff --git a/app/src/main/java/pl/szczodrzynski/edziennik/data/api/szkolny/response/ServerSyncResponse.kt b/app/src/main/java/pl/szczodrzynski/edziennik/data/api/szkolny/response/ServerSyncResponse.kt index 5bb98af8..0ed11f9c 100644 --- a/app/src/main/java/pl/szczodrzynski/edziennik/data/api/szkolny/response/ServerSyncResponse.kt +++ b/app/src/main/java/pl/szczodrzynski/edziennik/data/api/szkolny/response/ServerSyncResponse.kt @@ -6,4 +6,7 @@ package pl.szczodrzynski.edziennik.data.api.szkolny.response import pl.szczodrzynski.edziennik.data.db.full.EventFull -data class ServerSyncResponse(val events: List) +data class ServerSyncResponse( + val events: List, + val hasBrowsers: Boolean? = null +) diff --git a/app/src/main/java/pl/szczodrzynski/edziennik/data/api/task/AppSync.kt b/app/src/main/java/pl/szczodrzynski/edziennik/data/api/task/AppSync.kt index de877d1c..3d0e37d6 100644 --- a/app/src/main/java/pl/szczodrzynski/edziennik/data/api/task/AppSync.kt +++ b/app/src/main/java/pl/szczodrzynski/edziennik/data/api/task/AppSync.kt @@ -24,26 +24,25 @@ class AppSync(val app: App, val notifications: MutableList, val pr * * @return a number of events inserted to DB, possibly needing a notification */ - fun run(): Int { - val profiles = profiles.filter { it.registration == Profile.REGISTRATION_ENABLED && !it.archived } - if (profiles.isNotEmpty()) { - val blacklistedIds = app.db.eventDao().blacklistedIds; - val events = api.getEvents(profiles, notifications, blacklistedIds) + fun run(lastSyncTime: Long, markAsSeen: Boolean = false): Int { + val blacklistedIds = app.db.eventDao().blacklistedIds + val events = api.getEvents(profiles, notifications, blacklistedIds, lastSyncTime) - if (events.isNotEmpty()) { - app.db.metadataDao().addAllIgnore(events.map { event -> - Metadata( - event.profileId, - Metadata.TYPE_EVENT, - event.id, - event.seen, - event.notified, - event.addedDate - ) - }) - return app.db.eventDao().addAll(events).size - } + app.config.sync.lastAppSync = System.currentTimeMillis() + + if (events.isNotEmpty()) { + app.db.metadataDao().addAllIgnore(events.map { event -> + Metadata( + event.profileId, + Metadata.TYPE_EVENT, + event.id, + markAsSeen || event.seen, + markAsSeen || event.notified, + event.addedDate + ) + }) + return app.db.eventDao().addAll(events).size } return 0; } -} \ No newline at end of file +} diff --git a/app/src/main/java/pl/szczodrzynski/edziennik/data/api/task/SzkolnyTask.kt b/app/src/main/java/pl/szczodrzynski/edziennik/data/api/task/SzkolnyTask.kt index 5171e1f7..3ac80495 100644 --- a/app/src/main/java/pl/szczodrzynski/edziennik/data/api/task/SzkolnyTask.kt +++ b/app/src/main/java/pl/szczodrzynski/edziennik/data/api/task/SzkolnyTask.kt @@ -31,16 +31,23 @@ class SzkolnyTask(val app: App, val syncingProfiles: List) : IApiTask(- val notifications = Notifications(app, notificationList, profiles) notifications.run() - val shouldAppSync = notificationList.isNotEmpty() || (System.currentTimeMillis() - app.config.lastAppSync > 24*HOUR*1000) - // do an AppSync every 24 hours, or if WebPush has a notification + val appSyncProfiles = profiles.filter { it.registration == Profile.REGISTRATION_ENABLED && !it.archived } + // App Sync conditions: + // - every 24 hours && any profile is registered + // - if there are new notifications && any browser is paired + val shouldAppSync = + System.currentTimeMillis() - app.config.sync.lastAppSync > 24*HOUR*1000 + && appSyncProfiles.isNotEmpty() + || notificationList.isNotEmpty() + && app.config.sync.webPushEnabled + if (shouldAppSync) { // send notifications to web push, get shared events - val addedEvents = AppSync(app, notificationList, profiles, api).run() + val addedEvents = AppSync(app, notificationList, appSyncProfiles, api).run(app.config.sync.lastAppSync) if (addedEvents > 0) { // create notifications for shared events (not present before app sync) notifications.sharedEventNotifications() } - app.config.lastAppSync = System.currentTimeMillis() } d(TAG, "Created ${notificationList.count()} notifications.") diff --git a/app/src/main/java/pl/szczodrzynski/edziennik/ui/modules/webpush/WebPushFragment.kt b/app/src/main/java/pl/szczodrzynski/edziennik/ui/modules/webpush/WebPushFragment.kt index b9727901..4c8c34cc 100644 --- a/app/src/main/java/pl/szczodrzynski/edziennik/ui/modules/webpush/WebPushFragment.kt +++ b/app/src/main/java/pl/szczodrzynski/edziennik/ui/modules/webpush/WebPushFragment.kt @@ -116,6 +116,8 @@ class WebPushFragment : Fragment(), CoroutineScope { } adapter.notifyDataSetChanged() + app.config.sync.webPushEnabled = browsers.isNotEmpty() + if (browsers.isNotEmpty()) { b.browsersView.visibility = View.VISIBLE b.browsersNoData.visibility = View.GONE