Restructured to have subpackages, added server communication

This commit is contained in:
Aaron Lindsay 2013-02-20 23:43:01 -05:00
parent b193814371
commit 3fc3e3a963
10 changed files with 201 additions and 35 deletions

6
api.go Normal file
View File

@ -0,0 +1,6 @@
package asink
type APIResponse struct {
Status string //may be 'error' or 'success'
Explanation string
}

View File

@ -1,13 +1,20 @@
package main package main
import ( import (
"asink"
"asink/util"
"bytes"
"code.google.com/p/goconf/conf" "code.google.com/p/goconf/conf"
"database/sql" "database/sql"
"encoding/json"
"flag" "flag"
"fmt" "fmt"
"net/http"
"io/ioutil"
"os" "os"
"os/user" "os/user"
"path" "path"
"strconv"
) )
type AsinkGlobals struct { type AsinkGlobals struct {
@ -17,6 +24,8 @@ type AsinkGlobals struct {
tmpDir string tmpDir string
db *sql.DB db *sql.DB
storage Storage storage Storage
server string
port int
} }
var globals AsinkGlobals var globals AsinkGlobals
@ -56,15 +65,15 @@ func main() {
globals.tmpDir, err = config.GetString("local", "tmpdir") globals.tmpDir, err = config.GetString("local", "tmpdir")
//make sure all the necessary directories exist //make sure all the necessary directories exist
err = ensureDirExists(globals.syncDir) err = util.EnsureDirExists(globals.syncDir)
if err != nil { if err != nil {
panic(err) panic(err)
} }
err = ensureDirExists(globals.cacheDir) err = util.EnsureDirExists(globals.cacheDir)
if err != nil { if err != nil {
panic(err) panic(err)
} }
err = ensureDirExists(globals.tmpDir) err = util.EnsureDirExists(globals.tmpDir)
if err != nil { if err != nil {
panic(err) panic(err)
} }
@ -76,7 +85,10 @@ func main() {
fmt.Println(globals.storage) fmt.Println(globals.storage)
//TODO FIXME REMOVEME //TODO FIXME REMOVEME
fileUpdates := make(chan *Event) globals.server, err = config.GetString("server", "host")
globals.port, err = config.GetInt("server", "port")
fileUpdates := make(chan *asink.Event)
go StartWatching(globals.syncDir, fileUpdates) go StartWatching(globals.syncDir, fileUpdates)
globals.db, err = GetAndInitDB(config) globals.db, err = GetAndInitDB(config)
@ -91,7 +103,7 @@ func main() {
} }
} }
func ProcessEvent(globals AsinkGlobals, event *Event) { func ProcessEvent(globals AsinkGlobals, event *asink.Event) {
//add to database //add to database
err := DatabaseAddEvent(globals.db, event) err := DatabaseAddEvent(globals.db, event)
if err != nil { if err != nil {
@ -100,11 +112,11 @@ func ProcessEvent(globals AsinkGlobals, event *Event) {
if event.IsUpdate() { if event.IsUpdate() {
//copy to tmp //copy to tmp
tmpfilename, err := copyToTmp(event.Path, globals.tmpDir) tmpfilename, err := util.CopyToTmp(event.Path, globals.tmpDir)
if err != nil { if err != nil {
panic(err) panic(err)
} }
event.Status |= COPIED_TO_TMP event.Status |= asink.COPIED_TO_TMP
//get the file's hash //get the file's hash
hash, err := HashFile(tmpfilename) hash, err := HashFile(tmpfilename)
@ -112,7 +124,7 @@ func ProcessEvent(globals AsinkGlobals, event *Event) {
if err != nil { if err != nil {
panic(err) panic(err)
} }
event.Status |= HASHED event.Status |= asink.HASHED
//rename to local cache w/ filename=hash //rename to local cache w/ filename=hash
err = os.Rename(tmpfilename, path.Join(globals.cacheDir, event.Hash)) err = os.Rename(tmpfilename, path.Join(globals.cacheDir, event.Hash))
@ -122,7 +134,7 @@ func ProcessEvent(globals AsinkGlobals, event *Event) {
panic(err) panic(err)
} }
} }
event.Status |= CACHED event.Status |= asink.CACHED
//update database //update database
err = DatabaseUpdateEvent(globals.db, event) err = DatabaseUpdateEvent(globals.db, event)
@ -135,7 +147,7 @@ func ProcessEvent(globals AsinkGlobals, event *Event) {
if err != nil { if err != nil {
panic(err) panic(err)
} }
event.Status |= UPLOADED event.Status |= asink.UPLOADED
//update database again //update database again
err = DatabaseUpdateEvent(globals.db, event) err = DatabaseUpdateEvent(globals.db, event)
@ -144,7 +156,46 @@ func ProcessEvent(globals AsinkGlobals, event *Event) {
} }
} }
fmt.Println(event)
//TODO notify server of new file //finally, send it off to the server
url := "http://" + globals.server + ":" + strconv.Itoa(int(globals.port)) + "/events/"
//construct json payload
events := asink.EventList{
Events: []*asink.Event{event},
}
b, err := json.Marshal(events)
if err != nil {
panic(err)
}
fmt.Println(string(b))
//actually make the request
resp, err := http.Post(url, "application/json", bytes.NewReader(b))
if err != nil {
panic(err)
}
defer resp.Body.Close()
//check to make sure request succeeded
body, err := ioutil.ReadAll(resp.Body)
if err != nil {
panic(err)
}
var apistatus asink.APIResponse
err = json.Unmarshal(body, &apistatus)
if err != nil {
panic(err) //TODO handle sensibly
}
if apistatus.Status != "success" {
panic("Status not success") //TODO handle sensibly
}
fmt.Println(apistatus)
event.Status |= asink.ON_SERVER
err = DatabaseUpdateEvent(globals.db, event)
if err != nil {
panic(err)
}
} }

View File

@ -1,6 +1,7 @@
package main package main
import ( import (
"asink"
"code.google.com/p/goconf/conf" "code.google.com/p/goconf/conf"
"database/sql" "database/sql"
"errors" "errors"
@ -43,12 +44,12 @@ func GetAndInitDB(config *conf.ConfigFile) (*sql.DB, error) {
return db, nil return db, nil
} }
func DatabaseAddEvent(db *sql.DB, e *Event) error { func DatabaseAddEvent(db *sql.DB, e *asink.Event) error {
tx, err := db.Begin() tx, err := db.Begin()
if err != nil { if err != nil {
return err return err
} }
result, err := tx.Exec("INSERT INTO events (id, type, status, path, hash, timestamp, permissions) VALUES (?,?,?,?,?,?,?);", e.Id, e.Type, e.Status, e.Path, e.Hash, e.Timestamp.UnixNano(), 0) result, err := tx.Exec("INSERT INTO events (id, type, status, path, hash, timestamp, permissions) VALUES (?,?,?,?,?,?,?);", e.Id, e.Type, e.Status, e.Path, e.Hash, e.Timestamp, 0)
if err != nil { if err != nil {
return err return err
} }
@ -66,7 +67,7 @@ func DatabaseAddEvent(db *sql.DB, e *Event) error {
return nil return nil
} }
func DatabaseUpdateEvent(db *sql.DB, e *Event) error { func DatabaseUpdateEvent(db *sql.DB, e *asink.Event) error {
if !e.InDB { if !e.InDB {
return errors.New("Attempting to update an event in the database which hasn't been previously added.") return errors.New("Attempting to update an event in the database which hasn't been previously added.")
} }
@ -75,7 +76,7 @@ func DatabaseUpdateEvent(db *sql.DB, e *Event) error {
if err != nil { if err != nil {
return err return err
} }
result, err := tx.Exec("UPDATE events SET id=?, type=?, status=?, path=?, hash=?, timestamp=?, permissions=? WHERE localid=?;", e.Id, e.Type, e.Status, e.Path, e.Hash, e.Timestamp.UnixNano(), 0, e.LocalId) result, err := tx.Exec("UPDATE events SET id=?, type=?, status=?, path=?, hash=?, timestamp=?, permissions=? WHERE localid=?;", e.Id, e.Type, e.Status, e.Path, e.Hash, e.Timestamp, 0, e.LocalId)
if err != nil { if err != nil {
return err return err
} }

View File

@ -1,6 +1,7 @@
package main package main
import ( import (
"asink/util"
"code.google.com/p/goconf/conf" "code.google.com/p/goconf/conf"
"errors" "errors"
"io" "io"
@ -24,11 +25,11 @@ func NewLocalStorage(config *conf.ConfigFile) (*LocalStorage, error) {
ls.tmpSubdir = path.Join(storageDir, ".asink-tmpdir") ls.tmpSubdir = path.Join(storageDir, ".asink-tmpdir")
//make sure the base directory and tmp subdir exist //make sure the base directory and tmp subdir exist
err = ensureDirExists(ls.storageDir) err = util.EnsureDirExists(ls.storageDir)
if err != nil { if err != nil {
return nil, err return nil, err
} }
err = ensureDirExists(ls.tmpSubdir) err = util.EnsureDirExists(ls.tmpSubdir)
if err != nil { if err != nil {
return nil, err return nil, err
} }
@ -37,7 +38,7 @@ func NewLocalStorage(config *conf.ConfigFile) (*LocalStorage, error) {
} }
func (ls *LocalStorage) Put(filename string, hash string) (e error) { func (ls *LocalStorage) Put(filename string, hash string) (e error) {
tmpfile, err := copyToTmp(filename, ls.tmpSubdir) tmpfile, err := util.CopyToTmp(filename, ls.tmpSubdir)
if err != nil { if err != nil {
return err return err
} }

View File

@ -1,13 +1,14 @@
package main package main
import ( import (
"asink"
"github.com/howeyc/fsnotify" "github.com/howeyc/fsnotify"
"os" "os"
"path/filepath" "path/filepath"
"time" "time"
) )
func StartWatching(watchDir string, fileUpdates chan *Event) { func StartWatching(watchDir string, fileUpdates chan *asink.Event) {
watcher, err := fsnotify.NewWatcher() watcher, err := fsnotify.NewWatcher()
if err != nil { if err != nil {
panic("Failed to create fsnotify watcher") panic("Failed to create fsnotify watcher")
@ -37,18 +38,18 @@ func StartWatching(watchDir string, fileUpdates chan *Event) {
continue continue
} }
event := new(Event) event := new(asink.Event)
if ev.IsCreate() || ev.IsModify() { if ev.IsCreate() || ev.IsModify() {
event.Type = UPDATE event.Type = asink.UPDATE
} else if ev.IsDelete() || ev.IsRename() { } else if ev.IsDelete() || ev.IsRename() {
event.Type = DELETE event.Type = asink.DELETE
} else { } else {
panic("Unknown fsnotify event type") panic("Unknown fsnotify event type")
} }
event.Status = NOTICED event.Status = asink.NOTICED
event.Path = ev.Name event.Path = ev.Name
event.Timestamp = time.Now() event.Timestamp = time.Now().UnixNano()
fileUpdates <- event fileUpdates <- event

View File

@ -1,8 +1,4 @@
package main package asink
import (
"time"
)
//event type //event type
type EventType uint32 type EventType uint32
@ -31,8 +27,8 @@ type Event struct {
Status EventStatus Status EventStatus
Path string Path string
Hash string Hash string
Timestamp time.Time Timestamp int64
InDB bool //defaults to false InDB bool `json:"-"` //defaults to false. Omitted from json marshalling.
} }
func (e Event) IsUpdate() bool { func (e Event) IsUpdate() bool {
@ -42,3 +38,7 @@ func (e Event) IsUpdate() bool {
func (e Event) IsDelete() bool { func (e Event) IsDelete() bool {
return e.Type&DELETE == DELETE return e.Type&DELETE == DELETE
} }
type EventList struct {
Events []*Event
}

106
server/server.go Normal file
View File

@ -0,0 +1,106 @@
package main
import (
"asink"
"encoding/json"
"flag"
"fmt"
"io/ioutil"
"net/http"
"regexp"
"strconv"
)
var eventsRegexp *regexp.Regexp
var port int = 8080
func init() {
const port_usage = "Port on which to serve HTTP API"
flag.IntVar(&port, "port", 8080, port_usage)
flag.IntVar(&port, "p", 8080, port_usage+" (shorthand)")
eventsRegexp = regexp.MustCompile("^/events/([0-9]+)$")
}
func main() {
flag.Parse()
http.HandleFunc("/", rootHandler)
http.HandleFunc("/events", eventHandler)
http.HandleFunc("/events/", eventHandler)
//TODO replace with http://golang.org/pkg/net/http/#ListenAndServeTLS
err := http.ListenAndServe(fmt.Sprintf(":%d", port), nil)
if err != nil {
fmt.Println(err)
}
}
func rootHandler(w http.ResponseWriter, r *http.Request) {
fmt.Fprintf(w, "You're probably looking for /events/")
}
func getEvents(w http.ResponseWriter, r *http.Request, nextEvent uint64) {
fmt.Fprintf(w, strconv.FormatUint(nextEvent, 10))
}
func putEvents(w http.ResponseWriter, r *http.Request) {
var events asink.EventList
var error_occurred bool = false
var error_message string = ""
defer func() {
var apiresponse asink.APIResponse
if error_occurred {
apiresponse = asink.APIResponse{
Status: "error",
Explanation: error_message,
}
} else {
apiresponse = asink.APIResponse{
Status: "success",
}
}
b, err := json.Marshal(apiresponse)
if err != nil {
panic(err)
}
w.Write(b)
}()
body, err := ioutil.ReadAll(r.Body)
if err != nil {
error_message = err.Error()
return
}
err = json.Unmarshal(body, &events)
if err != nil {
error_message = err.Error()
return
}
for _, event := range events.Events {
fmt.Println(event)
}
}
func eventHandler(w http.ResponseWriter, r *http.Request) {
if r.Method == "GET" {
//if GET, return any events later than (and including) the event id passed in
if sm := eventsRegexp.FindStringSubmatch(r.RequestURI); sm != nil {
i, err := strconv.ParseUint(sm[1], 10, 64)
if err != nil {
//TODO display error message here instead
fmt.Printf("ERROR parsing " + sm[1] + "\n")
getEvents(w, r, 0)
} else {
getEvents(w, r, i)
}
} else {
getEvents(w, r, 0)
}
} else if r.Method == "POST" {
putEvents(w, r)
} else {
fmt.Fprintf(w, "You f-ed up.")
}
}

View File

@ -1,4 +1,4 @@
package main package util
import ( import (
"io" "io"
@ -7,7 +7,7 @@ import (
"path" "path"
) )
func ensureDirExists(dir string) error { func EnsureDirExists(dir string) error {
_, err := os.Lstat(dir) _, err := os.Lstat(dir)
if err != nil { if err != nil {
fi, err := os.Lstat(path.Dir(dir)) fi, err := os.Lstat(path.Dir(dir))
@ -22,7 +22,7 @@ func ensureDirExists(dir string) error {
return nil return nil
} }
func copyToTmp(src string, tmpdir string) (string, error) { func CopyToTmp(src string, tmpdir string) (string, error) {
infile, err := os.Open(src) infile, err := os.Open(src)
if err != nil { if err != nil {
return "", err return "", err