Refactor Bookmark plugin

This commit is contained in:
Kevin Zhang 2021-09-21 13:50:51 -05:00
parent df9cb4b1a4
commit 7ff9b688c9
9 changed files with 156 additions and 231 deletions

View file

@ -7,50 +7,19 @@ using System.Text;
namespace Flow.Launcher.Plugin.BrowserBookmark
{
public class Bookmark : IEquatable<Bookmark>, IEqualityComparer<Bookmark>
// Source may be important in the future
public record Bookmark(string Name, string Url, string Source = "")
{
private string m_Name;
public string Name
public override int GetHashCode()
{
get
{
return m_Name;
}
set
{
m_Name = value;
}
}
public string Url { get; set; }
public string Source { get; set; }
public int Score { get; set; }
/* TODO: since Source maybe unimportant, we just need to compare Name and Url */
public bool Equals(Bookmark other)
{
return Equals(this, other);
}
public bool Equals(Bookmark x, Bookmark y)
{
if (Object.ReferenceEquals(x, y)) return true;
if (Object.ReferenceEquals(x, null) || Object.ReferenceEquals(y, null))
return false;
return x.Name == y.Name && x.Url == y.Url;
}
public int GetHashCode(Bookmark bookmark)
{
if (Object.ReferenceEquals(bookmark, null)) return 0;
int hashName = bookmark.Name == null ? 0 : bookmark.Name.GetHashCode();
int hashUrl = bookmark.Url == null ? 0 : bookmark.Url.GetHashCode();
var hashName = Name?.GetHashCode() ?? 0;
var hashUrl = Url?.GetHashCode() ?? 0;
return hashName ^ hashUrl;
}
public override int GetHashCode()
public virtual bool Equals(Bookmark other)
{
return GetHashCode(this);
return other != null && Name == other.Name && Url == other.Url;
}
}
}
}

View file

@ -0,0 +1,26 @@
using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Text.RegularExpressions;
namespace Flow.Launcher.Plugin.BrowserBookmark
{
public class ChromeBookmarkLoader : ChromiumBookmarkLoader
{
public override List<Bookmark> GetBookmarks()
{
return LoadChromeBookmarks();
}
private List<Bookmark> LoadChromeBookmarks()
{
var bookmarks = new List<Bookmark>();
String platformPath = Environment.GetFolderPath(Environment.SpecialFolder.LocalApplicationData);
bookmarks.AddRange(LoadBookmarks(Path.Combine(platformPath, @"Google\Chrome\User Data"), "Google Chrome"));
bookmarks.AddRange(LoadBookmarks(Path.Combine(platformPath, @"Google\Chrome SxS\User Data"), "Google Chrome Canary"));
bookmarks.AddRange(LoadBookmarks(Path.Combine(platformPath, @"Chromium\User Data"), "Chromium"));
return bookmarks;
}
}
}

View file

@ -1,87 +0,0 @@
using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Text.RegularExpressions;
namespace Flow.Launcher.Plugin.BrowserBookmark
{
public class ChromeBookmarks
{
private List<Bookmark> bookmarks = new List<Bookmark>();
public List<Bookmark> GetBookmarks()
{
bookmarks.Clear();
LoadChromeBookmarks();
return bookmarks;
}
private void ParseChromeBookmarks(String path, string source)
{
if (!File.Exists(path)) return;
string all = File.ReadAllText(path);
Regex nameRegex = new Regex("\"name\": \"(?<name>.*?)\"");
MatchCollection nameCollection = nameRegex.Matches(all);
Regex typeRegex = new Regex("\"type\": \"(?<type>.*?)\"");
MatchCollection typeCollection = typeRegex.Matches(all);
Regex urlRegex = new Regex("\"url\": \"(?<url>.*?)\"");
MatchCollection urlCollection = urlRegex.Matches(all);
List<string> names = (from Match match in nameCollection select match.Groups["name"].Value).ToList();
List<string> types = (from Match match in typeCollection select match.Groups["type"].Value).ToList();
List<string> urls = (from Match match in urlCollection select match.Groups["url"].Value).ToList();
int urlIndex = 0;
for (int i = 0; i < names.Count; i++)
{
string name = DecodeUnicode(names[i]);
string type = types[i];
if (type == "url")
{
string url = urls[urlIndex];
urlIndex++;
if (url == null) continue;
if (url.StartsWith("javascript:", StringComparison.OrdinalIgnoreCase)) continue;
if (url.StartsWith("vbscript:", StringComparison.OrdinalIgnoreCase)) continue;
bookmarks.Add(new Bookmark()
{
Name = name,
Url = url,
Source = source
});
}
}
}
private void LoadChromeBookmarks(string path, string name)
{
if (!Directory.Exists(path)) return;
var paths = Directory.GetDirectories(path);
foreach (var profile in paths)
{
if (File.Exists(Path.Combine(profile, "Bookmarks")))
ParseChromeBookmarks(Path.Combine(profile, "Bookmarks"), name + (Path.GetFileName(profile) == "Default" ? "" : (" (" + Path.GetFileName(profile) + ")")));
}
}
private void LoadChromeBookmarks()
{
String platformPath = Environment.GetFolderPath(Environment.SpecialFolder.LocalApplicationData);
LoadChromeBookmarks(Path.Combine(platformPath, @"Google\Chrome\User Data"), "Google Chrome");
LoadChromeBookmarks(Path.Combine(platformPath, @"Google\Chrome SxS\User Data"), "Google Chrome Canary");
LoadChromeBookmarks(Path.Combine(platformPath, @"Chromium\User Data"), "Chromium");
}
private String DecodeUnicode(String dataStr)
{
Regex reg = new Regex(@"(?i)\\[uU]([0-9a-f]{4})");
return reg.Replace(dataStr, m => ((char)Convert.ToInt32(m.Groups[1].Value, 16)).ToString());
}
}
}

View file

@ -0,0 +1,62 @@
using Microsoft.AspNetCore.Authentication;
using System.Collections.Generic;
using System.IO;
using System.Text.Json;
namespace Flow.Launcher.Plugin.BrowserBookmark
{
public abstract class ChromiumBookmarkLoader : IBookmarkLoader
{
public abstract List<Bookmark> GetBookmarks();
protected List<Bookmark> LoadBookmarks(string browserDataPath, string name)
{
var bookmarks = new List<Bookmark>();
if (!Directory.Exists(browserDataPath)) return bookmarks;
var paths = Directory.GetDirectories(browserDataPath);
foreach (var profile in paths)
{
var bookmarkPath = Path.Combine(profile, "Bookmarks");
if (!File.Exists(bookmarkPath))
continue;
var source = name + (Path.GetFileName(profile) == "Default" ? "" : $" ({Path.GetFileName(profile)})");
bookmarks.AddRange(LoadBookmarksFromFile(bookmarkPath, source));
}
return bookmarks;
}
protected List<Bookmark> LoadBookmarksFromFile(string path, string source)
{
var bookmarks = new List<Bookmark>();
using var jsonDocument = JsonDocument.Parse(File.ReadAllText(path));
if (!jsonDocument.RootElement.TryGetProperty("roots", out var rootElement))
return new();
foreach (var folder in rootElement.EnumerateObject())
{
EnumerateFolderBookmark(folder.Value, bookmarks, source);
}
return bookmarks;
}
private void EnumerateFolderBookmark(JsonElement folderElement, List<Bookmark> bookmarks, string source)
{
foreach (var subElement in folderElement.GetProperty("children").EnumerateArray())
{
switch (subElement.GetProperty("type").GetString())
{
case "folder":
EnumerateFolderBookmark(subElement, bookmarks, source);
break;
default:
bookmarks.Add(new Bookmark(
subElement.GetProperty("name").GetString(),
subElement.GetProperty("url").GetString(),
source));
break;
}
}
}
}
}

View file

@ -20,9 +20,9 @@ namespace Flow.Launcher.Plugin.BrowserBookmark.Commands
{
var allbookmarks = new List<Bookmark>();
var chromeBookmarks = new ChromeBookmarks();
var mozBookmarks = new FirefoxBookmarks();
var edgeBookmarks = new EdgeBookmarks();
var chromeBookmarks = new ChromeBookmarkLoader();
var mozBookmarks = new FirefoxBookmarkLoader();
var edgeBookmarks = new EdgeBookmarkLoader();
//TODO: Let the user select which browser's bookmarks are displayed
// Add Firefox bookmarks

View file

@ -0,0 +1,30 @@
using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Text.Json;
using System.Text.RegularExpressions;
namespace Flow.Launcher.Plugin.BrowserBookmark
{
public class EdgeBookmarkLoader : ChromiumBookmarkLoader
{
private readonly List<Bookmark> _bookmarks = new();
private void LoadEdgeBookmarks()
{
var platformPath = Environment.GetFolderPath(Environment.SpecialFolder.LocalApplicationData);
LoadBookmarks(Path.Combine(platformPath, @"Microsoft\Edge\User Data"), "Microsoft Edge");
LoadBookmarks(Path.Combine(platformPath, @"Microsoft\Edge Dev\User Data"), "Microsoft Edge Dev");
LoadBookmarks(Path.Combine(platformPath, @"Microsoft\Edge SxS\User Data"), "Microsoft Edge Canary");
}
public override List<Bookmark> GetBookmarks()
{
_bookmarks.Clear();
LoadEdgeBookmarks();
return _bookmarks;
}
}
}

View file

@ -1,87 +0,0 @@
using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Text.RegularExpressions;
namespace Flow.Launcher.Plugin.BrowserBookmark
{
public class EdgeBookmarks
{
private List<Bookmark> bookmarks = new List<Bookmark>();
public List<Bookmark> GetBookmarks()
{
bookmarks.Clear();
LoadEdgeBookmarks();
return bookmarks;
}
private void ParseEdgeBookmarks(string path, string source)
{
if (!File.Exists(path)) return;
string all = File.ReadAllText(path);
Regex nameRegex = new Regex("\"name\": \"(?<name>.*?)\"");
MatchCollection nameCollection = nameRegex.Matches(all);
Regex typeRegex = new Regex("\"type\": \"(?<type>.*?)\"");
MatchCollection typeCollection = typeRegex.Matches(all);
Regex urlRegex = new Regex("\"url\": \"(?<url>.*?)\"");
MatchCollection urlCollection = urlRegex.Matches(all);
List<string> names = (from Match match in nameCollection select match.Groups["name"].Value).ToList();
List<string> types = (from Match match in typeCollection select match.Groups["type"].Value).ToList();
List<string> urls = (from Match match in urlCollection select match.Groups["url"].Value).ToList();
int urlIndex = 0;
for (int i = 0; i < names.Count; i++)
{
string name = DecodeUnicode(names[i]);
string type = types[i];
if (type == "url")
{
string url = urls[urlIndex];
urlIndex++;
if (url == null) continue;
if (url.StartsWith("javascript:", StringComparison.OrdinalIgnoreCase)) continue;
if (url.StartsWith("vbscript:", StringComparison.OrdinalIgnoreCase)) continue;
bookmarks.Add(new Bookmark()
{
Name = name,
Url = url,
Source = source
});
}
}
}
private void LoadEdgeBookmarks(string path, string name)
{
if (!Directory.Exists(path)) return;
var paths = Directory.GetDirectories(path);
foreach (var profile in paths)
{
if (File.Exists(Path.Combine(profile, "Bookmarks")))
ParseEdgeBookmarks(Path.Combine(profile, "Bookmarks"), name + (Path.GetFileName(profile) == "Default" ? "" : (" (" + Path.GetFileName(profile) + ")")));
}
}
private void LoadEdgeBookmarks()
{
string platformPath = Environment.GetFolderPath(Environment.SpecialFolder.LocalApplicationData);
LoadEdgeBookmarks(Path.Combine(platformPath, @"Microsoft\Edge\User Data"), "Microsoft Edge");
LoadEdgeBookmarks(Path.Combine(platformPath, @"Microsoft\Edge Dev\User Data"), "Microsoft Edge Dev");
LoadEdgeBookmarks(Path.Combine(platformPath, @"Microsoft\Edge SxS\User Data"), "Microsoft Edge Canary");
}
private string DecodeUnicode(string dataStr)
{
Regex reg = new Regex(@"(?i)\\[uU]([0-9a-f]{4})");
return reg.Replace(dataStr, m => ((char)Convert.ToInt32(m.Groups[1].Value, 16)).ToString());
}
}
}

View file

@ -6,7 +6,7 @@ using System.Linq;
namespace Flow.Launcher.Plugin.BrowserBookmark
{
public class FirefoxBookmarks
public class FirefoxBookmarkLoader : IBookmarkLoader
{
private const string queryAllBookmarks = @"SELECT moz_places.url, moz_bookmarks.title
FROM moz_places
@ -27,10 +27,10 @@ namespace Flow.Launcher.Plugin.BrowserBookmark
if (string.IsNullOrEmpty(PlacesPath) || !File.Exists(PlacesPath))
return new List<Bookmark>();
var bookmarList = new List<Bookmark>();
var bookmarkList = new List<Bookmark>();
// create the connection string and init the connection
string dbPath = string.Format(dbPathFormat, PlacesPath);
string dbPath = string.Format(dbPathFormat, PlacesPath);
using (var dbConnection = new SQLiteConnection(dbPath))
{
// Open connection to the database file and execute the query
@ -38,14 +38,13 @@ namespace Flow.Launcher.Plugin.BrowserBookmark
var reader = new SQLiteCommand(queryAllBookmarks, dbConnection).ExecuteReader();
// return results in List<Bookmark> format
bookmarList = reader.Select(x => new Bookmark()
{
Name = (x["title"] is DBNull) ? string.Empty : x["title"].ToString(),
Url = x["url"].ToString()
}).ToList();
bookmarkList = reader.Select(
x => new Bookmark(x["title"] is DBNull ? string.Empty : x["title"].ToString(),
x["url"].ToString())
).ToList();
}
return bookmarList;
return bookmarkList;
}
/// <summary>
@ -63,7 +62,8 @@ namespace Flow.Launcher.Plugin.BrowserBookmark
// get firefox default profile directory from profiles.ini
string ini;
using (var sReader = new StreamReader(profileIni)) {
using (var sReader = new StreamReader(profileIni))
{
ini = sReader.ReadToEnd();
}
@ -95,7 +95,10 @@ namespace Flow.Launcher.Plugin.BrowserBookmark
Version=2
*/
var lines = ini.Split(new string[] { "\r\n" }, StringSplitOptions.None).ToList();
var lines = ini.Split(new string[]
{
"\r\n"
}, StringSplitOptions.None).ToList();
var defaultProfileFolderNameRaw = lines.Where(x => x.Contains("Default=") && x != "Default=1").FirstOrDefault() ?? string.Empty;
@ -104,14 +107,14 @@ namespace Flow.Launcher.Plugin.BrowserBookmark
var defaultProfileFolderName = defaultProfileFolderNameRaw.Split('=').Last();
var indexOfDefaultProfileAtttributePath = lines.IndexOf("Path="+ defaultProfileFolderName);
var indexOfDefaultProfileAtttributePath = lines.IndexOf("Path=" + defaultProfileFolderName);
// Seen in the example above, the IsRelative attribute is always above the Path attribute
var relativeAttribute = lines[indexOfDefaultProfileAtttributePath - 1];
return relativeAttribute == "0" // See above, the profile is located in a custom location, path is not relative, so IsRelative=0
? defaultProfileFolderName + @"\places.sqlite"
: Path.Combine(profileFolderPath, defaultProfileFolderName) + @"\places.sqlite";
? defaultProfileFolderName + @"\places.sqlite"
: Path.Combine(profileFolderPath, defaultProfileFolderName) + @"\places.sqlite";
}
}
}
@ -126,4 +129,4 @@ namespace Flow.Launcher.Plugin.BrowserBookmark
}
}
}
}
}

View file

@ -0,0 +1,9 @@
using System.Collections.Generic;
namespace Flow.Launcher.Plugin.BrowserBookmark
{
public interface IBookmarkLoader
{
public List<Bookmark> GetBookmarks();
}
}