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">
|
<setting name="InvoiceExportPath" serializeAs="String">
|
||||||
<value />
|
<value />
|
||||||
</setting>
|
</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>
|
</Электронная_Фармация.Properties.Settings>
|
||||||
</userSettings>
|
</userSettings>
|
||||||
</configuration>
|
</configuration>
|
||||||
@ -10,6 +10,8 @@ namespace Электронная_Фармация.Classes
|
|||||||
public static class AppConfig
|
public static class AppConfig
|
||||||
{
|
{
|
||||||
public const string DefaultApiBaseUrl = "https://24pharmdata.ru";
|
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 LegacyApiBaseUrl = "http://195.34.241.84:9988";
|
||||||
private const string ApiBaseUrlEnvVar = "ELF_API_BASE_URL";
|
private const string ApiBaseUrlEnvVar = "ELF_API_BASE_URL";
|
||||||
private static bool _sqliteInteropPrepared;
|
private static bool _sqliteInteropPrepared;
|
||||||
@ -126,6 +128,69 @@ namespace Электронная_Фармация.Classes
|
|||||||
Settings.Default.Save();
|
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()
|
private static void EnsureSQLiteInteropInBaseDir()
|
||||||
{
|
{
|
||||||
// System.Data.SQLite ожидает SQLite.Interop.dll рядом с exe.
|
// 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\SuppliersItem.cs" />
|
||||||
<Compile Include="Classes\SupplierResponse.cs" />
|
<Compile Include="Classes\SupplierResponse.cs" />
|
||||||
<Compile Include="Classes\ToastNotification.cs" />
|
<Compile Include="Classes\ToastNotification.cs" />
|
||||||
|
<Compile Include="Classes\UpdateChecker.cs" />
|
||||||
<Compile Include="Form1.cs">
|
<Compile Include="Form1.cs">
|
||||||
<SubType>Form</SubType>
|
<SubType>Form</SubType>
|
||||||
</Compile>
|
</Compile>
|
||||||
@ -201,6 +202,12 @@
|
|||||||
<Compile Include="HelpForms\HF_Settings.Designer.cs">
|
<Compile Include="HelpForms\HF_Settings.Designer.cs">
|
||||||
<DependentUpon>HF_Settings.cs</DependentUpon>
|
<DependentUpon>HF_Settings.cs</DependentUpon>
|
||||||
</Compile>
|
</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">
|
<Compile Include="HelpForms\HF_UploadDataToServer.cs">
|
||||||
<SubType>Form</SubType>
|
<SubType>Form</SubType>
|
||||||
</Compile>
|
</Compile>
|
||||||
|
|||||||
@ -67,6 +67,28 @@ namespace Электронная_Фармация
|
|||||||
{
|
{
|
||||||
FinalizeModernShell();
|
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();
|
FCheckDatabaseIntegrary fCheckDatabaseIntegrary = new FCheckDatabaseIntegrary();
|
||||||
UiThemeHelper.ApplyToControlTree(fCheckDatabaseIntegrary);
|
UiThemeHelper.ApplyToControlTree(fCheckDatabaseIntegrary);
|
||||||
fCheckDatabaseIntegrary.ShowDialog();
|
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.CompilerServices;
|
||||||
using System.Runtime.InteropServices;
|
using System.Runtime.InteropServices;
|
||||||
|
|
||||||
// Общие сведения об этой сборке предоставляются следующим набором
|
// Общие сведения об этой сборке предоставляются следующим набором
|
||||||
// набора атрибутов. Измените значения этих атрибутов для изменения сведений,
|
// набора атрибутов. Измените значения этих атрибутов для изменения сведений,
|
||||||
// связанных со сборкой.
|
// связанных со сборкой.
|
||||||
[assembly: AssemblyTitle("Электронная Фармация")]
|
[assembly: AssemblyTitle("Электронная Фармация")]
|
||||||
[assembly: AssemblyDescription("")]
|
[assembly: AssemblyDescription("")]
|
||||||
[assembly: AssemblyConfiguration("")]
|
[assembly: AssemblyConfiguration("")]
|
||||||
[assembly: AssemblyCompany("")]
|
[assembly: AssemblyCompany("")]
|
||||||
[assembly: AssemblyProduct("Электронная Фармация")]
|
[assembly: AssemblyProduct("Электронная Фармация")]
|
||||||
[assembly: AssemblyCopyright("Copyright © 2023")]
|
[assembly: AssemblyCopyright("Copyright © 2023")]
|
||||||
[assembly: AssemblyTrademark("")]
|
[assembly: AssemblyTrademark("")]
|
||||||
[assembly: AssemblyCulture("")]
|
[assembly: AssemblyCulture("")]
|
||||||
|
|
||||||
// Установка значения False для параметра ComVisible делает типы в этой сборке невидимыми
|
// Установка значения False для параметра ComVisible делает типы в этой сборке невидимыми
|
||||||
// для компонентов COM. Если необходимо обратиться к типу в этой сборке через
|
// для компонентов COM. Если необходимо обратиться к типу в этой сборке через
|
||||||
// COM, следует установить атрибут ComVisible в TRUE для этого типа.
|
// COM, следует установить атрибут ComVisible в TRUE для этого типа.
|
||||||
[assembly: ComVisible(false)]
|
[assembly: ComVisible(false)]
|
||||||
|
|
||||||
// Следующий GUID служит для идентификации библиотеки типов, если этот проект будет видимым для COM
|
// Следующий GUID служит для идентификации библиотеки типов, если этот проект будет видимым для COM
|
||||||
[assembly: Guid("145d8060-4187-4871-b4fb-8df795727b66")]
|
[assembly: Guid("145d8060-4187-4871-b4fb-8df795727b66")]
|
||||||
|
|
||||||
// Сведения о версии сборки состоят из указанных ниже четырех значений:
|
// Сведения о версии сборки состоят из указанных ниже четырех значений:
|
||||||
//
|
//
|
||||||
// Основной номер версии
|
// Основной номер версии
|
||||||
// Дополнительный номер версии
|
// Дополнительный номер версии
|
||||||
// Номер сборки
|
// Номер сборки
|
||||||
// Редакция
|
// Редакция
|
||||||
//
|
//
|
||||||
// Можно задать все значения или принять номера сборки и редакции по умолчанию
|
// Можно задать все значения или принять номера сборки и редакции по умолчанию
|
||||||
// используя "*", как показано ниже:
|
// используя "*", как показано ниже:
|
||||||
// [assembly: AssemblyVersion("1.0.*")]
|
// [assembly: AssemblyVersion("1.0.*")]
|
||||||
[assembly: AssemblyVersion("1.0.3.0")]
|
[assembly: AssemblyVersion("1.0.10.0")]
|
||||||
[assembly: AssemblyFileVersion("1.0.3.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">
|
<Setting Name="LastPriceReminderUtc" Type="System.DateTime" Scope="User">
|
||||||
<Value Profile="(Default)">01.01.0001 00:00:00</Value>
|
<Value Profile="(Default)">01.01.0001 00:00:00</Value>
|
||||||
</Setting>
|
</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>
|
</Settings>
|
||||||
</SettingsFile>
|
</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