Skip to content
Merged
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
139 changes: 89 additions & 50 deletions Plugins/Flow.Launcher.Plugin.BrowserBookmark/FirefoxBookmarkLoader.cs
Original file line number Diff line number Diff line change
Expand Up @@ -24,10 +24,10 @@

// Updated query - removed favicon_id column
private const string QueryAllBookmarks = """
SELECT moz_places.url, moz_bookmarks.title

Check warning on line 27 in Plugins/Flow.Launcher.Plugin.BrowserBookmark/FirefoxBookmarkLoader.cs

View workflow job for this annotation

GitHub Actions / Check Spelling

`moz` is not a recognized word. (unrecognized-spelling)
FROM moz_places

Check warning on line 28 in Plugins/Flow.Launcher.Plugin.BrowserBookmark/FirefoxBookmarkLoader.cs

View workflow job for this annotation

GitHub Actions / Check Spelling

`moz` is not a recognized word. (unrecognized-spelling)
INNER JOIN moz_bookmarks ON (

Check warning on line 29 in Plugins/Flow.Launcher.Plugin.BrowserBookmark/FirefoxBookmarkLoader.cs

View workflow job for this annotation

GitHub Actions / Check Spelling

`moz` is not a recognized word. (unrecognized-spelling)
moz_bookmarks.fk NOT NULL AND moz_bookmarks.title NOT NULL AND moz_bookmarks.fk = moz_places.id

Check warning on line 30 in Plugins/Flow.Launcher.Plugin.BrowserBookmark/FirefoxBookmarkLoader.cs

View workflow job for this annotation

GitHub Actions / Check Spelling

`moz` is not a recognized word. (unrecognized-spelling)
)
ORDER BY moz_places.visit_count DESC
""";
Expand Down Expand Up @@ -80,12 +80,12 @@
// Load favicons after loading bookmarks
if (Main._settings.EnableFavicons)
{
var faviconDbPath = Path.Combine(Path.GetDirectoryName(placesPath), "favicons.sqlite");

Check warning on line 83 in Plugins/Flow.Launcher.Plugin.BrowserBookmark/FirefoxBookmarkLoader.cs

View workflow job for this annotation

GitHub Actions / Check Spelling

`favicons` is not a recognized word. (unrecognized-spelling)
if (File.Exists(faviconDbPath))
{
Main._context.API.StopwatchLogInfo(ClassName, $"Load {bookmarks.Count} favicons cost", () =>

Check warning on line 86 in Plugins/Flow.Launcher.Plugin.BrowserBookmark/FirefoxBookmarkLoader.cs

View workflow job for this annotation

GitHub Actions / Check Spelling

`favicons` is not a recognized word. (unrecognized-spelling)
{
LoadFaviconsFromDb(faviconDbPath, bookmarks);

Check warning on line 88 in Plugins/Flow.Launcher.Plugin.BrowserBookmark/FirefoxBookmarkLoader.cs

View workflow job for this annotation

GitHub Actions / Check Spelling

`Favicons` is not a recognized word. (unrecognized-spelling)
});
}
}
Expand Down Expand Up @@ -264,83 +264,122 @@
/// </summary>
public override List<Bookmark> GetBookmarks()
{
return GetBookmarksFromPath(PlacesPath);
var bookmarks = new List<Bookmark>();
bookmarks.AddRange(GetBookmarksFromPath(PlacesPath));
bookmarks.AddRange(GetBookmarksFromPath(MsixPlacesPath));
return bookmarks;
}

/// <summary>
/// Path to places.sqlite
/// Path to places.sqlite of Msi installer
/// E.g. C:\Users\{UserName}\AppData\Roaming\Mozilla\Firefox
/// <see href="https://support.mozilla.org/en-US/kb/profiles-where-firefox-stores-user-data#w_finding-your-profile-without-opening-firefox"/>
/// </summary>
/// <remarks></remarks>
private static string PlacesPath
{
get
{
var profileFolderPath = Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.ApplicationData), @"Mozilla\Firefox");
var profileIni = Path.Combine(profileFolderPath, @"profiles.ini");
return GetProfileIniPath(profileFolderPath);
}
}

if (!File.Exists(profileIni))
return string.Empty;
/// <summary>
/// Path to places.sqlite of MSIX installer
/// E.g. C:\Users\{UserName}\AppData\Local\Packages\Mozilla.Firefox_n80bbvh6b1yt2\LocalCache\Roaming\Mozilla\Firefox
/// <see href="https://support.mozilla.org/en-US/kb/profiles-where-firefox-stores-user-data#w_finding-your-profile-without-opening-firefox"/>
/// </summary>
public static string MsixPlacesPath
{
get
{
var platformPath = Environment.GetFolderPath(Environment.SpecialFolder.LocalApplicationData);
var packagesPath = Path.Combine(platformPath, "Packages");
try
{
// Search for folder with Mozilla.Firefox prefix
var firefoxPackageFolder = Directory.EnumerateDirectories(packagesPath, "Mozilla.Firefox*",
SearchOption.TopDirectoryOnly).FirstOrDefault();

// Msix FireFox not installed
if (firefoxPackageFolder == null) return string.Empty;

// get firefox default profile directory from profiles.ini
using var sReader = new StreamReader(profileIni);
var ini = sReader.ReadToEnd();
var profileFolderPath = Path.Combine(firefoxPackageFolder, @"LocalCache\Roaming\Mozilla\Firefox");
return GetProfileIniPath(profileFolderPath);
}
catch
{
return string.Empty;
}
}
}

var lines = ini.Split("\r\n").ToList();
private static string GetProfileIniPath(string profileFolderPath)
{
var profileIni = Path.Combine(profileFolderPath, @"profiles.ini");
if (!File.Exists(profileIni))
return string.Empty;

var defaultProfileFolderNameRaw = lines.FirstOrDefault(x => x.Contains("Default=") && x != "Default=1") ?? string.Empty;
// get firefox default profile directory from profiles.ini
using var sReader = new StreamReader(profileIni);
var ini = sReader.ReadToEnd();

if (string.IsNullOrEmpty(defaultProfileFolderNameRaw))
return string.Empty;
var lines = ini.Split("\r\n").ToList();

var defaultProfileFolderName = defaultProfileFolderNameRaw.Split('=').Last();
var defaultProfileFolderNameRaw = lines.FirstOrDefault(x => x.Contains("Default=") && x != "Default=1") ?? string.Empty;

var indexOfDefaultProfileAttributePath = lines.IndexOf("Path=" + defaultProfileFolderName);
if (string.IsNullOrEmpty(defaultProfileFolderNameRaw))
return string.Empty;

/*
Current profiles.ini structure example as of Firefox version 69.0.1
var defaultProfileFolderName = defaultProfileFolderNameRaw.Split('=').Last();

[Install736426B0AF4A39CB]
Default=Profiles/7789f565.default-release <== this is the default profile this plugin will get the bookmarks from. When opened Firefox will load the default profile
Locked=1
var indexOfDefaultProfileAttributePath = lines.IndexOf("Path=" + defaultProfileFolderName);

[Profile2]
Name=newblahprofile
IsRelative=0
Path=C:\t6h2yuq8.newblahprofile <== Note this is a custom location path for the profile user can set, we need to cater for this in code.
/*
Current profiles.ini structure example as of Firefox version 69.0.1

[Profile1]
Name=default
IsRelative=1
Path=Profiles/cydum7q4.default
Default=1
[Install736426B0AF4A39CB]
Default=Profiles/7789f565.default-release <== this is the default profile this plugin will get the bookmarks from. When opened Firefox will load the default profile
Locked=1

[Profile0]
Name=default-release
IsRelative=1
Path=Profiles/7789f565.default-release
[Profile2]
Name=dummyprofile
IsRelative=0
Path=C:\t6h2yuq8.dummyprofile <== Note this is a custom location path for the profile user can set, we need to cater for this in code.

[General]
StartWithLastProfile=1
Version=2
*/
// Seen in the example above, the IsRelative attribute is always above the Path attribute
[Profile1]
Name=default
IsRelative=1
Path=Profiles/cydum7q4.default
Default=1

var relativePath = Path.Combine(defaultProfileFolderName, "places.sqlite");
var absoluePath = Path.Combine(profileFolderPath, relativePath);
[Profile0]
Name=default-release
IsRelative=1
Path=Profiles/7789f565.default-release

// If the index is out of range, it means that the default profile is in a custom location or the file is malformed
// If the profile is in a custom location, we need to check
if (indexOfDefaultProfileAttributePath - 1 < 0 ||
indexOfDefaultProfileAttributePath - 1 >= lines.Count)
{
return Directory.Exists(absoluePath) ? absoluePath : relativePath;
}
[General]
StartWithLastProfile=1
Version=2
*/
// Seen in the example above, the IsRelative attribute is always above the Path attribute

var relativeAttribute = lines[indexOfDefaultProfileAttributePath - 1];
var relativePath = Path.Combine(defaultProfileFolderName, "places.sqlite");
var absolutePath = Path.Combine(profileFolderPath, relativePath);

return relativeAttribute == "0" // See above, the profile is located in a custom location, path is not relative, so IsRelative=0
? relativePath : absoluePath;
// If the index is out of range, it means that the default profile is in a custom location or the file is malformed
// If the profile is in a custom location, we need to check
if (indexOfDefaultProfileAttributePath - 1 < 0 ||
indexOfDefaultProfileAttributePath - 1 >= lines.Count)
{
return Directory.Exists(absolutePath) ? absolutePath : relativePath;
}

var relativeAttribute = lines[indexOfDefaultProfileAttributePath - 1];

// See above, the profile is located in a custom location, path is not relative, so IsRelative=0
return (relativeAttribute == "0" || relativeAttribute == "IsRelative=0")
? relativePath : absolutePath;
}
}

Expand Down
Loading