Suprabhat Biswal
Suprabhat Biswal

Reputation: 3216

Raise event from one class and subscribe it in another

I'm working on a project which basically runs a scan and identifies if sensitive information's are available in directories based on certain Regex. In this project we have a main project (WPF) namely DataScannerUI and a class library namely FileScanCore. FileScanCore is referenced to DataScannerUI.

Brief:

This is how my project works. All scan related logic are written in FileScanCore and the result-set that is generated from here is then passed onto DataScannerUI whenever this is called. Below is the scan hierarchy:-

  1. I define a source which needs to be scanned.
  2. Scan the source directory (root to children: this is just a look up to identity number of path that needs to scanned.)
  3. Inventory the scan results in database.
  4. Collect all path that got inventoried from DB.
  5. Perform a deep scan based on inventory path.
  6. Save deep scan results in database.

Now what is happening over here is that at no. 4 if data source size is less than 50k then it doesn't take much time to complete. If data-source size is 600k for e.g. then this take 30 minute approx. Here it seems like the UI got hanged as no status is shown here. What I need is to show some status and let user know how much inventory has been loaded so far in percent. This has been achieved in no time. Now this is where I'm stuck right now all status message logic's are written in DataScannerUI and the thing is I can't reference DataScannerUI in FileScanCore (circular dependency issue).

I need to pass the percent from FileScanCore to DataScannerUI. To sort this out I searched across google and came across below thread.

I tried using above but first 2 thread didn't worked for me though 3rd worked but it's not meeting my need. Shared is the snippet where I need to make changes.

FileScanCore

// This method is written in DeepScanner class that implements IScanner
private Queue<string> BuildInventory(FileSystem fs, IList<string> paths, bool includeChildren)
{
    var previousProgressStatus = 0L;
    var totalPathProcessed = 0L;

    var filesToInventory = new Stack<string>();
    var filesToDeepScan = new Queue<string>();

    //files To Inventory logic is written here. 
    {
        ...
        ...
        ...
    }

    // Here we find all directory and files 
    foreach (var c in fs.GetChildren(currentPath))
    {
        // This is where I determine the percentage completed
        var progressStatus = Math.Abs((totalPathProcessed++) * 100 / fs.TotalCount); 
        previousProgressStatus = ((progressStatus == previousProgressStatus) ? previousProgressStatus : progressStatus);

        filesToInventory.Push(c.FullPath);

        // I am looking for something that can trigger from here 
        // and send percentage change to DataScannerUI. 
    }
}

DataScannerUI

// This method is written in Scan class
foreach (var dataSource in _dataSources)
{
    try
    {
        _scanRunSemaphore.WaitAsync(_cancelSource.Token);
        ActiveDataSource = dataSource;
        _status = $"Performing deep scan - {ActiveDataSource.Name}";

        AllDeepScanners[ActiveDataSource.Name] = new DeepScanner(processors, extractor, _fileTypes, Job.Settings.SkipBinaries);
        ActiveScanner = AllDeepScanners[dataSource.Name];
        ActiveFileSystem = UsedFileSystems[ActiveDataSource.Name];

        // This is where I want to subscribe that event.
        // ActiveScanner is a property of type IScanner
        ActiveScanner.ScanAsync(ActiveFileSystem, _cancelSource.Token, _pauseSource.Token).Wait(); // This is where the inventory gets loaded.
        _status = $"Saving deep scan results - {ActiveDataSource.Name}";

        _logger.Debug($"Storing Deep scan results belonged to data source '{dataSource.Name}'");
        dataSource.SaveDeepScanResults(ActiveFileSystem, services);

        _logger.Debug($"Storing Job Last Scanned info belonged to data source '{dataSource.Name}'");
        SaveLastScanned(services, dataSource);        
    }
    // I'm not mentioning catch block 
}

I'm not sure what I'm looking for is even possible but worth give it a try.

Note: If this not enough kindly drop a comment I will try to make this more explanatory and readable.

Upvotes: 0

Views: 216

Answers (1)

gunnerone
gunnerone

Reputation: 3572

To relay the progress to your UI you can add an event to your IScanner class. Something like this:

public delegate void ProgressUpdateHandler(object sender, float progress);

interface IScanner
{
  event ProgressUpdateHandler OnProgressUpdate;
  ...
}

To implement this event in your DeepScanner class:

public class DeepScanner : IScanner
{
  public event ProgressUpdateHandler OnProgressUpdate;
  ...
}

To raise this event you would then do the following in your BuildInventory function:

// Here we find all directory and files 
foreach (var c in fs.GetChildren(currentPath))
{
  var progressStatus = Math.Abs((totalPathProcessed++) * 100 / fs.TotalCount); // This is where I detemine the percentage completed
  previousProgressStatus = ((progressStatus == previousProgressStatus) ? previousProgressStatus : progressStatus);

  filesToInventory.Push(c.FullPath);

  // Send the progress to anyone who is subscribed to this event.
  OnProgressUpdate?.Invoke(this, progressStatus);
}

To subscribe and handle this function in your UI code:

foreach (var dataSource in _dataSources)
{
  try
  {
    _scanRunSemaphore.WaitAsync(_cancelSource.Token);
    ActiveDataSource = dataSource;
    _status = $"Performing deep scan - {ActiveDataSource.Name}";

    AllDeepScanners[ActiveDataSource.Name] = new DeepScanner(processors, extractor, _fileTypes, Job.Settings.SkipBinaries);
    ActiveScanner = AllDeepScanners[dataSource.Name];
    ActiveFileSystem = UsedFileSystems[ActiveDataSource.Name];

    // Subscribe to the progress updates.
    ActiveScanner.OnProgressUpdate += ActiveScanner_OnProgressUpdate;

    ActiveScanner.ScanAsync(ActiveFileSystem, _cancelSource.Token, _pauseSource.Token).Wait(); // This is where the inventory gets loaded.

    // Now unsubscribe to the progress updates.
    ActiveScanner.OnProgressUpdate -= ActiveScanner_OnProgressUpdate;

    _status = $"Saving deep scan results - {ActiveDataSource.Name}";

    _logger.Debug($"Storing Deep scan results belonged to data source '{dataSource.Name}'");
    dataSource.SaveDeepScanResults(ActiveFileSystem, services);

    _logger.Debug($"Storing Job Last Scanned info belonged to data source '{dataSource.Name}'");
    SaveLastScanned(services, dataSource);
  }
  // I'm not mentioning catch block 
}

...
// Handle the progress updates.
private void ActiveScanner_OnProgressUpdate(object sender, float percent)
{
  // Here you can update your UI with the progress.
  // Note, in order to avoid cross-thread exceptions we need to invoke the
  // UI updates since this function will be called on a non-UI thread.
  if (progressLabel.InvokeRequired)
  {
    progressLabel.Invoke(new Action(() => ActiveScanner_OnProgressUpdate(sender, percent)));
  }
  else
  {
    progressLabel.Text = "Progress: " + progress;
  }
}

As a note, you may need to change _scanRunSemaphore.WaitAsync. I don't follow what you're doing with it. It seems like you might be using it in a non-typical way.

Upvotes: 1

Related Questions