|
|
|
@ -9,6 +9,7 @@ import io.legado.app.base.BaseService |
|
|
|
|
import io.legado.app.constant.AppConst |
|
|
|
|
import io.legado.app.constant.EventBus |
|
|
|
|
import io.legado.app.constant.IntentAction |
|
|
|
|
import io.legado.app.data.entities.Book |
|
|
|
|
import io.legado.app.data.entities.BookChapter |
|
|
|
|
import io.legado.app.help.AppConfig |
|
|
|
|
import io.legado.app.help.BookHelp |
|
|
|
@ -24,14 +25,21 @@ import org.jetbrains.anko.toast |
|
|
|
|
import java.util.concurrent.Executors |
|
|
|
|
|
|
|
|
|
class DownloadService : BaseService() { |
|
|
|
|
private val threadCount = AppConfig.threadCount |
|
|
|
|
private var searchPool = |
|
|
|
|
Executors.newFixedThreadPool(AppConfig.threadCount).asCoroutineDispatcher() |
|
|
|
|
Executors.newFixedThreadPool(threadCount).asCoroutineDispatcher() |
|
|
|
|
private var tasks = CompositeCoroutine() |
|
|
|
|
private val handler = Handler() |
|
|
|
|
private var runnable: Runnable = Runnable { upDownload() } |
|
|
|
|
private val bookMap = hashMapOf<String, Book>() |
|
|
|
|
private val webBookMap = hashMapOf<String, WebBook>() |
|
|
|
|
private val downloadMap = hashMapOf<String, LinkedHashSet<BookChapter>>() |
|
|
|
|
private val downloadCount = hashMapOf<String, DownloadCount>() |
|
|
|
|
private val finalMap = hashMapOf<String, LinkedHashSet<BookChapter>>() |
|
|
|
|
private val downloadingList = arrayListOf<String>() |
|
|
|
|
|
|
|
|
|
@Volatile |
|
|
|
|
private var downloadingCount = 0 |
|
|
|
|
private var notificationContent = "正在启动下载" |
|
|
|
|
|
|
|
|
|
private val notificationBuilder by lazy { |
|
|
|
@ -78,92 +86,136 @@ class DownloadService : BaseService() { |
|
|
|
|
postEvent(EventBus.UP_DOWNLOAD, downloadMap) |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
private fun getBook(bookUrl: String): Book? { |
|
|
|
|
var book = bookMap[bookUrl] |
|
|
|
|
if (book == null) { |
|
|
|
|
book = App.db.bookDao().getBook(bookUrl) |
|
|
|
|
} |
|
|
|
|
if (book == null) { |
|
|
|
|
removeDownload(bookUrl) |
|
|
|
|
} |
|
|
|
|
return book |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
private fun getWebBook(bookUrl: String, origin: String): WebBook? { |
|
|
|
|
var webBook = webBookMap[origin] |
|
|
|
|
if (webBook == null) { |
|
|
|
|
App.db.bookSourceDao().getBookSource(origin)?.let { |
|
|
|
|
webBook = WebBook(it) |
|
|
|
|
} |
|
|
|
|
} |
|
|
|
|
if (webBook == null) { |
|
|
|
|
removeDownload(bookUrl) |
|
|
|
|
} |
|
|
|
|
return webBook |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
private fun addDownloadData(bookUrl: String?, start: Int, end: Int) { |
|
|
|
|
bookUrl ?: return |
|
|
|
|
if (downloadMap.containsKey(bookUrl)) { |
|
|
|
|
toast("该书已在下载列表") |
|
|
|
|
return |
|
|
|
|
} |
|
|
|
|
downloadCount[bookUrl] = DownloadCount() |
|
|
|
|
execute { |
|
|
|
|
val chapterMap = downloadMap[bookUrl] ?: linkedSetOf<BookChapter>().apply { |
|
|
|
|
downloadMap[bookUrl] = this |
|
|
|
|
} |
|
|
|
|
App.db.bookChapterDao().getChapterList(bookUrl, start, end).let { |
|
|
|
|
chapterMap.addAll(it) |
|
|
|
|
if (it.isNotEmpty()) { |
|
|
|
|
val chapters = linkedSetOf<BookChapter>() |
|
|
|
|
chapters.addAll(it) |
|
|
|
|
downloadMap[bookUrl] = chapters |
|
|
|
|
} |
|
|
|
|
} |
|
|
|
|
for (i in 0 until threadCount) { |
|
|
|
|
if (downloadingCount < threadCount) { |
|
|
|
|
download() |
|
|
|
|
} |
|
|
|
|
} |
|
|
|
|
} |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
private fun removeDownload(bookUrl: String?) { |
|
|
|
|
downloadMap.remove(bookUrl) |
|
|
|
|
finalMap.remove(bookUrl) |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
private fun updateNotification(downloadCount:DownloadCount, totalCount: Int, content: String){ |
|
|
|
|
notificationContent = |
|
|
|
|
"进度:${downloadCount.downloadFinishedCount}/$totalCount,成功:${downloadCount.successCount},$content" |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
private fun download() { |
|
|
|
|
val task = Coroutine.async(this, context = searchPool) { |
|
|
|
|
downloadMap.forEach { entry -> |
|
|
|
|
++downloadingCount |
|
|
|
|
tasks.add(Coroutine.async(this, context = searchPool) { |
|
|
|
|
if (!isActive) return@async |
|
|
|
|
if (!finalMap.containsKey(entry.key)) { |
|
|
|
|
val book = App.db.bookDao().getBook(entry.key) ?: return@async |
|
|
|
|
val bookSource = |
|
|
|
|
App.db.bookSourceDao().getBookSource(book.origin) ?: return@async |
|
|
|
|
val webBook = WebBook(bookSource) |
|
|
|
|
|
|
|
|
|
downloadCount[entry.key] = DownloadCount() |
|
|
|
|
|
|
|
|
|
entry.value.forEach { chapter -> |
|
|
|
|
if (!isActive) return@async |
|
|
|
|
if (downloadMap.containsKey(book.bookUrl)) { |
|
|
|
|
if (!BookHelp.hasContent(book, chapter)) { |
|
|
|
|
val bookChapter: BookChapter? = synchronized(this@DownloadService) { |
|
|
|
|
downloadMap.forEach { |
|
|
|
|
it.value.forEach { chapter -> |
|
|
|
|
if (!downloadingList.contains(chapter.url)) { |
|
|
|
|
downloadingList.add(chapter.url) |
|
|
|
|
return@synchronized chapter |
|
|
|
|
} |
|
|
|
|
} |
|
|
|
|
} |
|
|
|
|
return@synchronized null |
|
|
|
|
} |
|
|
|
|
if (bookChapter == null) { |
|
|
|
|
postDownloading(false) |
|
|
|
|
} else { |
|
|
|
|
val book = getBook(bookChapter.bookUrl) |
|
|
|
|
if (book == null) { |
|
|
|
|
postDownloading(true) |
|
|
|
|
return@async |
|
|
|
|
} |
|
|
|
|
val webBook = getWebBook(bookChapter.bookUrl, book.origin) |
|
|
|
|
if (webBook == null) { |
|
|
|
|
postDownloading(true) |
|
|
|
|
return@async |
|
|
|
|
} |
|
|
|
|
if (!BookHelp.hasContent(book, bookChapter)) { |
|
|
|
|
webBook.getContent( |
|
|
|
|
book, |
|
|
|
|
chapter, |
|
|
|
|
bookChapter, |
|
|
|
|
scope = this, |
|
|
|
|
context = searchPool |
|
|
|
|
).onSuccess(IO) { content -> |
|
|
|
|
downloadCount[entry.key]?.increaseSuccess() |
|
|
|
|
BookHelp.saveContent(book, chapter, content) |
|
|
|
|
} |
|
|
|
|
.onFinally(IO) { |
|
|
|
|
downloadCount[book.bookUrl]?.increaseSuccess() |
|
|
|
|
BookHelp.saveContent(book, bookChapter, content) |
|
|
|
|
}.onFinally(IO) { |
|
|
|
|
synchronized(this@DownloadService) { |
|
|
|
|
downloadCount[entry.key]?.increaseFinished() |
|
|
|
|
downloadCount[entry.key]?.let { updateNotification(it, entry.value.size, chapter.title) } |
|
|
|
|
downloadCount[book.bookUrl]?.increaseFinished() |
|
|
|
|
downloadCount[book.bookUrl]?.let { |
|
|
|
|
updateNotification( |
|
|
|
|
it, |
|
|
|
|
downloadMap[book.bookUrl]?.size, |
|
|
|
|
bookChapter.title |
|
|
|
|
) |
|
|
|
|
} |
|
|
|
|
val chapterMap = |
|
|
|
|
finalMap[book.bookUrl] |
|
|
|
|
?: linkedSetOf<BookChapter>().apply { |
|
|
|
|
finalMap[book.bookUrl] = this |
|
|
|
|
} |
|
|
|
|
chapterMap.add(chapter) |
|
|
|
|
if (chapterMap.size == entry.value.size) { |
|
|
|
|
chapterMap.add(bookChapter) |
|
|
|
|
if (chapterMap.size == downloadMap[book.bookUrl]?.size) { |
|
|
|
|
downloadMap.remove(book.bookUrl) |
|
|
|
|
finalMap.remove(book.bookUrl) |
|
|
|
|
downloadCount.remove(entry.key) |
|
|
|
|
downloadCount.remove(book.bookUrl) |
|
|
|
|
} |
|
|
|
|
} |
|
|
|
|
} |
|
|
|
|
} else{ |
|
|
|
|
postDownloading(true) |
|
|
|
|
} else { |
|
|
|
|
//无需下载的,设置为增加成功 |
|
|
|
|
downloadCount[entry.key]?.increaseSuccess() |
|
|
|
|
downloadCount[entry.key]?.increaseFinished() |
|
|
|
|
} |
|
|
|
|
downloadCount[book.bookUrl]?.increaseSuccess() |
|
|
|
|
downloadCount[book.bookUrl]?.increaseFinished() |
|
|
|
|
postDownloading(true) |
|
|
|
|
} |
|
|
|
|
} |
|
|
|
|
}) |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
} |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
tasks.add(task) |
|
|
|
|
task.invokeOnCompletion { |
|
|
|
|
tasks.remove(task) |
|
|
|
|
if (tasks.isEmpty) { |
|
|
|
|
stopSelf() |
|
|
|
|
private fun postDownloading(hasChapter: Boolean) { |
|
|
|
|
--downloadingCount |
|
|
|
|
if (hasChapter) { |
|
|
|
|
download() |
|
|
|
|
} else { |
|
|
|
|
if (downloadingCount < 1) { |
|
|
|
|
stopDownload() |
|
|
|
|
} |
|
|
|
|
} |
|
|
|
|
} |
|
|
|
@ -180,6 +232,15 @@ class DownloadService : BaseService() { |
|
|
|
|
handler.postDelayed(runnable, 1000) |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
private fun updateNotification( |
|
|
|
|
downloadCount: DownloadCount, |
|
|
|
|
totalCount: Int?, |
|
|
|
|
content: String |
|
|
|
|
) { |
|
|
|
|
notificationContent = |
|
|
|
|
"进度:${downloadCount.downloadFinishedCount}/$totalCount,成功:${downloadCount.successCount},$content" |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
/** |
|
|
|
|
* 更新通知 |
|
|
|
|
*/ |
|
|
|
|