Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
157 commits
Select commit Hold shift + click to select a range
c35bd46
Linux/Avalonia: первый проход по ошибкам компиляции
ksv47 Aug 21, 2026
0f644f8
Linux/Avalonia: закрыта группа однозначных правок
ksv47 Aug 21, 2026
95b81b3
Linux/Avalonia: закрыты Visibility и Control против TemplatedControl
ksv47 Aug 21, 2026
0236a4a
Linux/Avalonia: сборка проходит, закрыта последняя группа ошибок
ksv47 Aug 21, 2026
96c9df8
Linux/Avalonia: приложение впервые запускается
ksv47 Aug 21, 2026
1f14888
Linux/Avalonia: тема оформления и корректный выход на старте
ksv47 Aug 21, 2026
b0c09c2
Linux/Avalonia: синхронные диалоги перестали вешать интерфейс
ksv47 Aug 21, 2026
d2292de
Порт на Linux: правки по встречному аудиту перед публикацией
ksv47 Aug 21, 2026
b78d889
Linux/Avalonia: пути к данным 1С и читаемость тёмной темы
ksv47 Aug 21, 2026
b710d07
Linux/Avalonia: новый корень кеша не отдаётся в поиск осиротевших кат…
ksv47 Aug 21, 2026
f7c5ddc
Linux/Avalonia: поиск платформы работает на штатной раскладке установки
ksv47 Aug 22, 2026
a97c816
Linux/Avalonia: определение окружения рабочего стола
ksv47 Aug 22, 2026
900db24
Linux/Avalonia: потребители binDir приведены к новому контракту
ksv47 Aug 22, 2026
5bd792d
Linux/Avalonia: у каждого файлового менеджера свой способ показа файла
ksv47 Aug 22, 2026
c7cb385
Linux/Avalonia: Этап 3, заработал список баз и добавление базы
ksv47 Aug 22, 2026
5e6374b
Linux/Avalonia: правки Этапа 3 по встречному аудиту
ksv47 Aug 22, 2026
2a88871
Linux/Avalonia: правки по второму аудиту Этапа 3
ksv47 Aug 22, 2026
93e1dae
Linux/Avalonia: состояние дерева, сортировка групп и порядок узлов
ksv47 Aug 22, 2026
88d893d
Linux/Avalonia: ключ хранения свёрнутости только для групп и служебны…
ksv47 Aug 22, 2026
640a4c9
Linux/Avalonia: правки по аудиту состояния дерева
ksv47 Aug 22, 2026
ab6a23d
Linux/Avalonia: панель отбора по тегам
ksv47 Aug 22, 2026
316ebb3
Linux/Avalonia: раскладка панели тегов приведена к эталону
ksv47 Aug 22, 2026
08b22cb
Linux/Avalonia: правки панели тегов по встречному аудиту
ksv47 Aug 22, 2026
8f3cc73
Linux/Avalonia: утечка подписок содержимого, двойная пересборка, сохр…
ksv47 Aug 22, 2026
b69489f
Linux/Avalonia: список баз стал таблицей с колонками
ksv47 Aug 22, 2026
c28e936
Linux/Avalonia: правки таблицы колонок по двум встречным аудитам
ksv47 Aug 22, 2026
eeba359
Linux/Avalonia: панель инструментов над списком и сортировка кликом
ksv47 Aug 22, 2026
d4d27f2
Linux/Avalonia: правки панели инструментов по двум встречным аудитам
ksv47 Aug 22, 2026
a1bcf18
Linux/Avalonia: правки по третьему аудиту панели инструментов
ksv47 Aug 22, 2026
af45d7d
Linux/Avalonia: ширина колонок списка меняется перетаскиванием
ksv47 Aug 22, 2026
89d7d99
Linux/Avalonia: правки перетаскивания колонок по двум встречным аудитам
ksv47 Aug 22, 2026
d15384b
Слияние апстрима 8befbad
ksv47 Aug 22, 2026
325262c
Linux/Avalonia: контекстное меню строки и горячие клавиши
ksv47 Aug 22, 2026
2684c86
Linux/Avalonia: правки после слияния с апстримом 8befbad
ksv47 Aug 22, 2026
3419f08
Linux/Avalonia: правки меню и клавиш по двум встречным аудитам
ksv47 Aug 22, 2026
42a3dcb
Linux/Avalonia: теги в строке базы и переключатель показа тегов
ksv47 Aug 22, 2026
54576d5
Linux/Avalonia: правки тегов строки по двум встречным аудитам
ksv47 Aug 22, 2026
a1b2937
Linux/Avalonia: блок текущей сессии и полные сведения о подключении
ksv47 Aug 22, 2026
b6f4386
Linux/Avalonia: блок сессии заработал, правки по двум встречным аудитам
ksv47 Aug 22, 2026
915fb5e
Linux/Avalonia: удаление базы и группы, переход по ссылке
ksv47 Aug 22, 2026
b7671be
Linux/Avalonia: правки удаления и ссылки по двум встречным аудитам
ksv47 Aug 22, 2026
2957420
Linux/Avalonia: вкладка «Отображение» в настройках
ksv47 Aug 22, 2026
daa4366
Linux/Avalonia: правки вкладки «Отображение» по двум встречным аудитам
ksv47 Aug 22, 2026
6d3419a
Linux/Avalonia: вкладка «Платформы», пустой выбор в правой панели, те…
ksv47 Aug 22, 2026
96fed92
Linux/Avalonia: правки вкладки «Платформы» по двум встречным аудитам
ksv47 Aug 22, 2026
5a9bac5
Linux/Avalonia: вкладка «Базы» и автосинхронизация с ibases.v8i
ksv47 Aug 22, 2026
d3e6ba2
Linux/Avalonia: правки синхронизации по двум встречным аудитам
ksv47 Aug 22, 2026
3aaba7e
Linux/Avalonia: переназначение горячих клавиш
ksv47 Aug 22, 2026
84a8d58
Linux/Avalonia: правки клавиш по двум встречным аудитам
ksv47 Aug 22, 2026
b480522
Linux/Avalonia: редактор цветовых схем в настройках
ksv47 Aug 22, 2026
ba7fafe
Linux/Avalonia: правки редактора схем по двум встречным аудитам
ksv47 Aug 22, 2026
7177056
Merge pull request #54 from ksv47/linux-fixes
sivatorov Aug 23, 2026
21dd22a
new-version
sivatorov Aug 23, 2026
cde32b7
fix-bugs
sivatorov Aug 23, 2026
31ef487
Linux/Avalonia: перетаскивание баз и групп мышью
ksv47 Aug 22, 2026
b9039d2
Linux/Avalonia: правки перетаскивания по двум встречным аудитам
ksv47 Aug 23, 2026
631de1c
Linux/Avalonia: действие после запуска базы или конфигуратора
ksv47 Aug 23, 2026
b14e04e
Linux/Avalonia: правки действия после запуска по двум встречным аудитам
ksv47 Aug 23, 2026
d720a4b
new-version
sivatorov Aug 23, 2026
8ce37b2
Merge branch 'main' of https://github.com/sivatorov/ConfigurationMana…
sivatorov Aug 23, 2026
36e4917
Linux/Avalonia: выбор и подсветка переживают пересборку дерева
ksv47 Aug 23, 2026
99209d4
Linux/Avalonia: правки восстановления выбора по двум встречным аудитам
ksv47 Aug 23, 2026
1f409f7
Merge pull request #55 from ksv47/linux-fixes
sivatorov Aug 23, 2026
c64d7b9
new-version
sivatorov Aug 23, 2026
f029a59
Linux/Avalonia: подписки диалогов на ресурсы темы освобождаются
ksv47 Aug 23, 2026
c685fc1
Linux/Avalonia: правки освобождения подписок по двум встречным аудитам
ksv47 Aug 23, 2026
b0d54e1
Linux/Avalonia: окна сообщений оформлены темой
ksv47 Aug 23, 2026
5c8be7c
Linux/Avalonia: правки окон сообщений по двум встречным аудитам
ksv47 Aug 23, 2026
33e5169
Linux/Avalonia: кисти темы через динамический ресурс вместо ручных по…
ksv47 Aug 23, 2026
2b18492
Linux/Avalonia: правки динамического ресурса по двум встречным аудитам
ksv47 Aug 23, 2026
ae1044e
Linux/Avalonia: оставшиеся подписки на кисти привязаны к жизни элемента
ksv47 Aug 23, 2026
ad281c0
Linux/Avalonia: правки подписок по двум встречным аудитам
ksv47 Aug 23, 2026
419e8e5
Merge remote-tracking branch 'origin/main' into linux-fixes
ksv47 Aug 23, 2026
c7846e7
Linux/Avalonia: позиция прокрутки переживает пересборку дерева
ksv47 Aug 23, 2026
0d52b4d
Linux/Avalonia: правки прокрутки по двум встречным аудитам
ksv47 Aug 23, 2026
6137b67
Merge pull request #56 from ksv47/linux-fixes
sivatorov Aug 23, 2026
9bf3a12
new-version
sivatorov Aug 23, 2026
df811c4
fix
sivatorov Aug 23, 2026
2aae206
Linux/Avalonia: выпадающие меню у кнопок запуска
ksv47 Aug 23, 2026
8a7ec82
Linux/Avalonia: правки меню запуска по двум встречным аудитам
ksv47 Aug 23, 2026
bd807c1
Linux/Avalonia: вертикальная полоса списка не уезжает за границу
ksv47 Aug 23, 2026
a64f8b6
Linux/Avalonia: правки полосы списка по двум встречным аудитам
ksv47 Aug 23, 2026
8ed0140
Linux/Avalonia: правки полосы по второму кругу аудитов
ksv47 Aug 23, 2026
faf97e2
Linux/Avalonia: PageUp, PageDown, Home и End прокручивают список
ksv47 Aug 23, 2026
cff5ff8
Linux/Avalonia: правки клавиш и колеса по третьему кругу аудитов
ksv47 Aug 23, 2026
61c27a0
Linux/Avalonia: убраны задвоенные ключи после слияния
ksv47 Aug 23, 2026
d6d7624
new-version
sivatorov Aug 23, 2026
6eb1845
Merge pull request #58 from ksv47/linux-fixes
sivatorov Aug 23, 2026
6d838d2
new-version
sivatorov Aug 23, 2026
0fe2ba2
Linux/Avalonia: каталоги шаблонов и операции со списком баз
ksv47 Aug 23, 2026
845bd9f
Linux/Avalonia: правки вкладки «Базы» по двум встречным аудитам
ksv47 Aug 23, 2026
ce5ca03
new-version
sivatorov Aug 23, 2026
cd1a14d
Merge branch 'main' into linux-fixes
sivatorov Aug 23, 2026
ebd4c81
Merge pull request #59 from ksv47/linux-fixes
sivatorov Aug 23, 2026
c956f47
new-version
sivatorov Aug 23, 2026
7328c0b
Merge branch 'main' of https://github.com/sivatorov/ConfigurationMana…
sivatorov Aug 23, 2026
569b063
Linux/Avalonia: обслуживание и опасные операции на вкладке «Базы»
ksv47 Aug 23, 2026
6460478
Linux/Avalonia: правки обслуживания по двум встречным аудитам
ksv47 Aug 23, 2026
6b88813
Linux/Avalonia: правки обслуживания по второму кругу аудитов
ksv47 Aug 23, 2026
f570ae3
Linux/Avalonia: счётчик выбранных баз не наезжает на кнопки
ksv47 Aug 23, 2026
1f3c3ba
Linux/Avalonia: настройки трея начали работать
ksv47 Aug 23, 2026
e0f077a
Linux/Avalonia: правки трея по двум встречным аудитам
ksv47 Aug 23, 2026
e0434f7
Linux/Avalonia: правки трея по второму кругу аудитов
ksv47 Aug 23, 2026
b77e0ab
Linux/Avalonia: правки по конечному аудиту перед отправкой
ksv47 Aug 23, 2026
bc98be8
Linux/Avalonia: правки по конечному аудиту всей ветки
ksv47 Aug 23, 2026
6352022
Linux/Avalonia: упаковка в deb и AppImage заработала
ksv47 Aug 23, 2026
dcca962
Linux/Avalonia: правки упаковки по двум встречным аудитам
ksv47 Aug 23, 2026
ccea4fa
Порт на Linux: кнопка тегов больше не налезает на подпись «Название»
ksv47 Aug 23, 2026
7183e37
Порт на Linux: читаемый справочник ключей в параметрах запуска
ksv47 Aug 23, 2026
9ea19a1
Порт на Linux: меню трея показывает текущее состояние списка
ksv47 Aug 23, 2026
da14799
Порт на Linux: правки по двум встречным аудитам
ksv47 Aug 23, 2026
9ef0910
Merge pull request #60 from ksv47/linux-fixes
sivatorov Aug 24, 2026
488d540
Merge pull request #61 from ksv47/linux-fixes-2
sivatorov Aug 24, 2026
9ff5bf7
CHANGELOG
sivatorov Aug 24, 2026
446e7df
new-version
sivatorov Aug 24, 2026
702e30b
new-version
sivatorov Aug 24, 2026
6069b96
new-version
sivatorov Aug 24, 2026
9101016
new-version
sivatorov Aug 24, 2026
b505486
new-version
sivatorov Aug 24, 2026
15ddfb9
new-version
sivatorov Aug 24, 2026
c328e87
new-version
sivatorov Aug 24, 2026
fc1da83
new-version
sivatorov Aug 24, 2026
c71da36
Порт на Linux: приложение не запускалось при недоступном каталоге нас…
ksv47 Aug 24, 2026
5e8bbdb
Порт на Linux: кнопки правой панели в один столбец, честные подсказки
ksv47 Aug 24, 2026
41fd601
Порт на Linux: правки по встречному аудиту первого этапа
ksv47 Aug 24, 2026
c3fb641
Merge pull request #68 from ksv47/linux-fixes-4
sivatorov Aug 25, 2026
b7d3394
Merge pull request #66 from ksv47/linux-fixes-3
sivatorov Aug 25, 2026
ee66c43
del-exe
sivatorov Aug 25, 2026
7d6118e
fix
sivatorov Aug 25, 2026
e7562ff
Update .gitignore
sivatorov Aug 25, 2026
3a1d093
del-old-files
sivatorov Aug 25, 2026
e7c1982
new-version
sivatorov Aug 25, 2026
29d1f11
new-version
sivatorov Aug 25, 2026
94672d9
Вынести COM-коннектор 1С в отдельный процесс
ksv47 Aug 24, 2026
4879dee
COM-агент: постоянный процесс, пароль вне командной строки, честный т…
ksv47 Aug 24, 2026
6bebee2
COM-агент: маскировка пароля, незамерзающее закрытие, устойчивая защёлка
ksv47 Aug 24, 2026
2fbcfc4
COM-агент: маскировка разбором, сброс без монитора, честная диагностика
ksv47 Aug 24, 2026
53a22e8
COM-агент: вырезание пароля по значению, номер запроса, подавление WER
ksv47 Aug 24, 2026
fd272d3
COM-агент: самодостаточная маскировка, точный секрет, состояние под о…
ksv47 Aug 25, 2026
cbaf2c6
COM-агент: не пересылать текст ошибки при наличии пароля
ksv47 Aug 25, 2026
33f9b80
COM-агент: решение о тексте ошибки принимает родитель
ksv47 Aug 25, 2026
679314c
COM-агент: решение о тексте по строке подключения, а не по догадке
ksv47 Aug 25, 2026
2def952
Десятая редакция: признак пароля от сборщика строки, одна таблица раз…
ksv47 Aug 25, 2026
10cddd3
Одиннадцатая редакция: V85 в переборе, накопление диагнозов, разряд B…
ksv47 Aug 25, 2026
67275af
Двенадцатая редакция: экранирование строки подключения, V85 в регистр…
ksv47 Aug 25, 2026
b16f7ec
Тринадцатая редакция: диагноз переживает гибель агента
ksv47 Aug 25, 2026
fa74986
Четырнадцатая редакция: сериализация записи агента, честный успех рег…
ksv47 Aug 25, 2026
a2f8ecc
Пятнадцатая редакция: юнит-тесты на протокол и строку подключения
ksv47 Aug 25, 2026
b732dbe
Шестнадцатая редакция: форма подробности проверяется на приёме
ksv47 Aug 25, 2026
122edd4
new-version
sivatorov Aug 25, 2026
1fd2430
Merge branch 'main' into win-debug
sivatorov Aug 25, 2026
2cdcda6
Merge pull request #69 from ksv47/win-debug
sivatorov Aug 25, 2026
8c7ac10
new-version
sivatorov Aug 25, 2026
9c10fba
new-version
sivatorov Aug 25, 2026
c94f023
new-version
sivatorov Aug 25, 2026
7bde386
CI: сборка обеих целей, Linux и Windows
ksv47 Aug 26, 2026
361b2ef
Linux/Avalonia: сборка апстрима 0.3.4.15 не собиралась
ksv47 Aug 26, 2026
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
65 changes: 65 additions & 0 deletions .github/workflows/build.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,65 @@
# =============================================================================
# Сборка обеих целей проекта на каждый push в main и на каждый pull request.
#
# Целевой фреймворк выбирается в csproj по операционной системе сборки:
# на Windows это net10.0-windows (WPF), на Linux net10.0 (Avalonia).
# Поэтому одной машиной обе цели не проверить: сборка на Windows не
# компилирует ни одного файла *.Avalonia.cs, а сборка на Linux не
# компилирует WPF-часть. Отсюда две задачи, по одной на систему.
#
# Каждая задача вызывает тот же скрипт, которым проект собирается вручную
# (build.ps1 и build.sh), чтобы CI и локальная сборка не разъезжались:
# в Release-конфигурации заданы RuntimeIdentifier и single-file, и оба
# скрипта их гасят для обычной сборки.
# =============================================================================
name: Сборка

on:
push:
branches: [main]
pull_request:
workflow_dispatch:

# Сборке хватает чтения: ничего не публикует и не пишет в репозиторий.
permissions:
contents: read

# Новый коммит в ветку отменяет незаконченный прогон по ней же,
# иначе на активной ветке копится очередь параллельных сборок.
concurrency:
group: ${{ github.workflow }}-${{ github.ref }}
cancel-in-progress: true

jobs:
windows:
name: Windows (WPF, net10.0-windows)
runs-on: windows-latest
timeout-minutes: 30
steps:
- uses: actions/checkout@v7

- name: Установка .NET SDK
uses: actions/setup-dotnet@v6
with:
dotnet-version: '10.0.x'

- name: Сборка Release
shell: pwsh
working-directory: Configuration Management
run: ./build.ps1 -Configuration Release

linux:
name: Linux (Avalonia, net10.0)
runs-on: ubuntu-latest
timeout-minutes: 30
steps:
- uses: actions/checkout@v7

- name: Установка .NET SDK
uses: actions/setup-dotnet@v6
with:
dotnet-version: '10.0.x'

- name: Сборка Release
working-directory: Configuration Management
run: ./build.sh Release
11 changes: 10 additions & 1 deletion .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,8 @@ obj/
*.suo
*.userosscache
*.sln.docstates
dist/
*_wpftmp.csproj

## Build results
[Dd]ebug/
Expand Down Expand Up @@ -35,4 +37,11 @@ packages/
## OS
Thumbs.db
Desktop.ini
.DS_Store
.DS_Store

# Артефакты упаковки Linux: собираются скриптами из package/linux,
# в репозитории нужны только сами скрипты.
package/linux/AppDir/
package/linux/out/
package/linux/deb/staging/
package/linux/deb/out/
561 changes: 561 additions & 0 deletions CHANGELOG.md

Large diffs are not rendered by default.

1 change: 1 addition & 0 deletions Configuration Management/AddEditWindow.Avalonia.cs
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
#if LINUX
using System;
using Avalonia;
using Avalonia.Controls;
using Avalonia.Layout;
using Avalonia.Media;
Expand Down
4 changes: 3 additions & 1 deletion Configuration Management/AddEditWindow.xaml.cs
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
#if WINDOWS
using System.Windows;
using System.Windows.Controls;

Expand Down Expand Up @@ -36,4 +37,5 @@ private void OnNext_Click(object sender, RoutedEventArgs e)
DialogResult = true;
}
}
}
}
#endif
1 change: 1 addition & 0 deletions Configuration Management/App.axaml
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@
<ResourceDictionary>
<ResourceDictionary.MergedDictionaries>
<ResourceInclude Source="avares://ConfigurationManagement/Themes/Icons.axaml" />
<ResourceInclude Source="avares://ConfigurationManagement/Themes/LightTheme.axaml" />
</ResourceDictionary.MergedDictionaries>
</ResourceDictionary>
</Application.Resources>
Expand Down
71 changes: 59 additions & 12 deletions Configuration Management/App.axaml.cs
Original file line number Diff line number Diff line change
Expand Up @@ -30,11 +30,15 @@ public partial class App : Application
private const string LockFileName = "configuration-management.lock";
private const string ActivateFileName = "activate";

/// <summary>Каталог данных приложения (например ~/.config/ConfigurationManagement).</summary>
/// <summary>
/// Каталог данных приложения (например ~/.config/ConfigurationManagement).
/// Считается тем же механизмом, что и у репозитория настроек: расчёт
/// напрямую через SpecialFolder.ApplicationData на Linux даёт пустую
/// строку, когда каталога из XDG_CONFIG_HOME нет, путь становится
/// относительным, и приложение молча не запускается.
/// </summary>
private static string DataDirectory =>
_dataDir ??= Path.Combine(
Environment.GetFolderPath(Environment.SpecialFolder.ApplicationData),
"ConfigurationManagement");
_dataDir ??= Services.PlatformPaths.AppDataDirectory;

public override void Initialize()
{
Expand Down Expand Up @@ -72,6 +76,29 @@ public override void OnFrameworkInitializationCompleted()
try { settings = repository.LoadSettings(); }
catch { settings = new AppSettings(); }

// Восстановление профиля из указанного каталога резервной копии
// (например, после переустановки системы): настройки, список баз
// (с пользователями и паролями запуска), группы и ibases.v8i.
// Файлы копируются до загрузки данных главным окном, поэтому приложение
// сразу открывается с привычным состоянием. Настройки перечитываются,
// чтобы последующие этапы запуска использовали восстановленные значения.
if (settings.ProfileRestoreOnStartup
&& !string.IsNullOrWhiteSpace(settings.ProfileBackupDirectory)
&& ProfileBackupService.HasBackup(settings.ProfileBackupDirectory))
{
try
{
ProfileBackupService.Restore(settings.ProfileBackupDirectory, settings.IbasesSyncFilePath);
try { settings = repository.LoadSettings(); }
catch { /* оставляем уже прочитанные настройки */ }
}
catch (Exception ex)
{
// Сбой восстановления не должен блокировать запуск.
Console.Error.WriteLine("[profile] Ошибка восстановления профиля: " + ex.Message);
}
}

// Инициализируем локализацию: выбранный или системный язык, а также
// загружаем внешние языки (.json) из папок Languages (рядом с приложением
// и в каталоге данных).
Expand Down Expand Up @@ -101,14 +128,25 @@ public override void OnFrameworkInitializationCompleted()
{
_desktopLifetime = desktop;

// Применяем сохранённую цветовую схему (тему оформления).
if (settings.ActiveColorScheme is { Colors.Count: > 0 })
ThemeManager.ApplyScheme(settings.ActiveColorScheme);
else
ThemeManager.ApplyTheme(
string.IsNullOrWhiteSpace(settings.Theme)
? ThemeManager.LightThemeName
: settings.Theme);
// Применяем сохранённую цветовую схему активной базовой темы. Предпочитаем
// раздельную схему для светлой/тёмной темы, иначе — старый одиночный
// ActiveColorScheme (миграция) или встроенные цвета.
var activeTheme = string.IsNullOrWhiteSpace(settings.Theme)
? (settings.ActiveColorScheme?.IsDark == true ? ThemeManager.DarkThemeName : ThemeManager.LightThemeName)
: settings.Theme;
var isDark = string.Equals(activeTheme, ThemeManager.DarkThemeName, StringComparison.OrdinalIgnoreCase);
Configuration_Management.Models.ColorScheme? activeScheme = isDark
? settings.DarkColorScheme
: settings.LightColorScheme;
if (activeScheme is not { Colors.Count: > 0 }
&& settings.ActiveColorScheme is { Colors.Count: > 0 }
&& settings.ActiveColorScheme.IsDark == isDark)
{
activeScheme = settings.ActiveColorScheme;
}
ThemeManager.ApplyScheme(activeScheme ?? (isDark
? Configuration_Management.Models.ColorScheme.CreateDark()
: Configuration_Management.Models.ColorScheme.CreateLight()));

// Компактный режим интерфейса (влияет на метрики отступов/иконок,
// должен быть установлен до построения главного окна).
Expand Down Expand Up @@ -141,6 +179,15 @@ public override void OnFrameworkInitializationCompleted()
base.OnFrameworkInitializationCompleted();
}

/// <summary>
/// Завершает приложение на этапе запуска. У Avalonia Application нет метода Shutdown,
/// а desktop.Shutdown здесь не годится: оба вызова происходят внутри
/// OnFrameworkInitializationCompleted, то есть до входа в цикл сообщений, и гасят
/// Dispatcher раньше времени. Тогда MainLoop падает с InvalidOperationException
/// «Cannot perform requested operation because the Dispatcher shut down».
/// </summary>
private static void Shutdown(int exitCode = 0) => Environment.Exit(exitCode);

/// <summary>
/// Захватывает исключительный файловый lock (один экземпляр на Linux).
/// Файл-блокировка в каталоге данных с FileShare.None.
Expand Down
100 changes: 91 additions & 9 deletions Configuration Management/App.xaml.cs
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
#if WINDOWS
using System.Reflection;
using System.Runtime.InteropServices;
using System.Text;
Expand All @@ -21,16 +22,23 @@ public partial class App : Application

protected override void OnStartup(StartupEventArgs e)
{
// Режим COM-агента перехватывается раньше, в Program.Main: агенту не нужны
// ни WPF, ни ресурсные словари тем. См. ComReadHost.

// Показываем любые необработанные ошибки — иначе окно просто не появляется.
DispatcherUnhandledException += (_, args) =>
{
LogFatal(LocalizationManager.T("App.Fatal.Interface"), args.Exception);
ShowFatalError(LocalizationManager.T("App.Fatal.Interface"), args.Exception);
args.Handled = true;
};
AppDomain.CurrentDomain.UnhandledException += (_, args) =>
{
if (args.ExceptionObject is Exception ex)
{
LogFatal(LocalizationManager.T("App.Fatal.Critical"), ex);
ShowFatalError(LocalizationManager.T("App.Fatal.Critical"), ex);
}
};
TaskScheduler.UnobservedTaskException += (_, args) =>
{
Expand All @@ -54,6 +62,29 @@ protected override void OnStartup(StartupEventArgs e)
settings = new AppSettings();
}

// Восстановление профиля из указанного каталога резервной копии
// (например, после переустановки системы): настройки, список баз
// (с пользователями и паролями запуска), группы и ibases.v8i.
// Файлы копируются до загрузки данных главным окном, поэтому приложение
// сразу открывается с привычным состоянием. Настройки перечитываются,
// чтобы последующие этапы запуска использовали восстановленные значения.
if (settings.ProfileRestoreOnStartup
&& !string.IsNullOrWhiteSpace(settings.ProfileBackupDirectory)
&& ProfileBackupService.HasBackup(settings.ProfileBackupDirectory))
{
try
{
ProfileBackupService.Restore(settings.ProfileBackupDirectory, settings.IbasesSyncFilePath);
try { settings = repository.LoadSettings(); }
catch { /* оставляем уже прочитанные настройки */ }
}
catch (Exception ex)
{
// Сбой восстановления не должен блокировать запуск.
System.Diagnostics.Debug.WriteLine("[profile] Ошибка восстановления профиля: " + ex.Message);
}
}

// Инициализируем локализацию: выбираем сохранённый язык, иначе язык
// системы. Внешние языки (.json) подгружаются из папки Languages.
try
Expand Down Expand Up @@ -82,19 +113,34 @@ protected override void OnStartup(StartupEventArgs e)

base.OnStartup(e);

// Применяем сохранённую цветовую схему (тему оформления).
if (settings.ActiveColorScheme is { Colors.Count: > 0 })
// Применяем сохранённую цветовую схему (тему оформления). Предпочитаем раздельную
// схему для активной базовой темы (Light/Dark), иначе — старый одиночный
// ActiveColorScheme (миграция) или встроенные цвета.
var themeName = string.IsNullOrWhiteSpace(settings.Theme)
? ThemeManager.LightThemeName
: settings.Theme;
var isDark = string.Equals(themeName, ThemeManager.DarkThemeName, StringComparison.OrdinalIgnoreCase);
Configuration_Management.Models.ColorScheme? scheme = isDark
? settings.DarkColorScheme
: settings.LightColorScheme;
if (scheme is not { Colors.Count: > 0 }
&& settings.ActiveColorScheme is { Colors.Count: > 0 }
&& settings.ActiveColorScheme.IsDark == isDark)
{
ThemeManager.ApplyScheme(settings.ActiveColorScheme);
scheme = settings.ActiveColorScheme;
}
else
ThemeManager.ApplyScheme(scheme ?? (isDark
? Configuration_Management.Models.ColorScheme.CreateDark()
: Configuration_Management.Models.ColorScheme.CreateLight()));
try
{
// Обратная совместимость: только имя темы Light/Dark.
var theme = string.IsNullOrWhiteSpace(settings.Theme)
? ThemeManager.LightThemeName
: settings.Theme;
ThemeManager.ApplyTheme(theme);
System.IO.File.AppendAllText(
System.IO.Path.Combine(System.IO.Path.GetTempPath(), "cm_theme_debug.log"),
$"[startup] theme='{themeName}' dark={isDark} applied='{ThemeManager.CurrentScheme.Name}' " +
$"darkSlot='{settings.DarkColorScheme?.Name}' lightSlot='{settings.LightColorScheme?.Name}' " +
$"active='{settings.ActiveColorScheme?.Name}'{System.Environment.NewLine}");
}
catch { /* не критично */ }

var mainWindow = AppServices.GetRequiredService<MainWindow>();
MainWindow = mainWindow;
Expand Down Expand Up @@ -122,11 +168,35 @@ protected override void OnStartup(StartupEventArgs e)
}
catch (Exception ex)
{
LogFatal(LocalizationManager.T("App.Fatal.StartupFailed"), ex);
ShowFatalError(LocalizationManager.T("App.Fatal.StartupFailed"), ex);
Shutdown(1);
}
}

/// <summary>
/// Записывает полный стек-трейс фатальной ошибки в лог-файл (для диагностики).
/// Сам по себе не бросает исключений, даже если логирование недоступно.
/// </summary>
private static void LogFatal(string title, Exception ex)
{
try
{
var logger = AppServices.GetRequiredService<IAppLogger>();
var sb = new StringBuilder();
sb.AppendLine(title);
sb.AppendLine("Исключение: " + ex.GetType().FullName);
sb.AppendLine("Сообщение: " + ex.Message);
sb.AppendLine("StackTrace:");
sb.AppendLine(ex.ToString());
logger.Error(sb.ToString());
}
catch
{
// Логирование не должно маскировать исходную ошибку.
}
}

private static void ShowFatalError(string title, Exception ex)
{
try
Expand Down Expand Up @@ -160,6 +230,17 @@ private static void ShowFatalError(string title, Exception ex)

protected override void OnExit(ExitEventArgs e)
{
// Процесс-агент COM закрывается сам, когда закроется его stdin, но при
// нештатном завершении лучше не полагаться на это и убрать его явно.
try
{
ComReadHost.Shutdown();
}
catch
{
// ignore
}

try
{
var logger = AppServices.GetRequiredService<IAppLogger>();
Expand Down Expand Up @@ -310,3 +391,4 @@ private static void ActivateMainWindow()
private static extern bool ShowWindow(IntPtr hWnd, int nCmdShow);
}
}
#endif
Loading