I like to save interesting websites as file system shortcuts in OneDrive directiory. It works nice using New->Shortcut in file explorer, but there is room for improvement.
In first part we will look how to fetch website info and create url shortcut and in second will convert prepared code to file explorer extension.
Development is easier using standard windows application. On main form is just one button to call another form what will do all the magic.
private void buttonUrl_Click(object sender, EventArgs e) { UrlForm uf = new UrlForm(); uf.Show(); }
The second form is already more complex
On form load clipboard is checked and if there is valid internet url then Url TextBox is filled
private void UrlForm_Load(object sender, EventArgs e) { pictureIcon.Tag = null; string url = Clipboard.GetText(); if (string.IsNullOrEmpty(url)) return; bool isUri = Uri.IsWellFormedUriString(url, UriKind.RelativeOrAbsolute); if (isUri) isUri = CheckURLValid(url); if (isUri) { textUrl.Text = url; } }
private bool CheckURLValid(string url) { Uri uriResult; return Uri.TryCreate(url, UriKind.Absolute, out uriResult) && uriResult.Scheme == Uri.UriSchemeHttp; }
Uri.IsWellFormedUriString alone is not enough as it is true also for file system paths. Often there is some other text on clipboard so we test it first before Uri.TryCreate but this is not mandatory and can be omitted.
For fetching web page we have inherited WebClient class with timeout option:
namespace UrlExtension { public class WebClientWithTimeout : WebClient { private int timeout; public WebClientWithTimeout(int Timeout = 10000) { timeout = Timeout; } protected override WebRequest GetWebRequest(Uri address) { WebRequest wr = base.GetWebRequest(address); wr.Timeout = timeout; // timeout in milliseconds (ms) return wr; } } }
Test button is used only for developing and will be removed later. It will do all web page related tasks – downloading web page, finding page title and favorite icon.
private void buttonTest_Click(object sender, EventArgs e) { if (string.IsNullOrEmpty(textUrl.Text)) return; // Create an instance of WebClient WebClient client = new WebClientWithTimeout(); try { string html = client.DownloadString(new Uri(textUrl.Text)); Regex x = new Regex("<title>(.*)</title>", RegexOptions.IgnoreCase); MatchCollection m = x.Matches(html); string title; if (m.Count > 0) { title = m[0].Value.Replace("<title>", "").Replace("</title>", ""); } else title = ""; textTitle.Text = title; GetFavouriteIcon(new Uri(textUrl.Text)); } catch (Exception ex) { MessageBox.Show("Could not complete. Error:" + ex.Message); } }
Page title will be used for shortcut name and is fetched from webpage using Regex. To get favorite icon needs a bit more effort
Image img; byte[] iconArray; private bool GetFavouriteIconFile(WebRequest requestImg, string hostName) { img = null; iconArray = null; pictureIcon.Tag = null; using (WebResponse response = requestImg.GetResponse()) { if ((response.ContentLength > 0) && (response.ContentType.Contains("icon"))) { using (Stream responseStream = response.GetResponseStream()) { using (MemoryStream ms = new MemoryStream()) { responseStream.CopyTo(ms); iconArray = ms.ToArray(); ms.Seek(0, SeekOrigin.Begin); img = Image.FromStream(ms); Bitmap bmp = new Bitmap(img); pictureIcon.Image = bmp; pictureIcon.Tag = $"{hostName}.ico"; return true; } } } } return false; } private bool GetFavouriteIcon(Uri url) { bool IconFound = false; WebRequest requestImg = WebRequest.Create($"http://{url.Host}/favicon.ico"); IconFound = GetFavouriteIconFile(requestImg, url.Host); if (!IconFound) { requestImg = WebRequest.Create($"http://www.google.com/s2/favicons?domain={url.Host}"); IconFound = GetFavouriteIconFile(requestImg, url.Host); } return IconFound; }
GetFavouriteIcon tries to download favicon.ico from website and if not found then from http://www.google.com/s2/favicons?domain={url.Host}
If found then icon content is saved in byte[] iconArray for later use and also onscreen PictureBox is filled with this content. So save actual icon file not converted image format the iconArray will be written to file. Using Image.Save() will convert Icon and it will not be usable as shortcut icon. pictureIcon.Tag is used to save icon name, but sure it can be also recalculated later ob schortcut creation.
Result will be something similar:
Add button will ask for File Directory and create inside it shortcut and icon file
using (FolderBrowserDialog fbd = new FolderBrowserDialog()) { DialogResult result = fbd.ShowDialog(); if (!string.IsNullOrWhiteSpace(fbd.SelectedPath)) { if (pictureIcon.Tag != null) CreateUrlShortcut(textTitle.Text, textUrl.Text, fbd.SelectedPath, pictureIcon.Tag.ToString()); else CreateUrlShortcut(textTitle.Text, textUrl.Text, fbd.SelectedPath); } }
On files creation the valid file name is created as by default page title is used and this my contain invalid charactes.
private void CreateUrlShortcut(string linkName, string linkUrl, string linkPath, string icoPath = "") { string safeName = RemoveInvalidFilePathCharacters(linkName, " "); using (StreamWriter writer = new StreamWriter($"{linkPath}\\{safeName}.url")) { writer.WriteLine("[InternetShortcut]"); writer.WriteLine($"URL={linkUrl}"); if (!string.IsNullOrEmpty(icoPath) && (img != null)) { safeName = RemoveInvalidFilePathCharacters(icoPath, " "); //img.Save($"{linkPath}\\{safeName}", ImageFormat.Icon); File.WriteAllBytes($"{linkPath}\\{safeName}", iconArray); writer.WriteLine("IconIndex = 0"); writer.WriteLine($"IconFile ={linkPath}\\{safeName}"); } writer.Flush(); } } public string RemoveInvalidFilePathCharacters(string filename, string replaceChar) { string regexSearch = new string(Path.GetInvalidFileNameChars()) + new string(Path.GetInvalidPathChars()); Regex r = new Regex(string.Format("[{0}]", Regex.Escape(regexSearch))); return r.Replace(filename, replaceChar); }
So we click on Add Shortcut button, select folder and will get shortcut
Shortcut file content is actually very simple
[InternetShortcut]
URL=http://stackoverflow.com/
IconIndex = 0
IconFile =C:\temp\Links\stackoverflow.com.ico
Created shortcut will look like this : (shortcut file Stack Overflow.url and icon file stackoverflow.com.ico)
Next time we will continue with transforming window application to file explorer extension.
Useful links
http://stackoverflow.com/questions/1053052/a-generic-error-occurred-in-gdi-jpeg-image-to-memorystream
http://www.codeproject.com/Articles/19132/Retrieve-a-Web-Pages-Shortcut-Icon
http://stackoverflow.com/questions/7578857/how-to-check-whether-a-string-is-a-valid-http-url
http://stackoverflow.com/questions/4897655/create-shortcut-on-desktop-c-sharp
http://stackoverflow.com/questions/3825433/c-sharp-remove-invalid-characters-from-filename
Continue to Part 2