Skip to content

Conversation

onesounds
Copy link
Contributor

@onesounds onesounds commented Mar 19, 2025

What's the PR

  • Support svg image file loading
  • Show Favicon with bookmark plugin
  • Unlike previous PRs, there is no network lookup. Instead, it retrieves the Favicon from local bookmark data, caches it in Flow, and then loads it.
  • Copilot told me that the images retrieved from bookmark data are separately cached in Flow and loaded by matching them when a URL is queried.
  • Improve Plugin Project Code Quality and References which can help improve plugin loading performance.
  • Improve IPublicAPI Documents.

Close #3132.

TODO

Test Cases

  • Works fine Chromium, edge, firefox.

Known issue

  • From my testing, I confirmed that it works fine on Chrome, Edge, and Firefox. In Firefox and Edge, there is an issue where the image appears small when added to the bookmark bar. However, I verified that it works correctly when added to bookmarks instead of the bookmark bar. It seems that Firefox and Edge saves a smaller image when adding it to the bookmark bar.
  • I'm not sure if I applied the NuGet package correctly. It has been applied to all .csproj files, but someone needs to check if it was applied only where necessary.
  • Flow may need an internal process to clear the bookmark plugin's favicon cache.

This comment has been minimized.

@Yusyuriv Yusyuriv self-requested a review March 19, 2025 20:23
@Yusyuriv Yusyuriv added the enhancement New feature or request label Mar 19, 2025
@Jack251970
Copy link
Member

@onesounds Similar to #3079? Should we close it?

This comment has been minimized.

This comment has been minimized.

@onesounds
Copy link
Contributor Author

onesounds commented Mar 20, 2025

@onesounds Similar to #3079? Should we close it?

I made it to do that.

This comment has been minimized.

This comment has been minimized.

This comment has been minimized.

This comment has been minimized.

This comment has been minimized.

This comment has been minimized.

This comment has been minimized.

@Jack251970 Jack251970 requested a review from Copilot April 9, 2025 09:20
@Jack251970 Jack251970 changed the title Local favicon for bookmark plugin & Improve Plugin Project Code Quality and References & Improve IPublicAPI Documents Support Svg File Icon Loading & Local favicon for bookmark plugin Apr 9, 2025
Copy link
Contributor

@Copilot Copilot AI left a comment

Choose a reason for hiding this comment

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

Copilot reviewed 30 out of 34 changed files in this pull request and generated 2 comments.

Files not reviewed (4)
  • Flow.Launcher.Infrastructure/Flow.Launcher.Infrastructure.csproj: Language not supported
  • Flow.Launcher.Infrastructure/NativeMethods.txt: Language not supported
  • Flow.Launcher.Plugin/NativeMethods.txt: Language not supported
  • Plugins/Flow.Launcher.Plugin.BrowserBookmark/Flow.Launcher.Plugin.BrowserBookmark.csproj: Language not supported

This comment has been minimized.

@Jack251970 Jack251970 requested a review from Copilot April 9, 2025 09:27

This comment has been minimized.

Copy link
Contributor

@Copilot Copilot AI left a comment

Choose a reason for hiding this comment

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

Copilot reviewed 30 out of 34 changed files in this pull request and generated 1 comment.

Files not reviewed (4)
  • Flow.Launcher.Infrastructure/Flow.Launcher.Infrastructure.csproj: Language not supported
  • Flow.Launcher.Infrastructure/NativeMethods.txt: Language not supported
  • Flow.Launcher.Plugin/NativeMethods.txt: Language not supported
  • Plugins/Flow.Launcher.Plugin.BrowserBookmark/Flow.Launcher.Plugin.BrowserBookmark.csproj: Language not supported

@Jack251970 Jack251970 requested a review from Copilot April 9, 2025 09:29
Copy link
Contributor

@Copilot Copilot AI left a comment

Choose a reason for hiding this comment

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

Copilot reviewed 30 out of 34 changed files in this pull request and generated no comments.

Files not reviewed (4)
  • Flow.Launcher.Infrastructure/Flow.Launcher.Infrastructure.csproj: Language not supported
  • Flow.Launcher.Infrastructure/NativeMethods.txt: Language not supported
  • Flow.Launcher.Plugin/NativeMethods.txt: Language not supported
  • Plugins/Flow.Launcher.Plugin.BrowserBookmark/Flow.Launcher.Plugin.BrowserBookmark.csproj: Language not supported
Comments suppressed due to low confidence (2)

Plugins/Flow.Launcher.Plugin.BrowserBookmark/ChromiumBookmarkLoader.cs:130

  • Deleting the temporary favicon DB file in the finally block immediately after copying will remove the file before it is used to open the SQLite connection. Consider moving the deletion to after the connection has been processed to avoid file not found errors.
finally { File.Delete(tempDbPath); }

Plugins/Flow.Launcher.Plugin.BrowserBookmark/FirefoxBookmarkLoader.cs:234

  • Changing the 'PlacesPath' property to static may affect support for multiple Firefox profiles by sharing the same value across instances. Please verify if this change is intentional.
private static string PlacesPath

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

🧹 Nitpick comments (3)
Plugins/Flow.Launcher.Plugin.BrowserBookmark/FirefoxBookmarkLoader.cs (3)

44-87: Consider using try-finally for temporary file cleanup.

The error handling and temporary file approach is good for avoiding lock issues with the original database. However, if an exception occurs between creating and deleting the temporary file, it could leave orphaned files in the cache directory.

-        var tempDbPath = Path.Combine(_faviconCacheDir, $"tempplaces_{Guid.NewGuid()}.sqlite");
-
-        try
+        var tempDbPath = Path.Combine(_faviconCacheDir, $"tempplaces_{Guid.NewGuid()}.sqlite");
+        try
+        {
+            // Try to register file monitoring
+            try
+            {
+                Main.RegisterBookmarkFile(placesPath);
+            }
+            catch (Exception ex)
+            {
+                Main._context.API.LogException(ClassName, $"Failed to register Firefox bookmark file monitoring: {placesPath}", ex);
+            }
+
+            // Use a copy to avoid lock issues with the original file
+            File.Copy(placesPath, tempDbPath, true);
+
+            // Connect to database and execute query
+            string dbPath = string.Format(DbPathFormat, tempDbPath);
+            using var dbConnection = new SqliteConnection(dbPath);
+            dbConnection.Open();
+            var reader = new SqliteCommand(QueryAllBookmarks, dbConnection).ExecuteReader();
+
+            // Create bookmark list
+            bookmarks = reader
+                .Select(
+                    x => new Bookmark(
+                        x["title"] is DBNull ? string.Empty : x["title"].ToString(),
+                        x["url"].ToString(),
+                        "Firefox"
+                    )
+                )
+                .ToList();
+
+            // Path to favicon database
+            var faviconDbPath = Path.Combine(Path.GetDirectoryName(placesPath), "favicons.sqlite");
+            if (File.Exists(faviconDbPath))
+            {
+                LoadFaviconsFromDb(faviconDbPath, bookmarks);
+            }
+            // https://github.com/dotnet/efcore/issues/26580
+            SqliteConnection.ClearPool(dbConnection);
+            dbConnection.Close();
+        }
+        catch (Exception ex)
+        {
+            Main._context.API.LogException(ClassName, $"Failed to load Firefox bookmarks: {placesPath}", ex);
+        }
+        finally
+        {
+            // Delete temporary file
+            try
+            {
+                if (File.Exists(tempDbPath))
+                {
+                    File.Delete(tempDbPath);
+                }
+            }
+            catch (Exception ex)
+            {
+                Main._context.API.LogException(ClassName, $"Failed to delete temporary favicon DB: {tempDbPath}", ex);
+            }
+        }

106-197: Consider enhancing the favicon loading process.

The LoadFaviconsFromDb method is comprehensive but could benefit from a few improvements:

  1. Use a try-finally block for temporary file cleanup
  2. Enhance the domain extraction logic to handle edge cases like IP addresses
  3. Consider caching results to avoid redundant database queries for the same domain
private void LoadFaviconsFromDb(string faviconDbPath, List<Bookmark> bookmarks)
{
    var tempDbPath = Path.Combine(_faviconCacheDir, $"tempfavicons_{Guid.NewGuid()}.sqlite");
+    var processedDomains = new Dictionary<string, string>();
    
    try
    {
        // Use a copy to avoid lock issues with the original file
        File.Copy(faviconDbPath, tempDbPath, true);
        
        var defaultIconPath = Path.Combine(
            Path.GetDirectoryName(typeof(FirefoxBookmarkLoaderBase).Assembly.Location),
            "bookmark.png");

        string dbPath = string.Format(DbPathFormat, tempDbPath);
        using var connection = new SqliteConnection(dbPath);
        connection.Open();

        // Get favicons based on bookmark URLs
        foreach (var bookmark in bookmarks)
        {
            try
            {
                if (string.IsNullOrEmpty(bookmark.Url))
                    continue;

                // Extract domain from URL
                if (!Uri.TryCreate(bookmark.Url, UriKind.Absolute, out Uri uri))
                    continue;

                var domain = uri.Host;
+                
+                // Skip if we've already processed this domain
+                if (processedDomains.TryGetValue(domain, out string cachedPath))
+                {
+                    bookmark.FaviconPath = cachedPath;
+                    continue;
+                }

                // Query for latest Firefox version favicon structure
                using var cmd = connection.CreateCommand();
                cmd.CommandText = @"
                    SELECT i.data
                    FROM moz_icons i
                    JOIN moz_icons_to_pages ip ON i.id = ip.icon_id
                    JOIN moz_pages_w_icons p ON ip.page_id = p.id
                    WHERE p.page_url LIKE @url
                    AND i.data IS NOT NULL
                    ORDER BY i.width DESC  -- Select largest icon available
                    LIMIT 1";

                cmd.Parameters.AddWithValue("@url", $"%{domain}%");

                using var reader = cmd.ExecuteReader();
                if (!reader.Read() || reader.IsDBNull(0))
                    continue;

                var imageData = (byte[])reader["data"];

                if (imageData is not { Length: > 0 })
                    continue;

                string faviconPath;
                if (IsSvgData(imageData))
                {
                    faviconPath = Path.Combine(_faviconCacheDir, $"firefox_{domain}.svg");
                }
                else
                {
                    faviconPath = Path.Combine(_faviconCacheDir, $"firefox_{domain}.png");
                }
                SaveBitmapData(imageData, faviconPath);

                bookmark.FaviconPath = faviconPath;
+                processedDomains[domain] = faviconPath;
            }
            catch (Exception ex)
            {
                Main._context.API.LogException(ClassName, $"Failed to extract Firefox favicon: {bookmark.Url}", ex);
            }
        }

        // https://github.com/dotnet/efcore/issues/26580
        SqliteConnection.ClearPool(connection);
        connection.Close();
    }
    catch (Exception ex)
    {
        Main._context.API.LogException(ClassName, $"Failed to load Firefox favicon DB: {faviconDbPath}", ex);
    }
+    finally
+    {
+        // Delete temporary file
+        try
+        {
+            if (File.Exists(tempDbPath))
+            {
+                File.Delete(tempDbPath);
+            }
+        }
+        catch (Exception ex)
+        {
+            Main._context.API.LogException(ClassName, $"Failed to delete temporary favicon DB: {tempDbPath}", ex);
+        }
+    }
-
-    // Delete temporary file
-    try
-    {
-        File.Delete(tempDbPath);
-    }
-    catch (Exception ex)
-    {
-        Main._context.API.LogException(ClassName, $"Failed to delete temporary favicon DB: {tempDbPath}", ex);
-    }
}

211-218: Consider enhancing SVG detection for edge cases.

The current SVG detection logic is simple and effective for most cases, but it may not handle all SVG formats, especially those with unusual headers or encodings.

private static bool IsSvgData(byte[] data)
{
    if (data.Length < 5)
        return false;
    string start = System.Text.Encoding.ASCII.GetString(data, 0, Math.Min(100, data.Length));
-    return start.Contains("<svg") ||
-           (start.StartsWith("<?xml") && start.Contains("<svg"));
+    // More comprehensive SVG detection
+    return start.Contains("<svg", StringComparison.OrdinalIgnoreCase) ||
+           (start.StartsWith("<?xml", StringComparison.OrdinalIgnoreCase) && 
+            start.Contains("<svg", StringComparison.OrdinalIgnoreCase)) ||
+           start.Contains("image/svg+xml", StringComparison.OrdinalIgnoreCase);
}
📜 Review details

Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 62a5dd7 and 150ea84.

📒 Files selected for processing (1)
  • Plugins/Flow.Launcher.Plugin.BrowserBookmark/FirefoxBookmarkLoader.cs (3 hunks)
🧰 Additional context used
🧬 Code Graph Analysis (1)
Plugins/Flow.Launcher.Plugin.BrowserBookmark/FirefoxBookmarkLoader.cs (3)
Plugins/Flow.Launcher.Plugin.BrowserBookmark/Main.cs (4)
  • Main (16-255)
  • List (56-116)
  • List (205-235)
  • RegisterBookmarkFile (142-175)
Plugins/Flow.Launcher.Plugin.BrowserBookmark/ChromiumBookmarkLoader.cs (3)
  • List (21-21)
  • List (23-62)
  • List (64-76)
Plugins/Flow.Launcher.Plugin.BrowserBookmark/Commands/BookmarkLoader.cs (1)
  • List (19-56)
⏰ Context from checks skipped due to timeout of 90000ms (2)
  • GitHub Check: gitStream.cm
  • GitHub Check: gitStream.cm
🔇 Additional comments (4)
Plugins/Flow.Launcher.Plugin.BrowserBookmark/FirefoxBookmarkLoader.cs (4)

12-19: Well-structured class initialization with proper logging identifier.

The addition of the static ClassName field and the constructor with favicon cache directory initialization follows good practices. This approach provides consistent logging identification and properly initializes the cache directory from a central location.


37-43: Improved start of the method with better variable management.

Initializing the bookmarks list at the beginning and using it as the return value when the path check fails is a good practice. It makes the code more readable and maintainable by having a single return point.


199-209: Simple and effective bitmap saving method.

This utility method is well-structured with proper error handling. It follows the single responsibility principle by focusing only on saving image data to a file.


234-234: Good change to static property.

Making PlacesPath static is appropriate since it doesn't access instance state and returns the same value regardless of the instance. This change also aligns with its usage pattern.

This comment has been minimized.

@Jack251970 Jack251970 requested a review from Copilot April 9, 2025 09:32
Copy link
Contributor

@Copilot Copilot AI left a comment

Choose a reason for hiding this comment

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

Copilot reviewed 30 out of 34 changed files in this pull request and generated no comments.

Files not reviewed (4)
  • Flow.Launcher.Infrastructure/Flow.Launcher.Infrastructure.csproj: Language not supported
  • Flow.Launcher.Infrastructure/NativeMethods.txt: Language not supported
  • Flow.Launcher.Plugin/NativeMethods.txt: Language not supported
  • Plugins/Flow.Launcher.Plugin.BrowserBookmark/Flow.Launcher.Plugin.BrowserBookmark.csproj: Language not supported
Comments suppressed due to low confidence (2)

Flow.Launcher.Infrastructure/Image/ImageLoader.cs:242

  • Ensure that 'API' is defined and accessible in the ImageLoader class. If API is intended to reference the IPublicAPI instance, consider injecting it or using the appropriate static accessor to avoid compilation issues.
API.LogException(ClassName, "Failed to load image file from path " + path + ": " + ex.Message, ex);

Plugins/Flow.Launcher.Plugin.BrowserBookmark/FirefoxBookmarkLoader.cs:234

  • Changing the PlacesPath property to static may cause concurrency issues when processing multiple Firefox profiles. Consider reverting it to an instance property if multiple instances or profiles are expected.
private static string PlacesPath

Copy link

github-actions bot commented Apr 9, 2025

@check-spelling-bot Report

🔴 Please review

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

❌ Errors Count
❌ forbidden-pattern 22
⚠️ 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.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
Documentation Update required to documentation enhancement New feature or request
Projects
None yet
Development

Successfully merging this pull request may close these issues.

Support SVG files as icons
4 participants