comet
Welcome to LanDen Labs
Source Code, Tools, Developer Notes and Performance Metrics

landenlabs.com

[ To Main Page ]


C# - UrlCleaner v1.6

UrlCleaner


UrlCleaner is a (URL) Uniform Resource Locator shortcut Cleaner.


main-screen


A URL shortcut is a file created when you drag a webpage URL to your desktop. Since I frequently switch browsers, I have avoided the complexity of using each browser's 'bookmark' or 'favorites' management system. Instead, I just drag the webpage address bar from my browser to the desktop and it creates a URL shortcut file. Later, I can click on URL shortcut and it reopens the page using the current default browser. If I switch my default browser, the URL shortcuts all switch to using the new default browser. You can organize your URL shortcuts, just like regular files by creating folders and drag and dropping them to move them around.


Example showing how to create a URL shortcut with Chrome browser
browser-drag


Program Features include:

Main Features:

  • Scan disk for *.url files using directories provided by user.
  • Extact URL resource from .url file.
  • Verify if one or more URLs can load a webpage or designated resource.
  • Summaries resources available from web-pages loaded by url.
  • Easy sorting and selection of URL shortcuts, so they can be deleted or copied to another location via drag&drop.
  • Display icon used on the desktop and icon associated with webpage.
  • Allow web page icon to be saved to disk or used to reset the shortcut's desktop icon.

    Other Feaures:

    1. Icon class to save 32bit icons.
    2. Icon class to extract icons from resources.
    3. Ini class to read and write data from/to URL shortcut files, provided by: Prashant Khandelwal
    4. Delete files to Recyle Bin provided by: Microsoft
    5. Multi-column Listview sorting.
    6. Play sound '.wav' file on verification success and failure.
    7. Find dialog to allow searching of ListView display.
  • Top

    Using UrlCleaner

    UrlCleaner has a fairly simple User Interface. You work from left-to-right, top-down like reading a book. Follow the steps marked in the image below.
    1. Specify one or more directory Paths to search. Multiple directories are separated by a semicolon. Press Path button to browse directories or enter the directories directly into the path text box.
    2. Press Scan to locate your URL files. As files are found they are added to the main list.
      If you wish to restrict the search, press the gear gear button to define your valid URL patterns.
    3. Once you have a list of URL shortcut files, you can either press Verify All or select one or more rows and press Verify Selected. You can also double click on a row.

      Verification can be slow depending on your network speed and the website speed the URL points to. Verification is accomplished by:

      1. Validating the URL is well-formed
      2. Validating the URL host name (dns lookup)
      3. Loading the URL using C# builtin web-browser
      The Status column shows Done if successful loading one or more resources, else the reason for the failure. Success is marked by drawing the row's background color in light green and failure in light red. Good and Bad URL rows are counted and appear in the Information bar.

    4. Once the verification completes or you press Abort button, you can sort the list to group by path, URL or status. Any failed verifications will automatically set the checkbox. You can select and drag the rows to File Explorer to move the URLs to a new folder. Click on a row to update the Information bar to see large copies of the icons and to update the status line at the bottom with the URL and Status information.
    5. If you press the Delete button, all Checked rows will be deleted, causing their associated URL shortcut to be sent to the recycle bin. If you think you made a mistake, go to your Recycle Bin and restore the URL shortcut files.
    6. In addition to deleting broken URL shortcuts, you can save valid URL web-icons to disk as either an icon or png image. Click on the row in the list to select the URL shortcut you wish to update. If you press the Set To Icon button, the web image will be saved to your local disk and the URL shortcut updated to use that image as its presentation image. Similarly, you press Set To Web button to set the URL shortcut to directly reference the URL's web image as its presentation image. Set To Image is more reliable because the image is saved locally on your computer. Set To Web requires periodic access to the web by Windows File Explorer (explorer.exe) to fetch the image from the network resource. In both cases, Windows has a private cache of icons and it can be tricky to get it to rebuild the cache after you reset the icon using either of the two buttons. See notes below on Windows Iconcache.db.


    steps


    If you press the gear button, the Options dialog will appear. Here you define text patterns in the URL string. There is a column for URLs to Include and Excludee. I recommend you exclude URLs which point directly to a data file. If you don't exclude these links, everytime you Verify the URL, it will download another copy of the resource. steps


    Top


    Building (Compiling) UrlCleaner code in Visual Studio

    I provide both Visual Studio 2008 and 2010 Solutions and Project files. One problem I never solved is Visual Studio will frequently add UrlCleaner namespace to the OAKListView class constructor in the designer file. When this occurs, you get the compiler error:
        The type name 'OAKListView' does not exist in the type 'UrlCleaner.UrlCleaner'
        
    The solution is just to delete the namespace UrlCleaner from the OAKListView constructor.
        Before:
          this.urlListView = new UrlCleaner.OAKListView();
    
        After:
          this.urlListView = new OAKListView();
        


    Example of Visual Studio Compiler Error. Resolve by deleting "UrlCleaner." in front of OAKListView. Visual Studio will randomly replace this namespace so it is a constant battle to fix it. build-error

    Top


    Extra Features

    1. Saving a 32bit Icon

    After looking for ways to convert a Bitmap image to a 32bit Icon, I discovered that it was fairly simple. A 32bit Icon is just an Icon with the the ICONDIR and ICONENTRY structure followed by either a BMP or PNG image. The details are in the
    Icon Format Wiki . I got lucky and I found an C# icon class which already had the logic. I placed it in IconUtil.cs and modified its save method to take a bitmap as one of its arguments.
        static public void SaveBitmapAsIcon(Bitmap bitmap, Stream outputStream)
        {
            BinaryWriter writer = new BinaryWriter(outputStream);
            SaveBitmapAsIcon(bitmap, writer);
            writer.Flush();
        }
        

    2. Extract Icon Associated with Webpage

    Most web sites associate an icon with their pages. This is called the Favicon. Details of how to specify a FAVorite Icon can be found on the Favicon Wiki.

    In general the FAVicon is tied to a LINK and has the reference name "shortcut icon" or "icon". Given a webpage document, here is my method to extract the FAVorite Icon.

    /// <summary>
    /// Extract "Shortcut Icon" from web document.
    /// </summary>
    /// <param name="image">Return Icon image</param>
    /// <param name="uriPath">Return Icon URI</param>
    /// <returns></returns>
    private bool RetrieveUrlImage(out Image image, out string uriPath)
    {
        uriPath = string.Empty;
    
        try
        {
            HtmlDocument doc = webBrowser.Document;
            HtmlElementCollection collect = doc.GetElementsByTagName("link");
    
            foreach (HtmlElement element in collect)
            {
                string linkRelStr = element.GetAttribute("rel");
    
                if (string.Compare(linkRelStr, "SHORTCUT ICON", true) == 0 ||
                    string.Compare(linkRelStr, "ICON", true) == 0)
                {
                    string iconPath = element.GetAttribute("href");
                    uriPath = iconPath;
    
                    if (Uri.IsWellFormedUriString(uriPath, UriKind.Relative))
                        uriPath = "http://" + UriCombine(webBrowser.Url.Host, iconPath);
    
                    WebRequest requestImg = WebRequest.Create(uriPath);
                    image = Image.FromStream(requestImg.GetResponse().GetResponseStream());
                    return true;
                }
            }
        }
        catch (Exception ex)
        {
            statusTx.Text = ex.Message + " " + uriPath;
        }
    
        image = null;
        return false;
    }
    

    3. Read and Write INI files

    Thanks to the Web and Prashant Khandelwal, I found a class which reads and writes Window's INI files. As it turns out a .url file uses the .ini file structure. The details of the INI file format can be found at the INI_file wiki.

    Here samples of two of my .url files. The key parts is the URL tag defines the resource and the IconFile and IconIndex define the icon to drawn on the desktop.

    [{000214A0-0000-0000-C000-000000000046}] Prop3=19,0 [InternetShortcut] URL=steam://rungameid/34330 IDList= IconFile=C:\Program Files (x86)\Steam\steam\games\71a76cd2fbcd1457887fe57727aa10e5e1ce2ea4.ico IconIndex=0
    [InternetShortcut] URL=http://technet.microsoft.com/en-us/library/cc723564.aspx

    4. Delete files to Recyle Bin

    If you delete a url using UrlCleaner's Delete button, the files are sent to the Windows Recycle Bin. Giving the user a chance to undo the deletion. I found the RecycleFile class code on this Microsoft WebSite. The class calls the Shell32.dll SHFileOperation function.
    /// <summary>
    /// Delete file or directory by placing it in recycle bin so it can undone.
    /// 
    /// A deleted code example can be found:
    /// http://social.msdn.microsoft.com/Forums/hu-HU/netfxbcl/thread/ce1e8a4a-dd6b-4add-84d1-95faa3d13404
    /// </summary>
    class RecycleFile
    {
        [StructLayout(LayoutKind.Sequential, CharSet=CharSet.Auto, Pack=1)] 
            public struct SHFILEOPSTRUCT 
        { 
                public IntPtr hwnd; 
                [MarshalAs(UnmanagedType.U4)] public int wFunc; 
                public string pFrom; 
                public string pTo; 
                public short fFlags; 
                [MarshalAs(UnmanagedType.Bool)] public bool fAnyOperationsAborted; 
                public IntPtr hNameMappings; 
                public string lpszProgressTitle; 
    
        } 
    
        [DllImport("shell32.dll", CharSet=CharSet.Auto)] 
        static extern int SHFileOperation(ref SHFILEOPSTRUCT FileOp); 
        const int FO_DELETE = 3; 
        const int FOF_ALLOWUNDO = 0x40; 
        const int FOF_NOCONFIRMATION = 0x10;    //Don't prompt the user.; 
    
        /// <summary>
        ///     Delete file or directoy by moving it to the recycle bin.
        ///     return true if successful.
        /// </summary>
        static public void DeleteFile(string path)
        {
            SHFILEOPSTRUCT shf = new SHFILEOPSTRUCT(); 
            shf.wFunc = FO_DELETE; 
            shf.fFlags = FOF_ALLOWUNDO | FOF_NOCONFIRMATION;
            shf.pFrom = path + '\0' + '\0';
    
            int zeroOk = SHFileOperation(ref shf); 
    
            if (zeroOk != 0)
                throw new FileNotFoundException("Delete failed", path);
    
        }
    }
    

    5. Multi-column Listview sorting

    I love to use the ListView container, but it does not have a built-in column sorter. Sorting is handle by setting ListView's sorter to your own sort method. My ListView column sort method is in the ListViewColumnSorter.cs file.

    To use the sort you:

      1. Make sure ListView HeaderStyle is Clickable, which is the default.
      2. Create and assign an ImageList to the SmallImageList resource of the ListView.
      3. Add Arrow images to ImageList

    listview-prop small-image-list

      4. Assign instance of sorter to your ListView.
      urlListView.ListViewItemSorter = new ListViewColumnSorter(ListViewColumnSorter.SortDataType.eAuto);
    

      5. Add a Column Click method.

    This implementation of ColumnSort will allow two columns to define the sort order. As you click a column it becomes the primary sort column and any previous sort column becomes the secondary sort if the first column has a duplicate.

    /// <summary>
    /// Column header click fires sort.
    /// Supports two tier sorting.
    /// </summary>
    private void ColumnSort_Click(object sender, ColumnClickEventArgs e)
    {
        bool isControl = System.Windows.Forms.Control.ModifierKeys == Keys.Control;
    
        ListView listView = sender as ListView;
        ListViewColumnSorter sorter = listView.ListViewItemSorter as ListViewColumnSorter;
        if (sorter == null)
            return;
    
        // Determine if clicked column is already the column that is being sorted.
        if (e.Column == sorter.SortColumn1)
        {
            // Reverse the current sort direction for this column.
            if (sorter.Order1 == SortOrder.Ascending)
                sorter.Order1 = SortOrder.Descending;
            else
                sorter.Order1 = SortOrder.Ascending;
        }
        else
        {
            // Set the column number that is to be sorted; default to ascending.
            sorter.SortColumn2 = sorter.SortColumn1;
            sorter.Order2 = sorter.Order1;
            sorter.SortColumn1 = e.Column;
            sorter.Order1 = SortOrder.Ascending;
        }
    
        // Clear old arrows and set new arrow
        foreach (ColumnHeader colHdr in listView.Columns)
            colHdr.ImageIndex = 4;
    
        if (sorter.SortColumn1 != sorter.SortColumn2)
            listView.Columns[sorter.SortColumn2].ImageIndex = (sorter.Order2 == SortOrder.Ascending) ? 2 : 3;
    
        listView.Columns[sorter.SortColumn1].ImageIndex = (sorter.Order1 == SortOrder.Ascending) ? 0 : 1;
    
        sorter.SortCheckedState = (isControl && sorter.SortColumn1 == 0);
    
        sorter.SortType =
            (listView.Columns[sorter.SortColumn1].TextAlign == HorizontalAlignment.Right) ?
            ListViewColumnSorter.SortDataType.eNumeric :
            ListViewColumnSorter.SortDataType.eAlpha;
    
        // Perform the sort with these new sort options.
        if (listView != null)
            listView.Sort();
    }
    
    

    6. Play sound '.wav' file on verification success and failure

    I always like to add sound to my programs. I feel it gives useful feedback, but if it is too much the sound can be muted. C# makes it easy to play sounds, just add some WAV files to your sound resource and feed them to a SoundPlayer object. Resources bad and good are associated with WAV files in the Resource Properties. wav-resource

    /// <summary>
    /// Play a sound.  Ignore play if recently played.
    /// </summary>
    /// <param name="sounds"></param>
    private void PlaySound(Sounds sounds)
    {
        if (!this.m_mute)
        {
            this.m_sndPlayer.Stream = Properties.Resources.bad;
            switch (sounds)
            {
                case Sounds.eAbort:
                case Sounds.eFail:
                    this.m_sndPlayer.Stream = Properties.Resources.bad;
                    break;
                case Sounds.eGood:
                    this.m_sndPlayer.Stream = Properties.Resources.good;
                    break;
            }
    
            if ((DateTime.Now - m_lastPlay).Duration().TotalSeconds > 2)
            {
                this.m_sndPlayer.Play();
                m_lastPlay = DateTime.Now;
            }
        }
    }        
    


    7. Find dialog to allow searching of ListView display

    If you have a lot of URL Shortcuts, it is handy if you can search the list and find a specific object. I created a basic Find Dialog which should pop-up if you type Control-F thou it seems to be a little lazy to open sometimes.

    find-dialog

    Here is the logic which opens the Find Dialog:

    private void UrlCleaner_KeyUp(object sender, KeyEventArgs e)
    {
        switch (e.KeyCode)
        {
            case Keys.F:    // Launch Find dialog in response to control-f.
                if (e.Control)
                    findMenu_Click(sender, EventArgs.Empty);
                break;
            case Keys.F3:
                Find(urlListView, findInfo, new FindDialog.FindEventArgs() { action = FindDialog.FindEventArgs.Action.Next });
                break;
        }
    }
    

    Here are all of the Find methods which manage the search logic:

    private void findMenu_Click(object sender, EventArgs e)
    {
        findDialog.GetArgs.action = FindDialog.FindEventArgs.Action.First;
        findDialog.Show();
    }
    
    private void findAgainMenu_Click(object sender, EventArgs e)
    {
        findDialog.GetArgs.action = FindDialog.FindEventArgs.Action.First;
        if (findDialog.GetArgs.toFind.Length == 0)
            findDialog.Show();
        else
            findDialog_findEventHandler(null, findDialog.GetArgs);
    }
    
    void findDialog_findEventHandler(object sender, FindDialog.FindEventArgs e)
    {
        Find(this.urlListView, findInfo, e);
    }
    
    void Find(ListView listView, FindInfo findInfo, FindDialog.FindEventArgs e)
    {
        if (e.action == FindDialog.FindEventArgs.Action.First)
        {
            listView.SelectedIndices.Clear();
            findInfo.index = -1;
        }
        else if (e.action == FindDialog.FindEventArgs.Action.All)
        {
            listView.SelectedIndices.Clear();
            findInfo.index = -1;
            while ((findInfo.index = FindMatch(this.urlListView, e, findInfo.index)) != -1)
            {
                listView.EnsureVisible(findInfo.index);
                listView.SelectedIndices.Add(findInfo.index);
            }
    
            if (listView.SelectedIndices.Count != 0)
                PlaySound(Sounds.eGood);
            else
                PlaySound(Sounds.eFail);
    
            return;
        }
    
        // Search ListView.
        findInfo.index = FindMatch(this.urlListView, e, findInfo.index);
        if (findInfo.index == -1)
        {
            MessageBox.Show("No Match for:" + e.toFind);
            PlaySound(Sounds.eFail);
        }
        else
        {
            PlaySound(Sounds.eGood);
            listView.EnsureVisible(findInfo.index);
            listView.SelectedIndices.Add(findInfo.index);
        }
    }
    
    public static bool Contains(string source, string toCheck, bool isMatchCase, bool isRegularExpression)
    {
        if (isRegularExpression)
            return Regex.IsMatch(source, toCheck, isMatchCase ? RegexOptions.None : RegexOptions.IgnoreCase);
        else
            return source.IndexOf(toCheck, isMatchCase ? StringComparison.Ordinal : StringComparison.OrdinalIgnoreCase) >= 0;
    }
    
    
    /// Find a match, starting search just after previous match.
    /// </summary>
    /// <param name="listView"></param>
    /// <param name="e"></param>
    /// <param name="prevIndex"></param>
    /// <returns>Matching row index or -1 on failure</returns>
    int FindMatch(ListView listView, FindDialog.FindEventArgs e, int prevIndex)
    {
        int index = prevIndex;
        while (++index < listView.Items.Count)
        {
            ListViewItem lvItem = listView.Items[index];
            for (int colIdx = 0; colIdx != lvItem.SubItems.Count; colIdx++)
            {
                if (lvItem.SubItems[colIdx] != null && 
                    Contains(lvItem.SubItems[colIdx].Text, e.toFind, e.isMatchCase, e.isRegularExpression))
                    return index;
            }
        }
    
        return -1;
    }
    

    Top



    History

  • v1.4 First version published to web.
  • v1.6 11-Nov-2014 Fix icon loading and page count stats, add blank page count column, add url page load details.

    WEB Links to referenced information

  • My home webpage - https://landenlabs.com//
  • Oak ListView class which allows images in column cell
  • Read/write INI file
  • Delete to Recycle bin

    Top


    Blog about URL Cleaner