PageRenderTime 108ms CodeModel.GetById 18ms RepoModel.GetById 1ms app.codeStats 0ms

/Services/WCell.RealmServer/Content/ContentHandler.cs

https://github.com/enjoii/WCell
C# | 568 lines | 401 code | 63 blank | 104 comment | 23 complexity | 889ee9b29fb4fd362a04d20a1a5f09d6 MD5 | raw file
  1. using System;
  2. using System.Collections.Generic;
  3. using System.IO;
  4. using System.Reflection;
  5. using Castle.ActiveRecord;
  6. using NLog;
  7. using WCell.Constants;
  8. using WCell.Core.Database;
  9. using WCell.Core.Initialization;
  10. using WCell.RealmServer.Database;
  11. using WCell.Util;
  12. using WCell.Util.Data;
  13. using WCell.Util.DB;
  14. using WCell.Util.Variables;
  15. using WCell.Util.Conversion;
  16. using System.Text;
  17. using WCell.Util.NLog;
  18. namespace WCell.RealmServer.Content
  19. {
  20. /// <summary>
  21. /// TODO: Make it simple to add content from outside
  22. /// </summary>
  23. public static class ContentHandler
  24. {
  25. public static readonly List<Assembly> AdditionalAssemblies = new List<Assembly>();
  26. private static readonly Logger log = LogManager.GetCurrentClassLogger();
  27. /// <summary>
  28. /// Determines how to interact with invalid content data.
  29. /// </summary>
  30. [Variable("ContentErrorResponse")]
  31. public static ErrorResponse ErrorResponse = ErrorResponse.None;
  32. /// <summary>
  33. /// Causes an error to be thrown if certain data is not present when requested
  34. /// </summary>
  35. [NotVariable]
  36. public static bool ForceDataPresence = false;
  37. //ErrorResponse.Warn;
  38. public static bool EnableCaching = true;
  39. private static string s_implementationFolder, s_implementationRoot;
  40. private static LightDBDefinitionSet s_definitions;
  41. private static Dictionary<Type, LightDBMapper> s_mappersByType;
  42. /// <summary>
  43. /// The name of the ContentProvider, which is also the name of the folder within the Content/Impl/ folder,
  44. /// in which to find the Table-definitions.
  45. /// </summary>
  46. public static string ContentProviderName = "UDB";
  47. private const string CacheFileSuffix = ".cache";
  48. public static string ImplementationRoot
  49. {
  50. get { return s_implementationRoot; }
  51. }
  52. public static string ImplementationFolder
  53. {
  54. get { return s_implementationFolder; }
  55. }
  56. public static LightDBDefinitionSet Definitions
  57. {
  58. get { return s_definitions; }
  59. }
  60. public static LightDBMapper GetMapper<T>() where T : IDataHolder
  61. {
  62. return GetMapper(typeof(T));
  63. }
  64. public static LightDBMapper GetMapper(Type t)
  65. {
  66. EnsureInitialized();
  67. LightDBMapper mapper;
  68. if (!s_mappersByType.TryGetValue(t, out mapper))
  69. {
  70. throw new Exception(string.Format(
  71. "DataHolder Type \"{0}\" was not registered - Make sure that it's XML definition was defined and associated correctly. " +
  72. "If the Type is not in the Core, call ContentHandler.Initialize(Assembly) on its Assembly first.", t.FullName));
  73. }
  74. return mapper;
  75. }
  76. public static Dictionary<object, IDataHolder> GetObjectMap<T>() where T : IDataHolder
  77. {
  78. return GetMapper<T>().GetObjectMap<T>();
  79. }
  80. #region Events
  81. /// <summary>
  82. /// Reports incorrect data, that is not Database-provider dependent.
  83. /// </summary>
  84. /// <param name="msg"></param>
  85. /// <param name="args"></param>
  86. public static void OnInvalidClientData(string msg, params object[] args)
  87. {
  88. OnInvalidClientData(string.Format(msg, args));
  89. }
  90. /// <summary>
  91. /// Reports incorrect data, that is not Database-provider dependent.
  92. /// </summary>
  93. public static void OnInvalidClientData(string msg)
  94. {
  95. switch (ErrorResponse)
  96. {
  97. case ErrorResponse.Exception:
  98. {
  99. throw new ContentException("Error encountered when loading Content: " + msg);
  100. }
  101. case ErrorResponse.Warn:
  102. {
  103. log.Warn(msg);
  104. break;
  105. }
  106. default:
  107. break;
  108. }
  109. }
  110. /// <summary>
  111. /// Reports incorrect data, caused by the Database-provider.
  112. /// </summary>
  113. public static void OnInvalidDBData(string msg, params object[] args)
  114. {
  115. OnInvalidDBData(string.Format(msg, args));
  116. }
  117. /// <summary>
  118. /// Reports incorrect data, caused by the Database-provider.
  119. /// </summary>
  120. public static void OnInvalidDBData(string msg)
  121. {
  122. switch (ErrorResponse)
  123. {
  124. case ErrorResponse.Exception:
  125. {
  126. throw new ContentException("Error encountered when loading Content: " + msg);
  127. }
  128. case ErrorResponse.Warn:
  129. {
  130. log.Warn("<" + ContentProviderName + ">" + msg);
  131. break;
  132. }
  133. default:
  134. break;
  135. }
  136. }
  137. #endregion
  138. public static void EnsureInitialized()
  139. {
  140. if (s_mappersByType == null)
  141. {
  142. InitializeDefault();
  143. }
  144. }
  145. #region Init
  146. private static bool inited;
  147. [Initialization(InitializationPass.Second, "Initialize Content")]
  148. public static void Initialize()
  149. {
  150. InitializeAndLoad(typeof(ContentHandler).Assembly);
  151. }
  152. public static void InitializeDefault()
  153. {
  154. RealmDBUtil.Initialize();
  155. InitializeAndLoad(typeof(ContentHandler).Assembly);
  156. }
  157. public static void InitializeAndLoad(Assembly asm)
  158. {
  159. if (!inited)
  160. {
  161. Converters.Provider = new NHibernateConverterProvider();
  162. Initialize(asm);
  163. Load();
  164. inited = true;
  165. }
  166. }
  167. public static void Initialize(Assembly asm)
  168. {
  169. s_implementationRoot = Path.Combine(RealmServerConfiguration.ContentDir, "Impl");
  170. s_implementationFolder = Path.Combine(s_implementationRoot, ContentProviderName);
  171. var defs = DataHolderMgr.CreateDataHolderDefinitionArray(asm);
  172. LightDBMgr.InvalidDataHandler = OnInvalidDBData;
  173. s_definitions = new LightDBDefinitionSet(defs);
  174. }
  175. #endregion
  176. #region Check
  177. /// <summary>
  178. /// Checks the validity of all Table definitions
  179. /// </summary>
  180. /// <returns>The amount of invalid columns.</returns>
  181. public static int Check(Action<string> feedbackCallback)
  182. {
  183. EnsureInitialized();
  184. var count = 0;
  185. foreach (var mapper in s_mappersByType.Values)
  186. {
  187. foreach (var table in mapper.Mapping.TableDefinitions)
  188. {
  189. var fields = table.ColumnDefinitions;
  190. foreach (var field in fields)
  191. {
  192. if (!field.IsEmpty)
  193. {
  194. try
  195. {
  196. using ( //var reader =
  197. mapper.Wrapper.Query(SqlUtil.BuildSelect(new[] { field.ColumnName }, table.Name, "LIMIT 1")))
  198. {
  199. }
  200. }
  201. catch (Exception e)
  202. {
  203. feedbackCallback(
  204. string.Format("Invalid column \"{0}\" in table \"{1}\": {2}", field, table.Name,
  205. e.GetAllMessages().ToString("\n\t")));
  206. count++;
  207. }
  208. }
  209. }
  210. }
  211. }
  212. return count;
  213. }
  214. #endregion
  215. #region Loading
  216. public static void Load()
  217. {
  218. s_definitions.Clear();
  219. var tableFile = Path.Combine(s_implementationFolder, "Tables.xml");
  220. var dataDefDir = Path.Combine(s_implementationFolder, "Data");
  221. s_definitions.LoadTableDefinitions(tableFile);
  222. CheckVersion();
  223. s_definitions.LoadDataHolderDefinitions(dataDefDir);
  224. if (!ActiveRecordStarter.IsInitialized)
  225. {
  226. throw new InvalidOperationException("ActiveRecord must be initialized.");
  227. }
  228. s_mappersByType = CreateMappersByType();
  229. }
  230. public static void CheckVersion()
  231. {
  232. var dbVersion = s_definitions.DBVersionLocation;
  233. if (dbVersion != null && dbVersion.IsValid)
  234. {
  235. string versionStr;
  236. try
  237. {
  238. versionStr = (new NHibernateDbWrapper()).GetDatabaseVersion(dbVersion.Table, dbVersion.Column);
  239. }
  240. catch (Exception e)
  241. {
  242. throw new ContentException(e, "Unable to validate version of database content - Reqired " + dbVersion);
  243. }
  244. //var minVersion = dbVersion.MinVersion;
  245. //var maxVersion = dbVersion.MaxVersion;
  246. //float version;
  247. //if (!float.TryParse(versionStr, out version))
  248. //{
  249. // throw new ContentException(string.Format("Unable to read version from Database due to an invalid format: " + versionStr));
  250. //}
  251. //if (version < minVersion || version > maxVersion)
  252. //{
  253. // throw new ContentException(string.Format("Supplied database's version is {0}, while content provider only supports versions {1} through {2}",
  254. // versionStr, minVersion, maxVersion));
  255. //}
  256. }
  257. }
  258. public static Dictionary<Type, LightDBMapper> CreateMappersByType()
  259. {
  260. var map = new Dictionary<Type, LightDBMapper>();
  261. foreach (var mapping in s_definitions.Mappings)
  262. {
  263. var mapper = new LightDBMapper(mapping, new NHibernateDbWrapper());
  264. foreach (var def in mapping.DataHolderDefinitions)
  265. {
  266. map.Add(def.Type, mapper);
  267. }
  268. }
  269. return map;
  270. }
  271. /// <summary>
  272. /// Ensures that the DataHolder of the given type and those that are connected with it, are loaded.
  273. ///
  274. /// </summary>
  275. public static void Load<T>() where T : IDataHolder
  276. {
  277. Load<T>(false);
  278. }
  279. /// <summary>
  280. /// Ensures that the DataHolder of the given type and those that are connected with it, are loaded.
  281. ///
  282. /// </summary>
  283. /// <param name="force">Whether to re-load if already loaded.</param>
  284. public static void Load<T>(bool force) where T : IDataHolder
  285. {
  286. EnsureInitialized();
  287. var mapper = GetMapper<T>();
  288. if (force || !mapper.Fetched)
  289. {
  290. Load(mapper);
  291. }
  292. }
  293. public static void Load(LightDBMapper mapper)
  294. {
  295. Load(mapper, true);
  296. }
  297. public static void Load(LightDBMapper mapper, bool failException)
  298. {
  299. try
  300. {
  301. if (EnableCaching && mapper.SupportsCaching)
  302. {
  303. if (mapper.LoadCache())
  304. {
  305. // loaded cache successfully
  306. return;
  307. }
  308. }
  309. //Utility.Measure("Loading content from DB - " + mapper + "", 1, () => {
  310. mapper.Fetch();
  311. //});
  312. if (EnableCaching && mapper.SupportsCaching)
  313. {
  314. log.Info("Saving cache for: " + mapper.Mapping.DataHolderDefinitions.ToString(", "));
  315. mapper.SaveCache();
  316. }
  317. }
  318. catch (Exception e)
  319. {
  320. if (failException)
  321. {
  322. throw new ContentException(e, "Unable to load entries using \"{0}\"", mapper);
  323. }
  324. // LogUtil.ErrorException(e, "Unable to load entries using \"{0}\"", mapper);
  325. }
  326. }
  327. /// <summary>
  328. /// Fetches all content of all registered DataHolders.
  329. /// </summary>
  330. public static void FetchAll()
  331. {
  332. EnsureInitialized();
  333. foreach (var mapper in s_mappersByType.Values)
  334. {
  335. mapper.Fetch();
  336. }
  337. }
  338. #endregion
  339. #region Editing
  340. /// <summary>
  341. /// Updates changes to the Object in the underlying Database.
  342. /// FlushCommit() needs to be called to persist the operation.
  343. /// </summary>
  344. public static void CommitUpdate(this IDataHolder obj)
  345. {
  346. var mapper = GetMapper(obj.GetType());
  347. mapper.Update(obj);
  348. }
  349. /// <summary>
  350. /// Inserts the Object into the underlying Database.
  351. /// FlushCommit() needs to be called to persist the operation.
  352. /// </summary>
  353. public static void CommitInsert(this IDataHolder obj)
  354. {
  355. var mapper = GetMapper(obj.GetType());
  356. mapper.Insert(obj);
  357. }
  358. /// <summary>
  359. /// Deletes the Object from the underlying Database.
  360. /// FlushCommit() needs to be called to persist the operation.
  361. /// </summary>
  362. public static void CommitDelete(this IDataHolder obj)
  363. {
  364. var mapper = GetMapper(obj.GetType());
  365. mapper.Delete(obj);
  366. }
  367. /// <summary>
  368. /// Updates changes to the Object in the underlying Database.
  369. /// </summary>
  370. public static void CommitUpdateAndFlush(this IDataHolder obj)
  371. {
  372. obj.CommitUpdate();
  373. FlushCommit(obj.GetType());
  374. }
  375. /// <summary>
  376. /// Inserts the Object into the underlying Database.
  377. /// FlushCommit() needs to be called to persist the operation.
  378. /// </summary>
  379. public static void CommitInsertAndFlush(this IDataHolder obj)
  380. {
  381. obj.CommitInsert();
  382. FlushCommit(obj.GetType());
  383. }
  384. /// <summary>
  385. /// Deletes the Object from the underlying Database.
  386. /// FlushCommit() needs to be called to persist the operation.
  387. /// </summary>
  388. public static void CommitDeleteAndFlush(this IDataHolder obj)
  389. {
  390. obj.CommitDelete();
  391. FlushCommit(obj.GetType());
  392. }
  393. /// <summary>
  394. /// Ignore all changes before last FlushCommit() (will not change the Object's state).
  395. /// </summary>
  396. public static void IgnoreUnflushedChanges<T>() where T : IDataHolder
  397. {
  398. var mapper = GetMapper(typeof(T));
  399. mapper.IgnoreUnflushedChanges();
  400. }
  401. /// <summary>
  402. /// Flush all commited changes to the underlying Database.
  403. /// FlushCommit() needs to be called to persist the operation.
  404. /// Will be executed in the global IO context.
  405. /// </summary>
  406. public static void FlushCommit<T>() where T : IDataHolder
  407. {
  408. var mapper = GetMapper(typeof(T));
  409. RealmServer.Instance.ExecuteInContext(() => mapper.Flush());
  410. }
  411. /// <summary>
  412. /// Flush all commited changes to the underlying Database.
  413. /// FlushCommit() needs to be called to persist the operation.
  414. /// Will be executed in the global IO context.
  415. /// </summary>
  416. public static void FlushCommit(Type t)
  417. {
  418. var mapper = GetMapper(t);
  419. RealmServer.Instance.ExecuteInContext(() => mapper.Flush());
  420. }
  421. #endregion
  422. #region Caching
  423. public static void SaveCache(this LightDBMapper mapper)
  424. {
  425. // save cache after loading
  426. var file = GetCacheFilename(mapper);
  427. try
  428. {
  429. mapper.SaveCache(file);
  430. }
  431. catch (Exception e)
  432. {
  433. File.Delete(file);
  434. LogUtil.ErrorException(e, "Failed to save cache to file: " + file);
  435. }
  436. }
  437. private static bool LoadCache(this LightDBMapper mapper)
  438. {
  439. var file = GetCacheFilename(mapper);
  440. if (File.Exists(file))
  441. {
  442. try
  443. {
  444. // Utility.Measure("Loading contnt from Cache - " + mapper, 1, () => {
  445. if (mapper.LoadCache(file))
  446. {
  447. // loaded cache successfully
  448. return true;
  449. }
  450. log.Warn("Cache signature in file \"{0}\" is out of date.", file);
  451. log.Warn("Reloading content from Database...");
  452. //});
  453. //return;
  454. }
  455. catch (Exception ex)
  456. {
  457. if (ex is EndOfStreamException)
  458. {
  459. log.Warn("Cache signature in file \"{0}\" is out of date.", file);
  460. }
  461. else
  462. {
  463. LogUtil.ErrorException(ex, "Unable to load cache from \"" + file + "\".");
  464. }
  465. log.Warn("Reloading content from Database...");
  466. }
  467. }
  468. return false;
  469. }
  470. public static string GetCacheFilename(this LightDBMapper mapper)
  471. {
  472. var str = new StringBuilder(mapper.Mapping.DataHolderDefinitions.Length * 12);
  473. foreach (var def in mapper.Mapping.DataHolderDefinitions)
  474. {
  475. str.Append(def.Name);
  476. }
  477. str.Append(CacheFileSuffix);
  478. return RealmServerConfiguration.Instance.GetCacheFile(str.ToString());
  479. }
  480. /// <summary>
  481. /// Deletes all cache-files
  482. /// </summary>
  483. public static void PurgeCache()
  484. {
  485. foreach (var file in Directory.GetFiles(RealmServerConfiguration.Instance.CacheDir))
  486. {
  487. if (file.EndsWith(CacheFileSuffix))
  488. {
  489. File.Delete(file);
  490. }
  491. }
  492. }
  493. #endregion
  494. public static void SaveDefaultStubs()
  495. {
  496. SaveStubs(typeof(ContentHandler).Assembly);
  497. }
  498. public static void SaveStubs(Assembly asm)
  499. {
  500. EnsureInitialized();
  501. LightDBMgr.SaveAllStubs(Path.Combine(s_implementationRoot, ".stubs"),
  502. DataHolderMgr.CreateDataHolderDefinitionArray(asm));
  503. }
  504. }
  505. }