Merge pull request #4594 from FernetMenta/paplayer
[xbmc.git] / xbmc / filesystem / RarFile.cpp
blob077b905fa136cf4c9d7d1c1bcb2ea61c737083a3
1 /*
2 * Copyright (C) 2005-2013 Team XBMC
3 * http://xbmc.org
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)
8 * any later version.
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/>.
21 #include "system.h"
22 #include "RarFile.h"
23 #include <sys/stat.h>
24 #include "Util.h"
25 #include "utils/CharsetConverter.h"
26 #include "utils/URIUtils.h"
27 #include "URL.h"
28 #include "Directory.h"
29 #include "RarManager.h"
30 #include "settings/AdvancedSettings.h"
31 #include "FileItem.h"
32 #include "utils/log.h"
33 #include "UnrarXLib/rar.hpp"
34 #include "utils/StringUtils.h"
36 #ifndef TARGET_POSIX
37 #include <process.h>
38 #endif
40 using namespace XFILE;
41 using namespace std;
43 #define SEEKTIMOUT 30000
45 #ifdef HAS_FILESYSTEM_RAR
46 CRarFileExtractThread::CRarFileExtractThread() : CThread("RarFileExtract"), hRunning(true), hQuit(true)
48 m_pArc = NULL;
49 m_pCmd = NULL;
50 m_pExtract = NULL;
51 StopThread();
52 Create();
55 CRarFileExtractThread::~CRarFileExtractThread()
57 hQuit.Set();
58 AbortableWait(hRestart);
59 StopThread();
62 void CRarFileExtractThread::Start(Archive* pArc, CommandData* pCmd, CmdExtract* pExtract, int iSize)
64 m_pArc = pArc;
65 m_pCmd = pCmd;
66 m_pExtract = pExtract;
67 m_iSize = iSize;
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);
75 hRunning.Set();
76 hRestart.Set();
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)
93 bool Repeat = false;
94 try
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);
102 catch (...)
104 CLog::Log(LOGERROR,"filerar CFileRarExtractThread::Process failed. CmdExtract::ExtractCurrentFile threw an Unknown exception");
107 hRunning.Reset();
110 hRestart.Set();
112 #endif
114 CRarFile::CRarFile()
116 m_strCacheDir.clear();
117 m_strRarPath.clear();
118 m_strPassword.clear();
119 m_strPathInRar.clear();
120 m_bFileOptions = 0;
121 #ifdef HAS_FILESYSTEM_RAR
122 m_pArc = NULL;
123 m_pCmd = NULL;
124 m_pExtract = NULL;
125 m_pExtractThread = NULL;
126 #endif
127 m_szBuffer = NULL;
128 m_szStartOfBuffer = NULL;
129 m_iDataInBuffer = 0;
130 m_bUseFile = false;
131 m_bOpen = false;
132 m_bSeekable = true;
135 CRarFile::~CRarFile()
137 #ifdef HAS_FILESYSTEM_RAR
138 if (!m_bOpen)
139 return;
141 if (m_bUseFile)
143 m_File.Close();
144 g_RarManager.ClearCachedFile(m_strRarPath,m_strPathInRar);
146 else
148 CleanUp();
149 if (m_pExtractThread)
151 delete m_pExtractThread;
152 m_pExtractThread = NULL;
155 #endif
158 bool CRarFile::Open(const CURL& url)
160 InitFromUrl(url);
161 CFileItemList items;
162 g_RarManager.GetFilesInRar(items,m_strRarPath,false);
163 int i;
164 for (i=0;i<items.Size();++i)
166 if (items[i]->GetLabel() == m_strPathInRar)
167 break;
170 if (i<items.Size())
172 if (items[i]->m_idepth == 0x30) // stored
174 if (!OpenInArchive())
175 return false;
177 m_iFileSize = items[i]->m_dwSize;
178 m_bOpen = true;
180 // perform 'noidx' check
181 CFileInfo* pFile = g_RarManager.GetFileInRar(m_strRarPath,m_strPathInRar);
182 if (pFile)
184 if (pFile->m_iIsSeekable == -1)
186 if (Seek(-1,SEEK_END) == -1)
188 m_bSeekable = false;
189 pFile->m_iIsSeekable = 0;
192 else
193 m_bSeekable = (pFile->m_iIsSeekable == 1);
195 return true;
197 else
199 CFileInfo* info = g_RarManager.GetFileInRar(m_strRarPath,m_strPathInRar);
200 if ((!info || !CFile::Exists(info->m_strCachedPath)) && m_bFileOptions & EXFILE_NOCACHE)
201 return false;
202 m_bUseFile = true;
203 CStdString strPathInCache;
205 if (!g_RarManager.CacheRarredFile(strPathInCache, m_strRarPath, m_strPathInRar,
206 EXFILE_AUTODELETE | m_bFileOptions, m_strCacheDir,
207 items[i]->m_dwSize))
209 CLog::Log(LOGERROR,"filerar::open failed to cache file %s",m_strPathInRar.c_str());
210 return false;
213 if (!m_File.Open( strPathInCache ))
215 CLog::Log(LOGERROR,"filerar::open failed to open file in cache: %s",strPathInCache.c_str());
216 return false;
219 m_bOpen = true;
220 return true;
223 return false;
226 bool CRarFile::Exists(const CURL& url)
228 InitFromUrl(url);
230 // First step:
231 // Make sure that the archive exists in the filesystem.
232 if (!CFile::Exists(m_strRarPath, false))
233 return false;
235 // Second step:
236 // Make sure that the requested file exists in the archive.
237 bool bResult;
239 if (!g_RarManager.IsFileInRar(bResult, m_strRarPath, m_strPathInRar))
240 return false;
242 return bResult;
245 int CRarFile::Stat(const CURL& url, struct __stat64* buffer)
247 memset(buffer, 0, sizeof(struct __stat64));
248 if (Open(url))
250 buffer->st_size = GetLength();
251 buffer->st_mode = _S_IFREG;
252 Close();
253 errno = 0;
254 return 0;
257 if (CDirectory::Exists(url.Get()))
259 buffer->st_mode = _S_IFDIR;
260 return 0;
263 errno = ENOENT;
264 return -1;
267 bool CRarFile::OpenForWrite(const CURL& url)
269 return false;
272 unsigned int CRarFile::Read(void *lpBuf, int64_t uiBufSize)
274 #ifdef HAS_FILESYSTEM_RAR
275 if (!m_bOpen)
276 return 0;
278 if (m_bUseFile)
279 return m_File.Read(lpBuf,uiBufSize);
281 if (m_iFilePosition >= GetLength()) // we are done
282 return 0;
284 if( !m_pExtract->GetDataIO().hBufferEmpty->WaitMSec(5000) )
286 CLog::Log(LOGERROR, "%s - Timeout waiting for buffer to empty", __FUNCTION__);
287 return 0;
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);
299 pBuf += iCopy;
300 uicBufSize -= 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)
317 break;
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");
326 m_iDataInBuffer = 0;
329 if (m_iDataInBuffer == 0)
330 break;
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);
339 uicBufSize = 0;
341 else
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;
348 m_iDataInBuffer = 0;
352 m_pExtract->GetDataIO().hBufferEmpty->Set();
354 return static_cast<unsigned int>(uiBufSize-uicBufSize);
355 #else
356 return 0;
357 #endif
360 unsigned int CRarFile::Write(void *lpBuf, int64_t uiBufSize)
362 return 0;
365 void CRarFile::Close()
367 #ifdef HAS_FILESYSTEM_RAR
368 if (!m_bOpen)
369 return;
371 if (m_bUseFile)
373 m_File.Close();
374 g_RarManager.ClearCachedFile(m_strRarPath,m_strPathInRar);
375 m_bOpen = false;
377 else
379 CleanUp();
380 if (m_pExtractThread)
382 delete m_pExtractThread;
383 m_pExtractThread = NULL;
385 m_bOpen = false;
387 #endif
390 int64_t CRarFile::Seek(int64_t iFilePosition, int iWhence)
392 #ifdef HAS_FILESYSTEM_RAR
393 if (!m_bOpen)
394 return -1;
396 if (!m_bSeekable)
397 return -1;
399 if (m_bUseFile)
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__);
405 return -1;
408 m_pExtract->GetDataIO().hBufferEmpty->Set();
410 switch (iWhence)
412 case SEEK_CUR:
413 if (iFilePosition == 0)
414 return m_iFilePosition; // happens sometimes
416 iFilePosition += m_iFilePosition;
417 break;
418 case SEEK_END:
419 if (iFilePosition == 0) // do not seek to end
421 m_iFilePosition = this->GetLength();
422 m_iDataInBuffer = 0;
423 m_iBufferStart = this->GetLength();
425 return this->GetLength();
428 iFilePosition += GetLength();
429 case SEEK_SET:
430 break;
431 default:
432 return -1;
435 if (iFilePosition > this->GetLength())
436 return -1;
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 )
453 CleanUp();
454 if (!OpenInArchive())
455 return -1;
457 if( !m_pExtract->GetDataIO().hBufferEmpty->WaitMSec(SEEKTIMOUT) )
459 CLog::Log(LOGERROR, "%s - Timeout waiting for buffer to empty", __FUNCTION__);
460 return -1;
462 m_pExtract->GetDataIO().hBufferEmpty->Set();
463 m_pExtract->GetDataIO().m_iSeekTo = iFilePosition;
465 else
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__);
474 return -1;
477 if (m_pExtract->GetDataIO().NextVolumeMissing)
479 m_iFilePosition = m_iFileSize;
480 return -1;
483 if( !m_pExtract->GetDataIO().hBufferEmpty->WaitMSec(SEEKTIMOUT) )
485 CLog::Log(LOGERROR, "%s - Timeout waiting for buffer to empty", __FUNCTION__);
486 return -1;
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");
495 m_iDataInBuffer = 0;
496 return -1;
499 m_szStartOfBuffer = m_szBuffer+MAXWINMEMSIZE-m_iDataInBuffer;
500 m_iFilePosition = iFilePosition;
502 return m_iFilePosition;
503 #else
504 return -1;
505 #endif
508 int64_t CRarFile::GetLength()
510 if (!m_bOpen)
511 return 0;
513 if (m_bUseFile)
514 return m_File.GetLength();
516 return m_iFileSize;
519 int64_t CRarFile::GetPosition()
521 if (!m_bOpen)
522 return -1;
524 if (m_bUseFile)
525 return m_File.GetPosition();
527 return m_iFilePosition;
530 int CRarFile::Write(const void* lpBuf, int64_t uiBufSize)
532 return -1;
535 void CRarFile::Flush()
537 if (m_bUseFile)
538 m_File.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, "&");
553 m_bFileOptions = 0;
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))
583 Sleep(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;
591 if (m_pExtract)
593 delete m_pExtract;
594 m_pExtract = NULL;
596 if (m_pArc)
598 delete m_pArc;
599 m_pArc = NULL;
601 if (m_pCmd)
603 delete m_pCmd;
604 m_pCmd = NULL;
606 if (m_szBuffer)
608 delete[] m_szBuffer;
609 m_szBuffer = NULL;
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);
617 catch (...)
619 CLog::Log(LOGERROR,"filerar failed in UnrarXLib while deleting CFileRar with an Unknown exception");
621 #endif
624 bool CRarFile::OpenInArchive()
626 #ifdef HAS_FILESYSTEM_RAR
629 int iHeaderSize;
631 InitCRC();
633 m_pCmd = new CommandData;
634 if (!m_pCmd)
636 CleanUp();
637 return false;
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());
656 m_pCmd->ParseDone();
658 // Open the archive
659 m_pArc = new Archive(m_pCmd);
660 if (!m_pArc)
662 CleanUp();
663 return false;
665 if (!m_pArc->WOpen(m_strRarPath.c_str(),NULL))
667 CleanUp();
668 return false;
670 if (!(m_pArc->IsOpened() && m_pArc->IsArchive(true)))
672 CleanUp();
673 return false;
676 m_pExtract = new CmdExtract;
677 if (!m_pExtract)
679 CleanUp();
680 return false;
682 m_pExtract->GetDataIO().SetUnpackToMemory(m_szBuffer,0);
683 m_pExtract->GetDataIO().SetCurrentCommand(*(m_pCmd->Command));
684 struct FindData FD;
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);
689 while (true)
691 if ((iHeaderSize = m_pArc->ReadHeader()) <= 0)
693 CleanUp();
694 return false;
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);
705 else
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)
716 break;
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;
727 m_iFilePosition = 0;
728 m_iBufferStart = 0;
730 delete m_pExtractThread;
731 m_pExtractThread = new CRarFileExtractThread();
732 m_pExtractThread->Start(m_pArc,m_pCmd,m_pExtract,iHeaderSize);
734 return true;
736 catch (int rarErrCode)
738 CLog::Log(LOGERROR,"filerar failed in UnrarXLib while CFileRar::OpenInArchive with an UnrarXLib error code of %d",rarErrCode);
739 return false;
741 catch (...)
743 CLog::Log(LOGERROR,"filerar failed in UnrarXLib while CFileRar::OpenInArchive with an Unknown exception");
744 return false;
746 #else
747 return false;
748 #endif