From e9ec60c31cc53542de82199ae7de5201c06952a0 Mon Sep 17 00:00:00 2001 From: Creeper Lv Date: Tue, 15 Feb 2022 14:29:18 +0800 Subject: [PATCH] CLUNL.CacheManager.0.0.1.0 Initial Release. CLUNL.0.0.6.0 Added a logger. --- CLUNL.CacheManager/CLUNL.CacheManager.csproj | 28 ++ CLUNL.CacheManager/Cache.cs | 109 +++++ CLUNL.CacheManager/CacheList.cs | 17 + CLUNL.CacheManager/Cacher.cs | 250 ++++++++++ CLUNL.CacheManager/CacherSettings.cs | 19 + CLUNL.CacheManager/Domain.cs | 25 + Creeper Lv's Universal dotNet Library.sln | 12 +- .../CLUNL.csproj | 81 ++-- .../Diagnostics/Logger.cs | 426 ++++++++++++++++++ 9 files changed, 924 insertions(+), 43 deletions(-) create mode 100644 CLUNL.CacheManager/CLUNL.CacheManager.csproj create mode 100644 CLUNL.CacheManager/Cache.cs create mode 100644 CLUNL.CacheManager/CacheList.cs create mode 100644 CLUNL.CacheManager/Cacher.cs create mode 100644 CLUNL.CacheManager/CacherSettings.cs create mode 100644 CLUNL.CacheManager/Domain.cs create mode 100644 Creeper Lv's Universal dotNet Library/Diagnostics/Logger.cs diff --git a/CLUNL.CacheManager/CLUNL.CacheManager.csproj b/CLUNL.CacheManager/CLUNL.CacheManager.csproj new file mode 100644 index 0000000..5360664 --- /dev/null +++ b/CLUNL.CacheManager/CLUNL.CacheManager.csproj @@ -0,0 +1,28 @@ + + + + netstandard2.1 + 0.0.1.0 + false + Copyright (C) 2022 Creeper Lv + + https://github.com/creeperlv/CLUNL + https://github.com/creeperlv/CLUNL + LICENSE + Creeper Lv + Creeper Lv + CacheManager of Creeper Lv's Universal dotNet Library + True + + + + + + + + True + + + + + diff --git a/CLUNL.CacheManager/Cache.cs b/CLUNL.CacheManager/Cache.cs new file mode 100644 index 0000000..9bca66a --- /dev/null +++ b/CLUNL.CacheManager/Cache.cs @@ -0,0 +1,109 @@ +using CLUNL.Diagnostics; +using System; +using System.IO; +using System.Threading.Tasks; + +namespace CLUNL.CacheManager +{ + /// + /// A cache entry. + /// + public class Cache + { + /// + /// The url of the request. + /// + public string RequestUrl; + /// + /// The path were the item is stored. + /// + public string StorePath; + /// + /// The time of when the cache is updated. + /// + public DateTime RequestDate; + /// + /// Requeset count. + /// + public int RequestCount; + /// + /// Obtain the file. + /// + /// + public FileInfo ObtainFile() + { + RequestCount++; + return new FileInfo(StorePath); + } + /// + /// Update the file. + /// + /// + public async Task UpdateFile() + { + Logger.WriteLine("Updating cache..."); + var f = new FileInfo(StorePath); + if (!f.Directory.Exists) f.Directory.Create(); + try + { + using (var t = await Cacher.client.GetAsync(RequestUrl)) + { + if (File.Exists(StorePath)) + File.Delete(StorePath); + using (var __f = File.Create(StorePath)) + { + await t.Content.CopyToAsync(__f); + + } + } + + + } + catch (Exception) + { + return null; + } + RequestDate = DateTime.Now; + RequestCount = 0; + Logger.WriteLine($"Cached -> {StorePath}"); + return f; + } + /// + /// If the cache is valid. + /// + /// + public bool isValid() + { + if (File.Exists(StorePath)) + { + return !isExpired(); + } + else + { + Logger.WriteLine("Invalid Cache: Cache fild not found."); + return false; + } + } + /// + /// If the cache is expired. + /// + /// + public bool isExpired() + { + + var NOW = DateTime.Now; + var __delta = NOW - this.RequestDate; + if (__delta.Duration() > TimeSpan.FromDays(1)) + { + Logger.WriteLine(MessageLevel.Warn, "Cache Expired for time."); + return true; + } + if (RequestCount > 5) + { + Logger.WriteLine(MessageLevel.Warn, "Cache Expired for using count."); + return true; + } + return false; + } + } +} diff --git a/CLUNL.CacheManager/CacheList.cs b/CLUNL.CacheManager/CacheList.cs new file mode 100644 index 0000000..d66a3cf --- /dev/null +++ b/CLUNL.CacheManager/CacheList.cs @@ -0,0 +1,17 @@ +using System; +using System.Collections.Generic; + +namespace CLUNL.CacheManager +{ + /// + /// The list of caches. + /// + [Serializable] + public class CacheList + { + /// + /// Domains + /// + public List Domains = new List(); + } +} diff --git a/CLUNL.CacheManager/Cacher.cs b/CLUNL.CacheManager/Cacher.cs new file mode 100644 index 0000000..69745df --- /dev/null +++ b/CLUNL.CacheManager/Cacher.cs @@ -0,0 +1,250 @@ +using CLUNL.Diagnostics; +using Newtonsoft.Json; +using System; +using System.IO; +using System.Linq; +using System.Net; +using System.Net.Http; +using System.Runtime.CompilerServices; +using System.Text; +using System.Threading.Tasks; + +namespace CLUNL.CacheManager +{ + /// + /// The main access point of CacheManager. + /// + public class Cacher + { + internal static HttpClient client = new HttpClient(); + /// + /// Settings of cache manager. + /// + public static CacherSettings Settings = new CacherSettings { ExpireTimeSpan = TimeSpan.FromDays(1), ExpireUsingCount = 10 }; + static JsonSerializerSettings settings = new JsonSerializerSettings + { + Formatting = Formatting.Indented, + NullValueHandling = NullValueHandling.Ignore, + DateFormatHandling = DateFormatHandling.IsoDateFormat, + MissingMemberHandling = MissingMemberHandling.Ignore, + DefaultValueHandling = DefaultValueHandling.Ignore, + FloatParseHandling = FloatParseHandling.Double, + StringEscapeHandling = StringEscapeHandling.EscapeHtml + }; + static CacheList CurrentList; + static string StorageBase; + static string Index; + /// + /// Product Name, such as "CLUNL", then, the cache will be stored in "/tmp/CLUNL/". + /// + public static string ProductName="Temp"; + /// + /// Initialize the cache manager. + /// + public static void Init() + { + StorageBase = Path.Combine(Path.GetTempPath(), ProductName); + Index = Path.Combine(StorageBase, "index.installation"); + if (!Directory.Exists(StorageBase)) + { + Directory.CreateDirectory(StorageBase); + CurrentList = new CacheList(); + File.WriteAllText(Index, JsonConvert.SerializeObject(CurrentList)); + } + else + { + if (File.Exists(Index)) + { + CurrentList = JsonConvert.DeserializeObject(File.ReadAllText(Index), settings); + } + else + { + CurrentList = new CacheList(); + File.WriteAllText(Index, JsonConvert.SerializeObject(CurrentList)); + } + } + AutoMaintain(); + } + /// + /// The interval between of maintenances. + /// + public static int MaintainTimeInterval = 5000; + static void AutoMaintain() + { + Task.Run(async () => + { + while (true) + { + await Task.Delay(MaintainTimeInterval); + Maintain(); + } + }); + } + /// + /// Force to require a maintenance. + /// + public static void ForceMaintain() + { + Maintain(); + } + [MethodImpl(MethodImplOptions.AggressiveInlining)] + static void Maintain() + { + lock (CurrentList) + { + File.Delete(Index); + File.WriteAllText(Index, JsonConvert.SerializeObject(CurrentList, settings)); + foreach (var d in CurrentList.Domains) + { + foreach (var c in d.Caches) + { + if (c.isExpired()) + { + if (File.Exists(c.StorePath)) + { + File.Delete(c.StorePath); + c.RequestCount = 0; + Logger.WriteLine("Maintenance",$"Cache \"{c.RequestUrl}\" is now forced to be invalid due to it is expired."); + } + } + } + } + } + } + /// + /// Obtain a cache. + /// + /// + /// + public static Cache FindCache(string URL) + { + + Uri uri = new Uri(URL); + var __host = uri.GetComponents(UriComponents.Host, UriFormat.UriEscaped).ToLower(); + var __file = uri.GetComponents(UriComponents.Path, UriFormat.UriEscaped); + var __port = uri.GetComponents(UriComponents.StrongPort, UriFormat.UriEscaped); + var __query = uri.GetComponents(UriComponents.Query, UriFormat.UriEscaped); + lock (CurrentList) + { + foreach (var item in CurrentList.Domains) + { + if (item.Host == __host && item.Port == __port) + { + foreach (var cache in item.Caches) + { + if (cache.RequestUrl == URL) + { + return cache; + } + } + return null; + } + } + } + + return null; + } + static async Task CreateCache(string URL) + { + Logger.WriteLine($"Creating cache for {URL}."); + + Uri uri = new Uri(URL); + var __host = uri.GetComponents(UriComponents.Host, UriFormat.UriEscaped).ToLower(); + var __file = uri.GetComponents(UriComponents.Path, UriFormat.UriEscaped); + var __port = uri.GetComponents(UriComponents.StrongPort, UriFormat.UriEscaped); + var __query = uri.GetComponents(UriComponents.Query, UriFormat.UriEscaped); + Domain D = null; + lock (CurrentList) + { + + foreach (var item in CurrentList.Domains) + { + if (item.Host == __host && item.Port == __port) + { + Logger.WriteLine($"Found domain: {item.Host} : {item.Port}"); + D = item; + break; + } + } + } + if (D != null) + { + Cache cache = new Cache(); + cache.RequestCount = 0; + cache.RequestDate = DateTime.Now; + cache.RequestUrl = URL; + cache.StorePath = Path.Combine(StorageBase, __host, __port, __file); + var f = await cache.UpdateFile(); + lock (D) + { + D.Caches.Add(cache); + } + return f; + } + else + { + Logger.WriteLine(MessageLevel.Warn, $"Domain not found."); + var d = new Domain { Host = __host, Port = __port }; + lock (CurrentList) + { + CurrentList.Domains.Add(d); + } + Logger.WriteLine($"Created domain:{d.Host}:{d.Port}"); + + Cache cache = new Cache(); + cache.RequestCount = 0; + cache.RequestDate = DateTime.Now; + cache.RequestUrl = URL; + cache.StorePath = Path.Combine(StorageBase, __host, __port, __file); + var f = await cache.UpdateFile(); + d.Caches.Add(cache); + return f; + } + } + /// + /// Request a file. + /// + /// + /// + public static async Task Request(string URL) + { + Logger.WriteLine($"Request:{URL}"); + var cache = FindCache(URL); + if (cache == null) + { + Logger.WriteLine(MessageLevel.Warn, $"Cache not found."); + return await CreateCache(URL); + } + else + { + if (cache.isValid()) + { + return cache.ObtainFile(); + } + else + { + return await cache.UpdateFile(); + } + } + } + /// + /// Force to request a file. (Force update a cache.) + /// + /// + /// + public static async Task ForceRequest(string URL) + { + Logger.WriteLine($"Request:{URL}"); + var cache = FindCache(URL); + if (cache == null) + { + Logger.WriteLine(MessageLevel.Warn, $"Cache not found."); + return await CreateCache(URL); + } + else + { + return await cache.UpdateFile(); + } + } + } +} diff --git a/CLUNL.CacheManager/CacherSettings.cs b/CLUNL.CacheManager/CacherSettings.cs new file mode 100644 index 0000000..565e95a --- /dev/null +++ b/CLUNL.CacheManager/CacherSettings.cs @@ -0,0 +1,19 @@ +using System; + +namespace CLUNL.CacheManager +{ + /// + /// Configuration of cache manager settings. + /// + public struct CacherSettings + { + /// + /// How long will a cache expire after an update. + /// + public TimeSpan ExpireTimeSpan; + /// + /// How many times of usage will a cache expire after an update. + /// + public int ExpireUsingCount; + } +} diff --git a/CLUNL.CacheManager/Domain.cs b/CLUNL.CacheManager/Domain.cs new file mode 100644 index 0000000..3073b47 --- /dev/null +++ b/CLUNL.CacheManager/Domain.cs @@ -0,0 +1,25 @@ +using System; +using System.Collections.Generic; + +namespace CLUNL.CacheManager +{ + /// + /// Definition of network domain. + /// + [Serializable] + public class Domain + { + /// + /// Hostname. + /// + public string Host; + /// + /// Port of domain + /// + public string Port; + /// + /// Caches belong to the domain + /// + public List Caches = new List(); + } +} diff --git a/Creeper Lv's Universal dotNet Library.sln b/Creeper Lv's Universal dotNet Library.sln index 88698df..07c7ec9 100644 --- a/Creeper Lv's Universal dotNet Library.sln +++ b/Creeper Lv's Universal dotNet Library.sln @@ -1,7 +1,7 @@  Microsoft Visual Studio Solution File, Format Version 12.00 -# Visual Studio Version 16 -VisualStudioVersion = 16.0.31624.102 +# Visual Studio Version 17 +VisualStudioVersion = 17.0.32126.317 MinimumVisualStudioVersion = 10.0.40219.1 Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "CLUNL", "Creeper Lv's Universal dotNet Library\CLUNL.csproj", "{597737F0-9561-49FF-B000-F45DF6156B2D}" EndProject @@ -37,7 +37,9 @@ Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "CLUNL.Localization", "CLUNL EndProject Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Samples", "Samples", "{EA434C75-880E-4D80-9415-8B91AE5431BA}" EndProject -Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "SampleConsoleApp", "SampleConsoleApp\SampleConsoleApp.csproj", "{01F9D58F-B60C-48F9-A2E7-9AEFC2E77321}" +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "SampleConsoleApp", "SampleConsoleApp\SampleConsoleApp.csproj", "{01F9D58F-B60C-48F9-A2E7-9AEFC2E77321}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "CLUNL.CacheManager", "CLUNL.CacheManager\CLUNL.CacheManager.csproj", "{B7F8665B-E9F3-47E1-B56B-37346C6BE50D}" EndProject Global GlobalSection(SolutionConfigurationPlatforms) = preSolution @@ -93,6 +95,10 @@ Global {01F9D58F-B60C-48F9-A2E7-9AEFC2E77321}.Debug|Any CPU.Build.0 = Debug|Any CPU {01F9D58F-B60C-48F9-A2E7-9AEFC2E77321}.Release|Any CPU.ActiveCfg = Release|Any CPU {01F9D58F-B60C-48F9-A2E7-9AEFC2E77321}.Release|Any CPU.Build.0 = Release|Any CPU + {B7F8665B-E9F3-47E1-B56B-37346C6BE50D}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {B7F8665B-E9F3-47E1-B56B-37346C6BE50D}.Debug|Any CPU.Build.0 = Debug|Any CPU + {B7F8665B-E9F3-47E1-B56B-37346C6BE50D}.Release|Any CPU.ActiveCfg = Release|Any CPU + {B7F8665B-E9F3-47E1-B56B-37346C6BE50D}.Release|Any CPU.Build.0 = Release|Any CPU EndGlobalSection GlobalSection(SolutionProperties) = preSolution HideSolutionNode = FALSE diff --git a/Creeper Lv's Universal dotNet Library/CLUNL.csproj b/Creeper Lv's Universal dotNet Library/CLUNL.csproj index 1bf7f26..7adb0ae 100644 --- a/Creeper Lv's Universal dotNet Library/CLUNL.csproj +++ b/Creeper Lv's Universal dotNet Library/CLUNL.csproj @@ -1,49 +1,50 @@  - - - CLUNL - CLUNL - 0.0.5.0 - false - Copyright (C) 2020-2021 Creeper Lv - - https://github.com/creeperlv/CLUNL - https://github.com/creeperlv/CLUNL - LICENSE - Creeper Lv - Creeper Lv - Creeper Lv's Universal dotNet Library - Improved SSS. - netstandard2.1 - + + + CLUNL + CLUNL + 0.0.6.0 + false + Copyright (C) 2020-2022 Creeper Lv + + https://github.com/creeperlv/CLUNL + https://github.com/creeperlv/CLUNL + LICENSE + Creeper Lv + Creeper Lv + Creeper Lv's Universal dotNet Library + CLUNL, please see github for release note. + netstandard2.1 + True + - - bin\Debug\ - DEBUG;TRACE;ENABLE_UNSTABLE - bin\Debug\netstandard2.0\CLUNL.xml - true - + + bin\Debug\ + DEBUG;TRACE;ENABLE_UNSTABLE + + true + - - bin\Release\ - TRACE;ENABLE_UNSTABLE - bin\Release\netstandard2.0\CLUNL.xml - true - AnyCPU - true - + + bin\Release\ + TRACE;ENABLE_UNSTABLE + + true + AnyCPU + true + - - - True - - - + + + True + + + - - - + + + diff --git a/Creeper Lv's Universal dotNet Library/Diagnostics/Logger.cs b/Creeper Lv's Universal dotNet Library/Diagnostics/Logger.cs new file mode 100644 index 0000000..88129fb --- /dev/null +++ b/Creeper Lv's Universal dotNet Library/Diagnostics/Logger.cs @@ -0,0 +1,426 @@ +using System; +using System.Collections.Generic; +using System.Diagnostics; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace CLUNL.Diagnostics +{ + /// + /// Logger + /// + public class Logger + { + static bool __init=false; + static List Loggers = new List(); + /// + /// Init the logger, call in the very early stage of the program. + /// + public static void Init() + { + if (__init) return; + __init = true; + RegisterLogger(new DefaultConsoleLogger()); + RegisterLogger(new DefaultTraceLogger()); + } + /// + /// Unregister a logger. + /// + /// + public static void UnregisterLogger(ILogger L) + { + lock (Loggers) + { + Loggers.Remove(L); + } + } + /// + /// Register a logger. + /// + /// + public static void RegisterLogger(ILogger l) + { + lock (Loggers) + { + Loggers.Add(l); + } + } + /// + /// Write a line. + /// + /// + public static void WriteLine(object o) + { + foreach (var item in Loggers) + { + item.WriteLine(o); + } + } + /// + /// Write an object. + /// + /// + public static void Write(object o) + { + foreach (var item in Loggers) + { + item.Write(o); + } + } + /// + /// Write two objects in format of "o0: o1". + /// + /// + /// + public static void Write(object o0, object o1) + { + foreach (var item in Loggers) + { + item.Write(o0,o1); + } + } + /// + /// Write a line in format of "o0: o1" + /// + /// + /// + public static void WriteLine(object o0, object o1) + { + foreach (var item in Loggers) + { + item.WriteLine(o0, o1); + } + } + /// + /// Write a line in format of "o0: o1" + /// + /// + /// + /// + public static void WriteLine(MessageLevel ML, object o0, object o1) + { + foreach (var item in Loggers) + { + item.WriteLine(ML, o0, o1); + } + } + /// + /// Write a line. + /// + /// + /// + public static void WriteLine(MessageLevel ML, object o) + { + foreach (var item in Loggers) + { + item.WriteLine(ML, o); + } + } + } + /// + /// Default logger, write to trace. + /// + public class DefaultTraceLogger : ILogger + { + /// + /// Write a message. + /// + /// + public void Write(object o) + { + Write(MessageLevel.Normal, o); + } + /// + /// Write a message. + /// + /// + /// + public void Write(MessageLevel ML, object o) + { + Trace.Write("["); + WriteMessageLevel(ML); + Trace.Write("]"); + Trace.Write(o); + } + + private static void WriteMessageLevel(MessageLevel ML) + { + switch (ML) + { + case MessageLevel.Normal: + Trace.Write("NORMAL"); + break; + case MessageLevel.Warn: + Trace.Write("WARN"); + break; + case MessageLevel.Error: + Trace.Write("ERROR"); + break; + default: + break; + } + } + /// + /// Write two objects in form of "o0: o1" + /// + /// + /// + public void Write(object o0, object o1) + { + Write(MessageLevel.Normal, o0, o1); + } + + /// + /// Write two objects in form of "o0: o1" + /// + /// + /// + /// + public void Write(MessageLevel ML, object o0, object o1) + { + Trace.Write("["); + WriteMessageLevel(ML); + Trace.Write("]"); + Trace.Write(o0); + Trace.Write(": "); + Trace.Write(o1); + } + /// + /// Write a line. + /// + /// + public void WriteLine(object o) + { + WriteLine(MessageLevel.Normal, o); + } + /// + /// Write a line. + /// + /// + /// + public void WriteLine(MessageLevel ML, object o) + { + Trace.Write("["); + WriteMessageLevel(ML); + Trace.Write("]"); + Trace.WriteLine(o); + + } + /// + /// Write two object in format of "o0: o1" + /// + /// + /// + public void WriteLine(object o0, object o1) + { + WriteLine(MessageLevel.Normal, o0, o1); + } + /// + /// Write two object in format of "o0: o1" + /// + /// + /// + /// + public void WriteLine(MessageLevel ML, object o0, object o1) + { + Trace.Write("["); + WriteMessageLevel(ML); + Trace.Write("]"); + Trace.Write(o0); + Trace.Write(": "); + Trace.WriteLine(o1); + } + } + /// + /// Default logger, write to console. + /// + public class DefaultConsoleLogger : ILogger + { + /// + /// Write a message. + /// + /// + public void Write(object o) + { + Write(MessageLevel.Normal, o); + } + /// + /// Write a message. + /// + /// + /// + public void Write(MessageLevel ML, object o) + { + Console.Write("["); + WriteMessageLevel(ML); + Console.Write("]"); + Console.Write(o); + } + + private static void WriteMessageLevel(MessageLevel ML) + { + switch (ML) + { + case MessageLevel.Normal: + Console.ForegroundColor = ConsoleColor.Green; + Console.Write("NORMAL"); + break; + case MessageLevel.Warn: + Console.ForegroundColor = ConsoleColor.Yellow; + Console.Write("WARN"); + break; + case MessageLevel.Error: + Console.ForegroundColor = ConsoleColor.Red; + Console.Write("ERROR"); + break; + default: + break; + } + Console.ResetColor(); + } + /// + /// Write two object in format of "o0: o1" + /// + /// + /// + public void Write(object o0, object o1) + { + Write(MessageLevel.Normal, o0, o1); + } + /// + /// Write two object in format of "o0: o1" + /// + /// + /// + /// + public void Write(MessageLevel ML, object o0, object o1) + { + Console.Write("["); + WriteMessageLevel(ML); + Console.Write("]"); + Console.Write(o0); + Console.Write(": "); + Console.Write(o1); + } + /// + /// Write a line. + /// + /// + public void WriteLine(object o) + { + WriteLine(MessageLevel.Normal, o); + } + /// + /// Write a line. + /// + /// + /// + public void WriteLine(MessageLevel ML, object o) + { + Console.Write("["); + WriteMessageLevel(ML); + Console.Write("]"); + Console.WriteLine(o); + + } + + /// + /// Write two object in format of "o0: o1" + /// + /// + /// + public void WriteLine(object o0, object o1) + { + WriteLine(MessageLevel.Normal, o0, o1); + } + /// + /// Write two object in format of "o0: o1" + /// + /// + /// + /// + public void WriteLine(MessageLevel ML, object o0, object o1) + { + Console.Write("["); + WriteMessageLevel(ML); + Console.Write("]"); + Console.Write(o0); + Console.Write(": "); + Console.WriteLine(o1); + } + } + /// + /// Interface of loggers. + /// + public interface ILogger + { + /// + /// Write a line. + /// + /// + void Write(object o); + /// + /// Write a line. + /// + /// + /// + void Write(MessageLevel ML, object o); + /// + /// Write two object in format of "o0: o1" + /// + /// + /// + void Write(object o0, object o1); + /// + /// Write two object in format of "o0: o1" + /// + /// + /// + /// + void Write(MessageLevel ML, object o0, object o1); + /// + /// Write an object. + /// + /// + void WriteLine(object o); + /// + /// Write an object. + /// + /// + /// + void WriteLine(MessageLevel ML, object o); + /// + /// Write two object in format of "o0: o1" + /// + /// + /// + void WriteLine(object o0, object o1); + /// + /// Write two object in format of "o0: o1" + /// + /// + /// + /// + void WriteLine(MessageLevel ML, object o0, object o1); + + } + /// + /// Level of message. + /// + public enum MessageLevel + { + /// + /// A normal message. + /// + Normal, + /// + /// A warning message. + /// + Warn, + /// + /// An error message. + /// + Error + } +}