Просмотр исходного кода

Merge pull request #2831 from Xwite/master

fix: 书籍保存的webdav链接添加authorization信息
kunfei 3 лет назад
Родитель
Сommit
d3a9d74bbf

+ 5 - 3
app/src/main/java/io/legado/app/data/AppDatabase.kt

@@ -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 - 0
app/src/main/java/io/legado/app/data/dao/ServerDao.kt

@@ -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 - 0
app/src/main/java/io/legado/app/data/entities/Server.kt

@@ -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()
+    }
+
+}

+ 23 - 2
app/src/main/java/io/legado/app/lib/webdav/Authorization.kt

@@ -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")
+                }
+            }
+        }
+    }
+
 }

+ 15 - 2
app/src/main/java/io/legado/app/lib/webdav/WebDav.kt

@@ -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}"
+    }
+
 }

+ 16 - 0
app/src/main/java/io/legado/app/model/analyzeRule/AnalyzeUrl.kt

@@ -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(

+ 10 - 5
app/src/main/java/io/legado/app/model/localBook/LocalBook.kt

@@ -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!!

+ 7 - 3
app/src/main/java/io/legado/app/model/remote/RemoteBookWebDav.kt

@@ -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()
     }
 

+ 2 - 1
app/src/main/java/io/legado/app/ui/book/import/remote/RemoteBookViewModel.kt

@@ -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
                 }

+ 34 - 17
app/src/main/java/io/legado/app/ui/book/info/BookInfoActivity.kt

@@ -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

+ 1 - 0
app/src/main/res/values-es-rES/strings.xml

@@ -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>

+ 1 - 0
app/src/main/res/values-ja-rJP/strings.xml

@@ -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>

+ 1 - 0
app/src/main/res/values-pt-rBR/strings.xml

@@ -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>

+ 1 - 0
app/src/main/res/values-zh-rHK/strings.xml

@@ -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>

+ 1 - 0
app/src/main/res/values-zh-rTW/strings.xml

@@ -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>

+ 1 - 0
app/src/main/res/values-zh/strings.xml

@@ -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>

+ 1 - 0
app/src/main/res/values/strings.xml

@@ -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>