internal/ui: bug fix: dropping multiple files didn't work on Firefox and Safari

Closes #3045
This commit is contained in:
Hajime Hoshi 2024-07-31 11:03:23 +09:00
parent 95ad1b158c
commit 1843f6acc1
2 changed files with 112 additions and 62 deletions

View File

@ -24,13 +24,24 @@ import (
) )
type FileEntryFS struct { type FileEntryFS struct {
rootEntry js.Value rootEntries []js.Value
} }
func NewFileEntryFS(root js.Value) *FileEntryFS { func NewFileEntryFS(rootEntries []js.Value) (*FileEntryFS, error) {
return &FileEntryFS{ // Check all the full paths are the same.
rootEntry: root, var fullpath string
for _, ent := range rootEntries {
if fullpath == "" {
fullpath = ent.Get("fullPath").String()
continue
}
if fullpath != ent.Get("fullPath").String() {
return nil, errors.New("file: all the full paths must be the same")
}
} }
return &FileEntryFS{
rootEntries: rootEntries,
}, nil
} }
func (f *FileEntryFS) Open(name string) (fs.File, error) { func (f *FileEntryFS) Open(name string) (fs.File, error) {
@ -43,33 +54,55 @@ func (f *FileEntryFS) Open(name string) (fs.File, error) {
} }
if name == "." { if name == "." {
return &dir{dirEntry: f.rootEntry}, nil var dirName string
for _, ent := range f.rootEntries {
if dirName == "" {
dirName = ent.Get("name").String()
continue
}
if dirName != ent.Get("name").String() {
return nil, &fs.PathError{
Op: "open",
Path: name,
Err: errors.New("invalid directory"),
}
}
}
return &dir{
name: dirName,
dirEntries: f.rootEntries,
}, nil
} }
var chEntry chan js.Value for _, ent := range f.rootEntries {
cbSuccess := js.FuncOf(func(this js.Value, args []js.Value) any { var chEntry chan js.Value
chEntry <- args[0] cbSuccess := js.FuncOf(func(this js.Value, args []js.Value) any {
close(chEntry) chEntry <- args[0]
return nil close(chEntry)
}) return nil
defer cbSuccess.Release() })
defer cbSuccess.Release()
cbFailure := js.FuncOf(func(this js.Value, args []js.Value) any { cbFailure := js.FuncOf(func(this js.Value, args []js.Value) any {
close(chEntry) close(chEntry)
return nil return nil
}) })
defer cbFailure.Release() defer cbFailure.Release()
chEntry = make(chan js.Value) chEntry = make(chan js.Value)
f.rootEntry.Call("getFile", name, nil, cbSuccess, cbFailure) ent.Call("getFile", name, nil, cbSuccess, cbFailure)
if entry := <-chEntry; entry.Truthy() { if entry := <-chEntry; entry.Truthy() {
return &file{entry: entry}, nil return &file{entry: entry}, nil
} }
chEntry = make(chan js.Value) chEntry = make(chan js.Value)
f.rootEntry.Call("getDirectory", name, nil, cbSuccess, cbFailure) ent.Call("getDirectory", name, nil, cbSuccess, cbFailure)
if entry := <-chEntry; entry.Truthy() { if entry := <-chEntry; entry.Truthy() {
return &dir{dirEntry: entry}, nil return &dir{
name: entry.Get("name").String(),
dirEntries: []js.Value{entry},
}, nil
}
} }
return nil, &fs.PathError{ return nil, &fs.PathError{
@ -163,21 +196,22 @@ func (f *file) Close() error {
} }
type dir struct { type dir struct {
dirEntry js.Value name string
dirEntries []js.Value
fileEntries []js.Value fileEntries []js.Value
offset int offset int
} }
func (d *dir) Stat() (fs.FileInfo, error) { func (d *dir) Stat() (fs.FileInfo, error) {
return &fileInfo{ return &fileInfo{
name: d.dirEntry.Get("name").String(), name: d.name,
}, nil }, nil
} }
func (d *dir) Read(buf []byte) (int, error) { func (d *dir) Read(buf []byte) (int, error) {
return 0, &fs.PathError{ return 0, &fs.PathError{
Op: "read", Op: "read",
Path: d.dirEntry.Get("name").String(), Path: d.name,
Err: errors.New("is a directory"), Err: errors.New("is a directory"),
} }
} }
@ -188,39 +222,48 @@ func (d *dir) Close() error {
func (d *dir) ReadDir(count int) ([]fs.DirEntry, error) { func (d *dir) ReadDir(count int) ([]fs.DirEntry, error) {
if d.fileEntries == nil { if d.fileEntries == nil {
ch := make(chan struct{}) names := map[string]struct{}{}
var rec js.Func for _, dirEntry := range d.dirEntries {
cb := js.FuncOf(func(this js.Value, args []js.Value) any { ch := make(chan struct{})
entries := args[0] var rec js.Func
if entries.Length() == 0 { cb := js.FuncOf(func(this js.Value, args []js.Value) any {
close(ch) entries := args[0]
if entries.Length() == 0 {
close(ch)
return nil
}
for i := 0; i < entries.Length(); i++ {
ent := entries.Index(i)
name := ent.Get("name").String()
// A name can be empty when this directory is a root directory.
if name == "" {
continue
}
// Avoid entry duplications. Entry duplications happen when multiple files are dropped on Chrome.
if _, ok := names[name]; ok {
continue
}
if !ent.Get("isFile").Bool() && !ent.Get("isDirectory").Bool() {
continue
}
d.fileEntries = append(d.fileEntries, ent)
names[name] = struct{}{}
}
rec.Value.Call("call")
return nil return nil
} })
for i := 0; i < entries.Length(); i++ { defer cb.Release()
ent := entries.Index(i)
// A name can be empty when this directory is a root directory. reader := dirEntry.Call("createReader")
if ent.Get("name").String() == "" { rec = js.FuncOf(func(this js.Value, args []js.Value) any {
continue reader.Call("readEntries", cb)
} return nil
if !ent.Get("isFile").Bool() && !ent.Get("isDirectory").Bool() { })
continue defer rec.Release()
}
d.fileEntries = append(d.fileEntries, ent)
}
rec.Value.Call("call") rec.Value.Call("call")
return nil <-ch
}) }
defer cb.Release()
reader := d.dirEntry.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.fileEntries) - d.offset n := len(d.fileEntries) - d.offset

View File

@ -707,14 +707,21 @@ func (u *UserInterface) appendDroppedFiles(data js.Value) {
defer u.dropFileM.Unlock() defer u.dropFileM.Unlock()
items := data.Get("items") items := data.Get("items")
var entries []js.Value
for i := 0; i < items.Length(); i++ { for i := 0; i < items.Length(); i++ {
kind := items.Index(i).Get("kind").String() kind := items.Index(i).Get("kind").String()
switch kind { switch kind {
case "file": case "file":
fs := items.Index(i).Call("webkitGetAsEntry").Get("filesystem").Get("root") entries = append(entries, items.Index(i).Call("webkitGetAsEntry").Get("filesystem").Get("root"))
u.inputState.DroppedFiles = file.NewFileEntryFS(fs) }
}
if len(entries) > 0 {
fs, err := file.NewFileEntryFS(entries)
if err != nil {
u.setError(err)
return return
} }
u.inputState.DroppedFiles = fs
} }
} }