Ticket #3998: mythflix_browseTop.cpp

File mythflix_browseTop.cpp, 22.1 KB (added by Britney Fransen <britney.fransen@…>, 12 years ago)
Line 
1/* ============================================================
2 * File  : mythflix.cpp
3 * Author: John Petrocik <john@petrocik.net>
4 * Date  : 2005-10-28
5 * Description :
6 *
7 * Copyright 2005 by John Petrocik
8
9 * This program is free software; you can redistribute it
10 * and/or modify it under the terms of the GNU General
11 * Public License as published bythe Free Software Foundation;
12 * either version 2, or (at your option)
13 * any later version.
14 *
15 * This program is distributed in the hope that it will be useful,
16 * but WITHOUT ANY WARRANTY; without even the implied warranty of
17 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
18 * GNU General Public License for more details.
19 *
20 * ============================================================ */
21
22#include <iostream>
23
24#include <unistd.h>
25
26#include <qnetwork.h>
27#include <qapplication.h>
28#include <qdatetime.h>
29#include <qpainter.h>
30#include <qdir.h>
31#include <qtimer.h>
32#include <qregexp.h>
33#include <qprocess.h>
34
35#include <qurl.h>
36#include "mythtv/mythcontext.h"
37#include "mythtv/mythdbcon.h"
38#include "mythtv/httpcomms.h"
39
40#include "mythflix.h"
41
42MythFlix::MythFlix(MythMainWindow *parent, const char *name )
43    : MythDialog(parent, name)
44{
45    qInitNetworkProtocols ();
46
47    // Setup cache directory
48
49    QString fileprefix = MythContext::GetConfDir();
50
51    QDir dir(fileprefix);
52    if (!dir.exists())
53        dir.mkdir(fileprefix);
54
55    fileprefix += "/MythFlix";
56    dir = QDir(fileprefix);
57    if (!dir.exists())
58        dir.mkdir(fileprefix);
59   
60    // Initialize variables
61        zoom = QString("-z %1").arg(gContext->GetNumSetting("WebBrowserZoomLevel",200));
62    browser = gContext->GetSetting("WebBrowserCommand",
63                                   gContext->GetInstallPrefix() +
64                                      "/bin/mythbrowser");
65        expectingPopup = false;
66    m_InColumn     = 0;
67    m_UISites      = 0;
68    m_UIArticles   = 0;
69
70    setNoErase();
71    loadTheme();
72
73    // Load sites from database
74
75    MSqlQuery query(MSqlQuery::InitCon());
76    query.exec("SELECT name, url, updated FROM netflix WHERE is_queue=0 ORDER BY name");
77
78    if (!query.isActive()) {
79        VERBOSE(VB_IMPORTANT, QString("MythFlix: Error in loading sites from DB"));
80    }
81    else {
82        QString name;
83        QString url;
84        QDateTime time;
85        while ( query.next() ) {
86            name = QString::fromUtf8(query.value(0).toString());
87            url  = QString::fromUtf8(query.value(1).toString());
88            time.setTime_t(query.value(2).toUInt());
89            m_NewsSites.append(new NewsSite(name,url,time));
90        }
91    }
92
93    for (NewsSite *site = m_NewsSites.first(); site; site = m_NewsSites.next())
94    {
95        UIListBtnTypeItem* item =
96            new UIListBtnTypeItem(m_UISites, site->name());
97        item->setData(site);
98    }
99
100   
101    NewsSite* site = (NewsSite*) m_NewsSites.first();
102    connect(site, SIGNAL(finished(NewsSite*)),
103            this, SLOT(slotNewsRetrieved(NewsSite*)));
104   
105    slotRetrieveNews();
106}
107
108MythFlix::~MythFlix()
109{
110    delete m_Theme;
111}
112
113void MythFlix::loadTheme()
114{
115    m_Theme = new XMLParse();
116    m_Theme->SetWMult(wmult);
117    m_Theme->SetHMult(hmult);
118
119    QDomElement xmldata;
120    m_Theme->LoadTheme(xmldata, "browse", "netflix-");
121
122    for (QDomNode child = xmldata.firstChild(); !child.isNull();
123         child = child.nextSibling()) {
124       
125        QDomElement e = child.toElement();
126        if (!e.isNull()) {
127
128            if (e.tagName() == "font") {
129                m_Theme->parseFont(e);
130            }
131            else if (e.tagName() == "container") {
132                QRect area;
133                QString name;
134                int context;
135                m_Theme->parseContainer(e, name, context, area);
136
137                if (name.lower() == "sites")
138                    m_SitesRect = area;
139                else if (name.lower() == "articles")
140                    m_ArticlesRect = area;
141                else if (name.lower() == "info")
142                    m_InfoRect = area;
143            }
144            else {
145                VERBOSE(VB_IMPORTANT, QString("MythFlix: Unknown element: %1").arg(e.tagName()));
146                exit(-1);
147            }
148        }
149    }
150
151    LayerSet *container = m_Theme->GetSet("sites");
152    if (!container) {
153        VERBOSE(VB_IMPORTANT, QString("MythFlix: Failed to get sites container."));
154        exit(-1);
155    }
156       
157    m_UISites = (UIListBtnType*)container->GetType("siteslist");
158    if (!m_UISites) {
159        VERBOSE(VB_IMPORTANT, QString("MythFlix: Failed to get list area."));
160        exit(-1);
161    }
162       
163    connect(m_UISites, SIGNAL(itemSelected(UIListBtnTypeItem*)),
164            SLOT(slotSiteSelected(UIListBtnTypeItem*)));
165
166    container = m_Theme->GetSet("articles");
167    if (!container) {
168        VERBOSE(VB_IMPORTANT, QString("MythFlix: Failed to get articles container."));
169        exit(-1);
170    }
171
172    m_UIArticles = (UIListBtnType*)container->GetType("articleslist");
173    if (!m_UIArticles) {
174        VERBOSE(VB_IMPORTANT, QString("MythFlix: Failed to get articles list area."));
175        exit(-1);
176    }
177   
178    connect(m_UIArticles, SIGNAL(itemSelected(UIListBtnTypeItem*)),
179            SLOT(slotArticleSelected(UIListBtnTypeItem*)));
180   
181    m_UISites->SetActive(true);
182    m_UIArticles->SetActive(false);
183}
184
185
186void MythFlix::paintEvent(QPaintEvent *e)
187{
188    QRect r = e->rect();
189
190    if (r.intersects(m_SitesRect))
191        updateSitesView();
192    if (r.intersects(m_ArticlesRect))
193        updateArticlesView();
194    if (r.intersects(m_InfoRect))
195        updateInfoView();
196}
197
198
199void MythFlix::updateSitesView()
200{
201    QPixmap pix(m_SitesRect.size());
202    pix.fill(this, m_SitesRect.topLeft());
203    QPainter p(&pix);
204
205    LayerSet* container = m_Theme->GetSet("sites");
206    if (container) {
207        container->Draw(&p, 0, 0);
208        container->Draw(&p, 1, 0);
209        container->Draw(&p, 2, 0);
210        container->Draw(&p, 3, 0);
211        container->Draw(&p, 4, 0);
212        container->Draw(&p, 5, 0);
213        container->Draw(&p, 6, 0);
214        container->Draw(&p, 7, 0);
215        container->Draw(&p, 8, 0);
216    }
217    p.end();
218
219    bitBlt(this, m_SitesRect.left(), m_SitesRect.top(),
220           &pix, 0, 0, -1, -1, Qt::CopyROP);
221}
222
223void MythFlix::updateArticlesView()
224{
225    QPixmap pix(m_ArticlesRect.size());
226    pix.fill(this, m_ArticlesRect.topLeft());
227    QPainter p(&pix);
228
229    LayerSet* container = m_Theme->GetSet("articles");
230    if (container) {
231        container->Draw(&p, 0, 0);
232        container->Draw(&p, 1, 0);
233        container->Draw(&p, 2, 0);
234        container->Draw(&p, 3, 0);
235        container->Draw(&p, 4, 0);
236        container->Draw(&p, 5, 0);
237        container->Draw(&p, 6, 0);
238        container->Draw(&p, 7, 0);
239        container->Draw(&p, 8, 0);
240    }
241    p.end();
242
243    bitBlt(this, m_ArticlesRect.left(), m_ArticlesRect.top(),
244           &pix, 0, 0, -1, -1, Qt::CopyROP);
245}
246
247void MythFlix::updateInfoView()
248{
249    QPixmap pix(m_InfoRect.size());
250    pix.fill(this, m_InfoRect.topLeft());
251    QPainter p(&pix);
252
253    LayerSet* container = m_Theme->GetSet("info");
254    if (container)
255    {
256        NewsSite    *site     = 0;
257        NewsArticle *article  = 0;
258
259        UIListBtnTypeItem *siteUIItem = m_UISites->GetItemCurrent();
260        if (siteUIItem && siteUIItem->getData())
261            site = (NewsSite*) siteUIItem->getData();
262       
263        UIListBtnTypeItem *articleUIItem = m_UIArticles->GetItemCurrent();
264        if (articleUIItem && articleUIItem->getData())
265            article = (NewsArticle*) articleUIItem->getData();
266       
267        if (m_InColumn == 1) {
268
269            if (article)
270            {
271
272                UITextType *ttype =
273                    (UITextType *)container->GetType("status");
274//                if (ttype)
275//                    ttype->SetText("");
276
277                ttype =
278                    (UITextType *)container->GetType("title");
279                if (ttype)
280                    ttype->SetText(article->title());
281
282                ttype =
283                    (UITextType *)container->GetType("description");
284                if (ttype)
285                    ttype->SetText(article->description());
286
287                // removes html tags
288                {
289                    QString artText = article->description();
290                    // Replace paragraph and break HTML with newlines
291                    if( artText.find(QRegExp("</(p|P)>")) )
292                    {
293                        artText.replace( QRegExp("<(p|P)>"), "");
294                        artText.replace( QRegExp("</(p|P)>"), "\n\n");
295                    }
296                    else
297                    {
298                        artText.replace( QRegExp("<(p|P)>"), "\n\n");
299                        artText.replace( QRegExp("</(p|P)>"), "");
300                    }                         
301                    artText.replace( QRegExp("<(br|BR|)/>"), "\n");
302                    artText.replace( QRegExp("<(br|BR|)>"), "\n");
303                    // These are done instead of simplifyWhitespace
304                    // because that function also strips out newlines
305                    // Replace tab characters with nothing
306                    artText.replace( QRegExp("\t"), "");
307                    // Replace double space with single
308                    artText.replace( QRegExp("  "), "");
309                    // Replace whitespace at beginning of lines with newline
310                    artText.replace( QRegExp("\n "), "\n");
311                    // Remove any remaining HTML tags
312                    QRegExp removeHTML(QRegExp("</?.+>"));
313                    removeHTML.setMinimal(true);
314                    artText.remove((const QRegExp&) removeHTML);
315                    artText = artText.stripWhiteSpace();
316                    ttype->SetText(artText);
317                } 
318
319                QString imageLoc = article->articleURL();
320                int length = imageLoc.length();
321                int index = imageLoc.findRev("/");
322                imageLoc = imageLoc.mid(index,length) + ".jpg";
323
324                QString fileprefix = MythContext::GetConfDir();
325               
326                QDir dir(fileprefix);
327                if (!dir.exists())
328                    dir.mkdir(fileprefix);
329           
330                fileprefix += "/MythFlix";
331           
332                dir = QDir(fileprefix);
333                if (!dir.exists())
334                    dir.mkdir(fileprefix);
335           
336                VERBOSE(VB_FILE, QString("MythFlix: Boxshot File Prefix: %1").arg(fileprefix));
337
338                QString sFilename(fileprefix + "/" + imageLoc);
339           
340                bool exists = QFile::exists(sFilename);
341                if (!exists)
342                {
343                    VERBOSE(VB_NETWORK, QString("MythFlix: Copying boxshot file from server (%1)").arg(imageLoc));
344                   
345                    QString sURL("http://cdn.nflximg.com/us/boxshots/large/" + imageLoc);
346               
347                    if (!HttpComms::getHttpFile(sFilename, sURL, 20000))
348                        VERBOSE(VB_NETWORK, QString("MythFlix: Failed to download image from: %1").arg(sURL));
349               
350                    VERBOSE(VB_NETWORK, QString("MythFlix: Finished copying boxshot file from server (%1)").arg(imageLoc));
351                }
352
353                UIImageType *itype = (UIImageType *)container->GetType("boxshot");
354                if (itype)
355                {
356                   itype->SetImage(sFilename);
357                   itype->LoadImage();
358       
359                   if (itype->isHidden())
360                       itype->show();   
361                }
362
363            }
364        }
365        else {
366
367            if (site)
368            {
369                UITextType *ttype =
370                    (UITextType *)container->GetType("status");
371//                if (ttype)
372//                    ttype->SetText("");
373
374                ttype =
375                    (UITextType *)container->GetType("title");
376                if (ttype)
377                    ttype->SetText(site->name());
378
379                ttype =
380                    (UITextType *)container->GetType("description");
381                if (ttype)
382                    ttype->SetText(site->description());
383
384                UIImageType *itype = (UIImageType *)container->GetType("boxshot");
385                if (itype)
386                {
387                    itype->hide();   
388                }
389
390            }
391        }
392
393        container->Draw(&p, 0, 0);
394        container->Draw(&p, 1, 0);
395        container->Draw(&p, 2, 0);
396        container->Draw(&p, 3, 0);
397        container->Draw(&p, 4, 0);
398        container->Draw(&p, 5, 0);
399        container->Draw(&p, 6, 0);
400        container->Draw(&p, 7, 0);
401        container->Draw(&p, 8, 0);
402    }
403
404    p.end();
405
406
407    bitBlt(this, m_InfoRect.left(), m_InfoRect.top(),
408           &pix, 0, 0, -1, -1, Qt::CopyROP);
409}
410
411void MythFlix::keyPressEvent(QKeyEvent *e)
412{
413    if (!e) return;
414
415    bool handled = false;
416    QStringList actions;
417    gContext->GetMainWindow()->TranslateKeyPress("NetFlix", e, actions);
418   
419    for (unsigned int i = 0; i < actions.size() && !handled; i++)
420    {
421        QString action = actions[i];
422        handled = true;
423
424        if (action == "UP")
425            cursorUp();
426        else if (action == "PAGEUP")
427             cursorUp(true);
428        else if (action == "DOWN")
429            cursorDown();
430        else if (action == "PAGEDOWN")
431             cursorDown(true);
432        else if (action == "LEFT")
433            cursorLeft();
434        else if (action == "RIGHT")
435            cursorRight();
436        else if(action == "SELECT")
437            displayOptions();
438        else
439            handled = false;
440    }
441
442    if (!handled)
443        MythDialog::keyPressEvent(e);
444}
445
446void MythFlix::cursorUp(bool page)
447{
448    UIListBtnType::MovementUnit unit = page ? UIListBtnType::MovePage : UIListBtnType::MoveItem;
449
450    if (m_InColumn == 0) {
451        m_UISites->MoveUp(unit);
452    }
453    else {
454        m_UIArticles->MoveUp(unit);
455    }
456}
457
458void MythFlix::cursorDown(bool page)
459{
460    UIListBtnType::MovementUnit unit = page ? UIListBtnType::MovePage : UIListBtnType::MoveItem;
461
462    if (m_InColumn == 0) {
463        m_UISites->MoveDown(unit);
464    }
465    else {
466        m_UIArticles->MoveDown(unit);
467    }
468}
469
470void MythFlix::cursorRight()
471{
472    if (m_InColumn == 1)
473    {
474        slotViewArticle();
475        return;
476    }
477
478    m_InColumn++;
479
480    m_UISites->SetActive(false);
481    m_UIArticles->SetActive(true);
482
483    update(m_SitesRect);
484    update(m_ArticlesRect);
485    update(m_InfoRect);
486}
487
488void MythFlix::cursorLeft()
489{
490    if (m_InColumn == 0)
491    {
492        accept();
493        return;
494    }
495
496    m_InColumn--;
497
498    m_UISites->SetActive(true);
499    m_UIArticles->SetActive(false);
500
501    update(m_SitesRect);
502    update(m_ArticlesRect);
503    update(m_InfoRect);
504}
505
506void MythFlix::slotRetrieveNews()
507{
508    if (m_NewsSites.count() == 0)
509        return;
510
511    for (NewsSite* site = m_NewsSites.first(); site; site = m_NewsSites.next())
512    {
513        site->retrieve();
514    }
515
516}
517
518void MythFlix::slotNewsRetrieved(NewsSite* site)
519{
520    processAndShowNews(site);
521}
522
523void MythFlix::processAndShowNews(NewsSite* site)
524{
525    if (!site)
526        return;
527
528    site->process();
529
530    UIListBtnTypeItem *siteUIItem = m_UISites->GetItemCurrent();
531    if (!siteUIItem || !siteUIItem->getData())
532        return;
533   
534    if (site == (NewsSite*) siteUIItem->getData()) {
535
536        m_UIArticles->Reset();
537
538        for (NewsArticle* article = site->articleList().first(); article;
539             article = site->articleList().next()) {
540            UIListBtnTypeItem* item =
541                new UIListBtnTypeItem(m_UIArticles, article->title());
542            item->setData(article);
543        }
544
545        update(m_ArticlesRect);
546        update(m_InfoRect);
547    }
548}
549
550void MythFlix::slotShowNetFlixPage()
551{
552        if (expectingPopup)
553        slotCancelPopup();
554   
555    UIListBtnTypeItem *articleUIItem = m_UIArticles->GetItemCurrent();
556        if (articleUIItem && articleUIItem->getData())
557    {
558        NewsArticle *article = (NewsArticle*) articleUIItem->getData();
559        if(article)
560        {
561                        QString cmdUrl(article->articleURL());
562                        cmdUrl.replace('\'', "%27");
563       
564                        QString cmd = QString("%1 %2 '%3'")
565                                 .arg(browser)
566                                 .arg(zoom)
567                                 .arg(cmdUrl);
568                        VERBOSE(VB_GENERAL, QString("MythFlixBrowse: Opening Neflix site: (%1)").arg(cmd));
569                        myth_system(cmd);
570        }
571    }
572}
573
574void MythFlix::slotSiteSelected(UIListBtnTypeItem *item)
575{
576    if (!item || !item->getData())
577        return;
578   
579    processAndShowNews((NewsSite*) item->getData());
580}
581
582void MythFlix::slotArticleSelected(UIListBtnTypeItem*)
583{
584    update(m_ArticlesRect);
585    update(m_InfoRect);
586}
587
588void MythFlix::slotViewArticle()
589{
590    UIListBtnTypeItem *articleUIItem = m_UIArticles->GetItemCurrent();
591
592    if (articleUIItem && articleUIItem->getData())
593    {
594        NewsArticle *article = (NewsArticle*) articleUIItem->getData();
595        if(article)
596        {
597
598            QStringList args = QStringList::split(' ',
599                    gContext->GetSetting("NetFlixAddQueueCommandLine",
600                    gContext->GetShareDir() + "mythflix/scripts/netflix.pl -A"));
601
602            QString movieID(article->articleURL());
603            int length = movieID.length();
604            int index = movieID.findRev("/");
605            movieID = movieID.mid(index+1,length);
606            args += movieID;
607               
608                        // execute external command to obtain list of possible movie matches
609                        QString results = executeExternal(args, "Add Movie");
610               
611        }
612    }
613}
614
615void MythFlix::slotViewArticleTop()
616{
617        if (expectingPopup)
618        slotCancelPopup();
619       
620    UIListBtnTypeItem *articleUIItem = m_UIArticles->GetItemCurrent();
621
622    if (articleUIItem && articleUIItem->getData())
623    {
624        NewsArticle *article = (NewsArticle*) articleUIItem->getData();
625        if(article)
626        {
627
628            QStringList args = QStringList::split(' ',
629                    gContext->GetSetting("NetFlixAddQueueCommandLine",
630                    gContext->GetShareDir() + "mythflix/scripts/netflix.pl -A"));
631
632            QString movieID(article->articleURL());
633            int length = movieID.length();
634            int index = movieID.findRev("/");
635            movieID = movieID.mid(index+1,length);
636            args += movieID;
637               
638            // execute external command to obtain list of possible movie matches
639            QString results = executeExternal(args, "Add Movie");
640 
641            // Move to top of queue as well
642            args = QStringList::split(' ',
643                    gContext->GetSetting("NetFlixMoveToTopCommandLine",
644                    gContext->GetShareDir() + "mythflix/scripts/netflix.pl -1"));
645            args += movieID;
646            results = executeExternal(args, "Move To Top");     
647        }
648    }
649}
650
651void MythFlix::displayOptions()
652{
653
654    popup = new MythPopupBox(gContext->GetMainWindow(), "menu popup");
655
656    QLabel *label = popup->addLabel(tr("Browse Options"),
657                                  MythPopupBox::Large, false);
658    label->setAlignment(Qt::AlignCenter | Qt::WordBreak);
659
660    QButton *topButton = popup->addButton(tr("Add to Top of Queue"), this,
661                     SLOT(slotViewArticleTop()));
662    popup->addButton(tr("Add to Bottom of Queue"), this, SLOT(slotViewArticle()));
663                     
664    popup->addButton(tr("Show NetFlix Page"), this, SLOT(slotShowNetFlixPage()));
665
666    popup->addButton(tr("Cancel"), this, SLOT(slotCancelPopup()));
667
668    popup->ShowPopup(this, SLOT(slotCancelPopup()));
669
670    topButton->setFocus();
671
672    expectingPopup = true;
673
674}
675
676void MythFlix::slotCancelPopup(void)
677{
678    popup->hide();
679    expectingPopup = false;
680
681    delete popup;
682    popup = NULL;
683
684    setActiveWindow();
685}
686
687
688// Execute an external command and return results in string
689//   probably should make this routing async vs polling like this
690//   but it would require a lot more code restructuring
691QString MythFlix::executeExternal(const QStringList& args, const QString& purpose)
692{
693    QString ret = "";
694    QString err = "";
695
696    VERBOSE(VB_GENERAL, QString("%1: Executing '%2'").arg(purpose).
697                      arg(args.join(" ")).local8Bit() );
698    QProcess proc(args, this);
699
700    QString cmd = args[0];
701    QFileInfo info(cmd);
702   
703    if (!info.exists())
704    {
705       err = QString("\"%1\" failed: does not exist").arg(cmd.local8Bit());
706    }
707    else if (!info.isExecutable())
708    {
709       err = QString("\"%1\" failed: not executable").arg(cmd.local8Bit());
710    }
711    else if (proc.start())
712    {
713        while (true)
714        {
715            while (proc.canReadLineStdout() || proc.canReadLineStderr())
716            {
717                if (proc.canReadLineStdout())
718                {
719                    ret += QString::fromLocal8Bit(proc.readLineStdout(),-1) + "\n";
720                }
721             
722                if (proc.canReadLineStderr())
723                {
724                    if (err == "")
725                    {
726                        err = cmd + ": ";
727                    }                   
728                 
729                    err += QString::fromLocal8Bit(proc.readLineStderr(),-1) + "\n";
730                }
731            }
732           
733            if (proc.isRunning())
734            {
735                qApp->processEvents();
736                usleep(10000);
737            }
738            else
739            {
740                if (!proc.normalExit())
741                {
742                    err = QString("\"%1\" failed: Process exited abnormally")
743                                  .arg(cmd.local8Bit());
744                }
745               
746                break;
747            }
748        }
749    }
750    else
751    {
752        err = QString("\"%1\" failed: Could not start process")
753                      .arg(cmd.local8Bit());
754    }
755
756    while (proc.canReadLineStdout() || proc.canReadLineStderr())
757    {
758        if (proc.canReadLineStdout())
759        {
760            ret += QString::fromLocal8Bit(proc.readLineStdout(),-1) + "\n";
761        }
762       
763        if (proc.canReadLineStderr())
764        {
765            if (err == "")
766            {
767                err = cmd + ": ";
768            }               
769           
770            err += QString::fromLocal8Bit(proc.readLineStderr(), -1) + "\n";
771        }
772    }
773
774    if (err != "")
775    {
776        QString tempPurpose(purpose);
777       
778        if (tempPurpose == "")
779            tempPurpose = "Command";
780
781        cerr << err << endl;
782        MythPopupBox::showOkPopup(gContext->GetMainWindow(),
783        QObject::tr(tempPurpose + " failed"), QObject::tr(err + "\n\nCheck NetFlix Settings"));
784        ret = "#ERROR";
785    }
786   
787    VERBOSE(VB_IMPORTANT, ret);
788    return ret;
789}
790
791