How to know when a file is edited? - c++

Is there a way ( or an API ) to know when a text file is edited ( by a program or by a person ) and do a specific action ?
For example: I want to show a MessageBox when the file c:\Users\john\free.txt is edited.

Depends on when you exactly want to know it.
is your application running continuously and do you want to see any change as soon as possible?
is your application a simple command-line application that needs to check for changes once?
In the second case, you could check the modification dates of the file (as suggested by PoweRoy and Michal) or use a hash (as suggested by PoweRoy).
If your application is running continuously, you should use the FindFirstChangeNotification and ReadDirectoryChanges functions. You can read more about it on the following pages:
http://msdn.microsoft.com/en-us/library/windows/desktop/aa364417(v=vs.85).aspx
http://msdn.microsoft.com/en-us/library/windows/desktop/aa365465(v=vs.85).aspx.

Simplest: compare modification dates. But this can be manipulated.
Or make a hash of the original file and compare it with the current file.

GetFileTime should help you.
http://msdn.microsoft.com/en-us/library/ms724320%28v=vs.85%29.aspx
and there is GetFileAttributesEx as well.

check the file's last modify datetime.
This method retrieves status information related to a given CFile object instance or a given file path.
BOOL GetStatus(
CFileStatus& rStatus
) const;
static BOOL PASCAL GetStatus(
LPCTSTR lpszFileName,
CFileStatus& rStatus
);
Parameters
rStatus
A reference to a user-supplied CFileStatus structure that will receive the status information. The CFileStatus structure has the following fields:
CTime m_ctime The date and time the file was created.
CTime m_mtime The date and time the file was last modified.
CTime m_atime The date and time the file was last accessed for reading.
ULONGLONG m_size The logical size of the file in bytes, as reported by the DIR command.
BYTE m_attribute The attribute byte of the file.
char m_szFullName[_MAX_PATH] The absolute filename in the Windows character set.
lpszFileName
A string in the Windows character set that is the path to the desired file. The path can be relative or absolute, or it can contain a network path name.
Return Value
TRUE if the status information for the specified file is successfully obtained; otherwise, FALSE.
PS:information from MSDN

Related

Overcome MAX_PATH filename length

I have read a lot of documentation on this subject, but I can't seem to figure it out.
The cause is that I have to process file paths which may be longer than the MAX_PATH parameter, causing a lot of issues
I have already replaced all my ANSI-functions like GetFileAttributesA with the UNICODE equivalent (GetFileAttributesW) in order to support the extended file path length with the prefix: \\?\.
However, I also need to check whether the file path for instance is a symbolic link and I need to know the filesize, last modified date, etc.
In order to do so, I use the stat function, as shown below:
if (fstat(LongFilePath, &file_info) == 0) //THIS FAILS WITH THE ENAMETOOLONG FOR LONG FILEPATHS
So, here again, the problem is in the ENAMETOOLONG error, due to a too long filename (exceeding MAX_PATH).
So, I found out I could use fstat to access the file by its descriptor. However, to obtain the descriptor, I need to use fopen, which also has the ENAMETOOLONG limitation.
So, my question is. How can I get the file information I need (symlink, filesize, last modified, .... as the stat function offers) for file paths exceeding MAX_PATH

I can't get SHGetFileInfo to return an icon location

I'm on Windows 10 Pro and Visual Studio 2013, and I'm using SHGetFileInfoW to get an icon location (path + index) for a file type:
std::wstring wFile { L"a.bas" };
SHFILEINFOW fi {};
DWORD success = ::SHGetFileInfoW(wFile.c_str(),
FILE_ATTRIBUTE_NORMAL,
&fi,
sizeof(fi),
SHGFI_USEFILEATTRIBUTES | SHGFI_ICONLOCATION);
No matter whether wFile refers to an existing file or is just any filename, the call returns 1 indicating success. The iIcon member of fi is set to a number, but szDisplayString is empty. Not just the drive letter is overwritten with \0 (as seemed to happen here) but it is completely filled with \0.
Microsoft recommends using IExtractIcon::GetIconLocation as an alternative, but I need to get the icon for files which are not on a local filesystem, so I can't get an IShellInfo object which would get me this interface pointer.
Getting an icon handle works, on the other hand. Is this function just buggy or am I doing something wrong? Is there a workaround?
Icons can be dynamically generated and might not expose the path to its images. Icon handlers communicate this to the shell by setting the GIL_NOTFILENAME flag in their IExtractIcon::GetIconLocation implementation. If GIL_SIMULATEDOC is set the shell must also typically generate a icon on the fly.
If you call SHGetFileInfo with the SHGFI_SELECTED flag set then then function probably has to generate a new icon no matter which file type you are asking for.
If you are displaying a file list in a ListView/TreeView then you typically use SHGFI_SYSICONINDEX|SHGFI_SHELLICONSIZE|SHGFI_SMALLICON and use the system image list.
Use SHGFI_ICON if you need a HICON.
If SHGFI_ICONLOCATION is specified then SHGetFileInfo uses IExtractIcon:
Retrieve the name of the file that contains the icon representing the file specified by pszPath, as returned by the IExtractIcon::GetIconLocation method of the file's icon handler.

Why can't I change the 'last write time' of my newly created files?

First off, I'm using Visual Studio 2015's implementation of the Filesystem library from the upcoming C++17 standard, which is based on Boost::Filesystem.
Basically, what I'm trying to do is save a file's timestamp (it's "last write time"), copy that file's contents into an archive along with said timestamp, then extract that file back out and use the saved timestamp to restore the correct "last write time".
// Get the file's 'last write time' and convert it into a usable integer.
__int64 timestamp = chrono::time_point_cast<chrono::seconds>(fs::last_write_time(src)).time_since_epoch().count();
// ... (do a bunch of stuff in here)
// Save the file
ofstream destfile(dest, ios::binary | ios::trunc);
destfile.write(ptr, size);
// Correct the file's 'last write time'
fs::last_write_time(dest, chrono::time_point<chrono::system_clock>(chrono::seconds(timestamp)));
The problem is that the new file will always end up with a timestamp equaling the time it was created (right now), as it I never called last_write_time() at all.
When I try copying the timestamp from one existing file to another, it works fine. When I copy the timestamp from a file, then use fs::copy to create a new copy of that file, then immediately change the copy's timestamp, it also works fine. The following code works correctly:
// Get the file's 'last write time' and convert it into a usable integer.
__int64 timestamp = chrono::time_point_cast<chrono::seconds>(fs::last_write_time("test.txt")).time_since_epoch().count();
fs::copy("test.txt", "new.txt");
// Correct the file's 'last write time'
fs::last_write_time("new.txt", chrono::time_point<chrono::system_clock>(chrono::seconds(timestamp)));
I have no reason to suspect that storing the timestamp could be incorrect, but I have no other ideas. What might be causing this?
This happens because you wrote to the stream but didn't close the file before actually updating the time. The time will be updated again on close.
The solution is to close the stream and then update the file time.

CFileDialog::GetNextPathName return a truncated path if it is too long [duplicate]

Using CFileDialog class, I select multiple files placed in a directory with a long path. It's OK when I select only one or two files; but when I select three files at the same time it returns only a part of the third file path. (Looks like it's limited to 512 characters possibly) How can I resolve this?
MFC uses a default buffer of size _MAX_PATH and that's why you are seeing that behavior. Look at dlgfile.cpp for the implementation of CFileDialog::CFileDialog and you will see m_ofn.lpstrFile and m_ofn.nMaxFile being set.
You can specify a larger buffer if you want to. Before calling DoModal you can either access the CFileDialog::m_pOFN member to get a pointer to the OPENFILENAME that the CFileDialog will use and update it directly or call CFileDialog::GetOFN to get a reference to the structure and update that.
Either way you will find this helpful: http://msdn.microsoft.com/en-US/library/ms646839(v=vs.80).aspx
Assuming that your code looks something like this:
CFileDialog dialog(...);
dialog.DoModal();
Determine the maximum number of files that you wish to support, for example:
#define MAX_FILE_NAMES 256
Add this before calling DoModal:
CString data;
dialog.m_pOFN->nMaxFile = (MAX_FILE_NAMES*(MAX_PATH+1))+1;
dialog.m_pOFN->lpstrFile = data.GetBuffer((MAX_FILE_NAMES*(MAX_PATH+1))+1);
Add this after calling DoModal:
data.ReleaseBuffer();

Efficiently List All Sub-Directories in a Directory

Please see edit with advice taken so far...
I am attempting to list all the directories(folders) in a given directory using WinAPI & C++.
Right now my algorithm is slow & inefficient:
- Use FindFirstFileEx() to open the folder I am searching
- I then look at every file in the directory(using FindNextFile()); if its a directory file then I store its absolute path in a vector, if its just a file I do nothing.
This seems extremely inefficient because I am looking at every file in the directory.
Is there a WinAPI function that I can use that will tell me all the sub-directories in a given directory?
Do you know of an algorithm I could use to efficiently locate & identify folders in a directory(folder)?
EDIT:
So after taking the advice I have searched using FindExSearchLimitToDirectories but for me it still prints out all the files(.txt, etc.) & not just folders. Am I doing something wrong?
WIN32_FIND_DATA dirData;
HANDLE dir = FindFirstFileEx( "c:/users/soribo/desktop\\*", FindExInfoStandard, &dirData,
FindExSearchLimitToDirectories, NULL, 0 );
while ( FindNextFile( dir, &dirData ) != 0 )
{
printf( "FileName: %s\n", dirData.cFileName );
}
In order to see a performance boost there must be support at the file system level. If this does not exist then the system must enumerate every single object in the directory.
In principle, you can use FindFirstFileEx specifying the FindExSearchLimitToDirectories flag. However, the documentation states (emphasis mine):
This is an advisory flag. If the file system supports directory filtering, the function searches for a file that matches the specified name and is also a directory. If the file system does not support directory filtering, this flag is silently ignored.
If directory filtering is desired, this flag can be used on all file systems, but because it is an advisory flag and only affects file systems that support it, the application must examine the file attribute data stored in the lpFindFileData parameter of the FindFirstFileEx function to determine whether the function has returned a handle to a directory.
However, from what I can tell, and information is sparse, FindExSearchLimitToDirectories flag is not widely supported on desktop file systems.
Your best bet is to use FindFirstFileEx with FindExSearchLimitToDirectories. You must still perform your own filtering in case you meet a file system that doesn't support directory filtering at file system level. If you get lucky and hit upon a file system that does support it then you will get the performance benefit.
If you're using FindFirstFileEx, then you should be able to specify the _FINDEX_SEARCH_OPS::FindExSearchLimitToDirectories option (to be used as the fSearchOp param in FindFirstFileEx) to limit the first search (and any subsequent FindNextFile()) calls to directories.