forked from vrcx-team/VRCX
-
Notifications
You must be signed in to change notification settings - Fork 0
/
Copy pathWorldDatabase.cs
322 lines (289 loc) · 13.9 KB
/
WorldDatabase.cs
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using SQLite;
namespace VRCX
{
[Table("data")]
public class WorldData
{
[PrimaryKey, AutoIncrement]
[Column("id")]
public int Id { get; set; }
[Column("world_id"), NotNull]
public string WorldId { get; set; }
[Column("key"), NotNull]
public string Key { get; set; }
[Column("value"), NotNull]
public string Value { get; set; }
[Column("value_size"), NotNull]
public int ValueSize { get; set; }
[Column("last_accessed")]
public DateTimeOffset LastAccessed { get; set; }
[Column("last_modified")]
public DateTimeOffset LastModified { get; set; }
}
[Table("worlds")]
public class World
{
[PrimaryKey, AutoIncrement]
[Column("id")]
public int Id { get; set; }
[Column("world_id"), NotNull]
public string WorldId { get; set; }
[Column("connection_key"), NotNull]
public string ConnectionKey { get; set; }
[Column("total_data_size"), NotNull]
public int TotalDataSize { get; set; }
[Column("allow_external_read")]
public bool AllowExternalRead { get; set; }
}
internal class WorldDatabase
{
private static SQLiteConnection sqlite;
private readonly static string dbInitQuery = @"
CREATE TABLE IF NOT EXISTS worlds (
id INTEGER PRIMARY KEY AUTOINCREMENT,
world_id TEXT NOT NULL UNIQUE,
connection_key TEXT NOT NULL,
total_data_size INTEGER DEFAULT 0,
allow_external_read INTEGER DEFAULT 0
);
\
CREATE TABLE IF NOT EXISTS data (
id INTEGER PRIMARY KEY AUTOINCREMENT,
world_id TEXT NOT NULL,
key TEXT NOT NULL,
value TEXT NOT NULL,
value_size INTEGER NOT NULL DEFAULT 0,
last_accessed INTEGER DEFAULT (strftime('%s', 'now')),
last_modified INTEGER DEFAULT (strftime('%s', 'now')),
FOREIGN KEY (world_id) REFERENCES worlds(world_id) ON DELETE CASCADE,
UNIQUE (world_id, key)
);
\
CREATE TRIGGER IF NOT EXISTS data_update_trigger
AFTER UPDATE ON data
FOR EACH ROW
BEGIN
UPDATE data SET last_modified = (strftime('%s', 'now')) WHERE id = OLD.id;
END;
\
CREATE TRIGGER IF NOT EXISTS data_insert_trigger
AFTER INSERT ON data
FOR EACH ROW
BEGIN
UPDATE data SET last_accessed = (strftime('%s', 'now')), last_modified = (strftime('%s', 'now')) WHERE id = NEW.id;
END;";
public WorldDatabase(string databaseLocation)
{
var options = new SQLiteConnectionString(databaseLocation, true);
sqlite = new SQLiteConnection(options);
sqlite.Execute(dbInitQuery);
// TODO: Split these init queries into their own functions so we can call/update them individually.
var queries = dbInitQuery.Split('\\');
sqlite.BeginTransaction();
foreach (var query in queries)
{
sqlite.Execute(query);
}
sqlite.Commit();
}
/// <summary>
/// Checks if a world with the specified ID exists in the database.
/// </summary>
/// <param name="worldId">The ID of the world to check for.</param>
/// <returns>True if the world exists in the database, false otherwise.</returns>
public bool DoesWorldExist(string worldId)
{
var query = sqlite.Table<World>().Where(w => w.WorldId == worldId).Select(w => w.WorldId);
return query.Any();
}
/// <summary>
/// Gets the ID of the world with the specified connection key from the database.
/// </summary>
/// <param name="connectionKey">The connection key of the world to get the ID for.</param>
/// <returns>The ID of the world with the specified connection key, or null if no such world exists in the database.</returns>
public string GetWorldByConnectionKey(string connectionKey)
{
var query = sqlite.Table<World>().Where(w => w.ConnectionKey == connectionKey).Select(w => w.WorldId);
return query.FirstOrDefault();
}
/// <summary>
/// Gets the connection key for a world from the database.
/// </summary>
/// <param name="worldId">The ID of the world to get the connection key for.</param>
/// <returns>The connection key for the specified world, or null if the world does not exist in the database.</returns>
public string GetWorldConnectionKey(string worldId)
{
var query = sqlite.Table<World>().Where(w => w.WorldId == worldId).Select(w => w.ConnectionKey);
return query.FirstOrDefault();
}
/// <summary>
/// Sets the connection key for a world in the database. If the world already exists in the database, the connection key is updated. Otherwise, a new world is added to the database with the specified connection key.
/// </summary>
/// <param name="worldId">The ID of the world to set the connection key for.</param>
/// <param name="connectionKey">The connection key to set for the world.</param>
/// <returns>The connection key that was set.</returns>
public string SetWorldConnectionKey(string worldId, string connectionKey)
{
var query = sqlite.Table<World>().Where(w => w.WorldId == worldId).Select(w => w.ConnectionKey);
if (query.Any())
{
sqlite.Execute("UPDATE worlds SET connection_key = ? WHERE world_id = ?", connectionKey, worldId);
}
else
{
sqlite.Insert(new World() { WorldId = worldId, ConnectionKey = connectionKey });
}
return connectionKey;
}
/// <summary>
/// Sets the value of the allow_external_read field for the world with the specified ID in the database.
/// </summary>
/// <param name="worldId">The ID of the world to set the allow_external_read field for.</param>
/// <param name="allowExternalRead">The value to set for the allow_external_read field.</param>
public void SetWorldAllowExternalRead(string worldId, bool allowExternalRead)
{
sqlite.Execute("UPDATE worlds SET allow_external_read = ? WHERE world_id = ?", allowExternalRead, worldId);
}
/// <summary>
/// Gets the value of the allow_external_read field for the world with the specified ID from the database.
/// </summary>
/// <param name="worldId">The ID of the world to get the allow_external_read field for.</param>
/// <returns>The value of the allow_external_read field for the specified world.</returns>
public bool GetWorldAllowExternalRead(string worldId)
{
var query = sqlite.Table<World>().Where(w => w.WorldId == worldId).Select(w => w.AllowExternalRead);
return query.FirstOrDefault();
}
/// <summary>
/// Adds a new world to the database.
/// </summary>
/// <param name="worldId">The ID of the world to add.</param>
/// <param name="connectionKey">The connection key of the world to add.</param>
/// <exception cref="SQLiteException">Thrown if a world with the specified ID already exists in the database.</exception>
public void AddWorld(string worldId, string connectionKey)
{
// * This will throw an error if the world already exists.. so don't do that
sqlite.Insert(new World() { WorldId = worldId, ConnectionKey = connectionKey });
}
/// <summary>
/// Gets the world with the specified ID from the database.
/// </summary>
/// <param name="worldId">The ID of the world to get.</param>
/// <returns>The world with the specified ID, or null if no such world exists in the database.</returns>
public World GetWorld(string worldId)
{
var query = sqlite.Table<World>().Where(w => w.WorldId == worldId);
return query.FirstOrDefault();
}
/// <summary>
/// Gets the total data size shared across all rows, in bytes, for the world with the specified ID from the database.
/// </summary>
/// <param name="worldId">The ID of the world to get the total data size for.</param>
/// <returns>The total data size for the world, in bytes.</returns>
public int GetWorldDataSize(string worldId)
{
var query = sqlite.Table<World>().Where(w => w.WorldId == worldId).Select(w => w.TotalDataSize);
return query.FirstOrDefault();
}
/// <summary>
/// Updates the total data size, in bytes for the world with the specified ID in the database.
/// </summary>
/// <param name="worldId">The ID of the world to update the total data size for.</param>
/// <param name="size">The new total data size for the world, in bytes.</param>
public void UpdateWorldDataSize(string worldId, int size)
{
sqlite.Execute("UPDATE worlds SET total_data_size = ? WHERE world_id = ?", size, worldId);
}
/// <summary>
/// Adds or updates a data entry in the database with the specified world ID, key, and value.
/// </summary>
/// <param name="worldId">The ID of the world to add the data entry for.</param>
/// <param name="key">The key of the data entry to add or replace.</param>
/// <param name="value">The value of the data entry to add or replace.</param>
/// <param name="dataSize">The size of the data entry to add or replace, in bytes. If null, the size is calculated from the value automatically.</param>
public void AddDataEntry(string worldId, string key, string value, int? dataSize = null)
{
int byteSize = dataSize ?? Encoding.UTF8.GetByteCount(value);
// check if entry already exists;
// INSERT OR REPLACE(InsertOrReplace method) deletes the old row and creates a new one, incrementing the id, which I don't want
var query = sqlite.Table<WorldData>().Where(w => w.WorldId == worldId && w.Key == key);
if (query.Any())
{
sqlite.Execute("UPDATE data SET value = ?, value_size = ? WHERE world_id = ? AND key = ?", value, byteSize, worldId, key);
}
else
{
sqlite.Insert(new WorldData() { WorldId = worldId, Key = key, Value = value, ValueSize = byteSize });
}
}
/// <summary>
/// Gets the data entry with the specified world ID and key from the database.
/// </summary>
/// <param name="worldId">The ID of the world to get the data entry for.</param>
/// <param name="key">The key of the data entry to get.</param>
/// <returns>The data entry with the specified world ID and key, or null if no such data entry exists in the database.</returns>
public WorldData GetDataEntry(string worldId, string key)
{
var query = sqlite.Table<WorldData>().Where(w => w.WorldId == worldId && w.Key == key);
return query.FirstOrDefault();
}
/// <summary>
/// Gets the data entries with the specified world ID and keys from the database.
/// </summary>
/// <param name="worldId">The ID of the world to get the data entries for.</param>
/// <param name="keys">The keys of the data entries to get.</param>
/// <returns>An enumerable collection of the data entries with the specified world ID and keys.</returns>
public IEnumerable<WorldData> GetDataEntries(string worldId, string[] keys)
{
var query = sqlite.Table<WorldData>().Where(w => w.WorldId == worldId && keys.Contains(w.Key));
return query.ToList();
}
/// <summary>
/// Gets all data entries for the world with the specified ID from the database.
/// </summary>
/// <param name="worldId">The ID of the world to get the data entries for.</param>
/// <returns>An enumerable collection of all data entries for the world with the specified ID.</returns>
public IEnumerable<WorldData> GetAllDataEntries(string worldId)
{
var query = sqlite.Table<WorldData>().Where(w => w.WorldId == worldId).Take(10000);
return query.ToList();
}
/// <summary>
/// Gets the size of the data entry, in bytes, with the specified world ID and key from the database.
/// </summary>
/// <param name="worldId">The ID of the world to get the data entry size for.</param>
/// <param name="key">The key of the data entry to get the size for.</param>
/// <returns>The size of the data entry with the specified world ID and key, or 0 if no such data entry exists in the database.</returns>
public int GetDataEntrySize(string worldId, string key)
{
var query = sqlite.Table<WorldData>().Where(w => w.WorldId == worldId && w.Key == key).Select(w => w.ValueSize);
return query.FirstOrDefault();
}
/// <summary>
/// Deletes the data entry with the specified world ID and key from the database.
/// </summary>
/// <param name="worldId">The ID of the world to delete the data entry from.</param>
/// <param name="key">The key of the data entry to delete.</param>
public void DeleteDataEntry(string worldId, string key)
{
sqlite.Execute("DELETE FROM data WHERE world_id = ? AND key = ?", worldId, key);
}
/// <summary>
/// Deletes all data entries for the world with the specified ID from the database.
/// </summary>
/// <param name="worldId">The ID of the world to delete all data entries for.</param>
public void DeleteAllDataEntriesForWorld(string worldId)
{
sqlite.Execute("DELETE FROM data WHERE world_id = ?", worldId);
}
public void Close()
{
sqlite.Close();
}
}
}