#COMPILE EXE
#DIM ALL

#INCLUDE ONCE "WIN32API.INC"



TYPE FILE_NOTIFY_INFORMATIONX
    NextEntryOffset AS LONG
    Action          AS LONG
    FileNameLength  AS LONG
    #IF %DEF(%WIDE_UNICODE)
        Filename        AS WSTRINGZ * 32000
    #ELSE
        Filename        AS WSTRINGZ * 1000
    #ENDIF
END TYPE


DECLARE FUNCTION zTrace LIB "zTrace.DLL" ALIAS "zTrace"(zMessage AS ASCIIZ) AS LONG
DECLARE SUB WriteProg(BYREF Buffer AS FILE_NOTIFY_INFORMATIONX)
DECLARE SUB Sub1( p1 AS DWORD)
%DEBUG                            = 1

GLOBAL   Watch                         AS WatchFile

FUNCTION PBMAIN()


    LET watch = CLASS "FWwatcher"
    watch.init( "f:\PBWin10", CODEPTR(WatchingThread()))

    watch.StartWatchThread()


    MSGBOX "File Watching End?"

    watch.SynchronousAbort
    watch = NOTHING

END FUNCTION


THREAD FUNCTION WatchingThread() AS LONG
    LOCAL    dwObj                     AS LONG

    IF ISOBJECT(watch) THEN
        DIM      hp(0 TO 1)            AS DWORD
        hp(0) = watch.IsEvtStopWatching()
        hp(1) = watch.getoverlaphevent()

        DO
            dwObj = WaitForMultipleObjects(2???, hp(0), %FALSE, %INFINITE)
            IF (dwObj = %WAIT_OBJECT_0) THEN     ' the user asked to quit the program
                EXIT DO
            END IF
            IF (dwObj <> %WAIT_OBJECT_0 + 1) THEN
                '// BUG!
                'assert(0);
                EXIT DO
            END IF
            watch.NotifyChange()
        LOOP
    END IF



END FUNCTION


CLASS FWwatcher GUID$( "{A8EEC91D-081A-43BD-BB7F-3EEF617954A8}") AS COM
    INSTANCE hWatchingThread           AS DWORD
    INSTANCE hEvtStopWatching          AS DWORD
    INSTANCE overl                     AS OVERLAPPED
    INSTANCE hDir                      AS DWORD
    INSTANCE curBuffer                 AS LONG
    INSTANCE M_buffer                  AS FILE_NOTIFY_INFORMATIONX
    INSTANCE pWatchingThread           AS DWORD
    INSTANCE pathname                  AS WSTRING
    INSTANCE BufferProg                AS DWORD
    INSTANCE WriteProgPtr              AS DWORD
    INSTANCE WatchFilter               AS DWORD

    CLASS METHOD CREATE()
        WatchFilter = %FILE_NOTIFY_CHANGE_FILE_NAME OR _
                      %FILE_NOTIFY_CHANGE_DIR_NAME OR _
                      %FILE_NOTIFY_CHANGE_ATTRIBUTES OR _
                      %FILE_NOTIFY_CHANGE_SIZE OR _
                      %FILE_NOTIFY_CHANGE_LAST_WRITE OR _
                      %FILE_NOTIFY_CHANGE_LAST_ACCESS OR _
                      %FILE_NOTIFY_CHANGE_CREATION OR _
                      %FILE_NOTIFY_CHANGE_SECURITY


    END METHOD

    INTERFACE WatchFile GUID$( "{D3420B9D-D552-4717-AF85-3E1929C18FC9}")
        INHERIT IUNKNOWN


        METHOD IsThreadRunning() AS LONG
            METHOD = hWatchingThread AND (WaitForSingleObject(hWatchingThread, 0) = %WAIT_TIMEOUT)
        END METHOD



        ' Ask for the thread to stop and waith until it ends

        METHOD SynchronousAbort()

            SetEvent(hEvtStopWatching)
            IF hWatchingThread THEN
                WaitForSingleObject(hWatchingThread, %INFINITE)
                CloseHandle(hWatchingThread)
                hWatchingThread = %NULL
            END IF

            CloseHandle(overl.hEvent)
            overl.hEvent = %NULL
            closeHandle(hEvtStopWatching)
            hEvtStopWatching = %NULL
            CloseHandle(hDir)
            hDir = %NULL
        END METHOD


        ' Start watching a file for changes
        METHOD StartWatchThread() AS LONG
            IF pWatchingThread = 0 THEN
                EXIT METHOD
            END IF

            ' if the thread already exists then stop it
            IF (me.IsThreadRunning()) THEN
                me.SynchronousAbort()
            END IF


            ' reset the hEvtStopWatching event so that it can be set if
            ' some thread requires the watching thread to stop
            ResetEvent(hEvtStopWatching)
            'local watchingthreadID as dword
            hWatchingThread = CreateThread(BYVAL %NULL, 0, BYVAL pWatchingThread, %NULL, 0, BYVAL 0)
            METHOD = hWatchingThread
        END METHOD

        METHOD ChangeFolder(BYREF folder AS WSTRING) AS LONG
            IF folder <> pathname THEN
               IF(me.Init(folder, pWatchingThread)) THEN
                    METHOD = me.StartWatchThread()
               END IF
            ELSE
               METHOD = %FALSE
            END IF
        END METHOD

        PROPERTY GET IsEvtStopWatching() AS LONG
            PROPERTY = hEvtStopWatching
        END PROPERTY

        PROPERTY GET getoverlaphevent() AS LONG
            PROPERTY = overl.hEvent
        END PROPERTY

        PROPERTY SET PtrWatchingThread(BYVAL threadPtr AS DWORD)
            pWatchingThread = threadPtr
        END PROPERTY

        PROPERTY GET Pathname() AS WSTRING
            PROPERTY = pathname
        END PROPERTY

        PROPERTY SET WriteProg(BYVAL WProgPtr AS DWORD)
            WriteProgPtr = WprogPtr
        END PROPERTY

        PROPERTY SET SetWatchFilter(BYVAL WFilter AS DWORD)
            WatchFilter = WFilter
        END PROPERTY

        METHOD Init(BYVAL fileFullPath AS WSTRING, BYVAL pWatchThread AS DWORD) AS LONG
            ' if the thread already exists then stop it


            #IF %DEF(%WIDE_UNICODE)
                pathname = CreateWideFileName(fileFullPath)
            #ELSE
                pathname = fileFullPath
            #ENDIF

            IF pWatchThread = 0 THEN
                EXIT METHOD
            ELSE
                pWatchingThread = pWatchThread
            END IF

            IF (me.IsThreadRunning()) THEN
                me.SynchronousAbort()
            END IF



            hDir = CreateFile( _
                               (pathname), _     ' pointer to the directory containing the tex files
                               %FILE_LIST_DIRECTORY, _    ' access (read-write) mode
                               %FILE_SHARE_READ OR _
                               %FILE_SHARE_DELETE OR _
                               %FILE_SHARE_WRITE, _    ' share mode
                               BYVAL %NULL, _    ' security descriptor
                               %OPEN_EXISTING, _ ' how to create
                               %FILE_FLAG_BACKUP_SEMANTICS OR _
                               %FILE_FLAG_OVERLAPPED, _    ' file attributes
                               BYVAL %NULL)      ' file with attributes to copy

            ZeroMemory(BYVAL VARPTR(overl), SIZEOF(overl))
            ZeroMemory(BYVAL VARPTR(m_buffer), SIZEOF(m_buffer))


            overl.hEvent = CreateEvent(BYVAL %NULL, %FALSE, %FALSE, BYVAL %NULL)

            hEvtStopWatching = CreateEvent(BYVAL %NULL, %TRUE, %FALSE, BYVAL %NULL)

            ' watch the directory
            ReadDirectoryChangesW( _
                                   hDir, _       ' handle to directory
                                   m_buffer, _   ' read results buffer
                                   SIZEOF(m_buffer), _    ' length of buffer
                                   %FALSE, _     ' monitoring option
                                   _             ' FILE_NOTIFY_CHANGE_CREATION
                                   WatchFilter, _' filter conditions
                                   %NULL, _      ' bytes returned
                                   overl, _      ' overlapped buffer
                                   BYVAL %NULL)  ' completion routine

            METHOD = %TRUE



        END METHOD






        ' Call ReadDirectoryChangesW to check if the file has changed since the last call.
        METHOD CheckForChanges(BYVAL waittime AS DWORD) AS LONG
            LOCAL    dwObj             AS LONG
            IF NOT (overl.hEvent) THEN
                METHOD = %FALSE
                EXIT METHOD
            END IF

            dwObj = WaitForSingleObject(overl.hEvent, waittime)
            IF (dwObj <> %WAIT_OBJECT_0) THEN
                METHOD = %FALSE
            END IF

            METHOD = Me.NotifyChange()
        END METHOD

        ' Call the ReadDirectory API and determine if the file being watched has been modified since the last call.
        ' Returns true if it is the case.
        METHOD NotifyChange() AS LONG


            ' Read the asynchronous result of the previous call to ReadDirectory
            LOCAL    dwNumberbytes     AS DWORD
            GetOverlappedResult(hDir, overl, dwNumberbytes, %FALSE)


            ' start a new asynchronous call to ReadDirectory in the alternate buffer
            ReadDirectoryChangesW( _
                                   hDir, _       ' handle to directory
                                   BYVAL VARPTR(m_buffer), _    ' read results buffer
                                   SIZEOF(m_buffer), _    ' length of buffer
                                   %FALSE, _     ' monitoring option
                                   WatchFilter, _' filter conditions
                                   dwNumberbytes, _    ' bytes returned
                                   overl, _      ' overlapped buffer
                                   BYVAL %NULL)  ' completion routine


            ' Note: the ReadDirectoryChangesW API fills the buffer with WCHAR strings.
            IF dwNumberbytes THEN
                IF WriteProgPtr THEN
                    CALL DWORD WriteProgPtr USING WriteProg(m_buffer)
                END IF

                #IF %DEF(%DEBUG)
                    LOCAL    ptrNextOffset AS DWORD
                    LOCAL    ptrNext       AS DWORD
                    LOCAL    ptrAction     AS DWORD
                    LOCAL    ptrFileLen    AS DWORD
                    LOCAL    sout          AS WSTRING


                    ptrNext = 0
                    DO

                        ptrNextOffset = PEEK(VARPTR(m_Buffer) + ptrNext)
                        ptrAction = PEEK(VARPTR(M_Buffer) + ptrNext + 4)
                        ptrFileLen = PEEK(VARPTR(M_Buffer) + ptrNext + 8)
                        SELECT CASE ptrAction
                            CASE %FILE_ACTION_ADDED
                                sOut = "File added: "
                            CASE %FILE_ACTION_REMOVED
                                sOut = "File removed: "
                            CASE %FILE_ACTION_MODIFIED
                                sOut = "File modified: "
                            CASE %FILE_ACTION_RENAMED_OLD_NAME
                                sOut = "Old filename: "
                            CASE %FILE_ACTION_RENAMED_NEW_NAME
                                sOut = "New filename: "
                            CASE ELSE
                                sOut = "Undocumented action!"
                        END SELECT
                        sOut = sOut &(PEEK$$(VARPTR(M_Buffer) + ptrNext + 12, ptrFileLen))
                        ztrace((sOut))
                        ptrNext = ptrNextOffset
                    LOOP UNTIL ptrNextOffset = 0
                #ENDIF
            END IF

        END METHOD
    END INTERFACE
END CLASS
