|
1 | | -#if NET6_0_OR_GREATER |
| 1 | +#if NET6_0_OR_GREATER |
2 | 2 |
|
3 | 3 | using RoboSharp.EventArgObjects; |
4 | 4 | using RoboSharp.Extensions.Helpers; |
@@ -293,12 +293,8 @@ private async Task RunAsync(CancellationToken cancellationToken) |
293 | 293 | bool touchFiles = CopyOptions.CreateDirectoryAndFileTree; |
294 | 294 | bool purging = !SelectionOptions.ExcludeExtra && (CopyOptions.Purge || CopyOptions.Mirror); |
295 | 295 | bool reportExtraFiles = (!SelectionOptions.ExcludeExtra || (purging && CopyOptions.Depth != 1) ) && (LoggingOptions.VerboseOutput || LoggingOptions.ReportExtraFiles); |
296 | | - |
297 | | - // !!!! -- TODO : FIX REPORTeXTRAdIRS -- THIS IS NOT WORKING YET --- |
298 | | - // Something to do with maxdepth == 1 & all other conditions... |
299 | | - bool reportExtraDirs = maxDepth != 1 && !(!purging && !(CopyOptions.IsRecursive()) && !LoggingOptions.ReportExtraFiles && !CopyOptions.HasDefaultFileFilter()); |
300 | | - //reportExtraFiles || (CopyOptions.CopySubdirectories || CopyOptions.CopySubdirectoriesIncludingEmpty || CopyOptions.MoveFilesAndDirectories); |
301 | | - |
| 296 | + bool reportExtraDirs = !SelectionOptions.ExcludeExtra; |
| 297 | + |
302 | 298 | SemaphoreSlim multiThreadedController = new SemaphoreSlim(CopyOptions.MultiThreadedCopiesCount >= 128 ? 128 : CopyOptions.MultiThreadedCopiesCount <= 1 ? 1 : CopyOptions.MultiThreadedCopiesCount); |
303 | 299 | Dictionary<string, ProcessedFileInfo> infoDict = new(); |
304 | 300 | ConcurrentDictionary<IFileCopier, Task> runningTasks = new(); |
@@ -353,81 +349,30 @@ private async Task RunAsync(CancellationToken cancellationToken) |
353 | 349 |
|
354 | 350 | // ── Process Purge candidates (destination-only files) ──────────────────── |
355 | 351 | // ── Perform this first to clear space and also reduce run-time (avoid evaluating files that are copied into destination) |
356 | | - if (dirPair.Destination.Exists) |
357 | | - { |
358 | | - if (purging && dirPair.IsExtra()) |
359 | | - { |
360 | | - dirPair.Destination.Delete(true); |
361 | | - continue; // source does not exist -> move to next dirpair |
362 | | - } |
| 352 | + // Detect Extra Directories (dest dirs not in source tree) |
| 353 | +if (dirPair.Destination.Exists) |
| 354 | +{ |
| 355 | + foreach (var child in Directory.EnumerateDirectories(dirPair.Destination.FullName, "*", SearchOption.TopDirectoryOnly)) |
| 356 | + { |
| 357 | + if (infoDict.ContainsKey(child)) |
| 358 | + continue; // part of source tree, already handled |
363 | 359 |
|
364 | | - // Report or Purge extra files |
365 | | - await foreach (IFileCopier purgeCopier in CreatePurgeCandidates(dirPair, cancellationToken)) |
366 | | - { |
367 | | - cancellationToken.ThrowIfCancellationRequested(); |
| 360 | + // This directory exists in dest but not source — it's Extra |
| 361 | + if (reportExtraDirs || purging) |
| 362 | + { |
| 363 | + var extraInfo = new ProcessedFileInfo(child, FileClassType.NewDir, |
| 364 | + fileClass: Configuration.LogParsing_ExtraDir, purging ? -1 : 0); |
| 365 | + resultsBuilder.AddDir(extraInfo); |
| 366 | + OnFileProcessed?.Invoke(this, new FileProcessedEventArgs(extraInfo)); |
| 367 | + } |
368 | 368 |
|
369 | | - EvaluateFilePair(purgeCopier); |
370 | | - ProcessedFileInfo purgeInfo = purgeCopier.ProcessedFileInfo; |
| 369 | + if (purging) |
| 370 | + { |
| 371 | + PurgeExtraDirectory(child, resultsBuilder, cancellationToken); |
| 372 | + } |
| 373 | + } |
| 374 | +} |
371 | 375 |
|
372 | | - if (purgeCopier.ShouldPurge) |
373 | | - { |
374 | | - OnFileProcessed?.Invoke(this, new FileProcessedEventArgs(purgeInfo)); |
375 | | - |
376 | | - try |
377 | | - { |
378 | | - purgeCopier.Destination.Delete(); |
379 | | - progressReporter.AddFileExtra(purgeInfo); |
380 | | - resultsBuilder.AddFilePurged(purgeInfo); |
381 | | - } |
382 | | - catch (OperationCanceledException) |
383 | | - { |
384 | | - throw; |
385 | | - } |
386 | | - catch (Exception ex) |
387 | | - { |
388 | | - resultsBuilder.AddFileFailed(purgeInfo); |
389 | | - OnCommandError?.Invoke(this, new CommandErrorEventArgs(ex.Message, ex)); |
390 | | - } |
391 | | - } |
392 | | - else |
393 | | - { |
394 | | - // Extra file is present but purge is disabled — treat as skipped/extra |
395 | | - progressReporter.AddFileExtra(purgeInfo); |
396 | | - resultsBuilder.AddFileExtra(purgeInfo); |
397 | | - OnFileProcessed?.Invoke(this, new FileProcessedEventArgs(purgeInfo)); |
398 | | - } |
399 | | - } |
400 | | - |
401 | | - // Detect Extra Directories |
402 | | - if (true || currentDepth <= maxDepth) |
403 | | - { |
404 | | - foreach (var child in Directory.EnumerateDirectories(dirPair.Destination.FullName, "*", SearchOption.TopDirectoryOnly)) |
405 | | - { |
406 | | - // check if dictionary contains the key |
407 | | - if (infoDict.ContainsKey(child)) |
408 | | - continue; |
409 | | - |
410 | | - // not part of source tree: |
411 | | - if (reportExtraDirs) |
412 | | - { |
413 | | - var info = new ProcessedFileInfo(child, FileClassType.NewDir, fileClass: Configuration.LogParsing_ExtraDir, purging ? -1 : 0); |
414 | | - resultsBuilder.AddDir(info); |
415 | | - } |
416 | | - |
417 | | - if (purging) |
418 | | - { |
419 | | - try |
420 | | - { |
421 | | - Directory.Delete(child, true); |
422 | | - } |
423 | | - catch (Exception e) |
424 | | - { |
425 | | - OnCommandError?.Invoke(this, new CommandErrorEventArgs($"Unable to purge directory : {child}", e)); |
426 | | - } |
427 | | - } |
428 | | - } |
429 | | - } |
430 | | - } |
431 | 376 |
|
432 | 377 | // ── Process Source files for copy/move ────────────────────────────────────────────────── |
433 | 378 | if (dirPair.Source.Exists) |
@@ -551,42 +496,48 @@ private async Task PerformCopyOrMove( |
551 | 496 | } |
552 | 497 | } |
553 | 498 |
|
554 | | - /// <summary> Processes an EXTRA directory tree from the destination, potentially purging it.</summary> |
555 | | - private void ProcessExtraDirectory(DirectoryPair pair, int currentDepth, ResultsBuilder resultsBuilder) |
556 | | - { |
557 | | - if (!pair.Destination.Exists) return; |
558 | | - bool shouldPurge = CopyOptions.Purge && this.ShouldPurge(pair); |
559 | | - |
560 | | - // This gets it to pass unit tests, but *feels* wrong |
561 | | - if (!shouldPurge && !CopyOptions.IsRecursive() && !LoggingOptions.ReportExtraFiles && !CopyOptions.HasDefaultFileFilter()) return; |
562 | | - |
563 | | - if (pair.ProcessedFileInfo is null) |
564 | | - pair.ProcessedFileInfo = new ProcessedFileInfo(directory: pair.Destination, this, ProcessedDirectoryFlag.ExtraDir, size: -1); |
565 | | - |
566 | | - resultsBuilder.AddDir(pair.ProcessedFileInfo); |
567 | | - if (!shouldPurge) return; |
568 | | - |
569 | | - ////Process Files |
570 | | - //IEnumerable<FilePair> files = pair.DestinationFiles; |
571 | | - //foreach (var file in files) |
572 | | - //{ |
573 | | - // if (cancelRequest.IsCancellationRequested) break; |
574 | | - // ProcessExtraFile(file); |
575 | | - //} |
576 | | - |
577 | | - //// Dig into subdirectories |
578 | | - //if (PairEvaluator.CanDigDeeper(currentDepth)) |
579 | | - //{ |
580 | | - // foreach (var dir in pair.ExtraDirectories) |
581 | | - // { |
582 | | - // if (cancelRequest.IsCancellationRequested) break; |
583 | | - // ProcessExtraDirectory(dir, currentDepth + 1); |
584 | | - // } |
585 | | - //} |
586 | | - |
587 | | - // Delete the current directory |
| 499 | + /// <summary> |
| 500 | +/// Recursively reports and deletes an extra destination directory and all its contents. |
| 501 | +/// Mirrors RoboCopy's behaviour: files are counted as Extra/Purged before the directory is deleted. |
| 502 | +/// </summary> |
| 503 | +private void PurgeExtraDirectory(string destDir, ResultsBuilder resultsBuilder, CancellationToken cancellationToken) |
| 504 | +{ |
| 505 | + cancellationToken.ThrowIfCancellationRequested(); |
| 506 | + |
| 507 | + // Report and count each file inside the extra directory before deleting |
| 508 | + foreach (var file in Directory.EnumerateFiles(destDir, "*", SearchOption.TopDirectoryOnly)) |
| 509 | + { |
| 510 | + var fileInfo = new FileInfo(file); |
| 511 | + var rPath = Path.GetRelativePath(CopyOptions.Destination, file); |
| 512 | + var pfi = new ProcessedFileInfo(rPath, FileClassType.NewDir, |
| 513 | + fileClass: Configuration.LogParsing_ExtraDir, size: -1); |
| 514 | + // Mark as extra/purged in results |
| 515 | + resultsBuilder.AddFilePurged(pfi); |
| 516 | + OnFileProcessed?.Invoke(this, new FileProcessedEventArgs(pfi)); |
| 517 | + } |
| 518 | + |
| 519 | + // Recurse into subdirectories of this extra dir |
| 520 | + foreach (var subDir in Directory.EnumerateDirectories(destDir, "*", SearchOption.TopDirectoryOnly)) |
| 521 | + { |
| 522 | + cancellationToken.ThrowIfCancellationRequested(); |
| 523 | + var extraInfo = new ProcessedFileInfo(subDir, FileClassType.NewDir, |
| 524 | + fileClass: Configuration.LogParsing_ExtraDir, size: -1); |
| 525 | + resultsBuilder.AddDir(extraInfo); |
| 526 | + OnFileProcessed?.Invoke(this, new FileProcessedEventArgs(extraInfo)); |
| 527 | + PurgeExtraDirectory(subDir, resultsBuilder, cancellationToken); |
| 528 | + } |
| 529 | + |
| 530 | + // Now delete the whole tree |
| 531 | + try |
| 532 | + { |
| 533 | + Directory.Delete(destDir, true); |
| 534 | + } |
| 535 | + catch (Exception e) |
| 536 | + { |
| 537 | + OnCommandError?.Invoke(this, new CommandErrorEventArgs($"Unable to purge directory: {destDir}", e)); |
| 538 | + } |
| 539 | +} |
588 | 540 |
|
589 | | - } |
590 | 541 |
|
591 | 542 | /// <summary> |
592 | 543 | /// Yields the root pair and (if recurse is true) all sub-directory pairs, mirroring Robocopy's directory tree walk. |
|
0 commit comments