@@ 1,5 1,8 @@
package main
+import "fmt"
+import "os"
+import "io"
import "io/ioutil"
const (
@@ 8,10 11,9 @@ const (
EventDelete = "delete"
)
-type EntityPath struct {
- WebPath string
- FSPath string
-}
+var (
+ ResourceNotFound = fmt.Errorf("Required resource was not found")
+)
type Request struct {
Subscribe bool
@@ 30,18 32,18 @@ type State struct {
}
type DelayedCachedReader struct {
- Path string
+ File io.Reader
IsLoaded bool
data []byte
}
-func NewDelayedCachedReader(path string) *DelayedCachedReader {
- return &DelayedCachedReader{path, false, nil}
+func NewDelayedCachedReader(r io.Reader) *DelayedCachedReader {
+ return &DelayedCachedReader{r, false, nil}
}
func (r *DelayedCachedReader) MarshalJSON() ([]byte, error) {
if r.IsLoaded == false {
- data, err := ioutil.ReadFile(r.Path)
+ data, err := ioutil.ReadAll(r.File)
if err != nil {
return nil, err
}
@@ 50,3 52,30 @@ func (r *DelayedCachedReader) MarshalJSO
}
return r.data, nil
}
+
+type Resource struct {
+ WebPath string
+ FSPath string
+}
+
+type OpenResource struct {
+ *Resource
+ *os.File
+ os.FileInfo
+}
+
+func (r *OpenResource) Payload() (interface{}, error) {
+ if r.IsDir() {
+ fis, err := r.Readdir(0)
+ if err != nil {
+ return nil, err
+ }
+ payload := make([]string, len(fis))
+ for i, fi := range fis {
+ payload[i] = fi.Name()
+ }
+ return payload, nil
+ } else {
+ return NewDelayedCachedReader(r), nil
+ }
+}
@@ 3,7 3,6 @@ package main
import "net/http"
import "os"
import "log"
-import "io/ioutil"
import "fmt"
import "strings"
import "path/filepath"
@@ 50,79 49,45 @@ func NewMetronomeHandler(watchdir string
return h, nil
}
-func (h *MetronomeHandler) toFsPath(path string) (string, error) {
- path, err := filepath.Abs(filepath.Join(h.watchdir, path))
- if err != nil {
- return "", err
+func (h *MetronomeHandler) resFromWebPath(path string) (*Resource, error) {
+ if path == "" {
+ return nil, fmt.Errorf("Path must not be empty")
}
- //todo filepath.EvalSymlinks?
- if strings.HasPrefix(path, h.watchdir) == false {
- return "", fmt.Errorf("Path does not appear to be inside a valid directory")
- }
- return path, nil
-}
-
-func dirListing(fspath string) ([]string, error) {
- listing := make([]string, 0)
- fis, err := ioutil.ReadDir(fspath)
+ fspath, err := filepath.Abs(filepath.Join(h.watchdir, path))
if err != nil {
return nil, err
- } else {
- for _, fi := range fis {
- item := fi.Name()
- log.Println("listing", item)
- if fi.IsDir() {
- item = givePathSuffix(item)
- }
- listing = append(listing, item)
- }
}
- return listing, nil
+ //fspath, err = filepath.EvalSymlinks(fspath)
+ //if err != nil {
+ // return nil, err
+ //}
+ return &Resource{path, fspath}, nil
}
-func (h *MetronomeHandler) serveWS(ws *websocket.Conn) {
- defer ws.Close()
- for {
- log.Println("serving:", ws)
- if err := h.readWS(ws); err != nil {
- log.Println("serveWS error:", err)
- break
- }
+func (h *MetronomeHandler) openResource(r *Resource) (*OpenResource, error) {
+ file, err := os.Open(r.FSPath)
+ if err != nil {
+ return nil,err
}
+ fi, err := file.Stat()
+ if err != nil {
+ return nil, err
+ }
+ return &OpenResource{r, file, fi}, nil
}
-func (h *MetronomeHandler) readWS(ws *websocket.Conn) error {
- r := new(Request)
- if err := websocket.JSON.Receive(ws, r); err != nil {
- return websocket.JSON.Send(ws, &Error{
- Message: "Invalid request",
- Details: err.Error(),
- })
- }
- log.Println(r)
- if r.Path == "" {
- return websocket.JSON.Send(ws, &Error{
- Message: "Invalid path in request",
- Details: "Path must not be empty",
- })
+func (h *MetronomeHandler) validateResource(r *Resource) error {
+ if strings.HasPrefix(r.FSPath, h.watchdir) == false {
+ return fmt.Errorf("Path does not appear to be inside a valid directory")
}
- fspath, err := h.toFsPath(r.Path)
- if err != nil {
- return websocket.JSON.Send(ws, &Error{
- Message: "Invalid path in request",
- Details: err.Error(),
- })
- }
- subs, ok := h.subs[r.Path]
- if ok == false {
- subs = make([]*websocket.Conn, 0)
- }
- subs = append(subs, ws)
- h.subs[r.Path] = subs
+ return nil
+}
+/*
+func (h *MetronomeHandler) resourceData(r *Resource) (interface{}, error) {
// Get Directory listing
- if r.Path[len(r.Path)-1] == os.PathSeparator {
- listing, err := dirListing(fspath)
+ if r.IsDir() {
+ listing, err := r.dirListing(fspath)
if err != nil {
log.Printf("error getting listing of %v: %v", fspath, err)
return websocket.JSON.Send(ws, &Error{
@@ 142,33 107,93 @@ func (h *MetronomeHandler) readWS(ws *we
Payload: NewDelayedCachedReader(fspath),
})
}
+*/
+
+func (h *MetronomeHandler) serveWS(ws *websocket.Conn) {
+ defer ws.Close()
+ for {
+ log.Println("serving:", ws)
+ if err := h.serveOneWS(ws); err != nil {
+ log.Println("serveWS error:", err)
+ break
+ }
+ }
+}
+
+func (h *MetronomeHandler) serveOneWS(ws *websocket.Conn) error {
+ r := new(Request)
+ if err := websocket.JSON.Receive(ws, r); err != nil {
+ return websocket.JSON.Send(ws, &Error{
+ Message: "Invalid request",
+ Details: err.Error(),
+ })
+ }
+
+ log.Println("request:", r)
+
+ res, err := h.resFromWebPath(r.Path)
+ if err == nil {
+ err = h.validateResource(res)
+ }
+ if err != nil {
+ return websocket.JSON.Send(ws, &Error{
+ Message: "Invalid path in request",
+ Details: err.Error(),
+ })
+ }
+
+ // Open the resource
+ ores, err := h.openResource(res)
+ if err != nil {
+ return websocket.JSON.Send(ws, &Error{
+ Message: "Could not read resource",
+ Details: err.Error(),
+ })
+ }
+ payload, err := ores.Payload()
+ if err != nil {
+ return websocket.JSON.Send(ws, &Error{
+ Message: "Could not read resource",
+ Details: err.Error(),
+ })
+ }
+
+ // Subscribe to the resource
+ subs, ok := h.subs[res.FSPath]
+ if ok == false {
+ subs = make([]*websocket.Conn, 0)
+ }
+ subs = append(subs, ws)
+ h.subs[res.WebPath] = subs
+
+ // Send the resource
+ return websocket.JSON.Send(ws, &State{
+ Path: res.WebPath,
+ Payload: payload,
+ })
+}
func (h *MetronomeHandler) handleFsEvents() {
for {
+ log.Println("waiting for fs events")
select {
case ev := <-h.watcher.Event:
+ var err error
path := ev.Name[len(h.watchdir):]
log.Println("event:", ev, path)
- if ev.IsDelete() || ev.IsRename() {
- if path[len(path)-1] == os.PathSeparator {
- h.dirRemoved(path)
- } else {
- // Not sure if it's a directory or a file I guess
- h.dirRemoved(path)
- h.fileRemoved(path)
+ res, err := h.resFromWebPath(path)
+ if err != nil {
+ log.Println("error resolving resource in handling fs event:", err)
+ } else {
+ if ev.IsDelete() || ev.IsRename() {
+ err = h.resRemoved(res)
+ } else if ev.IsModify() {
+ err = h.resModified(res)
+ } else if ev.IsCreate() {
+ err = h.resAdded(res)
}
- } else if ev.IsModify() {
- h.fileModified(path)
- } else if ev.IsCreate() {
- fi, err := os.Stat(ev.Name)
if err != nil {
- log.Printf("Failure getting stat of %v: %v\n", path, err)
- break
- }
- if fi.IsDir() {
- h.dirAdded(path)
- } else {
- h.fileAdded(path)
+ log.Println("error dealing with resource:", err)
}
}
case err := <-h.watcher.Error:
@@ 186,102 211,67 @@ func (h *MetronomeHandler) notifyConns(p
conns, ok := h.subs[path]
if ok {
for _, conn := range conns {
- if err := notifyConn(conn, msg); err != nil {
- log.Println("Error while notifying a connection", err)
- }
+ go func(conn *websocket.Conn) {
+ if err := notifyConn(conn, msg); err != nil {
+ log.Println("Error while notifying a connection:", err)
+ }
+ }(conn)
}
}
}
-func (h *MetronomeHandler) fileAdded(path string) {
- fspath, err := h.toFsPath(path)
+func (h *MetronomeHandler) resAdded(res *Resource) error {
+ ores, err := h.openResource(res)
if err != nil {
- panic(err)
+ return err
}
- h.notifyConns(path, &State{
- Event: EventCreate,
- Path: path,
- Payload: NewDelayedCachedReader(fspath),
- })
- h.dirModified(filepath.Dir(path));
-}
-
-func (h *MetronomeHandler) fileModified(path string) {
- fspath, err := h.toFsPath(path)
+ payload, err := ores.Payload()
if err != nil {
- panic(err)
+ return err
}
- h.notifyConns(path, &State{
- Event: EventDelete,
- Path: path,
- Payload: NewDelayedCachedReader(fspath),
+ h.notifyConns(res.WebPath, &State{
+ Event: EventCreate,
+ Path: res.WebPath,
+ Payload: payload,
})
-}
-
-func (h *MetronomeHandler) fileRemoved(path string) {
- h.notifyConns(path, &State{
- Event: EventDelete,
- Path: path,
- })
- h.dirModified(filepath.Dir(path));
+ if res.WebPath != "/" {
+ parent, err := h.resFromWebPath(filepath.Dir(res.WebPath))
+ if err != nil {
+ return err
+ }
+ return h.resModified(parent)
+ }
+ return nil
}
-func (h *MetronomeHandler) dirAdded(path string) {
- fspath, err := h.toFsPath(path)
+func (h *MetronomeHandler) resModified(res *Resource) error {
+ ores, err := h.openResource(res)
if err != nil {
- panic(err)
+ return err
}
- path = givePathSuffix(path)
- h.notifyConns(path, &State{
- Event: EventCreate,
- Path: path,
+ payload, err := ores.Payload()
+ if err != nil {
+ return err
+ }
+ h.notifyConns(res.WebPath, &State{
+ Event: EventModify,
+ Path: res.WebPath,
+ Payload: payload,
})
- filepath.Walk(fspath, func(path string, info os.FileInfo, err error) error {
- log.Println("walking", path)
- if err != nil {
- log.Println("error while walking into new directory: ", err)
- } else {
- if info.IsDir() {
- h.dirAdded(path)
- } else {
- h.fileAdded(path)
- }
- }
- return nil
- })
+ return nil
}
-func (h *MetronomeHandler) dirModified(path string) {
- fspath, err := h.toFsPath(path)
- if err != nil {
- panic(err)
+func (h *MetronomeHandler) resRemoved(res *Resource) error {
+ h.notifyConns(res.WebPath, &State{
+ Event: EventDelete,
+ Path: res.WebPath,
+ })
+ if res.WebPath != "/" {
+ parent, err := h.resFromWebPath(filepath.Dir(res.WebPath))
+ if err != nil {
+ return err
+ }
+ return h.resModified(parent)
}
- path = givePathSuffix(path)
- listing, err := dirListing(fspath)
- if err != nil {
- log.Printf("error getting listing of %v: %v", path, err)
- return
- }
- h.notifyConns(path, &State{
- Event: EventDelete,
- Path: path,
- Payload: listing,
- })
+ return nil
}
-
-func (h *MetronomeHandler) dirRemoved(path string) {
- path = givePathSuffix(path)
- for name, _ := range h.subs {
- if strings.HasPrefix(name, path) {
- if name[len(name)-1] == os.PathSeparator {
- h.dirRemoved(name)
- } else {
- h.fileRemoved(name)
- }
- }
- }
- h.notifyConns(path, &State{
- Event: EventDelete,
- Path: path,
- })
-}