BookController.kt 12 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325
  1. package io.legato.kazusa.api.controller
  2. import android.graphics.Bitmap
  3. import android.graphics.drawable.Drawable
  4. import androidx.core.graphics.drawable.toBitmap
  5. import com.bumptech.glide.Glide
  6. import io.legato.kazusa.api.ReturnData
  7. import io.legato.kazusa.data.appDb
  8. import io.legato.kazusa.data.entities.Book
  9. import io.legato.kazusa.data.entities.BookProgress
  10. import io.legato.kazusa.data.entities.BookSource
  11. import io.legato.kazusa.help.AppWebDav
  12. import io.legato.kazusa.help.CacheManager
  13. import io.legato.kazusa.help.book.BookHelp
  14. import io.legato.kazusa.help.book.ContentProcessor
  15. import io.legato.kazusa.help.book.isLocal
  16. import io.legato.kazusa.help.config.AppConfig
  17. import io.legato.kazusa.help.glide.ImageLoader
  18. import io.legato.kazusa.model.BookCover
  19. import io.legato.kazusa.model.ImageProvider
  20. import io.legato.kazusa.model.ReadBook
  21. import io.legato.kazusa.model.localBook.LocalBook
  22. import io.legato.kazusa.model.webBook.WebBook
  23. import io.legato.kazusa.utils.GSON
  24. import io.legato.kazusa.utils.cnCompare
  25. import io.legato.kazusa.utils.fromJsonObject
  26. import io.legato.kazusa.utils.printOnDebug
  27. import io.legato.kazusa.utils.stackTraceStr
  28. import kotlinx.coroutines.delay
  29. import kotlinx.coroutines.runBlocking
  30. import splitties.init.appCtx
  31. import java.io.File
  32. import java.util.WeakHashMap
  33. import java.util.concurrent.TimeUnit
  34. object BookController {
  35. private lateinit var book: Book
  36. private var bookSource: BookSource? = null
  37. private var bookUrl: String = ""
  38. private val defaultCoverCache by lazy { WeakHashMap<Drawable, Bitmap>() }
  39. /**
  40. * 书架所有书籍
  41. */
  42. val bookshelf: ReturnData
  43. get() {
  44. val books = appDb.bookDao.all
  45. val returnData = ReturnData()
  46. return if (books.isEmpty()) {
  47. returnData.setErrorMsg("还没有添加小说")
  48. } else {
  49. val data = when (AppConfig.bookshelfSort) {
  50. 1 -> books.sortedByDescending { it.latestChapterTime }
  51. 2 -> books.sortedWith { o1, o2 ->
  52. o1.name.cnCompare(o2.name)
  53. }
  54. 3 -> books.sortedBy { it.order }
  55. else -> books.sortedByDescending { it.durChapterTime }
  56. }
  57. returnData.setData(data)
  58. }
  59. }
  60. /**
  61. * 获取封面
  62. */
  63. fun getCover(parameters: Map<String, List<String>>): ReturnData {
  64. val returnData = ReturnData()
  65. val coverPath = parameters["path"]?.firstOrNull()
  66. val ftBitmap = ImageLoader.loadBitmap(appCtx, coverPath)
  67. .override(84, 112)
  68. .centerCrop()
  69. .submit()
  70. return try {
  71. returnData.setData(ftBitmap.get(3, TimeUnit.SECONDS))
  72. } catch (e: Exception) {
  73. try {
  74. val defaultBitmap = defaultCoverCache.getOrPut(BookCover.defaultDrawable) {
  75. Glide.with(appCtx)
  76. .asBitmap()
  77. .load(BookCover.defaultDrawable.toBitmap())
  78. .override(84, 112)
  79. .centerCrop()
  80. .submit()
  81. .get()
  82. }
  83. returnData.setData(defaultBitmap)
  84. } catch (e: Exception) {
  85. returnData.setErrorMsg(e.localizedMessage ?: "getCover error")
  86. }
  87. }
  88. }
  89. /**
  90. * 获取正文图片
  91. */
  92. fun getImg(parameters: Map<String, List<String>>): ReturnData {
  93. val returnData = ReturnData()
  94. val bookUrl = parameters["url"]?.firstOrNull()
  95. ?: return returnData.setErrorMsg("bookUrl为空")
  96. val src = parameters["path"]?.firstOrNull()
  97. ?: return returnData.setErrorMsg("图片链接为空")
  98. val width = parameters["width"]?.firstOrNull()?.toInt() ?: 640
  99. if (this.bookUrl != bookUrl) {
  100. this.book = appDb.bookDao.getBook(bookUrl)
  101. ?: return returnData.setErrorMsg("bookUrl不对")
  102. this.bookSource = appDb.bookSourceDao.getBookSource(book.origin)
  103. }
  104. this.bookUrl = bookUrl
  105. val bitmap = runBlocking {
  106. ImageProvider.cacheImage(book, src, bookSource)
  107. ImageProvider.getImage(book, src, width)
  108. }
  109. return returnData.setData(bitmap)
  110. }
  111. /**
  112. * 更新目录
  113. */
  114. fun refreshToc(parameters: Map<String, List<String>>): ReturnData {
  115. val returnData = ReturnData()
  116. try {
  117. val bookUrl = parameters["url"]?.firstOrNull()
  118. if (bookUrl.isNullOrEmpty()) {
  119. return returnData.setErrorMsg("参数url不能为空,请指定书籍地址")
  120. }
  121. val book = appDb.bookDao.getBook(bookUrl)
  122. ?: return returnData.setErrorMsg("未在数据库找到对应书籍,请先添加")
  123. if (book.isLocal) {
  124. val toc = LocalBook.getChapterList(book)
  125. appDb.bookChapterDao.delByBook(book.bookUrl)
  126. appDb.bookChapterDao.insert(*toc.toTypedArray())
  127. appDb.bookDao.update(book)
  128. return returnData.setData(toc)
  129. } else {
  130. val bookSource = appDb.bookSourceDao.getBookSource(book.origin)
  131. ?: return returnData.setErrorMsg("未找到对应书源,请换源")
  132. val toc = runBlocking {
  133. if (book.tocUrl.isBlank()) {
  134. WebBook.getBookInfoAwait(bookSource, book)
  135. }
  136. WebBook.getChapterListAwait(bookSource, book).getOrThrow()
  137. }
  138. appDb.bookChapterDao.delByBook(book.bookUrl)
  139. appDb.bookChapterDao.insert(*toc.toTypedArray())
  140. appDb.bookDao.update(book)
  141. return returnData.setData(toc)
  142. }
  143. } catch (e: Exception) {
  144. return returnData.setErrorMsg(e.localizedMessage ?: "refresh toc error")
  145. }
  146. }
  147. /**
  148. * 获取目录
  149. */
  150. fun getChapterList(parameters: Map<String, List<String>>): ReturnData {
  151. val bookUrl = parameters["url"]?.firstOrNull()
  152. val returnData = ReturnData()
  153. if (bookUrl.isNullOrEmpty()) {
  154. return returnData.setErrorMsg("参数url不能为空,请指定书籍地址")
  155. }
  156. val chapterList = appDb.bookChapterDao.getChapterList(bookUrl)
  157. if (chapterList.isEmpty()) {
  158. return refreshToc(parameters)
  159. }
  160. return returnData.setData(chapterList)
  161. }
  162. /**
  163. * 获取正文
  164. */
  165. fun getBookContent(parameters: Map<String, List<String>>): ReturnData {
  166. val bookUrl = parameters["url"]?.firstOrNull()
  167. val index = parameters["index"]?.firstOrNull()?.toInt()
  168. val returnData = ReturnData()
  169. if (bookUrl.isNullOrEmpty()) {
  170. return returnData.setErrorMsg("参数url不能为空,请指定书籍地址")
  171. }
  172. if (index == null) {
  173. return returnData.setErrorMsg("参数index不能为空, 请指定目录序号")
  174. }
  175. val book = appDb.bookDao.getBook(bookUrl)
  176. val chapter = runBlocking {
  177. var chapter = appDb.bookChapterDao.getChapter(bookUrl, index)
  178. var wait = 0
  179. while (chapter == null && wait < 30) {
  180. delay(1000)
  181. chapter = appDb.bookChapterDao.getChapter(bookUrl, index)
  182. wait++
  183. }
  184. chapter
  185. }
  186. if (book == null || chapter == null) {
  187. return returnData.setErrorMsg("未找到")
  188. }
  189. var content: String? = BookHelp.getContent(book, chapter)
  190. if (content != null) {
  191. val contentProcessor = ContentProcessor.get(book.name, book.origin)
  192. content = runBlocking {
  193. contentProcessor.getContent(book, chapter, content, includeTitle = false)
  194. .toString()
  195. }
  196. return returnData.setData(content)
  197. }
  198. val bookSource = appDb.bookSourceDao.getBookSource(book.origin)
  199. ?: return returnData.setErrorMsg("未找到书源")
  200. try {
  201. content = runBlocking {
  202. WebBook.getContentAwait(bookSource, book, chapter).let {
  203. val contentProcessor = ContentProcessor.get(book.name, book.origin)
  204. contentProcessor.getContent(book, chapter, it, includeTitle = false)
  205. .toString()
  206. }
  207. }
  208. returnData.setData(content)
  209. } catch (e: Exception) {
  210. returnData.setErrorMsg(e.stackTraceStr)
  211. }
  212. return returnData
  213. }
  214. /**
  215. * 保存书籍
  216. */
  217. suspend fun saveBook(postData: String?): ReturnData {
  218. val returnData = ReturnData()
  219. GSON.fromJsonObject<Book>(postData).getOrNull()?.let { book ->
  220. AppWebDav.uploadBookProgress(book)
  221. book.save()
  222. return returnData.setData("")
  223. }
  224. return returnData.setErrorMsg("格式不对")
  225. }
  226. /**
  227. * 删除书籍
  228. */
  229. fun deleteBook(postData: String?): ReturnData {
  230. val returnData = ReturnData()
  231. GSON.fromJsonObject<Book>(postData).getOrNull()?.let { book ->
  232. book.delete()
  233. return returnData.setData("")
  234. }
  235. return returnData.setErrorMsg("格式不对")
  236. }
  237. /**
  238. * 保存进度
  239. */
  240. suspend fun saveBookProgress(postData: String?): ReturnData {
  241. val returnData = ReturnData()
  242. GSON.fromJsonObject<BookProgress>(postData)
  243. .onFailure { it.printOnDebug() }
  244. .getOrNull()?.let { bookProgress ->
  245. appDb.bookDao.getBook(bookProgress.name, bookProgress.author)?.let { book ->
  246. book.durChapterIndex = bookProgress.durChapterIndex
  247. book.durChapterPos = bookProgress.durChapterPos
  248. book.durChapterTitle = bookProgress.durChapterTitle
  249. book.durChapterTime = bookProgress.durChapterTime
  250. AppWebDav.uploadBookProgress(bookProgress) {
  251. book.syncTime = System.currentTimeMillis()
  252. }
  253. appDb.bookDao.update(book)
  254. ReadBook.book?.let {
  255. if (it.name == bookProgress.name &&
  256. it.author == bookProgress.author
  257. ) {
  258. ReadBook.webBookProgress = bookProgress
  259. }
  260. }
  261. return returnData.setData("")
  262. }
  263. }
  264. return returnData.setErrorMsg("格式不对")
  265. }
  266. /**
  267. * 添加本地书籍
  268. */
  269. fun addLocalBook(
  270. parameters: Map<String, List<String>>,
  271. files: Map<String, String>
  272. ): ReturnData {
  273. val returnData = ReturnData()
  274. val fileName = parameters["fileName"]?.firstOrNull()
  275. ?: return returnData.setErrorMsg("fileName 不能为空")
  276. val fileData = files["fileData"]
  277. ?: return returnData.setErrorMsg("fileData 不能为空")
  278. kotlin.runCatching {
  279. val uri = LocalBook.saveBookFile(File(fileData).inputStream(), fileName)
  280. LocalBook.importFile(uri)
  281. }.onFailure {
  282. return when (it) {
  283. is SecurityException -> returnData.setErrorMsg("需重新设置书籍保存位置!")
  284. else -> returnData.setErrorMsg("保存书籍错误\n${it.localizedMessage}")
  285. }
  286. }
  287. return returnData.setData(true)
  288. }
  289. /**
  290. * 保存web阅读界面配置
  291. */
  292. fun saveWebReadConfig(postData: String?): ReturnData {
  293. val returnData = ReturnData()
  294. postData?.let {
  295. CacheManager.put("webReadConfig", postData)
  296. } ?: CacheManager.delete("webReadConfig")
  297. return returnData.setData("")
  298. }
  299. /**
  300. * 获取web阅读界面配置
  301. */
  302. fun getWebReadConfig(): ReturnData {
  303. val returnData = ReturnData()
  304. val data = CacheManager.get("webReadConfig")
  305. ?: return returnData.setErrorMsg("没有配置")
  306. return returnData.setData(data)
  307. }
  308. }