Added Background and BackgroundVerticalOffset to Mod. Added more XML comments. Added LICENSE and README files.
Added Background and BackgroundVerticalOffset to Mod. Added more XML comments. Added LICENSE and README files.

// KerbalStuffWrapper // KerbalStuffWrapper
// //
// Author: // Author:
// toadicus // toadicus
// //
// Copyright © 2014, toadicus // Copyright © 2014, toadicus
// //
// All rights reserved. // All rights reserved.
// //
// Redistribution and use in source and binary forms, with or without modification, are permitted provided that the // Redistribution and use in source and binary forms, with or without modification, are permitted provided that the
// following conditions are met: // following conditions are met:
// //
// * Redistributions of source code must retain the above copyright notice, this list of conditions and the // * Redistributions of source code must retain the above copyright notice, this list of conditions and the
// following disclaimer. // following disclaimer.
// * Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the // * Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the
// following disclaimer in the documentation and/or other materials provided with the distribution. // following disclaimer in the documentation and/or other materials provided with the distribution.
// //
// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS // THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT // "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR // LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR // A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
// CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, // CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
// EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, // EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
// PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR // PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
// PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF // PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
// LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING // LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
// NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS // NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
// SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. // SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
// //
// This software uses the CLAP .NET Command-Line Parser, Copyright © 2011 Adrian Aisemberg, SharpRegion. // This software uses the CLAP .NET Command-Line Parser, Copyright © 2011 Adrian Aisemberg, SharpRegion.
// Used under license. // Used under license.
// //
// This software uses the MiniJSON .NET JSON Parser, Copyright © 2013 Calvin Rien. Used under license. // This software uses the MiniJSON .NET JSON Parser, Copyright © 2013 Calvin Rien. Used under license.
// //
// This software uses the FormUpload multipart/form-data library, // This software uses the FormUpload multipart/form-data library,
// http://www.briangrinstead.com/blog/multipart-form-post-in-c. // http://www.briangrinstead.com/blog/multipart-form-post-in-c.
// //
  // KerbalStuff is copyright © 2014 Drew DeVault. Used under license.
  //
   
using MiniJSON; using MiniJSON;
using System; using System;
using System.Collections.Generic; using System.Collections.Generic;
using System.IO; using System.IO;
using System.Linq; using System.Linq;
using System.Net; using System.Net;
using System.Text; using System.Text;
   
namespace KerbalStuff namespace KerbalStuff
{ {
  /// <summary>
  /// <para>Class of static methods for accessing the read-write elements of the KerbalStuff API.</para>
  /// <para>https://github.com/KerbalStuff/KerbalStuff/blob/master/api.md</para>
  /// </summary>
public class KerbalStuff : KerbalStuffReadOnly public class KerbalStuff : KerbalStuffReadOnly
{ {
   
/// <summary> /// <summary>
/// The cookies returned by KerbalStuff after a Login request, and assigned to successive Create and Update /// The cookies returned by KerbalStuff after a Login request, and assigned to successive Create and Update
/// requests. /// requests.
/// </summary> /// </summary>
public static CookieCollection Cookies public static CookieCollection Cookies
{ {
get; get;
protected set; protected set;
} }
   
/// <summary> /// <summary>
/// <para>Performs a Login request to KerbalStuff with the given username and password, returning a Dictionary of /// <para>Performs a Login request to KerbalStuff with the given username and password, returning a Dictionary of
/// deserialized JSON objects received from KerbalStuff after the request, or null if an error occurs.</para> /// deserialized JSON objects received from KerbalStuff after the request, or null if an error occurs.</para>
/// <para>Sets <see cref="KerbalStuff.KerbalStuff.Cookies"/> on success.</para> /// <para>Sets KerbalStuff.Cookies on success.</para>
/// </summary> /// </summary>
/// <param name="username">A valid KerbalStuff username, exact and case-sensitive.</param> /// <param name="username">A valid KerbalStuff username, exact and case-sensitive.</param>
/// <param name="password">A valid KerbalStuff password associated with the username, exact and case-sensitive.</param> /// <param name="password">A valid KerbalStuff password associated with the username, exact and case-sensitive.</param>
public static Dictionary<string, object> Login(string username, string password) public static Dictionary<string, object> Login(string username, string password)
{ {
string uri = KerbalStuffAction.Login.UriFormat; string uri = KerbalStuffAction.Login.UriFormat;
   
Dictionary<string, object> postParams = new Dictionary<string, object>(); Dictionary<string, object> postParams = new Dictionary<string, object>();
postParams.Add("username", username); postParams.Add("username", username);
postParams.Add("password", password); postParams.Add("password", password);
   
ExecutePostRequest(uri, postParams, null); ExecutePostRequest(uri, postParams, null);
   
if (currentResponse.Cookies.Count > 0) if (currentResponse.Cookies.Count > 0)
{ {
Cookies = currentResponse.Cookies; Cookies = currentResponse.Cookies;
} }
   
if (currentJson != null && currentJson is Dictionary<string, object>) if (currentJson != null && currentJson is Dictionary<string, object>)
{ {
return currentJson as Dictionary<string, object>; return currentJson as Dictionary<string, object>;
} }
   
return null; return null;
} }
   
/// <summary> /// <summary>
/// <para>Performs a creation request to KerbalStuff, creating a new mod described by the given /// <para>Performs a creation request to KerbalStuff, creating a new mod described by the given
/// <see cref="KerbalStuff.Mod"/> object and uploading the file with the given name and path.</para> /// <see cref="KerbalStuff.Mod"/> object and uploading the file with the given name and path.</para>
/// <para>Returns a Dictionary of deserialized JSON objects received from KerbalStuff after the request, or null /// <para>Returns a Dictionary of deserialized JSON objects received from KerbalStuff after the request, or null
/// if an error occurs.</para> /// if an error occurs.</para>
/// </summary> /// </summary>
/// <param name="mod">The Mod to be created on KerbalStuff</param> /// <param name="mod">The Mod to be created on KerbalStuff</param>
/// <param name="fileName">The name of the file to be uploaded</param> /// <param name="fileName">The name of the file to be uploaded</param>
/// <param name="filePath">The program-relative path of the file to be uploaded</param> /// <param name="filePath">The program-relative path of the file to be uploaded</param>
public static Dictionary<string, object> Create(Mod mod, string fileName, string filePath) public static Dictionary<string, object> Create(Mod mod, string fileName, string filePath)
{ {
if (mod == null) if (mod == null)
{ {
throw new ArgumentNullException("KerbalStuffWrapper.Create: mod argument cannot be null."); throw new ArgumentNullException("KerbalStuffWrapper.Create: mod argument cannot be null.");
} }
else else
{ {
if (mod.Name == string.Empty) if (mod.Name == string.Empty)
throw new ArgumentException("mod.name cannot be empty."); throw new ArgumentException("mod.name cannot be empty.");
if (mod.License == string.Empty) if (mod.License == string.Empty)
throw new ArgumentException("mod.license cannot be empty."); throw new ArgumentException("mod.license cannot be empty.");
if (mod.ShortDescription == string.Empty) if (mod.ShortDescription == string.Empty)
throw new ArgumentException("mod.short_description cannot be empty."); throw new ArgumentException("mod.short_description cannot be empty.");
if (mod.Versions.Count < 1) if (mod.Versions.Count < 1)
throw new ArgumentException("mod must have a single version to create."); throw new ArgumentException("mod must have a single version to create.");
else if (mod.Versions[0] == null) else if (mod.Versions[0] == null)
{ {
throw new ArgumentNullException("mod.versions[0] cannot be null."); throw new ArgumentNullException("mod.versions[0] cannot be null.");
} }
else else
{ {
if (mod.Versions[0].FriendlyVersion == string.Empty) if (mod.Versions[0].FriendlyVersion == string.Empty)
throw new ArgumentException("mod.versions[0].friendly_version cannot be empty."); throw new ArgumentException("mod.versions[0].friendly_version cannot be empty.");
if (mod.Versions[0].KspVersion == string.Empty) if (mod.Versions[0].KspVersion == string.Empty)
throw new ArgumentException("mod.versions[0].ksp_version cannot be empty."); throw new ArgumentException("mod.versions[0].ksp_version cannot be empty.");
} }
} }
   
if (Cookies == null) if (Cookies == null)
{ {
throw new Exception("KerbalStuffWrapper.Create: Must log in first."); throw new Exception("KerbalStuffWrapper.Create: Must log in first.");
} }
   
if (!File.Exists(filePath)) if (!File.Exists(filePath))
{ {
throw new IOException(string.Format("KerbalStuffWrapper.Create: File '{0}' does not exist.", filePath)); throw new IOException(string.Format("KerbalStuffWrapper.Create: File '{0}' does not exist.", filePath));
} }
   
Dictionary<string, object> postParams = new Dictionary<string, object>(); Dictionary<string, object> postParams = new Dictionary<string, object>();
postParams.Add("name", mod.Name); postParams.Add("name", mod.Name);
postParams.Add("short-description", mod.ShortDescription); postParams.Add("short-description", mod.ShortDescription);
postParams.Add("license", mod.License); postParams.Add("license", mod.License);
postParams.Add("version", mod.Versions[0].FriendlyVersion); postParams.Add("version", mod.Versions[0].FriendlyVersion);
postParams.Add("ksp-version", mod.Versions[0].KspVersion); postParams.Add("ksp-version", mod.Versions[0].KspVersion);
postParams.Add("zipball", ReadZipballParameter(fileName, filePath)); postParams.Add("zipball", ReadZipballParameter(fileName, filePath));
   
ExecutePostRequest(KerbalStuffAction.Create.UriFormat, postParams, Cookies); ExecutePostRequest(KerbalStuffAction.Create.UriFormat, postParams, Cookies);
   
if (currentJson != null && currentJson is Dictionary<string, object>) if (currentJson != null && currentJson is Dictionary<string, object>)
{ {
var rval = currentJson as Dictionary<string, object>; var rval = currentJson as Dictionary<string, object>;
   
if (rval.ContainsKey("reason")) if (rval.ContainsKey("reason"))
{ {
rval["message"] = rval["reason"]; rval["message"] = rval["reason"];
} }
   
return rval; return rval;
} }
   
return null; return null;
} }
   
/// <summary> /// <summary>
/// <para>Performs and Update request to KerbalStuff, uploading a new version described in the given /// <para>Performs and Update request to KerbalStuff, uploading a new version described in the given
/// <see cref="KerbalStuff.ModVersion"/> of the mod with the given Id, uploading the file with the given name /// <see cref="KerbalStuff.ModVersion"/> of the mod with the given Id, uploading the file with the given name
/// and path. KerbalStuff will notify followers of the mod if requested.</para> /// and path. KerbalStuff will notify followers of the mod if requested.</para>
/// <para>Returns a Dictionary of deserialized JSON objects received from KerbalStuff after the request, or null /// <para>Returns a Dictionary of deserialized JSON objects received from KerbalStuff after the request, or null
/// if an error occurs.</para> /// if an error occurs.</para>
/// </summary> /// </summary>
/// <param name="modId">The Id of Mod to be updated on KerbalStuff.</param> /// <param name="modId">The Id of Mod to be updated on KerbalStuff.</param>
/// <param name="version">The ModVersion to be added to the Mod.</param> /// <param name="version">The ModVersion to be added to the Mod.</param>
/// <param name="notifyFollowers">If set to <c>true</c> KerbalStuff will notify followers of the mod.</param> /// <param name="notifyFollowers">If set to <c>true</c> KerbalStuff will notify followers of the mod.</param>
/// <param name="fileName">The name of the file to be uploaded</param> /// <param name="fileName">The name of the file to be uploaded</param>
/// <param name="filePath">The program-relative path of the file to be uploaded</param> /// <param name="filePath">The program-relative path of the file to be uploaded</param>
public static Dictionary<string, object> Update(long modId, ModVersion version, bool notifyFollowers, string fileName, string filePath) public static Dictionary<string, object> Update(long modId, ModVersion version, bool notifyFollowers, string fileName, string filePath)
{ {
if (version == null) if (version == null)
{ {
throw new ArgumentNullException("KerbalStuffWrapper.Update: version cannot be null"); throw new ArgumentNullException("KerbalStuffWrapper.Update: version cannot be null");
} }
if (version.FriendlyVersion == string.Empty) if (version.FriendlyVersion == string.Empty)
throw new ArgumentException("KerbalStuffWrapper.Update: version.friendly_version cannot be empty"); throw new ArgumentException("KerbalStuffWrapper.Update: version.friendly_version cannot be empty");
if (version.KspVersion == string.Empty) if (version.KspVersion == string.Empty)
throw new ArgumentException("KerbalStuffWrapper.Update: version.ksp_version cannot be empty"); throw new ArgumentException("KerbalStuffWrapper.Update: version.ksp_version cannot be empty");
   
if (Cookies == null) if (Cookies == null)
{ {
throw new Exception("KerbalStuffWrapper.Update: Must log in first."); throw new Exception("KerbalStuffWrapper.Update: Must log in first.");
} }
   
if (!File.Exists(filePath)) if (!File.Exists(filePath))
{ {
throw new IOException(string.Format("KerbalStuffWrapper.Update: File '{0}' does not exist.", filePath)); throw new IOException(string.Format("KerbalStuffWrapper.Update: File '{0}' does not exist.", filePath));
} }
   
string uri = string.Format(KerbalStuffAction.Update.UriFormat, modId); string uri = string.Format(KerbalStuffAction.Update.UriFormat, modId);
   
Dictionary<string, object> postParams = new Dictionary<string, object>(); Dictionary<string, object> postParams = new Dictionary<string, object>();
postParams.Add("version", version.FriendlyVersion); postParams.Add("version", version.FriendlyVersion);
postParams.Add("ksp-version", version.KspVersion); postParams.Add("ksp-version", version.KspVersion);
   
if (version.ChangeLog != null && version.ChangeLog != string.Empty) if (version.ChangeLog != null && version.ChangeLog != string.Empty)
{ {
postParams.Add("changelog", version.ChangeLog); postParams.Add("changelog", version.ChangeLog);
} }
   
postParams.Add("notify-followers", notifyFollowers ? "yes" : "no"); postParams.Add("notify-followers", notifyFollowers ? "yes" : "no");
   
postParams.Add("zipball", ReadZipballParameter(fileName, filePath)); postParams.Add("zipball", ReadZipballParameter(fileName, filePath));
   
ExecutePostRequest(uri, postParams, Cookies); ExecutePostRequest(uri, postParams, Cookies);
   
if (currentJson != null && currentJson is Dictionary<string, object>) if (currentJson != null && currentJson is Dictionary<string, object>)
{ {
return currentJson as Dictionary<string, object>; return currentJson as Dictionary<string, object>;
} }
   
return null; return null;
} }
   
  /// <summary>
  /// Executes an HTTP post request to the given URI, including the given Dictionary of post parameters as
  /// multipart/form-data. If cookieCollection is not null, includes the given cookies as a part of the request.
  /// <seealso cref="FormUpload"/>
  /// </summary>
  /// <param name="uri">Absolute URI</param>
  /// <param name="postParams">String-keyed dictionary of objects to be serialized into multipart/form-data</param>
  /// <param name="cookieCollection">Optional, cookies to be included with the request.</param>
protected static void ExecutePostRequest(string uri, Dictionary<string, object> postParams, CookieCollection cookieCollection = null) protected static void ExecutePostRequest(string uri, Dictionary<string, object> postParams, CookieCollection cookieCollection = null)
{ {
currentJson = null; currentJson = null;
currentRequest = null; currentRequest = null;
currentResponse = null; currentResponse = null;
   
CookieContainer jar = new CookieContainer(); CookieContainer jar = new CookieContainer();
   
if (cookieCollection != null) if (cookieCollection != null)
{ {
jar.Add(cookieCollection); jar.Add(cookieCollection);
} }
   
try try
{ {
currentResponse = FormUpload.MultipartFormDataPost( currentResponse = FormUpload.MultipartFormDataPost(
uri, uri,
"KerbalStuffWrapper by toadicus", "KerbalStuffWrapper by toadicus",
postParams, postParams,
jar jar
); );
} }
catch (WebException ex) catch (WebException ex)
{ {
currentResponse = ex.Response as HttpWebResponse; currentResponse = ex.Response as HttpWebResponse;
} }
   
if (currentResponse.ContentType == "application/json") if (currentResponse.ContentType == "application/json")
{ {
var responseReader = new StreamReader(currentResponse.GetResponseStream()); var responseReader = new StreamReader(currentResponse.GetResponseStream());
   
string json = responseReader.ReadToEnd(); string json = responseReader.ReadToEnd();
   
currentJson = Json.Deserialize(json); currentJson = Json.Deserialize(json);
} }
} }
   
  /// <summary>
  /// Reads in the file at the given path and generates a FormUpload.FileParameter object for use in postParam
  /// Dictionaries.
  /// </summary>
  /// <returns>The zipball parameter.</returns>
  /// <param name="fileName">Name of the file to be read</param>
  /// <param name="filePath">Program-relative path of the file to be read</param>
protected static FormUpload.FileParameter ReadZipballParameter(string fileName, string filePath) protected static FormUpload.FileParameter ReadZipballParameter(string fileName, string filePath)
{ {
using (FileStream file = File.OpenRead(filePath)) using (FileStream file = File.OpenRead(filePath))
{ {
byte[] buffer = new byte[1 << 16]; byte[] buffer = new byte[1 << 16];
int bytesRead; int bytesRead;
   
MemoryStream stream = new MemoryStream(); MemoryStream stream = new MemoryStream();
   
while ((bytesRead = file.Read(buffer, 0, buffer.Length)) > 0) while ((bytesRead = file.Read(buffer, 0, buffer.Length)) > 0)
{ {
stream.Write(buffer, 0, bytesRead); stream.Write(buffer, 0, bytesRead);
} }
   
stream.Seek(0, SeekOrigin.Begin); stream.Seek(0, SeekOrigin.Begin);
   
byte[] fileBytes = new byte[stream.Length]; byte[] fileBytes = new byte[stream.Length];
   
bytesRead = stream.Read(fileBytes, 0, (int)stream.Length); bytesRead = stream.Read(fileBytes, 0, (int)stream.Length);
   
return new FormUpload.FileParameter(fileBytes, fileName, "application/zip"); return new FormUpload.FileParameter(fileBytes, fileName, "application/zip");
} }
} }
   
  /// <summary>
  /// Constructor is protected to allow class inheritance, but the class is static and should not be instatiated.
  /// </summary>
  [Obsolete("Do not instantiate KerbalStuff objects; all class members are static.")]
protected KerbalStuff() {} protected KerbalStuff() {}
} }
} }
<?xml version="1.0" encoding="utf-8"?> <?xml version="1.0" encoding="utf-8"?>
<Project DefaultTargets="Build" ToolsVersion="4.0" xmlns="http://schemas.microsoft.com/developer/msbuild/2003"> <Project DefaultTargets="Build" ToolsVersion="4.0" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
<PropertyGroup> <PropertyGroup>
<Configuration Condition=" '$(Configuration)' == '' ">Debug</Configuration> <Configuration Condition=" '$(Configuration)' == '' ">Debug</Configuration>
<Platform Condition=" '$(Platform)' == '' ">AnyCPU</Platform> <Platform Condition=" '$(Platform)' == '' ">AnyCPU</Platform>
<ProductVersion>8.0.30703</ProductVersion> <ProductVersion>8.0.30703</ProductVersion>
<SchemaVersion>2.0</SchemaVersion> <SchemaVersion>2.0</SchemaVersion>
<ProjectGuid>{0CA12F64-AD3B-41E6-8A35-8781A8DAD1C1}</ProjectGuid> <ProjectGuid>{0CA12F64-AD3B-41E6-8A35-8781A8DAD1C1}</ProjectGuid>
<OutputType>Library</OutputType> <OutputType>Library</OutputType>
<RootNamespace>KerbalStuff</RootNamespace> <RootNamespace>KerbalStuff</RootNamespace>
<AssemblyName>KerbalStuff</AssemblyName> <AssemblyName>KerbalStuff</AssemblyName>
<TargetFrameworkVersion>v3.5</TargetFrameworkVersion> <TargetFrameworkVersion>v3.5</TargetFrameworkVersion>
<UseMSBuildEngine>False</UseMSBuildEngine> <UseMSBuildEngine>False</UseMSBuildEngine>
</PropertyGroup> </PropertyGroup>
<PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Release|AnyCPU' "> <PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Release|AnyCPU' ">
<DebugType>full</DebugType>  
<Optimize>true</Optimize> <Optimize>true</Optimize>
<OutputPath>bin\Release</OutputPath> <OutputPath>bin\Release</OutputPath>
<ErrorReport>prompt</ErrorReport> <ErrorReport>prompt</ErrorReport>
<WarningLevel>4</WarningLevel> <WarningLevel>4</WarningLevel>
<ConsolePause>false</ConsolePause> <ConsolePause>false</ConsolePause>
<PlatformTarget>x86</PlatformTarget> <PlatformTarget>x86</PlatformTarget>
  <DocumentationFile>bin\Release\KerbalStuff.xml</DocumentationFile>
</PropertyGroup> </PropertyGroup>
<PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Debug|AnyCPU' "> <PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Debug|AnyCPU' ">
<DebugSymbols>true</DebugSymbols> <DebugSymbols>true</DebugSymbols>
<DebugType>full</DebugType> <DebugType>full</DebugType>
<Optimize>false</Optimize> <Optimize>false</Optimize>
<OutputPath>bin\Debug</OutputPath> <OutputPath>bin\Debug</OutputPath>
<DefineConstants>DEBUG;</DefineConstants> <DefineConstants>DEBUG;</DefineConstants>
<ErrorReport>prompt</ErrorReport> <ErrorReport>prompt</ErrorReport>
<WarningLevel>4</WarningLevel> <WarningLevel>4</WarningLevel>
<ConsolePause>false</ConsolePause> <ConsolePause>false</ConsolePause>
<PlatformTarget>x86</PlatformTarget> <PlatformTarget>x86</PlatformTarget>
</PropertyGroup> </PropertyGroup>
<Import Project="$(MSBuildBinPath)\Microsoft.CSharp.targets" /> <Import Project="$(MSBuildBinPath)\Microsoft.CSharp.targets" />
<ItemGroup> <ItemGroup>
<ProjectReference Include="..\KerbalStuffReadOnly\KerbalStuffReadOnly.csproj"> <ProjectReference Include="..\KerbalStuffReadOnly\KerbalStuffReadOnly.csproj">
<Project>{720FA70F-D785-48ED-BA45-561921E0EEEC}</Project> <Project>{720FA70F-D785-48ED-BA45-561921E0EEEC}</Project>
<Name>KerbalStuffReadOnly</Name> <Name>KerbalStuffReadOnly</Name>
</ProjectReference> </ProjectReference>
</ItemGroup> </ItemGroup>
<ItemGroup> <ItemGroup>
<Reference Include="System" /> <Reference Include="System" />
<Reference Include="System.Net" /> <Reference Include="System.Net" />
</ItemGroup> </ItemGroup>
<ItemGroup> <ItemGroup>
<Compile Include="KerbalStuff.cs" /> <Compile Include="KerbalStuff.cs" />
<Compile Include="FormUpload.cs" /> <Compile Include="FormUpload.cs" />
</ItemGroup> </ItemGroup>
</Project> </Project>
// KerbalStuffWrapper // KerbalStuffWrapper
// //
// Author: // Author:
// toadicus // toadicus
// //
// Copyright © 2014, toadicus // Copyright © 2014, toadicus
// //
// All rights reserved. // All rights reserved.
// //
// Redistribution and use in source and binary forms, with or without modification, are permitted provided that the // Redistribution and use in source and binary forms, with or without modification, are permitted provided that the
// following conditions are met: // following conditions are met:
// //
// * Redistributions of source code must retain the above copyright notice, this list of conditions and the // * Redistributions of source code must retain the above copyright notice, this list of conditions and the
// following disclaimer. // following disclaimer.
// * Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the // * Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the
// following disclaimer in the documentation and/or other materials provided with the distribution. // following disclaimer in the documentation and/or other materials provided with the distribution.
// //
// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS // THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT // "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR // LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR // A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
// CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, // CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
// EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, // EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
// PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR // PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
// PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF // PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
// LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING // LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
// NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS // NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
// SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. // SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
// //
// This software uses the CLAP .NET Command-Line Parser, Copyright © 2011 Adrian Aisemberg, SharpRegion. // This software uses the CLAP .NET Command-Line Parser, Copyright © 2011 Adrian Aisemberg, SharpRegion.
// Used under license. // Used under license.
// //
// This software uses the MiniJSON .NET JSON Parser, Copyright © 2013 Calvin Rien. Used under license. // This software uses the MiniJSON .NET JSON Parser, Copyright © 2013 Calvin Rien. Used under license.
// //
// This software uses the FormUpload multipart/form-data library, // This software uses the FormUpload multipart/form-data library,
// http://www.briangrinstead.com/blog/multipart-form-post-in-c. // http://www.briangrinstead.com/blog/multipart-form-post-in-c.
// //
  // KerbalStuff is copyright © 2014 Drew DeVault. Used under license.
  //
   
  /*
  * PATH NOTE:
  *
  * All partial URI path string should end without a slash, and begin with a slash or protocol identifier.
  *
  * GOOD: "https://kerbalstuff.com", "/api", "/mod/{0:s}"
  *
  * BAD: "kerbalstuff.com/", "api", "mod/{0:s}"
  * */
   
using MiniJSON; using MiniJSON;
using System; using System;
using System.Collections.Generic; using System.Collections.Generic;
using System.IO; using System.IO;
using System.Linq; using System.Linq;
using System.Net; using System.Net;
using System.Text; using System.Text;
   
namespace KerbalStuff namespace KerbalStuff
{ {
  /// <summary>
  /// <para>Class of static methods for accessing the read-only elements of the KerbalStuff API.</para>
  /// <para>https://github.com/KerbalStuff/KerbalStuff/blob/master/api.md</para>
  /// </summary>
public class KerbalStuffReadOnly public class KerbalStuffReadOnly
{ {
/// <summary> /// <summary>
/// The root URI of the KerbalStuff website, including protocol. /// The root URI of the KerbalStuff website, including protocol.
/// </summary> /// </summary>
public const string RootUri = "https://kerbalstuff.com"; public const string RootUri = "https://kerbalstuff.com";
   
/// <summary> /// <summary>
/// The URI of the KerbalStuff API, relative to the KerbalStuff root. /// The URI of the KerbalStuff API, relative to the KerbalStuff root.
/// </summary> /// </summary>
/// <seealso cref="KerbalStuffReadOnly.RootUri"/> /// <seealso cref="KerbalStuffReadOnly.RootUri"/>
public const string APIUri = RootUri + "/api"; public const string APIUri = RootUri + "/api";
   
private const string UserAgent = "KerbalStuffWrapper by toadicus"; private const string UserAgent = "KerbalStuffWrapper by toadicus";
   
/// <summary> /// <summary>
/// The response received from KerbalStuff after the current request. Reset to null at the beginning of each /// The response received from KerbalStuff after the current request. Reset to null at the beginning of each
/// new request. /// new request.
/// </summary> /// </summary>
public static HttpWebResponse currentResponse public static HttpWebResponse currentResponse
{ {
get; get;
protected set; protected set;
} }
   
/// <summary> /// <summary>
/// The List or Dictionary of deserialized JSON objects received from KerbalStuff after the current request. /// The List or Dictionary of deserialized JSON objects received from KerbalStuff after the current request.
/// Reset to null at the beginning of each new request. /// Reset to null at the beginning of each new request.
/// </summary> /// </summary>
/// <value>The current json.</value> /// <value>The current json.</value>
public static object currentJson public static object currentJson
{ {
get; get;
protected set; protected set;
} }
   
/// <summary> /// <summary>
/// Queries KerbalStuff for the mod with the given Id, returning a <see cref="KerbalStuff.Mod"/> object, /// Queries KerbalStuff for the mod with the given Id, returning a <see cref="KerbalStuff.Mod"/> object,
/// or null if an error occured. /// or null if an error occured.
/// </summary> /// </summary>
/// <param name="modId">The Id of Mod to be queried on KerbalStuff.</param> /// <param name="modId">The Id of Mod to be queried on KerbalStuff.</param>
public static Mod ModInfo(long modId) public static Mod ModInfo(long modId)
{ {
string uri = string.Format(KerbalStuffAction.ModInfo.UriFormat, modId); ExecuteGetRequest(KerbalStuffAction.ModInfo, modId);
   
ExecuteGetRequest(uri, KerbalStuffAction.ModInfo.RequestMethod);  
   
Mod mod = null; Mod mod = null;
   
if (currentJson != null && currentJson is Dictionary<string, object>) if (currentJson != null && currentJson is Dictionary<string, object>)
{ {
mod = new Mod(currentJson as Dictionary<string, object>); mod = new Mod(currentJson as Dictionary<string, object>);
} }
   
return mod; return mod;
} }
   
/// <summary> /// <summary>
/// Queries KerbalStuff for the latest version of the mod with then given Id, returning a /// Queries KerbalStuff for the latest version of the mod with then given Id, returning a
/// <see cref="KerbalStuff.ModVersion"/> object, or null if an error occured. /// <see cref="KerbalStuff.ModVersion"/> object, or null if an error occured.
/// </summary> /// </summary>
/// <param name="modId">The Id of the Mod to be queried on KerbalStuff.</param> /// <param name="modId">The Id of the Mod to be queried on KerbalStuff.</param>
public static ModVersion ModLatest(long modId) public static ModVersion ModLatest(long modId)
{ {
string uri = string.Format(KerbalStuffAction.ModLatest.UriFormat, modId); ExecuteGetRequest(KerbalStuffAction.ModLatest, modId);
   
ExecuteGetRequest(uri, KerbalStuffAction.ModLatest.RequestMethod);  
   
ModVersion ver = null; ModVersion ver = null;
   
if (currentJson != null && currentJson is Dictionary<string, object>) if (currentJson != null && currentJson is Dictionary<string, object>)
{ {
ver = new ModVersion(currentJson as Dictionary<string, object>); ver = new ModVersion(currentJson as Dictionary<string, object>);
} }
   
return ver; return ver;
} }
   
/// <summary> /// <summary>
/// Searches KerbalStuff for a mod containing the given query string, returning a List of /// Searches KerbalStuff for a mod containing the given query string, returning a List of
/// <see cref="KerbalStuff.Mod"/> objects, or an empty List if none are found or an error occurs. /// <see cref="KerbalStuff.Mod"/> objects, or an empty List if none are found or an error occurs.
/// </summary> /// </summary>
/// <param name="query">The search query</param> /// <param name="query">The search query</param>
public static List<Mod> ModSearch(string query) public static List<Mod> ModSearch(string query)
{ {
string uri = string.Format(KerbalStuffAction.ModSearch.UriFormat, query); ExecuteGetRequest(KerbalStuffAction.ModSearch, query);
   
ExecuteGetRequest(uri, KerbalStuffAction.ModSearch.RequestMethod);  
   
List<Mod> rList = new List<Mod>(); List<Mod> rList = new List<Mod>();
   
if (currentJson != null && currentJson is List<object>) if (currentJson != null && currentJson is List<object>)
{ {
foreach (var modObj in (currentJson as List<object>)) foreach (var modObj in (currentJson as List<object>))
{ {
if (modObj is Dictionary<string, object>) if (modObj is Dictionary<string, object>)
{ {
rList.Add(new Mod(modObj as Dictionary<string, object>)); rList.Add(new Mod(modObj as Dictionary<string, object>));
} }
} }
} }
   
return rList; return rList;
} }
   
/// <summary> /// <summary>
/// Queries KerbalStuff for a user with the given username, returning a <see cref="KerbalStuff.User"/> object, /// Queries KerbalStuff for a user with the given username, returning a <see cref="KerbalStuff.User"/> object,
/// or null if an error occurs. /// or null if an error occurs.
/// </summary> /// </summary>
/// <param name="username">The exact, case-sensitive username to query.</param> /// <param name="username">The exact, case-sensitive username to query.</param>
public static User UserInfo(string username) public static User UserInfo(string username)
{ {
ExecuteGetRequest(KerbalStuffAction.UserInfo, false, username); ExecuteGetRequest(KerbalStuffAction.UserInfo, username);
   
   
User user = null; User user = null;
   
if (currentJson != null && currentJson is Dictionary<string, object>) if (currentJson != null && currentJson is Dictionary<string, object>)
{ {
user = new User(currentJson); user = new User(currentJson);
} }
   
return user; return user;
} }
   
/// <summary> /// <summary>
/// Searches KerbalStuff for a user containing the query string, returning a List of /// Searches KerbalStuff for a user containing the query string, returning a List of
/// <see cref="KerbalStuff.User"/> objects, or an empty List if none are found or an error occurs. /// <see cref="KerbalStuff.User"/> objects, or an empty List if none are found or an error occurs.
/// </summary> /// </summary>
/// <returns>The search.</returns> /// <returns>The search.</returns>
/// <param name="query">Query.</param> /// <param name="query">Query.</param>
public static List<User> UserSearch(string query) public static List<User> UserSearch(string query)
{ {
ExecuteGetRequest(KerbalStuffAction.UserSearch, false, query); ExecuteGetRequest(KerbalStuffAction.UserSearch, query);
   
List<User> users = new List<User>(); List<User> users = new List<User>();
   
if (currentJson != null && currentJson is List<object>) if (currentJson != null && currentJson is List<object>)
{ {
foreach (object userObj in (currentJson as List<object>)) foreach (object userObj in (currentJson as List<object>))
{ {
users.Add(new User(userObj)); users.Add(new User(userObj));
} }
} }
   
return users; return users;
} }
   
  /// <summary>
  /// The current HTTP request delivered in an API action. Reset to null at the beginning of each new action.
  /// </summary>
protected static HttpWebRequest currentRequest; protected static HttpWebRequest currentRequest;
   
protected static void ExecuteGetRequest(KerbalStuffAction action, bool assignCookies, params object[] formatArgs) /// <summary>
  /// Executes an HTTP GET request using the URI format from the specified
  /// <see cref="KerbalStuff.KerbalStuffAction"/> and objects for inclusion in the formatted URI string.
  /// </summary>
  /// <param name="action">A KerbalStuffAction object describing the desired API action.</param>
  /// <param name="formatArgs">Format arguments</param>
  protected static void ExecuteGetRequest(KerbalStuffAction action, params object[] formatArgs)
{ {
string uri = string.Format(action.UriFormat, formatArgs); string uri = string.Format(action.UriFormat, formatArgs);
   
ExecuteGetRequest(uri, action.RequestMethod); ExecuteGetRequest(uri);
} }
   
protected static void ExecuteGetRequest(string uri, string method) /// <summary>
  /// Executes an HTTP GET request to the specified uri.
  /// </summary>
  /// <param name="uri">Absolute URI</param>
  protected static void ExecuteGetRequest(string uri)
{ {
currentJson = null; currentJson = null;
currentRequest = null; currentRequest = null;
currentResponse = null; currentResponse = null;
   
if (uri == string.Empty) if (uri == string.Empty)
{ {
throw new ArgumentOutOfRangeException("KerbalStuffWrapper.ExecuteRequest: uri must not be empty."); throw new ArgumentOutOfRangeException("KerbalStuffWrapper.ExecuteRequest: uri must not be empty.");
} }
   
uri = Uri.EscapeUriString(uri); uri = Uri.EscapeUriString(uri);
   
method = method.ToUpper();  
   
if (method != "POST" && method != "GET")  
{  
throw new ArgumentOutOfRangeException("KerbalStuffWrapper.ExecuteRequest: method must be POST or GET.");  
}  
   
currentRequest = (HttpWebRequest)WebRequest.Create(uri); currentRequest = (HttpWebRequest)WebRequest.Create(uri);
currentRequest.Method = method; currentRequest.Method = "GET";
  currentRequest.UserAgent = UserAgent;
   
try try
{ {
currentResponse = currentRequest.GetResponse() as HttpWebResponse; currentResponse = currentRequest.GetResponse() as HttpWebResponse;
} }
catch (WebException ex) catch (WebException ex)
{ {
currentResponse = ex.Response as HttpWebResponse; currentResponse = ex.Response as HttpWebResponse;
} }
   
if (currentResponse.ContentType == "application/json") if (currentResponse.ContentType == "application/json")
{ {
var responseReader = new StreamReader(currentResponse.GetResponseStream()); var responseReader = new StreamReader(currentResponse.GetResponseStream());
   
string json = responseReader.ReadToEnd(); string json = responseReader.ReadToEnd();
   
currentJson = Json.Deserialize(json); currentJson = Json.Deserialize(json);
} }
} }
   
  /// <summary>
  /// Constructor is protected to allow class inheritance, but the class is static and should not be instatiated.
  /// </summary>
  [Obsolete("Do not instantiate KerbalStuffReadOnly objects; all class members are static.")]
protected KerbalStuffReadOnly() {} protected KerbalStuffReadOnly() {}
} }
   
  /// <summary>
  /// Struct describing a KerbalStuff API action.
  /// </summary>
public struct KerbalStuffAction public struct KerbalStuffAction
{ {
  /// <summary>
  /// KerbalStuffAction object describing access to the KerbalStuff create API action.
  /// </summary>
public static readonly KerbalStuffAction Create = new KerbalStuffAction("create", "/mod/create", "POST"); public static readonly KerbalStuffAction Create = new KerbalStuffAction("create", "/mod/create", "POST");
  /// <summary>
  /// KerbalStuffAction object describing access to the KerbalStuff login API action.
  /// </summary>
public static readonly KerbalStuffAction Login = new KerbalStuffAction("login", "/login", "POST"); public static readonly KerbalStuffAction Login = new KerbalStuffAction("login", "/login", "POST");
  /// <summary>
  /// KerbalStuffAction object describing access to the KerbalStuff mod/&lt;mod_id&gt; API action.
  /// </summary>
public static readonly KerbalStuffAction ModInfo = new KerbalStuffAction("modinfo", "/mod/{0:d}", "GET"); public static readonly KerbalStuffAction ModInfo = new KerbalStuffAction("modinfo", "/mod/{0:d}", "GET");
  /// <summary>
  /// KerbalStuffAction object describing access to the KerbalStuff mod/&lt;mod_id&gt;/latest API action.
  /// </summary>
public static readonly KerbalStuffAction ModLatest = new KerbalStuffAction( public static readonly KerbalStuffAction ModLatest = new KerbalStuffAction(
"modlatest", "modlatest",
"/mod/{0:d}/latest", "/mod/{0:d}/latest",
"GET" "GET"
); );
  /// <summary>
  /// KerbalStuffAction object describing access to the KerbalStuff search/mod API action.
  /// </summary>
public static readonly KerbalStuffAction ModSearch = new KerbalStuffAction( public static readonly KerbalStuffAction ModSearch = new KerbalStuffAction(
"modsearch", "modsearch",
"/search/mod?query={0}", "/search/mod?query={0}",
"GET" "GET"
); );
  /// <summary>
  /// KerbalStuffAction object describing access to the KerbalStuff update API action.
  /// </summary>
public static readonly KerbalStuffAction Update = new KerbalStuffAction("update", "/mod/{0:d}/update", "POST"); public static readonly KerbalStuffAction Update = new KerbalStuffAction("update", "/mod/{0:d}/update", "POST");
  /// <summary>
  /// KerbalStuffAction object describing access to the KerbalStuff user/&lt;username&gt; API action.
  /// </summary>
public static readonly KerbalStuffAction UserInfo = new KerbalStuffAction("userinfo", "/user/{0}", "GET"); public static readonly KerbalStuffAction UserInfo = new KerbalStuffAction("userinfo", "/user/{0}", "GET");
  /// <summary>
  /// KerbalStuffAction object describing access to the KerbalStuff search/user API action.
  /// </summary>
public static readonly KerbalStuffAction UserSearch = new KerbalStuffAction( public static readonly KerbalStuffAction UserSearch = new KerbalStuffAction(
"usersearch", "usersearch",
"/search/user?query={0}", "/search/user?query={0}",
"GET" "GET"
); );
   
  /// <summary>
  /// The name of the action, currently unused.
  /// </summary>
public string Action; public string Action;
   
  /// <summary>
  /// A format string for generating a URI path relative to the KerbalStuff root URI.
  /// </summary>
public string UriPathFormat; public string UriPathFormat;
   
  /// <summary>
  /// The HTTP request method, "GET" or "POST".
  /// </summary>
public string RequestMethod; public string RequestMethod;
   
  /// <summary>
  /// Read-only access to the absolute URI format string containing the protocol and KerbalStuff root URI.
  /// </summary>
  /// <value>The URI format.</value>
public string UriFormat public string UriFormat
{ {
get get
{ {
return string.Format("{0}{1}", KerbalStuffReadOnly.APIUri, this.UriPathFormat); return string.Format("{0}{1}", KerbalStuffReadOnly.APIUri, this.UriPathFormat);
} }
} }
   
public KerbalStuffAction(string action, string uriFormat, string requestMethod) : this() /// <summary>
{ /// Initializes a new instance of the <see cref="KerbalStuff.KerbalStuffAction"/> struct.
  /// </summary>
  /// <param name="action">The name of the action, currently unused.</param>
  /// <param name="uriPathFormat">A format string for generating a URI path relative to the KerbalStuff root URI.</param>
  /// <param name="requestMethod">The HTTP request method, "GET" or "POST".</param>
  public KerbalStuffAction(string action, string uriPathFormat, string requestMethod) : this()
  {
  requestMethod = requestMethod.ToUpper();
   
  if (requestMethod != "GET" && requestMethod != "POST")
  {
  throw new ArgumentOutOfRangeException(
  "KerbalStuffAction.ctor: 'requestMethod' must be one of \"GET\" or \"POST\"");
  }
   
this.Action = action; this.Action = action;
this.UriPathFormat = uriFormat; this.UriPathFormat = uriPathFormat;
this.RequestMethod = requestMethod; this.RequestMethod = requestMethod;
} }
} }
} }
<?xml version="1.0" encoding="utf-8"?> <?xml version="1.0" encoding="utf-8"?>
<Project DefaultTargets="Build" ToolsVersion="4.0" xmlns="http://schemas.microsoft.com/developer/msbuild/2003"> <Project DefaultTargets="Build" ToolsVersion="4.0" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
<PropertyGroup> <PropertyGroup>
<Configuration Condition=" '$(Configuration)' == '' ">Debug</Configuration> <Configuration Condition=" '$(Configuration)' == '' ">Debug</Configuration>
<Platform Condition=" '$(Platform)' == '' ">AnyCPU</Platform> <Platform Condition=" '$(Platform)' == '' ">AnyCPU</Platform>
<ProductVersion>8.0.30703</ProductVersion> <ProductVersion>8.0.30703</ProductVersion>
<SchemaVersion>2.0</SchemaVersion> <SchemaVersion>2.0</SchemaVersion>
<ProjectGuid>{720FA70F-D785-48ED-BA45-561921E0EEEC}</ProjectGuid> <ProjectGuid>{720FA70F-D785-48ED-BA45-561921E0EEEC}</ProjectGuid>
<OutputType>Library</OutputType> <OutputType>Library</OutputType>
<RootNamespace>KerbalStuff</RootNamespace> <RootNamespace>KerbalStuff</RootNamespace>
<AssemblyName>KerbalStuffReadOnly</AssemblyName> <AssemblyName>KerbalStuffReadOnly</AssemblyName>
<TargetFrameworkVersion>v3.5</TargetFrameworkVersion> <TargetFrameworkVersion>v3.5</TargetFrameworkVersion>
<UseMSBuildEngine>False</UseMSBuildEngine> <UseMSBuildEngine>False</UseMSBuildEngine>
</PropertyGroup> </PropertyGroup>
<PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Release|AnyCPU' "> <PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Release|AnyCPU' ">
<DebugType>full</DebugType>  
<Optimize>true</Optimize> <Optimize>true</Optimize>
<OutputPath>bin\Release</OutputPath> <OutputPath>bin\Release</OutputPath>
<ErrorReport>prompt</ErrorReport> <ErrorReport>prompt</ErrorReport>
<WarningLevel>4</WarningLevel> <WarningLevel>4</WarningLevel>
<ConsolePause>false</ConsolePause> <ConsolePause>false</ConsolePause>
<PlatformTarget>x86</PlatformTarget> <PlatformTarget>x86</PlatformTarget>
  <DocumentationFile>bin\Release\KerbalStuffReadOnly.xml</DocumentationFile>
</PropertyGroup> </PropertyGroup>
<PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Debug|AnyCPU' "> <PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Debug|AnyCPU' ">
<DebugSymbols>true</DebugSymbols> <DebugSymbols>true</DebugSymbols>
<DebugType>full</DebugType> <DebugType>full</DebugType>
<Optimize>false</Optimize> <Optimize>false</Optimize>
<OutputPath>bin\Debug</OutputPath> <OutputPath>bin\Debug</OutputPath>
<DefineConstants>DEBUG;</DefineConstants> <DefineConstants>DEBUG;</DefineConstants>
<ErrorReport>prompt</ErrorReport> <ErrorReport>prompt</ErrorReport>
<WarningLevel>4</WarningLevel> <WarningLevel>4</WarningLevel>
<ConsolePause>false</ConsolePause> <ConsolePause>false</ConsolePause>
<PlatformTarget>x86</PlatformTarget> <PlatformTarget>x86</PlatformTarget>
</PropertyGroup> </PropertyGroup>
<Import Project="$(MSBuildBinPath)\Microsoft.CSharp.targets" /> <Import Project="$(MSBuildBinPath)\Microsoft.CSharp.targets" />
<ItemGroup> <ItemGroup>
<Reference Include="System" /> <Reference Include="System" />
<Reference Include="System.Net" /> <Reference Include="System.Net" />
</ItemGroup> </ItemGroup>
<ItemGroup> <ItemGroup>
<Compile Include="Mod.cs" /> <Compile Include="Mod.cs" />
<Compile Include="User.cs" /> <Compile Include="User.cs" />
<Compile Include="MiniJSON.cs" /> <Compile Include="MiniJSON.cs" />
<Compile Include="KerbalStuffReadOnly.cs" /> <Compile Include="KerbalStuffReadOnly.cs" />
</ItemGroup> </ItemGroup>
</Project> </Project>
// KerbalStuffWrapper // KerbalStuffWrapper
// //
// Author: // Author:
// toadicus // toadicus
// //
// Copyright © 2014, toadicus // Copyright © 2014, toadicus
// //
// All rights reserved. // All rights reserved.
// //
// Redistribution and use in source and binary forms, with or without modification, are permitted provided that the // Redistribution and use in source and binary forms, with or without modification, are permitted provided that the
// following conditions are met: // following conditions are met:
// //
// * Redistributions of source code must retain the above copyright notice, this list of conditions and the // * Redistributions of source code must retain the above copyright notice, this list of conditions and the
// following disclaimer. // following disclaimer.
// * Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the // * Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the
// following disclaimer in the documentation and/or other materials provided with the distribution. // following disclaimer in the documentation and/or other materials provided with the distribution.
// //
// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS // THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT // "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR // LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR // A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
// CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, // CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
// EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, // EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
// PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR // PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
// PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF // PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
// LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING // LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
// NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS // NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
// SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. // SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
// //
// This software uses the CLAP .NET Command-Line Parser, Copyright © 2011 Adrian Aisemberg, SharpRegion. // This software uses the CLAP .NET Command-Line Parser, Copyright © 2011 Adrian Aisemberg, SharpRegion.
// Used under license. // Used under license.
// //
// This software uses the MiniJSON .NET JSON Parser, Copyright © 2013 Calvin Rien. Used under license. // This software uses the MiniJSON .NET JSON Parser, Copyright © 2013 Calvin Rien. Used under license.
// //
// This software uses the FormUpload multipart/form-data library, // This software uses the FormUpload multipart/form-data library,
// http://www.briangrinstead.com/blog/multipart-form-post-in-c. // http://www.briangrinstead.com/blog/multipart-form-post-in-c.
// //
  // KerbalStuff is copyright © 2014 Drew DeVault. Used under license.
  //
   
using System; using System;
using System.Collections.Generic; using System.Collections.Generic;
using System.Linq; using System.Linq;
   
namespace KerbalStuff namespace KerbalStuff
{ {
/// <summary> /// <summary>
/// Class representing a KerbalStuff Mod as presented by the KerbalStuff API. /// Class representing a KerbalStuff Mod as presented by the KerbalStuff API.
/// </summary> /// </summary>
public class Mod public class Mod
{ {
/// <summary> /// <summary>
/// The number of times all versions of this Mod have been downloaded. /// The number of times all versions of this Mod have been downloaded.
/// </summary> /// </summary>
public long Downloads public long Downloads
{ {
get; get;
private set; private set;
} }
   
/// <summary> /// <summary>
/// The name of the Mod. /// The name of the Mod.
/// </summary> /// </summary>
public string Name public string Name
{ {
get; get;
private set; private set;
} }
   
/// <summary> /// <summary>
/// The number of followers subscribed to this Mod on KerbalStuff. /// The number of followers subscribed to this Mod on KerbalStuff.
/// </summary> /// </summary>
public long Followers public long Followers
{ {
get; get;
private set; private set;
} }
   
/// <summary> /// <summary>
/// The name of the <see cref="KerbalStuff.User"/> that authors this Mod. /// The name of the <see cref="KerbalStuff.User"/> that authors this Mod.
/// </summary> /// </summary>
public string Author public string Author
{ {
get; get;
private set; private set;
} }
   
/// <summary> /// <summary>
/// The ID of the default <see cref="KerbalStuff.ModVersion"/> of this Mod. /// The ID of the default <see cref="KerbalStuff.ModVersion"/> of this Mod.
/// </summary> /// </summary>
public long DefaultVersionId public long DefaultVersionId
{ {
get; get;
private set; private set;
} }
   
/// <summary> /// <summary>
/// A read-only list of the available versions of this Mod. /// A read-only list of the available versions of this Mod.
/// </summary> /// </summary>
/// <seealso cref="KerbalStuff.ModVersion"/> /// <seealso cref="KerbalStuff.ModVersion"/>
public IList<ModVersion> Versions public IList<ModVersion> Versions
{ {
get get
{ {
return (versions == null) ? null : versions.AsReadOnly(); return (versions == null) ? null : versions.AsReadOnly();
} }
} }
   
/// <summary> /// <summary>
/// The Id of this Mod on KerbalStuff. /// The Id of this Mod on KerbalStuff.
/// </summary> /// </summary>
public long Id public long Id
{ {
get; get;
private set; private set;
} }
   
/// <summary> /// <summary>
  /// The URI of the Mod's background image, relative to the mediacru.sh CDN root.
  /// </summary>
  public string Background
  {
  get;
  private set;
  }
   
  /// <summary>
  /// Gets the vertical offset of the Mod's background image, in pixels.
  /// </summary>
  /// <value>The background vertical offset.</value>
  public long BackgroundVerticalOffset
  {
  get;
  private set;
  }
   
  /// <summary>
/// A short (1000 characters or less) description of this Mod. /// A short (1000 characters or less) description of this Mod.
/// </summary> /// </summary>
public string ShortDescription public string ShortDescription
{ {
get; get;
private set; private set;
} }
   
/// <summary> /// <summary>
/// The name or title (128 characters or less) of the License under which this Mod is released. /// The name or title (128 characters or less) of the License under which this Mod is released.
/// </summary> /// </summary>
public string License public string License
{ {
get; get;
private set; private set;
} }
   
private List<ModVersion> versions; private List<ModVersion> versions;
   
/// <summary> /// <summary>
/// Initializes a new instance of the <see cref="KerbalStuff.Mod"/> class from a Dictionary of JSON objects. /// Initializes a new instance of the <see cref="KerbalStuff.Mod"/> class from a Dictionary of JSON objects.
/// </summary> /// </summary>
/// <param name="jsonDict">Dictionary containing the deserialized JSON response from KerbalStuff.</param> /// <param name="jsonDict">Dictionary containing the deserialized JSON response from KerbalStuff.</param>
public Mod(Dictionary<string, object> jsonDict) : this() public Mod(Dictionary<string, object> jsonDict) : this()
{ {
this.Downloads = (long)jsonDict["downloads"]; this.Downloads = (long)jsonDict["downloads"];
this.Name = (string)jsonDict["name"]; this.Name = (string)jsonDict["name"];
this.Followers = (long)jsonDict["followers"]; this.Followers = (long)jsonDict["followers"];
this.Author = (string)jsonDict["author"]; this.Author = (string)jsonDict["author"];
this.DefaultVersionId = (long)jsonDict["default_version_id"]; this.DefaultVersionId = (long)jsonDict["default_version_id"];
this.Id = (long)jsonDict["id"]; this.Id = (long)jsonDict["id"];
this.ShortDescription = (string)jsonDict["short_description"]; this.ShortDescription = (string)jsonDict["short_description"];
  this.Background = (string)jsonDict["background"];
  this.BackgroundVerticalOffset = (long)jsonDict["bg_offset_y"];
   
if (jsonDict.ContainsKey("versions")) if (jsonDict.ContainsKey("versions"))
{ {
foreach (var ver in (jsonDict["versions"] as List<object>)) foreach (var ver in (jsonDict["versions"] as List<object>))
{ {
if (ver is Dictionary<string, object>) if (ver is Dictionary<string, object>)
{ {
this.versions.Add(new ModVersion(ver as Dictionary<string, object>)); this.versions.Add(new ModVersion(ver as Dictionary<string, object>));
} }
} }
} }
} }
   
/// <summary> /// <summary>
/// Initializes a new instance of the <see cref="KerbalStuff.Mod"/> class from strings describing the Mod. /// Initializes a new instance of the <see cref="KerbalStuff.Mod"/> class from strings describing the Mod.
/// Useful for creating new Mods for upload. /// Useful for creating new Mods for upload.
/// </summary> /// </summary>
/// <seealso cref="KerbalStuff.KerbalStuff.Create"/> /// <seealso cref="KerbalStuff.KerbalStuff.Create"/>
/// <param name="name">The name of the Mod</param> /// <param name="name">The name of the Mod</param>
/// <param name="shortDescription">A short (1000 characters or less) description of this Mod.</param> /// <param name="shortDescription">A short (1000 characters or less) description of this Mod.</param>
/// <param name="version">Version.</param> /// <param name="version">Version.</param>
/// <param name="kspVersion">Ksp version.</param> /// <param name="kspVersion">Ksp version.</param>
/// <param name="license">The name or title (128 characters or less) of the License under which this Mod is released.</param> /// <param name="license">The name or title (128 characters or less) of the License under which this Mod is released.</param>
public Mod(string name, string shortDescription, string version, string kspVersion, string license) : this() public Mod(string name, string shortDescription, string version, string kspVersion, string license) : this()
{ {
this.Name = name; this.Name = name;
this.ShortDescription = shortDescription; this.ShortDescription = shortDescription;
this.License = license; this.License = license;
   
this.versions.Add(new ModVersion(version, kspVersion)); this.versions.Add(new ModVersion(version, kspVersion));
} }
   
private Mod() private Mod()
{ {
this.versions = new List<ModVersion>(); this.versions = new List<ModVersion>();
} }
   
/// <summary> /// <summary>
/// Returns a <see cref="System.String"/> that represents the current <see cref="KerbalStuff.Mod"/>. /// Returns a <see cref="System.String"/> that represents the current <see cref="KerbalStuff.Mod"/>.
/// </summary> /// </summary>
public override string ToString() public override string ToString()
{ {
return string.Format( return string.Format(
"Mod: {1}\n" + "Mod: {1}\n" +
"id: {6}\n" + "id: {6}\n" +
"author: {3}\n" + "author: {3}\n" +
"downloads: {0}\n" + "downloads: {0}\n" +
"followers: {2}\n" + "followers: {2}\n" +
"short_description: {7}\n" + "short_description: {7}\n" +
"default_version_id: {4}\n" + "default_version_id: {4}\n" +
"versions:\n[\n{5}\n]\n", "versions:\n[\n{5}\n]\n",
Downloads, Downloads,
Name, Name,
Followers, Followers,
Author, Author,
DefaultVersionId, DefaultVersionId,
string.Join("\n", Versions.Select(v => v.ToString()).ToArray()), string.Join("\n", Versions.Select(v => v.ToString()).ToArray()),
Id, Id,
ShortDescription ShortDescription
); );
} }
} }
   
/// <summary> /// <summary>
/// Class representing a single version of a KerbalStuff Mod as presented by the KerbalStuff API. /// Class representing a single version of a KerbalStuff Mod as presented by the KerbalStuff API.
/// </summary> /// </summary>
public class ModVersion public class ModVersion
{ {
/// <summary> /// <summary>
/// An optional log describing the changes made in this ModVersion /// An optional log describing the changes made in this ModVersion
/// </summary> /// </summary>
public string ChangeLog public string ChangeLog
{ {
get; get;
private set; private set;
} }
   
/// <summary> /// <summary>
/// The primary version of KSP for which this ModVersion was developed. /// The primary version of KSP for which this ModVersion was developed.
/// </summary> /// </summary>
public string KspVersion public string KspVersion
{ {
get; get;
private set; private set;
} }
   
/// <summary> /// <summary>
/// The path of the download archive for this ModVersion, relative to the KerbalStuff /// The path of the download archive for this ModVersion, relative to the KerbalStuff
/// root. /// root.
/// </summary> /// </summary>
/// <seealso cref="KerbalStuff.KerbalStuff.RootUri"/> /// <seealso cref="KerbalStuff.KerbalStuff.RootUri"/>
public string DownloadPath public string DownloadPath
{ {
get; get;
private set; private set;
} }
   
/// <summary> /// <summary>
/// The Id of this ModVersion on KerbalStuff. /// The Id of this ModVersion on KerbalStuff.
/// </summary> /// </summary>
public long Id public long Id
{ {
get; get;
private set; private set;
} }
   
/// <summary> /// <summary>
/// The human-friendly (or not) name or number of this ModVersion. /// The human-friendly (or not) name or number of this ModVersion.
/// </summary> /// </summary>
/// <value>The friendly version.</value> /// <value>The friendly version.</value>
public string FriendlyVersion public string FriendlyVersion
{ {
get; get;
private set; private set;
} }
   
/// <summary> /// <summary>
/// Initializes a new instance of the <see cref="KerbalStuff.ModVersion"/> class from a Dictionary of JSON /// Initializes a new instance of the <see cref="KerbalStuff.ModVersion"/> class from a Dictionary of JSON
/// objects. /// objects.
/// </summary> /// </summary>
/// <param name="jsonDict">Dictionary containing the deserialized JSON response from KerbalStuff.</param> /// <param name="jsonDict">Dictionary containing the deserialized JSON response from KerbalStuff.</param>
public ModVersion(Dictionary<string, object> jsonDict) : this() public ModVersion(Dictionary<string, object> jsonDict) : this()
{ {
this.ChangeLog = (string)jsonDict["changelog"]; this.ChangeLog = (string)jsonDict["changelog"];
this.KspVersion = (string)jsonDict["ksp_version"]; this.KspVersion = (string)jsonDict["ksp_version"];
this.DownloadPath = (string)jsonDict["download_path"]; this.DownloadPath = (string)jsonDict["download_path"];
this.Id = (long)jsonDict["id"]; this.Id = (long)jsonDict["id"];
this.FriendlyVersion = (string)jsonDict["friendly_version"]; this.FriendlyVersion = (string)jsonDict["friendly_version"];
} }
   
/// <summary> /// <summary>
/// Initializes a new instance of the <see cref="KerbalStuff.ModVersion"/> class from strings describing the /// Initializes a new instance of the <see cref="KerbalStuff.ModVersion"/> class from strings describing the
/// Version. /// Version.
/// </summary> /// </summary>
/// <param name="version">The human-friendly (or not) name or number of this ModVersion.</param> /// <param name="version">The human-friendly (or not) name or number of this ModVersion.</param>
/// <param name="kspVersion">The primary version of KSP for which this ModVersion was developed.</param> /// <param name="kspVersion">The primary version of KSP for which this ModVersion was developed.</param>
/// <param name="changeLog">An optional log describing the changes made in this ModVersion </param> /// <param name="changeLog">An optional log describing the changes made in this ModVersion </param>
public ModVersion(string version, string kspVersion, string changeLog) : this(version, kspVersion) public ModVersion(string version, string kspVersion, string changeLog) : this(version, kspVersion)
{ {
this.ChangeLog = changeLog; this.ChangeLog = changeLog;
} }
   
/// <summary> /// <summary>
/// Initializes a new instance of the <see cref="KerbalStuff.ModVersion"/> class from strings describing the /// Initializes a new instance of the <see cref="KerbalStuff.ModVersion"/> class from strings describing the
/// Version. /// Version.
/// </summary> /// </summary>
/// <param name="version">The human-friendly (or not) name or number of this ModVersion.</param> /// <param name="version">The human-friendly (or not) name or number of this ModVersion.</param>
/// <param name="kspVersion">The primary version of KSP for which this ModVersion was developed.</param> /// <param name="kspVersion">The primary version of KSP for which this ModVersion was developed.</param>
public ModVersion(string version, string kspVersion) : this() public ModVersion(string version, string kspVersion) : this()
{ {
this.FriendlyVersion = version; this.FriendlyVersion = version;
this.KspVersion = kspVersion; this.KspVersion = kspVersion;
} }
   
private ModVersion() {} private ModVersion() {}
   
/// <summary> /// <summary>
/// Returns a <see cref="System.String"/> that represents the current <see cref="KerbalStuff.ModVersion"/>. /// Returns a <see cref="System.String"/> that represents the current <see cref="KerbalStuff.ModVersion"/>.
/// </summary> /// </summary>
public override string ToString() public override string ToString()
{ {
return string.Format( return string.Format(
"ModVersion {4}:\nid: {3}\nksp_version: {1}\ndownload_path: {2}\nchangelog: {0}", "ModVersion {4}:\nid: {3}\nksp_version: {1}\ndownload_path: {2}\nchangelog: {0}",
ChangeLog, ChangeLog,
KspVersion, KspVersion,
DownloadPath, DownloadPath,
Id, Id,
FriendlyVersion FriendlyVersion
); );
} }
} }
} }
// KerbalStuffWrapper // KerbalStuffWrapper
// //
// Author: // Author:
// toadicus // toadicus
// //
// Copyright © 2014, toadicus // Copyright © 2014, toadicus
// //
// All rights reserved. // All rights reserved.
// //
// Redistribution and use in source and binary forms, with or without modification, are permitted provided that the // Redistribution and use in source and binary forms, with or without modification, are permitted provided that the
// following conditions are met: // following conditions are met:
// //
// * Redistributions of source code must retain the above copyright notice, this list of conditions and the // * Redistributions of source code must retain the above copyright notice, this list of conditions and the
// following disclaimer. // following disclaimer.
// * Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the // * Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the
// following disclaimer in the documentation and/or other materials provided with the distribution. // following disclaimer in the documentation and/or other materials provided with the distribution.
// //
// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS // THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT // "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR // LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR // A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
// CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, // CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
// EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, // EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
// PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR // PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
// PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF // PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
// LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING // LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
// NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS // NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
// SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. // SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
// //
// This software uses the CLAP .NET Command-Line Parser, Copyright © 2011 Adrian Aisemberg, SharpRegion. // This software uses the CLAP .NET Command-Line Parser, Copyright © 2011 Adrian Aisemberg, SharpRegion.
// Used under license. // Used under license.
// //
// This software uses the MiniJSON .NET JSON Parser, Copyright © 2013 Calvin Rien. Used under license. // This software uses the MiniJSON .NET JSON Parser, Copyright © 2013 Calvin Rien. Used under license.
// //
// This software uses the FormUpload multipart/form-data library, // This software uses the FormUpload multipart/form-data library,
// http://www.briangrinstead.com/blog/multipart-form-post-in-c. // http://www.briangrinstead.com/blog/multipart-form-post-in-c.
  //
  // KerbalStuff is copyright © 2014 Drew DeVault. Used under license.
// //
   
using System; using System;
using System.Collections.Generic; using System.Collections.Generic;
using System.Linq; using System.Linq;
   
namespace KerbalStuff namespace KerbalStuff
{ {
/// <summary> /// <summary>
/// Class representing a KerbalStuff user as presented by the KerbalStuff API. /// Class representing a KerbalStuff user as presented by the KerbalStuff API.
/// </summary> /// </summary>
public class User public class User
{ {
/// <summary> /// <summary>
/// The user's KerbalStuff username. /// The user's KerbalStuff username.
/// </summary> /// </summary>
public string Username public string Username
{ {
get; get;
private set; private set;
} }
   
/// <summary> /// <summary>
/// The user's Twitter username. /// The user's Twitter username.
/// </summary> /// </summary>
public string TwitterUsername public string TwitterUsername
{ {
get; get;
private set; private set;
} }
   
/// <summary> /// <summary>
/// A read-only list of <see cref="KerbalStuff.Mod"/> objects maintained by the user. /// A read-only list of <see cref="KerbalStuff.Mod"/> objects maintained by the user.
/// </summary> /// </summary>
public IList<Mod> Mods public IList<Mod> Mods
{ {
get get
{ {
return (this.mods == null) ? null : this.mods.AsReadOnly(); return (this.mods == null) ? null : this.mods.AsReadOnly();
} }
} }
   
/// <summary> /// <summary>
/// The user's Reddit username. /// The user's Reddit username.
/// </summary> /// </summary>
public string RedditUsername public string RedditUsername
{ {
get; get;
private set; private set;
} }
   
/// <summary> /// <summary>
/// The user's IRC nickname. /// The user's IRC nickname.
/// </summary> /// </summary>
public string IrcNick public string IrcNick
{ {
get; get;
private set; private set;
} }
   
/// <summary> /// <summary>
/// The user's profile description. /// The user's profile description.
/// </summary> /// </summary>
public string Description public string Description
{ {
get; get;
private set; private set;
} }
   
/// <summary> /// <summary>
/// The user's KSP Forum username. /// The user's KSP Forum username.
/// </summary> /// </summary>
public string ForumUsername public string ForumUsername
{ {
get; get;
private set; private set;
} }
   
private List<Mod> mods; private List<Mod> mods;
   
/// <summary> /// <summary>
/// Initializes a new instance of the <see cref="KerbalStuff.User"/> class from a dictionary of JSON objects. /// Initializes a new instance of the <see cref="KerbalStuff.User"/> class from a dictionary of JSON objects.
/// </summary> /// </summary>
/// <param name="jsonDict">Dictionary containing the deserialized JSON response from KerbalStuff.</param> /// <param name="jsonDict">Dictionary containing the deserialized JSON response from KerbalStuff.</param>
public User(Dictionary<string, object> jsonDict) : this() public User(Dictionary<string, object> jsonDict) : this()
{ {
this.Username = (string)jsonDict["username"]; this.Username = (string)jsonDict["username"];
this.TwitterUsername = (string)jsonDict["twitterUsername"]; this.TwitterUsername = (string)jsonDict["twitterUsername"];
this.RedditUsername = (string)jsonDict["redditUsername"]; this.RedditUsername = (string)jsonDict["redditUsername"];
this.IrcNick = (string)jsonDict["ircNick"]; this.IrcNick = (string)jsonDict["ircNick"];
this.ForumUsername = (string)jsonDict["forumUsername"]; this.ForumUsername = (string)jsonDict["forumUsername"];
   
this.Description = (string)jsonDict["description"]; this.Description = (string)jsonDict["description"];
   
this.mods = new List<Mod>(); this.mods = new List<Mod>();
   
foreach (object modObj in (jsonDict["mods"] as List<object>)) foreach (object modObj in (jsonDict["mods"] as List<object>))
{ {
this.mods.Add(new Mod(modObj as Dictionary<string, object>)); this.mods.Add(new Mod(modObj as Dictionary<string, object>));
} }
} }
   
/// <summary> /// <summary>
/// Initializes a new instance of the <see cref="KerbalStuff.User"/> class from an ambiguously-typed dictionary /// Initializes a new instance of the <see cref="KerbalStuff.User"/> class from an ambiguously-typed dictionary
/// of JSON objects. /// of JSON objects.
/// </summary> /// </summary>
/// <param name="jsonDict">Dictionary containing the JSON response from KerbalStuff.</param> /// <param name="jsonObj">Dictionary containing the JSON response from KerbalStuff.</param>
public User(object jsonObj) : this((Dictionary<string, object>)jsonObj) {} public User(object jsonObj) : this((Dictionary<string, object>)jsonObj) {}
   
private User() {} private User() {}
   
/// <summary> /// <summary>
/// Returns a <see cref="System.String"/> that represents the current <see cref="KerbalStuff.User"/>. /// Returns a <see cref="System.String"/> that represents the current <see cref="KerbalStuff.User"/>.
/// </summary> /// </summary>
/// <returns>A <see cref="System.String"/> that represents the current <see cref="KerbalStuff.User"/>.</returns> /// <returns>A <see cref="System.String"/> that represents the current <see cref="KerbalStuff.User"/>.</returns>
public override string ToString() public override string ToString()
{ {
return string.Format( return string.Format(
"User: username={0}, twitterUsername={1}, redditUsername={3}, ircNick={4}, description={5}, forumUsername={6}\nmods:\n{2}", "User: username={0}, twitterUsername={1}, redditUsername={3}, ircNick={4}, description={5}, forumUsername={6}\nmods:\n{2}",
Username, Username,
TwitterUsername, TwitterUsername,
string.Join( string.Join(
"\n", "\n",
Mods.Select(m => m.ToString()).ToArray() Mods.Select(m => m.ToString()).ToArray()
), ),
RedditUsername, RedditUsername,
IrcNick, IrcNick,
Description, Description,
ForumUsername ForumUsername
); );
} }
} }
} }
 
Microsoft Visual Studio Solution File, Format Version 11.00 Microsoft Visual Studio Solution File, Format Version 11.00
# Visual Studio 2010 # Visual Studio 2010
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "KerbalStuffWrapper", "KerbalStuffWrapper\KerbalStuffWrapper.csproj", "{1E93CDA7-56A8-410F-A5A2-0ABB9210CA58}" Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "KerbalStuffWrapper", "KerbalStuffWrapper\KerbalStuffWrapper.csproj", "{1E93CDA7-56A8-410F-A5A2-0ABB9210CA58}"
EndProject EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "KerbalStuffReadOnly", "KerbalStuffReadOnly\KerbalStuffReadOnly.csproj", "{720FA70F-D785-48ED-BA45-561921E0EEEC}" Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "KerbalStuffReadOnly", "KerbalStuffReadOnly\KerbalStuffReadOnly.csproj", "{720FA70F-D785-48ED-BA45-561921E0EEEC}"
EndProject EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "KerbalStuff", "KerbalStuff\KerbalStuff.csproj", "{0CA12F64-AD3B-41E6-8A35-8781A8DAD1C1}" Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "KerbalStuff", "KerbalStuff\KerbalStuff.csproj", "{0CA12F64-AD3B-41E6-8A35-8781A8DAD1C1}"
EndProject EndProject
Project("{9344BDBB-3E7F-41FC-A0DD-8665D75EE146}") = "Packages", "Packages.mdproj", "{8D7818A4-B406-46CD-855D-88457014F717}" Project("{9344BDBB-3E7F-41FC-A0DD-8665D75EE146}") = "Packages", "Packages.mdproj", "{2CC7FF76-5C09-4329-92C1-DF48B954F1CE}"
EndProject EndProject
Global Global
GlobalSection(SolutionConfigurationPlatforms) = preSolution GlobalSection(SolutionConfigurationPlatforms) = preSolution
Release|Any CPU = Release|Any CPU Release|Any CPU = Release|Any CPU
Debug|Any CPU = Debug|Any CPU Debug|Any CPU = Debug|Any CPU
EndGlobalSection EndGlobalSection
GlobalSection(ProjectConfigurationPlatforms) = postSolution GlobalSection(ProjectConfigurationPlatforms) = postSolution
{0CA12F64-AD3B-41E6-8A35-8781A8DAD1C1}.Debug|Any CPU.ActiveCfg = Debug|Any CPU {0CA12F64-AD3B-41E6-8A35-8781A8DAD1C1}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{0CA12F64-AD3B-41E6-8A35-8781A8DAD1C1}.Debug|Any CPU.Build.0 = Debug|Any CPU {0CA12F64-AD3B-41E6-8A35-8781A8DAD1C1}.Debug|Any CPU.Build.0 = Debug|Any CPU
{0CA12F64-AD3B-41E6-8A35-8781A8DAD1C1}.Release|Any CPU.ActiveCfg = Release|Any CPU {0CA12F64-AD3B-41E6-8A35-8781A8DAD1C1}.Release|Any CPU.ActiveCfg = Release|Any CPU
{0CA12F64-AD3B-41E6-8A35-8781A8DAD1C1}.Release|Any CPU.Build.0 = Release|Any CPU {0CA12F64-AD3B-41E6-8A35-8781A8DAD1C1}.Release|Any CPU.Build.0 = Release|Any CPU
{1E93CDA7-56A8-410F-A5A2-0ABB9210CA58}.Debug|Any CPU.ActiveCfg = Debug|Any CPU {1E93CDA7-56A8-410F-A5A2-0ABB9210CA58}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{1E93CDA7-56A8-410F-A5A2-0ABB9210CA58}.Debug|Any CPU.Build.0 = Debug|Any CPU {1E93CDA7-56A8-410F-A5A2-0ABB9210CA58}.Debug|Any CPU.Build.0 = Debug|Any CPU
{1E93CDA7-56A8-410F-A5A2-0ABB9210CA58}.Release|Any CPU.ActiveCfg = Release|Any CPU {1E93CDA7-56A8-410F-A5A2-0ABB9210CA58}.Release|Any CPU.ActiveCfg = Release|Any CPU
{1E93CDA7-56A8-410F-A5A2-0ABB9210CA58}.Release|Any CPU.Build.0 = Release|Any CPU {1E93CDA7-56A8-410F-A5A2-0ABB9210CA58}.Release|Any CPU.Build.0 = Release|Any CPU
  {2CC7FF76-5C09-4329-92C1-DF48B954F1CE}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
  {2CC7FF76-5C09-4329-92C1-DF48B954F1CE}.Release|Any CPU.ActiveCfg = Release|Any CPU
{720FA70F-D785-48ED-BA45-561921E0EEEC}.Debug|Any CPU.ActiveCfg = Debug|Any CPU {720FA70F-D785-48ED-BA45-561921E0EEEC}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{720FA70F-D785-48ED-BA45-561921E0EEEC}.Debug|Any CPU.Build.0 = Debug|Any CPU {720FA70F-D785-48ED-BA45-561921E0EEEC}.Debug|Any CPU.Build.0 = Debug|Any CPU
{720FA70F-D785-48ED-BA45-561921E0EEEC}.Release|Any CPU.ActiveCfg = Release|Any CPU {720FA70F-D785-48ED-BA45-561921E0EEEC}.Release|Any CPU.ActiveCfg = Release|Any CPU
{720FA70F-D785-48ED-BA45-561921E0EEEC}.Release|Any CPU.Build.0 = Release|Any CPU {720FA70F-D785-48ED-BA45-561921E0EEEC}.Release|Any CPU.Build.0 = Release|Any CPU
{8D7818A4-B406-46CD-855D-88457014F717}.Debug|Any CPU.ActiveCfg = Debug|Any CPU  
{8D7818A4-B406-46CD-855D-88457014F717}.Release|Any CPU.ActiveCfg = Release|Any CPU  
EndGlobalSection EndGlobalSection
GlobalSection(MonoDevelopProperties) = preSolution GlobalSection(MonoDevelopProperties) = preSolution
StartupItem = KerbalStuffWrapper\KerbalStuffWrapper.csproj StartupItem = KerbalStuffWrapper\KerbalStuffWrapper.csproj
Policies = $0 Policies = $0
$0.DotNetNamingPolicy = $1 $0.DotNetNamingPolicy = $1
$1.DirectoryNamespaceAssociation = None $1.DirectoryNamespaceAssociation = None
$1.ResourceNamePolicy = FileFormatDefault $1.ResourceNamePolicy = FileFormatDefault
$0.TextStylePolicy = $2 $0.TextStylePolicy = $2
$2.inheritsSet = null $2.inheritsSet = null
$2.scope = text/x-csharp $2.scope = text/x-csharp
$0.CSharpFormattingPolicy = $3 $0.CSharpFormattingPolicy = $3
$3.IndentSwitchBody = True $3.IndentSwitchBody = True
$3.AnonymousMethodBraceStyle = NextLine $3.AnonymousMethodBraceStyle = NextLine
$3.PropertyBraceStyle = NextLine $3.PropertyBraceStyle = NextLine
$3.EventBraceStyle = NextLine $3.EventBraceStyle = NextLine
$3.StatementBraceStyle = NextLine $3.StatementBraceStyle = NextLine
$3.ElseNewLinePlacement = NewLine $3.ElseNewLinePlacement = NewLine
$3.CatchNewLinePlacement = NewLine $3.CatchNewLinePlacement = NewLine
$3.FinallyNewLinePlacement = NewLine $3.FinallyNewLinePlacement = NewLine
$3.WhileNewLinePlacement = NewLine $3.WhileNewLinePlacement = NewLine
$3.BeforeMethodDeclarationParentheses = False $3.BeforeMethodDeclarationParentheses = False
$3.BeforeMethodCallParentheses = False $3.BeforeMethodCallParentheses = False
$3.BeforeConstructorDeclarationParentheses = False $3.BeforeConstructorDeclarationParentheses = False
$3.BeforeIndexerDeclarationBracket = False $3.BeforeIndexerDeclarationBracket = False
$3.BeforeDelegateDeclarationParentheses = False $3.BeforeDelegateDeclarationParentheses = False
$3.AfterDelegateDeclarationParameterComma = True $3.AfterDelegateDeclarationParameterComma = True
$3.NewParentheses = False $3.NewParentheses = False
$3.SpacesBeforeBrackets = False $3.SpacesBeforeBrackets = False
$3.BlankLinesBetweenEventFields = 1 $3.BlankLinesBetweenEventFields = 1
$3.inheritsSet = Mono $3.inheritsSet = Mono
$3.inheritsScope = text/x-csharp $3.inheritsScope = text/x-csharp
$3.scope = text/x-csharp $3.scope = text/x-csharp
$0.StandardHeader = $4 $0.StandardHeader = $4
$4.Text = @KerbalStuffWrapper\n\nAuthor:\n\t${AuthorName}\n\nCopyright © ${Year}, ${AuthorName}\n\nAll rights reserved.\n\nRedistribution and use in source and binary forms, with or without modification, are permitted provided that the\nfollowing conditions are met:\n\n\t* Redistributions of source code must retain the above copyright notice, this list of conditions and the\n\t following disclaimer.\n\t* Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the\n\t following disclaimer in the documentation and/or other materials provided with the distribution.\n\t* Neither the name of the author nor the names of other contributors may be used to endorse or promote products\n\t derived from this software without specific prior written permission.\n\nTHIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS\n"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT\nLIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR\nA PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR\nCONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,\nEXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,\nPROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR\nPROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF\nLIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING\nNEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS\nSOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.\n\nThis software uses the CLAP .NET Command-Line Parser, Copyright © 2011 Adrian Aisemberg, SharpRegion.\nUsed under license.\n\nThis software uses the MiniJSON .NET JSON Parser, Copyright © 2013 Calvin Rien. Used under license.\n\nThis software uses the FormUpload multipart/form-data library,\nhttp://www.briangrinstead.com/blog/multipart-form-post-in-c.\n $4.Text = @KerbalStuffWrapper\n\nAuthor:\n\t${AuthorName}\n\nCopyright © ${Year}, ${AuthorName}\n\nAll rights reserved.\n\nRedistribution and use in source and binary forms, with or without modification, are permitted provided that the\nfollowing conditions are met:\n\n\t* Redistributions of source code must retain the above copyright notice, this list of conditions and the\n\t following disclaimer.\n\t* Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the\n\t following disclaimer in the documentation and/or other materials provided with the distribution.\n\t* Neither the name of the author nor the names of other contributors may be used to endorse or promote products\n\t derived from this software without specific prior written permission.\n\nTHIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS\n"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT\nLIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR\nA PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR\nCONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,\nEXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,\nPROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR\nPROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF\nLIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING\nNEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS\nSOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.\n\nThis software uses the CLAP .NET Command-Line Parser, Copyright © 2011 Adrian Aisemberg, SharpRegion.\nUsed under license.\n\nThis software uses the MiniJSON .NET JSON Parser, Copyright © 2013 Calvin Rien. Used under license.\n\nThis software uses the FormUpload multipart/form-data library,\nhttp://www.briangrinstead.com/blog/multipart-form-post-in-c.\n
$4.IncludeInNewFiles = True $4.IncludeInNewFiles = True
$0.TextStylePolicy = $5 $0.TextStylePolicy = $5
$5.FileWidth = 120 $5.FileWidth = 120
$5.TabsToSpaces = False $5.TabsToSpaces = False
$5.EolMarker = Unix $5.EolMarker = Unix
$5.inheritsSet = VisualStudio $5.inheritsSet = VisualStudio
$5.inheritsScope = text/plain $5.inheritsScope = text/plain
$5.scope = text/plain $5.scope = text/plain
$0.NameConventionPolicy = $6 $0.NameConventionPolicy = $6
$6.Rules = $7 $6.Rules = $7
$7.NamingRule = $8 $7.NamingRule = $8
$8.Name = Namespaces $8.Name = Namespaces
$8.AffectedEntity = Namespace $8.AffectedEntity = Namespace
$8.VisibilityMask = VisibilityMask $8.VisibilityMask = VisibilityMask
$8.NamingStyle = PascalCase $8.NamingStyle = PascalCase
$8.IncludeInstanceMembers = True $8.IncludeInstanceMembers = True
$8.IncludeStaticEntities = True $8.IncludeStaticEntities = True
$7.NamingRule = $9 $7.NamingRule = $9
$9.Name = Types $9.Name = Types
$9.AffectedEntity = Class, Struct, Enum, Delegate $9.AffectedEntity = Class, Struct, Enum, Delegate
$9.VisibilityMask = Public $9.VisibilityMask = Public
$9.NamingStyle = PascalCase $9.NamingStyle = PascalCase
$9.IncludeInstanceMembers = True $9.IncludeInstanceMembers = True
$9.IncludeStaticEntities = True $9.IncludeStaticEntities = True
$7.NamingRule = $10 $7.NamingRule = $10
$10.Name = Interfaces $10.Name = Interfaces
$10.RequiredPrefixes = $11 $10.RequiredPrefixes = $11
$11.String = I $11.String = I
$10.AffectedEntity = Interface $10.AffectedEntity = Interface
$10.VisibilityMask = Public $10.VisibilityMask = Public
$10.NamingStyle = PascalCase $10.NamingStyle = PascalCase
$10.IncludeInstanceMembers = True $10.IncludeInstanceMembers = True
$10.IncludeStaticEntities = True $10.IncludeStaticEntities = True
$7.NamingRule = $12 $7.NamingRule = $12
$12.Name = Attributes $12.Name = Attributes
$12.RequiredSuffixes = $13 $12.RequiredSuffixes = $13
$13.String = Attribute $13.String = Attribute
$12.AffectedEntity = CustomAttributes $12.AffectedEntity = CustomAttributes
$12.VisibilityMask = Public $12.VisibilityMask = Public
$12.NamingStyle = PascalCase $12.NamingStyle = PascalCase
$12.IncludeInstanceMembers = True $12.IncludeInstanceMembers = True
$12.IncludeStaticEntities = True $12.IncludeStaticEntities = True
$7.NamingRule = $14 $7.NamingRule = $14
$14.Name = Event Arguments $14.Name = Event Arguments
$14.RequiredSuffixes = $15 $14.RequiredSuffixes = $15
$15.String = EventArgs $15.String = EventArgs
$14.AffectedEntity = CustomEventArgs $14.AffectedEntity = CustomEventArgs
$14.VisibilityMask = Public $14.VisibilityMask = Public
$14.NamingStyle = PascalCase $14.NamingStyle = PascalCase
$14.IncludeInstanceMembers = True $14.IncludeInstanceMembers = True
$14.IncludeStaticEntities = True $14.IncludeStaticEntities = True
$7.NamingRule = $16 $7.NamingRule = $16
$16.Name = Exceptions $16.Name = Exceptions
$16.RequiredSuffixes = $17 $16.RequiredSuffixes = $17
$17.String = Exception $17.String = Exception
$16.AffectedEntity = CustomExceptions $16.AffectedEntity = CustomExceptions
$16.VisibilityMask = VisibilityMask $16.VisibilityMask = VisibilityMask
$16.NamingStyle = PascalCase $16.NamingStyle = PascalCase
$16.IncludeInstanceMembers = True $16.IncludeInstanceMembers = True
$16.IncludeStaticEntities = True $16.IncludeStaticEntities = True
$7.NamingRule = $18 $7.NamingRule = $18
$18.Name = Methods $18.Name = Methods
$18.AffectedEntity = Methods $18.AffectedEntity = Methods
$18.VisibilityMask = Protected, Public $18.VisibilityMask = Protected, Public
$18.NamingStyle = PascalCase $18.NamingStyle = PascalCase
$18.IncludeInstanceMembers = True $18.IncludeInstanceMembers = True
$18.IncludeStaticEntities = True $18.IncludeStaticEntities = True
$7.NamingRule = $19 $7.NamingRule = $19
$19.Name = Static Readonly Fields $19.Name = Static Readonly Fields
$19.AffectedEntity = ReadonlyField $19.AffectedEntity = ReadonlyField
$19.VisibilityMask = Protected, Public $19.VisibilityMask = Protected, Public
$19.NamingStyle = PascalCase $19.NamingStyle = PascalCase
$19.IncludeInstanceMembers = False $19.IncludeInstanceMembers = False
$19.IncludeStaticEntities = True $19.IncludeStaticEntities = True
$7.NamingRule = $20 $7.NamingRule = $20
$20.Name = Fields $20.Name = Fields
$20.AffectedEntity = Field $20.AffectedEntity = Field
$20.VisibilityMask = Protected, Public $20.VisibilityMask = Protected, Public
$20.NamingStyle = PascalCase $20.NamingStyle = PascalCase
$20.IncludeInstanceMembers = True $20.IncludeInstanceMembers = True
$20.IncludeStaticEntities = True $20.IncludeStaticEntities = True
$7.NamingRule = $21 $7.NamingRule = $21
$21.Name = ReadOnly Fields $21.Name = ReadOnly Fields
$21.AffectedEntity = ReadonlyField $21.AffectedEntity = ReadonlyField
$21.VisibilityMask = Protected, Public $21.VisibilityMask = Protected, Public
$21.NamingStyle = PascalCase $21.NamingStyle = PascalCase
$21.IncludeInstanceMembers = True $21.IncludeInstanceMembers = True
$21.IncludeStaticEntities = False $21.IncludeStaticEntities = False
$7.NamingRule = $22 $7.NamingRule = $22
$22.Name = Constant Fields $22.Name = Constant Fields
$22.AffectedEntity = ConstantField $22.AffectedEntity = ConstantField
$22.VisibilityMask = Protected, Public $22.VisibilityMask = Protected, Public
$22.NamingStyle = PascalCase $22.NamingStyle = PascalCase
$22.IncludeInstanceMembers = True $22.IncludeInstanceMembers = True
$22.IncludeStaticEntities = True $22.IncludeStaticEntities = True
$7.NamingRule = $23 $7.NamingRule = $23
$23.Name = Properties $23.Name = Properties
$23.AffectedEntity = Property $23.AffectedEntity = Property
$23.VisibilityMask = Protected, Public $23.VisibilityMask = Protected, Public
$23.NamingStyle = PascalCase $23.NamingStyle = PascalCase
$23.IncludeInstanceMembers = True $23.IncludeInstanceMembers = True
$23.IncludeStaticEntities = True $23.IncludeStaticEntities = True
$7.NamingRule = $24 $7.NamingRule = $24
$24.Name = Events $24.Name = Events
$24.AffectedEntity = Event $24.AffectedEntity = Event
$24.VisibilityMask = Protected, Public $24.VisibilityMask = Protected, Public
$24.NamingStyle = PascalCase $24.NamingStyle = PascalCase
$24.IncludeInstanceMembers = True $24.IncludeInstanceMembers = True
$24.IncludeStaticEntities = True $24.IncludeStaticEntities = True
$7.NamingRule = $25 $7.NamingRule = $25
$25.Name = Enum Members $25.Name = Enum Members
$25.AffectedEntity = EnumMember $25.AffectedEntity = EnumMember
$25.VisibilityMask = VisibilityMask $25.VisibilityMask = VisibilityMask
$25.NamingStyle = PascalCase $25.NamingStyle = PascalCase
$25.IncludeInstanceMembers = True $25.IncludeInstanceMembers = True
$25.IncludeStaticEntities = True $25.IncludeStaticEntities = True
$7.NamingRule = $26 $7.NamingRule = $26
$26.Name = Parameters $26.Name = Parameters
$26.AffectedEntity = Parameter $26.AffectedEntity = Parameter
$26.VisibilityMask = VisibilityMask $26.VisibilityMask = VisibilityMask
$26.NamingStyle = CamelCase $26.NamingStyle = CamelCase
$26.IncludeInstanceMembers = True $26.IncludeInstanceMembers = True
$26.IncludeStaticEntities = True $26.IncludeStaticEntities = True
$7.NamingRule = $27 $7.NamingRule = $27
$27.Name = Type Parameters $27.Name = Type Parameters
$27.RequiredPrefixes = $28 $27.RequiredPrefixes = $28
$28.String = T $28.String = T
$27.AffectedEntity = TypeParameter $27.AffectedEntity = TypeParameter
$27.VisibilityMask = VisibilityMask $27.VisibilityMask = VisibilityMask
$27.NamingStyle = PascalCase $27.NamingStyle = PascalCase
$27.IncludeInstanceMembers = True $27.IncludeInstanceMembers = True
$27.IncludeStaticEntities = True $27.IncludeStaticEntities = True
EndGlobalSection EndGlobalSection
EndGlobal EndGlobal
   
// KerbalStuffWrapper // KerbalStuffWrapper
// //
// Author: // Author:
// toadicus // toadicus
// //
// Copyright © 2014, toadicus // Copyright © 2014, toadicus
// //
// All rights reserved. // All rights reserved.
// //
// Redistribution and use in source and binary forms, with or without modification, are permitted provided that the // Redistribution and use in source and binary forms, with or without modification, are permitted provided that the
// following conditions are met: // following conditions are met:
// //
// * Redistributions of source code must retain the above copyright notice, this list of conditions and the // * Redistributions of source code must retain the above copyright notice, this list of conditions and the
// following disclaimer. // following disclaimer.
// * Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the // * Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the
// following disclaimer in the documentation and/or other materials provided with the distribution. // following disclaimer in the documentation and/or other materials provided with the distribution.
// * Neither the name of the author nor the names of other contributors may be used to endorse or promote products // * Neither the name of the author nor the names of other contributors may be used to endorse or promote products
// derived from this software without specific prior written permission. // derived from this software without specific prior written permission.
// //
// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS // THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT // "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR // LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR // A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
// CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, // CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
// EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, // EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
// PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR // PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
// PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF // PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
// LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING // LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
// NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS // NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
// SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. // SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
// //
// This software uses the CLAP .NET Command-Line Parser, Copyright © 2011 Adrian Aisemberg, SharpRegion. // This software uses the CLAP .NET Command-Line Parser, Copyright © 2011 Adrian Aisemberg, SharpRegion.
// Used under license. // Used under license.
// //
// This software uses the MiniJSON .NET JSON Parser, Copyright © 2013 Calvin Rien. Used under license. // This software uses the MiniJSON .NET JSON Parser, Copyright © 2013 Calvin Rien. Used under license.
// //
// This software uses the FormUpload multipart/form-data library, // This software uses the FormUpload multipart/form-data library,
// http://www.briangrinstead.com/blog/multipart-form-post-in-c. // http://www.briangrinstead.com/blog/multipart-form-post-in-c.
// //
  // KerbalStuff is copyright © 2014 Drew DeVault. Used under license.
  //
   
using CLAP; using CLAP;
using CLAP.Interception; using CLAP.Interception;
using CLAP.Validation; using CLAP.Validation;
using System; using System;
using System.Collections.Generic; using System.Collections.Generic;
using System.IO; using System.IO;
using System.Linq; using System.Linq;
   
// TODO: Write a console program that uses the KerbalStuff wrapper API. // TODO: Write a console program that uses the KerbalStuff wrapper API.
   
namespace KerbalStuff.Wrapper namespace KerbalStuff.Wrapper
{ {
public class KerbalStuffWrapper public class KerbalStuffWrapper
{ {
private static Parser<KerbalStuffWrapper> parser; private static Parser<KerbalStuffWrapper> parser;
private static string[] args; private static string[] args;
   
public static int Main(string[] args) public static int Main(string[] args)
{ {
KerbalStuffWrapper.args = args; KerbalStuffWrapper.args = args;
parser = new Parser<KerbalStuffWrapper>(); parser = new Parser<KerbalStuffWrapper>();
   
return parser.RunStatic(args); return parser.RunStatic(args);
} }
   
private static void Write(string message, TextWriter output) private static void Write(string message, TextWriter output)
{ {
   
output.Write(string.Format("{0}: {1}", appName, message)); output.Write(string.Format("{0}: {1}", appName, message));
} }
   
private static void WriteOut(string message) private static void WriteOut(string message)
{ {
Write(message, Console.Out); Write(message, Console.Out);
} }
   
private static void WriteOut(string format, params object[] args) private static void WriteOut(string format, params object[] args)
{ {
WriteOut(string.Format(format, args)); WriteOut(string.Format(format, args));
} }
   
private static void WriteOutLine(string format, params object[] args) private static void WriteOutLine(string format, params object[] args)
{ {
WriteOut(format, args); WriteOut(format, args);
Console.Out.WriteLine(); Console.Out.WriteLine();
} }
   
private static void WriteError(string message) private static void WriteError(string message)
{ {
Write(message, Console.Error); Write(message, Console.Error);
} }
   
private static void WriteError(string format, params object[] args) private static void WriteError(string format, params object[] args)
{ {
WriteError(string.Format(format, args)); WriteError(string.Format(format, args));
} }
   
private static void WriteErrorLine(string format, params object[] args) private static void WriteErrorLine(string format, params object[] args)
{ {
WriteError(format, args); WriteError(format, args);
Console.Error.WriteLine(); Console.Error.WriteLine();
} }
   
private const string appName = "KerbalStuffWrapper"; private const string appName = "KerbalStuffWrapper";
   
// TODO: Action methods. // TODO: Action methods.
   
[Verb(Aliases = "mod,m")] [Verb(Aliases = "mod,m")]
public static void ModInfo([Required]long modId) public static void ModInfo([Required]long modId)
{ {
Mod mod = KerbalStuff.ModInfo(modId); Mod mod = KerbalStuff.ModInfo(modId);
   
if (mod == null) if (mod == null)
{ {
WriteErrorLine("Couldn't get Mod info for mod {0}: {1}.", modId, KerbalStuff.currentResponse.StatusDescription); WriteErrorLine("Couldn't get Mod info for mod {0}: {1}.", modId, KerbalStuff.currentResponse.StatusDescription);
} }
else else
{ {
Console.WriteLine(mod); Console.WriteLine(mod);
} }
} }
   
[Verb(Aliases = "latest,l")] [Verb(Aliases = "latest,l")]
public static void ModLatest([Required]long modId) public static void ModLatest([Required]long modId)
{ {
ModVersion ver = KerbalStuff.ModLatest(modId); ModVersion ver = KerbalStuff.ModLatest(modId);
   
if (ver == null) if (ver == null)
{ {
WriteErrorLine("Couldn't get version info for mod {0}: {1}.", modId, KerbalStuff.currentResponse.StatusDescription); WriteErrorLine("Couldn't get version info for mod {0}: {1}.", modId, KerbalStuff.currentResponse.StatusDescription);
} }
else else
{ {
Console.WriteLine(ver); Console.WriteLine(ver);
} }
} }
   
[Verb(Aliases = "search,s")] [Verb(Aliases = "search,s")]
public static void ModSearch([Required]string query) public static void ModSearch([Required]string query)
{ {
List<Mod> mods = KerbalStuff.ModSearch(query); List<Mod> mods = KerbalStuff.ModSearch(query);
   
if (mods.Count < 1) if (mods.Count < 1)
{ {
WriteOutLine("Query yielded no results."); WriteOutLine("Query yielded no results.");
} }
else else
{ {
foreach (Mod mod in mods) foreach (Mod mod in mods)
{ {
Console.WriteLine(mod); Console.WriteLine(mod);
Console.WriteLine(); Console.WriteLine();
} }
} }
} }
   
[Verb(Aliases = "user,u")] [Verb(Aliases = "user,u")]
public static void UserInfo([Required]string username) public static void UserInfo([Required]string username)
{ {
User user = KerbalStuff.UserInfo(username); User user = KerbalStuff.UserInfo(username);
   
if (user == null) if (user == null)
{ {
WriteErrorLine("Couldn't get user info for username '{0}': {1}", username, KerbalStuff.currentResponse.StatusDescription); WriteErrorLine("Couldn't get user info for username '{0}': {1}", username, KerbalStuff.currentResponse.StatusDescription);
} }
else else
{ {
Console.WriteLine(user); Console.WriteLine(user);
} }
} }
   
[Verb(Aliases = "us")] [Verb(Aliases = "us")]
public static void UserSearch([Required]string query) public static void UserSearch([Required]string query)
{ {
List<User> users = KerbalStuff.UserSearch(query); List<User> users = KerbalStuff.UserSearch(query);
   
if (users.Count < 1) if (users.Count < 1)
{ {
WriteOutLine("Query yielded no results."); WriteOutLine("Query yielded no results.");
} }
else else
{ {
foreach (User user in users) foreach (User user in users)
{ {
Console.WriteLine(user); Console.WriteLine(user);
Console.WriteLine(); Console.WriteLine();
} }
} }
} }
   
[Verb(Aliases = "create,c")] [Verb(Aliases = "create,c")]
public static void CreateMod( public static void CreateMod(
[Required] [Required]
[Aliases("user,u")] [Aliases("user,u")]
string username, string username,
[Required] [Required]
[Aliases("pass,p")] [Aliases("pass,p")]
string password, string password,
[Required] [Required]
[Aliases("n")] [Aliases("n")]
string name, string name,
[Required] [Required]
[Aliases("desc,d")] [Aliases("desc,d")]
string short_description, string short_description,
[Required] [Required]
[Aliases("ver,v")] [Aliases("ver,v")]
string version, string version,
[Required] [Required]
[Aliases("ksp,k")] [Aliases("ksp,k")]
string ksp_version, string ksp_version,
[Required] [Required]
[Aliases("lic,l")] [Aliases("lic,l")]
string license, string license,
[Required] [Required]
[Aliases("file,f")] [Aliases("file,f")]
string filePath string filePath
) )
{ {
Mod mod = new Mod(name, short_description, version, ksp_version, license); Mod mod = new Mod(name, short_description, version, ksp_version, license);
   
var loginDict = KerbalStuff.Login(username, password); var loginDict = KerbalStuff.Login(username, password);
   
if (loginDict == null) if (loginDict == null)
{ {
WriteErrorLine("Could not complete login attempt: {0}", KerbalStuff.currentResponse.StatusDescription); WriteErrorLine("Could not complete login attempt: {0}", KerbalStuff.currentResponse.StatusDescription);
} }
else if (loginDict.ContainsKey("error") && loginDict["error"].ToString().ToLower() == "true") else if (loginDict.ContainsKey("error") && loginDict["error"].ToString().ToLower() == "true")
{ {
WriteErrorLine("Login failed: {0}", loginDict["reason"]); WriteErrorLine("Login failed: {0}", loginDict["reason"]);
} }
else else
{ {
var createDict = KerbalStuff.Create(mod, Path.GetFileName(filePath), filePath); var createDict = KerbalStuff.Create(mod, Path.GetFileName(filePath), filePath);
   
if (createDict == null) if (createDict == null)
{ {
WriteErrorLine("Could not complete creation attempt: {0}", KerbalStuff.currentResponse.StatusDescription); WriteErrorLine("Could not complete creation attempt: {0}", KerbalStuff.currentResponse.StatusDescription);
} }
else if (createDict.ContainsKey("error") && createDict["error"].ToString().ToLower() == "true") else if (createDict.ContainsKey("error") && createDict["error"].ToString().ToLower() == "true")
{ {
WriteErrorLine("Creation failed: {0}", createDict["message"]); WriteErrorLine("Creation failed: {0}", createDict["message"]);
} }
else else
{ {
WriteOutLine("New mod '{0}' created with id #{2}! You can view and publish the mod at {1}", WriteOutLine("New mod '{0}' created with id #{2}! You can view and publish the mod at {1}",
createDict["name"], createDict["name"],
string.Concat(KerbalStuff.RootUri, createDict["url"]), string.Concat(KerbalStuff.RootUri, createDict["url"]),
createDict["id"] createDict["id"]
); );
} }
} }
} }
   
[Verb(Aliases = "update,up")] [Verb(Aliases = "update,up")]
public static void UpdateMod( public static void UpdateMod(
[Required] [Required]
[Aliases("user,u")] [Aliases("user,u")]
string username, string username,
[Required] [Required]
[Aliases("pass,p")] [Aliases("pass,p")]
string password, string password,
[Required] [Required]
[Aliases("mod,m")] [Aliases("mod,m")]
long modId, long modId,
[Required] [Required]
[Aliases("ver,v")] [Aliases("ver,v")]
string version, string version,
[Required] [Required]
[Aliases("ksp,k")] [Aliases("ksp,k")]
string ksp_version, string ksp_version,
[Aliases("log,l")] [Aliases("log,l")]
string changelog, string changelog,
[DefaultValue(false)] [DefaultValue(false)]
[Aliases("notify,n")] [Aliases("notify,n")]
bool notifyFollowers, bool notifyFollowers,
[Required] [Required]
[Aliases("file,f")] [Aliases("file,f")]
string filePath string filePath
) )
{ {
ModVersion ver; ModVersion ver;
if (changelog != string.Empty) if (changelog != string.Empty)
{ {
ver = new ModVersion(version, ksp_version, changelog); ver = new ModVersion(version, ksp_version, changelog);
} }
else else
{ {
ver = new ModVersion(version, ksp_version); ver = new ModVersion(version, ksp_version);
} }
   
var loginDict = KerbalStuff.Login(username, password); var loginDict = KerbalStuff.Login(username, password);
   
if (loginDict == null) if (loginDict == null)
{ {
WriteErrorLine("Could not complete login attempt: {0}", KerbalStuff.currentResponse.StatusDescription); WriteErrorLine("Could not complete login attempt: {0}", KerbalStuff.currentResponse.StatusDescription);
} }
else if (loginDict.ContainsKey("error") && loginDict["error"].ToString().ToLower() == "true") else if (loginDict.ContainsKey("error") && loginDict["error"].ToString().ToLower() == "true")
{ {
WriteErrorLine("Login failed: {0}", loginDict["reason"]); WriteErrorLine("Login failed: {0}", loginDict["reason"]);
} }
else else
{ {
var updateDict = KerbalStuff.Update(modId, ver, notifyFollowers, Path.GetFileName(filePath), filePath); var updateDict = KerbalStuff.Update(modId, ver, notifyFollowers, Path.GetFileName(filePath), filePath);
   
if (updateDict == null) if (updateDict == null)
{ {
WriteErrorLine("Could not complete creation attempt: {0}", KerbalStuff.currentResponse.StatusDescription); WriteErrorLine("Could not complete creation attempt: {0}", KerbalStuff.currentResponse.StatusDescription);
} }
else if (updateDict.ContainsKey("error") && updateDict["error"].ToString().ToLower() == "true") else if (updateDict.ContainsKey("error") && updateDict["error"].ToString().ToLower() == "true")
{ {
WriteErrorLine("Creation failed: {0}", updateDict["message"]); WriteErrorLine("Creation failed: {0}", updateDict["message"]);
} }
else else
{ {
WriteOutLine("Mod #{0}! You can view the update at {1}", WriteOutLine("Mod #{0}! You can view the update at {1}",
updateDict["id"], updateDict["id"],
string.Concat(KerbalStuff.RootUri, updateDict["url"]) string.Concat(KerbalStuff.RootUri, updateDict["url"])
); );
} }
} }
} }
[Empty, Help, Global] [Empty, Help, Global]
public static void Help(string help) public static void Help(string help)
{ {
Console.Error.WriteLine(string.Format("Usage: {0} <ACTION> /OPTION[:ARG] [/OPTION[:ARG]...]", appName)); Console.Error.WriteLine(string.Format("Usage: {0} <ACTION> /OPTION[:ARG] [/OPTION[:ARG]...]", appName));
Console.Error.WriteLine("\nActions:"); Console.Error.WriteLine("\nActions:");
Console.Error.WriteLine(parser.GetHelpString()); Console.Error.WriteLine(parser.GetHelpString());
} }
   
[Error] [Error]
public static void HandleError(ExceptionContext context) public static void HandleError(ExceptionContext context)
{ {
if (context.Exception is VerbNotFoundException) if (context.Exception is VerbNotFoundException)
{ {
var ex = context.Exception as VerbNotFoundException; var ex = context.Exception as VerbNotFoundException;
WriteErrorLine("Action '{0}' does not exist.", ex.Verb); WriteErrorLine("Action '{0}' does not exist.", ex.Verb);
} }
else if (context.Exception is MissingDefaultVerbException) else if (context.Exception is MissingDefaultVerbException)
{ {
WriteErrorLine("An action is required."); WriteErrorLine("An action is required.");
} }
else if (context.Exception is UnhandledParametersException) else if (context.Exception is UnhandledParametersException)
{ {
var ex = context.Exception as UnhandledParametersException; var ex = context.Exception as UnhandledParametersException;
var keys = ex.UnhandledParameters.Keys; var keys = ex.UnhandledParameters.Keys;
   
WriteError("invalid option"); WriteError("invalid option");
   
if (keys.Count > 1) if (keys.Count > 1)
{ {
Console.Error.Write("s"); Console.Error.Write("s");
} }
   
Console.Error.Write(string.Format(" for action {0}: '{1}'", args[0], string.Join(", ", keys.ToArray()))); Console.Error.Write(string.Format(" for action {0}: '{1}'", args[0], string.Join(", ", keys.ToArray())));
} }
else if (context.Exception is MissingArgumentPrefixException) else if (context.Exception is MissingArgumentPrefixException)
{ {
var ex = context.Exception as MissingArgumentPrefixException; var ex = context.Exception as MissingArgumentPrefixException;
   
WriteErrorLine("{0}: option {1}", args[0], ex.Message); WriteErrorLine("{0}: option {1}", args[0], ex.Message);
} }
else if (context.Exception is MissingArgumentValueException) else if (context.Exception is MissingArgumentValueException)
{ {
var ex = context.Exception as MissingArgumentValueException; var ex = context.Exception as MissingArgumentValueException;
   
WriteErrorLine("{0}: option '{1}' requires an argument.", args[0], ex.ParameterName); WriteErrorLine("{0}: option '{1}' requires an argument.", args[0], ex.ParameterName);
} }
else if (context.Exception is TypeConvertionException) else if (context.Exception is TypeConvertionException)
{ {
var ex = context.Exception as TypeConvertionException; var ex = context.Exception as TypeConvertionException;
WriteErrorLine("Invalid argument for {2}: '{0}' cannot be converted to {1}.\n", ex.Value, ex.Type.HumanName(), args[0]); WriteErrorLine("Invalid argument for {2}: '{0}' cannot be converted to {1}.\n", ex.Value, ex.Type.HumanName(), args[0]);
Help(string.Empty); Help(string.Empty);
} }
else if (context.Exception is CommandLineParserException) else if (context.Exception is CommandLineParserException)
{ {
WriteErrorLine(context.Exception.Message); WriteErrorLine(context.Exception.Message);
} }
else else
{ {
WriteErrorLine("An unexpected error occured. {0}: {1}", context.Exception.GetType().Name, context.Exception.Message); WriteErrorLine("An unexpected error occured. {0}: {1}", context.Exception.GetType().Name, context.Exception.Message);
Console.Error.WriteLine(); Console.Error.WriteLine();
WriteErrorLine(context.Exception.StackTrace); WriteErrorLine(context.Exception.StackTrace);
   
Console.Error.WriteLine(); Console.Error.WriteLine();
Console.Error.WriteLine("Response:"); Console.Error.WriteLine("Response:");
Console.Error.WriteLine(); Console.Error.WriteLine();
   
if (KerbalStuff.currentResponse != null) if (KerbalStuff.currentResponse != null)
{ {
Console.Error.WriteLine((new StreamReader(KerbalStuff.currentResponse.GetResponseStream())).ReadToEnd()); Console.Error.WriteLine((new StreamReader(KerbalStuff.currentResponse.GetResponseStream())).ReadToEnd());
} }
   
if (KerbalStuff.currentJson != null) if (KerbalStuff.currentJson != null)
{ {
if (KerbalStuff.currentJson is Dictionary<string, object>) if (KerbalStuff.currentJson is Dictionary<string, object>)
{ {
var json = KerbalStuff.currentJson as Dictionary<string, object>; var json = KerbalStuff.currentJson as Dictionary<string, object>;
foreach (KeyValuePair<string, object> item in json) foreach (KeyValuePair<string, object> item in json)
{ {
Console.Error.WriteLine(string.Format("{0}: {1}", item.Key, item.Value)); Console.Error.WriteLine(string.Format("{0}: {1}", item.Key, item.Value));
} }
} }
} }
   
return; return;
} }
   
Console.Error.WriteLine(); Console.Error.WriteLine();
   
Help(string.Empty); Help(string.Empty);
} }
} }
} }
   
   
<?xml version="1.0" encoding="utf-8"?> <?xml version="1.0" encoding="utf-8"?>
<Project DefaultTargets="Build" ToolsVersion="4.0" xmlns="http://schemas.microsoft.com/developer/msbuild/2003"> <Project DefaultTargets="Build" ToolsVersion="4.0" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
<PropertyGroup> <PropertyGroup>
<Configuration Condition=" '$(Configuration)' == '' ">Debug</Configuration> <Configuration Condition=" '$(Configuration)' == '' ">Debug</Configuration>
<Platform Condition=" '$(Platform)' == '' ">AnyCPU</Platform> <Platform Condition=" '$(Platform)' == '' ">AnyCPU</Platform>
<ProductVersion>8.0.30703</ProductVersion> <ProductVersion>8.0.30703</ProductVersion>
<SchemaVersion>2.0</SchemaVersion> <SchemaVersion>2.0</SchemaVersion>
<ProjectGuid>{1E93CDA7-56A8-410F-A5A2-0ABB9210CA58}</ProjectGuid> <ProjectGuid>{1E93CDA7-56A8-410F-A5A2-0ABB9210CA58}</ProjectGuid>
<OutputType>Exe</OutputType> <OutputType>Exe</OutputType>
<RootNamespace>KerbalStuff.Wrapper</RootNamespace> <RootNamespace>KerbalStuff.Wrapper</RootNamespace>
<AssemblyName>KerbalStuffWrapper</AssemblyName> <AssemblyName>KerbalStuffWrapper</AssemblyName>
<UseMSBuildEngine>False</UseMSBuildEngine> <UseMSBuildEngine>False</UseMSBuildEngine>
<TargetFrameworkVersion>v3.5</TargetFrameworkVersion> <TargetFrameworkVersion>v3.5</TargetFrameworkVersion>
</PropertyGroup> </PropertyGroup>
<PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Release|AnyCPU' "> <PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Release|AnyCPU' ">
<DebugType>full</DebugType> <DebugType>full</DebugType>
<Optimize>true</Optimize> <Optimize>true</Optimize>
<OutputPath>bin\Release</OutputPath> <OutputPath>bin\Release</OutputPath>
<ErrorReport>prompt</ErrorReport> <ErrorReport>prompt</ErrorReport>
<WarningLevel>4</WarningLevel> <WarningLevel>4</WarningLevel>
<ConsolePause>false</ConsolePause> <ConsolePause>false</ConsolePause>
<PlatformTarget>x86</PlatformTarget> <PlatformTarget>x86</PlatformTarget>
</PropertyGroup> </PropertyGroup>
<PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Debug|AnyCPU' "> <PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Debug|AnyCPU' ">
<DebugSymbols>true</DebugSymbols> <DebugSymbols>true</DebugSymbols>
<DebugType>full</DebugType> <DebugType>full</DebugType>
<Optimize>false</Optimize> <Optimize>false</Optimize>
<OutputPath>bin\Debug</OutputPath> <OutputPath>bin\Debug</OutputPath>
<DefineConstants>DEBUG;</DefineConstants> <DefineConstants>DEBUG;</DefineConstants>
<ErrorReport>prompt</ErrorReport> <ErrorReport>prompt</ErrorReport>
<WarningLevel>4</WarningLevel> <WarningLevel>4</WarningLevel>
<ConsolePause>false</ConsolePause> <ConsolePause>false</ConsolePause>
<PlatformTarget>x86</PlatformTarget> <PlatformTarget>x86</PlatformTarget>
</PropertyGroup> </PropertyGroup>
<Import Project="$(MSBuildBinPath)\Microsoft.CSharp.targets" /> <Import Project="$(MSBuildBinPath)\Microsoft.CSharp.targets" />
<ItemGroup> <ItemGroup>
<Reference Include="System" /> <Reference Include="System" />
<Reference Include="CLAP"> <Reference Include="CLAP">
<HintPath>CLAP.dll</HintPath> <HintPath>CLAP.dll</HintPath>
</Reference> </Reference>
</ItemGroup> </ItemGroup>
<ItemGroup> <ItemGroup>
<Compile Include="KerbalStuffWrapper.cs" /> <Compile Include="KerbalStuffWrapper.cs" />
<Compile Include="Utils.cs"> <Compile Include="Utils.cs">
<DeployService-Deploy>True</DeployService-Deploy> <DeployService-Deploy>True</DeployService-Deploy>
</Compile> </Compile>
</ItemGroup> </ItemGroup>
<ItemGroup> <ItemGroup>
<ProjectReference Include="..\KerbalStuff\KerbalStuff.csproj"> <ProjectReference Include="..\KerbalStuff\KerbalStuff.csproj">
<Project>{0CA12F64-AD3B-41E6-8A35-8781A8DAD1C1}</Project> <Project>{0CA12F64-AD3B-41E6-8A35-8781A8DAD1C1}</Project>
<Name>KerbalStuff</Name> <Name>KerbalStuff</Name>
</ProjectReference> </ProjectReference>
<ProjectReference Include="..\KerbalStuffReadOnly\KerbalStuffReadOnly.csproj"> <ProjectReference Include="..\KerbalStuffReadOnly\KerbalStuffReadOnly.csproj">
<Project>{720FA70F-D785-48ED-BA45-561921E0EEEC}</Project> <Project>{720FA70F-D785-48ED-BA45-561921E0EEEC}</Project>
<Name>KerbalStuffReadOnly</Name> <Name>KerbalStuffReadOnly</Name>
</ProjectReference> </ProjectReference>
</ItemGroup> </ItemGroup>
  <ItemGroup>
  <None Include="README" />
  <None Include="LICENSE" />
  </ItemGroup>
</Project> </Project>
  KerbalStuffWrapper
 
  Author:
  toadicus
 
  Copyright © 2014, toadicus
 
  All rights reserved.
 
  Redistribution and use in source and binary forms, with or without modification, are permitted provided that the
  following conditions are met:
 
  * Redistributions of source code must retain the above copyright notice, this list of conditions and the
  following disclaimer.
  * Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the
  following disclaimer in the documentation and/or other materials provided with the distribution.
  * Neither the name of the author nor the names of other contributors may be used to endorse or promote products
  derived from this software without specific prior written permission.
  * Neither the name of the author nor the names of other contributors may be used to endorse or promote products
  derived from this software without specific prior written permission.¹
 
  THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
  "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
  LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
  A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
  CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
  EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
  PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
  PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
  LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
  NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
  SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
 
  This software uses the CLAP .NET Command-Line Parser, Copyright © 2011 Adrian Aisemberg, SharpRegion.
  Used under license.
 
  This software uses the MiniJSON .NET JSON Parser, Copyright © 2013 Calvin Rien. Used under license.
 
  This software uses the FormUpload multipart/form-data library,
  http:www.briangrinstead.com/blog/multipart-form-post-in-c.
 
  KerbalStuff is copyright © 2014 Drew DeVault. Used under license.
 
  ¹3rd clause applies only to KerbalStuffWrapper.exe; see README and full source at
  http://git.toad.homelinux.net/projects/KerbalStuffWrapper.git for more information.
  KerbalStuffWrapper
 
  Author:
  toadicus
 
  KerbalStuffWrapper is a suite of .NET-compatible utilities that provides access to the KerbalStuff API. The suite is
  broken into three parts:
 
  * KerbalStuffReadOnly.dll - A library implementing access to the documented, read-only KerbalStuff API members.
  This should be suitable for use in KSP mods for version checking and other purposes.
  Released under the BSD 2-Clause license.
  * KerbalStuff.dll - A library implementing access to the document, read-write KerbalStuff API members.
  Depends on and inherits from KerbalStuffReadOnly.dll. This should be suitable for use
  in external applications performing mod management for KerbalStuff users. Released
  under the BSD 2-Clause license.
  * KerbalStuffWrapper.exe - A proof-of-concept console application providing command-line access to all documented
  KerbalStuff API members. Released under the BSD 3-Clause license.
// KerbalStuffWrapper // KerbalStuffWrapper
// //
// Author: // Author:
// toadicus // toadicus
// //
// Copyright © 2014, toadicus // Copyright © 2014, toadicus
// //
// All rights reserved. // All rights reserved.
// //
// Redistribution and use in source and binary forms, with or without modification, are permitted provided that the // Redistribution and use in source and binary forms, with or without modification, are permitted provided that the
// following conditions are met: // following conditions are met:
// //
// * Redistributions of source code must retain the above copyright notice, this list of conditions and the // * Redistributions of source code must retain the above copyright notice, this list of conditions and the
// following disclaimer. // following disclaimer.
// * Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the // * Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the
// following disclaimer in the documentation and/or other materials provided with the distribution. // following disclaimer in the documentation and/or other materials provided with the distribution.
// * Neither the name of the author nor the names of other contributors may be used to endorse or promote products // * Neither the name of the author nor the names of other contributors may be used to endorse or promote products
// derived from this software without specific prior written permission. // derived from this software without specific prior written permission.
// //
// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS // THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT // "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR // LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR // A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
// CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, // CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
// EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, // EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
// PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR // PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
// PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF // PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
// LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING // LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
// NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS // NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
// SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. // SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
// //
// This software uses the CLAP .NET Command-Line Parser, Copyright © 2011 Adrian Aisemberg, SharpRegion. // This software uses the CLAP .NET Command-Line Parser, Copyright © 2011 Adrian Aisemberg, SharpRegion.
// Used under license. // Used under license.
// //
// This software uses the MiniJSON .NET JSON Parser, Copyright © 2013 Calvin Rien. Used under license. // This software uses the MiniJSON .NET JSON Parser, Copyright © 2013 Calvin Rien. Used under license.
// //
// This software uses the FormUpload multipart/form-data library, // This software uses the FormUpload multipart/form-data library,
// http://www.briangrinstead.com/blog/multipart-form-post-in-c. // http://www.briangrinstead.com/blog/multipart-form-post-in-c.
// //
  // KerbalStuff is copyright © 2014 Drew DeVault. Used under license.
  //
   
using System; using System;
using System.Collections.Generic; using System.Collections.Generic;
   
namespace KerbalStuff.Wrapper namespace KerbalStuff.Wrapper
{ {
public static class Utils public static class Utils
{ {
public static string HumanName(this Type type) public static string HumanName(this Type type)
{ {
switch (type.Name) switch (type.Name)
{ {
case "Int64": case "Int64":
case "Int32": case "Int32":
case "Int16": case "Int16":
return "integer"; return "integer";
case "Single": case "Single":
case "Double": case "Double":
return "decimal number"; return "decimal number";
default: default:
return type.Name.ToLower(); return type.Name.ToLower();
} }
} }
} }
} }