2 * abby Copyright (C) 2009 Toni Gundogdu.
3 * This file is part of abby.
5 * abby 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 3 of the License, or
8 * (at your option) any later version.
10 * abby 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.
15 * You should have received a copy of the GNU General Public License
16 * along with this program. If not, see <http://www.gnu.org/licenses/>.
18 #include <QMainWindow>
20 #include <QCloseEvent>
21 #include <QMessageBox>
22 #include <QFileDialog>
25 #include <QInputDialog>
26 #include <QNetworkProxy>
34 #include "formatdlg.h"
37 MainWindow::MainWindow():
41 The word "English" is not meant to be translated literally.
42 Replace the word "English" with target language, e.g. Suomi
43 or Deutsch. Note that the only purpose of this string is to
44 list it as an available language in the Preferences dialog.
46 const QString lang
= tr("English");
51 connect(&process
, SIGNAL(started()),
52 this, SLOT(onProcStarted()));
53 connect(&process
, SIGNAL(error(QProcess::ProcessError
)),
54 this, SLOT(onProcError(QProcess::ProcessError
)));
56 // NOTE: We read both channels stdout and stderr.
57 connect(&process
, SIGNAL(readyReadStandardOutput()),
58 this, SLOT(onProcStdoutReady()));
59 connect(&process
, SIGNAL(readyReadStandardError()),
60 this, SLOT(onProcStdoutReady()));
62 connect(&process
, SIGNAL(finished(int,QProcess::ExitStatus
)),
63 this, SLOT(onProcFinished(int,QProcess::ExitStatus
)));
65 prefs
= new PreferencesDialog(this);
66 rss
= new RSSDialog(this);
67 scan
= new ScanDialog(this);
68 format
= new FormatDialog(this);
72 parseCcliveHostsOutput();
75 scanButton
->setVisible(false);
79 MainWindow::checkCclivePath() {
80 if (prefs
->ccliveEdit
->text().isEmpty()) {
81 QMessageBox::information(this, QCoreApplication::applicationName(),
82 tr("abby requires either `clive' or `cclive'.\n"
83 "Please define a path to either command."));
91 MainWindow::isCclive(QString
& output
) {
92 QString path
= prefs
->ccliveEdit
->text();
98 process
.setProcessChannelMode(QProcess::MergedChannels
);
99 process
.start(path
, QStringList() << "--version");
102 if (!process
.waitForFinished())
103 qDebug() << path
<< ": " << process
.errorString();
105 output
= QString::fromLocal8Bit(process
.readAll());
106 QStringList lst
= output
.split(" ", QString::SkipEmptyParts
);
107 state
= lst
[0] == "cclive";
108 ccliveVersion
= lst
[2];
109 curlVersion
= lst
[6];
115 MainWindow::parseCcliveHostsOutput() {
116 QString path
= prefs
->ccliveEdit
->text();
122 proc
.setProcessChannelMode(QProcess::MergedChannels
);
123 proc
.start(path
, QStringList() << "--hosts");
125 if (!proc
.waitForFinished())
126 qDebug() << path
<< ": " << proc
.errorString();
129 QString output
= QString::fromLocal8Bit(proc
.readAll());
130 hostsOutput
= output
.split("\n", QString::SkipEmptyParts
);
135 MainWindow::ccliveSupportsFeature(const QString
& buildOption
) {
137 const bool _isCclive
= isCclive(output
);
139 if (!_isCclive
&& buildOption
== "--with-perl")
140 return false; // To keep the titleBox hidden always
142 return output
.contains(buildOption
);
146 MainWindow::ccliveSupportsHost(const QString
& lnk
) {
148 const QString host
= QUrl(lnk
).host();
149 const int size
= hostsOutput
.size();
151 for (register int i
=0; i
<size
; ++i
) {
152 QRegExp
re(hostsOutput
[i
]);
153 if (re
.indexIn(host
) != -1)
160 MainWindow::updateWidgets(const bool updateCcliveDepends
) {
161 // Enable widgets based on preferences and other settings.
164 s
= prefs
->streamEdit
->text();
165 streamBox
->setEnabled(!s
.isEmpty());
167 streamBox
->setCheckState(Qt::Unchecked
);
169 s
= prefs
->commandEdit
->text();
170 commandBox
->setEnabled(!s
.isEmpty());
172 commandBox
->setCheckState(Qt::Unchecked
);
174 if (updateCcliveDepends
) {
175 // The most time consuming check is to run (c)clive.
176 // Run it only when we cannot work around it.
177 if (ccliveSupportsFeature("--with-perl"))
180 titleBox
->setCheckState(Qt::Unchecked
);
187 MainWindow::closeEvent(QCloseEvent
*event
) {
193 MainWindow::writeSettings() {
195 s
.beginGroup("MainWindow");
196 s
.setValue("size", size());
197 s
.setValue("pos", pos());
198 s
.setValue("titleBox", titleBox
->checkState());
203 MainWindow::readSettings() {
205 s
.beginGroup("MainWindow");
206 resize( s
.value("size", QSize(525,265)).toSize() );
207 move( s
.value("pos", QPoint(200,200)).toPoint() );
208 titleBox
->setCheckState(
209 s
.value("titleBox").toBool()
216 MainWindow::updateLog(const QString
& newText
) {
217 QString text
= logEdit
->toPlainText() + newText
;
218 logEdit
->setPlainText(text
);
225 MainWindow::onPreferences() {
226 QString old
= prefs
->ccliveEdit
->text();
228 QString _new
= prefs
->ccliveEdit
->text();
229 updateWidgets(old
!= _new
);
231 parseCcliveHostsOutput();
236 MainWindow::setProxy() {
237 if (!prefs
->proxyEdit
->text().isEmpty()
238 && prefs
->proxyCombo
->currentIndex() > 0)
242 proxy
.setType(QNetworkProxy::HttpProxy
);
244 QUrl
url(prefs
->proxyEdit
->text());
246 proxy
.setHostName(url
.host());
247 proxy
.setPort(url
.port());
249 QNetworkProxy::setApplicationProxy(proxy
);
254 MainWindow::onStreamStateChanged(int state
) {
255 streamSpin
->setEnabled(state
!= 0);
259 MainWindow::onStart() {
261 if (linksList
->count() == 0) {
262 QMessageBox::information(this, QCoreApplication::applicationName(),
263 tr("Add video page links to the list."));
268 if (!checkCclivePath())
271 QString path
= prefs
->ccliveEdit
->text();
273 // Check video save directory
275 // cclive does not support this concept at command line option level.
276 // We work around this by changing the working directory instead.
278 QString savedir
= prefs
->savedirEdit
->text();
279 if (savedir
.isEmpty()) {
280 QMessageBox::information(this, QCoreApplication::applicationName(),
281 tr("Please specify a directory to which the downloaded videos\n"
282 "should be saved to."));
287 process
.setWorkingDirectory(savedir
);
290 const bool _isCclive
= isCclive(output
);
292 // Construct cclive/clive args
298 args
<< "--print-fname";
301 // Set environment variables for clive
302 env
<< "COLUMNS=80" << "LINES=24" // Term::ReadKey
303 << QString("HOME=%1").arg(QDir::homePath()); // $env{HOME}
306 QString s
= prefs
->streamEdit
->text();
307 if (!s
.isEmpty() && streamBox
->isChecked()) {
308 args
<< QString("--stream-exec=%1").arg(s
);
309 args
<< QString("--stream=%1").arg(streamSpin
->value());
312 s
= prefs
->commandEdit
->text();
313 if (!s
.isEmpty() && commandBox
->isChecked()) {
314 if (!s
.endsWith(";"))
316 args
<< QString("--exec=%1").arg(s
);
319 if (prefs
->proxyCombo
->currentIndex() == 0)
320 args
<< "--no-proxy";
322 s
= prefs
->proxyEdit
->text();
324 args
<< QString("--proxy=%1").arg(s
);
327 if (prefs
->limitBox
->checkState()) {
328 int n
= prefs
->limitSpin
->value();
329 args
<< QString("--limit-rate=%1").arg(n
);
332 if (prefs
->timeoutBox
->checkState()) {
333 int n
= prefs
->timeoutSpin
->value();
334 if (!prefs
->socksBox
->checkState())
335 args
<< QString("--connect-timeout=%1").arg(n
);
337 args
<< QString("--connect-timeout-socks=%1").arg(n
);
340 args
<< "--continue"; // default to continue
342 if (_isCclive
) { // clive defaults to this
343 if (titleBox
->isChecked()) {
345 s
= prefs
->cclassEdit
->text();
347 args
<< QString("--cclass=%1").arg(s
);
349 } else { // clive can use this
350 s
= prefs
->cclassEdit
->text();
352 args
<< QString("--cclass=%1").arg(s
);
355 // Check if all video page links are of the same host.
357 QUrl
first(linksList
->item(0)->text());
359 for (register int i
=0; i
<linksList
->count(); ++i
) {
360 QUrl
url(linksList
->item(i
)->text());
361 if (url
.host() != first
.host()) {
371 // Use format dialog setting for the host.
372 // Otherwise we will use "flv" as default for all.
374 QMap
<QString
, QComboBox
*> map
;
376 map
["youtube.com"] = format
->youtubeBox
;
377 map
["video.google."] = format
->googleBox
;
378 map
["dailymotion.com"] = format
->dailymotionBox
;
379 map
["vimeo.com"] = format
->vimeoBox
;
381 for (QMap
<QString
, QComboBox
*>::const_iterator iter
=map
.begin();
382 iter
!= map
.end(); ++iter
)
384 QRegExp
re(iter
.key());
385 if (re
.indexIn(first
.host()) != -1) {
386 s
= iter
.value()->currentText()
387 .split(" ", QString::SkipEmptyParts
)[0];
393 args
<< QString("--format=%1").arg(s
);
395 for (register int i
=0; i
<linksList
->count(); ++i
)
396 args
<< QString("%1").arg(linksList
->item(i
)->text());
398 totalProgressbar
->setMaximum(linksList
->count());
403 updateLog("% " +path
+ " " +args
.join(" ")+ "\n");
405 // And finally start the process
408 process
.setEnvironment(env
);
409 process
.setProcessChannelMode(QProcess::MergedChannels
);
410 process
.start(path
,args
);
414 MainWindow::onCancel() {
420 MainWindow::onAbout() {
421 AboutDialog(this, ccliveVersion
, curlVersion
).exec();
425 MainWindow::onRSS() {
426 if (rss
->exec() == QDialog::Accepted
) {
427 QTreeWidgetItemIterator
iter(rss
->itemsTree
);
429 if ((*iter
)->checkState(0) == Qt::Checked
)
430 addPageLink((*iter
)->text(1));
434 rss
->writeSettings();
438 MainWindow::onScan() {
439 QMessageBox::information(this, QCoreApplication::applicationName(),
440 "TODO: Implement video link scan");
442 if (scan
->exec() == QDialog::Accepted
) {
444 scan
->writeSettings();
448 MainWindow::onPasteURL() {
449 QClipboard
*cb
= QApplication::clipboard();
450 QStringList lst
= cb
->text().split("\n");
451 for (register int i
=0; i
<lst
.size(); ++i
)
456 MainWindow::onAdd() {
457 QString lnk
= QInputDialog::getText(this,
458 tr("Add new video page link"), tr("Enter link:"));
463 MainWindow::onRemove() {
465 QList
<QListWidgetItem
*> sel
= linksList
->selectedItems();
470 if (QMessageBox::warning(this, tr("Remove links"),
471 tr("Really remove the selected links?"),
472 QMessageBox::Yes
|QMessageBox::No
, QMessageBox::No
)
478 for (register int i
=0; i
<sel
.size(); ++i
) {
479 const int row
= linksList
->row(sel
[i
]);
480 delete linksList
->takeItem(row
);
485 MainWindow::onClear() {
487 if (linksList
->count() == 0)
490 if (QMessageBox::warning(this, tr("Clear list"),
491 tr("Really clear list?"),
492 QMessageBox::Yes
|QMessageBox::No
, QMessageBox::No
)
502 MainWindow::addPageLink(QString lnk
) {
508 if (!lnk
.startsWith("http://", Qt::CaseInsensitive
))
509 lnk
.insert(0,"http://");
511 if (!ccliveSupportsHost(lnk
)) {
512 QMessageBox::critical(this, QCoreApplication::applicationName(),
513 QString(tr("%1: unsupported")).arg(QUrl(lnk
).host()));
517 QList
<QListWidgetItem
*> found
518 = linksList
->findItems(lnk
, Qt::MatchExactly
);
520 if (found
.size() == 0)
521 linksList
->addItem(lnk
);
525 MainWindow::onFormats() {
527 format
->writeSettings();
531 MainWindow::onProcStarted() {
532 statusBar() ->clearMessage();
533 fileLabel
->setText("-");
534 sizeLabel
->setText("-- / --");
535 rateLabel
->setText("--.-");
536 etaLabel
->setText("--:--");
537 progressBar
->setValue(0);
538 totalProgressbar
->setValue(0);
540 startButton
->setEnabled(false);
541 cancelButton
->setEnabled(true);
543 errorOccurred
= false;
547 MainWindow::onProcError(QProcess::ProcessError err
) {
548 if (err
== QProcess::FailedToStart
) {
549 QString msg
= tr("Error: Failed to start the process.");
550 statusBar()->showMessage(msg
);
556 MainWindow::onProcStdoutReady() {
557 // NOTE: We read both channels stdout and stderr.
559 QString::fromLocal8Bit(process
.readAll());
561 QStringList tmp
= newText
.split("\n", QString::SkipEmptyParts
);
565 QString status
, last
= tmp
.last();
567 if (last
.startsWith("fetch http://")) {
568 status
= tr("Fetching link...");
569 totalProgressbar
->setValue(totalProgressbar
->value()+1);
571 else if (last
.startsWith("verify") || last
.startsWith("query length") )
572 status
= tr("Verifying video link...");
573 else if (last
.startsWith("error:"))
574 errorOccurred
= true;
576 if (newText
.contains("file:")) {
577 QStringList tmp
= newText
.split(" ", QString::SkipEmptyParts
);
578 fileLabel
->setText(tmp
[1].remove("\n"));
579 status
= tr("Extracting video...");
582 if (!status
.isEmpty())
583 statusBar()->showMessage(status
.remove("\n"));
585 if (last
.contains("%")) {
586 newText
.replace("\n", " ");
588 QStringList tmp
= newText
.split(" ", QString::SkipEmptyParts
);
589 QString percent
= tmp
[1].remove(QChar('%'));
590 QString now
= tmp
[2];
591 QString expected
= tmp
[4];
592 QString rate
= tmp
[5];
593 QString eta
= tmp
[6];
595 sizeLabel
->setText(now
+" / "+ expected
);
596 progressBar
->setValue(percent
.toInt());
597 rateLabel
->setText(rate
);
598 etaLabel
->setText(eta
);
605 MainWindow::onProcFinished(int exitCode
, QProcess::ExitStatus exitStatus
) {
607 if (!errorOccurred
) {
608 if (exitStatus
== QProcess::NormalExit
) {
609 status
= exitCode
!= 0
610 ? tr("Process exited with an error. See Log for details.")
611 : tr("Process exited normally.");
614 ? tr("Process terminated.")
615 : tr("Process crashed. See Log for details.");
617 updateLog(status
+ ".");
620 status
= tr("Error(s) occurred. See Log for details.");
622 statusBar()->showMessage(status
);
624 startButton
->setEnabled(true);
625 cancelButton
->setEnabled(false);