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