[Windows,Linux] Directory WatchDog

Share your advanced PureBasic knowledge/code with the community.
Deluxe0321
User
User
Posts: 69
Joined: Tue Sep 16, 2008 6:11 am
Location: ger

[Windows,Linux] Directory WatchDog

Post by Deluxe0321 »

Hi - just in case someone needs it.

The module above is a "wrapper" to easily use ReadDirectoryChangesW (Windows) or Inotify (Linux) to observe changes in a selected Directory.
It will recognize most common operations like:
- file deletion
- file changeing
- file adding

You can of course observe multiple directorys if you create a new "instance".

Note:
You'll need libnotify-dev on Linux in order to compile it.
$ sudo apt-get install libnotify-dev


Example included.

Code: Select all

;Supports x86, x64 - Ascii & unicode.

DeclareModule Storage
 
  Structure File
    Name.s
    Size.i
    Date.i
    ID.i
  EndStructure
 
  Structure Storage_Settings
    Folder.s
    Filter.i
    AllowedFiles.s
   
    List FileType.s() ; allowed file types (allowedfiles.s parsed) --> Content as example jpg
                      ; will be empty if default search "*.*"   
    *CallBack
    *UserData
    Mutex.i
    LastID.i
    ScanThread.i
  EndStructure
 
  Structure Storage
    Settings.Storage_Settings
    List File.File()
  EndStructure
 
  ;AllowedFiles.s Example (not case sensitive!):
  ; "*.*" --> get everything (default)
  ; "*.jpg" --> only jpg
  ; "*.png;*.jpg;*.jpeg" --> get jpg, jpeg & png
 
  Declare Init(Folder.s, AllowedFiles.s = "*.*", *DirectoryChangeCallBack = #Null, *UserData = #Null)
 
  Declare   ExamineStorage(Handle.i)
 
  Declare   NextStorageItem(Handle.i)
 
  Declare   LastStorageItem(Handle.i)
 
  Declare   GetItemSize(Handle.i,ItemID.i = -1)
 
  Declare.s GetItemFile(Handle.i,ItemID.i = -1)
 
  Declare   GetItemID(Handle.i)
 
  Declare   GetStorageSize(Handle.i)
 
EndDeclareModule

Module Storage
 
  ;{ - private
 
    Prototype DirectoryChange(Event.s,File.s,StorageID.i,*UserData = 0)
   
    Procedure NewID(*Storage.Storage)
     
      If Not *Storage
        Debug "STORAGE!"
        ProcedureReturn #False 
      EndIf
     
      LockMutex(*Storage\Settings\Mutex.i)
        *Storage\Settings\LastID.i + 1
        Protected LastID.i =   *Storage\Settings\LastID.i
      UnlockMutex(*Storage\Settings\Mutex.i)
     
      ProcedureReturn LastID.i
     
    EndProcedure
   
    Procedure RemoveFile(*Storage.Storage,File.s)
     
      Debug "trying to remove "+File.s
     
      If Not *Storage
        Debug "STORAGE!"
        ProcedureReturn #False 
      EndIf

      Protected FilePart.s = GetFilePart(File.s)
     
      LockMutex(*Storage\Settings\Mutex.i)
     
        ForEach *Storage\File()
          If *Storage\File()\Name.s = FilePart.s
            DeleteElement(*Storage\File()) 
            Protected DeleteDone.b = #True
            Break
          EndIf
        Next
     
      UnlockMutex(*Storage\Settings\Mutex.i)
     
      If DeleteDone.b
       Debug "Deleted: "+File.s
      EndIf
     
      ProcedureReturn DeleteDone.b

    EndProcedure   
   
    Procedure AddFile(*Storage.Storage,File.s)
     
      If Not FileSize(File.s) >= 0
        ProcedureReturn #False
      EndIf     
     
      If Not *Storage
        ProcedureReturn #False 
      EndIf
     
      Protected FileExt.s = LCase(GetExtensionPart(File.s))
      Protected FileID.i
     
      If ListSize(*Storage\Settings\FileType()) >= 1
        ForEach *Storage\Settings\FileType()
          If *Storage\Settings\FileType() = FileExt.s
            LockMutex(*Storage\Settings\Mutex.i)
            If AddElement(*Storage\File())
              *Storage\File()\Name.s = GetFilePart(File.s)
              *Storage\File()\Size.i = FileSize(File.s)
              *Storage\File()\Date.i = GetFileDate(File.s,#PB_Date_Created)
              *Storage\File()\ID.i   = NewID(*Storage)
              FileID.i = *Storage\File()\ID.i
            EndIf
            UnlockMutex(*Storage\Settings\Mutex.i)
            ProcedureReturn FileID.i
          EndIf
        Next
       
      Else
        LockMutex(*Storage\Settings\Mutex.i)
        If AddElement(*Storage\File())
          *Storage\File()\Name.s = GetFilePart(File.s)
          *Storage\File()\Size.i = FileSize(File.s)
          *Storage\File()\Date.i = GetFileDate(File.s,#PB_Date_Created)         
          *Storage\File()\ID.i   = NewID(*Storage)
           FileID.i = *Storage\File()\ID.i
          UnlockMutex(*Storage\Settings\Mutex.i)
          ProcedureReturn FileID.i
        EndIf
        UnlockMutex(*Storage\Settings\Mutex.i)
      EndIf
     
      ProcedureReturn #False
     
    EndProcedure
 
    Procedure InitialScan(*Storage.Storage)
       
      Protected Directory.i, File.s
       
      If Not *Storage
        ProcedureReturn #False 
      EndIf
     
      If Not FileSize(*Storage\Settings\Folder.s) -1
        ProcedureReturn #False
      EndIf
     
      If ListSize(*Storage\Settings\FileType()) = 1
        Directory.i = ExamineDirectory(#PB_Any,*Storage\Settings\Folder.s,"*."+*Storage\Settings\FileType()) 
      Else
        Directory.i = ExamineDirectory(#PB_Any,*Storage\Settings\Folder.s,"*.*")
      EndIf
     
      If Directory.i 
        While NextDirectoryEntry(Directory.i)
          If DirectoryEntryType(Directory.i) = #PB_DirectoryEntry_File
            File.s = *Storage\Settings\Folder.s + DirectoryEntryName(Directory.i)
            AddFile(*Storage,File.s)
          EndIf
        Wend
        FinishDirectory(Directory.i)
      Else
        ProcedureReturn #False
      EndIf
     
      Debug "Inital Directory scan found "+ListSize(*Storage\File())+" files.."
     
      ProcedureReturn #True
     
    EndProcedure   
   
    CompilerIf #PB_Compiler_OS = #PB_OS_Windows
     
      ;{ -import stuff
        ; Notify filter values
        #FILE_NOTIFY_CHANGE_FILE_NAME = 1
        #FILE_NOTIFY_CHANGE_DIR_NAME = 2
        #FILE_NOTIFY_CHANGE_ATTRIBUTES = 4
        #FILE_NOTIFY_CHANGE_SIZE = 8
        #FILE_NOTIFY_CHANGE_LAST_WRITE = $10
        #FILE_NOTIFY_CHANGE_LAST_ACCESS = $20
        #FILE_NOTIFY_CHANGE_CREATION = $40
        #FILE_NOTIFY_CHANGE_SECURITY = $100
        #FILE_NOTIFY_CHANGE_ALL = $17F
       
        ; not defined in purebasic
        #FILE_SHARE_DELETE = 4
       
        ; Notify events
        Enumeration
          #FILE_ACTION_ADDED = 1
          #FILE_ACTION_REMOVED
          #FILE_ACTION_MODIFIED
          #FILE_ACTION_RENAMED_OLD_NAME
          #FILE_ACTION_RENAMED_NEW_NAME
        EndEnumeration
       
        ; Needed for creating a watching thread
        Structure WatchFSParam_t
          Directory.s
          Filter.l
        EndStructure
       
        ; structure for the needed
        Structure FILE_NOTIFY_INFORMATION
          NextEntryOffset.l
          Action.l
          FileNameLength.l
          Filename.s{512}
        EndStructure
       
        Import "kernel32.lib"
          ReadDirectoryChangesW(hDirectory.l, *lpBuffer, nbBufferLen.l, bWatchSubTree.b, dwNotifyFilter.l, *lpBytesReturned, *lpOverlapped.OVERLAPPED, lpCompletitionRoutine)
        EndImport 
      ;} - import stuff
     
      Procedure WatchDirOrFile(*Storage.Storage)
       
        If Not *Storage
          ProcedureReturn #False 
        EndIf
       
        Protected FileID.i
        Protected DirectoryChange.DirectoryChange = *Storage\Settings\CallBack
        Protected *buffer = AllocateMemory(32*1024)
        Protected *ovlp.OVERLAPPED = AllocateMemory(SizeOf(OVERLAPPED))
        Protected dwOffset.l = 0
        Protected *pInfo.FILE_NOTIFY_INFORMATION
       
        hDir = CreateFile_(*Storage\Settings\Folder.s, #FILE_LIST_DIRECTORY, #FILE_SHARE_READ | #FILE_SHARE_WRITE | #FILE_SHARE_DELETE, #Null, #OPEN_EXISTING, #FILE_FLAG_BACKUP_SEMANTICS, #Null)

        While ReadDirectoryChangesW(hDir, *buffer, MemorySize(*buffer), #True, *Storage\Settings\Filter, bytesRead, *ovlp, #Null)
         
          dwOffset = 0
         
          Repeat
           
            FileID.i = 0
           
            *pInfo = *buffer + dwOffset
           
            filename$ = Trim(PeekS(@*pInfo\Filename, *pInfo\FileNameLength, #PB_Unicode))
           
            action$ = #NULL$
     
            Select *pInfo\Action
              Case #FILE_ACTION_ADDED
                action$ = "ADDED"
                FileID.i = AddFile(*Storage, *Storage\Settings\Folder.s + filename$)
              Case #FILE_ACTION_MODIFIED
                action$ = "MODIFIED"
                RemoveFile(*Storage, *Storage\Settings\Folder.s + filename$)
                FileID.i = AddFile(*Storage, *Storage\Settings\Folder.s + filename$)
              Case #FILE_ACTION_REMOVED
                action$ = "REMOVED"
                RemoveFile(*Storage, *Storage\Settings\Folder.s + filename$)
                FileID.i = -1
              Case #FILE_ACTION_RENAMED_NEW_NAME
                action$ = "RENAMED_NEWNAME"
                FileID.i = AddFile(*Storage, *Storage\Settings\Folder.s + filename$)
              Case #FILE_ACTION_RENAMED_OLD_NAME
                action$ = "RENAMED_OLDNAME"
                RemoveFile(*Storage, *Storage\Settings\Folder.s + filename$)
                FileID.i = -1
            EndSelect
           
            If *Storage\Settings\CallBack
              DirectoryChange(action$,filename$,FileID.i,*Storage\Settings\UserData)
            EndIf
           
            dwOffset + *pInfo\NextEntryOffset
           
          Until *pInfo\NextEntryOffset = 0
         
          FillMemory(*buffer, MemorySize(*Buffer))
         
        Wend
       
      EndProcedure   
      
    CompilerElseIf #PB_Compiler_OS = #PB_OS_Linux
      
      ;you need to install libnotify-dev
      ;  debian/ubuntu based distros
      ;  $ apt-get install libnotify-dev
      ImportC "-lnotify"
        inotify_init.i (app_name.p-utf8)
        inotify_add_watch.i (fd.i, pathname.p-utf8, mask.l)
        inotify_rm_watch.i (fd.i, wd.i)
        ;inotify_uninit()
      EndImport

      Structure inotify_event
        wd.i
        mask.i
        cookie.i
        len.i
        *name
      EndStructure
      
      #IN_ACCESS = $00000001
      #IN_MODIFY = $00000002
      #IN_ATTRIB = $00000004
      #IN_CLOSE_WRITE = $00000008
      #IN_CLOSE_NOWRITE = $00000010
      #IN_CLOSE = (#IN_CLOSE_WRITE | #IN_CLOSE_NOWRITE)
      #IN_OPEN = $00000020
      #IN_MOVED_FROM = $00000040
      #IN_MOVED_TO = $00000080
      #IN_MOVE = (#IN_MOVED_FROM | #IN_MOVED_TO)
      #IN_CREATE = $00000100
      #IN_DELETE = $00000200
      #IN_DELETE_SELF = $00000400
      #IN_MOVE_SELF = $00000800
      #IN_UNMOUNT = $00002000
      #IN_Q_OVERFLOW = $00004000
      #IN_IGNORED = $00008000
      #IN_CLOSE = (#IN_CLOSE_WRITE | #IN_CLOSE_NOWRITE)   
      #IN_MOVE = (#IN_MOVED_FROM | #IN_MOVED_TO)     
      #IN_ONLYDIR = $01000000
      #IN_DONT_FOLLOW = $02000000
      #IN_EXCL_UNLINK = $04000000
      #IN_MASK_ADD = $20000000
      #IN_ISDIR = $40000000
      #IN_ONESHOT = $80000000      
      
      ;you may edit this if you don't want to get notifyed on all events
      #IN_ALL_EVENTS = (#IN_ACCESS | #IN_MODIFY | #IN_ATTRIB | #IN_CLOSE_WRITE | #IN_CLOSE_NOWRITE | #IN_OPEN | #IN_MOVED_FROM | #IN_MOVED_TO | #IN_CREATE | #IN_DELETE | #IN_DELETE_SELF | #IN_MOVE_SELF)
      
      ;max buff
      #INotify_Buff = 16384

      Macro isMask(_MASK,_ISMASK)
        _MASK & _ISMASK = _ISMASK  
      EndMacro      
      
      Procedure WatchDirOrFile(*Storage.Storage)
        
        If Not *Storage
          ProcedureReturn #False 
        EndIf        
        
        Protected DirectoryChange.DirectoryChange = *Storage\Settings\CallBack
        Protected inotify.i = inotify_init(#NUL$)
        Protected length.i
        Protected action$, FileID.i, filename$
        Protected *temp.inotify_event
        
        If Not inotify.i
          Debug "could not init inotify!"
          ProcedureReturn #False  
        EndIf
        
        Protected *ReadBuff = AllocateMemory(#INotify_Buff)
        
        If Not *ReadBuff
          ProcedureReturn #False  
        EndIf
        
        Protected wd.l = inotify_add_watch(inotify.i, *Storage\Settings\Folder.s, *Storage\Settings\Filter.i)
        
        Repeat
          
          length.i = read_(inotify.i, *ReadBuff, #INotify_Buff)
          If (length.i <= 0)
            Debug "read error @ "+*Storage\Settings\Folder.s+ "! terminiating!"
            FreeMemory(*ReadBuff)
            End
          EndIf
          
          If length.i > 0
            
            *temp = *ReadBuff
            
            While *temp < *ReadBuff + length
              
              If Not *temp\name > 0
                *temp = *temp + SizeOf(inotify_event) + *temp\len
                Continue  
              EndIf
                
             filename$ = PeekS(@*temp\name,-1,#PB_Ascii)

              If isMask(*temp\mask,#IN_CREATE) Or isMask(*temp\mask,#IN_MOVED_TO) ;file created
                  If FileSize(*Storage\Settings\Folder.s + filename$) > 0 Or FileSize(*Storage\Settings\Folder.s + filename$) = -2
                    action$ = "ADDED"
                    FileID.i = AddFile(*Storage, *Storage\Settings\Folder.s + filename$)
                  EndIf
              ElseIf isMask(*temp\mask,#IN_ATTRIB) Or isMask(*temp\mask,#IN_MODIFY)  ;file modified
                  If FileSize(*Storage\Settings\Folder.s + filename$) > 0 Or FileSize(*Storage\Settings\Folder.s + filename$) = -2
                    action$ = "MODIFIED"
                    RemoveFile(*Storage, *Storage\Settings\Folder.s + filename$)               
                    FileID.i = AddFile(*Storage, *Storage\Settings\Folder.s + filename$)
                  EndIf 
              ElseIf isMask(*temp\mask,#IN_DELETE) Or isMask(*temp\mask,#IN_MOVED_FROM) ;file deleted / moved to diff dir  
                  action$ = "REMOVED"
                  RemoveFile(*Storage, *Storage\Settings\Folder.s + filename$)
                  FileID.i = -1
              Else
                *temp = *temp + SizeOf(inotify_event) + *temp\len  
                Continue    
              EndIf             
              
              If *Storage\Settings\CallBack
                DirectoryChange(action$,filename$,FileID.i,*Storage\Settings\UserData)
              EndIf
  
              *temp = *temp + SizeOf(inotify_event) + *temp\len
                
            Wend
            
          EndIf
          
        ForEver
        
        
      EndProcedure
      
      
    CompilerEndIf

  ;} - private
 
  Procedure Init(Folder.s, AllowedFiles.s = "*.*", *DirectoryChangeCallBack = #Null, *UserData = #Null)
   
    Protected Counter.i, StringPart.s
   
    If Not FileSize(Folder.s) = -2
      ProcedureReturn #False   
    EndIf
   
    Protected *Storage.Storage = AllocateMemory(SizeOf(Storage))
   
    If Not *Storage
      ProcedureReturn #False 
    EndIf
   
    InitializeStructure(*Storage,Storage)
   
    CompilerIf #PB_Compiler_OS = #PB_OS_Windows
      If Right(Folder.s,1) <> "\"
        Folder.s + "\" 
      EndIf
    CompilerElse
      If Right(Folder.s,1) <> "/"
        Folder.s + "/" 
      EndIf
    CompilerEndIf
   
    If AllowedFiles.s = "*.*"
    Else
      If CountString(AllowedFiles.s,";") = 0
        ;only one specific search string
        If AllowedFiles.s <> "*.*"
          If AddElement(*Storage\Settings\FileType())
            *Storage\Settings\FileType() = LCase(StringField(AllowedFiles.s,2,".")) 
          EndIf
        EndIf
      Else
        For Counter.i = 1 To CountString(AllowedFiles.s,";") + 1
          StringPart.s = StringField(AllowedFiles.s,Counter.i,";")   
          If StringPart.s <> "*.*"
            If AddElement(*Storage\Settings\FileType())
              *Storage\Settings\FileType() = LCase(StringField(StringPart.s,2,".")) 
            EndIf
          EndIf
        Next
      EndIf
    EndIf
         
    Debug "Got "+ListSize(*Storage\Settings\FileType())+" different file type to look for.."
   
   
    *Storage\Settings\Folder.s       = Folder.s
    *Storage\Settings\AllowedFiles.s = AllowedFiles.s
    *Storage\Settings\CallBack       = *DirectoryChangeCallBack
    *Storage\Settings\UserData       = *UserData
    *Storage\Settings\Mutex.i        = CreateMutex()
   
    InitialScan(*Storage)
    
    CompilerSelect #PB_Compiler_OS
        CompilerCase #PB_OS_Windows
          *Storage\Settings\Filter.i       = #FILE_NOTIFY_CHANGE_ALL
        CompilerCase #PB_OS_Linux
          *Storage\Settings\Filter.i       = #IN_ALL_EVENTS ;// you may edit the constant to filter a bit more     
    CompilerEndSelect
    *Storage\Settings\ScanThread.i   = CreateThread(@WatchDirOrFile(),*Storage)
   
    ProcedureReturn *Storage
   
  EndProcedure
 
  Procedure ExamineStorage(Handle.i)
   
    Protected *Storage.Storage = Handle.i
   
    If Not *Storage
      ProcedureReturn #False 
    EndIf
   
    If ListSize(*Storage\File())
      ProcedureReturn ResetList(*Storage\File())
    EndIf
   
  EndProcedure
 
  Procedure NextStorageItem(Handle.i)
   
    Protected *Storage.Storage = Handle.i
   
    If Not *Storage
      ProcedureReturn #False 
    EndIf
   
    If ListSize(*Storage\File())
      ProcedureReturn NextElement(*Storage\File())
    EndIf
   
  EndProcedure
 
  Procedure LastStorageItem(Handle.i)
   
    Protected *Storage.Storage = Handle.i
   
    If Not *Storage
      ProcedureReturn #False 
    EndIf
   
    If ListSize(*Storage\File())
      ProcedureReturn LastElement(*Storage\File())
    EndIf
   
  EndProcedure 
 
  Procedure GetItemSize(Handle.i,ItemID.i = -1)
   
    Protected *Storage.Storage = Handle.i
   
    If Not *Storage
      ProcedureReturn #False 
    EndIf
   
    If ListSize(*Storage\File())
     
      If ItemID.i > -1
        Protected *Curr = @*Storage\File()
       
        ForEach *Storage\File()
          If *Storage\File()\ID.i = ItemID.i
            Protected Size.i = *Storage\File()\Size.i
            If *Curr
              ChangeCurrentElement(*Storage\File(),*Curr)
            EndIf
            ProcedureReturn Size.i
          EndIf
        Next
     
      EndIf     
     
      ProcedureReturn *Storage\File()\Size.i
    EndIf
   
  EndProcedure
 
  Procedure.s GetItemFile(Handle.i,ItemID.i = -1)
   
    Protected *Storage.Storage = Handle.i
   
    If Not *Storage
      ProcedureReturn #NULL$
    EndIf
   
    If ListSize(*Storage\File())
     
      If ItemID.i > -1
       ; Protected *Curr = @*Storage\File()
       
        ForEach *Storage\File()
          If *Storage\File()\ID.i = ItemID.i
            Protected File.s = *Storage\Settings\Folder.s +*Storage\File()\Name.s 
           
            Debug "Asked to provide "+FileID.i+ " is "+File.s
           
            ProcedureReturn File.s
          EndIf
        Next
     
      EndIf
     
      ProcedureReturn *Storage\Settings\Folder.s + *Storage\File()\Name.s
    EndIf   
   
  EndProcedure
 
  Procedure GetItemID(Handle.i)
   
    Protected *Storage.Storage = Handle.i
   
    If Not *Storage
      ProcedureReturn #False 
    EndIf
   
    If ListSize(*Storage\File())
      ProcedureReturn *Storage\File()\ID.i
    EndIf   
   
  EndProcedure
 
  Procedure GetStorageSize(Handle.i)
   
    Protected *Storage.Storage = Handle.i
   
    If Not *Storage
      ProcedureReturn #False 
    EndIf
   
    ProcedureReturn ListSize(*Storage\File())
  EndProcedure
 
EndModule


CompilerIf #PB_Compiler_IsMainFile
 
  Procedure DirCallBack(Event.s,File.s,StorageID.i,*UserData)
   
    Debug "Event = "+Event.s + " @ "+File.s + " --> "+StorageID.i
   
  EndProcedure
 
  Define Path.s = PathRequester("Choose path to observe", #NUL$)
 
  If Path.s = ""
    End 
  EndIf
 
  Define Storage.i = Storage::Init(Path.s,"*.*",@DirCallBack())
  ;Define Storage.i = Storage::Init("C:\Development\XAMPP\htdocs\images","*.jpg;*.png;*.jpeg",@DirCallBack())
  If Storage.i
    Repeat
     
      If Storage::ExamineStorage(Storage.i)
        While Storage::NextStorageItem(Storage.i)
          Debug "> "+Storage::GetItemFile(Storage.i) + "(" +  Storage::GetItemSize(Storage.i) + ") = "+Storage::GetItemID(Storage.i)
        Wend
      EndIf
       
      Debug "--------------------------------"
     
      Delay(8000)
    ForEver
  EndIf 
 
CompilerEndIf
If anyone knows how to do that on MacOS I'm happy to include it.

Thank you!

//12/08/14 Fixed issues (Windows) & added linux support ( thanks to infratec & electrochrisso)
Last edited by Deluxe0321 on Mon Dec 08, 2014 8:34 am, edited 2 times in total.
infratec
Always Here
Always Here
Posts: 6817
Joined: Sun Sep 07, 2008 12:45 pm
Location: Germany

Re: [Windows] Directory WatchDog

Post by infratec »

Hi,

in Linux you do this with inotify:
http://www.purebasic.fr/english/viewtop ... it=inotify

But I don't know how you want to integrate this in your code.

Bernd
User avatar
electrochrisso
Addict
Addict
Posts: 980
Joined: Mon May 14, 2007 2:13 am
Location: Darling River

Re: [Windows] Directory WatchDog

Post by electrochrisso »

Thanks for sharing, could come in handy, except I cant work it out.
I set it to a directory and change a text file and save it several times, nothing changes in the debug window, the numbers for the file always the same, it seems to just list all the files in the directory.
PureBasic! Purely one of the best 8)
Deluxe0321
User
User
Posts: 69
Joined: Tue Sep 16, 2008 6:11 am
Location: ger

Re: [Windows,Linux] Directory WatchDog

Post by Deluxe0321 »

Code Updated:
- Windows: Changed filter to "all". The module does now also recognize file changes
- Linux: Added Linux support.

Thank you!
User avatar
electrochrisso
Addict
Addict
Posts: 980
Joined: Mon May 14, 2007 2:13 am
Location: Darling River

Re: [Windows,Linux] Directory WatchDog

Post by electrochrisso »

Thanks Deluxe0321, it is more clear to me now that I look through your good code,
I like the addition of #IN_ALL_EVENTS. :)
PureBasic! Purely one of the best 8)
User avatar
ts-soft
Always Here
Always Here
Posts: 5756
Joined: Thu Jun 24, 2004 2:44 pm
Location: Berlin - Germany

Re: [Windows,Linux] Directory WatchDog

Post by ts-soft »

:D Thanks, very useful!
PureBasic 5.73 | SpiderBasic 2.30 | Windows 10 Pro (x64) | Linux Mint 20.1 (x64)
Old bugs good, new bugs bad! Updates are evil: might fix old bugs and introduce no new ones.
Image
Post Reply