change compilations logic to detect album_artist set to 'Various' or 'Various Artists'
[vuplus_xbmc] / xbmc / music / infoscanner / MusicInfoScanner.cpp
1 /*
2  *      Copyright (C) 2005-2012 Team XBMC
3  *      http://www.xbmc.org
4  *
5  *  This Program is free software; you can redistribute it and/or modify
6  *  it under the terms of the GNU General Public License as published by
7  *  the Free Software Foundation; either version 2, or (at your option)
8  *  any later version.
9  *
10  *  This Program is distributed in the hope that it will be useful,
11  *  but WITHOUT ANY WARRANTY; without even the implied warranty of
12  *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13  *  GNU General Public License for more details.
14  *
15  *  You should have received a copy of the GNU General Public License
16  *  along with XBMC; see the file COPYING.  If not, see
17  *  <http://www.gnu.org/licenses/>.
18  *
19  */
20
21 #include "threads/SystemClock.h"
22 #include "MusicInfoScanner.h"
23 #include "music/tags/MusicInfoTagLoaderFactory.h"
24 #include "MusicAlbumInfo.h"
25 #include "MusicInfoScraper.h"
26 #include "filesystem/MusicDatabaseDirectory.h"
27 #include "filesystem/MusicDatabaseDirectory/DirectoryNode.h"
28 #include "Util.h"
29 #include "utils/md5.h"
30 #include "GUIInfoManager.h"
31 #include "utils/Variant.h"
32 #include "NfoFile.h"
33 #include "music/tags/MusicInfoTag.h"
34 #include "guilib/GUIWindowManager.h"
35 #include "dialogs/GUIDialogExtendedProgressBar.h"
36 #include "dialogs/GUIDialogProgress.h"
37 #include "dialogs/GUIDialogSelect.h"
38 #include "guilib/GUIKeyboardFactory.h"
39 #include "filesystem/File.h"
40 #include "filesystem/Directory.h"
41 #include "settings/AdvancedSettings.h"
42 #include "settings/GUISettings.h"
43 #include "settings/Settings.h"
44 #include "FileItem.h"
45 #include "guilib/LocalizeStrings.h"
46 #include "utils/StringUtils.h"
47 #include "utils/TimeUtils.h"
48 #include "utils/log.h"
49 #include "utils/URIUtils.h"
50 #include "TextureCache.h"
51 #include "music/MusicThumbLoader.h"
52 #include "interfaces/AnnouncementManager.h"
53 #include "GUIUserMessages.h"
54
55 #include <algorithm>
56
57 using namespace std;
58 using namespace MUSIC_INFO;
59 using namespace XFILE;
60 using namespace MUSIC_GRABBER;
61
62 CMusicInfoScanner::CMusicInfoScanner() : CThread("CMusicInfoScanner")
63 {
64   m_bRunning = false;
65   m_showDialog = false;
66   m_handle = NULL;
67   m_bCanInterrupt = false;
68   m_currentItem=0;
69   m_itemCount=0;
70   m_flags = 0;
71 }
72
73 CMusicInfoScanner::~CMusicInfoScanner()
74 {
75 }
76
77 void CMusicInfoScanner::Process()
78 {
79   ANNOUNCEMENT::CAnnouncementManager::Announce(ANNOUNCEMENT::AudioLibrary, "xbmc", "OnScanStarted");
80   try
81   {
82     unsigned int tick = XbmcThreads::SystemClockMillis();
83
84     m_musicDatabase.Open();
85
86     if (m_showDialog && !g_guiSettings.GetBool("musiclibrary.backgroundupdate"))
87     {
88       CGUIDialogExtendedProgressBar* dialog =
89         (CGUIDialogExtendedProgressBar*)g_windowManager.GetWindow(WINDOW_DIALOG_EXT_PROGRESS);
90       m_handle = dialog->GetHandle(g_localizeStrings.Get(314));
91     }
92
93     m_bCanInterrupt = true;
94
95     if (m_scanType == 0) // load info from files
96     {
97       CLog::Log(LOGDEBUG, "%s - Starting scan", __FUNCTION__);
98
99       if (m_handle)
100         m_handle->SetTitle(g_localizeStrings.Get(505));
101
102       // Reset progress vars
103       m_currentItem=0;
104       m_itemCount=-1;
105
106       // Create the thread to count all files to be scanned
107       SetPriority( GetMinPriority() );
108       CThread fileCountReader(this, "CMusicInfoScanner");
109       if (m_handle)
110         fileCountReader.Create();
111
112       // Database operations should not be canceled
113       // using Interupt() while scanning as it could
114       // result in unexpected behaviour.
115       m_bCanInterrupt = false;
116       m_needsCleanup = false;
117
118       bool commit = false;
119       bool cancelled = false;
120       while (!cancelled && m_pathsToScan.size())
121       {
122         /*
123          * A copy of the directory path is used because the path supplied is
124          * immediately removed from the m_pathsToScan set in DoScan(). If the
125          * reference points to the entry in the set a null reference error
126          * occurs.
127          */
128         CStdString directory = *m_pathsToScan.begin();
129         if (!DoScan(directory))
130           cancelled = true;
131         commit = !cancelled;
132       }
133
134       if (commit)
135       {
136         g_infoManager.ResetLibraryBools();
137
138         if (m_needsCleanup)
139         {
140           if (m_handle)
141           {
142             m_handle->SetTitle(g_localizeStrings.Get(700));
143             m_handle->SetText("");
144           }
145
146           m_musicDatabase.CleanupOrphanedItems();
147
148           if (m_handle)
149             m_handle->SetTitle(g_localizeStrings.Get(331));
150
151           m_musicDatabase.Compress(false);
152         }
153       }
154
155       fileCountReader.StopThread();
156
157       m_musicDatabase.EmptyCache();
158
159       m_musicDatabase.Close();
160       CLog::Log(LOGDEBUG, "%s - Finished scan", __FUNCTION__);
161
162       tick = XbmcThreads::SystemClockMillis() - tick;
163       CLog::Log(LOGNOTICE, "My Music: Scanning for music info using worker thread, operation took %s", StringUtils::SecondsToTimeString(tick / 1000).c_str());
164     }
165     bool bCanceled;
166     if (m_scanType == 1) // load album info
167     {
168       int iCurrentItem = 1;
169       for (set<CAlbum>::iterator it=m_albumsToScan.begin();it != m_albumsToScan.end();++it)
170       {
171         if (m_handle)
172         {
173           m_handle->SetText(StringUtils::Join(it->artist, g_advancedSettings.m_musicItemSeparator)+" - "+it->strAlbum);
174           m_handle->SetPercentage(iCurrentItem++/(float)m_albumsToScan.size());
175         }
176
177         CMusicAlbumInfo albumInfo;
178         DownloadAlbumInfo(it->genre[0],StringUtils::Join(it->artist, g_advancedSettings.m_musicItemSeparator),it->strAlbum, bCanceled, albumInfo); // genre field holds path - see fetchalbuminfo()
179
180         if (m_bStop || bCanceled)
181           break;
182       }
183     }
184     if (m_scanType == 2) // load artist info
185     {
186       int iCurrentItem=1;
187       for (set<CArtist>::iterator it=m_artistsToScan.begin();it != m_artistsToScan.end();++it)
188       {
189         if (m_handle)
190         {
191           m_handle->SetText(it->strArtist);
192           m_handle->SetPercentage(iCurrentItem++/(float)m_artistsToScan.size()*100);
193         }
194
195         DownloadArtistInfo(it->genre[0],it->strArtist,bCanceled); // genre field holds path - see fetchartistinfo()
196
197         if (m_bStop || bCanceled)
198           break;
199       }
200     }
201
202   }
203   catch (...)
204   {
205     CLog::Log(LOGERROR, "MusicInfoScanner: Exception while scanning.");
206   }
207
208   m_bRunning = false;
209   ANNOUNCEMENT::CAnnouncementManager::Announce(ANNOUNCEMENT::AudioLibrary, "xbmc", "OnScanFinished");
210   
211   // we need to clear the musicdb cache and update any active lists
212   CUtil::DeleteMusicDatabaseDirectoryCache();
213   CGUIMessage msg(GUI_MSG_SCAN_FINISHED, 0, 0, 0);
214   g_windowManager.SendThreadMessage(msg);
215   
216   if (m_handle)
217     m_handle->MarkFinished();
218   m_handle = NULL;
219 }
220
221 void CMusicInfoScanner::Start(const CStdString& strDirectory, int flags)
222 {
223   m_pathsToScan.clear();
224   m_albumsScanned.clear();
225   m_artistsScanned.clear();
226   m_flags = flags;
227
228   if (strDirectory.IsEmpty())
229   { // scan all paths in the database.  We do this by scanning all paths in the db, and crossing them off the list as
230     // we go.
231     m_musicDatabase.Open();
232     m_musicDatabase.GetPaths(m_pathsToScan);
233     m_musicDatabase.Close();
234   }
235   else
236     m_pathsToScan.insert(strDirectory);
237   m_pathsToCount = m_pathsToScan;
238   m_scanType = 0;
239   StopThread();
240   Create();
241   m_bRunning = true;
242 }
243
244 void CMusicInfoScanner::FetchAlbumInfo(const CStdString& strDirectory,
245                                        bool refresh)
246 {
247   m_albumsToScan.clear();
248   m_albumsScanned.clear();
249
250   CFileItemList items;
251   if (strDirectory.IsEmpty())
252   {
253     m_musicDatabase.Open();
254     m_musicDatabase.GetAlbumsNav("musicdb://3/", items);
255     m_musicDatabase.Close();
256   }
257   else
258   {
259     if (URIUtils::HasSlashAtEnd(strDirectory)) // directory
260       CDirectory::GetDirectory(strDirectory,items);
261     else
262     {
263       CFileItemPtr item(new CFileItem(strDirectory,false));
264       items.Add(item);
265     }
266   }
267
268   m_musicDatabase.Open();
269   for (int i=0;i<items.Size();++i)
270   {
271     if (CMusicDatabaseDirectory::IsAllItem(items[i]->GetPath()) || items[i]->IsParentFolder())
272       continue;
273
274     CAlbum album;
275     album.strAlbum = items[i]->GetMusicInfoTag()->GetAlbum();
276     album.artist = items[i]->GetMusicInfoTag()->GetArtist();
277     album.genre.push_back(items[i]->GetPath()); // a bit hacky use of field
278     m_albumsToScan.insert(album);
279     if (refresh)
280     {
281       int id = m_musicDatabase.GetAlbumByName(album.strAlbum, album.artist);
282       if (id > -1)
283         m_musicDatabase.DeleteAlbumInfo(id);
284     }
285   }
286   m_musicDatabase.Close();
287
288   m_scanType = 1;
289   StopThread();
290   Create();
291   m_bRunning = true;
292 }
293
294 void CMusicInfoScanner::FetchArtistInfo(const CStdString& strDirectory,
295                                         bool refresh)
296 {
297   m_artistsToScan.clear();
298   m_artistsScanned.clear();
299   CFileItemList items;
300
301   if (strDirectory.IsEmpty())
302   {
303     m_musicDatabase.Open();
304     m_musicDatabase.GetArtistsNav("musicdb://2/", items, false, -1);
305     m_musicDatabase.Close();
306   }
307   else
308   {
309     if (URIUtils::HasSlashAtEnd(strDirectory)) // directory
310       CDirectory::GetDirectory(strDirectory,items);
311     else
312     {
313       CFileItemPtr newItem(new CFileItem(strDirectory,false));
314       items.Add(newItem);
315     }
316   }
317
318   m_musicDatabase.Open();
319   for (int i=0;i<items.Size();++i)
320   {
321     if (CMusicDatabaseDirectory::IsAllItem(items[i]->GetPath()) || items[i]->IsParentFolder())
322       continue;
323
324     CArtist artist;
325     artist.strArtist = StringUtils::Join(items[i]->GetMusicInfoTag()->GetArtist(), g_advancedSettings.m_musicItemSeparator);
326     artist.genre.push_back(items[i]->GetPath()); // a bit hacky use of field
327     m_artistsToScan.insert(artist);
328     if (refresh)
329     {
330       int id = m_musicDatabase.GetArtistByName(artist.strArtist);
331       if (id > -1)
332         m_musicDatabase.DeleteArtistInfo(id);
333     }
334   }
335   m_musicDatabase.Close();
336
337   m_scanType = 2;
338   StopThread();
339   Create();
340   m_bRunning = true;
341 }
342
343 bool CMusicInfoScanner::IsScanning()
344 {
345   return m_bRunning;
346 }
347
348 void CMusicInfoScanner::Stop()
349 {
350   if (m_bCanInterrupt)
351     m_musicDatabase.Interupt();
352
353   StopThread(false);
354 }
355
356 static void OnDirectoryScanned(const CStdString& strDirectory)
357 {
358   CGUIMessage msg(GUI_MSG_DIRECTORY_SCANNED, 0, 0, 0);
359   msg.SetStringParam(strDirectory);
360   g_windowManager.SendThreadMessage(msg);
361 }
362
363 static CStdString Prettify(const CStdString& strDirectory)
364 {
365   CURL url(strDirectory);
366   CStdString strStrippedPath = url.GetWithoutUserDetails();
367   CURL::Decode(strStrippedPath);
368
369   return strStrippedPath;
370 }
371
372 bool CMusicInfoScanner::DoScan(const CStdString& strDirectory)
373 {
374   if (m_handle)
375     m_handle->SetText(Prettify(strDirectory));
376
377   /*
378    * remove this path from the list we're processing. This must be done prior to
379    * the check for file or folder exclusion to prevent an infinite while loop
380    * in Process().
381    */
382   set<CStdString>::iterator it = m_pathsToScan.find(strDirectory);
383   if (it != m_pathsToScan.end())
384     m_pathsToScan.erase(it);
385
386   // Discard all excluded files defined by m_musicExcludeRegExps
387
388   CStdStringArray regexps = g_advancedSettings.m_audioExcludeFromScanRegExps;
389
390   if (CUtil::ExcludeFileOrFolder(strDirectory, regexps))
391     return true;
392
393   // load subfolder
394   CFileItemList items;
395   CDirectory::GetDirectory(strDirectory, items, g_settings.m_musicExtensions + "|.jpg|.tbn|.lrc|.cdg");
396
397   // sort and get the path hash.  Note that we don't filter .cue sheet items here as we want
398   // to detect changes in the .cue sheet as well.  The .cue sheet items only need filtering
399   // if we have a changed hash.
400   items.Sort(SORT_METHOD_LABEL, SortOrderAscending);
401   CStdString hash;
402   GetPathHash(items, hash);
403
404   // check whether we need to rescan or not
405   CStdString dbHash;
406   if ((m_flags & SCAN_RESCAN) || !m_musicDatabase.GetPathHash(strDirectory, dbHash) || dbHash != hash)
407   { // path has changed - rescan
408     if (dbHash.IsEmpty())
409       CLog::Log(LOGDEBUG, "%s Scanning dir '%s' as not in the database", __FUNCTION__, strDirectory.c_str());
410     else
411       CLog::Log(LOGDEBUG, "%s Rescanning dir '%s' due to change", __FUNCTION__, strDirectory.c_str());
412
413     // filter items in the sub dir (for .cue sheet support)
414     items.FilterCueItems();
415     items.Sort(SORT_METHOD_LABEL, SortOrderAscending);
416
417     // and then scan in the new information
418     if (RetrieveMusicInfo(items, strDirectory) > 0)
419     {
420       if (m_handle)
421         OnDirectoryScanned(strDirectory);
422     }
423
424     // save information about this folder
425     m_musicDatabase.SetPathHash(strDirectory, hash);
426   }
427   else
428   { // path is the same - no need to rescan
429     CLog::Log(LOGDEBUG, "%s Skipping dir '%s' due to no change", __FUNCTION__, strDirectory.c_str());
430     m_currentItem += CountFiles(items, false);  // false for non-recursive
431
432     // updated the dialog with our progress
433     if (m_handle)
434     {
435       if (m_itemCount>0)
436         m_handle->SetPercentage(m_currentItem/(float)m_itemCount*100);
437       OnDirectoryScanned(strDirectory);
438     }
439   }
440
441   // now scan the subfolders
442   for (int i = 0; i < items.Size(); ++i)
443   {
444     CFileItemPtr pItem = items[i];
445
446     if (m_bStop)
447       break;
448     // if we have a directory item (non-playlist) we then recurse into that folder
449     if (pItem->m_bIsFolder && !pItem->IsParentFolder() && !pItem->IsPlayList())
450     {
451       CStdString strPath=pItem->GetPath();
452       if (!DoScan(strPath))
453       {
454         m_bStop = true;
455       }
456     }
457   }
458
459   return !m_bStop;
460 }
461
462 int CMusicInfoScanner::RetrieveMusicInfo(CFileItemList& items, const CStdString& strDirectory)
463 {
464   CSongMap songsMap;
465
466   // get all information for all files in current directory from database, and remove them
467   if (m_musicDatabase.RemoveSongsFromPath(strDirectory, songsMap))
468     m_needsCleanup = true;
469
470   VECSONGS songsToAdd;
471
472   CStdStringArray regexps = g_advancedSettings.m_audioExcludeFromScanRegExps;
473
474   // for every file found, but skip folder
475   for (int i = 0; i < items.Size(); ++i)
476   {
477     CFileItemPtr pItem = items[i];
478     CStdString strExtension;
479     URIUtils::GetExtension(pItem->GetPath(), strExtension);
480
481     if (m_bStop)
482       return 0;
483
484     // Discard all excluded files defined by m_musicExcludeRegExps
485     if (CUtil::ExcludeFileOrFolder(pItem->GetPath(), regexps))
486       continue;
487
488     // dont try reading id3tags for folders, playlists or shoutcast streams
489     if (!pItem->m_bIsFolder && !pItem->IsPlayList() && !pItem->IsPicture() && !pItem->IsLyrics() )
490     {
491       m_currentItem++;
492 //      CLog::Log(LOGDEBUG, "%s - Reading tag for: %s", __FUNCTION__, pItem->GetPath().c_str());
493
494       // grab info from the song
495       CSong *dbSong = songsMap.Find(pItem->GetPath());
496
497       CMusicInfoTag& tag = *pItem->GetMusicInfoTag();
498       if (!tag.Loaded() )
499       { // read the tag from a file
500         auto_ptr<IMusicInfoTagLoader> pLoader (CMusicInfoTagLoaderFactory::CreateLoader(pItem->GetPath()));
501         if (NULL != pLoader.get())
502           pLoader->Load(pItem->GetPath(), tag);
503       }
504
505       // if we have the itemcount, update our
506       // dialog with the progress we made
507       if (m_handle && m_itemCount>0)
508         m_handle->SetPercentage(m_currentItem/(float)m_itemCount*100);
509
510       if (tag.Loaded())
511       {
512         CSong song(tag);
513
514         // ensure our song has a valid filename or else it will assert in AddSong()
515         if (song.strFileName.IsEmpty())
516         {
517           // copy filename from path in case UPnP or other tag loaders didn't specify one (FIXME?)
518           song.strFileName = pItem->GetPath();
519
520           // if we still don't have a valid filename, skip the song
521           if (song.strFileName.IsEmpty())
522           {
523             // this shouldn't ideally happen!
524             CLog::Log(LOGERROR, "Skipping song since it doesn't seem to have a filename");
525             continue;
526           }
527         }
528
529         song.iStartOffset = pItem->m_lStartOffset;
530         song.iEndOffset = pItem->m_lEndOffset;
531         song.strThumb = pItem->GetUserMusicThumb(true);
532         if (dbSong)
533         { // keep the db-only fields intact on rescan...
534           song.iTimesPlayed = dbSong->iTimesPlayed;
535           song.lastPlayed = dbSong->lastPlayed;
536           song.iKaraokeNumber = dbSong->iKaraokeNumber;
537
538           if (song.rating == '0') song.rating = dbSong->rating;
539           if (song.strThumb.empty())
540             song.strThumb = dbSong->strThumb;
541         }
542         songsToAdd.push_back(song);
543 //        CLog::Log(LOGDEBUG, "%s - Tag loaded for: %s", __FUNCTION__, pItem->GetPath().c_str());
544       }
545       else
546         CLog::Log(LOGDEBUG, "%s - No tag found for: %s", __FUNCTION__, pItem->GetPath().c_str());
547     }
548   }
549
550   VECALBUMS albums;
551   CategoriseAlbums(songsToAdd, albums);
552   FindArtForAlbums(albums, items.GetPath());
553
554   // finally, add these to the database
555   m_musicDatabase.BeginTransaction();
556   int numAdded = 0;
557   set<int> albumsToScan;
558   set<int> artistsToScan;
559   for (VECALBUMS::iterator i = albums.begin(); i != albums.end(); ++i)
560   {
561     vector<int> songIDs;
562     int idAlbum = m_musicDatabase.AddAlbum(*i, songIDs);
563     numAdded += i->songs.size();
564     if (m_bStop)
565     {
566       m_musicDatabase.RollbackTransaction();
567       return numAdded;
568     }
569
570     // Build the artist & album sets
571     albumsToScan.insert(idAlbum);
572     for (vector<int>::iterator j = songIDs.begin(); j != songIDs.end(); ++j)
573     {
574       vector<int> songArtists;
575       m_musicDatabase.GetArtistsBySong(*j, false, songArtists);
576       artistsToScan.insert(songArtists.begin(), songArtists.end());
577     }
578     std::vector<int> albumArtists;
579     m_musicDatabase.GetArtistsByAlbum(idAlbum, false, albumArtists);
580     artistsToScan.insert(albumArtists.begin(), albumArtists.end());
581   }
582   m_musicDatabase.CommitTransaction();
583
584   // Download info & artwork
585   bool bCanceled;
586   for (set<int>::iterator it = artistsToScan.begin(); it != artistsToScan.end(); ++it)
587   {
588     bCanceled = false;
589     if (find(m_artistsScanned.begin(),m_artistsScanned.end(), *it) == m_artistsScanned.end())
590     {
591       CStdString strArtist = m_musicDatabase.GetArtistById(*it);
592       m_artistsScanned.push_back(*it);
593       if (!m_bStop && (m_flags & SCAN_ONLINE))
594       {
595         CStdString strPath;
596         strPath.Format("musicdb://2/%u/", *it);
597
598         if (!DownloadArtistInfo(strPath, strArtist, bCanceled)) // assume we want to retry
599           m_artistsScanned.pop_back();
600       }
601       else
602       {
603         map<string, string> artwork = GetArtistArtwork(*it);
604         m_musicDatabase.SetArtForItem(*it, "artist", artwork);
605       }
606     }
607   }
608
609   if (m_flags & SCAN_ONLINE)
610   {
611     for (set<int>::iterator it = albumsToScan.begin(); it != albumsToScan.end(); ++it)
612     {
613       if (m_bStop)
614         return songsToAdd.size();
615
616       CStdString strPath;
617       strPath.Format("musicdb://3/%u/",*it);
618
619       CAlbum album;
620       m_musicDatabase.GetAlbumInfo(*it, album, NULL);
621       bCanceled = false;
622       if (find(m_albumsScanned.begin(), m_albumsScanned.end(), *it) == m_albumsScanned.end())
623       {
624         CMusicAlbumInfo albumInfo;
625         if (DownloadAlbumInfo(strPath, StringUtils::Join(album.artist, g_advancedSettings.m_musicItemSeparator), album.strAlbum, bCanceled, albumInfo))
626           m_albumsScanned.push_back(*it);
627       }
628     }
629   }
630   if (m_handle)
631     m_handle->SetTitle(g_localizeStrings.Get(505));
632
633   return songsToAdd.size();
634 }
635
636 static bool SortSongsByTrack(CSong *song, CSong *song2)
637 {
638   return song->iTrack < song2->iTrack;
639 }
640
641 void CMusicInfoScanner::CategoriseAlbums(VECSONGS &songsToCheck, VECALBUMS &albums)
642 {
643   /* Step 1: categorise on the album name */
644   map<string, vector<CSong *> > albumNames;
645   for (VECSONGS::iterator i = songsToCheck.begin(); i != songsToCheck.end(); ++i)
646     albumNames[i->strAlbum].push_back(&(*i));
647
648   /*
649    Step 2: Split into unique albums based on album name and album artist
650    In the case where the album artist is unknown, we use the primary artist
651    (i.e. first artist from each song).
652    */
653   albums.clear();
654   for (map<string, vector<CSong *> >::iterator i = albumNames.begin(); i != albumNames.end(); ++i)
655   {
656     // sort the songs by tracknumber to identify duplicate track numbers
657     vector<CSong *> &songs = i->second;
658     sort(songs.begin(), songs.end(), SortSongsByTrack);
659
660     // map the songs to their primary artists
661     bool tracksOverlap = false;
662     bool hasAlbumArtist = false;
663
664     map<string, vector<CSong *> > artists;
665     for (vector<CSong *>::iterator j = songs.begin(); j != songs.end(); ++j)
666     {
667       CSong *song = *j;
668       // test for song overlap
669       if (j != songs.begin() && song->iTrack == (*(j-1))->iTrack)
670         tracksOverlap = true;
671
672       // get primary artist
673       string primary;
674       if (!song->albumArtist.empty())
675       {
676         primary = song->albumArtist[0];
677         hasAlbumArtist = true;
678       }
679       else if (!song->artist.empty())
680         primary = song->artist[0];
681
682       // add to the artist map
683       artists[primary].push_back(song);
684     }
685
686     /*
687      We have a compilation if
688      1. album name is non-empty AND
689      2. no tracks overlap AND
690      3a. a unique primary artist is specified as "various" or "various artists" OR
691      3b. we have at least two primary artists and no album artist specified.
692      */
693     bool compilation = !i->first.empty() && !tracksOverlap; // 1+2
694     if (artists.size() == 1)
695     {
696       string artist = artists.begin()->first; StringUtils::ToLower(artist);
697       if (!StringUtils::EqualsNoCase(artist, "various") &&
698           !StringUtils::EqualsNoCase(artist, "various artists")) // 3a
699         compilation = false;
700     }
701     else if (hasAlbumArtist) // 3b
702       compilation = false;
703
704     if (compilation)
705     {
706       CLog::Log(LOGDEBUG, "Album '%s' is a compilation as there's no overlapping tracks and %s", i->first.c_str(), hasAlbumArtist ? "the album artist is 'Various'" : "there is more than one unique artist");
707       artists.clear();
708       std::string various = g_localizeStrings.Get(340); // Various Artists
709       vector<string> va; va.push_back(various);
710       for (vector<CSong *>::iterator j = songs.begin(); j != songs.end(); ++j)
711         (*j)->albumArtist = va;
712       artists.insert(make_pair(various, songs));
713     }
714
715     /*
716      Step 3: Find the common albumartist for each song and assign
717      albumartist to those tracks that don't have it set.
718      */
719     for (map<string, vector<CSong *> >::iterator j = artists.begin(); j != artists.end(); ++j)
720     {
721       // find the common artist for these songs
722       vector<CSong *> &artistSongs = j->second;
723       vector<string> common = artistSongs.front()->albumArtist.empty() ? artistSongs.front()->artist : artistSongs.front()->albumArtist;
724       for (vector<CSong *>::iterator k = artistSongs.begin() + 1; k != artistSongs.end(); ++k)
725       {
726         unsigned int match = 0;
727         vector<string> &compare = (*k)->albumArtist.empty() ? (*k)->artist : (*k)->albumArtist;
728         for (; match < common.size() && match < compare.size(); match++)
729         {
730           if (compare[match] != common[match])
731             break;
732         }
733         common.erase(common.begin() + match, common.end());
734       }
735
736       /*
737        Step 4: Assign the album artist for each song that doesn't have it set
738        and add to the album vector
739        */
740       CAlbum album;
741       album.strAlbum = i->first;
742       album.artist = common;
743       album.bCompilation = compilation;
744       for (vector<CSong *>::iterator k = artistSongs.begin(); k != artistSongs.end(); ++k)
745       {
746         if ((*k)->albumArtist.empty())
747           (*k)->albumArtist = common;
748         album.songs.push_back(*(*k));
749         // TODO: in future we may wish to union up the genres, for now we assume they're the same
750         if (album.genre.empty())
751           album.genre = (*k)->genre;
752         //       in addition, we may want to use year as discriminating for albums
753         if (album.iYear == 0)
754           album.iYear = (*k)->iYear;
755       }
756
757       albums.push_back(album);
758     }
759   }
760 }
761
762 void CMusicInfoScanner::FindArtForAlbums(VECALBUMS &albums, const CStdString &path)
763 {
764   /*
765    If there's a single album in the folder, then art can be taken from
766    the folder art.
767    */
768   std::string albumArt;
769   if (albums.size() == 1)
770   {
771     CFileItem album(path, true);
772     albumArt = album.GetUserMusicThumb(true);
773     if (!albumArt.empty())
774       albums[0].art["thumb"] = albumArt;
775   }
776   for (VECALBUMS::iterator i = albums.begin(); i != albums.end(); ++i)
777   {
778     CAlbum &album = *i;
779
780     if (albums.size() != 1)
781       albumArt = "";
782
783     /*
784      Find art that is common across these items
785      If we find a single art image we treat it as the album art
786      and discard song art else we use first as album art and
787      keep everything as song art.
788      */
789     bool singleArt = true;
790     CSong *art = NULL;
791     for (VECSONGS::iterator k = album.songs.begin(); k != album.songs.end(); ++k)
792     {
793       CSong &song = *k;
794       if (song.HasArt())
795       {
796         if (art && !art->ArtMatches(song))
797         {
798           singleArt = false;
799           break;
800         }
801         if (!art)
802           art = &song;
803       }
804     }
805
806     /*
807       assign the first art found to the album - better than no art at all
808     */
809
810     if (art && albumArt.empty())
811     {
812       if (!art->strThumb.empty())
813         albumArt = art->strThumb;
814       else
815         albumArt = CTextureCache::GetWrappedImageURL(art->strFileName, "music");
816     }
817
818     if (!albumArt.empty())
819       album.art["thumb"] = albumArt;
820
821     if (singleArt)
822     { //if singleArt then we can clear the artwork for all songs
823       for (VECSONGS::iterator k = album.songs.begin(); k != album.songs.end(); ++k)
824         k->strThumb.clear();
825     }
826     else
827     { // more than one piece of art was found for these songs, so cache per song
828       for (VECSONGS::iterator k = album.songs.begin(); k != album.songs.end(); ++k)
829       {
830         if (k->strThumb.empty() && !k->embeddedArt.empty())
831           k->strThumb = CTextureCache::GetWrappedImageURL(k->strFileName, "music");
832       }
833     }
834   }
835   if (albums.size() == 1 && !albumArt.empty())
836   { // assign to folder thumb as well
837     CMusicThumbLoader::SetCachedImage(path, "thumb", albumArt);
838   }
839 }
840
841 // This function is run by another thread
842 void CMusicInfoScanner::Run()
843 {
844   int count = 0;
845   while (!m_bStop && m_pathsToCount.size())
846     count+=CountFilesRecursively(*m_pathsToCount.begin());
847   m_itemCount = count;
848 }
849
850 // Recurse through all folders we scan and count files
851 int CMusicInfoScanner::CountFilesRecursively(const CStdString& strPath)
852 {
853   // load subfolder
854   CFileItemList items;
855 //  CLog::Log(LOGDEBUG, __FUNCTION__" - processing dir: %s", strPath.c_str());
856   CDirectory::GetDirectory(strPath, items, g_settings.m_musicExtensions, DIR_FLAG_NO_FILE_DIRS);
857
858   if (m_bStop)
859     return 0;
860
861   // true for recursive counting
862   int count = CountFiles(items, true);
863
864   // remove this path from the list we're processing
865   set<CStdString>::iterator it = m_pathsToCount.find(strPath);
866   if (it != m_pathsToCount.end())
867     m_pathsToCount.erase(it);
868
869 //  CLog::Log(LOGDEBUG, __FUNCTION__" - finished processing dir: %s", strPath.c_str());
870   return count;
871 }
872
873 int CMusicInfoScanner::CountFiles(const CFileItemList &items, bool recursive)
874 {
875   int count = 0;
876   for (int i=0; i<items.Size(); ++i)
877   {
878     const CFileItemPtr pItem=items[i];
879
880     if (recursive && pItem->m_bIsFolder)
881       count+=CountFilesRecursively(pItem->GetPath());
882     else if (pItem->IsAudio() && !pItem->IsPlayList() && !pItem->IsNFO())
883       count++;
884   }
885   return count;
886 }
887
888 int CMusicInfoScanner::GetPathHash(const CFileItemList &items, CStdString &hash)
889 {
890   // Create a hash based on the filenames, filesize and filedate.  Also count the number of files
891   if (0 == items.Size()) return 0;
892   XBMC::XBMC_MD5 md5state;
893   int count = 0;
894   for (int i = 0; i < items.Size(); ++i)
895   {
896     const CFileItemPtr pItem = items[i];
897     md5state.append(pItem->GetPath());
898     md5state.append((unsigned char *)&pItem->m_dwSize, sizeof(pItem->m_dwSize));
899     FILETIME time = pItem->m_dateTime;
900     md5state.append((unsigned char *)&time, sizeof(FILETIME));
901     if (pItem->IsAudio() && !pItem->IsPlayList() && !pItem->IsNFO())
902       count++;
903   }
904   md5state.getDigest(hash);
905   return count;
906 }
907
908 #define THRESHOLD .95f
909
910 bool CMusicInfoScanner::DownloadAlbumInfo(const CStdString& strPath, const CStdString& strArtist, const CStdString& strAlbum, bool& bCanceled, CMusicAlbumInfo& albumInfo, CGUIDialogProgress* pDialog)
911 {
912   CAlbum album;
913   VECSONGS songs;
914   XFILE::MUSICDATABASEDIRECTORY::CQueryParams params;
915   XFILE::MUSICDATABASEDIRECTORY::CDirectoryNode::GetDatabaseInfo(strPath, params);
916   bCanceled = false;
917   m_musicDatabase.Open();
918   if (m_musicDatabase.HasAlbumInfo(params.GetAlbumId()) && m_musicDatabase.GetAlbumInfo(params.GetAlbumId(),album,&songs))
919     return true;
920
921   // find album info
922   ADDON::ScraperPtr info;
923   if (!m_musicDatabase.GetScraperForPath(strPath, info, ADDON::ADDON_SCRAPER_ALBUMS) || !info)
924   {
925     m_musicDatabase.Close();
926     return false;
927   }
928
929   if (m_handle)
930   {
931     m_handle->SetTitle(StringUtils::Format(g_localizeStrings.Get(20321), info->Name().c_str()));
932     m_handle->SetText(strArtist+" - "+strAlbum);
933   }
934
935   // clear our scraper cache
936   info->ClearCache();
937
938   CMusicInfoScraper scraper(info);
939
940   // handle nfo files
941   CStdString strAlbumPath, strNfo;
942   m_musicDatabase.GetAlbumPath(params.GetAlbumId(),strAlbumPath);
943   URIUtils::AddFileToFolder(strAlbumPath,"album.nfo",strNfo);
944   CNfoFile::NFOResult result=CNfoFile::NO_NFO;
945   CNfoFile nfoReader;
946   if (XFILE::CFile::Exists(strNfo))
947   {
948     CLog::Log(LOGDEBUG,"Found matching nfo file: %s", strNfo.c_str());
949     result = nfoReader.Create(strNfo, info, -1, strPath);
950     if (result == CNfoFile::FULL_NFO)
951     {
952       CLog::Log(LOGDEBUG, "%s Got details from nfo", __FUNCTION__);
953       CAlbum album;
954       nfoReader.GetDetails(album);
955       m_musicDatabase.SetAlbumInfo(params.GetAlbumId(), album, album.songs);
956       GetAlbumArtwork(params.GetAlbumId(), album);
957       m_musicDatabase.Close();
958       return true;
959     }
960     else if (result == CNfoFile::URL_NFO || result == CNfoFile::COMBINED_NFO)
961     {
962       CScraperUrl scrUrl(nfoReader.ScraperUrl());
963       CMusicAlbumInfo album("nfo",scrUrl);
964       info = nfoReader.GetScraperInfo();
965       CLog::Log(LOGDEBUG,"-- nfo-scraper: %s",info->Name().c_str());
966       CLog::Log(LOGDEBUG,"-- nfo url: %s", scrUrl.m_url[0].m_url.c_str());
967       scraper.SetScraperInfo(info);
968       scraper.GetAlbums().push_back(album);
969     }
970     else
971       CLog::Log(LOGERROR,"Unable to find an url in nfo file: %s", strNfo.c_str());
972   }
973
974   if (!scraper.CheckValidOrFallback(g_guiSettings.GetString("musiclibrary.albumsscraper")))
975   { // the current scraper is invalid, as is the default - bail
976     CLog::Log(LOGERROR, "%s - current and default scrapers are invalid.  Pick another one", __FUNCTION__);
977     return false;
978   }
979
980   if (!scraper.GetAlbumCount())
981   {
982     scraper.FindAlbumInfo(strAlbum, strArtist);
983
984     while (!scraper.Completed())
985     {
986       if (m_bStop)
987       {
988         scraper.Cancel();
989         bCanceled = true;
990       }
991       Sleep(1);
992     }
993   }
994
995   CGUIDialogSelect *pDlg=NULL;
996   int iSelectedAlbum=0;
997   if (result == CNfoFile::NO_NFO)
998   {
999     iSelectedAlbum = -1; // set negative so that we can detect a failure
1000     if (scraper.Succeeded() && scraper.GetAlbumCount() >= 1)
1001     {
1002       int bestMatch = -1;
1003       double bestRelevance = 0;
1004       double minRelevance = THRESHOLD;
1005       if (scraper.GetAlbumCount() > 1) // score the matches
1006       {
1007         //show dialog with all albums found
1008         if (pDialog)
1009         {
1010           pDlg = (CGUIDialogSelect*)g_windowManager.GetWindow(WINDOW_DIALOG_SELECT);
1011           pDlg->SetHeading(g_localizeStrings.Get(181).c_str());
1012           pDlg->Reset();
1013           pDlg->EnableButton(true, 413); // manual
1014         }
1015
1016         for (int i = 0; i < scraper.GetAlbumCount(); ++i)
1017         {
1018           CMusicAlbumInfo& info = scraper.GetAlbum(i);
1019           double relevance = info.GetRelevance();
1020           if (relevance < 0)
1021             relevance = CUtil::AlbumRelevance(info.GetAlbum().strAlbum, strAlbum, StringUtils::Join(info.GetAlbum().artist, g_advancedSettings.m_musicItemSeparator), strArtist);
1022
1023           // if we're doing auto-selection (ie querying all albums at once, then allow 95->100% for perfect matches)
1024           // otherwise, perfect matches only
1025           if (relevance >= max(minRelevance, bestRelevance))
1026           { // we auto-select the best of these
1027             bestRelevance = relevance;
1028             bestMatch = i;
1029           }
1030           if (pDialog)
1031           {
1032             // set the label to [relevance]  album - artist
1033             CStdString strTemp;
1034             strTemp.Format("[%0.2f]  %s", relevance, info.GetTitle2());
1035             CFileItem item(strTemp);
1036             item.m_idepth = i; // use this to hold the index of the album in the scraper
1037             pDlg->Add(&item);
1038           }
1039           if (relevance > .99f) // we're so close, no reason to search further
1040             break;
1041         }
1042       }
1043       else
1044       {
1045         CMusicAlbumInfo& info = scraper.GetAlbum(0);
1046         double relevance = info.GetRelevance();
1047         if (relevance < 0)
1048           relevance = CUtil::AlbumRelevance(info.GetAlbum().strAlbum, strAlbum, StringUtils::Join(info.GetAlbum().artist, g_advancedSettings.m_musicItemSeparator), strArtist);
1049         if (relevance < THRESHOLD)
1050         {
1051           m_musicDatabase.Close();
1052           return false;
1053         }
1054         bestRelevance = relevance;
1055         bestMatch = 0;
1056       }
1057
1058       iSelectedAlbum = bestMatch;
1059       if (pDialog && bestRelevance < THRESHOLD)
1060       {
1061         pDlg->Sort(false);
1062         pDlg->DoModal();
1063
1064         // and wait till user selects one
1065         if (pDlg->GetSelectedLabel() < 0)
1066         { // none chosen
1067           if (!pDlg->IsButtonPressed())
1068           {
1069             bCanceled = true;
1070             return false;
1071           }
1072           // manual button pressed
1073           CStdString strNewAlbum = strAlbum;
1074           if (!CGUIKeyboardFactory::ShowAndGetInput(strNewAlbum, g_localizeStrings.Get(16011), false)) return false;
1075           if (strNewAlbum == "") return false;
1076
1077           CStdString strNewArtist = strArtist;
1078           if (!CGUIKeyboardFactory::ShowAndGetInput(strNewArtist, g_localizeStrings.Get(16025), false)) return false;
1079
1080           pDialog->SetLine(0, strNewAlbum);
1081           pDialog->SetLine(1, strNewArtist);
1082           pDialog->Progress();
1083
1084           m_musicDatabase.Close();
1085           return DownloadAlbumInfo(strPath,strNewArtist,strNewAlbum,bCanceled,albumInfo,pDialog);
1086         }
1087         iSelectedAlbum = pDlg->GetSelectedItem()->m_idepth;
1088       }
1089     }
1090
1091     if (iSelectedAlbum < 0)
1092     {
1093       m_musicDatabase.Close();
1094       return false;
1095     }
1096   }
1097
1098   scraper.LoadAlbumInfo(iSelectedAlbum);
1099   while (!scraper.Completed())
1100   {
1101     if (m_bStop)
1102     {
1103       bCanceled = true;
1104       scraper.Cancel();
1105     }
1106     Sleep(1);
1107   }
1108
1109   if (scraper.Succeeded())
1110   {
1111     albumInfo = scraper.GetAlbum(iSelectedAlbum);
1112     album = scraper.GetAlbum(iSelectedAlbum).GetAlbum();
1113     if (result == CNfoFile::COMBINED_NFO)
1114       nfoReader.GetDetails(album,NULL,true);
1115     m_musicDatabase.SetAlbumInfo(params.GetAlbumId(), album, scraper.GetAlbum(iSelectedAlbum).GetSongs(),false);
1116   }
1117   else
1118   {
1119     m_musicDatabase.Close();
1120     return false;
1121   }
1122
1123   // check thumb stuff
1124   GetAlbumArtwork(params.GetAlbumId(), album);
1125   m_musicDatabase.Close();
1126   return true;
1127 }
1128
1129 void CMusicInfoScanner::GetAlbumArtwork(long id, const CAlbum &album)
1130 {
1131   if (album.thumbURL.m_url.size())
1132   {
1133     if (m_musicDatabase.GetArtForItem(id, "album", "thumb").empty())
1134     {
1135       string thumb = CScraperUrl::GetThumbURL(album.thumbURL.GetFirstThumb());
1136       if (!thumb.empty())
1137       {
1138         CTextureCache::Get().BackgroundCacheImage(thumb);
1139         m_musicDatabase.SetArtForItem(id, "album", "thumb", thumb);
1140       }
1141     }
1142   }
1143 }
1144
1145 bool CMusicInfoScanner::DownloadArtistInfo(const CStdString& strPath, const CStdString& strArtist, bool& bCanceled, CGUIDialogProgress* pDialog)
1146 {
1147   XFILE::MUSICDATABASEDIRECTORY::CQueryParams params;
1148   XFILE::MUSICDATABASEDIRECTORY::CDirectoryNode::GetDatabaseInfo(strPath, params);
1149   bCanceled = false;
1150   CArtist artist;
1151   m_musicDatabase.Open();
1152   if (m_musicDatabase.GetArtistInfo(params.GetArtistId(),artist)) // already got the info
1153     return true;
1154
1155   // find artist info
1156   ADDON::ScraperPtr info;
1157   if (!m_musicDatabase.GetScraperForPath(strPath, info, ADDON::ADDON_SCRAPER_ARTISTS) || !info)
1158   {
1159     m_musicDatabase.Close();
1160     return false;
1161   }
1162
1163   if (m_handle)
1164   {
1165     m_handle->SetTitle(StringUtils::Format(g_localizeStrings.Get(20320), info->Name().c_str()));
1166     m_handle->SetText(strArtist);
1167   }
1168
1169   // clear our scraper cache
1170   info->ClearCache();
1171
1172   CMusicInfoScraper scraper(info);
1173   // handle nfo files
1174   CStdString strArtistPath, strNfo;
1175   m_musicDatabase.GetArtistPath(params.GetArtistId(),strArtistPath);
1176   URIUtils::AddFileToFolder(strArtistPath,"artist.nfo",strNfo);
1177   CNfoFile::NFOResult result=CNfoFile::NO_NFO;
1178   CNfoFile nfoReader;
1179   if (XFILE::CFile::Exists(strNfo))
1180   {
1181     CLog::Log(LOGDEBUG,"Found matching nfo file: %s", strNfo.c_str());
1182     result = nfoReader.Create(strNfo, info);
1183     if (result == CNfoFile::FULL_NFO)
1184     {
1185       CLog::Log(LOGDEBUG, "%s Got details from nfo", __FUNCTION__);
1186       CArtist artist;
1187       nfoReader.GetDetails(artist);
1188       m_musicDatabase.SetArtistInfo(params.GetArtistId(), artist);
1189       map<string, string> artwork = GetArtistArtwork(params.GetArtistId(), &artist);
1190       m_musicDatabase.SetArtForItem(params.GetArtistId(), "artist", artwork);
1191       m_musicDatabase.Close();
1192       return true;
1193     }
1194     else if (result == CNfoFile::URL_NFO || result == CNfoFile::COMBINED_NFO)
1195     {
1196       CScraperUrl scrUrl(nfoReader.ScraperUrl());
1197       CMusicArtistInfo artist("nfo",scrUrl);
1198       info = nfoReader.GetScraperInfo();
1199       CLog::Log(LOGDEBUG,"-- nfo-scraper: %s",info->Name().c_str());
1200       CLog::Log(LOGDEBUG,"-- nfo url: %s", scrUrl.m_url[0].m_url.c_str());
1201       scraper.SetScraperInfo(info);
1202       scraper.GetArtists().push_back(artist);
1203     }
1204     else
1205       CLog::Log(LOGERROR,"Unable to find an url in nfo file: %s", strNfo.c_str());
1206   }
1207
1208   if (!scraper.GetArtistCount())
1209   {
1210     scraper.FindArtistInfo(strArtist);
1211
1212     while (!scraper.Completed())
1213     {
1214       if (m_bStop)
1215       {
1216         scraper.Cancel();
1217         bCanceled = true;
1218       }
1219       Sleep(1);
1220     }
1221   }
1222
1223   int iSelectedArtist = 0;
1224   if (result == CNfoFile::NO_NFO)
1225   {
1226     if (scraper.Succeeded() && scraper.GetArtistCount() >= 1)
1227     {
1228       // now load the first match
1229       if (pDialog && scraper.GetArtistCount() > 1)
1230       {
1231         // if we found more then 1 album, let user choose one
1232         CGUIDialogSelect *pDlg = (CGUIDialogSelect*)g_windowManager.GetWindow(WINDOW_DIALOG_SELECT);
1233         if (pDlg)
1234         {
1235           pDlg->SetHeading(g_localizeStrings.Get(21890));
1236           pDlg->Reset();
1237           pDlg->EnableButton(true, 413); // manual
1238
1239           for (int i = 0; i < scraper.GetArtistCount(); ++i)
1240           {
1241             // set the label to artist
1242             CFileItem item(scraper.GetArtist(i).GetArtist());
1243             CStdString strTemp=scraper.GetArtist(i).GetArtist().strArtist;
1244             if (!scraper.GetArtist(i).GetArtist().strBorn.IsEmpty())
1245               strTemp += " ("+scraper.GetArtist(i).GetArtist().strBorn+")";
1246             if (!scraper.GetArtist(i).GetArtist().genre.empty())
1247             {
1248               CStdString genres = StringUtils::Join(scraper.GetArtist(i).GetArtist().genre, g_advancedSettings.m_musicItemSeparator);
1249               if (!genres.empty())
1250                 strTemp.Format("[%s] %s", genres.c_str(), strTemp.c_str());
1251             }
1252             item.SetLabel(strTemp);
1253             item.m_idepth = i; // use this to hold the index of the album in the scraper
1254             pDlg->Add(&item);
1255           }
1256           pDlg->DoModal();
1257
1258           // and wait till user selects one
1259           if (pDlg->GetSelectedLabel() < 0)
1260           { // none chosen
1261             if (!pDlg->IsButtonPressed())
1262             {
1263               bCanceled = true;
1264               return false;
1265             }
1266             // manual button pressed
1267             CStdString strNewArtist = strArtist;
1268             if (!CGUIKeyboardFactory::ShowAndGetInput(strNewArtist, g_localizeStrings.Get(16025), false)) return false;
1269
1270             if (pDialog)
1271             {
1272               pDialog->SetLine(0, strNewArtist);
1273               pDialog->Progress();
1274             }
1275             m_musicDatabase.Close();
1276             return DownloadArtistInfo(strPath,strNewArtist,bCanceled,pDialog);
1277           }
1278           iSelectedArtist = pDlg->GetSelectedItem()->m_idepth;
1279         }
1280       }
1281     }
1282     else
1283     {
1284       m_musicDatabase.Close();
1285       return false;
1286     }
1287   }
1288
1289   scraper.LoadArtistInfo(iSelectedArtist, strArtist);
1290   while (!scraper.Completed())
1291   {
1292     if (m_bStop)
1293     {
1294       scraper.Cancel();
1295       bCanceled = true;
1296     }
1297     Sleep(1);
1298   }
1299
1300   if (scraper.Succeeded())
1301   {
1302     artist = scraper.GetArtist(iSelectedArtist).GetArtist();
1303     if (result == CNfoFile::COMBINED_NFO)
1304       nfoReader.GetDetails(artist,NULL,true);
1305     m_musicDatabase.SetArtistInfo(params.GetArtistId(), artist);
1306   }
1307
1308   // check thumb stuff
1309   map<string, string> artwork = GetArtistArtwork(params.GetArtistId(), &artist);
1310   m_musicDatabase.SetArtForItem(params.GetArtistId(), "artist", artwork);
1311
1312   m_musicDatabase.Close();
1313   return true;
1314 }
1315
1316 map<string, string> CMusicInfoScanner::GetArtistArtwork(long id, const CArtist *artist)
1317 {
1318   CStdString artistPath;
1319   m_musicDatabase.Open();
1320   bool checkLocal = m_musicDatabase.GetArtistPath(id, artistPath);
1321   m_musicDatabase.Close();
1322
1323   CFileItem item(artistPath, true);
1324   map<string, string> artwork;
1325
1326   // check thumb
1327   CStdString thumb;
1328   if (checkLocal)
1329     thumb = item.GetUserMusicThumb(true);
1330   if (thumb.IsEmpty() && artist)
1331     thumb = CScraperUrl::GetThumbURL(artist->thumbURL.GetFirstThumb());
1332   if (!thumb.IsEmpty())
1333   {
1334     CTextureCache::Get().BackgroundCacheImage(thumb);
1335     artwork.insert(make_pair("thumb", thumb));
1336   }
1337
1338   // check fanart
1339   CStdString fanart;
1340   if (checkLocal)
1341     fanart = item.GetLocalFanart();
1342   if (fanart.IsEmpty() && artist)
1343     fanart = artist->fanart.GetImageURL();
1344   if (!fanart.IsEmpty())
1345   {
1346     CTextureCache::Get().BackgroundCacheImage(fanart);
1347     artwork.insert(make_pair("fanart", fanart));
1348   }
1349
1350   return artwork;
1351 }