Team Ai
Modelpublic

AryaWu/sqlite

sourceHugging Faceupdated 10mo agoView on Hugging Face
0likes
GetFile.cs472 linesDownload Raw Back to tool
1/*2** 2015 October 73**4** The author disclaims copyright to this source code.  In place of5** a legal notice, here is a blessing:6**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.10**11*************************************************************************12** This file contains C# code to download a single file based on a URI.13*/14
15using System;
16using System.ComponentModel;
17using System.Diagnostics;
18using System.IO;
19using System.Net;
20using System.Reflection;
21using System.Runtime.InteropServices;
22using System.Threading;
23
24///////////////////////////////////////////////////////////////////////////////
25
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.*")]
35
36#if DEBUG
37[assembly: AssemblyConfiguration("Debug")]
38#else
39[assembly: AssemblyConfiguration("Release")]
40#endif
41#endregion
42
43///////////////////////////////////////////////////////////////////////////////
44
45namespace GetFile
46{
47    /// <summary>
48    /// This enumeration is used to represent all the possible exit codes from
49    /// this tool.
50    /// </summary>
51    internal enum ExitCode
52    {
53        /// <summary>
54        /// The file download was a success.
55        /// </summary>
56        Success = 0,
57
58        /// <summary>
59        /// The command line arguments are missing (i.e. null).  Generally,
60        /// this should not happen.
61        /// </summary>
62        MissingArgs = 1,
63
64        /// <summary>
65        /// The wrong number of command line arguments was supplied.
66        /// </summary>
67        WrongNumArgs = 2,
68
69        /// <summary>
70        /// The URI specified on the command line could not be parsed as a
71        /// supported absolute URI.
72        /// </summary>
73        BadUri = 3,
74
75        /// <summary>
76        /// The file name portion of the URI specified on the command line
77        /// could not be extracted from it.
78        /// </summary>
79        BadFileName = 4,
80
81        /// <summary>
82        /// The temporary directory is either invalid (i.e. null) or does not
83        /// represent an available directory.
84        /// </summary>
85        BadTempPath = 5,
86
87        /// <summary>
88        /// An exception was caught in <see cref="Main" />.  Generally, this
89        /// should not happen.
90        /// </summary>
91        Exception = 6,
92
93        /// <summary>
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.
97        /// </summary>
98        DownloadCanceled = 7,
99
100        /// <summary>
101        /// The file download encountered an error.  Further information about
102        /// this error should be displayed on the console.
103        /// </summary>
104        DownloadError = 8
105    }
106
107    ///////////////////////////////////////////////////////////////////////////
108
109    internal static class Program
110    {
111        #region Private Data
112        /// <summary>
113        /// This is used to synchronize multithreaded access to the
114        /// <see cref="previousPercent" /> and <see cref="exitCode"/>
115        /// fields.
116        /// </summary>
117        private static readonly object syncRoot = new object();
118
119        ///////////////////////////////////////////////////////////////////////
120
121        /// <summary>
122        /// This event will be signed when the file download has completed,
123        /// even if the file download itself was canceled or unsuccessful.
124        /// </summary>
125        private static EventWaitHandle doneEvent;
126
127        ///////////////////////////////////////////////////////////////////////
128
129        /// <summary>
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.
133        /// </summary>
134        private static int previousPercent = 0;
135
136        ///////////////////////////////////////////////////////////////////////
137
138        /// <summary>
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.
142        /// </summary>
143        private static ExitCode exitCode = ExitCode.Success;
144        #endregion
145
146        ///////////////////////////////////////////////////////////////////////
147
148        #region Private Support Methods
149        /// <summary>
150        /// This method displays an error message to the console and/or
151        /// displays the command line usage information for this tool.
152        /// </summary>
153        /// <param name="message">
154        /// The error message to display, if any.
155        /// </param>
156        /// <param name="usage">
157        /// Non-zero to display the command line usage information.
158        /// </param>
159        private static void Error(
160            string message,
161            bool usage
162            )
163        {
164            if (message != null)
165                Console.WriteLine(message);
166
167            string fileName = Path.GetFileName(
168                Process.GetCurrentProcess().MainModule.FileName);
169
170            Console.WriteLine(String.Format(
171                "usage: {0} <uri> [fileName]", fileName));
172        }
173
174        ///////////////////////////////////////////////////////////////////////
175
176        /// <summary>
177        /// This method attempts to determine the file name portion of the
178        /// specified URI.
179        /// </summary>
180        /// <param name="uri">
181        /// The URI to process.
182        /// </param>
183        /// <returns>
184        /// The file name portion of the specified URI -OR- null if it cannot
185        /// be determined.
186        /// </returns>
187        private static string GetFileName(
188            Uri uri
189            )
190        {
191            if (uri == null)
192                return null;
193
194            string pathAndQuery = uri.PathAndQuery;
195
196            if (String.IsNullOrEmpty(pathAndQuery))
197                return null;
198
199            int index = pathAndQuery.LastIndexOf('/');
200
201            if ((index < 0) || (index == pathAndQuery.Length))
202                return null;
203
204            return pathAndQuery.Substring(index + 1);
205        }
206        #endregion
207
208        ///////////////////////////////////////////////////////////////////////
209
210        #region Private Event Handlers
211        /// <summary>
212        /// This method is an event handler that is called when the file
213        /// download completion percentage changes.  It will display progress
214        /// on the console.  Special care is taken to make sure that progress
215        /// events are not displayed out-of-order, even if duplicate and/or
216        /// out-of-order events are received.
217        /// </summary>
218        /// <param name="sender">
219        /// The source of the event.
220        /// </param>
221        /// <param name="e">
222        /// Information for the event being processed.
223        /// </param>
224        private static void DownloadProgressChanged(
225            object sender,
226            DownloadProgressChangedEventArgs e
227            )
228        {
229            if (e != null)
230            {
231                int percent = e.ProgressPercentage;
232
233                lock (syncRoot)
234                {
235                    if (percent > previousPercent)
236                    {
237                        Console.Write('.');
238
239                        if ((percent % 10) == 0)
240                            Console.Write(" {0}% ", percent);
241
242                        previousPercent = percent;
243                    }
244                }
245            }
246        }
247
248        ///////////////////////////////////////////////////////////////////////
249
250        /// <summary>
251        /// This method is an event handler that is called when the file
252        /// download has completed, successfully or otherwise.  It will
253        /// display the overall result of the file download on the console,
254        /// including any <see cref="Exception" /> information, if applicable.
255        /// The <see cref="exitCode" /> field is changed by this method to
256        /// indicate the overall result of the file download and the event
257        /// within the <see cref="doneEvent" /> field will be signaled.
258        /// </summary>
259        /// <param name="sender">
260        /// The source of the event.
261        /// </param>
262        /// <param name="e">
263        /// Information for the event being processed.
264        /// </param>
265        private static void DownloadFileCompleted(
266            object sender,
267            AsyncCompletedEventArgs e
268            )
269        {
270            if (e != null)
271            {
272                lock (syncRoot)
273                {
274                    if (previousPercent < 100)
275                        Console.Write(' ');
276                }
277
278                if (e.Cancelled)
279                {
280                    Console.WriteLine("Canceled");
281
282                    lock (syncRoot)
283                    {
284                        exitCode = ExitCode.DownloadCanceled;
285                    }
286                }
287                else
288                {
289                    Exception error = e.Error;
290
291                    if (error != null)
292                    {
293                        Console.WriteLine("Error: {0}", error);
294
295                        lock (syncRoot)
296                        {
297                            exitCode = ExitCode.DownloadError;
298                        }
299                    }
300                    else
301                    {
302                        Console.WriteLine("Done");
303                    }
304                }
305            }
306
307            if (doneEvent != null)
308                doneEvent.Set();
309        }
310        #endregion
311
312        ///////////////////////////////////////////////////////////////////////
313
314        #region Program Entry Point
315        /// <summary>
316        /// This is the entry-point for this tool.  It handles processing the
317        /// command line arguments, setting up the web client, downloading the
318        /// file, and saving it to the file system.
319        /// </summary>
320        /// <param name="args">
321        /// The command line arguments.
322        /// </param>
323        /// <returns>
324        /// Zero upon success; non-zero on failure.  This will be one of the
325        /// values from the <see cref="ExitCode" /> enumeration.
326        /// </returns>
327        private static int Main(
328            string[] args
329            )
330        {
331            //
332            // NOTE: Sanity check the command line arguments.
333            //
334            if (args == null)
335            {
336                Error(null, true);
337                return (int)ExitCode.MissingArgs;
338            }
339
340            if ((args.Length < 1) || (args.Length > 2))
341            {
342                Error(null, true);
343                return (int)ExitCode.WrongNumArgs;
344            }
345
346            //
347            // NOTE: Attempt to convert the first (and only) command line
348            //       argument to an absolute URI.
349            //
350            Uri uri;
351
352            if (!Uri.TryCreate(args[0], UriKind.Absolute, out uri))
353            {
354                Error("Could not create absolute URI from argument.", false);
355                return (int)ExitCode.BadUri;
356            }
357
358            //
359            // NOTE: If a file name was specified on the command line, try to
360            //       use it (without its directory name); otherwise, fallback
361            //       to using the file name portion of the URI.
362            //
363            string fileName = (args.Length == 2) ?
364                Path.GetFileName(args[1]) : null;
365
366            if (String.IsNullOrEmpty(fileName))
367            {
368                //
369                // NOTE: Attempt to extract the file name portion of the URI
370                //       we just created.
371                //
372                fileName = GetFileName(uri);
373
374                if (fileName == null)
375                {
376                    Error("Could not extract file name from URI.", false);
377                    return (int)ExitCode.BadFileName;
378                }
379            }
380
381            //
382            // NOTE: Grab the temporary path setup for this process.  If it is
383            //       unavailable, we will not continue.
384            //
385            string directory = Path.GetTempPath();
386
387            if (String.IsNullOrEmpty(directory) ||
388                !Directory.Exists(directory))
389            {
390                Error("Temporary directory is invalid or unavailable.", false);
391                return (int)ExitCode.BadTempPath;
392            }
393
394            try
395            {
396                //
397                // HACK: For use of the TLS 1.2 security protocol because some
398                //       web servers fail without it.  In order to support the
399                //       .NET Framework 2.0+ at compilation time, must use its
400                //       integer constant here.
401                //
402                ServicePointManager.SecurityProtocol =
403                    (SecurityProtocolType)0xC00;
404
405                using (WebClient webClient = new WebClient())
406                {
407                    //
408                    // NOTE: Create the event used to signal completion of the
409                    //       file download.
410                    //
411                    doneEvent = new ManualResetEvent(false);
412
413                    //
414                    // NOTE: Hookup the event handlers we care about on the web
415                    //       client.  These are necessary because the file is
416                    //       downloaded asynchronously.
417                    //
418                    webClient.DownloadProgressChanged +=
419                        new DownloadProgressChangedEventHandler(
420                            DownloadProgressChanged);
421
422                    webClient.DownloadFileCompleted +=
423                        new AsyncCompletedEventHandler(
424                            DownloadFileCompleted);
425
426                    //
427                    // NOTE: Build the fully qualified path and file name,
428                    //       within the temporary directory, where the file to
429                    //       be downloaded will be saved.
430                    //
431                    fileName = Path.Combine(directory, fileName);
432
433                    //
434                    // NOTE: If the file name already exists (in the temporary)
435                    //       directory, delete it.
436                    //
437                    // TODO: Perhaps an error should be raised here instead?
438                    //
439                    if (File.Exists(fileName))
440                        File.Delete(fileName);
441
442                    //
443                    // NOTE: After kicking off the asynchronous file download
444                    //       process, wait [forever] until the "done" event is
445                    //       signaled.
446                    //
447                    Console.WriteLine(
448                        "Downloading \"{0}\" to \"{1}\"...", uri, fileName);
449
450                    webClient.DownloadFileAsync(uri, fileName);
451                    doneEvent.WaitOne();
452                }
453
454                lock (syncRoot)
455                {
456                    return (int)exitCode;
457                }
458            }
459            catch (Exception e)
460            {
461                //
462                // NOTE: An exception was caught.  Report it via the console
463                //       and return failure.
464                //
465                Error(e.ToString(), false);
466                return (int)ExitCode.Exception;
467            }
468        }
469        #endregion
470    }
471}
472