/////////////////////////////////////////////////////////////////////////////// // Copyright (C) 2002-2016, Open Design Alliance (the "Alliance"). // All rights reserved. // // This software and its documentation and related materials are owned by // the Alliance. The software may only be incorporated into application // programs owned by members of the Alliance, subject to a signed // Membership Agreement and Supplemental Software License Agreement with the // Alliance. The structure and organization of this software are the valuable // trade secrets of the Alliance and its suppliers. The software is also // protected by copyright law and international treaty provisions. Application // programs incorporating this software must include the following statement // with their copyright notices: // // This application incorporates Teigha(R) software pursuant to a license // agreement with Open Design Alliance. // Teigha(R) Copyright (C) 2002-2016 by Open Design Alliance. // All rights reserved. // // By use of this software, its documentation or related materials, you // acknowledge and accept the above terms. /////////////////////////////////////////////////////////////////////////////// // MemFileStreamImpl.h : implementation for the OdMemFileStreamImpl class. // ////////////////////////////////////////////////////////////////////// #if !defined(_MEMORYFILESTREAMIMPL_H_INCLUDED_) #define _MEMORYFILESTREAMIMPL_H_INCLUDED_ #include "RxObjectImpl.h" #include "MemoryStream.h" #include "OdAlloc.h" #ifdef OD_HAVE_UNISTD_FILE #include #endif #ifdef OD_HAVE_WINDOWS_STORAGE_FILE #include #endif #ifdef OD_HAVE_WRL_FILE #include #include #endif //DD:EXPORT_ON /** \details */ namespace TmpFileHelper { #define TEMPFILE_PREFIX "tx$" /** \details Returns the temporary file name. */ static OdString getTempFile(OdDbBaseHostAppServices* pHostAppServices, const OdAnsiString& prefix = TEMPFILE_PREFIX) { OdString tmpDir = pHostAppServices->getTempPath(); #if defined(_WINRT) GUID result; if (CoCreateGuid(&result) < 0) return OdString::kEmpty; tmpDir = tmpDir.format(L"%ls%llx%x%x.tmp", tmpDir.c_str(), result.Data1, result.Data2, result.Data3); #elif defined(_WIN32) // Win 32 + 64 bit OdChar tmpFile[MAX_PATH]; ::GetTempFileName(tmpDir.c_str(), !prefix.isEmpty() ? OdString(prefix).c_str() : L"tmp", 0, tmpFile); if (odStrLen(tmpFile)) tmpDir = OdString(tmpFile, (int)odStrLen(tmpFile)); #else char tmpFile[4096]; // sufficient for Win, Linux, Mac tmpFile[0] = 0; OdAnsiCharArray dstBuf; int lenStr = tmpDir.getLength(); dstBuf.reserve(lenStr * 4); // UTF-8 can use up to 4 bytes per character OdCharMapper::unicodeToUtf8(tmpDir.c_str(), lenStr, dstBuf); strcat(tmpFile, (const char*)dstBuf.asArrayPtr()); strcat(tmpFile, !prefix.isEmpty() ? prefix.c_str() : "tmp"); #ifdef OD_HAVE_MKSTEMPS_FUNC strcat(tmpFile, "XXXXXX.tmp"); int fileDescriptor = mkstemps(tmpFile, 4); #else strcat(tmpFile, "XXXXXX"); int fileDescriptor = mkstemp(tmpFile); #endif if (fileDescriptor == -1) { tmpFile[0] = 0; } else close(fileDescriptor); // Convert Utf8 back to Unicode OdAnsiString ansiTmpFile(tmpFile); OdCharArray buf; OdCharMapper::utf8ToUnicode(ansiTmpFile.c_str(), ansiTmpFile.getLength(), buf); tmpDir = OdString(buf.getPtr(), buf.size() - 1); #endif return tmpDir; } } // namespace /* same as OdMemoryStreamImpl, except that it uses a much larger PageSize, and a filled page is swapped to an automatically created + associated temporary disk file; the page data is then deleted, and if accessed, swapped-in from file again before accessing; the PAGE's size should be at least 512 kB */ /** \details Declares the page data. */ class PAGE { public: PAGE* m_pNextPage; PAGE* m_pPrevPage; OdUInt64 m_nPageStartAddr; OdUInt8* m_data; bool m_modified; bool m_savedToFile; OdUInt64 m_nFilePos; }; /** \details */ class OdMemFileStreamDummyBase { }; /** \details This class defines the interface for paged-memory file I/O objects. */ template class OdMemFileStreamImpl : public TBase { friend class OdMemoryStream; OdUInt64 m_numPages; PAGE* m_pFirstPage; PAGE* m_pCurrPage; PAGE* m_pLastPage; /** \details Helper class to swap in/out a PAGE from/to disk file. */ class PageSwitcher { // the *singleton* data buffer OdArray > m_dataBuffer; PAGE* m_activePage; OdString m_sTmpFileName; OdStreamBufPtr m_pTmpFile; OdDbBaseHostAppServices* m_pHostAppServices; /** \details Releases the disk file. */ bool releaseDiskFile(bool deleteIt) { m_pTmpFile = NULL; if (deleteIt && !m_sTmpFileName.isEmpty()) remove(m_sTmpFileName); m_sTmpFileName.empty(); m_activePage = 0; return true; }; public: /** \details Constructor creates the empty page switcher. */ PageSwitcher() : m_activePage(NULL) { }; /** \details Destructor deletes the buffer for page data and releases the disk file. */ ~PageSwitcher() { setBufferSize(0); releaseDiskFile(true); }; void setAppServices(OdDbBaseHostAppServices* pHostAppServices) { m_pHostAppServices = pHostAppServices; } /** \details Sets the buffer size for storing page data. \param pageSize [in] page size. */ inline void setBufferSize(OdUInt32 pageSize) { m_dataBuffer.resize(pageSize); }; /** \details Returns the page data as an array of 8-bit codes. \param pPage [in] pointer to the page data. */ inline OdUInt8* dataBuffer(PAGE* pPage) { return m_dataBuffer.asArrayPtr();}; /** \details Returns the size of the page data. \param pPage [in] pointer to the page data. */ inline OdUInt32 dataSize(PAGE* pPage) { return m_dataBuffer.length(); }; /** \details Returns the current active page data. */ inline PAGE* activePage() const { return m_activePage; }; /** \details Makes the page data as active. \param pPage [in] pointer to the page data. */ inline void setActivePage(PAGE* pPage) { m_activePage = pPage; }; /** \details Checks whether the page data is current and active, and returns True if the page data is active or False if the page data is inactive. \param pPage [in] pointer to the page data. */ inline bool isActivePage(PAGE* pPage) const { return (pPage == m_activePage); }; /** \details Checks whether the page data is modified, and returns True if the page data is modified or False if the page data is not changed. \param pPage [in] pointer to the page data. */ inline bool isModified(PAGE* pPage) const { return pPage->m_modified; }; /** \details Marks the page data as modified. \param pPage [in] pointer to the page data. \param modified [in] True if the page data is modified, False if the page data is not changed. */ inline void setModified(PAGE* pPage, bool modified) { pPage->m_modified = modified; }; /** \details Checks whether the page data is saved, and returns True if the page data is saved or False if the page data is unsaved. \param pPage [in] pointer to the page data. */ inline bool isSaved(PAGE* pPage) const { return pPage->m_savedToFile; }; /** \details Marks the page data as saved. \param pPage [in] pointer to the page data. \param saved [in] True if the page data is saved, False if the page data is unsaved. */ inline void setSaved(PAGE* pPage, bool saved) { pPage->m_savedToFile = saved; }; /** \details Returns the file position of the page data. \param pPage [in] pointer to the page data. */ inline OdUInt64 filePosition(PAGE* pPage) const { return pPage->m_nFilePos; }; /** \details Sets the file position of the page data. \param pPage [in] pointer to the page data. \param seekPos [in] position in the file. */ inline void setFilePosition(PAGE* pPage, OdUInt64 seekPos) { pPage->m_nFilePos = seekPos; }; /** \details Initializes the page data. \param pPage [in] pointer to the page data. */ void initializePage(PAGE* pPage) { ODA_ASSERT(pPage); pPage->m_pPrevPage = 0; pPage->m_pNextPage = 0; pPage->m_data = dataBuffer(pPage); setModified(pPage, false); setSaved(pPage, false); setFilePosition(pPage, 0); }; /** \details Gets the temporary disk file as a stream. */ OdStreamBufPtr& diskFile() { if (!m_pTmpFile.isNull()) return m_pTmpFile; // determine temporary filename if (m_pHostAppServices) { m_sTmpFileName = TmpFileHelper::getTempFile(m_pHostAppServices); m_pTmpFile = odrxSystemServices()->createFile(m_sTmpFileName, (Oda::FileAccessMode)(Oda::kFileRead | Oda::kFileWrite), Oda::kShareDenyReadWrite, Oda::kCreateAlways); ODA_ASSERT(!m_pTmpFile.isNull()); return m_pTmpFile; } else throw OdError(eNullObjectPointer); }; /** \details Saves the specified page data in the disk file and marks it as saved. Returns True if saving is successful. \param pPage [in] pointer to the page data. */ bool savePage(PAGE* pPage) { if (pPage && isSaved(pPage) && !isModified(pPage)) // no need to save here return true; if (!pPage || diskFile().isNull()) return false; if (isSaved(pPage)) { diskFile()->seek(filePosition(pPage), OdDb::kSeekFromStart); } else { diskFile()->seek(0, OdDb::kSeekFromEnd); setFilePosition(pPage, diskFile()->tell()); } diskFile()->putBytes(dataBuffer(pPage), dataSize(pPage)); setModified(pPage, false); setSaved(pPage, true); return true; }; /** \details Loads the specified page data from the disk file and makes it active. Returns True if loading is successful. \param pPage [in] pointer to the page data. */ bool loadPage(PAGE* pPage) { if (!pPage) return false; if (isActivePage(pPage) && !isModified(pPage)) // no need to (re-)load here return true; if (isSaved(pPage) && !diskFile().isNull()) { diskFile()->seek(filePosition(pPage), OdDb::kSeekFromStart); diskFile()->getBytes(dataBuffer(pPage), dataSize(pPage)); } setModified(pPage, false); setActivePage(pPage); return true; }; /** \details Checks whether the page data can be ensured. \param pPage [in] pointer to the page data. */ bool ensurePage(PAGE* pPage) { ODA_ASSERT(pPage); return isActivePage(pPage) ? true : switchToPage(pPage); }; /** \details Switches to the page data. If the specified page data is not active, the method saves the current active page data and loads the specified page data. Returns True if the page data is loaded successfully or is active. \param pPage [in] pointer to the page data. */ bool switchToPage(PAGE* pPage) { ODA_ASSERT(pPage); if (isActivePage(pPage)) return true; savePage(activePage()); return loadPage(pPage); }; /** \details Rewinds the page data. \param pPage [in] pointer to the page data. */ bool rewind(PAGE* pPage) { ODA_ASSERT(pPage); // 'pPage' is loaded at next read/write access return isActivePage(pPage) ? true : savePage(activePage()); } /** \details Truncates the page data. \param pPage [in] pointer to the page data. */ bool truncate(PAGE* pPage) { ODA_ASSERT(pPage); bool wasSaved = isSaved(pPage); setModified(pPage, false); setSaved(pPage, false); if (!wasSaved || diskFile().isNull()) { return true; } diskFile()->seek(filePosition(pPage), OdDb::kSeekFromStart); diskFile()->truncate(); return true; }; }; // class PageSwitcher PageSwitcher m_pageSwitcher; /** \details Copies the file stream. */ OdMemFileStreamImpl& operator= (const OdMemFileStreamImpl&) {return *this;} protected: OdUInt64 m_nCurPos; OdUInt64 m_nEndPos; OdUInt32 m_nPageDataSize; /** \details Returns the page switcher. */ inline PageSwitcher& pageSwitcher() { return m_pageSwitcher; }; /** \details Returns the allocated memory size for all pages. */ inline OdUInt64 allocated() const { return m_numPages * m_nPageDataSize; } inline OdUInt64 curPageNo() const { return (m_nCurPos / m_nPageDataSize); } inline OdUInt32 posInCurPage() const { return OdUInt32(m_nCurPos % m_nPageDataSize); } inline OdUInt32 leftInCurPage() const { return (m_pCurrPage ? m_nPageDataSize - posInCurPage() : 0); } /** \details Adds new page data. */ void addPage(); /** \details Finds the next page data. */ void seekNextPage(bool bCreateNew = false); /** \details Finds the previous page data. */ inline void seekPrevPage() { if(m_pCurrPage->m_pPrevPage) { m_nCurPos -= posInCurPage(); m_nCurPos -= m_nPageDataSize; m_pCurrPage = m_pCurrPage->m_pPrevPage; pageSwitcher().switchToPage(m_pCurrPage); } else throw OdError(eEndOfFile); } inline OdUInt8* currPos() { return m_pCurrPage->m_data + posInCurPage(); } inline const OdUInt8* currPos() const { return m_pCurrPage->m_data + posInCurPage(); } public: OdMemFileStreamImpl(OdUInt32 nPageDataSize = 0x00200000) // 2MB : m_numPages(0) , m_pFirstPage(0) , m_pCurrPage(0) , m_pLastPage(0) , m_nCurPos(0) , m_nEndPos(0) , m_nPageDataSize(0) { setPageDataSize(nPageDataSize); setAppServices(0); } void setAppServices(OdDbBaseHostAppServices* pHostAppServices) { pageSwitcher().setAppServices(pHostAppServices); } /** \details Returns the page data size. */ OdUInt32 pageDataSize() const { return m_nPageDataSize; } /** \details Sets the page data size and buffer size for page switcher. \param nPageSize [in] page data size. */ void setPageDataSize(OdUInt32 nPageSize) { m_nPageDataSize = nPageSize; pageSwitcher().setBufferSize(nPageSize); } /** \details Reserves the specified number of bytes for the file memory stream object. */ void reserve(OdUInt64 nSize) { while(nSize > allocated()) addPage(); } /** \details Returns the length in bytes of the file associated with this stream object. */ OdUInt64 length() { return m_nEndPos; } /** \details Moves the current position of the file pointer for this stream object to the specified location. \param offset [in] number of bytes \param seekType [in] seek type */ OdUInt64 seek(OdInt64 offset, OdDb::FilerSeekType whence); /** \details Returns the current position of the file pointer for this stream object. */ OdUInt64 tell() { return m_nCurPos; } /** \details Checks whether the file pointer has reached the end of a file, and returns True if the current position for this stream object is at the end of file. */ bool isEof() { return (m_nCurPos>=m_nEndPos); } /** \details Returns the byte at the current position of the file pointer for this stream object and increments the pointer. */ OdUInt8 getByte(); /** \details Returns the specified number of bytes, starting at the current position of the file pointer for this stream object, and increments the pointer on the specified number of bytes. \param buffer [in] pointer to the character buffer \param nLen [in] number of bytes */ void getBytes(void* buffer, OdUInt32 nLen); /** \details Writes the byte to the current position of the file pointer for this stream object, and increments the pointer. \param val [in] byte to be wrote */ void putByte(OdUInt8 val); /** \details Writes the specified number of bytes, starting at the current position of the file pointer for this stream object, and increments the pointer on the specified number of bytes. \param buffer [in] pointer to the character buffer \param nLen [in] number of bytes */ void putBytes(const void* buffer, OdUInt32 nLen); //void copyDataTo(TBase* pDest, OdUInt32 nSrcStart = 0, OdUInt32 nSrcEnd = 0) //{ // TBase::copyDataTo(pDest, nSrcStart, nSrcEnd); //} /** \details Sets the current position of the file pointer for this stream object to zero. */ void rewind() { m_nCurPos = 0; m_pCurrPage = m_pFirstPage; if(m_pCurrPage) { pageSwitcher().rewind(m_pCurrPage); } } /** \details Sets the length of the file associated with this stream object to zero. */ void truncate() { if(m_pCurrPage) { pageSwitcher().truncate(m_pCurrPage); PAGE* pNext, *pCurr = m_pCurrPage->m_pNextPage; m_pCurrPage->m_pNextPage = 0; m_pLastPage = m_pCurrPage; m_nEndPos = m_nCurPos; while(pCurr) { pNext = pCurr->m_pNextPage; pCurr->m_data = 0; if (pageSwitcher().activePage() == pCurr) { pageSwitcher().setActivePage(0); } ::odrxFree(pCurr); --m_numPages; pCurr = pNext; } } } /** \details Destructor deletes stream object. */ ~OdMemFileStreamImpl(); }; //////////////////////////////////////////////////////// template OdMemFileStreamImpl::~OdMemFileStreamImpl() { PAGE* pNext, *pCurr = m_pFirstPage; while(pCurr) { pNext = pCurr->m_pNextPage; pCurr->m_data = 0; if (pageSwitcher().activePage() == pCurr) { int t=0; } ::odrxFree(pCurr); pCurr = pNext; } m_pFirstPage = 0; } template void OdMemFileStreamImpl::addPage() { PAGE* pPage = (PAGE*)::odrxAlloc(sizeof(PAGE)); if (!pPage) throw OdError(eOutOfMemory); pageSwitcher().initializePage(pPage); pPage->m_pPrevPage = m_pLastPage; if(m_pLastPage) { m_pLastPage->m_pNextPage = pPage; if(!m_pCurrPage) { m_pCurrPage = pPage; pageSwitcher().switchToPage(m_pCurrPage); } pPage->m_nPageStartAddr = pPage->m_pPrevPage->m_nPageStartAddr + m_nPageDataSize; } else { m_pFirstPage = m_pCurrPage = pPage; pPage->m_nPageStartAddr = 0; pageSwitcher().switchToPage(m_pCurrPage); } m_pLastPage = pPage; ++m_numPages; } template void OdMemFileStreamImpl::seekNextPage(bool bCreateNew) { if(bCreateNew) { if(!m_pCurrPage) { addPage(); return; } if(!m_pCurrPage->m_pNextPage) addPage(); } if(m_pCurrPage->m_pNextPage) { m_pCurrPage = m_pCurrPage->m_pNextPage; m_nCurPos += leftInCurPage(); pageSwitcher().switchToPage(m_pCurrPage); } else throw OdError(eEndOfFile); } template OdUInt64 OdMemFileStreamImpl::seek(OdInt64 offset, OdDb::FilerSeekType from) { OdUInt64 nNewPos; switch(from) { case OdDb::kSeekFromEnd: nNewPos = m_nEndPos + offset; break; case OdDb::kSeekFromCurrent: nNewPos = m_nCurPos + offset; break; case OdDb::kSeekFromStart: if(!offset) { rewind(); return 0; } nNewPos = offset; break; default: throw OdError(eInvalidInput); break; }; if(nNewPos != m_nCurPos) { if (nNewPos > m_nEndPos) throw OdError( eEndOfFile ); OdInt64 nFromEnd = m_nEndPos - nNewPos; bool bForward = false; bool bBack = false; if (m_pCurrPage) { if (nNewPos >= m_pCurrPage->m_nPageStartAddr) { bForward = true; } else { bBack = true; } } if (nFromEnd == 0) { m_pCurrPage = m_pLastPage; bForward = true; } else if (bForward) { if (nFromEnd < ((OdInt64)nNewPos - (OdInt64)m_pCurrPage->m_nPageStartAddr)) { m_pCurrPage = m_pLastPage; bForward = false; } } else if (bBack) { if (nNewPos < (m_pCurrPage->m_nPageStartAddr - nNewPos)) { m_pCurrPage = m_pFirstPage; bForward = true; } } else { if (nNewPos <= (OdUInt64)nFromEnd) { m_pCurrPage = m_pFirstPage; bForward = true; } else { m_pCurrPage = m_pLastPage; } } m_nCurPos = nNewPos; if (bForward) { nNewPos = nNewPos / m_nPageDataSize * m_nPageDataSize ; while(m_pCurrPage && m_pCurrPage->m_nPageStartAddr < nNewPos) { m_pCurrPage = m_pCurrPage->m_pNextPage; } } else { while(m_pCurrPage->m_nPageStartAddr > m_nCurPos) { m_pCurrPage = m_pCurrPage->m_pPrevPage; } } pageSwitcher().switchToPage(m_pCurrPage); } return m_nCurPos; } template OdUInt8 OdMemFileStreamImpl::getByte() { OdUInt8 ret; if(m_nCurPos < m_nEndPos) { pageSwitcher().ensurePage(m_pCurrPage); OdUInt32 nPosInPage = posInCurPage(); ret = m_pCurrPage->m_data[nPosInPage]; ++m_nCurPos; if ((nPosInPage + 1) == m_nPageDataSize) m_pCurrPage = m_pCurrPage->m_pNextPage; } else { throw OdError(eEndOfFile); } return ret; } template void OdMemFileStreamImpl::getBytes(void* buffer, OdUInt32 nLen) { if (nLen==0) return; OdUInt64 nNewPos = m_nCurPos + nLen; if(nNewPos <= m_nEndPos) { OdUInt8* pDest = (OdUInt8*)buffer; OdUInt32 nToCopy = odmin(leftInCurPage(), nLen); if(nToCopy) { pageSwitcher().ensurePage(m_pCurrPage); ::memcpy(pDest, currPos(), nToCopy); pDest += nToCopy; nLen -= nToCopy; } while(nLen) { seekNextPage(); nToCopy = odmin(m_nPageDataSize, nLen); ::memcpy(pDest, m_pCurrPage->m_data, nToCopy); pDest += nToCopy; nLen -= nToCopy; } m_nCurPos = nNewPos; if(m_nCurPos && (m_nCurPos % m_nPageDataSize)==0) m_pCurrPage = m_pCurrPage->m_pNextPage; } else { throw OdError(eEndOfFile); } } template void OdMemFileStreamImpl::putByte(OdUInt8 val) { if(!m_pCurrPage) { seekNextPage(true); } pageSwitcher().ensurePage(m_pCurrPage); pageSwitcher().setModified(m_pCurrPage, true); OdUInt64 nPosInPage = posInCurPage(); m_pCurrPage->m_data[nPosInPage] = val; ++m_nCurPos; m_nEndPos = odmax(m_nCurPos, m_nEndPos); if ((nPosInPage + 1) == m_nPageDataSize) { m_pCurrPage = m_pCurrPage->m_pNextPage; } } template void OdMemFileStreamImpl::putBytes(const void* buffer, OdUInt32 nLen) { if (nLen == 0) { return; } const OdUInt8* pSrc = (const OdUInt8*)buffer; OdUInt32 nToCopy = odmin(leftInCurPage(), nLen); if(nToCopy) { pageSwitcher().ensurePage(m_pCurrPage); pageSwitcher().setModified(m_pCurrPage, true); ::memcpy(currPos(), pSrc, nToCopy); pSrc += nToCopy; nLen -= nToCopy; } while(nLen) { seekNextPage(true); pageSwitcher().setModified(m_pCurrPage, true); nToCopy = odmin(m_nPageDataSize, nLen); ::memcpy(m_pCurrPage->m_data, pSrc, nToCopy); pSrc += nToCopy; nLen -= nToCopy; } m_nCurPos += nToCopy; if(m_nCurPos && (m_nCurPos % m_nPageDataSize)==0) { m_pCurrPage = m_pCurrPage->m_pNextPage; } m_nEndPos = odmax(m_nCurPos, m_nEndPos); } //DD:EXPORT_OFF #endif // !defined(_MEMORYFILESTREAMIMPL_H_INCLUDED_)