diff --git a/Makefile b/Makefile index e9e7213..8c74a12 100644 --- a/Makefile +++ b/Makefile @@ -6,23 +6,19 @@ BUILDDIR=out CC=6g LD=6l -#${BUILDDIR}/exp/fsnotify/fsnotify.linux.6 # Order specific! OBJS=${BUILDDIR}/exp/fsnotify/fsnotify.6 \ - ${BUILDDIR}/livereload/http_server.6 \ ${BUILDDIR}/livereload.6 all: livereload init: - mkdir -p ${BUILDDIR}/livereload - -#tmp build -${BUILDDIR}/exp/fsnotify/fsnotify.6: mkdir -p ${BUILDDIR}/exp/fsnotify - $(CC) -o ${BUILDDIR}/exp/fsnotify/fsnotify.6 src/exp/fsnotify/fsnotify.go src/exp/fsnotify/fsnotify_bsd.go -${BUILDDIR}/%.6 : src/%.go ${BUILDDIR}/exp/fsnotify/fsnotify.6 init +${BUILDDIR}/exp/fsnotify/fsnotify.6: init + $(CC) -o ${BUILDDIR}/exp/fsnotify.6 src/exp/fsnotify/fsnotify.go src/exp/fsnotify/fsnotify_bsd.go + +${BUILDDIR}/%.6 : src/%.go ${BUILDDIR}/exp/fsnotify/fsnotify.6 $(CC) -o $@ -I ${BUILDDIR} $< livereload: ${OBJS} diff --git a/src/exp/fsnotify/Makefile b/src/exp/fsnotify/Makefile deleted file mode 100644 index 9f3a8b8..0000000 --- a/src/exp/fsnotify/Makefile +++ /dev/null @@ -1,22 +0,0 @@ -include $(GOROOT)/src/Make.inc - -TARG=exp/fsnotify - -GOFILES=\ - fsnotify.go - -GOFILES_linux=\ - fsnotify_linux.go\ - -GOFILES_freebsd=\ - fsnotify_bsd.go\ - -GOFILES_openbsd=\ - fsnotify_bsd.go\ - -GOFILES_darwin=\ - fsnotify_bsd.go\ - -GOFILES+=$(GOFILES_$(GOOS)) - -include $(GOROOT)/src/Make.pkg diff --git a/src/exp/fsnotify/fsnotify_linux.go b/src/exp/fsnotify/fsnotify_linux.go deleted file mode 100644 index 28e29cf..0000000 --- a/src/exp/fsnotify/fsnotify_linux.go +++ /dev/null @@ -1,263 +0,0 @@ -// Copyright 2010 The Go Authors. All rights reserved. -// Use of this source code is governed by a BSD-style -// license that can be found in the LICENSE file. - -/* -Package fsnotify implements a wrapper for the Linux inotify system. - -Example: - watcher, err := fsotify.NewWatcher() - if err != nil { - log.Fatal(err) - } - err = watcher.Watch("/tmp") - if err != nil { - log.Fatal(err) - } - for { - select { - case ev := <-watcher.Event: - log.Println("event:", ev) - case err := <-watcher.Error: - log.Println("error:", err) - } - } - -*/ -package fsnotify - -import ( - "errors" - "fmt" - "os" - "strings" - "syscall" - "unsafe" -) - -type FileEvent struct { - mask uint32 // Mask of events - cookie uint32 // Unique cookie associating related events (for rename(2)) - Name string // File name (optional) -} - -// IsCreate reports whether the FileEvent was triggerd by a creation -func (e *FileEvent) IsCreate() bool { return (e.mask & IN_CREATE) == IN_CREATE } - -// IsDelete reports whether the FileEvent was triggerd by a delete -func (e *FileEvent) IsDelete() bool { - return (e.mask&IN_DELETE_SELF) == IN_DELETE_SELF || (e.mask&IN_DELETE) == IN_DELETE -} - -// IsModify reports whether the FileEvent was triggerd by a file modification -func (e *FileEvent) IsModify() bool { return (e.mask & IN_MODIFY) == IN_MODIFY } - -// IsAttribute reports whether the FileEvent was triggerd by a change of attributes -func (e *FileEvent) IsAttribute() bool { return (e.mask & IN_ATTRIB) == IN_ATTRIB } - -// IsRename reports whether the FileEvent was triggerd by a change name -func (e *FileEvent) IsRename() bool { return (e.mask & IN_MOVE_SELF) == IN_MOVE_SELF } - -type watch struct { - wd uint32 // Watch descriptor (as returned by the inotify_add_watch() syscall) - flags uint32 // inotify flags of this watch (see inotify(7) for the list of valid flags) -} - -type Watcher struct { - fd int // File descriptor (as returned by the inotify_init() syscall) - watches map[string]*watch // Map of inotify watches (key: path) - paths map[int]string // Map of watched paths (key: watch descriptor) - Error chan error // Errors are sent on this channel - Event chan *FileEvent // Events are returned on this channel - done chan bool // Channel for sending a "quit message" to the reader goroutine - isClosed bool // Set to true when Close() is first called -} - -// NewWatcher creates and returns a new inotify instance using inotify_init(2) -func NewWatcher() (*Watcher, error) { - fd, errno := syscall.InotifyInit() - if fd == -1 { - return nil, os.NewSyscallError("inotify_init", errno) - } - w := &Watcher{ - fd: fd, - watches: make(map[string]*watch), - paths: make(map[int]string), - Event: make(chan *FileEvent), - Error: make(chan error), - done: make(chan bool, 1), - } - - go w.readEvents() - return w, nil -} - -// Close closes an inotify watcher instance -// It sends a message to the reader goroutine to quit and removes all watches -// associated with the inotify instance -func (w *Watcher) Close() error { - if w.isClosed { - return nil - } - w.isClosed = true - - // Send "quit" message to the reader goroutine - w.done <- true - for path := range w.watches { - w.RemoveWatch(path) - } - - return nil -} - -// AddWatch adds path to the watched file set. -// The flags are interpreted as described in inotify_add_watch(2). -func (w *Watcher) addWatch(path string, flags uint32) error { - if w.isClosed { - return errors.New("inotify instance already closed") - } - - watchEntry, found := w.watches[path] - if found { - watchEntry.flags |= flags - flags |= syscall.IN_MASK_ADD - } - wd, errno := syscall.InotifyAddWatch(w.fd, path, flags) - if wd == -1 { - return &os.PathError{"inotify_add_watch", path, os.Errno(errno)} - } - - if !found { - w.watches[path] = &watch{wd: uint32(wd), flags: flags} - w.paths[wd] = path - } - return nil -} - -// Watch adds path to the watched file set, watching all events. -func (w *Watcher) Watch(path string) error { - return w.addWatch(path, OS_AGNOSTIC_EVENTS) -} - -// RemoveWatch removes path from the watched file set. -func (w *Watcher) RemoveWatch(path string) error { - watch, ok := w.watches[path] - if !ok { - return errors.New(fmt.Sprintf("can't remove non-existent inotify watch for: %s", path)) - } - success, errno := syscall.InotifyRmWatch(w.fd, watch.wd) - if success == -1 { - return os.NewSyscallError("inotify_rm_watch", errno) - } - delete(w.watches, path) - return nil -} - -// readEvents reads from the inotify file descriptor, converts the -// received events into Event objects and sends them via the Event channel -func (w *Watcher) readEvents() { - var ( - buf [syscall.SizeofInotifyEvent * 4096]byte // Buffer for a maximum of 4096 raw events - n int // Number of bytes read with read() - errno int // Syscall errno - ) - - for { - n, errno = syscall.Read(w.fd, buf[0:]) - // See if there is a message on the "done" channel - var done bool - select { - case done = <-w.done: - default: - } - - // If EOF or a "done" message is received - if n == 0 || done { - errno := syscall.Close(w.fd) - if errno == -1 { - w.Error <- os.NewSyscallError("close", errno) - } - close(w.Event) - close(w.Error) - return - } - if n < 0 { - w.Error <- os.NewSyscallError("read", errno) - continue - } - if n < syscall.SizeofInotifyEvent { - w.Error <- errors.New("inotify: short read in readEvents()") - continue - } - - var offset uint32 = 0 - // We don't know how many events we just read into the buffer - // While the offset points to at least one whole event... - for offset <= uint32(n-syscall.SizeofInotifyEvent) { - // Point "raw" to the event in the buffer - raw := (*syscall.InotifyEvent)(unsafe.Pointer(&buf[offset])) - event := new(FileEvent) - event.mask = uint32(raw.Mask) - event.cookie = uint32(raw.Cookie) - nameLen := uint32(raw.Len) - // If the event happened to the watched directory or the watched file, the kernel - // doesn't append the filename to the event, but we would like to always fill the - // the "Name" field with a valid filename. We retrieve the path of the watch from - // the "paths" map. - event.Name = w.paths[int(raw.Wd)] - if nameLen > 0 { - // Point "bytes" at the first byte of the filename - bytes := (*[syscall.PathMax]byte)(unsafe.Pointer(&buf[offset+syscall.SizeofInotifyEvent])) - // The filename is padded with NUL bytes. TrimRight() gets rid of those. - event.Name += "/" + strings.TrimRight(string(bytes[0:nameLen]), "\000") - } - // Send the events that are not ignored on the events channel - if (event.mask & IN_IGNORED) == 0 { - w.Event <- event - } - - // Move to the next event in the buffer - offset += syscall.SizeofInotifyEvent + nameLen - } - } -} - -const ( - // Options for inotify_init() are not exported - // IN_CLOEXEC uint32 = syscall.IN_CLOEXEC - // IN_NONBLOCK uint32 = syscall.IN_NONBLOCK - - // Options for AddWatch - IN_DONT_FOLLOW uint32 = syscall.IN_DONT_FOLLOW - IN_ONESHOT uint32 = syscall.IN_ONESHOT - IN_ONLYDIR uint32 = syscall.IN_ONLYDIR - - // The "IN_MASK_ADD" option is not exported, as AddWatch - // adds it automatically, if there is already a watch for the given path - // IN_MASK_ADD uint32 = syscall.IN_MASK_ADD - - // Events - IN_ACCESS uint32 = syscall.IN_ACCESS - IN_ALL_EVENTS uint32 = syscall.IN_ALL_EVENTS - IN_ATTRIB uint32 = syscall.IN_ATTRIB - IN_CLOSE uint32 = syscall.IN_CLOSE - IN_CLOSE_NOWRITE uint32 = syscall.IN_CLOSE_NOWRITE - IN_CLOSE_WRITE uint32 = syscall.IN_CLOSE_WRITE - IN_CREATE uint32 = syscall.IN_CREATE - IN_DELETE uint32 = syscall.IN_DELETE - IN_DELETE_SELF uint32 = syscall.IN_DELETE_SELF - IN_MODIFY uint32 = syscall.IN_MODIFY - IN_MOVE uint32 = syscall.IN_MOVE - IN_MOVED_FROM uint32 = syscall.IN_MOVED_FROM - IN_MOVED_TO uint32 = syscall.IN_MOVED_TO - IN_MOVE_SELF uint32 = syscall.IN_MOVE_SELF - IN_OPEN uint32 = syscall.IN_OPEN - - OS_AGNOSTIC_EVENTS = IN_CREATE | IN_ATTRIB | IN_MODIFY | IN_MOVE_SELF | IN_DELETE - - // Special events - IN_ISDIR uint32 = syscall.IN_ISDIR - IN_IGNORED uint32 = syscall.IN_IGNORED - IN_Q_OVERFLOW uint32 = syscall.IN_Q_OVERFLOW - IN_UNMOUNT uint32 = syscall.IN_UNMOUNT -) diff --git a/src/exp/fsnotify/fsnotify_test.go b/src/exp/fsnotify/fsnotify_test.go deleted file mode 100644 index 13fb762..0000000 --- a/src/exp/fsnotify/fsnotify_test.go +++ /dev/null @@ -1,301 +0,0 @@ -// Copyright 2010 The Go Authors. All rights reserved. -// Use of this source code is governed by a BSD-style -// license that can be found in the LICENSE file. - -package fsnotify - -import ( - "os" - "os/exec" - "testing" - "time" -) - -func TestFsnotifyDirOnly(t *testing.T) { - // Create an fsnotify watcher instance and initialize it - watcher, err := NewWatcher() - if err != nil { - t.Fatalf("NewWatcher() failed: %s", err) - } - - const testDir string = "_test" - - // Add a watch for testDir - err = watcher.Watch(testDir) - if err != nil { - t.Fatalf("Watcher.Watch() failed: %s", err) - } - - // Receive errors on the error channel on a separate goroutine - go func() { - for err := range watcher.Error { - t.Fatalf("error received: %s", err) - } - }() - - const testFile string = "_test/TestFsnotifyEvents.testfile" - - // Receive events on the event channel on a separate goroutine - eventstream := watcher.Event - var createReceived = 0 - var modifyReceived = 0 - var deleteReceived = 0 - done := make(chan bool) - go func() { - for event := range eventstream { - // Only count relevant events - if event.Name == testDir || event.Name == testFile { - t.Logf("event received: %s", event) - if event.IsDelete() { - deleteReceived++ - } - if event.IsModify() { - modifyReceived++ - } - if event.IsDelete() { - createReceived++ - } - } else { - t.Logf("unexpected event received: %s", event) - } - } - done <- true - }() - - // Create a file - // This should add at least one event to the fsnotify event queue - var f *os.File - f, err = os.OpenFile(testFile, os.O_WRONLY|os.O_CREATE, 0666) - if err != nil { - t.Fatalf("creating test file failed: %s", err) - } - f.Sync() - time.Sleep(200e6) // give system time to sync write change before delete - - f.WriteString("data") - f.Sync() - f.Close() - - os.Remove(testFile) - - // We expect this event to be received almost immediately, but let's wait 500 ms to be sure - time.Sleep(500e6) // 500 ms - if createReceived == 0 { - t.Fatal("fsnotify create events have not been received after 500 ms") - } - if modifyReceived == 0 { - t.Fatal("fsnotify modify events have not been received after 500 ms") - } - if deleteReceived == 0 { - t.Fatal("fsnotify delete events have not been received after 500 ms") - } - - // Try closing the fsnotify instance - t.Log("calling Close()") - watcher.Close() - t.Log("waiting for the event channel to become closed...") - select { - case <-done: - t.Log("event channel closed") - case <-time.After(1e9): - t.Fatal("event stream was not closed after 1 second") - } -} - -func TestFsnotifyRename(t *testing.T) { - // Create an fsnotify watcher instance and initialize it - watcher, err := NewWatcher() - if err != nil { - t.Fatalf("NewWatcher() failed: %s", err) - } - - const testDir string = "_test" - - // Add a watch for testDir - err = watcher.Watch(testDir) - if err != nil { - t.Fatalf("Watcher.Watch() failed: %s", err) - } - - // Receive errors on the error channel on a separate goroutine - go func() { - for err := range watcher.Error { - t.Fatalf("error received: %s", err) - } - }() - - const testFile string = "_test/TestFsnotifyEvents.testfile" - const testFileRenamed string = "_test/TestFsnotifyEvents.testfileRenamed" - - // Receive events on the event channel on a separate goroutine - eventstream := watcher.Event - var renameReceived = 0 - done := make(chan bool) - go func() { - for event := range eventstream { - // Only count relevant events - if event.Name == testDir || event.Name == testFile || event.Name == testFileRenamed { - if event.IsRename() { - renameReceived++ - } - t.Logf("event received: %s", event) - } else { - t.Logf("unexpected event received: %s", event) - } - } - done <- true - }() - - // Create a file - // This should add at least one event to the fsnotify event queue - var f *os.File - f, err = os.OpenFile(testFile, os.O_WRONLY|os.O_CREATE, 0666) - if err != nil { - t.Fatalf("creating test file failed: %s", err) - } - f.Sync() - - f.WriteString("data") - f.Sync() - f.Close() - - // Add a watch for testFile - err = watcher.Watch(testFile) - if err != nil { - t.Fatalf("Watcher.Watch() failed: %s", err) - } - - cmd := exec.Command("mv", testFile, testFileRenamed) - err = cmd.Run() - if err != nil { - t.Fatalf("rename failed: %s", err) - } - - // We expect this event to be received almost immediately, but let's wait 500 ms to be sure - time.Sleep(500e6) // 500 ms - if renameReceived == 0 { - t.Fatal("fsnotify rename events have not been received after 500 ms") - } - - // Try closing the fsnotify instance - t.Log("calling Close()") - watcher.Close() - t.Log("waiting for the event channel to become closed...") - select { - case <-done: - t.Log("event channel closed") - case <-time.After(1e9): - t.Fatal("event stream was not closed after 1 second") - } - - os.Remove(testFileRenamed) -} - -func TestFsnotifyAttrib(t *testing.T) { - // Create an fsnotify watcher instance and initialize it - watcher, err := NewWatcher() - if err != nil { - t.Fatalf("NewWatcher() failed: %s", err) - } - - const testDir string = "_test" - - // Add a watch for testDir - err = watcher.Watch(testDir) - if err != nil { - t.Fatalf("Watcher.Watch() failed: %s", err) - } - - // Receive errors on the error channel on a separate goroutine - go func() { - for err := range watcher.Error { - t.Fatalf("error received: %s", err) - } - }() - - const testFile string = "_test/TestFsnotifyEvents.testfile" - - // Receive events on the event channel on a separate goroutine - eventstream := watcher.Event - var attribReceived = 0 - done := make(chan bool) - go func() { - for event := range eventstream { - // Only count relevant events - if event.Name == testDir || event.Name == testFile { - if event.IsAttribute() { - attribReceived++ - } - t.Logf("event received: %s", event) - } else { - t.Logf("unexpected event received: %s", event) - } - } - done <- true - }() - - // Create a file - // This should add at least one event to the fsnotify event queue - var f *os.File - f, err = os.OpenFile(testFile, os.O_WRONLY|os.O_CREATE, 0666) - if err != nil { - t.Fatalf("creating test file failed: %s", err) - } - f.Sync() - - f.WriteString("data") - f.Sync() - f.Close() - - // Add a watch for testFile - err = watcher.Watch(testFile) - if err != nil { - t.Fatalf("Watcher.Watch() failed: %s", err) - } - - cmd := exec.Command("chmod", "0700", testFile) - err = cmd.Run() - if err != nil { - t.Fatalf("chmod failed: %s", err) - } - - // We expect this event to be received almost immediately, but let's wait 500 ms to be sure - time.Sleep(500e6) // 500 ms - if attribReceived == 0 { - t.Fatal("fsnotify attribute events have not received after 500 ms") - } - - // Try closing the fsnotify instance - t.Log("calling Close()") - watcher.Close() - t.Log("waiting for the event channel to become closed...") - select { - case <-done: - t.Log("event channel closed") - case <-time.After(1e9): - t.Fatal("event stream was not closed after 1 second") - } - - os.Remove(testFile) -} - -func TestFsnotifyClose(t *testing.T) { - watcher, _ := NewWatcher() - watcher.Close() - - done := false - go func() { - watcher.Close() - done = true - }() - - time.Sleep(50e6) // 50 ms - if !done { - t.Fatal("double Close() test failed: second Close() call didn't return") - } - - err := watcher.Watch("_test") - if err == nil { - t.Fatal("expected error on Watch() after Close(), got nil") - } -} diff --git a/src/exp/fsnotify/pkg_Makefile b/src/exp/fsnotify/pkg_Makefile deleted file mode 100644 index bdcc8f1..0000000 --- a/src/exp/fsnotify/pkg_Makefile +++ /dev/null @@ -1,306 +0,0 @@ -# Copyright 2009 The Go Authors. All rights reserved. -# Use of this source code is governed by a BSD-style -# license that can be found in the LICENSE file. - -# After editing the DIRS= list or adding imports to any Go files -# in any of those directories, run: -# -# ./deps.bash -# -# to rebuild the dependency information in Make.deps. - -include ../Make.inc - -all: install - -DIRS=\ - archive/tar\ - archive/zip\ - bufio\ - bytes\ - compress/bzip2\ - compress/flate\ - compress/gzip\ - compress/lzw \ - compress/zlib\ - container/heap\ - container/list\ - container/ring\ - crypto\ - crypto/aes\ - crypto/blowfish\ - crypto/bcrypt\ - crypto/cast5\ - crypto/cipher\ - crypto/des\ - crypto/dsa\ - crypto/ecdsa\ - crypto/elliptic\ - crypto/hmac\ - crypto/md4\ - crypto/md5\ - crypto/ocsp\ - crypto/openpgp\ - crypto/openpgp/armor\ - crypto/openpgp/elgamal\ - crypto/openpgp/error\ - crypto/openpgp/packet\ - crypto/openpgp/s2k\ - crypto/rand\ - crypto/rc4\ - crypto/ripemd160\ - crypto/rsa\ - crypto/sha1\ - crypto/sha256\ - crypto/sha512\ - crypto/subtle\ - crypto/tls\ - crypto/twofish\ - crypto/x509\ - crypto/x509/pkix\ - crypto/xtea\ - debug/dwarf\ - debug/macho\ - debug/elf\ - debug/gosym\ - debug/pe\ - encoding/ascii85\ - encoding/asn1\ - encoding/base32\ - encoding/base64\ - encoding/binary\ - encoding/csv\ - encoding/git85\ - encoding/gob\ - encoding/hex\ - encoding/json\ - encoding/pem\ - encoding/xml\ - errors\ - exp/ebnf\ - exp/ebnflint\ - exp/gotype\ - exp/norm\ - exp/ssh\ - exp/spdy\ - exp/sql\ - exp/sql/driver\ - exp/types\ - expvar\ - flag\ - fmt\ - go/ast\ - go/build\ - go/doc\ - go/parser\ - go/printer\ - go/scanner\ - go/token\ - hash\ - hash/adler32\ - hash/crc32\ - hash/crc64\ - hash/fnv\ - html\ - html/template\ - image\ - image/bmp\ - image/color\ - image/draw\ - image/gif\ - image/jpeg\ - image/png\ - image/tiff\ - index/suffixarray\ - io\ - io/ioutil\ - log\ - log/syslog\ - math\ - math/big\ - math/cmplx\ - math/rand\ - mime\ - mime/multipart\ - net\ - net/dict\ - net/http\ - net/http/cgi\ - net/http/fcgi\ - net/http/pprof\ - net/http/httptest\ - net/http/httputil\ - net/mail\ - net/rpc\ - net/rpc/jsonrpc\ - net/smtp\ - net/textproto\ - net/url\ - old/netchan\ - old/regexp\ - old/template\ - os\ - os/exec\ - os/signal\ - os/user\ - patch\ - path\ - path/filepath\ - reflect\ - regexp\ - regexp/syntax\ - runtime\ - runtime/cgo\ - runtime/debug\ - runtime/pprof\ - sort\ - strconv\ - strings\ - sync\ - sync/atomic\ - syscall\ - testing\ - testing/iotest\ - testing/quick\ - testing/script\ - text/scanner\ - text/tabwriter\ - text/template\ - text/template/parse\ - time\ - unicode\ - unicode/utf16\ - unicode/utf8\ - websocket\ - ../cmd/cgo\ - ../cmd/godoc\ - ../cmd/gofix\ - ../cmd/gofmt\ - ../cmd/goinstall\ - ../cmd/gotest\ - ../cmd/govet\ - ../cmd/goyacc\ - ../cmd/hgpatch\ - -ifeq ($(GOOS),linux) -DIRS+=\ - exp/inotify\ - exp/terminal\ - exp/fsnotify\ - -endif - -ifeq ($(GOOS),freebsd) -DIRS+=\ - exp/fsnotify\ - -endif - -ifeq ($(GOOS),openbsd) -DIRS+=\ - exp/fsnotify\ - -endif - -ifeq ($(GOOS),darwin) -DIRS+=\ - exp/fsnotify\ - -endif - -ifeq ($(GOOS),plan9) -NOPLAN9BUILD=\ - os/signal\ - -DIRS:=$(filter-out $(NOPLAN9BUILD),$(DIRS)) -endif - -NOTEST+=\ - crypto\ - crypto/openpgp/error\ - crypto/x509/pkix\ - exp/ebnflint\ - go/doc\ - hash\ - image/bmp\ - image/gif\ - net/dict\ - net/http/pprof\ - net/http/httptest\ - runtime/cgo\ - syscall\ - testing\ - testing/iotest\ - ../cmd/cgo\ - ../cmd/godoc\ - ../cmd/gotest\ - ../cmd/goyacc\ - ../cmd/hgpatch\ - -NOBENCH+=\ - -# Disable tests that windows cannot run yet. -ifeq ($(GOOS),windows) -NOTEST+=os/signal # no signals -NOTEST+=syslog # no network -endif - -TEST=\ - $(filter-out $(NOTEST),$(DIRS)) - -BENCH=\ - $(filter-out $(NOBENCH),$(TEST)) - -CRAP: - echo $(DIRS) - -clean.dirs: $(addsuffix .clean, $(DIRS)) -install.dirs: $(addsuffix .install, $(DIRS)) -nuke.dirs: $(addsuffix .nuke, $(DIRS)) -test.dirs: $(addsuffix .test, $(TEST)) -testshort.dirs: $(addsuffix .testshort, $(TEST)) -bench.dirs: $(addsuffix .bench, $(BENCH)) - -%.clean: - +$(MAKE) -C $* clean - -%.install: - +@echo install $* - +@$(MAKE) -C $* install.clean >$*/build.out 2>&1 || (echo INSTALL FAIL $*; cat $*/build.out; exit 1) - -%.nuke: - +$(MAKE) -C $* nuke - -%.test: - +@echo test $* - +@$(MAKE) -C $* test.clean >$*/test.out 2>&1 || (echo TEST FAIL $*; cat $*/test.out; exit 1) - -%.testshort: - +@echo test $* - +@$(MAKE) -C $* testshort.clean >$*/test.out 2>&1 || (echo TEST FAIL $*; cat $*/test.out; exit 1) - -%.bench: - +$(MAKE) -C $* bench - -clean: clean.dirs - -install: install.dirs - -test: test.dirs - -testshort: testshort.dirs - -bench: bench.dirs ../../test/garbage.bench - -nuke: nuke.dirs - rm -rf "$(GOROOT)"/pkg/* - -deps: - ./deps.bash - -echo-dirs: - @echo $(DIRS) - --include Make.deps - -runtime/cgo.install: ../cmd/cgo.install diff --git a/src/livereload.go b/src/livereload.go index 970b753..b396c01 100644 --- a/src/livereload.go +++ b/src/livereload.go @@ -2,22 +2,126 @@ package main import ( "flag" - "livereload/http_server" + "net/http" + "websocket" + "fmt" + "log" + "exp/fsnotify" +) +const ( + API_VERSION = "1.6" ) - var ( hostname = flag.String("hostname", "", "host name") port = flag.Uint("port", 35729, "port number") - directories = Flag.Args() + directories = flag.Args() + + // http channels + subscriptionChannel = make(chan subscriptionMessage) + messageChannel = make(chan string) + watcher, watcher_err = fsnotify.NewWatcher() ) +type subscriptionMessage struct { + websocket *websocket.Conn + active bool + url string +} + +type livereloadClientUpdateMessage struct { + path string + apply_js_live bool + apply_css_live bool + apply_images_live bool +} + +func eventHub() { + connections := make(map[*websocket.Conn]string) + for { + select { + case subscriptionMessage := <-subscriptionChannel: + if subscriptionMessage.active { // on connection + connections[subscriptionMessage.websocket] = subscriptionMessage.url + } else { // on disconnection + delete(connections, subscriptionMessage.websocket) + } + case message := <-messageChannel: + if len(message) == 0 { + fmt.Printf("close ws requested") + } + fmt.Printf("Incomming Message: %s", message) + case ev := <-watcher.Event: + //update := livereloadClientUpdateMessage{ev.Name, true, true, true} + for k, _ := range connections { + websocket.Message.Send(k, fmt.Sprintf("[\"refresh\": {\"path\": \"%s\"}]", ev.Name)) + //websocket.JSON.Send(k, update) + } + /*'["refresh", { + "path" : %s, + "apply_js_live": true, + "apply_css_live" : true + "apply_images_live" : true + }]'*/ + log.Println("fsevent:", ev) + case err := <-watcher.Error: + log.Println("fserror:", err) + } + } +} + +func liveReload16ConnectionHandler(ws *websocket.Conn) { + defer func() { + subscriptionChannel <- subscriptionMessage{ws, false, ""} + fmt.Printf("Browser disconnected") + ws.Close() + }() + + websocket.Message.Send(ws, fmt.Sprintf("!!ver:%s", API_VERSION)) + fmt.Printf("Browser Connected") + + // on connection it's the client url + var onConnectionMessage string + websocket.Message.Receive(ws, &onConnectionMessage) + subscriptionChannel <- subscriptionMessage{ws, true, onConnectionMessage} + fmt.Printf("Browser URL: %s", onConnectionMessage) + + // websocket messages from the clients get pushed though the eventhub + for { + var msg string + websocket.Message.Receive(ws, &msg) + messageChannel <- msg + } +} + +func websocketHandshakeHandler(w http.ResponseWriter, req *http.Request) { + websocket.Handler(liveReload16ConnectionHandler).ServeHTTP(w, req) +} + func main() { + if watcher_err != nil { + log.Fatal("Unable to create file monitor.") + } + flag.Parse() + // register directories to monitory if len(directories) == 0 { directories = make([]string, 1, 1) - directories[1] = "." + directories[0] = "." + } + + for _, directory := range directories { + err := watcher.Watch(directory) + if err != nil { + log.Fatal(err) + } } - http_server.Start(*hostname, *port) + go eventHub() + http.HandleFunc("/websocket", websocketHandshakeHandler) + + err := http.ListenAndServe(fmt.Sprintf("%s:%d", *hostname, *port), nil) + if err != nil { + log.Fatal("http_server.Start.ListenAndServe: ", err) + } } diff --git a/src/livereload/http_server.go b/src/livereload/http_server.go deleted file mode 100644 index 9b895e6..0000000 --- a/src/livereload/http_server.go +++ /dev/null @@ -1,1007 +0,0 @@ -/** - * A simple implementation of the livereload protocol - * - * http://help.livereload.com/kb/ecosystem/livereload-protocol - */ - -package http_server - -import ( - "io" - "net/http" - "websocket" - "fmt" - "log" -) - -type livereloadClient struct { - websocket *websocket.Conn - connectionUrl string -} - -type subscription struct { - websocket *websocket.Conn - active bool -} - -var subscriptionChannel = make(chan subscription) -var messageChannel = make(chan string) - -func websocketSubscriptionRegistry() { - connections := make(map[*websocket.Conn]int) - for { - select { - case subscription := <-subscriptionChannel: - if subscription.active { - connections[subscription.websocket] = 0 - } else { - delete(connections, subscription.websocket) - } - case message := <-messageChannel: - fmt.Printf("Incomming Message: %s", message) - } - } -} - -func liveReload16ClientHandler(ws *websocket.Conn) { - defer func() { - subscriptionChannel <- subscription{ws, false} - fmt.Printf("Disconnected") - ws.Close() - }() - - subscriptionChannel <- subscription{ws, true} - // on open - for { - var message string - websocket.Message.Receive(ws, &message) - messageChannel <- message - } -} - -func websocketHandshakeHandler(w http.ResponseWriter, req *http.Request) { - websocket.Handler(liveReload16ClientHandler).ServeHTTP(w, req) -} - -func liveReloadJavascriptFileHandler(w http.ResponseWriter, req *http.Request) { - w.Header().Set("Content-Type", "text/javascript") - io.WriteString(w, livereloadJavascriptString) -} - -func Start(hostname string, port uint) { - go websocketSubscriptionRegistry() - http.HandleFunc("/websocket", websocketHandshakeHandler) - http.HandleFunc("/livereload.js", liveReloadJavascriptFileHandler) - - err := http.ListenAndServe(fmt.Sprintf("%s:%d", hostname, port), nil) - if err != nil { - log.Fatal("http_server.Start.ListenAndServe: ", err) - } -} - -// https://raw.github.com/livereload/livereload-js/master/dist/livereload.js -const livereloadJavascriptString = `(function() { -var __customevents = {}, __protocol = {}, __connector = {}, __timer = {}, __options = {}, __reloader = {}, __livereload = {}, __less = {}, __startup = {}; - -// customevents -var CustomEvents; -CustomEvents = { - bind: function(element, eventName, handler) { - if (element.addEventListener) { - return element.addEventListener(eventName, handler, false); - } else if (element.attachEvent) { - element[eventName] = 1; - return element.attachEvent('onpropertychange', function(event) { - if (event.propertyName === eventName) { - return handler(); - } - }); - } else { - throw new Error("Attempt to attach custom event " + eventName + " to something which isn't a DOMElement"); - } - }, - fire: function(element, eventName) { - var event; - if (element.addEventListener) { - event = document.createEvent('HTMLEvents'); - event.initEvent(eventName, true, true); - return document.dispatchEvent(event); - } else if (element.attachEvent) { - if (element[eventName]) { - return element[eventName]++; - } - } else { - throw new Error("Attempt to fire custom event " + eventName + " on something which isn't a DOMElement"); - } - } -}; -__customevents.bind = CustomEvents.bind; -__customevents.fire = CustomEvents.fire; - -// protocol -var PROTOCOL_6, PROTOCOL_7, Parser, ProtocolError; -var __indexOf = Array.prototype.indexOf || function(item) { - for (var i = 0, l = this.length; i < l; i++) { - if (this[i] === item) return i; - } - return -1; -}; -__protocol.PROTOCOL_6 = PROTOCOL_6 = 'http://livereload.com/protocols/official-6'; -__protocol.PROTOCOL_7 = PROTOCOL_7 = 'http://livereload.com/protocols/official-7'; -__protocol.ProtocolError = ProtocolError = (function() { - function ProtocolError(reason, data) { - this.message = "LiveReload protocol error (" + reason + ") after receiving data: \"" + data + "\"."; - } - return ProtocolError; -})(); -__protocol.Parser = Parser = (function() { - function Parser(handlers) { - this.handlers = handlers; - this.reset(); - } - Parser.prototype.reset = function() { - return this.protocol = null; - }; - Parser.prototype.process = function(data) { - var command, message, options, _ref; - try { - if (!(this.protocol != null)) { - if (data.match(/^!!ver:([\d.]+)$/)) { - this.protocol = 6; - } else if (message = this._parseMessage(data, ['hello'])) { - if (!message.protocols.length) { - throw new ProtocolError("no protocols specified in handshake message"); - } else if (__indexOf.call(message.protocols, PROTOCOL_7) >= 0) { - this.protocol = 7; - } else if (__indexOf.call(message.protocols, PROTOCOL_6) >= 0) { - this.protocol = 6; - } else { - throw new ProtocolError("no supported protocols found"); - } - } - return this.handlers.connected(this.protocol); - } else if (this.protocol === 6) { - message = JSON.parse(data); - if (!message.length) { - throw new ProtocolError("protocol 6 messages must be arrays"); - } - command = message[0], options = message[1]; - if (command !== 'refresh') { - throw new ProtocolError("unknown protocol 6 command"); - } - return this.handlers.message({ - command: 'reload', - path: options.path, - liveCSS: (_ref = options.apply_css_live) != null ? _ref : true - }); - } else { - message = this._parseMessage(data, ['reload', 'alert']); - return this.handlers.message(message); - } - } catch (e) { - if (e instanceof ProtocolError) { - return this.handlers.error(e); - } else { - throw e; - } - } - }; - Parser.prototype._parseMessage = function(data, validCommands) { - var message, _ref; - try { - message = JSON.parse(data); - } catch (e) { - throw new ProtocolError('unparsable JSON', data); - } - if (!message.command) { - throw new ProtocolError('missing "command" key', data); - } - if (_ref = message.command, __indexOf.call(validCommands, _ref) < 0) { - throw new ProtocolError("invalid command '" + message.command + "', only valid commands are: " + (validCommands.join(', ')) + ")", data); - } - return message; - }; - return Parser; -})(); - -// connector -var Connector, PROTOCOL_6, PROTOCOL_7, Parser, Version, _ref; -var __bind = function(fn, me){ return function(){ return fn.apply(me, arguments); }; }; -_ref = __protocol, Parser = _ref.Parser, PROTOCOL_6 = _ref.PROTOCOL_6, PROTOCOL_7 = _ref.PROTOCOL_7; -Version = '2.0.4'; -__connector.Connector = Connector = (function() { - function Connector(options, WebSocket, Timer, handlers) { - this.options = options; - this.WebSocket = WebSocket; - this.Timer = Timer; - this.handlers = handlers; - this._uri = "ws://" + this.options.host + ":" + this.options.port + "/livereload"; - this._nextDelay = this.options.mindelay; - this._connectionDesired = false; - this.protocol = 0; - this.protocolParser = new Parser({ - connected: __bind(function(protocol) { - this.protocol = protocol; - this._handshakeTimeout.stop(); - this._nextDelay = this.options.mindelay; - this._disconnectionReason = 'broken'; - return this.handlers.connected(protocol); - }, this), - error: __bind(function(e) { - this.handlers.error(e); - return this._closeOnError(); - }, this), - message: __bind(function(message) { - return this.handlers.message(message); - }, this) - }); - this._handshakeTimeout = new Timer(__bind(function() { - if (!this._isSocketConnected()) { - return; - } - this._disconnectionReason = 'handshake-timeout'; - return this.socket.close(); - }, this)); - this._reconnectTimer = new Timer(__bind(function() { - if (!this._connectionDesired) { - return; - } - return this.connect(); - }, this)); - this.connect(); - } - Connector.prototype._isSocketConnected = function() { - return this.socket && this.socket.readyState === this.WebSocket.OPEN; - }; - Connector.prototype.connect = function() { - this._connectionDesired = true; - if (this._isSocketConnected()) { - return; - } - if (this._reconnectTimer) { - clearTimeout(this._reconnectTimer); - } - this._disconnectionReason = 'cannot-connect'; - this.protocolParser.reset(); - this.handlers.connecting(); - this.socket = new this.WebSocket(this._uri); - this.socket.onopen = __bind(function(e) { - return this._onopen(e); - }, this); - this.socket.onclose = __bind(function(e) { - return this._onclose(e); - }, this); - this.socket.onmessage = __bind(function(e) { - return this._onmessage(e); - }, this); - return this.socket.onerror = __bind(function(e) { - return this._onerror(e); - }, this); - }; - Connector.prototype.disconnect = function() { - this._connectionDesired = false; - this._reconnectTimer.stop(); - if (!this._isSocketConnected()) { - return; - } - this._disconnectionReason = 'manual'; - return this.socket.close(); - }; - Connector.prototype._scheduleReconnection = function() { - if (!this._connectionDesired) { - return; - } - if (!this._reconnectTimer.running) { - this._reconnectTimer.start(this._nextDelay); - return this._nextDelay = Math.min(this.options.maxdelay, this._nextDelay * 2); - } - }; - Connector.prototype.sendCommand = function(command) { - if (this.protocol == null) { - return; - } - return this._sendCommand(command); - }; - Connector.prototype._sendCommand = function(command) { - return this.socket.send(JSON.stringify(command)); - }; - Connector.prototype._closeOnError = function() { - this._handshakeTimeout.stop(); - this._disconnectionReason = 'error'; - return this.socket.close(); - }; - Connector.prototype._onopen = function(e) { - var hello; - this.handlers.socketConnected(); - this._disconnectionReason = 'handshake-failed'; - hello = { - command: 'hello', - protocols: [PROTOCOL_6, PROTOCOL_7] - }; - hello.ver = Version; - if (this.options.ext) { - hello.ext = this.options.ext; - } - if (this.options.extver) { - hello.extver = this.options.extver; - } - if (this.options.snipver) { - hello.snipver = this.options.snipver; - } - this._sendCommand(hello); - return this._handshakeTimeout.start(this.options.handshake_timeout); - }; - Connector.prototype._onclose = function(e) { - this.protocol = 0; - this.handlers.disconnected(this._disconnectionReason, this._nextDelay); - return this._scheduleReconnection(); - }; - Connector.prototype._onerror = function(e) {}; - Connector.prototype._onmessage = function(e) { - return this.protocolParser.process(e.data); - }; - return Connector; -})(); - -// timer -var Timer; -var __bind = function(fn, me){ return function(){ return fn.apply(me, arguments); }; }; -__timer.Timer = Timer = (function() { - function Timer(func) { - this.func = func; - this.running = false; - this.id = null; - this._handler = __bind(function() { - this.running = false; - this.id = null; - return this.func(); - }, this); - } - Timer.prototype.start = function(timeout) { - if (this.running) { - clearTimeout(this.id); - } - this.id = setTimeout(this._handler, timeout); - return this.running = true; - }; - Timer.prototype.stop = function() { - if (this.running) { - clearTimeout(this.id); - this.running = false; - return this.id = null; - } - }; - return Timer; -})(); -Timer.start = function(timeout, func) { - return setTimeout(func, timeout); -}; - -// options -var Options; -__options.Options = Options = (function() { - function Options() { - this.host = null; - this.port = 35729; - this.snipver = null; - this.ext = null; - this.extver = null; - this.mindelay = 1000; - this.maxdelay = 60000; - this.handshake_timeout = 5000; - } - Options.prototype.set = function(name, value) { - switch (typeof this[name]) { - case 'undefined': - break; - case 'number': - return this[name] = +value; - default: - return this[name] = value; - } - }; - return Options; -})(); -Options.extract = function(document) { - var element, keyAndValue, m, mm, options, pair, src, _i, _j, _len, _len2, _ref, _ref2; - _ref = document.getElementsByTagName('script'); - for (_i = 0, _len = _ref.length; _i < _len; _i++) { - element = _ref[_i]; - if ((src = element.src) && (m = src.match(/^[^:]+:\/\/(.*)\/z?livereload\.js(?:\?(.*))?$/))) { - options = new Options(); - if (mm = m[1].match(/^([^\/:]+)(?::(\d+))?$/)) { - options.host = mm[1]; - if (mm[2]) { - options.port = parseInt(mm[2], 10); - } - } - if (m[2]) { - _ref2 = m[2].split('&'); - for (_j = 0, _len2 = _ref2.length; _j < _len2; _j++) { - pair = _ref2[_j]; - if ((keyAndValue = pair.split('=')).length > 1) { - options.set(keyAndValue[0].replace(/-/g, '_'), keyAndValue.slice(1).join('=')); - } - } - } - return options; - } - } - return null; -}; - -// reloader -var IMAGE_STYLES, Reloader, numberOfMatchingSegments, pathFromUrl, pathsMatch, pickBestMatch, splitUrl; -var __bind = function(fn, me){ return function(){ return fn.apply(me, arguments); }; }; -splitUrl = function(url) { - var hash, index, params; - if ((index = url.indexOf('#')) >= 0) { - hash = url.slice(index); - url = url.slice(0, index); - } else { - hash = ''; - } - if ((index = url.indexOf('?')) >= 0) { - params = url.slice(index); - url = url.slice(0, index); - } else { - params = ''; - } - return { - url: url, - params: params, - hash: hash - }; -}; -pathFromUrl = function(url) { - var path; - url = splitUrl(url).url; - if (url.indexOf('file://') === 0) { - path = url.replace(/^file:\/\/(localhost)?/, ''); - } else { - path = url.replace(/^([^:]+:)?\/\/([^:\/]+)(:\d*)?\//, '/'); - } - return decodeURIComponent(path); -}; -pickBestMatch = function(path, objects, pathFunc) { - var bestMatch, object, score, _i, _len; - bestMatch = { - score: 0 - }; - for (_i = 0, _len = objects.length; _i < _len; _i++) { - object = objects[_i]; - score = numberOfMatchingSegments(path, pathFunc(object)); - if (score > bestMatch.score) { - bestMatch = { - object: object, - score: score - }; - } - } - if (bestMatch.score > 0) { - return bestMatch; - } else { - return null; - } -}; -numberOfMatchingSegments = function(path1, path2) { - var comps1, comps2, eqCount, len; - path1 = path1.replace(/^\/+/, '').toLowerCase(); - path2 = path2.replace(/^\/+/, '').toLowerCase(); - if (path1 === path2) { - return 10000; - } - comps1 = path1.split('/').reverse(); - comps2 = path2.split('/').reverse(); - len = Math.min(comps1.length, comps2.length); - eqCount = 0; - while (eqCount < len && comps1[eqCount] === comps2[eqCount]) { - ++eqCount; - } - return eqCount; -}; -pathsMatch = function(path1, path2) { - return numberOfMatchingSegments(path1, path2) > 0; -}; -IMAGE_STYLES = [ - { - selector: 'background', - styleNames: ['backgroundImage'] - }, { - selector: 'border', - styleNames: ['borderImage', 'webkitBorderImage', 'MozBorderImage'] - } -]; -__reloader.Reloader = Reloader = (function() { - function Reloader(window, console, Timer) { - this.window = window; - this.console = console; - this.Timer = Timer; - this.document = this.window.document; - this.stylesheetGracePeriod = 200; - this.importCacheWaitPeriod = 200; - this.plugins = []; - } - Reloader.prototype.addPlugin = function(plugin) { - return this.plugins.push(plugin); - }; - Reloader.prototype.analyze = function(callback) { - return results; - }; - Reloader.prototype.reload = function(path, options) { - var plugin, _i, _len, _ref; - _ref = this.plugins; - for (_i = 0, _len = _ref.length; _i < _len; _i++) { - plugin = _ref[_i]; - if (plugin.reload && plugin.reload(path, options)) { - return; - } - } - if (options.liveCSS) { - if (path.match(/\.css$/i)) { - if (this.reloadStylesheet(path)) { - return; - } - } - } - if (options.liveImg) { - if (path.match(/\.(jpe?g|png|gif)$/i)) { - this.reloadImages(path); - return; - } - } - return this.reloadPage(); - }; - Reloader.prototype.reloadPage = function() { - return this.window.document.location.reload(); - }; - Reloader.prototype.reloadImages = function(path) { - var expando, img, selector, styleNames, styleSheet, _i, _j, _k, _l, _len, _len2, _len3, _len4, _ref, _ref2, _ref3, _ref4, _results; - expando = this.generateUniqueString(); - _ref = this.document.images; - for (_i = 0, _len = _ref.length; _i < _len; _i++) { - img = _ref[_i]; - if (pathsMatch(path, pathFromUrl(img.src))) { - img.src = this.generateCacheBustUrl(img.src, expando); - } - } - if (this.document.querySelectorAll) { - for (_j = 0, _len2 = IMAGE_STYLES.length; _j < _len2; _j++) { - _ref2 = IMAGE_STYLES[_j], selector = _ref2.selector, styleNames = _ref2.styleNames; - _ref3 = this.document.querySelectorAll("[style*=" + selector + "]"); - for (_k = 0, _len3 = _ref3.length; _k < _len3; _k++) { - img = _ref3[_k]; - this.reloadStyleImages(img.style, styleNames, path, expando); - } - } - } - if (this.document.styleSheets) { - _ref4 = this.document.styleSheets; - _results = []; - for (_l = 0, _len4 = _ref4.length; _l < _len4; _l++) { - styleSheet = _ref4[_l]; - _results.push(this.reloadStylesheetImages(styleSheet, path, expando)); - } - return _results; - } - }; - Reloader.prototype.reloadStylesheetImages = function(styleSheet, path, expando) { - var rule, rules, styleNames, _i, _j, _len, _len2; - try { - rules = styleSheet != null ? styleSheet.cssRules : void 0; - } catch (e) { - - } - if (!rules) { - return; - } - for (_i = 0, _len = rules.length; _i < _len; _i++) { - rule = rules[_i]; - switch (rule.type) { - case CSSRule.IMPORT_RULE: - this.reloadStylesheetImages(rule.styleSheet, path, expando); - break; - case CSSRule.STYLE_RULE: - for (_j = 0, _len2 = IMAGE_STYLES.length; _j < _len2; _j++) { - styleNames = IMAGE_STYLES[_j].styleNames; - this.reloadStyleImages(rule.style, styleNames, path, expando); - } - break; - case CSSRule.MEDIA_RULE: - this.reloadStylesheetImages(rule, path, expando); - } - } - }; - Reloader.prototype.reloadStyleImages = function(style, styleNames, path, expando) { - var newValue, styleName, value, _i, _len; - for (_i = 0, _len = styleNames.length; _i < _len; _i++) { - styleName = styleNames[_i]; - value = style[styleName]; - if (typeof value === 'string') { - newValue = value.replace(/\burl\s*\(([^)]*)\)/, __bind(function(match, src) { - if (pathsMatch(path, pathFromUrl(src))) { - return "url(" + (this.generateCacheBustUrl(src, expando)) + ")"; - } else { - return match; - } - }, this)); - if (newValue !== value) { - style[styleName] = newValue; - } - } - } - }; - Reloader.prototype.reloadStylesheet = function(path) { - var imported, link, links, match, style, _i, _j, _k, _len, _len2, _len3, _ref; - links = (function() { - var _i, _len, _ref, _results; - _ref = this.document.getElementsByTagName('link'); - _results = []; - for (_i = 0, _len = _ref.length; _i < _len; _i++) { - link = _ref[_i]; - if (link.rel === 'stylesheet' && !link.__LiveReload_pendingRemoval) { - _results.push(link); - } - } - return _results; - }).call(this); - imported = []; - _ref = this.document.getElementsByTagName('style'); - for (_i = 0, _len = _ref.length; _i < _len; _i++) { - style = _ref[_i]; - if (style.sheet) { - this.collectImportedStylesheets(style, style.sheet, imported); - } - } - for (_j = 0, _len2 = links.length; _j < _len2; _j++) { - link = links[_j]; - this.collectImportedStylesheets(link, link.sheet, imported); - } - this.console.log("LiveReload found " + links.length + " LINKed stylesheets, " + imported.length + " @imported stylesheets"); - match = pickBestMatch(path, links.concat(imported), function(l) { - return pathFromUrl(l.href); - }); - if (match) { - if (match.object.rule) { - this.console.log("LiveReload is reloading imported stylesheet: " + match.object.href); - this.reattachImportedRule(match.object); - } else { - this.console.log("LiveReload is reloading stylesheet: " + match.object.href); - this.reattachStylesheetLink(match.object); - } - } else { - this.console.log("LiveReload will reload all stylesheets because path '" + path + "' did not match any specific one"); - for (_k = 0, _len3 = links.length; _k < _len3; _k++) { - link = links[_k]; - this.reattachStylesheetLink(link); - } - } - return true; - }; - Reloader.prototype.collectImportedStylesheets = function(link, styleSheet, result) { - var index, rule, rules, _len; - try { - rules = styleSheet != null ? styleSheet.cssRules : void 0; - } catch (e) { - - } - if (rules && rules.length) { - for (index = 0, _len = rules.length; index < _len; index++) { - rule = rules[index]; - switch (rule.type) { - case CSSRule.CHARSET_RULE: - continue; - case CSSRule.IMPORT_RULE: - result.push({ - link: link, - rule: rule, - index: index, - href: rule.href - }); - this.collectImportedStylesheets(link, rule.styleSheet, result); - break; - default: - break; - } - } - } - }; - Reloader.prototype.reattachStylesheetLink = function(link) { - var clone, parent, timer; - if (link.__LiveReload_pendingRemoval) { - return; - } - link.__LiveReload_pendingRemoval = true; - clone = link.cloneNode(false); - clone.href = this.generateCacheBustUrl(link.href); - parent = link.parentNode; - if (parent.lastChild === link) { - parent.appendChild(clone); - } else { - parent.insertBefore(clone, link.nextSibling); - } - timer = new this.Timer(function() { - if (link.parentNode) { - return link.parentNode.removeChild(link); - } - }); - return timer.start(this.stylesheetGracePeriod); - }; - Reloader.prototype.reattachImportedRule = function(_arg) { - var href, index, link, media, newRule, parent, rule, tempLink; - rule = _arg.rule, index = _arg.index, link = _arg.link; - parent = rule.parentStyleSheet; - href = this.generateCacheBustUrl(rule.href); - media = rule.media.length ? [].join.call(rule.media, ', ') : ''; - newRule = "@import url(\"" + href + "\") " + media + ";"; - rule.__LiveReload_newHref = href; - tempLink = this.document.createElement("link"); - tempLink.rel = 'stylesheet'; - tempLink.href = href; - tempLink.__LiveReload_pendingRemoval = true; - if (link.parentNode) { - link.parentNode.insertBefore(tempLink, link); - } - return this.Timer.start(this.importCacheWaitPeriod, __bind(function() { - if (tempLink.parentNode) { - tempLink.parentNode.removeChild(tempLink); - } - if (rule.__LiveReload_newHref !== href) { - return; - } - parent.insertRule(newRule, index); - parent.deleteRule(index + 1); - rule = parent.cssRules[index]; - rule.__LiveReload_newHref = href; - return this.Timer.start(this.importCacheWaitPeriod, __bind(function() { - if (rule.__LiveReload_newHref !== href) { - return; - } - parent.insertRule(newRule, index); - return parent.deleteRule(index + 1); - }, this)); - }, this)); - }; - Reloader.prototype.generateUniqueString = function() { - return 'livereload=' + Date.now(); - }; - Reloader.prototype.generateCacheBustUrl = function(url, expando) { - var hash, oldParams, params, _ref; - if (expando == null) { - expando = this.generateUniqueString(); - } - _ref = splitUrl(url), url = _ref.url, hash = _ref.hash, oldParams = _ref.params; - params = oldParams.replace(/(\?|&)livereload=(\d+)/, function(match, sep) { - return "" + sep + expando; - }); - if (params === oldParams) { - if (oldParams.length === 0) { - params = "?" + expando; - } else { - params = "" + oldParams + "&" + expando; - } - } - return url + params + hash; - }; - return Reloader; -})(); - -// livereload -var Connector, LiveReload, Options, Reloader, Timer; -var __bind = function(fn, me){ return function(){ return fn.apply(me, arguments); }; }; -Connector = __connector.Connector; -Timer = __timer.Timer; -Options = __options.Options; -Reloader = __reloader.Reloader; -__livereload.LiveReload = LiveReload = (function() { - function LiveReload(window) { - this.window = window; - this.listeners = {}; - this.plugins = []; - this.pluginIdentifiers = {}; - this.console = this.window.console && this.window.console.log && this.window.console.error ? this.window.console : { - log: function() {}, - error: function() {} - }; - if (!(this.WebSocket = this.window.WebSocket || this.window.MozWebSocket)) { - console.error("LiveReload disabled because the browser does not seem to support web sockets"); - return; - } - if (!(this.options = Options.extract(this.window.document))) { - console.error("LiveReload disabled because it could not find its own