7b1801d0bbe95cac491f98e663033f89561509a1
[sdk] / ecere / src / sys / FileMonitor.ec
1 namespace sys;
2
3 import "System"
4
5 public class FileChange
6 {
7 public:
8    bool created:1;   // param: new file name
9    bool renamed:1;   // param: new file name
10    bool modified:1;
11    bool deleted:1;
12    bool attribs:1;   // param: new attributes
13 };
14
15 public define AnyFileChange = FileChange { true, true, true, true, true };
16
17 // *** File Monitoring ***
18
19 static class FileEntry : struct
20 {
21    FileEntry prev, next;
22    FileSize size;
23    FileAttribs attribs;
24    TimeStamp modified;
25    char * name;
26
27    void Free()
28    {
29       delete name;
30    }
31 };
32
33 public class FileMonitor
34 {
35 public:
36    property void * userData { set { data = value; } };
37    property FileChange fileChange { set { watchFor = value; } };
38    property char * fileName
39    {
40       set
41       {
42          //incref this;
43          StopMonitoring();
44          delete fileName;
45          if(value) MonitorFile(value);
46          //delete this;
47       }
48       get { return fileName; }
49    };
50    property char * directoryName
51    {
52       set
53       {
54          //incref this;
55          StopMonitoring();
56          delete fileName;
57          if(value) MonitorDirectory(value);
58          // delete this;
59       }
60       get { return fileName; }
61    };
62
63    void StopMonitoring()
64    {
65       if(active)
66       {
67          globalSystem.fileMonitorMutex.Wait();
68
69          if(reentrant)
70          {
71             toBeFreed = true;
72             fileNotifies.Free(FileNotify::Free);
73          }
74          else
75             FreeMonitor();
76
77          active = false;
78          globalSystem.fileMonitorMutex.Release();
79       }
80    }
81
82    void StartMonitoring()
83    {
84       if(fileName && !active)
85       {
86          globalSystem.fileMonitorMutex.Wait();
87          if(reentrant)
88          {
89             toBeFreed = false;
90             active = true;
91          }
92          //else
93          {
94             if(directory)
95                MonitorDirectory(fileName);
96             else
97                MonitorFile(fileName);
98          }
99          globalSystem.fileMonitorMutex.Release();
100       }
101    }
102
103 private:
104    FileMonitor prev, next;
105    char * fileName;
106    FileChange watchFor;
107    void * data;
108    bool reentrant, toBeFreed;
109    OldList fileNotifies;
110    bool active;
111
112    // For file monitors
113    bool exists;
114    TimeStamp modified;
115    FileSize size;
116    FileAttribs attribs;
117    public virtual bool any_object::OnDirNotify(FileChange action, char * fileName, char * param);
118
119    // For directory monitors
120    bool directory;
121    OldList files;
122    public virtual bool any_object::OnFileNotify(FileChange action, char * param);
123
124    /*
125 #if defined(__WIN32__)
126    HANDLE handle;
127 #elif defined(__linux__)
128    int fd;
129 #endif
130    */
131
132    ~FileMonitor()
133    {
134       globalSystem.fileMonitorMutex.Wait();
135       delete fileName;
136
137       // if(active)
138          FreeMonitor();
139       globalSystem.fileMonitorMutex.Release();
140    }
141
142    void MonitorFile(char * filePath)
143    {
144       FileAttribs exists = FileExists(filePath);
145       FileStats stats;
146       String oldFileName = fileName;
147
148       //incref this;
149       globalSystem.fileMonitorMutex.Wait();
150
151       files.Free(FileEntry::Free);
152       fileNotifies.Free(FileNotify::Free);
153
154       if(!active && !toBeFreed)
155          globalSystem.fileMonitors.Add(this);
156       this.exists = exists && !exists.isDirectory;
157       fileName = CopyString(filePath);
158       delete oldFileName;
159       this.watchFor = watchFor;
160
161       if(FileGetStats(filePath, stats))
162       {
163          attribs = stats.attribs;
164          size = stats.size;
165          modified = stats.modified;
166 #ifdef _DEBUG
167          /*if(strstr(filePath, "Debugger"))
168             printf("modified is %x", modified);*/
169 #endif
170       }
171
172       active = true;
173       toBeFreed = false;
174
175       globalSystem.fileMonitorMutex.Release();
176
177       if(!globalSystem.fileMonitorThread)
178       {
179          globalSystem.fileMonitorThread = MonitorThread { };
180          //incref globalSystem.fileMonitorThread;
181          globalSystem.fileMonitorThread.Create();
182       }
183    }
184
185    void MonitorDirectory(char * filePath)
186    {
187       FileStats stats;
188       String oldFileName = fileName;
189
190       //incref this;
191       globalSystem.fileMonitorMutex.Wait();
192
193       files.Free(FileEntry::Free);
194       fileNotifies.Free(FileNotify::Free);
195
196       if(!active && !toBeFreed)
197          globalSystem.fileMonitors.Add(this);
198       directory = true;
199       exists = FileExists(filePath).isDirectory;
200
201       this.watchFor = watchFor;
202       fileName = CopyString(filePath);
203       delete oldFileName;
204
205       if(FileGetStats(fileName, stats))
206       {
207          attribs = stats.attribs;
208          size = stats.size;
209          modified = stats.modified;
210       }
211
212       // Initialize files in directory
213       GetFileEntries(&files, fileName);
214
215       active = true;
216       toBeFreed = false;
217
218       globalSystem.fileMonitorMutex.Release();
219
220       if(!globalSystem.fileMonitorThread)
221       {
222          globalSystem.fileMonitorThread = MonitorThread { };
223          globalSystem.fileMonitorThread.Create();
224       }
225    }
226
227    void FreeMonitor()
228    {
229       files.Free(FileEntry::Free);
230       fileNotifies.Free(FileNotify::Free);
231       if(active)
232          globalSystem.fileMonitors.Remove(this);
233       // delete this;
234    }
235
236    bool AddFileNotify(FileChange action, char * fileName, char * param)
237    {
238       if(watchFor & action)
239       {
240          fileNotifies.Add(FileNotify {
241                monitor = this, action = action, fileName = CopyString(fileName), param = CopyString(param)
242             });
243          return true;
244       }
245       return false;
246    }
247 };
248
249 private class FileNotify : struct
250 {
251    FileNotify prev, next;
252    FileMonitor monitor;
253    FileChange action;
254    char * fileName;
255    char * param;
256
257    void Free()
258    {
259       delete fileName;
260       delete param;
261    }
262 };
263
264 static define FILE_MONITOR_RESOLUTION = 1.0; // Once every second
265
266 static int CompareFiles(FileEntry e1, FileEntry e2, void * data)
267 {
268    if(!e1.attribs.isDirectory && e2.attribs.isDirectory)
269       return 1;
270    else if(e1.attribs.isDirectory && !e2.attribs.isDirectory)
271       return -1;
272    else
273       return strcmp(e1.name, e2.name);
274 }
275
276 static void GetFileEntries(OldList list, char * path)
277 {
278    FileListing listing { path, null };
279    while(listing.Find())
280    {
281       list.Add(FileEntry {
282             name = CopyString(listing.name),
283             attribs = listing.stats.attribs,
284             modified = listing.stats.modified,
285             size = listing.stats.size
286          });
287    }
288    list.Sort(CompareFiles, null);
289 }
290
291 static class MonitorThread : Thread
292 {
293    uint Main()
294    {
295       Time lastTime = 0;
296
297       while(!globalSystem.systemTerminate)
298       {
299          Time currentTime = GetTime();
300          if(currentTime - lastTime > FILE_MONITOR_RESOLUTION)
301          {
302             FileMonitor monitor;
303             bool fileActivity = false;
304             lastTime = currentTime;
305
306             // printf("[%d] Waiting in MonitorThread for fileMonitor Mutex %x...\n", (int)GetCurrentThreadID(), globalSystem.fileMonitorMutex);
307             globalSystem.fileMonitorMutex.Wait();
308
309             for(monitor = globalSystem.fileMonitors.first; monitor; monitor = monitor.next)
310             {
311                if(monitor.directory)
312                {
313                   bool exists = FileExists(monitor.fileName).isDirectory;
314                   if(exists && !monitor.exists)
315                   {
316                      fileActivity |= monitor.AddFileNotify(FileChange { created = true }, monitor.fileName, null);
317                      monitor.exists = exists;
318                   }
319                   else if(!exists && monitor.exists)
320                   {
321                      fileActivity |= monitor.AddFileNotify(FileChange { deleted = true }, monitor.fileName, null);
322                      monitor.exists = exists;
323                   }
324                   else if(exists && monitor.watchFor & FileChange { created = true, deleted = true, modified = true, attribs = true})
325                   {
326                      FileEntry entry;
327                      FileEntry newEntry;
328                      OldList newEntries = { 0 };
329                      GetFileEntries(&newEntries, monitor.fileName);
330
331                      // Directory comparison
332                      entry = monitor.files.first;
333                      newEntry = newEntries.first;
334
335                      while(entry || newEntry)
336                      {
337                         int comparison;
338                         if(!entry)         comparison = 1;
339                         else if(!newEntry) comparison = -1;
340                         else               comparison = strcmp(entry.name,newEntry.name);
341
342                         if(comparison > 0)
343                         {
344                            fileActivity |= monitor.AddFileNotify(FileChange { created = true }, newEntry.name, null);
345                            newEntry = newEntry.next;
346                         }
347                         else if(comparison < 0)
348                         {
349                            fileActivity |= monitor.AddFileNotify(FileChange { deleted = true }, entry.name, null);
350                            entry = entry.next;
351                         }
352                         else
353                         {
354                            if(newEntry.modified != entry.modified || newEntry.size != entry.size)
355                            {
356                               fileActivity |= monitor.AddFileNotify(FileChange { modified = true }, entry.name, null);
357                            }
358                            else if(newEntry.attribs != entry.attribs)
359                            {
360                               fileActivity |= monitor.AddFileNotify(FileChange { attribs = true }, entry.name, null);
361                            }
362                            entry = entry.next;
363                            newEntry = newEntry.next;
364                         }
365                      }
366                      monitor.files.Free(FileEntry::Free);
367                      monitor.files = newEntries;
368                   }
369                }
370                else if(monitor.fileName)
371                {
372                   FileAttribs exists = FileExists(monitor.fileName);
373                   // printf("Iterating on Monitor for %s...\n", monitor.fileName);
374
375                   if((exists && !exists.isDirectory) && !monitor.exists)
376                   {
377                      fileActivity |= monitor.AddFileNotify(FileChange { created = true }, monitor.fileName, null);
378                      monitor.exists = true;
379                   }
380                   else if((!exists || exists.isDirectory) && monitor.exists)
381                   {
382                      fileActivity |= monitor.AddFileNotify(FileChange { deleted = true }, monitor.fileName, null);
383                      monitor.exists = false;
384                   }
385                   else if(monitor.exists)
386                   {
387                      FileStats stats { };
388                      if(FileGetStats(monitor.fileName, stats))
389                      {
390 #ifdef _DEBUG
391                         /*if(strstr(monitor.fileName, "Debugger"))
392                            printf("Now modified is %x\n", stats.modified);*/
393 #endif
394
395                         if(stats.modified != monitor.modified || stats.size != monitor.size)
396                         {
397                            // printf("Modified/size changed, adding file notify...\n");
398                            if(stats.modified > monitor.modified || monitor.modified - (TimeStamp)stats.modified > 2)
399                               fileActivity |= monitor.AddFileNotify(FileChange { modified = true }, monitor.fileName, null);
400                            monitor.size = stats.size;
401                            monitor.modified = stats.modified;
402 #ifdef _DEBUG
403                            /*if(strstr(monitor.fileName, "Debugger"))
404                               printf("Setting modified to %x\n", monitor.modified);*/
405 #endif
406                         }
407                         else if(stats.attribs != monitor.attribs)
408                         {
409                            // printf("Attribs changed, adding file notify...\n");
410
411                            fileActivity |= monitor.AddFileNotify(FileChange { attribs = true }, monitor.fileName, null);
412                            monitor.attribs = stats.attribs;
413                         }
414                      }
415                   }
416                }
417             }
418             if(fileActivity)
419             {
420                // printf("[%d] Signaling Event...\n", (int)GetCurrentThreadID());
421                guiApp.SignalEvent();
422             }
423
424             // printf("[%d] Releasing Mutex...\n", (int)GetCurrentThreadID());
425             globalSystem.fileMonitorMutex.Release();
426          }
427
428          // printf("[%d] Sleeping...\n", (int)GetCurrentThreadID());
429          Sleep(1.0 / 18.2);
430       }
431       return 0;
432    }
433 }