chat_settings: [chat] config section + live sync + fix test segfault
- New chat_setting.c/h: unified chat settings API (int/str getters) - Config parser supports [chat] section, calls chat_setting_set per key - chat_core exposes chat_core_set_setting / trampoline for GUI bridge - chatgui: AudioDevice + Storage pages persist to [chat] config + live apply - chatgui-android: ConfigProvider, NativeLib, headless handle chat settings - MessageList: 200 msg limit, fixed saveChannelState via trampoline - test_media_delivery_full: fix segfault at shutdown (mon() timer vs freed conns)
This commit is contained in:
1 parent
efbb495016
commit
b8920cf483
22 files changed
+420
-43
No files matched your search
@@ -189,6 +189,16 @@ allow_all=yes
|
||||
[ntp]
|
||||
enabled=no
|
||||
|
||||
[chat]
|
||||
storage_autoload=1
|
||||
storage_autoload_maxsize_mb=10
|
||||
storage_maxsize_gb=1
|
||||
opus_codec_preset=1
|
||||
compressor_enabled=0
|
||||
compressor_max_gain_db=25
|
||||
compressor_rise_rate=10
|
||||
media_download_max_peers=3
|
||||
|
||||
[debug]
|
||||
console_level=info
|
||||
|
||||
|
||||
@@ -36,9 +36,14 @@ class ConfigProvider(private val context: Context) {
|
||||
private val logUdpIp = stringPreferencesKey("log_udp_ip")
|
||||
private val logUdpPort = intPreferencesKey("log_udp_port")
|
||||
private val serversJson = stringPreferencesKey("servers")
|
||||
private val storageAutoload = intPreferencesKey("storage_autoload")
|
||||
private val storageAutoloadMaxsizeMb = intPreferencesKey("storage_autoload_maxsize_mb")
|
||||
private val storageMaxsizeGb = intPreferencesKey("storage_maxsize_gb")
|
||||
private val chatStorageAutoload = intPreferencesKey("chat_storage_autoload")
|
||||
private val chatStorageAutoloadMaxsizeMb = intPreferencesKey("chat_storage_autoload_maxsize_mb")
|
||||
private val chatStorageMaxsizeGb = intPreferencesKey("chat_storage_maxsize_gb")
|
||||
private val chatOpusCodecPreset = intPreferencesKey("chat_opus_codec_preset")
|
||||
private val chatCompressorEnabled = intPreferencesKey("chat_compressor_enabled")
|
||||
private val chatCompressorMaxGainDb = intPreferencesKey("chat_compressor_max_gain_db")
|
||||
private val chatCompressorRiseRate = intPreferencesKey("chat_compressor_rise_rate")
|
||||
private val chatMediaDownloadMaxPeers = intPreferencesKey("chat_media_download_max_peers")
|
||||
private val firstLaunchDone = booleanPreferencesKey("first_launch_done")
|
||||
|
||||
private val gson = Gson()
|
||||
@@ -133,9 +138,14 @@ class ConfigProvider(private val context: Context) {
|
||||
key == "node.listen_port" -> context.dataStore.data.first()[nodeListenPort] ?: 12345
|
||||
key == "control.port" -> context.dataStore.data.first()[controlPort] ?: 9999
|
||||
key == "log_udp.port" -> context.dataStore.data.first()[logUdpPort] ?: 9999
|
||||
key == "storage_autoload" -> context.dataStore.data.first()[storageAutoload] ?: 1
|
||||
key == "storage_autoload_maxsize_mb" -> context.dataStore.data.first()[storageAutoloadMaxsizeMb] ?: 10
|
||||
key == "storage_maxsize_gb" -> context.dataStore.data.first()[storageMaxsizeGb] ?: 1
|
||||
key == "chat.storage_autoload" -> context.dataStore.data.first()[chatStorageAutoload] ?: 1
|
||||
key == "chat.storage_autoload_maxsize_mb" -> context.dataStore.data.first()[chatStorageAutoloadMaxsizeMb] ?: 10
|
||||
key == "chat.storage_maxsize_gb" -> context.dataStore.data.first()[chatStorageMaxsizeGb] ?: 1
|
||||
key == "chat.opus_codec_preset" -> context.dataStore.data.first()[chatOpusCodecPreset] ?: 1
|
||||
key == "chat.compressor_enabled" -> context.dataStore.data.first()[chatCompressorEnabled] ?: 0
|
||||
key == "chat.compressor_max_gain_db" -> context.dataStore.data.first()[chatCompressorMaxGainDb] ?: 25
|
||||
key == "chat.compressor_rise_rate" -> context.dataStore.data.first()[chatCompressorRiseRate] ?: 10
|
||||
key == "chat.media_download_max_peers" -> context.dataStore.data.first()[chatMediaDownloadMaxPeers] ?: 3
|
||||
else -> 0
|
||||
}
|
||||
}
|
||||
@@ -155,9 +165,14 @@ class ConfigProvider(private val context: Context) {
|
||||
"debug.categories" -> prefs[debugCategories] = value
|
||||
"log_udp.ip" -> prefs[logUdpIp] = value
|
||||
"log_udp.port" -> prefs[logUdpPort] = value.toIntOrNull() ?: 0
|
||||
"storage_autoload" -> prefs[storageAutoload] = value.toIntOrNull() ?: 1
|
||||
"storage_autoload_maxsize_mb" -> prefs[storageAutoloadMaxsizeMb] = value.toIntOrNull() ?: 10
|
||||
"storage_maxsize_gb" -> prefs[storageMaxsizeGb] = value.toIntOrNull() ?: 1
|
||||
"chat.storage_autoload" -> prefs[chatStorageAutoload] = value.toIntOrNull() ?: 1
|
||||
"chat.storage_autoload_maxsize_mb" -> prefs[chatStorageAutoloadMaxsizeMb] = value.toIntOrNull() ?: 10
|
||||
"chat.storage_maxsize_gb" -> prefs[chatStorageMaxsizeGb] = value.toIntOrNull() ?: 1
|
||||
"chat.opus_codec_preset" -> prefs[chatOpusCodecPreset] = value.toIntOrNull() ?: 1
|
||||
"chat.compressor_enabled" -> prefs[chatCompressorEnabled] = value.toIntOrNull() ?: 0
|
||||
"chat.compressor_max_gain_db" -> prefs[chatCompressorMaxGainDb] = value.toIntOrNull() ?: 25
|
||||
"chat.compressor_rise_rate" -> prefs[chatCompressorRiseRate] = value.toIntOrNull() ?: 10
|
||||
"chat.media_download_max_peers" -> prefs[chatMediaDownloadMaxPeers] = value.toIntOrNull() ?: 3
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -281,6 +296,24 @@ class ConfigProvider(private val context: Context) {
|
||||
appendLine("[ntp]")
|
||||
appendLine("enabled=no")
|
||||
appendLine()
|
||||
appendLine("[chat]")
|
||||
val chatAutoload = getInt("chat.storage_autoload")
|
||||
val chatAutoloadMaxMb = getInt("chat.storage_autoload_maxsize_mb")
|
||||
val chatMaxGb = getInt("chat.storage_maxsize_gb")
|
||||
val chatOpusPreset = getInt("chat.opus_codec_preset")
|
||||
val chatCompEnabled = getInt("chat.compressor_enabled")
|
||||
val chatCompMaxGain = getInt("chat.compressor_max_gain_db")
|
||||
val chatCompRiseRate = getInt("chat.compressor_rise_rate")
|
||||
val chatDlMaxPeers = getInt("chat.media_download_max_peers")
|
||||
appendLine("storage_autoload=$chatAutoload")
|
||||
appendLine("storage_autoload_maxsize_mb=$chatAutoloadMaxMb")
|
||||
appendLine("storage_maxsize_gb=$chatMaxGb")
|
||||
appendLine("opus_codec_preset=$chatOpusPreset")
|
||||
appendLine("compressor_enabled=$chatCompEnabled")
|
||||
appendLine("compressor_max_gain_db=$chatCompMaxGain")
|
||||
appendLine("compressor_rise_rate=$chatCompRiseRate")
|
||||
appendLine("media_download_max_peers=$chatDlMaxPeers")
|
||||
appendLine()
|
||||
appendLine("[debug]")
|
||||
appendLine("console_level=info")
|
||||
val udpIp = getString("log_udp.ip") ?: ""
|
||||
|
||||
@@ -72,6 +72,10 @@ object NativeLib {
|
||||
fun clearDatabase(): Boolean = nativeClearDatabase()
|
||||
fun setDebugLevel(category: String, level: String) { nativeSetDebugLevel(category, level) }
|
||||
|
||||
/* ── Chat settings (live update via C side) ── */
|
||||
fun setChatSetting(name: String, value: String) { nativeSetChatSetting(name, value) }
|
||||
fun getChatSettingInt(name: String, def: Int = 0): Int = nativeGetChatSettingInt(name, def)
|
||||
|
||||
/** Enable/disable forwarding native C logs to LogManager (expensive, off by default) */
|
||||
fun subscribeNativeLogs(enabled: Boolean) { nativeSubscribeLogs(enabled) }
|
||||
fun setUdpLogTarget(ip: String, port: Int) { nativeSetUdpLogTarget(ip, port) }
|
||||
@@ -114,6 +118,8 @@ object NativeLib {
|
||||
private external fun nativeGetChannels(): String?
|
||||
private external fun nativeGetMessages(channelId: String, limit: Int): String?
|
||||
private external fun nativeSetDebugLevel(category: String, level: String)
|
||||
private external fun nativeSetChatSetting(name: String, value: String)
|
||||
private external fun nativeGetChatSettingInt(name: String, def: Int): Int
|
||||
private external fun nativeSubscribeLogs(enabled: Boolean)
|
||||
private external fun nativeSetUdpLogTarget(ip: String, port: Int)
|
||||
private external fun nativeRegenerateKeys()
|
||||
|
||||
@@ -324,6 +324,7 @@ private fun GeneralSettingsTab(vm: ChatViewModel, dirty: Boolean, onDirty: (Bool
|
||||
@Composable
|
||||
private fun AudioSettingsTab(vm: ChatViewModel) {
|
||||
val scope = rememberCoroutineScope()
|
||||
val provider = ChatApplication.instance.configProvider
|
||||
|
||||
/* Mic test state */
|
||||
var micTesting by remember { mutableStateOf(false) }
|
||||
@@ -352,6 +353,16 @@ private fun AudioSettingsTab(vm: ChatViewModel) {
|
||||
vm.setVoicePreset(preset)
|
||||
vm.setVoiceCompressor(compressorOn)
|
||||
vm.setCompressorConfig(maxGainDb, lookbackMs, lookaheadMs, riseRateTenths / 10f, targetLevelDb.toFloat())
|
||||
scope.launch {
|
||||
provider.setValue("chat.opus_codec_preset", preset.toString())
|
||||
provider.setValue("chat.compressor_enabled", if (compressorOn) "1" else "0")
|
||||
provider.setValue("chat.compressor_max_gain_db", maxGainDb.toString())
|
||||
provider.setValue("chat.compressor_rise_rate", riseRateTenths.toString())
|
||||
}
|
||||
NativeLib.setChatSetting("opus_codec_preset", preset.toString())
|
||||
NativeLib.setChatSetting("compressor_enabled", if (compressorOn) "1" else "0")
|
||||
NativeLib.setChatSetting("compressor_max_gain_db", maxGainDb.toString())
|
||||
NativeLib.setChatSetting("compressor_rise_rate", riseRateTenths.toString())
|
||||
}
|
||||
|
||||
Column(
|
||||
@@ -427,9 +438,9 @@ private fun StorageSettingsTab() {
|
||||
var maxStorageGb by remember { mutableStateOf("1") }
|
||||
|
||||
LaunchedEffect(Unit) {
|
||||
autoload = provider.getInt("storage_autoload") != 0
|
||||
autoloadMaxSizeMb = provider.getInt("storage_autoload_maxsize_mb").toString()
|
||||
maxStorageGb = provider.getInt("storage_maxsize_gb").toString()
|
||||
autoload = provider.getInt("chat.storage_autoload") != 0
|
||||
autoloadMaxSizeMb = provider.getInt("chat.storage_autoload_maxsize_mb").toString()
|
||||
maxStorageGb = provider.getInt("chat.storage_maxsize_gb").toString()
|
||||
}
|
||||
|
||||
Column(
|
||||
@@ -441,20 +452,22 @@ private fun StorageSettingsTab() {
|
||||
Text("Autoload media", modifier = Modifier.weight(1f))
|
||||
Switch(checked = autoload, onCheckedChange = {
|
||||
autoload = it
|
||||
scope.launch { provider.setValue("storage_autoload", if (it) "1" else "0") }
|
||||
val s = if (it) "1" else "0"
|
||||
scope.launch { provider.setValue("chat.storage_autoload", s) }
|
||||
NativeLib.setChatSetting("storage_autoload", s)
|
||||
})
|
||||
}
|
||||
|
||||
HorizontalDivider(Modifier.padding(vertical = 4.dp))
|
||||
|
||||
OutlinedTextField(autoloadMaxSizeMb,
|
||||
{ v -> autoloadMaxSizeMb = v; val nb = v.toIntOrNull(); if (nb != null && nb in 1..500) scope.launch { provider.setValue("storage_autoload_maxsize_mb", v) } },
|
||||
{ v -> autoloadMaxSizeMb = v; val nb = v.toIntOrNull(); if (nb != null && nb in 1..500) { scope.launch { provider.setValue("chat.storage_autoload_maxsize_mb", v) }; NativeLib.setChatSetting("storage_autoload_maxsize_mb", v) } },
|
||||
label = { Text("Autoload max size (MB)") },
|
||||
keyboardOptions = KeyboardOptions(keyboardType = KeyboardType.Number),
|
||||
singleLine = true, modifier = Modifier.padding(top = 4.dp))
|
||||
|
||||
OutlinedTextField(maxStorageGb,
|
||||
{ v -> maxStorageGb = v; val nb = v.toIntOrNull(); if (nb != null && nb in 1..100) scope.launch { provider.setValue("storage_maxsize_gb", v) } },
|
||||
{ v -> maxStorageGb = v; val nb = v.toIntOrNull(); if (nb != null && nb in 1..100) { scope.launch { provider.setValue("chat.storage_maxsize_gb", v) }; NativeLib.setChatSetting("storage_maxsize_gb", v) } },
|
||||
label = { Text("Max storage size (GB)") },
|
||||
keyboardOptions = KeyboardOptions(keyboardType = KeyboardType.Number),
|
||||
singleLine = true)
|
||||
|
||||
@@ -4,6 +4,7 @@
|
||||
#include "headless_control.h"
|
||||
#include "../libutun_lite/utun_config_api.h"
|
||||
#include "../libutun_lite/invite_link_c.h"
|
||||
#include "../../../src/chat/chat_setting.h"
|
||||
|
||||
#include <stdint.h>
|
||||
#include <stdio.h>
|
||||
@@ -303,6 +304,20 @@ static int handle_join(int fd, int id, const char* json) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int handle_chat_setting(int fd, int id, const char* json) {
|
||||
char name[128], value[128];
|
||||
if (json_get_str(json, "name", name, sizeof(name)) < 0 || json_get_str(json, "value", value, sizeof(value)) < 0) {
|
||||
send_response(fd, id, NULL, "missing name or value");
|
||||
return 0;
|
||||
}
|
||||
int rc = chat_setting_set(name, value);
|
||||
if (rc == -1) { send_response(fd, id, NULL, "unknown setting"); return 0; }
|
||||
if (rc == -2) { send_response(fd, id, NULL, "invalid value"); return 0; }
|
||||
printf("control: chat_setting %s=%s\n", name, value);
|
||||
send_response(fd, id, "{\"set\":true}", NULL);
|
||||
return 0;
|
||||
}
|
||||
|
||||
/* ── Command dispatch ── */
|
||||
|
||||
typedef int (*cmd_handler_fn)(int fd, int id, const char* json);
|
||||
@@ -310,13 +325,13 @@ typedef int (*cmd_handler_fn)(int fd, int id, const char* json);
|
||||
static const char* const g_cmd_names[] = {
|
||||
"ping", "status", "connect", "disconnect", "connections",
|
||||
"create_channel", "channels", "send", "messages",
|
||||
"debug_level", "subscribe", "join", "quit", NULL
|
||||
"debug_level", "subscribe", "join", "chat_setting", "quit", NULL
|
||||
};
|
||||
|
||||
static cmd_handler_fn g_cmd_handlers[] = {
|
||||
handle_ping, handle_status, NULL, NULL, handle_connections,
|
||||
NULL, NULL, NULL, NULL,
|
||||
handle_debug_level, handle_subscribe, handle_join, handle_quit
|
||||
handle_debug_level, handle_subscribe, handle_join, handle_chat_setting, handle_quit
|
||||
};
|
||||
|
||||
static void handle_command(struct control_client* cli, const char* json) {
|
||||
|
||||
@@ -18,6 +18,7 @@
|
||||
#include "../../../lib/mem.h"
|
||||
#include "../../../lib/sqlite3.h"
|
||||
#include "../../../src/chat/chat_core.h"
|
||||
#include "../../../src/chat/chat_setting.h"
|
||||
#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
@@ -435,6 +436,14 @@ void utun_bridge_set_debug_level(const char* category, const char* level) {
|
||||
bridge_log(BLEV_INFO, "debug %s=%s", category ? category : "all", level ? level : "info");
|
||||
}
|
||||
|
||||
void utun_bridge_chat_setting_set(const char* name, const char* value) {
|
||||
chat_setting_set(name, value);
|
||||
}
|
||||
|
||||
int utun_bridge_chat_setting_get_int(const char* name, int def) {
|
||||
return chat_setting_get_int(name, def);
|
||||
}
|
||||
|
||||
void utun_bridge_restart(const char* config_text) {
|
||||
bridge_log(BLEV_INFO, "bridge restart: posting to instance");
|
||||
instance_lite_restart(config_text);
|
||||
@@ -913,6 +922,25 @@ JNIEXPORT void JNICALL Java_com_utun_chat_data_NativeLib_nativeSetDebugLevel(
|
||||
if (c) (*env)->ReleaseStringUTFChars(env, category, c);
|
||||
}
|
||||
|
||||
JNIEXPORT void JNICALL Java_com_utun_chat_data_NativeLib_nativeSetChatSetting(
|
||||
JNIEnv* env, jobject thiz, jstring name, jstring value) {
|
||||
(void)thiz;
|
||||
const char* n = (*env)->GetStringUTFChars(env, name, NULL);
|
||||
const char* v = (*env)->GetStringUTFChars(env, value, NULL);
|
||||
chat_setting_set(n, v);
|
||||
if (v) (*env)->ReleaseStringUTFChars(env, value, v);
|
||||
if (n) (*env)->ReleaseStringUTFChars(env, name, n);
|
||||
}
|
||||
|
||||
JNIEXPORT jint JNICALL Java_com_utun_chat_data_NativeLib_nativeGetChatSettingInt(
|
||||
JNIEnv* env, jobject thiz, jstring name, jint def) {
|
||||
(void)thiz;
|
||||
const char* n = (*env)->GetStringUTFChars(env, name, NULL);
|
||||
int val = chat_setting_get_int(n, (int)def);
|
||||
if (n) (*env)->ReleaseStringUTFChars(env, name, n);
|
||||
return (jint)val;
|
||||
}
|
||||
|
||||
JNIEXPORT void JNICALL Java_com_utun_chat_data_NativeLib_nativeSubscribeLogs(
|
||||
JNIEnv* env, jobject thiz, jboolean enabled) {
|
||||
(void)thiz; (void)env;
|
||||
|
||||
@@ -105,6 +105,11 @@ void utun_bridge_voice_decode_close(void* handle);
|
||||
|
||||
void utun_bridge_set_debug_level(const char* category, const char* level);
|
||||
|
||||
/* ── Chat settings ── */
|
||||
|
||||
void utun_bridge_chat_setting_set(const char* name, const char* value);
|
||||
int utun_bridge_chat_setting_get_int(const char* name, int def);
|
||||
|
||||
#ifdef __cplusplus
|
||||
}
|
||||
#endif
|
||||
|
||||
@@ -9,6 +9,7 @@ extern "C" {
|
||||
#include "../../lib/debug_config.h"
|
||||
#include "../db/db_manager.h"
|
||||
#include "../transport/gui_bridge.h"
|
||||
#include "../transport/config_updater.h"
|
||||
#include "../../lib/mem.h"
|
||||
extern "C" {
|
||||
#include "chat/chat_core.h"
|
||||
@@ -211,6 +212,24 @@ void AudioDeviceSettingsPage::applyAndSave() {
|
||||
saveKey("compressor_max_gain_db", compMaxGain);
|
||||
saveKey("compressor_rise_rate", compRiseRate);
|
||||
|
||||
/* also update [chat] config section + live apply for headless-compat settings */
|
||||
if (!m_configPath.isEmpty()) {
|
||||
auto postChat = [](const QString& path, const char* name, int val) {
|
||||
config_update_option(path, "chat", name, QString::number(val), CONFIG_UPDATE_REPLACE);
|
||||
QByteArray v = QByteArray::number(val);
|
||||
size_t nlen = strlen(name), vlen = (size_t)v.size(), total = nlen + 1 + vlen + 1;
|
||||
if (total > 256) return;
|
||||
void* arg = u_malloc(total);
|
||||
memcpy(arg, name, nlen + 1);
|
||||
memcpy((char*)arg + nlen + 1, v.constData(), vlen + 1);
|
||||
gui_bridge_post_uasync_fn(chat_core_set_setting_trampoline, arg);
|
||||
};
|
||||
postChat(m_configPath, "opus_codec_preset", codecPreset);
|
||||
postChat(m_configPath, "compressor_enabled", compEnabled);
|
||||
postChat(m_configPath, "compressor_max_gain_db", compMaxGain);
|
||||
postChat(m_configPath, "compressor_rise_rate", compRiseRate);
|
||||
}
|
||||
|
||||
m_savedOutputIdx = outIdx;
|
||||
m_savedInputIdx = inIdx;
|
||||
m_savedCodecPreset = codecPreset;
|
||||
|
||||
@@ -15,6 +15,7 @@ class AudioDeviceSettingsPage : public QWidget {
|
||||
public:
|
||||
explicit AudioDeviceSettingsPage(DbManager* db, AudioRecorder* recorder, QWidget* parent = nullptr);
|
||||
~AudioDeviceSettingsPage() override;
|
||||
void setConfigPath(const QString& path) { m_configPath = path; }
|
||||
void loadFromDb();
|
||||
void applyAndSave();
|
||||
|
||||
@@ -31,6 +32,7 @@ private:
|
||||
DbManager* m_db;
|
||||
AudioRecorder* m_recorder = nullptr;
|
||||
AudioRecorder* m_testRecorder = nullptr;
|
||||
QString m_configPath;
|
||||
QComboBox* m_outputCombo;
|
||||
QComboBox* m_inputCombo;
|
||||
QComboBox* m_codecPresetCombo;
|
||||
|
||||
@@ -252,6 +252,8 @@ void MainWindow::setupBridgeCallbacks() {
|
||||
if (!lastCid.isEmpty()) {
|
||||
s_mainWindow->m_channelList->selectChannel(lastCid);
|
||||
s_mainWindow->m_accountList->setChannel(lastCid);
|
||||
s_mainWindow->m_currentChannelId = lastCid;
|
||||
s_mainWindow->m_messageList->loadChannel(lastCid);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
@@ -32,6 +32,8 @@ extern "C" {
|
||||
#include "../../lib/u_async.h"
|
||||
#include "../../lib/mem.h"
|
||||
#include "../../lib/debug_config.h"
|
||||
struct save_ui_state_arg { char data[256]; };
|
||||
void chat_core_save_ui_state_trampoline(void* arg);
|
||||
}
|
||||
|
||||
static QPixmap makeAvatar(const QColor &color, const QString &letter, int size = 36) {
|
||||
@@ -360,7 +362,7 @@ void MessageList::loadChannel(const QString& channelId) {
|
||||
|
||||
m_localNicks = m_db->getLocalNicknames(channelId);
|
||||
|
||||
auto msgs = m_db->getMessages(channelId, 100);
|
||||
auto msgs = m_db->getMessages(channelId, 200);
|
||||
|
||||
for (const auto& m : msgs) {
|
||||
AccountRow acc = m_db->getAccount(m.authorNodeId);
|
||||
@@ -450,7 +452,7 @@ void MessageList::refresh() {
|
||||
setVoiceMessageRoles(item, m.data, m.contentType,
|
||||
m_db ? m_db->dbPath() + "/media" : QString(), m_currentChannelId, m.localAttrs);
|
||||
m_model->appendRow(item);
|
||||
while (m_model->rowCount() > 100)
|
||||
while (m_model->rowCount() > 200)
|
||||
m_model->removeRow(0);
|
||||
}
|
||||
|
||||
@@ -495,29 +497,37 @@ void MessageList::addMessage(const QString& channelId, quint64 authorNodeId,
|
||||
void MessageList::saveChannelState() {
|
||||
if (m_currentChannelId.isEmpty() || m_model->rowCount() == 0) return;
|
||||
|
||||
int vpHeight = m_view->viewport()->height();
|
||||
QModelIndex topIdx = m_view->indexAt(QPoint(0, 0));
|
||||
QScrollBar* sb = m_view->verticalScrollBar();
|
||||
bool atBottom = sb && (sb->value() >= sb->maximum());
|
||||
|
||||
qint64 lastPosMsgId = 0;
|
||||
if (!atBottom && topIdx.isValid())
|
||||
lastPosMsgId = topIdx.data(MsgIdRole).toLongLong();
|
||||
if (!atBottom) {
|
||||
QModelIndex topIdx = m_view->indexAt(QPoint(0, 0));
|
||||
if (topIdx.isValid())
|
||||
lastPosMsgId = topIdx.data(MsgIdRole).toLongLong();
|
||||
}
|
||||
|
||||
qint64 lastReadMsgId = m_lastReadMsgId;
|
||||
DEBUG_DEBUG(DEBUG_CATEGORY_DEBUG, "MessageList::saveChannelState ch=%s read=%lld pos=%lld",
|
||||
qPrintable(m_currentChannelId), (long long)lastReadMsgId, (long long)lastPosMsgId);
|
||||
if (lastReadMsgId == 0 && lastPosMsgId == 0) return;
|
||||
DEBUG_DEBUG(DEBUG_CATEGORY_DEBUG, "MessageList::saveChannelState ch=%s bottom=%d read=%lld pos=%lld",
|
||||
qPrintable(m_currentChannelId), (int)atBottom, (long long)lastReadMsgId, (long long)lastPosMsgId);
|
||||
|
||||
QByteArray cid = m_currentChannelId.toUtf8();
|
||||
auto saveKey = [&cid](const char* suffix, qint64 val) {
|
||||
auto postSaveKey = [&cid](const char* suffix, qint64 val) {
|
||||
QByteArray key = "ch_" + cid + suffix;
|
||||
QByteArray valStr = QByteArray::number(val);
|
||||
chat_core_save_ui_state(key.constData(), valStr.constData());
|
||||
size_t keyLen = (size_t)key.size(), valLen = (size_t)valStr.size();
|
||||
size_t total = keyLen + 1 + valLen + 1;
|
||||
if (total > 256) return;
|
||||
struct save_ui_state_arg* arg = (struct save_ui_state_arg*)u_malloc(sizeof(struct save_ui_state_arg));
|
||||
if (!arg) return;
|
||||
memcpy(arg->data, key.constData(), keyLen + 1);
|
||||
memcpy(arg->data + keyLen + 1, valStr.constData(), valLen + 1);
|
||||
gui_bridge_post_uasync_fn(chat_core_save_ui_state_trampoline, arg);
|
||||
};
|
||||
|
||||
if (lastReadMsgId > 0) saveKey("_last_read", lastReadMsgId);
|
||||
if (lastPosMsgId > 0) saveKey("_last_pos", lastPosMsgId);
|
||||
if (lastReadMsgId > 0)
|
||||
postSaveKey("_last_read", lastReadMsgId);
|
||||
postSaveKey("_last_pos", lastPosMsgId);
|
||||
}
|
||||
|
||||
QString MessageList::authorName(quint64 nodeId) const {
|
||||
@@ -589,26 +599,32 @@ void MessageList::updateReadPosition() {
|
||||
qint64 lastPosMsgId = (!atBottom && topIdx.isValid()) ? topIdx.data(MsgIdRole).toLongLong() : 0;
|
||||
|
||||
DEBUG_DEBUG(DEBUG_CATEGORY_DEBUG,
|
||||
"MessageList::updateReadPosition ch=%s oldRead=%lld newRead=%lld pos=%lld lastVisRow=%d/%d",
|
||||
"MessageList::updateReadPosition ch=%s oldRead=%lld newRead=%lld pos=%lld bottom=%d lastVisRow=%d/%d",
|
||||
qPrintable(m_currentChannelId), (long long)m_lastReadMsgId,
|
||||
(long long)newReadMsgId, (long long)lastPosMsgId,
|
||||
(long long)newReadMsgId, (long long)lastPosMsgId, (int)atBottom,
|
||||
lastFullyVisibleRow, m_model->rowCount());
|
||||
|
||||
QByteArray cid = m_currentChannelId.toUtf8();
|
||||
auto postSaveKey = [&cid](const char* suffix, qint64 val) {
|
||||
QByteArray key = "ch_" + cid + suffix;
|
||||
QByteArray valStr = QByteArray::number(val);
|
||||
size_t keyLen = (size_t)key.size(), valLen = (size_t)valStr.size();
|
||||
size_t total = keyLen + 1 + valLen + 1;
|
||||
if (total > 256) return;
|
||||
struct save_ui_state_arg* arg = (struct save_ui_state_arg*)u_malloc(sizeof(struct save_ui_state_arg));
|
||||
if (!arg) return;
|
||||
memcpy(arg->data, key.constData(), keyLen + 1);
|
||||
memcpy(arg->data + keyLen + 1, valStr.constData(), valLen + 1);
|
||||
gui_bridge_post_uasync_fn(chat_core_save_ui_state_trampoline, arg);
|
||||
};
|
||||
|
||||
if (newReadMsgId > m_lastReadMsgId) {
|
||||
m_lastReadMsgId = newReadMsgId;
|
||||
QByteArray key = "ch_" + cid + "_last_read";
|
||||
QByteArray valStr = QByteArray::number(m_lastReadMsgId);
|
||||
chat_core_save_ui_state(key.constData(), valStr.constData());
|
||||
postSaveKey("_last_read", m_lastReadMsgId);
|
||||
emit readPositionChanged(m_currentChannelId, m_lastReadMsgId);
|
||||
}
|
||||
|
||||
if (lastPosMsgId > 0) {
|
||||
QByteArray posKey = "ch_" + cid + "_last_pos";
|
||||
QByteArray posVal = QByteArray::number(lastPosMsgId);
|
||||
chat_core_save_ui_state(posKey.constData(), posVal.constData());
|
||||
}
|
||||
postSaveKey("_last_pos", lastPosMsgId);
|
||||
}
|
||||
|
||||
void MessageList::cleanupAnimations() {
|
||||
|
||||
@@ -88,6 +88,7 @@ SettingsDialog::SettingsDialog(const QString& configPath, DbManager* db, UtunNod
|
||||
|
||||
// Storage page
|
||||
m_storagePage = new StorageSettingsPage(db, m_pages);
|
||||
m_storagePage->setConfigPath(m_configPath);
|
||||
m_pages->addWidget(m_storagePage);
|
||||
|
||||
// Network page
|
||||
@@ -110,6 +111,7 @@ SettingsDialog::SettingsDialog(const QString& configPath, DbManager* db, UtunNod
|
||||
m_pages->addWidget(m_soundPage);
|
||||
|
||||
m_audioDevicePage = new AudioDeviceSettingsPage(db, recorder, m_pages);
|
||||
m_audioDevicePage->setConfigPath(m_configPath);
|
||||
m_pages->addWidget(m_audioDevicePage);
|
||||
rightLayout->addWidget(m_pages, 1);
|
||||
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
#include "storagesettingspage.h"
|
||||
#include "../db/db_manager.h"
|
||||
#include "../transport/gui_bridge.h"
|
||||
#include "../transport/config_updater.h"
|
||||
#include "../../lib/mem.h"
|
||||
extern "C" {
|
||||
#include "chat/chat_core.h"
|
||||
@@ -22,6 +23,19 @@ static void saveUiStateInt(const char* key, int val) {
|
||||
gui_bridge_post_uasync_fn(chat_core_save_ui_state_trampoline, arg);
|
||||
}
|
||||
|
||||
static void postChatSetting(const QString& configPath, const char* name, int val) {
|
||||
/* persist to config file */
|
||||
config_update_option(configPath, "chat", name, QString::number(val), CONFIG_UPDATE_REPLACE);
|
||||
/* apply on-the-fly via bridge */
|
||||
QByteArray v = QByteArray::number(val);
|
||||
size_t nlen = strlen(name), vlen = (size_t)v.size(), total = nlen + 1 + vlen + 1;
|
||||
if (total > 256) return;
|
||||
void* arg = u_malloc(total);
|
||||
memcpy(arg, name, nlen + 1);
|
||||
memcpy((char*)arg + nlen + 1, v.constData(), vlen + 1);
|
||||
gui_bridge_post_uasync_fn(chat_core_set_setting_trampoline, arg);
|
||||
}
|
||||
|
||||
StorageSettingsPage::StorageSettingsPage(DbManager* db, QWidget* parent)
|
||||
: QWidget(parent)
|
||||
, m_db(db)
|
||||
@@ -84,7 +98,17 @@ void StorageSettingsPage::loadFromDb() {
|
||||
}
|
||||
|
||||
void StorageSettingsPage::applyAndSave() {
|
||||
saveUiStateInt("storage_autoload", m_autoloadCheck->isChecked() ? 1 : 0);
|
||||
saveUiStateInt("storage_autoload_maxsize_mb", m_autoloadMaxSize->value());
|
||||
saveUiStateInt("storage_maxsize_gb", m_maxStorageSize->value());
|
||||
int autoload = m_autoloadCheck->isChecked() ? 1 : 0;
|
||||
int autoloadMaxSize = m_autoloadMaxSize->value();
|
||||
int maxGb = m_maxStorageSize->value();
|
||||
|
||||
saveUiStateInt("storage_autoload", autoload);
|
||||
saveUiStateInt("storage_autoload_maxsize_mb", autoloadMaxSize);
|
||||
saveUiStateInt("storage_maxsize_gb", maxGb);
|
||||
|
||||
if (!m_configPath.isEmpty()) {
|
||||
postChatSetting(m_configPath, "storage_autoload", autoload);
|
||||
postChatSetting(m_configPath, "storage_autoload_maxsize_mb", autoloadMaxSize);
|
||||
postChatSetting(m_configPath, "storage_maxsize_gb", maxGb);
|
||||
}
|
||||
}
|
||||
@@ -11,11 +11,13 @@ class StorageSettingsPage : public QWidget {
|
||||
public:
|
||||
explicit StorageSettingsPage(DbManager* db, QWidget* parent = nullptr);
|
||||
|
||||
void setConfigPath(const QString& path) { m_configPath = path; }
|
||||
void loadFromDb();
|
||||
void applyAndSave();
|
||||
|
||||
private:
|
||||
DbManager* m_db = nullptr;
|
||||
QString m_configPath;
|
||||
QCheckBox* m_autoloadCheck;
|
||||
QSpinBox* m_autoloadMaxSize;
|
||||
QSpinBox* m_maxStorageSize;
|
||||
|
||||
Reference in new issue
Block a user