372 lines
16 KiB
C#

using ANX.Framework.Content.Pipeline.Serialization.Compiler;
using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.IO;
using System.Linq;
using System.Text;
using System.Xml;
using ANX.Framework.Content.Pipeline.Serialization;
using System.Diagnostics;
using System.Globalization;
using ANX.Framework.Content.Pipeline.Helpers;
namespace ANX.Framework.Content.Pipeline.Tasks
{
public class AnxBuildCache : IBuildCache
{
Dictionary<Uri, CacheData> cacheData = new Dictionary<Uri, CacheData>();
ImporterManager importerManager;
ProcessorManager processorManager;
ContentCompiler contentCompiler;
public AnxBuildCache(ImporterManager importerManager, ProcessorManager processorManager, ContentCompiler contentCompiler)
{
if (importerManager == null)
throw new ArgumentNullException("importerManager");
if (processorManager == null)
throw new ArgumentNullException("processorManager");
if (contentCompiler == null)
throw new ArgumentNullException("contentCompiler");
this.importerManager = importerManager;
this.processorManager = processorManager;
this.contentCompiler = contentCompiler;
}
public void LoadCache(Uri cacheFilePath)
{
if (cacheFilePath == null)
throw new ArgumentNullException("cacheFilePath");
if (!cacheFilePath.IsAbsoluteUri)
throw new ArgumentException("cacheFilePath must be absolute.");
if (!File.Exists(cacheFilePath.LocalPath))
throw new FileNotFoundException("cache file doesn't exist.", cacheFilePath.LocalPath);
using (XmlReader reader = XmlReader.Create(cacheFilePath.LocalPath))
{
XmlDocument document = new XmlDocument();
document.Load(reader);
//skip document root.
foreach (var assetNode in document.FirstChild.NextSibling.ChildNodes.GetAsEnumerable())
{
var data = new CacheData();
var uri = new Uri(assetNode.Attributes["Uri"].Value, UriKind.RelativeOrAbsolute);
foreach (var assetContentNode in assetNode.ChildNodes.GetAsEnumerable())
{
string name = assetContentNode.Name;
switch (name)
{
case "SourceWriteTime":
data.sourceWriteTime = new DateTime(long.Parse(assetContentNode.InnerText, CultureInfo.InvariantCulture), DateTimeKind.Utc);
break;
case "OutputWriteTime":
data.outputWriteTime = new DateTime(long.Parse(assetContentNode.InnerText, CultureInfo.InvariantCulture), DateTimeKind.Utc);
break;
case "ImporterType":
data.importerType = TypeHelper.GetType(assetContentNode.InnerText);
break;
case "ImporterAssemblyWriteTime":
data.importerAssemblyWriteTime = new DateTime(long.Parse(assetContentNode.InnerText, CultureInfo.InvariantCulture), DateTimeKind.Utc);
break;
case "ProcessorType":
data.processorType = TypeHelper.GetType(assetContentNode.InnerText);
break;
case "ProcessorAssemblyWriteTime":
data.processorAssemblyWriteTime = new DateTime(long.Parse(assetContentNode.InnerText, CultureInfo.InvariantCulture), DateTimeKind.Utc);
break;
case "CompilerType":
data.compilerType = TypeHelper.GetType(assetContentNode.InnerText);
break;
case "CompilerAssemblyWriteTime":
data.compilerAssemblyWriteTime = new DateTime(long.Parse(assetContentNode.InnerText, CultureInfo.InvariantCulture), DateTimeKind.Utc);
break;
case "ProcessorParameters":
foreach (var paramNode in assetContentNode.ChildNodes.GetAsEnumerable())
{
string paramName = paramNode.Attributes["Key"].Value;
if (paramNode.Attributes["IsNull"] != null && paramNode.Attributes["IsNull"].InnerText == "true")
data.processorParameters.Add(paramName, null);
else
data.processorParameters.Add(paramName, paramNode.InnerText);
}
break;
}
}
this.cacheData[uri] = data;
}
}
}
public void SaveCache(Uri cacheFilePath)
{
if (cacheFilePath == null)
throw new ArgumentNullException("cacheFilePath");
if (!cacheFilePath.IsAbsoluteUri)
throw new ArgumentException("cacheFilePath must be absolute.");
using (XmlWriter writer = XmlWriter.Create(cacheFilePath.LocalPath, new XmlWriterSettings() { Indent = true }))
{
writer.WriteStartDocument();
writer.WriteStartElement("CacheData");
foreach (var pair in this.cacheData)
{
var data = pair.Value;
writer.WriteStartElement("Asset");
writer.WriteAttributeString("Uri", pair.Key.OriginalString);
writer.WriteElementString("SourceWriteTime", data.sourceWriteTime.Ticks.ToString());
writer.WriteElementString("OutputWriteTime", data.outputWriteTime.Ticks.ToString());
if (data.importerType != null)
{
writer.WriteElementString("ImporterType", data.importerType.AssemblyQualifiedName);
//DateTimes are structs, so ne need to check for null.
writer.WriteElementString("ImporterAssemblyWriteTime", data.importerAssemblyWriteTime.Ticks.ToString(CultureInfo.InvariantCulture));
}
if (data.processorType != null)
{
writer.WriteElementString("ProcessorType", data.processorType.AssemblyQualifiedName);
writer.WriteElementString("ProcessorAssemblyWriteTime", data.processorAssemblyWriteTime.Ticks.ToString(CultureInfo.InvariantCulture));
}
if (data.compilerType != null)
{
writer.WriteElementString("CompilerType", data.compilerType.AssemblyQualifiedName);
writer.WriteElementString("CompilerAssemblyWriteTime", data.compilerAssemblyWriteTime.Ticks.ToString(CultureInfo.InvariantCulture));
}
writer.WriteStartElement("ProcessorParameters");
foreach (var parameterPair in data.processorParameters)
{
writer.WriteStartElement("Param");
writer.WriteAttributeString("Key", parameterPair.Key);
if (parameterPair.Value == null)
writer.WriteAttributeString("IsNull", "true");
else
try
{
writer.WriteValue(parameterPair.Value);
}
catch (Exception exc)
{
Trace.WriteLine(string.Format("Error while writing build.cache at processor parameter \"{0}\" for asset \"{1}\": {2}", parameterPair.Key, pair.Key.OriginalString, exc.Message));
}
writer.WriteEndElement();
}
writer.WriteEndElement();
writer.WriteEndElement();
}
writer.WriteEndElement();
writer.Flush();
}
}
public bool IsValid(BuildItem buildItem, Uri outputFilePath)
{
if (buildItem == null)
throw new ArgumentNullException("buildItem");
if (outputFilePath == null)
throw new ArgumentNullException("outputFilePath");
if (!File.Exists(GetAbsoluteFileName(buildItem.SourceFilename)) || !File.Exists(GetAbsoluteFileName(outputFilePath).LocalPath))
return false;
Uri filePath = new Uri(buildItem.SourceFilename, UriKind.RelativeOrAbsolute);
CacheData data;
if (cacheData.TryGetValue(filePath, out data))
{
if (File.GetLastWriteTimeUtc(filePath.OriginalString) != data.sourceWriteTime)
return false;
if (File.GetLastWriteTimeUtc(outputFilePath.OriginalString) != data.outputWriteTime)
return false;
Type importerType = importerManager.AvailableImporters.FirstOrDefault((x) => x.Key == buildItem.ImporterName).Value;
//The visual studio extension tries to set the importer types whenever it can, so the case when the importer is null should be rather rare and
//the extension should immediatly try to save the importer, same for the processor.
//For additional meta data, we check the write time of the assemblies that contain the used importer and processor. When a dependant assembly changes,
//the referencing assembly will have to relink and therefore the write time should change.
if (data.importerType != importerType)
return false;
else if (importerType != null)
{
if (!string.IsNullOrEmpty(importerType.Assembly.Location))
if (File.GetLastWriteTimeUtc(importerType.Assembly.Location) != data.importerAssemblyWriteTime)
return false;
}
var processor = processorManager.AvailableProcessors.FirstOrDefault((x) => x.Key == buildItem.ProcessorName).Value;
if (processor == null)
return false;
else
{
if (data.processorType != processor.GetType())
return false;
if (!string.IsNullOrEmpty(processor.GetType().Assembly.Location))
if (File.GetLastWriteTimeUtc(processor.GetType().Assembly.Location) != data.processorAssemblyWriteTime)
return false;
var compiler = this.contentCompiler.GetTypeWriter(processor.OutputType);
if (compiler == null)
return false;
if (compiler.GetType() != data.compilerType)
return false;
if (!string.IsNullOrEmpty(compiler.GetType().Assembly.Location))
if (File.GetLastWriteTimeUtc(compiler.GetType().Assembly.Location) != data.compilerAssemblyWriteTime)
return false;
}
if (buildItem.ProcessorParameters.Count != data.processorParameters.Count)
return false;
string[] keys = buildItem.ProcessorParameters.Keys.ToArray();
for (int i = 0; i < keys.Length; i++)
{
var key = keys[i];
object cachedValue;
if (!data.processorParameters.TryGetValue(key, out cachedValue))
return false;
var value = buildItem.ProcessorParameters[key];
if (!string.Equals(cachedValue, TypeHelper.ConvertToInvariantString(value)))
return false;
}
return true;
}
return false;
}
public void Refresh(BuildItem buildItem, Uri outputFilePath)
{
if (buildItem == null)
throw new ArgumentNullException("buildItem");
if (!File.Exists(GetAbsoluteFileName(buildItem.SourceFilename)))
throw new ArgumentException(string.Format("The file \"{0}\" does not exist.", buildItem.SourceFilename));
CacheData data = new CacheData();
if (File.Exists(GetAbsoluteFileName(outputFilePath.OriginalString)))
data.outputWriteTime = File.GetLastWriteTimeUtc(GetAbsoluteFileName(outputFilePath.OriginalString));
data.sourceWriteTime = File.GetLastWriteTimeUtc(buildItem.SourceFilename);
var importerType = importerManager.AvailableImporters.FirstOrDefault((x) => x.Key == buildItem.ImporterName).Value;
data.importerType = importerType;
if (importerType != null && File.Exists(importerType.Assembly.Location))
data.importerAssemblyWriteTime = File.GetLastWriteTimeUtc(importerType.Assembly.Location);
var processor = processorManager.AvailableProcessors.FirstOrDefault((x) => x.Key == buildItem.ProcessorName).Value;
if (processor == null)
data.processorType = null;
else
{
data.processorType = processor.GetType();
if (File.Exists(processor.GetType().Assembly.Location))
data.processorAssemblyWriteTime = File.GetLastWriteTimeUtc(processor.GetType().Assembly.Location);
var typeWriter = contentCompiler.GetTypeWriter(processor.OutputType);
if (typeWriter == null)
data.compilerType = null;
else
{
data.compilerType = typeWriter.GetType();
if (File.Exists(typeWriter.GetType().Assembly.Location))
data.compilerAssemblyWriteTime = File.GetLastWriteTimeUtc(typeWriter.GetType().Assembly.Location);
}
}
data.processorParameters.AddRange(buildItem.ProcessorParameters.Select((x) => new KeyValuePair<string,object>(x.Key, TypeHelper.ConvertToInvariantString(x.Value))));
this.cacheData[new Uri(buildItem.SourceFilename, UriKind.RelativeOrAbsolute)] = data;
}
public void Clear()
{
cacheData.Clear();
}
public void Remove(Uri filePath)
{
cacheData.Remove(filePath);
}
public Uri ProjectHome
{
get;
set;
}
private string GetAbsoluteFileName(string fileName)
{
if (Path.IsPathRooted(fileName))
return fileName;
var result = new Uri(GetProjectHome(), new Uri(fileName, UriKind.Relative)).LocalPath;
return result;
}
private Uri GetAbsoluteFileName(Uri fileName)
{
if (fileName == null)
throw new ArgumentNullException("fileName");
if (fileName.IsAbsoluteUri)
return fileName;
var result = new Uri(GetProjectHome(), fileName);
return result;
}
private Uri GetProjectHome()
{
if (ProjectHome != null)
return ProjectHome;
return new Uri(Environment.CurrentDirectory + "\\", UriKind.Absolute);
}
class CacheData
{
public DateTime outputWriteTime;
public DateTime sourceWriteTime;
public Type importerType;
public DateTime importerAssemblyWriteTime;
public Type processorType;
public DateTime processorAssemblyWriteTime;
public Type compilerType;
public DateTime compilerAssemblyWriteTime;
public OpaqueDataDictionary processorParameters = new OpaqueDataDictionary();
}
}
}