clockwerk-opensim – Blame information for rev 1

Subversion Repositories:
Rev:
Rev Author Line No. Line
1 vero 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 }