Permalink
Browse files

Add support for stat watchers

  • Loading branch information...
1 parent 2fa625d commit c8728f4e7ebc3bc952322fe6947fffe980aa3927 @miGlanz miGlanz committed Mar 9, 2011
Showing with 323 additions and 3 deletions.
  1. +2 −1 CMakeLists.txt
  2. +59 −2 README
  3. +7 −0 lua_ev.c
  4. +16 −0 lua_ev.h
  5. +180 −0 stat_lua_ev.c
  6. +59 −0 test/test_ev_stat.lua
View
@@ -46,7 +46,8 @@ CMAKE_MINIMUM_REQUIRED (VERSION 2.6)
ADD_TEST(ev_idle ${LUA} ${CMAKE_CURRENT_SOURCE_DIR}/test/test_ev_idle.lua ${CMAKE_CURRENT_SOURCE_DIR}/test/ ${CMAKE_CURRENT_BINARY_DIR}/)
ADD_TEST(ev_signal ${LUA} ${CMAKE_CURRENT_SOURCE_DIR}/test/test_ev_signal.lua ${CMAKE_CURRENT_SOURCE_DIR}/test/ ${CMAKE_CURRENT_BINARY_DIR}/)
ADD_TEST(ev_child ${LUA} ${CMAKE_CURRENT_SOURCE_DIR}/test/test_ev_child.lua ${CMAKE_CURRENT_SOURCE_DIR}/test/ ${CMAKE_CURRENT_BINARY_DIR}/)
- SET_TESTS_PROPERTIES(ev_io ev_loop ev_timer ev_signal ev_idle ev_child
+ ADD_TEST(ev_stat ${LUA} ${CMAKE_CURRENT_SOURCE_DIR}/test/test_ev_stat.lua ${CMAKE_CURRENT_SOURCE_DIR}/test/ ${CMAKE_CURRENT_BINARY_DIR}/)
+ SET_TESTS_PROPERTIES(ev_io ev_loop ev_timer ev_signal ev_idle ev_child ev_stat
PROPERTIES
FAIL_REGULAR_EXPRESSION
"not ok")
View
61 README
@@ -202,6 +202,30 @@ child = ev.Child.new(on_child, pid, trace)
See also ev_child_init() C function.
+stat = ev.Stat.new(on_stat, path [, interval])
+
+ Configures the watcher to wait for status changes of the given "path".
+ The "interval" is a hint on how quickly a change is expected to be
+ detected and may normally be left out to let libev choose a suitable
+ value.
+
+ The returned stat is an ev.Stat object. See below for the methods on
+ this object.
+
+ NOTE: You must explicitly register the stat with an event loop in
+ order for it to take effect.
+
+ The on_stat function will be called with these arguments (return
+ values are ignored):
+
+ on_stat(loop, stat, revents)
+
+ The loop is the event loop for which the idle object is
+ registered, the stat parameter is the ev.Stat object, and
+ revents is ev.STAT.
+
+ See also ev_stat_init() C function.
+
ev.READ (constant)
If this bit is set, the io watcher is ready to read. See also
@@ -227,6 +251,11 @@ ev.CHILD (constant)
If this bit is set, the watcher was triggered by a child signal.
See also EV_CHILD C definition.
+ev.STAT (constant)
+
+ If this bit is set, the watcher was triggered by a change in
+ attributes of the file system path. See also EV_STAT C definition.
+
-- ev.Loop object methods --
loop:fork()
@@ -444,6 +473,35 @@ child:getstatus()
- term_signal: (only if signaled == true) the number of the signal that
caused the termination of the child process.
+-- ev.Stat object methods --
+
+stat:start(loop [, is_daemon])
+
+ Start the stat watcher in the specified event loop. Optionally
+ make this watcher a "daemon" watcher which means that the event
+ loop will terminate even if this watcher has not triggered.
+
+ See also ev_stat_start() C function (document as ev_TYPE_start()).
+
+stat:stop(loop)
+
+ Unregister this stat watcher from the specified event loop.
+ Ensures that the watcher is neither active nor pending.
+
+ See also ev_stat_stop() C function (document as ev_TYPE_stop()).
+
+stat:getdata()
+
+ Returns a table with the following fields:
+ * - path: the file system path that is being watched;
+ * - interval: the specified interval;
+ * - attr: the most-recently detected attributes of the file in a form
+ * of table with the following fields: dev, ino, mode, nlink, uid, gid,
+ * rdev, size, atime, mtime, ctime corresponding to struct stat members
+ * (st_dev, st_ino, etc.);
+ * - prev: the previous attributes of the file with the same fields as
+ * attr fields.
+
EXCEPTION HANDLING NOTE:
If there is an exception when calling a watcher callback, the error
@@ -480,6 +538,5 @@ CALLING ev_loop() C API DIRECTLY:
TODO:
- * Add support for other watcher types (stat, periodic, embed,
- async, etc).
+ * Add support for other watcher types (periodic, embed, async, etc).
View
@@ -15,6 +15,7 @@
#include "signal_lua_ev.c"
#include "idle_lua_ev.c"
#include "child_lua_ev.c"
+#include "stat_lua_ev.c"
static const luaL_reg R[] = {
{"version", version},
@@ -54,6 +55,9 @@ LUALIB_API int luaopen_ev(lua_State *L) {
luaopen_ev_child(L);
lua_setfield(L, -2, "Child");
+ luaopen_ev_stat(L);
+ lua_setfield(L, -2, "Stat");
+
lua_pushnumber(L, EV_READ);
lua_setfield(L, -2, "READ");
@@ -72,6 +76,9 @@ LUALIB_API int luaopen_ev(lua_State *L) {
lua_pushnumber(L, EV_CHILD);
lua_setfield(L, -2, "CHILD");
+ lua_pushnumber(L, EV_STAT);
+ lua_setfield(L, -2, "STAT");
+
lua_pushnumber(L, EV_MINPRI);
lua_setfield(L, -2, "MINPRI");
View
@@ -23,6 +23,7 @@
#define SIGNAL_MT "ev{signal}"
#define IDLE_MT "ev{idle}"
#define CHILD_MT "ev{child}"
+#define STAT_MT "ev{stat}"
/**
* Special token to represent the uninitialized default loop. This is
@@ -72,6 +73,9 @@
#define check_child(L, narg) \
((struct ev_child*) luaL_checkudata((L), (narg), CHILD_MT))
+#define check_stat(L, narg) \
+ ((struct ev_stat*) luaL_checkudata((L), (narg), STAT_MT))
+
/**
* Copied from the lua source code lauxlib.c. It simply converts a
@@ -189,3 +193,15 @@ static int child_start(lua_State *L);
static int child_getpid(lua_State *L);
static int child_getrpid(lua_State *L);
static int child_getstatus(lua_State *L);
+
+/**
+ * Stat functions:
+ */
+static int luaopen_ev_stat(lua_State *L);
+static int create_stat_mt(lua_State *L);
+static int stat_new(lua_State* L);
+static void stat_cb(struct ev_loop* loop, ev_stat* sig, int revents);
+static int stat_stop(lua_State *L);
+static int stat_start(lua_State *L);
+static int stat_start(lua_State *L);
+static int stat_getdata(lua_State *L);
View
@@ -0,0 +1,180 @@
+/**
+ * Create a table for ev.STAT that gives access to the constructor for
+ * stat objects.
+ *
+ * [-0, +1, ?]
+ */
+static int luaopen_ev_stat(lua_State *L) {
+ lua_pop(L, create_stat_mt(L));
+
+ lua_createtable(L, 0, 1);
+
+ lua_pushcfunction(L, stat_new);
+ lua_setfield(L, -2, "new");
+
+ return 1;
+}
+
+/**
+ * Create the stat metatable in the registry.
+ *
+ * [-0, +1, ?]
+ */
+static int create_stat_mt(lua_State *L) {
+
+ static luaL_reg fns[] = {
+ { "stop", stat_stop },
+ { "start", stat_start },
+ { "getdata", stat_getdata },
+ { NULL, NULL }
+ };
+ luaL_newmetatable(L, STAT_MT);
+ add_watcher_mt(L);
+ luaL_register(L, NULL, fns);
+
+ return 1;
+}
+
+/**
+ * Create a new stat object. Arguments:
+ * 1 - callback function.
+ * 2 - path
+ * 3 - interval
+ *
+ * @see watcher_new()
+ *
+ * [+1, -0, ?]
+ */
+static int stat_new(lua_State* L) {
+ const char* path = luaL_checkstring(L, 2);
+ ev_tstamp interval = luaL_optint(L, 3, 0);
+ ev_stat* stat;
+
+ stat = watcher_new(L, sizeof(ev_stat), STAT_MT);
+ ev_stat_init(stat, &stat_cb, path, interval);
+ return 1;
+}
+
+/**
+ * @see watcher_cb()
+ *
+ * [+0, -0, m]
+ */
+static void stat_cb(struct ev_loop* loop, ev_stat* stat, int revents) {
+ watcher_cb(loop, stat, revents);
+}
+
+/**
+ * Stops the stat so it won't be called by the specified event loop.
+ *
+ * Usage:
+ * stat:stop(loop)
+ *
+ * [+0, -0, e]
+ */
+static int stat_stop(lua_State *L) {
+ ev_stat* stat = check_stat(L, 1);
+ struct ev_loop* loop = *check_loop_and_init(L, 2);
+
+ loop_stop_watcher(L, 2, 1);
+ ev_stat_stop(loop, stat);
+
+ return 0;
+}
+
+/**
+ * Starts the stat so it will be called by the specified event loop.
+ *
+ * Usage:
+ * stat:start(loop [, is_daemon])
+ *
+ * [+0, -0, e]
+ */
+static int stat_start(lua_State *L) {
+ ev_stat* stat = check_stat(L, 1);
+ struct ev_loop* loop = *check_loop_and_init(L, 2);
+ int is_daemon = lua_toboolean(L, 3);
+
+ ev_stat_start(loop, stat);
+ loop_start_watcher(L, 2, 1, is_daemon);
+
+ return 0;
+}
+
+#define set_attr(value) \
+ lua_pushliteral(L, #value); \
+ lua_pushinteger(L, stat->attr.st_##value); \
+ lua_settable(L, -3)
+
+#define set_prev(value) \
+ lua_pushliteral(L, #value); \
+ lua_pushinteger(L, stat->prev.st_##value); \
+ lua_settable(L, -3)
+
+/**
+ * Returns a table with the following fields:
+ * - path: the file system path that is being watched;
+ * - interval: the specified interval;
+ * - attr: the most-recently detected attributes of the file in a form
+ * of table with the following fields: dev, ino, mode, nlink, uid, gid,
+ * rdev, size, atime, mtime, ctime corresponding to struct stat members
+ * (st_dev, st_ino, etc.);
+ * - prev: the previous attributes of the file with the same fields as
+ * attr fields.
+ *
+ * Usage:
+ * stat:getdata()
+ *
+ * [+1, -0, e]
+ */
+static int stat_getdata(lua_State *L) {
+ ev_stat* stat = check_stat(L, 1);
+
+ lua_newtable(L);
+
+ lua_pushliteral(L, "path");
+ lua_pushstring(L, stat->path);
+ lua_settable(L, -3);
+
+ lua_pushliteral(L, "interval");
+ lua_pushinteger(L, stat->interval);
+ lua_settable(L, -3);
+
+ /* attr table */
+ lua_pushliteral(L, "attr");
+ lua_newtable(L);
+
+ set_attr(dev);
+ set_attr(ino);
+ set_attr(mode);
+ set_attr(nlink);
+ set_attr(uid);
+ set_attr(gid);
+ set_attr(rdev);
+ set_attr(size);
+ set_attr(atime);
+ set_attr(mtime);
+ set_attr(ctime);
+
+ lua_settable(L, -3);
+
+ /* prev table */
+ lua_pushliteral(L, "prev");
+ lua_newtable(L);
+
+ set_prev(dev);
+ set_prev(ino);
+ set_prev(mode);
+ set_prev(nlink);
+ set_prev(uid);
+ set_prev(gid);
+ set_prev(rdev);
+ set_prev(size);
+ set_prev(atime);
+ set_prev(mtime);
+ set_prev(ctime);
+
+ lua_settable(L, -3);
+
+ return 1;
+}
View
@@ -0,0 +1,59 @@
+local src_dir, build_dir = ...
+package.path = src_dir .. "?.lua;" .. package.path
+package.cpath = build_dir .. "?.so;" .. package.cpath
+
+local tap = require("tap")
+local ev = require("ev")
+local help = require("help")
+local dump = require("dumper").dump
+local ok = tap.ok
+
+local noleaks = help.collect_and_assert_no_watchers
+local loop = ev.Loop.default
+
+function touch_file(path)
+ ev.Timer.new(function(loop, timer, revents)
+ os.execute('touch ' .. path)
+ timer:stop(loop)
+ end, 1):start(loop)
+end
+
+function remove_file(path)
+ ev.Timer.new(function(loop, timer, revents)
+ os.execute('rm ' .. path)
+ timer:stop(loop)
+ end, 1):start(loop)
+end
+
+function test_basic()
+ local path = os.tmpname()
+ local stat = ev.Stat.new(function(loop, stat, revents)
+ local data = stat:getdata()
+ ok(data.path == path, 'got proper path')
+ ok(data.attr.nlink > 0, 'file exists')
+ ok(data.attr.size == 0, 'file has size of 0 bytes')
+ os.execute('rm ' .. path)
+ stat:stop(loop)
+ end, path)
+ stat:start(loop)
+ touch_file(path)
+ loop:loop()
+end
+
+function test_remove()
+ local path = os.tmpname()
+ local stat = ev.Stat.new(function(loop, stat, revents)
+ local data = stat:getdata()
+ ok(data.path == path, 'got proper path')
+ ok(data.attr.nlink == 0, 'file doesn\'t exist')
+ ok(data.prev.nlink > 0, 'file previously existed')
+ stat:stop(loop)
+ end, path)
+ stat:start(loop)
+ remove_file(path)
+ loop:loop()
+end
+
+noleaks(test_basic, "test_basic")
+noleaks(test_remove, "test_remove")
+

0 comments on commit c8728f4

Please sign in to comment.