Refactor SQLite to inform user of database errors

This commit is contained in:
Natsumi
2025-11-19 23:53:25 +11:00
parent 48e1393e4b
commit 95a85c5ea4
7 changed files with 83 additions and 60 deletions

View File

@@ -44,26 +44,15 @@ namespace VRCX
m_Connection.Close(); m_Connection.Close();
m_Connection.Dispose(); m_Connection.Dispose();
} }
public string ExecuteJson(string sql, IDictionary<string, object> args = null) // for Electron
public string ExecuteJson(string sql, IDictionary<string, object>? args = null)
{ {
var result = Execute(sql, args); var result = Execute(sql, args);
if (result.Item1 != null) return JsonSerializer.Serialize(result);
{
return JsonSerializer.Serialize(new
{
status = "error",
message = result.Item1
});
}
return JsonSerializer.Serialize(new
{
status = "success",
data = result.Item2
});
} }
public Tuple<string, object[]> Execute(string sql, IDictionary<string, object> args = null) public object[][] Execute(string sql, IDictionary<string, object>? args = null)
{ {
m_ConnectionLock.EnterReadLock(); m_ConnectionLock.EnterReadLock();
try try
@@ -88,11 +77,7 @@ namespace VRCX
} }
result.Add(values); result.Add(values);
} }
return new Tuple<string, object[]>(null, result.ToArray()); return result.ToArray();
}
catch (Exception ex)
{
return new Tuple<string, object[]>(ex.Message, null);
} }
finally finally
{ {
@@ -100,9 +85,9 @@ namespace VRCX
} }
} }
public int ExecuteNonQuery(string sql, IDictionary<string, object> args = null) public int ExecuteNonQuery(string sql, IDictionary<string, object>? args = null)
{ {
int result = -1; var result = -1;
m_ConnectionLock.EnterWriteLock(); m_ConnectionLock.EnterWriteLock();
try try
{ {

View File

@@ -130,7 +130,7 @@ namespace VRCX
); );
try try
{ {
var item = (object[])values.Item2[0]; var item = values[0];
using var stream = new MemoryStream(Convert.FromBase64String((string)item[0])); using var stream = new MemoryStream(Convert.FromBase64String((string)item[0]));
_cookieContainer = new CookieContainer(); _cookieContainer = new CookieContainer();
_cookieContainer.Add(System.Text.Json.JsonSerializer.Deserialize<CookieCollection>(stream)); _cookieContainer.Add(System.Text.Json.JsonSerializer.Deserialize<CookieCollection>(stream));

View File

@@ -957,7 +957,7 @@
<div class="detail"> <div class="detail">
<span <span
class="name" class="name"
:style="{ color: user.user.$userColour }" :style="{ color: user.user?.$userColour }"
v-text="user.user.displayName" /> v-text="user.user.displayName" />
<span class="extra"> <span class="extra">
<template v-if="hasGroupPermission(groupDialog.ref, 'group-members-manage')"> <template v-if="hasGroupPermission(groupDialog.ref, 'group-members-manage')">
@@ -1018,7 +1018,7 @@
<div class="detail"> <div class="detail">
<span <span
class="name" class="name"
:style="{ color: user.user.$userColour }" :style="{ color: user.user?.$userColour }"
v-text="user.user.displayName" /> v-text="user.user.displayName" />
<span class="extra"> <span class="extra">
<template v-if="hasGroupPermission(groupDialog.ref, 'group-members-manage')"> <template v-if="hasGroupPermission(groupDialog.ref, 'group-members-manage')">

View File

@@ -24,7 +24,7 @@
<el-button @click="cancelEditAndSendInvite"> <el-button @click="cancelEditAndSendInvite">
{{ t('dialog.edit_send_invite_message.cancel') }} {{ t('dialog.edit_send_invite_message.cancel') }}
</el-button> </el-button>
<el-button type="primary" @click="saveEditAndSendInvite"> <el-button type="primary" @click="saveEditAndSendInvite" :disabled="!editAndSendInviteDialog.newMessage">
{{ t('dialog.edit_send_invite_message.send') }} {{ t('dialog.edit_send_invite_message.send') }}
</el-button> </el-button>
</template> </template>

View File

@@ -1,35 +1,73 @@
// requires binding of SQLite import { ElMessageBox } from 'element-plus';
import { openExternalLink } from '../shared/utils';
// requires binding of SQLite
class SQLiteService { class SQLiteService {
async execute(callback, sql, args = null) { handleSQLiteError(e) {
if (LINUX) { if (typeof e.message === 'string') {
if (args) { if (e.message.includes('database disk image is malformed')) {
args = new Map(Object.entries(args)); ElMessageBox.confirm(
'Please repair or delete your database file by following these instructions.',
'Your database is corrupted',
{
confirmButtonText: 'Confirm',
type: 'warning'
}
)
.then(async (action) => {
if (action !== 'confirm') return;
openExternalLink(
'https://github.com/vrcx-team/VRCX/wiki#how-to-repair-vrcx-database'
);
})
.catch(() => {});
} }
var json = await SQLite.ExecuteJson(sql, args); if (e.message.includes('database or disk is full')) {
var items = JSON.parse(json); ElMessageBox.alert(
if (json.status === 'error') { 'Please free up some disk space.',
throw new Error(json.message); 'Disk containing database is full',
{
confirmButtonText: 'OK',
type: 'warning'
}
).catch(() => {});
} }
items.data.forEach((item) => {
callback(item);
});
return;
} }
var item = await SQLite.Execute(sql, args); throw e;
if (item.Item1 !== null) {
throw item.Item1;
}
item.Item2?.forEach((item) => {
callback(item);
});
} }
executeNonQuery(sql, args = null) { async execute(callback, sql, args = null) {
if (LINUX && args) { try {
args = new Map(Object.entries(args)); if (LINUX) {
if (args) {
args = new Map(Object.entries(args));
}
var json = await SQLite.ExecuteJson(sql, args);
var items = JSON.parse(json);
items.forEach((item) => {
callback(item);
});
return;
}
var data = await SQLite.Execute(sql, args);
data.forEach((row) => {
callback(row);
});
} catch (e) {
this.handleSQLiteError(e);
}
}
async executeNonQuery(sql, args = null) {
try {
if (LINUX && args) {
args = new Map(Object.entries(args));
}
return await SQLite.ExecuteNonQuery(sql, args);
} catch (e) {
this.handleSQLiteError(e);
} }
return SQLite.ExecuteNonQuery(sql, args);
} }
} }

View File

@@ -22,7 +22,7 @@ declare global {
utils: any; utils: any;
dayjs: any; dayjs: any;
configRepository: any; configRepository: any;
datebase: any; database: any;
gameLogService: any; gameLogService: any;
crypto: any; crypto: any;
sqliteService: any; sqliteService: any;
@@ -120,12 +120,9 @@ declare global {
}; };
const SQLite: { const SQLite: {
Execute: ( Execute: (sql: string, args: string) => Promise<any[]>;
sql: string,
args: string
) => Promise<{ Item1: any; Item2: any[] }>;
ExecuteJson: (sql: string, args: string) => Promise<string>; ExecuteJson: (sql: string, args: string) => Promise<string>;
ExecuteNonQuery: (sql: string, args: string) => Promise<void>; ExecuteNonQuery: (sql: string, args: string) => Promise<Number>;
}; };
const LogWatcher: { const LogWatcher: {

View File

@@ -23,9 +23,12 @@
<el-button @click="cancelEditAndSendInviteResponse">{{ <el-button @click="cancelEditAndSendInviteResponse">{{
t('dialog.edit_send_invite_response_message.cancel') t('dialog.edit_send_invite_response_message.cancel')
}}</el-button> }}</el-button>
<el-button type="primary" @click="saveEditAndSendInviteResponse">{{ <el-button
t('dialog.edit_send_invite_response_message.send') type="primary"
}}</el-button> @click="saveEditAndSendInviteResponse"
:disabled="!editAndSendInviteResponseDialog.newMessage"
>{{ t('dialog.edit_send_invite_response_message.send') }}</el-button
>
</template> </template>
</el-dialog> </el-dialog>
</template> </template>