ide/Project/epj monitoring: Added support for notifying of modification of added...
[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
147       //incref this;
148       globalSystem.fileMonitorMutex.Wait();
149
150       files.Free(FileEntry::Free);
151       fileNotifies.Free(FileNotify::Free);
152
153       if(!active && !toBeFreed)
154          globalSystem.fileMonitors.Add(this);
155       this.exists = exists && !exists.isDirectory;
156       delete fileName;
157       fileName = CopyString(filePath);
158       this.watchFor = watchFor;
159
160       if(FileGetStats(filePath, stats))
161       {
162          attribs = stats.attribs;
163          size = stats.size;
164          modified = stats.modified;
165 #ifdef _DEBUG
166          /*if(strstr(filePath, "Debugger"))
167             printf("modified is %x", modified);*/
168 #endif
169       }
170
171       active = true;
172       toBeFreed = false;
173
174       globalSystem.fileMonitorMutex.Release();
175
176       if(!globalSystem.fileMonitorThread)
177       {
178          globalSystem.fileMonitorThread = MonitorThread { };
179          //incref globalSystem.fileMonitorThread;
180          globalSystem.fileMonitorThread.Create();
181       }
182    }
183
184    void MonitorDirectory(char * filePath)
185    {
186       FileStats stats;
187
188       //incref this;
189       globalSystem.fileMonitorMutex.Wait();
190
191       files.Free(FileEntry::Free);
192       fileNotifies.Free(FileNotify::Free);
193
194       if(!active && !toBeFreed)
195          globalSystem.fileMonitors.Add(this);
196       directory = true;
197       exists = FileExists(filePath).isDirectory;
198
199       this.watchFor = watchFor;
200       delete fileName;
201       fileName = CopyString(filePath);
202
203       if(FileGetStats(fileName, stats))
204       {
205          attribs = stats.attribs;
206          size = stats.size;
207          modified = stats.modified;
208       }
209
210       // Initialize files in directory
211       GetFileEntries(&files, fileName);
212
213       active = true;
214       toBeFreed = false;
215
216       globalSystem.fileMonitorMutex.Release();
217
218       if(!globalSystem.fileMonitorThread)
219       {
220          globalSystem.fileMonitorThread = MonitorThread { };
221          globalSystem.fileMonitorThread.Create();
222       }
223    }
224
225    void FreeMonitor()
226    {
227       files.Free(FileEntry::Free);
228       fileNotifies.Free(FileNotify::Free);
229       if(active)
230          globalSystem.fileMonitors.Remove(this);
231       // delete this;
232    }
233
234    bool AddFileNotify(FileChange action, char * fileName, char * param)
235    {
236       if(watchFor & action)
237       {
238          fileNotifies.Add(FileNotify { 
239                monitor = this, action = action, fileName = CopyString(fileName), param = CopyString(param)
240             });
241          return true;
242       }
243       return false;
244    }
245 };
246
247 private class FileNotify : struct
248 {
249    FileNotify prev, next;
250    FileMonitor monitor;
251    FileChange action;
252    char * fileName;
253    char * param;
254
255    void Free()
256    {
257       delete fileName;
258       delete param;
259    }
260 };
261
262 static define FILE_MONITOR_RESOLUTION = 1.0; // Once every second
263
264 static int CompareFiles(FileEntry e1, FileEntry e2, void * data)
265 {
266    if(!e1.attribs.isDirectory && e2.attribs.isDirectory)
267       return 1;
268    else if(e1.attribs.isDirectory && !e2.attribs.isDirectory)
269       return -1;
270    else 
271       return strcmp(e1.name, e2.name);
272 }
273
274 static void GetFileEntries(OldList list, char * path)
275 {
276    FileListing listing { path, null };
277    while(listing.Find())
278    {
279       list.Add(FileEntry {
280             name = CopyString(listing.name),
281             attribs = listing.stats.attribs,
282             modified = listing.stats.modified,
283             size = listing.stats.size
284          });
285    }
286    list.Sort(CompareFiles, null);
287 }
288
289 static class MonitorThread : Thread
290 {
291    uint Main()
292    {
293       Time lastTime = 0;
294
295       while(!globalSystem.systemTerminate)
296       {
297          Time currentTime = GetTime();
298          if(currentTime - lastTime > FILE_MONITOR_RESOLUTION)
299          {
300             FileMonitor monitor;
301             bool fileActivity = false;
302             lastTime = currentTime;
303
304             // printf("[%d] Waiting in MonitorThread for fileMonitor Mutex %x...\n", GetCurrentThreadID(), globalSystem.fileMonitorMutex);
305             globalSystem.fileMonitorMutex.Wait();
306
307             for(monitor = globalSystem.fileMonitors.first; monitor; monitor = monitor.next)
308             {
309                if(monitor.directory)
310                {
311                   bool exists = FileExists(monitor.fileName).isDirectory;
312                   if(exists && !monitor.exists)
313                   {
314                      fileActivity |= monitor.AddFileNotify(FileChange { created = true }, monitor.fileName, null);
315                      monitor.exists = exists;
316                   }
317                   else if(!exists && monitor.exists)
318                   {
319                      fileActivity |= monitor.AddFileNotify(FileChange { deleted = true }, monitor.fileName, null);
320                      monitor.exists = exists;
321                   }
322                   else if(exists && monitor.watchFor & FileChange { created = true, deleted = true, modified = true, attribs = true})
323                   {
324                      FileEntry entry;
325                      FileEntry newEntry;
326                      OldList newEntries = { 0 };
327                      GetFileEntries(&newEntries, monitor.fileName);
328
329                      // Directory comparison
330                      entry = monitor.files.first;
331                      newEntry = newEntries.first;
332
333                      while(entry || newEntry)
334                      {
335                         int comparison;
336                         if(!entry)         comparison = 1;
337                         else if(!newEntry) comparison = -1;
338                         else               comparison = strcmp(entry.name,newEntry.name);
339
340                         if(comparison > 0)
341                         {
342                            fileActivity |= monitor.AddFileNotify(FileChange { created = true }, newEntry.name, null);
343                            newEntry = newEntry.next;
344                         }
345                         else if(comparison < 0)
346                         {
347                            fileActivity |= monitor.AddFileNotify(FileChange { deleted = true }, entry.name, null);
348                            entry = entry.next;
349                         }
350                         else
351                         {
352                            if(newEntry.modified != entry.modified || newEntry.size != entry.size)
353                            {
354                               fileActivity |= monitor.AddFileNotify(FileChange { modified = true }, entry.name, null);
355                            }
356                            else if(newEntry.attribs != entry.attribs)
357                            {
358                               fileActivity |= monitor.AddFileNotify(FileChange { attribs = true }, entry.name, null);
359                            }
360                            entry = entry.next;
361                            newEntry = newEntry.next;
362                         }
363                      }
364                      monitor.files.Free(FileEntry::Free);
365                      monitor.files = newEntries;
366                   }            
367                }
368                else if(monitor.fileName)
369                {
370                   FileAttribs exists = FileExists(monitor.fileName);
371                   // printf("Iterating on Monitor for %s...\n", monitor.fileName);
372
373                   if((exists && !exists.isDirectory) && !monitor.exists)
374                   {
375                      fileActivity |= monitor.AddFileNotify(FileChange { created = true }, monitor.fileName, null);
376                      monitor.exists = true;
377                   }
378                   else if((!exists || exists.isDirectory) && monitor.exists)
379                   {
380                      fileActivity |= monitor.AddFileNotify(FileChange { deleted = true }, monitor.fileName, null);
381                      monitor.exists = false;
382                   }            
383                   else if(monitor.exists)
384                   {
385                      FileStats stats { };
386                      if(FileGetStats(monitor.fileName, stats))
387                      {
388 #ifdef _DEBUG
389                         /*if(strstr(monitor.fileName, "Debugger"))
390                            printf("Now modified is %x\n", stats.modified);*/
391 #endif
392
393                         if(stats.modified != monitor.modified || stats.size != monitor.size)
394                         {
395                            // printf("Modified/size changed, adding file notify...\n");
396                            if(stats.modified > monitor.modified || monitor.modified - (TimeStamp)stats.modified > 2)
397                               fileActivity |= monitor.AddFileNotify(FileChange { modified = true }, monitor.fileName, null);
398                            monitor.size = stats.size;
399                            monitor.modified = stats.modified;
400 #ifdef _DEBUG
401                            /*if(strstr(monitor.fileName, "Debugger"))
402                               printf("Setting modified to %x\n", monitor.modified);*/
403 #endif
404                         }
405                         else if(stats.attribs != monitor.attribs)
406                         {
407                            // printf("Attribs changed, adding file notify...\n");
408
409                            fileActivity |= monitor.AddFileNotify(FileChange { attribs = true }, monitor.fileName, null);
410                            monitor.attribs = stats.attribs;
411                         }
412                      }
413                   }
414                }
415             }
416             if(fileActivity)
417             {
418                // printf("[%d] Signaling Event...\n", GetCurrentThreadID());
419                guiApp.SignalEvent();
420             }
421
422             // printf("[%d] Releasing Mutex...\n", GetCurrentThreadID());
423             globalSystem.fileMonitorMutex.Release();
424          }
425          
426          // printf("[%d] Sleeping...\n", GetCurrentThreadID());
427          Sleep(1.0 / 18.2);
428       }
429       return 0;
430    }
431 }