Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
9 changes: 7 additions & 2 deletions Refresh.GameServer/RefreshGameServer.cs
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@
using Refresh.GameServer.Importing;
using Refresh.GameServer.Middlewares;
using Refresh.GameServer.Services;
using Refresh.GameServer.Storage;
using Refresh.GameServer.Time;
using Refresh.GameServer.Types.Levels.Categories;
using Refresh.GameServer.Types.Roles;
Expand Down Expand Up @@ -52,16 +53,20 @@ public RefreshGameServer(
this._databaseProvider.Initialize();
this._dataStore = dataStore;

DryArchiveConfig dryConfig = Config.LoadFromJsonFile<DryArchiveConfig>("dry.json", this.Logger);
if (dryConfig.Enabled)
this._dataStore = new AggregateDataStore(dataStore, new DryDataStore(dryConfig));

this.SetupInitializer(() =>
{
GameDatabaseProvider provider = databaseProvider.Invoke();

this.WorkerManager?.Stop();
this.WorkerManager = new WorkerManager(this.Logger, this._dataStore!, provider);
this.WorkerManager = new WorkerManager(this.Logger, this._dataStore, provider);

authProvider ??= new GameAuthenticationProvider(this._config!);

this.InjectBaseServices(provider, authProvider, dataStore);
this.InjectBaseServices(provider, authProvider, this._dataStore);
});
}

Expand Down
42 changes: 42 additions & 0 deletions Refresh.GameServer/Storage/AggregateDataStore.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
using System.Collections.Frozen;
using Bunkum.Core.Storage;

namespace Refresh.GameServer.Storage;

public class AggregateDataStore : IDataStore
{
private IDataStore PrimaryStore { get; }
private FrozenSet<IDataStore> AllStores { get; }

public AggregateDataStore(IDataStore primary, params IDataStore[] others)
{
this.PrimaryStore = primary;

List<IDataStore> stores = new(others.Length + 1) { primary };
stores.AddRange(others);

this.AllStores = stores.ToFrozenSet();
}

private IDataStore GetStoreContainingKey(string key)
{
IDataStore? store = this.AllStores.FirstOrDefault(s => s.ExistsInStore(key));

if (store == null)
throw new InvalidOperationException($"No data stores contained the key '{key}'.");

return store;
}

public bool ExistsInStore(string key) => this.AllStores.Any(s => s.ExistsInStore(key));
public byte[] GetDataFromStore(string key) => this.GetStoreContainingKey(key).GetDataFromStore(key);
public Stream GetStreamFromStore(string key) => this.GetStoreContainingKey(key).GetStreamFromStore(key);

public string[] GetKeysFromStore() => this.AllStores.SelectMany(r => r.GetKeysFromStore()).ToArray();

public bool WriteToStore(string key, byte[] data) => this.PrimaryStore.WriteToStore(key, data);
public bool RemoveFromStore(string key) => this.PrimaryStore.RemoveFromStore(key);

public Stream OpenWriteStream(string key) => this.PrimaryStore.OpenWriteStream(key);
public bool WriteToStoreFromStream(string key, Stream data) => this.PrimaryStore.WriteToStoreFromStream(key, data);
}
18 changes: 18 additions & 0 deletions Refresh.GameServer/Storage/DryArchiveConfig.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
using Bunkum.Core.Configuration;

namespace Refresh.GameServer.Storage;

public class DryArchiveConfig : Config
{
public override int CurrentConfigVersion => 1;
public override int Version { get; set; }

protected override void Migrate(int oldVer, dynamic oldConfig)
{

}

public bool Enabled { get; set; }
public string Location { get; set; } = "/var/dry/";
public bool UseFolderNames { get; set; } = true;
}
99 changes: 99 additions & 0 deletions Refresh.GameServer/Storage/DryDataStore.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,99 @@
using System.Text;
using Bunkum.Core.Storage;
using JetBrains.Annotations;
using Refresh.GameServer.Verification;

namespace Refresh.GameServer.Storage;

public class DryDataStore : IDataStore
{
private readonly DryArchiveConfig _config;

private const string WriteError = $"{nameof(DryDataStore)} is an archive, and cannot be written to.";

public DryDataStore(DryArchiveConfig config)
{
this._config = config;
}

[Pure]
private string? GetPath(ReadOnlySpan<char> hash)
{
if (!CommonPatterns.Sha1Regex().IsMatch(hash))
return null;

StringBuilder builder = new();

// /var/dry
builder.Append(this._config.Location);

// /var/dry/
if (!this._config.Location.EndsWith('/'))
builder.Append('/');

// /var/dry/dry23r0/
if (this._config.UseFolderNames)
{
builder.Append("dry23r");
builder.Append(hash[0]);
builder.Append('/');
}

// /var/dry/dry23r0/01/
builder.Append(hash.Slice(0, 2));
builder.Append('/');

// /var/dry/dry23r0/01/02/
builder.Append(hash.Slice(2, 2));
builder.Append('/');

// /var/dry/dry2er0/01/02/010220123644c8e53d4054bf0d30d0e2bd0786ff8
builder.Append(hash);

return builder.ToString();
}

public bool ExistsInStore(string key) => File.Exists(this.GetPath(key));

public bool WriteToStore(string key, byte[] data)
{
throw new InvalidOperationException(WriteError);
}

public byte[] GetDataFromStore(string key)
{
string? path = this.GetPath(key);

if (path == null)
throw new FormatException("The key was invalid.");

return File.ReadAllBytes(path);
}

public bool RemoveFromStore(string key)
{
throw new InvalidOperationException(WriteError);
}

public string[] GetKeysFromStore() => []; // TODO: Implement when we want to store these as GameAssets

public bool WriteToStoreFromStream(string key, Stream data)
{
throw new InvalidOperationException(WriteError);
}

public Stream GetStreamFromStore(string key)
{
string? path = this.GetPath(key);

if (path == null)
throw new FormatException("The key was invalid.");

return File.OpenRead(path);
}

public Stream OpenWriteStream(string key)
{
throw new InvalidOperationException(WriteError);
}
}