Mercurial > repos > IBDev-IBBoard.WarFoundry.API
view api/WarFoundryLoader.cs @ 151:1d13820b3d96
Fixes #176: Bug when saving recently edited army
* Add loaded file cleanup to AbstractWarFoundryFactory
* Add override of method with Zip reference closing to WarFoundryXmlFactory
WarFoundry now no longer ends up with trailing handles to files, although why they only caused problems in some situations is unknown
Also:
* Some line ending fixes (curse cross-platform development and different line terminators!)
author | IBBoard <dev@ibboard.co.uk> |
---|---|
date | Sat, 26 Sep 2009 18:48:36 +0000 |
parents | 721891008b5c |
children | c931684f9024 |
line wrap: on
line source
// This file (WarFoundryLoader.cs) is a part of the IBBoard.WarFoundry.API project and is copyright 2007, 2008, 2009 IBBoard. // // The file and the library/program it is in are licensed and distributed, without warranty, under the GNU Affero GPL license, either version 3 of the License or (at your option) any later version. Please see COPYING for more information and the full license. using System; using System.Collections.Generic; using System.IO; using IBBoard.Collections; using IBBoard.IO; using IBBoard.Logging; using IBBoard.WarFoundry.API.Factories; using IBBoard.WarFoundry.API.Objects; using ICSharpCode.SharpZipLib.Zip; namespace IBBoard.WarFoundry.API { public class WarFoundryLoader { private static WarFoundryLoader loader; /// <summary> /// Gets the default <see cref="WarFoundryLoader"/> used to load WarFoundry data files. /// </summary> /// <returns> /// The default <see cref="WarFoundryLoader"/> /// </returns> public static WarFoundryLoader GetDefault() { if (loader == null) { loader = new WarFoundryLoader(); } return loader; } private ICollection<DirectoryInfo> directories; private ICollection<INativeWarFoundryFactory> factories; private ICollection<INonNativeWarFoundryFactory> nonNativeFactories; private Dictionary<string, GameSystem> systemsTable; private Dictionary<string, Dictionary<string, Dictionary<string, Race>>> racesTable; //Keys are: System, Race, SubRace private Dictionary<IWarFoundryFactory, SimpleSet<IWarFoundryObject>> loadedObjects; public delegate void FileLoadingCompleteDelegate(List<FileLoadFailure> failures); public event MethodInvoker FileLoadingStarted; public event FileLoadingCompleteDelegate FileLoadingFinished; private WarFoundryLoader() { directories = new List<DirectoryInfo>(); factories = new List<INativeWarFoundryFactory>(); nonNativeFactories = new List<INonNativeWarFoundryFactory>(); loadedObjects = new Dictionary<IWarFoundryFactory,SimpleSet<IWarFoundryObject>>(); } /// <summary> /// Adds a directory to the collection of directories that will be searched for WarFoundry data files. /// </summary> /// <param name="directory"> /// The <see cref="DirectoryInfo"/> to add to the list for searching for data files /// </param> public void AddLoadDirectory(DirectoryInfo directory) { if (!directories.Contains(directory)) { directories.Add(directory); } } /// <summary> /// Removes a directory from the collection of directories that will be searched for WarFoundry data files. /// </summary> /// <param name="directory"> /// A <see cref="DirectoryInfo"/> /// </param> public void RemoveLoadDirectory(DirectoryInfo directory) { if (directories.Contains(directory)) { directories.Remove(directory); } } /// <summary> /// Registers a <see cref="INativeWarFoundryFactory"/> as a factory that can parse native data files. /// </summary> /// <param name="factory"> /// The <see cref="INativeWarFoundryFactory"/> to register to parse native data files. /// </param> public void RegisterFactory(INativeWarFoundryFactory factory) { if (!factories.Contains(factory)) { factories.Add(factory); } } /// <summary> /// Unregisters a <see cref="INativeWarFoundryFactory"/> so that it will no longer be used to try to parse native data files. /// </summary> /// <param name="factory"> /// The <see cref="INativeWarFoundryFactory"/> to remove from the collection of factories that are used to try to parse native data files. /// </param> public void UnregisterFactory(INativeWarFoundryFactory factory) { if (factories.Contains(factory)) { factories.Remove(factory); } } /// <summary> /// Registers a <see cref="INonNativeWarFoundryFactory"/> so that it will be used to try to parse non-native data files from other applications. /// </summary> /// <param name="factory"> /// The <see cref="INonNativeWarFoundryFactory"/> to register to parse non-native data files. /// </param> public void RegisterNonNativeFactory(INonNativeWarFoundryFactory factory) { if (!nonNativeFactories.Contains(factory)) { nonNativeFactories.Add(factory); } } /// <summary> /// Unregisters a <see cref="INonNativeWarFoundryFactory"/> so that it will no longer be used to try to parse non-native data files from other applications. /// </summary> /// <param name="factory"> /// The <see cref="INonNativeWarFoundryFactory"/> to remove from the collection of factories that are used to try to parse non-native data files. /// </param> public void UnregisterNonNativeFactory(INonNativeWarFoundryFactory factory) { if (nonNativeFactories.Contains(factory)) { nonNativeFactories.Remove(factory); } } /// <summary> /// Loads all of the data files in the registered directories. /// </summary> /// <returns> /// A <see cref="List"/> of <see cref="FileLoadFailure"/> for files that failed to load /// </returns> public List<FileLoadFailure> LoadFiles() { LogNotifier.Debug(GetType(), "Load files"); PrepareForFileLoad(); Dictionary<FileInfo, IWarFoundryFactory> loadableRaces = new Dictionary<FileInfo, IWarFoundryFactory>(); Dictionary<FileInfo, IWarFoundryFactory> loadableGameSystems = new Dictionary<FileInfo, IWarFoundryFactory>(); List<FileLoadFailure> failedLoads = FillLoadableFiles(loadableRaces, loadableGameSystems); failedLoads.AddRange(LoadGameSystems(loadableGameSystems)); failedLoads.AddRange(LoadRaces(loadableRaces)); OnFileLoadingFinished(failedLoads); return failedLoads; } private void OnFileLoadingFinished(List<FileLoadFailure> failures) { if (FileLoadingFinished!=null) { FileLoadingFinished(failures); } } protected void PrepareForFileLoad() { //Just set up blank dictionaries for now - may try different and more complex handling in future systemsTable = new Dictionary<string,GameSystem>(); racesTable = new Dictionary<string,Dictionary<string,Dictionary<string,Race>>>(); } private List<FileLoadFailure> FillLoadableFiles(Dictionary<FileInfo, IWarFoundryFactory> loadableRaces, Dictionary<FileInfo, IWarFoundryFactory> loadableGameSystems) { List<FileLoadFailure> fails = new List<FileLoadFailure>(); foreach (DirectoryInfo directory in directories) { if (directory.Exists) { List<FileLoadFailure> directoryFails = FillLoadableFilesForDirectory(loadableRaces, loadableGameSystems, directory); fails.AddRange(directoryFails); } else { LogNotifier.WarnFormat(GetType(), "Load for {0} failed because directory didn't exist", directory.FullName); } } return fails; } private List<FileLoadFailure> FillLoadableFilesForDirectory(Dictionary<FileInfo, IWarFoundryFactory> loadableRaces, Dictionary<FileInfo, IWarFoundryFactory> loadableGameSystems, DirectoryInfo directory) { List<FileLoadFailure> fails = new List<FileLoadFailure>(); LogNotifier.Debug(GetType(), "Load from "+directory.FullName); foreach (FileInfo file in directory.GetFiles()) { IWarFoundryFactory factory = GetGameSystemLoadingFactoryForFile(file); if (factory != null) { loadableGameSystems.Add(file, factory); } else { factory = GetRaceLoadingFactoryForFile(file); if (factory!=null) { loadableRaces.Add(file, factory); } else { FileLoadFailure failure = new FileLoadFailure(file, "File not handled as a Race or Game System definition: {0}", "FileNotHandled"); fails.Add(failure); LogNotifier.Info(GetType(), failure.Message); } } } return fails; } private IWarFoundryFactory GetGameSystemLoadingFactoryForFile(FileInfo file) { IWarFoundryFactory loadingFactory = null; foreach (INonNativeWarFoundryFactory factory in nonNativeFactories) { if (factory.CanHandleFileAsGameSystem(file)) { loadingFactory = factory; break; } } if (loadingFactory == null) { foreach (INativeWarFoundryFactory factory in factories) { if (factory.CanHandleFileAsGameSystem(file)) { loadingFactory = factory; break; } } } return loadingFactory; } private IWarFoundryFactory GetRaceLoadingFactoryForFile(FileInfo file) { IWarFoundryFactory loadingFactory = null; foreach (INonNativeWarFoundryFactory factory in nonNativeFactories) { if (factory.CanHandleFileAsRace(file)) { loadingFactory = factory; break; } } if (loadingFactory == null) { foreach (INativeWarFoundryFactory factory in factories) { if (factory.CanHandleFileAsRace(file)) { loadingFactory = factory; break; } } } return loadingFactory; } private List<FileLoadFailure> LoadGameSystems(Dictionary<FileInfo, IWarFoundryFactory> gameSystemFiles) { List<FileLoadFailure> fails = new List<FileLoadFailure>(); foreach (FileInfo file in gameSystemFiles.Keys) { FileLoadFailure failure = null; try { bool loaded = LoadObject(file, gameSystemFiles[file]); if (!loaded) { failure = new FileLoadFailure(file, "FileLoadFailed", "Failed to load {0} as GameSystem using {1}"); } } catch (Exception ex) { failure = new FileLoadFailure(file, null, ex.Message, null, ex); } if (failure!=null) { fails.Add(failure); LogNotifier.Warn(GetType(), failure.Message, failure.Exception); } } return fails; } private List<FileLoadFailure> LoadRaces(Dictionary<FileInfo, IWarFoundryFactory> raceFiles) { List<FileLoadFailure> fails = new List<FileLoadFailure>(); foreach (FileInfo file in raceFiles.Keys) { FileLoadFailure failure = null; try { bool loaded = LoadObject(file, raceFiles[file]); if (!loaded) { failure = new FileLoadFailure(file, "FileLoadFailed", "Failed to load {0} as Race using {1}"); } } catch (Exception ex) { failure = new FileLoadFailure(file, null, ex.Message, null, ex); } if (failure!=null) { fails.Add(failure); LogNotifier.Warn(GetType(), failure.Message, failure.Exception); } } return fails; } private bool LoadObject(FileInfo file, IWarFoundryFactory factory) { bool loaded = false; LogNotifier.DebugFormat(GetType(), "Loading {0} using {1}", file.FullName, factory.GetType().Name); ICollection<IWarFoundryObject> objects = factory.CreateObjectsFromFile(file); if (objects.Count > 0) { AddLoadedObjects(objects, factory); loaded = true; } return loaded; } /// <summary> /// Loads a single file through the registered WarFoundryFactories, if a factory exists that supports the file format. /// </summary> /// <param name="file"> /// A <see cref="FileInfo"/> for the file to attempt to load /// </param> /// <returns> /// An ICollection of IWarFoundryObjects loaded from <code>file</code> /// </returns> public ICollection<IWarFoundryObject> LoadFile(FileInfo file) { ICollection<IWarFoundryObject> objs = null; IWarFoundryFactory loadFactory = null; try { objs = LoadFileWithNonNativeFactories(file, out loadFactory); if (objs == null) { objs = LoadFileWithNativeFactories(file, out loadFactory); } } catch (InvalidFileException ex) { LogNotifier.Error(GetType(), file.FullName+" failed to load", ex); } if (objs!=null) { AddLoadedObjects(objs, loadFactory); } else { objs = new List<IWarFoundryObject>(); } return objs; } private ICollection<IWarFoundryObject> LoadFileWithNonNativeFactories(FileInfo file, out IWarFoundryFactory loadFactory) { ICollection<IWarFoundryObject> objs = null; loadFactory = null; if (nonNativeFactories.Count > 0) { LogNotifier.Debug(GetType(), "Attempting to load "+file.FullName+" as a non-native file"); foreach (INonNativeWarFoundryFactory factory in nonNativeFactories) { bool canLoad = factory.CanHandleFileFormat(file); LogNotifier.Debug(GetType(), "Load using "+factory.GetType().FullName+"? " + (canLoad ? "yes" : "no")); if (canLoad) { objs = factory.CreateObjectsFromFile(file); if (objs!=null) { loadFactory = factory; break; } } } } return objs; } private ICollection<IWarFoundryObject> LoadFileWithNativeFactories(FileInfo file, out IWarFoundryFactory loadFactory) { ICollection<IWarFoundryObject> objs = null; loadFactory = null; if (factories.Count > 0) { LogNotifier.Debug(GetType(), "Attempting to load "+file.FullName+" as native file"); foreach (INativeWarFoundryFactory factory in factories) { if (factory.CanHandleFileFormat(file)) { objs = factory.CreateObjectsFromFile(file); if (objs!=null) { loadFactory = factory; break; } } } } return objs; } private void AddLoadedObjects(ICollection<IWarFoundryObject> loadedObjs, IWarFoundryFactory factory) { SimpleSet<IWarFoundryObject> objs; loadedObjects.TryGetValue(factory, out objs); if (objs == null) { objs = new SimpleSet<IWarFoundryObject>(); loadedObjects.Add(factory, objs); } objs.AddRange(loadedObjs); StoreObjects(loadedObjs); } private void StoreObjects(ICollection<IWarFoundryObject> loadedObjects) { foreach (IWarFoundryObject loadedObject in loadedObjects) { if (loadedObject is GameSystem) { StoreGameSystem((GameSystem)loadedObject); } else if (loadedObject is Race) { StoreRace((Race)loadedObject); } } } protected void StoreGameSystem(GameSystem system) { string sysid = system.ID.ToLower(); if (systemsTable.ContainsKey(sysid)) { GameSystem existingSystem = systemsTable[sysid]; if (!system.Equals(existingSystem)) { //TODO: Raise an event to say we got a different duplicate //We can't just fail, because failing is for completely unhandled files, not for objects in a file } } else { systemsTable.Add(sysid, (GameSystem)system); } } protected void StoreRace(Race race) { Dictionary<string, Dictionary<string, Race>> systemRaces; if (race.GameSystem == null) { throw new InvalidOperationException("Race cannot have null game system. Game system should be loaded before race."); } string systemID = race.GameSystem.ID.ToLower(); racesTable.TryGetValue(systemID, out systemRaces); if (systemRaces==null) { systemRaces = new Dictionary<string,Dictionary<string,Race>>(); racesTable.Add(systemID, systemRaces); } Dictionary<string, Race> subRaces; systemRaces.TryGetValue(race.ID.ToLower(), out subRaces); if (subRaces==null) { subRaces = new Dictionary<string,Race>(); systemRaces.Add(race.ID.ToLower(), subRaces); } string subID = race.SubID.ToLower(); if (subRaces.ContainsKey(subID)) { Race existingRace = subRaces[subID]; if (!race.Equals(existingRace)) { //TODO: Raise an event to say we got a different duplicate //We can't just fail, because failing is for completely unhandled files, not for objects in a file } } else { subRaces.Add(race.SubID.ToLower(), race); } } /// <summary> /// Gets all <see cref="GameSystem"/>s that are currently available, determined by those that can be loaded with the current <see cref="IWarFoundryFactory"/>s. /// </summary> /// <returns> /// An array of <see cref="GameSystem"/>s that are currently available. /// </returns> public GameSystem[] GetGameSystems() { if (systemsTable==null) { LoadFiles(); } return DictionaryUtils.ToArray<string, GameSystem>(systemsTable); } /// <summary> /// Gets a single <see cref="GameSystem"/> with a given ID. /// </summary> /// <param name="systemID"> /// The ID of the <see cref="GameSystem"/> to get, as a <see cref="System.String"/>. /// </param> /// <returns> /// The <see cref="GameSystem"/> with the given ID, or <code>null</code> if one doesn't exist. /// </returns> public GameSystem GetGameSystem(string systemID) { if (systemsTable==null) { LoadFiles(); } GameSystem system; systemsTable.TryGetValue(systemID.ToLower(), out system); return system; } /// <summary> /// Gets an array of the races for the specified <see cref="GameSystem"/>. /// </summary> /// <param name="system"> /// The <see cref="GameSystem"/> to get the available races for. /// </param> /// <returns> /// An array of <see cref="Race"/>s for the <see cref="GameSystem"/> /// </returns> public Race[] GetRaces(GameSystem system) { return GetRaces(system.ID); } /// <summary> /// Gets an array of the races for a game system by ID. /// </summary> /// <param name="systemID"> /// The <see cref="System.String"/> ID of the game system to get races for /// </param> /// <returns> /// An array of <see cref="Race"/>s for the specified game system /// </returns> public Race[] GetRaces(string systemID) { if (racesTable==null) { LoadFiles(); } systemID = systemID.ToLower(); Dictionary<string, Dictionary<string, Race>> system; racesTable.TryGetValue(systemID, out system); if (system==null) { return new Race[0]; } int count = 0; foreach (Dictionary<string, Race> racesDict in system.Values) { count+= racesDict.Count; } Race[] races = new Race[count]; int i = 0; foreach (string raceID in system.Keys) { foreach (string raceSubId in system[raceID].Keys) { races[i++] = GetRace(systemID, raceID, raceSubId); } } return races; } /// <summary> /// Gets a single race for a given <see cref="GameSystem"/> by ID of the race. /// </summary> /// <param name="system"> /// The <see cref="GameSystem"/> that the race is part of. /// </param> /// <param name="raceID"> /// A <see cref="System.String"/> ID for the race to load. /// </param> /// <returns> /// A <see cref="Race"/> with the specified ID from the <see cref="GameSystem"/>, or <code>null</code> if one doesn't exist. /// </returns> public Race GetRace(GameSystem system, string raceID) { return GetRace(system.ID, raceID); } /// <summary> /// Gets a single race for a given game system by ID of the game system and race. /// </summary> /// <param name="systemID"> /// The <see cref="System.String"/> ID of the game system that the race is part of. /// </param> /// <param name="raceID"> /// The <see cref="System.String"/> ID for the race to load. /// </param> /// <returns> /// A <see cref="Race"/> with the specified ID from the game system with the specified ID, or <code>null</code> if there is no race or game system with those IDs. /// </returns> public Race GetRace(string systemID, string raceID) { return GetRace(systemID, raceID, ""); } /// <summary> /// Gets a single race for a given <see cref="GameSystem"/> by the race's ID and sub-race ID. /// </summary> /// <param name="system"> /// The <see cref="GameSystem"/> that the race is part of. /// </param> /// <param name="raceID"> /// The <see cref="System.String"/> ID for the race to load. /// </param> /// <param name="raceSubID"> /// A <see cref="System.String"/> /// </param> /// <returns> /// A <see cref="Race"/> /// </returns> public Race GetRace(GameSystem system, string raceID, string raceSubID) { return GetRace(system.ID, raceID, raceSubID); } /// <summary> /// Gets a single race for a given game system by the game system's ID and the race's ID and sub-race ID. /// </summary> /// <param name="systemID"> /// The <see cref="System.String"/> ID of the game system that the race is part of. /// </param> /// <param name="raceID"> /// The <see cref="System.String"/> ID for the race to load. /// </param> /// <param name="raceSubID"> /// A <see cref="System.String"/> /// </param> /// <returns> /// A <see cref="Race"/> /// </returns> public Race GetRace(string systemID, string raceID, string raceSubID) { if (racesTable==null) { LoadFiles(); } Race race = null; systemID = systemID.ToLower(); raceID = raceID.ToLower(); raceSubID = raceSubID.ToLower(); Dictionary<string, Dictionary<string, Race>> races; racesTable.TryGetValue(systemID, out races); if (races!=null) { Dictionary<string, Race> subraces; races.TryGetValue(raceID, out subraces); if (subraces!=null) { subraces.TryGetValue(raceSubID, out race); } } return race; } /// <summary> /// Gets the IDs of all of the game systems currently available. /// </summary> /// <returns> /// An array of <see cref="System.String"/>s representing the IDs of the game systems. /// </returns> public string[] GetGameSystemIDs() { if (systemsTable==null) { LoadFiles(); } string[] keys = new string[systemsTable.Keys.Count]; int i = 0; foreach (string key in systemsTable.Keys) { keys[i++] = key; } return keys; } /// <summary> /// Gets the IDs of all of the races of a specified game system. /// </summary> /// <param name="system"> /// The <see cref="GameSystem"/> to get the available races for. /// </param> /// <returns> /// An array of <see cref="System.String"/>s representing the IDs of the races of the specified game system. /// </returns> public string[] GetSystemRaceIDs(GameSystem system) { return GetSystemRaceIDs(system.ID); } /// <summary> /// Gets the IDs of all of the races of a specified game system. /// </summary> /// <param name="systemID"> /// The <see cref="System.String"/> ID of the game system to get the available races for. /// </param> /// <returns> /// An array of <see cref="System.String"/>s representing the IDs of the races of the specified game system. /// </returns> public string[] GetSystemRaceIDs(string systemID) { if (racesTable == null) { LoadFiles(); } Dictionary<string, Dictionary<string, Race>> races = racesTable[systemID.ToLower()]; if (races==null) { return new string[0]; } else { string[] keys = new string[races.Keys.Count]; int i = 0; foreach (string key in races.Keys) { keys[i++] = key; } return keys; } } public Army LoadArmy(FileInfo file) { IWarFoundryFactory factory = GetArmyLoadingFactoryForFile(file); Army loadedArmy = null; if (factory != null) { ICollection<IWarFoundryObject> objs = factory.CreateObjectsFromFile(file); if (objs.Count == 1) { foreach (IWarFoundryObject obj in objs) { if (obj is Army) { loadedArmy = (Army) obj; } } } } return loadedArmy; } private IWarFoundryFactory GetArmyLoadingFactoryForFile(FileInfo file) { IWarFoundryFactory loadingFactory = null; foreach (INonNativeWarFoundryFactory factory in nonNativeFactories) { if (factory.CanHandleFileAsArmy(file)) { loadingFactory = factory; break; } } if (loadingFactory == null) { foreach (INativeWarFoundryFactory factory in factories) { if (factory.CanHandleFileAsArmy(file)) { loadingFactory = factory; break; } } } return loadingFactory; } } }