-
Notifications
You must be signed in to change notification settings - Fork 2
/
ChainLoader.cs
executable file
·227 lines (188 loc) · 8.98 KB
/
ChainLoader.cs
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
using System;
using System.Collections.Generic;
using System.Xml.Linq;
using System.Xml;
using System.IO;
using System.Reflection;
using System.Linq;
namespace TriggerHappy {
public class ChainLoader {
protected TriggerHappyPlugin parent;
/// <summary>
/// Returns a list of types loaded in the assembly by their attribute.
/// </summary>
/// <returns>
/// A dictionary containing the action name as a string, and the type of the action class,
/// or null if an error occured.
/// </returns>
public Dictionary<string, Type> LoadAttributeTypes<T>() where T: Attribute {
Dictionary<string, Type> actionDict = new Dictionary<string, Type>();
var qActions = from i in Assembly.GetExecutingAssembly().GetTypes()
let actions = i.GetCustomAttributes(typeof(T), true)
where actions.Length == 1
select new { Attribute = actions.FirstOrDefault() as T, Type = i };
foreach (var action in qActions) {
var p = (action.Attribute as T).GetType().GetProperty("Name");
string nameValue = null;
if (p == null) {
continue;
}
nameValue = p.GetValue(action.Attribute, null) as string;
if (string.IsNullOrEmpty(nameValue) == true) {
continue;
}
if (actionDict.ContainsKey(nameValue) == true) {
//TODO: Log error
return null;
} else {
actionDict.Add(nameValue, action.Type);
}
}
return actionDict;
}
public ChainLoader(TriggerHappyPlugin pluginInstance) {
this.parent = pluginInstance;
}
public bool VerifyChains() {
bool duplicatesFound = false;
//basic sanity check
if (parent.chainList == null || parent.chainList.Count == 0) {
THLog.Log(LogLevel.Error, "Chain verify failed: no chains have been loaded, triggerhappy is inactive.");
return false;
}
//verify there's an __INCOMING__ chain and only one of them
if (parent.chainList.Count(i => i.Name.Equals("__INCOMING__")) != 1) {
THLog.Log(LogLevel.Error, "Chain verify failed: there must be one and ONLY one __INCOMING__ chain.");
return false;
}
//verify there's no duplicate chain names
var qDuplicateChains = from i in parent.chainList
group i by i.Name into Group
where Group.Count() > 1
select Group.Key;
foreach (string duplicateChain in qDuplicateChains) {
if (duplicatesFound == false) {
duplicatesFound = true;
}
THLog.Log(LogLevel.Error, "Chain verify failed: duplicate chain by the name of {0}.", duplicateChain);
}
if (duplicatesFound == true) {
return false;
}
//verify accessible chains with no actions
//these are not fatal errors, just warn
foreach (Chain chain in parent.chainList) {
TerrariaApi.Server.GetDataEventArgs nullObject = null;
if (chain.ChainCanProcess(ref nullObject, false) == true && chain.Actions.Count == 0) {
THLog.Log(LogLevel.Warning, "Chain verify warning: Chain {0} is accessible but has no actions; it does nothing!", chain.Name);
}
}
//(maybe) verify cyclic redundancies
//no idea how the fuck I am going to achieve this without a maintaining a chain callstack
//cbf for now
return true;
}
/// <summary>
/// Writes the embedded resource XML files in the ChainLibrary folder to the specified dirName.
/// </summary>
/// <param name="dirName">Dir name.</param>
public void WriteChainLibrary(string dirName) {
foreach (string resourceName in Assembly.GetExecutingAssembly().GetManifestResourceNames()) {
string fullResourcePath = null;
if (resourceName.EndsWith(".xml") == false) {
continue;
}
fullResourcePath = dirName + System.IO.Path.DirectorySeparatorChar + resourceName;
try {
if (System.IO.File.Exists(fullResourcePath) == true) {
continue;
}
using (StreamReader sr = new StreamReader(Assembly.GetExecutingAssembly().GetManifestResourceStream(resourceName))) {
System.IO.File.WriteAllText(fullResourcePath, sr.ReadToEnd());
}
} catch (Exception) {
THLog.Log(LogLevel.Error, "Could not write resource {0} to path {1}!", resourceName, dirName);
}
}
}
/// <summary>
/// Loads the chains in the specified directory.
/// </summary>
/// <param name="dirName">Dir name.</param>
public void LoadChainsInDirectory(string dirName) {
if (Directory.Exists(dirName) == false) {
THLog.Log(LogLevel.Error, "Chain directory does not exist, creating a new one.");
try {
THLog.Debug("CreateDirectory {0}", dirName);
Directory.CreateDirectory(dirName);
} catch (Exception) {
THLog.Log(LogLevel.Error, "CreateDirectory {0} failed", dirName);
return;
}
THLog.Debug("CreateDirectory {0} succeeded", dirName);
}
WriteChainLibrary(dirName);
THLog.Debug("LoadChainsInDirectory {0}", dirName);
foreach (string xmlFile in Directory.GetFiles(dirName, "*.xml")) {
try {
THLog.Debug("LoadChain {0}", xmlFile);
LoadChain(xmlFile);
} catch (Exception) {
THLog.Log(LogLevel.Error, "LoadChain {0} failed.", dirName);
}
THLog.Debug("LoadChain {0} succeeded", xmlFile);
}
THLog.Debug("LoadChainsInDirectory {0} succeeded.", dirName);
}
/// <summary>
/// Loads a chain list from an XML file.
/// </summary>
/// <returns>The chain.</returns>
/// <param name="pathName">Path name.</param>
public void LoadChain(string pathName) {
XDocument chainDoc = null;
XElement rootElement = null;
List<Chain> chainList = new List<Chain>();
int chainCount = 0;
try {
using (FileStream fs = new FileStream(pathName, FileMode.Open)) {
THLog.Debug(null, "Loading chain file {0}.", pathName);
using (XmlReader xmlReader = XmlReader.Create(fs)) {
chainDoc = XDocument.Load(xmlReader);
THLog.Debug(null, "XmlLoad {0} succeeded.", pathName);
}
}
} catch (Exception) {
THLog.Log(LogLevel.Error, "Cannot load chain file {0}, skipping", pathName);
return;
}
if ((rootElement = chainDoc.Element("Configuration")) == null) {
THLog.Log(LogLevel.Error, "File {0} is not a chain file, skipping", pathName);
return;
}
THLog.Debug("File {0} is a valid chain configurqation file.", pathName);
foreach (XElement chainElement in rootElement.Elements("Chain")) {
string chainName = null;
if (chainElement.HasAttributes == true && chainElement.Attribute("Name") != null
&& string.IsNullOrEmpty((chainName = chainElement.Attribute("Name").Value)) == true) {
THLog.Log(LogLevel.Debug, "A chain in file {0} does not have a name, skipping", pathName);
continue;
}
THLog.Debug("ChainAdd {0}", chainName);
if (parent.GetChainByName(chainName) == null) {
try {
chainList.Add(new Chain(parent, chainElement));
} catch (Exception) {
THLog.Log(LogLevel.Error, "Internal error creating chain.");
}
THLog.Debug("ChainAdd {0} succeeded", chainName);
} else {
THLog.Log(LogLevel.Error, "A chain by the name of \"{0}\" already exists, skipping", chainName);
}
chainCount++;
}
THLog.Debug("LoadChains loaded {0} chains", chainCount);
parent.chainList.AddRange(chainList);
}
}
}