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>
33 #include "formatdlg.h"
36 MainWindow::MainWindow():
40 The word "English" is not meant to be translated literally.
41 Replace the word "English" with target language, e.g. Suomi
42 or Deutsch. Note that the only purpose of this string is to
43 list it as an available language in the Preferences dialog.
45 const QString lang
= tr("English");
50 connect(&process
, SIGNAL(started()),
51 this, SLOT(onProcStarted()));
52 connect(&process
, SIGNAL(error(QProcess::ProcessError
)),
53 this, SLOT(onProcError(QProcess::ProcessError
)));
55 // NOTE: We read both channels stdout and stderr.
56 connect(&process
, SIGNAL(readyReadStandardOutput()),
57 this, SLOT(onProcStdoutReady()));
58 connect(&process
, SIGNAL(readyReadStandardError()),
59 this, SLOT(onProcStdoutReady()));
61 connect(&process
, SIGNAL(finished(int,QProcess::ExitStatus
)),
62 this, SLOT(onProcFinished(int,QProcess::ExitStatus
)));
64 prefs
= new PreferencesDialog(this);
65 rss
= new RSSDialog(this);
66 scan
= new ScanDialog(this);
67 format
= new FormatDialog(this);
70 parseCcliveHostsOutput();
75 MainWindow::isCclive(QString
& output
) {
76 QString path
= prefs
->ccliveEdit
->text();
79 process
.setProcessChannelMode(QProcess::MergedChannels
);
80 process
.start(path
, QStringList() << "--version");
83 if (!process
.waitForFinished())
84 qDebug() << path
<< ": " << process
.errorString();
86 output
= QString::fromLocal8Bit(process
.readAll());
87 QStringList lst
= output
.split(" ", QString::SkipEmptyParts
);
88 state
= lst
[0] == "cclive";
89 ccliveVersion
= lst
[2];
96 MainWindow::parseCcliveHostsOutput() {
97 QString path
= prefs
->ccliveEdit
->text();
100 proc
.setProcessChannelMode(QProcess::MergedChannels
);
101 proc
.start(path
, QStringList() << "--hosts");
103 if (!proc
.waitForFinished())
104 qDebug() << path
<< ": " << proc
.errorString();
107 QString output
= QString::fromLocal8Bit(proc
.readAll());
108 hostsOutput
= output
.split("\n", QString::SkipEmptyParts
);
113 MainWindow::ccliveSupportsFeature(const QString
& buildOption
) {
115 const bool _isCclive
= isCclive(output
);
117 if (!_isCclive
&& buildOption
== "--with-perl")
118 return false; // To keep the titleBox hidden always
120 return output
.contains(buildOption
);
124 MainWindow::ccliveSupportsHost(const QString
& lnk
) {
126 const QString host
= QUrl(lnk
).host();
127 const int size
= hostsOutput
.size();
129 for (register int i
=0; i
<size
; ++i
) {
130 QRegExp
re(hostsOutput
[i
]);
131 if (re
.indexIn(host
) != -1)
138 MainWindow::updateWidgets(const bool updateCcliveDepends
) {
139 // Enable widgets based on preferences and other settings.
142 s
= prefs
->streamEdit
->text();
143 streamBox
->setEnabled(!s
.isEmpty());
145 streamBox
->setCheckState(Qt::Unchecked
);
147 s
= prefs
->commandEdit
->text();
148 commandBox
->setEnabled(!s
.isEmpty());
150 commandBox
->setCheckState(Qt::Unchecked
);
152 if (updateCcliveDepends
) {
153 // The most time consuming check is to run (c)clive.
154 // Run it only when we cannot work around it.
155 if (ccliveSupportsFeature("--with-perl"))
158 titleBox
->setCheckState(Qt::Unchecked
);
165 MainWindow::closeEvent(QCloseEvent
*event
) {
171 MainWindow::writeSettings() {
173 s
.beginGroup("MainWindow");
174 s
.setValue("size", size());
175 s
.setValue("pos", pos());
176 s
.setValue("titleBox", titleBox
->checkState());
181 MainWindow::readSettings() {
183 s
.beginGroup("MainWindow");
184 resize( s
.value("size", QSize(525,265)).toSize() );
185 move( s
.value("pos", QPoint(200,200)).toPoint() );
186 titleBox
->setCheckState(
187 s
.value("titleBox").toBool()
194 MainWindow::updateLog(const QString
& newText
) {
195 QString text
= logEdit
->toPlainText() + newText
;
196 logEdit
->setPlainText(text
);
203 MainWindow::onPreferences() {
204 QString old
= prefs
->ccliveEdit
->text();
206 QString _new
= prefs
->ccliveEdit
->text();
207 updateWidgets(old
!= _new
);
209 parseCcliveHostsOutput();
214 MainWindow::setProxy() {
215 if (!prefs
->proxyEdit
->text().isEmpty()
216 && prefs
->proxyCombo
->currentIndex() > 0)
220 proxy
.setType(QNetworkProxy::HttpProxy
);
222 QUrl
url(prefs
->proxyEdit
->text());
224 proxy
.setHostName(url
.host());
225 proxy
.setPort(url
.port());
227 QNetworkProxy::setApplicationProxy(proxy
);
232 MainWindow::onStreamStateChanged(int state
) {
233 streamSpin
->setEnabled(state
!= 0);
237 MainWindow::onStart() {
241 QString path
= prefs
->ccliveEdit
->text();
242 if (path
.isEmpty()) {
243 QMessageBox::information(this,QCoreApplication::applicationName(),
244 tr("Specify path to either clive or cclive command in the "
250 // Check video save directory
252 // NOTE: cclive itself does not support this concept so we
253 // work around it by changing the working directory to the
256 QString savedir
= prefs
->savedirEdit
->text();
257 if (savedir
.isEmpty()) {
258 QMessageBox::information(this,QCoreApplication::applicationName(),
259 tr("Specify path to save directory for the downloaded videos "
260 "in the Preferences."));
265 if (linksList
->count() == 0)
269 const bool _isCclive
= isCclive(output
);
271 // clive can use this same approach even if --savedir option exists.
272 process
.setWorkingDirectory(savedir
);
274 // Construct cclive/clive args
280 args
<< "--print-fname";
283 // Set environment variables for clive
284 env
<< "COLUMNS=80" << "LINES=24" // Term::ReadKey
285 << QString("HOME=%1").arg(QDir::homePath()); // $env{HOME}
288 QString s
= prefs
->additionalEdit
->text();
292 s
= prefs
->streamEdit
->text();
293 if (!s
.isEmpty() && streamBox
->isChecked()) {
294 args
<< QString("--stream-exec=%1").arg(s
);
295 args
<< QString("--stream=%1").arg(streamSpin
->value());
298 s
= prefs
->commandEdit
->text();
299 if (!s
.isEmpty() && commandBox
->isChecked()) {
300 if (!s
.endsWith(";"))
302 args
<< QString("--exec=%1").arg(s
);
305 if (prefs
->proxyCombo
->currentIndex() == 0)
306 args
<< "--no-proxy";
308 s
= prefs
->proxyEdit
->text();
310 args
<< QString("--proxy=%1").arg(s
);
313 if (prefs
->limitBox
->checkState()) {
314 int n
= prefs
->limitSpin
->value();
315 args
<< QString("--limit-rate=%1").arg(n
);
318 if (prefs
->timeoutBox
->checkState()) {
319 int n
= prefs
->timeoutSpin
->value();
320 if (!prefs
->socksBox
->checkState())
321 args
<< QString("--connect-timeout=%1").arg(n
);
323 args
<< QString("--connect-timeout-socks=%1").arg(n
);
326 args
<< "--continue"; // default to continue
328 if (_isCclive
) { // clive defaults to this
329 if (titleBox
->isChecked()) {
331 s
= prefs
->cclassEdit
->text();
333 args
<< QString("--cclass=%1").arg(s
);
335 } else { // clive can use this
336 s
= prefs
->cclassEdit
->text();
338 args
<< QString("--cclass=%1").arg(s
);
342 // * check if all links are of the same host
343 // * if yes, use "flv" or host specific settings from format dialog
344 // * otherwise, default to "flv"
346 args
<< QString("--format=%1").arg(s
);
348 for (register int i
=0; i
<linksList
->count(); ++i
)
349 args
<< QString("%1").arg(linksList
->item(i
)->text());
351 totalProgressbar
->setMaximum(linksList
->count());
356 updateLog("% " +path
+ " " +args
.join(" ")+ "\n");
358 // And finally start the process
361 process
.setEnvironment(env
);
362 process
.setProcessChannelMode(QProcess::MergedChannels
);
363 process
.start(path
,args
);
367 MainWindow::onCancel() {
373 MainWindow::onAbout() {
374 AboutDialog(this, ccliveVersion
, curlVersion
).exec();
378 MainWindow::onRSS() {
379 if (rss
->exec() == QDialog::Accepted
) {
380 QTreeWidgetItemIterator
iter(rss
->itemsTree
);
382 if ((*iter
)->checkState(0) == Qt::Checked
)
383 addPageLink((*iter
)->text(1));
387 rss
->writeSettings();
391 MainWindow::onScan() {
392 // TODO: implement link scanning.
393 QMessageBox::information(this, QCoreApplication::applicationName(),
394 "TODO: Implement link scanning.");
396 if (scan
->exec() == QDialog::Accepted
) {
398 scan
->writeSettings();
402 MainWindow::onPasteURL() {
403 QClipboard
*cb
= QApplication::clipboard();
404 QStringList lst
= cb
->text().split("\n");
405 for (register int i
=0; i
<lst
.size(); ++i
)
410 MainWindow::onAdd() {
411 QString lnk
= QInputDialog::getText(this,
412 tr("Add new video page link"), tr("Enter link:"));
417 MainWindow::onRemove() {
418 QList
<QListWidgetItem
*> sel
= linksList
->selectedItems();
419 for (register int i
=0; i
<sel
.size(); ++i
) {
420 const int row
= linksList
->row(sel
[i
]);
421 delete linksList
->takeItem(row
);
426 MainWindow::addPageLink(QString lnk
) {
432 if (!lnk
.startsWith("http://", Qt::CaseInsensitive
))
433 lnk
.insert(0,"http://");
435 if (!ccliveSupportsHost(lnk
)) {
436 QMessageBox::critical(this, QCoreApplication::applicationName(),
437 QString(tr("%1: unsupported")).arg(QUrl(lnk
).host()));
441 QList
<QListWidgetItem
*> found
442 = linksList
->findItems(lnk
, Qt::MatchExactly
);
444 if (found
.size() == 0)
445 linksList
->addItem(lnk
);
449 MainWindow::onFormats() {
451 // formats->writeSettings();
455 MainWindow::onProcStarted() {
456 statusBar() ->clearMessage();
457 fileLabel
->setText("-");
458 sizeLabel
->setText("-- / --");
459 rateLabel
->setText("--.-");
460 etaLabel
->setText("--:--");
461 progressBar
->setValue(0);
462 totalProgressbar
->setValue(0);
464 startButton
->setEnabled(false);
465 cancelButton
->setEnabled(true);
467 errorOccurred
= false;
471 MainWindow::onProcError(QProcess::ProcessError err
) {
472 if (err
== QProcess::FailedToStart
) {
473 QString msg
= tr("Error: Failed to start the process.");
474 statusBar()->showMessage(msg
);
480 MainWindow::onProcStdoutReady() {
481 // NOTE: We read both channels stdout and stderr.
483 QString::fromLocal8Bit(process
.readAll());
485 QStringList tmp
= newText
.split("\n", QString::SkipEmptyParts
);
489 QString status
, last
= tmp
.last();
492 if (last
.startsWith("fetch http://")) {
493 status
= tr("Fetching link...");
494 totalProgressbar
->setValue(totalProgressbar
->value()+1);
496 else if (last
.startsWith("verify") || last
.startsWith("query length") )
497 status
= tr("Verifying video link...");
498 else if (last
.startsWith("error:"))
499 errorOccurred
= true;
501 if (newText
.contains("file:")) {
502 QStringList tmp
= newText
.split(" ", QString::SkipEmptyParts
);
503 fileLabel
->setText(tmp
[1].remove("\n"));
504 status
= tr("Extracting video...");
507 if (!status
.isEmpty())
508 statusBar()->showMessage(status
.remove("\n"));
510 if (last
.contains("%")) {
511 newText
.replace("\n", " ");
513 QStringList tmp
= newText
.split(" ", QString::SkipEmptyParts
);
514 QString percent
= tmp
[1].remove(QChar('%'));
515 QString now
= tmp
[2];
516 QString expected
= tmp
[4];
517 QString rate
= tmp
[5];
518 QString eta
= tmp
[6];
520 sizeLabel
->setText(now
+" / "+ expected
);
521 progressBar
->setValue(percent
.toInt());
522 rateLabel
->setText(rate
);
523 etaLabel
->setText(eta
);
530 MainWindow::onProcFinished(int exitCode
, QProcess::ExitStatus exitStatus
) {
532 if (!errorOccurred
) {
533 if (exitStatus
== QProcess::NormalExit
) {
534 status
= exitCode
!= 0
535 ? tr("Process exited with an error. See Log for details.")
536 : tr("Process exited normally.");
539 ? tr("Process terminated.")
540 : tr("Process crashed. See Log for details.");
542 updateLog(status
+ ".");
545 status
= tr("Error(s) occurred. See Log for details.");
547 statusBar()->showMessage(status
);
549 startButton
->setEnabled(true);
550 cancelButton
->setEnabled(false);