Permalink
Browse files

Merge branch 'master' of git://github.com/mxcl/playdar-core

  • Loading branch information...
2 parents 4df7721 + 531379e commit 0a76742681e585339613ad66a8415ddfaba557f3 @RJ committed Jan 22, 2010
View
@@ -22,6 +22,7 @@
/playdar_modules/library/priv/taglib_driver/scanner_visual_studio_sln/Release/
/playdar_modules/library/priv/taglib_driver/scanner_visual_studio_sln/scanner_visual_studio_sln/Debug/
/playdar_modules/library/priv/taglib_driver/scanner_visual_studio_sln/scanner_visual_studio_sln/Release/
+/playdar_modules/library/priv/fswatcher_driver/fswatcher
erl_crash.dump
*.ncb
*.suo
View
@@ -6,7 +6,7 @@ endif
######################################################################## setup
ERLCFLAGS = -pa ebin +debug_info -W -I include
.DEFAULT_GOAL = all
-.PHONY: all clean
+.PHONY: all clean scanner
# [src/foo.erl, src/bar/tee.erl] -> [ebin/foo.beam, ebin/tee.beam]
define erl2beam
@@ -40,14 +40,12 @@ ebin/playdar.app: src/playdar.app
ESCRIPT="\nmain(_)\n"'-> io:format("default_config()->~p.~n",[file:consult("etc/playdar.conf.example")]),halt(0).'
.default_config.hrl.escript:
- echo $(ESCRIPT) > $@
+ printf $(ESCRIPT) > $@
src/playdar_config.erl: include/default_config.hrl
include/default_config.hrl: .default_config.hrl.escript
escript $< > $@
############################################################## playdar-modules
-TAGLIB_JSON_READER = playdar_modules/library/priv/taglib_driver/taglib_json_reader
-
define MODULE_template
$(1)/ebin:
mkdir -p $$@
@@ -60,13 +58,23 @@ endef
$(foreach d, $(wildcard playdar_modules/*), $(eval $(call MODULE_template, $(d))) )
+############################################################################ c
+TAGLIB_JSON_READER = playdar_modules/library/priv/taglib_driver/taglib_json_reader
+
$(TAGLIB_JSON_READER): $(TAGLIB_JSON_READER).cpp
g++ `taglib-config --cflags` `taglib-config --libs` -o $@ $<
-########################################################################## all
+ifeq ($(shell uname), Darwin)
+FSWATCHER = playdar_modules/library/priv/fswatcher_driver/fswatcher
+
+$(FSWATCHER): $(FSWATCHER).c
+ gcc -framework Carbon $< -o $@
+endif
+
+##################################################################### main bit
all: $(BEAM) ebin/playdar.app ebin/mochiweb.app ebin/erlydtl.app
-scanner: $(TAGLIB_JSON_READER)
+scanner: $(TAGLIB_JSON_READER) $(FSWATCHER)
clean:
rm -rf ebin $(EBIN)
@@ -0,0 +1,85 @@
+#if __APPLE__
+
+#include <CoreServices/CoreServices.h>
+#include <sys/stat.h>
+#include <sys/types.h>
+#include <sys/dir.h>
+
+static time_t global_last_mtime;
+
+static void callback(ConstFSEventStreamRef stream,
+ void *userinfo,
+ size_t const N,
+ void *paths,
+ const FSEventStreamEventFlags flags[],
+ const FSEventStreamEventId ids[])
+{
+ time_t last_mtime = global_last_mtime;
+ size_t i;
+ DIR* d;
+ struct dirent *dp;
+
+ for (i = 0; i < N; i++) {
+ const char* dname = ((char**)paths)[i];
+
+ if ((d = opendir(dname)) == NULL) {
+ perror(dname);
+ continue;
+ }
+ while ((dp = readdir(d)) != NULL) {
+ switch (dp->d_type) {
+ case DT_REG: case DT_LNK: break;
+ default: continue;
+ }
+
+ char path[dp->d_namlen + strlen(dname) + 1];
+ strcpy(path, dname);
+ strcat(path, dp->d_name);
+
+ struct stat sb;
+ if (lstat(path, &sb) < 0 && errno == EACCES)
+ perror(path);
+ else if (sb.st_mtime >= last_mtime) {
+ puts(path);
+ if (sb.st_mtime > global_last_mtime)
+ global_last_mtime = sb.st_mtime;
+ }
+ }
+ closedir(d);
+ }
+ fflush(stdout);
+}
+
+static inline CFArrayRef paths(const int N, char** in)
+{
+ CFStringRef out[N];
+ int x;
+ for (x = 0; x < N; ++x)
+ out[x] = CFStringCreateWithCString(kCFAllocatorDefault, in[x], kCFStringEncodingUTF8);
+ return CFArrayCreate(NULL, (const void **)out, N, NULL);
+}
+
+int main(int argc, char** argv)
+{
+ global_last_mtime = time(NULL);
+
+ FSEventStreamRef stream = FSEventStreamCreate(
+ NULL,
+ &callback,
+ NULL,
+ paths(--argc, ++argv),
+ kFSEventStreamEventIdSinceNow, // TODO previous event ID
+ 5.0, // seconds after an event before calling our callback
+ kFSEventStreamCreateFlagNone);
+ FSEventStreamScheduleWithRunLoop(stream, CFRunLoopGetCurrent(), kCFRunLoopDefaultMode);
+ FSEventStreamStart(stream);
+
+ CFRunLoopRun();
+}
+
+#elif WIN32
+
+int wmain(int argc, wchar_t* argv[])
+{}
+
+#endif
@@ -0,0 +1,51 @@
+% taglib interface to external scanner binary
+-module(fswatcher_driver).
+-behaviour(gen_server).
+-include("playdar.hrl").
+-export([start_link/2, watch/1]).
+-export([init/1, handle_call/3, handle_cast/2, handle_info/2, terminate/2, code_change/3]).
+
+-record(state, {port, librarypid, dirs}).
+
+start_link(LibraryPid, Dirs) ->
+ gen_server:start_link({local, ?MODULE}, ?MODULE, [LibraryPid, Dirs], []).
+
+watch(Dir) ->
+ gen_server:call(?MODULE, {restart_port, Dir}).
+
+start_fswatcher_port([]) ->
+ undefined;
+
+start_fswatcher_port(Dirs) ->
+ Exe = "playdar_modules/library/priv/fswatcher_driver/fswatcher",
+ open_port({spawn_executable, Exe}, [{args, Dirs}, binary, {line, 2048}, use_stdio]).
+%%
+
+init([LibraryPid, Dirs]) ->
+ {ok, #state{port = start_fswatcher_port(Dirs), librarypid = LibraryPid, dirs = Dirs}}.
+
+handle_call({restart_port, Dir}, _From, #state{port = Port} = State) ->
+ (catch port_close(Port)),
+ Dirs = State#state.dirs ++ [Dir],
+ {reply, {Dir, dir_added}, State#state{port = start_fswatcher_port(Dirs), dirs = Dirs}}.
+
+handle_cast(_Msg, State) ->
+ {noreply, State}.
+
+handle_info({Port, {data, {_, Line}}}, #state{port = Port} = State) ->
+ File = binary_to_list(Line),
+ library_dets:file_changed(State#state.librarypid, File),
+ {noreply, State};
+
+handle_info({'EXIT', Port, Reason}, #state{port = Port} = State) ->
+ {stop, {port_terminated, Reason}, State}.
+
+terminate({port_terminated, _Reason}, _State) ->
+ ok;
+
+terminate(_Reason, #state{port = Port} = _State) ->
+ (catch port_close(Port)),
+ exit(Port, interrupt).
+
+code_change(_OldVsn, State, _Extra) ->
+ {ok, State}.
@@ -5,20 +5,21 @@
-behaviour(playdar_resolver).
%% API
--export([start_link/0, resolve/2, weight/1, targettime/1, name/1, localonly/1]).
+-export([start_link/0, resolve/2, weight/1, targettime/1, name/1, localonly/1, file_changed/2]).
-export([dump_library/1]).
-export([scan/2, stats/1, add_file/5, sync/1 ]).
%% gen_server callbacks
-export([init/1, handle_call/3, handle_cast/2, handle_info/2, terminate/2, code_change/3]).
%% Records
--record(state, {scanner, ndb, fdb, customname}).
+-record(state, {scanner, ndb, fdb, customname, fswpid}).
%%
start_link() -> gen_server:start_link(?MODULE, [], []).
scan(Pid, Dir) -> gen_server:call(Pid, {scan, Dir}, infinity).
+file_changed(Pid, File) -> gen_server:call(Pid, {scan_file, File}, infinity).
add_file(Pid, F, Mtime, Size, L) -> gen_server:call(Pid, {add_file, F, Mtime, Size, L}, 60000).
sync(Pid) -> gen_server:cast(Pid, sync).
@@ -32,6 +33,12 @@ localonly(_Pid) -> false.
stats(Pid) -> gen_server:call(Pid, stats).
dump_library(Pid) -> gen_server:call(Pid, dump_library, 60000).
+
+music_dirs(Fdb) -> case dets:lookup(Fdb, music_dirs) of
+ [] -> [];
+ [{music_dirs, Dirs}] -> Dirs
+ end.
+
%%
% Non-standard init, when used by proxy for another type of library:
@@ -53,7 +60,8 @@ init([]) ->
% start the scanner (kind of a hack, but deadlock if we do it in init here):
self() ! start_scanner,
playdar_resolver:add_resolver(?MODULE, self()),
- {ok, #state{scanner=undefined, ndb=Ndb, fdb=Fdb, customname=""}}.
+ FswatcherPid = fswatcher_driver:start_link(self(), music_dirs(Fdb)),
+ {ok, #state{scanner=undefined, ndb=Ndb, fdb=Fdb, customname="", fswpid=FswatcherPid}}.
handle_cast(sync, State) ->
dets:sync(State#state.fdb),
@@ -141,11 +149,20 @@ handle_call({add_file, File, Mtime, Size, Tags}, _From, State) when is_list(Tags
end;
handle_call({scan, Dir}, From, State) ->
+ dets:insert(State#state.fdb, {music_dirs, music_dirs(State#state.fdb)++[Dir]}),
+ fswatcher_driver:watch(Dir),
+
spawn(fun()->
gen_server:reply(From, (catch scanner:scan_dir(State#state.scanner, Dir)))
end),
{noreply, State};
+handle_call({scan_file, File}, From, State) ->
+ spawn(fun()->
+ gen_server:reply(From, (catch scanner:scan_file(State#state.scanner, File)))
+ end),
+ {noreply, State};
+
handle_call(stats, _From, State) ->
NumFiles = proplists:get_value(size, dets:info(State#state.fdb), -1),
{reply, [{num_files, NumFiles}], State};

0 comments on commit 0a76742

Please sign in to comment.