diff --git a/app/schemas/com.hippo.ehviewer.dao.SearchDatabase/1.json b/app/schemas/com.hippo.ehviewer.dao.SearchDatabase/1.json new file mode 100644 index 000000000..a8c702b8d --- /dev/null +++ b/app/schemas/com.hippo.ehviewer.dao.SearchDatabase/1.json @@ -0,0 +1,46 @@ +{ + "formatVersion": 1, + "database": { + "version": 1, + "identityHash": "69230710633494b4b331e10dae436fb7", + "entities": [ + { + "tableName": "suggestions", + "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`_id` INTEGER, `date` LONG, `query` TEXT, PRIMARY KEY(`_id`))", + "fields": [ + { + "fieldPath": "id", + "columnName": "_id", + "affinity": "INTEGER", + "notNull": false + }, + { + "fieldPath": "date", + "columnName": "date", + "affinity": "LONG", + "notNull": false + }, + { + "fieldPath": "query", + "columnName": "query", + "affinity": "TEXT", + "notNull": false + } + ], + "primaryKey": { + "autoGenerate": false, + "columnNames": [ + "_id" + ] + }, + "indices": [], + "foreignKeys": [] + } + ], + "views": [], + "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, '69230710633494b4b331e10dae436fb7')" + ] + } +} diff --git a/app/schemas/com.hippo.ehviewer.dao.SearchDatabase/2.json b/app/schemas/com.hippo.ehviewer.dao.SearchDatabase/2.json new file mode 100644 index 000000000..a75be47db --- /dev/null +++ b/app/schemas/com.hippo.ehviewer.dao.SearchDatabase/2.json @@ -0,0 +1,46 @@ +{ + "formatVersion": 1, + "database": { + "version": 2, + "identityHash": "bf4a5b5f3cb9bc7383488bae14f5d4bd", + "entities": [ + { + "tableName": "suggestions", + "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`date` INTEGER NOT NULL, `query` TEXT NOT NULL, `_id` INTEGER, PRIMARY KEY(`_id`))", + "fields": [ + { + "fieldPath": "date", + "columnName": "date", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "query", + "columnName": "query", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "id", + "columnName": "_id", + "affinity": "INTEGER", + "notNull": false + } + ], + "primaryKey": { + "autoGenerate": false, + "columnNames": [ + "_id" + ] + }, + "indices": [], + "foreignKeys": [] + } + ], + "views": [], + "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, 'bf4a5b5f3cb9bc7383488bae14f5d4bd')" + ] + } +} \ No newline at end of file diff --git a/app/src/main/java/com/hippo/ehviewer/dao/EhDatabase.kt b/app/src/main/java/com/hippo/ehviewer/dao/EhDatabase.kt index 576d0bb55..8e043a446 100644 --- a/app/src/main/java/com/hippo/ehviewer/dao/EhDatabase.kt +++ b/app/src/main/java/com/hippo/ehviewer/dao/EhDatabase.kt @@ -8,6 +8,7 @@ import androidx.room.Room import androidx.room.RoomDatabase import androidx.room.migration.AutoMigrationSpec import androidx.sqlite.db.SupportSQLiteDatabase +import com.hippo.ehviewer.EhApplication @DeleteTable(tableName = "BOOKMARKS") class DropBookMarkDao : AutoMigrationSpec @@ -86,3 +87,22 @@ fun buildMainDB(context: Context): EhDatabase { fun buildCookiesDB(context: Context): CookiesDatabase { return Room.databaseBuilder(context, CookiesDatabase::class.java, "okhttp3-cookie.db").build() } + +@Database( + entities = [Search::class], + version = 2, + autoMigrations = [ + AutoMigration( + from = 1, + to = 2, + ), + ], +) +abstract class SearchDatabase : RoomDatabase() { + abstract fun searchDao(): SearchDao +} + +val searchDatabase by lazy { + val context = EhApplication.application + Room.databaseBuilder(context, SearchDatabase::class.java, "search_database.db").build() +} diff --git a/app/src/main/java/com/hippo/ehviewer/dao/Search.kt b/app/src/main/java/com/hippo/ehviewer/dao/Search.kt new file mode 100644 index 000000000..2f9ea4deb --- /dev/null +++ b/app/src/main/java/com/hippo/ehviewer/dao/Search.kt @@ -0,0 +1,16 @@ +package com.hippo.ehviewer.dao + +import androidx.room.ColumnInfo +import androidx.room.Entity +import androidx.room.PrimaryKey + +@Entity(tableName = "suggestions") +data class Search( + @ColumnInfo(name = "date") + val date: Long, + @ColumnInfo(name = "query") + val query: String, + @PrimaryKey + @ColumnInfo(name = "_id") + val id: Int? = null, +) diff --git a/app/src/main/java/com/hippo/ehviewer/dao/SearchDao.kt b/app/src/main/java/com/hippo/ehviewer/dao/SearchDao.kt new file mode 100644 index 000000000..9bdf2d7fd --- /dev/null +++ b/app/src/main/java/com/hippo/ehviewer/dao/SearchDao.kt @@ -0,0 +1,25 @@ +package com.hippo.ehviewer.dao + +import androidx.room.Dao +import androidx.room.Insert +import androidx.room.Query + +@Dao +interface SearchDao { + @Query("DELETE FROM suggestions WHERE query = :query") + fun deleteQuery(query: String) + + @Query("SELECT * FROM suggestions") + fun rawSuggestions(): Array + + @Insert + fun insert(search: Search) + + fun addQuery(query: String) { + deleteQuery(query) + val search = Search(System.currentTimeMillis(), query) + insert(search) + } + + fun suggestions(prefix: String, limit: Int) = rawSuggestions().map { it.query }.toTypedArray() +} diff --git a/app/src/main/java/com/hippo/ehviewer/ui/legacy/SearchDatabase.java b/app/src/main/java/com/hippo/ehviewer/ui/legacy/SearchDatabase.java deleted file mode 100644 index 44942c4c0..000000000 --- a/app/src/main/java/com/hippo/ehviewer/ui/legacy/SearchDatabase.java +++ /dev/null @@ -1,162 +0,0 @@ -/* - * Copyright (C) 2015 Hippo Seven - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package com.hippo.ehviewer.ui.legacy; - -import android.content.ContentValues; -import android.content.Context; -import android.database.Cursor; -import android.database.SQLException; -import android.database.sqlite.SQLiteDatabase; -import android.database.sqlite.SQLiteOpenHelper; -import android.text.TextUtils; -import android.util.Log; - -import com.hippo.ehviewer.util.SqlUtils; - -import java.util.LinkedList; -import java.util.List; - -public final class SearchDatabase { - - public static final String COLUMN_QUERY = "query"; - public static final String COLUMN_DATE = "date"; - private static final String TAG = SearchDatabase.class.getSimpleName(); - private static final String DATABASE_NAME = "search_database.db"; - private static final String TABLE_SUGGESTIONS = "suggestions"; - - private static final int MAX_HISTORY = 100; - private static SearchDatabase sInstance; - private final SQLiteDatabase mDatabase; - - private SearchDatabase(Context context) { - DatabaseHelper databaseHelper = new DatabaseHelper(context); - mDatabase = databaseHelper.getWritableDatabase(); - } - - public static SearchDatabase getInstance(Context context) { - if (sInstance == null) { - sInstance = new SearchDatabase(context.getApplicationContext()); - } - return sInstance; - } - - public String[] getSuggestions(String prefix, int limit) { - List queryList = new LinkedList<>(); - limit = Math.max(0, limit); - - StringBuilder sb = new StringBuilder(); - sb.append("SELECT * FROM ").append(TABLE_SUGGESTIONS); - if (!TextUtils.isEmpty(prefix)) { - sb.append(" WHERE ").append(COLUMN_QUERY).append(" LIKE '") - .append(SqlUtils.sqlEscapeString(prefix)).append("%'"); - } - sb.append(" ORDER BY ").append(COLUMN_DATE).append(" DESC") - .append(" LIMIT ").append(limit); - - try { - Cursor cursor = mDatabase.rawQuery(sb.toString(), null); - int queryIndex = cursor.getColumnIndex(COLUMN_QUERY); - if (cursor.moveToFirst()) { - while (!cursor.isAfterLast()) { - String suggestion = cursor.getString(queryIndex); - if (!prefix.equals(suggestion)) { - queryList.add(suggestion); - } - cursor.moveToNext(); - } - } - cursor.close(); - return queryList.toArray(new String[queryList.size()]); - } catch (SQLException e) { - return new String[0]; - } - } - - public void addQuery(final String query) { - if (!TextUtils.isEmpty(query)) { - // Delete old first - deleteQuery(query); - // Add it to database - ContentValues values = new ContentValues(); - values.put(COLUMN_QUERY, query); - values.put(COLUMN_DATE, System.currentTimeMillis()); - mDatabase.insert(TABLE_SUGGESTIONS, null, values); - // Remove history if more than max - truncateHistory(MAX_HISTORY); - } - } - - public void deleteQuery(final String query) { - mDatabase.delete(TABLE_SUGGESTIONS, COLUMN_QUERY + "=?", new String[]{query}); - } - - public void clearQuery() { - truncateHistory(0); - } - - /** - * Reduces the length of the history table, to prevent it from growing too large. - * - * @param maxEntries Max entries to leave in the table. 0 means remove all entries. - */ - private void truncateHistory(int maxEntries) { - if (maxEntries < 0) { - throw new IllegalArgumentException(); - } - - try { - // null means "delete all". otherwise "delete but leave n newest" - String selection = null; - if (maxEntries > 0) { - selection = "_id IN " + - "(SELECT _id FROM " + TABLE_SUGGESTIONS + - " ORDER BY " + COLUMN_DATE + " DESC" + - " LIMIT -1 OFFSET " + maxEntries + ")"; - } - mDatabase.delete(TABLE_SUGGESTIONS, selection, null); - } catch (RuntimeException e) { - Log.e(TAG, "truncateHistory", e); - } - } - - /** - * Builds the database. This version has extra support for using the version field - * as a mode flags field, and configures the database columns depending on the mode bits - * (features) requested by the extending class. - */ - private static class DatabaseHelper extends SQLiteOpenHelper { - - public DatabaseHelper(Context context) { - super(context, DATABASE_NAME, null, 1); - } - - @Override - public void onCreate(SQLiteDatabase db) { - db.execSQL("CREATE TABLE " + TABLE_SUGGESTIONS + " (" + - "_id INTEGER PRIMARY KEY" + - "," + COLUMN_QUERY + " TEXT" + - "," + COLUMN_DATE + " LONG" + - ");"); - } - - @Override - public void onUpgrade(SQLiteDatabase db, int oldVersion, int newVersion) { - db.execSQL("DROP TABLE IF EXISTS " + TABLE_SUGGESTIONS); - onCreate(db); - } - } -} diff --git a/app/src/main/java/com/hippo/ehviewer/ui/scene/SearchBarScene.kt b/app/src/main/java/com/hippo/ehviewer/ui/scene/SearchBarScene.kt index 0bb3247e7..0353f3aac 100644 --- a/app/src/main/java/com/hippo/ehviewer/ui/scene/SearchBarScene.kt +++ b/app/src/main/java/com/hippo/ehviewer/ui/scene/SearchBarScene.kt @@ -22,13 +22,13 @@ import com.google.android.material.shape.MaterialShapeDrawable import com.hippo.ehviewer.R import com.hippo.ehviewer.Settings import com.hippo.ehviewer.client.EhTagDatabase +import com.hippo.ehviewer.dao.searchDatabase import com.hippo.ehviewer.databinding.SceneSearchbarBinding import com.hippo.ehviewer.ui.legacy.BaseDialogBuilder -import com.hippo.ehviewer.ui.legacy.SearchDatabase +import eu.kanade.tachiyomi.util.lang.launchIO import eu.kanade.tachiyomi.util.lang.withUIContext import kotlinx.coroutines.flow.Flow import kotlinx.coroutines.flow.flow -import kotlinx.coroutines.launch abstract class SearchBarScene : BaseScene(), ToolBarScene { private var _binding: SceneSearchbarBinding? = null @@ -37,7 +37,7 @@ abstract class SearchBarScene : BaseScene(), ToolBarScene { private var mSuggestionAdapter: SuggestionAdapter? = null private var mSuggestionProvider: SuggestionProvider? = null private var mAllowEmptySearch = true - private val mSearchDatabase by lazy { SearchDatabase.getInstance(context) } + private val mSearchDatabase by lazy { searchDatabase.searchDao() } private var onApplySearch: (String) -> Unit = {} override fun onCreateView( @@ -296,7 +296,7 @@ abstract class SearchBarScene : BaseScene(), ToolBarScene { @SuppressLint("NotifyDataSetChanged") private fun updateSuggestions(scrollToTop: Boolean = true) { _binding ?: return - viewLifecycleOwner.lifecycleScope.launch { + viewLifecycleOwner.lifecycleScope.launchIO { val suggestions = mutableListOf() mergedSuggestionFlow().collect { suggestions.add(it) @@ -314,7 +314,7 @@ abstract class SearchBarScene : BaseScene(), ToolBarScene { private fun mergedSuggestionFlow(): Flow = flow { binding.searchview.editText.text?.toString()?.let { text -> mSuggestionProvider?.run { providerSuggestions(text)?.forEach { emit(it) } } - mSearchDatabase.getSuggestions(text, 128).forEach { emit(KeywordSuggestion(it)) } + mSearchDatabase.suggestions(text, 128).forEach { emit(KeywordSuggestion(it)) } EhTagDatabase.takeIf { it.isInitialized() }?.run { if (text.isNotEmpty() && !text.endsWith(' ')) { val keyword = text.substringAfterLast(' ')