source: branches/eraser6/Eraser/Program.cs @ 782

Revision 782, 37.1 KB checked in by lowjoel, 6 years ago (diff)

Documented the event handlers

  • Property svn:keywords set to Id
Line 
1/*
2 * $Id$
3 * Copyright 2008 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.IO.Pipes;
28using System.Text;
29using System.Threading;
30using System.Runtime.Serialization.Formatters.Binary;
31using System.Globalization;
32using System.Reflection;
33using System.Diagnostics;
34using System.ComponentModel;
35
36using Eraser.Manager;
37using Eraser.Util;
38using Microsoft.Win32;
39
40namespace Eraser
41{
42    static class Program
43    {
44        /// <summary>
45        /// The main entry point for the application.
46        /// </summary>
47        [STAThread]
48        static int Main(string[] commandLine)
49        {
50            //Trivial case: no command parameters
51            if (commandLine.Length == 0)
52                GUIMain(commandLine);
53
54            //Determine if the sole parameter is --restart; if it is, start the GUI
55            //passing isRestart as true. Otherwise, we're a console application.
56            else if (commandLine.Length == 1)
57            {
58                if (commandLine[0] == "--atRestart" || commandLine[0] == "--quiet")
59                {
60                    GUIMain(commandLine);
61                }
62                else
63                {
64                    return CommandMain(commandLine);
65                }
66            }
67
68            //The other trivial case: definitely a console application.
69            else
70                return CommandMain(commandLine);
71
72            //No error.
73            return 0;
74        }
75
76        /// <summary>
77        /// Runs Eraser as a command-line application.
78        /// </summary>
79        /// <param name="commandLine">The command line parameters passed to Eraser.</param>
80        private static int CommandMain(string[] commandLine)
81        {
82            //True if the user specified a quiet command.
83            bool isQuiet = false;
84
85            try
86            {
87                CommandLineProgram program = new CommandLineProgram(commandLine);
88                isQuiet = program.Arguments.Quiet;
89
90                using (ManagerLibrary library = new ManagerLibrary(new Settings()))
91                    program.Run();
92
93                return 0;
94            }
95            catch (Win32Exception e)
96            {
97                Console.WriteLine(e.Message);
98                return e.ErrorCode;
99            }
100            catch (Exception e)
101            {
102                Console.WriteLine(e.Message);
103                return 1;
104            }
105            finally
106            {
107                //Flush the buffered output to the console
108                Console.Out.Flush();
109
110                //Don't ask for a key to press if the user specified Quiet
111                if (!isQuiet)
112                {
113                    Console.Write("\nPress enter to continue . . . ");
114                    Console.Out.Flush();
115                    Console.ReadLine();
116                }
117
118                KernelAPI.FreeConsole();
119            }
120        }
121
122        /// <summary>
123        /// Runs Eraser as a GUI application.
124        /// </summary>
125        /// <param name="commandLine">The command line parameters passed to Eraser.</param>
126        private static void GUIMain(string[] commandLine)
127        {
128            //Create the program instance
129            GUIProgram program = new GUIProgram(commandLine, "Eraser-BAD0DAC6-C9EE-4acc-" +
130                "8701-C9B3C64BC65E-GUI-" +
131                System.Security.Principal.WindowsIdentity.GetCurrent().User.ToString());
132
133            //Then run the program instance.
134            using (ManagerLibrary library = new ManagerLibrary(new Settings()))
135            {
136                program.InitInstance += OnGUIInitInstance;
137                program.NextInstance += OnGUINextInstance;
138                program.ExitInstance += OnGUIExitInstance;
139                program.Run();
140            }
141        }
142
143        /// <summary>
144        /// Triggered when the Program is started for the first time.
145        /// </summary>
146        /// <param name="sender">The sender of the object.</param>
147        /// <returns>True if the user did not specify --quiet, false otherwise.</returns>
148        private static bool OnGUIInitInstance(object sender)
149        {
150            GUIProgram program = (GUIProgram)sender;
151            using (eraserClient = new RemoteExecutorServer())
152            {
153                //Set our UI language
154                EraserSettings settings = new EraserSettings();
155                System.Threading.Thread.CurrentThread.CurrentUICulture =
156                    new CultureInfo(settings.Language);
157                program.SafeTopLevelCaptionFormat = S._("Eraser");
158
159                //Load the task list
160                if (settings.TaskList != null)
161                    using (MemoryStream stream = new MemoryStream(settings.TaskList))
162                        try
163                        {
164                            eraserClient.LoadTaskList(stream);
165                        }
166                        catch (Exception)
167                        {
168                            settings.TaskList = null;
169                            MessageBox.Show(S._("Could not load task list. All task entries have " +
170                                "been lost."), S._("Eraser"), MessageBoxButtons.OK,
171                                MessageBoxIcon.Error);
172                        }
173
174                //Create the main form
175                program.MainForm = new MainForm();
176                bool showMainForm = true;
177                foreach (string param in program.CommandLine)
178                {
179                    //Run tasks which are meant to be run on restart
180                    switch (param)
181                    {
182                        case "--atRestart":
183                            eraserClient.QueueRestartTasks();
184                            goto case "--quiet";
185
186                        //Hide the main form if the user specified the quiet command
187                        //line
188                        case "--quiet":
189                            showMainForm = false;
190                            break;
191                    }
192                }
193
194                //Run the eraser client.
195                eraserClient.Run();
196                return showMainForm;
197            }
198        }
199
200        /// <summary>
201        /// Triggered when a second instance of Eraser is started.
202        /// </summary>
203        /// <param name="sender">The sender of the event.</param>
204        private static void OnGUINextInstance(object sender)
205        {
206            //Another instance of the GUI Program has been started: show the main window
207            //now as we still do not have a facility to handle the command line arguments.
208            GUIProgram program = (GUIProgram)sender;
209
210            //Invoke the function if we aren't on the main thread
211            if (program.MainForm.InvokeRequired)
212            {
213                program.MainForm.Invoke(new GUIProgram.NextInstanceFunction(
214                    OnGUINextInstance), new object[] { sender });
215                return;
216            }
217
218            program.MainForm.Visible = true;
219        }
220
221        /// <summary>
222        /// Triggered when the first instance of Eraser is exited.
223        /// </summary>
224        /// <param name="sender">The sender of the event.</param>
225        private static void OnGUIExitInstance(object sender)
226        {
227            //Save the task list
228            EraserSettings settings = new EraserSettings();
229            using (MemoryStream stream = new MemoryStream())
230            {
231                eraserClient.SaveTaskList(stream);
232                settings.TaskList = stream.ToArray();
233            }
234        }
235
236        /// <summary>
237        /// The global Executor instance.
238        /// </summary>
239        public static Executor eraserClient;
240    }
241
242    class GUIProgram
243    {
244        /// <summary>
245        /// Constructor.
246        /// </summary>
247        /// <param name="commandLine">The command line arguments associated with
248        /// this program launch</param>
249        /// <param name="instanceID">The instance ID of the program, used to group
250        /// instances of the program together.</param>
251        public GUIProgram(string[] commandLine, string instanceID)
252        {
253            Application.EnableVisualStyles();
254            Application.SetCompatibleTextRenderingDefault(false);
255            this.instanceID = instanceID;
256            this.commandLine = commandLine;
257
258            //Check if there already is another instance of the program.
259            globalMutex = new Mutex(true, instanceID, out isFirstInstance);
260        }
261
262        /// <summary>
263        /// Runs the event loop of the GUI program, returning true if the program
264        /// was started as there were no other instances of the program, or false
265        /// if other instances were found.
266        /// </summary>
267        /// <remarks>
268        /// This function must always be called in your program, regardless
269        /// of the value of <see cref="IsAlreadyRunning"/>. If this function is not
270        /// called, the first instance will never be notified that another was started.
271        /// </remarks>
272        /// <returns>True if the application was started, or false if another instance
273        /// was detected.</returns>
274        public bool Run()
275        {
276            //If no other instances are running, set up our pipe server so clients
277            //can connect and give us subsequent command lines.
278            if (isFirstInstance)
279            {
280                try
281                {
282                    //Create the pipe server which will handle connections to us
283                    pipeServer = new Thread(ServerMain);
284                    pipeServer.Start();
285
286                    //Initialise and run the program.
287                    if (OnInitInstance(this) && MainForm != null)
288                        Application.Run(mainForm);
289                    else
290                        Application.Run();
291                    return true;
292                }
293                finally
294                {
295                    pipeServer.Abort();
296                }
297            }
298
299            //Another instance of the program is running. Connect to it and transfer
300            //the command line arguments
301            else
302            {
303                try
304                {
305                    NamedPipeClientStream client = new NamedPipeClientStream(".", instanceID,
306                        PipeDirection.Out);
307                    client.Connect(500);
308
309                    StringBuilder commandLineStr = new StringBuilder(commandLine.Length * 64);
310                    foreach (string param in commandLine)
311                        commandLineStr.Append(string.Format("{0}\0", param));
312
313                    byte[] buffer = new byte[commandLineStr.Length];
314                    int count = Encoding.UTF8.GetBytes(commandLineStr.ToString(),0,
315                        commandLineStr.Length, buffer, 0);
316                    client.Write(buffer, 0, buffer.Length);
317                }
318                catch (TimeoutException)
319                {
320                    //Can't do much: half a second is a reasonably long time to wait.
321                }
322                return false;
323            }
324        }
325
326        /// <summary>
327        /// Holds information required for an asynchronous call to
328        /// NamedPipeServerStream.BeginWaitForConnection.
329        /// </summary>
330        private struct ServerAsyncInfo
331        {
332            public NamedPipeServerStream Server;
333            public AutoResetEvent WaitHandle;
334        }
335
336        /// <summary>
337        /// Runs a background thread, monitoring for new connections to the server.
338        /// </summary>
339        private void ServerMain()
340        {
341            while (pipeServer.ThreadState != System.Threading.ThreadState.AbortRequested)
342            {
343                using (NamedPipeServerStream server = new NamedPipeServerStream(instanceID,
344                    PipeDirection.In, 1, PipeTransmissionMode.Message, PipeOptions.Asynchronous))
345                {
346                    ServerAsyncInfo async = new ServerAsyncInfo();
347                    async.Server = server;
348                    async.WaitHandle = new AutoResetEvent(false);
349                    IAsyncResult result = server.BeginWaitForConnection(WaitForConnection, async);
350
351                    //Wait for the operation to complete.
352                    if (result.AsyncWaitHandle.WaitOne())
353                        //It completed. Wait for the processing to complete.
354                        async.WaitHandle.WaitOne();
355                }
356            }
357        }
358
359        /// <summary>
360        /// Waits for new connections to be made to the server.
361        /// </summary>
362        /// <param name="result"></param>
363        private void WaitForConnection(IAsyncResult result)
364        {
365            ServerAsyncInfo async = (ServerAsyncInfo)result.AsyncState;
366
367            try
368            {
369                //We're done waiting for the connection
370                async.Server.EndWaitForConnection(result);
371
372                //Process the connection if the server was successfully connected.
373                if (async.Server.IsConnected)
374                {
375                    //Read the message from the secondary instance
376                    byte[] buffer = new byte[8192];
377                    string message = string.Empty;
378
379                    do
380                    {
381                        int lastRead = async.Server.Read(buffer, 0, buffer.Length);
382                        message += Encoding.UTF8.GetString(buffer, 0, lastRead);
383                    }
384                    while (!async.Server.IsMessageComplete);
385
386                    //Let the event handler process the message.
387                    OnNextInstance(this, message);
388                }
389            }
390            catch (ObjectDisposedException)
391            {
392            }
393            finally
394            {
395                //Reset the wait event
396                async.WaitHandle.Set();
397            }
398        }
399
400        /// <summary>
401        /// Gets or sets the format string to apply to top-level window captions when
402        /// they are displayed with a warning banner.
403        /// </summary>
404        public string SafeTopLevelCaptionFormat
405        {
406            get
407            {
408                return Application.SafeTopLevelCaptionFormat;
409            }
410            set
411            {
412                Application.SafeTopLevelCaptionFormat = value;
413            }
414        }
415
416        /// <summary>
417        /// Gets the command line arguments this instance was started with.
418        /// </summary>
419        public string[] CommandLine
420        {
421            get
422            {
423                return commandLine;
424            }
425        }
426
427        /// <summary>
428        /// Gets whether another instance of the program is already running.
429        /// </summary>
430        public bool IsAlreadyRunning
431        {
432            get
433            {
434                return isFirstInstance;
435            }
436        }
437
438        /// <summary>
439        /// The main form for this program instance. This form will be shown when
440        /// run is called if it is non-null and if its Visible property is true.
441        /// </summary>
442        public Form MainForm
443        {
444            get
445            {
446                return mainForm;
447            }
448            set
449            {
450                mainForm = value;
451            }
452        }
453
454        #region Events
455        /// <summary>
456        /// The prototype of event handlers procesing the InitInstance event.
457        /// </summary>
458        /// <param name="sender">The sender of the event.</param>
459        /// <returns>True if the MainForm property holds a valid reference to
460        /// a form, and the form should be displayed to the user.</returns>
461        public delegate bool InitInstanceFunction(object sender);
462
463        /// <summary>
464        /// The event object managing listeners to the instance initialisation event.
465        /// This event is raised when the first instance of the program is started
466        /// and this is where the program initialisation code should be.
467        /// </summary>
468        public event InitInstanceFunction InitInstance;
469
470        /// <summary>
471        /// Broadcasts the InitInstance event.
472        /// </summary>
473        /// <param name="sender">The sender of the event.</param>
474        /// <returns>True if the MainForm object should be shown.</returns>
475        private bool OnInitInstance(object sender)
476        {
477            if (InitInstance != null)
478                return InitInstance(sender);
479            return true;
480        }
481
482        /// <summary>
483        /// The prototype of event handlers procesing the NextInstance event.
484        /// </summary>
485        /// <param name="sender">The sender of the event</param>
486        public delegate void NextInstanceFunction(object sender);
487
488        /// <summary>
489        /// The event object managing listeners to the next instance event. This
490        /// event is raised when a second instance of the program is started.
491        /// </summary>
492        public event NextInstanceFunction NextInstance;
493
494        /// <summary>
495        /// Broadcasts the NextInstance event.
496        /// </summary>
497        /// <param name="sender">The sender of the event.</param>
498        /// <param name="message">The message sent by the secondary instance.</param>
499        private void OnNextInstance(object sender, string message)
500        {
501            if (NextInstance != null)
502                NextInstance(sender);
503        }
504
505        /// <summary>
506        /// The prototype of event handlers procesing the ExitInstance event.
507        /// </summary>
508        /// <param name="sender">The sender of the event.</param>
509        public delegate void ExitInstanceFunction(object sender);
510
511        /// <summary>
512        /// The event object managing listeners to the exit instance event. This
513        /// event is raised when the first instance of the program is exited.
514        /// </summary>
515        public event ExitInstanceFunction ExitInstance;
516
517        /// <summary>
518        /// Broadcasts the ExitInstance event.
519        /// </summary>
520        /// <param name="sender">The sender of the event.</param>
521        private void OnExitInstance(object sender)
522        {
523            if (ExitInstance != null)
524                ExitInstance(sender);
525        }
526        #endregion
527
528        #region Instance variables
529        /// <summary>
530        /// The Instance ID of this program, used to group program instances together.
531        /// </summary>
532        private string instanceID;
533
534        /// <summary>
535        /// The named mutex ensuring that only one instance of the application runs
536        /// at a time.
537        /// </summary>
538        private Mutex globalMutex;
539
540        /// <summary>
541        /// The thread maintaining the pipe server for secondary instances to connect to.
542        /// </summary>
543        private Thread pipeServer;
544
545        /// <summary>
546        /// Holds whether this instance of the program is the first instance.
547        /// </summary>
548        private bool isFirstInstance;
549
550        /// <summary>
551        /// The command line arguments this instance was started with.
552        /// </summary>
553        private string[] commandLine;
554
555        /// <summary>
556        /// The main form for this program instance.
557        /// </summary>
558        private Form mainForm;
559        #endregion
560    }
561
562    class CommandLineProgram
563    {
564        #region Command Line parsing classes
565        /// <summary>
566        /// Manages a command line.
567        /// </summary>
568        public class CommandLine
569        {
570            /// <summary>
571            /// Constructor.
572            /// </summary>
573            /// <param name="cmdParams">The raw arguments passed to the program.</param>
574            public CommandLine(string[] cmdParams)
575            {
576                //Get the action.
577                if (cmdParams.Length < 1)
578                    throw new ArgumentException("An action must be specified.");
579                Action = cmdParams[0];
580
581                //Iterate over each argument, resolving them ourselves and letting
582                //subclasses resolve them if we don't know how to.
583                for (int i = 1; i != cmdParams.Length; ++i)
584                {
585                    if (IsParam(cmdParams[i], "quiet", "q"))
586                        Quiet = true;
587                    else if (!ResolveParameter(cmdParams[i]))
588                        throw new ArgumentException("Unknown argument: " + cmdParams[i]);
589                }
590            }
591
592            /// <summary>
593            /// Called when a parameter is not used by the current CommandLine object
594            /// for subclasses to handle their parameters.
595            /// </summary>
596            /// <param name="param">The parameter to resolve.</param>
597            /// <returns>Return true if the parameter was resolved and accepted.</returns>
598            virtual protected bool ResolveParameter(string param)
599            {
600                return false;
601            }
602
603            /// <summary>
604            /// Checks if the given <paramref name="parameter"/> refers to the
605            /// <paramref name="expectedParameter"/>, regardless of whether it is specified
606            /// with -, --, or /
607            /// </summary>
608            /// <param name="parameter">The parameter on the command line.</param>
609            /// <param name="expectedParameter">The parameter the program is looking for, without
610            /// the - or / prefix.</param>
611            /// <param name="shortParameter">The short parameter when used with a single hyphen,
612            /// without the - or / prefix.</param>
613            /// <returns>True if the parameter references the given expected parameter.</returns>
614            protected static bool IsParam(string parameter, string expectedParameter,
615                string shortParameter)
616            {
617                //Trivial case
618                if (parameter.Length < 1)
619                    return false;
620
621                //Extract the bits before the equal sign.
622                {
623                    int equalPos = parameter.IndexOf('=');
624                    if (equalPos != -1)
625                        parameter = parameter.Substring(0, equalPos);
626                }
627
628                //Get the first letter.
629                switch (parameter[0])
630                {
631                    case '-':
632                        //Can be a - or a --. Check for the second parameter
633                        if (parameter.Length < 2)
634                            //Nothing specified at the end... it's invalid.
635                            return false;
636
637                        if (parameter[1] == '-')
638                            return parameter.Substring(2) == expectedParameter;
639                        else if (shortParameter == null || shortParameter == string.Empty)
640                            return parameter.Substring(1) == expectedParameter;
641                        else
642                            return parameter.Substring(1) == shortParameter;
643
644                    case '/':
645                        //The / can be used with both long and short parameters.
646                        parameter = parameter.Substring(1);
647                        return parameter == expectedParameter || (
648                            shortParameter != null && shortParameter != string.Empty &&
649                            parameter == shortParameter
650                        );
651
652                    default:
653                        return false;
654                }
655            }
656
657            /// <summary>
658            /// Gets the list of subparameters of the parameter. Subparameters are text
659            /// after the first =, separated by commas.
660            /// </summary>
661            /// <param name="param">The subparameter text to parse.</param>
662            /// <returns>The list of subparameters in the parameter.</returns>
663            protected static List<KeyValuePair<string, string>> GetSubParameters(string param)
664            {
665                List<KeyValuePair<string, string>> result =
666                    new List<KeyValuePair<string, string>>();
667                int lastPos = 0;
668                int commaPos = (param += ',').IndexOf(',');
669
670                while (commaPos != -1)
671                {
672                    //Extract the current subparameter, and dissect the subparameter at
673                    //the first =.
674                    string subParam = param.Substring(lastPos, commaPos - lastPos);
675                    int equalPos = subParam.IndexOf('=');
676                    if (equalPos == -1)
677                        result.Add(new KeyValuePair<string, string>(subParam, null));
678                    else
679                        result.Add(new KeyValuePair<string, string>(subParam.Substring(0, equalPos),
680                            subParam.Substring(equalPos + 1)));
681
682                    //Find the next ,
683                    lastPos = ++commaPos;
684                    commaPos = param.IndexOf(',', commaPos);
685                }
686
687                return result;
688            }
689
690            /// <summary>
691            /// The action that the command line specifies.
692            /// </summary>
693            public string Action
694            {
695                get
696                {
697                    return action;
698                }
699                private set
700                {
701                    action = value;
702                }
703            }
704
705            /// <summary>
706            /// True if no console window should be created.
707            /// </summary>
708            public bool Quiet
709            {
710                get
711                {
712                    return quiet;
713                }
714                private set
715                {
716                    quiet = value;
717                }
718            }
719
720            private string action;
721            private bool quiet;
722        }
723
724        /// <summary>
725        /// Manages a command line for adding tasks to the global DirectExecutor
726        /// </summary>
727        class AddTaskCommandLine : CommandLine
728        {
729            /// <summary>
730            /// Constructor.
731            /// </summary>
732            /// <param name="cmdParams">The raw command line arguments passed to the program.</param>
733            public AddTaskCommandLine(string[] cmdParams)
734                : base(cmdParams)
735            {
736            }
737
738            protected override bool ResolveParameter(string param)
739            {
740                int equalPos = param.IndexOf('=');
741                if (IsParam(param, "method", "m"))
742                {
743                    if (equalPos == -1)
744                        throw new ArgumentException("--method must be specified with an Erasure " +
745                            "method GUID.");
746
747                    List<KeyValuePair<string, string>> subParams =
748                        GetSubParameters(param.Substring(equalPos + 1));
749                    ErasureMethod = new Guid(subParams[0].Key);
750                }
751                else if (IsParam(param, "schedule", "s"))
752                {
753                    if (equalPos == -1)
754                        throw new ArgumentException("--schedule must be specified with a Schedule " +
755                            "type.");
756
757                    List<KeyValuePair<string, string>> subParams =
758                        GetSubParameters(param.Substring(equalPos + 1));
759                    switch (subParams[0].Key)
760                    {
761                        case "now":
762                            Schedule = Schedule.RunNow;
763                            break;
764                        case "restart":
765                            schedule = Schedule.RunOnRestart;
766                            break;
767                        default:
768                            throw new ArgumentException("Unknown schedule type: " + subParams[0].Key);
769                    }
770                }
771                else if (IsParam(param, "recycled", "r"))
772                {
773                    targets.Add(new Task.RecycleBin());
774                }
775                else if (IsParam(param, "unused", "u"))
776                {
777                    if (equalPos == -1)
778                        throw new ArgumentException("--unused must be specified with the Volume " +
779                            "to erase.");
780
781                    //Create the UnusedSpace target for inclusion into the task.
782                    Task.UnusedSpace target = new Task.UnusedSpace();
783
784                    //Determine if cluster tips should be erased.
785                    target.EraseClusterTips = false;
786                    List<KeyValuePair<string, string>> subParams =
787                        GetSubParameters(param.Substring(equalPos + 1));
788                    foreach (KeyValuePair<string, string> kvp in subParams)
789                        if (kvp.Value == null && target.Drive == null)
790                            target.Drive = Path.GetFullPath(kvp.Key);
791                        else if (kvp.Key == "clusterTips")
792                            target.EraseClusterTips = true;
793                        else
794                            throw new ArgumentException("Unknown subparameter: " + kvp.Key);
795                    targets.Add(target);
796                }
797                else if (IsParam(param, "dir", "d") || IsParam(param, "directory", null))
798                {
799                    if (equalPos == -1)
800                        throw new ArgumentException("--directory must be specified with the " +
801                            "directory to erase.");
802
803                    //Create the base target
804                    Task.Folder target = new Task.Folder();
805
806                    //Parse the subparameters.
807                    List<KeyValuePair<string, string>> subParams =
808                        GetSubParameters(param.Substring(equalPos + 1));
809                    foreach (KeyValuePair<string, string> kvp in subParams)
810                        if (kvp.Value == null && target.Path == null)
811                            target.Path = Path.GetFullPath(kvp.Key);
812                        else if (kvp.Key == "excludeMask")
813                        {
814                            if (kvp.Value == null)
815                                throw new ArgumentException("The exclude mask must be specified " +
816                                    "if the excludeMask subparameter is specified");
817                            target.ExcludeMask = kvp.Value;
818                        }
819                        else if (kvp.Key == "includeMask")
820                        {
821                            if (kvp.Value == null)
822                                throw new ArgumentException("The include mask must be specified " +
823                                    "if the includeMask subparameter is specified");
824                            target.IncludeMask = kvp.Value;
825                        }
826                        else if (kvp.Key == "delete")
827                            target.DeleteIfEmpty = true;
828                        else
829                            throw new ArgumentException("Unknown subparameter: " + kvp.Key);
830
831                    //Add the target to the list of targets
832                    targets.Add(target);
833                }
834                else
835                {
836                    //It's just a file!
837                    Task.File target = new Task.File();
838                    target.Path = Path.GetFullPath(param);
839                    targets.Add(target);
840                }
841
842                return true;
843            }
844
845            /// <summary>
846            /// The erasure method which the user specified on the command line.
847            /// </summary>
848            public Guid ErasureMethod
849            {
850                get
851                {
852                    return erasureMethod;
853                }
854                private set
855                {
856                    erasureMethod = value;
857                }
858            }
859
860            /// <summary>
861            /// The schedule for the current set of targets.
862            /// </summary>
863            public Schedule Schedule
864            {
865                get
866                {
867                    return schedule;
868                }
869                set
870                {
871                    schedule = value;
872                }
873            }
874
875            /// <summary>
876            /// The list of targets which was specified on the command line.
877            /// </summary>
878            public List<Task.ErasureTarget> Targets
879            {
880                get
881                {
882                    return new List<Task.ErasureTarget>(targets.ToArray());
883                }
884                set
885                {
886                    targets = value;
887                }
888            }
889
890            private Guid erasureMethod;
891            private Schedule schedule = Schedule.RunNow;
892            private List<Task.ErasureTarget> targets = new List<Task.ErasureTarget>();
893        }
894        #endregion
895
896        /// <summary>
897        /// Constructor.
898        /// </summary>
899        /// <param name="cmdParams">The raw command line arguments passed to the program.</param>
900        public CommandLineProgram(string[] cmdParams)
901        {
902            try
903            {
904                //Parse the command line arguments.
905                if (cmdParams.Length < 1)
906                    throw new ArgumentException("An action must be specified.");
907
908                switch (cmdParams[0])
909                {
910                    case "addtask":
911                        Arguments = new AddTaskCommandLine(cmdParams);
912                        break;
913                    case "querymethods":
914                    case "help":
915                    default:
916                        Arguments = new CommandLine(cmdParams);
917                        break;
918                }
919
920                //If the user did not specify the quiet command line, then create the console.
921                if (!Arguments.Quiet)
922                    CreateConsole();
923
924                //Map actions to their handlers
925                actionHandlers.Add("addtask", AddTask);
926                actionHandlers.Add("querymethods", QueryMethods);
927                actionHandlers.Add("help", Help);
928            }
929            finally
930            {
931                if (Arguments == null || !Arguments.Quiet)
932                    CreateConsole();
933            }
934        }
935
936        /// <summary>
937        /// Runs the program, analogous to System.Windows.Forms.Application.Run.
938        /// </summary>
939        public void Run()
940        {
941            //Call the function handling the current command line.
942            actionHandlers[Arguments.Action]();
943        }
944
945        /// <summary>
946        /// Creates a console for our application, setting the input/output streams to the
947        /// defaults.
948        /// </summary>
949        private void CreateConsole()
950        {
951            if (KernelAPI.AllocConsole())
952            {
953                Console.SetOut(new StreamWriter(Console.OpenStandardOutput()));
954                Console.SetIn(new StreamReader(Console.OpenStandardInput()));
955            }
956        }
957
958        /// <summary>
959        /// Prints the command line help for Eraser.
960        /// </summary>
961        private static void CommandUsage()
962        {
963            Console.WriteLine(@"usage: Eraser <action> <arguments>
964where action is
965    help                    Show this help message.
966    addtask                 Adds tasks to the current task list.
967    querymethods            Lists all registered Erasure methods.
968
969global parameters:
970    --quiet, -q             Do not create a Console window to display progress.
971
972parameters for help:
973    eraser help
974
975    no parameters to set.
976
977parameters for addtask:
978    eraser addtask [--method=<methodGUID>] [--schedule=(now|restart)] (--recycled " +
979@"| --unused=<volume> | --dir=<directory> | [file1 [file2 [...]]])
980
981    --method, -m            The Erasure method to use.
982    --schedule, -s          The schedule the task will follow. The value must
983                            be one of:
984            now             The task will be queued for immediate execution.
985            restart         The task will be queued for execution when the
986                            computer is next restarted.
987    --recycled, -r          Erases files and folders in the recycle bin
988    --unused, -u            Erases unused space in the volume.
989        optional arguments: --unused=<drive>[,clusterTips]
990            clusterTips     If specified, the drive's files will have their
991                            cluster tips erased.
992    --dir, --directory, -d  Erases files and folders in the directory
993        optional arguments: --dir=<directory>[,e=excludeMask][,i=includeMask][,delete]
994            excludeMask     A wildcard expression for files and folders to
995                            exclude.
996            includeMask     A wildcard expression for files and folders to
997                            include.
998                            The include mask is applied before the exclude
999                            mask.
1000            delete          Deletes the folder at the end of the erasure if
1001                            specified.
1002    file1 ... fileN         The list of files to erase.
1003
1004parameters for querymethods:
1005    eraser querymethods
1006
1007    no parameters to set.
1008
1009All arguments are case sensitive.");
1010            Console.Out.Flush();
1011        }
1012
1013        #region Action Handlers
1014        /// <summary>
1015        /// The command line arguments passed to the program.
1016        /// </summary>
1017        public CommandLine Arguments
1018        {
1019            get
1020            {
1021                return arguments;
1022            }
1023            private set
1024            {
1025                arguments = value;
1026            }
1027        }
1028
1029        /// <summary>
1030        /// Prints the help text for Eraser (with copyright)
1031        /// </summary>
1032        private void Help()
1033        {
1034            Console.WriteLine(@"Eraser {0}
1035(c) 2008 The Eraser Project
1036Eraser is Open-Source Software: see http://eraser.heidi.ie/ for details.
1037", Assembly.GetExecutingAssembly().GetName().Version);
1038
1039            Console.Out.Flush();
1040            CommandUsage();
1041        }
1042
1043        /// <summary>
1044        /// Lists all registered erasure methods.
1045        /// </summary>
1046        /// <param name="commandLine">The command line parameters passed to the program.</param>
1047        private void QueryMethods()
1048        {
1049            //Output the header
1050            const string methodFormat = "{0,-2} {1,-39} {2}";
1051            Console.WriteLine(methodFormat, "", "Method", "GUID");
1052            Console.WriteLine(new string('-', 79));
1053
1054            //Refresh the list of erasure methods
1055            Dictionary<Guid, ErasureMethod> methods = ErasureMethodManager.GetAll();
1056            foreach (ErasureMethod method in methods.Values)
1057            {
1058                Console.WriteLine(methodFormat, (method is UnusedSpaceErasureMethod) ?
1059                    "U" : "", method.Name, method.GUID.ToString());
1060            }
1061        }
1062
1063        /// <summary>
1064        /// Parses the command line for tasks and adds them using the
1065        /// <see cref="RemoteExecutor"/> class.
1066        /// </summary>
1067        /// <param name="commandLine">The command line parameters passed to the program.</param>
1068        private void AddTask()
1069        {
1070            AddTaskCommandLine arguments = (AddTaskCommandLine)Arguments;
1071           
1072            //Create the task, and set the method to use.
1073            Task task = new Task();
1074            ErasureMethod method = arguments.ErasureMethod == Guid.Empty ? 
1075                ErasureMethodManager.Default :
1076                ErasureMethodManager.GetInstance(arguments.ErasureMethod);
1077            foreach (Task.ErasureTarget target in arguments.Targets)
1078            {
1079                target.Method = method;
1080                task.Targets.Add(target);
1081            }
1082
1083            //Check the number of tasks in the task.
1084            if (task.Targets.Count == 0)
1085                throw new ArgumentException("Tasks must contain at least one erasure target.");
1086
1087            //Set the schedule for the task.
1088            task.Schedule = arguments.Schedule;
1089
1090            //Send the task out.
1091            using (Program.eraserClient = new RemoteExecutorClient())
1092            {
1093                if (!((RemoteExecutorClient)Program.eraserClient).Connect())
1094                {
1095                    //The client cannot connect to the server. This probably means
1096                    //that the server process isn't running. Start an instance.
1097                    Process eraserInstance = Process.Start(
1098                        Assembly.GetExecutingAssembly().Location, "--quiet");
1099                    eraserInstance.WaitForInputIdle();
1100
1101                    if (!((RemoteExecutorClient)Program.eraserClient).Connect())
1102                        throw new Exception("Eraser cannot connect to the running " +
1103                            "instance for erasures.");
1104                }
1105
1106                Program.eraserClient.Run();
1107                Program.eraserClient.AddTask(ref task);
1108            }
1109        }
1110        #endregion
1111
1112        /// <see cref="Arguments"/>
1113        private CommandLine arguments;
1114
1115        /// <summary>
1116        /// The prototype of an action handler in the class which executes an
1117        /// action as specified in the command line.
1118        /// </summary>
1119        private delegate void ActionHandler();
1120
1121        /// <summary>
1122        /// Matches an action handler to a function in the class.
1123        /// </summary>
1124        private Dictionary<string, ActionHandler> actionHandlers =
1125            new Dictionary<string, ActionHandler>();
1126    }
1127
1128    internal class Settings : Manager.SettingsManager
1129    {
1130        /// <summary>
1131        /// Registry-based storage backing for the Settings class.
1132        /// </summary>
1133        private class RegistrySettings : Manager.Settings
1134        {
1135            /// <summary>
1136            /// Constructor.
1137            /// </summary>
1138            /// <param name="key">The registry key to look for the settings in.</param>
1139            public RegistrySettings(Guid pluginID, RegistryKey key)
1140            {
1141                this.key = key;
1142            }
1143
1144            public override object this[string setting]
1145            {
1146                get
1147                {
1148                    byte[] currentSetting = (byte[])key.GetValue(setting, null);
1149                    if (currentSetting != null && currentSetting.Length != 0)
1150                        using (MemoryStream stream = new MemoryStream(currentSetting))
1151                            try
1152                            {
1153                                return new BinaryFormatter().Deserialize(stream);
1154                            }
1155                            catch (Exception)
1156                            {
1157                                key.DeleteValue(setting);
1158                                MessageBox.Show(S._("Could not load the setting {0} for plugin {1}. " +
1159                                    "The setting has been lost.", key, pluginID.ToString()),
1160                                    S._("Eraser"), MessageBoxButtons.OK, MessageBoxIcon.Error);
1161                            }
1162
1163                    return null;
1164                }
1165                set
1166                {
1167                    if (value == null)
1168                    {
1169                        key.DeleteValue(setting);
1170                    }
1171                    else
1172                    {
1173                        using (MemoryStream stream = new MemoryStream())
1174                        {
1175                            new BinaryFormatter().Serialize(stream, value);
1176                            key.SetValue(setting, stream.ToArray(), RegistryValueKind.Binary);
1177                        }
1178                    }
1179                }
1180            }
1181
1182            /// <summary>
1183            /// The GUID of the plugin whose settings this object is storing.
1184            /// </summary>
1185            private Guid pluginID;
1186
1187            /// <summary>
1188            /// The registry key where the data is stored.
1189            /// </summary>
1190            private RegistryKey key;
1191        }
1192
1193        public override void Save()
1194        {
1195        }
1196
1197        protected override Manager.Settings GetSettings(Guid guid)
1198        {
1199            //Open the registry key containing the settings
1200            const string eraserKeyPath = @"SOFTWARE\Eraser\Eraser 6";
1201            RegistryKey eraserKey = Registry.CurrentUser.OpenSubKey(eraserKeyPath, true);
1202            if (eraserKey == null)
1203                eraserKey = Registry.CurrentUser.CreateSubKey(eraserKeyPath);
1204
1205            RegistryKey pluginsKey = eraserKey.OpenSubKey(guid.ToString(), true);
1206            if (pluginsKey == null)
1207                pluginsKey = eraserKey.CreateSubKey(guid.ToString());
1208
1209            //Return the Settings object.
1210            return new RegistrySettings(guid, pluginsKey);
1211        }
1212    }
1213
1214    internal class EraserSettings
1215    {
1216        public EraserSettings()
1217        {
1218            settings = Manager.ManagerLibrary.Instance.SettingsManager.ModuleSettings;
1219        }
1220
1221        /// <summary>
1222        /// Gets or sets the task list, serialised in binary form by the Manager assembly.
1223        /// </summary>
1224        public byte[] TaskList
1225        {
1226            get
1227            {
1228                return (byte[])settings["TaskList"];
1229            }
1230            set
1231            {
1232                settings["TaskList"] = value;
1233            }
1234        }
1235
1236        /// <summary>
1237        /// Gets or sets the LCID of the language which the UI should be displayed in.
1238        /// </summary>
1239        public string Language
1240        {
1241            get
1242            {
1243                return settings["Language"] == null ?
1244                    GetCurrentCulture().Name :
1245                    (string)settings["Language"];
1246            }
1247            set
1248            {
1249                settings["Language"] = value;
1250            }
1251        }
1252
1253        /// <summary>
1254        /// Gets or sets whether the Shell Extension should be loaded into Explorer.
1255        /// </summary>
1256        public bool IntegrateWithShell
1257        {
1258            get
1259            {
1260                return settings["IntegrateWithShell"] == null ?
1261                    true : (bool)settings["IntegrateWithShell"];
1262            }
1263            set
1264            {
1265                settings["IntegrateWithShell"] = value;
1266            }
1267        }
1268
1269        /// <summary>
1270        /// Gets or sets a value on whether the main frame should be minimised to the
1271        /// system notification area.
1272        /// </summary>
1273        public bool HideWhenMinimised
1274        {
1275            get
1276            {
1277                return settings["HideWhenMinimised"] == null ?
1278                    true : (bool)settings["HideWhenMinimised"];
1279            }
1280            set
1281            {
1282                settings["HideWhenMinimised"] = value;
1283            }
1284        }
1285
1286        /// <summary>
1287        /// Gets the current UI culture, correct to the top-level culture (i.e., English
1288        /// instead of English (United Kingdom))
1289        /// </summary>
1290        /// <returns>The CultureInfo of the current UI culture, correct to the top level.</returns>
1291        private static CultureInfo GetCurrentCulture()
1292        {
1293            CultureInfo culture = CultureInfo.CurrentUICulture;
1294            while (culture.Parent != CultureInfo.InvariantCulture)
1295                culture = culture.Parent;
1296
1297            return culture;
1298        }
1299
1300        private Manager.Settings settings;
1301    }
1302}
Note: See TracBrowser for help on using the repository browser.