mirror of
				https://github.com/Kareadita/Kavita.git
				synced 2025-10-30 18:22:29 -04:00 
			
		
		
		
	* Fixed a typo in a log * Invalid XML files now "validate" correctly by sending back a failure. * Cleaned up messaging on backend and frontend to provide some linking on series name when collision, handle corrupt xml files, etc. * When reading list conflict occurs, show the reading list name that's conflicting. Started refactoring the code to allow multiple files to be imported at once. * Started adding new CBL elements for some enhancements I have planned with maintainers. * Default to empty string for IpAddress to allow to fallback into existing experience * Tweaked the layout of reading list page (not complete), moved some not used much controls to page extras and reordered the buttons for reading list * Edit Reading Lists now allows selection of cover image from existing items * Fixed a bug where cover chooser base64 to image would fail to write webp files. * Refactored the validate step to now handle multiple files in one go. * Clean up code * Don't show CBL name if there were xml errors that prevented showing it * Don't allow user to go prev step after they perform the import. * Cleaned up the heading code for accordions * Fixed a bug with import keeping failed items * Sort the failures to the bottom of result windows * CBL import is pretty solid. Need one pass from Robbie on Reading List Page
		
			
				
	
	
		
			203 lines
		
	
	
		
			7.8 KiB
		
	
	
	
		
			C#
		
	
	
	
	
	
			
		
		
	
	
			203 lines
		
	
	
		
			7.8 KiB
		
	
	
	
		
			C#
		
	
	
	
	
	
| using System;
 | |
| using System.IO.Abstractions;
 | |
| using System.Linq;
 | |
| using System.Security.Cryptography;
 | |
| using System.Threading.Tasks;
 | |
| using API.Data;
 | |
| using API.Entities;
 | |
| using API.Entities.Enums;
 | |
| using API.Logging;
 | |
| using API.Services;
 | |
| using API.SignalR;
 | |
| using Kavita.Common;
 | |
| using Kavita.Common.EnvironmentInfo;
 | |
| using Microsoft.AspNetCore.Hosting;
 | |
| using Microsoft.AspNetCore.Identity;
 | |
| using Microsoft.AspNetCore.Server.Kestrel.Core;
 | |
| using Microsoft.EntityFrameworkCore;
 | |
| using Microsoft.Extensions.Configuration;
 | |
| using Microsoft.Extensions.DependencyInjection;
 | |
| using Microsoft.Extensions.Hosting;
 | |
| using Microsoft.Extensions.Logging;
 | |
| using Serilog;
 | |
| using Serilog.Events;
 | |
| using Serilog.Sinks.AspNetCore.SignalR.Extensions;
 | |
| 
 | |
| namespace API;
 | |
| 
 | |
| public class Program
 | |
| {
 | |
|     private static readonly int HttpPort = Configuration.Port;
 | |
| 
 | |
|     protected Program()
 | |
|     {
 | |
|     }
 | |
| 
 | |
|     public static async Task Main(string[] args)
 | |
|     {
 | |
|         Console.OutputEncoding = System.Text.Encoding.UTF8;
 | |
|         Log.Logger = new LoggerConfiguration()
 | |
|             .WriteTo.Console()
 | |
|             .MinimumLevel
 | |
|             .Information()
 | |
|             .CreateBootstrapLogger();
 | |
| 
 | |
|         var directoryService = new DirectoryService(null!, new FileSystem());
 | |
| 
 | |
|         // Before anything, check if JWT has been generated properly or if user still has default
 | |
|         if (!Configuration.CheckIfJwtTokenSet() &&
 | |
|             Environment.GetEnvironmentVariable("ASPNETCORE_ENVIRONMENT") != Environments.Development)
 | |
|         {
 | |
|             Log.Logger.Information("Generating JWT TokenKey for encrypting user sessions...");
 | |
|             var rBytes = new byte[128];
 | |
|             RandomNumberGenerator.Create().GetBytes(rBytes);
 | |
|             Configuration.JwtToken = Convert.ToBase64String(rBytes).Replace("/", string.Empty);
 | |
|         }
 | |
| 
 | |
|         try
 | |
|         {
 | |
|             var host = CreateHostBuilder(args).Build();
 | |
| 
 | |
|             using var scope = host.Services.CreateScope();
 | |
|             var services = scope.ServiceProvider;
 | |
| 
 | |
|             try
 | |
|             {
 | |
|                 var logger = services.GetRequiredService<ILogger<Program>>();
 | |
|                 var context = services.GetRequiredService<DataContext>();
 | |
|                 var pendingMigrations = await context.Database.GetPendingMigrationsAsync();
 | |
|                 if (pendingMigrations.Any())
 | |
|                 {
 | |
|                     logger.LogInformation("Performing backup as migrations are needed. Backup will be kavita.db in temp folder");
 | |
|                     var migrationDirectory = await GetMigrationDirectory(context, directoryService);
 | |
|                     directoryService.ExistOrCreate(migrationDirectory);
 | |
| 
 | |
|                     if (!directoryService.FileSystem.File.Exists(
 | |
|                             directoryService.FileSystem.Path.Join(migrationDirectory, "kavita.db")))
 | |
|                     {
 | |
|                         directoryService.CopyFileToDirectory(directoryService.FileSystem.Path.Join(directoryService.ConfigDirectory, "kavita.db"), migrationDirectory);
 | |
|                         logger.LogInformation("Database backed up to {MigrationDirectory}", migrationDirectory);
 | |
|                     }
 | |
|                 }
 | |
| 
 | |
|                 // This must run before the migration
 | |
|                 try
 | |
|                 {
 | |
|                     await MigrateSeriesRelationsExport.Migrate(context, logger);
 | |
|                 }
 | |
|                 catch (Exception)
 | |
|                 {
 | |
|                     // If fresh install, could fail and we should just carry on as it's not applicable
 | |
|                 }
 | |
| 
 | |
|                 await context.Database.MigrateAsync();
 | |
| 
 | |
|                 await Seed.SeedRoles(services.GetRequiredService<RoleManager<AppRole>>());
 | |
|                 await Seed.SeedSettings(context, directoryService);
 | |
|                 await Seed.SeedThemes(context);
 | |
|                 await Seed.SeedUserApiKeys(context);
 | |
|             }
 | |
|             catch (Exception ex)
 | |
|             {
 | |
|                 var logger = services.GetRequiredService<ILogger<Program>>();
 | |
|                 var context = services.GetRequiredService<DataContext>();
 | |
|                 var migrationDirectory = await GetMigrationDirectory(context, directoryService);
 | |
| 
 | |
|                 logger.LogCritical(ex, "A migration failed during startup. Restoring backup from {MigrationDirectory} and exiting", migrationDirectory);
 | |
|                 directoryService.CopyFileToDirectory(directoryService.FileSystem.Path.Join(migrationDirectory, "kavita.db"), directoryService.ConfigDirectory);
 | |
| 
 | |
|                 return;
 | |
|             }
 | |
| 
 | |
|             // Update the logger with the log level
 | |
|             var unitOfWork = services.GetRequiredService<IUnitOfWork>();
 | |
|             var settings = await unitOfWork.SettingsRepository.GetSettingsDtoAsync();
 | |
|             LogLevelOptions.SwitchLogLevel(settings.LoggingLevel);
 | |
| 
 | |
|             await host.RunAsync();
 | |
|         } catch (Exception ex)
 | |
|         {
 | |
|             Log.Fatal(ex, "Host terminated unexpectedly");
 | |
|         } finally
 | |
|         {
 | |
|             await Log.CloseAndFlushAsync();
 | |
|         }
 | |
|     }
 | |
| 
 | |
|     private static async Task<string> GetMigrationDirectory(DataContext context, IDirectoryService directoryService)
 | |
|     {
 | |
|         string? currentVersion = null;
 | |
|         try
 | |
|         {
 | |
|             if (!await context.ServerSetting.AnyAsync()) return "vUnknown";
 | |
|             currentVersion =
 | |
|                 (await context.ServerSetting.SingleOrDefaultAsync(s =>
 | |
|                     s.Key == ServerSettingKey.InstallVersion))?.Value;
 | |
|         }
 | |
|         catch (Exception)
 | |
|         {
 | |
|             // ignored
 | |
|         }
 | |
| 
 | |
|         if (string.IsNullOrEmpty(currentVersion))
 | |
|         {
 | |
|             currentVersion = "vUnknown";
 | |
|         }
 | |
| 
 | |
|         var migrationDirectory = directoryService.FileSystem.Path.Join(directoryService.TempDirectory,
 | |
|             "migration", currentVersion);
 | |
|         return migrationDirectory;
 | |
|     }
 | |
| 
 | |
|     private static IHostBuilder CreateHostBuilder(string[] args) =>
 | |
|         Host.CreateDefaultBuilder(args)
 | |
|             .UseSerilog((_, services, configuration) =>
 | |
|             {
 | |
|                 LogLevelOptions.CreateConfig(configuration)
 | |
|                     .WriteTo.SignalRSink<LogHub, ILogHub>(
 | |
|                         LogEventLevel.Information,
 | |
|                         services);
 | |
|             })
 | |
|             .ConfigureAppConfiguration((hostingContext, config) =>
 | |
|             {
 | |
|                 config.Sources.Clear();
 | |
| 
 | |
|                 var env = hostingContext.HostingEnvironment;
 | |
| 
 | |
|                 config.AddJsonFile("config/appsettings.json", optional: true, reloadOnChange: false)
 | |
|                     .AddJsonFile($"config/appsettings.{env.EnvironmentName}.json",
 | |
|                         optional: true, reloadOnChange: false);
 | |
|             })
 | |
|             .ConfigureWebHostDefaults(webBuilder =>
 | |
|             {
 | |
|                 webBuilder.UseKestrel((opts) =>
 | |
|                 {
 | |
|                     var ipAddresses = Configuration.IpAddresses;
 | |
|                     if (new OsInfo(Array.Empty<IOsVersionAdapter>()).IsDocker || string.IsNullOrEmpty(ipAddresses) || ipAddresses.Equals(Configuration.DefaultIpAddresses))
 | |
|                     {
 | |
|                         opts.ListenAnyIP(HttpPort, options => { options.Protocols = HttpProtocols.Http1AndHttp2; });
 | |
|                     }
 | |
|                     else
 | |
|                     {
 | |
|                         foreach (var ipAddress in ipAddresses.Split(','))
 | |
|                         {
 | |
|                             try
 | |
|                             {
 | |
|                                 var address = System.Net.IPAddress.Parse(ipAddress.Trim());
 | |
|                                 opts.Listen(address, HttpPort, options => { options.Protocols = HttpProtocols.Http1AndHttp2; });
 | |
|                             }
 | |
|                             catch (Exception ex)
 | |
|                             {
 | |
|                                 Log.Fatal(ex, "Could not parse ip address {IPAddress}", ipAddress);
 | |
|                             }
 | |
|                         }
 | |
|                     }
 | |
|                 });
 | |
| 
 | |
|                     webBuilder.UseStartup<Startup>();
 | |
|             });
 | |
| 
 | |
| 
 | |
| 
 | |
| }
 |