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 00012 #include "vexception.h" 00013 #include "vinstant.h" 00014 #include "vbufferedfilestream.h" 00015 #include "vtextiostream.h" 00016 #include "vbinaryiostream.h" 00017 00018 // VListNodeInfoCallback ----------------------------------------------------- 00019 00023 class VFSNodeListCallback : public VDirectoryIterationCallback { 00024 public: 00025 00026 VFSNodeListCallback(VFSNodeVector& nodeList) : VDirectoryIterationCallback(), mNodeList(nodeList) {} 00027 virtual ~VFSNodeListCallback() {} 00028 00029 virtual bool handleNextNode(const VFSNode& node); 00030 00031 private: 00032 00033 // Prevent copy construction and assignment. 00034 VFSNodeListCallback(const VFSNodeListCallback& other); 00035 VFSNodeListCallback& operator=(const VFSNodeListCallback& other); 00036 00037 VFSNodeVector& mNodeList; 00038 }; 00039 00040 bool VFSNodeListCallback::handleNextNode(const VFSNode& node) { 00041 mNodeList.push_back(node); 00042 return true; 00043 } 00044 00045 // VFSNodeNameCallback ----------------------------------------------------- 00046 00050 class VFSNodeNameCallback : public VDirectoryIterationCallback { 00051 public: 00052 00053 VFSNodeNameCallback(VStringVector& nameList) : VDirectoryIterationCallback(), mNameList(nameList) {} 00054 virtual ~VFSNodeNameCallback() {} 00055 00056 virtual bool handleNextNode(const VFSNode& node); 00057 00058 private: 00059 00060 // Prevent copy construction and assignment. 00061 VFSNodeNameCallback(const VFSNodeNameCallback& other); 00062 VFSNodeNameCallback& operator=(const VFSNodeNameCallback& other); 00063 00064 VStringVector& mNameList; 00065 }; 00066 00067 bool VFSNodeNameCallback::handleNextNode(const VFSNode& node) { 00068 VString nodeName; 00069 node.getName(nodeName); 00070 mNameList.push_back(nodeName); 00071 return true; 00072 } 00073 00074 // VFSNodeFindCallback ----------------------------------------------------- 00075 00080 class VFSNodeFindCallback : public VDirectoryIterationCallback { 00081 public: 00082 00083 VFSNodeFindCallback(const VString& nameToMatch); 00084 virtual ~VFSNodeFindCallback() {} 00085 00086 virtual bool handleNextNode(const VFSNode& node); 00087 00088 bool found() const { return mFound; } 00089 void getMatchedNode(VFSNode& node) const { node = mMatchedNode; } 00090 00091 private: 00092 00093 bool mFound; 00094 VString mNameToMatchLowerCase; 00095 VFSNode mMatchedNode; 00096 }; 00097 00098 VFSNodeFindCallback::VFSNodeFindCallback(const VString& nameToMatch) 00099 : VDirectoryIterationCallback() 00100 , mFound(false) 00101 , mNameToMatchLowerCase(nameToMatch) 00102 , mMatchedNode() 00103 { 00104 mNameToMatchLowerCase.toLowerCase(); 00105 } 00106 00107 bool VFSNodeFindCallback::handleNextNode(const VFSNode& node) { 00108 VString nodeNameLowerCase; 00109 node.getName(nodeNameLowerCase); 00110 nodeNameLowerCase.toLowerCase(); 00111 00112 if (nodeNameLowerCase == mNameToMatchLowerCase) { 00113 mFound = true; 00114 mMatchedNode = node; 00115 return false; // we found a match, so stop looking 00116 } 00117 00118 return true; 00119 } 00120 00121 // VFSNode ------------------------------------------------------------------- 00122 00123 // static 00124 VString VFSNode::normalizePath(const VString& path) { 00125 return VFSNode::_platform_normalizePath(path); 00126 } 00127 00128 // static 00129 VString VFSNode::denormalizePath(const VString& path) { 00130 return VFSNode::_platform_denormalizePath(path); 00131 } 00132 00133 // static 00134 VFSNode VFSNode::getKnownDirectoryNode(KnownDirectoryIdentifier id, const VString& companyName, const VString& appName) { 00135 return VFSNode::_platform_getKnownDirectoryNode(id, companyName, appName); 00136 } 00137 00138 // static 00139 VFSNode VFSNode::getCurrentWorkingDirectory() { 00140 return VFSNode::getKnownDirectoryNode(CURRENT_WORKING_DIRECTORY, VString::EMPTY(), VString::EMPTY()); 00141 } 00142 00143 // static 00144 VFSNode VFSNode::getExecutableDirectory() { 00145 return VFSNode::getKnownDirectoryNode(EXECUTABLE_DIRECTORY, VString::EMPTY(), VString::EMPTY()); 00146 } 00147 00148 // static 00149 VFSNode VFSNode::getExecutable() { 00150 return VFSNode::_platform_getExecutable(); 00151 } 00152 00153 static VInstantFormatter VFSNODE_SAFE_FILE_NAME_INSTANT_FORMATTER("yMMddHHmmssSSS"); 00154 00155 // static 00156 void VFSNode::safelyOverwriteFile(const VFSNode& target, Vs64 dataLength, VBinaryIOStream& dataStream) { 00157 bool success = true; 00158 VString errorMessage; 00159 00160 VString targetFileName = target.getName(); 00161 00162 VInstant now; 00163 VString temporaryFileName = now.getLocalString(VFSNODE_SAFE_FILE_NAME_INSTANT_FORMATTER) + "_tmp_" + targetFileName; 00164 00165 VFSNode directoryNode; 00166 target.getParentNode(directoryNode); 00167 VFSNode temporaryFileNode(directoryNode, temporaryFileName); 00168 00169 // Create and write to the temp file within a scope block to ensure file is closed when scope is exited. 00170 /* stream scope */ { 00171 VBufferedFileStream tempFileStream(temporaryFileNode); 00172 VBinaryIOStream tempOutputStream(tempFileStream); 00173 00174 try { 00175 tempFileStream.openWrite(); 00176 } catch (const VException& ex) { 00177 success = false; 00178 errorMessage = VSTRING_FORMAT("Unable to open temporary file '%s': %s", target.getPath().chars(), ex.what()); 00179 } 00180 00181 if (success) { 00182 try { 00183 VStream::streamCopy(dataStream, tempOutputStream, dataLength); 00184 tempOutputStream.flush(); 00185 } catch (const VException& ex) { 00186 success = false; 00187 errorMessage = VSTRING_FORMAT("Unable to write to temporary file '%s': %s", target.getPath().chars(), ex.what()); 00188 } 00189 } 00190 } 00191 00192 /* 00193 If we succeeded, delete the original file and rename the temporary file to replace it. 00194 If we failed, delete the temporary file. 00195 Do this itself in separate phases, so that if the delete/rename fails, we still delete the temporary file. 00196 */ 00197 // 1. Remove target. (It might not exist yet.) 00198 if (success && target.exists()) { 00199 if (! target.rm()) { 00200 success = false; 00201 errorMessage = VSTRING_FORMAT("Unable to remove target file '%s'.", target.getPath().chars()); 00202 } 00203 } 00204 00205 // 2. Rename temporary to target. 00206 if (success) { 00207 try { 00208 temporaryFileNode.renameToNode(target); 00209 } catch (const VException& ex) { 00210 success = false; 00211 errorMessage = VSTRING_FORMAT("Failed renaming '%s' to '%s': %s", temporaryFileNode.getPath().chars(), target.getPath().chars(), ex.what()); 00212 } 00213 } 00214 00215 // 3. Remove temporary if unsuccessful. 00216 if (! success) { 00217 if (! temporaryFileNode.rm()) { 00218 errorMessage += VSTRING_FORMAT(" Removal of temporary file '%s' failed.", temporaryFileNode.getPath().chars()); 00219 } 00220 } 00221 00222 // If we failed, throw an exception with the error message we built wherever we encountered errors. 00223 if (! success) { 00224 throw VException(errorMessage); 00225 } 00226 } 00227 00228 VFSNode::VFSNode() 00229 : mPath() 00230 { 00231 } 00232 00233 VFSNode::VFSNode(const VFSNode& node) 00234 : mPath(node.mPath) 00235 { 00236 } 00237 00238 VFSNode::VFSNode(const VString& path) 00239 : mPath(path) 00240 { 00241 00242 if (path.isEmpty()) { 00243 mPath = "."; 00244 } 00245 } 00246 00247 VFSNode::VFSNode(const VFSNode& directory, const VString& childName) 00248 : mPath() 00249 { 00250 directory.getChildNode(childName, *this); 00251 } 00252 00253 VFSNode& VFSNode::operator=(const VFSNode& other) { 00254 mPath = other.mPath; 00255 return *this; 00256 } 00257 00258 void VFSNode::setPath(const VString& path) { 00259 if (path.isEmpty()) 00260 mPath = "."; 00261 else 00262 mPath = path; 00263 } 00264 00265 void VFSNode::getPath(VString& path) const { 00266 path = mPath; 00267 } 00268 00269 const VString& VFSNode::getPath() const { 00270 return mPath; 00271 } 00272 00273 void VFSNode::getName(VString& name) const { 00274 // The following works even if lastIndexOf returns -1 "not found", 00275 // because we add 1 to get the correct startIndex parameter. 00276 name.copyFromBuffer(mPath.chars(), mPath.lastIndexOf('/') + 1, mPath.length()); 00277 } 00278 00279 VString VFSNode::getName() const { 00280 VString name; 00281 this->getName(name); 00282 return name; 00283 } 00284 00285 void VFSNode::setName(const VString& name) { 00286 VString parentPath; 00287 this->getParentPath(parentPath); 00288 00289 VString newPath(VSTRING_ARGS("%s/%s", parentPath.chars(), name.chars())); 00290 this->setPath(newPath); 00291 } 00292 00293 void VFSNode::getParentPath(VString& parentPath) const { 00294 parentPath.copyFromBuffer(mPath.chars(), 0, mPath.lastIndexOf('/')); 00295 } 00296 00297 void VFSNode::getParentNode(VFSNode& parent) const { 00298 VString parentPath; 00299 00300 this->getParentPath(parentPath); 00301 parent.setPath(parentPath); 00302 } 00303 00304 void VFSNode::getChildPath(const VString& childName, VString& childPath) const { 00305 childPath.format("%s/%s", mPath.chars(), childName.chars()); 00306 } 00307 00308 void VFSNode::getChildNode(const VString& childName, VFSNode& child) const { 00309 VString childPath; 00310 00311 this->getChildPath(childName, childPath); 00312 child.setPath(childPath); 00313 } 00314 00315 void VFSNode::mkdirs() const { 00316 // If this directory already exists, we are done. 00317 if (this->exists()) 00318 return; 00319 00320 // Create the parent directory (and its parents etc.) if necessary. 00321 VFSNode parentNode; 00322 this->getParentNode(parentNode); 00323 00324 if (! parentNode.getPath().isEmpty()) // root or parent of supplied path must be assumed to exist 00325 parentNode.mkdirs(); 00326 00327 // Create this directory specifically. 00328 this->mkdir(); 00329 } 00330 00331 void VFSNode::mkdir() const { 00332 this->_platform_createDirectory(); 00333 } 00334 00335 bool VFSNode::rm() const { 00336 /* 00337 This could be optimized for Mac APIs which do a fast delete of 00338 the directory and its contents in one swipe. The following way 00339 is required on Unix file systems and is slower because we must 00340 delete a directory's contents before deleting it. 00341 */ 00342 if (! this->exists()) { 00343 return false; 00344 } 00345 00346 bool success = true; 00347 bool isDir = this->isDirectory(); 00348 00349 if (isDir) { 00350 success = this->rmDirContents(); 00351 } 00352 00353 if (success) { 00354 if (isDir) { 00355 success = this->_platform_removeDirectory(); 00356 } else { 00357 success = this->_platform_removeFile(); 00358 } 00359 } 00360 00361 return success; 00362 } 00363 00364 bool VFSNode::rmDirContents() const { 00365 bool allSucceeded = true; 00366 VFSNodeVector children; 00367 00368 this->list(children); 00369 00370 for (VSizeType i = 0; i < children.size(); ++i) { 00371 allSucceeded = allSucceeded && children[i].rm(); 00372 } 00373 00374 return allSucceeded; 00375 } 00376 00377 void VFSNode::renameToPath(const VString& newPath) const { 00378 this->_platform_renameNode(newPath); 00379 } 00380 00381 void VFSNode::renameToName(const VString& newName) const { 00382 VFSNode destinationNode; // not used 00383 this->renameToName(newName, destinationNode); 00384 } 00385 00386 void VFSNode::renameToName(const VString& newName, VFSNode& nodeToUpdate) const { 00387 VFSNode parentNode; 00388 this->getParentNode(parentNode); 00389 00390 VString newPath; 00391 parentNode.getChildPath(newName, newPath); 00392 00393 this->_platform_renameNode(newPath); 00394 00395 nodeToUpdate.setPath(newPath); // it IS allowed for nodeToUpdate to be this 00396 } 00397 00398 void VFSNode::renameToNode(const VFSNode& newNode) const { 00399 VString newPath; 00400 newNode.getPath(newPath); 00401 00402 this->_platform_renameNode(newPath); 00403 } 00404 00405 void VFSNode::list(VStringVector& children) const { 00406 VFSNodeNameCallback callback(children); 00407 this->_platform_directoryIterate(callback); 00408 } 00409 00410 void VFSNode::list(VFSNodeVector& children) const { 00411 VFSNodeListCallback callback(children); 00412 this->_platform_directoryIterate(callback); 00413 } 00414 00415 void VFSNode::iterate(VDirectoryIterationCallback& callback) const { 00416 this->_platform_directoryIterate(callback); 00417 } 00418 00419 bool VFSNode::find(const VString& name, VFSNode& node) const { 00420 VFSNodeFindCallback callback(name); 00421 this->_platform_directoryIterate(callback); 00422 00423 bool found = callback.found(); 00424 if (found) { 00425 callback.getMatchedNode(node); 00426 } 00427 00428 return found; 00429 } 00430 00431 void VFSNode::readAll(VString& s, bool includeLineEndings) { 00432 VBufferedFileStream fs(*this); 00433 fs.openReadOnly(); 00434 VTextIOStream in(fs); 00435 in.readAll(s, includeLineEndings); 00436 } 00437 00438 void VFSNode::readAll(VStringVector& lines) { 00439 VBufferedFileStream fs(*this); 00440 fs.openReadOnly(); 00441 VTextIOStream in(fs); 00442 in.readAll(lines); 00443 } 00444 00445 bool VFSNode::exists() const { 00446 VFSNodeInfo info; 00447 return this->_platform_getNodeInfo(info); // only the function result is needed 00448 } 00449 00450 // static 00451 VString VFSNode::readTextFile(const VString& path, bool includeLineEndings) { 00452 VFSNode node(path); 00453 VString text; 00454 node.readAll(text, includeLineEndings); 00455 return text; 00456 } 00457 00458 // static 00459 void VFSNode::readTextFile(const VString& path, VStringVector& lines) { 00460 VFSNode node(path); 00461 node.readAll(lines); 00462 } 00463 00464 VInstant VFSNode::creationDate() const { 00465 VFSNodeInfo info; 00466 bool nodeExists = this->_platform_getNodeInfo(info); 00467 00468 if (!nodeExists) 00469 throw VException(VSystemError(info.mErrNo), VSTRING_FORMAT("VFSNode::creationDate failed for '%s'.", mPath.chars())); 00470 00471 return VInstant::instantFromRawValue(info.mCreationDate); 00472 } 00473 00474 VInstant VFSNode::modificationDate() const { 00475 VFSNodeInfo info; 00476 bool nodeExists = this->_platform_getNodeInfo(info); 00477 00478 if (!nodeExists) 00479 throw VException(VSystemError(info.mErrNo), VSTRING_FORMAT("VFSNode::modificationDate failed for '%s'.", mPath.chars())); 00480 00481 return VInstant::instantFromRawValue(info.mModificationDate); 00482 } 00483 00484 VFSize VFSNode::size() const { 00485 VFSNodeInfo info; 00486 bool nodeExists = this->_platform_getNodeInfo(info); 00487 00488 if (!nodeExists) 00489 throw VException(VSystemError(info.mErrNo), VSTRING_FORMAT("VFSNode::size failed for '%s'.", mPath.chars())); 00490 00491 return info.mFileSize; 00492 } 00493 00494 bool VFSNode::isFile() const { 00495 VFSNodeInfo info; 00496 bool nodeExists = this->_platform_getNodeInfo(info); 00497 00498 return nodeExists && info.mIsFile; 00499 } 00500 00501 bool VFSNode::isDirectory() const { 00502 VFSNodeInfo info; 00503 bool nodeExists = this->_platform_getNodeInfo(info); 00504 00505 return nodeExists && info.mIsDirectory; 00506 } 00507 00508 // VFSNodeInfo --------------------------------------------------------------- 00509 00510 VFSNodeInfo::VFSNodeInfo() 00511 : mCreationDate(0) 00512 , mModificationDate(0) 00513 , mFileSize(0) 00514 , mIsFile(false) 00515 , mIsDirectory(false) 00516 , mErrNo(0) 00517 { 00518 } 00519