diff --git a/CKAN/CKAN/ModuleInstaller.cs b/CKAN/CKAN/ModuleInstaller.cs index b2b1302821..f3608aa63a 100644 --- a/CKAN/CKAN/ModuleInstaller.cs +++ b/CKAN/CKAN/ModuleInstaller.cs @@ -6,6 +6,7 @@ using System.Security.Cryptography; using System.Text.RegularExpressions; using System.Threading; +using ICSharpCode.SharpZipLib.Core; using ICSharpCode.SharpZipLib.Zip; using System.Transactions; using ChinhDo.Transactions; @@ -726,7 +727,10 @@ public static List FindInstallableFiles(CkanModule module, stri } } - private void CopyZipEntry(ZipFile zipfile, ZipEntry entry, string fullPath, bool makeDirs) + /// + /// Copy the entry from the opened zipfile to the path specified. + /// + internal static void CopyZipEntry(ZipFile zipfile, ZipEntry entry, string fullPath, bool makeDirs) { if (entry.IsDirectory) { @@ -752,11 +756,17 @@ private void CopyZipEntry(ZipFile zipfile, ZipEntry entry, string fullPath, bool file_transaction.CreateDirectory(directory); } + // Snapshot whatever was there before. If there's nothing, this will just + // remove our file on rollback. + file_transaction.Snapshot(fullPath); + // It's a file! Prepare the streams using (Stream zipStream = zipfile.GetInputStream(entry)) - using (StreamReader reader = new StreamReader(zipStream)) + using (FileStream writer = File.Create(fullPath)) { - file_transaction.WriteAllText(fullPath, reader.ReadToEnd()); + // 4k is the block size on practically every disk and OS. + byte[] buffer = new byte[4096]; + StreamUtils.Copy(zipStream, writer, buffer); } } diff --git a/CKAN/Tests/CKAN/ModuleInstaller.cs b/CKAN/Tests/CKAN/ModuleInstaller.cs index e32f912d08..4547bce264 100644 --- a/CKAN/Tests/CKAN/ModuleInstaller.cs +++ b/CKAN/Tests/CKAN/ModuleInstaller.cs @@ -1,6 +1,7 @@ using NUnit.Framework; using System; using System.IO; +using System.Transactions; using System.Collections.Generic; using ICSharpCode.SharpZipLib.Zip; using CKAN; @@ -96,6 +97,59 @@ public void No_Installable_Files() } } + [Test()] + // GH #205, make sure we write in *binary*, not text. + public void BinaryNotText_205() + { + // Use CopyZipEntry (via CopyDogeFromZip) and make sure it + // comes out the right size. + string tmpfile = CopyDogeFromZip(); + long size = new System.IO.FileInfo(tmpfile).Length; + + try + { + // Compare recorded length against what we expect. + Assert.AreEqual(52043, size); + } + finally + { + // Tidy up. + File.Delete(tmpfile); + } + } + + [Test()] + // Make sure when we roll-back a transaction, files written with CopyZipEntry go + // back to their pre-transaction state. + public void FileSysRollBack() + { + string file; + + using (var scope = new TransactionScope()) + { + file = CopyDogeFromZip(); + Assert.IsTrue(new System.IO.FileInfo(file).Length > 0); + scope.Dispose(); // Rollback + } + + // CopyDogeFromZip creates a tempfile, so we check to make sure it's empty + // again on transaction rollback. + Assert.AreEqual(0, new System.IO.FileInfo(file).Length); + } + + private static string CopyDogeFromZip() + { + string dogezip = Tests.TestData.DogeCoinFlagZip(); + ZipFile zipfile = new ZipFile(dogezip); + + ZipEntry entry = zipfile.GetEntry("DogeCoinFlag-1.01/GameData/DogeCoinFlag/Flags/dogecoin.png"); + string tmpfile = Path.GetTempFileName(); + + CKAN.ModuleInstaller.CopyZipEntry(zipfile, entry, tmpfile, false); + + return tmpfile; + } + private void TestDogeCoinStanza(ModuleInstallDescriptor stanza) { Assert.AreEqual("GameData", stanza.install_to); diff --git a/CKAN/Tests/Tests.csproj b/CKAN/Tests/Tests.csproj index e560f9fcd0..e70028958a 100644 --- a/CKAN/Tests/Tests.csproj +++ b/CKAN/Tests/Tests.csproj @@ -39,6 +39,7 @@ ..\packages\SharpZipLib.0.86.0\lib\20\ICSharpCode.SharpZipLib.dll +