Skip to content

HTTPS clone URL

Subversion checkout URL

You can clone with
or
.
Download ZIP
Browse files

Importing tsunami to svn

svn path=/trunk/tsunami/; revision=114775
  • Loading branch information...
commit 014c42c446c133ae303aaf946d0099f91a8b6e42 0 parents
@alanmcgovern alanmcgovern authored
20 Tsunami.sln
@@ -0,0 +1,20 @@
+
+Microsoft Visual Studio Solution File, Format Version 9.00
+# Visual Studio 2005
+Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Tsunami", "src/Tsunami.csproj", "{907A9A8F-B472-479F-8643-A1A82057CD32}"
+EndProject
+Global
+ GlobalSection(SolutionConfigurationPlatforms) = preSolution
+ Debug|Any CPU = Debug|Any CPU
+ Release|Any CPU = Release|Any CPU
+ EndGlobalSection
+ GlobalSection(ProjectConfigurationPlatforms) = postSolution
+ {907A9A8F-B472-479F-8643-A1A82057CD32}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
+ {907A9A8F-B472-479F-8643-A1A82057CD32}.Debug|Any CPU.Build.0 = Debug|Any CPU
+ {907A9A8F-B472-479F-8643-A1A82057CD32}.Release|Any CPU.ActiveCfg = Release|Any CPU
+ {907A9A8F-B472-479F-8643-A1A82057CD32}.Release|Any CPU.Build.0 = Release|Any CPU
+ EndGlobalSection
+ GlobalSection(SolutionProperties) = preSolution
+ HideSolutionNode = FALSE
+ EndGlobalSection
+EndGlobal
11 src/AssemblyInfo.cs
@@ -0,0 +1,11 @@
+using System.Reflection;
+using System.Runtime.CompilerServices;
+using System.Runtime.InteropServices;
+
+
+[assembly: AssemblyTitle("Tsunami")]
+[assembly: AssemblyDescription("A commandline based torrent tracker")]
+[assembly: AssemblyProduct("Tsunami")]
+[assembly: AssemblyCopyright("Copyright © Alan McGovern 2008")]
+[assembly: AssemblyVersion("0.1.0.0")]
+[assembly: AssemblyFileVersion("0.1.0.0")]
122 src/CommandContexts/Context.cs
@@ -0,0 +1,122 @@
+//
+// Context.cs
+//
+// Author:
+// Alan McGovern <alan.mcgovern@gmail.com>
+//
+// Copyright (C) 2008 Alan McGovern.
+//
+// Permission is hereby granted, free of charge, to any person obtaining
+// a copy of this software and associated documentation files (the
+// "Software"), to deal in the Software without restriction, including
+// without limitation the rights to use, copy, modify, merge, publish,
+// distribute, sublicense, and/or sell copies of the Software, and to
+// permit persons to whom the Software is furnished to do so, subject to
+// the following conditions:
+//
+// The above copyright notice and this permission notice shall be
+// included in all copies or substantial portions of the Software.
+//
+// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
+// EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
+// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
+// NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
+// LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
+// OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
+// WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
+//
+
+using System;
+using System.IO;
+
+namespace Tsunami
+{
+ public enum Result
+ {
+ Handled,
+ ShouldPop,
+ Unhandled,
+ }
+
+ public abstract class Context
+ {
+ private Context childContext;
+ private Options options = new Options();
+ private Context parentContext;
+
+ protected Context ActiveContext
+ {
+ get
+ {
+ Context c = this;
+ while (c.childContext != null)
+ c = c.childContext;
+ return c;
+ }
+ }
+
+ protected Context BaseContext
+ {
+ get
+ {
+ Context c = this;
+ while (c.Parent != null)
+ c = c.Parent;
+ return c;
+ }
+ }
+
+ protected Context Child
+ {
+ get { return childContext; }
+ set { childContext = value; }
+ }
+
+ protected Options Options
+ {
+ get { return options; }
+ }
+
+ protected Context Parent
+ {
+ get { return parentContext; }
+ set { parentContext = value; }
+ }
+
+ public Result Handle(string line)
+ {
+ Result result = ActiveContext.HandleImpl(line);
+
+ if (result == Result.ShouldPop && ActiveContext.Parent != null)
+ {
+ ActiveContext.Parent.Child = null;
+ return Result.Handled;
+ }
+ return result;
+ }
+
+ protected virtual Result HandleImpl(string line)
+ {
+ if (string.IsNullOrEmpty(line))
+ return Result.ShouldPop;
+
+ Option option = Options.GetSelected(line);
+ if (option == null || option.Context == null)
+ return Result.Unhandled;
+
+ Child = option.Context;
+ return Result.Handled;
+ }
+
+ public void Print(TextWriter writer)
+ {
+ ActiveContext.PrintImpl(writer);
+ }
+
+ protected virtual void PrintImpl(TextWriter writer)
+ {
+ foreach (Option o in options)
+ o.Write(writer);
+ }
+ }
+}
76 src/CommandContexts/DirectoryManagement/AddDirectoryContext.cs
@@ -0,0 +1,76 @@
+//
+// AddDirectoryContext.cs
+//
+// Author:
+// Alan McGovern <alan.mcgovern@gmail.com>
+//
+// Copyright (C) 2008 Alan McGovern.
+//
+// Permission is hereby granted, free of charge, to any person obtaining
+// a copy of this software and associated documentation files (the
+// "Software"), to deal in the Software without restriction, including
+// without limitation the rights to use, copy, modify, merge, publish,
+// distribute, sublicense, and/or sell copies of the Software, and to
+// permit persons to whom the Software is furnished to do so, subject to
+// the following conditions:
+//
+// The above copyright notice and this permission notice shall be
+// included in all copies or substantial portions of the Software.
+//
+// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
+// EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
+// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
+// NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
+// LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
+// OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
+// WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
+//
+
+using System;
+using System.IO;
+
+namespace Tsunami
+{
+ class AddDirectoryContext : Context
+ {
+ private bool valid = true;
+ private bool alreadyAdded = false;
+ public AddDirectoryContext(Context parent)
+ {
+ Parent = parent;
+ }
+ protected override Result HandleImpl(string line)
+ {
+ if (string.IsNullOrEmpty(line))
+ return Result.ShouldPop;
+
+ TrackerHost host = ((GeneralContext)BaseContext).Tracker;
+
+ valid = Directory.Exists(line);
+ if (!valid)
+ {
+ return Result.Handled;
+ }
+ else if (host.Watchers.ContainsKey(line))
+ {
+ alreadyAdded = true;
+ return Result.Handled;
+ }
+ else
+ {
+ host.AddWatcher(line);
+ return Result.ShouldPop;
+ }
+ }
+
+ protected override void PrintImpl(System.IO.TextWriter writer)
+ {
+ if (valid && !alreadyAdded)
+ writer.WriteLine("Enter the path to monitor for torrents");
+ else if(valid && alreadyAdded)
+ writer.WriteLine("This path is already being monitored, please enter another");
+ else
+ writer.WriteLine("The path you entered was invalid, please enter another");
+ }
+ }
+}
45 src/CommandContexts/DirectoryManagement/DirectoryContext.cs
@@ -0,0 +1,45 @@
+//
+// DirectoryContext.cs
+//
+// Author:
+// Alan McGovern <alan.mcgovern@gmail.com>
+//
+// Copyright (C) 2008 Alan McGovern.
+//
+// Permission is hereby granted, free of charge, to any person obtaining
+// a copy of this software and associated documentation files (the
+// "Software"), to deal in the Software without restriction, including
+// without limitation the rights to use, copy, modify, merge, publish,
+// distribute, sublicense, and/or sell copies of the Software, and to
+// permit persons to whom the Software is furnished to do so, subject to
+// the following conditions:
+//
+// The above copyright notice and this permission notice shall be
+// included in all copies or substantial portions of the Software.
+//
+// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
+// EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
+// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
+// NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
+// LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
+// OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
+// WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
+//
+
+using System;
+
+namespace Tsunami
+{
+ public class DirectoryContext : Context
+ {
+ public DirectoryContext(Context parent)
+ {
+ Parent = parent;
+ Options.AddRange(new Option[] {
+ new Option("Add Directory", new AddDirectoryContext(this)),
+ new Option("Remove Directory", new RemoveDirectoryContext(this)),
+ new Option("List Directories", new ListDirectoriesContext(this))
+ });
+ }
+ }
+}
43 src/CommandContexts/DirectoryManagement/ListDirectoriesContext.cs
@@ -0,0 +1,43 @@
+//
+// ListDirectoriesContext.cs
+//
+// Author:
+// Alan McGovern <alan.mcgovern@gmail.com>
+//
+// Copyright (C) 2008 Alan McGovern.
+//
+// Permission is hereby granted, free of charge, to any person obtaining
+// a copy of this software and associated documentation files (the
+// "Software"), to deal in the Software without restriction, including
+// without limitation the rights to use, copy, modify, merge, publish,
+// distribute, sublicense, and/or sell copies of the Software, and to
+// permit persons to whom the Software is furnished to do so, subject to
+// the following conditions:
+//
+// The above copyright notice and this permission notice shall be
+// included in all copies or substantial portions of the Software.
+//
+// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
+// EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
+// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
+// NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
+// LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
+// OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
+// WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
+//
+
+using System;
+
+namespace Tsunami
+{
+ class ListDirectoriesContext : Context
+ {
+ public ListDirectoriesContext(Context parent)
+ {
+ Parent = parent;
+
+ foreach (string s in ((GeneralContext)BaseContext).Tracker.Watchers.Keys)
+ Options.Add(new Option(s));
+ }
+ }
+}
61 src/CommandContexts/DirectoryManagement/RemoveDirectoryContext.cs
@@ -0,0 +1,61 @@
+//
+// RemoveDirectoryContext.cs
+//
+// Author:
+// Alan McGovern <alan.mcgovern@gmail.com>
+//
+// Copyright (C) 2008 Alan McGovern.
+//
+// Permission is hereby granted, free of charge, to any person obtaining
+// a copy of this software and associated documentation files (the
+// "Software"), to deal in the Software without restriction, including
+// without limitation the rights to use, copy, modify, merge, publish,
+// distribute, sublicense, and/or sell copies of the Software, and to
+// permit persons to whom the Software is furnished to do so, subject to
+// the following conditions:
+//
+// The above copyright notice and this permission notice shall be
+// included in all copies or substantial portions of the Software.
+//
+// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
+// EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
+// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
+// NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
+// LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
+// OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
+// WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
+//
+
+using System;
+
+namespace Tsunami
+{
+ class RemoveDirectoryContext : Context
+ {
+ public RemoveDirectoryContext(Context parent)
+ {
+ Parent = parent;
+ foreach (string s in ((GeneralContext)BaseContext).Tracker.Watchers.Keys)
+ Options.Add(new Option(s, (Options.Count + 1).ToString()));
+ }
+
+ protected override Result HandleImpl(string line)
+ {
+ if (string.IsNullOrEmpty(line))
+ return Result.ShouldPop;
+
+ Option option = Options.GetSelected(line);
+ if (option == null)
+ return Result.Handled;
+
+ ((GeneralContext)BaseContext).Tracker.RemoveWatcher(option.Description);
+ return Result.ShouldPop;
+ }
+
+ protected override void PrintImpl(System.IO.TextWriter writer)
+ {
+ writer.WriteLine("Choose the directory to stop monitoring");
+ base.PrintImpl(writer);
+ }
+ }
+}
69 src/CommandContexts/GeneralContext.cs
@@ -0,0 +1,69 @@
+//
+// GeneralContext.cs
+//
+// Author:
+// Alan McGovern <alan.mcgovern@gmail.com>
+//
+// Copyright (C) 2008 Alan McGovern.
+//
+// Permission is hereby granted, free of charge, to any person obtaining
+// a copy of this software and associated documentation files (the
+// "Software"), to deal in the Software without restriction, including
+// without limitation the rights to use, copy, modify, merge, publish,
+// distribute, sublicense, and/or sell copies of the Software, and to
+// permit persons to whom the Software is furnished to do so, subject to
+// the following conditions:
+//
+// The above copyright notice and this permission notice shall be
+// included in all copies or substantial portions of the Software.
+//
+// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
+// EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
+// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
+// NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
+// LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
+// OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
+// WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
+//
+
+using System;
+
+namespace Tsunami
+{
+ class GeneralContext : Context
+ {
+ private TrackerHost tracker;
+
+ public TrackerHost Tracker
+ {
+ get { return tracker; }
+ }
+
+ public GeneralContext(TrackerHost tracker)
+ {
+ this.tracker = tracker;
+ Options.AddRange(new Option[] {
+ new Option("Manage HTTP endpoints", new ListenerContext(this)),
+ new Option("Manage watched directories", new DirectoryContext(this)),
+ new Option("Statistics", new StatisticsContext(this)),
+ new Option("Quit", "q", "quit")
+ });
+ }
+
+ protected override Result HandleImpl(string line)
+ {
+ base.HandleImpl(line);
+ Option o = Options.GetSelected(line);
+ if (o != null && o.Description == "Quit")
+ return Result.ShouldPop;
+
+ return Result.Handled;
+ }
+
+ protected override void PrintImpl(System.IO.TextWriter writer)
+ {
+ writer.WriteLine("General:");
+ base.PrintImpl(writer);
+ }
+ }
+}
81 src/CommandContexts/ListenerManagement/AddListenerContext.cs
@@ -0,0 +1,81 @@
+//
+// AddListenerContext.cs
+//
+// Author:
+// Alan McGovern <alan.mcgovern@gmail.com>
+//
+// Copyright (C) 2008 Alan McGovern.
+//
+// Permission is hereby granted, free of charge, to any person obtaining
+// a copy of this software and associated documentation files (the
+// "Software"), to deal in the Software without restriction, including
+// without limitation the rights to use, copy, modify, merge, publish,
+// distribute, sublicense, and/or sell copies of the Software, and to
+// permit persons to whom the Software is furnished to do so, subject to
+// the following conditions:
+//
+// The above copyright notice and this permission notice shall be
+// included in all copies or substantial portions of the Software.
+//
+// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
+// EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
+// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
+// NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
+// LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
+// OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
+// WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
+//
+
+using System;
+
+namespace Tsunami
+{
+ class AddListenerContext : Context
+ {
+ bool valid = true;
+ bool alreadyListening;
+
+ public AddListenerContext(Context parent)
+ {
+ Parent = parent;
+ }
+
+ protected override Result HandleImpl(string line)
+ {
+ if (string.IsNullOrEmpty(line))
+ return Result.ShouldPop;
+
+ Uri uri;
+ valid = Uri.TryCreate(line, UriKind.Absolute, out uri);
+ if (!valid)
+ return Result.Handled;
+
+ TrackerHost host = ((GeneralContext)BaseContext).Tracker;
+
+ alreadyListening = host.Listeners.ContainsKey(uri);
+ if (alreadyListening)
+ return Result.Handled;
+
+ host.AddListener(uri);
+ return Result.ShouldPop;
+ }
+
+ protected override void PrintImpl(System.IO.TextWriter writer)
+ {
+ if (valid && !alreadyListening)
+ {
+ writer.WriteLine("Enter the new address to monitor");
+ }
+ else if (valid && alreadyListening)
+ {
+ writer.WriteLine("This address is already in use, please try again");
+ }
+ else
+ {
+ writer.WriteLine("The address you entered was invalid, please try again");
+ }
+
+ writer.WriteLine("The address should be in the form: http://ip_or_hostname:port/address");
+ }
+ }
+}
49 src/CommandContexts/ListenerManagement/ListListenersContext.cs
@@ -0,0 +1,49 @@
+//
+// ListListenersContext.cs
+//
+// Author:
+// Alan McGovern <alan.mcgovern@gmail.com>
+//
+// Copyright (C) 2008 Alan McGovern.
+//
+// Permission is hereby granted, free of charge, to any person obtaining
+// a copy of this software and associated documentation files (the
+// "Software"), to deal in the Software without restriction, including
+// without limitation the rights to use, copy, modify, merge, publish,
+// distribute, sublicense, and/or sell copies of the Software, and to
+// permit persons to whom the Software is furnished to do so, subject to
+// the following conditions:
+//
+// The above copyright notice and this permission notice shall be
+// included in all copies or substantial portions of the Software.
+//
+// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
+// EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
+// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
+// NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
+// LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
+// OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
+// WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
+//
+
+using System;
+
+namespace Tsunami
+{
+ class ListListenersContext : Context
+ {
+ public ListListenersContext(Context parent)
+ {
+ Parent = parent;
+
+ foreach (Uri uri in ((GeneralContext)BaseContext).Tracker.Listeners.Keys)
+ Options.Add(new Option(uri.ToString(), (Options.Count + 1).ToString()));
+ }
+
+ protected override void PrintImpl(System.IO.TextWriter writer)
+ {
+ writer.WriteLine("There are {0} registered addresses", Options.Count);
+ base.PrintImpl(writer);
+ }
+ }
+}
45 src/CommandContexts/ListenerManagement/ListenerContext.cs
@@ -0,0 +1,45 @@
+//
+// ListenerContext.cs
+//
+// Author:
+// Alan McGovern <alan.mcgovern@gmail.com>
+//
+// Copyright (C) 2008 Alan McGovern.
+//
+// Permission is hereby granted, free of charge, to any person obtaining
+// a copy of this software and associated documentation files (the
+// "Software"), to deal in the Software without restriction, including
+// without limitation the rights to use, copy, modify, merge, publish,
+// distribute, sublicense, and/or sell copies of the Software, and to
+// permit persons to whom the Software is furnished to do so, subject to
+// the following conditions:
+//
+// The above copyright notice and this permission notice shall be
+// included in all copies or substantial portions of the Software.
+//
+// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
+// EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
+// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
+// NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
+// LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
+// OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
+// WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
+//
+
+using System;
+
+namespace Tsunami
+{
+ class ListenerContext : Context
+ {
+ public ListenerContext(Context parent)
+ {
+ Parent = parent;
+ Options.AddRange(new Option[] {
+ new Option("Add listener", new AddListenerContext(this)),
+ new Option("List listener", new ListListenersContext(this)),
+ new Option("Remove listener", new RemoveListenerContext(this))
+ });
+ }
+ }
+}
62 src/CommandContexts/ListenerManagement/RemoveListenerContext.cs
@@ -0,0 +1,62 @@
+//
+// RemoveListenerContext.cs
+//
+// Author:
+// Alan McGovern <alan.mcgovern@gmail.com>
+//
+// Copyright (C) 2008 Alan McGovern.
+//
+// Permission is hereby granted, free of charge, to any person obtaining
+// a copy of this software and associated documentation files (the
+// "Software"), to deal in the Software without restriction, including
+// without limitation the rights to use, copy, modify, merge, publish,
+// distribute, sublicense, and/or sell copies of the Software, and to
+// permit persons to whom the Software is furnished to do so, subject to
+// the following conditions:
+//
+// The above copyright notice and this permission notice shall be
+// included in all copies or substantial portions of the Software.
+//
+// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
+// EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
+// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
+// NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
+// LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
+// OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
+// WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
+//
+
+using System;
+
+namespace Tsunami
+{
+ class RemoveListenerContext : Context
+ {
+ public RemoveListenerContext(Context parent)
+ {
+ Parent = parent;
+
+ foreach (Uri uri in ((GeneralContext)BaseContext).Tracker.Listeners.Keys)
+ Options.Add(new Option(uri.ToString(), (Options.Count + 1).ToString()));
+ }
+
+ protected override Result HandleImpl(string line)
+ {
+ if (string.IsNullOrEmpty(line))
+ return Result.ShouldPop;
+
+ Option option = Options.GetSelected(line);
+ if (option == null)
+ return Result.Unhandled;
+
+ ((GeneralContext)BaseContext).Tracker.RemoveListener(new Uri(option.Description));
+ return Result.ShouldPop;
+ }
+
+ protected override void PrintImpl(System.IO.TextWriter writer)
+ {
+ writer.WriteLine("Choose the address to stop monitoring");
+ base.PrintImpl(writer);
+ }
+ }
+}
75 src/CommandContexts/Options/Option.cs
@@ -0,0 +1,75 @@
+//
+// Option.cs
+//
+// Author:
+// Alan McGovern <alan.mcgovern@gmail.com>
+//
+// Copyright (C) 2008 Alan McGovern.
+//
+// Permission is hereby granted, free of charge, to any person obtaining
+// a copy of this software and associated documentation files (the
+// "Software"), to deal in the Software without restriction, including
+// without limitation the rights to use, copy, modify, merge, publish,
+// distribute, sublicense, and/or sell copies of the Software, and to
+// permit persons to whom the Software is furnished to do so, subject to
+// the following conditions:
+//
+// The above copyright notice and this permission notice shall be
+// included in all copies or substantial portions of the Software.
+//
+// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
+// EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
+// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
+// NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
+// LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
+// OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
+// WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
+//
+
+using System;
+using System.Collections.Generic;
+
+namespace Tsunami
+{
+ public class Option
+ {
+ private List<string> activators;
+ private Context context;
+ private string description;
+
+ public List<string> Activators
+ {
+ get { return activators; }
+ }
+
+ public Context Context
+ {
+ get { return context; }
+ }
+
+ public string Description
+ {
+ get { return description; }
+ }
+
+
+ public Option(string description, params string[] activators)
+ : this(description, null, activators)
+ {
+ this.description = description;
+ this.activators = new List<string>(activators);
+ }
+
+ public Option(string description, Context context, params string[] activators)
+ {
+ this.activators = new List<string>(activators);
+ this.context = context;
+ this.description = description;
+ }
+
+ internal void Write(System.IO.TextWriter writer)
+ {
+ writer.WriteLine("{0}) {1}", activators[0], description);
+ }
+ }
+}
91 src/CommandContexts/Options/Options.cs
@@ -0,0 +1,91 @@
+//
+// Options.cs
+//
+// Author:
+// Alan McGovern <alan.mcgovern@gmail.com>
+//
+// Copyright (C) 2008 Alan McGovern.
+//
+// Permission is hereby granted, free of charge, to any person obtaining
+// a copy of this software and associated documentation files (the
+// "Software"), to deal in the Software without restriction, including
+// without limitation the rights to use, copy, modify, merge, publish,
+// distribute, sublicense, and/or sell copies of the Software, and to
+// permit persons to whom the Software is furnished to do so, subject to
+// the following conditions:
+//
+// The above copyright notice and this permission notice shall be
+// included in all copies or substantial portions of the Software.
+//
+// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
+// EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
+// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
+// NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
+// LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
+// OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
+// WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
+//
+
+using System;
+using System.Collections.Generic;
+
+namespace Tsunami
+{
+ public class Options : IEnumerable<Option>
+ {
+ private List<Option> options = new List<Option>();
+
+ public int Count
+ {
+ get { return options.Count; }
+ }
+ public Option this[int index]
+ {
+ get { return options[index]; }
+ }
+
+ public void Add(Option option)
+ {
+ option.Activators.Insert(0, (options.Count + 1).ToString());
+ options.Add(option);
+
+ }
+
+ public void AddRange(IEnumerable<Option> options)
+ {
+ foreach (Option o in options)
+ Add(o);
+ }
+
+ public Option Find (Predicate<Option> predicate)
+ {
+ return options.Find(predicate);
+ }
+
+ internal Option GetSelected(string line)
+ {
+ Option option = Find(delegate(Option o) {
+ return o.Activators.Exists(delegate(string s) { return s.Equals(line); });
+ });
+
+ if (option != null)
+ return option;
+
+ int index = -1;
+ if (!int.TryParse(line, out index) || index < 0 || index > Count)
+ return null;
+
+ return this[index];
+ }
+
+ public IEnumerator<Option> GetEnumerator()
+ {
+ return options.GetEnumerator();
+ }
+
+ System.Collections.IEnumerator System.Collections.IEnumerable.GetEnumerator()
+ {
+ return GetEnumerator();
+ }
+ }
+}
51 src/CommandContexts/StatisticsContext.cs
@@ -0,0 +1,51 @@
+//
+// StatisticsContext.cs
+//
+// Author:
+// Alan McGovern <alan.mcgovern@gmail.com>
+//
+// Copyright (C) 2008 Alan McGovern.
+//
+// Permission is hereby granted, free of charge, to any person obtaining
+// a copy of this software and associated documentation files (the
+// "Software"), to deal in the Software without restriction, including
+// without limitation the rights to use, copy, modify, merge, publish,
+// distribute, sublicense, and/or sell copies of the Software, and to
+// permit persons to whom the Software is furnished to do so, subject to
+// the following conditions:
+//
+// The above copyright notice and this permission notice shall be
+// included in all copies or substantial portions of the Software.
+//
+// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
+// EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
+// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
+// NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
+// LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
+// OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
+// WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
+//
+
+using System;
+
+namespace Tsunami
+{
+ class StatisticsContext : Context
+ {
+ public StatisticsContext(Context parent)
+ {
+ Parent = parent;
+ }
+
+ protected override void PrintImpl(System.IO.TextWriter writer)
+ {
+ TrackerHost host = ((GeneralContext)BaseContext).Tracker;
+ writer.WriteLine("Active Torrents: {0}", host.Tracker.Count);
+ writer.WriteLine("Announces/sec: {0}", host.Tracker.Requests.AnnounceRate);
+ writer.WriteLine("Scrapes/sec: {0}", host.Tracker.Requests.ScrapeRate);
+ writer.WriteLine("Total Announces: {0}", host.Tracker.Requests.TotalAnnounces);
+ writer.WriteLine("Total Scrapes: {0}", host.Tracker.Requests.TotalScrapes);
+ base.PrintImpl(writer);
+ }
+ }
+}
41 src/MainClass.cs
@@ -0,0 +1,41 @@
+//
+// MainClass.cs
+//
+// Author:
+// Alan McGovern <alan.mcgovern@gmail.com>
+//
+// Copyright (C) 2008 Alan McGovern.
+//
+// Permission is hereby granted, free of charge, to any person obtaining
+// a copy of this software and associated documentation files (the
+// "Software"), to deal in the Software without restriction, including
+// without limitation the rights to use, copy, modify, merge, publish,
+// distribute, sublicense, and/or sell copies of the Software, and to
+// permit persons to whom the Software is furnished to do so, subject to
+// the following conditions:
+//
+// The above copyright notice and this permission notice shall be
+// included in all copies or substantial portions of the Software.
+//
+// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
+// EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
+// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
+// NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
+// LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
+// OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
+// WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
+//
+
+using System;
+
+namespace Tsunami
+{
+ class MainClass
+ {
+ static void Main(string[] args)
+ {
+ TrackerHost host = new TrackerHost();
+ host.Run();
+ }
+ }
+}
170 src/TrackerHost.cs
@@ -0,0 +1,170 @@
+//The MIT License
+//
+//Copyright (c) <2008> Alan McGovern <alan.mcgovern@gmail.com>
+//
+//Permission is hereby granted, free of charge, to any person obtaining a copy
+//of this software and associated documentation files (the "Software"), to deal
+//in the Software without restriction, including without limitation the rights
+//to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+//copies of the Software, and to permit persons to whom the Software is
+//furnished to do so, subject to the following conditions:
+//
+//The above copyright notice and this permission notice shall be included in
+//all copies or substantial portions of the Software.
+//
+//THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+//IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+//FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+//AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+//LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+//OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+//THE SOFTWARE.
+
+
+using System;
+using System.Collections.Generic;
+using System.IO;
+using Mono.Terminal;
+using MonoTorrent.Tracker;
+using MonoTorrent.TorrentWatcher;
+using MonoTorrent.Common;
+using MonoTorrent.Tracker.Listeners;
+
+namespace Tsunami
+{
+ public class TrackerHost
+ {
+ private Context context;
+ private LineEditor editor = new LineEditor("Tsunami tracker");
+ private Queue<string> history = new Queue<string>();
+ private Dictionary<Uri, HttpListener> listeners = new Dictionary<Uri, HttpListener>();
+ private Tracker tracker = new Tracker();
+ private Dictionary<string, ITorrentWatcher> watchers = new Dictionary<string, ITorrentWatcher>();
+
+ public Dictionary<Uri, HttpListener> Listeners
+ {
+ get { return listeners; }
+ }
+
+ public Tracker Tracker
+ {
+ get { return tracker; }
+ }
+
+ public Dictionary<string, ITorrentWatcher> Watchers
+ {
+ get { return watchers; }
+ }
+
+ public TrackerHost()
+ {
+ context = new GeneralContext(this);
+ editor.AutoCompleteEvent += delegate {
+ // Use this event to allow the tab key to refresh the screen
+ Process("__RefreshTheScreen__");
+ return null;
+ };
+ }
+
+ public void Run()
+ {
+ string s;
+ context.Print(Console.Out);
+ Console.Out.WriteLine();
+
+ while ((s = editor.Edit("tsunami>", "")) != null)
+ if (!Process(s))
+ return;
+ }
+
+ private bool Process(string s)
+ {
+ bool r=false;
+
+ // If we're refreshing the screen, then ignore the input string input
+ if (s != "__RefreshTheScreen__")
+ r = context.Handle(s) != Result.ShouldPop;
+
+ Console.Clear();
+ context.Print(Console.Out);
+ Console.Out.WriteLine();
+
+ // HACK - If i clear the console, i need to rewrite the
+ // prompt otherwise things end up out of sync with getline.cs
+ if (s == "__RefreshTheScreen__")
+ Console.Write("tsunami>");
+ return r;
+ }
+
+ private void AddHistory(string line)
+ {
+ lock (history)
+ {
+ if (history.Count > 15)
+ history.Dequeue();
+ history.Enqueue(line);
+ }
+ }
+
+ private void AddHistory(string line, params object[] args)
+ {
+ AddHistory(string.Format(line, args));
+ }
+
+ internal void AddListener(Uri uri)
+ {
+ string url = uri.ToString();
+ if (!url.EndsWith("/"))
+ url = url + "/";
+
+ HttpListener listener = new HttpListener(url);
+ tracker.RegisterListener(listener);
+ listener.Start();
+ listeners.Add(uri, listener);
+ }
+
+ internal void AddWatcher(string line)
+ {
+ TorrentFolderWatcher watcher = new TorrentFolderWatcher(line, "*.torrent");
+ watcher.TorrentFound += TorrentFound;
+ watcher.ForceScan();
+ watcher.Start();
+ watchers.Add(line, watcher);
+ }
+
+ internal void RemoveListener(Uri uri)
+ {
+ HttpListener listener = listeners[uri];
+ tracker.UnregisterListener(listener);
+ listener.Stop();
+ listeners.Remove(uri);
+ }
+
+ internal void RemoveWatcher(string line)
+ {
+ if (!watchers.ContainsKey(line))
+ return;
+
+ ITorrentWatcher watcher = watchers[line];
+ watcher.Stop();
+ watcher.TorrentFound -= TorrentFound;
+ watchers.Remove(line);
+ }
+
+ void TorrentFound(object o, TorrentWatcherEventArgs e)
+ {
+ Torrent torrent = null;
+ try
+ {
+ torrent = Torrent.Load(e.TorrentPath);
+ }
+ catch(Exception ex)
+ {
+ AddHistory("Could not load: {0}. Reason: {1}", e.TorrentPath, ex.Message);
+ }
+
+ InfoHashTrackable trackable = new InfoHashTrackable(torrent);
+ tracker.Add(trackable);
+ }
+ }
+}
70 src/Tsunami.csproj
@@ -0,0 +1,70 @@
+<?xml version="1.0" encoding="utf-8"?>
+<Project DefaultTargets="Build" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
+ <PropertyGroup>
+ <Configuration Condition=" '$(Configuration)' == '' ">Debug</Configuration>
+ <Platform Condition=" '$(Platform)' == '' ">AnyCPU</Platform>
+ <ProductVersion>8.0.50727</ProductVersion>
+ <SchemaVersion>2.0</SchemaVersion>
+ <ProjectGuid>{907A9A8F-B472-479F-8643-A1A82057CD32}</ProjectGuid>
+ <OutputType>Exe</OutputType>
+ <AppDesignerFolder>Properties</AppDesignerFolder>
+ <RootNamespace>Tsunami</RootNamespace>
+ <AssemblyName>Tsunami</AssemblyName>
+ </PropertyGroup>
+ <PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Debug|AnyCPU' ">
+ <DebugSymbols>true</DebugSymbols>
+ <DebugType>full</DebugType>
+ <Optimize>false</Optimize>
+ <OutputPath>bin\Debug\</OutputPath>
+ <DefineConstants>DEBUG;TRACE</DefineConstants>
+ <ErrorReport>prompt</ErrorReport>
+ <WarningLevel>4</WarningLevel>
+ </PropertyGroup>
+ <PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Release|AnyCPU' ">
+ <DebugType>pdbonly</DebugType>
+ <Optimize>true</Optimize>
+ <OutputPath>bin\Release\</OutputPath>
+ <DefineConstants>TRACE</DefineConstants>
+ <ErrorReport>prompt</ErrorReport>
+ <WarningLevel>4</WarningLevel>
+ </PropertyGroup>
+ <ItemGroup>
+ <Reference Include="System" />
+ <Reference Include="System.Data" />
+ <Reference Include="System.Xml" />
+ </ItemGroup>
+ <ItemGroup>
+ <Compile Include="CommandContexts\DirectoryManagement\AddDirectoryContext.cs" />
+ <Compile Include="CommandContexts\DirectoryManagement\DirectoryContext.cs" />
+ <Compile Include="CommandContexts\ListenerManagement\AddListenerContext.cs" />
+ <Compile Include="CommandContexts\Context.cs" />
+ <Compile Include="CommandContexts\GeneralContext.cs" />
+ <Compile Include="CommandContexts\DirectoryManagement\ListDirectoriesContext.cs" />
+ <Compile Include="CommandContexts\ListenerManagement\ListenerContext.cs" />
+ <Compile Include="CommandContexts\ListenerManagement\ListListenersContext.cs" />
+ <Compile Include="CommandContexts\ListenerManagement\RemoveListenerContext.cs" />
+ <Compile Include="CommandContexts\Options\Option.cs" />
+ <Compile Include="CommandContexts\Options\Options.cs" />
+ <Compile Include="CommandContexts\DirectoryManagement\RemoveDirectoryContext.cs" />
+ <Compile Include="CommandContexts\StatisticsContext.cs" />
+ <Compile Include="getline.cs" />
+ <Compile Include="MainClass.cs" />
+ <Compile Include="TrackerHost.cs" />
+ <Compile Include="AssemblyInfo.cs" />
+ </ItemGroup>
+ <Import Project="$(MSBuildBinPath)\Microsoft.CSharp.targets" />
+ <!-- To modify your build process, add your task inside one of the targets below and uncomment it.
+ Other similar extension points exist, see Microsoft.Common.targets.
+ <Target Name="BeforeBuild">
+ </Target>
+ <Target Name="AfterBuild">
+ </Target>
+ -->
+ <ProjectExtensions>
+ <MonoDevelop>
+ <Properties>
+ <GtkDesignInfo />
+ </Properties>
+ </MonoDevelop>
+ </ProjectExtensions>
+</Project>
1,032 src/getline.cs
@@ -0,0 +1,1032 @@
+//
+// getline.cs: A command line editor
+//
+// Authors:
+// Miguel de Icaza (miguel@novell.com)
+//
+// Copyright 2008 Novell, Inc.
+//
+// Dual-licensed under the terms of the MIT X11 license or the
+// Apache License 2.0
+//
+// USE -define:DEMO to build this as a standalone file and test it
+//
+// TODO:
+// Enter an error (a = 1); Notice how the prompt is in the wrong line
+// This is caused by Stderr not being tracked by System.Console.
+// Completion support
+// Why is Thread.Interrupt not working? Currently I resort to Abort which is too much.
+//
+// Limitations in System.Console:
+// Console needs SIGWINCH support of some sort
+// Console needs a way of updating its position after things have been written
+// behind its back (P/Invoke puts for example).
+// System.Console needs to get the DELETE character, and report accordingly.
+// Typing before the program start causes the cursor position to be wrong
+// This is caused by Console not reading all the available data
+// before sending the report-position sequence and reading it back.
+//
+#if NET_2_0 || NET_1_1
+#define IN_MCS_BUILD
+#endif
+
+// Only compile this code in the 2.0 profile, but not in the Moonlight one
+#if (IN_MCS_BUILD && NET_2_0 && !SMCS_SOURCE) || !IN_MCS_BUILD
+using System;
+using System.Text;
+using System.IO;
+using System.Threading;
+using System.Reflection;
+
+namespace Mono.Terminal {
+
+ public class LineEditor {
+
+ public delegate string [] AutoCompleteHandler (string text, int pos);
+
+ //static StreamWriter log;
+
+ // The text being edited.
+ StringBuilder text;
+
+ // The text as it is rendered (replaces (char)1 with ^A on display for example).
+ StringBuilder rendered_text;
+
+ // The prompt specified, and the prompt shown to the user.
+ string prompt;
+ string shown_prompt;
+
+ // The current cursor position, indexes into "text", for an index
+ // into rendered_text, use TextToRenderPos
+ int cursor;
+
+ // The row where we started displaying data.
+ int home_row;
+
+ // The maximum length that has been displayed on the screen
+ int max_rendered;
+
+ // If we are done editing, this breaks the interactive loop
+ bool done = false;
+
+ // The thread where the Editing started taking place
+ Thread edit_thread;
+
+ // Our object that tracks history
+ History history;
+
+ // The contents of the kill buffer (cut/paste in Emacs parlance)
+ string kill_buffer = "";
+
+ // The string being searched for
+ string search;
+ string last_search;
+
+ // whether we are searching (-1= reverse; 0 = no; 1 = forward)
+ int searching;
+
+ // The position where we found the match.
+ int match_at;
+
+ // Used to implement the Kill semantics (multiple Alt-Ds accumulate)
+ KeyHandler last_handler;
+
+ delegate void KeyHandler ();
+
+ struct Handler {
+ public ConsoleKeyInfo CKI;
+ public KeyHandler KeyHandler;
+
+ public Handler (ConsoleKey key, KeyHandler h)
+ {
+ CKI = new ConsoleKeyInfo ((char) 0, key, false, false, false);
+ KeyHandler = h;
+ }
+
+ public Handler (char c, KeyHandler h)
+ {
+ KeyHandler = h;
+ // Use the "Zoom" as a flag that we only have a character.
+ CKI = new ConsoleKeyInfo (c, ConsoleKey.Zoom, false, false, false);
+ }
+
+ public Handler (ConsoleKeyInfo cki, KeyHandler h)
+ {
+ CKI = cki;
+ KeyHandler = h;
+ }
+
+ public static Handler Control (char c, KeyHandler h)
+ {
+ return new Handler ((char) (c - 'A' + 1), h);
+ }
+
+ public static Handler Alt (char c, ConsoleKey k, KeyHandler h)
+ {
+ ConsoleKeyInfo cki = new ConsoleKeyInfo ((char) c, k, false, true, false);
+ return new Handler (cki, h);
+ }
+ }
+
+ /// <summary>
+ /// Invoked when the user requests auto-completion using the tab character
+ /// </summary>
+ /// <remarks>
+ /// The result is null for no values found, an array with a single
+ /// string, in that case the string should be the text to be inserted
+ /// for example if the word at pos is "T", the result for a completion
+ /// of "ToString" should be "oString", not "ToString".
+ ///
+ /// When there are multiple results, the result should be the full
+ /// text
+ /// </remarks>
+ public AutoCompleteHandler AutoCompleteEvent;
+
+ static Handler [] handlers;
+
+ public LineEditor (string name) : this (name, 10) { }
+
+ public LineEditor (string name, int histsize)
+ {
+ handlers = new Handler [] {
+ new Handler (ConsoleKey.Home, CmdHome),
+ new Handler (ConsoleKey.End, CmdEnd),
+ new Handler (ConsoleKey.LeftArrow, CmdLeft),
+ new Handler (ConsoleKey.RightArrow, CmdRight),
+ new Handler (ConsoleKey.UpArrow, CmdHistoryPrev),
+ new Handler (ConsoleKey.DownArrow, CmdHistoryNext),
+ new Handler (ConsoleKey.Enter, CmdDone),
+ new Handler (ConsoleKey.Backspace, CmdBackspace),
+ new Handler (ConsoleKey.Delete, CmdDeleteChar),
+ new Handler (ConsoleKey.Tab, CmdTabOrComplete),
+
+ // Emacs keys
+ Handler.Control ('A', CmdHome),
+ Handler.Control ('E', CmdEnd),
+ Handler.Control ('B', CmdLeft),
+ Handler.Control ('F', CmdRight),
+ Handler.Control ('P', CmdHistoryPrev),
+ Handler.Control ('N', CmdHistoryNext),
+ Handler.Control ('K', CmdKillToEOF),
+ Handler.Control ('Y', CmdYank),
+ Handler.Control ('D', CmdDeleteChar),
+ Handler.Control ('L', CmdRefresh),
+ Handler.Control ('R', CmdReverseSearch),
+ Handler.Control ('G', delegate {} ),
+ Handler.Alt ('B', ConsoleKey.B, CmdBackwardWord),
+ Handler.Alt ('F', ConsoleKey.F, CmdForwardWord),
+
+ Handler.Alt ('D', ConsoleKey.D, CmdDeleteWord),
+ Handler.Alt ((char) 8, ConsoleKey.Backspace, CmdDeleteBackword),
+
+ // DEBUG
+ Handler.Control ('T', CmdDebug),
+
+ // quote
+ Handler.Control ('Q', delegate { HandleChar (Console.ReadKey (true).KeyChar); })
+ };
+
+ rendered_text = new StringBuilder ();
+ text = new StringBuilder ();
+
+ history = new History (name, histsize);
+
+ //if (File.Exists ("log"))File.Delete ("log");
+ //log = File.CreateText ("log");
+ }
+
+ void CmdDebug ()
+ {
+ history.Dump ();
+ Console.WriteLine ();
+ Render ();
+ }
+
+ void Render ()
+ {
+ Console.Write (shown_prompt);
+ Console.Write (rendered_text);
+
+ int max = System.Math.Max (rendered_text.Length + shown_prompt.Length, max_rendered);
+
+ for (int i = rendered_text.Length + shown_prompt.Length; i < max_rendered; i++)
+ Console.Write (' ');
+ max_rendered = shown_prompt.Length + rendered_text.Length;
+
+ // Write one more to ensure that we always wrap around properly if we are at the
+ // end of a line.
+ Console.Write (' ');
+
+ UpdateHomeRow (max);
+ }
+
+ void UpdateHomeRow (int screenpos)
+ {
+ int lines = 1 + (screenpos / Console.WindowWidth);
+
+ home_row = Console.CursorTop - (lines - 1);
+ if (home_row < 0)
+ home_row = 0;
+ }
+
+
+ void RenderFrom (int pos)
+ {
+ int rpos = TextToRenderPos (pos);
+ int i;
+
+ for (i = rpos; i < rendered_text.Length; i++)
+ Console.Write (rendered_text [i]);
+
+ if ((shown_prompt.Length + rendered_text.Length) > max_rendered)
+ max_rendered = shown_prompt.Length + rendered_text.Length;
+ else {
+ int max_extra = max_rendered - shown_prompt.Length;
+ for (; i < max_extra; i++)
+ Console.Write (' ');
+ }
+ }
+
+ void ComputeRendered ()
+ {
+ rendered_text.Length = 0;
+
+ for (int i = 0; i < text.Length; i++){
+ int c = (int) text [i];
+ if (c < 26){
+ if (c == '\t')
+ rendered_text.Append (" ");
+ else {
+ rendered_text.Append ('^');
+ rendered_text.Append ((char) (c + (int) 'A' - 1));
+ }
+ } else
+ rendered_text.Append ((char)c);
+ }
+ }
+
+ int TextToRenderPos (int pos)
+ {
+ int p = 0;
+
+ for (int i = 0; i < pos; i++){
+ int c;
+
+ c = (int) text [i];
+
+ if (c < 26){
+ if (c == 9)
+ p += 4;
+ else
+ p += 2;
+ } else
+ p++;
+ }
+
+ return p;
+ }
+
+ int TextToScreenPos (int pos)
+ {
+ return shown_prompt.Length + TextToRenderPos (pos);
+ }
+
+ string Prompt {
+ get { return prompt; }
+ set { prompt = value; }
+ }
+
+ int LineCount {
+ get {
+ return (shown_prompt.Length + rendered_text.Length)/Console.WindowWidth;
+ }
+ }
+
+ void ForceCursor (int newpos)
+ {
+ cursor = newpos;
+
+ int actual_pos = shown_prompt.Length + TextToRenderPos (cursor);
+ int row = home_row + (actual_pos/Console.WindowWidth);
+ int col = actual_pos % Console.WindowWidth;
+
+ if (row >= Console.BufferHeight)
+ row = Console.BufferHeight-1;
+ Console.SetCursorPosition (col, row);
+
+ //log.WriteLine ("Going to cursor={0} row={1} col={2} actual={3} prompt={4} ttr={5} old={6}", newpos, row, col, actual_pos, prompt.Length, TextToRenderPos (cursor), cursor);
+ //log.Flush ();
+ }
+
+ void UpdateCursor (int newpos)
+ {
+ if (cursor == newpos)
+ return;
+
+ ForceCursor (newpos);
+ }
+
+ void InsertChar (char c)
+ {
+ int prev_lines = LineCount;
+ text = text.Insert (cursor, c);
+ ComputeRendered ();
+ if (prev_lines != LineCount){
+
+ Console.SetCursorPosition (0, home_row);
+ Render ();
+ ForceCursor (++cursor);
+ } else {
+ RenderFrom (cursor);
+ ForceCursor (++cursor);
+ UpdateHomeRow (TextToScreenPos (cursor));
+ }
+ }
+
+ //
+ // Commands
+ //
+ void CmdDone ()
+ {
+ done = true;
+ }
+
+ void CmdTabOrComplete ()
+ {
+ bool complete = true;
+
+ if (AutoCompleteEvent != null){
+ for (int i = 0; i < cursor; i++){
+ if (!Char.IsWhiteSpace (text [i])){
+ complete = true;
+ break;
+ }
+ }
+ if (complete){
+ string [] completions = AutoCompleteEvent (text.ToString (), cursor);
+ if (completions == null || completions.Length == 0)
+ return;
+
+ if (completions.Length == 1){
+ InsertTextAtCursor (completions [0]);
+ } else {
+ Console.WriteLine ();
+ foreach (string s in completions){
+ Console.Write (s);
+ Console.Write (' ');
+ }
+ Console.WriteLine ();
+ Render ();
+ ForceCursor (cursor);
+ }
+ } else
+ HandleChar ('\t');
+ } else
+ HandleChar ('t');
+ }
+
+ void CmdHome ()
+ {
+ UpdateCursor (0);
+ }
+
+ void CmdEnd ()
+ {
+ UpdateCursor (text.Length);
+ }
+
+ void CmdLeft ()
+ {
+ if (cursor == 0)
+ return;
+
+ UpdateCursor (cursor-1);
+ }
+
+ void CmdBackwardWord ()
+ {
+ int p = WordBackward (cursor);
+ if (p == -1)
+ return;
+ UpdateCursor (p);
+ }
+
+ void CmdForwardWord ()
+ {
+ int p = WordForward (cursor);
+ if (p == -1)
+ return;
+ UpdateCursor (p);
+ }
+
+ void CmdRight ()
+ {
+ if (cursor == text.Length)
+ return;
+
+ UpdateCursor (cursor+1);
+ }
+
+ void RenderAfter (int p)
+ {
+ ForceCursor (p);
+ RenderFrom (p);
+ ForceCursor (cursor);
+ }
+
+ void CmdBackspace ()
+ {
+ if (cursor == 0)
+ return;
+
+ text.Remove (--cursor, 1);
+ ComputeRendered ();
+ RenderAfter (cursor);
+ }
+
+ void CmdDeleteChar ()
+ {
+ // If there is no input, this behaves like EOF
+ if (text.Length == 0){
+ done = true;
+ text = null;
+ Console.WriteLine ();
+ return;
+ }
+
+ if (cursor == text.Length)
+ return;
+ text.Remove (cursor, 1);
+ ComputeRendered ();
+ RenderAfter (cursor);
+ }
+
+ int WordForward (int p)
+ {
+ if (p >= text.Length)
+ return -1;
+
+ int i = p;
+ if (Char.IsPunctuation (text [p]) || Char.IsWhiteSpace (text[p])){
+ for (; i < text.Length; i++){
+ if (Char.IsLetterOrDigit (text [i]))
+ break;
+ }
+ for (; i < text.Length; i++){
+ if (!Char.IsLetterOrDigit (text [i]))
+ break;
+ }
+ } else {
+ for (; i < text.Length; i++){
+ if (!Char.IsLetterOrDigit (text [i]))
+ break;
+ }
+ }
+ if (i != p)
+ return i;
+ return -1;
+ }
+
+ int WordBackward (int p)
+ {
+ if (p == 0)
+ return -1;
+
+ int i = p-1;
+ if (i == 0)
+ return 0;
+
+ if (Char.IsPunctuation (text [i]) || Char.IsSymbol (text [i]) || Char.IsWhiteSpace (text[i])){
+ for (; i >= 0; i--){
+ if (Char.IsLetterOrDigit (text [i]))
+ break;
+ }
+ for (; i >= 0; i--){
+ if (!Char.IsLetterOrDigit (text[i]))
+ break;
+ }
+ } else {
+ for (; i >= 0; i--){
+ if (!Char.IsLetterOrDigit (text [i]))
+ break;
+ }
+ }
+ i++;
+
+ if (i != p)
+ return i;
+
+ return -1;
+ }
+
+ void CmdDeleteWord ()
+ {
+ int pos = WordForward (cursor);
+
+ if (pos == -1)
+ return;
+
+ string k = text.ToString (cursor, pos-cursor);
+
+ if (last_handler == CmdDeleteWord)
+ kill_buffer = kill_buffer + k;
+ else
+ kill_buffer = k;
+
+ text.Remove (cursor, pos-cursor);
+ ComputeRendered ();
+ RenderAfter (cursor);
+ }
+
+ void CmdDeleteBackword ()
+ {
+ int pos = WordBackward (cursor);
+ if (pos == -1)
+ return;
+
+ string k = text.ToString (pos, cursor-pos);
+
+ if (last_handler == CmdDeleteBackword)
+ kill_buffer = k + kill_buffer;
+ else
+ kill_buffer = k;
+
+ text.Remove (pos, cursor-pos);
+ ComputeRendered ();
+ RenderAfter (pos);
+ }
+
+ //
+ // Adds the current line to the history if needed
+ //
+ void HistoryUpdateLine ()
+ {
+ history.Update (text.ToString ());
+ }
+
+ void CmdHistoryPrev ()
+ {
+ if (!history.PreviousAvailable ())
+ return;
+
+ HistoryUpdateLine ();
+
+ SetText (history.Previous ());
+ }
+
+ void CmdHistoryNext ()
+ {
+ if (!history.NextAvailable())
+ return;
+
+ history.Update (text.ToString ());
+ SetText (history.Next ());
+
+ }
+
+ void CmdKillToEOF ()
+ {
+ kill_buffer = text.ToString (cursor, text.Length-cursor);
+ text.Length = cursor;
+ ComputeRendered ();
+ RenderAfter (cursor);
+ }
+
+ void CmdYank ()
+ {
+ InsertTextAtCursor (kill_buffer);
+ }
+
+ void InsertTextAtCursor (string str)
+ {
+ int prev_lines = LineCount;
+ text.Insert (cursor, str);
+ ComputeRendered ();
+ if (prev_lines != LineCount){
+ Console.SetCursorPosition (0, home_row);
+ Render ();
+ cursor += str.Length;
+ ForceCursor (cursor);
+ } else {
+ RenderFrom (cursor);
+ cursor += str.Length;
+ ForceCursor (cursor);
+ UpdateHomeRow (TextToScreenPos (cursor));
+ }
+ }
+
+ void SetSearchPrompt (string s)
+ {
+ SetPrompt ("(reverse-i-search)`" + s + "': ");
+ }
+
+ void ReverseSearch ()
+ {
+ int p;
+
+ if (cursor == text.Length){
+ // The cursor is at the end of the string
+
+ p = text.ToString ().LastIndexOf (search);
+ if (p != -1){
+ match_at = p;
+ cursor = p;
+ ForceCursor (cursor);
+ return;
+ }
+ } else {
+ // The cursor is somewhere in the middle of the string
+ int start = (cursor == match_at) ? cursor - 1 : cursor;
+ if (start != -1){
+ p = text.ToString ().LastIndexOf (search, start);
+ if (p != -1){
+ match_at = p;
+ cursor = p;
+ ForceCursor (cursor);
+ return;
+ }
+ }
+ }
+
+ // Need to search backwards in history
+ HistoryUpdateLine ();
+ string s = history.SearchBackward (search);
+ if (s != null){
+ match_at = -1;
+ SetText (s);
+ ReverseSearch ();
+ }
+ }
+
+ void CmdReverseSearch ()
+ {
+ if (searching == 0){
+ match_at = -1;
+ last_search = search;
+ searching = -1;
+ search = "";
+ SetSearchPrompt ("");
+ } else {
+ if (search == ""){
+ if (last_search != "" && last_search != null){
+ search = last_search;
+ SetSearchPrompt (search);
+
+ ReverseSearch ();
+ }
+ return;
+ }
+ ReverseSearch ();
+ }
+ }
+
+ void SearchAppend (char c)
+ {
+ search = search + c;
+ SetSearchPrompt (search);
+
+ //
+ // If the new typed data still matches the current text, stay here
+ //
+ if (cursor < text.Length){
+ string r = text.ToString (cursor, text.Length - cursor);
+ if (r.StartsWith (search))
+ return;
+ }
+
+ ReverseSearch ();
+ }
+
+ void CmdRefresh ()
+ {
+ Console.Clear ();
+ max_rendered = 0;
+ Render ();
+ ForceCursor (cursor);
+ }
+
+ void InterruptEdit (object sender, ConsoleCancelEventArgs a)
+ {
+ // Do not abort our program:
+ a.Cancel = true;
+
+ // Interrupt the editor
+ edit_thread.Abort();
+ }
+
+ void HandleChar (char c)
+ {
+ if (searching != 0)
+ SearchAppend (c);
+ else
+ InsertChar (c);
+ }
+
+ void EditLoop ()
+ {
+ ConsoleKeyInfo cki;
+
+ while (!done){
+ cki = Console.ReadKey (true);
+
+ bool handled = false;
+ foreach (Handler handler in handlers){
+ ConsoleKeyInfo t = handler.CKI;
+
+ if (t.Key == cki.Key && t.Modifiers == cki.Modifiers){
+ handled = true;
+ handler.KeyHandler ();
+ last_handler = handler.KeyHandler;
+ break;
+ } else if (t.KeyChar == cki.KeyChar && t.Key == ConsoleKey.Zoom){
+ handled = true;
+ handler.KeyHandler ();
+ last_handler = handler.KeyHandler;
+ break;
+ }
+ }
+ if (handled){
+ if (searching != 0){
+ if (last_handler != CmdReverseSearch){
+ searching = 0;
+ SetPrompt (prompt);
+ }
+ }
+ continue;
+ }
+
+ if (cki.KeyChar != (char) 0)
+ HandleChar (cki.KeyChar);
+ }
+ }
+
+ void InitText (string initial)
+ {
+ text = new StringBuilder (initial);
+ ComputeRendered ();
+ cursor = text.Length;
+ Render ();
+ ForceCursor (cursor);
+ }
+
+ void SetText (string newtext)
+ {
+ Console.SetCursorPosition (0, home_row);
+ InitText (newtext);
+ }
+
+ void SetPrompt (string newprompt)
+ {
+ shown_prompt = newprompt;
+ Console.SetCursorPosition (0, home_row);
+ Render ();
+ ForceCursor (cursor);
+ }
+
+ public string Edit (string prompt, string initial)
+ {
+ edit_thread = Thread.CurrentThread;
+ searching = 0;
+ Console.CancelKeyPress += InterruptEdit;
+
+ done = false;
+ history.CursorToEnd ();
+ max_rendered = 0;
+
+ Prompt = prompt;
+ shown_prompt = prompt;
+ InitText (initial);
+ history.Append (initial);
+
+ do {
+ try {
+ EditLoop ();
+ } catch (ThreadAbortException){
+ searching = 0;
+ Thread.ResetAbort ();
+ Console.WriteLine ();
+ SetPrompt (prompt);
+ SetText ("");
+ }
+ } while (!done);
+ Console.WriteLine ();
+
+ Console.CancelKeyPress -= InterruptEdit;
+
+ if (text == null){
+ history.Close ();
+ return null;
+ }
+
+ string result = text.ToString ();
+ if (result != "")
+ history.Accept (result);
+ else
+ history.RemoveLast ();
+
+ return result;
+ }
+
+ //
+ // Emulates the bash-like behavior, where edits done to the
+ // history are recorded
+ //
+ class History {
+ string [] history;
+ int head, tail;
+ int cursor, count;
+ string histfile;
+
+ public History (string app, int size)
+ {
+ if (size < 1)
+ throw new ArgumentException ("size");
+
+ if (app != null){
+ string dir = Environment.GetFolderPath (Environment.SpecialFolder.ApplicationData);
+ //Console.WriteLine (dir);
+ if (!Directory.Exists (dir)){
+ try {
+ Directory.CreateDirectory (dir);
+ } catch {
+ app = null;
+ }
+ }
+ if (app != null)
+ histfile = Path.Combine (dir, app) + ".history";
+ }
+
+ history = new string [size];
+ head = tail = cursor = 0;
+
+ if (File.Exists (histfile)){
+ using (StreamReader sr = File.OpenText (histfile)){
+ string line;
+
+ while ((line = sr.ReadLine ()) != null){
+ if (line != "")
+ Append (line);
+ }
+ }
+ }
+ }
+
+ public void Close ()
+ {
+ if (histfile == null)
+ return;
+
+ try {
+ using (StreamWriter sw = File.CreateText (histfile)){
+ int start = (count == history.Length) ? head : tail;
+ for (int i = start; i < start+count; i++){
+ int p = i % history.Length;
+ sw.WriteLine (history [p]);
+ }
+ }
+ } catch {
+ // ignore
+ }
+ }
+
+ //
+ // Appends a value to the history
+ //
+ public void Append (string s)
+ {
+ //Console.WriteLine ("APPENDING {0} {1}", s, Environment.StackTrace);
+ history [head] = s;
+ head = (head+1) % history.Length;
+ if (head == tail)
+ tail = (tail+1 % history.Length);
+ if (count != history.Length)
+ count++;
+ }
+
+ //
+ // Updates the current cursor location with the string,
+ // to support editing of history items. For the current
+ // line to participate, an Append must be done before.
+ //
+ public void Update (string s)
+ {
+ history [cursor] = s;
+ }
+
+ public void RemoveLast ()
+ {
+ head = head-1;
+ if (head < 0)
+ head = history.Length-1;
+ }
+
+ public void Accept (string s)
+ {
+ int t = head-1;
+ if (t < 0)
+ t = history.Length-1;
+
+ history [t] = s;
+ }
+
+ public bool PreviousAvailable ()
+ {
+ //Console.WriteLine ("h={0} t={1} cursor={2}", head, tail, cursor);
+ if (count == 0 || cursor == tail)
+ return false;
+
+ return true;
+ }
+
+ public bool NextAvailable ()
+ {
+ int next = (cursor + 1) % history.Length;
+ if (count == 0 || next > head)
+ return false;
+
+ return true;
+ }
+
+
+ //
+ // Returns: a string with the previous line contents, or
+ // nul if there is no data in the history to move to.
+ //
+ public string Previous ()
+ {
+ if (!PreviousAvailable ())
+ return null;
+
+ cursor--;
+ if (cursor < 0)
+ cursor = history.Length - 1;
+
+ return history [cursor];
+ }
+
+ public string Next ()
+ {
+ if (!NextAvailable ())
+ return null;
+
+ cursor = (cursor + 1) % history.Length;
+ return history [cursor];
+ }
+
+ public void CursorToEnd ()
+ {
+ if (head == tail)
+ return;
+
+ cursor = head;
+ }
+
+ public void Dump ()
+ {
+ Console.WriteLine ("Head={0} Tail={1} Cursor={2}", head, tail, cursor);
+ for (int i = 0; i < history.Length;i++){
+ Console.WriteLine (" {0} {1}: {2}", i == cursor ? "==>" : " ", i, history[i]);
+ }
+ //log.Flush ();
+ }
+
+ public string SearchBackward (string term)
+ {
+ for (int i = 1; i < count; i++){
+ int slot = cursor-i;
+ if (slot < 0)
+ slot = history.Length-1;
+ if (history [slot] != null && history [slot].IndexOf (term) != -1){
+ cursor = slot;
+ return history [slot];
+ }
+
+ // Will the next hit tail?
+ slot--;
+ if (slot < 0)
+ slot = history.Length-1;
+ if (slot == tail)
+ break;
+ }
+
+ return null;
+ }
+
+ }
+ }
+
+#if DEMO
+ class Demo {
+ static void Main ()
+ {
+ LineEditor le = new LineEditor (null);
+ string s;
+
+ while ((s = le.Edit ("shell> ", "")) != null){
+ Console.WriteLine ("----> [{0}]", s);
+ }
+ }
+ }
+#endif
+}
+#endif
Please sign in to comment.
Something went wrong with that request. Please try again.