Heavy instancing work

Added InstanceRootCommand for blocking instance based commands if no
instance is chosen
CLI now prints the service version
Added service level CLI commands
Fixed Service.LockLoggingID
Added InstanceMetadata DataContract
Implemented Service.ListInstances
ExitCode is now set to int parent type
Fixed ConnectivityLevel flags
Optimized channel closing during instance switching
This commit is contained in:
Cyberboss
2017-10-31 16:55:01 -04:00
parent e6f44a10b3
commit 8db5ecb26d
18 changed files with 607 additions and 45 deletions
+1 -1
View File
@@ -4,7 +4,7 @@ using TGServiceInterface.Components;
namespace TGCommandLine
{
class AdminCommand : RootCommand
class AdminCommand : InstanceRootCommand
{
public AdminCommand()
{
+1 -1
View File
@@ -6,7 +6,7 @@ using TGServiceInterface.Components;
namespace TGCommandLine
{
class BYONDCommand : RootCommand
class BYONDCommand : InstanceRootCommand
{
public BYONDCommand()
{
+2 -2
View File
@@ -5,7 +5,7 @@ using TGServiceInterface.Components;
namespace TGCommandLine
{
class IRCCommand : RootCommand
class IRCCommand : InstanceRootCommand
{
public IRCCommand()
{
@@ -17,7 +17,7 @@ namespace TGCommandLine
return "Manages the IRC bot";
}
}
class DiscordCommand : RootCommand
class DiscordCommand : InstanceRootCommand
{
public DiscordCommand()
{
+1 -1
View File
@@ -6,7 +6,7 @@ using TGServiceInterface.Components;
namespace TGCommandLine
{
class ConfigCommand : RootCommand
class ConfigCommand : InstanceRootCommand
{
public ConfigCommand()
{
+1 -1
View File
@@ -5,7 +5,7 @@ using TGServiceInterface.Components;
namespace TGCommandLine
{
class DDCommand : RootCommand
class DDCommand : InstanceRootCommand
{
public DDCommand()
{
+1 -1
View File
@@ -6,7 +6,7 @@ using TGServiceInterface.Components;
namespace TGCommandLine
{
class DMCommand : RootCommand
class DMCommand : InstanceRootCommand
{
public DMCommand()
{
+33
View File
@@ -0,0 +1,33 @@
using System.Collections.Generic;
using TGServiceInterface;
namespace TGCommandLine
{
abstract class InstanceRootCommand : RootCommand
{
public static Interface currentInterface;
public override ExitCode DoRun(IList<string> parameters)
{
if (currentInterface.InstanceName == null)
{
OutputProc("Missing instance!");
return ExitCode.BadCommand;
}
else
{
var res = currentInterface.ConnectToInstance();
if (!res.HasFlag(ConnectivityLevel.Connected))
{
OutputProc("Unable to connect to instance!");
return ExitCode.ConnectionError;
}
else if (!res.HasFlag(ConnectivityLevel.Authenticated))
{
OutputProc("The current user is not authorized to use this instance!");
return ExitCode.ConnectionError;
}
}
return base.DoRun(parameters);
}
}
}
+50 -5
View File
@@ -9,7 +9,7 @@ namespace TGCommandLine
class Program
{
static bool interactive = false;
static bool interactive = false, saidSrvVersion = false;
static Interface currentInterface;
static Command.ExitCode RunCommandLine(IList<string> argsAsList)
{
@@ -83,6 +83,11 @@ namespace TGCommandLine
SentVMMWarning = true;
Console.WriteLine(error);
}
else if (interactive && !saidSrvVersion)
{
Console.WriteLine("Connectd to service version: " + currentInterface.GetService().Version());
saidSrvVersion = true;
}
try
{
@@ -97,7 +102,6 @@ namespace TGCommandLine
static void ReplaceInterface(Interface I)
{
if (!interactive)
I.SetBadCertificateHandler((message) =>
{
@@ -110,6 +114,8 @@ namespace TGCommandLine
I.SetBadCertificateHandler(BadCertificateInteractive);
currentInterface = I;
ConsoleCommand.Interface = I;
InstanceRootCommand.currentInterface = I;
saidSrvVersion = false;
}
public static string ReadLineSecure()
@@ -156,21 +162,60 @@ namespace TGCommandLine
return false;
}
/// <summary>
/// Tries to set <see cref="currentInterface"/>'s <see cref="ITGInstance"/> to <paramref name="instanceName"/>, outputting appropriate messages
/// </summary>
/// <param name="instanceName">The name of the <see cref="ITGInstance"/> to test</param>
/// <param name="silentSuccess">If <see langword="true"/>, does not output on success</param>
/// <returns><see langword="true"/> if a <see cref="ConnectivityLevel.Authenticated"/> was achieved with <see cref="Interface.ConnectToInstance(string)"/>, <see langword="false"/> otherwise</returns>
static bool CheckInstanceConnectivity(string instanceName, bool silentSuccess)
{
var res = currentInterface.ConnectToInstance(instanceName);
if (!res.HasFlag(ConnectivityLevel.Connected))
Console.WriteLine("Unable to connect to instance! Does it exist?");
else if (!res.HasFlag(ConnectivityLevel.Authenticated))
Console.WriteLine("The current user is not authorized to use this instance!");
else
{
if(!silentSuccess)
Console.WriteLine("Successfully conected to instance!");
return true;
}
return false;
}
static int Main(string[] args)
{
ReplaceInterface(new Interface());
Command.OutputProcVar.Value = Console.WriteLine;
if (args.Length != 0)
return (int)RunCommandLine(new List<string>(args));
Console.WriteLine("Type 'remote' to connect to a remote service");
{
var argsAsList = new List<string>(args);
for (var I = 0; I < argsAsList.Count - 1; ++I)
if (argsAsList[I].ToLower() == "--instance")
{
if (!CheckInstanceConnectivity(args[I + 1], true))
return (int)Command.ExitCode.ConnectionError;
argsAsList.RemoveRange(I, 2);
break;
}
return (int)RunCommandLine(argsAsList);
}
//interactive mode
Console.WriteLine("Type 'instance' to connect to a server instance");
Console.WriteLine("Type 'remote' to connect to a remote service");
while (true)
{
Console.Write("Enter command: ");
var NextCommand = Console.ReadLine();
switch (NextCommand.ToLower())
{
case "instance":
Console.Write("Enter instance name: ");
CheckInstanceConnectivity(Console.ReadLine(), false);
break;
case "remote":
SentVMMWarning = false;
Console.Write("Enter server address: ");
+2
View File
@@ -12,6 +12,8 @@ namespace TGCommandLine
var tmp = new List<Command> { new UpdateCommand(), new TestmergeCommand(), new RepoCommand(), new BYONDCommand(), new DMCommand(), new DDCommand(), new ConfigCommand(), new IRCCommand(), new DiscordCommand(), new AutoUpdateCommand(), new SetAutoUpdateCommand() };
if (I.ConnectToInstance().HasFlag(ConnectivityLevel.Administrator))
tmp.Add(new AdminCommand());
if (I.ConnectionStatus().HasFlag(ConnectivityLevel.Administrator))
tmp.Add(new ServiceCommand());
Children = tmp.ToArray();
}
+433
View File
@@ -0,0 +1,433 @@
using System;
using System.Collections.Generic;
using TGServiceInterface;
namespace TGCommandLine
{
/// <summary>
/// Used for managing the <see cref="TGServiceInterface.Components.ITGSService"/>
/// </summary>
class ServiceCommand : RootCommand
{
/// <summary>
/// Construct a <see cref="ServiceCommand"/>
/// </summary>
public ServiceCommand()
{
Keyword = "service";
Children = new Command[] { new ServiceCreateInstanceCommand(), new ServiceDetachInstanceCommand(), new ServiceEnableInstanceCommand(), new ServiceImportInstanceCommand(), new ServiceListInstancesCommand(), new ServicePythonPathCommand(), new ServiceSetPythonPathCommand(), new ServiceSetRemoteAccessPortCommand(), new ServiceRemoteAccessPortCommand(), new ServiceDisableInstanceCommand(), new ServiceRenameInstanceCommand() };
}
/// <inheritdoc />
public override string GetHelpText()
{
return "Manage service wide settings";
}
}
/// <summary>
/// Command for calling <see cref="TGServiceInterface.Components.ITGSService.CreateInstance(string, string)"/>
/// </summary>
class ServiceCreateInstanceCommand : ConsoleCommand
{
/// <summary>
/// Construct a <see cref="ServiceCreateInstanceCommand"/>
/// </summary>
public ServiceCreateInstanceCommand()
{
Keyword = "create-instance";
RequiredParameters = 2;
}
/// <inheritdoc />
public override string GetHelpText()
{
return "Creates a new instance at the given path";
}
/// <inheritdoc />
public override string GetArgumentString()
{
return "<name> <path>";
}
/// <inheritdoc />
protected override ExitCode Run(IList<string> parameters)
{
var res = Interface.GetService().CreateInstance(parameters[0], parameters[1]);
if (res != null)
{
OutputProc(res);
return ExitCode.ServerError;
}
return ExitCode.Normal;
}
}
/// <summary>
/// Command for calling <see cref="TGServiceInterface.Components.ITGSService.ListInstances"/>
/// </summary>
class ServiceListInstancesCommand : ConsoleCommand
{
/// <summary>
/// Construct a <see cref="ServiceListInstancesCommand"/>
/// </summary>
public ServiceListInstancesCommand()
{
Keyword = "list-instances";
}
/// <inheritdoc />
public override string GetHelpText()
{
return "Lists all instances";
}
/// <inheritdoc />
protected override ExitCode Run(IList<string> parameters)
{
foreach (var I in Interface.GetService().ListInstances())
OutputProc(String.Format("{0} ({1}):\t{2}{3}", I.Name, I.Path, I.Enabled ? "Online" : "Offline", I.Enabled ? String.Format(" ({0})", I.LoggingID) : ""));
return ExitCode.Normal;
}
}
/// <summary>
/// Command for calling <see cref="TGServiceInterface.Components.ITGSService.DetachInstance(string)"/>
/// </summary>
class ServiceDetachInstanceCommand : ConsoleCommand
{
/// <summary>
/// Construct a <see cref="ServiceDetachInstanceCommand"/>
/// </summary>
public ServiceDetachInstanceCommand()
{
Keyword = "detach-instance";
RequiredParameters = 1;
}
/// <inheritdoc />
public override string GetHelpText()
{
return "Detaches an instance";
}
/// <inheritdoc />
public override string GetArgumentString()
{
return "<name>";
}
/// <inheritdoc />
protected override ExitCode Run(IList<string> parameters)
{
var res = Interface.GetService().DetachInstance(parameters[0]);
if (res != null)
{
OutputProc(res);
return ExitCode.ServerError;
}
return ExitCode.Normal;
}
}
/// <summary>
/// Command for calling <see cref="TGServiceInterface.Components.ITGSService.ImportInstance(string)"/>
/// </summary>
class ServiceImportInstanceCommand : ConsoleCommand
{
/// <summary>
/// Construct a <see cref="ServiceImportInstanceCommand"/>
/// </summary>
public ServiceImportInstanceCommand()
{
Keyword = "import-instance";
RequiredParameters = 1;
}
/// <inheritdoc />
public override string GetHelpText()
{
return "Imports an instance (Chat settings will be lost if they are from a different windows installation)";
}
/// <inheritdoc />
public override string GetArgumentString()
{
return "<path>";
}
/// <inheritdoc />
protected override ExitCode Run(IList<string> parameters)
{
var res = Interface.GetService().ImportInstance(parameters[0]);
if (res != null)
{
OutputProc(res);
return ExitCode.ServerError;
}
return ExitCode.Normal;
}
}
/// <summary>
/// Command for calling <see cref="TGServiceInterface.Components.ITGSService.PythonPath"/>
/// </summary>
class ServicePythonPathCommand : ConsoleCommand
{
/// <summary>
/// Construct a <see cref="ServicePythonPathCommand"/>
/// </summary>
public ServicePythonPathCommand()
{
Keyword = "python";
}
/// <inheritdoc />
public override string GetHelpText()
{
return "Displays configured path the service uses for python";
}
/// <inheritdoc />
protected override ExitCode Run(IList<string> parameters)
{
var res = Interface.GetService().PythonPath();
if (res != null)
{
OutputProc(res);
return ExitCode.ServerError;
}
return ExitCode.Normal;
}
}
/// <summary>
/// Command for calling <see cref="TGServiceInterface.Components.ITGSService.SetPythonPath(string)"/>
/// </summary>
class ServiceSetPythonPathCommand : ConsoleCommand
{
/// <summary>
/// Construct a <see cref="ServiceSetPythonPathCommand"/>
/// </summary>
public ServiceSetPythonPathCommand()
{
Keyword = "set-python";
RequiredParameters = 1;
}
/// <inheritdoc />
public override string GetHelpText()
{
return "Sets the path to the python installation";
}
/// <inheritdoc />
public override string GetArgumentString()
{
return "<path>";
}
/// <inheritdoc />
protected override ExitCode Run(IList<string> parameters)
{
Interface.GetService().SetPythonPath(parameters[0]);
return ExitCode.Normal;
}
}
/// <summary>
/// Command for calling <see cref="TGServiceInterface.Components.ITGSService.SetInstanceEnabled(string, bool)"/> with a <see langword="true"/> parameter
/// </summary>
class ServiceEnableInstanceCommand : ConsoleCommand
{
/// <summary>
/// Construct a <see cref="ServiceEnableInstanceCommand"/>
/// </summary>
public ServiceEnableInstanceCommand()
{
Keyword = "enable-instance";
RequiredParameters = 1;
}
/// <inheritdoc />
public override string GetHelpText()
{
return "Enables the specified instance";
}
/// <inheritdoc />
public override string GetArgumentString()
{
return "<name>";
}
/// <inheritdoc />
protected override ExitCode Run(IList<string> parameters)
{
var res = Interface.GetService().SetInstanceEnabled(parameters[0], true);
if (res != null)
{
OutputProc(res);
return ExitCode.ServerError;
}
return ExitCode.Normal;
}
}
/// <summary>
/// Command for calling <see cref="TGServiceInterface.Components.ITGSService.SetInstanceEnabled(string, bool)"/> with a <see langword="false"/> parameter
/// </summary>
class ServiceDisableInstanceCommand : ConsoleCommand
{
/// <summary>
/// Construct a <see cref="ServiceDisableInstanceCommand"/>
/// </summary>
public ServiceDisableInstanceCommand()
{
Keyword = "disable-instance";
RequiredParameters = 1;
}
/// <inheritdoc />
public override string GetHelpText()
{
return "Disables the specified instance";
}
/// <inheritdoc />
public override string GetArgumentString()
{
return "<name>";
}
/// <inheritdoc />
protected override ExitCode Run(IList<string> parameters)
{
var res = Interface.GetService().SetInstanceEnabled(parameters[0], false);
if (res != null)
{
OutputProc(res);
return ExitCode.ServerError;
}
return ExitCode.Normal;
}
}
/// <summary>
/// Command for calling <see cref="TGServiceInterface.Components.ITGSService.RemoteAccessPort"/>
/// </summary>
class ServiceRemoteAccessPortCommand : ConsoleCommand
{
/// <summary>
/// Construct a <see cref="ServiceRemoteAccessPortCommand"/>
/// </summary>
public ServiceRemoteAccessPortCommand()
{
Keyword = "port";
}
/// <inheritdoc />
public override string GetHelpText()
{
return "Displays the service's remote access port";
}
/// <inheritdoc />
protected override ExitCode Run(IList<string> parameters)
{
OutputProc(Interface.GetService().RemoteAccessPort().ToString());
return ExitCode.Normal;
}
}
/// <summary>
/// Command for calling <see cref="TGServiceInterface.Components.ITGSService.SetRemoteAccessPort(ushort)"/>
/// </summary>
class ServiceSetRemoteAccessPortCommand : ConsoleCommand
{
/// <summary>
/// Construct a <see cref="ServiceSetRemoteAccessPortCommand"/>
/// </summary>
public ServiceSetRemoteAccessPortCommand()
{
Keyword = "set-port";
RequiredParameters = 1;
}
/// <inheritdoc />
public override string GetHelpText()
{
return "Sets the service's remote access port";
}
/// <inheritdoc />
public override string GetArgumentString()
{
return "<port>";
}
/// <inheritdoc />
protected override ExitCode Run(IList<string> parameters)
{
ushort port;
try
{
port = Convert.ToUInt16(parameters[0]);
}
catch
{
OutputProc("Invalid port number!");
return ExitCode.BadCommand;
}
var res = Interface.GetService().SetRemoteAccessPort(port);
if (res != null)
{
OutputProc(res);
return ExitCode.ServerError;
}
OutputProc("Change will be applied after service restart");
return ExitCode.Normal;
}
}
/// <summary>
/// Command for calling <see cref="TGServiceInterface.Components.ITGSService.RenameInstance(string, string)"/>
/// </summary>
class ServiceRenameInstanceCommand : ConsoleCommand
{
/// <summary>
/// Construct a <see cref="ServiceRenameInstanceCommand"/>
/// </summary>
public ServiceRenameInstanceCommand()
{
Keyword = "rename-instance";
RequiredParameters = 1;
}
/// <inheritdoc />
public override string GetHelpText()
{
return "Renames an instance. Will temporarily disable the instance if it is active";
}
/// <inheritdoc />
public override string GetArgumentString()
{
return "<name> <new_name>";
}
/// <inheritdoc />
protected override ExitCode Run(IList<string> parameters)
{
var res = Interface.GetService().RenameInstance(parameters[0], parameters[1]);
if (res != null)
{
OutputProc(res);
return ExitCode.ServerError;
}
return ExitCode.Normal;
}
}
}
+2
View File
@@ -48,11 +48,13 @@
<Compile Include="DDCommands.cs" />
<Compile Include="DMCommands.cs" />
<Compile Include="ChatCommands.cs" />
<Compile Include="InstanceRootCommand.cs" />
<Compile Include="Program.cs" />
<Compile Include="Properties\AssemblyInfo.cs" />
<Compile Include="RepoCommands.cs" />
<Compile Include="RootCommands.cs" />
<Compile Include="..\Version.cs" />
<Compile Include="ServiceCommands.cs" />
</ItemGroup>
<ItemGroup>
<None Include="App.config" />
+17 -3
View File
@@ -265,6 +265,7 @@ namespace TGServerService
for (byte I = 1; I < 100; ++I)
if (!UsedLoggingIDs.Contains(I))
{
UsedLoggingIDs.Add(I);
return I;
}
}
@@ -407,9 +408,21 @@ namespace TGServerService
}
/// <inheritdoc />
public IDictionary<string, string> ListInstances()
public IList<InstanceMetadata> ListInstances()
{
throw new NotImplementedException();
var result = new List<InstanceMetadata>();
lock (this)
foreach (var I in Properties.Settings.Default.InstancePaths) {
var ic = InstanceConfig.Load(I);
result.Add(new InstanceMetadata
{
Name = ic.Name,
Path = I,
Enabled = ic.Enabled,
LoggingID = (byte)(ic.Enabled ? ((ServerInstance)hosts[ic.Name].SingletonInstance).LoggingID : 0)
});
}
return result;
}
/// <inheritdoc />
@@ -428,6 +441,7 @@ namespace TGServerService
try
{
var ic = new InstanceConfig(path);
ic.Name = Name;
Directory.CreateDirectory(path);
ic.Save();
Properties.Settings.Default.InstancePaths.Add(path);
@@ -593,7 +607,7 @@ namespace TGServerService
finally
{
if (ie)
result = SetInstanceEnabled(new_name, true) + " "+ result;
result = SetInstanceEnabled(new_name, true) + " " + result;
}
return result;
}
+1 -1
View File
@@ -12,7 +12,7 @@ namespace TGServiceInterface
/// <summary>
/// Exit codes for <see cref="Command"/>s
/// </summary>
public enum ExitCode
public enum ExitCode : int
{
/// <summary>
/// The <see cref="Command"/> ran successfully
+1 -1
View File
@@ -44,7 +44,7 @@ namespace TGServiceInterface.Components
/// </summary>
/// <returns>A <see cref="IDictionary{TKey, TValue}"/> of instance names relating to their paths</returns>
[OperationContract]
IDictionary<string, string> ListInstances();
IList<InstanceMetadata> ListInstances();
/// <summary>
/// Creates a new server instance
+2 -2
View File
@@ -19,11 +19,11 @@ namespace TGServiceInterface
/// <summary>
/// The connected user is authenticated
/// </summary>
Authenticated = 2,
Authenticated = 2 | Connected,
/// <summary>
/// The connected user is an administrator
/// </summary>
Administrator = 4,
Administrator = 4 | Authenticated,
}
/// <summary>
+32
View File
@@ -0,0 +1,32 @@
using System.Runtime.Serialization;
namespace TGServiceInterface
{
/// <summary>
/// Metadata about an <see cref="Components.ITGInstance"/>
/// </summary>
[DataContract]
public sealed class InstanceMetadata
{
/// <summary>
/// The name of the <see cref="Components.ITGInstance"/>
/// </summary>
[DataMember]
public string Name { get; set; }
/// <summary>
/// The path of the <see cref="Components.ITGInstance"/>
/// </summary>
[DataMember]
public string Path { get; set; }
/// <summary>
/// Whether or not the <see cref="Components.ITGInstance"/> is enabled
/// </summary>
[DataMember]
public bool Enabled { get; set; }
/// <summary>
/// The logging ID of the <see cref="Components.ITGInstance"/>. Will be 0 if <see cref="Enabled"/> is <see langword="false"/>
/// </summary>
[DataMember]
public byte LoggingID { get; set; }
}
}
+26 -26
View File
@@ -71,9 +71,9 @@ namespace TGServiceInterface
readonly string HTTPSPassword;
/// <summary>
/// Associated list of open <see cref="ChannelFactory"/>s keyed by <see langword="interface"/> type. A <see cref="ChannelFactory"/> in this list may close or fault at any time. Must be locked before being accessed
/// Associated list of open <see cref="ChannelFactory"/>s keyed by <see langword="interface"/> type name. A <see cref="ChannelFactory"/> in this list may close or fault at any time. Must be locked before being accessed
/// </summary>
IDictionary<Type, ChannelFactory> ChannelFactoryCache = new Dictionary<Type, ChannelFactory>();
IDictionary<string, ChannelFactory> ChannelFactoryCache = new Dictionary<string, ChannelFactory>();
/// <summary>
/// Returns a <see cref="IList{T}"/> of <see langword="interface"/> <see cref="Type"/>s that can be used with the service
@@ -136,7 +136,7 @@ namespace TGServiceInterface
return ConnectivityLevel.None;
var prevInstance = InstanceName;
if (prevInstance != instanceName)
CloseAllChannels();
CloseAllChannels(false);
InstanceName = instanceName;
if (skipAuthChecks)
return ConnectivityLevel.Connected;
@@ -202,13 +202,28 @@ namespace TGServiceInterface
/// <summary>
/// Closes all <see cref="ChannelFactory"/>s stored in <see cref="ChannelFactoryCache"/> and clears it
/// </summary>
void CloseAllChannels()
/// <param name="includingRoot">If set to <see langword="false"/>, doesn't clear the channels that are used by <see cref="ITGSService"/></param>
void CloseAllChannels(bool includingRoot)
{
string[] RootThings = { typeof(ITGSService).Name, 'S' + typeof(ITGConnectivity).Name };
lock (ChannelFactoryCache)
{
foreach (var I in ChannelFactoryCache)
CloseChannel(I.Value);
ChannelFactoryCache.Clear();
{
if (RootThings.Contains(I.Key))
continue;
var cf = I.Value;
try
{
cf.Closed += ChannelFactory_Closed;
cf.Close();
}
catch
{
cf.Abort();
}
ChannelFactoryCache.Remove(I);
}
}
}
@@ -231,23 +246,6 @@ namespace TGServiceInterface
return false;
}
/// <summary>
/// Safely shuts down a single <see cref="ChannelFactory"/>
/// </summary>
/// <param name="cf">The <see cref="ChannelFactory"/> to shutdown</param>
static void CloseChannel(ChannelFactory cf)
{
try
{
cf.Closed += ChannelFactory_Closed;
cf.Close();
}
catch
{
cf.Abort();
}
}
/// <summary>
/// Disposes a closed <see cref="ChannelFactory"/>
/// </summary>
@@ -275,8 +273,10 @@ namespace TGServiceInterface
{
if (useInstanceName & InstanceName == null)
throw new Exception("Instance not selected!");
var tot = typeof(T);
var actualToT = typeof(T);
var tot = actualToT.Name;
if (actualToT == typeof(ITGConnectivity) && !useInstanceName)
tot = 'S' + tot;
ChannelFactory<T> cf;
lock (ChannelFactoryCache)
@@ -413,7 +413,7 @@ namespace TGServiceInterface
{
if (disposing)
{
CloseAllChannels();
CloseAllChannels(true);
}
// TODO: free unmanaged resources (unmanaged objects) and override a finalizer below.
@@ -60,6 +60,7 @@
<Compile Include="DreamDaemonBridge.cs" />
<Compile Include="Enumerations.cs" />
<Compile Include="Helpers.cs" />
<Compile Include="InstanceMetadata.cs" />
<Compile Include="Properties\AssemblyInfo.cs" />
<Compile Include="Components\Repository.cs" />
<Compile Include="PullRequestInfo.cs" />