diff --git a/app/src/main/java/io/legado/app/data/AppDatabase.kt b/app/src/main/java/io/legado/app/data/AppDatabase.kt index b8f43abe1..3de3ebc71 100644 --- a/app/src/main/java/io/legado/app/data/AppDatabase.kt +++ b/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 { diff --git a/app/src/main/java/io/legado/app/data/dao/ServerDao.kt b/app/src/main/java/io/legado/app/data/dao/ServerDao.kt new file mode 100644 index 000000000..4933541e9 --- /dev/null +++ b/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> + + @get:Query("select * from servers order by sortNumber") + val all: List + + @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() +} \ No newline at end of file diff --git a/app/src/main/java/io/legado/app/data/entities/Server.kt b/app/src/main/java/io/legado/app/data/entities/Server.kt new file mode 100644 index 000000000..f586482cc --- /dev/null +++ b/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(json).getOrNull() + } + +} \ No newline at end of file diff --git a/app/src/main/java/io/legado/app/lib/webdav/Authorization.kt b/app/src/main/java/io/legado/app/lib/webdav/Authorization.kt index b4e4fb17e..91485322c 100644 --- a/app/src/main/java/io/legado/app/lib/webdav/Authorization.kt +++ b/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") + } + } + } + } + } \ No newline at end of file diff --git a/app/src/main/java/io/legado/app/lib/webdav/WebDav.kt b/app/src/main/java/io/legado/app/lib/webdav/WebDav.kt index e4a90df14..fc0ea1d0c 100644 --- a/app/src/main/java/io/legado/app/lib/webdav/WebDav.kt +++ b/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) { """ } - 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}" + } + } \ No newline at end of file diff --git a/app/src/main/java/io/legado/app/model/analyzeRule/AnalyzeUrl.kt b/app/src/main/java/io/legado/app/model/analyzeRule/AnalyzeUrl.kt index fef2e8c2a..3b9956ec6 100644 --- a/app/src/main/java/io/legado/app/model/analyzeRule/AnalyzeUrl.kt +++ b/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( diff --git a/app/src/main/java/io/legado/app/model/localBook/LocalBook.kt b/app/src/main/java/io/legado/app/model/localBook/LocalBook.kt index 9148d3e6c..9027a7826 100644 --- a/app/src/main/java/io/legado/app/model/localBook/LocalBook.kt +++ b/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!! diff --git a/app/src/main/java/io/legado/app/model/remote/RemoteBookWebDav.kt b/app/src/main/java/io/legado/app/model/remote/RemoteBookWebDav.kt index 5ff59b5c4..15ae21e28 100644 --- a/app/src/main/java/io/legado/app/model/remote/RemoteBookWebDav.kt +++ b/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 { 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() } diff --git a/app/src/main/java/io/legado/app/ui/book/import/remote/RemoteBookViewModel.kt b/app/src/main/java/io/legado/app/ui/book/import/remote/RemoteBookViewModel.kt index 4465312cc..2ee320ae9 100644 --- a/app/src/main/java/io/legado/app/ui/book/import/remote/RemoteBookViewModel.kt +++ b/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 } diff --git a/app/src/main/java/io/legado/app/ui/book/info/BookInfoActivity.kt b/app/src/main/java/io/legado/app/ui/book/info/BookInfoActivity.kt index b6170046f..fd8b4e97a 100644 --- a/app/src/main/java/io/legado/app/ui/book/info/BookInfoActivity.kt +++ b/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 diff --git a/app/src/main/res/values-es-rES/strings.xml b/app/src/main/res/values-es-rES/strings.xml index 706c8c721..a1a4750fc 100644 --- a/app/src/main/res/values-es-rES/strings.xml +++ b/app/src/main/res/values-es-rES/strings.xml @@ -1081,4 +1081,5 @@ 导入字典规则 保留分组 服务器配置 + Remote webDav url exists, Continue? diff --git a/app/src/main/res/values-ja-rJP/strings.xml b/app/src/main/res/values-ja-rJP/strings.xml index 60fe7421e..e1283cf01 100644 --- a/app/src/main/res/values-ja-rJP/strings.xml +++ b/app/src/main/res/values-ja-rJP/strings.xml @@ -1084,4 +1084,5 @@ 导入字典规则 保留分组 服务器配置 + Remote webDav url exists, Continue? diff --git a/app/src/main/res/values-pt-rBR/strings.xml b/app/src/main/res/values-pt-rBR/strings.xml index 8dc3ce630..72a7334f2 100644 --- a/app/src/main/res/values-pt-rBR/strings.xml +++ b/app/src/main/res/values-pt-rBR/strings.xml @@ -1084,4 +1084,5 @@ 导入字典规则 保留分组 服务器配置 + Remote webDav url exists, Continue? diff --git a/app/src/main/res/values-zh-rHK/strings.xml b/app/src/main/res/values-zh-rHK/strings.xml index 30bf50256..3e4f709b2 100644 --- a/app/src/main/res/values-zh-rHK/strings.xml +++ b/app/src/main/res/values-zh-rHK/strings.xml @@ -1081,4 +1081,5 @@ 导入字典规则 保留分组 服务器配置 + 远程webDav链接已存在,是否继续 diff --git a/app/src/main/res/values-zh-rTW/strings.xml b/app/src/main/res/values-zh-rTW/strings.xml index 0024c7f26..6f956ab0f 100644 --- a/app/src/main/res/values-zh-rTW/strings.xml +++ b/app/src/main/res/values-zh-rTW/strings.xml @@ -1083,4 +1083,5 @@ 导入字典规则 保留分组 服务器配置 + 远程webDav链接已存在,是否继续 diff --git a/app/src/main/res/values-zh/strings.xml b/app/src/main/res/values-zh/strings.xml index 320188851..ed320e1f2 100644 --- a/app/src/main/res/values-zh/strings.xml +++ b/app/src/main/res/values-zh/strings.xml @@ -1083,4 +1083,5 @@ 导入字典规则 保留分组 服务器配置 + 远程webDav链接已存在,是否继续 diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml index e0c674b4a..259ded3bb 100644 --- a/app/src/main/res/values/strings.xml +++ b/app/src/main/res/values/strings.xml @@ -1084,4 +1084,5 @@ Import dict rule Keep group 服务器配置 + Remote webDav url exists, Continue?