Sfoglia il codice sorgente

书籍类型采用位运算,查询更高效

kunfei 4 anni fa
parent
commit
e3442ef842
50 ha cambiato i file con 2074 aggiunte e 216 eliminazioni
  1. 4 4
      app/schemas/io.legado.app.data.AppDatabase/54.json
  2. 1711 0
      app/schemas/io.legado.app.data.AppDatabase/55.json
  3. 2 5
      app/src/main/java/io/legado/app/api/controller/BookController.kt
  4. 26 0
      app/src/main/java/io/legado/app/constant/BookSourceType.kt
  5. 36 7
      app/src/main/java/io/legado/app/constant/BookType.kt
  6. 2 1
      app/src/main/java/io/legado/app/data/AppDatabase.kt
  7. 51 32
      app/src/main/java/io/legado/app/data/DatabaseMigrations.kt
  8. 10 10
      app/src/main/java/io/legado/app/data/dao/BookDao.kt
  9. 6 6
      app/src/main/java/io/legado/app/data/dao/BookGroupDao.kt
  10. 10 32
      app/src/main/java/io/legado/app/data/entities/Book.kt
  11. 2 2
      app/src/main/java/io/legado/app/data/entities/BookSource.kt
  12. 2 1
      app/src/main/java/io/legado/app/data/entities/SearchBook.kt
  13. 65 0
      app/src/main/java/io/legado/app/help/BookExtensions.kt
  14. 5 5
      app/src/main/java/io/legado/app/help/BookHelp.kt
  15. 3 4
      app/src/main/java/io/legado/app/help/source/SourceAnalyzer.kt
  16. 4 2
      app/src/main/java/io/legado/app/help/storage/ImportOldData.kt
  17. 4 0
      app/src/main/java/io/legado/app/help/storage/Restore.kt
  18. 7 5
      app/src/main/java/io/legado/app/model/BookCover.kt
  19. 2 1
      app/src/main/java/io/legado/app/model/CacheBook.kt
  20. 1 1
      app/src/main/java/io/legado/app/model/Debug.kt
  21. 5 5
      app/src/main/java/io/legado/app/model/ReadBook.kt
  22. 16 15
      app/src/main/java/io/legado/app/model/localBook/LocalBook.kt
  23. 1 1
      app/src/main/java/io/legado/app/model/webBook/BookInfo.kt
  24. 3 2
      app/src/main/java/io/legado/app/model/webBook/BookList.kt
  25. 3 2
      app/src/main/java/io/legado/app/model/webBook/WebBook.kt
  26. 2 2
      app/src/main/java/io/legado/app/service/CheckSourceService.kt
  27. 2 2
      app/src/main/java/io/legado/app/ui/book/audio/AudioPlayActivity.kt
  28. 2 2
      app/src/main/java/io/legado/app/ui/book/cache/CacheActivity.kt
  29. 4 3
      app/src/main/java/io/legado/app/ui/book/cache/CacheAdapter.kt
  30. 9 8
      app/src/main/java/io/legado/app/ui/book/info/BookInfoActivity.kt
  31. 6 5
      app/src/main/java/io/legado/app/ui/book/info/BookInfoViewModel.kt
  32. 10 6
      app/src/main/java/io/legado/app/ui/book/info/edit/BookInfoEditActivity.kt
  33. 2 1
      app/src/main/java/io/legado/app/ui/book/manage/BookAdapter.kt
  34. 2 1
      app/src/main/java/io/legado/app/ui/book/manage/BookshelfManageViewModel.kt
  35. 2 1
      app/src/main/java/io/legado/app/ui/book/read/ContentEditDialog.kt
  36. 6 9
      app/src/main/java/io/legado/app/ui/book/read/ReadBookActivity.kt
  37. 5 4
      app/src/main/java/io/legado/app/ui/book/read/ReadBookViewModel.kt
  38. 2 1
      app/src/main/java/io/legado/app/ui/book/read/page/provider/ImageProvider.kt
  39. 1 1
      app/src/main/java/io/legado/app/ui/book/remote/RemoteBookViewModel.kt
  40. 1 1
      app/src/main/java/io/legado/app/ui/book/remote/manager/RemoteBookWebDav.kt
  41. 2 1
      app/src/main/java/io/legado/app/ui/book/searchContent/SearchContentActivity.kt
  42. 8 8
      app/src/main/java/io/legado/app/ui/book/source/edit/BookSourceEditActivity.kt
  43. 2 1
      app/src/main/java/io/legado/app/ui/book/toc/ChapterListFragment.kt
  44. 3 2
      app/src/main/java/io/legado/app/ui/main/MainViewModel.kt
  45. 2 2
      app/src/main/java/io/legado/app/ui/main/bookshelf/style1/books/BooksAdapterGrid.kt
  46. 2 2
      app/src/main/java/io/legado/app/ui/main/bookshelf/style1/books/BooksAdapterList.kt
  47. 7 3
      app/src/main/java/io/legado/app/ui/main/bookshelf/style1/books/BooksFragment.kt
  48. 2 2
      app/src/main/java/io/legado/app/ui/main/bookshelf/style2/BooksAdapterGrid.kt
  49. 2 2
      app/src/main/java/io/legado/app/ui/main/bookshelf/style2/BooksAdapterList.kt
  50. 7 3
      app/src/main/java/io/legado/app/ui/main/bookshelf/style2/BookshelfFragment2.kt

+ 4 - 4
app/schemas/io.legado.app.data.AppDatabase/54.json

@@ -2,11 +2,11 @@
   "formatVersion": 1,
   "formatVersion": 1,
   "database": {
   "database": {
     "version": 54,
     "version": 54,
-    "identityHash": "9045ef48ed80bddcaa0c7b4b05d15f24",
+    "identityHash": "2b32177325d903e84445cc80ad7cbce8",
     "entities": [
     "entities": [
       {
       {
         "tableName": "books",
         "tableName": "books",
-        "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`bookUrl` TEXT NOT NULL DEFAULT '', `tocUrl` TEXT NOT NULL DEFAULT '', `origin` TEXT NOT NULL DEFAULT '', `originName` TEXT NOT NULL DEFAULT '', `name` TEXT NOT NULL DEFAULT '', `author` TEXT NOT NULL DEFAULT '', `kind` TEXT, `customTag` TEXT, `coverUrl` TEXT, `customCoverUrl` TEXT, `intro` TEXT, `customIntro` TEXT, `charset` TEXT, `type` INTEGER NOT NULL DEFAULT 0, `group` INTEGER NOT NULL DEFAULT 0, `latestChapterTitle` TEXT, `latestChapterTime` INTEGER NOT NULL DEFAULT 0, `lastCheckTime` INTEGER NOT NULL DEFAULT 0, `lastCheckCount` INTEGER NOT NULL DEFAULT 0, `totalChapterNum` INTEGER NOT NULL DEFAULT 0, `durChapterTitle` TEXT, `durChapterIndex` INTEGER NOT NULL DEFAULT 0, `durChapterPos` INTEGER NOT NULL DEFAULT 0, `durChapterTime` INTEGER NOT NULL DEFAULT 0, `wordCount` TEXT, `canUpdate` INTEGER NOT NULL DEFAULT 1, `order` INTEGER NOT NULL DEFAULT 0, `originOrder` INTEGER NOT NULL DEFAULT 0, `variable` TEXT, `readConfig` TEXT, PRIMARY KEY(`bookUrl`))",
+        "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`bookUrl` TEXT NOT NULL DEFAULT '', `tocUrl` TEXT NOT NULL DEFAULT '', `origin` TEXT NOT NULL DEFAULT '', `originName` TEXT NOT NULL DEFAULT '', `name` TEXT NOT NULL DEFAULT '', `author` TEXT NOT NULL DEFAULT '', `kind` TEXT, `customTag` TEXT, `coverUrl` TEXT, `customCoverUrl` TEXT, `intro` TEXT, `customIntro` TEXT, `charset` TEXT, `type` INTEGER NOT NULL DEFAULT 1, `group` INTEGER NOT NULL DEFAULT 0, `latestChapterTitle` TEXT, `latestChapterTime` INTEGER NOT NULL DEFAULT 0, `lastCheckTime` INTEGER NOT NULL DEFAULT 0, `lastCheckCount` INTEGER NOT NULL DEFAULT 0, `totalChapterNum` INTEGER NOT NULL DEFAULT 0, `durChapterTitle` TEXT, `durChapterIndex` INTEGER NOT NULL DEFAULT 0, `durChapterPos` INTEGER NOT NULL DEFAULT 0, `durChapterTime` INTEGER NOT NULL DEFAULT 0, `wordCount` TEXT, `canUpdate` INTEGER NOT NULL DEFAULT 1, `order` INTEGER NOT NULL DEFAULT 0, `originOrder` INTEGER NOT NULL DEFAULT 0, `variable` TEXT, `readConfig` TEXT, PRIMARY KEY(`bookUrl`))",
         "fields": [
         "fields": [
           {
           {
             "fieldPath": "bookUrl",
             "fieldPath": "bookUrl",
@@ -97,7 +97,7 @@
             "columnName": "type",
             "columnName": "type",
             "affinity": "INTEGER",
             "affinity": "INTEGER",
             "notNull": true,
             "notNull": true,
-            "defaultValue": "0"
+            "defaultValue": "1"
           },
           },
           {
           {
             "fieldPath": "group",
             "fieldPath": "group",
@@ -1705,7 +1705,7 @@
     "views": [],
     "views": [],
     "setupQueries": [
     "setupQueries": [
       "CREATE TABLE IF NOT EXISTS room_master_table (id INTEGER PRIMARY KEY,identity_hash TEXT)",
       "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, '9045ef48ed80bddcaa0c7b4b05d15f24')"
+      "INSERT OR REPLACE INTO room_master_table (id,identity_hash) VALUES(42, '2b32177325d903e84445cc80ad7cbce8')"
     ]
     ]
   }
   }
 }
 }

+ 1711 - 0
app/schemas/io.legado.app.data.AppDatabase/55.json

@@ -0,0 +1,1711 @@
+{
+  "formatVersion": 1,
+  "database": {
+    "version": 55,
+    "identityHash": "09020e77cffa237f9a98c62eed0a01f2",
+    "entities": [
+      {
+        "tableName": "books",
+        "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`bookUrl` TEXT NOT NULL DEFAULT '', `tocUrl` TEXT NOT NULL DEFAULT '', `origin` TEXT NOT NULL DEFAULT 'loc_book', `originName` TEXT NOT NULL DEFAULT '', `name` TEXT NOT NULL DEFAULT '', `author` TEXT NOT NULL DEFAULT '', `kind` TEXT, `customTag` TEXT, `coverUrl` TEXT, `customCoverUrl` TEXT, `intro` TEXT, `customIntro` TEXT, `charset` TEXT, `type` INTEGER NOT NULL DEFAULT 1, `group` INTEGER NOT NULL DEFAULT 0, `latestChapterTitle` TEXT, `latestChapterTime` INTEGER NOT NULL DEFAULT 0, `lastCheckTime` INTEGER NOT NULL DEFAULT 0, `lastCheckCount` INTEGER NOT NULL DEFAULT 0, `totalChapterNum` INTEGER NOT NULL DEFAULT 0, `durChapterTitle` TEXT, `durChapterIndex` INTEGER NOT NULL DEFAULT 0, `durChapterPos` INTEGER NOT NULL DEFAULT 0, `durChapterTime` INTEGER NOT NULL DEFAULT 0, `wordCount` TEXT, `canUpdate` INTEGER NOT NULL DEFAULT 1, `order` INTEGER NOT NULL DEFAULT 0, `originOrder` INTEGER NOT NULL DEFAULT 0, `variable` TEXT, `readConfig` TEXT, PRIMARY KEY(`bookUrl`))",
+        "fields": [
+          {
+            "fieldPath": "bookUrl",
+            "columnName": "bookUrl",
+            "affinity": "TEXT",
+            "notNull": true,
+            "defaultValue": "''"
+          },
+          {
+            "fieldPath": "tocUrl",
+            "columnName": "tocUrl",
+            "affinity": "TEXT",
+            "notNull": true,
+            "defaultValue": "''"
+          },
+          {
+            "fieldPath": "origin",
+            "columnName": "origin",
+            "affinity": "TEXT",
+            "notNull": true,
+            "defaultValue": "'loc_book'"
+          },
+          {
+            "fieldPath": "originName",
+            "columnName": "originName",
+            "affinity": "TEXT",
+            "notNull": true,
+            "defaultValue": "''"
+          },
+          {
+            "fieldPath": "name",
+            "columnName": "name",
+            "affinity": "TEXT",
+            "notNull": true,
+            "defaultValue": "''"
+          },
+          {
+            "fieldPath": "author",
+            "columnName": "author",
+            "affinity": "TEXT",
+            "notNull": true,
+            "defaultValue": "''"
+          },
+          {
+            "fieldPath": "kind",
+            "columnName": "kind",
+            "affinity": "TEXT",
+            "notNull": false
+          },
+          {
+            "fieldPath": "customTag",
+            "columnName": "customTag",
+            "affinity": "TEXT",
+            "notNull": false
+          },
+          {
+            "fieldPath": "coverUrl",
+            "columnName": "coverUrl",
+            "affinity": "TEXT",
+            "notNull": false
+          },
+          {
+            "fieldPath": "customCoverUrl",
+            "columnName": "customCoverUrl",
+            "affinity": "TEXT",
+            "notNull": false
+          },
+          {
+            "fieldPath": "intro",
+            "columnName": "intro",
+            "affinity": "TEXT",
+            "notNull": false
+          },
+          {
+            "fieldPath": "customIntro",
+            "columnName": "customIntro",
+            "affinity": "TEXT",
+            "notNull": false
+          },
+          {
+            "fieldPath": "charset",
+            "columnName": "charset",
+            "affinity": "TEXT",
+            "notNull": false
+          },
+          {
+            "fieldPath": "type",
+            "columnName": "type",
+            "affinity": "INTEGER",
+            "notNull": true,
+            "defaultValue": "1"
+          },
+          {
+            "fieldPath": "group",
+            "columnName": "group",
+            "affinity": "INTEGER",
+            "notNull": true,
+            "defaultValue": "0"
+          },
+          {
+            "fieldPath": "latestChapterTitle",
+            "columnName": "latestChapterTitle",
+            "affinity": "TEXT",
+            "notNull": false
+          },
+          {
+            "fieldPath": "latestChapterTime",
+            "columnName": "latestChapterTime",
+            "affinity": "INTEGER",
+            "notNull": true,
+            "defaultValue": "0"
+          },
+          {
+            "fieldPath": "lastCheckTime",
+            "columnName": "lastCheckTime",
+            "affinity": "INTEGER",
+            "notNull": true,
+            "defaultValue": "0"
+          },
+          {
+            "fieldPath": "lastCheckCount",
+            "columnName": "lastCheckCount",
+            "affinity": "INTEGER",
+            "notNull": true,
+            "defaultValue": "0"
+          },
+          {
+            "fieldPath": "totalChapterNum",
+            "columnName": "totalChapterNum",
+            "affinity": "INTEGER",
+            "notNull": true,
+            "defaultValue": "0"
+          },
+          {
+            "fieldPath": "durChapterTitle",
+            "columnName": "durChapterTitle",
+            "affinity": "TEXT",
+            "notNull": false
+          },
+          {
+            "fieldPath": "durChapterIndex",
+            "columnName": "durChapterIndex",
+            "affinity": "INTEGER",
+            "notNull": true,
+            "defaultValue": "0"
+          },
+          {
+            "fieldPath": "durChapterPos",
+            "columnName": "durChapterPos",
+            "affinity": "INTEGER",
+            "notNull": true,
+            "defaultValue": "0"
+          },
+          {
+            "fieldPath": "durChapterTime",
+            "columnName": "durChapterTime",
+            "affinity": "INTEGER",
+            "notNull": true,
+            "defaultValue": "0"
+          },
+          {
+            "fieldPath": "wordCount",
+            "columnName": "wordCount",
+            "affinity": "TEXT",
+            "notNull": false
+          },
+          {
+            "fieldPath": "canUpdate",
+            "columnName": "canUpdate",
+            "affinity": "INTEGER",
+            "notNull": true,
+            "defaultValue": "1"
+          },
+          {
+            "fieldPath": "order",
+            "columnName": "order",
+            "affinity": "INTEGER",
+            "notNull": true,
+            "defaultValue": "0"
+          },
+          {
+            "fieldPath": "originOrder",
+            "columnName": "originOrder",
+            "affinity": "INTEGER",
+            "notNull": true,
+            "defaultValue": "0"
+          },
+          {
+            "fieldPath": "variable",
+            "columnName": "variable",
+            "affinity": "TEXT",
+            "notNull": false
+          },
+          {
+            "fieldPath": "readConfig",
+            "columnName": "readConfig",
+            "affinity": "TEXT",
+            "notNull": false
+          }
+        ],
+        "primaryKey": {
+          "columnNames": [
+            "bookUrl"
+          ],
+          "autoGenerate": false
+        },
+        "indices": [
+          {
+            "name": "index_books_name_author",
+            "unique": true,
+            "columnNames": [
+              "name",
+              "author"
+            ],
+            "orders": [],
+            "createSql": "CREATE UNIQUE INDEX IF NOT EXISTS `index_books_name_author` ON `${TABLE_NAME}` (`name`, `author`)"
+          }
+        ],
+        "foreignKeys": []
+      },
+      {
+        "tableName": "book_groups",
+        "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`groupId` INTEGER NOT NULL, `groupName` TEXT NOT NULL, `cover` TEXT, `order` INTEGER NOT NULL, `show` INTEGER NOT NULL, PRIMARY KEY(`groupId`))",
+        "fields": [
+          {
+            "fieldPath": "groupId",
+            "columnName": "groupId",
+            "affinity": "INTEGER",
+            "notNull": true
+          },
+          {
+            "fieldPath": "groupName",
+            "columnName": "groupName",
+            "affinity": "TEXT",
+            "notNull": true
+          },
+          {
+            "fieldPath": "cover",
+            "columnName": "cover",
+            "affinity": "TEXT",
+            "notNull": false
+          },
+          {
+            "fieldPath": "order",
+            "columnName": "order",
+            "affinity": "INTEGER",
+            "notNull": true
+          },
+          {
+            "fieldPath": "show",
+            "columnName": "show",
+            "affinity": "INTEGER",
+            "notNull": true
+          }
+        ],
+        "primaryKey": {
+          "columnNames": [
+            "groupId"
+          ],
+          "autoGenerate": false
+        },
+        "indices": [],
+        "foreignKeys": []
+      },
+      {
+        "tableName": "book_sources",
+        "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`bookSourceUrl` TEXT NOT NULL, `bookSourceName` TEXT NOT NULL, `bookSourceGroup` TEXT, `bookSourceType` INTEGER NOT NULL, `bookUrlPattern` TEXT, `customOrder` INTEGER NOT NULL, `enabled` INTEGER NOT NULL, `enabledExplore` INTEGER NOT NULL, `enabledReview` INTEGER, `enabledCookieJar` INTEGER DEFAULT 0, `concurrentRate` TEXT, `header` TEXT, `loginUrl` TEXT, `loginUi` TEXT, `loginCheckJs` TEXT, `coverDecodeJs` TEXT, `bookSourceComment` TEXT, `variableComment` TEXT, `lastUpdateTime` INTEGER NOT NULL, `respondTime` INTEGER NOT NULL, `weight` INTEGER NOT NULL, `exploreUrl` TEXT, `ruleExplore` TEXT, `searchUrl` TEXT, `ruleSearch` TEXT, `ruleBookInfo` TEXT, `ruleToc` TEXT, `ruleContent` TEXT, `ruleReview` TEXT, PRIMARY KEY(`bookSourceUrl`))",
+        "fields": [
+          {
+            "fieldPath": "bookSourceUrl",
+            "columnName": "bookSourceUrl",
+            "affinity": "TEXT",
+            "notNull": true
+          },
+          {
+            "fieldPath": "bookSourceName",
+            "columnName": "bookSourceName",
+            "affinity": "TEXT",
+            "notNull": true
+          },
+          {
+            "fieldPath": "bookSourceGroup",
+            "columnName": "bookSourceGroup",
+            "affinity": "TEXT",
+            "notNull": false
+          },
+          {
+            "fieldPath": "bookSourceType",
+            "columnName": "bookSourceType",
+            "affinity": "INTEGER",
+            "notNull": true
+          },
+          {
+            "fieldPath": "bookUrlPattern",
+            "columnName": "bookUrlPattern",
+            "affinity": "TEXT",
+            "notNull": false
+          },
+          {
+            "fieldPath": "customOrder",
+            "columnName": "customOrder",
+            "affinity": "INTEGER",
+            "notNull": true
+          },
+          {
+            "fieldPath": "enabled",
+            "columnName": "enabled",
+            "affinity": "INTEGER",
+            "notNull": true
+          },
+          {
+            "fieldPath": "enabledExplore",
+            "columnName": "enabledExplore",
+            "affinity": "INTEGER",
+            "notNull": true
+          },
+          {
+            "fieldPath": "enabledReview",
+            "columnName": "enabledReview",
+            "affinity": "INTEGER",
+            "notNull": false
+          },
+          {
+            "fieldPath": "enabledCookieJar",
+            "columnName": "enabledCookieJar",
+            "affinity": "INTEGER",
+            "notNull": false,
+            "defaultValue": "0"
+          },
+          {
+            "fieldPath": "concurrentRate",
+            "columnName": "concurrentRate",
+            "affinity": "TEXT",
+            "notNull": false
+          },
+          {
+            "fieldPath": "header",
+            "columnName": "header",
+            "affinity": "TEXT",
+            "notNull": false
+          },
+          {
+            "fieldPath": "loginUrl",
+            "columnName": "loginUrl",
+            "affinity": "TEXT",
+            "notNull": false
+          },
+          {
+            "fieldPath": "loginUi",
+            "columnName": "loginUi",
+            "affinity": "TEXT",
+            "notNull": false
+          },
+          {
+            "fieldPath": "loginCheckJs",
+            "columnName": "loginCheckJs",
+            "affinity": "TEXT",
+            "notNull": false
+          },
+          {
+            "fieldPath": "coverDecodeJs",
+            "columnName": "coverDecodeJs",
+            "affinity": "TEXT",
+            "notNull": false
+          },
+          {
+            "fieldPath": "bookSourceComment",
+            "columnName": "bookSourceComment",
+            "affinity": "TEXT",
+            "notNull": false
+          },
+          {
+            "fieldPath": "variableComment",
+            "columnName": "variableComment",
+            "affinity": "TEXT",
+            "notNull": false
+          },
+          {
+            "fieldPath": "lastUpdateTime",
+            "columnName": "lastUpdateTime",
+            "affinity": "INTEGER",
+            "notNull": true
+          },
+          {
+            "fieldPath": "respondTime",
+            "columnName": "respondTime",
+            "affinity": "INTEGER",
+            "notNull": true
+          },
+          {
+            "fieldPath": "weight",
+            "columnName": "weight",
+            "affinity": "INTEGER",
+            "notNull": true
+          },
+          {
+            "fieldPath": "exploreUrl",
+            "columnName": "exploreUrl",
+            "affinity": "TEXT",
+            "notNull": false
+          },
+          {
+            "fieldPath": "ruleExplore",
+            "columnName": "ruleExplore",
+            "affinity": "TEXT",
+            "notNull": false
+          },
+          {
+            "fieldPath": "searchUrl",
+            "columnName": "searchUrl",
+            "affinity": "TEXT",
+            "notNull": false
+          },
+          {
+            "fieldPath": "ruleSearch",
+            "columnName": "ruleSearch",
+            "affinity": "TEXT",
+            "notNull": false
+          },
+          {
+            "fieldPath": "ruleBookInfo",
+            "columnName": "ruleBookInfo",
+            "affinity": "TEXT",
+            "notNull": false
+          },
+          {
+            "fieldPath": "ruleToc",
+            "columnName": "ruleToc",
+            "affinity": "TEXT",
+            "notNull": false
+          },
+          {
+            "fieldPath": "ruleContent",
+            "columnName": "ruleContent",
+            "affinity": "TEXT",
+            "notNull": false
+          },
+          {
+            "fieldPath": "ruleReview",
+            "columnName": "ruleReview",
+            "affinity": "TEXT",
+            "notNull": false
+          }
+        ],
+        "primaryKey": {
+          "columnNames": [
+            "bookSourceUrl"
+          ],
+          "autoGenerate": false
+        },
+        "indices": [
+          {
+            "name": "index_book_sources_bookSourceUrl",
+            "unique": false,
+            "columnNames": [
+              "bookSourceUrl"
+            ],
+            "orders": [],
+            "createSql": "CREATE INDEX IF NOT EXISTS `index_book_sources_bookSourceUrl` ON `${TABLE_NAME}` (`bookSourceUrl`)"
+          }
+        ],
+        "foreignKeys": []
+      },
+      {
+        "tableName": "chapters",
+        "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`url` TEXT NOT NULL, `title` TEXT NOT NULL, `isVolume` INTEGER NOT NULL, `baseUrl` TEXT NOT NULL, `bookUrl` TEXT NOT NULL, `index` INTEGER NOT NULL, `isVip` INTEGER NOT NULL, `isPay` INTEGER NOT NULL, `resourceUrl` TEXT, `tag` TEXT, `start` INTEGER, `end` INTEGER, `startFragmentId` TEXT, `endFragmentId` TEXT, `variable` TEXT, PRIMARY KEY(`url`, `bookUrl`), FOREIGN KEY(`bookUrl`) REFERENCES `books`(`bookUrl`) ON UPDATE NO ACTION ON DELETE CASCADE )",
+        "fields": [
+          {
+            "fieldPath": "url",
+            "columnName": "url",
+            "affinity": "TEXT",
+            "notNull": true
+          },
+          {
+            "fieldPath": "title",
+            "columnName": "title",
+            "affinity": "TEXT",
+            "notNull": true
+          },
+          {
+            "fieldPath": "isVolume",
+            "columnName": "isVolume",
+            "affinity": "INTEGER",
+            "notNull": true
+          },
+          {
+            "fieldPath": "baseUrl",
+            "columnName": "baseUrl",
+            "affinity": "TEXT",
+            "notNull": true
+          },
+          {
+            "fieldPath": "bookUrl",
+            "columnName": "bookUrl",
+            "affinity": "TEXT",
+            "notNull": true
+          },
+          {
+            "fieldPath": "index",
+            "columnName": "index",
+            "affinity": "INTEGER",
+            "notNull": true
+          },
+          {
+            "fieldPath": "isVip",
+            "columnName": "isVip",
+            "affinity": "INTEGER",
+            "notNull": true
+          },
+          {
+            "fieldPath": "isPay",
+            "columnName": "isPay",
+            "affinity": "INTEGER",
+            "notNull": true
+          },
+          {
+            "fieldPath": "resourceUrl",
+            "columnName": "resourceUrl",
+            "affinity": "TEXT",
+            "notNull": false
+          },
+          {
+            "fieldPath": "tag",
+            "columnName": "tag",
+            "affinity": "TEXT",
+            "notNull": false
+          },
+          {
+            "fieldPath": "start",
+            "columnName": "start",
+            "affinity": "INTEGER",
+            "notNull": false
+          },
+          {
+            "fieldPath": "end",
+            "columnName": "end",
+            "affinity": "INTEGER",
+            "notNull": false
+          },
+          {
+            "fieldPath": "startFragmentId",
+            "columnName": "startFragmentId",
+            "affinity": "TEXT",
+            "notNull": false
+          },
+          {
+            "fieldPath": "endFragmentId",
+            "columnName": "endFragmentId",
+            "affinity": "TEXT",
+            "notNull": false
+          },
+          {
+            "fieldPath": "variable",
+            "columnName": "variable",
+            "affinity": "TEXT",
+            "notNull": false
+          }
+        ],
+        "primaryKey": {
+          "columnNames": [
+            "url",
+            "bookUrl"
+          ],
+          "autoGenerate": false
+        },
+        "indices": [
+          {
+            "name": "index_chapters_bookUrl",
+            "unique": false,
+            "columnNames": [
+              "bookUrl"
+            ],
+            "orders": [],
+            "createSql": "CREATE INDEX IF NOT EXISTS `index_chapters_bookUrl` ON `${TABLE_NAME}` (`bookUrl`)"
+          },
+          {
+            "name": "index_chapters_bookUrl_index",
+            "unique": true,
+            "columnNames": [
+              "bookUrl",
+              "index"
+            ],
+            "orders": [],
+            "createSql": "CREATE UNIQUE INDEX IF NOT EXISTS `index_chapters_bookUrl_index` ON `${TABLE_NAME}` (`bookUrl`, `index`)"
+          }
+        ],
+        "foreignKeys": [
+          {
+            "table": "books",
+            "onDelete": "CASCADE",
+            "onUpdate": "NO ACTION",
+            "columns": [
+              "bookUrl"
+            ],
+            "referencedColumns": [
+              "bookUrl"
+            ]
+          }
+        ]
+      },
+      {
+        "tableName": "replace_rules",
+        "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`id` INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, `name` TEXT NOT NULL DEFAULT '', `group` TEXT, `pattern` TEXT NOT NULL DEFAULT '', `replacement` TEXT NOT NULL DEFAULT '', `scope` TEXT, `scopeTitle` INTEGER NOT NULL DEFAULT 0, `scopeContent` INTEGER NOT NULL DEFAULT 1, `isEnabled` INTEGER NOT NULL DEFAULT 1, `isRegex` INTEGER NOT NULL DEFAULT 1, `timeoutMillisecond` INTEGER NOT NULL DEFAULT 3000, `sortOrder` INTEGER NOT NULL DEFAULT 0)",
+        "fields": [
+          {
+            "fieldPath": "id",
+            "columnName": "id",
+            "affinity": "INTEGER",
+            "notNull": true
+          },
+          {
+            "fieldPath": "name",
+            "columnName": "name",
+            "affinity": "TEXT",
+            "notNull": true,
+            "defaultValue": "''"
+          },
+          {
+            "fieldPath": "group",
+            "columnName": "group",
+            "affinity": "TEXT",
+            "notNull": false
+          },
+          {
+            "fieldPath": "pattern",
+            "columnName": "pattern",
+            "affinity": "TEXT",
+            "notNull": true,
+            "defaultValue": "''"
+          },
+          {
+            "fieldPath": "replacement",
+            "columnName": "replacement",
+            "affinity": "TEXT",
+            "notNull": true,
+            "defaultValue": "''"
+          },
+          {
+            "fieldPath": "scope",
+            "columnName": "scope",
+            "affinity": "TEXT",
+            "notNull": false
+          },
+          {
+            "fieldPath": "scopeTitle",
+            "columnName": "scopeTitle",
+            "affinity": "INTEGER",
+            "notNull": true,
+            "defaultValue": "0"
+          },
+          {
+            "fieldPath": "scopeContent",
+            "columnName": "scopeContent",
+            "affinity": "INTEGER",
+            "notNull": true,
+            "defaultValue": "1"
+          },
+          {
+            "fieldPath": "isEnabled",
+            "columnName": "isEnabled",
+            "affinity": "INTEGER",
+            "notNull": true,
+            "defaultValue": "1"
+          },
+          {
+            "fieldPath": "isRegex",
+            "columnName": "isRegex",
+            "affinity": "INTEGER",
+            "notNull": true,
+            "defaultValue": "1"
+          },
+          {
+            "fieldPath": "timeoutMillisecond",
+            "columnName": "timeoutMillisecond",
+            "affinity": "INTEGER",
+            "notNull": true,
+            "defaultValue": "3000"
+          },
+          {
+            "fieldPath": "order",
+            "columnName": "sortOrder",
+            "affinity": "INTEGER",
+            "notNull": true,
+            "defaultValue": "0"
+          }
+        ],
+        "primaryKey": {
+          "columnNames": [
+            "id"
+          ],
+          "autoGenerate": true
+        },
+        "indices": [
+          {
+            "name": "index_replace_rules_id",
+            "unique": false,
+            "columnNames": [
+              "id"
+            ],
+            "orders": [],
+            "createSql": "CREATE INDEX IF NOT EXISTS `index_replace_rules_id` ON `${TABLE_NAME}` (`id`)"
+          }
+        ],
+        "foreignKeys": []
+      },
+      {
+        "tableName": "searchBooks",
+        "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`bookUrl` TEXT NOT NULL, `origin` TEXT NOT NULL, `originName` TEXT NOT NULL, `type` INTEGER NOT NULL, `name` TEXT NOT NULL, `author` TEXT NOT NULL, `kind` TEXT, `coverUrl` TEXT, `intro` TEXT, `wordCount` TEXT, `latestChapterTitle` TEXT, `tocUrl` TEXT NOT NULL, `time` INTEGER NOT NULL, `variable` TEXT, `originOrder` INTEGER NOT NULL, PRIMARY KEY(`bookUrl`), FOREIGN KEY(`origin`) REFERENCES `book_sources`(`bookSourceUrl`) ON UPDATE NO ACTION ON DELETE CASCADE )",
+        "fields": [
+          {
+            "fieldPath": "bookUrl",
+            "columnName": "bookUrl",
+            "affinity": "TEXT",
+            "notNull": true
+          },
+          {
+            "fieldPath": "origin",
+            "columnName": "origin",
+            "affinity": "TEXT",
+            "notNull": true
+          },
+          {
+            "fieldPath": "originName",
+            "columnName": "originName",
+            "affinity": "TEXT",
+            "notNull": true
+          },
+          {
+            "fieldPath": "type",
+            "columnName": "type",
+            "affinity": "INTEGER",
+            "notNull": true
+          },
+          {
+            "fieldPath": "name",
+            "columnName": "name",
+            "affinity": "TEXT",
+            "notNull": true
+          },
+          {
+            "fieldPath": "author",
+            "columnName": "author",
+            "affinity": "TEXT",
+            "notNull": true
+          },
+          {
+            "fieldPath": "kind",
+            "columnName": "kind",
+            "affinity": "TEXT",
+            "notNull": false
+          },
+          {
+            "fieldPath": "coverUrl",
+            "columnName": "coverUrl",
+            "affinity": "TEXT",
+            "notNull": false
+          },
+          {
+            "fieldPath": "intro",
+            "columnName": "intro",
+            "affinity": "TEXT",
+            "notNull": false
+          },
+          {
+            "fieldPath": "wordCount",
+            "columnName": "wordCount",
+            "affinity": "TEXT",
+            "notNull": false
+          },
+          {
+            "fieldPath": "latestChapterTitle",
+            "columnName": "latestChapterTitle",
+            "affinity": "TEXT",
+            "notNull": false
+          },
+          {
+            "fieldPath": "tocUrl",
+            "columnName": "tocUrl",
+            "affinity": "TEXT",
+            "notNull": true
+          },
+          {
+            "fieldPath": "time",
+            "columnName": "time",
+            "affinity": "INTEGER",
+            "notNull": true
+          },
+          {
+            "fieldPath": "variable",
+            "columnName": "variable",
+            "affinity": "TEXT",
+            "notNull": false
+          },
+          {
+            "fieldPath": "originOrder",
+            "columnName": "originOrder",
+            "affinity": "INTEGER",
+            "notNull": true
+          }
+        ],
+        "primaryKey": {
+          "columnNames": [
+            "bookUrl"
+          ],
+          "autoGenerate": false
+        },
+        "indices": [
+          {
+            "name": "index_searchBooks_bookUrl",
+            "unique": true,
+            "columnNames": [
+              "bookUrl"
+            ],
+            "orders": [],
+            "createSql": "CREATE UNIQUE INDEX IF NOT EXISTS `index_searchBooks_bookUrl` ON `${TABLE_NAME}` (`bookUrl`)"
+          },
+          {
+            "name": "index_searchBooks_origin",
+            "unique": false,
+            "columnNames": [
+              "origin"
+            ],
+            "orders": [],
+            "createSql": "CREATE INDEX IF NOT EXISTS `index_searchBooks_origin` ON `${TABLE_NAME}` (`origin`)"
+          }
+        ],
+        "foreignKeys": [
+          {
+            "table": "book_sources",
+            "onDelete": "CASCADE",
+            "onUpdate": "NO ACTION",
+            "columns": [
+              "origin"
+            ],
+            "referencedColumns": [
+              "bookSourceUrl"
+            ]
+          }
+        ]
+      },
+      {
+        "tableName": "search_keywords",
+        "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`word` TEXT NOT NULL, `usage` INTEGER NOT NULL, `lastUseTime` INTEGER NOT NULL, PRIMARY KEY(`word`))",
+        "fields": [
+          {
+            "fieldPath": "word",
+            "columnName": "word",
+            "affinity": "TEXT",
+            "notNull": true
+          },
+          {
+            "fieldPath": "usage",
+            "columnName": "usage",
+            "affinity": "INTEGER",
+            "notNull": true
+          },
+          {
+            "fieldPath": "lastUseTime",
+            "columnName": "lastUseTime",
+            "affinity": "INTEGER",
+            "notNull": true
+          }
+        ],
+        "primaryKey": {
+          "columnNames": [
+            "word"
+          ],
+          "autoGenerate": false
+        },
+        "indices": [
+          {
+            "name": "index_search_keywords_word",
+            "unique": true,
+            "columnNames": [
+              "word"
+            ],
+            "orders": [],
+            "createSql": "CREATE UNIQUE INDEX IF NOT EXISTS `index_search_keywords_word` ON `${TABLE_NAME}` (`word`)"
+          }
+        ],
+        "foreignKeys": []
+      },
+      {
+        "tableName": "cookies",
+        "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`url` TEXT NOT NULL, `cookie` TEXT NOT NULL, PRIMARY KEY(`url`))",
+        "fields": [
+          {
+            "fieldPath": "url",
+            "columnName": "url",
+            "affinity": "TEXT",
+            "notNull": true
+          },
+          {
+            "fieldPath": "cookie",
+            "columnName": "cookie",
+            "affinity": "TEXT",
+            "notNull": true
+          }
+        ],
+        "primaryKey": {
+          "columnNames": [
+            "url"
+          ],
+          "autoGenerate": false
+        },
+        "indices": [
+          {
+            "name": "index_cookies_url",
+            "unique": true,
+            "columnNames": [
+              "url"
+            ],
+            "orders": [],
+            "createSql": "CREATE UNIQUE INDEX IF NOT EXISTS `index_cookies_url` ON `${TABLE_NAME}` (`url`)"
+          }
+        ],
+        "foreignKeys": []
+      },
+      {
+        "tableName": "rssSources",
+        "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`sourceUrl` TEXT NOT NULL, `sourceName` TEXT NOT NULL, `sourceIcon` TEXT NOT NULL, `sourceGroup` TEXT, `sourceComment` TEXT, `enabled` INTEGER NOT NULL, `variableComment` TEXT, `enabledCookieJar` INTEGER DEFAULT 0, `concurrentRate` TEXT, `header` TEXT, `loginUrl` TEXT, `loginUi` TEXT, `loginCheckJs` TEXT, `coverDecodeJs` TEXT, `sortUrl` TEXT, `singleUrl` INTEGER NOT NULL, `articleStyle` INTEGER NOT NULL, `ruleArticles` TEXT, `ruleNextPage` TEXT, `ruleTitle` TEXT, `rulePubDate` TEXT, `ruleDescription` TEXT, `ruleImage` TEXT, `ruleLink` TEXT, `ruleContent` TEXT, `style` TEXT, `enableJs` INTEGER NOT NULL, `loadWithBaseUrl` INTEGER NOT NULL, `lastUpdateTime` INTEGER NOT NULL DEFAULT 0, `customOrder` INTEGER NOT NULL, PRIMARY KEY(`sourceUrl`))",
+        "fields": [
+          {
+            "fieldPath": "sourceUrl",
+            "columnName": "sourceUrl",
+            "affinity": "TEXT",
+            "notNull": true
+          },
+          {
+            "fieldPath": "sourceName",
+            "columnName": "sourceName",
+            "affinity": "TEXT",
+            "notNull": true
+          },
+          {
+            "fieldPath": "sourceIcon",
+            "columnName": "sourceIcon",
+            "affinity": "TEXT",
+            "notNull": true
+          },
+          {
+            "fieldPath": "sourceGroup",
+            "columnName": "sourceGroup",
+            "affinity": "TEXT",
+            "notNull": false
+          },
+          {
+            "fieldPath": "sourceComment",
+            "columnName": "sourceComment",
+            "affinity": "TEXT",
+            "notNull": false
+          },
+          {
+            "fieldPath": "enabled",
+            "columnName": "enabled",
+            "affinity": "INTEGER",
+            "notNull": true
+          },
+          {
+            "fieldPath": "variableComment",
+            "columnName": "variableComment",
+            "affinity": "TEXT",
+            "notNull": false
+          },
+          {
+            "fieldPath": "enabledCookieJar",
+            "columnName": "enabledCookieJar",
+            "affinity": "INTEGER",
+            "notNull": false,
+            "defaultValue": "0"
+          },
+          {
+            "fieldPath": "concurrentRate",
+            "columnName": "concurrentRate",
+            "affinity": "TEXT",
+            "notNull": false
+          },
+          {
+            "fieldPath": "header",
+            "columnName": "header",
+            "affinity": "TEXT",
+            "notNull": false
+          },
+          {
+            "fieldPath": "loginUrl",
+            "columnName": "loginUrl",
+            "affinity": "TEXT",
+            "notNull": false
+          },
+          {
+            "fieldPath": "loginUi",
+            "columnName": "loginUi",
+            "affinity": "TEXT",
+            "notNull": false
+          },
+          {
+            "fieldPath": "loginCheckJs",
+            "columnName": "loginCheckJs",
+            "affinity": "TEXT",
+            "notNull": false
+          },
+          {
+            "fieldPath": "coverDecodeJs",
+            "columnName": "coverDecodeJs",
+            "affinity": "TEXT",
+            "notNull": false
+          },
+          {
+            "fieldPath": "sortUrl",
+            "columnName": "sortUrl",
+            "affinity": "TEXT",
+            "notNull": false
+          },
+          {
+            "fieldPath": "singleUrl",
+            "columnName": "singleUrl",
+            "affinity": "INTEGER",
+            "notNull": true
+          },
+          {
+            "fieldPath": "articleStyle",
+            "columnName": "articleStyle",
+            "affinity": "INTEGER",
+            "notNull": true
+          },
+          {
+            "fieldPath": "ruleArticles",
+            "columnName": "ruleArticles",
+            "affinity": "TEXT",
+            "notNull": false
+          },
+          {
+            "fieldPath": "ruleNextPage",
+            "columnName": "ruleNextPage",
+            "affinity": "TEXT",
+            "notNull": false
+          },
+          {
+            "fieldPath": "ruleTitle",
+            "columnName": "ruleTitle",
+            "affinity": "TEXT",
+            "notNull": false
+          },
+          {
+            "fieldPath": "rulePubDate",
+            "columnName": "rulePubDate",
+            "affinity": "TEXT",
+            "notNull": false
+          },
+          {
+            "fieldPath": "ruleDescription",
+            "columnName": "ruleDescription",
+            "affinity": "TEXT",
+            "notNull": false
+          },
+          {
+            "fieldPath": "ruleImage",
+            "columnName": "ruleImage",
+            "affinity": "TEXT",
+            "notNull": false
+          },
+          {
+            "fieldPath": "ruleLink",
+            "columnName": "ruleLink",
+            "affinity": "TEXT",
+            "notNull": false
+          },
+          {
+            "fieldPath": "ruleContent",
+            "columnName": "ruleContent",
+            "affinity": "TEXT",
+            "notNull": false
+          },
+          {
+            "fieldPath": "style",
+            "columnName": "style",
+            "affinity": "TEXT",
+            "notNull": false
+          },
+          {
+            "fieldPath": "enableJs",
+            "columnName": "enableJs",
+            "affinity": "INTEGER",
+            "notNull": true
+          },
+          {
+            "fieldPath": "loadWithBaseUrl",
+            "columnName": "loadWithBaseUrl",
+            "affinity": "INTEGER",
+            "notNull": true
+          },
+          {
+            "fieldPath": "lastUpdateTime",
+            "columnName": "lastUpdateTime",
+            "affinity": "INTEGER",
+            "notNull": true,
+            "defaultValue": "0"
+          },
+          {
+            "fieldPath": "customOrder",
+            "columnName": "customOrder",
+            "affinity": "INTEGER",
+            "notNull": true
+          }
+        ],
+        "primaryKey": {
+          "columnNames": [
+            "sourceUrl"
+          ],
+          "autoGenerate": false
+        },
+        "indices": [
+          {
+            "name": "index_rssSources_sourceUrl",
+            "unique": false,
+            "columnNames": [
+              "sourceUrl"
+            ],
+            "orders": [],
+            "createSql": "CREATE INDEX IF NOT EXISTS `index_rssSources_sourceUrl` ON `${TABLE_NAME}` (`sourceUrl`)"
+          }
+        ],
+        "foreignKeys": []
+      },
+      {
+        "tableName": "bookmarks",
+        "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`time` INTEGER NOT NULL, `bookName` TEXT NOT NULL, `bookAuthor` TEXT NOT NULL, `chapterIndex` INTEGER NOT NULL, `chapterPos` INTEGER NOT NULL, `chapterName` TEXT NOT NULL, `bookText` TEXT NOT NULL, `content` TEXT NOT NULL, PRIMARY KEY(`time`))",
+        "fields": [
+          {
+            "fieldPath": "time",
+            "columnName": "time",
+            "affinity": "INTEGER",
+            "notNull": true
+          },
+          {
+            "fieldPath": "bookName",
+            "columnName": "bookName",
+            "affinity": "TEXT",
+            "notNull": true
+          },
+          {
+            "fieldPath": "bookAuthor",
+            "columnName": "bookAuthor",
+            "affinity": "TEXT",
+            "notNull": true
+          },
+          {
+            "fieldPath": "chapterIndex",
+            "columnName": "chapterIndex",
+            "affinity": "INTEGER",
+            "notNull": true
+          },
+          {
+            "fieldPath": "chapterPos",
+            "columnName": "chapterPos",
+            "affinity": "INTEGER",
+            "notNull": true
+          },
+          {
+            "fieldPath": "chapterName",
+            "columnName": "chapterName",
+            "affinity": "TEXT",
+            "notNull": true
+          },
+          {
+            "fieldPath": "bookText",
+            "columnName": "bookText",
+            "affinity": "TEXT",
+            "notNull": true
+          },
+          {
+            "fieldPath": "content",
+            "columnName": "content",
+            "affinity": "TEXT",
+            "notNull": true
+          }
+        ],
+        "primaryKey": {
+          "columnNames": [
+            "time"
+          ],
+          "autoGenerate": false
+        },
+        "indices": [
+          {
+            "name": "index_bookmarks_bookName_bookAuthor",
+            "unique": false,
+            "columnNames": [
+              "bookName",
+              "bookAuthor"
+            ],
+            "orders": [],
+            "createSql": "CREATE INDEX IF NOT EXISTS `index_bookmarks_bookName_bookAuthor` ON `${TABLE_NAME}` (`bookName`, `bookAuthor`)"
+          }
+        ],
+        "foreignKeys": []
+      },
+      {
+        "tableName": "rssArticles",
+        "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`origin` TEXT NOT NULL, `sort` TEXT NOT NULL, `title` TEXT NOT NULL, `order` INTEGER NOT NULL, `link` TEXT NOT NULL, `pubDate` TEXT, `description` TEXT, `content` TEXT, `image` TEXT, `read` INTEGER NOT NULL, `variable` TEXT, PRIMARY KEY(`origin`, `link`))",
+        "fields": [
+          {
+            "fieldPath": "origin",
+            "columnName": "origin",
+            "affinity": "TEXT",
+            "notNull": true
+          },
+          {
+            "fieldPath": "sort",
+            "columnName": "sort",
+            "affinity": "TEXT",
+            "notNull": true
+          },
+          {
+            "fieldPath": "title",
+            "columnName": "title",
+            "affinity": "TEXT",
+            "notNull": true
+          },
+          {
+            "fieldPath": "order",
+            "columnName": "order",
+            "affinity": "INTEGER",
+            "notNull": true
+          },
+          {
+            "fieldPath": "link",
+            "columnName": "link",
+            "affinity": "TEXT",
+            "notNull": true
+          },
+          {
+            "fieldPath": "pubDate",
+            "columnName": "pubDate",
+            "affinity": "TEXT",
+            "notNull": false
+          },
+          {
+            "fieldPath": "description",
+            "columnName": "description",
+            "affinity": "TEXT",
+            "notNull": false
+          },
+          {
+            "fieldPath": "content",
+            "columnName": "content",
+            "affinity": "TEXT",
+            "notNull": false
+          },
+          {
+            "fieldPath": "image",
+            "columnName": "image",
+            "affinity": "TEXT",
+            "notNull": false
+          },
+          {
+            "fieldPath": "read",
+            "columnName": "read",
+            "affinity": "INTEGER",
+            "notNull": true
+          },
+          {
+            "fieldPath": "variable",
+            "columnName": "variable",
+            "affinity": "TEXT",
+            "notNull": false
+          }
+        ],
+        "primaryKey": {
+          "columnNames": [
+            "origin",
+            "link"
+          ],
+          "autoGenerate": false
+        },
+        "indices": [],
+        "foreignKeys": []
+      },
+      {
+        "tableName": "rssReadRecords",
+        "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`record` TEXT NOT NULL, `read` INTEGER NOT NULL, PRIMARY KEY(`record`))",
+        "fields": [
+          {
+            "fieldPath": "record",
+            "columnName": "record",
+            "affinity": "TEXT",
+            "notNull": true
+          },
+          {
+            "fieldPath": "read",
+            "columnName": "read",
+            "affinity": "INTEGER",
+            "notNull": true
+          }
+        ],
+        "primaryKey": {
+          "columnNames": [
+            "record"
+          ],
+          "autoGenerate": false
+        },
+        "indices": [],
+        "foreignKeys": []
+      },
+      {
+        "tableName": "rssStars",
+        "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`origin` TEXT NOT NULL, `sort` TEXT NOT NULL, `title` TEXT NOT NULL, `starTime` INTEGER NOT NULL, `link` TEXT NOT NULL, `pubDate` TEXT, `description` TEXT, `content` TEXT, `image` TEXT, `variable` TEXT, PRIMARY KEY(`origin`, `link`))",
+        "fields": [
+          {
+            "fieldPath": "origin",
+            "columnName": "origin",
+            "affinity": "TEXT",
+            "notNull": true
+          },
+          {
+            "fieldPath": "sort",
+            "columnName": "sort",
+            "affinity": "TEXT",
+            "notNull": true
+          },
+          {
+            "fieldPath": "title",
+            "columnName": "title",
+            "affinity": "TEXT",
+            "notNull": true
+          },
+          {
+            "fieldPath": "starTime",
+            "columnName": "starTime",
+            "affinity": "INTEGER",
+            "notNull": true
+          },
+          {
+            "fieldPath": "link",
+            "columnName": "link",
+            "affinity": "TEXT",
+            "notNull": true
+          },
+          {
+            "fieldPath": "pubDate",
+            "columnName": "pubDate",
+            "affinity": "TEXT",
+            "notNull": false
+          },
+          {
+            "fieldPath": "description",
+            "columnName": "description",
+            "affinity": "TEXT",
+            "notNull": false
+          },
+          {
+            "fieldPath": "content",
+            "columnName": "content",
+            "affinity": "TEXT",
+            "notNull": false
+          },
+          {
+            "fieldPath": "image",
+            "columnName": "image",
+            "affinity": "TEXT",
+            "notNull": false
+          },
+          {
+            "fieldPath": "variable",
+            "columnName": "variable",
+            "affinity": "TEXT",
+            "notNull": false
+          }
+        ],
+        "primaryKey": {
+          "columnNames": [
+            "origin",
+            "link"
+          ],
+          "autoGenerate": false
+        },
+        "indices": [],
+        "foreignKeys": []
+      },
+      {
+        "tableName": "txtTocRules",
+        "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`id` INTEGER NOT NULL, `name` TEXT NOT NULL, `rule` TEXT NOT NULL, `example` TEXT, `serialNumber` INTEGER NOT NULL, `enable` INTEGER NOT NULL, PRIMARY KEY(`id`))",
+        "fields": [
+          {
+            "fieldPath": "id",
+            "columnName": "id",
+            "affinity": "INTEGER",
+            "notNull": true
+          },
+          {
+            "fieldPath": "name",
+            "columnName": "name",
+            "affinity": "TEXT",
+            "notNull": true
+          },
+          {
+            "fieldPath": "rule",
+            "columnName": "rule",
+            "affinity": "TEXT",
+            "notNull": true
+          },
+          {
+            "fieldPath": "example",
+            "columnName": "example",
+            "affinity": "TEXT",
+            "notNull": false
+          },
+          {
+            "fieldPath": "serialNumber",
+            "columnName": "serialNumber",
+            "affinity": "INTEGER",
+            "notNull": true
+          },
+          {
+            "fieldPath": "enable",
+            "columnName": "enable",
+            "affinity": "INTEGER",
+            "notNull": true
+          }
+        ],
+        "primaryKey": {
+          "columnNames": [
+            "id"
+          ],
+          "autoGenerate": false
+        },
+        "indices": [],
+        "foreignKeys": []
+      },
+      {
+        "tableName": "readRecord",
+        "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`deviceId` TEXT NOT NULL, `bookName` TEXT NOT NULL, `readTime` INTEGER NOT NULL DEFAULT 0, `lastRead` INTEGER NOT NULL DEFAULT 0, PRIMARY KEY(`deviceId`, `bookName`))",
+        "fields": [
+          {
+            "fieldPath": "deviceId",
+            "columnName": "deviceId",
+            "affinity": "TEXT",
+            "notNull": true
+          },
+          {
+            "fieldPath": "bookName",
+            "columnName": "bookName",
+            "affinity": "TEXT",
+            "notNull": true
+          },
+          {
+            "fieldPath": "readTime",
+            "columnName": "readTime",
+            "affinity": "INTEGER",
+            "notNull": true,
+            "defaultValue": "0"
+          },
+          {
+            "fieldPath": "lastRead",
+            "columnName": "lastRead",
+            "affinity": "INTEGER",
+            "notNull": true,
+            "defaultValue": "0"
+          }
+        ],
+        "primaryKey": {
+          "columnNames": [
+            "deviceId",
+            "bookName"
+          ],
+          "autoGenerate": false
+        },
+        "indices": [],
+        "foreignKeys": []
+      },
+      {
+        "tableName": "httpTTS",
+        "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`id` INTEGER NOT NULL, `name` TEXT NOT NULL, `url` TEXT NOT NULL, `contentType` TEXT, `concurrentRate` TEXT DEFAULT '0', `loginUrl` TEXT, `loginUi` TEXT, `header` TEXT, `enabledCookieJar` INTEGER DEFAULT 0, `loginCheckJs` TEXT, `lastUpdateTime` INTEGER NOT NULL DEFAULT 0, PRIMARY KEY(`id`))",
+        "fields": [
+          {
+            "fieldPath": "id",
+            "columnName": "id",
+            "affinity": "INTEGER",
+            "notNull": true
+          },
+          {
+            "fieldPath": "name",
+            "columnName": "name",
+            "affinity": "TEXT",
+            "notNull": true
+          },
+          {
+            "fieldPath": "url",
+            "columnName": "url",
+            "affinity": "TEXT",
+            "notNull": true
+          },
+          {
+            "fieldPath": "contentType",
+            "columnName": "contentType",
+            "affinity": "TEXT",
+            "notNull": false
+          },
+          {
+            "fieldPath": "concurrentRate",
+            "columnName": "concurrentRate",
+            "affinity": "TEXT",
+            "notNull": false,
+            "defaultValue": "'0'"
+          },
+          {
+            "fieldPath": "loginUrl",
+            "columnName": "loginUrl",
+            "affinity": "TEXT",
+            "notNull": false
+          },
+          {
+            "fieldPath": "loginUi",
+            "columnName": "loginUi",
+            "affinity": "TEXT",
+            "notNull": false
+          },
+          {
+            "fieldPath": "header",
+            "columnName": "header",
+            "affinity": "TEXT",
+            "notNull": false
+          },
+          {
+            "fieldPath": "enabledCookieJar",
+            "columnName": "enabledCookieJar",
+            "affinity": "INTEGER",
+            "notNull": false,
+            "defaultValue": "0"
+          },
+          {
+            "fieldPath": "loginCheckJs",
+            "columnName": "loginCheckJs",
+            "affinity": "TEXT",
+            "notNull": false
+          },
+          {
+            "fieldPath": "lastUpdateTime",
+            "columnName": "lastUpdateTime",
+            "affinity": "INTEGER",
+            "notNull": true,
+            "defaultValue": "0"
+          }
+        ],
+        "primaryKey": {
+          "columnNames": [
+            "id"
+          ],
+          "autoGenerate": false
+        },
+        "indices": [],
+        "foreignKeys": []
+      },
+      {
+        "tableName": "caches",
+        "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`key` TEXT NOT NULL, `value` TEXT, `deadline` INTEGER NOT NULL, PRIMARY KEY(`key`))",
+        "fields": [
+          {
+            "fieldPath": "key",
+            "columnName": "key",
+            "affinity": "TEXT",
+            "notNull": true
+          },
+          {
+            "fieldPath": "value",
+            "columnName": "value",
+            "affinity": "TEXT",
+            "notNull": false
+          },
+          {
+            "fieldPath": "deadline",
+            "columnName": "deadline",
+            "affinity": "INTEGER",
+            "notNull": true
+          }
+        ],
+        "primaryKey": {
+          "columnNames": [
+            "key"
+          ],
+          "autoGenerate": false
+        },
+        "indices": [
+          {
+            "name": "index_caches_key",
+            "unique": true,
+            "columnNames": [
+              "key"
+            ],
+            "orders": [],
+            "createSql": "CREATE UNIQUE INDEX IF NOT EXISTS `index_caches_key` ON `${TABLE_NAME}` (`key`)"
+          }
+        ],
+        "foreignKeys": []
+      },
+      {
+        "tableName": "ruleSubs",
+        "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`id` INTEGER NOT NULL, `name` TEXT NOT NULL, `url` TEXT NOT NULL, `type` INTEGER NOT NULL, `customOrder` INTEGER NOT NULL, `autoUpdate` INTEGER NOT NULL, `update` INTEGER NOT NULL, PRIMARY KEY(`id`))",
+        "fields": [
+          {
+            "fieldPath": "id",
+            "columnName": "id",
+            "affinity": "INTEGER",
+            "notNull": true
+          },
+          {
+            "fieldPath": "name",
+            "columnName": "name",
+            "affinity": "TEXT",
+            "notNull": true
+          },
+          {
+            "fieldPath": "url",
+            "columnName": "url",
+            "affinity": "TEXT",
+            "notNull": true
+          },
+          {
+            "fieldPath": "type",
+            "columnName": "type",
+            "affinity": "INTEGER",
+            "notNull": true
+          },
+          {
+            "fieldPath": "customOrder",
+            "columnName": "customOrder",
+            "affinity": "INTEGER",
+            "notNull": true
+          },
+          {
+            "fieldPath": "autoUpdate",
+            "columnName": "autoUpdate",
+            "affinity": "INTEGER",
+            "notNull": true
+          },
+          {
+            "fieldPath": "update",
+            "columnName": "update",
+            "affinity": "INTEGER",
+            "notNull": true
+          }
+        ],
+        "primaryKey": {
+          "columnNames": [
+            "id"
+          ],
+          "autoGenerate": false
+        },
+        "indices": [],
+        "foreignKeys": []
+      },
+      {
+        "tableName": "keyboardAssists",
+        "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`type` INTEGER NOT NULL DEFAULT 0, `key` TEXT NOT NULL DEFAULT '', `value` TEXT NOT NULL DEFAULT '', `serialNo` INTEGER NOT NULL DEFAULT 0, PRIMARY KEY(`type`, `key`))",
+        "fields": [
+          {
+            "fieldPath": "type",
+            "columnName": "type",
+            "affinity": "INTEGER",
+            "notNull": true,
+            "defaultValue": "0"
+          },
+          {
+            "fieldPath": "key",
+            "columnName": "key",
+            "affinity": "TEXT",
+            "notNull": true,
+            "defaultValue": "''"
+          },
+          {
+            "fieldPath": "value",
+            "columnName": "value",
+            "affinity": "TEXT",
+            "notNull": true,
+            "defaultValue": "''"
+          },
+          {
+            "fieldPath": "serialNo",
+            "columnName": "serialNo",
+            "affinity": "INTEGER",
+            "notNull": true,
+            "defaultValue": "0"
+          }
+        ],
+        "primaryKey": {
+          "columnNames": [
+            "type",
+            "key"
+          ],
+          "autoGenerate": false
+        },
+        "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, '09020e77cffa237f9a98c62eed0a01f2')"
+    ]
+  }
+}

+ 2 - 5
app/src/main/java/io/legado/app/api/controller/BookController.kt

@@ -7,10 +7,7 @@ import io.legado.app.data.appDb
 import io.legado.app.data.entities.Book
 import io.legado.app.data.entities.Book
 import io.legado.app.data.entities.BookProgress
 import io.legado.app.data.entities.BookProgress
 import io.legado.app.data.entities.BookSource
 import io.legado.app.data.entities.BookSource
-import io.legado.app.help.AppWebDav
-import io.legado.app.help.BookHelp
-import io.legado.app.help.CacheManager
-import io.legado.app.help.ContentProcessor
+import io.legado.app.help.*
 import io.legado.app.help.glide.ImageLoader
 import io.legado.app.help.glide.ImageLoader
 import io.legado.app.model.BookCover
 import io.legado.app.model.BookCover
 import io.legado.app.model.localBook.LocalBook
 import io.legado.app.model.localBook.LocalBook
@@ -98,7 +95,7 @@ object BookController {
             }
             }
             val book = appDb.bookDao.getBook(bookUrl)
             val book = appDb.bookDao.getBook(bookUrl)
                 ?: return returnData.setErrorMsg("bookUrl不对")
                 ?: return returnData.setErrorMsg("bookUrl不对")
-            if (book.isLocalBook()) {
+            if (book.isLocal) {
                 val toc = LocalBook.getChapterList(book)
                 val toc = LocalBook.getChapterList(book)
                 appDb.bookChapterDao.delByBook(book.bookUrl)
                 appDb.bookChapterDao.delByBook(book.bookUrl)
                 appDb.bookChapterDao.insert(*toc.toTypedArray())
                 appDb.bookChapterDao.insert(*toc.toTypedArray())

+ 26 - 0
app/src/main/java/io/legado/app/constant/BookSourceType.kt

@@ -0,0 +1,26 @@
+package io.legado.app.constant
+
+import androidx.annotation.IntDef
+
+object BookSourceType {
+
+    const val default = 0           // 0 文本
+    const val audio = 1             // 1 音频
+    const val image = 2            // 2 图片
+    const val file = 3               // 3 只提供下载服务的网站
+
+    @Target(AnnotationTarget.VALUE_PARAMETER)
+    @Retention(AnnotationRetention.SOURCE)
+    @IntDef(default, audio, image, file)
+    annotation class Type
+
+    fun toBookType(sourceType: Int) {
+        when (sourceType) {
+            file -> BookType.text or BookType.webFile
+            image -> BookType.image
+            audio -> BookType.audio
+            else -> BookType.text
+        }
+    }
+
+}

+ 36 - 7
app/src/main/java/io/legado/app/constant/BookType.kt

@@ -2,20 +2,49 @@ package io.legado.app.constant
 
 
 import androidx.annotation.IntDef
 import androidx.annotation.IntDef
 
 
+/**
+ * 以二进制位来区分,可能一本书籍包含多个类型,每一位代表一个类型,数值为2的n次方
+ * 以二进制位来区分,数据库查询更高效
+ */
 object BookType {
 object BookType {
-    const val default = 0           // 0 文本
-    const val audio = 1             // 1 音频
-    const val image = 2            // 2 图片
-    const val file = 3               // 3 只提供下载服务的网站
-    const val local = "loc_book"
+    /**
+     * 8 文本
+     */
+    const val text = 0b1000
+
+    /**
+     * 16 音频
+     */
+    const val audio = 0b100000
+
+    /**
+     * 32 图片
+     */
+    const val image = 0b1000000
+
+    /**
+     * 64 只提供下载服务的网站
+     */
+    const val webFile = 0b10000000
+
+    /**
+     * 128 本地
+     */
+    const val local = 0b100000000
+
+
+    /**
+     * 本地书籍书源标志
+     */
+    const val localTag = "loc_book"
 
 
     /**
     /**
      * 书源已webDav::开头的书籍,可以从webDav更新或重新下载
      * 书源已webDav::开头的书籍,可以从webDav更新或重新下载
      */
      */
-    const val webDav = "webDav::"
+    const val webDavTag = "webDav::"
 
 
     @Target(AnnotationTarget.VALUE_PARAMETER)
     @Target(AnnotationTarget.VALUE_PARAMETER)
     @Retention(AnnotationRetention.SOURCE)
     @Retention(AnnotationRetention.SOURCE)
-    @IntDef(default, audio, image, file)
+    @IntDef(text, audio, image, webFile)
     annotation class Type
     annotation class Type
 }
 }

+ 2 - 1
app/src/main/java/io/legado/app/data/AppDatabase.kt

@@ -20,7 +20,7 @@ val appDb by lazy {
 }
 }
 
 
 @Database(
 @Database(
-    version = 54,
+    version = 55,
     exportSchema = true,
     exportSchema = true,
     entities = [Book::class, BookGroup::class, BookSource::class, BookChapter::class,
     entities = [Book::class, BookGroup::class, BookSource::class, BookChapter::class,
         ReplaceRule::class, SearchBook::class, SearchKeyword::class, Cookie::class,
         ReplaceRule::class, SearchBook::class, SearchKeyword::class, Cookie::class,
@@ -39,6 +39,7 @@ val appDb by lazy {
         AutoMigration(from = 51, to = 52),
         AutoMigration(from = 51, to = 52),
         AutoMigration(from = 52, to = 53),
         AutoMigration(from = 52, to = 53),
         AutoMigration(from = 53, to = 54),
         AutoMigration(from = 53, to = 54),
+        AutoMigration(from = 54, to = 55, spec = DatabaseMigrations.Migration_44_45::class)
     ]
     ]
 )
 )
 abstract class AppDatabase : RoomDatabase() {
 abstract class AppDatabase : RoomDatabase() {

+ 51 - 32
app/src/main/java/io/legado/app/data/DatabaseMigrations.kt

@@ -1,45 +1,24 @@
 package io.legado.app.data
 package io.legado.app.data
 
 
+import androidx.room.migration.AutoMigrationSpec
 import androidx.room.migration.Migration
 import androidx.room.migration.Migration
 import androidx.sqlite.db.SupportSQLiteDatabase
 import androidx.sqlite.db.SupportSQLiteDatabase
 import io.legado.app.constant.AppConst
 import io.legado.app.constant.AppConst
+import io.legado.app.constant.BookSourceType
+import io.legado.app.constant.BookType
 
 
 object DatabaseMigrations {
 object DatabaseMigrations {
 
 
     val migrations: Array<Migration> by lazy {
     val migrations: Array<Migration> by lazy {
         arrayOf(
         arrayOf(
-            migration_10_11,
-            migration_11_12,
-            migration_12_13,
-            migration_13_14,
-            migration_14_15,
-            migration_15_17,
-            migration_17_18,
-            migration_18_19,
-            migration_19_20,
-            migration_20_21,
-            migration_21_22,
-            migration_22_23,
-            migration_23_24,
-            migration_24_25,
-            migration_25_26,
-            migration_26_27,
-            migration_27_28,
-            migration_28_29,
-            migration_29_30,
-            migration_30_31,
-            migration_31_32,
-            migration_32_33,
-            migration_33_34,
-            migration_34_35,
-            migration_35_36,
-            migration_36_37,
-            migration_37_38,
-            migration_38_39,
-            migration_39_40,
-            migration_40_41,
-            migration_41_42,
-            migration_42_43
+            migration_10_11, migration_11_12, migration_12_13, migration_13_14,
+            migration_14_15, migration_15_17, migration_17_18, migration_18_19,
+            migration_19_20, migration_20_21, migration_21_22, migration_22_23,
+            migration_23_24, migration_24_25, migration_25_26, migration_26_27,
+            migration_27_28, migration_28_29, migration_29_30, migration_30_31,
+            migration_31_32, migration_32_33, migration_33_34, migration_34_35,
+            migration_35_36, migration_36_37, migration_37_38, migration_38_39,
+            migration_39_40, migration_40_41, migration_41_42, migration_42_43,
         )
         )
     }
     }
 
 
@@ -343,4 +322,44 @@ object DatabaseMigrations {
             database.execSQL("ALTER TABLE `chapters` ADD `isVolume` INTEGER NOT NULL DEFAULT 0")
             database.execSQL("ALTER TABLE `chapters` ADD `isVolume` INTEGER NOT NULL DEFAULT 0")
         }
         }
     }
     }
+
+
+    @Suppress("ClassName")
+    class Migration_44_45 : AutoMigrationSpec {
+
+        override fun onPostMigrate(db: SupportSQLiteDatabase) {
+            db.execSQL(
+                """
+                update books set type = ${BookType.audio}
+                where type = ${BookSourceType.audio}
+            """.trimIndent()
+            )
+            db.execSQL(
+                """
+                update books set type = ${BookType.image}
+                where type = ${BookSourceType.image}
+            """.trimIndent()
+            )
+            db.execSQL(
+                """
+                update books set type = ${BookType.webFile}
+                where type = ${BookSourceType.file}
+            """.trimIndent()
+            )
+            db.execSQL(
+                """
+                update books set type = ${BookType.text}
+                where type = ${BookSourceType.default}
+            """.trimIndent()
+            )
+            db.execSQL(
+                """
+                update books set type = type | ${BookType.local}
+                where origin = '${BookType.localTag}' or origin like '${BookType.webDavTag}%'
+            """.trimIndent()
+            )
+        }
+
+    }
+
 }
 }

+ 10 - 10
app/src/main/java/io/legado/app/data/dao/BookDao.kt

@@ -11,8 +11,8 @@ interface BookDao {
 
 
     @Query(
     @Query(
         """
         """
-        select * from books where type != ${BookType.audio} 
-        and origin != '${BookType.local}' and origin not like '${BookType.webDav}%'
+        select * from books where type & ${BookType.text} > 0
+        and type & ${BookType.local} = 0
         and ((SELECT sum(groupId) FROM book_groups where groupId > 0) & `group`) = 0
         and ((SELECT sum(groupId) FROM book_groups where groupId > 0) & `group`) = 0
         and (select show from book_groups where groupId = ${AppConst.bookGroupNetNoneId}) != 1
         and (select show from book_groups where groupId = ${AppConst.bookGroupNetNoneId}) != 1
         """
         """
@@ -22,15 +22,15 @@ interface BookDao {
     @Query("SELECT * FROM books order by durChapterTime desc")
     @Query("SELECT * FROM books order by durChapterTime desc")
     fun flowAll(): Flow<List<Book>>
     fun flowAll(): Flow<List<Book>>
 
 
-    @Query("SELECT * FROM books WHERE type = ${BookType.audio}")
+    @Query("SELECT * FROM books WHERE type & ${BookType.audio} > 0")
     fun flowAudio(): Flow<List<Book>>
     fun flowAudio(): Flow<List<Book>>
 
 
-    @Query("SELECT * FROM books WHERE origin = '${BookType.local}' or origin like '${BookType.webDav}%'")
+    @Query("SELECT * FROM books WHERE type & ${BookType.local} > 0")
     fun flowLocal(): Flow<List<Book>>
     fun flowLocal(): Flow<List<Book>>
 
 
     @Query(
     @Query(
         """
         """
-        select * from books where type != ${BookType.audio} and origin != '${BookType.local}' and origin not like '${BookType.webDav}%'
+        select * from books where type & ${BookType.audio} = 0 and type & ${BookType.local} = 0
         and ((SELECT sum(groupId) FROM book_groups where groupId > 0) & `group`) = 0
         and ((SELECT sum(groupId) FROM book_groups where groupId > 0) & `group`) = 0
         """
         """
     )
     )
@@ -38,13 +38,13 @@ interface BookDao {
 
 
     @Query(
     @Query(
         """
         """
-        select * from books where origin = '${BookType.local}' or origin like '${BookType.webDav}%'
+        select * from books where type & ${BookType.local} > 0
         and ((SELECT sum(groupId) FROM book_groups where groupId > 0) & `group`) = 0
         and ((SELECT sum(groupId) FROM book_groups where groupId > 0) & `group`) = 0
         """
         """
     )
     )
     fun flowLocalNoGroup(): Flow<List<Book>>
     fun flowLocalNoGroup(): Flow<List<Book>>
 
 
-    @Query("SELECT bookUrl FROM books WHERE origin = '${BookType.local}' or origin like '${BookType.webDav}%'")
+    @Query("SELECT bookUrl FROM books WHERE type & ${BookType.local} > 0")
     fun flowLocalUri(): Flow<List<String>>
     fun flowLocalUri(): Flow<List<String>>
 
 
     @Query("SELECT * FROM books WHERE (`group` & :group) > 0")
     @Query("SELECT * FROM books WHERE (`group` & :group) > 0")
@@ -65,13 +65,13 @@ interface BookDao {
     @Query("SELECT * FROM books WHERE name = :name and author = :author")
     @Query("SELECT * FROM books WHERE name = :name and author = :author")
     fun getBook(name: String, author: String): Book?
     fun getBook(name: String, author: String): Book?
 
 
-    @get:Query("select count(bookUrl) from books where (SELECT sum(groupId) FROM book_groups) & `group` = 0")
+    @get:Query("select count(bookUrl) from books where (SELECT sum(groupId) FROM book_groups)")
     val noGroupSize: Int
     val noGroupSize: Int
 
 
-    @get:Query("SELECT * FROM books where origin <> '${BookType.local}' and type = 0")
+    @get:Query("SELECT * FROM books where type & ${BookType.local} = 0")
     val webBooks: List<Book>
     val webBooks: List<Book>
 
 
-    @get:Query("SELECT * FROM books where origin <> '${BookType.local}' and canUpdate = 1")
+    @get:Query("SELECT * FROM books where type & ${BookType.local} = 0 and canUpdate = 1")
     val hasUpdateBooks: List<Book>
     val hasUpdateBooks: List<Book>
 
 
     @get:Query("SELECT * FROM books")
     @get:Query("SELECT * FROM books")

+ 6 - 6
app/src/main/java/io/legado/app/data/dao/BookGroupDao.kt

@@ -23,21 +23,21 @@ interface BookGroupDao {
         with const as (SELECT sum(groupId) sumGroupId FROM book_groups where groupId > 0)
         with const as (SELECT sum(groupId) sumGroupId FROM book_groups where groupId > 0)
         SELECT book_groups.* FROM book_groups, const where (groupId >= 0 and show > 0)
         SELECT book_groups.* FROM book_groups, const where (groupId >= 0 and show > 0)
         or (groupId = -1 and show > 0)
         or (groupId = -1 and show > 0)
-        or (groupId = -2 and show > 0 and (select count(bookUrl) from books where origin = '${BookType.local}') > 0)
-        or (groupId = -3 and show > 0 and (select count(bookUrl) from books where type = ${BookType.audio}) > 0)
+        or (groupId = -2 and show > 0 and (select count(bookUrl) from books where type & ${BookType.local} > 0) > 0)
+        or (groupId = -3 and show > 0 and (select count(bookUrl) from books where type & ${BookType.audio} > 0) > 0)
         or (groupId = -4 and show > 0 
         or (groupId = -4 and show > 0 
             and (
             and (
                 select count(bookUrl) from books 
                 select count(bookUrl) from books 
-                where type != '${BookType.audio}'
-                and origin != '${BookType.local}' 
+                where type & ${BookType.audio} = 0
+                and type & ${BookType.local} = 0
                 and const.sumGroupId & `group` = 0
                 and const.sumGroupId & `group` = 0
             ) > 0
             ) > 0
         )
         )
         or (groupId = -5 and show > 0 
         or (groupId = -5 and show > 0 
             and (
             and (
                 select count(bookUrl) from books 
                 select count(bookUrl) from books 
-                where type != '${BookType.audio}'
-                and origin = '${BookType.local}' 
+                where type & ${BookType.audio} = 0
+                and type & ${BookType.local} > 0
                 and const.sumGroupId & `group` = 0
                 and const.sumGroupId & `group` = 0
             ) > 0
             ) > 0
         )
         )

+ 10 - 32
app/src/main/java/io/legado/app/data/entities/Book.kt

@@ -10,11 +10,12 @@ import io.legado.app.help.BookHelp
 import io.legado.app.help.ContentProcessor
 import io.legado.app.help.ContentProcessor
 import io.legado.app.help.config.AppConfig
 import io.legado.app.help.config.AppConfig
 import io.legado.app.help.config.ReadBookConfig
 import io.legado.app.help.config.ReadBookConfig
+import io.legado.app.help.isEpub
+import io.legado.app.help.isImage
 import io.legado.app.model.ReadBook
 import io.legado.app.model.ReadBook
 import io.legado.app.utils.GSON
 import io.legado.app.utils.GSON
 import io.legado.app.utils.MD5Utils
 import io.legado.app.utils.MD5Utils
 import io.legado.app.utils.fromJsonObject
 import io.legado.app.utils.fromJsonObject
-import io.legado.app.utils.isUri
 import kotlinx.coroutines.runBlocking
 import kotlinx.coroutines.runBlocking
 import kotlinx.parcelize.IgnoredOnParcel
 import kotlinx.parcelize.IgnoredOnParcel
 import kotlinx.parcelize.Parcelize
 import kotlinx.parcelize.Parcelize
@@ -37,8 +38,8 @@ data class Book(
     @ColumnInfo(defaultValue = "")
     @ColumnInfo(defaultValue = "")
     var tocUrl: String = "",
     var tocUrl: String = "",
     // 书源URL(默认BookType.local)
     // 书源URL(默认BookType.local)
-    @ColumnInfo(defaultValue = "")
-    var origin: String = BookType.local,
+    @ColumnInfo(defaultValue = BookType.localTag)
+    var origin: String = BookType.localTag,
     //书源名称 or 本地书籍文件名
     //书源名称 or 本地书籍文件名
     @ColumnInfo(defaultValue = "")
     @ColumnInfo(defaultValue = "")
     var originName: String = "",
     var originName: String = "",
@@ -62,8 +63,8 @@ data class Book(
     var customIntro: String? = null,
     var customIntro: String? = null,
     // 自定义字符集名称(仅适用于本地书籍)
     // 自定义字符集名称(仅适用于本地书籍)
     var charset: String? = null,
     var charset: String? = null,
-    // 0:text 1:audio 3:image
-    @ColumnInfo(defaultValue = "0")
+    // 类型,详见BookType
+    @ColumnInfo(defaultValue = "1")
     var type: Int = 0,
     var type: Int = 0,
     // 自定义分组索引号
     // 自定义分组索引号
     @ColumnInfo(defaultValue = "0")
     @ColumnInfo(defaultValue = "0")
@@ -108,29 +109,6 @@ data class Book(
     var readConfig: ReadConfig? = null
     var readConfig: ReadConfig? = null
 ) : Parcelable, BaseBook {
 ) : Parcelable, BaseBook {
 
 
-    fun isLocalBook(): Boolean {
-        //通过判断书籍链接来判断http* file:// content://
-        //origin判断不可靠 http* BookType.local webDav::
-        return origin == BookType.local || bookUrl.isUri()
-    }
-
-    fun isLocalTxt(): Boolean {
-        return isLocalBook() && originName.endsWith(".txt", true)
-    }
-
-    fun isEpub(): Boolean {
-        return originName.endsWith(".epub", true)
-    }
-
-    fun isUmd(): Boolean {
-        return originName.endsWith(".umd", true)
-    }
-
-    @Suppress("unused")
-    fun isOnLineTxt(): Boolean {
-        return !isLocalBook() && type == 0
-    }
-
     override fun equals(other: Any?): Boolean {
     override fun equals(other: Any?): Boolean {
         if (other is Book) {
         if (other is Book) {
             return other.bookUrl == bookUrl
             return other.bookUrl == bookUrl
@@ -206,7 +184,7 @@ data class Book(
             return useReplaceRule
             return useReplaceRule
         }
         }
         //图片类书源 epub本地 默认关闭净化
         //图片类书源 epub本地 默认关闭净化
-        if (type == BookType.image || isEpub()) {
+        if (isImage || isEpub) {
             return false
             return false
         }
         }
         return AppConfig.replaceEnableDefault
         return AppConfig.replaceEnableDefault
@@ -226,7 +204,7 @@ data class Book(
 
 
     fun getPageAnim(): Int {
     fun getPageAnim(): Int {
         var pageAnim = config.pageAnim
         var pageAnim = config.pageAnim
-            ?: if (type == BookType.image) PageAnim.scrollPageAnim else ReadBookConfig.pageAnim
+            ?: if (type and BookType.image > 0) PageAnim.scrollPageAnim else ReadBookConfig.pageAnim
         if (pageAnim < 0) {
         if (pageAnim < 0) {
             pageAnim = ReadBookConfig.pageAnim
             pageAnim = ReadBookConfig.pageAnim
         }
         }
@@ -239,7 +217,7 @@ data class Book(
 
 
     fun getImageStyle(): String? {
     fun getImageStyle(): String? {
         return config.imageStyle
         return config.imageStyle
-            ?: if (type == BookType.image) imgStyleFull else null
+            ?: if (isImage) imgStyleFull else null
     }
     }
 
 
     fun setTtsEngine(ttsEngine: String?) {
     fun setTtsEngine(ttsEngine: String?) {
@@ -358,4 +336,4 @@ data class Book(
         @TypeConverter
         @TypeConverter
         fun stringToReadConfig(json: String?) = GSON.fromJsonObject<ReadConfig>(json).getOrNull()
         fun stringToReadConfig(json: String?) = GSON.fromJsonObject<ReadConfig>(json).getOrNull()
     }
     }
-}
+}

+ 2 - 2
app/src/main/java/io/legado/app/data/entities/BookSource.kt

@@ -4,7 +4,7 @@ import android.os.Parcelable
 import android.text.TextUtils
 import android.text.TextUtils
 import androidx.room.*
 import androidx.room.*
 import io.legado.app.constant.AppPattern
 import io.legado.app.constant.AppPattern
-import io.legado.app.constant.BookType
+import io.legado.app.constant.BookSourceType
 import io.legado.app.data.entities.rule.*
 import io.legado.app.data.entities.rule.*
 import io.legado.app.help.source.SourceAnalyzer
 import io.legado.app.help.source.SourceAnalyzer
 import io.legado.app.utils.GSON
 import io.legado.app.utils.GSON
@@ -29,7 +29,7 @@ data class BookSource(
     // 分组
     // 分组
     var bookSourceGroup: String? = null,
     var bookSourceGroup: String? = null,
     // 类型,0 文本,1 音频, 2 图片, 3 文件(指的是类似知轩藏书只提供下载的网站)
     // 类型,0 文本,1 音频, 2 图片, 3 文件(指的是类似知轩藏书只提供下载的网站)
-    @BookType.Type
+    @BookSourceType.Type
     var bookSourceType: Int = 0,
     var bookSourceType: Int = 0,
     // 详情页url正则
     // 详情页url正则
     var bookUrlPattern: String? = null,
     var bookUrlPattern: String? = null,

+ 2 - 1
app/src/main/java/io/legado/app/data/entities/SearchBook.kt

@@ -4,6 +4,7 @@ import android.content.Context
 import android.os.Parcelable
 import android.os.Parcelable
 import androidx.room.*
 import androidx.room.*
 import io.legado.app.R
 import io.legado.app.R
+import io.legado.app.constant.BookType
 import io.legado.app.utils.GSON
 import io.legado.app.utils.GSON
 import io.legado.app.utils.fromJsonObject
 import io.legado.app.utils.fromJsonObject
 import kotlinx.parcelize.IgnoredOnParcel
 import kotlinx.parcelize.IgnoredOnParcel
@@ -26,7 +27,7 @@ data class SearchBook(
     override var bookUrl: String = "",
     override var bookUrl: String = "",
     var origin: String = "",                     // 书源规则
     var origin: String = "",                     // 书源规则
     var originName: String = "",
     var originName: String = "",
-    var type: Int = 0,                          // @BookType
+    var type: Int = BookType.text,                          // @BookType
     override var name: String = "",
     override var name: String = "",
     override var author: String = "",
     override var author: String = "",
     override var kind: String? = null,
     override var kind: String? = null,

+ 65 - 0
app/src/main/java/io/legado/app/help/BookExtensions.kt

@@ -0,0 +1,65 @@
+package io.legado.app.help
+
+import io.legado.app.constant.BookSourceType
+import io.legado.app.constant.BookType
+import io.legado.app.data.entities.Book
+import io.legado.app.data.entities.BookSource
+
+
+val Book.isAudio: Boolean
+    get() {
+        return type and BookType.audio > 0
+    }
+
+val Book.isImage: Boolean
+    get() {
+        return type and BookType.image > 0
+    }
+
+val Book.isLocal: Boolean
+    get() {
+        return type and BookType.local > 0
+    }
+
+val Book.isLocalTxt: Boolean
+    get() {
+        return isLocal && originName.endsWith(".txt", true)
+    }
+
+val Book.isEpub: Boolean
+    get() {
+        return isLocal && originName.endsWith(".epub", true)
+    }
+
+val Book.isUmd: Boolean
+    get() {
+        return isLocal && originName.endsWith(".umd", true)
+    }
+
+val Book.isOnLineTxt: Boolean
+    get() {
+        return !isLocal && type and BookType.text > 0
+    }
+
+fun Book.upType() {
+    if (type < 8) {
+        type = when (type) {
+            BookSourceType.image -> BookType.image
+            BookSourceType.audio -> BookType.audio
+            BookSourceType.file -> BookType.webFile
+            else -> BookType.text
+        }
+        if (origin == "loc_book" || origin.startsWith(BookType.webDavTag)) {
+            type = type or BookType.local
+        }
+    }
+}
+
+fun BookSource.getBookType(): Int {
+    return when (bookSourceType) {
+        BookSourceType.file -> BookType.text or BookType.webFile
+        BookSourceType.image -> BookType.image
+        BookSourceType.audio -> BookType.audio
+        else -> BookType.text
+    }
+}

+ 5 - 5
app/src/main/java/io/legado/app/help/BookHelp.kt

@@ -60,7 +60,7 @@ object BookHelp {
             val originNames = ArrayList<String>()
             val originNames = ArrayList<String>()
             appDb.bookDao.all.forEach {
             appDb.bookDao.all.forEach {
                 bookFolderNames.add(it.getFolderName())
                 bookFolderNames.add(it.getFolderName())
-                if (it.isEpub()) originNames.add(it.originName)
+                if (it.isEpub) originNames.add(it.originName)
             }
             }
             downloadDir.getFile(cacheFolderName)
             downloadDir.getFile(cacheFolderName)
                 .listFiles()?.forEach { bookFile ->
                 .listFiles()?.forEach { bookFile ->
@@ -198,7 +198,7 @@ object BookHelp {
 
 
     fun getChapterFiles(book: Book): List<String> {
     fun getChapterFiles(book: Book): List<String> {
         val fileNameList = arrayListOf<String>()
         val fileNameList = arrayListOf<String>()
-        if (book.isLocalTxt()) {
+        if (book.isLocalTxt) {
             return fileNameList
             return fileNameList
         }
         }
         FileUtils.createFolderIfNotExist(
         FileUtils.createFolderIfNotExist(
@@ -214,7 +214,7 @@ object BookHelp {
      * 检测该章节是否下载
      * 检测该章节是否下载
      */
      */
     fun hasContent(book: Book, bookChapter: BookChapter): Boolean {
     fun hasContent(book: Book, bookChapter: BookChapter): Boolean {
-        return if (book.isLocalTxt()) {
+        return if (book.isLocalTxt) {
             true
             true
         } else {
         } else {
             downloadDir.exists(
             downloadDir.exists(
@@ -258,9 +258,9 @@ object BookHelp {
         if (file.exists()) {
         if (file.exists()) {
             return file.readText()
             return file.readText()
         }
         }
-        if (book.isLocalBook()) {
+        if (book.isLocal) {
             val string = LocalBook.getContent(book, bookChapter)
             val string = LocalBook.getContent(book, bookChapter)
-            if (string != null && book.isEpub()) {
+            if (string != null && book.isEpub) {
                 saveText(book, bookChapter, string)
                 saveText(book, bookChapter, string)
             }
             }
             return string
             return string

+ 3 - 4
app/src/main/java/io/legado/app/help/source/SourceAnalyzer.kt

@@ -4,13 +4,12 @@ import androidx.annotation.Keep
 import com.jayway.jsonpath.JsonPath
 import com.jayway.jsonpath.JsonPath
 import io.legado.app.constant.AppConst
 import io.legado.app.constant.AppConst
 import io.legado.app.constant.AppLog
 import io.legado.app.constant.AppLog
-import io.legado.app.constant.BookType
+import io.legado.app.constant.BookSourceType
 import io.legado.app.data.entities.BookSource
 import io.legado.app.data.entities.BookSource
 import io.legado.app.data.entities.rule.*
 import io.legado.app.data.entities.rule.*
 import io.legado.app.exception.NoStackTraceException
 import io.legado.app.exception.NoStackTraceException
 import io.legado.app.utils.*
 import io.legado.app.utils.*
 import java.io.InputStream
 import java.io.InputStream
-
 import java.util.regex.Pattern
 import java.util.regex.Pattern
 
 
 @Suppress("RegExpRedundantEscape")
 @Suppress("RegExpRedundantEscape")
@@ -91,7 +90,7 @@ object SourceAnalyzer {
                     searchUrl = toNewUrl(jsonItem.readString("ruleSearchUrl"))
                     searchUrl = toNewUrl(jsonItem.readString("ruleSearchUrl"))
                     exploreUrl = toNewUrls(jsonItem.readString("ruleFindUrl"))
                     exploreUrl = toNewUrls(jsonItem.readString("ruleFindUrl"))
                     bookSourceType =
                     bookSourceType =
-                        if (jsonItem.readString("bookSourceType") == "AUDIO") BookType.audio else BookType.default
+                        if (jsonItem.readString("bookSourceType") == "AUDIO") BookSourceType.audio else BookSourceType.default
                     enabled = jsonItem.readBool("enable") ?: true
                     enabled = jsonItem.readBool("enable") ?: true
                     if (exploreUrl.isNullOrBlank()) {
                     if (exploreUrl.isNullOrBlank()) {
                         enabledExplore = false
                         enabledExplore = false
@@ -226,7 +225,7 @@ object SourceAnalyzer {
         var bookSourceName: String = "",                // 名称
         var bookSourceName: String = "",                // 名称
         var bookSourceGroup: String? = null,            // 分组
         var bookSourceGroup: String? = null,            // 分组
         var bookSourceUrl: String = "",                 // 地址,包括 http/https
         var bookSourceUrl: String = "",                 // 地址,包括 http/https
-        var bookSourceType: Int = BookType.default,     // 类型,0 文本,1 音频
+        var bookSourceType: Int = BookSourceType.default,     // 类型,0 文本,1 音频
         var bookUrlPattern: String? = null,             // 详情页url正则
         var bookUrlPattern: String? = null,             // 详情页url正则
         var customOrder: Int = 0,                       // 手动排序编号
         var customOrder: Int = 0,                       // 手动排序编号
         var enabled: Boolean = true,                    // 是否启用
         var enabled: Boolean = true,                    // 是否启用

+ 4 - 2
app/src/main/java/io/legado/app/help/storage/ImportOldData.kt

@@ -3,6 +3,7 @@ package io.legado.app.help.storage
 import android.content.Context
 import android.content.Context
 import android.net.Uri
 import android.net.Uri
 import androidx.documentfile.provider.DocumentFile
 import androidx.documentfile.provider.DocumentFile
+import io.legado.app.constant.BookType
 import io.legado.app.data.appDb
 import io.legado.app.data.appDb
 import io.legado.app.data.entities.Book
 import io.legado.app.data.entities.Book
 import io.legado.app.data.entities.BookSource
 import io.legado.app.data.entities.BookSource
@@ -123,8 +124,9 @@ object ImportOldData {
             book.origin = jsonItem.readString("$.tag") ?: ""
             book.origin = jsonItem.readString("$.tag") ?: ""
             book.originName = jsonItem.readString("$.bookInfoBean.origin") ?: ""
             book.originName = jsonItem.readString("$.bookInfoBean.origin") ?: ""
             book.author = jsonItem.readString("$.bookInfoBean.author") ?: ""
             book.author = jsonItem.readString("$.bookInfoBean.author") ?: ""
-            book.type =
-                if (jsonItem.readString("$.bookInfoBean.bookSourceType") == "AUDIO") 1 else 0
+            val local = if (book.origin == "loc_book") BookType.local else 0
+            book.type = local or
+                    if (jsonItem.readString("$.bookInfoBean.bookSourceType") == "AUDIO") BookType.audio else BookType.text
             book.tocUrl = jsonItem.readString("$.bookInfoBean.chapterUrl") ?: book.bookUrl
             book.tocUrl = jsonItem.readString("$.bookInfoBean.chapterUrl") ?: book.bookUrl
             book.coverUrl = jsonItem.readString("$.bookInfoBean.coverUrl")
             book.coverUrl = jsonItem.readString("$.bookInfoBean.coverUrl")
             book.customCoverUrl = jsonItem.readString("$.customCoverPath")
             book.customCoverUrl = jsonItem.readString("$.customCoverPath")

+ 4 - 0
app/src/main/java/io/legado/app/help/storage/Restore.kt

@@ -16,6 +16,7 @@ import io.legado.app.help.LauncherIconHelp
 import io.legado.app.help.config.LocalConfig
 import io.legado.app.help.config.LocalConfig
 import io.legado.app.help.config.ReadBookConfig
 import io.legado.app.help.config.ReadBookConfig
 import io.legado.app.help.config.ThemeConfig
 import io.legado.app.help.config.ThemeConfig
+import io.legado.app.help.upType
 import io.legado.app.utils.*
 import io.legado.app.utils.*
 import kotlinx.coroutines.Dispatchers.IO
 import kotlinx.coroutines.Dispatchers.IO
 import kotlinx.coroutines.Dispatchers.Main
 import kotlinx.coroutines.Dispatchers.Main
@@ -65,6 +66,9 @@ object Restore {
     suspend fun restoreDatabase(path: String = Backup.backupPath) {
     suspend fun restoreDatabase(path: String = Backup.backupPath) {
         withContext(IO) {
         withContext(IO) {
             fileToListT<Book>(path, "bookshelf.json")?.let {
             fileToListT<Book>(path, "bookshelf.json")?.let {
+                it.forEach { book ->
+                    book.upType()
+                }
                 appDb.bookDao.insert(*it.toTypedArray())
                 appDb.bookDao.insert(*it.toTypedArray())
             }
             }
             fileToListT<Bookmark>(path, "bookmark.json")?.let {
             fileToListT<Bookmark>(path, "bookmark.json")?.let {

+ 7 - 5
app/src/main/java/io/legado/app/model/BookCover.kt

@@ -32,9 +32,13 @@ object BookCover {
         private set
         private set
     lateinit var defaultDrawable: Drawable
     lateinit var defaultDrawable: Drawable
         private set
         private set
-    var coverRuleConfig: CoverRuleConfig =
-        GSON.fromJsonObject<CoverRuleConfig>(CacheManager.get(coverRuleConfigKey)).getOrNull()
-            ?: DefaultData.coverRuleConfig
+    val coverRuleConfig: CoverRuleConfig
+        get() {
+            return GSON.fromJsonObject<CoverRuleConfig>(CacheManager.get(coverRuleConfigKey))
+                .getOrNull()
+                ?: DefaultData.coverRuleConfig
+        }
+
 
 
     init {
     init {
         upDefaultCover()
         upDefaultCover()
@@ -132,14 +136,12 @@ object BookCover {
     }
     }
 
 
     fun saveCoverRuleConfig(config: CoverRuleConfig) {
     fun saveCoverRuleConfig(config: CoverRuleConfig) {
-        coverRuleConfig = config
         val json = GSON.toJson(config)
         val json = GSON.toJson(config)
         CacheManager.put(coverRuleConfigKey, json)
         CacheManager.put(coverRuleConfigKey, json)
     }
     }
 
 
     fun delCoverRuleConfig() {
     fun delCoverRuleConfig() {
         CacheManager.delete(coverRuleConfigKey)
         CacheManager.delete(coverRuleConfigKey)
-        coverRuleConfig = DefaultData.coverRuleConfig
     }
     }
 
 
     data class CoverRuleConfig(
     data class CoverRuleConfig(

+ 2 - 1
app/src/main/java/io/legado/app/model/CacheBook.kt

@@ -10,6 +10,7 @@ import io.legado.app.data.entities.BookChapter
 import io.legado.app.data.entities.BookSource
 import io.legado.app.data.entities.BookSource
 import io.legado.app.exception.ConcurrentException
 import io.legado.app.exception.ConcurrentException
 import io.legado.app.help.BookHelp
 import io.legado.app.help.BookHelp
+import io.legado.app.help.isLocal
 import io.legado.app.model.webBook.WebBook
 import io.legado.app.model.webBook.WebBook
 import io.legado.app.service.CacheBookService
 import io.legado.app.service.CacheBookService
 import io.legado.app.utils.postEvent
 import io.legado.app.utils.postEvent
@@ -59,7 +60,7 @@ object CacheBook {
     }
     }
 
 
     fun start(context: Context, book: Book, start: Int, end: Int) {
     fun start(context: Context, book: Book, start: Int, end: Int) {
-        if (!book.isLocalBook()) {
+        if (!book.isLocal) {
             context.startService<CacheBookService> {
             context.startService<CacheBookService> {
                 action = IntentAction.start
                 action = IntentAction.start
                 putExtra("bookUrl", book.bookUrl)
                 putExtra("bookUrl", book.bookUrl)

+ 1 - 1
app/src/main/java/io/legado/app/model/Debug.kt

@@ -240,7 +240,7 @@ object Debug {
             .onSuccess {
             .onSuccess {
                 log(debugSource, "︽详情页解析完成")
                 log(debugSource, "︽详情页解析完成")
                 log(debugSource, showTime = false)
                 log(debugSource, showTime = false)
-                if (book.type != BookType.file) {
+                if (book.type and BookType.webFile == 0) {
                     tocDebug(scope, bookSource, book)
                     tocDebug(scope, bookSource, book)
                 } else {
                 } else {
                     log(debugSource, "≡文件类书源跳过解析目录", state = 1000)
                     log(debugSource, "≡文件类书源跳过解析目录", state = 1000)

+ 5 - 5
app/src/main/java/io/legado/app/model/ReadBook.kt

@@ -1,7 +1,6 @@
 package io.legado.app.model
 package io.legado.app.model
 
 
 import io.legado.app.constant.AppLog
 import io.legado.app.constant.AppLog
-import io.legado.app.constant.BookType
 import io.legado.app.data.appDb
 import io.legado.app.data.appDb
 import io.legado.app.data.entities.*
 import io.legado.app.data.entities.*
 import io.legado.app.help.AppWebDav
 import io.legado.app.help.AppWebDav
@@ -10,6 +9,7 @@ import io.legado.app.help.ContentProcessor
 import io.legado.app.help.config.AppConfig
 import io.legado.app.help.config.AppConfig
 import io.legado.app.help.config.ReadBookConfig
 import io.legado.app.help.config.ReadBookConfig
 import io.legado.app.help.coroutine.Coroutine
 import io.legado.app.help.coroutine.Coroutine
+import io.legado.app.help.isLocal
 import io.legado.app.model.webBook.WebBook
 import io.legado.app.model.webBook.WebBook
 import io.legado.app.service.BaseReadAloudService
 import io.legado.app.service.BaseReadAloudService
 import io.legado.app.ui.book.read.page.entities.TextChapter
 import io.legado.app.ui.book.read.page.entities.TextChapter
@@ -50,7 +50,7 @@ object ReadBook : CoroutineScope by MainScope() {
         chapterSize = appDb.bookChapterDao.getChapterCount(book.bookUrl)
         chapterSize = appDb.bookChapterDao.getChapterCount(book.bookUrl)
         durChapterIndex = book.durChapterIndex
         durChapterIndex = book.durChapterIndex
         durChapterPos = book.durChapterPos
         durChapterPos = book.durChapterPos
-        isLocalBook = book.origin == BookType.local
+        isLocalBook = book.isLocal
         clearTextChapter()
         clearTextChapter()
         callBack?.upMenuView()
         callBack?.upMenuView()
         callBack?.upPageAnim()
         callBack?.upPageAnim()
@@ -73,7 +73,7 @@ object ReadBook : CoroutineScope by MainScope() {
     }
     }
 
 
     fun upWebBook(book: Book) {
     fun upWebBook(book: Book) {
-        if (book.origin == BookType.local) {
+        if (book.isLocal) {
             bookSource = null
             bookSource = null
         } else {
         } else {
             appDb.bookSourceDao.getBookSource(book.origin)?.let {
             appDb.bookSourceDao.getBookSource(book.origin)?.let {
@@ -290,7 +290,7 @@ object ReadBook : CoroutineScope by MainScope() {
             return
             return
         }
         }
         book?.let { book ->
         book?.let { book ->
-            if (book.isLocalBook()) return
+            if (book.isLocal) return
             if (addLoading(index)) {
             if (addLoading(index)) {
                 Coroutine.async {
                 Coroutine.async {
                     appDb.bookChapterDao.getChapter(book.bookUrl, index)?.let { chapter ->
                     appDb.bookChapterDao.getChapter(book.bookUrl, index)?.let { chapter ->
@@ -318,7 +318,7 @@ object ReadBook : CoroutineScope by MainScope() {
         if (book != null && bookSource != null) {
         if (book != null && bookSource != null) {
             CacheBook.getOrCreate(bookSource, book).download(scope, chapter)
             CacheBook.getOrCreate(bookSource, book).download(scope, chapter)
         } else if (book != null) {
         } else if (book != null) {
-            val msg = if (book.isLocalBook()) "无内容" else "没有书源"
+            val msg = if (book.isLocal) "无内容" else "没有书源"
             contentLoadFinish(
             contentLoadFinish(
                 book, chapter, "加载正文失败\n$msg", resetPageOffset = resetPageOffset
                 book, chapter, "加载正文失败\n$msg", resetPageOffset = resetPageOffset
             ) {
             ) {

+ 16 - 15
app/src/main/java/io/legado/app/model/localBook/LocalBook.kt

@@ -14,17 +14,19 @@ import io.legado.app.data.entities.Book
 import io.legado.app.data.entities.BookChapter
 import io.legado.app.data.entities.BookChapter
 import io.legado.app.exception.NoStackTraceException
 import io.legado.app.exception.NoStackTraceException
 import io.legado.app.exception.TocEmptyException
 import io.legado.app.exception.TocEmptyException
+import io.legado.app.help.AppWebDav
 import io.legado.app.help.BookHelp
 import io.legado.app.help.BookHelp
 import io.legado.app.help.config.AppConfig
 import io.legado.app.help.config.AppConfig
-import io.legado.app.help.AppWebDav
+import io.legado.app.help.isEpub
+import io.legado.app.help.isUmd
 import io.legado.app.lib.webdav.WebDav
 import io.legado.app.lib.webdav.WebDav
 import io.legado.app.model.analyzeRule.AnalyzeUrl
 import io.legado.app.model.analyzeRule.AnalyzeUrl
 import io.legado.app.utils.*
 import io.legado.app.utils.*
+import kotlinx.coroutines.runBlocking
 import org.jsoup.nodes.Entities
 import org.jsoup.nodes.Entities
 import splitties.init.appCtx
 import splitties.init.appCtx
 import java.io.*
 import java.io.*
 import java.util.regex.Pattern
 import java.util.regex.Pattern
-import kotlinx.coroutines.runBlocking
 
 
 /**
 /**
  * 书籍文件导入 目录正文解析
  * 书籍文件导入 目录正文解析
@@ -65,10 +67,10 @@ object LocalBook {
     @Throws(Exception::class)
     @Throws(Exception::class)
     fun getChapterList(book: Book): ArrayList<BookChapter> {
     fun getChapterList(book: Book): ArrayList<BookChapter> {
         val chapters = when {
         val chapters = when {
-            book.isEpub() -> {
+            book.isEpub -> {
                 EpubFile.getChapterList(book)
                 EpubFile.getChapterList(book)
             }
             }
-            book.isUmd() -> {
+            book.isUmd -> {
                 UmdFile.getChapterList(book)
                 UmdFile.getChapterList(book)
             }
             }
             else -> {
             else -> {
@@ -89,10 +91,10 @@ object LocalBook {
     fun getContent(book: Book, chapter: BookChapter): String? {
     fun getContent(book: Book, chapter: BookChapter): String? {
         val content = try {
         val content = try {
             when {
             when {
-                book.isEpub() -> {
+                book.isEpub -> {
                     EpubFile.getContent(book, chapter)
                     EpubFile.getContent(book, chapter)
                 }
                 }
-                book.isUmd() -> {
+                book.isUmd -> {
                     UmdFile.getContent(book, chapter)
                     UmdFile.getContent(book, chapter)
                 }
                 }
                 else -> {
                 else -> {
@@ -146,6 +148,7 @@ object LocalBook {
         if (book == null) {
         if (book == null) {
             val nameAuthor = analyzeNameAuthor(fileName)
             val nameAuthor = analyzeNameAuthor(fileName)
             book = Book(
             book = Book(
+                type = BookType.text and BookType.local,
                 bookUrl = bookUrl,
                 bookUrl = bookUrl,
                 name = nameAuthor.first,
                 name = nameAuthor.first,
                 author = nameAuthor.second,
                 author = nameAuthor.second,
@@ -158,8 +161,8 @@ object LocalBook {
                 latestChapterTime = updateTime,
                 latestChapterTime = updateTime,
                 order = appDb.bookDao.minOrder - 1
                 order = appDb.bookDao.minOrder - 1
             )
             )
-            if (book.isEpub()) EpubFile.upBookInfo(book)
-            if (book.isUmd()) UmdFile.upBookInfo(book)
+            if (book.isEpub) EpubFile.upBookInfo(book)
+            if (book.isUmd) UmdFile.upBookInfo(book)
             appDb.bookDao.insert(book)
             appDb.bookDao.insert(book)
         } else {
         } else {
             //已有书籍说明是更新,删除原有目录
             //已有书籍说明是更新,删除原有目录
@@ -304,22 +307,20 @@ object LocalBook {
 
 
     //下载book.remoteUrl对应的远程文件并更新bookUrl 返回inputStream
     //下载book.remoteUrl对应的远程文件并更新bookUrl 返回inputStream
     private fun downloadRemoteBook(localBook: Book): InputStream? {
     private fun downloadRemoteBook(localBook: Book): InputStream? {
-        if (localBook.origin == BookType.local) return null
         //webDav::${http}
         //webDav::${http}
-        val webDavUrl = localBook.origin.split("::").getOrNull(1)
-        webDavUrl ?: return null
+        if (!localBook.origin.startsWith(BookType.webDavTag)) return null
+        val webDavUrl = localBook.origin.substring(8)
+        if (webDavUrl.isBlank()) return null
         try {
         try {
             val uri = AppWebDav.authorization?.let {
             val uri = AppWebDav.authorization?.let {
                 val webdav = WebDav(webDavUrl, it)
                 val webdav = WebDav(webDavUrl, it)
                 runBlocking {
                 runBlocking {
-                    webdav.downloadInputStream().let { inputStream ->
-                        saveBookFile(inputStream, localBook.originName)
-                    }
+                    saveBookFile(webdav.downloadInputStream(), localBook.originName)
                 }
                 }
             }
             }
             return uri?.let {
             return uri?.let {
                 localBook.bookUrl = if (it.isContentScheme()) it.toString()
                 localBook.bookUrl = if (it.isContentScheme()) it.toString()
-                  else it.path!!
+                else it.path!!
                 localBook.save()
                 localBook.save()
                 it.inputStream(appCtx)
                 it.inputStream(appCtx)
             }
             }

+ 1 - 1
app/src/main/java/io/legado/app/model/webBook/BookInfo.kt

@@ -137,7 +137,7 @@ object BookInfo {
             Debug.log(bookSource.bookSourceUrl, "└${e.localizedMessage}")
             Debug.log(bookSource.bookSourceUrl, "└${e.localizedMessage}")
             DebugLog.e("获取封面出错", e)
             DebugLog.e("获取封面出错", e)
         }
         }
-        if (book.type != BookType.file) {
+        if (book.type and BookType.webFile == 0) {
             coroutineContext.ensureActive()
             coroutineContext.ensureActive()
             Debug.log(bookSource.bookSourceUrl, "┌获取目录链接")
             Debug.log(bookSource.bookSourceUrl, "┌获取目录链接")
             book.tocUrl = analyzeRule.getString(infoRule.tocUrl, isUrl = true)
             book.tocUrl = analyzeRule.getString(infoRule.tocUrl, isUrl = true)

+ 3 - 2
app/src/main/java/io/legado/app/model/webBook/BookList.kt

@@ -7,6 +7,7 @@ import io.legado.app.data.entities.SearchBook
 import io.legado.app.data.entities.rule.BookListRule
 import io.legado.app.data.entities.rule.BookListRule
 import io.legado.app.exception.NoStackTraceException
 import io.legado.app.exception.NoStackTraceException
 import io.legado.app.help.BookHelp
 import io.legado.app.help.BookHelp
+import io.legado.app.help.getBookType
 import io.legado.app.model.Debug
 import io.legado.app.model.Debug
 import io.legado.app.model.analyzeRule.AnalyzeRule
 import io.legado.app.model.analyzeRule.AnalyzeRule
 import io.legado.app.model.analyzeRule.AnalyzeUrl
 import io.legado.app.model.analyzeRule.AnalyzeUrl
@@ -138,7 +139,7 @@ object BookList {
         book.origin = bookSource.bookSourceUrl
         book.origin = bookSource.bookSourceUrl
         book.originName = bookSource.bookSourceName
         book.originName = bookSource.bookSourceName
         book.originOrder = bookSource.customOrder
         book.originOrder = bookSource.customOrder
-        book.type = bookSource.bookSourceType
+        book.type = bookSource.getBookType()
         analyzeRule.ruleData = book
         analyzeRule.ruleData = book
         BookInfo.analyzeBookInfo(
         BookInfo.analyzeBookInfo(
             book,
             book,
@@ -173,9 +174,9 @@ object BookList {
         ruleLastChapter: List<AnalyzeRule.SourceRule>
         ruleLastChapter: List<AnalyzeRule.SourceRule>
     ): SearchBook? {
     ): SearchBook? {
         val searchBook = SearchBook(variable = variable)
         val searchBook = SearchBook(variable = variable)
+        searchBook.type = bookSource.getBookType()
         searchBook.origin = bookSource.bookSourceUrl
         searchBook.origin = bookSource.bookSourceUrl
         searchBook.originName = bookSource.bookSourceName
         searchBook.originName = bookSource.bookSourceName
-        searchBook.type = bookSource.bookSourceType
         searchBook.originOrder = bookSource.customOrder
         searchBook.originOrder = bookSource.customOrder
         analyzeRule.ruleData = searchBook
         analyzeRule.ruleData = searchBook
         analyzeRule.setContent(item)
         analyzeRule.setContent(item)

+ 3 - 2
app/src/main/java/io/legado/app/model/webBook/WebBook.kt

@@ -6,6 +6,7 @@ import io.legado.app.data.entities.BookSource
 import io.legado.app.data.entities.SearchBook
 import io.legado.app.data.entities.SearchBook
 import io.legado.app.exception.NoStackTraceException
 import io.legado.app.exception.NoStackTraceException
 import io.legado.app.help.coroutine.Coroutine
 import io.legado.app.help.coroutine.Coroutine
+import io.legado.app.help.getBookType
 import io.legado.app.help.http.StrResponse
 import io.legado.app.help.http.StrResponse
 import io.legado.app.model.Debug
 import io.legado.app.model.Debug
 import io.legado.app.model.analyzeRule.AnalyzeUrl
 import io.legado.app.model.analyzeRule.AnalyzeUrl
@@ -134,7 +135,7 @@ object WebBook {
         book: Book,
         book: Book,
         canReName: Boolean = true,
         canReName: Boolean = true,
     ): Book {
     ): Book {
-        book.type = bookSource.bookSourceType
+        book.type = bookSource.getBookType()
         if (!book.infoHtml.isNullOrEmpty()) {
         if (!book.infoHtml.isNullOrEmpty()) {
             BookInfo.analyzeBookInfo(
             BookInfo.analyzeBookInfo(
                 bookSource = bookSource,
                 bookSource = bookSource,
@@ -189,7 +190,7 @@ object WebBook {
         bookSource: BookSource,
         bookSource: BookSource,
         book: Book,
         book: Book,
     ): Result<List<BookChapter>> {
     ): Result<List<BookChapter>> {
-        book.type = bookSource.bookSourceType
+        book.type = bookSource.getBookType()
         return kotlin.runCatching {
         return kotlin.runCatching {
             if (book.bookUrl == book.tocUrl && !book.tocHtml.isNullOrEmpty()) {
             if (book.bookUrl == book.tocUrl && !book.tocHtml.isNullOrEmpty()) {
                 BookChapterList.analyzeChapterList(
                 BookChapterList.analyzeChapterList(

+ 2 - 2
app/src/main/java/io/legado/app/service/CheckSourceService.kt

@@ -6,7 +6,7 @@ import com.script.ScriptException
 import io.legado.app.R
 import io.legado.app.R
 import io.legado.app.base.BaseService
 import io.legado.app.base.BaseService
 import io.legado.app.constant.AppConst
 import io.legado.app.constant.AppConst
-import io.legado.app.constant.BookType
+import io.legado.app.constant.BookSourceType
 import io.legado.app.constant.EventBus
 import io.legado.app.constant.EventBus
 import io.legado.app.constant.IntentAction
 import io.legado.app.constant.IntentAction
 import io.legado.app.data.appDb
 import io.legado.app.data.appDb
@@ -214,7 +214,7 @@ class CheckSourceService : BaseService() {
                 }
                 }
                 //校验目录
                 //校验目录
                 if (CheckSource.checkCategory &&
                 if (CheckSource.checkCategory &&
-                    source.bookSourceType != BookType.file
+                    source.bookSourceType != BookSourceType.file
                 ) {
                 ) {
                     val toc = WebBook.getChapterListAwait(source, mBook).getOrThrow()
                     val toc = WebBook.getChapterListAwait(source, mBook).getOrThrow()
                     val nextChapterUrl = toc.getOrNull(1)?.url ?: toc.first().url
                     val nextChapterUrl = toc.getOrNull(1)?.url ?: toc.first().url

+ 2 - 2
app/src/main/java/io/legado/app/ui/book/audio/AudioPlayActivity.kt

@@ -12,7 +12,6 @@ import androidx.activity.viewModels
 import androidx.compose.runtime.mutableStateOf
 import androidx.compose.runtime.mutableStateOf
 import io.legado.app.R
 import io.legado.app.R
 import io.legado.app.base.VMBaseActivity
 import io.legado.app.base.VMBaseActivity
-import io.legado.app.constant.BookType
 import io.legado.app.constant.EventBus
 import io.legado.app.constant.EventBus
 import io.legado.app.constant.Status
 import io.legado.app.constant.Status
 import io.legado.app.constant.Theme
 import io.legado.app.constant.Theme
@@ -22,6 +21,7 @@ import io.legado.app.data.entities.BookChapter
 import io.legado.app.data.entities.BookSource
 import io.legado.app.data.entities.BookSource
 import io.legado.app.databinding.ActivityAudioPlayBinding
 import io.legado.app.databinding.ActivityAudioPlayBinding
 import io.legado.app.help.config.AppConfig
 import io.legado.app.help.config.AppConfig
+import io.legado.app.help.isAudio
 import io.legado.app.lib.dialogs.alert
 import io.legado.app.lib.dialogs.alert
 import io.legado.app.model.AudioPlay
 import io.legado.app.model.AudioPlay
 import io.legado.app.model.BookCover
 import io.legado.app.model.BookCover
@@ -207,7 +207,7 @@ class AudioPlayActivity :
         get() = AudioPlay.book
         get() = AudioPlay.book
 
 
     override fun changeTo(source: BookSource, book: Book, toc: List<BookChapter>) {
     override fun changeTo(source: BookSource, book: Book, toc: List<BookChapter>) {
-        if (book.type == BookType.audio) {
+        if (book.isAudio) {
             viewModel.changeTo(source, book, toc)
             viewModel.changeTo(source, book, toc)
         } else {
         } else {
             AudioPlay.stop(this)
             AudioPlay.stop(this)

+ 2 - 2
app/src/main/java/io/legado/app/ui/book/cache/CacheActivity.kt

@@ -10,7 +10,6 @@ import io.legado.app.R
 import io.legado.app.base.VMBaseActivity
 import io.legado.app.base.VMBaseActivity
 import io.legado.app.constant.AppConst
 import io.legado.app.constant.AppConst
 import io.legado.app.constant.AppConst.charsets
 import io.legado.app.constant.AppConst.charsets
-import io.legado.app.constant.BookType
 import io.legado.app.constant.EventBus
 import io.legado.app.constant.EventBus
 import io.legado.app.constant.PreferKey
 import io.legado.app.constant.PreferKey
 import io.legado.app.data.appDb
 import io.legado.app.data.appDb
@@ -21,6 +20,7 @@ import io.legado.app.databinding.ActivityCacheBookBinding
 import io.legado.app.databinding.DialogEditTextBinding
 import io.legado.app.databinding.DialogEditTextBinding
 import io.legado.app.help.BookHelp
 import io.legado.app.help.BookHelp
 import io.legado.app.help.config.AppConfig
 import io.legado.app.help.config.AppConfig
+import io.legado.app.help.isAudio
 import io.legado.app.lib.dialogs.SelectItem
 import io.legado.app.lib.dialogs.SelectItem
 import io.legado.app.lib.dialogs.alert
 import io.legado.app.lib.dialogs.alert
 import io.legado.app.lib.dialogs.selector
 import io.legado.app.lib.dialogs.selector
@@ -166,7 +166,7 @@ class CacheActivity : VMBaseActivity<ActivityCacheBookBinding, CacheViewModel>()
                 else -> appDb.bookDao.flowByGroup(groupId)
                 else -> appDb.bookDao.flowByGroup(groupId)
             }.conflate().map { books ->
             }.conflate().map { books ->
                 val booksDownload = books.filter {
                 val booksDownload = books.filter {
-                    it.type == BookType.default || it.type == BookType.image
+                    !it.isAudio
                 }
                 }
                 when (getPrefInt(PreferKey.bookshelfSort)) {
                 when (getPrefInt(PreferKey.bookshelfSort)) {
                     1 -> booksDownload.sortedByDescending { it.latestChapterTime }
                     1 -> booksDownload.sortedByDescending { it.latestChapterTime }

+ 4 - 3
app/src/main/java/io/legado/app/ui/book/cache/CacheAdapter.kt

@@ -10,6 +10,7 @@ import io.legado.app.base.adapter.ItemViewHolder
 import io.legado.app.base.adapter.RecyclerAdapter
 import io.legado.app.base.adapter.RecyclerAdapter
 import io.legado.app.data.entities.Book
 import io.legado.app.data.entities.Book
 import io.legado.app.databinding.ItemDownloadBinding
 import io.legado.app.databinding.ItemDownloadBinding
+import io.legado.app.help.isLocal
 import io.legado.app.model.CacheBook
 import io.legado.app.model.CacheBook
 import io.legado.app.utils.gone
 import io.legado.app.utils.gone
 import io.legado.app.utils.visible
 import io.legado.app.utils.visible
@@ -33,7 +34,7 @@ class CacheAdapter(context: Context, private val callBack: CallBack) :
             if (payloads.isEmpty()) {
             if (payloads.isEmpty()) {
                 tvName.text = item.name
                 tvName.text = item.name
                 tvAuthor.text = context.getString(R.string.author_show, item.getRealAuthor())
                 tvAuthor.text = context.getString(R.string.author_show, item.getRealAuthor())
-                if (item.isLocalBook()) {
+                if (item.isLocal) {
                     tvDownload.setText(R.string.local_book)
                     tvDownload.setText(R.string.local_book)
                 } else {
                 } else {
                     val cs = cacheChapters[item.bookUrl]
                     val cs = cacheChapters[item.bookUrl]
@@ -49,7 +50,7 @@ class CacheAdapter(context: Context, private val callBack: CallBack) :
                     }
                     }
                 }
                 }
             } else {
             } else {
-                if (item.isLocalBook()) {
+                if (item.isLocal) {
                     tvDownload.setText(R.string.local_book)
                     tvDownload.setText(R.string.local_book)
                 } else {
                 } else {
                     val cacheSize = cacheChapters[item.bookUrl]?.size ?: 0
                     val cacheSize = cacheChapters[item.bookUrl]?.size ?: 0
@@ -84,7 +85,7 @@ class CacheAdapter(context: Context, private val callBack: CallBack) :
     }
     }
 
 
     private fun upDownloadIv(iv: ImageView, book: Book) {
     private fun upDownloadIv(iv: ImageView, book: Book) {
-        if (book.isLocalBook()) {
+        if (book.isLocal) {
             iv.gone()
             iv.gone()
         } else {
         } else {
             iv.visible()
             iv.visible()

+ 9 - 8
app/src/main/java/io/legado/app/ui/book/info/BookInfoActivity.kt

@@ -2,7 +2,6 @@ package io.legado.app.ui.book.info
 
 
 import android.annotation.SuppressLint
 import android.annotation.SuppressLint
 import android.content.Intent
 import android.content.Intent
-import android.net.Uri
 import android.os.Bundle
 import android.os.Bundle
 import android.view.Menu
 import android.view.Menu
 import android.view.MenuItem
 import android.view.MenuItem
@@ -12,7 +11,6 @@ import androidx.activity.result.contract.ActivityResultContracts
 import androidx.activity.viewModels
 import androidx.activity.viewModels
 import io.legado.app.R
 import io.legado.app.R
 import io.legado.app.base.VMBaseActivity
 import io.legado.app.base.VMBaseActivity
-import io.legado.app.constant.BookType
 import io.legado.app.constant.EventBus
 import io.legado.app.constant.EventBus
 import io.legado.app.constant.Theme
 import io.legado.app.constant.Theme
 import io.legado.app.data.appDb
 import io.legado.app.data.appDb
@@ -22,6 +20,9 @@ import io.legado.app.data.entities.BookSource
 import io.legado.app.databinding.ActivityBookInfoBinding
 import io.legado.app.databinding.ActivityBookInfoBinding
 import io.legado.app.databinding.DialogEditTextBinding
 import io.legado.app.databinding.DialogEditTextBinding
 import io.legado.app.help.config.AppConfig
 import io.legado.app.help.config.AppConfig
+import io.legado.app.help.isAudio
+import io.legado.app.help.isLocal
+import io.legado.app.help.isLocalTxt
 import io.legado.app.lib.dialogs.alert
 import io.legado.app.lib.dialogs.alert
 import io.legado.app.lib.theme.backgroundColor
 import io.legado.app.lib.theme.backgroundColor
 import io.legado.app.lib.theme.bottomBackground
 import io.legado.app.lib.theme.bottomBackground
@@ -132,9 +133,9 @@ class BookInfoActivity :
         menu.findItem(R.id.menu_can_update)?.isVisible =
         menu.findItem(R.id.menu_can_update)?.isVisible =
             viewModel.bookSource != null
             viewModel.bookSource != null
         menu.findItem(R.id.menu_split_long_chapter)?.isVisible =
         menu.findItem(R.id.menu_split_long_chapter)?.isVisible =
-            viewModel.bookData.value?.isLocalTxt() ?: false
+            viewModel.bookData.value?.isLocalTxt ?: false
         menu.findItem(R.id.menu_upload)?.isVisible =
         menu.findItem(R.id.menu_upload)?.isVisible =
-            viewModel.bookData.value?.isLocalBook() ?: false
+            viewModel.bookData.value?.isLocal ?: false
         return super.onMenuOpened(featureId, menu)
         return super.onMenuOpened(featureId, menu)
     }
     }
 
 
@@ -161,7 +162,7 @@ class BookInfoActivity :
             R.id.menu_refresh -> {
             R.id.menu_refresh -> {
                 upLoading(true)
                 upLoading(true)
                 viewModel.bookData.value?.let {
                 viewModel.bookData.value?.let {
-                    if (it.isLocalBook()) {
+                    if (it.isLocal) {
                         it.tocUrl = ""
                         it.tocUrl = ""
                     }
                     }
                     viewModel.loadBookInfo(it, false)
                     viewModel.loadBookInfo(it, false)
@@ -437,7 +438,7 @@ class BookInfoActivity :
     @SuppressLint("InflateParams")
     @SuppressLint("InflateParams")
     private fun deleteBook() {
     private fun deleteBook() {
         viewModel.bookData.value?.let {
         viewModel.bookData.value?.let {
-            if (it.isLocalBook()) {
+            if (it.isLocal) {
                 alert(
                 alert(
                     titleResource = R.string.sure,
                     titleResource = R.string.sure,
                     messageResource = R.string.sure_del
                     messageResource = R.string.sure_del
@@ -490,8 +491,8 @@ class BookInfoActivity :
     }
     }
 
 
     private fun startReadActivity(book: Book) {
     private fun startReadActivity(book: Book) {
-        when (book.type) {
-            BookType.audio -> readBookResult.launch(
+        when {
+            book.isAudio -> readBookResult.launch(
                 Intent(this, AudioPlayActivity::class.java)
                 Intent(this, AudioPlayActivity::class.java)
                     .putExtra("bookUrl", book.bookUrl)
                     .putExtra("bookUrl", book.bookUrl)
                     .putExtra("inBookshelf", viewModel.inBookshelf)
                     .putExtra("inBookshelf", viewModel.inBookshelf)

+ 6 - 5
app/src/main/java/io/legado/app/ui/book/info/BookInfoViewModel.kt

@@ -16,6 +16,7 @@ import io.legado.app.data.entities.BookSource
 import io.legado.app.exception.NoStackTraceException
 import io.legado.app.exception.NoStackTraceException
 import io.legado.app.help.BookHelp
 import io.legado.app.help.BookHelp
 import io.legado.app.help.coroutine.Coroutine
 import io.legado.app.help.coroutine.Coroutine
+import io.legado.app.help.isLocal
 import io.legado.app.model.BookCover
 import io.legado.app.model.BookCover
 import io.legado.app.model.ReadBook
 import io.legado.app.model.ReadBook
 import io.legado.app.model.analyzeRule.AnalyzeRule
 import io.legado.app.model.analyzeRule.AnalyzeRule
@@ -33,7 +34,7 @@ class BookInfoViewModel(application: Application) : BaseViewModel(application) {
     var bookSource: BookSource? = null
     var bookSource: BookSource? = null
     private var changeSourceCoroutine: Coroutine<*>? = null
     private var changeSourceCoroutine: Coroutine<*>? = null
     val isImportBookOnLine: Boolean
     val isImportBookOnLine: Boolean
-      get() = (bookSource?.bookSourceType ?: BookType.local) == BookType.file
+        get() = (bookSource?.bookSourceType ?: BookType.local) == BookType.webFile
 
 
     fun initData(intent: Intent) {
     fun initData(intent: Intent) {
         execute {
         execute {
@@ -75,7 +76,7 @@ class BookInfoViewModel(application: Application) : BaseViewModel(application) {
         execute {
         execute {
             bookData.postValue(book)
             bookData.postValue(book)
             upCoverByRule(book)
             upCoverByRule(book)
-            bookSource = if (book.isLocalBook()) null else
+            bookSource = if (book.isLocal) null else
                 appDb.bookSourceDao.getBookSource(book.origin)
                 appDb.bookSourceDao.getBookSource(book.origin)
             if (book.tocUrl.isEmpty()) {
             if (book.tocUrl.isEmpty()) {
                 loadBookInfo(book)
                 loadBookInfo(book)
@@ -112,7 +113,7 @@ class BookInfoViewModel(application: Application) : BaseViewModel(application) {
         scope: CoroutineScope = viewModelScope
         scope: CoroutineScope = viewModelScope
     ) {
     ) {
         execute(scope) {
         execute(scope) {
-            if (book.isLocalBook()) {
+            if (book.isLocal) {
                 loadChapter(book, scope)
                 loadChapter(book, scope)
             } else {
             } else {
                 bookSource?.let { bookSource ->
                 bookSource?.let { bookSource ->
@@ -143,7 +144,7 @@ class BookInfoViewModel(application: Application) : BaseViewModel(application) {
         scope: CoroutineScope = viewModelScope
         scope: CoroutineScope = viewModelScope
     ) {
     ) {
         execute(scope) {
         execute(scope) {
-            if (book.isLocalBook()) {
+            if (book.isLocal) {
                 LocalBook.getChapterList(book).let {
                 LocalBook.getChapterList(book).let {
                     appDb.bookDao.update(book)
                     appDb.bookDao.update(book)
                     appDb.bookChapterDao.delByBook(book.bookUrl)
                     appDb.bookChapterDao.delByBook(book.bookUrl)
@@ -277,7 +278,7 @@ class BookInfoViewModel(application: Application) : BaseViewModel(application) {
             bookData.value?.let {
             bookData.value?.let {
                 it.delete()
                 it.delete()
                 inBookshelf = false
                 inBookshelf = false
-                if (it.isLocalBook()) {
+                if (it.isLocal) {
                     LocalBook.deleteBook(it, deleteOriginal)
                     LocalBook.deleteBook(it, deleteOriginal)
                 }
                 }
             }
             }

+ 10 - 6
app/src/main/java/io/legado/app/ui/book/info/edit/BookInfoEditActivity.kt

@@ -11,6 +11,9 @@ import io.legado.app.base.VMBaseActivity
 import io.legado.app.constant.BookType
 import io.legado.app.constant.BookType
 import io.legado.app.data.entities.Book
 import io.legado.app.data.entities.Book
 import io.legado.app.databinding.ActivityBookInfoEditBinding
 import io.legado.app.databinding.ActivityBookInfoEditBinding
+import io.legado.app.help.isAudio
+import io.legado.app.help.isImage
+import io.legado.app.help.isLocal
 import io.legado.app.ui.book.changecover.ChangeCoverDialog
 import io.legado.app.ui.book.changecover.ChangeCoverDialog
 import io.legado.app.utils.*
 import io.legado.app.utils.*
 import io.legado.app.utils.viewbindingdelegate.viewBinding
 import io.legado.app.utils.viewbindingdelegate.viewBinding
@@ -72,9 +75,9 @@ class BookInfoEditActivity :
         tieBookName.setText(book.name)
         tieBookName.setText(book.name)
         tieBookAuthor.setText(book.author)
         tieBookAuthor.setText(book.author)
         spType.setSelection(
         spType.setSelection(
-            when (book.type) {
-                BookType.image -> 2
-                BookType.audio -> 1
+            when {
+                book.isImage -> 2
+                book.isAudio -> 1
                 else -> 0
                 else -> 0
             }
             }
         )
         )
@@ -93,10 +96,11 @@ class BookInfoEditActivity :
         viewModel.book?.let { book ->
         viewModel.book?.let { book ->
             book.name = tieBookName.text?.toString() ?: ""
             book.name = tieBookName.text?.toString() ?: ""
             book.author = tieBookAuthor.text?.toString() ?: ""
             book.author = tieBookAuthor.text?.toString() ?: ""
+            val local = if (book.isLocal) BookType.local else 0
             book.type = when (spType.selectedItemPosition) {
             book.type = when (spType.selectedItemPosition) {
-                2 -> BookType.image
-                1 -> BookType.audio
-                else -> BookType.default
+                2 -> BookType.image or local
+                1 -> BookType.audio or local
+                else -> BookType.text or local
             }
             }
             val customCoverUrl = tieCoverUrl.text?.toString()
             val customCoverUrl = tieCoverUrl.text?.toString()
             book.customCoverUrl = if (customCoverUrl == book.coverUrl) null else customCoverUrl
             book.customCoverUrl = if (customCoverUrl == book.coverUrl) null else customCoverUrl

+ 2 - 1
app/src/main/java/io/legado/app/ui/book/manage/BookAdapter.kt

@@ -12,6 +12,7 @@ import io.legado.app.base.adapter.RecyclerAdapter
 import io.legado.app.data.entities.Book
 import io.legado.app.data.entities.Book
 import io.legado.app.data.entities.BookGroup
 import io.legado.app.data.entities.BookGroup
 import io.legado.app.databinding.ItemArrangeBookBinding
 import io.legado.app.databinding.ItemArrangeBookBinding
+import io.legado.app.help.isLocal
 import io.legado.app.lib.theme.backgroundColor
 import io.legado.app.lib.theme.backgroundColor
 import io.legado.app.ui.widget.recycler.DragSelectTouchHelper
 import io.legado.app.ui.widget.recycler.DragSelectTouchHelper
 import io.legado.app.ui.widget.recycler.ItemTouchCallback
 import io.legado.app.ui.widget.recycler.ItemTouchCallback
@@ -53,7 +54,7 @@ class BookAdapter(context: Context, val callBack: CallBack) :
             tvAuthor.visibility = if (item.author.isEmpty()) View.GONE else View.VISIBLE
             tvAuthor.visibility = if (item.author.isEmpty()) View.GONE else View.VISIBLE
             tvGroupS.text = getGroupName(item.group)
             tvGroupS.text = getGroupName(item.group)
             checkbox.isChecked = selectedBooks.contains(item)
             checkbox.isChecked = selectedBooks.contains(item)
-            if (item.isLocalBook()) {
+            if (item.isLocal) {
                 tvOrigin.setText(R.string.local_book)
                 tvOrigin.setText(R.string.local_book)
             } else {
             } else {
                 tvOrigin.text = item.originName
                 tvOrigin.text = item.originName

+ 2 - 1
app/src/main/java/io/legado/app/ui/book/manage/BookshelfManageViewModel.kt

@@ -7,6 +7,7 @@ import io.legado.app.data.appDb
 import io.legado.app.data.entities.Book
 import io.legado.app.data.entities.Book
 import io.legado.app.data.entities.BookSource
 import io.legado.app.data.entities.BookSource
 import io.legado.app.help.coroutine.Coroutine
 import io.legado.app.help.coroutine.Coroutine
+import io.legado.app.help.isLocal
 import io.legado.app.model.webBook.WebBook
 import io.legado.app.model.webBook.WebBook
 import io.legado.app.utils.toastOnUi
 import io.legado.app.utils.toastOnUi
 
 
@@ -45,7 +46,7 @@ class BookshelfManageViewModel(application: Application) : BaseViewModel(applica
             batchChangeSourceSize.value = books.size
             batchChangeSourceSize.value = books.size
             books.forEachIndexed { index, book ->
             books.forEachIndexed { index, book ->
                 batchChangeSourcePosition.value = index + 1
                 batchChangeSourcePosition.value = index + 1
-                if (book.isLocalBook()) return@forEachIndexed
+                if (book.isLocal) return@forEachIndexed
                 if (book.origin == source.bookSourceUrl) return@forEachIndexed
                 if (book.origin == source.bookSourceUrl) return@forEachIndexed
                 WebBook.preciseSearchAwait(this, source, book.name, book.author)
                 WebBook.preciseSearchAwait(this, source, book.name, book.author)
                     .onFailure {
                     .onFailure {

+ 2 - 1
app/src/main/java/io/legado/app/ui/book/read/ContentEditDialog.kt

@@ -14,6 +14,7 @@ import io.legado.app.databinding.DialogContentEditBinding
 import io.legado.app.databinding.DialogEditTextBinding
 import io.legado.app.databinding.DialogEditTextBinding
 import io.legado.app.help.BookHelp
 import io.legado.app.help.BookHelp
 import io.legado.app.help.ContentProcessor
 import io.legado.app.help.ContentProcessor
+import io.legado.app.help.isLocal
 import io.legado.app.lib.dialogs.alert
 import io.legado.app.lib.dialogs.alert
 import io.legado.app.lib.theme.primaryColor
 import io.legado.app.lib.theme.primaryColor
 import io.legado.app.model.ReadBook
 import io.legado.app.model.ReadBook
@@ -125,7 +126,7 @@ class ContentEditDialog : BaseDialogFragment(R.layout.dialog_content_edit) {
                 if (reset) {
                 if (reset) {
                     content = null
                     content = null
                     BookHelp.delContent(book, chapter)
                     BookHelp.delContent(book, chapter)
-                    if (!book.isLocalBook()) ReadBook.bookSource?.let { bookSource ->
+                    if (!book.isLocal) ReadBook.bookSource?.let { bookSource ->
                         WebBook.getContentAwait(bookSource, book, chapter)
                         WebBook.getContentAwait(bookSource, book, chapter)
                     }
                     }
                 }
                 }

+ 6 - 9
app/src/main/java/io/legado/app/ui/book/read/ReadBookActivity.kt

@@ -22,10 +22,7 @@ import io.legado.app.data.entities.BookChapter
 import io.legado.app.data.entities.BookProgress
 import io.legado.app.data.entities.BookProgress
 import io.legado.app.data.entities.BookSource
 import io.legado.app.data.entities.BookSource
 import io.legado.app.exception.NoStackTraceException
 import io.legado.app.exception.NoStackTraceException
-import io.legado.app.help.AppWebDav
-import io.legado.app.help.BookHelp
-import io.legado.app.help.IntentData
-import io.legado.app.help.TTS
+import io.legado.app.help.*
 import io.legado.app.help.config.AppConfig
 import io.legado.app.help.config.AppConfig
 import io.legado.app.help.config.ReadBookConfig
 import io.legado.app.help.config.ReadBookConfig
 import io.legado.app.help.config.ReadTipConfig
 import io.legado.app.help.config.ReadTipConfig
@@ -248,13 +245,13 @@ class ReadBookActivity : BaseReadBookActivity(),
         val menu = menu
         val menu = menu
         val book = ReadBook.book
         val book = ReadBook.book
         if (menu != null && book != null) {
         if (menu != null && book != null) {
-            val onLine = !book.isLocalBook()
+            val onLine = !book.isLocal
             for (i in 0 until menu.size) {
             for (i in 0 until menu.size) {
                 val item = menu[i]
                 val item = menu[i]
                 when (item.groupId) {
                 when (item.groupId) {
                     R.id.menu_group_on_line -> item.isVisible = onLine
                     R.id.menu_group_on_line -> item.isVisible = onLine
                     R.id.menu_group_local -> item.isVisible = !onLine
                     R.id.menu_group_local -> item.isVisible = !onLine
-                    R.id.menu_group_text -> item.isVisible = book.isLocalTxt()
+                    R.id.menu_group_text -> item.isVisible = book.isLocalTxt
                     else -> when (item.itemId) {
                     else -> when (item.itemId) {
                         R.id.menu_enable_replace -> item.isChecked = book.getUseReplaceRule()
                         R.id.menu_enable_replace -> item.isChecked = book.getUseReplaceRule()
                         R.id.menu_re_segment -> item.isChecked = book.getReSegment()
                         R.id.menu_re_segment -> item.isChecked = book.getReSegment()
@@ -346,7 +343,7 @@ class ReadBookActivity : BaseReadBookActivity(),
             }
             }
             R.id.menu_edit_content -> showDialogFragment(ContentEditDialog())
             R.id.menu_edit_content -> showDialogFragment(ContentEditDialog())
             R.id.menu_update_toc -> ReadBook.book?.let {
             R.id.menu_update_toc -> ReadBook.book?.let {
-                if (it.isEpub()) {
+                if (it.isEpub) {
                     BookHelp.clearCache(it)
                     BookHelp.clearCache(it)
                 }
                 }
                 loadChapterList(it)
                 loadChapterList(it)
@@ -790,7 +787,7 @@ class ReadBookActivity : BaseReadBookActivity(),
         get() = ReadBook.book
         get() = ReadBook.book
 
 
     override fun changeTo(source: BookSource, book: Book, toc: List<BookChapter>) {
     override fun changeTo(source: BookSource, book: Book, toc: List<BookChapter>) {
-        if (book.type != BookType.audio) {
+        if (!book.isAudio) {
             viewModel.changeTo(book, toc)
             viewModel.changeTo(book, toc)
         } else {
         } else {
             ReadAloud.stop(this)
             ReadAloud.stop(this)
@@ -986,7 +983,7 @@ class ReadBookActivity : BaseReadBookActivity(),
 
 
     override fun payAction() {
     override fun payAction() {
         ReadBook.book?.let { book ->
         ReadBook.book?.let { book ->
-            if (book.isLocalBook()) return
+            if (book.isLocal) return
             val chapter = appDb.bookChapterDao.getChapter(book.bookUrl, ReadBook.durChapterIndex)
             val chapter = appDb.bookChapterDao.getChapter(book.bookUrl, ReadBook.durChapterIndex)
             if (chapter == null) {
             if (chapter == null) {
                 toastOnUi("no chapter")
                 toastOnUi("no chapter")

+ 5 - 4
app/src/main/java/io/legado/app/ui/book/read/ReadBookViewModel.kt

@@ -20,6 +20,7 @@ import io.legado.app.help.BookHelp
 import io.legado.app.help.ContentProcessor
 import io.legado.app.help.ContentProcessor
 import io.legado.app.help.config.AppConfig
 import io.legado.app.help.config.AppConfig
 import io.legado.app.help.coroutine.Coroutine
 import io.legado.app.help.coroutine.Coroutine
+import io.legado.app.help.isLocal
 import io.legado.app.model.ReadAloud
 import io.legado.app.model.ReadAloud
 import io.legado.app.model.ReadBook
 import io.legado.app.model.ReadBook
 import io.legado.app.model.analyzeRule.AnalyzeRule
 import io.legado.app.model.analyzeRule.AnalyzeRule
@@ -78,7 +79,7 @@ class ReadBookViewModel(application: Application) : BaseViewModel(application) {
             } else {
             } else {
                 loadChapterList(book)
                 loadChapterList(book)
             }
             }
-        } else if (book.isLocalBook()
+        } else if (book.isLocal
             && LocalBook.getLastModified(book).getOrDefault(0L) > book.latestChapterTime
             && LocalBook.getLastModified(book).getOrDefault(0L) > book.latestChapterTime
         ) {
         ) {
             loadChapterList(book)
             loadChapterList(book)
@@ -97,7 +98,7 @@ class ReadBookViewModel(application: Application) : BaseViewModel(application) {
         if (!isSameBook || !BaseReadAloudService.isRun) {
         if (!isSameBook || !BaseReadAloudService.isRun) {
             syncBookProgress(book)
             syncBookProgress(book)
         }
         }
-        if (!book.isLocalBook() && ReadBook.bookSource == null) {
+        if (!book.isLocal && ReadBook.bookSource == null) {
             autoChangeSource(book.name, book.author)
             autoChangeSource(book.name, book.author)
             return
             return
         }
         }
@@ -107,7 +108,7 @@ class ReadBookViewModel(application: Application) : BaseViewModel(application) {
      * 加载详情页
      * 加载详情页
      */
      */
     private fun loadBookInfo(book: Book) {
     private fun loadBookInfo(book: Book) {
-        if (book.isLocalBook()) {
+        if (book.isLocal) {
             loadChapterList(book)
             loadChapterList(book)
         } else {
         } else {
             ReadBook.bookSource?.let { source ->
             ReadBook.bookSource?.let { source ->
@@ -125,7 +126,7 @@ class ReadBookViewModel(application: Application) : BaseViewModel(application) {
      * 加载目录
      * 加载目录
      */
      */
     fun loadChapterList(book: Book) {
     fun loadChapterList(book: Book) {
-        if (book.isLocalBook()) {
+        if (book.isLocal) {
             execute {
             execute {
                 LocalBook.getChapterList(book).let {
                 LocalBook.getChapterList(book).let {
                     book.latestChapterTime = System.currentTimeMillis()
                     book.latestChapterTime = System.currentTimeMillis()

+ 2 - 1
app/src/main/java/io/legado/app/ui/book/read/page/provider/ImageProvider.kt

@@ -13,6 +13,7 @@ import io.legado.app.exception.NoStackTraceException
 import io.legado.app.help.BookHelp
 import io.legado.app.help.BookHelp
 import io.legado.app.help.config.AppConfig
 import io.legado.app.help.config.AppConfig
 import io.legado.app.help.coroutine.Coroutine
 import io.legado.app.help.coroutine.Coroutine
+import io.legado.app.help.isEpub
 import io.legado.app.model.ReadBook
 import io.legado.app.model.ReadBook
 import io.legado.app.model.localBook.EpubFile
 import io.legado.app.model.localBook.EpubFile
 import io.legado.app.utils.BitmapUtils
 import io.legado.app.utils.BitmapUtils
@@ -71,7 +72,7 @@ object ImageProvider {
         return withContext(IO) {
         return withContext(IO) {
             val vFile = BookHelp.getImage(book, src)
             val vFile = BookHelp.getImage(book, src)
             if (!vFile.exists()) {
             if (!vFile.exists()) {
-                if (book.isEpub()) {
+                if (book.isEpub) {
                     EpubFile.getImage(book, src)?.use { input ->
                     EpubFile.getImage(book, src)?.use { input ->
                         val newFile = FileUtils.createFileIfNotExist(vFile.absolutePath)
                         val newFile = FileUtils.createFileIfNotExist(vFile.absolutePath)
                         @Suppress("BlockingMethodInNonBlockingContext")
                         @Suppress("BlockingMethodInNonBlockingContext")

+ 1 - 1
app/src/main/java/io/legado/app/ui/book/remote/RemoteBookViewModel.kt

@@ -97,7 +97,7 @@ class RemoteBookViewModel(application: Application) : BaseViewModel(application)
                 val downloadBookPath = RemoteBookWebDav.getRemoteBook(remoteBook)
                 val downloadBookPath = RemoteBookWebDav.getRemoteBook(remoteBook)
                 downloadBookPath?.let {
                 downloadBookPath?.let {
                     val localBook = LocalBook.importFile(it)
                     val localBook = LocalBook.importFile(it)
-                    localBook.origin = BookType.webDav + remoteBook.path
+                    localBook.origin = BookType.webDavTag + remoteBook.path
                     localBook.save()
                     localBook.save()
                     remoteBook.isOnBookShelf = true
                     remoteBook.isOnBookShelf = true
                 }
                 }

+ 1 - 1
app/src/main/java/io/legado/app/ui/book/remote/manager/RemoteBookWebDav.kt

@@ -100,7 +100,7 @@ object RemoteBookWebDav : RemoteBookManager() {
                 WebDav(putUrl, it).upload(localBookUri.path!!)
                 WebDav(putUrl, it).upload(localBookUri.path!!)
             }
             }
         }
         }
-        book.origin = BookType.webDav + putUrl
+        book.origin = BookType.webDavTag + putUrl
         book.save()
         book.save()
         return true
         return true
     }
     }

+ 2 - 1
app/src/main/java/io/legado/app/ui/book/searchContent/SearchContentActivity.kt

@@ -15,6 +15,7 @@ import io.legado.app.data.entities.BookChapter
 import io.legado.app.databinding.ActivitySearchContentBinding
 import io.legado.app.databinding.ActivitySearchContentBinding
 import io.legado.app.help.BookHelp
 import io.legado.app.help.BookHelp
 import io.legado.app.help.IntentData
 import io.legado.app.help.IntentData
+import io.legado.app.help.isLocal
 import io.legado.app.lib.theme.bottomBackground
 import io.legado.app.lib.theme.bottomBackground
 import io.legado.app.lib.theme.getPrimaryTextColor
 import io.legado.app.lib.theme.getPrimaryTextColor
 import io.legado.app.lib.theme.primaryTextColor
 import io.legado.app.lib.theme.primaryTextColor
@@ -193,7 +194,7 @@ class SearchContentActivity :
     }
     }
 
 
     val isLocalBook: Boolean
     val isLocalBook: Boolean
-        get() = viewModel.book?.isLocalBook() == true
+        get() = viewModel.book?.isLocal == true
 
 
     override fun openSearchResult(searchResult: SearchResult, index: Int) {
     override fun openSearchResult(searchResult: SearchResult, index: Int) {
         postEvent(EventBus.SEARCH_RESULT, viewModel.searchResultList as List<SearchResult>)
         postEvent(EventBus.SEARCH_RESULT, viewModel.searchResultList as List<SearchResult>)

+ 8 - 8
app/src/main/java/io/legado/app/ui/book/source/edit/BookSourceEditActivity.kt

@@ -11,7 +11,7 @@ import com.google.android.material.tabs.TabLayout
 import com.google.zxing.qrcode.decoder.ErrorCorrectionLevel
 import com.google.zxing.qrcode.decoder.ErrorCorrectionLevel
 import io.legado.app.R
 import io.legado.app.R
 import io.legado.app.base.VMBaseActivity
 import io.legado.app.base.VMBaseActivity
-import io.legado.app.constant.BookType
+import io.legado.app.constant.BookSourceType
 import io.legado.app.data.appDb
 import io.legado.app.data.appDb
 import io.legado.app.data.entities.BookSource
 import io.legado.app.data.entities.BookSource
 import io.legado.app.data.entities.rule.*
 import io.legado.app.data.entities.rule.*
@@ -204,9 +204,9 @@ class BookSourceEditActivity :
             binding.cbIsEnableReview.isChecked = it.enabledReview ?: false
             binding.cbIsEnableReview.isChecked = it.enabledReview ?: false
             binding.spType.setSelection(
             binding.spType.setSelection(
                 when (it.bookSourceType) {
                 when (it.bookSourceType) {
-                    BookType.file -> 3
-                    BookType.image -> 2
-                    BookType.audio -> 1
+                    BookSourceType.file -> 3
+                    BookSourceType.image -> 2
+                    BookSourceType.audio -> 1
                     else -> 0
                     else -> 0
                 }
                 }
             )
             )
@@ -327,10 +327,10 @@ class BookSourceEditActivity :
         source.enabledCookieJar = binding.cbIsEnableCookie.isChecked
         source.enabledCookieJar = binding.cbIsEnableCookie.isChecked
         source.enabledReview = binding.cbIsEnableReview.isChecked
         source.enabledReview = binding.cbIsEnableReview.isChecked
         source.bookSourceType = when (binding.spType.selectedItemPosition) {
         source.bookSourceType = when (binding.spType.selectedItemPosition) {
-            3 -> BookType.file
-            2 -> BookType.image
-            1 -> BookType.audio
-            else -> BookType.default
+            3 -> BookSourceType.file
+            2 -> BookSourceType.image
+            1 -> BookSourceType.audio
+            else -> BookSourceType.default
         }
         }
         val searchRule = SearchRule()
         val searchRule = SearchRule()
         val exploreRule = ExploreRule()
         val exploreRule = ExploreRule()

+ 2 - 1
app/src/main/java/io/legado/app/ui/book/toc/ChapterListFragment.kt

@@ -14,6 +14,7 @@ import io.legado.app.data.entities.Book
 import io.legado.app.data.entities.BookChapter
 import io.legado.app.data.entities.BookChapter
 import io.legado.app.databinding.FragmentChapterListBinding
 import io.legado.app.databinding.FragmentChapterListBinding
 import io.legado.app.help.BookHelp
 import io.legado.app.help.BookHelp
+import io.legado.app.help.isLocal
 import io.legado.app.lib.theme.bottomBackground
 import io.legado.app.lib.theme.bottomBackground
 import io.legado.app.lib.theme.getPrimaryTextColor
 import io.legado.app.lib.theme.getPrimaryTextColor
 import io.legado.app.ui.widget.recycler.UpLinearLayoutManager
 import io.legado.app.ui.widget.recycler.UpLinearLayoutManager
@@ -152,7 +153,7 @@ class ChapterListFragment : VMBaseFragment<TocViewModel>(R.layout.fragment_chapt
         get() = viewModel.bookData.value
         get() = viewModel.bookData.value
 
 
     override val isLocalBook: Boolean
     override val isLocalBook: Boolean
-        get() = viewModel.bookData.value?.isLocalBook() == true
+        get() = viewModel.bookData.value?.isLocal == true
 
 
     override fun durChapterIndex(): Int {
     override fun durChapterIndex(): Int {
         return durChapterIndex
         return durChapterIndex

+ 3 - 2
app/src/main/java/io/legado/app/ui/main/MainViewModel.kt

@@ -5,7 +5,6 @@ import androidx.lifecycle.viewModelScope
 import io.legado.app.base.BaseViewModel
 import io.legado.app.base.BaseViewModel
 import io.legado.app.constant.AppConst
 import io.legado.app.constant.AppConst
 import io.legado.app.constant.AppLog
 import io.legado.app.constant.AppLog
-import io.legado.app.constant.BookType
 import io.legado.app.constant.EventBus
 import io.legado.app.constant.EventBus
 import io.legado.app.data.appDb
 import io.legado.app.data.appDb
 import io.legado.app.data.entities.Book
 import io.legado.app.data.entities.Book
@@ -15,6 +14,7 @@ import io.legado.app.help.BookHelp
 import io.legado.app.help.DefaultData
 import io.legado.app.help.DefaultData
 import io.legado.app.help.config.AppConfig
 import io.legado.app.help.config.AppConfig
 import io.legado.app.help.config.LocalConfig
 import io.legado.app.help.config.LocalConfig
+import io.legado.app.help.isLocal
 import io.legado.app.model.CacheBook
 import io.legado.app.model.CacheBook
 import io.legado.app.model.analyzeRule.AnalyzeRule
 import io.legado.app.model.analyzeRule.AnalyzeRule
 import io.legado.app.model.webBook.WebBook
 import io.legado.app.model.webBook.WebBook
@@ -59,7 +59,7 @@ class MainViewModel(application: Application) : BaseViewModel(application) {
     fun upToc(books: List<Book>) {
     fun upToc(books: List<Book>) {
         execute(context = upTocPool) {
         execute(context = upTocPool) {
             books.filter {
             books.filter {
-                it.origin != BookType.local && it.canUpdate
+                !it.isLocal && it.canUpdate
             }.let {
             }.let {
                 addToWaitUp(it)
                 addToWaitUp(it)
             }
             }
@@ -233,6 +233,7 @@ class MainViewModel(application: Application) : BaseViewModel(application) {
             if (LocalConfig.needUpRssSources) {
             if (LocalConfig.needUpRssSources) {
                 DefaultData.importDefaultRssSources()
                 DefaultData.importDefaultRssSources()
             }
             }
+
         }
         }
     }
     }
 }
 }

+ 2 - 2
app/src/main/java/io/legado/app/ui/main/bookshelf/style1/books/BooksAdapterGrid.kt

@@ -4,10 +4,10 @@ import android.content.Context
 import android.os.Bundle
 import android.os.Bundle
 import android.view.ViewGroup
 import android.view.ViewGroup
 import io.legado.app.base.adapter.ItemViewHolder
 import io.legado.app.base.adapter.ItemViewHolder
-import io.legado.app.constant.BookType
 import io.legado.app.data.entities.Book
 import io.legado.app.data.entities.Book
 import io.legado.app.databinding.ItemBookshelfGridBinding
 import io.legado.app.databinding.ItemBookshelfGridBinding
 import io.legado.app.help.config.AppConfig
 import io.legado.app.help.config.AppConfig
+import io.legado.app.help.isLocal
 import io.legado.app.utils.invisible
 import io.legado.app.utils.invisible
 import splitties.views.onLongClick
 import splitties.views.onLongClick
 
 
@@ -41,7 +41,7 @@ class BooksAdapterGrid(context: Context, private val callBack: CallBack) :
     }
     }
 
 
     private fun upRefresh(binding: ItemBookshelfGridBinding, item: Book) {
     private fun upRefresh(binding: ItemBookshelfGridBinding, item: Book) {
-        if (item.origin != BookType.local && callBack.isUpdate(item.bookUrl)) {
+        if (!item.isLocal && callBack.isUpdate(item.bookUrl)) {
             binding.bvUnread.invisible()
             binding.bvUnread.invisible()
             binding.rlLoading.show()
             binding.rlLoading.show()
         } else {
         } else {

+ 2 - 2
app/src/main/java/io/legado/app/ui/main/bookshelf/style1/books/BooksAdapterList.kt

@@ -4,10 +4,10 @@ import android.content.Context
 import android.os.Bundle
 import android.os.Bundle
 import android.view.ViewGroup
 import android.view.ViewGroup
 import io.legado.app.base.adapter.ItemViewHolder
 import io.legado.app.base.adapter.ItemViewHolder
-import io.legado.app.constant.BookType
 import io.legado.app.data.entities.Book
 import io.legado.app.data.entities.Book
 import io.legado.app.databinding.ItemBookshelfListBinding
 import io.legado.app.databinding.ItemBookshelfListBinding
 import io.legado.app.help.config.AppConfig
 import io.legado.app.help.config.AppConfig
+import io.legado.app.help.isLocal
 import io.legado.app.utils.invisible
 import io.legado.app.utils.invisible
 import splitties.views.onLongClick
 import splitties.views.onLongClick
 
 
@@ -47,7 +47,7 @@ class BooksAdapterList(context: Context, private val callBack: CallBack) :
     }
     }
 
 
     private fun upRefresh(binding: ItemBookshelfListBinding, item: Book) {
     private fun upRefresh(binding: ItemBookshelfListBinding, item: Book) {
-        if (item.origin != BookType.local && callBack.isUpdate(item.bookUrl)) {
+        if (!item.isLocal && callBack.isUpdate(item.bookUrl)) {
             binding.bvUnread.invisible()
             binding.bvUnread.invisible()
             binding.rlLoading.show()
             binding.rlLoading.show()
         } else {
         } else {

+ 7 - 3
app/src/main/java/io/legado/app/ui/main/bookshelf/style1/books/BooksFragment.kt

@@ -10,11 +10,15 @@ import androidx.recyclerview.widget.LinearLayoutManager
 import androidx.recyclerview.widget.RecyclerView
 import androidx.recyclerview.widget.RecyclerView
 import io.legado.app.R
 import io.legado.app.R
 import io.legado.app.base.BaseFragment
 import io.legado.app.base.BaseFragment
-import io.legado.app.constant.*
+import io.legado.app.constant.AppConst
+import io.legado.app.constant.AppLog
+import io.legado.app.constant.EventBus
+import io.legado.app.constant.PreferKey
 import io.legado.app.data.appDb
 import io.legado.app.data.appDb
 import io.legado.app.data.entities.Book
 import io.legado.app.data.entities.Book
 import io.legado.app.databinding.FragmentBooksBinding
 import io.legado.app.databinding.FragmentBooksBinding
 import io.legado.app.help.config.AppConfig
 import io.legado.app.help.config.AppConfig
+import io.legado.app.help.isAudio
 import io.legado.app.lib.theme.accentColor
 import io.legado.app.lib.theme.accentColor
 import io.legado.app.lib.theme.primaryColor
 import io.legado.app.lib.theme.primaryColor
 import io.legado.app.ui.book.audio.AudioPlayActivity
 import io.legado.app.ui.book.audio.AudioPlayActivity
@@ -187,8 +191,8 @@ class BooksFragment() : BaseFragment(R.layout.fragment_books),
     }
     }
 
 
     override fun open(book: Book) {
     override fun open(book: Book) {
-        when (book.type) {
-            BookType.audio ->
+        when {
+            book.isAudio ->
                 startActivity<AudioPlayActivity> {
                 startActivity<AudioPlayActivity> {
                     putExtra("bookUrl", book.bookUrl)
                     putExtra("bookUrl", book.bookUrl)
                 }
                 }

+ 2 - 2
app/src/main/java/io/legado/app/ui/main/bookshelf/style2/BooksAdapterGrid.kt

@@ -5,12 +5,12 @@ import android.os.Bundle
 import android.view.LayoutInflater
 import android.view.LayoutInflater
 import android.view.ViewGroup
 import android.view.ViewGroup
 import androidx.recyclerview.widget.RecyclerView
 import androidx.recyclerview.widget.RecyclerView
-import io.legado.app.constant.BookType
 import io.legado.app.data.entities.Book
 import io.legado.app.data.entities.Book
 import io.legado.app.data.entities.BookGroup
 import io.legado.app.data.entities.BookGroup
 import io.legado.app.databinding.ItemBookshelfGridBinding
 import io.legado.app.databinding.ItemBookshelfGridBinding
 import io.legado.app.databinding.ItemBookshelfGridGroupBinding
 import io.legado.app.databinding.ItemBookshelfGridGroupBinding
 import io.legado.app.help.config.AppConfig
 import io.legado.app.help.config.AppConfig
+import io.legado.app.help.isLocal
 import io.legado.app.utils.invisible
 import io.legado.app.utils.invisible
 import splitties.views.onLongClick
 import splitties.views.onLongClick
 
 
@@ -111,7 +111,7 @@ class BooksAdapterGrid(context: Context, callBack: CallBack) :
     }
     }
 
 
     private fun upRefresh(binding: ItemBookshelfGridBinding, item: Book) {
     private fun upRefresh(binding: ItemBookshelfGridBinding, item: Book) {
-        if (item.origin != BookType.local && callBack.isUpdate(item.bookUrl)) {
+        if (!item.isLocal && callBack.isUpdate(item.bookUrl)) {
             binding.bvUnread.invisible()
             binding.bvUnread.invisible()
             binding.rlLoading.show()
             binding.rlLoading.show()
         } else {
         } else {

+ 2 - 2
app/src/main/java/io/legado/app/ui/main/bookshelf/style2/BooksAdapterList.kt

@@ -5,12 +5,12 @@ import android.os.Bundle
 import android.view.LayoutInflater
 import android.view.LayoutInflater
 import android.view.ViewGroup
 import android.view.ViewGroup
 import androidx.recyclerview.widget.RecyclerView
 import androidx.recyclerview.widget.RecyclerView
-import io.legado.app.constant.BookType
 import io.legado.app.data.entities.Book
 import io.legado.app.data.entities.Book
 import io.legado.app.data.entities.BookGroup
 import io.legado.app.data.entities.BookGroup
 import io.legado.app.databinding.ItemBookshelfListBinding
 import io.legado.app.databinding.ItemBookshelfListBinding
 import io.legado.app.databinding.ItemBookshelfListGroupBinding
 import io.legado.app.databinding.ItemBookshelfListGroupBinding
 import io.legado.app.help.config.AppConfig
 import io.legado.app.help.config.AppConfig
+import io.legado.app.help.isLocal
 import io.legado.app.utils.gone
 import io.legado.app.utils.gone
 import io.legado.app.utils.invisible
 import io.legado.app.utils.invisible
 import io.legado.app.utils.visible
 import io.legado.app.utils.visible
@@ -126,7 +126,7 @@ class BooksAdapterList(context: Context, callBack: CallBack) :
     }
     }
 
 
     private fun upRefresh(binding: ItemBookshelfListBinding, item: Book) {
     private fun upRefresh(binding: ItemBookshelfListBinding, item: Book) {
-        if (item.origin != BookType.local && callBack.isUpdate(item.bookUrl)) {
+        if (!item.isLocal && callBack.isUpdate(item.bookUrl)) {
             binding.bvUnread.invisible()
             binding.bvUnread.invisible()
             binding.rlLoading.show()
             binding.rlLoading.show()
         } else {
         } else {

+ 7 - 3
app/src/main/java/io/legado/app/ui/main/bookshelf/style2/BookshelfFragment2.kt

@@ -9,12 +9,16 @@ import androidx.recyclerview.widget.GridLayoutManager
 import androidx.recyclerview.widget.LinearLayoutManager
 import androidx.recyclerview.widget.LinearLayoutManager
 import androidx.recyclerview.widget.RecyclerView
 import androidx.recyclerview.widget.RecyclerView
 import io.legado.app.R
 import io.legado.app.R
-import io.legado.app.constant.*
+import io.legado.app.constant.AppConst
+import io.legado.app.constant.AppLog
+import io.legado.app.constant.EventBus
+import io.legado.app.constant.PreferKey
 import io.legado.app.data.appDb
 import io.legado.app.data.appDb
 import io.legado.app.data.entities.Book
 import io.legado.app.data.entities.Book
 import io.legado.app.data.entities.BookGroup
 import io.legado.app.data.entities.BookGroup
 import io.legado.app.databinding.FragmentBookshelf1Binding
 import io.legado.app.databinding.FragmentBookshelf1Binding
 import io.legado.app.help.config.AppConfig
 import io.legado.app.help.config.AppConfig
+import io.legado.app.help.isAudio
 import io.legado.app.lib.theme.accentColor
 import io.legado.app.lib.theme.accentColor
 import io.legado.app.lib.theme.primaryColor
 import io.legado.app.lib.theme.primaryColor
 import io.legado.app.ui.book.audio.AudioPlayActivity
 import io.legado.app.ui.book.audio.AudioPlayActivity
@@ -181,8 +185,8 @@ class BookshelfFragment2 : BaseBookshelfFragment(R.layout.fragment_bookshelf1),
 
 
     override fun onItemClick(position: Int) {
     override fun onItemClick(position: Int) {
         when (val item = getItem(position)) {
         when (val item = getItem(position)) {
-            is Book -> when (item.type) {
-                BookType.audio ->
+            is Book -> when {
+                item.isAudio ->
                     startActivity<AudioPlayActivity> {
                     startActivity<AudioPlayActivity> {
                         putExtra("bookUrl", item.bookUrl)
                         putExtra("bookUrl", item.bookUrl)
                     }
                     }