mirror of
https://github.com/gedoor/legado.git
synced 2024-07-17 00:58:29 +08:00
Merge pull request #2831 from Xwite/master
fix: 书籍保存的webdav链接添加authorization信息
This commit is contained in:
commit
e58d2874e6
@ -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 {
|
||||
|
||||
|
30
app/src/main/java/io/legado/app/data/dao/ServerDao.kt
Normal file
30
app/src/main/java/io/legado/app/data/dao/ServerDao.kt
Normal 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()
|
||||
}
|
54
app/src/main/java/io/legado/app/data/entities/Server.kt
Normal file
54
app/src/main/java/io/legado/app/data/entities/Server.kt
Normal 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()
|
||||
}
|
||||
|
||||
}
|
@ -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")
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
@ -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}"
|
||||
}
|
||||
|
||||
}
|
@ -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(
|
||||
|
@ -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!!
|
||||
|
@ -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()
|
||||
}
|
||||
|
||||
|
@ -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
|
||||
}
|
||||
|
@ -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
|
||||
|
@ -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>
|
||||
|
@ -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>
|
||||
|
@ -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>
|
||||
|
@ -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>
|
||||
|
@ -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>
|
||||
|
@ -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>
|
||||
|
@ -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>
|
||||
|
Loading…
Reference in New Issue
Block a user