"Fossies" - the Fresh Open Source Software Archive

Member "MP3Diags-unstable-1.5.01/src/AlbumInfoDownloaderDlgImpl.cpp" (3 Apr 2019, 36692 Bytes) of package /linux/privat/MP3Diags-unstable-1.5.01.tar.gz:


As a special service "Fossies" has tried to format the requested source page into HTML format using (guessed) C and C++ source code syntax highlighting (style: standard) with prefixed line numbers and code folding option. Alternatively you can here view or download the uninterpreted source code file. For more information about "AlbumInfoDownloaderDlgImpl.cpp" see the Fossies "Dox" file reference documentation and the latest Fossies "Diffs" side-by-side code changes report: 1.3.04_vs_1.5.01.

    1 /***************************************************************************
    2  *   MP3 Diags - diagnosis, repairs and tag editing for MP3 files          *
    3  *                                                                         *
    4  *   Copyright (C) 2009 by Marian Ciobanu                                  *
    5  *   ciobi@inbox.com                                                       *
    6  *                                                                         *
    7  *   This program is free software; you can redistribute it and/or modify  *
    8  *   it under the terms of the GNU General Public License version 2 as     *
    9  *   published by the Free Software Foundation.                            *
   10  *                                                                         *
   11  *   This program is distributed in the hope that it will be useful,       *
   12  *   but WITHOUT ANY WARRANTY; without even the implied warranty of        *
   13  *   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the         *
   14  *   GNU General Public License for more details.                          *
   15  *                                                                         *
   16  *   You should have received a copy of the GNU General Public License     *
   17  *   along with this program; if not, write to the                         *
   18  *   Free Software Foundation, Inc.,                                       *
   19  *   59 Temple Place - Suite 330, Boston, MA  02111-1307, USA.             *
   20  ***************************************************************************/
   21 
   22 
   23 #include  <memory>
   24 
   25 #include  <zlib.h>
   26 
   27 #ifndef WIN32
   28     #include  <sys/time.h>
   29 #else
   30     #include  <ctime>
   31 #endif
   32 
   33 #include  <QHttp>
   34 #include  <QMessageBox>
   35 #include  <QXmlSimpleReader>
   36 #include  <QBuffer>
   37 #include  <QPainter>
   38 #include  <QScrollBar>
   39 #include  <QHeaderView>
   40 #include  <QTime>
   41 #include  <QPixmap>
   42 
   43 #include  "AlbumInfoDownloaderDlgImpl.h"
   44 
   45 #include  "Helpers.h"
   46 #include  "ColumnResizer.h"
   47 #include  "Widgets.h"
   48 
   49 
   50 #include  "fstream_unicode.h"
   51 
   52 using namespace std;
   53 using namespace pearl;
   54 
   55 // /*extern*/ int CELL_WIDTH (22);
   56 extern int CELL_HEIGHT;
   57 
   58 
   59 
   60 
   61 AlbumInfoDownloaderDlgImpl::AlbumInfoDownloaderDlgImpl(QWidget* pParent, SessionSettings& settings, bool bSaveResults) : QDialog(pParent, getDialogWndFlags()), Ui::AlbumInfoDownloaderDlg(), m_bSaveResults(bSaveResults), m_nLastCount(0), m_nLastTime(0), m_settings(settings)
   62 {
   63     setupUi(this);
   64 
   65     m_pQHttp = new QHttp (this);
   66 
   67     connect(m_pQHttp, SIGNAL(requestFinished(int, bool)), this, SLOT(onRequestFinished(int, bool)));
   68 
   69     m_pTrackListG->verticalHeader()->setMinimumSectionSize(CELL_HEIGHT);
   70     m_pTrackListG->verticalHeader()->setDefaultSectionSize(CELL_HEIGHT);
   71     decreaseRowHeaderFont(*m_pTrackListG);
   72 
   73     { QAction* p (new QAction(this)); p->setShortcut(QKeySequence("F1")); connect(p, SIGNAL(triggered()), this, SLOT(onHelp())); addAction(p); }
   74 }
   75 
   76 
   77 AlbumInfoDownloaderDlgImpl::~AlbumInfoDownloaderDlgImpl()
   78 {
   79 }
   80 
   81 
   82 bool AlbumInfoDownloaderDlgImpl::getInfo(const std::string& strArtist, const std::string& strAlbum, int nTrackCount, AlbumInfo*& pAlbumInfo, ImageInfo*& pImageInfo)
   83 {
   84 LAST_STEP("AlbumInfoDownloaderDlgImpl::getInfo");
   85     m_nExpectedTracks = nTrackCount;
   86 
   87     pAlbumInfo = 0; pImageInfo = 0;
   88 
   89     if (initSearch(strArtist, strAlbum))
   90     {
   91         search();
   92     }
   93 
   94     bool bRes (QDialog::Accepted == exec());
   95     if (bRes)
   96     {
   97         saveSize();
   98         CB_ASSERT (0 != getAlbumCount());
   99         if (!m_bSaveImageOnly)
  100         {
  101             pAlbumInfo = new AlbumInfo();
  102             album(m_nCrtAlbum).copyTo(*pAlbumInfo);
  103             //pAlbumInfo->m_strReleased = convStr(m_pRealeasedE->text()); //ttt2 maybe allow users to overwrite fields in edit boxes, esp. genre; however, most of the cases it's almost as easy to make any changes in the tag editor (well, there's an F2 and then "copy form first"); there are several issues in implementing this: 1) going to prev/next album; 2) artist name gets copied to each track; 3) consistency: if the edit boxes are editable why not the table? so, better without
  104 
  105             if (m_pVolumeCbB->isEnabled())
  106             {
  107                 if (m_pVolumeCbB->currentIndex() == m_pVolumeCbB->count() - 1)
  108                 { // just give sequential numbers when "<All>" in a multivolume is used - see https://sourceforge.net/projects/mp3diags/forums/forum/947206/topic/4503061/index/page/1 - perhaps can be improved
  109                     char a [10];
  110                     for (int i = 0; i < cSize(pAlbumInfo->m_vTracks); ++i)
  111                     {
  112                         sprintf(a, "%02d", i + 1);
  113                         pAlbumInfo->m_vTracks[i].m_strPos = a;
  114                     }
  115                 }
  116                 else
  117                 {
  118                     vector<TrackInfo> vTracks;
  119                     string s (convStr(m_pVolumeCbB->itemText(m_pVolumeCbB->currentIndex())));
  120                     int k (cSize(s));
  121                     for (int i = 0, n = cSize(pAlbumInfo->m_vTracks); i < n; ++i)
  122                     {
  123                         if (beginsWith(pAlbumInfo->m_vTracks[i].m_strPos, s))
  124                         {
  125                             vTracks.push_back(pAlbumInfo->m_vTracks[i]);
  126                             vTracks.back().m_strPos.erase(0, k);
  127                         }
  128                     }
  129                     vTracks.swap(pAlbumInfo->m_vTracks);
  130                 }
  131             }
  132         }
  133 
  134         if (m_nCrtImage >= 0)
  135         {
  136             pImageInfo = new ImageInfo(*album(m_nCrtAlbum).m_vpImages[m_nCrtImage]);
  137         }
  138     }
  139     return bRes;
  140 }
  141 
  142 
  143 // clears pending HTTP requests, m_eNavigDir and m_eWaiting; restores the cursor if needed;
  144 /*virtual*/ void AlbumInfoDownloaderDlgImpl::resetNavigation()
  145 {
  146 LAST_STEP("AlbumInfoDownloaderDlgImpl::resetNavigation");
  147     m_pQHttp->clearPendingRequests();
  148     setWaiting(NOTHING);
  149     m_eNavigDir = NONE;
  150 }
  151 
  152 
  153 
  154 string AlbumInfoDownloaderDlgImpl::replaceSymbols(string s) // replaces everything besides letters and digits with getReplacementChar()
  155 {
  156 LAST_STEP("AlbumInfoDownloaderDlgImpl::replaceSymbols");
  157     char c (getReplacementChar());
  158     for (int i = 0; i < cSize(s); ++i)
  159     {
  160         if ((unsigned char)(s[i]) < 128 && '\'' != s[i] && !isalnum(s[i]))
  161         {
  162             s[i] = c;
  163         }
  164     }
  165 
  166     return s;
  167 }
  168 
  169 /*static*/ const char* AlbumInfoDownloaderDlgImpl::NOT_FOUND_AT_AMAZON = QT_TRANSLATE_NOOP("AlbumInfoDownloaderDlgImpl", "not found at amazon.com");
  170 
  171 void AlbumInfoDownloaderDlgImpl::search()
  172 {
  173 LAST_STEP("AlbumInfoDownloaderDlgImpl::search");
  174     m_pResArtistE->setText("");
  175     m_pResAlbumE->setText("");
  176     m_pDownloadsM->setText("");
  177     m_pAlbumNotesM->setText("");
  178     m_pFormatE->setText("");
  179     m_pRealeasedE->setText("");
  180     m_pResultNoL->setText("");
  181     updateTrackList();
  182     m_pVolumeCbB->clear();
  183     m_pImageL->setPixmap(QPixmap());
  184     m_pImageL->setText("");
  185     m_pImgSizeL->setText("\n");
  186     m_pViewAtAmazonL->setText(tr(NOT_FOUND_AT_AMAZON));
  187 
  188     m_strQuery = escapeHttp(createQuery()); // e.g. http://www.discogs.com/search?type=all&q=beatles&f=xml&api_key=f51e9c8f6c, without page number; to be used by loadNextPage();
  189     m_nTotalPages = 1; m_nLastLoadedPage = -1;
  190     m_nCrtAlbum = -1; m_nCrtImage = -1;
  191     resetNavigation();
  192     //m_eNavigDir = NEXT;
  193     m_eNavigDir = NEXT;
  194     m_bNavigateByAlbum = false;
  195     addNote(tr("searching ..."));
  196     m_pGenreE->setText("");
  197     //next(); //loadNextPage();
  198     retryNavigation();
  199 }
  200 
  201 
  202 void AlbumInfoDownloaderDlgImpl::on_m_pPrevB_clicked()
  203 {
  204 LAST_STEP("AlbumInfoDownloaderDlgImpl::on_m_pPrevB_clicked");
  205     if (0 == getAlbumCount() || NOTHING != m_eWaiting) { return; }
  206 
  207     m_bNavigateByAlbum = false;
  208     m_eNavigDir = PREV;
  209     retryNavigation();
  210 }
  211 
  212 
  213 void AlbumInfoDownloaderDlgImpl::on_m_pNextB_clicked()
  214 {
  215 LAST_STEP("AlbumInfoDownloaderDlgImpl::on_m_pNextB_clicked");
  216     if (0 == getAlbumCount() || NOTHING != m_eWaiting) { return; }
  217 
  218     m_bNavigateByAlbum = false;
  219     m_eNavigDir = NEXT;
  220     retryNavigation();
  221 }
  222 
  223 
  224 void AlbumInfoDownloaderDlgImpl::on_m_pPrevAlbumB_clicked()
  225 {
  226 LAST_STEP("AlbumInfoDownloaderDlgImpl::on_m_pPrevAlbumB_clicked");
  227     if (0 == getAlbumCount() || NOTHING != m_eWaiting) { return; }
  228 
  229     m_bNavigateByAlbum = true;
  230     m_eNavigDir = PREV;
  231     retryNavigation();
  232 }
  233 
  234 
  235 void AlbumInfoDownloaderDlgImpl::on_m_pNextAlbumB_clicked()
  236 {
  237 LAST_STEP("AlbumInfoDownloaderDlgImpl::on_m_pNextAlbumB_clicked");
  238     if (0 == getAlbumCount() || NOTHING != m_eWaiting) { return; }
  239 
  240     m_bNavigateByAlbum = true;
  241     m_eNavigDir = NEXT;
  242     retryNavigation();
  243 }
  244 
  245 
  246 void AlbumInfoDownloaderDlgImpl::on_m_pSaveAllB_clicked()
  247 {
  248 LAST_STEP("AlbumInfoDownloaderDlgImpl::on_m_pSaveAllB_clicked");
  249     if (NOTHING != m_eWaiting)
  250     {
  251         showCritical(this, tr("Error"), tr("You cannot save the results now, because a request is still pending"));
  252         return;
  253     }
  254 
  255     if (0 == getAlbumCount())
  256     {
  257         showCritical(this, tr("Error"), tr("You cannot save the results now, because no album is loaded"));
  258         return;
  259     }
  260 
  261     int nCnt (0);
  262     CB_ASSERT (m_nCrtAlbum >= 0 && m_nCrtAlbum < getAlbumCount());// ttt0 triggered according to mail on 2015.12.13
  263     const WebAlbumInfoBase& albumInfo (album(m_nCrtAlbum));
  264     if (m_pVolumeCbB->isEnabled() && m_pVolumeCbB->currentIndex() != m_pVolumeCbB->count() - 1)
  265     {
  266         string s (convStr(m_pVolumeCbB->itemText(m_pVolumeCbB->currentIndex())));
  267         for (int i = 0, n = cSize(albumInfo.m_vTracks); i < n; ++i)
  268         {
  269             if (beginsWith(albumInfo.m_vTracks[i].m_strPos, s))
  270             {
  271                 ++nCnt;
  272             }
  273         }
  274     }
  275     else
  276     {
  277         nCnt = cSize(albumInfo.m_vTracks);
  278     }
  279 
  280     if (nCnt != m_nExpectedTracks)
  281     {
  282         QString s;
  283         const QString& qstrVolMsg (
  284             m_pVolumeCbB->isEnabled() &&
  285             (
  286                 (nCnt > m_nExpectedTracks && m_pVolumeCbB->currentIndex() == m_pVolumeCbB->count() - 1) ||
  287                 (nCnt < m_nExpectedTracks && m_pVolumeCbB->currentIndex() != m_pVolumeCbB->count() - 1)
  288             )
  289             ? tr("You may want to use a different volume selection on this multi-volume release.\n\n") : "");
  290 
  291         if (nCnt > m_nExpectedTracks)
  292         {
  293             s = tr("A number of %1 tracks were expected, but your selection contains %2. Additional tracks will be discarded.\n\n%3Save anyway?").arg(m_nExpectedTracks).arg(nCnt).arg(qstrVolMsg);
  294         }
  295         else
  296         {
  297             s = tr("A number of %1 tracks were expected, but your selection only contains %2. Remaining tracks will get null values.\n\n%3Save anyway?").arg(m_nExpectedTracks).arg(nCnt).arg(qstrVolMsg);
  298         }
  299 
  300         if (showMessage(this, QMessageBox::Question, 1, 1, tr("Count inconsistency"), s, tr("&Save"), tr("Cancel")) != 0) { return; }
  301     }
  302 
  303     m_bSaveImageOnly = false;
  304     accept();
  305 }
  306 
  307 void AlbumInfoDownloaderDlgImpl::on_m_pSaveImageB_clicked()
  308 {
  309 LAST_STEP("AlbumInfoDownloaderDlgImpl::on_m_pSaveImageB_clicked");
  310     if (NOTHING != m_eWaiting)
  311     {
  312         showCritical(this, tr("Error"), tr("You cannot save the results now, because a request is still pending"));
  313         return;
  314     }
  315 
  316     if (0 == getAlbumCount() || -1 == m_nCrtImage)
  317     {
  318         showCritical(this, tr("Error"), tr("You cannot save any image now, because there is no image loaded"));
  319         return;
  320     }
  321     // ttt2 perhaps shouldn't save an "error" image
  322 
  323     m_bSaveImageOnly = true;
  324     accept();
  325     //ttt2 perhaps allow multiple images to be saved, by adding a button to "add to save list"; see https://sourceforge.net/projects/mp3diags/forums/forum/947207/topic/4006484
  326 }
  327 
  328 void AlbumInfoDownloaderDlgImpl::on_m_pCancelB_clicked()
  329 {
  330 LAST_STEP("AlbumInfoDownloaderDlgImpl::on_m_pCancelB_clicked");
  331     reject();
  332 }
  333 
  334 
  335 
  336 //==========================================================================================================================
  337 //==========================================================================================================================
  338 //==========================================================================================================================
  339 
  340 // what may happen:
  341 //   1) there is a "next" element and it is in memory; then the GUI is updated and that is all (this calls resetNavigation() );
  342 //   2) there is a "next" element but it is not loaded; album info or picture are missing, or there's another page with search results that wasn't loaded yet; then a request is made for what's missing to be downloaded, and the state is updated to reflect that; when the request completes, retryNavigation() will get called, which will call again next()
  343 //   3) there is no "next"; nothing happens in this case;
  344 //
  345 // if there's nothing to wait for, retryNavigation() calls resetNavigation() after next() exits; (retryNavigation() is the only function that is supposed to call next() );
  346 //
  347 // if no new HTTP request is sent, either setWaiting(NOTHING) or reloadGui() should be called, to leave m_eWaiting in a consistent state;
  348 //
  349 void AlbumInfoDownloaderDlgImpl::next()
  350 {
  351 LAST_STEP("AlbumInfoDownloaderDlgImpl::next");
  352     //if (NONE != m_eNavigDir) { return; }
  353     CB_ASSERT (NEXT == m_eNavigDir);
  354 
  355     int nNextAlbum (m_nCrtAlbum);
  356     int nNextImage (m_nCrtImage);
  357 
  358     for (;;)
  359     {
  360         if (nNextAlbum >= 0 && nNextImage < cSize(album(nNextAlbum).m_vstrImageNames) - 1 && (-1 == nNextImage || !m_bNavigateByAlbum))
  361         {
  362             ++nNextImage;
  363         }
  364         else if (nNextAlbum + 1 < getAlbumCount())
  365         { // m_vAlbums contains a "next" album, which may or may not be loaded
  366             if (album(nNextAlbum + 1).m_strTitle.empty())
  367             {
  368                 requestAlbum(nNextAlbum + 1);
  369                 return;
  370             }
  371             else
  372             {
  373                 ++nNextAlbum;
  374                 nNextImage = album(nNextAlbum).m_vstrImageNames.empty() ? -1 : 0;
  375             }
  376         }
  377         else
  378         { // nothing left in m_vAlbums
  379             if (m_nLastLoadedPage == m_nTotalPages - 1)
  380             { // !!! there's no "next" at all; just exit;
  381                 setWaiting(NOTHING);
  382             }
  383             else
  384             {
  385                 loadNextPage();
  386             }
  387             return;
  388         }
  389 
  390         // if it got here, we have a loaded album (pictures might be missing, though)
  391 
  392         if ((m_pImageFltCkB->isChecked() && album(nNextAlbum).m_vpImages.empty()) ||
  393             (m_pCdFltCkB->isChecked() && string::npos == album(nNextAlbum).m_strFormat.find("CD")) ||
  394             (m_pTrackCntFltCkB->isChecked() && m_nExpectedTracks != cSize(album(nNextAlbum).m_vTracks))) // ttt2 MusicBrainz could perform better here, because it knows how many tracks are in an album without actually loading it; ttt2 redo the whole next() / prev() thing in a more logical way; perhaps separate next() from nextAlbum();
  395         {
  396             continue;
  397         }
  398 
  399         if (-1 != nNextImage && 0 == album(nNextAlbum).m_vpImages[nNextImage])
  400         {
  401             requestImage(nNextAlbum, nNextImage);
  402             return;
  403         }
  404 
  405         m_nCrtAlbum = nNextAlbum;
  406         m_nCrtImage = nNextImage;
  407         reloadGui();
  408         return;
  409     }
  410 }
  411 //ttt2 perhaps filter by durations (but see first how it works without it)
  412 
  413 
  414 
  415 // like next(), but here there's no need to load result pages; another difference is that when m_bNavigateByAlbum is set and we are at the first album and some other picture than the first, it goes to the first picture; in the similar case, next doesn't do anything (going to the last picture doesn't feel right)
  416 void AlbumInfoDownloaderDlgImpl::previous()
  417 {
  418 LAST_STEP("AlbumInfoDownloaderDlgImpl::previous");
  419     CB_ASSERT (PREV == m_eNavigDir);
  420     //if (NONE != m_eNavigDir) { return; }
  421 
  422     int nPrevAlbum (m_nCrtAlbum);
  423     int nPrevImage (m_nCrtImage);
  424 
  425     for (;;)
  426     {
  427         if (nPrevImage > 0 && !m_bNavigateByAlbum)
  428         {
  429             --nPrevImage;
  430         }
  431         else if (nPrevAlbum > 0)
  432         { // m_vAlbums contains a "prev" album, which is loaded
  433             CB_ASSERT (!album(nPrevAlbum - 1).m_strTitle.empty());
  434 
  435             --nPrevAlbum;
  436             int nImgCnt (cSize(album(nPrevAlbum).m_vstrImageNames));
  437             nPrevImage = (m_bNavigateByAlbum && nImgCnt > 0) ? 0 : nImgCnt - 1; // !!! it's OK if images don't exist for that album
  438         }
  439         else
  440         { // nothing left in m_vAlbums
  441             if (m_bNavigateByAlbum && nPrevImage > 0)
  442             {
  443                 m_nCrtAlbum = 0;
  444                 m_nCrtImage = 0;
  445                 reloadGui();
  446             }
  447             else
  448             {
  449                 setWaiting(NOTHING);
  450             }
  451             return;
  452         }
  453 
  454         // if it got here, we have a loaded album (pictures might be missing, though)
  455 
  456         if ((m_pImageFltCkB->isChecked() && album(nPrevAlbum).m_vpImages.empty()) ||
  457             (m_pCdFltCkB->isChecked() && string::npos == album(nPrevAlbum).m_strFormat.find("CD")) || // Discogs has one format, but MusicBrainz may have several, so find() is used instead of "=="
  458             (m_pTrackCntFltCkB->isChecked() && m_nExpectedTracks != cSize(album(nPrevAlbum).m_vTracks)))
  459         {
  460             continue;
  461         }
  462 
  463         if (-1 != nPrevImage && 0 == album(nPrevAlbum).m_vpImages[nPrevImage])
  464         {
  465             requestImage(nPrevAlbum, nPrevImage);
  466             return;
  467         }
  468 
  469         m_nCrtAlbum = nPrevAlbum;
  470         m_nCrtImage = nPrevImage;
  471         reloadGui();
  472         return;
  473     }
  474 }
  475 
  476 
  477 void AlbumInfoDownloaderDlgImpl::setImageType(const string& strName)
  478 {
  479 LAST_STEP("AlbumInfoDownloaderDlgImpl::setImageType");
  480     m_eLoadingImageCompr = ImageInfo::INVALID;
  481     string::size_type m (strName.rfind('.'));
  482     if (string::npos != m)
  483     {
  484         string s;
  485         ++m;
  486         for (; m < strName.size(); ++m)
  487         {
  488             s += tolower(strName[m]);
  489         }
  490 
  491         if (s == "jpg" || s == "jpeg")
  492         {
  493             m_eLoadingImageCompr = ImageInfo::JPG;
  494         }
  495         else if (s == "png")
  496         {
  497             m_eLoadingImageCompr = ImageInfo::PNG;
  498         }
  499     }
  500 }
  501 
  502 
  503 
  504 void AlbumInfoDownloaderDlgImpl::onRequestFinished(int /*nId*/, bool bError)
  505 {
  506 LAST_STEP("AlbumInfoDownloaderDlgImpl::onRequestFinished");
  507 //cout << "received ID = " << nId << endl;
  508 //if (1 == nId) { return; } // some automatically generated request, which should be ignored
  509     if (bError)
  510     {
  511         addNote(tr("request error"));
  512         resetNavigation();
  513         return;
  514     }
  515 
  516     QHttp* pQHttp (getWaitingHttp());
  517 
  518     qint64 nAv (pQHttp->bytesAvailable());
  519     if (0 == nAv)
  520     {
  521         //addNote("received empty response");
  522         //cout << "empty request returned\n";
  523 
  524         //m_eState = NORMAL; // !!! DON'T set m_eWaiting to NOTHING; empty responses come for no identifiable requests and they should be just ignored
  525         return;
  526     }
  527 
  528     CB_ASSERT (NOTHING != m_eWaiting);
  529 
  530     { QString qstrMsg (tr("received %1 bytes").arg(nAv)); addNote(qstrMsg); }
  531 
  532     QString qstrXml;
  533 
  534     QByteArray b (pQHttp->readAll());
  535     CB_ASSERT (b.size() == nAv);
  536 
  537     if (nAv < 10)
  538     { // too short
  539         addNote(tr("received very short response; aborting request ..."));
  540         resetNavigation();
  541         return;
  542     }
  543 
  544     if (IMAGE == m_eWaiting)
  545     {
  546         QString qstrInfo;
  547         QImage img;
  548         QByteArray comprImg (b);
  549         ImageInfo::Compr eOrigCompr (m_eLoadingImageCompr);
  550 
  551         if (img.loadFromData(b)) //ttt2 not sure what happens for huge images;
  552         {
  553             qstrInfo = tr("Original: %1kB, %2x%3").arg(nAv/1024).arg(img.width()).arg(img.height());
  554             //cout << "image size " << img.width() << "x" << img.height() << endl;
  555 
  556             int nWidth (img.width()), nHeight (img.height());
  557 
  558             if (nAv > ImageInfo::MAX_IMAGE_SIZE || m_eLoadingImageCompr == ImageInfo::INVALID)
  559             {
  560                 QImage scaledImg;
  561                 ImageInfo::compress(img, scaledImg, comprImg);
  562                 nWidth = scaledImg.width(); nHeight = scaledImg.height();
  563                 m_eLoadingImageCompr = ImageInfo::JPG;
  564 
  565                 //cout << "scaled image size " << img.width() << "x" << img.height() << endl;
  566                 qstrInfo += tr("\nRecompressed to: %1kB, %2x%3").arg(comprImg.size()/1024).arg(img.width()).arg(img.height());
  567             }
  568             else
  569             {
  570                 qstrInfo += tr("\nNot recompressed");
  571             }
  572             onImageLoaded(comprImg, nWidth, nHeight, qstrInfo);
  573         }
  574         else
  575         {
  576             showCritical(this, tr("Error"), tr("Failed to load the image"));
  577             const int SIZE (150);
  578             QImage errImg (SIZE, SIZE, QImage::Format_ARGB32);
  579             QPainter pntr (&errImg);
  580             pntr.fillRect(0, 0, SIZE, SIZE, QColor(255, 128, 128));
  581             pntr.drawRect(0, 0, SIZE - 1, SIZE - 1);
  582             pntr.drawText(QRectF(0, 0, SIZE, SIZE), Qt::AlignCenter, tr("Error"));
  583             qstrInfo = tr("Error loading image\n");
  584             comprImg.clear();
  585             QBuffer bfr (&comprImg);
  586             errImg.save(&bfr, "png");
  587             m_eLoadingImageCompr = ImageInfo::PNG;
  588             onImageLoaded(comprImg, SIZE, SIZE, qstrInfo);
  589         }
  590 
  591         if (m_bSaveResults) { saveDownloadedData(b.constData(), b.size(), (ImageInfo::JPG == eOrigCompr ? "jpg" : (ImageInfo::PNG == eOrigCompr ? "png" : "unkn"))); }
  592 
  593         return;
  594     }
  595 
  596     if (0x1f == (unsigned char)b[0] && 0x8b == (unsigned char)b[1])
  597     { // gzip
  598         z_stream strm;
  599         strm.zalloc = Z_NULL;
  600         strm.zfree  = Z_NULL;
  601         strm.opaque = 0;
  602         strm.next_in = const_cast<unsigned char*>(reinterpret_cast<const unsigned char*>(b.constData()));
  603         strm.avail_in = nAv;
  604 
  605         vector<char> v (nAv);
  606 
  607         //int nRes (inflateInit(&strm));
  608         int nRes (inflateInit2(&strm, 16 + 15)); // !!! see libz.h for details; "32" makes this able to handle both gzip and zlib, by auto-detecting the format; 16 is used to force gzip
  609         if (Z_OK != nRes) { addNote(tr("init error")); goto e2; }
  610 
  611         strm.next_out = reinterpret_cast<unsigned char*>(&v[0]);
  612         strm.avail_out = v.size();
  613 
  614         //cout << (void*)strm.next_in << " " << strm.avail_in << " " << (void*)strm.next_out << " " << strm.avail_out << endl;
  615         for (;;)
  616         {
  617             nRes = inflate(&strm, Z_SYNC_FLUSH); // Z_FINISH
  618             //cout << (void*)strm.next_in << " " << strm.avail_in << " " << (void*)strm.next_out << " " << strm.avail_out << endl;
  619             if (Z_STREAM_END == nRes) { break; }
  620             if (Z_OK == nRes)
  621             { // extend the buffer
  622                 v.resize(v.size() + 128);
  623                 strm.next_out = reinterpret_cast<unsigned char*>(&v[v.size() - 128]);
  624                 strm.avail_out = 128;
  625                 continue;
  626             }
  627 
  628             addNote(tr("unexpected result")); goto e2;
  629         }
  630 
  631         {
  632             int nUncomprSize (reinterpret_cast<char*>(strm.next_out) - &v[0]);
  633             v.resize(nUncomprSize + 1);
  634             v[nUncomprSize] = 0;
  635             qstrXml = &v[0];
  636         }
  637 
  638     e2:
  639         inflateEnd(&strm);
  640     }
  641     else
  642     { // it's not gzip, so perhaps it is ASCII; //ttt2 check that it's ASCII
  643         qstrXml = b;
  644     }
  645 
  646     if (qstrXml.isEmpty())
  647     {
  648         addNote(tr("empty string received"));
  649     }
  650     else
  651     {
  652         if (m_bSaveResults)
  653         {
  654             QByteArray b1 (qstrXml.toUtf8());
  655             saveDownloadedData(b1.constData(), b1.size(), "xml");
  656         }
  657     }
  658 
  659     switch (m_eWaiting)
  660     {
  661     case ALBUM:
  662         onAlbumLoaded(qstrXml);
  663         break;
  664 
  665     case SEARCH:
  666         onSearchLoaded(qstrXml);
  667         break;
  668 
  669     default:
  670         CB_ASSERT (false);
  671     }
  672 }
  673 
  674 
  675 string AlbumInfoDownloaderDlgImpl::getTempName() // time-based, with no extension; doesn't check for existing names, but uses a counter, so files shouldn't get removed (except during daylight saving time changes)
  676 {
  677 LAST_STEP("AlbumInfoDownloaderDlgImpl::getTempName");
  678     time_t t (time(0));
  679     if (t == m_nLastTime)
  680     {
  681         ++m_nLastCount;
  682     }
  683     else
  684     {
  685         m_nLastTime = t;
  686         m_nLastCount = 0;
  687     }
  688 
  689     char a [50];
  690 #ifndef WIN32
  691     ctime_r(&t, &a[0]);
  692 #else
  693     strcpy(a, ctime(&t)); //ttt2 try to get rid of ctime
  694 #endif
  695     string s;
  696     const char* p (&a[0]);
  697     for (; 0 != *p; ++p)
  698     {
  699         char c (*p);
  700         if ('\n' == c || '\r' == c) { break; }
  701         s += ':' == c ? '.' : c;
  702     }
  703     sprintf(a, ".%03d", m_nLastCount);
  704     return s;
  705 }
  706 
  707 
  708 
  709 void AlbumInfoDownloaderDlgImpl::retryNavigation()
  710 {
  711 LAST_STEP("AlbumInfoDownloaderDlgImpl::retryNavigation");
  712     if (NEXT == m_eNavigDir)
  713     {
  714         next();
  715     }
  716     else if (PREV == m_eNavigDir)
  717     {
  718         previous();
  719     }
  720     else
  721     {
  722         CB_ASSERT (false);
  723     }
  724 
  725     if (NOTHING == m_eWaiting)
  726     {
  727         resetNavigation();
  728     }
  729     // ttt2 perhaps add assert that either there are no pending requests and NOTHING==m_eWaiting or there is 1 pending request and NOTHING!=m_eWaiting (keep in mind that when the connection is opened there is a system-generated request);
  730 }
  731 
  732 
  733 void AlbumInfoDownloaderDlgImpl::onSearchLoaded(const QString& qstrXml)
  734 {
  735 LAST_STEP("AlbumInfoDownloaderDlgImpl::onSearchLoaded");
  736     addNote(tr("search results received"));
  737     QByteArray b (qstrXml.toLatin1());
  738     QBuffer bfr (&b);
  739     //SearchXmlHandler hndl (*this);
  740     auto_ptr<QXmlDefaultHandler> pHndl (getSearchXmlHandler());
  741     QXmlDefaultHandler& hndl (*pHndl);
  742     QXmlSimpleReader rdr;
  743 
  744     rdr.setContentHandler(&hndl);
  745     rdr.setErrorHandler(&hndl);
  746     QXmlInputSource src (&bfr);
  747     if (!rdr.parse(src))
  748     {
  749         showCritical(this, tr("Error"), tr("Couldn't process the search result. (Usually this means that the server is busy, so trying later might work.)"));
  750         if (0 == getAlbumCount())
  751         {
  752             m_nTotalPages = 0;
  753             m_nLastLoadedPage = -1;
  754         }
  755         resetNavigation();
  756         return;
  757     }
  758 
  759     if (0 == getAlbumCount() && m_nLastLoadedPage == m_nTotalPages - 1)
  760     {
  761         showCritical(this, tr("Error"), tr("No results found"));
  762     }
  763 
  764     retryNavigation();
  765 }
  766 
  767 
  768 void AlbumInfoDownloaderDlgImpl::onAlbumLoaded(const QString& qstrXml)
  769 {
  770 LAST_STEP("AlbumInfoDownloaderDlgImpl::onAlbumLoaded");
  771     addNote(tr("album info received"));
  772     QByteArray b (qstrXml.toLatin1());
  773     QBuffer bfr (&b);
  774     //AlbumXmlHandler hndl (album(m_nLoadingAlbum));
  775     auto_ptr<QXmlDefaultHandler> pHndl (getAlbumXmlHandler(m_nLoadingAlbum));
  776     QXmlDefaultHandler& hndl (*pHndl);
  777     QXmlSimpleReader rdr;
  778 
  779     rdr.setContentHandler(&hndl);
  780     rdr.setErrorHandler(&hndl);
  781     QXmlInputSource src (&bfr);
  782     if (!rdr.parse(src))
  783     {
  784         //CB_ASSERT (false);
  785         showCritical(this, tr("Error"), tr("Couldn't process the album information. (Usually this means that the server is busy, so trying later might work.)"));
  786         /*if (0 == getAlbumCount())
  787         {
  788             m_nTotalPages = 0;
  789             m_nLastLoadedPage = -1;
  790         }*/
  791         resetNavigation();
  792         return;
  793     }
  794     //cout << album(m_nLoadingAlbum);
  795 
  796     retryNavigation();
  797 }
  798 
  799 
  800 void AlbumInfoDownloaderDlgImpl::onImageLoaded(const QByteArray& comprImg, int nWidth, int nHeight, const QString& qstrInfo)
  801 {
  802 LAST_STEP("AlbumInfoDownloaderDlgImpl::onImageLoaded");
  803     addNote(tr("image received"));
  804     CB_ASSERT (0 == album(m_nLoadingAlbum).m_vpImages[m_nLoadingImage]);
  805     CB_ASSERT (ImageInfo::INVALID != m_eLoadingImageCompr);
  806     album(m_nLoadingAlbum).m_vpImages[m_nLoadingImage] = new ImageInfo(-1, ImageInfo::OK, m_eLoadingImageCompr, comprImg, nWidth, nHeight);
  807     album(m_nLoadingAlbum).m_vstrImageInfo[m_nLoadingImage] = convStr(qstrInfo);
  808 
  809     m_vpImages.push_back(album(m_nLoadingAlbum).m_vpImages[m_nLoadingImage]);
  810 
  811     retryNavigation();
  812 }
  813 
  814 
  815 
  816 void AlbumInfoDownloaderDlgImpl::addNote(const QString& qstrNote)
  817 {
  818 LAST_STEP("AlbumInfoDownloaderDlgImpl::addNote");
  819     QString q (m_pDownloadsM->toPlainText());
  820     if (!q.isEmpty()) { q += "\n"; }
  821     {
  822         QTime t (QTime::currentTime());
  823         char a [15];
  824         sprintf(a, "%02d:%02d:%02d.%03d ", t.hour(), t.minute(), t.second(), t.msec());
  825         q += a;
  826     }
  827     q += qstrNote;
  828     m_pDownloadsM->setText(q);
  829 
  830     QScrollBar* p (m_pDownloadsM->verticalScrollBar());
  831     if (p->isVisible())
  832     {
  833         p->setValue(p->maximum());
  834     }
  835 }
  836 
  837 
  838 
  839 void AlbumInfoDownloaderDlgImpl::setWaiting(Waiting eWaiting)
  840 {
  841 LAST_STEP("AlbumInfoDownloaderDlgImpl::setWaiting");
  842     if (NOTHING == m_eWaiting && NOTHING != eWaiting)
  843     {
  844         QApplication::setOverrideCursor(QCursor(Qt::BusyCursor));
  845     }
  846 
  847     if (NOTHING != m_eWaiting && NOTHING == eWaiting)
  848     {
  849         QApplication::restoreOverrideCursor();
  850     }
  851 
  852     m_eWaiting = eWaiting;
  853 }
  854 
  855 
  856 void AlbumInfoDownloaderDlgImpl::reloadGui()
  857 {
  858 LAST_STEP("AlbumInfoDownloaderDlgImpl::reloadGui");
  859     resetNavigation();
  860 
  861     if (0 == getAlbumCount()) { return; }
  862     CB_ASSERT (m_nCrtAlbum >= 0 && m_nCrtAlbum < getAlbumCount());
  863 
  864     //const MusicBrainzAlbumInfo& album (m_vAlbums[m_nCrtAlbum]);
  865     const WebAlbumInfoBase& albumInfo (album(m_nCrtAlbum));
  866     m_pFormatE->setText(convStr(albumInfo.m_strFormat));
  867     m_pRealeasedE->setText(convStr(albumInfo.m_strReleased));
  868     //m_pTrackListL->clear();
  869     updateTrackList();
  870     set<string> sstrPrefixes;
  871     for (int i = 0, n = cSize(albumInfo.m_vTracks); i < n; ++i)
  872     {
  873         const TrackInfo& trk (albumInfo.m_vTracks[i]);
  874 
  875         const string& s1 (trk.m_strPos);
  876         string::size_type k (s1.find_last_not_of("0123456789"));
  877         if (string::npos != k && s1.size() - 1 != k)
  878         {
  879             sstrPrefixes.insert(s1.substr(0, k + 1));
  880         }
  881     }
  882 
  883     m_pVolumeCbB->clear();
  884     if (sstrPrefixes.empty() || sstrPrefixes.size() == albumInfo.m_vTracks.size())
  885     {
  886         m_pVolumeCbB->setEnabled(false);
  887         m_pVolumeL->setEnabled(false);
  888     }
  889     else
  890     {
  891         m_pVolumeCbB->setEnabled(true);
  892         m_pVolumeL->setEnabled(true);
  893         for (set<string>::iterator it = sstrPrefixes.begin(); it != sstrPrefixes.end(); ++it)
  894         {
  895             m_pVolumeCbB->addItem(convStr(*it));
  896         }
  897         m_pVolumeCbB->addItem(tr("<All>"));
  898     }
  899 
  900 
  901     QString q1 (m_nTotalPages == m_nLastLoadedPage + 1 ? "" : "+");
  902     QString s (tr("Album %1/%2%3, image %4/%5").arg(m_nCrtAlbum + 1).arg(getAlbumCount()).arg(q1).arg(m_nCrtImage + 1).arg(albumInfo.m_vpImages.size()));
  903     m_pResultNoL->setText(s);
  904 
  905     m_pResArtistE->setText(convStr(albumInfo.m_strArtist));
  906     m_pResAlbumE->setText(convStr(albumInfo.m_strTitle));
  907 
  908     if (-1 == m_nCrtImage || 0 == albumInfo.m_vpImages[m_nCrtImage])
  909     {
  910         m_pImageL->setPixmap(QPixmap());
  911         m_pImageL->setText("");
  912         m_pImgSizeL->setText(tr("No image\n"));
  913     }
  914     else
  915     {
  916         m_pImageL->setPixmap(QPixmap::fromImage(albumInfo.m_vpImages[m_nCrtImage]->getImage(m_pImageL->width(), m_pImageL->height())));
  917         m_pImgSizeL->setText(convStr(albumInfo.m_vstrImageInfo[m_nCrtImage]));
  918     }
  919 }
  920 
  921 
  922 /*override*/ void AlbumInfoDownloaderDlgImpl::updateTrackList()
  923 {
  924 LAST_STEP("AlbumInfoDownloaderDlgImpl::updateTrackList");
  925     m_pModel->emitLayoutChanged();
  926 
  927     SimpleQTableViewWidthInterface intf (*m_pTrackListG);
  928     ColumnResizer rsz (intf, 100, ColumnResizer::DONT_FILL, ColumnResizer::CONSISTENT_RESULTS);
  929 }
  930 
  931 
  932 
  933 /*static*/ string AlbumInfoDownloaderDlgImpl::removeParentheses(const string& s)
  934 {
  935     string r;
  936     int k1 (0), k2 (0), k3 (0), k4 (0);
  937     for (int i = 0, n = cSize(s); i < n; ++i)
  938     {
  939         char c (s[i]);
  940         if ('(' == c)
  941         {
  942             ++k1;
  943         }
  944         if ('[' == c)
  945         {
  946             ++k2;
  947         }
  948         if ('{' == c)
  949         {
  950             ++k3;
  951         }
  952         if ('<' == c)
  953         {
  954             ++k3;
  955         }
  956         if (0 == k1 && 0 == k2 && 0 == k3 && 0 == k4)
  957         {
  958             r += c;
  959         }
  960         if (')' == c)
  961         {
  962             --k1;
  963         }
  964         if (']' == c)
  965         {
  966             --k2;
  967         }
  968         if ('}' == c)
  969         {
  970             --k3;
  971         }
  972         if ('>' == c)
  973         {
  974             --k4;
  975         }
  976     }
  977     trim(r);
  978     return r;
  979 }
  980 
  981 
  982 void AlbumInfoDownloaderDlgImpl::saveDownloadedData(const char* p, int nSize, const char* szExt)
  983 {
  984 LAST_STEP("AlbumInfoDownloaderDlgImpl::saveDownloadedData");
  985     string s (getTempName() + "." + szExt);
  986     ofstream_utf8 out (s.c_str(), ios::binary);
  987     out.write(p, nSize);
  988 }
  989 
  990 
  991 void AlbumInfoDownloaderDlgImpl::onHelp()
  992 {
  993     openHelp("200_discogs_query.html");
  994 }
  995 
  996 
  997 
  998 
  999 //==========================================================================================================================
 1000 //==========================================================================================================================
 1001 //==========================================================================================================================
 1002 
 1003 
 1004 
 1005 void addIfMissing(string& strDest, const string& strSrc)
 1006 {
 1007     if (strDest.empty())
 1008     {
 1009         strDest = strSrc;
 1010     }
 1011     else
 1012     {
 1013         if (string::npos == strDest.find(strSrc))
 1014         {
 1015             strDest += ", " + strSrc;
 1016         }
 1017     }
 1018 }
 1019 
 1020 // splits a string based on a separator, putting the components in a vector; trims the substrings; discards empty components;
 1021 void split(const string& s, const string& sep, vector<string>& v)
 1022 {
 1023     v.clear();
 1024     string::size_type j (0), k;
 1025     int n (cSize(s));
 1026     do
 1027     {
 1028         k = s.find(sep, j);
 1029         if (string::npos == k)
 1030         {
 1031             k = n;
 1032         }
 1033         string s1 (s.substr(j, k - j));
 1034         trim(s1);
 1035         if (!s1.empty())
 1036         {
 1037             v.push_back(s1);
 1038         }
 1039         j = k + 1;
 1040     }
 1041     while ((int)k < n);
 1042 }
 1043 
 1044 
 1045 void addList(string& strDest, const string& strSrc)
 1046 {
 1047     if (strDest.empty())
 1048     {
 1049         strDest = strSrc;
 1050     }
 1051     else
 1052     {
 1053         vector<string> v;
 1054         split (strSrc, ",", v);
 1055         for (int i = 0, n = cSize(v); i < n; ++i)
 1056         {
 1057             addIfMissing(strDest, v[i]);
 1058         }
 1059     }
 1060 }
 1061 
 1062 
 1063 //====================================================================================================================
 1064 //====================================================================================================================
 1065 //====================================================================================================================
 1066 
 1067 
 1068 WebDwnldModel::WebDwnldModel(AlbumInfoDownloaderDlgImpl& dwnld, QTableView& grid) : QAbstractTableModel(&dwnld), m_dwnld(dwnld), m_grid(grid)
 1069 {
 1070 }
 1071 
 1072 
 1073 /*override*/ int WebDwnldModel::rowCount(const QModelIndex&) const
 1074 {
 1075     const WebAlbumInfoBase* p (m_dwnld.getCrtAlbum());
 1076     if (0 == p) { return 0; }
 1077     return cSize(p->m_vTracks);
 1078 }
 1079 
 1080 /*override*/ int WebDwnldModel::columnCount(const QModelIndex&) const
 1081 {
 1082     return m_dwnld.getColumnCount();
 1083 }
 1084 
 1085 
 1086 /*override*/ QVariant WebDwnldModel::data(const QModelIndex& index, int nRole) const
 1087 {
 1088 //LAST_STEP("WebDwnldModel::data()");
 1089     if (!index.isValid()) { return QVariant(); }
 1090     if (nRole != Qt::DisplayRole && nRole != Qt::ToolTipRole) { return QVariant(); }
 1091     int i (index.row()), j (index.column());
 1092 
 1093     //if (nRole == Qt::CheckStateRole && j == 2) { return Qt::Checked; }
 1094 
 1095     const WebAlbumInfoBase* p (m_dwnld.getCrtAlbum());
 1096     if (0 == p || i >= cSize(p->m_vTracks))
 1097     {
 1098         if (nRole == Qt::ToolTipRole) { return ""; }
 1099         return QVariant();
 1100     }
 1101 
 1102     const TrackInfo& t (p->m_vTracks[i]);
 1103     string s;
 1104     switch (j)
 1105     {
 1106     case 0: s = t.m_strPos; break;
 1107     case 1: s = t.m_strTitle; break;
 1108     case 2: s = t.m_strArtist; break;
 1109     case 3: s = t.m_strComposer; break;
 1110     default:
 1111         CB_ASSERT (false);
 1112     }
 1113     QString qs (convStr(s));
 1114 
 1115     if (nRole == Qt::ToolTipRole)
 1116     {
 1117         QFontMetrics fm (m_grid.fontMetrics()); // !!! no need to use "QApplication::fontMetrics()"
 1118         int nWidth (fm.width(qs));
 1119 
 1120         if (nWidth + 10 < m_grid.horizontalHeader()->sectionSize(j)) // ttt2 "10" is hard-coded
 1121         {
 1122             //return QVariant();
 1123             return ""; // !!! with "return QVariant()" the previous tooltip remains until the cursor moves over another cell that has a tooltip
 1124         }//*/
 1125 
 1126         return qs;
 1127     }
 1128 
 1129     return qs;
 1130 }
 1131 
 1132 
 1133 /*override*/ QVariant WebDwnldModel::headerData(int nSection, Qt::Orientation eOrientation, int nRole /* = Qt::DisplayRole*/) const
 1134 {
 1135     if (nRole != Qt::DisplayRole) { return QVariant(); }
 1136     if (Qt::Horizontal == eOrientation)
 1137     {
 1138         switch (nSection)
 1139         {
 1140         case 0: return tr("Pos");
 1141         case 1: return tr("Title");
 1142         case 2: return tr("Artist");
 1143         case 3: return tr("Composer");
 1144         default:
 1145             CB_ASSERT (false);
 1146         }
 1147     }
 1148 
 1149     return nSection + 1;
 1150 }
 1151 
 1152