Implement new syntax for package-specific filesystem requests.

This commit is contained in:
Paul Chote
2016-01-21 17:38:26 +00:00
parent 102880c80f
commit a0bc556172
8 changed files with 90 additions and 67 deletions

View File

@@ -21,6 +21,8 @@ namespace OpenRA.FileSystem
{ {
public IEnumerable<IReadOnlyPackage> MountedPackages { get { return mountedPackages.Keys; } } public IEnumerable<IReadOnlyPackage> MountedPackages { get { return mountedPackages.Keys; } }
readonly Dictionary<IReadOnlyPackage, int> mountedPackages = new Dictionary<IReadOnlyPackage, int>(); readonly Dictionary<IReadOnlyPackage, int> mountedPackages = new Dictionary<IReadOnlyPackage, int>();
readonly Dictionary<string, IReadOnlyPackage> explicitMounts = new Dictionary<string, IReadOnlyPackage>();
Cache<string, List<IReadOnlyPackage>> fileIndex = new Cache<string, List<IReadOnlyPackage>>(_ => new List<IReadOnlyPackage>()); Cache<string, List<IReadOnlyPackage>> fileIndex = new Cache<string, List<IReadOnlyPackage>>(_ => new List<IReadOnlyPackage>());
public IReadWritePackage CreatePackage(string filename, Dictionary<string, byte[]> content) public IReadWritePackage CreatePackage(string filename, Dictionary<string, byte[]> content)
@@ -54,7 +56,13 @@ namespace OpenRA.FileSystem
if (filename.EndsWith(".hdr", StringComparison.InvariantCultureIgnoreCase)) if (filename.EndsWith(".hdr", StringComparison.InvariantCultureIgnoreCase))
return new InstallShieldCABExtractor(this, filename); return new InstallShieldCABExtractor(this, filename);
return new Folder(filename); IReadOnlyPackage parent;
string subPath = null;
if (TryGetPackageContaining(filename, out parent, out subPath))
if (parent is Folder)
return new Folder(Path.Combine(((Folder)parent).Name, subPath));
return new Folder(Platform.ResolvePath(filename));
} }
public IReadWritePackage OpenWritablePackage(string filename) public IReadWritePackage OpenWritablePackage(string filename)
@@ -67,15 +75,13 @@ namespace OpenRA.FileSystem
return new Folder(filename); return new Folder(filename);
} }
public void Mount(string name) public void Mount(string name, string explicitName = null)
{ {
var optional = name.StartsWith("~"); var optional = name.StartsWith("~");
if (optional) if (optional)
name = name.Substring(1); name = name.Substring(1);
name = Platform.ResolvePath(name); Action a = () => Mount(OpenPackage(name), explicitName);
Action a = () => Mount(OpenPackage(name));
if (optional) if (optional)
try { a(); } try { a(); }
catch { } catch { }
@@ -83,7 +89,7 @@ namespace OpenRA.FileSystem
a(); a();
} }
public void Mount(IReadOnlyPackage package) public void Mount(IReadOnlyPackage package, string explicitName = null)
{ {
var mountCount = 0; var mountCount = 0;
if (mountedPackages.TryGetValue(package, out mountCount)) if (mountedPackages.TryGetValue(package, out mountCount))
@@ -101,6 +107,10 @@ namespace OpenRA.FileSystem
{ {
// Mounting the package for the first time // Mounting the package for the first time
mountedPackages.Add(package, 1); mountedPackages.Add(package, 1);
if (explicitName != null)
explicitMounts.Add(explicitName, package);
foreach (var filename in package.Contents) foreach (var filename in package.Contents)
fileIndex[filename].Add(package); fileIndex[filename].Add(package);
} }
@@ -118,6 +128,7 @@ namespace OpenRA.FileSystem
packagesForFile.RemoveAll(p => p == package); packagesForFile.RemoveAll(p => p == package);
mountedPackages.Remove(package); mountedPackages.Remove(package);
explicitMounts.Remove(package.Name);
package.Dispose(); package.Dispose();
} }
else else
@@ -132,14 +143,15 @@ namespace OpenRA.FileSystem
package.Dispose(); package.Dispose();
mountedPackages.Clear(); mountedPackages.Clear();
explicitMounts.Clear();
fileIndex = new Cache<string, List<IReadOnlyPackage>>(_ => new List<IReadOnlyPackage>()); fileIndex = new Cache<string, List<IReadOnlyPackage>>(_ => new List<IReadOnlyPackage>());
} }
public void LoadFromManifest(Manifest manifest) public void LoadFromManifest(Manifest manifest)
{ {
UnmountAll(); UnmountAll();
foreach (var pkg in manifest.Packages) foreach (var kv in manifest.Packages)
Mount(pkg); Mount(kv.Key, kv.Value);
} }
Stream GetFromCache(string filename) Stream GetFromCache(string filename)
@@ -162,58 +174,64 @@ namespace OpenRA.FileSystem
return s; return s;
} }
public bool TryOpen(string name, out Stream s) public bool TryGetPackageContaining(string path, out IReadOnlyPackage package, out string filename)
{ {
var filename = name; var explicitSplit = path.IndexOf('|');
var packageName = string.Empty; if (explicitSplit > 0 && explicitMounts.TryGetValue(path.Substring(0, explicitSplit), out package))
// Used for faction specific packages; rule out false positive on Windows C:\ drive notation
var explicitPackage = name.Contains(':') && !Directory.Exists(Path.GetDirectoryName(name));
if (explicitPackage)
{ {
var divide = name.Split(':'); filename = path.Substring(explicitSplit + 1);
packageName = divide.First(); return true;
filename = divide.Last();
} }
// Check the cache for a quick lookup if the package name is unknown package = fileIndex[path].LastOrDefault(x => x.Contains(path));
// TODO: This disables caching for explicit package requests filename = path;
if (filename.IndexOfAny(new[] { '/', '\\' }) == -1 && !explicitPackage)
return package != null;
}
public bool TryOpen(string filename, out Stream s)
{
var explicitSplit = filename.IndexOf('|');
if (explicitSplit > 0)
{ {
s = GetFromCache(filename); IReadOnlyPackage explicitPackage;
if (s != null) if (explicitMounts.TryGetValue(filename.Substring(0, explicitSplit), out explicitPackage))
return true; {
s = explicitPackage.GetStream(filename.Substring(explicitSplit + 1));
if (s != null)
return true;
}
} }
s = GetFromCache(filename);
if (s != null)
return true;
// Ask each package individually // Ask each package individually
IReadOnlyPackage package; // TODO: This fallback can be removed once the filesystem cleanups are complete
if (explicitPackage && !string.IsNullOrEmpty(packageName)) var package = mountedPackages.Keys.LastOrDefault(x => x.Contains(filename));
package = mountedPackages.Keys.LastOrDefault(x => x.Name == packageName);
else
package = mountedPackages.Keys.LastOrDefault(x => x.Contains(filename));
if (package != null) if (package != null)
{ {
s = package.GetStream(filename); s = package.GetStream(filename);
return true; return s != null;
} }
s = null; s = null;
return false; return false;
} }
public bool Exists(string name) public bool Exists(string filename)
{ {
var explicitPackage = name.Contains(':') && !Directory.Exists(Path.GetDirectoryName(name)); var explicitSplit = filename.IndexOf('|');
if (explicitPackage) if (explicitSplit > 0)
{ {
var divide = name.Split(':'); IReadOnlyPackage explicitPackage;
var packageName = divide.First(); if (explicitMounts.TryGetValue(filename.Substring(0, explicitSplit), out explicitPackage))
var filename = divide.Last(); if (explicitPackage.Contains(filename.Substring(explicitSplit + 1)))
return mountedPackages.Keys.Where(n => n.Name == packageName).Any(f => f.Contains(filename)); return true;
} }
else
return mountedPackages.Keys.Any(f => f.Contains(name)); return fileIndex.ContainsKey(filename);
} }
} }
} }

View File

@@ -35,11 +35,12 @@ namespace OpenRA
{ {
public readonly ModMetadata Mod; public readonly ModMetadata Mod;
public readonly string[] public readonly string[]
Packages, Rules, ServerTraits, Rules, ServerTraits,
Sequences, VoxelSequences, Cursors, Chrome, Assemblies, ChromeLayout, Sequences, VoxelSequences, Cursors, Chrome, Assemblies, ChromeLayout,
Weapons, Voices, Notifications, Music, Translations, TileSets, Weapons, Voices, Notifications, Music, Translations, TileSets,
ChromeMetrics, MapCompatibility, Missions; ChromeMetrics, MapCompatibility, Missions;
public readonly IReadOnlyDictionary<string, string> Packages;
public readonly IReadOnlyDictionary<string, string> MapFolders; public readonly IReadOnlyDictionary<string, string> MapFolders;
public readonly MiniYaml LoadScreen; public readonly MiniYaml LoadScreen;
public readonly MiniYaml LobbyDefaults; public readonly MiniYaml LobbyDefaults;
@@ -70,7 +71,11 @@ namespace OpenRA
// TODO: Use fieldloader // TODO: Use fieldloader
MapFolders = YamlDictionary(yaml, "MapFolders", true); MapFolders = YamlDictionary(yaml, "MapFolders", true);
Packages = YamlList(yaml, "Packages", true);
MiniYaml packages;
if (yaml.TryGetValue("Packages", out packages))
Packages = packages.ToDictionary(x => Platform.ResolvePath(x), x => x.Value).AsReadOnly();
Rules = YamlList(yaml, "Rules", true); Rules = YamlList(yaml, "Rules", true);
Sequences = YamlList(yaml, "Sequences", true); Sequences = YamlList(yaml, "Sequences", true);
VoxelSequences = YamlList(yaml, "VoxelSequences", true); VoxelSequences = YamlList(yaml, "VoxelSequences", true);

View File

@@ -1,7 +1,7 @@
Speech: Speech:
Prefixes: Prefixes:
gdi: speech01.mix: gdi: speech-gdi|
nod: speech02.mix: nod: speech-nod|
Notifications: Notifications:
AirUnitLost: 00-i074 AirUnitLost: 00-i074
AirstrikeReady: 00-n160 AirstrikeReady: 00-n160

View File

@@ -32,13 +32,13 @@ Packages:
isosnow.mix isosnow.mix
isotemp.mix isotemp.mix
local.mix local.mix
sidec01.mix sidec01.mix: sidebar-gdi
sidec02.mix sidec02.mix: sidebar-nod
sno.mix sno.mix
snow.mix snow.mix
sounds.mix sounds.mix
speech01.mix # EVA speech01.mix: speech-gdi
speech02.mix # Cabal speech02.mix: speech-nod
tem.mix tem.mix
temperat.mix temperat.mix
# Firestorm # Firestorm

View File

@@ -43,7 +43,7 @@
Filename: anim.pal Filename: anim.pal
PaletteFromFile@sidebar: PaletteFromFile@sidebar:
Name: sidebar Name: sidebar
Filename: sidec02.mix:sidebar.pal Filename: sidebar-nod|sidebar.pal
PaletteFromPaletteWithAlpha@clock: PaletteFromPaletteWithAlpha@clock:
Name: iconclock Name: iconclock
BasePalette: sidebar BasePalette: sidebar

View File

@@ -66,7 +66,7 @@ e1.gdi:
ShadowStart: 190 ShadowStart: 190
die6: electro die6: electro
Length: * Length: *
icon: sidec01.mix:e1icon icon: sidebar-gdi|e1icon
e1.nod: e1.nod:
Defaults: e1 Defaults: e1
@@ -136,7 +136,7 @@ e1.nod:
ShadowStart: 190 ShadowStart: 190
die6: electro die6: electro
Length: * Length: *
icon: sidec02.mix:e1icon icon: sidebar-nod|e1icon
e2: e2:
Defaults: Defaults:
@@ -477,7 +477,7 @@ engineer.gdi:
ShadowStart: 190 ShadowStart: 190
die6: electro die6: electro
Length: * Length: *
icon: sidec01.mix:engnicon icon: sidebar-gdi|engnicon
engineer.nod: engineer.nod:
Defaults: engineer Defaults: engineer
@@ -539,7 +539,7 @@ engineer.nod:
ShadowStart: 190 ShadowStart: 190
die6: electro die6: electro
Length: * Length: *
icon: sidec02.mix:engnicon icon: sidebar-nod|engnicon
umagon: umagon:
Defaults: Defaults:

View File

@@ -912,7 +912,7 @@ napuls.gdi:
UseTilesetCode: false UseTilesetCode: false
ZOffset: 512 ZOffset: 512
BlendMode: Additive BlendMode: Additive
icon: sidec01.mix:empicon icon: sidebar-gdi|empicon
Offset: 0, 0 Offset: 0, 0
UseTilesetCode: false UseTilesetCode: false
@@ -940,7 +940,7 @@ napuls.nod:
UseTilesetCode: false UseTilesetCode: false
ZOffset: 512 ZOffset: 512
BlendMode: Additive BlendMode: Additive
icon: sidec02.mix:empicon icon: sidebar-nod|empicon
Offset: 0, 0 Offset: 0, 0
UseTilesetCode: false UseTilesetCode: false
@@ -1190,7 +1190,7 @@ proc.gdi:
UseTilesetCode: false UseTilesetCode: false
ZOffset: 512 ZOffset: 512
BlendMode: Additive BlendMode: Additive
icon: sidec01.mix:reficon icon: sidebar-gdi|reficon
Offset: 0, 0 Offset: 0, 0
UseTilesetCode: false UseTilesetCode: false
@@ -1232,7 +1232,7 @@ proc.nod:
UseTilesetCode: false UseTilesetCode: false
ZOffset: 512 ZOffset: 512
BlendMode: Additive BlendMode: Additive
icon: sidec02.mix:reficon icon: sidebar-nod|reficon
Offset: 0, 0 Offset: 0, 0
UseTilesetCode: false UseTilesetCode: false
@@ -1323,7 +1323,7 @@ gasilo.gdi:
UseTilesetCode: false UseTilesetCode: false
ZOffset: 512 ZOffset: 512
BlendMode: Additive BlendMode: Additive
icon: sidec01.mix:siloicon icon: sidebar-gdi|siloicon
Offset: 0, 0 Offset: 0, 0
UseTilesetCode: false UseTilesetCode: false
@@ -1367,7 +1367,7 @@ gasilo.nod:
UseTilesetCode: false UseTilesetCode: false
ZOffset: 512 ZOffset: 512
BlendMode: Additive BlendMode: Additive
icon: sidec02.mix:siloicon icon: sidebar-nod|siloicon
Offset: 0, 0 Offset: 0, 0
UseTilesetCode: false UseTilesetCode: false
@@ -1431,7 +1431,7 @@ gadept.gdi:
UseTilesetCode: false UseTilesetCode: false
ZOffset: 512 ZOffset: 512
BlendMode: Additive BlendMode: Additive
icon: sidec01.mix:fixicon icon: sidebar-gdi|fixicon
Offset: 0, 0 Offset: 0, 0
UseTilesetCode: false UseTilesetCode: false
@@ -1495,7 +1495,7 @@ gadept.nod:
UseTilesetCode: false UseTilesetCode: false
ZOffset: 512 ZOffset: 512
BlendMode: Additive BlendMode: Additive
icon: sidec02.mix:fixicon icon: sidebar-nod|fixicon
Offset: 76, 66 Offset: 76, 66
UseTilesetCode: false UseTilesetCode: false

View File

@@ -2,13 +2,13 @@ mcv.gdi:
emp-overlay: emp_fx01 emp-overlay: emp_fx01
Length: * Length: *
BlendMode: Additive BlendMode: Additive
icon: sidec01.mix:mcvicon icon: sidebar-gdi|mcvicon
mcv.nod: mcv.nod:
emp-overlay: emp_fx01 emp-overlay: emp_fx01
Length: * Length: *
BlendMode: Additive BlendMode: Additive
icon: sidec02.mix:mcvicon icon: sidebar-nod|mcvicon
apc: apc:
emp-overlay: emp_fx01 emp-overlay: emp_fx01
@@ -22,7 +22,7 @@ harv.gdi:
BlendMode: Additive BlendMode: Additive
harvest: harvestr harvest: harvestr
Length: * Length: *
icon: sidec01.mix:harvicon icon: sidebar-gdi|harvicon
harv.nod: harv.nod:
emp-overlay: emp_fx01 emp-overlay: emp_fx01
@@ -30,7 +30,7 @@ harv.nod:
BlendMode: Additive BlendMode: Additive
harvest: harvestr harvest: harvestr
Length: * Length: *
icon: sidec02.mix:harvicon icon: sidebar-nod|harvicon
hvr: hvr:
emp-overlay: emp_fx01 emp-overlay: emp_fx01
@@ -56,7 +56,7 @@ lpst.gdi:
emp-overlay: emp_fx01 emp-overlay: emp_fx01
Length: * Length: *
BlendMode: Additive BlendMode: Additive
icon: sidec01.mix:lpsticon icon: sidebar-gdi|lpsticon
lpst.nod: lpst.nod:
idle: gadpsa idle: gadpsa
@@ -69,7 +69,7 @@ lpst.nod:
emp-overlay: emp_fx01 emp-overlay: emp_fx01
Length: * Length: *
BlendMode: Additive BlendMode: Additive
icon: sidec02.mix:lpsticon icon: sidebar-nod|lpsticon
repair: repair:
emp-overlay: emp_fx01 emp-overlay: emp_fx01