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"
39 using namespace XFILE;
42 #define SEEKTIMOUT 30000
44 #ifdef HAS_FILESYSTEM_RAR
45 CRarFileExtractThread::CRarFileExtractThread() : CThread("CFileRarExtractThread"), hRunning(true), hQuit(true)
54 CRarFileExtractThread::~CRarFileExtractThread()
57 AbortableWait(hRestart);
61 void CRarFileExtractThread::Start(Archive* pArc, CommandData* pCmd, CmdExtract* pExtract, int iSize)
65 m_pExtract = pExtract;
68 m_pExtract->GetDataIO().hBufferFilled = new CEvent;
69 m_pExtract->GetDataIO().hBufferEmpty = new CEvent;
70 m_pExtract->GetDataIO().hSeek = new CEvent(true);
71 m_pExtract->GetDataIO().hSeekDone = new CEvent;
72 m_pExtract->GetDataIO().hQuit = new CEvent(true);
78 void CRarFileExtractThread::OnStartup()
82 void CRarFileExtractThread::OnExit()
86 void CRarFileExtractThread::Process()
88 while (AbortableWait(hQuit,1) != WAIT_SIGNALED)
90 if (AbortableWait(hRestart,1) == WAIT_SIGNALED)
95 m_pExtract->ExtractCurrentFile(m_pCmd,*m_pArc,m_iSize,Repeat);
97 catch (int rarErrCode)
99 CLog::Log(LOGERROR,"filerar CFileRarExtractThread::Process failed. CmdExtract::ExtractCurrentFile threw a UnrarXLib error code of %d",rarErrCode);
103 CLog::Log(LOGERROR,"filerar CFileRarExtractThread::Process failed. CmdExtract::ExtractCurrentFile threw an Unknown exception");
115 m_strCacheDir.Empty();
116 m_strRarPath.Empty();
117 m_strPassword.Empty();
118 m_strPathInRar.Empty();
120 #ifdef HAS_FILESYSTEM_RAR
124 m_pExtractThread = NULL;
127 m_szStartOfBuffer = NULL;
134 CRarFile::~CRarFile()
136 #ifdef HAS_FILESYSTEM_RAR
143 g_RarManager.ClearCachedFile(m_strRarPath,m_strPathInRar);
148 if (m_pExtractThread)
150 delete m_pExtractThread;
151 m_pExtractThread = NULL;
157 bool CRarFile::Open(const CURL& url)
161 g_RarManager.GetFilesInRar(items,m_strRarPath,false);
163 for (i=0;i<items.Size();++i)
165 if (items[i]->GetLabel() == m_strPathInRar)
171 if (items[i]->m_idepth == 0x30) // stored
173 if (!OpenInArchive())
176 m_iFileSize = items[i]->m_dwSize;
179 // perform 'noidx' check
180 CFileInfo* pFile = g_RarManager.GetFileInRar(m_strRarPath,m_strPathInRar);
183 if (pFile->m_iIsSeekable == -1)
185 if (Seek(-1,SEEK_END) == -1)
188 pFile->m_iIsSeekable = 0;
192 m_bSeekable = (pFile->m_iIsSeekable == 1);
198 CFileInfo* info = g_RarManager.GetFileInRar(m_strRarPath,m_strPathInRar);
199 if ((!info || !CFile::Exists(info->m_strCachedPath)) && m_bFileOptions & EXFILE_NOCACHE)
202 CStdString strPathInCache;
204 if (!g_RarManager.CacheRarredFile(strPathInCache, m_strRarPath, m_strPathInRar,
205 EXFILE_AUTODELETE | m_bFileOptions, m_strCacheDir,
208 CLog::Log(LOGERROR,"filerar::open failed to cache file %s",m_strPathInRar.c_str());
212 if (!m_File.Open( strPathInCache ))
214 CLog::Log(LOGERROR,"filerar::open failed to open file in cache: %s",strPathInCache.c_str());
225 bool CRarFile::Exists(const CURL& url)
230 // Make sure that the archive exists in the filesystem.
231 if (!CFile::Exists(m_strRarPath, false))
235 // Make sure that the requested file exists in the archive.
238 if (!g_RarManager.IsFileInRar(bResult, m_strRarPath, m_strPathInRar))
244 int CRarFile::Stat(const CURL& url, struct __stat64* buffer)
246 memset(buffer, 0, sizeof(struct __stat64));
249 buffer->st_size = GetLength();
250 buffer->st_mode = _S_IFREG;
256 if (CDirectory::Exists(url.Get()))
258 buffer->st_mode = _S_IFDIR;
266 bool CRarFile::OpenForWrite(const CURL& url)
271 unsigned int CRarFile::Read(void *lpBuf, int64_t uiBufSize)
273 #ifdef HAS_FILESYSTEM_RAR
278 return m_File.Read(lpBuf,uiBufSize);
280 if (m_iFilePosition >= GetLength()) // we are done
283 if( !m_pExtract->GetDataIO().hBufferEmpty->WaitMSec(5000) )
285 CLog::Log(LOGERROR, "%s - Timeout waiting for buffer to empty", __FUNCTION__);
290 byte* pBuf = (byte*)lpBuf;
291 int64_t uicBufSize = uiBufSize;
292 if (m_iDataInBuffer > 0)
294 int64_t iCopy = uiBufSize<m_iDataInBuffer?uiBufSize:m_iDataInBuffer;
295 memcpy(lpBuf,m_szStartOfBuffer,size_t(iCopy));
296 m_szStartOfBuffer += iCopy;
297 m_iDataInBuffer -= int(iCopy);
300 m_iFilePosition += iCopy;
303 while ((uicBufSize > 0) && m_iFilePosition < GetLength() )
305 if (m_iDataInBuffer <= 0)
307 m_pExtract->GetDataIO().SetUnpackToMemory(m_szBuffer,MAXWINMEMSIZE);
308 m_szStartOfBuffer = m_szBuffer;
309 m_iBufferStart = m_iFilePosition;
312 m_pExtract->GetDataIO().hBufferFilled->Set();
313 m_pExtract->GetDataIO().hBufferEmpty->Wait();
315 if (m_pExtract->GetDataIO().NextVolumeMissing)
318 m_iDataInBuffer = MAXWINMEMSIZE-m_pExtract->GetDataIO().UnpackToMemorySize;
320 if (m_iDataInBuffer < 0 ||
321 m_iDataInBuffer > MAXWINMEMSIZE - (m_szStartOfBuffer - m_szBuffer))
323 // invalid data returned by UnrarXLib, prevent a crash
324 CLog::Log(LOGERROR, "CRarFile::Read - Data buffer in inconsistent state");
328 if (m_iDataInBuffer == 0)
331 if (m_iDataInBuffer > uicBufSize)
333 memcpy(pBuf,m_szStartOfBuffer,int(uicBufSize));
334 m_szStartOfBuffer += uicBufSize;
335 pBuf += int(uicBufSize);
336 m_iFilePosition += uicBufSize;
337 m_iDataInBuffer -= int(uicBufSize);
342 memcpy(pBuf,m_szStartOfBuffer,size_t(m_iDataInBuffer));
343 m_iFilePosition += m_iDataInBuffer;
344 m_szStartOfBuffer += m_iDataInBuffer;
345 uicBufSize -= m_iDataInBuffer;
346 pBuf += m_iDataInBuffer;
351 m_pExtract->GetDataIO().hBufferEmpty->Set();
353 return static_cast<unsigned int>(uiBufSize-uicBufSize);
359 unsigned int CRarFile::Write(void *lpBuf, int64_t uiBufSize)
364 void CRarFile::Close()
366 #ifdef HAS_FILESYSTEM_RAR
373 g_RarManager.ClearCachedFile(m_strRarPath,m_strPathInRar);
379 if (m_pExtractThread)
381 delete m_pExtractThread;
382 m_pExtractThread = NULL;
389 int64_t CRarFile::Seek(int64_t iFilePosition, int iWhence)
391 #ifdef HAS_FILESYSTEM_RAR
399 return m_File.Seek(iFilePosition,iWhence);
401 if( !m_pExtract->GetDataIO().hBufferEmpty->WaitMSec(SEEKTIMOUT) )
403 CLog::Log(LOGERROR, "%s - Timeout waiting for buffer to empty", __FUNCTION__);
407 m_pExtract->GetDataIO().hBufferEmpty->Set();
412 if (iFilePosition == 0)
413 return m_iFilePosition; // happens sometimes
415 iFilePosition += m_iFilePosition;
418 if (iFilePosition == 0) // do not seek to end
420 m_iFilePosition = this->GetLength();
422 m_iBufferStart = this->GetLength();
424 return this->GetLength();
427 iFilePosition += GetLength();
434 if (iFilePosition > this->GetLength())
437 if (iFilePosition == m_iFilePosition) // happens a lot
438 return m_iFilePosition;
440 if ((iFilePosition >= m_iBufferStart) && (iFilePosition < m_iBufferStart+MAXWINMEMSIZE)
441 && (m_iDataInBuffer > 0)) // we are within current buffer
443 m_iDataInBuffer = MAXWINMEMSIZE-(iFilePosition-m_iBufferStart);
444 m_szStartOfBuffer = m_szBuffer+MAXWINMEMSIZE-m_iDataInBuffer;
445 m_iFilePosition = iFilePosition;
447 return m_iFilePosition;
450 if (iFilePosition < m_iBufferStart )
453 if (!OpenInArchive())
456 if( !m_pExtract->GetDataIO().hBufferEmpty->WaitMSec(SEEKTIMOUT) )
458 CLog::Log(LOGERROR, "%s - Timeout waiting for buffer to empty", __FUNCTION__);
461 m_pExtract->GetDataIO().hBufferEmpty->Set();
462 m_pExtract->GetDataIO().m_iSeekTo = iFilePosition;
465 m_pExtract->GetDataIO().m_iSeekTo = iFilePosition;
467 m_pExtract->GetDataIO().SetUnpackToMemory(m_szBuffer,MAXWINMEMSIZE);
468 m_pExtract->GetDataIO().hSeek->Set();
469 m_pExtract->GetDataIO().hBufferFilled->Set();
470 if( !m_pExtract->GetDataIO().hSeekDone->WaitMSec(SEEKTIMOUT))
472 CLog::Log(LOGERROR, "%s - Timeout waiting for seek to finish", __FUNCTION__);
476 if (m_pExtract->GetDataIO().NextVolumeMissing)
478 m_iFilePosition = m_iFileSize;
482 if( !m_pExtract->GetDataIO().hBufferEmpty->WaitMSec(SEEKTIMOUT) )
484 CLog::Log(LOGERROR, "%s - Timeout waiting for buffer to empty", __FUNCTION__);
487 m_iDataInBuffer = m_pExtract->GetDataIO().m_iSeekTo; // keep data
488 m_iBufferStart = m_pExtract->GetDataIO().m_iStartOfBuffer;
490 if (m_iDataInBuffer < 0 || m_iDataInBuffer > MAXWINMEMSIZE)
492 // invalid data returned by UnrarXLib, prevent a crash
493 CLog::Log(LOGERROR, "CRarFile::Seek - Data buffer in inconsistent state");
498 m_szStartOfBuffer = m_szBuffer+MAXWINMEMSIZE-m_iDataInBuffer;
499 m_iFilePosition = iFilePosition;
501 return m_iFilePosition;
507 int64_t CRarFile::GetLength()
513 return m_File.GetLength();
518 int64_t CRarFile::GetPosition()
524 return m_File.GetPosition();
526 return m_iFilePosition;
529 int CRarFile::Write(const void* lpBuf, int64_t uiBufSize)
534 void CRarFile::Flush()
540 void CRarFile::InitFromUrl(const CURL& url)
542 m_strCacheDir = g_advancedSettings.m_cachePath;//url.GetDomain();
543 URIUtils::AddSlashAtEnd(m_strCacheDir);
544 m_strRarPath = url.GetHostName();
545 m_strPassword = url.GetUserName();
546 m_strPathInRar = url.GetFileName();
548 vector<CStdString> options;
549 CUtil::Tokenize(url.GetOptions().Mid(1), options, "&");
553 for( vector<CStdString>::iterator it = options.begin();it != options.end(); it++)
555 int iEqual = (*it).Find('=');
558 CStdString strOption = (*it).Left(iEqual);
559 CStdString strValue = (*it).Mid(iEqual+1);
561 if( strOption.Equals("flags") )
562 m_bFileOptions = atoi(strValue.c_str());
563 else if( strOption.Equals("cache") )
564 m_strCacheDir = strValue;
570 void CRarFile::CleanUp()
572 #ifdef HAS_FILESYSTEM_RAR
575 if (m_pExtractThread)
577 if (m_pExtractThread->hRunning.WaitMSec(1))
579 m_pExtract->GetDataIO().hQuit->Set();
580 while (m_pExtractThread->hRunning.WaitMSec(1))
583 delete m_pExtract->GetDataIO().hBufferFilled;
584 delete m_pExtract->GetDataIO().hBufferEmpty;
585 delete m_pExtract->GetDataIO().hSeek;
586 delete m_pExtract->GetDataIO().hSeekDone;
587 delete m_pExtract->GetDataIO().hQuit;
608 m_szStartOfBuffer = NULL;
611 catch (int rarErrCode)
613 CLog::Log(LOGERROR,"filerar failed in UnrarXLib while deleting CFileRar with an UnrarXLib error code of %d",rarErrCode);
617 CLog::Log(LOGERROR,"filerar failed in UnrarXLib while deleting CFileRar with an Unknown exception");
622 bool CRarFile::OpenInArchive()
624 #ifdef HAS_FILESYSTEM_RAR
631 m_pCmd = new CommandData;
638 // Set the arguments for the extract command
639 strcpy(m_pCmd->Command, "X");
641 m_pCmd->AddArcName(const_cast<char*>(m_strRarPath.c_str()),NULL);
643 strncpy(m_pCmd->ExtrPath, m_strCacheDir.c_str(), sizeof (m_pCmd->ExtrPath) - 2);
644 m_pCmd->ExtrPath[sizeof (m_pCmd->ExtrPath) - 2] = 0;
645 AddEndSlash(m_pCmd->ExtrPath);
647 // Set password for encrypted archives
648 if ((m_strPassword.size() > 0) &&
649 (m_strPassword.size() < sizeof (m_pCmd->Password)))
651 strcpy(m_pCmd->Password, m_strPassword.c_str());
657 m_pArc = new Archive(m_pCmd);
663 if (!m_pArc->WOpen(m_strRarPath.c_str(),NULL))
668 if (!(m_pArc->IsOpened() && m_pArc->IsArchive(true)))
674 m_pExtract = new CmdExtract;
680 m_pExtract->GetDataIO().SetUnpackToMemory(m_szBuffer,0);
681 m_pExtract->GetDataIO().SetCurrentCommand(*(m_pCmd->Command));
683 if (FindFile::FastFind(m_strRarPath.c_str(),NULL,&FD))
684 m_pExtract->GetDataIO().TotalArcSize+=FD.Size;
685 m_pExtract->ExtractArchiveInit(m_pCmd,*m_pArc);
689 if ((iHeaderSize = m_pArc->ReadHeader()) <= 0)
695 if (m_pArc->GetHeaderType() == FILE_HEAD)
697 CStdString strFileName;
699 if (wcslen(m_pArc->NewLhd.FileNameW) > 0)
701 g_charsetConverter.wToUTF8(m_pArc->NewLhd.FileNameW, strFileName);
705 g_charsetConverter.unknownToUTF8(m_pArc->NewLhd.FileName, strFileName);
708 /* replace back slashes into forward slashes */
709 /* this could get us into troubles, file could two different files, one with / and one with \ */
710 strFileName.Replace('\\', '/');
712 if (strFileName == m_strPathInRar)
718 m_pArc->SeekToNext();
721 m_szBuffer = new byte[MAXWINMEMSIZE];
722 m_szStartOfBuffer = m_szBuffer;
723 m_pExtract->GetDataIO().SetUnpackToMemory(m_szBuffer,0);
724 m_iDataInBuffer = -1;
728 delete m_pExtractThread;
729 m_pExtractThread = new CRarFileExtractThread();
730 m_pExtractThread->Start(m_pArc,m_pCmd,m_pExtract,iHeaderSize);
734 catch (int rarErrCode)
736 CLog::Log(LOGERROR,"filerar failed in UnrarXLib while CFileRar::OpenInArchive with an UnrarXLib error code of %d",rarErrCode);
741 CLog::Log(LOGERROR,"filerar failed in UnrarXLib while CFileRar::OpenInArchive with an Unknown exception");