/* * Copyright (c) 2002 Frodo * Portions Copyright (c) by the authors of ffmpeg and xvid * Copyright (C) 2002-2018 Team Kodi * This file is part of Kodi - https://kodi.tv * * SPDX-License-Identifier: GPL-2.0-or-later * See LICENSES/README.md for more information. */ #include "File.h" #include "Directory.h" #include "DirectoryCache.h" #include "FileCache.h" #include "FileFactory.h" #include "IFile.h" #include "PasswordManager.h" #include "ServiceBroker.h" #include "application/ApplicationComponents.h" #include "application/ApplicationPowerHandling.h" #include "commons/Exception.h" #include "settings/AdvancedSettings.h" #include "settings/SettingsComponent.h" #include "utils/BitstreamStats.h" #include "utils/StringUtils.h" #include "utils/URIUtils.h" #include "utils/log.h" using namespace XFILE; ////////////////////////////////////////////////////////////////////// // Construction/Destruction ////////////////////////////////////////////////////////////////////// #ifndef __GNUC__ #pragma warning (disable:4244) #endif //********************************************************************************************* CFile::CFile() = default; //********************************************************************************************* CFile::~CFile() { Close(); } //********************************************************************************************* bool CFile::Copy(const std::string& strFileName, const std::string& strDest, XFILE::IFileCallback* pCallback, void* pContext) { const CURL pathToUrl(strFileName); const CURL pathToUrlDest(strDest); return Copy(pathToUrl, pathToUrlDest, pCallback, pContext); } bool CFile::Copy(const CURL& url2, const CURL& dest, XFILE::IFileCallback* pCallback, void* pContext) { CFile file; const std::string pathToUrl(dest.Get()); if (pathToUrl.empty()) return false; // special case for zips - ignore caching CURL url(url2); if (StringUtils::StartsWith(url.Get(), "zip://") || URIUtils::IsInAPK(url.Get())) url.SetOptions("?cache=no"); if (file.Open(url.Get(), READ_TRUNCATED | READ_CHUNKED)) { CFile newFile; if (URIUtils::IsHD(pathToUrl)) // create possible missing dirs { std::vector tokens; std::string strDirectory = URIUtils::GetDirectory(pathToUrl); URIUtils::RemoveSlashAtEnd(strDirectory); // for the test below if (!(strDirectory.size() == 2 && strDirectory[1] == ':')) { CURL url(strDirectory); std::string pathsep; #ifndef TARGET_POSIX pathsep = "\\"; #else pathsep = "/"; #endif // Try to use the recursive creation first, if it fails // it might not be implemented for that subsystem so let's // fall back to the old method in that case if (!CDirectory::Create(url)) { StringUtils::Tokenize(url.GetFileName(), tokens, pathsep); std::string strCurrPath; // Handle special if (!url.GetProtocol().empty()) { pathsep = "/"; strCurrPath += url.GetProtocol() + "://"; } // If the directory has a / at the beginning, don't forget it else if (strDirectory[0] == pathsep[0]) strCurrPath += pathsep; for (const std::string& iter : tokens) { strCurrPath += iter + pathsep; CDirectory::Create(strCurrPath); } } } } if (CFile::Exists(dest)) CFile::Delete(dest); if (!newFile.OpenForWrite(dest, true)) // overwrite always { file.Close(); return false; } int iBufferSize = DetermineChunkSize(file.GetChunkSize(), 128 * 1024); std::vector buffer(iBufferSize); ssize_t iRead, iWrite; unsigned long long llFileSize = file.GetLength(); unsigned long long llPos = 0; CStopWatch timer; timer.StartZero(); float start = 0.0f; auto& components = CServiceBroker::GetAppComponents(); const auto appPower = components.GetComponent(); while (true) { appPower->ResetScreenSaver(); iRead = file.Read(buffer.data(), buffer.size()); if (iRead == 0) break; else if (iRead < 0) { CLog::Log(LOGERROR, "{} - Failed read from file {}", __FUNCTION__, url.GetRedacted()); llFileSize = (uint64_t)-1; break; } /* write data and make sure we managed to write it all */ iWrite = 0; while(iWrite < iRead) { ssize_t iWrite2 = newFile.Write(buffer.data() + iWrite, iRead - iWrite); if(iWrite2 <=0) break; iWrite+=iWrite2; } if (iWrite != iRead) { CLog::Log(LOGERROR, "{} - Failed write to file {}", __FUNCTION__, dest.GetRedacted()); llFileSize = (uint64_t)-1; break; } llPos += iRead; // calculate the current and average speeds float end = timer.GetElapsedSeconds(); if (pCallback && end - start > 0.5f && end) { start = end; float averageSpeed = llPos / end; int ipercent = 0; if(llFileSize) ipercent = 100 * llPos / llFileSize; if(!pCallback->OnFileCallback(pContext, ipercent, averageSpeed)) { CLog::Log(LOGERROR, "{} - User aborted copy", __FUNCTION__); llFileSize = (uint64_t)-1; break; } } } /* close both files */ newFile.Close(); file.Close(); /* verify that we managed to completed the file */ if (llFileSize && llPos != llFileSize) { CFile::Delete(dest); return false; } return true; } return false; } //********************************************************************************************* bool CFile::CURLCreate(const std::string &url) { m_curl.Parse(url); return true; } bool CFile::CURLAddOption(XFILE::CURLOPTIONTYPE type, const char* name, const char * value) { switch (type){ case XFILE::CURL_OPTION_CREDENTIALS: { m_curl.SetUserName(name); m_curl.SetPassword(value); break; } case XFILE::CURL_OPTION_PROTOCOL: case XFILE::CURL_OPTION_HEADER: { m_curl.SetProtocolOption(name, value); break; } case XFILE::CURL_OPTION_OPTION: { m_curl.SetOption(name, value); break; } default: return false; } return true; } bool CFile::CURLOpen(unsigned int flags) { return Open(m_curl, flags); } bool CFile::Open(const std::string& strFileName, const unsigned int flags) { const CURL pathToUrl(strFileName); return Open(pathToUrl, flags); } bool CFile::Open(const CURL& file, const unsigned int flags) { if (m_pFile) { if ((flags & READ_REOPEN) == 0) { CLog::Log(LOGERROR, "File::Open - already open: {}", file.GetRedacted()); return false; } else { return m_pFile->ReOpen(URIUtils::SubstitutePath(file)); } } m_flags = flags; try { bool bPathInCache; CURL url(URIUtils::SubstitutePath(file)), url2(url); if (url2.IsProtocol("apk") || url2.IsProtocol("zip") ) url2.SetOptions(""); if (!g_directoryCache.FileExists(url2.Get(), bPathInCache) ) { if (bPathInCache) return false; } /* * There are 5 buffer modes available (configurable in as.xml) * 0) Buffer all internet filesystems (like 2 but additionally also ftp, webdav, etc.) * 1) Buffer all filesystems (including local) * 2) Only buffer true internet filesystems (streams) (http, etc.) * 3) No buffer * 4) Buffer all remote (non-local) filesystems */ if (!(m_flags & READ_NO_CACHE)) { const std::string pathToUrl(url.Get()); if (URIUtils::IsDVD(pathToUrl) || URIUtils::IsBluray(pathToUrl) || (m_flags & READ_AUDIO_VIDEO)) { const unsigned int iCacheBufferMode = CServiceBroker::GetSettingsComponent()->GetAdvancedSettings()->m_cacheBufferMode; if ((iCacheBufferMode == CACHE_BUFFER_MODE_INTERNET && URIUtils::IsInternetStream(pathToUrl, true)) || (iCacheBufferMode == CACHE_BUFFER_MODE_TRUE_INTERNET && URIUtils::IsInternetStream(pathToUrl, false)) || (iCacheBufferMode == CACHE_BUFFER_MODE_NETWORK && URIUtils::IsNetworkFilesystem(pathToUrl)) || (iCacheBufferMode == CACHE_BUFFER_MODE_ALL && (URIUtils::IsNetworkFilesystem(pathToUrl) || URIUtils::IsHD(pathToUrl)))) { m_flags |= READ_CACHED; } } if (m_flags & READ_CACHED) { m_pFile = std::make_unique(m_flags); if (!m_pFile) return false; return m_pFile->Open(url); } } m_pFile.reset(CFileFactory::CreateLoader(url)); if (!m_pFile) return false; CURL authUrl(url); if (CPasswordManager::GetInstance().IsURLSupported(authUrl) && authUrl.GetUserName().empty()) CPasswordManager::GetInstance().AuthenticateURL(authUrl); try { if (!m_pFile->Open(authUrl)) return false; } catch (CRedirectException *pRedirectEx) { // the file implementation decided this item should use a different implementation. // the exception will contain the new implementation. CLog::Log(LOGDEBUG, "File::Open - redirecting implementation for {}", file.GetRedacted()); if (pRedirectEx && pRedirectEx->m_pNewFileImp) { std::unique_ptr pNewUrl(pRedirectEx->m_pNewUrl); m_pFile.reset(pRedirectEx->m_pNewFileImp); delete pRedirectEx; if (pNewUrl) { CURL newAuthUrl(*pNewUrl); if (CPasswordManager::GetInstance().IsURLSupported(newAuthUrl) && newAuthUrl.GetUserName().empty()) CPasswordManager::GetInstance().AuthenticateURL(newAuthUrl); if (!m_pFile->Open(newAuthUrl)) return false; } else { if (!m_pFile->Open(authUrl)) return false; } } } catch (...) { CLog::Log(LOGERROR, "File::Open - unknown exception when opening {}", file.GetRedacted()); return false; } if (m_pFile->GetChunkSize() && !(m_flags & READ_CHUNKED)) { m_pBuffer = std::make_unique(0); m_pBuffer->Attach(m_pFile.get()); } if (m_flags & READ_BITRATE) { m_bitStreamStats = std::make_unique(); m_bitStreamStats->Start(); } return true; } XBMCCOMMONS_HANDLE_UNCHECKED catch (...) { CLog::Log(LOGERROR, "{} - Unhandled exception", __FUNCTION__); } CLog::Log(LOGERROR, "{} - Error opening {}", __FUNCTION__, file.GetRedacted()); return false; } bool CFile::OpenForWrite(const std::string& strFileName, bool bOverWrite) { const CURL pathToUrl(strFileName); return OpenForWrite(pathToUrl, bOverWrite); } bool CFile::OpenForWrite(const CURL& file, bool bOverWrite) { try { CURL url = URIUtils::SubstitutePath(file); CURL authUrl = url; if (CPasswordManager::GetInstance().IsURLSupported(authUrl) && authUrl.GetUserName().empty()) CPasswordManager::GetInstance().AuthenticateURL(authUrl); m_pFile.reset(CFileFactory::CreateLoader(url)); if (m_pFile && m_pFile->OpenForWrite(authUrl, bOverWrite)) { // add this file to our directory cache (if it's stored) g_directoryCache.AddFile(url.Get()); return true; } return false; } XBMCCOMMONS_HANDLE_UNCHECKED catch(...) { CLog::Log(LOGERROR, "{} - Unhandled exception opening {}", __FUNCTION__, file.GetRedacted()); } CLog::Log(LOGERROR, "{} - Error opening {}", __FUNCTION__, file.GetRedacted()); return false; } int CFile::DetermineChunkSize(const int srcChunkSize, const int reqChunkSize) { // Determine cache chunk size: if source chunk size is bigger than 1 // use source chunk size else use requested chunk size return (srcChunkSize > 1 ? srcChunkSize : reqChunkSize); } bool CFile::Exists(const std::string& strFileName, bool bUseCache /* = true */) { const CURL pathToUrl(strFileName); return Exists(pathToUrl, bUseCache); } bool CFile::Exists(const CURL& file, bool bUseCache /* = true */) { CURL url(URIUtils::SubstitutePath(file)); CURL authUrl = url; if (CPasswordManager::GetInstance().IsURLSupported(authUrl) && authUrl.GetUserName().empty()) CPasswordManager::GetInstance().AuthenticateURL(authUrl); try { if (bUseCache) { bool bPathInCache; if (g_directoryCache.FileExists(url.Get(), bPathInCache)) return true; if (bPathInCache) return false; } std::unique_ptr pFile(CFileFactory::CreateLoader(url)); if (!pFile) return false; return pFile->Exists(authUrl); } XBMCCOMMONS_HANDLE_UNCHECKED catch (CRedirectException *pRedirectEx) { // the file implementation decided this item should use a different implementation. // the exception will contain the new implementation and optional a redirected URL. CLog::Log(LOGDEBUG, "File::Exists - redirecting implementation for {}", file.GetRedacted()); if (pRedirectEx && pRedirectEx->m_pNewFileImp) { std::unique_ptr pImp(pRedirectEx->m_pNewFileImp); std::unique_ptr pNewUrl(pRedirectEx->m_pNewUrl); delete pRedirectEx; if (pImp) { if (pNewUrl) { if (bUseCache) { bool bPathInCache; if (g_directoryCache.FileExists(pNewUrl->Get(), bPathInCache)) return true; if (bPathInCache) return false; } CURL newAuthUrl = *pNewUrl; if (CPasswordManager::GetInstance().IsURLSupported(newAuthUrl) && newAuthUrl.GetUserName().empty()) CPasswordManager::GetInstance().AuthenticateURL(newAuthUrl); return pImp->Exists(newAuthUrl); } else { return pImp->Exists(authUrl); } } } } catch (...) { CLog::Log(LOGERROR, "{} - Unhandled exception", __FUNCTION__); } CLog::Log(LOGERROR, "{} - Error checking for {}", __FUNCTION__, file.GetRedacted()); return false; } int CFile::Stat(struct __stat64 *buffer) { if (!buffer) return -1; if (!m_pFile) { *buffer = {}; errno = ENOENT; return -1; } return m_pFile->Stat(buffer); } int CFile::Stat(const std::string& strFileName, struct __stat64* buffer) { const CURL pathToUrl(strFileName); return Stat(pathToUrl, buffer); } int CFile::Stat(const CURL& file, struct __stat64* buffer) { if (!buffer) return -1; CURL url(URIUtils::SubstitutePath(file)); CURL authUrl = url; if (CPasswordManager::GetInstance().IsURLSupported(authUrl) && authUrl.GetUserName().empty()) CPasswordManager::GetInstance().AuthenticateURL(authUrl); try { std::unique_ptr pFile(CFileFactory::CreateLoader(url)); if (!pFile) return -1; return pFile->Stat(authUrl, buffer); } XBMCCOMMONS_HANDLE_UNCHECKED catch (CRedirectException *pRedirectEx) { // the file implementation decided this item should use a different implementation. // the exception will contain the new implementation and optional a redirected URL. CLog::Log(LOGDEBUG, "File::Stat - redirecting implementation for {}", file.GetRedacted()); if (pRedirectEx && pRedirectEx->m_pNewFileImp) { std::unique_ptr pImp(pRedirectEx->m_pNewFileImp); std::unique_ptr pNewUrl(pRedirectEx->m_pNewUrl); delete pRedirectEx; if (pNewUrl) { if (pImp) { CURL newAuthUrl = *pNewUrl; if (CPasswordManager::GetInstance().IsURLSupported(newAuthUrl) && newAuthUrl.GetUserName().empty()) CPasswordManager::GetInstance().AuthenticateURL(newAuthUrl); if (!pImp->Stat(newAuthUrl, buffer)) { return 0; } } } else { if (pImp.get() && !pImp->Stat(authUrl, buffer)) { return 0; } } } } catch (...) { CLog::Log(LOGERROR, "{} - Unhandled exception", __FUNCTION__); } CLog::Log(LOGERROR, "{} - Error statting {}", __FUNCTION__, file.GetRedacted()); return -1; } ssize_t CFile::Read(void *lpBuf, size_t uiBufSize) { if (!m_pFile) return -1; if (lpBuf == NULL && uiBufSize != 0) return -1; if (uiBufSize > SSIZE_MAX) uiBufSize = SSIZE_MAX; if (uiBufSize == 0) { // "test" read with zero size // some VFSs don't handle correctly null buffer pointer // provide valid buffer pointer for them char dummy; return m_pFile->Read(&dummy, 0); } if(m_pBuffer) { if(m_flags & READ_TRUNCATED) { const ssize_t nBytes = m_pBuffer->sgetn( (char *)lpBuf, std::min((std::streamsize)uiBufSize, m_pBuffer->in_avail())); if (m_bitStreamStats && nBytes>0) m_bitStreamStats->AddSampleBytes(nBytes); return nBytes; } else { const ssize_t nBytes = m_pBuffer->sgetn((char*)lpBuf, uiBufSize); if (m_bitStreamStats && nBytes>0) m_bitStreamStats->AddSampleBytes(nBytes); return nBytes; } } try { if(m_flags & READ_TRUNCATED) { const ssize_t nBytes = m_pFile->Read(lpBuf, uiBufSize); if (m_bitStreamStats && nBytes>0) m_bitStreamStats->AddSampleBytes(nBytes); return nBytes; } else { ssize_t done = 0; while((uiBufSize-done) > 0) { const ssize_t curr = m_pFile->Read((char*)lpBuf+done, uiBufSize-done); if (curr <= 0) { if (curr < 0 && done == 0) return -1; break; } done+=curr; } if (m_bitStreamStats && done > 0) m_bitStreamStats->AddSampleBytes(done); return done; } } XBMCCOMMONS_HANDLE_UNCHECKED catch(...) { CLog::Log(LOGERROR, "{} - Unhandled exception", __FUNCTION__); return -1; } return 0; } //********************************************************************************************* void CFile::Close() { try { if (m_pFile) m_pFile->Close(); m_pBuffer.reset(); m_pFile.reset(); } XBMCCOMMONS_HANDLE_UNCHECKED catch (...) { CLog::Log(LOGERROR, "{} - Unhandled exception", __FUNCTION__); } } void CFile::Flush() { try { if (m_pFile) m_pFile->Flush(); } XBMCCOMMONS_HANDLE_UNCHECKED catch (...) { CLog::Log(LOGERROR, "{} - Unhandled exception", __FUNCTION__); } } //********************************************************************************************* int64_t CFile::Seek(int64_t iFilePosition, int iWhence) { if (!m_pFile) return -1; if (m_pBuffer) { if(iWhence == SEEK_CUR) return m_pBuffer->pubseekoff(iFilePosition, std::ios_base::cur); else if(iWhence == SEEK_END) return m_pBuffer->pubseekoff(iFilePosition, std::ios_base::end); else if(iWhence == SEEK_SET) return m_pBuffer->pubseekoff(iFilePosition, std::ios_base::beg); } try { return m_pFile->Seek(iFilePosition, iWhence); } XBMCCOMMONS_HANDLE_UNCHECKED catch (...) { CLog::Log(LOGERROR, "{} - Unhandled exception", __FUNCTION__); } return -1; } //********************************************************************************************* int CFile::Truncate(int64_t iSize) { if (!m_pFile) return -1; try { return m_pFile->Truncate(iSize); } XBMCCOMMONS_HANDLE_UNCHECKED catch (...) { CLog::Log(LOGERROR, "{} - Unhandled exception", __FUNCTION__); } return -1; } //********************************************************************************************* int64_t CFile::GetLength() { try { if (m_pFile) return m_pFile->GetLength(); return 0; } XBMCCOMMONS_HANDLE_UNCHECKED catch (...) { CLog::Log(LOGERROR, "{} - Unhandled exception", __FUNCTION__); } return 0; } //********************************************************************************************* int64_t CFile::GetPosition() const { if (!m_pFile) return -1; if (m_pBuffer) return m_pBuffer->pubseekoff(0, std::ios_base::cur); try { return m_pFile->GetPosition(); } XBMCCOMMONS_HANDLE_UNCHECKED catch (...) { CLog::Log(LOGERROR, "{} - Unhandled exception", __FUNCTION__); } return -1; } //********************************************************************************************* bool CFile::ReadString(char *szLine, int iLineLength) { if (!m_pFile || !szLine) return false; if (m_pBuffer) { typedef CFileStreamBuffer::traits_type traits; CFileStreamBuffer::int_type aByte = m_pBuffer->sgetc(); if(aByte == traits::eof()) return false; while(iLineLength>0) { aByte = m_pBuffer->sbumpc(); if(aByte == traits::eof()) break; if(aByte == traits::to_int_type('\n')) { if(m_pBuffer->sgetc() == traits::to_int_type('\r')) m_pBuffer->sbumpc(); break; } if(aByte == traits::to_int_type('\r')) { if(m_pBuffer->sgetc() == traits::to_int_type('\n')) m_pBuffer->sbumpc(); break; } *szLine = traits::to_char_type(aByte); szLine++; iLineLength--; } // if we have no space for terminating character we failed if(iLineLength==0) return false; *szLine = 0; return true; } try { return m_pFile->ReadString(szLine, iLineLength); } XBMCCOMMONS_HANDLE_UNCHECKED catch (...) { CLog::Log(LOGERROR, "{} - Unhandled exception", __FUNCTION__); } return false; } ssize_t CFile::Write(const void* lpBuf, size_t uiBufSize) { if (!m_pFile) return -1; if (lpBuf == NULL && uiBufSize != 0) return -1; try { if (uiBufSize == 0 && lpBuf == NULL) { // "test" write with zero size // some VFSs don't handle correctly null buffer pointer // provide valid buffer pointer for them const char dummyBuf = 0; return m_pFile->Write(&dummyBuf, 0); } return m_pFile->Write(lpBuf, uiBufSize); } XBMCCOMMONS_HANDLE_UNCHECKED catch (...) { CLog::Log(LOGERROR, "{} - Unhandled exception", __FUNCTION__); } return -1; } bool CFile::Delete(const std::string& strFileName) { const CURL pathToUrl(strFileName); return Delete(pathToUrl); } bool CFile::Delete(const CURL& file) { try { CURL url(URIUtils::SubstitutePath(file)); CURL authUrl = url; if (CPasswordManager::GetInstance().IsURLSupported(authUrl) && authUrl.GetUserName().empty()) CPasswordManager::GetInstance().AuthenticateURL(authUrl); std::unique_ptr pFile(CFileFactory::CreateLoader(url)); if (!pFile) return false; if(pFile->Delete(authUrl)) { g_directoryCache.ClearFile(url.Get()); return true; } } XBMCCOMMONS_HANDLE_UNCHECKED catch (...) { CLog::Log(LOGERROR, "{} - Unhandled exception", __FUNCTION__); } if (Exists(file)) CLog::Log(LOGERROR, "{} - Error deleting file {}", __FUNCTION__, file.GetRedacted()); return false; } bool CFile::Rename(const std::string& strFileName, const std::string& strNewFileName) { const CURL pathToUrl(strFileName); const CURL pathToUrlNew(strNewFileName); return Rename(pathToUrl, pathToUrlNew); } bool CFile::Rename(const CURL& file, const CURL& newFile) { try { CURL url(URIUtils::SubstitutePath(file)); CURL urlnew(URIUtils::SubstitutePath(newFile)); CURL authUrl = url; if (CPasswordManager::GetInstance().IsURLSupported(authUrl) && authUrl.GetUserName().empty()) CPasswordManager::GetInstance().AuthenticateURL(authUrl); CURL authUrlNew = urlnew; if (CPasswordManager::GetInstance().IsURLSupported(authUrlNew) && authUrlNew.GetUserName().empty()) CPasswordManager::GetInstance().AuthenticateURL(authUrlNew); std::unique_ptr pFile(CFileFactory::CreateLoader(url)); if (!pFile) return false; if(pFile->Rename(authUrl, authUrlNew)) { g_directoryCache.ClearFile(url.Get()); g_directoryCache.AddFile(urlnew.Get()); return true; } } XBMCCOMMONS_HANDLE_UNCHECKED catch (...) { CLog::Log(LOGERROR, "{} - Unhandled exception ", __FUNCTION__); } CLog::Log(LOGERROR, "{} - Error renaming file {}", __FUNCTION__, file.GetRedacted()); return false; } bool CFile::SetHidden(const std::string& fileName, bool hidden) { const CURL pathToUrl(fileName); return SetHidden(pathToUrl, hidden); } bool CFile::SetHidden(const CURL& file, bool hidden) { try { CURL url(URIUtils::SubstitutePath(file)); CURL authUrl = url; if (CPasswordManager::GetInstance().IsURLSupported(authUrl) && authUrl.GetUserName().empty()) CPasswordManager::GetInstance().AuthenticateURL(authUrl); std::unique_ptr pFile(CFileFactory::CreateLoader(url)); if (!pFile) return false; return pFile->SetHidden(authUrl, hidden); } catch(...) { CLog::Log(LOGERROR, "{}({}) - Unhandled exception", __FUNCTION__, file.GetRedacted()); } return false; } int CFile::IoControl(EIoControl request, void* param) { int result = -1; if (!m_pFile) return -1; result = m_pFile->IoControl(request, param); if(result == -1 && request == IOCTRL_SEEK_POSSIBLE) { if(m_pFile->GetLength() >= 0 && m_pFile->Seek(0, SEEK_CUR) >= 0) return 1; else return 0; } return result; } int CFile::GetChunkSize() { if (m_pFile) return m_pFile->GetChunkSize(); return 0; } const std::string CFile::GetProperty(XFILE::FileProperty type, const std::string &name) const { if (!m_pFile) return ""; return m_pFile->GetProperty(type, name); } const std::vector CFile::GetPropertyValues(XFILE::FileProperty type, const std::string &name) const { if (!m_pFile) { return std::vector(); } return m_pFile->GetPropertyValues(type, name); } ssize_t CFile::LoadFile(const std::string& filename, std::vector& outputBuffer) { const CURL pathToUrl(filename); return LoadFile(pathToUrl, outputBuffer); } ssize_t CFile::LoadFile(const CURL& file, std::vector& outputBuffer) { static const size_t max_file_size = 0x7FFFFFFF; static const size_t min_chunk_size = 64 * 1024U; static const size_t max_chunk_size = 2048 * 1024U; outputBuffer.clear(); if (!Open(file, READ_TRUNCATED)) return 0; /* GetLength() will typically return values that fall into three cases: 1. The real filesize. This is the typical case. 2. Zero. This is the case for some http:// streams for example. 3. Some value smaller than the real filesize. This is the case for an expanding file. In order to handle all three cases, we read the file in chunks, relying on Read() returning 0 at EOF. To minimize (re)allocation of the buffer, the chunksize in cases 1 and 3 is set to one byte larger than the value returned by GetLength(). The chunksize in case 2 is set to the lowest value larger than min_chunk_size aligned to GetChunkSize(). We fill the buffer entirely before reallocation. Thus, reallocation never occurs in case 1 as the buffer is larger than the file, so we hit EOF before we hit the end of buffer. To minimize reallocation, we double the chunksize each read while chunksize is lower than max_chunk_size. */ int64_t filesize = GetLength(); if (filesize > (int64_t)max_file_size) return 0; /* file is too large for this function */ size_t chunksize = (filesize > 0) ? static_cast(filesize + 1) : static_cast(DetermineChunkSize(GetChunkSize(), min_chunk_size)); size_t total_read = 0; while (true) { if (total_read == outputBuffer.size()) { // (re)alloc if (outputBuffer.size() + chunksize > max_file_size) { outputBuffer.clear(); return -1; } outputBuffer.resize(outputBuffer.size() + chunksize); if (chunksize < max_chunk_size) chunksize *= 2; } ssize_t read = Read(outputBuffer.data() + total_read, outputBuffer.size() - total_read); if (read < 0) { outputBuffer.clear(); return -1; } total_read += read; if (!read) break; } outputBuffer.resize(total_read); return total_read; } double CFile::GetDownloadSpeed() { if (m_pFile) return m_pFile->GetDownloadSpeed(); return 0.0; } //********************************************************************************************* //*************** Stream IO for CFile objects ************************************************* //********************************************************************************************* CFileStreamBuffer::~CFileStreamBuffer() { sync(); Detach(); } CFileStreamBuffer::CFileStreamBuffer(int backsize) : std::streambuf() , m_file(NULL) , m_buffer(NULL) , m_backsize(backsize) { } void CFileStreamBuffer::Attach(IFile *file) { m_file = file; m_frontsize = CFile::DetermineChunkSize(m_file->GetChunkSize(), 64 * 1024); m_buffer = new char[m_frontsize+m_backsize]; setg(0,0,0); setp(0,0); } void CFileStreamBuffer::Detach() { setg(0,0,0); setp(0,0); delete[] m_buffer; m_buffer = NULL; } CFileStreamBuffer::int_type CFileStreamBuffer::underflow() { if(gptr() < egptr()) return traits_type::to_int_type(*gptr()); if(!m_file) return traits_type::eof(); size_t backsize = 0; if(m_backsize) { backsize = (size_t)std::min((ptrdiff_t)m_backsize, egptr()-eback()); memmove(m_buffer, egptr()-backsize, backsize); } ssize_t size = m_file->Read(m_buffer+backsize, m_frontsize); if (size == 0) return traits_type::eof(); else if (size < 0) { CLog::LogF(LOGWARNING, "Error reading file - assuming eof"); return traits_type::eof(); } setg(m_buffer, m_buffer+backsize, m_buffer+backsize+size); return traits_type::to_int_type(*gptr()); } CFileStreamBuffer::pos_type CFileStreamBuffer::seekoff( off_type offset, std::ios_base::seekdir way, std::ios_base::openmode mode) { // calculate relative offset off_type aheadbytes = (egptr() - gptr()); off_type pos = m_file->GetPosition() - aheadbytes; off_type offset2; if(way == std::ios_base::cur) offset2 = offset; else if(way == std::ios_base::beg) offset2 = offset - pos; else if(way == std::ios_base::end) offset2 = offset + m_file->GetLength() - pos; else return std::streampos(-1); // a non seek shouldn't modify our buffer if(offset2 == 0) return pos; // try to seek within buffer if(gptr()+offset2 >= eback() && gptr()+offset2 < egptr()) { gbump(offset2); return pos + offset2; } // reset our buffer pointer, will // start buffering on next read setg(0,0,0); setp(0,0); int64_t position = -1; if(way == std::ios_base::cur) position = m_file->Seek(offset - aheadbytes, SEEK_CUR); else if(way == std::ios_base::end) position = m_file->Seek(offset, SEEK_END); else position = m_file->Seek(offset, SEEK_SET); if(position<0) return std::streampos(-1); return position; } CFileStreamBuffer::pos_type CFileStreamBuffer::seekpos( pos_type pos, std::ios_base::openmode mode) { return seekoff(pos, std::ios_base::beg, mode); } std::streamsize CFileStreamBuffer::showmanyc() { underflow(); return egptr() - gptr(); } CFileStream::CFileStream(int backsize /*= 0*/) : std::istream(&m_buffer), m_buffer(backsize) { } CFileStream::~CFileStream() { Close(); } bool CFileStream::Open(const CURL& filename) { Close(); CURL url(URIUtils::SubstitutePath(filename)); m_file.reset(CFileFactory::CreateLoader(url)); CURL authUrl = url; if (CPasswordManager::GetInstance().IsURLSupported(authUrl) && authUrl.GetUserName().empty()) CPasswordManager::GetInstance().AuthenticateURL(authUrl); if(m_file && m_file->Open(authUrl)) { m_buffer.Attach(m_file.get()); return true; } setstate(failbit); return false; } int64_t CFileStream::GetLength() { return m_file->GetLength(); } void CFileStream::Close() { if(!m_file) return; m_buffer.Detach(); } bool CFileStream::Open(const std::string& filename) { const CURL pathToUrl(filename); return Open(pathToUrl); }