fix: 书籍保存的webdav链接凭据错误

This commit is contained in:
Xwite 2023-03-06 00:25:05 +08:00
parent c918a403b1
commit 3f53c10b8d
17 changed files with 203 additions and 33 deletions

View File

@ -20,13 +20,13 @@ val appDb by lazy {
} }
@Database( @Database(
version = 61, version = 62,
exportSchema = true, exportSchema = true,
entities = [Book::class, BookGroup::class, BookSource::class, BookChapter::class, entities = [Book::class, BookGroup::class, BookSource::class, BookChapter::class,
ReplaceRule::class, SearchBook::class, SearchKeyword::class, Cookie::class, ReplaceRule::class, SearchBook::class, SearchKeyword::class, Cookie::class,
RssSource::class, Bookmark::class, RssArticle::class, RssReadRecord::class, RssSource::class, Bookmark::class, RssArticle::class, RssReadRecord::class,
RssStar::class, TxtTocRule::class, ReadRecord::class, HttpTTS::class, Cache::class, RssStar::class, TxtTocRule::class, ReadRecord::class, HttpTTS::class, Cache::class,
RuleSub::class, DictRule::class, KeyboardAssist::class], RuleSub::class, DictRule::class, KeyboardAssist::class, Server::class],
autoMigrations = [ autoMigrations = [
AutoMigration(from = 43, to = 44), AutoMigration(from = 43, to = 44),
AutoMigration(from = 44, to = 45), AutoMigration(from = 44, to = 45),
@ -45,7 +45,8 @@ val appDb by lazy {
AutoMigration(from = 57, to = 58), AutoMigration(from = 57, to = 58),
AutoMigration(from = 58, to = 59), AutoMigration(from = 58, to = 59),
AutoMigration(from = 59, to = 60), AutoMigration(from = 59, to = 60),
AutoMigration(from = 60, to = 61) AutoMigration(from = 60, to = 61),
AutoMigration(from = 61, to = 62)
] ]
) )
abstract class AppDatabase : RoomDatabase() { abstract class AppDatabase : RoomDatabase() {
@ -69,6 +70,7 @@ abstract class AppDatabase : RoomDatabase() {
abstract val ruleSubDao: RuleSubDao abstract val ruleSubDao: RuleSubDao
abstract val dictRuleDao: DictRuleDao abstract val dictRuleDao: DictRuleDao
abstract val keyboardAssistsDao: KeyboardAssistsDao abstract val keyboardAssistsDao: KeyboardAssistsDao
abstract val serverDao: ServerDao
companion object { companion object {

View File

@ -0,0 +1,30 @@
package io.legado.app.data.dao
import androidx.room.*
import io.legado.app.data.entities.Server
import kotlinx.coroutines.flow.Flow
@Dao
interface ServerDao {
@Query("select * from servers order by sortNumber")
fun observeAll(): Flow<List<Server>>
@get:Query("select * from servers order by sortNumber")
val all: List<Server>
@Query("select * from servers where id = :id")
fun get(id: Long): Server?
@Insert(onConflict = OnConflictStrategy.REPLACE)
fun insert(vararg server: Server)
@Update(onConflict = OnConflictStrategy.REPLACE)
fun update(vararg server: Server)
@Delete
fun delete(vararg server: Server)
@Query("delete from servers where id < 0")
fun deleteDefault()
}

View File

@ -0,0 +1,54 @@
package io.legado.app.data.entities
import android.os.Parcelable
import androidx.room.*
import io.legado.app.utils.GSON
import kotlinx.parcelize.Parcelize
/**
* 服务器
*/
@Parcelize
@TypeConverters(Server.Converters::class)
@Entity(tableName = "servers")
data class Server(
@PrimaryKey
var id: Long = System.currentTimeMillis(),
var name: String = "",
var type: TYPE = TYPE.WEBDAV,
var config: Config? = null,
var sortNumber: Int = 0
): Parcelable {
enum class TYPE {
WEBDAV, ALIYUN, GOOGLEYUN
}
override fun hashCode(): Int {
return id.hashCode()
}
override fun equals(other: Any?): Boolean {
if (other is Server) {
return id == other.id
}
return false
}
@Parcelize
data class Config(
/* webdav */
var username: String? = null,
var password: String? = null,
): Parcelable
class Converters {
@TypeConverter
fun configToString(config: Server.Config?): String = GSON.toJson(config)
@TypeConverter
fun stringToConfig(json: String?) = GSON.fromJsonObject<Server.Config>(json).getOrNull()
}
}

View File

@ -3,6 +3,10 @@ package io.legado.app.lib.webdav
import okhttp3.Credentials import okhttp3.Credentials
import java.nio.charset.Charset import java.nio.charset.Charset
import java.nio.charset.StandardCharsets import java.nio.charset.StandardCharsets
import io.legado.app.data.entities.Server
import io.legado.app.data.entities.Server.TYPE
import io.legado.app.data.appDb
import io.legado.app.exception.NoStackTraceException
data class Authorization( data class Authorization(
val username: String, val username: String,
@ -10,12 +14,29 @@ data class Authorization(
val charset: Charset = StandardCharsets.ISO_8859_1 val charset: Charset = StandardCharsets.ISO_8859_1
) { ) {
val name = "Authorization" var name = "Authorization"
private set
val data: String = Credentials.basic(username, password, charset) var data: String = Credentials.basic(username, password, charset)
private set
override fun toString(): String { override fun toString(): String {
return "$username:$password" return "$username:$password"
} }
constructor(serverID: Long?): this("","") {
serverID ?: throw NoStackTraceException("Unexpected server ID")
appDb.serverDao.get(serverID!!)?.run {
when (type) {
TYPE.WEBDAV -> data = Credentials.basic(config.username, config.password, charset)
TYPE.ALIYUN -> {
TODO("not implemented")
}
TYPE.GOOGLEYUN -> {
TODO("not implemented")
}
}
}
}
} }

View File

@ -3,10 +3,12 @@ package io.legado.app.lib.webdav
import android.annotation.SuppressLint import android.annotation.SuppressLint
import android.net.Uri import android.net.Uri
import io.legado.app.constant.AppLog import io.legado.app.constant.AppLog
import io.legado.app.data.entities.Server
import io.legado.app.exception.NoStackTraceException import io.legado.app.exception.NoStackTraceException
import io.legado.app.help.http.newCallResponse import io.legado.app.help.http.newCallResponse
import io.legado.app.help.http.okHttpClient import io.legado.app.help.http.okHttpClient
import io.legado.app.help.http.text import io.legado.app.help.http.text
import io.legado.app.model.analyzeRule.AnalyzeUrl
import io.legado.app.utils.* import io.legado.app.utils.*
import kotlinx.coroutines.Dispatchers.IO import kotlinx.coroutines.Dispatchers.IO
import kotlinx.coroutines.withContext import kotlinx.coroutines.withContext
@ -30,7 +32,10 @@ import java.time.ZoneOffset
import java.time.format.DateTimeFormatter import java.time.format.DateTimeFormatter
@Suppress("unused", "MemberVisibilityCanBePrivate") @Suppress("unused", "MemberVisibilityCanBePrivate")
open class WebDav(val path: String, val authorization: Authorization) { open class WebDav(
val path: String,
val authorization: Authorization? = Authorization(AnalyzeUrl(path).serverID)
) {
companion object { companion object {
@SuppressLint("DateTimeFormatter") @SuppressLint("DateTimeFormatter")
@ -61,7 +66,9 @@ open class WebDav(val path: String, val authorization: Authorization) {
</propfind>""" </propfind>"""
} }
private val url: URL = URL(path) private val url: URL = URL(AnalyzeUrl(path).url)
/* 服务器id */
private var serverID: Long? = AnalyzeUrl(path).serverID
private val httpUrl: String? by lazy { private val httpUrl: String? by lazy {
val raw = url.toString() val raw = url.toString()
.replace("davs://", "https://") .replace("davs://", "https://")
@ -389,4 +396,10 @@ open class WebDav(val path: String, val authorization: Authorization) {
} }
} }
override fun toString(): String {
val url = httpUrl ?: return ""
serverID ?: return url
return "$url,{serverID:$serverID}"
}
} }

View File

@ -76,6 +76,9 @@ class AnalyzeUrl(
private var webJs: String? = null private var webJs: String? = null
private val enabledCookieJar = source?.enabledCookieJar ?: false private val enabledCookieJar = source?.enabledCookieJar ?: false
private val domain: String private val domain: String
// 服务器ID
var serverID: Long? = null
private set
init { init {
val urlMatcher = paramPattern.matcher(baseUrl) val urlMatcher = paramPattern.matcher(baseUrl)
@ -200,6 +203,7 @@ class AnalyzeUrl(
url = it url = it
} }
} }
serverID = option.getServerID()
} }
} }
urlNoQuery = url urlNoQuery = url
@ -667,6 +671,10 @@ class AnalyzeUrl(
* 执行结果会赋值给url * 执行结果会赋值给url
*/ */
private var js: String? = null, private var js: String? = null,
/**
* 服务器id
*/
private var serverID: Long? = null
) { ) {
fun setMethod(value: String?) { fun setMethod(value: String?) {
method = if (value.isNullOrBlank()) null else value method = if (value.isNullOrBlank()) null else value
@ -765,6 +773,14 @@ class AnalyzeUrl(
fun getJs(): String? { fun getJs(): String? {
return js return js
} }
fun setServerID(value: Long?) {
serverID = if (value.isNullOrBlank()) null else value
}
fun getServerID(): Long? {
return serverID
}
} }
data class ConcurrentRecord( data class ConcurrentRecord(

View File

@ -18,6 +18,7 @@ import io.legado.app.help.AppWebDav
import io.legado.app.help.book.* import io.legado.app.help.book.*
import io.legado.app.help.config.AppConfig import io.legado.app.help.config.AppConfig
import io.legado.app.lib.webdav.WebDav import io.legado.app.lib.webdav.WebDav
import io.legado.app.lib.webdav.WebDavException
import io.legado.app.model.analyzeRule.AnalyzeUrl import io.legado.app.model.analyzeRule.AnalyzeUrl
import io.legado.app.utils.* import io.legado.app.utils.*
import kotlinx.coroutines.runBlocking import kotlinx.coroutines.runBlocking
@ -338,11 +339,15 @@ object LocalBook {
try { try {
AppConfig.defaultBookTreeUri AppConfig.defaultBookTreeUri
?: throw NoStackTraceException("没有设置书籍保存位置!") ?: throw NoStackTraceException("没有设置书籍保存位置!")
val uri = AppWebDav.authorization?.let { // 兼容旧版链接
val webdav = WebDav(webDavUrl, it) val webdav = kotlin.runCatching {
runBlocking { WebDav(webDavUrl)
saveBookFile(webdav.downloadInputStream(), localBook.originName) }.onFailure {
} AppWebDav.defaultBookWebDav
?: throw WebDavException("Unexpected defaultBookWebDav")
}
val uri = runBlocking {
saveBookFile(webdav.downloadInputStream(), localBook.originName)
} }
return uri?.let { return uri?.let {
localBook.bookUrl = if (it.isContentScheme()) it.toString() else it.path!! localBook.bookUrl = if (it.isContentScheme()) it.toString() else it.path!!

View File

@ -4,6 +4,7 @@ import android.net.Uri
import io.legado.app.constant.AppPattern.bookFileRegex import io.legado.app.constant.AppPattern.bookFileRegex
import io.legado.app.constant.BookType import io.legado.app.constant.BookType
import io.legado.app.data.entities.Book import io.legado.app.data.entities.Book
import io.legado.app.data.entities.Server
import io.legado.app.exception.NoStackTraceException import io.legado.app.exception.NoStackTraceException
import io.legado.app.help.config.AppConfig import io.legado.app.help.config.AppConfig
import io.legado.app.lib.webdav.Authorization import io.legado.app.lib.webdav.Authorization
@ -26,6 +27,8 @@ class RemoteBookWebDav(val rootBookUrl: String, val authorization: Authorization
} }
} }
// constructor(server: WebDavServer): this(webDavServer.url, Authorization(webDavServer))
@Throws(Exception::class) @Throws(Exception::class)
override suspend fun getRemoteBookList(path: String): MutableList<RemoteBook> { override suspend fun getRemoteBookList(path: String): MutableList<RemoteBook> {
if (!NetworkUtils.isAvailable()) throw NoStackTraceException("网络不可用") if (!NetworkUtils.isAvailable()) throw NoStackTraceException("网络不可用")
@ -63,15 +66,16 @@ class RemoteBookWebDav(val rootBookUrl: String, val authorization: Authorization
if (!NetworkUtils.isAvailable()) throw NoStackTraceException("网络不可用") if (!NetworkUtils.isAvailable()) throw NoStackTraceException("网络不可用")
val localBookUri = Uri.parse(book.bookUrl) val localBookUri = Uri.parse(book.bookUrl)
val putUrl = "$rootBookUrl${File.separator}${book.originName}" val putUrl = "$rootBookUrl${File.separator}${book.originName}"
val webDav = WebDav(putUrl, authorization)
if (localBookUri.isContentScheme()) { if (localBookUri.isContentScheme()) {
WebDav(putUrl, authorization).upload( webDav.upload(
byteArray = localBookUri.readBytes(appCtx), byteArray = localBookUri.readBytes(appCtx),
contentType = "application/octet-stream" contentType = "application/octet-stream"
) )
} else { } else {
WebDav(putUrl, authorization).upload(localBookUri.path!!) webDav.upload(localBookUri.path!!)
} }
book.origin = BookType.webDavTag + putUrl book.origin = BookType.webDavTag + webDav.toString()
book.save() book.save()
} }

View File

@ -7,6 +7,7 @@ import io.legado.app.constant.BookType
import io.legado.app.exception.NoStackTraceException import io.legado.app.exception.NoStackTraceException
import io.legado.app.help.AppWebDav import io.legado.app.help.AppWebDav
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.model.localBook.LocalBook import io.legado.app.model.localBook.LocalBook
import io.legado.app.model.remote.RemoteBook import io.legado.app.model.remote.RemoteBook
import io.legado.app.model.remote.RemoteBookWebDav import io.legado.app.model.remote.RemoteBookWebDav
@ -124,7 +125,7 @@ class RemoteBookViewModel(application: Application) : BaseViewModel(application)
val downloadBookPath = bookWebDav.downloadRemoteBook(remoteBook) val downloadBookPath = bookWebDav.downloadRemoteBook(remoteBook)
downloadBookPath.let { downloadBookPath.let {
val localBook = LocalBook.importFile(it) val localBook = LocalBook.importFile(it)
localBook.origin = BookType.webDavTag + remoteBook.path localBook.origin = BookType.webDavTag + WebDav(remoteBook.path, bookWebDav.authorization).toString()
localBook.save() localBook.save()
remoteBook.isOnBookShelf = true remoteBook.isOnBookShelf = true
} }

View File

@ -24,12 +24,14 @@ import io.legado.app.help.AppWebDav
import io.legado.app.help.book.isAudio import io.legado.app.help.book.isAudio
import io.legado.app.help.book.isLocal import io.legado.app.help.book.isLocal
import io.legado.app.help.book.isLocalTxt import io.legado.app.help.book.isLocalTxt
import io.legado.app.help.book.getRemoteUrl
import io.legado.app.help.config.AppConfig import io.legado.app.help.config.AppConfig
import io.legado.app.lib.dialogs.alert import io.legado.app.lib.dialogs.alert
import io.legado.app.lib.theme.backgroundColor import io.legado.app.lib.theme.backgroundColor
import io.legado.app.lib.theme.bottomBackground import io.legado.app.lib.theme.bottomBackground
import io.legado.app.lib.theme.getPrimaryTextColor import io.legado.app.lib.theme.getPrimaryTextColor
import io.legado.app.model.BookCover import io.legado.app.model.BookCover
import io.legado.app.model.remote.RemoteBookWebDav
import io.legado.app.ui.about.AppLogDialog import io.legado.app.ui.about.AppLogDialog
import io.legado.app.ui.association.ImportOnLineBookFileDialog import io.legado.app.ui.association.ImportOnLineBookFileDialog
import io.legado.app.ui.book.audio.AudioPlayActivity import io.legado.app.ui.book.audio.AudioPlayActivity
@ -205,30 +207,45 @@ class BookInfoActivity :
} }
R.id.menu_upload -> { R.id.menu_upload -> {
launch { viewModel.bookData.value?.let { book ->
viewModel.bookData.value?.let { book.getRemoteUrl()?.let {
val waitDialog = WaitDialog(this@BookInfoActivity) alert(R.string.draw, R.string.sure_upload) {
waitDialog.setText("上传中.....") okButton {
waitDialog.show() upLoadBook(book)
try { }
AppWebDav.defaultBookWebDav cancelButton()
?.upload(it)
?: throw NoStackTraceException("未配置webDav")
//更新书籍最后更新时间,使之比远程书籍的时间新
it.lastCheckTime = System.currentTimeMillis()
viewModel.saveBook(it)
} catch (e: Exception) {
toastOnUi(e.localizedMessage)
} finally {
waitDialog.dismiss()
} }
} } ?: upLoadBook(book)
} }
} }
} }
return super.onCompatOptionsItemSelected(item) return super.onCompatOptionsItemSelected(item)
} }
private fun upLoadBook(
book: Book,
bookWebDav: RemoteBookWebDav? = AppWebDav.defaultBookWebDav
) {
launch {
val waitDialog = WaitDialog(this@BookInfoActivity)
waitDialog.setText("上传中.....")
waitDialog.show()
try {
bookWebDav
?.upload(book)
?: throw NoStackTraceException("未配置webDav")
//更新书籍最后更新时间,使之比远程书籍的时间新
book.lastCheckTime = System.currentTimeMillis()
viewModel.saveBook(book)
} catch (e: Exception) {
toastOnUi(e.localizedMessage)
} finally {
waitDialog.dismiss()
}
}
}
private fun showBook(book: Book) = binding.run { private fun showBook(book: Book) = binding.run {
showCover(book) showCover(book)
tvName.text = book.name tvName.text = book.name

View File

@ -1081,4 +1081,5 @@
<string name="import_dict_rule">导入字典规则</string> <string name="import_dict_rule">导入字典规则</string>
<string name="keep_group">保留分组</string> <string name="keep_group">保留分组</string>
<string name="server_config">服务器配置</string> <string name="server_config">服务器配置</string>
<string name="sure_upload">Remote webDav url exists, Continue?</string>
</resources> </resources>

View File

@ -1084,4 +1084,5 @@
<string name="import_dict_rule">导入字典规则</string> <string name="import_dict_rule">导入字典规则</string>
<string name="keep_group">保留分组</string> <string name="keep_group">保留分组</string>
<string name="server_config">服务器配置</string> <string name="server_config">服务器配置</string>
<string name="sure_upload">Remote webDav url exists, Continue?</string>
</resources> </resources>

View File

@ -1084,4 +1084,5 @@
<string name="import_dict_rule">导入字典规则</string> <string name="import_dict_rule">导入字典规则</string>
<string name="keep_group">保留分组</string> <string name="keep_group">保留分组</string>
<string name="server_config">服务器配置</string> <string name="server_config">服务器配置</string>
<string name="sure_upload">Remote webDav url exists, Continue?</string>
</resources> </resources>

View File

@ -1081,4 +1081,5 @@
<string name="import_dict_rule">导入字典规则</string> <string name="import_dict_rule">导入字典规则</string>
<string name="keep_group">保留分组</string> <string name="keep_group">保留分组</string>
<string name="server_config">服务器配置</string> <string name="server_config">服务器配置</string>
<string name="sure_upload">远程webDav链接已存在是否继续</string>
</resources> </resources>

View File

@ -1083,4 +1083,5 @@
<string name="import_dict_rule">导入字典规则</string> <string name="import_dict_rule">导入字典规则</string>
<string name="keep_group">保留分组</string> <string name="keep_group">保留分组</string>
<string name="server_config">服务器配置</string> <string name="server_config">服务器配置</string>
<string name="sure_upload">远程webDav链接已存在是否继续</string>
</resources> </resources>

View File

@ -1083,4 +1083,5 @@
<string name="import_dict_rule">导入字典规则</string> <string name="import_dict_rule">导入字典规则</string>
<string name="keep_group">保留分组</string> <string name="keep_group">保留分组</string>
<string name="server_config">服务器配置</string> <string name="server_config">服务器配置</string>
<string name="sure_upload">远程webDav链接已存在是否继续</string>
</resources> </resources>

View File

@ -1084,4 +1084,5 @@
<string name="import_dict_rule">Import dict rule</string> <string name="import_dict_rule">Import dict rule</string>
<string name="keep_group">Keep group</string> <string name="keep_group">Keep group</string>
<string name="server_config">服务器配置</string> <string name="server_config">服务器配置</string>
<string name="sure_upload">Remote webDav url exists, Continue?</string>
</resources> </resources>