// 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("\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; } } }