OSDN Git Service

Added progress indicator to CueSheet import thread.
[lamexp/LameXP.git] / src / Dialog_CueImport.cpp
1 ///////////////////////////////////////////////////////////////////////////////
2 // LameXP - Audio Encoder Front-End
3 // Copyright (C) 2004-2012 LoRd_MuldeR <MuldeR2@GMX.de>
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 program 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.
14 //
15 // You should have received a copy of the GNU General Public License along
16 // with this program; if not, write to the Free Software Foundation, Inc.,
17 // 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
18 //
19 // http://www.gnu.org/licenses/gpl-2.0.txt
20 ///////////////////////////////////////////////////////////////////////////////
21
22 #include "Dialog_CueImport.h"
23
24 #include "Global.h"
25 #include "Model_CueSheet.h"
26 #include "Model_AudioFile.h"
27 #include "Model_FileList.h"
28 #include "Dialog_WorkingBanner.h"
29 #include "Thread_FileAnalyzer.h"
30 #include "Thread_CueSplitter.h"
31 #include "LockedFile.h"
32
33 #include <QFileInfo>
34 #include <QMessageBox>
35 #include <QTimer>
36 #include <QFileDialog>
37 #include <QProgressDialog>
38 #include <QMenu>
39 #include <QTextCodec>
40 #include <QInputDialog>
41
42 #define SET_FONT_BOLD(WIDGET,BOLD) { QFont _font = WIDGET->font(); _font.setBold(BOLD); WIDGET->setFont(_font); }
43 #define EXPAND(STR) QString(STR).leftJustified(96, ' ')
44
45 ////////////////////////////////////////////////////////////
46 // Constructor & Destructor
47 ////////////////////////////////////////////////////////////
48
49 CueImportDialog::CueImportDialog(QWidget *parent, FileListModel *fileList, const QString &cueFile)
50 :
51         QDialog(parent),
52         m_cueFileName(cueFile),
53         m_fileList(fileList)
54 {
55         //Init the dialog, from the .ui file
56         setupUi(this);
57
58         //Fix size
59         setMinimumSize(this->size());
60         setMaximumHeight(this->height());
61
62         //Create model
63         m_model = new CueSheetModel();
64         connect(m_model, SIGNAL(modelReset()), this, SLOT(modelChanged()));
65         
66         //Setup table view
67         treeView->setModel(m_model);
68         treeView->header()->setStretchLastSection(false);
69         treeView->header()->setResizeMode(QHeaderView::ResizeToContents);
70         treeView->header()->setResizeMode(1, QHeaderView::Stretch);
71         treeView->header()->setMovable(false);
72         treeView->setItemsExpandable(false);
73
74         //Enable up/down button
75         connect(imprtButton, SIGNAL(clicked()), this, SLOT(importButtonClicked()));
76         connect(browseButton, SIGNAL(clicked()), this, SLOT(browseButtonClicked()));
77         connect(loadOtherButton, SIGNAL(clicked()), this, SLOT(loadOtherButtonClicked()));
78
79         //Translate
80         labelHeaderText->setText(QString("<b>%1</b><br>%2").arg(tr("Import Cue Sheet"), tr("The following Cue Sheet will be split and imported into LameXP.")));
81 }
82
83 CueImportDialog::~CueImportDialog(void)
84 {
85         LAMEXP_DELETE(m_model);
86 }
87
88 ////////////////////////////////////////////////////////////
89 // EVENTS
90 ////////////////////////////////////////////////////////////
91
92 void CueImportDialog::showEvent(QShowEvent *event)
93 {
94         QDialog::showEvent(event);
95         modelChanged();
96 }
97
98 ////////////////////////////////////////////////////////////
99 // Slots
100 ////////////////////////////////////////////////////////////
101
102 int CueImportDialog::exec(void)
103 {
104         WorkingBanner *progress = new WorkingBanner(dynamic_cast<QWidget*>(parent()));
105         progress->show(tr("Loading Cue Sheet file, please be patient..."));
106
107         QFileInfo cueFileInfo(m_cueFileName);
108         if(!cueFileInfo.exists() || !cueFileInfo.isFile())
109         {
110                 QString text = QString("<nobr>%1</nobr><br><nobr>%2</nobr><br><br><nobr>%3</nobr>").arg(tr("Failed to load the Cue Sheet file:"), QDir::toNativeSeparators(m_cueFileName), tr("The specified file could not be found!")).replace("-", "&minus;");
111                 QMessageBox::warning(progress, tr("Cue Sheet Error"), text);
112                 progress->close();
113                 LAMEXP_DELETE(progress);
114                 return CueSheetModel::ErrorIOFailure;
115         }
116
117         //----------------------//
118
119         QTextCodec *codec = NULL;
120
121         QFile cueFile(cueFileInfo.canonicalFilePath());
122         cueFile.open(QIODevice::ReadOnly);
123         QByteArray bomCheck = cueFile.isOpen() ? cueFile.peek(16) : QByteArray();
124
125         if((!bomCheck.isEmpty()) && bomCheck.startsWith("\xef\xbb\xbf"))
126         {
127                 codec = QTextCodec::codecForName("UTF-8");
128         }
129         else if((!bomCheck.isEmpty()) && bomCheck.startsWith("\xff\xfe"))
130         {
131                 codec = QTextCodec::codecForName("UTF-16LE");
132         }
133         else if((!bomCheck.isEmpty()) && bomCheck.startsWith("\xfe\xff"))
134         {
135                 codec = QTextCodec::codecForName("UTF-16BE");
136         }
137         else
138         {
139                 const QString systemDefault = tr("(System Default)");
140
141                 QStringList codecList;
142                 codecList.append(systemDefault);
143                 codecList.append(lamexp_available_codepages());
144
145                 QInputDialog *input = new QInputDialog(progress);
146                 input->setLabelText(EXPAND(tr("Select ANSI Codepage for Cue Sheet file:")));
147                 input->setOkButtonText(tr("OK"));
148                 input->setCancelButtonText(tr("Cancel"));
149                 input->setTextEchoMode(QLineEdit::Normal);
150                 input->setComboBoxItems(codecList);
151         
152                 if(input->exec() < 1)
153                 {
154                         progress->close();
155                         LAMEXP_DELETE(input);
156                         LAMEXP_DELETE(progress);
157                         return Rejected;
158                 }
159         
160                 if(input->textValue().compare(systemDefault, Qt::CaseInsensitive))
161                 {
162                         qDebug("User-selected codec is: %s", input->textValue().toLatin1().constData());
163                         codec = QTextCodec::codecForName(input->textValue().toLatin1().constData());
164                 }
165                 else
166                 {
167                         qDebug("Going to use the system's default codec!");
168                         codec = QTextCodec::codecForName("System");
169                 }
170
171                 LAMEXP_DELETE(input);
172         }
173
174         bomCheck.clear();
175
176         //----------------------//
177
178         m_outputDir = QString("%1/%2").arg(cueFileInfo.canonicalPath(), cueFileInfo.completeBaseName());
179         for(int n = 2; QDir(m_outputDir).exists(); n++)
180         {
181                 m_outputDir = QString("%1/%2 (%3)").arg(cueFileInfo.canonicalPath(), cueFileInfo.completeBaseName(), QString::number(n));
182         }
183
184         setWindowTitle(QString("%1: %2").arg(windowTitle().split(":", QString::SkipEmptyParts).first().trimmed(), cueFileInfo.fileName()));
185
186         int iResult = m_model->loadCueSheet(m_cueFileName, QApplication::instance(), codec);
187         if(iResult != CueSheetModel::ErrorSuccess)
188         {
189                 QString errorMsg = tr("An unknown error has occured!");
190                 
191                 switch(iResult)
192                 {
193                 case CueSheetModel::ErrorIOFailure:
194                         errorMsg = tr("The file could not be opened for reading. Make sure you have the required rights!");
195                         break;
196                 case CueSheetModel::ErrorBadFile:
197                         errorMsg = tr("The provided file does not look like a valid Cue Sheet disc image file!");
198                         break;
199                 case CueSheetModel::ErrorUnsupported:
200                         errorMsg = QString("%1<br>%2").arg(tr("Could not find any supported audio track in the Cue Sheet image!"), tr("Note that LameXP can not handle \"binary\" Cue Sheet images."));
201                         break;
202                 case CueSheetModel::ErrorInconsistent:
203                         errorMsg = tr("The selected Cue Sheet file contains inconsistent information. Take care!");
204                         break;
205                 }
206                 
207                 QString text = QString("<nobr>%1</nobr><br><nobr>%2</nobr><br><br><nobr>%3</nobr>").arg(tr("Failed to load the Cue Sheet file:"), QDir::toNativeSeparators(m_cueFileName), errorMsg).replace("-", "&minus;");
208                 QMessageBox::warning(progress, tr("Cue Sheet Error"), text);
209                 progress->close();
210                 LAMEXP_DELETE(progress);
211                 return iResult;
212         }
213         
214         progress->close();
215         LAMEXP_DELETE(progress);
216         return QDialog::exec();
217 }
218
219 void CueImportDialog::modelChanged(void)
220 {
221         treeView->expandAll();
222         editOutputDir->setText(QDir::toNativeSeparators(m_outputDir));
223         labelArtist->setText(m_model->getAlbumPerformer().isEmpty() ? tr("Unknown Artist") : m_model->getAlbumPerformer());
224         labelAlbum->setText(m_model->getAlbumTitle().isEmpty() ? tr("Unknown Album") : m_model->getAlbumTitle());
225 }
226
227 void CueImportDialog::browseButtonClicked(void)
228 {
229         QString newOutDir, currentDir = m_outputDir;
230         
231         while(QDir(currentDir).exists())
232         {
233                 int pos = qMax(currentDir.lastIndexOf(QChar('\\')), currentDir.lastIndexOf(QChar('/')));
234                 if(pos > 0) currentDir.left(pos - 1); else break;
235         }
236
237         if(lamexp_themes_enabled() || ((QSysInfo::windowsVersion() & QSysInfo::WV_NT_based) < QSysInfo::WV_XP))
238         {
239                 newOutDir = QFileDialog::getExistingDirectory(this, tr("Choose Output Directory"), currentDir);
240         }
241         else
242         {
243                 QFileDialog dialog(this, tr("Choose Output Directory"));
244                 dialog.setFileMode(QFileDialog::DirectoryOnly);
245                 dialog.setDirectory(currentDir);
246                 if(dialog.exec())
247                 {
248                         newOutDir = dialog.selectedFiles().first();
249                 }
250         }
251
252         if(!newOutDir.isEmpty())
253         {
254                 m_outputDir = newOutDir;
255                 modelChanged();
256         }
257 }
258
259 void CueImportDialog::importButtonClicked(void)
260 {
261         static const unsigned __int64 oneGigabyte = 1073741824ui64; 
262         static const unsigned __int64 minimumFreeDiskspaceMultiplier = 2ui64;
263         static const char *writeTestBuffer = "LAMEXP_WRITE_TEST";
264         
265         QDir outputDir(m_outputDir);
266         outputDir.mkpath(".");
267         if(!(outputDir.exists() && outputDir.isReadable()))
268         {
269                 QMessageBox::warning(this, tr("LameXP"), QString("<nobr>%2</nobr>").arg(tr("Error: The selected output directory could not be created!")));
270                 return;
271         }
272
273         QFile writeTest(QString("%1/~%2.txt").arg(m_outputDir, lamexp_rand_str()));
274         if(!(writeTest.open(QIODevice::ReadWrite) && (writeTest.write(writeTestBuffer) == strlen(writeTestBuffer))))
275         {
276                 QMessageBox::warning(this, tr("LameXP"), QString("<nobr>%2</nobr>").arg(tr("Error: The selected output directory is not writable!")));
277                 return;
278         }
279         else
280         {
281                 writeTest.close();
282                 writeTest.remove();
283         }
284
285         bool ok = false;
286         unsigned __int64 currentFreeDiskspace = lamexp_free_diskspace(m_outputDir, &ok);
287
288         if(ok && (currentFreeDiskspace < (oneGigabyte * minimumFreeDiskspaceMultiplier)))
289         {
290                 QMessageBox::warning(this, tr("Low Diskspace Warning"), QString("<nobr>%1</nobr><br><nobr>%2</nobr>").arg(tr("There are less than %1 GB of free diskspace available in the selected output directory.").arg(QString::number(minimumFreeDiskspaceMultiplier)), tr("It is highly recommend to free up more diskspace before proceeding with the import!")));
291                 return;
292         }
293
294         importCueSheet();
295         accept();
296 }
297
298 void CueImportDialog::loadOtherButtonClicked(void)
299 {
300         done(-1);
301 }
302
303 void CueImportDialog::analyzedFile(const AudioFileModel &file)
304 {
305         qDebug("Received result: <%s> <%s/%s>", file.filePath().toLatin1().constData(), file.formatContainerType().toLatin1().constData(), file.formatAudioType().toLatin1().constData());
306         m_fileInfo << file;
307 }
308
309 ////////////////////////////////////////////////////////////
310 // Private Functions
311 ////////////////////////////////////////////////////////////
312
313 void CueImportDialog::importCueSheet(void)
314 {
315         QStringList files;
316
317         //Fetch all files that are referenced in the Cue Sheet and lock them
318         int nFiles = m_model->getFileCount();
319         for(int i = 0; i < nFiles; i++)
320         {
321                 QString temp = m_model->getFileName(i);
322                 try
323                 {
324                         m_locks << new LockedFile(temp);
325                 }
326                 catch(char *err)
327                 {
328                         qWarning("Failed to lock file: %s", err);
329                         continue;
330                 }
331                 files << temp;
332         }
333         
334         //Analyze all source files first
335         if(analyzeFiles(files))
336         {
337                 //Now split files according to Cue Sheet
338                 splitFiles();
339         }
340         
341         //Release locks
342         while(!m_locks.isEmpty())
343         {
344                 delete m_locks.takeFirst();
345         }
346 }
347
348 bool CueImportDialog::analyzeFiles(QStringList &files)
349 {
350         m_fileInfo.clear();
351         bool bSuccess = true;
352
353         WorkingBanner *progress = new WorkingBanner(this);
354         FileAnalyzer *analyzer = new FileAnalyzer(files);
355         
356         connect(analyzer, SIGNAL(fileSelected(QString)), progress, SLOT(setText(QString)), Qt::QueuedConnection);
357         connect(analyzer, SIGNAL(fileAnalyzed(AudioFileModel)), this, SLOT(analyzedFile(AudioFileModel)), Qt::QueuedConnection);
358         connect(progress, SIGNAL(userAbort()), analyzer, SLOT(abortProcess()), Qt::DirectConnection);
359
360         progress->show(tr("Analyzing file(s), please wait..."), analyzer);
361         progress->close();
362
363         if(analyzer->filesAccepted() < static_cast<unsigned int>(files.count()))
364         {
365                 if(QMessageBox::warning(this, tr("Analysis Failed"), tr("Warning: The format of some of the input files could not be determined!"), tr("Continue Anyway"), tr("Abort")) == 1)
366                 {
367                         bSuccess = false;
368                 }
369         }
370
371         LAMEXP_DELETE(progress);
372         LAMEXP_DELETE(analyzer);
373
374         return bSuccess;
375 }
376
377 void CueImportDialog::splitFiles(void)
378 {
379         QString baseName = QFileInfo(m_cueFileName).completeBaseName().replace(".", " ").simplified();
380
381         WorkingBanner *progress = new WorkingBanner(this);
382         CueSplitter *splitter  = new CueSplitter(m_outputDir, baseName, m_model, m_fileInfo);
383
384         connect(splitter, SIGNAL(fileSelected(QString)), progress, SLOT(setText(QString)), Qt::QueuedConnection);
385         connect(splitter, SIGNAL(fileSplit(AudioFileModel)), m_fileList, SLOT(addFile(AudioFileModel)), Qt::QueuedConnection);
386         connect(splitter, SIGNAL(progressValChanged(unsigned int)), progress, SLOT(setProgressVal(unsigned int)), Qt::QueuedConnection);
387         connect(splitter, SIGNAL(progressMaxChanged(unsigned int)), progress, SLOT(setProgressMax(unsigned int)), Qt::QueuedConnection);
388         connect(progress, SIGNAL(userAbort()), splitter, SLOT(abortProcess()), Qt::DirectConnection);
389
390         progress->show(tr("Splitting file(s), please wait..."), splitter);
391         progress->close();
392
393         if(splitter->getAborted())      
394         {
395                 QMessageBox::warning(this, tr("Cue Sheet Error"), tr("Process was aborted by the user after %1 track(s)!").arg(QString::number(splitter->getTracksSuccess())));
396         }
397         else if(!splitter->getSuccess())
398         {
399                 QMessageBox::warning(this, tr("Cue Sheet Error"), tr("An unexpected error has occured while splitting the Cue Sheet!"));
400         }
401         else
402         {
403                 QString text = QString("<nobr>%1</nobr>").arg(tr("Imported %1 track(s) from the Cue Sheet and skipped %2 track(s).").arg(QString::number(splitter->getTracksSuccess()), QString::number(splitter->getTracksSkipped() /*+ nTracksSkipped*/)));
404                 QMessageBox::information(this, tr("Cue Sheet Completed"), text);
405         }
406
407         LAMEXP_DELETE(splitter);
408         LAMEXP_DELETE(progress);
409 }