forked from github/wulkanowy-mirror
Fix duplicate items after running automatic and manual sync at the same time (#1197)
This commit is contained in:
parent
af8108a649
commit
f14346ff32
@ -14,6 +14,7 @@ import io.github.wulkanowy.utils.monday
|
||||
import io.github.wulkanowy.utils.networkBoundResource
|
||||
import io.github.wulkanowy.utils.sunday
|
||||
import io.github.wulkanowy.utils.uniqueSubtract
|
||||
import kotlinx.coroutines.sync.Mutex
|
||||
import java.time.LocalDate
|
||||
import java.time.LocalDateTime
|
||||
import java.time.LocalTime
|
||||
@ -27,9 +28,12 @@ class AttendanceRepository @Inject constructor(
|
||||
private val refreshHelper: AutoRefreshHelper,
|
||||
) {
|
||||
|
||||
private val saveFetchResultMutex = Mutex()
|
||||
|
||||
private val cacheKey = "attendance"
|
||||
|
||||
fun getAttendance(student: Student, semester: Semester, start: LocalDate, end: LocalDate, forceRefresh: Boolean) = networkBoundResource(
|
||||
mutex = saveFetchResultMutex,
|
||||
shouldFetch = { it.isEmpty() || forceRefresh || refreshHelper.isShouldBeRefreshed(getRefreshKey(cacheKey, semester, start, end)) },
|
||||
query = { attendanceDb.loadAll(semester.diaryId, semester.studentId, start.monday, end.sunday) },
|
||||
fetch = {
|
||||
|
@ -10,6 +10,7 @@ import io.github.wulkanowy.utils.getRefreshKey
|
||||
import io.github.wulkanowy.utils.init
|
||||
import io.github.wulkanowy.utils.networkBoundResource
|
||||
import io.github.wulkanowy.utils.uniqueSubtract
|
||||
import kotlinx.coroutines.sync.Mutex
|
||||
import javax.inject.Inject
|
||||
import javax.inject.Singleton
|
||||
|
||||
@ -20,9 +21,12 @@ class AttendanceSummaryRepository @Inject constructor(
|
||||
private val refreshHelper: AutoRefreshHelper,
|
||||
) {
|
||||
|
||||
private val saveFetchResultMutex = Mutex()
|
||||
|
||||
private val cacheKey = "attendance_summary"
|
||||
|
||||
fun getAttendanceSummary(student: Student, semester: Semester, subjectId: Int, forceRefresh: Boolean) = networkBoundResource(
|
||||
mutex = saveFetchResultMutex,
|
||||
shouldFetch = { it.isEmpty() || forceRefresh || refreshHelper.isShouldBeRefreshed(getRefreshKey(cacheKey, semester)) },
|
||||
query = { attendanceDb.loadAll(semester.diaryId, semester.studentId, subjectId) },
|
||||
fetch = {
|
||||
|
@ -12,6 +12,7 @@ import io.github.wulkanowy.utils.monday
|
||||
import io.github.wulkanowy.utils.networkBoundResource
|
||||
import io.github.wulkanowy.utils.sunday
|
||||
import io.github.wulkanowy.utils.uniqueSubtract
|
||||
import kotlinx.coroutines.sync.Mutex
|
||||
import java.time.LocalDate
|
||||
import javax.inject.Inject
|
||||
import javax.inject.Singleton
|
||||
@ -23,9 +24,12 @@ class CompletedLessonsRepository @Inject constructor(
|
||||
private val refreshHelper: AutoRefreshHelper,
|
||||
) {
|
||||
|
||||
private val saveFetchResultMutex = Mutex()
|
||||
|
||||
private val cacheKey = "completed"
|
||||
|
||||
fun getCompletedLessons(student: Student, semester: Semester, start: LocalDate, end: LocalDate, forceRefresh: Boolean) = networkBoundResource(
|
||||
mutex = saveFetchResultMutex,
|
||||
shouldFetch = { it.isEmpty() || forceRefresh || refreshHelper.isShouldBeRefreshed(getRefreshKey(cacheKey, semester, start, end)) },
|
||||
query = { completedLessonsDb.loadAll(semester.studentId, semester.diaryId, start.monday, end.sunday) },
|
||||
fetch = {
|
||||
|
@ -10,6 +10,7 @@ import io.github.wulkanowy.utils.getRefreshKey
|
||||
import io.github.wulkanowy.utils.init
|
||||
import io.github.wulkanowy.utils.networkBoundResource
|
||||
import io.github.wulkanowy.utils.uniqueSubtract
|
||||
import kotlinx.coroutines.sync.Mutex
|
||||
import javax.inject.Inject
|
||||
import javax.inject.Singleton
|
||||
|
||||
@ -20,9 +21,12 @@ class ConferenceRepository @Inject constructor(
|
||||
private val refreshHelper: AutoRefreshHelper,
|
||||
) {
|
||||
|
||||
private val saveFetchResultMutex = Mutex()
|
||||
|
||||
private val cacheKey = "conference"
|
||||
|
||||
fun getConferences(student: Student, semester: Semester, forceRefresh: Boolean) = networkBoundResource(
|
||||
mutex = saveFetchResultMutex,
|
||||
shouldFetch = { it.isEmpty() || forceRefresh || refreshHelper.isShouldBeRefreshed(getRefreshKey(cacheKey, semester)) },
|
||||
query = { conferenceDb.loadAll(semester.diaryId, student.studentId) },
|
||||
fetch = {
|
||||
|
@ -12,6 +12,7 @@ import io.github.wulkanowy.utils.init
|
||||
import io.github.wulkanowy.utils.networkBoundResource
|
||||
import io.github.wulkanowy.utils.startExamsDay
|
||||
import io.github.wulkanowy.utils.uniqueSubtract
|
||||
import kotlinx.coroutines.sync.Mutex
|
||||
import java.time.LocalDate
|
||||
import javax.inject.Inject
|
||||
import javax.inject.Singleton
|
||||
@ -23,9 +24,12 @@ class ExamRepository @Inject constructor(
|
||||
private val refreshHelper: AutoRefreshHelper,
|
||||
) {
|
||||
|
||||
private val saveFetchResultMutex = Mutex()
|
||||
|
||||
private val cacheKey = "exam"
|
||||
|
||||
fun getExams(student: Student, semester: Semester, start: LocalDate, end: LocalDate, forceRefresh: Boolean) = networkBoundResource(
|
||||
mutex = saveFetchResultMutex,
|
||||
shouldFetch = { it.isEmpty() || forceRefresh || refreshHelper.isShouldBeRefreshed(getRefreshKey(cacheKey, semester, start, end)) },
|
||||
query = { examDb.loadAll(semester.diaryId, semester.studentId, start.startExamsDay, start.endExamsDay) },
|
||||
fetch = {
|
||||
|
@ -16,6 +16,7 @@ import io.github.wulkanowy.utils.uniqueSubtract
|
||||
import kotlinx.coroutines.flow.Flow
|
||||
import kotlinx.coroutines.flow.combine
|
||||
import kotlinx.coroutines.flow.map
|
||||
import kotlinx.coroutines.sync.Mutex
|
||||
import java.time.LocalDateTime
|
||||
import javax.inject.Inject
|
||||
import javax.inject.Singleton
|
||||
@ -28,14 +29,20 @@ class GradeRepository @Inject constructor(
|
||||
private val refreshHelper: AutoRefreshHelper,
|
||||
) {
|
||||
|
||||
private val saveFetchResultMutex = Mutex()
|
||||
|
||||
private val cacheKey = "grade"
|
||||
|
||||
fun getGrades(student: Student, semester: Semester, forceRefresh: Boolean, notify: Boolean = false) = networkBoundResource(
|
||||
shouldFetch = { (details, summaries) -> details.isEmpty() || summaries.isEmpty() || forceRefresh || refreshHelper.isShouldBeRefreshed(getRefreshKey(cacheKey, semester)) },
|
||||
mutex = saveFetchResultMutex,
|
||||
shouldFetch = { (details, summaries) ->
|
||||
val isShouldBeRefreshed = refreshHelper.isShouldBeRefreshed(getRefreshKey(cacheKey, semester))
|
||||
details.isEmpty() || summaries.isEmpty() || forceRefresh || isShouldBeRefreshed
|
||||
},
|
||||
query = {
|
||||
gradeDb.loadAll(semester.semesterId, semester.studentId).combine(gradeSummaryDb.loadAll(semester.semesterId, semester.studentId)) { details, summaries ->
|
||||
details to summaries
|
||||
}
|
||||
val detailsFlow = gradeDb.loadAll(semester.semesterId, semester.studentId)
|
||||
val summaryFlow = gradeSummaryDb.loadAll(semester.semesterId, semester.studentId)
|
||||
detailsFlow.combine(summaryFlow) { details, summaries -> details to summaries }
|
||||
},
|
||||
fetch = {
|
||||
val (details, summary) = sdk.init(student)
|
||||
@ -92,19 +99,27 @@ class GradeRepository @Inject constructor(
|
||||
}
|
||||
|
||||
fun getUnreadGrades(semester: Semester): Flow<List<Grade>> {
|
||||
return gradeDb.loadAll(semester.semesterId, semester.studentId).map { it.filter { grade -> !grade.isRead } }
|
||||
return gradeDb.loadAll(semester.semesterId, semester.studentId).map {
|
||||
it.filter { grade -> !grade.isRead }
|
||||
}
|
||||
}
|
||||
|
||||
fun getNotNotifiedGrades(semester: Semester): Flow<List<Grade>> {
|
||||
return gradeDb.loadAll(semester.semesterId, semester.studentId).map { it.filter { grade -> !grade.isNotified } }
|
||||
return gradeDb.loadAll(semester.semesterId, semester.studentId).map {
|
||||
it.filter { grade -> !grade.isNotified }
|
||||
}
|
||||
}
|
||||
|
||||
fun getNotNotifiedPredictedGrades(semester: Semester): Flow<List<GradeSummary>> {
|
||||
return gradeSummaryDb.loadAll(semester.semesterId, semester.studentId).map { it.filter { gradeSummary -> !gradeSummary.isPredictedGradeNotified } }
|
||||
return gradeSummaryDb.loadAll(semester.semesterId, semester.studentId).map {
|
||||
it.filter { gradeSummary -> !gradeSummary.isPredictedGradeNotified }
|
||||
}
|
||||
}
|
||||
|
||||
fun getNotNotifiedFinalGrades(semester: Semester): Flow<List<GradeSummary>> {
|
||||
return gradeSummaryDb.loadAll(semester.semesterId, semester.studentId).map { it.filter { gradeSummary -> !gradeSummary.isFinalGradeNotified } }
|
||||
return gradeSummaryDb.loadAll(semester.semesterId, semester.studentId).map {
|
||||
it.filter { gradeSummary -> !gradeSummary.isFinalGradeNotified }
|
||||
}
|
||||
}
|
||||
|
||||
suspend fun updateGrade(grade: Grade) {
|
||||
|
@ -17,6 +17,7 @@ import io.github.wulkanowy.utils.getRefreshKey
|
||||
import io.github.wulkanowy.utils.init
|
||||
import io.github.wulkanowy.utils.networkBoundResource
|
||||
import io.github.wulkanowy.utils.uniqueSubtract
|
||||
import kotlinx.coroutines.sync.Mutex
|
||||
import java.util.Locale
|
||||
import javax.inject.Inject
|
||||
import javax.inject.Singleton
|
||||
@ -30,11 +31,16 @@ class GradeStatisticsRepository @Inject constructor(
|
||||
private val refreshHelper: AutoRefreshHelper,
|
||||
) {
|
||||
|
||||
private val partialMutex = Mutex()
|
||||
private val semesterMutex = Mutex()
|
||||
private val pointsMutex = Mutex()
|
||||
|
||||
private val partialCacheKey = "grade_stats_partial"
|
||||
private val semesterCacheKey = "grade_stats_semester"
|
||||
private val pointsCacheKey = "grade_stats_points"
|
||||
|
||||
fun getGradesPartialStatistics(student: Student, semester: Semester, subjectName: String, forceRefresh: Boolean) = networkBoundResource(
|
||||
mutex = partialMutex,
|
||||
shouldFetch = { it.isEmpty() || forceRefresh || refreshHelper.isShouldBeRefreshed(getRefreshKey(partialCacheKey, semester)) },
|
||||
query = { gradePartialStatisticsDb.loadAll(semester.semesterId, semester.studentId) },
|
||||
fetch = {
|
||||
@ -71,6 +77,7 @@ class GradeStatisticsRepository @Inject constructor(
|
||||
)
|
||||
|
||||
fun getGradesSemesterStatistics(student: Student, semester: Semester, subjectName: String, forceRefresh: Boolean) = networkBoundResource(
|
||||
mutex = semesterMutex,
|
||||
shouldFetch = { it.isEmpty() || forceRefresh || refreshHelper.isShouldBeRefreshed(getRefreshKey(semesterCacheKey, semester)) },
|
||||
query = { gradeSemesterStatisticsDb.loadAll(semester.semesterId, semester.studentId) },
|
||||
fetch = {
|
||||
@ -112,6 +119,7 @@ class GradeStatisticsRepository @Inject constructor(
|
||||
)
|
||||
|
||||
fun getGradesPointsStatistics(student: Student, semester: Semester, subjectName: String, forceRefresh: Boolean) = networkBoundResource(
|
||||
mutex = pointsMutex,
|
||||
shouldFetch = { it.isEmpty() || forceRefresh || refreshHelper.isShouldBeRefreshed(getRefreshKey(pointsCacheKey, semester)) },
|
||||
query = { gradePointsStatisticsDb.loadAll(semester.semesterId, semester.studentId) },
|
||||
fetch = {
|
||||
|
@ -13,6 +13,7 @@ import io.github.wulkanowy.utils.monday
|
||||
import io.github.wulkanowy.utils.networkBoundResource
|
||||
import io.github.wulkanowy.utils.sunday
|
||||
import io.github.wulkanowy.utils.uniqueSubtract
|
||||
import kotlinx.coroutines.sync.Mutex
|
||||
import java.time.LocalDate
|
||||
import javax.inject.Inject
|
||||
import javax.inject.Singleton
|
||||
@ -24,9 +25,12 @@ class HomeworkRepository @Inject constructor(
|
||||
private val refreshHelper: AutoRefreshHelper,
|
||||
) {
|
||||
|
||||
private val saveFetchResultMutex = Mutex()
|
||||
|
||||
private val cacheKey = "homework"
|
||||
|
||||
fun getHomework(student: Student, semester: Semester, start: LocalDate, end: LocalDate, forceRefresh: Boolean) = networkBoundResource(
|
||||
mutex = saveFetchResultMutex,
|
||||
shouldFetch = { it.isEmpty() || forceRefresh || refreshHelper.isShouldBeRefreshed(getRefreshKey(cacheKey, semester, start, end)) },
|
||||
query = { homeworkDb.loadAll(semester.semesterId, semester.studentId, start.monday, end.sunday) },
|
||||
fetch = {
|
||||
|
@ -9,6 +9,7 @@ import io.github.wulkanowy.utils.init
|
||||
import io.github.wulkanowy.utils.networkBoundResource
|
||||
import kotlinx.coroutines.flow.first
|
||||
import kotlinx.coroutines.flow.map
|
||||
import kotlinx.coroutines.sync.Mutex
|
||||
import java.time.LocalDate
|
||||
import java.time.LocalDate.now
|
||||
import javax.inject.Inject
|
||||
@ -20,7 +21,10 @@ class LuckyNumberRepository @Inject constructor(
|
||||
private val sdk: Sdk
|
||||
) {
|
||||
|
||||
private val saveFetchResultMutex = Mutex()
|
||||
|
||||
fun getLuckyNumber(student: Student, forceRefresh: Boolean, notify: Boolean = false) = networkBoundResource(
|
||||
mutex = saveFetchResultMutex,
|
||||
shouldFetch = { it == null || forceRefresh },
|
||||
query = { luckyNumberDb.load(student.studentId, now()) },
|
||||
fetch = { sdk.init(student).getLuckyNumber(student.schoolShortName)?.mapToEntity(student) },
|
||||
|
@ -20,6 +20,7 @@ import io.github.wulkanowy.utils.networkBoundResource
|
||||
import io.github.wulkanowy.utils.uniqueSubtract
|
||||
import kotlinx.coroutines.flow.Flow
|
||||
import kotlinx.coroutines.flow.map
|
||||
import kotlinx.coroutines.sync.Mutex
|
||||
import timber.log.Timber
|
||||
import java.time.LocalDateTime.now
|
||||
import javax.inject.Inject
|
||||
@ -33,10 +34,13 @@ class MessageRepository @Inject constructor(
|
||||
private val refreshHelper: AutoRefreshHelper,
|
||||
) {
|
||||
|
||||
private val saveFetchResultMutex = Mutex()
|
||||
|
||||
private val cacheKey = "message"
|
||||
|
||||
@Suppress("UNUSED_PARAMETER")
|
||||
fun getMessages(student: Student, semester: Semester, folder: MessageFolder, forceRefresh: Boolean, notify: Boolean = false) = networkBoundResource(
|
||||
mutex = saveFetchResultMutex,
|
||||
shouldFetch = { it.isEmpty() || forceRefresh || refreshHelper.isShouldBeRefreshed(getRefreshKey(cacheKey, student, folder)) },
|
||||
query = { messagesDb.loadAll(student.id.toInt(), folder.id) },
|
||||
fetch = { sdk.init(student).getMessages(Folder.valueOf(folder.name), now().minusMonths(3), now()).mapToEntities(student) },
|
||||
|
@ -13,6 +13,7 @@ import io.github.wulkanowy.utils.getRefreshKey
|
||||
import io.github.wulkanowy.utils.init
|
||||
import io.github.wulkanowy.utils.networkBoundResource
|
||||
import io.github.wulkanowy.utils.uniqueSubtract
|
||||
import kotlinx.coroutines.sync.Mutex
|
||||
import javax.inject.Inject
|
||||
import javax.inject.Singleton
|
||||
|
||||
@ -23,9 +24,12 @@ class MobileDeviceRepository @Inject constructor(
|
||||
private val refreshHelper: AutoRefreshHelper,
|
||||
) {
|
||||
|
||||
private val saveFetchResultMutex = Mutex()
|
||||
|
||||
private val cacheKey = "devices"
|
||||
|
||||
fun getDevices(student: Student, semester: Semester, forceRefresh: Boolean) = networkBoundResource(
|
||||
mutex = saveFetchResultMutex,
|
||||
shouldFetch = { it.isEmpty() || forceRefresh || refreshHelper.isShouldBeRefreshed(getRefreshKey(cacheKey, student)) },
|
||||
query = { mobileDb.loadAll(student.userLoginId.takeIf { it != 0 } ?: student.studentId) },
|
||||
fetch = {
|
||||
|
@ -13,6 +13,7 @@ import io.github.wulkanowy.utils.networkBoundResource
|
||||
import io.github.wulkanowy.utils.uniqueSubtract
|
||||
import kotlinx.coroutines.flow.Flow
|
||||
import kotlinx.coroutines.flow.map
|
||||
import kotlinx.coroutines.sync.Mutex
|
||||
import javax.inject.Inject
|
||||
import javax.inject.Singleton
|
||||
|
||||
@ -23,9 +24,12 @@ class NoteRepository @Inject constructor(
|
||||
private val refreshHelper: AutoRefreshHelper,
|
||||
) {
|
||||
|
||||
private val saveFetchResultMutex = Mutex()
|
||||
|
||||
private val cacheKey = "note"
|
||||
|
||||
fun getNotes(student: Student, semester: Semester, forceRefresh: Boolean, notify: Boolean = false) = networkBoundResource(
|
||||
mutex = saveFetchResultMutex,
|
||||
shouldFetch = { it.isEmpty() || forceRefresh || refreshHelper.isShouldBeRefreshed(getRefreshKey(cacheKey, semester)) },
|
||||
query = { noteDb.loadAll(student.studentId) },
|
||||
fetch = {
|
||||
|
@ -7,6 +7,7 @@ import io.github.wulkanowy.data.mappers.mapToEntity
|
||||
import io.github.wulkanowy.sdk.Sdk
|
||||
import io.github.wulkanowy.utils.init
|
||||
import io.github.wulkanowy.utils.networkBoundResource
|
||||
import kotlinx.coroutines.sync.Mutex
|
||||
import javax.inject.Inject
|
||||
import javax.inject.Singleton
|
||||
|
||||
@ -16,8 +17,11 @@ class SchoolRepository @Inject constructor(
|
||||
private val sdk: Sdk
|
||||
) {
|
||||
|
||||
private val saveFetchResultMutex = Mutex()
|
||||
|
||||
fun getSchoolInfo(student: Student, semester: Semester, forceRefresh: Boolean) =
|
||||
networkBoundResource(
|
||||
mutex = saveFetchResultMutex,
|
||||
shouldFetch = { it == null || forceRefresh },
|
||||
query = { schoolDb.load(semester.studentId, semester.classId) },
|
||||
fetch = {
|
||||
|
@ -7,6 +7,7 @@ import io.github.wulkanowy.data.mappers.mapToEntity
|
||||
import io.github.wulkanowy.sdk.Sdk
|
||||
import io.github.wulkanowy.utils.init
|
||||
import io.github.wulkanowy.utils.networkBoundResource
|
||||
import kotlinx.coroutines.sync.Mutex
|
||||
import javax.inject.Inject
|
||||
import javax.inject.Singleton
|
||||
|
||||
@ -16,8 +17,11 @@ class StudentInfoRepository @Inject constructor(
|
||||
private val sdk: Sdk
|
||||
) {
|
||||
|
||||
private val saveFetchResultMutex = Mutex()
|
||||
|
||||
fun getStudentInfo(student: Student, semester: Semester, forceRefresh: Boolean) =
|
||||
networkBoundResource(
|
||||
mutex = saveFetchResultMutex,
|
||||
shouldFetch = { it == null || forceRefresh },
|
||||
query = { studentInfoDao.loadStudentInfo(student.studentId) },
|
||||
fetch = {
|
||||
|
@ -8,6 +8,7 @@ import io.github.wulkanowy.sdk.Sdk
|
||||
import io.github.wulkanowy.utils.init
|
||||
import io.github.wulkanowy.utils.networkBoundResource
|
||||
import io.github.wulkanowy.utils.uniqueSubtract
|
||||
import kotlinx.coroutines.sync.Mutex
|
||||
import javax.inject.Inject
|
||||
import javax.inject.Singleton
|
||||
|
||||
@ -17,7 +18,10 @@ class SubjectRepository @Inject constructor(
|
||||
private val sdk: Sdk
|
||||
) {
|
||||
|
||||
private val saveFetchResultMutex = Mutex()
|
||||
|
||||
fun getSubjects(student: Student, semester: Semester, forceRefresh: Boolean = false) = networkBoundResource(
|
||||
mutex = saveFetchResultMutex,
|
||||
shouldFetch = { it.isEmpty() || forceRefresh },
|
||||
query = { subjectDao.loadAll(semester.diaryId, semester.studentId) },
|
||||
fetch = {
|
||||
|
@ -8,6 +8,7 @@ import io.github.wulkanowy.sdk.Sdk
|
||||
import io.github.wulkanowy.utils.init
|
||||
import io.github.wulkanowy.utils.networkBoundResource
|
||||
import io.github.wulkanowy.utils.uniqueSubtract
|
||||
import kotlinx.coroutines.sync.Mutex
|
||||
import javax.inject.Inject
|
||||
import javax.inject.Singleton
|
||||
|
||||
@ -17,7 +18,10 @@ class TeacherRepository @Inject constructor(
|
||||
private val sdk: Sdk
|
||||
) {
|
||||
|
||||
private val saveFetchResultMutex = Mutex()
|
||||
|
||||
fun getTeachers(student: Student, semester: Semester, forceRefresh: Boolean) = networkBoundResource(
|
||||
mutex = saveFetchResultMutex,
|
||||
shouldFetch = { it.isEmpty() || forceRefresh },
|
||||
query = { teacherDb.loadAll(semester.studentId, semester.classId) },
|
||||
fetch = {
|
||||
|
@ -18,6 +18,7 @@ import io.github.wulkanowy.utils.sunday
|
||||
import io.github.wulkanowy.utils.uniqueSubtract
|
||||
import kotlinx.coroutines.flow.combine
|
||||
import kotlinx.coroutines.flow.map
|
||||
import kotlinx.coroutines.sync.Mutex
|
||||
import java.time.LocalDate
|
||||
import javax.inject.Inject
|
||||
import javax.inject.Singleton
|
||||
@ -31,9 +32,12 @@ class TimetableRepository @Inject constructor(
|
||||
private val refreshHelper: AutoRefreshHelper,
|
||||
) {
|
||||
|
||||
private val saveFetchResultMutex = Mutex()
|
||||
|
||||
private val cacheKey = "timetable"
|
||||
|
||||
fun getTimetable(student: Student, semester: Semester, start: LocalDate, end: LocalDate, forceRefresh: Boolean, refreshAdditional: Boolean = false) = networkBoundResource(
|
||||
mutex = saveFetchResultMutex,
|
||||
shouldFetch = { (timetable, additional) -> timetable.isEmpty() || (additional.isEmpty() && refreshAdditional) || forceRefresh || refreshHelper.isShouldBeRefreshed(getRefreshKey(cacheKey, semester, start, end)) },
|
||||
query = {
|
||||
timetableDb.loadAll(semester.diaryId, semester.studentId, start.monday, end.sunday)
|
||||
|
@ -13,8 +13,11 @@ import kotlinx.coroutines.flow.flow
|
||||
import kotlinx.coroutines.flow.map
|
||||
import kotlinx.coroutines.flow.onEach
|
||||
import kotlinx.coroutines.flow.takeWhile
|
||||
import kotlinx.coroutines.sync.Mutex
|
||||
import kotlinx.coroutines.sync.withLock
|
||||
|
||||
inline fun <ResultType, RequestType> networkBoundResource(
|
||||
mutex: Mutex = Mutex(),
|
||||
showSavedOnLoading: Boolean = true,
|
||||
crossinline query: () -> Flow<ResultType>,
|
||||
crossinline fetch: suspend (ResultType) -> RequestType,
|
||||
@ -31,7 +34,7 @@ inline fun <ResultType, RequestType> networkBoundResource(
|
||||
|
||||
try {
|
||||
val newData = fetch(data)
|
||||
saveFetchResult(data, newData)
|
||||
mutex.withLock { saveFetchResult(query().first(), newData) }
|
||||
query().map { Resource.success(filterResult(it)) }
|
||||
} catch (throwable: Throwable) {
|
||||
onFetchFailed(throwable)
|
||||
@ -44,11 +47,12 @@ inline fun <ResultType, RequestType> networkBoundResource(
|
||||
|
||||
@JvmName("networkBoundResourceWithMap")
|
||||
inline fun <ResultType, RequestType, T> networkBoundResource(
|
||||
mutex: Mutex = Mutex(),
|
||||
showSavedOnLoading: Boolean = true,
|
||||
crossinline query: () -> Flow<ResultType>,
|
||||
crossinline fetch: suspend (ResultType) -> RequestType,
|
||||
crossinline saveFetchResult: suspend (old: ResultType, new: RequestType) -> Unit,
|
||||
crossinline onFetchFailed: (Throwable) -> Unit = { Unit },
|
||||
crossinline onFetchFailed: (Throwable) -> Unit = { },
|
||||
crossinline shouldFetch: (ResultType) -> Boolean = { true },
|
||||
crossinline mapResult: (ResultType) -> T
|
||||
) = flow {
|
||||
@ -59,7 +63,8 @@ inline fun <ResultType, RequestType, T> networkBoundResource(
|
||||
if (showSavedOnLoading) emit(Resource.loading(mapResult(data)))
|
||||
|
||||
try {
|
||||
saveFetchResult(data, fetch(data))
|
||||
val newData = fetch(data)
|
||||
mutex.withLock { saveFetchResult(query().first(), newData) }
|
||||
query().map { Resource.success(mapResult(it)) }
|
||||
} catch (throwable: Throwable) {
|
||||
onFetchFailed(throwable)
|
||||
|
@ -87,6 +87,7 @@ class AttendanceRepositoryTest {
|
||||
coEvery { sdk.getAttendance(startDate, endDate, 1) } returns remoteList
|
||||
coEvery { attendanceDb.loadAll(1, 1, startDate, endDate) } returnsMany listOf(
|
||||
flowOf(remoteList.dropLast(1).mapToEntities(semester)),
|
||||
flowOf(remoteList.dropLast(1).mapToEntities(semester)), // after fetch end before save result
|
||||
flowOf(remoteList.mapToEntities(semester))
|
||||
)
|
||||
coEvery { attendanceDb.insertAll(any()) } returns listOf(1, 2, 3)
|
||||
@ -114,6 +115,7 @@ class AttendanceRepositoryTest {
|
||||
coEvery { sdk.getAttendance(startDate, endDate, 1) } returns remoteList.dropLast(1)
|
||||
coEvery { attendanceDb.loadAll(1, 1, startDate, endDate) } returnsMany listOf(
|
||||
flowOf(remoteList.mapToEntities(semester)),
|
||||
flowOf(remoteList.mapToEntities(semester)), // after fetch end before save result
|
||||
flowOf(remoteList.dropLast(1).mapToEntities(semester))
|
||||
)
|
||||
coEvery { attendanceDb.insertAll(any()) } returns listOf(1, 2, 3)
|
||||
|
@ -87,6 +87,7 @@ class CompletedLessonsRepositoryTest {
|
||||
coEvery { sdk.getCompletedLessons(startDate, endDate) } returns remoteList
|
||||
coEvery { completedLessonDb.loadAll(1, 1, startDate, endDate) } returnsMany listOf(
|
||||
flowOf(remoteList.dropLast(1).mapToEntities(semester)),
|
||||
flowOf(remoteList.dropLast(1).mapToEntities(semester)), // after fetch end before save result
|
||||
flowOf(remoteList.mapToEntities(semester))
|
||||
)
|
||||
coEvery { completedLessonDb.insertAll(any()) } returns listOf(1, 2, 3)
|
||||
@ -114,6 +115,7 @@ class CompletedLessonsRepositoryTest {
|
||||
coEvery { sdk.getCompletedLessons(startDate, endDate) } returns remoteList.dropLast(1)
|
||||
coEvery { completedLessonDb.loadAll(1, 1, startDate, endDate) } returnsMany listOf(
|
||||
flowOf(remoteList.mapToEntities(semester)),
|
||||
flowOf(remoteList.mapToEntities(semester)), // after fetch end before save result
|
||||
flowOf(remoteList.dropLast(1).mapToEntities(semester))
|
||||
)
|
||||
coEvery { completedLessonDb.insertAll(any()) } returns listOf(1, 2, 3)
|
||||
|
@ -88,6 +88,7 @@ class ExamRemoteTest {
|
||||
coEvery { sdk.getExams(startDate, realEndDate, 1) } returns remoteList
|
||||
coEvery { examDb.loadAll(1, 1, startDate, realEndDate) } returnsMany listOf(
|
||||
flowOf(remoteList.dropLast(1).mapToEntities(semester)),
|
||||
flowOf(remoteList.dropLast(1).mapToEntities(semester)), // after fetch end before save result
|
||||
flowOf(remoteList.mapToEntities(semester))
|
||||
)
|
||||
coEvery { examDb.insertAll(any()) } returns listOf(1, 2, 3)
|
||||
@ -115,6 +116,7 @@ class ExamRemoteTest {
|
||||
coEvery { sdk.getExams(startDate, realEndDate, 1) } returns remoteList.dropLast(1)
|
||||
coEvery { examDb.loadAll(1, 1, startDate, realEndDate) } returnsMany listOf(
|
||||
flowOf(remoteList.mapToEntities(semester)),
|
||||
flowOf(remoteList.mapToEntities(semester)), // after fetch end before save result
|
||||
flowOf(remoteList.dropLast(1).mapToEntities(semester))
|
||||
)
|
||||
coEvery { examDb.insertAll(any()) } returns listOf(1, 2, 3)
|
||||
|
@ -57,7 +57,7 @@ class GradeRepositoryTest {
|
||||
coEvery { gradeDb.deleteAll(any()) } just Runs
|
||||
coEvery { gradeDb.insertAll(any()) } returns listOf()
|
||||
|
||||
coEvery { gradeSummaryDb.loadAll(1, 1) } returnsMany listOf(flowOf(listOf()), flowOf(listOf()))
|
||||
coEvery { gradeSummaryDb.loadAll(1, 1) } returnsMany listOf(flowOf(listOf()), flowOf(listOf()), flowOf(listOf()))
|
||||
coEvery { gradeSummaryDb.deleteAll(any()) } just Runs
|
||||
coEvery { gradeSummaryDb.insertAll(any()) } returns listOf()
|
||||
}
|
||||
@ -76,7 +76,8 @@ class GradeRepositoryTest {
|
||||
|
||||
coEvery { gradeDb.loadAll(1, 1) } returnsMany listOf(
|
||||
flowOf(listOf()), // empty because it is new user
|
||||
flowOf(remoteList.mapToEntities(semester))
|
||||
flowOf(listOf()), // empty again, after fetch end before save result
|
||||
flowOf(remoteList.mapToEntities(semester)),
|
||||
)
|
||||
|
||||
// execute
|
||||
@ -114,6 +115,7 @@ class GradeRepositoryTest {
|
||||
)
|
||||
coEvery { gradeDb.loadAll(1, 1) } returnsMany listOf(
|
||||
flowOf(localList.mapToEntities(semester)),
|
||||
flowOf(localList.mapToEntities(semester)), // after fetch end before save result
|
||||
flowOf(remoteList.mapToEntities(semester))
|
||||
)
|
||||
|
||||
@ -155,6 +157,7 @@ class GradeRepositoryTest {
|
||||
)
|
||||
coEvery { gradeDb.loadAll(1, 1) } returnsMany listOf(
|
||||
flowOf(localList.mapToEntities(semester)),
|
||||
flowOf(localList.mapToEntities(semester)), // after fetch end before save result
|
||||
flowOf(remoteList.mapToEntities(semester))
|
||||
)
|
||||
|
||||
@ -184,6 +187,7 @@ class GradeRepositoryTest {
|
||||
)
|
||||
coEvery { gradeDb.loadAll(1, 1) } returnsMany listOf(
|
||||
flowOf(localList.mapToEntities(semester)),
|
||||
flowOf(localList.mapToEntities(semester)), // after fetch end before save result
|
||||
flowOf(remoteList.mapToEntities(semester))
|
||||
)
|
||||
|
||||
@ -209,6 +213,7 @@ class GradeRepositoryTest {
|
||||
|
||||
coEvery { gradeDb.loadAll(1, 1) } returnsMany listOf(
|
||||
flowOf(listOf()),
|
||||
flowOf(listOf()), // after fetch end before save result
|
||||
flowOf(remoteList.mapToEntities(semester))
|
||||
)
|
||||
|
||||
|
@ -72,6 +72,7 @@ class LuckyNumberRemoteTest {
|
||||
coEvery { sdk.getLuckyNumber(student.schoolShortName) } returns luckyNumber
|
||||
coEvery { luckyNumberDb.load(1, date) } returnsMany listOf(
|
||||
flowOf(luckyNumber.mapToEntity(student).copy(luckyNumber = 6666)),
|
||||
flowOf(luckyNumber.mapToEntity(student).copy(luckyNumber = 6666)), // after fetch end before save result
|
||||
flowOf(luckyNumber.mapToEntity(student))
|
||||
)
|
||||
coEvery { luckyNumberDb.insertAll(any()) } returns listOf(1, 2, 3)
|
||||
@ -101,6 +102,7 @@ class LuckyNumberRemoteTest {
|
||||
coEvery { sdk.getLuckyNumber(student.schoolShortName) } returns luckyNumber
|
||||
coEvery { luckyNumberDb.load(1, date) } returnsMany listOf(
|
||||
flowOf(null),
|
||||
flowOf(null), // after fetch end before save result
|
||||
flowOf(luckyNumber.mapToEntity(student))
|
||||
)
|
||||
coEvery { luckyNumberDb.insertAll(any()) } returns listOf(1, 2, 3)
|
||||
|
@ -82,6 +82,7 @@ class MobileDeviceRepositoryTest {
|
||||
coEvery { sdk.getRegisteredDevices() } returns remoteList
|
||||
coEvery { mobileDeviceDb.loadAll(1) } returnsMany listOf(
|
||||
flowOf(remoteList.dropLast(1).mapToEntities(semester)),
|
||||
flowOf(remoteList.dropLast(1).mapToEntities(semester)), // after fetch end before save result
|
||||
flowOf(remoteList.mapToEntities(semester))
|
||||
)
|
||||
coEvery { mobileDeviceDb.insertAll(any()) } returns listOf(1, 2, 3)
|
||||
@ -109,6 +110,7 @@ class MobileDeviceRepositoryTest {
|
||||
coEvery { sdk.getRegisteredDevices() } returns remoteList.dropLast(1)
|
||||
coEvery { mobileDeviceDb.loadAll(1) } returnsMany listOf(
|
||||
flowOf(remoteList.mapToEntities(semester)),
|
||||
flowOf(remoteList.mapToEntities(semester)), // after fetch end before save result
|
||||
flowOf(remoteList.dropLast(1).mapToEntities(semester))
|
||||
)
|
||||
coEvery { mobileDeviceDb.insertAll(any()) } returns listOf(1, 2, 3)
|
||||
|
192
app/src/test/java/io/github/wulkanowy/utils/FlowUtilsKtTest.kt
Normal file
192
app/src/test/java/io/github/wulkanowy/utils/FlowUtilsKtTest.kt
Normal file
@ -0,0 +1,192 @@
|
||||
package io.github.wulkanowy.utils
|
||||
|
||||
import io.mockk.Runs
|
||||
import io.mockk.coEvery
|
||||
import io.mockk.coVerifyOrder
|
||||
import io.mockk.just
|
||||
import io.mockk.mockk
|
||||
import kotlinx.coroutines.ExperimentalCoroutinesApi
|
||||
import kotlinx.coroutines.delay
|
||||
import kotlinx.coroutines.flow.flowOf
|
||||
import kotlinx.coroutines.flow.launchIn
|
||||
import kotlinx.coroutines.sync.Mutex
|
||||
import kotlinx.coroutines.test.TestCoroutineScope
|
||||
import org.junit.Test
|
||||
import kotlin.test.assertEquals
|
||||
|
||||
@OptIn(ExperimentalCoroutinesApi::class)
|
||||
class FlowUtilsKtTest {
|
||||
|
||||
private val testScope = TestCoroutineScope()
|
||||
|
||||
@Test
|
||||
fun `fetch from two places with same remote data`() {
|
||||
val repo = mockk<TestRepo>()
|
||||
coEvery { repo.query() } returnsMany listOf(
|
||||
// initial data
|
||||
flowOf(listOf(1, 2, 3)),
|
||||
flowOf(listOf(1, 2, 3)),
|
||||
|
||||
// for first
|
||||
flowOf(listOf(1, 2, 3)), // before save
|
||||
flowOf(listOf(2, 3, 4)), // after save
|
||||
|
||||
// for second
|
||||
flowOf(listOf(2, 3, 4)), // before save
|
||||
flowOf(listOf(2, 3, 4)), // after save
|
||||
)
|
||||
coEvery { repo.fetch() } returnsMany listOf(
|
||||
listOf(2, 3, 4),
|
||||
listOf(2, 3, 4),
|
||||
)
|
||||
coEvery { repo.save(any(), any()) } just Runs
|
||||
|
||||
// first
|
||||
networkBoundResource(
|
||||
showSavedOnLoading = false,
|
||||
query = { repo.query() },
|
||||
fetch = {
|
||||
val data = repo.fetch()
|
||||
delay(2_000)
|
||||
data
|
||||
},
|
||||
saveFetchResult = { old, new -> repo.save(old, new) }
|
||||
).launchIn(testScope)
|
||||
|
||||
testScope.advanceTimeBy(1_000)
|
||||
|
||||
// second
|
||||
networkBoundResource(
|
||||
showSavedOnLoading = false,
|
||||
query = { repo.query() },
|
||||
fetch = {
|
||||
val data = repo.fetch()
|
||||
delay(2_000)
|
||||
data
|
||||
},
|
||||
saveFetchResult = { old, new -> repo.save(old, new) }
|
||||
).launchIn(testScope)
|
||||
|
||||
testScope.advanceTimeBy(3_000)
|
||||
|
||||
coVerifyOrder {
|
||||
// from first
|
||||
repo.query()
|
||||
repo.fetch() // hang for 2 sec
|
||||
|
||||
// wait 1 sec
|
||||
|
||||
// from second
|
||||
repo.query()
|
||||
repo.fetch() // hang for 2 sec
|
||||
|
||||
// from first
|
||||
repo.query()
|
||||
repo.save(withArg {
|
||||
assertEquals(listOf(1, 2, 3), it)
|
||||
}, any())
|
||||
repo.query()
|
||||
|
||||
// from second
|
||||
repo.query()
|
||||
repo.save(withArg {
|
||||
assertEquals(listOf(2, 3, 4), it)
|
||||
}, any())
|
||||
repo.query()
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `fetch from two places with same remote data and save at the same moment`() {
|
||||
val repo = mockk<TestRepo>()
|
||||
coEvery { repo.query() } returnsMany listOf(
|
||||
// initial data
|
||||
flowOf(listOf(1, 2, 3)),
|
||||
flowOf(listOf(1, 2, 3)),
|
||||
|
||||
// for first
|
||||
flowOf(listOf(1, 2, 3)), // before save
|
||||
flowOf(listOf(2, 3, 4)), // after save
|
||||
|
||||
// for second
|
||||
flowOf(listOf(2, 3, 4)), // before save
|
||||
flowOf(listOf(2, 3, 4)), // after save
|
||||
)
|
||||
coEvery { repo.fetch() } returnsMany listOf(
|
||||
listOf(2, 3, 4),
|
||||
listOf(2, 3, 4),
|
||||
)
|
||||
coEvery { repo.save(any(), any()) } just Runs
|
||||
|
||||
val saveResultMutex = Mutex()
|
||||
|
||||
// first
|
||||
networkBoundResource(
|
||||
mutex = saveResultMutex,
|
||||
showSavedOnLoading = false,
|
||||
query = { repo.query() },
|
||||
fetch = {
|
||||
val data = repo.fetch()
|
||||
delay(2_000)
|
||||
data
|
||||
},
|
||||
saveFetchResult = { old, new ->
|
||||
delay(1_500)
|
||||
repo.save(old, new)
|
||||
}
|
||||
).launchIn(testScope)
|
||||
|
||||
testScope.advanceTimeBy(1_000)
|
||||
|
||||
// second
|
||||
networkBoundResource(
|
||||
mutex = saveResultMutex,
|
||||
showSavedOnLoading = false,
|
||||
query = { repo.query() },
|
||||
fetch = {
|
||||
val data = repo.fetch()
|
||||
delay(2_000)
|
||||
data
|
||||
},
|
||||
saveFetchResult = { old, new ->
|
||||
repo.save(old, new)
|
||||
}
|
||||
).launchIn(testScope)
|
||||
|
||||
testScope.advanceTimeBy(3_000)
|
||||
|
||||
coVerifyOrder {
|
||||
// from first
|
||||
repo.query()
|
||||
repo.fetch() // hang for 2 sec
|
||||
|
||||
// wait 1 sec
|
||||
|
||||
// from second
|
||||
repo.query()
|
||||
repo.fetch() // hang for 2 sec
|
||||
|
||||
// from first
|
||||
repo.query()
|
||||
repo.save(withArg {
|
||||
assertEquals(listOf(1, 2, 3), it)
|
||||
}, any())
|
||||
|
||||
// from second
|
||||
repo.query()
|
||||
repo.save(withArg {
|
||||
assertEquals(listOf(2, 3, 4), it)
|
||||
}, any())
|
||||
|
||||
repo.query()
|
||||
repo.query()
|
||||
}
|
||||
}
|
||||
|
||||
@Suppress("UNUSED_PARAMETER", "RedundantSuspendModifier")
|
||||
private class TestRepo {
|
||||
fun query() = flowOf<List<Int>>()
|
||||
suspend fun fetch() = listOf<Int>()
|
||||
suspend fun save(old: List<Int>, new: List<Int>) {}
|
||||
}
|
||||
}
|
Loading…
x
Reference in New Issue
Block a user