Vault  4.1
vfsnode_platform.cpp
Go to the documentation of this file.
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 

Copyright ©1997-2014 Trygve Isaacson. All rights reserved. This documentation was generated with Doxygen.