feat: 上传功能初步完成

pull/1894/head
谢意帆 3 years ago
parent 3f1c95b7be
commit 05951a98ea
  1. 15
      app/src/main/java/io/legado/app/ui/book/info/BookInfoActivity.kt
  2. 9
      app/src/main/java/io/legado/app/ui/book/remote/RemoteBookActivity.kt
  3. 4
      app/src/main/java/io/legado/app/ui/book/remote/RemoteBookAdapter.kt
  4. 51
      app/src/main/java/io/legado/app/ui/book/remote/RemoteBookManager.kt
  5. 69
      app/src/main/java/io/legado/app/ui/book/remote/RemoteBookViewModel.kt
  6. 260
      app/src/main/java/io/legado/app/ui/book/remote/manager/RemoteBookWebDav.kt
  7. 2
      app/src/main/res/layout/item_remote_book.xml

@ -2,6 +2,7 @@ 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
@ -31,6 +32,7 @@ import io.legado.app.ui.book.changesource.ChangeBookSourceDialog
import io.legado.app.ui.book.group.GroupSelectDialog import io.legado.app.ui.book.group.GroupSelectDialog
import io.legado.app.ui.book.info.edit.BookInfoEditActivity import io.legado.app.ui.book.info.edit.BookInfoEditActivity
import io.legado.app.ui.book.read.ReadBookActivity import io.legado.app.ui.book.read.ReadBookActivity
import io.legado.app.ui.book.remote.manager.RemoteBookWebDav
import io.legado.app.ui.book.search.SearchActivity import io.legado.app.ui.book.search.SearchActivity
import io.legado.app.ui.book.source.edit.BookSourceEditActivity import io.legado.app.ui.book.source.edit.BookSourceEditActivity
import io.legado.app.ui.book.toc.TocActivityResult import io.legado.app.ui.book.toc.TocActivityResult
@ -198,6 +200,19 @@ class BookInfoActivity :
item.isChecked = !item.isChecked item.isChecked = !item.isChecked
if (!item.isChecked) longToastOnUi(R.string.need_more_time_load_content) if (!item.isChecked) longToastOnUi(R.string.need_more_time_load_content)
} }
R.id.menu_upload -> {
launch {
val uri = Uri.parse(viewModel.bookData.value?.bookUrl.toString())
RemoteBookWebDav.getFilePathFromContentUri(uri, contentResolver)
// val doc = DocumentFile.fromTreeUri(appCtx,uri)
// if (uri.isContentScheme()){
// uri.path
// }
// RemoteBookWebDav.upload()
}
}
} }
return super.onCompatOptionsItemSelected(item) return super.onCompatOptionsItemSelected(item)
} }

@ -1,19 +1,14 @@
package io.legado.app.ui.book.remote package io.legado.app.ui.book.remote
import android.os.Bundle import android.os.Bundle
import android.util.Log
import androidx.activity.viewModels import androidx.activity.viewModels
import androidx.recyclerview.widget.LinearLayoutManager import androidx.recyclerview.widget.LinearLayoutManager
import io.legado.app.base.VMBaseActivity import io.legado.app.base.VMBaseActivity
import io.legado.app.data.entities.Book
import io.legado.app.databinding.ActivityRemoteBookBinding import io.legado.app.databinding.ActivityRemoteBookBinding
import io.legado.app.lib.theme.backgroundColor
import io.legado.app.utils.toastOnUi
import io.legado.app.utils.viewbindingdelegate.viewBinding import io.legado.app.utils.viewbindingdelegate.viewBinding
import kotlinx.coroutines.flow.conflate
import kotlinx.coroutines.launch import kotlinx.coroutines.launch
/** /**
@ -51,7 +46,7 @@ class RemoteBookActivity : VMBaseActivity<ActivityRemoteBookBinding,RemoteBookVi
// viewModel.getRemoteBooks().observe(this, { // viewModel.getRemoteBooks().observe(this, {
// adapter.submitList(it) // adapter.submitList(it)
// }) // })
viewModel.loadRemoteBookList() // viewModel.loadRemoteBookList()
launch { launch {
viewModel.dataFlow.collect { remoteBooks -> viewModel.dataFlow.collect { remoteBooks ->
@ -66,6 +61,6 @@ class RemoteBookActivity : VMBaseActivity<ActivityRemoteBookBinding,RemoteBookVi
override fun download(remoteBook: RemoteBook) { override fun download(remoteBook: RemoteBook) {
viewModel.downloadRemoteBook(remoteBook.url) viewModel.downloadRemoteBook(remoteBook.urlName)
} }
} }

@ -38,8 +38,8 @@ class RemoteBookAdapter (context: Context, val callBack: CallBack) :
payloads: MutableList<Any> payloads: MutableList<Any>
) { ) {
binding.run { binding.run {
tvName.text = item.name.substringBeforeLast(".") tvName.text = item.filename
tvContentType.text = item.name.substringAfterLast(".") tvContentType.text = item.contentType
tvSize.text = ConvertUtils.formatFileSize(item.size) tvSize.text = ConvertUtils.formatFileSize(item.size)
tvDate.text = LocalDateTimeUtil.format(LocalDateTimeUtil.of(item.lastModify), "yyyy-MM-dd") tvDate.text = LocalDateTimeUtil.format(LocalDateTimeUtil.of(item.lastModify), "yyyy-MM-dd")
} }

@ -0,0 +1,51 @@
package io.legado.app.ui.book.remote
import android.content.ContentResolver
import android.database.Cursor
import android.net.Uri
import android.provider.MediaStore
import android.provider.OpenableColumns
import android.webkit.MimeTypeMap
import java.io.File
import kotlin.random.Random
abstract class RemoteBookManager {
protected val remoteBookFolder : String = "books"
protected val contentTypeList: ArrayList<String> = arrayListOf("epub","txt")
abstract suspend fun initRemoteContext()
abstract suspend fun getRemoteBookList(): MutableList<RemoteBook>
abstract suspend fun upload(localBookUrl: String): Boolean
abstract suspend fun delete(remoteBookUrl: String): Boolean
abstract suspend fun getRemoteBook(remoteBookUrl: String): RemoteBook
/**
* 把content uri转为 文件路径
*
* @param contentUri 要转换的content uri
* @param contentResolver 解析器
* @return
*/
fun getFilePathFromContentUri(
contentUri: Uri,
contentResolver: ContentResolver
): String? {
val filePath: String
if (contentUri.scheme == ContentResolver.SCHEME_FILE)
return File(requireNotNull(contentUri.path)).absolutePath
else if(contentUri.scheme == ContentResolver.SCHEME_CONTENT){
val filePathColumn = arrayOf(MediaStore.MediaColumns.DATA)
val cursor: Cursor? =
contentResolver.query(contentUri, filePathColumn, null, null, null)
cursor!!.moveToFirst()
val columnIndex: Int = cursor.getColumnIndex(filePathColumn[0])
filePath = cursor.getString(columnIndex)
cursor.close()
return filePath
}
return null
}
}

@ -1,13 +1,15 @@
package io.legado.app.ui.book.remote package io.legado.app.ui.book.remote
import android.app.Application import android.app.Application
import android.net.Uri
import android.util.Log import android.util.Log
import io.legado.app.base.BaseViewModel import io.legado.app.base.BaseViewModel
import io.legado.app.constant.PreferKey import io.legado.app.constant.PreferKey
import io.legado.app.lib.webdav.Authorization import io.legado.app.lib.webdav.Authorization
import io.legado.app.lib.webdav.WebDav import io.legado.app.lib.webdav.WebDav
import io.legado.app.model.localBook.LocalBook
import io.legado.app.ui.book.remote.manager.RemoteBookWebDav
import io.legado.app.utils.FileUtils import io.legado.app.utils.FileUtils
import io.legado.app.utils.exists
import io.legado.app.utils.externalFiles import io.legado.app.utils.externalFiles
import io.legado.app.utils.getPrefString import io.legado.app.utils.getPrefString
import kotlinx.coroutines.* import kotlinx.coroutines.*
@ -60,35 +62,18 @@ class RemoteBookViewModel(application: Application): BaseViewModel(application){
fun loadRemoteBookList() { fun loadRemoteBookList() {
execute { execute {
dataCallback?.clear() dataCallback?.clear()
kotlin.runCatching { RemoteBookWebDav.getRemoteBookList()
authorization = null }
val account = appCtx.getPrefString(PreferKey.webDavAccount)
val password = appCtx.getPrefString(PreferKey.webDavPassword)
if (!account.isNullOrBlank() && !password.isNullOrBlank()) {
val mAuthorization = Authorization(account, password)
authorization = mAuthorization
}
}
authorization?.let { it ->
val remoteWebDavFileList = WebDav("http://txc.qianfanguojin.top/",it).listFiles()
val remoteList = remoteWebDavFileList.map {
RemoteBook(it.displayName,it.urlName,it.size,"epub",it.lastModify)
}
dataCallback?.setItems(remoteList)
}
// dataCallback?.setItems() // dataCallback?.setItems()
} }
// dataCallback?.setItems(listOf("1", "2", "3")) // dataCallback?.setItems(listOf("1", "2", "3"))
}
fun downloadRemoteBook(urlName: String) { fun downloadRemoteBook(urlName: String) {
val saveFolder = "${appCtx.externalFiles.absolutePath}${File.separator}${remoteBookFolderName}"
val trueCodeURLName = String(urlName.toByteArray(Charset.forName("GBK")), Charset.forName("UTF-8"))
val saveFilePath = "${saveFolder}${trueCodeURLName}"
execute { execute {
// kotlin.runCatching {
// val remoteWebDavFile = WebDav("http://txc.qianfanguojin.top/",authorization!!).getFile(url)
// val remoteBook = RemoteBook(remoteWebDavFile.displayName,remoteWebDavFile.urlName,remoteWebDavFile.size,"epub",remoteWebDavFile.lastModify)
// dataCallback?.addItems(listOf(remoteBook))
// }
kotlin.runCatching { kotlin.runCatching {
authorization = null authorization = null
val account = appCtx.getPrefString(PreferKey.webDavAccount) val account = appCtx.getPrefString(PreferKey.webDavAccount)
@ -100,20 +85,31 @@ class RemoteBookViewModel(application: Application): BaseViewModel(application){
} }
authorization?.let { it -> authorization?.let { it ->
Log.e("TAG", "downloadRemoteBook: 1", )
val saveFolder = "${appCtx.externalFiles.absolutePath}${File.separator}${remoteBookFolderName}"
FileUtils.createFolderIfNotExist(saveFolder).run{ FileUtils.createFolderIfNotExist(saveFolder).run{
withTimeout(15000L) {
// Log.e("TAG", "downloadRemoteBook: 2 ${appCtx.externalFiles.absoluteFile}", ) val webdav = WebDav(
val trueCodeURLName = String(urlName.toByteArray(Charset.forName("GBK")), Charset.forName("UTF-8")) "http://txc.qianfanguojin.top${trueCodeURLName}",
withTimeout(15000L) { it
val webdav = WebDav("http://txc.qianfanguojin.top${trueCodeURLName}", it) )
webdav.downloadTo("${saveFolder}${trueCodeURLName}", true).apply { webdav.downloadTo(saveFilePath, true)
} }
} }
} }
}.onFinally {
addToBookshelf(hashSetOf("${saveFolder}${trueCodeURLName}")){
Log.e("TAG", "downloadRemoteBook: add", )
} }
} }
}
fun addToBookshelf(uriList: HashSet<String>, finally: () -> Unit) {
execute {
uriList.forEach {
LocalBook.importFile(Uri.parse(it))
}
}.onFinally {
finally.invoke()
}
} }
interface DataCallback { interface DataCallback {
@ -127,9 +123,10 @@ class RemoteBookViewModel(application: Application): BaseViewModel(application){
} }
data class RemoteBook( data class RemoteBook(
val name: String, val filename: String,
val url: String, val urlName: String,
val size: Long, val size: Long,
val contentType: String, val contentType: String,
val lastModify: Long val lastModify: Long
) )

@ -0,0 +1,260 @@
package io.legado.app.ui.book.remote.manager
import io.legado.app.constant.PreferKey
import io.legado.app.exception.NoStackTraceException
import io.legado.app.help.config.AppConfig
import io.legado.app.lib.webdav.Authorization
import io.legado.app.lib.webdav.WebDav
import io.legado.app.lib.webdav.WebDavFile
import io.legado.app.ui.book.remote.RemoteBook
import io.legado.app.ui.book.remote.RemoteBookManager
import io.legado.app.utils.*
import kotlinx.coroutines.runBlocking
import splitties.init.appCtx
import java.io.File
import java.nio.charset.Charset
object RemoteBookWebDav : RemoteBookManager() {
private const val defaultWebDavUrl = "https://dav.jianguoyun.com/dav/"
private var authorization: Authorization? = null
private val remoteBookUrl get() = "${rootWebDavUrl}${remoteBookFolder}"
init {
runBlocking {
initRemoteContext()
}
}
private val rootWebDavUrl: String
get() {
val configUrl = appCtx.getPrefString(PreferKey.webDavUrl)?.trim()
var url = if (configUrl.isNullOrEmpty()) defaultWebDavUrl else configUrl
if (!url.endsWith("/")) url = "${url}/"
AppConfig.webDavDir?.trim()?.let {
if (it.isNotEmpty()) {
url = "${url}${it}/"
}
}
return url
}
override suspend fun initRemoteContext() {
kotlin.runCatching {
authorization = null
val account = appCtx.getPrefString(PreferKey.webDavAccount)
val password = appCtx.getPrefString(PreferKey.webDavPassword)
if (!account.isNullOrBlank() && !password.isNullOrBlank()) {
val mAuthorization = Authorization(account, password)
WebDav(rootWebDavUrl, mAuthorization).makeAsDir()
WebDav(remoteBookUrl, mAuthorization).makeAsDir()
authorization = mAuthorization
}
}.onFailure {
it.printStackTrace()
}
}
@Throws(Exception::class)
override suspend fun getRemoteBookList(): MutableList<RemoteBook> {
val remoteBooks = mutableListOf<RemoteBook>()
authorization?.let {
var remoteWebDavFileList : List<WebDavFile>? = null
kotlin.runCatching {
remoteWebDavFileList = WebDav(remoteBookUrl, it).listFiles()
}
remoteWebDavFileList = remoteWebDavFileList!!.reversed()
remoteWebDavFileList!!.forEach { webDavFile ->
val webDavFileName = webDavFile.displayName
val webDavUrlName = webDavFile.urlName
// 转码
val trueFileName = String(webDavFileName.toByteArray(Charset.forName("GBK")), Charset.forName("UTF-8"))
val trueUrlName = String(webDavUrlName.toByteArray(Charset.forName("GBK")), Charset.forName("UTF-8"))
//分割文件名和后缀
val filename = trueFileName.substringBeforeLast(".")
val fileExtension = trueFileName.substringAfterLast(".")
//扩展名符合阅读的格式则认为是书籍
if (contentTypeList.contains(fileExtension)) {
remoteBooks.add(RemoteBook(filename,trueUrlName,webDavFile.size,fileExtension,webDavFile.lastModify))
}
}
} ?: throw NoStackTraceException("webDav没有配置")
return remoteBooks
}
override suspend fun getRemoteBook(remoteBookUrl: String): RemoteBook {
TODO("Not yet implemented")
}
/**
* 上传本地导入的书籍到远程
*/
override suspend fun upload(localBookUrl: String): Boolean {
if (!NetworkUtils.isAvailable()) return false
val localBookName = localBookUrl.substringAfterLast(File.separator)
authorization?.let {
val putUrl = "${remoteBookUrl}${File.separator}${localBookName}"
WebDav(putUrl, it).upload(localBookUrl)
}
return true
}
override suspend fun delete(remoteBookUrl: String): Boolean {
TODO("Not yet implemented")
}
// suspend fun showRestoreDialog(context: Context) {
// val names = withContext(Dispatchers.IO) { getBackupNames() }
// if (names.isNotEmpty()) {
// withContext(Dispatchers.Main) {
// context.selector(
// title = context.getString(R.string.select_restore_file),
// items = names
// ) { _, index ->
// if (index in 0 until names.size) {
// Coroutine.async {
// restoreWebDav(names[index])
// }.onError {
// appCtx.toastOnUi("WebDav恢复出错\n${it.localizedMessage}")
// }
// }
// }
// }
// } else {
// throw NoStackTraceException("Web dav no back up file")
// }
// }
//
// @Throws(WebDavException::class)
// suspend fun restoreWebDav(name: String) {
// authorization?.let {
// val webDav = WebDav(rootWebDavUrl + name, it)
// webDav.downloadTo(zipFilePath, true)
// @Suppress("BlockingMethodInNonBlockingContext")
// ZipUtils.unzipFile(zipFilePath, Backup.backupPath)
// Restore.restoreDatabase()
// Restore.restoreConfig()
// }
// }
//
// suspend fun hasBackUp(): Boolean {
// authorization?.let {
// val url = "${rootWebDavUrl}${backupFileName}"
// return WebDav(url, it).exists()
// }
// return false
// }
//
// suspend fun lastBackUp(): Result<WebDavFile?> {
// return kotlin.runCatching {
// authorization?.let {
// var lastBackupFile: WebDavFile? = null
// WebDav(rootWebDavUrl, it).listFiles().reversed().forEach { webDavFile ->
// if (webDavFile.displayName.startsWith("backup")) {
// if (lastBackupFile == null
// || webDavFile.lastModify > lastBackupFile!!.lastModify
// ) {
// lastBackupFile = webDavFile
// }
// }
// }
// lastBackupFile
// }
// }
// }
//
// @Throws(Exception::class)
// suspend fun backUpWebDav(path: String) {
// if (!NetworkUtils.isAvailable()) return
// authorization?.let {
// val paths = arrayListOf(*Backup.backupFileNames)
// for (i in 0 until paths.size) {
// paths[i] = path + File.separator + paths[i]
// }
// FileUtils.delete(zipFilePath)
// if (ZipUtils.zipFiles(paths, zipFilePath)) {
// val putUrl = "${rootWebDavUrl}${backupFileName}"
// WebDav(putUrl, it).upload(zipFilePath)
// }
// }
// }
//
// suspend fun exportWebDav(byteArray: ByteArray, fileName: String) {
// if (!NetworkUtils.isAvailable()) return
// try {
// authorization?.let {
// // 如果导出的本地文件存在,开始上传
// val putUrl = exportsWebDavUrl + fileName
// WebDav(putUrl, it).upload(byteArray, "text/plain")
// }
// } catch (e: Exception) {
// val msg = "WebDav导出\n${e.localizedMessage}"
// AppLog.put(msg)
// appCtx.toastOnUi(msg)
// }
// }
//
// fun uploadBookProgress(book: Book) {
// val authorization = authorization ?: return
// if (!syncBookProgress) return
// if (!NetworkUtils.isAvailable()) return
// Coroutine.async {
// val bookProgress = BookProgress(book)
// val json = GSON.toJson(bookProgress)
// val url = getProgressUrl(book)
// WebDav(url, authorization).upload(json.toByteArray(), "application/json")
// }.onError {
// AppLog.put("上传进度失败\n${it.localizedMessage}")
// }
// }
//
// private fun getProgressUrl(book: Book): String {
// return bookProgressUrl + book.name + "_" + book.author + ".json"
// }
//
// /**
// * 获取书籍进度
// */
// suspend fun getBookProgress(book: Book): BookProgress? {
// authorization?.let {
// val url = getProgressUrl(book)
// kotlin.runCatching {
// WebDav(url, it).download().let { byteArray ->
// val json = String(byteArray)
// if (json.isJson()) {
// return GSON.fromJsonObject<BookProgress>(json).getOrNull()
// }
// }
// }
// }
// return null
// }
//
// suspend fun downloadAllBookProgress() {
// authorization ?: return
// if (!NetworkUtils.isAvailable()) return
// appDb.bookDao.all.forEach { book ->
// getBookProgress(book)?.let { bookProgress ->
// if (bookProgress.durChapterIndex > book.durChapterIndex
// || (bookProgress.durChapterIndex == book.durChapterIndex
// && bookProgress.durChapterPos > book.durChapterPos)
// ) {
// book.durChapterIndex = bookProgress.durChapterIndex
// book.durChapterPos = bookProgress.durChapterPos
// book.durChapterTitle = bookProgress.durChapterTitle
// book.durChapterTime = bookProgress.durChapterTime
// appDb.bookDao.update(book)
// }
// }
// }
// }
}

@ -55,7 +55,7 @@
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:ellipsize="end" android:ellipsize="end"
android:lines="1" android:lines="1"
android:text="\u4e28" android:text=""
android:textColor="@color/tv_text_summary" android:textColor="@color/tv_text_summary"
android:textSize="11sp" /> android:textSize="11sp" />

Loading…
Cancel
Save