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