Skip to content

Instantly share code, notes, and snippets.

@azyobuzin
Created October 23, 2011 04:52
Show Gist options
  • Save azyobuzin/1306890 to your computer and use it in GitHub Desktop.
Save azyobuzin/1306890 to your computer and use it in GitHub Desktop.
using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Runtime.Serialization.Json;
using System.Text;
using System.Xml;
using System.Xml.Linq;
using Ionic.Zip;
using Ionic.Zlib;
namespace Updater
{
public static class UpdaterCore
{
public static void ApplyPatch(string patchFile, string targetDir)
{
using (var zip = ZipFile.Read(patchFile))
{
foreach (var entry in zip)
{
using (var entryStream = new MemoryStream())
{
entry.Extract(entryStream);
entryStream.Position = 0;
var targetFileName = Path.Combine(targetDir, entry.FileName);
switch (entryStream.ReadByte())
{
case 0://パッチ
XElement elm;
using (var reader = JsonReaderWriterFactory.CreateJsonReader(entryStream, XmlDictionaryReaderQuotas.Max))
elm = XElement.Load(reader);
var @base =
Convert.ToBase64String(
File.ReadAllBytes(targetFileName),
Base64FormattingOptions.InsertLineBreaks
)
.Split(new[] { '\r', '\n' }, StringSplitOptions.RemoveEmptyEntries)
.ToList();
foreach (var diff in elm.Elements())
{
@base[(int)diff.Element("index")] = (string)diff.Element("data");
}
File.WriteAllBytes(targetFileName, Convert.FromBase64String(string.Join("\r\n", @base)));
break;
case 1://バイナリ
using (var fs = new FileStream(targetFileName, FileMode.Create, FileAccess.Write))
{
while (true)
{
var bs = new byte[1024 * 4];
var readCount = entryStream.Read(bs, 0, bs.Length);
if (readCount == 0)
break;
fs.Write(bs, 0, readCount);
}
}
break;
case 2://削除
File.Delete(targetFileName);
break;
default:
throw new ArgumentException("パッチが正しくありません。");
}
}
}
}
}
public static void CreatePatch(string oldZipFile, string newZipFile, string outputFile)
{
using (var zip = new ZipFile())
using (var oldZip = ZipFile.Read(oldZipFile))
using (var newZip = ZipFile.Read(newZipFile))
{
zip.CompressionLevel = CompressionLevel.BestCompression;
foreach (var newEntry in newZip.Except(oldZip, ZipEntryFileNameComparer.Default))
{
var memStream = new MemoryStream();
memStream.WriteByte(1);
newEntry.Extract(memStream);
zip.AddEntry(newEntry.FileName, memStream);
}
foreach (var deleteEntry in oldZip.Except(newZip, ZipEntryFileNameComparer.Default))
{
zip.AddEntry(deleteEntry.FileName, new byte[] { 2 });
}
foreach (var otherEntry in
oldZip.Where(entry => newZip.Contains(entry, ZipEntryFileNameComparer.Default))
.Zip(newZip.Where(entry => oldZip.Contains(entry, ZipEntryFileNameComparer.Default)),
(f, s) => new { old = f, @new = s }))
{
var oldBase64 = Convert.ToBase64String(Extract(otherEntry.old), Base64FormattingOptions.InsertLineBreaks)
.Split(new[] { '\r', '\n' }, StringSplitOptions.RemoveEmptyEntries);
var newBase64 = Convert.ToBase64String(Extract(otherEntry.@new), Base64FormattingOptions.InsertLineBreaks)
.Split(new[] { '\r', '\n' }, StringSplitOptions.RemoveEmptyEntries);
var diff = new List<Tuple<int, string>>();
if (oldBase64.Length < newBase64.Length)
{
diff.AddRange(
newBase64.Select((data, index) => Tuple.Create(index, data))
.Skip(oldBase64.Length)
);
}
if (oldBase64.Length > newBase64.Length)
{
diff.AddRange(
oldBase64.Select((data, index) => Tuple.Create(index, string.Empty))
.Skip(newBase64.Length)
);
}
var minLength = Math.Min(oldBase64.Length, newBase64.Length);
diff.AddRange(
oldBase64.Take(minLength)
.Select((data, index) => Tuple.Create(index, data))
.Zip(newBase64.Take(minLength),
(f, s) => new { index = f.Item1, old = f.Item2, @new = s })
.Where(_ => _.old != _.@new)
.Select(_ => Tuple.Create(_.index, _.@new))
);
if (diff.Any())
{
diff.Sort((f, s) => f.Item1.CompareTo(s.Item1));
var memStream = new MemoryStream();
memStream.WriteByte(0);
using (var writer = JsonReaderWriterFactory.CreateJsonWriter(memStream, Encoding.UTF8, false))
{
var elm = new XElement("root", new XAttribute("type", "array"));
elm.Add(diff.Select(d =>
new XElement(
"item",
new XAttribute("type", "object"),
new XElement("index", d.Item1),
new XElement("data", d.Item2)
)
));
elm.Save(writer);
}
zip.AddEntry([email protected], memStream);
}
}
zip.Save(outputFile);
}
}
private static byte[] Extract(ZipEntry entry)
{
using (var memStream = new MemoryStream())
{
entry.Extract(memStream);
return memStream.ToArray();
}
}
}
class ZipEntryFileNameComparer : IEqualityComparer<ZipEntry>
{
private static ZipEntryFileNameComparer instance;
public static ZipEntryFileNameComparer Default
{
get
{
return instance = instance ?? new ZipEntryFileNameComparer();
}
}
public bool Equals(ZipEntry x, ZipEntry y)
{
return x.FileName == y.FileName;
}
public int GetHashCode(ZipEntry obj)
{
return obj.FileName.GetHashCode();
}
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment