/* Copyright 2021 Tusky Contributors * * This file is a part of Tusky. * * This program is free software; you can redistribute it and/or modify it under the terms of the * GNU General Public License as published by the Free Software Foundation; either version 3 of the * License, or (at your option) any later version. * * Tusky is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even * the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General * Public License for more details. * * You should have received a copy of the GNU General Public License along with Tusky; if not, * see . */ package com.keylesspalace.tusky.db import androidx.room.Embedded import androidx.room.Entity import androidx.room.ForeignKey import androidx.room.Index import androidx.room.TypeConverters import com.keylesspalace.tusky.entity.Status /** * We're trying to play smart here. Server sends us reblogs as two entities one embedded into * another (reblogged status is a field inside of "reblog" status). But it's really inefficient from * the DB perspective and doesn't matter much for the display/interaction purposes. * What if when we store reblog we don't store almost empty "reblog status" but we store * *reblogged* status and we embed "reblog status" into reblogged status. This reversed * relationship takes much less space and is much faster to fetch (no N+1 type queries or JSON * serialization). * "Reblog status", if present, is marked by [reblogServerId], and [reblogAccountId] * fields. */ @Entity( primaryKeys = ["serverId", "timelineUserId"], foreignKeys = ( [ ForeignKey( entity = TimelineAccountEntity::class, parentColumns = ["serverId", "timelineUserId"], childColumns = ["authorServerId", "timelineUserId"] ) ] ), // Avoiding rescanning status table when accounts table changes. Recommended by Room(c). indices = [Index("authorServerId", "timelineUserId")] ) @TypeConverters(Converters::class) data class TimelineStatusEntity( val serverId: String, // id never flips: we need it for sorting so it's a real id val url: String?, // our local id for the logged in user in case there are multiple accounts per instance val timelineUserId: Long, val authorServerId: String?, val inReplyToId: String?, val inReplyToAccountId: String?, val content: String?, val createdAt: Long, val emojis: String?, val reblogsCount: Int, val favouritesCount: Int, val repliesCount: Int, val reblogged: Boolean, val bookmarked: Boolean, val favourited: Boolean, val sensitive: Boolean, val spoilerText: String, val visibility: Status.Visibility, val attachments: String?, val mentions: String?, val tags: String?, val application: String?, val reblogServerId: String?, // if it has a reblogged status, it's id is stored here val reblogAccountId: String?, val poll: String?, val muted: Boolean?, val expanded: Boolean, // used as the "loading" attribute when this TimelineStatusEntity is a placeholder val contentCollapsed: Boolean, val contentShowing: Boolean, val pinned: Boolean, val card: String?, val quote: String?, ) @Entity( primaryKeys = ["serverId", "timelineUserId"] ) data class TimelineAccountEntity( val serverId: String, val timelineUserId: Long, val localUsername: String, val username: String, val displayName: String, val url: String, val avatar: String, val emojis: String, val bot: Boolean ) class TimelineStatusWithAccount { @Embedded lateinit var status: TimelineStatusEntity @Embedded(prefix = "a_") lateinit var account: TimelineAccountEntity @Embedded(prefix = "rb_") var reblogAccount: TimelineAccountEntity? = null }