From c5fd7ab0db8b80f596846659729aa75cdf0de923 Mon Sep 17 00:00:00 2001 From: Jing <42014615+jing332@users.noreply.github.com> Date: Thu, 11 Jan 2024 10:27:13 +0800 Subject: [PATCH] action --- .gitignore | 2 + .idea/kotlinc.xml | 2 +- app/build.gradle | 25 ++- .../alistandroid/data/entities/ServerLog.kt | 23 ++- .../jing332/alistandroid/model/alist/AList.kt | 164 +++++++++++++----- .../alistandroid/service/AlistService.kt | 41 ++++- .../alistandroid/ui/nav/alist/AListScreen.kt | 15 +- .../jing332/alistandroid/util/StringUtils.kt | 5 + build.gradle | 11 +- 9 files changed, 205 insertions(+), 83 deletions(-) diff --git a/.gitignore b/.gitignore index 3beaf00..57cf795 100644 --- a/.gitignore +++ b/.gitignore @@ -1,4 +1,5 @@ # Project exclude paths +/.idea/ /.gradle/ /app/build/ /build/ @@ -16,4 +17,5 @@ alist-main *.tgz *.jar *.zip +*.so diff --git a/.idea/kotlinc.xml b/.idea/kotlinc.xml index fdf8d99..ae3f30a 100644 --- a/.idea/kotlinc.xml +++ b/.idea/kotlinc.xml @@ -1,6 +1,6 @@ - \ No newline at end of file diff --git a/app/build.gradle b/app/build.gradle index ed71671..17eeb5e 100644 --- a/app/build.gradle +++ b/app/build.gradle @@ -45,9 +45,6 @@ android { buildConfig true } - composeOptions { - kotlinCompilerExtensionVersion = "1.5.1" - } signingConfigs { release { storeFile file(pro["KEY_PATH"]) @@ -100,7 +97,9 @@ android { universalApk true } } - + composeOptions { + kotlinCompilerExtensionVersion = "1.5.7" + } compileOptions { // coreLibraryDesugaringEnabled true sourceCompatibility JavaVersion.VERSION_17 @@ -141,11 +140,11 @@ dependencies { implementation("com.github.jeziellago:compose-markdown:0.3.4") implementation("androidx.documentfile:documentfile:1.0.1") - implementation("androidx.core:core-ktx:1.10.1") + implementation("androidx.core:core-ktx:1.12.0") implementation("io.coil-kt:coil-compose:2.4.0") implementation("com.charleskorn.kaml:kaml:0.55.0") - implementation("org.jetbrains.kotlinx:kotlinx-serialization-json:1.5.1") + implementation("org.jetbrains.kotlinx:kotlinx-serialization-json:1.6.0") // Room implementation("androidx.room:room-runtime:$room_version") @@ -156,12 +155,12 @@ dependencies { // IO & NET implementation 'com.squareup.okio:okio:3.3.0' implementation 'com.squareup.okhttp3:okhttp:4.11.0' - implementation 'com.github.liangjingkanji:Net:3.5.8' + implementation 'com.github.liangjingkanji:Net:3.6.4' - implementation("com.google.android.exoplayer:exoplayer-core:2.19.0") + implementation("com.google.android.exoplayer:exoplayer-core:2.19.1") implementation("org.apache.commons:commons-lang3:3.12.0") - implementation("com.github.FunnySaltyFish.ComposeDataSaver:data-saver:v1.1.6") + implementation("com.github.FunnySaltyFish.ComposeDataSaver:data-saver:v1.1.8") implementation("com.louiscad.splitties:splitties-systemservices:3.0.0") implementation("androidx.localbroadcastmanager:localbroadcastmanager:1.1.0") @@ -171,12 +170,12 @@ dependencies { implementation("androidx.constraintlayout:constraintlayout-compose:1.0.1") - implementation("androidx.navigation:navigation-compose:2.7.0") + implementation("androidx.navigation:navigation-compose:2.7.6") - implementation 'androidx.activity:activity-compose:1.7.2' - implementation 'androidx.lifecycle:lifecycle-viewmodel-compose:2.6.1' + implementation 'androidx.activity:activity-compose:1.8.2' + implementation 'androidx.lifecycle:lifecycle-viewmodel-compose:2.6.2' // def composeBom = platform('androidx.compose:compose-bom:2023.06.01') - def composeBom = platform("dev.chrisbanes.compose:compose-bom:2023.07.00-alpha02") + def composeBom = platform("dev.chrisbanes.compose:compose-bom:2023.12.00-alpha04") implementation composeBom androidTestImplementation composeBom diff --git a/app/src/main/java/com/github/jing332/alistandroid/data/entities/ServerLog.kt b/app/src/main/java/com/github/jing332/alistandroid/data/entities/ServerLog.kt index ca3a6fa..d9ac640 100644 --- a/app/src/main/java/com/github/jing332/alistandroid/data/entities/ServerLog.kt +++ b/app/src/main/java/com/github/jing332/alistandroid/data/entities/ServerLog.kt @@ -12,4 +12,25 @@ data class ServerLog( @LogLevel val level: Int, val message: String, val description: String? = null, -) \ No newline at end of file +) { + companion object { + + @Suppress("RegExpRedundantEscape") + fun String.evalLog(): ServerLog? { + val logPattern = """(\w+)\[(\d{4}-\d{2}-\d{2} \d{2}:\d{2}:\d{2})\] (.+)""".toRegex() + val result = logPattern.find(this) + if (result != null) { + val (level, time, msg) = result.destructured + val l = when (level[0].toString()) { + "D" -> LogLevel.DEBUG + "I" -> LogLevel.INFO + "W" -> LogLevel.WARN + "E" -> LogLevel.ERROR + else -> LogLevel.INFO + } + return ServerLog(level = l, message = msg, description = time) + } + return null + } + } +} diff --git a/app/src/main/java/com/github/jing332/alistandroid/model/alist/AList.kt b/app/src/main/java/com/github/jing332/alistandroid/model/alist/AList.kt index 452af53..d0558cb 100644 --- a/app/src/main/java/com/github/jing332/alistandroid/model/alist/AList.kt +++ b/app/src/main/java/com/github/jing332/alistandroid/model/alist/AList.kt @@ -1,21 +1,27 @@ package com.github.jing332.alistandroid.model.alist -import alistlib.Alistlib -import alistlib.Event +import android.annotation.SuppressLint import android.content.Intent import android.util.Log import androidx.localbroadcastmanager.content.LocalBroadcastManager import com.github.jing332.alistandroid.R import com.github.jing332.alistandroid.app -import com.github.jing332.alistandroid.constant.AppConst import com.github.jing332.alistandroid.constant.LogLevel import com.github.jing332.alistandroid.data.appDb import com.github.jing332.alistandroid.data.entities.ServerLog +import com.github.jing332.alistandroid.data.entities.ServerLog.Companion.evalLog import com.github.jing332.alistandroid.service.AlistService +import com.github.jing332.alistandroid.util.FileUtils.readAllText +import com.github.jing332.alistandroid.util.StringUtils.removeAnsiCodes import com.github.jing332.alistandroid.util.ToastUtils.longToast -import kotlinx.serialization.ExperimentalSerializationApi -import kotlinx.serialization.json.decodeFromStream +import kotlinx.coroutines.CoroutineScope +import kotlinx.coroutines.Dispatchers +import kotlinx.coroutines.Job +import kotlinx.coroutines.isActive +import kotlinx.coroutines.launch import java.io.File +import java.io.IOException +import kotlin.coroutines.coroutineContext object AList { const val ACTION_STATUS_CHANGED = @@ -25,6 +31,10 @@ object AList { const val TYPE_HTTPS = "https" const val TYPE_UNIX = "unix" + private val execPath by lazy { + context.applicationInfo.nativeLibraryDir + File.separator + "libalist.so" + } + val context = app val dataPath: String @@ -37,37 +47,15 @@ object AList { * 是否有服务正在运行 */ val hasRunning: Boolean - get() = when { - Alistlib.isRunning(TYPE_HTTP) -> true - Alistlib.isRunning(TYPE_HTTPS) -> true - Alistlib.isRunning(TYPE_UNIX) -> true - else -> false - } + get() = false fun init() { - Alistlib.setConfigData(dataPath) +// Alistlib.setConfigData(dataPath) // Alistlib.setConfigDebug(BuildConfig.DEBUG) - Alistlib.setConfigLogStd(true) - - Alistlib.init(object : Event { - override fun onShutdown(type: String) { - notifyStatusChanged() - } - - override fun onStartError(type: String, msg: String) { - appDb.serverLogDao.insert( - ServerLog( - level = LogLevel.ERROR, - message = "${type}: $msg" - ) - ) - notifyStatusChanged() - } +// Alistlib.setConfigLogStd(true) - }) { level, msg -> - Log.i(AlistService.TAG, "level=${level}, msg=$msg") - appDb.serverLogDao.insert(ServerLog(level = level.toInt(), message = msg)) - } +// Log.i(AlistService.TAG, "level=${level}, msg=$msg") +// appDb.serverLogDao.insert(ServerLog(level = level.toInt(), message = msg)) } fun setAdminPassword(pwd: String) { @@ -75,33 +63,117 @@ object AList { init() } - Alistlib.setAdminPassword(pwd) + val log = execWithParams( + redirect = true, + params = arrayOf("admin", "set", pwd, "--data", dataPath) + ).inputStream.readAllText() + appDb.serverLogDao.insert(ServerLog(level = LogLevel.INFO, message = log.removeAnsiCodes())) } - @Suppress("DEPRECATION") - private fun notifyStatusChanged() { - LocalBroadcastManager.getInstance(context) - .sendBroadcast(Intent(ACTION_STATUS_CHANGED)) - } fun shutdown(timeout: Long = 5000L) { runCatching { - Alistlib.shutdown(timeout) + mProcess?.destroy() }.onFailure { context.longToast(R.string.server_shutdown_failed, it.toString()) } } + private var mProcess: Process? = null + + private suspend fun errorLogWatcher(onNewLine: (String) -> Unit) { + mProcess?.apply { + errorStream.bufferedReader().use { + while (coroutineContext.isActive) { + val line = it.readLine() ?: break + Log.d(AlistService.TAG, "Process errorStream: $line") + onNewLine(line) + } + } + } + } + + private suspend fun logWatcher(onNewLine: (String) -> Unit) { + mProcess?.apply { + inputStream.bufferedReader().use { + while (coroutineContext.isActive) { + val line = it.readLine() ?: break + Log.d(AlistService.TAG, "Process inputStream: $line") + onNewLine(line) + } + } + } + } + + private val mScope = CoroutineScope(Dispatchers.IO + Job()) + private fun initOutput() { + val dao = appDb.serverLogDao + mScope.launch { + runCatching { + logWatcher { msg -> + msg.removeAnsiCodes().evalLog()?.let { + dao.insert( + ServerLog( + level = it.level, + message = it.message + ) + ) + return@logWatcher + } + + dao.insert( + ServerLog( + level = if (msg.startsWith("fail")) LogLevel.ERROR else LogLevel.INFO, + message = msg + ) + ) - fun startup() { - if (Alistlib.isRunning("")) { - context.longToast("服务已在运行中") - return + } + }.onFailure { + it.printStackTrace() + } } + mScope.launch { + runCatching { + errorLogWatcher { msg -> + val log = msg.removeAnsiCodes().evalLog() ?: return@errorLogWatcher + dao.insert( + ServerLog( + level = log.level, + message = log.message, +// description = log.time + "\n" + log.code + ) + ) + } + }.onFailure { + it.printStackTrace() + } + } + } + + + @SuppressLint("SdCardPath") + fun startup( + dataFolder: String = context.getExternalFilesDir("data")?.absolutePath + ?: "/data/data/${context.packageName}/files/data" + ): Int { appDb.serverLogDao.deleteAll() + mProcess = + execWithParams(params = arrayOf("server", "--data", dataFolder)) + initOutput() + + return mProcess!!.waitFor() + } + - init() - Alistlib.start() - notifyStatusChanged() + private fun execWithParams( + redirect: Boolean = false, + vararg params: String + ): Process { + val cmdline = arrayOfNulls(params.size + 1) + cmdline[0] = execPath + System.arraycopy(params, 0, cmdline, 1, params.size) + return ProcessBuilder(*cmdline).redirectErrorStream(redirect).start() + ?: throw IOException("Process is null!") } } \ No newline at end of file diff --git a/app/src/main/java/com/github/jing332/alistandroid/service/AlistService.kt b/app/src/main/java/com/github/jing332/alistandroid/service/AlistService.kt index 7f41ded..ac70d40 100644 --- a/app/src/main/java/com/github/jing332/alistandroid/service/AlistService.kt +++ b/app/src/main/java/com/github/jing332/alistandroid/service/AlistService.kt @@ -1,6 +1,5 @@ package com.github.jing332.alistandroid.service -import alistlib.Alistlib import android.annotation.SuppressLint import android.app.Notification import android.app.NotificationChannel @@ -15,6 +14,8 @@ import android.os.Build import android.os.IBinder import android.os.PowerManager import androidx.core.content.ContextCompat +import androidx.localbroadcastmanager.content.LocalBroadcastManager +import com.drake.net.utils.withMain import com.github.jing332.alistandroid.R import com.github.jing332.alistandroid.config.AppConfig import com.github.jing332.alistandroid.constant.AppConst @@ -23,9 +24,12 @@ import com.github.jing332.alistandroid.model.alist.AListConfigManager import com.github.jing332.alistandroid.ui.MainActivity import com.github.jing332.alistandroid.ui.theme.androidColor import com.github.jing332.alistandroid.util.ClipboardUtils +import com.github.jing332.alistandroid.util.ToastUtils.longToast import com.github.jing332.alistandroid.util.ToastUtils.toast import kotlinx.coroutines.CoroutineScope +import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.Job +import kotlinx.coroutines.launch import splitties.systemservices.powerManager class AlistService : Service() { @@ -37,8 +41,13 @@ class AlistService : Service() { const val ACTION_COPY_ADDRESS = "com.github.jing332.alistandroid.service.AlistService.ACTION_COPY_ADDRESS" + const val ACTION_STATUS_CHANGED = + "com.github.jing332.alistandroid.service.AlistService.ACTION_STATUS_CHANGED" + const val NOTIFICATION_CHAN_ID = "alist_server" const val FOREGROUND_ID = 5224 + + var isRunning: Boolean = false } private val mScope = CoroutineScope(Job()) @@ -48,6 +57,12 @@ class AlistService : Service() { override fun onBind(p0: Intent?): IBinder? = null + @Suppress("DEPRECATION") + private fun notifyStatusChanged() { + LocalBroadcastManager.getInstance(this) + .sendBroadcast(Intent(ACTION_STATUS_CHANGED)) + } + @SuppressLint("WakelockTimeout") override fun onCreate() { super.onCreate() @@ -73,8 +88,6 @@ class AlistService : Service() { ContextCompat.RECEIVER_EXPORTED ) initNotification() - - AList.startup(); } @@ -89,14 +102,23 @@ class AlistService : Service() { AppConst.localBroadcast.unregisterReceiver(mReceiver) unregisterReceiver(mNotificationReceiver) - } override fun onStartCommand(intent: Intent?, flags: Int, startId: Int): Int { if (intent?.action == ACTION_SHUTDOWN) { AList.shutdown() - } else if (!AList.hasRunning) { - AList.startup() + } else { + isRunning = true + notifyStatusChanged() + mScope.launch(Dispatchers.IO) { + val ret = AList.startup() + isRunning = false + withMain { + if (ret != 0) toast("code: $ret") + stopSelf() + notifyStatusChanged() + } + } } return super.onStartCommand(intent, flags, startId) @@ -116,8 +138,9 @@ class AlistService : Service() { private fun httpAddress(): String { val cfg = AListConfigManager.config() - val ip = Alistlib.getOutboundIPString() - return "http://${ip}:${cfg.scheme.httpPort}" +// val ip = Alistlib.getOutboundIPString() +// return "http://${ip}:${cfg.scheme.httpPort}" + return "none" } @Suppress("DEPRECATION") @@ -183,7 +206,7 @@ class AlistService : Service() { .setSmallIcon(smallIconRes) .setContentIntent(pendingIntent) .addAction(0, getString(R.string.shutdown), shutdownAction) - .addAction(0, getString(R.string.copy_address), copyAddressPendingIntent) +// .addAction(0, getString(R.string.copy_address), copyAddressPendingIntent) .build() // 前台服务 diff --git a/app/src/main/java/com/github/jing332/alistandroid/ui/nav/alist/AListScreen.kt b/app/src/main/java/com/github/jing332/alistandroid/ui/nav/alist/AListScreen.kt index d462427..e750fbb 100644 --- a/app/src/main/java/com/github/jing332/alistandroid/ui/nav/alist/AListScreen.kt +++ b/app/src/main/java/com/github/jing332/alistandroid/ui/nav/alist/AListScreen.kt @@ -17,6 +17,7 @@ import androidx.compose.foundation.layout.padding import androidx.compose.foundation.layout.size import androidx.compose.foundation.shape.CircleShape import androidx.compose.material.icons.Icons +import androidx.compose.material.icons.automirrored.filled.Send import androidx.compose.material.icons.filled.AddBusiness import androidx.compose.material.icons.filled.MoreVert import androidx.compose.material.icons.filled.Password @@ -66,20 +67,18 @@ fun AListScreen() { val context = LocalContext.current val mainVM = LocalMainViewModel.current val view = LocalView.current - var alistRunning by remember { mutableStateOf(AList.hasRunning) } + var alistRunning by remember { mutableStateOf(AlistService.isRunning) } - LocalBroadcastReceiver(intentFilter = IntentFilter(AList.ACTION_STATUS_CHANGED)) { - println(it?.action) - if (it?.action == AList.ACTION_STATUS_CHANGED) { - alistRunning = AList.hasRunning - } + LocalBroadcastReceiver(intentFilter = IntentFilter(AlistService.ACTION_STATUS_CHANGED)) { + if (it?.action == AList.ACTION_STATUS_CHANGED) + alistRunning = AlistService.isRunning } fun switch() { context.startService(Intent(context, AlistService::class.java).apply { action = if (alistRunning) AlistService.ACTION_SHUTDOWN else "" }) -// alistRunning = !alistRunning + alistRunning = !alistRunning } var showPwdDialog by remember { mutableStateOf(false) } @@ -225,7 +224,7 @@ fun AListScreen() { @Composable fun SwitchFloatingButton(modifier: Modifier, switch: Boolean, onSwitchChange: (Boolean) -> Unit) { val targetIcon = - if (switch) Icons.Filled.Stop else Icons.Filled.Send + if (switch) Icons.Filled.Stop else Icons.AutoMirrored.Filled.Send val rotationAngle by animateFloatAsState(targetValue = if (switch) 360f else 0f, label = "") val color = diff --git a/app/src/main/java/com/github/jing332/alistandroid/util/StringUtils.kt b/app/src/main/java/com/github/jing332/alistandroid/util/StringUtils.kt index 63440f8..eb18b7f 100644 --- a/app/src/main/java/com/github/jing332/alistandroid/util/StringUtils.kt +++ b/app/src/main/java/com/github/jing332/alistandroid/util/StringUtils.kt @@ -21,4 +21,9 @@ object StringUtils { fun String.toNumberInt(): Int { return this.replace(Regex("[^0-9]"), "").toIntOrNull() ?: 0 } + + fun String.removeAnsiCodes(): String { + val ansiRegex = Regex("\\x1B\\[[0-9;]*[m|K]") + return this.replace(ansiRegex, "") + } } \ No newline at end of file diff --git a/build.gradle b/build.gradle index 1cb8735..4621b76 100644 --- a/build.gradle +++ b/build.gradle @@ -1,10 +1,11 @@ buildscript { ext { - kotlin_version = '1.9.0' - agp_version = '8.2.0-alpha16' - room_version = '2.5.2' - ksp_version = '1.9.0-1.0.13' - about_lib_version = "10.8.3" + kotlin_version = '1.9.21' + agp_version = '8.2.0' + compose_compiler = "1.5.7" + room_version = '2.6.1' + ksp_version = '1.9.21-1.0.16' + about_lib_version = "10.9.2" } }