# HG changeset patch # User sqwishy # Date 1383427194 25200 # Sat Nov 02 14:19:54 2013 -0700 # Node ID d1eb5cac620246d5a852a97487425842c7bb09a0 # Parent d4bb60adc9acb0cbee28e1ee4f4fcbc9e937dcdb refactoring diff --git a/metronome.go b/metronome.go --- a/metronome.go +++ b/metronome.go @@ -1,5 +1,8 @@ package main +import "fmt" +import "os" +import "io" import "io/ioutil" const ( @@ -8,10 +11,9 @@ 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 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 @@ } 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 + } +} diff --git a/server.go b/server.go --- a/server.go +++ b/server.go @@ -3,7 +3,6 @@ import "net/http" import "os" import "log" -import "io/ioutil" import "fmt" import "strings" import "path/filepath" @@ -50,79 +49,45 @@ 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 @@ 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 @@ 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, - }) -}