forked from agzam/vyatsu-schedule
fix: ssl error & offline teacher schedule
This commit is contained in:
@@ -33,6 +33,9 @@ interface TeacherScheduleDao {
|
||||
@Query("DELETE FROM teacher_schedules")
|
||||
suspend fun deleteAll()
|
||||
|
||||
@Query("SELECT lastSyncedAt FROM teacher_schedules WHERE departmentId = :departmentId AND season = :season AND weekStart = :weekStart")
|
||||
suspend fun lastSyncedAt(departmentId: String, season: String, weekStart: Long): Long?
|
||||
|
||||
@Insert(onConflict = OnConflictStrategy.REPLACE)
|
||||
suspend fun insertDays(days: List<TeacherDayEntity>): List<Long>
|
||||
|
||||
@@ -41,6 +44,7 @@ interface TeacherScheduleDao {
|
||||
|
||||
@Transaction
|
||||
suspend fun replaceSchedule(schedule: TeacherScheduleEntity, days: List<TeacherDayEntity>, lessons: List<TeacherLessonEntity>) {
|
||||
deleteSchedule(schedule.departmentId, schedule.season.name, schedule.weekStart)
|
||||
val scheduleId = insertSchedule(schedule)
|
||||
val daysWithScheduleId = days.map { it.copy(scheduleId = scheduleId) }
|
||||
deleteDaysForSchedule(scheduleId)
|
||||
|
||||
@@ -56,7 +56,7 @@ class TeacherScheduleRepositoryImpl(
|
||||
scheduleDao.observeDistinctTeachers(period.season.name, period.weekStart.toEpochDay())
|
||||
.map { names -> names.map { name -> Teacher(name = name) } }
|
||||
|
||||
override suspend fun syncPeriod(date: LocalDate): Flow<BulkSyncProgress> = flow {
|
||||
override suspend fun syncPeriod(date: LocalDate, force: Boolean): Flow<BulkSyncProgress> = flow {
|
||||
// Unique index (departmentId, season, weekStart) breaks on interleaved
|
||||
// delete+insert from concurrent syncPeriod() callers.
|
||||
syncMutex.withLock {
|
||||
@@ -68,18 +68,27 @@ class TeacherScheduleRepositoryImpl(
|
||||
return@withLock
|
||||
}
|
||||
|
||||
targets.map { it.season to it.weekStart }
|
||||
.distinct()
|
||||
.forEach { (season, weekStart) ->
|
||||
withContext(Dispatchers.IO) {
|
||||
scheduleDao.deletePeriod(season.name, weekStart)
|
||||
}
|
||||
val now = now()
|
||||
var skippedCount = 0
|
||||
val nonSkipped = mutableListOf<DepartmentPeriodEntity>()
|
||||
for (target in targets) {
|
||||
val last = withContext(Dispatchers.IO) {
|
||||
scheduleDao.lastSyncedAt(target.departmentId, target.season.name, target.weekStart)
|
||||
}
|
||||
val shouldSkip = !force && last != null && now - last < STALE_WINDOW_MS
|
||||
if (shouldSkip) {
|
||||
skippedCount++
|
||||
} else {
|
||||
nonSkipped.add(target)
|
||||
}
|
||||
}
|
||||
|
||||
emit(BulkSyncProgress(total = targets.size, done = skippedCount, failed = 0))
|
||||
|
||||
val total = targets.size
|
||||
val semaphore = Semaphore(PARALLELISM)
|
||||
val jobs = coroutineScope {
|
||||
targets.map { target ->
|
||||
nonSkipped.map { target ->
|
||||
async {
|
||||
semaphore.withPermit {
|
||||
try {
|
||||
@@ -102,7 +111,7 @@ class TeacherScheduleRepositoryImpl(
|
||||
}
|
||||
}
|
||||
|
||||
var done = 0
|
||||
var done = skippedCount
|
||||
var failed = 0
|
||||
for (job in jobs) {
|
||||
if (job.await()) done++ else failed++
|
||||
|
||||
@@ -10,7 +10,7 @@ import java.time.LocalDate
|
||||
interface TeacherScheduleRepository {
|
||||
fun observeTeachers(period: SchedulePeriod): Flow<List<Teacher>>
|
||||
fun observeSchedule(period: SchedulePeriod): Flow<TeacherSchedule?>
|
||||
suspend fun syncPeriod(date: LocalDate): Flow<BulkSyncProgress>
|
||||
suspend fun syncPeriod(date: LocalDate, force: Boolean = false): Flow<BulkSyncProgress>
|
||||
suspend fun hasData(period: SchedulePeriod): Boolean
|
||||
suspend fun weeksFor(date: LocalDate): List<SchedulePeriod>
|
||||
suspend fun clearAll()
|
||||
|
||||
@@ -146,7 +146,15 @@ class PickerViewModel(
|
||||
syncJob = viewModelScope.launch {
|
||||
try {
|
||||
teacherScheduleRepository.syncPeriod(today).collect { progress ->
|
||||
_uiState.value = _uiState.value.copy(syncProgress = progress)
|
||||
_uiState.value = _uiState.value.copy(
|
||||
syncProgress = progress,
|
||||
error = if (progress.failed > 0) {
|
||||
"Не удалось загрузить расписание: ${progress.failed} из " +
|
||||
"${progress.total} кафедр. Проверьте подключение к интернету."
|
||||
} else {
|
||||
_uiState.value.error
|
||||
}
|
||||
)
|
||||
}
|
||||
} catch (e: CancellationException) {
|
||||
throw e
|
||||
|
||||
@@ -89,7 +89,9 @@ class TeacherScheduleViewModel(
|
||||
if (!scheduleRepository.hasData(period)) {
|
||||
_uiState.value = _uiState.value.copy(isLoading = true, loadError = false)
|
||||
try {
|
||||
scheduleRepository.syncPeriod(period.weekStart).collect { }
|
||||
// force = true: hasData() already proved there is nothing cached for
|
||||
// this period, so never let the staleness window skip the fetch.
|
||||
scheduleRepository.syncPeriod(period.weekStart, force = true).collect { }
|
||||
} catch (e: CancellationException) {
|
||||
throw e
|
||||
} catch (e: Exception) {
|
||||
@@ -199,7 +201,7 @@ class TeacherScheduleViewModel(
|
||||
try {
|
||||
_uiState.value = _uiState.value.copy(isLoading = true, loadError = false)
|
||||
try {
|
||||
scheduleRepository.syncPeriod(ScheduleDates.moscowToday()).collect { }
|
||||
scheduleRepository.syncPeriod(ScheduleDates.moscowToday(), force = true).collect { }
|
||||
} catch (e: CancellationException) {
|
||||
throw e
|
||||
} catch (e: Exception) {
|
||||
|
||||
Reference in New Issue
Block a user