Radarr/CassiniDev/CassiniDevServerOP.cs

242 lines
8.2 KiB
C#

// /* **********************************************************************************
// *
// * Copyright (c) Sky Sanders. All rights reserved.
// *
// * This source code is subject to terms and conditions of the Microsoft Public
// * License (Ms-PL). A copy of the license can be found in the license.htm file
// * included in this distribution.
// *
// * You must not remove this notice, or any other, from this software.
// *
// * **********************************************************************************/
using System;
using System.Diagnostics;
using System.IO;
using System.Net;
using System.Threading;
namespace CassiniDev
{
/// <summary>
/// Made a go at spinning the server up from this process but after dealing with
/// irratic behaviour regarding apartment state, platform concerns, unloaded app domains,
/// and all the other issues that you can find that people struggle with I just decided
/// to strictly format the console app's output and just spin up an external process.
/// Seems robust so far.
/// </summary>
public class CassiniDevServerOP
{
//private bool _disposed;
private string _hostname;
private StreamWriter _input;
private IPAddress _ipAddress;
private Thread _outputThread;
private string _rootUrl;
private Process _serverProcess;
private const int TimeOut = 60000;
private const int WaitForPort = 5000;
/// <summary>
/// </summary>
public void Dispose()
{
{
if (_serverProcess != null)
{
StopServer();
}
}
}
/// <summary>
/// The root URL of the running web application
/// </summary>
public string RootUrl
{
get { return _rootUrl; }
}
/// <summary>
/// Combine the RootUrl of the running web application with the relative url specified.
/// </summary>
/// <param name="relativeUrl"></param>
/// <returns></returns>
public string NormalizeUrl(string relativeUrl)
{
return CassiniNetworkUtils.NormalizeUrl(RootUrl, relativeUrl);
}
/// <summary>
/// Will start specified application as "localhost" on loopback and first available port in the range 8000-10000 with vpath "/"
/// </summary>
/// <param name="applicationPath">Physical path to application.</param>
public void StartServer(string applicationPath)
{
StartServer(applicationPath, CassiniNetworkUtils.GetAvailablePort(8000, 10000, IPAddress.Loopback, true), "/", "localhost");
}
/// <summary>
/// Will start specified application on loopback
/// </summary>
/// <param name="applicationPath">Physical path to application.</param>
/// <param name="port">Port to listen on.</param>
/// <param name="virtualPath">Optional. defaults to "/"</param>
/// <param name="hostName">Optional. Is used to construct RootUrl. Defaults to "localhost"</param>
public void StartServer(string applicationPath, int port, string virtualPath, string hostName)
{
// WebHost.Server will not run on any other IP
IPAddress ipAddress = IPAddress.Loopback;
if (!CassiniNetworkUtils.IsPortAvailable(ipAddress, port))
{
throw new Exception(string.Format("Port {0} is in use.", port));
}
applicationPath = Path.GetFullPath(applicationPath);
virtualPath = String.Format("/{0}/", (virtualPath ?? string.Empty).Trim('/')).Replace("//", "/");
hostName = string.IsNullOrEmpty(hostName) ? "localhost" : hostName;
StartServer(applicationPath, ipAddress, port, virtualPath, hostName);
}
/// <summary>
/// </summary>
/// <param name="applicationPath">Physical path to application.</param>
/// <param name="ipAddress">IP to listen on.</param>
/// <param name="port">Port to listen on.</param>
/// <param name="virtualPath">Optional. default value '/'</param>
/// <param name="hostName">Optional. Used to construct RootUrl. Defaults to 'localhost'</param>
public virtual void StartServer(string applicationPath, IPAddress ipAddress, int port, string virtualPath, string hostName)
{
_hostname = hostName;
_ipAddress = ipAddress;
// massage and validate arguments
if (string.IsNullOrEmpty(virtualPath))
{
virtualPath = "/";
}
if (!virtualPath.StartsWith("/"))
{
virtualPath = "/" + virtualPath;
}
if (_serverProcess != null)
{
throw new InvalidOperationException("Server is running");
}
string commandLine = (new CommandLineArguments
{
Port = port,
ApplicationPath = string.Format("\"{0}\"", Path.GetFullPath(applicationPath).Trim('\"').TrimEnd('\\')),
HostName = hostName,
IPAddress = ipAddress.ToString(),
VirtualPath = string.Format("\"{0}\"", virtualPath),
TimeOut = TimeOut,
WaitForPort = WaitForPort,
IPMode = IPMode.Specific,
PortMode = PortMode.Specific
}).ToString();
_serverProcess = new Process
{
StartInfo = new ProcessStartInfo
{
UseShellExecute = false,
ErrorDialog = false,
CreateNoWindow = true,
RedirectStandardOutput = true,
RedirectStandardInput = true,
#if NET40 //TODO: find out the real flag
FileName = "CassiniDev4-console.exe",
#else
FileName = "CassiniDev-console.exe",
#endif
Arguments = commandLine,
WorkingDirectory = Environment.CurrentDirectory
}
};
// we are going to monitor each line of the output until we get a start or error signal
// and then just ignore the rest
string line = null;
_serverProcess.Start();
_outputThread = new Thread(() =>
{
string l = _serverProcess.StandardOutput.ReadLine();
while (l != null)
{
if (l.StartsWith("started:") || l.StartsWith("error:"))
{
line = l;
}
l = _serverProcess.StandardOutput.ReadLine();
}
});
_outputThread.Start();
// use StandardInput to send the newline to stop the server when required
_input = _serverProcess.StandardInput;
// block until we get a signal
while (line == null)
{
Thread.Sleep(10);
}
if (!line.StartsWith("started:"))
{
throw new Exception(string.Format("Could not start server: {0}", line));
}
// line is the root url
_rootUrl = line.Substring(line.IndexOf(':') + 1);
}
/// <summary>
/// <para>Stops the server, if running.</para>
/// </summary>
public virtual void StopServer()
{
StopServer(100);
}
/// <summary>
/// <para>Stops the server, if running.</para>
/// </summary>
protected virtual void StopServer(int delay)
{
Thread.Sleep(delay);
if (_serverProcess != null)
{
try
{
_input.WriteLine();
_serverProcess.WaitForExit(10000);
Thread.Sleep(10);
}
catch
{
}
finally
{
_serverProcess.Dispose();
_serverProcess = null;
}
}
}
}
}