diff --git a/std/cs/_std/sys/fs/File.hx b/std/cs/_std/sys/fs/File.hx index 26914c0c285..9c880c138d6 100644 --- a/std/cs/_std/sys/fs/File.hx +++ b/std/cs/_std/sys/fs/File.hx @@ -1,96 +1,106 @@ package sys.fs; +import cs.system.io.SeekOrigin; import cs.system.io.FileStream; +import cs.system.io.FileAccess; +import cs.system.io.FileMode; +import haxe.Int64; +import cs.system.io.File as CsFile; +import haxe.time.SystemTime; import haxe.io.Bytes; import haxe.Result; import sys.fs.Metadata; import sys.Error; import sys.fs.Path; +import sys.fs.SeekPos; -import cs.system.io.FileMode; -import cs.system.io.File as NativeFile; - -enum SeekPos { - SeekBegin(offset:haxe.Int64); - SeekCurrent(offset:haxe.Int64); - SeekEnd(offset:haxe.Int64); -} - +@:coreApi class File { - /** - Opens the file in read-only mode. - **/ - static function open(p:Path):Result { - try { - cs.system.io.File.Open(p.toString(), FileMode.Open); - - } - return Err(Unsupported); + public static function open(p:Path, ?options:OpenOptions):File { + options ??= {read: true}; + var mode:FileMode = if (options.append) Append else if (options.truncate) Truncate else if (options.create_new) CreateNew else if (options.create) + Create else Open; + var access:FileAccess = if (options.read && options.write) ReadWrite else if (options.write) Write else if (options.read) Read else + throw "can never read nor write to file"; + final stream = CsFile.Open(p.toString(), mode, access); + return new File(p.toString(), stream); } - /** - Opens the file in write-only mode, creating it if needed. - **/ - static function create(p:Path):Result { - return Err(Unsupported); + public static function create(p:Path):File { + return open(p, {write: true}); } - /** - Opens the file in write-only mode, always creating a new one. - Returns an error if the file already exists. - **/ - static function createNew(p:Path):Result { - return Err(Unsupported); + public static function createNew(p:Path):File { + return open(p, {write: true, create_new: true}); } - static function readAll(p:Path):Result { - return Err(Unsupported); + public static function readAll(p:Path):Bytes { + return Bytes.ofData(CsFile.ReadAllBytes(p.toString())); } - static function writeAll(p:Path, b:Bytes):Result { - return Err(Unsupported); + public static function writeAll(p:Path, b:Bytes):Void { + CsFile.WriteAllBytes(p.toString(), b.getData()); } - static function appendAll(p:Path, b:Bytes):Result { - return Err(Unsupported); + public static function appendAll(p:Path, b:Bytes):Void { + final file = open(p, {append: true, write: true}); + file.write(b, 0, b.length); + file.close(); } + final path:String; final stream:FileStream; - function new(file:FileStream) { - this.stream = file; + function new(path:String, stream:FileStream) { + this.path = path; + this.stream = stream; } - function syncAll():Result { + public function syncAll():Void { stream.Flush(); - return Ok((null:Void)); } - function syncData():Result { - return syncAll(); + public function syncData():Void { + stream.Flush(); } - function metadata():Result { - return Err(Unsupported); + public function metadata():Metadata { + return new Metadata(path, true); } - function setPermissions(perm:Permissions):Result { - return Err(Unsupported); + public function setPermissions(perm:Permissions):Void { + throw new haxe.exceptions.NotImplementedException(); } - // TODO: technically these should all use a UInt64 type - - function read(bytes:haxe.io.Bytes, bufferOffset:Int, bufferLength:Int):Result { - return Err(Unsupported); + public function read(bytes:haxe.io.Bytes, bufferOffset:Int, bufferLength:Int):Int { + return stream.Read(bytes.getData(), bufferOffset, bufferLength); } - function write(bytes:haxe.io.Bytes, bufferOffset:Int, bufferLength:Int):Result { - return Err(Unsupported); + public function write(bytes:haxe.io.Bytes, bufferOffset:Int, bufferLength:Int):Int { + var prevPos = stream.Position; + stream.Write(bytes.getData(), bufferOffset, bufferLength); + return (cast stream.Position - prevPos:Int); } - function seek(pos:SeekPos):Result { - return Err(Unsupported); + public function seek(pos:SeekPos):Int64 { + var origin:SeekOrigin; + var offset:cs.StdTypes.Int64; + switch pos { + case SeekBegin(_offset): + offset = _offset; + origin = Begin; + case SeekCurrent(_offset): + offset = _offset; + origin = Current; + case SeekEnd(_offset): + offset = _offset; + origin = End; + } + + return stream.Seek(offset, origin); } - function close():Void {} + public function close():Void { + stream.Close(); + } } diff --git a/std/cs/_std/sys/fs/Fs.hx b/std/cs/_std/sys/fs/Fs.hx index 475012ecb7b..01d53a96902 100644 --- a/std/cs/_std/sys/fs/Fs.hx +++ b/std/cs/_std/sys/fs/Fs.hx @@ -2,20 +2,53 @@ package sys.fs; import haxe.Result; import sys.fs.Metadata; +import cs.system.io.File as CsFile; + +class Fs { + static function metadata(path:Path):Metadata { + return new Metadata(path.toString(), true); + } -extern class Fs { - static function metadata(path:Path):Result; /** Returns the metadata for `path` without following symlinks. **/ - static function symlinkMetadata(path:Path):Result; - static function setPermissions(path:Path, perm:Permissions):Result; - static function copy(from:Path, to:Path):Result; - static function rename(from:Path, to:Path):Result; - static function readDir(path:Path):Result>, Error>; - static function createDir(path:Path):Result; - static function createDirRec(path:Path):Result; - static function removeDir(path:Path):Result; - static function removeDirRec(path:Path):Result; - static function removeFile(path:Path):Result; + static function symlinkMetadata(path:Path):Metadata { + throw new haxe.exceptions.NotImplementedException(); + } + + static function setPermissions(path:Path, perm:Permissions):Void { + throw new haxe.exceptions.NotImplementedException(); + } + + static function copy(from:Path, to:Path):Void { + throw new haxe.exceptions.NotImplementedException(); + } + + static function rename(from:Path, to:Path):Void { + throw new haxe.exceptions.NotImplementedException(); + } + + static function readDir(path:Path):Iterator { + throw new haxe.exceptions.NotImplementedException(); + } + + static function createDir(path:Path):Void { + throw new haxe.exceptions.NotImplementedException(); + } + + static function createDirRec(path:Path):Void { + throw new haxe.exceptions.NotImplementedException(); + } + + static function removeDir(path:Path):Void { + throw new haxe.exceptions.NotImplementedException(); + } + + static function removeDirRec(path:Path):Void { + throw new haxe.exceptions.NotImplementedException(); + } + + static function removeFile(path:Path):Void { + throw new haxe.exceptions.NotImplementedException(); + } } diff --git a/std/cs/_std/sys/fs/Metadata.hx b/std/cs/_std/sys/fs/Metadata.hx index 9bb34642472..8fb2343fbae 100644 --- a/std/cs/_std/sys/fs/Metadata.hx +++ b/std/cs/_std/sys/fs/Metadata.hx @@ -1,21 +1,80 @@ package sys.fs; +import cs.system.DateTime; +import cs.system.io.FileInfo; +import cs.system.io.FileAttributes; import haxe.Result; +import cs.system.io.File as CsFile; -extern class Metadata { - function isDir():Bool; - function isFile():Bool; - function isSymlink():Bool; - function size():haxe.Int64; - function permissions():Permissions; - function modified():Null; - function accessed():Null; - function created():Null; +class Metadata { + final path:String; + final followSymlinks:Bool; + final attributes:FileAttributes; + final fileInfo:FileInfo; + + @:allow(sys.fs) + function new(path:String, followSymlinks:Bool) { + this.path = path; + this.followSymlinks = followSymlinks; + this.attributes = CsFile.GetAttributes(this.path); + fileInfo = new FileInfo(path); + #if (cs_ver>=6) + if(followSymlinks && fileInfo.LinkTarget != null) { + fileInfo = fileInfo.ResolveTarget(true); + } + #end + } + + private inline function contains(attr:FileAttributes):Bool { + return untyped (attributes & attr) == attr; + } + + function isDir():Bool { + return contains(Directory); + } + + function isFile():Bool { + return !isDir() && !isSymlink(); + } + + function isSymlink():Bool { + #if (cs_ver >= 6) + return fileInfo.LinkTarget != null + #else + return false; + #end + } + + function size():haxe.Int64 { + return fileInfo.Length; + } + + function permissions():Permissions { + throw new haxe.exceptions.NotImplementedException(); + } + + function modified():Null { + // return fileInfo.LastWriteTime; + throw new haxe.exceptions.NotImplementedException(); + } + + function accessed():Null { + throw new haxe.exceptions.NotImplementedException(); + } + + function created():Null { + throw new haxe.exceptions.NotImplementedException(); + } +} + +private function SystemTimeFromDateTime(date:DateTime) { + // DateTime.UnixEpoch; + // return new haxe.time.SystemTime(date); } -extern class Permissions { - /** - Note that this does not affect the file, use `Fs.setPermissions`. - **/ - var readonly(get, set):Bool; -} \ No newline at end of file +// extern class Permissions { +// /** +// Note that this does not affect the file, use `Fs.setPermissions`. +// **/ +// var readonly(get, set):Bool; +// } diff --git a/std/java/_std/sys/fs/File.hx b/std/java/_std/sys/fs/File.hx index 1390df5fd75..dbbd56e66f6 100644 --- a/std/java/_std/sys/fs/File.hx +++ b/std/java/_std/sys/fs/File.hx @@ -37,6 +37,8 @@ class File { if(options.write) opts.add(WRITE); if(options.create) opts.add(CREATE); if(options.create_new) opts.add(CREATE_NEW); + if(options.append) opts.add(APPEND); + if(options.truncate) opts.add(TRUNCATE_EXISTING); final path = java.nio.file.Paths.get(p.toString()); return try new File(path, FileChannel.open(path, opts)) catch (e) { throw e; diff --git a/std/python/Memoryview.hx b/std/python/Memoryview.hx new file mode 100644 index 00000000000..bdcdcbc5037 --- /dev/null +++ b/std/python/Memoryview.hx @@ -0,0 +1,6 @@ +package python; + +@:native("memoryview") +extern class Memoryview implements ArrayAccess { + function new(b:Bytearray):Void; +} \ No newline at end of file diff --git a/std/python/_std/sys/fs/DirEntry.hx b/std/python/_std/sys/fs/DirEntry.hx new file mode 100644 index 00000000000..ded5a22854e --- /dev/null +++ b/std/python/_std/sys/fs/DirEntry.hx @@ -0,0 +1,6 @@ +package sys.fs; + +extern class DirEntry { + function path():Path; + function metadata():Metadata; +} diff --git a/std/python/_std/sys/fs/File.hx b/std/python/_std/sys/fs/File.hx new file mode 100644 index 00000000000..c3a88b47bc4 --- /dev/null +++ b/std/python/_std/sys/fs/File.hx @@ -0,0 +1,109 @@ +package sys.fs; + +import python.Memoryview; +import python.lib.Builtins; +import python.lib.io.RawIOBase; +import haxe.exceptions.NotImplementedException; +import haxe.io.Bytes; +import sys.fs.Metadata; +import python.lib.Os; + +@:coreApi +class File { + public static function open(p:Path, ?options:OpenOptions):File { + options ??= {read: true}; + var mode = switch [options.read, options.write, options.append] { + case [true, false, false]: "r"; + case [false, true, false]: if (options.truncate) "w" else "r+"; + case [true, true, false]: if (options.truncate) "w+" else "r+"; + case [false, _, true]: "a"; + case [true, _, true]: "rwa"; + case [false, false, false]: throw "invalid OpenOptions combinations"; + } + + if (options.create_new) { + mode += "x"; + } + + mode += "b"; + + return new File(p.toString(), cast Builtins.open(p.toString(), mode)); + } + + public static function create(p:Path):File { + throw new NotImplementedException(); + } + + public static function createNew(p:Path):File { + throw new NotImplementedException(); + } + + public static function readAll(p:Path):Bytes { + throw new NotImplementedException(); + } + + public static function writeAll(p:Path, b:Bytes):Void { + throw new NotImplementedException(); + } + + public static function appendAll(p:Path, b:Bytes):Void { + throw new NotImplementedException(); + } + + final path:String; + final file:RawIOBase; + + function new(path:String, file:RawIOBase) { + this.path = path; + this.file = file; + } + + public function syncAll():Void { + file.flush(); + Os.fsync(file.fileno()); + } + + public function syncData():Void { + if (Builtins.hasattr(Os, "fdatasync")) { + file.flush(); + Os.fdatasync(file.fileno()); + } else { + syncAll(); + } + } + + public function metadata():Metadata { + throw new NotImplementedException(); + } + + public function setPermissions(perm:Permissions):Void { + throw new NotImplementedException(); + } + + // TODO: technically these should all use a Int64 or even uint64 type, but `haxe.io.Bytes` uses an int length, so... + + public function read(bytes:haxe.io.Bytes, bufferOffset:Int, bufferLength:Int):Int { + final view:Memoryview = python.Syntax.arrayAccess(new Memoryview(bytes.getData()), [bufferOffset, bufferOffset + bufferLength]); + return file.readinto(view); + } + + public function write(bytes:haxe.io.Bytes, bufferOffset:Int, bufferLength:Int):Int { + final view:Memoryview = python.Syntax.arrayAccess(new Memoryview(bytes.getData()), [bufferOffset, bufferOffset + bufferLength]); + return file.write(view); + } + + public function seek(pos:SeekPos):haxe.Int64 { + switch pos { + case SeekBegin(offset): + return file.seek(haxe.Int64.toInt(offset), SeekSet); + case SeekCurrent(offset): + return file.seek(haxe.Int64.toInt(offset), SeekSet); + case SeekEnd(offset): + return file.seek(haxe.Int64.toInt(offset), SeekSet); + } + } + + public function close():Void { + throw new NotImplementedException(); + } +} diff --git a/std/python/_std/sys/fs/Fs.hx b/std/python/_std/sys/fs/Fs.hx new file mode 100644 index 00000000000..8ec21f23f6c --- /dev/null +++ b/std/python/_std/sys/fs/Fs.hx @@ -0,0 +1,21 @@ +package sys.fs; + +import sys.fs.Metadata; + +@:coreApi +extern class Fs { + static function metadata(path:Path):Metadata; + /** + Returns the metadata for `path` without following symlinks. + **/ + static function symlinkMetadata(path:Path):Metadata; + static function setPermissions(path:Path, perm:Permissions):Void; + static function copy(from:Path, to:Path):Void; + static function rename(from:Path, to:Path):Void; + static function readDir(path:Path):Iterator; + static function createDir(path:Path):Void; + static function createDirRec(path:Path):Void; + static function removeDir(path:Path):Void; + static function removeDirRec(path:Path):Void; + static function removeFile(path:Path):Void; +} diff --git a/std/python/_std/sys/fs/Metadata.hx b/std/python/_std/sys/fs/Metadata.hx new file mode 100644 index 00000000000..ccf084f62ed --- /dev/null +++ b/std/python/_std/sys/fs/Metadata.hx @@ -0,0 +1,47 @@ +package sys.fs; + +import haxe.exceptions.NotImplementedException; +import python.lib.Os; + +@:coreApi +class Metadata { + final stat:Stat; + + @:allow(sys.fs) + function new(path:String, followSymlinks:Bool) { + stat = followSymlinks ? Os.stat(path) : Os.lstat(path); + } + + public function isDir():Bool { + return Stat.S_ISDIR(stat.st_mode) != 0; + } + + public function isFile():Bool { + return !isDir() && !isSymlink(); + } + + public function isSymlink():Bool { + return Stat.S_ISLNK(stat.st_mode) != 0; + } + + public function size():haxe.Int64 { + return stat.st_size + } + + public function permissions():Permissions { + throw new NotImplementedException(); + } + + public function modified():Null { + stat. + throw new NotImplementedException(); + } + + public function accessed():Null { + throw new NotImplementedException(); + } + + public function created():Null { + throw new NotImplementedException(); + } +} diff --git a/std/python/_std/sys/fs/Permissions.hx b/std/python/_std/sys/fs/Permissions.hx new file mode 100644 index 00000000000..65b729f78d5 --- /dev/null +++ b/std/python/_std/sys/fs/Permissions.hx @@ -0,0 +1,9 @@ +package sys.fs; + +@:coreApi +extern class Permissions { + /** + Note that this does not affect the file, use `Fs.setPermissions`. + **/ + var readonly(get, set):Bool; +} \ No newline at end of file diff --git a/std/python/lib/Builtins.hx b/std/python/lib/Builtins.hx index 0b4a5c78f52..c3f455de26a 100644 --- a/std/python/lib/Builtins.hx +++ b/std/python/lib/Builtins.hx @@ -45,8 +45,9 @@ extern class Builtins { static function isinstance(obj:Dynamic, cl:Dynamic):Bool; static function hasattr(obj:Dynamic, attr:String):Bool; - static function getattr(obj:Dynamic, attr:String):Dynamic; - + overload static function getattr(obj:Dynamic, attr:String):Dynamic; + overload static function getattr(obj:Dynamic, attr:String, def:Dynamic):Dynamic; + @:overload(function(f:Set):Int {}) @:overload(function(f:StringBuf):Int {}) @:overload(function(f:Array):Int {}) diff --git a/std/python/lib/Os.hx b/std/python/lib/Os.hx index bf8a9839d64..b7e650db972 100644 --- a/std/python/lib/Os.hx +++ b/std/python/lib/Os.hx @@ -26,6 +26,7 @@ import python.Exceptions.OSError; import python.Tuple; import python.Dict; +@:pythonImport("stat") extern class Stat { var st_mode:Int; var st_ino:Int; @@ -49,6 +50,9 @@ extern class Stat { @:optional var st_rsize:Int; @:optional var st_creator:Int; @:optional var st_type:Int; + + static function S_ISDIR(mode:Int):Int; + static function S_ISLNK(mode:Int):Int; } @:pythonImport("os") @@ -95,4 +99,20 @@ extern class Os { static function makedirs(path:String, mode:Int = 511 /* Oktal 777 */, exist_ok:Bool = false):Void; static function mkdir(path:String, mode:Int = 511 /* Oktal 777 */):Void; + + static function open(path:String, flags:Int, mode:Int = 511):Int; + + static final O_RDONLY:Int; + static final O_WRONLY:Int; + static final O_RDWR:Int; + static final O_APPEND:Int; + static final O_CREAT:Int; + static final O_EXCL:Int; + static final O_TRUNC:Int; + + static function fsync(fd:Int):Void; + /** only available on Unix platforms (but not macOS) **/ + static function fdatasync(fd:Int):Void; + + static function lstat(path:String):Stat; } diff --git a/std/python/lib/io/RawIOBase.hx b/std/python/lib/io/RawIOBase.hx index d207a1f9ed9..54ab4290df9 100644 --- a/std/python/lib/io/RawIOBase.hx +++ b/std/python/lib/io/RawIOBase.hx @@ -29,8 +29,10 @@ import python.lib.io.IOBase; extern class RawIOBase extends IOBase implements IRawIOBase { function readall():Bytes; function read(n:Int = -1):Null; - function write(b:Bytearray):Null; - function readinto(b:Bytearray):Null; + overload function write(b:Memoryview):Null; + overload function write(b:Bytearray):Null; + overload function readinto(v:Memoryview):Null; + overload function readinto(b:Bytearray):Null; } @:remove extern interface IRawIOBase extends IIOBase { diff --git a/std/sys/fs/OpenOptions.hx b/std/sys/fs/OpenOptions.hx index 78e93dc84ba..49be5d36b02 100644 --- a/std/sys/fs/OpenOptions.hx +++ b/std/sys/fs/OpenOptions.hx @@ -5,8 +5,12 @@ package sys.fs; class OpenOptions { var read:Bool = true; var write:Bool = false; + /** implies write access **/ var append:Bool = false; + /** only has effect if write access is given too **/ var truncate:Bool = false; + /** requires write or append to actually create a file **/ var create:Bool = false; + /** create a new file and fail if it exists already. **/ var create_new:Bool = false; } \ No newline at end of file diff --git a/std/sys/net/IpAddress.hx b/std/sys/net/IpAddress.hx index eb218eb64c4..372446f9597 100644 --- a/std/sys/net/IpAddress.hx +++ b/std/sys/net/IpAddress.hx @@ -41,7 +41,7 @@ abstract Ipv4Addr(Bytes) { public static final LOCALHOST = new Ipv4Addr(127, 0, 0, 1); /** - Create an Ipv4 address from four eight-byte octets. + Create an Ipv4 address from four eight-bit octets. **/ public function new(a:Int, b:Int, c:Int, d:Int) { this = haxe.io.Bytes.alloc(4); @@ -55,7 +55,7 @@ abstract Ipv4Addr(Bytes) { These bytes are expected to contain a network order (big endian) representation of an ipv4 address. **/ public static function fromBytes(b:Bytes):Ipv4Addr { - if(b.length != 0) { + if(b.length != 4) { throw "invalid ipv4 address"; } return cast b.sub(0, 4); @@ -150,7 +150,7 @@ class IpAddressTools { } - // TODO: this doesn't actually work :( + // TODO: this static extension doesn't actually work :( public static function fromBytes(_:Enum, b:Bytes) { switch b.length { case 4: return Ipv4(Ipv4Addr.fromBytes(b));