diff options
author | UbitUmarov | 2015-09-01 11:43:07 +0100 |
---|---|---|
committer | UbitUmarov | 2015-09-01 11:43:07 +0100 |
commit | fb78b182520fc9bb0f971afd0322029c70278ea6 (patch) | |
tree | b4e30d383938fdeef8c92d1d1c2f44bb61d329bd /OpenSim/Framework/Console | |
parent | lixo (diff) | |
parent | Mantis #7713: fixed bug introduced by 1st MOSES patch. (diff) | |
download | opensim-SC-fb78b182520fc9bb0f971afd0322029c70278ea6.zip opensim-SC-fb78b182520fc9bb0f971afd0322029c70278ea6.tar.gz opensim-SC-fb78b182520fc9bb0f971afd0322029c70278ea6.tar.bz2 opensim-SC-fb78b182520fc9bb0f971afd0322029c70278ea6.tar.xz |
Merge remote-tracking branch 'os/master'
Diffstat (limited to 'OpenSim/Framework/Console')
-rw-r--r-- | OpenSim/Framework/Console/AssemblyInfo.cs | 58 | ||||
-rw-r--r-- | OpenSim/Framework/Console/CommandConsole.cs | 793 | ||||
-rwxr-xr-x | OpenSim/Framework/Console/ConsoleBase.cs | 180 | ||||
-rw-r--r-- | OpenSim/Framework/Console/ConsoleDisplayList.cs | 112 | ||||
-rw-r--r-- | OpenSim/Framework/Console/ConsoleDisplayTable.cs | 155 | ||||
-rw-r--r-- | OpenSim/Framework/Console/ConsoleDisplayUtil.cs | 48 | ||||
-rwxr-xr-x | OpenSim/Framework/Console/ConsolePluginCommand.cs | 139 | ||||
-rw-r--r-- | OpenSim/Framework/Console/ConsoleUtil.cs | 387 | ||||
-rw-r--r-- | OpenSim/Framework/Console/LocalConsole.cs | 585 | ||||
-rw-r--r-- | OpenSim/Framework/Console/MockConsole.cs | 89 | ||||
-rw-r--r-- | OpenSim/Framework/Console/OpenSimAppender.cs | 86 | ||||
-rw-r--r-- | OpenSim/Framework/Console/RemoteConsole.cs | 513 |
12 files changed, 3145 insertions, 0 deletions
diff --git a/OpenSim/Framework/Console/AssemblyInfo.cs b/OpenSim/Framework/Console/AssemblyInfo.cs new file mode 100644 index 0000000..67af471 --- /dev/null +++ b/OpenSim/Framework/Console/AssemblyInfo.cs | |||
@@ -0,0 +1,58 @@ | |||
1 | /* | ||
2 | * Copyright (c) Contributors, http://opensimulator.org/ | ||
3 | * See CONTRIBUTORS.TXT for a full list of copyright holders. | ||
4 | * | ||
5 | * Redistribution and use in source and binary forms, with or without | ||
6 | * modification, are permitted provided that the following conditions are met: | ||
7 | * * Redistributions of source code must retain the above copyright | ||
8 | * notice, this list of conditions and the following disclaimer. | ||
9 | * * Redistributions in binary form must reproduce the above copyright | ||
10 | * notice, this list of conditions and the following disclaimer in the | ||
11 | * documentation and/or other materials provided with the distribution. | ||
12 | * * Neither the name of the OpenSimulator Project nor the | ||
13 | * names of its contributors may be used to endorse or promote products | ||
14 | * derived from this software without specific prior written permission. | ||
15 | * | ||
16 | * THIS SOFTWARE IS PROVIDED BY THE DEVELOPERS ``AS IS'' AND ANY | ||
17 | * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED | ||
18 | * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE | ||
19 | * DISCLAIMED. IN NO EVENT SHALL THE CONTRIBUTORS BE LIABLE FOR ANY | ||
20 | * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES | ||
21 | * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; | ||
22 | * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND | ||
23 | * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT | ||
24 | * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS | ||
25 | * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. | ||
26 | */ | ||
27 | |||
28 | using System.Reflection; | ||
29 | using System.Runtime.InteropServices; | ||
30 | |||
31 | // Information about this assembly is defined by the following | ||
32 | // attributes. | ||
33 | // | ||
34 | // change them to the information which is associated with the assembly | ||
35 | // you compile. | ||
36 | |||
37 | [assembly : AssemblyTitle("ServerConsole")] | ||
38 | [assembly : AssemblyDescription("")] | ||
39 | [assembly: AssemblyConfiguration("")] | ||
40 | [assembly: AssemblyCompany("http://opensimulator.org")] | ||
41 | [assembly : AssemblyProduct("ServerConsole")] | ||
42 | [assembly: AssemblyCopyright("Copyright (c) OpenSimulator.org Developers")] | ||
43 | [assembly: AssemblyTrademark("")] | ||
44 | [assembly: AssemblyCulture("")] | ||
45 | |||
46 | // This sets the default COM visibility of types in the assembly to invisible. | ||
47 | // If you need to expose a type to COM, use [ComVisible(true)] on that type. | ||
48 | |||
49 | [assembly : ComVisible(false)] | ||
50 | |||
51 | // The assembly version has following format : | ||
52 | // | ||
53 | // Major.Minor.Build.Revision | ||
54 | // | ||
55 | // You can specify all values by your own or you can build default build and revision | ||
56 | // numbers with the '*' character (the default): | ||
57 | |||
58 | [assembly : AssemblyVersion("0.8.2.*")] | ||
diff --git a/OpenSim/Framework/Console/CommandConsole.cs b/OpenSim/Framework/Console/CommandConsole.cs new file mode 100644 index 0000000..0f68afe --- /dev/null +++ b/OpenSim/Framework/Console/CommandConsole.cs | |||
@@ -0,0 +1,793 @@ | |||
1 | /* | ||
2 | * Copyright (c) Contributors, http://opensimulator.org/ | ||
3 | * See CONTRIBUTORS.TXT for a full list of copyright holders. | ||
4 | * | ||
5 | * Redistribution and use in source and binary forms, with or without | ||
6 | * modification, are permitted provided that the following conditions are met: | ||
7 | * * Redistributions of source code must retain the above copyright | ||
8 | * notice, this list of conditions and the following disclaimer. | ||
9 | * * Redistributions in binary form must reproduce the above copyright | ||
10 | * notice, this list of conditions and the following disclaimer in the | ||
11 | * documentation and/or other materials provided with the distribution. | ||
12 | * * Neither the name of the OpenSimulator Project nor the | ||
13 | * names of its contributors may be used to endorse or promote products | ||
14 | * derived from this software without specific prior written permission. | ||
15 | * | ||
16 | * THIS SOFTWARE IS PROVIDED BY THE DEVELOPERS ``AS IS'' AND ANY | ||
17 | * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED | ||
18 | * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE | ||
19 | * DISCLAIMED. IN NO EVENT SHALL THE CONTRIBUTORS BE LIABLE FOR ANY | ||
20 | * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES | ||
21 | * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; | ||
22 | * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND | ||
23 | * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT | ||
24 | * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS | ||
25 | * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. | ||
26 | */ | ||
27 | |||
28 | using System; | ||
29 | using System.Xml; | ||
30 | using System.Collections.Generic; | ||
31 | using System.Diagnostics; | ||
32 | using System.Linq; | ||
33 | using System.Reflection; | ||
34 | using System.Text; | ||
35 | using System.Text.RegularExpressions; | ||
36 | using System.Threading; | ||
37 | using log4net; | ||
38 | using OpenSim.Framework; | ||
39 | |||
40 | namespace OpenSim.Framework.Console | ||
41 | { | ||
42 | public class Commands : ICommands | ||
43 | { | ||
44 | // private static readonly ILog m_log = LogManager.GetLogger(MethodBase.GetCurrentMethod().DeclaringType); | ||
45 | |||
46 | /// <summary> | ||
47 | /// Encapsulates a command that can be invoked from the console | ||
48 | /// </summary> | ||
49 | private class CommandInfo | ||
50 | { | ||
51 | /// <value> | ||
52 | /// The module from which this command comes | ||
53 | /// </value> | ||
54 | public string module; | ||
55 | |||
56 | /// <value> | ||
57 | /// Whether the module is shared | ||
58 | /// </value> | ||
59 | public bool shared; | ||
60 | |||
61 | /// <value> | ||
62 | /// Very short BNF description | ||
63 | /// </value> | ||
64 | public string help_text; | ||
65 | |||
66 | /// <value> | ||
67 | /// Longer one line help text | ||
68 | /// </value> | ||
69 | public string long_help; | ||
70 | |||
71 | /// <value> | ||
72 | /// Full descriptive help for this command | ||
73 | /// </value> | ||
74 | public string descriptive_help; | ||
75 | |||
76 | /// <value> | ||
77 | /// The method to invoke for this command | ||
78 | /// </value> | ||
79 | public List<CommandDelegate> fn; | ||
80 | } | ||
81 | |||
82 | public const string GeneralHelpText | ||
83 | = "To enter an argument that contains spaces, surround the argument with double quotes.\nFor example, show object name \"My long object name\"\n"; | ||
84 | |||
85 | public const string ItemHelpText | ||
86 | = @"For more information, type 'help all' to get a list of all commands, | ||
87 | or type help <item>' where <item> is one of the following:"; | ||
88 | |||
89 | /// <value> | ||
90 | /// Commands organized by keyword in a tree | ||
91 | /// </value> | ||
92 | private Dictionary<string, object> tree = | ||
93 | new Dictionary<string, object>(); | ||
94 | |||
95 | /// <summary> | ||
96 | /// Commands organized by module | ||
97 | /// </summary> | ||
98 | private Dictionary<string, List<CommandInfo>> m_modulesCommands = new Dictionary<string, List<CommandInfo>>(); | ||
99 | |||
100 | /// <summary> | ||
101 | /// Get help for the given help string | ||
102 | /// </summary> | ||
103 | /// <param name="helpParts">Parsed parts of the help string. If empty then general help is returned.</param> | ||
104 | /// <returns></returns> | ||
105 | public List<string> GetHelp(string[] cmd) | ||
106 | { | ||
107 | List<string> help = new List<string>(); | ||
108 | List<string> helpParts = new List<string>(cmd); | ||
109 | |||
110 | // Remove initial help keyword | ||
111 | helpParts.RemoveAt(0); | ||
112 | |||
113 | help.Add(""); // Will become a newline. | ||
114 | |||
115 | // General help | ||
116 | if (helpParts.Count == 0) | ||
117 | { | ||
118 | help.Add(GeneralHelpText); | ||
119 | help.Add(ItemHelpText); | ||
120 | help.AddRange(CollectModulesHelp(tree)); | ||
121 | } | ||
122 | else if (helpParts.Count == 1 && helpParts[0] == "all") | ||
123 | { | ||
124 | help.AddRange(CollectAllCommandsHelp()); | ||
125 | } | ||
126 | else | ||
127 | { | ||
128 | help.AddRange(CollectHelp(helpParts)); | ||
129 | } | ||
130 | |||
131 | help.Add(""); // Will become a newline. | ||
132 | |||
133 | return help; | ||
134 | } | ||
135 | |||
136 | /// <summary> | ||
137 | /// Collects the help from all commands and return in alphabetical order. | ||
138 | /// </summary> | ||
139 | /// <returns></returns> | ||
140 | private List<string> CollectAllCommandsHelp() | ||
141 | { | ||
142 | List<string> help = new List<string>(); | ||
143 | |||
144 | lock (m_modulesCommands) | ||
145 | { | ||
146 | foreach (List<CommandInfo> commands in m_modulesCommands.Values) | ||
147 | { | ||
148 | var ourHelpText = commands.ConvertAll(c => string.Format("{0} - {1}", c.help_text, c.long_help)); | ||
149 | help.AddRange(ourHelpText); | ||
150 | } | ||
151 | } | ||
152 | |||
153 | help.Sort(); | ||
154 | |||
155 | return help; | ||
156 | } | ||
157 | |||
158 | /// <summary> | ||
159 | /// See if we can find the requested command in order to display longer help | ||
160 | /// </summary> | ||
161 | /// <param name="helpParts"></param> | ||
162 | /// <returns></returns> | ||
163 | private List<string> CollectHelp(List<string> helpParts) | ||
164 | { | ||
165 | string originalHelpRequest = string.Join(" ", helpParts.ToArray()); | ||
166 | List<string> help = new List<string>(); | ||
167 | |||
168 | // Check modules first to see if we just need to display a list of those commands | ||
169 | if (TryCollectModuleHelp(originalHelpRequest, help)) | ||
170 | { | ||
171 | help.Insert(0, ItemHelpText); | ||
172 | return help; | ||
173 | } | ||
174 | |||
175 | Dictionary<string, object> dict = tree; | ||
176 | while (helpParts.Count > 0) | ||
177 | { | ||
178 | string helpPart = helpParts[0]; | ||
179 | |||
180 | if (!dict.ContainsKey(helpPart)) | ||
181 | break; | ||
182 | |||
183 | //m_log.Debug("Found {0}", helpParts[0]); | ||
184 | |||
185 | if (dict[helpPart] is Dictionary<string, Object>) | ||
186 | dict = (Dictionary<string, object>)dict[helpPart]; | ||
187 | |||
188 | helpParts.RemoveAt(0); | ||
189 | } | ||
190 | |||
191 | // There was a command for the given help string | ||
192 | if (dict.ContainsKey(String.Empty)) | ||
193 | { | ||
194 | CommandInfo commandInfo = (CommandInfo)dict[String.Empty]; | ||
195 | help.Add(commandInfo.help_text); | ||
196 | help.Add(commandInfo.long_help); | ||
197 | |||
198 | string descriptiveHelp = commandInfo.descriptive_help; | ||
199 | |||
200 | // If we do have some descriptive help then insert a spacing line before for readability. | ||
201 | if (descriptiveHelp != string.Empty) | ||
202 | help.Add(string.Empty); | ||
203 | |||
204 | help.Add(commandInfo.descriptive_help); | ||
205 | } | ||
206 | else | ||
207 | { | ||
208 | help.Add(string.Format("No help is available for {0}", originalHelpRequest)); | ||
209 | } | ||
210 | |||
211 | return help; | ||
212 | } | ||
213 | |||
214 | /// <summary> | ||
215 | /// Try to collect help for the given module if that module exists. | ||
216 | /// </summary> | ||
217 | /// <param name="moduleName"></param> | ||
218 | /// <param name="helpText">/param> | ||
219 | /// <returns>true if there was the module existed, false otherwise.</returns> | ||
220 | private bool TryCollectModuleHelp(string moduleName, List<string> helpText) | ||
221 | { | ||
222 | lock (m_modulesCommands) | ||
223 | { | ||
224 | foreach (string key in m_modulesCommands.Keys) | ||
225 | { | ||
226 | // Allow topic help requests to succeed whether they are upper or lowercase. | ||
227 | if (moduleName.ToLower() == key.ToLower()) | ||
228 | { | ||
229 | List<CommandInfo> commands = m_modulesCommands[key]; | ||
230 | var ourHelpText = commands.ConvertAll(c => string.Format("{0} - {1}", c.help_text, c.long_help)); | ||
231 | ourHelpText.Sort(); | ||
232 | helpText.AddRange(ourHelpText); | ||
233 | |||
234 | return true; | ||
235 | } | ||
236 | } | ||
237 | |||
238 | return false; | ||
239 | } | ||
240 | } | ||
241 | |||
242 | private List<string> CollectModulesHelp(Dictionary<string, object> dict) | ||
243 | { | ||
244 | lock (m_modulesCommands) | ||
245 | { | ||
246 | List<string> helpText = new List<string>(m_modulesCommands.Keys); | ||
247 | helpText.Sort(); | ||
248 | return helpText; | ||
249 | } | ||
250 | } | ||
251 | |||
252 | // private List<string> CollectHelp(Dictionary<string, object> dict) | ||
253 | // { | ||
254 | // List<string> result = new List<string>(); | ||
255 | // | ||
256 | // foreach (KeyValuePair<string, object> kvp in dict) | ||
257 | // { | ||
258 | // if (kvp.Value is Dictionary<string, Object>) | ||
259 | // { | ||
260 | // result.AddRange(CollectHelp((Dictionary<string, Object>)kvp.Value)); | ||
261 | // } | ||
262 | // else | ||
263 | // { | ||
264 | // if (((CommandInfo)kvp.Value).long_help != String.Empty) | ||
265 | // result.Add(((CommandInfo)kvp.Value).help_text+" - "+ | ||
266 | // ((CommandInfo)kvp.Value).long_help); | ||
267 | // } | ||
268 | // } | ||
269 | // return result; | ||
270 | // } | ||
271 | |||
272 | /// <summary> | ||
273 | /// Add a command to those which can be invoked from the console. | ||
274 | /// </summary> | ||
275 | /// <param name="module"></param> | ||
276 | /// <param name="command"></param> | ||
277 | /// <param name="help"></param> | ||
278 | /// <param name="longhelp"></param> | ||
279 | /// <param name="fn"></param> | ||
280 | public void AddCommand(string module, bool shared, string command, | ||
281 | string help, string longhelp, CommandDelegate fn) | ||
282 | { | ||
283 | AddCommand(module, shared, command, help, longhelp, String.Empty, fn); | ||
284 | } | ||
285 | |||
286 | /// <summary> | ||
287 | /// Add a command to those which can be invoked from the console. | ||
288 | /// </summary> | ||
289 | /// <param name="module"></param> | ||
290 | /// <param name="command"></param> | ||
291 | /// <param name="help"></param> | ||
292 | /// <param name="longhelp"></param> | ||
293 | /// <param name="descriptivehelp"></param> | ||
294 | /// <param name="fn"></param> | ||
295 | public void AddCommand(string module, bool shared, string command, | ||
296 | string help, string longhelp, string descriptivehelp, | ||
297 | CommandDelegate fn) | ||
298 | { | ||
299 | string[] parts = Parser.Parse(command); | ||
300 | |||
301 | Dictionary<string, Object> current = tree; | ||
302 | |||
303 | foreach (string part in parts) | ||
304 | { | ||
305 | if (current.ContainsKey(part)) | ||
306 | { | ||
307 | if (current[part] is Dictionary<string, Object>) | ||
308 | current = (Dictionary<string, Object>)current[part]; | ||
309 | else | ||
310 | return; | ||
311 | } | ||
312 | else | ||
313 | { | ||
314 | current[part] = new Dictionary<string, Object>(); | ||
315 | current = (Dictionary<string, Object>)current[part]; | ||
316 | } | ||
317 | } | ||
318 | |||
319 | CommandInfo info; | ||
320 | |||
321 | if (current.ContainsKey(String.Empty)) | ||
322 | { | ||
323 | info = (CommandInfo)current[String.Empty]; | ||
324 | if (!info.shared && !info.fn.Contains(fn)) | ||
325 | info.fn.Add(fn); | ||
326 | |||
327 | return; | ||
328 | } | ||
329 | |||
330 | info = new CommandInfo(); | ||
331 | info.module = module; | ||
332 | info.shared = shared; | ||
333 | info.help_text = help; | ||
334 | info.long_help = longhelp; | ||
335 | info.descriptive_help = descriptivehelp; | ||
336 | info.fn = new List<CommandDelegate>(); | ||
337 | info.fn.Add(fn); | ||
338 | current[String.Empty] = info; | ||
339 | |||
340 | // Now add command to modules dictionary | ||
341 | lock (m_modulesCommands) | ||
342 | { | ||
343 | List<CommandInfo> commands; | ||
344 | if (m_modulesCommands.ContainsKey(module)) | ||
345 | { | ||
346 | commands = m_modulesCommands[module]; | ||
347 | } | ||
348 | else | ||
349 | { | ||
350 | commands = new List<CommandInfo>(); | ||
351 | m_modulesCommands[module] = commands; | ||
352 | } | ||
353 | |||
354 | // m_log.DebugFormat("[COMMAND CONSOLE]: Adding to category {0} command {1}", module, command); | ||
355 | commands.Add(info); | ||
356 | } | ||
357 | } | ||
358 | |||
359 | public string[] FindNextOption(string[] cmd, bool term) | ||
360 | { | ||
361 | Dictionary<string, object> current = tree; | ||
362 | |||
363 | int remaining = cmd.Length; | ||
364 | |||
365 | foreach (string s in cmd) | ||
366 | { | ||
367 | remaining--; | ||
368 | |||
369 | List<string> found = new List<string>(); | ||
370 | |||
371 | foreach (string opt in current.Keys) | ||
372 | { | ||
373 | if (remaining > 0 && opt == s) | ||
374 | { | ||
375 | found.Clear(); | ||
376 | found.Add(opt); | ||
377 | break; | ||
378 | } | ||
379 | if (opt.StartsWith(s)) | ||
380 | { | ||
381 | found.Add(opt); | ||
382 | } | ||
383 | } | ||
384 | |||
385 | if (found.Count == 1 && (remaining != 0 || term)) | ||
386 | { | ||
387 | current = (Dictionary<string, object>)current[found[0]]; | ||
388 | } | ||
389 | else if (found.Count > 0) | ||
390 | { | ||
391 | return found.ToArray(); | ||
392 | } | ||
393 | else | ||
394 | { | ||
395 | break; | ||
396 | // return new string[] {"<cr>"}; | ||
397 | } | ||
398 | } | ||
399 | |||
400 | if (current.Count > 1) | ||
401 | { | ||
402 | List<string> choices = new List<string>(); | ||
403 | |||
404 | bool addcr = false; | ||
405 | foreach (string s in current.Keys) | ||
406 | { | ||
407 | if (s == String.Empty) | ||
408 | { | ||
409 | CommandInfo ci = (CommandInfo)current[String.Empty]; | ||
410 | if (ci.fn.Count != 0) | ||
411 | addcr = true; | ||
412 | } | ||
413 | else | ||
414 | choices.Add(s); | ||
415 | } | ||
416 | if (addcr) | ||
417 | choices.Add("<cr>"); | ||
418 | return choices.ToArray(); | ||
419 | } | ||
420 | |||
421 | if (current.ContainsKey(String.Empty)) | ||
422 | return new string[] { "Command help: "+((CommandInfo)current[String.Empty]).help_text}; | ||
423 | |||
424 | return new string[] { new List<string>(current.Keys)[0] }; | ||
425 | } | ||
426 | |||
427 | private CommandInfo ResolveCommand(string[] cmd, out string[] result) | ||
428 | { | ||
429 | result = cmd; | ||
430 | int index = -1; | ||
431 | |||
432 | Dictionary<string, object> current = tree; | ||
433 | |||
434 | foreach (string s in cmd) | ||
435 | { | ||
436 | index++; | ||
437 | |||
438 | List<string> found = new List<string>(); | ||
439 | |||
440 | foreach (string opt in current.Keys) | ||
441 | { | ||
442 | if (opt == s) | ||
443 | { | ||
444 | found.Clear(); | ||
445 | found.Add(opt); | ||
446 | break; | ||
447 | } | ||
448 | if (opt.StartsWith(s)) | ||
449 | { | ||
450 | found.Add(opt); | ||
451 | } | ||
452 | } | ||
453 | |||
454 | if (found.Count == 1) | ||
455 | { | ||
456 | result[index] = found[0]; | ||
457 | current = (Dictionary<string, object>)current[found[0]]; | ||
458 | } | ||
459 | else if (found.Count > 0) | ||
460 | { | ||
461 | return null; | ||
462 | } | ||
463 | else | ||
464 | { | ||
465 | break; | ||
466 | } | ||
467 | } | ||
468 | |||
469 | if (current.ContainsKey(String.Empty)) | ||
470 | return (CommandInfo)current[String.Empty]; | ||
471 | |||
472 | return null; | ||
473 | } | ||
474 | |||
475 | public bool HasCommand(string command) | ||
476 | { | ||
477 | string[] result; | ||
478 | return ResolveCommand(Parser.Parse(command), out result) != null; | ||
479 | } | ||
480 | |||
481 | public string[] Resolve(string[] cmd) | ||
482 | { | ||
483 | string[] result; | ||
484 | CommandInfo ci = ResolveCommand(cmd, out result); | ||
485 | |||
486 | if (ci == null) | ||
487 | return new string[0]; | ||
488 | |||
489 | if (ci.fn.Count == 0) | ||
490 | return new string[0]; | ||
491 | |||
492 | foreach (CommandDelegate fn in ci.fn) | ||
493 | { | ||
494 | if (fn != null) | ||
495 | fn(ci.module, result); | ||
496 | else | ||
497 | return new string[0]; | ||
498 | } | ||
499 | |||
500 | return result; | ||
501 | } | ||
502 | |||
503 | public XmlElement GetXml(XmlDocument doc) | ||
504 | { | ||
505 | CommandInfo help = (CommandInfo)((Dictionary<string, object>)tree["help"])[String.Empty]; | ||
506 | ((Dictionary<string, object>)tree["help"]).Remove(string.Empty); | ||
507 | if (((Dictionary<string, object>)tree["help"]).Count == 0) | ||
508 | tree.Remove("help"); | ||
509 | |||
510 | CommandInfo quit = (CommandInfo)((Dictionary<string, object>)tree["quit"])[String.Empty]; | ||
511 | ((Dictionary<string, object>)tree["quit"]).Remove(string.Empty); | ||
512 | if (((Dictionary<string, object>)tree["quit"]).Count == 0) | ||
513 | tree.Remove("quit"); | ||
514 | |||
515 | XmlElement root = doc.CreateElement("", "HelpTree", ""); | ||
516 | |||
517 | ProcessTreeLevel(tree, root, doc); | ||
518 | |||
519 | if (!tree.ContainsKey("help")) | ||
520 | tree["help"] = (object) new Dictionary<string, object>(); | ||
521 | ((Dictionary<string, object>)tree["help"])[String.Empty] = help; | ||
522 | |||
523 | if (!tree.ContainsKey("quit")) | ||
524 | tree["quit"] = (object) new Dictionary<string, object>(); | ||
525 | ((Dictionary<string, object>)tree["quit"])[String.Empty] = quit; | ||
526 | |||
527 | return root; | ||
528 | } | ||
529 | |||
530 | private void ProcessTreeLevel(Dictionary<string, object> level, XmlElement xml, XmlDocument doc) | ||
531 | { | ||
532 | foreach (KeyValuePair<string, object> kvp in level) | ||
533 | { | ||
534 | if (kvp.Value is Dictionary<string, Object>) | ||
535 | { | ||
536 | XmlElement next = doc.CreateElement("", "Level", ""); | ||
537 | next.SetAttribute("Name", kvp.Key); | ||
538 | |||
539 | xml.AppendChild(next); | ||
540 | |||
541 | ProcessTreeLevel((Dictionary<string, object>)kvp.Value, next, doc); | ||
542 | } | ||
543 | else | ||
544 | { | ||
545 | CommandInfo c = (CommandInfo)kvp.Value; | ||
546 | |||
547 | XmlElement cmd = doc.CreateElement("", "Command", ""); | ||
548 | |||
549 | XmlElement e; | ||
550 | |||
551 | e = doc.CreateElement("", "Module", ""); | ||
552 | cmd.AppendChild(e); | ||
553 | e.AppendChild(doc.CreateTextNode(c.module)); | ||
554 | |||
555 | e = doc.CreateElement("", "Shared", ""); | ||
556 | cmd.AppendChild(e); | ||
557 | e.AppendChild(doc.CreateTextNode(c.shared.ToString())); | ||
558 | |||
559 | e = doc.CreateElement("", "HelpText", ""); | ||
560 | cmd.AppendChild(e); | ||
561 | e.AppendChild(doc.CreateTextNode(c.help_text)); | ||
562 | |||
563 | e = doc.CreateElement("", "LongHelp", ""); | ||
564 | cmd.AppendChild(e); | ||
565 | e.AppendChild(doc.CreateTextNode(c.long_help)); | ||
566 | |||
567 | e = doc.CreateElement("", "Description", ""); | ||
568 | cmd.AppendChild(e); | ||
569 | e.AppendChild(doc.CreateTextNode(c.descriptive_help)); | ||
570 | |||
571 | xml.AppendChild(cmd); | ||
572 | } | ||
573 | } | ||
574 | } | ||
575 | |||
576 | public void FromXml(XmlElement root, CommandDelegate fn) | ||
577 | { | ||
578 | CommandInfo help = (CommandInfo)((Dictionary<string, object>)tree["help"])[String.Empty]; | ||
579 | ((Dictionary<string, object>)tree["help"]).Remove(string.Empty); | ||
580 | if (((Dictionary<string, object>)tree["help"]).Count == 0) | ||
581 | tree.Remove("help"); | ||
582 | |||
583 | CommandInfo quit = (CommandInfo)((Dictionary<string, object>)tree["quit"])[String.Empty]; | ||
584 | ((Dictionary<string, object>)tree["quit"]).Remove(string.Empty); | ||
585 | if (((Dictionary<string, object>)tree["quit"]).Count == 0) | ||
586 | tree.Remove("quit"); | ||
587 | |||
588 | tree.Clear(); | ||
589 | |||
590 | ReadTreeLevel(tree, root, fn); | ||
591 | |||
592 | if (!tree.ContainsKey("help")) | ||
593 | tree["help"] = (object) new Dictionary<string, object>(); | ||
594 | ((Dictionary<string, object>)tree["help"])[String.Empty] = help; | ||
595 | |||
596 | if (!tree.ContainsKey("quit")) | ||
597 | tree["quit"] = (object) new Dictionary<string, object>(); | ||
598 | ((Dictionary<string, object>)tree["quit"])[String.Empty] = quit; | ||
599 | } | ||
600 | |||
601 | private void ReadTreeLevel(Dictionary<string, object> level, XmlNode node, CommandDelegate fn) | ||
602 | { | ||
603 | Dictionary<string, object> next; | ||
604 | string name; | ||
605 | |||
606 | XmlNodeList nodeL = node.ChildNodes; | ||
607 | XmlNodeList cmdL; | ||
608 | CommandInfo c; | ||
609 | |||
610 | foreach (XmlNode part in nodeL) | ||
611 | { | ||
612 | switch (part.Name) | ||
613 | { | ||
614 | case "Level": | ||
615 | name = ((XmlElement)part).GetAttribute("Name"); | ||
616 | next = new Dictionary<string, object>(); | ||
617 | level[name] = next; | ||
618 | ReadTreeLevel(next, part, fn); | ||
619 | break; | ||
620 | case "Command": | ||
621 | cmdL = part.ChildNodes; | ||
622 | c = new CommandInfo(); | ||
623 | foreach (XmlNode cmdPart in cmdL) | ||
624 | { | ||
625 | switch (cmdPart.Name) | ||
626 | { | ||
627 | case "Module": | ||
628 | c.module = cmdPart.InnerText; | ||
629 | break; | ||
630 | case "Shared": | ||
631 | c.shared = Convert.ToBoolean(cmdPart.InnerText); | ||
632 | break; | ||
633 | case "HelpText": | ||
634 | c.help_text = cmdPart.InnerText; | ||
635 | break; | ||
636 | case "LongHelp": | ||
637 | c.long_help = cmdPart.InnerText; | ||
638 | break; | ||
639 | case "Description": | ||
640 | c.descriptive_help = cmdPart.InnerText; | ||
641 | break; | ||
642 | } | ||
643 | } | ||
644 | c.fn = new List<CommandDelegate>(); | ||
645 | c.fn.Add(fn); | ||
646 | level[String.Empty] = c; | ||
647 | break; | ||
648 | } | ||
649 | } | ||
650 | } | ||
651 | } | ||
652 | |||
653 | public class Parser | ||
654 | { | ||
655 | // If an unquoted portion ends with an element matching this regex | ||
656 | // and the next element contains a space, then we have stripped | ||
657 | // embedded quotes that should not have been stripped | ||
658 | private static Regex optionRegex = new Regex("^--[a-zA-Z0-9-]+=$"); | ||
659 | |||
660 | public static string[] Parse(string text) | ||
661 | { | ||
662 | List<string> result = new List<string>(); | ||
663 | |||
664 | int index; | ||
665 | |||
666 | string[] unquoted = text.Split(new char[] {'"'}); | ||
667 | |||
668 | for (index = 0 ; index < unquoted.Length ; index++) | ||
669 | { | ||
670 | if (index % 2 == 0) | ||
671 | { | ||
672 | string[] words = unquoted[index].Split(new char[] {' '}); | ||
673 | |||
674 | bool option = false; | ||
675 | foreach (string w in words) | ||
676 | { | ||
677 | if (w != String.Empty) | ||
678 | { | ||
679 | if (optionRegex.Match(w) == Match.Empty) | ||
680 | option = false; | ||
681 | else | ||
682 | option = true; | ||
683 | result.Add(w); | ||
684 | } | ||
685 | } | ||
686 | // The last item matched the regex, put the quotes back | ||
687 | if (option) | ||
688 | { | ||
689 | // If the line ended with it, don't do anything | ||
690 | if (index < (unquoted.Length - 1)) | ||
691 | { | ||
692 | // Get and remove the option name | ||
693 | string optionText = result[result.Count - 1]; | ||
694 | result.RemoveAt(result.Count - 1); | ||
695 | |||
696 | // Add the quoted value back | ||
697 | optionText += "\"" + unquoted[index + 1] + "\""; | ||
698 | |||
699 | // Push the result into our return array | ||
700 | result.Add(optionText); | ||
701 | |||
702 | // Skip the already used value | ||
703 | index++; | ||
704 | } | ||
705 | } | ||
706 | } | ||
707 | else | ||
708 | { | ||
709 | result.Add(unquoted[index]); | ||
710 | } | ||
711 | } | ||
712 | |||
713 | return result.ToArray(); | ||
714 | } | ||
715 | } | ||
716 | |||
717 | /// <summary> | ||
718 | /// A console that processes commands internally | ||
719 | /// </summary> | ||
720 | public class CommandConsole : ConsoleBase, ICommandConsole | ||
721 | { | ||
722 | // private static readonly ILog m_log = LogManager.GetLogger(MethodBase.GetCurrentMethod().DeclaringType); | ||
723 | |||
724 | public event OnOutputDelegate OnOutput; | ||
725 | |||
726 | public ICommands Commands { get; private set; } | ||
727 | |||
728 | public CommandConsole(string defaultPrompt) : base(defaultPrompt) | ||
729 | { | ||
730 | Commands = new Commands(); | ||
731 | |||
732 | Commands.AddCommand( | ||
733 | "Help", false, "help", "help [<item>]", | ||
734 | "Display help on a particular command or on a list of commands in a category", Help); | ||
735 | } | ||
736 | |||
737 | private void Help(string module, string[] cmd) | ||
738 | { | ||
739 | List<string> help = Commands.GetHelp(cmd); | ||
740 | |||
741 | foreach (string s in help) | ||
742 | Output(s); | ||
743 | } | ||
744 | |||
745 | protected void FireOnOutput(string text) | ||
746 | { | ||
747 | OnOutputDelegate onOutput = OnOutput; | ||
748 | if (onOutput != null) | ||
749 | onOutput(text); | ||
750 | } | ||
751 | |||
752 | /// <summary> | ||
753 | /// Display a command prompt on the console and wait for user input | ||
754 | /// </summary> | ||
755 | public void Prompt() | ||
756 | { | ||
757 | string line = ReadLine(DefaultPrompt + "# ", true, true); | ||
758 | |||
759 | if (line != String.Empty) | ||
760 | Output("Invalid command"); | ||
761 | } | ||
762 | |||
763 | public void RunCommand(string cmd) | ||
764 | { | ||
765 | string[] parts = Parser.Parse(cmd); | ||
766 | Commands.Resolve(parts); | ||
767 | } | ||
768 | |||
769 | public override string ReadLine(string p, bool isCommand, bool e) | ||
770 | { | ||
771 | System.Console.Write("{0}", p); | ||
772 | string cmdinput = System.Console.ReadLine(); | ||
773 | |||
774 | if (isCommand) | ||
775 | { | ||
776 | string[] cmd = Commands.Resolve(Parser.Parse(cmdinput)); | ||
777 | |||
778 | if (cmd.Length != 0) | ||
779 | { | ||
780 | int i; | ||
781 | |||
782 | for (i=0 ; i < cmd.Length ; i++) | ||
783 | { | ||
784 | if (cmd[i].Contains(" ")) | ||
785 | cmd[i] = "\"" + cmd[i] + "\""; | ||
786 | } | ||
787 | return String.Empty; | ||
788 | } | ||
789 | } | ||
790 | return cmdinput; | ||
791 | } | ||
792 | } | ||
793 | } | ||
diff --git a/OpenSim/Framework/Console/ConsoleBase.cs b/OpenSim/Framework/Console/ConsoleBase.cs new file mode 100755 index 0000000..2d8e723 --- /dev/null +++ b/OpenSim/Framework/Console/ConsoleBase.cs | |||
@@ -0,0 +1,180 @@ | |||
1 | /* | ||
2 | * Copyright (c) Contributors, http://opensimulator.org/ | ||
3 | * See CONTRIBUTORS.TXT for a full list of copyright holders. | ||
4 | * | ||
5 | * Redistribution and use in source and binary forms, with or without | ||
6 | * modification, are permitted provided that the following conditions are met: | ||
7 | * * Redistributions of source code must retain the above copyright | ||
8 | * notice, this list of conditions and the following disclaimer. | ||
9 | * * Redistributions in binary form must reproduce the above copyright | ||
10 | * notice, this list of conditions and the following disclaimer in the | ||
11 | * documentation and/or other materials provided with the distribution. | ||
12 | * * Neither the name of the OpenSimulator Project nor the | ||
13 | * names of its contributors may be used to endorse or promote products | ||
14 | * derived from this software without specific prior written permission. | ||
15 | * | ||
16 | * THIS SOFTWARE IS PROVIDED BY THE DEVELOPERS ``AS IS'' AND ANY | ||
17 | * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED | ||
18 | * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE | ||
19 | * DISCLAIMED. IN NO EVENT SHALL THE CONTRIBUTORS BE LIABLE FOR ANY | ||
20 | * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES | ||
21 | * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; | ||
22 | * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND | ||
23 | * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT | ||
24 | * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS | ||
25 | * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. | ||
26 | */ | ||
27 | |||
28 | using System; | ||
29 | using System.Collections.Generic; | ||
30 | using System.Diagnostics; | ||
31 | using System.Reflection; | ||
32 | using System.Text; | ||
33 | using System.Threading; | ||
34 | using log4net; | ||
35 | |||
36 | namespace OpenSim.Framework.Console | ||
37 | { | ||
38 | public class ConsoleBase | ||
39 | { | ||
40 | // private static readonly ILog m_log = LogManager.GetLogger(MethodBase.GetCurrentMethod().DeclaringType); | ||
41 | |||
42 | protected string prompt = "# "; | ||
43 | |||
44 | public object ConsoleScene { get; set; } | ||
45 | |||
46 | public string DefaultPrompt { get; set; } | ||
47 | |||
48 | public ConsoleBase(string defaultPrompt) | ||
49 | { | ||
50 | DefaultPrompt = defaultPrompt; | ||
51 | } | ||
52 | |||
53 | public virtual void LockOutput() | ||
54 | { | ||
55 | } | ||
56 | |||
57 | public virtual void UnlockOutput() | ||
58 | { | ||
59 | } | ||
60 | |||
61 | public virtual void Output(string text, string level) | ||
62 | { | ||
63 | Output(text); | ||
64 | } | ||
65 | |||
66 | public virtual void Output(string text) | ||
67 | { | ||
68 | System.Console.WriteLine(text); | ||
69 | } | ||
70 | |||
71 | public virtual void OutputFormat(string format, params object[] components) | ||
72 | { | ||
73 | Output(string.Format(format, components)); | ||
74 | } | ||
75 | |||
76 | public string CmdPrompt(string p) | ||
77 | { | ||
78 | return ReadLine(String.Format("{0}: ", p), false, true); | ||
79 | } | ||
80 | |||
81 | public string CmdPrompt(string p, string def) | ||
82 | { | ||
83 | string ret = ReadLine(String.Format("{0} [{1}]: ", p, def), false, true); | ||
84 | if (ret == String.Empty) | ||
85 | ret = def; | ||
86 | |||
87 | return ret; | ||
88 | } | ||
89 | |||
90 | public string CmdPrompt(string p, List<char> excludedCharacters) | ||
91 | { | ||
92 | bool itisdone = false; | ||
93 | string ret = String.Empty; | ||
94 | while (!itisdone) | ||
95 | { | ||
96 | itisdone = true; | ||
97 | ret = CmdPrompt(p); | ||
98 | |||
99 | foreach (char c in excludedCharacters) | ||
100 | { | ||
101 | if (ret.Contains(c.ToString())) | ||
102 | { | ||
103 | System.Console.WriteLine("The character \"" + c.ToString() + "\" is not permitted."); | ||
104 | itisdone = false; | ||
105 | } | ||
106 | } | ||
107 | } | ||
108 | |||
109 | return ret; | ||
110 | } | ||
111 | |||
112 | public string CmdPrompt(string p, string def, List<char> excludedCharacters) | ||
113 | { | ||
114 | bool itisdone = false; | ||
115 | string ret = String.Empty; | ||
116 | while (!itisdone) | ||
117 | { | ||
118 | itisdone = true; | ||
119 | ret = CmdPrompt(p, def); | ||
120 | |||
121 | if (ret == String.Empty) | ||
122 | { | ||
123 | ret = def; | ||
124 | } | ||
125 | else | ||
126 | { | ||
127 | foreach (char c in excludedCharacters) | ||
128 | { | ||
129 | if (ret.Contains(c.ToString())) | ||
130 | { | ||
131 | System.Console.WriteLine("The character \"" + c.ToString() + "\" is not permitted."); | ||
132 | itisdone = false; | ||
133 | } | ||
134 | } | ||
135 | } | ||
136 | } | ||
137 | |||
138 | return ret; | ||
139 | } | ||
140 | |||
141 | // Displays a command prompt and returns a default value, user may only enter 1 of 2 options | ||
142 | public string CmdPrompt(string prompt, string defaultresponse, List<string> options) | ||
143 | { | ||
144 | bool itisdone = false; | ||
145 | string optstr = String.Empty; | ||
146 | foreach (string s in options) | ||
147 | optstr += " " + s; | ||
148 | |||
149 | string temp = CmdPrompt(prompt, defaultresponse); | ||
150 | while (itisdone == false) | ||
151 | { | ||
152 | if (options.Contains(temp)) | ||
153 | { | ||
154 | itisdone = true; | ||
155 | } | ||
156 | else | ||
157 | { | ||
158 | System.Console.WriteLine("Valid options are" + optstr); | ||
159 | temp = CmdPrompt(prompt, defaultresponse); | ||
160 | } | ||
161 | } | ||
162 | return temp; | ||
163 | } | ||
164 | |||
165 | // Displays a prompt and waits for the user to enter a string, then returns that string | ||
166 | // (Done with no echo and suitable for passwords) | ||
167 | public string PasswdPrompt(string p) | ||
168 | { | ||
169 | return ReadLine(String.Format("{0}: ", p), false, false); | ||
170 | } | ||
171 | |||
172 | public virtual string ReadLine(string p, bool isCommand, bool e) | ||
173 | { | ||
174 | System.Console.Write("{0}", p); | ||
175 | string cmdinput = System.Console.ReadLine(); | ||
176 | |||
177 | return cmdinput; | ||
178 | } | ||
179 | } | ||
180 | } | ||
diff --git a/OpenSim/Framework/Console/ConsoleDisplayList.cs b/OpenSim/Framework/Console/ConsoleDisplayList.cs new file mode 100644 index 0000000..6885509 --- /dev/null +++ b/OpenSim/Framework/Console/ConsoleDisplayList.cs | |||
@@ -0,0 +1,112 @@ | |||
1 | /* | ||
2 | * Copyright (c) Contributors, http://opensimulator.org/ | ||
3 | * See CONTRIBUTORS.TXT for a full list of copyright holders. | ||
4 | * | ||
5 | * Redistribution and use in source and binary forms, with or without | ||
6 | * modification, are permitted provided that the following conditions are met: | ||
7 | * * Redistributions of source code must retain the above copyright | ||
8 | * notice, this list of conditions and the following disclaimer. | ||
9 | * * Redistributions in binary form must reproduce the above copyright | ||
10 | * notice, this list of conditions and the following disclaimer in the | ||
11 | * documentation and/or other materials provided with the distribution. | ||
12 | * * Neither the name of the OpenSimulator Project nor the | ||
13 | * names of its contributors may be used to endorse or promote products | ||
14 | * derived from this software without specific prior written permission. | ||
15 | * | ||
16 | * THIS SOFTWARE IS PROVIDED BY THE DEVELOPERS ``AS IS'' AND ANY | ||
17 | * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED | ||
18 | * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE | ||
19 | * DISCLAIMED. IN NO EVENT SHALL THE CONTRIBUTORS BE LIABLE FOR ANY | ||
20 | * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES | ||
21 | * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; | ||
22 | * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND | ||
23 | * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT | ||
24 | * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS | ||
25 | * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. | ||
26 | */ | ||
27 | |||
28 | using System; | ||
29 | using System.Collections.Generic; | ||
30 | using System.Linq; | ||
31 | using System.Text; | ||
32 | |||
33 | namespace OpenSim.Framework.Console | ||
34 | { | ||
35 | /// <summary> | ||
36 | /// Used to generated a formatted table for the console. | ||
37 | /// </summary> | ||
38 | /// <remarks> | ||
39 | /// Currently subject to change. If you use this, be prepared to change your code when this class changes. | ||
40 | /// </remarks> | ||
41 | public class ConsoleDisplayList | ||
42 | { | ||
43 | /// <summary> | ||
44 | /// The default divider between key and value for a list item. | ||
45 | /// </summary> | ||
46 | public const string DefaultKeyValueDivider = " : "; | ||
47 | |||
48 | /// <summary> | ||
49 | /// The divider used between key and value for a list item. | ||
50 | /// </summary> | ||
51 | public string KeyValueDivider { get; set; } | ||
52 | |||
53 | /// <summary> | ||
54 | /// Table rows | ||
55 | /// </summary> | ||
56 | public List<KeyValuePair<string, string>> Rows { get; private set; } | ||
57 | |||
58 | /// <summary> | ||
59 | /// Number of spaces to indent the list. | ||
60 | /// </summary> | ||
61 | public int Indent { get; set; } | ||
62 | |||
63 | public ConsoleDisplayList() | ||
64 | { | ||
65 | Rows = new List<KeyValuePair<string, string>>(); | ||
66 | KeyValueDivider = DefaultKeyValueDivider; | ||
67 | } | ||
68 | |||
69 | public override string ToString() | ||
70 | { | ||
71 | StringBuilder sb = new StringBuilder(); | ||
72 | AddToStringBuilder(sb); | ||
73 | return sb.ToString(); | ||
74 | } | ||
75 | |||
76 | public void AddToStringBuilder(StringBuilder sb) | ||
77 | { | ||
78 | string formatString = GetFormatString(); | ||
79 | // System.Console.WriteLine("FORMAT STRING [{0}]", formatString); | ||
80 | |||
81 | // rows | ||
82 | foreach (KeyValuePair<string, string> row in Rows) | ||
83 | sb.AppendFormat(formatString, row.Key, row.Value); | ||
84 | } | ||
85 | |||
86 | /// <summary> | ||
87 | /// Gets the format string for the table. | ||
88 | /// </summary> | ||
89 | private string GetFormatString() | ||
90 | { | ||
91 | StringBuilder formatSb = new StringBuilder(); | ||
92 | |||
93 | int longestKey = -1; | ||
94 | |||
95 | foreach (KeyValuePair<string, string> row in Rows) | ||
96 | if (row.Key.Length > longestKey) | ||
97 | longestKey = row.Key.Length; | ||
98 | |||
99 | formatSb.Append(' ', Indent); | ||
100 | |||
101 | // Can only do left formatting for now | ||
102 | formatSb.AppendFormat("{{0,-{0}}}{1}{{1}}\n", longestKey, KeyValueDivider); | ||
103 | |||
104 | return formatSb.ToString(); | ||
105 | } | ||
106 | |||
107 | public void AddRow(object key, object value) | ||
108 | { | ||
109 | Rows.Add(new KeyValuePair<string, string>(key.ToString(), value.ToString())); | ||
110 | } | ||
111 | } | ||
112 | } \ No newline at end of file | ||
diff --git a/OpenSim/Framework/Console/ConsoleDisplayTable.cs b/OpenSim/Framework/Console/ConsoleDisplayTable.cs new file mode 100644 index 0000000..711a337 --- /dev/null +++ b/OpenSim/Framework/Console/ConsoleDisplayTable.cs | |||
@@ -0,0 +1,155 @@ | |||
1 | /* | ||
2 | * Copyright (c) Contributors, http://opensimulator.org/ | ||
3 | * See CONTRIBUTORS.TXT for a full list of copyright holders. | ||
4 | * | ||
5 | * Redistribution and use in source and binary forms, with or without | ||
6 | * modification, are permitted provided that the following conditions are met: | ||
7 | * * Redistributions of source code must retain the above copyright | ||
8 | * notice, this list of conditions and the following disclaimer. | ||
9 | * * Redistributions in binary form must reproduce the above copyright | ||
10 | * notice, this list of conditions and the following disclaimer in the | ||
11 | * documentation and/or other materials provided with the distribution. | ||
12 | * * Neither the name of the OpenSimulator Project nor the | ||
13 | * names of its contributors may be used to endorse or promote products | ||
14 | * derived from this software without specific prior written permission. | ||
15 | * | ||
16 | * THIS SOFTWARE IS PROVIDED BY THE DEVELOPERS ``AS IS'' AND ANY | ||
17 | * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED | ||
18 | * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE | ||
19 | * DISCLAIMED. IN NO EVENT SHALL THE CONTRIBUTORS BE LIABLE FOR ANY | ||
20 | * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES | ||
21 | * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; | ||
22 | * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND | ||
23 | * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT | ||
24 | * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS | ||
25 | * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. | ||
26 | */ | ||
27 | |||
28 | using System; | ||
29 | using System.Collections.Generic; | ||
30 | using System.Linq; | ||
31 | using System.Text; | ||
32 | |||
33 | namespace OpenSim.Framework.Console | ||
34 | { | ||
35 | /// <summary> | ||
36 | /// Used to generated a formatted table for the console. | ||
37 | /// </summary> | ||
38 | /// <remarks> | ||
39 | /// Currently subject to change. If you use this, be prepared to change your code when this class changes. | ||
40 | /// </remarks> | ||
41 | public class ConsoleDisplayTable | ||
42 | { | ||
43 | /// <summary> | ||
44 | /// Default number of spaces between table columns. | ||
45 | /// </summary> | ||
46 | public const int DefaultTableSpacing = 2; | ||
47 | |||
48 | /// <summary> | ||
49 | /// Table columns. | ||
50 | /// </summary> | ||
51 | public List<ConsoleDisplayTableColumn> Columns { get; private set; } | ||
52 | |||
53 | /// <summary> | ||
54 | /// Table rows | ||
55 | /// </summary> | ||
56 | public List<ConsoleDisplayTableRow> Rows { get; private set; } | ||
57 | |||
58 | /// <summary> | ||
59 | /// Number of spaces to indent the whole table. | ||
60 | /// </summary> | ||
61 | public int Indent { get; set; } | ||
62 | |||
63 | /// <summary> | ||
64 | /// Spacing between table columns | ||
65 | /// </summary> | ||
66 | public int TableSpacing { get; set; } | ||
67 | |||
68 | public ConsoleDisplayTable() | ||
69 | { | ||
70 | TableSpacing = DefaultTableSpacing; | ||
71 | Columns = new List<ConsoleDisplayTableColumn>(); | ||
72 | Rows = new List<ConsoleDisplayTableRow>(); | ||
73 | } | ||
74 | |||
75 | public override string ToString() | ||
76 | { | ||
77 | StringBuilder sb = new StringBuilder(); | ||
78 | AddToStringBuilder(sb); | ||
79 | return sb.ToString(); | ||
80 | } | ||
81 | |||
82 | public void AddColumn(string name, int width) | ||
83 | { | ||
84 | Columns.Add(new ConsoleDisplayTableColumn(name, width)); | ||
85 | } | ||
86 | |||
87 | public void AddRow(params object[] cells) | ||
88 | { | ||
89 | Rows.Add(new ConsoleDisplayTableRow(cells)); | ||
90 | } | ||
91 | |||
92 | public void AddToStringBuilder(StringBuilder sb) | ||
93 | { | ||
94 | string formatString = GetFormatString(); | ||
95 | // System.Console.WriteLine("FORMAT STRING [{0}]", formatString); | ||
96 | |||
97 | // columns | ||
98 | sb.AppendFormat(formatString, Columns.ConvertAll(c => c.Header).ToArray()); | ||
99 | |||
100 | // rows | ||
101 | foreach (ConsoleDisplayTableRow row in Rows) | ||
102 | sb.AppendFormat(formatString, row.Cells.ToArray()); | ||
103 | } | ||
104 | |||
105 | /// <summary> | ||
106 | /// Gets the format string for the table. | ||
107 | /// </summary> | ||
108 | private string GetFormatString() | ||
109 | { | ||
110 | StringBuilder formatSb = new StringBuilder(); | ||
111 | |||
112 | formatSb.Append(' ', Indent); | ||
113 | |||
114 | for (int i = 0; i < Columns.Count; i++) | ||
115 | { | ||
116 | if (i != 0) | ||
117 | formatSb.Append(' ', TableSpacing); | ||
118 | |||
119 | // Can only do left formatting for now | ||
120 | formatSb.AppendFormat("{{{0},-{1}}}", i, Columns[i].Width); | ||
121 | } | ||
122 | |||
123 | formatSb.Append('\n'); | ||
124 | |||
125 | return formatSb.ToString(); | ||
126 | } | ||
127 | } | ||
128 | |||
129 | public struct ConsoleDisplayTableColumn | ||
130 | { | ||
131 | public string Header { get; set; } | ||
132 | public int Width { get; set; } | ||
133 | |||
134 | public ConsoleDisplayTableColumn(string header, int width) : this() | ||
135 | { | ||
136 | Header = header; | ||
137 | Width = width; | ||
138 | } | ||
139 | } | ||
140 | |||
141 | public struct ConsoleDisplayTableRow | ||
142 | { | ||
143 | public List<object> Cells { get; private set; } | ||
144 | |||
145 | public ConsoleDisplayTableRow(List<object> cells) : this() | ||
146 | { | ||
147 | Cells = cells; | ||
148 | } | ||
149 | |||
150 | public ConsoleDisplayTableRow(params object[] cells) : this() | ||
151 | { | ||
152 | Cells = new List<object>(cells); | ||
153 | } | ||
154 | } | ||
155 | } \ No newline at end of file | ||
diff --git a/OpenSim/Framework/Console/ConsoleDisplayUtil.cs b/OpenSim/Framework/Console/ConsoleDisplayUtil.cs new file mode 100644 index 0000000..6417663 --- /dev/null +++ b/OpenSim/Framework/Console/ConsoleDisplayUtil.cs | |||
@@ -0,0 +1,48 @@ | |||
1 | /* | ||
2 | * Copyright (c) Contributors, http://opensimulator.org/ | ||
3 | * See CONTRIBUTORS.TXT for a full list of copyright holders. | ||
4 | * | ||
5 | * Redistribution and use in source and binary forms, with or without | ||
6 | * modification, are permitted provided that the following conditions are met: | ||
7 | * * Redistributions of source code must retain the above copyright | ||
8 | * notice, this list of conditions and the following disclaimer. | ||
9 | * * Redistributions in binary form must reproduce the above copyright | ||
10 | * notice, this list of conditions and the following disclaimer in the | ||
11 | * documentation and/or other materials provided with the distribution. | ||
12 | * * Neither the name of the OpenSimulator Project nor the | ||
13 | * names of its contributors may be used to endorse or promote products | ||
14 | * derived from this software without specific prior written permission. | ||
15 | * | ||
16 | * THIS SOFTWARE IS PROVIDED BY THE DEVELOPERS ``AS IS'' AND ANY | ||
17 | * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED | ||
18 | * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE | ||
19 | * DISCLAIMED. IN NO EVENT SHALL THE CONTRIBUTORS BE LIABLE FOR ANY | ||
20 | * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES | ||
21 | * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; | ||
22 | * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND | ||
23 | * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT | ||
24 | * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS | ||
25 | * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. | ||
26 | */ | ||
27 | |||
28 | using System; | ||
29 | |||
30 | namespace OpenSim.Framework.Console | ||
31 | { | ||
32 | /// <summary> | ||
33 | /// This will be a set of typical column sizes to allow greater consistency between console commands. | ||
34 | /// </summary> | ||
35 | public static class ConsoleDisplayUtil | ||
36 | { | ||
37 | public const int CoordTupleSize = 11; | ||
38 | public const int PortSize = 5; | ||
39 | |||
40 | public const int EstateNameSize = 20; | ||
41 | public const int ParcelNameSize = 40; | ||
42 | public const int RegionNameSize = 20; | ||
43 | public const int UserNameSize = 35; | ||
44 | |||
45 | public const int UuidSize = 36; | ||
46 | public const int VectorSize = 15; | ||
47 | } | ||
48 | } \ No newline at end of file | ||
diff --git a/OpenSim/Framework/Console/ConsolePluginCommand.cs b/OpenSim/Framework/Console/ConsolePluginCommand.cs new file mode 100755 index 0000000..f4d3687 --- /dev/null +++ b/OpenSim/Framework/Console/ConsolePluginCommand.cs | |||
@@ -0,0 +1,139 @@ | |||
1 | /* | ||
2 | * Copyright (c) Contributors, http://opensimulator.org/ | ||
3 | * See CONTRIBUTORS.TXT for a full list of copyright holders. | ||
4 | * | ||
5 | * Redistribution and use in source and binary forms, with or without | ||
6 | * modification, are permitted provided that the following conditions are met: | ||
7 | * * Redistributions of source code must retain the above copyright | ||
8 | * notice, this list of conditions and the following disclaimer. | ||
9 | * * Redistributions in binary form must reproduce the above copyright | ||
10 | * notice, this list of conditions and the following disclaimer in the | ||
11 | * documentation and/or other materials provided with the distribution. | ||
12 | * * Neither the name of the OpenSimulator Project nor the | ||
13 | * names of its contributors may be used to endorse or promote products | ||
14 | * derived from this software without specific prior written permission. | ||
15 | * | ||
16 | * THIS SOFTWARE IS PROVIDED BY THE DEVELOPERS ``AS IS'' AND ANY | ||
17 | * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED | ||
18 | * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE | ||
19 | * DISCLAIMED. IN NO EVENT SHALL THE CONTRIBUTORS BE LIABLE FOR ANY | ||
20 | * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES | ||
21 | * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; | ||
22 | * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND | ||
23 | * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT | ||
24 | * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS | ||
25 | * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. | ||
26 | */ | ||
27 | |||
28 | using System; | ||
29 | |||
30 | namespace OpenSim.Framework.Console | ||
31 | { | ||
32 | public delegate void ConsoleCommand(string[] comParams); | ||
33 | |||
34 | /// <summary> | ||
35 | /// Holder object for a new console plugin command | ||
36 | /// | ||
37 | /// Override the methods like Run and IsHelpfull (but the defaults might work ok.) | ||
38 | /// </summary> | ||
39 | public class ConsolePluginCommand | ||
40 | { | ||
41 | /// <summary> | ||
42 | /// command delegate used in running | ||
43 | /// </summary> | ||
44 | private ConsoleCommand m_commandDelegate; | ||
45 | /// <summary> | ||
46 | /// help text displayed | ||
47 | /// </summary> | ||
48 | private string m_helpText; | ||
49 | /// <summary> | ||
50 | /// command in the form of "showme new commands" | ||
51 | /// </summary> | ||
52 | private string[] m_cmdText; | ||
53 | |||
54 | /// <summary> | ||
55 | /// Construct a new ConsolePluginCommand | ||
56 | /// | ||
57 | /// for use with OpenSim.RegisterConsolePluginCommand(myCmd); | ||
58 | /// | ||
59 | /// </summary> | ||
60 | /// <param name="command">in the form of "showme new commands"</param> | ||
61 | /// <param name="dlg">ommand delegate used in running</param> | ||
62 | /// <param name="help">the text displayed in "help showme new commands"</param> | ||
63 | public ConsolePluginCommand(string command, ConsoleCommand dlg, string help) | ||
64 | { | ||
65 | m_cmdText = command.Split(new char[] { ' ' }); | ||
66 | m_commandDelegate = dlg; | ||
67 | m_helpText = help; | ||
68 | } | ||
69 | |||
70 | /// <summary> | ||
71 | /// Returns the match length this command has upon the 'cmdWithParams' | ||
72 | /// At least a higher number for "show plugin status" then "show" would return | ||
73 | /// This is used to have multi length command verbs | ||
74 | /// | ||
75 | /// @see OopenSim.RunPluginCommands | ||
76 | /// It will only run the one with the highest number | ||
77 | /// | ||
78 | /// </summary> | ||
79 | public int matchLength(string cmdWithParams) | ||
80 | { | ||
81 | // QUESTION: have a case insensitive flag? | ||
82 | cmdWithParams = cmdWithParams.ToLower().Trim(); | ||
83 | string matchText = String.Join(" ",m_cmdText).ToLower().Trim(); | ||
84 | if (cmdWithParams.StartsWith(matchText)) | ||
85 | { | ||
86 | // QUESTION Instead return cmdText.Length; ? | ||
87 | return matchText.Length; | ||
88 | } | ||
89 | return 0; | ||
90 | } | ||
91 | |||
92 | /// <summary> | ||
93 | /// Run the delegate the incomming string may contain the command, if so, it is chopped off the cmdParams[] | ||
94 | /// </summary> | ||
95 | public void Run(string cmd, string[] cmdParams) | ||
96 | { | ||
97 | int skipParams = 0; | ||
98 | if (m_cmdText.Length > 1) | ||
99 | { | ||
100 | int currentParam = 1; | ||
101 | while (currentParam < m_cmdText.Length) | ||
102 | { | ||
103 | if (cmdParams[skipParams].ToLower().Equals(m_cmdText[currentParam].ToLower())) | ||
104 | { | ||
105 | skipParams++; | ||
106 | } | ||
107 | currentParam++; | ||
108 | } | ||
109 | |||
110 | } | ||
111 | string[] sendCmdParams = cmdParams; | ||
112 | if (skipParams > 0) | ||
113 | { | ||
114 | sendCmdParams = new string[cmdParams.Length-skipParams]; | ||
115 | for (int i=0;i<sendCmdParams.Length;i++) { | ||
116 | sendCmdParams[i] = cmdParams[skipParams++]; | ||
117 | } | ||
118 | } | ||
119 | m_commandDelegate(sendCmdParams);//.Trim().Split(new char[] { ' ' })); | ||
120 | } | ||
121 | |||
122 | /// <summary> | ||
123 | /// Shows help information on the console's Notice method | ||
124 | /// </summary> | ||
125 | public void ShowHelp(ConsoleBase console) | ||
126 | { | ||
127 | console.Output(String.Join(" ", m_cmdText) + " - " + m_helpText + "\n"); | ||
128 | } | ||
129 | |||
130 | /// <summary> | ||
131 | /// return true if the ShowHelp(..) method might be helpfull | ||
132 | /// </summary> | ||
133 | public bool IsHelpfull(string cmdWithParams) | ||
134 | { | ||
135 | cmdWithParams = cmdWithParams.ToLower(); | ||
136 | return cmdWithParams.Contains(String.Join(" ", m_cmdText).ToLower()) || m_helpText.ToLower().Contains(cmdWithParams); | ||
137 | } | ||
138 | } | ||
139 | } | ||
diff --git a/OpenSim/Framework/Console/ConsoleUtil.cs b/OpenSim/Framework/Console/ConsoleUtil.cs new file mode 100644 index 0000000..44f6dc1 --- /dev/null +++ b/OpenSim/Framework/Console/ConsoleUtil.cs | |||
@@ -0,0 +1,387 @@ | |||
1 | /* | ||
2 | * Copyright (c) Contributors, http://opensimulator.org/ | ||
3 | * See CONTRIBUTORS.TXT for a full list of copyright holders. | ||
4 | * | ||
5 | * Redistribution and use in source and binary forms, with or without | ||
6 | * modification, are permitted provided that the following conditions are met: | ||
7 | * * Redistributions of source code must retain the above copyright | ||
8 | * notice, this list of conditions and the following disclaimer. | ||
9 | * * Redistributions in binary form must reproduce the above copyright | ||
10 | * notice, this list of conditions and the following disclaimer in the | ||
11 | * documentation and/or other materials provided with the distribution. | ||
12 | * * Neither the name of the OpenSimulator Project nor the | ||
13 | * names of its contributors may be used to endorse or promote products | ||
14 | * derived from this software without specific prior written permission. | ||
15 | * | ||
16 | * THIS SOFTWARE IS PROVIDED BY THE DEVELOPERS ``AS IS'' AND ANY | ||
17 | * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED | ||
18 | * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE | ||
19 | * DISCLAIMED. IN NO EVENT SHALL THE CONTRIBUTORS BE LIABLE FOR ANY | ||
20 | * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES | ||
21 | * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; | ||
22 | * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND | ||
23 | * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT | ||
24 | * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS | ||
25 | * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. | ||
26 | */ | ||
27 | |||
28 | using System; | ||
29 | using System.Collections.Generic; | ||
30 | using System.IO; | ||
31 | using System.Linq; | ||
32 | using System.Reflection; | ||
33 | using log4net; | ||
34 | using OpenMetaverse; | ||
35 | |||
36 | namespace OpenSim.Framework.Console | ||
37 | { | ||
38 | public class ConsoleUtil | ||
39 | { | ||
40 | // private static readonly ILog m_log = LogManager.GetLogger(MethodBase.GetCurrentMethod().DeclaringType); | ||
41 | |||
42 | public const int LocalIdNotFound = 0; | ||
43 | |||
44 | /// <summary> | ||
45 | /// Used by modules to display stock co-ordinate help, though possibly this should be under some general section | ||
46 | /// rather than in each help summary. | ||
47 | /// </summary> | ||
48 | public const string CoordHelp | ||
49 | = @"Each component of the coord is comma separated. There must be no spaces between the commas. | ||
50 | If you don't care about the z component you can simply omit it. | ||
51 | If you don't care about the x or y components then you can leave them blank (though a comma is still required) | ||
52 | If you want to specify the maximum value of a component then you can use ~ instead of a number | ||
53 | If you want to specify the minimum value of a component then you can use -~ instead of a number | ||
54 | e.g. | ||
55 | show object pos 20,20,20 to 40,40,40 | ||
56 | delete object pos 20,20 to 40,40 | ||
57 | show object pos ,20,20 to ,40,40 | ||
58 | delete object pos ,,30 to ,,~ | ||
59 | show object pos ,,-~ to ,,30"; | ||
60 | |||
61 | public const string MinRawConsoleVectorValue = "-~"; | ||
62 | public const string MaxRawConsoleVectorValue = "~"; | ||
63 | |||
64 | public const string VectorSeparator = ","; | ||
65 | public static char[] VectorSeparatorChars = VectorSeparator.ToCharArray(); | ||
66 | |||
67 | /// <summary> | ||
68 | /// Check if the given file path exists. | ||
69 | /// </summary> | ||
70 | /// <remarks>If not, warning is printed to the given console.</remarks> | ||
71 | /// <returns>true if the file does not exist, false otherwise.</returns> | ||
72 | /// <param name='console'></param> | ||
73 | /// <param name='path'></param> | ||
74 | public static bool CheckFileDoesNotExist(ICommandConsole console, string path) | ||
75 | { | ||
76 | if (File.Exists(path)) | ||
77 | { | ||
78 | console.OutputFormat("File {0} already exists. Please move or remove it.", path); | ||
79 | return false; | ||
80 | } | ||
81 | |||
82 | return true; | ||
83 | } | ||
84 | |||
85 | /// <summary> | ||
86 | /// Try to parse a console UUID from the console. | ||
87 | /// </summary> | ||
88 | /// <remarks> | ||
89 | /// Will complain to the console if parsing fails. | ||
90 | /// </remarks> | ||
91 | /// <returns></returns> | ||
92 | /// <param name='console'>If null then no complaint is printed.</param> | ||
93 | /// <param name='rawUuid'></param> | ||
94 | /// <param name='uuid'></param> | ||
95 | public static bool TryParseConsoleUuid(ICommandConsole console, string rawUuid, out UUID uuid) | ||
96 | { | ||
97 | if (!UUID.TryParse(rawUuid, out uuid)) | ||
98 | { | ||
99 | if (console != null) | ||
100 | console.OutputFormat("ERROR: {0} is not a valid uuid", rawUuid); | ||
101 | |||
102 | return false; | ||
103 | } | ||
104 | |||
105 | return true; | ||
106 | } | ||
107 | |||
108 | public static bool TryParseConsoleLocalId(ICommandConsole console, string rawLocalId, out uint localId) | ||
109 | { | ||
110 | if (!uint.TryParse(rawLocalId, out localId)) | ||
111 | { | ||
112 | if (console != null) | ||
113 | console.OutputFormat("ERROR: {0} is not a valid local id", localId); | ||
114 | |||
115 | return false; | ||
116 | } | ||
117 | |||
118 | if (localId == 0) | ||
119 | { | ||
120 | if (console != null) | ||
121 | console.OutputFormat("ERROR: {0} is not a valid local id - it must be greater than 0", localId); | ||
122 | |||
123 | return false; | ||
124 | } | ||
125 | |||
126 | return true; | ||
127 | } | ||
128 | |||
129 | /// <summary> | ||
130 | /// Tries to parse the input as either a UUID or a local ID. | ||
131 | /// </summary> | ||
132 | /// <returns>true if parsing succeeded, false otherwise.</returns> | ||
133 | /// <param name='console'></param> | ||
134 | /// <param name='rawId'></param> | ||
135 | /// <param name='uuid'></param> | ||
136 | /// <param name='localId'> | ||
137 | /// Will be set to ConsoleUtil.LocalIdNotFound if parsing result was a UUID or no parse succeeded. | ||
138 | /// </param> | ||
139 | public static bool TryParseConsoleId(ICommandConsole console, string rawId, out UUID uuid, out uint localId) | ||
140 | { | ||
141 | if (TryParseConsoleUuid(null, rawId, out uuid)) | ||
142 | { | ||
143 | localId = LocalIdNotFound; | ||
144 | return true; | ||
145 | } | ||
146 | |||
147 | if (TryParseConsoleLocalId(null, rawId, out localId)) | ||
148 | { | ||
149 | return true; | ||
150 | } | ||
151 | |||
152 | if (console != null) | ||
153 | console.OutputFormat("ERROR: {0} is not a valid UUID or local id", rawId); | ||
154 | |||
155 | return false; | ||
156 | } | ||
157 | |||
158 | /// <summary> | ||
159 | /// Convert a console input to a bool, automatically complaining if a console is given. | ||
160 | /// </summary> | ||
161 | /// <param name='console'>Can be null if no console is available.</param> | ||
162 | /// <param name='rawConsoleVector'>/param> | ||
163 | /// <param name='vector'></param> | ||
164 | /// <returns></returns> | ||
165 | public static bool TryParseConsoleBool(ICommandConsole console, string rawConsoleString, out bool b) | ||
166 | { | ||
167 | if (!bool.TryParse(rawConsoleString, out b)) | ||
168 | { | ||
169 | if (console != null) | ||
170 | console.OutputFormat("ERROR: {0} is not a true or false value", rawConsoleString); | ||
171 | |||
172 | return false; | ||
173 | } | ||
174 | |||
175 | return true; | ||
176 | } | ||
177 | |||
178 | /// <summary> | ||
179 | /// Convert a console input to an int, automatically complaining if a console is given. | ||
180 | /// </summary> | ||
181 | /// <param name='console'>Can be null if no console is available.</param> | ||
182 | /// <param name='rawConsoleInt'>/param> | ||
183 | /// <param name='i'></param> | ||
184 | /// <returns></returns> | ||
185 | public static bool TryParseConsoleInt(ICommandConsole console, string rawConsoleInt, out int i) | ||
186 | { | ||
187 | if (!int.TryParse(rawConsoleInt, out i)) | ||
188 | { | ||
189 | if (console != null) | ||
190 | console.OutputFormat("ERROR: {0} is not a valid integer", rawConsoleInt); | ||
191 | |||
192 | return false; | ||
193 | } | ||
194 | |||
195 | return true; | ||
196 | } | ||
197 | |||
198 | /// <summary> | ||
199 | /// Convert a console input to a float, automatically complaining if a console is given. | ||
200 | /// </summary> | ||
201 | /// <param name='console'>Can be null if no console is available.</param> | ||
202 | /// <param name='rawConsoleInput'>/param> | ||
203 | /// <param name='i'></param> | ||
204 | /// <returns></returns> | ||
205 | public static bool TryParseConsoleFloat(ICommandConsole console, string rawConsoleInput, out float i) | ||
206 | { | ||
207 | if (!float.TryParse(rawConsoleInput, out i)) | ||
208 | { | ||
209 | if (console != null) | ||
210 | console.OutputFormat("ERROR: {0} is not a valid float", rawConsoleInput); | ||
211 | |||
212 | return false; | ||
213 | } | ||
214 | |||
215 | return true; | ||
216 | } | ||
217 | |||
218 | /// <summary> | ||
219 | /// Convert a console input to a double, automatically complaining if a console is given. | ||
220 | /// </summary> | ||
221 | /// <param name='console'>Can be null if no console is available.</param> | ||
222 | /// <param name='rawConsoleInput'>/param> | ||
223 | /// <param name='i'></param> | ||
224 | /// <returns></returns> | ||
225 | public static bool TryParseConsoleDouble(ICommandConsole console, string rawConsoleInput, out double i) | ||
226 | { | ||
227 | if (!double.TryParse(rawConsoleInput, out i)) | ||
228 | { | ||
229 | if (console != null) | ||
230 | console.OutputFormat("ERROR: {0} is not a valid double", rawConsoleInput); | ||
231 | |||
232 | return false; | ||
233 | } | ||
234 | |||
235 | return true; | ||
236 | } | ||
237 | |||
238 | /// <summary> | ||
239 | /// Convert a console integer to a natural int, automatically complaining if a console is given. | ||
240 | /// </summary> | ||
241 | /// <param name='console'>Can be null if no console is available.</param> | ||
242 | /// <param name='rawConsoleInt'>/param> | ||
243 | /// <param name='i'></param> | ||
244 | /// <returns></returns> | ||
245 | public static bool TryParseConsoleNaturalInt(ICommandConsole console, string rawConsoleInt, out int i) | ||
246 | { | ||
247 | if (TryParseConsoleInt(console, rawConsoleInt, out i)) | ||
248 | { | ||
249 | if (i < 0) | ||
250 | { | ||
251 | if (console != null) | ||
252 | console.OutputFormat("ERROR: {0} is not a positive integer", rawConsoleInt); | ||
253 | |||
254 | return false; | ||
255 | } | ||
256 | |||
257 | return true; | ||
258 | } | ||
259 | |||
260 | return false; | ||
261 | } | ||
262 | |||
263 | /// <summary> | ||
264 | /// Convert a minimum vector input from the console to an OpenMetaverse.Vector3 | ||
265 | /// </summary> | ||
266 | /// <param name='rawConsoleVector'>/param> | ||
267 | /// <param name='vector'></param> | ||
268 | /// <returns></returns> | ||
269 | public static bool TryParseConsoleMinVector(string rawConsoleVector, out Vector3 vector) | ||
270 | { | ||
271 | return TryParseConsoleVector(rawConsoleVector, c => float.MinValue.ToString(), out vector); | ||
272 | } | ||
273 | |||
274 | /// <summary> | ||
275 | /// Convert a maximum vector input from the console to an OpenMetaverse.Vector3 | ||
276 | /// </summary> | ||
277 | /// <param name='rawConsoleVector'>/param> | ||
278 | /// <param name='vector'></param> | ||
279 | /// <returns></returns> | ||
280 | public static bool TryParseConsoleMaxVector(string rawConsoleVector, out Vector3 vector) | ||
281 | { | ||
282 | return TryParseConsoleVector(rawConsoleVector, c => float.MaxValue.ToString(), out vector); | ||
283 | } | ||
284 | |||
285 | /// <summary> | ||
286 | /// Convert a vector input from the console to an OpenMetaverse.Vector3 | ||
287 | /// </summary> | ||
288 | /// <param name='rawConsoleVector'> | ||
289 | /// A string in the form <x>,<y>,<z> where there is no space between values. | ||
290 | /// Any component can be missing (e.g. ,,40). blankComponentFunc is invoked to replace the blank with a suitable value | ||
291 | /// Also, if the blank component is at the end, then the comma can be missed off entirely (e.g. 40,30 or 40) | ||
292 | /// The strings "~" and "-~" are valid in components. The first substitutes float.MaxValue whilst the second is float.MinValue | ||
293 | /// Other than that, component values must be numeric. | ||
294 | /// </param> | ||
295 | /// <param name='blankComponentFunc'> | ||
296 | /// Behaviour if component is blank. If null then conversion fails on a blank component. | ||
297 | /// </param> | ||
298 | /// <param name='vector'></param> | ||
299 | /// <returns></returns> | ||
300 | public static bool TryParseConsoleVector( | ||
301 | string rawConsoleVector, Func<string, string> blankComponentFunc, out Vector3 vector) | ||
302 | { | ||
303 | return Vector3.TryParse(CookVector(rawConsoleVector, 3, blankComponentFunc), out vector); | ||
304 | } | ||
305 | |||
306 | /// <summary> | ||
307 | /// Convert a vector input from the console to an OpenMetaverse.Vector2 | ||
308 | /// </summary> | ||
309 | /// <param name='rawConsoleVector'> | ||
310 | /// A string in the form <x>,<y> where there is no space between values. | ||
311 | /// Any component can be missing (e.g. ,40). blankComponentFunc is invoked to replace the blank with a suitable value | ||
312 | /// Also, if the blank component is at the end, then the comma can be missed off entirely (e.g. 40) | ||
313 | /// The strings "~" and "-~" are valid in components. The first substitutes float.MaxValue whilst the second is float.MinValue | ||
314 | /// Other than that, component values must be numeric. | ||
315 | /// </param> | ||
316 | /// <param name='blankComponentFunc'> | ||
317 | /// Behaviour if component is blank. If null then conversion fails on a blank component. | ||
318 | /// </param> | ||
319 | /// <param name='vector'></param> | ||
320 | /// <returns></returns> | ||
321 | public static bool TryParseConsole2DVector( | ||
322 | string rawConsoleVector, Func<string, string> blankComponentFunc, out Vector2 vector) | ||
323 | { | ||
324 | // We don't use Vector2.TryParse() for now because for some reason it expects an input with 3 components | ||
325 | // rather than 2. | ||
326 | string cookedVector = CookVector(rawConsoleVector, 2, blankComponentFunc); | ||
327 | |||
328 | if (cookedVector == null) | ||
329 | { | ||
330 | vector = Vector2.Zero; | ||
331 | |||
332 | return false; | ||
333 | } | ||
334 | else | ||
335 | { | ||
336 | string[] cookedComponents = cookedVector.Split(VectorSeparatorChars); | ||
337 | |||
338 | vector = new Vector2(float.Parse(cookedComponents[0]), float.Parse(cookedComponents[1])); | ||
339 | |||
340 | return true; | ||
341 | } | ||
342 | |||
343 | //return Vector2.TryParse(CookVector(rawConsoleVector, 2, blankComponentFunc), out vector); | ||
344 | } | ||
345 | |||
346 | /// <summary> | ||
347 | /// Convert a raw console vector into a vector that can be be parsed by the relevant OpenMetaverse.TryParse() | ||
348 | /// </summary> | ||
349 | /// <param name='rawConsoleVector'></param> | ||
350 | /// <param name='dimensions'></param> | ||
351 | /// <param name='blankComponentFunc'></param> | ||
352 | /// <returns>null if conversion was not possible</returns> | ||
353 | private static string CookVector( | ||
354 | string rawConsoleVector, int dimensions, Func<string, string> blankComponentFunc) | ||
355 | { | ||
356 | List<string> components = rawConsoleVector.Split(VectorSeparatorChars).ToList(); | ||
357 | |||
358 | if (components.Count < 1 || components.Count > dimensions) | ||
359 | return null; | ||
360 | |||
361 | if (components.Count < dimensions) | ||
362 | { | ||
363 | if (blankComponentFunc == null) | ||
364 | return null; | ||
365 | else | ||
366 | for (int i = components.Count; i < dimensions; i++) | ||
367 | components.Add(""); | ||
368 | } | ||
369 | |||
370 | List<string> cookedComponents | ||
371 | = components.ConvertAll<string>( | ||
372 | c => | ||
373 | { | ||
374 | if (c == "") | ||
375 | return blankComponentFunc.Invoke(c); | ||
376 | else if (c == MaxRawConsoleVectorValue) | ||
377 | return float.MaxValue.ToString(); | ||
378 | else if (c == MinRawConsoleVectorValue) | ||
379 | return float.MinValue.ToString(); | ||
380 | else | ||
381 | return c; | ||
382 | }); | ||
383 | |||
384 | return string.Join(VectorSeparator, cookedComponents.ToArray()); | ||
385 | } | ||
386 | } | ||
387 | } \ No newline at end of file | ||
diff --git a/OpenSim/Framework/Console/LocalConsole.cs b/OpenSim/Framework/Console/LocalConsole.cs new file mode 100644 index 0000000..28293c0 --- /dev/null +++ b/OpenSim/Framework/Console/LocalConsole.cs | |||
@@ -0,0 +1,585 @@ | |||
1 | /* | ||
2 | * Copyright (c) Contributors, http://opensimulator.org/ | ||
3 | * See CONTRIBUTORS.TXT for a full list of copyright holders. | ||
4 | * | ||
5 | * Redistribution and use in source and binary forms, with or without | ||
6 | * modification, are permitted provided that the following conditions are met: | ||
7 | * * Redistributions of source code must retain the above copyright | ||
8 | * notice, this list of conditions and the following disclaimer. | ||
9 | * * Redistributions in binary form must reproduce the above copyright | ||
10 | * notice, this list of conditions and the following disclaimer in the | ||
11 | * documentation and/or other materials provided with the distribution. | ||
12 | * * Neither the name of the OpenSimulator Project nor the | ||
13 | * names of its contributors may be used to endorse or promote products | ||
14 | * derived from this software without specific prior written permission. | ||
15 | * | ||
16 | * THIS SOFTWARE IS PROVIDED BY THE DEVELOPERS ``AS IS'' AND ANY | ||
17 | * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED | ||
18 | * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE | ||
19 | * DISCLAIMED. IN NO EVENT SHALL THE CONTRIBUTORS BE LIABLE FOR ANY | ||
20 | * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES | ||
21 | * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; | ||
22 | * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND | ||
23 | * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT | ||
24 | * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS | ||
25 | * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. | ||
26 | */ | ||
27 | |||
28 | using System; | ||
29 | using System.Collections.Generic; | ||
30 | using System.Diagnostics; | ||
31 | using System.Reflection; | ||
32 | using System.Text; | ||
33 | using System.Text.RegularExpressions; | ||
34 | using System.Threading; | ||
35 | using System.IO; | ||
36 | using Nini.Config; | ||
37 | using log4net; | ||
38 | |||
39 | namespace OpenSim.Framework.Console | ||
40 | { | ||
41 | /// <summary> | ||
42 | /// A console that uses cursor control and color | ||
43 | /// </summary> | ||
44 | public class LocalConsole : CommandConsole | ||
45 | { | ||
46 | private static readonly ILog m_log = LogManager.GetLogger(MethodBase.GetCurrentMethod().DeclaringType); | ||
47 | private string m_historyPath; | ||
48 | private bool m_historyEnable; | ||
49 | |||
50 | // private readonly object m_syncRoot = new object(); | ||
51 | private const string LOGLEVEL_NONE = "(none)"; | ||
52 | |||
53 | // Used to extract categories for colourization. | ||
54 | private Regex m_categoryRegex | ||
55 | = new Regex( | ||
56 | @"^(?<Front>.*?)\[(?<Category>[^\]]+)\]:?(?<End>.*)", RegexOptions.Singleline | RegexOptions.Compiled); | ||
57 | |||
58 | private int m_cursorYPosition = -1; | ||
59 | private int m_cursorXPosition = 0; | ||
60 | private StringBuilder m_commandLine = new StringBuilder(); | ||
61 | private bool m_echo = true; | ||
62 | private List<string> m_history = new List<string>(); | ||
63 | |||
64 | private static readonly ConsoleColor[] Colors = { | ||
65 | // the dark colors don't seem to be visible on some black background terminals like putty :( | ||
66 | //ConsoleColor.DarkBlue, | ||
67 | //ConsoleColor.DarkGreen, | ||
68 | //ConsoleColor.DarkCyan, | ||
69 | //ConsoleColor.DarkMagenta, | ||
70 | //ConsoleColor.DarkYellow, | ||
71 | ConsoleColor.Gray, | ||
72 | //ConsoleColor.DarkGray, | ||
73 | ConsoleColor.Blue, | ||
74 | ConsoleColor.Green, | ||
75 | ConsoleColor.Cyan, | ||
76 | ConsoleColor.Magenta, | ||
77 | ConsoleColor.Yellow | ||
78 | }; | ||
79 | |||
80 | private static ConsoleColor DeriveColor(string input) | ||
81 | { | ||
82 | // it is important to do Abs, hash values can be negative | ||
83 | return Colors[(Math.Abs(input.ToUpper().GetHashCode()) % Colors.Length)]; | ||
84 | } | ||
85 | |||
86 | public LocalConsole(string defaultPrompt, IConfig startupConfig = null) : base(defaultPrompt) | ||
87 | { | ||
88 | |||
89 | if (startupConfig == null) return; | ||
90 | |||
91 | m_historyEnable = startupConfig.GetBoolean("ConsoleHistoryFileEnabled", false); | ||
92 | if (!m_historyEnable) | ||
93 | { | ||
94 | m_log.Info("[LOCAL CONSOLE]: Persistent command line history from file is Disabled"); | ||
95 | return; | ||
96 | } | ||
97 | |||
98 | string m_historyFile = startupConfig.GetString("ConsoleHistoryFile", "OpenSimConsoleHistory.txt"); | ||
99 | int m_historySize = startupConfig.GetInt("ConsoleHistoryFileLines", 100); | ||
100 | m_historyPath = Path.GetFullPath(Path.Combine(Util.configDir(), m_historyFile)); | ||
101 | m_log.InfoFormat("[LOCAL CONSOLE]: Persistent command line history is Enabled, up to {0} lines from file {1}", m_historySize, m_historyPath); | ||
102 | |||
103 | if (File.Exists(m_historyPath)) | ||
104 | { | ||
105 | using (StreamReader history_file = new StreamReader(m_historyPath)) | ||
106 | { | ||
107 | string line; | ||
108 | while ((line = history_file.ReadLine()) != null) | ||
109 | { | ||
110 | m_history.Add(line); | ||
111 | } | ||
112 | } | ||
113 | |||
114 | if (m_history.Count > m_historySize) | ||
115 | { | ||
116 | while (m_history.Count > m_historySize) | ||
117 | m_history.RemoveAt(0); | ||
118 | |||
119 | using (StreamWriter history_file = new StreamWriter(m_historyPath)) | ||
120 | { | ||
121 | foreach (string line in m_history) | ||
122 | { | ||
123 | history_file.WriteLine(line); | ||
124 | } | ||
125 | } | ||
126 | } | ||
127 | m_log.InfoFormat("[LOCAL CONSOLE]: Read {0} lines of command line history from file {1}", m_history.Count, m_historyPath); | ||
128 | } | ||
129 | else | ||
130 | { | ||
131 | m_log.InfoFormat("[LOCAL CONSOLE]: Creating new empty command line history file {0}", m_historyPath); | ||
132 | File.Create(m_historyPath).Dispose(); | ||
133 | } | ||
134 | } | ||
135 | |||
136 | private void AddToHistory(string text) | ||
137 | { | ||
138 | while (m_history.Count >= 100) | ||
139 | m_history.RemoveAt(0); | ||
140 | |||
141 | m_history.Add(text); | ||
142 | if (m_historyEnable) | ||
143 | { | ||
144 | File.AppendAllText(m_historyPath, text + Environment.NewLine); | ||
145 | } | ||
146 | } | ||
147 | |||
148 | /// <summary> | ||
149 | /// Set the cursor row. | ||
150 | /// </summary> | ||
151 | /// | ||
152 | /// <param name="top"> | ||
153 | /// Row to set. If this is below 0, then the row is set to 0. If it is equal to the buffer height or greater | ||
154 | /// then it is set to one less than the height. | ||
155 | /// </param> | ||
156 | /// <returns> | ||
157 | /// The new cursor row. | ||
158 | /// </returns> | ||
159 | private int SetCursorTop(int top) | ||
160 | { | ||
161 | // From at least mono 2.4.2.3, window resizing can give mono an invalid row and column values. If we try | ||
162 | // to set a cursor row position with a currently invalid column, mono will throw an exception. | ||
163 | // Therefore, we need to make sure that the column position is valid first. | ||
164 | int left = System.Console.CursorLeft; | ||
165 | |||
166 | if (left < 0) | ||
167 | { | ||
168 | System.Console.CursorLeft = 0; | ||
169 | } | ||
170 | else | ||
171 | { | ||
172 | int bufferWidth = System.Console.BufferWidth; | ||
173 | |||
174 | // On Mono 2.4.2.3 (and possibly above), the buffer value is sometimes erroneously zero (Mantis 4657) | ||
175 | if (bufferWidth > 0 && left >= bufferWidth) | ||
176 | System.Console.CursorLeft = bufferWidth - 1; | ||
177 | } | ||
178 | |||
179 | if (top < 0) | ||
180 | { | ||
181 | top = 0; | ||
182 | } | ||
183 | else | ||
184 | { | ||
185 | int bufferHeight = System.Console.BufferHeight; | ||
186 | |||
187 | // On Mono 2.4.2.3 (and possibly above), the buffer value is sometimes erroneously zero (Mantis 4657) | ||
188 | if (bufferHeight > 0 && top >= bufferHeight) | ||
189 | top = bufferHeight - 1; | ||
190 | } | ||
191 | |||
192 | System.Console.CursorTop = top; | ||
193 | |||
194 | return top; | ||
195 | } | ||
196 | |||
197 | /// <summary> | ||
198 | /// Set the cursor column. | ||
199 | /// </summary> | ||
200 | /// | ||
201 | /// <param name="left"> | ||
202 | /// Column to set. If this is below 0, then the column is set to 0. If it is equal to the buffer width or greater | ||
203 | /// then it is set to one less than the width. | ||
204 | /// </param> | ||
205 | /// <returns> | ||
206 | /// The new cursor column. | ||
207 | /// </returns> | ||
208 | private int SetCursorLeft(int left) | ||
209 | { | ||
210 | // From at least mono 2.4.2.3, window resizing can give mono an invalid row and column values. If we try | ||
211 | // to set a cursor column position with a currently invalid row, mono will throw an exception. | ||
212 | // Therefore, we need to make sure that the row position is valid first. | ||
213 | int top = System.Console.CursorTop; | ||
214 | |||
215 | if (top < 0) | ||
216 | { | ||
217 | System.Console.CursorTop = 0; | ||
218 | } | ||
219 | else | ||
220 | { | ||
221 | int bufferHeight = System.Console.BufferHeight; | ||
222 | // On Mono 2.4.2.3 (and possibly above), the buffer value is sometimes erroneously zero (Mantis 4657) | ||
223 | if (bufferHeight > 0 && top >= bufferHeight) | ||
224 | System.Console.CursorTop = bufferHeight - 1; | ||
225 | } | ||
226 | |||
227 | if (left < 0) | ||
228 | { | ||
229 | left = 0; | ||
230 | } | ||
231 | else | ||
232 | { | ||
233 | int bufferWidth = System.Console.BufferWidth; | ||
234 | |||
235 | // On Mono 2.4.2.3 (and possibly above), the buffer value is sometimes erroneously zero (Mantis 4657) | ||
236 | if (bufferWidth > 0 && left >= bufferWidth) | ||
237 | left = bufferWidth - 1; | ||
238 | } | ||
239 | |||
240 | System.Console.CursorLeft = left; | ||
241 | |||
242 | return left; | ||
243 | } | ||
244 | |||
245 | private void Show() | ||
246 | { | ||
247 | lock (m_commandLine) | ||
248 | { | ||
249 | if (m_cursorYPosition == -1 || System.Console.BufferWidth == 0) | ||
250 | return; | ||
251 | |||
252 | int xc = prompt.Length + m_cursorXPosition; | ||
253 | int new_x = xc % System.Console.BufferWidth; | ||
254 | int new_y = m_cursorYPosition + xc / System.Console.BufferWidth; | ||
255 | int end_y = m_cursorYPosition + (m_commandLine.Length + prompt.Length) / System.Console.BufferWidth; | ||
256 | |||
257 | if (end_y >= System.Console.BufferHeight) // wrap | ||
258 | { | ||
259 | m_cursorYPosition--; | ||
260 | new_y--; | ||
261 | SetCursorLeft(0); | ||
262 | SetCursorTop(System.Console.BufferHeight - 1); | ||
263 | System.Console.WriteLine(" "); | ||
264 | } | ||
265 | |||
266 | m_cursorYPosition = SetCursorTop(m_cursorYPosition); | ||
267 | SetCursorLeft(0); | ||
268 | |||
269 | if (m_echo) | ||
270 | System.Console.Write("{0}{1}", prompt, m_commandLine); | ||
271 | else | ||
272 | System.Console.Write("{0}", prompt); | ||
273 | |||
274 | SetCursorTop(new_y); | ||
275 | SetCursorLeft(new_x); | ||
276 | } | ||
277 | } | ||
278 | |||
279 | public override void LockOutput() | ||
280 | { | ||
281 | Monitor.Enter(m_commandLine); | ||
282 | try | ||
283 | { | ||
284 | if (m_cursorYPosition != -1) | ||
285 | { | ||
286 | m_cursorYPosition = SetCursorTop(m_cursorYPosition); | ||
287 | System.Console.CursorLeft = 0; | ||
288 | |||
289 | int count = m_commandLine.Length + prompt.Length; | ||
290 | |||
291 | while (count-- > 0) | ||
292 | System.Console.Write(" "); | ||
293 | |||
294 | m_cursorYPosition = SetCursorTop(m_cursorYPosition); | ||
295 | SetCursorLeft(0); | ||
296 | } | ||
297 | } | ||
298 | catch (Exception) | ||
299 | { | ||
300 | } | ||
301 | } | ||
302 | |||
303 | public override void UnlockOutput() | ||
304 | { | ||
305 | if (m_cursorYPosition != -1) | ||
306 | { | ||
307 | m_cursorYPosition = System.Console.CursorTop; | ||
308 | Show(); | ||
309 | } | ||
310 | Monitor.Exit(m_commandLine); | ||
311 | } | ||
312 | |||
313 | private void WriteColorText(ConsoleColor color, string sender) | ||
314 | { | ||
315 | try | ||
316 | { | ||
317 | lock (this) | ||
318 | { | ||
319 | try | ||
320 | { | ||
321 | System.Console.ForegroundColor = color; | ||
322 | System.Console.Write(sender); | ||
323 | System.Console.ResetColor(); | ||
324 | } | ||
325 | catch (ArgumentNullException) | ||
326 | { | ||
327 | // Some older systems dont support coloured text. | ||
328 | System.Console.WriteLine(sender); | ||
329 | } | ||
330 | } | ||
331 | } | ||
332 | catch (ObjectDisposedException) | ||
333 | { | ||
334 | } | ||
335 | } | ||
336 | |||
337 | private void WriteLocalText(string text, string level) | ||
338 | { | ||
339 | string outText = text; | ||
340 | |||
341 | if (level != LOGLEVEL_NONE) | ||
342 | { | ||
343 | MatchCollection matches = m_categoryRegex.Matches(text); | ||
344 | |||
345 | if (matches.Count == 1) | ||
346 | { | ||
347 | outText = matches[0].Groups["End"].Value; | ||
348 | System.Console.Write(matches[0].Groups["Front"].Value); | ||
349 | |||
350 | System.Console.Write("["); | ||
351 | WriteColorText(DeriveColor(matches[0].Groups["Category"].Value), | ||
352 | matches[0].Groups["Category"].Value); | ||
353 | System.Console.Write("]:"); | ||
354 | } | ||
355 | else | ||
356 | { | ||
357 | outText = outText.Trim(); | ||
358 | } | ||
359 | } | ||
360 | |||
361 | if (level == "error") | ||
362 | WriteColorText(ConsoleColor.Red, outText); | ||
363 | else if (level == "warn") | ||
364 | WriteColorText(ConsoleColor.Yellow, outText); | ||
365 | else | ||
366 | System.Console.Write(outText); | ||
367 | |||
368 | System.Console.WriteLine(); | ||
369 | } | ||
370 | |||
371 | public override void Output(string text) | ||
372 | { | ||
373 | Output(text, LOGLEVEL_NONE); | ||
374 | } | ||
375 | |||
376 | public override void Output(string text, string level) | ||
377 | { | ||
378 | FireOnOutput(text); | ||
379 | |||
380 | lock (m_commandLine) | ||
381 | { | ||
382 | if (m_cursorYPosition == -1) | ||
383 | { | ||
384 | WriteLocalText(text, level); | ||
385 | |||
386 | return; | ||
387 | } | ||
388 | |||
389 | m_cursorYPosition = SetCursorTop(m_cursorYPosition); | ||
390 | SetCursorLeft(0); | ||
391 | |||
392 | int count = m_commandLine.Length + prompt.Length; | ||
393 | |||
394 | while (count-- > 0) | ||
395 | System.Console.Write(" "); | ||
396 | |||
397 | m_cursorYPosition = SetCursorTop(m_cursorYPosition); | ||
398 | SetCursorLeft(0); | ||
399 | |||
400 | WriteLocalText(text, level); | ||
401 | |||
402 | m_cursorYPosition = System.Console.CursorTop; | ||
403 | |||
404 | Show(); | ||
405 | } | ||
406 | } | ||
407 | |||
408 | private bool ContextHelp() | ||
409 | { | ||
410 | string[] words = Parser.Parse(m_commandLine.ToString()); | ||
411 | |||
412 | bool trailingSpace = m_commandLine.ToString().EndsWith(" "); | ||
413 | |||
414 | // Allow ? through while typing a URI | ||
415 | // | ||
416 | if (words.Length > 0 && words[words.Length-1].StartsWith("http") && !trailingSpace) | ||
417 | return false; | ||
418 | |||
419 | string[] opts = Commands.FindNextOption(words, trailingSpace); | ||
420 | |||
421 | if (opts[0].StartsWith("Command help:")) | ||
422 | Output(opts[0]); | ||
423 | else | ||
424 | Output(String.Format("Options: {0}", String.Join(" ", opts))); | ||
425 | |||
426 | return true; | ||
427 | } | ||
428 | |||
429 | public override string ReadLine(string p, bool isCommand, bool e) | ||
430 | { | ||
431 | m_cursorXPosition = 0; | ||
432 | prompt = p; | ||
433 | m_echo = e; | ||
434 | int historyLine = m_history.Count; | ||
435 | |||
436 | SetCursorLeft(0); // Needed for mono | ||
437 | System.Console.Write(" "); // Needed for mono | ||
438 | |||
439 | lock (m_commandLine) | ||
440 | { | ||
441 | m_cursorYPosition = System.Console.CursorTop; | ||
442 | m_commandLine.Remove(0, m_commandLine.Length); | ||
443 | } | ||
444 | |||
445 | while (true) | ||
446 | { | ||
447 | Show(); | ||
448 | |||
449 | ConsoleKeyInfo key = System.Console.ReadKey(true); | ||
450 | char enteredChar = key.KeyChar; | ||
451 | |||
452 | if (!Char.IsControl(enteredChar)) | ||
453 | { | ||
454 | if (m_cursorXPosition >= 318) | ||
455 | continue; | ||
456 | |||
457 | if (enteredChar == '?' && isCommand) | ||
458 | { | ||
459 | if (ContextHelp()) | ||
460 | continue; | ||
461 | } | ||
462 | |||
463 | m_commandLine.Insert(m_cursorXPosition, enteredChar); | ||
464 | m_cursorXPosition++; | ||
465 | } | ||
466 | else | ||
467 | { | ||
468 | switch (key.Key) | ||
469 | { | ||
470 | case ConsoleKey.Backspace: | ||
471 | if (m_cursorXPosition == 0) | ||
472 | break; | ||
473 | m_commandLine.Remove(m_cursorXPosition-1, 1); | ||
474 | m_cursorXPosition--; | ||
475 | |||
476 | SetCursorLeft(0); | ||
477 | m_cursorYPosition = SetCursorTop(m_cursorYPosition); | ||
478 | |||
479 | if (m_echo) | ||
480 | System.Console.Write("{0}{1} ", prompt, m_commandLine); | ||
481 | else | ||
482 | System.Console.Write("{0}", prompt); | ||
483 | |||
484 | break; | ||
485 | case ConsoleKey.Delete: | ||
486 | if (m_cursorXPosition == m_commandLine.Length) | ||
487 | break; | ||
488 | |||
489 | m_commandLine.Remove(m_cursorXPosition, 1); | ||
490 | |||
491 | SetCursorLeft(0); | ||
492 | m_cursorYPosition = SetCursorTop(m_cursorYPosition); | ||
493 | |||
494 | if (m_echo) | ||
495 | System.Console.Write("{0}{1} ", prompt, m_commandLine); | ||
496 | else | ||
497 | System.Console.Write("{0}", prompt); | ||
498 | |||
499 | break; | ||
500 | case ConsoleKey.End: | ||
501 | m_cursorXPosition = m_commandLine.Length; | ||
502 | break; | ||
503 | case ConsoleKey.Home: | ||
504 | m_cursorXPosition = 0; | ||
505 | break; | ||
506 | case ConsoleKey.UpArrow: | ||
507 | if (historyLine < 1) | ||
508 | break; | ||
509 | historyLine--; | ||
510 | LockOutput(); | ||
511 | m_commandLine.Remove(0, m_commandLine.Length); | ||
512 | m_commandLine.Append(m_history[historyLine]); | ||
513 | m_cursorXPosition = m_commandLine.Length; | ||
514 | UnlockOutput(); | ||
515 | break; | ||
516 | case ConsoleKey.DownArrow: | ||
517 | if (historyLine >= m_history.Count) | ||
518 | break; | ||
519 | historyLine++; | ||
520 | LockOutput(); | ||
521 | if (historyLine == m_history.Count) | ||
522 | { | ||
523 | m_commandLine.Remove(0, m_commandLine.Length); | ||
524 | } | ||
525 | else | ||
526 | { | ||
527 | m_commandLine.Remove(0, m_commandLine.Length); | ||
528 | m_commandLine.Append(m_history[historyLine]); | ||
529 | } | ||
530 | m_cursorXPosition = m_commandLine.Length; | ||
531 | UnlockOutput(); | ||
532 | break; | ||
533 | case ConsoleKey.LeftArrow: | ||
534 | if (m_cursorXPosition > 0) | ||
535 | m_cursorXPosition--; | ||
536 | break; | ||
537 | case ConsoleKey.RightArrow: | ||
538 | if (m_cursorXPosition < m_commandLine.Length) | ||
539 | m_cursorXPosition++; | ||
540 | break; | ||
541 | case ConsoleKey.Enter: | ||
542 | SetCursorLeft(0); | ||
543 | m_cursorYPosition = SetCursorTop(m_cursorYPosition); | ||
544 | |||
545 | System.Console.WriteLine(); | ||
546 | //Show(); | ||
547 | |||
548 | lock (m_commandLine) | ||
549 | { | ||
550 | m_cursorYPosition = -1; | ||
551 | } | ||
552 | |||
553 | string commandLine = m_commandLine.ToString(); | ||
554 | |||
555 | if (isCommand) | ||
556 | { | ||
557 | string[] cmd = Commands.Resolve(Parser.Parse(commandLine)); | ||
558 | |||
559 | if (cmd.Length != 0) | ||
560 | { | ||
561 | int index; | ||
562 | |||
563 | for (index=0 ; index < cmd.Length ; index++) | ||
564 | { | ||
565 | if (cmd[index].Contains(" ")) | ||
566 | cmd[index] = "\"" + cmd[index] + "\""; | ||
567 | } | ||
568 | AddToHistory(String.Join(" ", cmd)); | ||
569 | return String.Empty; | ||
570 | } | ||
571 | } | ||
572 | |||
573 | // If we're not echoing to screen (e.g. a password) then we probably don't want it in history | ||
574 | if (m_echo && commandLine != "") | ||
575 | AddToHistory(commandLine); | ||
576 | |||
577 | return commandLine; | ||
578 | default: | ||
579 | break; | ||
580 | } | ||
581 | } | ||
582 | } | ||
583 | } | ||
584 | } | ||
585 | } | ||
diff --git a/OpenSim/Framework/Console/MockConsole.cs b/OpenSim/Framework/Console/MockConsole.cs new file mode 100644 index 0000000..1a142df --- /dev/null +++ b/OpenSim/Framework/Console/MockConsole.cs | |||
@@ -0,0 +1,89 @@ | |||
1 | /* | ||
2 | * Copyright (c) Contributors, http://opensimulator.org/ | ||
3 | * See CONTRIBUTORS.TXT for a full list of copyright holders. | ||
4 | * | ||
5 | * Redistribution and use in source and binary forms, with or without | ||
6 | * modification, are permitted provided that the following conditions are met: | ||
7 | * * Redistributions of source code must retain the above copyright | ||
8 | * notice, this list of conditions and the following disclaimer. | ||
9 | * * Redistributions in binary form must reproduce the above copyright | ||
10 | * notice, this list of conditions and the following disclaimer in the | ||
11 | * documentation and/or other materials provided with the distribution. | ||
12 | * * Neither the name of the OpenSimulator Project nor the | ||
13 | * names of its contributors may be used to endorse or promote products | ||
14 | * derived from this software without specific prior written permission. | ||
15 | * | ||
16 | * THIS SOFTWARE IS PROVIDED BY THE DEVELOPERS ``AS IS'' AND ANY | ||
17 | * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED | ||
18 | * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE | ||
19 | * DISCLAIMED. IN NO EVENT SHALL THE CONTRIBUTORS BE LIABLE FOR ANY | ||
20 | * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES | ||
21 | * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; | ||
22 | * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND | ||
23 | * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT | ||
24 | * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS | ||
25 | * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. | ||
26 | */ | ||
27 | |||
28 | using System; | ||
29 | using System.Threading; | ||
30 | using System.Collections.Generic; | ||
31 | using System.Text; | ||
32 | using System.Xml; | ||
33 | |||
34 | namespace OpenSim.Framework.Console | ||
35 | { | ||
36 | /// <summary> | ||
37 | /// This is a Fake console that's used when setting up the Scene in Unit Tests | ||
38 | /// Don't use this except for Unit Testing or you're in for a world of hurt when the | ||
39 | /// sim gets to ReadLine | ||
40 | /// </summary> | ||
41 | public class MockConsole : ICommandConsole | ||
42 | { | ||
43 | #pragma warning disable 0067 | ||
44 | public event OnOutputDelegate OnOutput; | ||
45 | #pragma warning restore 0067 | ||
46 | |||
47 | private MockCommands m_commands = new MockCommands(); | ||
48 | |||
49 | public ICommands Commands { get { return m_commands; } } | ||
50 | |||
51 | public string DefaultPrompt { get; set; } | ||
52 | |||
53 | public void Prompt() {} | ||
54 | |||
55 | public void RunCommand(string cmd) {} | ||
56 | |||
57 | public string ReadLine(string p, bool isCommand, bool e) { return ""; } | ||
58 | |||
59 | public object ConsoleScene { | ||
60 | get { return null; } | ||
61 | set {} | ||
62 | } | ||
63 | |||
64 | public void Output(string text, string level) {} | ||
65 | public void Output(string text) {} | ||
66 | public void OutputFormat(string format, params object[] components) {} | ||
67 | |||
68 | public string CmdPrompt(string p) { return ""; } | ||
69 | public string CmdPrompt(string p, string def) { return ""; } | ||
70 | public string CmdPrompt(string p, List<char> excludedCharacters) { return ""; } | ||
71 | public string CmdPrompt(string p, string def, List<char> excludedCharacters) { return ""; } | ||
72 | |||
73 | public string CmdPrompt(string prompt, string defaultresponse, List<string> options) { return ""; } | ||
74 | |||
75 | public string PasswdPrompt(string p) { return ""; } | ||
76 | } | ||
77 | |||
78 | public class MockCommands : ICommands | ||
79 | { | ||
80 | public void FromXml(XmlElement root, CommandDelegate fn) {} | ||
81 | public List<string> GetHelp(string[] cmd) { return null; } | ||
82 | public void AddCommand(string module, bool shared, string command, string help, string longhelp, CommandDelegate fn) {} | ||
83 | public void AddCommand(string module, bool shared, string command, string help, string longhelp, string descriptivehelp, CommandDelegate fn) {} | ||
84 | public string[] FindNextOption(string[] cmd, bool term) { return null; } | ||
85 | public bool HasCommand(string cmd) { return false; } | ||
86 | public string[] Resolve(string[] cmd) { return null; } | ||
87 | public XmlElement GetXml(XmlDocument doc) { return null; } | ||
88 | } | ||
89 | } | ||
diff --git a/OpenSim/Framework/Console/OpenSimAppender.cs b/OpenSim/Framework/Console/OpenSimAppender.cs new file mode 100644 index 0000000..72a251e --- /dev/null +++ b/OpenSim/Framework/Console/OpenSimAppender.cs | |||
@@ -0,0 +1,86 @@ | |||
1 | /* | ||
2 | * Copyright (c) Contributors, http://opensimulator.org/ | ||
3 | * See CONTRIBUTORS.TXT for a full list of copyright holders. | ||
4 | * | ||
5 | * Redistribution and use in source and binary forms, with or without | ||
6 | * modification, are permitted provided that the following conditions are met: | ||
7 | * * Redistributions of source code must retain the above copyright | ||
8 | * notice, this list of conditions and the following disclaimer. | ||
9 | * * Redistributions in binary form must reproduce the above copyright | ||
10 | * notice, this list of conditions and the following disclaimer in the | ||
11 | * documentation and/or other materials provided with the distribution. | ||
12 | * * Neither the name of the OpenSimulator Project nor the | ||
13 | * names of its contributors may be used to endorse or promote products | ||
14 | * derived from this software without specific prior written permission. | ||
15 | * | ||
16 | * THIS SOFTWARE IS PROVIDED BY THE DEVELOPERS ``AS IS'' AND ANY | ||
17 | * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED | ||
18 | * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE | ||
19 | * DISCLAIMED. IN NO EVENT SHALL THE CONTRIBUTORS BE LIABLE FOR ANY | ||
20 | * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES | ||
21 | * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; | ||
22 | * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND | ||
23 | * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT | ||
24 | * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS | ||
25 | * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. | ||
26 | */ | ||
27 | |||
28 | using System; | ||
29 | using log4net.Appender; | ||
30 | using log4net.Core; | ||
31 | |||
32 | namespace OpenSim.Framework.Console | ||
33 | { | ||
34 | /// <summary> | ||
35 | /// Writes log information out onto the console | ||
36 | /// </summary> | ||
37 | public class OpenSimAppender : AnsiColorTerminalAppender | ||
38 | { | ||
39 | private ConsoleBase m_console = null; | ||
40 | |||
41 | public ConsoleBase Console | ||
42 | { | ||
43 | get { return m_console; } | ||
44 | set { m_console = value; } | ||
45 | } | ||
46 | |||
47 | override protected void Append(LoggingEvent le) | ||
48 | { | ||
49 | if (m_console != null) | ||
50 | m_console.LockOutput(); | ||
51 | |||
52 | string loggingMessage = RenderLoggingEvent(le); | ||
53 | |||
54 | try | ||
55 | { | ||
56 | if (m_console != null) | ||
57 | { | ||
58 | string level = "normal"; | ||
59 | |||
60 | if (le.Level == Level.Error) | ||
61 | level = "error"; | ||
62 | else if (le.Level == Level.Warn) | ||
63 | level = "warn"; | ||
64 | |||
65 | m_console.Output(loggingMessage, level); | ||
66 | } | ||
67 | else | ||
68 | { | ||
69 | if (!loggingMessage.EndsWith("\n")) | ||
70 | System.Console.WriteLine(loggingMessage); | ||
71 | else | ||
72 | System.Console.Write(loggingMessage); | ||
73 | } | ||
74 | } | ||
75 | catch (Exception e) | ||
76 | { | ||
77 | System.Console.WriteLine("Couldn't write out log message: {0}", e.ToString()); | ||
78 | } | ||
79 | finally | ||
80 | { | ||
81 | if (m_console != null) | ||
82 | m_console.UnlockOutput(); | ||
83 | } | ||
84 | } | ||
85 | } | ||
86 | } | ||
diff --git a/OpenSim/Framework/Console/RemoteConsole.cs b/OpenSim/Framework/Console/RemoteConsole.cs new file mode 100644 index 0000000..8ad7b0d --- /dev/null +++ b/OpenSim/Framework/Console/RemoteConsole.cs | |||
@@ -0,0 +1,513 @@ | |||
1 | /* | ||
2 | * Copyright (c) Contributors, http://opensimulator.org/ | ||
3 | * See CONTRIBUTORS.TXT for a full list of copyright holders. | ||
4 | * | ||
5 | * Redistribution and use in source and binary forms, with or without | ||
6 | * modification, are permitted provided that the following conditions are met: | ||
7 | * * Redistributions of source code must retain the above copyright | ||
8 | * notice, this list of conditions and the following disclaimer. | ||
9 | * * Redistributions in binary form must reproduce the above copyright | ||
10 | * notice, this list of conditions and the following disclaimer in the | ||
11 | * documentation and/or other materials provided with the distribution. | ||
12 | * * Neither the name of the OpenSimulator Project nor the | ||
13 | * names of its contributors may be used to endorse or promote products | ||
14 | * derived from this software without specific prior written permission. | ||
15 | * | ||
16 | * THIS SOFTWARE IS PROVIDED BY THE DEVELOPERS ``AS IS'' AND ANY | ||
17 | * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED | ||
18 | * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE | ||
19 | * DISCLAIMED. IN NO EVENT SHALL THE CONTRIBUTORS BE LIABLE FOR ANY | ||
20 | * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES | ||
21 | * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; | ||
22 | * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND | ||
23 | * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT | ||
24 | * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS | ||
25 | * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. | ||
26 | */ | ||
27 | |||
28 | using System; | ||
29 | using System.Xml; | ||
30 | using System.Collections; | ||
31 | using System.Collections.Generic; | ||
32 | using System.Diagnostics; | ||
33 | using System.Reflection; | ||
34 | using System.Text; | ||
35 | using System.Text.RegularExpressions; | ||
36 | using System.Threading; | ||
37 | using OpenMetaverse; | ||
38 | using Nini.Config; | ||
39 | using OpenSim.Framework.Servers.HttpServer; | ||
40 | using log4net; | ||
41 | |||
42 | namespace OpenSim.Framework.Console | ||
43 | { | ||
44 | public class ConsoleConnection | ||
45 | { | ||
46 | public int last; | ||
47 | public long lastLineSeen; | ||
48 | public bool newConnection = true; | ||
49 | } | ||
50 | |||
51 | // A console that uses REST interfaces | ||
52 | // | ||
53 | public class RemoteConsole : CommandConsole | ||
54 | { | ||
55 | private IHttpServer m_Server = null; | ||
56 | private IConfigSource m_Config = null; | ||
57 | |||
58 | private List<string> m_Scrollback = new List<string>(); | ||
59 | private ManualResetEvent m_DataEvent = new ManualResetEvent(false); | ||
60 | private List<string> m_InputData = new List<string>(); | ||
61 | private long m_LineNumber = 0; | ||
62 | private Dictionary<UUID, ConsoleConnection> m_Connections = | ||
63 | new Dictionary<UUID, ConsoleConnection>(); | ||
64 | private string m_UserName = String.Empty; | ||
65 | private string m_Password = String.Empty; | ||
66 | private string m_AllowedOrigin = String.Empty; | ||
67 | |||
68 | public RemoteConsole(string defaultPrompt) : base(defaultPrompt) | ||
69 | { | ||
70 | } | ||
71 | |||
72 | public void ReadConfig(IConfigSource config) | ||
73 | { | ||
74 | m_Config = config; | ||
75 | |||
76 | IConfig netConfig = m_Config.Configs["Network"]; | ||
77 | if (netConfig == null) | ||
78 | return; | ||
79 | |||
80 | m_UserName = netConfig.GetString("ConsoleUser", String.Empty); | ||
81 | m_Password = netConfig.GetString("ConsolePass", String.Empty); | ||
82 | m_AllowedOrigin = netConfig.GetString("ConsoleAllowedOrigin", String.Empty); | ||
83 | } | ||
84 | |||
85 | public void SetServer(IHttpServer server) | ||
86 | { | ||
87 | m_Server = server; | ||
88 | |||
89 | m_Server.AddHTTPHandler("/StartSession/", HandleHttpStartSession); | ||
90 | m_Server.AddHTTPHandler("/CloseSession/", HandleHttpCloseSession); | ||
91 | m_Server.AddHTTPHandler("/SessionCommand/", HandleHttpSessionCommand); | ||
92 | } | ||
93 | |||
94 | public override void Output(string text, string level) | ||
95 | { | ||
96 | lock (m_Scrollback) | ||
97 | { | ||
98 | while (m_Scrollback.Count >= 1000) | ||
99 | m_Scrollback.RemoveAt(0); | ||
100 | m_LineNumber++; | ||
101 | m_Scrollback.Add(String.Format("{0}", m_LineNumber)+":"+level+":"+text); | ||
102 | } | ||
103 | FireOnOutput(text.Trim()); | ||
104 | System.Console.WriteLine(text.Trim()); | ||
105 | } | ||
106 | |||
107 | public override void Output(string text) | ||
108 | { | ||
109 | Output(text, "normal"); | ||
110 | } | ||
111 | |||
112 | public override string ReadLine(string p, bool isCommand, bool e) | ||
113 | { | ||
114 | if (isCommand) | ||
115 | Output("+++"+p); | ||
116 | else | ||
117 | Output("-++"+p); | ||
118 | |||
119 | m_DataEvent.WaitOne(); | ||
120 | |||
121 | string cmdinput; | ||
122 | |||
123 | lock (m_InputData) | ||
124 | { | ||
125 | if (m_InputData.Count == 0) | ||
126 | { | ||
127 | m_DataEvent.Reset(); | ||
128 | return ""; | ||
129 | } | ||
130 | |||
131 | cmdinput = m_InputData[0]; | ||
132 | m_InputData.RemoveAt(0); | ||
133 | if (m_InputData.Count == 0) | ||
134 | m_DataEvent.Reset(); | ||
135 | |||
136 | } | ||
137 | |||
138 | if (isCommand) | ||
139 | { | ||
140 | string[] cmd = Commands.Resolve(Parser.Parse(cmdinput)); | ||
141 | |||
142 | if (cmd.Length != 0) | ||
143 | { | ||
144 | int i; | ||
145 | |||
146 | for (i=0 ; i < cmd.Length ; i++) | ||
147 | { | ||
148 | if (cmd[i].Contains(" ")) | ||
149 | cmd[i] = "\"" + cmd[i] + "\""; | ||
150 | } | ||
151 | return String.Empty; | ||
152 | } | ||
153 | } | ||
154 | return cmdinput; | ||
155 | } | ||
156 | |||
157 | private Hashtable CheckOrigin(Hashtable result) | ||
158 | { | ||
159 | if (!string.IsNullOrEmpty(m_AllowedOrigin)) | ||
160 | result["access_control_allow_origin"] = m_AllowedOrigin; | ||
161 | return result; | ||
162 | } | ||
163 | /* TODO: Figure out how PollServiceHTTPHandler can access the request headers | ||
164 | * in order to use m_AllowedOrigin as a regular expression | ||
165 | private Hashtable CheckOrigin(Hashtable headers, Hashtable result) | ||
166 | { | ||
167 | if (!string.IsNullOrEmpty(m_AllowedOrigin)) | ||
168 | { | ||
169 | if (headers.ContainsKey("origin")) | ||
170 | { | ||
171 | string origin = headers["origin"].ToString(); | ||
172 | if (Regex.IsMatch(origin, m_AllowedOrigin)) | ||
173 | result["access_control_allow_origin"] = origin; | ||
174 | } | ||
175 | } | ||
176 | return result; | ||
177 | } | ||
178 | */ | ||
179 | |||
180 | private void DoExpire() | ||
181 | { | ||
182 | List<UUID> expired = new List<UUID>(); | ||
183 | |||
184 | lock (m_Connections) | ||
185 | { | ||
186 | foreach (KeyValuePair<UUID, ConsoleConnection> kvp in m_Connections) | ||
187 | { | ||
188 | if (System.Environment.TickCount - kvp.Value.last > 500000) | ||
189 | expired.Add(kvp.Key); | ||
190 | } | ||
191 | |||
192 | foreach (UUID id in expired) | ||
193 | { | ||
194 | m_Connections.Remove(id); | ||
195 | CloseConnection(id); | ||
196 | } | ||
197 | } | ||
198 | } | ||
199 | |||
200 | private Hashtable HandleHttpStartSession(Hashtable request) | ||
201 | { | ||
202 | DoExpire(); | ||
203 | |||
204 | Hashtable post = DecodePostString(request["body"].ToString()); | ||
205 | Hashtable reply = new Hashtable(); | ||
206 | |||
207 | reply["str_response_string"] = ""; | ||
208 | reply["int_response_code"] = 401; | ||
209 | reply["content_type"] = "text/plain"; | ||
210 | |||
211 | if (m_UserName == String.Empty) | ||
212 | return reply; | ||
213 | |||
214 | if (post["USER"] == null || post["PASS"] == null) | ||
215 | return reply; | ||
216 | |||
217 | if (m_UserName != post["USER"].ToString() || | ||
218 | m_Password != post["PASS"].ToString()) | ||
219 | { | ||
220 | return reply; | ||
221 | } | ||
222 | |||
223 | ConsoleConnection c = new ConsoleConnection(); | ||
224 | c.last = System.Environment.TickCount; | ||
225 | c.lastLineSeen = 0; | ||
226 | |||
227 | UUID sessionID = UUID.Random(); | ||
228 | |||
229 | lock (m_Connections) | ||
230 | { | ||
231 | m_Connections[sessionID] = c; | ||
232 | } | ||
233 | |||
234 | string uri = "/ReadResponses/" + sessionID.ToString() + "/"; | ||
235 | |||
236 | m_Server.AddPollServiceHTTPHandler( | ||
237 | uri, new PollServiceEventArgs(null, uri, HasEvents, GetEvents, NoEvents, sessionID,25000)); // 25 secs timeout | ||
238 | |||
239 | XmlDocument xmldoc = new XmlDocument(); | ||
240 | XmlNode xmlnode = xmldoc.CreateNode(XmlNodeType.XmlDeclaration, | ||
241 | "", ""); | ||
242 | |||
243 | xmldoc.AppendChild(xmlnode); | ||
244 | XmlElement rootElement = xmldoc.CreateElement("", "ConsoleSession", | ||
245 | ""); | ||
246 | |||
247 | xmldoc.AppendChild(rootElement); | ||
248 | |||
249 | XmlElement id = xmldoc.CreateElement("", "SessionID", ""); | ||
250 | id.AppendChild(xmldoc.CreateTextNode(sessionID.ToString())); | ||
251 | |||
252 | rootElement.AppendChild(id); | ||
253 | |||
254 | XmlElement prompt = xmldoc.CreateElement("", "Prompt", ""); | ||
255 | prompt.AppendChild(xmldoc.CreateTextNode(DefaultPrompt)); | ||
256 | |||
257 | rootElement.AppendChild(prompt); | ||
258 | |||
259 | rootElement.AppendChild(MainConsole.Instance.Commands.GetXml(xmldoc)); | ||
260 | |||
261 | reply["str_response_string"] = xmldoc.InnerXml; | ||
262 | reply["int_response_code"] = 200; | ||
263 | reply["content_type"] = "text/xml"; | ||
264 | reply = CheckOrigin(reply); | ||
265 | |||
266 | return reply; | ||
267 | } | ||
268 | |||
269 | private Hashtable HandleHttpCloseSession(Hashtable request) | ||
270 | { | ||
271 | DoExpire(); | ||
272 | |||
273 | Hashtable post = DecodePostString(request["body"].ToString()); | ||
274 | Hashtable reply = new Hashtable(); | ||
275 | |||
276 | reply["str_response_string"] = ""; | ||
277 | reply["int_response_code"] = 404; | ||
278 | reply["content_type"] = "text/plain"; | ||
279 | |||
280 | if (post["ID"] == null) | ||
281 | return reply; | ||
282 | |||
283 | UUID id; | ||
284 | if (!UUID.TryParse(post["ID"].ToString(), out id)) | ||
285 | return reply; | ||
286 | |||
287 | lock (m_Connections) | ||
288 | { | ||
289 | if (m_Connections.ContainsKey(id)) | ||
290 | { | ||
291 | m_Connections.Remove(id); | ||
292 | CloseConnection(id); | ||
293 | } | ||
294 | } | ||
295 | |||
296 | XmlDocument xmldoc = new XmlDocument(); | ||
297 | XmlNode xmlnode = xmldoc.CreateNode(XmlNodeType.XmlDeclaration, | ||
298 | "", ""); | ||
299 | |||
300 | xmldoc.AppendChild(xmlnode); | ||
301 | XmlElement rootElement = xmldoc.CreateElement("", "ConsoleSession", | ||
302 | ""); | ||
303 | |||
304 | xmldoc.AppendChild(rootElement); | ||
305 | |||
306 | XmlElement res = xmldoc.CreateElement("", "Result", ""); | ||
307 | res.AppendChild(xmldoc.CreateTextNode("OK")); | ||
308 | |||
309 | rootElement.AppendChild(res); | ||
310 | |||
311 | reply["str_response_string"] = xmldoc.InnerXml; | ||
312 | reply["int_response_code"] = 200; | ||
313 | reply["content_type"] = "text/xml"; | ||
314 | reply = CheckOrigin(reply); | ||
315 | |||
316 | return reply; | ||
317 | } | ||
318 | |||
319 | private Hashtable HandleHttpSessionCommand(Hashtable request) | ||
320 | { | ||
321 | DoExpire(); | ||
322 | |||
323 | Hashtable post = DecodePostString(request["body"].ToString()); | ||
324 | Hashtable reply = new Hashtable(); | ||
325 | |||
326 | reply["str_response_string"] = ""; | ||
327 | reply["int_response_code"] = 404; | ||
328 | reply["content_type"] = "text/plain"; | ||
329 | |||
330 | if (post["ID"] == null) | ||
331 | return reply; | ||
332 | |||
333 | UUID id; | ||
334 | if (!UUID.TryParse(post["ID"].ToString(), out id)) | ||
335 | return reply; | ||
336 | |||
337 | lock (m_Connections) | ||
338 | { | ||
339 | if (!m_Connections.ContainsKey(id)) | ||
340 | return reply; | ||
341 | } | ||
342 | |||
343 | if (post["COMMAND"] == null) | ||
344 | return reply; | ||
345 | |||
346 | lock (m_InputData) | ||
347 | { | ||
348 | m_DataEvent.Set(); | ||
349 | m_InputData.Add(post["COMMAND"].ToString()); | ||
350 | } | ||
351 | |||
352 | XmlDocument xmldoc = new XmlDocument(); | ||
353 | XmlNode xmlnode = xmldoc.CreateNode(XmlNodeType.XmlDeclaration, | ||
354 | "", ""); | ||
355 | |||
356 | xmldoc.AppendChild(xmlnode); | ||
357 | XmlElement rootElement = xmldoc.CreateElement("", "ConsoleSession", | ||
358 | ""); | ||
359 | |||
360 | xmldoc.AppendChild(rootElement); | ||
361 | |||
362 | XmlElement res = xmldoc.CreateElement("", "Result", ""); | ||
363 | res.AppendChild(xmldoc.CreateTextNode("OK")); | ||
364 | |||
365 | rootElement.AppendChild(res); | ||
366 | |||
367 | reply["str_response_string"] = xmldoc.InnerXml; | ||
368 | reply["int_response_code"] = 200; | ||
369 | reply["content_type"] = "text/xml"; | ||
370 | reply = CheckOrigin(reply); | ||
371 | |||
372 | return reply; | ||
373 | } | ||
374 | |||
375 | private Hashtable DecodePostString(string data) | ||
376 | { | ||
377 | Hashtable result = new Hashtable(); | ||
378 | |||
379 | string[] terms = data.Split(new char[] {'&'}); | ||
380 | |||
381 | foreach (string term in terms) | ||
382 | { | ||
383 | string[] elems = term.Split(new char[] {'='}); | ||
384 | if (elems.Length == 0) | ||
385 | continue; | ||
386 | |||
387 | string name = System.Web.HttpUtility.UrlDecode(elems[0]); | ||
388 | string value = String.Empty; | ||
389 | |||
390 | if (elems.Length > 1) | ||
391 | value = System.Web.HttpUtility.UrlDecode(elems[1]); | ||
392 | |||
393 | result[name] = value; | ||
394 | } | ||
395 | |||
396 | return result; | ||
397 | } | ||
398 | |||
399 | public void CloseConnection(UUID id) | ||
400 | { | ||
401 | try | ||
402 | { | ||
403 | string uri = "/ReadResponses/" + id.ToString() + "/"; | ||
404 | |||
405 | m_Server.RemovePollServiceHTTPHandler("", uri); | ||
406 | } | ||
407 | catch (Exception) | ||
408 | { | ||
409 | } | ||
410 | } | ||
411 | |||
412 | private bool HasEvents(UUID RequestID, UUID sessionID) | ||
413 | { | ||
414 | ConsoleConnection c = null; | ||
415 | |||
416 | lock (m_Connections) | ||
417 | { | ||
418 | if (!m_Connections.ContainsKey(sessionID)) | ||
419 | return false; | ||
420 | c = m_Connections[sessionID]; | ||
421 | } | ||
422 | c.last = System.Environment.TickCount; | ||
423 | if (c.lastLineSeen < m_LineNumber) | ||
424 | return true; | ||
425 | return false; | ||
426 | } | ||
427 | |||
428 | private Hashtable GetEvents(UUID RequestID, UUID sessionID) | ||
429 | { | ||
430 | ConsoleConnection c = null; | ||
431 | |||
432 | lock (m_Connections) | ||
433 | { | ||
434 | if (!m_Connections.ContainsKey(sessionID)) | ||
435 | return NoEvents(RequestID, UUID.Zero); | ||
436 | c = m_Connections[sessionID]; | ||
437 | } | ||
438 | c.last = System.Environment.TickCount; | ||
439 | if (c.lastLineSeen >= m_LineNumber) | ||
440 | return NoEvents(RequestID, UUID.Zero); | ||
441 | |||
442 | Hashtable result = new Hashtable(); | ||
443 | |||
444 | XmlDocument xmldoc = new XmlDocument(); | ||
445 | XmlNode xmlnode = xmldoc.CreateNode(XmlNodeType.XmlDeclaration, | ||
446 | "", ""); | ||
447 | |||
448 | xmldoc.AppendChild(xmlnode); | ||
449 | XmlElement rootElement = xmldoc.CreateElement("", "ConsoleSession", | ||
450 | ""); | ||
451 | |||
452 | if (c.newConnection) | ||
453 | { | ||
454 | c.newConnection = false; | ||
455 | Output("+++" + DefaultPrompt); | ||
456 | } | ||
457 | |||
458 | lock (m_Scrollback) | ||
459 | { | ||
460 | long startLine = m_LineNumber - m_Scrollback.Count; | ||
461 | long sendStart = startLine; | ||
462 | if (sendStart < c.lastLineSeen) | ||
463 | sendStart = c.lastLineSeen; | ||
464 | |||
465 | for (long i = sendStart ; i < m_LineNumber ; i++) | ||
466 | { | ||
467 | XmlElement res = xmldoc.CreateElement("", "Line", ""); | ||
468 | long line = i + 1; | ||
469 | res.SetAttribute("Number", line.ToString()); | ||
470 | res.AppendChild(xmldoc.CreateTextNode(m_Scrollback[(int)(i - startLine)])); | ||
471 | |||
472 | rootElement.AppendChild(res); | ||
473 | } | ||
474 | } | ||
475 | c.lastLineSeen = m_LineNumber; | ||
476 | |||
477 | xmldoc.AppendChild(rootElement); | ||
478 | |||
479 | result["str_response_string"] = xmldoc.InnerXml; | ||
480 | result["int_response_code"] = 200; | ||
481 | result["content_type"] = "application/xml"; | ||
482 | result["keepalive"] = false; | ||
483 | result["reusecontext"] = false; | ||
484 | result = CheckOrigin(result); | ||
485 | |||
486 | return result; | ||
487 | } | ||
488 | |||
489 | private Hashtable NoEvents(UUID RequestID, UUID id) | ||
490 | { | ||
491 | Hashtable result = new Hashtable(); | ||
492 | |||
493 | XmlDocument xmldoc = new XmlDocument(); | ||
494 | XmlNode xmlnode = xmldoc.CreateNode(XmlNodeType.XmlDeclaration, | ||
495 | "", ""); | ||
496 | |||
497 | xmldoc.AppendChild(xmlnode); | ||
498 | XmlElement rootElement = xmldoc.CreateElement("", "ConsoleSession", | ||
499 | ""); | ||
500 | |||
501 | xmldoc.AppendChild(rootElement); | ||
502 | |||
503 | result["str_response_string"] = xmldoc.InnerXml; | ||
504 | result["int_response_code"] = 200; | ||
505 | result["content_type"] = "text/xml"; | ||
506 | result["keepalive"] = false; | ||
507 | result["reusecontext"] = false; | ||
508 | result = CheckOrigin(result); | ||
509 | |||
510 | return result; | ||
511 | } | ||
512 | } | ||
513 | } | ||