diff --git a/c_src/bsd/main.c b/c_src/bsd/main.c new file mode 100644 index 0000000000000000000000000000000000000000..72bcb2b4f2a94cb21532807226c97a1848edbf2b --- /dev/null +++ b/c_src/bsd/main.c @@ -0,0 +1,34 @@ +#include <sys/time.h> +#include <sys/event.h> +#include <sys/stat.h> +#include <fcntl.h> +#include <stdlib.h> +#include <unistd.h> + +int main(int argc, char *argv[]) { + struct kevent event; + struct kevent change; + int fd, kq, nev; + if ((fd = open(argv[1], O_RDONLY)) == -1) return 1; + EV_SET(&change, fd, EVFILT_VNODE , EV_ADD + | EV_ENABLE + | EV_DISABLE + | EV_CLEAR + | EV_DELETE + | EV_EOF + | EV_RECEIPT + | EV_DISPATCH + | EV_ONESHOT, + NOTE_DELETE + | NOTE_RENAME + | NOTE_EXTEND + | NOTE_ATTRIB + | NOTE_LINK + | NOTE_REVOKE + | NOTE_WRITE, 0, 0); + if ((kq = kqueue()) == -1) return 1; + nev = kevent(kq, &change, 1, &event, 1, NULL); + if (nev < 0) { return 1; } else if (nev > 0) { if (event.flags & EV_ERROR) { return 1; } } + close(kq); + return 0; +} diff --git a/c_src/mac/cli.c b/c_src/mac/cli.c new file mode 100644 index 0000000000000000000000000000000000000000..84b0c861aff84c429a61585b99ce4e27b068a672 --- /dev/null +++ b/c_src/mac/cli.c @@ -0,0 +1,180 @@ +#include <getopt.h> +#include "cli.h" + +const char* cli_info_purpose = "A flexible command-line interface for the FSEvents API"; +const char* cli_info_usage = "Usage: fsevent_watch [OPTIONS]... [PATHS]..."; +const char* cli_info_help[] = { + " -h, --help you're looking at it", + " -V, --version print version number and exit", + " -p, --show-plist display the embedded Info.plist values", + " -s, --since-when=EventID fire historical events since ID", + " -l, --latency=seconds latency period (default='0.5')", + " -n, --no-defer enable no-defer latency modifier", + " -r, --watch-root watch for when the root path has changed", + // " -i, --ignore-self ignore current process", + " -F, --file-events provide file level event data", + " -f, --format=name output format (ignored)", + 0 +}; + +static void default_args (struct cli_info* args_info) +{ + args_info->since_when_arg = kFSEventStreamEventIdSinceNow; + args_info->latency_arg = 0.5; + args_info->no_defer_flag = false; + args_info->watch_root_flag = false; + args_info->ignore_self_flag = false; + args_info->file_events_flag = false; + args_info->mark_self_flag = false; + args_info->format_arg = 0; +} + +static void cli_parser_release (struct cli_info* args_info) +{ + unsigned int i; + + for (i=0; i < args_info->inputs_num; ++i) { + free(args_info->inputs[i]); + } + + if (args_info->inputs_num) { + free(args_info->inputs); + } + + args_info->inputs_num = 0; +} + +void cli_parser_init (struct cli_info* args_info) +{ + default_args(args_info); + + args_info->inputs = 0; + args_info->inputs_num = 0; +} + +void cli_parser_free (struct cli_info* args_info) +{ + cli_parser_release(args_info); +} + +static void cli_print_info_dict (const void *key, + const void *value, + void *context) +{ + CFStringRef entry = CFStringCreateWithFormat(NULL, NULL, + CFSTR("%@:\n %@"), key, value); + if (entry) { + CFShow(entry); + CFRelease(entry); + } +} + +void cli_show_plist (void) +{ + CFBundleRef mainBundle = CFBundleGetMainBundle(); + CFRetain(mainBundle); + CFDictionaryRef mainBundleDict = CFBundleGetInfoDictionary(mainBundle); + if (mainBundleDict) { + CFRetain(mainBundleDict); + printf("Embedded Info.plist metadata:\n\n"); + CFDictionaryApplyFunction(mainBundleDict, cli_print_info_dict, NULL); + CFRelease(mainBundleDict); + } + CFRelease(mainBundle); + printf("\n"); +} + +void cli_print_version (void) +{ + printf("%s %s\n\n", "VXZ", "1.0"); +} + +void cli_print_help (void) +{ + cli_print_version(); + + printf("\n%s\n", cli_info_purpose); + printf("\n%s\n", cli_info_usage); + printf("\n"); + + int i = 0; + while (cli_info_help[i]) { + printf("%s\n", cli_info_help[i++]); + } +} + +int cli_parser (int argc, const char** argv, struct cli_info* args_info) +{ + static struct option longopts[] = { + { "help", no_argument, NULL, 'h' }, + { "version", no_argument, NULL, 'V' }, + { "show-plist", no_argument, NULL, 'p' }, + { "since-when", required_argument, NULL, 's' }, + { "latency", required_argument, NULL, 'l' }, + { "no-defer", no_argument, NULL, 'n' }, + { "watch-root", no_argument, NULL, 'r' }, + { "ignore-self", no_argument, NULL, 'i' }, + { "file-events", no_argument, NULL, 'F' }, + { "mark-self", no_argument, NULL, 'm' }, + { "format", required_argument, NULL, 'f' }, + { 0, 0, 0, 0 } + }; + + const char* shortopts = "hVps:l:nriFf:"; + + int c = -1; + + while ((c = getopt_long(argc, (char * const*)argv, shortopts, longopts, NULL)) != -1) { + switch(c) { + case 's': // since-when + args_info->since_when_arg = strtoull(optarg, NULL, 0); + break; + case 'l': // latency + args_info->latency_arg = strtod(optarg, NULL); + break; + case 'n': // no-defer + args_info->no_defer_flag = true; + break; + case 'r': // watch-root + args_info->watch_root_flag = true; + break; + case 'i': // ignore-self + args_info->ignore_self_flag = true; + break; + case 'F': // file-events + args_info->file_events_flag = true; + break; + case 'm': // mark-self + args_info->mark_self_flag = true; + break; + case 'f': // format + // XXX: ignored + break; + case 'V': // version + cli_print_version(); + exit(EXIT_SUCCESS); + case 'p': // show-plist + cli_show_plist(); + exit(EXIT_SUCCESS); + case 'h': // help + case '?': // invalid option + case ':': // missing argument + cli_print_help(); + exit((c == 'h') ? EXIT_SUCCESS : EXIT_FAILURE); + } + } + + if (optind < argc) { + int i = 0; + args_info->inputs_num = (unsigned int)(argc - optind); + args_info->inputs = + (char**)(malloc ((args_info->inputs_num)*sizeof(char*))); + while (optind < argc) + if (argv[optind++] != argv[0]) { + args_info->inputs[i++] = strdup(argv[optind-1]); + } + } + + return EXIT_SUCCESS; +} + diff --git a/c_src/mac/cli.h b/c_src/mac/cli.h new file mode 100644 index 0000000000000000000000000000000000000000..f176cf0027f033ebf82e1b76d4973feb3c331108 --- /dev/null +++ b/c_src/mac/cli.h @@ -0,0 +1,36 @@ +#ifndef CLI_H +#define CLI_H + +#include "common.h" + +#ifndef CLI_NAME +#define CLI_NAME "fsevent_watch" +#endif /* CLI_NAME */ + +struct cli_info { + UInt64 since_when_arg; + double latency_arg; + bool no_defer_flag; + bool watch_root_flag; + bool ignore_self_flag; + bool file_events_flag; + bool mark_self_flag; + int format_arg; + + char** inputs; + unsigned inputs_num; +}; + +extern const char* cli_info_purpose; +extern const char* cli_info_usage; +extern const char* cli_info_help[]; + +void cli_print_help(void); +void cli_print_version(void); + +int cli_parser (int argc, const char** argv, struct cli_info* args_info); +void cli_parser_init (struct cli_info* args_info); +void cli_parser_free (struct cli_info* args_info); + + +#endif /* CLI_H */ diff --git a/c_src/mac/cli.h.gch b/c_src/mac/cli.h.gch new file mode 100644 index 0000000000000000000000000000000000000000..1759022172e368b87231c340e27bc560b2f621c3 Binary files /dev/null and b/c_src/mac/cli.h.gch differ diff --git a/c_src/mac/common.h b/c_src/mac/common.h new file mode 100644 index 0000000000000000000000000000000000000000..70bd64865d8423a51b9b95cf9b7cec564ed8abe3 --- /dev/null +++ b/c_src/mac/common.h @@ -0,0 +1,55 @@ +#ifndef fsevent_watch_common_h +#define fsevent_watch_common_h + +#include <CoreFoundation/CoreFoundation.h> +#include <CoreServices/CoreServices.h> +#include <unistd.h> +#include <fcntl.h> +#include "compat.h" + +#define _str(s) #s +#define _xstr(s) _str(s) + +#define COMPILED_AT __DATE__ " " __TIME__ + +#define FPRINTF_FLAG_CHECK(flags, flag, msg, fd) \ + do { \ + if ((flags) & (flag)) { \ + fprintf(fd, "%s\n", msg); } } \ + while (0) + +#define FLAG_CHECK_STDERR(flags, flag, msg) \ + FPRINTF_FLAG_CHECK(flags, flag, msg, stderr) + +/* + * FSEVENTSBITS: + * generated by `make printflags` (and pasted here) + * flags MUST be ordered (bits ascending) and sorted + * + * idea from: http://www.openbsd.org/cgi-bin/cvsweb/src/sbin/ifconfig/ifconfig.c (see printb()) + */ +#define FSEVENTSBITS \ +"\1mustscansubdirs\2userdropped\3kerneldropped\4eventidswrapped\5historydone\6rootchanged\7mount\10unmount\11created\12removed\13inodemetamod\14renamed\15modified\16finderinfomod\17changeowner\20xattrmod\21isfile\22isdir\23issymlink\24ownevent" + +static inline void +sprintb(char *buf, unsigned short v, char *bits) +{ + int i, any = 0; + char c; + char *bufp = buf; + + while ((i = *bits++)) { + if (v & (1 << (i-1))) { + if (any) + *bufp++ = ','; + any = 1; + for (; (c = *bits) > 32; bits++) + *bufp++ = c; + } else + for (; *bits > 32; bits++) + ; + } + *bufp = '\0'; +} + +#endif /* fsevent_watch_common_h */ diff --git a/c_src/mac/common.h.gch b/c_src/mac/common.h.gch new file mode 100644 index 0000000000000000000000000000000000000000..6d5370128513b2a20009a1a0c060073633b1bc88 Binary files /dev/null and b/c_src/mac/common.h.gch differ diff --git a/c_src/mac/compat.c b/c_src/mac/compat.c new file mode 100644 index 0000000000000000000000000000000000000000..ab84dfd41c202df72af5a9380a27e9a659adbf3e --- /dev/null +++ b/c_src/mac/compat.c @@ -0,0 +1,25 @@ +#include "compat.h" + +#if MAC_OS_X_VERSION_MAX_ALLOWED < 1060 +FSEventStreamCreateFlags kFSEventStreamCreateFlagIgnoreSelf = 0x00000008; +#endif + +#if MAC_OS_X_VERSION_MAX_ALLOWED < 1070 +FSEventStreamCreateFlags kFSEventStreamCreateFlagFileEvents = 0x00000010; +FSEventStreamEventFlags kFSEventStreamEventFlagItemCreated = 0x00000100; +FSEventStreamEventFlags kFSEventStreamEventFlagItemRemoved = 0x00000200; +FSEventStreamEventFlags kFSEventStreamEventFlagItemInodeMetaMod = 0x00000400; +FSEventStreamEventFlags kFSEventStreamEventFlagItemRenamed = 0x00000800; +FSEventStreamEventFlags kFSEventStreamEventFlagItemModified = 0x00001000; +FSEventStreamEventFlags kFSEventStreamEventFlagItemFinderInfoMod = 0x00002000; +FSEventStreamEventFlags kFSEventStreamEventFlagItemChangeOwner = 0x00004000; +FSEventStreamEventFlags kFSEventStreamEventFlagItemXattrMod = 0x00008000; +FSEventStreamEventFlags kFSEventStreamEventFlagItemIsFile = 0x00010000; +FSEventStreamEventFlags kFSEventStreamEventFlagItemIsDir = 0x00020000; +FSEventStreamEventFlags kFSEventStreamEventFlagItemIsSymlink = 0x00040000; +#endif + +#if MAC_OS_X_VERSION_MAX_ALLOWED < 1090 +FSEventStreamCreateFlags kFSEventStreamCreateFlagMarkSelf = 0x00000020; +FSEventStreamEventFlags kFSEventStreamEventFlagOwnEvent = 0x00080000; +#endif diff --git a/c_src/mac/compat.h b/c_src/mac/compat.h new file mode 100644 index 0000000000000000000000000000000000000000..d44c0c88506f0967cd593dd8bd9bbeadfdc85f7d --- /dev/null +++ b/c_src/mac/compat.h @@ -0,0 +1,47 @@ +/** + * @headerfile compat.h + * FSEventStream flag compatibility shim + * + * In order to compile a binary against an older SDK yet still support the + * features present in later OS releases, we need to define any missing enum + * constants not present in the older SDK. This allows us to safely defer + * feature detection to runtime (and avoid recompilation). + */ + + +#ifndef fsevent_watch_compat_h +#define fsevent_watch_compat_h + +#ifndef __CORESERVICES__ +#include <CoreServices/CoreServices.h> +#endif // __CORESERVICES__ + +#if MAC_OS_X_VERSION_MAX_ALLOWED < 1060 +// ignoring events originating from the current process introduced in 10.6 +extern FSEventStreamCreateFlags kFSEventStreamCreateFlagIgnoreSelf; +#endif + +#if MAC_OS_X_VERSION_MAX_ALLOWED < 1070 +// file-level events introduced in 10.7 +extern FSEventStreamCreateFlags kFSEventStreamCreateFlagFileEvents; +extern FSEventStreamEventFlags kFSEventStreamEventFlagItemCreated, + kFSEventStreamEventFlagItemRemoved, + kFSEventStreamEventFlagItemInodeMetaMod, + kFSEventStreamEventFlagItemRenamed, + kFSEventStreamEventFlagItemModified, + kFSEventStreamEventFlagItemFinderInfoMod, + kFSEventStreamEventFlagItemChangeOwner, + kFSEventStreamEventFlagItemXattrMod, + kFSEventStreamEventFlagItemIsFile, + kFSEventStreamEventFlagItemIsDir, + kFSEventStreamEventFlagItemIsSymlink; +#endif + +#if MAC_OS_X_VERSION_MAX_ALLOWED < 1090 +// marking, rather than ignoring, events originating from the current process introduced in 10.9 +extern FSEventStreamCreateFlags kFSEventStreamCreateFlagMarkSelf; +extern FSEventStreamEventFlags kFSEventStreamEventFlagOwnEvent; +#endif + + +#endif // fsevent_watch_compat_h diff --git a/c_src/mac/compat.h.gch b/c_src/mac/compat.h.gch new file mode 100644 index 0000000000000000000000000000000000000000..e792a5ea56ba8ec205919cc9f183543db7e8ee04 Binary files /dev/null and b/c_src/mac/compat.h.gch differ diff --git a/c_src/mac/main.c b/c_src/mac/main.c new file mode 100644 index 0000000000000000000000000000000000000000..7b31f27be00815186a52ec4a913083c6dde7d234 --- /dev/null +++ b/c_src/mac/main.c @@ -0,0 +1,234 @@ +#include "common.h" +#include "cli.h" + +// TODO: set on fire. cli.{h,c} handle both parsing and defaults, so there's +// no need to set those here. also, in order to scope metadata by path, +// each stream will need its own configuration... so this won't work as +// a global any more. In the end the goal is to make the output format +// able to declare not just that something happened and what flags were +// attached, but what path it was watching that caused those events (so +// that the path itself can be used for routing that information to the +// relevant callback). +// +// Structure for storing metadata parsed from the commandline +static struct { + FSEventStreamEventId sinceWhen; + CFTimeInterval latency; + FSEventStreamCreateFlags flags; + CFMutableArrayRef paths; + int format; +} config = { + (UInt64) kFSEventStreamEventIdSinceNow, + (double) 0.3, + (CFOptionFlags) kFSEventStreamCreateFlagNone, + NULL, + 0 +}; + +// Prototypes +static void append_path(const char* path); +static inline void parse_cli_settings(int argc, const char* argv[]); +static void callback(FSEventStreamRef streamRef, + void* clientCallBackInfo, + size_t numEvents, + void* eventPaths, + const FSEventStreamEventFlags eventFlags[], + const FSEventStreamEventId eventIds[]); + + +static void append_path(const char* path) +{ + CFStringRef pathRef = CFStringCreateWithCString(kCFAllocatorDefault, + path, + kCFStringEncodingUTF8); + CFArrayAppendValue(config.paths, pathRef); + CFRelease(pathRef); +} + +// Parse commandline settings +static inline void parse_cli_settings(int argc, const char* argv[]) +{ + // runtime os version detection + SInt32 osMajorVersion, osMinorVersion; + if (!(Gestalt(gestaltSystemVersionMajor, &osMajorVersion) == noErr)) { + osMajorVersion = 0; + } + if (!(Gestalt(gestaltSystemVersionMinor, &osMinorVersion) == noErr)) { + osMinorVersion = 0; + } + + if ((osMajorVersion == 10) & (osMinorVersion < 5)) { + fprintf(stderr, "The FSEvents API is unavailable on this version of macos!\n"); + exit(EXIT_FAILURE); + } + + struct cli_info args_info; + cli_parser_init(&args_info); + + if (cli_parser(argc, argv, &args_info) != 0) { + exit(EXIT_FAILURE); + } + + config.paths = CFArrayCreateMutable(NULL, + (CFIndex)0, + &kCFTypeArrayCallBacks); + + config.sinceWhen = args_info.since_when_arg; + config.latency = args_info.latency_arg; + config.format = args_info.format_arg; + + if (args_info.no_defer_flag) { + config.flags |= kFSEventStreamCreateFlagNoDefer; + } + if (args_info.watch_root_flag) { + config.flags |= kFSEventStreamCreateFlagWatchRoot; + } + + if (args_info.ignore_self_flag) { + if ((osMajorVersion == 10) & (osMinorVersion >= 6)) { + config.flags |= kFSEventStreamCreateFlagIgnoreSelf; + } else { + fprintf(stderr, "MacOSX 10.6 or later is required for --ignore-self\n"); + exit(EXIT_FAILURE); + } + } + + if (args_info.file_events_flag) { + if ((osMajorVersion == 10) & (osMinorVersion >= 7)) { + config.flags |= kFSEventStreamCreateFlagFileEvents; + } else { + fprintf(stderr, "MacOSX 10.7 or later required for --file-events\n"); + exit(EXIT_FAILURE); + } + } + + if (args_info.mark_self_flag) { + if ((osMajorVersion == 10) & (osMinorVersion >= 9)) { + config.flags |= kFSEventStreamCreateFlagMarkSelf; + } else { + fprintf(stderr, "MacOSX 10.9 or later required for --mark-self\n"); + exit(EXIT_FAILURE); + } + } + + if (args_info.inputs_num == 0) { + append_path("."); + } else { + for (unsigned int i=0; i < args_info.inputs_num; ++i) { + append_path(args_info.inputs[i]); + } + } + + cli_parser_free(&args_info); + +#ifdef DEBUG + fprintf(stderr, "config.sinceWhen %llu\n", config.sinceWhen); + fprintf(stderr, "config.latency %f\n", config.latency); + fprintf(stderr, "config.flags %#.8x\n", config.flags); + + FLAG_CHECK_STDERR(config.flags, kFSEventStreamCreateFlagUseCFTypes, + " Using CF instead of C types"); + FLAG_CHECK_STDERR(config.flags, kFSEventStreamCreateFlagNoDefer, + " NoDefer latency modifier enabled"); + FLAG_CHECK_STDERR(config.flags, kFSEventStreamCreateFlagWatchRoot, + " WatchRoot notifications enabled"); + FLAG_CHECK_STDERR(config.flags, kFSEventStreamCreateFlagIgnoreSelf, + " IgnoreSelf enabled"); + FLAG_CHECK_STDERR(config.flags, kFSEventStreamCreateFlagFileEvents, + " FileEvents enabled"); + + fprintf(stderr, "config.paths\n"); + + long numpaths = CFArrayGetCount(config.paths); + + for (long i = 0; i < numpaths; i++) { + char path[PATH_MAX]; + CFStringGetCString(CFArrayGetValueAtIndex(config.paths, i), + path, + PATH_MAX, + kCFStringEncodingUTF8); + fprintf(stderr, " %s\n", path); + } + + fprintf(stderr, "\n"); +#endif +} + +static void callback(__attribute__((unused)) FSEventStreamRef streamRef, + __attribute__((unused)) void* clientCallBackInfo, + size_t numEvents, + void* eventPaths, + const FSEventStreamEventFlags eventFlags[], + const FSEventStreamEventId eventIds[]) +{ + char** paths = eventPaths; + char *buf = calloc(sizeof(FSEVENTSBITS), sizeof(char)); + + for (size_t i = 0; i < numEvents; i++) { + sprintb(buf, eventFlags[i], FSEVENTSBITS); + printf("%llu\t%#.8x=[%s]\t%s\n", eventIds[i], eventFlags[i], buf, paths[i]); + } + fflush(stdout); + free(buf); + + if (fcntl(STDIN_FILENO, F_GETFD) == -1) { + CFRunLoopStop(CFRunLoopGetCurrent()); + } +} + +static void stdin_callback(CFFileDescriptorRef fdref, CFOptionFlags callBackTypes, void *info) +{ + char buf[1024]; + int nread; + + do { + nread = read(STDIN_FILENO, buf, sizeof(buf)); + if (nread == -1 && errno == EAGAIN) { + CFFileDescriptorEnableCallBacks(fdref, kCFFileDescriptorReadCallBack); + return; + } else if (nread == 0) { + exit(1); + return; + } + } while (nread > 0); +} + +int main(int argc, const char* argv[]) +{ + parse_cli_settings(argc, argv); + + FSEventStreamContext context = {0, NULL, NULL, NULL, NULL}; + FSEventStreamRef stream; + stream = FSEventStreamCreate(kCFAllocatorDefault, + (FSEventStreamCallback)&callback, + &context, + config.paths, + config.sinceWhen, + config.latency, + config.flags); + +#ifdef DEBUG + FSEventStreamShow(stream); + fprintf(stderr, "\n"); +#endif + + fcntl(STDIN_FILENO, F_SETFL, O_NONBLOCK); + + CFFileDescriptorRef fdref = CFFileDescriptorCreate(kCFAllocatorDefault, STDIN_FILENO, false, stdin_callback, NULL); + CFFileDescriptorEnableCallBacks(fdref, kCFFileDescriptorReadCallBack); + CFRunLoopSourceRef source = CFFileDescriptorCreateRunLoopSource(kCFAllocatorDefault, fdref, 0); + CFRunLoopAddSource(CFRunLoopGetCurrent(), source, kCFRunLoopDefaultMode); + CFRelease(source); + + FSEventStreamScheduleWithRunLoop(stream, + CFRunLoopGetCurrent(), + kCFRunLoopDefaultMode); + FSEventStreamStart(stream); + CFRunLoopRun(); + FSEventStreamFlushSync(stream); + FSEventStreamStop(stream); + + return 0; +} + +// vim: ts=2 sts=2 et sw=2 diff --git a/c_src/windows/ArgumentParser.cs b/c_src/windows/ArgumentParser.cs new file mode 100644 index 0000000000000000000000000000000000000000..b7dc8fcbbea461a4a11f2815c396243421065118 --- /dev/null +++ b/c_src/windows/ArgumentParser.cs @@ -0,0 +1,143 @@ +using System; +using System.IO; +using System.Collections.Generic; +using System.Text.RegularExpressions; + +namespace De.Thekid.INotify +{ + + /// See also <a href="http://linux.die.net/man/1/inotifywait">inotifywait(1) - Linux man page</a> + public class ArgumentParser + { + + /// Helper method for parser + protected string Value(string[] args, int i, string name) + { + if (i > args.Length) + { + throw new ArgumentException("Argument " + name + " requires a value"); + } + return args[i]; + } + + /// Tokenizes "printf" format string into an array of strings + protected string[] TokenizeFormat(string arg) + { + var result = new List<string>(); + var tokens = arg.Split(new char[]{ '%' }); + foreach (var token in tokens) + { + if (token.Length == 0) continue; + + if ("efwT".IndexOf(token[0]) != -1) + { + result.Add(token[0].ToString()); + if (token.Length > 1) + { + result.Add(token.Substring(1)); + } + } + else + { + result.Add(token); + } + } + return result.ToArray(); + } + + private void ParseArgument(string option, string[] args, ref int i, Arguments result) + { + if ("--recursive" == option || "-r" == option) + { + result.Recursive = true; + } + else if ("--monitor" == option || "-m" == option) + { + result.Monitor = true; + } + else if ("--quiet" == option || "-q" == option) + { + result.Quiet = true; + } + else if ("--event" == option || "-e" == option) + { + result.Events = new List<string>(Value(args, ++i, "event").Split(',')); + } + else if ("--format" == option) + { + result.Format = TokenizeFormat(Value(args, ++i, "format")); + } + else if ("--exclude" == option) + { + result.Exclude = new Regex(Value(args, ++i, "exclude")); + } + else if ("--excludei" == option) + { + result.Exclude = new Regex(Value(args, ++i, "exclude"), RegexOptions.IgnoreCase); + } + else if (option.StartsWith("--event=")) + { + result.Events = new List<string>(option.Split(new Char[]{'='}, 2)[1].Split(',')); + } + else if (option.StartsWith("--format=")) + { + result.Format = TokenizeFormat(option.Split(new Char[]{'='}, 2)[1]); + } + else if (option.StartsWith("--exclude=")) + { + result.Exclude = new Regex(option.Split(new Char[]{'='}, 2)[1]); + } + else if (option.StartsWith("--excludei=")) + { + result.Exclude = new Regex(option.Split(new Char[]{'='}, 2)[1], RegexOptions.IgnoreCase); + } + else if (Directory.Exists(option)) + { + result.Paths.Add(System.IO.Path.GetFullPath(option)); + } + } + + /// Creates a new argument parser and parses the arguments + public Arguments Parse(string[] args) + { + var result = new Arguments(); + for (var i = 0; i < args.Length; i++) + { + if (!args[i].StartsWith("--") && args[i].StartsWith("-") && args[i].Length > 2) + { + string options = args[i]; + for (var j = 1; j < options.Length; j++) + { + ParseArgument("-" + options.Substring(j, 1), args, ref i, result); + } + } + else + { + ParseArgument(args[i], args, ref i, result); + } + } + return result; + } + + /// Usage + public void PrintUsage(string name, TextWriter writer) + { + writer.WriteLine("Usage: " + name + " [options] path [...]"); + writer.WriteLine(); + writer.WriteLine("Options:"); + writer.WriteLine("-r/--recursive: Recursively watch all files and subdirectories inside path"); + writer.WriteLine("-m/--monitor: Keep running until killed (e.g. via Ctrl+C)"); + writer.WriteLine("-q/--quiet: Do not output information about actions"); + writer.WriteLine("-e/--event list: Which events (create, modify, delete, move) to watch, comma-separated. Default: all"); + writer.WriteLine("--format format: Format string for output."); + writer.WriteLine("--exclude: Do not process any events whose filename matches the specified regex"); + writer.WriteLine("--excludei: Ditto, case-insensitive"); + writer.WriteLine(); + writer.WriteLine("Formats:"); + writer.WriteLine("%e : Event name"); + writer.WriteLine("%f : File name"); + writer.WriteLine("%w : Path name"); + writer.WriteLine("%T : Current date and time"); + } + } +} diff --git a/c_src/windows/Arguments.cs b/c_src/windows/Arguments.cs new file mode 100644 index 0000000000000000000000000000000000000000..b6efd64d8c71a28be51b51a3e5f88208b114e9c6 --- /dev/null +++ b/c_src/windows/Arguments.cs @@ -0,0 +1,32 @@ +using System; +using System.Collections.Generic; +using System.Text.RegularExpressions; + +namespace De.Thekid.INotify +{ + + public class Arguments + { + // Default values + private List<string> _Events = new List<string>(new string[] { "create", "modify", "delete", "move" }); + private string[] _Format = new string[] { "w", " ", "e", " ", "f" }; + private List<string> _Paths = new List<string>(); + + public bool Recursive { get; set; } + public bool Monitor { get; set; } + public bool Quiet { get; set; } + public List<string> Paths { + get { return this._Paths; } + } + public string[] Format { + get { return this._Format; } + set { this._Format = value; } + } + public List<string> Events + { + get { return this._Events; } + set { this._Events = value; } + } + public Regex Exclude { get; set; } + } +} diff --git a/c_src/windows/AssemblyInfo.cs b/c_src/windows/AssemblyInfo.cs new file mode 100644 index 0000000000000000000000000000000000000000..6a90948862bbbbc4aa8fd001cbb584483fce3464 --- /dev/null +++ b/c_src/windows/AssemblyInfo.cs @@ -0,0 +1,16 @@ +using System.Reflection; +using System.Runtime.CompilerServices; +using System.Runtime.InteropServices; + + +[assembly: AssemblyTitle("https://github.com/thekid/inotify-win")] +[assembly: AssemblyDescription("A port of the inotifywait tool for Windows")] +[assembly: AssemblyConfiguration("")] +[assembly: AssemblyCompany("Timm Friebe")] +[assembly: AssemblyProduct("inotify-win")] +[assembly: AssemblyCopyright("Copyright © 2012 - 2015 Timm Friebe")] +[assembly: AssemblyTrademark("")] +[assembly: AssemblyCulture("")] +[assembly: AssemblyVersion("1.5.1.0")] +[assembly: ComVisible(false)] +[assembly: Guid("4254314b-ae21-4e2f-ba52-d6f3d83a86b5")] diff --git a/c_src/windows/Makefile b/c_src/windows/Makefile new file mode 100644 index 0000000000000000000000000000000000000000..861eaa509c7ad19c8dbf2fac66d08e58a03521ab --- /dev/null +++ b/c_src/windows/Makefile @@ -0,0 +1,15 @@ +ifeq ($(OS),Windows_NT) + BASE=$(shell cd "$(WINDIR)";pwd -W) + CSC?=$(shell ls -1d $(BASE)/Microsoft.NET/Framework/v*|sort -rn|head -1)/csc.exe +else + CSC?=csc +endif + +MKDIR_P = mkdir -p + +inotifywait.exe: c_src/windows/*.cs + ${MKDIR_P} priv + $(CSC) //nologo //target:exe //out:priv\\$@ c_src\\windows\\*.cs + +clean: + -rm priv\\inotifywait.exe diff --git a/c_src/windows/Runner.cs b/c_src/windows/Runner.cs new file mode 100644 index 0000000000000000000000000000000000000000..15adbf86df6c16f283a2863c74c33c476b287321 --- /dev/null +++ b/c_src/windows/Runner.cs @@ -0,0 +1,209 @@ +using System; +using System.Threading; +using System.IO; +using System.Collections.Generic; + +namespace De.Thekid.INotify +{ + // List of possible changes + public enum Change + { + CREATE, MODIFY, DELETE, MOVED_FROM, MOVED_TO + } + + /// Main class + public class Runner + { + // Mappings + protected static Dictionary<WatcherChangeTypes, Change> Changes = new Dictionary<WatcherChangeTypes, Change>(); + + private List<Thread> _threads = new List<Thread>(); + private bool _stopMonitoring = false; + private ManualResetEventSlim _stopMonitoringEvent; + private object _notificationReactionLock = new object(); + private Arguments _args = null; + + static Runner() + { + Changes[WatcherChangeTypes.Created]= Change.CREATE; + Changes[WatcherChangeTypes.Changed]= Change.MODIFY; + Changes[WatcherChangeTypes.Deleted]= Change.DELETE; + } + + public Runner(Arguments args) + { + _args = args; + } + + /// Callback for errors in watcher + protected void OnWatcherError(object source, ErrorEventArgs e) + { + Console.Error.WriteLine("*** {0}", e.GetException()); + } + + private void OnWatcherNotification(object sender, FileSystemEventArgs e) + { + FileSystemWatcher w = (FileSystemWatcher)sender; + HandleNotification(w, e, () => Output(Console.Out, _args.Format, w, Changes[e.ChangeType], e.Name)); + } + + private void OnRenameNotification(object sender, RenamedEventArgs e) + { + FileSystemWatcher w = (FileSystemWatcher)sender; + HandleNotification(w, e, () => + { + Output(Console.Out, _args.Format, w, Change.MOVED_FROM, e.OldName); + Output(Console.Out, _args.Format, w, Change.MOVED_TO, e.Name); + }); + } + + private void HandleNotification(FileSystemWatcher sender, FileSystemEventArgs e, Action outputAction) + { + FileSystemWatcher w = (FileSystemWatcher)sender; + // Lock so we don't output more than one change if we were only supposed to watch for one. + // And to serialize access to the console + lock (_notificationReactionLock) + { + // if only looking for one change and another thread beat us to it, return + if (!_args.Monitor && _stopMonitoring) + { + return; + } + + if (null != _args.Exclude && _args.Exclude.IsMatch(e.FullPath)) + { + return; + } + + outputAction(); + + // If only looking for one change, signal to stop + if (!_args.Monitor) + { + _stopMonitoring = true; + _stopMonitoringEvent.Set(); + } + } + } + + /// Output method + protected void Output(TextWriter writer, string[] tokens, FileSystemWatcher source, Change type, string name) + { + foreach (var token in tokens) + { + var path = Path.Combine(source.Path, name); + switch (token[0]) + { + case 'e': + writer.Write(type); + if (Directory.Exists(path)) + { + writer.Write(",ISDIR"); + } + break; + case 'f': writer.Write(Path.GetFileName(path)); break; + case 'w': writer.Write(Path.Combine(source.Path, Path.GetDirectoryName(path))); break; + case 'T': writer.Write(DateTime.Now); break; + default: writer.Write(token); break; + } + } + writer.WriteLine(); + } + + public void Stop(object data) { + string s = Console.ReadLine(); + _stopMonitoring = true; + _stopMonitoringEvent.Set(); + + } + + public void Processor(object data) { + string path = (string)data; + using (var w = new FileSystemWatcher { + Path = path, + IncludeSubdirectories = _args.Recursive, + Filter = "*.*" + }) { + w.Error += new ErrorEventHandler(OnWatcherError); + + // Parse "events" argument + WatcherChangeTypes changes = 0; + if (_args.Events.Contains("create")) + { + changes |= WatcherChangeTypes.Created; + w.Created += new FileSystemEventHandler(OnWatcherNotification); + } + if (_args.Events.Contains("modify")) + { + changes |= WatcherChangeTypes.Changed; + w.Changed += new FileSystemEventHandler(OnWatcherNotification); + } + if (_args.Events.Contains("delete")) + { + changes |= WatcherChangeTypes.Deleted; + w.Deleted += new FileSystemEventHandler(OnWatcherNotification); + } + if (_args.Events.Contains("move")) + { + changes |= WatcherChangeTypes.Renamed; + w.Renamed += new RenamedEventHandler(OnRenameNotification); + } + + // Main loop + if (!_args.Quiet) + { + Console.Error.WriteLine( + "===> {0} for {1} in {2}{3} for {4}", + _args.Monitor ? "Monitoring" : "Watching", + changes, + path, + _args.Recursive ? " -r" : "", + String.Join(", ", _args.Events.ToArray()) + ); + } + w.EnableRaisingEvents = true; + _stopMonitoringEvent.Wait(); + } + } + + /// Entry point + public int Run() + { + using (_stopMonitoringEvent = new ManualResetEventSlim(initialState: false)) + { + foreach (var path in _args.Paths) + { + Thread t = new Thread(new ParameterizedThreadStart(Processor)); + t.Start(path); + _threads.Add(t); + } + Thread stop = new Thread(new ParameterizedThreadStart(Stop)); + stop.Start(""); + _stopMonitoringEvent.Wait(); + foreach (var thread in _threads) + { + if (thread.IsAlive) + thread.Abort(); + thread.Join(); + } + return 0; + } + } + + /// Entry point method + public static int Main(string[] args) + { + var p = new ArgumentParser(); + + // Show usage if no args or standard "help" args are given + if (0 == args.Length || args[0].Equals("-?") || args[0].Equals("--help")) + { + p.PrintUsage("inotifywait", Console.Error); + return 1; + } + + // Run! + return new Runner(p.Parse(args)).Run(); + } + } +} diff --git a/lib/exfswatch.ex b/lib/exfswatch.ex index 89196466aaea6ba98e403450c299b120fe7bd8d8..1026ed2a72551395a5e764f96fc7a168b81c883e 100644 --- a/lib/exfswatch.ex +++ b/lib/exfswatch.ex @@ -18,10 +18,11 @@ defmodule ExFSWatch do end @backend (case :os.type() do - {:unix, :darwin} -> :fsevents - {:unix, :linux} -> :inotifywait - {:"win32", :nt} -> :"inotifywait_win32" - _ -> nil + {:unix, :darwin} -> ExFSWatch.Backends.Fsevents + {:unix, :freebsd} -> ExFSWatch.Backends.Kqueue + {:unix, :linux} -> ExFSWatch.Backends.InotifyWait + {:win32, :nt} -> ExFSWatch.Backends.InotifyWaitWin32 + _ -> nil end) def start(_, _) do diff --git a/lib/exfswatch/backends/fsevents.ex b/lib/exfswatch/backends/fsevents.ex new file mode 100644 index 0000000000000000000000000000000000000000..314870992d48b225af2038c220cc6f7d38885784 --- /dev/null +++ b/lib/exfswatch/backends/fsevents.ex @@ -0,0 +1,33 @@ +alias ExFSWatch.Utils + +defmodule ExFSWatch.Backends.Fsevents do + + def find_executable do + :code.priv_dir(:exfswatch) ++ '/mac_listener' + end + + def start_port(path, listener_extra_args) do + path = path |> Utils.format_path() + args = listener_extra_args ++ ['-F' | path] + Port.open( + {:spawn_executable, find_executable()}, + [:stream, :exit_status, {:line, 16384}, {:args, args}, {:cd, System.tmp_dir!()}] + ) + end + + def known_events do + [ :mustscansubdirs, :userdropped, :kerneldropped, :eventidswrapped, :historydone, + :rootchanged, :mount, :unmount, :created, :removed, :inodemetamod, :renamed, :modified, + :finderinfomod, :changeowner, :xattrmod, :isfile, :isdir, :issymlink, :ownevent, + ] + end + + def line_to_event(line) do + [_event_id, flags, path] = :string.tokens(line, [?\t]) + [_, flags] = :string.tokens(flags, [?=]) + {:ok, t, _} = :erl_scan.string(flags ++ '.') + {:ok, flags} = :erl_parse.parse_term(t) + {path, flags} + end + +end diff --git a/lib/exfswatch/backends/inotify_wait.ex b/lib/exfswatch/backends/inotify_wait.ex new file mode 100644 index 0000000000000000000000000000000000000000..9309c1096591702f6e812b51ecac27db2c713096 --- /dev/null +++ b/lib/exfswatch/backends/inotify_wait.ex @@ -0,0 +1,65 @@ +alias ExFSWatch.Utils + +defmodule ExFSWatch.Backends.InotifyWait do + + def find_executable do + System.find_executable("sh") |> to_charlist + end + + def start_port(path, listener_extra_args) do + path = path |> Utils.format_path() + args = listener_extra_args ++ [ + '-c', 'inotifywait $0 $@ & PID=$!; read a; kill $PID', + '-m', '-e', 'modify', '-e', 'close_write', '-e', 'moved_to', '-e', 'create', + '-r' | path] + Port.open( + {:spawn_executable, find_executable()}, + [:stream, :exit_status, {:line, 16384}, {:args, args}, {:cd, System.tmp_dir!()}] + ) + end + + def known_events do + [:created, :deleted, :renamed, :closed, :modified, :isdir, :attribute, :undefined] + end + + def line_to_event(line) do + line + |> to_string + |> scan1 + |> scan2(line) + end + + def scan1(line) do + re = ~r/^(.*) ([A-Z_,]+) (.*)$/ + case Regex.scan re, line do + [] -> {:error, :unknown} + [[_, path, events, file]] -> + {Path.join(path, file), parse_events(events)} + end + + end + def scan2({:error, :unknown}, line) do + re = ~r/^(.*) ([A-Z_,]+)$/ + case Regex.scan re, line do + [] -> {:error, :unknown} + [[_, path, events]] -> + {path, parse_events(events)} + end + end + def scan2(res, _), do: res + + def parse_events(events) do + String.split(events, ",") + |> Enum.map(&(convert_flag &1)) + end + + def convert_flag("CREATE"), do: :created + def convert_flag("DELETE"), do: :deleted + def convert_flag("ISDIR"), do: :isdir + def convert_flag("MODIFY"), do: :modified + def convert_flag("CLOSE_WRITE"), do: :modified + def convert_flag("CLOSE"), do: :closed + def convert_flag("MOVED_TO"), do: :renamed + def convert_flag("ATTRIB"), do: :attribute + def convert_flag(_), do: :undefined +end diff --git a/lib/exfswatch/backends/inotify_wait_win32.ex b/lib/exfswatch/backends/inotify_wait_win32.ex new file mode 100644 index 0000000000000000000000000000000000000000..acfadc248651263efad7d3079eebd00ec385f381 --- /dev/null +++ b/lib/exfswatch/backends/inotify_wait_win32.ex @@ -0,0 +1,37 @@ +alias ExFSWatch.Utils + +defmodule ExFSWatch.Backends.InotifyWaitWin32 do + + @re :re.compile('^(.*\\\\.*) ([A-Z_,]+) (.*)$', [:unicode]) |> elem(1) + + def find_executable do + :code.priv_dir(:exfswatch) ++ '/inotifywait.exe' + end + + def start_port(path, listener_extra_args) do + path = path |> Utils.format_path() + args = listener_extra_args ++ ['-m', '-r' | path] + Port.open( + {:spawn_executable, find_executable()}, + [:stream, :exit_status, {:line, 16384}, {:args, args}, {:cd, System.tmp_dir!()}] + ) + end + + def known_events do + [:created, :modified, :removed, :renamed, :undefined] + end + + def line_to_event(line) do + {:match, [dir, flags, dir_entry]} = :re.run(line, @re, [{:capture, :all_but_first, :list}]) + flags = for f <- :string.tokens(flags, ','), do: convert_flag(f) + path = :filename.join(dir, dir_entry) + {path, flags} + end + + defp convert_flag('CREATE'), do: :created + defp convert_flag('MODIFY'), do: :modified + defp convert_flag('DELETE'), do: :removed + defp convert_flag('MOVED_TO'), do: :renamed + defp convert_flag(_), do: :undefined + +end diff --git a/lib/exfswatch/backends/kqueue.ex b/lib/exfswatch/backends/kqueue.ex new file mode 100644 index 0000000000000000000000000000000000000000..81adba2f1fe98fa3595e15237aa7ca91a69be539 --- /dev/null +++ b/lib/exfswatch/backends/kqueue.ex @@ -0,0 +1,26 @@ +alias ExFSWatch.Utils + +defmodule ExFSWatch.Backends.Kqueue do + + def known_events do + [:created, :deleted, :renamed, :closed, :modified, :isdir, :undefined] + end + + def find_executable do + :code.priv_dir(:exfswatch) ++ '/kqueue' + end + + def start_port(path, listener_extra_args) do + path = path |> Utils.format_path() + args = listener_extra_args ++ [path] + Port.open( + {:spawn_executable, find_executable()}, + [:stream, :exit_status, {:line, 16384}, {:args, args}, {:cd, System.tmp_dir!()}] + ) + end + + def line_to_event(line) do + {'.', line} + end + +end diff --git a/lib/exfswatch/sys/inotifywait.ex b/lib/exfswatch/sys/inotifywait.ex deleted file mode 100644 index 30ca0bf8307d4741db8b5365a92588c93491386c..0000000000000000000000000000000000000000 --- a/lib/exfswatch/sys/inotifywait.ex +++ /dev/null @@ -1,43 +0,0 @@ -defmodule ExFSWatch.Sys.InotifyWait do - - def line_to_event(line) do - line - |> to_string - |> scan1 - |> scan2(line) - end - - def scan1(line) do - re = ~r/^(.*) ([A-Z_,]+) (.*)$/ - case Regex.scan re, line do - [] -> {:error, :unknown} - [[_, path, events, file]] -> - {Path.join(path, file), parse_events(events)} - end - - end - def scan2({:error, :unknown}, line) do - re = ~r/^(.*) ([A-Z_,]+)$/ - case Regex.scan re, line do - [] -> {:error, :unknown} - [[_, path, events]] -> - {path, parse_events(events)} - end - end - def scan2(res, _), do: res - - def parse_events(events) do - String.split(events, ",") - |> Enum.map(&(convert_flag &1)) - end - - def convert_flag("CLOSE_WRITE"), do: :modified - def convert_flag("CLOSE"), do: :closed - def convert_flag("CREATE"), do: :create - def convert_flag("MOVED_TO"), do: :renamed - def convert_flag("ISDIR"), do: :isdir - def convert_flag("DELETE_SELF"), do: :delete_self - def convert_flag("DELETE"), do: :deleted - - def convert_flag(_), do: :unknown -end diff --git a/lib/exfswatch/utils.ex b/lib/exfswatch/utils.ex new file mode 100644 index 0000000000000000000000000000000000000000..218a8db03b642636361b7a86ab89db93b071bcc5 --- /dev/null +++ b/lib/exfswatch/utils.ex @@ -0,0 +1,13 @@ +defmodule ExFSWatch.Utils do + + def format_path(path) when is_list(path) do + for i <- path do + i |> Path.absname |> to_char_list + end + end + + def format_path(path) do + [path] |> format_path + end + +end diff --git a/lib/exfswatch/worker.ex b/lib/exfswatch/worker.ex index bacd172138d789e450d9935d8290c21527903d01..f02fe6d4ba232ec5dd358ab9fd674744a5a2471e 100644 --- a/lib/exfswatch/worker.ex +++ b/lib/exfswatch/worker.ex @@ -9,12 +9,12 @@ defmodule ExFSWatch.Worker do def init(module) do backend = ExFSWatch.backend - port = start_port(backend, module.__dirs__, module.__listener_extra_args__) + port = backend.start_port(module.__dirs__, module.__listener_extra_args__) {:ok, %__MODULE__{port: port, backend: backend, module: module}} end def handle_info({port, {:data, {:eol, line}}}, %__MODULE__{port: port, backend: backend, module: module}=sd) do - {file_path, events} = backend(backend).line_to_event(line) + {file_path, events} = backend.line_to_event(line) module.callback(file_path |> to_string, events) {:noreply, sd} end @@ -27,42 +27,4 @@ defmodule ExFSWatch.Worker do def handle_info(_, sd) do {:noreply, sd} end - - - defp start_port(:fsevents, path, listener_extra_args) do - path = path |> format_path |> IO.inspect - args = listener_extra_args ++ ['-F' | path] |> IO.inspect - Port.open({:spawn_executable, :fsevents.find_executable()}, - [:stream, :exit_status, {:line, 16384}, {:args, args}, {:cd, System.tmp_dir!}] - ) - end - defp start_port(:inotifywait, path, listener_extra_args) do - path = path |> format_path - args = listener_extra_args ++ ['-c', 'inotifywait $0 $@ & PID=$!; read a; kill $PID', - '-m', '-e', 'close_write', '-e', 'moved_to', '-e', 'create', '-e', - 'delete_self', '-e', 'delete', '-r' | path - ] - Port.open({:spawn_executable, :os.find_executable('sh')}, - [:stream, :exit_status, {:line, 16384}, {:args, args}, {:cd, System.tmp_dir!}] - ) - end - defp start_port(:"inotifywait_win32", path, listener_extra_args) do - path = path |> format_path - args = listener_extra_args ++ ['-m', '-r' | path] - Port.open({:spawn_executable, :"inotifywait_win32".find_executable()}, - [:stream, :exit_status, {:line, 16384}, {:args, args}, {:cd, System.tmp_dir!}] - ) - end - - defp format_path(path) when is_list(path) do - for i <- path do - i |> Path.absname |> to_char_list - end - end - defp format_path(path) do - [path] |> format_path - end - - defp backend(:inotifywait), do: ExFSWatch.Sys.InotifyWait - defp backend(be), do: be end diff --git a/mix.exs b/mix.exs index 23ebc0d067424c0ad44b94f3fea86f199877c77c..7dea1ee1ce476652fc5c752478bc5e78342850e6 100644 --- a/mix.exs +++ b/mix.exs @@ -1,3 +1,16 @@ +defmodule Mix.Tasks.Compile.Src do + def run(_) do + priv_dir = :code.priv_dir(:exfswatch) + case :os.type() do + {:unix, :darwin} -> + Mix.shell.cmd("clang -framework CoreFoundation -framework CoreServices -Wno-deprecated-declarations c_src/mac/*.c -o #{priv_dir}/mac_listener") + {:unix, :freebsd} -> + Mix.shell.cmd("cc c_src/bsd/*.c -o #{priv_dir}/kqueue") + _ -> nil + end + end +end + defmodule ExFSWatch.Mixfile do use Mix.Project @@ -5,6 +18,7 @@ defmodule ExFSWatch.Mixfile do [ app: :exfswatch, version: "0.3.2", elixir: "~> 1.0", + compilers: [ :src, :elixir, :app ], deps: deps(), description: "A file change watcher wrapper based on [fs](https://github.com/synrc/fs)", source_url: "https://github.com/falood/exfswatch", @@ -18,14 +32,12 @@ defmodule ExFSWatch.Mixfile do def application do [ mod: { ExFSWatch, [] }, - applications: [:logger], - included_applications: [:fs], + included_applications: [:logger], ] end defp deps do - [ { :fs, "~> 2.12" }, - { :ex_doc, "~> 0.14", only: :docs }, + [ { :ex_doc, "~> 0.14", only: :docs }, ] end diff --git a/priv/inotifywait.exe b/priv/inotifywait.exe new file mode 100644 index 0000000000000000000000000000000000000000..8b6a86ecc6dd6e5096aeaec450b76cee1504e44b Binary files /dev/null and b/priv/inotifywait.exe differ diff --git a/test/exfswatch_test.exs b/test/backends/fsevents.exs similarity index 70% rename from test/exfswatch_test.exs rename to test/backends/fsevents.exs index d1662723d96d468f754fdf0c091e7e0c8b1820b3..d50a0c95b5f9fff9776a3eb46b8298e19bea9a2e 100644 --- a/test/exfswatch_test.exs +++ b/test/backends/fsevents.exs @@ -1,6 +1,6 @@ -defmodule ExfswatchTest do +defmodule ExFSWatch.Backends.FseventsTest do use ExUnit.Case - import :fsevents + import ExFSWatch.Backends.Fsevents test "file modified" do assert line_to_event('37425557\t0x00011400=[inodemetamod,modified]\t/one/two/file') == diff --git a/test/inotifywait_test.exs b/test/backends/inotify_wait_test.exs similarity index 90% rename from test/inotifywait_test.exs rename to test/backends/inotify_wait_test.exs index 84f9842c43409dac448f31eb480bdc78cb61b368..22a5f9d4dde5712e51603069b69128925960275a 100644 --- a/test/inotifywait_test.exs +++ b/test/backends/inotify_wait_test.exs @@ -1,6 +1,6 @@ -defmodule Exfswatch.Sys.InotifyWaitTest do +defmodule ExFSWatch.Backends.InotifyWaitTest do use ExUnit.Case - import ExFSWatch.Sys.InotifyWait + import ExFSWatch.Backends.InotifyWait test "dir write close" do assert line_to_event("/one/two/ CLOSE_WRITE,CLOSE file") ==