Change: use QUrl for setting proxy.
[abby.git] / src / mainwnd.cpp
blob2c24f7a6dfefe0098ac5ea91355ab8acae6290f4
1 /*
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>
19 #include <QSettings>
20 #include <QCloseEvent>
21 #include <QMessageBox>
22 #include <QFileDialog>
23 #include <QClipboard>
24 #include <QDebug>
25 #include <QInputDialog>
26 #include <QNetworkProxy>
27 #include <QRegExp>
29 #include "mainwnd.h"
30 #include "prefsdlg.h"
31 #include "rssdlg.h"
32 #include "scandlg.h"
33 #include "formatdlg.h"
34 #include "aboutdlg.h"
36 MainWindow::MainWindow():
37 cancelled(false)
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");
47 setupUi(this);
48 readSettings();
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);
69 updateWidgets(true);
70 parseCcliveHostsOutput();
71 setProxy();
74 bool
75 MainWindow::isCclive(QString& output) {
76 QString path = prefs->ccliveEdit->text();
78 QProcess process;
79 process.setProcessChannelMode(QProcess::MergedChannels);
80 process.start(path, QStringList() << "--version");
82 bool state = false;
83 if (!process.waitForFinished())
84 qDebug() << path << ": " << process.errorString();
85 else {
86 output = QString::fromLocal8Bit(process.readAll());
87 QStringList lst = output.split(" ", QString::SkipEmptyParts);
88 state = lst[0] == "cclive";
89 ccliveVersion = lst[2];
90 curlVersion = lst[6];
92 return state;
95 void
96 MainWindow::parseCcliveHostsOutput() {
97 QString path = prefs->ccliveEdit->text();
99 QProcess proc;
100 proc.setProcessChannelMode(QProcess::MergedChannels);
101 proc.start(path, QStringList() << "--hosts");
103 if (!proc.waitForFinished())
104 qDebug() << path << ": " << proc.errorString();
105 else {
106 hostsOutput.clear();
107 QString output = QString::fromLocal8Bit(proc.readAll());
108 hostsOutput = output.split("\n", QString::SkipEmptyParts);
112 bool
113 MainWindow::ccliveSupportsFeature(const QString& buildOption) {
114 QString output;
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);
123 bool
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)
132 return true;
134 return false;
137 void
138 MainWindow::updateWidgets(const bool updateCcliveDepends) {
139 // Enable widgets based on preferences and other settings.
140 QString s;
142 s = prefs->streamEdit->text();
143 streamBox->setEnabled(!s.isEmpty());
144 if (s.isEmpty())
145 streamBox->setCheckState(Qt::Unchecked);
147 s = prefs->commandEdit->text();
148 commandBox->setEnabled(!s.isEmpty());
149 if (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"))
156 titleBox->show();
157 else {
158 titleBox->setCheckState(Qt::Unchecked);
159 titleBox->hide();
164 void
165 MainWindow::closeEvent(QCloseEvent *event) {
166 writeSettings();
167 event->accept();
170 void
171 MainWindow::writeSettings() {
172 QSettings s;
173 s.beginGroup("MainWindow");
174 s.setValue("size", size());
175 s.setValue("pos", pos());
176 s.setValue("titleBox", titleBox->checkState());
177 s.endGroup();
180 void
181 MainWindow::readSettings() {
182 QSettings s;
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()
188 ? Qt::Checked
189 : Qt::Unchecked);
190 s.endGroup();
193 void
194 MainWindow::updateLog(const QString& newText) {
195 QString text = logEdit->toPlainText() + newText;
196 logEdit->setPlainText(text);
200 // Slots
202 void
203 MainWindow::onPreferences() {
204 QString old = prefs->ccliveEdit->text();
205 prefs->exec();
206 QString _new = prefs->ccliveEdit->text();
207 updateWidgets(old != _new);
208 if (old != _new)
209 parseCcliveHostsOutput();
210 setProxy();
213 void
214 MainWindow::setProxy() {
215 if (!prefs->proxyEdit->text().isEmpty()
216 && prefs->proxyCombo->currentIndex() > 0)
218 QNetworkProxy proxy;
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);
231 void
232 MainWindow::onStreamStateChanged(int state) {
233 streamSpin->setEnabled(state != 0);
236 void
237 MainWindow::onStart() {
239 // Check cclive
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 "
245 "Preferences."));
246 onPreferences();
247 return;
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
254 // save directory.
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."));
261 onPreferences();
262 return;
265 if (linksList->count() == 0)
266 return;
268 QString output;
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
276 QStringList args;
277 QStringList env;
279 if (_isCclive) {
280 args << "--print-fname";
281 } else {
282 args << "--stderr";
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();
289 if (!s.isEmpty())
290 args << s;
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(";"))
301 s += ";";
302 args << QString("--exec=%1").arg(s);
305 if (prefs->proxyCombo->currentIndex() == 0)
306 args << "--no-proxy";
307 else {
308 s = prefs->proxyEdit->text();
309 if (!s.isEmpty())
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);
322 else
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()) {
330 args << "--title";
331 s = prefs->cclassEdit->text();
332 if (!s.isEmpty())
333 args << QString("--cclass=%1").arg(s);
335 } else { // clive can use this
336 s = prefs->cclassEdit->text();
337 if (!s.isEmpty())
338 args << QString("--cclass=%1").arg(s);
341 // TODO: format
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"
345 s = "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());
353 // Prepare log
355 logEdit->clear();
356 updateLog("% " +path+ " " +args.join(" ")+ "\n");
358 // And finally start the process
360 cancelled = false;
361 process.setEnvironment(env);
362 process.setProcessChannelMode(QProcess::MergedChannels);
363 process.start(path,args);
366 void
367 MainWindow::onCancel() {
368 cancelled = true;
369 process.kill();
372 void
373 MainWindow::onAbout() {
374 AboutDialog(this, ccliveVersion, curlVersion).exec();
377 void
378 MainWindow::onRSS() {
379 if (rss->exec() == QDialog::Accepted) {
380 QTreeWidgetItemIterator iter(rss->itemsTree);
381 while (*iter) {
382 if ((*iter)->checkState(0) == Qt::Checked)
383 addPageLink((*iter)->text(1));
384 ++iter;
387 rss->writeSettings();
390 void
391 MainWindow::onScan() {
392 // TODO: implement link scanning.
393 QMessageBox::information(this, QCoreApplication::applicationName(),
394 "TODO: Implement link scanning.");
395 return;
396 if (scan->exec() == QDialog::Accepted) {
398 scan->writeSettings();
401 void
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)
406 addPageLink(lst[i]);
409 void
410 MainWindow::onAdd() {
411 QString lnk = QInputDialog::getText(this,
412 tr("Add new video page link"), tr("Enter link:"));
413 addPageLink(lnk);
416 void
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);
425 void
426 MainWindow::addPageLink(QString lnk) {
427 if (lnk.isEmpty())
428 return;
430 lnk = lnk.trimmed();
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()));
438 return;
441 QList<QListWidgetItem *> found
442 = linksList->findItems(lnk, Qt::MatchExactly);
444 if (found.size() == 0)
445 linksList->addItem(lnk);
448 void
449 MainWindow::onFormats() {
450 format->exec();
451 // formats->writeSettings();
454 void
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;
470 void
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);
475 updateLog(msg);
479 void
480 MainWindow::onProcStdoutReady() {
481 // NOTE: We read both channels stdout and stderr.
482 QString newText =
483 QString::fromLocal8Bit(process.readAll());
485 QStringList tmp = newText.split("\n", QString::SkipEmptyParts);
486 if (tmp.isEmpty())
487 return;
489 QString status, last = tmp.last();
490 //qDebug() << 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);
525 else
526 updateLog(newText);
529 void
530 MainWindow::onProcFinished(int exitCode, QProcess::ExitStatus exitStatus) {
531 QString status;
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.");
537 } else {
538 status = cancelled
539 ? tr("Process terminated.")
540 : tr("Process crashed. See Log for details.");
542 updateLog(status + ".");
544 else
545 status = tr("Error(s) occurred. See Log for details.");
547 statusBar()->showMessage(status);
549 startButton ->setEnabled(true);
550 cancelButton->setEnabled(false);