| 1 | %%% Copyright (C) Dominic Williams |
|---|
| 2 | %%% All rights reserved. |
|---|
| 3 | %%% See file COPYING. |
|---|
| 4 | |
|---|
| 5 | -module (directory_watcher). |
|---|
| 6 | -export ([init/2]). |
|---|
| 7 | -export ([init_recursive/2]). |
|---|
| 8 | -include_lib ("kernel/include/file.hrl"). |
|---|
| 9 | |
|---|
| 10 | init (Directory, F) -> |
|---|
| 11 | D = filename: absname (Directory), |
|---|
| 12 | check (D, F, dict: new ()). |
|---|
| 13 | |
|---|
| 14 | init_recursive (Directory, F) -> |
|---|
| 15 | D = filename: absname (Directory), |
|---|
| 16 | Self = self (), |
|---|
| 17 | Watcher = spawn_link (?MODULE, init, [D, send (Self)]), |
|---|
| 18 | loop_recursive (D, F, [Watcher]). |
|---|
| 19 | |
|---|
| 20 | check (Directory, F, State) -> |
|---|
| 21 | New_state = list_dir (Directory, F), |
|---|
| 22 | compare (F, State, New_state), |
|---|
| 23 | loop (Directory, F, New_state). |
|---|
| 24 | |
|---|
| 25 | list_dir (Directory, F) -> |
|---|
| 26 | list_dir (Directory, F, file: list_dir (Directory)). |
|---|
| 27 | |
|---|
| 28 | list_dir (Directory, _, {ok, Filenames}) -> |
|---|
| 29 | Paths = [filename: join (Directory, F) || F <- Filenames], |
|---|
| 30 | read_state (Paths); |
|---|
| 31 | list_dir (Directory, F, Error) -> |
|---|
| 32 | F ({directory, Directory, Error}), |
|---|
| 33 | dict: new (). |
|---|
| 34 | |
|---|
| 35 | read_state (Filenames) -> |
|---|
| 36 | lists: foldl (fun read_state/2, dict: new (), Filenames). |
|---|
| 37 | |
|---|
| 38 | read_state (File_name, State) -> |
|---|
| 39 | case file: read_file_info (File_name) of |
|---|
| 40 | {ok, Info} -> |
|---|
| 41 | Value = value (Info#file_info.type, File_name), |
|---|
| 42 | dict: store (File_name, Value, State); |
|---|
| 43 | _ -> |
|---|
| 44 | State |
|---|
| 45 | end. |
|---|
| 46 | |
|---|
| 47 | value (regular, Filename) -> |
|---|
| 48 | {ok, Content} = file: read_file (Filename), |
|---|
| 49 | {regular, erlang: md5 (Content)}; |
|---|
| 50 | value (directory, _) -> |
|---|
| 51 | directory. |
|---|
| 52 | |
|---|
| 53 | loop (Directory, F, Filenames) -> |
|---|
| 54 | receive |
|---|
| 55 | check -> |
|---|
| 56 | check (Directory, F, Filenames); |
|---|
| 57 | stop -> |
|---|
| 58 | F (bye) |
|---|
| 59 | end. |
|---|
| 60 | |
|---|
| 61 | loop_recursive (Directory, F, Watchers) -> |
|---|
| 62 | receive |
|---|
| 63 | {?MODULE, _, {directory, Dir, found}=Event} -> |
|---|
| 64 | Self = self (), |
|---|
| 65 | Watcher = spawn_link (?MODULE, init, [Dir, send (Self)]), |
|---|
| 66 | F (Event), |
|---|
| 67 | loop_recursive (Directory, F, [Watcher | Watchers]); |
|---|
| 68 | {?MODULE, Watcher, {directory, _, {error, _}}} -> |
|---|
| 69 | Watcher ! stop, |
|---|
| 70 | loop_recursive (Directory, F, Watchers -- [Watcher]); |
|---|
| 71 | {?MODULE, _, bye} -> |
|---|
| 72 | loop_recursive (Directory, F, Watchers); |
|---|
| 73 | {?MODULE, _, Event} -> |
|---|
| 74 | F (Event), |
|---|
| 75 | loop_recursive (Directory, F, Watchers); |
|---|
| 76 | check -> |
|---|
| 77 | lists: foreach (fun (P) -> P ! check end, Watchers), |
|---|
| 78 | loop_recursive (Directory, F, Watchers); |
|---|
| 79 | stop -> |
|---|
| 80 | F (bye) |
|---|
| 81 | end. |
|---|
| 82 | |
|---|
| 83 | compare (F, Original, Modified) -> |
|---|
| 84 | Compared = adlib: compare_dict (Modified, Original), |
|---|
| 85 | dict: fold (fun notify_new/3, F, dict: fetch (new, Compared)), |
|---|
| 86 | dict: fold (fun notify_lost/3, F, dict: fetch (lost, Compared)), |
|---|
| 87 | dict: fold (fun notify_changed/3, F, dict: fetch (changed, Compared)), |
|---|
| 88 | done. |
|---|
| 89 | |
|---|
| 90 | notify_new (File_name, _, Fun) -> |
|---|
| 91 | report_found (Fun, File_name), |
|---|
| 92 | Fun. |
|---|
| 93 | |
|---|
| 94 | report_found (F, Filename) -> |
|---|
| 95 | F ({type (Filename), Filename, found}). |
|---|
| 96 | |
|---|
| 97 | notify_lost (File_name, Value, Fun) -> |
|---|
| 98 | report_lost (Fun, {File_name, Value}), |
|---|
| 99 | Fun. |
|---|
| 100 | |
|---|
| 101 | report_lost (F, {Filename, directory}) -> |
|---|
| 102 | F ({directory, Filename, lost}); |
|---|
| 103 | report_lost (F, {Filename, {regular, _}}) -> |
|---|
| 104 | F ({{file, filename: extension (Filename)}, Filename, lost}). |
|---|
| 105 | |
|---|
| 106 | notify_changed (File_name, {{regular, _}, {regular, _}}, Fun) -> |
|---|
| 107 | report_changed (Fun, File_name), |
|---|
| 108 | Fun; |
|---|
| 109 | notify_changed (File_name, {_, Original}, Fun) -> |
|---|
| 110 | report_lost (Fun, {File_name, Original}), |
|---|
| 111 | report_found (Fun, File_name), |
|---|
| 112 | Fun. |
|---|
| 113 | |
|---|
| 114 | report_changed (F, Filename) -> |
|---|
| 115 | F ({type (Filename), Filename, changed}). |
|---|
| 116 | |
|---|
| 117 | type (Path) -> |
|---|
| 118 | type (file: read_file_info (Path), Path). |
|---|
| 119 | |
|---|
| 120 | type ({ok, #file_info{type=directory}}, _) -> |
|---|
| 121 | directory; |
|---|
| 122 | type ({ok, #file_info{type=regular}}, Path) -> |
|---|
| 123 | {file, filename: extension (Path)}. |
|---|
| 124 | |
|---|
| 125 | send (Pid) -> |
|---|
| 126 | fun (Event) -> |
|---|
| 127 | Pid ! {?MODULE, self (), Event} |
|---|
| 128 | end. |
|---|
| 129 | |
|---|