Убраны попытки применить переменные окружения к сессии

This commit is contained in:
Roman Pytkov
2026-06-04 21:48:07 +03:00
parent 2f56bae044
commit a827bd608b
9 changed files with 4 additions and 152 deletions

View File

@@ -42,7 +42,7 @@ dotnet run --project src/Sms.TaskTwo.Avalonia/Sms.TaskTwo.Avalonia.csproj
{"SMS_MEAL_SERVER_URL":"URL сервера","SMS_MEAL_API_KEY":"ключ API"} {"SMS_MEAL_SERVER_URL":"URL сервера","SMS_MEAL_API_KEY":"ключ API"}
``` ```
Переменная записывается тем же механизмом, что и остальные, и доступна другим процессам после применения окружения ОС. Переменная записывается тем же механизмом, что и остальные, и доступна другим процессам после перезапуска сессии или приложений.
## Логирование ## Логирование
@@ -72,12 +72,10 @@ dotnet run --project src/Sms.TaskTwo.Avalonia/Sms.TaskTwo.Avalonia.csproj
### Windows ### Windows
- Чтение/запись: реестр `HKEY_CURRENT_USER\Environment`. - Чтение/запись: реестр `HKEY_CURRENT_USER\Environment`.
- **Применить к сессии** (`ReloadEnvironment`): обновляет env текущего процесса и рассылает `WM_SETTINGCHANGE` для других GUI-приложений.
### Linux ### Linux
- Запись в `~/.config/environment.d/` (systemd `KEY=value`). - Запись в `~/.config/environment.d/` (systemd `KEY=value`); переменные подхватываются после перезапуска login-сессии.
- **Применить к сессии** не поддерживается: переменные подхватываются после перезапуска login-сессии.
## Предположения (ТЗ) ## Предположения (ТЗ)

View File

@@ -52,7 +52,6 @@ public sealed class LinuxEnvironmentVariableStore : IEnvironmentVariableStore
var managed = LoadManagedFile(); var managed = LoadManagedFile();
managed[name] = value; managed[name] = value;
WriteManagedFileAtomic(managed); WriteManagedFileAtomic(managed);
System.Environment.SetEnvironmentVariable(name, value);
} }
catch (Exception ex) when (ex is UnauthorizedAccessException or IOException) catch (Exception ex) when (ex is UnauthorizedAccessException or IOException)
{ {
@@ -115,8 +114,6 @@ public sealed class LinuxEnvironmentVariableStore : IEnvironmentVariableStore
WriteConfFileAtomic(file, variables); WriteConfFileAtomic(file, variables);
} }
System.Environment.SetEnvironmentVariable(name, null);
} }
catch (Exception ex) when (ex is UnauthorizedAccessException or IOException) catch (Exception ex) when (ex is UnauthorizedAccessException or IOException)
{ {
@@ -126,15 +123,6 @@ public sealed class LinuxEnvironmentVariableStore : IEnvironmentVariableStore
} }
} }
public EnvironmentReloadResult ReloadEnvironment() =>
new()
{
Success = false,
Message =
"Применение переменных к сессии в Linux не поддерживается. " +
"Значения сохраняются в ~/.config/environment.d/ и подхватываются после перезапуска login-сессии.",
};
private Dictionary<string, string> LoadManagedFile() private Dictionary<string, string> LoadManagedFile()
{ {
if (!File.Exists(_managedFilePath)) if (!File.Exists(_managedFilePath))

View File

@@ -1,4 +1,3 @@
using System.Runtime.InteropServices;
using Microsoft.Win32; using Microsoft.Win32;
using Sms.Environment; using Sms.Environment;
@@ -6,8 +5,6 @@ namespace Sms.Environment.Windows;
public sealed class WindowsEnvironmentVariableStore : IEnvironmentVariableStore public sealed class WindowsEnvironmentVariableStore : IEnvironmentVariableStore
{ {
private const int HWND_BROADCAST = 0xffff;
private const int WM_SETTINGCHANGE = 0x001A;
private const string EnvironmentKeyPath = "Environment"; private const string EnvironmentKeyPath = "Environment";
public string? Get(string name) => public string? Get(string name) =>
@@ -25,8 +22,6 @@ public sealed class WindowsEnvironmentVariableStore : IEnvironmentVariableStore
using var key = Registry.CurrentUser.OpenSubKey(EnvironmentKeyPath, writable: true) using var key = Registry.CurrentUser.OpenSubKey(EnvironmentKeyPath, writable: true)
?? Registry.CurrentUser.CreateSubKey(EnvironmentKeyPath, writable: true); ?? Registry.CurrentUser.CreateSubKey(EnvironmentKeyPath, writable: true);
key.SetValue(name, value, RegistryValueKind.ExpandString); key.SetValue(name, value, RegistryValueKind.ExpandString);
System.Environment.SetEnvironmentVariable(name, value, EnvironmentVariableTarget.Process);
BroadcastEnvironmentChange();
} }
public bool Exists(string name) => Get(name) is not null; public bool Exists(string name) => Get(name) is not null;
@@ -79,27 +74,6 @@ public sealed class WindowsEnvironmentVariableStore : IEnvironmentVariableStore
{ {
using var key = Registry.CurrentUser.OpenSubKey(EnvironmentKeyPath, writable: true); using var key = Registry.CurrentUser.OpenSubKey(EnvironmentKeyPath, writable: true);
key?.DeleteValue(name, throwOnMissingValue: false); key?.DeleteValue(name, throwOnMissingValue: false);
System.Environment.SetEnvironmentVariable(name, null, EnvironmentVariableTarget.Process);
BroadcastEnvironmentChange();
}
public EnvironmentReloadResult ReloadEnvironment()
{
var variables = GetUserPersistedEnvironment();
foreach (var pair in variables)
{
System.Environment.SetEnvironmentVariable(pair.Key, pair.Value, EnvironmentVariableTarget.Process);
}
BroadcastEnvironmentChange();
return new EnvironmentReloadResult
{
Success = true,
Message = variables.Count == 0
? "Пользовательских переменных для применения нет."
: $"Применено {variables.Count} переменных к текущему процессу и отправлено WM_SETTINGCHANGE для других приложений.",
};
} }
private static Dictionary<string, string> ToDictionary(System.Collections.IDictionary source) private static Dictionary<string, string> ToDictionary(System.Collections.IDictionary source)
@@ -112,36 +86,4 @@ public sealed class WindowsEnvironmentVariableStore : IEnvironmentVariableStore
return result; return result;
} }
private static void BroadcastEnvironmentChange()
{
try
{
_ = NativeMethods.SendMessageTimeout(
HWND_BROADCAST,
WM_SETTINGCHANGE,
IntPtr.Zero,
"Environment",
0,
1000,
out _);
}
catch
{
// Non-critical: new processes still see updated registry values.
}
}
private static class NativeMethods
{
[DllImport("user32.dll", CharSet = CharSet.Auto, SetLastError = true)]
public static extern IntPtr SendMessageTimeout(
int hWnd,
int msg,
IntPtr wParam,
string lParam,
int fuFlags,
int uTimeout,
out IntPtr lpdwResult);
}
} }

View File

@@ -1,8 +0,0 @@
namespace Sms.Environment;
public sealed class EnvironmentReloadResult
{
public required bool Success { get; init; }
public required string Message { get; init; }
}

View File

@@ -19,6 +19,4 @@ public interface IEnvironmentVariableStore
bool IsPersistedInUserStore(string name); bool IsPersistedInUserStore(string name);
void RemoveFromUserStore(string name); void RemoveFromUserStore(string name);
EnvironmentReloadResult ReloadEnvironment();
} }

View File

@@ -22,7 +22,7 @@
<Grid RowDefinitions="Auto,Auto,Auto,*"> <Grid RowDefinitions="Auto,Auto,Auto,*">
<Grid Grid.Row="0" <Grid Grid.Row="0"
Margin="12,12,12,0" Margin="12,12,12,0"
ColumnDefinitions="*,Auto,Auto" ColumnDefinitions="*,Auto"
ColumnSpacing="8"> ColumnSpacing="8">
<CheckBox Content="Отображать все переменные" <CheckBox Content="Отображать все переменные"
IsChecked="{Binding ShowAllVariables}" IsChecked="{Binding ShowAllVariables}"
@@ -31,10 +31,6 @@
Content="Обновить" Content="Обновить"
Command="{Binding RefreshCommand}" Command="{Binding RefreshCommand}"
MinWidth="100" /> MinWidth="100" />
<Button Grid.Column="2"
Content="Применить к сессии"
Command="{Binding ReloadEnvironmentCommand}"
MinWidth="140" />
</Grid> </Grid>
<Grid Grid.Row="1" <Grid Grid.Row="1"
@@ -62,10 +58,6 @@
<TextBlock Foreground="#C62828" <TextBlock Foreground="#C62828"
Text="{Binding AddVariableError}" Text="{Binding AddVariableError}"
IsVisible="{Binding AddVariableError, Converter={x:Static StringConverters.IsNotNullOrEmpty}}" /> IsVisible="{Binding AddVariableError, Converter={x:Static StringConverters.IsNotNullOrEmpty}}" />
<TextBlock Foreground="#2E7D32"
Text="{Binding ReloadEnvironmentMessage}"
TextWrapping="Wrap"
IsVisible="{Binding ReloadEnvironmentMessage, Converter={x:Static StringConverters.IsNotNullOrEmpty}}" />
</StackPanel> </StackPanel>
<Border Grid.Row="3" <Border Grid.Row="3"
@@ -107,11 +99,6 @@
Binding="{Binding RequiredValue, Mode=TwoWay}" Binding="{Binding RequiredValue, Mode=TwoWay}"
Width="2*" Width="2*"
MinWidth="140" /> MinWidth="140" />
<DataGridTextColumn Header="Актуальное значение"
Binding="{Binding ActualValueDisplay}"
IsReadOnly="True"
Width="2*"
MinWidth="140" />
<DataGridTextColumn Header="Комментарий" <DataGridTextColumn Header="Комментарий"
Binding="{Binding Comment, Mode=TwoWay}" Binding="{Binding Comment, Mode=TwoWay}"
Width="240" Width="240"

View File

@@ -142,17 +142,6 @@ public sealed class EnvironmentVariablesService
public string GetDisplayValue(string name) => ResolveRequiredValue(name); public string GetDisplayValue(string name) => ResolveRequiredValue(name);
public string? GetProcessValue(string name) =>
_store.GetProcessEnvironment().TryGetValue(name, out var value) ? value : null;
public EnvironmentReloadResult ReloadEnvironment()
{
var result = _store.ReloadEnvironment();
_log.WriteLine(
$"{DateTime.Now:yyyy-MM-dd HH:mm:ss} [INFO] Reload environment: success={result.Success}; {result.Message}");
return result;
}
public void SaveValue(string name, string value) public void SaveValue(string name, string value)
{ {
var previous = _store.GetUserPersistedValue(name); var previous = _store.GetUserPersistedValue(name);

View File

@@ -21,7 +21,6 @@ public sealed partial class EnvironmentVariableRowViewModel : ObservableObject
_service = service; _service = service;
_onCustomRemoved = onCustomRemoved; _onCustomRemoved = onCustomRemoved;
ApplySnapshot(row, suppressSave: true); ApplySnapshot(row, suppressSave: true);
RefreshActualValue(_service.GetProcessValue(Field));
} }
public string Field { get; } public string Field { get; }
@@ -35,12 +34,6 @@ public sealed partial class EnvironmentVariableRowViewModel : ObservableObject
[ObservableProperty] [ObservableProperty]
private bool _useUserStore; private bool _useUserStore;
[ObservableProperty]
private string _actualValue = string.Empty;
public string ActualValueDisplay =>
string.IsNullOrEmpty(ActualValue) ? "<нет>" : ActualValue;
[ObservableProperty] [ObservableProperty]
private string _requiredValue = string.Empty; private string _requiredValue = string.Empty;
@@ -67,7 +60,6 @@ public sealed partial class EnvironmentVariableRowViewModel : ObservableObject
if (value) if (value)
{ {
_service.SaveValue(Field, RequiredValue); _service.SaveValue(Field, RequiredValue);
RefreshActualValue(_service.GetProcessValue(Field));
return; return;
} }
@@ -82,11 +74,8 @@ public sealed partial class EnvironmentVariableRowViewModel : ObservableObject
BeginLoad(); BeginLoad();
RequiredValue = _service.GetDisplayValue(Field); RequiredValue = _service.GetDisplayValue(Field);
EndLoad(); EndLoad();
RefreshActualValue(_service.GetProcessValue(Field));
} }
partial void OnActualValueChanged(string value) => OnPropertyChanged(nameof(ActualValueDisplay));
partial void OnRequiredValueChanged(string value) partial void OnRequiredValueChanged(string value)
{ {
if (_isLoading || !UseUserStore) if (_isLoading || !UseUserStore)
@@ -95,7 +84,6 @@ public sealed partial class EnvironmentVariableRowViewModel : ObservableObject
} }
_service.SaveValue(Field, value); _service.SaveValue(Field, value);
RefreshActualValue(_service.GetProcessValue(Field));
} }
public event EventHandler? RowAppearanceChanged; public event EventHandler? RowAppearanceChanged;
@@ -117,11 +105,6 @@ public sealed partial class EnvironmentVariableRowViewModel : ObservableObject
} }
} }
public void RefreshActualValue(string? processValue)
{
ActualValue = processValue ?? string.Empty;
}
public void BeginLoad() => _isLoading = true; public void BeginLoad() => _isLoading = true;
public void EndLoad() => _isLoading = false; public void EndLoad() => _isLoading = false;

View File

@@ -32,27 +32,12 @@ public sealed partial class MainWindowViewModel : ObservableObject
[ObservableProperty] [ObservableProperty]
private string? _addVariableError; private string? _addVariableError;
[ObservableProperty]
private string? _reloadEnvironmentMessage;
partial void OnShowAllVariablesChanged(bool value) => SyncRowsFromService(); partial void OnShowAllVariablesChanged(bool value) => SyncRowsFromService();
partial void OnNewVariableNameChanged(string value) => AddVariableCommand.NotifyCanExecuteChanged(); partial void OnNewVariableNameChanged(string value) => AddVariableCommand.NotifyCanExecuteChanged();
[RelayCommand] [RelayCommand]
private void Refresh() private void Refresh() => SyncRowsFromService();
{
SyncRowsFromService();
RefreshProcessStates();
}
[RelayCommand]
private void ReloadEnvironment()
{
var result = _service.ReloadEnvironment();
ReloadEnvironmentMessage = result.Message;
RefreshProcessStates();
}
[RelayCommand(CanExecute = nameof(CanAddVariable))] [RelayCommand(CanExecute = nameof(CanAddVariable))]
private void AddVariable() private void AddVariable()
@@ -142,16 +127,6 @@ public sealed partial class MainWindowViewModel : ObservableObject
MoveItem(Rows, currentIndex, targetIndex); MoveItem(Rows, currentIndex, targetIndex);
} }
} }
RefreshProcessStates();
}
private void RefreshProcessStates()
{
foreach (var row in Rows)
{
row.RefreshActualValue(_service.GetProcessValue(row.Field));
}
} }
private static void MoveItem( private static void MoveItem(