304 lines
11 KiB
C#
304 lines
11 KiB
C#
using System.IO.Compression;
|
|
using System.Reflection;
|
|
using System.Runtime.Loader;
|
|
using System.Diagnostics;
|
|
using Oqtane.Modules;
|
|
using Oqtane.Services;
|
|
using System.Globalization;
|
|
using System.Text.Json;
|
|
using System.Text.Json.Nodes;
|
|
|
|
namespace Oqtane.Maui;
|
|
|
|
public static class MauiProgram
|
|
{
|
|
public static MauiApp CreateMauiApp()
|
|
{
|
|
var builder = MauiApp.CreateBuilder();
|
|
builder.UseMauiApp<App>()
|
|
.ConfigureFonts(fonts =>
|
|
{
|
|
fonts.AddFont("OpenSans-Regular.ttf", "OpenSansRegular");
|
|
});
|
|
|
|
builder.Services.AddMauiBlazorWebView();
|
|
#if DEBUG
|
|
builder.Services.AddBlazorWebViewDeveloperTools();
|
|
#endif
|
|
|
|
var apiurl = LoadAppSettings();
|
|
|
|
if (!string.IsNullOrEmpty(apiurl))
|
|
{
|
|
var httpClient = new HttpClient { BaseAddress = new Uri(GetBaseUrl(apiurl)) };
|
|
httpClient.DefaultRequestHeaders.UserAgent.ParseAdd(Shared.Constants.MauiUserAgent);
|
|
httpClient.DefaultRequestHeaders.Add(Shared.Constants.MauiAliasPath, GetUrlPath(apiurl).Replace("/", ""));
|
|
builder.Services.AddSingleton(httpClient);
|
|
builder.Services.AddHttpClient(); // IHttpClientFactory for calling remote services via RemoteServiceBase
|
|
|
|
// dynamically load client assemblies
|
|
LoadClientAssemblies(httpClient, apiurl);
|
|
}
|
|
|
|
// register localization services
|
|
builder.Services.AddLocalization(options => options.ResourcesPath = "Resources");
|
|
|
|
// register auth services
|
|
builder.Services.AddOqtaneAuthentication();
|
|
|
|
// register scoped core services
|
|
builder.Services.AddOqtaneClientScopedServices();
|
|
|
|
var assemblies = AppDomain.CurrentDomain.GetOqtaneAssemblies();
|
|
foreach (var assembly in assemblies)
|
|
{
|
|
// dynamically register module services
|
|
RegisterModuleServices(assembly, builder.Services);
|
|
|
|
// register client startup services
|
|
RegisterClientStartups(assembly, builder.Services);
|
|
}
|
|
|
|
return builder.Build();
|
|
}
|
|
|
|
|
|
private static string LoadAppSettings()
|
|
{
|
|
var url = MauiConstants.ApiUrl;
|
|
if (MauiConstants.UseAppSettings)
|
|
{
|
|
string file = Path.Combine(FileSystem.Current.AppDataDirectory, "appsettings.json");
|
|
if (File.Exists(file))
|
|
{
|
|
using FileStream stream = File.OpenRead(file);
|
|
using StreamReader reader = new StreamReader(stream);
|
|
var content = reader.ReadToEnd();
|
|
var obj = JsonSerializer.Deserialize<JsonObject>(content)!;
|
|
if (!string.IsNullOrEmpty((string)obj["Url"]))
|
|
{
|
|
url = (string)obj["Url"];
|
|
}
|
|
}
|
|
else
|
|
{
|
|
// create template appsettings.json file
|
|
using (StreamWriter writer = File.CreateText(file))
|
|
{
|
|
writer.WriteLine("{ \"Url\": \"\" }");
|
|
}
|
|
}
|
|
}
|
|
return url;
|
|
}
|
|
|
|
private static void LoadClientAssemblies(HttpClient http, string apiurl)
|
|
{
|
|
try
|
|
{
|
|
// ensure local assembly folder exists
|
|
string folder = Path.Combine(FileSystem.Current.AppDataDirectory, "oqtane");
|
|
if (!Directory.Exists(folder))
|
|
{
|
|
Directory.CreateDirectory(folder);
|
|
}
|
|
|
|
var dlls = new Dictionary<string, byte[]>();
|
|
var pdbs = new Dictionary<string, byte[]>();
|
|
var list = new List<string>();
|
|
|
|
var files = new List<string>();
|
|
foreach (var file in Directory.EnumerateFiles(folder, "*.dll", SearchOption.AllDirectories))
|
|
{
|
|
files.Add(file.Substring(folder.Length + 1).Replace("\\", "/"));
|
|
}
|
|
|
|
if (files.Count() != 0)
|
|
{
|
|
// get list of assemblies from server
|
|
var json = Task.Run(() => http.GetStringAsync($"{GetUrlPath(apiurl)}api/Installation/list")).GetAwaiter().GetResult();
|
|
var assemblies = JsonSerializer.Deserialize<List<string>>(json);
|
|
|
|
// determine which assemblies need to be downloaded
|
|
foreach (var assembly in assemblies)
|
|
{
|
|
var file = files.FirstOrDefault(item => item.Contains(assembly));
|
|
if (file == null)
|
|
{
|
|
list.Add(assembly);
|
|
}
|
|
else
|
|
{
|
|
// check if newer version available
|
|
if (GetFileDate(assembly) > GetFileDate(file))
|
|
{
|
|
list.Add(assembly);
|
|
}
|
|
}
|
|
}
|
|
|
|
// get assemblies already downloaded
|
|
foreach (var file in files)
|
|
{
|
|
if (assemblies.Contains(file) && !list.Contains(file))
|
|
{
|
|
try
|
|
{
|
|
dlls.Add(file, File.ReadAllBytes(Path.Combine(folder, file)));
|
|
var pdb = file.Replace(".dll", ".pdb");
|
|
if (File.Exists(Path.Combine(folder, pdb)))
|
|
{
|
|
pdbs.Add(pdb, File.ReadAllBytes(Path.Combine(folder, pdb)));
|
|
}
|
|
}
|
|
catch
|
|
{
|
|
// ignore
|
|
}
|
|
}
|
|
else // file is deprecated
|
|
{
|
|
try
|
|
{
|
|
foreach (var path in Directory.EnumerateFiles(folder, Path.GetFileNameWithoutExtension(file) + ".*"))
|
|
{
|
|
File.Delete(path);
|
|
}
|
|
}
|
|
catch
|
|
{
|
|
// ignore
|
|
}
|
|
}
|
|
}
|
|
}
|
|
else
|
|
{
|
|
list.Add("*");
|
|
}
|
|
|
|
if (list.Count != 0)
|
|
{
|
|
// get assemblies from server
|
|
var zip = Task.Run(() => http.GetByteArrayAsync($"{GetUrlPath(apiurl)}api/Installation/load?list=" + string.Join(",", list))).GetAwaiter().GetResult();
|
|
|
|
// asemblies and debug symbols are packaged in a zip file
|
|
using (ZipArchive archive = new ZipArchive(new MemoryStream(zip)))
|
|
{
|
|
foreach (ZipArchiveEntry entry in archive.Entries)
|
|
{
|
|
using (var memoryStream = new MemoryStream())
|
|
{
|
|
entry.Open().CopyTo(memoryStream);
|
|
byte[] file = memoryStream.ToArray();
|
|
|
|
// save assembly to local folder
|
|
try
|
|
{
|
|
using var stream = File.Create(Path.Combine(folder, entry.FullName));
|
|
stream.Write(file, 0, file.Length);
|
|
}
|
|
catch
|
|
{
|
|
// ignore
|
|
}
|
|
|
|
if (Path.GetExtension(entry.FullName) == ".dll")
|
|
{
|
|
dlls.Add(entry.FullName, file);
|
|
}
|
|
else
|
|
{
|
|
pdbs.Add(entry.FullName, file);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
// load assemblies into app domain
|
|
foreach (var item in dlls)
|
|
{
|
|
if (pdbs.ContainsKey(item.Key.Replace(".dll", ".pdb")))
|
|
{
|
|
AssemblyLoadContext.Default.LoadFromStream(new MemoryStream(item.Value), new MemoryStream(pdbs[item.Key.Replace(".dll", ".pdb")]));
|
|
}
|
|
else
|
|
{
|
|
AssemblyLoadContext.Default.LoadFromStream(new MemoryStream(item.Value));
|
|
}
|
|
}
|
|
}
|
|
catch (Exception ex)
|
|
{
|
|
Debug.WriteLine($"Error Loading Client Assemblies From {apiurl} - {ex}");
|
|
}
|
|
}
|
|
|
|
private static DateTime GetFileDate(string filepath)
|
|
{
|
|
var segments = filepath.Split('.');
|
|
return DateTime.ParseExact(segments[segments.Length - 2], "yyyyMMddHHmmss", CultureInfo.InvariantCulture);
|
|
}
|
|
|
|
private static void RegisterModuleServices(Assembly assembly, IServiceCollection services)
|
|
{
|
|
// dynamically register module scoped services
|
|
try
|
|
{
|
|
var implementationTypes = assembly.GetInterfaces<IService>();
|
|
foreach (var implementationType in implementationTypes)
|
|
{
|
|
if (implementationType.AssemblyQualifiedName != null)
|
|
{
|
|
var serviceType = Type.GetType(implementationType.AssemblyQualifiedName.Replace(implementationType.Name, $"I{implementationType.Name}"));
|
|
services.AddScoped(serviceType ?? implementationType, implementationType);
|
|
}
|
|
}
|
|
|
|
implementationTypes = assembly.GetInterfaces<IClientService>();
|
|
foreach (var implementationType in implementationTypes)
|
|
{
|
|
if (implementationType.AssemblyQualifiedName != null)
|
|
{
|
|
var serviceType = Type.GetType(implementationType.AssemblyQualifiedName.Replace(implementationType.Name, $"I{implementationType.Name}"));
|
|
services.AddScoped(serviceType ?? implementationType, implementationType);
|
|
}
|
|
}
|
|
}
|
|
catch
|
|
{
|
|
// could not interrogate assembly - likely missing dependencies
|
|
}
|
|
}
|
|
|
|
private static void RegisterClientStartups(Assembly assembly, IServiceCollection services)
|
|
{
|
|
try
|
|
{
|
|
var startUps = assembly.GetInstances<IClientStartup>();
|
|
foreach (var startup in startUps)
|
|
{
|
|
startup.ConfigureServices(services);
|
|
}
|
|
}
|
|
catch
|
|
{
|
|
// could not interrogate assembly - likely missing dependencies
|
|
}
|
|
}
|
|
|
|
private static string GetBaseUrl(string url)
|
|
{
|
|
var uri = new Uri(url);
|
|
return uri.Scheme + "://"+ uri.Authority + "/";
|
|
}
|
|
|
|
private static string GetUrlPath(string url)
|
|
{
|
|
var path = new Uri(url).AbsolutePath.Substring(1);
|
|
path = (!string.IsNullOrEmpty(path) && !path.EndsWith("/")) ? path + "/" : path;
|
|
return path;
|
|
}
|
|
}
|