2 * Copyright (c) 2002 Frodo
3 * Portions Copyright (c) by the authors of ffmpeg and xvid
4 * Copyright (C) 2002-2013 Team XBMC
7 * This Program is free software; you can redistribute it and/or modify
8 * it under the terms of the GNU General Public License as published by
9 * the Free Software Foundation; either version 2, or (at your option)
12 * This Program is distributed in the hope that it will be useful,
13 * but WITHOUT ANY WARRANTY; without even the implied warranty of
14 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
15 * GNU General Public License for more details.
17 * You should have received a copy of the GNU General Public License
18 * along with XBMC; see the file COPYING. If not, see
19 * <http://www.gnu.org/licenses/>.
25 #include "FileFactory.h"
26 #include "Application.h"
27 #include "DirectoryCache.h"
28 #include "Directory.h"
29 #include "FileCache.h"
30 #include "utils/log.h"
31 #include "utils/URIUtils.h"
32 #include "utils/BitstreamStats.h"
35 #include "utils/StringUtils.h"
37 #include "commons/Exception.h"
39 using namespace XFILE;
42 //////////////////////////////////////////////////////////////////////
43 // Construction/Destruction
44 //////////////////////////////////////////////////////////////////////
46 #pragma warning (disable:4244)
49 //*********************************************************************************************
55 m_bitStreamStats = NULL;
58 //*********************************************************************************************
65 SAFE_DELETE(m_pBuffer);
67 SAFE_DELETE(m_bitStreamStats);
70 //*********************************************************************************************
72 #define MALLOC_EXCP_MSG "auto_buffer: malloc failed!"
74 #define MALLOC_EXCP_MSG
78 auto_buffer::auto_buffer(size_t size) : p(NULL), s(0)
85 throw std::bad_alloc(MALLOC_EXCP_MSG);
89 auto_buffer::~auto_buffer()
94 auto_buffer& auto_buffer::allocate(size_t size)
100 auto_buffer& auto_buffer::resize(size_t newSize)
102 void* newPtr = realloc(p, newSize);
103 if (!newPtr && newSize)
104 throw std::bad_alloc(MALLOC_EXCP_MSG);
110 auto_buffer& auto_buffer::clear(void)
118 auto_buffer& auto_buffer::attach(void* pointer, size_t size)
121 if ((pointer && size) || (!pointer && !size))
129 void* auto_buffer::detach(void)
138 // This *looks* like a copy function, therefor the name "Cache" is misleading
139 bool CFile::Cache(const CStdString& strFileName, const CStdString& strDest, XFILE::IFileCallback* pCallback, void* pContext)
143 if (strFileName.empty() || strDest.empty())
146 // special case for zips - ignore caching
147 CURL url(strFileName);
148 if (URIUtils::IsInZIP(strFileName) || URIUtils::IsInAPK(strFileName))
149 url.SetOptions("?cache=no");
150 if (file.Open(url.Get(), READ_TRUNCATED))
154 if (URIUtils::IsHD(strDest)) // create possible missing dirs
156 vector<std::string> tokens;
157 CStdString strDirectory = URIUtils::GetDirectory(strDest);
158 URIUtils::RemoveSlashAtEnd(strDirectory); // for the test below
159 if (!(strDirectory.size() == 2 && strDirectory[1] == ':'))
161 CURL url(strDirectory);
168 StringUtils::Tokenize(url.GetFileName(),tokens,pathsep.c_str());
169 CStdString strCurrPath;
171 if (!url.GetProtocol().empty()) {
173 strCurrPath += url.GetProtocol() + "://";
174 } // If the directory has a / at the beginning, don't forget it
175 else if (strDirectory[0] == pathsep[0])
176 strCurrPath += pathsep;
177 for (vector<std::string>::iterator iter=tokens.begin();iter!=tokens.end();++iter)
179 strCurrPath += *iter+pathsep;
180 CDirectory::Create(strCurrPath);
184 if (CFile::Exists(strDest))
185 CFile::Delete(strDest);
186 if (!newFile.OpenForWrite(strDest, true)) // overwrite always
192 int iBufferSize = 128 * 1024;
194 auto_buffer buffer(iBufferSize);
197 UINT64 llFileSize = file.GetLength();
205 g_application.ResetScreenSaver();
207 iRead = file.Read(buffer.get(), iBufferSize);
208 if (iRead == 0) break;
211 CLog::Log(LOGERROR, "%s - Failed read from file %s", __FUNCTION__, strFileName.c_str());
212 llFileSize = (uint64_t)-1;
216 /* write data and make sure we managed to write it all */
218 while(iWrite < iRead)
220 int iWrite2 = newFile.Write(buffer.get()+iWrite, iRead-iWrite);
228 CLog::Log(LOGERROR, "%s - Failed write to file %s", __FUNCTION__, strDest.c_str());
229 llFileSize = (uint64_t)-1;
235 // calculate the current and average speeds
236 float end = timer.GetElapsedSeconds();
238 if (pCallback && end - start > 0.5 && end)
242 float averageSpeed = llPos / end;
245 ipercent = 100 * llPos / llFileSize;
247 if(!pCallback->OnFileCallback(pContext, ipercent, averageSpeed))
249 CLog::Log(LOGERROR, "%s - User aborted copy", __FUNCTION__);
250 llFileSize = (uint64_t)-1;
256 /* close both files */
260 /* verify that we managed to completed the file */
261 if (llFileSize && llPos != llFileSize)
263 CFile::Delete(strDest);
271 //*********************************************************************************************
272 bool CFile::Open(const CStdString& strFileName, const unsigned int flags)
278 CURL url2(URIUtils::SubstitutePath(strFileName));
279 if (url2.GetProtocol() == "apk")
281 if (url2.GetProtocol() == "zip")
283 if (!g_directoryCache.FileExists(url2.Get(), bPathInCache) )
289 CURL url(URIUtils::SubstitutePath(strFileName));
291 if (!(m_flags & READ_NO_CACHE))
293 if (URIUtils::IsInternetStream(url, true) && !CUtil::IsPicture(strFileName) )
294 m_flags |= READ_CACHED;
296 if (m_flags & READ_CACHED)
298 // for internet stream, if it contains multiple stream, file cache need handle it specially.
299 m_pFile = new CFileCache(m_flags & READ_MULTI_STREAM);
300 return m_pFile->Open(url);
304 m_pFile = CFileFactory::CreateLoader(url);
310 if (!m_pFile->Open(url))
312 SAFE_DELETE(m_pFile);
316 catch (CRedirectException *pRedirectEx)
318 // the file implementation decided this item should use a different implementation.
319 // the exception will contain the new implementation.
320 CLog::Log(LOGDEBUG,"File::Open - redirecting implementation for %s", strFileName.c_str());
321 SAFE_DELETE(m_pFile);
322 if (pRedirectEx && pRedirectEx->m_pNewFileImp)
324 auto_ptr<CURL> pNewUrl(pRedirectEx->m_pNewUrl);
325 m_pFile = pRedirectEx->m_pNewFileImp;
330 if (!m_pFile->Open(*pNewUrl))
332 SAFE_DELETE(m_pFile);
338 if (!m_pFile->Open(url))
340 SAFE_DELETE(m_pFile);
348 CLog::Log(LOGERROR, "File::Open - unknown exception when opening %s", strFileName.c_str());
349 SAFE_DELETE(m_pFile);
353 if (m_pFile->GetChunkSize() && !(m_flags & READ_CHUNKED))
355 m_pBuffer = new CFileStreamBuffer(0);
356 m_pBuffer->Attach(m_pFile);
359 if (m_flags & READ_BITRATE)
361 m_bitStreamStats = new BitstreamStats();
362 m_bitStreamStats->Start();
367 XBMCCOMMONS_HANDLE_UNCHECKED
370 CLog::Log(LOGERROR, "%s - Unhandled exception", __FUNCTION__);
372 CLog::Log(LOGERROR, "%s - Error opening %s", __FUNCTION__, strFileName.c_str());
376 bool CFile::OpenForWrite(const CStdString& strFileName, bool bOverWrite)
380 CStdString storedFileName = URIUtils::SubstitutePath(strFileName);
381 CURL url(storedFileName);
383 m_pFile = CFileFactory::CreateLoader(url);
384 if (m_pFile && m_pFile->OpenForWrite(url, bOverWrite))
386 // add this file to our directory cache (if it's stored)
387 g_directoryCache.AddFile(storedFileName);
392 XBMCCOMMONS_HANDLE_UNCHECKED
395 CLog::Log(LOGERROR, "%s - Unhandled exception opening %s", __FUNCTION__, strFileName.c_str());
397 CLog::Log(LOGERROR, "%s - Error opening %s", __FUNCTION__, strFileName.c_str());
401 bool CFile::Exists(const CStdString& strFileName, bool bUseCache /* = true */)
403 CURL url = URIUtils::SubstitutePath(strFileName);
407 if (strFileName.empty())
413 if (g_directoryCache.FileExists(url.Get(), bPathInCache))
419 auto_ptr<IFile> pFile(CFileFactory::CreateLoader(url));
423 return pFile->Exists(url);
425 XBMCCOMMONS_HANDLE_UNCHECKED
426 catch (CRedirectException *pRedirectEx)
428 // the file implementation decided this item should use a different implementation.
429 // the exception will contain the new implementation and optional a redirected URL.
430 CLog::Log(LOGDEBUG,"File::Exists - redirecting implementation for %s", strFileName.c_str());
431 if (pRedirectEx && pRedirectEx->m_pNewFileImp)
433 auto_ptr<IFile> pImp(pRedirectEx->m_pNewFileImp);
434 auto_ptr<CURL> pNewUrl(pRedirectEx->m_pNewUrl);
444 if (g_directoryCache.FileExists(pNewUrl->Get(), bPathInCache))
449 return pImp->Exists(*pNewUrl);
452 return pImp->Exists(url);
458 CLog::Log(LOGERROR, "%s - Unhandled exception", __FUNCTION__);
460 CLog::Log(LOGERROR, "%s - Error checking for %s", __FUNCTION__, strFileName.c_str());
464 int CFile::Stat(struct __stat64 *buffer)
471 memset(buffer, 0, sizeof(struct __stat64));
476 return m_pFile->Stat(buffer);
479 bool CFile::SkipNext()
482 return m_pFile->SkipNext();
486 int CFile::Stat(const CStdString& strFileName, struct __stat64* buffer)
495 url = URIUtils::SubstitutePath(strFileName);
497 auto_ptr<IFile> pFile(CFileFactory::CreateLoader(url));
500 return pFile->Stat(url, buffer);
502 XBMCCOMMONS_HANDLE_UNCHECKED
503 catch (CRedirectException *pRedirectEx)
505 // the file implementation decided this item should use a different implementation.
506 // the exception will contain the new implementation and optional a redirected URL.
507 CLog::Log(LOGDEBUG,"File::Stat - redirecting implementation for %s", strFileName.c_str());
508 if (pRedirectEx && pRedirectEx->m_pNewFileImp)
510 auto_ptr<IFile> pImp(pRedirectEx->m_pNewFileImp);
511 auto_ptr<CURL> pNewUrl(pRedirectEx->m_pNewUrl);
516 if (pImp.get() && !pImp->Stat(*pNewUrl, buffer))
523 if (pImp.get() && !pImp->Stat(url, buffer))
532 CLog::Log(LOGERROR, "%s - Unhandled exception", __FUNCTION__);
534 CLog::Log(LOGERROR, "%s - Error statting %s", __FUNCTION__, strFileName.c_str());
538 unsigned int CFile::Read(void *lpBuf, int64_t uiBufSize)
540 if (!m_pFile || !lpBuf)
545 if(m_flags & READ_TRUNCATED)
547 unsigned int nBytes = m_pBuffer->sgetn(
548 (char *)lpBuf, min<streamsize>((streamsize)uiBufSize,
549 m_pBuffer->in_avail()));
550 if (m_bitStreamStats && nBytes>0)
551 m_bitStreamStats->AddSampleBytes(nBytes);
556 unsigned int nBytes = m_pBuffer->sgetn((char*)lpBuf, uiBufSize);
557 if (m_bitStreamStats && nBytes>0)
558 m_bitStreamStats->AddSampleBytes(nBytes);
565 if(m_flags & READ_TRUNCATED)
567 unsigned int nBytes = m_pFile->Read(lpBuf, uiBufSize);
568 if (m_bitStreamStats && nBytes>0)
569 m_bitStreamStats->AddSampleBytes(nBytes);
574 unsigned int done = 0;
575 while((uiBufSize-done) > 0)
577 int curr = m_pFile->Read((char*)lpBuf+done, uiBufSize-done);
583 if (m_bitStreamStats && done > 0)
584 m_bitStreamStats->AddSampleBytes(done);
588 XBMCCOMMONS_HANDLE_UNCHECKED
591 CLog::Log(LOGERROR, "%s - Unhandled exception", __FUNCTION__);
596 //*********************************************************************************************
604 SAFE_DELETE(m_pBuffer);
605 SAFE_DELETE(m_pFile);
607 XBMCCOMMONS_HANDLE_UNCHECKED
610 CLog::Log(LOGERROR, "%s - Unhandled exception", __FUNCTION__);
622 XBMCCOMMONS_HANDLE_UNCHECKED
625 CLog::Log(LOGERROR, "%s - Unhandled exception", __FUNCTION__);
630 //*********************************************************************************************
631 int64_t CFile::Seek(int64_t iFilePosition, int iWhence)
638 if(iWhence == SEEK_CUR)
639 return m_pBuffer->pubseekoff(iFilePosition,ios_base::cur);
640 else if(iWhence == SEEK_END)
641 return m_pBuffer->pubseekoff(iFilePosition,ios_base::end);
642 else if(iWhence == SEEK_SET)
643 return m_pBuffer->pubseekoff(iFilePosition,ios_base::beg);
648 return m_pFile->Seek(iFilePosition, iWhence);
650 XBMCCOMMONS_HANDLE_UNCHECKED
653 CLog::Log(LOGERROR, "%s - Unhandled exception", __FUNCTION__);
658 //*********************************************************************************************
659 int CFile::Truncate(int64_t iSize)
666 return m_pFile->Truncate(iSize);
668 XBMCCOMMONS_HANDLE_UNCHECKED
671 CLog::Log(LOGERROR, "%s - Unhandled exception", __FUNCTION__);
676 //*********************************************************************************************
677 int64_t CFile::GetLength()
682 return m_pFile->GetLength();
685 XBMCCOMMONS_HANDLE_UNCHECKED
688 CLog::Log(LOGERROR, "%s - Unhandled exception", __FUNCTION__);
693 //*********************************************************************************************
694 int64_t CFile::GetPosition() const
700 return m_pBuffer->pubseekoff(0, ios_base::cur);
704 return m_pFile->GetPosition();
706 XBMCCOMMONS_HANDLE_UNCHECKED
709 CLog::Log(LOGERROR, "%s - Unhandled exception", __FUNCTION__);
715 //*********************************************************************************************
716 bool CFile::ReadString(char *szLine, int iLineLength)
718 if (!m_pFile || !szLine)
723 typedef CFileStreamBuffer::traits_type traits;
724 CFileStreamBuffer::int_type aByte = m_pBuffer->sgetc();
726 if(aByte == traits::eof())
731 aByte = m_pBuffer->sbumpc();
733 if(aByte == traits::eof())
736 if(aByte == traits::to_int_type('\n'))
738 if(m_pBuffer->sgetc() == traits::to_int_type('\r'))
743 if(aByte == traits::to_int_type('\r'))
745 if(m_pBuffer->sgetc() == traits::to_int_type('\n'))
750 *szLine = traits::to_char_type(aByte);
755 // if we have no space for terminating character we failed
766 return m_pFile->ReadString(szLine, iLineLength);
768 XBMCCOMMONS_HANDLE_UNCHECKED
771 CLog::Log(LOGERROR, "%s - Unhandled exception", __FUNCTION__);
776 int CFile::Write(const void* lpBuf, int64_t uiBufSize)
778 if (!m_pFile || !lpBuf)
783 return m_pFile->Write(lpBuf, uiBufSize);
785 XBMCCOMMONS_HANDLE_UNCHECKED
788 CLog::Log(LOGERROR, "%s - Unhandled exception", __FUNCTION__);
793 bool CFile::Delete(const CStdString& strFileName)
797 CURL url(URIUtils::SubstitutePath(strFileName));
799 auto_ptr<IFile> pFile(CFileFactory::CreateLoader(url));
803 if(pFile->Delete(url))
805 g_directoryCache.ClearFile(url.Get());
809 XBMCCOMMONS_HANDLE_UNCHECKED
812 CLog::Log(LOGERROR, "%s - Unhandled exception", __FUNCTION__);
814 if (Exists(strFileName))
815 CLog::Log(LOGERROR, "%s - Error deleting file %s", __FUNCTION__, strFileName.c_str());
819 bool CFile::Rename(const CStdString& strFileName, const CStdString& strNewFileName)
823 CURL url(URIUtils::SubstitutePath(strFileName));
824 CURL urlnew(URIUtils::SubstitutePath(strNewFileName));
826 auto_ptr<IFile> pFile(CFileFactory::CreateLoader(url));
830 if(pFile->Rename(url, urlnew))
832 g_directoryCache.ClearFile(url.Get());
833 g_directoryCache.AddFile(urlnew.Get());
837 XBMCCOMMONS_HANDLE_UNCHECKED
840 CLog::Log(LOGERROR, "%s - Unhandled exception ", __FUNCTION__);
842 CLog::Log(LOGERROR, "%s - Error renaming file %s", __FUNCTION__, strFileName.c_str());
846 bool CFile::SetHidden(const CStdString& fileName, bool hidden)
850 CURL url(URIUtils::SubstitutePath(fileName));
852 auto_ptr<IFile> pFile(CFileFactory::CreateLoader(url));
856 return pFile->SetHidden(url, hidden);
860 CLog::Log(LOGERROR, "%s(%s) - Unhandled exception", __FUNCTION__, fileName.c_str());
865 int CFile::IoControl(EIoControl request, void* param)
870 result = m_pFile->IoControl(request, param);
872 if(result == -1 && request == IOCTRL_SEEK_POSSIBLE)
874 if(m_pFile->GetLength() >= 0 && m_pFile->Seek(0, SEEK_CUR) >= 0)
883 int CFile::GetChunkSize()
886 return m_pFile->GetChunkSize();
890 std::string CFile::GetContentMimeType(void)
894 return m_pFile->GetContent();
897 std::string CFile::GetContentCharset(void)
901 return m_pFile->GetContentCharset();
905 unsigned int CFile::LoadFile(const std::string &filename, auto_buffer& outputBuffer)
907 static const unsigned int max_file_size = 0x7FFFFFFF;
908 static const unsigned int min_chunk_size = 64 * 1024U;
909 static const unsigned int max_chunk_size = 2048 * 1024U;
911 outputBuffer.clear();
912 if (filename.empty())
915 if (!Open(filename, READ_TRUNCATED))
919 GetLength() will typically return values that fall into three cases:
920 1. The real filesize. This is the typical case.
921 2. Zero. This is the case for some http:// streams for example.
922 3. Some value smaller than the real filesize. This is the case for an expanding file.
924 In order to handle all three cases, we read the file in chunks, relying on Read()
925 returning 0 at EOF. To minimize (re)allocation of the buffer, the chunksize in
926 cases 1 and 3 is set to one byte larger than the value returned by GetLength().
927 The chunksize in case 2 is set to the lowest value larger than min_chunk_size aligned
930 We fill the buffer entirely before reallocation. Thus, reallocation never occurs in case 1
931 as the buffer is larger than the file, so we hit EOF before we hit the end of buffer.
933 To minimize reallocation, we double the chunksize each read while chunksize is lower
936 int64_t filesize = GetLength();
937 if (filesize > max_file_size)
938 return 0; /* file is too large for this function */
940 unsigned int chunksize = (filesize > 0) ? (unsigned int)(filesize + 1) : GetChunkSize(GetChunkSize(), min_chunk_size);
941 unsigned int total_read = 0;
944 if (total_read == outputBuffer.size())
946 if (outputBuffer.size() >= max_file_size)
948 outputBuffer.clear();
951 outputBuffer.resize(outputBuffer.size() + chunksize);
952 if (chunksize < max_chunk_size)
955 unsigned int read = Read(outputBuffer.get() + total_read, outputBuffer.size() - total_read);
961 outputBuffer.resize(total_read);
966 //*********************************************************************************************
967 //*************** Stream IO for CFile objects *************************************************
968 //*********************************************************************************************
969 CFileStreamBuffer::~CFileStreamBuffer()
975 CFileStreamBuffer::CFileStreamBuffer(int backsize)
979 , m_backsize(backsize)
984 void CFileStreamBuffer::Attach(IFile *file)
988 m_frontsize = CFile::GetChunkSize(m_file->GetChunkSize(), 64*1024);
990 m_buffer = new char[m_frontsize+m_backsize];
995 void CFileStreamBuffer::Detach()
1003 CFileStreamBuffer::int_type CFileStreamBuffer::underflow()
1005 if(gptr() < egptr())
1006 return traits_type::to_int_type(*gptr());
1009 return traits_type::eof();
1011 size_t backsize = 0;
1014 backsize = (size_t)min<ptrdiff_t>((ptrdiff_t)m_backsize, egptr()-eback());
1015 memmove(m_buffer, egptr()-backsize, backsize);
1018 unsigned int size = m_file->Read(m_buffer+backsize, m_frontsize);
1021 return traits_type::eof();
1023 setg(m_buffer, m_buffer+backsize, m_buffer+backsize+size);
1024 return traits_type::to_int_type(*gptr());
1027 CFileStreamBuffer::pos_type CFileStreamBuffer::seekoff(
1029 ios_base::seekdir way,
1030 ios_base::openmode mode)
1032 // calculate relative offset
1033 off_type pos = m_file->GetPosition() - (egptr() - gptr());
1035 if(way == ios_base::cur)
1037 else if(way == ios_base::beg)
1038 offset2 = offset - pos;
1039 else if(way == ios_base::end)
1040 offset2 = offset + m_file->GetLength() - pos;
1042 return streampos(-1);
1044 // a non seek shouldn't modify our buffer
1048 // try to seek within buffer
1049 if(gptr()+offset2 >= eback() && gptr()+offset2 < egptr())
1052 return pos + offset2;
1055 // reset our buffer pointer, will
1056 // start buffering on next read
1060 int64_t position = -1;
1061 if(way == ios_base::cur)
1062 position = m_file->Seek(offset, SEEK_CUR);
1063 else if(way == ios_base::end)
1064 position = m_file->Seek(offset, SEEK_END);
1066 position = m_file->Seek(offset, SEEK_SET);
1069 return streampos(-1);
1074 CFileStreamBuffer::pos_type CFileStreamBuffer::seekpos(
1076 ios_base::openmode mode)
1078 return seekoff(pos, ios_base::beg, mode);
1081 streamsize CFileStreamBuffer::showmanyc()
1084 return egptr() - gptr();
1087 CFileStream::CFileStream(int backsize /*= 0*/) :
1094 CFileStream::~CFileStream()
1100 bool CFileStream::Open(const CURL& filename)
1104 // NOTE: This is currently not translated - reason is that all entry points into CFileStream::Open currently
1105 // go from the CStdString version below. We may have to change this in future, but I prefer not decoding
1106 // the URL and re-encoding, or applying the translation twice.
1107 m_file = CFileFactory::CreateLoader(filename);
1108 if(m_file && m_file->Open(filename))
1110 m_buffer.Attach(m_file);
1118 int64_t CFileStream::GetLength()
1120 return m_file->GetLength();
1123 void CFileStream::Close()
1129 SAFE_DELETE(m_file);
1132 bool CFileStream::Open(const CStdString& filename)
1134 return Open(CURL(URIUtils::SubstitutePath(filename)));