diff --git a/MareSynchronosServer/MareSynchronosServer/Authentication/SecretKeyAuthenticationHandler.cs b/MareSynchronosServer/MareSynchronosServer/Authentication/SecretKeyAuthenticationHandler.cs index f379dce..401117f 100644 --- a/MareSynchronosServer/MareSynchronosServer/Authentication/SecretKeyAuthenticationHandler.cs +++ b/MareSynchronosServer/MareSynchronosServer/Authentication/SecretKeyAuthenticationHandler.cs @@ -1,4 +1,5 @@ using System; +using System.Collections.Concurrent; using System.Collections.Generic; using System.Linq; using System.Security.Claims; @@ -16,6 +17,7 @@ namespace MareSynchronosServer.Authentication { public class SecretKeyAuthenticationHandler : AuthenticationHandler { + public static ConcurrentDictionary IdentificationLocks = new(); private readonly MareDbContext _mareDbContext; public const string AuthScheme = "SecretKeyAuth"; @@ -45,15 +47,26 @@ namespace MareSynchronosServer.Authentication return AuthenticateResult.Fail("Failed Authorization"); } + if (!IdentificationLocks.TryGetValue(charNameHeader, out var lockObject)) + { + lockObject = new(); + IdentificationLocks[charNameHeader] = lockObject; + } + if (user.CharacterIdentification != charNameHeader) { - try + lock (lockObject) { - user.CharacterIdentification = charNameHeader; - _mareDbContext.Users.Update(user); - await _mareDbContext.SaveChangesAsync(); + try + { + user.CharacterIdentification = charNameHeader; + _mareDbContext.Users.Update(user); + _mareDbContext.SaveChanges(); + } + catch (DbUpdateConcurrencyException) + { + } } - catch (DbUpdateConcurrencyException) { } } var claims = new List { diff --git a/MareSynchronosServer/MareSynchronosServer/FileCleanupService.cs b/MareSynchronosServer/MareSynchronosServer/FileCleanupService.cs index 46ca13d..c0fa1ee 100644 --- a/MareSynchronosServer/MareSynchronosServer/FileCleanupService.cs +++ b/MareSynchronosServer/MareSynchronosServer/FileCleanupService.cs @@ -44,12 +44,13 @@ namespace MareSynchronosServer _logger.LogInformation($"Cleaning up files older than {filesOlderThanDays} days"); using var scope = _services.CreateScope(); - var dbContext = scope.ServiceProvider.GetService(); + var dbContext = scope.ServiceProvider.GetService()!; var prevTime = DateTime.Now.Subtract(TimeSpan.FromDays(filesOlderThanDays)); var filesToDelete = dbContext.Files.Where(f => f.LastAccessTime < prevTime); dbContext.RemoveRange(filesToDelete); + dbContext.SaveChanges(); foreach (var file in filesToDelete) { var fileName = Path.Combine(_configuration["CacheDirectory"], file.Hash); @@ -59,9 +60,6 @@ namespace MareSynchronosServer File.Delete(fileName); } } - - dbContext.SaveChanges(); - var allFiles = dbContext.Files; foreach (var file in allFiles) { diff --git a/MareSynchronosServer/MareSynchronosServer/Hubs/ConnectionHub.cs b/MareSynchronosServer/MareSynchronosServer/Hubs/ConnectionHub.cs index 7cf3e9a..35edb0d 100644 --- a/MareSynchronosServer/MareSynchronosServer/Hubs/ConnectionHub.cs +++ b/MareSynchronosServer/MareSynchronosServer/Hubs/ConnectionHub.cs @@ -21,7 +21,7 @@ namespace MareSynchronosServer.Hubs if (userId != null) { - Logger.LogInformation("Heartbeat from " + userId); + Logger.LogInformation("Connection from " + userId); var user = (await DbContext.Users.SingleAsync(u => u.UID == userId)); return new LoggedInUserDto { diff --git a/MareSynchronosServer/MareSynchronosServer/Hubs/FilesHub.cs b/MareSynchronosServer/MareSynchronosServer/Hubs/FilesHub.cs index 204a90c..1a7079d 100644 --- a/MareSynchronosServer/MareSynchronosServer/Hubs/FilesHub.cs +++ b/MareSynchronosServer/MareSynchronosServer/Hubs/FilesHub.cs @@ -42,6 +42,7 @@ namespace MareSynchronosServer.Hubs [Authorize(AuthenticationSchemes = SecretKeyAuthenticationHandler.AuthScheme)] public async Task> SendFiles(List fileListHashes) { + fileListHashes = fileListHashes.Distinct().ToList(); Logger.LogInformation("User " + AuthenticatedUserId + " sending files"); var forbiddenFiles = DbContext.ForbiddenUploadEntries.Where(f => fileListHashes.Contains(f.Hash)); var filesToUpload = new List(); @@ -51,10 +52,11 @@ namespace MareSynchronosServer.Hubs Hash = f.Hash, IsForbidden = true })); - var existingFiles = DbContext.Files.Where(f => fileListHashes.Contains(f.Hash)).ToList(); + fileListHashes.RemoveAll(f => filesToUpload.Any(u => u.Hash == f)); + var existingFiles = DbContext.Files.Where(f => fileListHashes.Contains(f.Hash)); foreach (var file in fileListHashes.Where(f => existingFiles.All(e => e.Hash != f) && filesToUpload.All(u => u.Hash != f))) { - Logger.LogInformation("Needs upload: " + file); + Logger.LogInformation("User " + AuthenticatedUserId + " needs upload: " + file); var userId = AuthenticatedUserId; await DbContext.Files.AddAsync(new FileCache() { @@ -90,9 +92,26 @@ namespace MareSynchronosServer.Hubs var forbiddenFile = DbContext.ForbiddenUploadEntries.SingleOrDefault(f => f.Hash == hash); if (forbiddenFile != null) return; var uploadedFile = new List(); - await foreach (var chunk in fileContent) + try { - uploadedFile.AddRange(chunk); + await foreach (var chunk in fileContent) + { + uploadedFile.AddRange(chunk); + } + } + catch + { + DbContext.Files.Remove(relatedFile); + try + { + await DbContext.SaveChangesAsync(); + } + catch + { + // already removed + } + + return; } Logger.LogInformation("User " + AuthenticatedUserId + " upload finished: " + hash + ", size: " + uploadedFile.Count); @@ -115,6 +134,7 @@ namespace MareSynchronosServer.Hubs relatedFile.Uploaded = true; relatedFile.LastAccessTime = DateTime.Now; await DbContext.SaveChangesAsync(); + Logger.LogInformation("File " + hash + " added to DB"); return; } catch (Exception ex) diff --git a/MareSynchronosServer/MareSynchronosServer/Hubs/UserHub.cs b/MareSynchronosServer/MareSynchronosServer/Hubs/UserHub.cs index 56985ea..0ee6403 100644 --- a/MareSynchronosServer/MareSynchronosServer/Hubs/UserHub.cs +++ b/MareSynchronosServer/MareSynchronosServer/Hubs/UserHub.cs @@ -69,37 +69,11 @@ namespace MareSynchronosServer.Hubs return AuthenticatedUserId; } - [Authorize(AuthenticationSchemes = SecretKeyAuthenticationHandler.AuthScheme)] - public async Task GetCharacterData(Dictionary visibleCharacterWithJobs) - { - var uid = AuthenticatedUserId; - Dictionary ret = new(); - var entriesHavingThisUser = DbContext.ClientPairs - .Include(w => w.User) - .Include(w => w.OtherUser) - .Where(w => w.OtherUser.UID == uid && !w.IsPaused && visibleCharacterWithJobs.Keys.Contains(w.User.CharacterIdentification)) - .ToList(); - foreach (var pair in entriesHavingThisUser) - { - bool isNotPaused = await DbContext.ClientPairs.AnyAsync(w => - !w.IsPaused && w.User.UID == uid && w.OtherUser.UID == pair.User.UID); - if (!isNotPaused) continue; - var dictEntry = visibleCharacterWithJobs[pair.User.CharacterIdentification]; - - var cachedChar = await - DbContext.CharacterData.SingleOrDefaultAsync(c => c.UserId == pair.User.UID && c.JobId == dictEntry); - if (cachedChar != null) - { - await Clients.User(uid).SendAsync("ReceiveCharacterData", cachedChar.CharacterCache, - pair.User.CharacterIdentification); - } - } - } [Authorize(AuthenticationSchemes = SecretKeyAuthenticationHandler.AuthScheme)] public async Task PushCharacterDataToVisibleClients(CharacterCacheDto characterCache, List visibleCharacterIds) { - Logger.LogInformation("User " + AuthenticatedUserId + " pushing character data to visible clients"); + Logger.LogInformation("User " + AuthenticatedUserId + " pushing character data to " + visibleCharacterIds.Count + " visible clients"); var uid = AuthenticatedUserId; var entriesHavingThisUser = DbContext.ClientPairs @@ -166,7 +140,7 @@ namespace MareSynchronosServer.Hubs [Authorize(AuthenticationSchemes = SecretKeyAuthenticationHandler.AuthScheme)] public async Task> GetOnlineCharacters() { - Logger.LogInformation("User " + AuthenticatedUserId + " sent character hash"); + Logger.LogInformation("User " + AuthenticatedUserId + " requested online characters"); var ownUser = DbContext.Users.Single(u => u.UID == AuthenticatedUserId); var otherUsers = await DbContext.ClientPairs diff --git a/MareSynchronosServer/MareSynchronosServer/MareSynchronosServer.csproj b/MareSynchronosServer/MareSynchronosServer/MareSynchronosServer.csproj index c298b5e..3c6d037 100644 --- a/MareSynchronosServer/MareSynchronosServer/MareSynchronosServer.csproj +++ b/MareSynchronosServer/MareSynchronosServer/MareSynchronosServer.csproj @@ -7,6 +7,7 @@ + diff --git a/MareSynchronosServer/MareSynchronosServer/Program.cs b/MareSynchronosServer/MareSynchronosServer/Program.cs index c0fca89..c27f10b 100644 --- a/MareSynchronosServer/MareSynchronosServer/Program.cs +++ b/MareSynchronosServer/MareSynchronosServer/Program.cs @@ -1,8 +1,12 @@ +using System; +using System.IO; using Microsoft.AspNetCore.Hosting; using Microsoft.Extensions.Hosting; using System.Linq; +using System.Reflection; using MareSynchronosServer.Data; using Microsoft.Extensions.DependencyInjection; +using Microsoft.Extensions.Logging; namespace MareSynchronosServer { @@ -36,6 +40,17 @@ namespace MareSynchronosServer .UseConsoleLifetime() .ConfigureWebHostDefaults(webBuilder => { + webBuilder.UseContentRoot(AppContext.BaseDirectory); + webBuilder.ConfigureLogging((ctx, builder) => + { + builder.AddSimpleConsole(options => + { + options.SingleLine = true; + options.TimestampFormat = "yyyy-MM-dd HH:mm:ss "; + }); + builder.AddConfiguration(ctx.Configuration.GetSection("Logging")); + builder.AddFile(o => o.RootPath = AppContext.BaseDirectory); + }); webBuilder.UseStartup(); }); } diff --git a/MareSynchronosServer/MareSynchronosServer/Startup.cs b/MareSynchronosServer/MareSynchronosServer/Startup.cs index d8666ca..f9d490d 100644 --- a/MareSynchronosServer/MareSynchronosServer/Startup.cs +++ b/MareSynchronosServer/MareSynchronosServer/Startup.cs @@ -39,7 +39,6 @@ namespace MareSynchronosServer services.AddSingleton(); - services.AddDbContext(options => { options.UseSqlServer(Configuration.GetConnectionString("DefaultConnection")); diff --git a/MareSynchronosServer/MareSynchronosServer/appsettings.json b/MareSynchronosServer/MareSynchronosServer/appsettings.json index c7b5c17..b388893 100644 --- a/MareSynchronosServer/MareSynchronosServer/appsettings.json +++ b/MareSynchronosServer/MareSynchronosServer/appsettings.json @@ -1,6 +1,6 @@ { "ConnectionStrings": { - "DefaultConnection": "Server=(localdb)\\mssqllocaldb;Database=aspnet-MareSynchronosServer-BA82A12A-0B30-463C-801D-B7E81318CD50;Trusted_Connection=True;MultipleActiveResultSets=true" + "DefaultConnection": "Server=(localdb)\\SQLEXPRESS;Database=mare;Trusted_Connection=True;MultipleActiveResultSets=true" }, "Logging": { "LogLevel": { @@ -9,20 +9,32 @@ "Microsoft.Hosting.Lifetime": "Information", "MareSynchronosServer.Authentication": "Warning", "System.IO.IOException": "Warning" + }, + "File": { + "BasePath": "logs", + "FileAccessMode": "KeepOpenAndAutoFlush", + "FileEncodingName": "utf-8", + "DateFormat": "yyyMMdd", + "MaxFileSize": 10485760, + "Files": [ + { + "Path": "mare-.log" + } + ] } }, - "UnusedFileRetentionPeriodInDays" : 7, - "CacheDirectory": "G:\\ServerTest", // do not delete this key and set it to the path where the files will be stored + "UnusedFileRetentionPeriodInDays": 7, + "CacheDirectory": "G:\\ServerTest", // do not delete this key and set it to the path where the files will be stored "AllowedHosts": "*", "Kestrel": { "Endpoints": { "Https": { - "Url": "https://+:5001", + "Url": "https://+:5000", "Certificate": { "Subject": "darkarchon.internet-box.ch", - "Store": "Root", + "Store": "My", "Location": "LocalMachine", - "AllowInvalid": false + //"AllowInvalid": false // "Path": "", //use path, keypath and password to provide a valid certificate if not using windows key store // "KeyPath": "" // "Password": ""