Rework read/write zip file loading/saving.

This commit is contained in:
Paul Chote
2017-05-24 20:17:27 +00:00
committed by abcdefg30
parent c06df1ed21
commit df40d38b91
6 changed files with 90 additions and 81 deletions

View File

@@ -53,9 +53,14 @@ namespace OpenRA.FileSystem
public IReadOnlyPackage OpenPackage(string filename, FileSystem context) public IReadOnlyPackage OpenPackage(string filename, FileSystem context)
{ {
var subFolder = Platform.ResolvePath(Path.Combine(Name, filename)); var resolvedPath = Platform.ResolvePath(Path.Combine(Name, filename));
if (Directory.Exists(subFolder)) if (Directory.Exists(resolvedPath))
return new Folder(subFolder); return new Folder(resolvedPath);
// Zip files loaded from Folders (and *only* from Folders) can be read-write
IReadWritePackage readWritePackage;
if (ZipFileLoader.TryParseReadWritePackage(resolvedPath, out readWritePackage))
return readWritePackage;
// Other package types can be loaded normally // Other package types can be loaded normally
IReadOnlyPackage package; IReadOnlyPackage package;

View File

@@ -23,38 +23,23 @@ namespace OpenRA.FileSystem
{ {
static readonly string[] Extensions = { ".zip", ".oramap" }; static readonly string[] Extensions = { ".zip", ".oramap" };
sealed class ZipFile : IReadWritePackage class ReadOnlyZipFile : IReadOnlyPackage
{ {
public IReadWritePackage Parent { get; private set; } public string Name { get; protected set; }
public string Name { get; private set; } protected SZipFile pkg;
readonly Stream pkgStream;
readonly SZipFile pkg;
static ZipFile() static ReadOnlyZipFile()
{ {
ZipConstants.DefaultCodePage = Encoding.UTF8.CodePage; ZipConstants.DefaultCodePage = Encoding.UTF8.CodePage;
} }
public ZipFile(Stream stream, string name, IReadOnlyPackage parent = null) // Dummy constructor for use with ReadWriteZipFile
protected ReadOnlyZipFile() { }
public ReadOnlyZipFile(Stream s, string filename)
{ {
// SharpZipLib breaks when asked to update archives loaded from outside streams or files
// We can work around this by creating a clean in-memory-only file, cutting all outside references
pkgStream = new MemoryStream();
stream.CopyTo(pkgStream);
pkgStream.Position = 0;
Name = name;
Parent = parent as IReadWritePackage;
pkg = new SZipFile(pkgStream);
}
public ZipFile(string filename, IReadWritePackage parent)
{
pkgStream = new MemoryStream();
Name = filename; Name = filename;
Parent = parent; pkg = new SZipFile(s);
pkg = SZipFile.Create(pkgStream);
} }
public Stream GetStream(string filename) public Stream GetStream(string filename)
@@ -86,14 +71,61 @@ namespace OpenRA.FileSystem
return pkg.GetEntry(filename) != null; return pkg.GetEntry(filename) != null;
} }
public void Dispose()
{
if (pkg != null)
pkg.Close();
}
public IReadOnlyPackage OpenPackage(string filename, FileSystem context)
{
// Directories are stored with a trailing "/" in the index
var entry = pkg.GetEntry(filename) ?? pkg.GetEntry(filename + "/");
if (entry == null)
return null;
if (entry.IsDirectory)
return new ZipFolder(this, filename);
// Other package types can be loaded normally
IReadOnlyPackage package;
var s = GetStream(filename);
if (s == null)
return null;
if (context.TryParsePackage(s, filename, out package))
return package;
s.Dispose();
return null;
}
}
sealed class ReadWriteZipFile : ReadOnlyZipFile, IReadWritePackage
{
readonly MemoryStream pkgStream;
public ReadWriteZipFile(Stream stream, string filename)
{
// SharpZipLib breaks when asked to update archives loaded from outside streams or files
// We can work around this by creating a clean in-memory-only file, cutting all outside references
pkgStream = new MemoryStream();
if (stream != null)
{
stream.CopyTo(pkgStream);
stream.Dispose();
}
pkgStream.Position = 0;
pkg = new SZipFile(pkgStream);
Name = filename;
}
void Commit() void Commit()
{ {
if (Parent == null)
throw new InvalidDataException("Cannot update ZipFile without writable parent");
var pos = pkgStream.Position; var pos = pkgStream.Position;
pkgStream.Position = 0; pkgStream.Position = 0;
Parent.Update(Name, pkgStream.ReadBytes((int)pkgStream.Length)); File.WriteAllBytes(Name, pkgStream.ReadBytes((int)pkgStream.Length));
pkgStream.Position = pos; pkgStream.Position = pos;
} }
@@ -112,47 +144,12 @@ namespace OpenRA.FileSystem
pkg.CommitUpdate(); pkg.CommitUpdate();
Commit(); Commit();
} }
public void Dispose()
{
if (pkg != null)
pkg.Close();
if (pkgStream != null)
pkgStream.Dispose();
}
public IReadOnlyPackage OpenPackage(string filename, FileSystem context)
{
// Directories are stored with a trailing "/" in the index
var entry = pkg.GetEntry(filename) ?? pkg.GetEntry(filename + "/");
if (entry == null)
return null;
if (entry.IsDirectory)
return new ZipFolder(this, filename);
if (Extensions.Any(e => filename.EndsWith(e, StringComparison.InvariantCultureIgnoreCase)))
return new ZipFile(GetStream(filename), filename, this);
// Other package types can be loaded normally
IReadOnlyPackage package;
var s = GetStream(filename);
if (s == null)
return null;
if (context.TryParsePackage(s, filename, out package))
return package;
s.Dispose();
return null;
}
} }
sealed class ZipFolder : IReadOnlyPackage sealed class ZipFolder : IReadOnlyPackage
{ {
public string Name { get { return path; } } public string Name { get { return path; } }
public ZipFile Parent { get; private set; } public ReadOnlyZipFile Parent { get; private set; }
readonly string path; readonly string path;
static ZipFolder() static ZipFolder()
@@ -160,7 +157,7 @@ namespace OpenRA.FileSystem
ZipConstants.DefaultCodePage = Encoding.UTF8.CodePage; ZipConstants.DefaultCodePage = Encoding.UTF8.CodePage;
} }
public ZipFolder(ZipFile parent, string path) public ZipFolder(ReadOnlyZipFile parent, string path)
{ {
if (path.EndsWith("/", StringComparison.Ordinal)) if (path.EndsWith("/", StringComparison.Ordinal))
path = path.Substring(0, path.Length - 1); path = path.Substring(0, path.Length - 1);
@@ -227,19 +224,26 @@ namespace OpenRA.FileSystem
return false; return false;
} }
string name; package = new ReadOnlyZipFile(s, filename);
IReadOnlyPackage p;
if (context.TryGetPackageContaining(filename, out p, out name))
package = new ZipFile(p.GetStream(name), name, p);
else
package = new ZipFile(s, filename, null);
return true; return true;
} }
public static IReadWritePackage Create(string filename, IReadWritePackage parent) public static bool TryParseReadWritePackage(string filename, out IReadWritePackage package)
{ {
return new ZipFile(filename, parent); if (!Extensions.Any(e => filename.EndsWith(e, StringComparison.InvariantCultureIgnoreCase)))
{
package = null;
return false;
}
var s = new FileStream(filename, FileMode.Open);
package = new ReadWriteZipFile(s, filename);
return true;
}
public static IReadWritePackage Create(string filename)
{
return new ReadWriteZipFile(null, filename);
} }
} }
} }

View File

@@ -271,7 +271,7 @@ namespace OpenRA.Mods.Cnc.UtilityCommands
map.PlayerDefinitions = mapPlayers.ToMiniYaml(); map.PlayerDefinitions = mapPlayers.ToMiniYaml();
var dest = Path.GetFileNameWithoutExtension(args[1]) + ".oramap"; var dest = Path.GetFileNameWithoutExtension(args[1]) + ".oramap";
map.Save(ZipFileLoader.Create(dest, new Folder("."))); map.Save(ZipFileLoader.Create(dest));
Console.WriteLine(dest + " saved."); Console.WriteLine(dest + " saved.");
} }

View File

@@ -103,7 +103,7 @@ namespace OpenRA.Mods.Common.UtilityCommands
var dest = Path.GetFileNameWithoutExtension(args[1]) + ".oramap"; var dest = Path.GetFileNameWithoutExtension(args[1]) + ".oramap";
Map.Save(ZipFileLoader.Create(dest, new Folder("."))); Map.Save(ZipFileLoader.Create(dest));
Console.WriteLine(dest + " saved."); Console.WriteLine(dest + " saved.");
} }

View File

@@ -185,7 +185,7 @@ namespace OpenRA.Mods.Common.Widgets.Logic
{ {
selectedDirectory.Folder.Delete(combinedPath); selectedDirectory.Folder.Delete(combinedPath);
if (fileType == MapFileType.OraMap) if (fileType == MapFileType.OraMap)
package = ZipFileLoader.Create(combinedPath, selectedDirectory.Folder); package = ZipFileLoader.Create(combinedPath);
else else
package = new Folder(combinedPath); package = new Folder(combinedPath);
} }

View File

@@ -37,7 +37,7 @@ namespace OpenRA.Mods.D2k.UtilityCommands
return; return;
var dest = Path.GetFileNameWithoutExtension(args[1]) + ".oramap"; var dest = Path.GetFileNameWithoutExtension(args[1]) + ".oramap";
map.Save(ZipFileLoader.Create(dest, new Folder("."))); map.Save(ZipFileLoader.Create(dest));
Console.WriteLine(dest + " saved."); Console.WriteLine(dest + " saved.");
} }
} }