This repository has been archived on 2022-09-30. You can view files and clone it, but cannot push or open issues or pull requests.
bookmark-manager/BookmarkItem.cs

524 lines
14 KiB
C#

using HtmlAgilityPack;
using System;
using System.ComponentModel;
using System.Drawing;
using System.IO;
using System.Net;
using System.Windows.Forms;
using HtmlDocument = HtmlAgilityPack.HtmlDocument;
namespace bzit.bomg
{
public class BookmarkItem
{
public delegate void RetrieveCompleted(BookmarkItem sender, bool hasError, string message);
public RetrieveCompleted OnRetrieveCompleted { get; set; } = null;
public TreeNode TreeViewNode { get; protected set; }
public byte[] IconData { get; set; } = null;
public string SiteName { get; set; } = null;
public string SiteAddress { get; set; }
public string Description { get; set; }
public string IconAddress { get; set; }
public string Fullpath { get; set; }
public string Created { get; set; }
protected const char pathSeparator = '|';
//protected TreeNode treeNode = null;
protected BackgroundWorker mainThread = null;
protected bool hasRetrieveError = false;
protected string retrieveErrorMessage = null;
public BookmarkItem()
{
this.Clear();
}
public new string ToString()
{
string rv = "";
rv += "Name=" + this.Fullpath + Environment.NewLine;
rv += "Address=" + this.SiteAddress + Environment.NewLine;
rv += "Description=" + this.Description + Environment.NewLine;
rv += "Created=" + this.Created?.Trim();
return rv;
}
#region public properties
//{
// get
// {
// return this.treeNode;
// }
// set
// {
// this.treeNode = value;
// }
//}
public Bitmap Icon
{
get
{
if (this.IconData == null)
{
return null;
}
try
{
Image img = Image.FromStream(new MemoryStream(this.IconData));
return new Bitmap(img, 16, 16);
}
catch
{
return null;
}
}
}
#endregion
#region public methods
public void Clear()
{
this.Fullpath = string.Empty;
this.SiteAddress = string.Empty;
this.Description = string.Empty;
this.Created = string.Empty;
if (this.mainThread == null)
{
this.mainThread = new BackgroundWorker();
this.mainThread.WorkerReportsProgress = this.mainThread.WorkerSupportsCancellation = true;
this.mainThread.DoWork += retrieveWorker_DoWork;
this.mainThread.RunWorkerCompleted += retrieveWorker_RunWorkerCompleted;
}
}
public string GetName()
{
if (this.Fullpath.Contains(pathSeparator.ToString()))
{
return decodePath(this.Fullpath.Substring(this.Fullpath.LastIndexOf(pathSeparator) + 1)).Trim();
}
return decodePath(this.Fullpath).Trim();
}
public void ChangeName(string newName)
{
string prefix = (this.Fullpath.Contains(pathSeparator.ToString()) ? this.Fullpath.Substring(0, this.Fullpath.IndexOf(pathSeparator)).Trim() : string.Empty);
this.Fullpath = string.Concat(prefix, pathSeparator, encodePath(newName.Trim()));
}
public void GetFaviconAddress()
{
hasRetrieveError = false;
retrieveErrorMessage = string.Empty;
this.IconData = null;
WebClient webClient = new WebClient();
webClient.CachePolicy = new System.Net.Cache.RequestCachePolicy(System.Net.Cache.RequestCacheLevel.NoCacheNoStore);
string sourceCode = string.Empty;
try
{
sourceCode = webClient.DownloadString(this.SiteAddress);
}
catch (Exception exc)
{
hasRetrieveError = true;
retrieveErrorMessage = exc.Message;
return;
}
HtmlAgilityPack.HtmlDocument document = new HtmlAgilityPack.HtmlDocument();
document.LoadHtml(sourceCode);
// favicon
this.IconAddress = parseSiteIcon(document);
if (!string.IsNullOrWhiteSpace(this.IconAddress))
{
Uri iconAddressURI;
bool rv = Uri.TryCreate(new Uri(this.SiteAddress), this.IconAddress, out iconAddressURI);
if (rv)
{
this.IconAddress = iconAddressURI.ToString();
}
}
// load favicon image
if (!string.IsNullOrEmpty(this.IconAddress))
{
try
{
this.IconData = webClient.DownloadData(this.IconAddress);
if (!RyzStudio.IO.FileType.IsImage(this.IconData))
{
this.IconData = null;
}
}
catch
{
this.IconData = null;
this.IconAddress = null;
}
}
//// parse icon
//HtmlAgilityPack.HtmlNodeCollection hnc = document.DocumentNode.SelectNodes("//link");
// if (hnc != null)
// {
// foreach (HtmlAgilityPack.HtmlNode node in hnc)
// {
// if (node.Attributes["rel"] == null)
// {
// continue;
// }
// string nodeAttr = node.Attributes["rel"].Value.Trim();
// if (nodeAttr.ToLower().Contains("icon"))
// {
// if (!string.IsNullOrEmpty(this.IconAddress))
// {
// continue;
// }
// if (node.Attributes["href"] != null)
// {
// Uri iconPath;
// bool rv = Uri.TryCreate(new Uri(this.SiteAddress), WebUtility.HtmlDecode(node.Attributes["href"].Value).Trim(), out iconPath);
// if (rv)
// {
// this.IconAddress = iconPath.ToString();
// }
// break;
// }
// }
// }
// }
// // default favicon
// if (string.IsNullOrEmpty(this.IconAddress))
// {
// Uri iconPath;
// if (Uri.TryCreate(new Uri(this.SiteAddress), "/favicon.ico", out iconPath))
// {
// this.IconAddress = iconPath.ToString();
// }
// }
// // load icon image
// if (!string.IsNullOrEmpty(this.IconAddress))
// {
// try
// {
// this.IconData = webClient.DownloadData(this.IconAddress);
// if (!RyzStudio.IO.FileType.IsImage(this.IconData))
// {
// this.IconData = null;
// }
// }
// catch
// {
// // do nothing
// }
// }
}
public void RetrieveAsync()
{
if (this.mainThread.IsBusy)
{
return;
}
this.mainThread.RunWorkerAsync();
}
public void RetrieveAsync(string address)
{
if (this.mainThread.IsBusy)
{
return;
}
this.SiteAddress = address;
this.mainThread.RunWorkerAsync();
}
#endregion
protected void retrieveWorker_DoWork(object sender, DoWorkEventArgs e)
{
hasRetrieveError = false;
retrieveErrorMessage = string.Empty;
this.IconData = null;
this.Fullpath = string.Empty;
this.Description = string.Empty;
this.IconAddress = string.Empty;
WebClient webClient = new WebClient();
webClient.CachePolicy = new System.Net.Cache.RequestCachePolicy(System.Net.Cache.RequestCacheLevel.NoCacheNoStore);
string sourceCode = string.Empty;
try
{
sourceCode = webClient.DownloadString(this.SiteAddress);
}
catch (Exception exc)
{
hasRetrieveError = true;
retrieveErrorMessage = exc.Message;
return;
}
HtmlDocument document = new HtmlDocument();
document.LoadHtml(sourceCode);
// title
this.SiteName = parseSiteTitle(document);
// description
this.Description = parseSiteDescription(document);
// favicon
this.IconAddress = parseSiteIcon(document);
if (!string.IsNullOrWhiteSpace(this.IconAddress))
{
Uri iconAddressURI;
bool rv = Uri.TryCreate(new Uri(this.SiteAddress), this.IconAddress, out iconAddressURI);
if (rv)
{
this.IconAddress = iconAddressURI.ToString();
}
}
// load favicon image
if (!string.IsNullOrWhiteSpace(this.IconAddress))
{
try
{
this.IconData = webClient.DownloadData(this.IconAddress);
if (!RyzStudio.IO.FileType.IsImage(this.IconData))
{
this.IconData = null;
}
}
catch
{
this.IconData = null;
this.IconAddress = null;
}
}
}
protected void retrieveWorker_RunWorkerCompleted(object sender, RunWorkerCompletedEventArgs e)
{
this.OnRetrieveCompleted?.Invoke(this, hasRetrieveError, retrieveErrorMessage);
}
protected string encodePath(string text) => System.Web.HttpUtility.UrlEncode(text);
protected string decodePath(string text) => System.Web.HttpUtility.UrlDecode(text);
protected string parseTagValue(HtmlDocument doc, string xpath, string defaultValue = "")
{
HtmlNodeCollection hnc = doc.DocumentNode.SelectNodes(xpath);
if (hnc == null)
{
return defaultValue;
}
if (hnc.Count <= 0)
{
return defaultValue;
}
foreach (HtmlNode hn in hnc)
{
if (string.IsNullOrWhiteSpace(hn.InnerHtml))
{
continue;
}
string rs = WebUtility.HtmlDecode(hn.InnerHtml)?.Replace("\r", "")?.Replace("\n", " ")?.Trim();
if (string.IsNullOrWhiteSpace(rs))
{
continue;
}
return rs;
}
return defaultValue;
}
protected string parseTagValue_Attr(HtmlDocument doc, string xpath, string attr, string defaultValue = "")
{
HtmlNodeCollection hnc = doc.DocumentNode.SelectNodes(xpath);
if (hnc == null)
{
return defaultValue;
}
if (hnc.Count <= 0)
{
return defaultValue;
}
foreach (HtmlNode hn in hnc)
{
if (hn.Attributes[attr] == null)
{
continue;
}
if (string.IsNullOrWhiteSpace(hn.Attributes[attr].Value))
{
continue;
}
return System.Web.HttpUtility.HtmlDecode(hn.Attributes[attr].Value?.Trim());
}
return defaultValue;
}
protected string parseSiteTitle(HtmlDocument doc)
{
string rs = null;
if (string.IsNullOrWhiteSpace(rs))
{
rs = parseTagValue(doc, "//title", string.Empty);
}
if (string.IsNullOrWhiteSpace(rs))
{
rs = parseTagValue_Attr(doc, "//meta[@property='og:title']", "content", string.Empty);
}
if (string.IsNullOrWhiteSpace(rs))
{
rs = parseTagValue_Attr(doc, "//meta[@name='twitter:title']", "content", string.Empty);
}
if (string.IsNullOrWhiteSpace(rs))
{
rs = parseTagValue_Attr(doc, "//meta[@property='og:site_name']", "content", string.Empty);
}
if (string.IsNullOrWhiteSpace(rs))
{
rs = parseTagValue_Attr(doc, "//meta[@itemprop='name']", "content", string.Empty);
}
return rs;
}
protected string parseSiteDescription(HtmlDocument doc)
{
string rs = null;
if (string.IsNullOrWhiteSpace(rs))
{
rs = parseTagValue_Attr(doc, "//meta[@name='description']", "content", string.Empty);
}
if (string.IsNullOrWhiteSpace(rs))
{
rs = parseTagValue_Attr(doc, "//meta[@property='og:description']", "content", string.Empty);
}
if (string.IsNullOrWhiteSpace(rs))
{
rs = parseTagValue_Attr(doc, "//meta[@name='twitter:description']", "content", string.Empty);
}
if (string.IsNullOrWhiteSpace(rs))
{
rs = parseTagValue_Attr(doc, "//meta[@property='og:description']", "content", string.Empty);
}
if (string.IsNullOrWhiteSpace(rs))
{
rs = parseTagValue_Attr(doc, "//meta[@itemprop='description']", "content", string.Empty);
}
return rs;
}
protected string parseSiteIcon(HtmlDocument doc)
{
string rs = null;
if (string.IsNullOrWhiteSpace(rs))
{
rs = parseTagValue_Attr(doc, "//link[@rel='shortcut icon']", "href", string.Empty);
}
if (string.IsNullOrWhiteSpace(rs))
{
rs = parseTagValue_Attr(doc, "//link[@rel='icon']", "href", string.Empty);
}
if (string.IsNullOrWhiteSpace(rs))
{
rs = parseTagValue_Attr(doc, "//link[@rel='apple-touch-icon']", "href", string.Empty);
}
if (string.IsNullOrWhiteSpace(rs))
{
rs = parseTagValue_Attr(doc, "//link[@rel='apple-touch-icon-precomposed']", "href", string.Empty);
}
if (string.IsNullOrWhiteSpace(rs))
{
rs = parseTagValue_Attr(doc, "//meta[@property='og:image']", "content", string.Empty);
}
if (string.IsNullOrWhiteSpace(rs))
{
rs = parseTagValue_Attr(doc, "//meta[@name='twitter:image']", "content", string.Empty);
}
if (string.IsNullOrWhiteSpace(rs))
{
rs = parseTagValue_Attr(doc, "//meta[@property='og:image']", "content", string.Empty);
}
if (string.IsNullOrWhiteSpace(rs))
{
rs = parseTagValue_Attr(doc, "//meta[@itemprop='image']", "content", string.Empty);
}
return rs;
}
}
}