/*
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 StreamReader 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");
// ReSharper disable once AssignNullToNotNullAttribute
StreamReader respReader = new StreamReader(resp.GetResponseStream());
return respReader;
}
///
/// 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