diff --git a/.github/ISSUE_TEMPLATE/bug-report.yml b/.github/ISSUE_TEMPLATE/bug-report.yml
index 4983009e3..7e8f9c4a6 100644
--- a/.github/ISSUE_TEMPLATE/bug-report.yml
+++ b/.github/ISSUE_TEMPLATE/bug-report.yml
@@ -135,10 +135,7 @@ body:
- label: 如果您有足够的时间和能力,并愿意为此提交 PR ,请勾上此复选框 / Pull request is welcome. Check this if you want to start a pull request
required: false
- - label: 您已仔细查看并知情 [Q&A](https://github.com/Ehviewer-Overhauled/Ehviewer/issues/61) 中的内容 / You have checked [Q&A](https://github.com/Ehviewer-Overhauled/Ehviewer/issues/61) carefully
- required: true
-
- - label: 您已搜索过 [Issue Tracker](https://github.com/Ehviewer-Overhauled/Ehviewer/issues),没有找到类似内容 / I have searched on [Issue Tracker](https://github.com/Ehviewer-Overhauled/Ehviewer/issues), No duplicate or related open issue has been found
+ - label: 您已搜索过 [Issue Tracker](https://github.com/FooIbar/EhViewer/issues),没有找到类似内容 / I have searched on [Issue Tracker](https://github.com/FooIbar/EhViewer/issues), No duplicate or related open issue has been found
required: true
- label: 您确保这个 Issue 只提及一个问题。如果您有多个问题报告,烦请发起多个 Issue / Ensure there is only one bug report in this issue. Please make mutiply issue for mutiply bugs
diff --git a/.github/ISSUE_TEMPLATE/config.yml b/.github/ISSUE_TEMPLATE/config.yml
index 86148a6fc..ec4bb386b 100644
--- a/.github/ISSUE_TEMPLATE/config.yml
+++ b/.github/ISSUE_TEMPLATE/config.yml
@@ -1,5 +1 @@
-blank_issues_enabled: false
-contact_links:
- - name: 所有其他问题 / All other questions
- url: https://github.com/Ehviewer-Overhauled/Ehviewer/discussions
- about: 转到 Discussions / Turn to discussions
\ No newline at end of file
+blank_issues_enabled: false
\ No newline at end of file
diff --git a/.github/ISSUE_TEMPLATE/feature-request.yml b/.github/ISSUE_TEMPLATE/feature-request.yml
deleted file mode 100644
index 91241d65c..000000000
--- a/.github/ISSUE_TEMPLATE/feature-request.yml
+++ /dev/null
@@ -1,94 +0,0 @@
-name: 功能请求 / Feature request
-description: 提出一个功能建议 / Suggest an idea
-labels:
- - "enhancement"
-body:
- - type: markdown
- attributes:
- value: |
- 感谢您愿意为 Ehviewer-Overhauled 做出贡献!
- 提交功能建议前,还请首先完成文末的自查步骤
-
- Thanks for your contribution on Ehviewer-Overhauled Project!
- Please finish verify steps which list in the end first before suggest an idea
-
- - type: textarea
- id: request
- attributes:
- label: 需求 / Requirement
- description: |
- 在此处描述您的需求。这通常会是一个您想要的功能
- Ddescribe what you need here.
- placeholder: |
- 我需要 XXX 功能 ...
- I want ABC feature ...
- validations:
- required: true
-
- - type: textarea
- id: impl
- attributes:
- label: 建议实现 / Suggested implements
- description: |
- 在此处表述您建议的实现方式。如有可能,UI 类功能请求还请尽量附上图示
- Describe your suggested implements here, It's recommend to add a photo if you are making a UI feature request.
- placeholder: |
- 建议在 XX 处添加 XX ...
- I recommend add ABC feature to DEF...
- 图片(如果有)/ Photos (if exists)
- validations:
- required: true
-
- - type: textarea
- id: more
- attributes:
- label: 备注 / Addition details
- description: |
- 在此处写下其他您想说的内容
- Describe addition details here
- placeholder: |
- 其他有用的信息与附件
- Additional details and attachments
- validations:
- required: false
-
- - type: input
- id: version
- attributes:
- label: EhViewer 版本号 / EhViewer version code
- description: 您可以在设置 - 关于处找到版本号 / You can get version code in Settings - About
- placeholder: 1.6.9.13
- validations:
- required: true
-
- - type: input
- id: ci
- attributes:
- label: EhViewer CI 版本 / EhViewer CI Version
- description: 位于设置 - 关于中的版本号后 / Next to the version code in Settings - About
- placeholder: 26f05a0
- validations:
- required: true
-
- - type: checkboxes
- id: check
- attributes:
- label: 自查步骤 / Verify steps
- description: |
- 请确认您已经遵守所有必选项
- Please ensure you have obtained all needed options
- options:
- - label: 如果您有足够的时间和能力,并愿意为此提交 PR ,请勾上此复选框 / Pull request is welcome. Check this if you want to start a pull request
- required: false
-
- - label: 您已仔细查看并知情 [Q&A](https://github.com/Ehviewer-Overhauled/Ehviewer/issues/61) 中的内容 / You have checked [Q&A](https://github.com/Ehviewer-Overhauled/Ehviewer/issues/61) carefully
- required: true
-
- - label: 您已搜索过 [Issue Tracker](https://github.com/Ehviewer-Overhauled/Ehviewer/issues),没有找到类似内容 / I have searched on [Issue Tracker](https://github.com/Ehviewer-Overhauled/Ehviewer/issues), No duplicate or related open issue has been found
- required: true
-
- - label: 您确保这个 Issue 只提及一个功能。如果您有多个功能请求,烦请发起多个 Issue / Ensure there is only one feature request in this issue. Please make mutiply issue for mutiply feature request
- required: true
-
- - label: 您确保已使用最新 CI 版本测试,并且该功能在最新 CI 版本中并未按照预期实现 / This feature don't implement correctly in latest CI version
- required: true
diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml
index 785dfc341..6b55afcf7 100644
--- a/.github/workflows/ci.yml
+++ b/.github/workflows/ci.yml
@@ -13,7 +13,7 @@ jobs:
runs-on: ubuntu-latest
steps:
- name: Checkout
- uses: actions/checkout@v3
+ uses: actions/checkout@3df4ab11eba7bda6032a0b82a6bb43b11571feac # v4
with:
submodules: 'recursive'
diff --git a/.github/workflows/pull_request.yml b/.github/workflows/pull_request.yml
index b79e605e9..946bf28b4 100644
--- a/.github/workflows/pull_request.yml
+++ b/.github/workflows/pull_request.yml
@@ -8,7 +8,7 @@ jobs:
runs-on: ubuntu-latest
steps:
- name: Checkout
- uses: actions/checkout@v3
+ uses: actions/checkout@3df4ab11eba7bda6032a0b82a6bb43b11571feac # v4
with:
submodules: 'recursive'
diff --git a/.github/workflows/releases.yml b/.github/workflows/releases.yml
index 01426e578..951332ef0 100644
--- a/.github/workflows/releases.yml
+++ b/.github/workflows/releases.yml
@@ -11,7 +11,7 @@ jobs:
runs-on: ubuntu-latest
steps:
- name: Checkout
- uses: actions/checkout@v3
+ uses: actions/checkout@3df4ab11eba7bda6032a0b82a6bb43b11571feac # v4
with:
submodules: 'recursive'
diff --git a/README.md b/README.md
index bf6f5aade..ac285be73 100644
--- a/README.md
+++ b/README.md
@@ -1,3 +1,7 @@
+# Maintenance Mode
+
+The project is in maintenance mode, will only receive bugfixes.
+
English
|
diff --git a/app/build.gradle.kts b/app/build.gradle.kts
index 56065e64b..691ebcdec 100644
--- a/app/build.gradle.kts
+++ b/app/build.gradle.kts
@@ -167,11 +167,11 @@ android {
dependencies {
// https://developer.android.com/jetpack/androidx/releases/activity
- implementation("androidx.activity:activity-compose:1.8.0-alpha07")
+ implementation("androidx.activity:activity-compose:1.8.0-beta01")
implementation("androidx.appcompat:appcompat:1.6.1")
implementation("androidx.biometric:biometric-ktx:1.2.0-alpha05")
implementation("androidx.browser:browser:1.6.0")
- implementation("androidx.collection:collection-ktx:1.3.0-beta01")
+ implementation("androidx.collection:collection-ktx:1.3.0-rc01")
// https://developer.android.com/jetpack/androidx/releases/compose-material3
// api(platform("androidx.compose:compose-bom:2023.05.00"))
@@ -181,22 +181,22 @@ dependencies {
implementation("androidx.compose.material3:material3-window-size-class")
implementation("androidx.compose.ui:ui-util")
- implementation("androidx.core:core-ktx:1.12.0-rc01")
+ implementation("androidx.core:core-ktx:1.12.0")
implementation("androidx.constraintlayout:constraintlayout-compose:1.1.0-alpha12")
implementation("androidx.coordinatorlayout:coordinatorlayout:1.2.0")
- implementation("androidx.fragment:fragment-ktx:1.7.0-alpha03")
+ implementation("androidx.fragment:fragment-ktx:1.7.0-alpha04")
// https://developer.android.com/jetpack/androidx/releases/lifecycle
- implementation("androidx.lifecycle:lifecycle-process:2.6.1")
+ implementation("androidx.lifecycle:lifecycle-process:2.6.2")
// https://developer.android.com/jetpack/androidx/releases/navigation
- val nav_version = "2.7.1"
+ val nav_version = "2.7.2"
implementation("androidx.navigation:navigation-fragment-ktx:$nav_version")
implementation("androidx.navigation:navigation-ui-ktx:$nav_version")
implementation("androidx.navigation:navigation-compose:$nav_version")
// https://developer.android.com/jetpack/androidx/releases/paging
- val paging = "3.2.0"
+ val paging = "3.2.1"
implementation("androidx.paging:paging-compose:$paging")
implementation("androidx.paging:paging-runtime-ktx:$paging")
implementation("androidx.recyclerview:recyclerview:1.3.1")
@@ -238,7 +238,7 @@ dependencies {
implementation("dev.rikka.rikkax.insets:insets:1.3.0")
implementation("dev.rikka.rikkax.layoutinflater:layoutinflater:1.3.0")
- implementation(platform("io.arrow-kt:arrow-stack:1.2.0"))
+ implementation(platform("io.arrow-kt:arrow-stack:1.2.1"))
implementation("io.arrow-kt:arrow-fx-coroutines")
// https://coil-kt.github.io/coil/changelog/
@@ -246,7 +246,7 @@ dependencies {
implementation("io.coil-kt:coil-compose")
implementation("io.coil-kt:coil-gif")
- val ktor = "2.3.3"
+ val ktor = "2.3.4"
implementation("io.ktor:ktor-io-jvm:$ktor")
implementation("io.ktor:ktor-utils-jvm:$ktor")
diff --git a/app/src/main/java/com/hippo/ehviewer/Settings.kt b/app/src/main/java/com/hippo/ehviewer/Settings.kt
index 4cde64a0b..4773db5a4 100755
--- a/app/src/main/java/com/hippo/ehviewer/Settings.kt
+++ b/app/src/main/java/com/hippo/ehviewer/Settings.kt
@@ -30,16 +30,16 @@ object Settings : DataStorePreferences(null) {
var downloadQuery by stringOrNullPref("image_query", null)
var downloadFragment by stringOrNullPref("image_fragment", null)
var archivePasswds by stringSetOrNullPref("archive_passwds")
- var downloadDelay by intFromStrPref("download_delay", 0)
- var gallerySite by intFromStrPref("gallery_site", 0).observed { updateWhenGallerySiteChanges() }
- var multiThreadDownload by intFromStrPref("download_thread", 3)
- var preloadImage by intFromStrPref("preload_image", 5)
- var theme by intFromStrPref("theme", -1).observed { updateWhenThemeChanges() }
- var listMode by intFromStrPref("list_mode", 0)
- var detailSize by intFromStrPref("detail_size", 0)
- var thumbResolution by intFromStrPref("thumb_resolution", 0)
- var readCacheSize by intFromStrPref("read_cache_size", 640)
- var launchPage by intFromStrPref("launch_page", 0)
+ var downloadDelay by intPref("download_delay_2", 0)
+ var gallerySite by intPref("gallery_site_2", 0).observed { updateWhenGallerySiteChanges() }
+ var multiThreadDownload by intPref("download_thread_2", 3)
+ var preloadImage by intPref("preload_image_2", 5)
+ var theme by intPref("theme_2", -1).observed { updateWhenThemeChanges() }
+ var listMode by intPref("list_mode_2", 0)
+ var detailSize by intPref("detail_size_2", 0)
+ var thumbResolution by intPref("thumb_resolution_2", 0)
+ var readCacheSize by intPref("read_cache_size_2", 640)
+ var launchPage by intPref("launch_page_2", 0)
var forceEhThumb by boolPref("force_eh_thumb", false)
var showComments by boolPref("show_gallery_comments", true)
var requestNews by boolPref("request_news", false).observed { updateWhenRequestNewsChanges() }
@@ -94,13 +94,6 @@ object Settings : DataStorePreferences(null) {
operator fun setValue(thisRef: Any?, prop: KProperty<*>?, value: R)
}
- private fun intFromStrPref(key: String, defValue: Int) = object : Delegate {
- override val flowGetter: () -> Flow
- private var _value by stringPref(key, defValue.toString()).also { flowGetter = { it.changesFlow() } }
- override fun getValue(thisRef: Any?, prop: KProperty<*>?) = _value.toIntOrNull() ?: defValue
- override fun setValue(thisRef: Any?, prop: KProperty<*>?, value: Int) { _value = value.toString() }
- }
-
private fun intArrayPref(key: String, count: Int) = object : Delegate {
override val flowGetter: () -> Flow = { _value.asFlow().flatMapMerge { it.changesFlow() }.conflate() }
private var _value = (0 until count).map { intPref("${key}_$it", 0) }.toTypedArray()
diff --git a/app/src/main/java/com/hippo/ehviewer/client/EhEngine.kt b/app/src/main/java/com/hippo/ehviewer/client/EhEngine.kt
index 9d80f269f..d6442efa7 100644
--- a/app/src/main/java/com/hippo/ehviewer/client/EhEngine.kt
+++ b/app/src/main/java/com/hippo/ehviewer/client/EhEngine.kt
@@ -120,8 +120,8 @@ private fun rethrowExactly(code: Int, headers: Headers, body: String, e: Throwab
} else if ("<" !in body) {
throw EhException(body)
} else {
- if (Settings.saveParseErrorBody) AppConfig.saveParseErrorBody(e)
- throw EhException(appCtx.getString(R.string.error_parse_error))
+ if (Settings.saveParseErrorBody) AppConfig.saveParseErrorBody(e, body)
+ throw EhException(appCtx.getString(R.string.error_parse_error), e)
}
}
diff --git a/app/src/main/java/com/hippo/ehviewer/client/exception/ParseException.kt b/app/src/main/java/com/hippo/ehviewer/client/exception/ParseException.kt
index df59bcce7..101d1ed54 100644
--- a/app/src/main/java/com/hippo/ehviewer/client/exception/ParseException.kt
+++ b/app/src/main/java/com/hippo/ehviewer/client/exception/ParseException.kt
@@ -16,16 +16,7 @@
package com.hippo.ehviewer.client.exception
class ParseException : EhException {
- val body: String
+ constructor(detailMessage: String?) : super(detailMessage)
- constructor(detailMessage: String?, body: String) : super(detailMessage) {
- this.body = body
- }
-
- constructor(detailMessage: String?, body: String, cause: Throwable?) : super(
- detailMessage,
- cause,
- ) {
- this.body = body
- }
+ constructor(detailMessage: String?, cause: Throwable?) : super(detailMessage, cause)
}
diff --git a/app/src/main/java/com/hippo/ehviewer/client/parser/FavoritesParser.kt b/app/src/main/java/com/hippo/ehviewer/client/parser/FavoritesParser.kt
index 1eee9ec63..0ac3aecb9 100644
--- a/app/src/main/java/com/hippo/ehviewer/client/parser/FavoritesParser.kt
+++ b/app/src/main/java/com/hippo/ehviewer/client/parser/FavoritesParser.kt
@@ -12,7 +12,7 @@ object FavoritesParser {
}
val catArray = arrayOfNulls(10)
val countArray = parseFav(body, catArray)
- if (countArray.isEmpty()) throw ParseException("Parse favorites error", body)
+ if (countArray.isEmpty()) throw ParseException("Parse favorites error")
val result = GalleryListParser.parse(body)
return Result(catArray.requireNoNulls(), countArray, result)
}
diff --git a/app/src/main/java/com/hippo/ehviewer/client/parser/GalleryDetailParser.kt b/app/src/main/java/com/hippo/ehviewer/client/parser/GalleryDetailParser.kt
index 366c5dd4d..648d89872 100644
--- a/app/src/main/java/com/hippo/ehviewer/client/parser/GalleryDetailParser.kt
+++ b/app/src/main/java/com/hippo/ehviewer/client/parser/GalleryDetailParser.kt
@@ -114,9 +114,9 @@ object GalleryDetailParser {
gd.token = groupValues[2]
gd.apiUid = groupValues[3].toLongOrNull() ?: -1L
gd.apiKey = groupValues[4]
- } ?: throw ParseException("Can't parse gallery detail", body)
+ } ?: throw ParseException("Can't parse gallery detail")
if (gd.gid == -1L) {
- throw ParseException("Can't parse gallery detail", body)
+ throw ParseException("Can't parse gallery detail")
}
PATTERN_TORRENT.find(body)?.run {
gd.torrentUrl = groupValues[1].trim().unescapeXml()
@@ -214,7 +214,7 @@ object GalleryDetailParser {
}
} catch (e: Throwable) {
ExceptionUtils.throwIfFatal(e)
- throw ParseException("Can't parse gallery detail", body)
+ throw ParseException("Can't parse gallery detail")
}
// newer version
@@ -450,7 +450,7 @@ object GalleryDetailParser {
*/
fun parsePreviewPages(body: String): Int {
return PATTERN_PREVIEW_PAGES.find(body)?.groupValues?.get(1)?.toIntOrNull()
- ?: throw ParseException("Parse preview page count error", body)
+ ?: throw ParseException("Parse preview page count error")
}
/**
@@ -458,7 +458,7 @@ object GalleryDetailParser {
*/
fun parsePages(body: String): Int {
return PATTERN_PAGES.find(body)?.groupValues?.get(1)?.toIntOrNull()
- ?: throw ParseException("Parse pages error", body)
+ ?: throw ParseException("Parse pages error")
}
fun parsePreviewList(body: String): Pair, List> {
diff --git a/app/src/main/java/com/hippo/ehviewer/client/parser/GalleryListParser.kt b/app/src/main/java/com/hippo/ehviewer/client/parser/GalleryListParser.kt
index 7381df195..bb5788a4a 100644
--- a/app/src/main/java/com/hippo/ehviewer/client/parser/GalleryListParser.kt
+++ b/app/src/main/java/com/hippo/ehviewer/client/parser/GalleryListParser.kt
@@ -25,7 +25,7 @@ object GalleryListParser {
if (body.contains("No hits found
")) {
throw EhException(R.string.gallery_list_empty_hit)
}
- throw ParseException("Can't parse gallery list", body, it)
+ throw ParseException("Can't parse gallery list", it)
}
}
diff --git a/app/src/main/java/com/hippo/ehviewer/client/parser/GalleryMultiPageViewerPTokenParser.kt b/app/src/main/java/com/hippo/ehviewer/client/parser/GalleryMultiPageViewerPTokenParser.kt
index 845fa5d3e..628930854 100644
--- a/app/src/main/java/com/hippo/ehviewer/client/parser/GalleryMultiPageViewerPTokenParser.kt
+++ b/app/src/main/java/com/hippo/ehviewer/client/parser/GalleryMultiPageViewerPTokenParser.kt
@@ -33,7 +33,7 @@ object GalleryMultiPageViewerPTokenParser {
imagelist.parseAs>().map(Item::token)
}.getOrElse {
ExceptionUtils.throwIfFatal(it)
- throw ParseException("Parse pToken from MPV error", body, it)
+ throw ParseException("Parse pToken from MPV error", it)
}
}
diff --git a/app/src/main/java/com/hippo/ehviewer/client/parser/GalleryPageApiParser.kt b/app/src/main/java/com/hippo/ehviewer/client/parser/GalleryPageApiParser.kt
index 8ca00e06a..1722d8c98 100644
--- a/app/src/main/java/com/hippo/ehviewer/client/parser/GalleryPageApiParser.kt
+++ b/app/src/main/java/com/hippo/ehviewer/client/parser/GalleryPageApiParser.kt
@@ -27,7 +27,7 @@ object GalleryPageApiParser {
private val PATTERN_ORIGIN_IMAGE_URL = Regex("")
fun parse(body: String): Result {
- runCatching { body.parseAs() }.onSuccess { throw ParseException(it.error, body) }
+ runCatching { body.parseAs() }.onSuccess { throw ParseException(it.error) }
val res = body.parseAs()
val imageUrl = PATTERN_IMAGE_URL.find(res.imageUrl)?.run {
groupValues[1].trim().unescapeXml()
@@ -41,7 +41,7 @@ object GalleryPageApiParser {
if (!imageUrl.isNullOrEmpty()) {
return Result(imageUrl, skipHathKey, originImageUrl)
} else {
- throw ParseException("Parse image url and skip hath key error", body)
+ throw ParseException("Parse image url and skip hath key error")
}
}
diff --git a/app/src/main/java/com/hippo/ehviewer/client/parser/GalleryPageParser.kt b/app/src/main/java/com/hippo/ehviewer/client/parser/GalleryPageParser.kt
index ef8e5a125..afd813f95 100644
--- a/app/src/main/java/com/hippo/ehviewer/client/parser/GalleryPageParser.kt
+++ b/app/src/main/java/com/hippo/ehviewer/client/parser/GalleryPageParser.kt
@@ -56,7 +56,7 @@ object GalleryPageParser {
return if (!imageUrl.isNullOrEmpty() && !showKey.isNullOrEmpty()) {
Result(imageUrl, skipHathKey, originImageUrl, showKey)
} else {
- throw ParseException("Parse image url and show error", body)
+ throw ParseException("Parse image url and show error")
}
}
diff --git a/app/src/main/java/com/hippo/ehviewer/client/parser/HomeParser.kt b/app/src/main/java/com/hippo/ehviewer/client/parser/HomeParser.kt
index 823f12a9b..d4b70e580 100644
--- a/app/src/main/java/com/hippo/ehviewer/client/parser/HomeParser.kt
+++ b/app/src/main/java/com/hippo/ehviewer/client/parser/HomeParser.kt
@@ -24,7 +24,7 @@ object HomeParser {
val fundsGP = ParserUtils.parseInt(get(2), 0) * 1000
return Funds(fundsGP, fundsC)
}
- throw ParseException("Parse funds error", body)
+ throw ParseException("Parse funds error")
}
@Parcelize
diff --git a/app/src/main/java/com/hippo/ehviewer/client/parser/ProfileParser.kt b/app/src/main/java/com/hippo/ehviewer/client/parser/ProfileParser.kt
index afbfc57e6..5e612c1d9 100644
--- a/app/src/main/java/com/hippo/ehviewer/client/parser/ProfileParser.kt
+++ b/app/src/main/java/com/hippo/ehviewer/client/parser/ProfileParser.kt
@@ -46,7 +46,7 @@ object ProfileParser {
Result(displayName, avatar)
}.getOrElse {
ExceptionUtils.throwIfFatal(it)
- throw ParseException("Parse forums error", body)
+ throw ParseException("Parse forums error")
}
}
diff --git a/app/src/main/java/com/hippo/ehviewer/client/parser/SignInParser.kt b/app/src/main/java/com/hippo/ehviewer/client/parser/SignInParser.kt
index 1ad7bc990..0568f0546 100644
--- a/app/src/main/java/com/hippo/ehviewer/client/parser/SignInParser.kt
+++ b/app/src/main/java/com/hippo/ehviewer/client/parser/SignInParser.kt
@@ -35,7 +35,7 @@ object SignInParser {
if (m.find()) {
throw EhException(m.group(1) ?: m.group(2))
} else {
- throw ParseException("Can't parse sign in", body)
+ throw ParseException("Can't parse sign in")
}
}
}
diff --git a/app/src/main/java/com/hippo/ehviewer/client/parser/TorrentParser.kt b/app/src/main/java/com/hippo/ehviewer/client/parser/TorrentParser.kt
index f4e880980..40f54df6e 100644
--- a/app/src/main/java/com/hippo/ehviewer/client/parser/TorrentParser.kt
+++ b/app/src/main/java/com/hippo/ehviewer/client/parser/TorrentParser.kt
@@ -1,9 +1,12 @@
package com.hippo.ehviewer.client.parser
+import com.hippo.ehviewer.client.exception.ParseException
import java.nio.ByteBuffer
object TorrentParser {
- fun parse(body: ByteBuffer) = parseTorrent(body)
+ fun parse(body: ByteBuffer) = runCatching {
+ parseTorrent(body)
+ }.getOrElse { throw ParseException("Can't parse torrent list", it) }
}
class Torrent(
diff --git a/app/src/main/java/com/hippo/ehviewer/download/DownloadManager.kt b/app/src/main/java/com/hippo/ehviewer/download/DownloadManager.kt
index 5eda17d11..068f59623 100644
--- a/app/src/main/java/com/hippo/ehviewer/download/DownloadManager.kt
+++ b/app/src/main/java/com/hippo/ehviewer/download/DownloadManager.kt
@@ -462,7 +462,11 @@ object DownloadManager : OnSpiderListener {
fun moveDownload(fromItem: DownloadInfo, toItem: DownloadInfo): List {
val fromPosition = allInfoList.indexOf(fromItem)
val toPosition = allInfoList.indexOf(toItem)
- return moveDownload(fromPosition, toPosition)
+ return if (fromPosition != -1 && toPosition != -1) {
+ moveDownload(fromPosition, toPosition)
+ } else {
+ emptyList()
+ }
}
suspend fun resetAllReadingProgress() = coroutineScope {
diff --git a/app/src/main/java/com/hippo/ehviewer/ui/legacy/SearchLayout.kt b/app/src/main/java/com/hippo/ehviewer/ui/legacy/SearchLayout.kt
index 8656bd157..7f6e1c688 100644
--- a/app/src/main/java/com/hippo/ehviewer/ui/legacy/SearchLayout.kt
+++ b/app/src/main/java/com/hippo/ehviewer/ui/legacy/SearchLayout.kt
@@ -13,6 +13,7 @@ import androidx.compose.foundation.layout.WindowInsets
import androidx.compose.foundation.layout.asPaddingValues
import androidx.compose.foundation.layout.fillMaxWidth
import androidx.compose.foundation.layout.height
+import androidx.compose.foundation.layout.imePadding
import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.layout.systemBars
import androidx.compose.foundation.rememberScrollState
@@ -71,7 +72,7 @@ class SearchLayout @JvmOverloads constructor(
}
}
Mdc3Theme {
- Column(modifier = Modifier.padding(horizontal = dimensionResource(id = R.dimen.search_layout_margin_h)).verticalScroll(rememberScrollState())) {
+ Column(modifier = Modifier.padding(horizontal = dimensionResource(id = R.dimen.search_layout_margin_h)).imePadding().verticalScroll(rememberScrollState())) {
AnimatedVisibility(visible = isNormalMode) {
ElevatedCard(modifier = Modifier.fillMaxWidth().padding(vertical = dimensionResource(id = R.dimen.search_layout_margin_v))) {
Column(modifier = Modifier.padding(horizontal = dimensionResource(id = R.dimen.search_category_padding_h), vertical = dimensionResource(id = R.dimen.search_category_padding_v))) {
diff --git a/app/src/main/java/com/hippo/ehviewer/ui/main/AdvancedSearch.kt b/app/src/main/java/com/hippo/ehviewer/ui/main/AdvancedSearch.kt
index 0b090c0fd..a0249fec5 100644
--- a/app/src/main/java/com/hippo/ehviewer/ui/main/AdvancedSearch.kt
+++ b/app/src/main/java/com/hippo/ehviewer/ui/main/AdvancedSearch.kt
@@ -28,6 +28,7 @@ import androidx.compose.ui.res.stringResource
import androidx.compose.ui.text.input.KeyboardType
import androidx.compose.ui.unit.dp
import com.hippo.ehviewer.R
+import com.hippo.ehviewer.util.toIntOrDefault
@Composable
fun SearchAdvanced(
@@ -55,9 +56,6 @@ fun SearchAdvanced(
onExpandedChange = { expanded = !expanded },
) {
val softwareKeyboardController = LocalSoftwareKeyboardController.current
- SideEffect {
- softwareKeyboardController?.hide()
- }
OutlinedTextField(
modifier = Modifier.menuAnchor(),
readOnly = true,
@@ -71,6 +69,9 @@ fun SearchAdvanced(
expanded = expanded,
onDismissRequest = { expanded = false },
) {
+ SideEffect {
+ softwareKeyboardController?.hide()
+ }
minRatingItems.forEach { selectionOption ->
DropdownMenuItem(
text = { Text(selectionOption) },
@@ -88,18 +89,18 @@ fun SearchAdvanced(
Checkbox(checked = enabled, onCheckedChange = { enabled = it })
Text(text = stringResource(id = R.string.search_sp), modifier = Modifier.align(Alignment.CenterVertically))
OutlinedTextField(
- keyboardOptions = KeyboardOptions(keyboardType = KeyboardType.Number),
+ keyboardOptions = KeyboardOptions(keyboardType = KeyboardType.NumberPassword),
value = if (enabled && state.fromPage != -1) state.fromPage.toString() else "",
- onValueChange = { onStateChanged(state.copy(fromPage = it.toInt())) },
+ onValueChange = { onStateChanged(state.copy(fromPage = it.toIntOrDefault(-1))) },
modifier = Modifier.width(96.dp).padding(16.dp),
singleLine = true,
enabled = enabled,
)
Text(text = stringResource(id = R.string.search_sp_to), modifier = Modifier.align(Alignment.CenterVertically))
OutlinedTextField(
- keyboardOptions = KeyboardOptions(keyboardType = KeyboardType.Number),
+ keyboardOptions = KeyboardOptions(keyboardType = KeyboardType.NumberPassword),
value = if (enabled && state.toPage != -1) state.toPage.toString() else "",
- onValueChange = { onStateChanged(state.copy(toPage = it.toInt())) },
+ onValueChange = { onStateChanged(state.copy(toPage = it.toIntOrDefault(-1))) },
modifier = Modifier.width(96.dp).padding(16.dp),
singleLine = true,
enabled = enabled,
diff --git a/app/src/main/java/com/hippo/ehviewer/ui/scene/DownloadsScene.kt b/app/src/main/java/com/hippo/ehviewer/ui/scene/DownloadsScene.kt
index cc6c3cf46..1ce5249d0 100644
--- a/app/src/main/java/com/hippo/ehviewer/ui/scene/DownloadsScene.kt
+++ b/app/src/main/java/com/hippo/ehviewer/ui/scene/DownloadsScene.kt
@@ -57,7 +57,6 @@ import androidx.drawerlayout.widget.DrawerLayout
import androidx.lifecycle.lifecycleScope
import androidx.recyclerview.widget.DiffUtil
import androidx.recyclerview.widget.ItemTouchHelper
-import androidx.recyclerview.widget.ItemTouchHelper.ACTION_STATE_DRAG
import androidx.recyclerview.widget.LinearLayoutManager
import androidx.recyclerview.widget.ListAdapter
import androidx.recyclerview.widget.RecyclerView
@@ -299,15 +298,13 @@ class DownloadsScene :
}
recyclerView.addItemDecoration(decoration)
mItemTouchHelper = ItemTouchHelper(object : ItemTouchHelper.Callback() {
- override fun onSelectedChanged(viewHolder: RecyclerView.ViewHolder?, actionState: Int) {
- super.onSelectedChanged(viewHolder, actionState)
- if (actionState == ACTION_STATE_DRAG) {
- tracker.clearSelection()
- }
- }
override fun isLongPressDragEnabled() = false
override fun getMovementFlags(recyclerView: RecyclerView, viewHolder: RecyclerView.ViewHolder): Int {
- return makeMovementFlags(ItemTouchHelper.UP or ItemTouchHelper.DOWN, ItemTouchHelper.LEFT)
+ return if (tracker.isInCustomChoice) {
+ 0
+ } else {
+ makeMovementFlags(ItemTouchHelper.UP or ItemTouchHelper.DOWN, ItemTouchHelper.LEFT)
+ }
}
override fun onMove(
recyclerView: RecyclerView,
@@ -364,14 +361,8 @@ class DownloadsScene :
}
override fun onSearchViewExpanded() {
+ hideActionFab(true)
super.onSearchViewExpanded()
- if (tracker.isInCustomChoice) tracker.clearSelection()
- showSearchFab(true)
- }
-
- override fun onSearchViewHidden() {
- super.onSearchViewHidden()
- hideSearchFab(true)
}
override fun onDestroyView() {
diff --git a/app/src/main/java/com/hippo/ehviewer/ui/scene/FavoritesScene.kt b/app/src/main/java/com/hippo/ehviewer/ui/scene/FavoritesScene.kt
index bdda08917..9d8c43f0e 100644
--- a/app/src/main/java/com/hippo/ehviewer/ui/scene/FavoritesScene.kt
+++ b/app/src/main/java/com/hippo/ehviewer/ui/scene/FavoritesScene.kt
@@ -392,7 +392,11 @@ class FavoritesScene : SearchBarScene() {
}
val itemTouchHelper = ItemTouchHelper(object : ItemTouchHelper.Callback() {
override fun getMovementFlags(recyclerView: RecyclerView, viewHolder: RecyclerView.ViewHolder): Int {
- return makeMovementFlags(0, ItemTouchHelper.LEFT)
+ return if (tracker.isInCustomChoice) {
+ 0
+ } else {
+ makeMovementFlags(0, ItemTouchHelper.LEFT)
+ }
}
override fun onMove(
recyclerView: RecyclerView,
@@ -478,13 +482,13 @@ class FavoritesScene : SearchBarScene() {
}
override fun onSearchViewExpanded() {
+ hideActionFab(true)
super.onSearchViewExpanded()
- selectSearchFab(true)
}
override fun onSearchViewHidden() {
super.onSearchViewHidden()
- selectActionFab(true)
+ showActionFab(true)
}
private fun showGoToDialog() {
diff --git a/app/src/main/java/com/hippo/ehviewer/ui/scene/GalleryListScene.kt b/app/src/main/java/com/hippo/ehviewer/ui/scene/GalleryListScene.kt
index 0573f3c38..c0fe7ab77 100644
--- a/app/src/main/java/com/hippo/ehviewer/ui/scene/GalleryListScene.kt
+++ b/app/src/main/java/com/hippo/ehviewer/ui/scene/GalleryListScene.kt
@@ -687,13 +687,21 @@ class GalleryListScene : SearchBarScene() {
}
override fun onSearchViewExpanded() {
+ if (mState == State.NORMAL) {
+ hideActionFab(true)
+ } else {
+ hideSearchFab(true)
+ }
super.onSearchViewExpanded()
- if (mState == State.NORMAL) selectSearchFab(true)
}
override fun onSearchViewHidden() {
super.onSearchViewHidden()
- if (mState == State.NORMAL) selectActionFab(true)
+ if (mState == State.NORMAL) {
+ showActionFab(true)
+ } else {
+ showSearchFab(true)
+ }
}
private fun showActionFab() {
diff --git a/app/src/main/java/com/hippo/ehviewer/ui/scene/SearchBarScene.kt b/app/src/main/java/com/hippo/ehviewer/ui/scene/SearchBarScene.kt
index d1113dada..a9e2d55e7 100644
--- a/app/src/main/java/com/hippo/ehviewer/ui/scene/SearchBarScene.kt
+++ b/app/src/main/java/com/hippo/ehviewer/ui/scene/SearchBarScene.kt
@@ -167,8 +167,7 @@ abstract class SearchBarScene : BaseScene(), ToolBarScene {
fun showSearchFab(animation: Boolean, delay: Long = 0) {
if (animation) {
mSearchFab.visibility = View.VISIBLE
- mSearchFab.rotation = -45.0f
- mSearchFab.animate().scaleX(1.0f).scaleY(1.0f).rotation(0.0f).setListener(null)
+ mSearchFab.animate().scaleX(1.0f).scaleY(1.0f).setListener(null)
.setDuration(ANIMATE_TIME).setStartDelay(delay)
.setInterpolator(AnimationUtils.FAST_SLOW_INTERPOLATOR).start()
} else {
@@ -196,10 +195,26 @@ abstract class SearchBarScene : BaseScene(), ToolBarScene {
}
}
- fun selectSearchFab(animation: Boolean) {
- _binding ?: return
+ fun showActionFab(animation: Boolean, delay: Long = ANIMATE_TIME) {
+ mShowActionFab = true
+ if (animation) {
+ val fab = fabLayout.primaryFab!!
+ fab.visibility = View.VISIBLE
+ fab.animate().scaleX(1.0f).scaleY(1.0f).setListener(null)
+ .setDuration(ANIMATE_TIME).setStartDelay(delay)
+ .setInterpolator(AnimationUtils.FAST_SLOW_INTERPOLATOR).start()
+ } else {
+ fabLayout.setExpanded(expanded = false, animation = false)
+ val fab = fabLayout.primaryFab!!
+ fab.visibility = View.VISIBLE
+ fab.scaleX = 1.0f
+ fab.scaleY = 1.0f
+ }
+ }
+
+ fun hideActionFab(animation: Boolean): Long {
mShowActionFab = false
- val delay = if (animation) {
+ return if (animation) {
val fab = fabLayout.primaryFab!!
if (View.INVISIBLE == fab.visibility) {
0L
@@ -218,27 +233,16 @@ abstract class SearchBarScene : BaseScene(), ToolBarScene {
fab.scaleY = 0.0f
0L
}
+ }
+
+ fun selectSearchFab(animation: Boolean) {
+ val delay = hideActionFab(animation)
showSearchFab(animation, delay)
}
fun selectActionFab(animation: Boolean) {
- _binding ?: return
- mShowActionFab = true
val delay = hideSearchFab(animation)
- if (animation) {
- val fab = fabLayout.primaryFab!!
- fab.visibility = View.VISIBLE
- fab.rotation = -45.0f
- fab.animate().scaleX(1.0f).scaleY(1.0f).rotation(0.0f).setListener(null)
- .setDuration(ANIMATE_TIME).setStartDelay(delay)
- .setInterpolator(AnimationUtils.FAST_SLOW_INTERPOLATOR).start()
- } else {
- fabLayout.setExpanded(expanded = false, animation = false)
- val fab = fabLayout.primaryFab!!
- fab.visibility = View.VISIBLE
- fab.scaleX = 1.0f
- fab.scaleY = 1.0f
- }
+ showActionFab(animation, delay)
}
fun setSearchBarHint(hint: String?) {
diff --git a/app/src/main/java/com/hippo/ehviewer/ui/settings/AboutScreen.kt b/app/src/main/java/com/hippo/ehviewer/ui/settings/AboutScreen.kt
index c3afab38a..8ceb72a1e 100644
--- a/app/src/main/java/com/hippo/ehviewer/ui/settings/AboutScreen.kt
+++ b/app/src/main/java/com/hippo/ehviewer/ui/settings/AboutScreen.kt
@@ -112,7 +112,7 @@ fun AboutScreen() {
title = stringResource(id = R.string.use_ci_update_channel),
value = Settings::useCIUpdateChannel,
)
- SimpleMenuPreferenceInt2(
+ SimpleMenuPreferenceInt(
title = stringResource(id = R.string.auto_updates),
entry = R.array.update_frequency,
entryValueRes = R.array.update_frequency_values,
diff --git a/app/src/main/java/com/hippo/ehviewer/ui/settings/PreferenceWidgets.kt b/app/src/main/java/com/hippo/ehviewer/ui/settings/PreferenceWidgets.kt
index 4c7402be2..3b9130fc1 100644
--- a/app/src/main/java/com/hippo/ehviewer/ui/settings/PreferenceWidgets.kt
+++ b/app/src/main/java/com/hippo/ehviewer/ui/settings/PreferenceWidgets.kt
@@ -114,20 +114,6 @@ fun HtmlPreference(title: String, summary: AnnotatedString? = null, onClick: ()
@Composable
fun SimpleMenuPreferenceInt(title: String, summary: String? = null, @ArrayRes entry: Int, @ArrayRes entryValueRes: Int, value: MutableState) {
- val entryArray = stringArrayResource(id = entry)
- val valuesArray = stringArrayResource(id = entryValueRes)
- val map = remember {
- val iter = entryArray.iterator()
- valuesArray.associateWith { iter.next() }
- }
- var v by value
- fun set(new: String) { v = new.toInt() }
- check(entryArray.size == valuesArray.size)
- DropDownPref(title = title, summary = summary, defaultValue = v.toString(), onValueChange = ::set, useSelectedAsSummary = summary.isNullOrBlank(), entries = map)
-}
-
-@Composable
-fun SimpleMenuPreferenceInt2(title: String, summary: String? = null, @ArrayRes entry: Int, @ArrayRes entryValueRes: Int, value: MutableState) {
val entryArray = stringArrayResource(id = entry)
val valuesArray = integerArrayResource(id = entryValueRes)
val map = remember {
diff --git a/app/src/main/java/com/hippo/ehviewer/util/AppConfig.kt b/app/src/main/java/com/hippo/ehviewer/util/AppConfig.kt
index afeda9a5d..8147a3345 100644
--- a/app/src/main/java/com/hippo/ehviewer/util/AppConfig.kt
+++ b/app/src/main/java/com/hippo/ehviewer/util/AppConfig.kt
@@ -60,12 +60,12 @@ object AppConfig {
return FileUtils.createTempFile(tempDir, null)
}
- fun saveParseErrorBody(e: ParseException) {
+ fun saveParseErrorBody(e: ParseException, body: String) {
val dir = externalParseErrorDir ?: return
File(dir, ReadableTime.getFilenamableTime(System.currentTimeMillis()) + ".txt").writer().use {
it.write(e.message)
it.appendLine()
- it.append(e.body)
+ it.append(body)
}
}
diff --git a/app/src/main/res/values/arrays.xml b/app/src/main/res/values/arrays.xml
index acacee360..0fb32f402 100644
--- a/app/src/main/res/values/arrays.xml
+++ b/app/src/main/res/values/arrays.xml
@@ -21,20 +21,20 @@
- @string/settings_eh_list_mode_thumb
-
+
- 0
- 1
-
+
- @string/site_e
- @string/site_ex
-
+
- 0
- 1
-
+
- @string/homepage
@@ -43,22 +43,22 @@
- @string/toplist
-
+
- 0
- 1
- 2
- 3
-
+
- @string/settings_eh_detail_size_long
- @string/settings_eh_detail_size_short
-
+
- 0
- 1
-
+
- @string/settings_eh_thumb_resolution_auto
@@ -66,11 +66,11 @@
- 300x
-
+
- 0
- 1
- 2
-
+
- @string/unlimited
@@ -87,12 +87,12 @@
- 7
-
+
- 1
- 3
- 5
- 7
-
+
- 0
@@ -107,7 +107,7 @@
- 20000
-
+
- 0
- 500
- 1000
@@ -118,7 +118,7 @@
- 5000
- 10000
- 20000
-
+
- 3
@@ -129,14 +129,14 @@
- 17
-
+
- 3
- 5
- 7
- 11
- 13
- 17
-
+
- 320 MB
@@ -146,13 +146,13 @@
- 5120 MB
-
+
- 320
- 640
- 1280
- 2560
- 5120
-
+
- @string/filter_title
@@ -208,11 +208,11 @@
- @string/dark_theme_on
-
+
- -1
- 1
- 2
-
+
- @string/download_all
diff --git a/app/src/main/rust/Cargo.toml b/app/src/main/rust/Cargo.toml
index ad388629e..6f953b13c 100644
--- a/app/src/main/rust/Cargo.toml
+++ b/app/src/main/rust/Cargo.toml
@@ -12,7 +12,7 @@ jnix = { path = "jnix" }
jnix-macros = "0.4"
catch_panic = { path = "catch_panic" }
catch_panic_macros = { path = "catch_panic_macros" }
-jni_fn = { path = "jni_fn" }
+jni_fn = "0.1.2"
quick-xml = { default-features = false, version = "0.30" }
tl = "0.7"
log = "0.4"
diff --git a/app/src/main/rust/catch_panic_macros/Cargo.toml b/app/src/main/rust/catch_panic_macros/Cargo.toml
index 65f0b739e..137878c44 100644
--- a/app/src/main/rust/catch_panic_macros/Cargo.toml
+++ b/app/src/main/rust/catch_panic_macros/Cargo.toml
@@ -19,4 +19,4 @@ proc-macro = true
[dependencies]
proc-macro2 = "1.0"
quote = "1.0"
-syn = { version = "1.0", features = ["full"] }
+syn = { version = "2.0", features = ["full"] }
diff --git a/app/src/main/rust/catch_panic_macros/src/lib.rs b/app/src/main/rust/catch_panic_macros/src/lib.rs
index bf4184c0f..174a3d81a 100644
--- a/app/src/main/rust/catch_panic_macros/src/lib.rs
+++ b/app/src/main/rust/catch_panic_macros/src/lib.rs
@@ -3,10 +3,7 @@ use std::ops::Deref;
use proc_macro::TokenStream;
use quote::{quote, quote_spanned, ToTokens};
use syn::spanned::Spanned;
-use syn::{
- parse_macro_input, AttributeArgs, Expr, FnArg, ItemFn, Lit, Meta, MetaNameValue, NestedMeta,
- Pat, Path,
-};
+use syn::{parse_macro_input, Expr, FnArg, ItemFn, LitStr, Pat, Path};
const ONLY_FUNCTIONS_MSG: &str = "#[catch_panic] can only be applied to functions";
const FIRST_ARG_MSG: &str =
@@ -33,30 +30,21 @@ pub fn catch_panic(attr: TokenStream, item: TokenStream) -> TokenStream {
let item = proc_macro2::TokenStream::from(item);
// parse macro arguments
- let args = parse_macro_input!(attr as AttributeArgs);
let mut default_value = None;
let mut handler = None;
-
- for arg in args {
- if let NestedMeta::Meta(Meta::NameValue(MetaNameValue {
- path,
- lit: Lit::Str(str),
- ..
- })) = arg
- {
- match path.get_ident() {
- Some(id) if id == "default" => match str.parse::() {
- Ok(expr) => default_value = Some(expr),
- Err(err) => abort!(err.to_compile_error(), item),
- },
- Some(id) if id == "handler" => match str.parse::() {
- Ok(expr) => handler = Some(expr),
- Err(err) => abort!(err.to_compile_error(), item),
- },
- _ => {}
- }
+ let parser = syn::meta::parser(|meta| {
+ if meta.path.is_ident("default") {
+ default_value = Some(meta.value()?.parse::()?.parse::()?);
+ Ok(())
+ } else if meta.path.is_ident("handler") {
+ handler = Some(meta.value()?.parse::()?.parse::()?);
+ Ok(())
+ } else {
+ Err(meta.error("unsupported argument"))
}
- }
+ });
+
+ parse_macro_input!(attr with parser);
let default_value = match default_value {
Some(val) => val.to_token_stream(),
diff --git a/app/src/main/rust/jni_fn/.gitignore b/app/src/main/rust/jni_fn/.gitignore
deleted file mode 100644
index 96ef6c0b9..000000000
--- a/app/src/main/rust/jni_fn/.gitignore
+++ /dev/null
@@ -1,2 +0,0 @@
-/target
-Cargo.lock
diff --git a/app/src/main/rust/jni_fn/Cargo.toml b/app/src/main/rust/jni_fn/Cargo.toml
deleted file mode 100644
index 3cbaed651..000000000
--- a/app/src/main/rust/jni_fn/Cargo.toml
+++ /dev/null
@@ -1,22 +0,0 @@
-[package]
-name = "jni_fn"
-version = "0.1.0"
-authors = ["Anton Lazarev "]
-edition = "2021"
-description = "JNI-compatible method signature generator"
-repository = "https://gitlab.com/antonok/jni_fn"
-readme = "README.md"
-keywords = ["jni", "java", "jvm", "android", "ndk"]
-categories = ["api-bindings", "development-tools::procedural-macro-helpers"]
-license = "MIT OR Apache-2.0"
-
-[lib]
-proc-macro = true
-
-[dependencies]
-syn = { version = "1.0", features = ["full"] }
-quote = "1.0"
-proc-macro2 = "1.0"
-
-[dev-dependencies]
-jni = "0.20"
diff --git a/app/src/main/rust/jni_fn/src/lib.rs b/app/src/main/rust/jni_fn/src/lib.rs
deleted file mode 100644
index 03700c8db..000000000
--- a/app/src/main/rust/jni_fn/src/lib.rs
+++ /dev/null
@@ -1,398 +0,0 @@
-//! JNI-compatible method signature generator for Rust libraries.
-//!
-//! This crate was designed for use with the [`jni`](https://crates.io/crates/jni) crate, which
-//! exposes JNI-compatible type bindings. Although it's possible to use `jni` without `jni_fn`, the
-//! procedural macro defined here will make it easier to write the method signatures correctly.
-//!
-//! See the `jni_fn` attribute macro documentation below for more info and usage examples.
-
-#![deny(missing_docs)]
-#![deny(unsafe_code)]
-
-use proc_macro2::TokenStream;
-use quote::ToTokens;
-use syn::spanned::Spanned;
-
-/// Annotate a function with this procedural macro attribute to expose it over the JNI.
-///
-/// This attribute takes a single string literal as an argument, specifying the package namespace
-/// this function should be placed under.
-///
-/// ```
-/// use jni::{ JNIEnv, objects::{ JClass, JString }, sys::jstring };
-/// use jni_fn::jni_fn;
-///
-/// #[jni_fn("com.example.RustBindings")]
-/// pub fn sayHello(env: JNIEnv, _: JClass, name: JString) -> jstring {
-/// let name_javastr = env.get_string(name).unwrap();
-/// let name = name_javastr.to_str().unwrap();
-///
-/// env.new_string(format!("Hello, {}!", name))
-/// .expect("Couldn't create java string!")
-/// .into_inner()
-/// }
-/// ```
-///
-/// The `sayHello` function will automatically be expanded to have the correct ABI specification
-/// and the appropriate JNI-compatible name, i.e. in this case -
-/// `Java_com_example_RustBindings_sayHello`.
-///
-/// Then it can be accessed by, for example, Kotlin code as follows:
-/// ```kotlin
-/// package com.example.RustBindings
-///
-/// class RustBindings {
-/// private external fun sayHello(name: String): String
-///
-/// fun greetWorld() {
-/// println(sayHello("world"))
-/// }
-/// }
-/// ```
-#[proc_macro_attribute]
-pub fn jni_fn(
- attr: proc_macro::TokenStream,
- item: proc_macro::TokenStream,
-) -> proc_macro::TokenStream {
- jni_fn2(attr.into(), item.into()).into()
-}
-
-/// Deals exclusively with `proc_macro2::TokenStream` instead of `proc_macro::TokenStream`,
-/// allowing it and all interior functionality to be unit tested.
-fn jni_fn2(attr: TokenStream, item: TokenStream) -> TokenStream {
- let attr_span = attr.span();
- let item_span = item.span();
-
- let mut function: syn::ItemFn = match syn::parse2(item) {
- Ok(f) => f,
- Err(_e) => {
- return syn::Error::new(
- item_span,
- "The `jni_fn` attribute can only be applied to `fn` items",
- )
- .to_compile_error()
- }
- };
-
- let namespace = match syn::parse2::(attr) {
- Ok(n) => n,
- Err(_e) => return syn::Error::new(attr_span, "The `jni_fn` attribute must have a single string literal supplied to specify the namespace").to_compile_error(),
- }.value();
-
- if !valid_namespace(&namespace) {
- return syn::Error::new(
- attr_span,
- "Invalid package namespace supplied to `jni_fn` attribute",
- )
- .to_compile_error();
- }
-
- let orig_fn_name = function.sig.ident.to_string();
-
- function.sig.ident = syn::Ident::new(
- &create_jni_fn_name(&namespace, &orig_fn_name),
- function.sig.ident.span(),
- );
-
- function.attrs.push(syn::Attribute {
- pound_token: Default::default(),
- style: syn::AttrStyle::Outer,
- bracket_token: Default::default(),
- path: syn::parse_str("no_mangle").unwrap(),
- tokens: TokenStream::new(),
- });
- function.attrs.push(syn::Attribute {
- pound_token: Default::default(),
- style: syn::AttrStyle::Outer,
- bracket_token: Default::default(),
- path: syn::parse_str("allow").unwrap(),
- tokens: quote::quote! { (non_snake_case) },
- });
-
- if function.sig.abi.is_some() {
- return syn::Error::new(function.sig.abi.span(), "Don't specify an ABI for `jni_fn` attributed functions - the correct ABI will be added automatically").to_compile_error();
- }
- function.sig.abi = Some(syn::Abi {
- extern_token: Default::default(),
- name: Some(syn::LitStr::new("system", function.sig.ident.span())),
- });
-
- if !matches!(function.vis, syn::Visibility::Public(_)) {
- return syn::Error::new(
- function.vis.span(),
- "`jni_fn` attributed functions must have public visibility (`pub`)",
- )
- .to_compile_error();
- }
-
- function.into_token_stream()
-}
-
-/// Ensures that `namespace` appears roughly like a valid package name.
-///
-/// A package name is a '.'-separated identifier list.
-///
-/// Identifiers are described in section 3.8 of the Java language specification, although some
-/// JVM-compatible languages have slightly different restrictions on what is considered a valid
-/// identifier. This function attempts to catch obviously incorrect strings.
-///
-/// Please submit an issue report or patch to make this more permissive if it's required for
-/// valid JVM code! Otherwise, making it more restrictive is appreciated as long as it's confirmed
-/// to work with multiple JVM-compatible languages.
-fn valid_namespace(namespace: &str) -> bool {
- /// These shouldn't occur _anywhere_ in the package name.
- const FORBIDDEN_CHARS: &[char] = &[
- ' ', ',', ':', ';', '|', '\\', '/', '!', '@', '#', '%', '^', '&', '*', '(', ')', '{', '}',
- '[', ']', '-', '`', '~', '\t', '\n', '\r',
- ];
-
- for c in FORBIDDEN_CHARS {
- if namespace.contains(*c) {
- return false;
- }
- }
-
- fn is_valid_ident(ident: &str) -> bool {
- /// These shouldn't occur as the first character of an identifier.
- const FORBIDDEN_START_CHARS: &[char] = &['0', '1', '2', '3', '4', '5', '6', '7', '8', '9'];
-
- if ident.is_empty() {
- return false;
- }
-
- for c in FORBIDDEN_START_CHARS {
- if ident.starts_with(*c) {
- return false;
- }
- }
-
- true
- }
-
- for ident in namespace.split('.') {
- if !is_valid_ident(ident) {
- return false;
- }
- }
-
- true
-}
-
-/// Creates a JNI-compatible function name from the given namespace and function name.
-/// This does _not_ transform the provided function name into `snakeCase` if it's not already; but
-/// `#[allow(non_snake_case)]` should be added to prevent errors.
-///
-/// Importantly, any underscores in the original namespace or function name need to be replaced by
-/// "_1", and then dot separators need to be turned into underscores.
-fn create_jni_fn_name(namespace: &str, fn_name: &str) -> String {
- let namespace_underscored = namespace.replace('_', "_1").replace('.', "_");
- let fn_name_underscored = fn_name.replace('_', "_1");
- format!("Java_{}_{}", namespace_underscored, fn_name_underscored)
-}
-
-#[cfg(test)]
-mod tests {
- use super::*;
-
- #[test]
- fn test_create_jni_fn_name() {
- assert_eq!(
- create_jni_fn_name("com.example.Foo", "init"),
- "Java_com_example_Foo_init"
- );
- assert_eq!(
- create_jni_fn_name("com.example.Bar", "closeIt"),
- "Java_com_example_Bar_closeIt"
- );
- assert_eq!(
- create_jni_fn_name("com.example.Bar", "close_it"),
- "Java_com_example_Bar_close_1it"
- );
- assert_eq!(
- create_jni_fn_name(
- "org.signal.client.internal.Native",
- "IdentityKeyPair_Deserialize"
- ),
- "Java_org_signal_client_internal_Native_IdentityKeyPair_1Deserialize"
- );
- }
-
- #[test]
- fn test_valid_namespace() {
- assert!(valid_namespace("com.example.Foo"));
- assert!(valid_namespace("com.antonok.kb"));
- assert!(valid_namespace("org.signal.client.internal.Native"));
- assert!(valid_namespace("net.under_score"));
- assert!(!valid_namespace("com example Foo"));
- assert!(!valid_namespace(" com.example.Foo"));
- assert!(!valid_namespace("com.example.Foo "));
- assert!(!valid_namespace("com.example.1Foo"));
- }
-
- #[test]
- fn test_code_generation() {
- let attr = quote::quote! {
- "com.example.Bar"
- };
- let source = quote::quote! {
- pub fn close_it(env: JNIEnv, _: JClass, filename: JString) -> jboolean {
- unimplemented!()
- }
- };
-
- let expanded = jni_fn2(attr.into(), source.into());
-
- assert_eq!(
- format!("{}", expanded),
- format!(
- "{}",
- quote::quote! {
- #[no_mangle]
- #[allow(non_snake_case)]
- pub extern "system" fn Java_com_example_Bar_close_1it (env: JNIEnv, _: JClass, filename: JString) -> jboolean {
- unimplemented!()
- }
- }
- )
- );
- }
-
- #[test]
- fn test_unsafe_fn() {
- let attr = quote::quote! {
- "com.example.Bar"
- };
- let source = quote::quote! {
- pub unsafe fn close_it(env: JNIEnv, _: JClass, filename: JString) -> jboolean {
- unimplemented!()
- }
- };
-
- let expanded = jni_fn2(attr.into(), source.into());
-
- assert_eq!(
- format!("{}", expanded),
- format!(
- "{}",
- quote::quote! {
- #[no_mangle]
- #[allow(non_snake_case)]
- pub unsafe extern "system" fn Java_com_example_Bar_close_1it (env: JNIEnv, _: JClass, filename: JString) -> jboolean {
- unimplemented!()
- }
- }
- )
- );
- }
-
- #[test]
- fn test_non_function() {
- let attr = quote::quote! { "com.example.Foo" };
- let source = quote::quote! {
- enum NotAFunction {
- Variant1,
- Variant2(u8),
- }
- };
-
- let expanded = jni_fn2(attr.into(), source.into());
-
- assert_eq!(
- format!("{}", expanded),
- format!(
- "{}",
- quote::quote! {
- compile_error! { "The `jni_fn` attribute can only be applied to `fn` items" }
- }
- )
- );
- }
-
- #[test]
- fn test_empty_attribute() {
- let attr = quote::quote! {};
- let source = quote::quote! {
- pub fn close_it(env: JNIEnv, _: JClass, filename: JString) -> jboolean {
- unimplemented!()
- }
- };
-
- let expanded = jni_fn2(attr.into(), source.into());
-
- assert_eq!(
- format!("{}", expanded),
- format!(
- "{}",
- quote::quote! {
- compile_error! { "The `jni_fn` attribute must have a single string literal supplied to specify the namespace" }
- }
- )
- );
- }
-
- #[test]
- fn test_invalid_namespace() {
- let attr = quote::quote! { "." };
- let source = quote::quote! {
- pub fn close_it(env: JNIEnv, _: JClass, filename: JString) -> jboolean {
- unimplemented!()
- }
- };
-
- let expanded = jni_fn2(attr.into(), source.into());
-
- assert_eq!(
- format!("{}", expanded),
- format!(
- "{}",
- quote::quote! {
- compile_error! { "Invalid package namespace supplied to `jni_fn` attribute" }
- }
- )
- );
- }
-
- #[test]
- fn test_specified_abi() {
- let attr = quote::quote! { "com.example.Foo" };
- let source = quote::quote! {
- pub extern "C" fn close_it(env: JNIEnv, _: JClass, filename: JString) -> jboolean {
- unimplemented!()
- }
- };
-
- let expanded = jni_fn2(attr.into(), source.into());
-
- assert_eq!(
- format!("{}", expanded),
- format!(
- "{}",
- quote::quote! {
- compile_error! { "Don't specify an ABI for `jni_fn` attributed functions - the correct ABI will be added automatically" }
- }
- )
- );
- }
-
- #[test]
- fn test_wrong_visibility() {
- let attr = quote::quote! { "com.example.Foo" };
- let source = quote::quote! {
- fn close_it(env: JNIEnv, _: JClass, filename: JString) -> jboolean {
- unimplemented!()
- }
- };
-
- let expanded = jni_fn2(attr.into(), source.into());
-
- assert_eq!(
- format!("{}", expanded),
- format!(
- "{}",
- quote::quote! {
- compile_error! { "`jni_fn` attributed functions must have public visibility (`pub`)" }
- }
- )
- );
- }
-}
diff --git a/app/src/main/rust/src/parser/torrent.rs b/app/src/main/rust/src/parser/torrent.rs
index 0fa140f33..f422c20aa 100644
--- a/app/src/main/rust/src/parser/torrent.rs
+++ b/app/src/main/rust/src/parser/torrent.rs
@@ -30,17 +30,21 @@ pub fn parseTorrent(env: JNIEnv, _class: JClass, buffer: JByteBuffer, limit: jin
parse_bytebuffer(&mut env, buffer, limit, |dom, parser, _env| {
Some(dom.query_selector("table")?.filter_map(|e| {
let html = e.get(parser)?.inner_html(parser);
- let grp = regex!(" ([0-9-]+) [0-9:]+[\\s\\S]+ ([0-9.]+ [KMGT]B)[\\s\\S]+ ([0-9]+)[\\s\\S]+ ([0-9]+)[\\s\\S]+ ([0-9]+)[\\s\\S]+([^<]+)[\\s\\S]+onclick=\"document.location='([^\"]+)'[^<]+>([^<]+)").captures(&html)?;
- let name = unescape(&grp[8]).ok()?;
- Some(Torrent {
- posted: grp[1].to_string(),
- size: grp[2].to_string(),
- seeds: grp[3].parse().ok()?,
- peers: grp[4].parse().ok()?,
- downloads: grp[5].parse().ok()?,
- url: grp[7].to_string(),
- name: name.to_string()
- })
+ if html.contains("Expunged") {
+ None
+ } else {
+ let grp = regex!(" ([0-9-]+) [0-9:]+[\\s\\S]+ ([0-9.]+ [KMGT]iB)[\\s\\S]+ ([0-9]+)[\\s\\S]+ ([0-9]+)[\\s\\S]+ ([0-9]+)[\\s\\S]+([^<]+)[\\s\\S]+onclick=\"document.location='([^\"]+)'[^<]+>([^<]+)").captures(&html).unwrap();
+ let name = unescape(&grp[8]).ok()?;
+ Some(Torrent {
+ posted: grp[1].to_string(),
+ size: grp[2].to_string(),
+ seeds: grp[3].parse().ok()?,
+ peers: grp[4].parse().ok()?,
+ downloads: grp[5].parse().ok()?,
+ url: grp[7].to_string(),
+ name: name.to_string()
+ })
+ }
}).collect::>())
}).unwrap().into_java(&env).forget().into_raw()
}
diff --git a/settings.gradle.kts b/settings.gradle.kts
index a5af90543..8c28e8012 100644
--- a/settings.gradle.kts
+++ b/settings.gradle.kts
@@ -19,5 +19,5 @@ dependencyResolutionManagement {
}
enableFeaturePreview("TYPESAFE_PROJECT_ACCESSORS")
-rootProject.name = "Ehviewer"
+rootProject.name = "EhViewer"
include(":app")