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

Revision 1054, 44.6 KB checked in by lowjoel, 6 years ago (diff)

Always call the OnExitInstance? function. When the systme is shutting down it doesn't get called before. Now we chain up with the main form so that even if shutdowns are ordered we get a chance to clean up/do whatever post-application run processing we need to do.

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