New cache system

Refactoring
Separation into libraries
This commit is contained in:
2021-03-17 19:47:53 +03:00
parent 2004cb7c5e
commit 84d812a6d6
198 changed files with 4892 additions and 3477 deletions
@@ -1,20 +0,0 @@
package com.meloda.fast.database
import androidx.room.Database
import androidx.room.RoomDatabase
import com.meloda.fast.api.model.*
import com.meloda.fast.database.dao.*
@Database(
entities = [VKConversation::class, VKMessage::class, VKUser::class, VKGroup::class, VKFriend::class],
version = 1,
exportSchema = false
)
abstract class AppDatabase : RoomDatabase() {
abstract val conversations: ConversationsDao
abstract val messages: MessagesDao
abstract val users: UsersDao
abstract val groups: GroupsDao
abstract val friends: FriendsDao
}
@@ -0,0 +1,115 @@
package com.meloda.fast.database
import android.content.ContentValues
import android.database.Cursor
import android.os.Bundle
import com.meloda.fast.common.AppGlobal.Companion.database
import com.meloda.fast.database.DatabaseUtils.TABLE_CHATS
import com.meloda.fast.database.DatabaseUtils.TABLE_FRIENDS
import com.meloda.fast.database.DatabaseUtils.TABLE_MESSAGES
import com.meloda.fast.database.DatabaseUtils.TABLE_USERS
import com.meloda.fast.database.storage.ChatsStorage
import com.meloda.fast.database.storage.GroupsStorage
import com.meloda.fast.database.storage.MessagesStorage
import com.meloda.fast.database.storage.UsersStorage
import com.meloda.vksdk.model.VKConversation
import com.meloda.vksdk.model.VKMessage
import com.meloda.vksdk.model.VKUser
import java.util.*
object CacheStorage {
val usersStorage = UsersStorage()
val messagesStorage = MessagesStorage()
val chatsStorage = ChatsStorage()
val groupsStorage = GroupsStorage()
fun selectCursor(tableName: String): Cursor {
return QueryBuilder.query()
.select("*").from(tableName)
.asCursor(database)
}
fun selectCursor(tableName: String, where: String): Cursor {
return QueryBuilder.query()
.select("*").from(tableName)
.where(where)
.asCursor(database)
}
fun selectCursor(tableName: String, columnName: String, value: Any): Cursor {
return QueryBuilder.query()
.select("*").from(tableName)
.where("$columnName=$value")
.asCursor(database)
}
fun selectCursor(tableName: String, columnName: String, ids: IntArray): Cursor {
val where = StringBuilder(5 * ids.size)
where.append("$columnName=${ids[0]}")
for (i in 1 until ids.size) {
where.append(" OR ")
where.append("$columnName=${ids[i]}")
}
return selectCursor(tableName, where.toString())
}
fun getInt(cursor: Cursor, columnName: String) =
cursor.getInt(cursor.getColumnIndexOrThrow(columnName))
fun getString(cursor: Cursor, columnName: String) =
cursor.getString(cursor.getColumnIndexOrThrow(columnName))
fun getBlob(cursor: Cursor, columnName: String) =
cursor.getBlob(cursor.getColumnIndexOrThrow(columnName))
fun <T> insert(tableName: String, values: ArrayList<T>) {
database.beginTransaction()
val contentValues = ContentValues()
for (value in values) {
when (tableName) {
TABLE_USERS -> {
usersStorage.cacheValue(contentValues, value as VKUser)
break
}
TABLE_FRIENDS -> {
usersStorage.cacheValue(
contentValues,
value as VKUser,
Bundle().apply { putBoolean("toFriends", true) })
break
}
TABLE_MESSAGES -> {
messagesStorage.cacheValue(contentValues, value as VKMessage)
break
}
TABLE_CHATS -> {
chatsStorage.cacheValue(contentValues, value as VKConversation)
break
}
}
database.insert(tableName, null, contentValues)
contentValues.clear()
}
database.setTransactionSuccessful()
database.endTransaction()
}
fun delete(tableName: String, whereClause: String, vararg whereArgs: String) {
database.delete(tableName, whereClause, whereArgs)
}
fun delete(tableName: String) {
database.delete(tableName, null, null)
}
}
@@ -0,0 +1,29 @@
package com.meloda.fast.database
import android.content.Context
import android.database.sqlite.SQLiteDatabase
import android.database.sqlite.SQLiteOpenHelper
class DatabaseHelper constructor(context: Context) : SQLiteOpenHelper(
context,
DB_NAME,
null,
DB_VERSION
) {
companion object {
private const val DB_NAME = "cache.db"
private const val DB_VERSION = 1
}
override fun onCreate(db: SQLiteDatabase) {
db.execSQL(DatabaseUtils.createUsersTable())
db.execSQL(DatabaseUtils.createGroupsTable())
db.execSQL(DatabaseUtils.createFriendsTable())
db.execSQL(DatabaseUtils.createMessagesTable())
db.execSQL(DatabaseUtils.createChatsTable())
}
override fun onUpgrade(db: SQLiteDatabase?, oldVersion: Int, newVersion: Int) {
}
}
@@ -0,0 +1,95 @@
package com.meloda.fast.database
object DatabaseKeys {
const val ID = "_id"
const val SORT_ID = "_sort_id"
const val USER_ID = "_user_id"
const val FIRST_NAME = "_first_name"
const val LAST_NAME = "_last_name"
const val DEACTIVATED = "_deactivated"
const val GENDER = "_gender"
const val SCREEN_NAME = "_screen_name"
const val PHOTOS = "_photos"
const val IS_ONLINE = "_is_online"
const val IS_ONLINE_MOBILE = "_is_online_mobile"
const val STATUS = "_status"
const val LAST_SEEN = "_last_seen"
const val MESSAGE_ID = "_message_id"
const val DATE = "_date"
const val PEER_ID = "_peer_id"
const val FROM_ID = "_from_id"
const val EDIT_TIME = "_edit_time"
const val IS_OUT = "_is_out"
const val TEXT = "_text"
const val RANDOM_ID = "_random_id"
const val CONVERSATION_MESSAGE_ID = "_conversation_message_id"
const val ATTACHMENTS = "_attachments"
const val FWD_MESSAGES = "_fwd_messages"
const val REPLY_MESSAGE_ID = "_reply_message_id"
const val ACTION = "_action"
const val IS_ALLOWED = "_is_allowed"
const val NOT_ALLOWED_REASON = "_not_allowed_reason"
const val IN_READ_MESSAGE_ID = "_in_read_message_id"
const val OUT_READ_MESSAGE_ID = "_out_read_message_id"
const val LAST_MESSAGE_ID = "_last_message_id"
const val UNREAD_COUNT = "_unread_count"
const val CONVERSATION_ID = "_conversation_id"
const val TYPE = "_type"
const val LOCAL_ID = "_local_id"
const val IS_NOTIFICATIONS_DISABLED = "_is_notifications_disabled"
const val MEMBERS_COUNT = "_members_count"
const val TITLE = "_title"
const val PINNED_MESSAGE_ID = "_pinned_message_id"
const val CHAT_STATE = "_chat_state"
const val IS_GROUP_CHANNEL = "_is_group_channel"
const val FRIEND_ID = "_friend_id"
const val GROUP_ID = "_group_id"
const val NAME = "_name"
const val IS_CLOSED = "_is_closed"
}
@@ -0,0 +1,153 @@
package com.meloda.fast.database
import com.meloda.fast.database.DatabaseKeys.ACTION
import com.meloda.fast.database.DatabaseKeys.ATTACHMENTS
import com.meloda.fast.database.DatabaseKeys.CHAT_STATE
import com.meloda.fast.database.DatabaseKeys.CONVERSATION_ID
import com.meloda.fast.database.DatabaseKeys.CONVERSATION_MESSAGE_ID
import com.meloda.fast.database.DatabaseKeys.DATE
import com.meloda.fast.database.DatabaseKeys.DEACTIVATED
import com.meloda.fast.database.DatabaseKeys.EDIT_TIME
import com.meloda.fast.database.DatabaseKeys.FIRST_NAME
import com.meloda.fast.database.DatabaseKeys.FRIEND_ID
import com.meloda.fast.database.DatabaseKeys.FROM_ID
import com.meloda.fast.database.DatabaseKeys.FWD_MESSAGES
import com.meloda.fast.database.DatabaseKeys.GENDER
import com.meloda.fast.database.DatabaseKeys.GROUP_ID
import com.meloda.fast.database.DatabaseKeys.IN_READ_MESSAGE_ID
import com.meloda.fast.database.DatabaseKeys.IS_ALLOWED
import com.meloda.fast.database.DatabaseKeys.IS_CLOSED
import com.meloda.fast.database.DatabaseKeys.IS_GROUP_CHANNEL
import com.meloda.fast.database.DatabaseKeys.IS_NOTIFICATIONS_DISABLED
import com.meloda.fast.database.DatabaseKeys.IS_ONLINE
import com.meloda.fast.database.DatabaseKeys.IS_ONLINE_MOBILE
import com.meloda.fast.database.DatabaseKeys.IS_OUT
import com.meloda.fast.database.DatabaseKeys.LAST_MESSAGE_ID
import com.meloda.fast.database.DatabaseKeys.LAST_NAME
import com.meloda.fast.database.DatabaseKeys.LAST_SEEN
import com.meloda.fast.database.DatabaseKeys.LOCAL_ID
import com.meloda.fast.database.DatabaseKeys.MEMBERS_COUNT
import com.meloda.fast.database.DatabaseKeys.MESSAGE_ID
import com.meloda.fast.database.DatabaseKeys.NAME
import com.meloda.fast.database.DatabaseKeys.NOT_ALLOWED_REASON
import com.meloda.fast.database.DatabaseKeys.OUT_READ_MESSAGE_ID
import com.meloda.fast.database.DatabaseKeys.PEER_ID
import com.meloda.fast.database.DatabaseKeys.PHOTOS
import com.meloda.fast.database.DatabaseKeys.PINNED_MESSAGE_ID
import com.meloda.fast.database.DatabaseKeys.RANDOM_ID
import com.meloda.fast.database.DatabaseKeys.REPLY_MESSAGE_ID
import com.meloda.fast.database.DatabaseKeys.SCREEN_NAME
import com.meloda.fast.database.DatabaseKeys.SORT_ID
import com.meloda.fast.database.DatabaseKeys.STATUS
import com.meloda.fast.database.DatabaseKeys.TEXT
import com.meloda.fast.database.DatabaseKeys.TITLE
import com.meloda.fast.database.DatabaseKeys.TYPE
import com.meloda.fast.database.DatabaseKeys.UNREAD_COUNT
import com.meloda.fast.database.DatabaseKeys.USER_ID
object DatabaseUtils {
const val TABLE_USERS = "users"
const val TABLE_MESSAGES = "messages"
const val TABLE_CHATS = "chats"
const val TABLE_FRIENDS = "friends"
const val TABLE_GROUPS = "groups"
private val usersTableMap = HashMap<String, String>().apply {
this[USER_ID] = "integer primary key on conflict replace"
this[FIRST_NAME] = "varchar(255)"
this[LAST_NAME] = "varchar(255)"
this[DEACTIVATED] = "varchar(255)"
this[GENDER] = "integer default 0"
this[SCREEN_NAME] = "varchar(255)"
this[PHOTOS] = "text"
this[IS_ONLINE] = "integer default 0"
this[IS_ONLINE_MOBILE] = "integer default 0"
this[STATUS] = "varchar(255)"
this[LAST_SEEN] = "integer"
}
private val groupsTableMap = HashMap<String, String>().apply {
this[GROUP_ID] = "integer primary key on conflict replace"
this[NAME] = "varchar(255)"
this[SCREEN_NAME] = "varchar(255)"
this[IS_CLOSED] = "integer default 0"
this[DEACTIVATED] = "varchar(255)"
this[TYPE] = "varchar(255)"
this[PHOTOS] = "text"
}
private val messagesTableMap = HashMap<String, String>().apply {
this[MESSAGE_ID] = "integer primary key on conflict replace"
this[DATE] = "integer"
this[PEER_ID] = "integer"
this[FROM_ID] = "integer"
this[EDIT_TIME] = "integer"
this[IS_OUT] = "integer default 0"
this[TEXT] = "text"
this[RANDOM_ID] = "integer"
this[CONVERSATION_MESSAGE_ID] = "integer"
this[ATTACHMENTS] = "blob"
this[REPLY_MESSAGE_ID] = "integer"
this[ACTION] = "blob"
//2,3,4,5 - message_ids
this[FWD_MESSAGES] = "text"
}
private val chatsTableMap = HashMap<String, String>().apply {
this[CONVERSATION_ID] = "integer primary key on conflict replace"
this[IS_ALLOWED] = "integer default 1"
this[NOT_ALLOWED_REASON] = "integer"
this[IN_READ_MESSAGE_ID] = "integer"
this[OUT_READ_MESSAGE_ID] = "integer"
this[LAST_MESSAGE_ID] = "integer"
this[UNREAD_COUNT] = "integer"
this[LOCAL_ID] = "integer"
this[IS_NOTIFICATIONS_DISABLED] = "integer default 0"
this[MEMBERS_COUNT] = "integer"
this[TITLE] = "varchar(255)"
this[IS_GROUP_CHANNEL] = "integer default 0"
this[TYPE] = "integer"
this[CHAT_STATE] = "integer"
this[PHOTOS] = "text"
this[PINNED_MESSAGE_ID] = "integer"
}
private val friendsTableMap = HashMap<String, String>().apply {
this[FRIEND_ID] = "integer primary key on conflict replace"
this[SORT_ID] = "integer"
//id which user friend
this[USER_ID] = "integer"
}
fun createUsersTable() = createTableQuery(TABLE_USERS, usersTableMap)
fun createGroupsTable() = createTableQuery(TABLE_GROUPS, groupsTableMap)
fun createMessagesTable() = createTableQuery(TABLE_MESSAGES, messagesTableMap)
fun createChatsTable() = createTableQuery(TABLE_CHATS, chatsTableMap)
fun createFriendsTable() = createTableQuery(TABLE_FRIENDS, friendsTableMap)
private fun createTableQuery(tableName: String, tableData: HashMap<String, String>): String {
val builder = StringBuilder("create table $tableName (")
val entry: Map.Entry<String, String> = tableData.entries.first()
builder.append(entry.key)
builder.append(" ")
builder.append(entry.value)
tableData.forEach {
if (it == entry) return@forEach
builder.append(", ")
builder.append(it.key)
builder.append(" ")
builder.append(it.value)
}
builder.append(");")
return builder.toString();
}
}
@@ -1,176 +0,0 @@
package com.meloda.fast.database
import android.util.SparseArray
import androidx.annotation.WorkerThread
import com.meloda.fast.api.model.*
import com.meloda.fast.common.AppGlobal
object MemoryCache {
private val users = SparseArray<VKUser>()
private val groups = SparseArray<VKGroup>()
@WorkerThread
fun getUserById(id: Int): VKUser? {
var user = users[id]
if (user == null) {
user = AppGlobal.database.users.getById(id)
user?.let { append(it) }
}
return user
}
@WorkerThread
fun getGroupById(positiveId: Int): VKGroup? {
var group = groups[positiveId]
if (group == null) {
group = AppGlobal.database.groups.getById(positiveId)
group?.let { append(it) }
}
return group
}
@WorkerThread
fun getMessageById(id: Int): VKMessage? {
return AppGlobal.database.messages.getById(id)
}
@WorkerThread
fun getMessagesByPeerId(peerId: Int): List<VKMessage> {
return AppGlobal.database.messages.getByPeerId(peerId)
}
@WorkerThread
fun getMessages(): List<VKMessage> {
return AppGlobal.database.messages.getAll()
}
@WorkerThread
fun getConversationById(id: Int): VKConversation? {
return AppGlobal.database.conversations.getById(id)
}
@WorkerThread
fun getConversations(): List<VKConversation> {
return AppGlobal.database.conversations.getAll()
}
@WorkerThread
fun getFriends(userId: Int): List<VKFriend> {
return AppGlobal.database.friends.getByUserId(userId)
}
fun appendUsers(users: Collection<VKUser>) {
for (user in users) {
append(user)
}
}
fun appendGroups(groups: Collection<VKGroup>) {
for (group in groups) {
append(group)
}
}
fun append(value: VKGroup) {
groups.append(value.groupId, value)
}
fun append(value: VKUser) {
users.append(value.userId, value)
}
@WorkerThread
fun put(value: VKUser) {
append(value)
AppGlobal.database.users.insert(value)
}
@WorkerThread
fun putUsers(users: List<VKUser>) {
appendUsers(users)
AppGlobal.database.users.insert(users)
}
@WorkerThread
fun put(value: VKFriend) {
AppGlobal.database.friends.insert(value)
}
@WorkerThread
fun putFriends(friends: List<VKFriend>) {
AppGlobal.database.friends.insert(friends)
}
@WorkerThread
fun put(value: VKMessage) {
AppGlobal.database.messages.insert(value)
}
@WorkerThread
fun putMessages(messages: List<VKMessage>) {
AppGlobal.database.messages.insert(messages)
}
@WorkerThread
fun put(value: VKGroup) {
append(value)
AppGlobal.database.groups.insert(value)
}
@WorkerThread
fun putGroups(groups: List<VKGroup>) {
appendGroups(groups)
AppGlobal.database.groups.insert(groups)
}
@WorkerThread
fun put(value: VKConversation) {
AppGlobal.database.conversations.insert(value)
}
@WorkerThread
fun putConversations(conversations: List<VKConversation>) {
AppGlobal.database.conversations.insert(conversations)
}
@WorkerThread
fun deleteMessage(messageId: Int, safe: Boolean = true) {
if (safe) {
AppGlobal.database.messages.getById(messageId) ?: return
}
AppGlobal.database.messages.deleteById(messageId)
}
@WorkerThread
fun deleteConversation(conversationId: Int, safe: Boolean = true) {
if (safe) {
AppGlobal.database.conversations.getById(conversationId) ?: return
}
AppGlobal.database.conversations.deleteById(conversationId)
}
@WorkerThread
fun edit(message: VKMessage?, safe: Boolean = true) {
message ?: return
if (safe) {
AppGlobal.database.messages.getById(message.messageId) ?: return
}
AppGlobal.database.messages.update(message)
}
fun clear() {
users.clear()
groups.clear()
}
}
@@ -0,0 +1,71 @@
package com.meloda.fast.database
import android.database.Cursor
import android.database.sqlite.SQLiteDatabase
class QueryBuilder private constructor() {
companion object {
fun query(): QueryBuilder {
return QueryBuilder()
}
}
private val builder: StringBuilder = StringBuilder()
fun select(column: String): QueryBuilder {
builder.append("SELECT ")
.append(column)
.append(" ")
return this
}
fun from(table: String): QueryBuilder {
builder.append("FROM ")
.append(table)
.append(" ")
return this
}
fun where(clause: String): QueryBuilder {
builder.append("WHERE ")
.append(clause)
.append(" ")
return this
}
fun leftJoin(table: String): QueryBuilder {
builder.append("LEFT JOIN ")
.append(table)
.append(" ")
return this
}
fun on(where: String): QueryBuilder {
builder.append("ON ")
.append(where)
.append(" ")
return this
}
fun and(): QueryBuilder {
builder.append("AND ")
return this
}
fun or(): QueryBuilder {
builder.append("OR ")
return this
}
fun asCursor(db: SQLiteDatabase): Cursor {
return db.rawQuery(toString(), null)
}
override fun toString(): String {
return builder.toString().trim()
}
}
@@ -0,0 +1,32 @@
package com.meloda.fast.database.base
import android.content.ContentValues
import android.database.Cursor
import android.os.Bundle
import androidx.annotation.WorkerThread
import com.meloda.fast.common.AppGlobal
abstract class Storage<T> {
abstract val tag: String
protected var database = AppGlobal.database
@WorkerThread
abstract fun getAllValues(): ArrayList<T>
@WorkerThread
abstract fun insertValues(values: ArrayList<T>, params: Bundle? = null)
@WorkerThread
fun insertValue(value: T, params: Bundle? = null) {
insertValues(arrayListOf(value), params)
}
@WorkerThread
abstract fun cacheValue(values: ContentValues, value: T, params: Bundle? = null)
@WorkerThread
abstract fun parseValue(cursor: Cursor): T
}
@@ -1,38 +0,0 @@
package com.meloda.fast.database.dao
import androidx.room.*
import com.meloda.fast.api.model.VKConversation
@Dao
interface ConversationsDao {
@Query("SELECT * FROM conversations")
fun getAll(): List<VKConversation>
@Query("SELECT * FROM conversations WHERE conversationId = :id")
fun getById(id: Int): VKConversation?
@Insert(onConflict = OnConflictStrategy.REPLACE)
fun insert(item: VKConversation)
@Insert(onConflict = OnConflictStrategy.REPLACE)
fun insert(items: List<VKConversation>)
@Update
fun update(item: VKConversation)
@Update
fun update(items: List<VKConversation>)
@Delete
fun delete(item: VKConversation)
@Delete
fun delete(items: List<VKConversation>)
@Query("DELETE FROM conversations WHERE conversationId = :id")
fun deleteById(id: Int)
@Query("DELETE FROM conversations")
fun clear()
}
@@ -1,41 +0,0 @@
package com.meloda.fast.database.dao
import androidx.room.*
import com.meloda.fast.api.model.VKFriend
@Dao
interface FriendsDao {
@Query("SELECT * FROM friends")
fun getAll(): List<VKFriend>
@Query("SELECT * FROM friends WHERE userId = :id")
fun getById(id: Int): VKFriend?
@Insert(onConflict = OnConflictStrategy.REPLACE)
fun insert(item: VKFriend)
@Insert(onConflict = OnConflictStrategy.REPLACE)
fun insert(items: List<VKFriend>)
@Update
fun update(item: VKFriend)
@Update
fun update(items: List<VKFriend>)
@Delete
fun delete(item: VKFriend)
@Delete
fun delete(items: List<VKFriend>)
@Query("DELETE FROM friends WHERE userId = :id")
fun deleteById(id: Int)
@Query("DELETE FROM friends")
fun clear()
@Query("SELECT * FROM friends WHERE userId = :id")
fun getByUserId(id: Int): List<VKFriend>
}
@@ -1,37 +0,0 @@
package com.meloda.fast.database.dao
import androidx.room.*
import com.meloda.fast.api.model.VKGroup
@Dao
interface GroupsDao {
@Query("SELECT * FROM groups")
fun getAll(): List<VKGroup>
@Query("SELECT * FROM groups WHERE groupId = :id")
fun getById(id: Int): VKGroup?
@Insert(onConflict = OnConflictStrategy.REPLACE)
fun insert(item: VKGroup)
@Insert(onConflict = OnConflictStrategy.REPLACE)
fun insert(items: List<VKGroup>)
@Update
fun update(item: VKGroup)
@Update
fun update(items: List<VKGroup>)
@Delete
fun delete(item: VKGroup)
@Delete
fun delete(items: List<VKGroup>)
@Query("DELETE FROM groups WHERE groupId = :id")
fun deleteById(id: Int)
@Query("DELETE FROM groups")
fun clear()
}
@@ -1,41 +0,0 @@
package com.meloda.fast.database.dao
import androidx.room.*
import com.meloda.fast.api.model.VKMessage
@Dao
interface MessagesDao {
@Query("SELECT * FROM messages")
fun getAll(): List<VKMessage>
@Query("SELECT * FROM messages WHERE messageId = :id")
fun getById(id: Int): VKMessage?
@Insert(onConflict = OnConflictStrategy.REPLACE)
fun insert(item: VKMessage)
@Insert(onConflict = OnConflictStrategy.REPLACE)
fun insert(items: List<VKMessage>)
@Update
fun update(item: VKMessage)
@Update
fun update(items: List<VKMessage>)
@Delete
fun delete(item: VKMessage)
@Delete
fun delete(items: List<VKMessage>)
@Query("DELETE FROM messages WHERE messageId = :id")
fun deleteById(id: Int)
@Query("DELETE FROM messages")
fun clear()
@Query("SELECT * FROM messages WHERE peerId = :peerId")
fun getByPeerId(peerId: Int): List<VKMessage>
}
@@ -1,38 +0,0 @@
package com.meloda.fast.database.dao
import androidx.room.*
import com.meloda.fast.api.model.VKUser
@Dao
interface UsersDao {
@Query("SELECT * FROM users")
fun getAll(): List<VKUser>
@Query("SELECT * FROM users WHERE userId = :id")
fun getById(id: Int): VKUser?
@Insert(onConflict = OnConflictStrategy.REPLACE)
fun insert(item: VKUser)
@Insert(onConflict = OnConflictStrategy.REPLACE)
fun insert(items: List<VKUser>)
@Update
fun update(item: VKUser)
@Update
fun update(items: List<VKUser>)
@Delete
fun delete(item: VKUser)
@Delete
fun delete(items: List<VKUser>)
@Query("DELETE FROM users WHERE userId = :id")
fun deleteById(id: Int)
@Query("DELETE FROM users")
fun clear()
}
@@ -1,24 +0,0 @@
package com.meloda.fast.database.dao.converters
import androidx.room.TypeConverter
import com.meloda.fast.api.model.VKModel
import com.meloda.fast.extensions.ArrayExtensions.isNullOrEmpty
import com.meloda.fast.util.Utils
@Suppress("UNCHECKED_CAST")
class ArrayListToByteArrayConverter {
@TypeConverter
fun toForwarded(data: ByteArray?): ArrayList<VKModel> {
return if (data.isNullOrEmpty()) arrayListOf() else {
val deserializedData = Utils.deserialize(data)
if (deserializedData == null) arrayListOf() else deserializedData as ArrayList<VKModel>
}
}
@TypeConverter
fun fromForwarded(forwarded: List<VKModel>): ByteArray {
return Utils.serialize(forwarded) ?: return byteArrayOf()
}
}
@@ -1,25 +0,0 @@
package com.meloda.fast.database.dao.converters
import androidx.room.TypeConverter
import com.meloda.fast.api.model.VKMessage
import com.meloda.fast.extensions.ArrayExtensions.isNullOrEmpty
import com.meloda.fast.util.Utils
import java.util.*
@Suppress("UNCHECKED_CAST")
class ForwardedConverter {
@TypeConverter
fun toForwarded(data: ByteArray?): ArrayList<VKMessage> {
return if (data.isNullOrEmpty()) arrayListOf() else {
val deserializedData = Utils.deserialize(data)
if (deserializedData == null) arrayListOf() else deserializedData as ArrayList<VKMessage>
}
}
@TypeConverter
fun fromForwarded(forwarded: List<VKMessage>): ByteArray {
return Utils.serialize(forwarded) ?: return byteArrayOf()
}
}
@@ -0,0 +1,141 @@
package com.meloda.fast.database.storage
import android.content.ContentValues
import android.database.Cursor
import android.os.Bundle
import android.util.Log
import androidx.annotation.WorkerThread
import com.meloda.fast.database.CacheStorage
import com.meloda.fast.database.CacheStorage.messagesStorage
import com.meloda.fast.database.DatabaseKeys.CHAT_STATE
import com.meloda.fast.database.DatabaseKeys.CONVERSATION_ID
import com.meloda.fast.database.DatabaseKeys.IN_READ_MESSAGE_ID
import com.meloda.fast.database.DatabaseKeys.IS_ALLOWED
import com.meloda.fast.database.DatabaseKeys.IS_GROUP_CHANNEL
import com.meloda.fast.database.DatabaseKeys.IS_NOTIFICATIONS_DISABLED
import com.meloda.fast.database.DatabaseKeys.LAST_MESSAGE_ID
import com.meloda.fast.database.DatabaseKeys.LOCAL_ID
import com.meloda.fast.database.DatabaseKeys.MEMBERS_COUNT
import com.meloda.fast.database.DatabaseKeys.NOT_ALLOWED_REASON
import com.meloda.fast.database.DatabaseKeys.OUT_READ_MESSAGE_ID
import com.meloda.fast.database.DatabaseKeys.PHOTOS
import com.meloda.fast.database.DatabaseKeys.PINNED_MESSAGE_ID
import com.meloda.fast.database.DatabaseKeys.TITLE
import com.meloda.fast.database.DatabaseKeys.TYPE
import com.meloda.fast.database.DatabaseKeys.UNREAD_COUNT
import com.meloda.fast.database.DatabaseUtils.TABLE_CHATS
import com.meloda.fast.database.base.Storage
import com.meloda.vksdk.model.VKConversation
import com.meloda.vksdk.util.VKUtil
import org.json.JSONObject
@WorkerThread
class ChatsStorage : Storage<VKConversation>() {
override val tag = "ChatsStorage"
override fun getAllValues(): ArrayList<VKConversation> {
val cursor = CacheStorage.selectCursor(TABLE_CHATS)
val conversations = ArrayList<VKConversation>()
while (cursor.moveToNext()) conversations.add(parseValue(cursor))
cursor.close()
return conversations
}
@WorkerThread
override fun insertValues(values: ArrayList<VKConversation>, params: Bundle?) {
if (values.isEmpty()) return
database.beginTransaction()
val contentValues = ContentValues()
for (value in values) {
cacheValue(contentValues, value, params)
database.insert(TABLE_CHATS, null, contentValues)
contentValues.clear()
}
database.setTransactionSuccessful()
database.endTransaction()
Log.d(tag, "Successful cached chats")
}
@WorkerThread
override fun cacheValue(values: ContentValues, value: VKConversation, params: Bundle?) {
values.put(CONVERSATION_ID, value.id)
values.put(IS_ALLOWED, value.isAllowed)
values.put(NOT_ALLOWED_REASON, value.notAllowedReason.value)
values.put(IN_READ_MESSAGE_ID, value.inReadMessageId)
values.put(OUT_READ_MESSAGE_ID, value.outReadMessageId)
values.put(LAST_MESSAGE_ID, value.lastMessageId)
values.put(UNREAD_COUNT, value.unreadCount)
values.put(LOCAL_ID, value.localId)
values.put(IS_NOTIFICATIONS_DISABLED, value.notificationsEnabled)
values.put(MEMBERS_COUNT, value.membersCount)
values.put(TITLE, value.title)
values.put(IS_GROUP_CHANNEL, value.isGroupChannel)
values.put(TYPE, value.intType)
values.put(CHAT_STATE, value.intState)
values.put(
PHOTOS,
VKUtil.putPhotosToJson(
value.photo50,
value.photo100,
value.photo200
).toString()
)
value.pinnedMessage?.let {
values.put(PINNED_MESSAGE_ID, it.id)
}
}
@WorkerThread
override fun parseValue(cursor: Cursor): VKConversation {
val conversation = VKConversation()
conversation.id = CacheStorage.getInt(cursor, CONVERSATION_ID)
conversation.isAllowed = CacheStorage.getInt(cursor, IS_ALLOWED) == 1
conversation.notAllowedReason = VKConversation.Reason.fromInt(
CacheStorage.getInt(cursor, NOT_ALLOWED_REASON)
)
conversation.inReadMessageId = CacheStorage.getInt(cursor, IN_READ_MESSAGE_ID)
conversation.outReadMessageId = CacheStorage.getInt(cursor, OUT_READ_MESSAGE_ID)
conversation.unreadCount = CacheStorage.getInt(cursor, UNREAD_COUNT)
conversation.localId = CacheStorage.getInt(cursor, LOCAL_ID)
conversation.notificationsEnabled =
CacheStorage.getInt(cursor, IS_NOTIFICATIONS_DISABLED) == 1
conversation.membersCount = CacheStorage.getInt(cursor, MEMBERS_COUNT)
conversation.title = CacheStorage.getString(cursor, TITLE)
conversation.isGroupChannel = CacheStorage.getInt(cursor, IS_GROUP_CHANNEL) == 1
val pinnedMessageId = CacheStorage.getInt(cursor, PINNED_MESSAGE_ID)
if (pinnedMessageId != -1) {
val pinnedMessage = messagesStorage.getMessageById(pinnedMessageId)
if (pinnedMessage != null) conversation.pinnedMessage = pinnedMessage
}
conversation.intType = CacheStorage.getInt(cursor, TYPE)
conversation.intState = CacheStorage.getInt(cursor, CHAT_STATE)
conversation.lastMessageId = CacheStorage.getInt(cursor, LAST_MESSAGE_ID)
val lastMessage = messagesStorage.getMessageById(conversation.lastMessageId)
if (lastMessage != null) conversation.lastMessage = lastMessage
val photos = VKUtil.parseJsonPhotos(JSONObject(CacheStorage.getString(cursor, PHOTOS)))
conversation.photo50 = photos[0]
conversation.photo100 = photos[1]
conversation.photo200 = photos[2]
return conversation
}
}
@@ -0,0 +1,111 @@
package com.meloda.fast.database.storage
import android.content.ContentValues
import android.database.Cursor
import android.os.Bundle
import android.util.Log
import androidx.annotation.WorkerThread
import com.meloda.fast.database.CacheStorage
import com.meloda.fast.database.CacheStorage.getInt
import com.meloda.fast.database.CacheStorage.getString
import com.meloda.fast.database.DatabaseKeys.DEACTIVATED
import com.meloda.fast.database.DatabaseKeys.GROUP_ID
import com.meloda.fast.database.DatabaseKeys.IS_CLOSED
import com.meloda.fast.database.DatabaseKeys.NAME
import com.meloda.fast.database.DatabaseKeys.PHOTOS
import com.meloda.fast.database.DatabaseKeys.SCREEN_NAME
import com.meloda.fast.database.DatabaseKeys.TYPE
import com.meloda.fast.database.DatabaseUtils.TABLE_GROUPS
import com.meloda.fast.database.base.Storage
import com.meloda.vksdk.model.VKGroup
import com.meloda.vksdk.util.VKUtil
import org.json.JSONObject
class GroupsStorage : Storage<VKGroup>() {
override val tag = "GroupsStorage"
@WorkerThread
fun getGroups(ids: IntArray): ArrayList<VKGroup> {
val cursor = CacheStorage.selectCursor(TABLE_GROUPS, GROUP_ID, ids)
val groups = ArrayList<VKGroup>(cursor.count)
while (cursor.moveToNext()) groups.add(parseValue(cursor))
cursor.close()
return groups
}
@WorkerThread
fun getGroup(userId: Int): VKGroup? {
val group = getGroups(intArrayOf(userId))
return if (group.isNotEmpty()) group[0] else null
}
override fun getAllValues(): ArrayList<VKGroup> {
val cursor = CacheStorage.selectCursor(TABLE_GROUPS)
val groups = ArrayList<VKGroup>()
while (cursor.moveToNext()) groups.add(parseValue(cursor))
cursor.close()
return groups
}
override fun insertValues(values: ArrayList<VKGroup>, params: Bundle?) {
if (values.isEmpty()) return
database.beginTransaction()
val contentValues = ContentValues()
for (value in values) {
cacheValue(contentValues, value, params)
database.insert(TABLE_GROUPS, null, contentValues)
contentValues.clear()
}
database.setTransactionSuccessful()
database.endTransaction()
Log.d(tag, "Successful cached groups")
}
override fun cacheValue(values: ContentValues, value: VKGroup, params: Bundle?) {
values.put(GROUP_ID, value.id)
values.put(NAME, value.name)
values.put(SCREEN_NAME, value.screenName)
values.put(IS_CLOSED, value.isClosed)
values.put(DEACTIVATED, value.deactivated)
values.put(TYPE, value.type.value)
val photos =
VKUtil.putPhotosToJson(value.photo50, value.photo100, value.photo200).toString()
values.put(PHOTOS, photos)
}
override fun parseValue(cursor: Cursor): VKGroup {
val group = VKGroup()
group.id = getInt(cursor, GROUP_ID)
group.name = getString(cursor, NAME)
group.screenName = getString(cursor, SCREEN_NAME)
group.isClosed = getInt(cursor, IS_CLOSED) == 1
group.deactivated = getString(cursor, DEACTIVATED)
group.type = VKGroup.Type.fromString(getString(cursor, TYPE))
val photos = VKUtil.parseJsonPhotos(JSONObject(getString(cursor, PHOTOS)))
group.photo50 = photos[0]
group.photo100 = photos[1]
group.photo200 = photos[2]
return group
}
}
@@ -0,0 +1,178 @@
package com.meloda.fast.database.storage
import android.content.ContentValues
import android.database.Cursor
import android.os.Bundle
import android.util.Log
import androidx.annotation.WorkerThread
import com.meloda.fast.database.CacheStorage
import com.meloda.fast.database.CacheStorage.selectCursor
import com.meloda.fast.database.DatabaseKeys.ACTION
import com.meloda.fast.database.DatabaseKeys.ATTACHMENTS
import com.meloda.fast.database.DatabaseKeys.CONVERSATION_MESSAGE_ID
import com.meloda.fast.database.DatabaseKeys.DATE
import com.meloda.fast.database.DatabaseKeys.EDIT_TIME
import com.meloda.fast.database.DatabaseKeys.FROM_ID
import com.meloda.fast.database.DatabaseKeys.FWD_MESSAGES
import com.meloda.fast.database.DatabaseKeys.IS_OUT
import com.meloda.fast.database.DatabaseKeys.MESSAGE_ID
import com.meloda.fast.database.DatabaseKeys.PEER_ID
import com.meloda.fast.database.DatabaseKeys.RANDOM_ID
import com.meloda.fast.database.DatabaseKeys.REPLY_MESSAGE_ID
import com.meloda.fast.database.DatabaseKeys.TEXT
import com.meloda.fast.database.DatabaseUtils.TABLE_MESSAGES
import com.meloda.fast.database.base.Storage
import com.meloda.fast.util.Utils
import com.meloda.vksdk.model.VKMessage
import com.meloda.vksdk.model.VKMessageAction
import com.meloda.vksdk.model.VKModel
import java.util.stream.Collectors
@WorkerThread
@Suppress("UNCHECKED_CAST")
class MessagesStorage : Storage<VKMessage>() {
override val tag = "MessagesStorage"
@WorkerThread
fun getMessagesHistory(peerId: Int): ArrayList<VKMessage> {
val cursor = CacheStorage.selectCursor(TABLE_MESSAGES, PEER_ID, peerId)
val messages = ArrayList<VKMessage>(cursor.count)
while (cursor.moveToNext()) messages.add(parseValue(cursor))
cursor.close()
return messages
}
@WorkerThread
fun getMessageById(messageId: Int): VKMessage? {
val cursor = CacheStorage.selectCursor(TABLE_MESSAGES, MESSAGE_ID, messageId)
if (cursor.moveToFirst()) {
val message = parseValue(cursor)
cursor.close()
return message
}
return null
}
override fun getAllValues(): ArrayList<VKMessage> {
val cursor = selectCursor(TABLE_MESSAGES)
val messages = ArrayList<VKMessage>()
while (cursor.moveToNext()) messages.add(parseValue(cursor))
cursor.close()
return messages
}
@WorkerThread
override fun insertValues(values: ArrayList<VKMessage>, params: Bundle?) {
if (values.isEmpty()) return
database.beginTransaction()
val contentValues = ContentValues()
for (value in values) {
cacheValue(contentValues, value)
database.insert(TABLE_MESSAGES, null, contentValues)
contentValues.clear()
}
database.setTransactionSuccessful()
database.endTransaction()
Log.d(tag, "Successful cached messages")
}
@WorkerThread
override fun cacheValue(values: ContentValues, value: VKMessage, params: Bundle?) {
values.put(MESSAGE_ID, value.id)
values.put(DATE, value.date)
values.put(PEER_ID, value.peerId)
values.put(FROM_ID, value.fromId)
values.put(EDIT_TIME, value.editTime)
values.put(TEXT, value.text)
values.put(RANDOM_ID, value.randomId)
values.put(CONVERSATION_MESSAGE_ID, value.conversationMessageId)
value.replyMessage?.let {
values.put(REPLY_MESSAGE_ID, it.id)
}
value.action?.let {
values.put(ACTION, Utils.serialize(it))
}
value.attachments.let {
if (it.isNotEmpty()) {
values.put(ATTACHMENTS, Utils.serialize(it))
}
}
value.fwdMessages.let {
if (it.isNotEmpty()) {
val ids = arrayListOf<String>()
it.forEach { message -> ids.add(message.id.toString()) }
ids.stream().collect(Collectors.joining(",")).let { str ->
values.put(FWD_MESSAGES, str)
}
}
}
}
@WorkerThread
override fun parseValue(cursor: Cursor): VKMessage {
val message = VKMessage()
message.id = CacheStorage.getInt(cursor, MESSAGE_ID)
message.date = CacheStorage.getInt(cursor, DATE)
message.peerId = CacheStorage.getInt(cursor, PEER_ID)
message.fromId = CacheStorage.getInt(cursor, FROM_ID)
message.editTime = CacheStorage.getInt(cursor, EDIT_TIME)
message.isOut = CacheStorage.getInt(cursor, IS_OUT) == 1
message.text = CacheStorage.getString(cursor, TEXT)
message.randomId = CacheStorage.getInt(cursor, RANDOM_ID)
message.conversationMessageId = CacheStorage.getInt(cursor, CONVERSATION_MESSAGE_ID)
val blobAttachments = Utils.deserialize(CacheStorage.getBlob(cursor, ATTACHMENTS))
if (blobAttachments != null) message.attachments = blobAttachments as ArrayList<VKModel>
else message.attachments = arrayListOf()
val replyMessageId = CacheStorage.getInt(cursor, REPLY_MESSAGE_ID)
val replyMessage = getMessageById(replyMessageId)
if (replyMessage != null) message.replyMessage = replyMessage
val blobAction = Utils.deserialize(CacheStorage.getBlob(cursor, ACTION))
if (blobAction != null) message.action = blobAction as VKMessageAction
val stringFwdMessages = CacheStorage.getString(cursor, FWD_MESSAGES)
if (stringFwdMessages != null) {
val split = stringFwdMessages.split(',')
val ids = arrayListOf<Int>()
for (s in split) ids.add(s.toInt())
val fwdMessages = arrayListOf<VKMessage>()
ids.forEach {
val fwdMessage = getMessageById(it)
if (fwdMessage != null) fwdMessages.add(fwdMessage)
}
message.fwdMessages = fwdMessages
} else message.fwdMessages = arrayListOf()
return message
}
}
@@ -0,0 +1,172 @@
package com.meloda.fast.database.storage
import android.content.ContentValues
import android.database.Cursor
import android.os.Bundle
import android.util.Log
import androidx.annotation.WorkerThread
import com.meloda.fast.UserConfig
import com.meloda.fast.database.CacheStorage
import com.meloda.fast.database.CacheStorage.selectCursor
import com.meloda.fast.database.DatabaseKeys.DEACTIVATED
import com.meloda.fast.database.DatabaseKeys.FIRST_NAME
import com.meloda.fast.database.DatabaseKeys.FRIEND_ID
import com.meloda.fast.database.DatabaseKeys.GENDER
import com.meloda.fast.database.DatabaseKeys.IS_ONLINE
import com.meloda.fast.database.DatabaseKeys.IS_ONLINE_MOBILE
import com.meloda.fast.database.DatabaseKeys.LAST_NAME
import com.meloda.fast.database.DatabaseKeys.LAST_SEEN
import com.meloda.fast.database.DatabaseKeys.PHOTOS
import com.meloda.fast.database.DatabaseKeys.SCREEN_NAME
import com.meloda.fast.database.DatabaseKeys.SORT_ID
import com.meloda.fast.database.DatabaseKeys.STATUS
import com.meloda.fast.database.DatabaseKeys.USER_ID
import com.meloda.fast.database.DatabaseUtils.TABLE_FRIENDS
import com.meloda.fast.database.DatabaseUtils.TABLE_USERS
import com.meloda.fast.database.QueryBuilder
import com.meloda.fast.database.base.Storage
import com.meloda.vksdk.model.VKUser
import com.meloda.vksdk.util.VKUtil
import org.json.JSONObject
@WorkerThread
class UsersStorage : Storage<VKUser>() {
override val tag = "UsersStorage"
@WorkerThread
fun getUsers(ids: IntArray): ArrayList<VKUser> {
val cursor = CacheStorage.selectCursor(TABLE_USERS, USER_ID, ids)
val users = ArrayList<VKUser>(cursor.count)
while (cursor.moveToNext()) users.add(parseValue(cursor))
cursor.close()
return users
}
@WorkerThread
fun getUser(userId: Int): VKUser? {
val user = getUsers(intArrayOf(userId))
return if (user.isNotEmpty()) user[0] else null
}
@WorkerThread
fun getFriends(userId: Int, onlyOnline: Boolean = false): ArrayList<VKUser> {
val cursor = QueryBuilder.query()
.select("*")
.from(TABLE_FRIENDS)
.leftJoin(TABLE_USERS)
.on("friends.${FRIEND_ID} = users.$USER_ID")
.where("friends.${USER_ID} = $userId")
.asCursor(database)
val users = ArrayList<VKUser>(cursor.count)
while (cursor.moveToNext()) {
val userOnline = CacheStorage.getInt(cursor, IS_ONLINE) == 1
if (onlyOnline && !userOnline) continue
val user = parseValue(cursor)
users.add(user)
}
cursor.close()
return users
}
override fun getAllValues(): ArrayList<VKUser> {
val cursor = selectCursor(TABLE_USERS)
val users = ArrayList<VKUser>()
while (cursor.moveToNext()) users.add(parseValue(cursor))
cursor.close()
return users
}
@WorkerThread
override fun insertValues(values: ArrayList<VKUser>, params: Bundle?) {
if (values.isEmpty()) return
val toFriends = params?.getBoolean("toFriends") ?: false
database.beginTransaction()
val contentValues = ContentValues()
for (user in values) {
cacheValue(contentValues, user, params)
database.insert(if (toFriends) TABLE_FRIENDS else TABLE_USERS, null, contentValues)
contentValues.clear()
}
database.setTransactionSuccessful()
database.endTransaction()
Log.d(tag, "Successful cached users. toFriends: $toFriends")
}
@WorkerThread
override fun cacheValue(values: ContentValues, value: VKUser, params: Bundle?) {
val toFriends = params?.getBoolean("toFriends") ?: false
if (toFriends) {
values.put(USER_ID, UserConfig.userId)
values.put(FRIEND_ID, value.userId)
values.put(SORT_ID, value.sortId)
return
}
values.put(USER_ID, value.userId)
values.put(FIRST_NAME, value.firstName)
values.put(LAST_NAME, value.lastName)
values.put(DEACTIVATED, value.deactivated)
values.put(GENDER, value.sex)
values.put(SCREEN_NAME, value.screenName)
values.put(IS_ONLINE, value.isOnline)
values.put(IS_ONLINE_MOBILE, value.isOnlineMobile)
values.put(STATUS, value.status)
values.put(LAST_SEEN, value.lastSeen)
values.put(
PHOTOS,
VKUtil.putPhotosToJson(
value.photo50,
value.photo100,
value.photo200
).toString()
)
}
@WorkerThread
override fun parseValue(cursor: Cursor): VKUser {
val user = VKUser()
user.userId = CacheStorage.getInt(cursor, USER_ID)
user.firstName = CacheStorage.getString(cursor, FIRST_NAME)
user.lastName = CacheStorage.getString(cursor, LAST_NAME)
user.deactivated = CacheStorage.getString(cursor, DEACTIVATED)
user.sex = CacheStorage.getInt(cursor, GENDER)
user.screenName = CacheStorage.getString(cursor, SCREEN_NAME)
user.isOnline = CacheStorage.getInt(cursor, IS_ONLINE) == 1
user.isOnlineMobile = CacheStorage.getInt(cursor, IS_ONLINE_MOBILE) == 1
user.status = CacheStorage.getString(cursor, STATUS)
user.lastSeen = CacheStorage.getInt(cursor, LAST_SEEN)
val photos =
VKUtil.parseJsonPhotos(JSONObject(CacheStorage.getString(cursor, PHOTOS)))
user.photo50 = photos[0]
user.photo100 = photos[1]
user.photo200 = photos[2]
return user
}
}