This commit is contained in:
armv9 2025-09-04 11:36:43 +09:00
parent 912de10cc3
commit cddcd84ca6
13 changed files with 53 additions and 39 deletions

View File

@ -113,7 +113,7 @@ object DataStore : OnPreferenceDataStoreChangeListener {
var remoteDns by configurationStore.string(Key.REMOTE_DNS) { "https://dns.google/dns-query" } var remoteDns by configurationStore.string(Key.REMOTE_DNS) { "https://dns.google/dns-query" }
var directDns by configurationStore.string(Key.DIRECT_DNS) { "https://223.5.5.5/dns-query" } var directDns by configurationStore.string(Key.DIRECT_DNS) { "https://223.5.5.5/dns-query" }
var enableDnsRouting by configurationStore.boolean(Key.ENABLE_DNS_ROUTING) { true } var enableDnsRouting by configurationStore.boolean(Key.ENABLE_DNS_ROUTING) { true }
var enableFakeDns by configurationStore.boolean(Key.ENABLE_FAKEDNS) var enableFakeDns by configurationStore.boolean(Key.ENABLE_FAKEDNS) { true }
var rulesProvider by configurationStore.stringToInt(Key.RULES_PROVIDER) var rulesProvider by configurationStore.stringToInt(Key.RULES_PROVIDER)
var logLevel by configurationStore.stringToInt(Key.LOG_LEVEL) var logLevel by configurationStore.stringToInt(Key.LOG_LEVEL)
@ -156,7 +156,7 @@ object DataStore : OnPreferenceDataStoreChangeListener {
var connectionTestConcurrent by configurationStore.int("connectionTestConcurrent") { 5 } var connectionTestConcurrent by configurationStore.int("connectionTestConcurrent") { 5 }
var alwaysShowAddress by configurationStore.boolean(Key.ALWAYS_SHOW_ADDRESS) var alwaysShowAddress by configurationStore.boolean(Key.ALWAYS_SHOW_ADDRESS)
var tunImplementation by configurationStore.stringToInt(Key.TUN_IMPLEMENTATION) { TunImplementation.MIXED } var tunImplementation by configurationStore.stringToInt(Key.TUN_IMPLEMENTATION) { TunImplementation.GVISOR }
var profileTrafficStatistics by configurationStore.boolean(Key.PROFILE_TRAFFIC_STATISTICS) { true } var profileTrafficStatistics by configurationStore.boolean(Key.PROFILE_TRAFFIC_STATISTICS) { true }
var yacdURL by configurationStore.string("yacdURL") { "http://127.0.0.1:9090/ui" } var yacdURL by configurationStore.string("yacdURL") { "http://127.0.0.1:9090/ui" }

View File

@ -34,6 +34,7 @@ abstract class SagerDatabase : RoomDatabase() {
SagerNet.application.getDatabasePath(Key.DB_PROFILE).parentFile?.mkdirs() SagerNet.application.getDatabasePath(Key.DB_PROFILE).parentFile?.mkdirs()
Room.databaseBuilder(SagerNet.application, SagerDatabase::class.java, Key.DB_PROFILE) Room.databaseBuilder(SagerNet.application, SagerDatabase::class.java, Key.DB_PROFILE)
// .addMigrations(*SagerDatabase_Migrations.build()) // .addMigrations(*SagerDatabase_Migrations.build())
.setJournalMode(JournalMode.TRUNCATE)
.allowMainThreadQueries() .allowMainThreadQueries()
.enableMultiInstanceInvalidation() .enableMultiInstanceInvalidation()
.fallbackToDestructiveMigration() .fallbackToDestructiveMigration()

View File

@ -16,6 +16,7 @@ abstract class PublicDatabase : RoomDatabase() {
val instance by lazy { val instance by lazy {
SagerNet.application.getDatabasePath(Key.DB_PROFILE).parentFile?.mkdirs() SagerNet.application.getDatabasePath(Key.DB_PROFILE).parentFile?.mkdirs()
Room.databaseBuilder(SagerNet.application, PublicDatabase::class.java, Key.DB_PUBLIC) Room.databaseBuilder(SagerNet.application, PublicDatabase::class.java, Key.DB_PUBLIC)
.setJournalMode(JournalMode.TRUNCATE)
.allowMainThreadQueries() .allowMainThreadQueries()
.enableMultiInstanceInvalidation() .enableMultiInstanceInvalidation()
.fallbackToDestructiveMigration() .fallbackToDestructiveMigration()

View File

@ -248,16 +248,19 @@ fun Fragment.needReload() {
fun Fragment.needRestart() { fun Fragment.needRestart() {
snackbar(R.string.need_restart).setAction(R.string.apply) { snackbar(R.string.need_restart).setAction(R.string.apply) {
triggerFullRestart(requireContext())
}.show()
}
fun triggerFullRestart(ctx: Context) {
runOnDefaultDispatcher {
SagerNet.stopService() SagerNet.stopService()
val ctx = requireContext() delay(500)
runOnDefaultDispatcher { Executable.killAll(true)
delay(500) runOnMainDispatcher {
SagerDatabase.instance.close()
PublicDatabase.instance.close()
Executable.killAll(true)
ProcessPhoenix.triggerRebirth(ctx, Intent(ctx, MainActivity::class.java)) ProcessPhoenix.triggerRebirth(ctx, Intent(ctx, MainActivity::class.java))
} }
}.show() }
} }
fun Context.getColour(@ColorRes colorRes: Int): Int { fun Context.getColour(@ColorRes colorRes: Int): Int {

View File

@ -266,6 +266,7 @@ class AboutFragment : ToolbarFragment(R.layout.layout_about) {
} }
} }
} catch (e: Exception) { } catch (e: Exception) {
Logs.w(e)
runOnMainDispatcher { runOnMainDispatcher {
Toast.makeText(app, e.readableMessage, Toast.LENGTH_SHORT).show() Toast.makeText(app, e.readableMessage, Toast.LENGTH_SHORT).show()
} }

View File

@ -16,6 +16,7 @@ import com.jakewharton.processphoenix.ProcessPhoenix
import io.nekohasekai.sagernet.BuildConfig import io.nekohasekai.sagernet.BuildConfig
import io.nekohasekai.sagernet.R import io.nekohasekai.sagernet.R
import io.nekohasekai.sagernet.SagerNet import io.nekohasekai.sagernet.SagerNet
import io.nekohasekai.sagernet.bg.Executable
import io.nekohasekai.sagernet.database.* import io.nekohasekai.sagernet.database.*
import io.nekohasekai.sagernet.database.preference.KeyValuePair import io.nekohasekai.sagernet.database.preference.KeyValuePair
import io.nekohasekai.sagernet.database.preference.PublicDatabase import io.nekohasekai.sagernet.database.preference.PublicDatabase
@ -65,19 +66,11 @@ class BackupFragment : NamedFragment(R.layout.layout_backup) {
binding.resetSettings.setOnClickListener { binding.resetSettings.setOnClickListener {
MaterialAlertDialogBuilder(requireContext()).setTitle(R.string.confirm) MaterialAlertDialogBuilder(requireContext()).setTitle(R.string.confirm)
.setMessage(R.string.reset_settings) .setMessage(R.string.reset_settings_message)
.setNegativeButton(R.string.no, null) .setNegativeButton(R.string.no, null)
.setPositiveButton(R.string.yes) { _, _ -> .setPositiveButton(R.string.yes) { _, _ ->
runOnDefaultDispatcher { DataStore.configurationStore.reset()
DataStore.configurationStore.reset() triggerFullRestart(requireContext())
delay(500)
runOnMainDispatcher {
ProcessPhoenix.triggerRebirth(
requireContext(),
Intent(requireContext(), MainActivity::class.java)
)
}
}
} }
.show() .show()
} }
@ -251,9 +244,7 @@ class BackupFragment : NamedFragment(R.layout.layout_backup) {
import.backupRules.isChecked, import.backupRules.isChecked,
import.backupSettings.isChecked import.backupSettings.isChecked
) )
ProcessPhoenix.triggerRebirth( triggerFullRestart(requireContext())
requireContext(), Intent(requireContext(), MainActivity::class.java)
)
}.onFailure { }.onFailure {
Logs.w(it) Logs.w(it)
onMainDispatcher { onMainDispatcher {

View File

@ -9,9 +9,10 @@ import android.text.Spanned.SPAN_EXCLUSIVE_EXCLUSIVE
import android.text.style.ForegroundColorSpan import android.text.style.ForegroundColorSpan
import android.view.MenuItem import android.view.MenuItem
import android.view.View import android.view.View
import android.widget.ScrollView import android.view.ViewGroup
import androidx.appcompat.widget.Toolbar import androidx.appcompat.widget.Toolbar
import androidx.core.view.ViewCompat import androidx.core.view.ViewCompat
import androidx.core.view.doOnLayout
import io.nekohasekai.sagernet.R import io.nekohasekai.sagernet.R
import io.nekohasekai.sagernet.databinding.LayoutLogcatBinding import io.nekohasekai.sagernet.databinding.LayoutLogcatBinding
import io.nekohasekai.sagernet.ktx.* import io.nekohasekai.sagernet.ktx.*
@ -41,7 +42,6 @@ class LogcatFragment : ToolbarFragment(R.layout.layout_logcat),
ViewCompat.setOnApplyWindowInsetsListener(binding.root, ListListener) ViewCompat.setOnApplyWindowInsetsListener(binding.root, ListListener)
reloadSession() reloadSession()
// TODO new logcat
} }
private fun getColorForLine(line: String): ForegroundColorSpan { private fun getColorForLine(line: String): ForegroundColorSpan {
@ -76,8 +76,15 @@ class LogcatFragment : ToolbarFragment(R.layout.layout_logcat),
} }
binding.textview.text = span binding.textview.text = span
binding.scroolview.post { // 阻止自动滚动/焦点干扰
binding.scroolview.fullScroll(ScrollView.FOCUS_DOWN) binding.scroolview.descendantFocusability = ViewGroup.FOCUS_BLOCK_DESCENDANTS
binding.textview.isFocusable = false
binding.textview.isFocusableInTouchMode = false
binding.textview.clearFocus()
// 等 textview 完成最终 layout 再滚动到底部
binding.textview.doOnLayout {
binding.scroolview.scrollTo(0, binding.textview.height)
} }
} }

View File

@ -493,4 +493,5 @@
<string name="update_dialog_message">当前版本:%1$s\n可升级版本%2$s\n是否前往下载</string> <string name="update_dialog_message">当前版本:%1$s\n可升级版本%2$s\n是否前往下载</string>
<string name="check_update_no">检查成功,但没有更新。</string> <string name="check_update_no">检查成功,但没有更新。</string>
<string name="reset_settings">恢复默认设置</string> <string name="reset_settings">恢复默认设置</string>
<string name="reset_settings_message">恢复默认设置,但节点、分组等数据将保留。如需完全清除数据,请在系统设置中直接清除应用数据。</string>
</resources> </resources>

View File

@ -573,4 +573,5 @@
<string name="update_dialog_message">Current version: %1$s\nAvailable version: %2$s\nDo you want to download it?</string> <string name="update_dialog_message">Current version: %1$s\nAvailable version: %2$s\nDo you want to download it?</string>
<string name="check_update_no">Check successful, but no updates.</string> <string name="check_update_no">Check successful, but no updates.</string>
<string name="reset_settings">Restore default settings</string> <string name="reset_settings">Restore default settings</string>
<string name="reset_settings_message">Restore default settings, but data such as nodes and groups will be retained. To completely clear data, clear application data directly in the system settings.</string>
</resources> </resources>

View File

@ -29,7 +29,7 @@
app:title="@string/service_mode" app:title="@string/service_mode"
app:useSimpleSummaryProvider="true" /> app:useSimpleSummaryProvider="true" />
<moe.matsuri.nb4a.ui.SimpleMenuPreference <moe.matsuri.nb4a.ui.SimpleMenuPreference
app:defaultValue="2" app:defaultValue="0"
app:entries="@array/tun_implementation" app:entries="@array/tun_implementation"
app:entryValues="@array/int_array_3" app:entryValues="@array/int_array_3"
app:icon="@drawable/ic_baseline_flip_camera_android_24" app:icon="@drawable/ic_baseline_flip_camera_android_24"
@ -181,6 +181,7 @@
app:summary="@string/dns_routing_message" app:summary="@string/dns_routing_message"
app:title="@string/enable_dns_routing" /> app:title="@string/enable_dns_routing" />
<SwitchPreference <SwitchPreference
app:defaultValue="true"
app:icon="@drawable/ic_action_lock" app:icon="@drawable/ic_action_lock"
app:key="enableFakeDns" app:key="enableFakeDns"
app:summary="@string/fakedns_message" app:summary="@string/fakedns_message"

View File

@ -13,9 +13,11 @@ func GoDebug(any interface{}) {
} }
} }
func DeferPanicToError(name string, err func(error)) { func DeferPanicToError(name string, onError func(error)) {
if r := recover(); r != nil { if r := recover(); r != nil {
s := fmt.Errorf("%s panic: %s\n%s", name, r, string(debug.Stack())) if onError != nil {
err(s) s := fmt.Errorf("%s panic: %s\n%s", name, r, string(debug.Stack()))
onError(s)
}
} }
} }

View File

@ -207,6 +207,7 @@ func (r *httpRequest) SetContentString(content string) {
} }
func (r *httpRequest) Execute() (HTTPResponse, error) { func (r *httpRequest) Execute() (HTTPResponse, error) {
defer device.DeferPanicToError("http execute", func(err error) { log.Println(err) })
// full direct // full direct
if r.tryH3Direct && !r.trySocks5 { if r.tryH3Direct && !r.trySocks5 {
return r.doH3Direct() return r.doH3Direct()
@ -272,7 +273,7 @@ func (r *httpRequest) doH3Direct() (HTTPResponse, error) {
} }
return echClient.Do(request) return echClient.Do(request)
}, },
// H3 // H3 HTTPS
func() (response *http.Response, err error) { func() (response *http.Response, err error) {
request := r.request.Clone(context.Background()) request := r.request.Clone(context.Background())
h3Client := &http.Client{ h3Client := &http.Client{
@ -287,11 +288,13 @@ func (r *httpRequest) doH3Direct() (HTTPResponse, error) {
}, },
} }
if r.request.URL.Scheme == "http" {
funcs = funcs[:1]
}
for i, f := range funcs { for i, f := range funcs {
go func(f requestFunc) { go func(f requestFunc) {
defer device.DeferPanicToError("http", func(err error) { defer device.DeferPanicToError("http", func(err error) { log.Println(err) })
log.Println(err)
})
defer func() { defer func() {
if successCount.Load() == 0 { if successCount.Load() == 0 {
if failedCount.Add(1) >= uint32(len(funcs)) { if failedCount.Add(1) >= uint32(len(funcs)) {
@ -317,17 +320,19 @@ func (r *httpRequest) doH3Direct() (HTTPResponse, error) {
mu.Lock() mu.Lock()
finalErr = errors.Join(finalErr, fmt.Errorf("%s: %w", t, err)) finalErr = errors.Join(finalErr, fmt.Errorf("%s: %w", t, err))
mu.Unlock() mu.Unlock()
if rsp != nil && rsp.Body != nil {
rsp.Body.Close()
}
return return
} }
// 处理 HTTP 状态码 // 处理 HTTP 状态码
if rsp.StatusCode != http.StatusOK { if rsp.StatusCode != http.StatusOK {
hr := &httpResponse{Response: rsp} hr := &httpResponse{Response: rsp}
err = errors.Join(finalErr, fmt.Errorf("%s: %s", t, hr.errorString())) err = fmt.Errorf("%s: %s", t, hr.errorString())
mu.Lock() mu.Lock()
finalErr = err finalErr = errors.Join(finalErr, err)
mu.Unlock() mu.Unlock()
rsp.Body.Close()
return return
} }

View File

@ -1,4 +1,4 @@
PACKAGE_NAME=moe.nb4a PACKAGE_NAME=moe.nb4a
VERSION_NAME=1.3.9 VERSION_NAME=1.3.9
PRE_VERSION_NAME=pre-1.4.0-20250903-1 PRE_VERSION_NAME=pre-1.4.0-20250904-1
VERSION_CODE=43 VERSION_CODE=43