373 lines
11 KiB
C#
373 lines
11 KiB
C#
using System;
|
|
using System.Linq;
|
|
using System.Text;
|
|
using System.Xml.Linq;
|
|
using System.Collections.Generic;
|
|
using System.Reflection;
|
|
|
|
namespace WixIncludeMake
|
|
{
|
|
public class Program
|
|
{
|
|
private const string DB_FILE = "generated-keys.xml";
|
|
private const string USER_FILE = "fixed-keys.xml";
|
|
private static readonly string DIR_SEP = System.IO.Path.DirectorySeparatorChar.ToString();
|
|
|
|
public static readonly XNamespace XWIX = "http://schemas.microsoft.com/wix/2006/wi";
|
|
|
|
private interface IFolder
|
|
{
|
|
Dictionary<string, FolderInfo> Folders { get; }
|
|
Dictionary<string, FileInfo> Files { get; }
|
|
}
|
|
|
|
private class FolderInfo : IFolder
|
|
{
|
|
public Dictionary<string, FolderInfo> Folders { get; private set; }
|
|
public Dictionary<string, FileInfo> Files { get; private set; }
|
|
public string Name { get; set; }
|
|
private string m_id;
|
|
public string ID { get { return m_id.Replace(" ", "").Replace("-", "").ToUpperInvariant(); } set { m_id = value; } }
|
|
public Guid GUID { get; set; }
|
|
|
|
private FolderInfo()
|
|
{
|
|
Folders = new Dictionary<string, FolderInfo>();
|
|
Files = new Dictionary<string, FileInfo>();
|
|
}
|
|
|
|
public FolderInfo(string name, string id, Guid guid)
|
|
: this()
|
|
{
|
|
this.Name = name;
|
|
this.ID = id;
|
|
this.GUID = guid;
|
|
}
|
|
|
|
public IEnumerable<XElement> FileNodes
|
|
{
|
|
get
|
|
{
|
|
return
|
|
from x in Files
|
|
orderby x.Key
|
|
select
|
|
new XElement(XWIX + "File",
|
|
new XAttribute("Id", "file_" + x.Value.ID),
|
|
new XAttribute("Checksum", "yes"),
|
|
new XAttribute("Source", x.Value.SourceFile)
|
|
);
|
|
}
|
|
}
|
|
|
|
public XElement FolderNode
|
|
{
|
|
get
|
|
{
|
|
return
|
|
new XElement(XWIX + "Directory",
|
|
new XAttribute("Id", "dir_" + this.ID),
|
|
new XAttribute("Name", this.Name),
|
|
new XElement(XWIX + "Component",
|
|
new XAttribute("Id", "comp_" + this.ID),
|
|
new XAttribute("DiskId", 1),
|
|
new XAttribute("KeyPath", "yes"),
|
|
new XAttribute("Guid", this.GUID.ToString()),
|
|
new XAttribute("Win64", "$(var.Win64)"),
|
|
FileNodes
|
|
),
|
|
from x in Folders
|
|
orderby x.Key
|
|
select x.Value.FolderNode
|
|
);
|
|
|
|
}
|
|
}
|
|
|
|
public IEnumerable<FolderInfo> Subfolders
|
|
{
|
|
get
|
|
{
|
|
List<FolderInfo> res = new List<FolderInfo>();
|
|
res.Add (this);
|
|
foreach(var f in this.Folders.Values)
|
|
res.AddRange(f.Subfolders);
|
|
return res;
|
|
}
|
|
}
|
|
}
|
|
|
|
private class FileInfo
|
|
{
|
|
public string Name { get; set; }
|
|
private string m_id;
|
|
public string ID { get { return m_id.Replace(" ", "").Replace("-", "").ToUpperInvariant(); } set { m_id = value; } }
|
|
public string SourceFile { get; set; }
|
|
|
|
public FileInfo(string name, string id, string sourcefile)
|
|
{
|
|
this.Name = name;
|
|
this.ID = id;
|
|
this.SourceFile = sourcefile;
|
|
}
|
|
}
|
|
|
|
private class RootFolderInfo : FolderInfo
|
|
{
|
|
public Dictionary<string, string> GeneratedKeys { get; private set; }
|
|
public Dictionary<string, string> UserKeys { get; private set; }
|
|
public string ComponentID { get; set; }
|
|
private readonly Options m_opt;
|
|
|
|
public RootFolderInfo(Options opt, IEnumerable<string> paths = null)
|
|
: base("", "", Guid.NewGuid())
|
|
{
|
|
GeneratedKeys = ReadKeyDatabase(opt.dbfilename);
|
|
UserKeys = ReadKeyDatabase(opt.userfilename);
|
|
ComponentID = opt.componentid;
|
|
m_opt = opt;
|
|
|
|
string id;
|
|
if (!GeneratedKeys.TryGetValue(DIR_SEP, out id))
|
|
id = Guid.NewGuid().ToString();
|
|
|
|
if (UserKeys.ContainsKey(DIR_SEP))
|
|
id = UserKeys[DIR_SEP];
|
|
|
|
this.ID = id;
|
|
|
|
Add (paths);
|
|
}
|
|
|
|
public void Add(IEnumerable<string> paths)
|
|
{
|
|
if (paths == null)
|
|
return;
|
|
|
|
foreach(var p in paths)
|
|
Add (p);
|
|
}
|
|
|
|
public void Add(string path)
|
|
{
|
|
if (string.IsNullOrEmpty(path))
|
|
return;
|
|
|
|
bool isDir = false;
|
|
if (path.EndsWith(DIR_SEP))
|
|
{
|
|
isDir = true;
|
|
path = path.Substring(0, path.Length - 1);
|
|
}
|
|
|
|
List<string> folders = new List<string>();
|
|
string filename = null;
|
|
|
|
while(!string.IsNullOrEmpty(path))
|
|
{
|
|
string sourcepath = System.IO.Path.GetDirectoryName(path);
|
|
string elname = System.IO.Path.GetFileName(path);
|
|
folders.Add(elname);
|
|
|
|
path = sourcepath;
|
|
if (path.EndsWith(DIR_SEP))
|
|
path = path.Substring(0, path.Length - 1);
|
|
|
|
}
|
|
|
|
if (!isDir)
|
|
{
|
|
filename = folders[0];
|
|
folders.RemoveAt (0);
|
|
}
|
|
|
|
IFolder cur = this;
|
|
folders.Reverse();
|
|
|
|
StringBuilder sbp = new StringBuilder();
|
|
|
|
foreach(var f in folders)
|
|
{
|
|
sbp.Append(f);
|
|
sbp.Append(DIR_SEP);
|
|
|
|
FolderInfo next;
|
|
if (!cur.Folders.TryGetValue(f, out next))
|
|
{
|
|
//We have a new folder
|
|
string id;
|
|
if (!GeneratedKeys.TryGetValue(sbp.ToString(), out id))
|
|
{
|
|
id = Guid.NewGuid().ToString();
|
|
GeneratedKeys.Add(sbp.ToString(), id);
|
|
}
|
|
|
|
Guid g;
|
|
try { g = new Guid(id); }
|
|
catch
|
|
{
|
|
g = Guid.NewGuid();
|
|
GeneratedKeys[sbp.ToString()] = g.ToString();
|
|
Console.Error.WriteLine ("Unable to parse {0} into a GUID, I told you not to edit the file!!!{1}A new GUID will be used: {2}", id, Environment.NewLine, g);
|
|
}
|
|
|
|
string userkey;
|
|
if (!UserKeys.TryGetValue(sbp.ToString(), out userkey))
|
|
userkey = g.ToString();
|
|
|
|
next = new FolderInfo(f, userkey, g);
|
|
cur.Folders.Add(f, next);
|
|
}
|
|
|
|
cur = next;
|
|
}
|
|
|
|
if (!string.IsNullOrEmpty(filename))
|
|
{
|
|
sbp.Append(filename);
|
|
|
|
string id;
|
|
if (!GeneratedKeys.TryGetValue(sbp.ToString(), out id))
|
|
GeneratedKeys.Add(sbp.ToString(), id = Guid.NewGuid().ToString());
|
|
if (UserKeys.ContainsKey(sbp.ToString()))
|
|
id = UserKeys[sbp.ToString()];
|
|
|
|
string fullpath = System.IO.Path.Combine(m_opt.fileprefix, sbp.ToString()).Replace(DIR_SEP, "\\");
|
|
|
|
cur.Files.Add(filename, new FileInfo(filename, id, fullpath));
|
|
}
|
|
}
|
|
|
|
public IEnumerable<XElement> Node
|
|
{
|
|
get
|
|
{
|
|
List<XElement> res = new List<XElement>();
|
|
res.Add(new XElement(XWIX + "Component",
|
|
new XAttribute("Id", "comp_" + this.ID),
|
|
new XAttribute("DiskId", 1),
|
|
new XAttribute("KeyPath", "yes"),
|
|
new XAttribute("Guid", this.GUID.ToString()),
|
|
new XAttribute("Win64", "$(var.Win64)"),
|
|
FileNodes
|
|
));
|
|
|
|
res.AddRange(
|
|
from x in Folders
|
|
orderby x.Key
|
|
select x.Value.FolderNode
|
|
);
|
|
|
|
return res;
|
|
}
|
|
}
|
|
|
|
public XElement GroupNode
|
|
{
|
|
get
|
|
{
|
|
return new XElement(XWIX + "ComponentGroup",
|
|
new XAttribute("Id", this.ComponentID),
|
|
from x in this.Subfolders
|
|
select new XElement(XWIX + "ComponentRef",
|
|
new XAttribute("Id", "comp_" + x.ID)
|
|
)
|
|
);
|
|
}
|
|
}
|
|
}
|
|
|
|
public static string GuidToString(Guid g)
|
|
{
|
|
StringBuilder sb = new StringBuilder();
|
|
foreach(var b in g.ToByteArray())
|
|
sb.Append(string.Format("{0:x2}", b));
|
|
return sb.ToString();
|
|
}
|
|
|
|
private class Options
|
|
{
|
|
public string dbfilename = System.IO.Path.Combine(Environment.CurrentDirectory, DB_FILE);
|
|
public string userfilename = System.IO.Path.Combine(Environment.CurrentDirectory, USER_FILE);
|
|
public string sourcefolder = System.IO.Path.GetFullPath(Environment.CurrentDirectory);
|
|
public string outputfile = System.IO.Path.GetFullPath("binfiles.wxs");
|
|
public string ignorefilter = null;
|
|
public string dirref = "INSTALLLOCATION";
|
|
public string componentid = "FILES_COMPONENT";
|
|
public string fileprefix = "";
|
|
|
|
public void Fixup()
|
|
{
|
|
sourcefolder = Duplicati.Library.Utility.Utility.AppendDirSeparator(System.IO.Path.GetFullPath(sourcefolder.Replace("/", DIR_SEP)));
|
|
outputfile = System.IO.Path.GetFullPath(outputfile.Replace("/", DIR_SEP));
|
|
fileprefix = fileprefix.Replace("/", "\\");
|
|
if (ignorefilter != null)
|
|
ignorefilter = ignorefilter.Replace("/", DIR_SEP);
|
|
}
|
|
}
|
|
|
|
public static void Main (string[] _args)
|
|
{
|
|
List<string> args = new List<string>(_args);
|
|
Dictionary<string, string> options = Duplicati.Library.Utility.CommandLineParser.ExtractOptions(args);
|
|
Options opt = new Options();
|
|
|
|
foreach(FieldInfo fi in opt.GetType().GetFields())
|
|
if (options.ContainsKey(fi.Name))
|
|
fi.SetValue(opt, options[fi.Name]);
|
|
|
|
opt.Fixup();
|
|
|
|
Duplicati.Library.Utility.IFilter filter = null;
|
|
if (!string.IsNullOrEmpty(opt.ignorefilter))
|
|
filter = new Duplicati.Library.Utility.FilterExpression(opt.ignorefilter, false);
|
|
|
|
var paths = Duplicati.Library.Utility.Utility.EnumerateFileSystemEntries(opt.sourcefolder, filter).Select(x => x.Substring(opt.sourcefolder.Length));
|
|
|
|
//A bit backwards, but we have flattend the file list, and now we re-construct the tree,
|
|
// but we do not care much about performance here
|
|
RootFolderInfo rootfolder = new RootFolderInfo(opt, paths);
|
|
|
|
new XDocument(
|
|
new XElement(XWIX + "Wix",
|
|
new XElement(XWIX + "Fragment",
|
|
rootfolder.GroupNode,
|
|
new XElement(XWIX + "DirectoryRef",
|
|
new XAttribute("Id", opt.dirref),
|
|
rootfolder.Node
|
|
)
|
|
)
|
|
)
|
|
).Save(opt.outputfile);
|
|
|
|
WriteKeyDatabase(rootfolder.GeneratedKeys, opt.dbfilename, true);
|
|
|
|
Console.WriteLine("Generated wxs: {0}", opt.outputfile);
|
|
|
|
}
|
|
|
|
private static Dictionary<string, string> ReadKeyDatabase(string filename)
|
|
{
|
|
if (!System.IO.File.Exists(filename))
|
|
return new Dictionary<string, string>();
|
|
|
|
return XDocument.Load(filename).Element("root").Elements("entry").ToSafeDictionary(
|
|
c => c.Attribute("name").Value.Replace("\\", DIR_SEP), c => c.Attribute("key").Value, filename);
|
|
}
|
|
|
|
private static void WriteKeyDatabase(Dictionary<string, string> keys, string filename, bool isKeyDb)
|
|
{
|
|
new XDocument(
|
|
new XComment(isKeyDb ? "This file is autogenerated, do not edit! EVER!" : "format is <entry name=\"name\" key=\"key\" />"),
|
|
new XElement("root",
|
|
from x in keys
|
|
select new XElement("entry",
|
|
new XAttribute("name", x.Key.Replace(DIR_SEP, "\\")),
|
|
new XAttribute("key", x.Value)
|
|
)
|
|
)
|
|
).Save(filename);
|
|
}
|
|
}
|
|
}
|