// FSFolder.cpp #include "StdAfx.h" #include "FSFolder.h" #include "Common/StringConvert.h" #include "Common/StdInStream.h" #include "Common/StdOutStream.h" #include "Common/UTFConvert.h" #include "Windows/Defs.h" #include "Windows/PropVariant.h" #include "Windows/FileDir.h" #include "Windows/FileIO.h" #include "../PropID.h" #include "SysIconUtils.h" #include "FSDrives.h" #include "NetFolder.h" namespace NWindows { namespace NFile { bool GetLongPath(LPCWSTR path, UString &longPath); }} using namespace NWindows; using namespace NFile; using namespace NFind; static STATPROPSTG kProperties[] = { { NULL, kpidName, VT_BSTR}, // { NULL, kpidIsFolder, VT_BOOL}, { NULL, kpidSize, VT_UI8}, { NULL, kpidLastWriteTime, VT_FILETIME}, { NULL, kpidCreationTime, VT_FILETIME}, { NULL, kpidLastAccessTime, VT_FILETIME}, { NULL, kpidAttributes, VT_UI4}, { NULL, kpidPackedSize, VT_UI8}, { NULL, kpidComment, VT_BSTR}, { NULL, kpidPrefix, VT_BSTR} }; HRESULT CFSFolder::Init(const UString &path, IFolderFolder *parentFolder) { _parentFolder = parentFolder; _path = path; _findChangeNotification.FindFirst(_path, 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 */); if (!_findChangeNotification.IsHandleAllocated()) { DWORD lastError = GetLastError(); CFindFile findFile; CFileInfoW fileInfo; if (!findFile.FindFirst(_path + UString(L"*"), fileInfo)) return lastError; } return S_OK; } static HRESULT GetFolderSize(const UString &path, UInt64 &size, IProgress *progress) { RINOK(progress->SetCompleted(NULL)); size = 0; CEnumeratorW enumerator(path + UString(L"\\*")); CFileInfoW fileInfo; while (enumerator.Next(fileInfo)) { if (fileInfo.IsDirectory()) { UInt64 subSize; RINOK(GetFolderSize(path + UString(L"\\") + fileInfo.Name, subSize, progress)); size += subSize; } else size += fileInfo.Size; } return S_OK; } HRESULT CFSFolder::LoadSubItems(CDirItem &dirItem, const UString &path) { { CEnumeratorW enumerator(path + L"*"); CDirItem fileInfo; while (enumerator.Next(fileInfo)) { fileInfo.CompressedSizeIsDefined = false; /* if (!GetCompressedFileSize(_path + fileInfo.Name, fileInfo.CompressedSize)) fileInfo.CompressedSize = fileInfo.Size; */ if (fileInfo.IsDirectory()) { // fileInfo.Size = GetFolderSize(_path + fileInfo.Name); fileInfo.Size = 0; } dirItem.Files.Add(fileInfo); } } if (!_flatMode) return S_OK; for (int i = 0; i < dirItem.Files.Size(); i++) { CDirItem &item = dirItem.Files[i]; if (item.IsDirectory()) LoadSubItems(item, path + item.Name + L'\\'); } return S_OK; } void CFSFolder::AddRefs(CDirItem &dirItem) { int i; for (i = 0; i < dirItem.Files.Size(); i++) { CDirItem &item = dirItem.Files[i]; item.Parent = &dirItem; _refs.Add(&item); } if (!_flatMode) return; for (i = 0; i < dirItem.Files.Size(); i++) { CDirItem &item = dirItem.Files[i]; if (item.IsDirectory()) AddRefs(item); } } STDMETHODIMP CFSFolder::LoadItems() { // OutputDebugString(TEXT("Start\n")); INT32 dummy; WasChanged(&dummy); Clear(); RINOK(LoadSubItems(_root, _path)); AddRefs(_root); // OutputDebugString(TEXT("Finish\n")); _commentsAreLoaded = false; return S_OK; } static const wchar_t *kDescriptionFileName = L"descript.ion"; bool CFSFolder::LoadComments() { if (_commentsAreLoaded) return true; _comments.Clear(); _commentsAreLoaded = true; NIO::CInFile file; if (!file.Open(_path + kDescriptionFileName)) return false; UInt64 length; if (!file.GetLength(length)) return false; if (length >= (1 << 28)) return false; AString s; char *p = s.GetBuffer((int)((size_t)length + 1)); UInt32 processedSize; file.Read(p, (UInt32)length, processedSize); p[length] = 0; s.ReleaseBuffer(); s.Replace("\r\n", "\n"); if (processedSize != length) return false; file.Close(); UString unicodeString; if (!ConvertUTF8ToUnicode(s, unicodeString)) return false; return _comments.ReadFromString(unicodeString); } static bool IsAscii(const UString &testString) { for (int i = 0; i < testString.Length(); i++) if (testString[i] >= 0x80) return false; return true; } bool CFSFolder::SaveComments() { NIO::COutFile file; if (!file.Create(_path + kDescriptionFileName, true)) return false; UString unicodeString; _comments.SaveToString(unicodeString); AString utfString; ConvertUnicodeToUTF8(unicodeString, utfString); UInt32 processedSize; if (!IsAscii(unicodeString)) { Byte bom [] = { 0xEF, 0xBB, 0xBF, 0x0D, 0x0A }; file.Write(bom , sizeof(bom), processedSize); } utfString.Replace("\n", "\r\n"); file.Write(utfString, utfString.Length(), processedSize); _commentsAreLoaded = false; return true; } STDMETHODIMP CFSFolder::GetNumberOfItems(UInt32 *numItems) { *numItems = _refs.Size(); return S_OK; } /* STDMETHODIMP CFSFolder::GetNumberOfSubFolders(UInt32 *numSubFolders) { UInt32 numSubFoldersLoc = 0; for (int i = 0; i < _files.Size(); i++) if (_files[i].IsDirectory()) numSubFoldersLoc++; *numSubFolders = numSubFoldersLoc; return S_OK; } */ bool MyGetCompressedFileSizeW(LPCWSTR fileName, UInt64 &size) { DWORD highPart; DWORD lowPart = ::GetCompressedFileSizeW(fileName, &highPart); if (lowPart == INVALID_FILE_SIZE && ::GetLastError() != NO_ERROR) { #ifdef WIN_LONG_PATH { UString longPath; if (GetLongPath(fileName, longPath)) lowPart = ::GetCompressedFileSizeW(longPath, &highPart); } #endif if (lowPart == INVALID_FILE_SIZE && ::GetLastError() != NO_ERROR) return false; } size = (UInt64(highPart) << 32) | lowPart; return true; } STDMETHODIMP CFSFolder::GetProperty(UInt32 itemIndex, PROPID propID, PROPVARIANT *value) { NCOM::CPropVariant propVariant; if (itemIndex >= (UInt32)_refs.Size()) return E_INVALIDARG; CDirItem &fileInfo = *_refs[itemIndex]; switch(propID) { case kpidIsFolder: propVariant = fileInfo.IsDirectory(); break; case kpidName: propVariant = fileInfo.Name; break; case kpidSize: propVariant = fileInfo.Size; break; case kpidPackedSize: if (!fileInfo.CompressedSizeIsDefined) { fileInfo.CompressedSizeIsDefined = true; if (fileInfo.IsDirectory () || !MyGetCompressedFileSizeW(_path + GetRelPath(fileInfo), fileInfo.CompressedSize)) fileInfo.CompressedSize = fileInfo.Size; } propVariant = fileInfo.CompressedSize; break; case kpidAttributes: propVariant = (UInt32)fileInfo.Attributes; break; case kpidCreationTime: propVariant = fileInfo.CreationTime; break; case kpidLastAccessTime: propVariant = fileInfo.LastAccessTime; break; case kpidLastWriteTime: propVariant = fileInfo.LastWriteTime; break; case kpidComment: { LoadComments(); UString comment; if (_comments.GetValue(GetRelPath(fileInfo), comment)) propVariant = comment; break; } case kpidPrefix: { if (_flatMode) { propVariant = GetPrefix(fileInfo); } break; } } propVariant.Detach(value); return S_OK; } HRESULT CFSFolder::BindToFolderSpec(const wchar_t *name, IFolderFolder **resultFolder) { *resultFolder = 0; CFSFolder *folderSpec = new CFSFolder; CMyComPtr subFolder = folderSpec; RINOK(folderSpec->Init(_path + name + UString(L'\\'), 0)); *resultFolder = subFolder.Detach(); return S_OK; } UString CFSFolder::GetPrefix(const CDirItem &item) const { UString path; CDirItem *cur = item.Parent; while (cur->Parent != 0) { path = cur->Name + UString('\\') + path; cur = cur->Parent; } return path; } UString CFSFolder::GetRelPath(const CDirItem &item) const { return GetPrefix(item) + item.Name; } STDMETHODIMP CFSFolder::BindToFolder(UInt32 index, IFolderFolder **resultFolder) { *resultFolder = 0; const CDirItem &fileInfo = *_refs[index]; if (!fileInfo.IsDirectory()) return E_INVALIDARG; return BindToFolderSpec(GetRelPath(fileInfo), resultFolder); } STDMETHODIMP CFSFolder::BindToFolder(const wchar_t *name, IFolderFolder **resultFolder) { return BindToFolderSpec(name, resultFolder); } STDMETHODIMP CFSFolder::BindToParentFolder(IFolderFolder **resultFolder) { *resultFolder = 0; if (_parentFolder) { CMyComPtr parentFolder = _parentFolder; *resultFolder = parentFolder.Detach(); return S_OK; } if (_path.IsEmpty()) return E_INVALIDARG; int pos = _path.ReverseFind(L'\\'); if (pos < 0 || pos != _path.Length() - 1) return E_FAIL; UString parentPath = _path.Left(pos); pos = parentPath.ReverseFind(L'\\'); if (pos < 0) { parentPath.Empty(); CFSDrives *drivesFolderSpec = new CFSDrives; CMyComPtr drivesFolder = drivesFolderSpec; drivesFolderSpec->Init(); *resultFolder = drivesFolder.Detach(); return S_OK; } UString parentPathReduced = parentPath.Left(pos); parentPath = parentPath.Left(pos + 1); pos = parentPathReduced.ReverseFind(L'\\'); if (pos == 1) { if (parentPath[0] != L'\\') return E_FAIL; CNetFolder *netFolderSpec = new CNetFolder; CMyComPtr netFolder = netFolderSpec; netFolderSpec->Init(parentPath); *resultFolder = netFolder.Detach(); return S_OK; } CFSFolder *parentFolderSpec = new CFSFolder; CMyComPtr parentFolder = parentFolderSpec; RINOK(parentFolderSpec->Init(parentPath, 0)); *resultFolder = parentFolder.Detach(); return S_OK; } STDMETHODIMP CFSFolder::GetName(BSTR * /* name */) { return E_NOTIMPL; /* CMyComBSTR aBSTRName = m_ProxyFolderItem->m_Name; *name = aBSTRName.Detach(); return S_OK; */ } STDMETHODIMP CFSFolder::GetNumberOfProperties(UInt32 *numProperties) { *numProperties = sizeof(kProperties) / sizeof(kProperties[0]); if (!_flatMode) (*numProperties)--; return S_OK; } STDMETHODIMP CFSFolder::GetPropertyInfo(UInt32 index, BSTR *name, PROPID *propID, VARTYPE *varType) { if (index >= sizeof(kProperties) / sizeof(kProperties[0])) return E_INVALIDARG; const STATPROPSTG &prop = kProperties[index]; *propID = prop.propid; *varType = prop.vt; *name = 0; return S_OK; } STDMETHODIMP CFSFolder::GetTypeID(BSTR *name) { CMyComBSTR temp = L"FSFolder"; *name = temp.Detach(); return S_OK; } STDMETHODIMP CFSFolder::GetPath(BSTR *path) { CMyComBSTR temp = _path; *path = temp.Detach(); return S_OK; } STDMETHODIMP CFSFolder::WasChanged(INT32 *wasChanged) { bool wasChangedMain = false; for (;;) { if (!_findChangeNotification.IsHandleAllocated()) { *wasChanged = BoolToInt(false); return S_OK; } DWORD waitResult = ::WaitForSingleObject(_findChangeNotification, 0); bool wasChangedLoc = (waitResult == WAIT_OBJECT_0); if (wasChangedLoc) { _findChangeNotification.FindNext(); wasChangedMain = true; } else break; } *wasChanged = BoolToInt(wasChangedMain); return S_OK; } STDMETHODIMP CFSFolder::Clone(IFolderFolder **resultFolder) { CFSFolder *fsFolderSpec = new CFSFolder; CMyComPtr folderNew = fsFolderSpec; fsFolderSpec->Init(_path, 0); *resultFolder = folderNew.Detach(); return S_OK; } HRESULT CFSFolder::GetItemFullSize(int index, UInt64 &size, IProgress *progress) { const CDirItem &fileInfo = *_refs[index]; if (fileInfo.IsDirectory()) { /* CMyComPtr subFolder; RINOK(BindToFolder(index, &subFolder)); CMyComPtr aFolderReload; subFolder.QueryInterface(&aFolderReload); aFolderReload->Reload(); UInt32 numItems; RINOK(subFolder->GetNumberOfItems(&numItems)); CMyComPtr aGetItemFullSize; subFolder.QueryInterface(&aGetItemFullSize); for (UInt32 i = 0; i < numItems; i++) { UInt64 size; RINOK(aGetItemFullSize->GetItemFullSize(i, &size)); *totalSize += size; } */ return GetFolderSize(_path + GetRelPath(fileInfo), size, progress); } size = fileInfo.Size; return S_OK; } STDMETHODIMP CFSFolder::GetItemFullSize(UInt32 index, PROPVARIANT *value, IProgress *progress) { NCOM::CPropVariant propVariant; if (index >= (UInt32)_refs.Size()) return E_INVALIDARG; UInt64 size = 0; HRESULT result = GetItemFullSize(index, size, progress); propVariant = size; propVariant.Detach(value); return result; } HRESULT CFSFolder::GetComplexName(const wchar_t *name, UString &resultPath) { UString newName = name; resultPath = _path + newName; if (newName.Length() < 1) return S_OK; if (newName[0] == L'\\') { resultPath = newName; return S_OK; } if (newName.Length() < 2) return S_OK; if (newName[1] == L':') resultPath = newName; return S_OK; } STDMETHODIMP CFSFolder::CreateFolder(const wchar_t *name, IProgress * /* progress */) { UString processedName; RINOK(GetComplexName(name, processedName)); if(NDirectory::MyCreateDirectory(processedName)) return S_OK; if(::GetLastError() == ERROR_ALREADY_EXISTS) return ::GetLastError(); if (!NDirectory::CreateComplexDirectory(processedName)) return ::GetLastError(); return S_OK; } STDMETHODIMP CFSFolder::CreateFile(const wchar_t *name, IProgress * /* progress */) { UString processedName; RINOK(GetComplexName(name, processedName)); NIO::COutFile outFile; if (!outFile.Create(processedName, false)) return ::GetLastError(); return S_OK; } STDMETHODIMP CFSFolder::Rename(UInt32 index, const wchar_t *newName, IProgress * /* progress */) { const CDirItem &fileInfo = *_refs[index]; const UString fullPrefix = _path + GetPrefix(fileInfo); if (!NDirectory::MyMoveFile(fullPrefix + fileInfo.Name, fullPrefix + newName)) return GetLastError(); return S_OK; } STDMETHODIMP CFSFolder::Delete(const UInt32 *indices, UInt32 numItems,IProgress *progress) { RINOK(progress->SetTotal(numItems)); for (UInt32 i = 0; i < numItems; i++) { const CDirItem &fileInfo = *_refs[indices[i]]; const UString fullPath = _path + GetRelPath(fileInfo); bool result; if (fileInfo.IsDirectory()) result = NDirectory::RemoveDirectoryWithSubItems(fullPath); else result = NDirectory::DeleteFileAlways(fullPath); if (!result) return GetLastError(); UInt64 completed = i; RINOK(progress->SetCompleted(&completed)); } return S_OK; } STDMETHODIMP CFSFolder::SetProperty(UInt32 index, PROPID propID, const PROPVARIANT *value, IProgress * /* progress */) { if (index >= (UInt32)_refs.Size()) return E_INVALIDARG; CDirItem &fileInfo = *_refs[index]; if (fileInfo.Parent->Parent != 0) return E_NOTIMPL; switch(propID) { case kpidComment: { UString filename = fileInfo.Name; filename.Trim(); if (value->vt == VT_EMPTY) _comments.DeletePair(filename); else if (value->vt == VT_BSTR) { CTextPair pair; pair.ID = filename; pair.ID.Trim(); pair.Value = value->bstrVal; pair.Value.Trim(); if (pair.Value.IsEmpty()) _comments.DeletePair(filename); else _comments.AddPair(pair); } else return E_INVALIDARG; SaveComments(); break; } default: return E_NOTIMPL; } return S_OK; } STDMETHODIMP CFSFolder::GetSystemIconIndex(UInt32 index, INT32 *iconIndex) { if (index >= (UInt32)_refs.Size()) return E_INVALIDARG; const CDirItem &fileInfo = *_refs[index]; *iconIndex = 0; int iconIndexTemp; if (GetRealIconIndex(_path + GetRelPath(fileInfo), fileInfo.Attributes, iconIndexTemp) != 0) { *iconIndex = iconIndexTemp; return S_OK; } return GetLastError(); } STDMETHODIMP CFSFolder::SetFlatMode(Int32 flatMode) { _flatMode = IntToBool(flatMode); return S_OK; } // static const LPCTSTR kInvalidFileChars = TEXT("\\/:*?\"<>|");