Merge pull request #2831 from Xwite/master

fix: 书籍保存的webdav链接添加authorization信息
This commit is contained in:
kunfei 2023-03-06 13:59:34 +08:00 committed by GitHub
commit e58d2874e6
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
17 changed files with 203 additions and 33 deletions

View File

@ -20,13 +20,13 @@ val appDb by lazy {
}
@Database(
version = 61,
version = 62,
exportSchema = true,
entities = [Book::class, BookGroup::class, BookSource::class, BookChapter::class,
ReplaceRule::class, SearchBook::class, SearchKeyword::class, Cookie::class,
RssSource::class, Bookmark::class, RssArticle::class, RssReadRecord::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 = [
AutoMigration(from = 43, to = 44),
AutoMigration(from = 44, to = 45),
@ -45,7 +45,8 @@ val appDb by lazy {
AutoMigration(from = 57, to = 58),
AutoMigration(from = 58, to = 59),
AutoMigration(from = 59, to = 60),
AutoMigration(from = 60, to = 61)
AutoMigration(from = 60, to = 61),
AutoMigration(from = 61, to = 62)
]
)
abstract class AppDatabase : RoomDatabase() {
@ -69,6 +70,7 @@ abstract class AppDatabase : RoomDatabase() {
abstract val ruleSubDao: RuleSubDao
abstract val dictRuleDao: DictRuleDao
abstract val keyboardAssistsDao: KeyboardAssistsDao
abstract val serverDao: ServerDao
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 java.nio.charset.Charset
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(
val username: String,
@ -10,12 +14,29 @@ data class Authorization(
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 {
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.net.Uri
import io.legado.app.constant.AppLog
import io.legado.app.data.entities.Server
import io.legado.app.exception.NoStackTraceException
import io.legado.app.help.http.newCallResponse
import io.legado.app.help.http.okHttpClient
import io.legado.app.help.http.text
import io.legado.app.model.analyzeRule.AnalyzeUrl
import io.legado.app.utils.*
import kotlinx.coroutines.Dispatchers.IO
import kotlinx.coroutines.withContext
@ -30,7 +32,10 @@ import java.time.ZoneOffset
import java.time.format.DateTimeFormatter
@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 {
@SuppressLint("DateTimeFormatter")
@ -61,7 +66,9 @@ open class WebDav(val path: String, val authorization: Authorization) {
</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 {
val raw = url.toString()
.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 val enabledCookieJar = source?.enabledCookieJar ?: false
private val domain: String
// 服务器ID
var serverID: Long? = null
private set
init {
val urlMatcher = paramPattern.matcher(baseUrl)
@ -200,6 +203,7 @@ class AnalyzeUrl(
url = it
}
}
serverID = option.getServerID()
}
}
urlNoQuery = url
@ -667,6 +671,10 @@ class AnalyzeUrl(
* 执行结果会赋值给url
*/
private var js: String? = null,
/**
* 服务器id
*/
private var serverID: Long? = null
) {
fun setMethod(value: String?) {
method = if (value.isNullOrBlank()) null else value
@ -765,6 +773,14 @@ class AnalyzeUrl(
fun getJs(): String? {
return js
}
fun setServerID(value: Long?) {
serverID = if (value.isNullOrBlank()) null else value
}
fun getServerID(): Long? {
return serverID
}
}
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.config.AppConfig
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.utils.*
import kotlinx.coroutines.runBlocking
@ -338,11 +339,15 @@ object LocalBook {
try {
AppConfig.defaultBookTreeUri
?: throw NoStackTraceException("没有设置书籍保存位置!")
val uri = AppWebDav.authorization?.let {
val webdav = WebDav(webDavUrl, it)
runBlocking {
saveBookFile(webdav.downloadInputStream(), localBook.originName)
}
// 兼容旧版链接
val webdav = kotlin.runCatching {
WebDav(webDavUrl)
}.onFailure {
AppWebDav.defaultBookWebDav
?: throw WebDavException("Unexpected defaultBookWebDav")
}
val uri = runBlocking {
saveBookFile(webdav.downloadInputStream(), localBook.originName)
}
return uri?.let {
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.BookType
import io.legado.app.data.entities.Book
import io.legado.app.data.entities.Server
import io.legado.app.exception.NoStackTraceException
import io.legado.app.help.config.AppConfig
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)
override suspend fun getRemoteBookList(path: String): MutableList<RemoteBook> {
if (!NetworkUtils.isAvailable()) throw NoStackTraceException("网络不可用")
@ -63,15 +66,16 @@ class RemoteBookWebDav(val rootBookUrl: String, val authorization: Authorization
if (!NetworkUtils.isAvailable()) throw NoStackTraceException("网络不可用")
val localBookUri = Uri.parse(book.bookUrl)
val putUrl = "$rootBookUrl${File.separator}${book.originName}"
val webDav = WebDav(putUrl, authorization)
if (localBookUri.isContentScheme()) {
WebDav(putUrl, authorization).upload(
webDav.upload(
byteArray = localBookUri.readBytes(appCtx),
contentType = "application/octet-stream"
)
} else {
WebDav(putUrl, authorization).upload(localBookUri.path!!)
webDav.upload(localBookUri.path!!)
}
book.origin = BookType.webDavTag + putUrl
book.origin = BookType.webDavTag + webDav.toString()
book.save()
}

View File

@ -7,6 +7,7 @@ import io.legado.app.constant.BookType
import io.legado.app.exception.NoStackTraceException
import io.legado.app.help.AppWebDav
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.remote.RemoteBook
import io.legado.app.model.remote.RemoteBookWebDav
@ -124,7 +125,7 @@ class RemoteBookViewModel(application: Application) : BaseViewModel(application)
val downloadBookPath = bookWebDav.downloadRemoteBook(remoteBook)
downloadBookPath.let {
val localBook = LocalBook.importFile(it)
localBook.origin = BookType.webDavTag + remoteBook.path
localBook.origin = BookType.webDavTag + WebDav(remoteBook.path, bookWebDav.authorization).toString()
localBook.save()
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.isLocal
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.lib.dialogs.alert
import io.legado.app.lib.theme.backgroundColor
import io.legado.app.lib.theme.bottomBackground
import io.legado.app.lib.theme.getPrimaryTextColor
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.association.ImportOnLineBookFileDialog
import io.legado.app.ui.book.audio.AudioPlayActivity
@ -205,30 +207,45 @@ class BookInfoActivity :
}
R.id.menu_upload -> {
launch {
viewModel.bookData.value?.let {
val waitDialog = WaitDialog(this@BookInfoActivity)
waitDialog.setText("上传中.....")
waitDialog.show()
try {
AppWebDav.defaultBookWebDav
?.upload(it)
?: throw NoStackTraceException("未配置webDav")
//更新书籍最后更新时间,使之比远程书籍的时间新
it.lastCheckTime = System.currentTimeMillis()
viewModel.saveBook(it)
} catch (e: Exception) {
toastOnUi(e.localizedMessage)
} finally {
waitDialog.dismiss()
viewModel.bookData.value?.let { book ->
book.getRemoteUrl()?.let {
alert(R.string.draw, R.string.sure_upload) {
okButton {
upLoadBook(book)
}
cancelButton()
}
}
} ?: upLoadBook(book)
}
}
}
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 {
showCover(book)
tvName.text = book.name

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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