From 9b90b655ab4e6056299842847c82499036314357 Mon Sep 17 00:00:00 2001 From: Sweetbread Date: Sun, 15 Feb 2026 19:13:38 +0300 Subject: [PATCH] wip: cache refactor --- .../main/java/ru/sweetbread/unn/api/API.kt | 9 +- .../java/ru/sweetbread/unn/api/endpoints.kt | 7 +- .../main/java/ru/sweetbread/unn/api/models.kt | 4 +- app/src/main/java/ru/sweetbread/unn/db/DB.kt | 4 +- .../java/ru/sweetbread/unn/db/Schedule.kt | 319 ++++++------------ 5 files changed, 109 insertions(+), 234 deletions(-) diff --git a/app/src/main/java/ru/sweetbread/unn/api/API.kt b/app/src/main/java/ru/sweetbread/unn/api/API.kt index 0b6e195..90009c1 100644 --- a/app/src/main/java/ru/sweetbread/unn/api/API.kt +++ b/app/src/main/java/ru/sweetbread/unn/api/API.kt @@ -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 @@ -66,11 +66,10 @@ private suspend fun getMyself(login: String) = coroutineScope { } suspend fun getScheduleDay( + date: LocalDate, type: Type = ME.type, - id: Int = ME.unnId!!, - date: LocalDate -): ArrayList { - + id: Int = ME.unnId!! +): List { if ((type == ME.type) and (id == ME.unnId!!)) { val schedule = withContext(Dispatchers.IO) { loadSchedule(date) } Log.d("Schedule", schedule.joinToString()) diff --git a/app/src/main/java/ru/sweetbread/unn/api/endpoints.kt b/app/src/main/java/ru/sweetbread/unn/api/endpoints.kt index 0054a2a..63a834b 100644 --- a/app/src/main/java/ru/sweetbread/unn/api/endpoints.kt +++ b/app/src/main/java/ru/sweetbread/unn/api/endpoints.kt @@ -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 @@ -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( type: Type = ME.type, id: Int = ME.unnId!!, diff --git a/app/src/main/java/ru/sweetbread/unn/api/models.kt b/app/src/main/java/ru/sweetbread/unn/api/models.kt index 28de14d..491a4df 100644 --- a/app/src/main/java/ru/sweetbread/unn/api/models.kt +++ b/app/src/main/java/ru/sweetbread/unn/api/models.kt @@ -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 @@ -29,7 +29,7 @@ class ScheduleUnit( val date: LocalDate, val discipline: Discipline, val kindOfWork: KindOfWork, - val lecturers: ArrayList, + val lecturers: List, val stream: String, val begin: LocalTime, val end: LocalTime diff --git a/app/src/main/java/ru/sweetbread/unn/db/DB.kt b/app/src/main/java/ru/sweetbread/unn/db/DB.kt index c3ce3ec..b37583f 100644 --- a/app/src/main/java/ru/sweetbread/unn/db/DB.kt +++ b/app/src/main/java/ru/sweetbread/unn/db/DB.kt @@ -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 @@ -17,7 +17,7 @@ val cacheDb = roomDb(name = "cache") { DisciplineDB::class, KindOfWorkDB::class, LecturerDB::class, - ScheduleUnitDB::class], version = 1) + ScheduleUnitDB::class], version = 2) abstract class AppDatabase : RoomDatabase() { abstract fun userDao(): UserDao abstract fun scheduleDao(): ScheduleItemDao diff --git a/app/src/main/java/ru/sweetbread/unn/db/Schedule.kt b/app/src/main/java/ru/sweetbread/unn/db/Schedule.kt index 0964f26..6ebb49a 100644 --- a/app/src/main/java/ru/sweetbread/unn/db/Schedule.kt +++ b/app/src/main/java/ru/sweetbread/unn/db/Schedule.kt @@ -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 -import android.util.Log import androidx.room.ColumnInfo import androidx.room.Dao import androidx.room.Delete @@ -11,6 +10,7 @@ import androidx.room.Insert import androidx.room.OnConflictStrategy import androidx.room.PrimaryKey import androidx.room.Query +import kotlinx.coroutines.flow.Flow import ru.sweetbread.unn.api.Auditorium import ru.sweetbread.unn.api.Building import ru.sweetbread.unn.api.Discipline @@ -28,7 +28,7 @@ data class BuildingDB( @PrimaryKey val oid: Int, @ColumnInfo val name: String, @ColumnInfo val gid: Int, - @ColumnInfo val expiredAt: String + @ColumnInfo val updatedAt: String ) @Dao @@ -37,40 +37,15 @@ interface BuildingDao { fun get(oid: Int): BuildingDB? @Insert(onConflict = OnConflictStrategy.REPLACE) - fun insert(building: BuildingDB) + suspend fun insert(buildings: List) @Delete fun delete(building: BuildingDB) } -fun cacheBuilding(building: Building) { - cacheDb.buildingDao().insert( - BuildingDB( - building.oid, - building.name, - building.gid, - LocalDateTime.now().plusMonths(1).format(DateTimeFormatter.ISO_DATE_TIME) - ) - ) -} +fun BuildingDB.toModel() = Building(name, gid, oid) +fun Building.toEntity() = BuildingDB(oid, name, gid, LocalDateTime.now().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 data class AuditoriumDB( @@ -78,7 +53,7 @@ data class AuditoriumDB( @ColumnInfo val name: String, @ColumnInfo val floor: Int?, @ColumnInfo val buildingOid: Int, - @ColumnInfo val expiredAt: String + @ColumnInfo val updatedAt: String ) @Dao @@ -87,43 +62,36 @@ interface AuditoriumDao { fun get(oid: Int): AuditoriumDB? @Insert(onConflict = OnConflictStrategy.REPLACE) - fun insert(auditorium: AuditoriumDB) + suspend fun insert(auditoriums: List) @Delete fun delete(auditorium: AuditoriumDB) } -fun cacheAuditorium(auditorium: Auditorium) { - cacheBuilding(auditorium.building) - cacheDb.auditoriumDao().insert( - AuditoriumDB( - auditorium.oid, - auditorium.name, - auditorium.floor, - auditorium.building.oid, - LocalDateTime.now().plusMonths(1).format(DateTimeFormatter.ISO_DATE_TIME) - ) +fun AuditoriumDB.toModel(): Auditorium? { + return Auditorium( + name, + oid, + floor ?: 0, + cacheDb.buildingDao().get(buildingOid)?.toModel() ?: return null ) } +fun Auditorium.toEntity() = AuditoriumDB( + oid, + name, + floor, + building.oid, + LocalDateTime.now().format(DateTimeFormatter.ISO_DATE_TIME) +) -fun loadAuditorium(oid: Int): Auditorium? { - return cacheDb.auditoriumDao().get(oid)?.let { - if (LocalDateTime.parse( - it.expiredAt, - DateTimeFormatter.ISO_DATE_TIME - ) > LocalDateTime.now() - ) { - cacheDb.auditoriumDao().delete(it) - return null - } - val building = loadBuilding(it.buildingOid) ?: return null - return Auditorium( - it.name, - it.oid, - it.floor ?: 0, - building - ) - } +suspend fun cacheAuditorium(auditoriums: List) { + cacheDb.buildingDao().insert( + auditoriums.map { it.building }.map { it.toEntity() } + ) + + cacheDb.auditoriumDao().insert( + auditoriums.map { it.toEntity() } + ) } @Entity @@ -131,7 +99,7 @@ data class DisciplineDB( @PrimaryKey val oid: Int, @ColumnInfo val name: String, @ColumnInfo val type: Int, - @ColumnInfo val expiredAt: String + @ColumnInfo val updatedAt: String ) @Dao @@ -140,41 +108,15 @@ interface DisciplineDao { fun get(oid: Int): DisciplineDB? @Insert(onConflict = OnConflictStrategy.REPLACE) - fun insert(discipline: DisciplineDB) + suspend fun insert(disciplines: List) @Delete fun delete(discipline: DisciplineDB) } -fun cacheDiscipline(discipline: Discipline) { - cacheDb.disciplineDao().insert( - DisciplineDB( - discipline.oid, - discipline.name, - discipline.type, - LocalDateTime.now().plusMonths(1).format(DateTimeFormatter.ISO_DATE_TIME) - ) - ) -} +fun DisciplineDB.toModel() = Discipline(name, oid, type) +fun Discipline.toEntity() = DisciplineDB(oid, name, type, LocalDateTime.now().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 data class KindOfWorkDB( @@ -182,7 +124,7 @@ data class KindOfWorkDB( @ColumnInfo val name: String, @ColumnInfo val uid: String, @ColumnInfo val complexity: Int, - @ColumnInfo val expiredAt: String + @ColumnInfo val updatedAt: String ) @Dao @@ -191,43 +133,21 @@ interface KindOfWorkDao { fun get(oid: Int): KindOfWorkDB? @Insert(onConflict = OnConflictStrategy.REPLACE) - fun insert(kindOfWork: KindOfWorkDB) + suspend fun insert(kindOfWork: List) @Delete fun delete(kindOfWork: KindOfWorkDB) } -fun cacheKindOfWork(kindOfWork: KindOfWork) { - cacheDb.kindOfWorkDao().insert( - KindOfWorkDB( - kindOfWork.oid, - kindOfWork.name, - kindOfWork.uid, - kindOfWork.complexity, - LocalDateTime.now().plusMonths(1).format(DateTimeFormatter.ISO_DATE_TIME) - ) - ) -} +fun KindOfWorkDB.toModel() = KindOfWork(name, oid, uid, complexity) +fun KindOfWork.toEntity() = KindOfWorkDB( + oid, + name, + uid, + complexity, + LocalDateTime.now().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 data class LecturerDB( @@ -236,7 +156,7 @@ data class LecturerDB( @ColumnInfo val rank: LecturerRank, @ColumnInfo val email: String, @ColumnInfo val uid: String, - @ColumnInfo val expiredAt: String + @ColumnInfo val updatedAt: String ) @Dao @@ -245,45 +165,22 @@ interface LecturerDao { fun get(unnId: Int): LecturerDB? @Insert(onConflict = OnConflictStrategy.REPLACE) - fun insert(lecturer: LecturerDB) + suspend fun insert(lecturers: List) @Delete fun delete(lecturer: LecturerDB) } -fun cacheLecturer(lecturer: Lecturer) { - cacheDb.lecturerDao().insert( - LecturerDB( - lecturer.unnId, - lecturer.name, - lecturer.rank, - lecturer.email, - lecturer.uid, - LocalDateTime.now().plusMonths(1).format(DateTimeFormatter.ISO_DATE_TIME) - ) - ) -} +fun LecturerDB.toModel() = Lecturer(name, rank, email, unnId, uid) +fun Lecturer.toEntity() = LecturerDB( + unnId, + name, + rank, + email, + uid, + LocalDateTime.now().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 data class ScheduleUnitDB( @@ -296,9 +193,9 @@ data class ScheduleUnitDB( @ColumnInfo val auditoriumOid: Int, @ColumnInfo val disciplineOid: 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 interface ScheduleItemDao { @@ -308,78 +205,52 @@ interface ScheduleItemDao { @Query("SELECT * FROM scheduleUnitDB WHERE date = :date ORDER BY `begin`") fun getSchedule(date: String): List + @Query("SELECT * FROM scheduleUnitDB WHERE date = :date ORDER BY `begin`") + fun getScheduleFlow(date: String): Flow> + @Insert(onConflict = OnConflictStrategy.REPLACE) - fun insert(user: ScheduleUnitDB) + suspend fun insert(units: List) @Delete - fun delete(user: ScheduleUnitDB) + fun delete(unit: ScheduleUnitDB) + + @Query("DELETE FROM scheduleUnitDB WHERE date = :date") + fun deleteByDate(date: String) } -fun cacheSchedule(item: ScheduleUnit) { - cacheAuditorium(item.auditorium) - cacheDiscipline(item.discipline) - cacheKindOfWork(item.kindOfWork) - cacheLecturer(item.lecturers[0]) - - cacheDb.scheduleDao().insert( - ScheduleUnitDB( - item.oid, - item.date.format(DateTimeFormatter.ISO_DATE), - item.stream, - 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 ScheduleUnitDB.toModel(): ScheduleUnit? { + return ScheduleUnit( + oid, + cacheDb.auditoriumDao().get(auditoriumOid)?.toModel() ?: return null, + LocalDate.parse(date, DateTimeFormatter.ISO_DATE), + cacheDb.disciplineDao().get(disciplineOid)?.toModel() ?: return null, + cacheDb.kindOfWorkDao().get(kindOfWorkOid)?.toModel() ?: return null, + lecturersId.split(",").map { cacheDb.lecturerDao().get(it.toInt())?.toModel() ?: return null }, + stream, + LocalTime.parse(begin, DateTimeFormatter.ISO_TIME), + LocalTime.parse(end, DateTimeFormatter.ISO_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) { - for (item in items) - cacheSchedule(item) +suspend fun cacheSchedule(units: List) { + cacheAuditorium(units.map { it.auditorium }) + 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(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 { +fun loadSchedule(date: LocalDate): List = cacheDb.scheduleDao().getSchedule(date.format(DateTimeFormatter.ISO_DATE)) - .map { Log.d("meow", "${it.oid}: ${loadSchedule(it.oid)}") } - return ArrayList( - cacheDb.scheduleDao().getSchedule(date.format(DateTimeFormatter.ISO_DATE)) - .mapNotNull { loadSchedule(it.oid) } - ) -} \ No newline at end of file + .mapNotNull { it.toModel() }