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

Revision 903, 39.5 KB checked in by lowjoel, 5 years ago (diff)

Ran Static code analysis on the Eraser project and implemented a few recommendations.

-Use more specific exceptions to allow code to determine the type of exception.
-Made GUIProgram implement IDisposable since the global mutex must be freed.
-Mde AboutForm? implement IDisposable, clearing the caching bitmaps on dispose.
-Made a few CommandLineProgram? functions static since they don't reference this.
-Name parameters/local with more unique, descriptive names for clarity.
-Use EventHandler?<EventArg?> instead of declaring our own delegate and event types as the type of the evenr handler

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