Files
gaseous-server/gaseous-server/Program.cs
Michael Green c8140d7178 Revamp of BIOS handling (#423)
Many of the platforms are similar enough to other platforms that they'll
share the same BIOS files.

The current storage method stores BIOS files per platform, meaning that
files can be duplicated multiple times to satisfy the requirements of
each platform.

This change stores the files as their hash with a .bios extension
(example: `85ad74194e87c08904327de1a9443b7a.bios`) in a flat directory
structure. This allows BIOS files that are used by multiple platforms to
be shared without duplication.
2024-09-15 20:35:36 +10:00

384 lines
12 KiB
C#

using System.Reflection;
using System.Text.Json.Serialization;
using gaseous_server;
using gaseous_server.Classes;
using gaseous_server.Models;
using Microsoft.AspNetCore.Http.Features;
using Microsoft.AspNetCore.Mvc;
using Microsoft.AspNetCore.Server.Kestrel.Core;
using Microsoft.OpenApi.Models;
using Authentication;
using Microsoft.AspNetCore.Identity;
using gaseous_server.Classes.Metadata;
using Asp.Versioning;
Logging.WriteToDiskOnly = true;
Logging.Log(Logging.LogType.Information, "Startup", "Starting Gaseous Server " + Assembly.GetExecutingAssembly().GetName().Version);
Database db = new Database(Database.databaseType.MySql, Config.DatabaseConfiguration.ConnectionStringNoDatabase);
// check db availability
bool dbOnline = false;
do
{
Logging.Log(Logging.LogType.Information, "Startup", "Waiting for database...");
if (db.TestConnection() == true)
{
dbOnline = true;
}
else
{
Thread.Sleep(30000);
}
} while (dbOnline == false);
db = new Database(Database.databaseType.MySql, Config.DatabaseConfiguration.ConnectionString);
// set up db
db.InitDB();
// create relation tables if they don't exist
Storage.CreateRelationsTables<IGDB.Models.Game>();
Storage.CreateRelationsTables<IGDB.Models.Platform>();
// populate db with static data for lookups
AgeRatings.PopulateAgeMap();
// load app settings
Config.InitSettings();
// write updated settings back to the config file
Config.UpdateConfig();
// update default library path
GameLibrary.UpdateDefaultLibraryPath();
// set api metadata source from config
Communications.MetadataSource = Config.MetadataConfiguration.MetadataSource;
// set up hasheous client
HasheousClient.WebApp.HttpHelper.BaseUri = Config.MetadataConfiguration.HasheousHost;
// clean up storage
if (Directory.Exists(Config.LibraryConfiguration.LibraryTempDirectory))
{
Directory.Delete(Config.LibraryConfiguration.LibraryTempDirectory, true);
}
if (Directory.Exists(Config.LibraryConfiguration.LibraryUploadDirectory))
{
Directory.Delete(Config.LibraryConfiguration.LibraryUploadDirectory, true);
}
// kick off any delayed upgrade tasks
// run 1002 background updates in the background on every start
DatabaseMigration.BackgroundUpgradeTargetSchemaVersions.Add(1002);
DatabaseMigration.BackgroundUpgradeTargetSchemaVersions.Add(1023);
// start the task
ProcessQueue.QueueItem queueItem = new ProcessQueue.QueueItem(
ProcessQueue.QueueItemType.BackgroundDatabaseUpgrade,
1,
new List<ProcessQueue.QueueItemType>
{
ProcessQueue.QueueItemType.SignatureIngestor
},
false,
true
);
queueItem.ForceExecute();
ProcessQueue.QueueItems.Add(queueItem);
// set up server
var builder = WebApplication.CreateBuilder(args);
// Add services to the container.
builder.Services.AddControllers().AddJsonOptions(x =>
{
// serialize enums as strings in api responses (e.g. Role)
x.JsonSerializerOptions.Converters.Add(new JsonStringEnumConverter());
// suppress nulls
x.JsonSerializerOptions.DefaultIgnoreCondition = JsonIgnoreCondition.WhenWritingNull;
// set max depth
x.JsonSerializerOptions.MaxDepth = 64;
});
builder.Services.AddResponseCaching();
builder.Services.AddControllers(options =>
{
options.CacheProfiles.Add("None",
new CacheProfile()
{
Duration = 1
});
options.CacheProfiles.Add("Default30",
new CacheProfile()
{
Duration = 30
});
options.CacheProfiles.Add("5Minute",
new CacheProfile()
{
Duration = 300,
Location = ResponseCacheLocation.Any
});
options.CacheProfiles.Add("7Days",
new CacheProfile()
{
Duration = 604800,
Location = ResponseCacheLocation.Any
});
});
builder.Services.AddApiVersioning(config =>
{
config.DefaultApiVersion = new ApiVersion(1, 0);
config.AssumeDefaultVersionWhenUnspecified = true;
config.ReportApiVersions = true;
config.ApiVersionReader = ApiVersionReader.Combine(new UrlSegmentApiVersionReader(),
new HeaderApiVersionReader("x-api-version"),
new MediaTypeApiVersionReader("x-api-version"));
}).AddApiExplorer(setup =>
{
setup.GroupNameFormat = "'v'VVV";
setup.SubstituteApiVersionInUrl = true;
});
// set max upload size
builder.Services.Configure<IISServerOptions>(options =>
{
options.MaxRequestBodySize = int.MaxValue;
});
builder.Services.Configure<KestrelServerOptions>(options =>
{
options.Limits.MaxRequestBodySize = int.MaxValue;
});
builder.Services.Configure<FormOptions>(options =>
{
options.ValueLengthLimit = int.MaxValue;
options.MultipartBodyLengthLimit = int.MaxValue;
options.MultipartHeadersLengthLimit = int.MaxValue;
});
// Learn more about configuring Swagger/OpenAPI at https://aka.ms/aspnetcore/swashbuckle
builder.Services.AddEndpointsApiExplorer();
builder.Services.AddSwaggerGen(options =>
{
options.SwaggerDoc("v1", new OpenApiInfo
{
Version = "v1.0",
Title = "Gaseous Server API",
Description = "An API for managing the Gaseous Server",
TermsOfService = new Uri("https://github.com/gaseous-project/gaseous-server"),
Contact = new OpenApiContact
{
Name = "GitHub Repository",
Url = new Uri("https://github.com/gaseous-project/gaseous-server")
},
License = new OpenApiLicense
{
Name = "Gaseous Server License",
Url = new Uri("https://github.com/gaseous-project/gaseous-server/blob/main/LICENSE")
}
});
options.SwaggerDoc("v1.1", new OpenApiInfo
{
Version = "v1.1",
Title = "Gaseous Server API",
Description = "An API for managing the Gaseous Server",
TermsOfService = new Uri("https://github.com/gaseous-project/gaseous-server"),
Contact = new OpenApiContact
{
Name = "GitHub Repository",
Url = new Uri("https://github.com/gaseous-project/gaseous-server")
},
License = new OpenApiLicense
{
Name = "Gaseous Server License",
Url = new Uri("https://github.com/gaseous-project/gaseous-server/blob/main/LICENSE")
}
});
// using System.Reflection;
var xmlFilename = $"{Assembly.GetExecutingAssembly().GetName().Name}.xml";
options.IncludeXmlComments(Path.Combine(AppContext.BaseDirectory, xmlFilename));
}
);
builder.Services.AddHostedService<TimedHostedService>();
// identity
builder.Services.AddIdentity<ApplicationUser, ApplicationRole>(options =>
{
options.Password.RequireDigit = true;
options.Password.RequireLowercase = true;
options.Password.RequireNonAlphanumeric = false;
options.Password.RequireUppercase = true;
options.Password.RequiredLength = 10;
options.User.AllowedUserNameCharacters = null;
options.User.RequireUniqueEmail = true;
options.SignIn.RequireConfirmedPhoneNumber = false;
options.SignIn.RequireConfirmedEmail = false;
options.SignIn.RequireConfirmedAccount = false;
})
.AddUserStore<UserStore>()
.AddRoleStore<RoleStore>()
.AddDefaultTokenProviders()
.AddDefaultUI()
;
builder.Services.ConfigureApplicationCookie(options =>
{
options.Cookie.Name = "Gaseous.Identity";
options.ExpireTimeSpan = TimeSpan.FromDays(90);
options.SlidingExpiration = true;
options.Cookie.HttpOnly = true;
options.Cookie.SecurePolicy = CookieSecurePolicy.SameAsRequest;
options.Cookie.SameSite = SameSiteMode.Strict;
});
builder.Services.AddScoped<UserStore>();
builder.Services.AddScoped<RoleStore>();
builder.Services.AddTransient<IUserStore<ApplicationUser>, UserStore>();
builder.Services.AddTransient<IRoleStore<ApplicationRole>, RoleStore>();
builder.Services.AddAuthorization(options =>
{
options.AddPolicy("Admin", policy => policy.RequireRole("Admin"));
options.AddPolicy("Gamer", policy => policy.RequireRole("Gamer"));
options.AddPolicy("Player", policy => policy.RequireRole("Player"));
});
// builder.Services.AddControllersWithViews(options =>
// {
// options.Filters.Add(new Microsoft.AspNetCore.Mvc.ValidateAntiForgeryTokenAttribute());
// });
var app = builder.Build();
// Configure the HTTP request pipeline.
//if (app.Environment.IsDevelopment())
//{
app.UseSwagger();
app.UseSwaggerUI(options =>
{
// options.SwaggerEndpoint($"/swagger/v1/swagger.json", "v1.0");
// options.SwaggerEndpoint($"/swagger/v1.1/swagger.json", "v1.1");
var descriptions = app.DescribeApiVersions();
foreach (var description in descriptions)
{
var url = $"/swagger/{description.GroupName}/swagger.json";
var name = description.GroupName.ToUpperInvariant();
options.SwaggerEndpoint(url, name);
}
}
);
//}
//app.UseHttpsRedirection();
app.UseResponseCaching();
// set up system roles
using (var scope = app.Services.CreateScope())
{
var roleManager = scope.ServiceProvider.GetRequiredService<RoleStore>();
var roles = new[] { "Admin", "Gamer", "Player" };
foreach (var role in roles)
{
if (await roleManager.FindByNameAsync(role, CancellationToken.None) == null)
{
ApplicationRole applicationRole = new ApplicationRole();
applicationRole.Name = role;
applicationRole.NormalizedName = role.ToUpper();
await roleManager.CreateAsync(applicationRole, CancellationToken.None);
}
}
}
app.UseAuthorization();
app.UseDefaultFiles();
app.UseStaticFiles(new StaticFileOptions
{
ServeUnknownFileTypes = true, //allow unkown file types also to be served
DefaultContentType = "plain/text" //content type to returned if fileType is not known.
});
app.MapControllers();
app.Use(async (context, next) =>
{
// set the correlation id
string correlationId = Guid.NewGuid().ToString();
CallContext.SetData("CorrelationId", correlationId);
CallContext.SetData("CallingProcess", context.Request.Method + ": " + context.Request.Path);
string userIdentity;
try
{
userIdentity = context.User.Claims.Where(x => x.Type == System.Security.Claims.ClaimTypes.NameIdentifier).FirstOrDefault().Value;
}
catch
{
userIdentity = "";
}
CallContext.SetData("CallingUser", userIdentity);
context.Response.Headers.Add("x-correlation-id", correlationId.ToString());
await next();
});
// setup library directories
Config.LibraryConfiguration.InitLibrary();
// insert unknown platform and game if not present
gaseous_server.Classes.Metadata.Games.GetGame(0, false, false, false);
gaseous_server.Classes.Metadata.Games.AssignAllGamesToPlatformIdZero();
gaseous_server.Classes.Metadata.Platforms.GetPlatform(0);
gaseous_server.Classes.Metadata.Platforms.AssignAllPlatformsToGameIdZero();
// extract platform map if not present
PlatformMapping.ExtractPlatformMap();
// migrate old firmware directory structure to new style
Bios.MigrateToNewFolderStructure();
// add background tasks
ProcessQueue.QueueItems.Add(new ProcessQueue.QueueItem(
ProcessQueue.QueueItemType.SignatureIngestor)
);
ProcessQueue.QueueItems.Add(new ProcessQueue.QueueItem(
ProcessQueue.QueueItemType.TitleIngestor)
);
ProcessQueue.QueueItems.Add(new ProcessQueue.QueueItem(
ProcessQueue.QueueItemType.MetadataRefresh)
);
ProcessQueue.QueueItems.Add(new ProcessQueue.QueueItem(
ProcessQueue.QueueItemType.OrganiseLibrary)
);
ProcessQueue.QueueItems.Add(new ProcessQueue.QueueItem(
ProcessQueue.QueueItemType.LibraryScan)
);
ProcessQueue.QueueItems.Add(new ProcessQueue.QueueItem(
ProcessQueue.QueueItemType.Rematcher)
);
// maintenance tasks
ProcessQueue.QueueItem dailyMaintenance = new ProcessQueue.QueueItem(
ProcessQueue.QueueItemType.DailyMaintainer
);
ProcessQueue.QueueItems.Add(dailyMaintenance);
ProcessQueue.QueueItem weeklyMaintenance = new ProcessQueue.QueueItem(
ProcessQueue.QueueItemType.WeeklyMaintainer
);
ProcessQueue.QueueItems.Add(weeklyMaintenance);
ProcessQueue.QueueItem tempCleanup = new ProcessQueue.QueueItem(
ProcessQueue.QueueItemType.TempCleanup
);
ProcessQueue.QueueItems.Add(tempCleanup);
Logging.WriteToDiskOnly = false;
// start the app
app.Run();