wip: cache refactor

This commit is contained in:
2026-02-15 23:28:40 +03:00
parent 12dc82e5f4
commit 9b90b655ab
5 changed files with 109 additions and 234 deletions
@@ -1,4 +1,4 @@
// Copyright (c) 2025 Gleb Zaharov. License: GPLv3 (see LICENSE). // Copyright (c) 2026 Gleb Zaharov. License: GPLv3 (see LICENSE).
package ru.sweetbread.unn.api package ru.sweetbread.unn.api
@@ -66,11 +66,10 @@ private suspend fun getMyself(login: String) = coroutineScope {
} }
suspend fun getScheduleDay( suspend fun getScheduleDay(
date: LocalDate,
type: Type = ME.type, type: Type = ME.type,
id: Int = ME.unnId!!, id: Int = ME.unnId!!
date: LocalDate ): List<ScheduleUnit> {
): ArrayList<ScheduleUnit> {
if ((type == ME.type) and (id == ME.unnId!!)) { if ((type == ME.type) and (id == ME.unnId!!)) {
val schedule = withContext(Dispatchers.IO) { loadSchedule(date) } val schedule = withContext(Dispatchers.IO) { loadSchedule(date) }
Log.d("Schedule", schedule.joinToString()) Log.d("Schedule", schedule.joinToString())
@@ -1,4 +1,4 @@
// Copyright (c) 2025 Gleb Zaharov. License: GPLv3 (see LICENSE). // Copyright (c) 2026 Gleb Zaharov. License: GPLv3 (see LICENSE).
package ru.sweetbread.unn.api package ru.sweetbread.unn.api
@@ -116,6 +116,11 @@ suspend fun getUser(userId: Int? = null): User {
) )
} }
/**
* Fetch schedule from Uni API
*
* @return [ArrayList] of all [ScheduleUnit]s between [start] and [finish]
*/
suspend fun downloadSchedule( suspend fun downloadSchedule(
type: Type = ME.type, type: Type = ME.type,
id: Int = ME.unnId!!, id: Int = ME.unnId!!,
@@ -1,4 +1,4 @@
// Copyright (c) 2025 Gleb Zaharov. License: GPLv3 (see LICENSE). // Copyright (c) 2026 Gleb Zaharov. License: GPLv3 (see LICENSE).
package ru.sweetbread.unn.api package ru.sweetbread.unn.api
@@ -29,7 +29,7 @@ class ScheduleUnit(
val date: LocalDate, val date: LocalDate,
val discipline: Discipline, val discipline: Discipline,
val kindOfWork: KindOfWork, val kindOfWork: KindOfWork,
val lecturers: ArrayList<Lecturer>, val lecturers: List<Lecturer>,
val stream: String, val stream: String,
val begin: LocalTime, val begin: LocalTime,
val end: LocalTime val end: LocalTime
+2 -2
View File
@@ -1,4 +1,4 @@
// Copyright (c) 2025 Gleb Zaharov. License: GPLv3 (see LICENSE). // Copyright (c) 2026 Gleb Zaharov. License: GPLv3 (see LICENSE).
package ru.sweetbread.unn.db package ru.sweetbread.unn.db
@@ -17,7 +17,7 @@ val cacheDb = roomDb<AppDatabase>(name = "cache") {
DisciplineDB::class, DisciplineDB::class,
KindOfWorkDB::class, KindOfWorkDB::class,
LecturerDB::class, LecturerDB::class,
ScheduleUnitDB::class], version = 1) ScheduleUnitDB::class], version = 2)
abstract class AppDatabase : RoomDatabase() { abstract class AppDatabase : RoomDatabase() {
abstract fun userDao(): UserDao abstract fun userDao(): UserDao
abstract fun scheduleDao(): ScheduleItemDao abstract fun scheduleDao(): ScheduleItemDao
@@ -1,8 +1,7 @@
// Copyright (c) 2025 Gleb Zaharov. License: GPLv3 (see LICENSE). // Copyright (c) 2026 Gleb Zaharov. License: GPLv3 (see LICENSE).
package ru.sweetbread.unn.db package ru.sweetbread.unn.db
import android.util.Log
import androidx.room.ColumnInfo import androidx.room.ColumnInfo
import androidx.room.Dao import androidx.room.Dao
import androidx.room.Delete import androidx.room.Delete
@@ -11,6 +10,7 @@ import androidx.room.Insert
import androidx.room.OnConflictStrategy import androidx.room.OnConflictStrategy
import androidx.room.PrimaryKey import androidx.room.PrimaryKey
import androidx.room.Query import androidx.room.Query
import kotlinx.coroutines.flow.Flow
import ru.sweetbread.unn.api.Auditorium import ru.sweetbread.unn.api.Auditorium
import ru.sweetbread.unn.api.Building import ru.sweetbread.unn.api.Building
import ru.sweetbread.unn.api.Discipline import ru.sweetbread.unn.api.Discipline
@@ -28,7 +28,7 @@ data class BuildingDB(
@PrimaryKey val oid: Int, @PrimaryKey val oid: Int,
@ColumnInfo val name: String, @ColumnInfo val name: String,
@ColumnInfo val gid: Int, @ColumnInfo val gid: Int,
@ColumnInfo val expiredAt: String @ColumnInfo val updatedAt: String
) )
@Dao @Dao
@@ -37,40 +37,15 @@ interface BuildingDao {
fun get(oid: Int): BuildingDB? fun get(oid: Int): BuildingDB?
@Insert(onConflict = OnConflictStrategy.REPLACE) @Insert(onConflict = OnConflictStrategy.REPLACE)
fun insert(building: BuildingDB) suspend fun insert(buildings: List<BuildingDB>)
@Delete @Delete
fun delete(building: BuildingDB) fun delete(building: BuildingDB)
} }
fun cacheBuilding(building: Building) { fun BuildingDB.toModel() = Building(name, gid, oid)
cacheDb.buildingDao().insert( fun Building.toEntity() = BuildingDB(oid, name, gid, LocalDateTime.now().format(DateTimeFormatter.ISO_DATE_TIME))
BuildingDB(
building.oid,
building.name,
building.gid,
LocalDateTime.now().plusMonths(1).format(DateTimeFormatter.ISO_DATE_TIME)
)
)
}
fun loadBuilding(oid: Int): Building? {
return cacheDb.buildingDao().get(oid)?.let {
if (LocalDateTime.parse(
it.expiredAt,
DateTimeFormatter.ISO_DATE_TIME
) > LocalDateTime.now()
) {
cacheDb.buildingDao().delete(it)
return null
}
Building(
it.name,
it.gid,
it.oid
)
}
}
@Entity @Entity
data class AuditoriumDB( data class AuditoriumDB(
@@ -78,7 +53,7 @@ data class AuditoriumDB(
@ColumnInfo val name: String, @ColumnInfo val name: String,
@ColumnInfo val floor: Int?, @ColumnInfo val floor: Int?,
@ColumnInfo val buildingOid: Int, @ColumnInfo val buildingOid: Int,
@ColumnInfo val expiredAt: String @ColumnInfo val updatedAt: String
) )
@Dao @Dao
@@ -87,43 +62,36 @@ interface AuditoriumDao {
fun get(oid: Int): AuditoriumDB? fun get(oid: Int): AuditoriumDB?
@Insert(onConflict = OnConflictStrategy.REPLACE) @Insert(onConflict = OnConflictStrategy.REPLACE)
fun insert(auditorium: AuditoriumDB) suspend fun insert(auditoriums: List<AuditoriumDB>)
@Delete @Delete
fun delete(auditorium: AuditoriumDB) fun delete(auditorium: AuditoriumDB)
} }
fun cacheAuditorium(auditorium: Auditorium) { fun AuditoriumDB.toModel(): Auditorium? {
cacheBuilding(auditorium.building) return Auditorium(
cacheDb.auditoriumDao().insert( name,
AuditoriumDB( oid,
auditorium.oid, floor ?: 0,
auditorium.name, cacheDb.buildingDao().get(buildingOid)?.toModel() ?: return null
auditorium.floor,
auditorium.building.oid,
LocalDateTime.now().plusMonths(1).format(DateTimeFormatter.ISO_DATE_TIME)
)
) )
} }
fun Auditorium.toEntity() = AuditoriumDB(
oid,
name,
floor,
building.oid,
LocalDateTime.now().format(DateTimeFormatter.ISO_DATE_TIME)
)
fun loadAuditorium(oid: Int): Auditorium? { suspend fun cacheAuditorium(auditoriums: List<Auditorium>) {
return cacheDb.auditoriumDao().get(oid)?.let { cacheDb.buildingDao().insert(
if (LocalDateTime.parse( auditoriums.map { it.building }.map { it.toEntity() }
it.expiredAt, )
DateTimeFormatter.ISO_DATE_TIME
) > LocalDateTime.now() cacheDb.auditoriumDao().insert(
) { auditoriums.map { it.toEntity() }
cacheDb.auditoriumDao().delete(it) )
return null
}
val building = loadBuilding(it.buildingOid) ?: return null
return Auditorium(
it.name,
it.oid,
it.floor ?: 0,
building
)
}
} }
@Entity @Entity
@@ -131,7 +99,7 @@ data class DisciplineDB(
@PrimaryKey val oid: Int, @PrimaryKey val oid: Int,
@ColumnInfo val name: String, @ColumnInfo val name: String,
@ColumnInfo val type: Int, @ColumnInfo val type: Int,
@ColumnInfo val expiredAt: String @ColumnInfo val updatedAt: String
) )
@Dao @Dao
@@ -140,41 +108,15 @@ interface DisciplineDao {
fun get(oid: Int): DisciplineDB? fun get(oid: Int): DisciplineDB?
@Insert(onConflict = OnConflictStrategy.REPLACE) @Insert(onConflict = OnConflictStrategy.REPLACE)
fun insert(discipline: DisciplineDB) suspend fun insert(disciplines: List<DisciplineDB>)
@Delete @Delete
fun delete(discipline: DisciplineDB) fun delete(discipline: DisciplineDB)
} }
fun cacheDiscipline(discipline: Discipline) { fun DisciplineDB.toModel() = Discipline(name, oid, type)
cacheDb.disciplineDao().insert( fun Discipline.toEntity() = DisciplineDB(oid, name, type, LocalDateTime.now().format(DateTimeFormatter.ISO_DATE_TIME))
DisciplineDB(
discipline.oid,
discipline.name,
discipline.type,
LocalDateTime.now().plusMonths(1).format(DateTimeFormatter.ISO_DATE_TIME)
)
)
}
fun loadDiscipline(oid: Int): Discipline? {
return cacheDb.disciplineDao().get(oid)?.let {
if (LocalDateTime.parse(
it.expiredAt,
DateTimeFormatter.ISO_DATE_TIME
) > LocalDateTime.now()
) {
cacheDb.disciplineDao().delete(it)
return null
}
return Discipline(
it.name,
it.oid,
it.type
)
}
}
@Entity @Entity
data class KindOfWorkDB( data class KindOfWorkDB(
@@ -182,7 +124,7 @@ data class KindOfWorkDB(
@ColumnInfo val name: String, @ColumnInfo val name: String,
@ColumnInfo val uid: String, @ColumnInfo val uid: String,
@ColumnInfo val complexity: Int, @ColumnInfo val complexity: Int,
@ColumnInfo val expiredAt: String @ColumnInfo val updatedAt: String
) )
@Dao @Dao
@@ -191,43 +133,21 @@ interface KindOfWorkDao {
fun get(oid: Int): KindOfWorkDB? fun get(oid: Int): KindOfWorkDB?
@Insert(onConflict = OnConflictStrategy.REPLACE) @Insert(onConflict = OnConflictStrategy.REPLACE)
fun insert(kindOfWork: KindOfWorkDB) suspend fun insert(kindOfWork: List<KindOfWorkDB>)
@Delete @Delete
fun delete(kindOfWork: KindOfWorkDB) fun delete(kindOfWork: KindOfWorkDB)
} }
fun cacheKindOfWork(kindOfWork: KindOfWork) { fun KindOfWorkDB.toModel() = KindOfWork(name, oid, uid, complexity)
cacheDb.kindOfWorkDao().insert( fun KindOfWork.toEntity() = KindOfWorkDB(
KindOfWorkDB( oid,
kindOfWork.oid, name,
kindOfWork.name, uid,
kindOfWork.uid, complexity,
kindOfWork.complexity, LocalDateTime.now().format(DateTimeFormatter.ISO_DATE_TIME)
LocalDateTime.now().plusMonths(1).format(DateTimeFormatter.ISO_DATE_TIME) )
)
)
}
fun loadKindOfWork(oid: Int): KindOfWork? {
return cacheDb.kindOfWorkDao().get(oid)?.let {
if (LocalDateTime.parse(
it.expiredAt,
DateTimeFormatter.ISO_DATE_TIME
) > LocalDateTime.now()
) {
cacheDb.kindOfWorkDao().delete(it)
return null
}
return KindOfWork(
it.name,
it.oid,
it.uid,
it.complexity
)
}
}
@Entity @Entity
data class LecturerDB( data class LecturerDB(
@@ -236,7 +156,7 @@ data class LecturerDB(
@ColumnInfo val rank: LecturerRank, @ColumnInfo val rank: LecturerRank,
@ColumnInfo val email: String, @ColumnInfo val email: String,
@ColumnInfo val uid: String, @ColumnInfo val uid: String,
@ColumnInfo val expiredAt: String @ColumnInfo val updatedAt: String
) )
@Dao @Dao
@@ -245,45 +165,22 @@ interface LecturerDao {
fun get(unnId: Int): LecturerDB? fun get(unnId: Int): LecturerDB?
@Insert(onConflict = OnConflictStrategy.REPLACE) @Insert(onConflict = OnConflictStrategy.REPLACE)
fun insert(lecturer: LecturerDB) suspend fun insert(lecturers: List<LecturerDB>)
@Delete @Delete
fun delete(lecturer: LecturerDB) fun delete(lecturer: LecturerDB)
} }
fun cacheLecturer(lecturer: Lecturer) { fun LecturerDB.toModel() = Lecturer(name, rank, email, unnId, uid)
cacheDb.lecturerDao().insert( fun Lecturer.toEntity() = LecturerDB(
LecturerDB( unnId,
lecturer.unnId, name,
lecturer.name, rank,
lecturer.rank, email,
lecturer.email, uid,
lecturer.uid, LocalDateTime.now().format(DateTimeFormatter.ISO_DATE_TIME)
LocalDateTime.now().plusMonths(1).format(DateTimeFormatter.ISO_DATE_TIME) )
)
)
}
fun loadLecturer(unnId: Int): Lecturer? {
return cacheDb.lecturerDao().get(unnId)?.let {
if (LocalDateTime.parse(
it.expiredAt,
DateTimeFormatter.ISO_DATE_TIME
) > LocalDateTime.now()
) {
cacheDb.lecturerDao().delete(it)
return null
}
return Lecturer(
it.name,
it.rank,
it.email,
it.unnId,
it.uid
)
}
}
@Entity @Entity
data class ScheduleUnitDB( data class ScheduleUnitDB(
@@ -296,9 +193,9 @@ data class ScheduleUnitDB(
@ColumnInfo val auditoriumOid: Int, @ColumnInfo val auditoriumOid: Int,
@ColumnInfo val disciplineOid: Int, @ColumnInfo val disciplineOid: Int,
@ColumnInfo val kindOfWorkOid: Int, @ColumnInfo val kindOfWorkOid: Int,
@ColumnInfo val lecturerId: Int, // TODO: many-to-many @ColumnInfo val lecturersId: String,
@ColumnInfo val expiredAt: String @ColumnInfo val updatedAt: String
) )
@Dao @Dao
interface ScheduleItemDao { interface ScheduleItemDao {
@@ -308,78 +205,52 @@ interface ScheduleItemDao {
@Query("SELECT * FROM scheduleUnitDB WHERE date = :date ORDER BY `begin`") @Query("SELECT * FROM scheduleUnitDB WHERE date = :date ORDER BY `begin`")
fun getSchedule(date: String): List<ScheduleUnitDB> fun getSchedule(date: String): List<ScheduleUnitDB>
@Query("SELECT * FROM scheduleUnitDB WHERE date = :date ORDER BY `begin`")
fun getScheduleFlow(date: String): Flow<List<ScheduleUnitDB>>
@Insert(onConflict = OnConflictStrategy.REPLACE) @Insert(onConflict = OnConflictStrategy.REPLACE)
fun insert(user: ScheduleUnitDB) suspend fun insert(units: List<ScheduleUnitDB>)
@Delete @Delete
fun delete(user: ScheduleUnitDB) fun delete(unit: ScheduleUnitDB)
@Query("DELETE FROM scheduleUnitDB WHERE date = :date")
fun deleteByDate(date: String)
} }
fun cacheSchedule(item: ScheduleUnit) { fun ScheduleUnitDB.toModel(): ScheduleUnit? {
cacheAuditorium(item.auditorium) return ScheduleUnit(
cacheDiscipline(item.discipline) oid,
cacheKindOfWork(item.kindOfWork) cacheDb.auditoriumDao().get(auditoriumOid)?.toModel() ?: return null,
cacheLecturer(item.lecturers[0]) LocalDate.parse(date, DateTimeFormatter.ISO_DATE),
cacheDb.disciplineDao().get(disciplineOid)?.toModel() ?: return null,
cacheDb.scheduleDao().insert( cacheDb.kindOfWorkDao().get(kindOfWorkOid)?.toModel() ?: return null,
ScheduleUnitDB( lecturersId.split(",").map { cacheDb.lecturerDao().get(it.toInt())?.toModel() ?: return null },
item.oid, stream,
item.date.format(DateTimeFormatter.ISO_DATE), LocalTime.parse(begin, DateTimeFormatter.ISO_TIME),
item.stream, LocalTime.parse(end, DateTimeFormatter.ISO_TIME)
item.begin.format(DateTimeFormatter.ISO_TIME),
item.end.format(DateTimeFormatter.ISO_TIME),
item.auditorium.oid,
item.discipline.oid,
item.kindOfWork.oid,
item.lecturers[0].unnId,
when {
(LocalDate.now() > item.date) -> LocalDateTime.now().plusWeeks(2)
(LocalDate.now().plusWeeks(1) > item.date) -> LocalDateTime.now().plusDays(1)
(LocalDate.now().plusWeeks(2) > item.date) -> LocalDateTime.now().plusWeeks(1)
else -> LocalDateTime.now().plusWeeks(2)
}.format(DateTimeFormatter.ISO_DATE_TIME)
)
) )
} }
fun ScheduleUnit.toEntity() = ScheduleUnitDB(
oid,
date.format(DateTimeFormatter.ISO_DATE),
stream,
begin.format(DateTimeFormatter.ISO_TIME),
end.format(DateTimeFormatter.ISO_TIME),
auditorium.oid,
discipline.oid,
kindOfWork.oid,
lecturers.map { it.unnId }.joinToString(","),
LocalDateTime.now().format(DateTimeFormatter.ISO_DATE_TIME)
)
fun cacheSchedule(items: ArrayList<ScheduleUnit>) { suspend fun cacheSchedule(units: List<ScheduleUnit>) {
for (item in items) cacheAuditorium(units.map { it.auditorium })
cacheSchedule(item) cacheDb.disciplineDao().insert(units.map { it.discipline.toEntity() })
cacheDb.kindOfWorkDao().insert(units.map { it.kindOfWork.toEntity() })
cacheDb.lecturerDao().insert(units.flatMap { it.lecturers }.map { it.toEntity() })
cacheDb.scheduleDao().insert(units.map { it.toEntity() })
} }
fun loadSchedule(date: LocalDate): List<ScheduleUnit> =
fun loadSchedule(oid: Int): ScheduleUnit? {
cacheDb.scheduleDao().getSchedule(oid)?.let {
Log.d("load", it.oid.toString())
if (LocalDateTime.parse(
it.expiredAt,
DateTimeFormatter.ISO_DATE_TIME
) < LocalDateTime.now()
) {
Log.d("delete", it.oid.toString())
cacheDb.scheduleDao().delete(it)
return null
}
return ScheduleUnit(
it.oid,
loadAuditorium(it.auditoriumOid) ?: return null,
LocalDate.parse(it.date, DateTimeFormatter.ISO_DATE),
loadDiscipline(it.disciplineOid) ?: return null,
loadKindOfWork(it.kindOfWorkOid) ?: return null,
arrayListOf(loadLecturer(it.lecturerId) ?: return null),
it.stream,
LocalTime.parse(it.begin, DateTimeFormatter.ISO_TIME),
LocalTime.parse(it.end, DateTimeFormatter.ISO_TIME)
)
}
return null
}
fun loadSchedule(date: LocalDate): ArrayList<ScheduleUnit> {
cacheDb.scheduleDao().getSchedule(date.format(DateTimeFormatter.ISO_DATE)) cacheDb.scheduleDao().getSchedule(date.format(DateTimeFormatter.ISO_DATE))
.map { Log.d("meow", "${it.oid}: ${loadSchedule(it.oid)}") } .mapNotNull { it.toModel() }
return ArrayList(
cacheDb.scheduleDao().getSchedule(date.format(DateTimeFormatter.ISO_DATE))
.mapNotNull { loadSchedule(it.oid) }
)
}