diff --git a/Controllers/HomeController.cs b/Controllers/HomeController.cs index 943ebb0..5264bfa 100644 --- a/Controllers/HomeController.cs +++ b/Controllers/HomeController.cs @@ -3,6 +3,7 @@ using System.Collections.Generic; using System.Diagnostics; using System.IO; using System.IO.IsolatedStorage; +using System.Threading.Tasks; using fxl.codes.kisekae.Models; using fxl.codes.kisekae.Services; using Microsoft.AspNetCore.Http; @@ -13,16 +14,16 @@ namespace fxl.codes.kisekae.Controllers { public class HomeController : Controller { - private readonly FileParserService _fileParserService; + private readonly DatabaseService _databaseService; private readonly ILogger _logger; private readonly ConfigurationReaderService _readerService; private readonly IsolatedStorageFile _storage; - public HomeController(ILogger logger, ConfigurationReaderService readerService, FileParserService fileParserService) + public HomeController(ILogger logger, ConfigurationReaderService readerService, DatabaseService databaseService) { _logger = logger; _readerService = readerService; - _fileParserService = fileParserService; + _databaseService = databaseService; _storage = IsolatedStorageFile.GetUserStoreForApplication(); } @@ -48,13 +49,13 @@ namespace fxl.codes.kisekae.Controllers } [HttpPost] - public IActionResult Upload(IFormFile file) + public async Task Upload(IFormFile file) { _logger.LogTrace($"File uploaded: {file?.FileName}"); if (!(file?.FileName.EndsWith("lzh", StringComparison.InvariantCultureIgnoreCase) ?? false)) throw new Exception("Please select a *.lzh file"); - _fileParserService.UnzipLzh(file); + await _databaseService.StoreToDatabase(file); return Redirect("/"); } diff --git a/Entities/CelDto.cs b/Entities/CelDto.cs new file mode 100644 index 0000000..1616f38 --- /dev/null +++ b/Entities/CelDto.cs @@ -0,0 +1,10 @@ +namespace fxl.codes.kisekae.Entities +{ + public class CelDto : IKisekaeFile, IKisekaeParseable + { + public int Id { get; set; } + public int KisekaeId { get; set; } + public string Filename { get; set; } + public byte[] Data { get; set; } + } +} \ No newline at end of file diff --git a/Entities/ConfigurationDto.cs b/Entities/ConfigurationDto.cs new file mode 100644 index 0000000..c091d71 --- /dev/null +++ b/Entities/ConfigurationDto.cs @@ -0,0 +1,10 @@ +namespace fxl.codes.kisekae.Entities +{ + public class ConfigurationDto : IKisekaeFile + { + public int Id { get; set; } + public string Filename { get; set; } + public string Data { get; set; } + public int KisekaeId { get; set; } + } +} \ No newline at end of file diff --git a/Entities/IKisekaeFile.cs b/Entities/IKisekaeFile.cs new file mode 100644 index 0000000..ac7ffa9 --- /dev/null +++ b/Entities/IKisekaeFile.cs @@ -0,0 +1,14 @@ +namespace fxl.codes.kisekae.Entities +{ + public interface IKisekaeFile + { + int Id { get; set; } + int KisekaeId { get; set; } + string Filename { get; set; } + } + + public interface IKisekaeParseable + { + byte[] Data { get; set; } + } +} \ No newline at end of file diff --git a/Entities/Kisekae.cs b/Entities/KisekaeDto.cs similarity index 63% rename from Entities/Kisekae.cs rename to Entities/KisekaeDto.cs index f23937a..d9ff5f1 100644 --- a/Entities/Kisekae.cs +++ b/Entities/KisekaeDto.cs @@ -1,10 +1,14 @@ +using System.Collections.Generic; + namespace fxl.codes.kisekae.Entities { - public class Kisekae + public class KisekaeDto { public int Id { get; set; } public string Name { get; set; } public string Filename { get; set; } public string Checksum { get; set; } + + public IEnumerable Cels { get; set; } } } \ No newline at end of file diff --git a/Entities/PaletteDto.cs b/Entities/PaletteDto.cs new file mode 100644 index 0000000..02cbb8d --- /dev/null +++ b/Entities/PaletteDto.cs @@ -0,0 +1,11 @@ +namespace fxl.codes.kisekae.Entities +{ + public class PaletteDto : IKisekaeFile, IKisekaeParseable + { + public int Id { get; set; } + public int KisekaeId { get; set; } + public string Filename { get; set; } + public string Comment { get; set; } + public byte[] Data { get; set; } + } +} \ No newline at end of file diff --git a/Services/DatabaseService.cs b/Services/DatabaseService.cs index 9669721..0349e25 100644 --- a/Services/DatabaseService.cs +++ b/Services/DatabaseService.cs @@ -1,9 +1,14 @@ +using System; +using System.Collections.Generic; using System.IO; using System.IO.IsolatedStorage; +using System.Security.Cryptography; +using System.Threading.Tasks; using Dapper; using fxl.codes.kisekae.Entities; using Microsoft.AspNetCore.Http; using Microsoft.Extensions.Configuration; +using Microsoft.Extensions.Logging; using Npgsql; namespace fxl.codes.kisekae.Services @@ -12,28 +17,103 @@ namespace fxl.codes.kisekae.Services { private readonly string _connectionString; private readonly FileParserService _fileParserService; + private readonly ILogger _logger; private readonly IsolatedStorageFile _storage; - public DatabaseService(IConfiguration configuration, FileParserService fileParserService) + public DatabaseService(ILogger logger, IConfiguration configuration, FileParserService fileParserService) { + _logger = logger; _fileParserService = fileParserService; _connectionString = configuration.GetConnectionString("kisekae"); _storage = IsolatedStorageFile.GetUserStoreForApplication(); } - public async void StoreToDatabase(IFormFile file) + public async Task StoreToDatabase(IFormFile file) { await using var connection = new NpgsqlConnection(_connectionString); await connection.OpenAsync(); - var existing = connection.QuerySingle("select * from kisekae where kisekae.filename = @Filename", file); - if (existing != null) return; + var existing = connection.QuerySingleOrDefault("select * from kisekae where kisekae.filename = @Filename", new { Filename = file.FileName }); + if (existing != null) + { + _logger.LogInformation($"Already existing upload {file.FileName} under id {existing.Id}"); + return existing; + } - _fileParserService.UnzipLzh(file); + await using var memoryStream = new MemoryStream(); + await file.CopyToAsync(memoryStream); + var checksum = Convert.ToBase64String(await new SHA256Managed().ComputeHashAsync(memoryStream)); + existing = connection.QuerySingleOrDefault("select * from kisekae where kisekae.checksum = @Checksum", new { Checksum = checksum }); + if (existing != null) + { + _logger.LogInformation($"Already existing upload (checksum: {checksum}) under id {existing.Id} and filename {existing.Filename}"); + return existing; + } + + memoryStream.Position = 0; + + _fileParserService.UnzipLzh(file, memoryStream); var directory = Path.GetFileNameWithoutExtension(file.FileName); + var kisekae = new KisekaeDto + { + Filename = file.FileName, + Name = directory, + Checksum = checksum + }; + + var id = connection.QueryFirst("insert into kisekae(name, filename, checksum) values (@Name, @Filename, @Checksum) returning id", kisekae); + kisekae.Id = id; + + var filenames = _storage.GetFileNames($"{Path.Combine(directory, "*")}"); + var cels = new List(); + var palettes = new List(); + var configurations = new List(); + foreach (var filename in filenames) + { + await using var reader = _storage.OpenFile(Path.Combine(directory, filename), FileMode.Open); + if (!reader.CanRead) throw new InvalidDataException(); + + var bytes = new byte[reader.Length]; + reader.Read(bytes, 0, bytes.Length); + + switch (Path.GetExtension(filename).ToLower()) + { + case ".cel": + cels.Add(new CelDto + { + Filename = filename, + Data = bytes, + KisekaeId = id + }); + break; + case ".cnf": + configurations.Add(new ConfigurationDto + { + Filename = filename, + Data = BitConverter.ToString(bytes), + KisekaeId = id + }); + break; + case ".kcf": + palettes.Add(new PaletteDto + { + Filename = filename, + Data = bytes, + KisekaeId = id + }); + break; + } + } + + await connection.ExecuteAsync("insert into cel (filename, data, kisekae_id) values (@Filename, @Data, @KisekaeId)", cels); + await connection.ExecuteAsync("insert into configuration (filename, data, kisekae_id) values (@Filename, @Data, @KisekaeId)", configurations); + await connection.ExecuteAsync("insert into palette (filename, data, kisekae_id) values (@Filename, @Data, @KisekaeId)", palettes); + await connection.CloseAsync(); + + return kisekae; } } } \ No newline at end of file diff --git a/Services/FileParserService.cs b/Services/FileParserService.cs index a995aea..9986b24 100644 --- a/Services/FileParserService.cs +++ b/Services/FileParserService.cs @@ -28,12 +28,17 @@ namespace fxl.codes.kisekae.Services _storage = IsolatedStorageFile.GetUserStoreForApplication(); } - public void UnzipLzh(IFormFile file) + public void UnzipLzh(IFormFile file, MemoryStream memoryStream = null) { if (_storage.FileExists(file.FileName)) _storage.DeleteFile(file.FileName); - + using var writer = _storage.CreateFile(file.FileName); - file.CopyTo(writer); + + if (memoryStream != null) + memoryStream.CopyTo(writer); + else + file.CopyTo(writer); + writer.Flush(); writer.Close(); @@ -43,7 +48,14 @@ namespace fxl.codes.kisekae.Services .ToString() ?? ""; var directory = Path.GetFileNameWithoutExtension(file.FileName); - if (_storage.DirectoryExists(directory)) _storage.DeleteDirectory(directory); + if (_storage.DirectoryExists(directory)) + { + var allFiles = _storage.GetFileNames(Path.Combine(directory, "*")); + foreach (var existingFile in allFiles) _storage.DeleteFile(Path.Combine(directory, existingFile)); + + _storage.DeleteDirectory(directory); + } + _storage.CreateDirectory(directory); var process = new Process @@ -59,9 +71,11 @@ namespace fxl.codes.kisekae.Services }; if (!process.Start()) throw new InvalidDataException("Unable to call p7zip to extract archive"); - + while (!process.StandardOutput.EndOfStream) _logger.LogTrace(process.StandardOutput.ReadLine()); process.WaitForExit(); + + _storage.DeleteFile(file.FileName); } public void ParsePalette(string directory, PaletteModel palette) diff --git a/Startup.cs b/Startup.cs index a792b87..6422aa4 100644 --- a/Startup.cs +++ b/Startup.cs @@ -35,7 +35,7 @@ namespace fxl.codes.kisekae if (env.IsDevelopment()) { app.UseDeveloperExceptionPage(); - NpgsqlLogManager.Provider = new ConsoleLoggingProvider(NpgsqlLogLevel.Trace); + NpgsqlLogManager.Provider = new ConsoleLoggingProvider(); NpgsqlLogManager.IsParameterLoggingEnabled = true; } else