source: branches/eraser6/CodeReview/Eraser/Program.cs @ 1525

Revision 1525, 20.8 KB checked in by lowjoel, 4 years ago (diff)

Being perfectionist about the order of the using directives.

  • Property svn:eol-style set to native
  • Property svn:keywords set to Id
Line 
1/*
2 * $Id$
3 * Copyright 2008-2010 The Eraser Project
4 * Original Author: Joel Low <lowjoel@users.sourceforge.net>
5 * Modified By:
6 *
7 * This file is part of Eraser.
8 *
9 * Eraser is free software: you can redistribute it and/or modify it under the
10 * terms of the GNU General Public License as published by the Free Software
11 * Foundation, either version 3 of the License, or (at your option) any later
12 * version.
13 *
14 * Eraser is distributed in the hope that it will be useful, but WITHOUT ANY
15 * WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR
16 * A PARTICULAR PURPOSE. See the GNU General Public License for more details.
17 *
18 * A copy of the GNU General Public License can be found at
19 * <http://www.gnu.org/licenses/>.
20 */
21
22using System;
23using System.Collections.Generic;
24using System.Windows.Forms;
25
26using System.IO;
27using System.Threading;
28using System.Globalization;
29using System.ComponentModel;
30using System.Runtime.Serialization;
31using System.Security.Principal;
32using System.Text.RegularExpressions;
33
34using System.Reflection;
35using System.Diagnostics;
36
37using ComLib.Arguments;
38
39using Eraser.Manager;
40using Eraser.Util;
41using File = System.IO.File;
42
43namespace Eraser
44{
45    internal static partial class Program
46    {
47        /// <summary>
48        /// The common program arguments shared between the GUI and console programs.
49        /// </summary>
50        class Arguments
51        {
52            /// <summary>
53            /// True if the program should not be started with any user-visible interfaces.
54            /// </summary>
55            /// <remarks>Errors will also be silently ignored.</remarks>
56            [Arg("quiet", "The program should not be started with any user-visible interfaces. " +
57                "Errors will be silently ignored.", typeof(bool), false, false, null)]
58            public bool Quiet { get; set; }
59        }
60
61        /// <summary>
62        /// Program arguments which only apply to the GUI program.
63        /// </summary>
64        class GuiArguments : Arguments
65        {
66            /// <summary>
67            /// True if the command line specified atRestart, which should result in the
68            /// queueing of tasks meant for running at restart.
69            /// </summary>
70            [Arg("atRestart", "The program should queue all tasks scheduled for running at " +
71                "the system restart.", typeof(bool), false, false, null)]
72            public bool AtRestart { get; set; }
73        }
74
75        class ConsoleArguments : Arguments
76        {
77            /// <summary>
78            /// The Action which this handler is in charge of.
79            /// </summary>
80            [Arg(0, "The action this command line is stating.", typeof(string), true, null, null)]
81            public string Action { get; set; }
82
83            /// <summary>
84            /// The list of command line parameters not placed in a switch.
85            /// </summary>
86            public List<string> PositionalArguments { get; set; }
87        }
88
89        class AddTaskArguments : ConsoleArguments
90        {
91            /// <summary>
92            /// The erasure method which the user specified on the command line.
93            /// </summary>
94            [Arg("method", "The erasure method to use", typeof(Guid), false, null, null)]
95            public Guid ErasureMethod { get; set; }
96
97            /// <summary>
98            /// The schedule for the current set of targets.
99            /// </summary>
100            [Arg("schedule", "The schedule to use", typeof(Schedule), true, null, null)]
101            public string Schedule { get; set; }
102        }
103
104        /// <summary>
105        /// The main entry point for the application.
106        /// </summary>
107        [STAThread]
108        static int Main(string[] commandLine)
109        {
110            //Immediately parse command line arguments
111            ComLib.BoolMessageItem argumentParser = Args.Parse(commandLine,
112                CommandLinePrefixes, CommandLineSeparators);
113            Args parsedArguments = (Args)argumentParser.Item;
114
115            //We default to a GUI if:
116            // - The parser did not succeed.
117            // - The parser resulted in an empty arguments list
118            // - The parser's argument at index 0 is not equal to the first argument (this
119            //   is when the user is passing GUI options -- command line options always
120            //   start with the action, e.g. Eraser help, or Eraser addtask
121            if (!argumentParser.Success || parsedArguments.IsEmpty ||
122                parsedArguments.Positional[0] != parsedArguments.Raw[0])
123            {
124                GUIMain(commandLine);
125            }
126            else
127            {
128                return CommandMain(commandLine);
129            }
130
131            //Return zero to signify success
132            return 0;
133        }
134
135        #region Console Program code
136        /// <summary>
137        /// Runs Eraser as a command-line application.
138        /// </summary>
139        /// <param name="commandLine">The command line parameters passed to Eraser.</param>
140        private static int CommandMain(string[] commandLine)
141        {
142            using (ConsoleProgram program = new ConsoleProgram(commandLine))
143            using (ManagerLibrary library = new ManagerLibrary(new Settings()))
144                try
145                {
146                    program.Handlers.Add("help",
147                        new ConsoleActionData(CommandHelp, new ConsoleArguments()));
148                    program.Handlers.Add("querymethods",
149                        new ConsoleActionData(CommandQueryMethods, new ConsoleArguments()));
150                    program.Handlers.Add("addtask",
151                        new ConsoleActionData(CommandAddTask, new AddTaskArguments()));
152                    program.Handlers.Add("importtasklist",
153                        new ConsoleActionData(CommandImportTaskList, new ConsoleArguments()));
154                    program.Run();
155                    return 0;
156                }
157                catch (UnauthorizedAccessException)
158                {
159                    return 5; //ERROR_ACCESS_DENIED
160                }
161                catch (Win32Exception e)
162                {
163                    Console.WriteLine(e.Message);
164                    return e.ErrorCode;
165                }
166                catch (Exception e)
167                {
168                    Console.WriteLine(e.Message);
169                    return 1;
170                }
171        }
172
173        /// <summary>
174        /// Prints the command line help for Eraser.
175        /// </summary>
176        private static void PrintCommandHelp()
177        {
178            Console.WriteLine(@"usage: Eraser <action> <arguments>
179where action is
180    help                    Show this help message.
181    addtask                 Adds tasks to the current task list.
182    querymethods            Lists all registered Erasure methods.
183    importtasklist          Imports an Eraser Task list to the curren user's Task List.
184
185global parameters:
186    --quiet, -q             Do not create a Console window to display progress.
187
188parameters for help:
189    eraser help
190
191    no parameters to set.
192
193parameters for addtask:
194    eraser addtask [--method=<methodGUID>] [--schedule=(now|manually|restart)] (recyclebin | unused=<volume> | dir=<directory> | file=<file>)[...]
195
196    --method                The Erasure method to use.
197    --schedule              The schedule the task will follow. The value must
198                            be one of:
199            now             The task will be queued for immediate execution.
200            manually        The task will be created but not queued for execution.
201            restart         The task will be queued for execution when the
202                            computer is next restarted.
203                            This parameter defaults to now.
204    recyclebin              Erases files and folders in the recycle bin
205    unused                  Erases unused space in the volume.
206        optional arguments: unused=<drive>[,clusterTips[=(true|false)]]
207            clusterTips     If specified, the drive's files will have their
208                            cluster tips erased.
209    dir                     Erases files and folders in the directory
210        optional arguments: dir=<directory>[,-excludeMask][,+includeMask][,deleteIfEmpty]
211            excludeMask     A wildcard expression for files and folders to
212                            exclude.
213            includeMask     A wildcard expression for files and folders to
214                            include.
215                            The include mask is applied before the exclude
216                            mask.
217            deleteIfEmpty   Deletes the folder at the end of the erasure if it
218                            is empty.
219    file                    Erases the specified file
220
221parameters for querymethods:
222    eraser querymethods
223
224    no parameters to set.
225
226parameters for importtasklist:
227    eraser importtasklist (file)[...]
228
229    [file]                  A list of one or more files to import.
230
231All arguments are case sensitive.");
232            Console.Out.Flush();
233        }
234
235        /// <summary>
236        /// Prints the help text for Eraser (with copyright)
237        /// </summary>
238        /// <param name="arguments">Not used.</param>
239        private static void CommandHelp(ConsoleArguments arguments)
240        {
241            Console.WriteLine(@"Eraser {0}
242(c) 2008-2010 The Eraser Project
243Eraser is Open-Source Software: see http://eraser.heidi.ie/ for details.
244", Assembly.GetExecutingAssembly().GetName().Version);
245
246            PrintCommandHelp();
247        }
248
249        /// <summary>
250        /// Lists all registered erasure methods.
251        /// </summary>
252        /// <param name="arguments">Not used.</param>
253        private static void CommandQueryMethods(ConsoleArguments arguments)
254        {
255            //Output the header
256            const string methodFormat = "{0,-2} {1,-39} {2}";
257            Console.WriteLine(methodFormat, "", "Method", "GUID");
258            Console.WriteLine(new string('-', 79));
259
260            //Refresh the list of erasure methods
261            Dictionary<Guid, ErasureMethod> methods = ErasureMethodManager.Items;
262            foreach (ErasureMethod method in methods.Values)
263            {
264                Console.WriteLine(methodFormat, (method is UnusedSpaceErasureMethod) ?
265                    "U" : "", method.Name, method.Guid.ToString());
266            }
267        }
268
269        /// <summary>
270        /// Parses the command line for tasks and adds them using the
271        /// <see cref="RemoteExecutor"/> class.
272        /// </summary>
273        /// <param name="arg">The command line parameters passed to the program.</param>
274        private static void CommandAddTask(ConsoleArguments arg)
275        {
276            AddTaskArguments arguments = (AddTaskArguments)arg;
277
278            //Create the task then set the method as well as schedule
279            Task task = new Task();
280            ErasureMethod method = arguments.ErasureMethod == Guid.Empty ?
281                ErasureMethodManager.Default :
282                ErasureMethodManager.GetInstance(arguments.ErasureMethod);
283            switch (arguments.Schedule.ToLowerInvariant())
284            {
285                case "now":
286                    task.Schedule = Schedule.RunNow;
287                    break;
288                case "manually":
289                    task.Schedule = Schedule.RunManually;
290                    break;
291                case "restart":
292                    task.Schedule = Schedule.RunOnRestart;
293                    break;
294                default:
295                    throw new ArgumentException(string.Format(
296                        "Unknown schedule type: {0}", arguments.Schedule), "--schedule");
297            }
298
299            //Parse the rest of the command line parameters as target expressions.
300            List<string> trueValues = new List<string>(new string[] { "yes", "true" });
301            string[] strings = new string[] {
302                //The recycle bin target
303                "(?<recycleBin>recyclebin)",
304
305                //The unused space erasure target, taking the optional clusterTips
306                //argument which defaults to true; if none is specified it's assumed
307                //false
308                "unused=(?<unusedVolume>.*)(?<unusedTips>,clusterTips(=(?<unusedTipsValue>true|false))?)?",
309
310                //The directory target, taking a list of + and - wildcard expressions.
311                "dir=(?<directoryName>.*)(?<directoryParams>(?<directoryExcludeMask>,-[^,]+)|(?<directoryIncludeMask>,\\+[^,]+)|(?<directoryDeleteIfEmpty>,deleteIfEmpty(=(?<directoryDeleteIfEmptyValue>true|false))?))*",
312
313                //The file target.
314                "file=(?<fileName>.*)"
315            };
316
317            Regex regex = new Regex(string.Join("|", strings),
318                RegexOptions.Compiled | RegexOptions.IgnoreCase | RegexOptions.RightToLeft);
319            foreach (string argument in arguments.PositionalArguments)
320            {
321                Match match = regex.Match(argument);
322                if (match.Captures.Count == 0)
323                {
324                    Console.WriteLine("Unknown argument: {0}, skipped.", argument);
325                    continue;
326                }
327
328                ErasureTarget target = null;
329                if (match.Groups["recycleBin"].Success)
330                {
331                    target = new RecycleBinTarget();
332                }
333                else if (match.Groups["unusedVolume"].Success)
334                {
335                    UnusedSpaceTarget unusedSpaceTarget = new UnusedSpaceTarget();
336                    target = unusedSpaceTarget;
337                    unusedSpaceTarget.Drive = match.Groups["unusedVolume"].Value;
338
339                    if (!match.Groups["unusedTips"].Success)
340                        unusedSpaceTarget.EraseClusterTips = false;
341                    else if (!match.Groups["unusedTipsValue"].Success)
342                        unusedSpaceTarget.EraseClusterTips = true;
343                    else
344                        unusedSpaceTarget.EraseClusterTips =
345                            trueValues.IndexOf(match.Groups["unusedTipsValue"].Value) != -1;
346                }
347                else if (match.Groups["directoryName"].Success)
348                {
349                    FolderTarget folderTarget = new FolderTarget();
350                    target = folderTarget;
351
352                    folderTarget.Path = match.Groups["directoryName"].Value;
353                    if (!match.Groups["directoryDeleteIfEmpty"].Success)
354                        folderTarget.DeleteIfEmpty = false;
355                    else if (!match.Groups["directoryDeleteIfEmptyValue"].Success)
356                        folderTarget.DeleteIfEmpty = true;
357                    else
358                        folderTarget.DeleteIfEmpty =
359                            trueValues.IndexOf(match.Groups["directoryDeleteIfEmptyValue"].Value) != -1;
360                    if (match.Groups["directoryExcludeMask"].Success)
361                        folderTarget.ExcludeMask += match.Groups["directoryExcludeMask"].Value.Remove(0, 2) + ' ';
362                    if (match.Groups["directoryIncludeMask"].Success)
363                        folderTarget.IncludeMask += match.Groups["directoryIncludeMask"].Value.Remove(0, 2) + ' ';
364                }
365                else if (match.Groups["fileName"].Success)
366                {
367                    FileTarget fileTarget = new FileTarget();
368                    target = fileTarget;
369                    fileTarget.Path = match.Groups["fileName"].Value;
370                }
371
372                if (target == null)
373                    continue;
374
375                target.Method = method;
376                task.Targets.Add(target);
377            }
378
379            //Check the number of tasks in the task.
380            if (task.Targets.Count == 0)
381                throw new ArgumentException("Tasks must contain at least one erasure target.");
382
383            //Send the task out.
384            try
385            {
386                using (RemoteExecutorClient client = new RemoteExecutorClient())
387                {
388                    client.Run();
389                    if (!client.IsConnected)
390                    {
391                        //The client cannot connect to the server. This probably means
392                        //that the server process isn't running. Start an instance.
393                        Process eraserInstance = Process.Start(
394                            Assembly.GetExecutingAssembly().Location, "--quiet");
395                        eraserInstance.WaitForInputIdle();
396
397                        client.Run();
398                        if (!client.IsConnected)
399                            throw new IOException("Eraser cannot connect to the running " +
400                                "instance for erasures.");
401                    }
402
403                    client.Tasks.Add(task);
404                }
405            }
406            catch (UnauthorizedAccessException e)
407            {
408                //We can't connect to the pipe because the other instance of Eraser
409                //is running with higher privileges than this instance.
410                throw new UnauthorizedAccessException("Another instance of Eraser " +
411                    "is already running but it is running with higher privileges than " +
412                    "this instance of Eraser. Tasks cannot be added in this manner.\n\n" +
413                    "Close the running instance of Eraser and start it again without " +
414                    "administrator privileges, or run the command again as an " +
415                    "administrator.", e);
416            }
417        }
418
419        /// <summary>
420        /// Imports the given tasklists and adds them to the global Eraser instance.
421        /// </summary>
422        /// <param name="args">The list of files specified on the command line.</param>
423        private static void CommandImportTaskList(ConsoleArguments args)
424        {
425            //Import the task list
426            try
427            {
428                using (RemoteExecutorClient client = new RemoteExecutorClient())
429                {
430                    client.Run();
431                    if (!client.IsConnected)
432                    {
433                        //The client cannot connect to the server. This probably means
434                        //that the server process isn't running. Start an instance.
435                        Process eraserInstance = Process.Start(
436                            Assembly.GetExecutingAssembly().Location, "--quiet");
437                        eraserInstance.WaitForInputIdle();
438
439                        client.Run();
440                        if (!client.IsConnected)
441                            throw new IOException("Eraser cannot connect to the running " +
442                                "instance for erasures.");
443                    }
444
445                    foreach (string path in args.PositionalArguments)
446                        using (FileStream stream = new FileStream(path, FileMode.Open, FileAccess.Read))
447                            client.Tasks.LoadFromStream(stream);
448                }
449            }
450            catch (UnauthorizedAccessException e)
451            {
452                //We can't connect to the pipe because the other instance of Eraser
453                //is running with higher privileges than this instance.
454                throw new UnauthorizedAccessException("Another instance of Eraser " +
455                    "is already running but it is running with higher privileges than " +
456                    "this instance of Eraser. Tasks cannot be added in this manner.\n\n" +
457                    "Close the running instance of Eraser and start it again without " +
458                    "administrator privileges, or run the command again as an " +
459                    "administrator.", e);
460            }
461        }
462        #endregion
463
464        #region GUI Program code
465        /// <summary>
466        /// Runs Eraser as a GUI application.
467        /// </summary>
468        /// <param name="commandLine">The command line parameters passed to Eraser.</param>
469        private static void GUIMain(string[] commandLine)
470        {
471            //Create a unique program instance ID for this user.
472            string instanceId = "Eraser-BAD0DAC6-C9EE-4acc-8701-C9B3C64BC65E-GUI-" +
473                WindowsIdentity.GetCurrent().User.ToString();
474
475            //Then initialise the instance and initialise the Manager library.
476            using (GuiProgram program = new GuiProgram(commandLine, instanceId))
477            using (ManagerLibrary library = new ManagerLibrary(new Settings()))
478            {
479                program.InitInstance += OnGUIInitInstance;
480                program.NextInstance += OnGUINextInstance;
481                program.ExitInstance += OnGUIExitInstance;
482                program.Run();
483            }
484        }
485
486        /// <summary>
487        /// Triggered when the Program is started for the first time.
488        /// </summary>
489        /// <param name="sender">The sender of the object.</param>
490        /// <param name="e">Event arguments.</param>
491        private static void OnGUIInitInstance(object sender, InitInstanceEventArgs e)
492        {
493            GuiProgram program = (GuiProgram)sender;
494            eraserClient = new RemoteExecutorServer();
495
496            //Set our UI language
497            EraserSettings settings = EraserSettings.Get();
498            Thread.CurrentThread.CurrentUICulture = new CultureInfo(settings.Language);
499            Application.SafeTopLevelCaptionFormat = S._("Eraser");
500
501            //Load the task list
502            SettingsCompatibility.Execute();
503            try
504            {
505                if (File.Exists(TaskListPath))
506                {
507                    using (FileStream stream = new FileStream(TaskListPath, FileMode.Open,
508                        FileAccess.Read, FileShare.Read))
509                    {
510                        eraserClient.Tasks.LoadFromStream(stream);
511                    }
512                }
513            }
514            catch (SerializationException ex)
515            {
516                System.IO.File.Delete(TaskListPath);
517                MessageBox.Show(S._("Could not load task list. All task entries have " +
518                    "been lost. The error returned was: {0}", ex.Message), S._("Eraser"),
519                    MessageBoxButtons.OK, MessageBoxIcon.Error,
520                    MessageBoxDefaultButton.Button1,
521                    S.IsRightToLeft(null) ? MessageBoxOptions.RtlReading : 0);
522            }
523
524            //Create the main form
525            program.MainForm = new MainForm();
526
527            //Decide whether to display any UI
528            GuiArguments arguments = new GuiArguments();
529            Args.Parse(program.CommandLine, CommandLinePrefixes, CommandLineSeparators, arguments);
530            e.ShowMainForm = !arguments.AtRestart && !arguments.Quiet;
531
532            //Queue tasks meant for running at restart if we are given that command line.
533            if (arguments.AtRestart)
534                eraserClient.QueueRestartTasks();
535
536            //Run the eraser client.
537            eraserClient.Run();
538        }
539
540        /// <summary>
541        /// Triggered when a second instance of Eraser is started.
542        /// </summary>
543        /// <param name="sender">The sender of the event.</param>
544        /// <param name="e">Event argument.</param>
545        private static void OnGUINextInstance(object sender, NextInstanceEventArgs e)
546        {
547            //Another instance of the GUI Program has been started: show the main window
548            //now as we still do not have a facility to handle the command line arguments.
549            GuiProgram program = (GuiProgram)sender;
550
551            //Invoke the function if we aren't on the main thread
552            if (program.MainForm.InvokeRequired)
553            {
554                program.MainForm.Invoke(
555                    (GuiProgram.NextInstanceEventHandler)OnGUINextInstance,
556                    sender, e);
557                return;
558            }
559
560            program.MainForm.Show();
561        }
562
563        /// <summary>
564        /// Triggered when the first instance of Eraser is exited.
565        /// </summary>
566        /// <param name="sender">The sender of the event.</param>
567        /// <param name="e">Event argument.</param>
568        private static void OnGUIExitInstance(object sender, EventArgs e)
569        {
570            //Save the task list
571            if (!Directory.Exists(Program.AppDataPath))
572                Directory.CreateDirectory(Program.AppDataPath);
573            using (FileStream stream = new FileStream(TaskListPath, FileMode.Create,
574                FileAccess.Write, FileShare.None))
575            {
576                eraserClient.Tasks.SaveToStream(stream);
577            }
578
579            //Dispose the eraser executor instance
580            eraserClient.Dispose();
581        }
582        #endregion
583
584        /// <summary>
585        /// The acceptable list of command line prefixes we will accept.
586        /// </summary>
587        public const string CommandLinePrefixes = "(/|-|--)";
588
589        /// <summary>
590        /// The acceptable list of command line separators we will accept.
591        /// </summary>
592        public const string CommandLineSeparators = "(:|=)";
593
594        /// <summary>
595        /// The global Executor instance.
596        /// </summary>
597        public static Executor eraserClient;
598
599        /// <summary>
600        /// Path to the Eraser application data path.
601        /// </summary>
602        public static readonly string AppDataPath = Path.Combine(Environment.GetFolderPath(
603            Environment.SpecialFolder.LocalApplicationData), @"Eraser 6");
604
605        /// <summary>
606        /// File name of the Eraser task list.
607        /// </summary>
608        private const string TaskListFileName = @"Task List.ersx";
609
610        /// <summary>
611        /// Path to the Eraser task list.
612        /// </summary>
613        public static readonly string TaskListPath = Path.Combine(AppDataPath, TaskListFileName);
614
615        /// <summary>
616        /// Path to the Eraser settings key (relative to HKCU)
617        /// </summary>
618        public const string SettingsPath = @"SOFTWARE\Eraser\Eraser 6";
619    }
620}
Note: See TracBrowser for help on using the repository browser.