feat: 2-week select
This commit is contained in:
@@ -0,0 +1,427 @@
|
|||||||
|
{
|
||||||
|
"formatVersion": 1,
|
||||||
|
"database": {
|
||||||
|
"version": 3,
|
||||||
|
"identityHash": "3fd5eda9ac5e29fc96371ecf246e437c",
|
||||||
|
"entities": [
|
||||||
|
{
|
||||||
|
"tableName": "groups",
|
||||||
|
"createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`id` TEXT NOT NULL, `name` TEXT NOT NULL, `faculty` TEXT NOT NULL, `lastSyncedAt` INTEGER NOT NULL, PRIMARY KEY(`id`))",
|
||||||
|
"fields": [
|
||||||
|
{
|
||||||
|
"fieldPath": "id",
|
||||||
|
"columnName": "id",
|
||||||
|
"affinity": "TEXT",
|
||||||
|
"notNull": true
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldPath": "name",
|
||||||
|
"columnName": "name",
|
||||||
|
"affinity": "TEXT",
|
||||||
|
"notNull": true
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldPath": "faculty",
|
||||||
|
"columnName": "faculty",
|
||||||
|
"affinity": "TEXT",
|
||||||
|
"notNull": true
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldPath": "lastSyncedAt",
|
||||||
|
"columnName": "lastSyncedAt",
|
||||||
|
"affinity": "INTEGER",
|
||||||
|
"notNull": true
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"primaryKey": {
|
||||||
|
"autoGenerate": false,
|
||||||
|
"columnNames": [
|
||||||
|
"id"
|
||||||
|
]
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"tableName": "group_periods",
|
||||||
|
"createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`id` INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, `groupId` TEXT NOT NULL, `season` TEXT NOT NULL, `weekStart` INTEGER NOT NULL, `weekEnd` INTEGER NOT NULL, FOREIGN KEY(`groupId`) REFERENCES `groups`(`id`) ON UPDATE NO ACTION ON DELETE CASCADE )",
|
||||||
|
"fields": [
|
||||||
|
{
|
||||||
|
"fieldPath": "id",
|
||||||
|
"columnName": "id",
|
||||||
|
"affinity": "INTEGER",
|
||||||
|
"notNull": true
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldPath": "groupId",
|
||||||
|
"columnName": "groupId",
|
||||||
|
"affinity": "TEXT",
|
||||||
|
"notNull": true
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldPath": "season",
|
||||||
|
"columnName": "season",
|
||||||
|
"affinity": "TEXT",
|
||||||
|
"notNull": true
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldPath": "weekStart",
|
||||||
|
"columnName": "weekStart",
|
||||||
|
"affinity": "INTEGER",
|
||||||
|
"notNull": true
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldPath": "weekEnd",
|
||||||
|
"columnName": "weekEnd",
|
||||||
|
"affinity": "INTEGER",
|
||||||
|
"notNull": true
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"primaryKey": {
|
||||||
|
"autoGenerate": true,
|
||||||
|
"columnNames": [
|
||||||
|
"id"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"indices": [
|
||||||
|
{
|
||||||
|
"name": "index_group_periods_groupId",
|
||||||
|
"unique": false,
|
||||||
|
"columnNames": [
|
||||||
|
"groupId"
|
||||||
|
],
|
||||||
|
"orders": [],
|
||||||
|
"createSql": "CREATE INDEX IF NOT EXISTS `index_group_periods_groupId` ON `${TABLE_NAME}` (`groupId`)"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"foreignKeys": [
|
||||||
|
{
|
||||||
|
"table": "groups",
|
||||||
|
"onDelete": "CASCADE",
|
||||||
|
"onUpdate": "NO ACTION",
|
||||||
|
"columns": [
|
||||||
|
"groupId"
|
||||||
|
],
|
||||||
|
"referencedColumns": [
|
||||||
|
"id"
|
||||||
|
]
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"tableName": "schedules",
|
||||||
|
"createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`id` INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, `groupId` TEXT NOT NULL, `season` TEXT NOT NULL, `weekStart` INTEGER NOT NULL, `weekEnd` INTEGER NOT NULL, `lastSyncedAt` INTEGER NOT NULL, FOREIGN KEY(`groupId`) REFERENCES `groups`(`id`) ON UPDATE NO ACTION ON DELETE CASCADE )",
|
||||||
|
"fields": [
|
||||||
|
{
|
||||||
|
"fieldPath": "id",
|
||||||
|
"columnName": "id",
|
||||||
|
"affinity": "INTEGER",
|
||||||
|
"notNull": true
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldPath": "groupId",
|
||||||
|
"columnName": "groupId",
|
||||||
|
"affinity": "TEXT",
|
||||||
|
"notNull": true
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldPath": "season",
|
||||||
|
"columnName": "season",
|
||||||
|
"affinity": "TEXT",
|
||||||
|
"notNull": true
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldPath": "weekStart",
|
||||||
|
"columnName": "weekStart",
|
||||||
|
"affinity": "INTEGER",
|
||||||
|
"notNull": true
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldPath": "weekEnd",
|
||||||
|
"columnName": "weekEnd",
|
||||||
|
"affinity": "INTEGER",
|
||||||
|
"notNull": true
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldPath": "lastSyncedAt",
|
||||||
|
"columnName": "lastSyncedAt",
|
||||||
|
"affinity": "INTEGER",
|
||||||
|
"notNull": true
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"primaryKey": {
|
||||||
|
"autoGenerate": true,
|
||||||
|
"columnNames": [
|
||||||
|
"id"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"indices": [
|
||||||
|
{
|
||||||
|
"name": "index_schedules_groupId",
|
||||||
|
"unique": false,
|
||||||
|
"columnNames": [
|
||||||
|
"groupId"
|
||||||
|
],
|
||||||
|
"orders": [],
|
||||||
|
"createSql": "CREATE INDEX IF NOT EXISTS `index_schedules_groupId` ON `${TABLE_NAME}` (`groupId`)"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "index_schedules_groupId_season_weekStart",
|
||||||
|
"unique": true,
|
||||||
|
"columnNames": [
|
||||||
|
"groupId",
|
||||||
|
"season",
|
||||||
|
"weekStart"
|
||||||
|
],
|
||||||
|
"orders": [],
|
||||||
|
"createSql": "CREATE UNIQUE INDEX IF NOT EXISTS `index_schedules_groupId_season_weekStart` ON `${TABLE_NAME}` (`groupId`, `season`, `weekStart`)"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"foreignKeys": [
|
||||||
|
{
|
||||||
|
"table": "groups",
|
||||||
|
"onDelete": "CASCADE",
|
||||||
|
"onUpdate": "NO ACTION",
|
||||||
|
"columns": [
|
||||||
|
"groupId"
|
||||||
|
],
|
||||||
|
"referencedColumns": [
|
||||||
|
"id"
|
||||||
|
]
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"tableName": "days",
|
||||||
|
"createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`id` INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, `scheduleId` INTEGER NOT NULL, `date` INTEGER NOT NULL, FOREIGN KEY(`scheduleId`) REFERENCES `schedules`(`id`) ON UPDATE NO ACTION ON DELETE CASCADE )",
|
||||||
|
"fields": [
|
||||||
|
{
|
||||||
|
"fieldPath": "id",
|
||||||
|
"columnName": "id",
|
||||||
|
"affinity": "INTEGER",
|
||||||
|
"notNull": true
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldPath": "scheduleId",
|
||||||
|
"columnName": "scheduleId",
|
||||||
|
"affinity": "INTEGER",
|
||||||
|
"notNull": true
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldPath": "date",
|
||||||
|
"columnName": "date",
|
||||||
|
"affinity": "INTEGER",
|
||||||
|
"notNull": true
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"primaryKey": {
|
||||||
|
"autoGenerate": true,
|
||||||
|
"columnNames": [
|
||||||
|
"id"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"indices": [
|
||||||
|
{
|
||||||
|
"name": "index_days_scheduleId",
|
||||||
|
"unique": false,
|
||||||
|
"columnNames": [
|
||||||
|
"scheduleId"
|
||||||
|
],
|
||||||
|
"orders": [],
|
||||||
|
"createSql": "CREATE INDEX IF NOT EXISTS `index_days_scheduleId` ON `${TABLE_NAME}` (`scheduleId`)"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "index_days_date",
|
||||||
|
"unique": false,
|
||||||
|
"columnNames": [
|
||||||
|
"date"
|
||||||
|
],
|
||||||
|
"orders": [],
|
||||||
|
"createSql": "CREATE INDEX IF NOT EXISTS `index_days_date` ON `${TABLE_NAME}` (`date`)"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"foreignKeys": [
|
||||||
|
{
|
||||||
|
"table": "schedules",
|
||||||
|
"onDelete": "CASCADE",
|
||||||
|
"onUpdate": "NO ACTION",
|
||||||
|
"columns": [
|
||||||
|
"scheduleId"
|
||||||
|
],
|
||||||
|
"referencedColumns": [
|
||||||
|
"id"
|
||||||
|
]
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"tableName": "lessons",
|
||||||
|
"createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`id` INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, `dayId` INTEGER NOT NULL, `date` INTEGER NOT NULL, `slotIndex` INTEGER NOT NULL, `start` INTEGER NOT NULL, `end` INTEGER NOT NULL, `lessonIndex` INTEGER NOT NULL, `raw` TEXT NOT NULL, `room` TEXT, `groupName` TEXT, `subgroup` TEXT, `subject` TEXT, `type` TEXT NOT NULL, `teacher` TEXT, FOREIGN KEY(`dayId`) REFERENCES `days`(`id`) ON UPDATE NO ACTION ON DELETE CASCADE )",
|
||||||
|
"fields": [
|
||||||
|
{
|
||||||
|
"fieldPath": "id",
|
||||||
|
"columnName": "id",
|
||||||
|
"affinity": "INTEGER",
|
||||||
|
"notNull": true
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldPath": "dayId",
|
||||||
|
"columnName": "dayId",
|
||||||
|
"affinity": "INTEGER",
|
||||||
|
"notNull": true
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldPath": "date",
|
||||||
|
"columnName": "date",
|
||||||
|
"affinity": "INTEGER",
|
||||||
|
"notNull": true
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldPath": "slotIndex",
|
||||||
|
"columnName": "slotIndex",
|
||||||
|
"affinity": "INTEGER",
|
||||||
|
"notNull": true
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldPath": "start",
|
||||||
|
"columnName": "start",
|
||||||
|
"affinity": "INTEGER",
|
||||||
|
"notNull": true
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldPath": "end",
|
||||||
|
"columnName": "end",
|
||||||
|
"affinity": "INTEGER",
|
||||||
|
"notNull": true
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldPath": "lessonIndex",
|
||||||
|
"columnName": "lessonIndex",
|
||||||
|
"affinity": "INTEGER",
|
||||||
|
"notNull": true
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldPath": "raw",
|
||||||
|
"columnName": "raw",
|
||||||
|
"affinity": "TEXT",
|
||||||
|
"notNull": true
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldPath": "room",
|
||||||
|
"columnName": "room",
|
||||||
|
"affinity": "TEXT"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldPath": "groupName",
|
||||||
|
"columnName": "groupName",
|
||||||
|
"affinity": "TEXT"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldPath": "subgroup",
|
||||||
|
"columnName": "subgroup",
|
||||||
|
"affinity": "TEXT"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldPath": "subject",
|
||||||
|
"columnName": "subject",
|
||||||
|
"affinity": "TEXT"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldPath": "type",
|
||||||
|
"columnName": "type",
|
||||||
|
"affinity": "TEXT",
|
||||||
|
"notNull": true
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldPath": "teacher",
|
||||||
|
"columnName": "teacher",
|
||||||
|
"affinity": "TEXT"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"primaryKey": {
|
||||||
|
"autoGenerate": true,
|
||||||
|
"columnNames": [
|
||||||
|
"id"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"indices": [
|
||||||
|
{
|
||||||
|
"name": "index_lessons_dayId",
|
||||||
|
"unique": false,
|
||||||
|
"columnNames": [
|
||||||
|
"dayId"
|
||||||
|
],
|
||||||
|
"orders": [],
|
||||||
|
"createSql": "CREATE INDEX IF NOT EXISTS `index_lessons_dayId` ON `${TABLE_NAME}` (`dayId`)"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "index_lessons_date",
|
||||||
|
"unique": false,
|
||||||
|
"columnNames": [
|
||||||
|
"date"
|
||||||
|
],
|
||||||
|
"orders": [],
|
||||||
|
"createSql": "CREATE INDEX IF NOT EXISTS `index_lessons_date` ON `${TABLE_NAME}` (`date`)"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "index_lessons_subject",
|
||||||
|
"unique": false,
|
||||||
|
"columnNames": [
|
||||||
|
"subject"
|
||||||
|
],
|
||||||
|
"orders": [],
|
||||||
|
"createSql": "CREATE INDEX IF NOT EXISTS `index_lessons_subject` ON `${TABLE_NAME}` (`subject`)"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "index_lessons_room",
|
||||||
|
"unique": false,
|
||||||
|
"columnNames": [
|
||||||
|
"room"
|
||||||
|
],
|
||||||
|
"orders": [],
|
||||||
|
"createSql": "CREATE INDEX IF NOT EXISTS `index_lessons_room` ON `${TABLE_NAME}` (`room`)"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"foreignKeys": [
|
||||||
|
{
|
||||||
|
"table": "days",
|
||||||
|
"onDelete": "CASCADE",
|
||||||
|
"onUpdate": "NO ACTION",
|
||||||
|
"columns": [
|
||||||
|
"dayId"
|
||||||
|
],
|
||||||
|
"referencedColumns": [
|
||||||
|
"id"
|
||||||
|
]
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"tableName": "preferences",
|
||||||
|
"createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`key` TEXT NOT NULL, `value` TEXT NOT NULL, PRIMARY KEY(`key`))",
|
||||||
|
"fields": [
|
||||||
|
{
|
||||||
|
"fieldPath": "key",
|
||||||
|
"columnName": "key",
|
||||||
|
"affinity": "TEXT",
|
||||||
|
"notNull": true
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldPath": "value",
|
||||||
|
"columnName": "value",
|
||||||
|
"affinity": "TEXT",
|
||||||
|
"notNull": true
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"primaryKey": {
|
||||||
|
"autoGenerate": false,
|
||||||
|
"columnNames": [
|
||||||
|
"key"
|
||||||
|
]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"setupQueries": [
|
||||||
|
"CREATE TABLE IF NOT EXISTS room_master_table (id INTEGER PRIMARY KEY,identity_hash TEXT)",
|
||||||
|
"INSERT OR REPLACE INTO room_master_table (id,identity_hash) VALUES(42, '3fd5eda9ac5e29fc96371ecf246e437c')"
|
||||||
|
]
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -7,12 +7,13 @@ import androidx.room.TypeConverters
|
|||||||
@Database(
|
@Database(
|
||||||
entities = [
|
entities = [
|
||||||
GroupEntity::class,
|
GroupEntity::class,
|
||||||
|
GroupPeriodEntity::class,
|
||||||
ScheduleEntity::class,
|
ScheduleEntity::class,
|
||||||
DayEntity::class,
|
DayEntity::class,
|
||||||
LessonEntity::class,
|
LessonEntity::class,
|
||||||
PreferenceEntity::class
|
PreferenceEntity::class
|
||||||
],
|
],
|
||||||
version = 2,
|
version = 3,
|
||||||
exportSchema = true
|
exportSchema = true
|
||||||
)
|
)
|
||||||
@TypeConverters(Converters::class)
|
@TypeConverters(Converters::class)
|
||||||
|
|||||||
@@ -25,4 +25,13 @@ interface GroupDao {
|
|||||||
|
|
||||||
@Query("SELECT MAX(lastSyncedAt) FROM groups")
|
@Query("SELECT MAX(lastSyncedAt) FROM groups")
|
||||||
suspend fun maxSyncedAt(): Long?
|
suspend fun maxSyncedAt(): Long?
|
||||||
|
|
||||||
|
@Query("SELECT * FROM group_periods WHERE groupId = :groupId AND weekEnd >= :fromEpochDay ORDER BY weekStart")
|
||||||
|
suspend fun getCurrentAndFuturePeriods(groupId: String, fromEpochDay: Long): List<GroupPeriodEntity>
|
||||||
|
|
||||||
|
@Query("DELETE FROM group_periods")
|
||||||
|
suspend fun deleteAllPeriods()
|
||||||
|
|
||||||
|
@Insert(onConflict = OnConflictStrategy.REPLACE)
|
||||||
|
suspend fun insertPeriods(periods: List<GroupPeriodEntity>)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -8,9 +8,5 @@ data class GroupEntity(
|
|||||||
@PrimaryKey val id: String,
|
@PrimaryKey val id: String,
|
||||||
val name: String,
|
val name: String,
|
||||||
val faculty: String,
|
val faculty: String,
|
||||||
val autumnFirst: Long?,
|
|
||||||
val autumnSecond: Long?,
|
|
||||||
val springFirst: Long?,
|
|
||||||
val springSecond: Long?,
|
|
||||||
val lastSyncedAt: Long
|
val lastSyncedAt: Long
|
||||||
)
|
)
|
||||||
|
|||||||
@@ -0,0 +1,28 @@
|
|||||||
|
package agzam.vyatsu.data.local
|
||||||
|
|
||||||
|
import agzam.vyatsu.domain.model.Season
|
||||||
|
import androidx.room.Entity
|
||||||
|
import androidx.room.ForeignKey
|
||||||
|
import androidx.room.Index
|
||||||
|
import androidx.room.PrimaryKey
|
||||||
|
|
||||||
|
// Все опубликованные на сайте 2-недельные периоды группы, включая ещё не начавшиеся.
|
||||||
|
@Entity(
|
||||||
|
tableName = "group_periods",
|
||||||
|
foreignKeys = [
|
||||||
|
ForeignKey(
|
||||||
|
entity = GroupEntity::class,
|
||||||
|
parentColumns = ["id"],
|
||||||
|
childColumns = ["groupId"],
|
||||||
|
onDelete = ForeignKey.CASCADE
|
||||||
|
)
|
||||||
|
],
|
||||||
|
indices = [Index("groupId")]
|
||||||
|
)
|
||||||
|
data class GroupPeriodEntity(
|
||||||
|
@PrimaryKey(autoGenerate = true) val id: Long = 0,
|
||||||
|
val groupId: String,
|
||||||
|
val season: Season,
|
||||||
|
val weekStart: Long,
|
||||||
|
val weekEnd: Long
|
||||||
|
)
|
||||||
@@ -2,12 +2,12 @@ package agzam.vyatsu.data.repository
|
|||||||
|
|
||||||
import agzam.vyatsu.data.local.GroupDao
|
import agzam.vyatsu.data.local.GroupDao
|
||||||
import agzam.vyatsu.data.local.GroupEntity
|
import agzam.vyatsu.data.local.GroupEntity
|
||||||
|
import agzam.vyatsu.data.local.GroupPeriodEntity
|
||||||
import agzam.vyatsu.data.remote.GroupsParser
|
import agzam.vyatsu.data.remote.GroupsParser
|
||||||
import agzam.vyatsu.data.remote.VyatsuHttpClient
|
import agzam.vyatsu.data.remote.VyatsuHttpClient
|
||||||
import agzam.vyatsu.data.remote.VyatsuUrls
|
import agzam.vyatsu.data.remote.VyatsuUrls
|
||||||
import agzam.vyatsu.domain.ScheduleDates
|
import agzam.vyatsu.domain.ScheduleDates
|
||||||
import agzam.vyatsu.domain.model.Group
|
import agzam.vyatsu.domain.model.Group
|
||||||
import agzam.vyatsu.domain.model.Season
|
|
||||||
import agzam.vyatsu.domain.repository.GroupRepository
|
import agzam.vyatsu.domain.repository.GroupRepository
|
||||||
import kotlinx.coroutines.Dispatchers
|
import kotlinx.coroutines.Dispatchers
|
||||||
import kotlinx.coroutines.flow.Flow
|
import kotlinx.coroutines.flow.Flow
|
||||||
@@ -45,26 +45,33 @@ class GroupRepositoryImpl(
|
|||||||
val parsed = withContext(Dispatchers.IO) {
|
val parsed = withContext(Dispatchers.IO) {
|
||||||
VyatsuHttpClient.openStream(VyatsuUrls.GROUPS_PAGE).use(GroupsParser::parseStream)
|
VyatsuHttpClient.openStream(VyatsuUrls.GROUPS_PAGE).use(GroupsParser::parseStream)
|
||||||
}
|
}
|
||||||
val entities = parsed.groups.map { group ->
|
val now = now()
|
||||||
val range = parsed.dateRanges[group.id]
|
groupDao.insertAll(
|
||||||
val autumn = range?.get(Season.AUTUMN)?.lastOrNull()
|
parsed.groups.map { group ->
|
||||||
val spring = range?.get(Season.SPRING)?.lastOrNull()
|
GroupEntity(
|
||||||
GroupEntity(
|
id = group.id,
|
||||||
id = group.id,
|
name = group.name,
|
||||||
name = group.name,
|
faculty = group.faculty,
|
||||||
faculty = group.faculty,
|
lastSyncedAt = now
|
||||||
autumnFirst = autumn?.first?.let(::epochDay),
|
)
|
||||||
autumnSecond = autumn?.second?.let(::epochDay),
|
}
|
||||||
springFirst = spring?.first?.let(::epochDay),
|
)
|
||||||
springSecond = spring?.second?.let(::epochDay),
|
val periods = parsed.groups.flatMap { group ->
|
||||||
lastSyncedAt = now()
|
parsed.dateRanges[group.id].orEmpty().flatMap { (season, ranges) ->
|
||||||
)
|
ranges.map { range ->
|
||||||
|
GroupPeriodEntity(
|
||||||
|
groupId = group.id,
|
||||||
|
season = season,
|
||||||
|
weekStart = ScheduleDates.asDate(range.first).toEpochDay(),
|
||||||
|
weekEnd = ScheduleDates.asDate(range.second).toEpochDay()
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
groupDao.insertAll(entities)
|
groupDao.deleteAllPeriods()
|
||||||
|
groupDao.insertPeriods(periods)
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun epochDay(dateStr: String): Long = ScheduleDates.asDate(dateStr).toEpochDay()
|
|
||||||
|
|
||||||
private fun now(): Long = System.currentTimeMillis()
|
private fun now(): Long = System.currentTimeMillis()
|
||||||
|
|
||||||
private fun GroupEntity.toDomain(): Group = Group(
|
private fun GroupEntity.toDomain(): Group = Group(
|
||||||
|
|||||||
@@ -1,6 +1,7 @@
|
|||||||
package agzam.vyatsu.data.repository
|
package agzam.vyatsu.data.repository
|
||||||
|
|
||||||
import agzam.vyatsu.data.local.DayEntity
|
import agzam.vyatsu.data.local.DayEntity
|
||||||
|
import agzam.vyatsu.data.local.GroupPeriodEntity
|
||||||
import agzam.vyatsu.data.local.LessonEntity
|
import agzam.vyatsu.data.local.LessonEntity
|
||||||
import agzam.vyatsu.data.local.ScheduleDao
|
import agzam.vyatsu.data.local.ScheduleDao
|
||||||
import agzam.vyatsu.data.local.ScheduleEntity
|
import agzam.vyatsu.data.local.ScheduleEntity
|
||||||
@@ -51,15 +52,38 @@ class ScheduleRepositoryImpl(
|
|||||||
Log.w(TAG, "sync: group $groupId not found in DB")
|
Log.w(TAG, "sync: group $groupId not found in DB")
|
||||||
return null
|
return null
|
||||||
}
|
}
|
||||||
val season = ScheduleDates.currentSeason()
|
val today = ScheduleDates.moscowToday().toEpochDay()
|
||||||
val range = dateRangeFor(group, season) ?: run {
|
val periods = groupDao.getCurrentAndFuturePeriods(groupId, today)
|
||||||
Log.w(TAG, "sync: no date range for season $season (group ${group.id})")
|
if (periods.isEmpty()) {
|
||||||
|
Log.w(TAG, "sync: no current/future periods for group ${group.id}")
|
||||||
return null
|
return null
|
||||||
}
|
}
|
||||||
Log.d(TAG, "sync: season=$season range=[${range.first}, ${range.second}]")
|
|
||||||
|
|
||||||
return try {
|
for (period in periods) {
|
||||||
val pdfUrl = VyatsuUrls.groupPdfUrl(groupId, season, range)
|
syncPeriod(group, period)
|
||||||
|
}
|
||||||
|
|
||||||
|
scheduleDao.trim(groupId, MAX_PERIODS)
|
||||||
|
|
||||||
|
return getSchedule(groupId)
|
||||||
|
}
|
||||||
|
|
||||||
|
private suspend fun syncPeriod(group: GroupEntity, period: GroupPeriodEntity) {
|
||||||
|
val season = period.season
|
||||||
|
val range = DateRange(
|
||||||
|
LocalDate.ofEpochDay(period.weekStart).format(RANGE_FMT),
|
||||||
|
LocalDate.ofEpochDay(period.weekEnd).format(RANGE_FMT)
|
||||||
|
)
|
||||||
|
val existing = scheduleDao.getSchedules(group.id)
|
||||||
|
.firstOrNull { it.season == season && it.weekStart == period.weekStart }
|
||||||
|
val isCurrent = ScheduleDates.moscowToday().toEpochDay() in period.weekStart..period.weekEnd
|
||||||
|
if (existing != null && !isCurrent && now() - existing.lastSyncedAt < STALE_WINDOW_MS) {
|
||||||
|
Log.d(TAG, "sync: period $range up to date, skip")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
val pdfUrl = VyatsuUrls.groupPdfUrl(group.id, season, range)
|
||||||
Log.d(TAG, "sync: downloading $pdfUrl")
|
Log.d(TAG, "sync: downloading $pdfUrl")
|
||||||
val bytes = VyatsuHttpClient.getBytes(pdfUrl)
|
val bytes = VyatsuHttpClient.getBytes(pdfUrl)
|
||||||
Log.d(TAG, "sync: downloaded ${bytes.size} bytes")
|
Log.d(TAG, "sync: downloaded ${bytes.size} bytes")
|
||||||
@@ -67,29 +91,19 @@ class ScheduleRepositoryImpl(
|
|||||||
val days = PdfScheduleParser.parse(bytes)
|
val days = PdfScheduleParser.parse(bytes)
|
||||||
Log.d(TAG, "sync: parsed ${days.size} days")
|
Log.d(TAG, "sync: parsed ${days.size} days")
|
||||||
|
|
||||||
val weekStart = ScheduleDates.asDate(range.first).toEpochDay()
|
val scheduleId = existing?.id ?: scheduleDao.insertSchedule(
|
||||||
val weekEnd = ScheduleDates.asDate(range.second).toEpochDay()
|
|
||||||
|
|
||||||
val existing = scheduleDao.getSchedules(groupId)
|
|
||||||
.firstOrNull { it.season == season && it.weekStart == weekStart }
|
|
||||||
val scheduleId = if (existing != null) existing.id
|
|
||||||
else scheduleDao.insertSchedule(
|
|
||||||
ScheduleEntity(
|
ScheduleEntity(
|
||||||
groupId = groupId,
|
groupId = group.id,
|
||||||
season = season,
|
season = season,
|
||||||
weekStart = weekStart,
|
weekStart = period.weekStart,
|
||||||
weekEnd = weekEnd,
|
weekEnd = period.weekEnd,
|
||||||
lastSyncedAt = now()
|
lastSyncedAt = now()
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
replaceDays(scheduleId, days)
|
replaceDays(scheduleId, days)
|
||||||
|
Log.d(TAG, "sync: period $range synced")
|
||||||
scheduleDao.trim(groupId, MAX_PERIODS)
|
|
||||||
|
|
||||||
getSchedule(groupId)
|
|
||||||
} catch (e: Throwable) {
|
} catch (e: Throwable) {
|
||||||
Log.e(TAG, "sync: failed for $groupId (${e::class.simpleName}: ${e.message})", e)
|
Log.e(TAG, "sync: failed for ${group.id} period $range (${e::class.simpleName}: ${e.message})", e)
|
||||||
null
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -187,30 +201,13 @@ class ScheduleRepositoryImpl(
|
|||||||
teacher = teacher
|
teacher = teacher
|
||||||
)
|
)
|
||||||
|
|
||||||
private fun dateRangeFor(group: GroupEntity, season: Season): DateRange? {
|
|
||||||
return when (season) {
|
|
||||||
Season.AUTUMN -> if (group.autumnFirst != null && group.autumnSecond != null) {
|
|
||||||
DateRange(
|
|
||||||
LocalDate.ofEpochDay(group.autumnFirst).format(RANGE_FMT),
|
|
||||||
LocalDate.ofEpochDay(group.autumnSecond).format(RANGE_FMT)
|
|
||||||
)
|
|
||||||
} else null
|
|
||||||
Season.SPRING -> if (group.springFirst != null && group.springSecond != null) {
|
|
||||||
DateRange(
|
|
||||||
LocalDate.ofEpochDay(group.springFirst).format(RANGE_FMT),
|
|
||||||
LocalDate.ofEpochDay(group.springSecond).format(RANGE_FMT)
|
|
||||||
)
|
|
||||||
} else null
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private fun LocalTime.toMinutesOfDay(): Int = hour * 60 + minute
|
private fun LocalTime.toMinutesOfDay(): Int = hour * 60 + minute
|
||||||
|
|
||||||
private fun now(): Long = System.currentTimeMillis()
|
private fun now(): Long = System.currentTimeMillis()
|
||||||
|
|
||||||
companion object {
|
companion object {
|
||||||
private const val STALE_WINDOW_MS = 12 * 60 * 60 * 1000L
|
private const val STALE_WINDOW_MS = 12 * 60 * 60 * 1000L
|
||||||
private const val MAX_PERIODS = 3
|
private const val MAX_PERIODS = 24
|
||||||
private const val TAG = "ScheduleRepo"
|
private const val TAG = "ScheduleRepo"
|
||||||
private val RANGE_FMT = java.time.format.DateTimeFormatter.ofPattern("ddMMyyyy")
|
private val RANGE_FMT = java.time.format.DateTimeFormatter.ofPattern("ddMMyyyy")
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user