storrent/fs/fs.go
2026-01-19 21:13:01 +09:00

157 lines
3.3 KiB
Go

package fs
import (
"fmt"
"strconv"
"strings"
"sync"
"github.com/knusbaum/go9p/fs"
"github.com/knusbaum/go9p/proto"
"storrent/client"
)
type Dir struct {
stat proto.Stat
parent fs.Dir
man *client.Manager
fsys *fs.FS
sync.RWMutex
}
func New(e *client.Manager) *fs.FS {
fsys, root := fs.NewFS("storrent", "storrent", 0555)
root.AddChild(&fs.WrappedFile{
File: fs.NewBaseFile(fsys.NewStat("ctl", "storrent", "storrent", 0222)),
WriteF: func(fid uint64, offset uint64, data []byte) (uint32, error) {
cmd := strings.TrimSpace(string(data))
if strings.HasPrefix(cmd, "add ") {
path := strings.TrimSpace(cmd[4:])
_, err := e.Add(path)
if err != nil {
return 0, err
}
return uint32(len(data)), nil
}
return 0, fmt.Errorf("unknown command: %s", cmd)
},
})
root.AddChild(fs.NewDynamicFile(
fsys.NewStat("list", "storrent", "storrent", 0444),
func() []byte {
data := e.List()
if len(data) > 0 {
return append(data, '\n')
}
return data
},
))
dir := &Dir{
stat: *fsys.NewStat("torrents", "storrent", "storrent", 0555|proto.DMDIR),
man: e,
fsys: fsys,
}
dir.stat.Qid.Qtype = uint8(dir.stat.Mode >> 24)
root.AddChild(dir)
return fsys
}
func (d *Dir) Stat() proto.Stat {
d.Lock()
defer d.Unlock()
return d.stat
}
func (d *Dir) WriteStat(s *proto.Stat) error {
d.Lock()
defer d.Unlock()
d.stat = *s
return nil
}
func (d *Dir) SetParent(p fs.Dir) {
d.Lock()
defer d.Unlock()
d.parent = p
}
func (d *Dir) Parent() fs.Dir {
d.RLock()
defer d.RUnlock()
return d.parent
}
func (d *Dir) Children() map[string]fs.FSNode {
data := d.man.List()
m := make(map[string]fs.FSNode)
if len(data) == 0 {
return m
}
for _, name := range strings.Split(string(data), "\n") {
id, err := strconv.Atoi(name)
if err != nil {
continue
}
m[name] = d.newTorrentDir(id)
}
return m
}
func (d *Dir) newTorrentDir(id int) fs.FSNode {
dir := fs.NewStaticDir(d.fsys.NewStat(strconv.Itoa(id), "storrent", "storrent", 0555))
dir.AddChild(&fs.WrappedFile{
File: fs.NewBaseFile(d.fsys.NewStat("ctl", "storrent", "storrent", 0222)),
WriteF: func(fid uint64, offset uint64, data []byte) (uint32, error) {
cmd := strings.TrimSpace(string(data))
var err error
switch {
case cmd == "start":
err = d.man.Start(id)
case cmd == "stop":
err = d.man.Stop(id)
case cmd == "seed":
err = d.man.Seed(id)
case cmd == "remove":
err = d.man.Remove(id)
case strings.HasPrefix(cmd, "peer "):
err = d.man.AddPeer(id, strings.TrimSpace(cmd[5:]))
default:
return 0, fmt.Errorf("unknown command: %s", cmd)
}
if err != nil {
return 0, err
}
return uint32(len(data)), nil
},
})
dir.AddChild(d.newStatusFile(id, "name"))
dir.AddChild(d.newStatusFile(id, "state"))
dir.AddChild(d.newStatusFile(id, "progress"))
dir.AddChild(d.newStatusFile(id, "size"))
dir.AddChild(d.newStatusFile(id, "down"))
dir.AddChild(d.newStatusFile(id, "up"))
dir.AddChild(d.newStatusFile(id, "pieces"))
dir.AddChild(d.newStatusFile(id, "peers"))
return dir
}
func (d *Dir) newStatusFile(id int, field string) fs.FSNode {
return fs.NewDynamicFile(
d.fsys.NewStat(field, "storrent", "storrent", 0444),
func() []byte {
data, err := d.man.Status(id, field)
if err != nil {
return nil
}
if len(data) > 0 {
return append(data, '\n')
}
return data
},
)
}