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

Revision 915, 39.4 KB checked in by lowjoel, 6 years ago (diff)

-Don't nest publicly accessible classes in classes.
-Camelcasing for abbreviations
-Abstract class constructors should be protected

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