Implement new syntax for package-specific filesystem requests.
This commit is contained in:
@@ -21,6 +21,8 @@ namespace OpenRA.FileSystem
|
||||
{
|
||||
public IEnumerable<IReadOnlyPackage> MountedPackages { get { return mountedPackages.Keys; } }
|
||||
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>());
|
||||
|
||||
public IReadWritePackage CreatePackage(string filename, Dictionary<string, byte[]> content)
|
||||
@@ -54,7 +56,13 @@ namespace OpenRA.FileSystem
|
||||
if (filename.EndsWith(".hdr", StringComparison.InvariantCultureIgnoreCase))
|
||||
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)
|
||||
@@ -67,15 +75,13 @@ namespace OpenRA.FileSystem
|
||||
return new Folder(filename);
|
||||
}
|
||||
|
||||
public void Mount(string name)
|
||||
public void Mount(string name, string explicitName = null)
|
||||
{
|
||||
var optional = name.StartsWith("~");
|
||||
if (optional)
|
||||
name = name.Substring(1);
|
||||
|
||||
name = Platform.ResolvePath(name);
|
||||
|
||||
Action a = () => Mount(OpenPackage(name));
|
||||
Action a = () => Mount(OpenPackage(name), explicitName);
|
||||
if (optional)
|
||||
try { a(); }
|
||||
catch { }
|
||||
@@ -83,7 +89,7 @@ namespace OpenRA.FileSystem
|
||||
a();
|
||||
}
|
||||
|
||||
public void Mount(IReadOnlyPackage package)
|
||||
public void Mount(IReadOnlyPackage package, string explicitName = null)
|
||||
{
|
||||
var mountCount = 0;
|
||||
if (mountedPackages.TryGetValue(package, out mountCount))
|
||||
@@ -101,6 +107,10 @@ namespace OpenRA.FileSystem
|
||||
{
|
||||
// Mounting the package for the first time
|
||||
mountedPackages.Add(package, 1);
|
||||
|
||||
if (explicitName != null)
|
||||
explicitMounts.Add(explicitName, package);
|
||||
|
||||
foreach (var filename in package.Contents)
|
||||
fileIndex[filename].Add(package);
|
||||
}
|
||||
@@ -118,6 +128,7 @@ namespace OpenRA.FileSystem
|
||||
packagesForFile.RemoveAll(p => p == package);
|
||||
|
||||
mountedPackages.Remove(package);
|
||||
explicitMounts.Remove(package.Name);
|
||||
package.Dispose();
|
||||
}
|
||||
else
|
||||
@@ -132,14 +143,15 @@ namespace OpenRA.FileSystem
|
||||
package.Dispose();
|
||||
|
||||
mountedPackages.Clear();
|
||||
explicitMounts.Clear();
|
||||
fileIndex = new Cache<string, List<IReadOnlyPackage>>(_ => new List<IReadOnlyPackage>());
|
||||
}
|
||||
|
||||
public void LoadFromManifest(Manifest manifest)
|
||||
{
|
||||
UnmountAll();
|
||||
foreach (var pkg in manifest.Packages)
|
||||
Mount(pkg);
|
||||
foreach (var kv in manifest.Packages)
|
||||
Mount(kv.Key, kv.Value);
|
||||
}
|
||||
|
||||
Stream GetFromCache(string filename)
|
||||
@@ -162,58 +174,64 @@ namespace OpenRA.FileSystem
|
||||
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 packageName = string.Empty;
|
||||
|
||||
// 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 explicitSplit = path.IndexOf('|');
|
||||
if (explicitSplit > 0 && explicitMounts.TryGetValue(path.Substring(0, explicitSplit), out package))
|
||||
{
|
||||
var divide = name.Split(':');
|
||||
packageName = divide.First();
|
||||
filename = divide.Last();
|
||||
filename = path.Substring(explicitSplit + 1);
|
||||
return true;
|
||||
}
|
||||
|
||||
// Check the cache for a quick lookup if the package name is unknown
|
||||
// TODO: This disables caching for explicit package requests
|
||||
if (filename.IndexOfAny(new[] { '/', '\\' }) == -1 && !explicitPackage)
|
||||
package = fileIndex[path].LastOrDefault(x => x.Contains(path));
|
||||
filename = path;
|
||||
|
||||
return package != null;
|
||||
}
|
||||
|
||||
public bool TryOpen(string filename, out Stream s)
|
||||
{
|
||||
var explicitSplit = filename.IndexOf('|');
|
||||
if (explicitSplit > 0)
|
||||
{
|
||||
s = GetFromCache(filename);
|
||||
if (s != null)
|
||||
return true;
|
||||
IReadOnlyPackage explicitPackage;
|
||||
if (explicitMounts.TryGetValue(filename.Substring(0, explicitSplit), out explicitPackage))
|
||||
{
|
||||
s = explicitPackage.GetStream(filename.Substring(explicitSplit + 1));
|
||||
if (s != null)
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
s = GetFromCache(filename);
|
||||
if (s != null)
|
||||
return true;
|
||||
|
||||
// Ask each package individually
|
||||
IReadOnlyPackage package;
|
||||
if (explicitPackage && !string.IsNullOrEmpty(packageName))
|
||||
package = mountedPackages.Keys.LastOrDefault(x => x.Name == packageName);
|
||||
else
|
||||
package = mountedPackages.Keys.LastOrDefault(x => x.Contains(filename));
|
||||
|
||||
// TODO: This fallback can be removed once the filesystem cleanups are complete
|
||||
var package = mountedPackages.Keys.LastOrDefault(x => x.Contains(filename));
|
||||
if (package != null)
|
||||
{
|
||||
s = package.GetStream(filename);
|
||||
return true;
|
||||
return s != null;
|
||||
}
|
||||
|
||||
s = null;
|
||||
return false;
|
||||
}
|
||||
|
||||
public bool Exists(string name)
|
||||
public bool Exists(string filename)
|
||||
{
|
||||
var explicitPackage = name.Contains(':') && !Directory.Exists(Path.GetDirectoryName(name));
|
||||
if (explicitPackage)
|
||||
var explicitSplit = filename.IndexOf('|');
|
||||
if (explicitSplit > 0)
|
||||
{
|
||||
var divide = name.Split(':');
|
||||
var packageName = divide.First();
|
||||
var filename = divide.Last();
|
||||
return mountedPackages.Keys.Where(n => n.Name == packageName).Any(f => f.Contains(filename));
|
||||
IReadOnlyPackage explicitPackage;
|
||||
if (explicitMounts.TryGetValue(filename.Substring(0, explicitSplit), out explicitPackage))
|
||||
if (explicitPackage.Contains(filename.Substring(explicitSplit + 1)))
|
||||
return true;
|
||||
}
|
||||
else
|
||||
return mountedPackages.Keys.Any(f => f.Contains(name));
|
||||
|
||||
return fileIndex.ContainsKey(filename);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -35,11 +35,12 @@ namespace OpenRA
|
||||
{
|
||||
public readonly ModMetadata Mod;
|
||||
public readonly string[]
|
||||
Packages, Rules, ServerTraits,
|
||||
Rules, ServerTraits,
|
||||
Sequences, VoxelSequences, Cursors, Chrome, Assemblies, ChromeLayout,
|
||||
Weapons, Voices, Notifications, Music, Translations, TileSets,
|
||||
ChromeMetrics, MapCompatibility, Missions;
|
||||
|
||||
public readonly IReadOnlyDictionary<string, string> Packages;
|
||||
public readonly IReadOnlyDictionary<string, string> MapFolders;
|
||||
public readonly MiniYaml LoadScreen;
|
||||
public readonly MiniYaml LobbyDefaults;
|
||||
@@ -70,7 +71,11 @@ namespace OpenRA
|
||||
|
||||
// TODO: Use fieldloader
|
||||
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);
|
||||
Sequences = YamlList(yaml, "Sequences", true);
|
||||
VoxelSequences = YamlList(yaml, "VoxelSequences", true);
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
Speech:
|
||||
Prefixes:
|
||||
gdi: speech01.mix:
|
||||
nod: speech02.mix:
|
||||
gdi: speech-gdi|
|
||||
nod: speech-nod|
|
||||
Notifications:
|
||||
AirUnitLost: 00-i074
|
||||
AirstrikeReady: 00-n160
|
||||
|
||||
@@ -32,13 +32,13 @@ Packages:
|
||||
isosnow.mix
|
||||
isotemp.mix
|
||||
local.mix
|
||||
sidec01.mix
|
||||
sidec02.mix
|
||||
sidec01.mix: sidebar-gdi
|
||||
sidec02.mix: sidebar-nod
|
||||
sno.mix
|
||||
snow.mix
|
||||
sounds.mix
|
||||
speech01.mix # EVA
|
||||
speech02.mix # Cabal
|
||||
speech01.mix: speech-gdi
|
||||
speech02.mix: speech-nod
|
||||
tem.mix
|
||||
temperat.mix
|
||||
# Firestorm
|
||||
|
||||
@@ -43,7 +43,7 @@
|
||||
Filename: anim.pal
|
||||
PaletteFromFile@sidebar:
|
||||
Name: sidebar
|
||||
Filename: sidec02.mix:sidebar.pal
|
||||
Filename: sidebar-nod|sidebar.pal
|
||||
PaletteFromPaletteWithAlpha@clock:
|
||||
Name: iconclock
|
||||
BasePalette: sidebar
|
||||
|
||||
@@ -66,7 +66,7 @@ e1.gdi:
|
||||
ShadowStart: 190
|
||||
die6: electro
|
||||
Length: *
|
||||
icon: sidec01.mix:e1icon
|
||||
icon: sidebar-gdi|e1icon
|
||||
|
||||
e1.nod:
|
||||
Defaults: e1
|
||||
@@ -136,7 +136,7 @@ e1.nod:
|
||||
ShadowStart: 190
|
||||
die6: electro
|
||||
Length: *
|
||||
icon: sidec02.mix:e1icon
|
||||
icon: sidebar-nod|e1icon
|
||||
|
||||
e2:
|
||||
Defaults:
|
||||
@@ -477,7 +477,7 @@ engineer.gdi:
|
||||
ShadowStart: 190
|
||||
die6: electro
|
||||
Length: *
|
||||
icon: sidec01.mix:engnicon
|
||||
icon: sidebar-gdi|engnicon
|
||||
|
||||
engineer.nod:
|
||||
Defaults: engineer
|
||||
@@ -539,7 +539,7 @@ engineer.nod:
|
||||
ShadowStart: 190
|
||||
die6: electro
|
||||
Length: *
|
||||
icon: sidec02.mix:engnicon
|
||||
icon: sidebar-nod|engnicon
|
||||
|
||||
umagon:
|
||||
Defaults:
|
||||
|
||||
@@ -912,7 +912,7 @@ napuls.gdi:
|
||||
UseTilesetCode: false
|
||||
ZOffset: 512
|
||||
BlendMode: Additive
|
||||
icon: sidec01.mix:empicon
|
||||
icon: sidebar-gdi|empicon
|
||||
Offset: 0, 0
|
||||
UseTilesetCode: false
|
||||
|
||||
@@ -940,7 +940,7 @@ napuls.nod:
|
||||
UseTilesetCode: false
|
||||
ZOffset: 512
|
||||
BlendMode: Additive
|
||||
icon: sidec02.mix:empicon
|
||||
icon: sidebar-nod|empicon
|
||||
Offset: 0, 0
|
||||
UseTilesetCode: false
|
||||
|
||||
@@ -1190,7 +1190,7 @@ proc.gdi:
|
||||
UseTilesetCode: false
|
||||
ZOffset: 512
|
||||
BlendMode: Additive
|
||||
icon: sidec01.mix:reficon
|
||||
icon: sidebar-gdi|reficon
|
||||
Offset: 0, 0
|
||||
UseTilesetCode: false
|
||||
|
||||
@@ -1232,7 +1232,7 @@ proc.nod:
|
||||
UseTilesetCode: false
|
||||
ZOffset: 512
|
||||
BlendMode: Additive
|
||||
icon: sidec02.mix:reficon
|
||||
icon: sidebar-nod|reficon
|
||||
Offset: 0, 0
|
||||
UseTilesetCode: false
|
||||
|
||||
@@ -1323,7 +1323,7 @@ gasilo.gdi:
|
||||
UseTilesetCode: false
|
||||
ZOffset: 512
|
||||
BlendMode: Additive
|
||||
icon: sidec01.mix:siloicon
|
||||
icon: sidebar-gdi|siloicon
|
||||
Offset: 0, 0
|
||||
UseTilesetCode: false
|
||||
|
||||
@@ -1367,7 +1367,7 @@ gasilo.nod:
|
||||
UseTilesetCode: false
|
||||
ZOffset: 512
|
||||
BlendMode: Additive
|
||||
icon: sidec02.mix:siloicon
|
||||
icon: sidebar-nod|siloicon
|
||||
Offset: 0, 0
|
||||
UseTilesetCode: false
|
||||
|
||||
@@ -1431,7 +1431,7 @@ gadept.gdi:
|
||||
UseTilesetCode: false
|
||||
ZOffset: 512
|
||||
BlendMode: Additive
|
||||
icon: sidec01.mix:fixicon
|
||||
icon: sidebar-gdi|fixicon
|
||||
Offset: 0, 0
|
||||
UseTilesetCode: false
|
||||
|
||||
@@ -1495,7 +1495,7 @@ gadept.nod:
|
||||
UseTilesetCode: false
|
||||
ZOffset: 512
|
||||
BlendMode: Additive
|
||||
icon: sidec02.mix:fixicon
|
||||
icon: sidebar-nod|fixicon
|
||||
Offset: 76, 66
|
||||
UseTilesetCode: false
|
||||
|
||||
|
||||
@@ -2,13 +2,13 @@ mcv.gdi:
|
||||
emp-overlay: emp_fx01
|
||||
Length: *
|
||||
BlendMode: Additive
|
||||
icon: sidec01.mix:mcvicon
|
||||
icon: sidebar-gdi|mcvicon
|
||||
|
||||
mcv.nod:
|
||||
emp-overlay: emp_fx01
|
||||
Length: *
|
||||
BlendMode: Additive
|
||||
icon: sidec02.mix:mcvicon
|
||||
icon: sidebar-nod|mcvicon
|
||||
|
||||
apc:
|
||||
emp-overlay: emp_fx01
|
||||
@@ -22,7 +22,7 @@ harv.gdi:
|
||||
BlendMode: Additive
|
||||
harvest: harvestr
|
||||
Length: *
|
||||
icon: sidec01.mix:harvicon
|
||||
icon: sidebar-gdi|harvicon
|
||||
|
||||
harv.nod:
|
||||
emp-overlay: emp_fx01
|
||||
@@ -30,7 +30,7 @@ harv.nod:
|
||||
BlendMode: Additive
|
||||
harvest: harvestr
|
||||
Length: *
|
||||
icon: sidec02.mix:harvicon
|
||||
icon: sidebar-nod|harvicon
|
||||
|
||||
hvr:
|
||||
emp-overlay: emp_fx01
|
||||
@@ -56,7 +56,7 @@ lpst.gdi:
|
||||
emp-overlay: emp_fx01
|
||||
Length: *
|
||||
BlendMode: Additive
|
||||
icon: sidec01.mix:lpsticon
|
||||
icon: sidebar-gdi|lpsticon
|
||||
|
||||
lpst.nod:
|
||||
idle: gadpsa
|
||||
@@ -69,7 +69,7 @@ lpst.nod:
|
||||
emp-overlay: emp_fx01
|
||||
Length: *
|
||||
BlendMode: Additive
|
||||
icon: sidec02.mix:lpsticon
|
||||
icon: sidebar-nod|lpsticon
|
||||
|
||||
repair:
|
||||
emp-overlay: emp_fx01
|
||||
|
||||
Reference in New Issue
Block a user