451 lines
		
	
	
		
			15 KiB
		
	
	
	
		
			C#
		
	
	
	
	
	
			
		
		
	
	
			451 lines
		
	
	
		
			15 KiB
		
	
	
	
		
			C#
		
	
	
	
	
	
/*
 | 
						|
** 2015 October 7
 | 
						|
**
 | 
						|
** The author disclaims copyright to this source code.  In place of
 | 
						|
** a legal notice, here is a blessing:
 | 
						|
**
 | 
						|
**    May you do good and not evil.
 | 
						|
**    May you find forgiveness for yourself and forgive others.
 | 
						|
**    May you share freely, never taking more than you give.
 | 
						|
**
 | 
						|
*************************************************************************
 | 
						|
** This file contains C# code to download a single file based on a URI.
 | 
						|
*/
 | 
						|
 | 
						|
using System;
 | 
						|
using System.ComponentModel;
 | 
						|
using System.Diagnostics;
 | 
						|
using System.IO;
 | 
						|
using System.Net;
 | 
						|
using System.Reflection;
 | 
						|
using System.Runtime.InteropServices;
 | 
						|
using System.Threading;
 | 
						|
 | 
						|
///////////////////////////////////////////////////////////////////////////////
 | 
						|
 | 
						|
#region Assembly Metadata
 | 
						|
[assembly: AssemblyTitle("GetFile Tool")]
 | 
						|
[assembly: AssemblyDescription("Download a single file based on a URI.")]
 | 
						|
[assembly: AssemblyCompany("SQLite Development Team")]
 | 
						|
[assembly: AssemblyProduct("SQLite")]
 | 
						|
[assembly: AssemblyCopyright("Public Domain")]
 | 
						|
[assembly: ComVisible(false)]
 | 
						|
[assembly: Guid("5c4b3728-1693-4a33-a218-8e6973ca15a6")]
 | 
						|
[assembly: AssemblyVersion("1.0.*")]
 | 
						|
 | 
						|
#if DEBUG
 | 
						|
[assembly: AssemblyConfiguration("Debug")]
 | 
						|
#else
 | 
						|
[assembly: AssemblyConfiguration("Release")]
 | 
						|
#endif
 | 
						|
#endregion
 | 
						|
 | 
						|
///////////////////////////////////////////////////////////////////////////////
 | 
						|
 | 
						|
namespace GetFile
 | 
						|
{
 | 
						|
    /// <summary>
 | 
						|
    /// This enumeration is used to represent all the possible exit codes from
 | 
						|
    /// this tool.
 | 
						|
    /// </summary>
 | 
						|
    internal enum ExitCode
 | 
						|
    {
 | 
						|
        /// <summary>
 | 
						|
        /// The file download was a success.
 | 
						|
        /// </summary>
 | 
						|
        Success = 0,
 | 
						|
 | 
						|
        /// <summary>
 | 
						|
        /// The command line arguments are missing (i.e. null).  Generally,
 | 
						|
        /// this should not happen.
 | 
						|
        /// </summary>
 | 
						|
        MissingArgs = 1,
 | 
						|
 | 
						|
        /// <summary>
 | 
						|
        /// The wrong number of command line arguments was supplied.
 | 
						|
        /// </summary>
 | 
						|
        WrongNumArgs = 2,
 | 
						|
 | 
						|
        /// <summary>
 | 
						|
        /// The URI specified on the command line could not be parsed as a
 | 
						|
        /// supported absolute URI.
 | 
						|
        /// </summary>
 | 
						|
        BadUri = 3,
 | 
						|
 | 
						|
        /// <summary>
 | 
						|
        /// The file name portion of the URI specified on the command line
 | 
						|
        /// could not be extracted from it.
 | 
						|
        /// </summary>
 | 
						|
        BadFileName = 4,
 | 
						|
 | 
						|
        /// <summary>
 | 
						|
        /// The temporary directory is either invalid (i.e. null) or does not
 | 
						|
        /// represent an available directory.
 | 
						|
        /// </summary>
 | 
						|
        BadTempPath = 5,
 | 
						|
 | 
						|
        /// <summary>
 | 
						|
        /// An exception was caught in <see cref="Main" />.  Generally, this
 | 
						|
        /// should not happen.
 | 
						|
        /// </summary>
 | 
						|
        Exception = 6,
 | 
						|
 | 
						|
        /// <summary>
 | 
						|
        /// The file download was canceled.  This tool does not make use of
 | 
						|
        /// the <see cref="WebClient.CancelAsync" /> method; therefore, this
 | 
						|
        /// should not happen.
 | 
						|
        /// </summary>
 | 
						|
        DownloadCanceled = 7,
 | 
						|
 | 
						|
        /// <summary>
 | 
						|
        /// The file download encountered an error.  Further information about
 | 
						|
        /// this error should be displayed on the console.
 | 
						|
        /// </summary>
 | 
						|
        DownloadError = 8
 | 
						|
    }
 | 
						|
 | 
						|
    ///////////////////////////////////////////////////////////////////////////
 | 
						|
 | 
						|
    internal static class Program
 | 
						|
    {
 | 
						|
        #region Private Data
 | 
						|
        /// <summary>
 | 
						|
        /// This is used to synchronize multithreaded access to the
 | 
						|
        /// <see cref="previousPercent" /> and <see cref="exitCode"/>
 | 
						|
        /// fields.
 | 
						|
        /// </summary>
 | 
						|
        private static readonly object syncRoot = new object();
 | 
						|
 | 
						|
        ///////////////////////////////////////////////////////////////////////
 | 
						|
 | 
						|
        /// <summary>
 | 
						|
        /// This event will be signed when the file download has completed,
 | 
						|
        /// even if the file download itself was canceled or unsuccessful.
 | 
						|
        /// </summary>
 | 
						|
        private static EventWaitHandle doneEvent;
 | 
						|
 | 
						|
        ///////////////////////////////////////////////////////////////////////
 | 
						|
 | 
						|
        /// <summary>
 | 
						|
        /// The previous file download completion percentage seen by the
 | 
						|
        /// <see cref="DownloadProgressChanged" /> event handler.  This value
 | 
						|
        /// is never decreased, nor is it ever reset to zero.
 | 
						|
        /// </summary>
 | 
						|
        private static int previousPercent = 0;
 | 
						|
 | 
						|
        ///////////////////////////////////////////////////////////////////////
 | 
						|
 | 
						|
        /// <summary>
 | 
						|
        /// This will be the exit code returned by this tool after the file
 | 
						|
        /// download completes, successfully or otherwise.  This value is only
 | 
						|
        /// changed by the <see cref="DownloadFileCompleted" /> event handler.
 | 
						|
        /// </summary>
 | 
						|
        private static ExitCode exitCode = ExitCode.Success;
 | 
						|
        #endregion
 | 
						|
 | 
						|
        ///////////////////////////////////////////////////////////////////////
 | 
						|
 | 
						|
        #region Private Support Methods
 | 
						|
        /// <summary>
 | 
						|
        /// This method displays an error message to the console and/or
 | 
						|
        /// displays the command line usage information for this tool.
 | 
						|
        /// </summary>
 | 
						|
        /// <param name="message">
 | 
						|
        /// The error message to display, if any.
 | 
						|
        /// </param>
 | 
						|
        /// <param name="usage">
 | 
						|
        /// Non-zero to display the command line usage information.
 | 
						|
        /// </param>
 | 
						|
        private static void Error(
 | 
						|
            string message,
 | 
						|
            bool usage
 | 
						|
            )
 | 
						|
        {
 | 
						|
            if (message != null)
 | 
						|
                Console.WriteLine(message);
 | 
						|
 | 
						|
            string fileName = Path.GetFileName(
 | 
						|
                Process.GetCurrentProcess().MainModule.FileName);
 | 
						|
 | 
						|
            Console.WriteLine(String.Format("usage: {0} <uri>", fileName));
 | 
						|
        }
 | 
						|
 | 
						|
        ///////////////////////////////////////////////////////////////////////
 | 
						|
 | 
						|
        /// <summary>
 | 
						|
        /// This method attempts to determine the file name portion of the
 | 
						|
        /// specified URI.
 | 
						|
        /// </summary>
 | 
						|
        /// <param name="uri">
 | 
						|
        /// The URI to process.
 | 
						|
        /// </param>
 | 
						|
        /// <returns>
 | 
						|
        /// The file name portion of the specified URI -OR- null if it cannot
 | 
						|
        /// be determined.
 | 
						|
        /// </returns>
 | 
						|
        private static string GetFileName(
 | 
						|
            Uri uri
 | 
						|
            )
 | 
						|
        {
 | 
						|
            if (uri == null)
 | 
						|
                return null;
 | 
						|
 | 
						|
            string pathAndQuery = uri.PathAndQuery;
 | 
						|
 | 
						|
            if (String.IsNullOrEmpty(pathAndQuery))
 | 
						|
                return null;
 | 
						|
 | 
						|
            int index = pathAndQuery.LastIndexOf('/');
 | 
						|
 | 
						|
            if ((index < 0) || (index == pathAndQuery.Length))
 | 
						|
                return null;
 | 
						|
 | 
						|
            return pathAndQuery.Substring(index + 1);
 | 
						|
        }
 | 
						|
        #endregion
 | 
						|
 | 
						|
        ///////////////////////////////////////////////////////////////////////
 | 
						|
 | 
						|
        #region Private Event Handlers
 | 
						|
        /// <summary>
 | 
						|
        /// This method is an event handler that is called when the file
 | 
						|
        /// download completion percentage changes.  It will display progress
 | 
						|
        /// on the console.  Special care is taken to make sure that progress
 | 
						|
        /// events are not displayed out-of-order, even if duplicate and/or
 | 
						|
        /// out-of-order events are received.
 | 
						|
        /// </summary>
 | 
						|
        /// <param name="sender">
 | 
						|
        /// The source of the event.
 | 
						|
        /// </param>
 | 
						|
        /// <param name="e">
 | 
						|
        /// Information for the event being processed.
 | 
						|
        /// </param>
 | 
						|
        private static void DownloadProgressChanged(
 | 
						|
            object sender,
 | 
						|
            DownloadProgressChangedEventArgs e
 | 
						|
            )
 | 
						|
        {
 | 
						|
            if (e != null)
 | 
						|
            {
 | 
						|
                int percent = e.ProgressPercentage;
 | 
						|
 | 
						|
                lock (syncRoot)
 | 
						|
                {
 | 
						|
                    if (percent > previousPercent)
 | 
						|
                    {
 | 
						|
                        Console.Write('.');
 | 
						|
 | 
						|
                        if ((percent % 10) == 0)
 | 
						|
                            Console.Write(" {0}% ", percent);
 | 
						|
 | 
						|
                        previousPercent = percent;
 | 
						|
                    }
 | 
						|
                }
 | 
						|
            }
 | 
						|
        }
 | 
						|
 | 
						|
        ///////////////////////////////////////////////////////////////////////
 | 
						|
 | 
						|
        /// <summary>
 | 
						|
        /// This method is an event handler that is called when the file
 | 
						|
        /// download has completed, successfully or otherwise.  It will
 | 
						|
        /// display the overall result of the file download on the console,
 | 
						|
        /// including any <see cref="Exception" /> information, if applicable.
 | 
						|
        /// The <see cref="exitCode" /> field is changed by this method to
 | 
						|
        /// indicate the overall result of the file download and the event
 | 
						|
        /// within the <see cref="doneEvent" /> field will be signaled.
 | 
						|
        /// </summary>
 | 
						|
        /// <param name="sender">
 | 
						|
        /// The source of the event.
 | 
						|
        /// </param>
 | 
						|
        /// <param name="e">
 | 
						|
        /// Information for the event being processed.
 | 
						|
        /// </param>
 | 
						|
        private static void DownloadFileCompleted(
 | 
						|
            object sender,
 | 
						|
            AsyncCompletedEventArgs e
 | 
						|
            )
 | 
						|
        {
 | 
						|
            if (e != null)
 | 
						|
            {
 | 
						|
                lock (syncRoot)
 | 
						|
                {
 | 
						|
                    if (previousPercent < 100)
 | 
						|
                        Console.Write(' ');
 | 
						|
                }
 | 
						|
 | 
						|
                if (e.Cancelled)
 | 
						|
                {
 | 
						|
                    Console.WriteLine("Canceled");
 | 
						|
 | 
						|
                    lock (syncRoot)
 | 
						|
                    {
 | 
						|
                        exitCode = ExitCode.DownloadCanceled;
 | 
						|
                    }
 | 
						|
                }
 | 
						|
                else
 | 
						|
                {
 | 
						|
                    Exception error = e.Error;
 | 
						|
 | 
						|
                    if (error != null)
 | 
						|
                    {
 | 
						|
                        Console.WriteLine("Error: {0}", error);
 | 
						|
 | 
						|
                        lock (syncRoot)
 | 
						|
                        {
 | 
						|
                            exitCode = ExitCode.DownloadError;
 | 
						|
                        }
 | 
						|
                    }
 | 
						|
                    else
 | 
						|
                    {
 | 
						|
                        Console.WriteLine("Done");
 | 
						|
                    }
 | 
						|
                }
 | 
						|
            }
 | 
						|
 | 
						|
            if (doneEvent != null)
 | 
						|
                doneEvent.Set();
 | 
						|
        }
 | 
						|
        #endregion
 | 
						|
 | 
						|
        ///////////////////////////////////////////////////////////////////////
 | 
						|
 | 
						|
        #region Program Entry Point
 | 
						|
        /// <summary>
 | 
						|
        /// This is the entry-point for this tool.  It handles processing the
 | 
						|
        /// command line arguments, setting up the web client, downloading the
 | 
						|
        /// file, and saving it to the file system.
 | 
						|
        /// </summary>
 | 
						|
        /// <param name="args">
 | 
						|
        /// The command line arguments.
 | 
						|
        /// </param>
 | 
						|
        /// <returns>
 | 
						|
        /// Zero upon success; non-zero on failure.  This will be one of the
 | 
						|
        /// values from the <see cref="ExitCode" /> enumeration.
 | 
						|
        /// </returns>
 | 
						|
        private static int Main(
 | 
						|
            string[] args
 | 
						|
            )
 | 
						|
        {
 | 
						|
            //
 | 
						|
            // NOTE: Sanity check the command line arguments.
 | 
						|
            //
 | 
						|
            if (args == null)
 | 
						|
            {
 | 
						|
                Error(null, true);
 | 
						|
                return (int)ExitCode.MissingArgs;
 | 
						|
            }
 | 
						|
 | 
						|
            if (args.Length != 1)
 | 
						|
            {
 | 
						|
                Error(null, true);
 | 
						|
                return (int)ExitCode.WrongNumArgs;
 | 
						|
            }
 | 
						|
 | 
						|
            //
 | 
						|
            // NOTE: Attempt to convert the first (and only) command line
 | 
						|
            //       argument to an absolute URI.
 | 
						|
            //
 | 
						|
            Uri uri;
 | 
						|
 | 
						|
            if (!Uri.TryCreate(args[0], UriKind.Absolute, out uri))
 | 
						|
            {
 | 
						|
                Error("Could not create absolute URI from argument.", false);
 | 
						|
                return (int)ExitCode.BadUri;
 | 
						|
            }
 | 
						|
 | 
						|
            //
 | 
						|
            // NOTE: Attempt to extract the file name portion of the URI we
 | 
						|
            //       just created.
 | 
						|
            //
 | 
						|
            string fileName = GetFileName(uri);
 | 
						|
 | 
						|
            if (fileName == null)
 | 
						|
            {
 | 
						|
                Error("Could not extract the file name from the URI.", false);
 | 
						|
                return (int)ExitCode.BadFileName;
 | 
						|
            }
 | 
						|
 | 
						|
            //
 | 
						|
            // NOTE: Grab the temporary path setup for this process.  If it is
 | 
						|
            //       unavailable, we will not continue.
 | 
						|
            //
 | 
						|
            string directory = Path.GetTempPath();
 | 
						|
 | 
						|
            if (String.IsNullOrEmpty(directory) ||
 | 
						|
                !Directory.Exists(directory))
 | 
						|
            {
 | 
						|
                Error("Temporary directory is invalid or unavailable.", false);
 | 
						|
                return (int)ExitCode.BadTempPath;
 | 
						|
            }
 | 
						|
 | 
						|
            try
 | 
						|
            {
 | 
						|
                using (WebClient webClient = new WebClient())
 | 
						|
                {
 | 
						|
                    //
 | 
						|
                    // NOTE: Create the event used to signal completion of the
 | 
						|
                    //       file download.
 | 
						|
                    //
 | 
						|
                    doneEvent = new ManualResetEvent(false);
 | 
						|
 | 
						|
                    //
 | 
						|
                    // NOTE: Hookup the event handlers we care about on the web
 | 
						|
                    //       client.  These are necessary because the file is
 | 
						|
                    //       downloaded asynchronously.
 | 
						|
                    //
 | 
						|
                    webClient.DownloadProgressChanged +=
 | 
						|
                        new DownloadProgressChangedEventHandler(
 | 
						|
                            DownloadProgressChanged);
 | 
						|
 | 
						|
                    webClient.DownloadFileCompleted +=
 | 
						|
                        new AsyncCompletedEventHandler(
 | 
						|
                            DownloadFileCompleted);
 | 
						|
 | 
						|
                    //
 | 
						|
                    // NOTE: Build the fully qualified path and file name,
 | 
						|
                    //       within the temporary directory, where the file to
 | 
						|
                    //       be downloaded will be saved.
 | 
						|
                    //
 | 
						|
                    fileName = Path.Combine(directory, fileName);
 | 
						|
 | 
						|
                    //
 | 
						|
                    // NOTE: If the file name already exists (in the temporary)
 | 
						|
                    //       directory, delete it.
 | 
						|
                    //
 | 
						|
                    // TODO: Perhaps an error should be raised here instead?
 | 
						|
                    //
 | 
						|
                    if (File.Exists(fileName))
 | 
						|
                        File.Delete(fileName);
 | 
						|
 | 
						|
                    //
 | 
						|
                    // NOTE: After kicking off the asynchronous file download
 | 
						|
                    //       process, wait [forever] until the "done" event is
 | 
						|
                    //       signaled.
 | 
						|
                    //
 | 
						|
                    Console.WriteLine(
 | 
						|
                        "Downloading \"{0}\" to \"{1}\"...", uri, fileName);
 | 
						|
 | 
						|
                    webClient.DownloadFileAsync(uri, fileName);
 | 
						|
                    doneEvent.WaitOne();
 | 
						|
                }
 | 
						|
 | 
						|
                lock (syncRoot)
 | 
						|
                {
 | 
						|
                    return (int)exitCode;
 | 
						|
                }
 | 
						|
            }
 | 
						|
            catch (Exception e)
 | 
						|
            {
 | 
						|
                //
 | 
						|
                // NOTE: An exception was caught.  Report it via the console
 | 
						|
                //       and return failure.
 | 
						|
                //
 | 
						|
                Error(e.ToString(), false);
 | 
						|
                return (int)ExitCode.Exception;
 | 
						|
            }
 | 
						|
        }
 | 
						|
        #endregion
 | 
						|
    }
 | 
						|
}
 |