2 * Copyright (C) 2005-2013 Team XBMC
5 * This Program is free software; you can redistribute it and/or modify
6 * it under the terms of the GNU General Public License as published by
7 * the Free Software Foundation; either version 2, or (at your option)
10 * This Program is distributed in the hope that it will be useful,
11 * but WITHOUT ANY WARRANTY; without even the implied warranty of
12 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13 * GNU General Public License for more details.
15 * You should have received a copy of the GNU General Public License
16 * along with XBMC; see the file COPYING. If not, see
17 * <http://www.gnu.org/licenses/>.
25 #include "utils/CharsetConverter.h"
26 #include "utils/URIUtils.h"
28 #include "Directory.h"
29 #include "RarManager.h"
30 #include "settings/AdvancedSettings.h"
32 #include "utils/log.h"
33 #include "UnrarXLib/rar.hpp"
34 #include "utils/StringUtils.h"
40 using namespace XFILE;
43 #define SEEKTIMOUT 30000
45 #ifdef HAS_FILESYSTEM_RAR
46 CRarFileExtractThread::CRarFileExtractThread() : CThread("RarFileExtract"), hRunning(true), hQuit(true)
55 CRarFileExtractThread::~CRarFileExtractThread()
58 AbortableWait(hRestart);
62 void CRarFileExtractThread::Start(Archive* pArc, CommandData* pCmd, CmdExtract* pExtract, int iSize)
66 m_pExtract = pExtract;
69 m_pExtract->GetDataIO().hBufferFilled = new CEvent;
70 m_pExtract->GetDataIO().hBufferEmpty = new CEvent;
71 m_pExtract->GetDataIO().hSeek = new CEvent(true);
72 m_pExtract->GetDataIO().hSeekDone = new CEvent;
73 m_pExtract->GetDataIO().hQuit = new CEvent(true);
79 void CRarFileExtractThread::OnStartup()
83 void CRarFileExtractThread::OnExit()
87 void CRarFileExtractThread::Process()
89 while (AbortableWait(hQuit,1) != WAIT_SIGNALED)
91 if (AbortableWait(hRestart,1) == WAIT_SIGNALED)
96 m_pExtract->ExtractCurrentFile(m_pCmd,*m_pArc,m_iSize,Repeat);
98 catch (int rarErrCode)
100 CLog::Log(LOGERROR,"filerar CFileRarExtractThread::Process failed. CmdExtract::ExtractCurrentFile threw a UnrarXLib error code of %d",rarErrCode);
104 CLog::Log(LOGERROR,"filerar CFileRarExtractThread::Process failed. CmdExtract::ExtractCurrentFile threw an Unknown exception");
116 m_strCacheDir.clear();
117 m_strRarPath.clear();
118 m_strPassword.clear();
119 m_strPathInRar.clear();
121 #ifdef HAS_FILESYSTEM_RAR
125 m_pExtractThread = NULL;
128 m_szStartOfBuffer = NULL;
135 CRarFile::~CRarFile()
137 #ifdef HAS_FILESYSTEM_RAR
144 g_RarManager.ClearCachedFile(m_strRarPath,m_strPathInRar);
149 if (m_pExtractThread)
151 delete m_pExtractThread;
152 m_pExtractThread = NULL;
158 bool CRarFile::Open(const CURL& url)
162 g_RarManager.GetFilesInRar(items,m_strRarPath,false);
164 for (i=0;i<items.Size();++i)
166 if (items[i]->GetLabel() == m_strPathInRar)
172 if (items[i]->m_idepth == 0x30) // stored
174 if (!OpenInArchive())
177 m_iFileSize = items[i]->m_dwSize;
180 // perform 'noidx' check
181 CFileInfo* pFile = g_RarManager.GetFileInRar(m_strRarPath,m_strPathInRar);
184 if (pFile->m_iIsSeekable == -1)
186 if (Seek(-1,SEEK_END) == -1)
189 pFile->m_iIsSeekable = 0;
193 m_bSeekable = (pFile->m_iIsSeekable == 1);
199 CFileInfo* info = g_RarManager.GetFileInRar(m_strRarPath,m_strPathInRar);
200 if ((!info || !CFile::Exists(info->m_strCachedPath)) && m_bFileOptions & EXFILE_NOCACHE)
203 CStdString strPathInCache;
205 if (!g_RarManager.CacheRarredFile(strPathInCache, m_strRarPath, m_strPathInRar,
206 EXFILE_AUTODELETE | m_bFileOptions, m_strCacheDir,
209 CLog::Log(LOGERROR,"filerar::open failed to cache file %s",m_strPathInRar.c_str());
213 if (!m_File.Open( strPathInCache ))
215 CLog::Log(LOGERROR,"filerar::open failed to open file in cache: %s",strPathInCache.c_str());
226 bool CRarFile::Exists(const CURL& url)
231 // Make sure that the archive exists in the filesystem.
232 if (!CFile::Exists(m_strRarPath, false))
236 // Make sure that the requested file exists in the archive.
239 if (!g_RarManager.IsFileInRar(bResult, m_strRarPath, m_strPathInRar))
245 int CRarFile::Stat(const CURL& url, struct __stat64* buffer)
247 memset(buffer, 0, sizeof(struct __stat64));
250 buffer->st_size = GetLength();
251 buffer->st_mode = _S_IFREG;
257 if (CDirectory::Exists(url.Get()))
259 buffer->st_mode = _S_IFDIR;
267 bool CRarFile::OpenForWrite(const CURL& url)
272 unsigned int CRarFile::Read(void *lpBuf, int64_t uiBufSize)
274 #ifdef HAS_FILESYSTEM_RAR
279 return m_File.Read(lpBuf,uiBufSize);
281 if (m_iFilePosition >= GetLength()) // we are done
284 if( !m_pExtract->GetDataIO().hBufferEmpty->WaitMSec(5000) )
286 CLog::Log(LOGERROR, "%s - Timeout waiting for buffer to empty", __FUNCTION__);
291 uint8_t* pBuf = (uint8_t*)lpBuf;
292 int64_t uicBufSize = uiBufSize;
293 if (m_iDataInBuffer > 0)
295 int64_t iCopy = uiBufSize<m_iDataInBuffer?uiBufSize:m_iDataInBuffer;
296 memcpy(lpBuf,m_szStartOfBuffer,size_t(iCopy));
297 m_szStartOfBuffer += iCopy;
298 m_iDataInBuffer -= int(iCopy);
301 m_iFilePosition += iCopy;
304 while ((uicBufSize > 0) && m_iFilePosition < GetLength() )
306 if (m_iDataInBuffer <= 0)
308 m_pExtract->GetDataIO().SetUnpackToMemory(m_szBuffer,MAXWINMEMSIZE);
309 m_szStartOfBuffer = m_szBuffer;
310 m_iBufferStart = m_iFilePosition;
313 m_pExtract->GetDataIO().hBufferFilled->Set();
314 m_pExtract->GetDataIO().hBufferEmpty->Wait();
316 if (m_pExtract->GetDataIO().NextVolumeMissing)
319 m_iDataInBuffer = MAXWINMEMSIZE-m_pExtract->GetDataIO().UnpackToMemorySize;
321 if (m_iDataInBuffer < 0 ||
322 m_iDataInBuffer > MAXWINMEMSIZE - (m_szStartOfBuffer - m_szBuffer))
324 // invalid data returned by UnrarXLib, prevent a crash
325 CLog::Log(LOGERROR, "CRarFile::Read - Data buffer in inconsistent state");
329 if (m_iDataInBuffer == 0)
332 if (m_iDataInBuffer > uicBufSize)
334 memcpy(pBuf,m_szStartOfBuffer,int(uicBufSize));
335 m_szStartOfBuffer += uicBufSize;
336 pBuf += int(uicBufSize);
337 m_iFilePosition += uicBufSize;
338 m_iDataInBuffer -= int(uicBufSize);
343 memcpy(pBuf,m_szStartOfBuffer,size_t(m_iDataInBuffer));
344 m_iFilePosition += m_iDataInBuffer;
345 m_szStartOfBuffer += m_iDataInBuffer;
346 uicBufSize -= m_iDataInBuffer;
347 pBuf += m_iDataInBuffer;
352 m_pExtract->GetDataIO().hBufferEmpty->Set();
354 return static_cast<unsigned int>(uiBufSize-uicBufSize);
360 unsigned int CRarFile::Write(void *lpBuf, int64_t uiBufSize)
365 void CRarFile::Close()
367 #ifdef HAS_FILESYSTEM_RAR
374 g_RarManager.ClearCachedFile(m_strRarPath,m_strPathInRar);
380 if (m_pExtractThread)
382 delete m_pExtractThread;
383 m_pExtractThread = NULL;
390 int64_t CRarFile::Seek(int64_t iFilePosition, int iWhence)
392 #ifdef HAS_FILESYSTEM_RAR
400 return m_File.Seek(iFilePosition,iWhence);
402 if( !m_pExtract->GetDataIO().hBufferEmpty->WaitMSec(SEEKTIMOUT) )
404 CLog::Log(LOGERROR, "%s - Timeout waiting for buffer to empty", __FUNCTION__);
408 m_pExtract->GetDataIO().hBufferEmpty->Set();
413 if (iFilePosition == 0)
414 return m_iFilePosition; // happens sometimes
416 iFilePosition += m_iFilePosition;
419 if (iFilePosition == 0) // do not seek to end
421 m_iFilePosition = this->GetLength();
423 m_iBufferStart = this->GetLength();
425 return this->GetLength();
428 iFilePosition += GetLength();
435 if (iFilePosition > this->GetLength())
438 if (iFilePosition == m_iFilePosition) // happens a lot
439 return m_iFilePosition;
441 if ((iFilePosition >= m_iBufferStart) && (iFilePosition < m_iBufferStart+MAXWINMEMSIZE)
442 && (m_iDataInBuffer > 0)) // we are within current buffer
444 m_iDataInBuffer = MAXWINMEMSIZE-(iFilePosition-m_iBufferStart);
445 m_szStartOfBuffer = m_szBuffer+MAXWINMEMSIZE-m_iDataInBuffer;
446 m_iFilePosition = iFilePosition;
448 return m_iFilePosition;
451 if (iFilePosition < m_iBufferStart )
454 if (!OpenInArchive())
457 if( !m_pExtract->GetDataIO().hBufferEmpty->WaitMSec(SEEKTIMOUT) )
459 CLog::Log(LOGERROR, "%s - Timeout waiting for buffer to empty", __FUNCTION__);
462 m_pExtract->GetDataIO().hBufferEmpty->Set();
463 m_pExtract->GetDataIO().m_iSeekTo = iFilePosition;
466 m_pExtract->GetDataIO().m_iSeekTo = iFilePosition;
468 m_pExtract->GetDataIO().SetUnpackToMemory(m_szBuffer,MAXWINMEMSIZE);
469 m_pExtract->GetDataIO().hSeek->Set();
470 m_pExtract->GetDataIO().hBufferFilled->Set();
471 if( !m_pExtract->GetDataIO().hSeekDone->WaitMSec(SEEKTIMOUT))
473 CLog::Log(LOGERROR, "%s - Timeout waiting for seek to finish", __FUNCTION__);
477 if (m_pExtract->GetDataIO().NextVolumeMissing)
479 m_iFilePosition = m_iFileSize;
483 if( !m_pExtract->GetDataIO().hBufferEmpty->WaitMSec(SEEKTIMOUT) )
485 CLog::Log(LOGERROR, "%s - Timeout waiting for buffer to empty", __FUNCTION__);
488 m_iDataInBuffer = m_pExtract->GetDataIO().m_iSeekTo; // keep data
489 m_iBufferStart = m_pExtract->GetDataIO().m_iStartOfBuffer;
491 if (m_iDataInBuffer < 0 || m_iDataInBuffer > MAXWINMEMSIZE)
493 // invalid data returned by UnrarXLib, prevent a crash
494 CLog::Log(LOGERROR, "CRarFile::Seek - Data buffer in inconsistent state");
499 m_szStartOfBuffer = m_szBuffer+MAXWINMEMSIZE-m_iDataInBuffer;
500 m_iFilePosition = iFilePosition;
502 return m_iFilePosition;
508 int64_t CRarFile::GetLength()
514 return m_File.GetLength();
519 int64_t CRarFile::GetPosition()
525 return m_File.GetPosition();
527 return m_iFilePosition;
530 int CRarFile::Write(const void* lpBuf, int64_t uiBufSize)
535 void CRarFile::Flush()
541 void CRarFile::InitFromUrl(const CURL& url)
543 m_strCacheDir = g_advancedSettings.m_cachePath;//url.GetDomain();
544 URIUtils::AddSlashAtEnd(m_strCacheDir);
545 m_strRarPath = url.GetHostName();
546 m_strPassword = url.GetUserName();
547 m_strPathInRar = url.GetFileName();
549 vector<std::string> options;
550 StringUtils::Tokenize(url.GetOptions().Mid(1), options, "&");
554 for( vector<std::string>::iterator it = options.begin();it != options.end(); it++)
556 int iEqual = (*it).find('=');
559 CStdString strOption = StringUtils::Left((*it), iEqual);
560 CStdString strValue = StringUtils::Mid((*it), iEqual+1);
562 if( strOption.Equals("flags") )
563 m_bFileOptions = atoi(strValue.c_str());
564 else if( strOption.Equals("cache") )
565 m_strCacheDir = strValue;
571 void CRarFile::CleanUp()
573 #ifdef HAS_FILESYSTEM_RAR
576 if (m_pExtractThread)
578 if (m_pExtractThread->hRunning.WaitMSec(1))
580 m_pExtract->GetDataIO().hQuit->Set();
581 while (m_pExtractThread->hRunning.WaitMSec(1))
584 delete m_pExtract->GetDataIO().hBufferFilled;
585 delete m_pExtract->GetDataIO().hBufferEmpty;
586 delete m_pExtract->GetDataIO().hSeek;
587 delete m_pExtract->GetDataIO().hSeekDone;
588 delete m_pExtract->GetDataIO().hQuit;
609 m_szStartOfBuffer = NULL;
612 catch (int rarErrCode)
614 CLog::Log(LOGERROR,"filerar failed in UnrarXLib while deleting CFileRar with an UnrarXLib error code of %d",rarErrCode);
618 CLog::Log(LOGERROR,"filerar failed in UnrarXLib while deleting CFileRar with an Unknown exception");
623 bool CRarFile::OpenInArchive()
625 #ifdef HAS_FILESYSTEM_RAR
632 m_pCmd = new CommandData;
639 // Set the arguments for the extract command
640 strcpy(m_pCmd->Command, "X");
642 m_pCmd->AddArcName(const_cast<char*>(m_strRarPath.c_str()),NULL);
644 strncpy(m_pCmd->ExtrPath, m_strCacheDir.c_str(), sizeof (m_pCmd->ExtrPath) - 2);
645 m_pCmd->ExtrPath[sizeof (m_pCmd->ExtrPath) - 2] = 0;
646 AddEndSlash(m_pCmd->ExtrPath);
648 // Set password for encrypted archives
649 if ((m_strPassword.size() > 0) &&
650 (m_strPassword.size() < sizeof (m_pCmd->Password)))
652 strcpy(m_pCmd->Password, m_strPassword.c_str());
658 m_pArc = new Archive(m_pCmd);
664 if (!m_pArc->WOpen(m_strRarPath.c_str(),NULL))
669 if (!(m_pArc->IsOpened() && m_pArc->IsArchive(true)))
675 m_pExtract = new CmdExtract;
681 m_pExtract->GetDataIO().SetUnpackToMemory(m_szBuffer,0);
682 m_pExtract->GetDataIO().SetCurrentCommand(*(m_pCmd->Command));
684 if (FindFile::FastFind(m_strRarPath.c_str(),NULL,&FD))
685 m_pExtract->GetDataIO().TotalArcSize+=FD.Size;
686 m_pExtract->ExtractArchiveInit(m_pCmd,*m_pArc);
690 if ((iHeaderSize = m_pArc->ReadHeader()) <= 0)
696 if (m_pArc->GetHeaderType() == FILE_HEAD)
698 CStdString strFileName;
700 if (wcslen(m_pArc->NewLhd.FileNameW) > 0)
702 g_charsetConverter.wToUTF8(m_pArc->NewLhd.FileNameW, strFileName);
706 g_charsetConverter.unknownToUTF8(m_pArc->NewLhd.FileName, strFileName);
709 /* replace back slashes into forward slashes */
710 /* this could get us into troubles, file could two different files, one with / and one with \ */
711 strFileName.Replace('\\', '/');
713 if (strFileName == m_strPathInRar)
719 m_pArc->SeekToNext();
722 m_szBuffer = new uint8_t[MAXWINMEMSIZE];
723 m_szStartOfBuffer = m_szBuffer;
724 m_pExtract->GetDataIO().SetUnpackToMemory(m_szBuffer,0);
725 m_iDataInBuffer = -1;
729 delete m_pExtractThread;
730 m_pExtractThread = new CRarFileExtractThread();
731 m_pExtractThread->Start(m_pArc,m_pCmd,m_pExtract,iHeaderSize);
735 catch (int rarErrCode)
737 CLog::Log(LOGERROR,"filerar failed in UnrarXLib while CFileRar::OpenInArchive with an UnrarXLib error code of %d",rarErrCode);
742 CLog::Log(LOGERROR,"filerar failed in UnrarXLib while CFileRar::OpenInArchive with an Unknown exception");