Vault
4.1
|
00001 /* 00002 Copyright c1997-2014 Trygve Isaacson. All rights reserved. 00003 This file is part of the Code Vault version 4.1 00004 http://www.bombaydigital.com/ 00005 License: MIT. See LICENSE.md in the Vault top level directory. 00006 */ 00007 00010 #include "vfsnode.h" 00011 #include "vtypes_internal.h" 00012 00013 #include "vexception.h" 00014 #include "vthread.h" 00015 #ifdef VCOMPILER_MSVC 00016 #pragma warning(disable: 6387) // the library file doesn't past muster 00017 #endif 00018 #include <shlobj.h> 00019 #ifdef VCOMPILER_MSVC 00020 #pragma warning(default: 6387) 00021 #endif 00022 00023 /* 00024 Notes on use of Win32 "wide" APIs: 00025 00026 We define APIs in VFileSystem for common functions across platforms, and these 00027 take VStrings and call through to VPlatformAPI which can do any necessary conversion. 00028 00029 But there are few Win32 APIs we call here that are not exposed in a cross-platform way 00030 in VFileSystem because they are not general-purpose. So we must do the necessary 00031 conversions here, in this manner: 00032 00033 Win32 has three flavors of each string-based API. 00034 The single-byte char, or "ANSI" versions, have an "A" suffix. 00035 For example: FindFirstFileA(const char* path, WIN32_FIND_DATAA* data) 00036 (WIN32_FIND_DATAA has instance variables containing char) 00037 These use ANSI C char strings that must be in the "current" code page. 00038 They are not compatible with arbitrary string data; only the current code page. 00039 The two-byte char, or "wide" versions, have a "W" suffix. 00040 For example: FindFirstFileW(const wchar_t* path, WIN32_FIND_DATAW* data) 00041 (WIN32_FIND_DATAW has instance variables containing wchar_t) 00042 These use UTF-16 wchar_t strings, so they take Unicode in UTF-16 format. 00043 They will work with any UTF-16 character, regardless of the current code page. 00044 A version of each API without any suffix at all is macro-based and defined 00045 as the "A" version if UNICODE is not defined, or the "W" version if UNICODE is defined. 00046 For example: FindFirstFile() is defined as either FindFirstFileA() or FindFirstFileW() 00047 00048 We call the "W" versions directly, since our paths are all VString objects internally, 00049 which store Unicode in UTF-8 format, and we can easily convert to/from UTF-16 when we call 00050 the Win32 APIs here. 00051 For APIs where we pass the string, we just convert on-the-fly as the parameter: 00052 SetterW(s.toUTF16().c_str()); 00053 For APIs where we get a string back, we pass a wchar_t buffer and then construct a 00054 VString from it afterwards: 00055 wchar_t buf[LENGTH]; 00056 GetterW(buf); 00057 VString s(buf); 00058 */ 00059 00060 /* Note: according to Microsoft KB article 177506, only the following characters 00061 are valid in file and folder names on their operating system. I should provide 00062 some way of filtering out bad stuff for the particular platform. 00063 00064 - letters 00065 - numbers 00066 ^ Accent circumflex (caret) 00067 & Ampersand 00068 ' Apostrophe (single quotation mark) 00069 @ At sign 00070 { Brace left 00071 } Brace right 00072 [ Bracket opening 00073 ] Bracket closing 00074 , Comma 00075 $ Dollar sign 00076 = Equal sign 00077 ! Exclamation point 00078 - Hyphen 00079 # Number sign 00080 ( Parenthesis opening 00081 ) Parenthesis closing 00082 % Percent 00083 . Period 00084 + Plus 00085 ~ Tilde 00086 _ Underscore 00087 */ 00088 00089 // static 00090 VString VFSNode::_platform_normalizePath(const VString& path) { 00091 // For the moment, we get almost all the functionality we need by 00092 // simply converting backslash to slash if we are compiled for Windows. 00093 // Mac OS X support is free since it's Unix. Mac OS 9 support would 00094 // have to deal with ':'. DOS drive letters and Mac OS 9 root/relative 00095 // paths would complicate things a little for things like getParentPath. 00096 VString normalized(path); 00097 normalized.replace("\\", "/"); 00098 return normalized; 00099 } 00100 00101 // static 00102 VString VFSNode::_platform_denormalizePath(const VString& path) { 00103 // See comments above. 00104 VString denormalized(path); 00105 denormalized.replace("/", "\\"); 00106 return denormalized; 00107 } 00108 00109 // static 00110 VFSNode VFSNode::_platform_getKnownDirectoryNode(KnownDirectoryIdentifier id, const VString& companyName, const VString& appName) { 00111 if (id == CURRENT_WORKING_DIRECTORY) { 00112 return VFSNode(VPlatformAPI::getcwd()); 00113 } 00114 00115 if (id == EXECUTABLE_DIRECTORY) { 00116 VFSNode executable = VFSNode::getExecutable(); 00117 VFSNode executableDirectory; 00118 executable.getParentNode(executableDirectory); 00119 return executableDirectory; 00120 } 00121 00122 wchar_t pathBuffer[MAX_PATH]; 00123 HRESULT gfpResult = ::SHGetFolderPathW(NULL, CSIDL_APPDATA | CSIDL_FLAG_CREATE, NULL, SHGFP_TYPE_CURRENT, pathBuffer); 00124 00125 if (gfpResult != S_OK) { 00126 throw VStackTraceException(VSTRING_FORMAT("VFSNode::_platform_getKnownDirectoryNode: Unable to find current user Application Data folder. Error code %d.", (int) gfpResult)); 00127 } 00128 00129 VString path(pathBuffer); 00130 VFSNode appDataFolder(VFSNode::normalizePath(path)); 00131 00132 // User's folder is one level up from user's Application Data folder. 00133 VFSNode currentUserFolder; 00134 appDataFolder.getParentNode(currentUserFolder); 00135 00136 if (id == USER_HOME_DIRECTORY) { 00137 return currentUserFolder; 00138 } 00139 00140 VFSNode companyFolder; 00141 if (companyName.isEmpty()) { 00142 companyFolder = appDataFolder; 00143 } else { 00144 appDataFolder.getChildNode(companyName, companyFolder); 00145 companyFolder.mkdir(); 00146 } 00147 00148 VFSNode appFolder; 00149 if (appName.isEmpty()) { 00150 appFolder = companyFolder; 00151 } else { 00152 companyFolder.getChildNode(appName, appFolder); 00153 appFolder.mkdir(); 00154 } 00155 00156 VFSNode resultNode; 00157 00158 switch (id) { 00159 case USER_HOME_DIRECTORY: 00160 // handled earlier; we returned above 00161 break; 00162 00163 case LOG_FILES_DIRECTORY: 00164 appFolder.getChildNode("Logs", resultNode); 00165 break; 00166 00167 case USER_PREFERENCES_DIRECTORY: 00168 appFolder.getChildNode("Preferences", resultNode); 00169 break; 00170 00171 case CACHED_DATA_DIRECTORY: 00172 appFolder.getChildNode("Caches", resultNode); 00173 break; 00174 00175 case APPLICATION_DATA_DIRECTORY: 00176 resultNode = appFolder; 00177 break; 00178 00179 case EXECUTABLE_DIRECTORY: 00180 // handled earlier; we returned above 00181 break; 00182 00183 default: 00184 throw VStackTraceException(VSTRING_FORMAT("VFSNode::_platform_getKnownDirectoryNode: Requested invalid directory ID %d.", (int) id)); 00185 break; 00186 } 00187 00188 resultNode.mkdir(); 00189 00190 return resultNode; 00191 } 00192 00193 // static 00194 VFSNode VFSNode::_platform_getExecutable() { 00195 wchar_t exePathBuffer[MAX_PATH]; 00196 DWORD result = ::GetModuleFileNameW(HMODULE(NULL), exePathBuffer, DWORD(MAX_PATH)); 00197 00198 if (result == 0) 00199 throw VStackTraceException(VSystemError(), "VFSNode::_platform_getExecutable: Unable to determine exe path."); 00200 00201 VString exePath(exePathBuffer); 00202 VFSNode exeNode(VFSNode::normalizePath(exePath)); 00203 return exeNode; 00204 } 00205 00206 bool VFSNode::_platform_getNodeInfo(VFSNodeInfo& info) const { 00207 struct stat statData; 00208 int result = VFileSystem::stat(mPath, &statData); 00209 00210 if (result >= 0) { 00211 info.mCreationDate = CONST_S64(1000) * static_cast<Vs64>(statData.st_ctime); 00212 info.mModificationDate = CONST_S64(1000) * static_cast<Vs64>(statData.st_mtime); 00213 info.mFileSize = statData.st_size; 00214 DWORD attributes = ::GetFileAttributesW(mPath.toUTF16().c_str()); 00215 info.mIsFile = ((attributes & FILE_ATTRIBUTE_DIRECTORY) == 0); 00216 info.mIsDirectory = ((attributes & FILE_ATTRIBUTE_DIRECTORY) != 0); 00217 info.mErrNo = 0; 00218 } else { 00219 info.mErrNo = errno; 00220 } 00221 00222 return (result >= 0); 00223 } 00224 00225 void VFSNode::_platform_createDirectory() const { 00226 int result = VFileSystem::mkdir(mPath, (S_IFDIR | S_IRWXO | S_IRWXG | S_IRWXU)); 00227 00228 if (result != 0) 00229 throw VException(VSystemError(), VSTRING_FORMAT("VFSNode::_platform_createDirectory failed with result %d for '%s'.", result, mPath.chars())); 00230 } 00231 00232 bool VFSNode::_platform_removeDirectory() const { 00233 int result = VFileSystem::rmdir(mPath); 00234 return (result == 0); 00235 } 00236 00237 bool VFSNode::_platform_removeFile() const { 00238 int result = VFileSystem::unlink(mPath); 00239 return (result == 0); 00240 } 00241 00242 void VFSNode::_platform_renameNode(const VString& newPath) const { 00243 int result = VFileSystem::rename(mPath, newPath); 00244 00245 if (result != 0) 00246 throw VException(VSystemError(), VSTRING_FORMAT("VFSNode::_platform_renameNode failed with result %d renaming '%s' to '%s'.", result, mPath.chars(), newPath.chars())); 00247 } 00248 00249 // This is the Windows implementation of directory iteration using 00250 // FindFirstFile(), FindNextFile(), FindClose() functions. 00251 00252 void VFSNode::_platform_directoryIterate(VDirectoryIterationCallback& callback) const { 00253 VString searchPath(VFSNode::denormalizePath(VSTRING_FORMAT("%s/*", mPath.chars()))); // Supply DOS path syntax to Win32 API 00254 WIN32_FIND_DATAW data; 00255 HANDLE dir = ::FindFirstFileW(searchPath.toUTF16().c_str(), &data); 00256 00257 if (dir == INVALID_HANDLE_VALUE) { 00258 DWORD error = ::GetLastError(); 00259 00260 if (error == ERROR_NO_MORE_FILES) { 00261 return; 00262 } 00263 00264 throw VException(VSTRING_FORMAT("VFSNode::_platform_getDirectoryList failed (error %d) for directory '%s'.", error , searchPath.chars())); 00265 } 00266 00267 try { 00268 bool keepGoing = true; 00269 00270 do { 00271 VThread::yield(); // be nice if we're iterating over a huge directory 00272 00273 VString nodeName = data.cFileName; // assign VString from wide char string 00274 00275 // Skip current and parent pseudo-entries. Otherwise client must 00276 // know too much detail in order to avoid traversal problems. 00277 if ((nodeName != ".") && 00278 (nodeName != "..")) { 00279 VFSNode childNode; 00280 this->getChildNode(nodeName, childNode); 00281 keepGoing = callback.handleNextNode(childNode); 00282 } 00283 00284 } while (keepGoing && ::FindNextFileW(dir, &data)); 00285 } catch (...) { 00286 ::FindClose(dir); 00287 throw; 00288 } 00289 00290 ::FindClose(dir); 00291 } 00292