Merge pull request #804 from jmarshallnz/i_dont_like_paths
[vuplus_xbmc] / xbmc / video / VideoInfoScanner.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 "threads/SystemClock.h"
23 #include "FileItem.h"
24 #include "VideoInfoScanner.h"
25 #include "addons/AddonManager.h"
26 #include "filesystem/DirectoryCache.h"
27 #include "Util.h"
28 #include "NfoFile.h"
29 #include "utils/RegExp.h"
30 #include "utils/md5.h"
31 #include "pictures/Picture.h"
32 #include "filesystem/StackDirectory.h"
33 #include "VideoInfoDownloader.h"
34 #include "GUIInfoManager.h"
35 #include "filesystem/File.h"
36 #include "dialogs/GUIDialogProgress.h"
37 #include "dialogs/GUIDialogYesNo.h"
38 #include "dialogs/GUIDialogOK.h"
39 #include "interfaces/AnnouncementManager.h"
40 #include "settings/AdvancedSettings.h"
41 #include "settings/GUISettings.h"
42 #include "settings/Settings.h"
43 #include "utils/StringUtils.h"
44 #include "guilib/LocalizeStrings.h"
45 #include "utils/TimeUtils.h"
46 #include "utils/log.h"
47 #include "utils/URIUtils.h"
48 #include "utils/Variant.h"
49
50 using namespace std;
51 using namespace XFILE;
52 using namespace ADDON;
53
54 namespace VIDEO
55 {
56
57   CVideoInfoScanner::CVideoInfoScanner()
58   {
59     m_bRunning = false;
60     m_pObserver = NULL;
61     m_bCanInterrupt = false;
62     m_currentItem = 0;
63     m_itemCount = 0;
64     m_bClean = false;
65     m_scanAll = false;
66   }
67
68   CVideoInfoScanner::~CVideoInfoScanner()
69   {
70   }
71
72   void CVideoInfoScanner::Process()
73   {
74     try
75     {
76       unsigned int tick = XbmcThreads::SystemClockMillis();
77
78       m_database.Open();
79
80       if (m_pObserver)
81         m_pObserver->OnStateChanged(PREPARING);
82
83       m_bCanInterrupt = true;
84
85       CLog::Log(LOGNOTICE, "VideoInfoScanner: Starting scan ..");
86
87       // Reset progress vars
88       m_currentItem = 0;
89       m_itemCount = -1;
90
91       SetPriority(GetMinPriority());
92
93       // Database operations should not be canceled
94       // using Interupt() while scanning as it could
95       // result in unexpected behaviour.
96       m_bCanInterrupt = false;
97
98       bool bCancelled = false;
99       while (!bCancelled && m_pathsToScan.size())
100       {
101         /*
102          * A copy of the directory path is used because the path supplied is
103          * immediately removed from the m_pathsToScan set in DoScan(). If the
104          * reference points to the entry in the set a null reference error
105          * occurs.
106          */
107         CStdString directory = *m_pathsToScan.begin();
108         if (!DoScan(directory))
109           bCancelled = true;
110       }
111
112       if (!bCancelled)
113       {
114         if (m_bClean)
115           m_database.CleanDatabase(m_pObserver,&m_pathsToClean);
116         else
117         {
118           if (m_pObserver)
119             m_pObserver->OnStateChanged(COMPRESSING_DATABASE);
120           m_database.Compress(false);
121         }
122       }
123
124       m_database.Close();
125
126       tick = XbmcThreads::SystemClockMillis() - tick;
127       CLog::Log(LOGNOTICE, "VideoInfoScanner: Finished scan. Scanning for video info took %s", StringUtils::SecondsToTimeString(tick / 1000).c_str());
128       ANNOUNCEMENT::CAnnouncementManager::Announce(ANNOUNCEMENT::VideoLibrary, "xbmc", "OnScanFinished");
129       
130       m_bRunning = false;
131       if (m_pObserver)
132         m_pObserver->OnFinished();
133     }
134     catch (...)
135     {
136       CLog::Log(LOGERROR, "VideoInfoScanner: Exception while scanning.");
137     }
138   }
139
140   void CVideoInfoScanner::Start(const CStdString& strDirectory, bool scanAll)
141   {
142     m_strStartDir = strDirectory;
143     m_scanAll = scanAll;
144     m_pathsToScan.clear();
145     m_pathsToClean.clear();
146
147     if (strDirectory.IsEmpty())
148     { // scan all paths in the database.  We do this by scanning all paths in the db, and crossing them off the list as
149       // we go.
150       m_database.Open();
151       m_database.GetPaths(m_pathsToScan);
152       m_database.Close();
153     }
154     else
155     {
156       m_pathsToScan.insert(strDirectory);
157     }
158     m_bClean = g_advancedSettings.m_bVideoLibraryCleanOnUpdate;
159
160     StopThread();
161     Create();
162     m_bRunning = true;
163   }
164
165   bool CVideoInfoScanner::IsScanning()
166   {
167     return m_bRunning;
168   }
169
170   void CVideoInfoScanner::Stop()
171   {
172     if (m_bCanInterrupt)
173       m_database.Interupt();
174
175     StopThread();
176   }
177
178   void CVideoInfoScanner::SetObserver(IVideoInfoScannerObserver* pObserver)
179   {
180     m_pObserver = pObserver;
181   }
182
183   bool CVideoInfoScanner::DoScan(const CStdString& strDirectory)
184   {
185     if (m_pObserver)
186     {
187       m_pObserver->OnDirectoryChanged(strDirectory);
188       m_pObserver->OnSetTitle(g_localizeStrings.Get(20415));
189     }
190
191     /*
192      * Remove this path from the list we're processing. This must be done prior to
193      * the check for file or folder exclusion to prevent an infinite while loop
194      * in Process().
195      */
196     set<CStdString>::iterator it = m_pathsToScan.find(strDirectory);
197     if (it != m_pathsToScan.end())
198       m_pathsToScan.erase(it);
199
200     // load subfolder
201     CFileItemList items;
202     bool foundDirectly = false;
203     bool bSkip = false;
204
205     SScanSettings settings;
206     ScraperPtr info = m_database.GetScraperForPath(strDirectory, settings, foundDirectly);
207     CONTENT_TYPE content = info ? info->Content() : CONTENT_NONE;
208
209     // exclude folders that match our exclude regexps
210     CStdStringArray regexps = content == CONTENT_TVSHOWS ? g_advancedSettings.m_tvshowExcludeFromScanRegExps
211                                                          : g_advancedSettings.m_moviesExcludeFromScanRegExps;
212
213     if (CUtil::ExcludeFileOrFolder(strDirectory, regexps))
214       return true;
215
216     bool ignoreFolder = m_scanAll && settings.noupdate;
217     if (content == CONTENT_NONE || ignoreFolder)
218       return true;
219
220     CStdString hash, dbHash;
221     if (content == CONTENT_MOVIES ||content == CONTENT_MUSICVIDEOS)
222     {
223       if (m_pObserver)
224         m_pObserver->OnStateChanged(content == CONTENT_MOVIES ? FETCHING_MOVIE_INFO : FETCHING_MUSICVIDEO_INFO);
225
226       CStdString fastHash = GetFastHash(strDirectory);
227       if (m_database.GetPathHash(strDirectory, dbHash) && !fastHash.IsEmpty() && fastHash == dbHash)
228       { // fast hashes match - no need to process anything
229         CLog::Log(LOGDEBUG, "VideoInfoScanner: Skipping dir '%s' due to no change (fasthash)", strDirectory.c_str());
230         hash = fastHash;
231         bSkip = true;
232       }
233       if (!bSkip)
234       { // need to fetch the folder
235         CDirectory::GetDirectory(strDirectory, items, g_settings.m_videoExtensions);
236         items.Stack();
237         // compute hash
238         GetPathHash(items, hash);
239         if (hash != dbHash && !hash.IsEmpty())
240         {
241           if (dbHash.IsEmpty())
242             CLog::Log(LOGDEBUG, "VideoInfoScanner: Scanning dir '%s' as not in the database", strDirectory.c_str());
243           else
244             CLog::Log(LOGDEBUG, "VideoInfoScanner: Rescanning dir '%s' due to change (%s != %s)", strDirectory.c_str(), dbHash.c_str(), hash.c_str());
245         }
246         else
247         { // they're the same or the hash is empty (dir empty/dir not retrievable)
248           if (hash.IsEmpty() && !dbHash.IsEmpty())
249           {
250             CLog::Log(LOGDEBUG, "VideoInfoScanner: Skipping dir '%s' as it's empty or doesn't exist - adding to clean list", strDirectory.c_str());
251             m_pathsToClean.insert(m_database.GetPathId(strDirectory));
252           }
253           else
254             CLog::Log(LOGDEBUG, "VideoInfoScanner: Skipping dir '%s' due to no change", strDirectory.c_str());
255           bSkip = true;
256           if (m_pObserver)
257             m_pObserver->OnDirectoryScanned(strDirectory);
258         }
259         // update the hash to a fast hash if needed
260         if (CanFastHash(items) && !fastHash.IsEmpty())
261           hash = fastHash;
262       }
263     }
264     else if (content == CONTENT_TVSHOWS)
265     {
266       if (m_pObserver)
267         m_pObserver->OnStateChanged(FETCHING_TVSHOW_INFO);
268
269       if (foundDirectly && !settings.parent_name_root)
270       {
271         CDirectory::GetDirectory(strDirectory, items, g_settings.m_videoExtensions);
272         items.SetPath(strDirectory);
273         GetPathHash(items, hash);
274         bSkip = true;
275         if (!m_database.GetPathHash(strDirectory, dbHash) || dbHash != hash)
276         {
277           m_database.SetPathHash(strDirectory, hash);
278           bSkip = false;
279         }
280         else
281           items.Clear();
282       }
283       else
284       {
285         CFileItemPtr item(new CFileItem(URIUtils::GetFileName(strDirectory)));
286         item->SetPath(strDirectory);
287         item->m_bIsFolder = true;
288         items.Add(item);
289         items.SetPath(URIUtils::GetParentPath(item->GetPath()));
290       }
291     }
292
293     if (!bSkip)
294     {
295       if (RetrieveVideoInfo(items, settings.parent_name_root, content))
296       {
297         if (!m_bStop && (content == CONTENT_MOVIES || content == CONTENT_MUSICVIDEOS))
298         {
299           m_database.SetPathHash(strDirectory, hash);
300           m_pathsToClean.insert(m_database.GetPathId(strDirectory));
301           CLog::Log(LOGDEBUG, "VideoInfoScanner: Finished adding information from dir %s", strDirectory.c_str());
302         }
303       }
304       else
305       {
306         m_pathsToClean.insert(m_database.GetPathId(strDirectory));
307         CLog::Log(LOGDEBUG, "VideoInfoScanner: No (new) information was found in dir %s", strDirectory.c_str());
308       }
309     }
310     else if (hash != dbHash && (content == CONTENT_MOVIES || content == CONTENT_MUSICVIDEOS))
311     { // update the hash either way - we may have changed the hash to a fast version
312       m_database.SetPathHash(strDirectory, hash);
313     }
314
315     if (m_pObserver)
316       m_pObserver->OnDirectoryScanned(strDirectory);
317
318     for (int i = 0; i < items.Size(); ++i)
319     {
320       CFileItemPtr pItem = items[i];
321
322       if (m_bStop)
323         break;
324
325       // if we have a directory item (non-playlist) we then recurse into that folder
326       // do not recurse for tv shows - we have already looked recursively for episodes
327       if (pItem->m_bIsFolder && !pItem->IsParentFolder() && !pItem->IsPlayList() && settings.recurse > 0 && content != CONTENT_TVSHOWS)
328       {
329         if (!DoScan(pItem->GetPath()))
330         {
331           m_bStop = true;
332         }
333       }
334     }
335     return !m_bStop;
336   }
337
338   bool CVideoInfoScanner::RetrieveVideoInfo(CFileItemList& items, bool bDirNames, CONTENT_TYPE content, bool useLocal, CScraperUrl* pURL, bool fetchEpisodes, CGUIDialogProgress* pDlgProgress)
339   {
340     if (pDlgProgress)
341     {
342       if (items.Size() > 1 || (items[0]->m_bIsFolder && fetchEpisodes))
343       {
344         pDlgProgress->ShowProgressBar(true);
345         pDlgProgress->SetPercentage(0);
346       }
347       else
348         pDlgProgress->ShowProgressBar(false);
349
350       pDlgProgress->Progress();
351     }
352
353     m_database.Open();
354
355     bool FoundSomeInfo = false;
356     vector<int> seenPaths;
357     for (int i = 0; i < (int)items.Size(); ++i)
358     {
359       m_nfoReader.Close();
360       CFileItemPtr pItem = items[i];
361
362       // we do this since we may have a override per dir
363       ScraperPtr info2 = m_database.GetScraperForPath(pItem->m_bIsFolder ? pItem->GetPath() : items.GetPath());
364       if (!info2) // skip
365         continue;
366
367       // Discard all exclude files defined by regExExclude
368       if (CUtil::ExcludeFileOrFolder(pItem->GetPath(), (content == CONTENT_TVSHOWS) ? g_advancedSettings.m_tvshowExcludeFromScanRegExps
369                                                                                     : g_advancedSettings.m_moviesExcludeFromScanRegExps))
370         continue;
371
372       if (info2->Content() == CONTENT_MOVIES || info2->Content() == CONTENT_MUSICVIDEOS)
373       {
374         if (m_pObserver)
375         {
376           m_pObserver->OnSetCurrentProgress(i, items.Size());
377           if (!pItem->m_bIsFolder && m_itemCount)
378             m_pObserver->OnSetProgress(m_currentItem++, m_itemCount);
379         }
380
381       }
382
383       // clear our scraper cache
384       info2->ClearCache();
385
386       INFO_RET ret = INFO_CANCELLED;
387       if (info2->Content() == CONTENT_TVSHOWS)
388         ret = RetrieveInfoForTvShow(pItem, bDirNames, info2, useLocal, pURL, fetchEpisodes, pDlgProgress);
389       else if (info2->Content() == CONTENT_MOVIES)
390         ret = RetrieveInfoForMovie(pItem, bDirNames, info2, useLocal, pURL, pDlgProgress);
391       else if (info2->Content() == CONTENT_MUSICVIDEOS)
392         ret = RetrieveInfoForMusicVideo(pItem, bDirNames, info2, useLocal, pURL, pDlgProgress);
393       else
394       {
395         CLog::Log(LOGERROR, "VideoInfoScanner: Unknown content type %d (%s)", info2->Content(), pItem->GetPath().c_str());
396         FoundSomeInfo = false;
397         break;
398       }
399       if (ret == INFO_CANCELLED || ret == INFO_ERROR)
400       {
401         FoundSomeInfo = false;
402         break;
403       }
404       if (ret == INFO_ADDED || ret == INFO_HAVE_ALREADY)
405         FoundSomeInfo = true;
406
407       pURL = NULL;
408
409       // Keep track of directories we've seen
410       if (pItem->m_bIsFolder)
411         seenPaths.push_back(m_database.GetPathId(pItem->GetPath()));
412     }
413
414     if (content == CONTENT_TVSHOWS && ! seenPaths.empty())
415     {
416       vector< pair<int,string> > libPaths;
417       m_database.GetSubPaths(items.GetPath(), libPaths);
418       for (vector< pair<int,string> >::iterator i = libPaths.begin(); i < libPaths.end(); ++i)
419       {
420         if (find(seenPaths.begin(), seenPaths.end(), i->first) == seenPaths.end())
421           m_pathsToClean.insert(i->first);
422       }
423     }
424     if(pDlgProgress)
425       pDlgProgress->ShowProgressBar(false);
426
427     g_infoManager.ResetLibraryBools();
428     m_database.Close();
429     return FoundSomeInfo;
430   }
431
432   INFO_RET CVideoInfoScanner::RetrieveInfoForTvShow(CFileItemPtr pItem, bool bDirNames, ScraperPtr &info2, bool useLocal, CScraperUrl* pURL, bool fetchEpisodes, CGUIDialogProgress* pDlgProgress)
433   {
434     long idTvShow = -1;
435     if (pItem->m_bIsFolder)
436       idTvShow = m_database.GetTvShowId(pItem->GetPath());
437     else
438     {
439       CStdString strPath;
440       URIUtils::GetDirectory(pItem->GetPath(),strPath);
441       idTvShow = m_database.GetTvShowId(strPath);
442     }
443     if (idTvShow > -1 && (fetchEpisodes || !pItem->m_bIsFolder))
444     {
445       INFO_RET ret = RetrieveInfoForEpisodes(pItem, idTvShow, info2, useLocal, pDlgProgress);
446       if (ret == INFO_ADDED)
447         m_database.SetPathHash(pItem->GetPath(), pItem->GetProperty("hash").asString());
448       return ret;
449     }
450
451     if (ProgressCancelled(pDlgProgress, pItem->m_bIsFolder ? 20353 : 20361, pItem->GetLabel()))
452       return INFO_CANCELLED;
453
454     CNfoFile::NFOResult result=CNfoFile::NO_NFO;
455     CScraperUrl scrUrl;
456     // handle .nfo files
457     if (useLocal)
458       result = CheckForNFOFile(pItem.get(), bDirNames, info2, scrUrl);
459     if (result != CNfoFile::NO_NFO && result != CNfoFile::ERROR_NFO)
460     { // check for preconfigured scraper; if found, overwrite with interpreted scraper (from Nfofile)
461       // but keep current scan settings
462       SScanSettings settings;
463       if (m_database.GetScraperForPath(pItem->GetPath(), settings))
464         m_database.SetScraperForPath(pItem->GetPath(), info2, settings);
465     }
466     if (result == CNfoFile::FULL_NFO)
467     {
468       pItem->GetVideoInfoTag()->Reset();
469       m_nfoReader.GetDetails(*pItem->GetVideoInfoTag());
470
471       long lResult = AddVideo(pItem.get(), info2->Content(), bDirNames);
472       if (lResult < 0)
473         return INFO_ERROR;
474       GetArtwork(pItem.get(), info2->Content(), bDirNames, useLocal, pDlgProgress);
475       if (!fetchEpisodes && g_guiSettings.GetBool("videolibrary.seasonthumbs"))
476         FetchSeasonThumbs(lResult);
477       if (fetchEpisodes)
478       {
479         INFO_RET ret = RetrieveInfoForEpisodes(pItem, lResult, info2, useLocal, pDlgProgress);
480         if (ret == INFO_ADDED)
481           m_database.SetPathHash(pItem->GetPath(), pItem->GetProperty("hash").asString());
482         return ret;
483       }
484       return INFO_ADDED;
485     }
486     if (result == CNfoFile::URL_NFO || result == CNfoFile::COMBINED_NFO)
487       pURL = &scrUrl;
488
489     CScraperUrl url;
490     int retVal = 0;
491     if (pURL)
492       url = *pURL;
493     else if ((retVal = FindVideo(pItem->GetMovieName(bDirNames), info2, url, pDlgProgress)) <= 0)
494       return retVal < 0 ? INFO_CANCELLED : INFO_NOT_FOUND;
495
496     long lResult=-1;
497     if (GetDetails(pItem.get(), url, info2, result == CNfoFile::COMBINED_NFO ? &m_nfoReader : NULL, pDlgProgress))
498     {
499       if ((lResult = AddVideo(pItem.get(), info2->Content(), bDirNames)) < 0)
500         return INFO_ERROR;
501       GetArtwork(pItem.get(), info2->Content(), false, useLocal);
502     }
503     if (fetchEpisodes)
504     {
505       INFO_RET ret = RetrieveInfoForEpisodes(pItem, lResult, info2, useLocal, pDlgProgress);
506       if (ret == INFO_ADDED)
507         m_database.SetPathHash(pItem->GetPath(), pItem->GetProperty("hash").asString());
508     }
509     else
510       if (g_guiSettings.GetBool("videolibrary.seasonthumbs"))
511         FetchSeasonThumbs(lResult);
512     return INFO_ADDED;
513   }
514
515   INFO_RET CVideoInfoScanner::RetrieveInfoForMovie(CFileItemPtr pItem, bool bDirNames, ScraperPtr &info2, bool useLocal, CScraperUrl* pURL, CGUIDialogProgress* pDlgProgress)
516   {
517     if (pItem->m_bIsFolder || !pItem->IsVideo() || pItem->IsNFO() ||
518        (pItem->IsPlayList() && !URIUtils::GetExtension(pItem->GetPath()).Equals(".strm")))
519       return INFO_NOT_NEEDED;
520
521     if (ProgressCancelled(pDlgProgress, 198, pItem->GetLabel()))
522       return INFO_CANCELLED;
523
524     if (m_database.HasMovieInfo(pItem->GetPath()))
525       return INFO_HAVE_ALREADY;
526
527     CNfoFile::NFOResult result=CNfoFile::NO_NFO;
528     CScraperUrl scrUrl;
529     // handle .nfo files
530     if (useLocal)
531       result = CheckForNFOFile(pItem.get(), bDirNames, info2, scrUrl);
532     if (result == CNfoFile::FULL_NFO)
533     {
534       pItem->GetVideoInfoTag()->Reset();
535       m_nfoReader.GetDetails(*pItem->GetVideoInfoTag());
536
537       if (AddVideo(pItem.get(), info2->Content(), bDirNames) < 0)
538         return INFO_ERROR;
539       GetArtwork(pItem.get(), info2->Content(), bDirNames, true, pDlgProgress);
540       return INFO_ADDED;
541     }
542     if (result == CNfoFile::URL_NFO || result == CNfoFile::COMBINED_NFO)
543       pURL = &scrUrl;
544
545     CScraperUrl url;
546     int retVal = 0;
547     if (pURL)
548       url = *pURL;
549     else if ((retVal = FindVideo(pItem->GetMovieName(bDirNames), info2, url, pDlgProgress)) <= 0)
550       return retVal < 0 ? INFO_CANCELLED : INFO_NOT_FOUND;
551
552     if (GetDetails(pItem.get(), url, info2, result == CNfoFile::COMBINED_NFO ? &m_nfoReader : NULL, pDlgProgress))
553     {
554       if (AddVideo(pItem.get(), info2->Content(), bDirNames) < 0)
555         return INFO_ERROR;
556       GetArtwork(pItem.get(), info2->Content(), bDirNames, useLocal);
557       return INFO_ADDED;
558     }
559     // TODO: This is not strictly correct as we could fail to download information here or error, or be cancelled
560     return INFO_NOT_FOUND;
561   }
562
563   INFO_RET CVideoInfoScanner::RetrieveInfoForMusicVideo(CFileItemPtr pItem, bool bDirNames, ScraperPtr &info2, bool useLocal, CScraperUrl* pURL, CGUIDialogProgress* pDlgProgress)
564   {
565     if (pItem->m_bIsFolder || !pItem->IsVideo() || pItem->IsNFO() ||
566        (pItem->IsPlayList() && !URIUtils::GetExtension(pItem->GetPath()).Equals(".strm")))
567       return INFO_NOT_NEEDED;
568
569     if (ProgressCancelled(pDlgProgress, 20394, pItem->GetLabel()))
570       return INFO_CANCELLED;
571
572     if (m_database.HasMusicVideoInfo(pItem->GetPath()))
573       return INFO_HAVE_ALREADY;
574
575     CNfoFile::NFOResult result=CNfoFile::NO_NFO;
576     CScraperUrl scrUrl;
577     // handle .nfo files
578     if (useLocal)
579       result = CheckForNFOFile(pItem.get(), bDirNames, info2, scrUrl);
580     if (result == CNfoFile::FULL_NFO)
581     {
582       pItem->GetVideoInfoTag()->Reset();
583       m_nfoReader.GetDetails(*pItem->GetVideoInfoTag());
584
585       if (AddVideo(pItem.get(), info2->Content(), bDirNames) < 0)
586         return INFO_ERROR;
587       GetArtwork(pItem.get(), info2->Content(), bDirNames, true, pDlgProgress);
588       return INFO_ADDED;
589     }
590     if (result == CNfoFile::URL_NFO || result == CNfoFile::COMBINED_NFO)
591       pURL = &scrUrl;
592
593     CScraperUrl url;
594     int retVal = 0;
595     if (pURL)
596       url = *pURL;
597     else if ((retVal = FindVideo(pItem->GetMovieName(bDirNames), info2, url, pDlgProgress)) <= 0)
598       return retVal < 0 ? INFO_CANCELLED : INFO_NOT_FOUND;
599
600     if (GetDetails(pItem.get(), url, info2, result == CNfoFile::COMBINED_NFO ? &m_nfoReader : NULL, pDlgProgress))
601     {
602       if (AddVideo(pItem.get(), info2->Content(), bDirNames) < 0)
603         return INFO_ERROR;
604       GetArtwork(pItem.get(), info2->Content(), bDirNames, useLocal);
605       return INFO_ADDED;
606     }
607     // TODO: This is not strictly correct as we could fail to download information here or error, or be cancelled
608     return INFO_NOT_FOUND;
609   }
610
611   INFO_RET CVideoInfoScanner::RetrieveInfoForEpisodes(CFileItemPtr item, long showID, const ADDON::ScraperPtr &scraper, bool useLocal, CGUIDialogProgress *progress)
612   {
613     // enumerate episodes
614     EPISODES files;
615     EnumerateSeriesFolder(item.get(), files);
616     if (files.size() == 0) // no update or no files
617       return INFO_NOT_NEEDED;
618
619     if (m_bStop || (progress && progress->IsCanceled()))
620       return INFO_CANCELLED;
621
622     if (m_pObserver)
623       m_pObserver->OnDirectoryChanged(item->GetPath());
624
625     CStdString showTitle = m_database.GetTvShowTitleById(showID);
626     return OnProcessSeriesFolder(files, scraper, useLocal, showID, showTitle, progress);
627   }
628
629   void CVideoInfoScanner::EnumerateSeriesFolder(CFileItem* item, EPISODES& episodeList)
630   {
631     CFileItemList items;
632
633     if (item->m_bIsFolder)
634     {
635       CUtil::GetRecursiveListing(item->GetPath(), items, g_settings.m_videoExtensions, true);
636       CStdString hash, dbHash;
637       int numFilesInFolder = GetPathHash(items, hash);
638
639       if (m_database.GetPathHash(item->GetPath(), dbHash) && dbHash == hash)
640       {
641         m_currentItem += numFilesInFolder;
642
643         // notify our observer of our progress
644         if (m_pObserver)
645         {
646           if (m_itemCount>0)
647           {
648             m_pObserver->OnSetProgress(m_currentItem, m_itemCount);
649             m_pObserver->OnSetCurrentProgress(numFilesInFolder, numFilesInFolder);
650           }
651           m_pObserver->OnDirectoryScanned(item->GetPath());
652         }
653         return;
654       }
655       m_pathsToClean.insert(m_database.GetPathId(item->GetPath()));
656       m_database.GetPathsForTvShow(m_database.GetTvShowId(item->GetPath()), m_pathsToClean);
657       item->SetProperty("hash", hash);
658     }
659     else
660     {
661       CFileItemPtr newItem(new CFileItem(*item));
662       items.Add(newItem);
663     }
664
665     /*
666     stack down any dvd folders
667     need to sort using the full path since this is a collapsed recursive listing of all subdirs
668     video_ts.ifo files should sort at the top of a dvd folder in ascending order
669
670     /foo/bar/video_ts.ifo
671     /foo/bar/vts_x_y.ifo
672     /foo/bar/vts_x_y.vob
673     */
674
675     // since we're doing this now anyway, should other items be stacked?
676     items.Sort(SORT_METHOD_FULLPATH, SORT_ORDER_ASC);
677     int x = 0;
678     while (x < items.Size())
679     {
680       if (items[x]->m_bIsFolder)
681         continue;
682
683
684       CStdString strPathX, strFileX;
685       URIUtils::Split(items[x]->GetPath(), strPathX, strFileX);
686       //CLog::Log(LOGDEBUG,"%i:%s:%s", x, strPathX.c_str(), strFileX.c_str());
687
688       int y = x + 1;
689       if (strFileX.Equals("VIDEO_TS.IFO"))
690       {
691         while (y < items.Size())
692         {
693           CStdString strPathY, strFileY;
694           URIUtils::Split(items[y]->GetPath(), strPathY, strFileY);
695           //CLog::Log(LOGDEBUG," %i:%s:%s", y, strPathY.c_str(), strFileY.c_str());
696
697           if (strPathY.Equals(strPathX))
698             /*
699             remove everything sorted below the video_ts.ifo file in the same path.
700             understandbly this wont stack correctly if there are other files in the the dvd folder.
701             this should be unlikely and thus is being ignored for now but we can monitor the
702             where the path changes and potentially remove the items above the video_ts.ifo file.
703             */
704             items.Remove(y);
705           else
706             break;
707         }
708       }
709       x = y;
710     }
711
712     // enumerate
713     CStdStringArray regexps = g_advancedSettings.m_tvshowExcludeFromScanRegExps;
714
715     for (int i=0;i<items.Size();++i)
716     {
717       if (items[i]->m_bIsFolder)
718         continue;
719       CStdString strPath;
720       URIUtils::GetDirectory(items[i]->GetPath(), strPath);
721       URIUtils::RemoveSlashAtEnd(strPath); // want no slash for the test that follows
722
723       if (URIUtils::GetFileName(strPath).Equals("sample"))
724         continue;
725
726       // Discard all exclude files defined by regExExcludes
727       if (CUtil::ExcludeFileOrFolder(items[i]->GetPath(), regexps))
728         continue;
729
730       /*
731        * Check if the media source has already set the season and episode or original air date in
732        * the VideoInfoTag. If it has, do not try to parse any of them from the file path to avoid
733        * any false positive matches.
734        */
735       if (ProcessItemByVideoInfoTag(items[i], episodeList))
736         continue;
737
738       if (!EnumerateEpisodeItem(items[i], episodeList))
739       {
740         CStdString decode(items[i]->GetPath());
741         CURL::Decode(decode);
742         CLog::Log(LOGDEBUG, "VideoInfoScanner: Could not enumerate file %s", decode.c_str());
743       }
744     }
745   }
746
747   bool CVideoInfoScanner::ProcessItemByVideoInfoTag(const CFileItemPtr item, EPISODES &episodeList)
748   {
749     if (!item->HasVideoInfoTag())
750       return false;
751
752     CVideoInfoTag* tag = item->GetVideoInfoTag();
753     /*
754      * First check the season and episode number. This takes precedence over the original air
755      * date and episode title. Must be a valid season and episode number combination.
756      */
757     if (tag->m_iSeason > -1 && tag->m_iEpisode > 0)
758     {
759       SEpisode episode;
760       episode.strPath = item->GetPath();
761       episode.iSeason = tag->m_iSeason;
762       episode.iEpisode = tag->m_iEpisode;
763       episode.isFolder = false;
764       episodeList.push_back(episode);
765       CLog::Log(LOGDEBUG, "%s - found match for: %s. Season %d, Episode %d", __FUNCTION__,
766                 episode.strPath.c_str(), episode.iSeason, episode.iEpisode);
767       return true;
768     }
769
770     /*
771      * Next preference is the first aired date. If it exists use that for matching the TV Show
772      * information. Also set the title in case there are multiple matches for the first aired date.
773      */
774     if (tag->m_firstAired.IsValid())
775     {
776       SEpisode episode;
777       episode.strPath = item->GetPath();
778       episode.strTitle = tag->m_strTitle;
779       episode.isFolder = false;
780       /*
781        * Set season and episode to -1 to indicate to use the aired date.
782        */
783       episode.iSeason = -1;
784       episode.iEpisode = -1;
785       /*
786        * The first aired date string must be parseable.
787        */
788       episode.cDate = item->GetVideoInfoTag()->m_firstAired;
789       episodeList.push_back(episode);
790       CLog::Log(LOGDEBUG, "%s - found match for: '%s', firstAired: '%s' = '%s', title: '%s'",
791         __FUNCTION__, episode.strPath.c_str(), tag->m_firstAired.GetAsDBDateTime().c_str(),
792                 episode.cDate.GetAsLocalizedDate().c_str(), episode.strTitle.c_str());
793       return true;
794     }
795
796     /*
797      * Next preference is the episode title. If it exists use that for matching the TV Show
798      * information.
799      */
800     if (!tag->m_strTitle.IsEmpty())
801     {
802       SEpisode episode;
803       episode.strPath = item->GetPath();
804       episode.strTitle = tag->m_strTitle;
805       episode.isFolder = false;
806       /*
807        * Set season and episode to -1 to indicate to use the title.
808        */
809       episode.iSeason = -1;
810       episode.iEpisode = -1;
811       episodeList.push_back(episode);
812       CLog::Log(LOGDEBUG,"%s - found match for: '%s', title: '%s'", __FUNCTION__,
813                 episode.strPath.c_str(), episode.strTitle.c_str());
814       return true;
815     }
816
817     /*
818      * There is no further episode information available if both the season and episode number have
819      * been set to 0. Return the match as true so no further matching is attempted, but don't add it
820      * to the episode list.
821      */
822     if (tag->m_iSeason == 0 && tag->m_iEpisode == 0)
823     {
824       CLog::Log(LOGDEBUG,"%s - found exclusion match for: %s. Both Season and Episode are 0. Item will be ignored for scanning.",
825                 __FUNCTION__, item->GetPath().c_str());
826       return true;
827     }
828
829     return false;
830   }
831
832   bool CVideoInfoScanner::EnumerateEpisodeItem(const CFileItemPtr item, EPISODES& episodeList)
833   {
834     SETTINGS_TVSHOWLIST expression = g_advancedSettings.m_tvshowEnumRegExps;
835
836     CStdString strLabel=item->GetPath();
837     // URLDecode in case an episode is on a http/https/dav/davs:// source and URL-encoded like foo%201x01%20bar.avi
838     CURL::Decode(strLabel);
839     strLabel.MakeLower();
840
841     for (unsigned int i=0;i<expression.size();++i)
842     {
843       CRegExp reg;
844       if (!reg.RegComp(expression[i].regexp))
845         continue;
846
847       int regexppos, regexp2pos;
848       //CLog::Log(LOGDEBUG,"running expression %s on %s",expression[i].regexp.c_str(),strLabel.c_str());
849       if ((regexppos = reg.RegFind(strLabel.c_str())) < 0)
850         continue;
851
852       SEpisode episode;
853       episode.strPath = item->GetPath();
854       episode.iSeason = -1;
855       episode.iEpisode = -1;
856       episode.cDate.SetValid(false);
857       episode.isFolder = false;
858
859       bool byDate = expression[i].byDate ? true : false;
860       int defaultSeason = expression[i].defaultSeason;
861
862       if (byDate)
863       {
864         if (!GetAirDateFromRegExp(reg, episode))
865           continue;
866
867         CLog::Log(LOGDEBUG, "VideoInfoScanner: Found date based match %s (%s) [%s]", strLabel.c_str(),
868                   episode.cDate.GetAsLocalizedDate().c_str(), expression[i].regexp.c_str());
869       }
870       else
871       {
872         if (!GetEpisodeAndSeasonFromRegExp(reg, episode, defaultSeason))
873           continue;
874
875         CLog::Log(LOGDEBUG, "VideoInfoScanner: Found episode match %s (s%ie%i) [%s]", strLabel.c_str(),
876                   episode.iSeason, episode.iEpisode, expression[i].regexp.c_str());
877       }
878
879       // Grab the remainder from first regexp run
880       // as second run might modify or empty it.
881       char *remainder = reg.GetReplaceString("\\3");
882
883       /*
884        * Check if the files base path is a dedicated folder that contains
885        * only this single episode. If season and episode match with the
886        * actual media file, we set episode.isFolder to true.
887        */
888       CStdString strBasePath = item->GetBaseMoviePath(true);
889       URIUtils::RemoveSlashAtEnd(strBasePath);
890       strBasePath = URIUtils::GetFileName(strBasePath);
891
892       if (reg.RegFind(strBasePath.c_str()) > -1)
893       {
894         SEpisode parent;
895         if (byDate)
896         {
897           GetAirDateFromRegExp(reg, parent);
898           if (episode.cDate == parent.cDate)
899             episode.isFolder = true;
900         }
901         else
902         {
903           GetEpisodeAndSeasonFromRegExp(reg, parent, defaultSeason);
904           if (episode.iSeason == parent.iSeason && episode.iEpisode == parent.iEpisode)
905             episode.isFolder = true;
906         }
907       }
908
909       // add what we found by now
910       episodeList.push_back(episode);
911
912       CRegExp reg2;
913       // check the remainder of the string for any further episodes.
914       if (!byDate && reg2.RegComp(g_advancedSettings.m_tvshowMultiPartEnumRegExp))
915       {
916         int offset = 0;
917
918         // we want "long circuit" OR below so that both offsets are evaluated
919         while (((regexp2pos = reg2.RegFind(remainder + offset)) > -1) | ((regexppos = reg.RegFind(remainder + offset)) > -1))
920         {
921           if (((regexppos <= regexp2pos) && regexppos != -1) ||
922              (regexppos >= 0 && regexp2pos == -1))
923           {
924             GetEpisodeAndSeasonFromRegExp(reg, episode, defaultSeason);
925
926             CLog::Log(LOGDEBUG, "VideoInfoScanner: Adding new season %u, multipart episode %u [%s]",
927                       episode.iSeason, episode.iEpisode,
928                       g_advancedSettings.m_tvshowMultiPartEnumRegExp.c_str());
929
930             episodeList.push_back(episode);
931             free(remainder);
932             remainder = reg.GetReplaceString("\\3");
933             offset = 0;
934           }
935           else if (((regexp2pos < regexppos) && regexp2pos != -1) ||
936                    (regexp2pos >= 0 && regexppos == -1))
937           {
938             char *ep = reg2.GetReplaceString("\\1");
939             episode.iEpisode = atoi(ep);
940             free(ep);
941             CLog::Log(LOGDEBUG, "VideoInfoScanner: Adding multipart episode %u [%s]",
942                       episode.iEpisode, g_advancedSettings.m_tvshowMultiPartEnumRegExp.c_str());
943             episodeList.push_back(episode);
944             offset += regexp2pos + reg2.GetFindLen();
945           }
946         }
947         free(remainder);
948       }
949       return true;
950     }
951     return false;
952   }
953
954   bool CVideoInfoScanner::GetEpisodeAndSeasonFromRegExp(CRegExp &reg, SEpisode &episodeInfo, int defaultSeason)
955   {
956     char* season = reg.GetReplaceString("\\1");
957     char* episode = reg.GetReplaceString("\\2");
958
959     if (season && episode)
960     {
961       if (strlen(season) == 0 && strlen(episode) > 0)
962       { // no season specified -> assume defaultSeason
963         episodeInfo.iSeason = defaultSeason;
964         if ((episodeInfo.iEpisode = CUtil::TranslateRomanNumeral(episode)) == -1)
965           episodeInfo.iEpisode = atoi(episode);
966       }
967       else if (strlen(season) > 0 && strlen(episode) == 0)
968       { // no episode specification -> assume defaultSeason
969         episodeInfo.iSeason = defaultSeason;
970         if ((episodeInfo.iEpisode = CUtil::TranslateRomanNumeral(season)) == -1)
971           episodeInfo.iEpisode = atoi(season);
972       }
973       else
974       { // season and episode specified
975         episodeInfo.iSeason = atoi(season);
976         episodeInfo.iEpisode = atoi(episode);
977       }
978     }
979     free(season);
980     free(episode);
981     return (season && episode);
982   }
983
984   bool CVideoInfoScanner::GetAirDateFromRegExp(CRegExp &reg, SEpisode &episodeInfo)
985   {
986     char* param1 = reg.GetReplaceString("\\1");
987     char* param2 = reg.GetReplaceString("\\2");
988     char* param3 = reg.GetReplaceString("\\3");
989
990     if (param1 && param2 && param3)
991     {
992       // regular expression by date
993       int len1 = strlen( param1 );
994       int len2 = strlen( param2 );
995       int len3 = strlen( param3 );
996
997       if (len1==4 && len2==2 && len3==2)
998       {
999         // yyyy mm dd format
1000         episodeInfo.cDate.SetDate(atoi(param1), atoi(param2), atoi(param3));
1001       }
1002       else if (len1==2 && len2==2 && len3==4)
1003       {
1004         // mm dd yyyy format
1005         episodeInfo.cDate.SetDate(atoi(param3), atoi(param1), atoi(param2));
1006       }
1007     }
1008     free(param1);
1009     free(param2);
1010     free(param3);
1011     return episodeInfo.cDate.IsValid();
1012   }
1013
1014   long CVideoInfoScanner::AddVideo(CFileItem *pItem, const CONTENT_TYPE &content, bool videoFolder, int idShow)
1015   {
1016     // ensure our database is open (this can get called via other classes)
1017     if (!m_database.Open())
1018       return -1;
1019
1020     CVideoInfoTag &movieDetails = *pItem->GetVideoInfoTag();
1021     if (movieDetails.m_basePath.IsEmpty())
1022       movieDetails.m_basePath = pItem->GetBaseMoviePath(videoFolder);
1023     movieDetails.m_parentPathID = m_database.AddPath(URIUtils::GetParentPath(movieDetails.m_basePath));
1024
1025     movieDetails.m_strFileNameAndPath = pItem->GetPath();
1026
1027     if (pItem->m_bIsFolder)
1028       movieDetails.m_strPath = pItem->GetPath();
1029
1030     CStdString strTitle(movieDetails.m_strTitle);
1031
1032     if (idShow > -1 && content == CONTENT_TVSHOWS)
1033     {
1034       CStdString strShowTitle = m_database.GetTvShowTitleById(idShow);
1035       strTitle.Format("%s - %ix%i - %s", strShowTitle.c_str(), movieDetails.m_iSeason, movieDetails.m_iEpisode, strTitle.c_str());
1036     }
1037
1038     if (m_pObserver)
1039       m_pObserver->OnSetTitle(strTitle);
1040
1041     CLog::Log(LOGDEBUG, "VideoInfoScanner: Adding new item to %s:%s", TranslateContent(content).c_str(), pItem->GetPath().c_str());
1042     long lResult = -1;
1043
1044     if (content == CONTENT_MOVIES)
1045     {
1046       // find local trailer first
1047       CStdString strTrailer = pItem->FindTrailer();
1048       if (!strTrailer.IsEmpty())
1049         movieDetails.m_strTrailer = strTrailer;
1050
1051       lResult = m_database.SetDetailsForMovie(pItem->GetPath(), movieDetails);
1052       movieDetails.m_iDbId = lResult;
1053
1054       // setup links to shows if the linked shows are in the db
1055       for (unsigned int i=0; i < movieDetails.m_showLink.size(); ++i)
1056       {
1057         CFileItemList items;
1058         m_database.GetTvShowsByName(movieDetails.m_showLink[i], items);
1059         if (items.Size())
1060           m_database.LinkMovieToTvshow(lResult, items[0]->GetVideoInfoTag()->m_iDbId, false);
1061         else
1062           CLog::Log(LOGDEBUG, "VideoInfoScanner: Failed to link movie %s to show %s", movieDetails.m_strTitle.c_str(), movieDetails.m_showLink[i].c_str());
1063       }
1064     }
1065     else if (content == CONTENT_TVSHOWS)
1066     {
1067       if (pItem->m_bIsFolder)
1068       {
1069         lResult = m_database.SetDetailsForTvShow(pItem->GetPath(), movieDetails);
1070         movieDetails.m_iDbId = lResult;
1071       }
1072       else
1073       {
1074         // we add episode then set details, as otherwise set details will delete the
1075         // episode then add, which breaks multi-episode files.
1076         int idEpisode = m_database.AddEpisode(idShow, pItem->GetPath());
1077         lResult = m_database.SetDetailsForEpisode(pItem->GetPath(), movieDetails, idShow, idEpisode);
1078         movieDetails.m_iDbId = lResult;
1079         if (movieDetails.m_fEpBookmark > 0)
1080         {
1081           movieDetails.m_strFileNameAndPath = pItem->GetPath();
1082           CBookmark bookmark;
1083           bookmark.timeInSeconds = movieDetails.m_fEpBookmark;
1084           bookmark.seasonNumber = movieDetails.m_iSeason;
1085           bookmark.episodeNumber = movieDetails.m_iEpisode;
1086           m_database.AddBookMarkForEpisode(movieDetails, bookmark);
1087         }
1088       }
1089     }
1090     else if (content == CONTENT_MUSICVIDEOS)
1091     {
1092       lResult = m_database.SetDetailsForMusicVideo(pItem->GetPath(), movieDetails);
1093       movieDetails.m_iDbId = lResult;
1094     }
1095
1096     if (g_advancedSettings.m_bVideoLibraryImportWatchedState)
1097       m_database.SetPlayCount(*pItem, movieDetails.m_playCount, movieDetails.m_lastPlayed);
1098
1099     m_database.Close();
1100     return lResult;
1101   }
1102
1103   void CVideoInfoScanner::GetArtwork(CFileItem *pItem, const CONTENT_TYPE &content, bool bApplyToDir, bool useLocal, CGUIDialogProgress* pDialog /* == NULL */)
1104   {
1105     CVideoInfoTag &movieDetails = *pItem->GetVideoInfoTag();
1106     // get & save fanart image
1107     bool isEpisode = (content == CONTENT_TVSHOWS && !pItem->m_bIsFolder);
1108     if (!isEpisode && (!useLocal || !pItem->CacheLocalFanart()))
1109     {
1110       if (movieDetails.m_fanart.GetNumFanarts())
1111         DownloadImage(movieDetails.m_fanart.GetImageURL(), pItem->GetCachedFanart(), false, pDialog);
1112     }
1113
1114     // get & save thumb image
1115     CStdString cachedThumb = pItem->GetCachedVideoThumb();
1116     if (isEpisode && CFile::Exists(cachedThumb))
1117     { // have an episode (??? and also a normal "cached" thumb that we're going to override now???)
1118       movieDetails.m_strFileNameAndPath = pItem->GetPath();
1119       CFileItem item(movieDetails);
1120       cachedThumb = item.GetCachedEpisodeThumb();
1121     }
1122
1123     CStdString localThumb;
1124     if (useLocal)
1125     {
1126       localThumb = pItem->GetUserVideoThumb();
1127       if (bApplyToDir && localThumb.IsEmpty())
1128       {
1129         CStdString strParent;
1130         URIUtils::GetParentPath(pItem->GetPath(), strParent);
1131         CFileItem item(*pItem);
1132         item.SetPath(strParent);
1133         item.m_bIsFolder = true;
1134         localThumb = item.GetUserVideoThumb();
1135       }
1136     }
1137
1138     // parent folder to apply the thumb to and to search for local actor thumbs
1139     CStdString parentDir = GetParentDir(*pItem);
1140
1141     if (!localThumb.IsEmpty())
1142       CPicture::CacheThumb(localThumb, cachedThumb);
1143     else
1144     { // see if we have an online image to use
1145       CStdString onlineThumb = CScraperUrl::GetThumbURL(movieDetails.m_strPictureURL.GetFirstThumb());
1146       if (!onlineThumb.IsEmpty())
1147       {
1148         if (onlineThumb.Find("http://") < 0 &&
1149             onlineThumb.Find("/") < 0 &&
1150             onlineThumb.Find("\\") < 0)
1151         {
1152           CStdString strPath;
1153           URIUtils::GetDirectory(pItem->GetPath(), strPath);
1154           onlineThumb = URIUtils::AddFileToFolder(strPath, onlineThumb);
1155         }
1156         DownloadImage(onlineThumb, cachedThumb, true, pDialog);
1157       }
1158     }
1159     if (g_guiSettings.GetBool("videolibrary.actorthumbs"))
1160       FetchActorThumbs(movieDetails.m_cast, parentDir);
1161     if (bApplyToDir)
1162       ApplyThumbToFolder(parentDir, cachedThumb);
1163
1164     CFileItemPtr itemCopy = CFileItemPtr(new CFileItem(*pItem));
1165     // Hack to make sure CVideoInfoTag::m_strShowTitle is set for tvshows
1166     // to make sure CAnnouncementManager provides the correct type for the item
1167     if (content == CONTENT_TVSHOWS && !isEpisode && itemCopy->HasVideoInfoTag())
1168       itemCopy->GetVideoInfoTag()->m_strShowTitle = itemCopy->GetVideoInfoTag()->m_strTitle;
1169     ANNOUNCEMENT::CAnnouncementManager::Announce(ANNOUNCEMENT::VideoLibrary, "xbmc", "OnUpdate", itemCopy);
1170   }
1171
1172   void CVideoInfoScanner::DownloadImage(const CStdString &url, const CStdString &destination, bool asThumb /*= true */, CGUIDialogProgress *progress /*= NULL */)
1173   {
1174     if (progress)
1175     {
1176       progress->SetLine(2, 415);
1177       progress->Progress();
1178     }
1179     bool result = false;
1180     if (asThumb)
1181       result = CPicture::CreateThumbnail(url, destination);
1182     else
1183       result = CPicture::CacheFanart(url, destination);
1184     if (!result)
1185     {
1186       CFile::Delete(destination);
1187       return;
1188     }
1189   }
1190
1191   INFO_RET CVideoInfoScanner::OnProcessSeriesFolder(EPISODES& files, const ADDON::ScraperPtr &scraper, bool useLocal, int idShow, const CStdString& strShowTitle, CGUIDialogProgress* pDlgProgress /* = NULL */)
1192   {
1193     if (pDlgProgress)
1194     {
1195       pDlgProgress->SetLine(1, strShowTitle);
1196       pDlgProgress->SetLine(2, 20361);
1197       pDlgProgress->SetPercentage(0);
1198       pDlgProgress->ShowProgressBar(true);
1199       pDlgProgress->Progress();
1200     }
1201
1202     EPISODELIST episodes;
1203     bool hasEpisodeGuide = false;
1204
1205     int iMax = files.size();
1206     int iCurr = 1;
1207     for (EPISODES::iterator file = files.begin(); file != files.end(); ++file)
1208     {
1209       m_nfoReader.Close();
1210       if (pDlgProgress)
1211       {
1212         pDlgProgress->SetLine(2, 20361);
1213         pDlgProgress->SetPercentage((int)((float)(iCurr++)/iMax*100));
1214         pDlgProgress->Progress();
1215       }
1216       if (m_pObserver)
1217       {
1218         if (m_itemCount > 0)
1219           m_pObserver->OnSetProgress(m_currentItem++, m_itemCount);
1220         m_pObserver->OnSetCurrentProgress(iCurr++, iMax);
1221       }
1222       if ((pDlgProgress && pDlgProgress->IsCanceled()) || m_bStop)
1223         return INFO_CANCELLED;
1224
1225       if (m_database.GetEpisodeId(file->strPath, file->iEpisode, file->iSeason) > -1)
1226       {
1227         if (m_pObserver)
1228           m_pObserver->OnSetTitle(g_localizeStrings.Get(20415));
1229         continue;
1230       }
1231
1232       CFileItem item;
1233       item.SetPath(file->strPath);
1234
1235       // handle .nfo files
1236       CNfoFile::NFOResult result=CNfoFile::NO_NFO;
1237       CScraperUrl scrUrl;
1238       ScraperPtr info(scraper);
1239       item.GetVideoInfoTag()->m_iEpisode = file->iEpisode;
1240       if (useLocal)
1241         result = CheckForNFOFile(&item, false, info,scrUrl);
1242       if (result == CNfoFile::FULL_NFO)
1243       {
1244         m_nfoReader.GetDetails(*item.GetVideoInfoTag());
1245         if (AddVideo(&item, CONTENT_TVSHOWS, file->isFolder, idShow) < 0)
1246           return INFO_ERROR;
1247         GetArtwork(&item, CONTENT_TVSHOWS);
1248         continue;
1249       }
1250
1251       if (!hasEpisodeGuide)
1252       {
1253         // fetch episode guide
1254         CVideoInfoTag details;
1255         m_database.GetTvShowInfo(item.GetPath(), details, idShow);
1256         if (!details.m_strEpisodeGuide.IsEmpty())
1257         {
1258           CScraperUrl url;
1259           url.ParseEpisodeGuide(details.m_strEpisodeGuide);
1260
1261           if (pDlgProgress)
1262           {
1263             pDlgProgress->SetLine(2, 20354);
1264             pDlgProgress->Progress();
1265           }
1266
1267           CVideoInfoDownloader imdb(scraper);
1268           if (!imdb.GetEpisodeList(url, episodes))
1269             return INFO_NOT_FOUND;
1270
1271           hasEpisodeGuide = true;
1272         }
1273       }
1274
1275       if (episodes.empty())
1276       {
1277         CLog::Log(LOGERROR, "VideoInfoScanner: Asked to lookup episode %s"
1278                             " online, but we have no episode guide. Check your tvshow.nfo and make"
1279                             " sure the <episodeguide> tag is in place.", file->strPath.c_str());
1280         continue;
1281       }
1282
1283       std::pair<int,int> key;
1284       key.first = file->iSeason;
1285       key.second = file->iEpisode;
1286       bool bFound = false;
1287       EPISODELIST::iterator guide = episodes.begin();;
1288       EPISODELIST matches;
1289
1290       for (; guide != episodes.end(); ++guide )
1291       {
1292         if ((file->iEpisode!=-1) && (file->iSeason!=-1) && (key==guide->key))
1293         {
1294           bFound = true;
1295           break;
1296         }
1297         if (file->cDate.IsValid() && guide->cDate.IsValid() && file->cDate==guide->cDate)
1298         {
1299           matches.push_back(*guide);
1300           continue;
1301         }
1302         if (!guide->cScraperUrl.strTitle.IsEmpty() && guide->cScraperUrl.strTitle.CompareNoCase(file->strTitle) == 0)
1303         {
1304           bFound = true;
1305           break;
1306         }
1307       }
1308
1309       if (!bFound)
1310       {
1311         /*
1312          * If there is only one match or there are matches but no title to compare with to help
1313          * identify the best match, then pick the first match as the best possible candidate.
1314          *
1315          * Otherwise, use the title to further refine the best match.
1316          */
1317         if (matches.size() == 1 || (file->strTitle.IsEmpty() && matches.size() > 1))
1318         {
1319           guide = matches.begin();
1320           bFound = true;
1321         }
1322         else if (!file->strTitle.IsEmpty())
1323         {
1324           double minscore = 0; // Default minimum score is 0 to find whatever is the best match.
1325
1326           EPISODELIST *candidates;
1327           if (matches.empty()) // No matches found using earlier criteria. Use fuzzy match on titles across all episodes.
1328           {
1329             minscore = 0.8; // 80% should ensure a good match.
1330             candidates = &episodes;
1331           }
1332           else // Multiple matches found. Use fuzzy match on the title with already matched episodes to pick the best.
1333             candidates = &matches;
1334
1335           CStdStringArray titles;
1336           for (guide = candidates->begin(); guide != candidates->end(); ++guide)
1337             titles.push_back(guide->cScraperUrl.strTitle.ToLower());
1338
1339           double matchscore;
1340           int index = StringUtils::FindBestMatch(file->strTitle.ToLower(), titles, matchscore);
1341           if (matchscore >= minscore)
1342           {
1343             guide = candidates->begin() + index;
1344             bFound = true;
1345             CLog::Log(LOGDEBUG,"%s fuzzy title match for show: '%s', title: '%s', match: '%s', score: %f >= %f",
1346                       __FUNCTION__, strShowTitle.c_str(), file->strTitle.c_str(), titles[index].c_str(), matchscore, minscore);
1347           }
1348         }
1349       }
1350
1351       if (bFound)
1352       {
1353         CVideoInfoDownloader imdb(scraper);
1354         CFileItem item;
1355         item.SetPath(file->strPath);
1356         if (!imdb.GetEpisodeDetails(guide->cScraperUrl, *item.GetVideoInfoTag(), pDlgProgress))
1357           return INFO_NOT_FOUND; // TODO: should we just skip to the next episode?
1358         item.GetVideoInfoTag()->m_iSeason = guide->key.first;
1359         item.GetVideoInfoTag()->m_iEpisode = guide->key.second;
1360         if (AddVideo(&item, CONTENT_TVSHOWS, file->isFolder, idShow) < 0)
1361           return INFO_ERROR;
1362         GetArtwork(&item, CONTENT_TVSHOWS);
1363       }
1364       else
1365       {
1366         CLog::Log(LOGDEBUG,"%s - no match for show: '%s', season: %d, episode: %d, airdate: '%s', title: '%s'",
1367                   __FUNCTION__, strShowTitle.c_str(), file->iSeason, file->iEpisode,
1368                   file->cDate.GetAsLocalizedDate().c_str(), file->strTitle.c_str());
1369       }
1370     }
1371     if (g_guiSettings.GetBool("videolibrary.seasonthumbs"))
1372       FetchSeasonThumbs(idShow);
1373     return INFO_ADDED;
1374   }
1375
1376   CStdString CVideoInfoScanner::GetnfoFile(CFileItem *item, bool bGrabAny) const
1377   {
1378     CStdString nfoFile;
1379     // Find a matching .nfo file
1380     if (!item->m_bIsFolder)
1381     {
1382       // file
1383       CStdString strExtension;
1384       URIUtils::GetExtension(item->GetPath(), strExtension);
1385
1386       if (URIUtils::IsInRAR(item->GetPath())) // we have a rarred item - we want to check outside the rars
1387       {
1388         CFileItem item2(*item);
1389         CURL url(item->GetPath());
1390         CStdString strPath;
1391         URIUtils::GetDirectory(url.GetHostName(), strPath);
1392         item2.SetPath(URIUtils::AddFileToFolder(strPath, URIUtils::GetFileName(item->GetPath())));
1393         return GetnfoFile(&item2, bGrabAny);
1394       }
1395
1396       // grab the folder path
1397       CStdString strPath;
1398       URIUtils::GetDirectory(item->GetPath(), strPath);
1399
1400       if (bGrabAny && !item->IsStack())
1401       { // looking up by folder name - movie.nfo takes priority - but not for stacked items (handled below)
1402         nfoFile = URIUtils::AddFileToFolder(strPath, "movie.nfo");
1403         if (CFile::Exists(nfoFile))
1404           return nfoFile;
1405       }
1406
1407       // try looking for .nfo file for a stacked item
1408       if (item->IsStack())
1409       {
1410         // first try .nfo file matching first file in stack
1411         CStackDirectory dir;
1412         CStdString firstFile = dir.GetFirstStackedFile(item->GetPath());
1413         CFileItem item2;
1414         item2.SetPath(firstFile);
1415         nfoFile = GetnfoFile(&item2, bGrabAny);
1416         // else try .nfo file matching stacked title
1417         if (nfoFile.IsEmpty())
1418         {
1419           CStdString stackedTitlePath = dir.GetStackedTitlePath(item->GetPath());
1420           item2.SetPath(stackedTitlePath);
1421           nfoFile = GetnfoFile(&item2, bGrabAny);
1422         }
1423       }
1424       else
1425       {
1426         // already an .nfo file?
1427         if ( strcmpi(strExtension.c_str(), ".nfo") == 0 )
1428           nfoFile = item->GetPath();
1429         // no, create .nfo file
1430         else
1431           nfoFile = URIUtils::ReplaceExtension(item->GetPath(), ".nfo");
1432       }
1433
1434       // test file existence
1435       if (!nfoFile.IsEmpty() && !CFile::Exists(nfoFile))
1436         nfoFile.Empty();
1437
1438       if (nfoFile.IsEmpty()) // final attempt - strip off any cd1 folders
1439       {
1440         URIUtils::RemoveSlashAtEnd(strPath); // need no slash for the check that follows
1441         CFileItem item2;
1442         if (strPath.Mid(strPath.size()-3).Equals("cd1"))
1443         {
1444           strPath = strPath.Mid(0,strPath.size()-3);
1445           item2.SetPath(URIUtils::AddFileToFolder(strPath, URIUtils::GetFileName(item->GetPath())));
1446           return GetnfoFile(&item2, bGrabAny);
1447         }
1448       }
1449
1450       if (nfoFile.IsEmpty() && item->IsOpticalMediaFile())
1451       {
1452         CFileItem parentDirectory(item->GetLocalMetadataPath(), true);
1453         nfoFile = GetnfoFile(&parentDirectory, true);
1454       }
1455     }
1456     // folders (or stacked dvds) can take any nfo file if there's a unique one
1457     if (item->m_bIsFolder || item->IsOpticalMediaFile() || (bGrabAny && nfoFile.IsEmpty()))
1458     {
1459       // see if there is a unique nfo file in this folder, and if so, use that
1460       CFileItemList items;
1461       CDirectory dir;
1462       CStdString strPath = item->GetPath();
1463       if (!item->m_bIsFolder)
1464         URIUtils::GetDirectory(item->GetPath(), strPath);
1465       if (dir.GetDirectory(strPath, items, ".nfo") && items.Size())
1466       {
1467         int numNFO = -1;
1468         for (int i = 0; i < items.Size(); i++)
1469         {
1470           if (items[i]->IsNFO())
1471           {
1472             if (numNFO == -1)
1473               numNFO = i;
1474             else
1475             {
1476               numNFO = -1;
1477               break;
1478             }
1479           }
1480         }
1481         if (numNFO > -1)
1482           return items[numNFO]->GetPath();
1483       }
1484     }
1485
1486     return nfoFile;
1487   }
1488
1489   bool CVideoInfoScanner::GetDetails(CFileItem *pItem, CScraperUrl &url, const ScraperPtr& scraper, CNfoFile *nfoFile, CGUIDialogProgress* pDialog /* = NULL */)
1490   {
1491     CVideoInfoTag movieDetails;
1492
1493     CVideoInfoDownloader imdb(scraper);
1494     bool ret = imdb.GetDetails(url, movieDetails, pDialog);
1495
1496     if (ret)
1497     {
1498       if (nfoFile)
1499         nfoFile->GetDetails(movieDetails,NULL,true);
1500
1501       if (m_pObserver && url.strTitle.IsEmpty())
1502         m_pObserver->OnSetTitle(movieDetails.m_strTitle);
1503
1504       if (pDialog)
1505       {
1506         pDialog->SetLine(1, movieDetails.m_strTitle);
1507         pDialog->Progress();
1508       }
1509
1510       *pItem->GetVideoInfoTag() = movieDetails;
1511       return true;
1512     }
1513     return false; // no info found, or cancelled
1514   }
1515
1516   void CVideoInfoScanner::ApplyThumbToFolder(const CStdString &folder, const CStdString &imdbThumb)
1517   {
1518     // copy icon to folder also;
1519     if (CFile::Exists(imdbThumb))
1520     {
1521       CFileItem folderItem(folder, true);
1522       CStdString strThumb(folderItem.GetCachedVideoThumb());
1523       CFile::Cache(imdbThumb.c_str(), strThumb.c_str(), NULL, NULL);
1524     }
1525   }
1526
1527   int CVideoInfoScanner::GetPathHash(const CFileItemList &items, CStdString &hash)
1528   {
1529     // Create a hash based on the filenames, filesize and filedate.  Also count the number of files
1530     if (0 == items.Size()) return 0;
1531     XBMC::XBMC_MD5 md5state;
1532     int count = 0;
1533     for (int i = 0; i < items.Size(); ++i)
1534     {
1535       const CFileItemPtr pItem = items[i];
1536       md5state.append(pItem->GetPath());
1537       md5state.append((unsigned char *)&pItem->m_dwSize, sizeof(pItem->m_dwSize));
1538       FILETIME time = pItem->m_dateTime;
1539       md5state.append((unsigned char *)&time, sizeof(FILETIME));
1540       if (pItem->IsVideo() && !pItem->IsPlayList() && !pItem->IsNFO())
1541         count++;
1542     }
1543     md5state.getDigest(hash);
1544     return count;
1545   }
1546
1547   bool CVideoInfoScanner::CanFastHash(const CFileItemList &items) const
1548   {
1549     // TODO: Probably should account for excluded folders here (eg samples), though that then
1550     //       introduces possible problems if the user then changes the exclude regexps and
1551     //       expects excluded folders that are inside a fast-hashed folder to then be picked
1552     //       up. The chances that the user has a folder which contains only excluded folders
1553     //       where some of those folders should be scanned recursively is pretty small.
1554     return items.GetFolderCount() == 0;
1555   }
1556
1557   CStdString CVideoInfoScanner::GetFastHash(const CStdString &directory) const
1558   {
1559     struct __stat64 buffer;
1560     if (XFILE::CFile::Stat(directory, &buffer) == 0)
1561     {
1562       int64_t time = buffer.st_mtime;
1563       if (!time)
1564         time = buffer.st_ctime;
1565       if (time)
1566       {
1567         CStdString hash;
1568         hash.Format("fast%"PRId64, time);
1569         return hash;
1570       }
1571     }
1572     return "";
1573   }
1574
1575   void CVideoInfoScanner::FetchSeasonThumbs(int idTvShow, const CStdString &folderToCheck, bool download, bool overwrite)
1576   {
1577     // ensure our database is open (this can get called via other classes)
1578     if (!m_database.Open())
1579       return;
1580
1581     CVideoInfoTag movie;
1582     m_database.GetTvShowInfo("", movie, idTvShow);
1583     CStdString showDir(folderToCheck.IsEmpty() ? movie.m_strPath : folderToCheck);
1584     CFileItemList items;
1585     CStdString strPath;
1586     strPath.Format("videodb://2/2/%i/", idTvShow);
1587     m_database.GetSeasonsNav(strPath, items, -1, -1, -1, -1, idTvShow);
1588     CFileItemPtr pItem;
1589     pItem.reset(new CFileItem(g_localizeStrings.Get(20366)));  // "All Seasons"
1590     CStdString path; path.Format("%s/-1/", strPath.c_str());
1591     pItem->SetPath(path);
1592     pItem->GetVideoInfoTag()->m_iSeason = -1;
1593     pItem->GetVideoInfoTag()->m_strPath = movie.m_strPath;
1594     if (overwrite || !XFILE::CFile::Exists(pItem->GetCachedSeasonThumb()))
1595       items.Add(pItem);
1596
1597     // used for checking for a season[ ._-](number).tbn
1598     CFileItemList tbnItems;
1599     CDirectory::GetDirectory(showDir, tbnItems, ".tbn");
1600     for (int i=0;i<items.Size();++i)
1601     {
1602       if (overwrite || !items[i]->HasThumbnail())
1603       {
1604         CStdString strExpression;
1605         int iSeason = items[i]->GetVideoInfoTag()->m_iSeason;
1606         if (iSeason == -1)
1607           strExpression = "season-all.tbn";
1608         else if (iSeason == 0)
1609           strExpression = "season-specials.tbn";
1610         else
1611           strExpression.Format("season[ ._-]?(0?%i)\\.tbn", items[i]->GetVideoInfoTag()->m_iSeason);
1612         bool bDownload = download;
1613         CRegExp reg;
1614         if (reg.RegComp(strExpression.c_str()))
1615         {
1616           for (int j=0;j<tbnItems.Size();++j)
1617           {
1618             CStdString strCheck = URIUtils::GetFileName(tbnItems[j]->GetPath());
1619             strCheck.ToLower();
1620             if (reg.RegFind(strCheck.c_str()) > -1)
1621             {
1622               CPicture::CreateThumbnail(tbnItems[j]->GetPath(), items[i]->GetCachedSeasonThumb());
1623               bDownload=false;
1624               break;
1625             }
1626           }
1627         }
1628         if (bDownload)
1629           DownloadImage(CScraperUrl::GetThumbURL(movie.m_strPictureURL.GetSeasonThumb(items[i]->GetVideoInfoTag()->m_iSeason)), items[i]->GetCachedSeasonThumb());
1630       }
1631     }
1632     m_database.Close();
1633   }
1634
1635   void CVideoInfoScanner::FetchActorThumbs(const vector<SActorInfo>& actors, const CStdString& strPath)
1636   {
1637     for (unsigned int i=0;i<actors.size();++i)
1638     {
1639       CFileItem item;
1640       item.SetLabel(actors[i].strName);
1641       CStdString strThumb = item.GetCachedActorThumb();
1642       if (!CFile::Exists(strThumb))
1643       {
1644         CStdString thumbFile = actors[i].strName;
1645         thumbFile.Replace(" ","_");
1646         thumbFile += ".tbn";
1647         CStdString strLocal = URIUtils::AddFileToFolder(URIUtils::AddFileToFolder(strPath, ".actors"), thumbFile);
1648         if (CFile::Exists(strLocal))
1649           CPicture::CreateThumbnail(strLocal, strThumb);
1650         else if (!actors[i].thumbUrl.GetFirstThumb().m_url.IsEmpty())
1651           DownloadImage(CScraperUrl::GetThumbURL(actors[i].thumbUrl.GetFirstThumb()), strThumb);
1652       }
1653     }
1654   }
1655
1656   CNfoFile::NFOResult CVideoInfoScanner::CheckForNFOFile(CFileItem* pItem, bool bGrabAny, ScraperPtr& info, CScraperUrl& scrUrl)
1657   {
1658     CStdString strNfoFile;
1659     if (info->Content() == CONTENT_MOVIES || info->Content() == CONTENT_MUSICVIDEOS
1660         || (info->Content() == CONTENT_TVSHOWS && !pItem->m_bIsFolder))
1661       strNfoFile = GetnfoFile(pItem, bGrabAny);
1662     if (info->Content() == CONTENT_TVSHOWS && pItem->m_bIsFolder)
1663       URIUtils::AddFileToFolder(pItem->GetPath(), "tvshow.nfo", strNfoFile);
1664
1665     CNfoFile::NFOResult result=CNfoFile::NO_NFO;
1666     if (!strNfoFile.IsEmpty() && CFile::Exists(strNfoFile))
1667     {
1668       result = m_nfoReader.Create(strNfoFile,info,pItem->GetVideoInfoTag()->m_iEpisode);
1669
1670       CStdString type;
1671       switch(result)
1672       {
1673         case CNfoFile::COMBINED_NFO:
1674           type = "Mixed";
1675           break;
1676         case CNfoFile::FULL_NFO:
1677           type = "Full";
1678           break;
1679         case CNfoFile::URL_NFO:
1680           type = "URL";
1681           break;
1682         case CNfoFile::NO_NFO:
1683           type = "";
1684           break;
1685         default:
1686           type = "malformed";
1687       }
1688       if (result != CNfoFile::NO_NFO)
1689         CLog::Log(LOGDEBUG, "VideoInfoScanner: Found matching %s NFO file: %s", type.c_str(), strNfoFile.c_str());
1690       if (result == CNfoFile::FULL_NFO)
1691       {
1692         if (info->Content() == CONTENT_TVSHOWS)
1693           info = m_nfoReader.GetScraperInfo();
1694       }
1695       else if (result != CNfoFile::NO_NFO && result != CNfoFile::ERROR_NFO)
1696       {
1697         scrUrl = m_nfoReader.ScraperUrl();
1698         info = m_nfoReader.GetScraperInfo();
1699
1700         CLog::Log(LOGDEBUG, "VideoInfoScanner: Fetching url '%s' using %s scraper (content: '%s')",
1701           scrUrl.m_url[0].m_url.c_str(), info->Name().c_str(), TranslateContent(info->Content()).c_str());
1702
1703         if (result == CNfoFile::COMBINED_NFO)
1704           m_nfoReader.GetDetails(*pItem->GetVideoInfoTag());
1705       }
1706     }
1707     else
1708       CLog::Log(LOGDEBUG, "VideoInfoScanner: No NFO file found. Using title search for '%s'", pItem->GetPath().c_str());
1709
1710     return result;
1711   }
1712
1713   bool CVideoInfoScanner::DownloadFailed(CGUIDialogProgress* pDialog)
1714   {
1715     if (g_advancedSettings.m_bVideoScannerIgnoreErrors)
1716       return true;
1717
1718     if (pDialog)
1719     {
1720       CGUIDialogOK::ShowAndGetInput(20448,20449,20022,20022);
1721       return false;
1722     }
1723     return CGUIDialogYesNo::ShowAndGetInput(20448,20449,20450,20022);
1724   }
1725
1726   bool CVideoInfoScanner::ProgressCancelled(CGUIDialogProgress* progress, int heading, const CStdString &line1)
1727   {
1728     if (progress)
1729     {
1730       progress->SetHeading(heading);
1731       progress->SetLine(0, line1);
1732       progress->SetLine(2, "");
1733       progress->Progress();
1734       return progress->IsCanceled();
1735     }
1736     return m_bStop;
1737   }
1738
1739   int CVideoInfoScanner::FindVideo(const CStdString &videoName, const ScraperPtr &scraper, CScraperUrl &url, CGUIDialogProgress *progress)
1740   {
1741     MOVIELIST movielist;
1742     CVideoInfoDownloader imdb(scraper);
1743     int returncode = imdb.FindMovie(videoName, movielist, progress);
1744     if (returncode < 0 || (returncode == 0 && !DownloadFailed(progress)))
1745     { // scraper reported an error, or we had an error and user wants to cancel the scan
1746       m_bStop = true;
1747       return -1; // cancelled
1748     }
1749     if (returncode > 0 && movielist.size())
1750     {
1751       url = movielist[0];
1752       return 1;  // found a movie
1753     }
1754     return 0;    // didn't find anything
1755   }
1756
1757   CStdString CVideoInfoScanner::GetParentDir(const CFileItem &item) const
1758   {
1759     CStdString strCheck = item.GetPath();
1760     if (item.IsStack())
1761       strCheck = CStackDirectory::GetFirstStackedFile(item.GetPath());
1762
1763     CStdString strDirectory;
1764     URIUtils::GetDirectory(strCheck, strDirectory);
1765     if (URIUtils::IsInRAR(strCheck))
1766     {
1767       CStdString strPath=strDirectory;
1768       URIUtils::GetParentPath(strPath, strDirectory);
1769     }
1770     if (item.IsStack())
1771     {
1772       strCheck = strDirectory;
1773       URIUtils::RemoveSlashAtEnd(strCheck);
1774       if (URIUtils::GetFileName(strCheck).size() == 3 && URIUtils::GetFileName(strCheck).Left(2).Equals("cd"))
1775         URIUtils::GetDirectory(strCheck, strDirectory);
1776     }
1777     return strDirectory;
1778   }
1779
1780 }