// LocalPath.h #if _MSC_VER >= 1000 #pragma once #endif // _MSC_VER >= 1000 #ifndef LocalPathh #define LocalPathh #include "CSV.h" struct __declspec(novtable) CLocalPath { CLocalPath() {} // Private Constructor: you shouldn't create one of these. public: static bool IsFolderEmpty(CString Path) { // Uses the parameter as a temp variable if(Path.IsEmpty()) return true; Path+=((Path.GetAt(Path.GetLength()-1)=='\\') ? "*" : "\\*"); WIN32_FIND_DATA FindFileData; HANDLE hFind; if((hFind=FindFirstFile(Path, &FindFileData))==INVALID_HANDLE_VALUE) return true; //Finds Files . & .. !!! if(!FindNextFile(hFind, &FindFileData)) return true; // Ignore ".." return !FindNextFile(hFind, &FindFileData); // This should be the first proper File Data } static void FormLongPathName(CString& Path) { int Length=::GetLongPathName(Path,0,0); // Make it the long path name so that the user can understand it if they see it. char* ptr=Path.GetBufferSetLength(max(Path.GetLength(), Length)+1); // +1 for appended back-slash. max because a long file name may be longer than a short one: long "c:\a b.txt" on my PC using "DIR /X" is short "c:\ABBE25~1.txt" ::GetLongPathName(Path, ptr, Length); Path.ReleaseBuffer(); } static void FormShortPathName(CString& Path) { int Length=::GetShortPathName(Path,0,0); // Make it the short path name so that DBCS (eg. Japanese) File Paths can be passed as simple char*. char* ptr=Path.GetBufferSetLength(max(Path.GetLength(), Length)+1); // +1 for appended back-slash. max because a long file name may be longer than a short one: long "c:\a b.txt" on my PC using "DIR /X" is short "c:\ABBE25~1.txt" ::GetShortPathName(Path, ptr, Length); Path.ReleaseBuffer(); } static CString FormatPath(CString Path, bool TrailingBackSlash=true) { // Uses the Path parameter as a temp variable DWORD Length=GetFullPathName(Path,0,0,0); // Make relative paths absolute: char* ptr=Path.GetBufferSetLength(Length+1); // +1 for appended back-slash GetFullPathName(Path, Length+1, ptr, 0); bool HasTrailingBackSlash=false; if(Length>0) { // Use Multi Byte Character Set for Japanese etc. ptr=reinterpret_cast(_mbsrchr(reinterpret_cast(ptr),'\\')); // find last occurrence of '\' HasTrailingBackSlash=(ptr && (*_mbsinc(reinterpret_cast(ptr))=='\0')); } Path.ReleaseBuffer(); if( TrailingBackSlash && !HasTrailingBackSlash) return Path+'\\'; if(!TrailingBackSlash && HasTrailingBackSlash) Path.TrimRight('\\'); return Path; // Anything using TrailingBackSlash=false should not use "C:" to get the current working Directory! } static CString GetExtension(CString S) { // Uses the parameter as a temp variable int i=S.ReverseFind('.'); if(i!=-1) S=S.Mid(i); S.MakeLower(); return S; } static CString GetCurrentDirectory() { CString S; DWORD Len=::GetCurrentDirectory(0,0); ::GetCurrentDirectory(Len, S.GetBufferSetLength(Len)); S.ReleaseBuffer(); return FormatPath(S); } static CString SetCurrentDirectory(const CString& Path) { // returns the path that was successfully created. CCSV CSV(0, Path, '\\'); // Change directories one-by-one: if(Path.Left(2)=="\\\\") { // \\ComputerName\Share\Directory format CSV.GetNextField(); // Skip the first two "empty fields" CSV.GetNextField(); CSV.GetNextField(); // Win9x needs the ComputerName and Share to be used together, so we have to skip the ComputerName too. } while(CSV.GetNextField() && ::SetCurrentDirectory(CSV.GetDone())); // This will stop if an invalid directory is in the Path. return CSV.GetDone(); } static bool CreatePath(const CString& Path) { CCSV CSV(0, Path, '\\'); if(Path.Left(2)=="\\\\") { // \\ComputerName\Share\Directory format CSV.GetNextField(); // Skip the first two "empty fields" CSV.GetNextField(); CSV.GetNextField(); // Win9x needs the ComputerName and Share to be used together, so we have to skip the ComputerName too. } while(CSV.GetNextField() && ((GetFileAttributes(CSV.GetDone())!=-1) || ::CreateDirectory(CSV.GetDone(),0))); return CSV.GetEnd().IsEmpty()!=0; } static void FlushDrive(char Drive) { // Empties Cached data so you can remove Removable Media without losing data. char Path[]="\\\\.\\c:"; Path[4]=Drive; HANDLE hFile; hFile=CreateFile(Path, 0, FILE_SHARE_READ | FILE_SHARE_WRITE, NULL, OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, NULL); if(hFile!=INVALID_HANDLE_VALUE) FlushFileBuffers(hFile); CloseHandle(hFile); } static CString GetTempFolder(bool Long=true) { CString Root; // Form a temporary file name DWORD Len=GetTempPath(0,0); GetTempPath(Len, Root.GetBufferSetLength(Len)); // on NT, will fail if directory pointed to by TEMP does not exist Root.ReleaseBuffer(); if(Long) FormLongPathName(Root); Root=FormatPath(Root); if(Root.IsEmpty()) Root="C:\\"; return Root; } static CString FindFreeFileName(CString Path, const char* NumberFormat="(%u)", unsigned MaxNumber=UINT_MAX, bool NoNumberIfFree=true) { // "%04u" gives file bumbers 0001 etc. Uses the Path parameter as a temp variable if(NoNumberIfFree && (GetFileAttributes(Path)==-1)) return Path; unsigned i=Path.ReverseFind('.'); CString Template((i==-1 ? Path : Path.Left(i))+NumberFormat+(i==-1 ? "" : Path.Mid(i))); i=NoNumberIfFree; do Path.Format(Template, i++); while((GetFileAttributes(Path)!=-1) && (i1099511627776) S.Format("%.1f TB",(Size/(1099511627776/100)+5)/100.0); else if(Size>1073741824) S.Format("%.1f GB",(Size/( 1073741824/100)+5)/100.0); else if(Size>1048576) S.Format("%.1f MB",(Size/( 1048576/100)+5)/100.0); else if(Size>1024) S.Format("%.1f kB",(Size/( 1024/100)+5)/100.0); else S.Format("%u Bytes",Size); return S; } }; class CCurrentDirectory { CString OldCurrentDirectory; public: CCurrentDirectory(const CString& Path) : OldCurrentDirectory(CLocalPath::GetCurrentDirectory()) {CLocalPath::SetCurrentDirectory(Path);} virtual ~CCurrentDirectory() {CLocalPath::SetCurrentDirectory(OldCurrentDirectory);} }; struct CFolderSizer { // A Directory Tree which finds the sizes of its contents and, optionally, the total number of Files and Folders: CString Name; // The name of this Folder or File (not the Path) __int64 Size; // For Folders, Size is the Size of the contents (the whole point of this struct is to find this) CFolderSizer* Parent; // The Root has Parent==0 CFolderSizer* Child; // A File has Child==GetFileToken(); An empty folder has Child==0; int ChildCount; void* Data; // For use in derived classes virtual ~CFolderSizer() {try{DeleteData(); if(!IsFile()) delete[] Child;}catch(...){}} // The Root destructor will recursively destruct all the children. virtual void DeleteData() {} CFolderSizer() : Size(0), Parent(0), Child(0), ChildCount(0), Data(0) {} CFolderSizer(const CString& Path, int* FolderCount=0, int* FileCount=0) : Size(0), Parent(0), Child(0), ChildCount(0), Data(0) {CalculateSize(Path,FolderCount,FileCount);} CFolderSizer* GetFileToken() const {return reinterpret_cast(-1);} bool IsFile() const {return (ChildCount==0) && (Child==GetFileToken());} int GetDepth() const {int i=0; for(CFolderSizer* it=Parent; it; it=it->Parent) ++i; return i;} CString GetPath() const {return Parent ? Parent->GetPath()+'\\'+Name : Name;} __int64 CalculateSize(const CString& Path, int* FolderCount, int* FileCount) { Name=Path; if((Path.GetLength()>1) && (Path[1]==':')) { char Drive=Path[0]; CLocalPath::FlushDrive(Drive); // FindFirstFile doesn't account for lazy writes, so try to ensure the sizes are up to date } if(FolderCount) *FolderCount=0; if(FileCount) *FileCount=0; __int64 Total=CalculateSize(FolderCount,FileCount); Sort(); return Total; } __int64 CalculateSize(int* FolderCount, int* FileCount) { HANDLE hFind; WIN32_FIND_DATA FindFileData; CString Path(GetPath()+"\\*"); if((hFind=FindFirstFile(Path, &FindFileData))==INVALID_HANDLE_VALUE) return 0; //Finds Files . & .. !!! while(FindFileData.cFileName[0]=='.' && (FindFileData.cFileName[1]==0 || (FindFileData.cFileName[1]=='.' && FindFileData.cFileName[2]==0))) { if(!FindNextFile(hFind, &FindFileData)) { FindClose(hFind); return 0; } } int i=1; // Count the children for the array: while(FindNextFile(hFind, &FindFileData)) ++i; FindClose(hFind); ChildCount=i; Child=new CFolderSizer[ChildCount]; // Start again now that the Array is ready: if((hFind=FindFirstFile(Path, &FindFileData))==INVALID_HANDLE_VALUE) { //Finds Files . & .. !!! FindClose(hFind); return 0; } while(FindFileData.cFileName[0]=='.' && (FindFileData.cFileName[1]==0 || (FindFileData.cFileName[1]=='.' && FindFileData.cFileName[2]==0))) { if(!FindNextFile(hFind, &FindFileData)) { FindClose(hFind); return 0; } } i=0; do { Child[i].Name=FindFileData.cFileName; Child[i].Parent=this; if((FindFileData.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY)!=0) { if(FolderCount) ++*FolderCount; Child[i].CalculateSize(FolderCount,FileCount); }else{ // It's a File: if(FileCount) ++*FileCount; Child[i].Size=(((__int64)(FindFileData.nFileSizeHigh))<<32)|FindFileData.nFileSizeLow; Child[i].Child=GetFileToken(); // Mark as a File } Size+=Child[i].Size; ++i; }while(FindNextFile(hFind, &FindFileData)); FindClose(hFind); return Size; } void Sort() { if(ChildCount>1) qsort(Child, ChildCount, sizeof(CFolderSizer), &SizeSortCallBack); for(int i=ChildCount; i--; ) { Child[i].Parent=this; // Moving the items around messes up the Parent Pointers. if(!Child[i].IsFile()) Child[i].Sort(); } } static int SizeSortCallBack(const void* p1, const void* p2) { // Used by qsort: CFolderSizer* a=(CFolderSizer*)p1; CFolderSizer* b=(CFolderSizer*)p2; __int64 d=b->IsFile() - a->IsFile(); if(d==0) d=b->Size - a->Size; // Folders last, otherwise sort by size return d<0 ? -1 : (d>0); } }; #endif // ndef LocalPathh