Monitoring a directory for new files being added

Just starting out? Need help? Post your questions and find answers here.
merendo
Enthusiast
Enthusiast
Posts: 449
Joined: Sat Apr 26, 2003 7:24 pm
Location: Germany
Contact:

Re: Monitoring a directory for new files being added

Post by merendo »

Terribly sorry to dig up this old thread, but I am having problems with the solution by Sparkie. It seems to randomly miss events.

I simply tested the code (which I updated a little bit, I'll post it below) by copy-n-pasting a considerable number of files and a few subdirectories, each of them also with files in them, but not all the inserted files appear to be registering to ReadDirectoryChangesW. When I delete a great many files at once, even more events seem to go unregistered.

I did some Google digging on my own already, but I'm afraid my knowledge of the Windows API will only take me so far. Maybe somebody could have a look at http://social.msdn.microsoft.com/forums ... ced6ffaa8/ and http://qualapps.blogspot.de/2010/05/und ... ngesw.html and suggest the necessary changes to the code below. Thanks!

Code: Select all

    ;>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>
    ; App Name: ReadDirectoryChangesW
    ; Author  : Sparkie
    ; Date    : April 23, 2005
    ;>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>
    ;>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>
    ; Check for NT4/2000/XP/2003
    ;>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>
If OSVersion() < #PB_OS_Windows_NT_4
  MessageRequester("Incorrect OS", "This program requires at least NT4.")
  End
EndIf
    ;>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>
    ; Window Enumerations
    ;>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>
    Enumeration
      #Win_Main
    EndEnumeration
    ;>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>
    ; Gadget Enumerations
    ;>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>
    Enumeration
      #Button_Dir
      #Text_Dir
      #ListIcon_RDCW
    EndEnumeration
    ;>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>
    ; Constants for use in ReadDirectoryChangesW function
    ;>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>
    ; Use these flags in the CallFunctionFast(*pRDCW, ...) line
    #FILE_NOTIFY_CHANGE_FILE_NAME = 1
    ; Any file name change in the watched directory or subtree
    ; causes a change notification wait operation to return.
    ; Changes include renaming, creating, or deleting a file name.
    #FILE_NOTIFY_CHANGE_DIR_NAME = 2
    ; Any directory-name change in the watched directory or
    ; subtree causes a change notification wait operation to return.
    ; Changes include creating or deleting a directory.
    #FILE_NOTIFY_CHANGE_ATTRIBUTES = 4
    ; Any attribute change in the watched directory or subtree causes
    ; a change notification wait operation to return.
    #FILE_NOTIFY_CHANGE_SIZE = 8
    ; Any file-size change in the watched directory or subtree causes
    ; a change notification wait operation to return. The operating
    ; system detects a change in file size only when the file is written
    ; to the disk. For operating systems that use extensive caching,
    ; detection occurs only when the cache is sufficiently flushed.
    #FILE_NOTIFY_CHANGE_LAST_WRITE = $10
    ; Any change to the last write-time of files in the watched directory
    ; or subtree causes a change notification wait operation to return. The
    ; operating system detects a change to the last write-time only when
    ; the file is written to the disk. For operating systems that use extensive
    ; caching, detection occurs only when the cache is sufficiently flushed.
    #FILE_NOTIFY_CHANGE_LAST_ACCESS = $20
    ;Any change to the last access time of files in the watched directory or
    ; subtree causes a change notification wait operation to return.
    #FILE_NOTIFY_CHANGE_CREATION = $40
    ;Any change to the creation time of files in the watched directory or subtree
    ; causes a change notification wait operation to return.
    #FILE_NOTIFY_CHANGE_SECURITY = $100
    ; Any security-descriptor change in the watched directory or subtree causes a
    ; change notification wait operation to return.
    ;>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>
    ; Constants for myFILE_NOTIFY_INFORMATION (*fni) actions
    ;>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>
    #FILE_SHARE_DELETE = 4
    #FILE_ACTION_ADDED = 1
    #FILE_ACTION_REMOVED = 2
    #FILE_ACTION_MODIFIED = 3
    #FILE_ACTION_RENAMED_OLD_NAME = 4
    #FILE_ACTION_RENAMED_NEW_NAME = 5
    ;>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>
    ; Constants for myFILE_NOTIFY_INFORMATION offsets
    ;>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>
    ; This is how we access information when more than 1 action
    ; occurs for a file in our watched directory
    #ActionOffset = 4
    #CharLenOffset = 8
    #FileNameOffset = 12
    ;>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>
    ; FILE_NOTIFY_INFORMATION structure
    ;>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>
    Structure myFILE_NOTIFY_INFORMATION
      NextEntryOffset.l
      action.l
      FileNameLength.l
      FileName.w[1]
    EndStructure
    ;>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>
    ; Globals
    ;>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>
    ; Pointer to ReadDirectoryChangesW function
    Global *pRDCW
    ; pointer ot myFILE_NOTIFY_INFORMATION structure
    Global *fni.myFILE_NOTIFY_INFORMATION
    ; Handle to our watched directory
    Global hDir
    ; Variable for ending app when #True
    Global quit
    ;>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>
    ; Open KERNEL32.DLL for ReadDirectoryChangesW function
    ;>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>
    If OpenLibrary(0, "KERNEL32.DLL")
      ; Get pointer to ReadDirectoryChangesW function
      *pRDCW = GetFunction(0, "ReadDirectoryChangesW")
      If *pRDCW = 0
        ; If function not found, close KERNEL32.DLL and quit
        CloseLibrary(0)
        MessageRequester("Error", "ReadDirectoryChangesW function not found.")
        End
      EndIf
    Else
      ; If KERNEL32.DLL not found, quit
      MessageRequester("Error", "KERNEL32.DLL not found.")
      End
    EndIf
    ;>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>
    ; Procedure to convert Unicode file name to ANSI
    ;>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>
    Procedure.s Uni2Ansi(*uni.l, uLen)
      ansi$ = Space(uLen)
      WideCharToMultiByte_(#CP_ACP, 0, *uni, -1, @ansi$, uLen, #Null, #Null)
      ProcedureReturn ansi$ 
    EndProcedure
    ;>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>
    ; Procedure to retrieve our directory changes
    ;>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>
    ; fniOffset is used for muliple action on 1 file
    Procedure GetRDCWInfo(fniOffset)
      ; Get the action
      action = PeekL(*fni + #ActionOffset + fniOffset)
      ; Get a time stamp for this action
      timeStamp$ = FormatDate("%mm/%dd/%yyyy %hh:%ii:%ss", Date())
      ; Get the UNI lenght of the file name
      uniLen = PeekL(*fni + #CharLenOffset + fniOffset)
      ; Get the ANSI version of the file name
      fileName$ = Uni2Ansi(*fni + #FileNameOffset + fniOffset, uniLen/2)
      ; Set the string to be displayed for the action
      Select action
        Case #FILE_ACTION_ADDED
          action$ = "was added to directory."
        Case #FILE_ACTION_REMOVED
          action$ = "was removed from directory."
        Case #FILE_ACTION_MODIFIED
          action$ = "attribute or time stamp modified."
        Case #FILE_ACTION_RENAMED_OLD_NAME
          action$ = "was renamed."
        Case #FILE_ACTION_RENAMED_NEW_NAME
          action$ = "is the new file name."
      EndSelect
      ; Add the info to the ListIconGadget
      If Not action = #FILE_ACTION_MODIFIED ; For now, I am content with knowing when an entry was created, deleted or renamed.
      	AddGadgetItem(#ListIcon_RDCW, -1, fileName$ + Chr(10) + action$ + Chr(10) + timeStamp$)
      EndIf
    EndProcedure
    ;>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>
    ; Procedure (Thread) to set the ReadDirectoryChangesW calls
    ;>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>
    Procedure RDCW(dir)
      ; Allocate memory for our myFILE_NOTIFY_INFORMATION pointer
      *fni = AllocateMemory(1024)
      ; Loop calls to ReadDirectoryChangesW
      ; We're watching for rename, delete, create, write
      While CallFunctionFast(*pRDCW, dir, *fni, 1024, #True, #FILE_NOTIFY_CHANGE_FILE_NAME | #FILE_NOTIFY_CHANGE_DIR_NAME | #FILE_NOTIFY_CHANGE_LAST_WRITE, @size, #Null, #Null)
        ; Get the info for offset 0
        GetRDCWInfo(0)
        ; See if there are more entries for this call
        nextEntry = *fni\NextEntryOffset
        ; If so, get the info
        While nextEntry > 0
          If previousEntry = nextEntry
            Break
          EndIf
          GetRDCWInfo(nextEntry)
          ; See if there are more entries for this call
          nextEntry = PeekL(*fni + nextEntry)
          previousEntry = nextEntry
        Wend
        ; No more entries, go to next ReadDirectoryChangesW call
      Wend
    EndProcedure
    ;>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>
    ; Main window and gadgets
    ;>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>
    If OpenWindow(0, 0, 0, 545, 350, "ReadDirectoryChangesW", #PB_Window_SystemMenu | #PB_Window_ScreenCentered ) And CreateGadgetList(WindowID(0))
      ButtonGadget(#Button_Dir, 5, 5, 150, 20, "Select Directory to watch")
      TextGadget(#Text_Dir, 5, 5, 535, 20, "")
      DisableGadget(#Text_Dir, 1)
      ListIconGadget(#ListIcon_RDCW, 5, 40, 535, 300, "File", 200, #PB_ListIcon_FullRowSelect | #PB_ListIcon_AlwaysShowSelection | #PB_ListIcon_GridLines)
      AddGadgetColumn(#ListIcon_RDCW, 1, "Action", 200)
      AddGadgetColumn(#ListIcon_RDCW, 2, "Time", 130)
      quit = #False
    ;>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>
    ; Main event loop
    ;>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>
      Repeat
        event = WaitWindowEvent()
        Select event
          Case #PB_Event_Gadget
            Select EventGadget()
              Case (#Button_Dir)
                ; Button pressed for getting directory to watch
                myPath$ = PathRequester("Select a Folder to watch", "c:\")
                ; If there is a valid path, start our ReadDirectoryChangesW thread
                If myPath$
                  DisableGadget(#Button_Dir, 1)
                  ; Get a handle to our watched directory
                  hDir = CreateFile_(myPath$, #FILE_LIST_DIRECTORY, #FILE_SHARE_WRITE | #FILE_SHARE_READ | #FILE_SHARE_DELETE, #Null, #OPEN_EXISTING, #FILE_FLAG_BACKUP_SEMANTICS, #Null)
                  ; --> Our thread for change notifications with the directory handle
                  myThread = CreateThread(@RDCW(), hDir)
                  DisableGadget(#Text_Dir, 0)
                  SetGadgetText(#Text_Dir, myPath$ + " is being watched.")
                EndIf
            EndSelect
          Case #PB_Event_CloseWindow
            FreeMemory(*fni)
            ; Kill our ReadDirectoryChangesW thread
            KillThread(myThread)
            ; Close handle to our watched directory
            CloseHandle_(hDir)
            ; If KERNEL32.DLL is open, close it
            If IsLibrary(0)
              CloseLibrary(0)
            EndIf
            quit = #True
        EndSelect
      Until quit 
    EndIf
    End
    ;>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>
    ; ExecutableFormat=Windows
    ; Requires=NT4/2000/XP/Server2003
    ; EOF
    ;>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>
The truth is never confined to a single number - especially scientific truth!
User avatar
Fangbeast
PureBasic Protozoa
PureBasic Protozoa
Posts: 4789
Joined: Fri Apr 25, 2003 3:08 pm
Location: Not Sydney!!! (Bad water, no goats)

Re: Monitoring a directory for new files being added

Post by Fangbeast »

Been playing with this code and must admit to not following a lot of it however, I also wanted modified files added to the list so change procedure GetRDCWInfo(fniOffset) at line 182 and 182, comment them out.

Code: Select all

;  If Not action = #FILE_ACTION_MODIFIED ; For now, I am content with knowing when an entry was created, deleted or renamed.
     AddGadgetItem(#ListIcon_RDCW, -1, fileName$ + Chr(10) + action$ + Chr(10) + timeStamp$)
 ; EndIf
Also, towards the end of the program, change FreeMemory(*fni)

to

If *fni
FreeMemory(*fni)
EndIf

and KillThread(myThread)

to

If myThread
KillThread(myThread)
EndIf

to avoid program errors. I'm still a rank beginner at this. Next i'll mask out duplicate change reports.
Amateur Radio/VK3HAF, (D-STAR/DMR and more), Arduino, ESP32, Coding, Crochet
User avatar
Thunder93
Addict
Addict
Posts: 1788
Joined: Tue Mar 21, 2006 12:31 am
Location: Canada

Re:

Post by Thunder93 »

Old topic but I was curios if you or anyone ever addressed this anomaly in the code? Awesome piece of code to have done up way back then, but now seeing how far PB has advanced, possible to have this anomaly corrected for once in for all?

I know it isn't normal, working with a single file on an idling / inactive folder don't duplicate this. Working with simultaneous files and experimenting reproduced double events.

Another problem I'm experiencing, five small-sized files concurrently issued to be copy to a watched folder... Five messages yes but...

Two of "was added to directory." messages
Two "attribute or time stamped modified." messages for the watched folder itself
Plus one more "attribute or time stamped modified." message

- Same file has two messages, “was added to directory” and "attribute or time stamped modified."
- Two messages for the watched folder itself
- And another filename triggered a "was added to directory."

This leaves me with three different files not having been listed like it should have been.
Sparkie wrote:I get anywhere from 1 to 3 "attribute or timestamp modified" messages. Googled around to find that other people are getting the same results. Some seem to think it's due to a timestamp change being signaled during each write operation. Doesn't seem right to me but I'll keep searching. ;)
ʽʽSuccess is almost totally dependent upon drive and persistence. The extra energy required to make another effort or try another approach is the secret of winning.ʾʾ --Dennis Waitley
User avatar
Thunder93
Addict
Addict
Posts: 1788
Joined: Tue Mar 21, 2006 12:31 am
Location: Canada

Re: Monitoring a directory for new files being added

Post by Thunder93 »

Was late but I see now... the famous PB offset problems, clearing the buffer every-time is a requirement!
ʽʽSuccess is almost totally dependent upon drive and persistence. The extra energy required to make another effort or try another approach is the secret of winning.ʾʾ --Dennis Waitley
User avatar
Thunder93
Addict
Addict
Posts: 1788
Joined: Tue Mar 21, 2006 12:31 am
Location: Canada

Re: Monitoring a directory for new files being added

Post by Thunder93 »

Clearing buffer and pointers structure still doesn't resolve the PB offset problem entirely. I suppose I have lots to learn to have this functioning 100% error-free.
ʽʽSuccess is almost totally dependent upon drive and persistence. The extra energy required to make another effort or try another approach is the secret of winning.ʾʾ --Dennis Waitley
User avatar
Thunder93
Addict
Addict
Posts: 1788
Joined: Tue Mar 21, 2006 12:31 am
Location: Canada

Re: Monitoring a directory for new files being added

Post by Thunder93 »

Even then I still get the reading issues, see image.

Image
ʽʽSuccess is almost totally dependent upon drive and persistence. The extra energy required to make another effort or try another approach is the secret of winning.ʾʾ --Dennis Waitley
erion
Enthusiast
Enthusiast
Posts: 128
Joined: Sun Jan 24, 2010 11:12 pm

Re: Monitoring a directory for new files being added

Post by erion »

Hi,
I was experimenting with this a bit today. Many people seemed to have problems with missing events, make sure you increase the buffer size (both when allocating memory, and when specifying it in the function call). 4096 bytes work fine with the default parameters, after copying 16 files, I receive all 3 events for each. Certainly, if you'd like to subscribe to more change events and you are expecting a huge list of changes, you must anticipate this and allocate accordingly.
While this API works, I personally wouldn't mind something that is more robust, but as far as I know, this is the best we can get for now.
To see a world in a grain of sand,
And a heaven in a wild flower,
Hold infinity in the palm of your hand,
And eternity in an hour.

- W. B.

Visit my site, also for PureBasic goodies http://erion.tdrealms.com
normeus
Enthusiast
Enthusiast
Posts: 470
Joined: Fri Apr 20, 2012 8:09 pm
Contact:

Re: Monitoring a directory for new files being added

Post by normeus »

If there is a better/newer way of doing this I could not find it.
I added a "flush File Buffer" which should help according to this post:
https://social.msdn.microsoft.com/Forum ... m=netfxbcl

also increased the buffer as recomended by erion

This will only work for letter drives (A - Z ) in its current state , no UNC , because it takes the drive letter and flushes the buffers
for that drive to make sure all updates are done before reading changes to file system. you can not watch full drives only directories

It is currently working for me.
Thank you.

Code: Select all

    ;>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>
    ; App Name: ReadDirectoryChangesW
    ; Author  : Sparkie
    ; Date    : April 23, 2005
    ;>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>
    ;>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>
    ; Check for NT4/2000/XP/2003
    ;>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>
If OSVersion() < #PB_OS_Windows_NT_4
  MessageRequester("Incorrect OS", "This program requires at least NT4.")
  End
EndIf
     
    ;>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>
    ; Window Enumerations
    ;>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>
    Enumeration
      #Win_Main
    EndEnumeration
    ;>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>
    ; Gadget Enumerations
    ;>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>
    Enumeration
      #Button_Dir
      #Text_Dir
      #ListIcon_RDCW
    EndEnumeration
    ;>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>
    ; Constants for use in ReadDirectoryChangesW function
    ;>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>
    ; Use these flags in the CallFunctionFast(*pRDCW, ...) line
    #FILE_NOTIFY_CHANGE_FILE_NAME = 1
    ; Any file name change in the watched directory or subtree
    ; causes a change notification wait operation to return.
    ; Changes include renaming, creating, or deleting a file name.
    #FILE_NOTIFY_CHANGE_DIR_NAME = 2
    ; Any directory-name change in the watched directory or
    ; subtree causes a change notification wait operation to return.
    ; Changes include creating or deleting a directory.
    #FILE_NOTIFY_CHANGE_ATTRIBUTES = 4
    ; Any attribute change in the watched directory or subtree causes
    ; a change notification wait operation to return.
    #FILE_NOTIFY_CHANGE_SIZE = 8
    ; Any file-size change in the watched directory or subtree causes
    ; a change notification wait operation to return. The operating
    ; system detects a change in file size only when the file is written
    ; to the disk. For operating systems that use extensive caching,
    ; detection occurs only when the cache is sufficiently flushed.
    #FILE_NOTIFY_CHANGE_LAST_WRITE = $10
    ; Any change to the last write-time of files in the watched directory
    ; or subtree causes a change notification wait operation to return. The
    ; operating system detects a change to the last write-time only when
    ; the file is written to the disk. For operating systems that use extensive
    ; caching, detection occurs only when the cache is sufficiently flushed.
    #FILE_NOTIFY_CHANGE_LAST_ACCESS = $20
    ;Any change to the last access time of files in the watched directory or
    ; subtree causes a change notification wait operation to return.
    #FILE_NOTIFY_CHANGE_CREATION = $40
    ;Any change to the creation time of files in the watched directory or subtree
    ; causes a change notification wait operation to return.
    #FILE_NOTIFY_CHANGE_SECURITY = $100
    ; Any security-descriptor change in the watched directory or subtree causes a
    ; change notification wait operation to return.
    ;>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>
    ; Constants for myFILE_NOTIFY_INFORMATION (*fni) actions
    ;>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>
    #FILE_SHARE_DELETE = 4
    #FILE_ACTION_ADDED = 1
    #FILE_ACTION_REMOVED = 2
    #FILE_ACTION_MODIFIED = 3
    #FILE_ACTION_RENAMED_OLD_NAME = 4
    #FILE_ACTION_RENAMED_NEW_NAME = 5
    ;>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>
    ; Constants for myFILE_NOTIFY_INFORMATION offsets
    ;>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>
    ; This is how we access information when more than 1 action
    ; occurs for a file in our watched directory
    #ActionOffset = 4
    #CharLenOffset = 8
    #FileNameOffset = 12
    ;>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>
    ; FILE_NOTIFY_INFORMATION structure
    ;>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>
    Structure myFILE_NOTIFY_INFORMATION
      NextEntryOffset.l
      action.l
      FileNameLength.l
      FileName.w[1]
    EndStructure
    ;>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>
    ; Globals
    ;>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>
    ; Pointer to ReadDirectoryChangesW function
    Global *pRDCW
    ; pointer ot myFILE_NOTIFY_INFORMATION structure
    Global *fni.myFILE_NOTIFY_INFORMATION
    ; Handle to our watched directory
    Global hDir
    ; Variable for ending app when #True
    Global quit
    ; pointer to FlushFileBuffers
    Global   *flushFB
    ; letter of hard drive to watch 
    Global HDVolume.s 
    
    ;>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>
    ; Open KERNEL32.DLL for ReadDirectoryChangesW function
    ;>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>
    If OpenLibrary(0, "KERNEL32.DLL")
      ;Added a pointer to FlushFileBuffers which forces changes to be updated
      *flushFB = GetFunction(0, "FlushFileBuffers")
      If *flushFB = 0
        ; If function not found, close KERNEL32.DLL and quit
        CloseLibrary(0)
        MessageRequester("Error", "FlushFileBuffers function not found.")
        End
      EndIf    
      ; Get pointer to ReadDirectoryChangesW function
      *pRDCW = GetFunction(0, "ReadDirectoryChangesW")
      If *pRDCW = 0
        ; If function not found, close KERNEL32.DLL and quit
        CloseLibrary(0)
        MessageRequester("Error", "ReadDirectoryChangesW function not found.")
        End
      EndIf  
    Else
      ; If KERNEL32.DLL not found, quit
      MessageRequester("Error", "KERNEL32.DLL not found.")
      End
    EndIf
    ;>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>
    ; Procedure to convert Unicode file name to ANSI
    ;>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>
    Procedure.s Uni2Ansi(uni.l, uLen)
      ansi$ = Space(uLen)
      WideCharToMultiByte_(#CP_ACP, 0, uni, -1, @ansi$, uLen, #Null, #Null)
      ProcedureReturn ansi$
    EndProcedure
    ;>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>
    ; Procedure to retrieve our directory changes
    ;>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>
    ; fniOffset is used for muliple action on 1 file
    Procedure GetRDCWInfo(fniOffset)
      ; Get the action
      action = PeekL(*fni + #ActionOffset + fniOffset)
      ; Get a time stamp for this action
      timeStamp$ = FormatDate("%mm/%dd/%yyyy %hh:%ii:%ss", Date())
      ; Get the UNI lenght of the file name
      uniLen = PeekL(*fni + #CharLenOffset + fniOffset)
      ; Get the ANSI version of the file name
      fileName$ = Uni2Ansi(*fni + #FileNameOffset + fniOffset, uniLen/2)
      ; Set the string to be displayed for the action
      Select action
        Case #FILE_ACTION_ADDED
          action$ = "was added to directory."
        Case #FILE_ACTION_REMOVED
          action$ = "was removed from directory."
        Case #FILE_ACTION_MODIFIED
          action$ = "attribute or time stamp modified."
        Case #FILE_ACTION_RENAMED_OLD_NAME
          action$ = "was renamed."
        Case #FILE_ACTION_RENAMED_NEW_NAME
          action$ = "is the new file name."
      EndSelect
      ; Add the info to the ListIconGadget
      If Not action = #FILE_ACTION_MODIFIED ; For now, I am content with knowing when an entry was created, deleted or renamed.
         AddGadgetItem(#ListIcon_RDCW, -1, fileName$ + Chr(10) + action$ + Chr(10) + timeStamp$)
      EndIf
    EndProcedure

    ;>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>
    ; Procedure (Thread) to set the ReadDirectoryChangesW calls
    ;>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>
    Procedure RDCW(dir)
      ; Allocate memory for our myFILE_NOTIFY_INFORMATION pointer
      *fni = AllocateMemory(4096)
      ; Loop calls to ReadDirectoryChangesW
      ; We're watching for rename, delete, create, write
      
      
      
      ;Create a file handle to the Volume being watched / not the directory but the main Volume
      
      volume = CreateFile_( "\\.\"+HDVolume+":\0",#GENERIC_WRITE,#FILE_SHARE_WRITE,#Null,#OPEN_EXISTING,0,#Null);

      
      ; FlushFileBuffers before call
      CallFunctionFast(*flushFB,volume)
      ; now do a call
      While CallFunctionFast(*pRDCW, dir, *fni, 1024, #True, #FILE_NOTIFY_CHANGE_FILE_NAME | #FILE_NOTIFY_CHANGE_DIR_NAME | #FILE_NOTIFY_CHANGE_LAST_WRITE, @size, #Null, #Null)
        ; Get the info for offset 0
        GetRDCWInfo(0)
        ; See if there are more entries for this call
        nextEntry = *fni\NextEntryOffset
        ; If so, get the info
        While nextEntry > 0
          If previousEntry = nextEntry
            Break
          EndIf
          GetRDCWInfo(nextEntry)
          ; See if there are more entries for this call
          nextEntry = PeekL(*fni + nextEntry)
          previousEntry = nextEntry
        Wend
        ; No more entries, go to next ReadDirectoryChangesW call after flushing buffers again
        CallFunctionFast(*flushFB,volume)
      Wend
    EndProcedure
    ;>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>
    ; Main window and gadgets
    ;>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>
    If OpenWindow(0, 0, 0, 545, 350, "ReadDirectoryChangesW", #PB_Window_SystemMenu | #PB_Window_ScreenCentered )
      ButtonGadget(#Button_Dir, 5, 5, 150, 20, "Select Directory to watch")
      TextGadget(#Text_Dir, 5, 5, 535, 20, "")
      DisableGadget(#Text_Dir, 1)
      ListIconGadget(#ListIcon_RDCW, 5, 40, 535, 300, "File", 200, #PB_ListIcon_FullRowSelect | #PB_ListIcon_AlwaysShowSelection | #PB_ListIcon_GridLines)
      AddGadgetColumn(#ListIcon_RDCW, 1, "Action", 200)
      AddGadgetColumn(#ListIcon_RDCW, 2, "Time", 130)
      quit = #False
    ;>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>
    ; Main event loop
    ;>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>
      Repeat
        event = WaitWindowEvent()
        Select event
          Case #PB_Event_Gadget
            Select EventGadget()
              Case (#Button_Dir)
                ; Button pressed for getting directory to watch
                myPath$ = PathRequester("Select a Folder to watch", "c:\")
                ; If there is a valid path, start our ReadDirectoryChangesW thread
                If myPath$
                  ;get drive letter
                  HDVolume=Left(myPath$,FindString(myPath$,":"))
                  If HDVolume=""
                    CloseLibrary(0)
                    MessageRequester("Error", "Only monitor harddrives whith letters no UNC for now.")
                   End
                  EndIf
                  DisableGadget(#Button_Dir, 1)
                  ; Get a handle to our watched directory
                  hDir = CreateFile_(myPath$, #FILE_LIST_DIRECTORY|#GENERIC_WRITE, #FILE_SHARE_WRITE | #FILE_SHARE_READ | #FILE_SHARE_DELETE, #Null, #OPEN_EXISTING, #FILE_FLAG_BACKUP_SEMANTICS, #Null)
                  ; --> Our thread for change notifications with the directory handle
                  myThread = CreateThread(@RDCW(), hDir)
                  DisableGadget(#Text_Dir, 0)
                  SetGadgetText(#Text_Dir, myPath$ + " is being watched.")
                EndIf
            EndSelect
          Case #PB_Event_CloseWindow
            FreeMemory(*fni)
            ; Kill our ReadDirectoryChangesW thread
            KillThread(myThread)
            ; Close handle to our watched directory
            CloseHandle_(hDir)
            ; If KERNEL32.DLL is open, close it
            If IsLibrary(0)
              CloseLibrary(0)
            EndIf
            quit = #True
        EndSelect
      Until quit
    EndIf
    End
    ;>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>
    ; ExecutableFormat=Windows
    ; Requires=NT4/2000/XP/Server2003
    ; EOF
    ;>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>
User avatar
Thunder93
Addict
Addict
Posts: 1788
Joined: Tue Mar 21, 2006 12:31 am
Location: Canada

Re: Monitoring a directory for new files being added

Post by Thunder93 »

Hi normeus. I haven't had chance to test it out. However it seems you've covered all bases on this. Keep up the good work. :D
ʽʽSuccess is almost totally dependent upon drive and persistence. The extra energy required to make another effort or try another approach is the secret of winning.ʾʾ --Dennis Waitley
flux
New User
New User
Posts: 6
Joined: Mon May 09, 2016 11:24 am

Re: Monitoring a directory for new files being added

Post by flux »

I know this one is pretty old but the functionality is still cool :D
The code runs under Win7SP1 (german) but somehow the filename translation broken.
I can see that the procedure translates from Unicode to Ansi but in my case it only returns Chinese looking characters.
Can someone give me a hint what actually is the problem during the conversion in that case?
Thanks!
F
User avatar
JHPJHP
Addict
Addict
Posts: 2250
Joined: Sat Oct 09, 2010 3:47 am

Re: Monitoring a directory for new files being added

Post by JHPJHP »

Hi flux,

Replace the Procedure Uni2Ansi with the following:

Code: Select all

Procedure.s Uni2Ansi(uni.l, uLen)
  *ansi = AllocateMemory(uLen)
  WideCharToMultiByte_(#CP_UTF8, 0, uni, -1, *ansi, uLen, #Null, #Null)
  ansi$ = PeekS(*ansi, uLen, #PB_Ascii)
  FreeMemory(*ansi)
  ProcedureReturn ansi$
EndProcedure

If you're not investing in yourself, you're falling behind.

My PureBasic StuffFREE STUFF, Scripts & Programs.
My PureBasic Forum ➤ Questions, Requests & Comments.
flux
New User
New User
Posts: 6
Joined: Mon May 09, 2016 11:24 am

Re: Monitoring a directory for new files being added

Post by flux »

That works. Thx JHPJHP
flux
New User
New User
Posts: 6
Joined: Mon May 09, 2016 11:24 am

Re: Monitoring a directory for new files being added

Post by flux »

OK, another question regarding this one:
My plan is to run this in the background and to write the names of new files into a log file. The only problem I see is the "Main Loop Event" Block which relies on the window events. I don't see a way to rewrite the main loop to my needs (background operation without gui window). Any suggestions?
Best regards!
F
User avatar
JHPJHP
Addict
Addict
Posts: 2250
Joined: Sat Oct 09, 2010 3:47 am

Re: Monitoring a directory for new files being added

Post by JHPJHP »

Hi flux,

You don't have to worry about the Window Event Loop, the main processing loop is located in the Procedure RDCW(), and accessed from a separate thread. Threads aren't needed without the Window and can be removed, but be sure to include the CreateFile Function from the button event, needed for obtaining the folder handle.

Take a look at my Services, Stuff, and Shellhook post if you're thinking about using a service.

NB*: You may want to use threads if monitoring multiple directories.

If you're not investing in yourself, you're falling behind.

My PureBasic StuffFREE STUFF, Scripts & Programs.
My PureBasic Forum ➤ Questions, Requests & Comments.
flux
New User
New User
Posts: 6
Joined: Mon May 09, 2016 11:24 am

Re: Monitoring a directory for new files being added

Post by flux »

JHPJHP wrote:Threads aren't needed without the Window and can be removed
Ah, the thread stuff was a little confusing. It works now.

What exactly would be the advantage of using a "real" service instead of starting the scanner and let it run in the background?
The plan was to use it in a bigger context and to start the scanning routine as result of some other checks so its actually not a true windows service.

Best regards!
F
Post Reply