Click here to Skip to main content
15,884,083 members
Articles / Programming Languages / C#

The Code Project Browser Add-in for Visual Studio 2005 and 2008

Rate me:
Please Sign up or sign in to vote.
4.90/5 (108 votes)
27 Mar 2008CPOL9 min read 316.2K   4.9K   296  
An add-in for browsing, downloading and managing CodeProject samples directly in Visual Studio
//  Copyright (c) 2007, SlickEdit, Inc
//  Email:  info@slickedit.com
//  All rights reserved.
//
//  Redistribution and use in source and binary forms, with or without modification, 
//  are permitted provided that the following conditions are met:
//
//  Redistributions of source code must retain the above copyright notice, 
//  this list of conditions and the following disclaimer. 
//  Redistributions in binary form must reproduce the above copyright notice, 
//  this list of conditions and the following disclaimer in the documentation 
//  and/or other materials provided with the distribution. 
//
//  THIS CODE AND INFORMATION IS PROVIDED "AS IS" WITHOUT WARRANTY OF ANY
//  KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE
//  IMPLIED WARRANTIES OF MERCHANTABILITY AND/OR FITNESS FOR A PARTICULAR
//  PURPOSE. IT CAN BE DISTRIBUTED FREE OF CHARGE AS LONG AS THIS HEADER 
//  REMAINS UNCHANGED.

using System;
using System.Collections.Generic;
using System.Text;
using System.Net;
using System.IO;
using System.Diagnostics;
using System.Windows.Forms;
using ICSharpCode.SharpZipLib.Zip;
using EnvDTE80;
using System.Text.RegularExpressions;
using Microsoft.Win32;
using EnvDTE;
using System.ComponentModel;

namespace CPBrowser
{
    /// <summary>
    /// This class is responsible for loggin into Code Project and maintaining the 
    /// session there.  Using this session, we are able to download files behind the
    /// web browser's back.  The session is required because Code Project does not 
    /// allow downloads if the user is not logged in.  If the user isn't logged in 
    /// and we try to download something using WebRequest/WebResponse, our request 
    /// is redirected to the login page.
    /// </summary>
    public class ProjectLoader
    {
        // the main Code Project page
        public const string PAGE_TITLE_PREFIX = "CodeProject: ";
        public const string PAGE_TITLE_SUFFIX = "Free source code and programming help";
        public const string CODE_PROJECT_HOME = "http://www.codeproject.com";
        public const string CODE_PROJECT_HOST = "www.codeproject.com";
        public const string SLICKEDIT_HOME = "http://www.slickedit.com/index.php?WT.mc_id=CPFeature";

        // the Visual Studio extensibility object
        private DTE m_dte = null;
        // the root folder for downloads.  This is "My Documents\MyCode Project Downloads"
        private string m_downloadsRoot = "";
        // a regex that tells whether or not a file is a solution file
        private Regex m_solutionRegex = null;
        // a regex that tells whether or not a file is a project file
        private Regex m_projectRegex = null;

        /// <summary>
        /// Constructor.  Initializes the download root directory and builds the 
        /// solution and project regex matches.
        /// </summary>
        /// <param name="dte"></param>
        public ProjectLoader(DTE dte)
        {
            m_dte = dte;
            // create the project and solution regexes
            m_solutionRegex = new Regex(@"\.(?:sln|dsw|vsw)$", RegexOptions.IgnoreCase);
            m_projectRegex = new Regex(@"\.(?:[a-z]{1,4}proj|v[a-z]p)$", RegexOptions.IgnoreCase);
            // initialize the directory where projects get downloaded to
            InitDownloadsRoot();
        }

        /// <summary>
        /// Determines whether or not "My Documents\MyCode Project Downloads" exists.  
        /// If it does not, then it is created.
        /// </summary>
        private void InitDownloadsRoot()
        {
            try
            {
                // get the root project folder
                string projectsRoot = Environment.GetFolderPath(Environment.SpecialFolder.MyDocuments);
                projectsRoot = Path.Combine(projectsRoot, "My Code Project Downloads");
                // create the directory if it doesn't exist
                if (Directory.Exists(projectsRoot) == false)
                    Directory.CreateDirectory(projectsRoot);
                // if we got here, then we were successful, so store the projects root
                // name in the member variable
                m_downloadsRoot = projectsRoot;
            }
            catch (Exception ex)
            {
                string errMsg = "Unable to create the download directory: " + ex.Message;
                MessageBox.Show(null, errMsg, "Code Project Browser", MessageBoxButtons.OK, MessageBoxIcon.Error);
            }
        }

        /// <summary>
        /// Gets the root directory where all projects are unzipped to.
        /// </summary>
        public string DownloadsRoot
        {
            get { return m_downloadsRoot; }
        }

        /// <summary>
        /// Gets whether or not a file is a solution file or not.
        /// </summary>
        /// <param name="filePath">The file name to check.</param>
        /// <returns>True if the file is a solution file, false if not.</returns>
        public bool IsSolutionFile(string filePath)
        {
            return m_solutionRegex.IsMatch(filePath);
        }

        /// <summary>
        /// Gets whether or not a file is a project file or not.
        /// </summary>
        /// <param name="filePath">The file name to check.</param>
        /// <returns>True if the file is a project file, false if not.</returns>
        public bool IsProjectFile(string filePath)
        {
            return m_projectRegex.IsMatch(filePath);
        }

        /// <summary>
        /// Gets whether or not a file is an executable file or not.
        /// </summary>
        /// <param name="filePath">The file name to check.</param>
        /// <returns>True if the file is an executable file, false if not.</returns>
        public bool IsExecutableFile(string filePath)
        {
            // don't be tricked by the vshost.exe files
            if (filePath.ToLower().EndsWith(".vshost.exe") == true)
                return false;

            // check if the file ends in ".exe", case insensitive
            string ext = Path.GetExtension(filePath);
            return (String.Compare(ext, ".exe", true) == 0);
        }

        /// <summary>
        /// Looks for the "loginkey" cookie and returns true if the cookie exists, false
        /// if not.  This is the indicator for whether or not the user is logged in to 
        /// the web site.
        /// </summary>
        /// <param name="cookies">The current cookie collection.</param>
        private bool IsUserLoggedIn(CookieCollection cookies)
        {
            foreach (Cookie cookie in cookies)
            {
                if (String.Compare(cookie.Name, "loginkey", true) == 0)
                    return true;
            }
            return false;
        }

        /// <summary>
        /// Downloads the contents at the URL and save them to a temporary file.
        /// </summary>
        /// <param name="url">The URL that specifies what to download.</param>
        /// <returns>The file name it was saved to.</returns>
        public string DownloadZipFile(DownloadInfo downloadInfo, BackgroundWorker workerThread)
        {
            CookieContainer cookieContainer = null;
            HttpWebRequest webRequest = null;
            HttpWebResponse webResponse = null;
            Stream responseStream = null;
            FileStream fileStream = null;
            string fileName = "";
            
            // we can't get the size of the download because it's not included in the response header
            // so we will estimate that it's 500k
            long estimatedSize = 500000;

            try
            {
                // create the cookie container
                cookieContainer = new CookieContainer();
                cookieContainer.Add(new Uri("http://www.codeproject.com/"), downloadInfo.Cookies);
                // Create the web request
                webRequest = (HttpWebRequest)HttpWebRequest.Create(downloadInfo.ProjectUrl);
                // Set default authentication for retrieving the file
                webRequest.Credentials = CredentialCache.DefaultCredentials;
                // identifies itself
                webRequest.UserAgent = "CPBrowser";
                // apply the session cookies
                webRequest.CookieContainer = cookieContainer;
                // Get the response
                webResponse = (HttpWebResponse)webRequest.GetResponse();
                // Get the response stream
                responseStream = webResponse.GetResponseStream();
                // get the name from the url
                fileName = ExtractNameFromUrl(downloadInfo.ProjectUrl.ToString()) + ".zip";
                fileName = Path.Combine(Path.GetTempPath(), fileName);
                // write it to a temporary file in 1000 byte chunks
                fileStream = new FileStream(fileName, FileMode.Create);

                int bytesRead = 0;
                int totalBytesRead = 0;
                int progress = 15;
                byte[] data = new byte[1000];
                // keep looping until we read nothing
                do
                {
                    // read the next 1000 bytes of data
                    bytesRead = responseStream.Read(data, 0, 1000);
                    // write the data to the file
                    fileStream.Write(data, 0, bytesRead);

                    // report the progress
                    totalBytesRead += bytesRead;
                    // because this is an estimation, if the downloaded amount goes over the
                    // estimated amount, just loop back to 0
                    if (totalBytesRead >= estimatedSize)
                        totalBytesRead = 0;
                    progress = (int)(((float)totalBytesRead / (float)estimatedSize) * 99.0f);
                    workerThread.ReportProgress(progress);
                }
                while (bytesRead > 0);

                // report that we're done
                workerThread.ReportProgress(100);
            }
            catch (Exception ex)
            {
                string errMsg = "Error downloading file: ";

                // check if the user is logged in
                bool isLoggedIn = IsUserLoggedIn(downloadInfo.Cookies);
                if (isLoggedIn == false)
                    errMsg += "Make sure you are logged in to the web site.";
                else
                    errMsg += ex.Message;
                throw new Exception(errMsg);
            }
            finally
            {
                // free any resources
                if (fileStream != null)
                {
                    fileStream.Close();
                    fileStream.Dispose();
                }
                if (responseStream != null)
                {
                    responseStream.Close();
                    responseStream.Dispose();
                }
                if (webResponse != null)
                    webResponse.Close();
            }
            // return the file name
            return fileName;
        }

        /// <summary>
        /// Uses the SharpZipLib to unzip the project to it's respective folder in the 
        /// download root directory.  The SharpZipLib can be found at:
        /// http://www.icsharpcode.net/OpenSource/SharpZipLib/
        /// </summary>
        /// <param name="zipFileName">The name of the zip file to extract.</param>
        /// <returns>Returns the name of the directory where the file was extracted to.
        /// </returns>
        public string UnzipDownloadedFile(string zipFileName)
        {
            // make sure that we have a projects download root
            if (Directory.Exists(m_downloadsRoot) == false)
                throw new Exception("The project download directory does not exist.");

            string fileNameOnly = Path.GetFileName(zipFileName);
            string name = Path.GetFileNameWithoutExtension(fileNameOnly);
            // first, create a temp directory in the project downloads root
            string unzipPath = Path.Combine(m_downloadsRoot, name);
            // if it doesn't exist, then create it
            if (Directory.Exists(unzipPath) == false)
                Directory.CreateDirectory(unzipPath);
            // unzip the contents of the file to that directory
            FastZip zip = new FastZip();
            zip.ExtractZip(zipFileName, unzipPath, "");
            // return the project directory name
            return unzipPath;
        }

        /// <summary>
        /// Uses the standard Code Project URL format to determine an appropriate name 
        /// for the download.  Code Project puts each project in its own unique 
        /// directory, so we take the last directory name from the URL and use that as 
        /// the name.
        /// </summary>
        /// <param name="url">The URL where the project zip file was downloaded from.
        /// </param>
        /// <returns>The name for the download.</returns>
        public string ExtractNameFromUrl(string url)
        {
            int pos2 = url.LastIndexOf('/');
            int pos1 = url.LastIndexOf('/', pos2 - 1) + 1;
            int length = pos2 - pos1;
            return url.Substring(pos1, length);
        }

        /// <summary>
        /// Takes a root directory and finds all project and solution files contained in
        /// that directory or sub-directories.
        /// </summary>
        /// <param name="path">The root directory to search.</param>
        /// <param name="solutionFiles">A list of solution files found in the path.
        /// The caller should create a new list object and pass it as a parameter.  
        /// This function will populate the list.
        /// </param>
        /// <param name="projectFiles">A list of project files found in the path.  
        /// The caller should create a new list object and pass it as a parameter.  
        /// This function will populate the list.</param>
        /// <param name="exeFiles">A list of executable files found in the path.  
        /// The caller should create a new list object and pass it as a parameter.  
        /// This function will populate the list.</param>
        public void GetSignificantFiles(string path, ref List<string> solutionFiles, ref List<string> projectFiles, ref List<string> exeFiles)
        {
            // make sure that both lists have been initialized
            if (solutionFiles == null)
                solutionFiles = new List<string>();
            if (projectFiles == null)
                projectFiles = new List<string>();
            if (exeFiles == null)
                exeFiles = new List<string>();

            // now find any solutions in that directory (or subdirectories)
            string[] fileNames = Directory.GetFiles(path, "*.*", SearchOption.AllDirectories);
            foreach (string fileName in fileNames)
            {
                // make sure that this isn't in a \Backup folder created by the conversion wizard, or a .vshost.exe file
                if (fileName.ToLower().IndexOf(@"\backup\") == -1)
                {
                    // determine if this is a project file
                    if (IsProjectFile(fileName) == true)
                        projectFiles.Add(fileName);
                    else if (IsSolutionFile(fileName) == true)
                        solutionFiles.Add(fileName);
                    else if (IsExecutableFile(fileName) == true)
                        exeFiles.Add(fileName);
                }
            }
        }

        /// <summary>
        /// Loads the project given the current state of the solution and the user's 
        /// preferences.  I originally used DTE.Solution.Open and AddFromFile, but these
        /// functions threw exceptions if they attempted to load projects or solutions
        /// from previous versions of Visual Studio.  Instead, using ExecuteCommand causes
        /// the conversion wizard to be run.  Thanks to the Gadget's CommandSpy for 
        /// telling me that.
        /// </summary>
        /// <param name="projectDir">The path where the project has been unzipped to.</param>
        /// <param name="dte">The DTE object for Visual Studio.</param>
        public bool LoadProjectOrSolutionFromDirectory(string projectDir)
        {
            bool retVal = true;

            try
            {
                // now find any solutions in that directory (or subdirectories)
                List<string> solutionFiles = new List<string>();
                List<string> projectFiles = new List<string>();
                List<string> exeFiles = new List<string>();

                // now find any solutions or projects in that directory (or subdirectories)
                GetSignificantFiles(projectDir, ref solutionFiles, ref projectFiles, ref exeFiles);
                if ((solutionFiles.Count == 0) && (projectFiles.Count == 0) && (exeFiles.Count == 0))
                    return false;

                // check if we have any solution files
                if (solutionFiles.Count > 0)
                {
                    OpenProjectOrSolution(solutionFiles[0]);
                }
                else if (projectFiles.Count > 0)
                {
                    // determine if we have a solution loaded.  If so, just add the project.
                    if (m_dte.Solution.IsOpen == true)
                        AddProject(projectFiles[0]);
                    else
                        OpenProjectOrSolution(projectFiles[0]);
                }
            }
            catch (Exception ex)
            {
                string errMsg = "Unable to load project: " + ex.Message;
                MessageBox.Show(null, errMsg, "Code Project Browser", MessageBoxButtons.OK, MessageBoxIcon.Error);
                retVal = false;
            }
            return retVal;
        }

        /// <summary>
        /// Opens a project or solution file.  We use the "File.OpenProject" command 
        /// because it will invoke the conversion wizard if the project or solution type
        /// is an older format.
        /// </summary>
        /// <param name="fileName">The project or solution file name to open.</param>
        public void OpenProjectOrSolution(string fileName)
        {
            // the file name must be quoted for this to work
            string quotedFileName = "\"" + fileName + "\"";
            m_dte.ExecuteCommand("File.OpenProject", quotedFileName);
        }

        /// <summary>
        /// Adds a project to the current solution file.  We use the "File.AddExistingProject" 
        /// command because it will invoke the conversion wizard if the project type is 
        /// an older format.
        /// </summary>
        /// <param name="fileName">The project file name to open.</param>
        public void AddProject(string fileName)
        {
            // the file name must be quoted for this to work
            string quotedFileName = "\"" + fileName + "\"";
            m_dte.ExecuteCommand("File.AddExistingProject", quotedFileName);
        }

    }
}

By viewing downloads associated with this article you agree to the Terms of Service and the article's licence.

If a file you wish to view isn't highlighted, and is a text file (not binary), please let us know and we'll add colourisation support for it.

License

This article, along with any associated source code and files, is licensed under The Code Project Open License (CPOL)


Written By
Web Developer
United States United States
SlickEdit Inc. provides software developers with multi-language development tools and the most advanced code editors available. Power programmers, from Fortune 500 companies to individuals, have chosen SlickEdit as their development tool of choice for over 19 years. Proven on Windows, Linux, UNIX, and Mac OS X platforms, SlickEdit products enable even the most accomplished developers to write more code faster, and more accurately. For more information about SlickEdit and free trial downloads, please visit http://www.slickedit.com.
This is a Organisation

1 members

Comments and Discussions