Tuesday, August 08, 2006

FileInfo and DirectoryInfo does not work with long file names (260 chars or something like it)

What I found:

NET Framework FileInfo and DirectoryInfo can't work with file and folder names longer then 260 chars. This is by design. For compatibility or something like this. Great. But you can stil lcreate them with calling the Unicode version of API function and prepending the name with
"file:////?\".
See for example CreateFile docs in MSDN http://msdn.microsoft.com/library/default.asp?url=/library/en-us/fileio/fs/createfile.asp.
Isn't it good to can't see the data you can create?!

So need to solve.
The only way - get back to API and write the classes from scratch.
I did not have time to duplicate them fully (if you really need this - see at Reflector tool http://www.aisto.com/roeder/dotnet/, it will help you alot to dig into Framework code).
So below are just simplest implementation, covering my needs.
Let me know if you have a better one!

Below are three separated classes.
Sorry for pure formatting, I'm new to blogging
and don't know if there are attachments or something here...


-------------------------------------------------------------------------------

using System;
using System.Collections.Generic;
using System.Text;
using System.Runtime.InteropServices;
using System.IO;
using System.ComponentModel;
using Microsoft.Win32.SafeHandles;
namespace MyCompany.MyProduct.Common.Win32
{
[Serializable]
public abstract class MyCompanyFileSystemInfo : MarshalByRefObject
{
const string UNC_PREFIX = "\\\\?\\UNC\\";
const string PREFIX = "\\\\?\\";
protected string _path;
protected WIN32_FILE_ATTRIBUTE_DATA _attributes;
protected bool _attributesNotFilled = false;
protected int _realPathStart;
int GetLastError()
{
return Marshal.GetLastWin32Error();
}
public string FullNameWithPrefix
{
get { return _path; }
}
public string FullName
{
get
{
//return _path.Substring(_realPathStart);
string t = _path;
if (t.StartsWith(UNC_PREFIX))
t = "\\\\" + t.Substring(UNC_PREFIX.Length);
else if (t.StartsWith(PREFIX))
t = t.Substring(PREFIX.Length);
return t;
}
}
public override string ToString()
{
return FullName;
}
public MyCompanyFileSystemInfo(string path)
{
path = path.TrimEnd('\\');
if (!path.StartsWith(PREFIX))
{
if (path.StartsWith("\\\\"))
{
// Unicode
path = UNC_PREFIX + path.TrimStart('\\');
_realPathStart = 8;
}
else
{
if (path[1] != ':')
{
if(path.StartsWith(".."))
{
path = Environment.CurrentDirectory.TrimEnd('\\') + "\\" + path.Substring(3);
}
else if (path.StartsWith("."))
{
path = Environment.CurrentDirectory.TrimEnd('\\') + "\\" + path.Substring(2);
}
}
// Local
path = PREFIX + path;
_realPathStart = 4;
}
}
_path = path;
FillAttributes();
}
private void FillAttributes()
{
if (!GetFileAttributesEx(_path, EFileInfoLevel.GetFileExInfoStandard, out _attributes))
_attributesNotFilled = true;
else
_attributesNotFilled = false;
}
public long Length
{
get
{
FillAttributes();
if (_attributesNotFilled)
throw new ApplicationException("File could not be opened");
long result = MakeLong(_attributes.fileSizeHigh, _attributes.fileSizeLow);
return result;
}
}
private long MakeLong(int high, int low)
{
return (((long)high) << result =" (EFileAttributes)_attributes.fileAttributes;" result =" DateTime.FromFileTime(MakeLong(" result =" DateTime.FromFileTime(MakeLong(" result =" DateTime.FromFileTime(MakeLong(" handle =" CreateFileInternal(" handle ="=" result =" false;" result =" true;" handle =" INVALID_HANDLE_VALUE;" handle =" CreateFile(" lastbslash =" _path.LastIndexOf('\\');">= _realPathStart)
{
string t = _path.Substring(0, lastBSlash).Substring(_realPathStart);
return new MyCompanyDirectoryInfo(t);
}
else
{
return null;
}
}
}
public string Name
{
get
{
string result = "";
int lastBSlash = _path.LastIndexOf('\\');
if (lastBSlash >= _realPathStart)
{
result = _path.Substring(lastBSlash+1);
}
else
{
result = _path.Substring(_realPathStart);
}
return result;
}
}
public abstract bool Delete();
#region API Wrappers
public static readonly IntPtr INVALID_HANDLE_VALUE = new IntPtr(-1);
[Flags]
public enum EFileAccess : uint
{
None = 0x0,
GenericRead = 0x80000000,
GenericWrite = 0x40000000,
GenericExecute = 0x20000000,
GenericAll = 0x10000000,
}
[Flags]
public enum EFileShare : uint
{
None = 0x00000000,
Read = 0x00000001,
Write = 0x00000002,
Delete = 0x00000004,
}
public enum ECreationDisposition : uint
{
New = 1,
CreateAlways = 2,
OpenExisting = 3,
OpenAlways = 4,
TruncateExisting = 5,
}
[Flags]
public enum EFileAttributes : uint
{
Readonly = 0x00000001,
Hidden = 0x00000002,
System = 0x00000004,
Directory = 0x00000010,
Archive = 0x00000020,
Device = 0x00000040,
Normal = 0x00000080,
Temporary = 0x00000100,
SparseFile = 0x00000200,
ReparsePoint = 0x00000400,
Compressed = 0x00000800,
Offline = 0x00001000,
NotContentIndexed = 0x00002000,
Encrypted = 0x00004000,
Write_Through = 0x80000000,
Overlapped = 0x40000000,
NoBuffering = 0x20000000,
RandomAccess = 0x10000000,
SequentialScan = 0x08000000,
DeleteOnClose = 0x04000000,
BackupSemantics = 0x02000000,
PosixSemantics = 0x01000000,
OpenReparsePoint = 0x00200000,
OpenNoRecall = 0x00100000,
FirstPipeInstance = 0x00080000
}
[DllImport("kernel32.dll", EntryPoint = "CreateFileW", SetLastError = true,
CharSet = CharSet.Unicode, ExactSpelling = true)]
public static extern IntPtr CreateFile(
string lpFileName,
EFileAccess dwDesiredAccess,
EFileShare dwShareMode,
IntPtr lpSecurityAttributes,
ECreationDisposition dwCreationDisposition,
EFileAttributes dwFlagsAndAttributes,
IntPtr hTemplateFile);
[StructLayout(LayoutKind.Sequential)]
public struct WIN32_FILE_ATTRIBUTE_DATA
{
public uint fileAttributes;
public System.Runtime.InteropServices.ComTypes.FILETIME creationTime;
public System.Runtime.InteropServices.ComTypes.FILETIME lastAccessTime;
public System.Runtime.InteropServices.ComTypes.FILETIME lastWriteTime;
public uint fileSizeHigh;
public uint fileSizeLow;
}
[DllImport("kernel32.dll", EntryPoint = "GetFileAttributesExW", SetLastError = true,
CharSet = CharSet.Unicode, ExactSpelling = true)]
public static extern bool GetFileAttributesEx
(string path, EFileInfoLevel level, out WIN32_FILE_ATTRIBUTE_DATA data);
[DllImport("kernel32.dll", EntryPoint = "CreateDirectoryExW", SetLastError = true,
CharSet = CharSet.Unicode, ExactSpelling = true)]
public static extern bool CreateDirectoryEx(string lpTemplateDirectory,
string lpNewDirectory, IntPtr lpSecurityAttributes);
public enum EFileInfoLevel : int
{
GetFileExInfoStandard = 0
}
public const int MAX_PATH = 260;
public const int MAX_ALTERNATE = 14;
[StructLayout(LayoutKind.Sequential, CharSet =
CharSet.Unicode)]
public struct WIN32_FIND_DATA
{
public EFileAttributes dwFileAttributes;
public System.Runtime.InteropServices.ComTypes.FILETIME ftCreationTime;
public System.Runtime.InteropServices.ComTypes.FILETIME ftLastAccessTime;
public System.Runtime.InteropServices.ComTypes.FILETIME ftLastWriteTime;
public int nFileSizeHigh;
public int nFileSizeLow;
public int dwReserved0;
public int dwReserved1;
[MarshalAs(UnmanagedType.ByValTStr, SizeConst = MAX_PATH)]
public string cFileName;
[MarshalAs(UnmanagedType.ByValTStr, SizeConst = MAX_ALTERNATE)]
public string cAlternate;
}
[DllImport("kernel32", EntryPoint = "FindFirstFileW", SetLastError = true,
CharSet = CharSet.Unicode, ExactSpelling = true)]
public static extern IntPtr FindFirstFile(string lpFileName, out WIN32_FIND_DATA lpFindFileData);
[DllImport("kernel32", EntryPoint = "FindNextFileW", SetLastError = true,
CharSet = CharSet.Unicode, ExactSpelling = true)]
public static extern bool FindNextFile(IntPtr hFindFile, out WIN32_FIND_DATA lpFindFileData);
[DllImport("kernel32.dll", SetLastError = true, ExactSpelling = true)]
public static extern bool FindClose(IntPtr hFindFile);
[DllImport("kernel32.dll", EntryPoint = "RemoveDirectoryW", SetLastError = true,
CharSet = CharSet.Unicode, ExactSpelling = true)]
public static extern bool RemoveDirectory(string lpPathName);
[DllImport("kernel32.dll", EntryPoint = "DeleteFileW", SetLastError = true,
CharSet = CharSet.Unicode, ExactSpelling = true)]
[return: MarshalAs(UnmanagedType.Bool)]
public static extern bool DeleteFile([MarshalAs(UnmanagedType.LPTStr)]string lpFileName);
[DllImport("kernel32.dll", SetLastError = true, ExactSpelling = true)]
[return: MarshalAs(UnmanagedType.Bool)]
static extern bool CloseHandle(IntPtr hObject);
[DllImport("kernel32.dll", EntryPoint = "CopyFileW", SetLastError = true,
CharSet = CharSet.Auto, ExactSpelling = true)]
public static extern bool CopyFile(
String src, String dst, bool failIfExists);
#endregion
}
}

---------------------------------------------


using System;
using System.Collections.Generic;
using System.Text;
using System.IO;
using System.ComponentModel;
using System.Runtime.InteropServices;
using Microsoft.Win32.SafeHandles;
namespace MyCompany.MyProduct.Common.Win32
{
public class MyCompanyDirectoryInfo : MyCompanyFileSystemInfo
{
public MyCompanyDirectoryInfo(string path) : base(path)
{
//if ((int)(Attributes & EFileAttributes.Directory) == 0)
// throw new ApplicationException("File path is specified. Use MyCompanyFileInfo instead");
}
public bool Create()
{
if (Exists)
return true;
if (Parent == null)
return true;
string t = Parent.FullNameWithPrefix;
if (t.EndsWith(":"))
t = Environment.SystemDirectory;
return CreateDirectoryEx(t, _path, new IntPtr());
}
public MyCompanyFileInfo[] GetFiles()
{
return GetFiles("*");
}
public MyCompanyDirectoryInfo[] GetDirectories()
{
return GetDirectories("*");
}
public MyCompanyFileInfo[] GetFiles(string pattern)
{
if (string.IsNullOrEmpty(pattern))
pattern = "*";
List _files = new List();
WIN32_FIND_DATA findData;
IntPtr findHandle;
findHandle = FindFirstFile(_path + @"\" + pattern, out findData);
if (findHandle != INVALID_HANDLE_VALUE)
{
do
{
if ((findData.dwFileAttributes & EFileAttributes.Directory) != 0)
{
}
else
{
string file = _path + @"\" + findData.cFileName;
_files.Add(new MyCompanyFileInfo(file));
}
}
while (FindNextFile(findHandle, out findData));
FindClose(findHandle);
}
return _files.ToArray();
}
public MyCompanyDirectoryInfo[] GetDirectories(string pattern)
{
if (string.IsNullOrEmpty(pattern))
pattern = "*";
List _folders = new List();
WIN32_FIND_DATA findData;
IntPtr findHandle;
findHandle = FindFirstFile(_path + @"\" + pattern, out findData);
if (findHandle != INVALID_HANDLE_VALUE)
{
do
{
if ((findData.dwFileAttributes & EFileAttributes.Directory) != 0)
{
if (findData.cFileName != "." && findData.cFileName != "..")
{
string subdirectory = _path + @"\" + findData.cFileName;
_folders.Add(new MyCompanyDirectoryInfo(subdirectory));
}
}
else
{
}
}
while (FindNextFile(findHandle, out findData));
FindClose(findHandle);
}
return _folders.ToArray();
}
public override bool Delete()
{
return RemoveDirectory(_path);
}
public bool Delete(bool recursively)
{
if (!recursively)
return Delete();
else
{
DeleteFilesAndFolders();
return Delete();
}
}
public void DeleteFilesAndFolders()
{
foreach (MyCompanyDirectoryInfo di in GetDirectories())
{
di.Delete(true);
}
foreach (MyCompanyFileInfo fi in GetFiles())
{
fi.Delete();
}
}
public bool Create(bool recursively)
{
if (recursively &&amp; Parent != null && !Parent.Exists)
Parent.Create(recursively);
bool b = Create();
return b;
}
public void CopyTo(MyCompanyDirectoryInfo dest, string pattern, bool recursively)
{
if (recursively)
{
foreach (MyCompanyDirectoryInfo di in GetDirectories(pattern))
{
MyCompanyDirectoryInfo di2 = new MyCompanyDirectoryInfo(dest.FullName + "\\" + di.Name);
di2.Create(true);
di.CopyTo(di2, pattern, true);
}
}
foreach (MyCompanyFileInfo fi in GetFiles(pattern))
{
MyCompanyFileInfo fi2 = new MyCompanyFileInfo(dest.FullName + "\\" + fi.Name);
fi.CopyTo(fi2, false);
}
}
}
}

----------------------------------------------------------------------------


using System;
using System.Collections.Generic;
using System.Text;
using System.IO;
using System.ComponentModel;
using System.Runtime.InteropServices;
using Microsoft.Win32.SafeHandles;
namespace MyCompany.MyProduct.Common.Win32
{
public class MyCompanyFileInfo : MyCompanyFileSystemInfo
{
public MyCompanyFileInfo(string path) : base(path)
{
//if ((int)(Attributes & EFileAttributes.Directory) != 0)
// throw new ApplicationException("Directory path is specified. Use MyCompanyDirectoryInfo instead");
}
public FileStream Open(EFileAccess access,
EFileShare share,
ECreationDisposition creationDisposition,
EFileAttributes fileAttributes)
{
IntPtr handle = CreateFileInternal(access, share, creationDisposition, fileAttributes);
if (handle == INVALID_HANDLE_VALUE)
{
throw new Win32Exception(Marshal.GetLastWin32Error());
}
else
{
return new FileStream(new SafeFileHandle(handle, true), FileAccess.ReadWrite);
}
}
public MyCompanyDirectoryInfo Directory
{
get
{
return Parent;
}
}
public FileStream OpenRead()
{
return Open(EFileAccess.GenericRead,EFileShare.Read,ECreationDisposition.OpenExisting,EFileAttributes.Normal);
}
public FileStream Create()
{
return Open(EFileAccess.GenericAll, EFileShare.None, ECreationDisposition.CreateAlways, EFileAttributes.Normal);
}
public FileStream OpenWrite()
{
return Open(EFileAccess.GenericWrite, EFileShare.None, ECreationDisposition.OpenAlways, EFileAttributes.Normal);
}
public override bool Delete()
{
return DeleteFile(_path);
}
internal void CopyTo(MyCompanyFileInfo fi2, bool failIfExists)
{
CopyFile(_path, fi2._path, failIfExists);
}
}
}

Throwing and catching an Exception from remoting object problem

So here is the problem I have:

When throwing any exception from Remoting'ly created object
on the client side we have RemotingException instead having something like this:
Server encountered an internal error. For more information, turn off customErrors in the server's .config file.
instead of the real exception.
Be aware - it happens only when calling object really remotely, not by remoting on the same computer.
The solution should be to add following lines to your *.config file (either app.config copyied then to appname.exe.config or web.config):



However it does not help!
The reason is in strange fact that Remoting does not load the config settings by default. Can't imagine why...
To workaround this, have something like this in startup method of your application:
string configPath =
System.Reflection.Assembly.GetExecutingAssembly().Location + ".config";

System.Runtime.Remoting.RemotingConfiguration.Configure(configPath);
The goal is to force Remoting to final load the config from proper file.
I saw an idea to use a null instead of file name, but this did no work for me.
Enjoy!
You can use code like this
bool b = System.Runtime.Remoting.RemotingConfiguration.CustomErrorsEnabled(false);
to check this setting.

Thanks Chris Taylor for great info from here:
http://dotnetjunkies.com/WebLog/chris.taylor/articles/5566.aspx