Skip to content

Conversation

Jack251970
Copy link
Member

@Jack251970 Jack251970 commented Nov 27, 2024

This PR contains those fixes and improvements

  1. Fix possible null reference when query is cancelled.
    It will happen when one query is cancelled and can cause query bar not working like this:
    https://github.com/user-attachments/assets/8a0a2c0f-6db6-4061-8ff6-a979408fc6ff
    Throw exception from VS:
Screenshot 2024-11-25 182150 Screenshot 2024-11-25 182242
  1. Add support when image format is not support.
    It add support for images whose formats are not support for preview like this:
Screenshot 2024-11-27 193044

Before it is catch in outer try catch sentences, and the icon of images will be AppMissingIcon.png, which is quite strange for .png extension.
Screenshot 2024-11-27 193035

Now show the preview like this:
image

  1. Introduce concurrent directionary for topmost record.
    Make sure TopMostRecord is thread safe.

  2. Fix modified collection enumeration issue when updating results, which can also cause the same problem like 1.
    It could happen when calling ResultsUpdated.Invoke event in IResultUpdate interface so fast that main view model cannot complete update event (still execute foreach sentence).

  3. Fix xaml convert issue and use icon loader for PluginActivationIcon.

Screenshot 2024-11-27 220729

@prlabeler prlabeler bot added the bug Something isn't working label Nov 27, 2024

This comment has been minimized.

Copy link
Contributor

coderabbitai bot commented Nov 27, 2024

📝 Walkthrough

Walkthrough

The pull request introduces several enhancements across multiple classes. The GetThumbnailResult method in the ImageLoader class now includes error handling for image loading failures. The TopMostRecord class has transitioned to using a ConcurrentDictionary for improved thread safety, with updates to relevant methods. The image source binding in MainWindow.xaml has been modified, and a null check has been added in the UserSelectedRecord class's hash code generation method. Additionally, the MainViewModel class has been updated with new image handling capabilities and improved error management.

Changes

File Path Change Summary
Flow.Launcher.Infrastructure/Image/ImageLoader.cs Modified GetThumbnailResult to include error handling for LoadFullImage(path) using a try-catch block.
Flow.Launcher/Storage/TopMostRecord.cs Changed records from Dictionary<string, Record> to ConcurrentDictionary<string, Record> for thread safety; updated methods for concurrent access.
Flow.Launcher/MainWindow.xaml Updated Source binding from PluginIconPath to PluginIconSource.
Flow.Launcher/Storage/UserSelectedRecord.cs Added null check in GenerateQueryAndResultHashCode method to handle null Query.
Flow.Launcher/ViewModel/MainViewModel.cs Added PluginIconSource property; updated QueryResults for async image loading and enhanced error handling in UpdateResultView.
Flow.Launcher.Infrastructure/Constant.cs Introduced new field ImageIcon for a default image path.

Possibly related PRs

  • Fix System.OperationCanceledException Issue #3092: The changes in this PR involve adding error handling with a try-catch block in the QueryAsync method, similar to the error handling improvements made in the GetThumbnailResult method of the main PR. Both PRs enhance robustness by managing exceptions to prevent unhandled errors during execution.

Suggested reviewers

  • taooceros

🐇 In the code we hop and play,
With error checks to save the day.
Nulls are caught, and images load,
A safer path along the road.
So let us celebrate this feat,
With happy hearts and nimble feet! 🥕


Thank you for using CodeRabbit. We offer it for free to the OSS community and would appreciate your support in helping us grow. If you find it useful, would you consider giving us a shout-out on your favorite social media?

❤️ Share
🪧 Tips

Chat

There are 3 ways to chat with CodeRabbit:

  • Review comments: Directly reply to a review comment made by CodeRabbit. Example:
    • I pushed a fix in commit <commit_id>, please review it.
    • Generate unit testing code for this file.
    • Open a follow-up GitHub issue for this discussion.
  • Files and specific lines of code (under the "Files changed" tab): Tag @coderabbitai in a new review comment at the desired location with your query. Examples:
    • @coderabbitai generate unit testing code for this file.
    • @coderabbitai modularize this function.
  • PR comments: Tag @coderabbitai in a new PR comment to ask questions about the PR branch. For the best results, please provide a very specific query, as very limited context is provided in this mode. Examples:
    • @coderabbitai gather interesting stats about this repository and render them as a table. Additionally, render a pie chart showing the language distribution in the codebase.
    • @coderabbitai read src/utils.ts and generate unit testing code.
    • @coderabbitai read the files in the src/scheduler package and generate a class diagram using mermaid and a README in the markdown format.
    • @coderabbitai help me debug CodeRabbit configuration file.

Note: Be mindful of the bot's finite context window. It's strongly recommended to break down tasks such as reading entire modules into smaller chunks. For a focused discussion, use review comments to chat about specific files and their changes, instead of using the PR comments.

CodeRabbit Commands (Invoked using PR comments)

  • @coderabbitai pause to pause the reviews on a PR.
  • @coderabbitai resume to resume the paused reviews.
  • @coderabbitai review to trigger an incremental review. This is useful when automatic reviews are disabled for the repository.
  • @coderabbitai full review to do a full review from scratch and review all the files again.
  • @coderabbitai summary to regenerate the summary of the PR.
  • @coderabbitai resolve resolve all the CodeRabbit review comments.
  • @coderabbitai configuration to show the current CodeRabbit configuration for the repository.
  • @coderabbitai help to get help.

Other keywords and placeholders

  • Add @coderabbitai ignore anywhere in the PR description to prevent this PR from being reviewed.
  • Add @coderabbitai summary to generate the high-level summary at a specific location in the PR description.
  • Add @coderabbitai anywhere in the PR title to generate the title automatically.

CodeRabbit Configuration File (.coderabbit.yaml)

  • You can programmatically configure CodeRabbit by adding a .coderabbit.yaml file to the root of your repository.
  • Please see the configuration documentation for more information.
  • If your editor has YAML language server enabled, you can add the path at the top of this file to enable auto-completion and validation: # yaml-language-server: $schema=https://coderabbit.ai/integrations/schema.v2.json

Documentation and Community

  • Visit our Documentation for detailed information on how to use CodeRabbit.
  • Join our Discord Community to get help, request features, and share feedback.
  • Follow us on X/Twitter for updates and announcements.

Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 2

🧹 Outside diff range and nitpick comments (5)
Flow.Launcher/Storage/TopMostRecord.cs (3)

Line range hint 1-8: Critical: Address thread safety concerns

The TODO comment indicates that this class is accessed from multiple threads but lacks thread safety mechanisms. This could lead to race conditions and data corruption when concurrent operations modify the records dictionary.

Consider these thread-safety options:

  1. Use ConcurrentDictionary<string, Record> instead of Dictionary<string, Record>
  2. Add lock mechanisms around dictionary operations
  3. Implement the double-checked locking pattern for read operations

Example implementation using ConcurrentDictionary:

- public Dictionary<string, Record> records { get; private set; } = new Dictionary<string, Record>();
+ public ConcurrentDictionary<string, Record> records { get; private set; } = new ConcurrentDictionary<string, Record>();

Line range hint 15-21: Enhance null-safety in IsTopMost method

While the added null check for result.OriginQuery is good, the method still has potential null reference issues:

  1. No null check for the result parameter itself
  2. The second return statement could throw if result.OriginQuery becomes null between checks

Consider this safer implementation:

 internal bool IsTopMost(Result result)
 {
-    if (records.Count == 0 || (result.OriginQuery != null && !records.ContainsKey(result.OriginQuery.RawQuery)))
+    if (result?.OriginQuery == null || records.Count == 0 || !records.ContainsKey(result.OriginQuery.RawQuery))
     {
         return false;
     }
 
     // since this dictionary should be very small (or empty) going over it should be pretty fast.
     return records[result.OriginQuery.RawQuery].Equals(result);
 }

Line range hint 23-39: Add null checks to Remove and AddOrUpdate methods

Both methods directly access result.OriginQuery.RawQuery without null checks, which could lead to null reference exceptions when handling cancelled queries.

Suggested implementation:

 internal void Remove(Result result)
 {
+    if (result?.OriginQuery == null) return;
     records.Remove(result.OriginQuery.RawQuery);
 }

 internal void AddOrUpdate(Result result)
 {
+    if (result?.OriginQuery == null) return;
     var record = new Record
     {
         PluginID = result.PluginID,
         Title = result.Title,
         SubTitle = result.SubTitle
     };
     records[result.OriginQuery.RawQuery] = record;
 }
Flow.Launcher.Infrastructure/Image/ImageLoader.cs (2)

218-227: Good addition of specific exception handling for unsupported formats.

The try-catch block appropriately handles NotSupportedException for unsupported image formats, preventing crashes and improving robustness.

However, consider these improvements:

  1. Instead of setting image = null, use the MissingImage as fallback
  2. Consider catching other potential exceptions (e.g., OutOfMemoryException) that could occur during full image loading
 try
 {
     image = LoadFullImage(path);
     type = ImageType.FullImageFile;
 }
-catch (NotSupportedException)
+catch (NotSupportedException ex)
 {
-    image = null;
+    Log.Debug($"|ImageLoader.GetThumbnailResult|Unsupported image format for {path}: {ex.Message}");
+    image = ImageCache[Constant.MissingImgIcon, false];
     type = ImageType.Error;
 }
+catch (Exception ex) when (ex is OutOfMemoryException or InvalidOperationException)
+{
+    Log.Exception($"|ImageLoader.GetThumbnailResult|Failed to load full image {path}", ex);
+    image = ImageCache[Constant.MissingImgIcon, false];
+    type = ImageType.Error;
+}

220-220: Consider optimizing image format validation.

The LoadFullImage method makes multiple attempts to load and resize the image. For unsupported formats, this could mean multiple unnecessary attempts before failing.

Consider validating the image format before attempting to load:

+private static bool IsFormatSupported(string path)
+{
+    try
+    {
+        using var stream = File.OpenRead(path);
+        using var decoder = BitmapDecoder.Create(
+            stream,
+            BitmapCreateOptions.IgnoreColorProfile,
+            BitmapCacheOption.None);
+        return true;
+    }
+    catch (NotSupportedException)
+    {
+        return false;
+    }
+}

 try
 {
+    if (!IsFormatSupported(path))
+    {
+        image = ImageCache[Constant.MissingImgIcon, false];
+        type = ImageType.Error;
+        return;
+    }
     image = LoadFullImage(path);
     type = ImageType.FullImageFile;
 }
📜 Review details

Configuration used: CodeRabbit UI
Review profile: CHILL

📥 Commits

Reviewing files that changed from the base of the PR and between 14f1a5a and 756e718.

📒 Files selected for processing (2)
  • Flow.Launcher.Infrastructure/Image/ImageLoader.cs (1 hunks)
  • Flow.Launcher/Storage/TopMostRecord.cs (1 hunks)

This comment has been minimized.

This comment has been minimized.

Copy link

gitstream-cm bot commented Nov 27, 2024

🥷 Code experts: onesounds

onesounds has most 👩‍💻 activity in the files.
onesounds has most 🧠 knowledge in the files.

See details

Flow.Launcher.Infrastructure/Image/ImageLoader.cs

Activity based on git-commit:

onesounds
NOV
OCT
SEP
AUG
JUL
JUN

Knowledge based on git-blame:
onesounds: 8%

Flow.Launcher/MainWindow.xaml

Activity based on git-commit:

onesounds
NOV
OCT
SEP
AUG
JUL
JUN 131 additions & 144 deletions

Knowledge based on git-blame:
onesounds: 91%

Flow.Launcher/Storage/TopMostRecord.cs

Activity based on git-commit:

onesounds
NOV
OCT
SEP
AUG
JUL
JUN

Knowledge based on git-blame:

Flow.Launcher/Storage/UserSelectedRecord.cs

Activity based on git-commit:

onesounds
NOV
OCT
SEP
AUG
JUL
JUN

Knowledge based on git-blame:

Flow.Launcher/ViewModel/MainViewModel.cs

Activity based on git-commit:

onesounds
NOV
OCT
SEP
AUG
JUL
JUN 3 additions & 3 deletions

Knowledge based on git-blame:
onesounds: 12%

To learn more about /:\ gitStream - Visit our Docs

Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 1

🧹 Outside diff range and nitpick comments (5)
Flow.Launcher/Storage/TopMostRecord.cs (2)

Line range hint 30-37: Add null check and refactor record creation

Two issues to address:

  1. Missing null check for result.OriginQuery
  2. Record creation logic could be extracted for reusability

Apply these changes:

+ private static Record CreateRecord(Result result) => new Record
+ {
+     PluginID = result.PluginID,
+     Title = result.Title,
+     SubTitle = result.SubTitle
+ };

internal void AddOrUpdate(Result result)
{
-    var record = new Record
-    {
-        PluginID = result.PluginID,
-        Title = result.Title,
-        SubTitle = result.SubTitle
-    };
-    records.AddOrUpdate(result.OriginQuery.RawQuery, record, (key, oldValue) => record);
+    if (result.OriginQuery?.RawQuery == null) return;
+    var record = CreateRecord(result);
+    records.AddOrUpdate(result.OriginQuery.RawQuery, record, (_, _) => record);
}

42-42: Add parameter validation and optimize initialization

Consider adding parameter validation and capacity planning for better performance.

Apply this change:

public void Load(Dictionary<string, Record> dictionary)
{
-    records = new ConcurrentDictionary<string, Record>(dictionary);
+    if (dictionary == null)
+        throw new ArgumentNullException(nameof(dictionary));
+
+    var concurrencyLevel = Environment.ProcessorCount;
+    var capacity = dictionary.Count;
+    records = new ConcurrentDictionary<string, Record>(concurrencyLevel, capacity);
+    foreach (var kvp in dictionary)
+    {
+        records.TryAdd(kvp.Key, kvp.Value);
+    }
}
Flow.Launcher/Storage/UserSelectedRecord.cs (1)

Line range hint 1-109: Consider using ConcurrentDictionary for thread safety.

The class uses regular Dictionary for storing records which isn't thread-safe. Since the PR introduces thread safety improvements elsewhere using ConcurrentDictionary, consider applying the same pattern here to prevent potential race conditions in Add() and GetSelectedCount() methods.

Here's the suggested implementation:

- public Dictionary<int, int> recordsWithQuery { get; private set; }
+ public ConcurrentDictionary<int, int> recordsWithQuery { get; private set; }

  public UserSelectedRecord()
  {
-     recordsWithQuery = new Dictionary<int, int>();
+     recordsWithQuery = new ConcurrentDictionary<int, int>();
  }

  public void Add(Result result)
  {
      TransformOldRecords();
      var keyWithQuery = GenerateQueryAndResultHashCode(result.OriginQuery, result);
-     if (!recordsWithQuery.TryAdd(keyWithQuery, 1))
-         recordsWithQuery[keyWithQuery]++;
+     recordsWithQuery.AddOrUpdate(keyWithQuery, 1, (_, count) => count + 1);

      var keyWithoutQuery = GenerateResultHashCode(result);
-     if (!recordsWithQuery.TryAdd(keyWithoutQuery, 1))
-         recordsWithQuery[keyWithoutQuery]++;
+     recordsWithQuery.AddOrUpdate(keyWithoutQuery, 1, (_, count) => count + 1);
  }
Flow.Launcher/ViewModel/MainViewModel.cs (2)

1450-1476: Consider enhancing error handling and logging.

While the addition of error handling is good, there are a few improvements that could be made:

  1. Consider catching specific exceptions rather than using a general catch-all
  2. The debug log could be enhanced to include the full exception details including stack trace for better troubleshooting

Here's a suggested improvement:

 try
 {
     foreach (var metaResults in resultsForUpdates)
     {
         foreach (var result in metaResults.Results)
         {
             if (_topMostRecord.IsTopMost(result))
             {
                 result.Score = int.MaxValue;
             }
             else
             {
                 var priorityScore = metaResults.Metadata.Priority * 150;
                 result.Score += _userSelectedRecord.GetSelectedCount(result) + priorityScore;
             }
         }
     }

     bool reSelect = resultsForUpdates.First().ReSelectFirstResult;
     Results.AddResults(resultsForUpdates, token, reSelect);
 }
-catch (Exception ex)
+catch (InvalidOperationException ex)
 {
-    Log.Debug("MainViewModel", $"Error in UpdateResultView: {ex.Message}");
+    Log.Error("MainViewModel", "Error in UpdateResultView", ex);
+}
+catch (Exception ex)
+{
+    Log.Error("MainViewModel", "Unexpected error in UpdateResultView", ex);
 }

1456-1464: Consider extracting magic numbers into constants.

The scoring logic uses a magic number (150) for priority score calculation. This should be extracted into a named constant for better maintainability and clarity.

+private const int PRIORITY_SCORE_MULTIPLIER = 150;
+
 if (_topMostRecord.IsTopMost(result))
 {
     result.Score = int.MaxValue;
 }
 else
 {
-    var priorityScore = metaResults.Metadata.Priority * 150;
+    var priorityScore = metaResults.Metadata.Priority * PRIORITY_SCORE_MULTIPLIER;
     result.Score += _userSelectedRecord.GetSelectedCount(result) + priorityScore;
 }
📜 Review details

Configuration used: CodeRabbit UI
Review profile: CHILL

📥 Commits

Reviewing files that changed from the base of the PR and between 0720779 and c3c6126.

📒 Files selected for processing (4)
  • Flow.Launcher/MainWindow.xaml (1 hunks)
  • Flow.Launcher/Storage/TopMostRecord.cs (2 hunks)
  • Flow.Launcher/Storage/UserSelectedRecord.cs (2 hunks)
  • Flow.Launcher/ViewModel/MainViewModel.cs (6 hunks)
🔇 Additional comments (6)
Flow.Launcher/Storage/TopMostRecord.cs (3)

1-1: LGTM: Good improvement for thread safety

The switch to ConcurrentDictionary is a solid improvement that will prevent potential race conditions in a multi-threaded environment.

Also applies to: 11-11


15-21: ⚠️ Potential issue

Strengthen null check protection

While the initial null check is good, the return statement still directly accesses result.OriginQuery.RawQuery without a null check, which could throw a NullReferenceException.

Apply this fix:

- return result.OriginQuery != null && records[result.OriginQuery.RawQuery].Equals(result);
+ if (result.OriginQuery?.RawQuery == null) return false;
+ return records.TryGetValue(result.OriginQuery.RawQuery, out var record) && record.Equals(result);

9-11: Verify concurrent access patterns

Let's verify how this class is accessed concurrently to ensure our thread safety improvements are sufficient.

✅ Verification successful

Let me gather more information about how TopMostRecord is used in MainViewModel.cs to understand the concurrent access patterns better.


Based on the analysis of the code, I can now generate the final response:

Thread safety implementation is appropriate

The ConcurrentDictionary usage in TopMostRecord is well-justified. The evidence shows that:

  • TopMostRecord is accessed from multiple contexts including async query operations and UI operations
  • The access patterns are simple CRUD operations (AddOrUpdate, Remove, IsTopMost) which are all thread-safe with ConcurrentDictionary
  • The class is used in result scoring during async query operations while also being modified through UI actions
  • All operations on _topMostRecord are atomic and don't require additional synchronization
🏁 Scripts executed

The following scripts were executed for the analysis:

Script:

#!/bin/bash
# Search for usages of TopMostRecord class
rg "TopMostRecord" -B 2 -A 2

# Search for async/await patterns near TopMostRecord usage
ast-grep --pattern 'async $_ $_($$$) {
  $$$
  TopMostRecord
  $$$
}'

Length of output: 1863


Script:

#!/bin/bash
# Look for methods in MainViewModel that use _topMostRecord
rg "_topMostRecord\." -B 5 -A 5 "Flow.Launcher/ViewModel/MainViewModel.cs"

# Search for any async methods in MainViewModel
ast-grep --pattern 'async $_ $_($$$) {
  $$$
}' "Flow.Launcher/ViewModel/MainViewModel.cs"

# Look for any thread-related code near TopMostRecord usage
rg "Task|Thread|Parallel|async|await" -B 2 -A 2 "Flow.Launcher/ViewModel/MainViewModel.cs"

Length of output: 11432

Flow.Launcher/Storage/UserSelectedRecord.cs (1)

54-58: LGTM! Null check prevents NullReferenceException.

The added null check properly handles cancelled queries by falling back to result-only hash code generation, preventing the NullReferenceException when accessing query properties.

Flow.Launcher/MainWindow.xaml (1)

311-311: LGTM! Verify ViewModel property binding.

The change from PluginIconPath to PluginIconSource aligns with the PR objectives to improve image format support and null reference handling.

Let's verify the corresponding ViewModel property exists:

✅ Verification successful

The previous command might have failed due to the pattern syntax. Let's try a broader search to:

  1. Find any occurrence of PluginIconSource property
  2. Locate the MainViewModel file to verify the property exists

Property binding verified and implemented correctly

The PluginIconSource property exists in MainViewModel.cs with proper implementation:

  • Defined as public ImageSource PluginIconSource { get; private set; }
  • Properly initialized and managed alongside PluginIconPath
  • Used with ImageLoader.LoadAsync() for loading the image source
🏁 Scripts executed

The following scripts were executed for the analysis:

Script:

#!/bin/bash
# Description: Verify the PluginIconSource property exists in MainViewModel
ast-grep --pattern 'class MainViewModel {
  $$$
  public ImageSource PluginIconSource { get; set; }
  $$$
}'

Length of output: 110


Script:

#!/bin/bash
# Search for PluginIconSource property in all files
rg "PluginIconSource" -A 2 -B 2

# Search for files containing MainViewModel to ensure we're looking in the right places
fd MainViewModel

Length of output: 2148

Flow.Launcher/ViewModel/MainViewModel.cs (1)

26-27: LGTM: Image handling changes look good!

The addition of PluginIconSource property and related imports properly supports asynchronous image loading while maintaining null safety.

Also applies to: 727-728

@Jack251970 Jack251970 changed the title Fix possible null reference when query is cancelled & Add support when image format is not support Fix possible null reference when query is cancelled & Add support when image format is not support & Other improvements Nov 27, 2024
@taooceros
Copy link
Member

@Jack251970 I don't want to swipe your interest! It is always a tough time (and discouraging) waiting people to review your prs. Just to let you know I will be a bit busy before 12/3. Afterward I will try my best to review your pr asap. If I forget afterward, ping me at discord😁. (Or maybe other people in our teams will review your pr earlier).

@Jack251970
Copy link
Member Author

@taooceros No worries at all! I completely understand that reviewing PRs can take time, especially with a busy schedule.

@taooceros
Copy link
Member

@onesounds how do you think about the not supported image?

#endif

foreach (var metaResults in resultsForUpdates)
try
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

could you explain for the try?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It could happen when calling ResultsUpdated.Invoke event in IResultUpdate interface so fast that main view model cannot complete update event (still execute foreach sentence).

Take WebSearch plugin for example,

public async Task<List<Result>> QueryAsync(Query query, CancellationToken token)
{
    var results = new List<Result>();

    foreach (...)
    {
        ...

        results.Add(result);  // edit this List

        ...

        // Here you use this List to give to MainViewModel for updating
        // If you do the time-consuming work after this so fast, you will edit this list when updating the MainViewModel
        // So we need to use Try to avoid this problem
        ResultsUpdated?.Invoke(this, new ResultUpdatedEventArgs
        {
            Results = results,
            Query = query
        });

        ... // do time-consuming work
    }

    return results;
}

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Make sense, but that also means we need additional synchronization? I remember the ConcurrentDictionary is already providing a enumerator that is thread safe?

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

How about a ConcurrentBag?

Copy link
Member Author

@Jack251970 Jack251970 Dec 3, 2024

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The key of the problem is that this plugin has not tell the developer to do thread-safe operation in QueryAsync (like return a copy of the list as the Results in ResultUpdatedEventArgs). So the developer will possibly return the same List to the ResultUpdatedEventArgs and the result of the QueryAsync function.

If you want to fully address this problem, you need to add additional synchronization.

For example, you can change the definition of the ResultUpdatedEventArgs to

public class ResultUpdatedEventArgs : EventArgs
{
    public ConcurrentBag<Result> Results;  // thread-safe
    public Query Query;
    public CancellationToken Token { get; init; }
}

Or, you need to ask the developer to return a copy of the List so that the plugin will not change the List for updating the view.

Take WebSearch plugin for example,

public async Task<List<Result>> QueryAsync(Query query, CancellationToken token)
{
    var results = new List<Result>();

    foreach (...)
    {
        ...

        results.Add(result);

        ...

        var resultsCopy = results.Clone();  // create a copy of the results
        ResultsUpdated?.Invoke(this, new ResultUpdatedEventArgs
        {
            Results = resultsCopy,
            Query = query
        });

        ...
    }

    return results;
}

Anyway, I think this is the most developer-friendly solution for this problem and developers don not need to redevelop their plugins to fix this.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@taooceros What do you mean by ConcurrentDictionary and ConcurrentBag?

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Hold on, I remember this is a producer/consumer model with appropriate synchronization through the Channel class. Let me recheck.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I am a little bit suspicious of this change. Is it possible for you to revert just this part so we can merge first. Then Include it in a new pr so we can discuss specifically about the potential synchronization problem.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@taooceros Sure! Please check!

This comment has been minimized.

Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 1

🧹 Outside diff range and nitpick comments (2)
Flow.Launcher/Storage/TopMostRecord.cs (2)

43-43: Add parameter validation

Consider adding parameter validation to handle null dictionary input.

Apply this change:

public void Load(Dictionary<string, Record> dictionary)
{
+    if (dictionary == null)
+    {
+        records = new ConcurrentDictionary<string, Record>();
+        return;
+    }
     records = new ConcurrentDictionary<string, Record>(dictionary);
}

Line range hint 9-44: Consider architectural improvements

  1. Extract common null checking logic into a helper method to reduce code duplication:
private string GetValidQueryKey(Result result)
{
    return result?.OriginQuery?.RawQuery;
}
  1. Consider adding logging for debugging purposes, especially around null queries and concurrent operations.

This will improve maintainability and make debugging easier.

📜 Review details

Configuration used: CodeRabbit UI
Review profile: CHILL

📥 Commits

Reviewing files that changed from the base of the PR and between c3c6126 and c665482.

📒 Files selected for processing (1)
  • Flow.Launcher/Storage/TopMostRecord.cs (2 hunks)
🔇 Additional comments (3)
Flow.Launcher/Storage/TopMostRecord.cs (3)

1-1: LGTM! Good improvement for thread safety

The switch to ConcurrentDictionary is a solid improvement that will help prevent race conditions when multiple threads access the records collection.

Also applies to: 11-11


15-22: LGTM! Robust null checking and thread-safe access

The changes properly handle:

  • Empty collection check
  • Null query check
  • Thread-safe value retrieval using TryGetValue

27-27: ⚠️ Potential issue

Add null check to prevent NullReferenceException

The method is still vulnerable to NullReferenceException when result.OriginQuery is null.

Apply this fix:

- records.Remove(result.OriginQuery.RawQuery, out _);
+ if (result?.OriginQuery?.RawQuery != null)
+ {
+     records.TryRemove(result.OriginQuery.RawQuery, out _);
+ }

@onesounds
Copy link
Contributor

@onesounds how do you think about the not supported image?

Blank area is not good. It's weird that there's no image at all. If there's an imgmissing icon like an appmissing icon, ok.
image
Like an example screenshot, such an "empty image" should come out anyway.

@Jack251970
Copy link
Member Author

@onesounds I will try to provide an image to show this message.

This comment has been minimized.

Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 0

🧹 Outside diff range and nitpick comments (1)
Flow.Launcher.Infrastructure/Constant.cs (1)

34-34: Consider adding XML documentation.

To maintain consistency and clarity, consider adding XML documentation to explain when this icon is used (i.e., for unsupported image formats).

+        /// <summary>
+        /// Icon displayed when an image format is not supported for preview.
+        /// </summary>
         public static readonly string NoImageIcon = Path.Combine(ImagesDirectory, "no_image.png");
📜 Review details

Configuration used: CodeRabbit UI
Review profile: CHILL

📥 Commits

Reviewing files that changed from the base of the PR and between c665482 and 4830988.

⛔ Files ignored due to path filters (1)
  • Flow.Launcher/Images/no_image.png is excluded by !**/*.png
📒 Files selected for processing (2)
  • Flow.Launcher.Infrastructure/Constant.cs (1 hunks)
  • Flow.Launcher.Infrastructure/Image/ImageLoader.cs (2 hunks)
🚧 Files skipped from review as they are similar to previous changes (1)
  • Flow.Launcher.Infrastructure/Image/ImageLoader.cs
🔇 Additional comments (1)
Flow.Launcher.Infrastructure/Constant.cs (1)

34-34: LGTM! Verify the image file exists.

The new constant follows the established pattern and is correctly implemented using Path.Combine.

Let's verify the image file exists:

✅ Verification successful

The no_image.png file exists and is correctly placed

The file no_image.png (2051 bytes) exists in the main Flow.Launcher/Images/ directory, which is the correct location referenced by the ImagesDirectory constant. The implementation is verified to be working as intended.

🏁 Scripts executed

The following scripts were executed for the analysis:

Script:

#!/bin/bash
# Description: Verify the no_image.png file exists in the Images directory

# Find the Images directory and check for no_image.png
fd -t f "no_image.png" -p "Images/"

Length of output: 128


Script:

#!/bin/bash
# Let's first find the actual Images directory location
fd -t d "Images" --full-path

# Then let's check the contents of any Images directories
fd -t d "Images" --exec ls -la {}

Length of output: 10021

@prlabeler prlabeler bot added the enhancement New feature or request label Dec 2, 2024
@Jack251970 Jack251970 requested a review from taooceros December 2, 2024 05:56
@Jack251970
Copy link
Member Author

@taooceros Have got a new icon for the images whose formats are not supported.

@Jack251970
Copy link
Member Author

@onesounds What do you think of this icon?

image

@onesounds
Copy link
Contributor

It's not bad, but I don't think it's the best. Isn't there this icon?
Screenshot_20241203_141204_GitHub.jpg

@Jack251970
Copy link
Member Author

@onesounds Please take a look in https://icons8.com/icon/set/image/fluency. Any image recommended?

@onesounds
Copy link
Contributor

onesounds commented Dec 3, 2024

Screenshot_20241203_142349_GitHub.jpg

How about this? Images exist and can be opened, only previews are not possible. The red line feels like there's something wrong with the file.

((I look good on the right)

@Jack251970
Copy link
Member Author

Jack251970 commented Dec 3, 2024

@onesounds Yeah, the right icon is better than my original icon with red line. And for this icon called Broken Image:
image
What do you think of it?

I search on web and I have learnt that unsupported file format is one kind of the causes of the broken images. Perhaps this can let user know this image is broken and cannot be previewed for some reasons?

@Jack251970
Copy link
Member Author

@onesounds And thanks for your help!

@onesounds
Copy link
Contributor

onesounds commented Dec 3, 2024

@onesounds Yeah, the right icon is better than my original icon with red line. And for this icon called Broken Image:
image
What do you think of it?

I search on web and I have learnt that unsupported file format is one kind of the causes of the broken images. Perhaps this can let user know this image is broken and cannot be previewed for some reasons?

If the file is not normal, you can use the icon you chose. But if it's a file that the preview doesn't support, it should just be an image icon. (Windows also process previews as icons if possible, and other images as image icons.) I understand that there is no indication of broken files. I won't distinguish it because it's based on extensions. You don't show a file in the preview called a "broken file." You shouldn't choose an icon that can be misunderstood like this. "The issues of 'a corrupted file' and 'unable to display a preview' are separate. Therefore, using an icon for 'file corruption' to represent 'unable to display a preview' is inappropriate.

(I am traveling, so I haven’t tested this PR overseas.) If this PR only applies to completely broken images, you may use the icon you selected.

This comment has been minimized.

@Jack251970
Copy link
Member Author

Now updated the image and will show the preview like this:
image

Copy link

github-actions bot commented Dec 4, 2024

@check-spelling-bot Report

🔴 Please review

See the 📂 files view, the 📜action log, or 📝 job summary for details.

❌ Errors Count
❌ forbidden-pattern 22
❌ ignored-expect-variant 1
⚠️ non-alpha-in-dictionary 19

See ❌ Event descriptions for more information.

If the flagged items are 🤯 false positives

If items relate to a ...

  • binary file (or some other file you wouldn't want to check at all).

    Please add a file path to the excludes.txt file matching the containing file.

    File paths are Perl 5 Regular Expressions - you can test yours before committing to verify it will match your files.

    ^ refers to the file's path from the root of the repository, so ^README\.md$ would exclude README.md (on whichever branch you're using).

  • well-formed pattern.

    If you can write a pattern that would match it,
    try adding it to the patterns.txt file.

    Patterns are Perl 5 Regular Expressions - you can test yours before committing to verify it will match your lines.

    Note that patterns can't match multiline strings.

Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 1

📜 Review details

Configuration used: CodeRabbit UI
Review profile: CHILL

📥 Commits

Reviewing files that changed from the base of the PR and between 3f413d1 and 00e76f6.

📒 Files selected for processing (1)
  • Flow.Launcher/ViewModel/MainViewModel.cs (5 hunks)
🔇 Additional comments (2)
Flow.Launcher/ViewModel/MainViewModel.cs (2)

727-728: LGTM: Well-designed property for plugin icon source

The property is correctly implemented as read-only with private setter and appropriate default value.


1073-1073: Verify the status of concurrent access fixes

Based on previous review comments, there were discussions about using ConcurrentDictionary/ConcurrentBag for thread-safe operations. Let's verify if these changes were implemented elsewhere.

✅ Verification successful

Let me gather more information about the concurrent access implementation in the MainViewModel.cs file to verify if the changes were properly implemented.


Based on the search results, I can now provide a final response:

Concurrent access has been properly implemented

The codebase shows a well-structured approach to handle concurrent access:

  • Uses a Channel-based architecture for result updates via ResultsForUpdate and _resultsUpdateChannelWriter
  • Implements thread-safe collections where needed (e.g., TopMostRecord uses ConcurrentDictionary)
  • Results updates are processed through a dedicated task (_resultsViewUpdateTask) that safely handles collection modifications
  • Collection modifications (Clear/Add) are performed through the channel system, preventing direct concurrent access
🏁 Scripts executed

The following scripts were executed for the analysis:

Script:

#!/bin/bash
# Description: Check for concurrent collection usage in the codebase

# Search for ConcurrentDictionary usage
echo "Searching for ConcurrentDictionary usage..."
rg "ConcurrentDictionary" -A 5

# Search for ConcurrentBag usage
echo "Searching for ConcurrentBag usage..."
rg "ConcurrentBag" -A 5

# Search for synchronization related changes in TopMostRecord
echo "Searching for TopMostRecord implementation..."
rg "class TopMostRecord" -A 10

Length of output: 6617


Script:

#!/bin/bash
# Search for concurrent access related code in MainViewModel.cs
echo "Searching for result collection handling in MainViewModel.cs..."
rg "Results" -A 5 "Flow.Launcher/ViewModel/MainViewModel.cs"

# Search for any lock or synchronization mechanisms
echo "Searching for synchronization mechanisms..."
rg "lock\s*\(" -A 5 "Flow.Launcher/ViewModel/MainViewModel.cs"

# Search for collection modifications
echo "Searching for collection modifications..."
rg "Clear|Add|Remove" -A 5 "Flow.Launcher/ViewModel/MainViewModel.cs"

Length of output: 18643

Copy link
Member

@taooceros taooceros left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Sorry for the late response. Thanks!

@taooceros taooceros merged commit 5f46ee7 into Flow-Launcher:dev Dec 7, 2024
4 checks passed
@taooceros
Copy link
Member

@Jack251970 Could you move the reverted change to a new pr?

@Jack251970
Copy link
Member Author

@taooceros Moved. Please check #3115.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

bug Something isn't working

Projects

None yet

Development

Successfully merging this pull request may close these issues.

4 participants