forked from github/szkolny
[API] Optimize App Sync a bit.
This commit is contained in:
parent
c568cd3f2e
commit
7c7dff743b
@ -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 <A, B, R> ifNotNull(a: A?, b: B?, code: (A, B) -> R): R? {
|
||||
if (a != null && b != null) {
|
||||
|
@ -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);
|
||||
|
@ -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<ConfigEntry> = db.configDao().getAllNow()
|
||||
private val profileConfigs: HashMap<Int, ProfileConfig> = hashMapOf()
|
||||
init {
|
||||
|
@ -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 }
|
||||
|
||||
/* ____ _ _ _
|
||||
/ __ \ (_) | | | |
|
||||
| | | |_ _ _ ___| |_ | |__ ___ _ _ _ __ ___
|
||||
|
@ -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))
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -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<Profile>, notifications: List<Notification>, blacklistedIds: List<Long>): List<EventFull> {
|
||||
fun getEvents(profiles: List<Profile>, notifications: List<Notification>, blacklistedIds: List<Long>, lastSyncTime: Long): List<EventFull> {
|
||||
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<EventFull>()
|
||||
hasBrowsers?.let {
|
||||
app.config.sync.webPushEnabled = it
|
||||
}
|
||||
|
||||
response.body()?.data?.events?.forEach { event ->
|
||||
val eventList = mutableListOf<EventFull>()
|
||||
|
||||
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<Update> {
|
||||
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
|
||||
}
|
||||
}
|
||||
|
@ -11,6 +11,8 @@ data class ServerSyncRequest(
|
||||
val userCodes: List<String>,
|
||||
val users: List<User>? = null,
|
||||
|
||||
val lastSync: Long,
|
||||
|
||||
val notifications: List<Notification>? = null
|
||||
) {
|
||||
data class User(
|
||||
|
@ -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<EventFull>)
|
||||
data class ServerSyncResponse(
|
||||
val events: List<EventFull>,
|
||||
val hasBrowsers: Boolean? = null
|
||||
)
|
||||
|
@ -24,26 +24,25 @@ class AppSync(val app: App, val notifications: MutableList<Notification>, 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;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -31,16 +31,23 @@ class SzkolnyTask(val app: App, val syncingProfiles: List<Profile>) : 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.")
|
||||
|
||||
|
@ -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
|
||||
|
Loading…
Reference in New Issue
Block a user