elfisa-pharmacy/src/ElectronicPharmacy/HelpForms/HF_DownloadDataFromServer.cs
Exest 9dfdb913cb Use server final price on download without local markup math.
Server now sums price-list, region and buyer markups; desktop must not re-apply discounts.

Co-authored-by: Cursor <cursoragent@cursor.com>
2026-07-30 15:40:36 +03:00

746 lines
31 KiB
C#
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

using System;
using System.Collections.Generic;
using System.Data;
using System.Data.SQLite;
using System.Drawing;
using System.Net.Http;
using System.Text;
using System.Threading.Tasks;
using System.Windows.Forms;
using Электроннаяармация.Classes;
using Электроннаяармация.Properties;
namespace Электроннаяармация.HelpForms
{
public partial class HF_DownloadDataFromServer : Form
{
// Доли общего прогресса 0..100.
private const int DownloadPhaseEnd = 45;
private const int BuildPhaseEnd = 55;
private const int SavePhaseEnd = 100;
public HF_DownloadDataFromServer()
{
InitializeComponent();
}
// Чтобы при обновлении прайса не терять корзины других аптек,
// чистим TempOrderItems только для активного грузополучателя.
public string ActiveConsigneeName { get; set; } = string.Empty;
private readonly string _login = Settings.Default.stringLogin ?? string.Empty;
private readonly string _password = Settings.Default.stringPassword ?? string.Empty;
private readonly string _connectionStringToLocalDb = AppConfig.SqliteConnectionString;
private bool _isDownloading;
private async void HF_DownloadDataFromServer_Load(object sender, EventArgs e)
{
UiThemeHelper.ApplyToControlTree(this);
ConfigureLogView();
btnCloseThis.Enabled = false;
dataGridView1.Visible = false;
rtxtDebug.Visible = true;
rtxtDebug.ReadOnly = true;
_isDownloading = true;
SetOverallProgress(0, "Подключение к серверу...");
var client = new ApiClient();
AppendStatus($"Сервер: {client.BaseUrl}");
AppendStatus($"Лог: {AppDebugLog.CurrentLogFilePath}");
AppDebugLog.Info("Download", $"Старт загрузки прайса. Сервер={client.BaseUrl}");
try
{
SetOverallProgress(1, "Авторизация...");
await EnsureAuthenticatedAsync(client);
SetOverallProgress(3, "Загрузка прайса с сервера...");
AppendStatus("Загружаю сводный прайс...");
var allSuppliersSummary = await GetFullPriceSummaryPagedAsync(client);
int totalItems = allSuppliersSummary.Summary?.Count ?? 0;
// Наценки прайса/региона/покупателя считает сервер.
// Десктоп сохраняет цену из API как есть, без локального пересчёта.
AppendStatus("Цена берётся с сервера без локального пересчёта наценок");
AppendStatus($"Получено {totalItems} позиций");
SetOverallProgress(DownloadPhaseEnd, $"Получено {totalItems} / {totalItems} позиций (100%)");
SetOverallProgress(DownloadPhaseEnd, "Подготовка таблицы...");
var buildProgress = new Progress<CountProgress>(p =>
{
int phasePercent = MapPhasePercent(p.Current, p.Total, DownloadPhaseEnd, BuildPhaseEnd);
SetOverallProgress(
phasePercent,
$"Обработка: {p.Current} / {p.Total} ({PercentOf(p.Current, p.Total)}%)");
});
var tablePrice = await Task.Run(() => BuildPriceTable(allSuppliersSummary, buildProgress));
SetOverallProgress(BuildPhaseEnd, "Сохранение в локальную базу...");
var saveProgress = new Progress<CountProgress>(p =>
{
int phasePercent = MapPhasePercent(p.Current, p.Total, BuildPhaseEnd, SavePhaseEnd);
SetOverallProgress(
phasePercent,
$"Сохранение: {p.Current} / {p.Total} ({PercentOf(p.Current, p.Total)}%)");
});
await Task.Run(() => SavePriceTable(tablePrice, saveProgress));
SetOverallProgress(100, $"Готово: {tablePrice.Rows.Count} / {tablePrice.Rows.Count} (100%)");
DialogResult = DialogResult.OK;
AppendStatus($"Готово. Сохранено {tablePrice.Rows.Count} позиций в локальную базу.");
ToastNotification.ShowSuccess($"Прайс обновлён: {tablePrice.Rows.Count} позиций");
Settings.Default.LastPriceUpdateUtc = DateTime.UtcNow;
Settings.Default.LastPriceReminderUtc = DateTime.MinValue;
Settings.Default.Save();
}
catch (UnauthorizedAccessException ex)
{
SetOverallProgress(0, "Ошибка авторизации");
AppendStatus($"Ошибка авторизации: {ex.Message}");
AppDebugLog.Error("Download", "Ошибка авторизации при загрузке прайса", ex);
ToastNotification.ShowError($"Ошибка авторизации: {ex.Message}");
}
catch (HttpRequestException ex)
{
SetOverallProgress(0, "Ошибка сети");
AppendStatus($"Ошибка сети: {ex.Message}");
AppDebugLog.Error("Download", "Ошибка сети при загрузке прайса", ex);
ToastNotification.ShowError($"Ошибка сети: {ex.Message}");
}
catch (Exception ex)
{
SetOverallProgress(0, "Ошибка загрузки");
AppendStatus($"Ошибка загрузки прайса: {ex.Message}");
AppDebugLog.Error("Download", "Неожиданная ошибка при загрузке прайса", ex);
ToastNotification.ShowError($"Ошибка загрузки прайса: {ex.Message}");
}
finally
{
_isDownloading = false;
btnCloseThis.Enabled = true;
}
}
private async Task EnsureAuthenticatedAsync(ApiClient client)
{
if (!string.IsNullOrWhiteSpace(Settings.Default.stringToken))
{
client.SetToken(Settings.Default.stringToken);
AppendStatus("Используется сохранённый токен...");
return;
}
await LoginAndSaveTokenAsync(client);
}
private async Task LoginAndSaveTokenAsync(ApiClient client)
{
if (string.IsNullOrWhiteSpace(_login) || string.IsNullOrWhiteSpace(_password))
{
throw new UnauthorizedAccessException(
"Не заданы логин и пароль. Выполните авторизацию в меню «Регистрация».");
}
AppendStatus("Выполняю авторизацию...");
var token = await client.LoginAsync(_login, _password);
Settings.Default.stringToken = token;
Settings.Default.Save();
AppendStatus("Авторизация успешно пройдена");
}
private async Task<PriceSummaryResponse> GetFullPriceSummaryPagedAsync(ApiClient client)
{
var regionId = string.IsNullOrWhiteSpace(Settings.Default.RegionId)
? null
: Settings.Default.RegionId;
const int supplierPageLimit = 100000;
const int discoveryLimit = 50000;
var combined = new PriceSummaryResponse { Summary = new List<PriceSummaryItem>() };
var transferProgress = new Progress<HttpTransferProgress>(ReportTransferProgress);
var supplierIds = new List<string>(ConsigneeHelper.GetKnownSupplierIds());
if (supplierIds.Count == 0)
{
AppendStatus("Первая загрузка: определяю список поставщиков...");
SetOverallProgress(3, "Определение поставщиков...");
var discovery = await GetPriceSummaryWithAuthRetryAsync(
client,
supplierId: null,
regionId: regionId,
limit: discoveryLimit,
offset: 0,
transferProgress: transferProgress);
if (discovery?.MarkupPercent.HasValue == true)
{
combined.MarkupPercent = discovery.MarkupPercent;
}
var discovered = new HashSet<string>(StringComparer.OrdinalIgnoreCase);
if (discovery?.Summary != null)
{
foreach (var item in discovery.Summary)
{
if (!string.IsNullOrWhiteSpace(item.SupplierId))
{
discovered.Add(item.SupplierId.Trim());
}
}
}
supplierIds.AddRange(discovered);
AppendStatus($"Найдено поставщиков: {supplierIds.Count}");
}
if (supplierIds.Count == 0)
{
AppendStatus("Поставщики не найдены, пробую полный запрос...");
var fallback = await GetPriceSummaryWithAuthRetryAsync(
client,
supplierId: null,
regionId: regionId,
limit: 200000,
offset: 0,
transferProgress: transferProgress);
if (fallback?.MarkupPercent.HasValue == true)
{
combined.MarkupPercent = fallback.MarkupPercent;
}
if (fallback?.Summary != null)
{
combined.Summary.AddRange(fallback.Summary);
}
return combined;
}
int totalSuppliers = supplierIds.Count;
for (int i = 0; i < totalSuppliers; i++)
{
var supplierId = supplierIds[i];
int supplierProgressStart = 3 + (int)((DownloadPhaseEnd - 3) * ((double)i / totalSuppliers));
int supplierProgressEnd = 3 + (int)((DownloadPhaseEnd - 3) * ((double)(i + 1) / totalSuppliers));
SetOverallProgress(
supplierProgressStart,
$"Поставщик {i + 1}/{totalSuppliers}...");
AppendStatus($"Загрузка поставщика {i + 1}/{totalSuppliers}: {supplierId}");
PriceSummaryResponse chunk;
try
{
chunk = await GetPriceSummaryWithAuthRetryAsync(
client,
supplierId: supplierId,
regionId: regionId,
limit: supplierPageLimit,
offset: 0,
transferProgress: transferProgress);
}
catch (Exception ex)
{
AppendStatus($"Ошибка поставщика {supplierId}: {ex.Message}");
AppDebugLog.Error("Download", $"Ошибка загрузки прайса поставщика {supplierId}", ex);
continue;
}
if (i == 0 && chunk?.MarkupPercent.HasValue == true && !combined.MarkupPercent.HasValue)
{
combined.MarkupPercent = chunk.MarkupPercent;
}
int got = chunk?.Summary?.Count ?? 0;
if (got > 0)
{
combined.Summary.AddRange(chunk.Summary);
}
SetOverallProgress(
Math.Max(supplierProgressStart, supplierProgressEnd - 1),
$"Поставщик {i + 1}/{totalSuppliers}: +{got}, всего {combined.Summary.Count}");
AppendStatus($"Поставщик {i + 1}/{totalSuppliers}: получено {got}, всего {combined.Summary.Count}");
}
// Лёгкий discovery новых поставщиков, которых ещё не было в локальной базе.
try
{
AppendStatus("Проверка новых поставщиков...");
var probe = await GetPriceSummaryWithAuthRetryAsync(
client,
supplierId: null,
regionId: regionId,
limit: 5000,
offset: 0,
transferProgress: null);
var known = new HashSet<string>(supplierIds, StringComparer.OrdinalIgnoreCase);
var newcomers = new List<string>();
if (probe?.Summary != null)
{
foreach (var item in probe.Summary)
{
if (!string.IsNullOrWhiteSpace(item.SupplierId) && known.Add(item.SupplierId.Trim()))
{
newcomers.Add(item.SupplierId.Trim());
}
}
}
for (int i = 0; i < newcomers.Count; i++)
{
var supplierId = newcomers[i];
AppendStatus($"Новый поставщик {i + 1}/{newcomers.Count}: {supplierId}");
var chunk = await GetPriceSummaryWithAuthRetryAsync(
client,
supplierId: supplierId,
regionId: regionId,
limit: supplierPageLimit,
offset: 0,
transferProgress: transferProgress);
int got = chunk?.Summary?.Count ?? 0;
if (got > 0)
{
combined.Summary.AddRange(chunk.Summary);
}
AppendStatus($"Новый поставщик: +{got}, всего {combined.Summary.Count}");
}
}
catch (Exception ex)
{
AppendStatus($"Проверка новых поставщиков пропущена: {ex.Message}");
AppDebugLog.Error("Download", "Ошибка discovery новых поставщиков", ex);
}
return combined;
}
private async Task<PriceSummaryResponse> GetPriceSummaryWithAuthRetryAsync(
ApiClient client,
string supplierId,
string regionId,
int limit,
int offset,
IProgress<HttpTransferProgress> transferProgress)
{
try
{
return await client.GetPriceSummaryAsync(
supplierId: supplierId,
regionId: regionId,
limit: limit,
offset: offset,
transferProgress: transferProgress);
}
catch (UnauthorizedAccessException)
{
AppendStatus("Токен недействителен, повторная авторизация...");
SetOverallProgress(2, "Повторная авторизация...");
Settings.Default.stringToken = string.Empty;
Settings.Default.Save();
client.SetToken(null);
await LoginAndSaveTokenAsync(client);
return await client.GetPriceSummaryAsync(
supplierId: supplierId,
regionId: regionId,
limit: limit,
offset: offset,
transferProgress: transferProgress);
}
}
private void ReportTransferProgress(HttpTransferProgress transfer)
{
if (transfer.TotalBytes.HasValue && transfer.TotalBytes.Value > 0)
{
int phasePercent = MapPhasePercent(transfer.BytesReceived, transfer.TotalBytes.Value, 3, DownloadPhaseEnd);
SetOverallProgress(
phasePercent,
$"Скачивание: {FormatBytes(transfer.BytesReceived)} / {FormatBytes(transfer.TotalBytes.Value)} ({transfer.Percent}%)");
}
else
{
SetOverallProgress(
Math.Min(DownloadPhaseEnd - 1, 5),
$"Скачивание: получено {FormatBytes(transfer.BytesReceived)}...");
}
}
private static DataTable BuildPriceTable(
PriceSummaryResponse allSuppliersSummary,
IProgress<CountProgress> progress = null)
{
var tablePrice = new DataTable();
tablePrice.Columns.Add("supplier_price_id");
tablePrice.Columns.Add("guid_es");
tablePrice.Columns.Add("es_code");
tablePrice.Columns.Add("supplier_id");
tablePrice.Columns.Add("DrugName");
tablePrice.Columns.Add("SupplierName");
tablePrice.Columns.Add("Price");
tablePrice.Columns.Add("Quantity");
tablePrice.Columns.Add(new DataColumn
{
ColumnName = "ExpiryPeriod",
DataType = typeof(string),
AllowDBNull = true
});
tablePrice.Columns.Add(new DataColumn
{
ColumnName = "Description",
DataType = typeof(string),
AllowDBNull = true
});
tablePrice.Columns.Add("Zakaz");
tablePrice.Columns.Add("SummaZakaza");
tablePrice.Columns.Add(new DataColumn
{
ColumnName = "TradeName",
DataType = typeof(string),
AllowDBNull = true
});
tablePrice.Columns.Add(new DataColumn
{
ColumnName = "Dosage",
DataType = typeof(string),
AllowDBNull = true
});
tablePrice.Columns.Add(new DataColumn
{
ColumnName = "MarkupPercent",
DataType = typeof(decimal),
AllowDBNull = true
});
if (allSuppliersSummary?.Summary == null)
{
return tablePrice;
}
int total = allSuppliersSummary.Summary.Count;
int current = 0;
foreach (var item in allSuppliersSummary.Summary)
{
var row = tablePrice.NewRow();
row["supplier_price_id"] = item.SupplierPriceID ?? string.Empty;
row["guid_es"] = item.GuidEs ?? string.Empty;
row["es_code"] = item.EsCode?.ToString() ?? "0";
row["supplier_id"] = item.SupplierId ?? string.Empty;
row["DrugName"] = item.DrugName ?? string.Empty;
row["SupplierName"] = item.SupplierName ?? string.Empty;
// Цена уже итоговая с сервера — локально не пересчитываем.
var serverPrice = item.Price ?? 0m;
row["Price"] = serverPrice.ToString(System.Globalization.CultureInfo.InvariantCulture);
row["Quantity"] = item.Quantity?.ToString() ?? string.Empty;
row["ExpiryPeriod"] = item.ExpiryPeriod == null ? (object)DBNull.Value : item.ExpiryPeriod;
row["Description"] = item.Description == null ? (object)DBNull.Value : item.Description;
row["Zakaz"] = string.Empty;
row["SummaZakaza"] = string.Empty;
row["TradeName"] = string.IsNullOrWhiteSpace(item.TradeName) ? (object)DBNull.Value : item.TradeName.Trim();
row["Dosage"] = string.IsNullOrWhiteSpace(item.Dosage) ? (object)DBNull.Value : item.Dosage.Trim();
row["MarkupPercent"] = item.MarkupPercent.HasValue && item.MarkupPercent.Value > 0m
? item.MarkupPercent.Value
: 0m;
tablePrice.Rows.Add(row);
current++;
if (progress != null && (current % 500 == 0 || current == total))
{
progress.Report(new CountProgress(current, total));
}
}
return tablePrice;
}
private void SavePriceTable(DataTable tablePrice, IProgress<CountProgress> progress = null)
{
const string commandToDelete = "delete from [PriceList]";
var commandToDeleteTempOrder = string.IsNullOrWhiteSpace(ActiveConsigneeName)
? "delete from [TempOrderItems]"
: "delete from [TempOrderItems] where [ConsigneeName] = @consignee";
const string commandToInsert = @"INSERT INTO [PriceList] (
[guid_es],
[es_code],
[supplier_id],
[DrugName],
[SupplierName],
[Price],
[Quantity],
[ExpiryPeriod],
[Description],
[Zakaz],
[SummaZakaza],
[supplier_price_id],
[TradeName],
[Dosage],
[MarkupPercent]
)
VALUES
(
@guid_es,
@es_code,
@supplier_id,
@DrugName,
@SupplierName,
@Price,
@Quantity,
@ExpiryPeriod,
@Description,
@Zakaz,
@SummaZakaza,
@supplier_price_id,
@TradeName,
@Dosage,
@MarkupPercent
)";
int total = tablePrice.Rows.Count;
using (var sqliteCon = new SQLiteConnection(_connectionStringToLocalDb))
{
sqliteCon.Open();
using (var cmdDeleteBeforeInsert = new SQLiteCommand(commandToDelete, sqliteCon))
using (var cmdDeleteTempOrder = new SQLiteCommand(commandToDeleteTempOrder, sqliteCon))
{
cmdDeleteBeforeInsert.ExecuteNonQuery();
if (!string.IsNullOrWhiteSpace(ActiveConsigneeName) &&
commandToDeleteTempOrder.Contains("@consignee"))
{
cmdDeleteTempOrder.Parameters.AddWithValue("@consignee", ActiveConsigneeName.Trim());
}
cmdDeleteTempOrder.ExecuteNonQuery();
}
using (var transaction = sqliteCon.BeginTransaction())
using (var cmd = new SQLiteCommand(commandToInsert, sqliteCon, transaction))
{
var pGuidEs = cmd.Parameters.Add("@guid_es", DbType.String);
var pEsCode = cmd.Parameters.Add("@es_code", DbType.String);
var pSupplierId = cmd.Parameters.Add("@supplier_id", DbType.String);
var pDrugName = cmd.Parameters.Add("@DrugName", DbType.String);
var pSupplierName = cmd.Parameters.Add("@SupplierName", DbType.String);
var pPrice = cmd.Parameters.Add("@Price", DbType.String);
var pQuantity = cmd.Parameters.Add("@Quantity", DbType.String);
var pExpiry = cmd.Parameters.Add("@ExpiryPeriod", DbType.String);
var pDescription = cmd.Parameters.Add("@Description", DbType.String);
var pZakaz = cmd.Parameters.Add("@Zakaz", DbType.String);
var pSumma = cmd.Parameters.Add("@SummaZakaza", DbType.String);
var pSupplierPriceId = cmd.Parameters.Add("@supplier_price_id", DbType.String);
var pTradeName = cmd.Parameters.Add("@TradeName", DbType.String);
var pDosage = cmd.Parameters.Add("@Dosage", DbType.String);
var pMarkup = cmd.Parameters.Add("@MarkupPercent", DbType.Object);
int saved = 0;
bool hasTradeName = tablePrice.Columns.Contains("TradeName");
bool hasDosage = tablePrice.Columns.Contains("Dosage");
bool hasMarkup = tablePrice.Columns.Contains("MarkupPercent");
foreach (DataRow row in tablePrice.Rows)
{
pGuidEs.Value = row["guid_es"] ?? DBNull.Value;
pEsCode.Value = row["es_code"] ?? DBNull.Value;
pSupplierId.Value = row["supplier_id"] ?? DBNull.Value;
pDrugName.Value = row["DrugName"] ?? DBNull.Value;
pSupplierName.Value = row["SupplierName"] ?? DBNull.Value;
pPrice.Value = row["Price"] ?? DBNull.Value;
pQuantity.Value = row["Quantity"] ?? DBNull.Value;
pExpiry.Value = row["ExpiryPeriod"] ?? DBNull.Value;
pDescription.Value = row["Description"] ?? DBNull.Value;
pZakaz.Value = row["Zakaz"] ?? DBNull.Value;
pSumma.Value = row["SummaZakaza"] ?? DBNull.Value;
pSupplierPriceId.Value = row["supplier_price_id"] ?? DBNull.Value;
pTradeName.Value = hasTradeName ? (row["TradeName"] ?? DBNull.Value) : DBNull.Value;
pDosage.Value = hasDosage ? (row["Dosage"] ?? DBNull.Value) : DBNull.Value;
pMarkup.Value = hasMarkup ? (row["MarkupPercent"] ?? DBNull.Value) : DBNull.Value;
cmd.ExecuteNonQuery();
saved++;
if (progress != null && (saved % 250 == 0 || saved == total))
{
progress.Report(new CountProgress(saved, total));
}
}
transaction.Commit();
}
}
}
private static int MapPhasePercent(long current, long total, int phaseStart, int phaseEnd)
{
if (total <= 0)
{
return phaseStart;
}
double ratio = Math.Max(0.0, Math.Min(1.0, (double)current / total));
return phaseStart + (int)((phaseEnd - phaseStart) * ratio);
}
private static int PercentOf(long current, long total)
{
if (total <= 0)
{
return 0;
}
return (int)Math.Max(0, Math.Min(100, (100.0 * current) / total));
}
private static string FormatBytes(long bytes)
{
if (bytes < 1024)
{
return $"{bytes} Б";
}
if (bytes < 1024 * 1024)
{
return $"{bytes / 1024.0:0.0} КБ";
}
return $"{bytes / (1024.0 * 1024.0):0.00} МБ";
}
private void SetOverallProgress(int percent, string text)
{
if (IsDisposed)
{
return;
}
if (InvokeRequired)
{
BeginInvoke(new Action<int, string>(SetOverallProgress), percent, text);
return;
}
if (progressDownload.Style != ProgressBarStyle.Continuous)
{
progressDownload.Style = ProgressBarStyle.Continuous;
}
progressDownload.Minimum = 0;
progressDownload.Maximum = 100;
progressDownload.Value = Math.Max(0, Math.Min(100, percent));
lblProgress.Text = FormatProgressText(percent, text);
lblProgress.Refresh();
progressDownload.Refresh();
}
private void ConfigureLogView()
{
rtxtDebug.ReadOnly = true;
rtxtDebug.WordWrap = true;
rtxtDebug.ScrollBars = RichTextBoxScrollBars.Vertical;
rtxtDebug.HideSelection = false;
rtxtDebug.DetectUrls = false;
rtxtDebug.BorderStyle = BorderStyle.FixedSingle;
if (rtxtDebug.Font.Name != "Consolas")
{
rtxtDebug.Font = new Font("Consolas", 9f);
}
lblProgress.AutoEllipsis = false;
lblProgress.UseMnemonic = false;
}
private static string FormatProgressText(int percent, string text)
{
return WrapLongText($"{Math.Max(0, Math.Min(100, percent))}% — {text}", 78, 2);
}
private static string WrapLongText(string text, int maxLineLength, int maxLines)
{
if (string.IsNullOrEmpty(text) || text.Length <= maxLineLength)
{
return text ?? string.Empty;
}
var lines = new List<string>();
var remaining = text.Trim();
while (!string.IsNullOrEmpty(remaining) && lines.Count < maxLines)
{
if (remaining.Length <= maxLineLength)
{
lines.Add(remaining);
break;
}
var breakIndex = remaining.LastIndexOf(' ', Math.Min(maxLineLength, remaining.Length - 1));
if (breakIndex <= 0)
{
breakIndex = maxLineLength;
}
lines.Add(remaining.Substring(0, breakIndex).TrimEnd());
remaining = remaining.Substring(breakIndex).TrimStart();
}
if (!string.IsNullOrEmpty(remaining) && lines.Count >= maxLines)
{
var lastLine = lines[lines.Count - 1];
if (lastLine.Length > maxLineLength - 3)
{
lastLine = lastLine.Substring(0, Math.Max(0, maxLineLength - 3)).TrimEnd();
}
lines[lines.Count - 1] = lastLine + "...";
}
return string.Join(Environment.NewLine, lines);
}
private void AppendStatus(string message)
{
if (InvokeRequired)
{
BeginInvoke(new Action<string>(AppendStatus), message);
return;
}
var wrapped = WrapLongText(message ?? string.Empty, 96, int.MaxValue);
rtxtDebug.AppendText(wrapped + Environment.NewLine);
rtxtDebug.SelectionStart = rtxtDebug.TextLength;
rtxtDebug.SelectionLength = 0;
rtxtDebug.ScrollToCaret();
rtxtDebug.Refresh();
AppDebugLog.Info("Download", message);
}
private void HF_DownloadDataFromServer_FormClosing(object sender, FormClosingEventArgs e)
{
if (_isDownloading)
{
e.Cancel = true;
}
}
private void btnCloseThis_Click(object sender, EventArgs e)
{
Close();
}
private sealed class CountProgress
{
public CountProgress(int current, int total)
{
Current = current;
Total = total;
}
public int Current { get; }
public int Total { get; }
}
}
}