mirror of
https://github.com/hajimehoshi/ebiten.git
synced 2024-11-10 04:57:26 +01:00
294 lines
5.7 KiB
Go
294 lines
5.7 KiB
Go
|
// Copyright 2023 The Ebitengine Authors
|
||
|
//
|
||
|
// Licensed under the Apache License, Version 2.0 (the "License");
|
||
|
// you may not use this file except in compliance with the License.
|
||
|
// You may obtain a copy of the License at
|
||
|
//
|
||
|
// http://www.apache.org/licenses/LICENSE-2.0
|
||
|
//
|
||
|
// Unless required by applicable law or agreed to in writing, software
|
||
|
// distributed under the License is distributed on an "AS IS" BASIS,
|
||
|
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||
|
// See the License for the specific language governing permissions and
|
||
|
// limitations under the License.
|
||
|
|
||
|
package file
|
||
|
|
||
|
import (
|
||
|
"errors"
|
||
|
"fmt"
|
||
|
"io"
|
||
|
"io/fs"
|
||
|
"syscall/js"
|
||
|
"time"
|
||
|
)
|
||
|
|
||
|
type FileEntryFS struct {
|
||
|
rootEntry js.Value
|
||
|
}
|
||
|
|
||
|
func NewFileEntryFS(root js.Value) *FileEntryFS {
|
||
|
return &FileEntryFS{
|
||
|
rootEntry: root,
|
||
|
}
|
||
|
}
|
||
|
|
||
|
func (f *FileEntryFS) Open(name string) (fs.File, error) {
|
||
|
if !fs.ValidPath(name) {
|
||
|
return nil, &fs.PathError{
|
||
|
Op: "open",
|
||
|
Path: name,
|
||
|
Err: fs.ErrNotExist,
|
||
|
}
|
||
|
}
|
||
|
|
||
|
if name == "." {
|
||
|
return &dir{entry: f.rootEntry}, nil
|
||
|
}
|
||
|
|
||
|
var chEntry chan js.Value
|
||
|
cbSuccess := js.FuncOf(func(this js.Value, args []js.Value) any {
|
||
|
chEntry <- args[0]
|
||
|
close(chEntry)
|
||
|
return nil
|
||
|
})
|
||
|
defer cbSuccess.Release()
|
||
|
|
||
|
cbFailure := js.FuncOf(func(this js.Value, args []js.Value) any {
|
||
|
close(chEntry)
|
||
|
return nil
|
||
|
})
|
||
|
defer cbFailure.Release()
|
||
|
|
||
|
chEntry = make(chan js.Value)
|
||
|
f.rootEntry.Call("getFile", name, nil, cbSuccess, cbFailure)
|
||
|
if entry := <-chEntry; entry.Truthy() {
|
||
|
return &file{entry: entry}, nil
|
||
|
}
|
||
|
|
||
|
chEntry = make(chan js.Value)
|
||
|
f.rootEntry.Call("getDirectory", name, nil, cbSuccess, cbFailure)
|
||
|
if entry := <-chEntry; entry.Truthy() {
|
||
|
return &dir{entry: entry}, nil
|
||
|
}
|
||
|
|
||
|
return nil, &fs.PathError{
|
||
|
Op: "open",
|
||
|
Path: name,
|
||
|
Err: fs.ErrNotExist,
|
||
|
}
|
||
|
}
|
||
|
|
||
|
type file struct {
|
||
|
entry js.Value
|
||
|
file js.Value
|
||
|
offset int64
|
||
|
uint8Array js.Value
|
||
|
}
|
||
|
|
||
|
func getFile(entry js.Value) js.Value {
|
||
|
ch := make(chan js.Value, 1)
|
||
|
cb := js.FuncOf(func(this js.Value, args []js.Value) any {
|
||
|
ch <- args[0]
|
||
|
return nil
|
||
|
})
|
||
|
defer cb.Release()
|
||
|
|
||
|
entry.Call("file", cb)
|
||
|
return <-ch
|
||
|
}
|
||
|
|
||
|
func (f *file) ensureFile() js.Value {
|
||
|
if f.file.Truthy() {
|
||
|
return f.file
|
||
|
}
|
||
|
|
||
|
f.file = getFile(f.entry)
|
||
|
return f.file
|
||
|
}
|
||
|
|
||
|
func (f *file) Stat() (fs.FileInfo, error) {
|
||
|
return &fileInfo{
|
||
|
entry: f.entry,
|
||
|
file: f.ensureFile(),
|
||
|
}, nil
|
||
|
}
|
||
|
|
||
|
func (f *file) Read(buf []byte) (int, error) {
|
||
|
if !f.uint8Array.Truthy() {
|
||
|
chArrayBuffer := make(chan js.Value, 1)
|
||
|
cbThen := js.FuncOf(func(this js.Value, args []js.Value) any {
|
||
|
chArrayBuffer <- args[0]
|
||
|
return nil
|
||
|
})
|
||
|
defer cbThen.Release()
|
||
|
|
||
|
chError := make(chan js.Value, 1)
|
||
|
cbCatch := js.FuncOf(func(this js.Value, args []js.Value) any {
|
||
|
chError <- args[0]
|
||
|
return nil
|
||
|
})
|
||
|
defer cbCatch.Release()
|
||
|
|
||
|
f.ensureFile().Call("arrayBuffer").Call("then", cbThen).Call("catch", cbCatch)
|
||
|
select {
|
||
|
case ab := <-chArrayBuffer:
|
||
|
f.uint8Array = js.Global().Get("Uint8Array").New(ab)
|
||
|
case err := <-chError:
|
||
|
return 0, fmt.Errorf("%s", err.Call("toString").String())
|
||
|
}
|
||
|
}
|
||
|
|
||
|
size := int64(f.uint8Array.Get("byteLength").Float())
|
||
|
if f.offset >= size {
|
||
|
return 0, io.EOF
|
||
|
}
|
||
|
|
||
|
if len(buf) == 0 {
|
||
|
return 0, nil
|
||
|
}
|
||
|
|
||
|
slice := f.uint8Array.Call("subarray", f.offset, f.offset+int64(len(buf)))
|
||
|
n := slice.Get("byteLength").Int()
|
||
|
js.CopyBytesToGo(buf[:n], slice)
|
||
|
f.offset += int64(n)
|
||
|
if f.offset >= size {
|
||
|
return n, io.EOF
|
||
|
}
|
||
|
return n, nil
|
||
|
}
|
||
|
|
||
|
func (f *file) Close() error {
|
||
|
return nil
|
||
|
}
|
||
|
|
||
|
type dir struct {
|
||
|
entry js.Value
|
||
|
entries []js.Value
|
||
|
offset int
|
||
|
}
|
||
|
|
||
|
func (d *dir) Stat() (fs.FileInfo, error) {
|
||
|
return &fileInfo{
|
||
|
entry: d.entry,
|
||
|
}, nil
|
||
|
}
|
||
|
|
||
|
func (d *dir) Read(buf []byte) (int, error) {
|
||
|
return 0, &fs.PathError{
|
||
|
Op: "read",
|
||
|
Path: d.entry.Get("name").String(),
|
||
|
Err: errors.New("is a directory"),
|
||
|
}
|
||
|
}
|
||
|
|
||
|
func (d *dir) Close() error {
|
||
|
return nil
|
||
|
}
|
||
|
|
||
|
func (d *dir) ReadDir(count int) ([]fs.DirEntry, error) {
|
||
|
if d.entries == nil {
|
||
|
ch := make(chan struct{})
|
||
|
var rec js.Func
|
||
|
cb := js.FuncOf(func(this js.Value, args []js.Value) any {
|
||
|
entries := args[0]
|
||
|
if entries.Length() == 0 {
|
||
|
close(ch)
|
||
|
return nil
|
||
|
}
|
||
|
for i := 0; i < entries.Length(); i++ {
|
||
|
ent := entries.Index(i)
|
||
|
// A name can be empty when this directory is a root directory.
|
||
|
if ent.Get("name").String() == "" {
|
||
|
continue
|
||
|
}
|
||
|
if !ent.Get("isFile").Bool() && !ent.Get("isDirectory").Bool() {
|
||
|
continue
|
||
|
}
|
||
|
d.entries = append(d.entries, ent)
|
||
|
}
|
||
|
rec.Value.Call("call")
|
||
|
return nil
|
||
|
})
|
||
|
defer cb.Release()
|
||
|
|
||
|
reader := d.entry.Call("createReader")
|
||
|
rec = js.FuncOf(func(this js.Value, args []js.Value) any {
|
||
|
reader.Call("readEntries", cb)
|
||
|
return nil
|
||
|
})
|
||
|
defer rec.Release()
|
||
|
|
||
|
rec.Value.Call("call")
|
||
|
<-ch
|
||
|
}
|
||
|
|
||
|
n := len(d.entries) - d.offset
|
||
|
|
||
|
if n == 0 {
|
||
|
if count <= 0 {
|
||
|
return nil, nil
|
||
|
}
|
||
|
return nil, io.EOF
|
||
|
}
|
||
|
|
||
|
if count > 0 && n > count {
|
||
|
n = count
|
||
|
}
|
||
|
|
||
|
ents := make([]fs.DirEntry, n)
|
||
|
for i := range ents {
|
||
|
fi := &fileInfo{
|
||
|
entry: d.entries[d.offset+i],
|
||
|
}
|
||
|
if fi.entry.Get("isFile").Bool() {
|
||
|
fi.file = getFile(fi.entry)
|
||
|
}
|
||
|
ents[i] = fs.FileInfoToDirEntry(fi)
|
||
|
}
|
||
|
d.offset += n
|
||
|
|
||
|
return ents, nil
|
||
|
}
|
||
|
|
||
|
type fileInfo struct {
|
||
|
entry js.Value
|
||
|
file js.Value
|
||
|
}
|
||
|
|
||
|
func (f *fileInfo) Name() string {
|
||
|
return f.entry.Get("name").String()
|
||
|
}
|
||
|
|
||
|
func (f *fileInfo) Size() int64 {
|
||
|
if !f.file.Truthy() {
|
||
|
return 0
|
||
|
}
|
||
|
return int64(f.file.Get("size").Float())
|
||
|
}
|
||
|
|
||
|
func (f *fileInfo) Mode() fs.FileMode {
|
||
|
if !f.file.Truthy() {
|
||
|
return 0555 | fs.ModeDir
|
||
|
}
|
||
|
return 0444
|
||
|
}
|
||
|
|
||
|
func (f *fileInfo) ModTime() time.Time {
|
||
|
if !f.file.Truthy() {
|
||
|
return time.Time{}
|
||
|
}
|
||
|
return time.UnixMilli(int64(f.file.Get("lastModified").Float()))
|
||
|
}
|
||
|
|
||
|
func (f *fileInfo) IsDir() bool {
|
||
|
if !f.file.Truthy() {
|
||
|
return true
|
||
|
}
|
||
|
return false
|
||
|
}
|
||
|
|
||
|
func (f *fileInfo) Sys() any {
|
||
|
return nil
|
||
|
}
|