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

Revision 786, 37.4 KB checked in by lowjoel, 5 years ago (diff)

Force the creation of the main window if the main window is not going to be displayed on the first run.

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