ebiten/internal/file/file_notjs.go
2023-01-25 14:27:11 +09:00

167 lines
3.3 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.
//go:build !js
package file
import (
"errors"
"io"
"io/fs"
"os"
"path/filepath"
"sort"
"strings"
"sync"
"time"
)
type VirtualFS struct {
root virtualFSRoot
}
func NewVirtualFS(paths []string) *VirtualFS {
fs := &VirtualFS{}
fs.root.addRealPaths(paths)
return fs
}
func (v *VirtualFS) Open(name string) (fs.File, error) {
if !fs.ValidPath(name) {
return nil, &fs.PathError{
Op: "open",
Path: name,
Err: fs.ErrNotExist,
}
}
if name == "." {
return &v.root, nil
}
// A valid path must not include a token "." or "..", except for "." itself.
es := strings.Split(name, "/")
for _, realPath := range v.root.realPaths {
if filepath.Base(realPath) != es[0] {
continue
}
return os.Open(filepath.Join(append([]string{realPath}, es[1:]...)...))
}
return nil, &fs.PathError{
Op: "open",
Path: name,
Err: fs.ErrNotExist,
}
}
type virtualFSRoot struct {
realPaths []string
offset int
m sync.Mutex
}
func (v *virtualFSRoot) addRealPaths(paths []string) {
v.m.Lock()
defer v.m.Unlock()
for _, path := range paths {
// If the path consists entirely of separators, filepath.Base returns a single separator.
// On Windows, filepath.Base(`C:\`) == `\`.
// Skip root directory paths on purpose. This is almost the same behavior as the Chrome browser.
if filepath.Base(path) == string(filepath.Separator) {
continue
}
v.realPaths = append(v.realPaths, path)
}
sort.Strings(v.realPaths)
}
func (v *virtualFSRoot) Stat() (fs.FileInfo, error) {
return &virtualFSRootFileInfo{}, nil
}
func (v *virtualFSRoot) Read([]byte) (int, error) {
return 0, &fs.PathError{
Op: "read",
Path: "/",
Err: errors.New("is a directory"),
}
}
func (v *virtualFSRoot) Close() error {
return nil
}
func (v *virtualFSRoot) ReadDir(count int) ([]fs.DirEntry, error) {
v.m.Lock()
defer v.m.Unlock()
n := len(v.realPaths) - v.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, err := os.Stat(v.realPaths[v.offset+i])
if err != nil {
if count <= 0 {
return ents, err
}
return nil, err
}
ents[i] = fs.FileInfoToDirEntry(fi)
}
v.offset += n
return ents, nil
}
type virtualFSRootFileInfo struct {
}
func (v *virtualFSRootFileInfo) Name() string {
return "."
}
func (v *virtualFSRootFileInfo) Size() int64 {
return 0
}
func (v *virtualFSRootFileInfo) Mode() fs.FileMode {
return 0555 | fs.ModeDir
}
func (v *virtualFSRootFileInfo) ModTime() time.Time {
return time.Time{}
}
func (v *virtualFSRootFileInfo) IsDir() bool {
return true
}
func (v *virtualFSRootFileInfo) Sys() any {
return nil
}