From de3aa1875b0d7d6915c108e1581591a8e532f7a8 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Matt=C3=A9o=20Delucchi?= Date: Mon, 3 Jun 2024 19:23:54 +0200 Subject: [PATCH 01/11] refactor(server): change le comportement de LOCK et UNLOCK --- src/Server/Command.cs | 24 ++++-------------------- 1 file changed, 4 insertions(+), 20 deletions(-) diff --git a/src/Server/Command.cs b/src/Server/Command.cs index d201d67..6a0aafa 100644 --- a/src/Server/Command.cs +++ b/src/Server/Command.cs @@ -135,21 +135,11 @@ public override Item execute(params string[] args) _db.Lock(); var mut = _db.Get(args[0]); - if (mut == null || mut == "f") - _db.Set(args[0], "l"); + if (mut == null) _db.Set(args[0], "locked"); _db.Unlock(); - switch (mut) - { - case null: - return new SimpleString("OK"); - case "f": - return new SimpleString("OK"); - case "l": - return new SimpleError("Key is already locked"); - default: - return new SimpleError("Key is already set"); - } + if (mut == null) return new SimpleString("OK"); + return new SimpleError("Key is already locked"); } } @@ -162,13 +152,7 @@ public override Item execute(params string[] args) if (args.Length != 1) return new SimpleError("Expected 1 argument"); - _db.Lock(); - var mut = _db.Get(args[0]); - if (mut == "l") _db.Set(args[0], "f"); - _db.Unlock(); - - if (mut != "l" && mut != "f") - return new SimpleError("Key is not a lock"); + _db.Del(args[0]); return new SimpleString("OK"); } From 481353e38973445f609b0352e39d05d80615b5c0 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Matt=C3=A9o=20Delucchi?= Date: Mon, 3 Jun 2024 19:54:16 +0200 Subject: [PATCH 02/11] =?UTF-8?q?feat(db):=20ajoute=20les=20m=C3=A9thodes?= =?UTF-8?q?=20Encode=20et=20Decode=20sur=20Database?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/Server/Database.cs | 52 ++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 52 insertions(+) diff --git a/src/Server/Database.cs b/src/Server/Database.cs index 5c73269..5ee76ba 100644 --- a/src/Server/Database.cs +++ b/src/Server/Database.cs @@ -1,4 +1,5 @@ using System.Collections.Concurrent; +using Shared.Resp; public class Database : IDisposable { @@ -76,6 +77,57 @@ private void Clean() } } + public Item Encode() + { + var db = new Dictionary(); + var data = new Dictionary(); + var ex = new Dictionary(); + + lock (_lock) + { + foreach (var (key, value) in _data) + data[new BulkString(key)] = new BulkString(value); + + foreach (var (key, value) in _ex) + ex[new BulkString(key)] = new Integer(value.ToUnixTimeMilliseconds()); + } + + db[new BulkString("data")] = new Map(data); + db[new BulkString("ex")] = new Map(ex); + return new Map(db); + } + + public static Database Decode(Item item) + { + if (item is not Map map) + throw new ArgumentException("Expected map"); + + var dict = new Dictionary(); + foreach (var (key, value) in map.Items) + dict[key.ToString() ?? ""] = value; + + if (!dict.TryGetValue("data", out var data) || data is not Map dataMap) + throw new ArgumentException("Expected data map"); + + if (!dict.TryGetValue("ex", out var ex) || ex is not Map exMap) + throw new ArgumentException("Expected ex map"); + + var db = new Database(); + db.Lock(); + + foreach (var (key, value) in dataMap.Items) + db._data[key.ToString() ?? ""] = value.ToString() ?? ""; + + foreach (var (key, value) in exMap.Items) + { + if (value is not Integer i) throw new ArgumentException("Expected integer"); + db._ex[key.ToString() ?? ""] = DateTimeOffset.FromUnixTimeMilliseconds(i.Value); + } + + db.Unlock(); + return db; + } + public void Dispose() { _cts.Cancel(); From 2fb1f40212ac34623defa1fd24502b2d085e3a40 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Matt=C3=A9o=20Delucchi?= Date: Mon, 3 Jun 2024 19:56:24 +0200 Subject: [PATCH 03/11] test(db): ajoute un test pour l'encodage et le decodage de Database --- tests/Server.UnitTests/Database.cs | 12 ++++++++++++ 1 file changed, 12 insertions(+) diff --git a/tests/Server.UnitTests/Database.cs b/tests/Server.UnitTests/Database.cs index 88a315b..0dc6268 100644 --- a/tests/Server.UnitTests/Database.cs +++ b/tests/Server.UnitTests/Database.cs @@ -50,4 +50,16 @@ public void AutoClean() Assert.Equal(1, data?.Count); } + + [Fact] + public void EncodeDecode() + { + using var db1 = new Database(); + db1.Set("key1", "value1"); + db1.Set("key2", "value2", 500); + + using var db2 = Database.Decode(db1.Encode()); + + Assert.Equal(db1.Encode().ToString(), db2.Encode().ToString()); + } } \ No newline at end of file From 91ce6d37dc6e2475fad6a17f69eef190b72052d9 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Matt=C3=A9o=20Delucchi?= Date: Mon, 3 Jun 2024 20:01:31 +0200 Subject: [PATCH 04/11] =?UTF-8?q?feat(db):=20ajoute=20les=20m=C3=A9thodes?= =?UTF-8?q?=20Save=20et=20Load=20=C3=A0=20Database?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/Server/Database.cs | 15 +++++++++++++++ 1 file changed, 15 insertions(+) diff --git a/src/Server/Database.cs b/src/Server/Database.cs index 5ee76ba..55e234d 100644 --- a/src/Server/Database.cs +++ b/src/Server/Database.cs @@ -128,6 +128,21 @@ public static Database Decode(Item item) return db; } + public void Save(string path) + { + using var file = File.Create(path); + using var writer = new StreamWriter(file); + writer.Write(Encode().Encode()); + } + + public static Database Load(string path) + { + using var file = File.OpenRead(path); + using var reader = new StreamReader(file); + var item = Item.Decode(reader); + return Decode(item); + } + public void Dispose() { _cts.Cancel(); From 5ca6cca5825d3a93652822cd8e446ce9baa5e703 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Matt=C3=A9o=20Delucchi?= Date: Mon, 3 Jun 2024 20:01:51 +0200 Subject: [PATCH 05/11] =?UTF-8?q?test(db):=20ajoute=20un=20test=20pour=20l?= =?UTF-8?q?es=20m=C3=A9thodes=20Save=20et=20Load=20de=20Database?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- tests/Server.UnitTests/Database.cs | 16 ++++++++++++++++ 1 file changed, 16 insertions(+) diff --git a/tests/Server.UnitTests/Database.cs b/tests/Server.UnitTests/Database.cs index 0dc6268..a91843e 100644 --- a/tests/Server.UnitTests/Database.cs +++ b/tests/Server.UnitTests/Database.cs @@ -62,4 +62,20 @@ public void EncodeDecode() Assert.Equal(db1.Encode().ToString(), db2.Encode().ToString()); } + + [Fact] + public void SaveLoad() + { + using var db1 = new Database(); + db1.Set("key1", "value1"); + db1.Set("key2", "value2", 500); + + db1.Save("db.dat"); + + using var db2 = Database.Load("db.dat"); + + Assert.Equal(db1.Encode().ToString(), db2.Encode().ToString()); + + File.Delete("db.dat"); + } } \ No newline at end of file From 2de51c0395dcc7f94470fb44aa8bcc6bb5a6ee3d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Matt=C3=A9o=20Delucchi?= Date: Mon, 3 Jun 2024 20:29:37 +0200 Subject: [PATCH 06/11] =?UTF-8?q?feat(db):=20emp=C3=AAche=20la=20corruptio?= =?UTF-8?q?n=20des=20sauvegardes?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/Server/Database.cs | 8 +++----- 1 file changed, 3 insertions(+), 5 deletions(-) diff --git a/src/Server/Database.cs b/src/Server/Database.cs index 55e234d..f4cf6c0 100644 --- a/src/Server/Database.cs +++ b/src/Server/Database.cs @@ -130,17 +130,15 @@ public static Database Decode(Item item) public void Save(string path) { - using var file = File.Create(path); - using var writer = new StreamWriter(file); - writer.Write(Encode().Encode()); + File.WriteAllText(path + ".tmp", Encode().Encode()); + File.Replace(path + ".tmp", path, null); } public static Database Load(string path) { using var file = File.OpenRead(path); using var reader = new StreamReader(file); - var item = Item.Decode(reader); - return Decode(item); + return Decode(Item.Decode(reader)); } public void Dispose() From 845b87c56b1e67c751bc69cda175806c3d36d0c1 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Matt=C3=A9o=20Delucchi?= Date: Mon, 3 Jun 2024 20:30:56 +0200 Subject: [PATCH 07/11] feat(server): ajoute un parseur d'argument rudimentaire --- src/Server/ArgParser.cs | 20 ++++++++++++++++++++ 1 file changed, 20 insertions(+) create mode 100644 src/Server/ArgParser.cs diff --git a/src/Server/ArgParser.cs b/src/Server/ArgParser.cs new file mode 100644 index 0000000..fa4b9df --- /dev/null +++ b/src/Server/ArgParser.cs @@ -0,0 +1,20 @@ +public class ArgParser +{ + public int Port { get; private set; } = 6379; + public string? Path { get; private set; } = null; + public int SaveInterval { get; private set; } = 5 * 60_000; + + public ArgParser(string[] args) + { + List arguments = new(args); + + int port = arguments.IndexOf("--port"); + if (port >= 0) Port = int.Parse(arguments[port + 1]); + + int path = arguments.IndexOf("--path"); + if (path >= 0) Path = arguments[path + 1]; + + int save = arguments.IndexOf("--save-interval"); + if (save >= 0) SaveInterval = int.Parse(arguments[save + 1]); + } +} \ No newline at end of file From 3f502a2c9e3cc8111b712ed35062f7366fd316dc Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Matt=C3=A9o=20Delucchi?= Date: Mon, 3 Jun 2024 20:40:49 +0200 Subject: [PATCH 08/11] =?UTF-8?q?feat(db):=20ajoute=20la=20m=C3=A9thode=20?= =?UTF-8?q?Link=20=C3=A0=20Database?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/Server/Database.cs | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/src/Server/Database.cs b/src/Server/Database.cs index f4cf6c0..dd74959 100644 --- a/src/Server/Database.cs +++ b/src/Server/Database.cs @@ -8,6 +8,7 @@ public class Database : IDisposable private readonly object _lock = new(); private readonly object _cleanLock = new(); private CancellationTokenSource _cts = new(); + private Timer? _timer; public Database() { @@ -141,12 +142,21 @@ public static Database Load(string path) return Decode(Item.Decode(reader)); } + public static Database Link(string? path, int saveInterval) + { + if (path == null) return new Database(); + var db = File.Exists(path) ? Load(path) : new Database(); + db._timer = new Timer(_ => db.Save(path), null, 0, saveInterval); + return db; + } + public void Dispose() { _cts.Cancel(); _cts.Dispose(); _data.Clear(); _ex.Clear(); + _timer?.Dispose(); GC.SuppressFinalize(this); } } \ No newline at end of file From cefbc60fba4b69d6aa192497db313999daa4565f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Matt=C3=A9o=20Delucchi?= Date: Mon, 3 Jun 2024 20:41:29 +0200 Subject: [PATCH 09/11] =?UTF-8?q?fix(db):=20cr=C3=A9e=20le=20fichier=20de?= =?UTF-8?q?=20sauvegarde=20si=20il=20n'existe=20pas=20encore?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/Server/Database.cs | 1 + 1 file changed, 1 insertion(+) diff --git a/src/Server/Database.cs b/src/Server/Database.cs index dd74959..5673a17 100644 --- a/src/Server/Database.cs +++ b/src/Server/Database.cs @@ -132,6 +132,7 @@ public static Database Decode(Item item) public void Save(string path) { File.WriteAllText(path + ".tmp", Encode().Encode()); + File.Create(path).Close(); File.Replace(path + ".tmp", path, null); } From 15d7ac91f3ccf09ceb06a7242dc9a118da50bf3b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Matt=C3=A9o=20Delucchi?= Date: Mon, 3 Jun 2024 20:42:43 +0200 Subject: [PATCH 10/11] =?UTF-8?q?feat(server):=20utilise=20la=20m=C3=A9tho?= =?UTF-8?q?de=20Link=20de=20Database=20pour=20associer=20un=20fichier=20?= =?UTF-8?q?=C3=A0=20la=20base=20de=20donn=C3=A9e?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/Server/Program.cs | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/src/Server/Program.cs b/src/Server/Program.cs index 328c3d6..590f13f 100644 --- a/src/Server/Program.cs +++ b/src/Server/Program.cs @@ -1,7 +1,6 @@ -int port = 6379; +var arguments = new ArgParser(args); -if (args.Length > 0) port = int.Parse(args[0]); +var database = Database.Link(arguments.Path, arguments.SaveInterval); +var server = new Server(arguments.Port, database); -var database = new Database(); -var server = new Server(port, database); server.Run(); \ No newline at end of file From 8cbf5a636890138ee03534e00bcf22ccc0c454bb Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Matt=C3=A9o=20Delucchi?= Date: Mon, 3 Jun 2024 21:34:52 +0200 Subject: [PATCH 11/11] feat(server): ajoute les fichiers Docker pour le serveur --- .dockerignore | 23 +++++++++++++++++++++++ Dockerfile | 21 +++++++++++++++++++++ compose.yaml | 7 +++++++ 3 files changed, 51 insertions(+) create mode 100644 .dockerignore create mode 100644 Dockerfile create mode 100644 compose.yaml diff --git a/.dockerignore b/.dockerignore new file mode 100644 index 0000000..d54f0cf --- /dev/null +++ b/.dockerignore @@ -0,0 +1,23 @@ +**/bin +**/obj +README.md +**/.dockerignore +**/.env +**/.git +**/.gitignore +**/.project +**/.vs +**/.vscode +**/.settings +**/.toolstarget +**/*.*proj.user +**/*.dbmdl +**/*.jfm +**/charts +**/docker-compose* +**/compose.y*ml +**/Dockerfile* +**/node_modules +**/npm-debug.log +**/secrets.dev.yaml +**/values.dev.yaml diff --git a/Dockerfile b/Dockerfile new file mode 100644 index 0000000..fc9c278 --- /dev/null +++ b/Dockerfile @@ -0,0 +1,21 @@ +# syntax=docker/dockerfile:1 + +FROM --platform=$BUILDPLATFORM mcr.microsoft.com/dotnet/sdk:8.0-alpine AS build + +COPY . /source + +WORKDIR /source/src/Server + +ARG TARGETARCH + +RUN --mount=type=cache,id=nuget,target=/root/.nuget/packages \ + dotnet publish -a ${TARGETARCH/amd64/x64} --use-current-runtime --self-contained false -o /app + +FROM mcr.microsoft.com/dotnet/aspnet:8.0-alpine AS final +WORKDIR /app + +COPY --from=build /app . + +USER $APP_UID + +ENTRYPOINT ["dotnet", "Server.dll"] diff --git a/compose.yaml b/compose.yaml new file mode 100644 index 0000000..a59aa22 --- /dev/null +++ b/compose.yaml @@ -0,0 +1,7 @@ +services: + server: + build: + context: . + target: final + ports: + - 6379:6379 \ No newline at end of file