source: trunk/eraser6/Eraser/Program.cs @ 1255

Revision 1255, 50.1 KB checked in by cjax, 5 years ago (diff)

Replace the RegistrySettings? class with IFFSSettings class for our secure settings management.

  • 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.Security.Permissions;
33using System.Globalization;
34using System.Reflection;
35using System.Diagnostics;
36using System.ComponentModel;
37
38using Eraser.Manager;
39using Eraser.Util;
40using Microsoft.Win32;
41
42namespace Eraser
43{
44    static class Program
45    {
46        /// <summary>
47        /// The main entry point for the application.
48        /// </summary>
49        [STAThread]
50        static int Main(string[] commandLine)
51        {
52            //Trivial case: no command parameters
53            if (commandLine.Length == 0)
54                GUIMain(commandLine);
55
56            //Determine if the sole parameter is --restart; if it is, start the GUI
57            //passing isRestart as true. Otherwise, we're a console application.
58            else if (commandLine.Length == 1)
59            {
60                if (commandLine[0] == "--atRestart" || commandLine[0] == "--quiet")
61                {
62                    GUIMain(commandLine);
63                }
64                else
65                {
66                    return CommandMain(commandLine);
67                }
68            }
69
70            //The other trivial case: definitely a console application.
71            else
72                return CommandMain(commandLine);
73
74            //No error.
75            return 0;
76        }
77
78        /// <summary>
79        /// Runs Eraser as a command-line application.
80        /// </summary>
81        /// <param name="commandLine">The command line parameters passed to Eraser.</param>
82        private static int CommandMain(string[] commandLine)
83        {
84            //True if the user specified a quiet command.
85            bool isQuiet = false;
86
87            try
88            {
89                CommandLineProgram program = new CommandLineProgram(commandLine);
90                isQuiet = program.Arguments.Quiet;
91
92                using (ManagerLibrary library = new ManagerLibrary(new Settings()))
93                    program.Run();
94
95                return 0;
96            }
97            catch (UnauthorizedAccessException)
98            {
99                return 5; //ERROR_ACCESS_DENIED
100            }
101            catch (Win32Exception e)
102            {
103                Console.WriteLine(e.Message);
104                return e.ErrorCode;
105            }
106            catch (Exception e)
107            {
108                Console.WriteLine(e.Message);
109                return 1;
110            }
111            finally
112            {
113                //Flush the buffered output to the console
114                Console.Out.Flush();
115
116                //Don't ask for a key to press if the user specified Quiet
117                if (!isQuiet)
118                {
119                    Console.Write("\nPress enter to continue . . . ");
120                    Console.Out.Flush();
121                    Console.ReadLine();
122                }
123
124                KernelApi.FreeConsole();
125            }
126        }
127
128        /// <summary>
129        /// Runs Eraser as a GUI application.
130        /// </summary>
131        /// <param name="commandLine">The command line parameters passed to Eraser.</param>
132        private static void GUIMain(string[] commandLine)
133        {
134            //Create the program instance
135            using (GUIProgram program = new GUIProgram(commandLine, "Eraser-BAD0DAC6-C9EE-4acc-" +
136                "8701-C9B3C64BC65E-GUI-" +
137                System.Security.Principal.WindowsIdentity.GetCurrent().User.ToString()))
138
139            //Then run the program instance.
140            using (ManagerLibrary library = new ManagerLibrary(new Settings()))
141            {
142                program.InitInstance += OnGUIInitInstance;
143                program.NextInstance += OnGUINextInstance;
144                program.ExitInstance += OnGUIExitInstance;
145                program.Run();
146            }
147        }
148
149        /// <summary>
150        /// Triggered when the Program is started for the first time.
151        /// </summary>
152        /// <param name="sender">The sender of the object.</param>
153        /// <returns>True if the user did not specify --quiet, false otherwise.</returns>
154        private static bool OnGUIInitInstance(object sender)
155        {
156            GUIProgram program = (GUIProgram)sender;
157            eraserClient = new RemoteExecutorServer();
158
159            //Set our UI language
160            EraserSettings settings = EraserSettings.Get();
161            System.Threading.Thread.CurrentThread.CurrentUICulture =
162                new CultureInfo(settings.Language);
163            Application.SafeTopLevelCaptionFormat = S._("Eraser");
164
165            //Load the task list
166            SettingsCompatibility.Execute();
167            try
168            {
169                if (System.IO.File.Exists(TaskListPath))
170                {
171                    using (FileStream stream = new FileStream(TaskListPath, FileMode.Open,
172                        FileAccess.Read, FileShare.Read))
173                    {
174                        eraserClient.Tasks.LoadFromStream(stream);
175                    }
176                }
177            }
178            catch (SerializationException e)
179            {
180                System.IO.File.Delete(TaskListPath);
181                MessageBox.Show(S._("Could not load task list. All task entries have " +
182                    "been lost. The error returned was: {0}", e.Message), S._("Eraser"),
183                    MessageBoxButtons.OK, MessageBoxIcon.Error,
184                    MessageBoxDefaultButton.Button1,
185                    S.IsRightToLeft(null) ? MessageBoxOptions.RtlReading : 0);
186            }
187
188            //Create the main form
189            program.MainForm = new MainForm();
190            program.MainForm.CreateControl();
191            bool showMainForm = true;
192            foreach (string param in program.CommandLine)
193            {
194                //Run tasks which are meant to be run on restart
195                switch (param)
196                {
197                    case "--atRestart":
198                        eraserClient.QueueRestartTasks();
199                        goto case "--quiet";
200
201                    //Hide the main form if the user specified the quiet command
202                    //line
203                    case "--quiet":
204                        showMainForm = false;
205                        break;
206                }
207            }
208
209            //Run the eraser client.
210            eraserClient.Run();
211            return showMainForm;
212        }
213
214        /// <summary>
215        /// Triggered when a second instance of Eraser is started.
216        /// </summary>
217        /// <param name="sender">The sender of the event.</param>
218        /// <param name="message">The message from the source application.</param>
219        private static void OnGUINextInstance(object sender, string message)
220        {
221            //Another instance of the GUI Program has been started: show the main window
222            //now as we still do not have a facility to handle the command line arguments.
223            GUIProgram program = (GUIProgram)sender;
224
225            //Invoke the function if we aren't on the main thread
226            if (program.MainForm.InvokeRequired)
227            {
228                program.MainForm.Invoke(new GUIProgram.NextInstanceFunction(
229                    OnGUINextInstance), new object[] { sender, message });
230                return;
231            }
232
233            program.MainForm.Visible = true;
234        }
235
236        /// <summary>
237        /// Triggered when the first instance of Eraser is exited.
238        /// </summary>
239        /// <param name="sender">The sender of the event.</param>
240        private static void OnGUIExitInstance(object sender)
241        {
242            //Save the task list
243            if (!Directory.Exists(Program.AppDataPath))
244                Directory.CreateDirectory(Program.AppDataPath);
245            using (FileStream stream = new FileStream(TaskListPath, FileMode.Create,
246                FileAccess.Write, FileShare.None))
247            {
248                eraserClient.Tasks.SaveToStream(stream);
249            }
250
251            //Dispose the eraser executor instance
252            eraserClient.Dispose();
253        }
254
255        /// <summary>
256        /// The global Executor instance.
257        /// </summary>
258        public static Executor eraserClient;
259
260        /// <summary>
261        /// Path to the Eraser application data path.
262        /// </summary>
263        public static readonly string AppDataPath = Path.Combine(Environment.GetFolderPath(
264            Environment.SpecialFolder.LocalApplicationData), @"Eraser 6");
265
266        /// <summary>
267        /// File name of the Eraser task list.
268        /// </summary>
269        private static readonly string TaskListFileName = @"Task List.ersx";
270
271        /// <summary>
272        /// Path to the Eraser task list.
273        /// </summary>
274        public static readonly string TaskListPath = Path.Combine(AppDataPath, TaskListFileName);
275
276        /// <summary>
277        /// Path to the Eraser settings key (relative to HKCU)
278        /// </summary>
279        public static readonly string SettingsPath = @"SOFTWARE\Eraser\Eraser 6";
280
281        /// <summary>
282        /// File name of the IFFS file container
283        /// </summary>
284        public static readonly string IFFSFileName = @"Settings.erdb";
285
286        /// <summary>
287        /// Path to eraser IFFS file container
288        /// </summary>
289        public static readonly string IFFSPath = Path.Combine(AppDataPath, IFFSFileName);
290    }
291
292    class GUIProgram : IDisposable
293    {
294        /// <summary>
295        /// Constructor.
296        /// </summary>
297        /// <param name="commandLine">The command line arguments associated with
298        /// this program launch</param>
299        /// <param name="instanceID">The instance ID of the program, used to group
300        /// instances of the program together.</param>
301        public GUIProgram(string[] commandLine, string instanceID)
302        {
303            Application.EnableVisualStyles();
304            Application.SetCompatibleTextRenderingDefault(false);
305            this.instanceID = instanceID;
306            this.CommandLine = commandLine;
307
308            //Check if there already is another instance of the program.
309            globalMutex = new Mutex(true, instanceID, out isFirstInstance);
310        }
311
312        ~GUIProgram()
313        {
314            Dispose(false);
315        }
316
317        protected virtual void Dispose(bool disposing)
318        {
319            if (disposing)
320                globalMutex.Close();
321        }
322
323        public void Dispose()
324        {
325            Dispose(true);
326            GC.SuppressFinalize(this);
327        }
328
329        /// <summary>
330        /// Runs the event loop of the GUI program, returning true if the program
331        /// was started as there were no other instances of the program, or false
332        /// if other instances were found.
333        /// </summary>
334        /// <remarks>
335        /// This function must always be called in your program, regardless
336        /// of the value of <see cref="IsAlreadyRunning"/>. If this function is not
337        /// called, the first instance will never be notified that another was started.
338        /// </remarks>
339        /// <returns>True if the application was started, or false if another instance
340        /// was detected.</returns>
341        public bool Run()
342        {
343            //If no other instances are running, set up our pipe server so clients
344            //can connect and give us subsequent command lines.
345            if (IsFirstInstance)
346            {
347                try
348                {
349                    //Create the pipe server which will handle connections to us
350                    pipeServer = new Thread(ServerMain);
351                    pipeServer.Start();
352
353                    //Initialise and run the program.
354                    bool ShowMainForm = OnInitInstance(this);
355                    if (MainForm == null)
356                        return false;
357
358                    //Handle the exit instance event. This will occur when the main form
359                    //has been closed.
360                    MainForm.FormClosed += OnExitInstance;
361               
362                    if (ShowMainForm)
363                        Application.Run(MainForm);
364                    else
365                    {
366                        //If we aren't showing the form, force the creation of the window
367                        //handle.
368                        MainForm.CreateControl();
369                        IntPtr handle = MainForm.Handle;
370                        Application.Run();
371                    }
372
373                    return true;
374                }
375                finally
376                {
377                    pipeServer.Abort();
378                }
379            }
380
381            //Another instance of the program is running. Connect to it and transfer
382            //the command line arguments
383            else
384            {
385                try
386                {
387                    NamedPipeClientStream client = new NamedPipeClientStream(".", instanceID,
388                        PipeDirection.Out);
389                    client.Connect(500);
390
391                    StringBuilder commandLineStr = new StringBuilder(CommandLine.Length * 64);
392                    foreach (string param in CommandLine)
393                        commandLineStr.Append(string.Format(
394                            CultureInfo.InvariantCulture, "{0}\0", param));
395
396                    byte[] buffer = new byte[commandLineStr.Length];
397                    int count = Encoding.UTF8.GetBytes(commandLineStr.ToString(), 0,
398                        commandLineStr.Length, buffer, 0);
399                    client.Write(buffer, 0, count);
400                }
401                catch (UnauthorizedAccessException)
402                {
403                    //We can't connect to the pipe because the other instance of Eraser
404                    //is running with higher privileges than this instance. Tell the
405                    //user this is the case and show him how to resolve the issue.
406                    MessageBox.Show(S._("Another instance of Eraser is already running but it is " +
407                        "running with higher privileges than this instance of Eraser.\n\n" +
408                        "Eraser will now exit."), S._("Eraser"), MessageBoxButtons.OK,
409                        MessageBoxIcon.Information, MessageBoxDefaultButton.Button1,
410                        S.IsRightToLeft(null) ? MessageBoxOptions.RtlReading : 0);
411                }
412                catch (TimeoutException)
413                {
414                    //Can't do much: half a second is a reasonably long time to wait.
415                }
416                return false;
417            }
418        }
419
420        /// <summary>
421        /// Holds information required for an asynchronous call to
422        /// NamedPipeServerStream.BeginWaitForConnection.
423        /// </summary>
424        private struct ServerAsyncInfo
425        {
426            public NamedPipeServerStream Server;
427            public AutoResetEvent WaitHandle;
428        }
429
430        /// <summary>
431        /// Runs a background thread, monitoring for new connections to the server.
432        /// </summary>
433        private void ServerMain()
434        {
435            while (pipeServer.ThreadState != System.Threading.ThreadState.AbortRequested)
436            {
437                using (NamedPipeServerStream server = new NamedPipeServerStream(instanceID,
438                    PipeDirection.In, 1, PipeTransmissionMode.Message, PipeOptions.Asynchronous))
439                {
440                    ServerAsyncInfo async = new ServerAsyncInfo();
441                    async.Server = server;
442                    async.WaitHandle = new AutoResetEvent(false);
443                    IAsyncResult result = server.BeginWaitForConnection(WaitForConnection, async);
444
445                    //Wait for the operation to complete.
446                    if (result.AsyncWaitHandle.WaitOne())
447                        //It completed. Wait for the processing to complete.
448                        async.WaitHandle.WaitOne();
449                }
450            }
451        }
452
453        /// <summary>
454        /// Waits for new connections to be made to the server.
455        /// </summary>
456        /// <param name="result"></param>
457        private void WaitForConnection(IAsyncResult result)
458        {
459            ServerAsyncInfo async = (ServerAsyncInfo)result.AsyncState;
460
461            try
462            {
463                //We're done waiting for the connection
464                async.Server.EndWaitForConnection(result);
465
466                //Process the connection if the server was successfully connected.
467                if (async.Server.IsConnected)
468                {
469                    //Read the message from the secondary instance
470                    byte[] buffer = new byte[8192];
471                    string message = string.Empty;
472
473                    do
474                    {
475                        int lastRead = async.Server.Read(buffer, 0, buffer.Length);
476                        message += Encoding.UTF8.GetString(buffer, 0, lastRead);
477                    }
478                    while (!async.Server.IsMessageComplete);
479
480                    //Let the event handler process the message.
481                    OnNextInstance(this, message);
482                }
483            }
484            catch (ObjectDisposedException)
485            {
486            }
487            finally
488            {
489                //Reset the wait event
490                async.WaitHandle.Set();
491            }
492        }
493
494        /// <summary>
495        /// Gets the command line arguments this instance was started with.
496        /// </summary>
497        public string[] CommandLine { get; private set; }
498
499        /// <summary>
500        /// Gets whether another instance of the program is already running.
501        /// </summary>
502        public bool IsFirstInstance
503        {
504            get
505            {
506                return isFirstInstance;
507            }
508        }
509
510        /// <summary>
511        /// The main form for this program instance. This form will be shown when
512        /// run is called if it is non-null and if its Visible property is true.
513        /// </summary>
514        public Form MainForm { get; set; }
515
516        #region Events
517        /// <summary>
518        /// The prototype of event handlers procesing the InitInstance event.
519        /// </summary>
520        /// <param name="sender">The sender of the event.</param>
521        /// <returns>True if the MainForm property holds a valid reference to
522        /// a form, and the form should be displayed to the user.</returns>
523        public delegate bool InitInstanceFunction(object sender);
524
525        /// <summary>
526        /// The event object managing listeners to the instance initialisation event.
527        /// This event is raised when the first instance of the program is started
528        /// and this is where the program initialisation code should be.
529        /// </summary>
530        public event InitInstanceFunction InitInstance;
531
532        /// <summary>
533        /// Broadcasts the InitInstance event.
534        /// </summary>
535        /// <param name="sender">The sender of the event.</param>
536        /// <returns>True if the MainForm object should be shown.</returns>
537        private bool OnInitInstance(object sender)
538        {
539            if (InitInstance != null)
540                return InitInstance(sender);
541            return true;
542        }
543
544        /// <summary>
545        /// The prototype of event handlers procesing the NextInstance event.
546        /// </summary>
547        /// <param name="sender">The sender of the event</param>
548        public delegate void NextInstanceFunction(object sender, string message);
549
550        /// <summary>
551        /// The event object managing listeners to the next instance event. This
552        /// event is raised when a second instance of the program is started.
553        /// </summary>
554        public event NextInstanceFunction NextInstance;
555
556        /// <summary>
557        /// Broadcasts the NextInstance event.
558        /// </summary>
559        /// <param name="sender">The sender of the event.</param>
560        /// <param name="message">The message sent by the secondary instance.</param>
561        private void OnNextInstance(object sender, string message)
562        {
563            if (NextInstance != null)
564                NextInstance(sender, message);
565        }
566
567        /// <summary>
568        /// The prototype of event handlers procesing the ExitInstance event.
569        /// </summary>
570        /// <param name="sender">The sender of the event.</param>
571        public delegate void ExitInstanceFunction(object sender);
572
573        /// <summary>
574        /// The event object managing listeners to the exit instance event. This
575        /// event is raised when the first instance of the program is exited.
576        /// </summary>
577        public event ExitInstanceFunction ExitInstance;
578
579        /// <summary>
580        /// Broadcasts the ExitInstance event.
581        /// </summary>
582        /// <param name="sender">The sender of the event.</param>
583        private void OnExitInstance(object sender)
584        {
585            if (ExitInstance != null)
586                ExitInstance(sender);
587        }
588
589        /// <summary>
590        /// Broadcasts the ExitInstance event after getting the FormClosed event from
591        /// the application's main form.
592        /// </summary>
593        /// <param name="sender">The sender of the event.</param>
594        private void OnExitInstance(object sender, FormClosedEventArgs e)
595        {
596            if (ExitInstance != null)
597                ExitInstance(sender);
598        }
599        #endregion
600
601        #region Instance variables
602        /// <summary>
603        /// The Instance ID of this program, used to group program instances together.
604        /// </summary>
605        private string instanceID;
606
607        /// <summary>
608        /// The named mutex ensuring that only one instance of the application runs
609        /// at a time.
610        /// </summary>
611        private Mutex globalMutex;
612
613        /// <summary>
614        /// The thread maintaining the pipe server for secondary instances to connect to.
615        /// </summary>
616        private Thread pipeServer;
617
618        /// <summary>
619        /// Holds whether this instance of the program is the first instance.
620        /// </summary>
621        private bool isFirstInstance;
622        #endregion
623    }
624
625    class CommandLineProgram
626    {
627        #region Command Line parsing classes
628        /// <summary>
629        /// Manages a command line.
630        /// </summary>
631        public abstract class CommandLine
632        {
633            /// <summary>
634            /// Gets the CommandLine-derived object for the given command line.
635            /// </summary>
636            /// <param name="cmdParams">The raw arguments passed to the program.</param>
637            /// <returns>A processed CommandLine Object.</returns>
638            public static CommandLine Get(string[] cmdParams)
639            {
640                //Get the action.
641                if (cmdParams.Length < 1)
642                    throw new ArgumentException("An action must be specified.");
643                string action = cmdParams[0];
644
645                CommandLine result = null;
646                switch (action)
647                {
648                    case "help":
649                        result = new HelpCommandLine();
650                        break;
651                    case "querymethods":
652                        result = new QueryMethodsCommandLine();
653                        break;
654                    case "importtasklist":
655                        result = new ImportTaskListCommandLine();
656                        break;
657                    case "addtask":
658                        result = new AddTaskCommandLine();
659                        break;
660                }
661
662                if (result != null)
663                {
664                    result.Parse(cmdParams);
665                    return result;
666                }
667                else
668                    throw new ArgumentException(string.Format(CultureInfo.CurrentCulture,
669                        "Unknown action: {0}", action));
670            }
671
672            /// <summary>
673            /// Constructor.
674            /// </summary>
675            protected CommandLine()
676            {
677            }
678
679            /// <summary>
680            /// Parses the given command line arguments to the respective properties of
681            /// the class.
682            /// </summary>
683            /// <param name="cmdParams">The raw arguments passed to the program.</param>
684            /// <returns></returns>
685            public bool Parse(string[] cmdParams)
686            {
687                //Iterate over each argument, resolving them ourselves and letting
688                //subclasses resolve them if we don't know how to.
689                for (int i = 1; i != cmdParams.Length; ++i)
690                {
691                    if (IsParam(cmdParams[i], "quiet", "q"))
692                        Quiet = true;
693                    else if (!ResolveParameter(cmdParams[i]))
694                        throw new ArgumentException("Unknown argument: " + cmdParams[i]);
695                }
696
697                return true;
698            }
699
700            /// <summary>
701            /// Called when a parameter is not used by the current CommandLine object
702            /// for subclasses to handle their parameters.
703            /// </summary>
704            /// <param name="param">The parameter to resolve.</param>
705            /// <returns>Return true if the parameter was resolved and accepted.</returns>
706            protected virtual bool ResolveParameter(string param)
707            {
708                return false;
709            }
710
711            /// <summary>
712            /// Checks if the given <paramref name="parameter"/> refers to the
713            /// <paramref name="expectedParameter"/>, regardless of whether it is specified
714            /// with -, --, or /
715            /// </summary>
716            /// <param name="parameter">The parameter on the command line.</param>
717            /// <param name="expectedParameter">The parameter the program is looking for, without
718            /// the - or / prefix.</param>
719            /// <param name="shortParameter">The short parameter when used with a single hyphen,
720            /// without the - or / prefix.</param>
721            /// <returns>True if the parameter references the given expected parameter.</returns>
722            protected static bool IsParam(string parameter, string expectedParameter,
723                string shortParameter)
724            {
725                //Trivial case
726                if (parameter.Length < 1)
727                    return false;
728
729                //Extract the bits before the equal sign.
730                {
731                    int equalPos = parameter.IndexOf('=');
732                    if (equalPos != -1)
733                        parameter = parameter.Substring(0, equalPos);
734                }
735
736                //Get the first letter.
737                switch (parameter[0])
738                {
739                    case '-':
740                        //Can be a - or a --. Check for the second parameter
741                        if (parameter.Length < 2)
742                            //Nothing specified at the end... it's invalid.
743                            return false;
744
745                        if (parameter[1] == '-')
746                            return parameter.Substring(2) == expectedParameter;
747                        else if (string.IsNullOrEmpty(shortParameter))
748                            return parameter.Substring(1) == expectedParameter;
749                        else
750                            return parameter.Substring(1) == shortParameter;
751
752                    case '/':
753                        //The / can be used with both long and short parameters.
754                        parameter = parameter.Substring(1);
755                        return parameter == expectedParameter || (
756                            !string.IsNullOrEmpty(shortParameter) && parameter == shortParameter
757                        );
758
759                    default:
760                        return false;
761                }
762            }
763
764            /// <summary>
765            /// Gets the list of subparameters of the parameter. Subparameters are text
766            /// after the first =, separated by commas.
767            /// </summary>
768            /// <param name="param">The subparameter text to parse.</param>
769            /// <returns>The list of subparameters in the parameter.</returns>
770            protected static List<KeyValuePair<string, string>> GetSubParameters(string param)
771            {
772                List<KeyValuePair<string, string>> result =
773                    new List<KeyValuePair<string, string>>();
774                int lastPos = 0;
775                int commaPos = (param += ',').IndexOf(',');
776
777                while (commaPos != -1)
778                {
779                    //Check that the first parameter is not a \ otherwise this comma
780                    //is escaped
781                    if (commaPos == 0 ||                                    //No possibility of escaping
782                        (commaPos >= 1 && param[commaPos - 1] != '\\') ||   //Second character
783                        (commaPos >= 2 && param[commaPos - 2] == '\\'))     //Cannot be a \\ which is an escape
784                    {
785                        //Extract the current subparameter, and dissect the subparameter
786                        //at the first =.
787                        string subParam = param.Substring(lastPos, commaPos - lastPos);
788                        int equalPos = -1;
789
790                        do
791                        {
792                            equalPos = subParam.IndexOf('=', equalPos + 1);
793                            if (equalPos == -1)
794                            {
795                                result.Add(new KeyValuePair<string, string>(
796                                    UnescapeCommandLine(subParam), null));
797                            }
798                            else if (equalPos == 0 ||                               //No possibility of escaping
799                                (equalPos >= 1 && subParam[equalPos - 1] != '\\') ||//Second character
800                                (equalPos >= 2 && subParam[equalPos - 2] == '\\'))  //Double \\ which is an escape
801                            {
802                                result.Add(new KeyValuePair<string, string>(
803                                    UnescapeCommandLine(subParam.Substring(0, equalPos)),
804                                    UnescapeCommandLine(subParam.Substring(equalPos + 1))));
805                                break;
806                            }
807                        }
808                        while (equalPos != -1);
809                        lastPos = ++commaPos;
810                    }
811                    else
812                        ++commaPos;
813
814                    //Find the next ,
815                    commaPos = param.IndexOf(',', commaPos);
816                }
817
818                return result;
819            }
820
821            /// <summary>
822            /// Unescapes a subparameter command line, removing the extra
823            /// </summary>
824            /// <param name="param"></param>
825            /// <returns></returns>
826            private static string UnescapeCommandLine(string param)
827            {
828                StringBuilder result = new StringBuilder(param.Length);
829                for (int i = 0; i < param.Length; ++i)
830                    if (param[i] == '\\' && i < param.Length - 1)
831                        result.Append(param[++i]);
832                    else
833                        result.Append(param[i]);
834                return result.ToString();
835            }
836
837            /// <summary>
838            /// True if no console window should be created.
839            /// </summary>
840            public bool Quiet { get; private set; }
841        }
842
843        /// <summary>
844        /// Manages a help query command line.
845        /// </summary>
846        class HelpCommandLine : CommandLine
847        {
848            public HelpCommandLine()
849            {
850            }
851        }
852
853        class QueryMethodsCommandLine : CommandLine
854        {
855            public QueryMethodsCommandLine()
856            {
857            }
858        }
859
860        /// <summary>
861        /// Manages a command line for adding tasks to the global DirectExecutor
862        /// </summary>
863        class AddTaskCommandLine : CommandLine
864        {
865            /// <summary>
866            /// Constructor.
867            /// </summary>
868            public AddTaskCommandLine()
869            {
870                Schedule = Schedule.RunNow;
871                Targets = new List<ErasureTarget>();
872            }
873
874            protected override bool ResolveParameter(string param)
875            {
876                int equalPos = param.IndexOf('=');
877                if (IsParam(param, "method", "m"))
878                {
879                    if (equalPos == -1)
880                        throw new ArgumentException("--method must be specified with an Erasure " +
881                            "method GUID.");
882
883                    List<KeyValuePair<string, string>> subParams =
884                        GetSubParameters(param.Substring(equalPos + 1));
885                    ErasureMethod = new Guid(subParams[0].Key);
886                }
887                else if (IsParam(param, "schedule", "s"))
888                {
889                    if (equalPos == -1)
890                        throw new ArgumentException("--schedule must be specified with a Schedule " +
891                            "type.");
892
893                    List<KeyValuePair<string, string>> subParams =
894                        GetSubParameters(param.Substring(equalPos + 1));
895                    switch (subParams[0].Key)
896                    {
897                        case "now":
898                            Schedule = Schedule.RunNow;
899                            break;
900                        case "manually":
901                            Schedule = Schedule.RunManually;
902                            break;
903                        case "restart":
904                            Schedule = Schedule.RunOnRestart;
905                            break;
906                        default:
907                            throw new ArgumentException("Unknown schedule type: " + subParams[0].Key);
908                    }
909                }
910                else if (IsParam(param, "recycled", "r"))
911                {
912                    Targets.Add(new RecycleBinTarget());
913                }
914                else if (IsParam(param, "unused", "u"))
915                {
916                    if (equalPos == -1)
917                        throw new ArgumentException("--unused must be specified with the Volume " +
918                            "to erase.");
919
920                    //Create the UnusedSpace target for inclusion into the task.
921                    UnusedSpaceTarget target = new UnusedSpaceTarget();
922
923                    //Determine if cluster tips should be erased.
924                    target.EraseClusterTips = false;
925                    List<KeyValuePair<string, string>> subParams =
926                        GetSubParameters(param.Substring(equalPos + 1));
927                    foreach (KeyValuePair<string, string> kvp in subParams)
928                        if (kvp.Value == null && target.Drive == null)
929                            target.Drive = Path.GetFullPath(kvp.Key);
930                        else if (kvp.Key == "clusterTips")
931                            target.EraseClusterTips = true;
932                        else
933                            throw new ArgumentException("Unknown subparameter: " + kvp.Key);
934                    Targets.Add(target);
935                }
936                else if (IsParam(param, "dir", "d") || IsParam(param, "directory", null))
937                {
938                    if (equalPos == -1)
939                        throw new ArgumentException("--directory must be specified with the " +
940                            "directory to erase.");
941
942                    //Create the base target
943                    FolderTarget target = new FolderTarget();
944
945                    //Parse the subparameters.
946                    List<KeyValuePair<string, string>> subParams =
947                        GetSubParameters(param.Substring(equalPos + 1));
948                    foreach (KeyValuePair<string, string> kvp in subParams)
949                        if (kvp.Value == null && target.Path == null)
950                            target.Path = Path.GetFullPath(kvp.Key);
951                        else if (kvp.Key == "excludeMask")
952                        {
953                            if (kvp.Value == null)
954                                throw new ArgumentException("The exclude mask must be specified " +
955                                    "if the excludeMask subparameter is specified");
956                            target.ExcludeMask = kvp.Value;
957                        }
958                        else if (kvp.Key == "includeMask")
959                        {
960                            if (kvp.Value == null)
961                                throw new ArgumentException("The include mask must be specified " +
962                                    "if the includeMask subparameter is specified");
963                            target.IncludeMask = kvp.Value;
964                        }
965                        else if (kvp.Key == "delete")
966                            target.DeleteIfEmpty = true;
967                        else
968                            throw new ArgumentException("Unknown subparameter: " + kvp.Key);
969
970                    //Add the target to the list of targets
971                    Targets.Add(target);
972                }
973                else if (IsParam(param, "file", "f"))
974                {
975                    if (equalPos == -1)
976                        throw new ArgumentException("--file must be specified with the " +
977                            "file to erase.");
978
979                    //It's just a file!
980                    FileTarget target = new FileTarget();
981
982                    //Parse the subparameters.
983                    List<KeyValuePair<string, string>> subParams =
984                        GetSubParameters(param.Substring(equalPos + 1));
985                    foreach (KeyValuePair<string, string> kvp in subParams)
986                        if (kvp.Value == null && target.Path == null)
987                            target.Path = Path.GetFullPath(kvp.Key);
988                        else
989                            throw new ArgumentException("Unknown subparameter: " + kvp.Key);
990
991                    Targets.Add(target);
992                }
993                else
994                    return false;
995
996                return true;
997            }
998
999            /// <summary>
1000            /// The erasure method which the user specified on the command line.
1001            /// </summary>
1002            public Guid ErasureMethod { get; private set; }
1003
1004            /// <summary>
1005            /// The schedule for the current set of targets.
1006            /// </summary>
1007            public Schedule Schedule { get; private set; }
1008
1009            /// <summary>
1010            /// The list of targets which was specified on the command line.
1011            /// </summary>
1012            public List<ErasureTarget> Targets { get; private set; }
1013        }
1014
1015        /// <summary>
1016        /// Manages a command line for importing a task list into the global
1017        /// DirectExecutor.
1018        /// </summary>
1019        class ImportTaskListCommandLine : CommandLine
1020        {
1021            /// <summary>
1022            /// Constructor.
1023            /// </summary>
1024            public ImportTaskListCommandLine()
1025            {
1026            }
1027
1028            protected override bool ResolveParameter(string param)
1029            {
1030                if (!System.IO.File.Exists(param))
1031                    throw new ArgumentException(string.Format(CultureInfo.CurrentCulture,
1032                        "The file {0} does not exist.", param));
1033
1034                files.Add(param);
1035                return true;
1036            }
1037
1038            public ICollection<string> Files
1039            {
1040                get
1041                {
1042                    return files.AsReadOnly();
1043                }
1044            }
1045
1046            private List<string> files = new List<string>();
1047        }
1048        #endregion
1049
1050        /// <summary>
1051        /// Constructor.
1052        /// </summary>
1053        /// <param name="cmdParams">The raw command line arguments passed to the program.</param>
1054        public CommandLineProgram(string[] cmdParams)
1055        {
1056            try
1057            {
1058                Arguments = CommandLine.Get(cmdParams);
1059
1060                //If the user did not specify the quiet command line, then create the console.
1061                if (!Arguments.Quiet)
1062                    CreateConsole();
1063
1064                //Map actions to their handlers
1065                actionHandlers.Add(typeof(AddTaskCommandLine), AddTask);
1066                actionHandlers.Add(typeof(ImportTaskListCommandLine), ImportTaskList);
1067                actionHandlers.Add(typeof(QueryMethodsCommandLine), QueryMethods);
1068                actionHandlers.Add(typeof(HelpCommandLine), Help);
1069            }
1070            finally
1071            {
1072                if (Arguments == null || !Arguments.Quiet)
1073                    CreateConsole();
1074            }
1075        }
1076
1077        /// <summary>
1078        /// Runs the program, analogous to System.Windows.Forms.Application.Run.
1079        /// </summary>
1080        public void Run()
1081        {
1082            //Call the function handling the current command line.
1083            actionHandlers[Arguments.GetType()]();
1084        }
1085
1086        /// <summary>
1087        /// Creates a console for our application, setting the input/output streams to the
1088        /// defaults.
1089        /// </summary>
1090        private static void CreateConsole()
1091        {
1092            if (KernelApi.AllocConsole())
1093            {
1094                Console.SetOut(new StreamWriter(Console.OpenStandardOutput()));
1095                Console.SetIn(new StreamReader(Console.OpenStandardInput()));
1096            }
1097        }
1098
1099        /// <summary>
1100        /// Prints the command line help for Eraser.
1101        /// </summary>
1102        private static void CommandUsage()
1103        {
1104            Console.WriteLine(@"usage: Eraser <action> <arguments>
1105where action is
1106    help                    Show this help message.
1107    addtask                 Adds tasks to the current task list.
1108    querymethods            Lists all registered Erasure methods.
1109
1110global parameters:
1111    --quiet, -q             Do not create a Console window to display progress.
1112
1113parameters for help:
1114    eraser help
1115
1116    no parameters to set.
1117
1118parameters for addtask:
1119    eraser addtask [--method=<methodGUID>] [--schedule=(now|manually|restart)] (--recycled " +
1120@"| --unused=<volume> | --dir=<directory> | --file=<file>)[...]
1121
1122    --method, -m            The Erasure method to use.
1123    --schedule, -s          The schedule the task will follow. The value must
1124                            be one of:
1125            now             The task will be queued for immediate execution.
1126            manually        The task will be created but not queued for execution.
1127            restart         The task will be queued for execution when the
1128                            computer is next restarted.
1129                            This parameter defaults to now.
1130    --recycled, -r          Erases files and folders in the recycle bin
1131    --unused, -u            Erases unused space in the volume.
1132        optional arguments: --unused=<drive>[,clusterTips]
1133            clusterTips     If specified, the drive's files will have their
1134                            cluster tips erased.
1135    --dir, --directory, -d  Erases files and folders in the directory
1136        optional arguments: --dir=<directory>[,e=excludeMask][,i=includeMask][,delete]
1137            excludeMask     A wildcard expression for files and folders to
1138                            exclude.
1139            includeMask     A wildcard expression for files and folders to
1140                            include.
1141                            The include mask is applied before the exclude
1142                            mask.
1143            delete          Deletes the folder at the end of the erasure if
1144                            specified.
1145    --file, -f              Erases the specified file
1146
1147parameters for querymethods:
1148    eraser querymethods
1149
1150    no parameters to set.
1151
1152All arguments are case sensitive.");
1153            Console.Out.Flush();
1154        }
1155
1156        #region Action Handlers
1157        /// <summary>
1158        /// The command line arguments passed to the program.
1159        /// </summary>
1160        public CommandLine Arguments { get; private set; }
1161
1162        /// <summary>
1163        /// Prints the help text for Eraser (with copyright)
1164        /// </summary>
1165        private void Help()
1166        {
1167            Console.WriteLine(@"Eraser {0}
1168(c) 2008 The Eraser Project
1169Eraser is Open-Source Software: see http://eraser.heidi.ie/ for details.
1170", Assembly.GetExecutingAssembly().GetName().Version);
1171
1172            Console.Out.Flush();
1173            CommandUsage();
1174        }
1175
1176        /// <summary>
1177        /// Lists all registered erasure methods.
1178        /// </summary>
1179        /// <param name="commandLine">The command line parameters passed to the program.</param>
1180        private void QueryMethods()
1181        {
1182            //Output the header
1183            const string methodFormat = "{0,-2} {1,-39} {2}";
1184            Console.WriteLine(methodFormat, "", "Method", "GUID");
1185            Console.WriteLine(new string('-', 79));
1186
1187            //Refresh the list of erasure methods
1188            Dictionary<Guid, ErasureMethod> methods = ErasureMethodManager.Items;
1189            foreach (ErasureMethod method in methods.Values)
1190            {
1191                Console.WriteLine(methodFormat, (method is UnusedSpaceErasureMethod) ?
1192                    "U" : "", method.Name, method.Guid.ToString());
1193            }
1194        }
1195
1196        /// <summary>
1197        /// Parses the command line for tasks and adds them using the
1198        /// <see cref="RemoteExecutor"/> class.
1199        /// </summary>
1200        /// <param name="commandLine">The command line parameters passed to the program.</param>
1201        private void AddTask()
1202        {
1203            AddTaskCommandLine taskArgs = (AddTaskCommandLine)Arguments;
1204
1205            //Create the task, and set the method to use.
1206            Task task = new Task();
1207            ErasureMethod method = taskArgs.ErasureMethod == Guid.Empty ?
1208                ErasureMethodManager.Default :
1209                ErasureMethodManager.GetInstance(taskArgs.ErasureMethod);
1210
1211            foreach (ErasureTarget target in taskArgs.Targets)
1212            {
1213                target.Method = method;
1214                task.Targets.Add(target);
1215            }
1216
1217            //Check the number of tasks in the task.
1218            if (task.Targets.Count == 0)
1219                throw new ArgumentException("Tasks must contain at least one erasure target.");
1220
1221            //Set the schedule for the task.
1222            task.Schedule = taskArgs.Schedule;
1223
1224            //Send the task out.
1225            try
1226            {
1227                using (RemoteExecutorClient client = new RemoteExecutorClient())
1228                {
1229                    client.Run();
1230                    if (!client.IsConnected)
1231                    {
1232                        //The client cannot connect to the server. This probably means
1233                        //that the server process isn't running. Start an instance.
1234                        Process eraserInstance = Process.Start(
1235                            Assembly.GetExecutingAssembly().Location, "--quiet");
1236                        eraserInstance.WaitForInputIdle();
1237
1238                        client.Run();
1239                        if (!client.IsConnected)
1240                            throw new IOException("Eraser cannot connect to the running " +
1241                                "instance for erasures.");
1242                    }
1243
1244                    client.Tasks.Add(task);
1245                }
1246            }
1247            catch (UnauthorizedAccessException e)
1248            {
1249                //We can't connect to the pipe because the other instance of Eraser
1250                //is running with higher privileges than this instance.
1251                throw new UnauthorizedAccessException("Another instance of Eraser " +
1252                    "is already running but it is running with higher privileges than " +
1253                    "this instance of Eraser. Tasks cannot be added in this manner.\n\n" +
1254                    "Close the running instance of Eraser and start it again without " +
1255                    "administrator privileges, or run the command again as an " +
1256                    "administrator.", e);
1257            }
1258        }
1259
1260        /// <summary>
1261        /// Imports the given tasklists and adds them to the global Eraser instance.
1262        /// </summary>
1263        private void ImportTaskList()
1264        {
1265            ImportTaskListCommandLine cmdLine = (ImportTaskListCommandLine)Arguments;
1266
1267            //Import the task list
1268            try
1269            {
1270                using (RemoteExecutorClient client = new RemoteExecutorClient())
1271                {
1272                    client.Run();
1273                    if (!client.IsConnected)
1274                    {
1275                        //The client cannot connect to the server. This probably means
1276                        //that the server process isn't running. Start an instance.
1277                        Process eraserInstance = Process.Start(
1278                            Assembly.GetExecutingAssembly().Location, "--quiet");
1279                        eraserInstance.WaitForInputIdle();
1280
1281                        client.Run();
1282                        if (!client.IsConnected)
1283                            throw new IOException("Eraser cannot connect to the running " +
1284                                "instance for erasures.");
1285                    }
1286
1287                    foreach (string path in cmdLine.Files)
1288                        using (FileStream stream = new FileStream(path, FileMode.Open, FileAccess.Read))
1289                            client.Tasks.LoadFromStream(stream);
1290                }
1291            }
1292            catch (UnauthorizedAccessException e)
1293            {
1294                //We can't connect to the pipe because the other instance of Eraser
1295                //is running with higher privileges than this instance.
1296                throw new UnauthorizedAccessException("Another instance of Eraser " +
1297                    "is already running but it is running with higher privileges than " +
1298                    "this instance of Eraser. Tasks cannot be added in this manner.\n\n" +
1299                    "Close the running instance of Eraser and start it again without " +
1300                    "administrator privileges, or run the command again as an " +
1301                    "administrator.", e);
1302            }
1303        }
1304        #endregion
1305
1306        /// <summary>
1307        /// The prototype of an action handler in the class which executes an
1308        /// action as specified in the command line.
1309        /// </summary>
1310        private delegate void ActionHandler();
1311
1312        /// <summary>
1313        /// Matches an action handler to a function in the class.
1314        /// </summary>
1315        private Dictionary<Type, ActionHandler> actionHandlers =
1316            new Dictionary<Type, ActionHandler>();
1317    }
1318
1319    /// <summary>
1320    /// This class provides a File Based Win32 Registry using .NET
1321    /// binary serialization.
1322    /// </summary>
1323    internal class Settings : Manager.SettingsManager
1324    {
1325        /// <summary>
1326        /// IFFS based setting managment
1327        /// </summary>
1328        private class IFFSSettings : Manager.Settings, IDisposable
1329        {
1330            /// <summary>
1331            /// Constructor.
1332            /// </summary>
1333            /// <param name="key">The registry key to look for the settings in.</param>
1334            public IFFSSettings(IFFS.Handle handle)
1335            {
1336                this.handle = handle;
1337            }
1338
1339            #region IDisposable Members
1340
1341            ~IFFSSettings()
1342            {
1343                Dispose(false);
1344            }
1345
1346            public void Dispose()
1347            {
1348                Dispose(true);
1349                GC.SuppressFinalize(this);
1350            }
1351
1352            private void Dispose(bool disposing)
1353            {
1354                if (disposing)
1355                    handle.Close();
1356
1357                foreach (var hFile in hFiles.Values)
1358                    hFile.Close();
1359
1360                hFiles.Clear();
1361            }
1362
1363            #endregion
1364
1365            private Dictionary<String, IFFS.Handle> hFiles = new Dictionary<string, IFFS.Handle>();
1366            public override object this[string setting]
1367            {
1368                get
1369                {
1370                    IFFS.Handle hFile = null;
1371
1372                    // Open a handle to the settings file
1373                    if (hFiles.ContainsKey(setting))
1374                    {
1375                        hFile = hFiles[setting];
1376                    }
1377                    else
1378                    {
1379                        hFiles.Add(setting, hFile = IFFS.Open(handle, setting));
1380                    }
1381
1382                    try
1383                    {
1384                        if (hFile.IsValid)
1385                        {
1386                            return IFFS.GetFileData(hFile);
1387                        }
1388                    }
1389                    catch
1390                    {
1391                        hFiles.Remove(setting);
1392                        hFile.Close();
1393                        throw;
1394                    }
1395
1396                    return null;
1397                }
1398                set
1399                {
1400                    IFFS.Handle hFile = null;
1401
1402                    if (hFiles.ContainsKey(setting))
1403                    {
1404                        hFile = hFiles[setting];
1405                    }
1406                    else
1407                    {
1408                        hFiles.Add(setting, hFile = IFFS.CreateFile(handle, setting));
1409                    }
1410
1411                    if (!hFile.IsValid && hFiles.ContainsKey(setting))
1412                    {
1413                        hFiles.Remove(setting);
1414                        hFile.Close();
1415                        this[setting] = value;
1416                    }
1417                    else
1418                        try
1419                        {
1420                            if (value == null)
1421                            {
1422                                IFFS.Delete(hFile);
1423                            }
1424                            else
1425                            {
1426                                IFFS.FileNode file = IFFS.GetFile(hFile);
1427                                file.Data = value;
1428                                file.DataType = Type.GetTypeFromHandle(Type.GetTypeHandle(value));
1429                            }
1430                        }
1431                        catch
1432                        {
1433                            hFile.Close();
1434                            hFiles.Remove(setting);
1435                            throw;
1436                        }
1437                }
1438            }
1439
1440            /// <summary>
1441            /// The handle where the data is stored.
1442            /// </summary>
1443            private IFFS.Handle handle;
1444        }
1445
1446        public override void Save()
1447        {
1448            Stream.Position = 0;
1449            Stream.SetLength(0);
1450            Instance.Serialize(Stream);
1451        }
1452
1453#if DEBUG
1454        private static bool MonteCarloTestHasRun = false;
1455
1456        private void MonteCarloTest()
1457        {
1458            Random rand = new Random();
1459            const int MAX_ITERATIONS = 64;
1460
1461            IFFS.Handle hMonteCarlo = IFFS.CreateDirectory(Instance.RootHandle, "MonteCarlo");
1462
1463            if (!MonteCarloTestHasRun)
1464                for (int iterations = 0; iterations < MAX_ITERATIONS; iterations++)
1465                {
1466                    const int MAX_VALUE = 1024 * 16;
1467
1468                    for (int i = 0; i < MAX_VALUE; i++)
1469                        IFFS.CreateFile(hMonteCarlo, i.ToString());
1470
1471                    for (int i = 0; i < MAX_VALUE; i++)
1472                    {
1473                        IFFS.Handle hFile = IFFS.Open(hMonteCarlo, rand.Next(0, MAX_VALUE).ToString());
1474                        IFFS.GetFile(hFile).Data = i;
1475                    }
1476
1477                    Save();
1478                }
1479
1480            if (!IFFS.Delete(hMonteCarlo) && !MonteCarloTestHasRun)
1481                throw new Exception();
1482            else
1483                Save();
1484
1485            MonteCarloTestHasRun = true;
1486        }
1487#endif
1488
1489        protected override Manager.Settings GetSettings(Guid guid)
1490        {
1491            IFFS.Handle hPlugin = IFFS.CreateDirectory(Instance.RootHandle, guid.ToString());
1492
1493            return new IFFSSettings(hPlugin);
1494        }
1495
1496        /// <summary>
1497        /// Our encrypted filestream handle
1498        /// </summary>
1499        protected static CryptoFileStream Stream { get; set; }
1500
1501        /// <summary>
1502        /// Create a new instance of the IFFS manager.
1503        /// </summary>
1504        /// <param name="key">key for the underlying cryptostream</param>
1505        /// <param name="iv">iv for the underlying cryptostream</param>
1506        /// <throws>SerializationException</throws>
1507        /// <returns></returns>
1508        static IFFS CreateIFFSInstance(byte[] key, byte[] iv)
1509        {           
1510            // create a new file stream for our settings
1511            Stream = new CryptoFileStream(key, iv,
1512                Program.IFFSPath,
1513                FileMode.OpenOrCreate,/* doesnt matter if it was not created before */
1514                FileAccess.ReadWrite, /* we need IO for get/set */
1515                FileShare.None);      /* make sure we fully lock this file */
1516           
1517            IFFS newInstance = null;
1518                       
1519            if (Stream.Length != 0)
1520            {
1521                long OriginalPosition = Stream.Position;
1522                try
1523                {
1524                    newInstance = new IFFS(Stream);
1525                }
1526                catch (SerializationException)
1527                {
1528                    // XXX for future ref, if this happens could be
1529                    // due to invalid credentials dont erase the
1530                    // settings.
1531                    // lets roll back
1532                    Stream.Position = OriginalPosition;
1533                    throw;
1534                }
1535            }
1536
1537            if (newInstance == null)
1538            {
1539                return new IFFS();
1540            }
1541            else
1542            {
1543                return newInstance;
1544            }
1545        }
1546
1547        /// <summary>
1548        /// Global IFFS instance management methods
1549        /// </summary>
1550        private static IFFS instance = null;
1551        protected static IFFS Instance
1552        {
1553            get
1554            {
1555                if (instance != null)
1556                {
1557                    return instance;
1558                }
1559
1560                /// XXX currently no user login is implemented
1561                /// predefined and determinable key and iv is
1562                /// generated.
1563
1564                // Hash algorithm
1565                System.Security.Cryptography.SHA512Managed Hash = new System.Security.Cryptography.SHA512Managed();
1566
1567                // use a per user seed for our key/iv generation
1568                string UserID = System.Security.Principal.WindowsIdentity.GetCurrent().User.Value;
1569
1570                // Key and IV for our CryptoStream
1571                byte[] defaultKey = Hash.ComputeHash(Encoding.UTF8.GetBytes(UserID));
1572                byte[] defaultIV = Hash.ComputeHash(defaultKey);
1573
1574                try
1575                {
1576                    instance = CreateIFFSInstance(defaultKey, defaultIV);
1577                }
1578                catch (SerializationException)
1579                {
1580                    // this happens when serialization fails
1581                    // howerver if the wrong credentials were
1582                    // supplied this is not a corrupt archive.
1583                    // lets ask the user on the appropriate action.
1584
1585                    string message = S._("Could not load the setting. To clear current settings click 'ok'");
1586                    DialogResult answer = MessageBox.Show(message, S._("Eraser"),
1587                        MessageBoxButtons.RetryCancel, MessageBoxIcon.Error,
1588                        MessageBoxDefaultButton.Button1,
1589                        S.IsRightToLeft(null) ? MessageBoxOptions.RtlReading : 0);
1590
1591                    if (answer == DialogResult.Cancel)
1592                    {
1593                        throw new NotImplementedException("Not Implemented");
1594                    }
1595                    else // (answer == DialogResult.Ok)
1596                    {
1597                        // nothing to do
1598                    }
1599                }
1600
1601                if (instance == null)
1602                {
1603                    return instance = new IFFS();
1604                }
1605               
1606                return instance;
1607            }
1608            set
1609            {
1610                instance = value;
1611            }
1612        }
1613    }
1614
1615    internal class EraserSettings
1616    {
1617        /// <summary>
1618        /// Constructor.
1619        /// </summary>
1620        private EraserSettings()
1621        {
1622            settings = Manager.ManagerLibrary.Instance.SettingsManager.ModuleSettings;
1623        }
1624
1625        /// <summary>
1626        /// Gets the singleton instance of the Eraser UI Settings.
1627        /// </summary>
1628        /// <returns>The global instance of the Eraser UI settings.</returns>
1629        public static EraserSettings Get()
1630        {
1631            if (instance == null)
1632                instance = new EraserSettings();
1633            return instance;
1634        }
1635
1636        /// <summary>
1637        /// Gets or sets the LCID of the language which the UI should be displayed in.
1638        /// </summary>
1639        public string Language
1640        {
1641            get
1642            {
1643                return settings["Language"] == null ?
1644                    GetCurrentCulture().Name :
1645                    (string)settings["Language"];
1646            }
1647            set
1648            {
1649                settings["Language"] = value;
1650            }
1651        }
1652
1653        /// <summary>
1654        /// Gets or sets whether the Shell Extension should be loaded into Explorer.
1655        /// </summary>
1656        public bool IntegrateWithShell
1657        {
1658            get
1659            {
1660                return settings["IntegrateWithShell"] == null ?
1661                    true : Convert.ToBoolean(settings["IntegrateWithShell"],
1662                        CultureInfo.InvariantCulture);
1663            }
1664            set
1665            {
1666                settings["IntegrateWithShell"] = value;
1667            }
1668        }
1669
1670        /// <summary>
1671        /// Gets or sets a value on whether the main frame should be minimised to the
1672        /// system notification area.
1673        /// </summary>
1674        public bool HideWhenMinimised
1675        {
1676            get
1677            {
1678                return settings["HideWhenMinimised"] == null ?
1679                    true : Convert.ToBoolean(settings["HideWhenMinimised"],
1680                        CultureInfo.InvariantCulture);
1681            }
1682            set
1683            {
1684                settings["HideWhenMinimised"] = value;
1685            }
1686        }
1687
1688        /// <summary>
1689        /// Gets ot setts a value whether tasks which were completed successfully
1690        /// should be removed by the Eraser client.
1691        /// </summary>
1692        public bool ClearCompletedTasks
1693        {
1694            get
1695            {
1696                return settings["ClearCompletedTasks"] == null ?
1697                    true : Convert.ToBoolean(settings["ClearCompletedTasks"],
1698                        CultureInfo.InvariantCulture);
1699            }
1700            set
1701            {
1702                settings["ClearCompletedTasks"] = value;
1703            }
1704        }
1705
1706        /// <summary>
1707        /// Gets the current UI culture, correct to the top-level culture (i.e., English
1708        /// instead of English (United Kingdom))
1709        /// </summary>
1710        /// <returns>The CultureInfo of the current UI culture, correct to the top level.</returns>
1711        private static CultureInfo GetCurrentCulture()
1712        {
1713            CultureInfo culture = CultureInfo.CurrentUICulture;
1714            while (culture.Parent != CultureInfo.InvariantCulture)
1715                culture = culture.Parent;
1716
1717            return culture;
1718        }
1719
1720        /// <summary>
1721        /// The data store behind the object.
1722        /// </summary>
1723        private Manager.Settings settings;
1724
1725        /// <summary>
1726        /// The global instance of the settings class.
1727        /// </summary>
1728        private static EraserSettings instance;
1729    }
1730}
Note: See TracBrowser for help on using the repository browser.