a little more consolidation

This commit is contained in:
LukePulverenti
2013-02-26 16:05:52 -05:00
parent fbac08fead
commit 51b3c32e2c
54 changed files with 3382 additions and 103 deletions

View File

@@ -1,482 +0,0 @@
using MediaBrowser.Common.IO;
using MediaBrowser.Common.Kernel;
using MediaBrowser.Common.Net;
using MediaBrowser.Model.Logging;
using MediaBrowser.Model.Net;
using System;
using System.Collections.Concurrent;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Net;
using System.Net.Cache;
using System.Net.Http;
using System.Text;
using System.Threading;
using System.Threading.Tasks;
namespace MediaBrowser.Networking.HttpManager
{
/// <summary>
/// Class HttpManager
/// </summary>
public class HttpManager : IHttpClient
{
/// <summary>
/// The _logger
/// </summary>
private readonly ILogger _logger;
/// <summary>
/// The _app paths
/// </summary>
private readonly IApplicationPaths _appPaths;
/// <summary>
/// Initializes a new instance of the <see cref="HttpManager" /> class.
/// </summary>
/// <param name="appPaths">The kernel.</param>
/// <param name="logger">The logger.</param>
public HttpManager(IApplicationPaths appPaths, ILogger logger)
{
if (appPaths == null)
{
throw new ArgumentNullException("appPaths");
}
if (logger == null)
{
throw new ArgumentNullException("logger");
}
_logger = logger;
_appPaths = appPaths;
}
/// <summary>
/// Holds a dictionary of http clients by host. Use GetHttpClient(host) to retrieve or create a client for web requests.
/// DON'T dispose it after use.
/// </summary>
/// <value>The HTTP clients.</value>
private readonly ConcurrentDictionary<string, HttpClient> _httpClients = new ConcurrentDictionary<string, HttpClient>();
/// <summary>
/// Gets
/// </summary>
/// <param name="host">The host.</param>
/// <returns>HttpClient.</returns>
/// <exception cref="System.ArgumentNullException">host</exception>
private HttpClient GetHttpClient(string host)
{
if (string.IsNullOrEmpty(host))
{
throw new ArgumentNullException("host");
}
HttpClient client;
if (!_httpClients.TryGetValue(host, out client))
{
var handler = new WebRequestHandler
{
AutomaticDecompression = DecompressionMethods.Deflate,
CachePolicy = new RequestCachePolicy(RequestCacheLevel.Revalidate)
};
client = new HttpClient(handler);
client.DefaultRequestHeaders.Add("Accept", "application/json,image/*");
client.Timeout = TimeSpan.FromSeconds(15);
_httpClients.TryAdd(host, client);
}
return client;
}
/// <summary>
/// Performs a GET request and returns the resulting stream
/// </summary>
/// <param name="url">The URL.</param>
/// <param name="resourcePool">The resource pool.</param>
/// <param name="cancellationToken">The cancellation token.</param>
/// <returns>Task{Stream}.</returns>
/// <exception cref="MediaBrowser.Model.Net.HttpException"></exception>
public async Task<Stream> Get(string url, SemaphoreSlim resourcePool, CancellationToken cancellationToken)
{
ValidateParams(url, resourcePool, cancellationToken);
cancellationToken.ThrowIfCancellationRequested();
await resourcePool.WaitAsync(cancellationToken).ConfigureAwait(false);
_logger.Info("HttpManager.Get url: {0}", url);
try
{
cancellationToken.ThrowIfCancellationRequested();
var msg = await GetHttpClient(GetHostFromUrl(url)).GetAsync(url, cancellationToken).ConfigureAwait(false);
EnsureSuccessStatusCode(msg);
return await msg.Content.ReadAsStreamAsync().ConfigureAwait(false);
}
catch (OperationCanceledException ex)
{
throw GetCancellationException(url, cancellationToken, ex);
}
catch (HttpRequestException ex)
{
_logger.ErrorException("Error getting response from " + url, ex);
throw new HttpException(ex.Message, ex);
}
finally
{
resourcePool.Release();
}
}
/// <summary>
/// Performs a POST request
/// </summary>
/// <param name="url">The URL.</param>
/// <param name="postData">Params to add to the POST data.</param>
/// <param name="resourcePool">The resource pool.</param>
/// <param name="cancellationToken">The cancellation token.</param>
/// <returns>stream on success, null on failure</returns>
/// <exception cref="System.ArgumentNullException">postData</exception>
/// <exception cref="MediaBrowser.Model.Net.HttpException"></exception>
public async Task<Stream> Post(string url, Dictionary<string, string> postData, SemaphoreSlim resourcePool, CancellationToken cancellationToken)
{
ValidateParams(url, resourcePool, cancellationToken);
if (postData == null)
{
throw new ArgumentNullException("postData");
}
cancellationToken.ThrowIfCancellationRequested();
var strings = postData.Keys.Select(key => string.Format("{0}={1}", key, postData[key]));
var postContent = string.Join("&", strings.ToArray());
var content = new StringContent(postContent, Encoding.UTF8, "application/x-www-form-urlencoded");
await resourcePool.WaitAsync(cancellationToken).ConfigureAwait(false);
_logger.Info("HttpManager.Post url: {0}", url);
try
{
cancellationToken.ThrowIfCancellationRequested();
var msg = await GetHttpClient(GetHostFromUrl(url)).PostAsync(url, content, cancellationToken).ConfigureAwait(false);
EnsureSuccessStatusCode(msg);
return await msg.Content.ReadAsStreamAsync().ConfigureAwait(false);
}
catch (OperationCanceledException ex)
{
throw GetCancellationException(url, cancellationToken, ex);
}
catch (HttpRequestException ex)
{
_logger.ErrorException("Error getting response from " + url, ex);
throw new HttpException(ex.Message, ex);
}
finally
{
resourcePool.Release();
}
}
/// <summary>
/// Downloads the contents of a given url into a temporary location
/// </summary>
/// <param name="url">The URL.</param>
/// <param name="resourcePool">The resource pool.</param>
/// <param name="cancellationToken">The cancellation token.</param>
/// <param name="progress">The progress.</param>
/// <param name="userAgent">The user agent.</param>
/// <returns>Task{System.String}.</returns>
/// <exception cref="System.ArgumentNullException">progress</exception>
/// <exception cref="MediaBrowser.Model.Net.HttpException"></exception>
public async Task<string> GetTempFile(string url, SemaphoreSlim resourcePool, CancellationToken cancellationToken, IProgress<double> progress, string userAgent = null)
{
ValidateParams(url, resourcePool, cancellationToken);
if (progress == null)
{
throw new ArgumentNullException("progress");
}
cancellationToken.ThrowIfCancellationRequested();
var tempFile = Path.Combine(_appPaths.TempDirectory, Guid.NewGuid() + ".tmp");
var message = new HttpRequestMessage(HttpMethod.Get, url);
if (!string.IsNullOrEmpty(userAgent))
{
message.Headers.Add("User-Agent", userAgent);
}
await resourcePool.WaitAsync(cancellationToken).ConfigureAwait(false);
_logger.Info("HttpManager.GetTempFile url: {0}, temp file: {1}", url, tempFile);
try
{
cancellationToken.ThrowIfCancellationRequested();
using (var response = await GetHttpClient(GetHostFromUrl(url)).SendAsync(message, HttpCompletionOption.ResponseHeadersRead, cancellationToken).ConfigureAwait(false))
{
EnsureSuccessStatusCode(response);
cancellationToken.ThrowIfCancellationRequested();
IEnumerable<string> lengthValues;
if (!response.Headers.TryGetValues("content-length", out lengthValues) &&
!response.Content.Headers.TryGetValues("content-length", out lengthValues))
{
// We're not able to track progress
using (var stream = await response.Content.ReadAsStreamAsync().ConfigureAwait(false))
{
using (var fs = new FileStream(tempFile, FileMode.Create, FileAccess.Write, FileShare.Read, StreamDefaults.DefaultFileStreamBufferSize, FileOptions.Asynchronous))
{
await stream.CopyToAsync(fs, StreamDefaults.DefaultCopyToBufferSize, cancellationToken).ConfigureAwait(false);
}
}
}
else
{
var length = long.Parse(string.Join(string.Empty, lengthValues.ToArray()));
using (var stream = ProgressStream.CreateReadProgressStream(await response.Content.ReadAsStreamAsync().ConfigureAwait(false), progress.Report, length))
{
using (var fs = new FileStream(tempFile, FileMode.Create, FileAccess.Write, FileShare.Read, StreamDefaults.DefaultFileStreamBufferSize, FileOptions.Asynchronous))
{
await stream.CopyToAsync(fs, StreamDefaults.DefaultCopyToBufferSize, cancellationToken).ConfigureAwait(false);
}
}
}
progress.Report(100);
cancellationToken.ThrowIfCancellationRequested();
}
return tempFile;
}
catch (OperationCanceledException ex)
{
// Cleanup
if (File.Exists(tempFile))
{
File.Delete(tempFile);
}
throw GetCancellationException(url, cancellationToken, ex);
}
catch (HttpRequestException ex)
{
_logger.ErrorException("Error getting response from " + url, ex);
// Cleanup
if (File.Exists(tempFile))
{
File.Delete(tempFile);
}
throw new HttpException(ex.Message, ex);
}
catch (Exception ex)
{
_logger.ErrorException("Error getting response from " + url, ex);
// Cleanup
if (File.Exists(tempFile))
{
File.Delete(tempFile);
}
throw;
}
finally
{
resourcePool.Release();
}
}
/// <summary>
/// Downloads the contents of a given url into a MemoryStream
/// </summary>
/// <param name="url">The URL.</param>
/// <param name="resourcePool">The resource pool.</param>
/// <param name="cancellationToken">The cancellation token.</param>
/// <returns>Task{MemoryStream}.</returns>
/// <exception cref="MediaBrowser.Model.Net.HttpException"></exception>
public async Task<MemoryStream> GetMemoryStream(string url, SemaphoreSlim resourcePool, CancellationToken cancellationToken)
{
ValidateParams(url, resourcePool, cancellationToken);
cancellationToken.ThrowIfCancellationRequested();
var message = new HttpRequestMessage(HttpMethod.Get, url);
await resourcePool.WaitAsync(cancellationToken).ConfigureAwait(false);
var ms = new MemoryStream();
_logger.Info("HttpManager.GetMemoryStream url: {0}", url);
try
{
cancellationToken.ThrowIfCancellationRequested();
using (var response = await GetHttpClient(GetHostFromUrl(url)).SendAsync(message, HttpCompletionOption.ResponseHeadersRead, cancellationToken).ConfigureAwait(false))
{
EnsureSuccessStatusCode(response);
cancellationToken.ThrowIfCancellationRequested();
using (var stream = await response.Content.ReadAsStreamAsync().ConfigureAwait(false))
{
await stream.CopyToAsync(ms, StreamDefaults.DefaultCopyToBufferSize, cancellationToken).ConfigureAwait(false);
}
cancellationToken.ThrowIfCancellationRequested();
}
ms.Position = 0;
return ms;
}
catch (OperationCanceledException ex)
{
ms.Dispose();
throw GetCancellationException(url, cancellationToken, ex);
}
catch (HttpRequestException ex)
{
_logger.ErrorException("Error getting response from " + url, ex);
ms.Dispose();
throw new HttpException(ex.Message, ex);
}
catch (Exception ex)
{
_logger.ErrorException("Error getting response from " + url, ex);
ms.Dispose();
throw;
}
finally
{
resourcePool.Release();
}
}
/// <summary>
/// Validates the params.
/// </summary>
/// <param name="url">The URL.</param>
/// <param name="resourcePool">The resource pool.</param>
/// <param name="cancellationToken">The cancellation token.</param>
/// <exception cref="System.ArgumentNullException">url</exception>
private void ValidateParams(string url, SemaphoreSlim resourcePool, CancellationToken cancellationToken)
{
if (string.IsNullOrEmpty(url))
{
throw new ArgumentNullException("url");
}
if (resourcePool == null)
{
throw new ArgumentNullException("resourcePool");
}
if (cancellationToken == null)
{
throw new ArgumentNullException("cancellationToken");
}
}
/// <summary>
/// Gets the host from URL.
/// </summary>
/// <param name="url">The URL.</param>
/// <returns>System.String.</returns>
private string GetHostFromUrl(string url)
{
var start = url.IndexOf("://", StringComparison.OrdinalIgnoreCase) + 3;
var len = url.IndexOf('/', start) - start;
return url.Substring(start, len);
}
/// <summary>
/// Performs application-defined tasks associated with freeing, releasing, or resetting unmanaged resources.
/// </summary>
public void Dispose()
{
Dispose(true);
GC.SuppressFinalize(this);
}
/// <summary>
/// Releases unmanaged and - optionally - managed resources.
/// </summary>
/// <param name="dispose"><c>true</c> to release both managed and unmanaged resources; <c>false</c> to release only unmanaged resources.</param>
protected virtual void Dispose(bool dispose)
{
if (dispose)
{
foreach (var client in _httpClients.Values.ToList())
{
client.Dispose();
}
_httpClients.Clear();
}
}
/// <summary>
/// Throws the cancellation exception.
/// </summary>
/// <param name="url">The URL.</param>
/// <param name="cancellationToken">The cancellation token.</param>
/// <param name="exception">The exception.</param>
/// <returns>Exception.</returns>
private Exception GetCancellationException(string url, CancellationToken cancellationToken, OperationCanceledException exception)
{
// If the HttpClient's timeout is reached, it will cancel the Task internally
if (!cancellationToken.IsCancellationRequested)
{
var msg = string.Format("Connection to {0} timed out", url);
_logger.Error(msg);
// Throw an HttpException so that the caller doesn't think it was cancelled by user code
return new HttpException(msg, exception) { IsTimedOut = true };
}
return exception;
}
/// <summary>
/// Ensures the success status code.
/// </summary>
/// <param name="response">The response.</param>
/// <exception cref="MediaBrowser.Model.Net.HttpException"></exception>
private void EnsureSuccessStatusCode(HttpResponseMessage response)
{
if (!response.IsSuccessStatusCode)
{
throw new HttpException(response.ReasonPhrase) { StatusCode = response.StatusCode };
}
}
}
}

View File

@@ -1,453 +0,0 @@
using MediaBrowser.Common.Extensions;
using MediaBrowser.Common.IO;
using MediaBrowser.Common.Kernel;
using MediaBrowser.Common.Net;
using MediaBrowser.Model.Logging;
using ServiceStack.Common;
using ServiceStack.Common.Web;
using ServiceStack.ServiceHost;
using ServiceStack.ServiceInterface;
using ServiceStack.WebHost.Endpoints;
using System;
using System.Globalization;
using System.IO;
using System.Linq;
using System.Net;
using System.Threading.Tasks;
using MimeTypes = MediaBrowser.Common.Net.MimeTypes;
using StreamWriter = MediaBrowser.Common.Net.StreamWriter;
namespace MediaBrowser.Networking.HttpServer
{
/// <summary>
/// Class BaseRestService
/// </summary>
public class BaseRestService : Service, IRestfulService
{
/// <summary>
/// Gets or sets the kernel.
/// </summary>
/// <value>The kernel.</value>
public IKernel Kernel { get; set; }
/// <summary>
/// Gets or sets the logger.
/// </summary>
/// <value>The logger.</value>
public ILogger Logger { get; set; }
/// <summary>
/// Gets a value indicating whether this instance is range request.
/// </summary>
/// <value><c>true</c> if this instance is range request; otherwise, <c>false</c>.</value>
protected bool IsRangeRequest
{
get
{
return Request.Headers.AllKeys.Contains("Range");
}
}
/// <summary>
/// To the optimized result.
/// </summary>
/// <typeparam name="T"></typeparam>
/// <param name="result">The result.</param>
/// <returns>System.Object.</returns>
/// <exception cref="System.ArgumentNullException">result</exception>
protected object ToOptimizedResult<T>(T result)
where T : class
{
if (result == null)
{
throw new ArgumentNullException("result");
}
Response.AddHeader("Vary", "Accept-Encoding");
return RequestContext.ToOptimizedResult(result);
}
/// <summary>
/// To the optimized result using cache.
/// </summary>
/// <typeparam name="T"></typeparam>
/// <param name="cacheKey">The cache key.</param>
/// <param name="lastDateModified">The last date modified.</param>
/// <param name="cacheDuration">Duration of the cache.</param>
/// <param name="factoryFn">The factory fn.</param>
/// <returns>System.Object.</returns>
/// <exception cref="System.ArgumentNullException">cacheKey</exception>
protected object ToOptimizedResultUsingCache<T>(Guid cacheKey, DateTime lastDateModified, TimeSpan? cacheDuration, Func<T> factoryFn)
where T : class
{
if (cacheKey == Guid.Empty)
{
throw new ArgumentNullException("cacheKey");
}
if (factoryFn == null)
{
throw new ArgumentNullException("factoryFn");
}
var key = cacheKey.ToString("N");
var result = PreProcessCachedResult(cacheKey, key, lastDateModified, cacheDuration, string.Empty);
if (result != null)
{
return result;
}
return ToOptimizedResult(factoryFn());
}
/// <summary>
/// To the cached result.
/// </summary>
/// <typeparam name="T"></typeparam>
/// <param name="cacheKey">The cache key.</param>
/// <param name="lastDateModified">The last date modified.</param>
/// <param name="cacheDuration">Duration of the cache.</param>
/// <param name="factoryFn">The factory fn.</param>
/// <param name="contentType">Type of the content.</param>
/// <returns>System.Object.</returns>
/// <exception cref="System.ArgumentNullException">cacheKey</exception>
protected object ToCachedResult<T>(Guid cacheKey, DateTime lastDateModified, TimeSpan? cacheDuration, Func<T> factoryFn, string contentType)
where T : class
{
if (cacheKey == Guid.Empty)
{
throw new ArgumentNullException("cacheKey");
}
if (factoryFn == null)
{
throw new ArgumentNullException("factoryFn");
}
var key = cacheKey.ToString("N");
var result = PreProcessCachedResult(cacheKey, key, lastDateModified, cacheDuration, contentType);
if (result != null)
{
return result;
}
return factoryFn();
}
/// <summary>
/// To the static file result.
/// </summary>
/// <param name="path">The path.</param>
/// <returns>System.Object.</returns>
/// <exception cref="System.ArgumentNullException">path</exception>
protected object ToStaticFileResult(string path)
{
if (string.IsNullOrEmpty(path))
{
throw new ArgumentNullException("path");
}
var dateModified = File.GetLastWriteTimeUtc(path);
var cacheKey = path + dateModified.Ticks;
return ToStaticResult(cacheKey.GetMD5(), dateModified, null, MimeTypes.GetMimeType(path), () => Task.FromResult(GetFileStream(path)));
}
/// <summary>
/// Gets the file stream.
/// </summary>
/// <param name="path">The path.</param>
/// <returns>Stream.</returns>
private Stream GetFileStream(string path)
{
return new FileStream(path, FileMode.Open, FileAccess.Read, FileShare.Read, StreamDefaults.DefaultFileStreamBufferSize, FileOptions.Asynchronous);
}
/// <summary>
/// To the static result.
/// </summary>
/// <param name="cacheKey">The cache key.</param>
/// <param name="lastDateModified">The last date modified.</param>
/// <param name="cacheDuration">Duration of the cache.</param>
/// <param name="contentType">Type of the content.</param>
/// <param name="factoryFn">The factory fn.</param>
/// <returns>System.Object.</returns>
/// <exception cref="System.ArgumentNullException">cacheKey</exception>
protected object ToStaticResult(Guid cacheKey, DateTime? lastDateModified, TimeSpan? cacheDuration, string contentType, Func<Task<Stream>> factoryFn)
{
if (cacheKey == Guid.Empty)
{
throw new ArgumentNullException("cacheKey");
}
if (factoryFn == null)
{
throw new ArgumentNullException("factoryFn");
}
var key = cacheKey.ToString("N");
var result = PreProcessCachedResult(cacheKey, key, lastDateModified, cacheDuration, contentType);
if (result != null)
{
return result;
}
var compress = ShouldCompressResponse(contentType);
if (compress)
{
Response.AddHeader("Vary", "Accept-Encoding");
}
return ToStaticResult(contentType, factoryFn, compress).Result;
}
/// <summary>
/// Shoulds the compress response.
/// </summary>
/// <param name="contentType">Type of the content.</param>
/// <returns><c>true</c> if XXXX, <c>false</c> otherwise</returns>
private bool ShouldCompressResponse(string contentType)
{
// It will take some work to support compression with byte range requests
if (IsRangeRequest)
{
return false;
}
// Don't compress media
if (contentType.StartsWith("audio/", StringComparison.OrdinalIgnoreCase) || contentType.StartsWith("video/", StringComparison.OrdinalIgnoreCase))
{
return false;
}
// Don't compress images
if (contentType.StartsWith("image/", StringComparison.OrdinalIgnoreCase))
{
return false;
}
if (contentType.StartsWith("font/", StringComparison.OrdinalIgnoreCase))
{
return false;
}
if (contentType.StartsWith("application/", StringComparison.OrdinalIgnoreCase))
{
return false;
}
return true;
}
/// <summary>
/// To the static result.
/// </summary>
/// <param name="contentType">Type of the content.</param>
/// <param name="factoryFn">The factory fn.</param>
/// <param name="compress">if set to <c>true</c> [compress].</param>
/// <returns>System.Object.</returns>
private async Task<object> ToStaticResult(string contentType, Func<Task<Stream>> factoryFn, bool compress)
{
if (!compress || string.IsNullOrEmpty(RequestContext.CompressionType))
{
Response.ContentType = contentType;
var stream = await factoryFn().ConfigureAwait(false);
return new StreamWriter(stream);
}
string content;
using (var stream = await factoryFn().ConfigureAwait(false))
{
using (var reader = new StreamReader(stream))
{
content = await reader.ReadToEndAsync().ConfigureAwait(false);
}
}
var contents = content.Compress(RequestContext.CompressionType);
return new CompressedResult(contents, RequestContext.CompressionType, contentType);
}
/// <summary>
/// Pres the process optimized result.
/// </summary>
/// <param name="cacheKey">The cache key.</param>
/// <param name="cacheKeyString">The cache key string.</param>
/// <param name="lastDateModified">The last date modified.</param>
/// <param name="cacheDuration">Duration of the cache.</param>
/// <param name="contentType">Type of the content.</param>
/// <returns>System.Object.</returns>
private object PreProcessCachedResult(Guid cacheKey, string cacheKeyString, DateTime? lastDateModified, TimeSpan? cacheDuration, string contentType)
{
Response.AddHeader("ETag", cacheKeyString);
if (IsNotModified(cacheKey, lastDateModified, cacheDuration))
{
AddAgeHeader(lastDateModified);
AddExpiresHeader(cacheKeyString, cacheDuration);
//ctx.Response.SendChunked = false;
if (!string.IsNullOrEmpty(contentType))
{
Response.ContentType = contentType;
}
return new HttpResult(new byte[] { }, HttpStatusCode.NotModified);
}
SetCachingHeaders(cacheKeyString, lastDateModified, cacheDuration);
return null;
}
/// <summary>
/// Determines whether [is not modified] [the specified cache key].
/// </summary>
/// <param name="cacheKey">The cache key.</param>
/// <param name="lastDateModified">The last date modified.</param>
/// <param name="cacheDuration">Duration of the cache.</param>
/// <returns><c>true</c> if [is not modified] [the specified cache key]; otherwise, <c>false</c>.</returns>
private bool IsNotModified(Guid? cacheKey, DateTime? lastDateModified, TimeSpan? cacheDuration)
{
var isNotModified = true;
if (Request.Headers.AllKeys.Contains("If-Modified-Since"))
{
DateTime ifModifiedSince;
if (DateTime.TryParse(Request.Headers["If-Modified-Since"], out ifModifiedSince))
{
isNotModified = IsNotModified(ifModifiedSince.ToUniversalTime(), cacheDuration, lastDateModified);
}
}
// Validate If-None-Match
if (isNotModified && (cacheKey.HasValue || !string.IsNullOrEmpty(Request.Headers["If-None-Match"])))
{
Guid ifNoneMatch;
if (Guid.TryParse(Request.Headers["If-None-Match"] ?? string.Empty, out ifNoneMatch))
{
if (cacheKey.HasValue && cacheKey.Value == ifNoneMatch)
{
return true;
}
}
}
return false;
}
/// <summary>
/// Determines whether [is not modified] [the specified if modified since].
/// </summary>
/// <param name="ifModifiedSince">If modified since.</param>
/// <param name="cacheDuration">Duration of the cache.</param>
/// <param name="dateModified">The date modified.</param>
/// <returns><c>true</c> if [is not modified] [the specified if modified since]; otherwise, <c>false</c>.</returns>
private bool IsNotModified(DateTime ifModifiedSince, TimeSpan? cacheDuration, DateTime? dateModified)
{
if (dateModified.HasValue)
{
var lastModified = NormalizeDateForComparison(dateModified.Value);
ifModifiedSince = NormalizeDateForComparison(ifModifiedSince);
return lastModified <= ifModifiedSince;
}
if (cacheDuration.HasValue)
{
var cacheExpirationDate = ifModifiedSince.Add(cacheDuration.Value);
if (DateTime.UtcNow < cacheExpirationDate)
{
return true;
}
}
return false;
}
/// <summary>
/// When the browser sends the IfModifiedDate, it's precision is limited to seconds, so this will account for that
/// </summary>
/// <param name="date">The date.</param>
/// <returns>DateTime.</returns>
private DateTime NormalizeDateForComparison(DateTime date)
{
return new DateTime(date.Year, date.Month, date.Day, date.Hour, date.Minute, date.Second, date.Kind);
}
/// <summary>
/// Sets the caching headers.
/// </summary>
/// <param name="cacheKey">The cache key.</param>
/// <param name="lastDateModified">The last date modified.</param>
/// <param name="cacheDuration">Duration of the cache.</param>
private void SetCachingHeaders(string cacheKey, DateTime? lastDateModified, TimeSpan? cacheDuration)
{
// Don't specify both last modified and Etag, unless caching unconditionally. They are redundant
// https://developers.google.com/speed/docs/best-practices/caching#LeverageBrowserCaching
if (lastDateModified.HasValue && (string.IsNullOrEmpty(cacheKey) || cacheDuration.HasValue))
{
AddAgeHeader(lastDateModified);
Response.AddHeader("LastModified", lastDateModified.Value.ToString("r"));
}
if (cacheDuration.HasValue)
{
Response.AddHeader("Cache-Control", "public, max-age=" + Convert.ToInt32(cacheDuration.Value.TotalSeconds));
}
else if (!string.IsNullOrEmpty(cacheKey))
{
Response.AddHeader("Cache-Control", "public");
}
else
{
Response.AddHeader("Cache-Control", "no-cache, no-store, must-revalidate");
Response.AddHeader("pragma", "no-cache, no-store, must-revalidate");
}
AddExpiresHeader(cacheKey, cacheDuration);
}
/// <summary>
/// Adds the expires header.
/// </summary>
/// <param name="cacheKey">The cache key.</param>
/// <param name="cacheDuration">Duration of the cache.</param>
private void AddExpiresHeader(string cacheKey, TimeSpan? cacheDuration)
{
if (cacheDuration.HasValue)
{
Response.AddHeader("Expires", DateTime.UtcNow.Add(cacheDuration.Value).ToString("r"));
}
else if (string.IsNullOrEmpty(cacheKey))
{
Response.AddHeader("Expires", "-1");
}
}
/// <summary>
/// Adds the age header.
/// </summary>
/// <param name="lastDateModified">The last date modified.</param>
private void AddAgeHeader(DateTime? lastDateModified)
{
if (lastDateModified.HasValue)
{
Response.AddHeader("Age", Convert.ToInt64((DateTime.UtcNow - lastDateModified.Value).TotalSeconds).ToString(CultureInfo.InvariantCulture));
}
}
}
}

View File

@@ -1,551 +0,0 @@
using Funq;
using MediaBrowser.Common.Extensions;
using MediaBrowser.Common.Kernel;
using MediaBrowser.Common.Net;
using MediaBrowser.Model.Logging;
using MediaBrowser.Model.Serialization;
using ServiceStack.Api.Swagger;
using ServiceStack.Common.Web;
using ServiceStack.Configuration;
using ServiceStack.Logging.NLogger;
using ServiceStack.ServiceHost;
using ServiceStack.ServiceInterface.Cors;
using ServiceStack.Text;
using ServiceStack.WebHost.Endpoints;
using ServiceStack.WebHost.Endpoints.Extensions;
using ServiceStack.WebHost.Endpoints.Support;
using System;
using System.Collections.Generic;
using System.Globalization;
using System.IO;
using System.Linq;
using System.Net;
using System.Net.WebSockets;
using System.Reactive.Linq;
using System.Reflection;
using System.Text;
using System.Threading.Tasks;
namespace MediaBrowser.Networking.HttpServer
{
/// <summary>
/// Class HttpServer
/// </summary>
public class HttpServer : HttpListenerBase, IHttpServer
{
/// <summary>
/// The logger
/// </summary>
private readonly ILogger _logger;
/// <summary>
/// Gets the URL prefix.
/// </summary>
/// <value>The URL prefix.</value>
public string UrlPrefix { get; private set; }
/// <summary>
/// The _rest services
/// </summary>
private readonly List<IRestfulService> _restServices = new List<IRestfulService>();
/// <summary>
/// Gets or sets the application host.
/// </summary>
/// <value>The application host.</value>
private IApplicationHost ApplicationHost { get; set; }
/// <summary>
/// This subscribes to HttpListener requests and finds the appropriate BaseHandler to process it
/// </summary>
/// <value>The HTTP listener.</value>
private IDisposable HttpListener { get; set; }
/// <summary>
/// Gets or sets the protobuf serializer.
/// </summary>
/// <value>The protobuf serializer.</value>
private IProtobufSerializer ProtobufSerializer { get; set; }
/// <summary>
/// Occurs when [web socket connected].
/// </summary>
public event EventHandler<WebSocketConnectEventArgs> WebSocketConnected;
/// <summary>
/// Gets the default redirect path.
/// </summary>
/// <value>The default redirect path.</value>
private string DefaultRedirectPath { get; set; }
/// <summary>
/// Gets or sets the name of the server.
/// </summary>
/// <value>The name of the server.</value>
private string ServerName { get; set; }
/// <summary>
/// Initializes a new instance of the <see cref="HttpServer" /> class.
/// </summary>
/// <param name="applicationHost">The application host.</param>
/// <param name="protobufSerializer">The protobuf serializer.</param>
/// <param name="logger">The logger.</param>
/// <param name="serverName">Name of the server.</param>
/// <param name="defaultRedirectpath">The default redirectpath.</param>
/// <exception cref="System.ArgumentNullException">urlPrefix</exception>
public HttpServer(IApplicationHost applicationHost, IProtobufSerializer protobufSerializer, ILogger logger, string serverName, string defaultRedirectpath)
: base()
{
if (protobufSerializer == null)
{
throw new ArgumentNullException("protobufSerializer");
}
if (logger == null)
{
throw new ArgumentNullException("logger");
}
if (applicationHost == null)
{
throw new ArgumentNullException("applicationHost");
}
if (string.IsNullOrEmpty(serverName))
{
throw new ArgumentNullException("serverName");
}
if (string.IsNullOrEmpty(defaultRedirectpath))
{
throw new ArgumentNullException("defaultRedirectpath");
}
ServerName = serverName;
DefaultRedirectPath = defaultRedirectpath;
ProtobufSerializer = protobufSerializer;
_logger = logger;
ApplicationHost = applicationHost;
EndpointHostConfig.Instance.ServiceStackHandlerFactoryPath = null;
EndpointHostConfig.Instance.MetadataRedirectPath = "metadata";
}
/// <summary>
/// Configures the specified container.
/// </summary>
/// <param name="container">The container.</param>
public override void Configure(Container container)
{
JsConfig.DateHandler = JsonDateHandler.ISO8601;
JsConfig.ExcludeTypeInfo = true;
JsConfig.IncludeNullValues = false;
SetConfig(new EndpointHostConfig
{
DefaultRedirectPath = DefaultRedirectPath,
// Tell SS to bubble exceptions up to here
WriteErrorsToResponse = false,
DebugMode = true
});
container.Adapter = new ContainerAdapter(ApplicationHost);
Plugins.Add(new SwaggerFeature());
Plugins.Add(new CorsFeature());
ServiceStack.Logging.LogManager.LogFactory = new NLogFactory();
}
/// <summary>
/// Starts the Web Service
/// </summary>
/// <param name="urlBase">A Uri that acts as the base that the server is listening on.
/// Format should be: http://127.0.0.1:8080/ or http://127.0.0.1:8080/somevirtual/
/// Note: the trailing slash is required! For more info see the
/// HttpListener.Prefixes property on MSDN.</param>
public override void Start(string urlBase)
{
if (string.IsNullOrEmpty(urlBase))
{
throw new ArgumentNullException("urlBase");
}
// *** Already running - just leave it in place
if (IsStarted)
{
return;
}
if (Listener == null)
{
Listener = new HttpListener();
}
EndpointHost.Config.ServiceStackHandlerFactoryPath = HttpListenerRequestWrapper.GetHandlerPathIfAny(urlBase);
UrlPrefix = urlBase;
Listener.Prefixes.Add(urlBase);
IsStarted = true;
Listener.Start();
HttpListener = CreateObservableStream().Subscribe(ProcessHttpRequestAsync);
}
/// <summary>
/// Creates the observable stream.
/// </summary>
/// <returns>IObservable{HttpListenerContext}.</returns>
private IObservable<HttpListenerContext> CreateObservableStream()
{
return Observable.Create<HttpListenerContext>(obs =>
Observable.FromAsync(() => Listener.GetContextAsync())
.Subscribe(obs))
.Repeat()
.Retry()
.Publish()
.RefCount();
}
/// <summary>
/// Processes incoming http requests by routing them to the appropiate handler
/// </summary>
/// <param name="context">The CTX.</param>
private async void ProcessHttpRequestAsync(HttpListenerContext context)
{
LogHttpRequest(context);
if (context.Request.IsWebSocketRequest)
{
await ProcessWebSocketRequest(context).ConfigureAwait(false);
return;
}
Task.Run(() =>
{
RaiseReceiveWebRequest(context);
try
{
ProcessRequest(context);
}
catch (InvalidOperationException ex)
{
HandleException(context.Response, ex, 422);
throw;
}
catch (ResourceNotFoundException ex)
{
HandleException(context.Response, ex, 404);
throw;
}
catch (FileNotFoundException ex)
{
HandleException(context.Response, ex, 404);
throw;
}
catch (DirectoryNotFoundException ex)
{
HandleException(context.Response, ex, 404);
throw;
}
catch (UnauthorizedAccessException ex)
{
HandleException(context.Response, ex, 401);
throw;
}
catch (ArgumentException ex)
{
HandleException(context.Response, ex, 400);
throw;
}
catch (Exception ex)
{
HandleException(context.Response, ex, 500);
throw;
}
});
}
/// <summary>
/// Processes the web socket request.
/// </summary>
/// <param name="ctx">The CTX.</param>
/// <returns>Task.</returns>
private async Task ProcessWebSocketRequest(HttpListenerContext ctx)
{
try
{
var webSocketContext = await ctx.AcceptWebSocketAsync(null).ConfigureAwait(false);
if (WebSocketConnected != null)
{
WebSocketConnected(this, new WebSocketConnectEventArgs { WebSocket = new NativeWebSocket(webSocketContext.WebSocket, _logger), Endpoint = ctx.Request.RemoteEndPoint.ToString() });
}
}
catch (Exception ex)
{
_logger.ErrorException("AcceptWebSocketAsync error", ex);
ctx.Response.StatusCode = 500;
ctx.Response.Close();
}
}
/// <summary>
/// Logs the HTTP request.
/// </summary>
/// <param name="ctx">The CTX.</param>
private void LogHttpRequest(HttpListenerContext ctx)
{
var log = new StringBuilder();
log.AppendLine("Url: " + ctx.Request.Url);
log.AppendLine("Headers: " + string.Join(",", ctx.Request.Headers.AllKeys.Select(k => k + "=" + ctx.Request.Headers[k])));
var type = ctx.Request.IsWebSocketRequest ? "Web Socket" : "HTTP " + ctx.Request.HttpMethod;
if (EnableHttpRequestLogging)
{
_logger.LogMultiline(type + " request received from " + ctx.Request.RemoteEndPoint, LogSeverity.Debug, log);
}
}
/// <summary>
/// Appends the error message.
/// </summary>
/// <param name="response">The response.</param>
/// <param name="ex">The ex.</param>
/// <param name="statusCode">The status code.</param>
private void HandleException(HttpListenerResponse response, Exception ex, int statusCode)
{
_logger.ErrorException("Error processing request", ex);
response.StatusCode = statusCode;
response.Headers.Add("Status", statusCode.ToString(new CultureInfo("en-US")));
response.Headers.Remove("Age");
response.Headers.Remove("Expires");
response.Headers.Remove("Cache-Control");
response.Headers.Remove("Etag");
response.Headers.Remove("Last-Modified");
response.ContentType = "text/plain";
if (!string.IsNullOrEmpty(ex.Message))
{
response.AddHeader("X-Application-Error-Code", ex.Message);
}
// This could fail, but try to add the stack trace as the body content
try
{
var sb = new StringBuilder();
sb.AppendLine("{");
sb.AppendLine("\"ResponseStatus\":{");
sb.AppendFormat(" \"ErrorCode\":{0},\n", ex.GetType().Name.EncodeJson());
sb.AppendFormat(" \"Message\":{0},\n", ex.Message.EncodeJson());
sb.AppendFormat(" \"StackTrace\":{0}\n", ex.StackTrace.EncodeJson());
sb.AppendLine("}");
sb.AppendLine("}");
response.StatusCode = 500;
response.ContentType = ContentType.Json;
var sbBytes = sb.ToString().ToUtf8Bytes();
response.OutputStream.Write(sbBytes, 0, sbBytes.Length);
response.Close();
}
catch (Exception errorEx)
{
_logger.ErrorException("Error processing failed request", errorEx);
}
}
/// <summary>
/// Overridable method that can be used to implement a custom hnandler
/// </summary>
/// <param name="context">The context.</param>
/// <exception cref="System.NotImplementedException">Cannot execute handler: + handler + at PathInfo: + httpReq.PathInfo</exception>
protected override void ProcessRequest(HttpListenerContext context)
{
if (string.IsNullOrEmpty(context.Request.RawUrl)) return;
var operationName = context.Request.GetOperationName();
var httpReq = new HttpListenerRequestWrapper(operationName, context.Request);
var httpRes = new HttpListenerResponseWrapper(context.Response);
var handler = ServiceStackHttpHandlerFactory.GetHandler(httpReq);
var serviceStackHandler = handler as IServiceStackHttpHandler;
if (serviceStackHandler != null)
{
var restHandler = serviceStackHandler as RestHandler;
if (restHandler != null)
{
httpReq.OperationName = operationName = restHandler.RestPath.RequestType.Name;
}
serviceStackHandler.ProcessRequest(httpReq, httpRes, operationName);
LogResponse(context);
httpRes.Close();
return;
}
throw new NotImplementedException("Cannot execute handler: " + handler + " at PathInfo: " + httpReq.PathInfo);
}
/// <summary>
/// Logs the response.
/// </summary>
/// <param name="ctx">The CTX.</param>
private void LogResponse(HttpListenerContext ctx)
{
var statusode = ctx.Response.StatusCode;
var log = new StringBuilder();
log.AppendLine(string.Format("Url: {0}", ctx.Request.Url));
log.AppendLine("Headers: " + string.Join(",", ctx.Response.Headers.AllKeys.Select(k => k + "=" + ctx.Response.Headers[k])));
var msg = "Http Response Sent (" + statusode + ") to " + ctx.Request.RemoteEndPoint;
if (EnableHttpRequestLogging)
{
_logger.LogMultiline(msg, LogSeverity.Debug, log);
}
}
/// <summary>
/// Creates the service manager.
/// </summary>
/// <param name="assembliesWithServices">The assemblies with services.</param>
/// <returns>ServiceManager.</returns>
protected override ServiceManager CreateServiceManager(params Assembly[] assembliesWithServices)
{
var types = _restServices.Select(r => r.GetType()).ToArray();
return new ServiceManager(new Container(), new ServiceController(() => types));
}
/// <summary>
/// Shut down the Web Service
/// </summary>
public override void Stop()
{
if (HttpListener != null)
{
HttpListener.Dispose();
HttpListener = null;
}
if (Listener != null)
{
Listener.Prefixes.Remove(UrlPrefix);
}
base.Stop();
}
/// <summary>
/// The _supports native web socket
/// </summary>
private bool? _supportsNativeWebSocket;
/// <summary>
/// Gets a value indicating whether [supports web sockets].
/// </summary>
/// <value><c>true</c> if [supports web sockets]; otherwise, <c>false</c>.</value>
public bool SupportsWebSockets
{
get
{
if (!_supportsNativeWebSocket.HasValue)
{
try
{
new ClientWebSocket();
_supportsNativeWebSocket = true;
}
catch (PlatformNotSupportedException)
{
_supportsNativeWebSocket = false;
}
}
return _supportsNativeWebSocket.Value;
}
}
/// <summary>
/// Gets or sets a value indicating whether [enable HTTP request logging].
/// </summary>
/// <value><c>true</c> if [enable HTTP request logging]; otherwise, <c>false</c>.</value>
public bool EnableHttpRequestLogging { get; set; }
/// <summary>
/// Adds the rest handlers.
/// </summary>
/// <param name="services">The services.</param>
public void Init(IEnumerable<IRestfulService> services)
{
_restServices.AddRange(services);
EndpointHost.ConfigureHost(this, ServerName, CreateServiceManager());
ContentTypeFilters.Register(ContentType.ProtoBuf, (reqCtx, res, stream) => ProtobufSerializer.SerializeToStream(res, stream), (type, stream) => ProtobufSerializer.DeserializeFromStream(stream, type));
Init();
}
}
/// <summary>
/// Class ContainerAdapter
/// </summary>
class ContainerAdapter : IContainerAdapter
{
/// <summary>
/// The _app host
/// </summary>
private readonly IApplicationHost _appHost;
/// <summary>
/// Initializes a new instance of the <see cref="ContainerAdapter" /> class.
/// </summary>
/// <param name="appHost">The app host.</param>
public ContainerAdapter(IApplicationHost appHost)
{
_appHost = appHost;
}
/// <summary>
/// Resolves this instance.
/// </summary>
/// <typeparam name="T"></typeparam>
/// <returns>``0.</returns>
public T Resolve<T>()
{
return _appHost.Resolve<T>();
}
/// <summary>
/// Tries the resolve.
/// </summary>
/// <typeparam name="T"></typeparam>
/// <returns>``0.</returns>
public T TryResolve<T>()
{
return _appHost.TryResolve<T>();
}
}
}

View File

@@ -1,165 +0,0 @@
using MediaBrowser.Common.Net;
using MediaBrowser.Model.Logging;
using System;
using System.Net.WebSockets;
using System.Threading;
using System.Threading.Tasks;
using WebSocketMessageType = MediaBrowser.Common.Net.WebSocketMessageType;
using WebSocketState = MediaBrowser.Common.Net.WebSocketState;
namespace MediaBrowser.Networking.HttpServer
{
/// <summary>
/// Class NativeWebSocket
/// </summary>
public class NativeWebSocket : IWebSocket
{
/// <summary>
/// The logger
/// </summary>
private readonly ILogger _logger;
/// <summary>
/// Gets or sets the web socket.
/// </summary>
/// <value>The web socket.</value>
private System.Net.WebSockets.WebSocket WebSocket { get; set; }
/// <summary>
/// Initializes a new instance of the <see cref="NativeWebSocket" /> class.
/// </summary>
/// <param name="socket">The socket.</param>
/// <param name="logger">The logger.</param>
/// <exception cref="System.ArgumentNullException">socket</exception>
public NativeWebSocket(System.Net.WebSockets.WebSocket socket, ILogger logger)
{
if (socket == null)
{
throw new ArgumentNullException("socket");
}
if (logger == null)
{
throw new ArgumentNullException("logger");
}
_logger = logger;
WebSocket = socket;
Receive();
}
/// <summary>
/// Gets or sets the state.
/// </summary>
/// <value>The state.</value>
public WebSocketState State
{
get
{
WebSocketState commonState;
if (!Enum.TryParse(WebSocket.State.ToString(), true, out commonState))
{
_logger.Warn("Unrecognized WebSocketState: {0}", WebSocket.State.ToString());
}
return commonState;
}
}
/// <summary>
/// Receives this instance.
/// </summary>
private async void Receive()
{
while (true)
{
byte[] bytes;
try
{
bytes = await ReceiveBytesAsync(CancellationToken.None).ConfigureAwait(false);
}
catch (WebSocketException ex)
{
_logger.ErrorException("Error reveiving web socket message", ex);
break;
}
if (OnReceiveDelegate != null)
{
OnReceiveDelegate(bytes);
}
}
}
/// <summary>
/// Receives the async.
/// </summary>
/// <param name="cancellationToken">The cancellation token.</param>
/// <returns>Task{WebSocketMessageInfo}.</returns>
/// <exception cref="System.Net.WebSockets.WebSocketException">Connection closed</exception>
private async Task<byte[]> ReceiveBytesAsync(CancellationToken cancellationToken)
{
var bytes = new byte[4096];
var buffer = new ArraySegment<byte>(bytes);
var result = await WebSocket.ReceiveAsync(buffer, cancellationToken).ConfigureAwait(false);
if (result.CloseStatus.HasValue)
{
throw new WebSocketException("Connection closed");
}
return buffer.Array;
}
/// <summary>
/// Sends the async.
/// </summary>
/// <param name="bytes">The bytes.</param>
/// <param name="type">The type.</param>
/// <param name="endOfMessage">if set to <c>true</c> [end of message].</param>
/// <param name="cancellationToken">The cancellation token.</param>
/// <returns>Task.</returns>
public Task SendAsync(byte[] bytes, WebSocketMessageType type, bool endOfMessage, CancellationToken cancellationToken)
{
System.Net.WebSockets.WebSocketMessageType nativeType;
if (!Enum.TryParse(type.ToString(), true, out nativeType))
{
_logger.Warn("Unrecognized WebSocketMessageType: {0}", type.ToString());
}
return WebSocket.SendAsync(new ArraySegment<byte>(bytes), nativeType, true, cancellationToken);
}
/// <summary>
/// Performs application-defined tasks associated with freeing, releasing, or resetting unmanaged resources.
/// </summary>
public void Dispose()
{
Dispose(true);
}
/// <summary>
/// Releases unmanaged and - optionally - managed resources.
/// </summary>
/// <param name="dispose"><c>true</c> to release both managed and unmanaged resources; <c>false</c> to release only unmanaged resources.</param>
protected virtual void Dispose(bool dispose)
{
if (dispose)
{
WebSocket.Dispose();
}
}
/// <summary>
/// Gets or sets the receive action.
/// </summary>
/// <value>The receive action.</value>
public Action<byte[]> OnReceiveDelegate { get; set; }
}
}

View File

@@ -1,27 +0,0 @@
using MediaBrowser.Common.Kernel;
using MediaBrowser.Common.Net;
using MediaBrowser.Model.Logging;
using MediaBrowser.Model.Serialization;
namespace MediaBrowser.Networking.HttpServer
{
/// <summary>
/// Class ServerFactory
/// </summary>
public static class ServerFactory
{
/// <summary>
/// Creates the server.
/// </summary>
/// <param name="applicationHost">The application host.</param>
/// <param name="protobufSerializer">The protobuf serializer.</param>
/// <param name="logger">The logger.</param>
/// <param name="serverName">Name of the server.</param>
/// <param name="defaultRedirectpath">The default redirectpath.</param>
/// <returns>IHttpServer.</returns>
public static IHttpServer CreateServer(IApplicationHost applicationHost, IProtobufSerializer protobufSerializer, ILogger logger, string serverName, string defaultRedirectpath)
{
return new HttpServer(applicationHost, protobufSerializer, logger, serverName, defaultRedirectpath);
}
}
}

View File

@@ -1,72 +0,0 @@
using System;
using System.Runtime.InteropServices;
using System.Security;
namespace MediaBrowser.Networking.Management
{
/// <summary>
/// Class NativeMethods
/// </summary>
[SuppressUnmanagedCodeSecurity]
public static class NativeMethods
{
//declare the Netapi32 : NetServerEnum method import
/// <summary>
/// Nets the server enum.
/// </summary>
/// <param name="ServerName">Name of the server.</param>
/// <param name="dwLevel">The dw level.</param>
/// <param name="pBuf">The p buf.</param>
/// <param name="dwPrefMaxLen">The dw pref max len.</param>
/// <param name="dwEntriesRead">The dw entries read.</param>
/// <param name="dwTotalEntries">The dw total entries.</param>
/// <param name="dwServerType">Type of the dw server.</param>
/// <param name="domain">The domain.</param>
/// <param name="dwResumeHandle">The dw resume handle.</param>
/// <returns>System.Int32.</returns>
[DllImport("Netapi32", CharSet = CharSet.Auto, SetLastError = true),
SuppressUnmanagedCodeSecurityAttribute]
public static extern int NetServerEnum(
string ServerName, // must be null
int dwLevel,
ref IntPtr pBuf,
int dwPrefMaxLen,
out int dwEntriesRead,
out int dwTotalEntries,
int dwServerType,
string domain, // null for login domain
out int dwResumeHandle
);
//declare the Netapi32 : NetApiBufferFree method import
/// <summary>
/// Nets the API buffer free.
/// </summary>
/// <param name="pBuf">The p buf.</param>
/// <returns>System.Int32.</returns>
[DllImport("Netapi32", SetLastError = true),
SuppressUnmanagedCodeSecurityAttribute]
public static extern int NetApiBufferFree(
IntPtr pBuf);
}
//create a _SERVER_INFO_100 STRUCTURE
/// <summary>
/// Struct _SERVER_INFO_100
/// </summary>
[StructLayout(LayoutKind.Sequential)]
public struct _SERVER_INFO_100
{
/// <summary>
/// The sv100_platform_id
/// </summary>
internal int sv100_platform_id;
/// <summary>
/// The sv100_name
/// </summary>
[MarshalAs(UnmanagedType.LPWStr)]
internal string sv100_name;
}
}

View File

@@ -1,377 +0,0 @@
using MediaBrowser.Common.Net;
using MediaBrowser.Model.Net;
using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.Linq;
using System.Management;
using System.Net;
using System.Net.Sockets;
using System.Runtime.InteropServices;
namespace MediaBrowser.Networking.Management
{
/// <summary>
/// Class NetUtils
/// </summary>
public class NetworkManager : INetworkManager
{
/// <summary>
/// Gets the machine's local ip address
/// </summary>
/// <returns>IPAddress.</returns>
public string GetLocalIpAddress()
{
var host = Dns.GetHostEntry(Dns.GetHostName());
var ip = host.AddressList.FirstOrDefault(i => i.AddressFamily == AddressFamily.InterNetwork);
if (ip == null)
{
return null;
}
return ip.ToString();
}
/// <summary>
/// Gets a random port number that is currently available
/// </summary>
/// <returns>System.Int32.</returns>
public int GetRandomUnusedPort()
{
var listener = new TcpListener(IPAddress.Any, 0);
listener.Start();
var port = ((IPEndPoint)listener.LocalEndpoint).Port;
listener.Stop();
return port;
}
/// <summary>
/// Creates the netsh URL registration.
/// </summary>
public void AuthorizeHttpListening(string url)
{
var startInfo = new ProcessStartInfo
{
FileName = "netsh",
Arguments = string.Format("http add urlacl url={0} user=\"NT AUTHORITY\\Authenticated Users\"", url),
CreateNoWindow = true,
WindowStyle = ProcessWindowStyle.Hidden,
Verb = "runas",
ErrorDialog = false
};
using (var process = Process.Start(startInfo))
{
process.WaitForExit();
}
}
/// <summary>
/// Adds the windows firewall rule.
/// </summary>
/// <param name="port">The port.</param>
/// <param name="protocol">The protocol.</param>
public void AddSystemFirewallRule(int port, NetworkProtocol protocol)
{
// First try to remove it so we don't end up creating duplicates
RemoveSystemFirewallRule(port, protocol);
var args = string.Format("advfirewall firewall add rule name=\"Port {0}\" dir=in action=allow protocol={1} localport={0}", port, protocol);
RunNetsh(args);
}
/// <summary>
/// Removes the windows firewall rule.
/// </summary>
/// <param name="port">The port.</param>
/// <param name="protocol">The protocol.</param>
public void RemoveSystemFirewallRule(int port, NetworkProtocol protocol)
{
var args = string.Format("advfirewall firewall delete rule name=\"Port {0}\" protocol={1} localport={0}", port, protocol);
RunNetsh(args);
}
/// <summary>
/// Runs the netsh.
/// </summary>
/// <param name="args">The args.</param>
private void RunNetsh(string args)
{
var startInfo = new ProcessStartInfo
{
FileName = "netsh",
Arguments = args,
CreateNoWindow = true,
WindowStyle = ProcessWindowStyle.Hidden,
Verb = "runas",
ErrorDialog = false
};
using (var process = new Process { StartInfo = startInfo })
{
process.Start();
process.WaitForExit();
}
}
/// <summary>
/// Returns MAC Address from first Network Card in Computer
/// </summary>
/// <returns>[string] MAC Address</returns>
public string GetMacAddress()
{
var mc = new ManagementClass("Win32_NetworkAdapterConfiguration");
var moc = mc.GetInstances();
var macAddress = String.Empty;
foreach (ManagementObject mo in moc)
{
if (macAddress == String.Empty) // only return MAC Address from first card
{
try
{
if ((bool)mo["IPEnabled"]) macAddress = mo["MacAddress"].ToString();
}
catch
{
mo.Dispose();
return "";
}
}
mo.Dispose();
}
return macAddress.Replace(":", "");
}
/// <summary>
/// Uses the DllImport : NetServerEnum with all its required parameters
/// (see http://msdn.microsoft.com/library/default.asp?url=/library/en-us/netmgmt/netmgmt/netserverenum.asp
/// for full details or method signature) to retrieve a list of domain SV_TYPE_WORKSTATION
/// and SV_TYPE_SERVER PC's
/// </summary>
/// <returns>Arraylist that represents all the SV_TYPE_WORKSTATION and SV_TYPE_SERVER
/// PC's in the Domain</returns>
public IEnumerable<string> GetNetworkDevices()
{
//local fields
const int MAX_PREFERRED_LENGTH = -1;
var SV_TYPE_WORKSTATION = 1;
var SV_TYPE_SERVER = 2;
var buffer = IntPtr.Zero;
var tmpBuffer = IntPtr.Zero;
var entriesRead = 0;
var totalEntries = 0;
var resHandle = 0;
var sizeofINFO = Marshal.SizeOf(typeof(_SERVER_INFO_100));
try
{
//call the DllImport : NetServerEnum with all its required parameters
//see http://msdn.microsoft.com/library/default.asp?url=/library/en-us/netmgmt/netmgmt/netserverenum.asp
//for full details of method signature
var ret = NativeMethods.NetServerEnum(null, 100, ref buffer, MAX_PREFERRED_LENGTH, out entriesRead, out totalEntries, SV_TYPE_WORKSTATION | SV_TYPE_SERVER, null, out resHandle);
//if the returned with a NERR_Success (C++ term), =0 for C#
if (ret == 0)
{
//loop through all SV_TYPE_WORKSTATION and SV_TYPE_SERVER PC's
for (var i = 0; i < totalEntries; i++)
{
//get pointer to, Pointer to the buffer that received the data from
//the call to NetServerEnum. Must ensure to use correct size of
//STRUCTURE to ensure correct location in memory is pointed to
tmpBuffer = new IntPtr((int)buffer + (i * sizeofINFO));
//Have now got a pointer to the list of SV_TYPE_WORKSTATION and
//SV_TYPE_SERVER PC's, which is unmanaged memory
//Needs to Marshal data from an unmanaged block of memory to a
//managed object, again using STRUCTURE to ensure the correct data
//is marshalled
var svrInfo = (_SERVER_INFO_100)Marshal.PtrToStructure(tmpBuffer, typeof(_SERVER_INFO_100));
//add the PC names to the ArrayList
if (!string.IsNullOrEmpty(svrInfo.sv100_name))
{
yield return svrInfo.sv100_name;
}
}
}
}
finally
{
//The NetApiBufferFree function frees
//the memory that the NetApiBufferAllocate function allocates
NativeMethods.NetApiBufferFree(buffer);
}
}
/// <summary>
/// Gets the network shares.
/// </summary>
/// <param name="path">The path.</param>
/// <returns>IEnumerable{NetworkShare}.</returns>
public IEnumerable<NetworkShare> GetNetworkShares(string path)
{
return new ShareCollection(path).OfType<Share>().Select(ToNetworkShare);
}
/// <summary>
/// To the network share.
/// </summary>
/// <param name="share">The share.</param>
/// <returns>NetworkShare.</returns>
private NetworkShare ToNetworkShare(Share share)
{
return new NetworkShare
{
Name = share.NetName,
Path = share.Path,
Remark = share.Remark,
Server = share.Server,
ShareType = ToNetworkShareType(share.ShareType)
};
}
/// <summary>
/// To the type of the network share.
/// </summary>
/// <param name="shareType">Type of the share.</param>
/// <returns>NetworkShareType.</returns>
/// <exception cref="System.ArgumentException">Unknown share type</exception>
private NetworkShareType ToNetworkShareType(ShareType shareType)
{
switch (shareType)
{
case ShareType.Device:
return NetworkShareType.Device;
case ShareType.Disk :
return NetworkShareType.Disk;
case ShareType.IPC :
return NetworkShareType.Ipc;
case ShareType.Printer :
return NetworkShareType.Printer;
case ShareType.Special:
return NetworkShareType.Special;
default:
throw new ArgumentException("Unknown share type");
}
}
/// <summary>
/// Parses the specified endpointstring.
/// </summary>
/// <param name="endpointstring">The endpointstring.</param>
/// <returns>IPEndPoint.</returns>
public IPEndPoint Parse(string endpointstring)
{
return Parse(endpointstring, -1);
}
/// <summary>
/// Parses the specified endpointstring.
/// </summary>
/// <param name="endpointstring">The endpointstring.</param>
/// <param name="defaultport">The defaultport.</param>
/// <returns>IPEndPoint.</returns>
/// <exception cref="System.ArgumentException">Endpoint descriptor may not be empty.</exception>
/// <exception cref="System.FormatException"></exception>
private static IPEndPoint Parse(string endpointstring, int defaultport)
{
if (string.IsNullOrEmpty(endpointstring)
|| endpointstring.Trim().Length == 0)
{
throw new ArgumentException("Endpoint descriptor may not be empty.");
}
if (defaultport != -1 &&
(defaultport < IPEndPoint.MinPort
|| defaultport > IPEndPoint.MaxPort))
{
throw new ArgumentException(string.Format("Invalid default port '{0}'", defaultport));
}
string[] values = endpointstring.Split(new char[] { ':' });
IPAddress ipaddy;
int port = -1;
//check if we have an IPv6 or ports
if (values.Length <= 2) // ipv4 or hostname
{
if (values.Length == 1)
//no port is specified, default
port = defaultport;
else
port = GetPort(values[1]);
//try to use the address as IPv4, otherwise get hostname
if (!IPAddress.TryParse(values[0], out ipaddy))
ipaddy = GetIPfromHost(values[0]);
}
else if (values.Length > 2) //ipv6
{
//could [a:b:c]:d
if (values[0].StartsWith("[") && values[values.Length - 2].EndsWith("]"))
{
string ipaddressstring = string.Join(":", values.Take(values.Length - 1).ToArray());
ipaddy = IPAddress.Parse(ipaddressstring);
port = GetPort(values[values.Length - 1]);
}
else //[a:b:c] or a:b:c
{
ipaddy = IPAddress.Parse(endpointstring);
port = defaultport;
}
}
else
{
throw new FormatException(string.Format("Invalid endpoint ipaddress '{0}'", endpointstring));
}
if (port == -1)
throw new ArgumentException(string.Format("No port specified: '{0}'", endpointstring));
return new IPEndPoint(ipaddy, port);
}
/// <summary>
/// Gets the port.
/// </summary>
/// <param name="p">The p.</param>
/// <returns>System.Int32.</returns>
/// <exception cref="System.FormatException"></exception>
private static int GetPort(string p)
{
int port;
if (!int.TryParse(p, out port)
|| port < IPEndPoint.MinPort
|| port > IPEndPoint.MaxPort)
{
throw new FormatException(string.Format("Invalid end point port '{0}'", p));
}
return port;
}
/// <summary>
/// Gets the I pfrom host.
/// </summary>
/// <param name="p">The p.</param>
/// <returns>IPAddress.</returns>
/// <exception cref="System.ArgumentException"></exception>
private static IPAddress GetIPfromHost(string p)
{
var hosts = Dns.GetHostAddresses(p);
if (hosts == null || hosts.Length == 0)
throw new ArgumentException(string.Format("Host not found: {0}", p));
return hosts[0];
}
}
}

View File

@@ -1,638 +0,0 @@
using System;
using System.IO;
using System.Collections;
using System.Runtime.InteropServices;
namespace MediaBrowser.Networking.Management
{
/// <summary>
/// Type of share
/// </summary>
[Flags]
public enum ShareType
{
/// <summary>Disk share</summary>
Disk = 0,
/// <summary>Printer share</summary>
Printer = 1,
/// <summary>Device share</summary>
Device = 2,
/// <summary>IPC share</summary>
IPC = 3,
/// <summary>Special share</summary>
Special = -2147483648, // 0x80000000,
}
#region Share
/// <summary>
/// Information about a local share
/// </summary>
public class Share
{
#region Private data
private string _server;
private string _netName;
private string _path;
private ShareType _shareType;
private string _remark;
#endregion
#region Constructor
/// <summary>
/// Constructor
/// </summary>
/// <param name="Server"></param>
/// <param name="shi"></param>
public Share(string server, string netName, string path, ShareType shareType, string remark)
{
if (ShareType.Special == shareType && "IPC$" == netName)
{
shareType |= ShareType.IPC;
}
_server = server;
_netName = netName;
_path = path;
_shareType = shareType;
_remark = remark;
}
#endregion
#region Properties
/// <summary>
/// The name of the computer that this share belongs to
/// </summary>
public string Server
{
get { return _server; }
}
/// <summary>
/// Share name
/// </summary>
public string NetName
{
get { return _netName; }
}
/// <summary>
/// Local path
/// </summary>
public string Path
{
get { return _path; }
}
/// <summary>
/// Share type
/// </summary>
public ShareType ShareType
{
get { return _shareType; }
}
/// <summary>
/// Comment
/// </summary>
public string Remark
{
get { return _remark; }
}
/// <summary>
/// Returns true if this is a file system share
/// </summary>
public bool IsFileSystem
{
get
{
// Shared device
if (0 != (_shareType & ShareType.Device)) return false;
// IPC share
if (0 != (_shareType & ShareType.IPC)) return false;
// Shared printer
if (0 != (_shareType & ShareType.Printer)) return false;
// Standard disk share
if (0 == (_shareType & ShareType.Special)) return true;
// Special disk share (e.g. C$)
return ShareType.Special == _shareType && !string.IsNullOrEmpty(_netName);
}
}
/// <summary>
/// Get the root of a disk-based share
/// </summary>
public DirectoryInfo Root
{
get
{
if (IsFileSystem)
{
if (string.IsNullOrEmpty(_server))
if (string.IsNullOrEmpty(_path))
return new DirectoryInfo(ToString());
else
return new DirectoryInfo(_path);
return new DirectoryInfo(ToString());
}
return null;
}
}
#endregion
/// <summary>
/// Returns the path to this share
/// </summary>
/// <returns></returns>
public override string ToString()
{
if (string.IsNullOrEmpty(_server))
{
return string.Format(@"\\{0}\{1}", Environment.MachineName, _netName);
}
return string.Format(@"\\{0}\{1}", _server, _netName);
}
/// <summary>
/// Returns true if this share matches the local path
/// </summary>
/// <param name="path"></param>
/// <returns></returns>
public bool MatchesPath(string path)
{
if (!IsFileSystem) return false;
if (string.IsNullOrEmpty(path)) return true;
return path.ToLower().StartsWith(_path.ToLower());
}
}
#endregion
/// <summary>
/// A collection of shares
/// </summary>
public class ShareCollection : ReadOnlyCollectionBase
{
#region Platform
/// <summary>
/// Is this an NT platform?
/// </summary>
protected static bool IsNT
{
get { return (PlatformID.Win32NT == Environment.OSVersion.Platform); }
}
/// <summary>
/// Returns true if this is Windows 2000 or higher
/// </summary>
protected static bool IsW2KUp
{
get
{
OperatingSystem os = Environment.OSVersion;
if (PlatformID.Win32NT == os.Platform && os.Version.Major >= 5)
return true;
else
return false;
}
}
#endregion
#region Interop
#region Constants
/// <summary>Maximum path length</summary>
protected const int MAX_PATH = 260;
/// <summary>No error</summary>
protected const int NO_ERROR = 0;
/// <summary>Access denied</summary>
protected const int ERROR_ACCESS_DENIED = 5;
/// <summary>Access denied</summary>
protected const int ERROR_WRONG_LEVEL = 124;
/// <summary>More data available</summary>
protected const int ERROR_MORE_DATA = 234;
/// <summary>Not connected</summary>
protected const int ERROR_NOT_CONNECTED = 2250;
/// <summary>Level 1</summary>
protected const int UNIVERSAL_NAME_INFO_LEVEL = 1;
/// <summary>Max extries (9x)</summary>
protected const int MAX_SI50_ENTRIES = 20;
#endregion
#region Structures
/// <summary>Unc name</summary>
[StructLayout(LayoutKind.Sequential, CharSet = CharSet.Auto)]
protected struct UNIVERSAL_NAME_INFO
{
[MarshalAs(UnmanagedType.LPTStr)]
public string lpUniversalName;
}
/// <summary>Share information, NT, level 2</summary>
/// <remarks>
/// Requires admin rights to work.
/// </remarks>
[StructLayout(LayoutKind.Sequential, CharSet = CharSet.Unicode)]
protected struct SHARE_INFO_2
{
[MarshalAs(UnmanagedType.LPWStr)]
public string NetName;
public ShareType ShareType;
[MarshalAs(UnmanagedType.LPWStr)]
public string Remark;
public int Permissions;
public int MaxUsers;
public int CurrentUsers;
[MarshalAs(UnmanagedType.LPWStr)]
public string Path;
[MarshalAs(UnmanagedType.LPWStr)]
public string Password;
}
/// <summary>Share information, NT, level 1</summary>
/// <remarks>
/// Fallback when no admin rights.
/// </remarks>
[StructLayout(LayoutKind.Sequential, CharSet = CharSet.Unicode)]
protected struct SHARE_INFO_1
{
[MarshalAs(UnmanagedType.LPWStr)]
public string NetName;
public ShareType ShareType;
[MarshalAs(UnmanagedType.LPWStr)]
public string Remark;
}
/// <summary>Share information, Win9x</summary>
[StructLayout(LayoutKind.Sequential, CharSet = CharSet.Ansi, Pack = 1)]
protected struct SHARE_INFO_50
{
[MarshalAs(UnmanagedType.ByValTStr, SizeConst = 13)]
public string NetName;
public byte bShareType;
public ushort Flags;
[MarshalAs(UnmanagedType.LPTStr)]
public string Remark;
[MarshalAs(UnmanagedType.LPTStr)]
public string Path;
[MarshalAs(UnmanagedType.ByValTStr, SizeConst = 9)]
public string PasswordRW;
[MarshalAs(UnmanagedType.ByValTStr, SizeConst = 9)]
public string PasswordRO;
public ShareType ShareType
{
get { return (ShareType)((int)bShareType & 0x7F); }
}
}
/// <summary>Share information level 1, Win9x</summary>
[StructLayout(LayoutKind.Sequential, CharSet = CharSet.Ansi, Pack = 1)]
protected struct SHARE_INFO_1_9x
{
[MarshalAs(UnmanagedType.ByValTStr, SizeConst = 13)]
public string NetName;
public byte Padding;
public ushort bShareType;
[MarshalAs(UnmanagedType.LPTStr)]
public string Remark;
public ShareType ShareType
{
get { return (ShareType)((int)bShareType & 0x7FFF); }
}
}
#endregion
#region Functions
/// <summary>Get a UNC name</summary>
[DllImport("mpr", CharSet = CharSet.Auto)]
protected static extern int WNetGetUniversalName(string lpLocalPath,
int dwInfoLevel, ref UNIVERSAL_NAME_INFO lpBuffer, ref int lpBufferSize);
/// <summary>Get a UNC name</summary>
[DllImport("mpr", CharSet = CharSet.Auto)]
protected static extern int WNetGetUniversalName(string lpLocalPath,
int dwInfoLevel, IntPtr lpBuffer, ref int lpBufferSize);
/// <summary>Enumerate shares (NT)</summary>
[DllImport("netapi32", CharSet = CharSet.Unicode)]
protected static extern int NetShareEnum(string lpServerName, int dwLevel,
out IntPtr lpBuffer, int dwPrefMaxLen, out int entriesRead,
out int totalEntries, ref int hResume);
/// <summary>Enumerate shares (9x)</summary>
[DllImport("svrapi", CharSet = CharSet.Ansi)]
protected static extern int NetShareEnum(
[MarshalAs(UnmanagedType.LPTStr)] string lpServerName, int dwLevel,
IntPtr lpBuffer, ushort cbBuffer, out ushort entriesRead,
out ushort totalEntries);
/// <summary>Free the buffer (NT)</summary>
[DllImport("netapi32")]
protected static extern int NetApiBufferFree(IntPtr lpBuffer);
#endregion
#region Enumerate shares
/// <summary>
/// Enumerates the shares on Windows NT
/// </summary>
/// <param name="server">The server name</param>
/// <param name="shares">The ShareCollection</param>
protected static void EnumerateSharesNT(string server, ShareCollection shares)
{
int level = 2;
int entriesRead, totalEntries, nRet, hResume = 0;
IntPtr pBuffer = IntPtr.Zero;
try
{
nRet = NetShareEnum(server, level, out pBuffer, -1,
out entriesRead, out totalEntries, ref hResume);
if (ERROR_ACCESS_DENIED == nRet)
{
//Need admin for level 2, drop to level 1
level = 1;
nRet = NetShareEnum(server, level, out pBuffer, -1,
out entriesRead, out totalEntries, ref hResume);
}
if (NO_ERROR == nRet && entriesRead > 0)
{
Type t = (2 == level) ? typeof(SHARE_INFO_2) : typeof(SHARE_INFO_1);
int offset = Marshal.SizeOf(t);
for (int i = 0, lpItem = pBuffer.ToInt32(); i < entriesRead; i++, lpItem += offset)
{
IntPtr pItem = new IntPtr(lpItem);
if (1 == level)
{
SHARE_INFO_1 si = (SHARE_INFO_1)Marshal.PtrToStructure(pItem, t);
shares.Add(si.NetName, string.Empty, si.ShareType, si.Remark);
}
else
{
SHARE_INFO_2 si = (SHARE_INFO_2)Marshal.PtrToStructure(pItem, t);
shares.Add(si.NetName, si.Path, si.ShareType, si.Remark);
}
}
}
}
finally
{
// Clean up buffer allocated by system
if (IntPtr.Zero != pBuffer)
NetApiBufferFree(pBuffer);
}
}
/// <summary>
/// Enumerates the shares on Windows 9x
/// </summary>
/// <param name="server">The server name</param>
/// <param name="shares">The ShareCollection</param>
protected static void EnumerateShares9x(string server, ShareCollection shares)
{
int level = 50;
int nRet = 0;
ushort entriesRead, totalEntries;
Type t = typeof(SHARE_INFO_50);
int size = Marshal.SizeOf(t);
ushort cbBuffer = (ushort)(MAX_SI50_ENTRIES * size);
//On Win9x, must allocate buffer before calling API
IntPtr pBuffer = Marshal.AllocHGlobal(cbBuffer);
try
{
nRet = NetShareEnum(server, level, pBuffer, cbBuffer,
out entriesRead, out totalEntries);
if (ERROR_WRONG_LEVEL == nRet)
{
level = 1;
t = typeof(SHARE_INFO_1_9x);
size = Marshal.SizeOf(t);
nRet = NetShareEnum(server, level, pBuffer, cbBuffer,
out entriesRead, out totalEntries);
}
if (NO_ERROR == nRet || ERROR_MORE_DATA == nRet)
{
for (int i = 0, lpItem = pBuffer.ToInt32(); i < entriesRead; i++, lpItem += size)
{
IntPtr pItem = new IntPtr(lpItem);
if (1 == level)
{
SHARE_INFO_1_9x si = (SHARE_INFO_1_9x)Marshal.PtrToStructure(pItem, t);
shares.Add(si.NetName, string.Empty, si.ShareType, si.Remark);
}
else
{
SHARE_INFO_50 si = (SHARE_INFO_50)Marshal.PtrToStructure(pItem, t);
shares.Add(si.NetName, si.Path, si.ShareType, si.Remark);
}
}
}
else
Console.WriteLine(nRet);
}
finally
{
//Clean up buffer
Marshal.FreeHGlobal(pBuffer);
}
}
/// <summary>
/// Enumerates the shares
/// </summary>
/// <param name="server">The server name</param>
/// <param name="shares">The ShareCollection</param>
protected static void EnumerateShares(string server, ShareCollection shares)
{
if (null != server && 0 != server.Length && !IsW2KUp)
{
server = server.ToUpper();
// On NT4, 9x and Me, server has to start with "\\"
if (!('\\' == server[0] && '\\' == server[1]))
server = @"\\" + server;
}
if (IsNT)
EnumerateSharesNT(server, shares);
else
EnumerateShares9x(server, shares);
}
#endregion
#endregion
#region Static methods
/// <summary>
/// Returns true if fileName is a valid local file-name of the form:
/// X:\, where X is a drive letter from A-Z
/// </summary>
/// <param name="fileName">The filename to check</param>
/// <returns></returns>
public static bool IsValidFilePath(string fileName)
{
if (null == fileName || 0 == fileName.Length) return false;
char drive = char.ToUpper(fileName[0]);
if ('A' > drive || drive > 'Z')
return false;
else if (Path.VolumeSeparatorChar != fileName[1])
return false;
else if (Path.DirectorySeparatorChar != fileName[2])
return false;
else
return true;
}
#endregion
/// <summary>The name of the server this collection represents</summary>
private string _server;
#region Constructor
/// <summary>
/// Default constructor - local machine
/// </summary>
public ShareCollection()
{
_server = string.Empty;
EnumerateShares(_server, this);
}
/// <summary>
/// Constructor
/// </summary>
/// <param name="Server"></param>
public ShareCollection(string server)
{
_server = server;
EnumerateShares(_server, this);
}
#endregion
#region Add
protected void Add(Share share)
{
InnerList.Add(share);
}
protected void Add(string netName, string path, ShareType shareType, string remark)
{
InnerList.Add(new Share(_server, netName, path, shareType, remark));
}
#endregion
#region Properties
/// <summary>
/// Returns the name of the server this collection represents
/// </summary>
public string Server
{
get { return _server; }
}
/// <summary>
/// Returns the <see cref="Share"/> at the specified index.
/// </summary>
public Share this[int index]
{
get { return (Share)InnerList[index]; }
}
/// <summary>
/// Returns the <see cref="Share"/> which matches a given local path
/// </summary>
/// <param name="path">The path to match</param>
public Share this[string path]
{
get
{
if (null == path || 0 == path.Length) return null;
path = Path.GetFullPath(path);
if (!IsValidFilePath(path)) return null;
Share match = null;
for (int i = 0; i < InnerList.Count; i++)
{
Share s = (Share)InnerList[i];
if (s.IsFileSystem && s.MatchesPath(path))
{
//Store first match
if (null == match)
match = s;
// If this has a longer path,
// and this is a disk share or match is a special share,
// then this is a better match
else if (match.Path.Length < s.Path.Length)
{
if (ShareType.Disk == s.ShareType || ShareType.Disk != match.ShareType)
match = s;
}
}
}
return match;
}
}
#endregion
/// <summary>
/// Copy this collection to an array
/// </summary>
/// <param name="array"></param>
/// <param name="index"></param>
public void CopyTo(Share[] array, int index)
{
InnerList.CopyTo(array, index);
}
}
}

View File

@@ -106,18 +106,7 @@
<Compile Include="..\SharedVersion.cs">
<Link>Properties\SharedVersion.cs</Link>
</Compile>
<Compile Include="HttpManager\HttpManager.cs" />
<Compile Include="HttpServer\BaseRestService.cs" />
<Compile Include="Udp\UdpServer.cs" />
<Compile Include="WebSocket\AlchemyServer.cs" />
<Compile Include="WebSocket\AlchemyWebSocket.cs" />
<Compile Include="HttpServer\HttpServer.cs" />
<Compile Include="Management\NativeMethods.cs" />
<Compile Include="Management\NetworkManager.cs" />
<Compile Include="Management\NetworkShares.cs" />
<Compile Include="Properties\AssemblyInfo.cs" />
<Compile Include="HttpServer\ServerFactory.cs" />
<Compile Include="HttpServer\NativeWebSocket.cs" />
</ItemGroup>
<ItemGroup>
<ProjectReference Include="..\MediaBrowser.Common\MediaBrowser.Common.csproj">

View File

@@ -1,203 +0,0 @@
using MediaBrowser.Common.Net;
using MediaBrowser.Model.Logging;
using MediaBrowser.Networking.Management;
using System;
using System.Net;
using System.Net.Sockets;
using System.Reactive.Linq;
using System.Text;
using System.Threading.Tasks;
namespace MediaBrowser.Networking.Udp
{
/// <summary>
/// Provides a Udp Server
/// </summary>
public class UdpServer : IUdpServer
{
/// <summary>
/// Occurs when [message received].
/// </summary>
public event EventHandler<UdpMessageReceivedEventArgs> MessageReceived;
/// <summary>
/// Gets or sets the logger.
/// </summary>
/// <value>The logger.</value>
private ILogger Logger { get; set; }
/// <summary>
/// Initializes a new instance of the <see cref="UdpServer" /> class.
/// </summary>
/// <param name="logger">The logger.</param>
public UdpServer(ILogger logger)
{
Logger = logger;
}
/// <summary>
/// Raises the <see cref="E:MessageReceived" /> event.
/// </summary>
/// <param name="e">The <see cref="UdpMessageReceivedEventArgs" /> instance containing the event data.</param>
protected virtual void OnMessageReceived(UdpMessageReceivedEventArgs e)
{
EventHandler<UdpMessageReceivedEventArgs> handler = MessageReceived;
if (handler != null) handler(this, e);
}
/// <summary>
/// The _udp client
/// </summary>
private UdpClient _udpClient;
/// <summary>
/// Starts the specified port.
/// </summary>
/// <param name="port">The port.</param>
public void Start(int port)
{
_udpClient = new UdpClient(new IPEndPoint(IPAddress.Any, port));
_udpClient.Client.SetSocketOption(SocketOptionLevel.Socket, SocketOptionName.ReuseAddress, true);
CreateObservable().Subscribe(OnMessageReceived);
}
/// <summary>
/// Creates the observable.
/// </summary>
/// <returns>IObservable{UdpReceiveResult}.</returns>
private IObservable<UdpReceiveResult> CreateObservable()
{
return Observable.Create<UdpReceiveResult>(obs =>
Observable.FromAsync(() =>
{
try
{
return _udpClient.ReceiveAsync();
}
catch (ObjectDisposedException)
{
return Task.FromResult(new UdpReceiveResult(new byte[]{}, new IPEndPoint(IPAddress.Any, 0)));
}
catch (Exception ex)
{
Logger.ErrorException("Error receiving udp message", ex);
return Task.FromResult(new UdpReceiveResult(new byte[] { }, new IPEndPoint(IPAddress.Any, 0)));
}
})
.Subscribe(obs))
.Repeat()
.Retry()
.Publish()
.RefCount();
}
/// <summary>
/// Called when [message received].
/// </summary>
/// <param name="message">The message.</param>
private void OnMessageReceived(UdpReceiveResult message)
{
if (message.RemoteEndPoint.Port == 0)
{
return;
}
var bytes = message.Buffer;
OnMessageReceived(new UdpMessageReceivedEventArgs
{
Bytes = bytes,
RemoteEndPoint = message.RemoteEndPoint.ToString()
});
}
/// <summary>
/// Performs application-defined tasks associated with freeing, releasing, or resetting unmanaged resources.
/// </summary>
public void Dispose()
{
Dispose(true);
GC.SuppressFinalize(this);
}
/// <summary>
/// Stops this instance.
/// </summary>
public void Stop()
{
_udpClient.Close();
}
/// <summary>
/// Releases unmanaged and - optionally - managed resources.
/// </summary>
/// <param name="dispose"><c>true</c> to release both managed and unmanaged resources; <c>false</c> to release only unmanaged resources.</param>
protected virtual void Dispose(bool dispose)
{
if (dispose)
{
Stop();
}
}
/// <summary>
/// Sends the async.
/// </summary>
/// <param name="data">The data.</param>
/// <param name="ipAddress">The ip address.</param>
/// <param name="port">The port.</param>
/// <returns>Task{System.Int32}.</returns>
/// <exception cref="System.ArgumentNullException">data</exception>
public Task SendAsync(string data, string ipAddress, int port)
{
return SendAsync(Encoding.UTF8.GetBytes(data), ipAddress, port);
}
/// <summary>
/// Sends the async.
/// </summary>
/// <param name="bytes">The bytes.</param>
/// <param name="ipAddress">The ip address.</param>
/// <param name="port">The port.</param>
/// <returns>Task{System.Int32}.</returns>
/// <exception cref="System.ArgumentNullException">bytes</exception>
public Task SendAsync(byte[] bytes, string ipAddress, int port)
{
if (bytes == null)
{
throw new ArgumentNullException("bytes");
}
if (string.IsNullOrEmpty(ipAddress))
{
throw new ArgumentNullException("ipAddress");
}
return _udpClient.SendAsync(bytes, bytes.Length, ipAddress, port);
}
/// <summary>
/// Sends the async.
/// </summary>
/// <param name="bytes">The bytes.</param>
/// <param name="remoteEndPoint">The remote end point.</param>
/// <returns>Task.</returns>
public Task SendAsync(byte[] bytes, string remoteEndPoint)
{
if (bytes == null)
{
throw new ArgumentNullException("bytes");
}
if (string.IsNullOrEmpty(remoteEndPoint))
{
throw new ArgumentNullException("remoteEndPoint");
}
return _udpClient.SendAsync(bytes, bytes.Length, new NetworkManager().Parse(remoteEndPoint));
}
}
}

View File

@@ -1,113 +0,0 @@
using Alchemy;
using Alchemy.Classes;
using MediaBrowser.Common.Net;
using MediaBrowser.Model.Logging;
using System;
using System.Net;
namespace MediaBrowser.Networking.WebSocket
{
/// <summary>
/// Class AlchemyServer
/// </summary>
public class AlchemyServer : IWebSocketServer
{
/// <summary>
/// Occurs when [web socket connected].
/// </summary>
public event EventHandler<WebSocketConnectEventArgs> WebSocketConnected;
/// <summary>
/// Gets or sets the web socket server.
/// </summary>
/// <value>The web socket server.</value>
private WebSocketServer WebSocketServer { get; set; }
/// <summary>
/// The _logger
/// </summary>
private readonly ILogger _logger;
/// <summary>
/// Initializes a new instance of the <see cref="AlchemyServer" /> class.
/// </summary>
/// <param name="logger">The logger.</param>
/// <exception cref="System.ArgumentNullException">logger</exception>
public AlchemyServer(ILogger logger)
{
if (logger == null)
{
throw new ArgumentNullException("logger");
}
_logger = logger;
}
/// <summary>
/// Gets the port.
/// </summary>
/// <value>The port.</value>
public int Port { get; private set; }
/// <summary>
/// Starts the specified port number.
/// </summary>
/// <param name="portNumber">The port number.</param>
public void Start(int portNumber)
{
WebSocketServer = new WebSocketServer(portNumber, IPAddress.Any)
{
OnConnected = OnAlchemyWebSocketClientConnected,
TimeOut = TimeSpan.FromMinutes(60)
};
WebSocketServer.Start();
Port = portNumber;
_logger.Info("Alchemy Web Socket Server started");
}
/// <summary>
/// Called when [alchemy web socket client connected].
/// </summary>
/// <param name="context">The context.</param>
private void OnAlchemyWebSocketClientConnected(UserContext context)
{
if (WebSocketConnected != null)
{
var socket = new AlchemyWebSocket(context, _logger);
WebSocketConnected(this, new WebSocketConnectEventArgs
{
WebSocket = socket,
Endpoint = context.ClientAddress.ToString()
});
}
}
/// <summary>
/// Stops this instance.
/// </summary>
public void Stop()
{
}
/// <summary>
/// Performs application-defined tasks associated with freeing, releasing, or resetting unmanaged resources.
/// </summary>
public void Dispose()
{
Dispose(true);
GC.SuppressFinalize(this);
}
/// <summary>
/// Releases unmanaged and - optionally - managed resources.
/// </summary>
/// <param name="dispose"><c>true</c> to release both managed and unmanaged resources; <c>false</c> to release only unmanaged resources.</param>
protected virtual void Dispose(bool dispose)
{
}
}
}

View File

@@ -1,132 +0,0 @@
using Alchemy.Classes;
using MediaBrowser.Common.Net;
using MediaBrowser.Model.Logging;
using System;
using System.Text;
using System.Threading;
using System.Threading.Tasks;
namespace MediaBrowser.Networking.WebSocket
{
/// <summary>
/// Class AlchemyWebSocket
/// </summary>
public class AlchemyWebSocket : IWebSocket
{
/// <summary>
/// The logger
/// </summary>
private readonly ILogger _logger;
/// <summary>
/// Gets or sets the web socket.
/// </summary>
/// <value>The web socket.</value>
private UserContext UserContext { get; set; }
/// <summary>
/// Initializes a new instance of the <see cref="AlchemyWebSocket" /> class.
/// </summary>
/// <param name="context">The context.</param>
/// <param name="logger">The logger.</param>
/// <exception cref="System.ArgumentNullException">context</exception>
public AlchemyWebSocket(UserContext context, ILogger logger)
{
if (context == null)
{
throw new ArgumentNullException("context");
}
_logger = logger;
UserContext = context;
context.SetOnDisconnect(OnDisconnected);
context.SetOnReceive(OnReceive);
_logger.Info("Client connected from {0}", context.ClientAddress);
}
/// <summary>
/// The _disconnected
/// </summary>
private bool _disconnected = false;
/// <summary>
/// Gets or sets the state.
/// </summary>
/// <value>The state.</value>
public WebSocketState State
{
get { return _disconnected ? WebSocketState.Closed : WebSocketState.Open; }
}
/// <summary>
/// Called when [disconnected].
/// </summary>
/// <param name="context">The context.</param>
private void OnDisconnected(UserContext context)
{
_disconnected = true;
}
/// <summary>
/// Called when [receive].
/// </summary>
/// <param name="context">The context.</param>
private void OnReceive(UserContext context)
{
if (OnReceiveDelegate != null)
{
var json = context.DataFrame.ToString();
if (!string.IsNullOrWhiteSpace(json))
{
try
{
var bytes = Encoding.UTF8.GetBytes(json);
OnReceiveDelegate(bytes);
}
catch (Exception ex)
{
_logger.ErrorException("Error processing web socket message", ex);
}
}
}
}
/// <summary>
/// Sends the async.
/// </summary>
/// <param name="bytes">The bytes.</param>
/// <param name="type">The type.</param>
/// <param name="endOfMessage">if set to <c>true</c> [end of message].</param>
/// <param name="cancellationToken">The cancellation token.</param>
/// <returns>Task.</returns>
public Task SendAsync(byte[] bytes, WebSocketMessageType type, bool endOfMessage, CancellationToken cancellationToken)
{
return Task.Run(() => UserContext.Send(bytes));
}
/// <summary>
/// Performs application-defined tasks associated with freeing, releasing, or resetting unmanaged resources.
/// </summary>
public void Dispose()
{
Dispose(true);
}
/// <summary>
/// Releases unmanaged and - optionally - managed resources.
/// </summary>
/// <param name="dispose"><c>true</c> to release both managed and unmanaged resources; <c>false</c> to release only unmanaged resources.</param>
protected virtual void Dispose(bool dispose)
{
}
/// <summary>
/// Gets or sets the receive action.
/// </summary>
/// <value>The receive action.</value>
public Action<byte[]> OnReceiveDelegate { get; set; }
}
}