diff --git a/polymod/backends/OpenFLWithNodeBackend.hx b/polymod/backends/OpenFLWithNodeBackend.hx index 3663979f..4e27f58b 100644 --- a/polymod/backends/OpenFLWithNodeBackend.hx +++ b/polymod/backends/OpenFLWithNodeBackend.hx @@ -319,7 +319,7 @@ class OpenFLNodeModLibrary extends LimeModLibrary { return checkIfAssetIsCached(id, type); } - else if (hasFallback) + else if (fallback != null) { return fallback.isLocal(id, type); } @@ -335,7 +335,7 @@ class OpenFLNodeModLibrary extends LimeModLibrary { modText = cachedText.get(id); } - else if (hasFallback) + else if (fallback != null) { var path:String = fallback.paths.get(id); // check the file name for '?' and remove anything after it diff --git a/polymod/fs/BaseFileSystem.hx b/polymod/fs/BaseFileSystem.hx new file mode 100644 index 00000000..b0bf23d6 --- /dev/null +++ b/polymod/fs/BaseFileSystem.hx @@ -0,0 +1,378 @@ +package polymod.fs; + +import haxe.io.Bytes; +import polymod.Polymod.ModMetadata; +import polymod.Polymod.PolymodErrorOrigin; +import polymod.fs.PolymodFileSystem.IFileSystem; +import polymod.fs.PolymodFileSystem.PolymodFileSystemParams; +import polymod.util.Util; +import polymod.util.VersionUtil; +import thx.semver.VersionRule; + +/** + * Abstract base class providing partial file system implementations. + * Extend this and override methods as needed. + */ +abstract class BaseFileSystem implements IFileSystem +{ + /** + * The directory relative to the application path where mods are located. + */ + public final modRoot:String; + + /** + * A cache of the directories containing mod metadata, indexed by mod ID. + */ + var modMetadataLocations:Map = []; + + public function new(params:PolymodFileSystemParams) + { + this.modRoot = params.modRoot; + } + + /** + * Called when Polymod commands that a mod with the given ID is loaded. + * @param id The ID of the mod that was loaded. + */ + public function onLoadMod(id:String):Void {} + + /** + * Called when Polymod commands that a mod with the given ID is unloaded. + * @param id The ID of the mod that was unloaded. + */ + public function onUnloadMod(id:String):Void {} + + /** + * Return whether the file or directory exists in a specific mod. + * + * @param path The path to check. + * @param modId A specific mod ID to check within. + * @return Whether the file or directory exists in that mod. + */ + public function existsByModId(path:String, modId:String):Bool + { + var modDir:String = scanModDirectoriesForId(modId) ?? return false; + var relativeDir:String = Util.pathJoin(modRoot, modDir); + + return exists(Util.pathJoin(relativeDir, path)); + } + + /** + * Returns a list of files contained within the provided mod directory path. + * + * @param modDir The mod path to check. + * @param recursive Whether to check all subfolder recursively. Returns only files. + * @return An array of file paths. + */ + public function readModDirectory(modDir:String, recursive:Bool = true):Array + { + return recursive ? readDirectoryRecursive(Util.pathJoin(modRoot, modDir)) : readDirectory(Util.pathJoin(modRoot, modDir)); + } + + /** + * Returns the content of a given file as a string. + * + * @param path The file to read. + * @return The text content of the file, or `null` if the file can't be found. + */ + public function getFileContent(path:String):Null + { + return getFileBytes(path)?.toString(); + } + + /** + * Get the byte data for a file from a specific mod. + * + * @param path The path to retrieve byte data from, relative to the asset root. + * @param modId A specific mod ID to retrieve an asset from. + * @return The file bytes, or `null` if it couldn't be fetched. + */ + public function getFileBytesByModId(path:String, modId:String):Null + { + var modDir:String = scanModDirectoriesForId(modId) ?? return null; + var relativeDir:String = Util.pathJoin(modRoot, modDir); + + return getFileBytes(Util.pathJoin(relativeDir, path)); + } + + /** + * Provide a list of valid mods for this file system to load. + * + * @param apiVersionRule (optional) A version query to match against the mod's API version. + * @return An array of matching mods. + */ + public function scanMods(?apiVersionRule:VersionRule):Array + { + if (apiVersionRule == null) apiVersionRule = VersionUtil.DEFAULT_VERSION_RULE; + + var result:Array = []; + + for (modId => modDir in modMetadataLocations) + { + if (!hasMetadataFile(modDir)) + { + // Remove locations that no longer have metadata. + modMetadataLocations.remove(modId); + continue; + } + + var meta:Null = this.getMetadataByModDir(modDir, PolymodErrorOrigin.SCAN); + if (meta == null) + { + // Remove locations whose metadata can no longer be parsed. + modMetadataLocations.remove(modId); + continue; + } + + if (!meta.isCompatible(apiVersionRule)) + { + // Remove locations whose metadata is no longer compatible with the current API version. + Polymod.warning( + MOD_API_VERSION_MISMATCH, + 'Mod "${modDir}" is not compatible with API version "${apiVersionRule.toString()}", got "${meta.apiVersion.toString()}"', + SCAN + ); + modMetadataLocations.remove(modId); + continue; + } + + // Leave the known metadata in place. + result.push(meta); + } + + // Now check EVERY directory. + var knownDirectories:Array = [for (key => value in modMetadataLocations) value]; + var dirsInModRoot:Array = readDirectory(modRoot); + for (modDir in dirsInModRoot) + { + if (knownDirectories.contains(modDir)) + { + // We've already found mod metadata there. + continue; + } + + if (!hasMetadataFile(modDir)) + { + // No mod metadata there. + continue; + } + + var meta:Null = this.getMetadataByModDir(modDir, PolymodErrorOrigin.SCAN); + if (meta == null) + { + // Unparsable mod metadata there. + continue; + } + + if (!meta.isCompatible(apiVersionRule)) + { + // Incompatible mod metadata there. + Polymod.warning( + MOD_API_VERSION_MISMATCH, + 'Mod "${modDir}" is not compatible with API version "${apiVersionRule.toString()}", got "${meta.apiVersion.toString()}"', + SCAN + ); + continue; + } + + // Found a new mod! + modMetadataLocations.set(meta.id, modDir); + result.push(meta); + } + + return result; + } + + /** + * Determines the mod directory associated with a given mod ID. + * + * @param modId The ID of the mod to look for. + * @param origin The context the error occurred in (while scanning for mods, while initializing mods, etc.). + * Used for error reporting. + * @return The directory path where the mod was found, or `null` if not found. + */ + public function scanModDirectoriesForId(modId:String, ?origin:PolymodErrorOrigin):Null + { + // Get the directory that the mod metadata is in from cache. + var knownDirectory:Null = modMetadataLocations.get(modId); + if (knownDirectory != null) return knownDirectory; + + // Otherwise, scan all the directories in the mod root. + for (dir in readDirectory(modRoot)) + { + var modPath:String = Util.pathJoin(modRoot, dir); + if (exists(modPath)) + { + var meta:Null = null; + + var metaFile = Util.pathJoin(modPath, PolymodConfig.modMetadataFile); + var iconFile = Util.pathJoin(modPath, PolymodConfig.modIconFile); + + if (!exists(metaFile)) + { + continue; + } + else + { + var metaText:Null = getFileContent(metaFile); + if (metaText == null) + { + Polymod.warning(MOD_MISSING_METADATA, 'Could not retrieve contents for metadata $metaFile', origin); + continue; + } + meta = ModMetadata.fromJsonStr(metaText, origin); + } + + if (meta == null) continue; + + modMetadataLocations.set(meta.id, dir); + + if (meta.id != modId && dir != modId) continue; + meta.dirName = dir; + meta.modPath = modPath; + + if (!exists(iconFile)) + { + Polymod.warning(MOD_MISSING_ICON, 'Could not find mod icon file: $iconFile', origin); + } + else + { + var iconBytes:Null = getFileBytes(iconFile); + if (iconBytes == null) + { + Polymod.warning(MOD_MISSING_ICON, 'Could not obtain mod icon for icon file : $iconFile', origin); + } + else + { + meta.icon = iconBytes; + meta.iconPath = iconFile; + } + } + + return dir; + } + } + Polymod.error(MOD_MISSING_ID, 'Could not find mod with ID: $modId', origin); + return null; + } + + /** + * Get the metadata for a given mod. + * This function is DEPRECATED, use `getMetadataByModDir` for the same result. + * + * @param dirName The directory name of the mod. + * @param origin The error reporting origin. + * @return The mod metadata, or `null` if not found. + */ + @:deprecated('getMetadata is deprecated, use getMetadataByModDir') + public function getMetadata(dirName:String, ?origin:PolymodErrorOrigin):Null + { + return getMetadataByModDir(dirName, origin); + } + + /** + * Provides the metadata for a given mod by its directory. + * + * @param dir The directory of the mod. + * @param origin The context the error occurred in (while scanning for mods, while initializing mods, etc.). + * Used for error reporting. + * @return The mod metadata, or `null` if the mod does not exist. + */ + public function getMetadataByModDir(dirName:String, ?origin:PolymodErrorOrigin):Null + { + var modPath:String = Util.pathJoin(modRoot, dirName); + if (exists(modPath)) + { + var meta:Null = null; + + var metaFile:String = Util.pathJoin(modPath, PolymodConfig.modMetadataFile); + var iconFile:String = Util.pathJoin(modPath, PolymodConfig.modIconFile); + + if (!exists(metaFile)) + { + Polymod.warning(MOD_MISSING_METADATA, 'Could not find mod metadata file: $metaFile', origin); + return null; + } + else + { + var metaText:Null = getFileContent(metaFile); + if (metaText == null || metaText == '') + { + Polymod.warning(MOD_MISSING_METADATA, 'Could not read mod metadata file for mod "$dirName"', origin); + return null; + } + + meta = ModMetadata.fromJsonStr(metaText, origin); + } + + if (meta == null) + { + return null; + } + + meta.id = meta.id == '' ? dirName : meta.id; + meta.dirName = dirName; + meta.modPath = modPath; + + if (!exists(iconFile)) + { + Polymod.warning(MOD_MISSING_ICON, 'Could not find mod icon file: $iconFile', origin); + } + else + { + var iconBytes:Null = getFileBytes(iconFile); + if (iconBytes == null) + { + Polymod.warning(MOD_MISSING_ICON, 'Could not obtain mod icon for icon file : $iconFile', origin); + } + else + { + meta.icon = iconBytes; + meta.iconPath = iconFile; + } + } + return meta; + } + else + { + Polymod.error(MOD_MISSING_DIRECTORY, 'Could not find mod directory: $modPath', origin); + } + return null; + } + + /** + * Provides the metadata for a given mod by its ID. + * + * @param modId The ID of the mod. + * @param origin The context the error occurred in (while scanning for mods, while initializing mods, etc.). + * Used for error reporting. + * @return The mod metadata, or `null` if the mod does not exist. + */ + public function getMetadataByModId(modId:String, ?origin:PolymodErrorOrigin):Null + { + var knownDirectory:Null = scanModDirectoriesForId(modId, origin); + if (knownDirectory != null) + { + var result:Null = getMetadataByModDir(knownDirectory, origin); + if (result != null) + { + return result; + } + else + { + modMetadataLocations.remove(modId); + } + } + + // We don't know where the mod is located. + return null; + } + + function hasMetadataFile(dirName:String):Bool + { + var modPath:String = Util.pathJoin(modRoot, dirName); + if (!isDirectory(modPath)) return false; + var metaFile:String = Util.pathJoin(modPath, PolymodConfig.modMetadataFile); + return exists(metaFile); + } +} diff --git a/polymod/fs/MemoryFileSystem.hx b/polymod/fs/MemoryFileSystem.hx index 012071b9..c53dd9b9 100644 --- a/polymod/fs/MemoryFileSystem.hx +++ b/polymod/fs/MemoryFileSystem.hx @@ -2,11 +2,7 @@ package polymod.fs; import haxe.io.Bytes; import haxe.io.Path; -import polymod.Polymod; import polymod.util.Util; -import polymod.util.VersionUtil; -import thx.semver.VersionRule; -import polymod.fs.PolymodFileSystem.IFileSystem; import polymod.fs.PolymodFileSystem.PolymodFileSystemParams; /** @@ -21,16 +17,10 @@ import polymod.fs.PolymodFileSystem.PolymodFileSystemParams; * Using this file system directly is not recommended, as it is not optimized for native platforms. * If you can use a native file system, use `SysFileSystem` or `ZipFileSystem` instead. */ -class MemoryFileSystem implements IFileSystem +class MemoryFileSystem extends BaseFileSystem { - var files:Map = new Map(); + var files:Map = []; var directories:Array = []; - public final modRoot:String = ''; - - /** - * A cache of the directories containing mod metadata, indexed by mod ID. - */ - var modMetadataLocations:Map = []; /** * Receive parameters to instantiate the MemoryFileSystem. @@ -38,7 +28,8 @@ class MemoryFileSystem implements IFileSystem public function new(params:PolymodFileSystemParams) { // No-op constructor. - modRoot = (params.modRoot == null) ? '' : params.modRoot; + params.modRoot ??= ''; + super(params); } /** @@ -70,88 +61,12 @@ class MemoryFileSystem implements IFileSystem files.remove(path); } - public function onLoadMod(modId:String):Void - { - } - - public function onUnloadMod(modId:String):Void - { - } - - public function readModDirectory(modDir:String, recursive:Bool = true):Array - { - return recursive - ? readDirectoryRecursive(Util.pathJoin(modRoot, modDir)) - : readDirectory(Util.pathJoin(modRoot, modDir)); - } - - /** - * Determines the mod directory associated with a given mod ID. - * - * @param modId The ID of the mod to look for. - * @param origin The context the error occurred in (while scanning for mods, while initializing mods, etc.). - * Used for error reporting. - * @return The directory path where the mod was found, or `null` if not found. - */ - public function scanModDirectoriesForId(modId:String, ?origin:PolymodErrorOrigin):Null - { - // Get the directory that the mod metadata is in from cache. - var knownDirectory:Null = modMetadataLocations.get(modId); - if (knownDirectory != null) return knownDirectory; - - // Otherwise, scan all the directories in the mod root. - for (dir in readDirectory(modRoot)) - { - var modPath = Util.pathJoin(modRoot, dir); - if (exists(modPath)) - { - var meta:ModMetadata = null; - - var metaFile = Util.pathJoin(modPath, PolymodConfig.modMetadataFile); - var iconFile = Util.pathJoin(modPath, PolymodConfig.modIconFile); - - if (!exists(metaFile)) - { - continue; - } - else - { - var metaText = getFileContent(metaFile); - meta = ModMetadata.fromJsonStr(metaText, origin); - } - - if (meta == null) continue; - - modMetadataLocations.set(meta.id, dir); - - if (meta.id != modId && dir != modId) continue; - meta.dirName = dir; - meta.modPath = modPath; - - if (!exists(iconFile)) - { - Polymod.warning(MOD_MISSING_ICON, 'Could not find mod icon file: $iconFile', origin); - } - else - { - var iconBytes = getFileBytes(iconFile); - meta.icon = iconBytes; - meta.iconPath = iconFile; - } - - return dir; - } - } - Polymod.error(MOD_MISSING_ID, 'Could not find mod with ID: $modId', origin); - return null; - } - /** * Call this function to clear all files from the virtual file system. */ public function clear():Void { - files = new Map(); + files.clear(); directories = []; } @@ -167,22 +82,6 @@ class MemoryFileSystem implements IFileSystem return files.exists(path) || directories.contains(path); // checks both files and folders } - /** - * Return whether the file or directory exists in a specific mod. - * - * @param path The path to check. - * @param modId A specific mod ID to check within. - * @return Whether the file or directory exists in that mod. - */ - public function existsByModId(path:String, modId:String):Bool - { - var modDir:Null = scanModDirectoriesForId(modId); - if (modDir == null) return false; - var relativeDir = Util.pathJoin(modRoot, modDir); - - return exists(Util.pathJoin(relativeDir, path)); - } - /** * Returns whether the provided path is a directory. * @@ -229,19 +128,6 @@ class MemoryFileSystem implements IFileSystem return result; } - /** - * Returns the content of a given file as a string. - * - * @param path The file to read. - * @return The text content of the file, or `null` if the file can't be found. - */ - public function getFileContent(path:String):Null - { - var fileBytes = getFileBytes(path); - if (fileBytes == null) return null; - return fileBytes.toString(); - } - /** * Returns the content of a given file as Bytes. * @@ -253,22 +139,6 @@ class MemoryFileSystem implements IFileSystem return files.get(path); } - /** - * Get the byte data for a file from a specific mod. - * - * @param path The path to retrieve byte data from, relative to the asset root. - * @param modId A specific mod ID to retrieve an asset from. - * @return The file bytes, or `null` if it couldn't be fetched. - */ - public function getFileBytesByModId(path:String, modId:String):Null - { - var modDir:Null = scanModDirectoriesForId(modId); - if (modDir == null) return null; - var relativeDir = Util.pathJoin(modRoot, modDir); - - return getFileBytes(Util.pathJoin(relativeDir, path)); - } - /** * Returns a list of files contained within the provided directory path. * Checks all subfolders recursively. Returns only files. @@ -295,182 +165,4 @@ class MemoryFileSystem implements IFileSystem // })); return result; } - - /** - * Provide a list of valid mods for this file system to load. - * - * @param apiVersionRule (optional) A version query to match against the mod's API version. - * @return An array of matching mods. - */ - public function scanMods(?apiVersionRule:VersionRule):Array - { - if (apiVersionRule == null) apiVersionRule = VersionUtil.DEFAULT_VERSION_RULE; - - var result:Array = []; - - for (modId => modDir in modMetadataLocations) - { - if (!hasMetadataFile(modDir)) - { - // Remove locations that no longer have metadata. - modMetadataLocations.remove(modId); - continue; - } - var meta:ModMetadata = this.getMetadataByModDir(modDir, PolymodErrorOrigin.SCAN); - if (meta == null) - { - // Remove locations whose metadata can no longer be parsed. - modMetadataLocations.remove(modId); - continue; - } - - if (!meta.isCompatible(apiVersionRule)) - { - // Remove locations whose metadata is no longer compatible with the current API version. - Polymod.warning(MOD_API_VERSION_MISMATCH, - 'Mod "${modDir}" is not compatible with API version "${apiVersionRule.toString()}", got "${meta.apiVersion.toString()}"', - SCAN); - modMetadataLocations.remove(modId); - continue; - } - - // Leave the known metadata in place. - result.push(meta); - } - - var knownDirectories:Array = [for (key => value in modMetadataLocations) value]; - var dirsInModRoot:Array = readDirectory(modRoot); - for (modDir in dirsInModRoot) - { - if (knownDirectories.contains(modDir)) - { - // We've already found mod metadata there. - continue; - } - - if (!hasMetadataFile(modDir)) - { - // No mod metadata there. - continue; - } - - var meta:ModMetadata = this.getMetadataByModDir(modDir, PolymodErrorOrigin.SCAN); - if (meta == null) - { - // Unparsable mod metadata there. - continue; - } - - if (!meta.isCompatible(apiVersionRule)) - { - // Incompatible mod metadata there. - Polymod.warning(MOD_API_VERSION_MISMATCH, - 'Mod "${modDir}" is not compatible with API version "${apiVersionRule.toString()}", got "${meta.apiVersion.toString()}"', - SCAN); - continue; - } - - // Found a new mod! - modMetadataLocations.set(meta.id, modDir); - result.push(meta); - } - - return result; - } - - /** - * Get the metadata for a given mod. - * This function is DEPRECATED, use `getMetadataByModDir` for the same result. - * - * @param dirName The directory name of the mod. - * @param origin The error reporting origin. - * @return The mod metadata, or `null` if not found. - */ - function hasMetadataFile(dirName:String):Bool - { - var modPath = Util.pathJoin(modRoot, dirName); - if (!isDirectory(modPath)) return false; - var metaFile = Util.pathJoin(modPath, PolymodConfig.modMetadataFile); - return exists(metaFile); - } - - /** - * Get the metadata for a given mod. - * This function is DEPRECATED, use `getMetadataByModDir` for the same result. - * - * @param dirName The directory name of the mod. - * @param origin The error reporting origin. - * @return The mod metadata, or `null` if not found. - */ - @:deprecated('getMetadata is deprecated, use getMetadataByModDir') - public function getMetadata(dirName:String, ?origin:PolymodErrorOrigin):Null - { - return getMetadataByModDir(dirName, origin); - } - - /** - * Provides the metadata for a given mod by its directory. - * - * @param dirName The directory of the mod. - * @param origin The context the error occurred in (while scanning for mods, while initializing mods, etc.). - * Used for error reporting. - * @return The mod metadata, or `null` if the mod does not exist. - */ - public function getMetadataByModDir(dirName:String, ?origin:PolymodErrorOrigin):Null - { - var modpath = Util.pathJoin(modRoot, dirName); - if (exists(modpath)) - { - var meta:ModMetadata = null; - - var metaFile = Util.pathJoin(modpath, PolymodConfig.modMetadataFile); - var iconFile = Util.pathJoin(modpath, PolymodConfig.modIconFile); - - if (!exists(metaFile)) - { - Polymod.warning(MOD_MISSING_METADATA, 'Could not find mod metadata file: $metaFile', origin); - return null; - } - else - { - var metaText = getFileContent(metaFile); - meta = ModMetadata.fromJsonStr(metaText, origin); - if (meta == null) return null; - - meta.id = meta.id == '' ? dirName : meta.id; - meta.dirName = dirName; - meta.modPath = modpath; - } - - if (!exists(iconFile)) - { - Polymod.warning(MOD_MISSING_ICON, 'Could not find mod icon file: $iconFile', origin); - } - else - { - var iconBytes = getFileBytes(iconFile); - meta.icon = iconBytes; - meta.iconPath = iconFile; - } - return meta; - } - else - { - Polymod.error(MOD_MISSING_DIRECTORY, 'Could not find mod directory: $dirName', origin); - } - return null; - } - - /** - * Provides the metadata for a given mod by its ID. - * - * @param modId The ID of the mod. - * @param origin The context the error occurred in (while scanning for mods, while initializing mods, etc.). - * Used for error reporting. - * @return The mod metadata, or `null` if the mod does not exist. - */ - public function getMetadataByModId(modId:String, ?origin:PolymodErrorOrigin):Null - { - return null; - } } diff --git a/polymod/fs/MemoryZipFileSystem.hx b/polymod/fs/MemoryZipFileSystem.hx index 5ed7ab7b..9ef3a564 100644 --- a/polymod/fs/MemoryZipFileSystem.hx +++ b/polymod/fs/MemoryZipFileSystem.hx @@ -6,8 +6,6 @@ import polymod.util.Util; import haxe.io.Bytes; import haxe.io.BytesInput; import haxe.io.Path; -import polymod.fs.PolymodFileSystem.IFileSystem; -import polymod.fs.PolymodFileSystem.PolymodFileSystemParams; #if !html5 /** diff --git a/polymod/fs/NodeFileSystem.hx b/polymod/fs/NodeFileSystem.hx index 9f676f52..a1840c19 100644 --- a/polymod/fs/NodeFileSystem.hx +++ b/polymod/fs/NodeFileSystem.hx @@ -5,34 +5,16 @@ import haxe.io.UInt8Array; import js.Browser; import js.html.ScriptElement; import js.Lib; -import polymod.Polymod; -import polymod.PolymodConfig; -import polymod.fs.PolymodFileSystem.IFileSystem; -import polymod.util.Util; -import polymod.util.VersionUtil; -import thx.semver.VersionRule; -import polymod.fs.PolymodFileSystem.IFileSystem; -import polymod.fs.PolymodFileSystem.PolymodFileSystemParams; /** * An implementation of IFileSystem which accesses files from the local directory, * when running in Node.js via Electron. */ -class NodeFileSystem implements IFileSystem +class NodeFileSystem extends BaseFileSystem { // hack to make sure NodeUtils.injectJSCode is called static var _jsCodeInjected:Bool = injectJSCode(); - /** - * The directory relative to the application path where mods are located. - */ - public final modRoot:String; - - public function new(params:polymod.fs.PolymodFileSystem.PolymodFileSystemParams) - { - this.modRoot = params.modRoot; - } - /** * Injects JS code needed to interact with Node's file system into the head element of the HTML document. * @return @@ -73,7 +55,7 @@ class NodeFileSystem implements IFileSystem jsCode.push('function exists(path) { return _nodefs.existsSync(path); }'); jsCode.push('function getStats(path) { return exists(path) ? _nodefs.statSync(path) : null; }'); jsCode.push('function isDirectory(path) { var stats = getStats(path); return stats != null && stats.isDirectory(); }'); - jsCode.push('function getFileContent(path) { return exists(path) ? _nodefs.readFileSync(path, {encoding:'utf8', flag:'r'}) : ''; }'); + jsCode.push('function getFileContent(path) { return exists(path) ? _nodefs.readFileSync(path, {encoding:\' utf8 \', flag:\' r \'}) : \' \'; }'); jsCode.push('function getFileBytes(path) { return exists(path) ? Uint8Array.from( _nodefs.readFileSync(path) ) : null; }'); jsCode.push('function readDirectory(path) { return getDirectoryContents(path, false, []) }'); jsCode.push('function readDirectoryRecursive(path) { return getDirectoryContents(path, true, []) }'); @@ -95,7 +77,7 @@ class NodeFileSystem implements IFileSystem * @param arg * @return */ - function callFunc(functionName:String, arg:Dynamic = null):Dynamic + function callFunc(functionName:String, ?arg:Dynamic):Dynamic { if (!~/^\(.+\)$/.match(functionName)) { @@ -141,22 +123,6 @@ class NodeFileSystem implements IFileSystem return callFunc('exists', path); } - /** - * Return whether the file or directory exists in a specific mod. - * - * @param path The path to check. - * @param modId A specific mod ID to check within. - * @return Whether the file or directory exists in that mod. - */ - public function existsByModId(path:String, modId:String):Bool - { - var modDir:Null = scanModDirectoriesForId(modId); - if (modDir == null) return false; - var relativeDir = Util.pathJoin(modRoot, modDir); - - return exists(Util.pathJoin(relativeDir, path)); - } - /** * Returns whether the provided path is a directory. * @@ -188,7 +154,7 @@ class NodeFileSystem implements IFileSystem * @param path The file to read. * @return The text content of the file, or `null` if the file can't be found. */ - public inline function getFileContent(path:String):Null + public override inline function getFileContent(path:String):Null { return callFunc('getFileContent', path); } @@ -205,22 +171,6 @@ class NodeFileSystem implements IFileSystem return intArr != null ? Bytes.ofArray(intArr) : null; } - /** - * Get the byte data for a file from a specific mod. - * - * @param path The path to retrieve byte data from, relative to the asset root. - * @param modId A specific mod ID to retrieve an asset from. - * @return The file bytes, or `null` if it couldn't be fetched. - */ - public function getFileBytesByModId(path:String, modId:String):Null - { - var modDir:Null = scanModDirectoriesForId(modId); - if (modDir == null) return null; - var relativeDir = Util.pathJoin(modRoot, modDir); - - return getFileBytes(Util.pathJoin(relativeDir, path)); - } - /** * Returns a list of files contained within the provided directory path. * Checks all subfolders recursively. Returns only files. @@ -234,108 +184,4 @@ class NodeFileSystem implements IFileSystem sanitizePaths(path, arr); return arr; } - - /** - * Provide a list of valid mods for this file system to load. - * - * @param apiVersionRule (optional) A version query to match against the mod's API version. - * @return An array of matching mods. - */ - public function scanMods(?apiVersionRule:VersionRule):Array - { - if (apiVersionRule == null) apiVersionRule = VersionUtil.DEFAULT_VERSION_RULE; - - var dirs = readDirectory(modRoot); - var result:Array = []; - for (dir in dirs) - { - var testDir = Util.pathJoin(modRoot, dir); - - if (!exists(testDir)) continue; - - if (!isDirectory(testDir)) continue; - - var meta:ModMetadata = this.getMetadataByModDir(dir, PolymodErrorOrigin.SCAN); - - if (meta == null) continue; - - if (!meta.isCompatible(apiVersionRule)) continue; - - result.push(meta); - } - - return result; - } - - /** - * Get the metadata for a given mod. - * This function is DEPRECATED, use `getMetadataByModDir` for the same result. - * - * @param dirName The directory name of the mod. - * @param origin The error reporting origin. - * @return The mod metadata, or `null` if not found. - */ - @:deprecated('getMetadata is deprecated, use getMetadataByModDir') - public function getMetadata(dirName:String, ?origin:PolymodErrorOrigin):Null - { - return getMetadataByModDir(dirName, origin); - } - - /** - * Provides the metadata for a given mod by its directory. - * - * @param dir The directory of the mod. - * @param origin The context the error occurred in (while scanning for mods, while initializing mods, etc.). - * Used for error reporting. - * @return The mod metadata, or `null` if the mod does not exist. - */ - public function getMetadataByModDir(dir:String, ?origin:PolymodErrorOrigin):Null - { - if (exists(dir)) - { - var meta:ModMetadata = null; - - var metaFile = Util.pathJoin(dir, PolymodConfig.modMetadataFile); - var iconFile = Util.pathJoin(dir, PolymodConfig.modIconFile); - - if (!exists(metaFile)) - { - Polymod.warning(MOD_MISSING_METADATA, 'Could not find mod metadata file: $metaFile', origin); - } - else - { - var metaText = getFileContent(metaFile); - meta = ModMetadata.fromJsonStr(metaText, origin); - } - if (!exists(iconFile)) - { - Polymod.warning(MOD_MISSING_ICON, 'Could not find mod icon file: $iconFile', origin); - } - else - { - var iconBytes = getFileBytes(iconFile); - meta.icon = iconBytes; - meta.iconPath = iconFile; - } - return meta; - } - else - { - Polymod.error(MOD_MISSING_DIRECTORY, 'Could not find mod directory: "$dir"', origin); - } - return null; - } - - /** - * Provides the metadata for a given mod by its ID. - * - * @param modId The ID of the mod. - * @param origin The context the error occurred in (while scanning for mods, while initializing mods, etc.). - * Used for error reporting. - * @return The mod metadata, or `null` if the mod does not exist. - */ - public function getMetadataByModId(modId:String, ?origin:PolymodErrorOrigin):Null - { - return null; - } } diff --git a/polymod/fs/PolymodFileSystem.hx b/polymod/fs/PolymodFileSystem.hx index b34c61ad..0650ba13 100644 --- a/polymod/fs/PolymodFileSystem.hx +++ b/polymod/fs/PolymodFileSystem.hx @@ -78,6 +78,7 @@ typedef PolymodFileSystemParams = interface IFileSystem { public final modRoot:String; + /** * Returns whether the file or directory at the given path exists. * @@ -133,6 +134,13 @@ interface IFileSystem */ public function readDirectoryRecursive(path:String):Array; + /** + * Returns a list of files contained within the provided mod directory path. + * + * @param modDir The mod path to check. + * @param recursive Whether to check all subfolder recursively. Returns only files. + * @return An array of file paths. + */ public function readModDirectory(modDir:String, recursive:Bool = true):Array; /** diff --git a/polymod/fs/SysFileSystem.hx b/polymod/fs/SysFileSystem.hx index 36dad5b4..d0b8836d 100644 --- a/polymod/fs/SysFileSystem.hx +++ b/polymod/fs/SysFileSystem.hx @@ -2,11 +2,7 @@ package polymod.fs; #if sys import polymod.Polymod; -import polymod.fs.PolymodFileSystem.IFileSystem; -import polymod.fs.PolymodFileSystem.PolymodFileSystemParams; import polymod.util.Util; -import polymod.util.VersionUtil; -import thx.semver.VersionRule; import sys.io.FileInput; #if (!windows) @@ -17,18 +13,8 @@ using StringTools; * An implementation of IFileSystem which accesses files from folders in the local directory. * This is currently the default file system for native/Desktop platforms. */ -class SysFileSystem implements IFileSystem +class SysFileSystem extends BaseFileSystem { - /** - * The directory relative to the application path where mods are located. - */ - public final modRoot:String; - - /** - * A cache of the directories containing mod metadata, indexed by mod ID. - */ - var modMetadataLocations:Map = []; - /** * By establishing and maintaining a file handle to the mod metadata, * we can stop other applications from trying to delete mods that are in use! @@ -37,11 +23,6 @@ class SysFileSystem implements IFileSystem */ var fileHandles:Map = []; - public function new(params:PolymodFileSystemParams) - { - this.modRoot = params.modRoot; - } - /** * Check if a file or directory exists. * @@ -57,22 +38,6 @@ class SysFileSystem implements IFileSystem #end } - /** - * Return whether the file or directory exists in a specific mod. - * - * @param path The path to check. - * @param modId A specific mod ID to check within. - * @return Whether the file or directory exists in that mod. - */ - public function existsByModId(path:String, modId:String):Bool - { - var modDir:Null = scanModDirectoriesForId(modId); - if (modDir == null) return false; - var relativeDir = Util.pathJoin(modRoot, modDir); - - return exists(Util.pathJoin(relativeDir, path)); - } - /** * Check if the specified path is a directory. * @@ -82,7 +47,7 @@ class SysFileSystem implements IFileSystem public function isDirectory(path:String) { #if (!windows) - path = getPathLike(path); + path = getPathLike(path) ?? return false; #end return sys.FileSystem.isDirectory(path); } @@ -99,7 +64,7 @@ class SysFileSystem implements IFileSystem try { #if (!windows) - path = getPathLike(path); + path = getPathLike(path) ?? throw 'Invalid directory "$path".'; #end return sys.FileSystem.readDirectory(path); } @@ -110,26 +75,19 @@ class SysFileSystem implements IFileSystem } } - public function readModDirectory(modDir:String, recursive:Bool = true):Array - { - return recursive - ? readDirectoryRecursive(Util.pathJoin(modRoot, modDir)) - : readDirectory(Util.pathJoin(modRoot, modDir)); - } - /** * Get the byte data for a file. * * @param path The path to retrieve byte data from. * @return The file contents, or `null` if it couldn't be fetched. */ - public function getFileContent(path:String):Null + public override function getFileContent(path:String):Null { #if (!windows) - path = getPathLike(path); + path = getPathLike(path) ?? return null; #end - return getFileBytes(path)?.toString(); + return super.getFileContent(path); } /** @@ -141,8 +99,7 @@ class SysFileSystem implements IFileSystem public function getFileBytes(path:String):Null { #if (!windows) - path = getPathLike(path); - if (path == null) return null; + path = getPathLike(path) ?? return null; #else if (!exists(path)) return null; #end @@ -157,7 +114,7 @@ class SysFileSystem implements IFileSystem return sys.io.File.getBytes(path); } - public function onLoadMod(modId:String):Void + public override function onLoadMod(modId:String):Void { if (!PolymodConfig.fileLock) return; @@ -174,7 +131,7 @@ class SysFileSystem implements IFileSystem fileHandles.set(metaFile, sys.io.File.read(metaFile)); } - public function onUnloadMod(modId:String):Void + public override function onUnloadMod(modId:String):Void { if (!PolymodConfig.fileLock) return; @@ -191,280 +148,6 @@ class SysFileSystem implements IFileSystem fileHandles.remove(metaFile); } - /** - * Get the byte data for a file from a specific mod. - * - * @param path The path to retrieve byte data from, relative to the asset root. - * @param modId A specific mod ID to retrieve an asset from. - * @return The file bytes, or `null` if it couldn't be fetched. - */ - public function getFileBytesByModId(path:String, modId:String):Null - { - var modDir:Null = scanModDirectoriesForId(modId); - if (modDir == null) return null; - var relativeDir = Util.pathJoin(modRoot, modDir); - - return getFileBytes(Util.pathJoin(relativeDir, path)); - } - - /** - * Retrieve a list of ModMetadata for each installed mod. - * - * @param apiVersionRule (optional) Specify a version rule that scanned mods must conform to. - * @return The list of ModMetadata for found mods. - */ - public function scanMods(?apiVersionRule:VersionRule):Array - { - if (apiVersionRule == null) apiVersionRule = VersionUtil.DEFAULT_VERSION_RULE; - - var result:Array = []; - - for (modId => modDir in modMetadataLocations) - { - if (!hasMetadataFile(modDir)) - { - // Remove locations that no longer have metadata. - modMetadataLocations.remove(modId); - continue; - } - - var meta:ModMetadata = this.getMetadataByModDir(modDir, PolymodErrorOrigin.SCAN); - if (meta == null) - { - // Remove locations whose metadata can no longer be parsed. - modMetadataLocations.remove(modId); - continue; - } - - if (!meta.isCompatible(apiVersionRule)) - { - // Remove locations whose metadata is no longer compatible with the current API version. - Polymod.warning(MOD_API_VERSION_MISMATCH, - 'Mod "${modDir}" is not compatible with API version "${apiVersionRule.toString()}", got "${meta.apiVersion.toString()}"', - SCAN); - modMetadataLocations.remove(modId); - continue; - } - - // Leave the known metadata in place. - result.push(meta); - } - - // Now check EVERY directory. - var knownDirectories:Array = [for (key => value in modMetadataLocations) value]; - var dirsInModRoot:Array = readDirectory(modRoot); - for (modDir in dirsInModRoot) - { - if (knownDirectories.contains(modDir)) - { - // We've already found mod metadata there. - continue; - } - - if (!hasMetadataFile(modDir)) - { - // No mod metadata there. - continue; - } - - var meta:ModMetadata = this.getMetadataByModDir(modDir, PolymodErrorOrigin.SCAN); - if (meta == null) - { - // Unparsable mod metadata there. - continue; - } - - if (!meta.isCompatible(apiVersionRule)) - { - // Incompatible mod metadata there. - Polymod.warning(MOD_API_VERSION_MISMATCH, - 'Mod "${modDir}" is not compatible with API version "${apiVersionRule.toString()}", got "${meta.apiVersion.toString()}"', - SCAN); - continue; - } - - // Found a new mod! - modMetadataLocations.set(meta.id, modDir); - result.push(meta); - } - - return result; - } - - /** - * Get the metadata for a given mod. - * This function is DEPRECATED, use `getMetadataByModDir` for the same result. - * - * @param dirName The directory name of the mod. - * @param origin The error reporting origin. - * @return The mod metadata, or `null` if not found. - */ - @:deprecated('getMetadata is deprecated, use getMetadataByModDir') - public function getMetadata(dirName:String, ?origin:PolymodErrorOrigin):Null - { - return getMetadataByModDir(dirName, origin); - } - - function hasMetadataFile(dirName:String):Bool - { - var modPath = Util.pathJoin(modRoot, dirName); - if (!isDirectory(modPath)) return false; - var metaFile = Util.pathJoin(modPath, PolymodConfig.modMetadataFile); - return exists(metaFile); - } - - /** - * Provides the metadata for a given mod by its directory. - * - * @param dirName The directory of the mod. - * @param origin The context the error occurred in (while scanning for mods, while initializing mods, etc.). - * Used for error reporting. - * @return The mod metadata, or `null` if the mod does not exist. - */ - public function getMetadataByModDir(dirName:String, ?origin:PolymodErrorOrigin):Null - { - var modPath = Util.pathJoin(modRoot, dirName); - if (exists(modPath)) - { - var meta:ModMetadata = null; - - var metaFile = Util.pathJoin(modPath, PolymodConfig.modMetadataFile); - var iconFile = Util.pathJoin(modPath, PolymodConfig.modIconFile); - - if (!exists(metaFile)) - { - Polymod.warning(MOD_MISSING_METADATA, 'Could not find mod metadata file: $metaFile', origin); - return null; - } - else - { - var metaText:Null = getFileContent(metaFile); - if (metaText == null || metaText == '') { - Polymod.warning(MOD_MISSING_METADATA, 'Could not read mod metadata file for mod "$dirName"', origin); - return null; - } - - meta = ModMetadata.fromJsonStr(metaText, origin); - } - - if (meta == null) - { - return null; - } - - meta.id = meta.id == '' ? dirName : meta.id; - meta.dirName = dirName; - meta.modPath = modPath; - - if (!exists(iconFile)) - { - Polymod.warning(MOD_MISSING_ICON, 'Could not find mod icon file: $iconFile', origin); - } - else - { - var iconBytes = getFileBytes(iconFile); - meta.icon = iconBytes; - meta.iconPath = iconFile; - } - return meta; - } - else - { - Polymod.error(MOD_MISSING_DIRECTORY, 'Could not find mod directory: $modPath', origin); - } - return null; - } - - /** - * Provides the metadata for a given mod by its ID. - * - * @param modId The ID of the mod. - * @param origin The context the error occurred in (while scanning for mods, while initializing mods, etc.). - * Used for error reporting. - * @return The mod metadata, or `null` if the mod does not exist. - */ - public function getMetadataByModId(modId:String, ?origin:PolymodErrorOrigin):Null - { - var knownDirectory:Null = scanModDirectoriesForId(modId, origin); - if (knownDirectory != null) - { - var result = getMetadataByModDir(knownDirectory, origin); - if (result != null) - { - return result; - } - else - { - trace('LOST metadata for mod $modId'); - modMetadataLocations.remove(modId); - } - } - - // We don't know where the mod is located. - return null; - } - - /** - * Determines the mod directory associated with a given mod ID. - * - * @param modId The ID of the mod to look for. - * @param origin The context the error occurred in (while scanning for mods, while initializing mods, etc.). - * Used for error reporting. - * @return The directory path where the mod was found, or `null` if not found. - */ - public function scanModDirectoriesForId(modId:String, ?origin:PolymodErrorOrigin):Null - { - // Get the directory that the mod metadata is in from cache. - var knownDirectory:Null = modMetadataLocations.get(modId); - if (knownDirectory != null) return knownDirectory; - - // Otherwise, scan all the directories in the mod root. - for (dir in readDirectory(modRoot)) - { - var modPath = Util.pathJoin(modRoot, dir); - if (exists(modPath)) - { - var meta:ModMetadata = null; - - var metaFile = Util.pathJoin(modPath, PolymodConfig.modMetadataFile); - var iconFile = Util.pathJoin(modPath, PolymodConfig.modIconFile); - - if (!exists(metaFile)) - { - continue; - } - else - { - var metaText = getFileContent(metaFile); - meta = ModMetadata.fromJsonStr(metaText, origin); - } - - if (meta == null) continue; - - modMetadataLocations.set(meta.id, dir); - - if (meta.id != modId && dir != modId) continue; - meta.dirName = dir; - meta.modPath = modPath; - - if (!exists(iconFile)) - { - Polymod.warning(MOD_MISSING_ICON, 'Could not find mod icon file: $iconFile', origin); - } - else - { - var iconBytes = getFileBytes(iconFile); - meta.icon = iconBytes; - meta.iconPath = iconFile; - } - - return dir; - } - } - Polymod.error(MOD_MISSING_ID, 'Could not find mod with ID: $modId', origin); - return null; - } - /** * Retrieve a list of files and directories in the given path, recursively. * diff --git a/polymod/fs/SysZipFileSystem.hx b/polymod/fs/SysZipFileSystem.hx index 2cf6fd24..2059aa72 100644 --- a/polymod/fs/SysZipFileSystem.hx +++ b/polymod/fs/SysZipFileSystem.hx @@ -1,16 +1,12 @@ package polymod.fs; - #if sys -import polymod.util.VersionUtil; -import polymod.Polymod; -import polymod.fs.ZipFileSystem.ZipFileSystemParams; -import polymod.fs.PolymodFileSystem.IFileSystem; -import polymod.fs.PolymodFileSystem.PolymodFileSystemParams; import haxe.Constraints.IMap; import haxe.ds.StringMap; import haxe.io.Bytes; import haxe.io.Path; +import polymod.Polymod; +import polymod.fs.ZipFileSystem.ZipFileSystemParams; import polymod.util.Util; import polymod.util.InsensitiveMap; import polymod.util.zip.ZipParser; @@ -66,7 +62,8 @@ class SysZipFileSystem extends SysFileSystem } #end - public override function onLoadMod(modId:String):Void { + public override function onLoadMod(modId:String):Void + { if (!PolymodConfig.fileLock) return; var modDir:Null = scanModDirectoriesForId(modId); @@ -98,7 +95,8 @@ class SysZipFileSystem extends SysFileSystem } } - public override function onUnloadMod(modId:String):Void { + public override function onUnloadMod(modId:String):Void + { if (!PolymodConfig.fileLock) return; var modDir:Null = scanModDirectoriesForId(modId); @@ -123,7 +121,8 @@ class SysZipFileSystem extends SysFileSystem } } - function isModDirInZip(dirName:String):Bool { + function isModDirInZip(dirName:String):Bool + { var modPath = Util.pathJoin(modRoot, dirName); if (!exists(modPath)) return false; @@ -147,18 +146,19 @@ class SysZipFileSystem extends SysFileSystem // we go directly to the zip file and extract the individual file. // Determine which zip the target file is in. - var zipPath = filesLocations.get(path); - var zipParser = zipParsers.get(zipPath); + var zipPath:Null = filesLocations.get(path); + var zipParser:Null = zipPath != null ? zipParsers.get(zipPath) : null; // Check that the ZIP is valid. - if (zipParser == null || !zipParser.isValid()) { + if (zipParser == null || !zipParser.isValid()) + { purgeZipPath(zipPath); return null; } - var modId = Path.withoutExtension(Path.withoutDirectory(zipPath)); + var modId:String = Path.withoutExtension(Path.withoutDirectory(zipPath)); - var innerPath = path; + var innerPath:String = path; // Remove mod root from path if (innerPath.startsWith(modRoot)) { @@ -422,7 +422,8 @@ class SysZipFileSystem extends SysFileSystem zipParsers.set(zipPath, zipParser); } - function validateZipCache():Void { + function validateZipCache():Void + { for (zipPath => zipParser in zipParsers) { // Check that the associated ZIP is still valid. @@ -439,14 +440,16 @@ class SysZipFileSystem extends SysFileSystem * * @param zipPath */ - function purgeZipPath(zipPath:String):Void { + function purgeZipPath(zipPath:String):Void + { Polymod.debug('Purging invalid ZIP: ${zipPath}'); zipParsers.remove(zipPath); for (filePath => fileZipPath in filesLocations) { - if (fileZipPath == zipPath) { + if (fileZipPath == zipPath) + { Polymod.debug(' - ${filePath}'); filesLocations.remove(filePath); if (fileDirectories.contains(filePath)) fileDirectories.remove(filePath);