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

Revision 858, 39.5 KB checked in by lowjoel, 6 years ago (diff)

-Allow users to specify that they want compelted tasks to be removed automatically
-Tasks will pop up a balloon telling users that they have been completed

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