Vereinsmeisterschaften  22aa7800eae54b428d40e835886cefe1fdefdfdf
This is a software that can be used to manage the internal competition of the swimming club Illertissen called "Vereinsmeisterschaften".
Loading...
Searching...
No Matches
WorkspaceService.cs
1using CommunityToolkit.Mvvm.ComponentModel;
2using System.Collections.ObjectModel;
3using System.Globalization;
4using System.Resources;
8
10{
14 public partial class WorkspaceService : ObservableObject, IWorkspaceService
15 {
20
24 public event EventHandler OnFileFinished;
25
26 // ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
27
28 private ObservableCollection<string> _lastWorkspacePaths = new ObservableCollection<string>();
30 public ObservableCollection<string> LastWorkspacePaths
31 {
32 get => _lastWorkspacePaths;
33 set { _lastWorkspacePaths = value; OnPropertyChanged(); }
34 }
35
37 public void AddLastWorkspacePath(string path)
38 {
39 if (LastWorkspacePaths.Contains(path))
40 {
41 LastWorkspacePaths.Remove(path);
42 }
43 LastWorkspacePaths.Insert(0, path);
44 }
45
48 => LastWorkspacePaths?.Clear();
49
50 // ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
51
55 public string PersistentPath { get; private set; } = string.Empty;
56
57 private bool _isWorkspaceOpen;
61 public bool IsWorkspaceOpen
62 {
63 get => _isWorkspaceOpen;
64 set
65 {
66 if (SetProperty(ref _isWorkspaceOpen, value))
67 {
68 OnPropertyChangedAllHasUnsavedChanges();
69 }
70 }
71 }
72
74 [ObservableProperty]
76
77 // ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
78
82 public bool HasUnsavedChanges_Persons => _personService?.HasUnsavedChanges ?? false;
83
87 public bool HasUnsavedChanges_Competitions => (_competitionService?.HasUnsavedChanges ?? false) || (_competitionDistanceRuleService?.HasUnsavedChanges ?? false);
88
92 public bool HasUnsavedChanges_Races => _raceService?.HasUnsavedChanges ?? false;
93
97 public bool HasUnsavedChanges_Settings => _settings != null && SettingsPersistedInFile != null && (!Settings?.Equals(SettingsPersistedInFile) ?? true);
98
104
105 private void OnPropertyChangedAllHasUnsavedChanges()
106 {
107 OnPropertyChanged(nameof(HasUnsavedChanges_Persons));
108 OnPropertyChanged(nameof(HasUnsavedChanges_Competitions));
109 OnPropertyChanged(nameof(HasUnsavedChanges_Races));
110 OnPropertyChanged(nameof(HasUnsavedChanges_Settings));
111 OnPropertyChanged(nameof(HasUnsavedChanges));
112 }
113
114 // ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
115
116 #region Reset methods of services
117
122 {
123 _competitionService.ResetToLoadedState();
124 _competitionDistanceRuleService.ResetToLoadedState();
125 }
126
130 public void ResetPersonsToLoadedState() => _personService.ResetToLoadedState();
131
135 public void ResetRacesToLoadedState() => _raceService.ResetToLoadedState();
136
141 {
142 if (SettingsPersistedInFile != null)
143 {
144 // Unsubscribe from the old settings PropertyChanged event
145 if (Settings != null) { Settings.PropertyChanged -= Settings_PropertyChanged; }
146
148 Settings.PropertyChanged += Settings_PropertyChanged;
149 }
150 }
151
152 #endregion
153
154 // ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
155
156 private WorkspaceSettings _settings;
161 {
162 get => _settings;
163 set
164 {
165 if (SetProperty(ref _settings, value))
166 {
167 OnPropertyChanged(nameof(HasUnsavedChanges_Settings));
168 OnPropertyChanged(nameof(HasUnsavedChanges));
169 }
170 }
171 }
172
173 private void Settings_PropertyChanged(object sender, System.ComponentModel.PropertyChangedEventArgs e)
174 {
175 OnPropertyChanged(nameof(Settings));
176 OnPropertyChanged(nameof(HasUnsavedChanges_Settings));
177 OnPropertyChanged(nameof(HasUnsavedChanges));
178 }
179
180 // ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
181
185 [ObservableProperty]
187
188 // ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
189
190 private IPersonService _personService;
191 private ICompetitionService _competitionService;
192 private ICompetitionDistanceRuleService _competitionDistanceRuleService;
193 private IRaceService _raceService;
194 private IFileService _fileService;
195
204 public WorkspaceService(IPersonService personService, ICompetitionService competitionService, ICompetitionDistanceRuleService competitionDistanceRuleService, IRaceService raceService, IFileService fileService)
205 {
206 _personService = personService;
207 _competitionService = competitionService;
208 _competitionService.SetWorkspaceServiceObj(this); // Dependency Injection can't be used in the constructor because of circular dependency
209 _competitionDistanceRuleService = competitionDistanceRuleService;
210 _raceService = raceService;
211 _raceService.SetWorkspaceServiceObj(this); // Dependency Injection can't be used in the constructor because of circular dependency
212 _fileService = fileService;
213 IsWorkspaceOpen = false;
214 _personService.OnFileProgress += (sender, p, currentStep) => OnFileProgress?.Invoke(this, p / 3, "Loading persons...");
215 _competitionService.OnFileProgress += (sender, p, currentStep) => OnFileProgress?.Invoke(this, (100 / 3) + (p / 3), "Loading competitions...");
216 _raceService.OnFileProgress += (sender, p, currentStep) => OnFileProgress?.Invoke(this, (2 * (100 / 3)) + (p / 3), "Loading best race...");
217
218 _personService.PropertyChanged += (sender, e) =>
219 {
220 switch(e.PropertyName)
221 {
222 case nameof(PersonService.HasUnsavedChanges): OnPropertyChanged(nameof(HasUnsavedChanges)); break;
223 default: break;
224 }
225 };
226 _competitionService.PropertyChanged += (sender, e) =>
227 {
228 switch (e.PropertyName)
229 {
230 case nameof(CompetitionService.HasUnsavedChanges): OnPropertyChanged(nameof(HasUnsavedChanges)); break;
231 default: break;
232 }
233 };
234 _competitionDistanceRuleService.PropertyChanged += (sender, e) =>
235 {
236 switch (e.PropertyName)
237 {
238 case nameof(CompetitionService.HasUnsavedChanges): OnPropertyChanged(nameof(HasUnsavedChanges)); break;
239 default: break;
240 }
241 };
242 _raceService.PropertyChanged += (sender, e) =>
243 {
244 switch (e.PropertyName)
245 {
246 case nameof(RaceService.HasUnsavedChanges): OnPropertyChanged(nameof(HasUnsavedChanges)); break;
247 default: break;
248 }
249 };
250 }
251
252 // ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
253
261 public async Task<bool> Load(string path, CancellationToken cancellationToken)
262 {
263 if (!Directory.Exists(path)) { return false; }
264
266 {
267 await CloseWorkspace(cancellationToken, true);
268 }
269
270 string previousPath = PersistentPath;
271 PersistentPath = path;
272
273 bool openResult = false;
274 Exception exception = null;
275 try
276 {
277 string filePathCompetitions = getFilePathToLoadFrom(KEY_FILENAME_COMPETITIONS);
278 string filePathCompetitionDistanceRules = getFilePathToLoadFrom(KEY_FILENAME_COMPETITIONDISTANCERULES);
279 string filePathPersons = getFilePathToLoadFrom(KEY_FILENAME_PERSON);
280 string filePathBestRace = getFilePathToLoadFrom(KEY_FILENAME_BESTRACE);
281 if (!File.Exists(WorkspaceSettingsFilePath) && !File.Exists(filePathCompetitions) && !File.Exists(filePathPersons) && !File.Exists(filePathBestRace))
282 {
284 }
285 else
286 {
288 }
289
290 // Workspace settings
292 Settings.Load(_fileService, WorkspaceSettingsFilePath);
293 Settings.PropertyChanged += Settings_PropertyChanged;
294
295 // Competitions
296 openResult = await _competitionService.Load(filePathCompetitions, cancellationToken);
297 if (!openResult) { return openResult; }
298
299 // Competition Distance Rules
300 openResult = await _competitionDistanceRuleService.Load(filePathCompetitionDistanceRules, cancellationToken);
301 if (!openResult) { return openResult; }
302
303 // Persons
304 openResult = await _personService.Load(filePathPersons, cancellationToken);
305 if(!openResult) { return openResult; }
306
307 _competitionService.UpdateAllCompetitionsForPerson();
308 _competitionService.UpdateAllCompetitionDistancesFromDistanceRules(true);
309
310 // Best Race
311 openResult = await _raceService.Load(filePathBestRace, cancellationToken);
312
314 OnPropertyChangedAllHasUnsavedChanges();
315 IsWorkspaceOpen = openResult;
316
318 }
319 catch(Exception ex)
320 {
321 PersistentPath = previousPath;
322 OnPropertyChangedAllHasUnsavedChanges();
323 OnPropertyChanged(nameof(IsWorkspaceOpen));
324 exception = ex;
325 }
326 OnFileFinished?.Invoke(this, null);
327 if (exception != null) { throw exception; }
328 return openResult;
329 }
330
331 // ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
332
339 public async Task<bool> Save(CancellationToken cancellationToken, string path = "")
340 {
341 if (string.IsNullOrEmpty(path)) { path = PersistentPath; }
342
343 bool saveResult = false;
344 Exception exception = null;
345 try
346 {
347 // Workspace settings
348 Settings?.Save(_fileService, WorkspaceSettingsFilePath);
349
350 // Competitions (delete old file if the file name changed)
351 if (!await saveServiceAsync(KEY_FILENAME_COMPETITIONS, _competitionService, cancellationToken))
352 return false;
353
354 // Competition Distance Rules
355 if (!await saveServiceAsync(KEY_FILENAME_COMPETITIONDISTANCERULES, _competitionDistanceRuleService, cancellationToken))
356 return false;
357
358 // Persons
359 if (!await saveServiceAsync(KEY_FILENAME_PERSON, _personService, cancellationToken))
360 return false;
361
362 // Best Race
363 if (!await saveServiceAsync(KEY_FILENAME_BESTRACE, _raceService, cancellationToken))
364 return false;
365
368 OnPropertyChangedAllHasUnsavedChanges();
369 }
370 catch (Exception ex)
371 {
372 OnPropertyChangedAllHasUnsavedChanges();
373 exception = ex;
374 }
375 OnFileFinished?.Invoke(this, null);
376 if (exception != null) { throw exception; }
377 return saveResult;
378 }
379
387 private async Task<bool> saveServiceAsync(string fileNameResourceKey, ISaveable saveableService, CancellationToken cancellationToken)
388 {
389 string newPath = getFilePathToSaveTo(fileNameResourceKey);
390
391 if (!string.IsNullOrEmpty(saveableService.PersistentPath) &&
392 File.Exists(saveableService.PersistentPath) &&
393 !string.Equals(saveableService.PersistentPath, newPath, StringComparison.OrdinalIgnoreCase))
394 {
395 File.Delete(saveableService.PersistentPath);
396 }
397
398 return await saveableService.Save(cancellationToken, newPath);
399 }
400
401 // ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
402
409 public async Task<bool> CloseWorkspace(CancellationToken cancellationToken, bool save = true)
410 {
411 bool saveResult = true;
412 if (save)
413 {
414 saveResult = await Save(cancellationToken);
415 }
416
417 Settings.PropertyChanged -= Settings_PropertyChanged;
418 Settings = null;
419 _personService.ClearAll();
420 _competitionService.ClearAll();
421 _competitionDistanceRuleService.ClearAll();
422 _raceService.AllRacesVariants.Clear();
423 PersistentPath = string.Empty;
424 IsWorkspaceOpen = false;
425
426 OnFileFinished?.Invoke(this, null);
427 return saveResult;
428 }
429
430 // ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
431
432 #region File name handling
433
437 public const string WorkspaceSettingsFileName = "WorkspaceSettings.json";
438
443
444 private readonly ResourceManager _fileNameResources = new ResourceManager(typeof(Properties.Resources));
445 public const string KEY_FILENAME_PERSON = "FileName_Person";
446 public const string KEY_FILENAME_COMPETITIONS = "FileName_Competitions";
447 public const string KEY_FILENAME_COMPETITIONDISTANCERULES = "FileName_CompetitionDistanceRules";
448 public const string KEY_FILENAME_BESTRACE = "FileName_BestRace";
449
450 private string getLocalizedFilePath(string resourceKey, CultureInfo culture = null)
451 {
452 string localizedFileName = _fileNameResources.GetString(resourceKey, culture == null ? CultureInfo.CurrentUICulture : culture)
453 ?? _fileNameResources.GetString(resourceKey, CultureInfo.InvariantCulture);
454 return Path.Combine(PersistentPath, localizedFileName);
455 }
456
462 private string getFilePathToSaveTo(string resourceKey)
463 => getLocalizedFilePath(resourceKey);
464
470 private string getFilePathToLoadFrom(string resourceKey)
471 {
472 if (string.IsNullOrEmpty(resourceKey)) { return string.Empty; }
473 List<string> candidates = new List<string>();
474
475 // Test if the file for the current culture exists
476 string current = getLocalizedFilePath(resourceKey);
477 if (File.Exists(current))
478 {
479 return current;
480 }
481
482 // Check all known cultures
483 List<string> localizedFileNames = GeneralLocalizationHelper.GetAllTranslationsForKey(_fileNameResources, resourceKey).Values.ToList();
484 List<string> localizedFilePaths = localizedFileNames.Select(f => Path.Combine(PersistentPath, f)).ToList();
485 foreach (string localizedFilePath in localizedFilePaths)
486 {
487 if (File.Exists(localizedFilePath) && !candidates.Contains(localizedFilePath, StringComparer.OrdinalIgnoreCase))
488 {
489 candidates.Add(localizedFilePath);
490 }
491 }
492
493 // Use newest file depending on last write time
494 return candidates.Select(f => new FileInfo(f))
495 .OrderByDescending(f => f.LastWriteTimeUtc)
496 .FirstOrDefault()?.FullName;
497 }
498
499 #endregion
500
501 }
502}
Eine stark typisierte Ressourcenklasse zum Suchen von lokalisierten Zeichenfolgen usw.
Service used to get and store a list of Competition objects.
bool HasUnsavedChanges
Check if the list of Competition has not saved changed.
Service used to get and store a list of Person objects.
bool HasUnsavedChanges
Check if the list of Person has not saved changed.
Service used to manage Race and RacesVariant objects.
bool HasUnsavedChanges
Check if the PersistedRacesVariant has not saved changed.
ObservableCollection< string > LastWorkspacePaths
List with previous workspace paths.
bool HasUnsavedChanges_Races
Unsaved changes exist in the RaceService
bool HasUnsavedChanges_Competitions
Unsaved changes exist in the <see cref="CompetitionService" or CompetitionDistanceRuleService/>
WorkspaceSettings Settings
Workspace settings for the current workspace.
void ResetRacesToLoadedState()
Call the IRaceService.ResetToLoadedState
WorkspaceSettings _settingsPersistedInFile
Last workspace settings that were loaded from the file.
void ResetCompetitionsToLoadedState()
Call the ICompetitionService.ResetToLoadedState and ICompetitionDistanceRuleService....
void AddLastWorkspacePath(string path)
Add a path to the LastWorkspacePaths
async Task< bool > Save(CancellationToken cancellationToken, string path="")
Save all workspace files.
EventHandler OnFileFinished
Event that is raised when the file operation is finished.
async Task< bool > Load(string path, CancellationToken cancellationToken)
Open the workspace and load all files Caution: If there is already a workspace opened that has unsave...
string getFilePathToSaveTo(string resourceKey)
This method returns the file path to save to.
void ResetSettingsToLoadedState()
Reset the to the state when the method was called.
string WorkspaceSettingsFilePath
Combined path to the workspace settings file.
bool HasUnsavedChanges
Check if there are unsaved changes in the workspace.
bool IsWorkspaceOpen
If true, a workspace is loaded; if false, not workspace is loaded.
WorkspaceService(IPersonService personService, ICompetitionService competitionService, ICompetitionDistanceRuleService competitionDistanceRuleService, IRaceService raceService, IFileService fileService)
Constructor for the WorkspaceService.
bool HasUnsavedChanges_Settings
Unsaved changes exist in the Settings.
async Task< bool > CloseWorkspace(CancellationToken cancellationToken, bool save=true)
Close the current workspace (set the current path to string.Empty and the Settings to null)
void ClearAllLastWorkspacePaths()
Clear all LastWorkspacePaths
void ResetPersonsToLoadedState()
Call the IPersonService.ResetToLoadedState
string PersistentPath
Path of the current workspace.
bool HasUnsavedChanges_Persons
Unsaved changes exist in the PersonService
const string WorkspaceSettingsFileName
Name of the workspace settings file.
string getFilePathToLoadFrom(string resourceKey)
This method returns the file path to load from.
ProgressDelegate OnFileProgress
Event that is raised when the file operation progress changes.
async Task< bool > saveServiceAsync(string fileNameResourceKey, ISaveable saveableService, CancellationToken cancellationToken)
Helper method to save a service and delete the old file if the file name changed.
Interface for a service used to get and store a list of CompetitionDistanceRule objects.
Interface for a service used to get and store a list of objects.
void SetWorkspaceServiceObj(IWorkspaceService workspaceService)
Save the reference to the IWorkspaceService object.
Interface for a service that handles file operations.
Interface for a service used to get and store a list of Person objects.
Interface for a service used to manage Race and RacesVariant objects.
void SetWorkspaceServiceObj(IWorkspaceService workspaceService)
Save the reference to the IWorkspaceService object.
Task< bool > Save(CancellationToken cancellationToken, string path="")
Save to a file.
Interface for a service used to manage a workspace.
bool IsCompletelyNewWorkspace
If true, the workspace wasn't loaded from previously saved files.
WorkspaceSettings SettingsPersistedInFile
Settings loaded from the file for the current workspace.
delegate void ProgressDelegate(object sender, float progress, string currentStep="")
Delegate void for progress changes.