diff --git a/Elwig/App.xaml.cs b/Elwig/App.xaml.cs index a6181aa..f15e698 100644 --- a/Elwig/App.xaml.cs +++ b/Elwig/App.xaml.cs @@ -11,6 +11,7 @@ using System.Windows.Threading; using System.Globalization; using System.Threading; using System.Windows.Markup; +using System.Reflection; namespace Elwig { public partial class App : Application { @@ -20,10 +21,23 @@ namespace Elwig { public static readonly string TempPath = Path.Combine(Path.GetTempPath(), "Elwig"); public static readonly Config Config = new(DataPath + "config.ini"); + public static int VersionMajor { get; private set; } + public static int VersionMinor { get; private set; } + public static int VersionPatch { get; private set; } + public static string Version { + get => $"{VersionMajor}.{VersionMinor}.{VersionPatch}"; + private set { + var p = value.Split(".").Select(p => int.Parse(p.Trim())).ToArray(); + VersionMajor = p.ElementAtOrDefault(0); + VersionMinor = p.ElementAtOrDefault(1); + VersionPatch = p.ElementAtOrDefault(2); + } + } + public static string ZwstId { get; private set; } public static string BranchName { get; private set; } public static int? BranchPlz { get; private set; } - public static string? BranchOrt { get; private set; } + public static string? BranchLocation { get; private set; } public static string? BranchAddress { get; private set; } public static string? BranchPhoneNr { get; private set; } public static string? BranchFaxNr { get; private set; } @@ -36,7 +50,7 @@ namespace Elwig { public App() : base() { System.Text.Encoding.RegisterProvider(System.Text.CodePagesEncodingProvider.Instance); - Directory.CreateDirectory(App.TempPath); + Directory.CreateDirectory(TempPath); Directory.CreateDirectory(DataPath); MainDispatcher = Dispatcher; Scales = Array.Empty(); @@ -56,29 +70,28 @@ namespace Elwig { new FrameworkPropertyMetadata(XmlLanguage.GetLanguage(CultureInfo.CurrentCulture.IetfLanguageTag)) ); + Version = typeof(App).GetTypeInfo().Assembly.GetCustomAttribute()?.InformationalVersion ?? ""; + + try { + AppDbUpdater.CheckDb(); + } catch (Exception e) { + MessageBox.Show($"Invalid Database:\n\n{e.Message}", "Invalid Database", MessageBoxButton.OK, MessageBoxImage.Error); + Shutdown(); + return; + } + Dictionary branches = new(); using (var ctx = new AppDbContext()) { + branches = ctx.Branches.ToDictionary(b => b.Name.ToLower(), b => (b.ZwstId, b.Name, b.PostalDest?.AtPlz?.Plz, b.PostalDest?.AtPlz?.Dest, b.Address, b.PhoneNr, b.FaxNr, b.MobileNr)); try { - if (!ctx.Database.CanConnect()) { - MessageBox.Show($"Invalid Database:\n\n{Config.DatabaseFile}", "Invalid Database", MessageBoxButton.OK, MessageBoxImage.Error); - Shutdown(); - return; - } else { - branches = ctx.Branches.ToDictionary(b => b.Name.ToLower(), b => (b.ZwstId, b.Name, b.PostalDest?.AtPlz?.Plz, b.PostalDest?.AtPlz?.Dest, b.Address, b.PhoneNr, b.FaxNr, b.MobileNr)); - try { - Client = new(ctx); - } catch (Exception e) { - MessageBox.Show($"Fehler beim Laden der Mandantendaten:\n\n{e.Message}", "Fehler", MessageBoxButton.OK, MessageBoxImage.Error); - Shutdown(); - return; - } - } + Client = new(ctx); } catch (Exception e) { - MessageBox.Show($"Invalid Database:\n\n{e.Message}", "Invalid Database", MessageBoxButton.OK, MessageBoxImage.Error); + MessageBox.Show($"Fehler beim Laden der Mandantendaten:\n\n{e.Message}", "Fehler", MessageBoxButton.OK, MessageBoxImage.Error); Shutdown(); return; } } + Utils.RunBackground("HTML Initialization", () => Documents.Html.Init(PrintingReadyChanged)); Utils.RunBackground("PDF Initialization", () => Documents.Pdf.Init(PrintingReadyChanged)); @@ -114,7 +127,7 @@ namespace Elwig { ZwstId = entry.Item1; BranchName = entry.Item2; BranchPlz = entry.Item3; - BranchOrt = entry.Item4; + BranchLocation = entry.Item4; BranchAddress = entry.Item5; BranchPhoneNr = entry.Item6; BranchFaxNr = entry.Item7; @@ -125,7 +138,7 @@ namespace Elwig { ZwstId = entry.Item1; BranchName = entry.Item2; BranchPlz = entry.Item3; - BranchOrt = entry.Item4; + BranchLocation = entry.Item4; BranchAddress = entry.Item5; BranchPhoneNr = entry.Item6; BranchFaxNr = entry.Item7; diff --git a/Elwig/Documents/BusinessDocument.cshtml.cs b/Elwig/Documents/BusinessDocument.cshtml.cs index daeba08..6813833 100644 --- a/Elwig/Documents/BusinessDocument.cshtml.cs +++ b/Elwig/Documents/BusinessDocument.cshtml.cs @@ -13,7 +13,7 @@ namespace Elwig.Documents { public BusinessDocument(string title, Member m, bool includeSender = false) : base(title) { Member = m; - Location = App.BranchName; + Location = App.BranchLocation; IncludeSender = includeSender; var uid = (m.UstIdNr ?? "-") + (m.IsBuchführend ? "" : " (pauschaliert)"); Aside = $"" + diff --git a/Elwig/Helpers/AppDbUpdater.cs b/Elwig/Helpers/AppDbUpdater.cs new file mode 100644 index 0000000..acbde6f --- /dev/null +++ b/Elwig/Helpers/AppDbUpdater.cs @@ -0,0 +1,78 @@ +using Microsoft.Data.Sqlite; +using System; + +namespace Elwig.Helpers { + public static class AppDbUpdater { + + public static readonly int RequiredSchemaVersion = 1; + + private static int _versionOffset = 0; + private static readonly Action[] _updaters = new[] { + UpdateDbSchema_1_To_2, UpdateDbSchema_2_To_3 + }; + + private static void ExecuteNonQuery(SqliteConnection cnx, string sql) { + using var cmd = cnx.CreateCommand(); + cmd.CommandText = sql; + cmd.ExecuteNonQuery(); + } + + private static object? ExecuteScalar(SqliteConnection cnx, string sql) { + using var cmd = cnx.CreateCommand(); + cmd.CommandText = sql; + return cmd.ExecuteScalar(); + } + + public static string CheckDb() { + using var cnx = AppDbContext.Connect(); + + var applId = (long?)ExecuteScalar(cnx, "PRAGMA application_id") ?? 0; + if (applId != 0x454C5747) throw new Exception("Invalid application_id of database"); + + var schemaVers = (long?)ExecuteScalar(cnx, "PRAGMA schema_version") ?? 0; + _versionOffset = (int)(schemaVers % 100); + if (_versionOffset != 0) { + // schema was modified manually/externally + // TODO issue warning + } + UpdateDbSchema(cnx, (int)(schemaVers / 100), RequiredSchemaVersion); + + var userVers = (long?)ExecuteScalar(cnx, "PRAGMA user_version") ?? 0; + var major = userVers >> 24; + var minor = (userVers >> 16) & 0xFF; + var patch = userVers & 0xFFFF; + + if (App.VersionMajor > major || App.VersionMinor > minor || App.VersionPatch > patch) { + long vers = (App.VersionMajor << 24) | (App.VersionMinor << 16) | App.VersionPatch; + ExecuteNonQuery(cnx, $"PRAGMA user_version = {vers}"); + } + + return $"{major}.{minor}.{patch}"; + } + + private static void UpdateDbSchema(SqliteConnection cnx, int fromVersion, int toVersion) { + if (fromVersion == toVersion) { + return; + } else if (fromVersion > toVersion) { + throw new Exception("schema_version of database is too new"); + } else if (toVersion - 1 > _updaters.Length) { + throw new Exception("Unable to update database schema: Updater not implemented"); + } else if (fromVersion <= 0) { + throw new Exception("schema_version of database is invalid"); + } + + ExecuteNonQuery(cnx, "PRAGMA locking_mode = EXCLUSIVE"); + ExecuteNonQuery(cnx, "BEGIN EXCLUSIVE"); + for (int i = fromVersion; i < toVersion; i++) { + _updaters[i - 1](cnx); + } + ExecuteNonQuery(cnx, "COMMIT"); + ExecuteNonQuery(cnx, "VACUUM"); + ExecuteNonQuery(cnx, $"PRAGMA schema_version = {toVersion * 100 + _versionOffset}"); + } + + private static void UpdateDbSchema_1_To_2(SqliteConnection cnx) { } + + private static void UpdateDbSchema_2_To_3(SqliteConnection cnx) { } + } +}