IStream *Locate_File(Parser *p, shared_ptr<SceneData>& sd, const UCS2String& filename, unsigned int stype, UCS2String& buffer, bool err_flag) { UCS2String fn(filename); UCS2String foundfile(sd->FindFile(p->GetPOVMSContext(), fn, stype)); if(foundfile.empty() == true) { if(err_flag == true) p->PossibleError("Cannot find file '%s', even after trying to append file type extension.", UCS2toASCIIString(fn).c_str()); return NULL; } if(fn.find('.') == UCS2String::npos) { // the passed-in filename didn't have an extension, but a file has been found, // which means one of the appended extensions worked. we need to work out which // one and append it to the original filename so we can store it in the cache // (since it's that name that the cache search routine looks for). UCS2String ext = GetFileExtension(Path(foundfile)); if (ext.size() != 0) fn += ext; } // ReadFile will store both fn and foundfile in the cache for next time round IStream *result(sd->ReadFile(p->GetPOVMSContext(), fn, foundfile.c_str(), stype)); if((result == NULL) && (err_flag == true)) p->PossibleError("Cannot open file '%s'.", UCS2toASCIIString(foundfile).c_str()); buffer = foundfile; return result; }
bool vfeParsePathString (const UCS2String& path, UCS2String& volume, vector<UCS2String>& components, UCS2String& filename) { UCS2String q; if(path.empty() == true) return true; if(path[0] == '/') volume = '/'; for(size_t i = 0; i < path.length(); ++i) { if(path[i] == '/') { if(q.empty() == false) components.push_back(q); q.clear(); } else q += path[i]; } filename = q; return true; }
UCS2String UTF8toUCS2String(const UTF8String& s) { UCS2String result; UCS4 c; int len = 0; // Compute the encoded length by simply counting all non-continuation bytes. // In case of malformed sequences we might get the number wrong, but this is just an estimate // used to pre-allocate memory. for (UTF8String::const_iterator i = s.begin(); i != s.end(); ++i) if (((*i) & 0xC0) != 0x80) ++len; result.reserve(len); for(UTF8String::const_iterator i = s.begin(); i != s.end(); ) { c = DecodeUTF8Character(i); if ((c > 0xFFFFu) || (c == 0x0000u)) // Not valid in UCS2, or we can't deal with it; substitute with a replacement character. c = 0xFFFDu; result.push_back(c); } return result; }
UCS2String TemporaryFile::SuggestName() { POV_ASSERT(!gTempPath.empty()); // TODO FIXME - This allows only one temporary file per process! // TODO FIXME - Avoid converting back and forth between UCS-2 and system-specific encoding. char str [POV_FILENAME_BUFFER_CHARS + 1] = ""; std::snprintf(str, POV_FILENAME_BUFFER_CHARS + 1, "%spov%d", UCS2toSysString(gTempPath).c_str(), int(getpid())); return SysToUCS2String(str); }
UCS2 *Parser::Convert_UTF8_To_UCS2(const unsigned char *text_array, int *char_array_size) { POV_PARSER_ASSERT(text_array); POV_PARSER_ASSERT(char_array_size); UCS2String s = UTF8toUCS2String(UTF8String(reinterpret_cast<const char*>(text_array))); UCS2String::size_type len = s.length(); *char_array_size = len; if (len == 0) return nullptr; size_t size = (len+1)*sizeof(UCS2); UCS2 *char_array = reinterpret_cast<UCS2 *>(POV_MALLOC(size, "Character Array")); if (char_array == nullptr) throw POV_EXCEPTION_CODE(kOutOfMemoryErr); memcpy(char_array, s.c_str(), size); return char_array; }
std::string UCS2toASCIIString(const UCS2String& s) { std::string r; for(std::size_t i = 0; i < s.length(); i++) { if(s[i] >= 256) r += ' '; // TODO - according to most encoding conventions, this should be '?' else r += (char)(s[i]); } return r; }
/* TODO FIXME - this is the correct code but it has a bug. The code above is just a hack [trf] IStream *BackendSceneData::ReadFile(POVMSContext ctx, const UCS2String& filename, unsigned int stype) { UCS2String scenefile(filename); UCS2String localfile; UCS2String fileurl; // see if the file is available locally FilenameToFilenameMap::iterator ilocalfile(scene2LocalFiles.find(scenefile)); // if available locally, open it end return if(ilocalfile != scene2LocalFiles.end()) return NewIStream(ilocalfile->second.c_str(), stype); // see if the file is available as temporary file FilenameToFilenameMap::iterator itempfile(scene2TempFiles.find(scenefile)); // if available as temporary file, open it end return if(itempfile != scene2TempFiles.end()) return NewIStream(itempfile->second.c_str(), stype); // otherwise, request the file RenderBackend::SendReadFile(ctx, sceneId, frontendAddress, scenefile, localfile, fileurl); // if it is available locally, add it to the map and then open it if(localfile.length() > 0) { scene2LocalFiles[scenefile] = localfile; local2SceneFiles[localfile] = scenefile; return NewIStream(localfile.c_str(), stype); } // if it is available remotely ... if(fileurl.length() > 0) { // create a temporary file UCS2String tempname = POV_PLATFORM_BASE.CreateTemporaryFile(); OStream *tempfile = NewOStream(tempname.c_str(), stype, false); if(tempfile == NULL) { POV_PLATFORM_BASE.DeleteTemporaryFile(tempname); throw POV_EXCEPTION_CODE(kCannotOpenFileErr); } // download the file from the URL // TODO - handle referrer if(POV_PLATFORM_BASE.ReadFileFromURL(tempfile, fileurl) == false) { delete tempfile; POV_PLATFORM_BASE.DeleteTemporaryFile(tempname); throw POV_EXCEPTION_CODE(kNetworkConnectionErr); } delete tempfile; // add the temporary file to the map scene2TempFiles[scenefile] = tempname; temp2SceneFiles[tempname] = scenefile; return NewIStream(tempname.c_str(), stype); } // file not found return NULL; } */ OStream *BackendSceneData::CreateFile(POVMSContext ctx, const UCS2String& filename, unsigned int stype, bool append) { UCS2String scenefile(filename); #ifdef USE_SCENE_FILE_MAPPING // see if the file is available as temporary file FilenameToFilenameMap::iterator itempfile(scene2TempFiles.find(scenefile)); // if available as temporary file, open it end return if(itempfile != scene2TempFiles.end()) return NewOStream(itempfile->second.c_str(), stype, append); // otherwise, create a temporary file ... UCS2String tempname = POV_PLATFORM_BASE.CreateTemporaryFile(); OStream *tempfile = NewOStream(tempname.c_str(), stype, append); // failed to open file if(tempfile == NULL) return NULL; // add the temporary file to the map scene2TempFiles[scenefile] = tempname; temp2SceneFiles[tempname] = scenefile; #else // this is a workaround for the incomplete scene temp file support // until someone has time to finish it. OStream *tempfile = NewOStream(scenefile.c_str(), stype, append); if (tempfile == NULL) return NULL; #endif // let the frontend know that a new file was created RenderBackend::SendCreatedFile(ctx, sceneId, frontendAddress, scenefile); return tempfile; }
// Gets the next non-status message (meaning generic or console messages) // from the aforementioned queues; whichever is the earliest. Returns false // if there is no message to fetch, otherwise will set the message type, // filename, line, and column parameters supplied. If the message retrieved // did not contain this information, the relevent entry is either set to 0 // (line and column) or the empty string (filename). The filename parameter // is a UCS2String. bool vfeSession::GetNextNonStatusMessage (MessageType &Type, string& Message, UCS2String& File, int& Line, int& Col) { POV_LONG mqTime = 0x7fffffffffffffffLL ; POV_LONG cqTime = 0x7fffffffffffffffLL ; boost::mutex::scoped_lock lock(m_MessageMutex); if (m_MessageQueue.empty() && m_ConsoleQueue.empty()) return (false); if (m_MessageQueue.empty() == false) mqTime = m_MessageQueue.front().m_TimeStamp ; if (m_ConsoleQueue.empty() == false) cqTime = m_ConsoleQueue.front().m_TimeStamp ; POV_LONG oldest = min (cqTime, mqTime); // if equal we give preference to the console queue if (oldest == cqTime) { MessageBase msg = m_ConsoleQueue.front(); m_ConsoleQueue.pop(); Type = msg.m_Type; Message = msg.m_Message; File.clear(); Line = 0; Col = 0; } else { GenericMessage msg = m_MessageQueue.front(); m_MessageQueue.pop(); Type = msg.m_Type; Message = msg.m_Message; File = msg.m_Filename; Line = msg.m_Line; Col = msg.m_Col; } return (true); }
///////////////////////////////////////////////////////////////////////// // case-sensitive string comparison bool vfeUnixSession::StrCompare (const UCS2String& lhs, const UCS2String& rhs) const { return lhs.compare(rhs); }
bool vfeParsePathString (const UCS2String& path, UCS2String& volume, vector<UCS2String>& components, UCS2String& filename) { char str [MAX_PATH * 4] ; volume.clear() ; filename.clear() ; components.clear() ; if (path.empty() == true) return (true); if (path.size () >= sizeof (str)) return (false) ; strcpy (str, UCS2toASCIIString (path).c_str ()) ; char lastch = str[strlen(str) - 1]; // now determine if it's a network or drive path. // (we could use the shlwapi functions here but I'd rather avoid the shlwapi.dll dependency). char *p2 = str ; char *p3 ; if ((strlen (str) > 1) && ((str [1] == ':') || (str[0] == '\\' && str[1] == '\\') || (str[0] == '/' && str[1] == '/'))) { if (str [1] == ':') { // if it's a drive reference the first character must be in range 'a' - 'z' if (!isalpha (str[0])) return (false) ; // currently we don't support relative paths if a volume is specified if ((str [2] != '\\') && (str [2] != '/')) return (false) ; volume = ASCIItoUCS2String (string (str).substr (0, 3).c_str()) ; p2 += 3 ; } else { // it's a UNC path ... look for the next separator p2 = strchr (str + 2, '\\'); p3 = strchr (str + 2, '/'); if ((p3 != NULL) && ((p2 == NULL) || (p2-str) > (p3-str))) p2 = p3; if (p2 == NULL) { // no separator; technically this is valid, but it's a relative reference // and as above we don't currently support this. return (false) ; } volume = ASCIItoUCS2String (string (str).substr (0, (size_t) (++p2 - str)).c_str()) ; } } else if ((str [0] == '\\') || (str [0] == '/')) { // it's a path relative to the root of the current drive. // we will use '\' as the volume name. // for volume-relative paths we also accept '/' as a path separator volume = ASCIItoUCS2String("\\"); p2++; } // p2 now points at the start of any path or file components // the first call to strtok will skip over any extra separators // at the start of the path for (char *p1 = strtok (p2, "\\/"); p1 != NULL; p1 = strtok (NULL, "/\\")) { if (*p1 == '\0') continue; if (p1[0] == '.' && p1[1] == '\0') continue; if (p1[0] == '.' && p1[1] == '.' && p1[2] == '\0') { // it's a relative directory reference ... see if we can pop a // path from components; if not we leave it in there since it // is permitted to refer to a directory above the CWD if ((components.empty() == false) && (components.back() != ASCIItoUCS2String(".."))) { components.pop_back(); continue; } } components.push_back (ASCIItoUCS2String (p1)) ; } // the filename, if present, will be the last entry in components. // we first check the last character of the supplied path to see // if it's a path separator char; if it is there's no filename. if (lastch == '\\' || lastch == '/') return true; if (components.empty() == false) { filename = components.back(); components.pop_back(); } return true ; }
UCS2String BackendSceneData::FindFile(POVMSContext ctx, const UCS2String& filename, unsigned int stype) { vector<UCS2String> filenames; UCS2String foundfile; bool tryExactFirst; // if filename extension, matches one of the standard ones, try the exact name first // (otherwise, try it last) UCS2String::size_type pos = filename.find_last_of('.'); tryExactFirst = false; if (pos != UCS2String::npos) { for (size_t i = 0; i < POV_FILE_EXTENSIONS_PER_TYPE; i++) { if ( ( strlen(gPOV_File_Extensions[stype].ext[i]) > 0 ) && ( filename.compare(pos,filename.length()-pos, ASCIItoUCS2String(gPOV_File_Extensions[stype].ext[i])) == 0 ) ) { // match tryExactFirst = true; break; } } } // build list of files to search for if (tryExactFirst) filenames.push_back(filename); // add filename with extension variants to list of files to search for for (size_t i = 0; i < POV_FILE_EXTENSIONS_PER_TYPE; i++) { if (strlen(gPOV_File_Extensions[stype].ext[i]) > 0) { UCS2String fn(filename); fn += ASCIItoUCS2String(gPOV_File_Extensions[stype].ext[i]); filenames.push_back(fn); } } if (!tryExactFirst) filenames.push_back(filename); #ifdef USE_SCENE_FILE_MAPPING // see if the file is available locally for(vector<UCS2String>::const_iterator i(filenames.begin()); i != filenames.end(); i++) { FilenameToFilenameMap::iterator ilocalfile(scene2LocalFiles.find(*i)); if(ilocalfile != scene2LocalFiles.end()) return *i; } // see if the file is available as temporary file for(vector<UCS2String>::const_iterator i(filenames.begin()); i != filenames.end(); i++) { FilenameToFilenameMap::iterator itempfile(scene2TempFiles.find(*i)); if(itempfile != scene2TempFiles.end()) return *i; } #endif // otherwise, request to find the file RenderBackend::SendFindFile(ctx, sceneId, frontendAddress, filenames, foundfile); return foundfile; }
IStream *BackendSceneData::ReadFile(POVMSContext ctx, const UCS2String& origname, const UCS2String& filename, unsigned int stype) { UCS2String scenefile(filename); UCS2String localfile; UCS2String fileurl; #ifdef USE_SCENE_FILE_MAPPING // see if the file is available locally FilenameToFilenameMap::iterator ilocalfile(scene2LocalFiles.find(scenefile)); // if available locally, open it end return if(ilocalfile != scene2LocalFiles.end()) return NewIStream(ilocalfile->second.c_str(), stype); // now try the original name as given in the scene if((ilocalfile = scene2LocalFiles.find(origname)) != scene2LocalFiles.end()) return NewIStream(ilocalfile->second.c_str(), stype); // see if the file is available as temporary file FilenameToFilenameMap::iterator itempfile(scene2TempFiles.find(scenefile)); // if available as temporary file, open it end return if(itempfile != scene2TempFiles.end()) return NewIStream(itempfile->second.c_str(), stype); // otherwise, request the file RenderBackend::SendReadFile(ctx, sceneId, frontendAddress, scenefile, localfile, fileurl); // if it is available locally, add it to the map and then open it if(localfile.length() > 0) { scene2LocalFiles[scenefile] = localfile; local2SceneFiles[localfile] = scenefile; // yes this is a hack scene2LocalFiles[origname] = localfile; return NewIStream(localfile.c_str(), stype); } // if it is available remotely ... if(fileurl.length() > 0) { // create a temporary file UCS2String tempname = POV_PLATFORM_BASE.CreateTemporaryFile(); OStream *tempfile = NewOStream(tempname.c_str(), stype, false); if(tempfile == NULL) { POV_PLATFORM_BASE.DeleteTemporaryFile(tempname); throw POV_EXCEPTION_CODE(kCannotOpenFileErr); } // download the file from the URL // TODO - handle referrer if(POV_PLATFORM_BASE.ReadFileFromURL(tempfile, fileurl) == false) { delete tempfile; POV_PLATFORM_BASE.DeleteTemporaryFile(tempname); throw POV_EXCEPTION_CODE(kNetworkConnectionErr); } delete tempfile; // add the temporary file to the map scene2TempFiles[scenefile] = tempname; temp2SceneFiles[tempname] = scenefile; return NewIStream(tempname.c_str(), stype); } // file not found return NULL; #else return NewIStream(filename.c_str(), stype); #endif }
bool Path::ParsePathString (UCS2String& volume, vector<UCS2String>& dirnames, UCS2String& filename, const UCS2String& path) { UCS2String stash; // Unless noted otherwise, all components are considered empty. volume.clear(); dirnames.clear(); filename.clear(); // Empty strings are considered valid path names, too. if (path.empty() == true) return true; UCS2String::const_iterator i = path.begin(); // Check for a volume identifier: // - If the second character is a colon, we presume the first two characters to identify the drive. In that case // we'll also check whether the following character is a path separator, indicating an absolute path on that // drive, in which case we'll also include a trailing separator to the drive letter. // - If the path starts with two identical path separator characters, we presume the string to be a UNC path, in // which case we set the volume identifier to the network share, including two leading and a trailing separator // characters. // - Otherwise, if the first character is a path separator, this indicates an absolute path on the current drive, // in which case we set the volume identifier to a single path separator character. // - In any other case, we presume the string to be a relative path, and set the volume identifier to an empty // string. if ((*i == POV_PATH_SEPARATOR) || (*i == POV_PATH_SEPARATOR_2)) { // String starts with a path separator; may be an absolute path on the current drive or a UNC path. // Stash the separator (use the canonical one, not the one actually used). stash += POV_PATH_SEPARATOR; ++i; if ((i != path.end()) && (*i == stash[0])) { // The second character is also an (identical) separator character, indicating a UNC path. // Stash another path separators (use the canonical one, not the one actually used). stash += POV_PATH_SEPARATOR; ++i; // Stash everything that follows, up to the next path separator. for (; (i != path.end()) && (*i != POV_PATH_SEPARATOR) && (*i != POV_PATH_SEPARATOR_2); ++i) stash += *i; // Currently, we don't support bare UNC share names without trailing separator character, // even though allegedly they are technically valid. if (i == path.end()) return false; // Stash another path separator (use the canonical one, not the one actually used) // to indicate an absolute path. stash += POV_PATH_SEPARATOR; ++i; } // If it's not a UNC path, at this point our stash contains a single path separator, // which is exactly what we intend to emit as the volume identifier in that case. // Emit whatever string we have stashed as the volume identifier. volume = stash; stash.clear(); } else if (isalpha (*i)) { // String starts with an ASCII letter; may be a relative path or a drive letter. // Stash the character, then go on to test what's next. stash += *i; ++i; if ((i != path.end()) && (*i == ':')) { // Yup, that's a drive letter. Add the colon to the stashed letter. stash += ':'; ++i; // Currently, we don't support relative paths if a volume is specified. if ((i == path.end()) || ((*i != POV_PATH_SEPARATOR) && (*i != POV_PATH_SEPARATOR_2))) return false; // Stash another path separator (use the canonical one, not the one actually used) // to indicate an absolute path. stash += POV_PATH_SEPARATOR; ++i; // Emit the stashed string as the volume identifier. volume = stash; stash.clear(); } // If it's not a drive letter, at this point we have only stashed the first letter, but // our index still points to the second one so the following algorithm will take care of it. } // Walk through the path string, stashing any non-separator characters. Whenever we hit a separator // character, emit the stashed characters (if any) as a directory name and clear the stash. // Also, as we go along, resolve the special directory names `.` and `..` if possible. // NB since we do not emit "empty" directory names, any sequence of consecutive separator // characters is effectively treated as a single separator character. for(; i != path.end(); ++i) { if ((*i == POV_PATH_SEPARATOR) || (*i == POV_PATH_SEPARATOR_2)) { if (!stash.empty() && !IsCurrentDir(stash)) { if (!dirnames.empty() && IsParentDir(stash)) dirnames.pop_back(); else dirnames.push_back (stash); stash.clear(); } } else stash += *i; } // Whatever is left in the stash is presumably the actual file name. // NB as a consequence of the algorithm chosen, any path name ending in a path separator // character will be emitted as a list of directories only, with the file name left empty. filename = stash; return true; }
/// Test for the special directory name denoting the parent directory (`..`). static inline bool IsParentDir(const UCS2String& s) { return (s.length() == 2) && (s[0] == '.') && (s[1] == '.'); }
/// Test for the special directory name denoting the current directory (`.`). static inline bool IsCurrentDir(const UCS2String& s) { return (s.length() == 1) && (s[0] == '.'); }
namespace Filesystem { //****************************************************************************** #if !POV_USE_DEFAULT_DELETEFILE bool DeleteFile(const UCS2String& fileName) { return (unlink(UCS2toSysString(fileName).c_str()) == 0); } #endif // POV_USE_DEFAULT_DELETEFILE //****************************************************************************** #if !POV_USE_DEFAULT_LARGEFILE #ifndef POVUNIX_LSEEK64 #define POVUNIX_LSEEK64(h,b,o) lseek(h,b,o) #endif using Offset = decltype(POVUNIX_LSEEK64(0,0,0)); static_assert( std::numeric_limits<Offset>::digits > 32, "Large files (> 2 GiB) not supported, limiting image size to approx. 100 Megapixels. Proceed at your own risk." ); struct LargeFile::Data final { int handle; Data() : handle(-1) {} }; LargeFile::LargeFile() : mpData(new Data) {} LargeFile::~LargeFile() { Close(); } bool LargeFile::CreateRW(const UCS2String& fileName) { mpData->handle = open(UCS2toSysString(fileName).c_str(), O_CREAT | O_RDWR | O_TRUNC, S_IRUSR | S_IWUSR); return (mpData->handle != -1); } bool LargeFile::Seek(std::int_least64_t offset) { if (mpData->handle == -1) return false; if ((offset < 0) || (offset > std::numeric_limits<Offset>::max())) return false; return (POVUNIX_LSEEK64(mpData->handle, Offset(offset), SEEK_SET) == offset); } std::size_t LargeFile::Read(void* data, std::size_t maxSize) { if (mpData->handle == -1) return false; return read(mpData->handle, data, int(maxSize)); } bool LargeFile::Write(const void* data, std::size_t size) { if (mpData->handle == -1) return false; return (write(mpData->handle, data, int(size)) == size); } void LargeFile::Close() { if (mpData->handle != -1) { close(mpData->handle); mpData->handle = -1; } } #endif // POV_USE_DEFAULT_LARGEFILE //****************************************************************************** #if !POV_USE_DEFAULT_TEMPORARYFILE static UCS2String gTempPath; void SetTempFilePath(const UCS2String& tempPath) { gTempPath = tempPath; } UCS2String TemporaryFile::SuggestName() { POV_ASSERT(!gTempPath.empty()); // TODO FIXME - This allows only one temporary file per process! // TODO FIXME - Avoid converting back and forth between UCS-2 and system-specific encoding. char str [POV_FILENAME_BUFFER_CHARS + 1] = ""; std::snprintf(str, POV_FILENAME_BUFFER_CHARS + 1, "%spov%d", UCS2toSysString(gTempPath).c_str(), int(getpid())); return SysToUCS2String(str); } #endif // POV_USE_DEFAULT_TEMPORARYFILE //****************************************************************************** }