1 /*****************************************************************************
2 ** QNapi
3 ** Copyright (C) 2008-2017 Piotr Krzemiński <pio.krzeminski@gmail.com>
4 **
5 ** This program 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 2 of the License, or
8 ** (at your option) any later version.
9 **
10 ** This file is provided AS IS with NO WARRANTY OF ANY KIND, INCLUDING THE
11 ** WARRANTY OF DESIGN, MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE.
12 **
13 *****************************************************************************/
14 
15 #include "frmprogress.h"
16 #include <QCloseEvent>
17 #include <QDesktopWidget>
18 #include <QDir>
19 #include <QFile>
20 #include <QFileInfo>
21 #include <QMessageBox>
22 #include <QSystemTrayIcon>
23 #include "libqnapi.h"
24 #include "qnapiapp.h"
25 
frmProgress(QWidget * parent,Qt::WindowFlags f)26 frmProgress::frmProgress(QWidget *parent, Qt::WindowFlags f)
27     : QWidget(parent, f) {
28   qRegisterMetaType<SubtitleInfoList>("QNapiSubtitleInfoList");
29 
30   ui.setupUi(this);
31 
32   setAttribute(Qt::WA_DeleteOnClose, false);
33   setAttribute(Qt::WA_QuitOnClose, false);
34 
35   setBatchMode(false);
36 
37   connect(&getThread, SIGNAL(fileNameChange(const QString &)), ui.lbFileName,
38           SLOT(setText(const QString &)));
39   connect(&getThread, SIGNAL(actionChange(const QString &)), ui.lbAction,
40           SLOT(setText(const QString &)));
41   connect(&getThread, SIGNAL(progressChange(int, int, float)), this,
42           SLOT(updateProgress(int, int, float)));
43   connect(&getThread, SIGNAL(selectSubtitles(QString, SubtitleInfoList)), this,
44           SLOT(selectSubtitles(QString, SubtitleInfoList)));
45   connect(this, SIGNAL(subtitlesSelected(int)), &getThread,
46           SLOT(subtitlesSelected(int)));
47   connect(&getThread, SIGNAL(finished()), this, SLOT(downloadFinished()));
48 
49   QRect position = frameGeometry();
50   position.moveCenter(QDesktopWidget().availableGeometry().center());
51   move(position.topLeft());
52 }
53 
receiveRequest(const QString & request)54 void frmProgress::receiveRequest(const QString &request) {
55   enqueueFile(request);
56   if (!getThread.isRunning()) download();
57   raise();
58   activateWindow();
59 }
60 
enqueueFile(const QString & filePath)61 void frmProgress::enqueueFile(const QString &filePath) {
62   static QMutex locker;
63   locker.lock();
64   if (QFile::exists(filePath)) {
65     getThread.queue << filePath;
66     updateProgress(-1, getThread.queue.size(), -1);
67   }
68   locker.unlock();
69 }
70 
enqueueFiles(const QStringList & fileList)71 void frmProgress::enqueueFiles(const QStringList &fileList) {
72   for (int i = 0; i < fileList.size(); i++) {
73     enqueueFile(fileList.at(i));
74   }
75 }
76 
download()77 bool frmProgress::download() {
78   if (summary.isVisible()) {
79     summary.close();
80   }
81 
82   auto config = LibQNapi::loadConfig();
83   auto config1 = config;
84   if (!targetFormatOverride.isEmpty()) {
85     config1 = config1.setPostProcessingConfig(
86         config1.postProcessingConfig().setSubFormat(targetFormatOverride));
87   }
88   auto config2 = config1;
89   if (!targetExtOverride.isEmpty()) {
90     config2 = config2.setPostProcessingConfig(
91         config2.postProcessingConfig().setSubExtension(targetExtOverride));
92   }
93 
94   QNapi napi(config2);
95 
96   // TODO: perform these checks earlier
97   if (!napi.checkP7ZipPath()) {
98     QMessageBox::warning(0, tr("Can not find p7zip!"),
99                          tr("The path to the program p7zip is incorrect!"));
100     return false;
101   }
102 
103   if (!napi.checkTmpPath()) {
104     QMessageBox::warning(
105         0, tr("Invalid temporary directory!"),
106         tr("Unable to write to the temporary directory! Check your settings."));
107     return false;
108   }
109 
110   if (getThread.queue.isEmpty()) {
111     QMessageBox::warning(
112         0, tr("No files!"),
113         tr("Can't download subtitles as no movie files specified!"));
114     return false;
115   }
116 
117   if (!isVisible()) {
118     QRect position = frameGeometry();
119     position.moveCenter(QDesktopWidget().availableGeometry().center());
120     move(position.topLeft());
121     show();
122   }
123 
124   showSummary = true;
125   closeRequested = false;
126   ui.pbCancel->setEnabled(true);
127 
128   getThread.setConfig(config2);
129   getThread.start();
130   return true;
131 }
132 
updateProgress(int current,int all,float stageProgress)133 void frmProgress::updateProgress(int current, int all, float stageProgress) {
134   static int lastCurrent, lastAll;
135   static float lastStageProgress;
136 
137   static QMutex m;
138   m.lock();
139 
140   if (current >= 0) lastCurrent = current;
141   if (all >= 0) lastAll = all;
142   if (stageProgress >= 0) lastStageProgress = stageProgress;
143 
144   QString windowTitle =
145       (lastAll > 1) ? QString(tr("QNapi - downloading subtitles (%1/%2)"))
146                           .arg(lastCurrent + 1)
147                           .arg(lastAll)
148                     : QString(tr("QNapi - downloading subtitles..."));
149   setWindowTitle(windowTitle);
150 
151   ui.pbProgress->setMaximum(lastAll * 100);
152   ui.pbProgress->setValue(lastCurrent * 100 + (int)(lastStageProgress * 100));
153 
154   m.unlock();
155 }
156 
selectSubtitles(QString fileName,SubtitleInfoList subtitles)157 void frmProgress::selectSubtitles(QString fileName,
158                                   SubtitleInfoList subtitles) {
159   frmSelect.setFileName(fileName);
160   frmSelect.setSubtitlesList(subtitles);
161 
162   int selIdx;
163 
164   if (frmSelect.exec() == QDialog::Accepted) {
165     selIdx = frmSelect.getSelectedIndex();
166   } else {
167     selIdx = -1;
168   }
169 
170   emit subtitlesSelected(selIdx);
171 }
172 
downloadFinished()173 void frmProgress::downloadFinished() {
174   hide();
175 
176   QStringList queue = getThread.queue;
177   getThread.queue.clear();
178 
179   mutex.lock();
180   if (showSummary) {
181     if (!getThread.criticalMessage.isEmpty()) {
182       QMessageBox::critical(0, tr("Critical error!"),
183                             getThread.criticalMessage);
184     } else if (queue.size() > 0 && !getThread.subStatusList.isEmpty()) {
185       summary.setSummaryList(getThread.subStatusList);
186       summary.exec();
187     }
188   }
189 
190   if (closeRequested) close();
191 
192   mutex.unlock();
193   if (batchMode) qApp->quit();
194 }
195 
closeEvent(QCloseEvent * event)196 void frmProgress::closeEvent(QCloseEvent *event) {
197   if (getThread.isRunning()) {
198     if (QMessageBox::question(
199             this, tr("QNapi"),
200             tr("Do you want to cancel subtitles downloading?"),
201             QMessageBox::Yes | QMessageBox::No) == QMessageBox::Yes) {
202       mutex.lock();
203       showSummary = false;
204       getThread.requestAbort();
205       ui.lbAction->setText(tr("Finishing the tasks..."));
206       ui.lbFileName->setText("");
207       ui.pbCancel->setEnabled(false);
208       qApp->processEvents();
209       closeRequested = true;
210       mutex.unlock();
211       event->ignore();
212     } else {
213       event->ignore();
214     }
215   } else {
216     event->accept();
217   }
218 }
219 
dragEnterEvent(QDragEnterEvent * event)220 void frmProgress::dragEnterEvent(QDragEnterEvent *event) {
221   QUrl url(event->mimeData()->urls().at(0));
222   QFileInfo fi(url.toLocalFile());
223   if (fi.exists() && fi.isFile()) event->acceptProposedAction();
224 }
225 
dropEvent(QDropEvent * event)226 void frmProgress::dropEvent(QDropEvent *event) {
227   QList<QUrl> urlList;
228   QFileInfo info;
229 
230   urlList = event->mimeData()->urls();
231 
232   foreach (QUrl url, urlList) {
233     info.setFile(url.toLocalFile());
234     if (!info.exists() || !info.isFile()) continue;
235     enqueueFile(url.toLocalFile());
236   }
237 }
238 
GetThread()239 GetThread::GetThread()
240     : langBackupPassed(false), config(LibQNapi::loadConfig()) {
241   connect(this, SIGNAL(criticalError(const QString &)), this,
242           SLOT(setCriticalMessage(const QString &)));
243 }
244 
subtitlesSelected(int idx)245 void GetThread::subtitlesSelected(int idx) {
246   selIdx = idx;
247   waitForDlg.unlock();
248 }
249 
250 #define ABORT_POINT \
251   {                 \
252     if (abort) {    \
253       return;       \
254     }               \
255   }
256 
run()257 void GetThread::run() {
258   abort = false;
259   criticalMessage.clear();
260   if (queue.size() <= 0) return;
261 
262   napiSuccess = napiFail = 0;
263   subStatusList.clear();
264 
265   QNapi napi(config, specificEngine);
266 
267   emit progressChange(0, queue.size(), 0.0f);
268 
269   QString language = !lang.isEmpty() ? lang : config.generalConfig().language();
270   QString languageBackup =
271       langBackupPassed ? langBackup : config.generalConfig().backupLanguage();
272 
273   for (int i = 0; i < queue.size(); i++) {
274     ABORT_POINT
275 
276     QFileInfo fi(queue[i]);
277     emit fileNameChange(fi.fileName());
278 
279     napi.setMoviePath(queue[i]);
280 
281     emit progressChange(i, queue.size(), 0.1f);
282     emit actionChange(tr("Checking permissions of the video directory..."));
283 
284     if (!napi.checkWritePermissions()) {
285       emit criticalError(tr("No permission to write to the directory '%1'!")
286                              .arg(QFileInfo(queue[i]).path()));
287       return;
288     }
289 
290     napi.clearSubtitlesList();
291 
292     emit progressChange(i, queue.size(), 0.3f);
293     emit actionChange(tr("Calculating checksum of the file..."));
294 
295     napi.checksum();
296 
297     ABORT_POINT
298 
299     bool found = false;
300     SearchPolicy sp = config.generalConfig().searchPolicy();
301 
302     if (sp == SP_SEARCH_ALL_WITH_BACKUP_LANG) {
303       foreach (QString e, napi.listLoadedEngines()) {
304         emit progressChange(i, queue.size(), 0.4f);
305         emit actionChange(
306             tr("Searching for subtitles [%1] (%2)...").arg(language, e));
307         found = napi.lookForSubtitles(language, e) || found;
308 
309         ABORT_POINT
310 
311         emit actionChange(
312             tr("Searching for subtitles in alternative language [%1] (%2)...")
313                 .arg(languageBackup, e));
314         found = napi.lookForSubtitles(languageBackup, e) || found;
315 
316         ABORT_POINT
317       }
318     } else {
319       foreach (QString e, napi.listLoadedEngines()) {
320         emit progressChange(i, queue.size(), 0.4f);
321         emit actionChange(
322             tr("Searching for subtitles [%1] (%2)...").arg(language, e));
323         found = napi.lookForSubtitles(language, e) || found;
324 
325         if (sp == SP_BREAK_IF_FOUND && found) {
326           break;
327         }
328 
329         ABORT_POINT
330       }
331 
332       if (!found && !languageBackup.isEmpty()) {
333         foreach (QString e, napi.listLoadedEngines()) {
334           emit progressChange(i, queue.size(), 0.45f);
335           emit actionChange(
336               tr("Searching for subtitles in alternative language [%1] (%2)...")
337                   .arg(languageBackup, e));
338           found = napi.lookForSubtitles(languageBackup, e) || found;
339 
340           if (sp == SP_BREAK_IF_FOUND && found) break;
341 
342           ABORT_POINT
343         }
344       }
345     }
346 
347     if (!found) {
348       ++napiFail;
349       subStatusList << SubtitleInfo::fromFailed(queue[i]);
350       continue;
351     }
352 
353     // jesli mozna i potrzeba, listujemy dostepne napisy
354     if (napi.needToShowList()) {
355       emit selectSubtitles(QFileInfo(queue[i]).fileName(),
356                            napi.listSubtitles());
357 
358       waitForDlg.lock();
359       waitForDlg.lock();
360       waitForDlg.unlock();
361     } else {
362       selIdx = napi.bestIdx();
363     }
364 
365     ABORT_POINT
366 
367     if (selIdx == -1) {
368       ++napiFail;
369       subStatusList << SubtitleInfo::fromFailed(queue[i]);
370       continue;
371     }
372 
373     emit progressChange(i, queue.size(), 0.5);
374     emit actionChange(tr("Downloading subtitles file..."));
375 
376     if (!napi.download(selIdx)) {
377       ABORT_POINT
378 
379       ++napiFail;
380       subStatusList << SubtitleInfo::fromFailed(queue[i]);
381       continue;
382     }
383 
384     ABORT_POINT
385 
386     emit progressChange(i, queue.size(), 0.65f);
387     emit actionChange(tr("Unpacking subtitles file..."));
388 
389     if (!napi.unpack(selIdx)) {
390       ++napiFail;
391       subStatusList << SubtitleInfo::fromFailed(queue[i]);
392       continue;
393     }
394 
395     if (napi.ppEnabled()) {
396       emit progressChange(i, queue.size(), 0.8f);
397       emit actionChange(tr("Post-processing subtitles..."));
398       napi.postProcessSubtitles();
399     }
400 
401     emit progressChange(i, queue.size(), 0.9f);
402     emit actionChange(tr("Adjusting subtitles..."));
403 
404     if (!napi.matchSubtitles()) {
405       ABORT_POINT
406 
407       ++napiFail;
408       subStatusList << SubtitleInfo::fromFailed(queue[i]);
409 
410       emit criticalError(tr("Could not adjust subtitles!"));
411       return;
412     }
413 
414     ++napiSuccess;
415     subStatusList << napi.listSubtitles().at(selIdx);
416 
417     napi.cleanup();
418 
419     emit progressChange(i, queue.size(), 1.0f);
420   }
421 
422   emit progressChange(queue.size() - 1, queue.size(), 1);
423 }
424