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

Revision 1013, 44.2 KB checked in by lowjoel, 5 years ago (diff)

Added Task List shell integration to Eraser - implemented the Shell icon, Shell open verb, but not the RemoteExecutor?. Draft file icon thanks to Dennis.

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