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

Revision 740, 23.5 KB checked in by lowjoel, 6 years ago (diff)

Allow Eraser to accept remote connections from other sources on the same machine.

  • 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 Eraser.Manager;
27using Eraser.Util;
28using Microsoft.Win32;
29using System.IO;
30using System.Runtime.Serialization.Formatters.Binary;
31using System.Globalization;
32using System.Reflection;
33using System.Diagnostics;
34
35namespace Eraser
36{
37    static class Program
38    {
39        /// <summary>
40        /// The main entry point for the application.
41        /// </summary>
42        [STAThread]
43        static void Main(string[] commandLine)
44        {
45            //Trivial case: no command parameters
46            if (commandLine.Length == 0)
47                GUIMain(false);
48
49            //Determine if the sole parameter is --restart; if it is, start the GUI
50            //passing isRestart as true. Otherwise, we're a console application.
51            else if (commandLine.Length == 1)
52            {
53                if (commandLine[0] == "/restart" || commandLine[0] == "--restart")
54                {
55                    GUIMain(true);
56                }
57                else
58                {
59                    CommandMain(commandLine);
60                }
61            }
62
63            //The other trivial case: definitely a console application.
64            else
65                CommandMain(commandLine);
66        }
67
68        /// <summary>
69        /// Runs Eraser as a command-line application.
70        /// </summary>
71        /// <param name="commandLine">The command line parameters passed to Eraser.</param>
72        private static void CommandMain(string[] commandLine)
73        {
74            //True if the user specified a quiet command.
75            bool isQuiet = false;
76
77            try
78            {
79                CommandLineProgram program = new CommandLineProgram(commandLine);
80                isQuiet = program.Arguments.Quiet;
81
82                using (ManagerLibrary library = new ManagerLibrary(new Settings()))
83                using (Program.eraserClient = new RemoteExecutorClient())
84                {
85                    if (!((RemoteExecutorClient)Program.eraserClient).Connect())
86                    {
87                        //The client cannot connect to the server. This probably means
88                        //that the server process isn't running. Start an instance.
89                        Process eraserInstance = Process.Start(
90                            Assembly.GetExecutingAssembly().Location);
91                        eraserInstance.WaitForInputIdle();
92                    }
93
94                    eraserClient.Run();
95                    program.Run();
96                }
97            }
98            catch (Exception e)
99            {
100                Console.WriteLine(e.Message);
101            }
102            finally
103            {
104                //Flush the buffered output to the console
105                Console.Out.Flush();
106
107                //Don't ask for a key to press if the user specified Quiet
108                if (!isQuiet)
109                {
110                    Console.Write("\nPress any key to continue . . . ");
111                    Console.Out.Flush();
112                    Console.ReadLine();
113                }
114
115                KernelAPI.FreeConsole();
116            }
117        }
118
119        /// <summary>
120        /// Runs Eraser as a GUI application.
121        /// </summary>
122        /// <param name="isRestart">True if the program was passed the --restart
123        /// switch.</param>
124        private static void GUIMain(bool isRestart)
125        {
126            Application.EnableVisualStyles();
127            Application.SetCompatibleTextRenderingDefault(false);
128            Application.SafeTopLevelCaptionFormat = S._("Eraser");
129
130            using (ManagerLibrary library = new ManagerLibrary(new Settings()))
131            using (eraserClient = new RemoteExecutorServer())
132            {
133                //Set our UI language
134                EraserSettings settings = new EraserSettings();
135                System.Threading.Thread.CurrentThread.CurrentUICulture =
136                    new CultureInfo(settings.Language);
137
138                //Load the task list
139                if (settings.TaskList != null)
140                    using (MemoryStream stream = new MemoryStream(settings.TaskList))
141                        try
142                        {
143                            eraserClient.LoadTaskList(stream);
144                        }
145                        catch (Exception)
146                        {
147                            settings.TaskList = null;
148                            MessageBox.Show(S._("Could not load task list. All task entries have " +
149                                "been lost."), S._("Eraser"), MessageBoxButtons.OK,
150                                MessageBoxIcon.Error);
151                        }
152
153                //Create the main form
154                MainForm form = new MainForm();
155
156                //Run tasks which are meant to be run on restart
157                if (isRestart)
158                {
159                    eraserClient.QueueRestartTasks();
160                }
161
162                //Run the program
163                eraserClient.Run();
164                Application.Run(form);
165
166                //Save the task list
167                using (MemoryStream stream = new MemoryStream())
168                {
169                    eraserClient.SaveTaskList(stream);
170                    settings.TaskList = stream.ToArray();
171                }
172            }
173        }
174
175        /// <summary>
176        /// The global Executor instance.
177        /// </summary>
178        public static Executor eraserClient;
179
180        /// <summary>
181        /// Handles commands passed to the program
182        /// </summary>
183        /// <param name="arguments">The arguments to the command</param>
184        private delegate void CommandHandler(Dictionary<string, string> arguments);
185    }
186
187    class CommandLineProgram
188    {
189        #region Command Line parsing classes
190        /// <summary>
191        /// Manages a command line.
192        /// </summary>
193        public class CommandLine
194        {
195            /// <summary>
196            /// Constructor.
197            /// </summary>
198            /// <param name="cmdParams">The raw arguments passed to the program.</param>
199            public CommandLine(string[] cmdParams)
200            {
201                //Get the action.
202                if (cmdParams.Length < 1)
203                    throw new ArgumentException("An action must be specified.");
204                Action = cmdParams[0];
205
206                //Iterate over each argument, resolving them ourselves and letting
207                //subclasses resolve them if we don't know how to.
208                for (int i = 1; i != cmdParams.Length; ++i)
209                {
210                    if (IsParam(cmdParams[i], "quiet", "q"))
211                        Quiet = true;
212                    else if (!ResolveParameter(cmdParams[i]))
213                        throw new ArgumentException("Unknown argument: " + cmdParams[i]);
214                }
215            }
216
217            /// <summary>
218            /// Called when a parameter is not used by the current CommandLine object
219            /// for subclasses to handle their parameters.
220            /// </summary>
221            /// <param name="param">The parameter to resolve.</param>
222            /// <returns>Return true if the parameter was resolved and accepted.</returns>
223            virtual protected bool ResolveParameter(string param)
224            {
225                return false;
226            }
227
228            /// <summary>
229            /// Checks if the given <paramref name="parameter"/> refers to the
230            /// <paramref name="expectedParameter"/>, regardless of whether it is specified
231            /// with -, --, or /
232            /// </summary>
233            /// <param name="parameter">The parameter on the command line.</param>
234            /// <param name="expectedParameter">The parameter the program is looking for, without
235            /// the - or / prefix.</param>
236            /// <param name="shortParameter">The short parameter when used with a single hyphen,
237            /// without the - or / prefix.</param>
238            /// <returns>True if the parameter references the given expected parameter.</returns>
239            protected static bool IsParam(string parameter, string expectedParameter,
240                string shortParameter)
241            {
242                //Trivial case
243                if (parameter.Length < 1)
244                    return false;
245
246                //Extract the bits before the equal sign.
247                {
248                    int equalPos = parameter.IndexOf('=');
249                    if (equalPos != -1)
250                        parameter = parameter.Substring(0, equalPos);
251                }
252
253                //Get the first letter.
254                switch (parameter[0])
255                {
256                    case '-':
257                        //Can be a - or a --. Check for the second parameter
258                        if (parameter.Length < 2)
259                            //Nothing specified at the end... it's invalid.
260                            return false;
261
262                        if (parameter[1] == '-')
263                            return parameter.Substring(2) == expectedParameter;
264                        else if (shortParameter == null || shortParameter == string.Empty)
265                            return parameter.Substring(1) == expectedParameter;
266                        else
267                            return parameter.Substring(1) == shortParameter;
268                       
269                    case '/':
270                        //The / can be used with both long and short parameters.
271                        parameter = parameter.Substring(1);
272                        return parameter == expectedParameter || (
273                            shortParameter != null && shortParameter != string.Empty &&
274                            parameter == shortParameter
275                        );
276
277                    default:
278                        return false;
279                }
280            }
281
282            /// <summary>
283            /// Gets the list of subparameters of the parameter. Subparameters are text
284            /// after the first =, separated by commas.
285            /// </summary>
286            /// <param name="param">The subparameter text to parse.</param>
287            /// <returns>The list of subparameters in the parameter.</returns>
288            protected static List<KeyValuePair<string, string>> GetSubParameters(string param)
289            {
290                List<KeyValuePair<string, string>> result =
291                    new List<KeyValuePair<string, string>>();
292                int lastPos = 0;
293                int commaPos = (param += ',').IndexOf(',');
294
295                while (commaPos != -1)
296                {
297                    //Extract the current subparameter, and dissect the subparameter at
298                    //the first =.
299                    string subParam = param.Substring(lastPos, commaPos - lastPos);
300                    int equalPos = subParam.IndexOf('=');
301                    if (equalPos == -1)
302                        result.Add(new KeyValuePair<string, string>(subParam, null));
303                    else
304                        result.Add(new KeyValuePair<string, string>(subParam.Substring(0, equalPos),
305                            subParam.Substring(equalPos + 1)));
306
307                    //Find the next ,
308                    lastPos = ++commaPos;
309                    commaPos = param.IndexOf(',', commaPos);
310                }
311
312                return result;
313            }
314
315            /// <summary>
316            /// The action that the command line specifies.
317            /// </summary>
318            public string Action
319            {
320                get
321                {
322                    return action;
323                }
324                private set
325                {
326                    action = value;
327                }
328            }
329
330            /// <summary>
331            /// True if no console window should be created.
332            /// </summary>
333            public bool Quiet
334            {
335                get
336                {
337                    return quiet;
338                }
339                private set
340                {
341                    quiet = value;
342                }
343            }
344
345            private string action;
346            private bool quiet;
347        }
348
349        /// <summary>
350        /// Manages a command line for adding tasks to the global DirectExecutor
351        /// </summary>
352        class AddTaskCommandLine : CommandLine
353        {
354            /// <summary>
355            /// Constructor.
356            /// </summary>
357            /// <param name="cmdParams">The raw command line arguments passed to the program.</param>
358            public AddTaskCommandLine(string[] cmdParams)
359                : base(cmdParams)
360            {
361            }
362
363            protected override bool ResolveParameter(string param)
364            {
365                int equalPos = param.IndexOf('=');
366                if (IsParam(param, "method", "m"))
367                {
368                    if (equalPos == -1)
369                        throw new ArgumentException("--method must be specified with an Erasure " +
370                            "method GUID.");
371                    ErasureMethod = new Guid(param.Substring(equalPos + 1));
372                }
373                else if (IsParam(param, "recycled", "r"))
374                {
375                    targets.Add(new Task.RecycleBin());
376                }
377                else if (IsParam(param, "unused", "u"))
378                {
379                    if (equalPos == -1)
380                        throw new ArgumentException("--unused must be specified with the Volume " +
381                            "to erase.");
382
383                    //Create the UnusedSpace target for inclusion into the task.
384                    Task.UnusedSpace target = new Task.UnusedSpace();
385
386                    //Determine if cluster tips should be erased.
387                    target.EraseClusterTips = false;
388                    List<KeyValuePair<string, string>> subParams =
389                        GetSubParameters(param.Substring(equalPos + 1));
390                    foreach (KeyValuePair<string, string> kvp in subParams)
391                        if (kvp.Value == null && target.Drive == null)
392                            target.Drive = kvp.Key;
393                        else if (kvp.Key == "clusterTips")
394                            target.EraseClusterTips = true;
395                        else
396                            throw new ArgumentException("Unknown subparameter: " + kvp.Key);
397                    targets.Add(target);
398                }
399                else if (IsParam(param, "dir", "d") || IsParam(param, "directory", null))
400                {
401                    if (equalPos == -1)
402                        throw new ArgumentException("--directory must be specified with the " +
403                            "directory to erase.");
404
405                    //Create the base target
406                    Task.Folder target = new Task.Folder();
407
408                    //Parse the subparameters.
409                    List<KeyValuePair<string, string>> subParams =
410                        GetSubParameters(param.Substring(equalPos + 1));
411                    foreach (KeyValuePair<string, string> kvp in subParams)
412                        if (kvp.Value == null && target.Path == null)
413                            target.Path = kvp.Key;
414                        else if (kvp.Key == "excludeMask")
415                        {
416                            if (kvp.Value == null)
417                                throw new ArgumentException("The exclude mask must be specified " +
418                                    "if the excludeMask subparameter is specified");
419                            target.ExcludeMask = kvp.Value;
420                        }
421                        else if (kvp.Key == "includeMask")
422                        {
423                            if (kvp.Value == null)
424                                throw new ArgumentException("The include mask must be specified " +
425                                    "if the includeMask subparameter is specified");
426                            target.IncludeMask = kvp.Value;
427                        }
428                        else if (kvp.Key == "delete")
429                            target.DeleteIfEmpty = true;
430                        else
431                            throw new ArgumentException("Unknown subparameter: " + kvp.Key);
432
433                    //Add the target to the list of targets
434                    targets.Add(target);
435                }
436                else
437                {
438                    //It's just a file!
439                    Task.File target = new Task.File();
440                    target.Path = param;
441                    targets.Add(target);
442                }
443
444                return true;
445            }
446
447            /// <summary>
448            /// The erasure method which the user specified on the command line.
449            /// </summary>
450            public Guid ErasureMethod
451            {
452                get
453                {
454                    return erasureMethod;
455                }
456                private set
457                {
458                    erasureMethod = value;
459                }
460            }
461
462            /// <summary>
463            /// The list of targets which was specified on the command line.
464            /// </summary>
465            public List<Task.ErasureTarget> Targets
466            {
467                get
468                {
469                    return new List<Task.ErasureTarget>(targets.ToArray());
470                }
471                set
472                {
473                    targets = value;
474                }
475            }
476
477            private Guid erasureMethod;
478            private List<Task.ErasureTarget> targets = new List<Task.ErasureTarget>();
479        }
480        #endregion
481
482        /// <summary>
483        /// Constructor.
484        /// </summary>
485        /// <param name="cmdParams">The raw command line arguments passed to the program.</param>
486        public CommandLineProgram(string[] cmdParams)
487        {
488            try
489            {
490                //Parse the command line arguments.
491                if (cmdParams.Length < 1)
492                    throw new ArgumentException("An action must be specified.");
493
494                switch (cmdParams[0])
495                {
496                    case "addtask":
497                        Arguments = new AddTaskCommandLine(cmdParams);
498                        break;
499                    case "querymethods":
500                    case "help":
501                    default:
502                        Arguments = new CommandLine(cmdParams);
503                        break;
504                }
505
506                //If the user did not specify the quiet command line, then create the console.
507                if (!Arguments.Quiet)
508                    CreateConsole();
509
510                //Map actions to their handlers
511                actionHandlers.Add("addtask", AddTask);
512                actionHandlers.Add("querymethods", QueryMethods);
513                actionHandlers.Add("help", Help);
514            }
515            finally
516            {
517                if (Arguments == null || !Arguments.Quiet)
518                    CreateConsole();
519            }
520        }
521
522        /// <summary>
523        /// Runs the program, analogous to System.Windows.Forms.Application.Run.
524        /// </summary>
525        public void Run()
526        {
527            //Call the function handling the current command line.
528            actionHandlers[Arguments.Action]();
529        }
530
531        /// <summary>
532        /// Creates a console for our application, setting the input/output streams to the
533        /// defaults.
534        /// </summary>
535        private void CreateConsole()
536        {
537            if (KernelAPI.AllocConsole())
538            {
539                Console.SetOut(new StreamWriter(Console.OpenStandardOutput()));
540                Console.SetIn(new StreamReader(Console.OpenStandardInput()));
541            }
542        }
543
544        /// <summary>
545        /// Prints the command line help for Eraser.
546        /// </summary>
547        private static void CommandUsage()
548        {
549            Console.WriteLine(@"usage: Eraser <action> <arguments>
550where action is
551    addtask                 Adds tasks to the current task list.
552    querymethods            Lists all registered Erasure methods.
553
554global parameters:
555    --quiet, -q             Do not create a Console window to display progress.
556
557parameters for addtask:
558    eraser addtask --method=<methodGUID> (--recycled | --unused=<volume> | " +
559@"--dir=<directory> | [file1 [file2 [...]]])
560
561    --method, -m            The Erasure method to use.
562    --recycled, -r          Erases files and folders in the recycle bin
563    --unused, -u            Erases unused space in the volume.
564        optional arguments: --unused=<drive>[,clusterTips]
565            clusterTips     If specified, the drive's files will have their cluster tips
566                            erased.
567    --dir, --directory, -d  Erases files and folders in the directory
568        optional arguments: --dir=<directory>[,e=excludeMask][,i=includeMask][,delete]
569            excludeMask     A wildcard expression for files and folders to exclude.
570            includeMask     A wildcard expression for files and folders to include.
571                            The include mask is applied before the exclude mask.
572            delete          Deletes the folder at the end of the erasure if specified.
573    file1 ... fileN         The list of files to erase.
574
575parameters for querymethods:
576    eraser querymethods
577
578    no parameters to set.
579
580All arguments are case sensitive.");
581            Console.Out.Flush();
582        }
583
584        #region Action Handlers
585        /// <summary>
586        /// The command line arguments passed to the program.
587        /// </summary>
588        public CommandLine Arguments
589        {
590            get
591            {
592                return arguments;
593            }
594            private set
595            {
596                arguments = value;
597            }
598        }
599
600        /// <summary>
601        /// Prints the help text for Eraser (with copyright)
602        /// </summary>
603        private void Help()
604        {
605            Console.WriteLine(@"Eraser {0}
606(c) 2008 The Eraser Project
607Eraser is Open-Source Software: see http://eraser.heidi.ie/ for details.
608", Assembly.GetExecutingAssembly().GetName().Version);
609
610            Console.Out.Flush();
611            CommandUsage();
612        }
613
614        /// <summary>
615        /// Lists all registered erasure methods.
616        /// </summary>
617        /// <param name="commandLine">The command line parameters passed to the program.</param>
618        private void QueryMethods()
619        {
620            //Output the header
621            const string methodFormat = "{0,-2} {1,-39} {2}";
622            Console.WriteLine(methodFormat, "", "Method", "GUID");
623            Console.WriteLine(new string('-', 79));
624
625            //Refresh the list of erasure methods
626            Dictionary<Guid, ErasureMethod> methods = ErasureMethodManager.GetAll();
627            foreach (ErasureMethod method in methods.Values)
628            {
629                Console.WriteLine(methodFormat, (method is UnusedSpaceErasureMethod) ?
630                    "U" : "", method.Name, method.GUID.ToString());
631            }
632        }
633
634        /// <summary>
635        /// Parses the command line for tasks and adds them using the
636        /// <see cref="RemoteExecutor"/> class.
637        /// </summary>
638        /// <param name="commandLine">The command line parameters passed to the program.</param>
639        private void AddTask()
640        {
641            AddTaskCommandLine arguments = (AddTaskCommandLine)Arguments;
642           
643            //Create the task, and set the method to use.
644            Task task = new Task();
645            ErasureMethod method = arguments.ErasureMethod == Guid.Empty ? 
646                ErasureMethodManager.Default :
647                ErasureMethodManager.GetInstance(arguments.ErasureMethod);
648            foreach (Task.ErasureTarget target in arguments.Targets)
649            {
650                target.Method = method;
651                task.Targets.Add(target);
652            }
653
654            //Send the task out.
655            Program.eraserClient.AddTask(ref task);
656        }
657        #endregion
658
659        /// <see cref="Arguments"/>
660        private CommandLine arguments;
661
662        /// <summary>
663        /// The prototype of an action handler in the class which executes an
664        /// action as specified in the command line.
665        /// </summary>
666        private delegate void ActionHandler();
667
668        /// <summary>
669        /// Matches an action handler to a function in the class.
670        /// </summary>
671        private Dictionary<string, ActionHandler> actionHandlers =
672            new Dictionary<string, ActionHandler>();
673    }
674
675    internal class Settings : Manager.SettingsManager
676    {
677        /// <summary>
678        /// Registry-based storage backing for the Settings class.
679        /// </summary>
680        private class RegistrySettings : Manager.Settings
681        {
682            /// <summary>
683            /// Constructor.
684            /// </summary>
685            /// <param name="key">The registry key to look for the settings in.</param>
686            public RegistrySettings(Guid pluginID, RegistryKey key)
687            {
688                this.key = key;
689            }
690
691            public override object this[string setting]
692            {
693                get
694                {
695                    byte[] currentSetting = (byte[])key.GetValue(setting, null);
696                    if (currentSetting != null && currentSetting.Length != 0)
697                        using (MemoryStream stream = new MemoryStream(currentSetting))
698                            try
699                            {
700                                return new BinaryFormatter().Deserialize(stream);
701                            }
702                            catch (Exception)
703                            {
704                                key.DeleteValue(setting);
705                                MessageBox.Show(S._("Could not load the setting {0} for plugin {1}. " +
706                                    "The setting has been lost.", key, pluginID.ToString()),
707                                    S._("Eraser"), MessageBoxButtons.OK, MessageBoxIcon.Error);
708                            }
709
710                    return null;
711                }
712                set
713                {
714                    if (value == null)
715                    {
716                        key.DeleteValue(setting);
717                    }
718                    else
719                    {
720                        using (MemoryStream stream = new MemoryStream())
721                        {
722                            new BinaryFormatter().Serialize(stream, value);
723                            key.SetValue(setting, stream.ToArray(), RegistryValueKind.Binary);
724                        }
725                    }
726                }
727            }
728
729            /// <summary>
730            /// The GUID of the plugin whose settings this object is storing.
731            /// </summary>
732            private Guid pluginID;
733
734            /// <summary>
735            /// The registry key where the data is stored.
736            /// </summary>
737            private RegistryKey key;
738        }
739
740        public override void Save()
741        {
742        }
743
744        protected override Manager.Settings GetSettings(Guid guid)
745        {
746            //Open the registry key containing the settings
747            const string eraserKeyPath = @"SOFTWARE\Eraser\Eraser 6";
748            RegistryKey eraserKey = Registry.CurrentUser.OpenSubKey(eraserKeyPath, true);
749            if (eraserKey == null)
750                eraserKey = Registry.CurrentUser.CreateSubKey(eraserKeyPath);
751
752            RegistryKey pluginsKey = eraserKey.OpenSubKey(guid.ToString(), true);
753            if (pluginsKey == null)
754                pluginsKey = eraserKey.CreateSubKey(guid.ToString());
755
756            //Return the Settings object.
757            return new RegistrySettings(guid, pluginsKey);
758        }
759    }
760
761    internal class EraserSettings
762    {
763        public EraserSettings()
764        {
765            settings = Manager.ManagerLibrary.Instance.SettingsManager.ModuleSettings;
766        }
767
768        /// <summary>
769        /// Gets or sets the task list, serialised in binary form by the Manager assembly.
770        /// </summary>
771        public byte[] TaskList
772        {
773            get
774            {
775                return (byte[])settings["TaskList"];
776            }
777            set
778            {
779                settings["TaskList"] = value;
780            }
781        }
782
783        /// <summary>
784        /// Gets or sets the LCID of the language which the UI should be displayed in.
785        /// </summary>
786        public string Language
787        {
788            get
789            {
790                return settings["Language"] == null ? 
791                    GetCurrentCulture().Name :
792                    (string)settings["Language"];
793            }
794            set
795            {
796                settings["Language"] = value;
797            }
798        }
799
800        /// <summary>
801        /// Gets or sets a value on whether the main frame should be minimised to the
802        /// system notification area.
803        /// </summary>
804        public bool HideWhenMinimised
805        {
806            get
807            {
808                return settings["HideWhenMinimised"] == null ?
809                    true : (bool)settings["HideWhenMinimised"];
810            }
811            set
812            {
813                settings["HideWhenMinimised"] = value;
814            }
815        }
816
817        /// <summary>
818        /// Gets the current UI culture, correct to the top-level culture (i.e., English
819        /// instead of English (United Kingdom))
820        /// </summary>
821        /// <returns>The CultureInfo of the current UI culture, correct to the top level.</returns>
822        private static CultureInfo GetCurrentCulture()
823        {
824            CultureInfo culture = CultureInfo.CurrentUICulture;
825            while (culture.Parent != CultureInfo.InvariantCulture)
826                culture = culture.Parent;
827
828            return culture;
829        }
830
831        private Manager.Settings settings;
832    }
833}
Note: See TracBrowser for help on using the repository browser.