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 }, ) }