1 module sbylib.event.fileevent;
2 
3 import std;
4 import std.digest.md : md5Of;
5 import sbylib.event;
6 
7 private abstract class FileModifyNotification {
8     abstract bool hasModified() const;
9     abstract void update();
10 
11     FileModifyNotification opBinary(string op : "&")(FileModifyNotification other) {
12         return new AndFileModifyNotification([this, other]);
13     }
14 
15     FileModifyNotification opBinary(string op : "|")(FileModifyNotification other) {
16         return new OrFileModifyNotification([this, other]);
17     }
18 }
19 
20 private class SingleFileModifyNotification : FileModifyNotification {
21     private string path;
22     private ubyte[16] hash;
23 
24     this(string path) {
25         this.path = path;
26         update();
27     }
28 
29     override bool hasModified() const {
30         return path.exists && md5Of(readText(path)) != hash;
31     }
32 
33     override void update() {
34         if (path.exists) {
35             this.hash = md5Of(readText(path));
36         } else {
37             this.hash = typeof(hash).init;
38         }
39     }
40 }
41 
42 private class AndFileModifyNotification : FileModifyNotification {
43     private FileModifyNotification[] fs;
44 
45     this(FileModifyNotification[] fs) {
46         this.fs = fs.dup;
47     }
48 
49     override bool hasModified() const {
50         return fs.all!(f => f.hasModified());
51     }
52 
53     override void update() {
54         foreach (f; fs) f.update();
55     }
56 }
57 
58 private class OrFileModifyNotification : FileModifyNotification {
59     private FileModifyNotification[] fs;
60 
61     this(FileModifyNotification[] fs) {
62         this.fs = fs.dup;
63     }
64 
65     override bool hasModified() const {
66         return fs.any!(f => f.hasModified());
67     }
68 
69     override void update() {
70         foreach (f; fs) f.update();
71     }
72 }
73 
74 FileModifyNotification hasModified(string path) {
75     return new SingleFileModifyNotification(path);
76 }
77 
78 VoidEvent when(FileModifyNotification notification) {
79     import sbylib.event : when;
80 
81     return VoidEvent.create(fire =>
82         when(notification.hasModified()).then({
83             fire();
84             notification.update();
85         })
86     );
87 }
88 
89 unittest {
90     import std : buildPath, tempDir;
91     import std.file : fwrite = write, fremove = remove;
92 
93     auto file1 = tempDir.buildPath("test1.txt");
94     auto file2 = tempDir.buildPath("test2.txt");
95     auto file3 = tempDir.buildPath("test3.txt");
96     scope (exit) {
97         fremove(file1);
98         fremove(file2);
99         fremove(file3);
100     }
101 
102     int[3] count;
103     when(file1.hasModified).then({
104         count[0]++;
105     });
106 
107     when(file1.hasModified & file2.hasModified).then({
108         count[1]++;
109     });
110 
111     when((file1.hasModified | file2.hasModified) & file3.hasModified).then({
112         count[2]++;
113     });
114 
115     file1.fwrite("foo");
116     FrameEventWatcher.update();
117     assert(count == [1,0,0]);
118 
119     file2.fwrite("foo");
120     FrameEventWatcher.update();
121     assert(count == [1,1,0]);
122 
123     file3.fwrite("foo");
124     FrameEventWatcher.update();
125     assert(count == [1,1,1]);
126 
127     file1.fwrite("bar");
128     file2.fwrite("bar");
129     file3.fwrite("bar");
130     FrameEventWatcher.update();
131     assert(count == [2,2,2]);
132 }