mirror of
				https://github.com/sqlite/sqlite.git
				synced 2025-10-30 07:05:46 +03:00 
			
		
		
		
	
		
			
				
	
	
		
			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
 | |
|     }
 | |
| }
 |