Skip to content

Commit d372e87

Browse files
committed
Fix #700: Local configuration not saved when slnx solution file is used
1 parent 657914b commit d372e87

File tree

14 files changed

+135
-88
lines changed

14 files changed

+135
-88
lines changed

src/.editorconfig

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -220,3 +220,5 @@ dotnet_diagnostic.IDE0305.severity = suggestion
220220
# CA1515: Because an application's API isn't typically referenced from outside the assembly, types can be made internal
221221
dotnet_diagnostic.CA1515.severity = suggestion
222222

223+
# IDE0028: Simplify collection initialization
224+
dotnet_diagnostic.IDE0028.severity = suggestion

src/ResXManager.Model/ConfigurationBase.cs

Lines changed: 79 additions & 28 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,8 @@
99

1010
using AutoProperties;
1111

12+
using PropertyChanged;
13+
1214
using ResXManager.Infrastructure;
1315

1416
using Throttle;
@@ -25,44 +27,47 @@ public sealed class ForceGlobalAttribute : Attribute
2527
/// </summary>
2628
public abstract class ConfigurationBase : INotifyPropertyChanged
2729
{
28-
private const string FileName = "Configuration.xml";
30+
private const string GlobalConfigFileName = "Configuration.xml";
31+
private const string SolutionConfigFileName = "ResXManager.config.xml";
2932

30-
private static readonly string _directory = Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.LocalApplicationData), "tom-englert.de", "ResXManager");
33+
private static readonly string _appDataFolder = Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.LocalApplicationData), "tom-englert.de", "ResXManager");
3134

32-
private readonly string _filePath;
33-
private readonly XmlConfiguration _configuration;
35+
private readonly string _globalConfigFilePath;
36+
private readonly XmlConfiguration _globalConfiguration;
3437
private readonly Dictionary<string, object> _cachedObjects = new();
3538

39+
private XmlConfiguration? _solutionConfiguration;
40+
private string? _solutionConfigFilePath;
41+
private readonly PropertyInfo[] _allPublicProperties;
42+
3643
protected ConfigurationBase(ITracer tracer)
3744
{
3845
Tracer = tracer;
39-
_filePath = Path.Combine(_directory, FileName);
40-
41-
try
42-
{
43-
Directory.CreateDirectory(_directory);
44-
45-
using var reader = new StreamReader(File.OpenRead(_filePath));
46+
_allPublicProperties = GetType().GetProperties(BindingFlags.Public | BindingFlags.Instance);
4647

47-
_configuration = new XmlConfiguration(tracer, reader);
48+
_globalConfigFilePath = Path.Combine(_appDataFolder, GlobalConfigFileName);
49+
_globalConfiguration = LoadConfiguration(_globalConfigFilePath, tracer);
50+
}
4851

49-
return;
50-
}
51-
catch
52-
{
53-
// can't read configuration, just go with default.
54-
}
52+
[InterceptIgnore]
53+
public bool IsScopeSupported => true;
5554

56-
_configuration = new XmlConfiguration(tracer);
57-
}
55+
[InterceptIgnore]
56+
public ConfigurationScope Scope { get; private set; }
5857

59-
public abstract bool IsScopeSupported { get; }
58+
[InterceptIgnore]
59+
public XmlConfiguration EffectiveConfiguration => _solutionConfiguration ?? _globalConfiguration;
6060

61-
public abstract ConfigurationScope Scope { get; }
61+
[InterceptIgnore]
62+
public string EffectiveConfigFilePath => _solutionConfigFilePath ?? _globalConfigFilePath;
6263

6364
[InterceptIgnore]
6465
protected ITracer Tracer { get; }
6566

67+
[InterceptIgnore]
68+
[OnChangedMethod(nameof(OnSolutionFolderChanged))]
69+
public string? SolutionFolder { get; set; }
70+
6671
[GetInterceptor]
6772
protected T? GetProperty<T>(string key, PropertyInfo propertyInfo)
6873
{
@@ -107,7 +112,7 @@ protected ConfigurationBase(ITracer tracer)
107112

108113
protected virtual T? InternalGetValue<T>(T? defaultValue, string key)
109114
{
110-
return ConvertFromString(_configuration.GetValue(key, null), defaultValue);
115+
return ConvertFromString(EffectiveConfiguration.GetValue(key, null), defaultValue);
111116
}
112117

113118
[SetInterceptor]
@@ -122,7 +127,9 @@ protected virtual void InternalSetValue<T>(T? value, string key, bool forceGloba
122127
{
123128
try
124129
{
125-
_configuration.SetValue(key, ConvertToString(value));
130+
var configuration = forceGlobal ? _globalConfiguration : EffectiveConfiguration;
131+
132+
configuration.SetValue(key, ConvertToString(value));
126133

127134
Save();
128135
}
@@ -137,13 +144,13 @@ private void Save()
137144
{
138145
try
139146
{
140-
using var writer = new StreamWriter(File.Create(_filePath));
147+
using var writer = new StreamWriter(File.Create(EffectiveConfigFilePath));
141148

142-
_configuration.Save(writer);
149+
EffectiveConfiguration.Save(writer);
143150
}
144151
catch (Exception ex)
145152
{
146-
Tracer.TraceError($"Fatal error writing configuration file: {_filePath} - {ex.Message}");
153+
Tracer.TraceError($"Fatal error writing configuration file: {EffectiveConfigFilePath} - {ex.Message}");
147154
}
148155
}
149156

@@ -212,6 +219,50 @@ private static TypeConverter GetTypeConverter(Type type)
212219

213220
protected virtual void OnPropertyChanged(string propertyName)
214221
{
215-
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
222+
PropertyChanged?.Invoke(this, new(propertyName));
223+
}
224+
225+
private void OnSolutionFolderChanged(string oldValue, string newValue)
226+
{
227+
if (newValue.IsNullOrEmpty())
228+
{
229+
_solutionConfigFilePath = null;
230+
_solutionConfiguration = null;
231+
Scope = ConfigurationScope.Global;
232+
}
233+
else
234+
{
235+
_solutionConfigFilePath = Path.Combine(newValue, SolutionConfigFileName);
236+
_solutionConfiguration = LoadConfiguration(_solutionConfigFilePath, Tracer);
237+
Scope = ConfigurationScope.Solution;
238+
}
239+
240+
NotifyAll();
241+
}
242+
243+
private static XmlConfiguration LoadConfiguration(string filePath, ITracer tracer)
244+
{
245+
try
246+
{
247+
Directory.CreateDirectory(Path.GetDirectoryName(filePath));
248+
249+
using var reader = new StreamReader(File.OpenRead(filePath));
250+
251+
return new(tracer, reader);
252+
}
253+
catch
254+
{
255+
// can't read configuration, just go with default.
256+
}
257+
258+
return new(tracer);
259+
}
260+
261+
private void NotifyAll()
262+
{
263+
foreach (var property in _allPublicProperties)
264+
{
265+
OnPropertyChanged(property.Name);
266+
}
216267
}
217268
}

src/ResXManager.Model/IConfiguration.cs

Lines changed: 10 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,8 @@
66

77
public interface IConfiguration : INotifyPropertyChanged
88
{
9+
string? SolutionFolder { get; set; }
10+
911
bool IsScopeSupported { get; }
1012

1113
ConfigurationScope Scope { get; }
@@ -34,28 +36,28 @@ public interface IConfiguration : INotifyPropertyChanged
3436

3537
StringComparison ResXSortingComparison { get; }
3638

37-
public bool PrefixTranslations { get; }
39+
bool PrefixTranslations { get; }
3840

39-
public string? TranslationPrefix { get; }
41+
string? TranslationPrefix { get; }
4042

41-
public string? EffectiveTranslationPrefix { get; }
43+
string? EffectiveTranslationPrefix { get; }
4244

4345
/// <summary>
4446
/// Apply translation prefix to the value
4547
/// </summary>
46-
public bool PrefixValue { get; }
48+
bool PrefixValue { get; }
4749

4850
/// <summary>
4951
/// Apply translation prefix to comment of neutral language
5052
/// </summary>
51-
public bool PrefixNeutralComment { get; }
53+
bool PrefixNeutralComment { get; }
5254

5355
/// <summary>
5456
/// Apply translation prefix to comment of target language
5557
/// </summary>
56-
public bool PrefixTargetComment { get; }
58+
bool PrefixTargetComment { get; }
5759

58-
public ExcelExportMode ExcelExportMode { get; }
60+
ExcelExportMode ExcelExportMode { get; }
5961

6062
bool ShowPerformanceTraces { get; }
6163

@@ -66,4 +68,4 @@ public interface IConfiguration : INotifyPropertyChanged
6668
bool AutoApplyExistingTranslations { get; }
6769

6870
string? CultureCountyOverrides { get; set; }
69-
}
71+
}
Lines changed: 12 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -1,14 +1,12 @@
1-
namespace ResXManager.View.Tools;
2-
3-
using System.Collections.Generic;
4-
using System.Threading;
5-
using System.Threading.Tasks;
6-
7-
using ResXManager.Model;
8-
9-
public interface ISourceFilesProvider
10-
{
11-
Task<IList<ProjectFile>> GetSourceFilesAsync(CancellationToken? cancellationToken);
12-
13-
string? SolutionFolder { get; }
14-
}
1+
namespace ResXManager.Model;
2+
3+
using System.Collections.Generic;
4+
using System.Threading;
5+
using System.Threading.Tasks;
6+
7+
public interface ISourceFilesProvider
8+
{
9+
Task<IList<ProjectFile>> GetSourceFilesAsync(CancellationToken? cancellationToken);
10+
11+
string? SolutionFolder { get; }
12+
}

src/ResXManager.Model/ResourceManager.cs

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -101,6 +101,7 @@ public void Save()
101101

102102
private void OnSolutionFolderChanged()
103103
{
104+
Configuration.SolutionFolder = SolutionFolder;
104105
SolutionFolderChanged?.Invoke(this, new TextEventArgs(SolutionFolder ?? string.Empty));
105106
}
106107

@@ -316,4 +317,4 @@ private void OnPropertyChanged(string propertyName)
316317
{
317318
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
318319
}
319-
}
320+
}

src/ResXManager.Scripting/Host.cs

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -166,6 +166,8 @@ private bool CanEdit(ResourceEntity entity, CultureKey? cultureKey)
166166
[Shared]
167167
public class Configuration : IConfiguration
168168
{
169+
public string? SolutionFolder { get; set; }
170+
169171
public bool IsScopeSupported { get; set; }
170172

171173
public ConfigurationScope Scope { get; set; }
@@ -224,4 +226,4 @@ protected virtual void OnPropertyChanged(string propertyName)
224226
{
225227
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
226228
}
227-
}
229+
}

src/ResXManager.VSIX.Compatibility.Shared/DteConfiguration.cs

Lines changed: 18 additions & 26 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,8 @@
1212

1313
using static Microsoft.VisualStudio.Shell.ThreadHelper;
1414

15+
using Configuration = ResXManager.Model.Configuration;
16+
1517
[Shared]
1618
[Export(typeof(IConfiguration))]
1719
[Export(typeof(IDteConfiguration))]
@@ -38,38 +40,26 @@ public DteConfiguration(DteSolution solution, ITracer tracer)
3840
[DefaultValue(TaskErrorCategory.Warning)]
3941
public TaskErrorCategory TaskErrorCategory { get; set; }
4042

41-
public override bool IsScopeSupported => true;
42-
43-
public override ConfigurationScope Scope
44-
{
45-
get
46-
{
47-
ThrowIfNotOnUIThread();
48-
return _solution.Globals != null ? ConfigurationScope.Solution : ConfigurationScope.Global;
49-
}
50-
}
51-
5243
protected override T? InternalGetValue<T>(T? defaultValue, string key) where T : default
5344
{
5445
ThrowIfNotOnUIThread();
5546

56-
return TryGetValue(GetKey(key), defaultValue, out var value) ? value : base.InternalGetValue(defaultValue, key);
47+
// Convert old solution settings to new ones.
48+
if (!TryGetValue(GetKey(key), defaultValue, out var value))
49+
return base.InternalGetValue(defaultValue, key);
50+
51+
TryClearValue<T>(_solution.Globals, GetKey(key));
52+
base.InternalSetValue(value, key, false);
53+
return value;
5754
}
5855

5956
protected override void InternalSetValue<T>(T? value, string key, bool forceGlobal) where T : default
6057
{
6158
ThrowIfNotOnUIThread();
6259

63-
var globals = _solution.Globals;
64-
65-
if (globals != null && !forceGlobal)
66-
{
67-
TrySetValue(globals, GetKey(key), value);
68-
}
69-
else
70-
{
71-
base.InternalSetValue(value, key, forceGlobal);
72-
}
60+
TryClearValue<T>(_solution.Globals, GetKey(key));
61+
62+
base.InternalSetValue(value, key, forceGlobal);
7363
}
7464

7565
private bool TryGetValue<T>(string? key, T? defaultValue, out T? value)
@@ -101,14 +91,16 @@ private static bool TryGetValue<T>(EnvDTE.Globals? globals, string? key, ref T?
10191
return false;
10292
}
10393

104-
private void TrySetValue<T>(EnvDTE.Globals globals, string? internalKey, T? value)
94+
private void TryClearValue<T>(EnvDTE.Globals? globals, string? internalKey)
10595
{
10696
ThrowIfNotOnUIThread();
10797

98+
if (globals == null || string.IsNullOrEmpty(internalKey))
99+
return;
100+
108101
try
109102
{
110-
globals[internalKey] = ConvertToString(value);
111-
globals.VariablePersists[internalKey] = true;
103+
globals.VariablePersists[internalKey] = false;
112104
}
113105
catch (Exception ex)
114106
{
@@ -120,4 +112,4 @@ private static string GetKey(string? propertyName)
120112
{
121113
return @"RESX_" + propertyName;
122114
}
123-
}
115+
}

src/ResXManager.VSIX.Compatibility.Shared/DteSourceFilesProvider.cs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -49,4 +49,4 @@ public string SolutionFolder
4949
return _solution.SolutionFolder;
5050
}
5151
}
52-
}
52+
}

src/ResXManager.VSIX/Properties/AssemblyInfo.cs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -41,4 +41,4 @@
4141
[assembly: ProvideCodeBase(CodeBase = "$PackageFolder$\\System.Text.Encodings.Web.dll")]
4242
[assembly: ProvideCodeBase(CodeBase = "$PackageFolder$\\System.Text.Json.dll")]
4343
[assembly: ProvideCodeBase(CodeBase = "$PackageFolder$\\System.Threading.Tasks.Extensions.dll")]
44-
[assembly: ProvideCodeBase(CodeBase = "$PackageFolder$\\System.ValueTuple.dll")]
44+
// [assembly: ProvideCodeBase(CodeBase = "$PackageFolder$\\System.ValueTuple.dll")]

src/ResXManager.View/Visuals/ResourceViewModel.cs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -111,7 +111,7 @@ public CollectionView GroupedResourceTableEntries
111111

112112
public ICommand ReloadCommand => new DelegateCommand(Reload);
113113

114-
public ICommand SaveCommand => new DelegateCommand(() => ResourceManager.HasChanges, () => ResourceManager.Save());
114+
public ICommand SaveCommand => new DelegateCommand(() => ResourceManager.HasChanges, ResourceManager.Save);
115115

116116
public ICommand BeginFindCodeReferencesCommand => new DelegateCommand(BeginFindCodeReferences);
117117

0 commit comments

Comments
 (0)