Clean obsolete Extentions during client synchronization
Include the prepared cleanup of both client roots while preserving manifest-listed files and unrelated user state. Verified on Windows through FVM: full formatting and analysis, all 76 tests, production dry run, x64 build and DSA signature verification.
This commit is contained in:
@@ -96,6 +96,8 @@ or local-only API URLs.
|
||||
- The server manifest is the source of truth for client files.
|
||||
- Sync removes files absent from the manifest under `Data` and `WoW/Data`,
|
||||
plus legacy `WoW.exe` and DLLs directly in the installation root or `WoW`.
|
||||
Also clean obsolete `Extentions` directories in either client root and remove
|
||||
the directory itself when no manifest files require it.
|
||||
Remove unused Data subdirectories only after their children, without recursive
|
||||
deletion. Preserve unrelated files and user state outside these areas.
|
||||
- Language archives under `Data/ruRU` or `WoW/Data/ruRU` use the regular manifest sync.
|
||||
|
||||
@@ -28,6 +28,8 @@ During synchronization, the manifest is authoritative for `Data/` and
|
||||
Legacy `WoW.exe` and DLLs in the installation root or `WoW/` are also removed
|
||||
when absent from the manifest. Required DLLs stay; player settings, addons,
|
||||
logs, screenshots, and other files outside these cleanup areas are preserved.
|
||||
The obsolete `Extentions/` directory in either client root is cleaned the same
|
||||
way and removed when no manifest files require it.
|
||||
Cleanup runs even when all manifest files are already up to date.
|
||||
|
||||
## Stack
|
||||
|
||||
@@ -173,6 +173,9 @@ to the selected installation root, including files in nested directories.
|
||||
The scan separately collects stale paths without hashing their contents:
|
||||
|
||||
- every file absent from the manifest in `Data/` and `WoW/Data/`
|
||||
- every file absent from the manifest in obsolete `Extentions/` and
|
||||
`WoW/Extentions/` directories, including unused subdirectories and the
|
||||
Extentions directory itself when no manifest files require it
|
||||
- `WoW.exe` and `*.dll` absent from the manifest directly in the installation
|
||||
root or `WoW/` (binary names are matched without case sensitivity)
|
||||
- unused Data subdirectories, ordered after their children; the Data roots and
|
||||
@@ -325,7 +328,8 @@ The automated tests cover:
|
||||
- deterministic `buildHash`
|
||||
- path normalization used by `buildHash`
|
||||
- preservation of user state and unrelated unmanaged files during cleanup
|
||||
- removal of unmanaged Data files, unused subdirectories, WoW.exe, and DLLs
|
||||
- removal of unmanaged Data files, unused subdirectories, WoW.exe, DLLs, and
|
||||
obsolete Extentions directories
|
||||
- preservation of manifest-listed DLLs and WoW.exe
|
||||
- cache cleanup behavior
|
||||
- safe path resolution
|
||||
|
||||
@@ -630,11 +630,11 @@ Future<List<String>> _collectStalePaths(
|
||||
String relativePath(FileSystemEntity entity) =>
|
||||
normalizeClientPath(p.relative(entity.path, from: rootDirectory.path));
|
||||
|
||||
Future<void> collectData(Directory directory) async {
|
||||
Future<void> collectStaleDirectory(Directory directory) async {
|
||||
await for (final entity in directory.list(followLinks: false)) {
|
||||
final path = relativePath(entity);
|
||||
if (entity is Directory) {
|
||||
await collectData(entity);
|
||||
await collectStaleDirectory(entity);
|
||||
if (!manifestDirectories.contains(pathKey(path))) {
|
||||
stalePaths.add(path);
|
||||
}
|
||||
@@ -653,12 +653,20 @@ Future<List<String>> _collectStalePaths(
|
||||
final name = p.basename(entity.path).toLowerCase();
|
||||
if (entity is Directory) {
|
||||
if (name == 'data') {
|
||||
await collectData(entity);
|
||||
await collectStaleDirectory(entity);
|
||||
} else if (name == 'extentions') {
|
||||
await collectStaleDirectory(entity);
|
||||
if (!manifestDirectories.contains(pathKey(relativePath(entity)))) {
|
||||
stalePaths.add(relativePath(entity));
|
||||
}
|
||||
} else if (!nested && name == 'wow') {
|
||||
await collectClientRoot(entity, nested: true);
|
||||
}
|
||||
} else if ((name == 'wow.exe' || name.endsWith('.dll')) &&
|
||||
!manifestPaths.contains(pathKey(relativePath(entity)))) {
|
||||
} else if ((name == 'wow.exe' ||
|
||||
name.endsWith('.dll') ||
|
||||
(entity is Link && name == 'extentions')) &&
|
||||
!manifestPaths.contains(pathKey(relativePath(entity))) &&
|
||||
!manifestDirectories.contains(pathKey(relativePath(entity)))) {
|
||||
stalePaths.add(relativePath(entity));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -169,7 +169,12 @@ void main() {
|
||||
addTearDown(() => root.delete(recursive: true));
|
||||
final service = GameInstallationService();
|
||||
final retained = ['MoonWell.exe', 'Data/common.MPQ', 'required.dll'];
|
||||
final obsolete = ['Data/old/patch.MPQ', 'WoW.exe', 'unused.DLL'];
|
||||
final obsolete = [
|
||||
'Data/old/patch.MPQ',
|
||||
'WoW.exe',
|
||||
'unused.DLL',
|
||||
'Extentions/bin/extension.dll',
|
||||
];
|
||||
for (final path in [
|
||||
...retained,
|
||||
...obsolete,
|
||||
@@ -217,7 +222,12 @@ void main() {
|
||||
contains(ClientSyncStage.removingStaleFiles),
|
||||
);
|
||||
expect(api.downloadedPaths, isEmpty);
|
||||
for (final path in [...obsolete, 'Data/old']) {
|
||||
for (final path in [
|
||||
...obsolete,
|
||||
'Data/old',
|
||||
'Extentions/bin',
|
||||
'Extentions',
|
||||
]) {
|
||||
expect(
|
||||
await FileSystemEntity.type(
|
||||
service.resolveClientPath(root.path, path),
|
||||
|
||||
@@ -77,6 +77,8 @@ void main() {
|
||||
'WoW/WoW.exe',
|
||||
'WoW/old.dll',
|
||||
'WoW/Data/old.MPQ',
|
||||
'Extentions/bin/extension.dll',
|
||||
'WoW/Extentions/old.txt',
|
||||
];
|
||||
final untouchedPaths = [
|
||||
'Cache/cache.bin',
|
||||
@@ -105,6 +107,9 @@ void main() {
|
||||
await Directory(
|
||||
service.resolveClientPath(rootDirectory.path, 'Data/unused/nested'),
|
||||
).create(recursive: true);
|
||||
await Directory(
|
||||
service.resolveClientPath(rootDirectory.path, 'Extentions/empty'),
|
||||
).create(recursive: true);
|
||||
final manifest = await _manifestForPaths(
|
||||
service,
|
||||
rootDirectory,
|
||||
@@ -129,6 +134,10 @@ void main() {
|
||||
'Data/empty',
|
||||
'Data/unused/nested',
|
||||
'Data/unused',
|
||||
'Extentions/bin',
|
||||
'Extentions/empty',
|
||||
'Extentions',
|
||||
'WoW/Extentions',
|
||||
]),
|
||||
);
|
||||
for (final path in snapshot.stalePaths) {
|
||||
|
||||
Reference in New Issue
Block a user