-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathAppDataPaths.cs
More file actions
125 lines (112 loc) · 4.98 KB
/
Copy pathAppDataPaths.cs
File metadata and controls
125 lines (112 loc) · 4.98 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
using System;
using System.Collections.Generic;
using System.IO;
namespace SDRIQStreamer.App;
/// <summary>
/// Resolves the per-user data folder under <c>%APPDATA%</c> and performs the
/// one-shot folder rename from the legacy name <c>SDRIQStreamer</c> to the
/// product-aligned name <c>SmartStreamer4</c> (issue #34).
/// </summary>
public static class AppDataPaths
{
internal const string LegacyFolderName = "SDRIQStreamer";
internal const string CurrentFolderName = "SmartStreamer4";
private static string? _root;
private static readonly List<string> _migrationMessages = new();
private static readonly object _gate = new();
/// <summary>
/// Resolved per-user data folder for this process. The path lives directly
/// under <c>%APPDATA%</c> and is either the new <c>SmartStreamer4</c>
/// folder (the steady-state result) or, when an in-flight rename failed
/// because the legacy folder was locked, the legacy <c>SDRIQStreamer</c>
/// folder for this session only — next launch retries the rename.
/// </summary>
/// <exception cref="InvalidOperationException">
/// Thrown if accessed before <see cref="EnsureMigrated"/> has run.
/// </exception>
public static string Root => _root ?? throw new InvalidOperationException(
$"{nameof(AppDataPaths)}.{nameof(EnsureMigrated)} must be called before {nameof(Root)} is accessed.");
/// <summary>
/// Runs the legacy-to-current rename if it hasn't been done already and
/// sets <see cref="Root"/>. Call once from <c>Program.Main</c> before any
/// settings load, log file open, or other file access under the per-user
/// data folder. Idempotent on repeated calls within the same process.
/// </summary>
public static void EnsureMigrated() => EnsureMigrated(
Environment.GetFolderPath(Environment.SpecialFolder.ApplicationData),
Directory.Move);
internal static void EnsureMigrated(string appDataRoot, Action<string, string> moveDirectory)
{
lock (_gate)
{
if (_root is not null)
return;
var legacy = Path.Combine(appDataRoot, LegacyFolderName);
var current = Path.Combine(appDataRoot, CurrentFolderName);
var legacyExists = Directory.Exists(legacy);
var currentExists = Directory.Exists(current);
if (legacyExists && !currentExists)
{
try
{
// Same-volume rename under %APPDATA% — atomic on Windows, no
// partial-copy window. Falls back to legacy on lock failure
// (AV scanner, Explorer holding a handle, etc).
moveDirectory(legacy, current);
_migrationMessages.Add(
$"Settings folder renamed: %APPDATA%\\{LegacyFolderName} -> %APPDATA%\\{CurrentFolderName}.");
_root = current;
return;
}
catch (Exception ex)
{
_migrationMessages.Add(
$"Settings folder rename failed ({ex.GetType().Name}: {ex.Message}). " +
$"Using legacy %APPDATA%\\{LegacyFolderName} for this session; will retry next launch.");
_root = legacy;
return;
}
}
if (legacyExists && currentExists)
{
// Deliberately silent. This used to log "both folders present,
// legacy left intact for manual review" on every launch, which
// is a nag rather than a diagnostic: the condition is permanent
// until the operator deletes the legacy folder, so the message
// repeated at every startup from the issue #34 rename onward
// without ever telling them anything new. The behaviour is
// unchanged; only the logging is gone.
_root = current;
return;
}
// Either only the new folder exists (already migrated, or a second
// launch after the rename) or neither exists (fresh install). Use
// the current path either way; nothing to migrate, nothing to log.
_root = current;
}
}
internal static void ResetForTests()
{
lock (_gate)
{
_root = null;
_migrationMessages.Clear();
}
}
/// <summary>
/// Returns and clears the buffered messages produced by
/// <see cref="EnsureMigrated"/>. Call from the ViewModel during startup so
/// the operator sees migration outcomes in the Logs tab.
/// </summary>
public static IReadOnlyList<string> DrainMigrationMessages()
{
lock (_gate)
{
if (_migrationMessages.Count == 0)
return Array.Empty<string>();
var snapshot = _migrationMessages.ToArray();
_migrationMessages.Clear();
return snapshot;
}
}
}