4 ** The author disclaims copyright to this source code. In place of
5 ** a legal notice, here is a blessing:
7 ** May you do good and not evil.
8 ** May you find forgiveness for yourself and forgive others.
9 ** May you share freely, never taking more than you give.
11 *************************************************************************
12 ** This file contains C# code to download a single file based on a URI.
16 using System
.ComponentModel
;
17 using System
.Diagnostics
;
20 using System
.Reflection
;
21 using System
.Runtime
.InteropServices
;
22 using System
.Threading
;
24 ///////////////////////////////////////////////////////////////////////////////
26 #region Assembly Metadata
27 [assembly
: AssemblyTitle("GetFile Tool")]
28 [assembly
: AssemblyDescription("Download a single file based on a URI.")]
29 [assembly
: AssemblyCompany("SQLite Development Team")]
30 [assembly
: AssemblyProduct("SQLite")]
31 [assembly
: AssemblyCopyright("Public Domain")]
32 [assembly
: ComVisible(false)]
33 [assembly
: Guid("5c4b3728-1693-4a33-a218-8e6973ca15a6")]
34 [assembly
: AssemblyVersion("1.0.*")]
37 [assembly
: AssemblyConfiguration("Debug")]
39 [assembly
: AssemblyConfiguration("Release")]
43 ///////////////////////////////////////////////////////////////////////////////
48 /// This enumeration is used to represent all the possible exit codes from
51 internal enum ExitCode
54 /// The file download was a success.
59 /// The command line arguments are missing (i.e. null). Generally,
60 /// this should not happen.
65 /// The wrong number of command line arguments was supplied.
70 /// The URI specified on the command line could not be parsed as a
71 /// supported absolute URI.
76 /// The file name portion of the URI specified on the command line
77 /// could not be extracted from it.
82 /// The temporary directory is either invalid (i.e. null) or does not
83 /// represent an available directory.
88 /// An exception was caught in <see cref="Main" />. Generally, this
89 /// should not happen.
94 /// The file download was canceled. This tool does not make use of
95 /// the <see cref="WebClient.CancelAsync" /> method; therefore, this
96 /// should not happen.
101 /// The file download encountered an error. Further information about
102 /// this error should be displayed on the console.
107 ///////////////////////////////////////////////////////////////////////////
109 internal static class Program
113 /// This is used to synchronize multithreaded access to the
114 /// <see cref="previousPercent" /> and <see cref="exitCode"/>
117 private static readonly object syncRoot
= new object();
119 ///////////////////////////////////////////////////////////////////////
122 /// This event will be signed when the file download has completed,
123 /// even if the file download itself was canceled or unsuccessful.
125 private static EventWaitHandle doneEvent
;
127 ///////////////////////////////////////////////////////////////////////
130 /// The previous file download completion percentage seen by the
131 /// <see cref="DownloadProgressChanged" /> event handler. This value
132 /// is never decreased, nor is it ever reset to zero.
134 private static int previousPercent
= 0;
136 ///////////////////////////////////////////////////////////////////////
139 /// This will be the exit code returned by this tool after the file
140 /// download completes, successfully or otherwise. This value is only
141 /// changed by the <see cref="DownloadFileCompleted" /> event handler.
143 private static ExitCode exitCode
= ExitCode
.Success
;
146 ///////////////////////////////////////////////////////////////////////
148 #region Private Support Methods
150 /// This method displays an error message to the console and/or
151 /// displays the command line usage information for this tool.
153 /// <param name="message">
154 /// The error message to display, if any.
156 /// <param name="usage">
157 /// Non-zero to display the command line usage information.
159 private static void Error(
165 Console
.WriteLine(message
);
167 string fileName
= Path
.GetFileName(
168 Process
.GetCurrentProcess().MainModule
.FileName
);
170 Console
.WriteLine(String
.Format("usage: {0} <uri>", fileName
));
173 ///////////////////////////////////////////////////////////////////////
176 /// This method attempts to determine the file name portion of the
179 /// <param name="uri">
180 /// The URI to process.
183 /// The file name portion of the specified URI -OR- null if it cannot
186 private static string GetFileName(
193 string pathAndQuery
= uri
.PathAndQuery
;
195 if (String
.IsNullOrEmpty(pathAndQuery
))
198 int index
= pathAndQuery
.LastIndexOf('/');
200 if ((index
< 0) || (index
== pathAndQuery
.Length
))
203 return pathAndQuery
.Substring(index
+ 1);
207 ///////////////////////////////////////////////////////////////////////
209 #region Private Event Handlers
211 /// This method is an event handler that is called when the file
212 /// download completion percentage changes. It will display progress
213 /// on the console. Special care is taken to make sure that progress
214 /// events are not displayed out-of-order, even if duplicate and/or
215 /// out-of-order events are received.
217 /// <param name="sender">
218 /// The source of the event.
221 /// Information for the event being processed.
223 private static void DownloadProgressChanged(
225 DownloadProgressChangedEventArgs e
230 int percent
= e
.ProgressPercentage
;
234 if (percent
> previousPercent
)
238 if ((percent
% 10) == 0)
239 Console
.Write(" {0}% ", percent
);
241 previousPercent
= percent
;
247 ///////////////////////////////////////////////////////////////////////
250 /// This method is an event handler that is called when the file
251 /// download has completed, successfully or otherwise. It will
252 /// display the overall result of the file download on the console,
253 /// including any <see cref="Exception" /> information, if applicable.
254 /// The <see cref="exitCode" /> field is changed by this method to
255 /// indicate the overall result of the file download and the event
256 /// within the <see cref="doneEvent" /> field will be signaled.
258 /// <param name="sender">
259 /// The source of the event.
262 /// Information for the event being processed.
264 private static void DownloadFileCompleted(
266 AsyncCompletedEventArgs e
273 if (previousPercent
< 100)
279 Console
.WriteLine("Canceled");
283 exitCode
= ExitCode
.DownloadCanceled
;
288 Exception error
= e
.Error
;
292 Console
.WriteLine("Error: {0}", error
);
296 exitCode
= ExitCode
.DownloadError
;
301 Console
.WriteLine("Done");
306 if (doneEvent
!= null)
311 ///////////////////////////////////////////////////////////////////////
313 #region Program Entry Point
315 /// This is the entry-point for this tool. It handles processing the
316 /// command line arguments, setting up the web client, downloading the
317 /// file, and saving it to the file system.
319 /// <param name="args">
320 /// The command line arguments.
323 /// Zero upon success; non-zero on failure. This will be one of the
324 /// values from the <see cref="ExitCode" /> enumeration.
326 private static int Main(
331 // NOTE: Sanity check the command line arguments.
336 return (int)ExitCode
.MissingArgs
;
339 if (args
.Length
!= 1)
342 return (int)ExitCode
.WrongNumArgs
;
346 // NOTE: Attempt to convert the first (and only) command line
347 // argument to an absolute URI.
351 if (!Uri
.TryCreate(args
[0], UriKind
.Absolute
, out uri
))
353 Error("Could not create absolute URI from argument.", false);
354 return (int)ExitCode
.BadUri
;
358 // NOTE: Attempt to extract the file name portion of the URI we
361 string fileName
= GetFileName(uri
);
363 if (fileName
== null)
365 Error("Could not extract the file name from the URI.", false);
366 return (int)ExitCode
.BadFileName
;
370 // NOTE: Grab the temporary path setup for this process. If it is
371 // unavailable, we will not continue.
373 string directory
= Path
.GetTempPath();
375 if (String
.IsNullOrEmpty(directory
) ||
376 !Directory
.Exists(directory
))
378 Error("Temporary directory is invalid or unavailable.", false);
379 return (int)ExitCode
.BadTempPath
;
384 using (WebClient webClient
= new WebClient())
387 // NOTE: Create the event used to signal completion of the
390 doneEvent
= new ManualResetEvent(false);
393 // NOTE: Hookup the event handlers we care about on the web
394 // client. These are necessary because the file is
395 // downloaded asynchronously.
397 webClient
.DownloadProgressChanged
+=
398 new DownloadProgressChangedEventHandler(
399 DownloadProgressChanged
);
401 webClient
.DownloadFileCompleted
+=
402 new AsyncCompletedEventHandler(
403 DownloadFileCompleted
);
406 // NOTE: Build the fully qualified path and file name,
407 // within the temporary directory, where the file to
408 // be downloaded will be saved.
410 fileName
= Path
.Combine(directory
, fileName
);
413 // NOTE: If the file name already exists (in the temporary)
414 // directory, delete it.
416 // TODO: Perhaps an error should be raised here instead?
418 if (File
.Exists(fileName
))
419 File
.Delete(fileName
);
422 // NOTE: After kicking off the asynchronous file download
423 // process, wait [forever] until the "done" event is
427 "Downloading \"{0}\" to \"{1}\"...", uri
, fileName
);
429 webClient
.DownloadFileAsync(uri
, fileName
);
435 return (int)exitCode
;
441 // NOTE: An exception was caught. Report it via the console
442 // and return failure.
444 Error(e
.ToString(), false);
445 return (int)ExitCode
.Exception
;