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