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 okhttp3.TlsVersion
|
||||||
import okio.Buffer
|
import okio.Buffer
|
||||||
import pl.szczodrzynski.edziennik.data.api.*
|
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.SzkolnyApiException
|
||||||
import pl.szczodrzynski.edziennik.data.api.szkolny.response.ApiResponse
|
import pl.szczodrzynski.edziennik.data.api.szkolny.response.ApiResponse
|
||||||
import pl.szczodrzynski.edziennik.data.db.entity.Notification
|
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) {
|
private fun ApiResponse.Error.toErrorCode() = when (this.code) {
|
||||||
else -> ERROR_API_EXCEPTION
|
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? {
|
inline fun <A, B, R> ifNotNull(a: A?, b: B?, code: (A, B) -> R): R? {
|
||||||
if (a != null && b != null) {
|
if (a != null && b != null) {
|
||||||
|
@ -444,6 +444,8 @@ class MainActivity : AppCompatActivity(), CoroutineScope {
|
|||||||
|
|
||||||
// WHAT'S NEW DIALOG
|
// WHAT'S NEW DIALOG
|
||||||
if (app.config.appVersion < BuildConfig.VERSION_CODE) {
|
if (app.config.appVersion < BuildConfig.VERSION_CODE) {
|
||||||
|
// force an AppSync after update
|
||||||
|
app.config.sync.lastAppSync = 0L
|
||||||
ChangelogDialog(this)
|
ChangelogDialog(this)
|
||||||
if (app.config.appVersion < 170) {
|
if (app.config.appVersion < 170) {
|
||||||
//Intent intent = new Intent(this, ChangelogIntroActivity.class);
|
//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() }
|
get() { mWidgetConfigs = mWidgetConfigs ?: values.get("widgetConfigs", JsonObject()); return mWidgetConfigs ?: JsonObject() }
|
||||||
set(value) { set("widgetConfigs", value); mWidgetConfigs = value }
|
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 var rawEntries: List<ConfigEntry> = db.configDao().getAllNow()
|
||||||
private val profileConfigs: HashMap<Int, ProfileConfig> = hashMapOf()
|
private val profileConfigs: HashMap<Int, ProfileConfig> = hashMapOf()
|
||||||
init {
|
init {
|
||||||
|
@ -20,6 +20,11 @@ class ConfigSync(private val config: Config) {
|
|||||||
get() { mSyncEnabled = mSyncEnabled ?: config.values.get("syncEnabled", true); return mSyncEnabled ?: true }
|
get() { mSyncEnabled = mSyncEnabled ?: config.values.get("syncEnabled", true); return mSyncEnabled ?: true }
|
||||||
set(value) { config.set("syncEnabled", value); mSyncEnabled = value }
|
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
|
private var mSyncOnlyWifi: Boolean? = null
|
||||||
var onlyWifi: Boolean
|
var onlyWifi: Boolean
|
||||||
get() { mSyncOnlyWifi = mSyncOnlyWifi ?: config.values.get("syncOnlyWifi", false); return mSyncOnlyWifi ?: notifyAboutUpdates }
|
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 }
|
get() { mNotifyAboutUpdates = mNotifyAboutUpdates ?: config.values.get("notifyAboutUpdates", true); return mNotifyAboutUpdates ?: true }
|
||||||
set(value) { config.set("notifyAboutUpdates", value); mNotifyAboutUpdates = value }
|
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.IApiTask
|
||||||
import pl.szczodrzynski.edziennik.data.api.task.SzkolnyTask
|
import pl.szczodrzynski.edziennik.data.api.task.SzkolnyTask
|
||||||
import pl.szczodrzynski.edziennik.data.db.entity.Profile
|
import pl.szczodrzynski.edziennik.data.db.entity.Profile
|
||||||
|
import pl.szczodrzynski.edziennik.toApiError
|
||||||
import pl.szczodrzynski.edziennik.utils.Utils.d
|
import pl.szczodrzynski.edziennik.utils.Utils.d
|
||||||
import kotlin.math.min
|
import kotlin.math.min
|
||||||
import kotlin.math.roundToInt
|
import kotlin.math.roundToInt
|
||||||
@ -181,7 +182,7 @@ class ApiService : Service() {
|
|||||||
is SzkolnyTask -> task.run(taskCallback)
|
is SzkolnyTask -> task.run(taskCallback)
|
||||||
}
|
}
|
||||||
} catch (e: Exception) {
|
} 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.Job
|
||||||
import kotlinx.coroutines.withContext
|
import kotlinx.coroutines.withContext
|
||||||
import okhttp3.OkHttpClient
|
import okhttp3.OkHttpClient
|
||||||
import pl.szczodrzynski.edziennik.App
|
import pl.szczodrzynski.edziennik.*
|
||||||
import pl.szczodrzynski.edziennik.BuildConfig
|
|
||||||
import pl.szczodrzynski.edziennik.R
|
|
||||||
import pl.szczodrzynski.edziennik.data.api.models.ApiError
|
|
||||||
import pl.szczodrzynski.edziennik.data.api.szkolny.adapter.DateAdapter
|
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.adapter.TimeAdapter
|
||||||
import pl.szczodrzynski.edziennik.data.api.szkolny.interceptor.SignatureInterceptor
|
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.Notification
|
||||||
import pl.szczodrzynski.edziennik.data.db.entity.Profile
|
import pl.szczodrzynski.edziennik.data.db.entity.Profile
|
||||||
import pl.szczodrzynski.edziennik.data.db.full.EventFull
|
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.ErrorDetailsDialog
|
||||||
import pl.szczodrzynski.edziennik.ui.modules.error.ErrorSnackbar
|
import pl.szczodrzynski.edziennik.ui.modules.error.ErrorSnackbar
|
||||||
import pl.szczodrzynski.edziennik.utils.models.Date
|
import pl.szczodrzynski.edziennik.utils.models.Date
|
||||||
@ -80,7 +76,7 @@ class SzkolnyApi(val app: App) : CoroutineScope {
|
|||||||
withContext(Dispatchers.Default) { block() }
|
withContext(Dispatchers.Default) { block() }
|
||||||
}
|
}
|
||||||
catch (e: Exception) {
|
catch (e: Exception) {
|
||||||
errorSnackbar.addError(ApiError.fromThrowable(TAG, e)).show()
|
errorSnackbar.addError(e.toApiError(TAG)).show()
|
||||||
null
|
null
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -91,7 +87,7 @@ class SzkolnyApi(val app: App) : CoroutineScope {
|
|||||||
catch (e: Exception) {
|
catch (e: Exception) {
|
||||||
ErrorDetailsDialog(
|
ErrorDetailsDialog(
|
||||||
activity,
|
activity,
|
||||||
listOf(ApiError.fromThrowable(TAG, e)),
|
listOf(e.toApiError(TAG)),
|
||||||
R.string.error_occured
|
R.string.error_occured
|
||||||
)
|
)
|
||||||
null
|
null
|
||||||
@ -160,7 +156,7 @@ class SzkolnyApi(val app: App) : CoroutineScope {
|
|||||||
}
|
}
|
||||||
|
|
||||||
@Throws(Exception::class)
|
@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 teams = app.db.teamDao().allNow
|
||||||
|
|
||||||
val response = api.serverSync(ServerSyncRequest(
|
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) }
|
notifications = notifications.map { ServerSyncRequest.Notification(it.profileName ?: "", it.type, it.text) }
|
||||||
)).execute()
|
)).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)
|
if (event.id in blacklistedIds)
|
||||||
return@forEach
|
return@forEach
|
||||||
|
// create the event for every matching team and profile
|
||||||
teams.filter { it.code == event.teamCode }.onEach { team ->
|
teams.filter { it.code == event.teamCode }.onEach { team ->
|
||||||
val profile = profiles.firstOrNull { it.id == team.profileId } ?: return@onEach
|
val profile = profiles.firstOrNull { it.id == team.profileId } ?: return@onEach
|
||||||
|
|
||||||
events.add(EventFull(event).apply {
|
eventList += EventFull(event).apply {
|
||||||
profileId = team.profileId
|
profileId = team.profileId
|
||||||
teamId = team.id
|
teamId = team.id
|
||||||
addedManually = true
|
addedManually = true
|
||||||
@ -205,11 +208,11 @@ class SzkolnyApi(val app: App) : CoroutineScope {
|
|||||||
notified = profile.empty
|
notified = profile.empty
|
||||||
|
|
||||||
if (profile.userCode == event.sharedBy) sharedBy = "self"
|
if (profile.userCode == event.sharedBy) sharedBy = "self"
|
||||||
})
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return events
|
return eventList
|
||||||
}
|
}
|
||||||
|
|
||||||
@Throws(Exception::class)
|
@Throws(Exception::class)
|
||||||
@ -253,9 +256,8 @@ class SzkolnyApi(val app: App) : CoroutineScope {
|
|||||||
browserId = browserId,
|
browserId = browserId,
|
||||||
pairToken = pairToken
|
pairToken = pairToken
|
||||||
)).execute()
|
)).execute()
|
||||||
parseResponse(response)
|
|
||||||
|
|
||||||
return response.body()?.data?.browsers ?: emptyList()
|
return parseResponse(response).browsers
|
||||||
}
|
}
|
||||||
|
|
||||||
@Throws(Exception::class)
|
@Throws(Exception::class)
|
||||||
@ -265,9 +267,8 @@ class SzkolnyApi(val app: App) : CoroutineScope {
|
|||||||
device = getDevice(),
|
device = getDevice(),
|
||||||
action = "listBrowsers"
|
action = "listBrowsers"
|
||||||
)).execute()
|
)).execute()
|
||||||
parseResponse(response)
|
|
||||||
|
|
||||||
return response.body()?.data?.browsers ?: emptyList()
|
return parseResponse(response).browsers
|
||||||
}
|
}
|
||||||
|
|
||||||
@Throws(Exception::class)
|
@Throws(Exception::class)
|
||||||
@ -278,9 +279,8 @@ class SzkolnyApi(val app: App) : CoroutineScope {
|
|||||||
action = "unpairBrowser",
|
action = "unpairBrowser",
|
||||||
browserId = browserId
|
browserId = browserId
|
||||||
)).execute()
|
)).execute()
|
||||||
parseResponse(response)
|
|
||||||
|
|
||||||
return response.body()?.data?.browsers ?: emptyList()
|
return parseResponse(response).browsers
|
||||||
}
|
}
|
||||||
|
|
||||||
@Throws(Exception::class)
|
@Throws(Exception::class)
|
||||||
@ -307,9 +307,7 @@ class SzkolnyApi(val app: App) : CoroutineScope {
|
|||||||
@Throws(Exception::class)
|
@Throws(Exception::class)
|
||||||
fun getUpdate(channel: String): List<Update> {
|
fun getUpdate(channel: String): List<Update> {
|
||||||
val response = api.updates(channel).execute()
|
val response = api.updates(channel).execute()
|
||||||
parseResponse(response)
|
return parseResponse(response)
|
||||||
|
|
||||||
return response.body()?.data ?: emptyList()
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Throws(Exception::class)
|
@Throws(Exception::class)
|
||||||
@ -321,8 +319,7 @@ class SzkolnyApi(val app: App) : CoroutineScope {
|
|||||||
targetDeviceId = targetDeviceId,
|
targetDeviceId = targetDeviceId,
|
||||||
text = text
|
text = text
|
||||||
)).execute()
|
)).execute()
|
||||||
val data = parseResponse(response)
|
|
||||||
|
|
||||||
return data.message
|
return parseResponse(response).message
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -11,6 +11,8 @@ data class ServerSyncRequest(
|
|||||||
val userCodes: List<String>,
|
val userCodes: List<String>,
|
||||||
val users: List<User>? = null,
|
val users: List<User>? = null,
|
||||||
|
|
||||||
|
val lastSync: Long,
|
||||||
|
|
||||||
val notifications: List<Notification>? = null
|
val notifications: List<Notification>? = null
|
||||||
) {
|
) {
|
||||||
data class User(
|
data class User(
|
||||||
|
@ -6,4 +6,7 @@ package pl.szczodrzynski.edziennik.data.api.szkolny.response
|
|||||||
|
|
||||||
import pl.szczodrzynski.edziennik.data.db.full.EventFull
|
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
|
* @return a number of events inserted to DB, possibly needing a notification
|
||||||
*/
|
*/
|
||||||
fun run(): Int {
|
fun run(lastSyncTime: Long, markAsSeen: Boolean = false): Int {
|
||||||
val profiles = profiles.filter { it.registration == Profile.REGISTRATION_ENABLED && !it.archived }
|
val blacklistedIds = app.db.eventDao().blacklistedIds
|
||||||
if (profiles.isNotEmpty()) {
|
val events = api.getEvents(profiles, notifications, blacklistedIds, lastSyncTime)
|
||||||
val blacklistedIds = app.db.eventDao().blacklistedIds;
|
|
||||||
val events = api.getEvents(profiles, notifications, blacklistedIds)
|
|
||||||
|
|
||||||
if (events.isNotEmpty()) {
|
app.config.sync.lastAppSync = System.currentTimeMillis()
|
||||||
app.db.metadataDao().addAllIgnore(events.map { event ->
|
|
||||||
Metadata(
|
if (events.isNotEmpty()) {
|
||||||
event.profileId,
|
app.db.metadataDao().addAllIgnore(events.map { event ->
|
||||||
Metadata.TYPE_EVENT,
|
Metadata(
|
||||||
event.id,
|
event.profileId,
|
||||||
event.seen,
|
Metadata.TYPE_EVENT,
|
||||||
event.notified,
|
event.id,
|
||||||
event.addedDate
|
markAsSeen || event.seen,
|
||||||
)
|
markAsSeen || event.notified,
|
||||||
})
|
event.addedDate
|
||||||
return app.db.eventDao().addAll(events).size
|
)
|
||||||
}
|
})
|
||||||
|
return app.db.eventDao().addAll(events).size
|
||||||
}
|
}
|
||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -31,16 +31,23 @@ class SzkolnyTask(val app: App, val syncingProfiles: List<Profile>) : IApiTask(-
|
|||||||
val notifications = Notifications(app, notificationList, profiles)
|
val notifications = Notifications(app, notificationList, profiles)
|
||||||
notifications.run()
|
notifications.run()
|
||||||
|
|
||||||
val shouldAppSync = notificationList.isNotEmpty() || (System.currentTimeMillis() - app.config.lastAppSync > 24*HOUR*1000)
|
val appSyncProfiles = profiles.filter { it.registration == Profile.REGISTRATION_ENABLED && !it.archived }
|
||||||
// do an AppSync every 24 hours, or if WebPush has a notification
|
// 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) {
|
if (shouldAppSync) {
|
||||||
// send notifications to web push, get shared events
|
// 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) {
|
if (addedEvents > 0) {
|
||||||
// create notifications for shared events (not present before app sync)
|
// create notifications for shared events (not present before app sync)
|
||||||
notifications.sharedEventNotifications()
|
notifications.sharedEventNotifications()
|
||||||
}
|
}
|
||||||
app.config.lastAppSync = System.currentTimeMillis()
|
|
||||||
}
|
}
|
||||||
d(TAG, "Created ${notificationList.count()} notifications.")
|
d(TAG, "Created ${notificationList.count()} notifications.")
|
||||||
|
|
||||||
|
@ -116,6 +116,8 @@ class WebPushFragment : Fragment(), CoroutineScope {
|
|||||||
}
|
}
|
||||||
adapter.notifyDataSetChanged()
|
adapter.notifyDataSetChanged()
|
||||||
|
|
||||||
|
app.config.sync.webPushEnabled = browsers.isNotEmpty()
|
||||||
|
|
||||||
if (browsers.isNotEmpty()) {
|
if (browsers.isNotEmpty()) {
|
||||||
b.browsersView.visibility = View.VISIBLE
|
b.browsersView.visibility = View.VISIBLE
|
||||||
b.browsersNoData.visibility = View.GONE
|
b.browsersNoData.visibility = View.GONE
|
||||||
|
Loading…
x
Reference in New Issue
Block a user