view api/WarFoundryLoader.cs @ 5:b9346894319c

Fixes #6 - Stop missing data folder being fatal * Add check that directory exists before trying to get file list * Add warning message if directory doesn't exist
author IBBoard <dev@ibboard.co.uk>
date Fri, 26 Dec 2008 12:45:32 +0000
parents 520818033bb6
children 613bc5eaac59
line wrap: on
line source

// WarFoundryLoader.cs
//
//  Copyright (C) 2007 IBBoard
//
// This library is free software; you can redistribute it and/or
// modify it under the terms of the GNU Lesser General Public
// License version 2.1 of the License as published by the Free
// Software Foundation.
//
// This library is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
// Lesser General Public License for more details.
//
// You should have received a copy of the GNU Lesser General Public
// License along with this library; if not, write to the Free Software
// Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA 
//
//

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;
		
		protected 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>
		public void 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>(); 
			
			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);
			}					
		}
		
		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>>>();
		}
		
		/// <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>
		protected void LoadFile(FileInfo file)
		{
			bool handled = false;
			
			if (!handled)
			{
				ICollection<IWarFoundryObject> objs = null;
				IWarFoundryFactory loadFactory = null;
				
				if (nonNativeFactories.Count > 0)
				{
					LogNotifier.Debug(GetType(), "Attempting to load "+file.FullName+" as a non-native file");
					
					foreach (INonNativeWarFoundryFactory factory in nonNativeFactories)
					{
						LogNotifier.Debug(GetType(), "Load using "+factory.GetType().AssemblyQualifiedName+"? " + (factory.CanHandleFileFormat(file) ? "yes" : "no"));
						
						if (factory.CanHandleFileFormat(file))
						{
							objs = factory.CreateObjectsFromFile(file);
							
							if (objs!=null)
							{
								loadFactory = factory;
								break;
							}
						}			         
					}
				}
				
				if (objs == null)
				{
					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;
							}
						}
					}
				}
					
				if (objs!=null)
				{
					AddLoadedObjects(objs, loadFactory);
				}
			}
		}
		
		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);
		}
		
		private void AddLoadedObject(IWarFoundryObject obj, IWarFoundryFactory factory)
		{
			SimpleSet<IWarFoundryObject> objs;
			loadedObjects.TryGetValue(factory, out objs);
			
			if (objs == null)
			{
				objs = new SimpleSet<IWarFoundryObject>();
				loadedObjects.Add(factory, objs);
			}
				
			objs.Add(obj);
		}
		
		protected virtual ZipFile MakeZipFile(FileInfo file)
		{
			return new ZipFile(file.FullName);
		}
		
		protected void StoreGameSystem(GameSystem system)
		{
			string sysid = system.ID.ToLower();
					
			if (systemsTable.ContainsKey(sysid))
			{
				LogNotifier.WarnFormat(GetType(), "System {0} ({1}) has already been loaded. Duplicate file ({3}) discarded", system.Name, system.ID, system.SourceFile.FullName);
			}
			else
			{
				systemsTable.Add(sysid, (GameSystem)system);
			}
		}
		
		
		/// <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;

			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);
			}

			if (subRaces.ContainsKey(race.SubID.ToLower()))
			{
				LogNotifier.WarnFormat(GetType(), "Race {0} ({1} - {2}) for system {3} ({4}) has already been loaded. Duplicate file ({5}) discarded", race.Name, race.ID, race.SubID, race.GameSystem.ID, race.GameSystem.Name, race.SourceFile.Name);
				race = null;
			}
			else
			{
				subRaces.Add(race.SubID.ToLower(), race);
			}
		}
		
		/// <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>
		/// An array of <see cref="GameSystem"/>s that are currently available.
		/// </returns>
		public GameSystem[] GetGameSystems()
		{
			if (systemsTable==null)
			{
				LoadFiles();
			}
			
			GameSystem[] systems = new GameSystem[systemsTable.Count];
			int iSys = 0;
			
			foreach (GameSystem sys in systemsTable.Values)
			{
				systems[iSys++] = sys;
			}
			
			return systems;
		}

		/// <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();
			
			foreach (string key in racesTable.Keys)
			{
				Console.WriteLine(key);
			}

			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();

			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;
			}
		}
	}
}