From 7ff1d057086134db8ba69429a4f2032c87143141 Mon Sep 17 00:00:00 2001 From: Kim Date: Thu, 13 Jul 2023 16:17:58 +0200 Subject: [PATCH] Added S3Explorer a cmdline tool to grab and display data from our s3 buckets --- csharp/App/Backend/Resources/s3AdminKey.json | 4 + csharp/App/Backend/S3/S3Access.cs | 6 +- csharp/App/Backend/S3/S3Cmd.cs | 15 +- csharp/App/S3Explorer/Program.cs | 79 ++++++ csharp/App/S3Explorer/S3Explorer.csproj | 14 + csharp/App/S3Explorer/SnakeGameSS.cs | 283 +++++++++++++++++++ csharp/InnovEnergy.sln | 7 + 7 files changed, 402 insertions(+), 6 deletions(-) create mode 100644 csharp/App/Backend/Resources/s3AdminKey.json create mode 100644 csharp/App/S3Explorer/Program.cs create mode 100644 csharp/App/S3Explorer/S3Explorer.csproj create mode 100644 csharp/App/S3Explorer/SnakeGameSS.cs diff --git a/csharp/App/Backend/Resources/s3AdminKey.json b/csharp/App/Backend/Resources/s3AdminKey.json new file mode 100644 index 000000000..0cacecb5b --- /dev/null +++ b/csharp/App/Backend/Resources/s3AdminKey.json @@ -0,0 +1,4 @@ +{ + "Key": "EXO1abcb772bf43ab72951ba1dc", + "Secret": "_ym1KsGBSp90S5dwhZn18XD-u9Y4ghHvyIxg5gv5fHw" +} \ No newline at end of file diff --git a/csharp/App/Backend/S3/S3Access.cs b/csharp/App/Backend/S3/S3Access.cs index d509ae460..03dcfb4c7 100644 --- a/csharp/App/Backend/S3/S3Access.cs +++ b/csharp/App/Backend/S3/S3Access.cs @@ -9,9 +9,5 @@ public static class S3Access { public static S3Cmd ReadOnly => Deserialize(OpenRead("./Resources/s3ReadOnlyKey.json"))!; public static S3Cmd ReadWrite => Deserialize(OpenRead("./Resources/s3ReadWriteKey.json"))!; - - public static async Task CreateKey(String bucketName) - { - throw new NotImplementedException(); - } + public static S3Cmd Admin => Deserialize(OpenRead("./Resources/s3AdminKey.json"))!; } \ No newline at end of file diff --git a/csharp/App/Backend/S3/S3Cmd.cs b/csharp/App/Backend/S3/S3Cmd.cs index 6cd4a8f6e..40c32fa09 100644 --- a/csharp/App/Backend/S3/S3Cmd.cs +++ b/csharp/App/Backend/S3/S3Cmd.cs @@ -57,11 +57,23 @@ public class S3Cmd "; var result = await Run(bucketName, "mb"); - var setCors = await Run(bucketName, "PutBucketCors", cors); + var setCors = await Run(bucketName, "setcors", cors); return result.ExitCode == 0 && setCors.ExitCode == 0; } + public async Task ListFilesInBucket(String bucketName) + { + var result = await Run(bucketName, "ls"); + return result.StandardOutput; + } + + public async Task GetFileText(String bucketName, String filename) + { + var result = await Run(bucketName + "/" + filename, "get", "--force"); + return File.ReadAllLines("./" + filename); + } + public async Task DeleteBucket(String bucketName) { var result = await Run(bucketName, "rb"); @@ -86,4 +98,5 @@ public class S3Cmd .WithArguments(args) .ExecuteBufferedAsync(); } + } \ No newline at end of file diff --git a/csharp/App/S3Explorer/Program.cs b/csharp/App/S3Explorer/Program.cs new file mode 100644 index 000000000..a00764ff8 --- /dev/null +++ b/csharp/App/S3Explorer/Program.cs @@ -0,0 +1,79 @@ +using InnovEnergy.App.Backend.S3; +using InnovEnergy.Lib.Utils; + +namespace S3Explorer; + +public static class Program +{ + + public static async Task Main(String[] args) + { + if (args.Contains("-s")) + { + await SnakeGameSs.PlaySnake(); + } + + if (args.Length < 4 || args.Contains("-h")) + { + Console.WriteLine("To use: $S3Explorer [BucketId] [from:Unix-time] [to:Unix-time] [#Data-points]"); + Console.WriteLine("-h shows this message"); + Console.WriteLine("-s 🐍"); + return 0; + } + + + Console.WriteLine(""); + + var bucketName = args[0] + "-3e5b3069-214a-43ee-8d85-57d72000c19d"; + var fromTime = Int64.Parse(args[1]); + var toTime = Int64.Parse(args[2]); + var numberOfDataPoints = Int64.Parse(args[3]); + + var time = toTime - fromTime; + var timeBetweenDataPoints = time / numberOfDataPoints; + timeBetweenDataPoints = Math.Max(timeBetweenDataPoints, 2); + + // var fileList = ListAllFileNamesInBucket(bucketName); + var timestampList = new List { }; + + for (var i = fromTime; i <= toTime; i += timeBetweenDataPoints) + { + timestampList.Add((i/2 *2).ToString()); + } + await GrabFiles(bucketName,timestampList); + + return 0; + } + + private static async Task GrabFiles(String bucketName, List timestampList) + { + var last = timestampList.Last(); + var fileCsv = await S3Access.Admin.GetFileText(bucketName, last + ".csv"); + var dataKeys = fileCsv + .Select(l => l.Split(";")) + .Select(l => l[0]) + .Prepend("Timestamp") + .JoinWith(";") + .WriteLine(); + + foreach (var timestamp in timestampList) + { + + fileCsv = await S3Access.Admin.GetFileText(bucketName,timestamp + ".csv"); + + fileCsv.Select(l => l.Split(";")) + .Select(l => l[1]) + .Prepend(timestamp) + .JoinWith(";") + .WriteLine(); + + // Parse csv, build csv + } + } + + private static List ListAllFileNamesInBucket(String bucketName) + { + // Todo refactor S3Access into Lib + return S3Access.Admin.ListFilesInBucket(bucketName).Result.Split(',').ToList(); + } +} \ No newline at end of file diff --git a/csharp/App/S3Explorer/S3Explorer.csproj b/csharp/App/S3Explorer/S3Explorer.csproj new file mode 100644 index 000000000..3c32de060 --- /dev/null +++ b/csharp/App/S3Explorer/S3Explorer.csproj @@ -0,0 +1,14 @@ + + + + Exe + net7.0 + enable + enable + + + + + + + diff --git a/csharp/App/S3Explorer/SnakeGameSS.cs b/csharp/App/S3Explorer/SnakeGameSS.cs new file mode 100644 index 000000000..60d5eb253 --- /dev/null +++ b/csharp/App/S3Explorer/SnakeGameSS.cs @@ -0,0 +1,283 @@ +namespace S3Explorer; + +public static class SnakeGameSs +{ + public static async Task PlaySnake() + { + var tickRate = TimeSpan.FromMilliseconds(100); + var snakeGame = new SnakeGame(); + + using (var cts = new CancellationTokenSource()) + { + async Task MonitorKeyPresses() + { + while (!cts.Token.IsCancellationRequested) + { + if (Console.KeyAvailable) + { + var key = Console.ReadKey(intercept: true).Key; + snakeGame.OnKeyPress(key); + } + + await Task.Delay(10); + } + } + + var monitorKeyPresses = MonitorKeyPresses(); + + do + { + snakeGame.OnGameTick(); + snakeGame.Render(); + await Task.Delay(tickRate); + } while (!snakeGame.GameOver); + + // Allow time for user to weep before application exits. + for (var i = 0; i < 3; i++) + { + Console.Clear(); + await Task.Delay(500); + snakeGame.Render(); + await Task.Delay(500); + } + + cts.Cancel(); + await monitorKeyPresses; + } + } + + enum Direction + { + Up, + Down, + Left, + Right + } + + interface IRenderable + { + void Render(); + } + + readonly struct Position + { + public Position(int top, int left) + { + Top = top; + Left = left; + } + + public int Top { get; } + public int Left { get; } + + public Position RightBy(int n) => new Position(Top, Left + n); + public Position DownBy(int n) => new Position(Top + n, Left); + } + + class Apple : IRenderable + { + public Apple(Position position) + { + Position = position; + } + + public Position Position { get; } + + public void Render() + { + Console.SetCursorPosition(Position.Left, Position.Top); + Console.Write("🍏"); + } + } + + class Snake : IRenderable + { + private List _body; + private int _growthSpurtsRemaining; + + public Snake(Position spawnLocation, int initialSize = 1) + { + _body = new List { spawnLocation }; + _growthSpurtsRemaining = Math.Max(0, initialSize - 1); + Dead = false; + } + + public bool Dead { get; private set; } + public Position Head => _body.First(); + private IEnumerable Body => _body.Skip(1); + + public void Move(Direction direction) + { + if (Dead) throw new InvalidOperationException(); + + Position newHead; + + switch (direction) + { + case Direction.Up: + newHead = Head.DownBy(-1); + break; + + case Direction.Left: + newHead = Head.RightBy(-1); + break; + + case Direction.Down: + newHead = Head.DownBy(1); + break; + + case Direction.Right: + newHead = Head.RightBy(1); + break; + + default: + throw new ArgumentOutOfRangeException(); + } + + if (_body.Contains(newHead) || !PositionIsValid(newHead)) + { + Dead = true; + return; + } + + _body.Insert(0, newHead); + + if (_growthSpurtsRemaining > 0) + { + _growthSpurtsRemaining--; + } + else + { + _body.RemoveAt(_body.Count - 1); + } + } + + public void Grow() + { + if (Dead) throw new InvalidOperationException(); + + _growthSpurtsRemaining++; + } + + public void Render() + { + Console.SetCursorPosition(Head.Left, Head.Top); + Console.Write("◉"); + + foreach (var position in Body) + { + Console.SetCursorPosition(position.Left, position.Top); + Console.Write("■"); + } + } + + private static bool PositionIsValid(Position position) => + position.Top >= 0 && position.Left >= 0; + } + + class SnakeGame : IRenderable + { + private static readonly Position Origin = new Position(0, 0); + + private Direction _currentDirection; + private Direction _nextDirection; + private Snake _snake; + private Apple _apple; + + public SnakeGame() + { + _snake = new Snake(Origin, initialSize: 5); + _apple = CreateApple(); + _currentDirection = Direction.Right; + _nextDirection = Direction.Right; + } + + public bool GameOver => _snake.Dead; + + public void OnKeyPress(ConsoleKey key) + { + Direction newDirection; + + switch (key) + { + case ConsoleKey.W: + newDirection = Direction.Up; + break; + + case ConsoleKey.A: + newDirection = Direction.Left; + break; + + case ConsoleKey.S: + newDirection = Direction.Down; + break; + + case ConsoleKey.D: + newDirection = Direction.Right; + break; + + default: + return; + } + + // Snake cannot turn 180 degrees. + if (newDirection == OppositeDirectionTo(_currentDirection)) + { + return; + } + + _nextDirection = newDirection; + } + + public void OnGameTick() + { + if (GameOver) throw new InvalidOperationException(); + + _currentDirection = _nextDirection; + _snake.Move(_currentDirection); + + // If the snake's head moves to the same position as an apple, the snake + // eats it. + if (_snake.Head.Equals(_apple.Position)) + { + _snake.Grow(); + _apple = CreateApple(); + } + } + + public void Render() + { + Console.Clear(); + _snake.Render(); + _apple.Render(); + Console.SetCursorPosition(0, 0); + } + + private static Direction OppositeDirectionTo(Direction direction) + { + switch (direction) + { + case Direction.Up: return Direction.Down; + case Direction.Left: return Direction.Right; + case Direction.Right: return Direction.Left; + case Direction.Down: return Direction.Up; + default: throw new ArgumentOutOfRangeException(); + } + } + + private static Apple CreateApple() + { + // Can be factored elsewhere. + const int numberOfRows = 20; + const int numberOfColumns = 20; + + var random = new Random(); + var top = random.Next(0, numberOfRows + 1); + var left = random.Next(0, numberOfColumns + 1); + var position = new Position(top, left); + var apple = new Apple(position); + + return apple; + } + } +} \ No newline at end of file diff --git a/csharp/InnovEnergy.sln b/csharp/InnovEnergy.sln index beaac246d..735ef04c7 100644 --- a/csharp/InnovEnergy.sln +++ b/csharp/InnovEnergy.sln @@ -77,6 +77,8 @@ Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Adam6360D", "Lib\Devices\Ad EndProject Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "IEM3kGridMeter", "Lib\Devices\IEM3kGridMeter\IEM3kGridMeter.csproj", "{1391165D-51F1-45B4-8B7F-042A20AA0277}" EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "S3Explorer", "App\S3Explorer\S3Explorer.csproj", "{EB56EF94-D8A7-4111-A8E7-A87EF13596DA}" +EndProject Global GlobalSection(SolutionConfigurationPlatforms) = preSolution @@ -196,6 +198,10 @@ Global {1391165D-51F1-45B4-8B7F-042A20AA0277}.Debug|Any CPU.Build.0 = Debug|Any CPU {1391165D-51F1-45B4-8B7F-042A20AA0277}.Release|Any CPU.ActiveCfg = Release|Any CPU {1391165D-51F1-45B4-8B7F-042A20AA0277}.Release|Any CPU.Build.0 = Release|Any CPU + {EB56EF94-D8A7-4111-A8E7-A87EF13596DA}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {EB56EF94-D8A7-4111-A8E7-A87EF13596DA}.Debug|Any CPU.Build.0 = Debug|Any CPU + {EB56EF94-D8A7-4111-A8E7-A87EF13596DA}.Release|Any CPU.ActiveCfg = Release|Any CPU + {EB56EF94-D8A7-4111-A8E7-A87EF13596DA}.Release|Any CPU.Build.0 = Release|Any CPU EndGlobalSection GlobalSection(NestedProjects) = preSolution {CF4834CB-91B7-4172-AC13-ECDA8613CD17} = {145597B4-3E30-45E6-9F72-4DD43194539A} @@ -230,5 +236,6 @@ Global {1A56992B-CB72-490F-99A4-DF1186BA3A18} = {AD5B98A8-AB7F-4DA2-B66D-5B4E63E7D854} {A3C79247-4CAA-44BE-921E-7285AB39E71F} = {4931A385-24DC-4E78-BFF4-356F8D6D5183} {1391165D-51F1-45B4-8B7F-042A20AA0277} = {4931A385-24DC-4E78-BFF4-356F8D6D5183} + {EB56EF94-D8A7-4111-A8E7-A87EF13596DA} = {145597B4-3E30-45E6-9F72-4DD43194539A} EndGlobalSection EndGlobal