Add tool capable of downloading a TclKit (and its associated SDK) on Windows.

FossilOrigin-Name: 50673ddaf813335777673fa8585997a7551e5323
diff --git a/tool/GetFile.cs b/tool/GetFile.cs
new file mode 100644
index 0000000..9c489c6
--- /dev/null
+++ b/tool/GetFile.cs
@@ -0,0 +1,450 @@
+/*
+** 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("First argument is not an absolute URI.", 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

+    }

+}