I am currently working on a program that traverses through various directories to ensure that specific files are present by using File.Exists()
.
The app
You need to P/Invoke Win32 APIs to get this to work properly:
[DllImport("kernel32.dll", CharSet = CharSet.Unicode, SetLastError = true)]
static extern uint GetFileAttributes(string lpFileName);
public static bool DirectoryExists(string path)
{
uint attributes = GetFileAttributes(path.StartsWith(@"\\?\") ? path : @"\\?\" + path);
if (attributes != 0xFFFFFFFF)
{
return ((FileAttributes)attributes).HasFlag(FileAttributes.Directory);
}
else
{
return false;
}
}
public static bool FileExists(string path)
{
uint attributes = GetFileAttributes(path.StartsWith(@"\\?\") ? path : @"\\?\" + path);
if (attributes != 0xFFFFFFFF)
{
return !((FileAttributes)attributes).HasFlag(FileAttributes.Directory);
}
else
{
return false;
}
}
This is ugly and inefficient, but it DOES get around the MAX_PATH limitation:
const int MAX_PATH = 260;
private static void checkPath(string path)
{
if (path.Length >= MAX_PATH)
{
checkFile_LongPath(path);
}
else if (!File.Exists(path))
{
Console.WriteLine(" * File: " + path + " does not exist.");
}
}
And here is the checkFile_LongPath function:
private static void checkFile_LongPath(string path)
{
string[] subpaths = path.Split('\\');
StringBuilder sbNewPath = new StringBuilder(subpaths[0]);
// Build longest subpath that is less than MAX_PATH characters
for (int i = 1; i < subpaths.Length; i++)
{
if (sbNewPath.Length + subpaths[i].Length >= MAX_PATH)
{
subpaths = subpaths.Skip(i).ToArray();
break;
}
sbNewPath.Append("\\" + subpaths[i]);
}
DirectoryInfo dir = new DirectoryInfo(sbNewPath.ToString());
bool foundMatch = dir.Exists;
if (foundMatch)
{
// Make sure that all of the subdirectories in our path exist.
// Skip the last entry in subpaths, since it is our filename.
// If we try to specify the path in dir.GetDirectories(),
// We get a max path length error.
int i = 0;
while(i < subpaths.Length - 1 && foundMatch)
{
foundMatch = false;
foreach (DirectoryInfo subDir in dir.GetDirectories())
{
if (subDir.Name == subpaths[i])
{
// Move on to the next subDirectory
dir = subDir;
foundMatch = true;
break;
}
}
i++;
}
if (foundMatch)
{
foundMatch = false;
// Now that we've gone through all of the subpaths, see if our file exists.
// Once again, If we try to specify the path in dir.GetFiles(),
// we get a max path length error.
foreach (FileInfo fi in dir.GetFiles())
{
if (fi.Name == subpaths[subpaths.Length - 1])
{
foundMatch = true;
break;
}
}
}
}
// If we didn't find a match, write to the console.
if (!foundMatch)
{
Console.WriteLine(" * File: " + path + " does not exist.");
}
}
Check
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" />
<uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE" />
<provider
android:name="androidx.core.content.FileProvider"
android:authorities="your_package.fileprovider"
android:exported="false"
android:grantUriPermissions="true">
<meta-data
android:name="android.support.FILE_PROVIDER_PATHS"
android:resource="@xml/file_list" />
</provider>
<?xml version="1.0" encoding="utf-8"?>
<paths>
<external-path
name="external"
path="." />
<external-files-path
name="external_files"
path="." />
<cache-path
name="cache"
path="." />
<external-cache-path
name="external_cache"
path="." />
<files-path
name="files"
path="." />
</paths>
Never had the issue myself, someone on another SO post suggests opening a handle to the file, thus avoiding the whole 'exists' check in the first place. Not sure if this still has the 'long filename' issue:
It's the second answer here:
Check if a file/directory exists: is there a better way?
Not sure if that's useful :P
From MSDN - Naming Files, Paths, and Namespaces:
In the Windows API (with some exceptions discussed in the following paragraphs), the maximum length for a path is MAX_PATH, which is defined as 260 characters.
...
The Windows API has many functions that also have Unicode versions to permit an extended-length path for a maximum total path length of 32,767 characters. This type of path is composed of components separated by backslashes, each up to the value returned in the lpMaximumComponentLength parameter of the GetVolumeInformation function (this value is commonly 255 characters). To specify an extended-length path, use the
"\\?\"
prefix. For example,"\\?\D:\very long path"
....
Because you cannot use the
"\\?\"
prefix with a relative path, relative paths are always limited to a total of MAX_PATH characters.
(Emphasis added)
If all your paths are full paths, you could update your code to use the extended-length path specifier as follows:
const longPathSpecifier = @"\\?";
private void checkFile(string path)
{
// Add the long-path specifier if it's missing
string longPath = (path.StartsWith(longPathSpecifier) ? path : longPathSpecifier + path);
if (!File.Exists(longPath))
{
// Print the original path
Console.WriteLine(" * File: " + path + " does not exist.");
}
}
Update:
For file I/O, the "\?\" prefix to a path string tells the Windows APIs to disable all string parsing and to send the string that follows it straight to the file system. For example, if the file system supports large paths and file names, you can exceed the MAX_PATH limits that are otherwise enforced by the Windows APIs.
At least on my system (using Windows 7), long file names are not supported, so I can't verify if the above solution will work for you.
Update: I found a solution that does work, but it is fairly ugly. Here's what I did in pseudo-code:
dir.GetDirectories()
and check if the next directory is contained in the resultsdir
to that DirectoryInfo
and keep diggingdir.GetFiles()
and see if our file exists in the returned FileInfo
objects.