From f56f94a03b362430fe28b12ee8ae78c8667c8231 Mon Sep 17 00:00:00 2001 From: ChiaraBm Date: Mon, 24 Mar 2025 22:15:05 +0100 Subject: [PATCH] Implemented zip and tar compressing and decompressing. Implemented chunked file uploading --- .../Client/ServerFileSystemController.cs | 24 ++ .../Services/ServerFileSystemService.cs | 32 +++ .../Helpers/ServerFileSystem.cs | 256 +++++++++++++++++- .../Servers/ServerFileSystemController.cs | 31 +++ .../Controllers/Servers/UploadController.cs | 43 ++- .../MoonlightServers.Daemon.csproj | 7 +- .../Requests/ServerFilesCompressRequest.cs | 10 + .../Requests/ServerFilesDecompressRequest.cs | 10 + .../Enums/CompressType.cs | 7 + .../MoonlightServers.DaemonShared.csproj | 1 + .../Helpers/ServerFileSystemProvider.cs | 97 ++++++- .../Services/ServerFileSystemService.cs | 45 ++- .../Servers/ServerTabs/FilesTab.razor | 12 +- .../UI/Views/Admin/All/Index.razor | 4 +- .../UI/Views/Admin/Nodes/Index.razor | 8 +- .../UI/Views/Admin/Stars/Index.razor | 12 +- .../Files/ServerFilesCompressRequest.cs | 14 + .../Files/ServerFilesDecompressRequest.cs | 15 + 18 files changed, 573 insertions(+), 55 deletions(-) create mode 100644 MoonlightServers.DaemonShared/DaemonSide/Http/Requests/ServerFilesCompressRequest.cs create mode 100644 MoonlightServers.DaemonShared/DaemonSide/Http/Requests/ServerFilesDecompressRequest.cs create mode 100644 MoonlightServers.DaemonShared/Enums/CompressType.cs create mode 100644 MoonlightServers.Shared/Http/Requests/Client/Servers/Files/ServerFilesCompressRequest.cs create mode 100644 MoonlightServers.Shared/Http/Requests/Client/Servers/Files/ServerFilesDecompressRequest.cs diff --git a/MoonlightServers.ApiServer/Http/Controllers/Client/ServerFileSystemController.cs b/MoonlightServers.ApiServer/Http/Controllers/Client/ServerFileSystemController.cs index 4902c31..fb45c8d 100644 --- a/MoonlightServers.ApiServer/Http/Controllers/Client/ServerFileSystemController.cs +++ b/MoonlightServers.ApiServer/Http/Controllers/Client/ServerFileSystemController.cs @@ -6,6 +6,8 @@ using MoonCore.Extended.Abstractions; using Moonlight.ApiServer.Database.Entities; using MoonlightServers.ApiServer.Database.Entities; using MoonlightServers.ApiServer.Services; +using MoonlightServers.DaemonShared.Enums; +using MoonlightServers.Shared.Http.Requests.Client.Servers.Files; using MoonlightServers.Shared.Http.Responses.Client.Servers.Files; namespace MoonlightServers.ApiServer.Http.Controllers.Client; @@ -132,6 +134,28 @@ public class ServerFileSystemController : Controller }; } + [HttpPost("{serverId:int}/files/compress")] + public async Task Compress([FromRoute] int serverId, [FromBody] ServerFilesCompressRequest request) + { + var server = await GetServerById(serverId); + + if (!Enum.TryParse(request.Type, true, out CompressType type)) + throw new HttpApiException("Invalid compress type provided", 400); + + await ServerFileSystemService.Compress(server, type, request.Items, request.Destination); + } + + [HttpPost("{serverId:int}/files/decompress")] + public async Task Decompress([FromRoute] int serverId, [FromBody] ServerFilesDecompressRequest request) + { + var server = await GetServerById(serverId); + + if (!Enum.TryParse(request.Type, true, out CompressType type)) + throw new HttpApiException("Invalid compress type provided", 400); + + await ServerFileSystemService.Decompress(server, type, request.Path, request.Destination); + } + private async Task GetServerById(int serverId) { var server = await ServerRepository diff --git a/MoonlightServers.ApiServer/Services/ServerFileSystemService.cs b/MoonlightServers.ApiServer/Services/ServerFileSystemService.cs index 7f8cd64..2453076 100644 --- a/MoonlightServers.ApiServer/Services/ServerFileSystemService.cs +++ b/MoonlightServers.ApiServer/Services/ServerFileSystemService.cs @@ -4,7 +4,9 @@ using MoonCore.Attributes; using MoonCore.Extended.Abstractions; using MoonCore.Helpers; using MoonlightServers.ApiServer.Database.Entities; +using MoonlightServers.DaemonShared.DaemonSide.Http.Requests; using MoonlightServers.DaemonShared.DaemonSide.Http.Responses.Servers; +using MoonlightServers.DaemonShared.Enums; namespace MoonlightServers.ApiServer.Services; @@ -59,6 +61,36 @@ public class ServerFileSystemService ); } + public async Task Compress(Server server, CompressType type, string[] items, string destination) + { + using var apiClient = await GetApiClient(server); + + await apiClient.Post( + $"api/servers/{server.Id}/files/compress", + new ServerFilesCompressRequest() + { + Type = type, + Items = items, + Destination = destination + } + ); + } + + public async Task Decompress(Server server, CompressType type, string path, string destination) + { + using var apiClient = await GetApiClient(server); + + await apiClient.Post( + $"api/servers/{server.Id}/files/decompress", + new ServerFilesDecompressRequest() + { + Type = type, + Path = path, + Destination = destination + } + ); + } + #region Helpers private async Task GetApiClient(Server server) diff --git a/MoonlightServers.Daemon/Helpers/ServerFileSystem.cs b/MoonlightServers.Daemon/Helpers/ServerFileSystem.cs index 4f68b0d..0861fff 100644 --- a/MoonlightServers.Daemon/Helpers/ServerFileSystem.cs +++ b/MoonlightServers.Daemon/Helpers/ServerFileSystem.cs @@ -1,6 +1,12 @@ +using System.Text; +using ICSharpCode.SharpZipLib.GZip; +using ICSharpCode.SharpZipLib.Tar; +using ICSharpCode.SharpZipLib.Zip; using Mono.Unix.Native; +using MoonCore.Unix.Exceptions; using MoonCore.Unix.SecureFs; using MoonlightServers.DaemonShared.DaemonSide.Http.Responses.Servers; +using MoonlightServers.DaemonShared.Enums; namespace MoonlightServers.Daemon.Helpers; @@ -36,49 +42,78 @@ public class ServerFileSystem { var oldPath = Normalize(inputOldPath); var newPath = Normalize(inputNewPath); - + FileSystem.Rename(oldPath, newPath); - + return Task.CompletedTask; } public Task Delete(string inputPath) { var path = Normalize(inputPath); - + FileSystem.RemoveAll(path); - + return Task.CompletedTask; } public Task Mkdir(string inputPath) { var path = Normalize(inputPath); - + FileSystem.MkdirAll(path, FilePermissions.ACCESSPERMS); + + return Task.CompletedTask; + } + + public Task CreateChunk(string inputPath, long totalSize, long positionToSkip, Stream chunkStream) + { + var path = Normalize(inputPath); + + var parentDirectory = Path.GetDirectoryName(path); + + if (!string.IsNullOrEmpty(parentDirectory) && parentDirectory != "/") + FileSystem.MkdirAll(parentDirectory, FilePermissions.ACCESSPERMS); + FileSystem.OpenFileWrite(path, fileStream => + { + if (fileStream.Length != totalSize) + fileStream.SetLength(totalSize); + + fileStream.Position = positionToSkip; + + chunkStream.CopyTo(fileStream); + fileStream.Flush(); + }, OpenFlags.O_CREAT | OpenFlags.O_RDWR); // We use these custom flags to ensure we aren't overwriting the file + return Task.CompletedTask; } public Task Create(string inputPath, Stream dataStream) { var path = Normalize(inputPath); - + var parentDirectory = Path.GetDirectoryName(path); - - if(!string.IsNullOrEmpty(parentDirectory) && parentDirectory != "/") + + if (!string.IsNullOrEmpty(parentDirectory) && parentDirectory != "/") FileSystem.MkdirAll(parentDirectory, FilePermissions.ACCESSPERMS); - - FileSystem.WriteFile(path, dataStream); + + FileSystem.OpenFileWrite(path, stream => + { + stream.Position = 0; + dataStream.CopyTo(stream); + + stream.Flush(); + }); return Task.CompletedTask; } - + public Task Read(string inputPath, Func onHandle) { var path = Normalize(inputPath); - - FileSystem.OpenFile(path, stream => + + FileSystem.OpenFileRead(path, stream => { // No try catch here because the safe fs abstraction already handles every error occuring in the handle onHandle.Invoke(stream).Wait(); @@ -87,6 +122,201 @@ public class ServerFileSystem return Task.CompletedTask; } + #region Compression + + public Task Compress(string[] itemsInput, string destinationInput, CompressType type) + { + var destination = Normalize(destinationInput); + var items = itemsInput.Select(Normalize); + + if (type == CompressType.Zip) + { + FileSystem.OpenFileWrite(destination, stream => + { + using var zipStream = new ZipOutputStream(stream); + + foreach (var item in items) + AddItemToZip(item, zipStream); + + zipStream.Flush(); + stream.Flush(); + + zipStream.Close(); + }); + } + else if (type == CompressType.TarGz) + { + FileSystem.OpenFileWrite(destination, stream => + { + using var gzStream = new GZipOutputStream(stream); + using var tarStream = new TarOutputStream(gzStream, Encoding.UTF8); + + foreach (var item in items) + AddItemToTar(item, tarStream); + + tarStream.Flush(); + gzStream.Flush(); + stream.Flush(); + + tarStream.Close(); + gzStream.Close(); + }); + } + + return Task.CompletedTask; + } + + public Task Decompress(string pathInput, string destinationInput, CompressType type) + { + var path = Normalize(pathInput); + var destination = Normalize(destinationInput); + + if (type == CompressType.Zip) + { + FileSystem.OpenFileRead(path, fileStream => + { + var zipInputStream = new ZipInputStream(fileStream); + + ExtractZip(zipInputStream, destination); + }); + } + else if (type == CompressType.TarGz) + { + FileSystem.OpenFileRead(path, fileStream => + { + var gzInputStream = new GZipInputStream(fileStream); + var zipInputStream = new TarInputStream(gzInputStream, Encoding.UTF8); + + ExtractTar(zipInputStream, destination); + }); + } + + return Task.CompletedTask; + } + + private void AddItemToZip(string path, ZipOutputStream outputStream) + { + var item = FileSystem.Stat(path); + + if (item.IsDirectory) + { + var contents = FileSystem.ReadDir(path); + + foreach (var content in contents) + { + AddItemToZip( + Path.Combine(path, content.Name), + outputStream + ); + } + } + else + { + var entry = new ZipEntry(path) + { + Size = item.Size, + DateTime = item.LastChanged + }; + + outputStream.PutNextEntry(entry); + + FileSystem.OpenFileRead(path, stream => { stream.CopyTo(outputStream); }); + + outputStream.CloseEntry(); + } + } + + private void AddItemToTar(string path, TarOutputStream outputStream) + { + var item = FileSystem.Stat(path); + + if (item.IsDirectory) + { + var contents = FileSystem.ReadDir(path); + + foreach (var content in contents) + { + AddItemToTar( + Path.Combine(path, content.Name), + outputStream + ); + } + } + else + { + var entry = TarEntry.CreateTarEntry(path); + + entry.Name = path; + entry.Size = item.Size; + entry.ModTime = item.LastChanged; + + outputStream.PutNextEntry(entry); + + FileSystem.OpenFileRead(path, stream => + { + stream.CopyTo(outputStream); + }); + + outputStream.CloseEntry(); + } + } + + private void ExtractZip(ZipInputStream inputStream, string destination) + { + while (true) + { + var entry = inputStream.GetNextEntry(); + + if(entry == null || entry.IsDirectory) + break; + + var fileDestination = Path.Combine(destination, entry.Name); + + var parentDirectory = Path.GetDirectoryName(fileDestination); + + if (!string.IsNullOrEmpty(parentDirectory) && parentDirectory != "/") + FileSystem.MkdirAll(parentDirectory, FilePermissions.ACCESSPERMS); + + FileSystem.OpenFileWrite(fileDestination, stream => + { + stream.Position = 0; + + inputStream.CopyTo(stream); + + stream.Flush(); + }); // This will override the file if it exists + } + } + + private void ExtractTar(TarInputStream inputStream, string destination) + { + while (true) + { + var entry = inputStream.GetNextEntry(); + + if(entry == null || entry.IsDirectory) + break; + + var fileDestination = Path.Combine(destination, entry.Name); + + var parentDirectory = Path.GetDirectoryName(fileDestination); + + if (!string.IsNullOrEmpty(parentDirectory) && parentDirectory != "/") + FileSystem.MkdirAll(parentDirectory, FilePermissions.ACCESSPERMS); + + FileSystem.OpenFileWrite(fileDestination, stream => + { + stream.Position = 0; + + inputStream.CopyTo(stream); + + stream.Flush(); + }); // This will override the file if it exists + } + } + + #endregion + private string Normalize(string path) { return path diff --git a/MoonlightServers.Daemon/Http/Controllers/Servers/ServerFileSystemController.cs b/MoonlightServers.Daemon/Http/Controllers/Servers/ServerFileSystemController.cs index 9285490..425b32a 100644 --- a/MoonlightServers.Daemon/Http/Controllers/Servers/ServerFileSystemController.cs +++ b/MoonlightServers.Daemon/Http/Controllers/Servers/ServerFileSystemController.cs @@ -2,6 +2,7 @@ using Microsoft.AspNetCore.Authorization; using Microsoft.AspNetCore.Mvc; using MoonCore.Exceptions; using MoonlightServers.Daemon.Services; +using MoonlightServers.DaemonShared.DaemonSide.Http.Requests; using MoonlightServers.DaemonShared.DaemonSide.Http.Responses.Servers; namespace MoonlightServers.Daemon.Http.Controllers.Servers; @@ -61,4 +62,34 @@ public class ServerFileSystemController : Controller await server.FileSystem.Mkdir(path); } + + [HttpPost("{id:int}/files/compress")] + public async Task Compress([FromRoute] int id, [FromBody] ServerFilesCompressRequest request) + { + var server = ServerService.GetServer(id); + + if (server == null) + throw new HttpApiException("No server with this id found", 404); + + await server.FileSystem.Compress( + request.Items, + request.Destination, + request.Type + ); + } + + [HttpPost("{id:int}/files/decompress")] + public async Task Decompress([FromRoute] int id, [FromBody] ServerFilesDecompressRequest request) + { + var server = ServerService.GetServer(id); + + if (server == null) + throw new HttpApiException("No server with this id found", 404); + + await server.FileSystem.Decompress( + request.Path, + request.Destination, + request.Type + ); + } } \ No newline at end of file diff --git a/MoonlightServers.Daemon/Http/Controllers/Servers/UploadController.cs b/MoonlightServers.Daemon/Http/Controllers/Servers/UploadController.cs index 8556268..437ab83 100644 --- a/MoonlightServers.Daemon/Http/Controllers/Servers/UploadController.cs +++ b/MoonlightServers.Daemon/Http/Controllers/Servers/UploadController.cs @@ -17,6 +17,8 @@ public class UploadController : Controller private readonly AppConfiguration Configuration; private readonly ServerService ServerService; + private readonly long ChunkSize = ByteConverter.FromMegaBytes(20).Bytes; // TODO config + public UploadController( AccessTokenHelper accessTokenHelper, ServerService serverService, @@ -29,15 +31,24 @@ public class UploadController : Controller } [HttpPost] - public async Task Upload([FromQuery] string token) + public async Task Upload( + [FromQuery] string token, + [FromQuery] long totalSize, // TODO: Add limit in config + [FromQuery] int chunkId, + [FromQuery] string path + ) { - var file = Request.Form.Files.FirstOrDefault(); + #region File validation - if (file == null) - throw new HttpApiException("No file provided", 400); + if (Request.Form.Files.Count != 1) + throw new HttpApiException("You need to provide exactly one file", 400); + + var file = Request.Form.Files[0]; - if(file.Length > ByteConverter.FromMegaBytes(Configuration.Files.UploadLimit).Bytes) - throw new HttpApiException("The provided file is bigger than the upload limit", 400); + if (file.Length > ChunkSize) + throw new HttpApiException("The provided data exceeds the chunk size limit", 400); + + #endregion #region Token validation @@ -56,14 +67,30 @@ public class UploadController : Controller #endregion + #region Chunk calculation and validation + + var chunks = totalSize / ChunkSize; + chunks += totalSize % ChunkSize > 0 ? 1 : 0; + + if (chunkId > chunks) + throw new HttpApiException("Invalid chunk id: Out of bounds", 400); + + var positionToSkipTo = ChunkSize * chunkId; + + #endregion + var server = ServerService.GetServer(serverId); if (server == null) throw new HttpApiException("No server with this id found", 404); var dataStream = file.OpenReadStream(); - var path = file.FileName; - await server.FileSystem.Create(path, dataStream); + await server.FileSystem.CreateChunk( + path, + totalSize, + positionToSkipTo, + dataStream + ); } } \ No newline at end of file diff --git a/MoonlightServers.Daemon/MoonlightServers.Daemon.csproj b/MoonlightServers.Daemon/MoonlightServers.Daemon.csproj index 598a111..944475d 100644 --- a/MoonlightServers.Daemon/MoonlightServers.Daemon.csproj +++ b/MoonlightServers.Daemon/MoonlightServers.Daemon.csproj @@ -9,9 +9,10 @@ - - - + + + + diff --git a/MoonlightServers.DaemonShared/DaemonSide/Http/Requests/ServerFilesCompressRequest.cs b/MoonlightServers.DaemonShared/DaemonSide/Http/Requests/ServerFilesCompressRequest.cs new file mode 100644 index 0000000..887eb09 --- /dev/null +++ b/MoonlightServers.DaemonShared/DaemonSide/Http/Requests/ServerFilesCompressRequest.cs @@ -0,0 +1,10 @@ +using MoonlightServers.DaemonShared.Enums; + +namespace MoonlightServers.DaemonShared.DaemonSide.Http.Requests; + +public class ServerFilesCompressRequest +{ + public string Destination { get; set; } + public string[] Items { get; set; } + public CompressType Type { get; set; } +} \ No newline at end of file diff --git a/MoonlightServers.DaemonShared/DaemonSide/Http/Requests/ServerFilesDecompressRequest.cs b/MoonlightServers.DaemonShared/DaemonSide/Http/Requests/ServerFilesDecompressRequest.cs new file mode 100644 index 0000000..d6cb544 --- /dev/null +++ b/MoonlightServers.DaemonShared/DaemonSide/Http/Requests/ServerFilesDecompressRequest.cs @@ -0,0 +1,10 @@ +using MoonlightServers.DaemonShared.Enums; + +namespace MoonlightServers.DaemonShared.DaemonSide.Http.Requests; + +public class ServerFilesDecompressRequest +{ + public string Destination { get; set; } + public string Path { get; set; } + public CompressType Type { get; set; } +} \ No newline at end of file diff --git a/MoonlightServers.DaemonShared/Enums/CompressType.cs b/MoonlightServers.DaemonShared/Enums/CompressType.cs new file mode 100644 index 0000000..3511d14 --- /dev/null +++ b/MoonlightServers.DaemonShared/Enums/CompressType.cs @@ -0,0 +1,7 @@ +namespace MoonlightServers.DaemonShared.Enums; + +public enum CompressType +{ + Zip = 0, + TarGz = 1 +} \ No newline at end of file diff --git a/MoonlightServers.DaemonShared/MoonlightServers.DaemonShared.csproj b/MoonlightServers.DaemonShared/MoonlightServers.DaemonShared.csproj index 9e728c3..97e9cc2 100644 --- a/MoonlightServers.DaemonShared/MoonlightServers.DaemonShared.csproj +++ b/MoonlightServers.DaemonShared/MoonlightServers.DaemonShared.csproj @@ -8,6 +8,7 @@ + diff --git a/MoonlightServers.Frontend/Helpers/ServerFileSystemProvider.cs b/MoonlightServers.Frontend/Helpers/ServerFileSystemProvider.cs index ac98f58..6d37d9b 100644 --- a/MoonlightServers.Frontend/Helpers/ServerFileSystemProvider.cs +++ b/MoonlightServers.Frontend/Helpers/ServerFileSystemProvider.cs @@ -1,20 +1,41 @@ using MoonCore.Blazor.Tailwind.Fm; +using MoonCore.Blazor.Tailwind.Fm.Models; +using MoonCore.Blazor.Tailwind.Services; +using MoonCore.Helpers; using MoonlightServers.Frontend.Services; namespace MoonlightServers.Frontend.Helpers; -public class ServerFileSystemProvider : IFileSystemProvider +public class ServerFileSystemProvider : IFileSystemProvider, ICompressFileSystemProvider { - private readonly int ServerId; + private readonly DownloadService DownloadService; private readonly ServerFileSystemService FileSystemService; + public CompressType[] CompressTypes { get; } = + [ + new() + { + Extension = "zip", + DisplayName = "ZIP Archive" + }, + new() + { + Extension = "tar.gz", + DisplayName = "GZ Compressed Tar Archive" + } + ]; + + private readonly int ServerId; + public ServerFileSystemProvider( int serverId, - ServerFileSystemService fileSystemService + ServerFileSystemService fileSystemService, + DownloadService downloadService ) { ServerId = serverId; FileSystemService = fileSystemService; + DownloadService = downloadService; } public async Task List(string path) @@ -35,7 +56,7 @@ public class ServerFileSystemProvider : IFileSystemProvider public async Task Create(string path, Stream stream) { - await FileSystemService.Upload(ServerId, path, stream); + await Upload(_ => Task.CompletedTask, path, stream); } public async Task Move(string oldPath, string newPath) @@ -55,6 +76,72 @@ public class ServerFileSystemProvider : IFileSystemProvider public async Task Read(string path) { - return await FileSystemService.Download(ServerId, path); + var downloadSession = await FileSystemService.Download(ServerId, path); + + using var httpClient = new HttpClient(); + return await httpClient.GetStreamAsync(downloadSession.DownloadUrl); + } + + public async Task Download(Func updateProgress, string path, string fileName) + { + var downloadSession = await FileSystemService.Download(ServerId, path); + + await DownloadService.DownloadUrl(fileName, downloadSession.DownloadUrl, + async (loaded, total) => + { + var percent = total == 0 ? 0 : (int)Math.Round((float)loaded / total * 100); + await updateProgress.Invoke(percent); + } + ); + } + + public async Task Upload(Func updateProgress, string path, Stream stream) + { + using var httpClient = new HttpClient(); + + var uploadSession = await FileSystemService.Upload(ServerId); + + var size = stream.Length; + var chunkSize = ByteConverter.FromMegaBytes(20).Bytes; + + var chunks = size / chunkSize; + chunks += size % chunkSize > 0 ? 1 : 0; + + for (var chunkId = 0; chunkId < chunks; chunkId++) + { + var percent = (int)Math.Round((chunkId + 1f) / chunks * 100); + await updateProgress.Invoke(percent); + + var buffer = new byte[chunkSize]; + var bytesRead = await stream.ReadAsync(buffer); + + var uploadForm = new MultipartFormDataContent(); + uploadForm.Add(new ByteArrayContent(buffer, 0, bytesRead), "file", "file"); + + await httpClient.PostAsync( + $"{uploadSession.UploadUrl}&totalSize={size}&chunkId={chunkId}&path={path}", + uploadForm + ); + } + } + + public async Task Compress(CompressType type, string path, string[] itemsToCompress) + { + await FileSystemService.Compress( + ServerId, + type.Extension.Replace(".", ""), + itemsToCompress, + path + ); + } + + public async Task Decompress(CompressType type, string path, string destination) + { + await FileSystemService.Decompress( + ServerId, + type.Extension.Replace(".", ""), + path, + destination + ); } } \ No newline at end of file diff --git a/MoonlightServers.Frontend/Services/ServerFileSystemService.cs b/MoonlightServers.Frontend/Services/ServerFileSystemService.cs index 86465ef..cb6f0d2 100644 --- a/MoonlightServers.Frontend/Services/ServerFileSystemService.cs +++ b/MoonlightServers.Frontend/Services/ServerFileSystemService.cs @@ -1,5 +1,7 @@ +using System.Net; using MoonCore.Attributes; using MoonCore.Helpers; +using MoonlightServers.Shared.Http.Requests.Client.Servers.Files; using MoonlightServers.Shared.Http.Responses.Client.Servers.Files; namespace MoonlightServers.Frontend.Services; @@ -42,28 +44,43 @@ public class ServerFileSystemService ); } - public async Task Upload(int serverId, string path, Stream dataStream) + public async Task Upload(int serverId) { - var uploadSession = await ApiClient.GetJson( + return await ApiClient.GetJson( $"api/client/servers/{serverId}/files/upload" ); - - using var httpClient = new HttpClient(); - - var content = new MultipartFormDataContent(); - content.Add(new StreamContent(dataStream), "file", path); - - await httpClient.PostAsync(uploadSession.UploadUrl, content); } - public async Task Download(int serverId, string path) + public async Task Download(int serverId, string path) { - var downloadSession = await ApiClient.GetJson( + return await ApiClient.GetJson( $"api/client/servers/{serverId}/files/download?path={path}" ); + } - using var httpClient = new HttpClient(); - - return await httpClient.GetStreamAsync(downloadSession.DownloadUrl); + public async Task Compress(int serverId, string type, string[] items, string destination) + { + await ApiClient.Post( + $"api/client/servers/{serverId}/files/compress", + new ServerFilesCompressRequest() + { + Type = type, + Items = items, + Destination = destination + } + ); + } + + public async Task Decompress(int serverId, string type, string path, string destination) + { + await ApiClient.Post( + $"api/client/servers/{serverId}/files/decompress", + new ServerFilesDecompressRequest() + { + Type = type, + Path = path, + Destination = destination + } + ); } } \ No newline at end of file diff --git a/MoonlightServers.Frontend/UI/Components/Servers/ServerTabs/FilesTab.razor b/MoonlightServers.Frontend/UI/Components/Servers/ServerTabs/FilesTab.razor index 8b8e008..fd89340 100644 --- a/MoonlightServers.Frontend/UI/Components/Servers/ServerTabs/FilesTab.razor +++ b/MoonlightServers.Frontend/UI/Components/Servers/ServerTabs/FilesTab.razor @@ -1,19 +1,25 @@ @using MoonlightServers.Frontend.Services @using MoonCore.Blazor.Tailwind.Fm +@using MoonCore.Blazor.Tailwind.Services @using MoonlightServers.Frontend.Helpers @inherits BaseServerTab @inject ServerFileSystemService FileSystemService +@inject DownloadService DownloadService - + @code { private IFileSystemProvider Provider; - + protected override void OnInitialized() { - Provider = new ServerFileSystemProvider(Server.Id, FileSystemService); + Provider = new ServerFileSystemProvider( + Server.Id, + FileSystemService, + DownloadService + ); } } diff --git a/MoonlightServers.Frontend/UI/Views/Admin/All/Index.razor b/MoonlightServers.Frontend/UI/Views/Admin/All/Index.razor index 07ed32a..2e472cf 100644 --- a/MoonlightServers.Frontend/UI/Views/Admin/All/Index.razor +++ b/MoonlightServers.Frontend/UI/Views/Admin/All/Index.razor @@ -26,8 +26,10 @@ - + + + diff --git a/MoonlightServers.Frontend/UI/Views/Admin/Nodes/Index.razor b/MoonlightServers.Frontend/UI/Views/Admin/Nodes/Index.razor index b1c24f1..71991ad 100644 --- a/MoonlightServers.Frontend/UI/Views/Admin/Nodes/Index.razor +++ b/MoonlightServers.Frontend/UI/Views/Admin/Nodes/Index.razor @@ -27,8 +27,10 @@ - + + + @@ -103,8 +105,8 @@ + HeaderCss="p-2 font-semibold text-left hidden xl:table-cell" + ColumnCss="p-2 text-left font-normal hidden xl:table-cell">
diff --git a/MoonlightServers.Frontend/UI/Views/Admin/Stars/Index.razor b/MoonlightServers.Frontend/UI/Views/Admin/Stars/Index.razor index 4a9fe5d..cad0d02 100644 --- a/MoonlightServers.Frontend/UI/Views/Admin/Stars/Index.razor +++ b/MoonlightServers.Frontend/UI/Views/Admin/Stars/Index.razor @@ -33,9 +33,11 @@
- + - + + + @@ -43,8 +45,8 @@ - - + +
@@ -68,7 +70,7 @@ Export - + diff --git a/MoonlightServers.Shared/Http/Requests/Client/Servers/Files/ServerFilesCompressRequest.cs b/MoonlightServers.Shared/Http/Requests/Client/Servers/Files/ServerFilesCompressRequest.cs new file mode 100644 index 0000000..a5f8e0d --- /dev/null +++ b/MoonlightServers.Shared/Http/Requests/Client/Servers/Files/ServerFilesCompressRequest.cs @@ -0,0 +1,14 @@ +using System.ComponentModel.DataAnnotations; + +namespace MoonlightServers.Shared.Http.Requests.Client.Servers.Files; + +public class ServerFilesCompressRequest +{ + [Required(ErrorMessage = "You need to specify a type")] + public string Type { get; set; } + + public string[] Items { get; set; } = []; + + [Required(ErrorMessage = "You need to specify a destination")] + public string Destination { get; set; } +} \ No newline at end of file diff --git a/MoonlightServers.Shared/Http/Requests/Client/Servers/Files/ServerFilesDecompressRequest.cs b/MoonlightServers.Shared/Http/Requests/Client/Servers/Files/ServerFilesDecompressRequest.cs new file mode 100644 index 0000000..45fff83 --- /dev/null +++ b/MoonlightServers.Shared/Http/Requests/Client/Servers/Files/ServerFilesDecompressRequest.cs @@ -0,0 +1,15 @@ +using System.ComponentModel.DataAnnotations; + +namespace MoonlightServers.Shared.Http.Requests.Client.Servers.Files; + +public class ServerFilesDecompressRequest +{ + [Required(ErrorMessage = "You need to specify a type")] + public string Type { get; set; } + + [Required(ErrorMessage = "You need to specify a path")] + public string Path { get; set; } + + [Required(ErrorMessage = "You need to specify a destination")] + public string Destination { get; set; } +} \ No newline at end of file