From 37fdc8c90815f23ec2067b0759509dea57c5695e Mon Sep 17 00:00:00 2001
From: J0shlerB0y <86416124+J0shlerB0y@users.noreply.github.com>
Date: Tue, 28 Apr 2026 18:57:47 +0300
Subject: [PATCH 01/19] =?UTF-8?q?=D0=A1=D0=B4=D0=B5=D0=BB=D0=B0=D0=BB=20?=
=?UTF-8?q?=D1=81=D0=BE=D0=B7=D0=B4=D0=B0=D0=BD=D0=B8=D0=B5=20=D0=B1=D0=B0?=
=?UTF-8?q?=D0=B7=D1=8B=20=D0=B8=D0=B7=20=D0=BB=D0=B0=D1=83=D0=BD=D1=87?=
=?UTF-8?q?=D0=B5=D1=80=D0=B0=20=D0=B2=D1=85=D0=BE=D0=B4=D0=B0?=
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
---
QS.Cloud.Client/QSCloudProvider.cs | 4 +-
QS.DbManagement/IDbProvider.cs | 3 +-
QS.DbManagement/MariaDb/MariaDBProvider.cs | 53 ++++++++++++-------
QS.DbManagement/ProviderResponces.cs | 1 +
.../QS.Launcher.Avalonia.csproj | 2 +-
.../Pages/DataBase/CreateDataBaseWindow.axaml | 27 ++++++++++
.../DataBase/CreateDataBaseWindow.axaml.cs | 32 +++++++++++
.../Pages/{ => DataBase}/DataBasesView.axaml | 17 +++---
.../{ => DataBase}/DataBasesView.axaml.cs | 19 +++++--
QS.Launcher/DependencyInjection.cs | 1 +
QS.Launcher/QS.Launcher.csproj | 1 +
QS.Launcher/ViewModels/MainWindowVM.cs | 1 +
.../DataBase/CreateDataBaseVM.cs | 42 +++++++++++++++
.../{ => DataBase}/DataBasesVM.cs | 51 ++++++++++++------
.../ViewModels/PageViewModels/LoginVM.cs | 1 +
15 files changed, 207 insertions(+), 48 deletions(-)
create mode 100644 QS.Launcher.Avalonia/Views/Pages/DataBase/CreateDataBaseWindow.axaml
create mode 100644 QS.Launcher.Avalonia/Views/Pages/DataBase/CreateDataBaseWindow.axaml.cs
rename QS.Launcher.Avalonia/Views/Pages/{ => DataBase}/DataBasesView.axaml (83%)
rename QS.Launcher.Avalonia/Views/Pages/{ => DataBase}/DataBasesView.axaml.cs (77%)
create mode 100644 QS.Launcher/ViewModels/PageViewModels/DataBase/CreateDataBaseVM.cs
rename QS.Launcher/ViewModels/PageViewModels/{ => DataBase}/DataBasesVM.cs (80%)
diff --git a/QS.Cloud.Client/QSCloudProvider.cs b/QS.Cloud.Client/QSCloudProvider.cs
index c6e33dd8e..6ec7e78c5 100644
--- a/QS.Cloud.Client/QSCloudProvider.cs
+++ b/QS.Cloud.Client/QSCloudProvider.cs
@@ -25,6 +25,8 @@ public class QSCloudProvider : IDbProvider {
#endregion
public string UserName { get; private set; }
+ public bool CanCreateDatabase => throw new NotImplementedException();
+
private CloudFeaturesClient featuresClient;
private LoginManagementCloudClient loginClient;
private SessionManagementCloudClient sessionClient;
@@ -49,7 +51,7 @@ public bool ChangePassword(string username, string oldPassword, string newPasswo
throw new NotImplementedException();
}
- public bool CreateDatabase(string databaseName)
+ public bool CreateDatabase(string databaseName, string title)
{
throw new NotImplementedException();
}
diff --git a/QS.DbManagement/IDbProvider.cs b/QS.DbManagement/IDbProvider.cs
index da6ea6964..d0c6fd3e6 100644
--- a/QS.DbManagement/IDbProvider.cs
+++ b/QS.DbManagement/IDbProvider.cs
@@ -11,7 +11,7 @@ public interface IDbProvider : IDisposable
bool ChangePassword(string username, string oldPassword, string newPassword);
- bool CreateDatabase(string databaseName);
+ bool CreateDatabase(string databaseName, string title);
bool DropDatabase(string databaseName);
@@ -26,5 +26,6 @@ public interface IDbProvider : IDisposable
bool IsConnected { get; }
bool IsAdmin { get; }
+ bool CanCreateDatabase { get; }
}
}
diff --git a/QS.DbManagement/MariaDb/MariaDBProvider.cs b/QS.DbManagement/MariaDb/MariaDBProvider.cs
index 1e011a88b..090b993cc 100644
--- a/QS.DbManagement/MariaDb/MariaDBProvider.cs
+++ b/QS.DbManagement/MariaDb/MariaDBProvider.cs
@@ -16,7 +16,7 @@ public class MariaDBProvider : IDbProvider {
private static readonly string[] SystemDatabases = { "information_schema", "mysql", "performance_schema", "sys" };
readonly MySqlConnection connection;
- readonly MySqlConnectionStringBuilder connectionStringBuilder;
+ readonly MySqlConnectionStringBuilder ConnectionStringBuilder;
public bool IsConnected => connection.State == ConnectionState.Open;
@@ -28,10 +28,15 @@ public class MariaDBProvider : IDbProvider {
///
public bool CanCreateDatabase { get; private set; }
+ ///
+ /// Переданный в тайтл созданой базы,
+ /// нужен потом при применения скрипта с наполнением базы
+ ///
+ public string CreatedTitle { get; private set; }
+
#region Параметры подключения
public string Server { get; }
public string UserName { get; }
- public string ProductName { get; }
private readonly string password;
#endregion
@@ -39,19 +44,29 @@ public MariaDBProvider(IList parameters, string passwo
if(parameters == null)
throw new ArgumentNullException(nameof(parameters));
- Server = parameters.First(p => p.Name == "Server").Value;
+ string serverValue = parameters.First(p => p.Name == "Server").Value;
UserName = parameters.First(p => p.Name == "Login").Value;
this.password = password;
- var builder = new MySqlConnectionStringBuilder {
- Server = Server,
+ string host = serverValue;
+ uint? port = null;
+ if(serverValue.Contains(":")) {
+ var parts = serverValue.Split(':');
+ host = parts[0];
+ if(uint.TryParse(parts[1], out var parsedPort))
+ port = parsedPort;
+ }
+ Server = serverValue;
+
+ ConnectionStringBuilder = new MySqlConnectionStringBuilder {
+ Server = host,
UserID = UserName,
Password = password,
- AllowUserVariables = true,
- ConvertZeroDateTime = true
+ AllowUserVariables = true
};
- connectionStringBuilder = builder;
- connection = new MySqlConnection(builder.ConnectionString);
+ if(port != null)
+ ConnectionStringBuilder.Port = port.Value;
+ connection = new MySqlConnection(ConnectionStringBuilder.ConnectionString);
}
#region IDbProvider
@@ -70,13 +85,13 @@ public LoginToServerResponse LoginToServer() {
CanCreateDatabase = IsAdmin || grants.Any(g =>
g.IndexOf("ALL PRIVILEGES", StringComparison.OrdinalIgnoreCase) >= 0
- || g.IndexOf("CREATE", StringComparison.OrdinalIgnoreCase) >= 0);
- //todo : разбить на токены и одним циклом по массиву прав проставить булы
+ || g.IndexOf("CREATE", StringComparison.OrdinalIgnoreCase) >= 0);
return new LoginToServerResponse {
Success = true,
IsAdmin = IsAdmin,
- NeedToUpdateLauncher = false
+ NeedToUpdateLauncher = false,
+ CanCreateDatabase = CanCreateDatabase
};
}
catch(MySqlException ex) {
@@ -125,8 +140,7 @@ public List GetUserDatabases(IApplicationInfo applicationInfo) {
result.Add(new DbInfo {
BaseName = dbName,
Title = title ?? dbName,
- Version = version,
- CanCreateDatabase = CanCreateDatabase
+ Version = version
});
}
@@ -135,15 +149,14 @@ public List GetUserDatabases(IApplicationInfo applicationInfo) {
public LoginToDatabaseResponse LoginToDatabase(DbInfo dbInfo) {
try {
- connectionStringBuilder.Database = dbInfo.BaseName;
+ ConnectionStringBuilder.Database = dbInfo.BaseName;
return new LoginToDatabaseResponse {
Success = true,
- ConnectionString = connectionStringBuilder.ConnectionString,
+ ConnectionString = ConnectionStringBuilder.ConnectionString,
Login = UserName,
Parameters = new Dictionary {
- { "BaseTitle", dbInfo.Title },
- { "SessionId", string.Empty }
+ { "BaseTitle", dbInfo.Title }
}
};
}
@@ -165,7 +178,8 @@ public bool ChangePassword(string username, string oldPassword, string newPasswo
return connection.Execute(sql) != 0;
}
- public bool CreateDatabase(string databaseName) {
+ public bool CreateDatabase(string databaseName, string title) {
+ CreatedTitle = title;
string sql = $"CREATE DATABASE IF NOT EXISTS `{databaseName}`";
return connection.Execute(sql) != 0;
}
@@ -178,7 +192,6 @@ public bool DropDatabase(string databaseName) {
public void Dispose() {
connection?.Dispose();
}
-
#endregion
}
}
diff --git a/QS.DbManagement/ProviderResponces.cs b/QS.DbManagement/ProviderResponces.cs
index b08cf0a57..b9e6dfa62 100644
--- a/QS.DbManagement/ProviderResponces.cs
+++ b/QS.DbManagement/ProviderResponces.cs
@@ -9,6 +9,7 @@ public class Response {
public class LoginToServerResponse : Response
{
+ public bool CanCreateDatabase { get; set; }
public bool IsAdmin { get; set; }
public bool NeedToUpdateLauncher { get; set; }
}
diff --git a/QS.Launcher.Avalonia/QS.Launcher.Avalonia.csproj b/QS.Launcher.Avalonia/QS.Launcher.Avalonia.csproj
index 0573db99d..553012330 100644
--- a/QS.Launcher.Avalonia/QS.Launcher.Avalonia.csproj
+++ b/QS.Launcher.Avalonia/QS.Launcher.Avalonia.csproj
@@ -53,7 +53,7 @@
LoginView.axaml
-
+
DataBasesView.axaml
diff --git a/QS.Launcher.Avalonia/Views/Pages/DataBase/CreateDataBaseWindow.axaml b/QS.Launcher.Avalonia/Views/Pages/DataBase/CreateDataBaseWindow.axaml
new file mode 100644
index 000000000..73c0a0e0c
--- /dev/null
+++ b/QS.Launcher.Avalonia/Views/Pages/DataBase/CreateDataBaseWindow.axaml
@@ -0,0 +1,27 @@
+
+
+
+
+
+
+
+
+
+
+
diff --git a/QS.Launcher.Avalonia/Views/Pages/DataBase/CreateDataBaseWindow.axaml.cs b/QS.Launcher.Avalonia/Views/Pages/DataBase/CreateDataBaseWindow.axaml.cs
new file mode 100644
index 000000000..5835534e6
--- /dev/null
+++ b/QS.Launcher.Avalonia/Views/Pages/DataBase/CreateDataBaseWindow.axaml.cs
@@ -0,0 +1,32 @@
+using Avalonia;
+using Avalonia.Controls;
+using Avalonia.Markup.Xaml;
+using QS.Launcher.ViewModels.PageViewModels.DataBase;
+using System;
+using System.Threading.Tasks;
+using System.Xml.Linq;
+
+namespace QS.Launcher.Views.Pages.DataBase;
+
+public partial class CreateDataBaseWindow : Window
+{
+ private CreateDataBaseVM ViewModel;
+ private TaskCompletionSource<(string? dbTitle, string? dbName)> Result;
+ public CreateDataBaseWindow(CreateDataBaseVM viewModel)
+ {
+ DataContext = ViewModel = viewModel;
+ InitializeComponent();
+
+ Result = new TaskCompletionSource<(string? dbTitle, string? dbName)>();
+ ViewModel.DatabaseCreated += () => {
+ Result.TrySetResult((ViewModel.DbTitle, ViewModel.DbName));
+ Close();
+ };
+
+ this.Closed += (s, e) =>
+ Result.TrySetResult((null, null));
+ }
+ public (string? dbTitle, string? dbName) GetResult(){
+ return Result.Task.Result;
+ }
+}
diff --git a/QS.Launcher.Avalonia/Views/Pages/DataBasesView.axaml b/QS.Launcher.Avalonia/Views/Pages/DataBase/DataBasesView.axaml
similarity index 83%
rename from QS.Launcher.Avalonia/Views/Pages/DataBasesView.axaml
rename to QS.Launcher.Avalonia/Views/Pages/DataBase/DataBasesView.axaml
index 436cfe89c..bb771c5f6 100644
--- a/QS.Launcher.Avalonia/Views/Pages/DataBasesView.axaml
+++ b/QS.Launcher.Avalonia/Views/Pages/DataBase/DataBasesView.axaml
@@ -6,21 +6,26 @@
xmlns:vm="clr-namespace:QS.Launcher.ViewModels.PageViewModels;assembly=QS.Launcher"
d:DesignHeight="650"
d:DesignWidth="450"
- x:DataType="vm:DataBasesVM"
+ x:DataType="vm:DataBase.DataBasesVM"
mc:Ignorable="d">
+
+
+
+
-
-
+
+
-
-
+
+
@@ -29,7 +34,7 @@
-
+
diff --git a/QS.Launcher.Avalonia/Views/Pages/DataBasesView.axaml.cs b/QS.Launcher.Avalonia/Views/Pages/DataBase/DataBasesView.axaml.cs
similarity index 77%
rename from QS.Launcher.Avalonia/Views/Pages/DataBasesView.axaml.cs
rename to QS.Launcher.Avalonia/Views/Pages/DataBase/DataBasesView.axaml.cs
index 08ca5d336..15621efb1 100644
--- a/QS.Launcher.Avalonia/Views/Pages/DataBasesView.axaml.cs
+++ b/QS.Launcher.Avalonia/Views/Pages/DataBase/DataBasesView.axaml.cs
@@ -2,7 +2,8 @@
using Avalonia.Controls;
using Avalonia.Controls.ApplicationLifetimes;
using Avalonia.Input;
-using QS.Launcher.ViewModels.PageViewModels;
+using QS.Launcher.ViewModels.PageViewModels.DataBase;
+using QS.Launcher.Views.Pages.DataBase;
using System.Linq;
using System.Threading.Tasks;
@@ -17,6 +18,7 @@ public DataBasesView(DataBasesVM viewModel) {
DataContext = ViewModel = viewModel;
viewModel.StartLaunchProgram += HandleStartMainProgram;
+ viewModel.RequestShowCreateDbWindow += ShowCreateDatabaseWindowAsync;
KeyDown += (s, e) => {
if(e.Key == Key.Enter) {
@@ -26,16 +28,27 @@ public DataBasesView(DataBasesVM viewModel) {
};
}
+ private async Task<(string? dbTitle, string? dbName)> ShowCreateDatabaseWindowAsync(DataBasesVM dataBases) {
+ CreateDataBaseVM viewModel = new CreateDataBaseVM(ViewModel.Provider);
+ var window = new CreateDataBaseWindow(viewModel);
+
+ var parentWindow = TopLevel.GetTopLevel(this) as Window;
+ if(parentWindow != null) {
+ await window.ShowDialog(parentWindow);
+ }
+ return window.GetResult();
+ }
+
public async void HandleStartMainProgram(bool shouldCloseLauncher) {
logger.Info($">>> HandleStartMainProgram: shouldCloseLauncher={shouldCloseLauncher}");
-
+
loadingPanel.IsVisible = true;
cogwheel.Classes.Add("rolled");
var transition = cogwheel.Transitions.OfType().FirstOrDefault();
await Task.Delay(transition.Duration);
loadingPanel.IsVisible = false;
-
+
if(shouldCloseLauncher) {
logger.Info($">>> HandleStartMainProgram: Вызываем Shutdown!");
// NewProcessRunner: закрываем всё приложение лаунчера (Shutdown)
diff --git a/QS.Launcher/DependencyInjection.cs b/QS.Launcher/DependencyInjection.cs
index e0897f04f..2e4bc8a1a 100644
--- a/QS.Launcher/DependencyInjection.cs
+++ b/QS.Launcher/DependencyInjection.cs
@@ -4,6 +4,7 @@
using QS.Launcher.AppRunner;
using QS.Launcher.ViewModels;
using QS.Launcher.ViewModels.PageViewModels;
+using QS.Launcher.ViewModels.PageViewModels.DataBase;
namespace QS.Launcher {
public static partial class DependencyInjection {
diff --git a/QS.Launcher/QS.Launcher.csproj b/QS.Launcher/QS.Launcher.csproj
index 271188d35..3481e8522 100644
--- a/QS.Launcher/QS.Launcher.csproj
+++ b/QS.Launcher/QS.Launcher.csproj
@@ -20,6 +20,7 @@
+
diff --git a/QS.Launcher/ViewModels/MainWindowVM.cs b/QS.Launcher/ViewModels/MainWindowVM.cs
index 8c04e209f..ef09b2b60 100644
--- a/QS.Launcher/ViewModels/MainWindowVM.cs
+++ b/QS.Launcher/ViewModels/MainWindowVM.cs
@@ -2,6 +2,7 @@
using ReactiveUI;
using System;
using QS.ViewModels;
+using QS.Launcher.ViewModels.PageViewModels.DataBase;
namespace QS.Launcher.ViewModels {
public class MainWindowVM : ViewModelBase {
diff --git a/QS.Launcher/ViewModels/PageViewModels/DataBase/CreateDataBaseVM.cs b/QS.Launcher/ViewModels/PageViewModels/DataBase/CreateDataBaseVM.cs
new file mode 100644
index 000000000..936f4c80d
--- /dev/null
+++ b/QS.Launcher/ViewModels/PageViewModels/DataBase/CreateDataBaseVM.cs
@@ -0,0 +1,42 @@
+using Dapper;
+using QS.DbManagement;
+using ReactiveUI;
+using QS.Launcher.AppRunner;
+using QS.Project.Versioning;
+using System;
+using System.Windows.Input;
+using System.Reactive;
+
+namespace QS.Launcher.ViewModels.PageViewModels.DataBase {
+ public class CreateDataBaseVM : ReactiveObject {
+ private static NLog.Logger logger = NLog.LogManager.GetCurrentClassLogger();
+
+ public IDbProvider Provider { get; private set; }
+
+ private string dbTitle;
+ public string DbTitle {
+ get => dbTitle;
+ set => this.RaiseAndSetIfChanged(ref dbTitle, value);
+ }
+ private string dbName;
+ public string DbName {
+ get => dbName;
+ set => this.RaiseAndSetIfChanged(ref dbName, value);
+ }
+ public ReactiveCommand CreateDataBaseCommand { get; }
+ public event Action DatabaseCreated;
+
+ public CreateDataBaseVM(IDbProvider dbProvider) {
+ Provider = dbProvider;
+
+ CreateDataBaseCommand = ReactiveCommand.Create(() => {
+ if(Provider.CanCreateDatabase)
+ Provider.CreateDatabase(DbName, DbTitle);
+ else
+ throw new InvalidOperationException("пользователь не должен получать доступ к созданию базы");
+
+ DatabaseCreated?.Invoke();
+ });
+ }
+ }
+}
diff --git a/QS.Launcher/ViewModels/PageViewModels/DataBasesVM.cs b/QS.Launcher/ViewModels/PageViewModels/DataBase/DataBasesVM.cs
similarity index 80%
rename from QS.Launcher/ViewModels/PageViewModels/DataBasesVM.cs
rename to QS.Launcher/ViewModels/PageViewModels/DataBase/DataBasesVM.cs
index 29acce4bb..c9606765c 100644
--- a/QS.Launcher/ViewModels/PageViewModels/DataBasesVM.cs
+++ b/QS.Launcher/ViewModels/PageViewModels/DataBase/DataBasesVM.cs
@@ -1,16 +1,19 @@
using DynamicData.Kernel;
using QS.DbManagement;
using QS.Dialog;
+using QS.Launcher.AppRunner;
+using QS.Project.Versioning;
using ReactiveUI;
using System;
using System.Collections.Generic;
using System.Linq;
+using System.Reactive;
using System.Reactive.Linq;
+using System.Threading.Tasks;
using System.Windows.Input;
-using QS.Launcher.AppRunner;
-using QS.Project.Versioning;
+using System.Xml.Linq;
-namespace QS.Launcher.ViewModels.PageViewModels {
+namespace QS.Launcher.ViewModels.PageViewModels.DataBase {
public class DataBasesVM : CarouselPageVM {
private static NLog.Logger logger = NLog.LogManager.GetCurrentClassLogger();
@@ -23,12 +26,12 @@ public IDbProvider Provider {
this.RaiseAndSetIfChanged(ref provider, value);
Databases = provider.GetUserDatabases(applicationInfo).AsList();
this.RaisePropertyChanged(nameof(Databases));
-
+
// Загружаем и устанавливаем последнюю выбранную базу
LoadLastSelectedDatabase();
}
}
-
+
public void SetProvider(IDbProvider dbProvider, Connection connection, Action saveConnections) {
currentConnection = connection;
saveConnectionsAction = saveConnections;
@@ -56,8 +59,10 @@ public DbInfo SelectedDatabase {
public bool VisibleShouldCloseLauncherCheckBox => launcherOptions?.IsStandalone ?? false;
public ICommand ConnectCommand { get; }
+ public ReactiveCommand OpenCreateDatabaseCommand { get; }
public event Action StartLaunchProgram;
+ public event Func> RequestShowCreateDbWindow;
IInteractiveMessage interactiveMessage;
@@ -69,19 +74,34 @@ public DataBasesVM(IAppRunner appRunner, IApplicationInfo applicationInfo, IInte
this.applicationInfo = applicationInfo ?? throw new ArgumentNullException(nameof(applicationInfo));
this.interactiveMessage = interactiveMessage ?? throw new ArgumentNullException(nameof(interactiveMessage));
this.launcherOptions = launcherOptions;
-
+
logger.Info($">>> DataBasesVM constructor: launcherOptions={launcherOptions}, IsStandalone={launcherOptions?.IsStandalone}");
- IObservable canExecute = this
+ IObservable canExecuteConnection = this
+ .WhenAnyValue(x => x.SelectedDatabase)
+ .Select(x => x != null);
+
+ IObservable canExecuteCreation = this
.WhenAnyValue(x => x.SelectedDatabase)
.Select(x => x != null);
- ConnectCommand = ReactiveCommand.Create(Connect, canExecute);
+ ConnectCommand = ReactiveCommand.Create(Connect, canExecuteConnection);
+ OpenCreateDatabaseCommand = ReactiveCommand.CreateFromTask(OpenCreateDatabaseAsync);
}
-
+ private async Task OpenCreateDatabaseAsync() {
+ if(OpenCreateDatabaseCommand != null) {
+ (string title, string name) = await RequestShowCreateDbWindow.Invoke(this);
+
+ Databases = provider.GetUserDatabases(applicationInfo).AsList();
+ this.RaisePropertyChanged(nameof(Databases));
+ SelectedDatabase = Databases.FirstOrDefault(db => db.Title == title && db.BaseName == name);
+ this.RaisePropertyChanged(nameof(SelectedDatabase));
+ }
+ }
+
private void LoadLastSelectedDatabase() {
if(Databases == null || Databases.Count == 0)
return;
-
+
// Используем LastBaseId из текущего подключения
if(currentConnection?.LastBaseId != null)
SelectedDatabase = Databases.FirstOrDefault(db => db.BaseId == currentConnection.LastBaseId.Value);
@@ -107,22 +127,21 @@ public void Connect() {
// В in-process режиме НЕ делаем shutdown (возвращаем false)
var isStandalone = launcherOptions?.IsStandalone ?? false;
logger.Info($">>> Connect: IsStandalone={isStandalone}, ShouldCloseLauncherAfterStart={ShouldCloseLauncherAfterStart}");
-
+
bool shouldCloseLauncher = isStandalone && ShouldCloseLauncherAfterStart;
-
+
logger.Info($">>> Connect: shouldCloseLauncher={shouldCloseLauncher}");
-
+
StartLaunchProgram?.Invoke(shouldCloseLauncher);
appRunner.Run(resp);
}
-
+
private void SaveLastSelectedDatabase() {
if(SelectedDatabase == null || currentConnection == null)
return;
-
// Сохраняем BaseId в текущее подключение
currentConnection.LastBaseId = SelectedDatabase.BaseId;
-
+
// Вызываем сохранение подключений
saveConnectionsAction?.Invoke();
}
diff --git a/QS.Launcher/ViewModels/PageViewModels/LoginVM.cs b/QS.Launcher/ViewModels/PageViewModels/LoginVM.cs
index 3dc7b311e..8e0c6c73f 100644
--- a/QS.Launcher/ViewModels/PageViewModels/LoginVM.cs
+++ b/QS.Launcher/ViewModels/PageViewModels/LoginVM.cs
@@ -7,6 +7,7 @@
using System.Linq;
using System.Threading.Tasks;
using System.Windows.Input;
+using QS.Launcher.ViewModels.PageViewModels.DataBase;
namespace QS.Launcher.ViewModels.PageViewModels {
public class LoginVM : CarouselPageVM {
From c5593f6f5cda345b84991b32dba9d6ed98675ef4 Mon Sep 17 00:00:00 2001
From: J0shlerB0y <86416124+J0shlerB0y@users.noreply.github.com>
Date: Tue, 28 Apr 2026 22:33:57 +0300
Subject: [PATCH 02/19] =?UTF-8?q?=D1=81=D0=B4=D0=B5=D0=BB=D0=B0=D0=BB=20?=
=?UTF-8?q?=D0=BA=D0=BB=D0=B8=D0=B5=D0=BD=D1=82=20=D1=81=D0=BE=D0=B7=D0=B4?=
=?UTF-8?q?=D0=B0=D0=BD=D0=B8=D0=B5=20=D0=B1=D0=B0=D0=B7=D1=8B=20=D0=B4?=
=?UTF-8?q?=D0=BB=D1=8F=20=D0=BE=D0=B1=D0=BB=D0=B0=D0=BA=D0=B0?=
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
---
.../Clients/DataBaseManagementCloudClient.cs | 13 +++++++++++++
QS.Cloud.Client/Protos/DataBaseManagement.proto | 16 ++++++++++++++++
QS.Cloud.Client/QS.Cloud.Client.csproj | 2 ++
QS.Cloud.Client/QSCloudProvider.cs | 5 ++++-
4 files changed, 35 insertions(+), 1 deletion(-)
create mode 100644 QS.Cloud.Client/Clients/DataBaseManagementCloudClient.cs
create mode 100644 QS.Cloud.Client/Protos/DataBaseManagement.proto
diff --git a/QS.Cloud.Client/Clients/DataBaseManagementCloudClient.cs b/QS.Cloud.Client/Clients/DataBaseManagementCloudClient.cs
new file mode 100644
index 000000000..27e07a9d9
--- /dev/null
+++ b/QS.Cloud.Client/Clients/DataBaseManagementCloudClient.cs
@@ -0,0 +1,13 @@
+using QS.Cloud.Core;
+namespace QS.Cloud.Client.Clients {
+ public class DataBaseManagementCloudClient : CloudClientByBasicAuth {
+ public DataBaseManagementCloudClient(IBasicAuthInfoProvider basicAuthInfoProvider)
+ : base(basicAuthInfoProvider, "core.cloud.qsolution.ru", 443) { }
+
+ public CreateDataBaseResponse CreateDataBase(string dbName, string dbTitle) {
+ var client = new DataBaseManagement.DataBaseManagementClient(Channel);
+ var request = new CreateDataBaseRequest { Name = dbName, Title = dbTitle };
+ return client.CreateDataBase(request, headers); ;
+ }
+ }
+}
diff --git a/QS.Cloud.Client/Protos/DataBaseManagement.proto b/QS.Cloud.Client/Protos/DataBaseManagement.proto
new file mode 100644
index 000000000..6bd23648d
--- /dev/null
+++ b/QS.Cloud.Client/Protos/DataBaseManagement.proto
@@ -0,0 +1,16 @@
+syntax = "proto3";
+
+package QS.Cloud.Core;
+
+service DataBaseManagement{
+ rpc CreateDataBase (CreateDataBaseRequest) returns (CreateDataBaseResponse);
+}
+
+message CreateDataBaseRequest{
+ string name = 1;
+ string title = 2;
+}
+
+message CreateDataBaseResponse{
+ bool succsess = 1;
+}
diff --git a/QS.Cloud.Client/QS.Cloud.Client.csproj b/QS.Cloud.Client/QS.Cloud.Client.csproj
index ec2e96649..d2eee9082 100644
--- a/QS.Cloud.Client/QS.Cloud.Client.csproj
+++ b/QS.Cloud.Client/QS.Cloud.Client.csproj
@@ -22,11 +22,13 @@
+
+
diff --git a/QS.Cloud.Client/QSCloudProvider.cs b/QS.Cloud.Client/QSCloudProvider.cs
index 6ec7e78c5..3faae9181 100644
--- a/QS.Cloud.Client/QSCloudProvider.cs
+++ b/QS.Cloud.Client/QSCloudProvider.cs
@@ -8,6 +8,7 @@
using System.Linq;
using System.Reflection;
using System;
+using QS.Cloud.Client.Clients;
namespace QS.Cloud.Client
{
@@ -30,6 +31,7 @@ public class QSCloudProvider : IDbProvider {
private CloudFeaturesClient featuresClient;
private LoginManagementCloudClient loginClient;
private SessionManagementCloudClient sessionClient;
+ private DataBaseManagementCloudClient dbClient;
private UserManagementCloudClient userClient;
@@ -39,6 +41,7 @@ public QSCloudProvider(IList parameters, string passwo
BasicAuthInfoProvider authInfo = new BasicAuthInfoProvider($@"{Account}\{UserName}", password);
loginClient = new LoginManagementCloudClient(authInfo);
+ dbClient = new DataBaseManagementCloudClient(authInfo);
}
public bool AddUser(string username, string password)
@@ -53,7 +56,7 @@ public bool ChangePassword(string username, string oldPassword, string newPasswo
public bool CreateDatabase(string databaseName, string title)
{
- throw new NotImplementedException();
+ return dbClient.CreateDataBase(databaseName, title).Succsess;
}
public void Dispose()
From 2266c4b293281064c454a3e723345c5dc138d5dc Mon Sep 17 00:00:00 2001
From: J0shlerB0y <86416124+J0shlerB0y@users.noreply.github.com>
Date: Thu, 30 Apr 2026 20:56:29 +0300
Subject: [PATCH 03/19] =?UTF-8?q?=D1=83=D0=BD=D0=B8=D0=B2=D0=B5=D1=80?=
=?UTF-8?q?=D1=81=D0=B0=D0=BB=D1=8C=D0=BD=D0=BE=D0=B5=20=D0=BD=D0=B0=D0=BF?=
=?UTF-8?q?=D0=BE=D0=BB=D0=BD=D0=B5=D0=BD=D0=B8=D0=B5=20=D0=B1=D0=B0=D0=B7?=
=?UTF-8?q?=D1=8B=20=D0=B4=D0=B0=D0=BD=D0=BD=D1=8B=D1=85=20=D0=B8=D0=B7=20?=
=?UTF-8?q?=D1=81=D0=BA=D1=80=D0=B8=D0=BF=D1=82=D0=BE=D0=B2?=
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
Views больше не регистрируются, они разворачиваются через DataTemplates в LauncherApp.axaml по типу VM
Корневые страницы (Login, DataBases, BaseManagement, UserManagement) добавляются при создании и не удаляются. Дополнительные страницы добавляются в конец через PushPage и удаляются PopPage/PopToRoot
и прогресс создания базы
---
QS.Cloud.Client/QS.Cloud.Client.csproj | 1 +
QS.DbManagement/ConnectionTypeBase.cs | 29 ++-
QS.DbManagement/MariaDb/MariaDBProvider.cs | 6 +-
QS.DbManagement/QS.DbManagement.csproj | 1 +
QS.Launcher.Avalonia/DependencyInjection.cs | 10 +-
QS.Launcher.Avalonia/LauncherApp.axaml | 25 +++
.../QS.Launcher.Avalonia.csproj | 6 +
.../Services/AvaloniaUiThreadInvoker.cs | 12 ++
QS.Launcher.Avalonia/Views/MainWindow.axaml | 5 +-
.../Views/MainWindow.axaml.cs | 8 +-
.../Views/Pages/BaseManagementView.axaml.cs | 4 +-
.../DataBase/CreateDataBaseProgressView.axaml | 45 +++++
.../CreateDataBaseProgressView.axaml.cs | 19 ++
.../DataBase/CreateDataBaseSettingsView.axaml | 25 +++
.../CreateDataBaseSettingsView.axaml.cs | 9 +
.../Pages/DataBase/CreateDataBaseWindow.axaml | 27 ---
.../DataBase/CreateDataBaseWindow.axaml.cs | 32 ----
.../Pages/DataBase/DataBasesView.axaml.cs | 37 ++--
.../Views/Pages/LoginView.axaml.cs | 19 +-
.../Views/Pages/UserManagementView.axaml.cs | 8 +-
QS.Launcher/DependencyInjection.cs | 9 +-
QS.Launcher/Services/IUiThreadInvoker.cs | 11 ++
.../Services/LauncherDbCreatorInteraction.cs | 53 ++++++
QS.Launcher/ViewModels/MainWindowVM.cs | 92 ++++++++--
.../PageViewModels/CarouselPageVM.cs | 18 ++
.../DataBase/CreateDataBaseProgressVM.cs | 148 +++++++++++++++
.../DataBase/CreateDataBaseSettingsVM.cs | 59 ++++++
.../DataBase/CreateDataBaseVM.cs | 42 -----
.../PageViewModels/DataBase/DataBasesVM.cs | 88 +++++----
.../DBScripts/Controllers/IDBCreator.cs | 9 +-
.../Controllers/IDbCreatorInteraction.cs | 14 ++
.../Controllers/IDbScriptsConfiguration.cs | 14 ++
.../Controllers/IDbCreateController.cs | 2 +-
.../DBScripts/Models/MySqlDbCreateModel.cs | 168 ++++++++++--------
34 files changed, 763 insertions(+), 292 deletions(-)
create mode 100644 QS.Launcher.Avalonia/Services/AvaloniaUiThreadInvoker.cs
create mode 100644 QS.Launcher.Avalonia/Views/Pages/DataBase/CreateDataBaseProgressView.axaml
create mode 100644 QS.Launcher.Avalonia/Views/Pages/DataBase/CreateDataBaseProgressView.axaml.cs
create mode 100644 QS.Launcher.Avalonia/Views/Pages/DataBase/CreateDataBaseSettingsView.axaml
create mode 100644 QS.Launcher.Avalonia/Views/Pages/DataBase/CreateDataBaseSettingsView.axaml.cs
delete mode 100644 QS.Launcher.Avalonia/Views/Pages/DataBase/CreateDataBaseWindow.axaml
delete mode 100644 QS.Launcher.Avalonia/Views/Pages/DataBase/CreateDataBaseWindow.axaml.cs
create mode 100644 QS.Launcher/Services/IUiThreadInvoker.cs
create mode 100644 QS.Launcher/Services/LauncherDbCreatorInteraction.cs
create mode 100644 QS.Launcher/ViewModels/PageViewModels/DataBase/CreateDataBaseProgressVM.cs
create mode 100644 QS.Launcher/ViewModels/PageViewModels/DataBase/CreateDataBaseSettingsVM.cs
delete mode 100644 QS.Launcher/ViewModels/PageViewModels/DataBase/CreateDataBaseVM.cs
create mode 100644 QS.Project.Core/DBScripts/Controllers/IDbCreatorInteraction.cs
create mode 100644 QS.Project.Core/DBScripts/Controllers/IDbScriptsConfiguration.cs
diff --git a/QS.Cloud.Client/QS.Cloud.Client.csproj b/QS.Cloud.Client/QS.Cloud.Client.csproj
index d2eee9082..086526cf2 100644
--- a/QS.Cloud.Client/QS.Cloud.Client.csproj
+++ b/QS.Cloud.Client/QS.Cloud.Client.csproj
@@ -25,6 +25,7 @@
+
diff --git a/QS.DbManagement/ConnectionTypeBase.cs b/QS.DbManagement/ConnectionTypeBase.cs
index 377ecad56..e8b3f5905 100644
--- a/QS.DbManagement/ConnectionTypeBase.cs
+++ b/QS.DbManagement/ConnectionTypeBase.cs
@@ -1,5 +1,7 @@
using System;
using System.Collections.Generic;
+using QS.DBScripts.Controllers;
+using QS.Dialog;
namespace QS.DbManagement {
@@ -10,9 +12,34 @@ public abstract class ConnectionTypeBase {
public List Parameters { get; } = new List();
public byte[] IconBytes { get; protected set; }
-
+
public abstract bool CanConnect(IEnumerable parameters);
public abstract IDbProvider CreateProvider(IList parameters, string password = null);
+
+ ///
+ /// Заполняется композиционным корнем приложения
+ /// который один знает обо всех конкретных реализациях creator-ов и
+ /// о том, как из IDbProvider достать строку подключения
+ ///
+ /// interaction — канал диалогов с пользователем
+ /// serviceProvider — для резолва дополнительных зависимостей
+ ///
+ public Func CreatorFactory { get; set; }
+
+ public IDBCreator CreateCreator(CreatorFactoryArgs args) {
+ if(CreatorFactory == null)
+ throw new InvalidOperationException(
+ $"Для типа подключения '{ConnectionTypeName}' не задана CreatorFactory. "
+ + "Зарегистрируйте её в композиционном корне приложения.");
+ return CreatorFactory(args);
+ }
+ }
+ public class CreatorFactoryArgs {
+ public IDbProvider Provider { get; set; }
+ public IProgressBarDisplayable Progress { get; set; }
+ public IDbCreatorInteraction Interaction { get; set; }
+ public System.Threading.CancellationToken CancellationToken { get; set; }
+ public IServiceProvider ServiceProvider { get; set; }
}
}
diff --git a/QS.DbManagement/MariaDb/MariaDBProvider.cs b/QS.DbManagement/MariaDb/MariaDBProvider.cs
index 491fd201b..7a031b382 100644
--- a/QS.DbManagement/MariaDb/MariaDBProvider.cs
+++ b/QS.DbManagement/MariaDb/MariaDBProvider.cs
@@ -15,7 +15,11 @@ public class MariaDBProvider : IDbProvider {
private static readonly string[] SystemDatabases = { "information_schema", "mysql", "performance_schema", "sys" };
readonly MySqlConnection connection;
- readonly MySqlConnectionStringBuilder ConnectionStringBuilder;
+ ///
+ /// Публичный, чтобы внешние компоненты могли получить подключение
+ /// без повторного разбора параметров.
+ ///
+ public MySqlConnectionStringBuilder ConnectionStringBuilder { get; }
public bool IsConnected => connection.State == ConnectionState.Open;
diff --git a/QS.DbManagement/QS.DbManagement.csproj b/QS.DbManagement/QS.DbManagement.csproj
index 60196acee..178ef368e 100644
--- a/QS.DbManagement/QS.DbManagement.csproj
+++ b/QS.DbManagement/QS.DbManagement.csproj
@@ -26,6 +26,7 @@
+
diff --git a/QS.Launcher.Avalonia/DependencyInjection.cs b/QS.Launcher.Avalonia/DependencyInjection.cs
index 6af20d57e..652f84797 100644
--- a/QS.Launcher.Avalonia/DependencyInjection.cs
+++ b/QS.Launcher.Avalonia/DependencyInjection.cs
@@ -1,18 +1,12 @@
-using Avalonia.Controls;
using Microsoft.Extensions.DependencyInjection;
+using QS.Launcher.Services;
using QS.Launcher.Views;
-using QS.Launcher.Views.Pages;
namespace QS.Launcher;
public static partial class DependencyInjection {
public static IServiceCollection AddPages(this IServiceCollection services) {
return services
.AddSingleton()
- .AddSingleton()
- .AddSingleton()
- .AddSingleton()
- .AddSingleton();
+ .AddSingleton();
}
-
-
}
diff --git a/QS.Launcher.Avalonia/LauncherApp.axaml b/QS.Launcher.Avalonia/LauncherApp.axaml
index e0f3cd31e..c02018fd8 100644
--- a/QS.Launcher.Avalonia/LauncherApp.axaml
+++ b/QS.Launcher.Avalonia/LauncherApp.axaml
@@ -2,6 +2,10 @@
xmlns="https://github.com/avaloniaui"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:converters1="clr-namespace:QS.Project.Converters;assembly=QS.Project.Avalonia"
+ xmlns:vm="clr-namespace:QS.Launcher.ViewModels.PageViewModels;assembly=QS.Launcher"
+ xmlns:vmDb="clr-namespace:QS.Launcher.ViewModels.PageViewModels.DataBase;assembly=QS.Launcher"
+ xmlns:views="using:QS.Launcher.Views.Pages"
+ xmlns:viewsDb="using:QS.Launcher.Views.Pages.DataBase"
x:CompileBindings="False"
RequestedThemeVariant="Light">
@@ -11,6 +15,27 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/QS.Launcher.Avalonia/QS.Launcher.Avalonia.csproj b/QS.Launcher.Avalonia/QS.Launcher.Avalonia.csproj
index 553012330..e8a347a5a 100644
--- a/QS.Launcher.Avalonia/QS.Launcher.Avalonia.csproj
+++ b/QS.Launcher.Avalonia/QS.Launcher.Avalonia.csproj
@@ -56,6 +56,12 @@
DataBasesView.axaml
+
+ CreateDataBaseSettingsView.axaml
+
+
+ CreateDataBaseProgressView.axaml
+
UserManagementView.axaml
diff --git a/QS.Launcher.Avalonia/Services/AvaloniaUiThreadInvoker.cs b/QS.Launcher.Avalonia/Services/AvaloniaUiThreadInvoker.cs
new file mode 100644
index 000000000..6ac1bc79e
--- /dev/null
+++ b/QS.Launcher.Avalonia/Services/AvaloniaUiThreadInvoker.cs
@@ -0,0 +1,12 @@
+using System;
+using Avalonia.Threading;
+using QS.Launcher.Services;
+
+namespace QS.Launcher.Services {
+ public class AvaloniaUiThreadInvoker : IUiThreadInvoker {
+ public void Post(Action action) {
+ if(action == null) return;
+ Dispatcher.UIThread.Post(action);
+ }
+ }
+}
diff --git a/QS.Launcher.Avalonia/Views/MainWindow.axaml b/QS.Launcher.Avalonia/Views/MainWindow.axaml
index b678546ae..761d4a59b 100644
--- a/QS.Launcher.Avalonia/Views/MainWindow.axaml
+++ b/QS.Launcher.Avalonia/Views/MainWindow.axaml
@@ -3,7 +3,6 @@
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
- xmlns:views="clr-namespace:QS.Launcher.Views"
xmlns:vm="clr-namespace:QS.Launcher.ViewModels;assembly=QS.Launcher"
Title="QS.Лаунчер"
Width="450"
@@ -17,7 +16,9 @@
mc:Ignorable="d">
-
+
diff --git a/QS.Launcher.Avalonia/Views/MainWindow.axaml.cs b/QS.Launcher.Avalonia/Views/MainWindow.axaml.cs
index d17804705..bb6747c0d 100644
--- a/QS.Launcher.Avalonia/Views/MainWindow.axaml.cs
+++ b/QS.Launcher.Avalonia/Views/MainWindow.axaml.cs
@@ -1,11 +1,11 @@
using Avalonia.Controls;
using Avalonia.Media.Imaging;
using QS.Launcher.ViewModels;
-using System.Collections.Generic;
+
namespace QS.Launcher.Views;
public partial class MainWindow : Window {
- public MainWindow(MainWindowVM vm, IEnumerable pages, LauncherOptions options) {
+ public MainWindow(MainWindowVM vm, LauncherOptions options) {
InitializeComponent();
Icon = new WindowIcon(new Bitmap(new System.IO.MemoryStream(options.LogoIcon)));
@@ -13,10 +13,6 @@ public MainWindow(MainWindowVM vm, IEnumerable pages, LauncherOptio
Closing += (_, _) => vm.SaveConnections();
- foreach(var page in pages)
- carousel.Items.Add(page);
- vm.PagesCount = carousel.ItemCount;
-
DataContext = vm;
}
}
diff --git a/QS.Launcher.Avalonia/Views/Pages/BaseManagementView.axaml.cs b/QS.Launcher.Avalonia/Views/Pages/BaseManagementView.axaml.cs
index e6332d279..d96f22ca3 100644
--- a/QS.Launcher.Avalonia/Views/Pages/BaseManagementView.axaml.cs
+++ b/QS.Launcher.Avalonia/Views/Pages/BaseManagementView.axaml.cs
@@ -1,11 +1,9 @@
using Avalonia.Controls;
-using QS.Launcher.ViewModels.PageViewModels;
namespace QS.Launcher.Views.Pages;
public partial class BaseManagementView : UserControl {
- public BaseManagementView(BaseManagementVM viewModel) {
+ public BaseManagementView() {
InitializeComponent();
- DataContext = viewModel;
}
}
diff --git a/QS.Launcher.Avalonia/Views/Pages/DataBase/CreateDataBaseProgressView.axaml b/QS.Launcher.Avalonia/Views/Pages/DataBase/CreateDataBaseProgressView.axaml
new file mode 100644
index 000000000..9a468a3f1
--- /dev/null
+++ b/QS.Launcher.Avalonia/Views/Pages/DataBase/CreateDataBaseProgressView.axaml
@@ -0,0 +1,45 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/QS.Launcher.Avalonia/Views/Pages/DataBase/CreateDataBaseProgressView.axaml.cs b/QS.Launcher.Avalonia/Views/Pages/DataBase/CreateDataBaseProgressView.axaml.cs
new file mode 100644
index 000000000..ec4b43f39
--- /dev/null
+++ b/QS.Launcher.Avalonia/Views/Pages/DataBase/CreateDataBaseProgressView.axaml.cs
@@ -0,0 +1,19 @@
+using System;
+using Avalonia.Controls;
+using Avalonia.Interactivity;
+using QS.Launcher.ViewModels.PageViewModels.DataBase;
+
+namespace QS.Launcher.Views.Pages.DataBase;
+
+public partial class CreateDataBaseProgressView : UserControl {
+ public CreateDataBaseProgressView() {
+ InitializeComponent();
+ }
+
+ private void OnLoaded(object? sender, RoutedEventArgs e) {
+ cogwheel.Classes.Add("rolled");
+
+ if(DataContext is CreateDataBaseProgressVM vm)
+ vm.StartCreationCommand.Execute().Subscribe();
+ }
+}
diff --git a/QS.Launcher.Avalonia/Views/Pages/DataBase/CreateDataBaseSettingsView.axaml b/QS.Launcher.Avalonia/Views/Pages/DataBase/CreateDataBaseSettingsView.axaml
new file mode 100644
index 000000000..7092888cd
--- /dev/null
+++ b/QS.Launcher.Avalonia/Views/Pages/DataBase/CreateDataBaseSettingsView.axaml
@@ -0,0 +1,25 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/QS.Launcher.Avalonia/Views/Pages/DataBase/CreateDataBaseSettingsView.axaml.cs b/QS.Launcher.Avalonia/Views/Pages/DataBase/CreateDataBaseSettingsView.axaml.cs
new file mode 100644
index 000000000..35dab5b31
--- /dev/null
+++ b/QS.Launcher.Avalonia/Views/Pages/DataBase/CreateDataBaseSettingsView.axaml.cs
@@ -0,0 +1,9 @@
+using Avalonia.Controls;
+
+namespace QS.Launcher.Views.Pages.DataBase;
+
+public partial class CreateDataBaseSettingsView : UserControl {
+ public CreateDataBaseSettingsView() {
+ InitializeComponent();
+ }
+}
diff --git a/QS.Launcher.Avalonia/Views/Pages/DataBase/CreateDataBaseWindow.axaml b/QS.Launcher.Avalonia/Views/Pages/DataBase/CreateDataBaseWindow.axaml
deleted file mode 100644
index 73c0a0e0c..000000000
--- a/QS.Launcher.Avalonia/Views/Pages/DataBase/CreateDataBaseWindow.axaml
+++ /dev/null
@@ -1,27 +0,0 @@
-
-
-
-
-
-
-
-
-
-
-
diff --git a/QS.Launcher.Avalonia/Views/Pages/DataBase/CreateDataBaseWindow.axaml.cs b/QS.Launcher.Avalonia/Views/Pages/DataBase/CreateDataBaseWindow.axaml.cs
deleted file mode 100644
index 5835534e6..000000000
--- a/QS.Launcher.Avalonia/Views/Pages/DataBase/CreateDataBaseWindow.axaml.cs
+++ /dev/null
@@ -1,32 +0,0 @@
-using Avalonia;
-using Avalonia.Controls;
-using Avalonia.Markup.Xaml;
-using QS.Launcher.ViewModels.PageViewModels.DataBase;
-using System;
-using System.Threading.Tasks;
-using System.Xml.Linq;
-
-namespace QS.Launcher.Views.Pages.DataBase;
-
-public partial class CreateDataBaseWindow : Window
-{
- private CreateDataBaseVM ViewModel;
- private TaskCompletionSource<(string? dbTitle, string? dbName)> Result;
- public CreateDataBaseWindow(CreateDataBaseVM viewModel)
- {
- DataContext = ViewModel = viewModel;
- InitializeComponent();
-
- Result = new TaskCompletionSource<(string? dbTitle, string? dbName)>();
- ViewModel.DatabaseCreated += () => {
- Result.TrySetResult((ViewModel.DbTitle, ViewModel.DbName));
- Close();
- };
-
- this.Closed += (s, e) =>
- Result.TrySetResult((null, null));
- }
- public (string? dbTitle, string? dbName) GetResult(){
- return Result.Task.Result;
- }
-}
diff --git a/QS.Launcher.Avalonia/Views/Pages/DataBase/DataBasesView.axaml.cs b/QS.Launcher.Avalonia/Views/Pages/DataBase/DataBasesView.axaml.cs
index 15621efb1..9d35405e8 100644
--- a/QS.Launcher.Avalonia/Views/Pages/DataBase/DataBasesView.axaml.cs
+++ b/QS.Launcher.Avalonia/Views/Pages/DataBase/DataBasesView.axaml.cs
@@ -3,42 +3,30 @@
using Avalonia.Controls.ApplicationLifetimes;
using Avalonia.Input;
using QS.Launcher.ViewModels.PageViewModels.DataBase;
-using QS.Launcher.Views.Pages.DataBase;
using System.Linq;
using System.Threading.Tasks;
namespace QS.Launcher.Views.Pages;
public partial class DataBasesView : UserControl {
private static NLog.Logger logger = NLog.LogManager.GetCurrentClassLogger();
- private DataBasesVM ViewModel;
- public DataBasesView(DataBasesVM viewModel) {
+ public DataBasesView() {
InitializeComponent();
- DataContext = ViewModel = viewModel;
-
- viewModel.StartLaunchProgram += HandleStartMainProgram;
- viewModel.RequestShowCreateDbWindow += ShowCreateDatabaseWindowAsync;
+ DataContextChanged += (_, _) => {
+ if(DataContext is DataBasesVM vm)
+ vm.StartLaunchProgram += HandleStartMainProgram;
+ };
KeyDown += (s, e) => {
if(e.Key == Key.Enter) {
- TopLevel.GetTopLevel(this).FocusManager.ClearFocus();
- viewModel.ConnectCommand.Execute(null);
+ TopLevel.GetTopLevel(this)?.FocusManager?.ClearFocus();
+ if(DataContext is DataBasesVM vm)
+ vm.ConnectCommand.Execute(null);
}
};
}
- private async Task<(string? dbTitle, string? dbName)> ShowCreateDatabaseWindowAsync(DataBasesVM dataBases) {
- CreateDataBaseVM viewModel = new CreateDataBaseVM(ViewModel.Provider);
- var window = new CreateDataBaseWindow(viewModel);
-
- var parentWindow = TopLevel.GetTopLevel(this) as Window;
- if(parentWindow != null) {
- await window.ShowDialog(parentWindow);
- }
- return window.GetResult();
- }
-
public async void HandleStartMainProgram(bool shouldCloseLauncher) {
logger.Info($">>> HandleStartMainProgram: shouldCloseLauncher={shouldCloseLauncher}");
@@ -46,17 +34,16 @@ public async void HandleStartMainProgram(bool shouldCloseLauncher) {
cogwheel.Classes.Add("rolled");
var transition = cogwheel.Transitions.OfType().FirstOrDefault();
- await Task.Delay(transition.Duration);
+ if(transition != null)
+ await Task.Delay(transition.Duration);
loadingPanel.IsVisible = false;
if(shouldCloseLauncher) {
logger.Info($">>> HandleStartMainProgram: Вызываем Shutdown!");
- // NewProcessRunner: закрываем всё приложение лаунчера (Shutdown)
(LauncherApp.Current!.ApplicationLifetime as ClassicDesktopStyleApplicationLifetime)?.Shutdown();
}
else {
logger.Info($">>> HandleStartMainProgram: Закрываем только окно");
- // InProcessRunner: закрываем только окно лаунчера
var window = TopLevel.GetTopLevel(this) as Window;
window?.Close();
}
@@ -68,7 +55,7 @@ public void Label_PointerPressed(object? sender, PointerPressedEventArgs e) {
}
private void Databases_OnDoubleTapped(object? sender, TappedEventArgs e) {
- if(databases.SelectedItem is not null)
- ViewModel.ConnectCommand.Execute(null);
+ if(databases.SelectedItem is not null && DataContext is DataBasesVM vm)
+ vm.ConnectCommand.Execute(null);
}
}
diff --git a/QS.Launcher.Avalonia/Views/Pages/LoginView.axaml.cs b/QS.Launcher.Avalonia/Views/Pages/LoginView.axaml.cs
index de2fcf8b0..00ede44dc 100644
--- a/QS.Launcher.Avalonia/Views/Pages/LoginView.axaml.cs
+++ b/QS.Launcher.Avalonia/Views/Pages/LoginView.axaml.cs
@@ -2,10 +2,8 @@
using Avalonia.Controls;
using Avalonia.Input;
using Avalonia.Media.Transformation;
-using Avalonia.Platform;
using Avalonia.Styling;
using QS.Launcher.ViewModels.PageViewModels;
-using System.Globalization;
namespace QS.Launcher.Views.Pages;
@@ -13,8 +11,8 @@ public partial class LoginView : UserControl
{
private readonly Style upStyle;
- public LoginView(LoginVM viewModel)
- {
+ public LoginView()
+ {
upStyle = new Style(x => x.OfType().Class("up")) {
Setters =
{
@@ -24,23 +22,22 @@ public LoginView(LoginVM viewModel)
},
};
- InitializeComponent();
+ InitializeComponent();
loginContainer.Styles.Add(upStyle);
- DataContext = viewModel;
-
Loaded += (s, e) => {
passwordTextBox.Focus();
};
KeyDown += (s, e) => {
- if(e.Key == Avalonia.Input.Key.Enter) {
- TopLevel.GetTopLevel(this).FocusManager.ClearFocus();
- viewModel.LoginCommand.Execute(null);
+ if(e.Key == Key.Enter) {
+ TopLevel.GetTopLevel(this)?.FocusManager?.ClearFocus();
+ if(DataContext is LoginVM vm)
+ vm.LoginCommand.Execute(null);
}
};
- }
+ }
private void ShowCreationView(object? sender, Avalonia.Interactivity.RoutedEventArgs e)
diff --git a/QS.Launcher.Avalonia/Views/Pages/UserManagementView.axaml.cs b/QS.Launcher.Avalonia/Views/Pages/UserManagementView.axaml.cs
index ce3f1258f..cd038c5c8 100644
--- a/QS.Launcher.Avalonia/Views/Pages/UserManagementView.axaml.cs
+++ b/QS.Launcher.Avalonia/Views/Pages/UserManagementView.axaml.cs
@@ -1,15 +1,9 @@
using Avalonia.Controls;
-using Microsoft.Extensions.DependencyInjection;
-using QS.Launcher.ViewModels.PageViewModels;
-using System;
-using System.Linq;
namespace QS.Launcher.Views.Pages;
public partial class UserManagementView : UserControl {
- public UserManagementView(UserManagementVM viewModel) {
+ public UserManagementView() {
InitializeComponent();
-
- DataContext = viewModel;
}
}
diff --git a/QS.Launcher/DependencyInjection.cs b/QS.Launcher/DependencyInjection.cs
index 2e4bc8a1a..8dcb66f2f 100644
--- a/QS.Launcher/DependencyInjection.cs
+++ b/QS.Launcher/DependencyInjection.cs
@@ -1,7 +1,9 @@
using Autofac;
using Microsoft.Extensions.DependencyInjection;
+using QS.DBScripts.Controllers;
using QS.DbManagement;
using QS.Launcher.AppRunner;
+using QS.Launcher.Services;
using QS.Launcher.ViewModels;
using QS.Launcher.ViewModels.PageViewModels;
using QS.Launcher.ViewModels.PageViewModels.DataBase;
@@ -14,7 +16,12 @@ public static IServiceCollection AddLauncherViewModels(this IServiceCollection s
.AddSingleton()
.AddSingleton()
.AddSingleton()
- .AddSingleton();
+ .AddSingleton()
+ // Wizard-страницы создания БД — Transient: новый экземпляр на каждое открытие.
+ .AddTransient()
+ .AddTransient()
+ // Сервисы лаунчера, нужные wizard-страницам.
+ .AddSingleton();
}
public static IServiceCollection AddLauncherOptions(this IServiceCollection services, LauncherOptions launcherOptions) {
diff --git a/QS.Launcher/Services/IUiThreadInvoker.cs b/QS.Launcher/Services/IUiThreadInvoker.cs
new file mode 100644
index 000000000..b7801d342
--- /dev/null
+++ b/QS.Launcher/Services/IUiThreadInvoker.cs
@@ -0,0 +1,11 @@
+using System;
+
+namespace QS.Launcher.Services {
+ ///
+ /// проксирование действия в UI-поток
+ ///
+ public interface IUiThreadInvoker {
+ /// Запланировать действие в UI-потоке без его блокировки
+ void Post(Action action);
+ }
+}
diff --git a/QS.Launcher/Services/LauncherDbCreatorInteraction.cs b/QS.Launcher/Services/LauncherDbCreatorInteraction.cs
new file mode 100644
index 000000000..fa2262cee
--- /dev/null
+++ b/QS.Launcher/Services/LauncherDbCreatorInteraction.cs
@@ -0,0 +1,53 @@
+using System;
+using System.Threading.Tasks;
+using QS.DBScripts.Controllers;
+using QS.Dialog;
+
+namespace QS.Launcher.Services {
+ ///
+ /// проксирует вопросы и ошибки
+ /// все вызовы диалогов уходят в UI-поток через IUiThreadInvoker,
+ /// потому что creator дёргает их с фонового потока
+ ///
+ public class LauncherDbCreatorInteraction : IDbCreatorInteraction {
+ private readonly IInteractiveQuestion question;
+ private readonly IInteractiveMessage message;
+ private readonly IUiThreadInvoker uiThread;
+
+ public LauncherDbCreatorInteraction(
+ IInteractiveQuestion question,
+ IInteractiveMessage message,
+ IUiThreadInvoker uiThread)
+ {
+ this.question = question ?? throw new ArgumentNullException(nameof(question));
+ this.message = message ?? throw new ArgumentNullException(nameof(message));
+ this.uiThread = uiThread ?? throw new ArgumentNullException(nameof(uiThread));
+ }
+
+ public Task AskDropExistingDatabaseAsync(string dbName) {
+ var tcs = new TaskCompletionSource();
+ uiThread.Post(() => {
+ try {
+ bool answer = question.Question(
+ $"База с именем `{dbName}` уже существует на сервере. Удалить существующую базу перед созданием новой?",
+ "Создание базы данных");
+ tcs.TrySetResult(answer);
+ }
+ catch(Exception ex) { tcs.TrySetException(ex); }
+ });
+ return tcs.Task;
+ }
+
+ public Task ReportErrorAsync(string text, string lastExecutedStatement) {
+ var tcs = new TaskCompletionSource();
+ uiThread.Post(() => {
+ try {
+ message.ShowMessage(ImportanceLevel.Error, text, "Ошибка создания базы");
+ tcs.TrySetResult(true);
+ }
+ catch(Exception ex) { tcs.TrySetException(ex); }
+ });
+ return tcs.Task;
+ }
+ }
+}
diff --git a/QS.Launcher/ViewModels/MainWindowVM.cs b/QS.Launcher/ViewModels/MainWindowVM.cs
index ef09b2b60..af5495e60 100644
--- a/QS.Launcher/ViewModels/MainWindowVM.cs
+++ b/QS.Launcher/ViewModels/MainWindowVM.cs
@@ -1,14 +1,19 @@
-using QS.Launcher.ViewModels.PageViewModels;
-using ReactiveUI;
using System;
-using QS.ViewModels;
+using System.Collections.ObjectModel;
+using QS.Launcher.ViewModels.PageViewModels;
using QS.Launcher.ViewModels.PageViewModels.DataBase;
+using QS.ViewModels;
+using ReactiveUI;
namespace QS.Launcher.ViewModels {
+ ///
+ /// Держит коллекцию страниц для Carousel и индекс активной
+ ///
public class MainWindowVM : ViewModelBase {
- public int PagesCount { get; set; }
-
LoginVM login;
+ private readonly int rootPagesCount;
+
+ public ObservableCollection Pages { get; }
private int selectedPageIndex;
public int SelectedPageIndex {
@@ -16,16 +21,35 @@ public int SelectedPageIndex {
set => this.RaiseAndSetIfChanged(ref selectedPageIndex, value);
}
- public MainWindowVM(DataBasesVM dataBasesVM, LoginVM loginVM, BaseManagementVM baseManagementVM, UserManagementVM userManagementVM
- , IServiceProvider provider)
+ public int PagesCount {
+ get => rootPagesCount;
+ set { /* кол-во корневых страниц фиксируется в ctor */ }
+ }
+
+ public MainWindowVM(
+ DataBasesVM dataBasesVM,
+ LoginVM loginVM,
+ BaseManagementVM baseManagementVM,
+ UserManagementVM userManagementVM,
+ IServiceProvider provider)
{
- CarouselPageVM[] pages = { dataBasesVM, loginVM, baseManagementVM, userManagementVM };
- foreach (var page in pages) {
- page.NextPageCommand = ReactiveCommand.Create(NextPage);
- page.PreviousPageCommand = ReactiveCommand.Create(PreviousPage);
- page.ChangePageCommand = ReactiveCommand.Create(ChangePage);
- }
+ Pages = new ObservableCollection {
+ loginVM, dataBasesVM, baseManagementVM, userManagementVM
+ };
+ rootPagesCount = Pages.Count;
login = loginVM;
+
+ foreach(var page in Pages)
+ WirePage(page);
+ }
+
+ private void WirePage(CarouselPageVM page) {
+ page.NextPageCommand = ReactiveCommand.Create(NextPage);
+ page.PreviousPageCommand = ReactiveCommand.Create(PreviousPage);
+ page.ChangePageCommand = ReactiveCommand.Create(ChangePage);
+ page.PushPageCommand = ReactiveCommand.Create(PushPage);
+ page.PopPageCommand = ReactiveCommand.Create(PopPage);
+ page.PopToRootCommand = ReactiveCommand.Create(PopToRoot);
}
public void SaveConnections() {
@@ -33,15 +57,53 @@ public void SaveConnections() {
}
public void ChangePage(int index) {
+ if(index < 0 || index >= Pages.Count) return;
SelectedPageIndex = index;
}
public void NextPage() {
- ChangePage((SelectedPageIndex + 1) % PagesCount);
+ PopToRoot();
+ ChangePage((SelectedPageIndex + 1) % rootPagesCount);
}
public void PreviousPage() {
- ChangePage((SelectedPageIndex - 1 + PagesCount) % PagesCount);
+ PopToRoot();
+ ChangePage((SelectedPageIndex - 1 + rootPagesCount) % rootPagesCount);
+ }
+
+ public void PushPage(CarouselPageVM page) {
+ if(page == null) return;
+ WirePage(page);
+ Pages.Add(page);
+ SelectedPageIndex = Pages.Count - 1;
+ }
+
+ public void PopPage() {
+ if(Pages.Count <= rootPagesCount) return;
+ int last = Pages.Count - 1;
+ Pages.RemoveAt(last);
+ SelectedPageIndex = Pages.Count - 1;
+ }
+
+ public void PopToRoot() {
+ while(Pages.Count > rootPagesCount)
+ Pages.RemoveAt(Pages.Count - 1);
+ if(SelectedPageIndex >= rootPagesCount)
+ SelectedPageIndex = rootPagesCount - 1;
+ }
+
+ ///
+ /// Найти первую страницу указанного типа в стеке и переключиться на неё, сняв всё, что стоит выше
+ ///
+ public void PopToPage() where TPage : CarouselPageVM {
+ int targetIdx = -1;
+ for(int i = 0; i < Pages.Count; i++) {
+ if(Pages[i] is TPage) { targetIdx = i; break; }
+ }
+ if(targetIdx < 0) return;
+ while(Pages.Count > targetIdx + 1)
+ Pages.RemoveAt(Pages.Count - 1);
+ SelectedPageIndex = targetIdx;
}
}
}
diff --git a/QS.Launcher/ViewModels/PageViewModels/CarouselPageVM.cs b/QS.Launcher/ViewModels/PageViewModels/CarouselPageVM.cs
index 8cfa391a0..90524aba9 100644
--- a/QS.Launcher/ViewModels/PageViewModels/CarouselPageVM.cs
+++ b/QS.Launcher/ViewModels/PageViewModels/CarouselPageVM.cs
@@ -2,11 +2,29 @@
using QS.ViewModels;
namespace QS.Launcher.ViewModels.PageViewModels {
+ ///
+ /// NextPage/PreviousPage/ChangePage — кольцевая навигация по корневым страницам
+ ///
public class CarouselPageVM : ViewModelBase {
public ICommand NextPageCommand { get; set; }
public ICommand PreviousPageCommand { get; set; }
public ICommand ChangePageCommand { get; set; }
+
+ ///
+ /// добавить страницу в конец Carousel и переключить фокус на неё
+ ///
+ public ICommand PushPageCommand { get; set; }
+
+ ///
+ /// Закрыть текущую нерутовую страницу и вернуться на предыдущую.
+ ///
+ public ICommand PopPageCommand { get; set; }
+
+ ///
+ /// Закрыть все нерутовые страницы и вернуться к корневым вкладкам.
+ ///
+ public ICommand PopToRootCommand { get; set; }
}
}
diff --git a/QS.Launcher/ViewModels/PageViewModels/DataBase/CreateDataBaseProgressVM.cs b/QS.Launcher/ViewModels/PageViewModels/DataBase/CreateDataBaseProgressVM.cs
new file mode 100644
index 000000000..77b077e07
--- /dev/null
+++ b/QS.Launcher/ViewModels/PageViewModels/DataBase/CreateDataBaseProgressVM.cs
@@ -0,0 +1,148 @@
+using System;
+using System.Reactive;
+using System.Threading;
+using System.Threading.Tasks;
+using QS.DbManagement;
+using QS.DBScripts.Controllers;
+using QS.Dialog;
+using QS.Launcher.Services;
+using ReactiveUI;
+
+namespace QS.Launcher.ViewModels.PageViewModels.DataBase {
+ ///
+ /// показывает прогресс создания базы, прогресс приходит из не-UI потока, поэтому все мутации
+ /// reactive-свойств проксируются через IUiThreadInvoker
+ ///
+ public class CreateDataBaseProgressVM : CarouselPageVM, IProgressBarDisplayable {
+ private static NLog.Logger logger = NLog.LogManager.GetCurrentClassLogger();
+
+ public IDbProvider Provider { get; }
+ public Connection Connection { get; }
+ public string DbName { get; }
+ public string DbTitle { get; }
+
+ private readonly IDbCreatorInteraction interaction;
+ private readonly IServiceProvider services;
+ private readonly IUiThreadInvoker uiThread;
+ private readonly CancellationTokenSource cts;
+
+ #region IProgressBarDisplayable backed properties
+
+ private double minValue;
+ private double maxValue = 1;
+ private double currentValue;
+ private string currentText;
+ private bool isStarted;
+
+ public double MinValue {
+ get => minValue;
+ private set => this.RaiseAndSetIfChanged(ref minValue, value);
+ }
+ public double MaxValue {
+ get => maxValue;
+ private set => this.RaiseAndSetIfChanged(ref maxValue, value);
+ }
+ public double Value {
+ get => currentValue;
+ private set => this.RaiseAndSetIfChanged(ref currentValue, value);
+ }
+ public string CurrentText {
+ get => currentText;
+ private set => this.RaiseAndSetIfChanged(ref currentText, value);
+ }
+ public bool IsStarted {
+ get => isStarted;
+ private set => this.RaiseAndSetIfChanged(ref isStarted, value);
+ }
+
+ #endregion
+
+ /// Поднимается, когда база успешно создана, на него должен быть подписан DataBasesVM
+ public event Action DatabaseCreated;
+
+ /// Поднимается, когда создание завершилось отменой
+ public event Action DatabaseCreationFailed;
+
+ public ReactiveCommand StartCreationCommand { get; }
+ public ReactiveCommand CancelCommand { get; }
+
+ public CreateDataBaseProgressVM(
+ IDbProvider provider,
+ Connection connection,
+ string dbName,
+ string dbTitle,
+ IDbCreatorInteraction interaction,
+ IUiThreadInvoker uiThread,
+ IServiceProvider services)
+ {
+ Provider = provider ?? throw new ArgumentNullException(nameof(provider));
+ Connection = connection ?? throw new ArgumentNullException(nameof(connection));
+ DbName = dbName ?? throw new ArgumentNullException(nameof(dbName));
+ DbTitle = dbTitle ?? throw new ArgumentNullException(nameof(dbTitle));
+ this.interaction = interaction ?? throw new ArgumentNullException(nameof(interaction));
+ this.uiThread = uiThread ?? throw new ArgumentNullException(nameof(uiThread));
+ this.services = services ?? throw new ArgumentNullException(nameof(services));
+ cts = new CancellationTokenSource();
+
+ StartCreationCommand = ReactiveCommand.CreateFromTask(StartCreationAsync);
+ CancelCommand = ReactiveCommand.Create(() => {
+ cts.Cancel();
+ PopToRootCommand?.Execute(null);
+ });
+ }
+
+ public async Task StartCreationAsync() {
+ try {
+ var args = new CreatorFactoryArgs {
+ Provider = Provider,
+ Progress = this,
+ Interaction = interaction,
+ CancellationToken = cts.Token,
+ ServiceProvider = services
+ };
+ IDBCreator creator = Connection.ConnectionType.CreateCreator(args);
+ bool ok = await creator.RunCreationAsync(DbName, DbTitle);
+ if(ok)
+ DatabaseCreated?.Invoke();
+ else
+ DatabaseCreationFailed?.Invoke();
+ }
+ catch(OperationCanceledException) {
+ logger.Info("Создание базы отменено.");
+ DatabaseCreationFailed?.Invoke();
+ }
+ catch(Exception ex) {
+ logger.Error(ex, "Сбой в процессе создания базы.");
+ await interaction.ReportErrorAsync(ex.Message, null);
+ DatabaseCreationFailed?.Invoke();
+ }
+ }
+
+ #region IProgressBarDisplayable
+ public void Start(double maxValue = 1, double minValue = 0, string text = null, double startValue = 0) {
+ uiThread.Post(() => {
+ MaxValue = maxValue;
+ MinValue = minValue;
+ Value = startValue;
+ if(text != null) CurrentText = text;
+ IsStarted = true;
+ });
+ }
+
+ public void Update(double curValue) => uiThread.Post(() => Value = curValue);
+
+ public void UpdateMax(double maxValue) => uiThread.Post(() => MaxValue = maxValue);
+
+ public void Update(string curText) => uiThread.Post(() => CurrentText = curText);
+
+ public void Add(double addValue = 1, string text = null) {
+ uiThread.Post(() => {
+ Value += addValue;
+ if(text != null) CurrentText = text;
+ });
+ }
+
+ public void Close() => uiThread.Post(() => IsStarted = false);
+ #endregion
+ }
+}
diff --git a/QS.Launcher/ViewModels/PageViewModels/DataBase/CreateDataBaseSettingsVM.cs b/QS.Launcher/ViewModels/PageViewModels/DataBase/CreateDataBaseSettingsVM.cs
new file mode 100644
index 000000000..ea1d5811a
--- /dev/null
+++ b/QS.Launcher/ViewModels/PageViewModels/DataBase/CreateDataBaseSettingsVM.cs
@@ -0,0 +1,59 @@
+using System;
+using System.Reactive;
+using System.Reactive.Linq;
+using QS.DbManagement;
+using ReactiveUI;
+
+namespace QS.Launcher.ViewModels.PageViewModels.DataBase {
+ ///
+ /// По «Далее» создаёт CreateDataBaseProgressVM и пушит её в Carousel поверх текущей
+ ///
+ public class CreateDataBaseSettingsVM : CarouselPageVM {
+ public IDbProvider Provider { get; }
+ public Connection Connection { get; }
+ private readonly IServiceProvider services;
+
+ private string dbTitle;
+ public string DbTitle {
+ get => dbTitle;
+ set => this.RaiseAndSetIfChanged(ref dbTitle, value);
+ }
+
+ private string dbName;
+ public string DbName {
+ get => dbName;
+ set => this.RaiseAndSetIfChanged(ref dbName, value);
+ }
+
+ public ReactiveCommand CreateDataBaseCommand { get; }
+ public ReactiveCommand CancelCommand { get; }
+
+ ///
+ /// Сообщает заинтересованным о том, что только что создана
+ /// progress-VM и пора подписаться на её события
+ ///
+ public event Action ProgressPageRequested;
+
+ public CreateDataBaseSettingsVM(IDbProvider provider, Connection connection, IServiceProvider services) {
+ Provider = provider ?? throw new ArgumentNullException(nameof(provider));
+ Connection = connection ?? throw new ArgumentNullException(nameof(connection));
+ this.services = services ?? throw new ArgumentNullException(nameof(services));
+
+ var canCreate = this.WhenAnyValue(x => x.DbName, x => x.DbTitle,
+ (name, title) => !string.IsNullOrWhiteSpace(name) && !string.IsNullOrWhiteSpace(title));
+
+ CreateDataBaseCommand = ReactiveCommand.Create(GoToProgress, canCreate);
+ CancelCommand = ReactiveCommand.Create(() => PopPageCommand?.Execute(null));
+ }
+
+ private void GoToProgress() {
+ // Резолв через ActivatorUtilities — DI подставляет IDbCreatorInteraction/IUiThreadInvoker,
+ // а провайдер/соединение/имена приходят как runtime-аргументы.
+ var progress = Microsoft.Extensions.DependencyInjection.ActivatorUtilities
+ .CreateInstance(services, Provider, Connection, DbName, DbTitle);
+
+ ProgressPageRequested?.Invoke(progress);
+ PushPageCommand?.Execute(progress);
+ }
+ }
+}
diff --git a/QS.Launcher/ViewModels/PageViewModels/DataBase/CreateDataBaseVM.cs b/QS.Launcher/ViewModels/PageViewModels/DataBase/CreateDataBaseVM.cs
deleted file mode 100644
index 936f4c80d..000000000
--- a/QS.Launcher/ViewModels/PageViewModels/DataBase/CreateDataBaseVM.cs
+++ /dev/null
@@ -1,42 +0,0 @@
-using Dapper;
-using QS.DbManagement;
-using ReactiveUI;
-using QS.Launcher.AppRunner;
-using QS.Project.Versioning;
-using System;
-using System.Windows.Input;
-using System.Reactive;
-
-namespace QS.Launcher.ViewModels.PageViewModels.DataBase {
- public class CreateDataBaseVM : ReactiveObject {
- private static NLog.Logger logger = NLog.LogManager.GetCurrentClassLogger();
-
- public IDbProvider Provider { get; private set; }
-
- private string dbTitle;
- public string DbTitle {
- get => dbTitle;
- set => this.RaiseAndSetIfChanged(ref dbTitle, value);
- }
- private string dbName;
- public string DbName {
- get => dbName;
- set => this.RaiseAndSetIfChanged(ref dbName, value);
- }
- public ReactiveCommand CreateDataBaseCommand { get; }
- public event Action DatabaseCreated;
-
- public CreateDataBaseVM(IDbProvider dbProvider) {
- Provider = dbProvider;
-
- CreateDataBaseCommand = ReactiveCommand.Create(() => {
- if(Provider.CanCreateDatabase)
- Provider.CreateDatabase(DbName, DbTitle);
- else
- throw new InvalidOperationException("пользователь не должен получать доступ к созданию базы");
-
- DatabaseCreated?.Invoke();
- });
- }
- }
-}
diff --git a/QS.Launcher/ViewModels/PageViewModels/DataBase/DataBasesVM.cs b/QS.Launcher/ViewModels/PageViewModels/DataBase/DataBasesVM.cs
index c9606765c..1b7198ae1 100644
--- a/QS.Launcher/ViewModels/PageViewModels/DataBase/DataBasesVM.cs
+++ b/QS.Launcher/ViewModels/PageViewModels/DataBase/DataBasesVM.cs
@@ -1,17 +1,16 @@
-using DynamicData.Kernel;
-using QS.DbManagement;
-using QS.Dialog;
-using QS.Launcher.AppRunner;
-using QS.Project.Versioning;
-using ReactiveUI;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Reactive;
using System.Reactive.Linq;
-using System.Threading.Tasks;
using System.Windows.Input;
-using System.Xml.Linq;
+using DynamicData.Kernel;
+using Microsoft.Extensions.DependencyInjection;
+using QS.DbManagement;
+using QS.Dialog;
+using QS.Launcher.AppRunner;
+using QS.Project.Versioning;
+using ReactiveUI;
namespace QS.Launcher.ViewModels.PageViewModels.DataBase {
public class DataBasesVM : CarouselPageVM {
@@ -27,11 +26,12 @@ public IDbProvider Provider {
Databases = provider.GetUserDatabases(applicationInfo).AsList();
this.RaisePropertyChanged(nameof(Databases));
- // Загружаем и устанавливаем последнюю выбранную базу
LoadLastSelectedDatabase();
}
}
+ public Connection CurrentConnection => currentConnection;
+
public void SetProvider(IDbProvider dbProvider, Connection connection, Action saveConnections) {
currentConnection = connection;
saveConnectionsAction = saveConnections;
@@ -49,9 +49,9 @@ public DbInfo SelectedDatabase {
public bool IsAdmin { get; set; } = false;
public bool ShouldCloseLauncherAfterStart { get; set; } = true;
-
+
private readonly LauncherOptions launcherOptions;
-
+
///
/// Указывает, должна ли быть видна галочка "Не закрывать лаунчер после подключения".
/// Видна только в standalone режиме (когда лаунчер - отдельное приложение).
@@ -62,40 +62,66 @@ public DbInfo SelectedDatabase {
public ReactiveCommand OpenCreateDatabaseCommand { get; }
public event Action StartLaunchProgram;
- public event Func> RequestShowCreateDbWindow;
IInteractiveMessage interactiveMessage;
+ private readonly IServiceProvider serviceProvider;
private readonly IAppRunner appRunner;
private readonly IApplicationInfo applicationInfo;
- public DataBasesVM(IAppRunner appRunner, IApplicationInfo applicationInfo, IInteractiveMessage interactiveMessage, LauncherOptions launcherOptions) {
+ public DataBasesVM(
+ IAppRunner appRunner,
+ IApplicationInfo applicationInfo,
+ IInteractiveMessage interactiveMessage,
+ LauncherOptions launcherOptions,
+ IServiceProvider serviceProvider)
+ {
this.appRunner = appRunner ?? throw new ArgumentNullException(nameof(appRunner));
this.applicationInfo = applicationInfo ?? throw new ArgumentNullException(nameof(applicationInfo));
this.interactiveMessage = interactiveMessage ?? throw new ArgumentNullException(nameof(interactiveMessage));
this.launcherOptions = launcherOptions;
-
- logger.Info($">>> DataBasesVM constructor: launcherOptions={launcherOptions}, IsStandalone={launcherOptions?.IsStandalone}");
+ this.serviceProvider = serviceProvider ?? throw new ArgumentNullException(nameof(serviceProvider));
IObservable canExecuteConnection = this
.WhenAnyValue(x => x.SelectedDatabase)
.Select(x => x != null);
- IObservable canExecuteCreation = this
- .WhenAnyValue(x => x.SelectedDatabase)
- .Select(x => x != null);
ConnectCommand = ReactiveCommand.Create(Connect, canExecuteConnection);
- OpenCreateDatabaseCommand = ReactiveCommand.CreateFromTask(OpenCreateDatabaseAsync);
+ OpenCreateDatabaseCommand = ReactiveCommand.Create(OpenCreateDatabase);
}
- private async Task OpenCreateDatabaseAsync() {
- if(OpenCreateDatabaseCommand != null) {
- (string title, string name) = await RequestShowCreateDbWindow.Invoke(this);
- Databases = provider.GetUserDatabases(applicationInfo).AsList();
- this.RaisePropertyChanged(nameof(Databases));
- SelectedDatabase = Databases.FirstOrDefault(db => db.Title == title && db.BaseName == name);
- this.RaisePropertyChanged(nameof(SelectedDatabase));
- }
+ ///
+ /// создаёт CreateDataBaseSettingsVM возвращает фокус на DataBasesVM и обновляет список баз.
+ ///
+ private void OpenCreateDatabase() {
+ if(provider == null || currentConnection == null)
+ return;
+
+ var settings = ActivatorUtilities.CreateInstance(
+ serviceProvider, provider, currentConnection);
+
+ settings.ProgressPageRequested += progressVm => {
+ progressVm.DatabaseCreated += OnDatabaseCreatedFromWizard;
+ progressVm.DatabaseCreationFailed += () => {
+ // пользователь сам решит вернуться или попробовать снова
+ };
+ };
+
+ PushPageCommand?.Execute(settings);
+ }
+
+ private void OnDatabaseCreatedFromWizard() {
+ // Закрываем все wizard-страницы и возвращаемся на DataBasesVM.
+ PopToRootCommand?.Execute(null);
+ RefreshDatabases();
+ }
+
+ public void RefreshDatabases() {
+ if(provider == null) return;
+ Databases = provider.GetUserDatabases(applicationInfo).AsList();
+ this.RaisePropertyChanged(nameof(Databases));
+ SelectedDatabase = Databases.FirstOrDefault();
+ this.RaisePropertyChanged(nameof(SelectedDatabase));
}
private void LoadLastSelectedDatabase() {
@@ -103,7 +129,7 @@ private void LoadLastSelectedDatabase() {
return;
// Используем LastBaseId из текущего подключения
- if(currentConnection?.LastBaseId != null)
+ if(currentConnection?.LastBaseId != null)
SelectedDatabase = Databases.FirstOrDefault(db => db.BaseId == currentConnection.LastBaseId.Value);
if(SelectedDatabase == null)
@@ -119,12 +145,8 @@ public void Connect() {
return;
}
- // Сохраняем последнюю выбранную базу
SaveLastSelectedDatabase();
- // Определяем, нужно ли закрывать лаунчер через Shutdown
- // В standalone режиме учитываем галочку ShouldCloseLauncherAfterStart
- // В in-process режиме НЕ делаем shutdown (возвращаем false)
var isStandalone = launcherOptions?.IsStandalone ?? false;
logger.Info($">>> Connect: IsStandalone={isStandalone}, ShouldCloseLauncherAfterStart={ShouldCloseLauncherAfterStart}");
@@ -139,10 +161,8 @@ public void Connect() {
private void SaveLastSelectedDatabase() {
if(SelectedDatabase == null || currentConnection == null)
return;
- // Сохраняем BaseId в текущее подключение
currentConnection.LastBaseId = SelectedDatabase.BaseId;
- // Вызываем сохранение подключений
saveConnectionsAction?.Invoke();
}
}
diff --git a/QS.Project.Core/DBScripts/Controllers/IDBCreator.cs b/QS.Project.Core/DBScripts/Controllers/IDBCreator.cs
index d8c7daaaf..6ab27ea04 100644
--- a/QS.Project.Core/DBScripts/Controllers/IDBCreator.cs
+++ b/QS.Project.Core/DBScripts/Controllers/IDBCreator.cs
@@ -1,7 +1,12 @@
+using System.Threading.Tasks;
+
namespace QS.DBScripts.Controllers
{
+ ///
+ /// Универсальный контракт создания и наполнения базы данных.
+ ///
public interface IDBCreator
{
- void RunCreation(string server, string dbname);
+ Task RunCreationAsync(string dbName, string dbTitle);
}
-}
\ No newline at end of file
+}
diff --git a/QS.Project.Core/DBScripts/Controllers/IDbCreatorInteraction.cs b/QS.Project.Core/DBScripts/Controllers/IDbCreatorInteraction.cs
new file mode 100644
index 000000000..3a499361a
--- /dev/null
+++ b/QS.Project.Core/DBScripts/Controllers/IDbCreatorInteraction.cs
@@ -0,0 +1,14 @@
+using System.Threading.Tasks;
+
+namespace QS.DBScripts.Controllers
+{
+ ///
+ /// всплывающие окна у пользователя при уточнениях
+ ///
+ public interface IDbCreatorInteraction
+ {
+ Task AskDropExistingDatabaseAsync(string dbName);
+
+ Task ReportErrorAsync(string text, string lastExecutedStatement);
+ }
+}
diff --git a/QS.Project.Core/DBScripts/Controllers/IDbScriptsConfiguration.cs b/QS.Project.Core/DBScripts/Controllers/IDbScriptsConfiguration.cs
new file mode 100644
index 000000000..9732a81d4
--- /dev/null
+++ b/QS.Project.Core/DBScripts/Controllers/IDbScriptsConfiguration.cs
@@ -0,0 +1,14 @@
+using System;
+
+namespace QS.DBScripts.Controllers
+{
+ ///
+ /// Источник SQL-скриптов конкретного приложения, передаваемый в реализации IDBCreator
+ ///
+ public interface IDbScriptsConfiguration
+ {
+ Version CreationVersion { get; }
+
+ string GetCreationSqlScript();
+ }
+}
diff --git a/QS.Updater.Core/DBScripts/Controllers/IDbCreateController.cs b/QS.Updater.Core/DBScripts/Controllers/IDbCreateController.cs
index b7f14edae..98e27d3c4 100644
--- a/QS.Updater.Core/DBScripts/Controllers/IDbCreateController.cs
+++ b/QS.Updater.Core/DBScripts/Controllers/IDbCreateController.cs
@@ -1,4 +1,4 @@
-using System;
+using System;
using QS.Dialog;
namespace QS.DBScripts.Controllers
diff --git a/QS.Updater.Core/DBScripts/Models/MySqlDbCreateModel.cs b/QS.Updater.Core/DBScripts/Models/MySqlDbCreateModel.cs
index 1ccec37f4..f9d5fb545 100644
--- a/QS.Updater.Core/DBScripts/Models/MySqlDbCreateModel.cs
+++ b/QS.Updater.Core/DBScripts/Models/MySqlDbCreateModel.cs
@@ -1,112 +1,127 @@
using System;
using System.Text.RegularExpressions;
+using System.Threading;
+using System.Threading.Tasks;
using MySqlConnector;
using QS.DBScripts.Controllers;
+using QS.Dialog;
namespace QS.DBScripts.Models
{
- public class MySqlDbCreateModel
+ public class MySqlDbCreateModel : IDBCreator
{
static NLog.Logger logger = NLog.LogManager.GetCurrentClassLogger();
- private readonly IDbCreateController controller;
- private readonly CreationScript script;
- ///
- /// Если true (по умолчанию), после создания базы автоматически записывает
- /// новый GUID в таблицу base_parameters (параметр BaseGuid).
- ///
+ private readonly string connectionString;
+ private readonly IDbScriptsConfiguration scripts;
+ private readonly IProgressBarDisplayable progress;
+ private readonly IDbCreatorInteraction interaction;
+ private readonly CancellationToken cancellationToken;
+
public bool FillBaseGuid { get; set; } = true;
- public MySqlDbCreateModel(IDbCreateController controller, CreationScript script)
+ public MySqlDbCreateModel(
+ string connectionString,
+ IDbScriptsConfiguration scripts,
+ IProgressBarDisplayable progress,
+ IDbCreatorInteraction interaction,
+ CancellationToken cancellationToken)
{
- this.controller = controller ?? throw new ArgumentNullException(nameof(controller));
- this.script = script ?? throw new ArgumentNullException(nameof(script));
+ if(string.IsNullOrWhiteSpace(connectionString))
+ throw new ArgumentException("Connection string is required", nameof(connectionString));
+ this.connectionString = connectionString;
+ this.scripts = scripts ?? throw new ArgumentNullException(nameof(scripts));
+ this.progress = progress ?? throw new ArgumentNullException(nameof(progress));
+ this.interaction = interaction ?? throw new ArgumentNullException(nameof(interaction));
+ this.cancellationToken = cancellationToken;
}
- public bool RunCreation(string server, string dbname, string login, string password)
- {
- string connStr, host;
- uint port = 3306;
- string[] uriSplit = server.Split(new char[] { ':' }, 2, StringSplitOptions.RemoveEmptyEntries);
-
- if (uriSplit.Length == 0) {
- controller.WasError("Имя сервера не корректно.", null);
- return false;
- }
-
- host = uriSplit[0];
- if (uriSplit.Length > 1) {
- uint.TryParse(uriSplit[1], out port);
- }
-
- var conStrBuilder = new MySqlConnectionStringBuilder();
- conStrBuilder.Server = host;
- conStrBuilder.Port = port;
- conStrBuilder.UserID = login;
- conStrBuilder.Password = password;
- conStrBuilder.AllowUserVariables = true;
-
- connStr = conStrBuilder.ConnectionString;
+ public Task RunCreationAsync(string dbName, string dbTitle) {
+ // Тяжёлая часть с MySqlScript.Execute синхронная,
+ // поэтому уносим её на пул, чтобы не блокировать UI-поток
+ return Task.Run(() => RunCreation(dbName, dbTitle), cancellationToken);
+ }
- using (var connectionDB = new MySqlConnection(connStr)) {
- try
- {
+ public bool RunCreation(string dbName, string dbTitle) {
+ using(var connectionDB = new MySqlConnection(connectionString)) {
+ try {
logger.Info("Connecting to MySQL...");
connectionDB.Open();
+ cancellationToken.ThrowIfCancellationRequested();
logger.Info("Проверяем существует ли уже база.");
-
- var sql = "SHOW DATABASES;";
- var cmd = new MySqlCommand(sql, connectionDB);
+ var cmd = new MySqlCommand("SHOW DATABASES;", connectionDB);
bool needDropBase = false;
using (var rdr = cmd.ExecuteReader())
{
- while (rdr.Read())
+ while (rdr.Read())
{
- if (rdr[0].ToString() == dbname)
+ if (rdr[0].ToString() == dbName)
{
- if (controller.NeedDropDatabaseIfExists(dbname))
+ if (interaction.AskDropExistingDatabaseAsync(dbName).GetAwaiter().GetResult())
{
needDropBase = true;
break;
- } else
- return false;
+ }
+ return false;
}
}
}
+ cancellationToken.ThrowIfCancellationRequested();
logger.Info("Создаем новую базу.");
+ progress.Start(text: "Получаем скрипт создания базы");
- controller.Progress.Start(text: "Получаем скрипт создания базы");
-
- string sqlScript = script.GetSqlScript();
+ string sqlScript = scripts.GetCreationSqlScript();
int predictedCount = Regex.Matches(sqlScript, ";").Count;
logger.Debug("Предполагаем наличие {0} команд в скрипте.", predictedCount);
- controller.Progress.Start(maxValue: predictedCount + (needDropBase ? 2 : 1));
+ progress.Start(maxValue: predictedCount + (needDropBase ? 2 : 1));
- if (needDropBase)
+ if (needDropBase)
{
- logger.Info("Удаляем существующую базу {0}.", dbname);
- controller.Progress.Add(text: $"Удаляем существующую базу {dbname}");
- cmd.CommandText = String.Format("DROP DATABASE `{0}`", dbname);
+ logger.Info("Удаляем существующую базу {0}.", dbName);
+ progress.Add(text: $"Удаляем существующую базу {dbName}");
+ cmd.CommandText = $"DROP DATABASE `{dbName}`";
cmd.ExecuteNonQuery();
}
+ cancellationToken.ThrowIfCancellationRequested();
- controller.Progress.Add(text: $"Создаем базу {dbname}");
- cmd.CommandText = String.Format("CREATE SCHEMA `{0}` DEFAULT CHARACTER SET utf8mb4 ;", dbname);
+ progress.Add(text: $"Создаем базу {dbName}");
+ cmd.CommandText = $"CREATE SCHEMA `{dbName}` DEFAULT CHARACTER SET utf8mb4 ;";
cmd.ExecuteNonQuery();
- cmd.CommandText = String.Format("USE `{0}` ;", dbname);
+ cmd.CommandText = $"USE `{dbName}` ;";
cmd.ExecuteNonQuery();
- controller.Progress.Add(text: $"Создаем таблицы в {dbname}");
+ progress.Add(text: $"Создаем таблицы в {dbName}");
var myscript = new MySqlScript(connectionDB, sqlScript);
myscript.StatementExecuted += Myscript_StatementExecuted;
var commands = myscript.Execute();
logger.Debug("Выполнено {0} SQL-команд.", commands);
+ cancellationToken.ThrowIfCancellationRequested();
+
+ // Записываем человекочитаемое название базы, используется для отображения в списке БД
+ logger.Info("Записываем Title='{0}' в base_parameters.", dbTitle);
+ cmd.CommandText =
+ "INSERT INTO base_parameters (name, str_value) VALUES ('Title', @title) "
+ + "ON DUPLICATE KEY UPDATE str_value = @title";
+ cmd.Parameters.Clear();
+ cmd.Parameters.AddWithValue("@title", dbTitle ?? string.Empty);
+ cmd.ExecuteNonQuery();
- if (FillBaseGuid) {
+ // Версия пустой базы для апдейтера.
+ if(scripts.CreationVersion != null) {
+ logger.Info("Записываем version='{0}' в base_parameters.", scripts.CreationVersion);
+ cmd.CommandText =
+ "INSERT INTO base_parameters (name, str_value) VALUES ('version', @ver) "
+ + "ON DUPLICATE KEY UPDATE str_value = @ver";
+ cmd.Parameters.Clear();
+ cmd.Parameters.AddWithValue("@ver", scripts.CreationVersion.ToString());
+ cmd.ExecuteNonQuery();
+ }
+
+ if(FillBaseGuid) {
logger.Info("Генерируем BaseGuid");
cmd.CommandText =
"INSERT INTO base_parameters (name, str_value) VALUES ('BaseGuid', @guid)";
@@ -115,36 +130,41 @@ public bool RunCreation(string server, string dbname, string login, string passw
cmd.ExecuteNonQuery();
logger.Info("BaseGuid успешно записан.");
}
-
+ }
+ catch(OperationCanceledException) {
+ logger.Info("Создание базы отменено пользователем.");
+ throw;
}
catch(InvalidCastException ex) { //FIXME Временный для более адекватного обхода проблемы с отсутствием поддержки MariaDB 10.10. Удалить как починим работу с этой версией.
logger.Error(ex, "Ошибка подключения к серверу.");
- controller.WasError("Работа с MariaDB 10.10 пока не поддерживается. Установите версию MariaDB 10.9.", lastExecutedStatement);
+ interaction.ReportErrorAsync("Работа с MariaDB 10.10 пока не поддерживается. Установите версию MariaDB 10.9.", lastExecutedStatement)
+ .GetAwaiter().GetResult();
return false;
}
- catch (MySqlException ex)
- {
- logger.Info("Строка соединения: {0}", connStr);
- logger.Error(ex, "Ошибка подключения к серверу.");
- if (ex.Number == 1045 || ex.Number == 0)
- controller.WasError("Доступ запрещен.\nПроверьте логин и пароль.", lastExecutedStatement);
- else if (ex.Number == 1042)
- controller.WasError("Не удалось подключиться к серверу БД.", lastExecutedStatement);
+ catch(MySqlException ex) {
+ logger.Error(ex, "Ошибка работы с MySQL.");
+ string text;
+ if(ex.Number == 1045 || ex.Number == 0)
+ text = "Доступ запрещен.\nПроверьте логин и пароль.";
+ else if(ex.Number == 1042)
+ text = "Не удалось подключиться к серверу БД.";
else
- controller.WasError(ex.Message, lastExecutedStatement);
-
+ text = ex.Message;
+ interaction.ReportErrorAsync(text, lastExecutedStatement).GetAwaiter().GetResult();
return false;
- } finally {
- controller.Progress.Close();
+ }
+ finally {
+ if(progress.IsStarted)
+ progress.Close();
}
}
return true;
}
private string lastExecutedStatement;
- void Myscript_StatementExecuted(object sender, MySqlScriptEventArgs args)
+ private void Myscript_StatementExecuted(object sender, MySqlScriptEventArgs args)
{
- controller.Progress.Add();
+ progress.Add();
logger.Debug("SQL Command = {0}", args.StatementText);
lastExecutedStatement = $"[{args.Line}:{args.Position}]{args.StatementText}";
}
From a43ac621d26ce158efacabc04c41dbf35c99bde9 Mon Sep 17 00:00:00 2001
From: J0shlerB0y <86416124+J0shlerB0y@users.noreply.github.com>
Date: Fri, 1 May 2026 22:03:23 +0300
Subject: [PATCH 04/19] =?UTF-8?q?=D0=BF=D0=BE=D1=87=D0=B8=D0=BD=D0=B8?=
=?UTF-8?q?=D0=BB=20=D0=BC=D0=B0=D0=BF=D0=BF=D0=B8=D0=BD=D0=B3=20=D1=81?=
=?UTF-8?q?=D1=82=D1=80=D0=B0=D0=BD=D0=B8=D1=86=20+=20=D1=81=D0=BA=D1=80?=
=?UTF-8?q?=D0=B8=D0=BF=D1=82=D1=8B=20=D0=BD=D0=B0=D0=BF=D0=BE=D0=BB=D0=BD?=
=?UTF-8?q?=D0=B5=D0=BD=D0=B8=D1=8F=20=D0=B1=D0=B0=D0=B7?=
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
перевёл обратно на старый маппинг страниц
---
.../{ => DataBase}/QSCloudProvider.cs | 2 +-
.../QsCloudConnectionTypeBase.cs | 2 +-
.../DataBase/QsCloudScriptsConfiguration.cs | 30 +
QS.Cloud.Client/QS.Cloud.Client.csproj | 8 +-
QS.Cloud.Client/Scripts/new_empty.sql | 772 ++++++++++++++++++
QS.Launcher.Avalonia/DependencyInjection.cs | 6 +
QS.Launcher.Avalonia/LauncherApp.axaml | 21 -
.../Views/MainWindow.axaml.cs | 7 +-
.../Views/Pages/BaseManagementView.axaml.cs | 5 +-
.../Pages/DataBase/DataBasesView.axaml.cs | 11 +-
.../Views/Pages/LoginView.axaml.cs | 4 +-
.../Views/Pages/UserManagementView.axaml.cs | 5 +-
.../PageViewModels/BaseManagementVM.cs | 1 -
.../Controllers/IDbScriptsConfiguration.cs | 14 -
.../DBScripts/IDbScriptsConfiguration.cs | 12 +
.../DBScripts/Models/CreationScript.cs | 2 +-
.../DBScripts/Models/MySqlDbCreateModel.cs | 67 +-
17 files changed, 875 insertions(+), 94 deletions(-)
rename QS.Cloud.Client/{ => DataBase}/QSCloudProvider.cs (99%)
rename QS.Cloud.Client/{ => DataBase}/QsCloudConnectionTypeBase.cs (96%)
create mode 100644 QS.Cloud.Client/DataBase/QsCloudScriptsConfiguration.cs
create mode 100644 QS.Cloud.Client/Scripts/new_empty.sql
delete mode 100644 QS.Project.Core/DBScripts/Controllers/IDbScriptsConfiguration.cs
create mode 100644 QS.Updater.Core/DBScripts/IDbScriptsConfiguration.cs
diff --git a/QS.Cloud.Client/QSCloudProvider.cs b/QS.Cloud.Client/DataBase/QSCloudProvider.cs
similarity index 99%
rename from QS.Cloud.Client/QSCloudProvider.cs
rename to QS.Cloud.Client/DataBase/QSCloudProvider.cs
index 3faae9181..5395936aa 100644
--- a/QS.Cloud.Client/QSCloudProvider.cs
+++ b/QS.Cloud.Client/DataBase/QSCloudProvider.cs
@@ -10,7 +10,7 @@
using System;
using QS.Cloud.Client.Clients;
-namespace QS.Cloud.Client
+namespace QS.Cloud.Client.DataBase
{
public class QSCloudProvider : IDbProvider {
public string ConnectionString { get => throw new NotImplementedException(); set => throw new NotImplementedException(); }
diff --git a/QS.Cloud.Client/QsCloudConnectionTypeBase.cs b/QS.Cloud.Client/DataBase/QsCloudConnectionTypeBase.cs
similarity index 96%
rename from QS.Cloud.Client/QsCloudConnectionTypeBase.cs
rename to QS.Cloud.Client/DataBase/QsCloudConnectionTypeBase.cs
index 504c04fe5..31a13de3e 100644
--- a/QS.Cloud.Client/QsCloudConnectionTypeBase.cs
+++ b/QS.Cloud.Client/DataBase/QsCloudConnectionTypeBase.cs
@@ -4,7 +4,7 @@
using System.Reflection;
using QS.Utilities.Extensions;
-namespace QS.Cloud.Client {
+namespace QS.Cloud.Client.DataBase {
public class QsCloudConnectionTypeBase : ConnectionTypeBase {
public QsCloudConnectionTypeBase() {
Title = "QS: Облако";
diff --git a/QS.Cloud.Client/DataBase/QsCloudScriptsConfiguration.cs b/QS.Cloud.Client/DataBase/QsCloudScriptsConfiguration.cs
new file mode 100644
index 000000000..091d50455
--- /dev/null
+++ b/QS.Cloud.Client/DataBase/QsCloudScriptsConfiguration.cs
@@ -0,0 +1,30 @@
+using QS.DBScripts;
+using QS.DBScripts.Models;
+using QS.Updater.DB;
+using System;
+using System.Collections.Generic;
+using System.Reflection;
+using System.Text;
+
+namespace QS.Cloud.Client.DataBase {
+ public class QsCloudScriptsConfiguration : IDbScriptsConfiguration {
+ public CreationScript MakeCreationScript() {
+ return new CreationScript(
+ Assembly.GetAssembly(typeof(QsCloudScriptsConfiguration)),
+ "QS.Cloud.Client.Scripts.new_empty.sql",
+ new Version(1, 0, 1)
+ );
+ }
+
+ public UpdateConfiguration MakeUpdateConfiguration() {
+ var configuration = new UpdateConfiguration();
+
+ configuration.AddUpdate(
+ new Version(1, 0),
+ new Version(1, 0, 1),
+ "QS.Cloud.Client.Scripts.1.0.1.sql");
+
+ return configuration;
+ }
+ }
+}
diff --git a/QS.Cloud.Client/QS.Cloud.Client.csproj b/QS.Cloud.Client/QS.Cloud.Client.csproj
index 086526cf2..ebee0064c 100644
--- a/QS.Cloud.Client/QS.Cloud.Client.csproj
+++ b/QS.Cloud.Client/QS.Cloud.Client.csproj
@@ -1,4 +1,4 @@
-
+
netstandard2.0
@@ -15,6 +15,11 @@
+
+
+
+
+
@@ -26,6 +31,7 @@
+
diff --git a/QS.Cloud.Client/Scripts/new_empty.sql b/QS.Cloud.Client/Scripts/new_empty.sql
new file mode 100644
index 000000000..0b30d71aa
--- /dev/null
+++ b/QS.Cloud.Client/Scripts/new_empty.sql
@@ -0,0 +1,772 @@
+-- phpMyAdmin SQL Dump
+-- version 5.0.4deb2~bpo10+1
+-- https://www.phpmyadmin.net/
+--
+-- Host: demeter.srv.qsolution.ru
+-- Generation Time: Apr 30, 2026 at 01:52 PM
+-- Server version: 10.3.39-MariaDB-0+deb10u2
+-- PHP Version: 7.3.31-1~deb10u7
+
+SET SQL_MODE = "NO_AUTO_VALUE_ON_ZERO";
+START TRANSACTION;
+SET time_zone = "+00:00";
+
+
+/*!40101 SET @OLD_CHARACTER_SET_CLIENT=@@CHARACTER_SET_CLIENT */;
+/*!40101 SET @OLD_CHARACTER_SET_RESULTS=@@CHARACTER_SET_RESULTS */;
+/*!40101 SET @OLD_COLLATION_CONNECTION=@@COLLATION_CONNECTION */;
+/*!40101 SET NAMES utf8mb4 */;
+
+--
+-- Database: `QSService`
+--
+
+-- --------------------------------------------------------
+
+--
+-- Table structure for table `accounts`
+--
+
+CREATE TABLE `accounts` (
+ `id` int(10) UNSIGNED NOT NULL,
+ `login` varchar(20) NOT NULL,
+ `client_id` int(10) UNSIGNED DEFAULT NULL,
+ `customer` varchar(50) NOT NULL,
+ `email` varchar(50) DEFAULT NULL,
+ `paid_until` date DEFAULT NULL,
+ `notify_by_days` int(11) DEFAULT NULL COMMENT 'Уведомить за Н дней до окончания',
+ `bases_limit` int(10) UNSIGNED NOT NULL DEFAULT 1,
+ `users_limit` int(10) UNSIGNED NOT NULL DEFAULT 3,
+ `space_limit` int(10) UNSIGNED NOT NULL DEFAULT 500,
+ `deactivated` tinyint(1) NOT NULL DEFAULT 0
+) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_general_ci;
+
+-- --------------------------------------------------------
+
+--
+-- Table structure for table `api_tokens`
+--
+
+CREATE TABLE `api_tokens` (
+ `id` int(10) UNSIGNED NOT NULL,
+ `base_id` int(10) UNSIGNED NOT NULL,
+ `token` char(36) NOT NULL
+) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_general_ci;
+
+-- --------------------------------------------------------
+
+--
+-- Table structure for table `bases`
+--
+
+CREATE TABLE `bases` (
+ `id` int(10) UNSIGNED NOT NULL,
+ `account_id` int(10) UNSIGNED NOT NULL,
+ `server_id` int(10) UNSIGNED NOT NULL,
+ `base_title` varchar(64) DEFAULT NULL COMMENT 'Русское название базы для пользователя',
+ `base_name` varchar(45) NOT NULL,
+ `product_id` int(10) UNSIGNED NOT NULL,
+ `real_name` varchar(64) DEFAULT NULL,
+ `base_guid` char(36) DEFAULT NULL,
+ `wear_lk` tinyint(1) NOT NULL DEFAULT 0,
+ `number_of_lk_client` int(11) DEFAULT 0,
+ `claims_lk` tinyint(1) NOT NULL DEFAULT 0,
+ `postomats` tinyint(1) NOT NULL DEFAULT 0,
+ `catalog` tinyint(1) NOT NULL DEFAULT 0,
+ `comments` text DEFAULT NULL,
+ `ratings` tinyint(1) NOT NULL DEFAULT 0,
+ `appointment_lk` tinyint(1) NOT NULL DEFAULT 0 COMMENT 'включение предварительной записи',
+ `washing_lk` tinyint(1) NOT NULL DEFAULT 0 COMMENT 'Включение отображения стирки в мобильном кабинете',
+ `speccoin_lk` tinyint(1) NOT NULL DEFAULT 0 COMMENT 'Включение функциональности спецкойнов',
+ `size_editing_lk` tinyint(1) NOT NULL DEFAULT 1 COMMENT 'Редактирование размеров в мобилке',
+ `size_editing_days_before` int(10) UNSIGNED NOT NULL DEFAULT 0 COMMENT 'Запрет изменения размеров за указанное количество дней до выдачи.',
+ `postomat_email_notification` tinyint(1) NOT NULL DEFAULT 0 COMMENT 'Дублируют ли постоматы уведомления на Email.',
+ `stock_availability_enable` tinyint(1) NOT NULL DEFAULT 0,
+ `stock_availability_warehouse_id` int(10) UNSIGNED DEFAULT NULL COMMENT 'id склада по которому показывать наличие',
+ `choice_nomenclature_lk` tinyint(1) NOT NULL DEFAULT 0 COMMENT 'Выбор номенклатур сотрудником'
+) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_general_ci;
+
+-- --------------------------------------------------------
+
+--
+-- Table structure for table `bases_scripts`
+--
+
+CREATE TABLE `bases_scripts` (
+ `id` int(10) UNSIGNED NOT NULL,
+ `product_id` int(10) UNSIGNED NOT NULL,
+ `start_version` varchar(15) DEFAULT NULL,
+ `end_version` varchar(15) NOT NULL,
+ `script` mediumtext DEFAULT NULL
+) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_general_ci;
+
+-- --------------------------------------------------------
+
+--
+-- Table structure for table `base_access`
+--
+
+CREATE TABLE `base_access` (
+ `id` int(10) UNSIGNED NOT NULL,
+ `user_id` int(10) UNSIGNED NOT NULL,
+ `base_id` int(10) UNSIGNED NOT NULL,
+ `admin` tinyint(1) NOT NULL DEFAULT 0,
+ `read_only` tinyint(1) NOT NULL DEFAULT 0,
+ `torpedo` tinyint(1) NOT NULL DEFAULT 0 COMMENT 'База доступна в панели инструментов'
+) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_general_ci;
+
+-- --------------------------------------------------------
+
+--
+-- Table structure for table `base_parameters`
+--
+
+CREATE TABLE `base_parameters` (
+ `name` varchar(20) NOT NULL,
+ `str_value` varchar(100) DEFAULT NULL
+) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_general_ci;
+
+-- --------------------------------------------------------
+
+--
+-- Table structure for table `bug_reports`
+--
+
+CREATE TABLE `bug_reports` (
+ `id` int(10) UNSIGNED NOT NULL,
+ `created` datetime DEFAULT NULL,
+ `last_update` datetime DEFAULT NULL,
+ `product_id` int(10) UNSIGNED NOT NULL,
+ `edition` varchar(20) DEFAULT NULL,
+ `version` varchar(16) NOT NULL,
+ `fixed_in_version` varchar(16) DEFAULT NULL COMMENT 'Версия в которой баг пофикшен',
+ `message` varchar(2000) DEFAULT NULL,
+ `stack_trace` text DEFAULT NULL,
+ `description` text DEFAULT NULL,
+ `email` varchar(600) DEFAULT NULL,
+ `count` int(10) UNSIGNED DEFAULT 1,
+ `status` enum('New','InWork','NeedInfo','Rejected','Later','Known','Unreproducable','EndOfLife','Done') DEFAULT 'New',
+ `comments` text DEFAULT NULL
+) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_general_ci;
+
+-- --------------------------------------------------------
+
+--
+-- Table structure for table `bug_reports_messages`
+--
+
+CREATE TABLE `bug_reports_messages` (
+ `id` int(10) UNSIGNED NOT NULL,
+ `created` datetime NOT NULL,
+ `bug_reports_id` int(10) UNSIGNED NOT NULL,
+ `email` varchar(254) DEFAULT NULL,
+ `user_name` varchar(60) DEFAULT NULL,
+ `messages` text DEFAULT NULL,
+ `db_name` varchar(60) DEFAULT NULL,
+ `report_type` enum('User','Automatic','Known') NOT NULL DEFAULT 'User',
+ `log_file` mediumtext DEFAULT NULL
+) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_general_ci;
+
+-- --------------------------------------------------------
+
+--
+-- Table structure for table `clients`
+--
+
+CREATE TABLE `clients` (
+ `id` int(10) UNSIGNED NOT NULL,
+ `name` varchar(300) NOT NULL,
+ `email` varchar(45) DEFAULT NULL,
+ `email_notifications` varchar(200) DEFAULT NULL COMMENT 'Адреса для уведомлений',
+ `city` varchar(45) DEFAULT NULL,
+ `comments` text DEFAULT NULL
+) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_general_ci;
+
+-- --------------------------------------------------------
+
+--
+-- Table structure for table `cloud_users`
+--
+
+CREATE TABLE `cloud_users` (
+ `id` int(10) UNSIGNED NOT NULL,
+ `login` varchar(20) NOT NULL,
+ `name` varchar(80) DEFAULT NULL,
+ `password` varchar(81) NOT NULL,
+ `disabled` tinyint(1) NOT NULL DEFAULT 0 COMMENT 'Пользователь отключен',
+ `is_account_admin` tinyint(1) NOT NULL DEFAULT 0 COMMENT 'Администратор учетной записи',
+ `post` varchar(200) DEFAULT NULL COMMENT 'Должность',
+ `phone` varchar(16) DEFAULT NULL COMMENT 'Телефон',
+ `email` varchar(60) DEFAULT NULL,
+ `account_id` int(10) UNSIGNED NOT NULL,
+ `multi_ip` tinyint(1) NOT NULL DEFAULT 0,
+ `client_id` int(10) UNSIGNED DEFAULT NULL,
+ `comment` text DEFAULT NULL
+) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_general_ci;
+
+-- --------------------------------------------------------
+
+--
+-- Table structure for table `products`
+--
+
+CREATE TABLE `products` (
+ `id` int(10) UNSIGNED NOT NULL,
+ `name` varchar(45) NOT NULL,
+ `internal_name` varchar(45) NOT NULL,
+ `not_support_ver_regexp` varchar(45) DEFAULT NULL,
+ `telegram_notify` varchar(50) DEFAULT NULL COMMENT 'Отправлять уведомления о новых ошибках в чат'
+) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_general_ci;
+
+-- --------------------------------------------------------
+
+--
+-- Table structure for table `product_editions`
+--
+
+CREATE TABLE `product_editions` (
+ `id` int(10) UNSIGNED NOT NULL,
+ `product_id` int(10) UNSIGNED NOT NULL,
+ `code_number` int(10) UNSIGNED DEFAULT NULL COMMENT 'Номер редакции, внутри продукта',
+ `code_name` varchar(10) DEFAULT NULL COMMENT 'Кодовое имя редакции.',
+ `name` varchar(100) DEFAULT NULL COMMENT 'Название редакции отображаемое для пользователя.'
+) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_general_ci;
+
+-- --------------------------------------------------------
+
+--
+-- Table structure for table `product_versions`
+--
+
+CREATE TABLE `product_versions` (
+ `id` int(10) UNSIGNED NOT NULL,
+ `product_id` int(10) UNSIGNED NOT NULL,
+ `modification` varchar(25) DEFAULT NULL,
+ `channel` enum('Current','Stable') NOT NULL DEFAULT 'Current',
+ `disable` tinyint(1) NOT NULL DEFAULT 0,
+ `version_major` int(10) UNSIGNED NOT NULL DEFAULT 0,
+ `version_minor` int(10) UNSIGNED NOT NULL DEFAULT 0,
+ `version_build` int(10) UNSIGNED NOT NULL DEFAULT 0,
+ `version_revision` int(10) UNSIGNED NOT NULL DEFAULT 0,
+ `date` date NOT NULL,
+ `link_install` varchar(256) DEFAULT NULL,
+ `link_news` varchar(256) DEFAULT NULL,
+ `changes` text DEFAULT NULL,
+ `db_update` enum('None','Required','BreakingChange') NOT NULL DEFAULT 'None',
+ `comment` text DEFAULT NULL
+) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_general_ci;
+
+-- --------------------------------------------------------
+
+--
+-- Table structure for table `serial_numbers`
+--
+
+CREATE TABLE `serial_numbers` (
+ `id` int(10) UNSIGNED NOT NULL,
+ `client_id` int(10) UNSIGNED NOT NULL,
+ `number` varchar(50) NOT NULL,
+ `recall` tinyint(1) NOT NULL DEFAULT 0,
+ `notify_by_days` int(11) DEFAULT NULL COMMENT 'Уведомить за Н дней до окончания',
+ `active_until` date DEFAULT NULL,
+ `serial_expiry_date` date DEFAULT NULL COMMENT 'Дата окончания действия серийного номера',
+ `instance` int(10) UNSIGNED NOT NULL DEFAULT 1,
+ `comment` text DEFAULT NULL
+) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_general_ci;
+
+-- --------------------------------------------------------
+
+--
+-- Table structure for table `servers`
+--
+
+CREATE TABLE `servers` (
+ `id` int(10) UNSIGNED NOT NULL,
+ `server_address` varchar(60) NOT NULL,
+ `service_user` varchar(16) NOT NULL,
+ `service_password` varchar(81) NOT NULL,
+ `comment` text DEFAULT NULL
+) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_general_ci;
+
+-- --------------------------------------------------------
+
+--
+-- Table structure for table `sessions`
+--
+
+CREATE TABLE `sessions` (
+ `id` int(10) UNSIGNED NOT NULL,
+ `session_id` varchar(36) NOT NULL,
+ `user_id` int(10) UNSIGNED DEFAULT NULL,
+ `account_id` int(10) UNSIGNED NOT NULL,
+ `base_id` int(10) UNSIGNED NOT NULL,
+ `start_time` datetime NOT NULL,
+ `end_time` datetime NOT NULL,
+ `is_closed` tinyint(1) NOT NULL DEFAULT 0,
+ `login` varchar(40) DEFAULT NULL
+) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_general_ci;
+
+-- --------------------------------------------------------
+
+--
+-- Table structure for table `telemetry_statistics`
+--
+
+CREATE TABLE `telemetry_statistics` (
+ `id` int(10) UNSIGNED NOT NULL,
+ `last_update` datetime NOT NULL,
+ `ip_address` varchar(39) DEFAULT NULL,
+ `product` varchar(20) NOT NULL,
+ `edition` varchar(20) DEFAULT NULL,
+ `version` varchar(20) NOT NULL,
+ `os` varchar(100) DEFAULT NULL,
+ `net_framework` varchar(100) DEFAULT NULL,
+ `is_demo` tinyint(1) NOT NULL DEFAULT 0,
+ `app_edition` int(10) UNSIGNED DEFAULT NULL COMMENT 'Редакция программы',
+ `base_employees` int(10) UNSIGNED DEFAULT NULL COMMENT 'Количество сотрудников в базе',
+ `counter` mediumtext NOT NULL
+) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_general_ci;
+
+-- --------------------------------------------------------
+
+--
+-- Table structure for table `update_info`
+--
+
+CREATE TABLE `update_info` (
+ `id` int(10) UNSIGNED NOT NULL,
+ `product` varchar(25) NOT NULL,
+ `edition` varchar(25) DEFAULT NULL,
+ `serial_number` varchar(45) DEFAULT NULL,
+ `start_version_major` int(10) UNSIGNED DEFAULT 0,
+ `start_version_minor` int(10) UNSIGNED DEFAULT 0,
+ `start_version_build` int(10) UNSIGNED DEFAULT 0,
+ `start_version_revision` int(10) UNSIGNED DEFAULT 0,
+ `new_version_major` int(10) UNSIGNED DEFAULT 0,
+ `new_version_minor` int(10) UNSIGNED DEFAULT 0,
+ `new_version_build` int(10) UNSIGNED DEFAULT 0,
+ `new_version_revision` int(10) UNSIGNED DEFAULT 0,
+ `link_install` varchar(256) NOT NULL,
+ `link_news` varchar(256) DEFAULT NULL,
+ `use_common` tinyint(1) DEFAULT 0,
+ `update_description` text DEFAULT NULL
+) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_general_ci;
+
+-- --------------------------------------------------------
+
+--
+-- Table structure for table `update_statistics`
+--
+
+CREATE TABLE `update_statistics` (
+ `id` int(10) UNSIGNED NOT NULL,
+ `product_id` int(10) UNSIGNED DEFAULT NULL,
+ `edition` varchar(25) DEFAULT NULL,
+ `serial_number` varchar(45) DEFAULT NULL,
+ `client_version` varchar(16) NOT NULL,
+ `new_version` varchar(16) DEFAULT NULL,
+ `date` datetime NOT NULL DEFAULT utc_timestamp(),
+ `client_ip` varchar(15) DEFAULT NULL,
+ `channel` enum('Current','Stable') NOT NULL DEFAULT 'Current',
+ `status` enum('NoUpdates','NeedUpdate','Expired','Recalled','LicenceNotFound') DEFAULT NULL
+) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_general_ci;
+
+-- --------------------------------------------------------
+
+--
+-- Table structure for table `users`
+--
+
+CREATE TABLE `users` (
+ `id` int(10) UNSIGNED NOT NULL,
+ `name` varchar(45) NOT NULL,
+ `login` varchar(45) NOT NULL,
+ `deactivated` tinyint(1) NOT NULL DEFAULT 0,
+ `email` varchar(60) DEFAULT NULL,
+ `description` text DEFAULT NULL,
+ `admin` tinyint(1) NOT NULL DEFAULT 0
+) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_general_ci;
+
+--
+-- Indexes for dumped tables
+--
+
+--
+-- Indexes for table `accounts`
+--
+ALTER TABLE `accounts`
+ ADD PRIMARY KEY (`id`),
+ ADD UNIQUE KEY `login_UNIQUE` (`login`),
+ ADD KEY `fk_accounts_1_idx` (`client_id`);
+
+--
+-- Indexes for table `api_tokens`
+--
+ALTER TABLE `api_tokens`
+ ADD PRIMARY KEY (`id`),
+ ADD UNIQUE KEY `token_UNIQUE` (`token`),
+ ADD KEY `fk_api_tokens_1_idx` (`base_id`);
+
+--
+-- Indexes for table `bases`
+--
+ALTER TABLE `bases`
+ ADD PRIMARY KEY (`id`),
+ ADD UNIQUE KEY `idx1_bases` (`base_name`,`account_id`),
+ ADD UNIQUE KEY `base_guid_UNIQUE` (`base_guid`),
+ ADD KEY `fk1_bases_idx` (`account_id`),
+ ADD KEY `fk2_bases_idx` (`server_id`),
+ ADD KEY `fk3_bases_idx` (`product_id`);
+
+--
+-- Indexes for table `bases_scripts`
+--
+ALTER TABLE `bases_scripts`
+ ADD PRIMARY KEY (`id`),
+ ADD KEY `fk1_bases_scripts_idx` (`product_id`);
+
+--
+-- Indexes for table `base_access`
+--
+ALTER TABLE `base_access`
+ ADD PRIMARY KEY (`id`),
+ ADD UNIQUE KEY `base_access_idx` (`user_id`,`base_id`),
+ ADD KEY `fk1_base_access_idx` (`user_id`),
+ ADD KEY `fk1_base_access_idx1` (`base_id`);
+
+--
+-- Indexes for table `base_parameters`
+--
+ALTER TABLE `base_parameters`
+ ADD PRIMARY KEY (`name`);
+
+--
+-- Indexes for table `bug_reports`
+--
+ALTER TABLE `bug_reports`
+ ADD PRIMARY KEY (`id`),
+ ADD KEY `fk_bug_reports_1_idx` (`product_id`),
+ ADD KEY `bug_reports_created` (`created`),
+ ADD KEY `bug_reports_last_update` (`last_update`),
+ ADD KEY `bug_reports_edition` (`edition`),
+ ADD KEY `bug_reports_version` (`version`),
+ ADD KEY `bug_reports_status` (`status`);
+
+--
+-- Indexes for table `bug_reports_messages`
+--
+ALTER TABLE `bug_reports_messages`
+ ADD PRIMARY KEY (`id`),
+ ADD KEY `fk_bug_reports_messages_1_idx` (`bug_reports_id`);
+
+--
+-- Indexes for table `clients`
+--
+ALTER TABLE `clients`
+ ADD PRIMARY KEY (`id`);
+
+--
+-- Indexes for table `cloud_users`
+--
+ALTER TABLE `cloud_users`
+ ADD PRIMARY KEY (`id`),
+ ADD UNIQUE KEY `idx1_users` (`login`,`account_id`),
+ ADD KEY `fk1_users_idx` (`account_id`),
+ ADD KEY `fk_cloud_users_1_idx` (`client_id`);
+
+--
+-- Indexes for table `products`
+--
+ALTER TABLE `products`
+ ADD PRIMARY KEY (`id`),
+ ADD UNIQUE KEY `internal_name_UNIQUE` (`internal_name`);
+
+--
+-- Indexes for table `product_editions`
+--
+ALTER TABLE `product_editions`
+ ADD PRIMARY KEY (`id`),
+ ADD KEY `fk_product_id_idx` (`product_id`);
+
+--
+-- Indexes for table `product_versions`
+--
+ALTER TABLE `product_versions`
+ ADD PRIMARY KEY (`id`),
+ ADD KEY `_idx` (`product_id`);
+
+--
+-- Indexes for table `serial_numbers`
+--
+ALTER TABLE `serial_numbers`
+ ADD PRIMARY KEY (`id`),
+ ADD UNIQUE KEY `number_UNIQUE` (`number`),
+ ADD KEY `fk_serial_numbers_1_idx` (`client_id`);
+
+--
+-- Indexes for table `servers`
+--
+ALTER TABLE `servers`
+ ADD PRIMARY KEY (`id`);
+
+--
+-- Indexes for table `sessions`
+--
+ALTER TABLE `sessions`
+ ADD PRIMARY KEY (`id`),
+ ADD UNIQUE KEY `session_id_UNIQUE` (`session_id`),
+ ADD KEY `fk1_sessions_idx` (`account_id`),
+ ADD KEY `fk2_sessions_idx` (`user_id`),
+ ADD KEY `fk3_sessions_idx` (`base_id`),
+ ADD KEY `end_time_idx` (`end_time`),
+ ADD KEY `is_closed_idx` (`is_closed`);
+
+--
+-- Indexes for table `telemetry_statistics`
+--
+ALTER TABLE `telemetry_statistics`
+ ADD PRIMARY KEY (`id`),
+ ADD KEY `index_telemetry_statistics_last_update` (`last_update`),
+ ADD KEY `index_telemetry_statistics_ip` (`ip_address`),
+ ADD KEY `index_telemetry_statistics_product` (`product`),
+ ADD KEY `index_telemetry_statistics_edition` (`edition`),
+ ADD KEY `index_telemetry_statistics_version` (`version`),
+ ADD KEY `inxex_telemetry_statistics_os` (`os`);
+
+--
+-- Indexes for table `update_info`
+--
+ALTER TABLE `update_info`
+ ADD PRIMARY KEY (`id`);
+
+--
+-- Indexes for table `update_statistics`
+--
+ALTER TABLE `update_statistics`
+ ADD PRIMARY KEY (`id`),
+ ADD UNIQUE KEY `id_UNIQUE` (`id`),
+ ADD KEY `fk_update_statistics_1_idx` (`product_id`),
+ ADD KEY `update_statistics_edition_idx` (`edition`),
+ ADD KEY `update_statistics_serial_idx` (`serial_number`),
+ ADD KEY `update_statistics_client_version_idx` (`client_version`),
+ ADD KEY `update_statistics_ip_idx` (`client_ip`),
+ ADD KEY `update_statistics_new_version_idx` (`new_version`),
+ ADD KEY `update_statistics_date_idx` (`date`),
+ ADD KEY `update_statistics_channel_idx` (`channel`);
+
+--
+-- Indexes for table `users`
+--
+ALTER TABLE `users`
+ ADD PRIMARY KEY (`id`);
+
+--
+-- AUTO_INCREMENT for dumped tables
+--
+
+--
+-- AUTO_INCREMENT for table `accounts`
+--
+ALTER TABLE `accounts`
+ MODIFY `id` int(10) UNSIGNED NOT NULL AUTO_INCREMENT;
+
+--
+-- AUTO_INCREMENT for table `api_tokens`
+--
+ALTER TABLE `api_tokens`
+ MODIFY `id` int(10) UNSIGNED NOT NULL AUTO_INCREMENT;
+
+--
+-- AUTO_INCREMENT for table `bases`
+--
+ALTER TABLE `bases`
+ MODIFY `id` int(10) UNSIGNED NOT NULL AUTO_INCREMENT;
+
+--
+-- AUTO_INCREMENT for table `bases_scripts`
+--
+ALTER TABLE `bases_scripts`
+ MODIFY `id` int(10) UNSIGNED NOT NULL AUTO_INCREMENT;
+
+--
+-- AUTO_INCREMENT for table `base_access`
+--
+ALTER TABLE `base_access`
+ MODIFY `id` int(10) UNSIGNED NOT NULL AUTO_INCREMENT;
+
+--
+-- AUTO_INCREMENT for table `bug_reports`
+--
+ALTER TABLE `bug_reports`
+ MODIFY `id` int(10) UNSIGNED NOT NULL AUTO_INCREMENT;
+
+--
+-- AUTO_INCREMENT for table `bug_reports_messages`
+--
+ALTER TABLE `bug_reports_messages`
+ MODIFY `id` int(10) UNSIGNED NOT NULL AUTO_INCREMENT;
+
+--
+-- AUTO_INCREMENT for table `clients`
+--
+ALTER TABLE `clients`
+ MODIFY `id` int(10) UNSIGNED NOT NULL AUTO_INCREMENT;
+
+--
+-- AUTO_INCREMENT for table `cloud_users`
+--
+ALTER TABLE `cloud_users`
+ MODIFY `id` int(10) UNSIGNED NOT NULL AUTO_INCREMENT;
+
+--
+-- AUTO_INCREMENT for table `products`
+--
+ALTER TABLE `products`
+ MODIFY `id` int(10) UNSIGNED NOT NULL AUTO_INCREMENT;
+
+--
+-- AUTO_INCREMENT for table `product_editions`
+--
+ALTER TABLE `product_editions`
+ MODIFY `id` int(10) UNSIGNED NOT NULL AUTO_INCREMENT;
+
+--
+-- AUTO_INCREMENT for table `product_versions`
+--
+ALTER TABLE `product_versions`
+ MODIFY `id` int(10) UNSIGNED NOT NULL AUTO_INCREMENT;
+
+--
+-- AUTO_INCREMENT for table `serial_numbers`
+--
+ALTER TABLE `serial_numbers`
+ MODIFY `id` int(10) UNSIGNED NOT NULL AUTO_INCREMENT;
+
+--
+-- AUTO_INCREMENT for table `servers`
+--
+ALTER TABLE `servers`
+ MODIFY `id` int(10) UNSIGNED NOT NULL AUTO_INCREMENT;
+
+--
+-- AUTO_INCREMENT for table `sessions`
+--
+ALTER TABLE `sessions`
+ MODIFY `id` int(10) UNSIGNED NOT NULL AUTO_INCREMENT;
+
+--
+-- AUTO_INCREMENT for table `telemetry_statistics`
+--
+ALTER TABLE `telemetry_statistics`
+ MODIFY `id` int(10) UNSIGNED NOT NULL AUTO_INCREMENT;
+
+--
+-- AUTO_INCREMENT for table `update_info`
+--
+ALTER TABLE `update_info`
+ MODIFY `id` int(10) UNSIGNED NOT NULL AUTO_INCREMENT;
+
+--
+-- AUTO_INCREMENT for table `update_statistics`
+--
+ALTER TABLE `update_statistics`
+ MODIFY `id` int(10) UNSIGNED NOT NULL AUTO_INCREMENT;
+
+--
+-- AUTO_INCREMENT for table `users`
+--
+ALTER TABLE `users`
+ MODIFY `id` int(10) UNSIGNED NOT NULL AUTO_INCREMENT;
+
+--
+-- Constraints for dumped tables
+--
+
+--
+-- Constraints for table `accounts`
+--
+ALTER TABLE `accounts`
+ ADD CONSTRAINT `fk_accounts_1` FOREIGN KEY (`client_id`) REFERENCES `clients` (`id`) ON DELETE SET NULL ON UPDATE CASCADE;
+
+--
+-- Constraints for table `api_tokens`
+--
+ALTER TABLE `api_tokens`
+ ADD CONSTRAINT `fk_api_tokens_1` FOREIGN KEY (`base_id`) REFERENCES `bases` (`id`) ON DELETE NO ACTION ON UPDATE NO ACTION;
+
+--
+-- Constraints for table `bases`
+--
+ALTER TABLE `bases`
+ ADD CONSTRAINT `fk1_bases` FOREIGN KEY (`account_id`) REFERENCES `accounts` (`id`) ON DELETE NO ACTION ON UPDATE NO ACTION,
+ ADD CONSTRAINT `fk2_bases` FOREIGN KEY (`server_id`) REFERENCES `servers` (`id`) ON DELETE NO ACTION ON UPDATE NO ACTION,
+ ADD CONSTRAINT `fk3_bases` FOREIGN KEY (`product_id`) REFERENCES `products` (`id`) ON DELETE NO ACTION ON UPDATE NO ACTION;
+
+--
+-- Constraints for table `bases_scripts`
+--
+ALTER TABLE `bases_scripts`
+ ADD CONSTRAINT `fk1_bases_scripts` FOREIGN KEY (`product_id`) REFERENCES `products` (`id`) ON DELETE NO ACTION ON UPDATE NO ACTION;
+
+--
+-- Constraints for table `base_access`
+--
+ALTER TABLE `base_access`
+ ADD CONSTRAINT `fk1_base_access` FOREIGN KEY (`user_id`) REFERENCES `cloud_users` (`id`) ON DELETE NO ACTION ON UPDATE NO ACTION,
+ ADD CONSTRAINT `fk2_base_access` FOREIGN KEY (`base_id`) REFERENCES `bases` (`id`) ON DELETE NO ACTION ON UPDATE NO ACTION;
+
+--
+-- Constraints for table `bug_reports`
+--
+ALTER TABLE `bug_reports`
+ ADD CONSTRAINT `fk_bug_reports_1` FOREIGN KEY (`product_id`) REFERENCES `products` (`id`) ON DELETE CASCADE ON UPDATE NO ACTION;
+
+--
+-- Constraints for table `bug_reports_messages`
+--
+ALTER TABLE `bug_reports_messages`
+ ADD CONSTRAINT `fk_bug_reports_messages_1` FOREIGN KEY (`bug_reports_id`) REFERENCES `bug_reports` (`id`) ON DELETE CASCADE ON UPDATE CASCADE;
+
+--
+-- Constraints for table `cloud_users`
+--
+ALTER TABLE `cloud_users`
+ ADD CONSTRAINT `fk1_users` FOREIGN KEY (`account_id`) REFERENCES `accounts` (`id`) ON DELETE CASCADE ON UPDATE NO ACTION,
+ ADD CONSTRAINT `fk_cloud_users_1` FOREIGN KEY (`client_id`) REFERENCES `clients` (`id`) ON DELETE NO ACTION ON UPDATE NO ACTION;
+
+--
+-- Constraints for table `product_editions`
+--
+ALTER TABLE `product_editions`
+ ADD CONSTRAINT `fk_product_id` FOREIGN KEY (`product_id`) REFERENCES `products` (`id`) ON DELETE NO ACTION ON UPDATE NO ACTION;
+
+--
+-- Constraints for table `product_versions`
+--
+ALTER TABLE `product_versions`
+ ADD CONSTRAINT `fk_product_versions_1` FOREIGN KEY (`product_id`) REFERENCES `products` (`id`) ON DELETE CASCADE ON UPDATE CASCADE;
+
+--
+-- Constraints for table `serial_numbers`
+--
+ALTER TABLE `serial_numbers`
+ ADD CONSTRAINT `fk_serial_numbers_1` FOREIGN KEY (`client_id`) REFERENCES `clients` (`id`) ON DELETE CASCADE ON UPDATE CASCADE;
+
+--
+-- Constraints for table `sessions`
+--
+ALTER TABLE `sessions`
+ ADD CONSTRAINT `fk1_sessions` FOREIGN KEY (`account_id`) REFERENCES `accounts` (`id`) ON DELETE NO ACTION ON UPDATE NO ACTION,
+ ADD CONSTRAINT `fk2_sessions` FOREIGN KEY (`user_id`) REFERENCES `cloud_users` (`id`) ON DELETE NO ACTION ON UPDATE NO ACTION,
+ ADD CONSTRAINT `fk3_sessions` FOREIGN KEY (`base_id`) REFERENCES `bases` (`id`) ON DELETE NO ACTION ON UPDATE NO ACTION;
+
+--
+-- Constraints for table `update_statistics`
+--
+ALTER TABLE `update_statistics`
+ ADD CONSTRAINT `fk_update_statistics_1` FOREIGN KEY (`product_id`) REFERENCES `products` (`id`) ON DELETE NO ACTION ON UPDATE NO ACTION;
+COMMIT;
+
+/*!40101 SET CHARACTER_SET_CLIENT=@OLD_CHARACTER_SET_CLIENT */;
+/*!40101 SET CHARACTER_SET_RESULTS=@OLD_CHARACTER_SET_RESULTS */;
+/*!40101 SET COLLATION_CONNECTION=@OLD_COLLATION_CONNECTION */;
diff --git a/QS.Launcher.Avalonia/DependencyInjection.cs b/QS.Launcher.Avalonia/DependencyInjection.cs
index 652f84797..b27725ca7 100644
--- a/QS.Launcher.Avalonia/DependencyInjection.cs
+++ b/QS.Launcher.Avalonia/DependencyInjection.cs
@@ -1,12 +1,18 @@
+using Avalonia.Controls;
using Microsoft.Extensions.DependencyInjection;
using QS.Launcher.Services;
using QS.Launcher.Views;
+using QS.Launcher.Views.Pages;
namespace QS.Launcher;
public static partial class DependencyInjection {
public static IServiceCollection AddPages(this IServiceCollection services) {
return services
.AddSingleton()
+ .AddSingleton()
+ .AddSingleton()
+ .AddSingleton()
+ .AddSingleton()
.AddSingleton();
}
}
diff --git a/QS.Launcher.Avalonia/LauncherApp.axaml b/QS.Launcher.Avalonia/LauncherApp.axaml
index c02018fd8..f1502c652 100644
--- a/QS.Launcher.Avalonia/LauncherApp.axaml
+++ b/QS.Launcher.Avalonia/LauncherApp.axaml
@@ -15,27 +15,6 @@
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
diff --git a/QS.Launcher.Avalonia/Views/MainWindow.axaml.cs b/QS.Launcher.Avalonia/Views/MainWindow.axaml.cs
index bb6747c0d..d377b5ad7 100644
--- a/QS.Launcher.Avalonia/Views/MainWindow.axaml.cs
+++ b/QS.Launcher.Avalonia/Views/MainWindow.axaml.cs
@@ -1,11 +1,12 @@
using Avalonia.Controls;
using Avalonia.Media.Imaging;
using QS.Launcher.ViewModels;
+using System.Collections.Generic;
namespace QS.Launcher.Views;
public partial class MainWindow : Window {
- public MainWindow(MainWindowVM vm, LauncherOptions options) {
+ public MainWindow(MainWindowVM vm, IEnumerable pages, LauncherOptions options) {
InitializeComponent();
Icon = new WindowIcon(new Bitmap(new System.IO.MemoryStream(options.LogoIcon)));
@@ -13,6 +14,10 @@ public MainWindow(MainWindowVM vm, LauncherOptions options) {
Closing += (_, _) => vm.SaveConnections();
+ foreach(var page in pages)
+ carousel.Items.Add(page);
+ vm.PagesCount = carousel.ItemCount;
+
DataContext = vm;
}
}
diff --git a/QS.Launcher.Avalonia/Views/Pages/BaseManagementView.axaml.cs b/QS.Launcher.Avalonia/Views/Pages/BaseManagementView.axaml.cs
index d96f22ca3..b0bbc5238 100644
--- a/QS.Launcher.Avalonia/Views/Pages/BaseManagementView.axaml.cs
+++ b/QS.Launcher.Avalonia/Views/Pages/BaseManagementView.axaml.cs
@@ -1,9 +1,12 @@
using Avalonia.Controls;
+using QS.Launcher.ViewModels.PageViewModels;
namespace QS.Launcher.Views.Pages;
public partial class BaseManagementView : UserControl {
- public BaseManagementView() {
+ public BaseManagementView(BaseManagementVM viewModel) {
InitializeComponent();
+
+ DataContext = viewModel;
}
}
diff --git a/QS.Launcher.Avalonia/Views/Pages/DataBase/DataBasesView.axaml.cs b/QS.Launcher.Avalonia/Views/Pages/DataBase/DataBasesView.axaml.cs
index 9d35405e8..157d54db3 100644
--- a/QS.Launcher.Avalonia/Views/Pages/DataBase/DataBasesView.axaml.cs
+++ b/QS.Launcher.Avalonia/Views/Pages/DataBase/DataBasesView.axaml.cs
@@ -10,13 +10,12 @@ namespace QS.Launcher.Views.Pages;
public partial class DataBasesView : UserControl {
private static NLog.Logger logger = NLog.LogManager.GetCurrentClassLogger();
- public DataBasesView() {
+ public DataBasesView(DataBasesVM viewModel) {
InitializeComponent();
- DataContextChanged += (_, _) => {
- if(DataContext is DataBasesVM vm)
- vm.StartLaunchProgram += HandleStartMainProgram;
- };
+ DataContext = viewModel;
+
+ viewModel.StartLaunchProgram += HandleStartMainProgram;
KeyDown += (s, e) => {
if(e.Key == Key.Enter) {
@@ -40,10 +39,12 @@ public async void HandleStartMainProgram(bool shouldCloseLauncher) {
if(shouldCloseLauncher) {
logger.Info($">>> HandleStartMainProgram: Вызываем Shutdown!");
+ // NewProcessRunner: закрываем всё приложение лаунчера (Shutdown)
(LauncherApp.Current!.ApplicationLifetime as ClassicDesktopStyleApplicationLifetime)?.Shutdown();
}
else {
logger.Info($">>> HandleStartMainProgram: Закрываем только окно");
+ // InProcessRunner: закрываем только окно лаунчера
var window = TopLevel.GetTopLevel(this) as Window;
window?.Close();
}
diff --git a/QS.Launcher.Avalonia/Views/Pages/LoginView.axaml.cs b/QS.Launcher.Avalonia/Views/Pages/LoginView.axaml.cs
index 00ede44dc..69dc133f4 100644
--- a/QS.Launcher.Avalonia/Views/Pages/LoginView.axaml.cs
+++ b/QS.Launcher.Avalonia/Views/Pages/LoginView.axaml.cs
@@ -11,7 +11,7 @@ public partial class LoginView : UserControl
{
private readonly Style upStyle;
- public LoginView()
+ public LoginView(LoginVM viewModel)
{
upStyle = new Style(x => x.OfType().Class("up")) {
Setters =
@@ -26,6 +26,8 @@ public LoginView()
loginContainer.Styles.Add(upStyle);
+ DataContext = viewModel;
+
Loaded += (s, e) => {
passwordTextBox.Focus();
};
diff --git a/QS.Launcher.Avalonia/Views/Pages/UserManagementView.axaml.cs b/QS.Launcher.Avalonia/Views/Pages/UserManagementView.axaml.cs
index cd038c5c8..5a4b62bc7 100644
--- a/QS.Launcher.Avalonia/Views/Pages/UserManagementView.axaml.cs
+++ b/QS.Launcher.Avalonia/Views/Pages/UserManagementView.axaml.cs
@@ -1,9 +1,12 @@
using Avalonia.Controls;
+using QS.Launcher.ViewModels.PageViewModels;
namespace QS.Launcher.Views.Pages;
public partial class UserManagementView : UserControl {
- public UserManagementView() {
+ public UserManagementView(UserManagementVM viewModel) {
InitializeComponent();
+
+ DataContext = viewModel;
}
}
diff --git a/QS.Launcher/ViewModels/PageViewModels/BaseManagementVM.cs b/QS.Launcher/ViewModels/PageViewModels/BaseManagementVM.cs
index 7844609c2..d0d0de5ae 100644
--- a/QS.Launcher/ViewModels/PageViewModels/BaseManagementVM.cs
+++ b/QS.Launcher/ViewModels/PageViewModels/BaseManagementVM.cs
@@ -9,7 +9,6 @@ public class BaseManagementVM : CarouselPageVM {
public DatabaseViewModel SelectedDatabase { get; set; }
public BaseManagementVM() {
-
}
}
}
diff --git a/QS.Project.Core/DBScripts/Controllers/IDbScriptsConfiguration.cs b/QS.Project.Core/DBScripts/Controllers/IDbScriptsConfiguration.cs
deleted file mode 100644
index 9732a81d4..000000000
--- a/QS.Project.Core/DBScripts/Controllers/IDbScriptsConfiguration.cs
+++ /dev/null
@@ -1,14 +0,0 @@
-using System;
-
-namespace QS.DBScripts.Controllers
-{
- ///
- /// Источник SQL-скриптов конкретного приложения, передаваемый в реализации IDBCreator
- ///
- public interface IDbScriptsConfiguration
- {
- Version CreationVersion { get; }
-
- string GetCreationSqlScript();
- }
-}
diff --git a/QS.Updater.Core/DBScripts/IDbScriptsConfiguration.cs b/QS.Updater.Core/DBScripts/IDbScriptsConfiguration.cs
new file mode 100644
index 000000000..a3bdba61a
--- /dev/null
+++ b/QS.Updater.Core/DBScripts/IDbScriptsConfiguration.cs
@@ -0,0 +1,12 @@
+using QS.DBScripts.Models;
+using QS.Updater.DB;
+
+namespace QS.DBScripts
+{
+ public interface IDbScriptsConfiguration
+ {
+ CreationScript MakeCreationScript();
+
+ UpdateConfiguration MakeUpdateConfiguration();
+ }
+}
diff --git a/QS.Updater.Core/DBScripts/Models/CreationScript.cs b/QS.Updater.Core/DBScripts/Models/CreationScript.cs
index 67e9bb0b2..e14c5e6d8 100644
--- a/QS.Updater.Core/DBScripts/Models/CreationScript.cs
+++ b/QS.Updater.Core/DBScripts/Models/CreationScript.cs
@@ -52,4 +52,4 @@ public string GetSqlScript()
"Для получения скрипта sql должен быть указано либо имя файла либо название ресурса");
}
}
-}
\ No newline at end of file
+}
diff --git a/QS.Updater.Core/DBScripts/Models/MySqlDbCreateModel.cs b/QS.Updater.Core/DBScripts/Models/MySqlDbCreateModel.cs
index f9d5fb545..e15822e62 100644
--- a/QS.Updater.Core/DBScripts/Models/MySqlDbCreateModel.cs
+++ b/QS.Updater.Core/DBScripts/Models/MySqlDbCreateModel.cs
@@ -1,10 +1,11 @@
+using MySqlConnector;
+using QS.DBScripts.Controllers;
+using QS.Dialog;
using System;
using System.Text.RegularExpressions;
using System.Threading;
using System.Threading.Tasks;
-using MySqlConnector;
-using QS.DBScripts.Controllers;
-using QS.Dialog;
+using System.Xml.Linq;
namespace QS.DBScripts.Models
{
@@ -13,7 +14,7 @@ public class MySqlDbCreateModel : IDBCreator
static NLog.Logger logger = NLog.LogManager.GetCurrentClassLogger();
private readonly string connectionString;
- private readonly IDbScriptsConfiguration scripts;
+ private readonly CreationScript scripts;
private readonly IProgressBarDisplayable progress;
private readonly IDbCreatorInteraction interaction;
private readonly CancellationToken cancellationToken;
@@ -30,7 +31,7 @@ public MySqlDbCreateModel(
if(string.IsNullOrWhiteSpace(connectionString))
throw new ArgumentException("Connection string is required", nameof(connectionString));
this.connectionString = connectionString;
- this.scripts = scripts ?? throw new ArgumentNullException(nameof(scripts));
+ this.scripts = scripts.MakeCreationScript() ?? throw new ArgumentNullException(nameof(scripts));
this.progress = progress ?? throw new ArgumentNullException(nameof(progress));
this.interaction = interaction ?? throw new ArgumentNullException(nameof(interaction));
this.cancellationToken = cancellationToken;
@@ -47,50 +48,46 @@ public bool RunCreation(string dbName, string dbTitle) {
try {
logger.Info("Connecting to MySQL...");
connectionDB.Open();
- cancellationToken.ThrowIfCancellationRequested();
logger.Info("Проверяем существует ли уже база.");
- var cmd = new MySqlCommand("SHOW DATABASES;", connectionDB);
+
+ var sql = "SHOW DATABASES;";
+ var cmd = new MySqlCommand(sql, connectionDB);
bool needDropBase = false;
- using (var rdr = cmd.ExecuteReader())
- {
- while (rdr.Read())
- {
- if (rdr[0].ToString() == dbName)
- {
- if (interaction.AskDropExistingDatabaseAsync(dbName).GetAwaiter().GetResult())
- {
+ using(var rdr = cmd.ExecuteReader()) {
+ while(rdr.Read()) {
+ if(rdr[0].ToString() == dbName) {
+ if(interaction.AskDropExistingDatabaseAsync(dbName).GetAwaiter().GetResult()) {
needDropBase = true;
break;
}
- return false;
+ else
+ return false;
}
}
}
- cancellationToken.ThrowIfCancellationRequested();
logger.Info("Создаем новую базу.");
+
progress.Start(text: "Получаем скрипт создания базы");
- string sqlScript = scripts.GetCreationSqlScript();
+ string sqlScript = scripts.GetSqlScript();
int predictedCount = Regex.Matches(sqlScript, ";").Count;
logger.Debug("Предполагаем наличие {0} команд в скрипте.", predictedCount);
progress.Start(maxValue: predictedCount + (needDropBase ? 2 : 1));
- if (needDropBase)
- {
+ if(needDropBase) {
logger.Info("Удаляем существующую базу {0}.", dbName);
progress.Add(text: $"Удаляем существующую базу {dbName}");
- cmd.CommandText = $"DROP DATABASE `{dbName}`";
+ cmd.CommandText = String.Format("DROP DATABASE `{0}`", dbName);
cmd.ExecuteNonQuery();
}
- cancellationToken.ThrowIfCancellationRequested();
progress.Add(text: $"Создаем базу {dbName}");
- cmd.CommandText = $"CREATE SCHEMA `{dbName}` DEFAULT CHARACTER SET utf8mb4 ;";
+ cmd.CommandText = String.Format("CREATE SCHEMA `{0}` DEFAULT CHARACTER SET utf8mb4 ;", dbName);
cmd.ExecuteNonQuery();
- cmd.CommandText = $"USE `{dbName}` ;";
+ cmd.CommandText = String.Format("USE `{0}` ;", dbName);
cmd.ExecuteNonQuery();
progress.Add(text: $"Создаем таблицы в {dbName}");
@@ -99,27 +96,6 @@ public bool RunCreation(string dbName, string dbTitle) {
myscript.StatementExecuted += Myscript_StatementExecuted;
var commands = myscript.Execute();
logger.Debug("Выполнено {0} SQL-команд.", commands);
- cancellationToken.ThrowIfCancellationRequested();
-
- // Записываем человекочитаемое название базы, используется для отображения в списке БД
- logger.Info("Записываем Title='{0}' в base_parameters.", dbTitle);
- cmd.CommandText =
- "INSERT INTO base_parameters (name, str_value) VALUES ('Title', @title) "
- + "ON DUPLICATE KEY UPDATE str_value = @title";
- cmd.Parameters.Clear();
- cmd.Parameters.AddWithValue("@title", dbTitle ?? string.Empty);
- cmd.ExecuteNonQuery();
-
- // Версия пустой базы для апдейтера.
- if(scripts.CreationVersion != null) {
- logger.Info("Записываем version='{0}' в base_parameters.", scripts.CreationVersion);
- cmd.CommandText =
- "INSERT INTO base_parameters (name, str_value) VALUES ('version', @ver) "
- + "ON DUPLICATE KEY UPDATE str_value = @ver";
- cmd.Parameters.Clear();
- cmd.Parameters.AddWithValue("@ver", scripts.CreationVersion.ToString());
- cmd.ExecuteNonQuery();
- }
if(FillBaseGuid) {
logger.Info("Генерируем BaseGuid");
@@ -130,6 +106,7 @@ public bool RunCreation(string dbName, string dbTitle) {
cmd.ExecuteNonQuery();
logger.Info("BaseGuid успешно записан.");
}
+
}
catch(OperationCanceledException) {
logger.Info("Создание базы отменено пользователем.");
From 0e8244296785c503051800b35a3b65aa5b5eb051 Mon Sep 17 00:00:00 2001
From: J0shlerB0y <86416124+J0shlerB0y@users.noreply.github.com>
Date: Sat, 2 May 2026 13:43:48 +0300
Subject: [PATCH 05/19] =?UTF-8?q?=D0=BF=D0=BE=D0=BC=D0=B5=D0=BD=D1=8F?=
=?UTF-8?q?=D0=BB=20=D0=BF=D0=BE=D0=BB=D1=83=D1=87=D0=B5=D0=BD=D0=B8=D1=8F?=
=?UTF-8?q?=20=D0=BA=D1=80=D0=B5=D0=B9=D1=82=D0=BE=D1=80=D0=B0=20=D0=B1?=
=?UTF-8?q?=D0=B0=D0=B7=D1=8B=20=D0=B2=20=D1=82=D0=B8=D0=BF=D0=B5=20=D0=BF?=
=?UTF-8?q?=D0=BE=D0=B4=D0=BA=D0=BB=D1=8E=D1=87=D0=B5=D0=BD=D0=B8=D1=8F?=
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
---
.../DataBase/QsCloudConnectionTypeBase.cs | 12 ++++++++++--
QS.DbManagement/ConnectionTypeBase.cs | 19 ++++++-------------
.../MariaDb/MariaDbConnectionTypeBase.cs | 17 ++++++++++++++++-
.../Dialog/IProgressBarDisplayable.cs | 5 +----
4 files changed, 33 insertions(+), 20 deletions(-)
diff --git a/QS.Cloud.Client/DataBase/QsCloudConnectionTypeBase.cs b/QS.Cloud.Client/DataBase/QsCloudConnectionTypeBase.cs
index 31a13de3e..de2031830 100644
--- a/QS.Cloud.Client/DataBase/QsCloudConnectionTypeBase.cs
+++ b/QS.Cloud.Client/DataBase/QsCloudConnectionTypeBase.cs
@@ -3,16 +3,17 @@
using System.Linq;
using System.Reflection;
using QS.Utilities.Extensions;
+using QS.DBScripts.Controllers;
namespace QS.Cloud.Client.DataBase {
public class QsCloudConnectionTypeBase : ConnectionTypeBase {
public QsCloudConnectionTypeBase() {
Title = "QS: Облако";
ConnectionTypeName = "QSCloud";
-
+
Parameters.Add(new ConnectionParameter("Account","Организация"));
Parameters.Add(new ConnectionParameter("Login","Пользователь"));
-
+
IconBytes = Assembly.GetExecutingAssembly().GetResourceByteArray("QS.Cloud.Client.Icons.qscloud.ico");
}
@@ -23,5 +24,12 @@ public override bool CanConnect(IEnumerable parameters
public override IDbProvider CreateProvider(IList parameters, string password = null)
=> new QSCloudProvider(parameters, password);
+
+ public override IDBCreator CreatorFactory(CreatorFactoryArgs args)
+ => new QsCloudDbCreator(
+ args.Provider,
+ args.Progress,
+ args.Interaction,
+ args.CancellationToken);
}
}
diff --git a/QS.DbManagement/ConnectionTypeBase.cs b/QS.DbManagement/ConnectionTypeBase.cs
index e8b3f5905..995c0d9af 100644
--- a/QS.DbManagement/ConnectionTypeBase.cs
+++ b/QS.DbManagement/ConnectionTypeBase.cs
@@ -17,24 +17,17 @@ public abstract class ConnectionTypeBase {
public abstract IDbProvider CreateProvider(IList parameters, string password = null);
- ///
- /// Заполняется композиционным корнем приложения
- /// который один знает обо всех конкретных реализациях creator-ов и
- /// о том, как из IDbProvider достать строку подключения
- ///
- /// interaction — канал диалогов с пользователем
- /// serviceProvider — для резолва дополнительных зависимостей
- ///
- public Func CreatorFactory { get; set; }
+ public abstract IDBCreator CreatorFactory(CreatorFactoryArgs args);
public IDBCreator CreateCreator(CreatorFactoryArgs args) {
- if(CreatorFactory == null)
- throw new InvalidOperationException(
- $"Для типа подключения '{ConnectionTypeName}' не задана CreatorFactory. "
- + "Зарегистрируйте её в композиционном корне приложения.");
return CreatorFactory(args);
}
}
+
+ ///
+ /// interaction — канал диалогов с пользователем
+ /// serviceProvider — для резолва дополнительных зависимостей
+ ///
public class CreatorFactoryArgs {
public IDbProvider Provider { get; set; }
public IProgressBarDisplayable Progress { get; set; }
diff --git a/QS.DbManagement/MariaDb/MariaDbConnectionTypeBase.cs b/QS.DbManagement/MariaDb/MariaDbConnectionTypeBase.cs
index 960a29b4e..771eac670 100644
--- a/QS.DbManagement/MariaDb/MariaDbConnectionTypeBase.cs
+++ b/QS.DbManagement/MariaDb/MariaDbConnectionTypeBase.cs
@@ -1,7 +1,11 @@
+using Microsoft.Extensions.DependencyInjection;
+using QS.DBScripts;
+using QS.DBScripts.Controllers;
+using QS.DBScripts.Models;
+using QS.Utilities.Extensions;
using System.Collections.Generic;
using System.Linq;
using System.Reflection;
-using QS.Utilities.Extensions;
namespace QS.DbManagement
{
@@ -20,6 +24,17 @@ public override bool CanConnect(IEnumerable parameters
parameters.Any(p => p.Name == "Login" && !string.IsNullOrEmpty(p.Value));
}
+ public override IDBCreator CreatorFactory(CreatorFactoryArgs args){
+ var provider = (MariaDBProvider)args.Provider;
+ var scripts = args.ServiceProvider.GetRequiredService();
+ return new MySqlDbCreateModel(
+ provider.ConnectionStringBuilder.ConnectionString,
+ scripts,
+ args.Progress,
+ args.Interaction,
+ args.CancellationToken);
+ }
+
public override IDbProvider CreateProvider(IList parameters, string password = null)
=> new MariaDBProvider(parameters, password);
}
diff --git a/QS.Project.Core/Dialog/IProgressBarDisplayable.cs b/QS.Project.Core/Dialog/IProgressBarDisplayable.cs
index 43bab345e..8ed6f1770 100644
--- a/QS.Project.Core/Dialog/IProgressBarDisplayable.cs
+++ b/QS.Project.Core/Dialog/IProgressBarDisplayable.cs
@@ -1,9 +1,6 @@
-using System;
+using System;
namespace QS.Dialog
{
- ///
- /// Интерфейс позволяющий управлять прогресс баром не зависимо от графического тул кита.
- ///
public interface IProgressBarDisplayable
{
void Start(double maxValue = 1, double minValue = 0, string text = null, double startValue = 0);
From ab2ff0dfab3482aa917af2e8b6d73735a1bf029e Mon Sep 17 00:00:00 2001
From: J0shlerB0y <86416124+J0shlerB0y@users.noreply.github.com>
Date: Sat, 2 May 2026 15:55:30 +0300
Subject: [PATCH 06/19] =?UTF-8?q?=D0=BF=D0=BE=D1=87=D0=B8=D0=BD=D0=B8?=
=?UTF-8?q?=D0=BB=20=D0=BF=D1=80=D0=BE=D0=B1=D0=BB=D0=B5=D0=BC=D1=83=20?=
=?UTF-8?q?=D0=BF=D0=B5=D1=80=D0=B5=D0=B4=D0=B0=D1=87=D0=B8=20=D0=B7=D0=B0?=
=?UTF-8?q?=D0=B2=D0=B8=D1=81=D0=B8=D0=BC=D0=BE=D1=81=D1=82=D0=B5=D0=B9?=
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
---
QS.DbManagement/QS.DbManagement.csproj | 1 +
QS.Launcher.Avalonia/DependencyInjection.cs | 3 +++
.../CreateDataBaseProgressView.axaml.cs | 4 ++-
.../CreateDataBaseSettingsView.axaml.cs | 5 +++-
QS.Launcher/DependencyInjection.cs | 7 +++--
.../DataBase/CreateDataBaseProgressVM.cs | 26 ++++++++++---------
.../DataBase/CreateDataBaseSettingsVM.cs | 17 +++++++-----
.../PageViewModels/DataBase/DataBasesVM.cs | 4 +--
8 files changed, 41 insertions(+), 26 deletions(-)
diff --git a/QS.DbManagement/QS.DbManagement.csproj b/QS.DbManagement/QS.DbManagement.csproj
index 178ef368e..f6c7779e5 100644
--- a/QS.DbManagement/QS.DbManagement.csproj
+++ b/QS.DbManagement/QS.DbManagement.csproj
@@ -27,6 +27,7 @@
+
diff --git a/QS.Launcher.Avalonia/DependencyInjection.cs b/QS.Launcher.Avalonia/DependencyInjection.cs
index b27725ca7..068c14555 100644
--- a/QS.Launcher.Avalonia/DependencyInjection.cs
+++ b/QS.Launcher.Avalonia/DependencyInjection.cs
@@ -3,6 +3,7 @@
using QS.Launcher.Services;
using QS.Launcher.Views;
using QS.Launcher.Views.Pages;
+using QS.Launcher.Views.Pages.DataBase;
namespace QS.Launcher;
public static partial class DependencyInjection {
@@ -13,6 +14,8 @@ public static IServiceCollection AddPages(this IServiceCollection services) {
.AddSingleton()
.AddSingleton()
.AddSingleton()
+ .AddTransient()
+ .AddTransient()
.AddSingleton();
}
}
diff --git a/QS.Launcher.Avalonia/Views/Pages/DataBase/CreateDataBaseProgressView.axaml.cs b/QS.Launcher.Avalonia/Views/Pages/DataBase/CreateDataBaseProgressView.axaml.cs
index ec4b43f39..e2ffde338 100644
--- a/QS.Launcher.Avalonia/Views/Pages/DataBase/CreateDataBaseProgressView.axaml.cs
+++ b/QS.Launcher.Avalonia/Views/Pages/DataBase/CreateDataBaseProgressView.axaml.cs
@@ -6,8 +6,10 @@
namespace QS.Launcher.Views.Pages.DataBase;
public partial class CreateDataBaseProgressView : UserControl {
- public CreateDataBaseProgressView() {
+ public CreateDataBaseProgressView(CreateDataBaseProgressVM progressVM) {
InitializeComponent();
+
+ DataContext = progressVM;
}
private void OnLoaded(object? sender, RoutedEventArgs e) {
diff --git a/QS.Launcher.Avalonia/Views/Pages/DataBase/CreateDataBaseSettingsView.axaml.cs b/QS.Launcher.Avalonia/Views/Pages/DataBase/CreateDataBaseSettingsView.axaml.cs
index 35dab5b31..fc36e41c4 100644
--- a/QS.Launcher.Avalonia/Views/Pages/DataBase/CreateDataBaseSettingsView.axaml.cs
+++ b/QS.Launcher.Avalonia/Views/Pages/DataBase/CreateDataBaseSettingsView.axaml.cs
@@ -1,9 +1,12 @@
using Avalonia.Controls;
+using QS.Launcher.ViewModels.PageViewModels.DataBase;
namespace QS.Launcher.Views.Pages.DataBase;
public partial class CreateDataBaseSettingsView : UserControl {
- public CreateDataBaseSettingsView() {
+ public CreateDataBaseSettingsView(CreateDataBaseSettingsVM settingsVM) {
InitializeComponent();
+
+ DataContext = settingsVM;
}
}
diff --git a/QS.Launcher/DependencyInjection.cs b/QS.Launcher/DependencyInjection.cs
index 8dcb66f2f..af7b67634 100644
--- a/QS.Launcher/DependencyInjection.cs
+++ b/QS.Launcher/DependencyInjection.cs
@@ -17,10 +17,9 @@ public static IServiceCollection AddLauncherViewModels(this IServiceCollection s
.AddSingleton()
.AddSingleton()
.AddSingleton()
- // Wizard-страницы создания БД — Transient: новый экземпляр на каждое открытие.
- .AddTransient()
- .AddTransient()
- // Сервисы лаунчера, нужные wizard-страницам.
+ // Wizard-страницы создания БД
+ .AddSingleton()
+ .AddSingleton()
.AddSingleton();
}
diff --git a/QS.Launcher/ViewModels/PageViewModels/DataBase/CreateDataBaseProgressVM.cs b/QS.Launcher/ViewModels/PageViewModels/DataBase/CreateDataBaseProgressVM.cs
index 77b077e07..697b7449b 100644
--- a/QS.Launcher/ViewModels/PageViewModels/DataBase/CreateDataBaseProgressVM.cs
+++ b/QS.Launcher/ViewModels/PageViewModels/DataBase/CreateDataBaseProgressVM.cs
@@ -16,10 +16,10 @@ namespace QS.Launcher.ViewModels.PageViewModels.DataBase {
public class CreateDataBaseProgressVM : CarouselPageVM, IProgressBarDisplayable {
private static NLog.Logger logger = NLog.LogManager.GetCurrentClassLogger();
- public IDbProvider Provider { get; }
- public Connection Connection { get; }
- public string DbName { get; }
- public string DbTitle { get; }
+ public IDbProvider Provider { get; private set; }
+ public Connection Connection { get; private set; }
+ public string DbName { get; private set; }
+ public string DbTitle { get; private set; }
private readonly IDbCreatorInteraction interaction;
private readonly IServiceProvider services;
@@ -67,18 +67,10 @@ public bool IsStarted {
public ReactiveCommand CancelCommand { get; }
public CreateDataBaseProgressVM(
- IDbProvider provider,
- Connection connection,
- string dbName,
- string dbTitle,
IDbCreatorInteraction interaction,
IUiThreadInvoker uiThread,
IServiceProvider services)
{
- Provider = provider ?? throw new ArgumentNullException(nameof(provider));
- Connection = connection ?? throw new ArgumentNullException(nameof(connection));
- DbName = dbName ?? throw new ArgumentNullException(nameof(dbName));
- DbTitle = dbTitle ?? throw new ArgumentNullException(nameof(dbTitle));
this.interaction = interaction ?? throw new ArgumentNullException(nameof(interaction));
this.uiThread = uiThread ?? throw new ArgumentNullException(nameof(uiThread));
this.services = services ?? throw new ArgumentNullException(nameof(services));
@@ -91,6 +83,16 @@ public CreateDataBaseProgressVM(
});
}
+ public void SetDbSettings(
+ string dbName,
+ string dbTitle,
+ IDbProvider provider, Connection connection) {
+ DbName = dbName ?? throw new ArgumentNullException(nameof(dbName));
+ DbTitle = dbTitle ?? throw new ArgumentNullException(nameof(dbTitle));
+ Provider = provider ?? throw new ArgumentNullException(nameof(provider));
+ Connection = connection ?? throw new ArgumentNullException(nameof(connection));
+ }
+
public async Task StartCreationAsync() {
try {
var args = new CreatorFactoryArgs {
diff --git a/QS.Launcher/ViewModels/PageViewModels/DataBase/CreateDataBaseSettingsVM.cs b/QS.Launcher/ViewModels/PageViewModels/DataBase/CreateDataBaseSettingsVM.cs
index ea1d5811a..efdd40e67 100644
--- a/QS.Launcher/ViewModels/PageViewModels/DataBase/CreateDataBaseSettingsVM.cs
+++ b/QS.Launcher/ViewModels/PageViewModels/DataBase/CreateDataBaseSettingsVM.cs
@@ -9,8 +9,8 @@ namespace QS.Launcher.ViewModels.PageViewModels.DataBase {
/// По «Далее» создаёт CreateDataBaseProgressVM и пушит её в Carousel поверх текущей
///
public class CreateDataBaseSettingsVM : CarouselPageVM {
- public IDbProvider Provider { get; }
- public Connection Connection { get; }
+ public IDbProvider Provider { get; private set; }
+ public Connection Connection { get; private set; }
private readonly IServiceProvider services;
private string dbTitle;
@@ -34,9 +34,7 @@ public string DbName {
///
public event Action ProgressPageRequested;
- public CreateDataBaseSettingsVM(IDbProvider provider, Connection connection, IServiceProvider services) {
- Provider = provider ?? throw new ArgumentNullException(nameof(provider));
- Connection = connection ?? throw new ArgumentNullException(nameof(connection));
+ public CreateDataBaseSettingsVM(IServiceProvider services) {
this.services = services ?? throw new ArgumentNullException(nameof(services));
var canCreate = this.WhenAnyValue(x => x.DbName, x => x.DbTitle,
@@ -46,11 +44,18 @@ public CreateDataBaseSettingsVM(IDbProvider provider, Connection connection, ISe
CancelCommand = ReactiveCommand.Create(() => PopPageCommand?.Execute(null));
}
+ public void SetDbSettings(IDbProvider provider, Connection connection) {
+ Provider = provider ?? throw new ArgumentNullException(nameof(provider));
+ Connection = connection ?? throw new ArgumentNullException(nameof(connection));
+ }
+
private void GoToProgress() {
// Резолв через ActivatorUtilities — DI подставляет IDbCreatorInteraction/IUiThreadInvoker,
// а провайдер/соединение/имена приходят как runtime-аргументы.
var progress = Microsoft.Extensions.DependencyInjection.ActivatorUtilities
- .CreateInstance(services, Provider, Connection, DbName, DbTitle);
+ .GetServiceOrCreateInstance(services);
+
+ progress.SetDbSettings(dbName, dbTitle, Provider, Connection);
ProgressPageRequested?.Invoke(progress);
PushPageCommand?.Execute(progress);
diff --git a/QS.Launcher/ViewModels/PageViewModels/DataBase/DataBasesVM.cs b/QS.Launcher/ViewModels/PageViewModels/DataBase/DataBasesVM.cs
index 1b7198ae1..d14c43eee 100644
--- a/QS.Launcher/ViewModels/PageViewModels/DataBase/DataBasesVM.cs
+++ b/QS.Launcher/ViewModels/PageViewModels/DataBase/DataBasesVM.cs
@@ -97,8 +97,8 @@ private void OpenCreateDatabase() {
if(provider == null || currentConnection == null)
return;
- var settings = ActivatorUtilities.CreateInstance(
- serviceProvider, provider, currentConnection);
+ var settings = ActivatorUtilities.GetServiceOrCreateInstance(serviceProvider);
+ settings.SetDbSettings(Provider, CurrentConnection);
settings.ProgressPageRequested += progressVm => {
progressVm.DatabaseCreated += OnDatabaseCreatedFromWizard;
From 80ebe2c01fa15d40bdf220c8e79281f53b0a7fb3 Mon Sep 17 00:00:00 2001
From: J0shlerB0y <86416124+J0shlerB0y@users.noreply.github.com>
Date: Sat, 2 May 2026 15:56:11 +0300
Subject: [PATCH 07/19] =?UTF-8?q?=D0=B4=D0=BE=D0=B1=D0=B0=D0=B2=D0=BB?=
=?UTF-8?q?=D0=B5=D0=BD=D0=B8=D0=B5=20title=20=D0=BF=D1=80=D0=B8=20=D1=81?=
=?UTF-8?q?=D0=BE=D0=B7=D0=B4=D0=B0=D0=BD=D0=B8=D0=B8=20=D0=B1=D0=B0=D0=B7?=
=?UTF-8?q?=D1=8B?=
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
---
.../DataBase/QsCloudScriptsConfiguration.cs | 4 ++--
QS.Cloud.Client/Scripts/new_empty.sql | 7 +++++++
.../DBScripts/Models/MySqlDbCreateModel.cs | 12 +++++++++++-
3 files changed, 20 insertions(+), 3 deletions(-)
diff --git a/QS.Cloud.Client/DataBase/QsCloudScriptsConfiguration.cs b/QS.Cloud.Client/DataBase/QsCloudScriptsConfiguration.cs
index 091d50455..488a1f546 100644
--- a/QS.Cloud.Client/DataBase/QsCloudScriptsConfiguration.cs
+++ b/QS.Cloud.Client/DataBase/QsCloudScriptsConfiguration.cs
@@ -12,7 +12,7 @@ public CreationScript MakeCreationScript() {
return new CreationScript(
Assembly.GetAssembly(typeof(QsCloudScriptsConfiguration)),
"QS.Cloud.Client.Scripts.new_empty.sql",
- new Version(1, 0, 1)
+ new Version(1, 7)
);
}
@@ -22,7 +22,7 @@ public UpdateConfiguration MakeUpdateConfiguration() {
configuration.AddUpdate(
new Version(1, 0),
new Version(1, 0, 1),
- "QS.Cloud.Client.Scripts.1.0.1.sql");
+ "QS.Cloud.Client.Scripts.1.7.sql");
return configuration;
}
diff --git a/QS.Cloud.Client/Scripts/new_empty.sql b/QS.Cloud.Client/Scripts/new_empty.sql
index 0b30d71aa..b25952e11 100644
--- a/QS.Cloud.Client/Scripts/new_empty.sql
+++ b/QS.Cloud.Client/Scripts/new_empty.sql
@@ -126,6 +126,13 @@ CREATE TABLE `base_parameters` (
`str_value` varchar(100) DEFAULT NULL
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_general_ci;
+
+
+INSERT INTO `base_parameters` (`name`, `str_value`) VALUES
+('ProductCode', '5'),
+('product_name', 'ClientManager'),
+('version', '1.7');
+
-- --------------------------------------------------------
--
diff --git a/QS.Updater.Core/DBScripts/Models/MySqlDbCreateModel.cs b/QS.Updater.Core/DBScripts/Models/MySqlDbCreateModel.cs
index e15822e62..dac612eb5 100644
--- a/QS.Updater.Core/DBScripts/Models/MySqlDbCreateModel.cs
+++ b/QS.Updater.Core/DBScripts/Models/MySqlDbCreateModel.cs
@@ -43,7 +43,7 @@ public Task RunCreationAsync(string dbName, string dbTitle) {
return Task.Run(() => RunCreation(dbName, dbTitle), cancellationToken);
}
- public bool RunCreation(string dbName, string dbTitle) {
+ public bool RunCreation(string dbName, string dbTitle = null) {
using(var connectionDB = new MySqlConnection(connectionString)) {
try {
logger.Info("Connecting to MySQL...");
@@ -107,6 +107,16 @@ public bool RunCreation(string dbName, string dbTitle) {
logger.Info("BaseGuid успешно записан.");
}
+ if(dbTitle != null) {
+ logger.Info("Генерируем BaseTitle");
+ cmd.CommandText =
+ "INSERT INTO base_parameters (name, str_value) VALUES ('BaseTitle', @title)";
+ cmd.Parameters.Clear();
+ cmd.Parameters.AddWithValue("@title", dbTitle);
+ cmd.ExecuteNonQuery();
+ logger.Info("BaseTitle успешно записан.");
+ }
+
}
catch(OperationCanceledException) {
logger.Info("Создание базы отменено пользователем.");
From a771542256311cf601eeed9f2d0320a780985cfa Mon Sep 17 00:00:00 2001
From: J0shlerB0y <86416124+J0shlerB0y@users.noreply.github.com>
Date: Tue, 5 May 2026 13:01:53 +0300
Subject: [PATCH 08/19] =?UTF-8?q?=D1=80=D0=B5=D0=B0=D0=BB=D0=B8=D0=B7?=
=?UTF-8?q?=D0=B0=D1=86=D0=B8=D1=8F=20IDbCreatorModel=20=D0=B4=D0=BB=D1=8F?=
=?UTF-8?q?=20QSCloud=20=D0=B8=20=D0=BF=D1=80=D0=B0=D0=B2=D0=BA=D0=B8?=
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
Делает то же, что MySqlDbCreateModel
для MariaDB, но физически выполняется на стороне облака:
первый gRPC-вызов CreateDataBase создаёт пустую схему;
второй FillDataBase прогоняет SQL-скрипт и шлёт
прогресс назад. Каждое сообщение стрима транслируется в
IProgressBarDisplayable, поэтому wizard-страница лаунчера
выглядит идентично сценарию с MariaDB.
разделил интерфейс создания базы, для гтк, и ее наполнения моделью
---
.../Clients/DataBaseManagementCloudClient.cs | 13 +-
QS.Cloud.Client/DataBase/QSCloudProvider.cs | 7 ++
.../DataBase/QsCloudConnectionTypeBase.cs | 2 +-
QS.Cloud.Client/DataBase/QsCloudDbCreator.cs | 111 ++++++++++++++++++
.../DataBase/QsCloudScriptsConfiguration.cs | 10 +-
.../Protos/DataBaseManagement.proto | 23 ++++
QS.DbManagement/ConnectionTypeBase.cs | 4 +-
.../MariaDb/MariaDbConnectionTypeBase.cs | 2 +-
.../DataBase/CreateDataBaseSettingsView.axaml | 8 +-
.../DataBase/CreateDataBaseProgressVM.cs | 2 +-
.../DataBase/CreateDataBaseSettingsVM.cs | 2 -
QS.LibsTest.Core/Launcher/ConfiguratorTest.cs | 4 +
.../DBScripts/Controllers/IDBCreator.cs | 7 +-
.../DBScripts/Controllers/IDbCreatorModel.cs | 12 ++
.../DBScripts/IDbScriptsConfiguration.cs | 2 +
.../DBScripts/Models/MySqlDbCreateModel.cs | 2 +-
16 files changed, 191 insertions(+), 20 deletions(-)
create mode 100644 QS.Cloud.Client/DataBase/QsCloudDbCreator.cs
create mode 100644 QS.Project.Core/DBScripts/Controllers/IDbCreatorModel.cs
diff --git a/QS.Cloud.Client/Clients/DataBaseManagementCloudClient.cs b/QS.Cloud.Client/Clients/DataBaseManagementCloudClient.cs
index 27e07a9d9..841025fd1 100644
--- a/QS.Cloud.Client/Clients/DataBaseManagementCloudClient.cs
+++ b/QS.Cloud.Client/Clients/DataBaseManagementCloudClient.cs
@@ -1,4 +1,7 @@
+using System.Threading;
+using Grpc.Core;
using QS.Cloud.Core;
+
namespace QS.Cloud.Client.Clients {
public class DataBaseManagementCloudClient : CloudClientByBasicAuth {
public DataBaseManagementCloudClient(IBasicAuthInfoProvider basicAuthInfoProvider)
@@ -7,7 +10,15 @@ public DataBaseManagementCloudClient(IBasicAuthInfoProvider basicAuthInfoProvide
public CreateDataBaseResponse CreateDataBase(string dbName, string dbTitle) {
var client = new DataBaseManagement.DataBaseManagementClient(Channel);
var request = new CreateDataBaseRequest { Name = dbName, Title = dbTitle };
- return client.CreateDataBase(request, headers); ;
+ return client.CreateDataBase(request, headers);
+ }
+
+ public AsyncServerStreamingCall FillDataBase(
+ string dbName, string dbTitle, CancellationToken cancellationToken = default)
+ {
+ var client = new DataBaseManagement.DataBaseManagementClient(Channel);
+ var request = new FillDataBaseRequest { Name = dbName, Title = dbTitle };
+ return client.FillDataBase(request, headers, cancellationToken: cancellationToken);
}
}
}
diff --git a/QS.Cloud.Client/DataBase/QSCloudProvider.cs b/QS.Cloud.Client/DataBase/QSCloudProvider.cs
index 5395936aa..edb515109 100644
--- a/QS.Cloud.Client/DataBase/QSCloudProvider.cs
+++ b/QS.Cloud.Client/DataBase/QSCloudProvider.cs
@@ -8,6 +8,7 @@
using System.Linq;
using System.Reflection;
using System;
+using System.Threading;
using QS.Cloud.Client.Clients;
namespace QS.Cloud.Client.DataBase
@@ -58,6 +59,12 @@ public bool CreateDatabase(string databaseName, string title)
{
return dbClient.CreateDataBase(databaseName, title).Succsess;
}
+
+ public AsyncServerStreamingCall FillDataBase(
+ string databaseName, string title, CancellationToken cancellationToken = default)
+ {
+ return dbClient.FillDataBase(databaseName, title, cancellationToken);
+ }
public void Dispose()
{
diff --git a/QS.Cloud.Client/DataBase/QsCloudConnectionTypeBase.cs b/QS.Cloud.Client/DataBase/QsCloudConnectionTypeBase.cs
index de2031830..a823a8c5d 100644
--- a/QS.Cloud.Client/DataBase/QsCloudConnectionTypeBase.cs
+++ b/QS.Cloud.Client/DataBase/QsCloudConnectionTypeBase.cs
@@ -25,7 +25,7 @@ public override bool CanConnect(IEnumerable parameters
public override IDbProvider CreateProvider(IList parameters, string password = null)
=> new QSCloudProvider(parameters, password);
- public override IDBCreator CreatorFactory(CreatorFactoryArgs args)
+ public override IDbCreatorModel CreatorFactory(CreatorFactoryArgs args)
=> new QsCloudDbCreator(
args.Provider,
args.Progress,
diff --git a/QS.Cloud.Client/DataBase/QsCloudDbCreator.cs b/QS.Cloud.Client/DataBase/QsCloudDbCreator.cs
new file mode 100644
index 000000000..87f85863e
--- /dev/null
+++ b/QS.Cloud.Client/DataBase/QsCloudDbCreator.cs
@@ -0,0 +1,111 @@
+using System;
+using System.Threading;
+using System.Threading.Tasks;
+using QS.Cloud.Core;
+using QS.DbManagement;
+using QS.DBScripts.Controllers;
+using QS.Dialog;
+
+namespace QS.Cloud.Client.DataBase
+{
+ public class QsCloudDbCreator : IDbCreatorModel
+ {
+ static NLog.Logger logger = NLog.LogManager.GetCurrentClassLogger();
+
+ private readonly QSCloudProvider provider;
+ private readonly IProgressBarDisplayable progress;
+ private readonly IDbCreatorInteraction interaction;
+ private readonly CancellationToken cancellationToken;
+
+ public QsCloudDbCreator(
+ IDbProvider provider,
+ IProgressBarDisplayable progress,
+ IDbCreatorInteraction interaction,
+ CancellationToken cancellationToken)
+ {
+ this.provider = (provider ?? throw new ArgumentNullException(nameof(provider))) as QSCloudProvider
+ ?? throw new ArgumentException("Ожидается QSCloudProvider", nameof(provider));
+ this.progress = progress ?? throw new ArgumentNullException(nameof(progress));
+ this.interaction = interaction ?? throw new ArgumentNullException(nameof(interaction));
+ this.cancellationToken = cancellationToken;
+ }
+
+ public async Task RunCreationAsync(string dbName, string dbTitle) {
+ try {
+ cancellationToken.ThrowIfCancellationRequested();
+
+ // это унарный вызов, прогресс индикативный
+ progress.Start(maxValue: 1, text: $"Создаём базу {dbTitle} в облаке");
+ bool created = await Task.Run(
+ () => provider.CreateDatabase(dbName, dbTitle),
+ cancellationToken);
+
+ if(!created) {
+ await interaction.ReportErrorAsync(
+ "Облако сообщило, что создание базы не удалось.", null);
+ return false;
+ }
+ progress.Add(text: "База создана, начинаем наполнение");
+ cancellationToken.ThrowIfCancellationRequested();
+
+ // Сервер напоняет базу и шлёт прогресс
+ bool finalSuccess = false;
+ string finalError = null;
+ using(var call = provider.FillDataBase(dbName, dbTitle, cancellationToken)) {
+ while(await call.ResponseStream.MoveNext(cancellationToken)) {
+ var msg = call.ResponseStream.Current;
+ ApplyToProgress(msg);
+
+ if(msg.Stage == FillDataBaseProgress.Types.Stage.Finished) {
+ finalSuccess = msg.Success;
+ finalError = msg.ErrorText;
+ break;
+ }
+ }
+ }
+
+ if(!finalSuccess) {
+ await interaction.ReportErrorAsync(
+ string.IsNullOrEmpty(finalError) ? "Облако не смогло наполнить базу." : finalError,
+ null);
+ return false;
+ }
+ return true;
+ }
+ catch(OperationCanceledException) {
+ logger.Info("Создание базы в облаке отменено пользователем.");
+ return false;
+ }
+ catch(Exception ex) {
+ logger.Error(ex, "Ошибка при создании базы в облаке.");
+ await interaction.ReportErrorAsync(ex.Message, null);
+ throw;
+ }
+ finally {
+ if(progress.IsStarted)
+ progress.Close();
+ }
+ }
+
+ private void ApplyToProgress(FillDataBaseProgress msg) {
+ switch(msg.Stage) {
+ //перезапускает шкалу с новым max
+ case FillDataBaseProgress.Types.Stage.Started:
+ progress.Start(
+ maxValue: msg.Max <= 0 ? 1 : msg.Max,
+ text: string.IsNullOrEmpty(msg.Text) ? null : msg.Text,
+ startValue: msg.Current);
+ break;
+ case FillDataBaseProgress.Types.Stage.Progress:
+ progress.Update(msg.Current);
+ if(!string.IsNullOrEmpty(msg.Text))
+ progress.Update(msg.Text);
+ break;
+ case FillDataBaseProgress.Types.Stage.Finished:
+ if(!string.IsNullOrEmpty(msg.Text))
+ progress.Update(msg.Text);
+ break;
+ }
+ }
+ }
+}
diff --git a/QS.Cloud.Client/DataBase/QsCloudScriptsConfiguration.cs b/QS.Cloud.Client/DataBase/QsCloudScriptsConfiguration.cs
index 488a1f546..b6345d95a 100644
--- a/QS.Cloud.Client/DataBase/QsCloudScriptsConfiguration.cs
+++ b/QS.Cloud.Client/DataBase/QsCloudScriptsConfiguration.cs
@@ -3,15 +3,23 @@
using QS.Updater.DB;
using System;
using System.Collections.Generic;
+using System.Linq;
using System.Reflection;
using System.Text;
namespace QS.Cloud.Client.DataBase {
public class QsCloudScriptsConfiguration : IDbScriptsConfiguration {
+ private string ResourceName = "QS.Cloud.Client.Scripts.new_empty.sql";
+ public bool HasCreationScript() {
+ return Assembly.GetAssembly(typeof(QsCloudScriptsConfiguration))
+ .GetReferencedAssemblies().Select(x => x.FullName)
+ .Contains(ResourceName);
+ }
+
public CreationScript MakeCreationScript() {
return new CreationScript(
Assembly.GetAssembly(typeof(QsCloudScriptsConfiguration)),
- "QS.Cloud.Client.Scripts.new_empty.sql",
+ ResourceName,
new Version(1, 7)
);
}
diff --git a/QS.Cloud.Client/Protos/DataBaseManagement.proto b/QS.Cloud.Client/Protos/DataBaseManagement.proto
index 6bd23648d..ef24ca6a3 100644
--- a/QS.Cloud.Client/Protos/DataBaseManagement.proto
+++ b/QS.Cloud.Client/Protos/DataBaseManagement.proto
@@ -3,7 +3,11 @@ syntax = "proto3";
package QS.Cloud.Core;
service DataBaseManagement{
+ // Создать пустую базу
rpc CreateDataBase (CreateDataBaseRequest) returns (CreateDataBaseResponse);
+
+ // Наполняет базу стримит, прогресс-сообщения клиенту по мере выполнения
+ rpc FillDataBase (FillDataBaseRequest) returns (stream FillDataBaseProgress);
}
message CreateDataBaseRequest{
@@ -14,3 +18,22 @@ message CreateDataBaseRequest{
message CreateDataBaseResponse{
bool succsess = 1;
}
+
+message FillDataBaseRequest{
+ string name = 1;
+ string title = 2;
+}
+
+message FillDataBaseProgress{
+ enum Stage {
+ STARTED = 0;
+ PROGRESS = 1;
+ FINISHED = 2;
+ }
+ Stage stage = 1;
+ double current = 2;
+ double max = 3;
+ string text = 4;
+ bool success = 5;
+ string error_text = 6;
+}
diff --git a/QS.DbManagement/ConnectionTypeBase.cs b/QS.DbManagement/ConnectionTypeBase.cs
index 995c0d9af..c3caca35c 100644
--- a/QS.DbManagement/ConnectionTypeBase.cs
+++ b/QS.DbManagement/ConnectionTypeBase.cs
@@ -17,9 +17,9 @@ public abstract class ConnectionTypeBase {
public abstract IDbProvider CreateProvider(IList parameters, string password = null);
- public abstract IDBCreator CreatorFactory(CreatorFactoryArgs args);
+ public abstract IDbCreatorModel CreatorFactory(CreatorFactoryArgs args);
- public IDBCreator CreateCreator(CreatorFactoryArgs args) {
+ public IDbCreatorModel CreateCreator(CreatorFactoryArgs args) {
return CreatorFactory(args);
}
}
diff --git a/QS.DbManagement/MariaDb/MariaDbConnectionTypeBase.cs b/QS.DbManagement/MariaDb/MariaDbConnectionTypeBase.cs
index 771eac670..9560280de 100644
--- a/QS.DbManagement/MariaDb/MariaDbConnectionTypeBase.cs
+++ b/QS.DbManagement/MariaDb/MariaDbConnectionTypeBase.cs
@@ -24,7 +24,7 @@ public override bool CanConnect(IEnumerable parameters
parameters.Any(p => p.Name == "Login" && !string.IsNullOrEmpty(p.Value));
}
- public override IDBCreator CreatorFactory(CreatorFactoryArgs args){
+ public override IDbCreatorModel CreatorFactory(CreatorFactoryArgs args){
var provider = (MariaDBProvider)args.Provider;
var scripts = args.ServiceProvider.GetRequiredService();
return new MySqlDbCreateModel(
diff --git a/QS.Launcher.Avalonia/Views/Pages/DataBase/CreateDataBaseSettingsView.axaml b/QS.Launcher.Avalonia/Views/Pages/DataBase/CreateDataBaseSettingsView.axaml
index 7092888cd..0bd1897bd 100644
--- a/QS.Launcher.Avalonia/Views/Pages/DataBase/CreateDataBaseSettingsView.axaml
+++ b/QS.Launcher.Avalonia/Views/Pages/DataBase/CreateDataBaseSettingsView.axaml
@@ -11,11 +11,11 @@
-
-
+
+
-
-
+
+
diff --git a/QS.Launcher/ViewModels/PageViewModels/DataBase/CreateDataBaseProgressVM.cs b/QS.Launcher/ViewModels/PageViewModels/DataBase/CreateDataBaseProgressVM.cs
index 697b7449b..213707c8b 100644
--- a/QS.Launcher/ViewModels/PageViewModels/DataBase/CreateDataBaseProgressVM.cs
+++ b/QS.Launcher/ViewModels/PageViewModels/DataBase/CreateDataBaseProgressVM.cs
@@ -102,7 +102,7 @@ public async Task StartCreationAsync() {
CancellationToken = cts.Token,
ServiceProvider = services
};
- IDBCreator creator = Connection.ConnectionType.CreateCreator(args);
+ IDbCreatorModel creator = Connection.ConnectionType.CreateCreator(args);
bool ok = await creator.RunCreationAsync(DbName, DbTitle);
if(ok)
DatabaseCreated?.Invoke();
diff --git a/QS.Launcher/ViewModels/PageViewModels/DataBase/CreateDataBaseSettingsVM.cs b/QS.Launcher/ViewModels/PageViewModels/DataBase/CreateDataBaseSettingsVM.cs
index efdd40e67..d68ab0611 100644
--- a/QS.Launcher/ViewModels/PageViewModels/DataBase/CreateDataBaseSettingsVM.cs
+++ b/QS.Launcher/ViewModels/PageViewModels/DataBase/CreateDataBaseSettingsVM.cs
@@ -50,8 +50,6 @@ public void SetDbSettings(IDbProvider provider, Connection connection) {
}
private void GoToProgress() {
- // Резолв через ActivatorUtilities — DI подставляет IDbCreatorInteraction/IUiThreadInvoker,
- // а провайдер/соединение/имена приходят как runtime-аргументы.
var progress = Microsoft.Extensions.DependencyInjection.ActivatorUtilities
.GetServiceOrCreateInstance(services);
diff --git a/QS.LibsTest.Core/Launcher/ConfiguratorTest.cs b/QS.LibsTest.Core/Launcher/ConfiguratorTest.cs
index b68265cf1..48c3b3b8c 100644
--- a/QS.LibsTest.Core/Launcher/ConfiguratorTest.cs
+++ b/QS.LibsTest.Core/Launcher/ConfiguratorTest.cs
@@ -6,6 +6,7 @@
using NSubstitute;
using NUnit.Framework;
using QS.DbManagement;
+using QS.DBScripts.Controllers;
using QS.Dialog;
using QS.Launcher;
@@ -428,6 +429,9 @@ public TestConnectionType(string name) {
public override IDbProvider CreateProvider(IList parameters, string password = null)
=> Substitute.For();
+
+ public override IDbCreatorModel CreatorFactory(CreatorFactoryArgs args)
+ => Substitute.For();
}
}
}
diff --git a/QS.Project.Core/DBScripts/Controllers/IDBCreator.cs b/QS.Project.Core/DBScripts/Controllers/IDBCreator.cs
index 6ab27ea04..e124aff86 100644
--- a/QS.Project.Core/DBScripts/Controllers/IDBCreator.cs
+++ b/QS.Project.Core/DBScripts/Controllers/IDBCreator.cs
@@ -1,12 +1,7 @@
-using System.Threading.Tasks;
-
namespace QS.DBScripts.Controllers
{
- ///
- /// Универсальный контракт создания и наполнения базы данных.
- ///
public interface IDBCreator
{
- Task RunCreationAsync(string dbName, string dbTitle);
+ void RunCreation(string server, string dbname);
}
}
diff --git a/QS.Project.Core/DBScripts/Controllers/IDbCreatorModel.cs b/QS.Project.Core/DBScripts/Controllers/IDbCreatorModel.cs
new file mode 100644
index 000000000..625b9dd3c
--- /dev/null
+++ b/QS.Project.Core/DBScripts/Controllers/IDbCreatorModel.cs
@@ -0,0 +1,12 @@
+using System.Threading.Tasks;
+
+namespace QS.DBScripts.Controllers
+{
+ ///
+ /// Низкоуровневая модель создания БД: знает, как физически создать и наполнить базу
+ ///
+ public interface IDbCreatorModel
+ {
+ Task RunCreationAsync(string dbName, string dbTitle);
+ }
+}
diff --git a/QS.Updater.Core/DBScripts/IDbScriptsConfiguration.cs b/QS.Updater.Core/DBScripts/IDbScriptsConfiguration.cs
index a3bdba61a..fef1a1880 100644
--- a/QS.Updater.Core/DBScripts/IDbScriptsConfiguration.cs
+++ b/QS.Updater.Core/DBScripts/IDbScriptsConfiguration.cs
@@ -8,5 +8,7 @@ public interface IDbScriptsConfiguration
CreationScript MakeCreationScript();
UpdateConfiguration MakeUpdateConfiguration();
+
+ bool HasCreationScript();
}
}
diff --git a/QS.Updater.Core/DBScripts/Models/MySqlDbCreateModel.cs b/QS.Updater.Core/DBScripts/Models/MySqlDbCreateModel.cs
index dac612eb5..e5473e3d8 100644
--- a/QS.Updater.Core/DBScripts/Models/MySqlDbCreateModel.cs
+++ b/QS.Updater.Core/DBScripts/Models/MySqlDbCreateModel.cs
@@ -9,7 +9,7 @@
namespace QS.DBScripts.Models
{
- public class MySqlDbCreateModel : IDBCreator
+ public class MySqlDbCreateModel : IDbCreatorModel
{
static NLog.Logger logger = NLog.LogManager.GetCurrentClassLogger();
From 47c7b2e28a02ac9307019d828a9a4a8c6ab4ac17 Mon Sep 17 00:00:00 2001
From: Gankov
Date: Tue, 5 May 2026 21:36:49 +0300
Subject: [PATCH 09/19] =?UTF-8?q?=D0=95=D1=81=D0=BB=D0=B8=20=D0=BA=D0=B0?=
=?UTF-8?q?=D0=BD=D0=B0=D0=BB=D1=8B=20=D0=BD=D0=B0=20=D0=BF=D1=80=D0=BE?=
=?UTF-8?q?=D0=B5=D0=BA=D1=82=D0=B5=20=D0=BE=D1=82=D1=81=D1=83=D1=82=D1=81?=
=?UTF-8?q?=D1=82=D0=B2=D1=83=D1=8E=20=D1=82=D0=BE=20=D0=BA=D0=BD=D0=BE?=
=?UTF-8?q?=D0=BF=D0=BA=D1=83=20=D0=BE=D1=82=D0=BA=D0=BB=D1=8E=D1=87=D0=B8?=
=?UTF-8?q?=D1=82=D1=8C=20=D0=BE=D0=B1=D0=BD=D0=BE=D0=B2=D0=BB=D0=B5=D0=BD?=
=?UTF-8?q?=D0=B8=D1=8F=20=D0=B1=D0=B5=D1=81=D1=81=D0=BC=D1=8B=D1=81=D0=BB?=
=?UTF-8?q?=D0=B5=D0=BD=D0=BD=D0=BE=20=D0=BF=D0=BE=D0=BA=D0=B0=D0=B7=D1=8B?=
=?UTF-8?q?=D0=B2=D0=B0=D1=82=D1=8C=20=D1=82=D0=B0=D0=BA=20=D0=BA=D0=B0?=
=?UTF-8?q?=D0=BA=20=D0=BE=D0=BD=D0=B8=20=D0=B2=D1=81=D0=B5=20=D1=80=D0=B0?=
=?UTF-8?q?=D0=B2=D0=BD=D0=BE=20=D0=BD=D0=B5=20=D1=81=D1=80=D0=B0=D0=B1?=
=?UTF-8?q?=D0=BE=D1=82=D0=B0=D1=8E=D1=82.=20=D0=BF=D1=80=D0=B8=20=D1=81?=
=?UTF-8?q?=D0=BB=D0=B5=D0=B4=D1=83=D1=8E=D1=89=D0=B5=D0=BC=20=D0=B2=D1=85?=
=?UTF-8?q?=D0=BE=D0=B4=D0=B5.?=
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
---
QS.Updater.App.Gtk/Views/NewVersionView.cs | 5 ++++-
QS.Updater.App/ViewModels/NewVersionViewModel.cs | 5 ++++-
2 files changed, 8 insertions(+), 2 deletions(-)
diff --git a/QS.Updater.App.Gtk/Views/NewVersionView.cs b/QS.Updater.App.Gtk/Views/NewVersionView.cs
index f047249a1..43c40b637 100644
--- a/QS.Updater.App.Gtk/Views/NewVersionView.cs
+++ b/QS.Updater.App.Gtk/Views/NewVersionView.cs
@@ -27,7 +27,10 @@ public NewVersionView(NewVersionViewModel viewModel) : base(viewModel) {
comboSelectInstaller.Binding.AddBinding(ViewModel, v => v.SelectedRelease, w => w.SelectedItem).InitializeFromSource();
buttonSkip.Binding.AddBinding(ViewModel, vm => vm.CanSkipUpdate, w => w.Sensitive).InitializeFromSource();
- buttonOffAutoUpdate.Binding.AddBinding(ViewModel, vm => vm.CanSkipUpdate, w => w.Sensitive).InitializeFromSource();
+ buttonOffAutoUpdate.Binding.AddSource(ViewModel)
+ .AddBinding(vm => vm.VisibleUpdateOff, w => w.Visible)
+ .AddBinding(vm => vm.CanSkipUpdate, w => w.Sensitive)
+ .InitializeFromSource();
for(uint i = 0; i < ViewModel.Releases.Length; i++) {
uint baseRow = i * 4;
diff --git a/QS.Updater.App/ViewModels/NewVersionViewModel.cs b/QS.Updater.App/ViewModels/NewVersionViewModel.cs
index 36da3d5b4..0e807f8e5 100644
--- a/QS.Updater.App/ViewModels/NewVersionViewModel.cs
+++ b/QS.Updater.App/ViewModels/NewVersionViewModel.cs
@@ -26,6 +26,7 @@ public class NewVersionViewModel : WindowDialogViewModelBase {
private readonly IDataBaseInfo dataBaseInfo;
private readonly IChangeableConfiguration configuration;
private readonly CheckBaseVersion checkBaseVersion;
+ private readonly IUpdateChannelService channelService;
public NewVersionViewModel(
ReleaseInfo[] releases,
@@ -37,6 +38,7 @@ public NewVersionViewModel(
IInteractiveMessage interactive,
IChangeableConfiguration configuration,
CheckBaseVersion checkBaseVersion = null,
+ IUpdateChannelService channelService = null,
IDataBaseInfo dataBaseInfo = null) : base(navigation) {
Title = "Доступна новая версия программы!";
WindowPosition = WindowGravity.None;
@@ -48,6 +50,7 @@ public NewVersionViewModel(
this.interactive = interactive ?? throw new ArgumentNullException(nameof(interactive));
this.configuration = configuration ?? throw new ArgumentNullException(nameof(configuration));
this.checkBaseVersion = checkBaseVersion;
+ this.channelService = channelService;
this.dataBaseInfo = dataBaseInfo;
if(!releases.Any())
@@ -114,7 +117,7 @@ public virtual ReleaseInfo SelectedRelease {
public bool VisibleDbUpdateInfo => DbUpdateInfo != null;
public bool VisibleDbInfo => dataBaseInfo != null;
public bool VisibleSelectRelease => CanSelectedReleases.Count() > 1;
-
+ public bool VisibleUpdateOff => channelService != null && channelService.CurrentChannel != UpdateChannel.Off;
public bool CanSkipUpdate => checkBaseVersion?.Result != CheckBaseResult.BaseVersionGreater;
#endregion
From b1417d214893eb625ca2dab1b6ea608919e032b6 Mon Sep 17 00:00:00 2001
From: Gankov
Date: Wed, 6 May 2026 17:45:41 +0300
Subject: [PATCH 10/19] =?UTF-8?q?=D0=94=D0=BE=D0=B1=D0=B0=D0=B2=D0=BB?=
=?UTF-8?q?=D0=B5=D0=BD=20=D1=84=D0=BE=D1=80=D0=BC=D0=B0=D1=82=20=D0=BF?=
=?UTF-8?q?=D1=80=D0=BE=D0=B2=D0=B5=D1=80=D0=BA=D0=B8=20=D0=B5=D0=BC=D0=B5?=
=?UTF-8?q?=D0=B9=D0=BB=D0=BE=D0=B2=20=D0=B8=D0=B7=20=D0=BD=D0=B5=D1=81?=
=?UTF-8?q?=D0=BA=D0=BE=D0=BB=D1=8C=D0=BA=D0=BE=20=D0=B0=D0=B4=D1=80=D0=B5?=
=?UTF-8?q?=D1=81=D0=BE=D0=B2.?=
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
---
QS.Project.Gtk/Widgets/ValidatedEntry.cs | 27 ++++++++++++++++++++++++
1 file changed, 27 insertions(+)
diff --git a/QS.Project.Gtk/Widgets/ValidatedEntry.cs b/QS.Project.Gtk/Widgets/ValidatedEntry.cs
index 1e33f9884..5ae7652f5 100644
--- a/QS.Project.Gtk/Widgets/ValidatedEntry.cs
+++ b/QS.Project.Gtk/Widgets/ValidatedEntry.cs
@@ -47,6 +47,12 @@ public ValidationType ValidationMode {
this.Changed += RemoveInvalidSymbols;
this.Changed += RegexValidate;
break;
+ case ValidationType.MultipleEmail:
+ regex = new Regex(@"^[a-zA-Z0-9.!#$%&'*+/=?^_`{|}~-]+@" +
+ @"[a-zA-Z0-9][a-zA-Z0-9-]{0,61}[a-zA-Z0-9]\.[a-zA-Z0-9][a-zA-Z0-9-]{0,61}[a-zA-Z0-9]$");
+ this.Changed += RemoveInvalidSymbolsMultiple;
+ this.Changed += MultipleEmailValidate;
+ break;
default:
break;
}
@@ -87,6 +93,26 @@ protected void RemoveInvalidSymbols(object sender, System.EventArgs Args)
this.Text = Text.Replace(" ", "").Replace("\n", "");
}
+ protected void RemoveInvalidSymbolsMultiple(object sender, System.EventArgs Args)
+ {
+ this.Text = Text.Replace(" ", "").Replace("\n", "").Replace(";", ",");
+ }
+
+ protected void MultipleEmailValidate(object sender, System.EventArgs Args)
+ {
+ var text = (sender as Gtk.Entry).Text;
+ if(string.IsNullOrEmpty(text)) {
+ (sender as Gtk.Entry).ModifyText(Gtk.StateType.Normal);
+ return;
+ }
+ var emails = text.Split(new[] { ',' }, StringSplitOptions.RemoveEmptyEntries);
+ bool allValid = emails.Length > 0 && System.Array.TrueForAll(emails, e => regex.IsMatch(e.Trim()));
+ if(!allValid)
+ (sender as Gtk.Entry).ModifyText(Gtk.StateType.Normal, new Gdk.Color(255, 0, 0));
+ else
+ (sender as Gtk.Entry).ModifyText(Gtk.StateType.Normal);
+ }
+
protected override void OnChanged()
{
Binding.FireChange(w => w.Text);
@@ -98,6 +124,7 @@ public enum ValidationType {
None,
Numeric,
Email,
+ MultipleEmail,
Price,
CustomRegex
};
From 8229cb581ec85b2b4b8c9a24b30780e46c5b924a Mon Sep 17 00:00:00 2001
From: J0shlerB0y <86416124+J0shlerB0y@users.noreply.github.com>
Date: Wed, 6 May 2026 21:30:20 +0300
Subject: [PATCH 11/19] =?UTF-8?q?=D0=A1=D0=BE=D0=B7=D0=B4=D0=B0=D0=BD?=
=?UTF-8?q?=D0=B8=D0=B5=20=D0=B1=D0=B0=D0=B7=D1=8B=20=D1=87=D0=B5=D1=80?=
=?UTF-8?q?=D0=B5=D0=B7=20=D0=BE=D0=B1=D0=BB=D0=B0=D0=BA=D0=BE?=
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
---
.../Clients/DataBaseManagementCloudClient.cs | 8 --
QS.Cloud.Client/DataBase/QSCloudProvider.cs | 39 ++++---
.../DataBase/QsCloudConnectionTypeBase.cs | 25 +++--
QS.Cloud.Client/DataBase/QsCloudDbCreator.cs | 101 +++++++-----------
.../Protos/DataBaseManagement.proto | 26 +----
QS.DbManagement/MariaDb/MariaDBProvider.cs | 9 +-
.../MariaDb/MariaDbConnectionTypeBase.cs | 4 +-
QS.Launcher.Avalonia/DependencyInjection.cs | 2 -
.../DataBase/CreateDataBaseProgressView.axaml | 5 +-
QS.Launcher/ViewModels/MainWindowVM.cs | 2 +-
.../PageViewModels/DataBase/DataBasesVM.cs | 2 +-
.../DBScripts/Models/MySqlDbCreateModel.cs | 26 ++++-
12 files changed, 116 insertions(+), 133 deletions(-)
diff --git a/QS.Cloud.Client/Clients/DataBaseManagementCloudClient.cs b/QS.Cloud.Client/Clients/DataBaseManagementCloudClient.cs
index 841025fd1..9bd26b15b 100644
--- a/QS.Cloud.Client/Clients/DataBaseManagementCloudClient.cs
+++ b/QS.Cloud.Client/Clients/DataBaseManagementCloudClient.cs
@@ -12,13 +12,5 @@ public CreateDataBaseResponse CreateDataBase(string dbName, string dbTitle) {
var request = new CreateDataBaseRequest { Name = dbName, Title = dbTitle };
return client.CreateDataBase(request, headers);
}
-
- public AsyncServerStreamingCall FillDataBase(
- string dbName, string dbTitle, CancellationToken cancellationToken = default)
- {
- var client = new DataBaseManagement.DataBaseManagementClient(Channel);
- var request = new FillDataBaseRequest { Name = dbName, Title = dbTitle };
- return client.FillDataBase(request, headers, cancellationToken: cancellationToken);
- }
}
}
diff --git a/QS.Cloud.Client/DataBase/QSCloudProvider.cs b/QS.Cloud.Client/DataBase/QSCloudProvider.cs
index edb515109..77e1d8e63 100644
--- a/QS.Cloud.Client/DataBase/QSCloudProvider.cs
+++ b/QS.Cloud.Client/DataBase/QSCloudProvider.cs
@@ -10,11 +10,18 @@
using System;
using System.Threading;
using QS.Cloud.Client.Clients;
+using QS.DBScripts.Controllers;
+using System.Threading.Tasks;
namespace QS.Cloud.Client.DataBase
{
- public class QSCloudProvider : IDbProvider {
- public string ConnectionString { get => throw new NotImplementedException(); set => throw new NotImplementedException(); }
+ public class QSCloudProvider : IDbProvider, IDbCreatorModel {
+
+ ///
+ /// Публичный - в типе родключения нужен доступ, реализацию он знает и так
+ ///
+ public int BaseId { get; private set; }
+ public BasicAuthInfoProvider AuthInfo { get; private set; }
public bool IsConnected => throw new NotImplementedException();
@@ -22,27 +29,23 @@ public class QSCloudProvider : IDbProvider {
#region Параметры подключени
public string Account { get; private set; }
-
#endregion
public string UserName { get; private set; }
public bool CanCreateDatabase => throw new NotImplementedException();
- private CloudFeaturesClient featuresClient;
private LoginManagementCloudClient loginClient;
- private SessionManagementCloudClient sessionClient;
private DataBaseManagementCloudClient dbClient;
- private UserManagementCloudClient userClient;
public QSCloudProvider(IList parameters, string password = null) {
Account = parameters.First(p => p.Name == "Account").Value;
UserName = parameters.First(p => p.Name == "Login").Value;
- BasicAuthInfoProvider authInfo = new BasicAuthInfoProvider($@"{Account}\{UserName}", password);
+ AuthInfo = new BasicAuthInfoProvider($@"{Account}\{UserName}", password);
- loginClient = new LoginManagementCloudClient(authInfo);
- dbClient = new DataBaseManagementCloudClient(authInfo);
+ loginClient = new LoginManagementCloudClient(AuthInfo);
+ dbClient = new DataBaseManagementCloudClient(AuthInfo);
}
public bool AddUser(string username, string password)
@@ -57,13 +60,9 @@ public bool ChangePassword(string username, string oldPassword, string newPasswo
public bool CreateDatabase(string databaseName, string title)
{
- return dbClient.CreateDataBase(databaseName, title).Succsess;
- }
-
- public AsyncServerStreamingCall FillDataBase(
- string databaseName, string title, CancellationToken cancellationToken = default)
- {
- return dbClient.FillDataBase(databaseName, title, cancellationToken);
+ CreateDataBaseResponse response = dbClient.CreateDataBase(databaseName, title);
+ BaseId = response.BaseId;
+ return true;
}
public void Dispose()
@@ -139,6 +138,14 @@ public LoginToServerResponse LoginToServer() {
return resp;
}
+
+ public Task RunCreationAsync(string dbName, string dbTitle) {
+ throw new NotImplementedException();
+ }
+
+ public void RunCreation(string server, string dbname) {
+ throw new NotImplementedException();
+ }
}
}
diff --git a/QS.Cloud.Client/DataBase/QsCloudConnectionTypeBase.cs b/QS.Cloud.Client/DataBase/QsCloudConnectionTypeBase.cs
index a823a8c5d..bf0553cb2 100644
--- a/QS.Cloud.Client/DataBase/QsCloudConnectionTypeBase.cs
+++ b/QS.Cloud.Client/DataBase/QsCloudConnectionTypeBase.cs
@@ -1,12 +1,16 @@
+using Microsoft.Extensions.DependencyInjection;
using QS.DbManagement;
+using QS.DBScripts;
+using QS.DBScripts.Controllers;
+using QS.DBScripts.Models;
+using QS.Utilities.Extensions;
using System.Collections.Generic;
using System.Linq;
using System.Reflection;
-using QS.Utilities.Extensions;
-using QS.DBScripts.Controllers;
namespace QS.Cloud.Client.DataBase {
public class QsCloudConnectionTypeBase : ConnectionTypeBase {
+
public QsCloudConnectionTypeBase() {
Title = "QS: Облако";
ConnectionTypeName = "QSCloud";
@@ -22,14 +26,21 @@ public override bool CanConnect(IEnumerable parameters
parameters.Any(p => p.Name == "Login" && !string.IsNullOrEmpty(p.Value));
}
- public override IDbProvider CreateProvider(IList parameters, string password = null)
- => new QSCloudProvider(parameters, password);
+ public override IDbProvider CreateProvider(IList parameters, string password = null) {
+ return new QSCloudProvider(parameters, password);
+ }
- public override IDbCreatorModel CreatorFactory(CreatorFactoryArgs args)
- => new QsCloudDbCreator(
- args.Provider,
+ public override IDbCreatorModel CreatorFactory(CreatorFactoryArgs args) {
+ var provider = (QSCloudProvider)args.Provider;
+ var scripts = args.ServiceProvider.GetRequiredService();
+ var creator = new QsCloudDbCreator(
+ provider.BaseId,
+ provider.AuthInfo,
+ scripts,
args.Progress,
args.Interaction,
args.CancellationToken);
+ return creator;
+ }
}
}
diff --git a/QS.Cloud.Client/DataBase/QsCloudDbCreator.cs b/QS.Cloud.Client/DataBase/QsCloudDbCreator.cs
index 87f85863e..426e0b2e6 100644
--- a/QS.Cloud.Client/DataBase/QsCloudDbCreator.cs
+++ b/QS.Cloud.Client/DataBase/QsCloudDbCreator.cs
@@ -1,10 +1,11 @@
-using System;
-using System.Threading;
-using System.Threading.Tasks;
using QS.Cloud.Core;
-using QS.DbManagement;
+using QS.DBScripts;
using QS.DBScripts.Controllers;
+using QS.DBScripts.Models;
using QS.Dialog;
+using System;
+using System.Threading;
+using System.Threading.Tasks;
namespace QS.Cloud.Client.DataBase
{
@@ -12,65 +13,60 @@ public class QsCloudDbCreator : IDbCreatorModel
{
static NLog.Logger logger = NLog.LogManager.GetCurrentClassLogger();
- private readonly QSCloudProvider provider;
+ private readonly int baseId;
+
private readonly IProgressBarDisplayable progress;
private readonly IDbCreatorInteraction interaction;
+ private readonly IDbScriptsConfiguration configuration;
private readonly CancellationToken cancellationToken;
+ private LoginManagementCloudClient loginClient;
+
public QsCloudDbCreator(
- IDbProvider provider,
+ int baseId,
+ BasicAuthInfoProvider AuthInfo,
+ IDbScriptsConfiguration configuration,
IProgressBarDisplayable progress,
IDbCreatorInteraction interaction,
CancellationToken cancellationToken)
{
- this.provider = (provider ?? throw new ArgumentNullException(nameof(provider))) as QSCloudProvider
- ?? throw new ArgumentException("Ожидается QSCloudProvider", nameof(provider));
+ this.baseId = baseId;
+
+ loginClient = new LoginManagementCloudClient(AuthInfo);
+
+ this.configuration = configuration ?? throw new ArgumentNullException(nameof(progress));
this.progress = progress ?? throw new ArgumentNullException(nameof(progress));
this.interaction = interaction ?? throw new ArgumentNullException(nameof(interaction));
this.cancellationToken = cancellationToken;
}
+
public async Task RunCreationAsync(string dbName, string dbTitle) {
try {
- cancellationToken.ThrowIfCancellationRequested();
-
- // это унарный вызов, прогресс индикативный
- progress.Start(maxValue: 1, text: $"Создаём базу {dbTitle} в облаке");
- bool created = await Task.Run(
- () => provider.CreateDatabase(dbName, dbTitle),
- cancellationToken);
+ StartSessionResponse session = loginClient.StartSession(baseId);
- if(!created) {
- await interaction.ReportErrorAsync(
- "Облако сообщило, что создание базы не удалось.", null);
- return false;
+ if(!session.Success) {
+ await interaction.ReportErrorAsync("Ошибка в создании сесии", "Запрос в облако");
+ throw new InvalidOperationException("Ошибка в создании сесии");
+ }
+ else if(!session.IsAdmin) {
+ await interaction.ReportErrorAsync("Вы не имеете прав Администратора", "Запрос в облако, по типу SELECT cloud_users.multi_ip as multiIp, base_access.admin AS isAdmin, base_access.read_only as readOnly \" +\n\t\t\t\t\t\t\t\"FROM cloud_users \" + \n\t\t\t\t\t\t\t\"LEFT JOIN base_access ON base_access.user_id = cloud_users.id \" +\n\t\t\t\t\t\t\t\"WHERE cloud_users.id = @id AND base_access.base_id = @dbid;");
}
- progress.Add(text: "База создана, начинаем наполнение");
- cancellationToken.ThrowIfCancellationRequested();
- // Сервер напоняет базу и шлёт прогресс
- bool finalSuccess = false;
- string finalError = null;
- using(var call = provider.FillDataBase(dbName, dbTitle, cancellationToken)) {
- while(await call.ResponseStream.MoveNext(cancellationToken)) {
- var msg = call.ResponseStream.Current;
- ApplyToProgress(msg);
+ var infoProvider = new SessionInfoProvider(sessionId: session.SessionId);
+ var sessionLife = new AliveCloudClient(infoProvider);
+ sessionLife.NewMessage += (mes) => {
+ progress.Update("Сессия: " + mes + " в статусе " + sessionLife.LastStatus.ToString());
+ };
+ sessionLife.KeepAlive();
- if(msg.Stage == FillDataBaseProgress.Types.Stage.Finished) {
- finalSuccess = msg.Success;
- finalError = msg.ErrorText;
- break;
- }
- }
- }
+ var creator = new MySqlDbCreateModel(session.Db.Server, session.Db.Port, session.Db.Login, session.Db.Password, configuration, progress, interaction, cancellationToken);
+ creator.FillBaseGuid = false;
+ bool success = await creator.RunCreationAsync(dbName, dbTitle);
- if(!finalSuccess) {
- await interaction.ReportErrorAsync(
- string.IsNullOrEmpty(finalError) ? "Облако не смогло наполнить базу." : finalError,
- null);
- return false;
- }
- return true;
+ sessionLife.Dispose();
+
+ return success;
}
catch(OperationCanceledException) {
logger.Info("Создание базы в облаке отменено пользователем.");
@@ -86,26 +82,5 @@ await interaction.ReportErrorAsync(
progress.Close();
}
}
-
- private void ApplyToProgress(FillDataBaseProgress msg) {
- switch(msg.Stage) {
- //перезапускает шкалу с новым max
- case FillDataBaseProgress.Types.Stage.Started:
- progress.Start(
- maxValue: msg.Max <= 0 ? 1 : msg.Max,
- text: string.IsNullOrEmpty(msg.Text) ? null : msg.Text,
- startValue: msg.Current);
- break;
- case FillDataBaseProgress.Types.Stage.Progress:
- progress.Update(msg.Current);
- if(!string.IsNullOrEmpty(msg.Text))
- progress.Update(msg.Text);
- break;
- case FillDataBaseProgress.Types.Stage.Finished:
- if(!string.IsNullOrEmpty(msg.Text))
- progress.Update(msg.Text);
- break;
- }
- }
}
}
diff --git a/QS.Cloud.Client/Protos/DataBaseManagement.proto b/QS.Cloud.Client/Protos/DataBaseManagement.proto
index ef24ca6a3..bb5c03f91 100644
--- a/QS.Cloud.Client/Protos/DataBaseManagement.proto
+++ b/QS.Cloud.Client/Protos/DataBaseManagement.proto
@@ -5,35 +5,15 @@ package QS.Cloud.Core;
service DataBaseManagement{
// Создать пустую базу
rpc CreateDataBase (CreateDataBaseRequest) returns (CreateDataBaseResponse);
-
- // Наполняет базу стримит, прогресс-сообщения клиенту по мере выполнения
- rpc FillDataBase (FillDataBaseRequest) returns (stream FillDataBaseProgress);
}
message CreateDataBaseRequest{
string name = 1;
string title = 2;
+ uint32 product_id = 3;
}
message CreateDataBaseResponse{
- bool succsess = 1;
-}
-
-message FillDataBaseRequest{
- string name = 1;
- string title = 2;
-}
-
-message FillDataBaseProgress{
- enum Stage {
- STARTED = 0;
- PROGRESS = 1;
- FINISHED = 2;
- }
- Stage stage = 1;
- double current = 2;
- double max = 3;
- string text = 4;
- bool success = 5;
- string error_text = 6;
+ int32 base_id = 1;
+ string base_guid = 2;
}
diff --git a/QS.DbManagement/MariaDb/MariaDBProvider.cs b/QS.DbManagement/MariaDb/MariaDBProvider.cs
index 7a031b382..948b225ff 100644
--- a/QS.DbManagement/MariaDb/MariaDBProvider.cs
+++ b/QS.DbManagement/MariaDb/MariaDBProvider.cs
@@ -1,4 +1,5 @@
using Dapper;
+using FluentNHibernate.Cfg.Db;
using MySqlConnector;
using QS.DbManagement.Responces;
using QS.Project.Versioning;
@@ -15,9 +16,9 @@ public class MariaDBProvider : IDbProvider {
private static readonly string[] SystemDatabases = { "information_schema", "mysql", "performance_schema", "sys" };
readonly MySqlConnection connection;
+
///
- /// Публичный, чтобы внешние компоненты могли получить подключение
- /// без повторного разбора параметров.
+ /// Публичный - в типе родключения нужен доступ, реализацию он знает и так
///
public MySqlConnectionStringBuilder ConnectionStringBuilder { get; }
@@ -25,10 +26,6 @@ public class MariaDBProvider : IDbProvider {
public bool IsAdmin { get; private set; }
- ///
- /// Есть ли у текущего пользователя право создавать базы данных.
- /// Определяется в момент из SHOW GRANTS.
- ///
public bool CanCreateDatabase { get; private set; }
///
diff --git a/QS.DbManagement/MariaDb/MariaDbConnectionTypeBase.cs b/QS.DbManagement/MariaDb/MariaDbConnectionTypeBase.cs
index 9560280de..29b4805a3 100644
--- a/QS.DbManagement/MariaDb/MariaDbConnectionTypeBase.cs
+++ b/QS.DbManagement/MariaDb/MariaDbConnectionTypeBase.cs
@@ -27,12 +27,14 @@ public override bool CanConnect(IEnumerable parameters
public override IDbCreatorModel CreatorFactory(CreatorFactoryArgs args){
var provider = (MariaDBProvider)args.Provider;
var scripts = args.ServiceProvider.GetRequiredService();
- return new MySqlDbCreateModel(
+ var creator = new MySqlDbCreateModel(
provider.ConnectionStringBuilder.ConnectionString,
scripts,
args.Progress,
args.Interaction,
args.CancellationToken);
+ creator.FillBaseGuid = false;
+ return creator;
}
public override IDbProvider CreateProvider(IList parameters, string password = null)
diff --git a/QS.Launcher.Avalonia/DependencyInjection.cs b/QS.Launcher.Avalonia/DependencyInjection.cs
index 068c14555..d5dce86ef 100644
--- a/QS.Launcher.Avalonia/DependencyInjection.cs
+++ b/QS.Launcher.Avalonia/DependencyInjection.cs
@@ -12,8 +12,6 @@ public static IServiceCollection AddPages(this IServiceCollection services) {
.AddSingleton()
.AddSingleton()
.AddSingleton()
- .AddSingleton()
- .AddSingleton()
.AddTransient()
.AddTransient()
.AddSingleton();
diff --git a/QS.Launcher.Avalonia/Views/Pages/DataBase/CreateDataBaseProgressView.axaml b/QS.Launcher.Avalonia/Views/Pages/DataBase/CreateDataBaseProgressView.axaml
index 9a468a3f1..ee33ff10a 100644
--- a/QS.Launcher.Avalonia/Views/Pages/DataBase/CreateDataBaseProgressView.axaml
+++ b/QS.Launcher.Avalonia/Views/Pages/DataBase/CreateDataBaseProgressView.axaml
@@ -24,7 +24,7 @@
@@ -36,9 +36,6 @@
Maximum="{Binding MaxValue}"
Value="{Binding Value}" />
-
-
diff --git a/QS.Launcher/ViewModels/MainWindowVM.cs b/QS.Launcher/ViewModels/MainWindowVM.cs
index af5495e60..34d9d7c4b 100644
--- a/QS.Launcher/ViewModels/MainWindowVM.cs
+++ b/QS.Launcher/ViewModels/MainWindowVM.cs
@@ -34,7 +34,7 @@ public MainWindowVM(
IServiceProvider provider)
{
Pages = new ObservableCollection {
- loginVM, dataBasesVM, baseManagementVM, userManagementVM
+ loginVM, dataBasesVM
};
rootPagesCount = Pages.Count;
login = loginVM;
diff --git a/QS.Launcher/ViewModels/PageViewModels/DataBase/DataBasesVM.cs b/QS.Launcher/ViewModels/PageViewModels/DataBase/DataBasesVM.cs
index d14c43eee..047307e6e 100644
--- a/QS.Launcher/ViewModels/PageViewModels/DataBase/DataBasesVM.cs
+++ b/QS.Launcher/ViewModels/PageViewModels/DataBase/DataBasesVM.cs
@@ -91,7 +91,7 @@ public DataBasesVM(
}
///
- /// создаёт CreateDataBaseSettingsVM возвращает фокус на DataBasesVM и обновляет список баз.
+ /// создаёт возвращает фокус на и обновляет список баз
///
private void OpenCreateDatabase() {
if(provider == null || currentConnection == null)
diff --git a/QS.Updater.Core/DBScripts/Models/MySqlDbCreateModel.cs b/QS.Updater.Core/DBScripts/Models/MySqlDbCreateModel.cs
index e5473e3d8..48d5397d7 100644
--- a/QS.Updater.Core/DBScripts/Models/MySqlDbCreateModel.cs
+++ b/QS.Updater.Core/DBScripts/Models/MySqlDbCreateModel.cs
@@ -1,6 +1,9 @@
+using FluentNHibernate.Cfg.Db;
using MySqlConnector;
using QS.DBScripts.Controllers;
using QS.Dialog;
+using QS.Project.DB;
+using QS.Project.Domain;
using System;
using System.Text.RegularExpressions;
using System.Threading;
@@ -37,7 +40,28 @@ public MySqlDbCreateModel(
this.cancellationToken = cancellationToken;
}
- public Task RunCreationAsync(string dbName, string dbTitle) {
+ public MySqlDbCreateModel(
+ string server, uint port, string login, string password,
+ IDbScriptsConfiguration scripts,
+ IProgressBarDisplayable progress,
+ IDbCreatorInteraction interaction,
+ CancellationToken cancellationToken) {
+
+ this.connectionString = new MySqlConnectionStringBuilder {
+ Server = server,
+ Port = port,
+ UserID = login,
+ Password = password,
+ AllowUserVariables = true
+ }.ConnectionString;
+ this.scripts = scripts.MakeCreationScript() ?? throw new ArgumentNullException(nameof(scripts));
+ this.progress = progress ?? throw new ArgumentNullException(nameof(progress));
+ this.interaction = interaction ?? throw new ArgumentNullException(nameof(interaction));
+ this.cancellationToken = cancellationToken;
+ }
+
+
+ public Task RunCreationAsync(string dbName, string dbTitle = null) {
// Тяжёлая часть с MySqlScript.Execute синхронная,
// поэтому уносим её на пул, чтобы не блокировать UI-поток
return Task.Run(() => RunCreation(dbName, dbTitle), cancellationToken);
From 3f9ae8e9ee77145846ca28e638fb3fd46a0e0441 Mon Sep 17 00:00:00 2001
From: J0shlerB0y <86416124+J0shlerB0y@users.noreply.github.com>
Date: Wed, 6 May 2026 22:31:31 +0300
Subject: [PATCH 12/19] =?UTF-8?q?=D1=83=D0=B1=D1=80=D0=B0=D0=BB=20=D0=BB?=
=?UTF-8?q?=D0=B8=D1=88=D0=BD=D0=B8=D0=B5=20=D1=8D=D0=BB=D0=B5=D0=BC=D0=B5?=
=?UTF-8?q?=D0=BD=D1=82=D1=8B?=
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
---
QS.Cloud.Client/DataBase/QSCloudProvider.cs | 10 +---------
QS.Cloud.Client/DataBase/QsCloudDbCreator.cs | 2 +-
.../DataBase/QsCloudScriptsConfiguration.cs | 4 ++--
QS.Cloud.Client/QS.Cloud.Client.csproj | 1 -
.../Views/Pages/BaseManagementView.axaml.cs | 1 -
QS.Launcher/ViewModels/MainWindowVM.cs | 2 +-
.../ViewModels/PageViewModels/BaseManagementVM.cs | 1 +
.../ViewModels/PageViewModels/CarouselPageVM.cs | 4 ++--
.../DataBase/CreateDataBaseSettingsVM.cs | 3 ---
QS.Updater.Core/DBScripts/Models/MySqlDbCreateModel.cs | 4 ----
10 files changed, 8 insertions(+), 24 deletions(-)
diff --git a/QS.Cloud.Client/DataBase/QSCloudProvider.cs b/QS.Cloud.Client/DataBase/QSCloudProvider.cs
index 77e1d8e63..9682fb105 100644
--- a/QS.Cloud.Client/DataBase/QSCloudProvider.cs
+++ b/QS.Cloud.Client/DataBase/QSCloudProvider.cs
@@ -15,7 +15,7 @@
namespace QS.Cloud.Client.DataBase
{
- public class QSCloudProvider : IDbProvider, IDbCreatorModel {
+ public class QSCloudProvider : IDbProvider {
///
/// Публичный - в типе родключения нужен доступ, реализацию он знает и так
@@ -138,14 +138,6 @@ public LoginToServerResponse LoginToServer() {
return resp;
}
-
- public Task RunCreationAsync(string dbName, string dbTitle) {
- throw new NotImplementedException();
- }
-
- public void RunCreation(string server, string dbname) {
- throw new NotImplementedException();
- }
}
}
diff --git a/QS.Cloud.Client/DataBase/QsCloudDbCreator.cs b/QS.Cloud.Client/DataBase/QsCloudDbCreator.cs
index 426e0b2e6..92cca32d3 100644
--- a/QS.Cloud.Client/DataBase/QsCloudDbCreator.cs
+++ b/QS.Cloud.Client/DataBase/QsCloudDbCreator.cs
@@ -50,7 +50,7 @@ public async Task RunCreationAsync(string dbName, string dbTitle) {
throw new InvalidOperationException("Ошибка в создании сесии");
}
else if(!session.IsAdmin) {
- await interaction.ReportErrorAsync("Вы не имеете прав Администратора", "Запрос в облако, по типу SELECT cloud_users.multi_ip as multiIp, base_access.admin AS isAdmin, base_access.read_only as readOnly \" +\n\t\t\t\t\t\t\t\"FROM cloud_users \" + \n\t\t\t\t\t\t\t\"LEFT JOIN base_access ON base_access.user_id = cloud_users.id \" +\n\t\t\t\t\t\t\t\"WHERE cloud_users.id = @id AND base_access.base_id = @dbid;");
+ await interaction.ReportErrorAsync("Вы не имеете прав Администратора", "Запрос в облако");
}
var infoProvider = new SessionInfoProvider(sessionId: session.SessionId);
diff --git a/QS.Cloud.Client/DataBase/QsCloudScriptsConfiguration.cs b/QS.Cloud.Client/DataBase/QsCloudScriptsConfiguration.cs
index b6345d95a..20c1d0ac2 100644
--- a/QS.Cloud.Client/DataBase/QsCloudScriptsConfiguration.cs
+++ b/QS.Cloud.Client/DataBase/QsCloudScriptsConfiguration.cs
@@ -28,8 +28,8 @@ public UpdateConfiguration MakeUpdateConfiguration() {
var configuration = new UpdateConfiguration();
configuration.AddUpdate(
- new Version(1, 0),
- new Version(1, 0, 1),
+ new Version(1, 7),
+ new Version(1, 7, 1),
"QS.Cloud.Client.Scripts.1.7.sql");
return configuration;
diff --git a/QS.Cloud.Client/QS.Cloud.Client.csproj b/QS.Cloud.Client/QS.Cloud.Client.csproj
index ebee0064c..2ee482c3f 100644
--- a/QS.Cloud.Client/QS.Cloud.Client.csproj
+++ b/QS.Cloud.Client/QS.Cloud.Client.csproj
@@ -30,7 +30,6 @@
-
diff --git a/QS.Launcher.Avalonia/Views/Pages/BaseManagementView.axaml.cs b/QS.Launcher.Avalonia/Views/Pages/BaseManagementView.axaml.cs
index b0bbc5238..e6332d279 100644
--- a/QS.Launcher.Avalonia/Views/Pages/BaseManagementView.axaml.cs
+++ b/QS.Launcher.Avalonia/Views/Pages/BaseManagementView.axaml.cs
@@ -6,7 +6,6 @@ namespace QS.Launcher.Views.Pages;
public partial class BaseManagementView : UserControl {
public BaseManagementView(BaseManagementVM viewModel) {
InitializeComponent();
-
DataContext = viewModel;
}
}
diff --git a/QS.Launcher/ViewModels/MainWindowVM.cs b/QS.Launcher/ViewModels/MainWindowVM.cs
index 34d9d7c4b..6039da0c0 100644
--- a/QS.Launcher/ViewModels/MainWindowVM.cs
+++ b/QS.Launcher/ViewModels/MainWindowVM.cs
@@ -23,7 +23,7 @@ public int SelectedPageIndex {
public int PagesCount {
get => rootPagesCount;
- set { /* кол-во корневых страниц фиксируется в ctor */ }
+ set { }
}
public MainWindowVM(
diff --git a/QS.Launcher/ViewModels/PageViewModels/BaseManagementVM.cs b/QS.Launcher/ViewModels/PageViewModels/BaseManagementVM.cs
index d0d0de5ae..035ef2690 100644
--- a/QS.Launcher/ViewModels/PageViewModels/BaseManagementVM.cs
+++ b/QS.Launcher/ViewModels/PageViewModels/BaseManagementVM.cs
@@ -9,6 +9,7 @@ public class BaseManagementVM : CarouselPageVM {
public DatabaseViewModel SelectedDatabase { get; set; }
public BaseManagementVM() {
+
}
}
}
diff --git a/QS.Launcher/ViewModels/PageViewModels/CarouselPageVM.cs b/QS.Launcher/ViewModels/PageViewModels/CarouselPageVM.cs
index 90524aba9..1d865db8b 100644
--- a/QS.Launcher/ViewModels/PageViewModels/CarouselPageVM.cs
+++ b/QS.Launcher/ViewModels/PageViewModels/CarouselPageVM.cs
@@ -18,12 +18,12 @@ public class CarouselPageVM : ViewModelBase {
public ICommand PushPageCommand { get; set; }
///
- /// Закрыть текущую нерутовую страницу и вернуться на предыдущую.
+ /// Закрыть текущую нерутовую страницу и вернуться на предыдущую
///
public ICommand PopPageCommand { get; set; }
///
- /// Закрыть все нерутовые страницы и вернуться к корневым вкладкам.
+ /// Закрыть все нерутовые страницы и вернуться к корневым вкладкам
///
public ICommand PopToRootCommand { get; set; }
}
diff --git a/QS.Launcher/ViewModels/PageViewModels/DataBase/CreateDataBaseSettingsVM.cs b/QS.Launcher/ViewModels/PageViewModels/DataBase/CreateDataBaseSettingsVM.cs
index d68ab0611..4a813af5b 100644
--- a/QS.Launcher/ViewModels/PageViewModels/DataBase/CreateDataBaseSettingsVM.cs
+++ b/QS.Launcher/ViewModels/PageViewModels/DataBase/CreateDataBaseSettingsVM.cs
@@ -5,9 +5,6 @@
using ReactiveUI;
namespace QS.Launcher.ViewModels.PageViewModels.DataBase {
- ///
- /// По «Далее» создаёт CreateDataBaseProgressVM и пушит её в Carousel поверх текущей
- ///
public class CreateDataBaseSettingsVM : CarouselPageVM {
public IDbProvider Provider { get; private set; }
public Connection Connection { get; private set; }
diff --git a/QS.Updater.Core/DBScripts/Models/MySqlDbCreateModel.cs b/QS.Updater.Core/DBScripts/Models/MySqlDbCreateModel.cs
index 48d5397d7..5d16e3b2e 100644
--- a/QS.Updater.Core/DBScripts/Models/MySqlDbCreateModel.cs
+++ b/QS.Updater.Core/DBScripts/Models/MySqlDbCreateModel.cs
@@ -1,14 +1,10 @@
-using FluentNHibernate.Cfg.Db;
using MySqlConnector;
using QS.DBScripts.Controllers;
using QS.Dialog;
-using QS.Project.DB;
-using QS.Project.Domain;
using System;
using System.Text.RegularExpressions;
using System.Threading;
using System.Threading.Tasks;
-using System.Xml.Linq;
namespace QS.DBScripts.Models
{
From 024eeda11a82fd5b3c9ffb6ddeac5329257e21b2 Mon Sep 17 00:00:00 2001
From: J0shlerB0y <86416124+J0shlerB0y@users.noreply.github.com>
Date: Tue, 12 May 2026 00:13:32 +0300
Subject: [PATCH 13/19] =?UTF-8?q?=D0=BD=D0=B0=D1=81=D1=82=D1=80=D0=BE?=
=?UTF-8?q?=D0=B8=D0=BB=20=D0=BA=D0=BB=D0=B8=D0=B5=D0=BD=D1=82=20=D1=81?=
=?UTF-8?q?=D0=BE=D0=B7=D0=B4=D0=B0=D0=BD=D0=B8=D1=8F=20=D0=B1=D0=B0=D0=B7?=
=?UTF-8?q?=D1=8B=20=D1=87=D0=B5=D1=80=D0=B5=D0=B7=20=D0=BE=D0=B1=D0=BB?=
=?UTF-8?q?=D0=B0=D0=BA=D0=BE?=
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
---
.../Clients/DataBaseManagementCloudClient.cs | 13 ++--
QS.Cloud.Client/DataBase/QSCloudProvider.cs | 13 ++--
QS.Cloud.Client/DataBase/QsCloudDbCreator.cs | 2 +-
QS.DbManagement/IDbProvider.cs | 4 +-
QS.DbManagement/MariaDb/MariaDBProvider.cs | 2 +-
.../Services/LauncherDbCreatorInteraction.cs | 4 +-
.../DataBase/CreateDataBaseProgressVM.cs | 37 ++++++----
.../DataBase/CreateDataBaseSettingsVM.cs | 15 +++-
.../DataBase/DbCreationPhase.cs | 20 ++++++
.../Dialog/AvaloniaInteractiveQuestion.cs | 70 +++++++++++++++++--
.../Dialog/AvaloniaInteractiveService.cs | 10 +++
.../Dialog/IInteractiveQuestion.cs | 21 +++++-
.../DBScripts/Models/MySqlDbCreateModel.cs | 14 ++--
13 files changed, 182 insertions(+), 43 deletions(-)
create mode 100644 QS.Launcher/ViewModels/PageViewModels/DataBase/DbCreationPhase.cs
diff --git a/QS.Cloud.Client/Clients/DataBaseManagementCloudClient.cs b/QS.Cloud.Client/Clients/DataBaseManagementCloudClient.cs
index 9bd26b15b..9579fd835 100644
--- a/QS.Cloud.Client/Clients/DataBaseManagementCloudClient.cs
+++ b/QS.Cloud.Client/Clients/DataBaseManagementCloudClient.cs
@@ -1,15 +1,20 @@
-using System.Threading;
using Grpc.Core;
using QS.Cloud.Core;
+using QS.Project.Versioning;
+using System.Threading;
namespace QS.Cloud.Client.Clients {
public class DataBaseManagementCloudClient : CloudClientByBasicAuth {
+ private IApplicationInfo applicationInfo { get; set; }
public DataBaseManagementCloudClient(IBasicAuthInfoProvider basicAuthInfoProvider)
- : base(basicAuthInfoProvider, "core.cloud.qsolution.ru", 443) { }
+ : base(basicAuthInfoProvider, "core.cloud.qsolution.ru", 443)
+ {
+ this.applicationInfo = applicationInfo;
+ }
- public CreateDataBaseResponse CreateDataBase(string dbName, string dbTitle) {
+ public CreateDataBaseResponse CreateDataBase(string dbName, string dbTitle, IApplicationInfo applicationInfo) {
var client = new DataBaseManagement.DataBaseManagementClient(Channel);
- var request = new CreateDataBaseRequest { Name = dbName, Title = dbTitle };
+ var request = new CreateDataBaseRequest { Name = dbName, Title = dbTitle, ProductId = applicationInfo.ProductCode };
return client.CreateDataBase(request, headers);
}
}
diff --git a/QS.Cloud.Client/DataBase/QSCloudProvider.cs b/QS.Cloud.Client/DataBase/QSCloudProvider.cs
index 9682fb105..c8c2c9738 100644
--- a/QS.Cloud.Client/DataBase/QSCloudProvider.cs
+++ b/QS.Cloud.Client/DataBase/QSCloudProvider.cs
@@ -12,18 +12,16 @@
using QS.Cloud.Client.Clients;
using QS.DBScripts.Controllers;
using System.Threading.Tasks;
+using Microsoft.Extensions.DependencyInjection;
namespace QS.Cloud.Client.DataBase
{
public class QSCloudProvider : IDbProvider {
- ///
- /// Публичный - в типе родключения нужен доступ, реализацию он знает и так
- ///
public int BaseId { get; private set; }
public BasicAuthInfoProvider AuthInfo { get; private set; }
- public bool IsConnected => throw new NotImplementedException();
+ public bool IsConnected { get; private set; }
public bool IsAdmin { get; protected set; }
@@ -33,7 +31,7 @@ public class QSCloudProvider : IDbProvider {
#endregion
public string UserName { get; private set; }
- public bool CanCreateDatabase => throw new NotImplementedException();
+ public bool CanCreateDatabase => dbClient.CanConnect;
private LoginManagementCloudClient loginClient;
private DataBaseManagementCloudClient dbClient;
@@ -58,9 +56,10 @@ public bool ChangePassword(string username, string oldPassword, string newPasswo
throw new NotImplementedException();
}
- public bool CreateDatabase(string databaseName, string title)
+ public bool CreateDatabase(string databaseName, string title, IServiceProvider services)
{
- CreateDataBaseResponse response = dbClient.CreateDataBase(databaseName, title);
+ IApplicationInfo applicationInfo = services.GetService();
+ CreateDataBaseResponse response = dbClient.CreateDataBase(databaseName, title, applicationInfo);
BaseId = response.BaseId;
return true;
}
diff --git a/QS.Cloud.Client/DataBase/QsCloudDbCreator.cs b/QS.Cloud.Client/DataBase/QsCloudDbCreator.cs
index 92cca32d3..7e189244f 100644
--- a/QS.Cloud.Client/DataBase/QsCloudDbCreator.cs
+++ b/QS.Cloud.Client/DataBase/QsCloudDbCreator.cs
@@ -62,7 +62,7 @@ public async Task RunCreationAsync(string dbName, string dbTitle) {
var creator = new MySqlDbCreateModel(session.Db.Server, session.Db.Port, session.Db.Login, session.Db.Password, configuration, progress, interaction, cancellationToken);
creator.FillBaseGuid = false;
- bool success = await creator.RunCreationAsync(dbName, dbTitle);
+ bool success = await creator.RunCreationAsync(session.Db.BaseName, dbTitle);
sessionLife.Dispose();
diff --git a/QS.DbManagement/IDbProvider.cs b/QS.DbManagement/IDbProvider.cs
index d0c6fd3e6..c97d2f142 100644
--- a/QS.DbManagement/IDbProvider.cs
+++ b/QS.DbManagement/IDbProvider.cs
@@ -10,8 +10,8 @@ public interface IDbProvider : IDisposable
string UserName { get; }
bool ChangePassword(string username, string oldPassword, string newPassword);
-
- bool CreateDatabase(string databaseName, string title);
+
+ bool CreateDatabase(string databaseName, string title, IServiceProvider services = null);
bool DropDatabase(string databaseName);
diff --git a/QS.DbManagement/MariaDb/MariaDBProvider.cs b/QS.DbManagement/MariaDb/MariaDBProvider.cs
index 948b225ff..880714f9c 100644
--- a/QS.DbManagement/MariaDb/MariaDBProvider.cs
+++ b/QS.DbManagement/MariaDb/MariaDBProvider.cs
@@ -178,7 +178,7 @@ public bool ChangePassword(string username, string oldPassword, string newPasswo
return connection.Execute(sql) != 0;
}
- public bool CreateDatabase(string databaseName, string title) {
+ public bool CreateDatabase(string databaseName, string title, IServiceProvider services = null) {
CreatedTitle = title;
string sql = $"CREATE DATABASE IF NOT EXISTS `{databaseName}`";
return connection.Execute(sql) != 0;
diff --git a/QS.Launcher/Services/LauncherDbCreatorInteraction.cs b/QS.Launcher/Services/LauncherDbCreatorInteraction.cs
index fa2262cee..e68e95b79 100644
--- a/QS.Launcher/Services/LauncherDbCreatorInteraction.cs
+++ b/QS.Launcher/Services/LauncherDbCreatorInteraction.cs
@@ -26,9 +26,9 @@ public LauncherDbCreatorInteraction(
public Task AskDropExistingDatabaseAsync(string dbName) {
var tcs = new TaskCompletionSource();
- uiThread.Post(() => {
+ uiThread.Post(async () => {
try {
- bool answer = question.Question(
+ bool answer = await question.QuestionAsync(
$"База с именем `{dbName}` уже существует на сервере. Удалить существующую базу перед созданием новой?",
"Создание базы данных");
tcs.TrySetResult(answer);
diff --git a/QS.Launcher/ViewModels/PageViewModels/DataBase/CreateDataBaseProgressVM.cs b/QS.Launcher/ViewModels/PageViewModels/DataBase/CreateDataBaseProgressVM.cs
index 213707c8b..5a59100a0 100644
--- a/QS.Launcher/ViewModels/PageViewModels/DataBase/CreateDataBaseProgressVM.cs
+++ b/QS.Launcher/ViewModels/PageViewModels/DataBase/CreateDataBaseProgressVM.cs
@@ -1,4 +1,6 @@
using System;
+using System.Collections.Generic;
+using System.Linq;
using System.Reactive;
using System.Threading;
using System.Threading.Tasks;
@@ -18,8 +20,7 @@ public class CreateDataBaseProgressVM : CarouselPageVM, IProgressBarDisplayable
public IDbProvider Provider { get; private set; }
public Connection Connection { get; private set; }
- public string DbName { get; private set; }
- public string DbTitle { get; private set; }
+ private IReadOnlyList phases = Array.Empty();
private readonly IDbCreatorInteraction interaction;
private readonly IServiceProvider services;
@@ -83,14 +84,15 @@ public CreateDataBaseProgressVM(
});
}
- public void SetDbSettings(
- string dbName,
- string dbTitle,
- IDbProvider provider, Connection connection) {
- DbName = dbName ?? throw new ArgumentNullException(nameof(dbName));
- DbTitle = dbTitle ?? throw new ArgumentNullException(nameof(dbTitle));
+ public void SetPipeline(
+ IDbProvider provider, Connection connection,
+ IEnumerable phases) {
Provider = provider ?? throw new ArgumentNullException(nameof(provider));
Connection = connection ?? throw new ArgumentNullException(nameof(connection));
+ if(phases == null) throw new ArgumentNullException(nameof(phases));
+ this.phases = phases.ToList();
+ if(this.phases.Count == 0)
+ throw new ArgumentException("Пайплайн создания базы пуст.", nameof(phases));
}
public async Task StartCreationAsync() {
@@ -102,12 +104,19 @@ public async Task StartCreationAsync() {
CancellationToken = cts.Token,
ServiceProvider = services
};
- IDbCreatorModel creator = Connection.ConnectionType.CreateCreator(args);
- bool ok = await creator.RunCreationAsync(DbName, DbTitle);
- if(ok)
- DatabaseCreated?.Invoke();
- else
- DatabaseCreationFailed?.Invoke();
+
+ for(int i = 0; i < phases.Count; i++) {
+ cts.Token.ThrowIfCancellationRequested();
+ var phase = phases[i];
+ uiThread.Post(() => CurrentText = phase.Title);
+
+ bool ok = await phase.Action(args);
+ if(!ok) {
+ DatabaseCreationFailed?.Invoke();
+ return;
+ }
+ }
+ DatabaseCreated?.Invoke();
}
catch(OperationCanceledException) {
logger.Info("Создание базы отменено.");
diff --git a/QS.Launcher/ViewModels/PageViewModels/DataBase/CreateDataBaseSettingsVM.cs b/QS.Launcher/ViewModels/PageViewModels/DataBase/CreateDataBaseSettingsVM.cs
index 4a813af5b..9957dc2f4 100644
--- a/QS.Launcher/ViewModels/PageViewModels/DataBase/CreateDataBaseSettingsVM.cs
+++ b/QS.Launcher/ViewModels/PageViewModels/DataBase/CreateDataBaseSettingsVM.cs
@@ -1,7 +1,9 @@
using System;
using System.Reactive;
using System.Reactive.Linq;
+using System.Threading.Tasks;
using QS.DbManagement;
+using QS.DBScripts.Controllers;
using ReactiveUI;
namespace QS.Launcher.ViewModels.PageViewModels.DataBase {
@@ -50,8 +52,19 @@ private void GoToProgress() {
var progress = Microsoft.Extensions.DependencyInjection.ActivatorUtilities
.GetServiceOrCreateInstance(services);
- progress.SetDbSettings(dbName, dbTitle, Provider, Connection);
+ var pipeline = new[] {
+ new DbCreationPhase(
+ "Создание базы данных",
+ (args) => Task.FromResult(args.Provider.CreateDatabase(DbName, DbTitle, services))),
+ new DbCreationPhase(
+ "Наполнение базы данных",
+ async args => {
+ IDbCreatorModel creator = Connection.ConnectionType.CreateCreator(args);
+ return await creator.RunCreationAsync(DbName, DbTitle);
+ })
+ };
+ progress.SetPipeline(Provider, Connection, pipeline);
ProgressPageRequested?.Invoke(progress);
PushPageCommand?.Execute(progress);
}
diff --git a/QS.Launcher/ViewModels/PageViewModels/DataBase/DbCreationPhase.cs b/QS.Launcher/ViewModels/PageViewModels/DataBase/DbCreationPhase.cs
new file mode 100644
index 000000000..7e9085445
--- /dev/null
+++ b/QS.Launcher/ViewModels/PageViewModels/DataBase/DbCreationPhase.cs
@@ -0,0 +1,20 @@
+using System;
+using System.Threading.Tasks;
+using QS.DbManagement;
+using QS.DBScripts.Controllers;
+
+namespace QS.Launcher.ViewModels.PageViewModels.DataBase {
+ ///
+ /// Один шаг пайплайна создания базы. Settings-VM собирает список таких фаз
+ /// и передаёт его в Progress-VM, который выполняет их последовательно.
+ ///
+ public sealed class DbCreationPhase {
+ public string Title { get; }
+ public Func> Action { get; }
+
+ public DbCreationPhase(string title, Func> action) {
+ Title = title ?? throw new ArgumentNullException(nameof(title));
+ Action = action ?? throw new ArgumentNullException(nameof(action));
+ }
+ }
+}
diff --git a/QS.Project.Avalonia/Dialog/AvaloniaInteractiveQuestion.cs b/QS.Project.Avalonia/Dialog/AvaloniaInteractiveQuestion.cs
index e40799c80..2d97cbf5c 100644
--- a/QS.Project.Avalonia/Dialog/AvaloniaInteractiveQuestion.cs
+++ b/QS.Project.Avalonia/Dialog/AvaloniaInteractiveQuestion.cs
@@ -1,14 +1,76 @@
using System;
+using System.Threading.Tasks;
+using Avalonia;
+using Avalonia.Controls;
+using Avalonia.Controls.ApplicationLifetimes;
+using Avalonia.Threading;
+using QS.Project.Avalonia;
namespace QS.Dialog;
+
public class AvaloniaInteractiveQuestion : IInteractiveQuestion {
+
public bool Question(string message, string title = null) {
- // TODO: Implement Question dialog behaviour to DialogWindow.axaml.cs
- throw new NotImplementedException();
+ if(Dispatcher.UIThread.CheckAccess())
+ throw new InvalidOperationException(
+ "Синхронный Question нельзя вызывать из UI-потока — это приведёт к дедлоку. Используйте QuestionAsync.");
+ return QuestionAsync(message, title).GetAwaiter().GetResult();
}
public string Question(string[] buttons, string message, string title = null) {
- // How to solve answer from several buttons?
- throw new NotImplementedException();
+ if(Dispatcher.UIThread.CheckAccess())
+ throw new InvalidOperationException(
+ "Синхронный Question нельзя вызывать из UI-потока — это приведёт к дедлоку. Используйте QuestionAsync.");
+ return QuestionAsync(buttons, message, title).GetAwaiter().GetResult();
+ }
+
+ public Task QuestionAsync(string message, string title = null) =>
+ Dispatcher.UIThread.InvokeAsync(async () => {
+ var dlg = new DialogWindow(message, title ?? string.Empty, ImportanceLevel.Info);
+ var tcs = new TaskCompletionSource();
+
+ var yes = new Button { Content = "Да" };
+ var no = new Button { Content = "Нет" };
+ yes.Click += (_, _) => { tcs.TrySetResult(true); dlg.Close(); };
+ no.Click += (_, _) => { tcs.TrySetResult(false); dlg.Close(); };
+ dlg.AddButton(yes);
+ dlg.AddButton(no);
+ // Закрытие крестиком / штатной кнопкой "Закрыть" => Нет
+ dlg.Closed += (_, _) => tcs.TrySetResult(false);
+
+ await ShowAsync(dlg);
+ return await tcs.Task;
+ });
+
+ public Task QuestionAsync(string[] buttons, string message, string title = null) =>
+ Dispatcher.UIThread.InvokeAsync(async () => {
+ var dlg = new DialogWindow(message, title ?? string.Empty, ImportanceLevel.Info);
+ var tcs = new TaskCompletionSource();
+
+ foreach(var label in buttons) {
+ var captured = label;
+ var btn = new Button { Content = captured };
+ btn.Click += (_, _) => { tcs.TrySetResult(captured); dlg.Close(); };
+ dlg.AddButton(btn);
+ }
+ // Закрытие крестиком => null
+ dlg.Closed += (_, _) => tcs.TrySetResult(null);
+
+ await ShowAsync(dlg);
+ return await tcs.Task;
+ });
+
+ private static Task ShowAsync(Window dlg) {
+ var owner = GetOwner();
+ if(owner != null && owner != dlg && owner.IsVisible)
+ return dlg.ShowDialog(owner);
+ dlg.Show();
+ return Task.CompletedTask;
+ }
+
+ private static Window GetOwner() {
+ if(Application.Current?.ApplicationLifetime is IClassicDesktopStyleApplicationLifetime desktop)
+ return desktop.MainWindow;
+ return null;
}
}
diff --git a/QS.Project.Avalonia/Dialog/AvaloniaInteractiveService.cs b/QS.Project.Avalonia/Dialog/AvaloniaInteractiveService.cs
index a1fe6409e..9856d4b6f 100644
--- a/QS.Project.Avalonia/Dialog/AvaloniaInteractiveService.cs
+++ b/QS.Project.Avalonia/Dialog/AvaloniaInteractiveService.cs
@@ -1,3 +1,5 @@
+using System.Threading.Tasks;
+
namespace QS.Dialog;
public class AvaloniaInteractiveService(AvaloniaInteractiveMessage interactiveMessage, AvaloniaInteractiveQuestion interactiveQuestion) : IInteractiveService {
@@ -9,6 +11,14 @@ public string Question(string[] buttons, string message, string title = null) {
return interactiveQuestion.Question(buttons, message, title);
}
+ public Task QuestionAsync(string message, string title = null) {
+ throw new System.NotImplementedException();
+ }
+
+ public Task QuestionAsync(string[] buttons, string message, string title = null) {
+ throw new System.NotImplementedException();
+ }
+
public void ShowMessage(ImportanceLevel level, string message, string title = null) {
interactiveMessage.ShowMessage(level, message, title);
}
diff --git a/QS.Project.Core/Dialog/IInteractiveQuestion.cs b/QS.Project.Core/Dialog/IInteractiveQuestion.cs
index c471a6d3e..49f9c698d 100644
--- a/QS.Project.Core/Dialog/IInteractiveQuestion.cs
+++ b/QS.Project.Core/Dialog/IInteractiveQuestion.cs
@@ -1,4 +1,6 @@
-namespace QS.Dialog
+using System.Threading.Tasks;
+
+namespace QS.Dialog
{
public interface IInteractiveQuestion
{
@@ -18,5 +20,22 @@ public interface IInteractiveQuestion
/// Заголовок окна диалога
/// Вернет заголовок кнопки которую нажал пользователь. Если пользователь закроет диалог крестиком, вернется null.
string Question(string[] buttons, string message, string title = null);
+
+ ///
+ /// Отобразит диалог с вопросом пользователю и кнопками Да Нет.
+ ///
+ /// Сообщение диалога
+ /// Заголовок окна диалога
+ /// True - Да, False - Нет(или закрытие крестиком)
+ Task QuestionAsync(string message, string title = null);
+
+ ///
+ /// Отобразит диалог с вопросом пользователю.
+ ///
+ /// Список заголовков для кнопок
+ /// Сообщение диалога
+ /// Заголовок окна диалога
+ /// Вернет заголовок кнопки которую нажал пользователь. Если пользователь закроет диалог крестиком, вернется null.
+ Task QuestionAsync(string[] buttons, string message, string title = null);
}
}
diff --git a/QS.Updater.Core/DBScripts/Models/MySqlDbCreateModel.cs b/QS.Updater.Core/DBScripts/Models/MySqlDbCreateModel.cs
index 5d16e3b2e..fb4a00696 100644
--- a/QS.Updater.Core/DBScripts/Models/MySqlDbCreateModel.cs
+++ b/QS.Updater.Core/DBScripts/Models/MySqlDbCreateModel.cs
@@ -74,15 +74,15 @@ public bool RunCreation(string dbName, string dbTitle = null) {
var sql = "SHOW DATABASES;";
var cmd = new MySqlCommand(sql, connectionDB);
bool needDropBase = false;
+ bool hasBase = false;
using(var rdr = cmd.ExecuteReader()) {
while(rdr.Read()) {
if(rdr[0].ToString() == dbName) {
if(interaction.AskDropExistingDatabaseAsync(dbName).GetAwaiter().GetResult()) {
needDropBase = true;
- break;
}
- else
- return false;
+ hasBase = true;
+ break;
}
}
}
@@ -104,9 +104,11 @@ public bool RunCreation(string dbName, string dbTitle = null) {
cmd.ExecuteNonQuery();
}
- progress.Add(text: $"Создаем базу {dbName}");
- cmd.CommandText = String.Format("CREATE SCHEMA `{0}` DEFAULT CHARACTER SET utf8mb4 ;", dbName);
- cmd.ExecuteNonQuery();
+ if(!hasBase || needDropBase) {
+ progress.Add(text: $"Создаем базу {dbName}");
+ cmd.CommandText = String.Format("CREATE SCHEMA `{0}` DEFAULT CHARACTER SET utf8mb4 ;", dbName);
+ cmd.ExecuteNonQuery();
+ }
cmd.CommandText = String.Format("USE `{0}` ;", dbName);
cmd.ExecuteNonQuery();
From 0dc4147d6b1c01d79f9eb9a84bc2174d410b610f Mon Sep 17 00:00:00 2001
From: J0shlerB0y <86416124+J0shlerB0y@users.noreply.github.com>
Date: Thu, 14 May 2026 18:05:34 +0300
Subject: [PATCH 14/19] =?UTF-8?q?=D1=81=D0=B4=D0=B5=D0=BB=D0=B0=D0=BB=20?=
=?UTF-8?q?=D0=B0=D0=B4=D0=B0=D0=BF=D1=82=D0=B5=D1=80=20=D0=B4=D0=BB=D1=8F?=
=?UTF-8?q?=20=D1=81=D1=82=D1=80=D0=BE=D0=B3=D0=BE=20=D0=BA=D0=BE=D0=B4?=
=?UTF-8?q?=D0=B0=20=D0=B8=20=D0=BF=D0=B5=D1=80=D0=B5=D1=80=D0=B0=D0=B1?=
=?UTF-8?q?=D1=82=D0=B0=D0=BB=20=D0=BF=D0=BE=D0=B4=20=D0=BD=D0=BE=D0=B2?=
=?UTF-8?q?=D1=8B=D0=B9=20=D0=B8=D0=BD=D1=82=D0=B5=D1=80=D1=84=D0=B5=D0=B9?=
=?UTF-8?q?=D1=81=20=D0=B2=D0=B7=D0=B0=D0=B8=D0=BC=D0=BE=D0=B4=D0=B5=D0=B9?=
=?UTF-8?q?=D1=81=D1=82=D0=B2=D0=B8=D1=8F?=
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
Адаптер, превращающий пару (CreationScript, UpdateConfiguration), которые исторически регистрируются в Autofac-контейнере проекта-потребителя как AsSelf, в реализацию IDbScriptsConfiguration. Нужен для GTK-десктоп-флоу: UpdaterDBAutofacModule → UserCreateDbController → MySqlDbCreateModel требуют IDbScriptsConfiguration, а сами приложения по-прежнему поставляют только CreationScript/UpdateConfiguration
Адаптер избавляет их от необходимости менять регистрацию контейнера
UpdateConfiguration опционален — если приложение его не регистрирует, отдаём пустой
Конструирую MySqlDbCreateModel в UserCreateDbController явно — без Autofac TypedParameter, которым раньше прокидывался IDbCreateController. Теперь модель получает все сессионные зависимости в свой ctor.
---
.../GtkInteractiveService.cs | 9 ++
.../Interactive/ConsoleInteractiveQuestion.cs | 11 ++-
.../Interactive/ConsoleInteractiveService.cs | 9 ++
.../DbScriptsConfigurationAdapter.cs | 24 +++++
QS.Updater.DB.Gtk/UpdaterDBAutofacModule.cs | 13 ++-
.../Controllers/UserCreateDbController.cs | 92 ++++++++++++++-----
6 files changed, 134 insertions(+), 24 deletions(-)
create mode 100644 QS.Updater.Core/DBScripts/DbScriptsConfigurationAdapter.cs
diff --git a/QS.Project.Gtk/Project.Services.GtkUI/GtkInteractiveService.cs b/QS.Project.Gtk/Project.Services.GtkUI/GtkInteractiveService.cs
index ccafcd490..e44d53802 100644
--- a/QS.Project.Gtk/Project.Services.GtkUI/GtkInteractiveService.cs
+++ b/QS.Project.Gtk/Project.Services.GtkUI/GtkInteractiveService.cs
@@ -1,4 +1,5 @@
using QS.Dialog;
+using System.Threading.Tasks;
namespace QS.Project.Services.GtkUI {
public class GtkInteractiveService : IInteractiveService
@@ -25,5 +26,13 @@ public string Question(string[] buttons, string message, string title = null)
{
return interactiveQuestion.Question(buttons, message, title);
}
+
+ public Task QuestionAsync(string message, string title = null) {
+ throw new System.NotImplementedException();
+ }
+
+ public Task QuestionAsync(string[] buttons, string message, string title = null) {
+ throw new System.NotImplementedException();
+ }
}
}
diff --git a/QS.Project/Project.Services/Interactive/ConsoleInteractiveQuestion.cs b/QS.Project/Project.Services/Interactive/ConsoleInteractiveQuestion.cs
index c872e1a2e..3ca6bc1f2 100644
--- a/QS.Project/Project.Services/Interactive/ConsoleInteractiveQuestion.cs
+++ b/QS.Project/Project.Services/Interactive/ConsoleInteractiveQuestion.cs
@@ -1,6 +1,7 @@
-using System;
+using System;
using QS.Dialog;
using System.Linq;
+using System.Threading.Tasks;
namespace QS.Project.Services.Interactive
{
public class ConsoleInteractiveQuestion : IInteractiveQuestion
@@ -36,5 +37,13 @@ public string Question(string[] buttons, string message, string title = null)
}
return null;
}
+
+ public Task QuestionAsync(string message, string title = null) {
+ throw new NotImplementedException();
+ }
+
+ public Task QuestionAsync(string[] buttons, string message, string title = null) {
+ throw new NotImplementedException();
+ }
}
}
diff --git a/QS.Project/Project.Services/Interactive/ConsoleInteractiveService.cs b/QS.Project/Project.Services/Interactive/ConsoleInteractiveService.cs
index 2158bdd89..8f3b69424 100644
--- a/QS.Project/Project.Services/Interactive/ConsoleInteractiveService.cs
+++ b/QS.Project/Project.Services/Interactive/ConsoleInteractiveService.cs
@@ -1,4 +1,5 @@
using QS.Dialog;
+using System.Threading.Tasks;
namespace QS.Project.Services.Interactive
{
@@ -27,5 +28,13 @@ public string Question(string[] buttons, string message, string title = null)
{
return interactiveQuestion.Question(buttons, message, title);
}
+
+ public Task QuestionAsync(string message, string title = null) {
+ throw new System.NotImplementedException();
+ }
+
+ public Task QuestionAsync(string[] buttons, string message, string title = null) {
+ throw new System.NotImplementedException();
+ }
}
}
diff --git a/QS.Updater.Core/DBScripts/DbScriptsConfigurationAdapter.cs b/QS.Updater.Core/DBScripts/DbScriptsConfigurationAdapter.cs
new file mode 100644
index 000000000..905a75275
--- /dev/null
+++ b/QS.Updater.Core/DBScripts/DbScriptsConfigurationAdapter.cs
@@ -0,0 +1,24 @@
+using System;
+using QS.DBScripts.Models;
+using QS.Updater.DB;
+
+namespace QS.DBScripts
+{
+ public class DbScriptsConfigurationAdapter : IDbScriptsConfiguration
+ {
+ private readonly CreationScript creationScript;
+ private readonly UpdateConfiguration updateConfiguration;
+
+ public DbScriptsConfigurationAdapter(CreationScript creationScript, UpdateConfiguration updateConfiguration = null)
+ {
+ this.creationScript = creationScript ?? throw new ArgumentNullException(nameof(creationScript));
+ this.updateConfiguration = updateConfiguration;
+ }
+
+ public bool HasCreationScript() => creationScript != null;
+
+ public CreationScript MakeCreationScript() => creationScript;
+
+ public UpdateConfiguration MakeUpdateConfiguration() => updateConfiguration ?? new UpdateConfiguration();
+ }
+}
diff --git a/QS.Updater.DB.Gtk/UpdaterDBAutofacModule.cs b/QS.Updater.DB.Gtk/UpdaterDBAutofacModule.cs
index 5bfe0afab..971241817 100644
--- a/QS.Updater.DB.Gtk/UpdaterDBAutofacModule.cs
+++ b/QS.Updater.DB.Gtk/UpdaterDBAutofacModule.cs
@@ -1,4 +1,5 @@
-using Autofac;
+using Autofac;
+using QS.DBScripts;
using QS.DBScripts.Controllers;
using QS.DBScripts.Models;
using QS.Updater.DB;
@@ -19,6 +20,16 @@ protected override void Load(ContainerBuilder builder)
#region Desktop
builder.RegisterType().As();
#endregion
+ builder.Register(c => {
+ var creation = c.Resolve();
+ UpdateConfiguration updates = null;
+ if(c.IsRegistered())
+ updates = c.Resolve();
+ return new DbScriptsConfigurationAdapter(creation, updates);
+ })
+ .As()
+ .SingleInstance()
+ .PreserveExistingDefaults();
#region ViewModels
builder.RegisterAssemblyTypes(System.Reflection.Assembly.GetAssembly(typeof(UpdateProcessViewModel)))
.Where(t => t.IsAssignableTo() && t.Name.EndsWith("ViewModel"))
diff --git a/QS.Updater.DB/DBScripts/Controllers/UserCreateDbController.cs b/QS.Updater.DB/DBScripts/Controllers/UserCreateDbController.cs
index ebfc35f36..531b8d3ce 100644
--- a/QS.Updater.DB/DBScripts/Controllers/UserCreateDbController.cs
+++ b/QS.Updater.DB/DBScripts/Controllers/UserCreateDbController.cs
@@ -1,5 +1,6 @@
using System;
-using Autofac;
+using System.Threading;
+using System.Threading.Tasks;
using QS.DBScripts.Models;
using QS.DBScripts.ViewModels;
using QS.Dialog;
@@ -8,22 +9,25 @@
namespace QS.DBScripts.Controllers
{
- public class UserCreateDbController : IDBCreator, IDbCreateController
+ public class UserCreateDbController : IDBCreator, IDbCreatorInteraction
{
static NLog.Logger logger = NLog.LogManager.GetCurrentClassLogger();
private readonly INavigationManager navigation;
- private readonly ILifetimeScope autofacScope;
private readonly IInteractiveService interactive;
private readonly IGuiDispatcher guiDispatcher;
+ private readonly IDbScriptsConfiguration scripts;
- public UserCreateDbController(INavigationManager navigation, ILifetimeScope autofacScope, IInteractiveService interactive, IGuiDispatcher guiDispatcher)
+ public UserCreateDbController(
+ INavigationManager navigation,
+ IInteractiveService interactive,
+ IGuiDispatcher guiDispatcher,
+ IDbScriptsConfiguration scripts)
{
-
this.navigation = navigation ?? throw new ArgumentNullException(nameof(navigation));
- this.autofacScope = autofacScope ?? throw new ArgumentNullException(nameof(autofacScope));
this.interactive = interactive ?? throw new ArgumentNullException(nameof(interactive));
this.guiDispatcher = guiDispatcher ?? throw new ArgumentNullException(nameof(guiDispatcher));
+ this.scripts = scripts ?? throw new ArgumentNullException(nameof(scripts));
}
public void RunCreation(string server, string dbname)
@@ -37,31 +41,76 @@ public void RunCreation(string server, string dbname)
};
}
- void StartCreation(string server, string dbname, string login, string password)
+ async void StartCreation(string server, string dbname, string login, string password)
{
- var createModel = autofacScope.Resolve(new TypedParameter(typeof(IDbCreateController), this));
+ ParseServer(server, out string host, out uint port);
+
+ bool success = false;
try {
- bool success = createModel.RunCreation(server, dbname, login, password);
- if (success)
- interactive.ShowMessage(ImportanceLevel.Info, "Создание базы успешно завершено.\nЗайдите в программу под администратором для добавления пользователей.");
+ var createModel = new MySqlDbCreateModel(
+ host, port, login, password,
+ scripts,
+ Progress,
+ interaction: this,
+ cancellationToken: CancellationToken.None);
+
+ success = await createModel.RunCreationAsync(dbname, dbTitle: null);
+ }
+ catch(Exception ex) {
+ logger.Error(ex, "Ошибка создания базы.");
+ guiDispatcher.RunInGuiTread(() => interactive.ShowMessage(ImportanceLevel.Error, ex.Message));
}
finally {
- if(progressPage != null)
- navigation.ForceClosePage(progressPage, CloseSource.FromParentPage);
+ guiDispatcher.RunInGuiTread(() => {
+ if(progressPage != null)
+ navigation.ForceClosePage(progressPage, CloseSource.FromParentPage);
+ });
+ }
+
+ if(success) {
+ guiDispatcher.RunInGuiTread(() =>
+ interactive.ShowMessage(ImportanceLevel.Info,
+ "Создание базы успешно завершено.\nЗайдите в программу под администратором для добавления пользователей."));
}
}
- #region Взаимодействие с моделью
- public void WasError(string text, string lastSqlCommand)
- {
- interactive.ShowMessage(ImportanceLevel.Error, text);
+ private static void ParseServer(string server, out string host, out uint port) {
+ port = 3306;
+ var parts = (server ?? string.Empty).Split(new[] { ':' }, 2, StringSplitOptions.RemoveEmptyEntries);
+ if(parts.Length == 0)
+ throw new InvalidOperationException("Имя сервера не корректно.");
+ host = parts[0];
+ if(parts.Length > 1)
+ uint.TryParse(parts[1], out port);
}
- public bool NeedDropDatabaseIfExists(string dbname)
- {
- return interactive.Question($"База с именем `{dbname}` уже существует на сервере. Удалить существующую базу перед созданием новой?");
+ #region IDbCreatorInteraction
+
+ public Task AskDropExistingDatabaseAsync(string dbName) {
+ var tcs = new TaskCompletionSource();
+ guiDispatcher.RunInGuiTread(() => {
+ try {
+ tcs.SetResult(interactive.Question(
+ $"База с именем `{dbName}` уже существует на сервере. Удалить существующую базу перед созданием новой?"));
+ }
+ catch(Exception ex) { tcs.SetException(ex); }
+ });
+ return tcs.Task;
}
+ public Task ReportErrorAsync(string text, string lastExecutedStatement) {
+ var tcs = new TaskCompletionSource();
+ guiDispatcher.RunInGuiTread(() => {
+ try {
+ interactive.ShowMessage(ImportanceLevel.Error, text);
+ tcs.SetResult(true);
+ }
+ catch(Exception ex) { tcs.SetException(ex); }
+ });
+ return tcs.Task;
+ }
+ #endregion
+
#region Свойства процесса
IPage progressPage;
@@ -76,6 +125,5 @@ public IProgressBarDisplayable Progress {
}
}
#endregion
- #endregion
}
-}
\ No newline at end of file
+}
From 2611bf32c8178c3ab23fa72d5813d3b8552b4de3 Mon Sep 17 00:00:00 2001
From: J0shlerB0y <86416124+J0shlerB0y@users.noreply.github.com>
Date: Wed, 10 Jun 2026 20:56:32 +0300
Subject: [PATCH 15/19] =?UTF-8?q?=D0=B4=D0=BE=D1=80=D0=B0=D0=B1=D0=BE?=
=?UTF-8?q?=D1=82=D0=BA=D0=B8=20=D0=BF=D0=BE=20=D1=80=D0=B5=D0=B2=D1=8C?=
=?UTF-8?q?=D1=8E?=
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
убрана многопоточность и асинхронность, настроена конфигурация наличия возможности создать базу и мелкие доработки
---
.../Clients/Base/CloudClientByBasicAuth.cs | 19 ++++--
.../DataBase/QsCloudConnectionTypeBase.cs | 26 ++++----
QS.Cloud.Client/DataBase/QsCloudDbCreator.cs | 33 +++++-----
.../DataBase/QsCloudScriptsConfiguration.cs | 2 +-
QS.DbManagement/ConnectionTypeBase.cs | 23 +++++--
.../DbCreationPhase.cs | 9 +--
QS.DbManagement/MariaDb/MariaDBProvider.cs | 3 +-
.../MariaDb/MariaDbConnectionTypeBase.cs | 27 ++++----
QS.Launcher.Avalonia/DependencyInjection.cs | 4 +-
.../Services/AvaloniaUiThreadInvoker.cs | 12 ----
.../Views/Pages/DataBase/DataBasesView.axaml | 2 +-
.../Pages/DataBase/DataBasesView.axaml.cs | 13 ++--
QS.Launcher/QS.Launcher.csproj | 1 -
QS.Launcher/Services/IUiThreadInvoker.cs | 11 ----
.../Services/LauncherDbCreatorInteraction.cs | 39 +++---------
.../DataBase/CreateDataBaseProgressVM.cs | 62 +++++++++----------
.../DataBase/CreateDataBaseSettingsVM.cs | 7 +--
.../PageViewModels/DataBase/DataBasesVM.cs | 17 ++++-
QS.LibsTest.Core/Launcher/ConfiguratorTest.cs | 6 +-
.../Controllers/IDbCreatorInteraction.cs | 6 +-
.../DBScripts/Controllers/IDbCreatorModel.cs | 8 +--
.../Dialog/IInteractiveQuestion.cs | 17 -----
.../GtkInteractiveService.cs | 8 ---
.../Interactive/ConsoleInteractiveQuestion.cs | 8 ---
.../Interactive/ConsoleInteractiveService.cs | 9 ---
.../DBScripts/Models/MySqlDbCreateModel.cs | 13 +---
26 files changed, 156 insertions(+), 229 deletions(-)
rename {QS.Launcher/ViewModels/PageViewModels/DataBase => QS.DbManagement}/DbCreationPhase.cs (59%)
delete mode 100644 QS.Launcher.Avalonia/Services/AvaloniaUiThreadInvoker.cs
delete mode 100644 QS.Launcher/Services/IUiThreadInvoker.cs
diff --git a/QS.Cloud.Client/Clients/Base/CloudClientByBasicAuth.cs b/QS.Cloud.Client/Clients/Base/CloudClientByBasicAuth.cs
index d7babc3d9..f6aa9ab06 100644
--- a/QS.Cloud.Client/Clients/Base/CloudClientByBasicAuth.cs
+++ b/QS.Cloud.Client/Clients/Base/CloudClientByBasicAuth.cs
@@ -4,19 +4,26 @@
namespace QS.Cloud.Client
{
- public class CloudClientByBasicAuth : CloudClientServiceBase
- {
+ public class CloudClientByBasicAuth : CloudClientServiceBase {
public CloudClientByBasicAuth(IBasicAuthInfoProvider basicAuthInfoProvider, string serviceAddress, int servicePort)
- : base(serviceAddress, servicePort)
- {
+ : base(serviceAddress, servicePort) {
headers = new Metadata
{ {
"Authorization",
$"Basic {Convert.ToBase64String(Encoding.UTF8.GetBytes($"{basicAuthInfoProvider.UserName}:{basicAuthInfoProvider.Password}"))}"
} };
-
+
}
- public override bool CanConnect => throw new NotImplementedException();
+ public override bool CanConnect { get {
+ try {
+ Channel.ConnectAsync().GetAwaiter().GetResult();
+ }
+ catch {
+ return false;
+ }
+ return true;
+ }
+ }
}
}
diff --git a/QS.Cloud.Client/DataBase/QsCloudConnectionTypeBase.cs b/QS.Cloud.Client/DataBase/QsCloudConnectionTypeBase.cs
index bf0553cb2..cb206c682 100644
--- a/QS.Cloud.Client/DataBase/QsCloudConnectionTypeBase.cs
+++ b/QS.Cloud.Client/DataBase/QsCloudConnectionTypeBase.cs
@@ -4,6 +4,7 @@
using QS.DBScripts.Controllers;
using QS.DBScripts.Models;
using QS.Utilities.Extensions;
+using System;
using System.Collections.Generic;
using System.Linq;
using System.Reflection;
@@ -19,6 +20,18 @@ public QsCloudConnectionTypeBase() {
Parameters.Add(new ConnectionParameter("Login","Пользователь"));
IconBytes = Assembly.GetExecutingAssembly().GetResourceByteArray("QS.Cloud.Client.Icons.qscloud.ico");
+
+ CreatorFactory = args => {
+ var provider = (QSCloudProvider)args.Provider;
+ var scripts = args.ServiceProvider.GetRequiredService();
+ return new QsCloudDbCreator(
+ provider.BaseId,
+ provider.AuthInfo,
+ scripts,
+ args.Progress,
+ args.Interaction,
+ args.CancellationToken);
+ };
}
public override bool CanConnect(IEnumerable parameters) {
@@ -29,18 +42,5 @@ public override bool CanConnect(IEnumerable parameters
public override IDbProvider CreateProvider(IList parameters, string password = null) {
return new QSCloudProvider(parameters, password);
}
-
- public override IDbCreatorModel CreatorFactory(CreatorFactoryArgs args) {
- var provider = (QSCloudProvider)args.Provider;
- var scripts = args.ServiceProvider.GetRequiredService();
- var creator = new QsCloudDbCreator(
- provider.BaseId,
- provider.AuthInfo,
- scripts,
- args.Progress,
- args.Interaction,
- args.CancellationToken);
- return creator;
- }
}
}
diff --git a/QS.Cloud.Client/DataBase/QsCloudDbCreator.cs b/QS.Cloud.Client/DataBase/QsCloudDbCreator.cs
index 7e189244f..a11307579 100644
--- a/QS.Cloud.Client/DataBase/QsCloudDbCreator.cs
+++ b/QS.Cloud.Client/DataBase/QsCloudDbCreator.cs
@@ -5,7 +5,6 @@
using QS.Dialog;
using System;
using System.Threading;
-using System.Threading.Tasks;
namespace QS.Cloud.Client.DataBase
{
@@ -14,7 +13,6 @@ public class QsCloudDbCreator : IDbCreatorModel
static NLog.Logger logger = NLog.LogManager.GetCurrentClassLogger();
private readonly int baseId;
-
private readonly IProgressBarDisplayable progress;
private readonly IDbCreatorInteraction interaction;
private readonly IDbScriptsConfiguration configuration;
@@ -24,33 +22,33 @@ public class QsCloudDbCreator : IDbCreatorModel
public QsCloudDbCreator(
int baseId,
- BasicAuthInfoProvider AuthInfo,
+ BasicAuthInfoProvider authInfo,
IDbScriptsConfiguration configuration,
IProgressBarDisplayable progress,
IDbCreatorInteraction interaction,
CancellationToken cancellationToken)
{
this.baseId = baseId;
-
- loginClient = new LoginManagementCloudClient(AuthInfo);
-
- this.configuration = configuration ?? throw new ArgumentNullException(nameof(progress));
+ loginClient = new LoginManagementCloudClient(authInfo);
+ this.configuration = configuration ?? throw new ArgumentNullException(nameof(configuration));
this.progress = progress ?? throw new ArgumentNullException(nameof(progress));
this.interaction = interaction ?? throw new ArgumentNullException(nameof(interaction));
this.cancellationToken = cancellationToken;
}
-
- public async Task RunCreationAsync(string dbName, string dbTitle) {
+ public bool RunCreation(string dbName, string dbTitle) {
try {
+ cancellationToken.ThrowIfCancellationRequested();
+
StartSessionResponse session = loginClient.StartSession(baseId);
if(!session.Success) {
- await interaction.ReportErrorAsync("Ошибка в создании сесии", "Запрос в облако");
- throw new InvalidOperationException("Ошибка в создании сесии");
+ interaction.ReportError("Ошибка в создании сессии", "Запрос в облако");
+ return false;
}
- else if(!session.IsAdmin) {
- await interaction.ReportErrorAsync("Вы не имеете прав Администратора", "Запрос в облако");
+ if(!session.IsAdmin) {
+ interaction.ReportError("Вы не имеете прав Администратора", "Запрос в облако");
+ return false;
}
var infoProvider = new SessionInfoProvider(sessionId: session.SessionId);
@@ -60,12 +58,13 @@ public async Task RunCreationAsync(string dbName, string dbTitle) {
};
sessionLife.KeepAlive();
- var creator = new MySqlDbCreateModel(session.Db.Server, session.Db.Port, session.Db.Login, session.Db.Password, configuration, progress, interaction, cancellationToken);
+ var creator = new MySqlDbCreateModel(
+ session.Db.Server, session.Db.Port, session.Db.Login, session.Db.Password,
+ configuration, progress, interaction, cancellationToken);
creator.FillBaseGuid = false;
- bool success = await creator.RunCreationAsync(session.Db.BaseName, dbTitle);
+ bool success = creator.RunCreation(session.Db.BaseName, dbTitle);
sessionLife.Dispose();
-
return success;
}
catch(OperationCanceledException) {
@@ -74,7 +73,7 @@ public async Task RunCreationAsync(string dbName, string dbTitle) {
}
catch(Exception ex) {
logger.Error(ex, "Ошибка при создании базы в облаке.");
- await interaction.ReportErrorAsync(ex.Message, null);
+ interaction.ReportError(ex.Message, null);
throw;
}
finally {
diff --git a/QS.Cloud.Client/DataBase/QsCloudScriptsConfiguration.cs b/QS.Cloud.Client/DataBase/QsCloudScriptsConfiguration.cs
index 20c1d0ac2..163e42421 100644
--- a/QS.Cloud.Client/DataBase/QsCloudScriptsConfiguration.cs
+++ b/QS.Cloud.Client/DataBase/QsCloudScriptsConfiguration.cs
@@ -12,7 +12,7 @@ public class QsCloudScriptsConfiguration : IDbScriptsConfiguration {
private string ResourceName = "QS.Cloud.Client.Scripts.new_empty.sql";
public bool HasCreationScript() {
return Assembly.GetAssembly(typeof(QsCloudScriptsConfiguration))
- .GetReferencedAssemblies().Select(x => x.FullName)
+ .GetManifestResourceNames()
.Contains(ResourceName);
}
diff --git a/QS.DbManagement/ConnectionTypeBase.cs b/QS.DbManagement/ConnectionTypeBase.cs
index c3caca35c..82ea666c5 100644
--- a/QS.DbManagement/ConnectionTypeBase.cs
+++ b/QS.DbManagement/ConnectionTypeBase.cs
@@ -1,7 +1,9 @@
-using System;
-using System.Collections.Generic;
+using Microsoft.Extensions.DependencyInjection;
+using QS.DBScripts;
using QS.DBScripts.Controllers;
using QS.Dialog;
+using System;
+using System.Collections.Generic;
namespace QS.DbManagement {
@@ -17,16 +19,29 @@ public abstract class ConnectionTypeBase {
public abstract IDbProvider CreateProvider(IList parameters, string password = null);
- public abstract IDbCreatorModel CreatorFactory(CreatorFactoryArgs args);
+ public Func CreatorFactory { get; set; }
+
+ ///
+ /// Создание базы доступно, только если задана фабрика и приложение
+ /// зарегистрировало конфигурацию скриптов с реальным скриптом создания
+ ///
+ public virtual bool SupportsDatabaseCreation(IServiceProvider services) {
+ return CreatorFactory != null
+ && services.GetService()?.HasCreationScript() == true;
+ }
public IDbCreatorModel CreateCreator(CreatorFactoryArgs args) {
+ if(CreatorFactory == null)
+ throw new InvalidOperationException(
+ $"Для типа подключения '{ConnectionTypeName}' не настроена фабрика создания БД (CreatorFactory). "
+ + "Заполните её в композиционном корне приложения.");
return CreatorFactory(args);
}
}
///
/// interaction — канал диалогов с пользователем
- /// serviceProvider — для резолва дополнительных зависимостей
+ /// serviceProvider — для получения дополнительных зависимостей
///
public class CreatorFactoryArgs {
public IDbProvider Provider { get; set; }
diff --git a/QS.Launcher/ViewModels/PageViewModels/DataBase/DbCreationPhase.cs b/QS.DbManagement/DbCreationPhase.cs
similarity index 59%
rename from QS.Launcher/ViewModels/PageViewModels/DataBase/DbCreationPhase.cs
rename to QS.DbManagement/DbCreationPhase.cs
index 7e9085445..0c70e4528 100644
--- a/QS.Launcher/ViewModels/PageViewModels/DataBase/DbCreationPhase.cs
+++ b/QS.DbManagement/DbCreationPhase.cs
@@ -1,18 +1,15 @@
using System;
-using System.Threading.Tasks;
using QS.DbManagement;
-using QS.DBScripts.Controllers;
namespace QS.Launcher.ViewModels.PageViewModels.DataBase {
///
- /// Один шаг пайплайна создания базы. Settings-VM собирает список таких фаз
- /// и передаёт его в Progress-VM, который выполняет их последовательно.
+ /// Один шаг пайплайна создания базы
///
public sealed class DbCreationPhase {
public string Title { get; }
- public Func> Action { get; }
+ public Func Action { get; }
- public DbCreationPhase(string title, Func> action) {
+ public DbCreationPhase(string title, Func action) {
Title = title ?? throw new ArgumentNullException(nameof(title));
Action = action ?? throw new ArgumentNullException(nameof(action));
}
diff --git a/QS.DbManagement/MariaDb/MariaDBProvider.cs b/QS.DbManagement/MariaDb/MariaDBProvider.cs
index 880714f9c..7565a4b0d 100644
--- a/QS.DbManagement/MariaDb/MariaDBProvider.cs
+++ b/QS.DbManagement/MariaDb/MariaDBProvider.cs
@@ -1,5 +1,4 @@
using Dapper;
-using FluentNHibernate.Cfg.Db;
using MySqlConnector;
using QS.DbManagement.Responces;
using QS.Project.Versioning;
@@ -29,7 +28,7 @@ public class MariaDBProvider : IDbProvider {
public bool CanCreateDatabase { get; private set; }
///
- /// Переданный в тайтл созданой базы,
+ /// Переданный в тайтл созданой базы,
/// нужен потом при применения скрипта с наполнением базы
///
public string CreatedTitle { get; private set; }
diff --git a/QS.DbManagement/MariaDb/MariaDbConnectionTypeBase.cs b/QS.DbManagement/MariaDb/MariaDbConnectionTypeBase.cs
index 29b4805a3..47dd17c1e 100644
--- a/QS.DbManagement/MariaDb/MariaDbConnectionTypeBase.cs
+++ b/QS.DbManagement/MariaDb/MariaDbConnectionTypeBase.cs
@@ -1,8 +1,8 @@
using Microsoft.Extensions.DependencyInjection;
using QS.DBScripts;
-using QS.DBScripts.Controllers;
using QS.DBScripts.Models;
using QS.Utilities.Extensions;
+using System;
using System.Collections.Generic;
using System.Linq;
using System.Reflection;
@@ -17,6 +17,16 @@ public MariaDbConnectionTypeBase() {
Parameters.Add(new ConnectionParameter("Server", "Адрес сервера"));
Parameters.Add(new ConnectionParameter("Login", "Пользователь"));
IconBytes = Assembly.GetExecutingAssembly().GetResourceByteArray("QS.DbManagement.Assets.mariadb.ico");
+
+
+ CreatorFactory = args => {
+ var p = (MariaDBProvider)args.Provider;
+ var scripts = args.ServiceProvider.GetRequiredService();
+ return new MySqlDbCreateModel(
+ p.ConnectionStringBuilder.ConnectionString,
+ scripts, args.Progress, args.Interaction, args.CancellationToken) { FillBaseGuid = false };
+ };
+
}
public override bool CanConnect(IEnumerable parameters) {
@@ -24,20 +34,7 @@ public override bool CanConnect(IEnumerable parameters
parameters.Any(p => p.Name == "Login" && !string.IsNullOrEmpty(p.Value));
}
- public override IDbCreatorModel CreatorFactory(CreatorFactoryArgs args){
- var provider = (MariaDBProvider)args.Provider;
- var scripts = args.ServiceProvider.GetRequiredService();
- var creator = new MySqlDbCreateModel(
- provider.ConnectionStringBuilder.ConnectionString,
- scripts,
- args.Progress,
- args.Interaction,
- args.CancellationToken);
- creator.FillBaseGuid = false;
- return creator;
- }
-
- public override IDbProvider CreateProvider(IList parameters, string password = null)
+ public override IDbProvider CreateProvider(IList parameters, string password = null)
=> new MariaDBProvider(parameters, password);
}
}
diff --git a/QS.Launcher.Avalonia/DependencyInjection.cs b/QS.Launcher.Avalonia/DependencyInjection.cs
index d5dce86ef..30848bc44 100644
--- a/QS.Launcher.Avalonia/DependencyInjection.cs
+++ b/QS.Launcher.Avalonia/DependencyInjection.cs
@@ -1,6 +1,6 @@
using Avalonia.Controls;
using Microsoft.Extensions.DependencyInjection;
-using QS.Launcher.Services;
+using QS.Dialog;
using QS.Launcher.Views;
using QS.Launcher.Views.Pages;
using QS.Launcher.Views.Pages.DataBase;
@@ -14,6 +14,6 @@ public static IServiceCollection AddPages(this IServiceCollection services) {
.AddSingleton()
.AddTransient()
.AddTransient()
- .AddSingleton();
+ .AddSingleton();
}
}
diff --git a/QS.Launcher.Avalonia/Services/AvaloniaUiThreadInvoker.cs b/QS.Launcher.Avalonia/Services/AvaloniaUiThreadInvoker.cs
deleted file mode 100644
index 6ac1bc79e..000000000
--- a/QS.Launcher.Avalonia/Services/AvaloniaUiThreadInvoker.cs
+++ /dev/null
@@ -1,12 +0,0 @@
-using System;
-using Avalonia.Threading;
-using QS.Launcher.Services;
-
-namespace QS.Launcher.Services {
- public class AvaloniaUiThreadInvoker : IUiThreadInvoker {
- public void Post(Action action) {
- if(action == null) return;
- Dispatcher.UIThread.Post(action);
- }
- }
-}
diff --git a/QS.Launcher.Avalonia/Views/Pages/DataBase/DataBasesView.axaml b/QS.Launcher.Avalonia/Views/Pages/DataBase/DataBasesView.axaml
index bb771c5f6..9ebc48b73 100644
--- a/QS.Launcher.Avalonia/Views/Pages/DataBase/DataBasesView.axaml
+++ b/QS.Launcher.Avalonia/Views/Pages/DataBase/DataBasesView.axaml
@@ -16,7 +16,7 @@
-
+
{
if(e.Key == Key.Enter) {
TopLevel.GetTopLevel(this)?.FocusManager?.ClearFocus();
- if(DataContext is DataBasesVM vm)
- vm.ConnectCommand.Execute(null);
+ ViewModel.ConnectCommand.Execute(null);
}
};
}
@@ -56,7 +57,7 @@ public void Label_PointerPressed(object? sender, PointerPressedEventArgs e) {
}
private void Databases_OnDoubleTapped(object? sender, TappedEventArgs e) {
- if(databases.SelectedItem is not null && DataContext is DataBasesVM vm)
- vm.ConnectCommand.Execute(null);
+ if(databases.SelectedItem is not null)
+ ViewModel.ConnectCommand.Execute(null);
}
}
diff --git a/QS.Launcher/QS.Launcher.csproj b/QS.Launcher/QS.Launcher.csproj
index 3481e8522..271188d35 100644
--- a/QS.Launcher/QS.Launcher.csproj
+++ b/QS.Launcher/QS.Launcher.csproj
@@ -20,7 +20,6 @@
-
diff --git a/QS.Launcher/Services/IUiThreadInvoker.cs b/QS.Launcher/Services/IUiThreadInvoker.cs
deleted file mode 100644
index b7801d342..000000000
--- a/QS.Launcher/Services/IUiThreadInvoker.cs
+++ /dev/null
@@ -1,11 +0,0 @@
-using System;
-
-namespace QS.Launcher.Services {
- ///
- /// проксирование действия в UI-поток
- ///
- public interface IUiThreadInvoker {
- /// Запланировать действие в UI-потоке без его блокировки
- void Post(Action action);
- }
-}
diff --git a/QS.Launcher/Services/LauncherDbCreatorInteraction.cs b/QS.Launcher/Services/LauncherDbCreatorInteraction.cs
index e68e95b79..9d0a11469 100644
--- a/QS.Launcher/Services/LauncherDbCreatorInteraction.cs
+++ b/QS.Launcher/Services/LauncherDbCreatorInteraction.cs
@@ -1,53 +1,28 @@
using System;
-using System.Threading.Tasks;
using QS.DBScripts.Controllers;
using QS.Dialog;
namespace QS.Launcher.Services {
- ///
- /// проксирует вопросы и ошибки
- /// все вызовы диалогов уходят в UI-поток через IUiThreadInvoker,
- /// потому что creator дёргает их с фонового потока
- ///
public class LauncherDbCreatorInteraction : IDbCreatorInteraction {
private readonly IInteractiveQuestion question;
private readonly IInteractiveMessage message;
- private readonly IUiThreadInvoker uiThread;
public LauncherDbCreatorInteraction(
IInteractiveQuestion question,
- IInteractiveMessage message,
- IUiThreadInvoker uiThread)
+ IInteractiveMessage message)
{
this.question = question ?? throw new ArgumentNullException(nameof(question));
this.message = message ?? throw new ArgumentNullException(nameof(message));
- this.uiThread = uiThread ?? throw new ArgumentNullException(nameof(uiThread));
}
- public Task AskDropExistingDatabaseAsync(string dbName) {
- var tcs = new TaskCompletionSource();
- uiThread.Post(async () => {
- try {
- bool answer = await question.QuestionAsync(
- $"База с именем `{dbName}` уже существует на сервере. Удалить существующую базу перед созданием новой?",
- "Создание базы данных");
- tcs.TrySetResult(answer);
- }
- catch(Exception ex) { tcs.TrySetException(ex); }
- });
- return tcs.Task;
+ public bool AskDropExistingDatabase(string dbName) {
+ return question.Question(
+ $"База с именем `{dbName}` уже существует на сервере. Удалить существующую базу перед созданием новой?",
+ "Создание базы данных");
}
- public Task ReportErrorAsync(string text, string lastExecutedStatement) {
- var tcs = new TaskCompletionSource();
- uiThread.Post(() => {
- try {
- message.ShowMessage(ImportanceLevel.Error, text, "Ошибка создания базы");
- tcs.TrySetResult(true);
- }
- catch(Exception ex) { tcs.TrySetException(ex); }
- });
- return tcs.Task;
+ public void ReportError(string text, string lastExecutedStatement) {
+ message.ShowMessage(ImportanceLevel.Error, text, "Ошибка создания базы");
}
}
}
diff --git a/QS.Launcher/ViewModels/PageViewModels/DataBase/CreateDataBaseProgressVM.cs b/QS.Launcher/ViewModels/PageViewModels/DataBase/CreateDataBaseProgressVM.cs
index 5a59100a0..a679d57ed 100644
--- a/QS.Launcher/ViewModels/PageViewModels/DataBase/CreateDataBaseProgressVM.cs
+++ b/QS.Launcher/ViewModels/PageViewModels/DataBase/CreateDataBaseProgressVM.cs
@@ -7,14 +7,9 @@
using QS.DbManagement;
using QS.DBScripts.Controllers;
using QS.Dialog;
-using QS.Launcher.Services;
using ReactiveUI;
namespace QS.Launcher.ViewModels.PageViewModels.DataBase {
- ///
- /// показывает прогресс создания базы, прогресс приходит из не-UI потока, поэтому все мутации
- /// reactive-свойств проксируются через IUiThreadInvoker
- ///
public class CreateDataBaseProgressVM : CarouselPageVM, IProgressBarDisplayable {
private static NLog.Logger logger = NLog.LogManager.GetCurrentClassLogger();
@@ -24,10 +19,10 @@ public class CreateDataBaseProgressVM : CarouselPageVM, IProgressBarDisplayable
private readonly IDbCreatorInteraction interaction;
private readonly IServiceProvider services;
- private readonly IUiThreadInvoker uiThread;
+ private readonly IGuiDispatcher guiDispatcher;
private readonly CancellationTokenSource cts;
- #region IProgressBarDisplayable backed properties
+ #region IProgressBarDisplayable поля
private double minValue;
private double maxValue = 1;
@@ -58,10 +53,7 @@ public bool IsStarted {
#endregion
- /// Поднимается, когда база успешно создана, на него должен быть подписан DataBasesVM
public event Action DatabaseCreated;
-
- /// Поднимается, когда создание завершилось отменой
public event Action DatabaseCreationFailed;
public ReactiveCommand StartCreationCommand { get; }
@@ -69,11 +61,11 @@ public bool IsStarted {
public CreateDataBaseProgressVM(
IDbCreatorInteraction interaction,
- IUiThreadInvoker uiThread,
+ IGuiDispatcher guiDispatcher,
IServiceProvider services)
{
this.interaction = interaction ?? throw new ArgumentNullException(nameof(interaction));
- this.uiThread = uiThread ?? throw new ArgumentNullException(nameof(uiThread));
+ this.guiDispatcher = guiDispatcher ?? throw new ArgumentNullException(nameof(guiDispatcher));
this.services = services ?? throw new ArgumentNullException(nameof(services));
cts = new CancellationTokenSource();
@@ -95,6 +87,9 @@ public void SetPipeline(
throw new ArgumentException("Пайплайн создания базы пуст.", nameof(phases));
}
+ ///
+ /// выносим всю синхронную цепочку фаз в пул, чтобы UI поток оставался свободным для перерисовки прогрессбара
+ ///
public async Task StartCreationAsync() {
try {
var args = new CreatorFactoryArgs {
@@ -105,18 +100,12 @@ public async Task StartCreationAsync() {
ServiceProvider = services
};
- for(int i = 0; i < phases.Count; i++) {
- cts.Token.ThrowIfCancellationRequested();
- var phase = phases[i];
- uiThread.Post(() => CurrentText = phase.Title);
-
- bool ok = await phase.Action(args);
- if(!ok) {
- DatabaseCreationFailed?.Invoke();
- return;
- }
- }
- DatabaseCreated?.Invoke();
+ bool success = await Task.Run(() => RunPipeline(args), cts.Token);
+
+ if(success)
+ DatabaseCreated?.Invoke();
+ else
+ DatabaseCreationFailed?.Invoke();
}
catch(OperationCanceledException) {
logger.Info("Создание базы отменено.");
@@ -124,14 +113,23 @@ public async Task StartCreationAsync() {
}
catch(Exception ex) {
logger.Error(ex, "Сбой в процессе создания базы.");
- await interaction.ReportErrorAsync(ex.Message, null);
+ interaction.ReportError(ex.Message, null);
DatabaseCreationFailed?.Invoke();
}
}
+ private bool RunPipeline(CreatorFactoryArgs args) {
+ foreach(var phase in phases) {
+ args.CancellationToken.ThrowIfCancellationRequested();
+ guiDispatcher.RunInGuiTread(() => CurrentText = phase.Title);
+ if(!phase.Action(args))
+ return false;
+ }
+ return true;
+ }
- #region IProgressBarDisplayable
+ #region IProgressBarDisplayable методы
public void Start(double maxValue = 1, double minValue = 0, string text = null, double startValue = 0) {
- uiThread.Post(() => {
+ guiDispatcher.RunInGuiTread(() => {
MaxValue = maxValue;
MinValue = minValue;
Value = startValue;
@@ -140,20 +138,20 @@ public void Start(double maxValue = 1, double minValue = 0, string text = null,
});
}
- public void Update(double curValue) => uiThread.Post(() => Value = curValue);
+ public void Update(double curValue) => guiDispatcher.RunInGuiTread(() => Value = curValue);
- public void UpdateMax(double maxValue) => uiThread.Post(() => MaxValue = maxValue);
+ public void UpdateMax(double maxValue) => guiDispatcher.RunInGuiTread(() => MaxValue = maxValue);
- public void Update(string curText) => uiThread.Post(() => CurrentText = curText);
+ public void Update(string curText) => guiDispatcher.RunInGuiTread(() => CurrentText = curText);
public void Add(double addValue = 1, string text = null) {
- uiThread.Post(() => {
+ guiDispatcher.RunInGuiTread(() => {
Value += addValue;
if(text != null) CurrentText = text;
});
}
- public void Close() => uiThread.Post(() => IsStarted = false);
+ public void Close() => guiDispatcher.RunInGuiTread(() => IsStarted = false);
#endregion
}
}
diff --git a/QS.Launcher/ViewModels/PageViewModels/DataBase/CreateDataBaseSettingsVM.cs b/QS.Launcher/ViewModels/PageViewModels/DataBase/CreateDataBaseSettingsVM.cs
index 9957dc2f4..02e73dc81 100644
--- a/QS.Launcher/ViewModels/PageViewModels/DataBase/CreateDataBaseSettingsVM.cs
+++ b/QS.Launcher/ViewModels/PageViewModels/DataBase/CreateDataBaseSettingsVM.cs
@@ -1,7 +1,6 @@
using System;
using System.Reactive;
using System.Reactive.Linq;
-using System.Threading.Tasks;
using QS.DbManagement;
using QS.DBScripts.Controllers;
using ReactiveUI;
@@ -55,12 +54,12 @@ private void GoToProgress() {
var pipeline = new[] {
new DbCreationPhase(
"Создание базы данных",
- (args) => Task.FromResult(args.Provider.CreateDatabase(DbName, DbTitle, services))),
+ args => args.Provider.CreateDatabase(DbName, DbTitle, services)),
new DbCreationPhase(
"Наполнение базы данных",
- async args => {
+ args => {
IDbCreatorModel creator = Connection.ConnectionType.CreateCreator(args);
- return await creator.RunCreationAsync(DbName, DbTitle);
+ return creator.RunCreation(DbName, DbTitle);
})
};
diff --git a/QS.Launcher/ViewModels/PageViewModels/DataBase/DataBasesVM.cs b/QS.Launcher/ViewModels/PageViewModels/DataBase/DataBasesVM.cs
index 047307e6e..f288d3c2f 100644
--- a/QS.Launcher/ViewModels/PageViewModels/DataBase/DataBasesVM.cs
+++ b/QS.Launcher/ViewModels/PageViewModels/DataBase/DataBasesVM.cs
@@ -25,11 +25,23 @@ public IDbProvider Provider {
this.RaiseAndSetIfChanged(ref provider, value);
Databases = provider.GetUserDatabases(applicationInfo).AsList();
this.RaisePropertyChanged(nameof(Databases));
+ this.RaisePropertyChanged(nameof(CanCreateDatabase));
LoadLastSelectedDatabase();
}
}
+ ///
+ /// можно создать базу только если:
+ /// есть права пользователя на создание
+ /// тип подключения поддерживает создание в текущем окружении
+ /// задана фабрика и зарегистрирован скрипт создания
+ ///
+ public bool CanCreateDatabase =>
+ provider != null
+ && provider.CanCreateDatabase
+ && currentConnection?.ConnectionType?.SupportsDatabaseCreation(serviceProvider) == true;
+
public Connection CurrentConnection => currentConnection;
public void SetProvider(IDbProvider dbProvider, Connection connection, Action saveConnections) {
@@ -94,7 +106,7 @@ public DataBasesVM(
/// создаёт возвращает фокус на и обновляет список баз
///
private void OpenCreateDatabase() {
- if(provider == null || currentConnection == null)
+ if(!CanCreateDatabase)
return;
var settings = ActivatorUtilities.GetServiceOrCreateInstance(serviceProvider);
@@ -147,6 +159,9 @@ public void Connect() {
SaveLastSelectedDatabase();
+ // Определяем, нужно ли закрывать лаунчер через Shutdown
+ // В standalone режиме учитываем галочку ShouldCloseLauncherAfterStart
+ // В in-process режиме НЕ делаем shutdown (возвращаем false)
var isStandalone = launcherOptions?.IsStandalone ?? false;
logger.Info($">>> Connect: IsStandalone={isStandalone}, ShouldCloseLauncherAfterStart={ShouldCloseLauncherAfterStart}");
diff --git a/QS.LibsTest.Core/Launcher/ConfiguratorTest.cs b/QS.LibsTest.Core/Launcher/ConfiguratorTest.cs
index 48c3b3b8c..99a042658 100644
--- a/QS.LibsTest.Core/Launcher/ConfiguratorTest.cs
+++ b/QS.LibsTest.Core/Launcher/ConfiguratorTest.cs
@@ -430,8 +430,10 @@ public TestConnectionType(string name) {
public override IDbProvider CreateProvider(IList parameters, string password = null)
=> Substitute.For();
- public override IDbCreatorModel CreatorFactory(CreatorFactoryArgs args)
- => Substitute.For();
+ public TestConnectionType WithStubCreator() {
+ CreatorFactory = args => Substitute.For();
+ return this;
+ }
}
}
}
diff --git a/QS.Project.Core/DBScripts/Controllers/IDbCreatorInteraction.cs b/QS.Project.Core/DBScripts/Controllers/IDbCreatorInteraction.cs
index 3a499361a..f2c6c61e8 100644
--- a/QS.Project.Core/DBScripts/Controllers/IDbCreatorInteraction.cs
+++ b/QS.Project.Core/DBScripts/Controllers/IDbCreatorInteraction.cs
@@ -1,5 +1,3 @@
-using System.Threading.Tasks;
-
namespace QS.DBScripts.Controllers
{
///
@@ -7,8 +5,8 @@ namespace QS.DBScripts.Controllers
///
public interface IDbCreatorInteraction
{
- Task AskDropExistingDatabaseAsync(string dbName);
+ bool AskDropExistingDatabase(string dbName);
- Task ReportErrorAsync(string text, string lastExecutedStatement);
+ void ReportError(string text, string lastExecutedStatement);
}
}
diff --git a/QS.Project.Core/DBScripts/Controllers/IDbCreatorModel.cs b/QS.Project.Core/DBScripts/Controllers/IDbCreatorModel.cs
index 625b9dd3c..c562fac51 100644
--- a/QS.Project.Core/DBScripts/Controllers/IDbCreatorModel.cs
+++ b/QS.Project.Core/DBScripts/Controllers/IDbCreatorModel.cs
@@ -1,12 +1,12 @@
-using System.Threading.Tasks;
-
namespace QS.DBScripts.Controllers
{
///
- /// Низкоуровневая модель создания БД: знает, как физически создать и наполнить базу
+ /// Низкоуровневая модель наполнения БД для конкретного движка
///
public interface IDbCreatorModel
{
- Task RunCreationAsync(string dbName, string dbTitle);
+ // Метод блокирует вызывающий поток на время работы с базой
+ // Вынесение в фоновый поток — ответственность вызывающего кода
+ bool RunCreation(string dbName, string dbTitle);
}
}
diff --git a/QS.Project.Core/Dialog/IInteractiveQuestion.cs b/QS.Project.Core/Dialog/IInteractiveQuestion.cs
index 49f9c698d..51384f120 100644
--- a/QS.Project.Core/Dialog/IInteractiveQuestion.cs
+++ b/QS.Project.Core/Dialog/IInteractiveQuestion.cs
@@ -20,22 +20,5 @@ public interface IInteractiveQuestion
/// Заголовок окна диалога
/// Вернет заголовок кнопки которую нажал пользователь. Если пользователь закроет диалог крестиком, вернется null.
string Question(string[] buttons, string message, string title = null);
-
- ///
- /// Отобразит диалог с вопросом пользователю и кнопками Да Нет.
- ///
- /// Сообщение диалога
- /// Заголовок окна диалога
- /// True - Да, False - Нет(или закрытие крестиком)
- Task QuestionAsync(string message, string title = null);
-
- ///
- /// Отобразит диалог с вопросом пользователю.
- ///
- /// Список заголовков для кнопок
- /// Сообщение диалога
- /// Заголовок окна диалога
- /// Вернет заголовок кнопки которую нажал пользователь. Если пользователь закроет диалог крестиком, вернется null.
- Task QuestionAsync(string[] buttons, string message, string title = null);
}
}
diff --git a/QS.Project.Gtk/Project.Services.GtkUI/GtkInteractiveService.cs b/QS.Project.Gtk/Project.Services.GtkUI/GtkInteractiveService.cs
index e44d53802..d086b26c7 100644
--- a/QS.Project.Gtk/Project.Services.GtkUI/GtkInteractiveService.cs
+++ b/QS.Project.Gtk/Project.Services.GtkUI/GtkInteractiveService.cs
@@ -26,13 +26,5 @@ public string Question(string[] buttons, string message, string title = null)
{
return interactiveQuestion.Question(buttons, message, title);
}
-
- public Task QuestionAsync(string message, string title = null) {
- throw new System.NotImplementedException();
- }
-
- public Task QuestionAsync(string[] buttons, string message, string title = null) {
- throw new System.NotImplementedException();
- }
}
}
diff --git a/QS.Project/Project.Services/Interactive/ConsoleInteractiveQuestion.cs b/QS.Project/Project.Services/Interactive/ConsoleInteractiveQuestion.cs
index 3ca6bc1f2..87d40cc74 100644
--- a/QS.Project/Project.Services/Interactive/ConsoleInteractiveQuestion.cs
+++ b/QS.Project/Project.Services/Interactive/ConsoleInteractiveQuestion.cs
@@ -37,13 +37,5 @@ public string Question(string[] buttons, string message, string title = null)
}
return null;
}
-
- public Task QuestionAsync(string message, string title = null) {
- throw new NotImplementedException();
- }
-
- public Task QuestionAsync(string[] buttons, string message, string title = null) {
- throw new NotImplementedException();
- }
}
}
diff --git a/QS.Project/Project.Services/Interactive/ConsoleInteractiveService.cs b/QS.Project/Project.Services/Interactive/ConsoleInteractiveService.cs
index 8f3b69424..879365700 100644
--- a/QS.Project/Project.Services/Interactive/ConsoleInteractiveService.cs
+++ b/QS.Project/Project.Services/Interactive/ConsoleInteractiveService.cs
@@ -23,18 +23,9 @@ public bool Question(string message, string title = null)
{
return interactiveQuestion.Question(message, title);
}
-
public string Question(string[] buttons, string message, string title = null)
{
return interactiveQuestion.Question(buttons, message, title);
}
-
- public Task QuestionAsync(string message, string title = null) {
- throw new System.NotImplementedException();
- }
-
- public Task QuestionAsync(string[] buttons, string message, string title = null) {
- throw new System.NotImplementedException();
- }
}
}
diff --git a/QS.Updater.Core/DBScripts/Models/MySqlDbCreateModel.cs b/QS.Updater.Core/DBScripts/Models/MySqlDbCreateModel.cs
index fb4a00696..f1b8c2df1 100644
--- a/QS.Updater.Core/DBScripts/Models/MySqlDbCreateModel.cs
+++ b/QS.Updater.Core/DBScripts/Models/MySqlDbCreateModel.cs
@@ -4,7 +4,6 @@
using System;
using System.Text.RegularExpressions;
using System.Threading;
-using System.Threading.Tasks;
namespace QS.DBScripts.Models
{
@@ -57,12 +56,6 @@ public MySqlDbCreateModel(
}
- public Task RunCreationAsync(string dbName, string dbTitle = null) {
- // Тяжёлая часть с MySqlScript.Execute синхронная,
- // поэтому уносим её на пул, чтобы не блокировать UI-поток
- return Task.Run(() => RunCreation(dbName, dbTitle), cancellationToken);
- }
-
public bool RunCreation(string dbName, string dbTitle = null) {
using(var connectionDB = new MySqlConnection(connectionString)) {
try {
@@ -78,7 +71,7 @@ public bool RunCreation(string dbName, string dbTitle = null) {
using(var rdr = cmd.ExecuteReader()) {
while(rdr.Read()) {
if(rdr[0].ToString() == dbName) {
- if(interaction.AskDropExistingDatabaseAsync(dbName).GetAwaiter().GetResult()) {
+ if(interaction.AskDropExistingDatabase(dbName)) {
needDropBase = true;
}
hasBase = true;
@@ -146,8 +139,6 @@ public bool RunCreation(string dbName, string dbTitle = null) {
}
catch(InvalidCastException ex) { //FIXME Временный для более адекватного обхода проблемы с отсутствием поддержки MariaDB 10.10. Удалить как починим работу с этой версией.
logger.Error(ex, "Ошибка подключения к серверу.");
- interaction.ReportErrorAsync("Работа с MariaDB 10.10 пока не поддерживается. Установите версию MariaDB 10.9.", lastExecutedStatement)
- .GetAwaiter().GetResult();
return false;
}
catch(MySqlException ex) {
@@ -159,7 +150,7 @@ public bool RunCreation(string dbName, string dbTitle = null) {
text = "Не удалось подключиться к серверу БД.";
else
text = ex.Message;
- interaction.ReportErrorAsync(text, lastExecutedStatement).GetAwaiter().GetResult();
+ interaction.ReportError(text, lastExecutedStatement);
return false;
}
finally {
From d20c4985455c5e2dd05e9296a561802c35f8379b Mon Sep 17 00:00:00 2001
From: J0shlerB0y <86416124+J0shlerB0y@users.noreply.github.com>
Date: Wed, 10 Jun 2026 20:58:38 +0300
Subject: [PATCH 16/19] Update MySqlDbCreateModel.cs
---
QS.Updater.Core/DBScripts/Models/MySqlDbCreateModel.cs | 1 +
1 file changed, 1 insertion(+)
diff --git a/QS.Updater.Core/DBScripts/Models/MySqlDbCreateModel.cs b/QS.Updater.Core/DBScripts/Models/MySqlDbCreateModel.cs
index f1b8c2df1..c7e7f1ea6 100644
--- a/QS.Updater.Core/DBScripts/Models/MySqlDbCreateModel.cs
+++ b/QS.Updater.Core/DBScripts/Models/MySqlDbCreateModel.cs
@@ -139,6 +139,7 @@ public bool RunCreation(string dbName, string dbTitle = null) {
}
catch(InvalidCastException ex) { //FIXME Временный для более адекватного обхода проблемы с отсутствием поддержки MariaDB 10.10. Удалить как починим работу с этой версией.
logger.Error(ex, "Ошибка подключения к серверу.");
+ interaction.ReportError("Работа с MariaDB 10.10 пока не поддерживается. Установите версию MariaDB 10.9.", lastExecutedStatement);
return false;
}
catch(MySqlException ex) {
From 41ba7a77c6883a910015c19761ca7d76ed60c128 Mon Sep 17 00:00:00 2001
From: J0shlerB0y <86416124+J0shlerB0y@users.noreply.github.com>
Date: Thu, 11 Jun 2026 11:53:27 +0300
Subject: [PATCH 17/19] =?UTF-8?q?=D0=B4=D0=BE=D1=80=D0=B0=D0=B1=D0=BE?=
=?UTF-8?q?=D1=82=D0=B0=D0=BB=20=D0=BB=D0=B5=D0=B3=D0=B0=D1=81=D0=B8=20Use?=
=?UTF-8?q?rCreateDbController?=
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
IDbCreateController нигде не используется кроме UserCreateDbController.cs и тестов, так что его можно удалить
---
QS.Cloud.Client/DataBase/QsCloudDbCreator.cs | 2 +-
.../MariaDb/MariaDbConnectionTypeBase.cs | 2 +-
.../DbScriptsConfigurationAdapter.cs | 24 -------
.../DBScripts/Models/MySqlDbCreateModel.cs | 12 ++--
QS.Updater.DB.Gtk/UpdaterDBAutofacModule.cs | 13 ----
.../Controllers/UserCreateDbController.cs | 64 +++++++------------
6 files changed, 30 insertions(+), 87 deletions(-)
delete mode 100644 QS.Updater.Core/DBScripts/DbScriptsConfigurationAdapter.cs
diff --git a/QS.Cloud.Client/DataBase/QsCloudDbCreator.cs b/QS.Cloud.Client/DataBase/QsCloudDbCreator.cs
index a11307579..4550edba8 100644
--- a/QS.Cloud.Client/DataBase/QsCloudDbCreator.cs
+++ b/QS.Cloud.Client/DataBase/QsCloudDbCreator.cs
@@ -60,7 +60,7 @@ public bool RunCreation(string dbName, string dbTitle) {
var creator = new MySqlDbCreateModel(
session.Db.Server, session.Db.Port, session.Db.Login, session.Db.Password,
- configuration, progress, interaction, cancellationToken);
+ configuration.MakeCreationScript(), progress, interaction, cancellationToken);
creator.FillBaseGuid = false;
bool success = creator.RunCreation(session.Db.BaseName, dbTitle);
diff --git a/QS.DbManagement/MariaDb/MariaDbConnectionTypeBase.cs b/QS.DbManagement/MariaDb/MariaDbConnectionTypeBase.cs
index 47dd17c1e..8b561cd6b 100644
--- a/QS.DbManagement/MariaDb/MariaDbConnectionTypeBase.cs
+++ b/QS.DbManagement/MariaDb/MariaDbConnectionTypeBase.cs
@@ -24,7 +24,7 @@ public MariaDbConnectionTypeBase() {
var scripts = args.ServiceProvider.GetRequiredService();
return new MySqlDbCreateModel(
p.ConnectionStringBuilder.ConnectionString,
- scripts, args.Progress, args.Interaction, args.CancellationToken) { FillBaseGuid = false };
+ scripts.MakeCreationScript(), args.Progress, args.Interaction, args.CancellationToken) { FillBaseGuid = false };
};
}
diff --git a/QS.Updater.Core/DBScripts/DbScriptsConfigurationAdapter.cs b/QS.Updater.Core/DBScripts/DbScriptsConfigurationAdapter.cs
deleted file mode 100644
index 905a75275..000000000
--- a/QS.Updater.Core/DBScripts/DbScriptsConfigurationAdapter.cs
+++ /dev/null
@@ -1,24 +0,0 @@
-using System;
-using QS.DBScripts.Models;
-using QS.Updater.DB;
-
-namespace QS.DBScripts
-{
- public class DbScriptsConfigurationAdapter : IDbScriptsConfiguration
- {
- private readonly CreationScript creationScript;
- private readonly UpdateConfiguration updateConfiguration;
-
- public DbScriptsConfigurationAdapter(CreationScript creationScript, UpdateConfiguration updateConfiguration = null)
- {
- this.creationScript = creationScript ?? throw new ArgumentNullException(nameof(creationScript));
- this.updateConfiguration = updateConfiguration;
- }
-
- public bool HasCreationScript() => creationScript != null;
-
- public CreationScript MakeCreationScript() => creationScript;
-
- public UpdateConfiguration MakeUpdateConfiguration() => updateConfiguration ?? new UpdateConfiguration();
- }
-}
diff --git a/QS.Updater.Core/DBScripts/Models/MySqlDbCreateModel.cs b/QS.Updater.Core/DBScripts/Models/MySqlDbCreateModel.cs
index c7e7f1ea6..85e818350 100644
--- a/QS.Updater.Core/DBScripts/Models/MySqlDbCreateModel.cs
+++ b/QS.Updater.Core/DBScripts/Models/MySqlDbCreateModel.cs
@@ -12,7 +12,7 @@ public class MySqlDbCreateModel : IDbCreatorModel
static NLog.Logger logger = NLog.LogManager.GetCurrentClassLogger();
private readonly string connectionString;
- private readonly CreationScript scripts;
+ private readonly CreationScript script;
private readonly IProgressBarDisplayable progress;
private readonly IDbCreatorInteraction interaction;
private readonly CancellationToken cancellationToken;
@@ -21,7 +21,7 @@ public class MySqlDbCreateModel : IDbCreatorModel
public MySqlDbCreateModel(
string connectionString,
- IDbScriptsConfiguration scripts,
+ CreationScript script,
IProgressBarDisplayable progress,
IDbCreatorInteraction interaction,
CancellationToken cancellationToken)
@@ -29,7 +29,7 @@ public MySqlDbCreateModel(
if(string.IsNullOrWhiteSpace(connectionString))
throw new ArgumentException("Connection string is required", nameof(connectionString));
this.connectionString = connectionString;
- this.scripts = scripts.MakeCreationScript() ?? throw new ArgumentNullException(nameof(scripts));
+ this.script = script ?? throw new ArgumentNullException(nameof(script));
this.progress = progress ?? throw new ArgumentNullException(nameof(progress));
this.interaction = interaction ?? throw new ArgumentNullException(nameof(interaction));
this.cancellationToken = cancellationToken;
@@ -37,7 +37,7 @@ public MySqlDbCreateModel(
public MySqlDbCreateModel(
string server, uint port, string login, string password,
- IDbScriptsConfiguration scripts,
+ CreationScript script,
IProgressBarDisplayable progress,
IDbCreatorInteraction interaction,
CancellationToken cancellationToken) {
@@ -49,7 +49,7 @@ public MySqlDbCreateModel(
Password = password,
AllowUserVariables = true
}.ConnectionString;
- this.scripts = scripts.MakeCreationScript() ?? throw new ArgumentNullException(nameof(scripts));
+ this.script = script ?? throw new ArgumentNullException(nameof(script));
this.progress = progress ?? throw new ArgumentNullException(nameof(progress));
this.interaction = interaction ?? throw new ArgumentNullException(nameof(interaction));
this.cancellationToken = cancellationToken;
@@ -84,7 +84,7 @@ public bool RunCreation(string dbName, string dbTitle = null) {
progress.Start(text: "Получаем скрипт создания базы");
- string sqlScript = scripts.GetSqlScript();
+ string sqlScript = script.GetSqlScript();
int predictedCount = Regex.Matches(sqlScript, ";").Count;
logger.Debug("Предполагаем наличие {0} команд в скрипте.", predictedCount);
diff --git a/QS.Updater.DB.Gtk/UpdaterDBAutofacModule.cs b/QS.Updater.DB.Gtk/UpdaterDBAutofacModule.cs
index 971241817..4f89c9b4f 100644
--- a/QS.Updater.DB.Gtk/UpdaterDBAutofacModule.cs
+++ b/QS.Updater.DB.Gtk/UpdaterDBAutofacModule.cs
@@ -1,7 +1,5 @@
using Autofac;
-using QS.DBScripts;
using QS.DBScripts.Controllers;
-using QS.DBScripts.Models;
using QS.Updater.DB;
using QS.Updater.DB.ViewModels;
using QS.ViewModels;
@@ -14,22 +12,11 @@ protected override void Load(ContainerBuilder builder)
{
builder.RegisterType().As();
#region Models
- builder.RegisterType().AsSelf();
builder.RegisterType().AsSelf();
#endregion
#region Desktop
builder.RegisterType().As();
#endregion
- builder.Register(c => {
- var creation = c.Resolve();
- UpdateConfiguration updates = null;
- if(c.IsRegistered())
- updates = c.Resolve();
- return new DbScriptsConfigurationAdapter(creation, updates);
- })
- .As()
- .SingleInstance()
- .PreserveExistingDefaults();
#region ViewModels
builder.RegisterAssemblyTypes(System.Reflection.Assembly.GetAssembly(typeof(UpdateProcessViewModel)))
.Where(t => t.IsAssignableTo() && t.Name.EndsWith("ViewModel"))
diff --git a/QS.Updater.DB/DBScripts/Controllers/UserCreateDbController.cs b/QS.Updater.DB/DBScripts/Controllers/UserCreateDbController.cs
index 531b8d3ce..81b9217d7 100644
--- a/QS.Updater.DB/DBScripts/Controllers/UserCreateDbController.cs
+++ b/QS.Updater.DB/DBScripts/Controllers/UserCreateDbController.cs
@@ -1,6 +1,5 @@
using System;
using System.Threading;
-using System.Threading.Tasks;
using QS.DBScripts.Models;
using QS.DBScripts.ViewModels;
using QS.Dialog;
@@ -16,18 +15,18 @@ public class UserCreateDbController : IDBCreator, IDbCreatorInteraction
private readonly INavigationManager navigation;
private readonly IInteractiveService interactive;
private readonly IGuiDispatcher guiDispatcher;
- private readonly IDbScriptsConfiguration scripts;
+ private readonly CreationScript creationScript;
public UserCreateDbController(
INavigationManager navigation,
IInteractiveService interactive,
IGuiDispatcher guiDispatcher,
- IDbScriptsConfiguration scripts)
+ CreationScript creationScript)
{
this.navigation = navigation ?? throw new ArgumentNullException(nameof(navigation));
this.interactive = interactive ?? throw new ArgumentNullException(nameof(interactive));
this.guiDispatcher = guiDispatcher ?? throw new ArgumentNullException(nameof(guiDispatcher));
- this.scripts = scripts ?? throw new ArgumentNullException(nameof(scripts));
+ this.creationScript = creationScript ?? throw new ArgumentNullException(nameof(creationScript));
}
public void RunCreation(string server, string dbname)
@@ -41,36 +40,29 @@ public void RunCreation(string server, string dbname)
};
}
- async void StartCreation(string server, string dbname, string login, string password)
+ void StartCreation(string server, string dbname, string login, string password)
{
- ParseServer(server, out string host, out uint port);
-
- bool success = false;
try {
+ ParseServer(server, out string host, out uint port);
+
var createModel = new MySqlDbCreateModel(
host, port, login, password,
- scripts,
+ creationScript,
Progress,
interaction: this,
cancellationToken: CancellationToken.None);
- success = await createModel.RunCreationAsync(dbname, dbTitle: null);
+ bool success = createModel.RunCreation(dbname);
+ if(success)
+ interactive.ShowMessage(ImportanceLevel.Info, "Создание базы успешно завершено.\nЗайдите в программу под администратором для добавления пользователей.");
}
catch(Exception ex) {
logger.Error(ex, "Ошибка создания базы.");
- guiDispatcher.RunInGuiTread(() => interactive.ShowMessage(ImportanceLevel.Error, ex.Message));
+ interactive.ShowMessage(ImportanceLevel.Error, ex.Message);
}
finally {
- guiDispatcher.RunInGuiTread(() => {
- if(progressPage != null)
- navigation.ForceClosePage(progressPage, CloseSource.FromParentPage);
- });
- }
-
- if(success) {
- guiDispatcher.RunInGuiTread(() =>
- interactive.ShowMessage(ImportanceLevel.Info,
- "Создание базы успешно завершено.\nЗайдите в программу под администратором для добавления пользователей."));
+ if(progressPage != null)
+ navigation.ForceClosePage(progressPage, CloseSource.FromParentPage);
}
}
@@ -85,33 +77,21 @@ private static void ParseServer(string server, out string host, out uint port) {
}
#region IDbCreatorInteraction
+ //Создание идет в GUI-потоке (как и раньше), прогресс сам прокачивает событийный цикл,
+ //поэтому диалоги показываем напрямую.
- public Task AskDropExistingDatabaseAsync(string dbName) {
- var tcs = new TaskCompletionSource();
- guiDispatcher.RunInGuiTread(() => {
- try {
- tcs.SetResult(interactive.Question(
- $"База с именем `{dbName}` уже существует на сервере. Удалить существующую базу перед созданием новой?"));
- }
- catch(Exception ex) { tcs.SetException(ex); }
- });
- return tcs.Task;
+ public bool AskDropExistingDatabase(string dbName)
+ {
+ return interactive.Question($"База с именем `{dbName}` уже существует на сервере. Удалить существующую базу перед созданием новой?");
}
- public Task ReportErrorAsync(string text, string lastExecutedStatement) {
- var tcs = new TaskCompletionSource();
- guiDispatcher.RunInGuiTread(() => {
- try {
- interactive.ShowMessage(ImportanceLevel.Error, text);
- tcs.SetResult(true);
- }
- catch(Exception ex) { tcs.SetException(ex); }
- });
- return tcs.Task;
+ public void ReportError(string text, string lastExecutedStatement)
+ {
+ interactive.ShowMessage(ImportanceLevel.Error, text);
}
#endregion
- #region Свойства процесса
+ #region Progress page
IPage progressPage;
public IProgressBarDisplayable Progress {
From c9c4b79caf73e92e8fb4a1a2a6fc472222494bb6 Mon Sep 17 00:00:00 2001
From: J0shlerB0y <86416124+J0shlerB0y@users.noreply.github.com>
Date: Thu, 11 Jun 2026 13:23:59 +0300
Subject: [PATCH 18/19] =?UTF-8?q?=D0=B2=D1=8B=D0=BD=D0=B5=D1=81=20=D1=82?=
=?UTF-8?q?=D0=B5=D1=81=D1=82=D0=BE=D0=B2=D1=83=D1=8E=20=D0=BA=D0=BE=D0=BD?=
=?UTF-8?q?=D1=84=D0=B8=D0=B3=D1=83=D1=80=D0=B0=D1=86=D0=B8=D1=8E=20=D1=81?=
=?UTF-8?q?=D0=BE=D0=B7=D0=B4=D0=B0=D0=BD=D0=B8=D1=8F=20=D0=B1=D0=B0=D0=B7?=
=?UTF-8?q?=D1=8B?=
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
---
.../DataBase/QsCloudScriptsConfiguration.cs | 38 -
QS.Cloud.Client/Scripts/new_empty.sql | 779 ------------------
2 files changed, 817 deletions(-)
delete mode 100644 QS.Cloud.Client/DataBase/QsCloudScriptsConfiguration.cs
delete mode 100644 QS.Cloud.Client/Scripts/new_empty.sql
diff --git a/QS.Cloud.Client/DataBase/QsCloudScriptsConfiguration.cs b/QS.Cloud.Client/DataBase/QsCloudScriptsConfiguration.cs
deleted file mode 100644
index 163e42421..000000000
--- a/QS.Cloud.Client/DataBase/QsCloudScriptsConfiguration.cs
+++ /dev/null
@@ -1,38 +0,0 @@
-using QS.DBScripts;
-using QS.DBScripts.Models;
-using QS.Updater.DB;
-using System;
-using System.Collections.Generic;
-using System.Linq;
-using System.Reflection;
-using System.Text;
-
-namespace QS.Cloud.Client.DataBase {
- public class QsCloudScriptsConfiguration : IDbScriptsConfiguration {
- private string ResourceName = "QS.Cloud.Client.Scripts.new_empty.sql";
- public bool HasCreationScript() {
- return Assembly.GetAssembly(typeof(QsCloudScriptsConfiguration))
- .GetManifestResourceNames()
- .Contains(ResourceName);
- }
-
- public CreationScript MakeCreationScript() {
- return new CreationScript(
- Assembly.GetAssembly(typeof(QsCloudScriptsConfiguration)),
- ResourceName,
- new Version(1, 7)
- );
- }
-
- public UpdateConfiguration MakeUpdateConfiguration() {
- var configuration = new UpdateConfiguration();
-
- configuration.AddUpdate(
- new Version(1, 7),
- new Version(1, 7, 1),
- "QS.Cloud.Client.Scripts.1.7.sql");
-
- return configuration;
- }
- }
-}
diff --git a/QS.Cloud.Client/Scripts/new_empty.sql b/QS.Cloud.Client/Scripts/new_empty.sql
deleted file mode 100644
index b25952e11..000000000
--- a/QS.Cloud.Client/Scripts/new_empty.sql
+++ /dev/null
@@ -1,779 +0,0 @@
--- phpMyAdmin SQL Dump
--- version 5.0.4deb2~bpo10+1
--- https://www.phpmyadmin.net/
---
--- Host: demeter.srv.qsolution.ru
--- Generation Time: Apr 30, 2026 at 01:52 PM
--- Server version: 10.3.39-MariaDB-0+deb10u2
--- PHP Version: 7.3.31-1~deb10u7
-
-SET SQL_MODE = "NO_AUTO_VALUE_ON_ZERO";
-START TRANSACTION;
-SET time_zone = "+00:00";
-
-
-/*!40101 SET @OLD_CHARACTER_SET_CLIENT=@@CHARACTER_SET_CLIENT */;
-/*!40101 SET @OLD_CHARACTER_SET_RESULTS=@@CHARACTER_SET_RESULTS */;
-/*!40101 SET @OLD_COLLATION_CONNECTION=@@COLLATION_CONNECTION */;
-/*!40101 SET NAMES utf8mb4 */;
-
---
--- Database: `QSService`
---
-
--- --------------------------------------------------------
-
---
--- Table structure for table `accounts`
---
-
-CREATE TABLE `accounts` (
- `id` int(10) UNSIGNED NOT NULL,
- `login` varchar(20) NOT NULL,
- `client_id` int(10) UNSIGNED DEFAULT NULL,
- `customer` varchar(50) NOT NULL,
- `email` varchar(50) DEFAULT NULL,
- `paid_until` date DEFAULT NULL,
- `notify_by_days` int(11) DEFAULT NULL COMMENT 'Уведомить за Н дней до окончания',
- `bases_limit` int(10) UNSIGNED NOT NULL DEFAULT 1,
- `users_limit` int(10) UNSIGNED NOT NULL DEFAULT 3,
- `space_limit` int(10) UNSIGNED NOT NULL DEFAULT 500,
- `deactivated` tinyint(1) NOT NULL DEFAULT 0
-) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_general_ci;
-
--- --------------------------------------------------------
-
---
--- Table structure for table `api_tokens`
---
-
-CREATE TABLE `api_tokens` (
- `id` int(10) UNSIGNED NOT NULL,
- `base_id` int(10) UNSIGNED NOT NULL,
- `token` char(36) NOT NULL
-) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_general_ci;
-
--- --------------------------------------------------------
-
---
--- Table structure for table `bases`
---
-
-CREATE TABLE `bases` (
- `id` int(10) UNSIGNED NOT NULL,
- `account_id` int(10) UNSIGNED NOT NULL,
- `server_id` int(10) UNSIGNED NOT NULL,
- `base_title` varchar(64) DEFAULT NULL COMMENT 'Русское название базы для пользователя',
- `base_name` varchar(45) NOT NULL,
- `product_id` int(10) UNSIGNED NOT NULL,
- `real_name` varchar(64) DEFAULT NULL,
- `base_guid` char(36) DEFAULT NULL,
- `wear_lk` tinyint(1) NOT NULL DEFAULT 0,
- `number_of_lk_client` int(11) DEFAULT 0,
- `claims_lk` tinyint(1) NOT NULL DEFAULT 0,
- `postomats` tinyint(1) NOT NULL DEFAULT 0,
- `catalog` tinyint(1) NOT NULL DEFAULT 0,
- `comments` text DEFAULT NULL,
- `ratings` tinyint(1) NOT NULL DEFAULT 0,
- `appointment_lk` tinyint(1) NOT NULL DEFAULT 0 COMMENT 'включение предварительной записи',
- `washing_lk` tinyint(1) NOT NULL DEFAULT 0 COMMENT 'Включение отображения стирки в мобильном кабинете',
- `speccoin_lk` tinyint(1) NOT NULL DEFAULT 0 COMMENT 'Включение функциональности спецкойнов',
- `size_editing_lk` tinyint(1) NOT NULL DEFAULT 1 COMMENT 'Редактирование размеров в мобилке',
- `size_editing_days_before` int(10) UNSIGNED NOT NULL DEFAULT 0 COMMENT 'Запрет изменения размеров за указанное количество дней до выдачи.',
- `postomat_email_notification` tinyint(1) NOT NULL DEFAULT 0 COMMENT 'Дублируют ли постоматы уведомления на Email.',
- `stock_availability_enable` tinyint(1) NOT NULL DEFAULT 0,
- `stock_availability_warehouse_id` int(10) UNSIGNED DEFAULT NULL COMMENT 'id склада по которому показывать наличие',
- `choice_nomenclature_lk` tinyint(1) NOT NULL DEFAULT 0 COMMENT 'Выбор номенклатур сотрудником'
-) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_general_ci;
-
--- --------------------------------------------------------
-
---
--- Table structure for table `bases_scripts`
---
-
-CREATE TABLE `bases_scripts` (
- `id` int(10) UNSIGNED NOT NULL,
- `product_id` int(10) UNSIGNED NOT NULL,
- `start_version` varchar(15) DEFAULT NULL,
- `end_version` varchar(15) NOT NULL,
- `script` mediumtext DEFAULT NULL
-) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_general_ci;
-
--- --------------------------------------------------------
-
---
--- Table structure for table `base_access`
---
-
-CREATE TABLE `base_access` (
- `id` int(10) UNSIGNED NOT NULL,
- `user_id` int(10) UNSIGNED NOT NULL,
- `base_id` int(10) UNSIGNED NOT NULL,
- `admin` tinyint(1) NOT NULL DEFAULT 0,
- `read_only` tinyint(1) NOT NULL DEFAULT 0,
- `torpedo` tinyint(1) NOT NULL DEFAULT 0 COMMENT 'База доступна в панели инструментов'
-) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_general_ci;
-
--- --------------------------------------------------------
-
---
--- Table structure for table `base_parameters`
---
-
-CREATE TABLE `base_parameters` (
- `name` varchar(20) NOT NULL,
- `str_value` varchar(100) DEFAULT NULL
-) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_general_ci;
-
-
-
-INSERT INTO `base_parameters` (`name`, `str_value`) VALUES
-('ProductCode', '5'),
-('product_name', 'ClientManager'),
-('version', '1.7');
-
--- --------------------------------------------------------
-
---
--- Table structure for table `bug_reports`
---
-
-CREATE TABLE `bug_reports` (
- `id` int(10) UNSIGNED NOT NULL,
- `created` datetime DEFAULT NULL,
- `last_update` datetime DEFAULT NULL,
- `product_id` int(10) UNSIGNED NOT NULL,
- `edition` varchar(20) DEFAULT NULL,
- `version` varchar(16) NOT NULL,
- `fixed_in_version` varchar(16) DEFAULT NULL COMMENT 'Версия в которой баг пофикшен',
- `message` varchar(2000) DEFAULT NULL,
- `stack_trace` text DEFAULT NULL,
- `description` text DEFAULT NULL,
- `email` varchar(600) DEFAULT NULL,
- `count` int(10) UNSIGNED DEFAULT 1,
- `status` enum('New','InWork','NeedInfo','Rejected','Later','Known','Unreproducable','EndOfLife','Done') DEFAULT 'New',
- `comments` text DEFAULT NULL
-) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_general_ci;
-
--- --------------------------------------------------------
-
---
--- Table structure for table `bug_reports_messages`
---
-
-CREATE TABLE `bug_reports_messages` (
- `id` int(10) UNSIGNED NOT NULL,
- `created` datetime NOT NULL,
- `bug_reports_id` int(10) UNSIGNED NOT NULL,
- `email` varchar(254) DEFAULT NULL,
- `user_name` varchar(60) DEFAULT NULL,
- `messages` text DEFAULT NULL,
- `db_name` varchar(60) DEFAULT NULL,
- `report_type` enum('User','Automatic','Known') NOT NULL DEFAULT 'User',
- `log_file` mediumtext DEFAULT NULL
-) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_general_ci;
-
--- --------------------------------------------------------
-
---
--- Table structure for table `clients`
---
-
-CREATE TABLE `clients` (
- `id` int(10) UNSIGNED NOT NULL,
- `name` varchar(300) NOT NULL,
- `email` varchar(45) DEFAULT NULL,
- `email_notifications` varchar(200) DEFAULT NULL COMMENT 'Адреса для уведомлений',
- `city` varchar(45) DEFAULT NULL,
- `comments` text DEFAULT NULL
-) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_general_ci;
-
--- --------------------------------------------------------
-
---
--- Table structure for table `cloud_users`
---
-
-CREATE TABLE `cloud_users` (
- `id` int(10) UNSIGNED NOT NULL,
- `login` varchar(20) NOT NULL,
- `name` varchar(80) DEFAULT NULL,
- `password` varchar(81) NOT NULL,
- `disabled` tinyint(1) NOT NULL DEFAULT 0 COMMENT 'Пользователь отключен',
- `is_account_admin` tinyint(1) NOT NULL DEFAULT 0 COMMENT 'Администратор учетной записи',
- `post` varchar(200) DEFAULT NULL COMMENT 'Должность',
- `phone` varchar(16) DEFAULT NULL COMMENT 'Телефон',
- `email` varchar(60) DEFAULT NULL,
- `account_id` int(10) UNSIGNED NOT NULL,
- `multi_ip` tinyint(1) NOT NULL DEFAULT 0,
- `client_id` int(10) UNSIGNED DEFAULT NULL,
- `comment` text DEFAULT NULL
-) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_general_ci;
-
--- --------------------------------------------------------
-
---
--- Table structure for table `products`
---
-
-CREATE TABLE `products` (
- `id` int(10) UNSIGNED NOT NULL,
- `name` varchar(45) NOT NULL,
- `internal_name` varchar(45) NOT NULL,
- `not_support_ver_regexp` varchar(45) DEFAULT NULL,
- `telegram_notify` varchar(50) DEFAULT NULL COMMENT 'Отправлять уведомления о новых ошибках в чат'
-) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_general_ci;
-
--- --------------------------------------------------------
-
---
--- Table structure for table `product_editions`
---
-
-CREATE TABLE `product_editions` (
- `id` int(10) UNSIGNED NOT NULL,
- `product_id` int(10) UNSIGNED NOT NULL,
- `code_number` int(10) UNSIGNED DEFAULT NULL COMMENT 'Номер редакции, внутри продукта',
- `code_name` varchar(10) DEFAULT NULL COMMENT 'Кодовое имя редакции.',
- `name` varchar(100) DEFAULT NULL COMMENT 'Название редакции отображаемое для пользователя.'
-) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_general_ci;
-
--- --------------------------------------------------------
-
---
--- Table structure for table `product_versions`
---
-
-CREATE TABLE `product_versions` (
- `id` int(10) UNSIGNED NOT NULL,
- `product_id` int(10) UNSIGNED NOT NULL,
- `modification` varchar(25) DEFAULT NULL,
- `channel` enum('Current','Stable') NOT NULL DEFAULT 'Current',
- `disable` tinyint(1) NOT NULL DEFAULT 0,
- `version_major` int(10) UNSIGNED NOT NULL DEFAULT 0,
- `version_minor` int(10) UNSIGNED NOT NULL DEFAULT 0,
- `version_build` int(10) UNSIGNED NOT NULL DEFAULT 0,
- `version_revision` int(10) UNSIGNED NOT NULL DEFAULT 0,
- `date` date NOT NULL,
- `link_install` varchar(256) DEFAULT NULL,
- `link_news` varchar(256) DEFAULT NULL,
- `changes` text DEFAULT NULL,
- `db_update` enum('None','Required','BreakingChange') NOT NULL DEFAULT 'None',
- `comment` text DEFAULT NULL
-) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_general_ci;
-
--- --------------------------------------------------------
-
---
--- Table structure for table `serial_numbers`
---
-
-CREATE TABLE `serial_numbers` (
- `id` int(10) UNSIGNED NOT NULL,
- `client_id` int(10) UNSIGNED NOT NULL,
- `number` varchar(50) NOT NULL,
- `recall` tinyint(1) NOT NULL DEFAULT 0,
- `notify_by_days` int(11) DEFAULT NULL COMMENT 'Уведомить за Н дней до окончания',
- `active_until` date DEFAULT NULL,
- `serial_expiry_date` date DEFAULT NULL COMMENT 'Дата окончания действия серийного номера',
- `instance` int(10) UNSIGNED NOT NULL DEFAULT 1,
- `comment` text DEFAULT NULL
-) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_general_ci;
-
--- --------------------------------------------------------
-
---
--- Table structure for table `servers`
---
-
-CREATE TABLE `servers` (
- `id` int(10) UNSIGNED NOT NULL,
- `server_address` varchar(60) NOT NULL,
- `service_user` varchar(16) NOT NULL,
- `service_password` varchar(81) NOT NULL,
- `comment` text DEFAULT NULL
-) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_general_ci;
-
--- --------------------------------------------------------
-
---
--- Table structure for table `sessions`
---
-
-CREATE TABLE `sessions` (
- `id` int(10) UNSIGNED NOT NULL,
- `session_id` varchar(36) NOT NULL,
- `user_id` int(10) UNSIGNED DEFAULT NULL,
- `account_id` int(10) UNSIGNED NOT NULL,
- `base_id` int(10) UNSIGNED NOT NULL,
- `start_time` datetime NOT NULL,
- `end_time` datetime NOT NULL,
- `is_closed` tinyint(1) NOT NULL DEFAULT 0,
- `login` varchar(40) DEFAULT NULL
-) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_general_ci;
-
--- --------------------------------------------------------
-
---
--- Table structure for table `telemetry_statistics`
---
-
-CREATE TABLE `telemetry_statistics` (
- `id` int(10) UNSIGNED NOT NULL,
- `last_update` datetime NOT NULL,
- `ip_address` varchar(39) DEFAULT NULL,
- `product` varchar(20) NOT NULL,
- `edition` varchar(20) DEFAULT NULL,
- `version` varchar(20) NOT NULL,
- `os` varchar(100) DEFAULT NULL,
- `net_framework` varchar(100) DEFAULT NULL,
- `is_demo` tinyint(1) NOT NULL DEFAULT 0,
- `app_edition` int(10) UNSIGNED DEFAULT NULL COMMENT 'Редакция программы',
- `base_employees` int(10) UNSIGNED DEFAULT NULL COMMENT 'Количество сотрудников в базе',
- `counter` mediumtext NOT NULL
-) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_general_ci;
-
--- --------------------------------------------------------
-
---
--- Table structure for table `update_info`
---
-
-CREATE TABLE `update_info` (
- `id` int(10) UNSIGNED NOT NULL,
- `product` varchar(25) NOT NULL,
- `edition` varchar(25) DEFAULT NULL,
- `serial_number` varchar(45) DEFAULT NULL,
- `start_version_major` int(10) UNSIGNED DEFAULT 0,
- `start_version_minor` int(10) UNSIGNED DEFAULT 0,
- `start_version_build` int(10) UNSIGNED DEFAULT 0,
- `start_version_revision` int(10) UNSIGNED DEFAULT 0,
- `new_version_major` int(10) UNSIGNED DEFAULT 0,
- `new_version_minor` int(10) UNSIGNED DEFAULT 0,
- `new_version_build` int(10) UNSIGNED DEFAULT 0,
- `new_version_revision` int(10) UNSIGNED DEFAULT 0,
- `link_install` varchar(256) NOT NULL,
- `link_news` varchar(256) DEFAULT NULL,
- `use_common` tinyint(1) DEFAULT 0,
- `update_description` text DEFAULT NULL
-) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_general_ci;
-
--- --------------------------------------------------------
-
---
--- Table structure for table `update_statistics`
---
-
-CREATE TABLE `update_statistics` (
- `id` int(10) UNSIGNED NOT NULL,
- `product_id` int(10) UNSIGNED DEFAULT NULL,
- `edition` varchar(25) DEFAULT NULL,
- `serial_number` varchar(45) DEFAULT NULL,
- `client_version` varchar(16) NOT NULL,
- `new_version` varchar(16) DEFAULT NULL,
- `date` datetime NOT NULL DEFAULT utc_timestamp(),
- `client_ip` varchar(15) DEFAULT NULL,
- `channel` enum('Current','Stable') NOT NULL DEFAULT 'Current',
- `status` enum('NoUpdates','NeedUpdate','Expired','Recalled','LicenceNotFound') DEFAULT NULL
-) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_general_ci;
-
--- --------------------------------------------------------
-
---
--- Table structure for table `users`
---
-
-CREATE TABLE `users` (
- `id` int(10) UNSIGNED NOT NULL,
- `name` varchar(45) NOT NULL,
- `login` varchar(45) NOT NULL,
- `deactivated` tinyint(1) NOT NULL DEFAULT 0,
- `email` varchar(60) DEFAULT NULL,
- `description` text DEFAULT NULL,
- `admin` tinyint(1) NOT NULL DEFAULT 0
-) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_general_ci;
-
---
--- Indexes for dumped tables
---
-
---
--- Indexes for table `accounts`
---
-ALTER TABLE `accounts`
- ADD PRIMARY KEY (`id`),
- ADD UNIQUE KEY `login_UNIQUE` (`login`),
- ADD KEY `fk_accounts_1_idx` (`client_id`);
-
---
--- Indexes for table `api_tokens`
---
-ALTER TABLE `api_tokens`
- ADD PRIMARY KEY (`id`),
- ADD UNIQUE KEY `token_UNIQUE` (`token`),
- ADD KEY `fk_api_tokens_1_idx` (`base_id`);
-
---
--- Indexes for table `bases`
---
-ALTER TABLE `bases`
- ADD PRIMARY KEY (`id`),
- ADD UNIQUE KEY `idx1_bases` (`base_name`,`account_id`),
- ADD UNIQUE KEY `base_guid_UNIQUE` (`base_guid`),
- ADD KEY `fk1_bases_idx` (`account_id`),
- ADD KEY `fk2_bases_idx` (`server_id`),
- ADD KEY `fk3_bases_idx` (`product_id`);
-
---
--- Indexes for table `bases_scripts`
---
-ALTER TABLE `bases_scripts`
- ADD PRIMARY KEY (`id`),
- ADD KEY `fk1_bases_scripts_idx` (`product_id`);
-
---
--- Indexes for table `base_access`
---
-ALTER TABLE `base_access`
- ADD PRIMARY KEY (`id`),
- ADD UNIQUE KEY `base_access_idx` (`user_id`,`base_id`),
- ADD KEY `fk1_base_access_idx` (`user_id`),
- ADD KEY `fk1_base_access_idx1` (`base_id`);
-
---
--- Indexes for table `base_parameters`
---
-ALTER TABLE `base_parameters`
- ADD PRIMARY KEY (`name`);
-
---
--- Indexes for table `bug_reports`
---
-ALTER TABLE `bug_reports`
- ADD PRIMARY KEY (`id`),
- ADD KEY `fk_bug_reports_1_idx` (`product_id`),
- ADD KEY `bug_reports_created` (`created`),
- ADD KEY `bug_reports_last_update` (`last_update`),
- ADD KEY `bug_reports_edition` (`edition`),
- ADD KEY `bug_reports_version` (`version`),
- ADD KEY `bug_reports_status` (`status`);
-
---
--- Indexes for table `bug_reports_messages`
---
-ALTER TABLE `bug_reports_messages`
- ADD PRIMARY KEY (`id`),
- ADD KEY `fk_bug_reports_messages_1_idx` (`bug_reports_id`);
-
---
--- Indexes for table `clients`
---
-ALTER TABLE `clients`
- ADD PRIMARY KEY (`id`);
-
---
--- Indexes for table `cloud_users`
---
-ALTER TABLE `cloud_users`
- ADD PRIMARY KEY (`id`),
- ADD UNIQUE KEY `idx1_users` (`login`,`account_id`),
- ADD KEY `fk1_users_idx` (`account_id`),
- ADD KEY `fk_cloud_users_1_idx` (`client_id`);
-
---
--- Indexes for table `products`
---
-ALTER TABLE `products`
- ADD PRIMARY KEY (`id`),
- ADD UNIQUE KEY `internal_name_UNIQUE` (`internal_name`);
-
---
--- Indexes for table `product_editions`
---
-ALTER TABLE `product_editions`
- ADD PRIMARY KEY (`id`),
- ADD KEY `fk_product_id_idx` (`product_id`);
-
---
--- Indexes for table `product_versions`
---
-ALTER TABLE `product_versions`
- ADD PRIMARY KEY (`id`),
- ADD KEY `_idx` (`product_id`);
-
---
--- Indexes for table `serial_numbers`
---
-ALTER TABLE `serial_numbers`
- ADD PRIMARY KEY (`id`),
- ADD UNIQUE KEY `number_UNIQUE` (`number`),
- ADD KEY `fk_serial_numbers_1_idx` (`client_id`);
-
---
--- Indexes for table `servers`
---
-ALTER TABLE `servers`
- ADD PRIMARY KEY (`id`);
-
---
--- Indexes for table `sessions`
---
-ALTER TABLE `sessions`
- ADD PRIMARY KEY (`id`),
- ADD UNIQUE KEY `session_id_UNIQUE` (`session_id`),
- ADD KEY `fk1_sessions_idx` (`account_id`),
- ADD KEY `fk2_sessions_idx` (`user_id`),
- ADD KEY `fk3_sessions_idx` (`base_id`),
- ADD KEY `end_time_idx` (`end_time`),
- ADD KEY `is_closed_idx` (`is_closed`);
-
---
--- Indexes for table `telemetry_statistics`
---
-ALTER TABLE `telemetry_statistics`
- ADD PRIMARY KEY (`id`),
- ADD KEY `index_telemetry_statistics_last_update` (`last_update`),
- ADD KEY `index_telemetry_statistics_ip` (`ip_address`),
- ADD KEY `index_telemetry_statistics_product` (`product`),
- ADD KEY `index_telemetry_statistics_edition` (`edition`),
- ADD KEY `index_telemetry_statistics_version` (`version`),
- ADD KEY `inxex_telemetry_statistics_os` (`os`);
-
---
--- Indexes for table `update_info`
---
-ALTER TABLE `update_info`
- ADD PRIMARY KEY (`id`);
-
---
--- Indexes for table `update_statistics`
---
-ALTER TABLE `update_statistics`
- ADD PRIMARY KEY (`id`),
- ADD UNIQUE KEY `id_UNIQUE` (`id`),
- ADD KEY `fk_update_statistics_1_idx` (`product_id`),
- ADD KEY `update_statistics_edition_idx` (`edition`),
- ADD KEY `update_statistics_serial_idx` (`serial_number`),
- ADD KEY `update_statistics_client_version_idx` (`client_version`),
- ADD KEY `update_statistics_ip_idx` (`client_ip`),
- ADD KEY `update_statistics_new_version_idx` (`new_version`),
- ADD KEY `update_statistics_date_idx` (`date`),
- ADD KEY `update_statistics_channel_idx` (`channel`);
-
---
--- Indexes for table `users`
---
-ALTER TABLE `users`
- ADD PRIMARY KEY (`id`);
-
---
--- AUTO_INCREMENT for dumped tables
---
-
---
--- AUTO_INCREMENT for table `accounts`
---
-ALTER TABLE `accounts`
- MODIFY `id` int(10) UNSIGNED NOT NULL AUTO_INCREMENT;
-
---
--- AUTO_INCREMENT for table `api_tokens`
---
-ALTER TABLE `api_tokens`
- MODIFY `id` int(10) UNSIGNED NOT NULL AUTO_INCREMENT;
-
---
--- AUTO_INCREMENT for table `bases`
---
-ALTER TABLE `bases`
- MODIFY `id` int(10) UNSIGNED NOT NULL AUTO_INCREMENT;
-
---
--- AUTO_INCREMENT for table `bases_scripts`
---
-ALTER TABLE `bases_scripts`
- MODIFY `id` int(10) UNSIGNED NOT NULL AUTO_INCREMENT;
-
---
--- AUTO_INCREMENT for table `base_access`
---
-ALTER TABLE `base_access`
- MODIFY `id` int(10) UNSIGNED NOT NULL AUTO_INCREMENT;
-
---
--- AUTO_INCREMENT for table `bug_reports`
---
-ALTER TABLE `bug_reports`
- MODIFY `id` int(10) UNSIGNED NOT NULL AUTO_INCREMENT;
-
---
--- AUTO_INCREMENT for table `bug_reports_messages`
---
-ALTER TABLE `bug_reports_messages`
- MODIFY `id` int(10) UNSIGNED NOT NULL AUTO_INCREMENT;
-
---
--- AUTO_INCREMENT for table `clients`
---
-ALTER TABLE `clients`
- MODIFY `id` int(10) UNSIGNED NOT NULL AUTO_INCREMENT;
-
---
--- AUTO_INCREMENT for table `cloud_users`
---
-ALTER TABLE `cloud_users`
- MODIFY `id` int(10) UNSIGNED NOT NULL AUTO_INCREMENT;
-
---
--- AUTO_INCREMENT for table `products`
---
-ALTER TABLE `products`
- MODIFY `id` int(10) UNSIGNED NOT NULL AUTO_INCREMENT;
-
---
--- AUTO_INCREMENT for table `product_editions`
---
-ALTER TABLE `product_editions`
- MODIFY `id` int(10) UNSIGNED NOT NULL AUTO_INCREMENT;
-
---
--- AUTO_INCREMENT for table `product_versions`
---
-ALTER TABLE `product_versions`
- MODIFY `id` int(10) UNSIGNED NOT NULL AUTO_INCREMENT;
-
---
--- AUTO_INCREMENT for table `serial_numbers`
---
-ALTER TABLE `serial_numbers`
- MODIFY `id` int(10) UNSIGNED NOT NULL AUTO_INCREMENT;
-
---
--- AUTO_INCREMENT for table `servers`
---
-ALTER TABLE `servers`
- MODIFY `id` int(10) UNSIGNED NOT NULL AUTO_INCREMENT;
-
---
--- AUTO_INCREMENT for table `sessions`
---
-ALTER TABLE `sessions`
- MODIFY `id` int(10) UNSIGNED NOT NULL AUTO_INCREMENT;
-
---
--- AUTO_INCREMENT for table `telemetry_statistics`
---
-ALTER TABLE `telemetry_statistics`
- MODIFY `id` int(10) UNSIGNED NOT NULL AUTO_INCREMENT;
-
---
--- AUTO_INCREMENT for table `update_info`
---
-ALTER TABLE `update_info`
- MODIFY `id` int(10) UNSIGNED NOT NULL AUTO_INCREMENT;
-
---
--- AUTO_INCREMENT for table `update_statistics`
---
-ALTER TABLE `update_statistics`
- MODIFY `id` int(10) UNSIGNED NOT NULL AUTO_INCREMENT;
-
---
--- AUTO_INCREMENT for table `users`
---
-ALTER TABLE `users`
- MODIFY `id` int(10) UNSIGNED NOT NULL AUTO_INCREMENT;
-
---
--- Constraints for dumped tables
---
-
---
--- Constraints for table `accounts`
---
-ALTER TABLE `accounts`
- ADD CONSTRAINT `fk_accounts_1` FOREIGN KEY (`client_id`) REFERENCES `clients` (`id`) ON DELETE SET NULL ON UPDATE CASCADE;
-
---
--- Constraints for table `api_tokens`
---
-ALTER TABLE `api_tokens`
- ADD CONSTRAINT `fk_api_tokens_1` FOREIGN KEY (`base_id`) REFERENCES `bases` (`id`) ON DELETE NO ACTION ON UPDATE NO ACTION;
-
---
--- Constraints for table `bases`
---
-ALTER TABLE `bases`
- ADD CONSTRAINT `fk1_bases` FOREIGN KEY (`account_id`) REFERENCES `accounts` (`id`) ON DELETE NO ACTION ON UPDATE NO ACTION,
- ADD CONSTRAINT `fk2_bases` FOREIGN KEY (`server_id`) REFERENCES `servers` (`id`) ON DELETE NO ACTION ON UPDATE NO ACTION,
- ADD CONSTRAINT `fk3_bases` FOREIGN KEY (`product_id`) REFERENCES `products` (`id`) ON DELETE NO ACTION ON UPDATE NO ACTION;
-
---
--- Constraints for table `bases_scripts`
---
-ALTER TABLE `bases_scripts`
- ADD CONSTRAINT `fk1_bases_scripts` FOREIGN KEY (`product_id`) REFERENCES `products` (`id`) ON DELETE NO ACTION ON UPDATE NO ACTION;
-
---
--- Constraints for table `base_access`
---
-ALTER TABLE `base_access`
- ADD CONSTRAINT `fk1_base_access` FOREIGN KEY (`user_id`) REFERENCES `cloud_users` (`id`) ON DELETE NO ACTION ON UPDATE NO ACTION,
- ADD CONSTRAINT `fk2_base_access` FOREIGN KEY (`base_id`) REFERENCES `bases` (`id`) ON DELETE NO ACTION ON UPDATE NO ACTION;
-
---
--- Constraints for table `bug_reports`
---
-ALTER TABLE `bug_reports`
- ADD CONSTRAINT `fk_bug_reports_1` FOREIGN KEY (`product_id`) REFERENCES `products` (`id`) ON DELETE CASCADE ON UPDATE NO ACTION;
-
---
--- Constraints for table `bug_reports_messages`
---
-ALTER TABLE `bug_reports_messages`
- ADD CONSTRAINT `fk_bug_reports_messages_1` FOREIGN KEY (`bug_reports_id`) REFERENCES `bug_reports` (`id`) ON DELETE CASCADE ON UPDATE CASCADE;
-
---
--- Constraints for table `cloud_users`
---
-ALTER TABLE `cloud_users`
- ADD CONSTRAINT `fk1_users` FOREIGN KEY (`account_id`) REFERENCES `accounts` (`id`) ON DELETE CASCADE ON UPDATE NO ACTION,
- ADD CONSTRAINT `fk_cloud_users_1` FOREIGN KEY (`client_id`) REFERENCES `clients` (`id`) ON DELETE NO ACTION ON UPDATE NO ACTION;
-
---
--- Constraints for table `product_editions`
---
-ALTER TABLE `product_editions`
- ADD CONSTRAINT `fk_product_id` FOREIGN KEY (`product_id`) REFERENCES `products` (`id`) ON DELETE NO ACTION ON UPDATE NO ACTION;
-
---
--- Constraints for table `product_versions`
---
-ALTER TABLE `product_versions`
- ADD CONSTRAINT `fk_product_versions_1` FOREIGN KEY (`product_id`) REFERENCES `products` (`id`) ON DELETE CASCADE ON UPDATE CASCADE;
-
---
--- Constraints for table `serial_numbers`
---
-ALTER TABLE `serial_numbers`
- ADD CONSTRAINT `fk_serial_numbers_1` FOREIGN KEY (`client_id`) REFERENCES `clients` (`id`) ON DELETE CASCADE ON UPDATE CASCADE;
-
---
--- Constraints for table `sessions`
---
-ALTER TABLE `sessions`
- ADD CONSTRAINT `fk1_sessions` FOREIGN KEY (`account_id`) REFERENCES `accounts` (`id`) ON DELETE NO ACTION ON UPDATE NO ACTION,
- ADD CONSTRAINT `fk2_sessions` FOREIGN KEY (`user_id`) REFERENCES `cloud_users` (`id`) ON DELETE NO ACTION ON UPDATE NO ACTION,
- ADD CONSTRAINT `fk3_sessions` FOREIGN KEY (`base_id`) REFERENCES `bases` (`id`) ON DELETE NO ACTION ON UPDATE NO ACTION;
-
---
--- Constraints for table `update_statistics`
---
-ALTER TABLE `update_statistics`
- ADD CONSTRAINT `fk_update_statistics_1` FOREIGN KEY (`product_id`) REFERENCES `products` (`id`) ON DELETE NO ACTION ON UPDATE NO ACTION;
-COMMIT;
-
-/*!40101 SET CHARACTER_SET_CLIENT=@OLD_CHARACTER_SET_CLIENT */;
-/*!40101 SET CHARACTER_SET_RESULTS=@OLD_CHARACTER_SET_RESULTS */;
-/*!40101 SET COLLATION_CONNECTION=@OLD_COLLATION_CONNECTION */;
From 84669e9c6ee15aee0bdaf2d40a7ee629ae6571fa Mon Sep 17 00:00:00 2001
From: J0shlerB0y <86416124+J0shlerB0y@users.noreply.github.com>
Date: Thu, 11 Jun 2026 14:32:18 +0300
Subject: [PATCH 19/19] =?UTF-8?q?=D0=B2=D0=B5=D1=80=D0=BD=D1=83=D0=BB=20?=
=?UTF-8?q?=D0=BA=D0=BE=D0=BC=D0=BC=D0=B5=D0=BD=D1=82=D0=B0=D1=80=D0=B8?=
=?UTF-8?q?=D0=B9?=
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
---
QS.Project.Core/Dialog/IProgressBarDisplayable.cs | 3 +++
1 file changed, 3 insertions(+)
diff --git a/QS.Project.Core/Dialog/IProgressBarDisplayable.cs b/QS.Project.Core/Dialog/IProgressBarDisplayable.cs
index 8ed6f1770..f8041b878 100644
--- a/QS.Project.Core/Dialog/IProgressBarDisplayable.cs
+++ b/QS.Project.Core/Dialog/IProgressBarDisplayable.cs
@@ -1,6 +1,9 @@
using System;
namespace QS.Dialog
{
+ ///
+ /// Интерфейс позволяющий управлять прогресс баром не зависимо от графического тул кита.
+ ///
public interface IProgressBarDisplayable
{
void Start(double maxValue = 1, double minValue = 0, string text = null, double startValue = 0);