using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.Diagnostics.Contracts;
using System.Globalization;
using System.IO;
using System.Linq;
using System.Net;
using System.Text;
using System.Text.RegularExpressions;
namespace PleaseIgnore.IntelMap {
///
/// Series of helper methods to assist with the construction of
/// classes within PleaseIgnore.IntelMap.
///
///
internal static class IntelExtensions {
// The Unix time epoc
private static readonly DateTime Epoch = new DateTime(1970, 1, 1, 0, 0, 0, DateTimeKind.Utc);
// The 'unreserved' characters from RFC 3986
private const string Unreserved = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789-_.~";
// Convert a number 0...15 to hex
private const string HexUpperString = "0123456789ABCDEF";
private const string HexLowerString = "0123456789abcdef";
// Category for Network Tracing
public const string WebTraceCategory = "PleaseIgnore.IntelMap";
// Downtime in ticks from beginning of day
private const Int64 DowntimeTicks = 11L * TimeSpan.TicksPerHour;
// Priority list of IntelChannelStatus
private static readonly IntelStatus[] StatusPriority = new IntelStatus[] {
IntelStatus.FatalError,
IntelStatus.AuthenticationError,
IntelStatus.NetworkError,
IntelStatus.InvalidPath,
IntelStatus.Active,
IntelStatus.Waiting
};
// The base URL for requests on the intel map server
public readonly static Uri BaseUrl = new Uri("http://map.pleaseignore.com/");
// The URL for quering the channel list
public readonly static Uri ChannelsUrl = new Uri(BaseUrl, "intelchannels.pl");
// The URL for reporting intel
public readonly static Uri ReportUrl = new Uri(BaseUrl, "report.pl");
///
/// Gets the time and date of the most recent scheduled Tranquility
/// downtime.
///
public static DateTime LastDowntime {
get {
var nowTicks = DateTime.UtcNow.Ticks;
var eveTicks = nowTicks - DowntimeTicks;
return new DateTime(
eveTicks - eveTicks % TimeSpan.TicksPerDay + DowntimeTicks,
DateTimeKind.Utc);
}
}
///
/// Gets the time and date of the next scheduled Tranquility
/// downtime.
///
public static DateTime NextDowntime {
get {
return LastDowntime + new TimeSpan(TimeSpan.TicksPerDay);
}
}
///
/// Runs an action against each member of a collection.
///
///
/// The type of the elements of .
///
///
/// An to process.
///
///
/// A function to execute on each member of .
///
///
/// The collection after
/// has been executed on each member.
///
public static IEnumerable ForEach(this IEnumerable source, Action action) {
Contract.Requires(source != null, "source");
Contract.Requires(action != null, "action");
foreach (var item in source) {
action(item);
}
return source;
}
///
/// Converts the value of the current
/// object to a standard Unix timestamp.
///
///
/// The object to convert
///
///
/// The representation of as a Unix
/// timestamp, specifically the number of seconds elapsed since
/// midnight, 1 Jan 1970 GMT.
///
[Pure]
public static double ToUnixTime(this DateTime timestamp) {
Contract.Ensures(!double.IsInfinity(Contract.Result()));
Contract.Ensures(!double.IsNaN(Contract.Result()));
return Math.Floor((timestamp.ToUniversalTime() - Epoch).TotalSeconds);
}
///
/// Picks the highest priority status out of an array of
/// .
///
///
/// An array of values.
///
///
/// The highest priority status from .
///
[Pure]
public static IntelStatus Combine(params IntelStatus[] array) {
Contract.Requires(array != null);
Contract.Requires(array.Length > 0);
foreach (var status in StatusPriority) {
if (array.Any(x => x == status)) {
return status;
}
}
return array[0];
}
///
/// Tests if a value of the
/// enumeration refers to a "running" state.
///
///
/// Value of to test.
///
///
/// if the value of
/// refers to a normal operating state; otherwise,
/// if it's in a stopped state.
///
[Pure]
public static bool IsRunning(this IntelStatus status) {
switch (status) {
case IntelStatus.Active:
case IntelStatus.InvalidPath:
case IntelStatus.Starting:
case IntelStatus.Waiting:
case IntelStatus.NetworkError:
case IntelStatus.AuthenticationError:
return true;
default:
return false;
}
}
///
/// Tests if a value of the
/// enumeration refers to an "error" state.
///
///
/// Value of to test.
///
///
/// if the value of
/// refers to an error state; otherwise,
/// if it's in a stopped state.
///
[Pure]
public static bool IsError(this IntelStatus status) {
switch (status) {
case IntelStatus.NetworkError:
case IntelStatus.AuthenticationError:
case IntelStatus.FatalError:
case IntelStatus.InvalidPath:
return true;
default:
return false;
}
}
///
/// Submits a standard POST to an HTTP(S) server.
///
///
/// The instance of to use when submitting
/// the HTTP POST.
///
///
/// The "application/x-www-form-urlencoded" encoded payload to
/// send as the POST content.
///
///
/// The instance of providing the server's
/// response to the POST.
///
public static WebResponse Post(this WebRequest webRequest, byte[] payload) {
Contract.Requires(webRequest != null, "webRequest");
Contract.Requires(payload != null, "payload");
Contract.Ensures(Contract.Result() != null);
return Post(webRequest, payload, 0, payload.Length);
}
///
/// Submits a standard POST to an HTTP(S) server.
///
///
/// The instance of to use when submitting
/// the HTTP POST.
///
///
/// The "application/x-www-form-urlencoded" encoded payload to
/// send as the POST content.
///
///
/// The zero-based byte offset in at
/// which to begin copying bytes to the server.
///
///
/// The number of bytes to be sent to the server.
///
///
/// The instance of providing the server's
/// response to the POST.
///
public static WebResponse Post(this WebRequest webRequest, byte[] payload, int offset, int count) {
Contract.Requires(webRequest != null, "webRequest");
Contract.Requires(payload != null, "payload");
Contract.Requires(offset >= 0, "offset");
Contract.Requires(count >= 0, "count");
Contract.Requires(offset + count <= payload.Length);
Contract.Ensures(Contract.Result() != null);
try {
Trace.WriteLine("<< " + Encoding.UTF8.GetString(payload, offset, count),
WebTraceCategory);
webRequest.Method = "POST";
webRequest.ContentLength = count;
webRequest.ContentType = "application/x-www-form-urlencoded";
using (var stream = webRequest.GetRequestStream()) {
stream.Write(payload, offset, count);
}
return webRequest.GetResponse();
} catch (Exception e) {
Trace.WriteLine("!! " + e.Message, WebTraceCategory);
throw;
}
}
///
/// Submits a standard POST to an HTTP(S) server after encoding
/// the name-value pairs.
///
///
/// The instance of to use when submitting
/// the HTTP POST.
///
///
/// A list of name-value pairs to send to the server.
///
///
/// The instance of providing the server's
/// response to the POST.
///
///
/// The name-value pairs provided by
/// will be encoded as per the method described in the HTML
/// specification (part 17.13.4) after being converted to
/// strings by calling .
///
public static WebResponse Post(this WebRequest webRequest,
IEnumerable> variables) {
Contract.Requires(webRequest != null, "webRequest");
Contract.Requires(variables != null, "variables");
Contract.Requires(Contract.ForAll(variables,
x => !String.IsNullOrEmpty(x.Key)));
Contract.Ensures(Contract.Result() != null);
// Compute an upper bounds on the payload length
var maxLength = variables.Sum(x => 2 + 9 * x.Key.Length
+ 9 * (x.Value ?? String.Empty).Length);
// Build up the POST payload
using (var stream = new MemoryStream(maxLength)) {
bool first = true;
foreach (var keypair in variables) {
// Write a '&' between each variable
if (!first) {
stream.WriteByte((byte)'&');
} else {
first = false;
}
// Write the variable name
WriteUriEncoded(stream, keypair.Key);
// Write the '=' between the name and value
stream.WriteByte((byte)'=');
// Write the variable value
WriteUriEncoded(stream, keypair.Value);
}
return Post(webRequest, stream.ToArray());
}
}
///
/// Reads the entirety of the response body of a
/// and then disposes the instances.
///
///
/// The instance of to use when reading
/// the response payload.
///
///
/// The response payload parsed as a string.
///
public static string ReadContent(this WebResponse webResponse) {
Contract.Requires(webResponse != null, "webResponse");
Contract.Ensures(Contract.Result() != null);
try {
using (var stream = webResponse.GetResponseStream()) {
using (var reader = new StreamReader(stream)) {
var responseData = reader.ReadToEnd();
Trace.WriteLine(">> " + responseData, WebTraceCategory);
return responseData;
}
}
} catch (Exception e) {
Trace.WriteLine("!! " + e.Message, WebTraceCategory);
throw;
} finally {
webResponse.Close();
}
}
///
/// Writes the HTML form url encoded form a string to a
/// .
///
///
/// The instance of to write the data
/// string to.
///
///
/// The to be encoded.
///
public static void WriteUriEncoded(this Stream stream, string dataString) {
Contract.Requires(stream != null, "stream");
if (!String.IsNullOrEmpty(dataString)) {
var bytes = Encoding.UTF8.GetBytes(dataString);
foreach (var current in bytes) {
if (current == ' ') {
// Escape space as '+'
stream.WriteByte((byte)'+');
} else if (Unreserved.Contains((char)current)) {
// Characters that should not be escaped
stream.WriteByte(current);
} else {
// Everything else
stream.WriteByte((byte)'%');
stream.WriteByte((byte)HexUpperString[current / 16]);
stream.WriteByte((byte)HexUpperString[current % 16]);
}
}
}
}
///
/// Converts a byte array into a hex string using lower-case
/// characters for A-F.
///
///
/// Byte array to convert to a hex string.
///
///
/// The hex string representation of .
///
[Pure]
public static string ToLowerHexString(this byte[] array) {
Contract.Requires(array != null, "array");
Contract.Ensures(Contract.Result() != null);
Contract.Ensures(Contract.Result().Length == array.Length * 2);
if (array.Length == 0) {
return String.Empty;
} else {
StringBuilder builder = new StringBuilder(array.Length * 2);
foreach (var current in array) {
builder.Append(HexLowerString[current / 16]);
builder.Append(HexLowerString[current % 16]);
}
return builder.ToString();
}
}
///
/// Parse an integer found in a Regular Expression match.
///
///
/// Regular expression to parse.
///
///
/// The integer represented by the string matched by
/// .
///
///
/// decodes
/// according to the invariant culture.
///
[Pure]
public static int ToInt32(this Capture capture) {
Contract.Requires(capture != null, "capture");
return int.Parse(capture.Value, CultureInfo.InvariantCulture);
}
}
}