Add forced update check against Gitea releases.
Block startup when a newer Release exists; keep offline pharmacies working. Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
parent
09535e9b02
commit
b69e1b4498
24
docs/gitea-updates.md
Normal file
24
docs/gitea-updates.md
Normal file
@ -0,0 +1,24 @@
|
||||
# Gitea + принудительные обновления десктопа
|
||||
|
||||
Полная инструкция по установке Gitea: в репозитории `source` → `docs/gitea/`.
|
||||
|
||||
Клиент при старте (`ElectroPharmacy_Load`) вызывает Gitea API:
|
||||
|
||||
`GET {UpdateCheckBaseUrl}/api/v1/repos/{UpdateRepo}/releases/latest`
|
||||
|
||||
Настройки (можно сменить без пересборки через user settings):
|
||||
|
||||
| Ключ | По умолчанию |
|
||||
|------|----------------|
|
||||
| `UpdateCheckBaseUrl` | `https://git.24pharmdata.ru` |
|
||||
| `UpdateRepo` | `pharmdata/elfisa-pharmacy` |
|
||||
| `UpdateApiToken` | пусто (нужен для private releases) |
|
||||
|
||||
## Регламент релиза
|
||||
|
||||
1. Поднять `AssemblyVersion` / `AssemblyFileVersion` в `Properties/AssemblyInfo.cs`.
|
||||
2. `git tag vX.Y.Z && git push gitea master --tags`
|
||||
3. `powershell -File dist\build-release.ps1`
|
||||
4. Gitea → Releases → New Release → прикрепить ZIP.
|
||||
|
||||
Старый клиент с меньшей версией увидит модалку «Доступно новое обновление» и не пустит в работу.
|
||||
@ -42,6 +42,15 @@
|
||||
<setting name="InvoiceExportPath" serializeAs="String">
|
||||
<value />
|
||||
</setting>
|
||||
<setting name="UpdateCheckBaseUrl" serializeAs="String">
|
||||
<value>https://git.24pharmdata.ru</value>
|
||||
</setting>
|
||||
<setting name="UpdateRepo" serializeAs="String">
|
||||
<value>pharmdata/elfisa-pharmacy</value>
|
||||
</setting>
|
||||
<setting name="UpdateApiToken" serializeAs="String">
|
||||
<value />
|
||||
</setting>
|
||||
</Электронная_Фармация.Properties.Settings>
|
||||
</userSettings>
|
||||
</configuration>
|
||||
@ -10,6 +10,8 @@ namespace Электронная_Фармация.Classes
|
||||
public static class AppConfig
|
||||
{
|
||||
public const string DefaultApiBaseUrl = "https://24pharmdata.ru";
|
||||
public const string DefaultUpdateCheckBaseUrl = "https://git.24pharmdata.ru";
|
||||
public const string DefaultUpdateRepo = "pharmdata/elfisa-pharmacy";
|
||||
private const string LegacyApiBaseUrl = "http://195.34.241.84:9988";
|
||||
private const string ApiBaseUrlEnvVar = "ELF_API_BASE_URL";
|
||||
private static bool _sqliteInteropPrepared;
|
||||
@ -126,6 +128,69 @@ namespace Электронная_Фармация.Classes
|
||||
Settings.Default.Save();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gitea base URL for forced update checks (no trailing slash).
|
||||
/// </summary>
|
||||
public static string UpdateCheckBaseUrl
|
||||
{
|
||||
get
|
||||
{
|
||||
var fromEnv = Environment.GetEnvironmentVariable("ELF_UPDATE_CHECK_BASE_URL");
|
||||
if (!string.IsNullOrWhiteSpace(fromEnv))
|
||||
{
|
||||
return NormalizeApiBaseUrl(fromEnv);
|
||||
}
|
||||
|
||||
var fromSettings = Settings.Default.UpdateCheckBaseUrl;
|
||||
if (!string.IsNullOrWhiteSpace(fromSettings))
|
||||
{
|
||||
return NormalizeApiBaseUrl(fromSettings);
|
||||
}
|
||||
|
||||
return DefaultUpdateCheckBaseUrl;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// owner/repo on Gitea, e.g. pharmdata/elfisa-pharmacy.
|
||||
/// </summary>
|
||||
public static string UpdateRepo
|
||||
{
|
||||
get
|
||||
{
|
||||
var fromEnv = Environment.GetEnvironmentVariable("ELF_UPDATE_REPO");
|
||||
if (!string.IsNullOrWhiteSpace(fromEnv))
|
||||
{
|
||||
return fromEnv.Trim().Trim('/');
|
||||
}
|
||||
|
||||
var fromSettings = Settings.Default.UpdateRepo;
|
||||
if (!string.IsNullOrWhiteSpace(fromSettings))
|
||||
{
|
||||
return fromSettings.Trim().Trim('/');
|
||||
}
|
||||
|
||||
return DefaultUpdateRepo;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Optional read-only Gitea token for private release repos.
|
||||
/// </summary>
|
||||
public static string UpdateApiToken
|
||||
{
|
||||
get
|
||||
{
|
||||
var fromEnv = Environment.GetEnvironmentVariable("ELF_UPDATE_API_TOKEN");
|
||||
if (!string.IsNullOrWhiteSpace(fromEnv))
|
||||
{
|
||||
return fromEnv.Trim();
|
||||
}
|
||||
|
||||
return Settings.Default.UpdateApiToken ?? string.Empty;
|
||||
}
|
||||
}
|
||||
|
||||
private static void EnsureSQLiteInteropInBaseDir()
|
||||
{
|
||||
// System.Data.SQLite ожидает SQLite.Interop.dll рядом с exe.
|
||||
|
||||
302
src/ElectronicPharmacy/Classes/UpdateChecker.cs
Normal file
302
src/ElectronicPharmacy/Classes/UpdateChecker.cs
Normal file
@ -0,0 +1,302 @@
|
||||
using System;
|
||||
using System.Linq;
|
||||
using System.Net.Http;
|
||||
using System.Net.Http.Headers;
|
||||
using System.Reflection;
|
||||
using System.Text.Json;
|
||||
using System.Text.Json.Serialization;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace Электронная_Фармация.Classes
|
||||
{
|
||||
public sealed class UpdateCheckResult
|
||||
{
|
||||
public bool CheckedOk { get; set; }
|
||||
public bool UpdateRequired { get; set; }
|
||||
public string LocalVersion { get; set; }
|
||||
public string LatestVersion { get; set; }
|
||||
public string ReleaseUrl { get; set; }
|
||||
public string DownloadUrl { get; set; }
|
||||
public string ErrorMessage { get; set; }
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Checks Gitea Releases API for a newer desktop build.
|
||||
/// </summary>
|
||||
public static class UpdateChecker
|
||||
{
|
||||
private static readonly JsonSerializerOptions JsonOptions = new JsonSerializerOptions
|
||||
{
|
||||
PropertyNameCaseInsensitive = true
|
||||
};
|
||||
|
||||
public static UpdateCheckResult CheckForUpdate()
|
||||
{
|
||||
return CheckForUpdate(null, null, null, null);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Overload for tests / smoke scripts (null = use AppConfig).
|
||||
/// </summary>
|
||||
public static UpdateCheckResult CheckForUpdate(
|
||||
string baseUrl,
|
||||
string repo,
|
||||
string token,
|
||||
string localVersionOverride)
|
||||
{
|
||||
try
|
||||
{
|
||||
return CheckForUpdateAsync(baseUrl, repo, token, localVersionOverride)
|
||||
.GetAwaiter().GetResult();
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
AppDebugLog.Info("UpdateChecker", $"Проверка обновлений пропущена: {ex.Message}");
|
||||
return new UpdateCheckResult
|
||||
{
|
||||
CheckedOk = false,
|
||||
UpdateRequired = false,
|
||||
LocalVersion = localVersionOverride ?? GetLocalVersionString(),
|
||||
ErrorMessage = ex.Message
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
public static async Task<UpdateCheckResult> CheckForUpdateAsync()
|
||||
{
|
||||
return await CheckForUpdateAsync(null, null, null, null).ConfigureAwait(false);
|
||||
}
|
||||
|
||||
public static async Task<UpdateCheckResult> CheckForUpdateAsync(
|
||||
string baseUrlOverride,
|
||||
string repoOverride,
|
||||
string tokenOverride,
|
||||
string localVersionOverride)
|
||||
{
|
||||
var localVersion = string.IsNullOrWhiteSpace(localVersionOverride)
|
||||
? GetLocalVersionString()
|
||||
: NormalizeVersion(localVersionOverride);
|
||||
var result = new UpdateCheckResult
|
||||
{
|
||||
LocalVersion = localVersion
|
||||
};
|
||||
|
||||
var baseUrl = (baseUrlOverride ?? AppConfig.UpdateCheckBaseUrl).TrimEnd('/');
|
||||
var repo = (repoOverride ?? AppConfig.UpdateRepo).Trim('/');
|
||||
if (string.IsNullOrWhiteSpace(baseUrl) || string.IsNullOrWhiteSpace(repo))
|
||||
{
|
||||
result.CheckedOk = false;
|
||||
result.ErrorMessage = "UpdateCheckBaseUrl/UpdateRepo не заданы";
|
||||
return result;
|
||||
}
|
||||
|
||||
var url = $"{baseUrl}/api/v1/repos/{repo}/releases/latest";
|
||||
using (var client = new HttpClient { Timeout = TimeSpan.FromSeconds(8) })
|
||||
{
|
||||
var token = tokenOverride ?? AppConfig.UpdateApiToken;
|
||||
if (!string.IsNullOrWhiteSpace(token))
|
||||
{
|
||||
client.DefaultRequestHeaders.Authorization =
|
||||
new AuthenticationHeaderValue("token", token.Trim());
|
||||
}
|
||||
|
||||
client.DefaultRequestHeaders.Accept.Add(
|
||||
new MediaTypeWithQualityHeaderValue("application/json"));
|
||||
|
||||
AppDebugLog.Info("UpdateChecker", $"GET {url}");
|
||||
HttpResponseMessage response;
|
||||
try
|
||||
{
|
||||
response = await client.GetAsync(url).ConfigureAwait(false);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
// Offline pharmacies must continue working.
|
||||
AppDebugLog.Info("UpdateChecker", $"Сеть недоступна, обновление не блокируем: {ex.Message}");
|
||||
result.CheckedOk = false;
|
||||
result.ErrorMessage = ex.Message;
|
||||
return result;
|
||||
}
|
||||
|
||||
var body = await response.Content.ReadAsStringAsync().ConfigureAwait(false);
|
||||
if (!response.IsSuccessStatusCode)
|
||||
{
|
||||
AppDebugLog.Info("UpdateChecker",
|
||||
$"releases/latest HTTP {(int)response.StatusCode}: {Truncate(body, 200)}");
|
||||
result.CheckedOk = false;
|
||||
result.ErrorMessage = $"HTTP {(int)response.StatusCode}";
|
||||
return result;
|
||||
}
|
||||
|
||||
var release = JsonSerializer.Deserialize<GiteaRelease>(body, JsonOptions);
|
||||
if (release == null || string.IsNullOrWhiteSpace(release.TagName))
|
||||
{
|
||||
result.CheckedOk = false;
|
||||
result.ErrorMessage = "Пустой ответ releases/latest";
|
||||
return result;
|
||||
}
|
||||
|
||||
var latest = NormalizeVersion(release.TagName);
|
||||
result.LatestVersion = latest;
|
||||
result.ReleaseUrl = string.IsNullOrWhiteSpace(release.HtmlUrl)
|
||||
? $"{baseUrl}/{repo}/releases/tag/{release.TagName}"
|
||||
: release.HtmlUrl;
|
||||
result.DownloadUrl = PickZipAssetUrl(release) ?? result.ReleaseUrl;
|
||||
result.CheckedOk = true;
|
||||
result.UpdateRequired = IsRemoteNewer(latest, localVersion);
|
||||
|
||||
AppDebugLog.Info("UpdateChecker",
|
||||
$"Локально={localVersion}, latest={latest}, updateRequired={result.UpdateRequired}");
|
||||
return result;
|
||||
}
|
||||
}
|
||||
|
||||
public static string GetLocalVersionString()
|
||||
{
|
||||
try
|
||||
{
|
||||
var asm = Assembly.GetExecutingAssembly();
|
||||
var file = asm.GetCustomAttribute<AssemblyFileVersionAttribute>()?.Version;
|
||||
if (!string.IsNullOrWhiteSpace(file))
|
||||
{
|
||||
return NormalizeVersion(file);
|
||||
}
|
||||
|
||||
return NormalizeVersion(asm.GetName().Version?.ToString() ?? "0.0.0.0");
|
||||
}
|
||||
catch
|
||||
{
|
||||
return "0.0.0.0";
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Returns true when remoteTag is strictly newer than localVersion.
|
||||
/// Accepts "v1.0.9", "1.0.9", "1.0.9.0".
|
||||
/// </summary>
|
||||
public static bool IsRemoteNewer(string remoteTag, string localVersion)
|
||||
{
|
||||
if (!TryParseVersion(remoteTag, out var remote) ||
|
||||
!TryParseVersion(localVersion, out var local))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
return remote > local;
|
||||
}
|
||||
|
||||
public static string NormalizeVersion(string value)
|
||||
{
|
||||
if (string.IsNullOrWhiteSpace(value))
|
||||
{
|
||||
return "0.0.0.0";
|
||||
}
|
||||
|
||||
value = value.Trim();
|
||||
if (value.StartsWith("v", StringComparison.OrdinalIgnoreCase))
|
||||
{
|
||||
value = value.Substring(1);
|
||||
}
|
||||
|
||||
// Drop pre-release suffix: 1.0.9-beta -> 1.0.9
|
||||
var dash = value.IndexOf('-');
|
||||
if (dash > 0)
|
||||
{
|
||||
value = value.Substring(0, dash);
|
||||
}
|
||||
|
||||
if (TryParseVersion(value, out var v))
|
||||
{
|
||||
return v.ToString();
|
||||
}
|
||||
|
||||
return value;
|
||||
}
|
||||
|
||||
public static bool TryParseVersion(string value, out Version version)
|
||||
{
|
||||
version = null;
|
||||
if (string.IsNullOrWhiteSpace(value))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
value = value.Trim();
|
||||
if (value.StartsWith("v", StringComparison.OrdinalIgnoreCase))
|
||||
{
|
||||
value = value.Substring(1);
|
||||
}
|
||||
|
||||
var dash = value.IndexOf('-');
|
||||
if (dash > 0)
|
||||
{
|
||||
value = value.Substring(0, dash);
|
||||
}
|
||||
|
||||
var parts = value.Split('.');
|
||||
if (parts.Length == 1)
|
||||
{
|
||||
value = value + ".0.0.0";
|
||||
}
|
||||
else if (parts.Length == 2)
|
||||
{
|
||||
value = value + ".0.0";
|
||||
}
|
||||
else if (parts.Length == 3)
|
||||
{
|
||||
value = value + ".0";
|
||||
}
|
||||
|
||||
return Version.TryParse(value, out version);
|
||||
}
|
||||
|
||||
private static string PickZipAssetUrl(GiteaRelease release)
|
||||
{
|
||||
if (release?.Assets == null || release.Assets.Length == 0)
|
||||
{
|
||||
return null;
|
||||
}
|
||||
|
||||
var zip = release.Assets.FirstOrDefault(a =>
|
||||
!string.IsNullOrWhiteSpace(a?.Name) &&
|
||||
a.Name.EndsWith(".zip", StringComparison.OrdinalIgnoreCase));
|
||||
if (zip == null)
|
||||
{
|
||||
zip = release.Assets.FirstOrDefault(a => !string.IsNullOrWhiteSpace(a?.BrowserDownloadUrl));
|
||||
}
|
||||
|
||||
return zip?.BrowserDownloadUrl;
|
||||
}
|
||||
|
||||
private static string Truncate(string s, int max)
|
||||
{
|
||||
if (string.IsNullOrEmpty(s) || s.Length <= max)
|
||||
{
|
||||
return s ?? string.Empty;
|
||||
}
|
||||
|
||||
return s.Substring(0, max) + "...";
|
||||
}
|
||||
|
||||
private sealed class GiteaRelease
|
||||
{
|
||||
[JsonPropertyName("tag_name")]
|
||||
public string TagName { get; set; }
|
||||
|
||||
[JsonPropertyName("html_url")]
|
||||
public string HtmlUrl { get; set; }
|
||||
|
||||
[JsonPropertyName("assets")]
|
||||
public GiteaAsset[] Assets { get; set; }
|
||||
}
|
||||
|
||||
private sealed class GiteaAsset
|
||||
{
|
||||
[JsonPropertyName("name")]
|
||||
public string Name { get; set; }
|
||||
|
||||
[JsonPropertyName("browser_download_url")]
|
||||
public string BrowserDownloadUrl { get; set; }
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -129,6 +129,7 @@
|
||||
<Compile Include="Classes\SuppliersItem.cs" />
|
||||
<Compile Include="Classes\SupplierResponse.cs" />
|
||||
<Compile Include="Classes\ToastNotification.cs" />
|
||||
<Compile Include="Classes\UpdateChecker.cs" />
|
||||
<Compile Include="Form1.cs">
|
||||
<SubType>Form</SubType>
|
||||
</Compile>
|
||||
@ -201,6 +202,12 @@
|
||||
<Compile Include="HelpForms\HF_Settings.Designer.cs">
|
||||
<DependentUpon>HF_Settings.cs</DependentUpon>
|
||||
</Compile>
|
||||
<Compile Include="HelpForms\HF_UpdateAvailable.cs">
|
||||
<SubType>Form</SubType>
|
||||
</Compile>
|
||||
<Compile Include="HelpForms\HF_UpdateAvailable.Designer.cs">
|
||||
<DependentUpon>HF_UpdateAvailable.cs</DependentUpon>
|
||||
</Compile>
|
||||
<Compile Include="HelpForms\HF_UploadDataToServer.cs">
|
||||
<SubType>Form</SubType>
|
||||
</Compile>
|
||||
|
||||
@ -67,6 +67,28 @@ namespace Электронная_Фармация
|
||||
{
|
||||
FinalizeModernShell();
|
||||
|
||||
// Принудительное обновление: если на Gitea есть более новый Release — не пускаем в работу.
|
||||
// При недоступной сети не блокируем (офлайн-аптека должна открываться).
|
||||
try
|
||||
{
|
||||
var update = UpdateChecker.CheckForUpdate();
|
||||
if (update != null && update.UpdateRequired)
|
||||
{
|
||||
using (var dlg = new HF_UpdateAvailable(update))
|
||||
{
|
||||
UiThemeHelper.ApplyToControlTree(dlg);
|
||||
dlg.ShowDialog(this);
|
||||
}
|
||||
|
||||
Application.Exit();
|
||||
return;
|
||||
}
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
AppDebugLog.Info("App", $"Проверка обновлений пропущена: {ex.Message}");
|
||||
}
|
||||
|
||||
FCheckDatabaseIntegrary fCheckDatabaseIntegrary = new FCheckDatabaseIntegrary();
|
||||
UiThemeHelper.ApplyToControlTree(fCheckDatabaseIntegrary);
|
||||
fCheckDatabaseIntegrary.ShowDialog();
|
||||
|
||||
107
src/ElectronicPharmacy/HelpForms/HF_UpdateAvailable.Designer.cs
generated
Normal file
107
src/ElectronicPharmacy/HelpForms/HF_UpdateAvailable.Designer.cs
generated
Normal file
@ -0,0 +1,107 @@
|
||||
namespace Электронная_Фармация.HelpForms
|
||||
{
|
||||
partial class HF_UpdateAvailable
|
||||
{
|
||||
private System.ComponentModel.IContainer components = null;
|
||||
|
||||
protected override void Dispose(bool disposing)
|
||||
{
|
||||
if (disposing && (components != null))
|
||||
{
|
||||
components.Dispose();
|
||||
}
|
||||
base.Dispose(disposing);
|
||||
}
|
||||
|
||||
#region Windows Form Designer generated code
|
||||
|
||||
private void InitializeComponent()
|
||||
{
|
||||
this.panelHeader = new System.Windows.Forms.Panel();
|
||||
this.lblTitle = new System.Windows.Forms.Label();
|
||||
this.lblMessage = new System.Windows.Forms.Label();
|
||||
this.btnDownload = new Elfisa.UI.Controls.ModernButton();
|
||||
this.btnExit = new Elfisa.UI.Controls.ModernButton();
|
||||
this.panelHeader.SuspendLayout();
|
||||
this.SuspendLayout();
|
||||
//
|
||||
// panelHeader
|
||||
//
|
||||
this.panelHeader.Controls.Add(this.lblTitle);
|
||||
this.panelHeader.Dock = System.Windows.Forms.DockStyle.Top;
|
||||
this.panelHeader.Location = new System.Drawing.Point(0, 0);
|
||||
this.panelHeader.Name = "panelHeader";
|
||||
this.panelHeader.Size = new System.Drawing.Size(520, 56);
|
||||
this.panelHeader.TabIndex = 0;
|
||||
//
|
||||
// lblTitle
|
||||
//
|
||||
this.lblTitle.Font = new System.Drawing.Font("Segoe UI Semibold", 13.8F, System.Drawing.FontStyle.Bold);
|
||||
this.lblTitle.ForeColor = System.Drawing.Color.White;
|
||||
this.lblTitle.Location = new System.Drawing.Point(12, 10);
|
||||
this.lblTitle.Name = "lblTitle";
|
||||
this.lblTitle.Size = new System.Drawing.Size(480, 36);
|
||||
this.lblTitle.TabIndex = 0;
|
||||
this.lblTitle.Text = "Доступно новое обновление";
|
||||
this.lblTitle.TextAlign = System.Drawing.ContentAlignment.MiddleLeft;
|
||||
//
|
||||
// lblMessage
|
||||
//
|
||||
this.lblMessage.Font = new System.Drawing.Font("Segoe UI", 11F);
|
||||
this.lblMessage.Location = new System.Drawing.Point(24, 72);
|
||||
this.lblMessage.Name = "lblMessage";
|
||||
this.lblMessage.Size = new System.Drawing.Size(472, 120);
|
||||
this.lblMessage.TabIndex = 1;
|
||||
this.lblMessage.Text = "Доступно новое обновление";
|
||||
//
|
||||
// btnDownload
|
||||
//
|
||||
this.btnDownload.Font = new System.Drawing.Font("Segoe UI", 11F);
|
||||
this.btnDownload.Location = new System.Drawing.Point(24, 220);
|
||||
this.btnDownload.Name = "btnDownload";
|
||||
this.btnDownload.Size = new System.Drawing.Size(180, 40);
|
||||
this.btnDownload.TabIndex = 2;
|
||||
this.btnDownload.Text = "Скачать";
|
||||
this.btnDownload.Click += new System.EventHandler(this.btnDownload_Click);
|
||||
//
|
||||
// btnExit
|
||||
//
|
||||
this.btnExit.Font = new System.Drawing.Font("Segoe UI", 11F);
|
||||
this.btnExit.Location = new System.Drawing.Point(316, 220);
|
||||
this.btnExit.Name = "btnExit";
|
||||
this.btnExit.Size = new System.Drawing.Size(180, 40);
|
||||
this.btnExit.TabIndex = 3;
|
||||
this.btnExit.Text = "Выход";
|
||||
this.btnExit.Click += new System.EventHandler(this.btnExit_Click);
|
||||
//
|
||||
// HF_UpdateAvailable
|
||||
//
|
||||
this.AutoScaleDimensions = new System.Drawing.SizeF(8F, 16F);
|
||||
this.AutoScaleMode = System.Windows.Forms.AutoScaleMode.Font;
|
||||
this.ClientSize = new System.Drawing.Size(520, 290);
|
||||
this.Controls.Add(this.btnExit);
|
||||
this.Controls.Add(this.btnDownload);
|
||||
this.Controls.Add(this.lblMessage);
|
||||
this.Controls.Add(this.panelHeader);
|
||||
this.FormBorderStyle = System.Windows.Forms.FormBorderStyle.FixedDialog;
|
||||
this.MaximizeBox = false;
|
||||
this.MinimizeBox = false;
|
||||
this.Name = "HF_UpdateAvailable";
|
||||
this.ShowIcon = false;
|
||||
this.ShowInTaskbar = false;
|
||||
this.StartPosition = System.Windows.Forms.FormStartPosition.CenterParent;
|
||||
this.Text = "Обновление";
|
||||
this.FormClosing += new System.Windows.Forms.FormClosingEventHandler(this.HF_UpdateAvailable_FormClosing);
|
||||
this.panelHeader.ResumeLayout(false);
|
||||
this.ResumeLayout(false);
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
private System.Windows.Forms.Panel panelHeader;
|
||||
private System.Windows.Forms.Label lblTitle;
|
||||
private System.Windows.Forms.Label lblMessage;
|
||||
private Elfisa.UI.Controls.ModernButton btnDownload;
|
||||
private Elfisa.UI.Controls.ModernButton btnExit;
|
||||
}
|
||||
}
|
||||
65
src/ElectronicPharmacy/HelpForms/HF_UpdateAvailable.cs
Normal file
65
src/ElectronicPharmacy/HelpForms/HF_UpdateAvailable.cs
Normal file
@ -0,0 +1,65 @@
|
||||
using System;
|
||||
using System.Diagnostics;
|
||||
using System.Windows.Forms;
|
||||
using Электронная_Фармация.Classes;
|
||||
|
||||
namespace Электронная_Фармация.HelpForms
|
||||
{
|
||||
public partial class HF_UpdateAvailable : Form
|
||||
{
|
||||
private readonly UpdateCheckResult _result;
|
||||
|
||||
public HF_UpdateAvailable(UpdateCheckResult result)
|
||||
{
|
||||
_result = result ?? throw new ArgumentNullException(nameof(result));
|
||||
InitializeComponent();
|
||||
UiThemeHelper.ApplyToControlTree(this);
|
||||
|
||||
var latest = string.IsNullOrWhiteSpace(_result.LatestVersion) ? "?" : _result.LatestVersion;
|
||||
var local = string.IsNullOrWhiteSpace(_result.LocalVersion) ? "?" : _result.LocalVersion;
|
||||
lblMessage.Text =
|
||||
$"Доступно новое обновление {latest} (у вас {local}).{Environment.NewLine}{Environment.NewLine}" +
|
||||
"Для продолжения работы необходимо установить новую версию.";
|
||||
}
|
||||
|
||||
private void btnDownload_Click(object sender, EventArgs e)
|
||||
{
|
||||
var url = !string.IsNullOrWhiteSpace(_result.DownloadUrl)
|
||||
? _result.DownloadUrl
|
||||
: _result.ReleaseUrl;
|
||||
if (string.IsNullOrWhiteSpace(url))
|
||||
{
|
||||
UiDialogs.ShowError("Ссылка на обновление не задана.", "Обновление", this);
|
||||
return;
|
||||
}
|
||||
|
||||
try
|
||||
{
|
||||
Process.Start(new ProcessStartInfo
|
||||
{
|
||||
FileName = url,
|
||||
UseShellExecute = true
|
||||
});
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
UiDialogs.ShowError($"Не удалось открыть ссылку:{Environment.NewLine}{ex.Message}", "Обновление", this);
|
||||
}
|
||||
}
|
||||
|
||||
private void btnExit_Click(object sender, EventArgs e)
|
||||
{
|
||||
DialogResult = DialogResult.Abort;
|
||||
Close();
|
||||
}
|
||||
|
||||
private void HF_UpdateAvailable_FormClosing(object sender, FormClosingEventArgs e)
|
||||
{
|
||||
// Крестик / Alt+F4 — тоже выход из приложения (не пускать в работу).
|
||||
if (DialogResult != DialogResult.Abort)
|
||||
{
|
||||
DialogResult = DialogResult.Abort;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -1,36 +1,36 @@
|
||||
using System.Reflection;
|
||||
using System.Reflection;
|
||||
using System.Runtime.CompilerServices;
|
||||
using System.Runtime.InteropServices;
|
||||
|
||||
// Общие сведения об этой сборке предоставляются следующим набором
|
||||
// набора атрибутов. Измените значения этих атрибутов для изменения сведений,
|
||||
// связанных со сборкой.
|
||||
[assembly: AssemblyTitle("Электронная Фармация")]
|
||||
// Общие сведения об этой сборке предоставляются следующим набором
|
||||
// набора атрибутов. Измените значения этих атрибутов для изменения сведений,
|
||||
// связанных со сборкой.
|
||||
[assembly: AssemblyTitle("Электронная Фармация")]
|
||||
[assembly: AssemblyDescription("")]
|
||||
[assembly: AssemblyConfiguration("")]
|
||||
[assembly: AssemblyCompany("")]
|
||||
[assembly: AssemblyProduct("Электронная Фармация")]
|
||||
[assembly: AssemblyCopyright("Copyright © 2023")]
|
||||
[assembly: AssemblyProduct("Электронная Фармация")]
|
||||
[assembly: AssemblyCopyright("Copyright © 2023")]
|
||||
[assembly: AssemblyTrademark("")]
|
||||
[assembly: AssemblyCulture("")]
|
||||
|
||||
// Установка значения False для параметра ComVisible делает типы в этой сборке невидимыми
|
||||
// для компонентов COM. Если необходимо обратиться к типу в этой сборке через
|
||||
// COM, следует установить атрибут ComVisible в TRUE для этого типа.
|
||||
// Установка значения False для параметра ComVisible делает типы в этой сборке невидимыми
|
||||
// для компонентов COM. Если необходимо обратиться к типу в этой сборке через
|
||||
// COM, следует установить атрибут ComVisible в TRUE для этого типа.
|
||||
[assembly: ComVisible(false)]
|
||||
|
||||
// Следующий GUID служит для идентификации библиотеки типов, если этот проект будет видимым для COM
|
||||
// Следующий GUID служит для идентификации библиотеки типов, если этот проект будет видимым для COM
|
||||
[assembly: Guid("145d8060-4187-4871-b4fb-8df795727b66")]
|
||||
|
||||
// Сведения о версии сборки состоят из указанных ниже четырех значений:
|
||||
// Сведения о версии сборки состоят из указанных ниже четырех значений:
|
||||
//
|
||||
// Основной номер версии
|
||||
// Дополнительный номер версии
|
||||
// Номер сборки
|
||||
// Редакция
|
||||
// Основной номер версии
|
||||
// Дополнительный номер версии
|
||||
// Номер сборки
|
||||
// Редакция
|
||||
//
|
||||
// Можно задать все значения или принять номера сборки и редакции по умолчанию
|
||||
// используя "*", как показано ниже:
|
||||
// Можно задать все значения или принять номера сборки и редакции по умолчанию
|
||||
// используя "*", как показано ниже:
|
||||
// [assembly: AssemblyVersion("1.0.*")]
|
||||
[assembly: AssemblyVersion("1.0.3.0")]
|
||||
[assembly: AssemblyFileVersion("1.0.3.0")]
|
||||
[assembly: AssemblyVersion("1.0.10.0")]
|
||||
[assembly: AssemblyFileVersion("1.0.10.0")]
|
||||
|
||||
@ -167,5 +167,41 @@ namespace Электронная_Фармация.Properties {
|
||||
}
|
||||
}
|
||||
|
||||
[global::System.Configuration.UserScopedSettingAttribute()]
|
||||
[global::System.Diagnostics.DebuggerNonUserCodeAttribute()]
|
||||
[global::System.Configuration.DefaultSettingValueAttribute("https://git.24pharmdata.ru")]
|
||||
public string UpdateCheckBaseUrl {
|
||||
get {
|
||||
return ((string)(this["UpdateCheckBaseUrl"]));
|
||||
}
|
||||
set {
|
||||
this["UpdateCheckBaseUrl"] = value;
|
||||
}
|
||||
}
|
||||
|
||||
[global::System.Configuration.UserScopedSettingAttribute()]
|
||||
[global::System.Diagnostics.DebuggerNonUserCodeAttribute()]
|
||||
[global::System.Configuration.DefaultSettingValueAttribute("pharmdata/elfisa-pharmacy")]
|
||||
public string UpdateRepo {
|
||||
get {
|
||||
return ((string)(this["UpdateRepo"]));
|
||||
}
|
||||
set {
|
||||
this["UpdateRepo"] = value;
|
||||
}
|
||||
}
|
||||
|
||||
[global::System.Configuration.UserScopedSettingAttribute()]
|
||||
[global::System.Diagnostics.DebuggerNonUserCodeAttribute()]
|
||||
[global::System.Configuration.DefaultSettingValueAttribute("")]
|
||||
public string UpdateApiToken {
|
||||
get {
|
||||
return ((string)(this["UpdateApiToken"]));
|
||||
}
|
||||
set {
|
||||
this["UpdateApiToken"] = value;
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
@ -38,5 +38,14 @@
|
||||
<Setting Name="LastPriceReminderUtc" Type="System.DateTime" Scope="User">
|
||||
<Value Profile="(Default)">01.01.0001 00:00:00</Value>
|
||||
</Setting>
|
||||
<Setting Name="UpdateCheckBaseUrl" Type="System.String" Scope="User">
|
||||
<Value Profile="(Default)">https://git.24pharmdata.ru</Value>
|
||||
</Setting>
|
||||
<Setting Name="UpdateRepo" Type="System.String" Scope="User">
|
||||
<Value Profile="(Default)">pharmdata/elfisa-pharmacy</Value>
|
||||
</Setting>
|
||||
<Setting Name="UpdateApiToken" Type="System.String" Scope="User">
|
||||
<Value Profile="(Default)" />
|
||||
</Setting>
|
||||
</Settings>
|
||||
</SettingsFile>
|
||||
31
tools/UpdateCheckSmoke/Program.cs
Normal file
31
tools/UpdateCheckSmoke/Program.cs
Normal file
@ -0,0 +1,31 @@
|
||||
using System;
|
||||
using Электронная_Фармация.Classes;
|
||||
|
||||
namespace UpdateCheckSmoke
|
||||
{
|
||||
internal static class Program
|
||||
{
|
||||
private static int Main(string[] args)
|
||||
{
|
||||
// args: baseUrl repo localVersion
|
||||
var baseUrl = args.Length > 0 ? args[0] : "http://127.0.0.1:18765";
|
||||
var repo = args.Length > 1 ? args[1] : "pharmdata/elfisa-pharmacy";
|
||||
var local = args.Length > 2 ? args[2] : "1.0.3.0";
|
||||
|
||||
var result = UpdateChecker.CheckForUpdate(baseUrl, repo, null, local);
|
||||
Console.WriteLine("CheckedOk=" + result.CheckedOk);
|
||||
Console.WriteLine("UpdateRequired=" + result.UpdateRequired);
|
||||
Console.WriteLine("Local=" + result.LocalVersion);
|
||||
Console.WriteLine("Latest=" + result.LatestVersion);
|
||||
Console.WriteLine("Download=" + result.DownloadUrl);
|
||||
Console.WriteLine("Error=" + result.ErrorMessage);
|
||||
|
||||
if (!result.CheckedOk)
|
||||
{
|
||||
return 2;
|
||||
}
|
||||
|
||||
return result.UpdateRequired ? 0 : 1;
|
||||
}
|
||||
}
|
||||
}
|
||||
Loading…
Reference in New Issue
Block a user