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