From 81bdfc63462b20f42b30515a163beb1fbdaf2552 Mon Sep 17 00:00:00 2001 From: nezroy Date: Wed, 8 Apr 2015 14:13:06 -0600 Subject: [PATCH 01/14] modify extensibility loader to use plugin framework modify extensibility loader to use plugin framework --- .../CityWebServer.Extensibility.csproj | 2 + CityWebServer.Extensibility/ICityWebPlugin.cs | 18 + CityWebServer.Extensibility/IPluginInfo.cs | 25 ++ .../IRequestHandler.cs | 25 +- CityWebServer.Extensibility/IWebServer.cs | 9 +- .../RequestHandlerBase.cs | 37 +- .../RestfulRequestHandlerBase.cs | 16 +- CityWebServer/CityWebServer.csproj | 4 + CityWebServer/Helpers/ApacheMimeTypes.cs | 2 +- CityWebServer/Helpers/CityWebPluginInfo.cs | 93 ++++ CityWebServer/Helpers/TemplateHelper.cs | 5 +- CityWebServer/IntegratedWebServer.cs | 413 ++++++++---------- .../RequestHandlers/APIPluginInfo.cs | 33 ++ .../RequestHandlers/BudgetRequestHandler.cs | 2 +- .../RequestHandlers/BuildingRequestHandler.cs | 2 +- .../RequestHandlers/CityInfoRequestHandler.cs | 2 +- .../RequestHandlers/LogPluginInfo.cs | 46 ++ .../RequestHandlers/MessageRequestHandler.cs | 2 +- .../RequestHandlers/RootPluginInfo.cs | 59 +++ .../TransportRequestHandler.cs | 2 +- .../RequestHandlers/VehicleRequestHandler.cs | 2 +- CityWebServer/wwwroot/index.html | 2 +- 22 files changed, 477 insertions(+), 324 deletions(-) create mode 100644 CityWebServer.Extensibility/ICityWebPlugin.cs create mode 100644 CityWebServer.Extensibility/IPluginInfo.cs create mode 100644 CityWebServer/Helpers/CityWebPluginInfo.cs create mode 100644 CityWebServer/RequestHandlers/APIPluginInfo.cs create mode 100644 CityWebServer/RequestHandlers/LogPluginInfo.cs create mode 100644 CityWebServer/RequestHandlers/RootPluginInfo.cs diff --git a/CityWebServer.Extensibility/CityWebServer.Extensibility.csproj b/CityWebServer.Extensibility/CityWebServer.Extensibility.csproj index d60dbba..64793b9 100644 --- a/CityWebServer.Extensibility/CityWebServer.Extensibility.csproj +++ b/CityWebServer.Extensibility/CityWebServer.Extensibility.csproj @@ -41,6 +41,8 @@ + + diff --git a/CityWebServer.Extensibility/ICityWebPlugin.cs b/CityWebServer.Extensibility/ICityWebPlugin.cs new file mode 100644 index 0000000..991cbe1 --- /dev/null +++ b/CityWebServer.Extensibility/ICityWebPlugin.cs @@ -0,0 +1,18 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; + +namespace CityWebServer.Extensibility +{ + public interface ICityWebPlugin : IPluginInfo + { + /// + /// Called when the server first starts and is registering handlers for this plugin. Returns an enumerable collection of IRequestHandler objects. + /// + /// + /// Each of the returned handlers will be used by the server to handle requests. Handlers show up on the main index page of the server. + /// + List GetHandlers(IWebServer server); + } +} diff --git a/CityWebServer.Extensibility/IPluginInfo.cs b/CityWebServer.Extensibility/IPluginInfo.cs new file mode 100644 index 0000000..34e280f --- /dev/null +++ b/CityWebServer.Extensibility/IPluginInfo.cs @@ -0,0 +1,25 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; + +namespace CityWebServer.Extensibility +{ + public interface IPluginInfo + { + /// + /// The name of this plugin. Shown in the index page as a link. + /// + string PluginName { get; } + + /// + /// The author of this plugin (shown in the index page). + /// + string PluginAuthor { get; } + + /// + /// The ID/slug for this plugin. Also used as the root for all request handlers provided by this plugin. + /// + string PluginID { get; } + } +} diff --git a/CityWebServer.Extensibility/IRequestHandler.cs b/CityWebServer.Extensibility/IRequestHandler.cs index b2ff843..8d1cf47 100644 --- a/CityWebServer.Extensibility/IRequestHandler.cs +++ b/CityWebServer.Extensibility/IRequestHandler.cs @@ -10,38 +10,15 @@ public interface IRequestHandler { IWebServer Server { get; } - /// - /// Gets a unique identifier for this handler. Only one handler can be loaded with a given identifier. - /// - Guid HandlerID { get; } - - /// - /// Gets the priority of this request handler. A request will be handled by the request handler with the lowest priority. - /// - int Priority { get; } - - /// - /// Gets the display name of this request handler. - /// - String Name { get; } - - /// - /// Gets the author of this request handler. - /// - String Author { get; } - /// /// Gets the absolute path to the main page for this request handler. Your class is responsible for handling requests at this path. /// - /// - /// When set to a value other than null, the Web Server will show this url as a link on the home page. - /// String MainPath { get; } /// /// Returns a value that indicates whether this handler is capable of servicing the given request. /// - Boolean ShouldHandle(HttpListenerRequest request); + Boolean ShouldHandle(HttpListenerRequest request, string slug); /// /// Handles the specified request. The method should not close the stream. diff --git a/CityWebServer.Extensibility/IWebServer.cs b/CityWebServer.Extensibility/IWebServer.cs index 286ce12..2422859 100644 --- a/CityWebServer.Extensibility/IWebServer.cs +++ b/CityWebServer.Extensibility/IWebServer.cs @@ -1,10 +1,15 @@ -namespace CityWebServer.Extensibility +using System; +using System.Collections.Generic; + +namespace CityWebServer.Extensibility { public interface IWebServer { /// /// Gets an array containing all currently registered request handlers. /// - IRequestHandler[] RequestHandlers { get; } + IPluginInfo[] Plugins { get; } + String CityName { get; } + List LogLines { get; } } } \ No newline at end of file diff --git a/CityWebServer.Extensibility/RequestHandlerBase.cs b/CityWebServer.Extensibility/RequestHandlerBase.cs index a09f7ac..38c34f2 100644 --- a/CityWebServer.Extensibility/RequestHandlerBase.cs +++ b/CityWebServer.Extensibility/RequestHandlerBase.cs @@ -22,23 +22,15 @@ protected void OnLogMessage(String message) #endregion ILogAppender Implementation protected readonly IWebServer _server; - protected Guid _handlerID; - protected int _priority; - protected String _name; - protected String _author; protected String _mainPath; private RequestHandlerBase() { } - protected RequestHandlerBase(IWebServer server, Guid handlerID, String name, String author, int priority, String mainPath) + protected RequestHandlerBase(IWebServer server, String mainPath) { _server = server; - _handlerID = handlerID; - _name = name; - _author = author; - _priority = priority; _mainPath = mainPath; } @@ -47,40 +39,17 @@ protected RequestHandlerBase(IWebServer server, Guid handlerID, String name, Str /// public virtual IWebServer Server { get { return _server; } } - /// - /// Gets a unique identifier for this handler. Only one handler can be loaded with a given identifier. - /// - public virtual Guid HandlerID { get { return _handlerID; } } - - /// - /// Gets the priority of this request handler. A request will be handled by the request handler with the lowest priority. - /// - public virtual int Priority { get { return _priority; } } - - /// - /// Gets the display name of this request handler. - /// - public virtual String Name { get { return _name; } } - - /// - /// Gets the author of this request handler. - /// - public virtual String Author { get { return _author; } } - /// /// Gets the absolute path to the main page for this request handler. Your class is responsible for handling requests at this path. /// - /// - /// When set to a value other than null, the Web Server will show this url as a link on the home page. - /// public virtual String MainPath { get { return _mainPath; } } /// /// Returns a value that indicates whether this handler is capable of servicing the given request. /// - public virtual Boolean ShouldHandle(HttpListenerRequest request) + public virtual Boolean ShouldHandle(HttpListenerRequest request, string slug) { - return (request.Url.AbsolutePath.Equals(_mainPath, StringComparison.OrdinalIgnoreCase)); + return (request.Url.AbsolutePath.Equals(String.Format("/{0}{1}", slug, _mainPath), StringComparison.OrdinalIgnoreCase)); } /// diff --git a/CityWebServer.Extensibility/RestfulRequestHandlerBase.cs b/CityWebServer.Extensibility/RestfulRequestHandlerBase.cs index 02f370f..90e42c9 100644 --- a/CityWebServer.Extensibility/RestfulRequestHandlerBase.cs +++ b/CityWebServer.Extensibility/RestfulRequestHandlerBase.cs @@ -5,24 +5,16 @@ namespace CityWebServer.Extensibility { public abstract class RestfulRequestHandlerBase : RequestHandlerBase { - public RestfulRequestHandlerBase(IWebServer server, Guid handlerID, String name, String author, int priority, String mainPath) - : base(server, handlerID, name, author, priority, mainPath) + public RestfulRequestHandlerBase(IWebServer server, String mainPath) + : base(server, mainPath) { } - public override Guid HandlerID { get { return _handlerID; } } - - public override int Priority { get { return _priority; } } - - public override string Name { get { return _name; } } - - public override string Author { get { return _author; } } - public override string MainPath { get { return _mainPath; } } - public override bool ShouldHandle(HttpListenerRequest request) + public override bool ShouldHandle(HttpListenerRequest request, String slug) { - return (request.Url.AbsolutePath.StartsWith(_mainPath, StringComparison.OrdinalIgnoreCase)); + return (request.Url.AbsolutePath.StartsWith(String.Format("/{0}{1}", slug, _mainPath), StringComparison.OrdinalIgnoreCase)); } public override IResponseFormatter Handle(HttpListenerRequest request) diff --git a/CityWebServer/CityWebServer.csproj b/CityWebServer/CityWebServer.csproj index 8abdf1f..f860272 100644 --- a/CityWebServer/CityWebServer.csproj +++ b/CityWebServer/CityWebServer.csproj @@ -63,6 +63,10 @@ + + + + diff --git a/CityWebServer/Helpers/ApacheMimeTypes.cs b/CityWebServer/Helpers/ApacheMimeTypes.cs index 4d807a2..6c5389d 100644 --- a/CityWebServer/Helpers/ApacheMimeTypes.cs +++ b/CityWebServer/Helpers/ApacheMimeTypes.cs @@ -3,7 +3,7 @@ using System.Linq; // Source: https://raw.githubusercontent.com/cymen/ApacheMimeTypesToDotNet/master/ApacheMimeTypes.cs -namespace ApacheMimeTypes +namespace CityWebServer.Helpers { internal class Apache { diff --git a/CityWebServer/Helpers/CityWebPluginInfo.cs b/CityWebServer/Helpers/CityWebPluginInfo.cs new file mode 100644 index 0000000..d00a565 --- /dev/null +++ b/CityWebServer/Helpers/CityWebPluginInfo.cs @@ -0,0 +1,93 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.IO; +using System.Net; +using ColossalFramework.Plugins; +using CityWebServer.Extensibility; + +namespace CityWebServer.Helpers +{ + // This gets encapsulated because I don't want to maintain a reference to either the plugininfo or the plugin instance + // to remove any chance for these references to interfere with the engine's plugin loading/processing chain. + // Plus most of what we need can be figured out once up front anyway. This also allows us to fake entries for core + // things not actually loaded by plugins too. + public class CityWebPluginInfo : IPluginInfo + { + protected string _name = null; + protected string _author = null; + protected List _handlers = null; + protected string _wwwroot = null; + protected string _ID = null; + protected bool _isEnabled = false; + + public string Name { get { return _name; } } + public string PluginName { get { return _name; } } + public string Author { get { return _author; } } + public string PluginAuthor { get { return _author; } } + public string ID { get { return _ID; } } + public string PluginID { get { return _ID; } } + public bool HasStatic { get { return _wwwroot != null; } } + public bool IsEnabled { get { return _isEnabled; } } + public string WebRoot { get { return _wwwroot; } } + public List Handlers { get { return _handlers; } } + + protected CityWebPluginInfo() + { + } + + public CityWebPluginInfo(ICityWebPlugin p, PluginManager.PluginInfo pi, IWebServer server) + { + _ID = p.PluginID.ToLower().Replace(" ", "_"); + _name = p.PluginName; + _author = p.PluginAuthor; + _isEnabled = pi.isEnabled; + if (_isEnabled) + { + string testPath = Path.Combine(pi.modPath, "wwwroot"); + if (Directory.Exists(testPath)) + { + _wwwroot = testPath; + } + List h = p.GetHandlers(server); + if (null != h) + { + _handlers = h; + } + else + { + _handlers = new List(); + } + } + } + + public CityWebPluginInfo(string name, string author, string ID, IEnumerable h) + { + _ID = ID; + _name = name; + _author = author; + _isEnabled = true; + if (null != h) + { + _handlers = new List(h); + } + else + { + _handlers = new List(); + } + } + + public bool HandleRequest(HttpListenerRequest request, HttpListenerResponse response) + { + // string localPath = request.Url.AbsolutePath.Replace(String.Format("/{0}/", _ID), ""); + var handler = _handlers.FirstOrDefault(obj => obj.ShouldHandle(request, _ID)); + if (null == handler) return false; + + IResponseFormatter responseFormatterWriter = handler.Handle(request); + responseFormatterWriter.WriteContent(response); + return true; + } + + } +} diff --git a/CityWebServer/Helpers/TemplateHelper.cs b/CityWebServer/Helpers/TemplateHelper.cs index 1c887f8..87ebb77 100644 --- a/CityWebServer/Helpers/TemplateHelper.cs +++ b/CityWebServer/Helpers/TemplateHelper.cs @@ -78,10 +78,9 @@ public static String PopulateTemplate(String template, Dictionary /// Gets a dictionary that contains standard replacement tokens using the specified values. /// - public static Dictionary GetTokenReplacements(String cityName, String title, List handlers, String body) + public static Dictionary GetTokenReplacements(String cityName, String title, IPluginInfo[] plugins, String body) { - var orderedHandlers = handlers.OrderBy(obj => obj.Priority).ThenBy(obj => obj.Name); - var handlerLinks = orderedHandlers.Select(obj => String.Format("
  • {1}
  • ", obj.MainPath, obj.Name)).ToArray(); + var handlerLinks = plugins.Select(obj => String.Format("
  • {1}
  • ", obj.PluginID, obj.PluginName)).ToArray(); String nav = String.Join(Environment.NewLine, handlerLinks); return new Dictionary diff --git a/CityWebServer/IntegratedWebServer.cs b/CityWebServer/IntegratedWebServer.cs index 711e6cf..a21c6af 100644 --- a/CityWebServer/IntegratedWebServer.cs +++ b/CityWebServer/IntegratedWebServer.cs @@ -4,10 +4,10 @@ using System.Linq; using System.Net; using System.Reflection; -using ApacheMimeTypes; using CityWebServer.Extensibility; using CityWebServer.Extensibility.Responses; using CityWebServer.Helpers; +using CityWebServer.RequestHandlers; using ColossalFramework; using ColossalFramework.Plugins; using ICities; @@ -25,29 +25,10 @@ public class IntegratedWebServer : ThreadingExtensionBase, IWebServer private static string _endpoint; private WebServer _server; - private List _requestHandlers; + private Dictionary _plugins; private String _cityName = "CityName"; - - // Not required, but prevents a number of spurious entries from making it to the log file. - private static readonly List IgnoredAssemblies = new List - { - "Anonymously Hosted DynamicMethods Assembly", - "Assembly-CSharp", - "Assembly-CSharp-firstpass", - "Assembly-UnityScript-firstpass", - "Boo.Lang", - "ColossalManaged", - "ICSharpCode.SharpZipLib", - "ICities", - "Mono.Security", - "mscorlib", - "System", - "System.Configuration", - "System.Core", - "System.Xml", - "UnityEngine", - "UnityEngine.UI", - }; + private string _wwwroot = null; + private bool _secondPass = false; /// /// Gets the root endpoint for which the server is configured to service HTTP requests. @@ -56,33 +37,14 @@ public static String Endpoint { get { return _endpoint; } } - - /// - /// Gets the full path to the directory where static pages are served from. - /// - public static String GetWebRoot() + + public List LogLines { - var modPaths = PluginManager.instance.GetPluginsInfo().Select(obj => obj.modPath); - foreach (var path in modPaths) - { - var testPath = Path.Combine(path, "wwwroot"); - - if (Directory.Exists(testPath)) - { - return testPath; - } - } - return null; - } - - /// - /// Gets an array containing all currently registered request handlers. - /// - public IRequestHandler[] RequestHandlers - { - get { return _requestHandlers.ToArray(); } + get { return _logLines; } } + public String CityName { get { return _cityName; } } + /// /// Initializes a new instance of the class. /// @@ -91,9 +53,6 @@ public IntegratedWebServer() // For the entire lifetime of this instance, we'll preseve log messages. // After a certain point, it might be worth truncating them, but we'll cross that bridge when we get to it. _logLines = new List(); - - // We need a place to store all the request handlers that have been registered. - _requestHandlers = new List(); } #region Create @@ -149,18 +108,8 @@ private void InitializeServer() WebServer ws = new WebServer(HandleRequest, bindings.ToArray()); _server = ws; _server.Run(); + LogMessage("Server Initialized."); - - _requestHandlers = new List(); - - try - { - RegisterHandlers(); - } - catch (Exception ex) - { - UnityEngine.Debug.LogException(ex); - } } #endregion Create @@ -175,7 +124,7 @@ public override void OnReleased() ReleaseServer(); // TODO: Unregister from events (i.e. ILogAppender.LogMessage) - _requestHandlers.Clear(); + _plugins.Clear(); Configuration.SaveSettings(); @@ -196,6 +145,43 @@ private void ReleaseServer() #endregion Release + /// ; + /// Performs init tasks on first request. + /// + /// + /// Certain plugin init processes must take place after all plugins have loaded. This defers these tasks + /// until the first request arrives at the server, which should be after the plugin stack is done. + /// + private void SecondPassInit() + { + LogMessage("Second pass initialization..."); + try + { + var simulationManager = Singleton.instance; + _cityName = simulationManager.m_metaData.m_CityName; + } + catch (Exception ex) + { + LogMessage(String.Format("failed to get city name: {0}", ex)); + return; + } + LogMessage(String.Format("set city name: {0}", _cityName)); + + try + { + RegisterPlugins(); + } + catch (Exception ex) + { + UnityEngine.Debug.LogException(ex); + LogMessage(String.Format("failed to register plugins: {0}", ex)); + return; + } + + _secondPass = true; + LogMessage("Second pass complete."); + } + /// ; /// Handles the specified request. /// @@ -206,37 +192,38 @@ private void ReleaseServer() private void HandleRequest(HttpListenerRequest request, HttpListenerResponse response) { LogMessage(String.Format("{0} {1}", request.HttpMethod, request.RawUrl)); - - var simulationManager = Singleton.instance; - _cityName = simulationManager.m_metaData.m_CityName; - - // There are two reserved endpoints: "/" and "/Log". - // These take precedence over all other request handlers. - if (ServiceRoot(request, response)) + if (!_secondPass) { - return; + SecondPassInit(); } - if (ServiceLog(request, response)) + // Get the request handler associated with the current request. + string url = request.Url.AbsolutePath; + string slug = null; + String wwwroot = _wwwroot; + bool handled = false; + if (url.IsNullOrWhiteSpace() || url.Equals("/")) { - return; + slug = "root"; + } + else if (url.StartsWith("/")) + { + slug = url.Split('/')[1]; } - // Get the request handler associated with the current request. - var handler = _requestHandlers.FirstOrDefault(obj => obj.ShouldHandle(request)); - if (handler != null) + if (slug != null && _plugins.ContainsKey(slug)) { + CityWebPluginInfo cwpi; + _plugins.TryGetValue(slug, out cwpi); + wwwroot = cwpi.WebRoot; try { - IResponseFormatter responseFormatterWriter = handler.Handle(request); - responseFormatterWriter.WriteContent(response); - - return; + handled = cwpi.HandleRequest(request, response); } catch (Exception ex) { String errorBody = String.Format("

    An error has occurred!

    {0}
    ", ex); - var tokens = TemplateHelper.GetTokenReplacements(_cityName, "Error", _requestHandlers, errorBody); + var tokens = TemplateHelper.GetTokenReplacements(_cityName, "Error", this.Plugins, errorBody); var template = TemplateHelper.PopulateTemplate("index", tokens); IResponseFormatter errorResponseFormatter = new HtmlResponseFormatter(template); @@ -246,221 +233,165 @@ private void HandleRequest(HttpListenerRequest request, HttpListenerResponse res } } - var wwwroot = GetWebRoot(); + if (handled) return; // something handled it, great // At this point, we can guarantee that we don't need any game data, so we can safely start a new thread to perform the remaining tasks. - ServiceFileRequest(wwwroot, request, response); + if (ServiceFileRequest(wwwroot, request, response)) return; // check for static files + + String body = String.Format("No resource is available at the specified filepath: {0}", url); + IResponseFormatter notFoundResponseFormatter = new PlainTextResponseFormatter(body, HttpStatusCode.NotFound); + notFoundResponseFormatter.WriteContent(response); + return; } + + private static bool ServiceFileRequest(string wwwroot, HttpListenerRequest request, HttpListenerResponse response) { + if (null == wwwroot || wwwroot.IsNullOrWhiteSpace()) return false; - private static void ServiceFileRequest(String wwwroot, HttpListenerRequest request, HttpListenerResponse response) - { var relativePath = request.Url.AbsolutePath.Substring(1); relativePath = relativePath.Replace("/", Path.DirectorySeparatorChar.ToString()); var absolutePath = Path.Combine(wwwroot, relativePath); - if (File.Exists(absolutePath)) - { - var extension = Path.GetExtension(absolutePath); - response.ContentType = Apache.GetMime(extension); - response.StatusCode = 200; // HTTP 200 - SUCCESS + if (!File.Exists(absolutePath)) return false; + + var extension = Path.GetExtension(absolutePath); + response.ContentType = Apache.GetMime(extension); + response.StatusCode = 200; // HTTP 200 - SUCCESS - // Open file, read bytes into buffer and write them to the output stream. - using (FileStream fileReader = File.OpenRead(absolutePath)) + // Open file, read bytes into buffer and write them to the output stream. + using (FileStream fileReader = File.OpenRead(absolutePath)) + { + byte[] buffer = new byte[4096]; + int read; + while ((read = fileReader.Read(buffer, 0, buffer.Length)) > 0) { - byte[] buffer = new byte[4096]; - int read; - while ((read = fileReader.Read(buffer, 0, buffer.Length)) > 0) - { - response.OutputStream.Write(buffer, 0, read); - } + response.OutputStream.Write(buffer, 0, read); } } - else - { - String body = String.Format("No resource is available at the specified filepath: {0}", absolutePath); - IResponseFormatter notFoundResponseFormatter = new PlainTextResponseFormatter(body, HttpStatusCode.NotFound); - notFoundResponseFormatter.WriteContent(response); - } + return true; } /// - /// Searches all the assemblies in the current AppDomain for class definitions that implement the interface. Those classes are instantiated and registered as request handlers. + /// Searches all the plugins in the PluginManager for ones that implement . /// - private void RegisterHandlers() + private void RegisterPlugins() { - IEnumerable handlers = FindHandlersInLoadedAssemblies(); - RegisterHandlers(handlers); - } - - private void RegisterHandlers(IEnumerable handlers) - { - if (handlers == null) { return; } - - if (_requestHandlers == null) + if (null != _plugins) { - _requestHandlers = new List(); + _plugins.Clear(); + _plugins = null; } - foreach (var handler in handlers) - { - // Only register handlers that we don't already have an instance of. - if (_requestHandlers.Any(h => h.GetType() == handler)) - { - continue; - } + List plugins = new List(PluginManager.instance.GetPluginsInfo()); - IRequestHandler handlerInstance = null; - Boolean exists = false; + for (int i = 0; i < plugins.Count; i++) + { + PluginManager.PluginInfo pi = plugins.ElementAt(i); + if (!pi.isEnabled || pi.isBuiltin) continue; + // get this plugin instance as a standard IUserMod + IUserMod[] minsts = null; try { - if (typeof(RequestHandlerBase).IsAssignableFrom(handler)) - { - handlerInstance = (RequestHandlerBase)Activator.CreateInstance(handler, this); - } - else - { - handlerInstance = (IRequestHandler)Activator.CreateInstance(handler); - } - - if (handlerInstance == null) - { - LogMessage(String.Format("Request Handler ({0}) could not be instantiated!", handler.Name)); - continue; - } - - // Duplicates handlers seem to pass the check above, so now we filter them based on their identifier values, which should work. - exists = _requestHandlers.Any(obj => obj.HandlerID == handlerInstance.HandlerID); + minsts = pi.GetInstances(); } catch (Exception ex) { - LogMessage(ex.ToString()); - } - - if (exists) - { - // TODO: Allow duplicate registrations to occur; previous registration is removed and replaced with a new one? - LogMessage(String.Format("Supressing duplicate handler registration for '{0}'", handler.Name)); + LogMessage(String.Format("IUserMod cast error: {0}", ex)); + continue; } - else + if (null == minsts || minsts.Length < 1) continue; + if (minsts[0].Name.Equals("Integrated Web Server")) { - _requestHandlers.Add(handlerInstance); - if (handlerInstance is ILogAppender) + // hey it's me! get our web root + string testPath = Path.Combine(pi.modPath, "wwwroot"); + if (Directory.Exists(testPath)) { - var logAppender = (handlerInstance as ILogAppender); - logAppender.LogMessage += RequestHandlerLogAppender_OnLogMessage; + LogMessage(String.Format("Setting server wwwroot location: {0}", testPath)); + _wwwroot = testPath; } - - LogMessage(String.Format("Added Request Handler: {0}", handler.FullName)); + break; } } - } - - private void RequestHandlerLogAppender_OnLogMessage(object sender, LogAppenderEventArgs logAppenderEventArgs) - { - var senderTypeName = sender.GetType().Name; - LogMessage(logAppenderEventArgs.LogLine, senderTypeName, false); - } - /// - /// Searches all the assemblies in the current AppDomain, and returns a collection of those that implement the interface. - /// - private static IEnumerable FindHandlersInLoadedAssemblies() - { - var assemblies = AppDomain.CurrentDomain.GetAssemblies(); + _plugins = new Dictionary(); + DefaultPlugins(); // register the default fake plugins - foreach (var assembly in assemblies) + LogMessage("Looking for CityWeb plugins..."); + for (int i = 0; i < plugins.Count; i++) { - var handlers = FetchHandlers(assembly); - foreach (var handler in handlers) + PluginManager.PluginInfo pi = plugins.ElementAt(i); + if (!pi.isEnabled || pi.isBuiltin) continue; + + // get this plugin instance as a ICityWebPlugin + ICityWebPlugin[] insts = null; + try { - yield return handler; + insts = pi.GetInstances(); } - } - } - - private static IEnumerable FetchHandlers(Assembly assembly) - { - var assemblyName = assembly.GetName().Name; - - // Skip any assemblies that we don't anticipate finding anything in. - if (IgnoredAssemblies.Contains(assemblyName)) { yield break; } - - Type[] types = new Type[0]; - try - { - types = assembly.GetTypes(); - } - catch { } + catch (Exception ex) + { + LogMessage(String.Format("ICityWebPlugin cast error: {0}", ex)); + continue; + } + if (null == insts || insts.Length < 1) continue; + LogMessage(String.Format("Loading plugin \"{0}\"", insts[0].PluginName)); - foreach (var type in types) - { - Boolean isValid = false; + CityWebPluginInfo cwpi = null; try { - isValid = typeof(IRequestHandler).IsAssignableFrom(type) && type.IsClass && !type.IsAbstract; + cwpi = new CityWebPluginInfo(insts[0], pi, this); } - catch { } - - if (isValid) + catch (Exception ex) { - yield return type; + LogMessage(String.Format("Failed to load plugin: \"{0}\"", ex)); + continue; } - } - } - - #region Reserved Endpoint Handlers + if (!cwpi.IsEnabled) continue; - /// - /// Services requests to ~/ - /// - private Boolean ServiceRoot(HttpListenerRequest request, HttpListenerResponse response) - { - if (request.Url.AbsolutePath.ToLower() == "/") - { - List links = new List(); - foreach (var requestHandler in this._requestHandlers.OrderBy(obj => obj.Priority)) + string slug = cwpi.ID; + if (slug == null || slug.IsNullOrWhiteSpace()) { - links.Add(String.Format("
  • {0} by {2} (Priority: {3})
  • ", requestHandler.Name, requestHandler.MainPath, requestHandler.Author, requestHandler.Priority)); + LogMessage(String.Format("Invalid plugin \"{0}\" by \"{1}\"", cwpi.Name, cwpi.Author)); } - - String body = String.Format("

    Cities: Skylines - Integrated Web Server

      {0}
    ", String.Join("", links.ToArray())); - var tokens = TemplateHelper.GetTokenReplacements(_cityName, "Home", _requestHandlers, body); - var template = TemplateHelper.PopulateTemplate("index", tokens); - - IResponseFormatter htmlResponseFormatter = new HtmlResponseFormatter(template); - htmlResponseFormatter.WriteContent(response); - - return true; - } - - return false; - } - - /// - /// Services requests to ~/Log - /// - private Boolean ServiceLog(HttpListenerRequest request, HttpListenerResponse response) - { - if (request.Url.AbsolutePath.ToLower() == "/log") - { + else if (_plugins.ContainsKey(slug)) { - String body = String.Format("

    Server Log

    {0}
    ", String.Join("", _logLines.ToArray())); - var tokens = TemplateHelper.GetTokenReplacements(_cityName, "Log", _requestHandlers, body); - var template = TemplateHelper.PopulateTemplate("index", tokens); - - IResponseFormatter htmlResponseFormatter = new HtmlResponseFormatter(template); - htmlResponseFormatter.WriteContent(response); - - return true; + LogMessage(String.Format("Conflicting plugin; ID already exists! {0}:\"{1}\" by \"{2}\"", slug, cwpi.Name, cwpi.Author)); + } + else + { + LogMessage(String.Format("Loaded plugin {0}:\"{1}\" by \"{2}\"", slug, cwpi.Name, cwpi.Author)); + _plugins.Add(slug, cwpi); + if (cwpi.Handlers != null) + { + for (int j = 0; j < cwpi.Handlers.Count; j++) + { + if (cwpi.Handlers.ElementAt(j) is ILogAppender) + { + ILogAppender la = (cwpi.Handlers.ElementAt(j) as ILogAppender); + la.LogMessage += RequestHandlerLogAppender_OnLogMessage; + } + } + } } } + } - return false; + public IPluginInfo[] Plugins { get { return _plugins.Values.ToArray(); } } + + private void RequestHandlerLogAppender_OnLogMessage(object sender, LogAppenderEventArgs logAppenderEventArgs) + { + var senderTypeName = sender.GetType().Name; + LogMessage(logAppenderEventArgs.LogLine, senderTypeName, false); } - #endregion Reserved Endpoint Handlers + private void DefaultPlugins() + { + LogMessage("adding default plugins"); + _plugins.Add("root", new RootPluginInfo(this)); + _plugins.Add("log", new LogPluginInfo(this)); + _plugins.Add("api", new APIPluginInfo(this)); + } #region Logging diff --git a/CityWebServer/RequestHandlers/APIPluginInfo.cs b/CityWebServer/RequestHandlers/APIPluginInfo.cs new file mode 100644 index 0000000..c085d71 --- /dev/null +++ b/CityWebServer/RequestHandlers/APIPluginInfo.cs @@ -0,0 +1,33 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.IO; +using System.Net; +using ColossalFramework.Plugins; +using CityWebServer.Helpers; +using CityWebServer.Extensibility; +using CityWebServer.Extensibility.Responses; + +namespace CityWebServer.RequestHandlers +{ + public class APIPluginInfo : CityWebPluginInfo + { + public APIPluginInfo(IWebServer server) + { + _ID = "api"; + _name = "JSON API"; + _author = "Rychard"; + _isEnabled = true; + _wwwroot = null; + + _handlers = new List(); + _handlers.Add(new BudgetRequestHandler(server)); + _handlers.Add(new BuildingRequestHandler(server)); + _handlers.Add(new CityInfoRequestHandler(server)); + _handlers.Add(new MessageRequestHandler(server)); + _handlers.Add(new TransportRequestHandler(server)); + _handlers.Add(new VehicleRequestHandler(server)); + } + } +} diff --git a/CityWebServer/RequestHandlers/BudgetRequestHandler.cs b/CityWebServer/RequestHandlers/BudgetRequestHandler.cs index 80425b0..56802d1 100644 --- a/CityWebServer/RequestHandlers/BudgetRequestHandler.cs +++ b/CityWebServer/RequestHandlers/BudgetRequestHandler.cs @@ -9,7 +9,7 @@ namespace CityWebServer.RequestHandlers public class BudgetRequestHandler : RequestHandlerBase { public BudgetRequestHandler(IWebServer server) - : base(server, new Guid("87205a0d-1b53-47bd-91fa-9cddf0a3bd9e"), "Budget", "Rychard", 100, "/Budget") + : base(server, "/Budget") { } diff --git a/CityWebServer/RequestHandlers/BuildingRequestHandler.cs b/CityWebServer/RequestHandlers/BuildingRequestHandler.cs index c203aec..465667f 100644 --- a/CityWebServer/RequestHandlers/BuildingRequestHandler.cs +++ b/CityWebServer/RequestHandlers/BuildingRequestHandler.cs @@ -9,7 +9,7 @@ namespace CityWebServer.RequestHandlers public class BuildingRequestHandler : RequestHandlerBase { public BuildingRequestHandler(IWebServer server) - : base(server, new Guid("03897cb0-d53f-4189-a613-e7d22705dc2f"), "Building", "Rychard", 100, "/Building") + : base(server, "/Building") { } diff --git a/CityWebServer/RequestHandlers/CityInfoRequestHandler.cs b/CityWebServer/RequestHandlers/CityInfoRequestHandler.cs index 93228d8..79ffbdb 100644 --- a/CityWebServer/RequestHandlers/CityInfoRequestHandler.cs +++ b/CityWebServer/RequestHandlers/CityInfoRequestHandler.cs @@ -14,7 +14,7 @@ namespace CityWebServer.RequestHandlers public class CityInfoRequestHandler : RequestHandlerBase { public CityInfoRequestHandler(IWebServer server) - : base(server, new Guid("eeada0d0-f1d2-43b0-9595-2a6a4d917631"), "City Info", "Rychard", 100, "/CityInfo") + : base(server, "/CityInfo") { } diff --git a/CityWebServer/RequestHandlers/LogPluginInfo.cs b/CityWebServer/RequestHandlers/LogPluginInfo.cs new file mode 100644 index 0000000..abfb352 --- /dev/null +++ b/CityWebServer/RequestHandlers/LogPluginInfo.cs @@ -0,0 +1,46 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.IO; +using System.Net; +using ColossalFramework.Plugins; +using CityWebServer.Helpers; +using CityWebServer.Extensibility; +using CityWebServer.Extensibility.Responses; + +namespace CityWebServer.RequestHandlers +{ + public class LogPluginInfo : CityWebPluginInfo + { + public LogPluginInfo(IWebServer server) + { + _ID = "log"; + _name = "System Log"; + _author = "Rychard"; + _isEnabled = true; + _wwwroot = null; + + _handlers = new List(); + _handlers.Add(new LogRequestHandler(server)); + } + + private class LogRequestHandler : RequestHandlerBase + { + public LogRequestHandler(IWebServer server) + : base(server, "/") + { + } + + public override IResponseFormatter Handle(HttpListenerRequest request) + { + String body = String.Format("

    Server Log

    {0}
    ", String.Join("", _server.LogLines.ToArray())); + var tokens = TemplateHelper.GetTokenReplacements(_server.CityName, "Log", _server.Plugins, body); + var template = TemplateHelper.PopulateTemplate("index", tokens); + + return new HtmlResponseFormatter(template); + } + } + + } +} diff --git a/CityWebServer/RequestHandlers/MessageRequestHandler.cs b/CityWebServer/RequestHandlers/MessageRequestHandler.cs index 1fa060b..3c91e0e 100644 --- a/CityWebServer/RequestHandlers/MessageRequestHandler.cs +++ b/CityWebServer/RequestHandlers/MessageRequestHandler.cs @@ -12,7 +12,7 @@ public class MessageRequestHandler : RequestHandlerBase private readonly ChirpRetriever _chirpRetriever; public MessageRequestHandler(IWebServer server) - : base(server, new Guid("b4efeced-1dbb-435a-8999-9f8adaa5036e"), "Chirper Messages", "Rychard", 100, "/Messages") + : base(server, "/Messages") { _chirpRetriever = new ChirpRetriever(); _chirpRetriever.LogMessage += (sender, args) => { OnLogMessage(args.LogLine); }; diff --git a/CityWebServer/RequestHandlers/RootPluginInfo.cs b/CityWebServer/RequestHandlers/RootPluginInfo.cs new file mode 100644 index 0000000..f81210d --- /dev/null +++ b/CityWebServer/RequestHandlers/RootPluginInfo.cs @@ -0,0 +1,59 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.IO; +using System.Net; +using ColossalFramework.Plugins; +using CityWebServer.Helpers; +using CityWebServer.Extensibility; +using CityWebServer.Extensibility.Responses; + +namespace CityWebServer.RequestHandlers +{ + public class RootPluginInfo : CityWebPluginInfo + { + public RootPluginInfo(IWebServer server) + { + _ID = "root"; + _name = "Index Page Listing"; + _author = "Rychard"; + _isEnabled = true; + _wwwroot = null; + + _handlers = new List(); + _handlers.Add(new RootRequestHandler(server)); + } + + private class RootRequestHandler : RequestHandlerBase + { + public RootRequestHandler(IWebServer server) + : base(server, "/") + { + } + + public override IResponseFormatter Handle(HttpListenerRequest request) + { + List links = new List(); + foreach (var plugin in _server.Plugins.OrderBy(obj => obj.PluginID)) + { + if (!plugin.PluginID.Equals("root")) { + links.Add(String.Format("
  • {0} by {2}
  • ", plugin.PluginName, plugin.PluginID, plugin.PluginAuthor)); + } + } + + String body = String.Format("

    Cities: Skylines - Integrated Web Server

      {0}
    ", String.Join("", links.ToArray())); + var tokens = TemplateHelper.GetTokenReplacements(_server.CityName, "Home", _server.Plugins, body); + var template = TemplateHelper.PopulateTemplate("index", tokens); + + return new HtmlResponseFormatter(template); + } + + public override Boolean ShouldHandle(HttpListenerRequest request, string slug) + { + string url = request.Url.AbsolutePath; + return (null != url && (url.Equals("") || url.Equals("/"))); + } + } + } +} diff --git a/CityWebServer/RequestHandlers/TransportRequestHandler.cs b/CityWebServer/RequestHandlers/TransportRequestHandler.cs index 42bc11b..49818b5 100644 --- a/CityWebServer/RequestHandlers/TransportRequestHandler.cs +++ b/CityWebServer/RequestHandlers/TransportRequestHandler.cs @@ -13,7 +13,7 @@ namespace CityWebServer.RequestHandlers public class TransportRequestHandler : RequestHandlerBase { public TransportRequestHandler(IWebServer server) - : base(server, new Guid("89c8ef27-fc8c-4fe8-9793-1f6432feb179"), "Transport", "Rychard", 100, "/Transport") + : base(server, "/Transport") { } diff --git a/CityWebServer/RequestHandlers/VehicleRequestHandler.cs b/CityWebServer/RequestHandlers/VehicleRequestHandler.cs index 8dc5278..b62be0b 100644 --- a/CityWebServer/RequestHandlers/VehicleRequestHandler.cs +++ b/CityWebServer/RequestHandlers/VehicleRequestHandler.cs @@ -12,7 +12,7 @@ namespace CityWebServer.RequestHandlers public class VehicleRequestHandler : RequestHandlerBase { public VehicleRequestHandler(IWebServer server) - : base(server, new Guid("2be6546a-d416-4939-8e08-1d0b739be835"), "Vehicle", "Rychard", 100, "/Vehicle") + : base(server, "/Vehicle") { } diff --git a/CityWebServer/wwwroot/index.html b/CityWebServer/wwwroot/index.html index ebe0dd3..f96bfe9 100644 --- a/CityWebServer/wwwroot/index.html +++ b/CityWebServer/wwwroot/index.html @@ -87,7 +87,7 @@

    Cities: Skylines - Integrated Web Server

    - - - - - + + + + + + + + + + \ No newline at end of file From d61a97a37e05f779f967b6c6442667c0732bd118 Mon Sep 17 00:00:00 2001 From: nezroy Date: Wed, 8 Apr 2015 16:51:09 -0600 Subject: [PATCH 05/14] update plugin loading to be more tolerant of items not yet ready --- CityWebServer/CityWebServer.csproj | 2 + CityWebServer/Helpers/CityWebPlugin.cs | 72 ++++++++++++ CityWebServer/Helpers/UserMod.cs | 55 +++++++++ CityWebServer/IntegratedWebServer.cs | 149 ++++++++++++------------- 4 files changed, 201 insertions(+), 77 deletions(-) create mode 100644 CityWebServer/Helpers/CityWebPlugin.cs create mode 100644 CityWebServer/Helpers/UserMod.cs diff --git a/CityWebServer/CityWebServer.csproj b/CityWebServer/CityWebServer.csproj index f860272..9e12e7d 100644 --- a/CityWebServer/CityWebServer.csproj +++ b/CityWebServer/CityWebServer.csproj @@ -63,6 +63,8 @@ + + diff --git a/CityWebServer/Helpers/CityWebPlugin.cs b/CityWebServer/Helpers/CityWebPlugin.cs new file mode 100644 index 0000000..7be9f6b --- /dev/null +++ b/CityWebServer/Helpers/CityWebPlugin.cs @@ -0,0 +1,72 @@ +using System; +using System.Collections.Generic; +using System.Text; +using System.Reflection; +using CityWebServer.Extensibility; + +namespace CityWebServer.Helpers +{ + public class CityWebPlugin : ICityWebPlugin + { + private string _name = null; + private string _author = null; + private string _ID = null; + private bool _topMenu = false; + List _handlers = null; + + public string PluginName { get { return _name; } } + public string PluginAuthor { get { return _author; } } + public string PluginID { get { return _ID; } } + public bool TopMenu { get { return _topMenu; } } + + public List GetHandlers(IWebServer server) + { + return _handlers; + } + + public static CityWebPlugin CreateByReflection(UserMod um, IWebServer server) + { + // just casting um.Mod to ICityWebPlugin is not reliable; somtimes works, sometimes not + // instead we reflect through methods to identify valid plugins + Type ut = um.Mod.GetType(); + MethodInfo[] mia = ut.GetMethods(); + bool isCityWebPlugin = false; + for (int i = 0; i < mia.Length; i++) + { + MethodInfo mi = mia[i]; + if (mi.Name.Equals("GetHandlers") && mi.ReturnType == typeof(List)) + { + isCityWebPlugin = true; + break; + } + } + + if (!isCityWebPlugin) return null; + + CityWebPlugin cwp = new CityWebPlugin(); + + // since casting doesn't work reliably, we need to fill out a placeholder class with source + // values by reflection as well + PropertyInfo[] pia = ut.GetProperties(); + for (int i = 0; i < pia.Length; i++) + { + PropertyInfo pi = pia[i]; + if (pi.Name.Equals("PluginName")) cwp._name = (string)pi.GetValue(um.Mod, null); + else if (pi.Name.Equals("PluginAuthor")) cwp._author = (string)pi.GetValue(um.Mod, null); + else if (pi.Name.Equals("PluginID")) cwp._ID = (string)pi.GetValue(um.Mod, null); + else if (pi.Name.Equals("TopMenu")) cwp._topMenu = (bool)pi.GetValue(um.Mod, null); + } + + for (int i = 0; i < mia.Length; i++) + { + MethodInfo mi = mia[i]; + if (mi.Name.Equals("GetHandlers") && mi.ReturnType == typeof(List)) + { + cwp._handlers = (List)mi.Invoke(um.Mod, new Object[1]{ server }); + } + } + + return cwp; + } + } +} diff --git a/CityWebServer/Helpers/UserMod.cs b/CityWebServer/Helpers/UserMod.cs new file mode 100644 index 0000000..4f8d482 --- /dev/null +++ b/CityWebServer/Helpers/UserMod.cs @@ -0,0 +1,55 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using ICities; +using ColossalFramework; +using ColossalFramework.Plugins; + +namespace CityWebServer.Helpers +{ + public class UserMod + { + private IUserMod _umod = null; + private PluginManager.PluginInfo _pi = null; + + public IUserMod Mod { get { return _umod; } } + public PluginManager.PluginInfo PluginInfo { get { return _pi; } } + + UserMod(PluginManager.PluginInfo pi) + { + _pi = pi; + IUserMod[] insts = null; + try + { + insts = pi.GetInstances(); + } + catch + { + } + if (null != insts && insts.Length > 0) + { + _umod = insts[0]; + } + } + + /// + /// Static factory to collect mods from the PluginManager into a list of ModInfo's + /// + public static List CollectPlugins() + { + List miList = new List(); + PluginManager pm = Singleton.instance; + if (null == pm) return miList; + + List piList = new List(pm.GetPluginsInfo()); + foreach (PluginManager.PluginInfo pi in piList) + { + miList.Add(new UserMod(pi)); + } + + return miList; + } + + } +} diff --git a/CityWebServer/IntegratedWebServer.cs b/CityWebServer/IntegratedWebServer.cs index a21c6af..e841a66 100644 --- a/CityWebServer/IntegratedWebServer.cs +++ b/CityWebServer/IntegratedWebServer.cs @@ -29,21 +29,19 @@ public class IntegratedWebServer : ThreadingExtensionBase, IWebServer private String _cityName = "CityName"; private string _wwwroot = null; private bool _secondPass = false; + private bool _pluginPass = false; + private List _mods = null; /// /// Gets the root endpoint for which the server is configured to service HTTP requests. /// - public static String Endpoint - { - get { return _endpoint; } - } + public static String Endpoint { get { return _endpoint; } } - public List LogLines - { - get { return _logLines; } - } + public List LogLines { get { return _logLines; } } public String CityName { get { return _cityName; } } + + public String WebRoot { get { return _wwwroot; } } /// /// Initializes a new instance of the class. @@ -124,7 +122,11 @@ public override void OnReleased() ReleaseServer(); // TODO: Unregister from events (i.e. ILogAppender.LogMessage) - _plugins.Clear(); + if (null != _plugins) + { + _plugins.Clear(); + _plugins = null; + } Configuration.SaveSettings(); @@ -157,8 +159,16 @@ private void SecondPassInit() LogMessage("Second pass initialization..."); try { - var simulationManager = Singleton.instance; - _cityName = simulationManager.m_metaData.m_CityName; + SimulationManager sm = Singleton.instance; + if (null != sm) + { + _cityName = sm.m_metaData.m_CityName; + } + else + { + LogMessage(String.Format("failed to get city name: null SimulationManager")); + _cityName = "foo"; + } } catch (Exception ex) { @@ -169,7 +179,8 @@ private void SecondPassInit() try { - RegisterPlugins(); + _mods = UserMod.CollectPlugins(); + RegisterDefaultPlugins(); } catch (Exception ex) { @@ -192,10 +203,17 @@ private void SecondPassInit() private void HandleRequest(HttpListenerRequest request, HttpListenerResponse response) { LogMessage(String.Format("{0} {1}", request.HttpMethod, request.RawUrl)); - if (!_secondPass) + if (!_secondPass) SecondPassInit(); + if (!_pluginPass) RegisterPlugins(); + /* { - SecondPassInit(); + RegisterPlugins(); + if (!_pluginPass) + { + LogMessage("failed to properly scan plugins; will try again next request"); + } } + * */ // Get the request handler associated with the current request. string url = request.Url.AbsolutePath; @@ -224,7 +242,7 @@ private void HandleRequest(HttpListenerRequest request, HttpListenerResponse res { String errorBody = String.Format("

    An error has occurred!

    {0}
    ", ex); var tokens = TemplateHelper.GetTokenReplacements(_cityName, "Error", this.Plugins, errorBody); - var template = TemplateHelper.PopulateTemplate("index", tokens); + var template = TemplateHelper.PopulateTemplate("index", _wwwroot, tokens); IResponseFormatter errorResponseFormatter = new HtmlResponseFormatter(template); errorResponseFormatter.WriteContent(response); @@ -276,78 +294,27 @@ private static bool ServiceFileRequest(string wwwroot, HttpListenerRequest reque ///
    private void RegisterPlugins() { - if (null != _plugins) - { - _plugins.Clear(); - _plugins = null; - } - - List plugins = new List(PluginManager.instance.GetPluginsInfo()); - - for (int i = 0; i < plugins.Count; i++) - { - PluginManager.PluginInfo pi = plugins.ElementAt(i); - if (!pi.isEnabled || pi.isBuiltin) continue; - - // get this plugin instance as a standard IUserMod - IUserMod[] minsts = null; - try - { - minsts = pi.GetInstances(); - } - catch (Exception ex) - { - LogMessage(String.Format("IUserMod cast error: {0}", ex)); - continue; - } - if (null == minsts || minsts.Length < 1) continue; - if (minsts[0].Name.Equals("Integrated Web Server")) - { - // hey it's me! get our web root - string testPath = Path.Combine(pi.modPath, "wwwroot"); - if (Directory.Exists(testPath)) - { - LogMessage(String.Format("Setting server wwwroot location: {0}", testPath)); - _wwwroot = testPath; - } - break; - } - } + LogMessage("Looking for CityWeb plugins..."); + foreach (UserMod um in _mods) { + if (!um.PluginInfo.isEnabled || um.PluginInfo.isBuiltin) continue; + if (null == um.Mod) continue; - _plugins = new Dictionary(); - DefaultPlugins(); // register the default fake plugins + LogMessage(String.Format("scanning {0} <{1}>...", um.PluginInfo.name, um.Mod.GetType())); + CityWebPlugin cwp = CityWebPlugin.CreateByReflection(um, this); + if (null == cwp) continue; - LogMessage("Looking for CityWeb plugins..."); - for (int i = 0; i < plugins.Count; i++) - { - PluginManager.PluginInfo pi = plugins.ElementAt(i); - if (!pi.isEnabled || pi.isBuiltin) continue; - - // get this plugin instance as a ICityWebPlugin - ICityWebPlugin[] insts = null; - try - { - insts = pi.GetInstances(); - } - catch (Exception ex) - { - LogMessage(String.Format("ICityWebPlugin cast error: {0}", ex)); - continue; - } - if (null == insts || insts.Length < 1) continue; - LogMessage(String.Format("Loading plugin \"{0}\"", insts[0].PluginName)); + LogMessage(String.Format("Loading plugin \"{0}\"", cwp.PluginName)); CityWebPluginInfo cwpi = null; try { - cwpi = new CityWebPluginInfo(insts[0], pi, this); + cwpi = new CityWebPluginInfo(cwp, um.PluginInfo, this); } catch (Exception ex) { LogMessage(String.Format("Failed to load plugin: \"{0}\"", ex)); continue; } - if (!cwpi.IsEnabled) continue; string slug = cwpi.ID; if (slug == null || slug.IsNullOrWhiteSpace()) @@ -375,6 +342,8 @@ private void RegisterPlugins() } } } + + _pluginPass = true; } public IPluginInfo[] Plugins { get { return _plugins.Values.ToArray(); } } @@ -384,10 +353,36 @@ private void RequestHandlerLogAppender_OnLogMessage(object sender, LogAppenderEv var senderTypeName = sender.GetType().Name; LogMessage(logAppenderEventArgs.LogLine, senderTypeName, false); } - - private void DefaultPlugins() + + /// + /// Does some initial setup dependent on scanning plugins and also default plugin registration. + /// + private void RegisterDefaultPlugins() { + if (null != _plugins) + { + _plugins.Clear(); + _plugins = null; + } + foreach (UserMod um in _mods) { + if (!um.PluginInfo.isEnabled || um.PluginInfo.isBuiltin) continue; + if (null == um.Mod) continue; + // ICityWebPlugin foo = minsts[0] as ICityWebPlugin; + if (um.Mod.Name.Equals("Integrated Web Server")) + { + // hey it's me! get our web root + string testPath = Path.Combine(um.PluginInfo.modPath, "wwwroot"); + if (Directory.Exists(testPath)) + { + LogMessage(String.Format("Setting server wwwroot location: {0}", testPath)); + _wwwroot = testPath; + } + break; + } + } + LogMessage("adding default plugins"); + _plugins = new Dictionary(); _plugins.Add("root", new RootPluginInfo(this)); _plugins.Add("log", new LogPluginInfo(this)); _plugins.Add("api", new APIPluginInfo(this)); From 7c9df85d6b415fedcbd0a26dacb7ef382c9f8202 Mon Sep 17 00:00:00 2001 From: nezroy Date: Wed, 8 Apr 2015 22:45:03 -0600 Subject: [PATCH 06/14] fix static file path check for plugins --- CityWebServer/IntegratedWebServer.cs | 24 +++++++++++------------- 1 file changed, 11 insertions(+), 13 deletions(-) diff --git a/CityWebServer/IntegratedWebServer.cs b/CityWebServer/IntegratedWebServer.cs index e841a66..c2d66d7 100644 --- a/CityWebServer/IntegratedWebServer.cs +++ b/CityWebServer/IntegratedWebServer.cs @@ -205,15 +205,6 @@ private void HandleRequest(HttpListenerRequest request, HttpListenerResponse res LogMessage(String.Format("{0} {1}", request.HttpMethod, request.RawUrl)); if (!_secondPass) SecondPassInit(); if (!_pluginPass) RegisterPlugins(); - /* - { - RegisterPlugins(); - if (!_pluginPass) - { - LogMessage("failed to properly scan plugins; will try again next request"); - } - } - * */ // Get the request handler associated with the current request. string url = request.Url.AbsolutePath; @@ -254,7 +245,7 @@ private void HandleRequest(HttpListenerRequest request, HttpListenerResponse res if (handled) return; // something handled it, great // At this point, we can guarantee that we don't need any game data, so we can safely start a new thread to perform the remaining tasks. - if (ServiceFileRequest(wwwroot, request, response)) return; // check for static files + if (ServiceFileRequest(wwwroot, request, response, slug)) return; // check for static files String body = String.Format("No resource is available at the specified filepath: {0}", url); IResponseFormatter notFoundResponseFormatter = new PlainTextResponseFormatter(body, HttpStatusCode.NotFound); @@ -262,13 +253,20 @@ private void HandleRequest(HttpListenerRequest request, HttpListenerResponse res return; } - private static bool ServiceFileRequest(string wwwroot, HttpListenerRequest request, HttpListenerResponse response) { + private static bool ServiceFileRequest(string wwwroot, HttpListenerRequest request, HttpListenerResponse response, string slug) { if (null == wwwroot || wwwroot.IsNullOrWhiteSpace()) return false; - var relativePath = request.Url.AbsolutePath.Substring(1); + var relativePath = request.Url.AbsolutePath; + if (!slug.Equals("root")) + { + relativePath = relativePath.Replace(String.Format("/{0}/", slug), ""); + } + else + { + relativePath = relativePath.Substring(1); + } relativePath = relativePath.Replace("/", Path.DirectorySeparatorChar.ToString()); var absolutePath = Path.Combine(wwwroot, relativePath); - if (!File.Exists(absolutePath)) return false; var extension = Path.GetExtension(absolutePath); From ab7b480f1afe3cf6850c100e722d8f15dd4f34fe Mon Sep 17 00:00:00 2001 From: nezroy Date: Wed, 8 Apr 2015 23:07:55 -0600 Subject: [PATCH 07/14] fixup! fix static file path check for plugins --- CityWebServer/IntegratedWebServer.cs | 10 +++++++++- 1 file changed, 9 insertions(+), 1 deletion(-) diff --git a/CityWebServer/IntegratedWebServer.cs b/CityWebServer/IntegratedWebServer.cs index c2d66d7..0fb67a4 100644 --- a/CityWebServer/IntegratedWebServer.cs +++ b/CityWebServer/IntegratedWebServer.cs @@ -217,7 +217,15 @@ private void HandleRequest(HttpListenerRequest request, HttpListenerResponse res } else if (url.StartsWith("/")) { - slug = url.Split('/')[1]; + string[] urlparts = url.Split('/'); + if (urlparts.Length < 3) + { + slug = "root"; + } + else + { + slug = urlparts[1]; + } } if (slug != null && _plugins.ContainsKey(slug)) From 2ff180bbbb62782fc42dc65157c369fe7a4d5a21 Mon Sep 17 00:00:00 2001 From: nezroy Date: Thu, 9 Apr 2015 16:45:58 -0600 Subject: [PATCH 08/14] refactor pass to simplify naming and architecture --- .../CityWebServer.Extensibility.csproj | 3 +- CityWebServer.Extensibility/ICityWebMod.cs | 38 +++++ CityWebServer.Extensibility/ICityWebPlugin.cs | 18 -- CityWebServer.Extensibility/IPluginInfo.cs | 31 ---- CityWebServer.Extensibility/IWebServer.cs | 4 +- CityWebServer/CityWebServer.csproj | 9 +- CityWebServer/Helpers/CityWebMod.cs | 158 ++++++++++++++++++ CityWebServer/Helpers/CityWebPlugin.cs | 72 -------- CityWebServer/Helpers/CityWebPluginInfo.cs | 95 ----------- CityWebServer/Helpers/TemplateHelper.cs | 4 +- CityWebServer/Helpers/UserMod.cs | 4 +- CityWebServer/IntegratedWebServer.cs | 102 +++++------ .../{APIPluginInfo.cs => APICWM.cs} | 4 +- .../{LogPluginInfo.cs => LogCWM.cs} | 6 +- .../{RootPluginInfo.cs => RootCWM.cs} | 12 +- .../SampleWebServerExtension.csproj | 5 +- SampleWebServerExtension/UserModInfo.cs | 8 +- 17 files changed, 281 insertions(+), 292 deletions(-) create mode 100644 CityWebServer.Extensibility/ICityWebMod.cs delete mode 100644 CityWebServer.Extensibility/ICityWebPlugin.cs delete mode 100644 CityWebServer.Extensibility/IPluginInfo.cs create mode 100644 CityWebServer/Helpers/CityWebMod.cs delete mode 100644 CityWebServer/Helpers/CityWebPlugin.cs delete mode 100644 CityWebServer/Helpers/CityWebPluginInfo.cs rename CityWebServer/RequestHandlers/{APIPluginInfo.cs => APICWM.cs} (90%) rename CityWebServer/RequestHandlers/{LogPluginInfo.cs => LogCWM.cs} (89%) rename CityWebServer/RequestHandlers/{RootPluginInfo.cs => RootCWM.cs} (81%) diff --git a/CityWebServer.Extensibility/CityWebServer.Extensibility.csproj b/CityWebServer.Extensibility/CityWebServer.Extensibility.csproj index 64793b9..f104494 100644 --- a/CityWebServer.Extensibility/CityWebServer.Extensibility.csproj +++ b/CityWebServer.Extensibility/CityWebServer.Extensibility.csproj @@ -41,8 +41,7 @@
    - - + diff --git a/CityWebServer.Extensibility/ICityWebMod.cs b/CityWebServer.Extensibility/ICityWebMod.cs new file mode 100644 index 0000000..2f37f34 --- /dev/null +++ b/CityWebServer.Extensibility/ICityWebMod.cs @@ -0,0 +1,38 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; + +namespace CityWebServer.Extensibility +{ + public interface ICityWebMod + { + /// + /// The name of this mod. Shown in the index page/nav menu as a link. + /// + string ModName { get; } + + /// + /// The author of this mod. + /// + string ModAuthor { get; } + + /// + /// The ID/slug for this mod. + /// + /// + /// This must be unique across all CityWebMods, and is used as the root for all request handlers provided by this mod. + /// + string ModID { get; } + + /// + /// Whether this mod should show up in the top menu. + /// + bool TopMenu { get; } + + /// + /// Called when the server first starts and is registering handlers. Returns an enumerable collection of IRequestHandler objects. + /// + List GetHandlers(IWebServer server); + } +} diff --git a/CityWebServer.Extensibility/ICityWebPlugin.cs b/CityWebServer.Extensibility/ICityWebPlugin.cs deleted file mode 100644 index 991cbe1..0000000 --- a/CityWebServer.Extensibility/ICityWebPlugin.cs +++ /dev/null @@ -1,18 +0,0 @@ -using System; -using System.Collections.Generic; -using System.Linq; -using System.Text; - -namespace CityWebServer.Extensibility -{ - public interface ICityWebPlugin : IPluginInfo - { - /// - /// Called when the server first starts and is registering handlers for this plugin. Returns an enumerable collection of IRequestHandler objects. - /// - /// - /// Each of the returned handlers will be used by the server to handle requests. Handlers show up on the main index page of the server. - /// - List GetHandlers(IWebServer server); - } -} diff --git a/CityWebServer.Extensibility/IPluginInfo.cs b/CityWebServer.Extensibility/IPluginInfo.cs deleted file mode 100644 index 4403282..0000000 --- a/CityWebServer.Extensibility/IPluginInfo.cs +++ /dev/null @@ -1,31 +0,0 @@ -using System; -using System.Collections.Generic; -using System.Linq; -using System.Text; - -namespace CityWebServer.Extensibility -{ - public interface IPluginInfo - { - /// - /// The name of this plugin. Shown in the index page as a link. - /// - string PluginName { get; } - - /// - /// The author of this plugin (shown in the index page). - /// - string PluginAuthor { get; } - - /// - /// The ID/slug for this plugin. Also used as the root for all request handlers provided by this plugin. - /// - string PluginID { get; } - - /// - /// Whether this plugin should show up in the top menu. - /// - bool TopMenu { get; } - - } -} diff --git a/CityWebServer.Extensibility/IWebServer.cs b/CityWebServer.Extensibility/IWebServer.cs index a277e0f..2bf56e6 100644 --- a/CityWebServer.Extensibility/IWebServer.cs +++ b/CityWebServer.Extensibility/IWebServer.cs @@ -6,9 +6,9 @@ namespace CityWebServer.Extensibility public interface IWebServer { /// - /// Gets an array containing all currently registered plugins. + /// Gets an array containing all currently registered CityWebMods. /// - IPluginInfo[] Plugins { get; } + ICityWebMod[] Mods { get; } /// /// Gets the name of the current city. diff --git a/CityWebServer/CityWebServer.csproj b/CityWebServer/CityWebServer.csproj index 9e12e7d..a61a969 100644 --- a/CityWebServer/CityWebServer.csproj +++ b/CityWebServer/CityWebServer.csproj @@ -63,12 +63,11 @@ - - - - - + + + + diff --git a/CityWebServer/Helpers/CityWebMod.cs b/CityWebServer/Helpers/CityWebMod.cs new file mode 100644 index 0000000..eaba843 --- /dev/null +++ b/CityWebServer/Helpers/CityWebMod.cs @@ -0,0 +1,158 @@ +using System; +using System.Collections.Generic; +using System.Reflection; +using System.Linq; +using System.Text; +using System.IO; +using System.Net; +using ColossalFramework.Plugins; +using CityWebServer.Extensibility; + +namespace CityWebServer.Helpers +{ + public class CityWebMod : ICityWebMod + { + protected string _name = null; + protected string _author = null; + protected List _handlers = null; + protected string _wwwroot = null; + protected string _ID = null; + protected bool _isEnabled = false; + protected bool _topMenu = true; + + public string ModName { get { return _name; } } + public string ModAuthor { get { return _author; } } + public string ModID { get { return _ID; } } + public bool HasStatic { get { return _wwwroot != null; } } + public bool IsEnabled { get { return _isEnabled; } } + public bool TopMenu { get { return _topMenu; } } + public string WebRoot { get { return _wwwroot; } } + public List GetHandlers(IWebServer server) + { + return _handlers; + } + + protected CityWebMod() + { + } + + /* + public CityWebMod(UserMod um, IWebServer server) + { + _ID = p.PluginID.ToLower().Replace(" ", "_"); + _name = p.PluginName; + _author = p.PluginAuthor; + _topMenu = p.TopMenu; + _isEnabled = pi.isEnabled; + if (_isEnabled) + { + string testPath = Path.Combine(pi.modPath, "wwwroot"); + if (Directory.Exists(testPath)) + { + _wwwroot = testPath; + } + List h = p.GetHandlers(server); + if (null != h) + { + _handlers = h; + } + else + { + _handlers = new List(); + } + } + } + */ + /* + public CityWebPluginInfo(string name, string author, string ID, IEnumerable h) + { + _ID = ID; + _name = name; + _author = author; + _isEnabled = true; + if (null != h) + { + _handlers = new List(h); + } + else + { + _handlers = new List(); + } + } + */ + + public bool HandleRequest(HttpListenerRequest request, HttpListenerResponse response) + { + var handler = _handlers.FirstOrDefault(obj => obj.ShouldHandle(request, _ID)); + if (null == handler) return false; + + IResponseFormatter responseFormatterWriter = handler.Handle(request); + responseFormatterWriter.WriteContent(response); + return true; + } + + public static CityWebMod CreateByReflection(UserMod um, IWebServer server) + { + // just casting um.Mod to ICityWebPlugin is not reliable; somtimes works, sometimes not + // instead we need to reflect on it to identify mods that have implemented ICityWebMod + Type ut = um.Mod.GetType(); + MethodInfo getHandlers = null; + PropertyInfo pName = null; + PropertyInfo pAuthor = null; + PropertyInfo pID = null; + PropertyInfo pTop = null; + try + { + getHandlers = ut.GetMethod("GetHandlers"); + pName = ut.GetProperty("ModName"); + pAuthor = ut.GetProperty("ModAuthor"); + pID = ut.GetProperty("ModID"); + pTop = ut.GetProperty("TopMenu"); + } + catch + { + } + + if (null != getHandlers && getHandlers.ReturnType != typeof(List)) return null; + if (null == getHandlers || null == pName || null == pAuthor || null == pID || null == pTop) return null; + + CityWebMod cwm = new CityWebMod(); + + // grab values from the PluginManager.PluginInfo + cwm._isEnabled = um.PluginInfo.isEnabled; + string testPath = Path.Combine(um.PluginInfo.modPath, "wwwroot"); + if (Directory.Exists(testPath)) + { + cwm._wwwroot = testPath; + } + + // grab reflected property values + cwm._name = (string)pName.GetValue(um.Mod, null); + cwm._author = (string)pAuthor.GetValue(um.Mod, null); + cwm._topMenu = (bool)pTop.GetValue(um.Mod, null); + cwm._ID = (string)pID.GetValue(um.Mod, null); + if (null != cwm._ID) cwm._ID = cwm._ID.ToLower().Replace(" ", "_"); + + // invoke the method to get the list of request handlers managed by this CityWebMod + List h = null; + try + { + h = (List)getHandlers.Invoke(um.Mod, new Object[1] { server }); + } + catch (Exception ex) + { + IntegratedWebServer.LogMessage(String.Format("failed to invoke GetHandlers: {0}", ex)); + return null; + } + if (null != h) { + cwm._handlers = h; + } + else { + cwm._handlers = new List(); + } + + return cwm; + } + + } +} diff --git a/CityWebServer/Helpers/CityWebPlugin.cs b/CityWebServer/Helpers/CityWebPlugin.cs deleted file mode 100644 index 7be9f6b..0000000 --- a/CityWebServer/Helpers/CityWebPlugin.cs +++ /dev/null @@ -1,72 +0,0 @@ -using System; -using System.Collections.Generic; -using System.Text; -using System.Reflection; -using CityWebServer.Extensibility; - -namespace CityWebServer.Helpers -{ - public class CityWebPlugin : ICityWebPlugin - { - private string _name = null; - private string _author = null; - private string _ID = null; - private bool _topMenu = false; - List _handlers = null; - - public string PluginName { get { return _name; } } - public string PluginAuthor { get { return _author; } } - public string PluginID { get { return _ID; } } - public bool TopMenu { get { return _topMenu; } } - - public List GetHandlers(IWebServer server) - { - return _handlers; - } - - public static CityWebPlugin CreateByReflection(UserMod um, IWebServer server) - { - // just casting um.Mod to ICityWebPlugin is not reliable; somtimes works, sometimes not - // instead we reflect through methods to identify valid plugins - Type ut = um.Mod.GetType(); - MethodInfo[] mia = ut.GetMethods(); - bool isCityWebPlugin = false; - for (int i = 0; i < mia.Length; i++) - { - MethodInfo mi = mia[i]; - if (mi.Name.Equals("GetHandlers") && mi.ReturnType == typeof(List)) - { - isCityWebPlugin = true; - break; - } - } - - if (!isCityWebPlugin) return null; - - CityWebPlugin cwp = new CityWebPlugin(); - - // since casting doesn't work reliably, we need to fill out a placeholder class with source - // values by reflection as well - PropertyInfo[] pia = ut.GetProperties(); - for (int i = 0; i < pia.Length; i++) - { - PropertyInfo pi = pia[i]; - if (pi.Name.Equals("PluginName")) cwp._name = (string)pi.GetValue(um.Mod, null); - else if (pi.Name.Equals("PluginAuthor")) cwp._author = (string)pi.GetValue(um.Mod, null); - else if (pi.Name.Equals("PluginID")) cwp._ID = (string)pi.GetValue(um.Mod, null); - else if (pi.Name.Equals("TopMenu")) cwp._topMenu = (bool)pi.GetValue(um.Mod, null); - } - - for (int i = 0; i < mia.Length; i++) - { - MethodInfo mi = mia[i]; - if (mi.Name.Equals("GetHandlers") && mi.ReturnType == typeof(List)) - { - cwp._handlers = (List)mi.Invoke(um.Mod, new Object[1]{ server }); - } - } - - return cwp; - } - } -} diff --git a/CityWebServer/Helpers/CityWebPluginInfo.cs b/CityWebServer/Helpers/CityWebPluginInfo.cs deleted file mode 100644 index 7cc41fa..0000000 --- a/CityWebServer/Helpers/CityWebPluginInfo.cs +++ /dev/null @@ -1,95 +0,0 @@ -using System; -using System.Collections.Generic; -using System.Linq; -using System.Text; -using System.IO; -using System.Net; -using ColossalFramework.Plugins; -using CityWebServer.Extensibility; - -namespace CityWebServer.Helpers -{ - // This gets encapsulated because I don't want to maintain a reference to either the plugininfo or the plugin instance - // to remove any chance for these references to interfere with the engine's plugin loading/processing chain. - // Plus most of what we need can be figured out once up front anyway. This also allows us to fake entries for core - // things not actually loaded by plugins too. - public class CityWebPluginInfo : IPluginInfo - { - protected string _name = null; - protected string _author = null; - protected List _handlers = null; - protected string _wwwroot = null; - protected string _ID = null; - protected bool _isEnabled = false; - protected bool _topMenu = true; - - public string Name { get { return _name; } } - public string PluginName { get { return _name; } } - public string Author { get { return _author; } } - public string PluginAuthor { get { return _author; } } - public string ID { get { return _ID; } } - public string PluginID { get { return _ID; } } - public bool HasStatic { get { return _wwwroot != null; } } - public bool IsEnabled { get { return _isEnabled; } } - public bool TopMenu { get { return _topMenu; } } - public string WebRoot { get { return _wwwroot; } } - public List Handlers { get { return _handlers; } } - - protected CityWebPluginInfo() - { - } - - public CityWebPluginInfo(ICityWebPlugin p, PluginManager.PluginInfo pi, IWebServer server) - { - _ID = p.PluginID.ToLower().Replace(" ", "_"); - _name = p.PluginName; - _author = p.PluginAuthor; - _topMenu = p.TopMenu; - _isEnabled = pi.isEnabled; - if (_isEnabled) - { - string testPath = Path.Combine(pi.modPath, "wwwroot"); - if (Directory.Exists(testPath)) - { - _wwwroot = testPath; - } - List h = p.GetHandlers(server); - if (null != h) - { - _handlers = h; - } - else - { - _handlers = new List(); - } - } - } - - public CityWebPluginInfo(string name, string author, string ID, IEnumerable h) - { - _ID = ID; - _name = name; - _author = author; - _isEnabled = true; - if (null != h) - { - _handlers = new List(h); - } - else - { - _handlers = new List(); - } - } - - public bool HandleRequest(HttpListenerRequest request, HttpListenerResponse response) - { - var handler = _handlers.FirstOrDefault(obj => obj.ShouldHandle(request, _ID)); - if (null == handler) return false; - - IResponseFormatter responseFormatterWriter = handler.Handle(request); - responseFormatterWriter.WriteContent(response); - return true; - } - - } -} diff --git a/CityWebServer/Helpers/TemplateHelper.cs b/CityWebServer/Helpers/TemplateHelper.cs index aaf3f96..473867d 100644 --- a/CityWebServer/Helpers/TemplateHelper.cs +++ b/CityWebServer/Helpers/TemplateHelper.cs @@ -57,9 +57,9 @@ public static String PopulateTemplate(String template, String tmplPath, Dictiona /// /// Gets a dictionary that contains standard replacement tokens using the specified values. /// - public static Dictionary GetTokenReplacements(String cityName, String title, IPluginInfo[] plugins, String body) + public static Dictionary GetTokenReplacements(String cityName, String title, ICityWebMod[] mods, String body) { - var handlerLinks = plugins.Select(obj => obj.TopMenu ? String.Format("
  • {1}
  • ", obj.PluginID, obj.PluginName) : "").ToArray(); + var handlerLinks = mods.Select(obj => obj.TopMenu ? String.Format("
  • {1}
  • ", obj.ModID, obj.ModName) : "").ToArray(); String nav = String.Join(Environment.NewLine, handlerLinks); return new Dictionary diff --git a/CityWebServer/Helpers/UserMod.cs b/CityWebServer/Helpers/UserMod.cs index 4f8d482..04c41ac 100644 --- a/CityWebServer/Helpers/UserMod.cs +++ b/CityWebServer/Helpers/UserMod.cs @@ -8,6 +8,8 @@ namespace CityWebServer.Helpers { + // this is a wrapper class to encapsulate both the PluginManager.PluginInfo and its + // associated IUserMod instance, to simplify spinning through these in tandem public class UserMod { private IUserMod _umod = null; @@ -34,7 +36,7 @@ public class UserMod } /// - /// Static factory to collect mods from the PluginManager into a list of ModInfo's + /// Static factory to collect plugins from the PluginManager into a list of UserMods /// public static List CollectPlugins() { diff --git a/CityWebServer/IntegratedWebServer.cs b/CityWebServer/IntegratedWebServer.cs index 0fb67a4..6544789 100644 --- a/CityWebServer/IntegratedWebServer.cs +++ b/CityWebServer/IntegratedWebServer.cs @@ -25,12 +25,13 @@ public class IntegratedWebServer : ThreadingExtensionBase, IWebServer private static string _endpoint; private WebServer _server; - private Dictionary _plugins; private String _cityName = "CityName"; private string _wwwroot = null; private bool _secondPass = false; - private bool _pluginPass = false; - private List _mods = null; + private bool _cwmPass = false; + + private Dictionary _cwMods; // the list of CityWebMods we've identified and registered + private List _usrMods = null; // utility list of all UserMods found in ColossalFramework PluginManager /// /// Gets the root endpoint for which the server is configured to service HTTP requests. @@ -122,10 +123,16 @@ public override void OnReleased() ReleaseServer(); // TODO: Unregister from events (i.e. ILogAppender.LogMessage) - if (null != _plugins) + if (null != _cwMods) + { + _cwMods.Clear(); + _cwMods = null; + } + if (null != _usrMods) // TODO: we ought to clean this up much sooner, only needed for init and i don't + // like hanging on to these references for very long { - _plugins.Clear(); - _plugins = null; + _usrMods.Clear(); + _usrMods = null; } Configuration.SaveSettings(); @@ -179,8 +186,8 @@ private void SecondPassInit() try { - _mods = UserMod.CollectPlugins(); - RegisterDefaultPlugins(); + _usrMods = UserMod.CollectPlugins(); + RegisterDefaultCWM(); } catch (Exception ex) { @@ -204,7 +211,7 @@ private void HandleRequest(HttpListenerRequest request, HttpListenerResponse res { LogMessage(String.Format("{0} {1}", request.HttpMethod, request.RawUrl)); if (!_secondPass) SecondPassInit(); - if (!_pluginPass) RegisterPlugins(); + if (!_cwmPass) RegisterCWM(); // Get the request handler associated with the current request. string url = request.Url.AbsolutePath; @@ -228,19 +235,19 @@ private void HandleRequest(HttpListenerRequest request, HttpListenerResponse res } } - if (slug != null && _plugins.ContainsKey(slug)) + if (slug != null && _cwMods.ContainsKey(slug)) { - CityWebPluginInfo cwpi; - _plugins.TryGetValue(slug, out cwpi); - wwwroot = cwpi.WebRoot; + CityWebMod cwm; + _cwMods.TryGetValue(slug, out cwm); + wwwroot = cwm.WebRoot; try { - handled = cwpi.HandleRequest(request, response); + handled = cwm.HandleRequest(request, response); } catch (Exception ex) { String errorBody = String.Format("

    An error has occurred!

    {0}
    ", ex); - var tokens = TemplateHelper.GetTokenReplacements(_cityName, "Error", this.Plugins, errorBody); + var tokens = TemplateHelper.GetTokenReplacements(_cityName, "Error", this.Mods, errorBody); var template = TemplateHelper.PopulateTemplate("index", _wwwroot, tokens); IResponseFormatter errorResponseFormatter = new HtmlResponseFormatter(template); @@ -296,21 +303,22 @@ private static bool ServiceFileRequest(string wwwroot, HttpListenerRequest reque } /// - /// Searches all the plugins in the PluginManager for ones that implement . + /// Searches all the plugins in the PluginManager for ones that implement . /// - private void RegisterPlugins() + private void RegisterCWM() { - LogMessage("Looking for CityWeb plugins..."); - foreach (UserMod um in _mods) { + LogMessage("Looking for CityWebMods..."); + foreach (UserMod um in _usrMods) { if (!um.PluginInfo.isEnabled || um.PluginInfo.isBuiltin) continue; if (null == um.Mod) continue; LogMessage(String.Format("scanning {0} <{1}>...", um.PluginInfo.name, um.Mod.GetType())); - CityWebPlugin cwp = CityWebPlugin.CreateByReflection(um, this); - if (null == cwp) continue; + CityWebMod cwm = CityWebMod.CreateByReflection(um, this); + if (null == cwm) continue; - LogMessage(String.Format("Loading plugin \"{0}\"", cwp.PluginName)); + // LogMessage(String.Format("Loading mod \"{0}\"", cwm.ModName)); + /* CityWebPluginInfo cwpi = null; try { @@ -321,27 +329,28 @@ private void RegisterPlugins() LogMessage(String.Format("Failed to load plugin: \"{0}\"", ex)); continue; } + */ - string slug = cwpi.ID; + string slug = cwm.ModID; if (slug == null || slug.IsNullOrWhiteSpace()) { - LogMessage(String.Format("Invalid plugin \"{0}\" by \"{1}\"", cwpi.Name, cwpi.Author)); + LogMessage(String.Format("Invalid CityWebMod \"{0}\" by \"{1}\"", cwm.ModName, cwm.ModAuthor)); } - else if (_plugins.ContainsKey(slug)) + else if (_cwMods.ContainsKey(slug)) { - LogMessage(String.Format("Conflicting plugin; ID already exists! {0}:\"{1}\" by \"{2}\"", slug, cwpi.Name, cwpi.Author)); + LogMessage(String.Format("Conflicting CityWebMod; ID already exists! {0}:\"{1}\" by \"{2}\"", slug, cwm.ModName, cwm.ModAuthor)); } else { - LogMessage(String.Format("Loaded plugin {0}:\"{1}\" by \"{2}\"", slug, cwpi.Name, cwpi.Author)); - _plugins.Add(slug, cwpi); - if (cwpi.Handlers != null) - { - for (int j = 0; j < cwpi.Handlers.Count; j++) + LogMessage(String.Format("Loaded CityWebMod {0}:\"{1}\" by \"{2}\"", slug, cwm.ModName, cwm.ModAuthor)); + _cwMods.Add(slug, cwm); + List hList = cwm.GetHandlers(this); + if (hList != null) { + foreach (IRequestHandler h in hList) { - if (cwpi.Handlers.ElementAt(j) is ILogAppender) + if (h is ILogAppender) { - ILogAppender la = (cwpi.Handlers.ElementAt(j) as ILogAppender); + ILogAppender la = (h as ILogAppender); la.LogMessage += RequestHandlerLogAppender_OnLogMessage; } } @@ -349,10 +358,10 @@ private void RegisterPlugins() } } - _pluginPass = true; + _cwmPass = true; } - public IPluginInfo[] Plugins { get { return _plugins.Values.ToArray(); } } + public ICityWebMod[] Mods { get { return _cwMods.Values.ToArray(); } } private void RequestHandlerLogAppender_OnLogMessage(object sender, LogAppenderEventArgs logAppenderEventArgs) { @@ -361,19 +370,18 @@ private void RequestHandlerLogAppender_OnLogMessage(object sender, LogAppenderEv } /// - /// Does some initial setup dependent on scanning plugins and also default plugin registration. + /// Does some initial setup dependent on scanning plugins and also default/core CWM registration. /// - private void RegisterDefaultPlugins() + private void RegisterDefaultCWM() { - if (null != _plugins) + if (null != _cwMods) { - _plugins.Clear(); - _plugins = null; + _cwMods.Clear(); + _cwMods = null; } - foreach (UserMod um in _mods) { + foreach (UserMod um in _usrMods) { if (!um.PluginInfo.isEnabled || um.PluginInfo.isBuiltin) continue; if (null == um.Mod) continue; - // ICityWebPlugin foo = minsts[0] as ICityWebPlugin; if (um.Mod.Name.Equals("Integrated Web Server")) { // hey it's me! get our web root @@ -387,11 +395,11 @@ private void RegisterDefaultPlugins() } } - LogMessage("adding default plugins"); - _plugins = new Dictionary(); - _plugins.Add("root", new RootPluginInfo(this)); - _plugins.Add("log", new LogPluginInfo(this)); - _plugins.Add("api", new APIPluginInfo(this)); + LogMessage("adding default CityWebMods"); + _cwMods = new Dictionary(); + _cwMods.Add("root", new RootCWM(this)); + _cwMods.Add("log", new LogCWM(this)); + _cwMods.Add("api", new APICWM(this)); } #region Logging diff --git a/CityWebServer/RequestHandlers/APIPluginInfo.cs b/CityWebServer/RequestHandlers/APICWM.cs similarity index 90% rename from CityWebServer/RequestHandlers/APIPluginInfo.cs rename to CityWebServer/RequestHandlers/APICWM.cs index c085d71..eff368a 100644 --- a/CityWebServer/RequestHandlers/APIPluginInfo.cs +++ b/CityWebServer/RequestHandlers/APICWM.cs @@ -11,9 +11,9 @@ namespace CityWebServer.RequestHandlers { - public class APIPluginInfo : CityWebPluginInfo + public class APICWM : CityWebMod { - public APIPluginInfo(IWebServer server) + public APICWM(IWebServer server) { _ID = "api"; _name = "JSON API"; diff --git a/CityWebServer/RequestHandlers/LogPluginInfo.cs b/CityWebServer/RequestHandlers/LogCWM.cs similarity index 89% rename from CityWebServer/RequestHandlers/LogPluginInfo.cs rename to CityWebServer/RequestHandlers/LogCWM.cs index 78b55f1..3837d48 100644 --- a/CityWebServer/RequestHandlers/LogPluginInfo.cs +++ b/CityWebServer/RequestHandlers/LogCWM.cs @@ -11,9 +11,9 @@ namespace CityWebServer.RequestHandlers { - public class LogPluginInfo : CityWebPluginInfo + public class LogCWM : CityWebMod { - public LogPluginInfo(IWebServer server) + public LogCWM(IWebServer server) { _ID = "log"; _name = "Server Log"; @@ -36,7 +36,7 @@ public LogRequestHandler(IWebServer server) public override IResponseFormatter Handle(HttpListenerRequest request) { String body = String.Format("
    {0}
    ", String.Join("", _server.LogLines.ToArray())); - var tokens = TemplateHelper.GetTokenReplacements(_server.CityName, "Log", _server.Plugins, body); + var tokens = TemplateHelper.GetTokenReplacements(_server.CityName, "Log", _server.Mods, body); var template = TemplateHelper.PopulateTemplate("log", _server.WebRoot, tokens); return new HtmlResponseFormatter(template); diff --git a/CityWebServer/RequestHandlers/RootPluginInfo.cs b/CityWebServer/RequestHandlers/RootCWM.cs similarity index 81% rename from CityWebServer/RequestHandlers/RootPluginInfo.cs rename to CityWebServer/RequestHandlers/RootCWM.cs index 22ba3b6..ee004ec 100644 --- a/CityWebServer/RequestHandlers/RootPluginInfo.cs +++ b/CityWebServer/RequestHandlers/RootCWM.cs @@ -11,9 +11,9 @@ namespace CityWebServer.RequestHandlers { - public class RootPluginInfo : CityWebPluginInfo + public class RootCWM : CityWebMod { - public RootPluginInfo(IWebServer server) + public RootCWM(IWebServer server) { _ID = "root"; _name = "Index Page"; @@ -36,15 +36,15 @@ public RootRequestHandler(IWebServer server) public override IResponseFormatter Handle(HttpListenerRequest request) { List links = new List(); - foreach (var plugin in _server.Plugins.OrderBy(obj => obj.PluginID)) + foreach (var cwm in _server.Mods.OrderBy(obj => obj.ModID)) { - if (!plugin.PluginID.Equals("root")) { - links.Add(String.Format("
  • {0} by {2}
  • ", plugin.PluginName, plugin.PluginID, plugin.PluginAuthor)); + if (!cwm.ModID.Equals("root")) { + links.Add(String.Format("
  • {0} by {2}
  • ", cwm.ModName, cwm.ModID, cwm.ModAuthor)); } } String body = String.Format("
      {0}
    ", String.Join("", links.ToArray())); - var tokens = TemplateHelper.GetTokenReplacements(_server.CityName, "Home", _server.Plugins, body); + var tokens = TemplateHelper.GetTokenReplacements(_server.CityName, "Home", _server.Mods, body); var template = TemplateHelper.PopulateTemplate("index", _server.WebRoot, tokens); return new HtmlResponseFormatter(template); diff --git a/SampleWebServerExtension/SampleWebServerExtension.csproj b/SampleWebServerExtension/SampleWebServerExtension.csproj index b7e80a2..4753e17 100644 --- a/SampleWebServerExtension/SampleWebServerExtension.csproj +++ b/SampleWebServerExtension/SampleWebServerExtension.csproj @@ -58,9 +58,10 @@ mkdir "%25LOCALAPPDATA%25\Colossal Order\Cities_Skylines\Addons\Mods\$(SolutionName)_$(ProjectName)" + del "%25LOCALAPPDATA%25\Colossal Order\Cities_Skylines\Addons\Mods\$(SolutionName)_$(ProjectName)\$(TargetFileName)" -xcopy /Y "$(TargetPath)" "%25LOCALAPPDATA%25\Colossal Order\Cities_Skylines\Addons\Mods\$(SolutionName)_$(ProjectName)" -xcopy /Y "$(TargetDir)CityWebServer.Extensibility.dll" "%25LOCALAPPDATA%25\Colossal Order\Cities_Skylines\Addons\Mods\$(SolutionName)_$(ProjectName)\*.*" + +xcopy /Y "$(TargetPath)" "%25LOCALAPPDATA%25\Colossal Order\Cities_Skylines\Addons\Mods\$(SolutionName)_$(ProjectName)" + #PAGETITLE# + + + + + + +
    +

    #PAGETITLE#

    + #PAGEBODY# +
    + + + + \ No newline at end of file diff --git a/CityWebServer/wwwroot/log.html b/CityWebServer/wwwroot/log.html deleted file mode 100644 index d78103a..0000000 --- a/CityWebServer/wwwroot/log.html +++ /dev/null @@ -1,76 +0,0 @@ - - - - - - - - #PAGETITLE# - - - - - - -
    -

    #PAGETITLE#

    - #PAGEBODY# -
    - - - - - \ No newline at end of file diff --git a/SampleWebServerExtension/SampleRequestHandler.cs b/SampleWebServerExtension/SampleRequestHandler.cs index 6644164..9f51f03 100644 --- a/SampleWebServerExtension/SampleRequestHandler.cs +++ b/SampleWebServerExtension/SampleRequestHandler.cs @@ -13,7 +13,7 @@ public SampleRequestHandler(IWebServer server) { } - public override IResponseFormatter Handle(HttpListenerRequest request) + public override IResponseFormatter Handle(HttpListenerRequest request, string slug, string wwwroot) { const String content = "This is a sample page!"; From 269db6511271f0b40a4caffb3d19e39391111a3d Mon Sep 17 00:00:00 2001 From: nezroy Date: Thu, 16 Apr 2015 20:14:08 -0600 Subject: [PATCH 12/14] style fixes --- CityWebServer.Extensibility/ICityWebMod.cs | 8 +-- .../IRequestHandler.cs | 4 +- .../RequestHandlerBase.cs | 4 +- CityWebServer/Helpers/CityWebMod.cs | 55 ++++++++++--------- CityWebServer/Helpers/UserMod.cs | 4 +- CityWebServer/IntegratedWebServer.cs | 53 +++++++++--------- .../RequestHandlers/BudgetRequestHandler.cs | 2 +- .../RequestHandlers/BuildingRequestHandler.cs | 2 +- .../RequestHandlers/CityInfoRequestHandler.cs | 2 +- CityWebServer/RequestHandlers/LogCWM.cs | 2 +- .../RequestHandlers/MessageRequestHandler.cs | 2 +- CityWebServer/RequestHandlers/RootCWM.cs | 6 +- .../TransportRequestHandler.cs | 2 +- .../RequestHandlers/VehicleRequestHandler.cs | 2 +- .../SampleRequestHandler.cs | 2 +- SampleWebServerExtension/UserModInfo.cs | 2 +- 16 files changed, 79 insertions(+), 73 deletions(-) diff --git a/CityWebServer.Extensibility/ICityWebMod.cs b/CityWebServer.Extensibility/ICityWebMod.cs index 2f37f34..84ba47f 100644 --- a/CityWebServer.Extensibility/ICityWebMod.cs +++ b/CityWebServer.Extensibility/ICityWebMod.cs @@ -10,12 +10,12 @@ public interface ICityWebMod /// /// The name of this mod. Shown in the index page/nav menu as a link. /// - string ModName { get; } + String ModName { get; } /// /// The author of this mod. /// - string ModAuthor { get; } + String ModAuthor { get; } /// /// The ID/slug for this mod. @@ -23,12 +23,12 @@ public interface ICityWebMod /// /// This must be unique across all CityWebMods, and is used as the root for all request handlers provided by this mod. /// - string ModID { get; } + String ModID { get; } /// /// Whether this mod should show up in the top menu. /// - bool TopMenu { get; } + Boolean TopMenu { get; } /// /// Called when the server first starts and is registering handlers. Returns an enumerable collection of IRequestHandler objects. diff --git a/CityWebServer.Extensibility/IRequestHandler.cs b/CityWebServer.Extensibility/IRequestHandler.cs index 7139214..18e89a2 100644 --- a/CityWebServer.Extensibility/IRequestHandler.cs +++ b/CityWebServer.Extensibility/IRequestHandler.cs @@ -18,11 +18,11 @@ public interface IRequestHandler /// /// Returns a value that indicates whether this handler is capable of servicing the given request. /// - Boolean ShouldHandle(HttpListenerRequest request, string slug); + Boolean ShouldHandle(HttpListenerRequest request, String slug); /// /// Handles the specified request. The method should not close the stream. /// - IResponseFormatter Handle(HttpListenerRequest request, string slug, string wwwroot); + IResponseFormatter Handle(HttpListenerRequest request, String slug, String wwwroot); } } \ No newline at end of file diff --git a/CityWebServer.Extensibility/RequestHandlerBase.cs b/CityWebServer.Extensibility/RequestHandlerBase.cs index bded166..dff651b 100644 --- a/CityWebServer.Extensibility/RequestHandlerBase.cs +++ b/CityWebServer.Extensibility/RequestHandlerBase.cs @@ -47,7 +47,7 @@ protected RequestHandlerBase(IWebServer server, String mainPath) /// /// Returns a value that indicates whether this handler is capable of servicing the given request. /// - public virtual Boolean ShouldHandle(HttpListenerRequest request, string slug) + public virtual Boolean ShouldHandle(HttpListenerRequest request, String slug) { return (request.Url.AbsolutePath.Equals(String.Format("/{0}{1}", slug, _mainPath), StringComparison.OrdinalIgnoreCase)); } @@ -55,7 +55,7 @@ public virtual Boolean ShouldHandle(HttpListenerRequest request, string slug) /// /// Handles the specified request. The method should not close the stream. /// - public abstract IResponseFormatter Handle(HttpListenerRequest request, string slug, string wwwroot); + public abstract IResponseFormatter Handle(HttpListenerRequest request, String slug, String wwwroot); /// /// Returns a response in JSON format. diff --git a/CityWebServer/Helpers/CityWebMod.cs b/CityWebServer/Helpers/CityWebMod.cs index 620793f..daeee37 100644 --- a/CityWebServer/Helpers/CityWebMod.cs +++ b/CityWebServer/Helpers/CityWebMod.cs @@ -12,21 +12,21 @@ namespace CityWebServer.Helpers { public class CityWebMod : ICityWebMod { - protected string _name = null; - protected string _author = null; + protected String _name = null; + protected String _author = null; protected List _handlers = null; - protected string _wwwroot = null; - protected string _ID = null; - protected bool _isEnabled = false; - protected bool _topMenu = true; + protected String _wwwroot = null; + protected String _ID = null; + protected Boolean _isEnabled = false; + protected Boolean _topMenu = true; - public string ModName { get { return _name; } } - public string ModAuthor { get { return _author; } } - public string ModID { get { return _ID; } } - public bool HasStatic { get { return _wwwroot != null; } } - public bool IsEnabled { get { return _isEnabled; } } - public bool TopMenu { get { return _topMenu; } } - public string WebRoot { get { return _wwwroot; } } + public String ModName { get { return _name; } } + public String ModAuthor { get { return _author; } } + public String ModID { get { return _ID; } } + public Boolean HasStatic { get { return _wwwroot != null; } } + public Boolean IsEnabled { get { return _isEnabled; } } + public Boolean TopMenu { get { return _topMenu; } } + public String WebRoot { get { return _wwwroot; } } public List GetHandlers(IWebServer server) { return _handlers; @@ -36,10 +36,10 @@ protected CityWebMod() { } - public bool HandleRequest(HttpListenerRequest request, HttpListenerResponse response, string slug, string wwwroot) + public Boolean HandleRequest(HttpListenerRequest request, HttpListenerResponse response, String slug, String wwwroot) { var handler = _handlers.FirstOrDefault(obj => obj.ShouldHandle(request, slug)); - if (null == handler) return false; + if (handler == null) { return false; } IResponseFormatter responseFormatterWriter = handler.Handle(request, slug, wwwroot); responseFormatterWriter.WriteContent(response); @@ -68,25 +68,28 @@ public static CityWebMod CreateByReflection(UserMod um, IWebServer server) { } - if (null != getHandlers && getHandlers.ReturnType != typeof(List)) return null; - if (null == getHandlers || null == pName || null == pAuthor || null == pID || null == pTop) return null; + if (getHandlers != null && getHandlers.ReturnType != typeof(List)) { return null; } + if (getHandlers == null || pName == null || pAuthor == null || pID == null || pTop == null) { return null; } CityWebMod cwm = new CityWebMod(); // grab values from the PluginManager.PluginInfo cwm._isEnabled = um.PluginInfo.isEnabled; - string testPath = Path.Combine(um.PluginInfo.modPath, "wwwroot"); + String testPath = Path.Combine(um.PluginInfo.modPath, "wwwroot"); if (Directory.Exists(testPath)) { cwm._wwwroot = testPath; } // grab reflected property values - cwm._name = (string)pName.GetValue(um.Mod, null); - cwm._author = (string)pAuthor.GetValue(um.Mod, null); - cwm._topMenu = (bool)pTop.GetValue(um.Mod, null); - cwm._ID = (string)pID.GetValue(um.Mod, null); - if (null != cwm._ID) cwm._ID = cwm._ID.ToLower().Replace(" ", "_"); + cwm._name = (String)pName.GetValue(um.Mod, null); + cwm._author = (String)pAuthor.GetValue(um.Mod, null); + cwm._topMenu = (Boolean)pTop.GetValue(um.Mod, null); + cwm._ID = (String)pID.GetValue(um.Mod, null); + if (cwm._ID != null) + { + cwm._ID = cwm._ID.ToLower().Replace(" ", "_"); + } // invoke the method to get the list of request handlers managed by this CityWebMod List h = null; @@ -99,10 +102,12 @@ public static CityWebMod CreateByReflection(UserMod um, IWebServer server) IntegratedWebServer.LogMessage(String.Format("failed to invoke GetHandlers: {0}", ex)); return null; } - if (null != h) { + if (h != null) + { cwm._handlers = h; } - else { + else + { cwm._handlers = new List(); } diff --git a/CityWebServer/Helpers/UserMod.cs b/CityWebServer/Helpers/UserMod.cs index 04c41ac..8385a63 100644 --- a/CityWebServer/Helpers/UserMod.cs +++ b/CityWebServer/Helpers/UserMod.cs @@ -29,7 +29,7 @@ public class UserMod catch { } - if (null != insts && insts.Length > 0) + if (insts != null && insts.Length > 0) { _umod = insts[0]; } @@ -42,7 +42,7 @@ public static List CollectPlugins() { List miList = new List(); PluginManager pm = Singleton.instance; - if (null == pm) return miList; + if (null == pm) { return miList; } List piList = new List(pm.GetPluginsInfo()); foreach (PluginManager.PluginInfo pi in piList) diff --git a/CityWebServer/IntegratedWebServer.cs b/CityWebServer/IntegratedWebServer.cs index d70fcbd..72cff06 100644 --- a/CityWebServer/IntegratedWebServer.cs +++ b/CityWebServer/IntegratedWebServer.cs @@ -22,15 +22,15 @@ public class IntegratedWebServer : ThreadingExtensionBase, IWebServer private const String WebServerHostKey = "webServerHost{0}"; private static List _logLines; - private static string _endpoint; + private static String _endpoint; private WebServer _server; private String _cityName = "CityName"; - private string _wwwroot = null; - private bool _secondPass = false; - private bool _cwmPass = false; + private String _wwwroot = null; + private Boolean _secondPass = false; + private Boolean _cwmPass = false; - private Dictionary _cwMods; // the list of CityWebMods we've identified and registered + private Dictionary _cwMods; // the list of CityWebMods we've identified and registered private List _usrMods = null; // utility list of all UserMods found in ColossalFramework PluginManager /// @@ -123,7 +123,7 @@ public override void OnReleased() ReleaseServer(); // TODO: Unregister from events (i.e. ILogAppender.LogMessage) - if (null != _cwMods) + if (_cwMods != null) { _cwMods.Clear(); _cwMods = null; @@ -167,7 +167,7 @@ private void SecondPassInit() try { SimulationManager sm = Singleton.instance; - if (null != sm) + if (sm != null) { _cityName = sm.m_metaData.m_CityName; } @@ -210,21 +210,21 @@ private void SecondPassInit() private void HandleRequest(HttpListenerRequest request, HttpListenerResponse response) { LogMessage(String.Format("{0} {1}", request.HttpMethod, request.RawUrl)); - if (!_secondPass) SecondPassInit(); - if (!_cwmPass) RegisterCWM(); + if (!_secondPass) { SecondPassInit(); } + if (!_cwmPass) { RegisterCWM(); } // Get the request handler associated with the current request. - string url = request.Url.AbsolutePath; - string slug = null; + String url = request.Url.AbsolutePath; + String slug = null; String wwwroot = _wwwroot; - bool handled = false; + Boolean handled = false; if (url.IsNullOrWhiteSpace() || url.Equals("/")) { slug = "root"; } else if (url.StartsWith("/")) { - string[] urlparts = url.Split('/'); + String[] urlparts = url.Split('/'); if (urlparts.Length < 3) { slug = "root"; @@ -257,10 +257,10 @@ private void HandleRequest(HttpListenerRequest request, HttpListenerResponse res } } - if (handled) return; // something handled it, great + if (handled) { return; } // something handled it, great // At this point, we can guarantee that we don't need any game data, so we can safely start a new thread to perform the remaining tasks. - if (ServiceFileRequest(wwwroot, request, response, slug)) return; // check for static files + if (ServiceFileRequest(wwwroot, request, response, slug)) { return; } // check for static files String body = String.Format("No resource is available at the specified filepath: {0}", url); IResponseFormatter notFoundResponseFormatter = new PlainTextResponseFormatter(body, HttpStatusCode.NotFound); @@ -268,8 +268,8 @@ private void HandleRequest(HttpListenerRequest request, HttpListenerResponse res return; } - private static bool ServiceFileRequest(string wwwroot, HttpListenerRequest request, HttpListenerResponse response, string slug) { - if (null == wwwroot || wwwroot.IsNullOrWhiteSpace()) return false; + private static Boolean ServiceFileRequest(String wwwroot, HttpListenerRequest request, HttpListenerResponse response, String slug) { + if (wwwroot == null || wwwroot.IsNullOrWhiteSpace()) { return false; } var relativePath = request.Url.AbsolutePath; if (!slug.Equals("root")) @@ -282,7 +282,7 @@ private static bool ServiceFileRequest(string wwwroot, HttpListenerRequest reque } relativePath = relativePath.Replace("/", Path.DirectorySeparatorChar.ToString()); var absolutePath = Path.Combine(wwwroot, relativePath); - if (!File.Exists(absolutePath)) return false; + if (!File.Exists(absolutePath)) { return false; } var extension = Path.GetExtension(absolutePath); response.ContentType = Apache.GetMime(extension); @@ -309,14 +309,14 @@ private void RegisterCWM() { LogMessage("Looking for CityWebMods..."); foreach (UserMod um in _usrMods) { - if (!um.PluginInfo.isEnabled || um.PluginInfo.isBuiltin) continue; - if (null == um.Mod) continue; + if (!um.PluginInfo.isEnabled || um.PluginInfo.isBuiltin) { continue; } + if (um.Mod == null) { continue; } LogMessage(String.Format("scanning {0} <{1}>...", um.PluginInfo.name, um.Mod.GetType())); CityWebMod cwm = CityWebMod.CreateByReflection(um, this); - if (null == cwm) continue; + if (cwm == null) { continue; } - string slug = cwm.ModID; + String slug = cwm.ModID; if (slug == null || slug.IsNullOrWhiteSpace()) { LogMessage(String.Format("Invalid CityWebMod \"{0}\" by \"{1}\"", cwm.ModName, cwm.ModAuthor)); @@ -330,7 +330,8 @@ private void RegisterCWM() LogMessage(String.Format("Loaded CityWebMod {0}:\"{1}\" by \"{2}\"", slug, cwm.ModName, cwm.ModAuthor)); _cwMods.Add(slug, cwm); List hList = cwm.GetHandlers(this); - if (hList != null) { + if (hList != null) + { foreach (IRequestHandler h in hList) { if (h is ILogAppender) @@ -359,7 +360,7 @@ private void RequestHandlerLogAppender_OnLogMessage(object sender, LogAppenderEv /// private void RegisterDefaultCWM() { - if (null != _cwMods) + if (_cwMods != null) { _cwMods.Clear(); _cwMods = null; @@ -370,7 +371,7 @@ private void RegisterDefaultCWM() if (um.Mod.Name.Equals("Integrated Web Server")) { // hey it's me! get our web root - string testPath = Path.Combine(um.PluginInfo.modPath, "wwwroot"); + String testPath = Path.Combine(um.PluginInfo.modPath, "wwwroot"); if (Directory.Exists(testPath)) { LogMessage(String.Format("Setting server wwwroot location: {0}", testPath)); @@ -381,7 +382,7 @@ private void RegisterDefaultCWM() } LogMessage("adding default CityWebMods"); - _cwMods = new Dictionary(); + _cwMods = new Dictionary(); _cwMods.Add("root", new RootCWM(this)); _cwMods.Add("log", new LogCWM(this)); _cwMods.Add("api", new APICWM(this)); diff --git a/CityWebServer/RequestHandlers/BudgetRequestHandler.cs b/CityWebServer/RequestHandlers/BudgetRequestHandler.cs index b5d0b76..5b51d27 100644 --- a/CityWebServer/RequestHandlers/BudgetRequestHandler.cs +++ b/CityWebServer/RequestHandlers/BudgetRequestHandler.cs @@ -13,7 +13,7 @@ public BudgetRequestHandler(IWebServer server) { } - public override IResponseFormatter Handle(HttpListenerRequest request, string slug, string wwwroot) + public override IResponseFormatter Handle(HttpListenerRequest request, String slug, String wwwroot) { // TODO: Expand upon this to expose substantially more information. var economyManager = Singleton.instance; diff --git a/CityWebServer/RequestHandlers/BuildingRequestHandler.cs b/CityWebServer/RequestHandlers/BuildingRequestHandler.cs index 6251850..7d9d6d8 100644 --- a/CityWebServer/RequestHandlers/BuildingRequestHandler.cs +++ b/CityWebServer/RequestHandlers/BuildingRequestHandler.cs @@ -13,7 +13,7 @@ public BuildingRequestHandler(IWebServer server) { } - public override IResponseFormatter Handle(HttpListenerRequest request, string slug, string wwwroot) + public override IResponseFormatter Handle(HttpListenerRequest request, String slug, String wwwroot) { var buildingManager = Singleton.instance; diff --git a/CityWebServer/RequestHandlers/CityInfoRequestHandler.cs b/CityWebServer/RequestHandlers/CityInfoRequestHandler.cs index 1b5435f..547c477 100644 --- a/CityWebServer/RequestHandlers/CityInfoRequestHandler.cs +++ b/CityWebServer/RequestHandlers/CityInfoRequestHandler.cs @@ -18,7 +18,7 @@ public CityInfoRequestHandler(IWebServer server) { } - public override IResponseFormatter Handle(HttpListenerRequest request, string slug, string wwwroot) + public override IResponseFormatter Handle(HttpListenerRequest request, String slug, String wwwroot) { if (request.QueryString.HasKey("showList")) { diff --git a/CityWebServer/RequestHandlers/LogCWM.cs b/CityWebServer/RequestHandlers/LogCWM.cs index 940a399..74924d1 100644 --- a/CityWebServer/RequestHandlers/LogCWM.cs +++ b/CityWebServer/RequestHandlers/LogCWM.cs @@ -33,7 +33,7 @@ public LogRequestHandler(IWebServer server) { } - public override IResponseFormatter Handle(HttpListenerRequest request, string slug, string wwwroot) + public override IResponseFormatter Handle(HttpListenerRequest request, String slug, String wwwroot) { String body = String.Format("
    {0}
    ", String.Join("", _server.LogLines.ToArray())); var tokens = TemplateHelper.GetTokenReplacements(_server.CityName, "Log", _server.Mods, body); diff --git a/CityWebServer/RequestHandlers/MessageRequestHandler.cs b/CityWebServer/RequestHandlers/MessageRequestHandler.cs index 0aa0b28..4802c7f 100644 --- a/CityWebServer/RequestHandlers/MessageRequestHandler.cs +++ b/CityWebServer/RequestHandlers/MessageRequestHandler.cs @@ -18,7 +18,7 @@ public MessageRequestHandler(IWebServer server) _chirpRetriever.LogMessage += (sender, args) => { OnLogMessage(args.LogLine); }; } - public override IResponseFormatter Handle(HttpListenerRequest request, string slug, string wwwroot) + public override IResponseFormatter Handle(HttpListenerRequest request, String slug, String wwwroot) { // TODO: Customize request handling. var messages = _chirpRetriever.Messages; diff --git a/CityWebServer/RequestHandlers/RootCWM.cs b/CityWebServer/RequestHandlers/RootCWM.cs index 0607335..1bae107 100644 --- a/CityWebServer/RequestHandlers/RootCWM.cs +++ b/CityWebServer/RequestHandlers/RootCWM.cs @@ -33,7 +33,7 @@ public RootRequestHandler(IWebServer server) { } - public override IResponseFormatter Handle(HttpListenerRequest request, string slug, string wwwroot) + public override IResponseFormatter Handle(HttpListenerRequest request, String slug, String wwwroot) { List links = new List(); foreach (var cwm in _server.Mods.OrderBy(obj => obj.ModID)) @@ -50,9 +50,9 @@ public override IResponseFormatter Handle(HttpListenerRequest request, string sl return new HtmlResponseFormatter(template); } - public override Boolean ShouldHandle(HttpListenerRequest request, string slug) + public override Boolean ShouldHandle(HttpListenerRequest request, String slug) { - string url = request.Url.AbsolutePath; + String url = request.Url.AbsolutePath; return (null != url && (url.Equals("") || url.Equals("/"))); } } diff --git a/CityWebServer/RequestHandlers/TransportRequestHandler.cs b/CityWebServer/RequestHandlers/TransportRequestHandler.cs index 11e494f..43288b7 100644 --- a/CityWebServer/RequestHandlers/TransportRequestHandler.cs +++ b/CityWebServer/RequestHandlers/TransportRequestHandler.cs @@ -17,7 +17,7 @@ public TransportRequestHandler(IWebServer server) { } - public override IResponseFormatter Handle(HttpListenerRequest request, string slug, string wwwroot) + public override IResponseFormatter Handle(HttpListenerRequest request, String slug, String wwwroot) { var transportManager = Singleton.instance; diff --git a/CityWebServer/RequestHandlers/VehicleRequestHandler.cs b/CityWebServer/RequestHandlers/VehicleRequestHandler.cs index 7b64e52..e0de0cc 100644 --- a/CityWebServer/RequestHandlers/VehicleRequestHandler.cs +++ b/CityWebServer/RequestHandlers/VehicleRequestHandler.cs @@ -16,7 +16,7 @@ public VehicleRequestHandler(IWebServer server) { } - public override IResponseFormatter Handle(HttpListenerRequest request, string slug, string wwwroot) + public override IResponseFormatter Handle(HttpListenerRequest request, String slug, String wwwroot) { var vehicleManager = Singleton.instance; diff --git a/SampleWebServerExtension/SampleRequestHandler.cs b/SampleWebServerExtension/SampleRequestHandler.cs index 9f51f03..b700108 100644 --- a/SampleWebServerExtension/SampleRequestHandler.cs +++ b/SampleWebServerExtension/SampleRequestHandler.cs @@ -13,7 +13,7 @@ public SampleRequestHandler(IWebServer server) { } - public override IResponseFormatter Handle(HttpListenerRequest request, string slug, string wwwroot) + public override IResponseFormatter Handle(HttpListenerRequest request, String slug, String wwwroot) { const String content = "This is a sample page!"; diff --git a/SampleWebServerExtension/UserModInfo.cs b/SampleWebServerExtension/UserModInfo.cs index 1bb0afa..4839878 100644 --- a/SampleWebServerExtension/UserModInfo.cs +++ b/SampleWebServerExtension/UserModInfo.cs @@ -32,7 +32,7 @@ public String Description get { return "Adds a sample page to the integrated web server. Doesn't do anything without it!"; } } - public bool TopMenu { + public Boolean TopMenu { get { return true; } } From 0629dee2a0b26747404a0ebcb2b29a7f3638902c Mon Sep 17 00:00:00 2001 From: nezroy Date: Thu, 16 Apr 2015 23:39:08 -0600 Subject: [PATCH 13/14] add support for naked handlers alongside CityWebMods --- .../IRequestHandler.cs | 18 +++ .../RequestHandlerBase.cs | 42 +++++- CityWebServer/CityWebServer.csproj | 1 + CityWebServer/Helpers/UserMod.cs | 2 + CityWebServer/IntegratedWebServer.cs | 81 ++++++----- CityWebServer/RequestHandlers/APICWM.cs | 32 ++++- CityWebServer/RequestHandlers/HandlerCWM.cs | 134 ++++++++++++++++++ CityWebServer/RequestHandlers/LogCWM.cs | 1 - CityWebServer/RequestHandlers/RootCWM.cs | 12 +- 9 files changed, 276 insertions(+), 47 deletions(-) create mode 100644 CityWebServer/RequestHandlers/HandlerCWM.cs diff --git a/CityWebServer.Extensibility/IRequestHandler.cs b/CityWebServer.Extensibility/IRequestHandler.cs index 18e89a2..1a919e2 100644 --- a/CityWebServer.Extensibility/IRequestHandler.cs +++ b/CityWebServer.Extensibility/IRequestHandler.cs @@ -10,9 +10,27 @@ public interface IRequestHandler { IWebServer Server { get; } + /// + /// Gets the priority of this request handler. A request will be handled by the request handler with the lowest priority. + /// + int Priority { get; } + + /// + /// Gets the display name of this request handler. + /// + String Name { get; } + + /// + /// Gets the author of this request handler. + /// + String Author { get; } + /// /// Gets the absolute path to the main page for this request handler. Your class is responsible for handling requests at this path. /// + /// + /// When set to a value other than null, the Web Server will show this url as a link on the home page. + /// String MainPath { get; } /// diff --git a/CityWebServer.Extensibility/RequestHandlerBase.cs b/CityWebServer.Extensibility/RequestHandlerBase.cs index dff651b..1ea3c83 100644 --- a/CityWebServer.Extensibility/RequestHandlerBase.cs +++ b/CityWebServer.Extensibility/RequestHandlerBase.cs @@ -22,6 +22,9 @@ protected void OnLogMessage(String message) #endregion ILogAppender Implementation protected readonly IWebServer _server; + protected int _priority = 100; + protected String _name = null; + protected String _author = null; protected String _mainPath; private RequestHandlerBase() @@ -34,11 +37,41 @@ protected RequestHandlerBase(IWebServer server, String mainPath) _mainPath = mainPath; } + protected RequestHandlerBase(IWebServer server, String name, String author, int priority, String mainPath) + { + _server = server; + _name = name; + _author = author; + _priority = priority; + _mainPath = mainPath; + } + + public RequestHandlerBase(IWebServer server) + { + _server = server; + _mainPath = null; + } + /// /// Gets the server that is currently servicing this instance. /// public virtual IWebServer Server { get { return _server; } } + /// + /// Gets the priority of this request handler. A request will be handled by the request handler with the lowest priority. + /// + public virtual int Priority { get { return _priority; } } + + /// + /// Gets the display name of this request handler. + /// + public virtual String Name { get { return _name; } } + + /// + /// Gets the author of this request handler. + /// + public virtual String Author { get { return _author; } } + /// /// Gets the absolute path to the main page for this request handler. Your class is responsible for handling requests at this path. /// @@ -49,7 +82,14 @@ protected RequestHandlerBase(IWebServer server, String mainPath) /// public virtual Boolean ShouldHandle(HttpListenerRequest request, String slug) { - return (request.Url.AbsolutePath.Equals(String.Format("/{0}{1}", slug, _mainPath), StringComparison.OrdinalIgnoreCase)); + if (slug == null) + { + return request.Url.AbsolutePath.Equals(_mainPath, StringComparison.OrdinalIgnoreCase); + } + else + { + return request.Url.AbsolutePath.Equals(String.Format("/{0}{1}", slug, _mainPath), StringComparison.OrdinalIgnoreCase); + } } /// diff --git a/CityWebServer/CityWebServer.csproj b/CityWebServer/CityWebServer.csproj index 7c16132..208e2a1 100644 --- a/CityWebServer/CityWebServer.csproj +++ b/CityWebServer/CityWebServer.csproj @@ -65,6 +65,7 @@ + diff --git a/CityWebServer/Helpers/UserMod.cs b/CityWebServer/Helpers/UserMod.cs index 8385a63..dedc02f 100644 --- a/CityWebServer/Helpers/UserMod.cs +++ b/CityWebServer/Helpers/UserMod.cs @@ -17,6 +17,7 @@ public class UserMod public IUserMod Mod { get { return _umod; } } public PluginManager.PluginInfo PluginInfo { get { return _pi; } } + public Boolean isMe { get; set; } UserMod(PluginManager.PluginInfo pi) { @@ -33,6 +34,7 @@ public class UserMod { _umod = insts[0]; } + isMe = false; } /// diff --git a/CityWebServer/IntegratedWebServer.cs b/CityWebServer/IntegratedWebServer.cs index 72cff06..6a45931 100644 --- a/CityWebServer/IntegratedWebServer.cs +++ b/CityWebServer/IntegratedWebServer.cs @@ -29,7 +29,8 @@ public class IntegratedWebServer : ThreadingExtensionBase, IWebServer private String _wwwroot = null; private Boolean _secondPass = false; private Boolean _cwmPass = false; - + + private HandlerCWM _hWrap; // the CWM to use for wrapping naked handlers private Dictionary _cwMods; // the list of CityWebMods we've identified and registered private List _usrMods = null; // utility list of all UserMods found in ColossalFramework PluginManager @@ -43,7 +44,7 @@ public class IntegratedWebServer : ThreadingExtensionBase, IWebServer public String CityName { get { return _cityName; } } public String WebRoot { get { return _wwwroot; } } - + /// /// Initializes a new instance of the class. /// @@ -128,12 +129,6 @@ public override void OnReleased() _cwMods.Clear(); _cwMods = null; } - if (null != _usrMods) // TODO: we ought to clean this up much sooner, only needed for init and i don't - // like hanging on to these references for very long - { - _usrMods.Clear(); - _usrMods = null; - } Configuration.SaveSettings(); @@ -235,14 +230,25 @@ private void HandleRequest(HttpListenerRequest request, HttpListenerResponse res } } - if (slug != null && _cwMods.ContainsKey(slug)) + if (slug != null) { - CityWebMod cwm; - _cwMods.TryGetValue(slug, out cwm); - wwwroot = cwm.WebRoot; + CityWebMod cwm = null; try { - handled = cwm.HandleRequest(request, response, slug, wwwroot); + if (_cwMods.ContainsKey(slug)) + { + _cwMods.TryGetValue(slug, out cwm); + wwwroot = cwm.WebRoot; + handled = cwm.HandleRequest(request, response, slug, wwwroot); + } + if (!handled) + { + handled = _hWrap.HandleRequest(request, response, null, this.WebRoot); + if (handled) + { + wwwroot = this.WebRoot; + } + } } catch (Exception ex) { @@ -307,43 +313,52 @@ private static Boolean ServiceFileRequest(String wwwroot, HttpListenerRequest re /// private void RegisterCWM() { - LogMessage("Looking for CityWebMods..."); + LogMessage("Looking for CityWebMods and naked handlers..."); foreach (UserMod um in _usrMods) { - if (!um.PluginInfo.isEnabled || um.PluginInfo.isBuiltin) { continue; } + if (!um.PluginInfo.isEnabled || um.PluginInfo.isBuiltin || um.isMe) { continue; } if (um.Mod == null) { continue; } LogMessage(String.Format("scanning {0} <{1}>...", um.PluginInfo.name, um.Mod.GetType())); CityWebMod cwm = CityWebMod.CreateByReflection(um, this); - if (cwm == null) { continue; } - - String slug = cwm.ModID; - if (slug == null || slug.IsNullOrWhiteSpace()) - { - LogMessage(String.Format("Invalid CityWebMod \"{0}\" by \"{1}\"", cwm.ModName, cwm.ModAuthor)); - } - else if (_cwMods.ContainsKey(slug)) + if (cwm == null) { - LogMessage(String.Format("Conflicting CityWebMod; ID already exists! {0}:\"{1}\" by \"{2}\"", slug, cwm.ModName, cwm.ModAuthor)); + _hWrap.AddHandlers(um, this); } else { - LogMessage(String.Format("Loaded CityWebMod {0}:\"{1}\" by \"{2}\"", slug, cwm.ModName, cwm.ModAuthor)); - _cwMods.Add(slug, cwm); - List hList = cwm.GetHandlers(this); - if (hList != null) + String slug = cwm.ModID; + if (slug == null || slug.IsNullOrWhiteSpace()) { - foreach (IRequestHandler h in hList) + LogMessage(String.Format("Invalid CityWebMod \"{0}\" by \"{1}\"", cwm.ModName, cwm.ModAuthor)); + } + else if (_cwMods.ContainsKey(slug)) + { + LogMessage(String.Format("Conflicting CityWebMod; ID already exists! {0}:\"{1}\" by \"{2}\"", slug, cwm.ModName, cwm.ModAuthor)); + } + else + { + LogMessage(String.Format("Loaded CityWebMod {0}:\"{1}\" by \"{2}\"", slug, cwm.ModName, cwm.ModAuthor)); + _cwMods.Add(slug, cwm); + List hList = cwm.GetHandlers(this); + if (hList != null) { - if (h is ILogAppender) + foreach (IRequestHandler h in hList) { - ILogAppender la = (h as ILogAppender); - la.LogMessage += RequestHandlerLogAppender_OnLogMessage; + if (h is ILogAppender) + { + ILogAppender la = (h as ILogAppender); + la.LogMessage += RequestHandlerLogAppender_OnLogMessage; + } } } } } } + // we are done with _usrMods now; clear it up so we aren't holding references into the engine + _usrMods.Clear(); + _usrMods = null; + _cwmPass = true; } @@ -371,6 +386,7 @@ private void RegisterDefaultCWM() if (um.Mod.Name.Equals("Integrated Web Server")) { // hey it's me! get our web root + um.isMe = true; String testPath = Path.Combine(um.PluginInfo.modPath, "wwwroot"); if (Directory.Exists(testPath)) { @@ -386,6 +402,7 @@ private void RegisterDefaultCWM() _cwMods.Add("root", new RootCWM(this)); _cwMods.Add("log", new LogCWM(this)); _cwMods.Add("api", new APICWM(this)); + _cwMods.Add("handlers", _hWrap = new HandlerCWM(this)); } #region Logging diff --git a/CityWebServer/RequestHandlers/APICWM.cs b/CityWebServer/RequestHandlers/APICWM.cs index eff368a..3195baa 100644 --- a/CityWebServer/RequestHandlers/APICWM.cs +++ b/CityWebServer/RequestHandlers/APICWM.cs @@ -4,7 +4,6 @@ using System.Text; using System.IO; using System.Net; -using ColossalFramework.Plugins; using CityWebServer.Helpers; using CityWebServer.Extensibility; using CityWebServer.Extensibility.Responses; @@ -19,9 +18,11 @@ public APICWM(IWebServer server) _name = "JSON API"; _author = "Rychard"; _isEnabled = true; - _wwwroot = null; + _wwwroot = server.WebRoot; + _topMenu = true; _handlers = new List(); + _handlers.Add(new APIRequestHandler(server, this)); _handlers.Add(new BudgetRequestHandler(server)); _handlers.Add(new BuildingRequestHandler(server)); _handlers.Add(new CityInfoRequestHandler(server)); @@ -29,5 +30,32 @@ public APICWM(IWebServer server) _handlers.Add(new TransportRequestHandler(server)); _handlers.Add(new VehicleRequestHandler(server)); } + + private class APIRequestHandler : RequestHandlerBase + { + private APICWM _container; + public APIRequestHandler(IWebServer server, APICWM container) + : base(server, "/") + { + _container = container; + } + + public override IResponseFormatter Handle(HttpListenerRequest request, String slug, String wwwroot) + { + List links = new List(); + foreach (var h in _container.GetHandlers(_server).OrderBy(obj => obj.MainPath)) + { + if (h.MainPath.Equals("/")) { continue; } + links.Add(String.Format("
  • {2}
  • ", slug, h.MainPath, String.IsNullOrEmpty(h.Name) ? h.MainPath : h.Name)); + } + + String body = String.Format("
      {0}
    ", String.Join("", links.ToArray())); + var tokens = TemplateHelper.GetTokenReplacements(_server.CityName, "API Endpoints", _server.Mods, body); + var template = TemplateHelper.PopulateTemplate("content", wwwroot, tokens); + + return new HtmlResponseFormatter(template); + } + } + } } diff --git a/CityWebServer/RequestHandlers/HandlerCWM.cs b/CityWebServer/RequestHandlers/HandlerCWM.cs new file mode 100644 index 0000000..24b7351 --- /dev/null +++ b/CityWebServer/RequestHandlers/HandlerCWM.cs @@ -0,0 +1,134 @@ +using System; +using System.Collections.Generic; +using System.Net; +using System.Linq; +using System.Reflection; +using CityWebServer.Helpers; +using CityWebServer.Extensibility; +using CityWebServer.Extensibility.Responses; + +namespace CityWebServer.RequestHandlers +{ + class HandlerCWM : CityWebMod + { + public HandlerCWM(IWebServer server) + { + _ID = "handlers"; + _name = "Naked Handlers"; + _author = "nezroy"; + _isEnabled = true; + _wwwroot = server.WebRoot; + _topMenu = true; + + _handlers = new List(); + _handlers.Add(new HandlersRequestHandler(server, this)); + } + + private class HandlersRequestHandler : RequestHandlerBase + { + private HandlerCWM _container; + public HandlersRequestHandler(IWebServer server, HandlerCWM container) + : base(server, "/") + { + _container = container; + } + + public override IResponseFormatter Handle(HttpListenerRequest request, String slug, String wwwroot) + { + List links = new List(); + foreach (var h in _container.GetHandlers(_server).OrderBy(obj => obj.Priority)) + { + if (h.MainPath.Equals("/")) { continue; } + links.Add(String.Format("
  • [{3}] {1} by {2}
  • ", h.MainPath, h.Name, h.Author, h.Priority)); + } + + String body = String.Format("
      {0}
    ", String.Join("", links.ToArray())); + var tokens = TemplateHelper.GetTokenReplacements(_server.CityName, "Naked Handlers", _server.Mods, body); + var template = TemplateHelper.PopulateTemplate("content", wwwroot, tokens); + + return new HtmlResponseFormatter(template); + } + } + + public void AddHandlers(UserMod umod, IWebServer server) + { + Assembly modAssm = umod.Mod.GetType().Assembly; + var handlers = FetchHandlers(modAssm); + foreach (var handler in handlers) + { + IRequestHandler handlerInstance = null; + try + { + if (typeof(RequestHandlerBase).IsAssignableFrom(handler)) + { + handlerInstance = (RequestHandlerBase)Activator.CreateInstance(handler, server); + } + else + { + handlerInstance = (IRequestHandler)Activator.CreateInstance(handler); + } + + if (handlerInstance == null) + { + IntegratedWebServer.LogMessage(String.Format("Request Handler ({0}) could not be instantiated!", handler.Name)); + continue; + } + } + catch (Exception ex) + { + IntegratedWebServer.LogMessage(ex.ToString()); + } + + AddHandler(handlerInstance); + if (handlerInstance is ILogAppender) + { + var logAppender = (handlerInstance as ILogAppender); + logAppender.LogMessage += RequestHandlerLogAppender_OnLogMessage; + } + + IntegratedWebServer.LogMessage(String.Format("added request handler: {0}", handler.FullName)); + } + } + + private static IEnumerable FetchHandlers(Assembly assembly) + { + var assemblyName = assembly.GetName().Name; + + Type[] types = new Type[0]; + try + { + types = assembly.GetTypes(); + } + catch { } + + foreach (var type in types) + { + Boolean isValid = false; + try + { + isValid = typeof(IRequestHandler).IsAssignableFrom(type) && type.IsClass && !type.IsAbstract; + } + catch { } + + if (isValid) + { + yield return type; + } + } + } + + public void AddHandler(IRequestHandler handler) + { + // TODO: use prio to put it in correct order + // int prio = handler.Priority; + _handlers.Add(handler); + } + + // TODO: this is copypasta from IWS; find a cleaner way to do this + private void RequestHandlerLogAppender_OnLogMessage(object sender, LogAppenderEventArgs logAppenderEventArgs) + { + var senderTypeName = sender.GetType().Name; + IntegratedWebServer.LogMessage(logAppenderEventArgs.LogLine, senderTypeName, false); + } + } +} diff --git a/CityWebServer/RequestHandlers/LogCWM.cs b/CityWebServer/RequestHandlers/LogCWM.cs index 74924d1..deb635f 100644 --- a/CityWebServer/RequestHandlers/LogCWM.cs +++ b/CityWebServer/RequestHandlers/LogCWM.cs @@ -4,7 +4,6 @@ using System.Text; using System.IO; using System.Net; -using ColossalFramework.Plugins; using CityWebServer.Helpers; using CityWebServer.Extensibility; using CityWebServer.Extensibility.Responses; diff --git a/CityWebServer/RequestHandlers/RootCWM.cs b/CityWebServer/RequestHandlers/RootCWM.cs index 1bae107..621f9c5 100644 --- a/CityWebServer/RequestHandlers/RootCWM.cs +++ b/CityWebServer/RequestHandlers/RootCWM.cs @@ -4,7 +4,6 @@ using System.Text; using System.IO; using System.Net; -using ColossalFramework.Plugins; using CityWebServer.Helpers; using CityWebServer.Extensibility; using CityWebServer.Extensibility.Responses; @@ -35,16 +34,7 @@ public RootRequestHandler(IWebServer server) public override IResponseFormatter Handle(HttpListenerRequest request, String slug, String wwwroot) { - List links = new List(); - foreach (var cwm in _server.Mods.OrderBy(obj => obj.ModID)) - { - if (!cwm.ModID.Equals("root")) { - links.Add(String.Format("
  • {0} by {2}
  • ", cwm.ModName, cwm.ModID, cwm.ModAuthor)); - } - } - - String body = String.Format("
      {0}
    ", String.Join("", links.ToArray())); - var tokens = TemplateHelper.GetTokenReplacements(_server.CityName, "Home", _server.Mods, body); + var tokens = TemplateHelper.GetTokenReplacements(_server.CityName, "Home", _server.Mods, ""); var template = TemplateHelper.PopulateTemplate("index", wwwroot, tokens); return new HtmlResponseFormatter(template); From de87d971dd81c4c6fece1b91f0870d80969b928a Mon Sep 17 00:00:00 2001 From: nezroy Date: Thu, 16 Apr 2015 23:57:30 -0600 Subject: [PATCH 14/14] split sample project into naked and CWM samples --- CityWebServer.sln | 8 +- .../Properties/AssemblyInfo.cs | 6 +- .../SampleCityWebMod.csproj | 4 +- .../SampleRequestHandler.cs | 2 +- .../UserModInfo.cs | 10 +-- .../Properties/AssemblyInfo.cs | 34 +++++++++ .../SampleNakedHandlerMod.csproj | 73 +++++++++++++++++++ SampleNakedHandlerMod/SampleRequestHandler.cs | 23 ++++++ SampleNakedHandlerMod/UserModInfo.cs | 19 +++++ 9 files changed, 167 insertions(+), 12 deletions(-) rename {SampleWebServerExtension => SampleCityWebMod}/Properties/AssemblyInfo.cs (87%) rename SampleWebServerExtension/SampleWebServerExtension.csproj => SampleCityWebMod/SampleCityWebMod.csproj (96%) rename {SampleWebServerExtension => SampleCityWebMod}/SampleRequestHandler.cs (93%) rename {SampleWebServerExtension => SampleCityWebMod}/UserModInfo.cs (79%) create mode 100644 SampleNakedHandlerMod/Properties/AssemblyInfo.cs create mode 100644 SampleNakedHandlerMod/SampleNakedHandlerMod.csproj create mode 100644 SampleNakedHandlerMod/SampleRequestHandler.cs create mode 100644 SampleNakedHandlerMod/UserModInfo.cs diff --git a/CityWebServer.sln b/CityWebServer.sln index a1e90ff..9898c04 100644 --- a/CityWebServer.sln +++ b/CityWebServer.sln @@ -7,7 +7,9 @@ Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "CityWebServer", "CityWebSer EndProject Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "CityWebServer.Extensibility", "CityWebServer.Extensibility\CityWebServer.Extensibility.csproj", "{DB96EFB4-FA45-4ACC-8D51-7ED37065CC79}" EndProject -Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "SampleWebServerExtension", "SampleWebServerExtension\SampleWebServerExtension.csproj", "{7DF15DF6-C475-4866-9111-F5150C1336E1}" +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "SampleNakedHandlerMod", "SampleNakedHandlerMod\SampleNakedHandlerMod.csproj", "{671CB9E5-3EFD-4466-935E-1BF58470296F}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "SampleCityWebMod", "SampleCityWebMod\SampleCityWebMod.csproj", "{7DF15DF6-C475-4866-9111-F5150C1336E1}" EndProject Global GlobalSection(SolutionConfigurationPlatforms) = preSolution @@ -23,6 +25,10 @@ Global {DB96EFB4-FA45-4ACC-8D51-7ED37065CC79}.Debug|Any CPU.Build.0 = Debug|Any CPU {DB96EFB4-FA45-4ACC-8D51-7ED37065CC79}.Release|Any CPU.ActiveCfg = Release|Any CPU {DB96EFB4-FA45-4ACC-8D51-7ED37065CC79}.Release|Any CPU.Build.0 = Release|Any CPU + {671CB9E5-3EFD-4466-935E-1BF58470296F}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {671CB9E5-3EFD-4466-935E-1BF58470296F}.Debug|Any CPU.Build.0 = Debug|Any CPU + {671CB9E5-3EFD-4466-935E-1BF58470296F}.Release|Any CPU.ActiveCfg = Release|Any CPU + {671CB9E5-3EFD-4466-935E-1BF58470296F}.Release|Any CPU.Build.0 = Release|Any CPU {7DF15DF6-C475-4866-9111-F5150C1336E1}.Debug|Any CPU.ActiveCfg = Debug|Any CPU {7DF15DF6-C475-4866-9111-F5150C1336E1}.Debug|Any CPU.Build.0 = Debug|Any CPU {7DF15DF6-C475-4866-9111-F5150C1336E1}.Release|Any CPU.ActiveCfg = Release|Any CPU diff --git a/SampleWebServerExtension/Properties/AssemblyInfo.cs b/SampleCityWebMod/Properties/AssemblyInfo.cs similarity index 87% rename from SampleWebServerExtension/Properties/AssemblyInfo.cs rename to SampleCityWebMod/Properties/AssemblyInfo.cs index bb65233..051b4be 100644 --- a/SampleWebServerExtension/Properties/AssemblyInfo.cs +++ b/SampleCityWebMod/Properties/AssemblyInfo.cs @@ -4,11 +4,11 @@ // General Information about an assembly is controlled through the following // set of attributes. Change these attribute values to modify the information // associated with an assembly. -[assembly: AssemblyTitle("SampleWebServerExtension")] +[assembly: AssemblyTitle("SampleCityWebMod")] [assembly: AssemblyDescription("")] [assembly: AssemblyConfiguration("")] [assembly: AssemblyCompany("")] -[assembly: AssemblyProduct("SampleWebServerExtension")] +[assembly: AssemblyProduct("SampleCityWebMod")] [assembly: AssemblyCopyright("Copyright © 2015")] [assembly: AssemblyTrademark("")] [assembly: AssemblyCulture("")] @@ -19,7 +19,7 @@ [assembly: ComVisible(false)] // The following GUID is for the ID of the typelib if this project is exposed to COM -[assembly: Guid("4f860aa2-80ae-427e-b9ea-b31b40d67d8a")] +[assembly: Guid("f256642a-950b-4950-a608-ce6891954c29")] // Version information for an assembly consists of the following four values: // diff --git a/SampleWebServerExtension/SampleWebServerExtension.csproj b/SampleCityWebMod/SampleCityWebMod.csproj similarity index 96% rename from SampleWebServerExtension/SampleWebServerExtension.csproj rename to SampleCityWebMod/SampleCityWebMod.csproj index 4753e17..40f1584 100644 --- a/SampleWebServerExtension/SampleWebServerExtension.csproj +++ b/SampleCityWebMod/SampleCityWebMod.csproj @@ -7,8 +7,8 @@ {7DF15DF6-C475-4866-9111-F5150C1336E1} Library Properties - SampleWebServerExtension - SampleWebServerExtension + SampleCityWebMod + SampleCityWebMod v3.5 512 diff --git a/SampleWebServerExtension/SampleRequestHandler.cs b/SampleCityWebMod/SampleRequestHandler.cs similarity index 93% rename from SampleWebServerExtension/SampleRequestHandler.cs rename to SampleCityWebMod/SampleRequestHandler.cs index b700108..ec341ae 100644 --- a/SampleWebServerExtension/SampleRequestHandler.cs +++ b/SampleCityWebMod/SampleRequestHandler.cs @@ -3,7 +3,7 @@ using CityWebServer.Extensibility; using JetBrains.Annotations; -namespace SampleWebServerExtension +namespace SampleCityWebMod { [UsedImplicitly] public class SampleRequestHandler : RequestHandlerBase diff --git a/SampleWebServerExtension/UserModInfo.cs b/SampleCityWebMod/UserModInfo.cs similarity index 79% rename from SampleWebServerExtension/UserModInfo.cs rename to SampleCityWebMod/UserModInfo.cs index 4839878..02d91fa 100644 --- a/SampleWebServerExtension/UserModInfo.cs +++ b/SampleCityWebMod/UserModInfo.cs @@ -3,28 +3,28 @@ using ICities; using CityWebServer.Extensibility; -namespace SampleWebServerExtension +namespace SampleCityWebMod { public class UserModInfo : IUserMod, ICityWebMod { public String Name { - get { return "Sample Web Server Extension"; } + get { return "CityWebServer - Sample CityWebMod"; } } public String ModName { - get { return "Sample Extension"; } + get { return "Sample CWM"; } } public String ModAuthor { - get { return "Rychard"; } + get { return "nezroy"; } } public String ModID { - get { return "sample"; } + get { return "samplecwm"; } } public String Description diff --git a/SampleNakedHandlerMod/Properties/AssemblyInfo.cs b/SampleNakedHandlerMod/Properties/AssemblyInfo.cs new file mode 100644 index 0000000..3391bb0 --- /dev/null +++ b/SampleNakedHandlerMod/Properties/AssemblyInfo.cs @@ -0,0 +1,34 @@ +using System.Reflection; +using System.Runtime.InteropServices; + +// General Information about an assembly is controlled through the following +// set of attributes. Change these attribute values to modify the information +// associated with an assembly. +[assembly: AssemblyTitle("SampleNakedHandlerMod")] +[assembly: AssemblyDescription("")] +[assembly: AssemblyConfiguration("")] +[assembly: AssemblyCompany("")] +[assembly: AssemblyProduct("SampleNakedHandlerMod")] +[assembly: AssemblyCopyright("Copyright © 2015")] +[assembly: AssemblyTrademark("")] +[assembly: AssemblyCulture("")] + +// Setting ComVisible to false makes the types in this assembly not visible +// to COM components. If you need to access a type in this assembly from +// COM, set the ComVisible attribute to true on that type. +[assembly: ComVisible(false)] + +// The following GUID is for the ID of the typelib if this project is exposed to COM +[assembly: Guid("5ae91567-7726-4730-a8a1-8bebf725b9a0")] + +// Version information for an assembly consists of the following four values: +// +// Major Version +// Minor Version +// Build Number +// Revision +// +// You can specify all the values or you can default the Build and Revision Numbers +// by using the '*' as shown below: +// [assembly: AssemblyVersion("1.0.*")] +[assembly: AssemblyVersion("1.0.*")] \ No newline at end of file diff --git a/SampleNakedHandlerMod/SampleNakedHandlerMod.csproj b/SampleNakedHandlerMod/SampleNakedHandlerMod.csproj new file mode 100644 index 0000000..09bf354 --- /dev/null +++ b/SampleNakedHandlerMod/SampleNakedHandlerMod.csproj @@ -0,0 +1,73 @@ + + + + + Debug + AnyCPU + {671CB9E5-3EFD-4466-935E-1BF58470296F} + Library + Properties + SampleNakedHandlerMod + SampleNakedHandlerMod + v3.5 + 512 + + + true + full + false + bin\Debug\ + DEBUG;TRACE + prompt + 4 + + + pdbonly + true + bin\Release\ + TRACE + prompt + 4 + + + + ..\ICities.dll + + + + + + + + + False + ..\UnityEngine.dll + + + + + + + + + + {db96efb4-fa45-4acc-8d51-7ed37065cc79} + CityWebServer.Extensibility + + + + + mkdir "%25LOCALAPPDATA%25\Colossal Order\Cities_Skylines\Addons\Mods\$(SolutionName)_$(ProjectName)" + +del "%25LOCALAPPDATA%25\Colossal Order\Cities_Skylines\Addons\Mods\$(SolutionName)_$(ProjectName)\$(TargetFileName)" + +xcopy /Y "$(TargetPath)" "%25LOCALAPPDATA%25\Colossal Order\Cities_Skylines\Addons\Mods\$(SolutionName)_$(ProjectName)" + + + \ No newline at end of file diff --git a/SampleNakedHandlerMod/SampleRequestHandler.cs b/SampleNakedHandlerMod/SampleRequestHandler.cs new file mode 100644 index 0000000..d5a110c --- /dev/null +++ b/SampleNakedHandlerMod/SampleRequestHandler.cs @@ -0,0 +1,23 @@ +using System; +using System.Net; +using CityWebServer.Extensibility; +using JetBrains.Annotations; + +namespace SampleNakedHandlerMod +{ + [UsedImplicitly] + public class SampleRequestHandler : RequestHandlerBase + { + public SampleRequestHandler(IWebServer server) + : base(server, "Sample Naked Handler", "Rychard", 100, "/samplenaked") + { + } + + public override IResponseFormatter Handle(HttpListenerRequest request, String slug, String wwwroot) + { + const String content = "This is a sample page!"; + + return HtmlResponse(content); + } + } +} \ No newline at end of file diff --git a/SampleNakedHandlerMod/UserModInfo.cs b/SampleNakedHandlerMod/UserModInfo.cs new file mode 100644 index 0000000..f34aa54 --- /dev/null +++ b/SampleNakedHandlerMod/UserModInfo.cs @@ -0,0 +1,19 @@ +using System; +using System.Collections.Generic; +using ICities; + +namespace SampleNakedHandlerMod +{ + public class UserModInfo : IUserMod + { + public String Name + { + get { return "CityWebServer - Sample Naked Handler Mod"; } + } + + public String Description + { + get { return "Adds a sample page to the integrated web server. Doesn't do anything without it!"; } + } + } +} \ No newline at end of file