diff --git a/BookmarkForm.cs b/BookmarkForm.cs index 4325cbf..6af7b81 100644 --- a/BookmarkForm.cs +++ b/BookmarkForm.cs @@ -33,13 +33,12 @@ namespace FizzyLauncher private System.ComponentModel.IContainer components; protected bool isBusy = false; - protected string faviconAddress = null; protected WebParser webParser = null; protected WebClient webClient = null; - public BookmarkForm(BookmarkItem model) : base() + public BookmarkForm(BookmarkItem model, Image icon) : base() { InitializeComponent(); @@ -47,7 +46,7 @@ namespace FizzyLauncher textBox2.Text = model.SiteAddress?.Trim() ?? string.Empty; memoBox1.Text = model.SiteDescription?.Trim() ?? string.Empty; memoBox2.Text = model.Notes?.Trim() ?? string.Empty; - faviconAddress = model.FaviconAddress; + pictureBox1.Image = icon; textBox2.SetTooltipText(toolTip1, "Retrieve web page information"); } @@ -308,19 +307,21 @@ namespace FizzyLauncher } - public BookmarkItem Model + public BookmarkResult Model { - get => new BookmarkItem() + get => new BookmarkResult() { - SiteName = textBox1.Text?.Trim() ?? string.Empty, - SiteAddress = textBox2.Text?.Trim() ?? string.Empty, - SiteDescription = memoBox1.Text?.Trim() ?? string.Empty, - Notes = memoBox2.Text?.Trim() ?? string.Empty, - FaviconAddress = faviconAddress + Item = new BookmarkItem() + { + SiteName = textBox1.Text?.Trim() ?? string.Empty, + SiteAddress = textBox2.Text?.Trim() ?? string.Empty, + SiteDescription = memoBox1.Text?.Trim() ?? string.Empty, + Notes = memoBox2.Text?.Trim() ?? string.Empty + }, + Icon = pictureBox1.Image }; } - public Image Favicon { get => pictureBox1.Image; } protected bool IsBusy { @@ -362,23 +363,25 @@ namespace FizzyLauncher return; } - BookmarkItem rs = webParser.RetrieveDetails(textBox2.Text); + BookmarkResult rs = webParser.RetrieveDetails(textBox2.Text); if (rs == null) { IsBusy = false; return; } - if (!string.IsNullOrWhiteSpace(rs.SiteName)) ThreadControl.SetText(textBox1, rs.SiteName); - if (!string.IsNullOrWhiteSpace(rs.SiteDescription)) ThreadControl.SetText(memoBox1, rs.SiteDescription); + if (rs.Item == null) rs.Item = new BookmarkItem(); - if (string.IsNullOrWhiteSpace(rs.FaviconAddress)) + if (!string.IsNullOrWhiteSpace(rs.Item.SiteName)) ThreadControl.SetText(textBox1, rs.Item.SiteName); + if (!string.IsNullOrWhiteSpace(rs.Item.SiteDescription)) ThreadControl.SetText(memoBox1, rs.Item.SiteDescription); + + if (string.IsNullOrWhiteSpace(rs.IconURL)) { ThreadControl.Clear(pictureBox1); } else { - ThreadControl.SetValue(pictureBox1, RetrieveImage(rs.FaviconAddress)); + ThreadControl.SetValue(pictureBox1, RetrieveImage(rs.IconURL)); } IsBusy = false; diff --git a/BookmarkManager.csproj b/BookmarkManager.csproj index a9f1e44..c091635 100644 --- a/BookmarkManager.csproj +++ b/BookmarkManager.csproj @@ -142,6 +142,7 @@ + diff --git a/Classes/BookmarkResult.cs b/Classes/BookmarkResult.cs new file mode 100644 index 0000000..3514f95 --- /dev/null +++ b/Classes/BookmarkResult.cs @@ -0,0 +1,15 @@ +using bzit.bomg.Models; +using System.Drawing; + +namespace BookmarkManager +{ + public class BookmarkResult + { + public BookmarkItem Item { get; set; } = null; + + public string IconURL { get; set; } = null; + + public Image Icon { get; set; } = null; + + } +} diff --git a/Classes/Crypto.cs b/Classes/Crypto.cs new file mode 100644 index 0000000..24747d8 --- /dev/null +++ b/Classes/Crypto.cs @@ -0,0 +1,22 @@ +using System; +using System.Security.Cryptography; +using System.Text; + +namespace BookmarkManager +{ + public class Crypto + { + public static string GetSHA256Hash(string text) + { + if (string.IsNullOrWhiteSpace(text)) return string.Empty; + + using (var sha = new SHA256Managed()) + { + byte[] hash = sha.ComputeHash(Encoding.UTF8.GetBytes(text)); + + return BitConverter.ToString(hash).Replace("-", string.Empty) + "+" + text.Length.ToString(); + } + } + + } +} \ No newline at end of file diff --git a/Classes/IconDatabase.cs b/Classes/IconDatabase.cs new file mode 100644 index 0000000..aa1fccc --- /dev/null +++ b/Classes/IconDatabase.cs @@ -0,0 +1,244 @@ +using Microsoft.Data.Sqlite; +using System; +using System.Drawing; +using System.Drawing.Imaging; +using System.IO; + +namespace BookmarkManager +{ + public class IconDatabase + { + protected string dbFilename = null; + protected SqliteConnection dbConnection = null; + + + public IconDatabase(string filename) + { + if (string.IsNullOrWhiteSpace(filename)) return; + + dbFilename = filename; + + bool isNew = !File.Exists(dbFilename); + + SqliteConnectionStringBuilder builder = new SqliteConnectionStringBuilder(); + builder.DataSource = dbFilename; + builder.Cache = SqliteCacheMode.Private; + builder.Mode = SqliteOpenMode.ReadWriteCreate; + + dbConnection = new SqliteConnection(builder.ToString()); + dbConnection.Open(); + + if (isNew) Initialise(); + + + + + //if (File.Exists(filename)) + //{ + // File.Delete("test.db3"); + //} + + } + + public bool AddIcon(string id, Image image) + { + if (dbConnection == null) return false; + if (string.IsNullOrWhiteSpace(id)) return false; + + if (image == null) return DeleteIcon(id); + if (HasIcon(id)) return UpdateIcon(id, image); + + SqliteCommand command = new SqliteCommand(); + command.Connection = dbConnection; + command.CommandText = "INSERT INTO icons (id, content) VALUES (@id, @content)"; + command.Parameters.Add("@id", SqliteType.Text).Value = id; + command.Parameters.Add("@content", SqliteType.Blob, 20).Value = ImageToBytes(image); + + try + { + command.ExecuteNonQuery(); + } + catch (Exception) + { + return false; + } + + return true; + } + + public void Close() + { + if (dbConnection == null) return; + + dbConnection.Close(); + dbConnection.Dispose(); + dbConnection = null; + } + + public bool DeleteIcon(string id) + { + if (dbConnection == null) return false; + if (string.IsNullOrWhiteSpace(id)) return false; + + SqliteCommand command = new SqliteCommand(); + command.Connection = dbConnection; + command.CommandText = "DELETE FROM icons WHERE id = @id"; + command.Parameters.Add("@id", SqliteType.Text).Value = id; + + try + { + command.ExecuteNonQuery(); + } + catch (Exception) + { + return false; + } + + return true; + } + + + public Image FindIcon(string id) + { + if (dbConnection == null) return null; + if (string.IsNullOrWhiteSpace(id)) return null; + + byte[] iconBytes = null; + + SqliteCommand command = new SqliteCommand(); + command.Connection = dbConnection; + command.CommandText = "SELECT content FROM icons WHERE @id"; + command.Parameters.Add("@id", SqliteType.Text).Value = id; + + try + { + using (var reader = command.ExecuteReader()) + { + while (reader.Read()) + { + if (reader["content"] != null && !Convert.IsDBNull(reader["content"])) + { + iconBytes = (byte[])reader["content"]; + } + } + } + + command.Dispose(); + } + catch (Exception) + { + return null; + } + + if (iconBytes == null) + { + return null; + } + + Image img = null; + + try + { + img = Image.FromStream(new MemoryStream(iconBytes)); + } + catch (Exception) + { + return null; + } + + return new Bitmap(img, 16, 16); + } + + public bool HasIcon(string id) + { + if (dbConnection == null) return false; + if (string.IsNullOrWhiteSpace(id)) return false; + + int rv = 0; + + SqliteCommand command = new SqliteCommand(); + command.Connection = dbConnection; + command.CommandText = "SELECT COUNT(1) AS count FROM icons WHERE @id"; + command.Parameters.Add("@id", SqliteType.Text).Value = id; + + try + { + using (var reader = command.ExecuteReader()) + { + while (reader.Read()) + { + if (reader["count"] != null && !Convert.IsDBNull(reader["count"])) + { + rv = (int)reader["count"]; + } + } + } + + command.Dispose(); + } + catch (Exception) + { + return false; + } + + return (rv > 0); + } + + public bool UpdateIcon(string id, Image image) + { + if (dbConnection == null) return false; + if (string.IsNullOrWhiteSpace(id)) return false; + + if (image == null) return DeleteIcon(id); + if (!HasIcon(id)) return AddIcon(id, image); + + SqliteCommand command = new SqliteCommand(); + command.Connection = dbConnection; + command.CommandText = "UPDATE icons SET content = @content WHERE id = @id"; + command.Parameters.Add("@id", SqliteType.Text).Value = id; + command.Parameters.Add("@content", SqliteType.Blob, 20).Value = ImageToBytes(image); + + try + { + command.ExecuteNonQuery(); + } + catch (Exception) + { + return false; + } + + return true; + } + + + protected bool Initialise() + { + if (dbConnection == null) return false; + + try + { + SqliteCommand command = new SqliteCommand("CREATE TABLE icons (id TEXT PRIMARY KEY, content BLOB)", dbConnection); + command.ExecuteNonQuery(); + command.Dispose(); + } + catch (Exception) + { + return false; + } + + return true; + } + + protected byte[] ImageToBytes(Image image) + { + MemoryStream stream = new MemoryStream(); + //image.Save(stream, ((image.RawFormat == null) ? ImageFormat.Bmp : new ImageFormat(image.RawFormat.Guid))); + image.Save(stream, ImageFormat.Bmp); + stream.Close(); + + return stream.ToArray(); + } + + + } +} \ No newline at end of file diff --git a/Classes/WebParser.cs b/Classes/WebParser.cs index 6fbe2dc..f5c185c 100644 --- a/Classes/WebParser.cs +++ b/Classes/WebParser.cs @@ -11,7 +11,7 @@ namespace BookmarkManager protected HttpWeb webClient = null; - public BookmarkItem RetrieveDetails(string url) + public BookmarkResult RetrieveDetails(string url) { string sourceCode = retrieveSourceCode(url); if (string.IsNullOrWhiteSpace(sourceCode)) @@ -19,24 +19,25 @@ namespace BookmarkManager return null; } - BookmarkItem rs = new BookmarkItem(); + BookmarkResult rs = new BookmarkResult(); + rs.Item = new BookmarkItem(); HtmlDocument document = new HtmlDocument(); document.LoadHtml(sourceCode); - rs.SiteName = parseSiteTitle(document); - rs.SiteAddress = url; - rs.SiteDescription = parseSiteDescription(document); - rs.FaviconAddress = parseSiteIcon(document); + rs.Item.SiteName = parseSiteTitle(document); + rs.Item.SiteAddress = url; + rs.Item.SiteDescription = parseSiteDescription(document); + rs.IconURL = parseSiteIcon(document); // resolve relative URL - if (!string.IsNullOrWhiteSpace(rs.FaviconAddress)) + if (!string.IsNullOrWhiteSpace(rs.IconURL)) { Uri iconAddressURI; - bool rv = Uri.TryCreate(new Uri(url), rs.FaviconAddress, out iconAddressURI); + bool rv = Uri.TryCreate(new Uri(url), rs.IconURL, out iconAddressURI); if (rv) { - rs.FaviconAddress = iconAddressURI.ToString(); + rs.IconURL = iconAddressURI.ToString(); } } diff --git a/MainForm.cs b/MainForm.cs index 0935e87..a53486c 100644 --- a/MainForm.cs +++ b/MainForm.cs @@ -1,11 +1,13 @@ using BookmarkManager; using bzit.bomg.Models; using FizzyLauncher.Models; +using Microsoft.Data.Sqlite; using Newtonsoft.Json; using RyzStudio.Windows.Forms; using System; using System.Collections.Generic; using System.ComponentModel; +using System.Drawing; using System.IO; using System.Linq; using System.Reflection; @@ -23,7 +25,7 @@ namespace FizzyLauncher New } - + protected IconDatabase iconDatabase = null; protected OptionsForm optionsForm = null; protected FindForm findForm = null; @@ -39,13 +41,16 @@ namespace FizzyLauncher { InitializeComponent(); + jsonfigFilename = Path.ChangeExtension(Application.ExecutablePath, "jsonfig"); + + if (iconDatabase == null) iconDatabase = new IconDatabase(Path.ChangeExtension(Application.ExecutablePath, "db")); + iconDatabase.AddIcon(Guid.NewGuid().ToString(), UIResource.arrow_down); + this.AutoScaleMode = AutoScaleMode.None; this.StartPosition = FormStartPosition.WindowsDefaultLocation; this.ClientSize = new System.Drawing.Size(300, 580); //this.Visible = false; - jsonfigFilename = Path.ChangeExtension(Application.ExecutablePath, "jsonfig"); - treeView1.RootContextMenu = rootContextMenu; treeView1.FolderContextMenu = folderContextMenu; treeView1.PageContextMenu = pageContextMenu; @@ -71,6 +76,7 @@ namespace FizzyLauncher //ThreadControl.SetVisible(this, false); await LoadAppSession(jsonfigFilename); + //await InitialiseIconDB(iconDBFilename); InvalidateAppSession(); @@ -107,6 +113,7 @@ namespace FizzyLauncher sessionPassword = null; treeView1.Clear(); + iconDatabase?.Close(); ApplicationMode = AppMode.Clear; @@ -680,10 +687,16 @@ namespace FizzyLauncher case Keys.F2: if (nodeType == BookmarkTreeView.NodeType.Page) { - BookmarkForm bookmarkForm = new BookmarkForm(treeView1.GetNodeModel()); + BookmarkForm bookmarkForm = new BookmarkForm(treeView1.GetNodeModel(), null); if (bookmarkForm.ShowDialog() == DialogResult.OK) { - treeView1.UpdateItem(treeView1.SelectedNode, bookmarkForm.Model, bookmarkForm.Favicon); + string iconID = Crypto.GetSHA256Hash(bookmarkForm.Model.Item?.SiteName); + if (!string.IsNullOrWhiteSpace(iconID)) + { + iconDatabase.AddIcon(iconID, bookmarkForm.Model.Icon); + } + + treeView1.UpdateItem(treeView1.SelectedNode, bookmarkForm.Model.Item); } } @@ -708,10 +721,16 @@ namespace FizzyLauncher node.EnsureVisible(); treeView1.SelectedNode = node; - BookmarkForm bookmarkForm = new BookmarkForm(treeView1.GetNodeModel()); + BookmarkForm bookmarkForm = new BookmarkForm(treeView1.GetNodeModel(), null); if (bookmarkForm.ShowDialog() == DialogResult.OK) { - treeView1.UpdateItem(treeView1.SelectedNode, bookmarkForm.Model, bookmarkForm.Favicon); + string iconID = Crypto.GetSHA256Hash(bookmarkForm.Model.Item?.SiteName); + if (!string.IsNullOrWhiteSpace(iconID)) + { + iconDatabase.AddIcon(iconID, bookmarkForm.Model.Icon); + } + + treeView1.UpdateItem(treeView1.SelectedNode, bookmarkForm.Model.Item); } else { diff --git a/Models/BookmarkItem.cs b/Models/BookmarkItem.cs index 8088bd0..5bc0a3a 100644 --- a/Models/BookmarkItem.cs +++ b/Models/BookmarkItem.cs @@ -11,23 +11,10 @@ namespace bzit.bomg.Models public string SiteDescription { get; set; } - public string FaviconAddress { get; set; } - public string TreeviewPath { get; set; } public string Notes { get; set; } - //public BookmarkItemModel ToModel() - //{ - // return new BookmarkItemModel() - // { - // SiteName = this.SiteName, - // SiteAddress = this.SiteAddress, - // SiteDescription = this.SiteDescription, - // FaviconAddress = this.FaviconAddress, - // TreeviewPath = this.TreeviewPath - // }; - //} public new string ToString() { diff --git a/Windows/Forms/BookmarkTreeView.cs b/Windows/Forms/BookmarkTreeView.cs index 0ad3d48..2412fdb 100644 --- a/Windows/Forms/BookmarkTreeView.cs +++ b/Windows/Forms/BookmarkTreeView.cs @@ -1,4 +1,5 @@ -using bzit.bomg; +using BookmarkManager; +using bzit.bomg; using bzit.bomg.Models; using System; using System.Collections.Generic; @@ -519,6 +520,9 @@ namespace RyzStudio.Windows.Forms } } + [Browsable(false), DesignerSerializationVisibility(DesignerSerializationVisibility.Hidden)] + public IconDatabase IconDatabase { get; set; } = null; + [Browsable(false), DesignerSerializationVisibility(DesignerSerializationVisibility.Hidden)] public ContextMenuStrip RootContextMenu { get; set; } = null;