// File: LoadRemoteCode.cs
#region Usings
using System;
using System.Collections.Generic;
using System.Diagnostics.Contracts;
using System.IO;
using System.Net;
using System.Text;
using System.Threading.Tasks;
using System.Xml.Linq;
using Buddy.Coroutines;
using CommonBehaviors.Actions;
using Styx.CommonBot.Profiles;
using Styx.TreeSharp;
#endregion
/* Place tag as one of the first in the QuestOrder.
* It will look for RemoteCode behaviors and load a
* profile based off of the code from the urls.
*/
/* Example usage:
*
*/
namespace Pookthetook.QuestBehaviors
{
///
/// The load remote code quest behavior.
///
[CustomBehaviorFileName("LoadRemoteCode")]
public class LoadRemoteCode : CustomForcedBehavior
{
///
/// The is behavior done.
///
private bool isBehaviorDone;
///
/// Initializes a new instance of the class.
///
///
/// Styx.CommonBot.Profiles.CustomForcedBehavior
///
///
///
///
/// The args.
///
public LoadRemoteCode(Dictionary args)
: base(args)
{
}
///
/// Gets a value indicating whether this object is done.
///
///
/// true if this object is done, false if not.
///
public override bool IsDone
{
get
{
return this.isBehaviorDone;
}
}
///
/// The create behavior composite.
///
///
/// The .
///
protected override Composite CreateBehavior()
{
return new ActionRunCoroutine(coroutine => this.MainCoroutine());
}
///
/// Gets a new profile with the remote code loaded.
///
///
/// The current profile.
///
///
/// The containing the new profile.
///
///
/// currentProfile is null.
///
///
/// currentProfile does not contain a root element.
///
///
/// Failed to create new profile.
///
private async Task GetNewProfile(XDocument currentProfile)
{
if (currentProfile == null) throw new ArgumentNullException("currentProfile");
if (currentProfile.Root == null) throw new ArgumentException("currentProfile does not contain a root element.");
Contract.EndContractBlock();
try
{
var profileCode = await this.GetElementCode(currentProfile.Root);
var newProfile = XDocument.Parse(profileCode);
return newProfile;
}
catch (Exception ex)
{
throw new InvalidOperationException("Failed to create new profile.", ex);
}
}
///
/// Gets all of the code for a parent element.
///
///
/// The parent element.
///
///
/// The containing the string of code.
///
///
/// parentElement is null.
///
private async Task GetElementCode(XElement parentElement)
{
if (parentElement == null) throw new ArgumentNullException("parentElement");
Contract.EndContractBlock();
var attributeBuilder = new StringBuilder();
if (parentElement.HasAttributes)
{
foreach (var attribute in parentElement.Attributes())
{
attributeBuilder.Append(attribute + " ");
}
}
var profileBuilder = new StringBuilder();
profileBuilder.AppendFormat("<{0} {1}>\n", parentElement.Name, attributeBuilder);
foreach (var element in parentElement.Elements())
{
if (element.HasElements)
{
var childCode = await this.GetElementCode(element);
profileBuilder.AppendLine(childCode);
}
else if (element.Name == "CustomBehavior" && element.Attribute("File") != null && element.Attribute("File")
.Value == "LoadRemoteCode")
{
// Ignore so that it won't try to load again.
}
else if (element.Name == "CustomBehavior" && element.Attribute("File") != null && element.Attribute("File")
.Value == "RemoteCode")
{
var remoteCode = await this.GetRemoteCode(element);
profileBuilder.AppendLine(remoteCode);
}
else
{
profileBuilder.AppendLine(element.ToString());
}
}
profileBuilder.AppendFormat("{0}>\n", parentElement.Name);
return profileBuilder.ToString();
}
///
/// Gets remote code from a server.
///
///
/// The remote code element containing a CodeUrl.
///
///
/// The returning the remote code.
///
///
/// remoteCodeElement is null.
///
///
/// RemoteCode element is invalid.
///
///
/// Failed to load code.
///
private async Task GetRemoteCode(XElement remoteCodeElement)
{
if (remoteCodeElement == null) throw new ArgumentNullException("remoteCodeElement");
if (remoteCodeElement.Attribute("File") == null) throw new ArgumentException("CustomBehavior RemoteCode element does not contain a File attribute.");
if (remoteCodeElement.Attribute("File").Value != "RemoteCode") throw new ArgumentException("CustomBehavior RemoteCode element File attribute is not RemoteCode.");
if (remoteCodeElement.Attribute("CodeUrl") == null) throw new ArgumentException("CustomBehavior RemoteCode element does not contain a CodeUrl attribute.");
if (string.IsNullOrWhiteSpace(remoteCodeElement.Attribute("CodeUrl").Value)) throw new ArgumentException("CustomBehavior RemoteCode element CodeUrl attribute is empty.");
Contract.EndContractBlock();
var codeUrl = remoteCodeElement.Attribute("CodeUrl")
.Value;
string codeString;
using (var client = new WebClient())
{
try
{
this.LogMessage("info", string.Format("Loading remote code from {0}.", codeUrl));
codeString = await Coroutine.ExternalTask(client.DownloadStringTaskAsync(new Uri(codeUrl)));
}
catch (Exception ex)
{
throw new InvalidOperationException(string.Format("Failed to load remote code from {0}.", codeUrl), ex);
}
}
try
{
// Make sure it's formatted correctly
var stringBuilder = new StringBuilder(
XDocument.Parse(codeString)
.ToString());
stringBuilder.Replace("", string.Empty);
stringBuilder.Replace("", string.Empty);
stringBuilder.Replace("", string.Empty);
stringBuilder.Replace("", string.Empty);
codeString = stringBuilder.ToString();
}
catch (Exception)
{
// Profile can't be parsed into a document so it's just code.
}
return codeString;
}
///
/// Gets the XDocument for the current profile.
///
///
/// The containing the current profile.
///
///
/// Profile could not be loaded.
///
private XDocument GetProfileXDocument()
{
var path = ProfileManager.XmlLocation;
try
{
XDocument profile;
using (var streamReader = new StreamReader(path, Encoding.UTF8, true))
{
profile = XDocument.Load(streamReader, LoadOptions.PreserveWhitespace);
}
return profile;
}
catch (FileNotFoundException ex)
{
throw new InvalidOperationException(string.Format("Profile could not be found at {0}.", path), ex);
}
catch (DirectoryNotFoundException ex)
{
throw new InvalidOperationException(string.Format("Directory for profile could not be found at {0}.", path), ex);
}
catch (Exception ex)
{
throw new InvalidOperationException(string.Format("Failed to get profile from {0}.", path), ex);
}
}
///
/// The main coroutine.
///
///
/// The .
///
private async Task MainCoroutine()
{
if (this.IsDone) return true;
this.LogMessage("info", "Getting new profile.");
try
{
var currentProfile = this.GetProfileXDocument();
var newProfile = await this.GetNewProfile(currentProfile);
using (var memoryStream = new MemoryStream(Encoding.UTF8.GetBytes(newProfile.ToString())))
{
this.LogMessage("info", "Loading new profile.");
ProfileManager.LoadNew(memoryStream);
await Coroutine.Sleep(300);
}
}
catch (Exception ex)
{
this.LogMessage("fatal", string.Format("LoadRemoteCode Failed.\n{0}", ex));
}
this.isBehaviorDone = true;
return true;
}
}
}