mpvfs

9P file server for controlling mpv playback

git clone https://git.8pit.net/mpvfs.git

  1package fileserver
  2
  3import (
  4	"go.rbn.im/neinp"
  5	"go.rbn.im/neinp/fid"
  6	"go.rbn.im/neinp/message"
  7	"go.rbn.im/neinp/qid"
  8	"go.rbn.im/neinp/stat"
  9
 10	"context"
 11	"errors"
 12	"io"
 13	"sync"
 14)
 15
 16type File interface {
 17	Read(int64, []byte) (int, error)
 18	Write(int64, []byte) (int, error)
 19	Close() error
 20}
 21
 22type Cons func() (File, error)
 23
 24type FileMap map[string]Cons
 25
 26type FileServer struct {
 27	neinp.NopP2000
 28	root File
 29	fids *fid.Map
 30
 31	stat map[string]*stat.Stat
 32	open *sync.Map // fid.Fid → File
 33	cons FileMap
 34}
 35
 36const version = "9P2000"
 37
 38// TODO: Currently only supports a flat hierarchy with one root
 39// directory and several regular files located inside this directory.
 40
 41func NewFileServer(files FileMap) *FileServer {
 42	fs := &FileServer{
 43		stat: make(map[string]*stat.Stat),
 44		open: new(sync.Map),
 45		cons: files,
 46		fids: fid.New(),
 47	}
 48
 49	rootStat := createStat("/", 0555|stat.Dir)
 50	rootFile := directory{stat: rootStat}
 51
 52	for name, _ := range files {
 53		s := createStat(name, 0644)
 54		fs.stat[name] = &s
 55		rootFile.children = append(rootFile.children, s)
 56	}
 57
 58	fs.stat[rootStat.Name] = &rootStat
 59	fs.cons[rootStat.Name] = func() (File, error) { return rootFile, nil }
 60
 61	return fs
 62}
 63
 64func (s *FileServer) Version(ctx context.Context, msg message.TVersion) (message.RVersion, error) {
 65	if msg.Version != version {
 66		return message.RVersion{}, errors.New(message.BotchErrorString)
 67	}
 68
 69	// TODO: Sanity check on msize
 70	return message.RVersion{Version: version, Msize: msg.Msize}, nil
 71}
 72
 73func (s *FileServer) Attach(ctx context.Context, msg message.TAttach) (message.RAttach, error) {
 74	stat := s.stat["/"]
 75
 76	s.fids.Set(msg.Fid, stat)
 77	return message.RAttach{Qid: stat.Qid}, nil
 78}
 79
 80func (s *FileServer) Stat(ctx context.Context, msg message.TStat) (message.RStat, error) {
 81	stat, ok := s.fids.Get(msg.Fid).(*stat.Stat)
 82	if !ok {
 83		return message.RStat{}, errors.New(message.NoStatErrorString)
 84	}
 85
 86	return message.RStat{Stat: *stat}, nil
 87}
 88
 89func (s *FileServer) Walk(ctx context.Context, msg message.TWalk) (message.RWalk, error) {
 90	stat, ok := s.fids.Get(msg.Fid).(*stat.Stat)
 91	if !ok {
 92		return message.RWalk{}, errors.New(message.UnknownFidErrorString)
 93	} else if len(msg.Wname) > 1 {
 94		return message.RWalk{}, errors.New(message.WalkNoDirErrorString)
 95	}
 96
 97	newStat := stat
 98	if len(msg.Wname) == 1 {
 99		name := msg.Wname[0]
100		newStat, ok = s.stat[name]
101		if !ok {
102			return message.RWalk{}, errors.New(message.NotFoundErrorString)
103		}
104	}
105
106	if msg.Newfid != msg.Fid {
107		if s.fids.Get(msg.Newfid) != nil {
108			return message.RWalk{}, errors.New(message.DupFidErrorString)
109		}
110		s.fids.Set(msg.Newfid, newStat)
111	}
112
113	if len(msg.Wname) == 1 {
114		return message.RWalk{Wqid: []qid.Qid{newStat.Qid}}, nil
115	} else {
116		return message.RWalk{}, nil
117	}
118}
119
120func (s *FileServer) Open(ctx context.Context, msg message.TOpen) (message.ROpen, error) {
121	stat, ok := s.fids.Get(msg.Fid).(*stat.Stat)
122	if !ok {
123		return message.ROpen{}, errors.New(message.UnknownFidErrorString)
124	}
125
126	file, err := s.cons[stat.Name]()
127	if err != nil {
128		return message.ROpen{}, err
129	}
130
131	s.open.Store(msg.Fid, file)
132	return message.ROpen{Qid: stat.Qid}, nil
133}
134
135func (s *FileServer) Read(ctx context.Context, msg message.TRead) (message.RRead, error) {
136	f, ok := s.open.Load(msg.Fid)
137	if !ok {
138		return message.RRead{}, errors.New(message.UnknownFidErrorString)
139	}
140	file := f.(File)
141
142	// TODO: Sanity check count
143	buf := make([]byte, msg.Count)
144	n, err := file.Read(int64(msg.Offset), buf)
145	if err == io.EOF {
146		return message.RRead{Count: 0}, nil
147	} else if err != nil {
148		return message.RRead{}, err
149	}
150
151	return message.RRead{Count: uint32(n), Data: buf[:n]}, nil
152}
153
154func (s *FileServer) Write(ctx context.Context, msg message.TWrite) (message.RWrite, error) {
155	f, ok := s.open.Load(msg.Fid)
156	if !ok {
157		return message.RWrite{}, errors.New(message.UnknownFidErrorString)
158	}
159	file := f.(File)
160
161	n, err := file.Write(int64(msg.Offset), msg.Data)
162	if err != nil {
163		return message.RWrite{}, err
164	}
165
166	return message.RWrite{Count: uint32(n)}, nil
167}
168
169func (s *FileServer) Clunk(ctx context.Context, msg message.TClunk) (message.RClunk, error) {
170	defer s.fids.Delete(msg.Fid)
171	defer s.open.Delete(msg.Fid)
172
173	f, ok := s.open.Load(msg.Fid)
174	if ok {
175		file := f.(File)
176		err := file.Close()
177		if err != nil {
178			return message.RClunk{}, err
179		}
180	}
181
182	return message.RClunk{}, nil
183}