parent
3f1c95b7be
commit
05951a98ea
@ -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 |
||||||
|
} |
||||||
|
|
||||||
|
|
||||||
|
} |
@ -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) |
||||||
|
// } |
||||||
|
// } |
||||||
|
// } |
||||||
|
// } |
||||||
|
} |
Loading…
Reference in new issue