Deletes the song serialiser class, it has no use anymore Also fixes the BPM changes Note style is properly set when metadata toolbox is refreshed Add variation dialog now fills note style dropdown with note styles and sets it properly afterwards Also makes a few optimisations for the chart editor
1560 lines
43 KiB
Haxe
1560 lines
43 KiB
Haxe
package funkin.util;
|
|
|
|
import haxe.zip.Entry;
|
|
import lime.utils.Bytes;
|
|
import lime.ui.FileDialog;
|
|
import openfl.net.FileFilter;
|
|
import haxe.io.Path;
|
|
import openfl.net.FileReference;
|
|
import openfl.events.Event;
|
|
import openfl.events.IOErrorEvent;
|
|
import haxe.ui.containers.dialogs.Dialog.DialogButton;
|
|
import haxe.ui.containers.dialogs.Dialogs;
|
|
import haxe.ui.containers.dialogs.Dialogs.SelectedFileInfo;
|
|
import haxe.ui.containers.dialogs.Dialogs.FileDialogExtensionInfo;
|
|
|
|
using StringTools;
|
|
|
|
/**
|
|
* Utilities for reading and writing files on various platforms.
|
|
*/
|
|
@:nullSafety
|
|
class FileUtil
|
|
{
|
|
public static final FILE_FILTER_FNFC:FileFilter = new FileFilter("Friday Night Funkin' Chart (.fnfc)", "*.fnfc");
|
|
public static final FILE_FILTER_JSON:FileFilter = new FileFilter("JSON Data File (.json)", "*.json");
|
|
public static final FILE_FILTER_ZIP:FileFilter = new FileFilter("ZIP Archive (.zip)", "*.zip");
|
|
public static final FILE_FILTER_PNG:FileFilter = new FileFilter("PNG Image (.png)", "*.png");
|
|
public static final FILE_FILTER_FNFS:FileFilter = new FileFilter("Friday Night Funkin' Stage (.fnfs)", "*.fnfs");
|
|
|
|
public static final FILE_EXTENSION_INFO_FNFC:FileDialogExtensionInfo =
|
|
{
|
|
extension: 'fnfc',
|
|
label: 'Friday Night Funkin\' Chart',
|
|
};
|
|
public static final FILE_EXTENSION_INFO_ZIP:FileDialogExtensionInfo =
|
|
{
|
|
extension: 'zip',
|
|
label: 'ZIP Archive',
|
|
};
|
|
public static final FILE_EXTENSION_INFO_PNG:FileDialogExtensionInfo =
|
|
{
|
|
extension: 'png',
|
|
label: 'PNG Image',
|
|
};
|
|
|
|
public static final FILE_EXTENSION_INFO_FNFS:FileDialogExtensionInfo =
|
|
{
|
|
extension: 'fnfs',
|
|
label: 'Friday Night Funkin\' Stage',
|
|
};
|
|
|
|
/**
|
|
* Paths which should not be deleted or modified by scripts.
|
|
*/
|
|
public static var PROTECTED_PATHS(get, never):Array<String>;
|
|
|
|
public static function get_PROTECTED_PATHS():Array<String>
|
|
{
|
|
final protected:Array<String> = [
|
|
'',
|
|
'.',
|
|
'assets',
|
|
'assets/*',
|
|
'backups',
|
|
'backups/*',
|
|
'manifest',
|
|
'manifest/*',
|
|
'plugins',
|
|
'plugins/*',
|
|
'Funkin.exe',
|
|
'Funkin',
|
|
'icon.ico',
|
|
'libvlc.dll',
|
|
'libvlccore.dll',
|
|
'lime.ndll',
|
|
'scores.json'
|
|
];
|
|
|
|
#if sys
|
|
for (i in 0...protected.length)
|
|
{
|
|
protected[i] = sys.FileSystem.fullPath(Path.join([gameDirectory, protected[i]]));
|
|
}
|
|
#end
|
|
|
|
return protected;
|
|
}
|
|
|
|
/**
|
|
* Regex for invalid filesystem characters.
|
|
*/
|
|
public static final INVALID_CHARS:EReg = ~/[:*?"<>|\n\r\t]/g;
|
|
|
|
#if sys
|
|
private static var _gameDirectory:Null<String> = null;
|
|
public static var gameDirectory(get, never):String;
|
|
|
|
public static function get_gameDirectory():String
|
|
{
|
|
if (_gameDirectory != null)
|
|
{
|
|
return _gameDirectory;
|
|
}
|
|
|
|
return _gameDirectory = sys.FileSystem.fullPath(Path.directory(Sys.programPath()));
|
|
}
|
|
#end
|
|
|
|
/**
|
|
* Browses for a single file, then calls `onSelect(fileInfo)` when a file is selected.
|
|
* Powered by HaxeUI, so it works on all platforms.
|
|
* File contents will be binary, not String.
|
|
*
|
|
* @param typeFilter
|
|
* @param onSelect A callback that provides a `SelectedFileInfo` object when a file is selected.
|
|
* @param onCancel A callback that is called when the user closes the dialog without selecting a file.
|
|
*/
|
|
public static function browseForBinaryFile(dialogTitle:String, ?typeFilter:Array<FileDialogExtensionInfo>, onSelect:(SelectedFileInfo) -> Void,
|
|
?onCancel:() -> Void)
|
|
{
|
|
var onComplete = function(button, selectedFiles) {
|
|
if (button == DialogButton.OK && selectedFiles.length > 0)
|
|
{
|
|
onSelect(selectedFiles[0]);
|
|
}
|
|
else if (onCancel != null)
|
|
{
|
|
onCancel();
|
|
}
|
|
};
|
|
|
|
Dialogs.openFile(onComplete,
|
|
{
|
|
readContents: true,
|
|
readAsBinary: true, // Binary
|
|
multiple: false,
|
|
extensions: typeFilter ?? new Array<FileDialogExtensionInfo>(),
|
|
title: dialogTitle,
|
|
});
|
|
}
|
|
|
|
/**
|
|
* Browses for a single file, then calls `onSelect(fileInfo)` when a file is selected.
|
|
* Powered by HaxeUI, so it works on all platforms.
|
|
* File contents will be a String, not binary.
|
|
*
|
|
* @param typeFilter
|
|
* @param onSelect A callback that provides a `SelectedFileInfo` object when a file is selected.
|
|
* @param onCancel A callback that is called when the user closes the dialog without selecting a file.
|
|
*/
|
|
public static function browseForTextFile(dialogTitle:String, ?typeFilter:Array<FileDialogExtensionInfo>, onSelect:(SelectedFileInfo) -> Void,
|
|
?onCancel:() -> Void):Void
|
|
{
|
|
var onComplete = function(button, selectedFiles) {
|
|
if (button == DialogButton.OK && selectedFiles.length > 0)
|
|
{
|
|
onSelect(selectedFiles[0]);
|
|
}
|
|
else if (onCancel != null)
|
|
{
|
|
onCancel();
|
|
}
|
|
};
|
|
|
|
Dialogs.openFile(onComplete,
|
|
{
|
|
readContents: true,
|
|
readAsBinary: false, // Text
|
|
multiple: false,
|
|
extensions: typeFilter ?? new Array<FileDialogExtensionInfo>(),
|
|
title: dialogTitle,
|
|
});
|
|
}
|
|
|
|
/**
|
|
* Browses for a directory, then calls `onSelect(path)` when a path chosen.
|
|
* Note that on HTML5 this will immediately fail.
|
|
*
|
|
* @param typeFilter TODO What does this do?
|
|
* @return Whether the file dialog was opened successfully.
|
|
*/
|
|
public static function browseForDirectory(?typeFilter:Array<FileFilter>, onSelect:(String) -> Void, ?onCancel:() -> Void, ?defaultPath:String,
|
|
?dialogTitle:String):Bool
|
|
{
|
|
#if desktop
|
|
var filter:Null<String> = convertTypeFilter(typeFilter);
|
|
var fileDialog:FileDialog = new FileDialog();
|
|
fileDialog.onSelect.add(onSelect);
|
|
if (onCancel != null)
|
|
{
|
|
fileDialog.onCancel.add(onCancel);
|
|
}
|
|
|
|
fileDialog.browse(OPEN_DIRECTORY, filter, defaultPath, dialogTitle);
|
|
|
|
return true;
|
|
#else
|
|
trace('WARNING: browseForDirectory not implemented for this platform');
|
|
|
|
if (onCancel != null)
|
|
{
|
|
onCancel();
|
|
}
|
|
|
|
return false;
|
|
#end
|
|
}
|
|
|
|
/**
|
|
* Browses for multiple file, then calls `onSelect(paths)` when a path chosen.
|
|
* Note that on HTML5 this will immediately fail.
|
|
*
|
|
* @return Whether the file dialog was opened successfully.
|
|
*/
|
|
public static function browseForMultipleFiles(?typeFilter:Array<FileFilter>, onSelect:(Array<String>) -> Void, ?onCancel:() -> Void, ?defaultPath:String,
|
|
?dialogTitle:String):Bool
|
|
{
|
|
#if desktop
|
|
var filter:Null<String> = convertTypeFilter(typeFilter);
|
|
var fileDialog:FileDialog = new FileDialog();
|
|
fileDialog.onSelectMultiple.add(onSelect);
|
|
if (onCancel != null)
|
|
{
|
|
fileDialog.onCancel.add(onCancel);
|
|
}
|
|
|
|
fileDialog.browse(OPEN_MULTIPLE, filter, defaultPath, dialogTitle);
|
|
|
|
return true;
|
|
#else
|
|
trace('WARNING: browseForMultipleFiles not implemented for this platform');
|
|
|
|
if (onCancel != null)
|
|
{
|
|
onCancel();
|
|
}
|
|
|
|
return false;
|
|
#end
|
|
}
|
|
|
|
/**
|
|
* Browses for a file location to save to, then calls `onSave(path)` when a path chosen.
|
|
* Note that on HTML5 you can't do much with this, you should call `saveFile(resource:haxe.io.Bytes)` instead.
|
|
*
|
|
* @param typeFilter TODO What does this do?
|
|
* @return Whether the file dialog was opened successfully.
|
|
*/
|
|
public static function browseForSaveFile(?typeFilter:Array<FileFilter>, onSelect:(String) -> Void, ?onCancel:() -> Void, ?defaultPath:String,
|
|
?dialogTitle:String):Bool
|
|
{
|
|
#if desktop
|
|
var filter:Null<String> = convertTypeFilter(typeFilter);
|
|
var fileDialog:FileDialog = new FileDialog();
|
|
fileDialog.onSelect.add(onSelect);
|
|
if (onCancel != null)
|
|
{
|
|
fileDialog.onCancel.add(onCancel);
|
|
}
|
|
|
|
fileDialog.browse(SAVE, filter, defaultPath, dialogTitle);
|
|
|
|
return true;
|
|
#else
|
|
trace('WARNING: browseForSaveFile not implemented for this platform');
|
|
|
|
if (onCancel != null)
|
|
{
|
|
onCancel();
|
|
}
|
|
|
|
return false;
|
|
#end
|
|
}
|
|
|
|
/**
|
|
* Browses for a single file location, then writes the provided `haxe.io.Bytes` data and calls `onSave(path)` when done.
|
|
* Works great on desktop and HTML5.
|
|
*
|
|
* @return Whether the file dialog was opened successfully.
|
|
*/
|
|
public static function saveFile(data:Bytes, ?typeFilter:Array<FileFilter>, ?onSave:(String) -> Void, ?onCancel:() -> Void, ?defaultFileName:String,
|
|
?dialogTitle:String):Bool
|
|
{
|
|
#if desktop
|
|
var filter:Null<String> = convertTypeFilter(typeFilter);
|
|
var fileDialog:FileDialog = new FileDialog();
|
|
if (onSave != null)
|
|
{
|
|
fileDialog.onSave.add(onSave);
|
|
}
|
|
|
|
if (onCancel != null)
|
|
{
|
|
fileDialog.onCancel.add(onCancel);
|
|
}
|
|
|
|
fileDialog.save(data, filter, defaultFileName, dialogTitle);
|
|
|
|
return true;
|
|
#elseif html5
|
|
var filter:Null<String> = defaultFileName != null ? Path.extension(defaultFileName) : null;
|
|
var fileDialog:FileDialog = new FileDialog();
|
|
if (onSave != null)
|
|
{
|
|
fileDialog.onSave.add(onSave);
|
|
}
|
|
|
|
if (onCancel != null)
|
|
{
|
|
fileDialog.onCancel.add(onCancel);
|
|
}
|
|
|
|
fileDialog.save(data, filter, defaultFileName, dialogTitle);
|
|
|
|
return true;
|
|
#else
|
|
trace('WARNING: saveFile not implemented for this platform');
|
|
|
|
if (onCancel != null)
|
|
{
|
|
onCancel();
|
|
}
|
|
|
|
return false;
|
|
#end
|
|
}
|
|
|
|
/**
|
|
* Prompts the user to save multiple files.
|
|
* On desktop, this will prompt the user for a directory, then write all of the files to there.
|
|
* On HTML5, this will zip the files up and prompt the user to save that.
|
|
*
|
|
* @param typeFilter TODO What does this do?
|
|
* @return Whether the file dialog was opened successfully.
|
|
*/
|
|
public static function saveMultipleFiles(resources:Array<Entry>, ?onSaveAll:(Array<String>) -> Void, ?onCancel:() -> Void, ?defaultPath:String,
|
|
force:Bool = false):Bool
|
|
{
|
|
#if desktop
|
|
// Prompt the user for a directory, then write all of the files to there.
|
|
var onSelectDir:(String) -> Void = function(targetPath:String):Void {
|
|
var paths:Array<String> = new Array<String>();
|
|
for (resource in resources)
|
|
{
|
|
/*
|
|
var filePath:String = Path.join([targetPath, resource.fileName]);
|
|
try
|
|
{
|
|
if (resource.data == null)
|
|
{
|
|
trace('WARNING: File $filePath has no data or content. Skipping.');
|
|
continue;
|
|
}
|
|
else
|
|
{
|
|
writeBytesToPath(filePath, resource.data, force ? Force : Skip);
|
|
}
|
|
}
|
|
catch (e:Dynamic)
|
|
{
|
|
throw 'Failed to write file (probably already exists): $filePath';
|
|
}
|
|
*/
|
|
if (resource.data == null)
|
|
{
|
|
trace('WARNING: File ${resource.fileName} has no data or content. Skipping.');
|
|
continue;
|
|
}
|
|
|
|
var filePath:String = Path.join([targetPath, resource.fileName]);
|
|
|
|
paths.push(filePath);
|
|
}
|
|
|
|
if (onSaveAll != null)
|
|
{
|
|
onSaveAll(paths);
|
|
}
|
|
}
|
|
|
|
trace('Browsing for directory to save individual files to...');
|
|
|
|
#if mac
|
|
defaultPath = null;
|
|
#end
|
|
|
|
browseForDirectory(null, onSelectDir, onCancel, defaultPath, 'Choose directory to save all files to...');
|
|
|
|
return true;
|
|
#elseif html5
|
|
saveFilesAsZIP(resources, onSaveAll, onCancel, defaultPath, force);
|
|
|
|
return true;
|
|
#else
|
|
trace('WARNING: saveMultipleFiles not implemented for this platform');
|
|
|
|
if (onCancel != null)
|
|
{
|
|
onCancel();
|
|
}
|
|
|
|
return false;
|
|
#end
|
|
}
|
|
|
|
/**
|
|
* Takes an array of file entries and prompts the user to save them as a ZIP file.
|
|
*/
|
|
public static function saveFilesAsZIP(resources:Array<Entry>, ?onSave:(Array<String>) -> Void, ?onCancel:() -> Void, ?defaultPath:String,
|
|
force:Bool = false):Bool
|
|
{
|
|
// Create a ZIP file.
|
|
var zipBytes:Bytes = createZIPFromEntries(resources);
|
|
var onSave:(String) -> Void = function(path:String) {
|
|
trace('Saved ${resources.length} files to ZIP at "$path"');
|
|
|
|
if (onSave != null)
|
|
{
|
|
onSave([path]);
|
|
}
|
|
};
|
|
|
|
// Prompt the user to save the ZIP file.
|
|
saveFile(zipBytes, [FILE_FILTER_ZIP], onSave, onCancel, defaultPath, 'Save files as ZIP...');
|
|
return true;
|
|
}
|
|
|
|
/**
|
|
* Takes an array of file entries and prompts the user to save them as a FNFC file.
|
|
*/
|
|
public static function saveChartAsFNFC(resources:Array<Entry>, ?onSave:(Array<String>) -> Void, ?onCancel:() -> Void, ?defaultPath:String,
|
|
force:Bool = false):Bool
|
|
{
|
|
// Create a ZIP file.
|
|
var zipBytes:Bytes = createZIPFromEntries(resources);
|
|
var onSave:(String) -> Void = function(path:String) {
|
|
trace('Saved FNFC file to "$path"');
|
|
|
|
if (onSave != null)
|
|
{
|
|
onSave([path]);
|
|
}
|
|
};
|
|
// Prompt the user to save the ZIP file.
|
|
saveFile(zipBytes, [FILE_FILTER_FNFC], onSave, onCancel, defaultPath, 'Save chart as FNFC...');
|
|
return true;
|
|
}
|
|
|
|
/**
|
|
* Takes an array of file entries and forcibly writes a ZIP to the given path.
|
|
* Only works on native, because HTML5 doesn't allow you to write files to arbitrary paths.
|
|
* Use `saveFilesAsZIP` instead.
|
|
* @param force Whether to force overwrite an existing file.
|
|
*/
|
|
public static function saveFilesAsZIPToPath(resources:Array<Entry>, path:String, mode:FileWriteMode = Skip):Bool
|
|
{
|
|
#if sys
|
|
// Create a ZIP file.
|
|
var zipBytes:Bytes = createZIPFromEntries(resources);
|
|
// Write the ZIP.
|
|
writeBytesToPath(path, zipBytes, mode);
|
|
return true;
|
|
#else
|
|
return false;
|
|
#end
|
|
}
|
|
|
|
/**
|
|
* Read string file contents directly from a given path.
|
|
* Only works on native.
|
|
*
|
|
* @param path The path to the file.
|
|
* @return The file contents.
|
|
*/
|
|
public static function readStringFromPath(path:String):String
|
|
{
|
|
#if sys
|
|
return sys.io.File.getContent(path);
|
|
#else
|
|
throw 'Direct file reading by path is not supported on this platform.';
|
|
#end
|
|
}
|
|
|
|
/**
|
|
* Read bytes file contents directly from a given path.
|
|
* Only works on native.
|
|
*
|
|
* @param path The path to the file.
|
|
* @return The file contents.
|
|
*/
|
|
public static function readBytesFromPath(path:String):Bytes
|
|
{
|
|
#if sys
|
|
return sys.io.File.getBytes(path);
|
|
#else
|
|
throw 'Direct file reading by path is not supported on this platform.';
|
|
#end
|
|
}
|
|
|
|
/**
|
|
* Browse for a file to read and execute a callback once we have a file reference.
|
|
* Works great on HTML5 or desktop.
|
|
*
|
|
* @param callback The function to call when the file is loaded.
|
|
*/
|
|
public static function browseFileReference(callback:(FileReference) -> Void):Void
|
|
{
|
|
var file = new FileReference();
|
|
file.addEventListener(Event.SELECT, function(e) {
|
|
var selectedFileRef:FileReference = e.target;
|
|
trace('Selected file: ' + selectedFileRef.name);
|
|
|
|
selectedFileRef.addEventListener(Event.COMPLETE, function(e) {
|
|
var loadedFileRef:FileReference = e.target;
|
|
trace('Loaded file: ' + loadedFileRef.name);
|
|
|
|
callback(loadedFileRef);
|
|
});
|
|
|
|
selectedFileRef.load();
|
|
});
|
|
|
|
file.browse();
|
|
}
|
|
|
|
/**
|
|
* Prompts the user to save a file to their computer.
|
|
*/
|
|
public static function writeFileReference(path:String, data:String, callback:String->Void)
|
|
{
|
|
var file = new FileReference();
|
|
|
|
file.addEventListener(Event.COMPLETE, function(e:Event) {
|
|
trace('Successfully wrote file: "$path"');
|
|
callback("success");
|
|
});
|
|
|
|
file.addEventListener(Event.CANCEL, function(e:Event) {
|
|
trace('Cancelled writing file: "$path"');
|
|
callback("info");
|
|
});
|
|
|
|
file.addEventListener(IOErrorEvent.IO_ERROR, function(e:IOErrorEvent) {
|
|
trace('IO error writing file: "$path"');
|
|
callback("error");
|
|
});
|
|
|
|
file.save(data, path);
|
|
}
|
|
|
|
/**
|
|
* Read JSON file contents directly from a given path.
|
|
* Only works on native.
|
|
*
|
|
* @param path The path to the file.
|
|
* @return The JSON data.
|
|
*/
|
|
public static function readJSONFromPath(path:String):Dynamic
|
|
{
|
|
#if sys
|
|
return SerializerUtil.fromJSON(sys.io.File.getContent(path));
|
|
#else
|
|
throw 'Direct file reading by path is not supported on this platform.';
|
|
#end
|
|
}
|
|
|
|
/**
|
|
* Write string file contents directly to a given path.
|
|
* Only works on native.
|
|
*
|
|
* @param path The path to the file.
|
|
* @param data The string to write.
|
|
* @param mode Whether to Force, Skip, or Ask to overwrite an existing file.
|
|
*/
|
|
public static function writeStringToPath(path:String, data:String, mode:FileWriteMode = Skip):Void
|
|
{
|
|
#if sys
|
|
if (directoryExists(path))
|
|
{
|
|
throw 'Target path is a directory, not a file: "$path"';
|
|
}
|
|
|
|
createDirIfNotExists(Path.directory(path));
|
|
|
|
switch (mode)
|
|
{
|
|
case Force:
|
|
sys.io.File.saveContent(path, data);
|
|
case Skip:
|
|
if (!pathExists(path))
|
|
{
|
|
sys.io.File.saveContent(path, data);
|
|
}
|
|
case Ask:
|
|
if (pathExists(path))
|
|
{
|
|
// TODO: We don't have the technology to use native popups yet.
|
|
throw 'Entry at path already exists: $path';
|
|
}
|
|
else
|
|
{
|
|
sys.io.File.saveContent(path, data);
|
|
}
|
|
}
|
|
#else
|
|
throw 'Direct file writing by path is not supported on this platform.';
|
|
#end
|
|
}
|
|
|
|
/**
|
|
* Write byte file contents directly to a given path.
|
|
* Only works on native.
|
|
*
|
|
* @param path The path to the file.
|
|
* @param data The bytes to write.
|
|
* @param mode Whether to Force, Skip, or Ask to overwrite an existing file.
|
|
*/
|
|
public static function writeBytesToPath(path:String, data:Bytes, mode:FileWriteMode = Skip):Void
|
|
{
|
|
#if sys
|
|
if (directoryExists(path))
|
|
{
|
|
throw 'Target path is a directory, not a file: "$path"';
|
|
}
|
|
|
|
var shouldWrite:Bool = true;
|
|
switch (mode)
|
|
{
|
|
case Force:
|
|
shouldWrite = true;
|
|
case Skip:
|
|
if (!pathExists(path))
|
|
{
|
|
shouldWrite = true;
|
|
}
|
|
case Ask:
|
|
if (pathExists(path))
|
|
{
|
|
// TODO: We don't have the technology to use native popups yet.
|
|
throw 'Entry at path already exists: "$path"';
|
|
}
|
|
else
|
|
{
|
|
shouldWrite = true;
|
|
}
|
|
}
|
|
|
|
if (shouldWrite)
|
|
{
|
|
createDirIfNotExists(Path.directory(path));
|
|
sys.io.File.saveBytes(path, data);
|
|
}
|
|
#else
|
|
throw 'Direct file writing by path is not supported on this platform.';
|
|
#end
|
|
}
|
|
|
|
/**
|
|
* Write string file contents directly to the end of a file at the given path.
|
|
* Only works on native.
|
|
*
|
|
* @param path The path to the file.
|
|
* @param data The string to append.
|
|
*/
|
|
public static function appendStringToPath(path:String, data:String):Void
|
|
{
|
|
#if sys
|
|
if (!pathExists(path))
|
|
{
|
|
writeStringToPath(path, data, Force);
|
|
return;
|
|
}
|
|
else if (directoryExists(path))
|
|
{
|
|
throw 'Target path is a directory, not a file: "$path"';
|
|
}
|
|
|
|
var output:Null<sys.io.FileOutput> = null;
|
|
try
|
|
{
|
|
output = sys.io.File.append(path, false);
|
|
output.writeString(data);
|
|
output.close();
|
|
}
|
|
catch (e:Dynamic)
|
|
{
|
|
if (output != null)
|
|
{
|
|
output.close();
|
|
}
|
|
|
|
throw 'Failed to append to file: "$path"';
|
|
}
|
|
#else
|
|
throw 'Direct file writing by path is not supported on this platform.';
|
|
#end
|
|
}
|
|
|
|
/**
|
|
* Moves a file from one location to another.
|
|
* Only works on native.
|
|
*
|
|
* @param path The path to the file.
|
|
* @param destination The path to move the file to.
|
|
*/
|
|
public static function moveFile(path:String, destination:String):Void
|
|
{
|
|
#if sys
|
|
if (Path.extension(destination) != '')
|
|
{
|
|
destination = Path.directory(destination);
|
|
}
|
|
|
|
sys.FileSystem.rename(path, Path.join([destination, Path.withoutDirectory(path)]));
|
|
#else
|
|
throw 'File moving is not supported on this platform.';
|
|
#end
|
|
}
|
|
|
|
/**
|
|
* Delete a file at the given path.
|
|
* Only works on native.
|
|
*
|
|
* @param path The path to the file.
|
|
*/
|
|
public static function deleteFile(path:String):Void
|
|
{
|
|
#if sys
|
|
sys.FileSystem.deleteFile(path);
|
|
#else
|
|
throw 'File deletion is not supported on this platform.';
|
|
#end
|
|
}
|
|
|
|
/**
|
|
* Get a file's size in bytes. Max representable size is ~2.147 GB.
|
|
* Only works on native.
|
|
*
|
|
* @param path The path to the file.
|
|
* @return The size of the file in bytes.
|
|
*/
|
|
public static function getFileSize(path:String):Int
|
|
{
|
|
#if sys
|
|
return sys.FileSystem.stat(path).size;
|
|
#else
|
|
throw 'File size calculation is not supported on this platform.';
|
|
#end
|
|
}
|
|
|
|
/**
|
|
* Check if a path exists on the filesystem.
|
|
* Only works on native.
|
|
*
|
|
* @param path The path to the potential file or directory.
|
|
* @return Whether the path exists.
|
|
*/
|
|
public static function pathExists(path:String):Bool
|
|
{
|
|
#if sys
|
|
return sys.FileSystem.exists(path);
|
|
#else
|
|
return false;
|
|
#end
|
|
}
|
|
|
|
/**
|
|
* Check if a path is a file on the filesystem.
|
|
* Only works on native.
|
|
*
|
|
* @param path The path to the potential file.
|
|
* @return Whether the path exists and is a file.
|
|
*/
|
|
public static function fileExists(path:String):Bool
|
|
{
|
|
#if sys
|
|
return pathExists(path) && !directoryExists(path);
|
|
#else
|
|
throw 'Filesystem check is not supported on this platform.';
|
|
#end
|
|
}
|
|
|
|
/**
|
|
* Check if a path is a directory on the filesystem.
|
|
* Only works on native.
|
|
*
|
|
* @param path The path to the potential directory.
|
|
* @return Whether the path exists and is a directory.
|
|
*/
|
|
public static function directoryExists(path:String):Bool
|
|
{
|
|
#if sys
|
|
try
|
|
{
|
|
return sys.FileSystem.isDirectory(path);
|
|
}
|
|
catch (e:Dynamic)
|
|
{
|
|
return false;
|
|
}
|
|
#else
|
|
throw 'Filesystem check is not supported on this platform.';
|
|
#end
|
|
}
|
|
|
|
/**
|
|
* Create a directory if it doesn't already exist.
|
|
* Only works on native.
|
|
*
|
|
* @param dir The path to the directory.
|
|
*/
|
|
public static function createDirIfNotExists(dir:String):Void
|
|
{
|
|
if (!directoryExists(dir))
|
|
{
|
|
#if sys
|
|
sys.FileSystem.createDirectory(dir);
|
|
#else
|
|
throw 'Directory creation is not supported on this platform.';
|
|
#end
|
|
}
|
|
}
|
|
|
|
/**
|
|
* List all entries in a directory.
|
|
* Only works on native.
|
|
*
|
|
* @param path The path to the directory.
|
|
* @return An array of entries in the directory.
|
|
*/
|
|
public static function readDir(path:String):Array<String>
|
|
{
|
|
#if sys
|
|
return sys.FileSystem.readDirectory(path);
|
|
#else
|
|
throw 'Directory reading is not supported on this platform.';
|
|
#end
|
|
}
|
|
|
|
/**
|
|
* Move a directory from one location to another, optionally ignoring some paths.
|
|
* Only works on native.
|
|
*
|
|
* @param path The path to the directory.
|
|
* @param destination The path to move the directory to.
|
|
* @param ignore A list of paths to ignore.
|
|
* @param strict Fails if the destination directory is not empty.
|
|
*/
|
|
public static function moveDir(path:String, destination:String, ?ignore:Array<String>, strict:Bool = true):Void
|
|
{
|
|
#if sys
|
|
if (!directoryExists(path))
|
|
{
|
|
throw 'Path is not a directory: "$path"';
|
|
}
|
|
|
|
createDirIfNotExists(destination);
|
|
if (strict)
|
|
{
|
|
// Ensure the destination is empty if strict mode is enabled.
|
|
var entries:Array<String> = readDir(destination);
|
|
if (entries.length > 0)
|
|
{
|
|
throw 'Destination directory "$destination" is not empty.';
|
|
}
|
|
}
|
|
|
|
var stack:Array<String> = [path];
|
|
while (stack.length > 0)
|
|
{
|
|
var currentPath:Null<String> = stack.pop();
|
|
if (currentPath == null) continue;
|
|
|
|
var entries:Array<String> = readDir(currentPath);
|
|
for (entry in entries)
|
|
{
|
|
var entryPath:String = Path.join([currentPath, entry]);
|
|
if (ignore != null && ignore.contains(entryPath)) continue;
|
|
if (directoryExists(entryPath))
|
|
{
|
|
stack.push(entryPath);
|
|
}
|
|
else
|
|
{
|
|
moveFile(entryPath, Path.join([destination, entry]));
|
|
}
|
|
}
|
|
}
|
|
|
|
if (readDir(path)?.length == 0)
|
|
{
|
|
deleteDir(path);
|
|
}
|
|
#else
|
|
throw 'Directory moving is not supported on this platform.';
|
|
#end
|
|
}
|
|
|
|
/**
|
|
* Delete a directory, optionally including its contents, and optionally ignoring some paths.
|
|
* Only works on native.
|
|
*
|
|
* @param path The path to the directory.
|
|
* @param recursive Whether to delete all contents of the directory.
|
|
* @param ignore A list of paths to ignore.
|
|
*/
|
|
public static function deleteDir(path:String, recursive:Bool = false, ?ignore:Array<String>):Void
|
|
{
|
|
#if sys
|
|
if (!directoryExists(path))
|
|
{
|
|
throw 'Path is not a valid directory: "$path"';
|
|
}
|
|
|
|
if (recursive)
|
|
{
|
|
var stack:Array<String> = [path];
|
|
while (stack.length > 0)
|
|
{
|
|
var currentPath:Null<String> = stack.pop();
|
|
if (currentPath == null) continue;
|
|
|
|
var entries:Array<String> = readDir(currentPath);
|
|
for (entry in entries)
|
|
{
|
|
var entryPath:String = Path.join([currentPath, entry]);
|
|
if (ignore != null && ignore.contains(entryPath)) continue;
|
|
if (directoryExists(entryPath))
|
|
{
|
|
stack.push(entryPath);
|
|
}
|
|
else
|
|
{
|
|
deleteFile(entryPath);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
else
|
|
{
|
|
sys.FileSystem.deleteDirectory(path);
|
|
}
|
|
#else
|
|
throw 'Directory deletion is not supported on this platform.';
|
|
#end
|
|
}
|
|
|
|
/**
|
|
* Get a directory's total size in bytes. Max representable size is ~2.147 GB.
|
|
* Only works on native.
|
|
*
|
|
* @param path The path to the directory.
|
|
* @return The total size of the directory in bytes.
|
|
*/
|
|
public static function getDirSize(path:String):Int
|
|
{
|
|
#if sys
|
|
if (!directoryExists(path))
|
|
{
|
|
throw 'Path is not a valid directory path: $path';
|
|
}
|
|
|
|
var stack:Array<String> = [path];
|
|
var total:Int = 0;
|
|
while (stack.length > 0)
|
|
{
|
|
var currentPath:Null<String> = stack.pop();
|
|
if (currentPath == null) continue;
|
|
|
|
for (entry in readDir(currentPath))
|
|
{
|
|
var entryPath:String = Path.join([currentPath, entry]);
|
|
if (directoryExists(entryPath))
|
|
{
|
|
stack.push(entryPath);
|
|
}
|
|
else
|
|
{
|
|
total += getFileSize(entryPath);
|
|
}
|
|
}
|
|
}
|
|
|
|
return total;
|
|
#else
|
|
throw 'Directory size calculation not supported on this platform.';
|
|
#end
|
|
}
|
|
|
|
static var tempDir:Null<String> = null;
|
|
static final TEMP_ENV_VARS:Array<String> = ['TEMP', 'TMPDIR', 'TEMPDIR', 'TMP'];
|
|
|
|
/**
|
|
* Get the path to a temporary directory we can use for writing files.
|
|
* Only works on native.
|
|
*
|
|
* @return The path to the temporary directory.
|
|
*/
|
|
public static function getTempDir():Null<String>
|
|
{
|
|
if (tempDir != null) return tempDir;
|
|
#if sys
|
|
#if windows
|
|
var path:Null<String> = null;
|
|
for (envName in TEMP_ENV_VARS)
|
|
{
|
|
path = Sys.getEnv(envName);
|
|
if (path == '') path = null;
|
|
if (path != null) break;
|
|
}
|
|
tempDir = Path.join([path ?? '', 'funkin/']);
|
|
return tempDir;
|
|
#elseif android
|
|
tempDir = Path.addTrailingSlash(extension.androidtools.content.Context.getCacheDir());
|
|
return tempDir;
|
|
#else
|
|
tempDir = '/tmp/funkin/';
|
|
return tempDir;
|
|
#end
|
|
#else
|
|
return null;
|
|
#end
|
|
}
|
|
|
|
/**
|
|
* Rename a file or directory.
|
|
* Only works on native.
|
|
*
|
|
* @param path The path to the file or directory.
|
|
* @param newName The new name of the file or directory.
|
|
* @param keepExtension Whether to keep the extension the same, if applicable.
|
|
*/
|
|
public static function rename(path:String, newName:String, keepExtension:Bool = true):Void
|
|
{
|
|
#if sys
|
|
if (!pathExists(path))
|
|
{
|
|
throw 'Path does not exist: "$path"';
|
|
}
|
|
|
|
final isDirectory:Bool = directoryExists(path);
|
|
newName = Path.withoutDirectory(newName);
|
|
if (isDirectory)
|
|
{
|
|
newName = Path.withoutExtension(newName);
|
|
}
|
|
else if (keepExtension)
|
|
{
|
|
newName = Path.withExtension(newName, Path.extension(path));
|
|
}
|
|
|
|
newName = Path.join([Path.directory(path), newName]);
|
|
if (newName == path)
|
|
{
|
|
return;
|
|
}
|
|
|
|
if (pathExists(newName))
|
|
{
|
|
// Prevent overwriting something.
|
|
throw 'Destination path already exists: "$newName"';
|
|
}
|
|
|
|
sys.FileSystem.rename(path, newName);
|
|
#else
|
|
throw 'File renaming by path is not supported on this platform.';
|
|
#end
|
|
}
|
|
|
|
/**
|
|
* Create a Bytes object containing a ZIP file, containing the provided entries.
|
|
*
|
|
* @param entries The entries to add to the ZIP file.
|
|
* @return The ZIP file as a Bytes object.
|
|
*/
|
|
public static function createZIPFromEntries(entries:Array<Entry>):Bytes
|
|
{
|
|
var o:haxe.io.BytesOutput = new haxe.io.BytesOutput();
|
|
var zipWriter:haxe.zip.Writer = new haxe.zip.Writer(o);
|
|
zipWriter.write(entries.list());
|
|
return o.getBytes();
|
|
}
|
|
|
|
public static function readZIPFromBytes(input:Bytes):Array<Entry>
|
|
{
|
|
var bytesInput = new haxe.io.BytesInput(input);
|
|
var zippedEntries = haxe.zip.Reader.readZip(bytesInput);
|
|
var results:Array<Entry> = new Array<Entry>();
|
|
for (entry in zippedEntries)
|
|
{
|
|
if (entry.compressed)
|
|
{
|
|
entry.data = haxe.zip.Reader.unzip(entry);
|
|
}
|
|
|
|
results.push(entry);
|
|
}
|
|
|
|
return results;
|
|
}
|
|
|
|
public static function mapZIPEntriesByName(input:Array<Entry>):Map<String, Entry>
|
|
{
|
|
var results:Map<String, Entry> = new Map<String, Entry>();
|
|
for (entry in input)
|
|
{
|
|
results.set(entry.fileName, entry);
|
|
}
|
|
|
|
return results;
|
|
}
|
|
|
|
/**
|
|
* Create a ZIP file entry from a file name and its string contents.
|
|
*
|
|
* @param name The name of the file. You can use slashes to create subdirectories.
|
|
* @param content The string contents of the file.
|
|
* @return The resulting entry.
|
|
*/
|
|
public static function makeZIPEntry(name:String, content:String):Entry
|
|
{
|
|
var data:Bytes = haxe.io.Bytes.ofString(content, UTF8);
|
|
return makeZIPEntryFromBytes(name, data);
|
|
}
|
|
|
|
/**
|
|
* Create a ZIP file entry from a file name and its string contents.
|
|
*
|
|
* @param name The name of the file. You can use slashes to create subdirectories.
|
|
* @param data The byte data of the file.
|
|
* @return The resulting entry.
|
|
*/
|
|
public static function makeZIPEntryFromBytes(name:String, data:haxe.io.Bytes):Entry
|
|
{
|
|
return {
|
|
fileName: name,
|
|
fileSize: data.length,
|
|
data: data,
|
|
dataSize: data.length,
|
|
compressed: false,
|
|
fileTime: Date.now(),
|
|
crc32: null,
|
|
extraFields: null,
|
|
};
|
|
}
|
|
|
|
/**
|
|
* Runs platform-specific code to open a path in the file explorer.
|
|
*
|
|
* @param pathFolder The path of the folder to open.
|
|
* @param createIfNotExists If `true`, creates the folder if missing; otherwise, throws an error.
|
|
*/
|
|
public static function openFolder(pathFolder:String, createIfNotExists:Bool = true):Void
|
|
{
|
|
#if sys
|
|
pathFolder = pathFolder.trim();
|
|
if (createIfNotExists)
|
|
{
|
|
createDirIfNotExists(pathFolder);
|
|
}
|
|
else if (!directoryExists(pathFolder))
|
|
{
|
|
throw 'Path is not a directory: "$pathFolder"';
|
|
}
|
|
|
|
#if windows
|
|
Sys.command('explorer', [pathFolder.replace('/', '\\')]);
|
|
#elseif mac
|
|
// mac could be fuckie with where the log folder is relative to the game file...
|
|
// if this comment is still here... it means it has NOT been verified on mac yet!
|
|
//
|
|
// FileUtil.hx note: this was originally used to open the logs specifically!
|
|
// thats why the above comment is there!
|
|
Sys.command('open', [pathFolder]);
|
|
#elseif linux
|
|
// TODO: implement linux
|
|
// some shit with xdg-open :thinking: emoji...
|
|
#end
|
|
#else
|
|
throw 'External folder open is not supported on this platform.';
|
|
#end
|
|
}
|
|
|
|
/**
|
|
* Runs platform-specific code to open a file explorer and select a specific file.
|
|
*
|
|
* @param path The path of the file to select.
|
|
*/
|
|
public static function openSelectFile(path:String):Void
|
|
{
|
|
#if sys
|
|
path = path.trim();
|
|
if (!pathExists(path))
|
|
{
|
|
throw 'Path does not exist: "$path"';
|
|
}
|
|
|
|
#if windows
|
|
Sys.command('explorer', ['/select,', path.replace('/', '\\')]);
|
|
#elseif mac
|
|
Sys.command('open', ['-R', path]);
|
|
#elseif linux
|
|
// TODO: unsure of the linux equivalent to opening a folder and then "selecting" a file.
|
|
Sys.command('open', [path]);
|
|
#end
|
|
#else
|
|
throw 'External file selection is not supported on this platform.';
|
|
#end
|
|
}
|
|
|
|
private static function convertTypeFilter(?typeFilter:Array<FileFilter>):Null<String>
|
|
{
|
|
var filter:Null<String> = null;
|
|
if (typeFilter != null)
|
|
{
|
|
var filters:Array<String> = new Array<String>();
|
|
for (type in typeFilter)
|
|
{
|
|
filters.push(type.extension.replace('*.', '').replace(';', ','));
|
|
}
|
|
|
|
filter = filters.join(';');
|
|
}
|
|
|
|
return filter;
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Utilities for reading and writing files on various platforms.
|
|
* Wrapper for `FileUtil` that sanitizes paths for script safety.
|
|
*/
|
|
@:nullSafety
|
|
class FileUtilSandboxed
|
|
{
|
|
/**
|
|
* Prevent paths from exiting the root.
|
|
*
|
|
* @param path The path to sanitize.
|
|
* @return The sanitized path.
|
|
*/
|
|
public static function sanitizePath(path:String):String
|
|
{
|
|
path = (path ?? '').trim();
|
|
if (path == '')
|
|
{
|
|
#if sys
|
|
return FileUtil.gameDirectory;
|
|
#else
|
|
return '';
|
|
#end
|
|
}
|
|
|
|
if (path.contains(':'))
|
|
{
|
|
path = path.substring(path.lastIndexOf(':') + 1);
|
|
}
|
|
|
|
path = path.replace('\\', '/');
|
|
while (path.contains('//'))
|
|
{
|
|
path = path.replace('//', '/');
|
|
}
|
|
|
|
final parts:Array<String> = FileUtil.INVALID_CHARS.replace(path, '').split('/');
|
|
final sanitized:Array<String> = new Array<String>();
|
|
for (part in parts)
|
|
{
|
|
switch (part)
|
|
{
|
|
case '.' | '':
|
|
continue;
|
|
case '..':
|
|
sanitized.pop();
|
|
default:
|
|
sanitized.push(part.trim());
|
|
}
|
|
}
|
|
|
|
if (sanitized.length == 0)
|
|
{
|
|
#if sys
|
|
return FileUtil.gameDirectory;
|
|
#else
|
|
return '';
|
|
#end
|
|
}
|
|
|
|
#if sys
|
|
// TODO: figure out how to get "real" path of symlinked paths
|
|
final realPath:String = sys.FileSystem.fullPath(Path.join([FileUtil.gameDirectory, sanitized.join('/')]));
|
|
if (!realPath.startsWith(FileUtil.gameDirectory))
|
|
{
|
|
return FileUtil.gameDirectory;
|
|
}
|
|
|
|
return realPath;
|
|
#else
|
|
return sanitized.join('/');
|
|
#end
|
|
}
|
|
|
|
/**
|
|
* Check against protected paths.
|
|
* @param path The path to check.
|
|
* @return Whether the path is protected.
|
|
*/
|
|
public static function isProtected(path:String, sanitizeFirst:Bool = true):Bool
|
|
{
|
|
if (sanitizeFirst) path = sanitizePath(path);
|
|
@:privateAccess for (protected in FileUtil.PROTECTED_PATHS)
|
|
{
|
|
if (path == protected || (protected.contains('*') && path.startsWith(protected.substring(0, protected.indexOf('*')))))
|
|
{
|
|
return true;
|
|
}
|
|
}
|
|
|
|
return false;
|
|
}
|
|
|
|
public static final FILE_FILTER_FNFC:FileFilter = FileUtil.FILE_FILTER_FNFC;
|
|
public static final FILE_FILTER_JSON:FileFilter = FileUtil.FILE_FILTER_JSON;
|
|
public static final FILE_FILTER_ZIP:FileFilter = FileUtil.FILE_FILTER_ZIP;
|
|
public static final FILE_FILTER_PNG:FileFilter = FileUtil.FILE_FILTER_PNG;
|
|
|
|
public static final FILE_EXTENSION_INFO_FNFC:FileDialogExtensionInfo = FileUtil.FILE_EXTENSION_INFO_FNFC;
|
|
public static final FILE_EXTENSION_INFO_ZIP:FileDialogExtensionInfo = FileUtil.FILE_EXTENSION_INFO_ZIP;
|
|
public static final FILE_EXTENSION_INFO_PNG:FileDialogExtensionInfo = FileUtil.FILE_EXTENSION_INFO_PNG;
|
|
|
|
public static function browseForBinaryFile(dialogTitle:String, ?typeFilter:Array<FileDialogExtensionInfo>, onSelect:(SelectedFileInfo) -> Void,
|
|
?onCancel:() -> Void)
|
|
{
|
|
FileUtil.browseForBinaryFile(dialogTitle, typeFilter, onSelect, onCancel);
|
|
}
|
|
|
|
public static function browseForTextFile(dialogTitle:String, ?typeFilter:Array<FileDialogExtensionInfo>, onSelect:(SelectedFileInfo) -> Void,
|
|
?onCancel:() -> Void):Void
|
|
{
|
|
FileUtil.browseForTextFile(dialogTitle, typeFilter, onSelect, onCancel);
|
|
}
|
|
|
|
public static function browseForDirectory(?typeFilter:Array<FileFilter>, onSelect:(String) -> Void, ?onCancel:() -> Void, ?defaultPath:String,
|
|
?dialogTitle:String):Bool
|
|
{
|
|
return FileUtil.browseForDirectory(typeFilter, onSelect, onCancel, defaultPath, dialogTitle);
|
|
}
|
|
|
|
public static function browseForMultipleFiles(?typeFilter:Array<FileFilter>, onSelect:(Array<String>) -> Void, ?onCancel:() -> Void, ?defaultPath:String,
|
|
?dialogTitle:String):Bool
|
|
{
|
|
return FileUtil.browseForMultipleFiles(typeFilter, onSelect, onCancel, defaultPath, dialogTitle);
|
|
}
|
|
|
|
public static function browseForSaveFile(?typeFilter:Array<FileFilter>, onSelect:(String) -> Void, ?onCancel:() -> Void, ?defaultPath:String,
|
|
?dialogTitle:String):Bool
|
|
{
|
|
return FileUtil.browseForSaveFile(typeFilter, onSelect, onCancel, defaultPath, dialogTitle);
|
|
}
|
|
|
|
public static function saveFile(data:Bytes, ?typeFilter:Array<FileFilter>, ?onSave:(String) -> Void, ?onCancel:() -> Void, ?defaultFileName:String,
|
|
?dialogTitle:String):Bool
|
|
{
|
|
return FileUtil.saveFile(data, typeFilter, onSave, onCancel, defaultFileName, dialogTitle);
|
|
}
|
|
|
|
public static function saveMultipleFiles(resources:Array<Entry>, ?onSaveAll:(Array<String>) -> Void, ?onCancel:() -> Void, ?defaultPath:String,
|
|
force:Bool = false):Bool
|
|
{
|
|
return FileUtil.saveMultipleFiles(resources, onSaveAll, onCancel, defaultPath, force);
|
|
}
|
|
|
|
public static function saveFilesAsZIP(resources:Array<Entry>, ?onSave:(Array<String>) -> Void, ?onCancel:() -> Void, ?defaultPath:String,
|
|
force:Bool = false):Bool
|
|
{
|
|
return FileUtil.saveFilesAsZIP(resources, onSave, onCancel, defaultPath, force);
|
|
}
|
|
|
|
public static function saveChartAsFNFC(resources:Array<Entry>, ?onSave:(Array<String>) -> Void, ?onCancel:() -> Void, ?defaultPath:String,
|
|
force:Bool = false):Bool
|
|
{
|
|
return FileUtil.saveChartAsFNFC(resources, onSave, onCancel, defaultPath, force);
|
|
}
|
|
|
|
public static function saveFilesAsZIPToPath(resources:Array<Entry>, path:String, mode:FileWriteMode = Skip):Bool
|
|
{
|
|
if (isProtected(path = sanitizePath(path), false)) throw 'Cannot write to protected path: $path';
|
|
return FileUtil.saveFilesAsZIPToPath(resources, path, mode);
|
|
}
|
|
|
|
public static function readStringFromPath(path:String):String
|
|
{
|
|
return FileUtil.readStringFromPath(sanitizePath(path));
|
|
}
|
|
|
|
public static function readBytesFromPath(path:String):Bytes
|
|
{
|
|
return FileUtil.readBytesFromPath(sanitizePath(path));
|
|
}
|
|
|
|
public static function browseFileReference(callback:(FileReference) -> Void):Void
|
|
{
|
|
FileUtil.browseFileReference(callback);
|
|
}
|
|
|
|
public static function writeFileReference(path:String, data:String):Void
|
|
{
|
|
FileUtil.writeFileReference(path, data);
|
|
}
|
|
|
|
public static function readJSONFromPath(path:String):Dynamic
|
|
{
|
|
return FileUtil.readJSONFromPath(sanitizePath(path));
|
|
}
|
|
|
|
public static function writeStringToPath(path:String, data:String, mode:FileWriteMode = Skip):Void
|
|
{
|
|
if (isProtected(path = sanitizePath(path), false)) throw 'Cannot write to protected path: $path';
|
|
FileUtil.writeStringToPath(path, data, mode);
|
|
}
|
|
|
|
public static function writeBytesToPath(path:String, data:Bytes, mode:FileWriteMode = Skip):Void
|
|
{
|
|
if (isProtected(path = sanitizePath(path), false)) throw 'Cannot write to protected path: $path';
|
|
FileUtil.writeBytesToPath(path, data, mode);
|
|
}
|
|
|
|
public static function appendStringToPath(path:String, data:String):Void
|
|
{
|
|
if (isProtected(path = sanitizePath(path), false)) throw 'Cannot write to protected path: $path';
|
|
FileUtil.appendStringToPath(path, data);
|
|
}
|
|
|
|
public static function moveFile(path:String, destination:String):Void
|
|
{
|
|
if (isProtected(path = sanitizePath(path), false)) throw 'Cannot move protected path: $path';
|
|
if (isProtected(destination = sanitizePath(destination), false)) throw 'Cannot move to protected path: $destination';
|
|
FileUtil.moveFile(path, destination);
|
|
}
|
|
|
|
public static function deleteFile(path:String):Void
|
|
{
|
|
if (isProtected(path = sanitizePath(path), false)) throw 'Cannot delete protected path: $path';
|
|
FileUtil.deleteFile(path);
|
|
}
|
|
|
|
public static function getFileSize(path:String):Int
|
|
{
|
|
return FileUtil.getFileSize(sanitizePath(path));
|
|
}
|
|
|
|
public static function pathExists(path:String):Bool
|
|
{
|
|
return FileUtil.pathExists(sanitizePath(path));
|
|
}
|
|
|
|
public static function fileExists(path:String):Bool
|
|
{
|
|
return FileUtil.fileExists(sanitizePath(path));
|
|
}
|
|
|
|
public static function directoryExists(path:String):Bool
|
|
{
|
|
return FileUtil.directoryExists(sanitizePath(path));
|
|
}
|
|
|
|
public static function createDirIfNotExists(dir:String):Void
|
|
{
|
|
FileUtil.createDirIfNotExists(sanitizePath(dir));
|
|
}
|
|
|
|
public static function readDir(path:String):Array<String>
|
|
{
|
|
return FileUtil.readDir(sanitizePath(path));
|
|
}
|
|
|
|
public static function moveDir(path:String, destination:String, ?ignore:Array<String>, strict:Bool = true):Void
|
|
{
|
|
if (isProtected(path = sanitizePath(path), false)) throw 'Cannot move protected path: "$path"';
|
|
if (isProtected(destination = sanitizePath(destination), false)) throw 'Cannot move to protected path: "$destination"';
|
|
FileUtil.moveDir(path, destination, ignore, strict);
|
|
}
|
|
|
|
public static function deleteDir(path:String, recursive:Bool = false, ?ignore:Array<String>):Void
|
|
{
|
|
if (isProtected(path = sanitizePath(path), false)) throw 'Cannot delete protected path: "$path"';
|
|
FileUtil.deleteDir(path, recursive, ignore);
|
|
}
|
|
|
|
public static function getDirSize(path:String):Int
|
|
{
|
|
return FileUtil.getDirSize(sanitizePath(path));
|
|
}
|
|
|
|
public static function getTempDir():Null<String>
|
|
{
|
|
return FileUtil.getTempDir();
|
|
}
|
|
|
|
public static function rename(path:String, newName:String, keepExtension:Bool = true):Void
|
|
{
|
|
if (isProtected(path = sanitizePath(path), false)) throw 'Cannot rename protected path: "$path"';
|
|
FileUtil.rename(path, sanitizePath(newName), keepExtension);
|
|
}
|
|
|
|
public static function createZIPFromEntries(entries:Array<Entry>):Bytes
|
|
{
|
|
return FileUtil.createZIPFromEntries(entries);
|
|
}
|
|
|
|
public static function readZIPFromBytes(input:Bytes):Array<Entry>
|
|
{
|
|
return FileUtil.readZIPFromBytes(input);
|
|
}
|
|
|
|
public static function mapZIPEntriesByName(input:Array<Entry>):Map<String, Entry>
|
|
{
|
|
return FileUtil.mapZIPEntriesByName(input);
|
|
}
|
|
|
|
public static function makeZIPEntry(name:String, content:String):Entry
|
|
{
|
|
return FileUtil.makeZIPEntry(name, content);
|
|
}
|
|
|
|
public static function makeZIPEntryFromBytes(name:String, data:haxe.io.Bytes):Entry
|
|
{
|
|
return FileUtil.makeZIPEntryFromBytes(name, data);
|
|
}
|
|
|
|
public static function openFolder(pathFolder:String, createIfNotExists:Bool = true):Void
|
|
{
|
|
FileUtil.openFolder(sanitizePath(pathFolder), createIfNotExists);
|
|
}
|
|
|
|
public static function openSelectFile(path:String):Void
|
|
{
|
|
FileUtil.openSelectFile(sanitizePath(path));
|
|
}
|
|
}
|
|
|
|
enum FileWriteMode
|
|
{
|
|
/**
|
|
* Forcibly overwrite the file if it already exists.
|
|
*/
|
|
Force;
|
|
|
|
/**
|
|
* Ask the user if they want to overwrite the file if it already exists.
|
|
*/
|
|
Ask;
|
|
|
|
/**
|
|
* Skip the file if it already exists.
|
|
*/
|
|
Skip;
|
|
}
|