[ios] fixed, merge fallout. 'Contents' in the path only exists in osx app containers
[xbmc:xbmc.git] / xbmc / Util.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 #include "system.h"
22 #ifdef __APPLE__
23 #include <sys/param.h>
24 #include <mach-o/dyld.h>
25 #endif
26
27 #ifdef _LINUX
28 #include <sys/types.h>
29 #include <dirent.h>
30 #include <stdlib.h>
31 #include <unistd.h>
32 #include <sys/wait.h>
33 #endif
34
35 #include "Application.h"
36 #include "utils/AutoPtrHandle.h"
37 #include "Util.h"
38 #include "addons/Addon.h"
39 #include "storage/IoSupport.h"
40 #include "filesystem/StackDirectory.h"
41 #include "filesystem/VirtualPathDirectory.h"
42 #include "filesystem/MultiPathDirectory.h"
43 #include "filesystem/DirectoryCache.h"
44 #include "filesystem/SpecialProtocol.h"
45 #include "filesystem/RSSDirectory.h"
46 #include "ThumbnailCache.h"
47 #ifdef HAS_FILESYSTEM_RAR
48 #include "filesystem/RarManager.h"
49 #endif
50 #include "filesystem/MythDirectory.h"
51 #ifdef HAS_UPNP
52 #include "filesystem/UPnPDirectory.h"
53 #endif
54 #ifdef HAS_CREDITS
55 #include "Credits.h"
56 #endif
57 #ifdef HAS_VIDEO_PLAYBACK
58 #include "cores/VideoRenderers/RenderManager.h"
59 #endif
60 #include "utils/RegExp.h"
61 #include "settings/GUISettings.h"
62 #include "guilib/TextureManager.h"
63 #include "utils/fstrcmp.h"
64 #include "storage/MediaManager.h"
65 #include "guilib/DirectXGraphics.h"
66 #include "network/DNSNameCache.h"
67 #include "guilib/GUIWindowManager.h"
68 #ifdef _WIN32
69 #include <shlobj.h>
70 #include "WIN32Util.h"
71 #endif
72 #if defined(__APPLE__)
73 #if defined(__arm__)
74 #include "osx/iOSUtils.h"
75 #else
76 #include "osx/CocoaInterface.h"
77 #endif
78 #endif
79 #include "GUIUserMessages.h"
80 #include "filesystem/File.h"
81 #include "utils/Crc32.h"
82 #include "settings/Settings.h"
83 #include "utils/StringUtils.h"
84 #include "settings/AdvancedSettings.h"
85 #ifdef HAS_IRSERVERSUITE
86   #include "input/windows/IRServerSuite.h"
87 #endif
88 #include "guilib/LocalizeStrings.h"
89 #include "utils/md5.h"
90 #include "utils/TimeUtils.h"
91 #include "utils/URIUtils.h"
92 #include "utils/log.h"
93 #include "pictures/Picture.h"
94 #include "utils/JobManager.h"
95 #include "cores/dvdplayer/DVDSubtitles/DVDSubtitleTagSami.h"
96 #include "cores/dvdplayer/DVDSubtitles/DVDSubtitleStream.h"
97 #include "windowing/WindowingFactory.h"
98
99 using namespace std;
100 using namespace XFILE;
101
102 #define clamp(x) (x) > 255.f ? 255 : ((x) < 0 ? 0 : (BYTE)(x+0.5f)) // Valid ranges: brightness[-1 -> 1 (0 is default)] contrast[0 -> 2 (1 is default)]  gamma[0.5 -> 3.5 (1 is default)] default[ramp is linear]
103 static const __int64 SECS_BETWEEN_EPOCHS = 11644473600LL;
104 static const __int64 SECS_TO_100NS = 10000000;
105
106 using namespace AUTOPTR;
107 using namespace XFILE;
108 using namespace PLAYLIST;
109
110 #ifdef HAS_DX
111 static D3DGAMMARAMP oldramp, flashramp;
112 #elif defined(HAS_SDL_2D)
113 static uint16_t oldrampRed[256];
114 static uint16_t oldrampGreen[256];
115 static uint16_t oldrampBlue[256];
116 static uint16_t flashrampRed[256];
117 static uint16_t flashrampGreen[256];
118 static uint16_t flashrampBlue[256];
119 #endif
120
121 CUtil::CUtil(void)
122 {
123 }
124
125 CUtil::~CUtil(void)
126 {}
127
128 CStdString CUtil::GetTitleFromPath(const CStdString& strFileNameAndPath, bool bIsFolder /* = false */)
129 {
130   // use above to get the filename
131   CStdString path(strFileNameAndPath);
132   URIUtils::RemoveSlashAtEnd(path);
133   CStdString strFilename = URIUtils::GetFileName(path);
134
135   CURL url(strFileNameAndPath);
136   CStdString strHostname = url.GetHostName();
137
138 #ifdef HAS_UPNP
139   // UPNP
140   if (url.GetProtocol() == "upnp")
141     strFilename = CUPnPDirectory::GetFriendlyName(strFileNameAndPath.c_str());
142 #endif
143
144   if (url.GetProtocol() == "rss")
145   {
146     CRSSDirectory dir;
147     CFileItemList items;
148     if(dir.GetDirectory(strFileNameAndPath, items) && !items.m_strTitle.IsEmpty())
149       return items.m_strTitle;
150   }
151
152   // LastFM
153   if (url.GetProtocol() == "lastfm")
154   {
155     if (strFilename.IsEmpty())
156       strFilename = g_localizeStrings.Get(15200);
157     else
158       strFilename = g_localizeStrings.Get(15200) + " - " + strFilename;
159   }
160
161   // Shoutcast
162   else if (url.GetProtocol() == "shout")
163   {
164     const int genre = strFileNameAndPath.find_first_of('=');
165     if(genre <0)
166       strFilename = g_localizeStrings.Get(260);
167     else
168       strFilename = g_localizeStrings.Get(260) + " - " + strFileNameAndPath.substr(genre+1).c_str();
169   }
170
171   // Windows SMB Network (SMB)
172   else if (url.GetProtocol() == "smb" && strFilename.IsEmpty())
173   {
174     if (url.GetHostName().IsEmpty())
175     {
176       strFilename = g_localizeStrings.Get(20171);
177     }
178     else
179     {
180       strFilename = url.GetHostName();
181     }
182   }
183   // XBMSP Network
184   else if (url.GetProtocol() == "xbms" && strFilename.IsEmpty())
185     strFilename = "XBMSP Network";
186
187   // iTunes music share (DAAP)
188   else if (url.GetProtocol() == "daap" && strFilename.IsEmpty())
189     strFilename = g_localizeStrings.Get(20174);
190
191   // HDHomerun Devices
192   else if (url.GetProtocol() == "hdhomerun" && strFilename.IsEmpty())
193     strFilename = "HDHomerun Devices";
194
195   // ReplayTV Devices
196   else if (url.GetProtocol() == "rtv")
197     strFilename = "ReplayTV Devices";
198
199   // HTS Tvheadend client
200   else if (url.GetProtocol() == "htsp")
201     strFilename = g_localizeStrings.Get(20256);
202
203   // VDR Streamdev client
204   else if (url.GetProtocol() == "vtp")
205     strFilename = g_localizeStrings.Get(20257);
206   
207   // MythTV client
208   else if (url.GetProtocol() == "myth")
209     strFilename = g_localizeStrings.Get(20258);
210
211   // SAP Streams
212   else if (url.GetProtocol() == "sap" && strFilename.IsEmpty())
213     strFilename = "SAP Streams";
214
215   // Music Playlists
216   else if (path.Left(24).Equals("special://musicplaylists"))
217     strFilename = g_localizeStrings.Get(20011);
218
219   // Video Playlists
220   else if (path.Left(24).Equals("special://videoplaylists"))
221     strFilename = g_localizeStrings.Get(20012);
222
223   // now remove the extension if needed
224   if (!g_guiSettings.GetBool("filelists.showextensions") && !bIsFolder)
225   {
226     URIUtils::RemoveExtension(strFilename);
227     return strFilename;
228   }
229   
230   // URLDecode since the original path may be an URL
231   CURL::Decode(strFilename);
232   return strFilename;
233 }
234
235 bool CUtil::GetVolumeFromFileName(const CStdString& strFileName, CStdString& strFileTitle, CStdString& strVolumeNumber)
236 {
237   const CStdStringArray &regexps = g_advancedSettings.m_videoStackRegExps;
238
239   CStdString strFileNameTemp = strFileName;
240
241   CRegExp reg(true);
242
243   for (unsigned int i = 0; i < regexps.size(); i++)
244   {
245     CStdString strRegExp = regexps[i];
246     if (!reg.RegComp(strRegExp.c_str()))
247     { // invalid regexp - complain in logs
248       CLog::Log(LOGERROR, "Invalid RegExp:[%s]", regexps[i].c_str());
249       continue;
250     }
251 //    CLog::Log(LOGDEBUG, "Regexp:[%s]", regexps[i].c_str());
252
253     int iFoundToken = reg.RegFind(strFileName.c_str());
254     if (iFoundToken >= 0)
255     {
256       int iRegLength = reg.GetFindLen();
257       int iCount = reg.GetSubCount();
258
259       /*
260       reg.DumpOvector(LOGDEBUG);
261       CLog::Log(LOGDEBUG, "Subcount=%i", iCount);
262       for (int j = 0; j <= iCount; j++)
263       {
264         CStdString str = reg.GetMatch(j);
265         CLog::Log(LOGDEBUG, "Sub(%i):[%s]", j, str.c_str());
266       }
267       */
268
269       // simple regexp, only the volume is captured
270       if (iCount == 1)
271       {
272         strVolumeNumber = reg.GetMatch(1);
273         if (strVolumeNumber.IsEmpty()) return false;
274
275         // Remove the extension (if any).  We do this on the base filename, as the regexp
276         // match may include some of the extension (eg the "." in particular).
277         // The extension will then be added back on at the end - there is no reason
278         // to clean it off here. It will be cleaned off during the display routine, if
279         // the settings to hide extensions are turned on.
280         CStdString strFileNoExt = strFileNameTemp;
281         URIUtils::RemoveExtension(strFileNoExt);
282         CStdString strFileExt = strFileNameTemp.Right(strFileNameTemp.length() - strFileNoExt.length());
283         CStdString strFileRight = strFileNoExt.Mid(iFoundToken + iRegLength);
284         strFileTitle = strFileName.Left(iFoundToken) + strFileRight + strFileExt;
285
286         return true;
287       }
288
289       // advanced regexp with prefix (1), volume (2), and suffix (3)
290       else if (iCount == 3)
291       {
292         // second subpatten contains the stacking volume
293         strVolumeNumber = reg.GetMatch(2);
294         if (strVolumeNumber.IsEmpty()) return false;
295
296         // everything before the regexp match
297         strFileTitle = strFileName.Left(iFoundToken);
298
299         // first subpattern contains prefix
300         strFileTitle += reg.GetMatch(1);
301
302         // third subpattern contains suffix
303         strFileTitle += reg.GetMatch(3);
304
305         // everything after the regexp match
306         strFileTitle += strFileNameTemp.Mid(iFoundToken + iRegLength);
307
308         return true;
309       }
310
311       // unknown regexp format
312       else
313       {
314         CLog::Log(LOGERROR, "Incorrect movie stacking regexp format:[%s]", regexps[i].c_str());
315       }
316     }
317   }
318   return false;
319 }
320
321 void CUtil::CleanString(const CStdString& strFileName, CStdString& strTitle, CStdString& strTitleAndYear, CStdString& strYear, bool bRemoveExtension /* = false */, bool bCleanChars /* = true */)
322 {
323   strTitleAndYear = strFileName;
324
325   if (strFileName.Equals(".."))
326    return;
327
328   const CStdStringArray &regexps = g_advancedSettings.m_videoCleanStringRegExps;
329
330   CRegExp reTags(true);
331   CRegExp reYear;
332   CStdString strExtension;
333   URIUtils::GetExtension(strFileName, strExtension);
334
335   if (!reYear.RegComp(g_advancedSettings.m_videoCleanDateTimeRegExp))
336   {
337     CLog::Log(LOGERROR, "%s: Invalid datetime clean RegExp:'%s'", __FUNCTION__, g_advancedSettings.m_videoCleanDateTimeRegExp.c_str());
338   }
339   else
340   {
341     if (reYear.RegFind(strTitleAndYear.c_str()) >= 0)
342     {
343       strTitleAndYear = reYear.GetReplaceString("\\1");
344       strYear = reYear.GetReplaceString("\\2");
345     }
346   }
347
348   URIUtils::RemoveExtension(strTitleAndYear);
349
350   for (unsigned int i = 0; i < regexps.size(); i++)
351   {
352     if (!reTags.RegComp(regexps[i].c_str()))
353     { // invalid regexp - complain in logs
354       CLog::Log(LOGERROR, "%s: Invalid string clean RegExp:'%s'", __FUNCTION__, regexps[i].c_str());
355       continue;
356     }
357     int j=0;
358     if ((j=reTags.RegFind(strFileName.c_str())) > 0)
359       strTitleAndYear = strTitleAndYear.Mid(0, j);
360   }
361
362   // final cleanup - special characters used instead of spaces:
363   // all '_' tokens should be replaced by spaces
364   // if the file contains no spaces, all '.' tokens should be replaced by
365   // spaces - one possibility of a mistake here could be something like:
366   // "Dr..StrangeLove" - hopefully no one would have anything like this.
367   if (bCleanChars)
368   {
369     bool initialDots = true;
370     bool alreadyContainsSpace = (strTitleAndYear.Find(' ') >= 0);
371
372     for (int i = 0; i < (int)strTitleAndYear.size(); i++)
373     {
374       char c = strTitleAndYear.GetAt(i);
375
376       if (c != '.')
377         initialDots = false;
378
379       if ((c == '_') || ((!alreadyContainsSpace) && !initialDots && (c == '.')))
380       {
381         strTitleAndYear.SetAt(i, ' ');
382       }
383     }
384   }
385
386   strTitle = strTitleAndYear.Trim();
387
388   // append year
389   if (!strYear.IsEmpty())
390     strTitleAndYear = strTitle + " (" + strYear + ")";
391
392   // restore extension if needed
393   if (!bRemoveExtension)
394     strTitleAndYear += strExtension;
395 }
396
397 void CUtil::GetQualifiedFilename(const CStdString &strBasePath, CStdString &strFilename)
398 {
399   //Make sure you have a full path in the filename, otherwise adds the base path before.
400   CURL plItemUrl(strFilename);
401   CURL plBaseUrl(strBasePath);
402   int iDotDotLoc, iBeginCut, iEndCut;
403
404   if (plBaseUrl.IsLocal()) //Base in local directory
405   {
406     if (plItemUrl.IsLocal() ) //Filename is local or not qualified
407     {
408 #ifdef _LINUX
409       if (!( (strFilename.c_str()[1] == ':') || (strFilename.c_str()[0] == '/') ) ) //Filename not fully qualified
410 #else
411       if (!( strFilename.c_str()[1] == ':')) //Filename not fully qualified
412 #endif
413       {
414         if (strFilename.c_str()[0] == '/' || strFilename.c_str()[0] == '\\' || URIUtils::HasSlashAtEnd(strBasePath))
415         {
416           strFilename = strBasePath + strFilename;
417           strFilename.Replace('/', '\\');
418         }
419         else
420         {
421           strFilename = strBasePath + '\\' + strFilename;
422           strFilename.Replace('/', '\\');
423         }
424       }
425     }
426     strFilename.Replace("\\.\\", "\\");
427     while ((iDotDotLoc = strFilename.Find("\\..\\")) > 0)
428     {
429       iEndCut = iDotDotLoc + 4;
430       iBeginCut = strFilename.Left(iDotDotLoc).ReverseFind('\\') + 1;
431       strFilename.Delete(iBeginCut, iEndCut - iBeginCut);
432     }
433   }
434   else //Base is remote
435   {
436     if (plItemUrl.IsLocal()) //Filename is local
437     {
438 #ifdef _LINUX
439       if ( (strFilename.c_str()[1] == ':') || (strFilename.c_str()[0] == '/') )  //Filename not fully qualified
440 #else
441       if (strFilename[1] == ':') // already fully qualified
442 #endif
443         return;
444       if (strFilename.c_str()[0] == '/' || strFilename.c_str()[0] == '\\' || URIUtils::HasSlashAtEnd(strBasePath)) //Begins with a slash.. not good.. but we try to make the best of it..
445
446       {
447         strFilename = strBasePath + strFilename;
448         strFilename.Replace('\\', '/');
449       }
450       else
451       {
452         strFilename = strBasePath + '/' + strFilename;
453         strFilename.Replace('\\', '/');
454       }
455     }
456     strFilename.Replace("/./", "/");
457     while ((iDotDotLoc = strFilename.Find("/../")) > 0)
458     {
459       iEndCut = iDotDotLoc + 4;
460       iBeginCut = strFilename.Left(iDotDotLoc).ReverseFind('/') + 1;
461       strFilename.Delete(iBeginCut, iEndCut - iBeginCut);
462     }
463   }
464 }
465
466 void CUtil::RunShortcut(const char* szShortcutPath)
467 {
468 }
469
470 void CUtil::GetHomePath(CStdString& strPath, const CStdString& strTarget)
471 {
472   CStdString strHomePath;
473   strHomePath = ResolveExecutablePath();
474 #ifdef _WIN32
475   CStdStringW strPathW, strTargetW;
476   g_charsetConverter.utf8ToW(strTarget, strTargetW);
477   strPathW = _wgetenv(strTargetW);
478   g_charsetConverter.wToUTF8(strPathW,strPath);
479 #else
480   strPath = getenv(strTarget);
481 #endif
482
483   if (strPath != NULL && !strPath.IsEmpty())
484   {
485 #ifdef _WIN32
486     char tmp[1024];
487     //expand potential relative path to full path
488     if(GetFullPathName(strPath, 1024, tmp, 0) != 0)
489     {
490       strPath = tmp;
491     }
492 #endif
493   }
494   else
495   {
496 #ifdef __APPLE__
497     int      result = -1;
498     char     given_path[2*MAXPATHLEN];
499     uint32_t path_size = 2*MAXPATHLEN;
500
501     #if defined(__arm__)
502       result = GetIOSExecutablePath(given_path, &path_size);
503     #else
504       result = _NSGetExecutablePath(given_path, &path_size);
505     #endif
506     if (result == 0)
507     {
508       // Move backwards to last /.
509       for (int n=strlen(given_path)-1; given_path[n] != '/'; n--)
510         given_path[n] = '\0';
511
512       #if defined(__arm__)
513         strcat(given_path, "/XBMCData/XBMCHome/");
514       #else
515         // Assume local path inside application bundle.
516         strcat(given_path, "../Resources/XBMC/");
517       #endif
518
519       // Convert to real path.
520       char real_path[2*MAXPATHLEN];
521       if (realpath(given_path, real_path) != NULL)
522       {
523         strPath = real_path;
524         return;
525       }
526     }
527 #endif
528     size_t last_sep = strHomePath.find_last_of(PATH_SEPARATOR_CHAR);
529     if (last_sep != string::npos)
530       strPath = strHomePath.Left(last_sep);
531     else
532       strPath = strHomePath;
533   }
534
535 #if defined(_LINUX) && !defined(__APPLE__)
536   /* Change strPath accordingly when target is XBMC_HOME and when INSTALL_PATH
537    * and BIN_INSTALL_PATH differ
538    */
539   CStdString installPath = INSTALL_PATH;
540   CStdString binInstallPath = BIN_INSTALL_PATH;
541   if (!strTarget.compare("XBMC_HOME") && installPath.compare(binInstallPath))
542   {
543     int pos = strPath.length() - binInstallPath.length();
544     CStdString tmp = strPath;
545     tmp.erase(0, pos);
546     if (!tmp.compare(binInstallPath))
547     {
548       strPath.erase(pos, strPath.length());
549       strPath.append(installPath);
550     }
551   }
552 #endif
553 }
554
555 bool CUtil::IsPicture(const CStdString& strFile)
556 {
557   CStdString extension = URIUtils::GetExtension(strFile);
558
559   if (extension.IsEmpty())
560     return false;
561
562   extension.ToLower();
563   if (g_settings.m_pictureExtensions.Find(extension) != -1)
564     return true;
565
566   if (extension == ".tbn" || extension == ".dds")
567     return true;
568
569   return false;
570 }
571
572 bool CUtil::ExcludeFileOrFolder(const CStdString& strFileOrFolder, const CStdStringArray& regexps)
573 {
574   if (strFileOrFolder.IsEmpty())
575     return false;
576
577   CRegExp regExExcludes(true);  // case insensitive regex
578
579   for (unsigned int i = 0; i < regexps.size(); i++)
580   {
581     if (!regExExcludes.RegComp(regexps[i].c_str()))
582     { // invalid regexp - complain in logs
583       CLog::Log(LOGERROR, "%s: Invalid exclude RegExp:'%s'", __FUNCTION__, regexps[i].c_str());
584       continue;
585     }
586     if (regExExcludes.RegFind(strFileOrFolder) > -1)
587     {
588       CLog::Log(LOGDEBUG, "%s: File '%s' excluded. (Matches exclude rule RegExp:'%s')", __FUNCTION__, strFileOrFolder.c_str(), regexps[i].c_str());
589       return true;
590     }
591   }
592   return false;
593 }
594
595 void CUtil::GetFileAndProtocol(const CStdString& strURL, CStdString& strDir)
596 {
597   strDir = strURL;
598   if (!URIUtils::IsRemote(strURL)) return ;
599   if (URIUtils::IsDVD(strURL)) return ;
600
601   CURL url(strURL);
602   strDir.Format("%s://%s", url.GetProtocol().c_str(), url.GetFileName().c_str());
603 }
604
605 int CUtil::GetDVDIfoTitle(const CStdString& strFile)
606 {
607   CStdString strFilename = URIUtils::GetFileName(strFile);
608   if (strFilename.Equals("video_ts.ifo")) return 0;
609   //VTS_[TITLE]_0.IFO
610   return atoi(strFilename.Mid(4, 2).c_str());
611 }
612
613 CStdString CUtil::GetFileMD5(const CStdString& strPath)
614 {
615   CFile file;
616   CStdString result;
617   if (file.Open(strPath))
618   {
619     XBMC::XBMC_MD5 md5;
620     char temp[1024];
621     int pos=0;
622     int read=1;
623     while (read > 0)
624     {
625       read = file.Read(temp,1024);
626       pos += read;
627       md5.append(temp,read);
628     }
629     md5.getDigest(result);
630     file.Close();
631   }
632
633   return result;
634 }
635
636 bool CUtil::GetDirectoryName(const CStdString& strFileName, CStdString& strDescription)
637 {
638   CStdString strFName = URIUtils::GetFileName(strFileName);
639   strDescription = strFileName.Left(strFileName.size() - strFName.size());
640   URIUtils::RemoveSlashAtEnd(strDescription);
641
642   int iPos = strDescription.ReverseFind("\\");
643   if (iPos < 0)
644     iPos = strDescription.ReverseFind("/");
645   if (iPos >= 0)
646   {
647     CStdString strTmp = strDescription.Right(strDescription.size()-iPos-1);
648     strDescription = strTmp;//strDescription.Right(strDescription.size() - iPos - 1);
649   }
650   else if (strDescription.size() <= 0)
651     strDescription = strFName;
652   return true;
653 }
654
655 void CUtil::GetDVDDriveIcon( const CStdString& strPath, CStdString& strIcon )
656 {
657   if ( !g_mediaManager.IsDiscInDrive() )
658   {
659     strIcon = "DefaultDVDEmpty.png";
660     return ;
661   }
662
663   if ( URIUtils::IsDVD(strPath) )
664   {
665 #ifdef HAS_DVD_DRIVE
666     CCdInfo* pInfo = g_mediaManager.GetCdInfo();
667     //  xbox DVD
668     if ( pInfo != NULL && pInfo->IsUDFX( 1 ) )
669     {
670       strIcon = "DefaultXboxDVD.png";
671       return ;
672     }
673 #endif
674     strIcon = "DefaultDVDRom.png";
675     return ;
676   }
677
678   if ( URIUtils::IsISO9660(strPath) )
679   {
680 #ifdef HAS_DVD_DRIVE
681     CCdInfo* pInfo = g_mediaManager.GetCdInfo();
682     if ( pInfo != NULL && pInfo->IsVideoCd( 1 ) )
683     {
684       strIcon = "DefaultVCD.png";
685       return ;
686     }
687 #endif
688     strIcon = "DefaultDVDRom.png";
689     return ;
690   }
691
692   if ( URIUtils::IsCDDA(strPath) )
693   {
694     strIcon = "DefaultCDDA.png";
695     return ;
696   }
697 }
698
699 void CUtil::RemoveTempFiles()
700 {
701   CStdString searchPath = g_settings.GetDatabaseFolder();
702   CFileItemList items;
703   if (!XFILE::CDirectory::GetDirectory(searchPath, items, ".tmp", false))
704     return;
705
706   for (int i = 0; i < items.Size(); ++i)
707   {
708     if (items[i]->m_bIsFolder)
709       continue;
710     XFILE::CFile::Delete(items[i]->m_strPath);
711   }
712 }
713
714 void CUtil::ClearSubtitles()
715 {
716   //delete cached subs
717   CFileItemList items;
718   CDirectory::GetDirectory("special://temp/",items);
719   for( int i=0;i<items.Size();++i)
720   {
721     if (!items[i]->m_bIsFolder)
722     {
723       if ( items[i]->m_strPath.Find("subtitle") >= 0 || items[i]->m_strPath.Find("vobsub_queue") >= 0 )
724       {
725         CLog::Log(LOGDEBUG, "%s - Deleting temporary subtitle %s", __FUNCTION__, items[i]->m_strPath.c_str());
726         CFile::Delete(items[i]->m_strPath);
727       }
728     }
729   }
730 }
731
732 static const char * sub_exts[] = { ".utf", ".utf8", ".utf-8", ".sub", ".srt", ".smi", ".rt", ".txt", ".ssa", ".aqt", ".jss", ".ass", ".idx", NULL};
733
734 int64_t CUtil::ToInt64(uint32_t high, uint32_t low)
735 {
736   int64_t n;
737   n = high;
738   n <<= 32;
739   n += low;
740   return n;
741 }
742
743 bool CUtil::ThumbExists(const CStdString& strFileName, bool bAddCache)
744 {
745   return CThumbnailCache::GetThumbnailCache()->ThumbExists(strFileName, bAddCache);
746 }
747
748 void CUtil::ThumbCacheAdd(const CStdString& strFileName, bool bFileExists)
749 {
750   CThumbnailCache::GetThumbnailCache()->Add(strFileName, bFileExists);
751 }
752
753 void CUtil::ThumbCacheClear()
754 {
755   CThumbnailCache::GetThumbnailCache()->Clear();
756 }
757
758 bool CUtil::ThumbCached(const CStdString& strFileName)
759 {
760   return CThumbnailCache::GetThumbnailCache()->IsCached(strFileName);
761 }
762
763 void CUtil::PlayDVD(const CStdString& strProtocol)
764 {
765 #if defined(HAS_DVDPLAYER) && defined(HAS_DVD_DRIVE)
766   CIoSupport::Dismount("Cdrom0");
767   CIoSupport::RemapDriveLetter('D', "Cdrom0");
768   CStdString strPath;
769   strPath.Format("%s://1", strProtocol.c_str());
770   CFileItem item(strPath, false);
771   item.SetLabel(g_mediaManager.GetDiskLabel());
772   g_application.PlayFile(item);
773 #endif
774 }
775
776 CStdString CUtil::GetNextFilename(const CStdString &fn_template, int max)
777 {
778   if (!fn_template.Find("%03d"))
779     return "";
780
781   CStdString searchPath;
782   URIUtils::GetDirectory(fn_template, searchPath);
783   CStdString mask = URIUtils::GetExtension(fn_template);
784
785   CStdString name;
786   name.Format(fn_template.c_str(), 0);
787
788   CFileItemList items;
789   if (!CDirectory::GetDirectory(searchPath, items, mask, false))
790     return name;
791
792   items.SetFastLookup(true);
793   for (int i = 0; i <= max; i++)
794   {
795     CStdString name;
796     name.Format(fn_template.c_str(), i);
797     if (!items.Get(name))
798       return name;
799   }
800   return "";
801 }
802
803 CStdString CUtil::GetNextPathname(const CStdString &path_template, int max)
804 {
805   if (!path_template.Find("%04d"))
806     return "";
807   
808   for (int i = 0; i <= max; i++)
809   {
810     CStdString name;
811     name.Format(path_template.c_str(), i);
812     if (!CFile::Exists(name))
813       return name;
814   }
815   return "";
816 }
817
818 void CUtil::Tokenize(const CStdString& path, vector<CStdString>& tokens, const string& delimiters)
819 {
820   // Tokenize ripped from http://www.linuxselfhelp.com/HOWTO/C++Programming-HOWTO-7.html
821   // Skip delimiters at beginning.
822   string::size_type lastPos = path.find_first_not_of(delimiters, 0);
823   // Find first "non-delimiter".
824   string::size_type pos = path.find_first_of(delimiters, lastPos);
825
826   while (string::npos != pos || string::npos != lastPos)
827   {
828     // Found a token, add it to the vector.
829     tokens.push_back(path.substr(lastPos, pos - lastPos));
830     // Skip delimiters.  Note the "not_of"
831     lastPos = path.find_first_not_of(delimiters, pos);
832     // Find next "non-delimiter"
833     pos = path.find_first_of(delimiters, lastPos);
834   }
835 }
836
837 void CUtil::TakeScreenshot(const CStdString &filename, bool sync)
838 {
839   int            width;
840   int            height;
841   int            stride;
842   unsigned char* outpixels = NULL;
843
844 #ifdef HAS_DX
845   LPDIRECT3DSURFACE9 lpSurface = NULL, lpBackbuffer = NULL;
846   g_graphicsContext.Lock();
847   if (g_application.IsPlayingVideo())
848   {
849 #ifdef HAS_VIDEO_PLAYBACK
850     g_renderManager.SetupScreenshot();
851 #endif
852   }
853   g_application.RenderNoPresent();
854
855   if (FAILED(g_Windowing.Get3DDevice()->CreateOffscreenPlainSurface(g_Windowing.GetWidth(), g_Windowing.GetHeight(), D3DFMT_X8R8G8B8, D3DPOOL_SYSTEMMEM, &lpSurface, NULL)))
856     return;
857
858   if (FAILED(g_Windowing.Get3DDevice()->GetRenderTarget(0, &lpBackbuffer)))
859     return;
860
861   // now take screenshot
862   if (SUCCEEDED(g_Windowing.Get3DDevice()->GetRenderTargetData(lpBackbuffer, lpSurface)))
863   {
864     D3DLOCKED_RECT lr;
865     D3DSURFACE_DESC desc;
866     lpSurface->GetDesc(&desc);
867     if (SUCCEEDED(lpSurface->LockRect(&lr, NULL, D3DLOCK_READONLY)))
868     {
869       width = desc.Width;
870       height = desc.Height;
871       stride = lr.Pitch;
872       outpixels = new unsigned char[height * stride];
873       memcpy(outpixels, lr.pBits, height * stride);
874       lpSurface->UnlockRect();
875     }
876     else
877     {
878       CLog::Log(LOGERROR, "%s LockRect failed", __FUNCTION__);
879     }
880   }
881   else
882   {
883     CLog::Log(LOGERROR, "%s GetBackBuffer failed", __FUNCTION__);
884   }
885   lpSurface->Release();
886   lpBackbuffer->Release();
887
888   g_graphicsContext.Unlock();
889
890 #elif defined(HAS_GL) || defined(HAS_GLES)
891
892   g_graphicsContext.BeginPaint();
893   if (g_application.IsPlayingVideo())
894   {
895 #ifdef HAS_VIDEO_PLAYBACK
896     g_renderManager.SetupScreenshot();
897 #endif
898   }
899   g_application.RenderNoPresent();
900 #ifndef HAS_GLES
901   glReadBuffer(GL_BACK);
902 #endif
903   //get current viewport
904   GLint viewport[4];
905   glGetIntegerv(GL_VIEWPORT, viewport);
906
907   width  = viewport[2] - viewport[0];
908   height = viewport[3] - viewport[1];
909   stride = width * 4;
910   unsigned char* pixels = new unsigned char[stride * height];
911
912   //read pixels from the backbuffer
913 #if HAS_GLES == 2
914   glReadPixels(viewport[0], viewport[1], viewport[2], viewport[3], GL_RGBA, GL_UNSIGNED_BYTE, (GLvoid*)pixels);
915 #else
916   glReadPixels(viewport[0], viewport[1], viewport[2], viewport[3], GL_BGRA, GL_UNSIGNED_BYTE, (GLvoid*)pixels);
917 #endif
918   g_graphicsContext.EndPaint();
919
920   //make a new buffer and copy the read image to it with the Y axis inverted
921   outpixels = new unsigned char[stride * height];
922   for (int y = 0; y < height; y++)
923     memcpy(outpixels + y * stride, pixels + (height - y - 1) * stride, stride);
924
925   delete [] pixels;
926
927 #else
928   //nothing to take a screenshot from
929   return;
930 #endif
931
932   if (!outpixels)
933   {
934     CLog::Log(LOGERROR, "Screenshot %s failed", filename.c_str());
935     return;
936   }
937
938   CLog::Log(LOGDEBUG, "Saving screenshot %s", filename.c_str());
939
940   //set alpha byte to 0xFF
941   for (int y = 0; y < height; y++)
942   {
943     unsigned char* alphaptr = outpixels - 1 + y * stride;
944     for (int x = 0; x < width; x++)
945       *(alphaptr += 4) = 0xFF;
946   }
947
948   //if sync is true, the png file needs to be completely written when this function returns
949   if (sync)
950   {
951     if (!CPicture::CreateThumbnailFromSurface(outpixels, width, height, stride, filename))
952       CLog::Log(LOGERROR, "Unable to write screenshot %s", filename.c_str());
953
954     delete [] outpixels;
955   }
956   else
957   {
958     //make sure the file exists to avoid concurrency issues
959     FILE* fp = fopen(filename.c_str(), "w");
960     if (fp)
961       fclose(fp);
962     else
963       CLog::Log(LOGERROR, "Unable to create file %s", filename.c_str());
964
965     //write .png file asynchronous with CThumbnailWriter, prevents stalling of the render thread
966     //outpixels is deleted from CThumbnailWriter
967     CThumbnailWriter* thumbnailwriter = new CThumbnailWriter(outpixels, width, height, stride, filename);
968     CJobManager::GetInstance().AddJob(thumbnailwriter, NULL);
969   }
970 }
971
972 void CUtil::TakeScreenshot()
973 {
974   static bool savingScreenshots = false;
975   static vector<CStdString> screenShots;
976
977   bool promptUser = false;
978   // check to see if we have a screenshot folder yet
979   CStdString strDir = g_guiSettings.GetString("debug.screenshotpath", false);
980   if (strDir.IsEmpty())
981   {
982     strDir = "special://temp/";
983     if (!savingScreenshots)
984     {
985       promptUser = true;
986       savingScreenshots = true;
987       screenShots.clear();
988     }
989   }
990   URIUtils::RemoveSlashAtEnd(strDir);
991
992   if (!strDir.IsEmpty())
993   {
994     CStdString file = CUtil::GetNextFilename(URIUtils::AddFileToFolder(strDir, "screenshot%03d.png"), 999);
995
996     if (!file.IsEmpty())
997     {
998       TakeScreenshot(file, false);
999       if (savingScreenshots)
1000         screenShots.push_back(file);
1001       if (promptUser)
1002       { // grab the real directory
1003         CStdString newDir = g_guiSettings.GetString("debug.screenshotpath");
1004         if (!newDir.IsEmpty())
1005         {
1006           for (unsigned int i = 0; i < screenShots.size(); i++)
1007           {
1008             CStdString file = CUtil::GetNextFilename(URIUtils::AddFileToFolder(newDir, "screenshot%03d.png"), 999);
1009             CFile::Cache(screenShots[i], file);
1010           }
1011           screenShots.clear();
1012         }
1013         savingScreenshots = false;
1014       }
1015     }
1016     else
1017     {
1018       CLog::Log(LOGWARNING, "Too many screen shots or invalid folder");
1019     }
1020   }
1021 }
1022
1023 void CUtil::StatToStatI64(struct _stati64 *result, struct stat *stat)
1024 {
1025   result->st_dev = stat->st_dev;
1026   result->st_ino = stat->st_ino;
1027   result->st_mode = stat->st_mode;
1028   result->st_nlink = stat->st_nlink;
1029   result->st_uid = stat->st_uid;
1030   result->st_gid = stat->st_gid;
1031   result->st_rdev = stat->st_rdev;
1032   result->st_size = (int64_t)stat->st_size;
1033
1034 #ifndef _LINUX
1035   result->st_atime = (long)(stat->st_atime & 0xFFFFFFFF);
1036   result->st_mtime = (long)(stat->st_mtime & 0xFFFFFFFF);
1037   result->st_ctime = (long)(stat->st_ctime & 0xFFFFFFFF);
1038 #else
1039   result->_st_atime = (long)(stat->st_atime & 0xFFFFFFFF);
1040   result->_st_mtime = (long)(stat->st_mtime & 0xFFFFFFFF);
1041   result->_st_ctime = (long)(stat->st_ctime & 0xFFFFFFFF);
1042 #endif
1043 }
1044
1045 void CUtil::Stat64ToStatI64(struct _stati64 *result, struct __stat64 *stat)
1046 {
1047   result->st_dev = stat->st_dev;
1048   result->st_ino = stat->st_ino;
1049   result->st_mode = stat->st_mode;
1050   result->st_nlink = stat->st_nlink;
1051   result->st_uid = stat->st_uid;
1052   result->st_gid = stat->st_gid;
1053   result->st_rdev = stat->st_rdev;
1054   result->st_size = stat->st_size;
1055 #ifndef _LINUX
1056   result->st_atime = (long)(stat->st_atime & 0xFFFFFFFF);
1057   result->st_mtime = (long)(stat->st_mtime & 0xFFFFFFFF);
1058   result->st_ctime = (long)(stat->st_ctime & 0xFFFFFFFF);
1059 #else
1060   result->_st_atime = (long)(stat->st_atime & 0xFFFFFFFF);
1061   result->_st_mtime = (long)(stat->st_mtime & 0xFFFFFFFF);
1062   result->_st_ctime = (long)(stat->st_ctime & 0xFFFFFFFF);
1063 #endif
1064 }
1065
1066 void CUtil::StatI64ToStat64(struct __stat64 *result, struct _stati64 *stat)
1067 {
1068   result->st_dev = stat->st_dev;
1069   result->st_ino = stat->st_ino;
1070   result->st_mode = stat->st_mode;
1071   result->st_nlink = stat->st_nlink;
1072   result->st_uid = stat->st_uid;
1073   result->st_gid = stat->st_gid;
1074   result->st_rdev = stat->st_rdev;
1075   result->st_size = stat->st_size;
1076 #ifndef _LINUX
1077   result->st_atime = stat->st_atime;
1078   result->st_mtime = stat->st_mtime;
1079   result->st_ctime = stat->st_ctime;
1080 #else
1081   result->st_atime = stat->_st_atime;
1082   result->st_mtime = stat->_st_mtime;
1083   result->st_ctime = stat->_st_ctime;
1084 #endif
1085 }
1086
1087 void CUtil::Stat64ToStat(struct stat *result, struct __stat64 *stat)
1088 {
1089   result->st_dev = stat->st_dev;
1090   result->st_ino = stat->st_ino;
1091   result->st_mode = stat->st_mode;
1092   result->st_nlink = stat->st_nlink;
1093   result->st_uid = stat->st_uid;
1094   result->st_gid = stat->st_gid;
1095   result->st_rdev = stat->st_rdev;
1096 #ifndef _LINUX
1097   if (stat->st_size <= LONG_MAX)
1098     result->st_size = (_off_t)stat->st_size;
1099 #else
1100   if (sizeof(stat->st_size) <= sizeof(result->st_size) )
1101     result->st_size = (off_t)stat->st_size;
1102 #endif
1103   else
1104   {
1105     result->st_size = 0;
1106     CLog::Log(LOGWARNING, "WARNING: File is larger than 32bit stat can handle, file size will be reported as 0 bytes");
1107   }
1108   result->st_atime = (time_t)(stat->st_atime & 0xFFFFFFFF);
1109   result->st_mtime = (time_t)(stat->st_mtime & 0xFFFFFFFF);
1110   result->st_ctime = (time_t)(stat->st_ctime & 0xFFFFFFFF);
1111 }
1112
1113 #ifdef _WIN32
1114 void CUtil::Stat64ToStat64i32(struct _stat64i32 *result, struct __stat64 *stat)
1115 {
1116   result->st_dev = stat->st_dev;
1117   result->st_ino = stat->st_ino;
1118   result->st_mode = stat->st_mode;
1119   result->st_nlink = stat->st_nlink;
1120   result->st_uid = stat->st_uid;
1121   result->st_gid = stat->st_gid;
1122   result->st_rdev = stat->st_rdev;
1123 #ifndef _LINUX
1124   if (stat->st_size <= LONG_MAX)
1125     result->st_size = (_off_t)stat->st_size;
1126 #else
1127   if (sizeof(stat->st_size) <= sizeof(result->st_size) )
1128     result->st_size = (off_t)stat->st_size;
1129 #endif
1130   else
1131   {
1132     result->st_size = 0;
1133     CLog::Log(LOGWARNING, "WARNING: File is larger than 32bit stat can handle, file size will be reported as 0 bytes");
1134   }
1135 #ifndef _LINUX
1136   result->st_atime = stat->st_atime;
1137   result->st_mtime = stat->st_mtime;
1138   result->st_ctime = stat->st_ctime;
1139 #else
1140   result->st_atime = stat->_st_atime;
1141   result->st_mtime = stat->_st_mtime;
1142   result->st_ctime = stat->_st_ctime;
1143 #endif
1144 }
1145 #endif
1146
1147 bool CUtil::CreateDirectoryEx(const CStdString& strPath)
1148 {
1149   // Function to create all directories at once instead
1150   // of calling CreateDirectory for every subdir.
1151   // Creates the directory and subdirectories if needed.
1152
1153   // return true if directory already exist
1154   if (CDirectory::Exists(strPath)) return true;
1155
1156   // we currently only allow HD and smb paths
1157   if (!URIUtils::IsHD(strPath) && !URIUtils::IsSmb(strPath))
1158   {
1159     CLog::Log(LOGERROR,"%s called with an unsupported path: %s", __FUNCTION__, strPath.c_str());
1160     return false;
1161   }
1162
1163   CURL url(strPath);
1164   // silly CStdString can't take a char in the constructor
1165   CStdString sep(1, url.GetDirectorySeparator());
1166
1167   // split the filename portion of the URL up into separate dirs
1168   CStdStringArray dirs;
1169   StringUtils::SplitString(url.GetFileName(), sep, dirs);
1170
1171   // we start with the root path
1172   CStdString dir = url.GetWithoutFilename();
1173   unsigned int i = 0;
1174   if (dir.IsEmpty())
1175   { // local directory - start with the first dirs member so that
1176     // we ensure CUtil::AddFileToFolder() below has something to work with
1177     dir = dirs[i++] + sep;
1178   }
1179   // and append the rest of the directories successively, creating each dir
1180   // as we go
1181   for (; i < dirs.size(); i++)
1182   {
1183     dir = URIUtils::AddFileToFolder(dir, dirs[i]);
1184     CDirectory::Create(dir);
1185   }
1186
1187   // was the final destination directory successfully created ?
1188   if (!CDirectory::Exists(strPath)) return false;
1189   return true;
1190 }
1191
1192 CStdString CUtil::MakeLegalFileName(const CStdString &strFile, int LegalType)
1193 {
1194   CStdString result = strFile;
1195
1196   result.Replace('/', '_');
1197   result.Replace('\\', '_');
1198   result.Replace('?', '_');
1199
1200   if (LegalType == LEGAL_WIN32_COMPAT)
1201   {
1202     // just filter out some illegal characters on windows
1203     result.Replace(':', '_');
1204     result.Replace('*', '_');
1205     result.Replace('?', '_');
1206     result.Replace('\"', '_');
1207     result.Replace('<', '_');
1208     result.Replace('>', '_');
1209     result.Replace('|', '_');
1210     result.TrimRight(".");
1211     result.TrimRight(" ");
1212   }
1213   return result;
1214 }
1215
1216 // same as MakeLegalFileName, but we assume that we're passed a complete path,
1217 // and just legalize the filename
1218 CStdString CUtil::MakeLegalPath(const CStdString &strPathAndFile, int LegalType)
1219 {
1220   CStdString strPath;
1221   URIUtils::GetDirectory(strPathAndFile,strPath);
1222   CStdString strFileName = URIUtils::GetFileName(strPathAndFile);
1223   return strPath + MakeLegalFileName(strFileName, LegalType);
1224 }
1225
1226 CStdString CUtil::ValidatePath(const CStdString &path, bool bFixDoubleSlashes /* = false */)
1227 {
1228   CStdString result = path;
1229
1230   // Don't do any stuff on URLs containing %-characters or protocols that embed
1231   // filenames. NOTE: Don't use IsInZip or IsInRar here since it will infinitely
1232   // recurse and crash XBMC
1233   if (URIUtils::IsURL(path) && 
1234      (path.Find('%') >= 0 ||
1235       path.Left(4).Equals("zip:") ||
1236       path.Left(4).Equals("rar:") ||
1237       path.Left(6).Equals("stack:") ||
1238       path.Left(10).Equals("multipath:") ))
1239     return result;
1240
1241   // check the path for incorrect slashes
1242 #ifdef _WIN32
1243   if (URIUtils::IsDOSPath(path))
1244   {
1245     result.Replace('/', '\\');
1246     /* The double slash correction should only be used when *absolutely*
1247        necessary! This applies to certain DLLs or use from Python DLLs/scripts
1248        that incorrectly generate double (back) slashes.
1249     */
1250     if (bFixDoubleSlashes)
1251     {
1252       // Fixup for double back slashes (but ignore the \\ of unc-paths)
1253       for (int x = 1; x < result.GetLength() - 1; x++)
1254       {
1255         if (result[x] == '\\' && result[x+1] == '\\')
1256           result.Delete(x);
1257       }
1258     }
1259   }
1260   else if (path.Find("://") >= 0 || path.Find(":\\\\") >= 0)
1261 #endif
1262   {
1263     result.Replace('\\', '/');
1264     /* The double slash correction should only be used when *absolutely*
1265        necessary! This applies to certain DLLs or use from Python DLLs/scripts
1266        that incorrectly generate double (back) slashes.
1267     */
1268     if (bFixDoubleSlashes)
1269     {
1270       // Fixup for double forward slashes(/) but don't touch the :// of URLs
1271       for (int x = 2; x < result.GetLength() - 1; x++)
1272       {
1273         if ( result[x] == '/' && result[x + 1] == '/' && !(result[x - 1] == ':' || (result[x - 1] == '/' && result[x - 2] == ':')) )
1274           result.Delete(x);
1275       }
1276     }
1277   }
1278   return result;
1279 }
1280
1281 bool CUtil::IsUsingTTFSubtitles()
1282 {
1283   return URIUtils::GetExtension(g_guiSettings.GetString("subtitles.font")).Equals(".ttf");
1284 }
1285
1286 #ifdef UNIT_TESTING
1287 bool CUtil::TestSplitExec()
1288 {
1289   CStdString function;
1290   vector<CStdString> params;
1291   CUtil::SplitExecFunction("ActivateWindow(Video, \"C:\\test\\foo\")", function, params);
1292   if (function != "ActivateWindow" || params.size() != 2 || params[0] != "Video" || params[1] != "C:\\test\\foo")
1293     return false;
1294   params.clear();
1295   CUtil::SplitExecFunction("ActivateWindow(Video, \"C:\\test\\foo\\\")", function, params);
1296   if (function != "ActivateWindow" || params.size() != 2 || params[0] != "Video" || params[1] != "C:\\test\\foo\"")
1297     return false;
1298   CUtil::SplitExecFunction("ActivateWindow(Video, \"C:\\\\test\\\\foo\\\\\")", function, params);
1299   if (function != "ActivateWindow" || params.size() != 2 || params[0] != "Video" || params[1] != "C:\\test\\foo\\")
1300     return false;
1301   CUtil::SplitExecFunction("ActivateWindow(Video, \"C:\\\\\\\\test\\\\\\foo\\\\\")", function, params);
1302   if (function != "ActivateWindow" || params.size() != 2 || params[0] != "Video" || params[1] != "C:\\\\test\\\\foo\\")
1303     return false;
1304   return true;
1305 }
1306 #endif
1307
1308 void CUtil::SplitExecFunction(const CStdString &execString, CStdString &function, vector<CStdString> &parameters)
1309 {
1310   CStdString paramString;
1311
1312   int iPos = execString.Find("(");
1313   int iPos2 = execString.ReverseFind(")");
1314   if (iPos > 0 && iPos2 > 0)
1315   {
1316     paramString = execString.Mid(iPos + 1, iPos2 - iPos - 1);
1317     function = execString.Left(iPos);
1318   }
1319   else
1320     function = execString;
1321
1322   // remove any whitespace, and the standard prefix (if it exists)
1323   function.Trim();
1324   if( function.Left(5).Equals("xbmc.", false) )
1325     function.Delete(0, 5);
1326
1327   // now split up our parameters - we may have quotes to deal with as well as brackets and whitespace
1328   bool inQuotes = false;
1329   bool lastEscaped = false; // only every second character can be escaped
1330   int inFunction = 0;
1331   size_t whiteSpacePos = 0;
1332   CStdString parameter;
1333   parameters.clear();
1334   for (size_t pos = 0; pos < paramString.size(); pos++)
1335   {
1336     char ch = paramString[pos];
1337     bool escaped = (pos > 0 && paramString[pos - 1] == '\\' && !lastEscaped);
1338     lastEscaped = escaped;
1339     if (inQuotes)
1340     { // if we're in a quote, we accept everything until the closing quote
1341       if (ch == '\"' && !escaped)
1342       { // finished a quote - no need to add the end quote to our string
1343         inQuotes = false;
1344         continue;
1345       }
1346     }
1347     else
1348     { // not in a quote, so check if we should be starting one
1349       if (ch == '\"' && !escaped)
1350       { // start of quote - no need to add the quote to our string
1351         inQuotes = true;
1352         continue;
1353       }
1354       if (inFunction && ch == ')')
1355       { // end of a function
1356         inFunction--;
1357       }
1358       if (ch == '(')
1359       { // start of function
1360         inFunction++;
1361       }
1362       if (!inFunction && !URIUtils::IsStack(paramString) && ch == ',')
1363       { // not in a function, so a comma signfies the end of this parameter
1364         if (whiteSpacePos)
1365           parameter = parameter.Left(whiteSpacePos);
1366         parameters.push_back(parameter);
1367         parameter.Empty();
1368         whiteSpacePos = 0;
1369         continue;
1370       }
1371     }
1372     if ((ch == '\"' || ch == '\\') && escaped)
1373     { // escaped quote or backslash
1374       parameter[parameter.size()-1] = ch;
1375       continue;
1376     }
1377     // whitespace handling - we skip any whitespace at the left or right of an unquoted parameter
1378     if (ch == ' ' && !inQuotes)
1379     {
1380       if (parameter.IsEmpty()) // skip whitespace on left
1381         continue;
1382       if (!whiteSpacePos) // make a note of where whitespace starts on the right
1383         whiteSpacePos = parameter.size();
1384     }
1385     else
1386       whiteSpacePos = 0;
1387     parameter += ch;
1388   }
1389   if (inFunction || inQuotes)
1390     CLog::Log(LOGWARNING, "%s(%s) - end of string while searching for ) or \"", __FUNCTION__, execString.c_str());
1391   if (whiteSpacePos)
1392     parameter = parameter.Left(whiteSpacePos);
1393   if (!parameter.IsEmpty())
1394     parameters.push_back(parameter);
1395 }
1396
1397 int CUtil::GetMatchingSource(const CStdString& strPath1, VECSOURCES& VECSOURCES, bool& bIsSourceName)
1398 {
1399   if (strPath1.IsEmpty())
1400     return -1;
1401
1402   //CLog::Log(LOGDEBUG,"CUtil::GetMatchingSource, testing original path/name [%s]", strPath1.c_str());
1403
1404   // copy as we may change strPath
1405   CStdString strPath = strPath1;
1406
1407   // Check for special protocols
1408   CURL checkURL(strPath);
1409
1410   // stack://
1411   if (checkURL.GetProtocol() == "stack")
1412     strPath.Delete(0, 8); // remove the stack protocol
1413
1414   if (checkURL.GetProtocol() == "shout")
1415     strPath = checkURL.GetHostName();
1416   if (checkURL.GetProtocol() == "lastfm")
1417     return 1;
1418   if (checkURL.GetProtocol() == "tuxbox")
1419     return 1;
1420   if (checkURL.GetProtocol() == "plugin")
1421     return 1;
1422   if (checkURL.GetProtocol() == "multipath")
1423     strPath = CMultiPathDirectory::GetFirstPath(strPath);
1424
1425   //CLog::Log(LOGDEBUG,"CUtil::GetMatchingSource, testing for matching name [%s]", strPath.c_str());
1426   bIsSourceName = false;
1427   int iIndex = -1;
1428   int iLength = -1;
1429   // we first test the NAME of a source
1430   for (int i = 0; i < (int)VECSOURCES.size(); ++i)
1431   {
1432     CMediaSource share = VECSOURCES.at(i);
1433     CStdString strName = share.strName;
1434
1435     // special cases for dvds
1436     if (URIUtils::IsOnDVD(share.strPath))
1437     {
1438       if (URIUtils::IsOnDVD(strPath))
1439         return i;
1440
1441       // not a path, so we need to modify the source name
1442       // since we add the drive status and disc name to the source
1443       // "Name (Drive Status/Disc Name)"
1444       int iPos = strName.ReverseFind('(');
1445       if (iPos > 1)
1446         strName = strName.Mid(0, iPos - 1);
1447     }
1448     //CLog::Log(LOGDEBUG,"CUtil::GetMatchingSource, comparing name [%s]", strName.c_str());
1449     if (strPath.Equals(strName))
1450     {
1451       bIsSourceName = true;
1452       return i;
1453     }
1454   }
1455
1456   // now test the paths
1457
1458   // remove user details, and ensure path only uses forward slashes
1459   // and ends with a trailing slash so as not to match a substring
1460   CURL urlDest(strPath);
1461   urlDest.SetOptions("");
1462   CStdString strDest = urlDest.GetWithoutUserDetails();
1463   ForceForwardSlashes(strDest);
1464   if (!URIUtils::HasSlashAtEnd(strDest))
1465     strDest += "/";
1466   int iLenPath = strDest.size();
1467
1468   //CLog::Log(LOGDEBUG,"CUtil::GetMatchingSource, testing url [%s]", strDest.c_str());
1469
1470   for (int i = 0; i < (int)VECSOURCES.size(); ++i)
1471   {
1472     CMediaSource share = VECSOURCES.at(i);
1473
1474     // does it match a source name?
1475     if (share.strPath.substr(0,8) == "shout://")
1476     {
1477       CURL url(share.strPath);
1478       if (strPath.Equals(url.GetHostName()))
1479         return i;
1480     }
1481
1482     // doesnt match a name, so try the source path
1483     vector<CStdString> vecPaths;
1484
1485     // add any concatenated paths if they exist
1486     if (share.vecPaths.size() > 0)
1487       vecPaths = share.vecPaths;
1488
1489     // add the actual share path at the front of the vector
1490     vecPaths.insert(vecPaths.begin(), share.strPath);
1491
1492     // test each path
1493     for (int j = 0; j < (int)vecPaths.size(); ++j)
1494     {
1495       // remove user details, and ensure path only uses forward slashes
1496       // and ends with a trailing slash so as not to match a substring
1497       CURL urlShare(vecPaths[j]);
1498       urlShare.SetOptions("");
1499       CStdString strShare = urlShare.GetWithoutUserDetails();
1500       ForceForwardSlashes(strShare);
1501       if (!URIUtils::HasSlashAtEnd(strShare))
1502         strShare += "/";
1503       int iLenShare = strShare.size();
1504       //CLog::Log(LOGDEBUG,"CUtil::GetMatchingSource, comparing url [%s]", strShare.c_str());
1505
1506       if ((iLenPath >= iLenShare) && (strDest.Left(iLenShare).Equals(strShare)) && (iLenShare > iLength))
1507       {
1508         //CLog::Log(LOGDEBUG,"Found matching source at index %i: [%s], Len = [%i]", i, strShare.c_str(), iLenShare);
1509
1510         // if exact match, return it immediately
1511         if (iLenPath == iLenShare)
1512         {
1513           // if the path EXACTLY matches an item in a concatentated path
1514           // set source name to true to load the full virtualpath
1515           bIsSourceName = false;
1516           if (vecPaths.size() > 1)
1517             bIsSourceName = true;
1518           return i;
1519         }
1520         iIndex = i;
1521         iLength = iLenShare;
1522       }
1523     }
1524   }
1525
1526   // return the index of the share with the longest match
1527   if (iIndex == -1)
1528   {
1529
1530     // rar:// and zip://
1531     // if archive wasn't mounted, look for a matching share for the archive instead
1532     if( strPath.Left(6).Equals("rar://") || strPath.Left(6).Equals("zip://") )
1533     {
1534       // get the hostname portion of the url since it contains the archive file
1535       strPath = checkURL.GetHostName();
1536
1537       bIsSourceName = false;
1538       bool bDummy;
1539       return GetMatchingSource(strPath, VECSOURCES, bDummy);
1540     }
1541
1542     CLog::Log(LOGWARNING,"CUtil::GetMatchingSource... no matching source found for [%s]", strPath1.c_str());
1543   }
1544   return iIndex;
1545 }
1546
1547 CStdString CUtil::TranslateSpecialSource(const CStdString &strSpecial)
1548 {
1549   CStdString strReturn=strSpecial;
1550   if (!strSpecial.IsEmpty() && strSpecial[0] == '$')
1551   {
1552     if (strSpecial.Left(5).Equals("$HOME"))
1553       URIUtils::AddFileToFolder("special://home/", strSpecial.Mid(5), strReturn);
1554     else if (strSpecial.Left(10).Equals("$SUBTITLES"))
1555       URIUtils::AddFileToFolder("special://subtitles/", strSpecial.Mid(10), strReturn);
1556     else if (strSpecial.Left(9).Equals("$USERDATA"))
1557       URIUtils::AddFileToFolder("special://userdata/", strSpecial.Mid(9), strReturn);
1558     else if (strSpecial.Left(9).Equals("$DATABASE"))
1559       URIUtils::AddFileToFolder("special://database/", strSpecial.Mid(9), strReturn);
1560     else if (strSpecial.Left(11).Equals("$THUMBNAILS"))
1561       URIUtils::AddFileToFolder("special://thumbnails/", strSpecial.Mid(11), strReturn);
1562     else if (strSpecial.Left(11).Equals("$RECORDINGS"))
1563       URIUtils::AddFileToFolder("special://recordings/", strSpecial.Mid(11), strReturn);
1564     else if (strSpecial.Left(12).Equals("$SCREENSHOTS"))
1565       URIUtils::AddFileToFolder("special://screenshots/", strSpecial.Mid(12), strReturn);
1566     else if (strSpecial.Left(15).Equals("$MUSICPLAYLISTS"))
1567       URIUtils::AddFileToFolder("special://musicplaylists/", strSpecial.Mid(15), strReturn);
1568     else if (strSpecial.Left(15).Equals("$VIDEOPLAYLISTS"))
1569       URIUtils::AddFileToFolder("special://videoplaylists/", strSpecial.Mid(15), strReturn);
1570     else if (strSpecial.Left(7).Equals("$CDRIPS"))
1571       URIUtils::AddFileToFolder("special://cdrips/", strSpecial.Mid(7), strReturn);
1572     // this one will be removed post 2.0
1573     else if (strSpecial.Left(10).Equals("$PLAYLISTS"))
1574       URIUtils::AddFileToFolder(g_guiSettings.GetString("system.playlistspath",false), strSpecial.Mid(10), strReturn);
1575   }
1576   return strReturn;
1577 }
1578
1579 CStdString CUtil::MusicPlaylistsLocation()
1580 {
1581   vector<CStdString> vec;
1582   CStdString strReturn;
1583   URIUtils::AddFileToFolder(g_guiSettings.GetString("system.playlistspath"), "music", strReturn);
1584   vec.push_back(strReturn);
1585   URIUtils::AddFileToFolder(g_guiSettings.GetString("system.playlistspath"), "mixed", strReturn);
1586   vec.push_back(strReturn);
1587   return XFILE::CMultiPathDirectory::ConstructMultiPath(vec);;
1588 }
1589
1590 CStdString CUtil::VideoPlaylistsLocation()
1591 {
1592   vector<CStdString> vec;
1593   CStdString strReturn;
1594   URIUtils::AddFileToFolder(g_guiSettings.GetString("system.playlistspath"), "video", strReturn);
1595   vec.push_back(strReturn);
1596   URIUtils::AddFileToFolder(g_guiSettings.GetString("system.playlistspath"), "mixed", strReturn);
1597   vec.push_back(strReturn);
1598   return XFILE::CMultiPathDirectory::ConstructMultiPath(vec);;
1599 }
1600
1601 void CUtil::DeleteMusicDatabaseDirectoryCache()
1602 {
1603   CUtil::DeleteDirectoryCache("mdb-");
1604 }
1605
1606 void CUtil::DeleteVideoDatabaseDirectoryCache()
1607 {
1608   CUtil::DeleteDirectoryCache("vdb-");
1609 }
1610
1611 void CUtil::DeleteDirectoryCache(const CStdString &prefix)
1612 {
1613   CStdString searchPath = "special://temp/";
1614   CFileItemList items;
1615   if (!XFILE::CDirectory::GetDirectory(searchPath, items, ".fi", false))
1616     return;
1617
1618   for (int i = 0; i < items.Size(); ++i)
1619   {
1620     if (items[i]->m_bIsFolder)
1621       continue;
1622     CStdString fileName = URIUtils::GetFileName(items[i]->m_strPath);
1623     if (fileName.Left(prefix.GetLength()) == prefix)
1624       XFILE::CFile::Delete(items[i]->m_strPath);
1625   }
1626 }
1627
1628 bool CUtil::SetSysDateTimeYear(int iYear, int iMonth, int iDay, int iHour, int iMinute)
1629 {
1630   TIME_ZONE_INFORMATION tziNew;
1631   SYSTEMTIME CurTime;
1632   SYSTEMTIME NewTime;
1633   GetLocalTime(&CurTime);
1634   GetLocalTime(&NewTime);
1635   int iRescBiases, iHourUTC;
1636   int iMinuteNew;
1637
1638   DWORD dwRet = GetTimeZoneInformation(&tziNew);  // Get TimeZone Informations
1639   float iGMTZone = (float(tziNew.Bias)/(60));     // Calc's the GMT Time
1640
1641   CLog::Log(LOGDEBUG, "------------ TimeZone -------------");
1642   CLog::Log(LOGDEBUG, "-      GMT Zone: GMT %.1f",iGMTZone);
1643   CLog::Log(LOGDEBUG, "-          Bias: %lu minutes",tziNew.Bias);
1644   CLog::Log(LOGDEBUG, "-  DaylightBias: %lu",tziNew.DaylightBias);
1645   CLog::Log(LOGDEBUG, "-  StandardBias: %lu",tziNew.StandardBias);
1646
1647   switch (dwRet)
1648   {
1649     case TIME_ZONE_ID_STANDARD:
1650       {
1651         iRescBiases   = tziNew.Bias + tziNew.StandardBias;
1652         CLog::Log(LOGDEBUG, "-   Timezone ID: 1, Standart");
1653       }
1654       break;
1655     case TIME_ZONE_ID_DAYLIGHT:
1656       {
1657         iRescBiases   = tziNew.Bias + tziNew.StandardBias + tziNew.DaylightBias;
1658         CLog::Log(LOGDEBUG, "-   Timezone ID: 2, Daylight");
1659       }
1660       break;
1661     case TIME_ZONE_ID_UNKNOWN:
1662       {
1663         iRescBiases   = tziNew.Bias + tziNew.StandardBias;
1664         CLog::Log(LOGDEBUG, "-   Timezone ID: 0, Unknown");
1665       }
1666       break;
1667     case TIME_ZONE_ID_INVALID:
1668       {
1669         iRescBiases   = tziNew.Bias + tziNew.StandardBias;
1670         CLog::Log(LOGDEBUG, "-   Timezone ID: Invalid");
1671       }
1672       break;
1673     default:
1674       iRescBiases   = tziNew.Bias + tziNew.StandardBias;
1675   }
1676     CLog::Log(LOGDEBUG, "--------------- END ---------------");
1677
1678   // Calculation
1679   iHourUTC = GMTZoneCalc(iRescBiases, iHour, iMinute, iMinuteNew);
1680   iMinute = iMinuteNew;
1681   if(iHourUTC <0)
1682   {
1683     iDay = iDay - 1;
1684     iHourUTC =iHourUTC + 24;
1685   }
1686   if(iHourUTC >23)
1687   {
1688     iDay = iDay + 1;
1689     iHourUTC =iHourUTC - 24;
1690   }
1691
1692   // Set the New-,Detected Time Values to System Time!
1693   NewTime.wYear     = (WORD)iYear;
1694   NewTime.wMonth    = (WORD)iMonth;
1695   NewTime.wDay      = (WORD)iDay;
1696   NewTime.wHour     = (WORD)iHourUTC;
1697   NewTime.wMinute   = (WORD)iMinute;
1698
1699   FILETIME stNewTime, stCurTime;
1700   SystemTimeToFileTime(&NewTime, &stNewTime);
1701   SystemTimeToFileTime(&CurTime, &stCurTime);
1702   return false;
1703 }
1704 int CUtil::GMTZoneCalc(int iRescBiases, int iHour, int iMinute, int &iMinuteNew)
1705 {
1706   int iHourUTC, iTemp;
1707   iMinuteNew = iMinute;
1708   iTemp = iRescBiases/60;
1709
1710   if (iRescBiases == 0 )return iHour;   // GMT Zone 0, no need calculate
1711   if (iRescBiases > 0)
1712     iHourUTC = iHour + abs(iTemp);
1713   else
1714     iHourUTC = iHour - abs(iTemp);
1715
1716   if ((iTemp*60) != iRescBiases)
1717   {
1718     if (iRescBiases > 0)
1719       iMinuteNew = iMinute + abs(iTemp*60 - iRescBiases);
1720     else
1721       iMinuteNew = iMinute - abs(iTemp*60 - iRescBiases);
1722
1723     if (iMinuteNew >= 60)
1724     {
1725       iMinuteNew = iMinuteNew -60;
1726       iHourUTC = iHourUTC + 1;
1727     }
1728     else if (iMinuteNew < 0)
1729     {
1730       iMinuteNew = iMinuteNew +60;
1731       iHourUTC = iHourUTC - 1;
1732     }
1733   }
1734   return iHourUTC;
1735 }
1736
1737 void CUtil::GetRecursiveListing(const CStdString& strPath, CFileItemList& items, const CStdString& strMask, bool bUseFileDirectories)
1738 {
1739   CFileItemList myItems;
1740   CDirectory::GetDirectory(strPath,myItems,strMask,bUseFileDirectories);
1741   for (int i=0;i<myItems.Size();++i)
1742   {
1743     if (myItems[i]->m_bIsFolder)
1744       CUtil::GetRecursiveListing(myItems[i]->m_strPath,items,strMask,bUseFileDirectories);
1745     else
1746       items.Add(myItems[i]);
1747   }
1748 }
1749
1750 void CUtil::GetRecursiveDirsListing(const CStdString& strPath, CFileItemList& item)
1751 {
1752   CFileItemList myItems;
1753   CDirectory::GetDirectory(strPath,myItems,"",false);
1754   for (int i=0;i<myItems.Size();++i)
1755   {
1756     if (myItems[i]->m_bIsFolder && !myItems[i]->m_strPath.Equals(".."))
1757     {
1758       item.Add(myItems[i]);
1759       CUtil::GetRecursiveDirsListing(myItems[i]->m_strPath,item);
1760     }
1761   }
1762 }
1763
1764 void CUtil::ForceForwardSlashes(CStdString& strPath)
1765 {
1766   int iPos = strPath.ReverseFind('\\');
1767   while (iPos > 0)
1768   {
1769     strPath.at(iPos) = '/';
1770     iPos = strPath.ReverseFind('\\');
1771   }
1772 }
1773
1774 double CUtil::AlbumRelevance(const CStdString& strAlbumTemp1, const CStdString& strAlbum1, const CStdString& strArtistTemp1, const CStdString& strArtist1)
1775 {
1776   // case-insensitive fuzzy string comparison on the album and artist for relevance
1777   // weighting is identical, both album and artist are 50% of the total relevance
1778   // a missing artist means the maximum relevance can only be 0.50
1779   CStdString strAlbumTemp = strAlbumTemp1;
1780   strAlbumTemp.MakeLower();
1781   CStdString strAlbum = strAlbum1;
1782   strAlbum.MakeLower();
1783   double fAlbumPercentage = fstrcmp(strAlbumTemp, strAlbum, 0.0f);
1784   double fArtistPercentage = 0.0f;
1785   if (!strArtist1.IsEmpty())
1786   {
1787     CStdString strArtistTemp = strArtistTemp1;
1788     strArtistTemp.MakeLower();
1789     CStdString strArtist = strArtist1;
1790     strArtist.MakeLower();
1791     fArtistPercentage = fstrcmp(strArtistTemp, strArtist, 0.0f);
1792   }
1793   double fRelevance = fAlbumPercentage * 0.5f + fArtistPercentage * 0.5f;
1794   return fRelevance;
1795 }
1796
1797 CStdString CUtil::SubstitutePath(const CStdString& strFileName)
1798 {
1799   for (CAdvancedSettings::StringMapping::iterator i = g_advancedSettings.m_pathSubstitutions.begin(); 
1800                                                   i != g_advancedSettings.m_pathSubstitutions.end(); i++)
1801   {
1802     if (strncmp(strFileName.c_str(), i->first.c_str(), i->first.size()) == 0)
1803       return URIUtils::AddFileToFolder(i->second, strFileName.Mid(i->first.size()));
1804   }
1805   return strFileName;
1806 }
1807
1808 bool CUtil::MakeShortenPath(CStdString StrInput, CStdString& StrOutput, int iTextMaxLength)
1809 {
1810   int iStrInputSize = StrInput.size();
1811   if((iStrInputSize <= 0) || (iTextMaxLength >= iStrInputSize))
1812     return false;
1813
1814   char cDelim = '\0';
1815   size_t nGreaterDelim, nPos;
1816
1817   nPos = StrInput.find_last_of( '\\' );
1818   if ( nPos != CStdString::npos )
1819     cDelim = '\\';
1820   else
1821   {
1822     nPos = StrInput.find_last_of( '/' );
1823     if ( nPos != CStdString::npos )
1824       cDelim = '/';
1825   }
1826   if ( cDelim == '\0' )
1827     return false;
1828
1829   if (nPos == StrInput.size() - 1)
1830   {
1831     StrInput.erase(StrInput.size() - 1);
1832     nPos = StrInput.find_last_of( cDelim );
1833   }
1834   while( iTextMaxLength < iStrInputSize )
1835   {
1836     nPos = StrInput.find_last_of( cDelim, nPos );
1837     nGreaterDelim = nPos;
1838     if ( nPos != CStdString::npos )
1839       nPos = StrInput.find_last_of( cDelim, nPos - 1 );
1840     if ( nPos == CStdString::npos ) break;
1841     if ( nGreaterDelim > nPos ) StrInput.replace( nPos + 1, nGreaterDelim - nPos - 1, ".." );
1842     iStrInputSize = StrInput.size();
1843   }
1844   // replace any additional /../../ with just /../ if necessary
1845   CStdString replaceDots;
1846   replaceDots.Format("..%c..", cDelim);
1847   while (StrInput.size() > (unsigned int)iTextMaxLength)
1848     if (!StrInput.Replace(replaceDots, ".."))
1849       break;
1850   // finally, truncate our string to force inside our max text length,
1851   // replacing the last 2 characters with ".."
1852
1853   // eg end up with:
1854   // "smb://../Playboy Swimsuit Cal.."
1855   if (iTextMaxLength > 2 && StrInput.size() > (unsigned int)iTextMaxLength)
1856   {
1857     StrInput = StrInput.Left(iTextMaxLength - 2);
1858     StrInput += "..";
1859   }
1860   StrOutput = StrInput;
1861   return true;
1862 }
1863
1864 bool CUtil::SupportsFileOperations(const CStdString& strPath)
1865 {
1866   // currently only hd and smb support delete and rename
1867   if (URIUtils::IsHD(strPath))
1868     return true;
1869   if (URIUtils::IsSmb(strPath))
1870     return true;
1871   if (URIUtils::IsMythTV(strPath))
1872   {
1873     /*
1874      * Can't use CFile::Exists() to check whether the myth:// path supports file operations because
1875      * it hits the directory cache on the way through, which has the Live Channels and Guide
1876      * items cached.
1877      */
1878     return CMythDirectory::SupportsFileOperations(strPath);
1879   }
1880   if (URIUtils::IsStack(strPath))
1881   {
1882     CStackDirectory dir;
1883     return SupportsFileOperations(dir.GetFirstStackedFile(strPath));
1884   }
1885   if (URIUtils::IsMultiPath(strPath))
1886     return CMultiPathDirectory::SupportsFileOperations(strPath);
1887
1888   return false;
1889 }
1890
1891 CStdString CUtil::GetCachedAlbumThumb(const CStdString& album, const CStdString& artist)
1892 {
1893   if (album.IsEmpty())
1894     return GetCachedMusicThumb("unknown"+artist);
1895   if (artist.IsEmpty())
1896     return GetCachedMusicThumb(album+"unknown");
1897   return GetCachedMusicThumb(album+artist);
1898 }
1899
1900 CStdString CUtil::GetCachedMusicThumb(const CStdString& path)
1901 {
1902   Crc32 crc;
1903   CStdString noSlashPath(path);
1904   URIUtils::RemoveSlashAtEnd(noSlashPath);
1905   crc.ComputeFromLowerCase(noSlashPath);
1906   CStdString hex;
1907   hex.Format("%08x", (unsigned __int32) crc);
1908   CStdString thumb;
1909   thumb.Format("%c/%s.tbn", hex[0], hex.c_str());
1910   return URIUtils::AddFileToFolder(g_settings.GetMusicThumbFolder(), thumb);
1911 }
1912
1913 CStdString CUtil::GetDefaultFolderThumb(const CStdString &folderThumb)
1914 {
1915   if (g_TextureManager.HasTexture(folderThumb))
1916     return folderThumb;
1917   return "";
1918 }
1919
1920 void CUtil::GetSkinThemes(vector<CStdString>& vecTheme)
1921 {
1922   CStdString strPath;
1923   URIUtils::AddFileToFolder(g_graphicsContext.GetMediaDir(),"media",strPath);
1924   CFileItemList items;
1925   CDirectory::GetDirectory(strPath, items);
1926   // Search for Themes in the Current skin!
1927   for (int i = 0; i < items.Size(); ++i)
1928   {
1929     CFileItemPtr pItem = items[i];
1930     if (!pItem->m_bIsFolder)
1931     {
1932       CStdString strExtension;
1933       URIUtils::GetExtension(pItem->m_strPath, strExtension);
1934       if ((strExtension == ".xpr" && pItem->GetLabel().CompareNoCase("Textures.xpr")) ||
1935           (strExtension == ".xbt" && pItem->GetLabel().CompareNoCase("Textures.xbt")))
1936       {
1937         CStdString strLabel = pItem->GetLabel();
1938         vecTheme.push_back(strLabel.Mid(0, strLabel.size() - 4));
1939       }
1940     }
1941   }
1942   sort(vecTheme.begin(), vecTheme.end(), sortstringbyname());
1943 }
1944
1945 void CUtil::InitRandomSeed()
1946 {
1947   // Init random seed
1948   int64_t now;
1949   now = CurrentHostCounter();
1950   unsigned int seed = (unsigned int)now;
1951 //  CLog::Log(LOGDEBUG, "%s - Initializing random seed with %u", __FUNCTION__, seed);
1952   srand(seed);
1953 }
1954
1955 #ifdef _LINUX
1956 bool CUtil::RunCommandLine(const CStdString& cmdLine, bool waitExit)
1957 {
1958   CStdStringArray args;
1959
1960   StringUtils::SplitString(cmdLine, ",", args);
1961
1962   // Strip quotes and whitespace around the arguments, or exec will fail.
1963   // This allows the python invocation to be written more naturally with any amount of whitespace around the args.
1964   // But it's still limited, for example quotes inside the strings are not expanded, etc.
1965   // TODO: Maybe some python library routine can parse this more properly ?
1966   for (size_t i=0; i<args.size(); i++)
1967   {
1968     CStdString &s = args[i];
1969     CStdString stripd = s.Trim();
1970     if (stripd[0] == '"' || stripd[0] == '\'')
1971     {
1972       s = s.TrimLeft();
1973       s = s.Right(s.size() - 1);
1974     }
1975     if (stripd[stripd.size() - 1] == '"' || stripd[stripd.size() - 1] == '\'')
1976     {
1977       s = s.TrimRight();
1978       s = s.Left(s.size() - 1);
1979     }
1980   }
1981
1982   return Command(args, waitExit);
1983 }
1984
1985 //
1986 // FIXME, this should be merged with the function below.
1987 //
1988 bool CUtil::Command(const CStdStringArray& arrArgs, bool waitExit)
1989 {
1990 #ifdef _DEBUG
1991   printf("Executing: ");
1992   for (size_t i=0; i<arrArgs.size(); i++)
1993     printf("%s ", arrArgs[i].c_str());
1994   printf("\n");
1995 #endif
1996
1997   pid_t child = fork();
1998   int n = 0;
1999   if (child == 0)
2000   {
2001     close(0);
2002     close(1);
2003     close(2);
2004     if (arrArgs.size() > 0)
2005     {
2006       char **args = (char **)alloca(sizeof(char *) * (arrArgs.size() + 3));
2007       memset(args, 0, (sizeof(char *) * (arrArgs.size() + 3)));
2008       for (size_t i=0; i<arrArgs.size(); i++)
2009         args[i] = (char *)arrArgs[i].c_str();
2010       execvp(args[0], args);
2011     }
2012   }
2013   else
2014   {
2015     if (waitExit) waitpid(child, &n, 0);
2016   }
2017
2018   return (waitExit) ? (WEXITSTATUS(n) == 0) : true;
2019 }
2020
2021 bool CUtil::SudoCommand(const CStdString &strCommand)
2022 {
2023   CLog::Log(LOGDEBUG, "Executing sudo command: <%s>", strCommand.c_str());
2024   pid_t child = fork();
2025   int n = 0;
2026   if (child == 0)
2027   {
2028     close(0); // close stdin to avoid sudo request password
2029     close(1);
2030     close(2);
2031     CStdStringArray arrArgs;
2032     StringUtils::SplitString(strCommand, " ", arrArgs);
2033     if (arrArgs.size() > 0)
2034     {
2035       char **args = (char **)alloca(sizeof(char *) * (arrArgs.size() + 3));
2036       memset(args, 0, (sizeof(char *) * (arrArgs.size() + 3)));
2037       args[0] = (char *)"/usr/bin/sudo";
2038       args[1] = (char *)"-S";
2039       for (size_t i=0; i<arrArgs.size(); i++)
2040       {
2041         args[i+2] = (char *)arrArgs[i].c_str();
2042       }
2043       execvp("/usr/bin/sudo", args);
2044     }
2045   }
2046   else
2047     waitpid(child, &n, 0);
2048
2049   return WEXITSTATUS(n) == 0;
2050 }
2051 #endif
2052
2053 int CUtil::LookupRomanDigit(char roman_digit)
2054 {
2055   switch (roman_digit)
2056   {
2057     case 'i':
2058     case 'I':
2059       return 1;
2060     case 'v':
2061     case 'V':
2062       return 5;
2063     case 'x':
2064     case 'X':
2065       return 10;
2066     case 'l':
2067     case 'L':
2068       return 50;
2069     case 'c':
2070     case 'C':
2071       return 100;
2072     case 'd':
2073     case 'D':
2074       return 500;
2075     case 'm':
2076     case 'M':
2077       return 1000;
2078     default:
2079       return 0;
2080   }
2081 }
2082
2083 int CUtil::TranslateRomanNumeral(const char* roman_numeral)
2084 {
2085   
2086   int decimal = -1;
2087
2088   if (roman_numeral && roman_numeral[0])
2089   {
2090     int temp_sum  = 0,
2091         last      = 0,
2092         repeat    = 0,
2093         trend     = 1;
2094     decimal = 0;
2095     while (*roman_numeral)
2096     {
2097       int digit = CUtil::LookupRomanDigit(*roman_numeral);
2098       int test  = last;
2099       
2100       // General sanity checks
2101
2102       // numeral not in LUT
2103       if (!digit)
2104         return -1;
2105       
2106       while (test > 5)
2107         test /= 10;
2108       
2109       // N = 10^n may not precede (N+1) > 10^(N+1)
2110       if (test == 1 && digit > last * 10)
2111         return -1;
2112       
2113       // N = 5*10^n may not precede (N+1) >= N
2114       if (test == 5 && digit >= last) 
2115         return -1;
2116
2117       // End general sanity checks
2118
2119       if (last < digit)
2120       {
2121         // smaller numerals may not repeat before a larger one
2122         if (repeat) 
2123           return -1;
2124
2125         temp_sum += digit;
2126         
2127         repeat  = 0;
2128         trend   = 0;
2129       }
2130       else if (last == digit)
2131       {
2132         temp_sum += digit;
2133         repeat++;
2134         trend = 1;
2135       }
2136       else
2137       {
2138         if (!repeat)
2139           decimal += 2 * last - temp_sum;
2140         else
2141           decimal += temp_sum;
2142         
2143         temp_sum = digit;
2144
2145         trend   = 1;
2146         repeat  = 0;
2147       }
2148       // Post general sanity checks
2149
2150       // numerals may not repeat more than thrice
2151       if (repeat == 3)
2152         return -1;
2153
2154       last = digit;
2155       roman_numeral++;
2156     }
2157
2158     if (trend)
2159       decimal += temp_sum;
2160     else
2161       decimal += 2 * last - temp_sum;
2162   }
2163   return decimal;
2164 }
2165
2166 CStdString CUtil::ResolveExecutablePath()
2167 {
2168   CStdString strExecutablePath;
2169 #ifdef WIN32
2170   wchar_t szAppPathW[MAX_PATH] = L"";
2171   ::GetModuleFileNameW(0, szAppPathW, sizeof(szAppPathW) - 1);
2172   CStdStringW strPathW = szAppPathW;
2173   g_charsetConverter.wToUTF8(strPathW,strExecutablePath);
2174 #elif defined(__APPLE__)
2175   int      result = -1;
2176   char     given_path[2*MAXPATHLEN];
2177   char     real_given_path[2*MAXPATHLEN];
2178   uint32_t path_size = 2*MAXPATHLEN;
2179
2180   #if defined(__arm__)
2181     result = GetIOSExecutablePath(given_path, &path_size);
2182   #else
2183     result = _NSGetExecutablePath(given_path, &path_size);
2184   #endif
2185   if (result == 0)
2186     realpath(given_path, real_given_path);
2187   strExecutablePath = real_given_path;
2188 #else
2189   /* Get our PID and build the name of the link in /proc */
2190   pid_t pid = getpid();
2191   char linkname[64]; /* /proc/<pid>/exe */
2192   snprintf(linkname, sizeof(linkname), "/proc/%i/exe", pid);
2193
2194   /* Now read the symbolic link */
2195   char buf[PATH_MAX];
2196   int ret = readlink(linkname, buf, PATH_MAX);
2197   buf[ret] = 0;
2198
2199   strExecutablePath = buf;
2200 #endif
2201   return strExecutablePath;
2202 }
2203
2204 CStdString CUtil::GetFrameworksPath(void)
2205 {
2206   CStdString strFrameworksPath;
2207 #if defined(__APPLE__)
2208   char     given_path[2*MAXPATHLEN];
2209   char     real_given_path[2*MAXPATHLEN];
2210   uint32_t path_size = 2*MAXPATHLEN;
2211
2212   #if defined(__arm__)
2213     GetIOSFrameworkPath(given_path, &path_size);
2214     if (realpath(given_path, real_given_path) != NULL)
2215       strFrameworksPath = real_given_path;
2216   #else
2217     _NSGetExecutablePath(given_path, &path_size);
2218     // Move backwards to last /.
2219     for (int n=strlen(given_path)-1; given_path[n] != '/'; n--)
2220       given_path[n] = '\0';
2221
2222     // Assume local path inside application bundle.
2223     strcat(given_path, "../Frameworks");
2224     // Convert to real path.
2225     if (realpath(given_path, real_given_path) != NULL)
2226     {
2227       // check if we are running as real xbmc.app
2228       if (strstr(real_given_path, "Contents"))
2229         strFrameworksPath = real_given_path;
2230     }
2231   #endif
2232 #endif
2233   return strFrameworksPath;
2234 }
2235
2236 void CUtil::ScanForExternalSubtitles(const CStdString& strMovie, std::vector<CStdString>& vecSubtitles )
2237 {
2238   unsigned int startTimer = CTimeUtils::GetTimeMS();
2239   
2240   // new array for commons sub dirs
2241   const char * common_sub_dirs[] = {"subs",
2242     "Subs",
2243     "subtitles",
2244     "Subtitles",
2245     "vobsubs",
2246     "Vobsubs",
2247     "sub",
2248     "Sub",
2249     "vobsub",
2250     "Vobsub",
2251     "subtitle",
2252     "Subtitle",
2253     NULL};
2254   
2255   vector<CStdString> vecExtensionsCached;
2256   //strExtensionCached = "";
2257   
2258   CFileItem item(strMovie, false);
2259   if (item.IsInternetStream()) return ;
2260   if (item.IsHDHomeRun()) return ;
2261   if (item.IsPlayList()) return ;
2262   if (!item.IsVideo()) return ;
2263   
2264   vector<CStdString> strLookInPaths;
2265   
2266   CStdString strMovieFileName;
2267   CStdString strPath;
2268   
2269   URIUtils::Split(strMovie, strPath, strMovieFileName);
2270   CStdString strMovieFileNameNoExt(URIUtils::ReplaceExtension(strMovieFileName, ""));
2271   strLookInPaths.push_back(strPath);
2272   
2273   if (!g_settings.iAdditionalSubtitleDirectoryChecked && !g_guiSettings.GetString("subtitles.custompath").IsEmpty()) // to avoid checking non-existent directories (network) every time..
2274   {
2275     if (!g_application.getNetwork().IsAvailable() && !URIUtils::IsHD(g_guiSettings.GetString("subtitles.custompath")))
2276     {
2277       CLog::Log(LOGINFO,"CUtil::CacheSubtitles: disabling alternate subtitle directory for this session, it's nonaccessible");
2278       g_settings.iAdditionalSubtitleDirectoryChecked = -1; // disabled
2279     }
2280     else if (!CDirectory::Exists(g_guiSettings.GetString("subtitles.custompath")))
2281     {
2282       CLog::Log(LOGINFO,"CUtil::CacheSubtitles: disabling alternate subtitle directory for this session, it's nonexistant");
2283       g_settings.iAdditionalSubtitleDirectoryChecked = -1; // disabled
2284     }
2285     
2286     g_settings.iAdditionalSubtitleDirectoryChecked = 1;
2287   }
2288   
2289   if (strMovie.Left(6) == "rar://") // <--- if this is found in main path then ignore it!
2290   {
2291     CURL url(strMovie);
2292     CStdString strArchive = url.GetHostName();
2293     URIUtils::Split(strArchive, strPath, strMovieFileName);
2294     strLookInPaths.push_back(strPath);
2295   }
2296   
2297   // checking if any of the common subdirs exist ..
2298   CStdStringArray directories;
2299   int nTokens = StringUtils::SplitString( strPath, "/", directories );
2300   if (nTokens == 1)
2301     StringUtils::SplitString( strPath, "\\", directories );
2302   
2303   // if it's inside a cdX dir, add parent path
2304   if (directories[directories.size()-2].size() == 3 && directories[directories.size()-2].Left(2).Equals("cd")) // SplitString returns empty token as last item, hence size-2
2305   {
2306     CStdString strPath2;
2307     URIUtils::GetParentPath(strPath,strPath2);
2308     strLookInPaths.push_back(strPath2);
2309   }
2310   int iSize = strLookInPaths.size();
2311   for (int i=0;i<iSize;++i)
2312   {
2313     for (int j=0; common_sub_dirs[j]; j++)
2314     {
2315       CStdString strPath2 = URIUtils::AddFileToFolder(strLookInPaths[i],common_sub_dirs[j]);
2316       if (CDirectory::Exists(strPath2))
2317         strLookInPaths.push_back(strPath2);
2318     }
2319   }
2320   // .. done checking for common subdirs
2321   
2322   // check if there any cd-directories in the paths we have added so far
2323   iSize = strLookInPaths.size();
2324   for (int i=0;i<9;++i) // 9 cd's
2325   {
2326     CStdString cdDir;
2327     cdDir.Format("cd%i",i+1);
2328     for (int i=0;i<iSize;++i)
2329     {
2330       CStdString strPath2 = URIUtils::AddFileToFolder(strLookInPaths[i],cdDir);
2331       URIUtils::AddSlashAtEnd(strPath2);
2332       bool pathAlreadyAdded = false;
2333       for (unsigned int i=0; i<strLookInPaths.size(); i++)
2334       {
2335         // if movie file is inside cd-dir, this directory can exist in vector already
2336         if (strLookInPaths[i].Equals( strPath2 ) )
2337           pathAlreadyAdded = true;
2338       }
2339       if (CDirectory::Exists(strPath2) && !pathAlreadyAdded) 
2340         strLookInPaths.push_back(strPath2);
2341     }
2342   }
2343   // .. done checking for cd-dirs
2344   
2345   // this is last because we dont want to check any common subdirs or cd-dirs in the alternate <subtitles> dir.
2346   if (g_settings.iAdditionalSubtitleDirectoryChecked == 1)
2347   {
2348     strPath = g_guiSettings.GetString("subtitles.custompath");
2349     URIUtils::AddSlashAtEnd(strPath);
2350     strLookInPaths.push_back(strPath);
2351   }
2352   
2353   CStdString strLExt;
2354   CStdString strDest;
2355   CStdString strItem;
2356   
2357   // 2 steps for movie directory and alternate subtitles directory
2358   CLog::Log(LOGDEBUG,"%s: Searching for subtitles...", __FUNCTION__);
2359   for (unsigned int step = 0; step < strLookInPaths.size(); step++)
2360   {
2361     if (strLookInPaths[step].length() != 0)
2362     {
2363       CFileItemList items;
2364       
2365       CDirectory::GetDirectory(strLookInPaths[step], items,".utf|.utf8|.utf-8|.sub|.srt|.smi|.rt|.txt|.ssa|.text|.ssa|.aqt|.jss|.ass|.idx|.ifo|.rar|.zip",false);
2366       int fnl = strMovieFileNameNoExt.size();
2367       
2368       for (int j = 0; j < items.Size(); j++)
2369       {
2370         URIUtils::Split(items[j]->m_strPath, strPath, strItem);
2371         
2372         // is this a rar or zip-file
2373         if (URIUtils::IsRAR(strItem) || URIUtils::IsZIP(strItem))
2374         {
2375           ScanArchiveForSubtitles( items[j]->m_strPath, strMovieFileNameNoExt, vecSubtitles );
2376         }
2377         else    // not a rar/zip file
2378         {
2379           for (int i = 0; sub_exts[i]; i++)
2380           {
2381             //Cache subtitle with same name as movie
2382             if (URIUtils::GetExtension(strItem).Equals(sub_exts[i]) && strItem.Left(fnl).Equals(strMovieFileNameNoExt))
2383             {
2384               vecSubtitles.push_back( items[j]->m_strPath ); 
2385               CLog::Log(LOGINFO, "%s: found subtitle file %s\n", __FUNCTION__, items[j]->m_strPath.c_str() );
2386             }
2387           }
2388         }
2389       }
2390       g_directoryCache.ClearDirectory(strLookInPaths[step]);
2391     }
2392   }
2393
2394   iSize = vecSubtitles.size();
2395   for (int i = 0; i < iSize; i++)
2396   {
2397     if (URIUtils::GetExtension(vecSubtitles[i]).Equals(".smi"))
2398     {
2399       //Cache multi-language sami subtitle
2400       CDVDSubtitleStream* pStream = new CDVDSubtitleStream();
2401       if(pStream->Open(vecSubtitles[i]))
2402       {
2403         CDVDSubtitleTagSami TagConv;
2404         TagConv.LoadHead(pStream);
2405         if (TagConv.m_Langclass.size() >= 2)
2406         {
2407           for (unsigned int k = 0; k < TagConv.m_Langclass.size(); k++)
2408           {
2409             strDest.Format("special://temp/subtitle.%s.%d.smi", TagConv.m_Langclass[k].Name, i);
2410             if (CFile::Cache(vecSubtitles[i], strDest))
2411             {
2412               CLog::Log(LOGINFO, " cached subtitle %s->%s\n", vecSubtitles[i].c_str(), strDest.c_str());
2413               vecSubtitles.push_back(strDest);
2414             }
2415           }
2416         }
2417       }
2418       delete pStream;
2419     }
2420   }
2421   CLog::Log(LOGDEBUG,"%s: END (total time: %i ms)", __FUNCTION__, (int)(CTimeUtils::GetTimeMS() - startTimer));
2422 }
2423
2424 int CUtil::ScanArchiveForSubtitles( const CStdString& strArchivePath, const CStdString& strMovieFileNameNoExt, std::vector<CStdString>& vecSubtitles )
2425 {
2426   int nSubtitlesAdded = 0;
2427   CFileItemList ItemList;
2428  
2429   // zip only gets the root dir
2430   if (URIUtils::GetExtension(strArchivePath).Equals(".zip"))
2431   {
2432    CStdString strZipPath;
2433    URIUtils::CreateArchivePath(strZipPath,"zip",strArchivePath,"");
2434    if (!CDirectory::GetDirectory(strZipPath,ItemList,"",false))
2435     return false;
2436   }
2437   else
2438   {
2439  #ifdef HAS_FILESYSTEM_RAR
2440    // get _ALL_files in the rar, even those located in subdirectories because we set the bMask to false.
2441    // so now we dont have to find any subdirs anymore, all files in the rar is checked.
2442    if( !g_RarManager.GetFilesInRar(ItemList, strArchivePath, false, "") )
2443     return false;
2444  #else
2445    return false;
2446  #endif
2447   }
2448   for (int it= 0 ; it <ItemList.Size();++it)
2449   {
2450    CStdString strPathInRar = ItemList[it]->m_strPath;
2451    CStdString strExt = URIUtils::GetExtension(strPathInRar);
2452    
2453    CLog::Log(LOGDEBUG, "ScanArchiveForSubtitles:: Found file %s", strPathInRar.c_str());
2454    // always check any embedded rar archives
2455    // checking for embedded rars, I moved this outside the sub_ext[] loop. We only need to check this once for each file.
2456    if (URIUtils::IsRAR(strPathInRar) || URIUtils::IsZIP(strPathInRar))
2457    {
2458     CStdString strRarInRar;
2459     if (URIUtils::GetExtension(strPathInRar).Equals(".rar"))
2460       URIUtils::CreateArchivePath(strRarInRar, "rar", strArchivePath, strPathInRar);
2461     else
2462       URIUtils::CreateArchivePath(strRarInRar, "zip", strArchivePath, strPathInRar);
2463     ScanArchiveForSubtitles(strRarInRar,strMovieFileNameNoExt,vecSubtitles);
2464    }
2465    // done checking if this is a rar-in-rar
2466    
2467    int iPos=0;
2468     while (sub_exts[iPos])
2469     {
2470      if (strExt.CompareNoCase(sub_exts[iPos]) == 0)
2471      {
2472       CStdString strSourceUrl;
2473       if (URIUtils::GetExtension(strArchivePath).Equals(".rar"))
2474        URIUtils::CreateArchivePath(strSourceUrl, "rar", strArchivePath, strPathInRar);
2475       else
2476        strSourceUrl = strPathInRar;
2477       
2478        CLog::Log(LOGINFO, "%s: found subtitle file %s\n", __FUNCTION__, strSourceUrl.c_str() );
2479        vecSubtitles.push_back( strSourceUrl );
2480        nSubtitlesAdded++;
2481      }
2482      
2483      iPos++;
2484     }
2485   }
2486
2487   return nSubtitlesAdded;
2488 }
2489
2490 /*! \brief in a vector of subtitles finds the corresponding .sub file for a given .idx file
2491  */
2492 bool CUtil::FindVobSubPair( const std::vector<CStdString>& vecSubtitles, const CStdString& strIdxPath, CStdString& strSubPath )
2493 {
2494   if (URIUtils::GetExtension(strIdxPath) == ".idx")
2495   {
2496     CStdString strIdxFile;
2497     CStdString strIdxDirectory;
2498     URIUtils::Split(strIdxPath, strIdxDirectory, strIdxFile);
2499     for (unsigned int j=0; j < vecSubtitles.size(); j++)
2500     {
2501       CStdString strSubFile;
2502       CStdString strSubDirectory;
2503       URIUtils::Split(vecSubtitles[j], strSubDirectory, strSubFile);
2504       if (URIUtils::GetExtension(strSubFile) == ".sub" &&
2505           URIUtils::ReplaceExtension(strIdxFile,"").Equals(URIUtils::ReplaceExtension(strSubFile,"")))
2506       {
2507         strSubPath = vecSubtitles[j];
2508         return true;
2509       }
2510     }
2511   }
2512   return false;
2513 }
2514
2515 /*! \brief checks if in the vector of subtitles the given .sub file has a corresponding idx and hence is a vobsub file
2516  */
2517 bool CUtil::IsVobSub( const std::vector<CStdString>& vecSubtitles, const CStdString& strSubPath )
2518 {
2519   if (URIUtils::GetExtension(strSubPath) == ".sub")
2520   {
2521     CStdString strSubFile;
2522     CStdString strSubDirectory;
2523     URIUtils::Split(strSubPath, strSubDirectory, strSubFile);
2524     for (unsigned int j=0; j < vecSubtitles.size(); j++)
2525     {
2526       CStdString strIdxFile;
2527       CStdString strIdxDirectory;
2528       URIUtils::Split(vecSubtitles[j], strIdxDirectory, strIdxFile);
2529       if (URIUtils::GetExtension(strIdxFile) == ".idx" &&
2530           URIUtils::ReplaceExtension(strIdxFile,"").Equals(URIUtils::ReplaceExtension(strSubFile,"")))
2531         return true;
2532     }
2533   }
2534   return false;
2535 }
2536