This code works on Windows XP at home but fails at work on 64bit Windows 7. The loop isn't entered even once although there are more than 50 files in the supplied folder. Not only it doesn't enter, it also returns ERROR_NO_MORE_FILES for GetLastError. Why?
string dir = "d:\\validfolder";
WIN32_FIND_DATA ffd;
HANDLE h = FindFirstFile(dir.c_str(), &ffd);
while(FindNextFile(h, &ffd))
{
// some operation
}
DWORD dw = GetLastError();// returns ERROR_NO_MORE_FILES
I tried Wow64DisableWow64FsRedirection but that has no effect.
You need to add a file wildcard to your dir:
string dir = "d:\\validfolder\\*";
For it to list the files in a directory. Otherwise you are only asking for information about the directory itself.
At least that's how I read the documentation for FindFirstFile
To examine a directory that is not a root directory, use the path to
that directory, without a trailing backslash. For example, an argument
of "C:\Windows" returns information about the directory "C:\Windows",
not about a directory or file in "C:\Windows". To examine the files
and directories in "C:\Windows", use an lpFileName of "C:\Windows*".
I don't know why it's working for you on XP
This code is incorrect in a number of ways.
You must check the return value of FindFirstFile. If the call to FindFirstFile succeeds then you already have the first file in ffd. As your code stands, you throw away the first file. So you need to re-jig your loop logic to account for that. Naturally, if GetLastError returns ERROR_NO_MORE_FILES then that means the search has exhausted all files.
So, what is probably happening is you ask for the first file matching the search string "d:\\validfolder". This is returned in ffd after the call to FindFirstFile. You then ignore that information and ask for the next match. But there is no subsequent match since there is only one object matching "d:\\validfolder" since you included no wildcards in your search pattern.
This code will behave exactly the same on XP as it does on Windows 7 and I suspect that you are not running the same code on both systems.
If you want to enumerate the contents of the folder then you need to search for "d:\\validfolder\\*". Something like this:
string dir = "d:\\validfolder\\*";
WIN32_FIND_DATA ffd;
HANDLE h = FindFirstFile(dir.c_str(), &ffd);
BOOL success = h<>INVALID_HANDLE_VALUE;
while(success)
{
// do something with ffd
success = FindNextFile(h, &ffd));
}
Related
I am writing a Lua function to delete a folder using Luacom in Windows (version 7 onwards and I can't dictate the version). The folder path is specified in UTF-8 and will contain non-ASCII characters, so os.remove, io.whatever, Penlight and lfs will not work. So far I have (using Luacom to access the Windows com model):
function delFolder(sPath, bForce)
--sPath is a fully specified folder path
--bForce is a Boolean indicating whether the folder should be
--deleted even if it contains read-only files
require('luacom')
fso = luacom.CreateObject("Scripting.FileSystemObject")
--code in here to test that the folder exists,
--and return an error if it does not
fso:DeleteFolder(sPath, bForce)
end
My problem is that, if bForce = false, and the folder is effectively read-only, the operation errors out. I need to be able to test for this situation and return an error instead of attempting the operation.
One possibility is to manipulate the Luacom error handling not to abort on error, and test the last error after the operation:
luacom.config.abort_on_error = false
luacom.config.last_error = nil
fso:DeleteFolder(sPath, bForce)
if luacom.config.last_error then
--return error indicating that the folder cannot be deleted
end
but is there a simpler way, using the com model or some other alternative available in Lua?
Reference for FileSystemObject
I am using vs 2010 in windows.
In my program, I want to create a new directory under the current directory.
I use
TCHAR szPath[MAX_PATH];
GetModuleFileName( NULL, szPath, MAX_PATH );
And the string of szPath is "E:\A20J\Bin\***.exe".
But when I use:
BOOL bol = CreateDirectory("Path", NULL);
bol becomes 1, which means successful.
But under the "E:\A20J\Bin\", I found no such directory as "Path" even after refreshing, why?
The path of the executable is not indicative of the directory in which the current process is executing. Use GetCurrentDirectory() to detemine the directory in which the process is executing, and the directory "Path" will have been created there. If you wish to create the directory in the same directory as the binary then some string manipulation is required to construct the path.
Note that CreateDirectory() returns non-zero only if it creates the directory. The directory may already exist which you may wish to not treat as a failure (see this old answer of mine Create a directory if it doesn't exist).
I am trying get the parent folder of a Windows user's profile path. But I couldn't find any "parameter" to get this using SHGetSpecialFolderPath, so far I am using CSIDL_PROFILE.
Expected Path:
Win7 - "C:\Users"
Windows XP - "C:\Documents and Settings"
For most purposes other than displaying the path to a user, it should work to append "\\.." (or "..\\" if it ends with a backslash) to the path in question.
With the shell libary version 6.0 you have the CSIDL_PROFILES (not to be confused with CSIDL_PROFILE) which gives you what you want. This value was removed (see here), you have to use your own workaround.
On any prior version you'll have to implement your own workaround, such as looking for the possible path separator(s), i.e. \ and / on Windows, and terminate the string at the last one. A simple version of this could use strrchr (or wcsrchr) to locate the backslash and then, assuming the string is writable, terminate the string at that location.
Example:
char* path;
// Retrieve the path at this point, e.g. "C:\\Users\\username"
char* lastSlash = strrchr(path, '\\');
if(!lastSlash)
lastSlash = strrchr(path, '/');
if(lastSlash)
*lastSlash = 0;
Or of course GetProfilesDirectory (that eluded me) which you pointed out in a comment to this answer.
I'm trying to crawl FTP and pull down all the files recursively.
Up until now I was trying to pull down a directory with
ftp.list.each do |entry|
if entry.split(/\s+/)[0][0, 1] == "d"
out[:dirs] << entry.split.last unless black_dirs.include? entry.split.last
else
out[:files] << entry.split.last unless black_files.include? entry.split.last
end
But turns out, if you split the list up until last space, filenames and directories with spaces are fetched wrong.
Need a little help on the logic here.
You can avoid recursion if you list all files at once
files = ftp.nlst('**/*.*')
Directories are not included in the list but the full ftp path is still available in the name.
EDIT
I'm assuming that each file name contains a dot and directory names don't. Thanks for mentioning #Niklas B.
There are a huge variety of FTP servers around.
We have clients who use some obscure proprietary, Windows-based servers and the file listing returned by them look completely different from Linux versions.
So what I ended up doing is for each file/directory entry I try changing directory into it and if this doesn't work - consider it a file :)
The following method is "bullet proof":
# Checks if the give file_name is actually a file.
def is_ftp_file?(ftp, file_name)
ftp.chdir(file_name)
ftp.chdir('..')
false
rescue
true
end
file_names = ftp.nlst.select {|fname| is_ftp_file?(ftp, fname)}
Works like a charm, but please note: if the FTP directory has tons of files in it - this method takes a while to traverse all of them.
You can also use a regular expression. I put one together. Please verify if it works for you as well as I don't know it your dir listing look different. You have to use Ruby 1.9 btw.
reg = /^(?<type>.{1})(?<mode>\S+)\s+(?<number>\d+)\s+(?<owner>\S+)\s+(?<group>\S+)\s+(?<size>\d+)\s+(?<mod_time>.{12})\s+(?<path>.+)$/
match = entry.match(reg)
You are able to access the elements by name then
match[:type] contains a 'd' if it's a directory, a space if it's a file.
All the other elements are there as well. Most importantly match[:path].
Assuming that the FTP server returns Unix-like file listings, the following code works. At least for me.
regex = /^d[r|w|x|-]+\s+[0-9]\s+\S+\s+\S+\s+\d+\s+\w+\s+\d+\s+[\d|:]+\s(.+)/
ftp.ls.each do |line|
if dir = line.match(regex)
puts dir[1]
end
end
dir[1] contains the name of the directory (given that the inspected line actually represents a directory).
As #Alex pointed out, using patterns in filenames for this is hardly reliable. Directories CAN have dots in their names (.ssh for example), and listings can be very different on different servers.
His method works, but as he himself points out, takes too long.
I prefer using the .size method from Net::FTP.
It returns the size of a file, or throws an error if the file is a directory.
def item_is_file? (item)
ftp = Net::FTP.new(host, username, password)
begin
if ftp.size(item).is_a? Numeric
true
end
rescue Net::FTPPermError
return false
end
end
I'll add my solution to the mix...
Using ftp.nlst('**/*.*') did not work for me... server doesn't seem to support that ** syntax.
The chdir trick with a rescue seems expensive and hackish.
Assuming that all files have at least one char, a single period, and then an extension, I did a simple recursion.
def list_all_files(ftp, folder)
entries = ftp.nlst(folder)
file_regex = /.+\.{1}.*/
files = entries.select{|e| e.match(file_regex)}
subfolders = entries.reject{|e| e.match(file_regex)}
subfolders.each do |subfolder|
files += list_all_files(ftp, subfolder)
end
files
end
nlst seems to return the full path to whatever it finds non-recursively... so each time you get a listing, separate the files from the folders, and then process any folder you find recrsively. Collect all the file results.
To call, you can pass a starting folder
files = list_all_files(ftp, "my_starting_folder/my_sub_folder")
files = list_all_files(ftp, ".")
files = list_all_files(ftp, "")
files = list_all_files(ftp, nil)
I am using CComboBox::Dir(DDL_READWRITE, path) to populate the contents of a combobox. Everything is fine, but when I reset the Archive flag and set the Index flag, the Dir() returns no files. I am using
attrib -A *.*
attrib +I *.*
in the directory I am listing. I have tried changing the first parameter to Dir() function but it does not help. I have tried FindFirstFile()/FindNextFile() and they are working fine
Any ideas to explain the reason of this behavior?
Could this a bug in the Dir() function? If yes, what other functions could it effect?
How to solve this problem?
This is quite an interesting issue. I debugged it on Win 7 64 Bit down into comctl32.dll ListBox_DirHandler assembler code and found out that Windows is doing something like this (just some simplified code):
// attr is the low word of the parameter passed to CComboBox::Dir.
attr &= FILE_ATTRIBUTE_READONLY|FILE_ATTRIBUTE_HIDDEN|FILE_ATTRIBUTE_SYSTEM|FILE_ATTRIBUTE_DIRECTORY|FILE_ATTRIBUTE_ARCHIVE|FILE_ATTRIBUTE_NORMAL;
attr |= FILE_ATTRIBUTE_ARCHIVE|FILE_ATTRIBUTE_NORMAL|FILE_ATTRIBUTE_READONLY;
WIN32_FIND_DATA finddata;
FindFirstFile(..., &finddata)
while(...) {
if(finddata.dwFileAttributes == FILE_ATTRIBUTE_COMPRESSED)
finddata.dwFileAttributes = FILE_ATTRIBUTE_NORMAL;
if(finddata.dwFileAttributes & attr) {
// some more checks and then might add the file name to the control;
}
FindNextFile(..., &finddata);
}
The problem is that your file is returned with finddata.dwFileAttributes==FILE_ATTRIBUTE_NOT_CONTENT_INDEXED. At entry of the function attr is changed so that it can never have FILE_ATTRIBUTE_NOT_CONTENT_INDEXED set, so the if inside the loop will never be true and the file name never be added to the control.
Sorry, but as far as I can see you will have to wait for an MS bugfix or do the work yourself.