mirror of
https://github.com/mihonapp/mihon.git
synced 2025-11-18 15:07:30 +01:00
Added recently read tab (#316)
This commit is contained in:
@@ -9,7 +9,7 @@ import eu.kanade.tachiyomi.data.database.queries.*
|
||||
* This class provides operations to manage the database through its interfaces.
|
||||
*/
|
||||
open class DatabaseHelper(context: Context)
|
||||
: MangaQueries, ChapterQueries, MangaSyncQueries, CategoryQueries, MangaCategoryQueries {
|
||||
: MangaQueries, ChapterQueries, MangaSyncQueries, CategoryQueries, MangaCategoryQueries, HistoryQueries {
|
||||
|
||||
override val db = DefaultStorIOSQLite.builder()
|
||||
.sqliteOpenHelper(DbOpenHelper(context))
|
||||
@@ -18,6 +18,7 @@ open class DatabaseHelper(context: Context)
|
||||
.addTypeMapping(MangaSync::class.java, MangaSyncSQLiteTypeMapping())
|
||||
.addTypeMapping(Category::class.java, CategorySQLiteTypeMapping())
|
||||
.addTypeMapping(MangaCategory::class.java, MangaCategorySQLiteTypeMapping())
|
||||
.addTypeMapping(History::class.java, HistorySQLiteTypeMapping())
|
||||
.build()
|
||||
|
||||
inline fun inTransaction(block: () -> Unit) = db.inTransaction(block)
|
||||
|
||||
@@ -17,7 +17,7 @@ class DbOpenHelper(context: Context)
|
||||
/**
|
||||
* Version of the database.
|
||||
*/
|
||||
const val DATABASE_VERSION = 2
|
||||
const val DATABASE_VERSION = 3
|
||||
}
|
||||
|
||||
override fun onCreate(db: SQLiteDatabase) = with(db) {
|
||||
@@ -26,11 +26,13 @@ class DbOpenHelper(context: Context)
|
||||
execSQL(MangaSyncTable.createTableQuery)
|
||||
execSQL(CategoryTable.createTableQuery)
|
||||
execSQL(MangaCategoryTable.createTableQuery)
|
||||
execSQL(HistoryTable.createTableQuery)
|
||||
|
||||
// DB indexes
|
||||
execSQL(MangaTable.createUrlIndexQuery)
|
||||
execSQL(MangaTable.createFavoriteIndexQuery)
|
||||
execSQL(ChapterTable.createMangaIdIndexQuery)
|
||||
execSQL(HistoryTable.createChapterIdIndexQuery)
|
||||
}
|
||||
|
||||
override fun onUpgrade(db: SQLiteDatabase, oldVersion: Int, newVersion: Int) {
|
||||
@@ -41,10 +43,15 @@ class DbOpenHelper(context: Context)
|
||||
db.execSQL("""UPDATE mangas SET thumbnail_url =
|
||||
REPLACE(thumbnail_url, '93.174.95.110', 'kissmanga.com') WHERE source = 4""")
|
||||
}
|
||||
if (oldVersion < 3) {
|
||||
// Initialize history tables
|
||||
db.execSQL(HistoryTable.createTableQuery)
|
||||
db.execSQL(HistoryTable.createChapterIdIndexQuery)
|
||||
}
|
||||
}
|
||||
|
||||
override fun onConfigure(db: SQLiteDatabase) {
|
||||
db.setForeignKeyConstraintsEnabled(true)
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,58 @@
|
||||
package eu.kanade.tachiyomi.data.database.models;
|
||||
|
||||
import com.pushtorefresh.storio.sqlite.annotations.StorIOSQLiteColumn;
|
||||
import com.pushtorefresh.storio.sqlite.annotations.StorIOSQLiteType;
|
||||
|
||||
import java.io.Serializable;
|
||||
|
||||
import eu.kanade.tachiyomi.data.database.tables.HistoryTable;
|
||||
|
||||
/**
|
||||
* Object containing the history statistics of a chapter
|
||||
*/
|
||||
@StorIOSQLiteType(table = HistoryTable.TABLE)
|
||||
public class History implements Serializable {
|
||||
|
||||
/**
|
||||
* Id of history object.
|
||||
*/
|
||||
@StorIOSQLiteColumn(name = HistoryTable.COL_ID, key = true)
|
||||
public Long id;
|
||||
|
||||
/**
|
||||
* Chapter id of history object.
|
||||
*/
|
||||
@StorIOSQLiteColumn(name = HistoryTable.COL_CHAPTER_ID)
|
||||
public long chapter_id;
|
||||
|
||||
/**
|
||||
* Last time chapter was read in time long format
|
||||
*/
|
||||
@StorIOSQLiteColumn(name = HistoryTable.COL_LAST_READ)
|
||||
public long last_read;
|
||||
|
||||
/**
|
||||
* Total time chapter was read - todo not yet implemented
|
||||
*/
|
||||
@StorIOSQLiteColumn(name = HistoryTable.COL_TIME_READ)
|
||||
public long time_read;
|
||||
|
||||
/**
|
||||
* Empty history constructor
|
||||
*/
|
||||
public History() {
|
||||
}
|
||||
|
||||
/**
|
||||
* History constructor
|
||||
*
|
||||
* @param chapter chapter object
|
||||
* @return history object
|
||||
*/
|
||||
public static History create(Chapter chapter) {
|
||||
History history = new History();
|
||||
history.chapter_id = chapter.id;
|
||||
return history;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,27 @@
|
||||
package eu.kanade.tachiyomi.data.database.models;
|
||||
|
||||
/**
|
||||
* Object containing manga, chapter and history
|
||||
*/
|
||||
public class MangaChapterHistory {
|
||||
/**
|
||||
* Object containing manga and chapter
|
||||
*/
|
||||
public MangaChapter mangaChapter;
|
||||
|
||||
/**
|
||||
* Object containing history
|
||||
*/
|
||||
public History history;
|
||||
|
||||
/**
|
||||
* MangaChapterHistory constructor
|
||||
*
|
||||
* @param mangaChapter object containing manga and chapter
|
||||
* @param history object containing history
|
||||
*/
|
||||
public MangaChapterHistory(MangaChapter mangaChapter, History history) {
|
||||
this.mangaChapter = mangaChapter;
|
||||
this.history = history;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,53 @@
|
||||
package eu.kanade.tachiyomi.data.database.queries
|
||||
|
||||
import com.pushtorefresh.storio.sqlite.queries.RawQuery
|
||||
import eu.kanade.tachiyomi.data.database.DbProvider
|
||||
import eu.kanade.tachiyomi.data.database.models.History
|
||||
import eu.kanade.tachiyomi.data.database.models.MangaChapterHistory
|
||||
import eu.kanade.tachiyomi.data.database.resolvers.HistoryLastReadPutResolver
|
||||
import eu.kanade.tachiyomi.data.database.resolvers.MangaChapterHistoryGetResolver
|
||||
import eu.kanade.tachiyomi.data.database.tables.HistoryTable
|
||||
import java.util.*
|
||||
|
||||
interface HistoryQueries : DbProvider {
|
||||
|
||||
/**
|
||||
* Insert history into database
|
||||
* @param history object containing history information
|
||||
*/
|
||||
fun insertHistory(history: History) = db.put().`object`(history).prepare()
|
||||
|
||||
/**
|
||||
* Returns history of recent manga containing last read chapter
|
||||
* @param date recent date range
|
||||
*/
|
||||
fun getRecentManga(date: Date) = db.get()
|
||||
.listOfObjects(MangaChapterHistory::class.java)
|
||||
.withQuery(RawQuery.builder()
|
||||
.query(getRecentMangasQuery())
|
||||
.args(date.time)
|
||||
.observesTables(HistoryTable.TABLE)
|
||||
.build())
|
||||
.withGetResolver(MangaChapterHistoryGetResolver.INSTANCE)
|
||||
.prepare()
|
||||
|
||||
fun getHistoryByMangaId(mangaId: Long) = db.get()
|
||||
.listOfObjects(History::class.java)
|
||||
.withQuery(RawQuery.builder()
|
||||
.query(getHistoryByMangaId())
|
||||
.args(mangaId)
|
||||
.observesTables(HistoryTable.TABLE)
|
||||
.build())
|
||||
.prepare()
|
||||
|
||||
|
||||
/**
|
||||
* Updates the history last read.
|
||||
* Inserts history object if not yet in database
|
||||
* @param history history object
|
||||
*/
|
||||
fun updateHistoryLastRead(history: History) = db.put()
|
||||
.`object`(history)
|
||||
.withPutResolver(HistoryLastReadPutResolver())
|
||||
.prepare()
|
||||
}
|
||||
@@ -2,6 +2,7 @@ package eu.kanade.tachiyomi.data.database.queries
|
||||
|
||||
import eu.kanade.tachiyomi.data.database.tables.CategoryTable as Category
|
||||
import eu.kanade.tachiyomi.data.database.tables.ChapterTable as Chapter
|
||||
import eu.kanade.tachiyomi.data.database.tables.HistoryTable as History
|
||||
import eu.kanade.tachiyomi.data.database.tables.MangaCategoryTable as MangaCategory
|
||||
import eu.kanade.tachiyomi.data.database.tables.MangaTable as Manga
|
||||
|
||||
@@ -39,6 +40,38 @@ fun getRecentsQuery() = """
|
||||
ORDER BY ${Chapter.COL_DATE_UPLOAD} DESC
|
||||
"""
|
||||
|
||||
/**
|
||||
* Query to get the recently read chapters of manga from the library up to a date.
|
||||
* The max_last_read table contains the most recent chapters grouped by manga
|
||||
* The select statement returns all information of chapters that have the same id as the chapter in max_last_read
|
||||
* and are read after the given time period
|
||||
* @return return limit is 25
|
||||
*/
|
||||
fun getRecentMangasQuery() = """
|
||||
SELECT ${Manga.TABLE}.${Manga.COL_URL} as mangaUrl, ${Manga.TABLE}.*, ${Chapter.TABLE}.*, ${History.TABLE}.*
|
||||
FROM ${Manga.TABLE}
|
||||
JOIN ${Chapter.TABLE}
|
||||
ON ${Manga.TABLE}.${Manga.COL_ID} = ${Chapter.TABLE}.${Chapter.COL_MANGA_ID}
|
||||
JOIN ${History.TABLE}
|
||||
ON ${Chapter.TABLE}.${Chapter.COL_ID} = ${History.TABLE}.${History.COL_CHAPTER_ID}
|
||||
JOIN (
|
||||
SELECT ${Chapter.TABLE}.${Chapter.COL_MANGA_ID},${Chapter.TABLE}.${Chapter.COL_ID} as ${History.COL_CHAPTER_ID}, MAX(${History.TABLE}.${History.COL_LAST_READ}) as ${History.COL_LAST_READ}
|
||||
FROM ${Chapter.TABLE} JOIN ${History.TABLE}
|
||||
ON ${Chapter.TABLE}.${Chapter.COL_ID} = ${History.TABLE}.${History.COL_CHAPTER_ID}
|
||||
GROUP BY ${Chapter.TABLE}.${Chapter.COL_MANGA_ID}) AS max_last_read
|
||||
ON ${Chapter.TABLE}.${Chapter.COL_MANGA_ID} = max_last_read.${Chapter.COL_MANGA_ID}
|
||||
WHERE ${History.TABLE}.${History.COL_LAST_READ} > ? AND max_last_read.${History.COL_CHAPTER_ID} = ${History.TABLE}.${History.COL_CHAPTER_ID}
|
||||
ORDER BY max_last_read.${History.COL_LAST_READ} DESC
|
||||
LIMIT 25
|
||||
"""
|
||||
|
||||
fun getHistoryByMangaId() = """
|
||||
SELECT ${History.TABLE}.*
|
||||
FROM ${History.TABLE}
|
||||
JOIN ${Chapter.TABLE}
|
||||
ON ${History.TABLE}.${History.COL_CHAPTER_ID} = ${Chapter.TABLE}.${Chapter.COL_ID}
|
||||
WHERE ${Chapter.TABLE}.${Chapter.COL_MANGA_ID} = ? AND ${History.TABLE}.${History.COL_CHAPTER_ID} = ${Chapter.TABLE}.${Chapter.COL_ID}
|
||||
"""
|
||||
|
||||
/**
|
||||
* Query to get the categories for a manga.
|
||||
|
||||
@@ -0,0 +1,52 @@
|
||||
package eu.kanade.tachiyomi.data.database.resolvers
|
||||
|
||||
import android.content.ContentValues
|
||||
import android.support.annotation.NonNull
|
||||
import com.pushtorefresh.storio.sqlite.StorIOSQLite
|
||||
import com.pushtorefresh.storio.sqlite.operations.put.PutResolver
|
||||
import com.pushtorefresh.storio.sqlite.operations.put.PutResult
|
||||
import com.pushtorefresh.storio.sqlite.queries.UpdateQuery
|
||||
import eu.kanade.tachiyomi.data.database.inTransactionReturn
|
||||
import eu.kanade.tachiyomi.data.database.models.History
|
||||
import eu.kanade.tachiyomi.data.database.tables.HistoryTable
|
||||
|
||||
class HistoryLastReadPutResolver : PutResolver<History>() {
|
||||
|
||||
/**
|
||||
* Updates last_read time of chapter
|
||||
*/
|
||||
override fun performPut(@NonNull db: StorIOSQLite, @NonNull history: History): PutResult = db.inTransactionReturn {
|
||||
// Create put query
|
||||
val updateQuery = mapToUpdateQuery(history)
|
||||
val contentValues = mapToContentValues(history)
|
||||
|
||||
// Execute query
|
||||
val numberOfRowsUpdated = db.internal().update(updateQuery, contentValues)
|
||||
|
||||
// If chapter not found in history insert into database
|
||||
if (numberOfRowsUpdated == 0) {
|
||||
db.put().`object`(history).prepare().asRxObservable().subscribe()
|
||||
}
|
||||
// Update result
|
||||
PutResult.newUpdateResult(numberOfRowsUpdated, updateQuery.table())
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates update query
|
||||
* @param history object
|
||||
*/
|
||||
fun mapToUpdateQuery(history: History) = UpdateQuery.builder()
|
||||
.table(HistoryTable.TABLE)
|
||||
.where("${HistoryTable.COL_CHAPTER_ID} = ?")
|
||||
.whereArgs(history.chapter_id)
|
||||
.build()
|
||||
|
||||
/**
|
||||
* Create content query
|
||||
* @param history object
|
||||
*/
|
||||
fun mapToContentValues(history: History) = ContentValues(1).apply {
|
||||
put(HistoryTable.COL_LAST_READ, history.last_read)
|
||||
}
|
||||
|
||||
}
|
||||
@@ -20,7 +20,7 @@ class MangaChapterGetResolver : DefaultGetResolver<MangaChapter>() {
|
||||
val manga = mangaGetResolver.mapFromCursor(cursor)
|
||||
val chapter = chapterGetResolver.mapFromCursor(cursor)
|
||||
manga.id = chapter.manga_id
|
||||
manga.url = cursor.getString(cursor.getColumnIndex("mangaUrl"));
|
||||
manga.url = cursor.getString(cursor.getColumnIndex("mangaUrl"))
|
||||
|
||||
return MangaChapter(manga, chapter)
|
||||
}
|
||||
|
||||
@@ -0,0 +1,51 @@
|
||||
package eu.kanade.tachiyomi.data.database.resolvers
|
||||
|
||||
import android.database.Cursor
|
||||
import com.pushtorefresh.storio.sqlite.operations.get.DefaultGetResolver
|
||||
import eu.kanade.tachiyomi.data.database.models.*
|
||||
|
||||
class MangaChapterHistoryGetResolver : DefaultGetResolver<MangaChapterHistory>() {
|
||||
companion object {
|
||||
val INSTANCE = MangaChapterHistoryGetResolver()
|
||||
}
|
||||
|
||||
/**
|
||||
* Manga get resolver
|
||||
*/
|
||||
private val mangaGetResolver = MangaStorIOSQLiteGetResolver()
|
||||
|
||||
/**
|
||||
* Chapter get resolver
|
||||
*/
|
||||
private val chapterResolver = ChapterStorIOSQLiteGetResolver()
|
||||
|
||||
/**
|
||||
* History get resolver
|
||||
*/
|
||||
private val historyGetResolver = HistoryStorIOSQLiteGetResolver()
|
||||
|
||||
/**
|
||||
* Map correct objects from cursor result
|
||||
*/
|
||||
override fun mapFromCursor(cursor: Cursor): MangaChapterHistory {
|
||||
// Get manga object
|
||||
val manga = mangaGetResolver.mapFromCursor(cursor)
|
||||
|
||||
// Get chapter object
|
||||
val chapter = chapterResolver.mapFromCursor(cursor)
|
||||
|
||||
// Get history object
|
||||
val history = historyGetResolver.mapFromCursor(cursor)
|
||||
|
||||
// Make certain column conflicts are dealt with
|
||||
manga.id = chapter.manga_id
|
||||
manga.url = cursor.getString(cursor.getColumnIndex("mangaUrl"))
|
||||
chapter.id = history.chapter_id
|
||||
|
||||
// Create mangaChapter object
|
||||
val mangaChapter = MangaChapter(manga, chapter)
|
||||
|
||||
// Return result
|
||||
return MangaChapterHistory(mangaChapter, history)
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,48 @@
|
||||
package eu.kanade.tachiyomi.data.database.tables
|
||||
|
||||
object HistoryTable {
|
||||
|
||||
/**
|
||||
* Table name
|
||||
*/
|
||||
const val TABLE = "history"
|
||||
|
||||
/**
|
||||
* Id column name
|
||||
*/
|
||||
const val COL_ID = "${TABLE}_id"
|
||||
|
||||
/**
|
||||
* Chapter id column name
|
||||
*/
|
||||
const val COL_CHAPTER_ID = "${TABLE}_chapter_id"
|
||||
|
||||
/**
|
||||
* Last read column name
|
||||
*/
|
||||
const val COL_LAST_READ = "${TABLE}_last_read"
|
||||
|
||||
/**
|
||||
* Time read column name
|
||||
*/
|
||||
const val COL_TIME_READ = "${TABLE}_time_read"
|
||||
|
||||
/**
|
||||
* query to create history table
|
||||
*/
|
||||
val createTableQuery: String
|
||||
get() = """CREATE TABLE $TABLE(
|
||||
$COL_ID INTEGER NOT NULL PRIMARY KEY,
|
||||
$COL_CHAPTER_ID INTEGER NOT NULL UNIQUE,
|
||||
$COL_LAST_READ LONG,
|
||||
$COL_TIME_READ LONG,
|
||||
FOREIGN KEY($COL_CHAPTER_ID) REFERENCES ${ChapterTable.TABLE} (${ChapterTable.COL_ID})
|
||||
ON DELETE CASCADE
|
||||
)"""
|
||||
|
||||
/**
|
||||
* query to index history chapter id
|
||||
*/
|
||||
val createChapterIdIndexQuery: String
|
||||
get() = "CREATE INDEX ${TABLE}_${COL_CHAPTER_ID}_index ON $TABLE($COL_CHAPTER_ID)"
|
||||
}
|
||||
Reference in New Issue
Block a user