/* -*- c-basic-offset: 4 indent-tabs-mode: nil -*- vi:set ts=8 sts=4 sw=4: */ /* Rosegarden A MIDI and audio sequencer and musical notation editor. Copyright 2000-2011 the Rosegarden development team. Other copyrights also apply to some parts of this work. Please see the AUTHORS file and individual file headers for details. This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. See the file COPYING included with this distribution for more m_information. */ #include "ProjectPackager.h" #include "document/RosegardenDocument.h" #include "base/Composition.h" #include "base/Track.h" #include "gui/general/IconLoader.h" #include "gui/widgets/FileDialog.h" #include "gui/widgets/ProgressBar.h" #include "misc/ConfigGroups.h" #include "misc/Strings.h" #include "sound/AudioFile.h" #include "sound/AudioFileManager.h" #include "document/GzipFile.h" #include #include #include #include #include #include #include #include #include #include #include #include #include // NOTE: we're using std::cout everywhere in here for the moment. It's easy to // swap later to std::cerr, and for the time being this is convenient, because // we can ./rosegarden > /dev/null to ignore everything except these messages // we're generating in here. namespace Rosegarden { ProjectPackager::ProjectPackager(QWidget *parent, RosegardenDocument *document, int mode, QString filename) : QDialog(parent), m_doc(document), m_mode(mode), m_filename(filename), m_trueFilename(filename), m_packTmpDirName("fatal error"), m_packDataDirName("fatal error"), m_abortText(tr("

Processing aborted

")) { // (I'm not sure why RG_DEBUG didn't work from in here. Having to use // iostream is mildly irritating, as QStrings have to be converted, but // whatever, I'll figure that out later, or just leave well enough alone) std::cout << "ProjectPackager::ProjectPackager(): mode: " << mode << " m_filename: " << m_filename.toStdString() << std::endl; this->setModal(false); setIcon(IconLoader().loadPixmap("window-packager")); QGridLayout *layout = new QGridLayout; this->setLayout(layout); QLabel *icon = new QLabel(this); icon->setPixmap(IconLoader().loadPixmap("rosegarden-packager")); layout->addWidget(icon, 0, 0); QString modeStr; switch (mode) { case ProjectPackager::Unpack: modeStr = tr("Unpack"); break; case ProjectPackager::Pack: modeStr = tr("Pack"); break; } this->setWindowTitle(tr("Rosegarden - %1 Project Package...").arg(modeStr)); m_info = new QLabel(this); m_info->setWordWrap(true); layout->addWidget(m_info, 0, 1); m_progress = new ProgressBar(100, this); layout->addWidget(m_progress, 1, 1); QPushButton *ok = new QPushButton(tr("Cancel"), this); connect(ok, SIGNAL(clicked()), this, SLOT(reject())); layout->addWidget(ok, 3, 1); sanityCheck(); } QString ProjectPackager::getTrueFilename() { // get the path from the original m_filename, which is wherever the unpacked // .rgp file sat on disk, eg. /home/melvin/Documents/ QFileInfo origFI(m_filename); QString dirname = origFI.path(); std::cout << "ProjectPackager::getTrueFilename() - directory component is: " << dirname.toStdString() << std::endl; // get the filename component from the true m_trueFilename discovered while // unpacking the .rgp + extension (eg. foo.rgp yields bar.rg here) QFileInfo trueFI(m_trueFilename); QString basename = QString("%1.%2").arg(trueFI.baseName()).arg(trueFI.completeSuffix()); std::cout << " name component is: " << basename.toStdString() << std::endl; return QString("%1/%2").arg(dirname).arg(basename); } void ProjectPackager::puke(QString error) { m_progress->setMaximum(100); m_progress->hide(); m_info->setText(tr("

Fatal error.

%1
").arg(m_abortText)); QMessageBox::critical(this, tr("Rosegarden - Fatal Processing Error"), error, QMessageBox::Ok, QMessageBox::Ok); // abort processing after a fatal error, so calls to puke() abort the whole // process in its tracks reject(); // Well, that was the theory. In practice it apparently isn't so easy to do // the bash equivalent of a spontaneous "exit 1" inside a QDialog. Hrm. } void ProjectPackager::rmTmpDir() { std::cout << "ProjectPackager - rmTmpDir() removing " << m_packTmpDirName.toStdString() << std::endl; QDir d; if (d.exists(m_packTmpDirName)) { QProcess rm; rm.start("rm", QStringList() << "-rf" << m_packTmpDirName); rm.waitForStarted(); std::cout << "process started: rm -rf " << qstrtostr(m_packTmpDirName) << std::endl; rm.waitForFinished(); } // Bleargh, bollocks to this! Using a QDirIterator is the right way to handle // the possibility of there being extra unexpected subdirectories full of files, // but there are sort order issues and all manner of other ills. While we live // on Linux, let's just say the hell with it and do an rm -rf // // if (dir.exists()) { // // first find and remove all the files // QDirIterator fi(dir.path(), QDir::Files, QDirIterator::Subdirectories); // while (fi.hasNext()) { // std::cout << "rm " << fi.next().toStdString() << (QFile::remove(fi.next()) ? "OK" : "FAILED") << std::endl; // } // // // then clean up the empty directories // QDirIterator di(dir.path(), QDir::Dirs | QDir::NoDotAndDotDot, QDirIterator::Subdirectories); // while (di.hasNext()) { // QDir d; // std::cout << "rmdir: " << di.next().toStdString() << (d.remove(di.next()) ? "OK" : "FAILED") << std::endl; // } // } } void ProjectPackager::reject() { std::cout << "User pressed cancel" << std::endl; rmTmpDir(); QDialog::reject(); } QStringList ProjectPackager::getAudioFiles() { QStringList list; // get the Composition from the document, so we can iterate through it Composition *comp = &m_doc->getComposition(); // We don't particularly care about tracks here, so just iterate through the // entire Composition to find the audio segments and get the associated // file IDs from which to obtain a list of actual files. This could // conceivably pick up audio segments that are residing on MIDI tracks and // wouldn't otherwise be functional, but the important thing is to never // miss a single file that has any chance of being worth preserving. for (Composition::iterator i = comp->begin(); i != comp->end(); ++i) { if ((*i)->getType() == Segment::Audio) { AudioFileManager *manager = &m_doc->getAudioFileManager(); unsigned int id = (*i)->getAudioFileId(); AudioFile *file = manager->getAudioFile(id); // some polite sanity checking to avoid possible crashes if (!file) continue; list << file->getFilename(); } } // QStringList::removeDuplicates() would have been easy, but it's only in Qt // 4.5.0 and up. So here's the algorithm from Qt 4.5.0, courtesy of (and // originally Copyright 2009) Nokia QStringList *that = &list; int n = that->size(); int j = 0; QSet seen; seen.reserve(n); for (int i = 0; i < n; ++i) { const QString &s = that->at(i); if (seen.contains(s)) continue; seen.insert(s); if (j != i) (*that)[j] = s; ++j; } if (n != j) that->erase(that->begin() + j, that->end()); // return n - j; return list; } QStringList ProjectPackager::getPluginFilesAndRewriteXML(const QString fileToModify, const QString newPath) { // yet another miserable wrinkle in this whole wretched thing: we // automatically ignore audio files not actually used by segments, but // Rosegarden wants to hunt for the missing (but useless) files when we load // the result, so we have to strip them out of the XML too // // ARGH!!! QStringList usedAudioFiles; if (m_mode == ProjectPackager::Pack) usedAudioFiles = getAudioFiles(); QStringList list; // read the input file QString inText; bool readOK = GzipFile::readFromFile(fileToModify, inText); if (!readOK) { puke(tr("

Unable to read %1.

%2
").arg(fileToModify).arg(m_abortText)); return QStringList(); } // the pre-process input stream QString preText = inText; QTextStream preIn(&preText, QIODevice::ReadOnly); // the pre-process output steram QString postText; QTextStream preOut(&postText, QIODevice::WriteOnly); // insert \n between tags do { QString l = preIn.readLine(); l.replace(QRegExp("><"), ">\n<"); preOut << l << endl; } while (!preIn.atEnd()); // the input stream QTextStream inStream(&postText, QIODevice::ReadOnly); // the output stream QString outText; QTextStream outStream(&outText, QIODevice::WriteOnly); outStream.setEncoding(QTextStream::UnicodeUTF8); // synth plugin XML: // // // // // QString pluginAudioPathKey(" // // QString audioPathKey(" // -or- //