diff --git a/android/app/src/main/cpp/log.c b/android/app/src/main/cpp/log.c index e9e9690..7ffc568 100644 --- a/android/app/src/main/cpp/log.c +++ b/android/app/src/main/cpp/log.c @@ -29,114 +29,118 @@ void log_cb_android(ChiakiLogLevel level, const char *msg, void *user) { - int prio; - switch(level) - { - case CHIAKI_LOG_DEBUG: - prio = ANDROID_LOG_DEBUG; - break; - case CHIAKI_LOG_VERBOSE: - prio = ANDROID_LOG_VERBOSE; - break; - case CHIAKI_LOG_INFO: - prio = ANDROID_LOG_INFO; - break; - case CHIAKI_LOG_WARNING: - prio = ANDROID_LOG_ERROR; - break; - case CHIAKI_LOG_ERROR: - prio = ANDROID_LOG_ERROR; - break; - default: - prio = ANDROID_LOG_INFO; - break; - } - __android_log_write(prio, LOG_TAG, msg); + int prio; + switch(level) + { + case CHIAKI_LOG_DEBUG: + prio = ANDROID_LOG_DEBUG; + break; + case CHIAKI_LOG_VERBOSE: + prio = ANDROID_LOG_VERBOSE; + break; + case CHIAKI_LOG_INFO: + prio = ANDROID_LOG_INFO; + break; + case CHIAKI_LOG_WARNING: + prio = ANDROID_LOG_ERROR; + break; + case CHIAKI_LOG_ERROR: + prio = ANDROID_LOG_ERROR; + break; + default: + prio = ANDROID_LOG_INFO; + break; + } + __android_log_write(prio, LOG_TAG, msg); } static void log_cb_android_file(ChiakiLogLevel level, const char *msg, void *user) { - log_cb_android(level, msg, user); - FILE *f = user; - if(!f) - return; - switch(level) - { - case CHIAKI_LOG_DEBUG: - fwrite("[D] ", 4, 1, f); - break; - case CHIAKI_LOG_VERBOSE: - fwrite("[V] ", 4, 1, f); - break; - case CHIAKI_LOG_INFO: - fwrite("[I] ", 4, 1, f); - break; - case CHIAKI_LOG_WARNING: - fwrite("[W] ", 4, 1, f); - break; - case CHIAKI_LOG_ERROR: - fwrite("[E] ", 4, 1, f); - break; - default: - fwrite("[?] ", 4, 1, f); - break; - } - fwrite(msg, strlen(msg), 1, f); - fwrite("\n", 1, 1, f); + log_cb_android(level, msg, user); + FILE *f = user; + if(!f) + return; + switch(level) + { + case CHIAKI_LOG_DEBUG: + fwrite("[D] ", 4, 1, f); + break; + case CHIAKI_LOG_VERBOSE: + fwrite("[V] ", 4, 1, f); + break; + case CHIAKI_LOG_INFO: + fwrite("[I] ", 4, 1, f); + break; + case CHIAKI_LOG_WARNING: + fwrite("[W] ", 4, 1, f); + break; + case CHIAKI_LOG_ERROR: + fwrite("[E] ", 4, 1, f); + break; + default: + fwrite("[?] ", 4, 1, f); + break; + } + fwrite(msg, strlen(msg), 1, f); + fwrite("\n", 1, 1, f); } ChiakiErrorCode android_chiaki_file_log_init(ChiakiLog *log, uint32_t level, const char *file) { - chiaki_log_init(log, level, log_cb_android, NULL); - if(file) - { - FILE *f = fopen(file, "w+"); - if(!f) - { - CHIAKI_LOGE(log, "Failed to open log file %s for writing: %s", file, strerror(errno)); - return CHIAKI_ERR_UNKNOWN; - } - log->user = f; - log->cb = log_cb_android_file; - } - return CHIAKI_ERR_SUCCESS; + chiaki_log_init(log, level, log_cb_android, NULL); + if(file) + { + FILE *f = fopen(file, "w+"); + if(!f) + { + CHIAKI_LOGE(log, "Failed to open log file %s for writing: %s", file, strerror(errno)); + return CHIAKI_ERR_UNKNOWN; + } + else + { + log->user = f; + log->cb = log_cb_android_file; + CHIAKI_LOGI(log, "Logging to file %s", file); + } + } + return CHIAKI_ERR_SUCCESS; } void android_chiaki_file_log_fini(ChiakiLog *log) { - if(log->user) - { - FILE *f = log->user; - fclose(f); - log->user = NULL; - } + if(log->user) + { + FILE *f = log->user; + fclose(f); + log->user = NULL; + } } static void android_chiaki_log_cb(ChiakiLogLevel level, const char *msg, void *user) { - log_cb_android(level, msg, NULL); + log_cb_android(level, msg, NULL); - AndroidChiakiJNILog *log = user; - JNIEnv *env = attach_thread_jni(); - if(!env) - return; - E->CallVoidMethod(env, log->java_log, log->java_log_meth, (jint)level, jnistr_from_ascii(env, msg)); - (*global_vm)->DetachCurrentThread(global_vm); + AndroidChiakiJNILog *log = user; + JNIEnv *env = attach_thread_jni(); + if(!env) + return; + E->CallVoidMethod(env, log->java_log, log->java_log_meth, (jint)level, jnistr_from_ascii(env, msg)); + (*global_vm)->DetachCurrentThread(global_vm); } void android_chiaki_jni_log_init(AndroidChiakiJNILog *log, JNIEnv *env, jobject java_log) { - log->java_log = E->NewGlobalRef(env, java_log); - jclass log_class = E->GetObjectClass(env, log->java_log); - log->java_log_meth = E->GetMethodID(env, log_class, "log", "(ILjava/lang/String;)V"); - log->log.level_mask = (uint32_t)E->GetIntField(env, log->java_log, E->GetFieldID(env, log_class, "levelMask", "I")); - log->log.cb = android_chiaki_log_cb; - log->log.user = log; + log->java_log = E->NewGlobalRef(env, java_log); + jclass log_class = E->GetObjectClass(env, log->java_log); + log->java_log_meth = E->GetMethodID(env, log_class, "log", "(ILjava/lang/String;)V"); + log->log.level_mask = (uint32_t)E->GetIntField(env, log->java_log, E->GetFieldID(env, log_class, "levelMask", "I")); + log->log.cb = android_chiaki_log_cb; + log->log.user = log; } void android_chiaki_jni_log_fini(AndroidChiakiJNILog *log, JNIEnv *env) { - E->DeleteGlobalRef(env, log->java_log); + E->DeleteGlobalRef(env, log->java_log); } \ No newline at end of file diff --git a/android/app/src/main/java/com/metallic/chiaki/common/LogManager.kt b/android/app/src/main/java/com/metallic/chiaki/common/LogManager.kt new file mode 100644 index 0000000..26fbfb5 --- /dev/null +++ b/android/app/src/main/java/com/metallic/chiaki/common/LogManager.kt @@ -0,0 +1,74 @@ +/* + * This file is part of Chiaki. + * + * Chiaki is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * Chiaki is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with Chiaki. If not, see . + */ + +package com.metallic.chiaki.common + +import android.content.Context +import java.io.File +import java.io.FilenameFilter +import java.text.SimpleDateFormat +import java.util.* +import java.util.regex.Pattern + +private val dateFormat = SimpleDateFormat("yyyy-MM-dd_HH-mm-ss-SSSSSS", Locale.US) +private val filePrefix = "chiaki_session_" +private val filePostfix = ".log" +private val fileRegex = Regex("$filePrefix(.*)$filePostfix") +private val keepLogFilesCount = 5 + +class LogFile private constructor(val logManager: LogManager, val filename: String) +{ + val date = fileRegex.matchEntire(filename)?.groupValues?.get(1)?.let { + dateFormat.parse(it) + } ?: throw IllegalArgumentException() + + val file get() = File(logManager.baseDir, filename) + + companion object + { + fun fromFilename(logManager: LogManager, filename: String) = try { LogFile(logManager, filename) } catch(e: IllegalArgumentException) { null } + } +} + +class LogManager(context: Context) +{ + val baseDir = File(context.filesDir, "session_logs").also { + it.mkdirs() + } + + val files: List get() = + (baseDir.list { _, s -> s.matches(fileRegex) }?.toList() ?: listOf()).mapNotNull { + LogFile.fromFilename(this, it) + }.sortedByDescending { + it.date + } + + fun createNewFile(): LogFile + { + val currentFiles = files + if(currentFiles.size > keepLogFilesCount) + { + currentFiles.subList(keepLogFilesCount, currentFiles.size).forEach { + it.file.delete() + } + } + + val date = Date() + val filename = "$filePrefix${dateFormat.format(date)}$filePostfix" + return LogFile.fromFilename(this, filename)!! + } +} \ No newline at end of file diff --git a/android/app/src/main/java/com/metallic/chiaki/session/StreamSession.kt b/android/app/src/main/java/com/metallic/chiaki/session/StreamSession.kt index 10a07c4..aaf70aa 100644 --- a/android/app/src/main/java/com/metallic/chiaki/session/StreamSession.kt +++ b/android/app/src/main/java/com/metallic/chiaki/session/StreamSession.kt @@ -22,6 +22,7 @@ import android.util.Log import android.view.* import androidx.lifecycle.LiveData import androidx.lifecycle.MutableLiveData +import com.metallic.chiaki.common.LogManager import com.metallic.chiaki.lib.* sealed class StreamState @@ -32,7 +33,7 @@ data class StreamStateCreateError(val error: CreateError): StreamState() data class StreamStateQuit(val reason: QuitReason, val reasonString: String?): StreamState() data class StreamStateLoginPinRequest(val pinIncorrect: Boolean): StreamState() -class StreamSession(val connectInfo: ConnectInfo, val logVerbose: Boolean, val input: StreamInput) +class StreamSession(val connectInfo: ConnectInfo, val logManager: LogManager, val logVerbose: Boolean, val input: StreamInput) { var session: Session? = null private set @@ -69,7 +70,7 @@ class StreamSession(val connectInfo: ConnectInfo, val logVerbose: Boolean, val i return try { - val session = Session(connectInfo, null, logVerbose) // TODO: log file + val session = Session(connectInfo, logManager.createNewFile().file.absolutePath, logVerbose) _state.value = StreamStateConnecting session.eventCallback = this::eventCallback session.start() diff --git a/android/app/src/main/java/com/metallic/chiaki/stream/StreamActivity.kt b/android/app/src/main/java/com/metallic/chiaki/stream/StreamActivity.kt index 8ba742b..43dee24 100644 --- a/android/app/src/main/java/com/metallic/chiaki/stream/StreamActivity.kt +++ b/android/app/src/main/java/com/metallic/chiaki/stream/StreamActivity.kt @@ -34,6 +34,7 @@ import androidx.fragment.app.Fragment import androidx.lifecycle.* import com.google.android.material.dialog.MaterialAlertDialogBuilder import com.metallic.chiaki.R +import com.metallic.chiaki.common.LogManager import com.metallic.chiaki.common.Preferences import com.metallic.chiaki.common.ext.viewModelFactory import com.metallic.chiaki.lib.ConnectInfo @@ -69,7 +70,7 @@ class StreamActivity : AppCompatActivity(), View.OnSystemUiVisibilityChangeListe } viewModel = ViewModelProviders.of(this, viewModelFactory { - StreamViewModel(Preferences(this), connectInfo) + StreamViewModel(Preferences(this), LogManager(this), connectInfo) })[StreamViewModel::class.java] setContentView(R.layout.activity_stream) diff --git a/android/app/src/main/java/com/metallic/chiaki/stream/StreamViewModel.kt b/android/app/src/main/java/com/metallic/chiaki/stream/StreamViewModel.kt index d8eb35e..8f59f72 100644 --- a/android/app/src/main/java/com/metallic/chiaki/stream/StreamViewModel.kt +++ b/android/app/src/main/java/com/metallic/chiaki/stream/StreamViewModel.kt @@ -20,16 +20,17 @@ package com.metallic.chiaki.stream import androidx.lifecycle.LiveData import androidx.lifecycle.MutableLiveData import androidx.lifecycle.ViewModel +import com.metallic.chiaki.common.LogManager import com.metallic.chiaki.session.StreamSession import com.metallic.chiaki.common.Preferences import com.metallic.chiaki.lib.* import com.metallic.chiaki.session.StreamInput -class StreamViewModel(val preferences: Preferences, val connectInfo: ConnectInfo): ViewModel() +class StreamViewModel(val preferences: Preferences, val logManager: LogManager, val connectInfo: ConnectInfo): ViewModel() { private var _session: StreamSession? = null val input = StreamInput(preferences) - val session = StreamSession(connectInfo, preferences.logVerbose, input) + val session = StreamSession(connectInfo, logManager, preferences.logVerbose, input) private var _onScreenControlsEnabled = MutableLiveData(preferences.onScreenControlsEnabled) val onScreenControlsEnabled: LiveData get() = _onScreenControlsEnabled