/* Copyright (c) Microsoft Corporation All rights reserved. Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0 THIS CODE IS PROVIDED *AS IS* BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, EITHER EXPRESS OR IMPLIED, INCLUDING WITHOUT LIMITATION ANY IMPLIED WARRANTIES OR CONDITIONS OF TITLE, FITNESS FOR A PARTICULAR PURPOSE, MERCHANTABLITY OR NON-INFRINGEMENT. See the Apache Version 2.0 License for specific language governing permissions and limitations under the License. */ using System; using System.Collections; using System.Collections.Generic; using System.ComponentModel; using System.Diagnostics; using System.Drawing; // for color using System.IO; using System.Linq; using System.Net; using System.Reflection; using System.Runtime.InteropServices; using System.Text; using System.Windows.Forms; using System.Xml; using System.Xml.Serialization; using System.Text.RegularExpressions; using System.Threading; using System.Security.Cryptography; // Implement here generally-useful tools. namespace Microsoft.Research.Tools { /// /// An error handling function. /// /// Message to display. /// Kind of message. public delegate void StatusReporter(string message, StatusKind messageKind); /// /// Kind of status displayed. /// public enum StatusKind { /// /// Everything is fine. /// OK, /// /// Some error occurred. /// Error, /// /// A new long-term operation is initiated. /// LongOp, }; /// /// Communication management with background activities. /// public struct CommManager { /// /// Used to report status. /// public StatusReporter Status; /// /// Used to report progress. /// public Action Progress; /// /// Used to cancel activities. /// public CancellationToken Token; /// /// Create a communication manager. /// /// Status to report errors. /// Action to report progress. /// Token to cancel computations. public CommManager(StatusReporter status, Action progress, CancellationToken token) { this.Status = status; this.Progress = progress; this.Token = token; } } /// /// Untyped version of work item. /// public interface IBackgroundWorkItem { /// /// Description of the work item. /// string Description { get; } /// /// Perform the background work. /// /// Queue for work. /// Delegate used to report errors. /// Delegate used to report progress. /// If true for an item in the queue, cancel it. void Queue(BackgroundWorkQueue queue, StatusReporter reporter, Action progressReporter, Func cancel); /// /// True if the item has been cancelled. /// bool Cancelled { get; } /// /// Cancel this item. /// void Cancel(); /// /// Run the computation (called on a background thread). /// void Run(); /// /// Run the continuation (called on the foreground thread). /// Exception that occurred during background work (or null). /// void RunContinuation(Exception ex); /// /// Can be used to cancel this work item. /// CancellationTokenSource TokenSource { get; } } /// /// A piece of work to be performed in the background. /// /// Type of result from computation. public class BackgroundWorkItem : IBackgroundWorkItem { /// /// Computation to invoke. If the computation is not cancelled the result is passed as the second argument to the continuation. /// public Func Computation { get; protected set; } /// /// Function to call when the work is completed. The first argument is 'true' if the computation was not cancelled. The second argument is the result of the computation. /// public Action Continuation { get; protected set; } /// /// Structure used to signal to the Computation. /// StatusReporter reporter; /// /// Progress reporter. /// private Action progress; /// /// Result of background computation. /// T Result; /// /// Description of the background work. /// public string Description { get; protected set; } /// /// True if item has been cancelled. /// public bool Cancelled { get; protected set; } /// /// Queue containing item. /// private BackgroundWorkQueue queue; /// /// Source for cancellation token. /// public CancellationTokenSource TokenSource { get; protected set; } // ReSharper disable ConvertToConstant.Local bool TraceAsync = // ReSharper restore ConvertToConstant.Local #if DEBUG_WORKQUEUE #else false; #endif // ReSharper disable once StaticFieldInGenericType private static int crtid; /// /// Create a background work item. /// /// Computation to perform on a background thread. Ideally this should always be a static method. /// Continuation to invoke on the foreground thread when work is done. /// Description of the background work. public BackgroundWorkItem(Func computation, Action continuation, string description) { this.Description = description; this.Computation = computation; this.Continuation = continuation; this.reporter = null; this.queue = null; this.Id = crtid++; this.TokenSource = new CancellationTokenSource(); } /// /// Perform the background work. /// /// Worker which does the work. /// Delegate used to report errors. /// Delegate used to report progress. /// If true for an item, cancel it. public void Queue(BackgroundWorkQueue q, StatusReporter rep, Action progressReporter, Func cancel) { if (TraceAsync) Console.WriteLine("{0} Queueing {1}", Utilities.PreciseTime, this.Description); this.queue = q; this.reporter = rep; this.progress = progressReporter; this.queue.CancelMatching(cancel); this.queue.Enqueue(this); } /// /// Run the computation; called on the background thread. /// public void Run() { DateTime startTime = DateTime.Now; if (TraceAsync) Console.WriteLine("{0} Running function {1}", Utilities.PreciseTime, this.Description); try { CommManager manager = new CommManager(this.reporter, this.progress, this.TokenSource.Token); this.Result = this.Computation(manager); } catch (Exception ex) { this.reporter(this.Description + " failed with " + ex.Message, StatusKind.Error); Console.WriteLine(ex); this.Cancelled = true; } DateTime endTime = DateTime.Now; TimeSpan duration = endTime - startTime; Console.WriteLine("Operation <" + this.Description + "> took " + duration); } /// /// Run the continuation; called on the foreground thread. /// /// Exception that occurred during background work. public void RunContinuation(Exception ex) { if (ex != null) this.reporter(this.Id + ": Exception during background work: " + ex.Message, StatusKind.Error); if (TraceAsync) Console.WriteLine("{0} Running continuation of {1}", Utilities.PreciseTime, this.Description); this.Continuation(this.Cancelled, this.Result); } /// /// Cancel the work. /// public void Cancel() { if (TraceAsync) Console.WriteLine("{1}/{0}: Cancelling", this.Description, this.Id); this.Cancelled = true; this.TokenSource.Cancel(); this.queue.CancelMe(this); } /// /// Unique id of this work item. /// public int Id { get; protected set; } } /// /// Maintains a queue of tasks for a background worker. /// public class BackgroundWorkQueue { /// /// Worker performing the work. /// public BackgroundWorker BackgroundWorker { get; protected set; } /// /// Queue of items waiting for worker. /// readonly List queue; /// /// Currently executing work item. /// IBackgroundWorkItem current; private ToolStripStatusLabel currentItemLabel, queueSizeLabel; /// /// Create a background work queue servicing a specified worker. /// /// Worker to use. /// Label where the current work is displayed. /// Label where the queue size is displayed. public BackgroundWorkQueue(BackgroundWorker worker, ToolStripStatusLabel current, ToolStripStatusLabel queue) { this.currentItemLabel = current; this.queueSizeLabel = queue; if (worker == null) throw new ArgumentNullException("worker"); this.BackgroundWorker = worker; this.BackgroundWorker.WorkerSupportsCancellation = true; this.BackgroundWorker.RunWorkerCompleted += this.worker_RunWorkerCompleted; this.BackgroundWorker.DoWork += this.worker_DoWork; this.queue = new List(); this.current = null; this.stopped = false; } /// /// Called on background thread to do the work specified by the 'current' item. /// /// Unused. /// Unused. void worker_DoWork(object sender, DoWorkEventArgs e) { if (this.stopped || this.current == null) return; #if DEBUG_WORKQUEUE #endif if (!this.current.Cancelled) { this.current.Run(); } else { #if DEBUG_WORKQUEUE #endif } if (this.BackgroundWorker.CancellationPending) e.Cancel = true; } private bool stopped; /// /// Called when the worker is completed. /// /// Unused. /// Event describing completion. void worker_RunWorkerCompleted(object sender, RunWorkerCompletedEventArgs e) { if (this.current != null) { #if DEBUG_WORKQUEUE #endif IBackgroundWorkItem crt = this.current; this.current = null; if (this.currentItemLabel != null) this.currentItemLabel.Text = ""; crt.RunContinuation(e.Error); } this.Kick(); } /// /// Add an item to the work queue. /// /// Item to add. internal void Enqueue(IBackgroundWorkItem item) { this.queue.Add(item); this.Kick(); } /// /// Try to run one more item from the queue. /// public void Kick() { if (this.BackgroundWorker.IsBusy) return; if (this.queue.Count == 0) return; if (this.current != null) throw new Exception("current is not null"); this.current = this.queue[0]; if (this.currentItemLabel != null) this.currentItemLabel.Text = "Doing " + this.current.Description; this.queue.RemoveAt(0); if (this.queueSizeLabel != null) this.queueSizeLabel.Text = "Pending " + this.queue.Count + " items"; this.Start(); } /// /// Start execution of the current item. /// private void Start() { if (this.BackgroundWorker.IsBusy) throw new Exception("Worker is busy"); this.BackgroundWorker.RunWorkerAsync(); } /// /// Cancel everything matching the filter from the queue. /// /// Filter: the items where the filter is true will be cancelled. internal void CancelMatching(Func filter) { if (filter == null) return; foreach (IBackgroundWorkItem item in this.queue) { if (filter(item)) { #if DEBUG_WORKQUEUE #endif item.Cancel(); } } if (this.current != null && filter(this.current)) { this.Cancel(this.current); } } /// /// Cancel the specified item. /// /// Item to cancel. internal void Cancel(IBackgroundWorkItem backgroundWorkItem) { if (this.current == backgroundWorkItem) { this.BackgroundWorker.CancelAsync(); this.current.Cancel(); } } /// /// An item asks to be cancelled. /// /// Item to cancel. internal void CancelMe(IBackgroundWorkItem backgroundWorkItem) { if (this.current == backgroundWorkItem) { this.BackgroundWorker.CancelAsync(); } } /// /// Stop the queue. /// public void Stop() { this.stopped = true; this.CancelCurrentWork(); } /// /// Cancel the currently running work. /// public void CancelCurrentWork() { if (this.current == null) return; this.current.Cancel(); } } /// /// Useful static methods for all applications. /// public static class Utilities { private static DateTime firstTime = DateTime.MinValue; /// /// Current time precise enough for logging. /// public static string PreciseTime { get { if (Utilities.firstTime == DateTime.MinValue) { Utilities.firstTime = DateTime.Now; } TimeSpan elapsed = (DateTime.Now - Utilities.firstTime); return elapsed + "\t"; } } /// /// Read an unquoted word from the given line starting at index 'currentIndex'. /// /// Line to parse. /// Start index of word. /// Found word. /// Index of separator after word (or of end of line). private static int ParseUnquotedWord(string line, int currentIndex, out string word) { int separatorIndex = line.IndexOf(',', currentIndex); if (separatorIndex == -1) { word = line.Substring(currentIndex); return line.Length; } else { word = line.Substring(currentIndex, separatorIndex - currentIndex); return separatorIndex; } } /// /// Read a quoted word from the given line starting at index 'currentIndex'. /// /// Line to parse. /// Start index of word. /// Found word. /// Index of separator after word (or of end of line). Returns -1 if the end quote is missing. private static int ParseQuotedWord(string line, int currentIndex, out string word) { int endIndex = currentIndex + 1; while (true) { endIndex = line.IndexOf('\"', endIndex); if (endIndex == -1) { word = ""; return -1; } if (endIndex == line.Length - 1) { // last word on line break; } else if (line[endIndex + 1] == '\"') { // quoted quote, continue endIndex += 2; continue; } else { // end of quoted word break; } } word = line.Substring(currentIndex + 1, endIndex - currentIndex - 1); // drop the start and end quotes word = word.Replace("\"\"", "\""); // fix quoted quotes return endIndex + 1; } /// /// Split a comma-separated value line into fields. Properly parses quoted fields. /// /// Line to parse. /// A list of the fields parsed. public static List SplitCSVLine(string line) { List results = new List(); int currentIndex = 0; while (currentIndex < line.Length) { string word; if (line[currentIndex] == '\"') { currentIndex = ParseQuotedWord(line, currentIndex, out word); if (currentIndex < 0) // end-of-line in quoted word; need more data return null; } else { currentIndex = ParseUnquotedWord(line, currentIndex, out word); } results.Add(word); if (currentIndex < line.Length) { // expect a separator if (line[currentIndex] != ',') throw new ArgumentException("Not found expected separator character after quoted field"); currentIndex++; } } return results; } /// /// Regular expression matching a GUID. /// public static readonly Regex GuidRegex = new Regex(@"([0-9A-F]{8})-([0-9A-F]{4})-[0-9A-F]{4}-[0-9A-F]{4}-[0-9A-F]{12}", RegexOptions.Compiled); /// /// Format string to use to represent datetimes in high precision as a string. /// public static string HighPrecisionDateFormat = "MM/dd/yyyy HH:mm:ss.fff tt"; static MD5 MD5Cached; /// /// A string encoding of the MD5 checksum of the input string. /// /// String to encode using MD5. /// A string containing the MD5 encoding. public static string MD5(string s) { if (MD5Cached == null) MD5Cached = System.Security.Cryptography.MD5.Create(); byte [] code = MD5Cached.ComputeHash(Encoding.UTF8.GetBytes(s)); StringBuilder result = new StringBuilder(); foreach (byte b in code) result.Append(b.ToString("X2")); return result.ToString(); } /// /// Move or rename a file. /// /// Existing file. /// New file. /// True on success. [return: MarshalAs(UnmanagedType.Bool)] [DllImport("kernel32", CharSet = CharSet.Auto, SetLastError = true)] private static extern bool MoveFile(string lpExistingFileName, string lpNewFileName); /// /// Move or rename a file. Throws an exception on failure. /// /// Existing file. /// New file. public static void Move(string from, string to) { bool success = MoveFile(from, to); if (success) return; throw new Win32Exception(); } /// /// Given a string of the form [k=v](,[k=v]*), parse it into a dictionary. /// Keys and values cannot contain commas or equal signs. /// /// Line to parse. /// A dictionary mapping the keys to values. public static Dictionary ParseCommaSeparatedKeyValuePair(string line) { Dictionary result = new Dictionary(); if (line.Length == 0) return result; string[] pieces = line.Split(','); foreach (string piece in pieces) { if (piece.Length == 0) continue; string[] parts = piece.Split('='); if (parts.Length != 2) throw new ArgumentException("Element `" + piece + "' not in k=v form"); result.Add(parts[0].Trim(), parts[1].Trim()); } return result; } /// /// True if this file name seems to indicate a text file. /// /// File whose name is tested. /// True if this name indicates a text file. public static bool FileNameIndicatesTextFile(string filename) { string[] textSuffixes = { ".txt", ".bat", ".cmd", ".log", ".config", ".xml", ".html" }; foreach (string suffix in textSuffixes) { if (filename.EndsWith(suffix)) return true; } return false; } /// /// Create the directory if it does not exist; it retries a few times. /// /// Path for file; directory name is extracted and created. public static void EnsureDirectoryExistsForFile(string filename) { string dir = Path.GetDirectoryName(filename); bool ok = false; int count = 0; while (!ok && count < 5) { try { if (dir != null && !Directory.Exists(dir)) Directory.CreateDirectory(dir); ok = true; } catch (IOException) { Thread.Sleep(200); } count++; } } /// /// Add an item in front of a list; the list is mutated. /// /// List to add item to. /// Item to add in front. /// Maximum list lenght. /// The new list. public static IList AddItemInFront(IList list, int maxlen, T item) { if (list.Contains(item)) // move to front list.Remove(item); else if (list.Count >= maxlen) // drop last list.RemoveAt(list.Count - 1); list.Insert(0, item); return list; } /// /// The file name may contain illegal characters; replace them with something legal. /// Does not guarantee that the file name will be unique. /// /// Filename to legalize. /// A file name which is legal. public static string LegalizeFileName(string filename) { HashSet illegal = new HashSet(Path.GetInvalidFileNameChars()); StringBuilder result = new StringBuilder(); foreach (char c in filename) { if (illegal.Contains(c)) // replace illegal characters with a dash result.Append('-'); else result.Append(c); } return result.ToString(); } /// /// Truncate x to a given number of decimals after decimal point. /// /// Value to truncate. /// Number of decimal values. /// The input with at most the indicated number of decimal places. public static string Round(double x, int decimals) { if (decimals < 0) decimals = 0; double rounded = Math.Round(x, decimals); return rounded.ToString(); } private static Random jitterRandom = new Random(); /// /// Generate a random value in the interval -max .. max with a distribution skewed towards the center /// /// Maximum amount of jitter. /// The jitter value. public static double Jitter(double max) { double rand = 100 * jitterRandom.NextDouble() - 50; return rand * max / 50; } /// /// Check if two strings represent the same machine. /// /// First machine. /// Second machine. /// 'true' if the two names point to the same machine really. public static bool SameMachine(string m1, string m2) { return m1.ToLower() == m2.ToLower(); } /// /// Extract the executable embedded in the assembly, then store it in the disk. /// /// The name of the file in the assembly /// the path to the instantiated file on disk(a local path) public static string InstantiateExecutableFromRes(string filename) { if (File.Exists(filename)) return (new FileInfo(filename)).FullName; // Get Current Assembly refrence Assembly currentAssembly = Assembly.GetExecutingAssembly(); // Get all embedded resources string[] arrResources = currentAssembly.GetManifestResourceNames(); foreach (string resourceName in arrResources) { if (resourceName.EndsWith(filename)) { //or other extension desired //Name of the file saved on disk string saveAsName = filename; //just save in the current directory FileInfo fileInfoOutputFile = new FileInfo(saveAsName); if (fileInfoOutputFile.Exists) { //overwrite if desired (depending on your needs) fileInfoOutputFile.Delete(); } FileStream streamToOutputFile = fileInfoOutputFile.OpenWrite(); Stream streamToResourceFile = currentAssembly.GetManifestResourceStream(resourceName); const int size = 4096; byte[] bytes = new byte[4096]; int numBytes; while ((numBytes = streamToResourceFile.Read(bytes, 0, size)) > 0) { streamToOutputFile.Write(bytes, 0, numBytes); } streamToOutputFile.Close(); streamToResourceFile.Close(); return fileInfoOutputFile.FullName; } } return null; } /// /// Copy the source directory to the target directory. /// /// The path to the source directory. /// The path to the target directory. /// Only filenames and subdirectories matching the pattern will be copied. public static void CopyDirectory(string sourceDirectory, string targetDirectory, string pattern) { DirectoryInfo diSource = new DirectoryInfo(sourceDirectory); DirectoryInfo diTarget = new DirectoryInfo(targetDirectory); CopyAll(diSource, diTarget, pattern); } /// /// Copy all the stuff from source dir to target dir. Just a private version of CopyDirectory /// /// The directory information of the source directory. /// The directory information of thetarget directory. /// Only files and subdirectories matching the pattern will be copied. private static void CopyAll(DirectoryInfo source, DirectoryInfo target, string pattern) { // Check if the target directory exists, if not, create it. if (Directory.Exists(target.FullName) == false) { Directory.CreateDirectory(target.FullName); } // Copy each file into it's new directory. foreach (FileInfo fi in source.GetFiles(pattern)) { //Trace.TraceInformation(@"Copying {0}\{1}", target.FullName, fi.Name); fi.CopyTo(Path.Combine(target.ToString(), fi.Name), true); } // Copy each subdirectory using recursion. foreach (DirectoryInfo diSourceSubDir in source.GetDirectories(pattern)) { DirectoryInfo nextTargetSubDir = target.CreateSubdirectory(diSourceSubDir.Name); CopyAll(diSourceSubDir, nextTargetSubDir, pattern); } } /// /// Convert a time value printed by the dryad job manager into a datetime object. /// /// Configuration describing the local time zone. /// CsTime value (A Cosmos time implementation). /// The absolute time represented by the value. public static DateTime Convert64time(TimeZoneInfo tzone, string cosmosTime) { DateTime time = new DateTime(Convert.ToInt64(cosmosTime), DateTimeKind.Unspecified).AddYears(1600); time = System.TimeZoneInfo.ConvertTimeFromUtc(time, tzone); return time; } /// /// Add one more key-value pair to a stringbuilder in the form k=v. If the builder is not empty, add a comma in front too. /// /// Stringbuilder where the string is built. /// Key. /// Value. public static void AddKVP(StringBuilder builder, string key, object value) { if (builder.Length > 0) builder.Append(","); // remove commas from key and value, otherwise it won't work key = key.Replace(',', '-'); builder.Append(key); builder.Append("="); string val = value.ToString().Replace(',', '-'); builder.Append(val); } /// /// CreateHardLink will utilize PInvoke to call the Win32 function CreateHardLink. /// /// Source FileName /// Destination FileName /// Security Attributes - Should be IntPtr.Zero /// True if the function succeeds. [DllImport("kernel32.dll", SetLastError = true, CharSet = CharSet.Auto)] static extern public bool CreateHardLink(string lpFileName, string lpExistingFileName, IntPtr lpSecurityAttributes); /// /// Create a hard link, and throw an exception if this fails. /// /// Destination file. /// Source file. public static void CreateHardLink(string to, string from) { bool status = CreateHardLink(to, from, new IntPtr(0)); if (!status) { throw new Win32Exception(Marshal.GetLastWin32Error()); } } /// /// Copy a file, but try creating a hard link first. /// /// Destination file. /// Source file. /// False if the copy failed. May throw exceptions. public static bool LinkOrCopy(string destination, string source) { if (!File.Exists(source)) return false; if (File.Exists(destination)) { // check if the source and destination may be the same file FileInfo si = new FileInfo(source); FileInfo di = new FileInfo(destination); if (si.LastWriteTime == di.LastWriteTime && si.Length == di.Length) return true; File.Delete(destination); } // no exceptions thrown here bool success = Utilities.CreateHardLink(destination, source, new IntPtr(0)); if (success) return true; File.Copy(source, destination); return true; } /// /// Run a process, return exit code if waiting for completion. /// /// Command-line to invoke. /// If not null, used to set the work directory of the process. /// If true, add quotes around parameters. /// If true, wait for completion. /// Do we use shell for executing? /// Do we need admin privileges? (if true, usually also requires useshell to be true) /// Arguments to pass. /// Exit code of the process if waiting for the process, zero otherwise. // ReSharper disable once UnusedMethodReturnValue.Global public static int RunProcess(string process, string workDirectory, bool quote, bool wait, bool useshell, bool runAsAdmin, params string[] arguments) { StringBuilder args = new StringBuilder(); string q = quote ? @"""" : ""; foreach (string arg in arguments) { args.Append(@" " + q + arg + q); } Trace.TraceInformation("Running: " + process + " " + args); ProcessStartInfo processStartInfo = new ProcessStartInfo(process, args.ToString()); processStartInfo.CreateNoWindow = true; processStartInfo.UseShellExecute = useshell; processStartInfo.RedirectStandardOutput = !useshell; processStartInfo.RedirectStandardError = !useshell; if (workDirectory != null) { processStartInfo.WorkingDirectory = workDirectory; } if (runAsAdmin) { processStartInfo.Verb = "runas"; } Process p = new Process(); p.StartInfo = processStartInfo; int exitcode; try { p.Start(); if (!wait) // no waiting return 0; if (!useshell) { StreamReader procout = p.StandardOutput; if (!p.HasExited) { string outLine; while ((outLine = procout.ReadLine()) != null) { Trace.TraceInformation(outLine); } } } p.WaitForExit(); exitcode = p.ExitCode; if (exitcode != 0) Trace.TraceInformation("Process has exited with exitcode " + exitcode); } catch (InvalidOperationException e) { exitcode = -1; Trace.TraceInformation("Could not start process " + process + " exception " + e); } catch (Win32Exception e) { exitcode = -1; Trace.TraceInformation("Could not start process " + process + " exception " + e); } return exitcode; } /// /// Private representation wrapping process and delegate to invoke on exit. /// class BackgroundProcessHandle { Process process; Action onExit; /// /// Create a handle for a process to run in the background. /// /// Process to run. public BackgroundProcessHandle(Process p) { this.process = p; } /// /// Run the process and invoke this delegate on exit. /// /// Delegate to invoke on process exit; arg is process exit code. /// True if launching the process succeeds. public bool Run(Action oe) { this.onExit = oe; try { this.process.EnableRaisingEvents = true; this.process.Exited += this.processExited; this.process.Start(); } catch (InvalidOperationException e) { Trace.TraceInformation("Could not start process " + this.process.ProcessName + " exception " + e); return false; } catch (Win32Exception e) { Trace.TraceInformation("Could not start process " + this.process.ProcessName + " exception " + e); return false; } return true; } /// /// Event handler when the process exits. /// /// /// private void processExited(object sender, EventArgs e) { if (this.onExit != null) this.onExit(this.process.ExitCode); } } /// /// Run a process, return exit code if waiting for completion. /// /// Command-line to invoke. /// If not null, used to set the work directory of the process. /// Delegate to invoke on exit; passed exit code. /// Arguments to pass. /// True if running succeeded. public static bool RunProcessAsync(string process, string workDirectory, Action onExit, params string[] arguments) { string args = string.Join(" ", arguments); Trace.TraceInformation("Running: " + process + " " + args); ProcessStartInfo processStartInfo = new ProcessStartInfo(process, args); processStartInfo.CreateNoWindow = true; if (workDirectory != null) { processStartInfo.WorkingDirectory = workDirectory; } Process p = new Process(); p.StartInfo = processStartInfo; BackgroundProcessHandle bph = new BackgroundProcessHandle(p); bool started = bph.Run(onExit); return started; } /// /// Save an object as xml. /// /// Type of object to save. /// File to save object description to. /// Object to save. public static void SaveAsXml(string filename, T obj) { using (StreamWriter sw = new StreamWriter(filename)) { XmlSerializer s = new XmlSerializer(typeof(T)); s.Serialize(sw, obj); } } /// /// Load the description of an object from a file. /// /// Type of object to read from file. /// File containing the description. public static T LoadXml(string filename) { using (StreamReader sr = new StreamReader(filename)) { XmlSerializer s = new XmlSerializer(typeof(T)); Object o = s.Deserialize(sr); return (T)o; } } /// /// Convert a hsv color value to a rgb color value. /// /// Hue, between 0 and 1. /// Saturation, between 0 and 1. /// Value, between 0 and 1. /// Red component, between 0 and 1. /// Green component, between 0 and 1. /// Blue component, between 0 and 1. public static void HSVtoRGB(double h, double s, double v, out double r, out double g, out double b) { h *= 6; int i = (int)Math.Floor(h); double f = h - i; if ((i & 1) == 0) f = 1 - f; // if i is even double m = v * (1 - s); double n = v * (1 - s * f); r = 0; g = 0; b = 0; switch (i) { case 6: throw new ArgumentOutOfRangeException("h"); case 0: r = v; g = n; b = m; break; case 1: r = n; g = v; b = m; break; case 2: r = m; g = v; b = n; break; case 3: r = m; g = n; b = v; break; case 4: r = n; g = m; b = v; break; case 5: r = v; g = m; b = n; break; } } /// /// If the xpath expression matches return the first Xml node matching. /// Else return null. /// /// Node where matching starts. /// Xpath expression to match. /// First matching node or null. public static XmlNode FirstIfAnyXmlMatch(XmlNode node, string xpath) { XmlNodeList matching = node.SelectNodes(xpath); if (matching.Count == 0) return null; return matching[0]; } /// /// Get the single expected matching node in an XML document. /// /// Search starting from this node. /// Xpath expression to search for nodes. /// The single matching node. public static XmlNode SingleXmlMatch(XmlNode node, string xpath) { XmlNode child = Utilities.FirstIfAnyXmlMatch(node, xpath); if (child == null) throw new XmlException("Node " + node.Name + " does not have exactly 1 child with path " + xpath + " in xml document"); return child; } /// /// Read the web page with the specified URL. /// /// Url to reach. /// A streamreader that returns the loaded web page. /// Credentials to use. public static Stream Navigate(string url, ICredentials credentials) { CookieContainer cookiejar = new CookieContainer(); HttpWebRequest req = (HttpWebRequest)WebRequest.Create(url); req.CookieContainer = cookiejar; req.ServicePoint.ConnectionLimit = 1; if (credentials == null) req.UseDefaultCredentials = true; else req.Credentials = credentials; req.PreAuthenticate = false; req.Method = "GET"; HttpWebResponse resp = (HttpWebResponse)req.GetResponse(); System.Diagnostics.Trace.Assert(resp.StatusCode == HttpStatusCode.OK); Trace.TraceInformation("Received response"); return resp.GetResponseStream(); } /// /// Convert Hexadecimal number to integer. /// /// A string representing a hex number. /// The equivalent integer value. public static int HexToInt(string hexString) { return int.Parse(hexString, System.Globalization.NumberStyles.HexNumber, null); } /// /// Convert integer to hexadecimal. /// /// Number to convert. /// A string which is the hexadecimal representation. public static string IntToHex(int number) { return String.Format("{0:x}", number); } /// /// This is like Path.Combine, but with multiple arguments. /// /// Paths to combine. /// A single path composed of all segments. public static string PathCombine(params string[] paths) { if (paths.Length == 0) return ""; string result = paths[0]; for (int i = 1; i < paths.Length; i++) result = Path.Combine(result, paths[i]); return result; } /// /// Compute the average of a set of colors. /// /// Colors to average. /// A new color, which is the average of all other colors. public static Color AverageColor(IEnumerable colors) { int count = 0; int totalA = 0, totalR = 0, totalG = 0, totalB = 0; foreach (Color c in colors) { totalA += c.A; totalR += c.R; totalG += c.G; totalB += c.B; count++; } if (count == 0) return Color.Black; return Color.FromArgb(totalA / count, totalR / count, totalG / count, totalB / count); } /// /// Find a color contrasting with the given one (e.g., to use as foreground when the other is background). /// /// Color to contrast with. /// A contrasting color. public static Color VisibleColor(Color color) { if (color.GetBrightness() < 0.5) return Color.White; else return Color.Black; } /// /// Copy a file which may be already opened for writing. /// /// Original name. /// New name. public static void CopyFile(string from, string to) { Stream rd = new FileStream(from, FileMode.Open, FileAccess.Read, FileShare.ReadWrite); Stream wr = new FileStream(to, FileMode.Create, FileAccess.Write); byte[] buf = new byte[1 << 13]; for (; ; ) { int len = rd.Read(buf, 0, buf.Length); if (len == 0) break; wr.Write(buf, 0, len); } rd.Close(); wr.Close(); } static char[] FolderSeparators = { Path.DirectorySeparatorChar, Path.AltDirectorySeparatorChar }; /// /// Split a pathname into a list of directories. /// /// Path to split. /// The complete list of directories on the path. public static string[] SplitPathname(string path) { string[] subpaths = path.Split(FolderSeparators, StringSplitOptions.RemoveEmptyEntries); return subpaths; } /// /// Check whether a given string could be the current user. /// /// Name of user. /// True if it matches the login name. public static bool IsThisUser(string username) { System.Security.Principal.WindowsIdentity id = System.Security.Principal.WindowsIdentity.GetCurrent(); return (id.Name == username); } /// /// Given a file with records serialized by DryadLINQ create the metadata to morph it into a single-partition file. /// /// File containing DryadLINQ-serialized records. /// A uri pointing to to partitioned file table whose body is the specified file. public static string TransformToPartitionedTable(UNCPathname file) { string prefix = file.DirectoryAndFilename; PartitionedFileMetadata md = new PartitionedFileMetadata(); PartitionedFileMetadata.Partition part = new PartitionedFileMetadata.Partition(0, 0, file.Machine, prefix); string partFilename = part.Replica(0).ToString(); md.Add(part); // partitions have to have a very stylized name Utilities.CreateHardLink(partFilename, file.ToString()); // Create the metadata for the file we are returning UNCPathname metadatafile = file; metadatafile.Filename = "metadata-" + file.Filename; string uri = md.CreateMetadataFile(metadatafile); return uri; } /// /// Generate a regular expression corresponding to a search pattern. /// This replaces ? with .? and * with .* /// /// /// public static Regex RegexFromSearchPattern(string match) { if (string.IsNullOrEmpty(match)) return new Regex(""); match = match.Replace("?", ".?"); match = match.Replace("*", ".*"); return new Regex(match); } /// /// Parse a line of the form k=v,k=v. Values may be quoted. /// /// Line to parse. /// A dictionary with all key=value parts, or null if parsing fails because of an end-of-line in quoted value. public static Dictionary ParseCSVKVP(string line) { Dictionary result = new Dictionary(); while (!string.IsNullOrWhiteSpace(line)) { int eq = line.IndexOf('='); if (eq < 0) throw new ArgumentException("Could not find equal sign in " + line); string key = line.Substring(0, eq).Trim(); string value; int cont; if (line.Length > eq && line[eq+1] == '"') { cont = ParseQuotedWord(line, eq+1, out value); } else { cont = ParseUnquotedWord(line, eq+1, out value); } if (cont < 0) return null; // end of line in quoted value if (cont >= line.Length) line = ""; else line = line.Substring(cont + 1); // skip next comma result.Add(key, value); } return result; } } /// /// A binding list implementation which permits sorting. /// /// Type of elements in list. public class BindingListSortable : BindingList { private bool isSorted; private ListSortDirection direction = ListSortDirection.Ascending; private PropertyDescriptor sortProperty; /// /// Create an empty binding list. /// public BindingListSortable() { this.isSorted = false; } /// /// Initialize a sortable binding list with a set of values. /// /// Values to insert in list. public BindingListSortable(IList values) : base(values) { this.isSorted = false; } /// /// What is the list sorted on? /// public string SortedOn { get; set; } /// /// Sort the list on the indicated property. /// /// Property of T to sort on. public void Sort(string property) { this.sortProperty = this.FindPropertyDescriptor(property); this.ApplySortCore(sortProperty, direction); } /// /// Redo the last sorting operation. /// True if there was a last sorting operation. /// public bool RedoSort() { if (this.SortedOn == null) return false; this.Sort(this.SortedOn); return true; } /// /// Override indicating that the list supports sorting. /// protected override bool SupportsSortingCore { get { return true; } } /// /// Apply the sorting operation. /// /// Property to sort on. /// Direction of sort. protected override void ApplySortCore(PropertyDescriptor property, ListSortDirection dir) { this.SortedOn = property.Name; this.direction = dir; List items = this.Items as List; if (null != items) { PropertyComparer pc = new PropertyComparer(property, dir); items.Sort(pc); /* Set sorted */ this.isSorted = true; } else { /* Set sorted */ this.isSorted = false; } } /// /// Is the list sorted? /// protected override bool IsSortedCore { get { return this.isSorted; } } /// /// "Unsort" the list. /// protected override void RemoveSortCore() { this.isSorted = false; } private PropertyDescriptor FindPropertyDescriptor(string property) { PropertyDescriptorCollection pdc = TypeDescriptor.GetProperties(typeof(T)); PropertyDescriptor prop = pdc.Find(property, true); return prop; } internal class PropertyComparer : System.Collections.Generic.IComparer { private PropertyDescriptor property; private ListSortDirection direction; public PropertyComparer(PropertyDescriptor property, ListSortDirection direction) { this.property = property; this.direction = direction; } public int Compare(TKey xVal, TKey yVal) { /* Get property values */ object xValue; object yValue; if (property != null) { xValue = GetPropertyValue(xVal, property.Name); yValue = GetPropertyValue(yVal, property.Name); } else { xValue = xVal; yValue = yVal; } /* Determine sort order */ int sort = CompareAscending(xValue, yValue); if (direction == ListSortDirection.Descending) { sort = -sort; } return sort; } public bool Equals(TKey xVal, TKey yVal) { return xVal.Equals(yVal); } public int GetHashCode(TKey obj) { return obj.GetHashCode(); } /* Compare two property values of any type */ private int CompareAscending(object xValue, object yValue) { int result; /* If values implement IComparer */ var value = xValue as IComparable; if (value != null) { result = value.CompareTo(yValue); } else throw new ArgumentException("values are not comparable"); return result; } private object GetPropertyValue(TKey value, string prop) { /* Get property */ PropertyInfo propertyInfo = value.GetType().GetProperty(prop); /* Return value */ return propertyInfo.GetValue(value, null); } } } /// /// A legend maps a color to an explanation. /// public class Legend { /// /// The name of the entity which was used to construct the legend information. /// public string LegendSourceName { get; internal set; } /// /// Explanation for the choice of a color. /// public class ColorLegend { /// /// Explanation for the color. /// public string label; /// /// Colors will be ordered on this index when showing the legend. /// public double orderingIndex; /// /// String representation of the color legend. /// /// A string representation. public override string ToString() { return label; } }; /// /// Minimum color index represented. /// public double MinIndex { get; protected set; } /// /// Maximum color index represented. /// public double MaxIndex { get; protected set; } /// /// Color with minimum index. /// public Color MinIndexColor { get; protected set; } /// /// Color with maximum index. /// public Color MaxIndexColor { get; protected set; } /// /// Map from color to explanation. /// protected readonly Dictionary explanations; /// /// Create an empty legend. /// /// Name of entity used to construct the legend. public Legend(string sourcename) { this.LegendSourceName = sourcename; this.explanations = new Dictionary(); } /// /// The number of colors in the legend. /// public int Size { get { return this.explanations.Count; } } /// /// Explanation for color c, if any. /// /// Color whose explanation is sought. /// The meaning of color c, or null. public ColorLegend this[Color c] { get { if (this.explanations.ContainsKey(c)) return this.explanations[c]; else return null; } } /// /// Add a new explanation to the legend. /// /// Color to explain. /// Explanation for the color. /// Ordering index of color. public void Add(Color c, string explanation, double index) { if (explanation != null && !this.explanations.ContainsKey(c)) { if (this.explanations.Count == 0) { // first element added this.MinIndex = this.MaxIndex = index; this.MinIndexColor = this.MaxIndexColor = c; } else { if (this.MinIndex > index) { this.MinIndex = index; this.MinIndexColor = c; } if (this.MaxIndex < index) { this.MaxIndex = index; this.MaxIndexColor = c; } } this.explanations.Add(c, new ColorLegend { label = explanation, orderingIndex = index }); } } /// /// Select only the specified colors from the legend. /// /// Restrict to these colors. /// A new legend. public Legend Select(IEnumerable restrictTo) { Legend result = new Legend(this.LegendSourceName); foreach (Color c in restrictTo) { ColorLegend legend = this[c]; if (legend == null) continue; result.Add(c, legend.label, legend.orderingIndex); } return result; } /// /// The list of all colors in the legend. /// /// The list of all colors represented in the legend. public IEnumerable GetAllColors() { return this.explanations.Keys; } /// /// Add a new explanation for a color. /// /// Color to explain. /// Explanation to add. public void Add(Color col, ColorLegend colorLegend) { if (colorLegend == null) return; if (!this.explanations.ContainsKey(col)) this.explanations.Add(col, colorLegend); } } /// /// Point on a two-dimensional surface, with double coordinates. /// public struct Point2D { /// /// Point coordinates. /// double x, y; /// /// Create a point on a 2-D plane surface. /// /// X coordinate. /// Y coordinate. public Point2D(double x, double y) { this.x = x; this.y = y; } /// /// Create a point at the specified coordinates from the origin. /// /// Size to encode as a point. public Point2D(SizeF size) { this.x = size.Width; this.y = size.Height; } /// /// X coordinate of point. /// public double X { get { return this.x; } } /// /// Y Coordinate of point. /// public double Y { get { return this.y; } } /// /// Return a point having the max coordinates from two points. /// /// Point to compare against. /// A new point having X = max(this.x, other.x) (similar for Y) public Point2D Max(Point2D other) { return new Point2D(Math.Max(this.X, other.X), Math.Max(this.Y, other.Y)); } /// /// Return a point having the min coordinates from two points. /// /// Point to compare against. /// A new point having X = min(this.x, other.x) (similar for Y) public Point2D Min(Point2D other) { return new Point2D(Math.Min(this.X, other.X), Math.Min(this.Y, other.Y)); } /// /// True if a point has coordinates smaller than another one. /// /// Left point. /// Right point. /// True if the left point has both coordinates smaller. public static bool operator <(Point2D left, Point2D right) { return left.X < right.X && left.Y < right.Y; } /// /// True if a point has coordinates bigger than another one. /// /// Left point. /// Right point. /// True if the left point has both coordinates bigger. public static bool operator >(Point2D left, Point2D right) { return left.X > right.X && left.Y > right.Y; } /// /// True if a point has coordinates smaller or equal than another one. /// /// Left point. /// Right point. /// True if the left point has both coordinates smaller or equal. public static bool operator <=(Point2D left, Point2D right) { return left.X <= right.X && left.Y <= right.Y; } /// /// True if a point has coordinates greater or equal than another one. /// /// Left point. /// Right point. /// True if the left point has both coordinates greater or equal. public static bool operator >=(Point2D left, Point2D right) { return left.X >= right.X && left.Y >= right.Y; } /// /// The second point is a displacement: compute new endpoint. /// /// Original. /// Displacement. /// A new point, at the given displacement from the original one. public static Point2D operator +(Point2D left, Point2D right) { return new Point2D(left.X + right.X, left.Y + right.Y); } /// /// Translate a point by a given amount. /// /// Amount to translate in X direction. /// Amount to translate in Y direction. /// A new point. public Point2D Translate(double xp, double yp) { return new Point2D(this.X + xp, this.Y + yp); } /// /// String representation of the point. /// /// A string describing the point. public override string ToString() { return "(" + this.X + "," + this.Y + ")"; } } /// /// Class describing a rectangle on a 2D plane with edges parallel to the axes. /// public class Rectangle2D { /// /// Two opposite corners of the rectangle. /// Point2D one, two; /// /// True if one is less than two. /// bool normalized; /// /// Create a rectangle given 4 coordinates. Note that there is no requirement to have points in either order. /// /// Left X coordinate. /// Left Y coordinate. /// Right X coordinate. /// Right Y coordinate. public Rectangle2D(double lx, double ly, double rx, double ry) { one = new Point2D(lx, ly); two = new Point2D(rx, ry); normalized = one < two; } /// /// Create a rectangle from two points. /// /// One corner. /// Second corner. public Rectangle2D(Point2D one, Point2D two) { this.one = one; this.two = two; normalized = one < two; } /// /// Make the first corner to be lower and to the left of the second corner. /// /// A new rectangle. public Rectangle2D Normalize() { if (normalized) return this; bool swapx = one.X > two.X; bool swapy = one.Y > two.Y; return new Rectangle2D( swapx ? two.X : one.X, swapy ? two.Y : one.Y, swapx ? one.X : two.X, swapy ? one.Y : two.Y); } /// /// True if rectangle has null width or height. /// /// True if rectangle has zero area. public bool Degenerate() { // ReSharper disable CompareOfFloatsByEqualityOperator return (one.X == two.X) || (one.Y == two.Y); // ReSharper restore CompareOfFloatsByEqualityOperator } /// /// First corner. /// public Point2D Corner1 { get { return one; } } /// /// Second corner. /// public Point2D Corner2 { get { return two; } } /// /// Center of the rectangle. /// public Point2D Center { get { return new Point2D(one.X + this.Width / 2, one.Y + this.Height / 2); } } /// /// Compute the intersection of two rectangles. /// /// Rectangle to intersect with. /// A new rectangle. The result may be degenerate if the intersection is empty. public Rectangle2D Intersect(Rectangle2D with) { Point2D ul = this.one.Max(with.one); Point2D lr = this.two.Min(with.two); if (ul.X > lr.X || ul.Y > lr.Y) return new Rectangle2D(0, 0, 0, 0); // We expect this is normalized return new Rectangle2D(ul, lr); } /// /// Rectangle area. /// /// The area of the rectangle. public double Area() { return Math.Abs((one.X - two.X) * (one.Y - two.Y)); } /// /// If a rectangle is degenerate, stretch it a little. /// /// Always a non-degenerate, normalized rectangle. public Rectangle2D FixDegeneracy() { const double epsilon = 0.1; Rectangle2D norm = this.Normalize(); // if the size is 0 make it slightly larger if (norm.Degenerate()) { double xmin = norm.Corner1.X; double xmax = norm.Corner2.X; double ymin = norm.Corner1.Y; double ymax = norm.Corner2.Y; if (xmax <= xmin) xmin -= xmax * 0.1 + epsilon; if (ymax <= ymin) ymin -= ymax * 0.1 + epsilon; norm = new Rectangle2D(xmin, ymin, xmax, ymax); } return norm; } /// /// Height of rectangle. /// public double Height { get { return Math.Abs(one.Y - two.Y); } } /// /// Width of rectangle. /// public double Width { get { return Math.Abs(one.X - two.X); } } /// /// Generate a new rectangle, with the smallest integer coordinates which contains this one. /// /// A new rectangle. public Rectangle2D ExpandToIntegerCoordinates() { Rectangle2D norm = this.Normalize(); return new Rectangle2D(Math.Floor(norm.one.X), Math.Floor(norm.one.Y), Math.Ceiling(norm.two.X), Math.Ceiling(norm.two.Y)); } /// /// Create a rectangle given a corner and size. /// /// Left X coordinate. /// Left Y coordinate. /// Width of rectangle (may be negative). /// Height of rectangle (may be negative). /// public static Rectangle2D MakeRectangle(double xl, double yl, double width, double height) { return new Rectangle2D(xl, yl, xl + width, yl + height); } /// /// Check whether a point is inside a rectangle. /// /// Point to check. /// True if the point is inside the rectangle. public bool Inside(Point2D point) { Rectangle2D norm = this.Normalize(); return norm.Corner1 <= point && point <= norm.Corner2; } /// /// Check whether a rectangle includes another one. /// /// The rectangle that should be inside. /// True if other is inside this. public bool Includes(Rectangle2D other) { return Inside(other.Corner1) && Inside(other.Corner2); } /// /// Move a rectangle. /// /// Amount to move in x direction. /// Amount to move in y direction. /// A new rectangle. public Rectangle2D Translate(double x, double y) { return new Rectangle2D(this.Corner1.Translate(x, y), this.Corner2.Translate(x, y)); } /// /// Size of rectangle. /// public Point2D Size { get { return new Point2D(this.Width, this.Height); } } } /// /// Represents a UNC pathname: machine, directory, file. /// If 'machine' is null, this is a directory on localhost. /// if 'file' is null, this represents a directory. /// The directory cannot be null or empty. /// [Serializable] public class UNCPathname { /// /// Machine hosting the path; if null, it represents 'localhost'. /// public string Machine { get; set; } /// /// Directory; may not be null; includes the share name. /// public string Directory { get; set; } /// /// Filename; may be null. /// public string Filename { get; set; } /// /// Create a UNC pathname from a machine, directory and file. /// /// Machine hosting the path; if null or empty, it represents 'localhost'. /// Directory; may not be null. /// Filename; if null or empty, the pathname represents a directory. public UNCPathname(string machine, string directory, string file) { if (machine == "") machine = null; this.Machine = machine; if (string.IsNullOrEmpty(directory)) throw new ArgumentException("The directory of a UNC pathname cannot be null"); this.Directory = directory; if (file == "") file = null; this.Filename = file; } /// /// The name of the share. /// public string Sharename { get { if (string.IsNullOrEmpty(this.DirectoryAndFilename)) return ""; string[] subpaths = Utilities.SplitPathname(this.DirectoryAndFilename); return subpaths[0]; } } /// /// Path without the share. /// public string DirectoryAndFilenameNoShare { get { if (string.IsNullOrEmpty(this.DirectoryAndFilename)) return ""; string[] subpaths = Utilities.SplitPathname(this.DirectoryAndFilename); string result = string.Join(Path.DirectorySeparatorChar.ToString(), subpaths, 1, subpaths.Length - 1); return result; } } /// /// Create a new uncpathname holding the same information as the other. /// /// UNCPathname to copy. public UNCPathname(UNCPathname other) { this.Machine = other.Machine; this.Directory = other.Directory; this.Filename = other.Filename; } /// /// Create a UNC pathname from a string. /// /// Path to break into pieces. public UNCPathname(string path) { // First extract machine name if (path.StartsWith(@"\\") || path.StartsWith(@"//")) { int slash = path.IndexOf("/", 2); int bkslash = path.IndexOf("\\", 2); if (bkslash < slash || slash < 0) slash = bkslash; if (slash >= 0) { this.Machine = path.Substring(2, slash - 2); // length - 2 path = path.Substring(slash + 1); } else { // there is just a machine this.Machine = path.Substring(2); path = ""; } } else this.Machine = null; // extract the directory and file { int slash = path.LastIndexOf('/'); int bkslash = path.LastIndexOf('\\'); if (bkslash > slash) slash = bkslash; if (slash >= 0) { this.Filename = path.Substring(slash + 1); this.Directory = path.Substring(0, slash); } else { this.Filename = path; this.Directory = null; throw new ArgumentException("Pathname cannot contain an empty directory"); } } } /// /// Create a pathname from a machine and a file name. /// /// Machine name. /// Pathname on local machine. public UNCPathname(string machine, string dirandfile) : this(machine, Path.GetDirectoryName(dirandfile), Path.GetFileName(dirandfile)) { } /// /// Pathname represented as a UNC pathname suitable for passing to File/Directory functions. /// /// The UNC pathname as a string. public override string ToString() { if (Filename == null) return this.UNCDirectory; else return Path.Combine(this.UNCDirectory, this.Filename); } /// /// The combined directory and filename path. /// If filename is null, only the directory is returned. /// public string DirectoryAndFilename { get { if (this.Filename == null) return this.Directory.Trim('/', '\\'); return Path.Combine(this.Directory.Trim('/', '\\'), this.Filename.Trim('/', '\\')); } } /// /// True if the pathname represents a directory, false if it represents a file. /// public bool IsDirectory { get { return this.Filename == null; } } /// /// True if the pathname is on the local machine (implicitly 'localhost'), false otherwise. /// public bool IsLocal { get { return this.Machine == null; } } /// /// Just the machine and directory, in UNC form. /// public string UNCDirectory { get { StringBuilder builder = new StringBuilder(); if (Machine != null) { builder.Append(@"\\"); builder.Append(this.Machine); } if (!Directory.StartsWith(@"\")) builder.Append(@"\"); builder.Append(this.Directory); return builder.ToString(); } } } /// /// Map from objects to colors. /// Type of data source providing the information in the color map. /// public class ColorMap : IEnumerable { /// /// Source of data used to build the color map. /// protected readonly T source; /// /// Map from object (a value in the column) to color. /// protected Dictionary map; /// /// Explanation for the color choices. /// protected readonly Legend legend; /// /// Create an empty color map. /// public ColorMap(T source) { this.source = source; this.map = new Dictionary(); this.legend = new Legend(""); this.DefaultColor = Color.Gray; this.IsContinuous = false; } /// /// Set the name of the source which originated the legend. /// /// Name of the source which originated the legend. public void SetLegendSourceName(string legendSource) { this.Legend.LegendSourceName = legendSource; } /// /// If true this is a map from continuous values, else it is from discrete values. /// public bool IsContinuous { get; protected set; } /// /// Source providing the data for the map. /// public T ColorSource { get { return this.source; } } /// /// Find color associated with a label. /// /// Label. /// Color value. public virtual Color this[object label] { get { if (this.map.ContainsKey(label)) return this.map[label]; else return this.DefaultColor; } set { if (this.map.ContainsKey(label)) this.map[label] = value; else this.map.Add(label, value); } } /// /// The default color, when color is not in the map. /// public Color DefaultColor { get; set; } /// /// Color scale 0..1 => 0..255 /// protected static int CS(double c) { return (int)(c * 255); } /// /// Copy all data in a color map. /// /// A new color map which has the same information. public ColorMap Clone() { ColorMap retval = new ColorMap(this.source); retval.DefaultColor = this.DefaultColor; retval.map = new Dictionary(this.map); return retval; } /// /// An enumerator over the map objects. /// /// All the objects stored in the map. public IEnumerator GetEnumerator() { return this.map.Keys.GetEnumerator(); } IEnumerator IEnumerable.GetEnumerator() { return this.GetEnumerator(); } /// /// The legend associated with this color map. /// public Legend Legend { get { return this.legend; } } /// /// Explanation for a given color. /// /// Color selected. /// The legend explaining the color, if any. public Legend.ColorLegend Explanation(Color c) { return this.Legend[c]; } /// /// Explanation for the color of a label. /// /// Label whose color is explained. /// A Legend.ColorLegend explaining the color. public Legend.ColorLegend Explanation(object label) { return this.Legend[this[label]]; } } /// /// Color map representing discrete data. /// /// Type of data source for color map. public abstract class DiscreteColorMap : ColorMap { /// /// Total number of colors represented by this colormap. /// protected readonly int totalcolors; /// /// Generate a new colormap for the given number of colors. /// /// Number of distinct colors expected. /// Source of data. protected DiscreteColorMap(T source, int colors) : base(source) { this.totalcolors = colors; this.IsContinuous = false; } /// /// Generate a color for color #index. /// /// Color number to generate [0..total). /// A nice color, such that all total colors are distinguishable. protected abstract Color BucketColor(int index); /// /// Add a set of labels, all at the given index. /// /// Labels to add. /// Index between 0..totalcolors. /// Explanation for this class. public void AddLabelClass(IEnumerable labels, int index, string legd) { if (index < 0 || index >= this.totalcolors) throw new System.ArgumentException("Color index " + index + " out of range 0.." + totalcolors); Color c = this.BucketColor(index); int added = 0; foreach (object label in labels) { added++; this[label] = c; } if (added > 0 && legd != null) { this.legend.Add(c, legd, index); } } } /// /// A colormap containing some pre-computed colors. /// This works up to 32 colors only. /// /// Type of data source for color map. public class PrecomputedColorMap : DiscreteColorMap { // ReSharper disable once StaticFieldInGenericType static Color[] preAssigned; static PrecomputedColorMap() { preAssigned = new Color[] { Color.Red, Color.Blue, Color.Yellow, Color.Green, Color.Magenta, Color.Brown, Color.DarkRed, Color.Indigo, Color.Orange, Color.DarkGreen, Color.DarkViolet, Color.Chocolate, Color.Tomato, Color.Aqua, Color.YellowGreen, Color.SeaGreen, Color.Plum, Color.Wheat, Color.Coral, Color.Firebrick, Color.Pink, Color.Olive, Color.PaleGreen, Color.SaddleBrown,Color.Lime, Color.Sienna, Color.Goldenrod, Color.PaleTurquoise, Color.Khaki, Color.DarkKhaki, Color.Purple, Color.Peru }; } /// /// Maximum number of colors that can be represented by this color map. /// public static int MaxColors { get { return preAssigned.Length; } } /// /// Allocate a pre-computed color map. /// /// Data source for this color map. /// Number of distinct colors. public PrecomputedColorMap(T source, int colors) : base(source, colors) { if (colors > preAssigned.Length) throw new ArgumentOutOfRangeException("The pre-assigned color map does not support more than " + preAssigned.Length + " colors"); } /// /// The color associated to a bucket. /// /// Bucket index. /// The color. protected override Color BucketColor(int index) { int ix = index; // (index * preAssigned.Length) / this.totalcolors; return preAssigned[ix]; } } /// /// Map from objects to colors computing automatically colors for many objects mapped into a small number of buckets. /// /// Type of data source for color map. public class HSVColorMap : DiscreteColorMap { /// /// Generate a new colormap for the given number of colors. /// /// Number of distinct colors expected. /// Source of data. public HSVColorMap(T source, int colors) : base(source, colors) { } /// /// Generate a color for color #index. /// /// Color number to generate [0..total). /// A nice color, such that all total colors are distinguishable. protected override Color BucketColor(int index) { return HSVColorMap.OneCircleColor(index, this.totalcolors); } /// /// Generate a color for color #index, out of 'total' colors. /// /// Color number to generate [0..total). /// Total number of colors expected. /// A nice color, such that all total colors are distinguishable. private static Color OneCircleColor(int index, int total) { int samplesPerCircle = total; const int startOffset = 1; // rotate around circle const double fractionOfCircle = 0.8; // leave a gap to distinguish start from end int pointno = index + startOffset; double h = pointno * fractionOfCircle / samplesPerCircle; const double s = 1; double r, g, b; Utilities.HSVtoRGB(h, s, s, out r, out g, out b); Color c = Color.FromArgb(CS(r), CS(g), CS(b)); return c; } } /// /// This class is used to turn selected properties of an object into something suitable for a databinding. /// public class PropertyEnumerator { /// /// Skip these properties when enumerating. /// HashSet skip; /// /// Expand these properties (which are probably classes themselves) into sub-fields. /// HashSet expand; /// /// A property and its value. /// public class PropertyValue : IName { /// /// Name of property. /// public string ObjectName { get; private set; } /// /// Value of property. /// public string Value { get; private set; } /// /// Create a property value with a given name and value. /// /// Name of property. /// Value of property. public PropertyValue(string name, string value) { this.ObjectName = name ?? ""; this.Value = value ?? ""; } /// /// String representation of the property value. /// /// A string representing the property value. public override string ToString() { return this.ObjectName + "=" + this.Value; } } /// /// Type of the data item. /// // ReSharper disable once StaticFieldInGenericType static Type dataType = typeof(T); /// /// Common initialization code. /// private void Initialize() { this.skip = new HashSet(); this.expand = new HashSet(); } /// /// Create a propertyenumerator which looks at the properties of an /// /// Object whose properties should be enumerated. public PropertyEnumerator(T data) { this.Initialize(); this.Data = data; } /// /// Create an empty property enumerator, with no object bound yet. /// public PropertyEnumerator() { this.Initialize(); this.Data = default(T); this.SetExpandAllNonPrimitive = false; this.ValueFormatter = null; } /// /// Item whose properties are listed. /// public T Data { get; set; } /// /// In not null this function is used to format the values returned as strings. /// public Func ValueFormatter { get; set; } /// /// Formats the value depending on the type. /// /// Value to format. /// The formatted value. string FormattedValue(object propertyValue) { if (this.ValueFormatter != null) return this.ValueFormatter(propertyValue); return propertyValue.ToString(); } /// /// If set all non-primitive properties (structs) are expanded by default. /// public bool SetExpandAllNonPrimitive { set { if (value) { IEnumerable properties = PropertyEnumerator.dataType.GetProperties(); foreach (var prop in properties) { if (prop.PropertyType == typeof(string)) continue; IEnumerable recursiveproperties = prop.PropertyType.GetProperties(); if (recursiveproperties.Count() > 1) this.expand.Add(prop.Name); } } } } /// /// Extract all properties whose values are computed. /// public IEnumerable AllPropertyNames() { IEnumerable properties = PropertyEnumerator.dataType.GetProperties(). Where(prop => !this.skip.Contains(prop.Name)). SelectMany(this.ExtracRecursivetProperties); return properties; } /// /// If the property has to be expanded recursively extract its properties. /// /// Property to expand on. /// The list of properties of this property. private IEnumerable ExtracRecursivetProperties(PropertyInfo prop) { if (!this.expand.Contains(prop.Name)) return new string [] { prop.Name }; IEnumerable retval = prop.GetType().GetProperties(); return retval.Select(p => prop.Name + "." + p.Name); } /// /// Extract the value of a property; if the property has to be expanded, it will be a list of property values. /// /// Property whose value is extracted. /// A collection of property values with one or more elements. private IEnumerable ExtractPropertyValue(PropertyInfo prop) { object propvalue = prop.GetValue(this.Data, null); if (this.expand.Contains(prop.Name)) { IEnumerable retval = propvalue.GetType().GetProperties().Select(p => new PropertyValue(prop.Name + "." + p.Name, this.FormattedValue(p.GetValue(propvalue, null)))); foreach (PropertyValue p in retval) yield return p; } else { yield return new PropertyValue(prop.Name, this.FormattedValue(propvalue)); } } /// /// Get the list of all interesting properties of this object. /// Populate this list with the property values. /// public void PopulateWithProperties(IList.PropertyValue> retval) { IEnumerable properties = PropertyEnumerator.dataType.GetProperties(). Where(prop => !this.skip.Contains(prop.Name)). SelectMany(this.ExtractPropertyValue); foreach (PropertyValue v in properties) retval.Add(v); } /// /// Do not display these properties. /// /// Properties to skip. public void Skip(params string[] properties) { foreach (string p in properties) this.skip.Add(p); } /// /// Do not display these properties. /// /// Properties to skip. public void Skip(IEnumerable properties) { foreach (string p in properties) this.skip.Add(p); } /// /// Expand the fields of these properties. /// /// Properties to expand; each subproperty will become one result. public void Expand(params string[] properties) { foreach (string p in properties) this.expand.Add(p); } } /// /// Base implementation of file streamer. /// public abstract class BaseFileStreamer { /// /// True if the file is expected to contain a header. /// protected bool HasHeader { get; set; } /// /// File header. /// protected string[] header; /// /// Used to read the file. /// protected ISharedStreamReader reader; /// /// Used to write the file. /// protected StreamWriter writer; /// /// Number of fields on each line. /// protected int fields; /// /// Current line number in file. /// protected long currentLine; /// /// If true, all lines are expected to have the same length. /// public bool AllLinesSameLength { get; set; } /// /// File to access. /// protected readonly string filename; /// /// Mode the stream operates. /// FileMode mode; /// /// Delegate used to report errors. /// protected readonly StatusReporter statusReporter; /// /// Create a base streamer. /// /// File to access. /// File mode. /// Delegate used to report errors. /// If true keep newlines. protected BaseFileStreamer(string filename, FileMode mode, bool keepNewlines, StatusReporter statusReporter) { this.reader = null; this.writer = null; this.filename = filename; this.mode = mode; this.statusReporter = statusReporter; // ReSharper disable once DoNotCallOverridableMethodsInConstructor this.Reset(keepNewlines); } /// /// Go to the beginning. /// public virtual void Reset(bool keepNewlines) { this.Close(); switch (this.mode) { case FileMode.Open: this.reader = new FileSharedStreamReader(filename, keepNewlines); if (this.reader.Exception != null) throw this.reader.Exception; this.writer = null; break; case FileMode.Create: this.writer = new StreamWriter(filename); this.reader = null; break; default: this.Error("no support for file mode " + this.mode); break; } } /// /// We are done reading/writing to the csv file. /// public virtual void Close() { if (this.reader != null) this.reader.Close(); else if (this.writer != null) this.writer.Close(); } /// /// Read one line from the file. If the file has a header, the header should be read first. /// /// The line read. public virtual string[] ReadLine() { if (this.HasHeader && this.header == null) { this.Error("Attempt to read a line from a file before reading the header"); } return this.ReadLineInternal(); } /// /// Returns the header of the file; if the file does not have a header, this will trigger an exception. /// Must be called before ReadLine(). /// /// The file header. public virtual string[] ReadHeader() { if (!this.HasHeader) this.Error("Attempt to read header from a file without a header"); this.header = ReadLineInternal(); return this.header; } /// /// Line being parsed. /// public virtual long CurrentLineNumber { get { return this.currentLine; } } /// /// Must be called before WriteLine. Writes the file header. /// /// File header. public virtual void WriteHeader(IEnumerable hdr) { if (!this.HasHeader) this.Error("Attempt to add a header to a file without header"); this.header = hdr.ToArray(); this.WriteLineInternal(this.header.ToList()); } /// /// Writes a line in the file; if the file has a header, must be called after WriteHeader. /// /// Line to write to the file. public virtual void WriteLine(IEnumerable line) { if (this.HasHeader && this.header == null) this.Error("Attempt to write a line in file before a header"); this.WriteLineInternal(line.ToList()); } /// /// The contents of the file, except the header. /// Closes the file at the end. /// /// An iterator over the contents. public IEnumerable ReadFile() { string[] line; if (this.HasHeader) { line = this.ReadHeader(); if (line == null) goto done; } while (true) { line = this.ReadLine(); if (line == null) { goto done; } yield return line; } done: this.reader.Close(); this.reader = null; yield break; } /// /// Internal implementation for line reading. /// /// The line read as a sequence of strings. Returns null when there is nothing to read. protected abstract string[] ReadLineInternal(); /// /// Internal implementation of writing. /// /// Strings to be written to one file line. protected abstract void WriteLineInternal(List line); /// /// Signal an error. /// /// Error message. protected abstract void Error(string message); } /// /// Reads a file as a set of key-value pairs. /// Each record is a complete line. /// TODO: Change this to properly parse the fields. /// public class KVPFileStreamer : BaseFileStreamer { private const string separator = ","; /// /// Create a file streamer which knows how to operate on a KVP file. /// It always reads and writes complete lines. /// /// File to operate on. /// Mode of access. /// Delegate used to report errors. public KVPFileStreamer(string filename, FileMode mode, StatusReporter statusReporter) : base(filename, mode, false, statusReporter) { } /// /// Internal read implementation. /// /// A line read from the file. protected override string[] ReadLineInternal() { string line = this.reader.ReadLine(); if (line == null) { return null; } this.currentLine++; return new string[] { line }; } /// /// Internal write implementation. /// /// Line to write. protected override void WriteLineInternal(List line) { StringBuilder builder = new StringBuilder(); bool first = true; foreach (string w in line) { if (!first) builder.Append(separator); builder.Append(w); first = false; } this.writer.WriteLine(builder.ToString()); this.currentLine++; } /// /// Signal an error. /// /// Error message. protected override void Error(string message) { this.statusReporter("KVP file `" + this.filename + "' format error on line " + this.currentLine + ": " + message, StatusKind.Error); } } /// /// A comma-separated list of values in a file; may be used to read or write the file (but not both at the same time). /// (The separator may be something else besides comma too). /// Values may be quoted. /// public class CSVFileStreamer : BaseFileStreamer { /// /// Create a CSV file. /// /// File to create. /// Read or write? /// True if the file contains a header. /// Delegate used to report errors. public CSVFileStreamer(string filename, FileMode mode, bool hasHeader, StatusReporter statusReporter) : base(filename, mode, false, statusReporter) { this.HasHeader = hasHeader; this.header = null; this.fields = -1; // not known yet this.Separator = ','; this.currentLine = 0; this.AllLinesSameLength = true; } /// /// Field separator; by default it's comma. /// private char separator; /// /// The field separator. /// public char Separator { set { if (this.currentLine != 0) this.Error("You cannot change the file separator in the middle of the file"); if (value == '\"') throw new ArgumentException("You cannot use the quote as a field separator in a CSV file"); this.separator = value; } get { return this.separator; } } private void Check(IEnumerable line) { int linelen = line.Count(); if (this.fields == -1) this.fields = linelen; else if (this.AllLinesSameLength && (this.fields != linelen)) this.Error("Line has " + linelen + " instead of " + this.fields + " fields."); } /// /// Internal line reading from CSV files. /// /// A set of tokens read from a line. /// True when done reading. protected override string[] ReadLineInternal() { if (this.reader.EndOfStream) { return null; } string line = this.reader.ReadLine(); if (!line.Contains("\"")) { // quick case string[] retval = line.Split(this.Separator); Check(retval); this.currentLine++; return retval; } tryToSplit: List results = new List(); int currentIndex = 0; while (currentIndex < line.Length) { string word; if (line[currentIndex] == '\"') { currentIndex = this.ParseQuotedWord(line, currentIndex, out word); } else { currentIndex = this.ParseUnquotedWord(line, currentIndex, out word); } if (currentIndex == -1) { string nextOne = this.reader.ReadLine(); line += nextOne; this.currentLine++; goto tryToSplit; // retry parsing together with the next line } results.Add(word); if (currentIndex < line.Length) { // expect a separator if (line[currentIndex] != this.separator) this.Error("Not found expected separator character"); currentIndex++; } } this.currentLine++; this.Check(results); return results.ToArray(); } /// /// Read an unquoted word from the given line starting at index 'currentIndex'. /// /// Line to parse. /// Start index of word. /// Found word. /// Index of separator after word (or of end of line). private int ParseUnquotedWord(string line, int currentIndex, out string word) { int separatorIndex = line.IndexOf(this.separator, currentIndex); if (separatorIndex == -1) { word = line.Substring(currentIndex); return line.Length; } else { word = line.Substring(currentIndex, separatorIndex - currentIndex); return separatorIndex; } } /// /// Read a quoted word from the given line starting at index 'currentIndex'. /// /// Line to parse. /// Start index of word. /// Found word. /// Index of separator after word (or of end of line). Returns -1 if the end quote is missing. private int ParseQuotedWord(string line, int currentIndex, out string word) { int endIndex = currentIndex + 1; while (true) { endIndex = line.IndexOf('\"', endIndex); if (endIndex == -1) { this.Error("Newline in quoted string"); word = ""; return -1; } if (endIndex == line.Length - 1) { // last word on line break; } else if (line[endIndex + 1] == '\"') { // quoted quote, continue endIndex += 2; continue; } else { // end of quoted word break; } } word = line.Substring(currentIndex + 1, endIndex - currentIndex - 1); // drop the start and end quotes word = word.Replace("\"\"", "\""); // fix quoted quotes return endIndex + 1; } /// /// Signal an error that occurred during file reading. /// /// Message describing the error. protected override void Error(string message) { this.statusReporter("CSV file `" + Path.GetFileName(this.filename) + "' format error on line " + this.currentLine + ": " + message, StatusKind.Error); } /// /// Add a line to the file. /// /// Line to add. protected override void WriteLineInternal(List line) { Check(line); bool first = true; foreach (string word in line) { string wordToWrite = Quote(word); if (!first) this.writer.Write("{0}", this.separator); else first = false; this.writer.Write("{0}", wordToWrite); } this.writer.WriteLine(); this.currentLine++; } private string Quote(string word) { if (word.Contains('\"')) { // double the quotes word = word.Replace("\"", "\"\""); return "\"" + word + "\""; } // quote the whole word if it contains spaces if (word.Contains(this.Separator)) return "\"" + word + "\""; else return word; } } /// /// The objects in this class have each a unique id (in the class). /// public interface IUniqueId { /// /// Get the object's unique id. /// int Id { get; } } /// /// Interface for objects which have a name. /// public interface IName { /// /// Get the object's name. Cannot use just 'name' since this property is often already defined. /// string ObjectName { get; } } /// /// A stripped-down stream reader. /// Never throws an exception; rather, stores the state in the 'Exception' field. /// public interface ISharedStreamReader : IDisposable { /// /// Exception that occurred while opening the stream. /// Exception Exception { get; } /// /// True if the reader has reached the end of stream. /// bool EndOfStream { get; } /// /// Read one line from the stream. /// /// string ReadLine(); /// /// Close the actual stream reader. /// void Close(); /// /// Read the stream to the end from the current position. /// /// The contents of the stream. /// Can be used to cancel the reading. string ReadToEnd(CancellationToken token); /// /// Read all the lines remaining in the stream. /// /// An iterator over all remaining lines. IEnumerable ReadAllLines(); } /// /// Common functionality too all ISharedStreamReaders. /// public abstract class BaseSharedStreamReader : ISharedStreamReader { /// /// Exception that occurred while opening the stream. /// public Exception Exception { get; protected set; } /// /// An exception occurred while trying to build this stream. /// /// Exception that occurred. protected BaseSharedStreamReader(Exception ex) { this.Exception = ex; } /// /// Basic shared stream reader. /// protected BaseSharedStreamReader() { this.Exception = null; } /// /// True if we have reached the end of the stream. /// public abstract bool EndOfStream { get; } /// /// Read one line from the stream. /// /// public abstract string ReadLine(); /// /// Dispose of the stream. /// public abstract void Dispose(); /// /// Read the whole stream to the end. /// /// A string containing the whole contents of the stream. /// Can be used to cancel the reading. public virtual string ReadToEnd(CancellationToken token) { StringBuilder result = new StringBuilder(); foreach (string s in this.ReadAllLines()) { token.ThrowIfCancellationRequested(); result.AppendLine(s); } return result.ToString(); } /// /// Close the stream. /// public abstract void Close(); /// /// Read all the lines remaining in the stream. /// /// An iterator over all remaining lines. public IEnumerable ReadAllLines() { while (! this.EndOfStream) { yield return this.ReadLine(); } this.Close(); } } /// /// SharedStreamReader reading from another stream. /// public class SharedStreamReader : BaseSharedStreamReader { /// /// If not null use this stream to write to the cache. /// StreamWriter cacheWriter; /// /// Delegate to call when stream is closed if caching. /// Action onClose; /// /// If true the reader keeps all newlines. /// private bool readerKeepsNewlines; /// /// A shared stream reader representing an exception. /// /// Exception represented by this stream reader. public SharedStreamReader(Exception ex) : base(ex) { this.cacheWriter = null; } /// /// Actual stream where the data is being read from. /// protected ISimpleStreamReader actualReader; /// /// Create a stream reader for the specified stream. /// /// Stream to read. /// Keep newlines in the input stream. public SharedStreamReader(ISimpleStreamReader reader, bool keepNewlines) { this.actualReader = reader; this.cacheWriter = null; this.readerKeepsNewlines = keepNewlines; this.readerKeepsNewlines = false; } /// /// Create a stream reader for the specified stream, cache the file in the specified stream. /// /// Stream to read. /// Use this stream to copy the file to a cache. /// Delegate to call when stream is completely read. /// If true the reader keeps all newlines. public SharedStreamReader(ISimpleStreamReader reader, StreamWriter cacheWriter, bool readerKeepsNewlines, Action onClose) { this.readerKeepsNewlines = readerKeepsNewlines; this.actualReader = reader; this.cacheWriter = cacheWriter; this.onClose = onClose; } /// /// Set the cache writer stream. /// /// Stream used to write to the cache. /// Action to invoke on close. /// If true keep newlines. public void SetCacheWriter(StreamWriter cw, bool keepNewlines, Action onCl) { this.readerKeepsNewlines = keepNewlines; this.cacheWriter = cw; this.onClose = onCl; } /// /// SharedStreamReader reading from a null stream. /// public SharedStreamReader() { this.actualReader = new WrapperSimpleStreamReader(StreamReader.Null); this.cacheWriter = null; this.readerKeepsNewlines = false; } /// /// True if the reader has reached the end of stream. /// public override bool EndOfStream { get { return this.actualReader.EndOfStream; } } /// /// Read one line from the stream. /// /// The read line. public override string ReadLine() { string line = this.actualReader.ReadLine(); if (this.cacheWriter != null) { if (this.readerKeepsNewlines) this.cacheWriter.Write(line); else this.cacheWriter.WriteLine(line); } return line; } /// /// Close the actual stream reader. /// public override void Close() { this.actualReader.Close(); if (this.cacheWriter != null) this.cacheWriter.Close(); if (this.onClose != null) this.onClose(); } /// /// Done with the stream. /// public override void Dispose() { this.actualReader.Dispose(); } /// /// Read the stream to the end from the current position. /// /// The contents of the stream. /// Can be used to cancel the reading. public override string ReadToEnd(CancellationToken token) { token.ThrowIfCancellationRequested(); string result = this.actualReader.ReadToEnd(token); if (this.cacheWriter != null) { this.cacheWriter.Write(result); this.cacheWriter.Close(); if (this.onClose != null) this.onClose(); } return result; } } /// /// SharedStreamReader reading from another stream. /// public class FileSharedStreamReader : SharedStreamReader { /// /// File that is being read. /// string file; /// /// Create a file stream reader representing an exception. /// /// Exception. public FileSharedStreamReader(Exception ex) : base(ex) { } /// /// Create a stream reader for the specified file. /// /// File to read. /// If true keep newlines. public FileSharedStreamReader(string file, bool keepNewline) { try { this.file = file; Stream rd = new FileStream(file, FileMode.Open, FileAccess.Read, FileShare.ReadWrite); this.actualReader = new SimpleStreamReader(rd, keepNewline); } catch (Exception ex) { // I don't know how to handle other exceptions. this.file = "Exception: " + ex.Message; this.Exception = ex; return; } } /// /// Create a file shared stream reader backed-up by a cache. /// /// File to read from. /// Cache here the contents read from the file. /// Action to invoke when file is closed. /// If true keep newlines. public FileSharedStreamReader(string file, StreamWriter cache, bool keepNewlines, Action onClose) { try { Stream rd = new FileStream(file, FileMode.Open, FileAccess.Read, FileShare.ReadWrite); this.actualReader = new SimpleStreamReader(rd, true); this.SetCacheWriter(cache, keepNewlines, onClose); } catch (Exception ex) { // I don't know how to handle other exceptions. this.Exception = ex; return; } } /// /// String representation of the File reader. /// /// A string representing the stream reader. public override string ToString() { return this.file; } } /// /// A shared stream reader reading from a string collection. /// public class StringIteratorStreamReader : BaseSharedStreamReader { /// /// Read from this iterator. /// IEnumerator contents; /// /// True if we have reached the end of the stream. /// bool endOfStream; private bool keepNewlines; /// /// End of the stream. /// public override bool EndOfStream { get { return this.endOfStream; } } /// /// Create a stream reader representing an exception. /// /// Exception. public StringIteratorStreamReader(Exception ex) : base(ex) { this.endOfStream = true; } /// /// A string iterator reading from this data. /// /// Strings to read from; each is a "line". /// If true keep the newlines. public StringIteratorStreamReader(IEnumerable data, bool keepNewlines) { this.keepNewlines = keepNewlines; this.contents = data.GetEnumerator(); this.endOfStream = !this.contents.MoveNext(); } /// /// Read one line from the stream. /// /// One line from the set of strings. public override string ReadLine() { string line = this.contents.Current; this.endOfStream = !this.contents.MoveNext(); return line; } /// /// Get rid of the stream. /// public override void Dispose() { } /// /// Finish reading the stream. /// public override void Close() { this.endOfStream = true; this.contents = null; } } /// /// A completely stripped-down StreamReader; only 3 public methods. /// public interface ISimpleStreamReader : IDisposable { /// /// Read one line of text. /// /// The read line. string ReadLine(); /// /// True if we have reached the end of stream. /// bool EndOfStream { get; } /// /// Read all the data from the stream. /// /// Cancellation token. /// All the data in the stream. string ReadToEnd(CancellationToken token); /// /// Close the stream. /// void Close(); } /// /// A simple stream reader which wraps a true StreamReader. /// This class is only here for compatibility purposes, it should be unused. /// public class WrapperSimpleStreamReader : ISimpleStreamReader { private StreamReader reader; /// /// Create a Wrapper around a stream reader. /// /// Stream reader to wrap. public WrapperSimpleStreamReader(StreamReader reader) { this.reader = reader; } /// /// Read one line of text. /// /// The read line. public string ReadLine() { return this.reader.ReadLine(); } /// /// True if we have reached the end of stream. /// public bool EndOfStream { get { return this.reader.EndOfStream; } } /// /// Read all the data from the stream. /// /// Cancellation token. /// All the data in the stream. public string ReadToEnd(CancellationToken token) { token.ThrowIfCancellationRequested(); return this.reader.ReadToEnd(); } /// /// Performs application-defined tasks associated with freeing, releasing, or resetting unmanaged resources. /// /// 2 public void Dispose() { this.reader.Dispose(); } /// /// Close the stream. /// public void Close() { this.reader.Close(); } } /// /// A simple stream reader. This is almost like a standard StreamReader, but /// has the option not to strip the end-of-line characters in ReadLine. /// public class SimpleStreamReader : ISimpleStreamReader { /// /// Stream we are reading from. /// private Stream stream; /// /// If true do not strip the End of line characters. /// private bool keepEndOfLine; /// /// FIFO buffer. /// /// Type of data stored in buffer. private class Buffer { /// /// Data in buffer. /// public T[] Data; /// /// Last index of data available in the buffer. /// public int EndOfData; /// /// Current position for reading from buffer. /// public int CurrentPosition; private const int minBufferSize = 4096; /// /// Allocate a buffer. /// /// Buffer size. public Buffer(int size) { if (size < minBufferSize) size = minBufferSize; this.Data = new T[size]; this.EndOfData = 0; this.CurrentPosition = 0; } /// /// Actual size of allocated buffer. /// public int Size { get { return this.Data.Length; } } /// /// True if the buffer is empty. /// /// A boolean indicating whether there is any data in the buffer. public bool IsEmpty() { return this.CurrentPosition == this.EndOfData; } /// /// Items available in the buffer. /// public int ItemsAvailable { get { return this.EndOfData - this.CurrentPosition; } } /// /// Value at specified index in buffer. /// /// Index of value in buffer. /// The value in the buffer. public T this[int index] { get { if (index < 0 || index > this.EndOfData) throw new ArgumentOutOfRangeException("index", "must be between 0 and " + this.EndOfData); return this.Data[index]; } } /// /// Delete first items from the buffer. /// /// Number of items to delete. public void DeletePreamble(int items) { if (this.EndOfData < items) throw new ArgumentException("Cannot delete " + items + " from buffer since only " + this.EndOfData + " are present"); Buffer.BlockCopy(this.Data, items, this.Data, 0, this.EndOfData - items); this.EndOfData -= items; } } /// /// Buffer for read data. /// private Buffer byteBuffer; /// /// Buffer for decoded data. /// private Buffer charBuffer; private bool checkPreamble; /// /// Files may have a preamble which indicates the data encoding. /// private byte[] preamble; /// /// If true encoding must be detected. /// private bool encodingUnknown; // ReSharper disable once PrivateFieldCanBeConvertedToLocalVariable private Encoding dataEncoding; private Decoder dataDecoder; /// /// Create a SimpleStream reader to read from a stream. /// /// Stream to read from. /// If true do not strip the end of line characters. public SimpleStreamReader(Stream Stream, bool keepEndOfLine = false) : this(Stream, keepEndOfLine, Encoding.UTF8, true) { } /// /// Create a SimpleStream reader reading from a stream. /// /// Stream to read from. /// If true do not strip the end of line characters. /// Size of buffer to use when reading from the underlying stream. /// Character encoding to use. /// If true detect the encoding. public SimpleStreamReader(Stream stream, bool keepEndOfLine, Encoding encoding, bool detectEncoding, int bufferSize = 4096) { if (stream == null) throw new ArgumentNullException("stream"); if (!stream.CanRead) throw new ArgumentException("StreamNotReadable"); if (bufferSize <= 0) throw new ArgumentOutOfRangeException("bufferSize"); this.dataEncoding = encoding; this.dataDecoder = this.dataEncoding.GetDecoder(); this.stream = stream; this.keepEndOfLine = keepEndOfLine; this.byteBuffer = new Buffer(bufferSize); int charSize = encoding.GetMaxCharCount(this.byteBuffer.Size); this.charBuffer = new Buffer(charSize); this.encodingUnknown = detectEncoding; this.preamble = encoding.GetPreamble(); this.checkPreamble = (this.preamble.Length > 0); } /// /// Read one line of text. /// /// The read line. public string ReadLine() { if (this.stream == null) throw new InvalidOperationException("Reader closed"); if (this.charBuffer.IsEmpty()) { if (this.ReadAndConvert() == 0) return null; } StringBuilder sb = new StringBuilder(); do { int i = this.charBuffer.CurrentPosition; do { char ch = this.charBuffer[i]; if (ch == '\r' || ch == '\n') { int offset = this.keepEndOfLine ? 1 : 0; sb.Append(this.charBuffer.Data, this.charBuffer.CurrentPosition, i + offset - this.charBuffer.CurrentPosition); this.charBuffer.CurrentPosition = i + 1; if (ch == '\r') { // check for \r\n if (this.charBuffer.CurrentPosition < this.charBuffer.EndOfData || // one more character available this.ReadAndConvert() > 0) // read next batch { if (this.charBuffer[this.charBuffer.CurrentPosition] == '\n') this.charBuffer.CurrentPosition++; if (this.keepEndOfLine) sb.Append('\n'); } } return sb.ToString(); } i++; } while (i < this.charBuffer.EndOfData); sb.Append(this.charBuffer.Data, this.charBuffer.CurrentPosition, this.charBuffer.ItemsAvailable); } while (this.ReadAndConvert() > 0); return sb.ToString(); } /// /// True if we have reached the end of stream. /// public bool EndOfStream { get { if (this.stream == null) throw new InvalidOperationException("Reader closed"); if (!this.charBuffer.IsEmpty()) return false; int numRead = this.ReadAndConvert(); return numRead == 0; } } private bool CheckForPreamble() { if (this.checkPreamble) { int len = (this.byteBuffer.EndOfData >= (this.preamble.Length)) ? (this.preamble.Length - this.byteBuffer.CurrentPosition) : this.byteBuffer.ItemsAvailable; for (int i = 0; i < len; i++) { if (this.byteBuffer.Data[this.byteBuffer.CurrentPosition + i] != this.preamble[this.byteBuffer.CurrentPosition + i]) { this.checkPreamble = false; return false; } } // preamble found this.byteBuffer.DeletePreamble(this.preamble.Length); this.byteBuffer.CurrentPosition = 0; this.checkPreamble = false; this.encodingUnknown = false; } return this.checkPreamble; } /// /// Read one buffer of bytes, and transfer them to a byte of chars. /// Return number of chars transferred. /// /// The number of chars read. private int ReadAndConvert() { this.charBuffer.EndOfData = this.charBuffer.CurrentPosition = 0; if (!this.checkPreamble) this.byteBuffer.EndOfData = 0; do { if (this.checkPreamble) { int len = stream.Read(this.byteBuffer.Data, this.byteBuffer.CurrentPosition, this.byteBuffer.Size - this.byteBuffer.CurrentPosition); if (len == 0) { if (this.byteBuffer.EndOfData > 0) { this.charBuffer.EndOfData += this.dataDecoder.GetChars(this.byteBuffer.Data, 0, this.byteBuffer.EndOfData, this.charBuffer.Data, this.charBuffer.EndOfData); this.byteBuffer.CurrentPosition = this.byteBuffer.EndOfData = 0; } return this.charBuffer.EndOfData; } this.byteBuffer.EndOfData += len; } else { this.byteBuffer.EndOfData = stream.Read(this.byteBuffer.Data, 0, this.byteBuffer.Size); if (this.byteBuffer.EndOfData == 0) // EOF return this.charBuffer.EndOfData; } if (this.CheckForPreamble()) continue; if (this.encodingUnknown && this.byteBuffer.EndOfData >= 2) this.DetectEncoding(); this.charBuffer.EndOfData += this.dataDecoder.GetChars(this.byteBuffer.Data, 0, this.byteBuffer.EndOfData, this.charBuffer.Data, this.charBuffer.EndOfData); } while (this.charBuffer.EndOfData == 0); return this.charBuffer.EndOfData; } private void DetectEncoding() { // first few bytes in the buffer, starting at 0 int len = this.byteBuffer.EndOfData; if (len < 2) return; this.encodingUnknown = false; bool changed = false; if (this.byteBuffer[0] == 0xFE && this.byteBuffer[1] == 0xFF) { // Big Endian Unicode this.dataEncoding = new UnicodeEncoding(true, true); this.byteBuffer.DeletePreamble(2); changed = true; } else if (this.byteBuffer[0] == 0xFF && this.byteBuffer[1] == 0xFE) { if (len < 4 || this.byteBuffer[2] != 0 || this.byteBuffer[3] != 0) { this.dataEncoding = new UnicodeEncoding(false, true); this.byteBuffer.DeletePreamble(2); changed = true; } else { this.dataEncoding = new UTF32Encoding(false, true); this.byteBuffer.DeletePreamble(4); changed = true; } } else if (len >= 3 && this.byteBuffer[0] == 0xEF && this.byteBuffer[1] == 0xBB && this.byteBuffer[2] == 0xBF) { this.dataEncoding = Encoding.UTF8; this.byteBuffer.DeletePreamble(3); changed = true; } else if (len >= 4 && this.byteBuffer[0] == 0 && this.byteBuffer[1] == 0 && this.byteBuffer[2] == 0xFE && this.byteBuffer[3] == 0xFF) { this.dataEncoding = new UTF32Encoding(true, true); this.byteBuffer.DeletePreamble(4); changed = true; } else if (len == 2) this.encodingUnknown = true; if (changed) { this.dataDecoder = this.dataEncoding.GetDecoder(); int maxCharsPerBuffer = this.dataEncoding.GetMaxCharCount(byteBuffer.Size); this.charBuffer = new Buffer(maxCharsPerBuffer); } } /// /// Read all the data from the stream. /// /// Cancellation token. /// All the data in the stream. public string ReadToEnd(CancellationToken token) { if (this.stream == null) throw new InvalidOperationException("Stream closed"); StringBuilder builder = new StringBuilder(); do { token.ThrowIfCancellationRequested(); builder.Append(this.charBuffer.Data, this.charBuffer.CurrentPosition, this.charBuffer.ItemsAvailable); this.charBuffer.CurrentPosition = this.charBuffer.EndOfData; this.ReadAndConvert(); } while (this.charBuffer.EndOfData > 0); return builder.ToString(); } /// /// Close the stream. /// public void Close() { this.Dispose(true); } /// /// Performs application-defined tasks associated with freeing, releasing, or resetting unmanaged resources. /// /// 2 public void Dispose() { this.Dispose(false); } /// /// Internal dispose method. /// /// protected void Dispose(bool disposing) { this.stream.Dispose(); this.stream = null; this.charBuffer = null; this.byteBuffer = null; } } }