[cstdstring] remove CStdString::Empty and replace with std::string::clear
[vuplus_xbmc] / xbmc / filesystem / RarFile.cpp
1 /*
2  *      Copyright (C) 2005-2013 Team XBMC
3  *      http://xbmc.org
4  *
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.
9  *
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.
14  *
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/>.
18  *
19  */
20
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"
35
36 #ifndef TARGET_POSIX
37 #include <process.h>
38 #endif
39
40 using namespace XFILE;
41 using namespace std;
42
43 #define SEEKTIMOUT 30000
44
45 #ifdef HAS_FILESYSTEM_RAR
46 CRarFileExtractThread::CRarFileExtractThread() : CThread("RarFileExtract"), hRunning(true), hQuit(true)
47 {
48   m_pArc = NULL;
49   m_pCmd = NULL;
50   m_pExtract = NULL;
51   StopThread();
52   Create();
53 }
54
55 CRarFileExtractThread::~CRarFileExtractThread()
56 {
57   hQuit.Set();
58   AbortableWait(hRestart);
59   StopThread();
60 }
61
62 void CRarFileExtractThread::Start(Archive* pArc, CommandData* pCmd, CmdExtract* pExtract, int iSize)
63 {
64   m_pArc = pArc;
65   m_pCmd = pCmd;
66   m_pExtract = pExtract;
67   m_iSize = iSize;
68
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);
74
75   hRunning.Set();
76   hRestart.Set();
77 }
78
79 void CRarFileExtractThread::OnStartup()
80 {
81 }
82
83 void CRarFileExtractThread::OnExit()
84 {
85 }
86
87 void CRarFileExtractThread::Process()
88 {
89   while (AbortableWait(hQuit,1) != WAIT_SIGNALED)
90   {
91     if (AbortableWait(hRestart,1) == WAIT_SIGNALED)
92     {
93       bool Repeat = false;
94       try
95       {
96         m_pExtract->ExtractCurrentFile(m_pCmd,*m_pArc,m_iSize,Repeat);
97       }
98       catch (int rarErrCode)
99       {
100         CLog::Log(LOGERROR,"filerar CFileRarExtractThread::Process failed. CmdExtract::ExtractCurrentFile threw a UnrarXLib error code of %d",rarErrCode);
101       }
102       catch (...)
103       {
104         CLog::Log(LOGERROR,"filerar CFileRarExtractThread::Process failed. CmdExtract::ExtractCurrentFile threw an Unknown exception");
105       }
106
107       hRunning.Reset();
108     }
109   }
110   hRestart.Set();
111 }
112 #endif
113
114 CRarFile::CRarFile()
115 {
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;
133 }
134
135 CRarFile::~CRarFile()
136 {
137 #ifdef HAS_FILESYSTEM_RAR
138   if (!m_bOpen)
139     return;
140
141   if (m_bUseFile)
142   {
143     m_File.Close();
144     g_RarManager.ClearCachedFile(m_strRarPath,m_strPathInRar);
145   }
146   else
147   {
148     CleanUp();
149     if (m_pExtractThread)
150     {
151       delete m_pExtractThread;
152       m_pExtractThread = NULL;
153     }
154   }
155 #endif
156 }
157
158 bool CRarFile::Open(const CURL& url)
159 {
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)
165   {
166     if (items[i]->GetLabel() == m_strPathInRar)
167       break;
168   }
169
170   if (i<items.Size())
171   {
172     if (items[i]->m_idepth == 0x30) // stored
173     {
174       if (!OpenInArchive())
175         return false;
176
177       m_iFileSize = items[i]->m_dwSize;
178       m_bOpen = true;
179
180       // perform 'noidx' check
181       CFileInfo* pFile = g_RarManager.GetFileInRar(m_strRarPath,m_strPathInRar);
182       if (pFile)
183       {
184         if (pFile->m_iIsSeekable == -1)
185         {
186           if (Seek(-1,SEEK_END) == -1)
187           {
188             m_bSeekable = false;
189             pFile->m_iIsSeekable = 0;
190           }
191         }
192         else
193           m_bSeekable = (pFile->m_iIsSeekable == 1);
194       }
195       return true;
196     }
197     else
198     {
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;
204
205       if (!g_RarManager.CacheRarredFile(strPathInCache, m_strRarPath, m_strPathInRar,
206                                         EXFILE_AUTODELETE | m_bFileOptions, m_strCacheDir,
207                                         items[i]->m_dwSize))
208       {
209         CLog::Log(LOGERROR,"filerar::open failed to cache file %s",m_strPathInRar.c_str());
210         return false;
211       }
212
213       if (!m_File.Open( strPathInCache ))
214       {
215         CLog::Log(LOGERROR,"filerar::open failed to open file in cache: %s",strPathInCache.c_str());
216         return false;
217       }
218
219       m_bOpen = true;
220       return true;
221     }
222   }
223   return false;
224 }
225
226 bool CRarFile::Exists(const CURL& url)
227 {
228   InitFromUrl(url);
229   
230   // First step:
231   // Make sure that the archive exists in the filesystem.
232   if (!CFile::Exists(m_strRarPath, false)) 
233     return false;
234
235   // Second step:
236   // Make sure that the requested file exists in the archive.
237   bool bResult;
238
239   if (!g_RarManager.IsFileInRar(bResult, m_strRarPath, m_strPathInRar))
240     return false;
241
242   return bResult;
243 }
244
245 int CRarFile::Stat(const CURL& url, struct __stat64* buffer)
246 {
247   memset(buffer, 0, sizeof(struct __stat64));
248   if (Open(url))
249   {
250     buffer->st_size = GetLength();
251     buffer->st_mode = _S_IFREG;
252     Close();
253     errno = 0;
254     return 0;
255   }
256
257   if (CDirectory::Exists(url.Get()))
258   {
259     buffer->st_mode = _S_IFDIR;
260     return 0;
261   }
262
263   errno = ENOENT;
264   return -1;
265 }
266
267 bool CRarFile::OpenForWrite(const CURL& url)
268 {
269   return false;
270 }
271
272 unsigned int CRarFile::Read(void *lpBuf, int64_t uiBufSize)
273 {
274 #ifdef HAS_FILESYSTEM_RAR
275   if (!m_bOpen)
276     return 0;
277
278   if (m_bUseFile)
279     return m_File.Read(lpBuf,uiBufSize);
280
281   if (m_iFilePosition >= GetLength()) // we are done
282     return 0;
283
284   if( !m_pExtract->GetDataIO().hBufferEmpty->WaitMSec(5000) )
285   {
286     CLog::Log(LOGERROR, "%s - Timeout waiting for buffer to empty", __FUNCTION__);
287     return 0;
288   }
289
290
291   uint8_t* pBuf = (uint8_t*)lpBuf;
292   int64_t uicBufSize = uiBufSize;
293   if (m_iDataInBuffer > 0)
294   {
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;
302   }
303
304   while ((uicBufSize > 0) && m_iFilePosition < GetLength() )
305   {
306     if (m_iDataInBuffer <= 0)
307     {
308       m_pExtract->GetDataIO().SetUnpackToMemory(m_szBuffer,MAXWINMEMSIZE);
309       m_szStartOfBuffer = m_szBuffer;
310       m_iBufferStart = m_iFilePosition;
311     }
312
313     m_pExtract->GetDataIO().hBufferFilled->Set();
314     m_pExtract->GetDataIO().hBufferEmpty->Wait();
315
316     if (m_pExtract->GetDataIO().NextVolumeMissing)
317       break;
318
319     m_iDataInBuffer = MAXWINMEMSIZE-m_pExtract->GetDataIO().UnpackToMemorySize;
320
321     if (m_iDataInBuffer < 0 ||
322         m_iDataInBuffer > MAXWINMEMSIZE - (m_szStartOfBuffer - m_szBuffer))
323     {
324       // invalid data returned by UnrarXLib, prevent a crash
325       CLog::Log(LOGERROR, "CRarFile::Read - Data buffer in inconsistent state");
326       m_iDataInBuffer = 0;
327     }
328
329     if (m_iDataInBuffer == 0)
330       break;
331
332     if (m_iDataInBuffer > uicBufSize)
333     {
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;
340     }
341     else
342     {
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;
349     }
350   }
351
352   m_pExtract->GetDataIO().hBufferEmpty->Set();
353
354   return static_cast<unsigned int>(uiBufSize-uicBufSize);
355 #else
356   return 0;
357 #endif
358 }
359
360 unsigned int CRarFile::Write(void *lpBuf, int64_t uiBufSize)
361 {
362   return 0;
363 }
364
365 void CRarFile::Close()
366 {
367 #ifdef HAS_FILESYSTEM_RAR
368   if (!m_bOpen)
369     return;
370
371   if (m_bUseFile)
372   {
373     m_File.Close();
374     g_RarManager.ClearCachedFile(m_strRarPath,m_strPathInRar);
375     m_bOpen = false;
376   }
377   else
378   {
379     CleanUp();
380     if (m_pExtractThread)
381     {
382       delete m_pExtractThread;
383       m_pExtractThread = NULL;
384     }
385     m_bOpen = false;
386   }
387 #endif
388 }
389
390 int64_t CRarFile::Seek(int64_t iFilePosition, int iWhence)
391 {
392 #ifdef HAS_FILESYSTEM_RAR
393   if (!m_bOpen)
394     return -1;
395
396   if (!m_bSeekable)
397     return -1;
398
399   if (m_bUseFile)
400     return m_File.Seek(iFilePosition,iWhence);
401
402   if( !m_pExtract->GetDataIO().hBufferEmpty->WaitMSec(SEEKTIMOUT) )
403   {
404     CLog::Log(LOGERROR, "%s - Timeout waiting for buffer to empty", __FUNCTION__);
405     return -1;
406   }
407
408   m_pExtract->GetDataIO().hBufferEmpty->Set();
409
410   switch (iWhence)
411   {
412     case SEEK_CUR:
413       if (iFilePosition == 0)
414         return m_iFilePosition; // happens sometimes
415
416       iFilePosition += m_iFilePosition;
417       break;
418     case SEEK_END:
419       if (iFilePosition == 0) // do not seek to end
420       {
421         m_iFilePosition = this->GetLength();
422         m_iDataInBuffer = 0;
423         m_iBufferStart = this->GetLength();
424
425         return this->GetLength();
426       }
427
428       iFilePosition += GetLength();
429     case SEEK_SET:
430       break;
431     default:
432       return -1;
433   }
434
435   if (iFilePosition > this->GetLength())
436     return -1;
437
438   if (iFilePosition == m_iFilePosition) // happens a lot
439     return m_iFilePosition;
440
441   if ((iFilePosition >= m_iBufferStart) && (iFilePosition < m_iBufferStart+MAXWINMEMSIZE)
442                                         && (m_iDataInBuffer > 0)) // we are within current buffer
443   {
444     m_iDataInBuffer = MAXWINMEMSIZE-(iFilePosition-m_iBufferStart);
445     m_szStartOfBuffer = m_szBuffer+MAXWINMEMSIZE-m_iDataInBuffer;
446     m_iFilePosition = iFilePosition;
447
448     return m_iFilePosition;
449   }
450
451   if (iFilePosition < m_iBufferStart )
452   {
453     CleanUp();
454     if (!OpenInArchive())
455       return -1;
456
457     if( !m_pExtract->GetDataIO().hBufferEmpty->WaitMSec(SEEKTIMOUT) )
458     {
459       CLog::Log(LOGERROR, "%s - Timeout waiting for buffer to empty", __FUNCTION__);
460       return -1;
461     }
462     m_pExtract->GetDataIO().hBufferEmpty->Set();
463     m_pExtract->GetDataIO().m_iSeekTo = iFilePosition;
464   }
465   else
466     m_pExtract->GetDataIO().m_iSeekTo = iFilePosition;
467
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))
472   {
473     CLog::Log(LOGERROR, "%s - Timeout waiting for seek to finish", __FUNCTION__);
474     return -1;
475   }
476
477   if (m_pExtract->GetDataIO().NextVolumeMissing)
478   {
479     m_iFilePosition = m_iFileSize;
480     return -1;
481   }
482
483   if( !m_pExtract->GetDataIO().hBufferEmpty->WaitMSec(SEEKTIMOUT) )
484   {
485     CLog::Log(LOGERROR, "%s - Timeout waiting for buffer to empty", __FUNCTION__);
486     return -1;
487   }
488   m_iDataInBuffer = m_pExtract->GetDataIO().m_iSeekTo; // keep data
489   m_iBufferStart = m_pExtract->GetDataIO().m_iStartOfBuffer;
490
491   if (m_iDataInBuffer < 0 || m_iDataInBuffer > MAXWINMEMSIZE)
492   {
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;
497   }
498
499   m_szStartOfBuffer = m_szBuffer+MAXWINMEMSIZE-m_iDataInBuffer;
500   m_iFilePosition = iFilePosition;
501
502   return m_iFilePosition;
503 #else
504   return -1;
505 #endif
506 }
507
508 int64_t CRarFile::GetLength()
509 {
510   if (!m_bOpen)
511     return 0;
512
513   if (m_bUseFile)
514     return m_File.GetLength();
515
516   return m_iFileSize;
517 }
518
519 int64_t CRarFile::GetPosition()
520 {
521   if (!m_bOpen)
522     return -1;
523
524   if (m_bUseFile)
525     return m_File.GetPosition();
526
527   return m_iFilePosition;
528 }
529
530 int CRarFile::Write(const void* lpBuf, int64_t uiBufSize)
531 {
532   return -1;
533 }
534
535 void CRarFile::Flush()
536 {
537   if (m_bUseFile)
538     m_File.Flush();
539 }
540
541 void CRarFile::InitFromUrl(const CURL& url)
542 {
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();
548
549   vector<std::string> options;
550   StringUtils::Tokenize(url.GetOptions().Mid(1), options, "&");
551
552   m_bFileOptions = 0;
553
554   for( vector<std::string>::iterator it = options.begin();it != options.end(); it++)
555   {
556     int iEqual = (*it).find('=');
557     if( iEqual >= 0 )
558     {
559       CStdString strOption = StringUtils::Left((*it), iEqual);
560       CStdString strValue = StringUtils::Mid((*it), iEqual+1);
561
562       if( strOption.Equals("flags") )
563         m_bFileOptions = atoi(strValue.c_str());
564       else if( strOption.Equals("cache") )
565         m_strCacheDir = strValue;
566     }
567   }
568
569 }
570
571 void CRarFile::CleanUp()
572 {
573 #ifdef HAS_FILESYSTEM_RAR
574   try
575   {
576     if (m_pExtractThread)
577     {
578       if (m_pExtractThread->hRunning.WaitMSec(1))
579       {
580         m_pExtract->GetDataIO().hQuit->Set();
581         while (m_pExtractThread->hRunning.WaitMSec(1))
582           Sleep(1);
583       }
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;
589     }
590     if (m_pExtract)
591     {
592       delete m_pExtract;
593       m_pExtract = NULL;
594     }
595     if (m_pArc)
596     {
597       delete m_pArc;
598       m_pArc = NULL;
599     }
600     if (m_pCmd)
601     {
602       delete m_pCmd;
603       m_pCmd = NULL;
604     }
605     if (m_szBuffer)
606     {
607       delete[] m_szBuffer;
608       m_szBuffer = NULL;
609       m_szStartOfBuffer = NULL;
610     }
611   }
612   catch (int rarErrCode)
613   {
614     CLog::Log(LOGERROR,"filerar failed in UnrarXLib while deleting CFileRar with an UnrarXLib error code of %d",rarErrCode);
615   }
616   catch (...)
617   {
618     CLog::Log(LOGERROR,"filerar failed in UnrarXLib while deleting CFileRar with an Unknown exception");
619   }
620 #endif
621 }
622
623 bool CRarFile::OpenInArchive()
624 {
625 #ifdef HAS_FILESYSTEM_RAR
626   try
627   {
628     int iHeaderSize;
629
630     InitCRC();
631
632     m_pCmd = new CommandData;
633     if (!m_pCmd)
634     {
635       CleanUp();
636       return false;
637     }
638
639     // Set the arguments for the extract command
640     strcpy(m_pCmd->Command, "X");
641
642     m_pCmd->AddArcName(const_cast<char*>(m_strRarPath.c_str()),NULL);
643
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);
647
648     // Set password for encrypted archives
649     if ((m_strPassword.size() > 0) &&
650         (m_strPassword.size() < sizeof (m_pCmd->Password)))
651     {
652       strcpy(m_pCmd->Password, m_strPassword.c_str());
653     }
654
655     m_pCmd->ParseDone();
656
657     // Open the archive
658     m_pArc = new Archive(m_pCmd);
659     if (!m_pArc)
660     {
661       CleanUp();
662       return false;
663     }
664     if (!m_pArc->WOpen(m_strRarPath.c_str(),NULL))
665     {
666       CleanUp();
667       return false;
668     }
669     if (!(m_pArc->IsOpened() && m_pArc->IsArchive(true)))
670     {
671       CleanUp();
672       return false;
673     }
674
675     m_pExtract = new CmdExtract;
676     if (!m_pExtract)
677     {
678       CleanUp();
679       return false;
680     }
681     m_pExtract->GetDataIO().SetUnpackToMemory(m_szBuffer,0);
682     m_pExtract->GetDataIO().SetCurrentCommand(*(m_pCmd->Command));
683     struct FindData FD;
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);
687
688     while (true)
689     {
690       if ((iHeaderSize = m_pArc->ReadHeader()) <= 0)
691       {
692         CleanUp();
693         return false;
694       }
695
696       if (m_pArc->GetHeaderType() == FILE_HEAD)
697       {
698         CStdString strFileName;
699
700         if (wcslen(m_pArc->NewLhd.FileNameW) > 0)
701         {
702           g_charsetConverter.wToUTF8(m_pArc->NewLhd.FileNameW, strFileName);
703         }
704         else
705         {
706           g_charsetConverter.unknownToUTF8(m_pArc->NewLhd.FileName, strFileName);
707         }
708
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('\\', '/');
712
713         if (strFileName == m_strPathInRar)
714         {
715           break;
716         }
717       }
718
719       m_pArc->SeekToNext();
720     }
721
722     m_szBuffer = new uint8_t[MAXWINMEMSIZE];
723     m_szStartOfBuffer = m_szBuffer;
724     m_pExtract->GetDataIO().SetUnpackToMemory(m_szBuffer,0);
725     m_iDataInBuffer = -1;
726     m_iFilePosition = 0;
727     m_iBufferStart = 0;
728
729     delete m_pExtractThread;
730     m_pExtractThread = new CRarFileExtractThread();
731     m_pExtractThread->Start(m_pArc,m_pCmd,m_pExtract,iHeaderSize);
732
733     return true;
734   }
735   catch (int rarErrCode)
736   {
737     CLog::Log(LOGERROR,"filerar failed in UnrarXLib while CFileRar::OpenInArchive with an UnrarXLib error code of %d",rarErrCode);
738     return false;
739   }
740   catch (...)
741   {
742     CLog::Log(LOGERROR,"filerar failed in UnrarXLib while CFileRar::OpenInArchive with an Unknown exception");
743     return false;
744   }
745 #else
746   return false;
747 #endif
748 }
749