|
2 | 2 | using System.Collections.Generic; |
3 | 3 | using System.IO; |
4 | 4 | using System.Linq; |
| 5 | +using System.Runtime.InteropServices; |
5 | 6 | using System.Threading; |
6 | 7 | using System.Threading.Tasks; |
7 | 8 | using GeneralUpdate.Core.Differential; |
@@ -445,24 +446,42 @@ private async Task ApplyPatch(string appFilePath, string patchFilePath, Cancella |
445 | 446 | /// deletion failures caused by read-only attributes. |
446 | 447 | /// </para> |
447 | 448 | /// </remarks> |
448 | | - private static void HandleDeleteList(IEnumerable<FileInfo> patchFiles, IEnumerable<FileInfo> oldFiles) |
| 449 | + private static void HandleDeleteList(IEnumerable<FileInfo> patchFiles, List<FileInfo> oldFiles) |
449 | 450 | { |
450 | 451 | var json = patchFiles.FirstOrDefault(i => i.Name.Equals(DeleteListFileName, StringComparison.OrdinalIgnoreCase)); |
451 | 452 | if (json == null) return; |
452 | 453 |
|
453 | 454 | var deleteFiles = StorageManager.GetJson<List<FileNode>>(json.FullName, FileNodesJsonContext.Default.ListFileNode); |
454 | 455 | if (deleteFiles == null) return; |
455 | 456 |
|
456 | | - var hashAlgorithm = new Sha256HashAlgorithm(); |
457 | | - var toDelete = oldFiles |
458 | | - .Where(old => deleteFiles.Any(del => del.Hash.SequenceEqual(hashAlgorithm.ComputeHash(old.FullName)))) |
| 457 | + // Build a HashSet of delete-manifest hashes (hex strings from FileNode.Hash) |
| 458 | + // so each oldFile is checked in O(1) instead of O(n × m). |
| 459 | + var deleteHashes = new HashSet<string>(StringComparer.OrdinalIgnoreCase); |
| 460 | + foreach (var del in deleteFiles) |
| 461 | + { |
| 462 | + if (!string.IsNullOrEmpty(del.Hash)) |
| 463 | + deleteHashes.Add(del.Hash); |
| 464 | + } |
| 465 | + if (deleteHashes.Count == 0) return; |
| 466 | + |
| 467 | + // Exclude .patch files from oldFiles — they are patch input, not app files. |
| 468 | + var appFiles = oldFiles |
| 469 | + .Where(f => !Path.GetExtension(f.FullName) |
| 470 | + .Equals(PatchExtension, StringComparison.OrdinalIgnoreCase)) |
459 | 471 | .ToList(); |
| 472 | + if (appFiles.Count == 0) return; |
460 | 473 |
|
461 | | - foreach (var file in toDelete) |
| 474 | + var hashAlgorithm = new Sha256HashAlgorithm(); |
| 475 | + foreach (var file in appFiles) |
462 | 476 | { |
463 | 477 | if (!File.Exists(file.FullName)) continue; |
464 | | - File.SetAttributes(file.FullName, FileAttributes.Normal); |
465 | | - File.Delete(file.FullName); |
| 478 | + |
| 479 | + var fileHash = hashAlgorithm.ComputeHash(file.FullName); |
| 480 | + if (fileHash != null && deleteHashes.Contains(fileHash)) |
| 481 | + { |
| 482 | + File.SetAttributes(file.FullName, FileAttributes.Normal); |
| 483 | + File.Delete(file.FullName); |
| 484 | + } |
466 | 485 | } |
467 | 486 | } |
468 | 487 |
|
@@ -501,15 +520,24 @@ private static Task CopyUnknownFiles(string appPath, string patchPath) |
501 | 520 | var extensionName = Path.GetExtension(file.FullName); |
502 | 521 | if (BlackDefaults.DefaultFormats.Contains(extensionName)) continue; |
503 | 522 |
|
504 | | - var targetFileName = file.FullName.Replace(patchPath, "").TrimStart(Path.DirectorySeparatorChar); |
505 | | - var targetPath = Path.Combine(appPath, targetFileName); |
| 523 | + // Compute the relative path by stripping the patch directory prefix. |
| 524 | + // Using StartsWith + Substring instead of string.Replace to avoid |
| 525 | + // incorrect replacements when appPath is a substring of patchPath. |
| 526 | + var patchPrefix = patchPath.TrimEnd(Path.DirectorySeparatorChar) + Path.DirectorySeparatorChar; |
| 527 | + var comparison = RuntimeInformation.IsOSPlatform(OSPlatform.Windows) |
| 528 | + ? StringComparison.OrdinalIgnoreCase |
| 529 | + : StringComparison.Ordinal; |
| 530 | + var relativePart = file.FullName.StartsWith(patchPrefix, comparison) |
| 531 | + ? file.FullName.Substring(patchPrefix.Length) |
| 532 | + : file.FullName; |
| 533 | + var targetPath = Path.Combine(appPath, relativePart); |
506 | 534 | var parentFolder = Directory.GetParent(targetPath); |
507 | 535 | if (parentFolder?.Exists == false) |
508 | 536 | parentFolder.Create(); |
509 | 537 |
|
510 | 538 | // Atomic replace via temp file, same strategy as ApplyPatch. |
511 | 539 | // Avoids file-in-use errors when the process just exited. |
512 | | - var safeName = targetFileName.Replace(Path.DirectorySeparatorChar, '_'); |
| 540 | + var safeName = relativePart.Replace(Path.DirectorySeparatorChar, '_'); |
513 | 541 | var tempPath = Path.Combine(appPath, $"{Path.GetRandomFileName()}_{safeName}"); |
514 | 542 | File.Copy(file.FullName, tempPath, true); |
515 | 543 |
|
|
0 commit comments