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