How do I make my program watch for file modification in C++?
Asked Answered
A

6

87

There are a lot of programs, Visual Studio for instance, that can detect when an outside program modifies a file and then reload the file if the user wants chooses. Is there a relatively easy way to do this sort of thing in C++ (doesn't necessarily have to be platform independent)?

Anorexia answered 31/5, 2009 at 2:10 Comment(0)
E
133

There are several ways to do this depending on the platform. I would choose from the following choices:

Cross Platform

Trolltech's Qt has an object called QFileSystemWatcher which allows you to monitor files and directories. I'm sure there are other cross platform frameworks that give you this sort of capability too, but this one works fairly well in my experience.

Windows (Win32)

There is a Win32 api called FindFirstChangeNotification which does the job. There is a nice article which a small wrapper class for the api called How to get a notification if change occurs in a specified directory which will get you started.

Windows (.NET Framework)

If you are ok using C++/CLI with the .NET Framework then System.IO.FileSystemWatcher is your class of choice. Microsoft has a nice article on how to monitor file system changes using this class.

OS X

The FSEvents API is new for OS X 10.5 and very full-featured.

Linux

Use inotify as Alex mentioned in his answer.

Elevated answered 31/5, 2009 at 3:10 Comment(10)
Note: inotify is Linux specific, if you want some UNIX portable features you are pobably looking for something like libfamParnassus
I think you're confusing C++ with C++/CLI. Similar name, different language. Other than that, this is a thorough and useful answer.Dalessio
FileSystemWatcher has issues (that wont be fixed) in Windows 8 connect.microsoft.com/VisualStudio/feedback/details/772182/…Enemy
QFileSystemWatcher has limitations to the amount of files it can watch.doc.qt.io/qt-4.8/qfilesystemwatcher.htmlFood
The FSEvents docs say "The file system events API is also not designed for finding out when a particular file changes. For such purposes, the kqueues mechanism is more appropriate."Dacoit
The FSEvents link for macOS is dead.Appraise
Inotify does not give PID of whatever modified or did stuff to the file/dir in question. This is a major disadvantage if being used for a monitor for security purposes.Rameau
Another cross-platform solution: efswOsber
Cloud drives provides such feature in the WebDAV protocol.Sandon
Cross platform github.com/SpartanJ/efswGillard
V
22

If you don't need to be platform-independent, an approach on Linux that may be less of a machine load than "polling" (checking periodically) is inotify, see http://en.wikipedia.org/wiki/Inotify and the many links from it for example. For Windows, see http://msdn.microsoft.com/en-us/library/aa365261(VS.85).aspx .

Villanelle answered 31/5, 2009 at 2:16 Comment(1)
Good answer! This really is an OS-level task that would be difficult to make cross-platform.Bramble
M
13

SimpleFileWatcher might be what you are looking for. But of course it is an external dependency - maybe that is no option for you.

Mixed answered 7/4, 2015 at 11:5 Comment(5)
Super easy and lightweight solution. Thank you.Impedance
@MartinGerhardy Github link is brokenMejia
Great library! If you include files directly into your projet, it is no longer a dependency, but just helper files... This is what I have done and it rocks !Eats
Note: this lib has a bug. If you delete a subfolder which has a file, when you delete the folder, the file deleted evetn won't be fired (in Windows). Maybe adding a listener to every subfolders (you can detect a new folder from file added event) can solve it.Carlock
I recently switch my io handling to libuv - it also has file/dir watcher support implemented and is cross platform.Mixed
C
6

Sure, just like VC++ does. You get the last modified time when you open the file, and you periodically check it while you have the file open. If last_mod_time > saved_mod_time, it happened.

Carmellacarmelle answered 31/5, 2009 at 2:13 Comment(10)
Polling is a very inefficient way to do this. As Alex noted, Windows does have notifications available (though of course I don't know if VS uses them).Dalessio
@Matthew "Polling is a very inefficient way to do this." Nonsense. One stat(2) call every 5 minutes has epsilon impact. When you use the word "inefficient", quantify the time or cost it is, and compare that with the time you spend looking for "efficient" solutions. If, as in this case, the difference is on the order of 1e6, you're probably making a perverse optimization.Carmellacarmelle
for checking a single file (as the original question mentions), polling is quick enough. if you want to act on any change on an unbounded-depth directory, it can quickly get out of hand.Laic
One stat check per /file/. What if you want to monitor hundreds of files (not at all unreasonable for a dev working on a complex project) in a directory tree? As for time looking for the solution, it took me about 10 seconds to find the "Directory Change Notifications"/FindFirstChangeNotification API. So I don't think this is premature or perverse at all. I also don't think I need to give an exact cost when stating the obvious.Dalessio
One variation on this that is possible is to only poll when the application gains focus. This works fine in the case that the file is only modified by the user. I'm not sure how much it costs to have a lot of simultaneous change registrations...and profiling it is not really possible, since such costs are continuous. I doubt it costs much, though. Even so, polling is not totally awful.Whitley
Gee, @Matthew, I wonder if anyone ever had to solve that problem before? But, real quick now, if a stat call takes 10 msec/file, amd you do it periodically, how many open files do you need to have before it consumes significant time. (Recall that a closed file never matters.) I get ROM 1000 files to consume 3 pct of the time. So, as to your other point, you're now concerned about optimizing a few tens of milliseconds for a wild edge case — even as wild an EMACS used as I am, I rarely have 50 files open, much less 1000. So yeah, I think a moment's estimation would have served you well.Carmellacarmelle
In this particular case I'm doing this for a game (if you update the file that defines a stage for example, I want the stage to reload). Since even a frame of lag can be annoying I think I'll try to avoid polling (though I haven't had a chance to test either method yet, I'm going to default to the one that is likely faster). Thank you for the responses though.Anorexia
@CharlieMartin: I am inclined to agree with you. While your answer may or may not be good, if anyone says anything about its efficiency (whether for or against) he/she must always provide statistics to back his/her statement. Only then can an understanding be achieved... Otw it's just talk.Kamseen
@Anorexia But how long will the stage reload actually take? If you poll much more quickly than that the user won't care, also no point polling for levels other than the current level, and perhaps connected levels if you want them updated before they move to them. Of course, a level may be built up from many asset files, and scanning them all might be an issue. If you are also writing the level builder, could you have some IPC that the game app can be notified the builder has saved? Or a key log file that is updated when any of the assets are saved, so you only need to check that one file?.Periosteum
I need to check if a file changed once every 6 hours. Polling is the best solution for this.Perish
S
6

A working exemple for WinCE

void FileInfoHelper::WatchFileChanges( TCHAR *ptcFileBaseDir, TCHAR *ptcFileName ){
static int iCount = 0;
DWORD dwWaitStatus; 
HANDLE dwChangeHandles; 

if( ! ptcFileBaseDir || ! ptcFileName ) return;

wstring wszFileNameToWatch = ptcFileName;

dwChangeHandles = FindFirstChangeNotification(
    ptcFileBaseDir,
    FALSE,
    FILE_NOTIFY_CHANGE_FILE_NAME |
    FILE_NOTIFY_CHANGE_DIR_NAME |
    FILE_NOTIFY_CHANGE_ATTRIBUTES |
    FILE_NOTIFY_CHANGE_SIZE |
    FILE_NOTIFY_CHANGE_LAST_WRITE |
    FILE_NOTIFY_CHANGE_LAST_ACCESS |
    FILE_NOTIFY_CHANGE_CREATION |
    FILE_NOTIFY_CHANGE_SECURITY |
    FILE_NOTIFY_CHANGE_CEGETINFO
    );

if (dwChangeHandles == INVALID_HANDLE_VALUE) 
{
    printf("\n ERROR: FindFirstChangeNotification function failed [%d].\n", GetLastError());
    return;
}

while (TRUE) 
{ 
    // Wait for notification.
    printf("\n\n[%d] Waiting for notification...\n", iCount);
    iCount++;

    dwWaitStatus = WaitForSingleObject(dwChangeHandles, INFINITE); 
    switch (dwWaitStatus) 
    { 
        case WAIT_OBJECT_0: 

            printf( "Change detected\n" );

            DWORD iBytesReturned, iBytesAvaible;
            if( CeGetFileNotificationInfo( dwChangeHandles, 0, NULL, 0, &iBytesReturned, &iBytesAvaible) != 0 ) 
            {
                std::vector< BYTE > vecBuffer( iBytesAvaible );

                if( CeGetFileNotificationInfo( dwChangeHandles, 0, &vecBuffer.front(), vecBuffer.size(), &iBytesReturned, &iBytesAvaible) != 0 ) {
                    BYTE* p_bCurrent = &vecBuffer.front();
                    PFILE_NOTIFY_INFORMATION info = NULL;

                    do {
                        info = reinterpret_cast<PFILE_NOTIFY_INFORMATION>( p_bCurrent );
                        p_bCurrent += info->NextEntryOffset;

                        if( wszFileNameToWatch.compare( info->FileName ) == 0 )
                        {
                            wcout << "\n\t[" << info->FileName << "]: 0x" << ::hex << info->Action;

                            switch(info->Action) {
                                case FILE_ACTION_ADDED:
                                    break;
                                case FILE_ACTION_MODIFIED:
                                    break;
                                case FILE_ACTION_REMOVED:
                                    break;
                                case FILE_ACTION_RENAMED_NEW_NAME:
                                    break;
                                case FILE_ACTION_RENAMED_OLD_NAME:
                                    break;
                            }
                        }
                    }while (info->NextEntryOffset != 0);
                }
            }

            if ( FindNextChangeNotification( dwChangeHandles ) == FALSE )
            {
                printf("\n ERROR: FindNextChangeNotification function failed [%d].\n", GetLastError());
                return;
            }

            break; 

        case WAIT_TIMEOUT:
            printf("\nNo changes in the timeout period.\n");
            break;

        default: 
            printf("\n ERROR: Unhandled dwWaitStatus [%d].\n", GetLastError());
            return;
            break;
    }
}

FindCloseChangeNotification( dwChangeHandles );
}
Seth answered 4/3, 2015 at 10:15 Comment(0)
W
1

Add an answer for libuv (though it's written in C), it has support for both Windows and Linux with system-specific APIs:

inotify on Linux, FSEvents on Darwin, kqueue on BSDs, ReadDirectoryChangesW on Windows, event ports on Solaris, unsupported on Cygwin

You may check the document here, beware that the document says that the notification related APIs are not very consistent.

Wanigan answered 11/9, 2019 at 9:41 Comment(2)
Can libuv watch for file move withing the same filesystem?Demott
It seems that the file move is not a normal file system event, the document doesn't show anything about the move event.Wanigan

© 2022 - 2024 — McMap. All rights reserved.