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 }