changeset 14:0770e5cbba7c

Closes #21 - File loading in order * Reworked LoadFiles to smaller methods for readability (also re #10) and structure * Now determine expected load return before loading then load all "expected GameSystem" before "expected Race" * Make "can load as race/game system/army" methods public in interface Re #22 - Get errored file loading * Created FileLoadFailure class and made LoadFiles return a list of them Also * Some code cleanup * Change to DictionaryUtils calls
author IBBoard <dev@ibboard.co.uk>
date Sun, 25 Jan 2009 14:03:20 +0000
parents ad8eaed12e66
children 306558904c2a
files IBBoard.WarFoundry.API.mdp api/Factories/AbstractNativeWarFoundryFactory.cs api/Factories/AbstractNonNativeFileExtensionWarFoundryFactory.cs api/Factories/AbstractWarFoundryFactory.cs api/Factories/IWarFoundryFactory.cs api/FileLoadFailure.cs api/Objects/GameSystem.cs api/Objects/Race.cs api/WarFoundryLoader.cs
diffstat 9 files changed, 414 insertions(+), 258 deletions(-) [+]
line wrap: on
line diff
--- a/IBBoard.WarFoundry.API.mdp	Thu Jan 22 20:26:08 2009 +0000
+++ b/IBBoard.WarFoundry.API.mdp	Sun Jan 25 14:03:20 2009 +0000
@@ -88,6 +88,7 @@
     <File name="api/Objects/WarFoundryStagedLoadingObject.cs" subtype="Code" buildaction="Compile" />
     <File name="api/Objects/IWarFoundryStagedLoadObject.cs" subtype="Code" buildaction="Compile" />
     <File name="api/Objects/DuplicateItemException.cs" subtype="Code" buildaction="Compile" />
+    <File name="api/FileLoadFailure.cs" subtype="Code" buildaction="Compile" />
   </Contents>
   <References>
     <ProjectReference type="Gac" localcopy="True" refto="System, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089" />
--- a/api/Factories/AbstractNativeWarFoundryFactory.cs	Thu Jan 22 20:26:08 2009 +0000
+++ b/api/Factories/AbstractNativeWarFoundryFactory.cs	Sun Jan 25 14:03:20 2009 +0000
@@ -58,39 +58,32 @@
 			}
 			catch(ZipException)
 			{
-				//Silently dispose - we don't support the file and our other methods should handle that
+				//Silently dispose as per spec for the method
 			}
 			
 			return zip;
 		}
 		
 		protected override bool CheckCanHandleFileFormat (ZipFile file)
-		{
-			bool canHandle = false;
-			
-			if (file!=null)
-			{			
-				canHandle = CheckCanHandleAsSystem(file) || CheckCanHandleAsRace(file) || CheckCanHandleAsArmy(file); 
-			}
-			
-			return canHandle;
+		{	
+			return CheckCanHandleFileAsGameSystem(file) || CheckCanHandleFileAsRace(file) || CheckCanHandleFileAsArmy(file); 
 		}
 		
-		protected bool CheckCanHandleAsSystem(ZipFile file)
+		protected override bool CheckCanHandleFileAsGameSystem(ZipFile file)
 		{
 			return file.ZipFileComment.StartsWith(SYSTEM_ZIP_IDENTIFIER) && CheckCanFindSystemFileContent(file);
 		}
 		
 		protected abstract bool CheckCanFindSystemFileContent(ZipFile file);
 		
-		protected bool CheckCanHandleAsRace(ZipFile file)
+		protected override bool CheckCanHandleFileAsRace(ZipFile file)
 		{
 			return file.ZipFileComment.StartsWith(RACE_ZIP_IDENTIFIER) && CheckCanFindRaceFileContent(file);
 		}
 		
 		protected abstract bool CheckCanFindRaceFileContent(ZipFile file);
 		
-		protected bool CheckCanHandleAsArmy(ZipFile file)
+		protected override bool CheckCanHandleFileAsArmy(ZipFile file)
 		{
 			return file.ZipFileComment.StartsWith(ARMY_ZIP_IDENTIFIER) && CheckCanFindArmyFileContent(file);
 		}
@@ -124,108 +117,10 @@
 			
 			return objects;
 		}
-
-		
-/*	
-		public override bool CanHandleArmyFileFormat(FileInfo file)
-		{
-			bool canHandle = false;
-			
-			try
-			{
-				ZipFile zip = new ZipFile(file.FullName);
-				canHandle = zip.ZipFileComment.StartsWith(ARMY_ZIP_IDENTIFIER) && CanHandleArmyFileFormat(zip);
-			}
-			catch (ZipException)
-			{
-				//Not a valid zip file so we can't handle it
-			}
-			
-			return canHandle;
-		}
-		
-		public override bool CanHandleRaceFileFormat(FileInfo file)
-		{
-			bool canHandle = false;
-			
-			try
-			{
-				ZipFile zip = new ZipFile(file.FullName);
-				canHandle = zip.ZipFileComment.StartsWith(RACE_ZIP_IDENTIFIER) && CanHandleRaceFileFormat(zip);
-			}
-			catch (ZipException)
-			{
-				//Not a valid zip file so we can't handle it
-			}
-			
-			return canHandle;
-		}
-		
-		public override bool CanHandleSystemFileFormat(FileInfo file)
-		{
-			bool canHandle = false;
-			
-			try
-			{
-				ZipFile zip = new ZipFile(file.FullName);
-				canHandle = zip.ZipFileComment.StartsWith(SYSTEM_ZIP_IDENTIFIER) && CanHandleSystemFileFormat(zip);
-			}
-			catch (ZipException)
-			{
-				//Not a valid zip file so we can't handle it
-			}
-			
-			return canHandle;
-		}
-		
-		public abstract bool CanHandleArmyFileFormat(ZipFile file);
-		public abstract bool CanHandleRaceFileFormat(ZipFile file);
-		public abstract bool CanHandleSystemFileFormat(ZipFile file);
-		
-		public override Army CreateArmyFromFile (FileInfo file)
-		{
-			try
-			{
-				ZipFile zip = new ZipFile(file.FullName);
-				return CreateArmyFromFile(zip);
-			}
-			catch(ZipException ex)
-			{
-				throw new InvalidFileException(Translation.GetTranslation("InvalidArmyFileException", "Cannot get Army for file {0} as it was not a recognised Army file", file.Name), ex);
-			}
-		}
-				
-		public override Race CreateRaceFromFile (FileInfo file)
-		{
-			try
-			{
-				ZipFile zip = new ZipFile(file.FullName);
-				return CreateRaceFromFile(zip);
-			}
-			catch(ZipException ex)
-			{
-				throw new InvalidFileException(Translation.GetTranslation("InvalidRaceFileException", "Cannot get Race for file {0} as it was not a recognised Race file", file.Name), ex);
-			}
-		}
-				
-		public override GameSystem CreateGameSystemFromFile (FileInfo file)
-		{
-			try
-			{
-				ZipFile zip = new ZipFile(file.FullName);
-				return CreateGameSystemFromFile(zip);
-			}
-			catch(ZipException ex)
-			{
-				throw new InvalidFileException(Translation.GetTranslation("InvalidGameSystemFileException", "Cannot get Game System for file {0} as it was not a recognised Race file", file.Name), ex);
-			}
-		}*/
 		
 		protected Army CreateArmyFromFile(ZipFile file)
 		{
-			Army army = CreateArmyFromStream(file, GetArmyDataStream(file));
-			//throw new InvalidFileException(Translation.GetTranslation("InvalidArmyFileException", "Cannot get Army for file {0} as it was not an Army file", file.Name));
-			return army;
+			return CreateArmyFromStream(file, GetArmyDataStream(file));
 		}
 		
 		protected abstract Stream GetArmyDataStream(ZipFile file);
@@ -235,9 +130,7 @@
 		{
 			try
 			{
-				Race race = CreateRaceFromStream(file, GetRaceDataStream(file));			
-				//throw new InvalidFileException(Translation.GetTranslation("InvalidRaceFileException", "Cannot get Race for file {0} as it was not a Race file", file.Name));
-				return race;
+				return CreateRaceFromStream(file, GetRaceDataStream(file));
 			}
 			catch (InvalidFileException ex)
 			{
@@ -250,9 +143,7 @@
 		
 		protected GameSystem CreateGameSystemFromFile(ZipFile file)
 		{
-			GameSystem system = CreateGameSystemFromStream(file, GetGameSystemDataStream(file));		
-			//throw new InvalidFileException(Translation.GetTranslation("InvalidGameSystemFileException", "Cannot get Game System for file {0} as it was not a Game System file", file.Name));
-			return system;
+			return CreateGameSystemFromStream(file, GetGameSystemDataStream(file));
 		}
 		
 		protected abstract Stream GetGameSystemDataStream(ZipFile file);
--- a/api/Factories/AbstractNonNativeFileExtensionWarFoundryFactory.cs	Thu Jan 22 20:26:08 2009 +0000
+++ b/api/Factories/AbstractNonNativeFileExtensionWarFoundryFactory.cs	Sun Jan 25 14:03:20 2009 +0000
@@ -33,20 +33,20 @@
 		
 		protected override bool CheckCanHandleFileFormat (FileInfo file)
 		{
-			return IsArmyFile(file) || IsRaceFile(file) || IsSystemFile(file);
+			return CheckCanHandleFileAsArmy(file) || CheckCanHandleFileAsRace(file) || CheckCanHandleFileAsGameSystem(file);
 		}
 		
-		protected bool IsArmyFile(FileInfo file)
+		protected override bool CheckCanHandleFileAsArmy(FileInfo file)
 		{
 			return ArmyFileExtension!=null && file.Name.ToLower().EndsWith(ArmyFileExtension);
 		}
 		
-		protected bool IsRaceFile(FileInfo file)
+		protected override bool CheckCanHandleFileAsRace(FileInfo file)
 		{
 			return RaceFileExtension!=null && file.Name.ToLower().EndsWith(RaceFileExtension);
 		}
 		
-		protected bool IsSystemFile(FileInfo file)
+		protected override bool CheckCanHandleFileAsGameSystem(FileInfo file)
 		{
 			return GameSystemFileExtension!=null && file.Name.ToLower().EndsWith(GameSystemFileExtension);
 		}
@@ -54,8 +54,7 @@
 		protected override FileInfo GetFileAsSupportedType (FileInfo file)
 		{
 			return file;
-		} 
-		
+		}		
 		
 		protected abstract Army CreateArmyFromFile(FileInfo file);
 		protected abstract Race CreateRaceFromFile(FileInfo file);
--- a/api/Factories/AbstractWarFoundryFactory.cs	Thu Jan 22 20:26:08 2009 +0000
+++ b/api/Factories/AbstractWarFoundryFactory.cs	Sun Jan 25 14:03:20 2009 +0000
@@ -34,7 +34,26 @@
 			
 		public bool CanHandleFileFormat (FileInfo file)
 		{
-			return CheckCanHandleFileFormat(GetFileAsSupportedType(file));
+			FILE_TYPE typedFile = GetFileAsSupportedType(file);
+			return typedFile != null && CheckCanHandleFileFormat(typedFile);
+		}
+
+		public bool CanHandleFileAsRace(FileInfo file)
+		{
+			FILE_TYPE typedFile = GetFileAsSupportedType(file);
+			return typedFile != null && CheckCanHandleFileAsRace(typedFile);
+		}
+
+		public bool CanHandleFileAsGameSystem(FileInfo file)
+		{
+			FILE_TYPE typedFile = GetFileAsSupportedType(file);
+			return typedFile != null && CheckCanHandleFileAsGameSystem(typedFile);
+		}
+
+		public bool CanHandleFileAsArmy(FileInfo file)
+		{
+			FILE_TYPE typedFile = GetFileAsSupportedType(file);
+			return typedFile != null && CheckCanHandleFileAsArmy(typedFile);
 		}
 		
 		/// <summary>
@@ -58,6 +77,40 @@
 		/// <code>true</code> if the factory thinks it can support the file, else <code>false</code>
 		/// </returns>
 		protected abstract bool CheckCanHandleFileFormat(FILE_TYPE file);
+
+		/// <summary>
+		/// Checks whether the factory thinks it can load data from the file in its paramaterised type as a Race object.
+		/// </summary>
+		/// <param name="file">
+		/// An object of the converted <see cref="FILE_TYPE"/> to check support for
+		/// </param>
+		/// <returns>
+		/// <code>true</code> if the factory thinks it can support the file as a Race, else <code>false</code>
+		/// </returns>
+		protected abstract bool CheckCanHandleFileAsRace(FILE_TYPE file);
+
+		/// <summary>
+		/// Checks whether the factory thinks it can load data from the file in its paramaterised type as a GameSystem object.
+		/// </summary>
+		/// <param name="file">
+		/// An object of the converted <see cref="FILE_TYPE"/> to check support for
+		/// </param>
+		/// <returns>
+		/// <code>true</code> if the factory thinks it can support the file as a GameSystem, else <code>false</code>
+		/// </returns>
+		protected abstract bool CheckCanHandleFileAsGameSystem(FILE_TYPE file);
+
+		/// <summary>
+		/// Checks whether the factory thinks it can load data from the file in its paramaterised type as an Army object.
+		/// </summary>
+		/// <param name="file">
+		/// An object of the converted <see cref="FILE_TYPE"/> to check support for
+		/// </param>
+		/// <returns>
+		/// <code>true</code> if the factory thinks it can support the file as a Army, else <code>false</code>
+		/// </returns>
+		protected abstract bool CheckCanHandleFileAsArmy(FILE_TYPE file);
+		
 		
 		public ICollection<IWarFoundryObject> CreateObjectsFromFile(FileInfo file)
 		{
--- a/api/Factories/IWarFoundryFactory.cs	Thu Jan 22 20:26:08 2009 +0000
+++ b/api/Factories/IWarFoundryFactory.cs	Sun Jan 25 14:03:20 2009 +0000
@@ -44,7 +44,40 @@
 		/// <returns>
 		/// <code>true</code> if the file appears to be supported for loading by this factory, else returns <code>false</code>
 		/// </returns>
-		bool CanHandleFileFormat(FileInfo file);	
+		bool CanHandleFileFormat(FileInfo file);
+
+		/// <summary>
+		/// Checks if the factory thinks it can handle the supplied file as a Race. Checks can be performed on file extension or some basic check of file content, or some other method.
+		/// </summary>
+		/// <param name="file">
+		/// A <see cref="FileInfo"/> for the file to check support for as a file containing Race information.
+		/// </param>
+		/// <returns>
+		/// <code>true</code> if the file appears to be supported for loading by this factory as a Race, else returns <code>false</code>
+		/// </returns>
+		bool CanHandleFileAsRace(FileInfo file);
+
+		/// <summary>
+		/// Checks if the factory thinks it can handle the supplied file as a GameSystem. Checks can be performed on file extension or some basic check of file content, or some other method.
+		/// </summary>
+		/// <param name="file">
+		/// A <see cref="FileInfo"/> for the file to check support for as a file containing GameSystem information.
+		/// </param>
+		/// <returns>
+		/// <code>true</code> if the file appears to be supported for loading by this factory as a GameSystem, else returns <code>false</code>
+		/// </returns>
+		bool CanHandleFileAsGameSystem(FileInfo file);
+
+		/// <summary>
+		/// Checks if the factory thinks it can handle the supplied file as a Army. Checks can be performed on file extension or some basic check of file content, or some other method.
+		/// </summary>
+		/// <param name="file">
+		/// A <see cref="FileInfo"/> for the file to check support for as a file containing Army information.
+		/// </param>
+		/// <returns>
+		/// <code>true</code> if the file appears to be supported for loading by this factory as a Army, else returns <code>false</code>
+		/// </returns>
+		bool CanHandleFileAsArmy(FileInfo file);
 		
 		/// <summary>
 		/// Reads the data from the supplied file and returns it as a collection of loadable objects.
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/api/FileLoadFailure.cs	Sun Jan 25 14:03:20 2009 +0000
@@ -0,0 +1,68 @@
+// This file (FileLoadFailure.cs) is a part of the IBBoard.WarFoundry.API project and is copyright 2009 IBBoard.
+//
+// The file and the library/program it is in are licensed under the GNU LGPL license. Please see COPYING.LGPL for more information and the full license.
+
+using System;
+using System.IO;
+using IBBoard.Lang;
+using IBBoard.WarFoundry.API.Factories;
+
+namespace IBBoard.WarFoundry.API
+{
+	public class FileLoadFailure
+	{
+		private FileInfo failedFile;
+		private IWarFoundryFactory loadingFactory;
+		private string defaultMessage;
+		private string messageTranslationID;
+		private string message;
+
+		public FileLoadFailure(FileInfo file, string message) : this (file, message, "")
+		{
+		}
+		
+		public FileLoadFailure(FileInfo file, string message, string translationID) : this (file, null, message, "")
+		{
+		}
+
+		public FileLoadFailure(FileInfo file, IWarFoundryFactory factory, string message) : this (file, factory, message, "")
+		{
+		}
+		
+		public FileLoadFailure(FileInfo file,IWarFoundryFactory factory, string message, string translationID)
+		{
+			failedFile = file;
+			loadingFactory = factory;
+			defaultMessage = message;
+			messageTranslationID = translationID;
+		}
+
+		public FileInfo FailedFile
+		{
+			get
+			{
+				return failedFile;
+			}
+		}
+
+		public string Message
+		{
+			get
+			{
+				if (message == null)
+				{
+					if (messageTranslationID == "")
+					{
+						message = String.Format(defaultMessage, FailedFile, loadingFactory);
+				 	}
+					else
+				 	{
+						message = Translation.GetTranslation(messageTranslationID, defaultMessage, failedFile, loadingFactory);
+					}
+				}
+				
+				return message;
+			}
+		}
+	}
+}
--- a/api/Objects/GameSystem.cs	Thu Jan 22 20:26:08 2009 +0000
+++ b/api/Objects/GameSystem.cs	Sun Jan 25 14:03:20 2009 +0000
@@ -67,7 +67,7 @@
 			get 
 			{ 
 				EnsureFullyLoaded();
-				return DictionaryToArrayConverter.Convert<string, Category>(RawCategories); 
+				return DictionaryUtils.ToArray<string, Category>(RawCategories); 
 			}
 		}
 		
--- a/api/Objects/Race.cs	Thu Jan 22 20:26:08 2009 +0000
+++ b/api/Objects/Race.cs	Sun Jan 25 14:03:20 2009 +0000
@@ -119,7 +119,7 @@
 				}
 				else
 				{
-					cats = DictionaryToArrayConverter.Convert<string, Category>(categories);
+					cats = DictionaryUtils.ToArray<string, Category>(categories);
 				}
 				
 				return cats;
@@ -192,7 +192,7 @@
 			}
 			else
 			{
-				unitTypesArray = DictionaryToArrayConverter.Convert<string, UnitType>(unitTypesDictionary);
+				unitTypesArray = DictionaryUtils.ToArray<string, UnitType>(unitTypesDictionary);
 			}
 			
 			return unitTypesArray;
--- a/api/WarFoundryLoader.cs	Thu Jan 22 20:26:08 2009 +0000
+++ b/api/WarFoundryLoader.cs	Sun Jan 25 14:03:20 2009 +0000
@@ -152,49 +152,22 @@
 		/// <summary>
 		/// Loads all of the data files in the registered directories.
 		/// </summary>
-		public void LoadFiles()
+		/// <returns>
+		/// A <see cref="Dictionary"/> of files that failed to load mapped against the message that their failure returned
+		/// </returns>
+		public List<FileLoadFailure> LoadFiles()
 		{
 			LogNotifier.Debug(GetType(), "Load files");
 			PrepareForFileLoad();
-			
-			foreach (DirectoryInfo directory in directories)
-			{
-				if (directory.Exists)
-				{
-					LogNotifier.Debug(GetType(), "Load from "+directory.FullName);
-				
-					foreach (FileInfo file in directory.GetFiles())
-					{
-						LoadFile(file);
-					}
-				}
-				else
-				{
-					LogNotifier.WarnFormat(GetType(), "Load for {0} failed because directory didn't exist", directory.FullName);
-				}
-			}
-			
-			ICollection<Race> races = new List<Race>(); 
+			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));
+
+			LogNotifier.Debug(GetType(), failedLoads.Count + " failed file loads");
 			
-			foreach (SimpleSet<IWarFoundryObject> objs in loadedObjects.Values)
-			{			
-				foreach (IWarFoundryObject obj in  objs)
-				{
-					if (obj is Race)
-					{
-						races.Add((Race)obj);
-					}
-					else if (obj is GameSystem)
-					{
-						StoreGameSystem((GameSystem)obj);
-					}
-				}
-			}
-			
-			foreach (Race race in races)
-			{
-				StoreRace(race);
-			}					
+			return failedLoads;
 		}
 		
 		protected void PrepareForFileLoad()
@@ -203,6 +176,179 @@
 			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 = GetGameSystemRaceLoadingFactoryForFile(file);
+				
+				if (factory != null)
+				{
+					loadableGameSystems.Add(file, factory);
+				}
+				else
+				{
+					factory = GetRaceLoadingFactoryForFile(file);
+	
+					if (factory!=null)
+					{
+						loadableRaces.Add(file, factory);
+					}
+					else
+					{
+						fails.Add(new FileLoadFailure(file, "FileNotHandled", "File not handled as a Race or Game System definition: {0}"));
+					}
+				}
+			}
+
+			return fails;
+		}
+
+		private IWarFoundryFactory GetGameSystemRaceLoadingFactoryForFile(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)
+			{
+				try
+				{
+					bool loaded = LoadObject(file, gameSystemFiles[file]);
+	
+					if (!loaded)
+					{
+						fails.Add(new FileLoadFailure(file, "FileLoadFailed", "Failed to load {0} as Race using {1}"));
+					}
+				}
+				catch (Exception ex)
+				{
+					fails.Add(new FileLoadFailure(file, ex.Message));
+				}
+			}
+			
+			return fails;
+		}
+
+		private List<FileLoadFailure> LoadRaces(Dictionary<FileInfo, IWarFoundryFactory> raceFiles)
+		{
+			List<FileLoadFailure> fails = new List<FileLoadFailure>();
+			
+			foreach (FileInfo file in raceFiles.Keys)
+			{
+				try
+				{
+					bool loaded = LoadObject(file, raceFiles[file]);
+	
+					if (!loaded)
+					{
+						fails.Add(new FileLoadFailure(file, "FileLoadFailed", "Failed to load {0} as Race using {1}"));
+					}
+				}
+				catch (Exception ex)
+				{
+					fails.Add(new FileLoadFailure(file, ex.Message));
+				}
+			}
+			
+			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.
@@ -210,60 +356,66 @@
 		/// <param name="file">
 		/// A <see cref="FileInfo"/> for the file to attempt to load
 		/// </param>
-		protected void LoadFile(FileInfo file)
+		/// <returns>
+		/// An ICollection of IWarFoundryObjects loaded from <code>file</code>
+		/// </returns>
+		public ICollection<IWarFoundryObject> LoadFile(FileInfo file)
 		{
-			bool handled = false;
+			ICollection<IWarFoundryObject> objs = null;			
+			IWarFoundryFactory loadFactory = null;
 			
-			if (!handled)
+			if (nonNativeFactories.Count > 0)
 			{
-				ICollection<IWarFoundryObject> objs = null;
-				IWarFoundryFactory loadFactory = null;
+				LogNotifier.Debug(GetType(), "Attempting to load "+file.FullName+" as a non-native file");
 				
-				if (nonNativeFactories.Count > 0)
+				foreach (INonNativeWarFoundryFactory factory in nonNativeFactories)
 				{
-					LogNotifier.Debug(GetType(), "Attempting to load "+file.FullName+" as a non-native file");
+					bool canLoad = factory.CanHandleFileFormat(file);
+					LogNotifier.Debug(GetType(), "Load using "+factory.GetType().FullName+"? " + (canLoad ? "yes" : "no"));
 					
-					foreach (INonNativeWarFoundryFactory factory in nonNativeFactories)
+					if (canLoad)
 					{
-						LogNotifier.Debug(GetType(), "Load using "+factory.GetType().AssemblyQualifiedName+"? " + (factory.CanHandleFileFormat(file) ? "yes" : "no"));
+						objs = factory.CreateObjectsFromFile(file);
 						
-						if (factory.CanHandleFileFormat(file))
+						if (objs!=null)
 						{
-							objs = factory.CreateObjectsFromFile(file);
-							
-							if (objs!=null)
-							{
-								loadFactory = factory;
-								break;
-							}
-						}			         
-					}
+							loadFactory = factory;
+							break;
+						}
+					}			         
 				}
+			}
+			
+			if (objs == null)
+			{
+				LogNotifier.Debug(GetType(), "Attempting to load "+file.FullName+" as native file");
 				
-				if (objs == null)
+				foreach (INativeWarFoundryFactory factory in factories)
 				{
-					LogNotifier.Debug(GetType(), "Attempting to load "+file.FullName+" as native file");
-					
-					foreach (INativeWarFoundryFactory factory in factories)
+					if (factory.CanHandleFileFormat(file))
 					{
-						if (factory.CanHandleFileFormat(file))
+						objs = factory.CreateObjectsFromFile(file);
+						
+						if (objs!=null)
 						{
-							objs = factory.CreateObjectsFromFile(file);
-							
-							if (objs!=null)
-							{
-								loadFactory = factory;
-								break;
-							}
+							loadFactory = factory;
+							break;
 						}
 					}
 				}
-					
-				if (objs!=null)
-				{
-					AddLoadedObjects(objs, loadFactory);
-				}
+			}
+				
+			if (objs!=null)
+			{
+				AddLoadedObjects(objs, loadFactory);
 			}
+			else
+			{
+				LogNotifier.Debug(GetType(), "Loading of "+file.FullName+" failed");
+				objs = new List<IWarFoundryObject>();
+			}
+
+			return objs;
 		}
 		
 		private void AddLoadedObjects(ICollection<IWarFoundryObject> loadedObjs, IWarFoundryFactory factory)
@@ -278,20 +430,22 @@
 			}
 				
 			objs.AddRange(loadedObjs);
+			StoreObjects(loadedObjs);
 		}
-		
-		private void AddLoadedObject(IWarFoundryObject obj, IWarFoundryFactory factory)
+
+		private void StoreObjects(ICollection<IWarFoundryObject> loadedObjects)
 		{
-			SimpleSet<IWarFoundryObject> objs;
-			loadedObjects.TryGetValue(factory, out objs);
-			
-			if (objs == null)
+			foreach (IWarFoundryObject loadedObject in loadedObjects)
 			{
-				objs = new SimpleSet<IWarFoundryObject>();
-				loadedObjects.Add(factory, objs);
+				if (loadedObject is GameSystem)
+				{
+					StoreGameSystem((GameSystem)loadedObject);
+				}
+				else if (loadedObject is Race)
+				{
+					StoreRace((Race)loadedObject);
+				}
 			}
-				
-			objs.Add(obj);
 		}
 		
 		protected virtual ZipFile MakeZipFile(FileInfo file)
@@ -313,24 +467,6 @@
 			}
 		}
 		
-		
-		/// <summary>
-		/// Stores a loaded <see cref="GameSystem"/> that has been generated outside the core loading structure.
-		/// 
-		/// Note: Calls to this function should be made before calls to StoreRace(Race, IWarFoundryFactory).
-		/// </summary>
-		/// <param name="system">
-		/// The <see cref="GameSystem"/> to register as loaded.
-		/// </param>
-		/// <param name="factory">
-		/// The <see cref="IWarFoundryFactory"/> that created it.
-		/// </param>
-		public void StoreGameSystem(GameSystem system, IWarFoundryFactory factory)
-		{
-			AddLoadedObject(system, factory);	
-			StoreGameSystem(system);
-		}
-		
 		protected void StoreRace(Race race)
 		{
 			Dictionary<string, Dictionary<string, Race>> systemRaces;
@@ -370,23 +506,6 @@
 		}
 		
 		/// <summary>
-		/// Stores a loaded <see cref="Race"/> that has been generated outside the core loading structure.
-		/// 
-		/// Note: Calls to this function should ensure that the relevant <see cref="GameSystem"> has been created first.
-		/// </summary>
-		/// <param name="race">
-		/// The <see cref="Race"/> to register as loaded.
-		/// </param>
-		/// <param name="factory">
-		/// The <see cref="IWarFoundryFactory"/> that created it
-		/// </param>
-		public void StoreRace(Race race, IWarFoundryFactory factory)
-		{
-			AddLoadedObject(race, factory);
-			StoreRace(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>
@@ -399,15 +518,7 @@
 				LoadFiles();
 			}
 			
-			GameSystem[] systems = new GameSystem[systemsTable.Count];
-			int iSys = 0;
-			
-			foreach (GameSystem sys in systemsTable.Values)
-			{
-				systems[iSys++] = sys;
-			}
-			
-			return systems;
+			return DictionaryUtils.ToArray<string, GameSystem>(systemsTable);
 		}
 
 		/// <summary>