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 if (!url
.GetOptions().empty())
551 StringUtils::Tokenize(url
.GetOptions().substr(1), options
, "&");
555 for( vector
<std::string
>::iterator it
= options
.begin();it
!= options
.end(); it
++)
557 size_t iEqual
= (*it
).find('=');
558 if(iEqual
!= std::string::npos
)
560 CStdString strOption
= StringUtils::Left((*it
), iEqual
);
561 CStdString strValue
= StringUtils::Mid((*it
), iEqual
+1);
563 if( strOption
.Equals("flags") )
564 m_bFileOptions
= atoi(strValue
.c_str());
565 else if( strOption
.Equals("cache") )
566 m_strCacheDir
= strValue
;
572 void CRarFile::CleanUp()
574 #ifdef HAS_FILESYSTEM_RAR
577 if (m_pExtractThread
)
579 if (m_pExtractThread
->hRunning
.WaitMSec(1))
581 m_pExtract
->GetDataIO().hQuit
->Set();
582 while (m_pExtractThread
->hRunning
.WaitMSec(1))
585 delete m_pExtract
->GetDataIO().hBufferFilled
;
586 delete m_pExtract
->GetDataIO().hBufferEmpty
;
587 delete m_pExtract
->GetDataIO().hSeek
;
588 delete m_pExtract
->GetDataIO().hSeekDone
;
589 delete m_pExtract
->GetDataIO().hQuit
;
610 m_szStartOfBuffer
= NULL
;
613 catch (int rarErrCode
)
615 CLog::Log(LOGERROR
,"filerar failed in UnrarXLib while deleting CFileRar with an UnrarXLib error code of %d",rarErrCode
);
619 CLog::Log(LOGERROR
,"filerar failed in UnrarXLib while deleting CFileRar with an Unknown exception");
624 bool CRarFile::OpenInArchive()
626 #ifdef HAS_FILESYSTEM_RAR
633 m_pCmd
= new CommandData
;
640 // Set the arguments for the extract command
641 strcpy(m_pCmd
->Command
, "X");
643 m_pCmd
->AddArcName(const_cast<char*>(m_strRarPath
.c_str()),NULL
);
645 strncpy(m_pCmd
->ExtrPath
, m_strCacheDir
.c_str(), sizeof (m_pCmd
->ExtrPath
) - 2);
646 m_pCmd
->ExtrPath
[sizeof (m_pCmd
->ExtrPath
) - 2] = 0;
647 AddEndSlash(m_pCmd
->ExtrPath
);
649 // Set password for encrypted archives
650 if ((m_strPassword
.size() > 0) &&
651 (m_strPassword
.size() < sizeof (m_pCmd
->Password
)))
653 strcpy(m_pCmd
->Password
, m_strPassword
.c_str());
659 m_pArc
= new Archive(m_pCmd
);
665 if (!m_pArc
->WOpen(m_strRarPath
.c_str(),NULL
))
670 if (!(m_pArc
->IsOpened() && m_pArc
->IsArchive(true)))
676 m_pExtract
= new CmdExtract
;
682 m_pExtract
->GetDataIO().SetUnpackToMemory(m_szBuffer
,0);
683 m_pExtract
->GetDataIO().SetCurrentCommand(*(m_pCmd
->Command
));
685 if (FindFile::FastFind(m_strRarPath
.c_str(),NULL
,&FD
))
686 m_pExtract
->GetDataIO().TotalArcSize
+=FD
.Size
;
687 m_pExtract
->ExtractArchiveInit(m_pCmd
,*m_pArc
);
691 if ((iHeaderSize
= m_pArc
->ReadHeader()) <= 0)
697 if (m_pArc
->GetHeaderType() == FILE_HEAD
)
699 CStdString strFileName
;
701 if (wcslen(m_pArc
->NewLhd
.FileNameW
) > 0)
703 g_charsetConverter
.wToUTF8(m_pArc
->NewLhd
.FileNameW
, strFileName
);
707 g_charsetConverter
.unknownToUTF8(m_pArc
->NewLhd
.FileName
, strFileName
);
710 /* replace back slashes into forward slashes */
711 /* this could get us into troubles, file could two different files, one with / and one with \ */
712 StringUtils::Replace(strFileName
, '\\', '/');
714 if (strFileName
== m_strPathInRar
)
720 m_pArc
->SeekToNext();
723 m_szBuffer
= new uint8_t[MAXWINMEMSIZE
];
724 m_szStartOfBuffer
= m_szBuffer
;
725 m_pExtract
->GetDataIO().SetUnpackToMemory(m_szBuffer
,0);
726 m_iDataInBuffer
= -1;
730 delete m_pExtractThread
;
731 m_pExtractThread
= new CRarFileExtractThread();
732 m_pExtractThread
->Start(m_pArc
,m_pCmd
,m_pExtract
,iHeaderSize
);
736 catch (int rarErrCode
)
738 CLog::Log(LOGERROR
,"filerar failed in UnrarXLib while CFileRar::OpenInArchive with an UnrarXLib error code of %d",rarErrCode
);
743 CLog::Log(LOGERROR
,"filerar failed in UnrarXLib while CFileRar::OpenInArchive with an Unknown exception");