Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
@@ -1,2 +1,3 @@
.DS_Store
lib/test/temp
node_modules
8 changes: 3 additions & 5 deletions lib/watchers/stat.js

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

12 changes: 6 additions & 6 deletions src/test/testing_util.coffee
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@

{exec} = require 'child_process'
sys = require 'sys'
util = try require 'util' catch e then require 'sys'


exports.check_exec_options = check_exec_options = (cmd, options, callback) ->
Expand All @@ -26,11 +26,11 @@ exports.listsContainSameElements = listsContainSameElements = (t, arr1, arr2) ->


exports.EventBuffer = class EventBuffer

constructor: () ->
@stack = []
@callback = null

wait: (callback) ->
if @stack.length > 0
event = @stack.pop()
Expand All @@ -39,14 +39,14 @@ exports.EventBuffer = class EventBuffer
if @callback
throw new Error "Only store one callback"
@callback = callback

expect: (t, args...) ->
sys.debug "Expecting #{args[0]}..."
util.debug "Expecting #{args[0]}..."
@wait (event) ->
for x, i in args[...-1]
t.equal event[i], x
args[-1...][0](event)

event: (event) ->
if @callback
callback = @callback
Expand Down
79 changes: 37 additions & 42 deletions src/watchers/stat.coffee
Original file line number Diff line number Diff line change
Expand Up @@ -4,30 +4,25 @@ events = require 'events'
assert = require 'assert'
async = require 'async'


ENOENT = 2
ENOTDIR = 20


class Paths

constructor: () ->

@items = []
@itemsDict = {}

@numItems = 0
@pos = 0

add: (x) ->
assert.ok not @contains x
@items.push x
@numItems += 1
@itemsDict[x] = true

contains: (x) ->
@itemsDict[x]?

next: () ->
return null if @items.length == 0
@pos = (@pos + 1) % @numItems
Expand All @@ -36,116 +31,116 @@ class Paths


exports.StatWatcher = class StatWatcher extends events.EventEmitter

constructor: (top, opt) ->

events.EventEmitter.call this

options = opt or {}
@ignore = if opt.ignore? then new RegExp opt.ignore else null
@match = if opt.match? then new RegExp opt.match else null
@sampleRate = if opt['sample-rate']? then (1 * opt['sample-rate']) else 5
@maxStatsPending = 10 # Does not apply to the initial scan

@paths = new Paths()
@paths.add top
@path_mtime = {}
@numStatsPending = 0
@preexistingPathsToReport = {}
@numPreexistingPathsToReport = 0

pathsIn top, (paths) =>
for path in paths
if (not @paths.contains path) and
(not @ignore or not path.match @ignore) and
(not @ignore or not path.match @ignore) and
(not @match or path.match @match)

@preexistingPathsToReport[path] = true
@numPreexistingPathsToReport++

@paths.add path
@statPath path
@intervalId = setInterval (() => @tick()), @sampleRate

end: () ->
clearInterval @intervalId

tick: () ->
if @numStatsPending <= @maxStatsPending
path = @paths.next()
if path
@statPath path

statPath: (path) ->

@numStatsPending++
fs.stat path, (err, stats) =>
@numStatsPending--
last_mtime = @path_mtime[path] or null

if err

# file deleted
if err.errno == ENOENT
if err.code == 'ENOENT'
if last_mtime
@emit 'fileDeleted', path
delete @path_mtime[path]

# error
else
throw err

else

@path_mtime[path] = stats.mtime

# (new or modified) dir
if stats.isDirectory()
if (not last_mtime) or (stats.mtime > last_mtime)
@scanDir path

else

# new file
if not last_mtime

eventName = 'fileCreated'
if @preexistingPathsToReport[path]
eventName = 'filePreexisted'
delete @preexistingPathsToReport[path]
@numPreexistingPathsToReport--
@numPreexistingPathsToReport--
@emit eventName, path, stats

# modified file
else if stats.mtime > last_mtime
@emit 'fileModified', path, stats

if @numPreexistingPathsToReport == 0
@emit 'allPreexistingFilesReported'
@numPreexistingPathsToReport = -1

scanDir: (path) ->
fs.readdir path, (err, files) =>
for file in files
path2 = "#{path}/#{file}"
if (not @paths.contains path2) and
(not @ignore or not path2.match @ignore) and
(not @ignore or not path2.match @ignore) and
(not @match or path2.match @match)
@paths.add path2
@statPath path2


_pathsIn = (path, paths, callback) ->
fs.readdir path, (err, files) ->

# Case: file
if err and err.errno == ENOTDIR
if err and err.code == 'ENOTDIR'
paths.push path
return callback()

# Case: error
throw err if err

# Case: dir
async.forEach(
files,
Expand Down