forked from agzam/vyatsu-schedule
init
This commit is contained in:
@@ -0,0 +1,74 @@
|
||||
package agzam.vyatsu.data.repository
|
||||
|
||||
import agzam.vyatsu.data.local.GroupDao
|
||||
import agzam.vyatsu.data.local.GroupEntity
|
||||
import agzam.vyatsu.data.remote.GroupsParser
|
||||
import agzam.vyatsu.data.remote.VyatsuHttpClient
|
||||
import agzam.vyatsu.data.remote.VyatsuUrls
|
||||
import agzam.vyatsu.domain.model.Group
|
||||
import agzam.vyatsu.domain.model.Season
|
||||
import agzam.vyatsu.domain.repository.GroupRepository
|
||||
import kotlinx.coroutines.flow.Flow
|
||||
import kotlinx.coroutines.flow.first
|
||||
import kotlinx.coroutines.flow.map
|
||||
|
||||
class GroupRepositoryImpl(
|
||||
private val groupDao: GroupDao,
|
||||
) : GroupRepository {
|
||||
|
||||
override fun observeGroups(): Flow<List<Group>> =
|
||||
groupDao.observeAll().map { list -> list.map { it.toDomain() } }
|
||||
|
||||
override suspend fun getGroups(): List<Group> =
|
||||
groupDao.observeAll().first().map { it.toDomain() }
|
||||
|
||||
override suspend fun getFaculties(): List<String> = groupDao.getFaculties()
|
||||
|
||||
override suspend fun refreshIfStale(): Boolean {
|
||||
val last = groupDao.maxSyncedAt()
|
||||
if (last != null && now() - last < STALE_WINDOW_MS) {
|
||||
return true
|
||||
}
|
||||
refresh()
|
||||
return true
|
||||
}
|
||||
|
||||
override suspend fun forceRefresh(): Boolean {
|
||||
refresh()
|
||||
return true
|
||||
}
|
||||
|
||||
private suspend fun refresh() {
|
||||
val html = VyatsuHttpClient.getText(VyatsuUrls.GROUPS_PAGE)
|
||||
val groups = GroupsParser.parseGroups(html)
|
||||
val ranges = GroupsParser.parseDateRanges(html)
|
||||
val entities = groups.map { group ->
|
||||
val range = ranges[group.id]
|
||||
val autumn = range?.get(Season.AUTUMN)?.lastOrNull()
|
||||
val spring = range?.get(Season.SPRING)?.lastOrNull()
|
||||
GroupEntity(
|
||||
id = group.id,
|
||||
name = group.name,
|
||||
faculty = group.faculty,
|
||||
autumnFirst = autumn?.first ?: "",
|
||||
autumnSecond = autumn?.second ?: "",
|
||||
springFirst = spring?.first ?: "",
|
||||
springSecond = spring?.second ?: "",
|
||||
lastSyncedAt = now()
|
||||
)
|
||||
}
|
||||
groupDao.insertAll(entities)
|
||||
}
|
||||
|
||||
private fun now(): Long = System.currentTimeMillis()
|
||||
|
||||
private fun GroupEntity.toDomain(): Group = Group(
|
||||
id = id,
|
||||
name = name,
|
||||
faculty = faculty
|
||||
)
|
||||
|
||||
companion object {
|
||||
private const val STALE_WINDOW_MS = 12 * 60 * 60 * 1000L
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,12 @@
|
||||
package agzam.vyatsu.data.repository
|
||||
|
||||
import agzam.vyatsu.data.local.PreferencesDao
|
||||
import agzam.vyatsu.domain.repository.PreferencesRepository
|
||||
|
||||
class PreferencesRepositoryImpl(
|
||||
private val dao: PreferencesDao
|
||||
) : PreferencesRepository {
|
||||
override suspend fun selectedGroupId(): String? = dao.selectedGroupId()
|
||||
override suspend fun setSelectedGroup(groupId: String) = dao.setSelectedGroup(groupId)
|
||||
override suspend fun clearSelectedGroup() = dao.clearSelectedGroup()
|
||||
}
|
||||
@@ -0,0 +1,105 @@
|
||||
package agzam.vyatsu.data.repository
|
||||
|
||||
import agzam.vyatsu.data.local.ScheduleCodec
|
||||
import agzam.vyatsu.data.local.ScheduleDao
|
||||
import agzam.vyatsu.data.local.ScheduleEntity
|
||||
import agzam.vyatsu.data.local.GroupDao
|
||||
import agzam.vyatsu.data.local.GroupEntity
|
||||
import agzam.vyatsu.data.remote.pdf.PdfScheduleParser
|
||||
import agzam.vyatsu.data.remote.VyatsuHttpClient
|
||||
import agzam.vyatsu.data.remote.VyatsuUrls
|
||||
import agzam.vyatsu.domain.ScheduleDates
|
||||
import agzam.vyatsu.domain.model.DateRange
|
||||
import agzam.vyatsu.domain.model.Schedule
|
||||
import agzam.vyatsu.domain.model.Season
|
||||
import agzam.vyatsu.domain.repository.ScheduleRepository
|
||||
import android.util.Log
|
||||
import kotlinx.coroutines.flow.Flow
|
||||
import kotlinx.coroutines.flow.map
|
||||
|
||||
class ScheduleRepositoryImpl(
|
||||
private val scheduleDao: ScheduleDao,
|
||||
private val groupDao: GroupDao,
|
||||
private val codec: ScheduleCodec
|
||||
) : ScheduleRepository {
|
||||
|
||||
override fun observeSchedule(groupId: String): Flow<Schedule?> =
|
||||
scheduleDao.observe(groupId).map { it?.toDomain() }
|
||||
|
||||
override suspend fun getSchedule(groupId: String): Schedule? =
|
||||
scheduleDao.get(groupId)?.toDomain()
|
||||
|
||||
override suspend fun sync(groupId: String): Schedule? {
|
||||
Log.d(TAG, "sync(groupId=$groupId) start")
|
||||
val group = groupDao.getById(groupId)
|
||||
if (group == null) {
|
||||
Log.w(TAG, "sync: group $groupId not found in DB")
|
||||
return null
|
||||
}
|
||||
val season = ScheduleDates.currentSeason()
|
||||
val range = dateRangeFor(group, season)
|
||||
if (range == null) {
|
||||
Log.w(TAG, "sync: no date range for season $season (group ${group.id}, " +
|
||||
"autumn=[${group.autumnFirst}, ${group.autumnSecond}], " +
|
||||
"spring=[${group.springFirst}, ${group.springSecond}])")
|
||||
return null
|
||||
}
|
||||
Log.d(TAG, "sync: season=$season range=[${range.first}, ${range.second}]")
|
||||
|
||||
return try {
|
||||
val pdfUrl = VyatsuUrls.groupPdfUrl(groupId, season, range)
|
||||
Log.d(TAG, "sync: downloading $pdfUrl")
|
||||
val bytes = VyatsuHttpClient.getBytes(pdfUrl)
|
||||
Log.d(TAG, "sync: downloaded ${bytes.size} bytes")
|
||||
|
||||
val days = PdfScheduleParser.parse(bytes)
|
||||
Log.d(TAG, "sync: parsed ${days.size} days")
|
||||
|
||||
val entity = ScheduleEntity(
|
||||
groupId = groupId,
|
||||
season = season.name,
|
||||
dateRangeFirst = range.first,
|
||||
dateRangeSecond = range.second,
|
||||
weeksJson = codec.toJson(days),
|
||||
lastSyncedAt = now()
|
||||
)
|
||||
scheduleDao.upsert(entity)
|
||||
Log.d(TAG, "sync: upserted schedule for $groupId")
|
||||
entity.toDomain()
|
||||
} catch (e: Throwable) {
|
||||
Log.e(TAG, "sync: failed for $groupId (${e::class.simpleName}: ${e.message})", e)
|
||||
null
|
||||
}
|
||||
}
|
||||
|
||||
override suspend fun isStale(groupId: String): Boolean {
|
||||
val entity = scheduleDao.get(groupId) ?: return true
|
||||
return now() - entity.lastSyncedAt > STALE_WINDOW_MS
|
||||
}
|
||||
|
||||
override suspend fun clear(groupId: String) = scheduleDao.delete(groupId)
|
||||
|
||||
private fun dateRangeFor(group: GroupEntity, season: Season): DateRange? {
|
||||
return when (season) {
|
||||
Season.AUTUMN -> if (group.autumnFirst.isNotEmpty() && group.autumnSecond.isNotEmpty()) {
|
||||
DateRange(group.autumnFirst, group.autumnSecond)
|
||||
} else null
|
||||
Season.SPRING -> if (group.springFirst.isNotEmpty() && group.springSecond.isNotEmpty()) {
|
||||
DateRange(group.springFirst, group.springSecond)
|
||||
} else null
|
||||
}
|
||||
}
|
||||
|
||||
private fun now(): Long = System.currentTimeMillis()
|
||||
|
||||
private fun ScheduleEntity.toDomain(): Schedule = Schedule(
|
||||
groupId = groupId,
|
||||
season = Season.valueOf(season),
|
||||
days = codec.fromJson(weeksJson)
|
||||
)
|
||||
|
||||
companion object {
|
||||
private const val STALE_WINDOW_MS = 12 * 60 * 60 * 1000L
|
||||
private const val TAG = "ScheduleRepo"
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user