fixed: and back to int64_t for Edl as it's passing by pointer to dvdplayer. Also...
[xbmc:xbmc.git] / xbmc / FileSystem / RarManager.cpp
1 /*
2  *      Copyright (C) 2005-2008 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, write to
17  *  the Free Software Foundation, 675 Mass Ave, Cambridge, MA 02139, USA.
18  *  http://www.gnu.org/copyleft/gpl.html
19  *
20  */
21
22 #include "system.h"
23 #include "RarManager.h"
24 #ifdef HAS_RAR
25 #include "../lib/UnrarXLib/rar.hpp"
26 #endif
27 #include "Util.h"
28 #include "utils/CharsetConverter.h"
29 #include "utils/SingleLock.h"
30 #include "GUIWindowManager.h"
31 #include "GUIDialogYesNo.h"
32 #include "FileSystem/Directory.h"
33 #include "FileSystem/SpecialProtocol.h"
34 #include "AdvancedSettings.h"
35 #include "FileItem.h"
36 #include "utils/log.h"
37
38 #include <set>
39
40 #define EXTRACTION_WARN_SIZE 50*1024*1024
41
42 using namespace std;
43 using namespace XFILE;
44 using namespace DIRECTORY;
45
46 CRarManager g_RarManager;
47
48 CFileInfo::CFileInfo()
49 {
50   m_strCachedPath.Empty();
51   m_bAutoDel = true;
52   m_iUsed = 0;
53   m_iIsSeekable = -1;
54 }
55
56 CFileInfo::~CFileInfo()
57 {
58 }
59
60 /////////////////////////////////////////////////
61 CRarManager::CRarManager()
62 {
63   m_bWipe = true;
64 }
65
66 CRarManager::~CRarManager()
67 {
68   ClearCache(true);
69 }
70
71 bool CRarManager::CacheRarredFile(CStdString& strPathInCache, const CStdString& strRarPath, const CStdString& strPathInRar, BYTE  bOptions, const CStdString& strDir, const int64_t iSize)
72 {
73 #ifdef HAS_RAR
74   CSingleLock lock(m_CritSection);
75
76   //If file is listed in the cache, then use listed copy or cleanup before overwriting.
77   bool bOverwrite = (bOptions & EXFILE_OVERWRITE) != 0;
78   map<CStdString, pair<ArchiveList_struct*,vector<CFileInfo> > >::iterator j = m_ExFiles.find( strRarPath );
79   CFileInfo* pFile=NULL;
80   if( j != m_ExFiles.end() )
81   {
82     pFile = GetFileInRar(strRarPath,strPathInRar);
83     if (pFile)
84     {
85       if (pFile->m_bIsCanceled())
86         return false;
87
88       if( CFile::Exists( pFile->m_strCachedPath) )
89       {
90         if( !bOverwrite )
91         {
92           strPathInCache = pFile->m_strCachedPath;
93           pFile->m_iUsed++;
94           return true;
95         }
96
97         CFile::Delete(pFile->m_strCachedPath);
98         pFile->m_iUsed++;
99       }
100     }
101   }
102
103   int iRes = 0;
104 #if 0 // temporary workaround. disable dialogs as they cause deadlocks since we cannot render
105       // from spawned threads and dvdplayer stalls the app thread during startup
106   //Extract archived file, using existing local copy or overwriting if wanted...
107   if (iSize > EXTRACTION_WARN_SIZE)
108   {
109     CGUIDialogYesNo* pDialog = (CGUIDialogYesNo*)m_gWindowManager.GetWindow(WINDOW_DIALOG_YES_NO);
110     if (pDialog)
111     {
112       pDialog->SetHeading(120);
113       pDialog->SetLine(0, 645);
114       pDialog->SetLine(1, CUtil::GetFileName(strPathInRar));
115       pDialog->SetLine(2, "");
116       pDialog->DoModal();
117       if (!pDialog->IsConfirmed())
118         iRes = 2; // pretend to be canceled
119     }
120   }
121 #endif
122   if (CheckFreeSpace(strDir) < iSize && iRes != 2)
123   {
124     ClearCache();
125     if (CheckFreeSpace(strDir) < iSize)
126     {
127       // wipe at will - if allowed. fixes the evil file manager bug
128       if (!m_bWipe)
129         return false;
130
131       CFileItemList items;
132       CDirectory::GetDirectory(g_advancedSettings.m_cachePath,items);
133       items.Sort(SORT_METHOD_SIZE, SORT_ORDER_DESC);
134       while (items.Size() && CheckFreeSpace(strDir) < iSize)
135       {
136         CStdString strPath = items[0]->m_strPath;
137         if (!items[0]->m_bIsFolder)
138           if (!CFile::Delete(items[0]->m_strPath))
139             break;
140
141         items.Remove(0);
142       }
143       if (!items.Size())
144         return false;
145     }
146   }
147
148   CStdString strPath = strPathInRar;
149 #ifndef _LINUX
150   strPath.Replace('/', '\\');
151 #endif
152   //g_charsetConverter.unknownToUTF8(strPath);
153
154   long long iOffset = -1;
155   if (iRes != 2)
156   {
157     if (pFile)
158     {
159       if (pFile->m_iOffset != -1)
160         iOffset = pFile->m_iOffset;
161     }
162
163
164     if (iOffset == -1)  // grab from list
165     {
166       for( ArchiveList_struct* pIterator = j->second.first; pIterator  ; pIterator ? pIterator = pIterator->next : NULL)
167       {
168         CStdString strName;
169
170         /* convert to utf8 */
171         if( pIterator->item.NameW && wcslen(pIterator->item.NameW) > 0)
172           g_charsetConverter.wToUTF8(pIterator->item.NameW, strName);
173         else
174           g_charsetConverter.unknownToUTF8(pIterator->item.Name, strName);
175         if (strName.Equals(strPath))
176         {
177           iOffset = pIterator->item.iOffset;
178           break;
179         }
180       }
181     }
182     bool bShowProgress=false;
183     if (iSize > 1024*1024 || iSize == -2) // 1MB
184       bShowProgress=true;
185
186     CStdString strDir2(strDir);
187     CUtil::RemoveSlashAtEnd(strDir2);
188     iRes = urarlib_get(const_cast<char*>(strRarPath.c_str()), const_cast<char*>(strDir2.c_str()),
189                        const_cast<char*>(strPath.c_str()),NULL,&iOffset,bShowProgress);
190   }
191   if (iRes == 0)
192   {
193     CLog::Log(LOGERROR,"failed to extract file: %s",strPathInRar.c_str());
194     return false;
195   }
196
197   if(!pFile)
198   {
199     CFileInfo fileInfo;
200     fileInfo.m_strPathInRar = strPathInRar;
201     if (j == m_ExFiles.end())
202     {
203       ArchiveList_struct* pArchiveList;
204       if(ListArchive(strRarPath,pArchiveList))
205       {
206         m_ExFiles.insert(make_pair(strRarPath,make_pair(pArchiveList,vector<CFileInfo>())));
207         j = m_ExFiles.find(strRarPath);
208       }
209       else
210         return false;
211     }
212     j->second.second.push_back(fileInfo);
213     pFile = &(j->second.second[j->second.second.size()-1]);
214     pFile->m_iUsed = 1;
215   }
216   CUtil::AddFileToFolder(strDir,CUtil::GetFileName(strPathInRar),pFile->m_strCachedPath); // GetFileName
217   pFile->m_strCachedPath = CUtil::MakeLegalPath(pFile->m_strCachedPath);
218   pFile->m_bAutoDel = (bOptions & EXFILE_AUTODELETE) != 0;
219   pFile->m_iOffset = iOffset;
220   strPathInCache = pFile->m_strCachedPath;
221
222   if (iRes == 2) //canceled
223   {
224     pFile->watch.StartZero();
225     CFile::Delete(pFile->m_strCachedPath);
226     return false;
227   }
228 #endif
229   return true;
230 }
231
232 // NB: The rar manager expects paths in rars to be terminated with a "\".
233 bool CRarManager::GetFilesInRar(CFileItemList& vecpItems, const CStdString& strRarPath,
234                                 bool bMask, const CStdString& strPathInRar)
235 {
236 #ifdef HAS_RAR
237   CSingleLock lock(m_CritSection);
238
239   ArchiveList_struct* pFileList = NULL;
240   map<CStdString,pair<ArchiveList_struct*,vector<CFileInfo> > >::iterator it = m_ExFiles.find(strRarPath);
241   if (it == m_ExFiles.end())
242   {
243     if( urarlib_list((char*) strRarPath.c_str(), &pFileList, NULL) )
244       m_ExFiles.insert(make_pair(strRarPath,make_pair(pFileList,vector<CFileInfo>())));
245     else
246     {
247       if( pFileList ) urarlib_freelist(pFileList);
248       return false;
249     }
250   }
251   else
252     pFileList = it->second.first;
253
254   CFileItemPtr pFileItem;
255   vector<CStdString> vec;
256   set<CStdString> dirSet;
257   CUtil::Tokenize(strPathInRar,vec,"/");
258   unsigned int iDepth = vec.size();
259
260   ArchiveList_struct* pIterator;
261   CStdString strMatch;
262   CStdString strCompare = strPathInRar;
263   if (!CUtil::HasSlashAtEnd(strCompare) && !strCompare.IsEmpty())
264     strCompare += '/';
265   for( pIterator = pFileList; pIterator  ; pIterator ? pIterator = pIterator->next : NULL)
266   {
267     CStdString strDirDelimiter = (pIterator->item.HostOS==3 ? "/":"\\"); // win32 or unix paths?
268     CStdString strName;
269
270     /* convert to utf8 */
271     if( pIterator->item.NameW && wcslen(pIterator->item.NameW) > 0)
272       g_charsetConverter.wToUTF8(pIterator->item.NameW, strName);
273     else
274       g_charsetConverter.unknownToUTF8(pIterator->item.Name, strName);
275
276     /* replace back slashes into forward slashes */
277     /* this could get us into troubles, file could two different files, one with / and one with \ */
278     strName.Replace('\\', '/');
279
280     if (bMask)
281     {
282       if (!strstr(strName.c_str(),strCompare.c_str()))
283         continue;
284
285       vec.clear();
286       CUtil::Tokenize(strName,vec,"/");
287       if (vec.size() < iDepth)
288         continue;
289     }
290
291     unsigned int iMask = (pIterator->item.HostOS==3 ? 0x0040000:16); // win32 or unix attribs?
292     if (((pIterator->item.FileAttr & iMask) == iMask) || (vec.size() > iDepth+1 && bMask)) // we have a directory
293     {
294       if (!bMask) continue;
295       if (vec.size() == iDepth)
296         continue; // remove root of listing
297
298       if (dirSet.find(vec[iDepth]) == dirSet.end())
299       {
300         dirSet.insert(vec[iDepth]);
301         pFileItem.reset(new CFileItem(vec[iDepth]));
302         pFileItem->m_strPath = vec[iDepth];
303         pFileItem->m_strPath += '/';
304         pFileItem->m_bIsFolder = true;
305         pFileItem->m_idepth = pIterator->item.Method;
306         pFileItem->m_iDriveType = pIterator->item.HostOS;
307         //pFileItem->m_lEndOffset = long(pIterator->item.iOffset);
308       }
309     }
310     else
311     {
312       if (vec.size() == iDepth+1 || !bMask)
313       {
314         if (vec.size() == 0)
315           pFileItem.reset(new CFileItem(strName));
316         else
317           pFileItem.reset(new CFileItem(vec[iDepth]));
318         pFileItem->m_strPath = strName.c_str()+strPathInRar.size();
319         pFileItem->m_dwSize = pIterator->item.UnpSize;
320         pFileItem->m_idepth = pIterator->item.Method;
321         pFileItem->m_iDriveType = pIterator->item.HostOS;
322         //pFileItem->m_lEndOffset = long(pIterator->item.iOffset);
323       }
324     }
325     if (pFileItem)
326       vecpItems.Add(pFileItem);
327
328     pFileItem.reset();
329   }
330   return vecpItems.Size() > 0;
331 #else
332   return false;
333 #endif
334 }
335
336 bool CRarManager::ListArchive(const CStdString& strRarPath, ArchiveList_struct* &pArchiveList)
337 {
338 #ifdef HAS_RAR
339  return urarlib_list((char*) strRarPath.c_str(), &pArchiveList, NULL) == 1;
340 #else
341  return false;
342 #endif
343 }
344
345 CFileInfo* CRarManager::GetFileInRar(const CStdString& strRarPath, const CStdString& strPathInRar)
346 {
347 #ifdef HAS_RAR
348   map<CStdString,pair<ArchiveList_struct*,vector<CFileInfo> > >::iterator j = m_ExFiles.find(strRarPath);
349   if (j == m_ExFiles.end())
350     return NULL;
351
352   for (vector<CFileInfo>::iterator it2=j->second.second.begin(); it2 != j->second.second.end(); ++it2)
353     if (it2->m_strPathInRar == strPathInRar)
354       return &(*it2);
355 #endif
356   return NULL;
357 }
358
359 bool CRarManager::GetPathInCache(CStdString& strPathInCache, const CStdString& strRarPath, const CStdString& strPathInRar)
360 {
361 #ifdef HAS_RAR
362   map<CStdString,pair<ArchiveList_struct*,vector<CFileInfo> > >::iterator j = m_ExFiles.find(strRarPath);
363   if (j == m_ExFiles.end())
364     return false;
365
366   for (vector<CFileInfo>::iterator it2=j->second.second.begin(); it2 != j->second.second.end(); ++it2)
367     if (it2->m_strPathInRar == strPathInRar)
368       return CFile::Exists(it2->m_strCachedPath);
369 #endif
370   return false;
371 }
372
373 bool CRarManager::IsFileInRar(bool& bResult, const CStdString& strRarPath, const CStdString& strPathInRar)
374 {
375 #ifdef HAS_RAR
376   bResult = false;
377   CFileItemList ItemList;
378
379   if (!GetFilesInRar(ItemList,strRarPath,false))
380     return false;
381
382   int it;
383   for (it=0;it<ItemList.Size();++it)
384   {
385     if (strPathInRar.compare(ItemList[it]->m_strPath) == 0)
386       break;
387   }
388   if (it != ItemList.Size())
389     bResult = true;
390
391   return true;
392 #else
393   return false;
394 #endif
395 }
396
397 void CRarManager::ClearCache(bool force)
398 {
399 #ifdef HAS_RAR
400   CSingleLock lock(m_CritSection);
401   map<CStdString, pair<ArchiveList_struct*,vector<CFileInfo> > >::iterator j;
402   for (j = m_ExFiles.begin() ; j != m_ExFiles.end() ; j++)
403   {
404
405     for (vector<CFileInfo>::iterator it2 = j->second.second.begin(); it2 != j->second.second.end(); ++it2)
406     {
407       CFileInfo* pFile = &(*it2);
408       if (pFile->m_bAutoDel && (pFile->m_iUsed < 1 || force))
409         CFile::Delete( pFile->m_strCachedPath );
410     }
411     urarlib_freelist(j->second.first);
412   }
413
414   m_ExFiles.clear();
415 #endif
416 }
417
418 void CRarManager::ClearCachedFile(const CStdString& strRarPath, const CStdString& strPathInRar)
419 {
420 #ifdef HAS_RAR
421   CSingleLock lock(m_CritSection);
422
423   map<CStdString,pair<ArchiveList_struct*,vector<CFileInfo> > >::iterator j = m_ExFiles.find(strRarPath);
424   if (j == m_ExFiles.end())
425   {
426     return; // no such subpath
427   }
428
429   for (vector<CFileInfo>::iterator it = j->second.second.begin(); it != j->second.second.end(); ++it)
430   {
431     if (it->m_strPathInRar == strPathInRar)
432       if (it->m_iUsed > 0)
433       {
434         it->m_iUsed--;
435         break;
436       }
437   }
438 #endif
439 }
440
441 void CRarManager::ExtractArchive(const CStdString& strArchive, const CStdString& strPath)
442 {
443 #ifdef HAS_RAR
444   CStdString strPath2(strPath);
445   CUtil::RemoveSlashAtEnd(strPath2);
446   if (!urarlib_get(const_cast<char*>(strArchive.c_str()), const_cast<char*>(strPath2.c_str()),NULL))
447   {
448     CLog::Log(LOGERROR,"rarmanager::extractarchive error while extracting %s", strArchive.c_str());
449     return;
450   }
451 #endif
452 }
453
454 int64_t CRarManager::CheckFreeSpace(const CStdString& strDrive)
455 {
456   ULARGE_INTEGER lTotalFreeBytes;
457   if (GetDiskFreeSpaceEx(_P(strDrive.c_str()), NULL, NULL, &lTotalFreeBytes))
458     return lTotalFreeBytes.QuadPart;
459
460   return 0;
461 }