xref: /sqlite-3.40.0/tool/GetFile.cs (revision fe293347)
180372ae2Smistachkin /*
280372ae2Smistachkin ** 2015 October 7
380372ae2Smistachkin **
480372ae2Smistachkin ** The author disclaims copyright to this source code.  In place of
580372ae2Smistachkin ** a legal notice, here is a blessing:
680372ae2Smistachkin **
780372ae2Smistachkin **    May you do good and not evil.
880372ae2Smistachkin **    May you find forgiveness for yourself and forgive others.
980372ae2Smistachkin **    May you share freely, never taking more than you give.
1080372ae2Smistachkin **
1180372ae2Smistachkin *************************************************************************
1280372ae2Smistachkin ** This file contains C# code to download a single file based on a URI.
1380372ae2Smistachkin */
1480372ae2Smistachkin 
1580372ae2Smistachkin using System;
1680372ae2Smistachkin using System.ComponentModel;
1780372ae2Smistachkin using System.Diagnostics;
1880372ae2Smistachkin using System.IO;
1980372ae2Smistachkin using System.Net;
2080372ae2Smistachkin using System.Reflection;
2180372ae2Smistachkin using System.Runtime.InteropServices;
2280372ae2Smistachkin using System.Threading;
2380372ae2Smistachkin 
2480372ae2Smistachkin ///////////////////////////////////////////////////////////////////////////////
2580372ae2Smistachkin 
2680372ae2Smistachkin #region Assembly Metadata
2780372ae2Smistachkin [assembly: AssemblyTitle("GetFile Tool")]
2880372ae2Smistachkin [assembly: AssemblyDescription("Download a single file based on a URI.")]
2980372ae2Smistachkin [assembly: AssemblyCompany("SQLite Development Team")]
3080372ae2Smistachkin [assembly: AssemblyProduct("SQLite")]
3180372ae2Smistachkin [assembly: AssemblyCopyright("Public Domain")]
3280372ae2Smistachkin [assembly: ComVisible(false)]
3380372ae2Smistachkin [assembly: Guid("5c4b3728-1693-4a33-a218-8e6973ca15a6")]
3480372ae2Smistachkin [assembly: AssemblyVersion("1.0.*")]
3580372ae2Smistachkin 
3680372ae2Smistachkin #if DEBUG
3780372ae2Smistachkin [assembly: AssemblyConfiguration("Debug")]
3880372ae2Smistachkin #else
3980372ae2Smistachkin [assembly: AssemblyConfiguration("Release")]
4080372ae2Smistachkin #endif
4180372ae2Smistachkin #endregion
4280372ae2Smistachkin 
4380372ae2Smistachkin ///////////////////////////////////////////////////////////////////////////////
4480372ae2Smistachkin 
4580372ae2Smistachkin namespace GetFile
4680372ae2Smistachkin {
4780372ae2Smistachkin     /// <summary>
4880372ae2Smistachkin     /// This enumeration is used to represent all the possible exit codes from
4980372ae2Smistachkin     /// this tool.
5080372ae2Smistachkin     /// </summary>
5180372ae2Smistachkin     internal enum ExitCode
5280372ae2Smistachkin     {
5380372ae2Smistachkin         /// <summary>
5480372ae2Smistachkin         /// The file download was a success.
5580372ae2Smistachkin         /// </summary>
5680372ae2Smistachkin         Success = 0,
5780372ae2Smistachkin 
5880372ae2Smistachkin         /// <summary>
5980372ae2Smistachkin         /// The command line arguments are missing (i.e. null).  Generally,
6080372ae2Smistachkin         /// this should not happen.
6180372ae2Smistachkin         /// </summary>
6280372ae2Smistachkin         MissingArgs = 1,
6380372ae2Smistachkin 
6480372ae2Smistachkin         /// <summary>
6580372ae2Smistachkin         /// The wrong number of command line arguments was supplied.
6680372ae2Smistachkin         /// </summary>
6780372ae2Smistachkin         WrongNumArgs = 2,
6880372ae2Smistachkin 
6980372ae2Smistachkin         /// <summary>
7080372ae2Smistachkin         /// The URI specified on the command line could not be parsed as a
7180372ae2Smistachkin         /// supported absolute URI.
7280372ae2Smistachkin         /// </summary>
7380372ae2Smistachkin         BadUri = 3,
7480372ae2Smistachkin 
7580372ae2Smistachkin         /// <summary>
7680372ae2Smistachkin         /// The file name portion of the URI specified on the command line
7780372ae2Smistachkin         /// could not be extracted from it.
7880372ae2Smistachkin         /// </summary>
7980372ae2Smistachkin         BadFileName = 4,
8080372ae2Smistachkin 
8180372ae2Smistachkin         /// <summary>
8280372ae2Smistachkin         /// The temporary directory is either invalid (i.e. null) or does not
8380372ae2Smistachkin         /// represent an available directory.
8480372ae2Smistachkin         /// </summary>
8580372ae2Smistachkin         BadTempPath = 5,
8680372ae2Smistachkin 
8780372ae2Smistachkin         /// <summary>
8880372ae2Smistachkin         /// An exception was caught in <see cref="Main" />.  Generally, this
8980372ae2Smistachkin         /// should not happen.
9080372ae2Smistachkin         /// </summary>
9180372ae2Smistachkin         Exception = 6,
9280372ae2Smistachkin 
9380372ae2Smistachkin         /// <summary>
9480372ae2Smistachkin         /// The file download was canceled.  This tool does not make use of
9580372ae2Smistachkin         /// the <see cref="WebClient.CancelAsync" /> method; therefore, this
9680372ae2Smistachkin         /// should not happen.
9780372ae2Smistachkin         /// </summary>
9880372ae2Smistachkin         DownloadCanceled = 7,
9980372ae2Smistachkin 
10080372ae2Smistachkin         /// <summary>
10180372ae2Smistachkin         /// The file download encountered an error.  Further information about
10280372ae2Smistachkin         /// this error should be displayed on the console.
10380372ae2Smistachkin         /// </summary>
10480372ae2Smistachkin         DownloadError = 8
10580372ae2Smistachkin     }
10680372ae2Smistachkin 
10780372ae2Smistachkin     ///////////////////////////////////////////////////////////////////////////
10880372ae2Smistachkin 
10980372ae2Smistachkin     internal static class Program
11080372ae2Smistachkin     {
11180372ae2Smistachkin         #region Private Data
11280372ae2Smistachkin         /// <summary>
11380372ae2Smistachkin         /// This is used to synchronize multithreaded access to the
11480372ae2Smistachkin         /// <see cref="previousPercent" /> and <see cref="exitCode"/>
11580372ae2Smistachkin         /// fields.
11680372ae2Smistachkin         /// </summary>
11780372ae2Smistachkin         private static readonly object syncRoot = new object();
11880372ae2Smistachkin 
11980372ae2Smistachkin         ///////////////////////////////////////////////////////////////////////
12080372ae2Smistachkin 
12180372ae2Smistachkin         /// <summary>
12280372ae2Smistachkin         /// This event will be signed when the file download has completed,
12380372ae2Smistachkin         /// even if the file download itself was canceled or unsuccessful.
12480372ae2Smistachkin         /// </summary>
12580372ae2Smistachkin         private static EventWaitHandle doneEvent;
12680372ae2Smistachkin 
12780372ae2Smistachkin         ///////////////////////////////////////////////////////////////////////
12880372ae2Smistachkin 
12980372ae2Smistachkin         /// <summary>
13080372ae2Smistachkin         /// The previous file download completion percentage seen by the
13180372ae2Smistachkin         /// <see cref="DownloadProgressChanged" /> event handler.  This value
13280372ae2Smistachkin         /// is never decreased, nor is it ever reset to zero.
13380372ae2Smistachkin         /// </summary>
13480372ae2Smistachkin         private static int previousPercent = 0;
13580372ae2Smistachkin 
13680372ae2Smistachkin         ///////////////////////////////////////////////////////////////////////
13780372ae2Smistachkin 
13880372ae2Smistachkin         /// <summary>
13980372ae2Smistachkin         /// This will be the exit code returned by this tool after the file
14080372ae2Smistachkin         /// download completes, successfully or otherwise.  This value is only
14180372ae2Smistachkin         /// changed by the <see cref="DownloadFileCompleted" /> event handler.
14280372ae2Smistachkin         /// </summary>
14380372ae2Smistachkin         private static ExitCode exitCode = ExitCode.Success;
14480372ae2Smistachkin         #endregion
14580372ae2Smistachkin 
14680372ae2Smistachkin         ///////////////////////////////////////////////////////////////////////
14780372ae2Smistachkin 
14880372ae2Smistachkin         #region Private Support Methods
14980372ae2Smistachkin         /// <summary>
15080372ae2Smistachkin         /// This method displays an error message to the console and/or
15180372ae2Smistachkin         /// displays the command line usage information for this tool.
15280372ae2Smistachkin         /// </summary>
15380372ae2Smistachkin         /// <param name="message">
15480372ae2Smistachkin         /// The error message to display, if any.
15580372ae2Smistachkin         /// </param>
15680372ae2Smistachkin         /// <param name="usage">
15780372ae2Smistachkin         /// Non-zero to display the command line usage information.
15880372ae2Smistachkin         /// </param>
Error( string message, bool usage )15980372ae2Smistachkin         private static void Error(
16080372ae2Smistachkin             string message,
16180372ae2Smistachkin             bool usage
16280372ae2Smistachkin             )
16380372ae2Smistachkin         {
16480372ae2Smistachkin             if (message != null)
16580372ae2Smistachkin                 Console.WriteLine(message);
16680372ae2Smistachkin 
16780372ae2Smistachkin             string fileName = Path.GetFileName(
16880372ae2Smistachkin                 Process.GetCurrentProcess().MainModule.FileName);
16980372ae2Smistachkin 
170*fe293347Smistachkin             Console.WriteLine(String.Format(
171*fe293347Smistachkin                 "usage: {0} <uri> [fileName]", fileName));
17280372ae2Smistachkin         }
17380372ae2Smistachkin 
17480372ae2Smistachkin         ///////////////////////////////////////////////////////////////////////
17580372ae2Smistachkin 
17680372ae2Smistachkin         /// <summary>
17780372ae2Smistachkin         /// This method attempts to determine the file name portion of the
17880372ae2Smistachkin         /// specified URI.
17980372ae2Smistachkin         /// </summary>
18080372ae2Smistachkin         /// <param name="uri">
18180372ae2Smistachkin         /// The URI to process.
18280372ae2Smistachkin         /// </param>
18380372ae2Smistachkin         /// <returns>
18480372ae2Smistachkin         /// The file name portion of the specified URI -OR- null if it cannot
18580372ae2Smistachkin         /// be determined.
18680372ae2Smistachkin         /// </returns>
GetFileName( Uri uri )18780372ae2Smistachkin         private static string GetFileName(
18880372ae2Smistachkin             Uri uri
18980372ae2Smistachkin             )
19080372ae2Smistachkin         {
19180372ae2Smistachkin             if (uri == null)
19280372ae2Smistachkin                 return null;
19380372ae2Smistachkin 
19480372ae2Smistachkin             string pathAndQuery = uri.PathAndQuery;
19580372ae2Smistachkin 
19680372ae2Smistachkin             if (String.IsNullOrEmpty(pathAndQuery))
19780372ae2Smistachkin                 return null;
19880372ae2Smistachkin 
19980372ae2Smistachkin             int index = pathAndQuery.LastIndexOf('/');
20080372ae2Smistachkin 
20180372ae2Smistachkin             if ((index < 0) || (index == pathAndQuery.Length))
20280372ae2Smistachkin                 return null;
20380372ae2Smistachkin 
20480372ae2Smistachkin             return pathAndQuery.Substring(index + 1);
20580372ae2Smistachkin         }
20680372ae2Smistachkin         #endregion
20780372ae2Smistachkin 
20880372ae2Smistachkin         ///////////////////////////////////////////////////////////////////////
20980372ae2Smistachkin 
21080372ae2Smistachkin         #region Private Event Handlers
21180372ae2Smistachkin         /// <summary>
21280372ae2Smistachkin         /// This method is an event handler that is called when the file
21380372ae2Smistachkin         /// download completion percentage changes.  It will display progress
21480372ae2Smistachkin         /// on the console.  Special care is taken to make sure that progress
21580372ae2Smistachkin         /// events are not displayed out-of-order, even if duplicate and/or
21680372ae2Smistachkin         /// out-of-order events are received.
21780372ae2Smistachkin         /// </summary>
21880372ae2Smistachkin         /// <param name="sender">
21980372ae2Smistachkin         /// The source of the event.
22080372ae2Smistachkin         /// </param>
22180372ae2Smistachkin         /// <param name="e">
22280372ae2Smistachkin         /// Information for the event being processed.
22380372ae2Smistachkin         /// </param>
DownloadProgressChanged( object sender, DownloadProgressChangedEventArgs e )22480372ae2Smistachkin         private static void DownloadProgressChanged(
22580372ae2Smistachkin             object sender,
22680372ae2Smistachkin             DownloadProgressChangedEventArgs e
22780372ae2Smistachkin             )
22880372ae2Smistachkin         {
22980372ae2Smistachkin             if (e != null)
23080372ae2Smistachkin             {
23180372ae2Smistachkin                 int percent = e.ProgressPercentage;
23280372ae2Smistachkin 
23380372ae2Smistachkin                 lock (syncRoot)
23480372ae2Smistachkin                 {
23580372ae2Smistachkin                     if (percent > previousPercent)
23680372ae2Smistachkin                     {
23780372ae2Smistachkin                         Console.Write('.');
23880372ae2Smistachkin 
23980372ae2Smistachkin                         if ((percent % 10) == 0)
24080372ae2Smistachkin                             Console.Write(" {0}% ", percent);
24180372ae2Smistachkin 
24280372ae2Smistachkin                         previousPercent = percent;
24380372ae2Smistachkin                     }
24480372ae2Smistachkin                 }
24580372ae2Smistachkin             }
24680372ae2Smistachkin         }
24780372ae2Smistachkin 
24880372ae2Smistachkin         ///////////////////////////////////////////////////////////////////////
24980372ae2Smistachkin 
25080372ae2Smistachkin         /// <summary>
25180372ae2Smistachkin         /// This method is an event handler that is called when the file
25280372ae2Smistachkin         /// download has completed, successfully or otherwise.  It will
25380372ae2Smistachkin         /// display the overall result of the file download on the console,
25480372ae2Smistachkin         /// including any <see cref="Exception" /> information, if applicable.
25580372ae2Smistachkin         /// The <see cref="exitCode" /> field is changed by this method to
25680372ae2Smistachkin         /// indicate the overall result of the file download and the event
25780372ae2Smistachkin         /// within the <see cref="doneEvent" /> field will be signaled.
25880372ae2Smistachkin         /// </summary>
25980372ae2Smistachkin         /// <param name="sender">
26080372ae2Smistachkin         /// The source of the event.
26180372ae2Smistachkin         /// </param>
26280372ae2Smistachkin         /// <param name="e">
26380372ae2Smistachkin         /// Information for the event being processed.
26480372ae2Smistachkin         /// </param>
DownloadFileCompleted( object sender, AsyncCompletedEventArgs e )26580372ae2Smistachkin         private static void DownloadFileCompleted(
26680372ae2Smistachkin             object sender,
26780372ae2Smistachkin             AsyncCompletedEventArgs e
26880372ae2Smistachkin             )
26980372ae2Smistachkin         {
27080372ae2Smistachkin             if (e != null)
27180372ae2Smistachkin             {
27280372ae2Smistachkin                 lock (syncRoot)
27380372ae2Smistachkin                 {
27480372ae2Smistachkin                     if (previousPercent < 100)
27580372ae2Smistachkin                         Console.Write(' ');
27680372ae2Smistachkin                 }
27780372ae2Smistachkin 
27880372ae2Smistachkin                 if (e.Cancelled)
27980372ae2Smistachkin                 {
28080372ae2Smistachkin                     Console.WriteLine("Canceled");
28180372ae2Smistachkin 
28280372ae2Smistachkin                     lock (syncRoot)
28380372ae2Smistachkin                     {
28480372ae2Smistachkin                         exitCode = ExitCode.DownloadCanceled;
28580372ae2Smistachkin                     }
28680372ae2Smistachkin                 }
28780372ae2Smistachkin                 else
28880372ae2Smistachkin                 {
28980372ae2Smistachkin                     Exception error = e.Error;
29080372ae2Smistachkin 
29180372ae2Smistachkin                     if (error != null)
29280372ae2Smistachkin                     {
29380372ae2Smistachkin                         Console.WriteLine("Error: {0}", error);
29480372ae2Smistachkin 
29580372ae2Smistachkin                         lock (syncRoot)
29680372ae2Smistachkin                         {
29780372ae2Smistachkin                             exitCode = ExitCode.DownloadError;
29880372ae2Smistachkin                         }
29980372ae2Smistachkin                     }
30080372ae2Smistachkin                     else
30180372ae2Smistachkin                     {
30280372ae2Smistachkin                         Console.WriteLine("Done");
30380372ae2Smistachkin                     }
30480372ae2Smistachkin                 }
30580372ae2Smistachkin             }
30680372ae2Smistachkin 
30780372ae2Smistachkin             if (doneEvent != null)
30880372ae2Smistachkin                 doneEvent.Set();
30980372ae2Smistachkin         }
31080372ae2Smistachkin         #endregion
31180372ae2Smistachkin 
31280372ae2Smistachkin         ///////////////////////////////////////////////////////////////////////
31380372ae2Smistachkin 
31480372ae2Smistachkin         #region Program Entry Point
31580372ae2Smistachkin         /// <summary>
31680372ae2Smistachkin         /// This is the entry-point for this tool.  It handles processing the
31780372ae2Smistachkin         /// command line arguments, setting up the web client, downloading the
31880372ae2Smistachkin         /// file, and saving it to the file system.
31980372ae2Smistachkin         /// </summary>
32080372ae2Smistachkin         /// <param name="args">
32180372ae2Smistachkin         /// The command line arguments.
32280372ae2Smistachkin         /// </param>
32380372ae2Smistachkin         /// <returns>
32480372ae2Smistachkin         /// Zero upon success; non-zero on failure.  This will be one of the
32580372ae2Smistachkin         /// values from the <see cref="ExitCode" /> enumeration.
32680372ae2Smistachkin         /// </returns>
Main( string[] args )32780372ae2Smistachkin         private static int Main(
32880372ae2Smistachkin             string[] args
32980372ae2Smistachkin             )
33080372ae2Smistachkin         {
33180372ae2Smistachkin             //
33280372ae2Smistachkin             // NOTE: Sanity check the command line arguments.
33380372ae2Smistachkin             //
33480372ae2Smistachkin             if (args == null)
33580372ae2Smistachkin             {
33680372ae2Smistachkin                 Error(null, true);
33780372ae2Smistachkin                 return (int)ExitCode.MissingArgs;
33880372ae2Smistachkin             }
33980372ae2Smistachkin 
340*fe293347Smistachkin             if ((args.Length < 1) || (args.Length > 2))
34180372ae2Smistachkin             {
34280372ae2Smistachkin                 Error(null, true);
34380372ae2Smistachkin                 return (int)ExitCode.WrongNumArgs;
34480372ae2Smistachkin             }
34580372ae2Smistachkin 
34680372ae2Smistachkin             //
34780372ae2Smistachkin             // NOTE: Attempt to convert the first (and only) command line
34880372ae2Smistachkin             //       argument to an absolute URI.
34980372ae2Smistachkin             //
35080372ae2Smistachkin             Uri uri;
35180372ae2Smistachkin 
35280372ae2Smistachkin             if (!Uri.TryCreate(args[0], UriKind.Absolute, out uri))
35380372ae2Smistachkin             {
354cc730488Smistachkin                 Error("Could not create absolute URI from argument.", false);
35580372ae2Smistachkin                 return (int)ExitCode.BadUri;
35680372ae2Smistachkin             }
35780372ae2Smistachkin 
35880372ae2Smistachkin             //
359*fe293347Smistachkin             // NOTE: If a file name was specified on the command line, try to
360*fe293347Smistachkin             //       use it (without its directory name); otherwise, fallback
361*fe293347Smistachkin             //       to using the file name portion of the URI.
36280372ae2Smistachkin             //
363*fe293347Smistachkin             string fileName = (args.Length == 2) ?
364*fe293347Smistachkin                 Path.GetFileName(args[1]) : null;
365*fe293347Smistachkin 
366*fe293347Smistachkin             if (String.IsNullOrEmpty(fileName))
367*fe293347Smistachkin             {
368*fe293347Smistachkin                 //
369*fe293347Smistachkin                 // NOTE: Attempt to extract the file name portion of the URI
370*fe293347Smistachkin                 //       we just created.
371*fe293347Smistachkin                 //
372*fe293347Smistachkin                 fileName = GetFileName(uri);
37380372ae2Smistachkin 
37480372ae2Smistachkin                 if (fileName == null)
37580372ae2Smistachkin                 {
376*fe293347Smistachkin                     Error("Could not extract file name from URI.", false);
37780372ae2Smistachkin                     return (int)ExitCode.BadFileName;
37880372ae2Smistachkin                 }
379*fe293347Smistachkin             }
38080372ae2Smistachkin 
38180372ae2Smistachkin             //
38280372ae2Smistachkin             // NOTE: Grab the temporary path setup for this process.  If it is
38380372ae2Smistachkin             //       unavailable, we will not continue.
38480372ae2Smistachkin             //
38580372ae2Smistachkin             string directory = Path.GetTempPath();
38680372ae2Smistachkin 
38780372ae2Smistachkin             if (String.IsNullOrEmpty(directory) ||
38880372ae2Smistachkin                 !Directory.Exists(directory))
38980372ae2Smistachkin             {
39080372ae2Smistachkin                 Error("Temporary directory is invalid or unavailable.", false);
39180372ae2Smistachkin                 return (int)ExitCode.BadTempPath;
39280372ae2Smistachkin             }
39380372ae2Smistachkin 
39480372ae2Smistachkin             try
39580372ae2Smistachkin             {
396*fe293347Smistachkin                 //
397*fe293347Smistachkin                 // HACK: For use of the TLS 1.2 security protocol because some
398*fe293347Smistachkin                 //       web servers fail without it.  In order to support the
399*fe293347Smistachkin                 //       .NET Framework 2.0+ at compilation time, must use its
400*fe293347Smistachkin                 //       integer constant here.
401*fe293347Smistachkin                 //
402*fe293347Smistachkin                 ServicePointManager.SecurityProtocol =
403*fe293347Smistachkin                     (SecurityProtocolType)0xC00;
404*fe293347Smistachkin 
40580372ae2Smistachkin                 using (WebClient webClient = new WebClient())
40680372ae2Smistachkin                 {
40780372ae2Smistachkin                     //
40880372ae2Smistachkin                     // NOTE: Create the event used to signal completion of the
40980372ae2Smistachkin                     //       file download.
41080372ae2Smistachkin                     //
41180372ae2Smistachkin                     doneEvent = new ManualResetEvent(false);
41280372ae2Smistachkin 
41380372ae2Smistachkin                     //
41480372ae2Smistachkin                     // NOTE: Hookup the event handlers we care about on the web
41580372ae2Smistachkin                     //       client.  These are necessary because the file is
41680372ae2Smistachkin                     //       downloaded asynchronously.
41780372ae2Smistachkin                     //
41880372ae2Smistachkin                     webClient.DownloadProgressChanged +=
41980372ae2Smistachkin                         new DownloadProgressChangedEventHandler(
42080372ae2Smistachkin                             DownloadProgressChanged);
42180372ae2Smistachkin 
42280372ae2Smistachkin                     webClient.DownloadFileCompleted +=
42380372ae2Smistachkin                         new AsyncCompletedEventHandler(
42480372ae2Smistachkin                             DownloadFileCompleted);
42580372ae2Smistachkin 
42680372ae2Smistachkin                     //
42780372ae2Smistachkin                     // NOTE: Build the fully qualified path and file name,
42880372ae2Smistachkin                     //       within the temporary directory, where the file to
42980372ae2Smistachkin                     //       be downloaded will be saved.
43080372ae2Smistachkin                     //
43180372ae2Smistachkin                     fileName = Path.Combine(directory, fileName);
43280372ae2Smistachkin 
43380372ae2Smistachkin                     //
43480372ae2Smistachkin                     // NOTE: If the file name already exists (in the temporary)
43580372ae2Smistachkin                     //       directory, delete it.
43680372ae2Smistachkin                     //
43780372ae2Smistachkin                     // TODO: Perhaps an error should be raised here instead?
43880372ae2Smistachkin                     //
43980372ae2Smistachkin                     if (File.Exists(fileName))
44080372ae2Smistachkin                         File.Delete(fileName);
44180372ae2Smistachkin 
44280372ae2Smistachkin                     //
44380372ae2Smistachkin                     // NOTE: After kicking off the asynchronous file download
44480372ae2Smistachkin                     //       process, wait [forever] until the "done" event is
44580372ae2Smistachkin                     //       signaled.
44680372ae2Smistachkin                     //
44780372ae2Smistachkin                     Console.WriteLine(
44880372ae2Smistachkin                         "Downloading \"{0}\" to \"{1}\"...", uri, fileName);
44980372ae2Smistachkin 
45080372ae2Smistachkin                     webClient.DownloadFileAsync(uri, fileName);
45180372ae2Smistachkin                     doneEvent.WaitOne();
45280372ae2Smistachkin                 }
45380372ae2Smistachkin 
45480372ae2Smistachkin                 lock (syncRoot)
45580372ae2Smistachkin                 {
45680372ae2Smistachkin                     return (int)exitCode;
45780372ae2Smistachkin                 }
45880372ae2Smistachkin             }
45980372ae2Smistachkin             catch (Exception e)
46080372ae2Smistachkin             {
46180372ae2Smistachkin                 //
46280372ae2Smistachkin                 // NOTE: An exception was caught.  Report it via the console
46380372ae2Smistachkin                 //       and return failure.
46480372ae2Smistachkin                 //
46580372ae2Smistachkin                 Error(e.ToString(), false);
46680372ae2Smistachkin                 return (int)ExitCode.Exception;
46780372ae2Smistachkin             }
46880372ae2Smistachkin         }
46980372ae2Smistachkin         #endregion
47080372ae2Smistachkin     }
47180372ae2Smistachkin }
472