Skip to content

Commit 29ee3e1

Browse files
committed
Storage API fixes
1 parent d2f18e5 commit 29ee3e1

5 files changed

Lines changed: 73 additions & 115 deletions

File tree

LabExtended/Core/Configs/Sections/StorageSection.cs

Lines changed: 2 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -19,23 +19,11 @@ public class StorageSection
1919
[Description("Whether or not a storage instance should be automatically loaded for each player with Do Not Track disabled.")]
2020
public bool LoadPlayerStorage { get; set; }
2121

22-
/// <summary>
23-
/// Gets or sets the path to the parent directory for player storage.
24-
/// </summary>
25-
[Description("Sets the path to the parent directory for player storage.")]
26-
public string PlayerPath { get; set; } = string.Empty;
27-
2822
/// <summary>
2923
/// Gets or sets the directory of the shared storage.
3024
/// </summary>
31-
[Description("Path to the shared storage directory.")]
32-
public string SharedPath { get; set; } = string.Empty;
33-
34-
/// <summary>
35-
/// Gets or sets the directory of the server's storage.
36-
/// </summary>
37-
[Description("Sets the directory of the server's storage.")]
38-
public string ServerPath { get; set; } = string.Empty;
25+
[Description("Path to the storage directory.")]
26+
public string StoragePath { get; set; } = string.Empty;
3927

4028
/// <summary>
4129
/// Gets or sets a collection of custom paths for storage instances, identified by name.

LabExtended/Core/Storage/StorageInstance.cs

Lines changed: 29 additions & 57 deletions
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,8 @@
11
using LabExtended.Utilities;
22
using LabExtended.Utilities.Update;
3-
4-
using LabExtended.API.Collections.Unsafe;
5-
6-
using System.Diagnostics;
7-
83
using Mirror;
4+
using System.Diagnostics;
5+
using System.IO;
96

107
namespace LabExtended.Core.Storage
118
{
@@ -14,10 +11,6 @@ namespace LabExtended.Core.Storage
1411
/// </summary>
1512
public class StorageInstance
1613
{
17-
internal ulong dirtyBits = 0;
18-
19-
private ulong previousBit = 1;
20-
2114
private NetworkWriter writer = new();
2215
private NetworkReader reader = new(default);
2316

@@ -28,7 +21,7 @@ public class StorageInstance
2821

2922
private FileSystemSafeWatcher watcher;
3023

31-
internal UnsafeList<StorageValue> values = new();
24+
internal List<StorageValue> values = new();
3225

3326
private Dictionary<string, StorageValue> lookup = new();
3427

@@ -45,7 +38,7 @@ public class StorageInstance
4538
/// <summary>
4639
/// Gets or sets the amount of milliseconds that must pass between writes performed by this server.
4740
/// </summary>
48-
public int WriteGuard { get; set; } = 100;
41+
public int WriteGuard { get; set; } = 300;
4942

5043
/// <summary>
5144
/// Gets or sets the amount of milliseconds that must pass between each update tick.
@@ -89,6 +82,8 @@ public T GetOrAdd<T>(string name, Func<T> factory) where T : StorageValue
8982
if (factory is null)
9083
throw new ArgumentNullException(nameof(factory));
9184

85+
value = factory();
86+
9287
if (value is null)
9388
throw new Exception($"Factory provided a null value");
9489

@@ -234,10 +229,7 @@ public bool Add(StorageValue value)
234229
if (lookup.ContainsKey(value.Name))
235230
return false;
236231

237-
value.DirtyBit = previousBit << 1;
238-
value.Path = System.IO.Path.Combine(Path, value.Name);
239-
240-
previousBit = value.DirtyBit;
232+
value.Path = System.IO.Path.GetFullPath(System.IO.Path.Combine(Path, value.Name));
241233

242234
value.Storage = this;
243235
value.OnAdded();
@@ -249,6 +241,7 @@ public bool Add(StorageValue value)
249241
values.Add(value);
250242
lookup.Add(value.Name, value);
251243

244+
ApiLog.Debug("StorageManager", $"Added value &3{value.ValuePath}&r to storage &3{Name}&r! (Path: &6{value.Path}&r)");
252245
return true;
253246
}
254247

@@ -274,15 +267,13 @@ public bool Remove(StorageValue value, bool deleteFile = false)
274267
if (!lookup.Remove(value.Name))
275268
return false;
276269

277-
dirtyBits &= ~value.DirtyBit;
278-
279270
if (deleteFile && File.Exists(value.Path))
280271
File.Delete(value.Path);
281272

282273
value.OnDestroyed();
283274

284-
value.DirtyBit = 0;
285275
value.Storage = null!;
276+
value.IsDirty = false;
286277
value.Path = string.Empty;
287278

288279
return true;
@@ -347,8 +338,6 @@ public void Destroy()
347338
values.Clear();
348339

349340
lookup.Clear();
350-
351-
dirtyBits = 0;
352341
}
353342

354343
/// <summary>
@@ -359,12 +348,12 @@ public void Destroy()
359348
/// items before repopulating it.</remarks>
360349
public void Save()
361350
{
362-
values.ForEach(x => x.MakeDirty());
351+
values.ForEach(x => x.IsDirty = true);
363352
}
364353

365354
private void Internal_Update()
366355
{
367-
if (UpdateGuard > 0)
356+
if (UpdateGuard > 0 && updateGuard != null)
368357
{
369358
if (updateGuard.ElapsedMilliseconds < UpdateGuard)
370359
return;
@@ -386,17 +375,15 @@ private void Internal_ReadFile(StorageValue value, bool isRemote)
386375

387376
reader.SetBuffer(segment);
388377

389-
value.ReadValue(reader);
390-
391-
if (value.IsDirty)
392-
dirtyBits &= ~value.DirtyBit;
393-
378+
value.IsDirty = false;
394379
value.dirtyRetries = 0;
395380

381+
value.ReadValue(reader);
382+
396383
if (isRemote)
397384
value.OnChanged();
398385

399-
ApiLog.Debug("StorageManager", $"Read value of &3{value.ValuePath}&r (IsRemote: &6{isRemote}&r)");
386+
ApiLog.Debug("StorageManager", $"Read value of &3{value.ValuePath}&r (IsRemote: &6{isRemote}&r): {value}");
400387
}
401388
catch (Exception ex)
402389
{
@@ -407,7 +394,7 @@ private void Internal_ReadFile(StorageValue value, bool isRemote)
407394
{
408395
value.ApplyDefault();
409396

410-
ApiLog.Debug("StorageManager", $"Applied default value of &3{value.ValuePath}&r (IsDirty: &6{value.IsDirty}&r)");
397+
ApiLog.Debug("StorageManager", $"Applied default value of &3{value.ValuePath}&r (IsDirty: &6{value.IsDirty}&r): {value}");
411398
}
412399
}
413400

@@ -430,8 +417,7 @@ private void Internal_WriteDirty()
430417

431418
if (value.dirtyRetries > MaxRetries)
432419
{
433-
dirtyBits &= ~value.DirtyBit;
434-
420+
value.IsDirty = false;
435421
value.dirtyRetries = 0;
436422

437423
ApiLog.Warn("StorageManager", $"Dirty value of &3{value.ValuePath}&r will be discarded due to exceeding maximum retry count!");
@@ -448,15 +434,11 @@ private void Internal_WriteDirty()
448434
{
449435
writeGuard.Restart();
450436

451-
using var file = File.Open(value.Path, FileMode.Create, FileAccess.Write, FileShare.ReadWrite);
437+
using var fileStream = new FileStream(value.Path, FileMode.Create, FileAccess.Write, FileShare.Read, 4096, FileOptions.None);
452438

453-
for (var x = 0; x < writer.Position; x++)
454-
{
455-
file.WriteByte(writer.buffer[x]);
456-
}
457-
458-
dirtyBits &= ~value.DirtyBit;
439+
fileStream.Write(writer.buffer, 0, writer.Position);
459440

441+
value.IsDirty = false;
460442
value.dirtyRetries = 0;
461443

462444
ApiLog.Debug("StorageManager", $"Saved dirty value of &3{value.ValuePath}&r");
@@ -470,8 +452,7 @@ private void Internal_WriteDirty()
470452
}
471453
else
472454
{
473-
dirtyBits &= ~value.DirtyBit;
474-
455+
value.IsDirty = false;
475456
value.dirtyRetries = 0;
476457

477458
ApiLog.Warn("StorageManager", $"Value &3{value.ValuePath}&r did not write any data into the buffer!");
@@ -485,34 +466,25 @@ private void Internal_WriteDirty()
485466

486467
private void Internal_FileChanged(object _, FileSystemEventArgs args)
487468
{
488-
var targetValue = values.Find(x => System.IO.Path.GetFullPath(x.Path) == System.IO.Path.GetFullPath(args.FullPath));
469+
var fullPath = System.IO.Path.GetFullPath(args.FullPath);
470+
var targetValue = values.FirstOrDefault(x => x.Path == fullPath);
489471

490-
ApiLog.Debug("StorageManager", $"Received value change at &6{args.FullPath}&r! (Value: &3{targetValue?.ValuePath ?? "(null)"}&r)");
472+
ApiLog.Debug("StorageManager", $"Received value change at &6{fullPath}&r! (Value: &3{targetValue?.ValuePath ?? "(null)"}&r)");
491473

492474
if (targetValue is null)
493-
{
494-
ApiLog.Warn("StorageManager", $"Received value change for an unknown file: &3{args.FullPath}&r");
495475
return;
496-
}
497476

498477
Internal_ReadFile(targetValue, true);
499478
}
500479

501480
private bool Internal_CheckGuard(FileSystemEventArgs args)
502481
{
503-
if (WriteGuard < 1)
504-
return false;
505-
506-
if (!writeGuard.IsRunning)
507-
return false;
508-
509-
if (writeGuard.ElapsedMilliseconds < WriteGuard)
510-
{
511-
ApiLog.Debug("StorageManager", $"WriteGuard of &3{Name}&r is active");
512-
return true;
513-
}
482+
var isActive = WriteGuard > 0
483+
&& writeGuard != null
484+
&& writeGuard.IsRunning
485+
&& writeGuard.ElapsedMilliseconds < WriteGuard;
514486

515-
return false;
487+
return isActive;
516488
}
517489
}
518490
}

LabExtended/Core/Storage/StorageManager.cs

Lines changed: 35 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,6 @@
1-
using LabExtended.API;
1+
using LabApi.Features.Wrappers;
2+
3+
using LabExtended.API;
24
using LabExtended.Events;
35

46
namespace LabExtended.Core.Storage
@@ -61,13 +63,11 @@ public static StorageInstance CreateStorage(string name, bool shared = false)
6163
if (!ApiLoader.ApiConfig.StorageSection.IsEnabled)
6264
throw new Exception($"Storage is disabled in config!");
6365

64-
var basePath = shared && !string.IsNullOrWhiteSpace(ApiLoader.ApiConfig.StorageSection.SharedPath)
65-
? ApiLoader.ApiConfig.StorageSection.SharedPath
66-
: ApiLoader.ApiConfig.StorageSection.ServerPath;
67-
6866
var path = ApiLoader.ApiConfig.StorageSection.CustomPaths.TryGetValue(name, out var customPath)
6967
? customPath
70-
: Path.Combine(basePath, name);
68+
: (shared
69+
? Path.Combine(ApiLoader.ApiConfig.StorageSection.StoragePath, $"{name}_Shared")
70+
: Path.Combine(ApiLoader.ApiConfig.StorageSection.StoragePath, $"{name}_{Server.Port.ToString()}"));
7171

7272
var storage = new StorageInstance() { Name = name, Path = path };
7373

@@ -89,10 +89,11 @@ private static void Internal_Verified(ExPlayer player)
8989
|| player.DoNotTrack)
9090
return;
9191

92-
if (string.IsNullOrEmpty(ApiLoader.ApiConfig.StorageSection.PlayerPath))
93-
return;
94-
95-
var playerStorage = new StorageInstance { Name = player.UserId, Path = Path.Combine(ApiLoader.ApiConfig.StorageSection.PlayerPath, player.UserId) };
92+
var playerStorage = new StorageInstance
93+
{
94+
Name = player.UserId,
95+
Path = Path.Combine(ApiLoader.ApiConfig.StorageSection.StoragePath, "Players", player.UserId)
96+
};
9697

9798
playerStorage.Initialize();
9899
player.FileStorage = playerStorage;
@@ -105,21 +106,34 @@ internal static void Internal_Init()
105106
if (!ApiLoader.ApiConfig.StorageSection.IsEnabled)
106107
return;
107108

108-
if (!string.IsNullOrWhiteSpace(ApiLoader.ApiConfig.StorageSection.ServerPath))
109+
if (string.IsNullOrWhiteSpace(ApiLoader.ApiConfig.StorageSection.StoragePath))
109110
{
110-
ServerStorage = new() { Name = "ServerSpecific", Path = ApiLoader.ApiConfig.StorageSection.ServerPath };
111-
ServerStorage.Initialize();
111+
ApiLog.Warn("StorageManager", $"Storage is enabled in config, but it's missing the root directory!");
112+
return;
113+
}
112114

113-
ServerStorageLoaded?.Invoke();
114-
}
115+
var playersDir = Path.Combine(ApiLoader.ApiConfig.StorageSection.StoragePath, "Players");
116+
var sharedDir = Path.Combine(ApiLoader.ApiConfig.StorageSection.StoragePath, "Shared");
117+
var serverDir = Path.Combine(ApiLoader.ApiConfig.StorageSection.StoragePath, Server.Port.ToString());
115118

116-
if (!string.IsNullOrWhiteSpace(ApiLoader.ApiConfig.StorageSection.SharedPath))
117-
{
118-
SharedStorage = new() { Name = "ServerShared", Path = ApiLoader.ApiConfig.StorageSection.SharedPath };
119-
SharedStorage.Initialize();
119+
if (!Directory.Exists(playersDir))
120+
Directory.CreateDirectory(playersDir);
120121

121-
SharedStorageLoaded?.Invoke();
122-
}
122+
if (!Directory.Exists(sharedDir))
123+
Directory.CreateDirectory(sharedDir);
124+
125+
if (!Directory.Exists(serverDir))
126+
Directory.CreateDirectory(serverDir);
127+
128+
ServerStorage = new() { Name = "ServerSpecific", Path = Path.Combine(ApiLoader.ApiConfig.StorageSection.StoragePath, Server.Port.ToString()) };
129+
ServerStorage.Initialize();
130+
131+
ServerStorageLoaded?.Invoke();
132+
133+
SharedStorage = new() { Name = "ServerShared", Path = Path.Combine(ApiLoader.ApiConfig.StorageSection.StoragePath, "Shared") };
134+
SharedStorage.Initialize();
135+
136+
SharedStorageLoaded?.Invoke();
123137

124138
ExPlayerEvents.Left += Internal_Left;
125139
ExPlayerEvents.Verified += Internal_Verified;

LabExtended/Core/Storage/StorageValue.cs

Lines changed: 2 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -9,11 +9,6 @@ public class StorageValue
99
{
1010
internal int dirtyRetries = 0;
1111

12-
/// <summary>
13-
/// Gets the dirty bitmask of this storage value.
14-
/// </summary>
15-
public ulong DirtyBit { get; internal set; }
16-
1712
/// <summary>
1813
/// Gets the name of this storage value.
1914
/// </summary>
@@ -44,9 +39,9 @@ public string ValuePath
4439
public StorageInstance Storage { get; internal set; }
4540

4641
/// <summary>
47-
/// Gets whether this value is dirty and needs to be saved.
42+
/// Gets or sets whether this value is dirty and needs to be saved.
4843
/// </summary>
49-
public bool IsDirty => Storage != null && (Storage.dirtyBits & DirtyBit) == DirtyBit;
44+
public bool IsDirty { get; set; }
5045

5146
/// <summary>
5247
/// Initializes a new instance of the <see cref="StorageValue"/> class.
@@ -126,19 +121,5 @@ public virtual void ReadValue(NetworkReader reader)
126121
{
127122

128123
}
129-
130-
/// <summary>
131-
/// Marks the object as dirty, indicating that it has been modified and requires further processing.
132-
/// </summary>
133-
/// <remarks>This method sets the appropriate dirty bit in the associated storage if the object is
134-
/// not already marked as dirty. No action is taken if the storage is null or the object is already marked as
135-
/// dirty.</remarks>
136-
public void MakeDirty()
137-
{
138-
if (Storage is null || IsDirty)
139-
return;
140-
141-
Storage.dirtyBits |= DirtyBit;
142-
}
143124
}
144125
}

0 commit comments

Comments
 (0)