X-Git-Url: https://git.sesse.net/?a=blobdiff_plain;f=src%2Farchivewidget.cpp;h=a645172e5a9efe19f83a28dc7423d5a5f6d353c4;hb=c3302003093710ee247ad84c0fe2ef3c579d417f;hp=c1d931c0434951c68eed8fab3871d7ba1b9b9b40;hpb=6cf8fb23d3137672c1371c72a528efce204a620f;p=kdenlive diff --git a/src/archivewidget.cpp b/src/archivewidget.cpp index c1d931c0..a645172e 100644 --- a/src/archivewidget.cpp +++ b/src/archivewidget.cpp @@ -26,24 +26,42 @@ #include #include #include -#include +#include #include - +#include #include +#include +#include +#if KDE_IS_VERSION(4,7,0) +#include +#endif + #include +#include #include "projectsettings.h" -ArchiveWidget::ArchiveWidget(QDomDocument doc, QList list, QStringList luma_list, QWidget * parent) : - QDialog(parent), - m_requestedSize(0), - m_copyJob(NULL), - m_doc(doc) +ArchiveWidget::ArchiveWidget(const QString &projectName, const QDomDocument &doc, const QList &list, const QStringList &luma_list, QWidget * parent) : + QDialog(parent) + , m_requestedSize(0) + , m_copyJob(NULL) + , m_name(projectName.section('.', 0, -2)) + , m_doc(doc) + , m_temp(NULL) + , m_abortArchive(false) + , m_extractMode(false) + , m_progressTimer(NULL) + , m_extractArchive(NULL) + , m_missingClips(0) { + setAttribute(Qt::WA_DeleteOnClose); setupUi(this); setWindowTitle(i18n("Archive Project")); archive_url->setUrl(KUrl(QDir::homePath())); - connect(archive_url, SIGNAL(textChanged (const QString &)), this, SLOT(slotCheckSpace())); + connect(archive_url, SIGNAL(textChanged(QString)), this, SLOT(slotCheckSpace())); + connect(this, SIGNAL(archivingFinished(bool)), this, SLOT(slotArchivingFinished(bool))); + connect(this, SIGNAL(archiveProgress(int)), this, SLOT(slotArchivingProgress(int))); + connect(proxy_only, SIGNAL(stateChanged(int)), this, SLOT(slotProxyOnly(int))); // Setup categories QTreeWidgetItem *videos = new QTreeWidgetItem(files_list, QStringList() << i18n("Video clips")); @@ -66,6 +84,10 @@ ArchiveWidget::ArchiveWidget(QDomDocument doc, QList list, QStrin texts->setIcon(0, KIcon("text-plain")); texts->setData(0, Qt::UserRole, "texts"); texts->setExpanded(false); + QTreeWidgetItem *playlists = new QTreeWidgetItem(files_list, QStringList() << i18n("Playlist clips")); + playlists->setIcon(0, KIcon("video-mlt-playlist")); + playlists->setData(0, Qt::UserRole, "playlist"); + playlists->setExpanded(false); QTreeWidgetItem *others = new QTreeWidgetItem(files_list, QStringList() << i18n("Other clips")); others->setIcon(0, KIcon("unknown")); others->setData(0, Qt::UserRole, "others"); @@ -84,71 +106,105 @@ ArchiveWidget::ArchiveWidget(QDomDocument doc, QList list, QStrin QStringList allFonts; KUrl::List fileUrls; QStringList fileNames; + QStringList extraImageUrls; + QStringList otherUrls; generateItems(lumas, luma_list); - QStringList slideUrls; - QStringList audioUrls; - QStringList videoUrls; - QStringList imageUrls; - QStringList otherUrls; - QStringList proxyUrls; + QMap slideUrls; + QMap audioUrls; + QMap videoUrls; + QMap imageUrls; + QMap playlistUrls; + QMap proxyUrls; - for (int i = 0; i < list.count(); i++) { + for (int i = 0; i < list.count(); ++i) { DocClipBase *clip = list.at(i); CLIPTYPE t = clip->clipType(); + QString id = clip->getId(); if (t == SLIDESHOW) { KUrl slideUrl = clip->fileURL(); //TODO: Slideshow files - slideUrls << slideUrl.path(); + slideUrls.insert(id, slideUrl.path()); } - else if (t == IMAGE) imageUrls << clip->fileURL().path(); + else if (t == IMAGE) imageUrls.insert(id, clip->fileURL().path()); else if (t == TEXT) { QStringList imagefiles = TitleWidget::extractImageList(clip->getProperty("xmldata")); QStringList fonts = TitleWidget::extractFontList(clip->getProperty("xmldata")); - imageUrls << imagefiles; + extraImageUrls << imagefiles; allFonts << fonts; } else if (t == PLAYLIST) { + playlistUrls.insert(id, clip->fileURL().path()); QStringList files = ProjectSettings::extractPlaylistUrls(clip->fileURL().path()); otherUrls << files; } else if (!clip->fileURL().isEmpty()) { - if (t == AUDIO) audioUrls << clip->fileURL().path(); + if (t == AUDIO) audioUrls.insert(id, clip->fileURL().path()); else { - videoUrls << clip->fileURL().path(); + videoUrls.insert(id, clip->fileURL().path()); // Check if we have a proxy QString proxy = clip->getProperty("proxy"); - if (!proxy.isEmpty() && proxy != "-" && QFile::exists(proxy)) proxyUrls << proxy; + if (!proxy.isEmpty() && proxy != "-" && QFile::exists(proxy)) proxyUrls.insert(id, proxy); } } } + generateItems(images, extraImageUrls); generateItems(sounds, audioUrls); generateItems(videos, videoUrls); generateItems(images, imageUrls); generateItems(slideshows, slideUrls); + generateItems(playlists, playlistUrls); generateItems(others, otherUrls); generateItems(proxies, proxyUrls); -#if QT_VERSION >= 0x040500 allFonts.removeDuplicates(); + +#if KDE_IS_VERSION(4,7,0) + m_infoMessage = new KMessageWidget(this); + QVBoxLayout *s = static_cast (layout()); + s->insertWidget(5, m_infoMessage); + m_infoMessage->setCloseButtonVisible(false); + m_infoMessage->setWordWrap(true); + m_infoMessage->hide(); #endif + + // missing clips, warn user + if (m_missingClips > 0) { + QString infoText = i18np("You have %1 missing clip in your project.", "You have %1 missing clips in your project.", m_missingClips); +#if KDE_IS_VERSION(4,7,0) + m_infoMessage->setMessageType(KMessageWidget::Warning); + m_infoMessage->setText(infoText); + m_infoMessage->animatedShow(); +#else + KMessageBox::sorry(this, infoText); +#endif + } //TODO: fonts // Hide unused categories, add item count int total = 0; - for (int i = 0; i < files_list->topLevelItemCount(); i++) { - int items = files_list->topLevelItem(i)->childCount(); + for (int i = 0; i < files_list->topLevelItemCount(); ++i) { + QTreeWidgetItem *parentItem = files_list->topLevelItem(i); + int items = parentItem->childCount(); if (items == 0) { files_list->topLevelItem(i)->setHidden(true); } else { - total += items; - files_list->topLevelItem(i)->setText(0, files_list->topLevelItem(i)->text(0) + " " + i18np("(%1 item)", "(%1 items)", items)); + if (parentItem->data(0, Qt::UserRole).toString() == "slideshows") + { + // Special case: slideshows contain several files + for (int j = 0; j < items; j++) { + total += parentItem->child(j)->data(0, Qt::UserRole + 1).toStringList().count(); + } + } + else total += items; + parentItem->setText(0, files_list->topLevelItem(i)->text(0) + ' ' + i18np("(%1 item)", "(%1 items)", items)); } } - - project_files->setText(i18n("%1 files to archive, requires %2", total, KIO::convertSize(m_requestedSize))); + if (m_name.isEmpty()) m_name = i18n("Untitled"); + compressed_archive->setText(compressed_archive->text() + " (" + m_name + ".tar.gz)"); + project_files->setText(i18np("%1 file to archive, requires %2", "%1 files to archive, requires %2", total, KIO::convertSize(m_requestedSize))); buttonBox->button(QDialogButtonBox::Apply)->setText(i18n("Archive")); connect(buttonBox->button(QDialogButtonBox::Apply), SIGNAL(clicked()), this, SLOT(slotStartArchiving())); buttonBox->button(QDialogButtonBox::Apply)->setEnabled(false); @@ -156,11 +212,120 @@ ArchiveWidget::ArchiveWidget(QDomDocument doc, QList list, QStrin slotCheckSpace(); } +// Constructor for extract widget +ArchiveWidget::ArchiveWidget(const KUrl &url, QWidget * parent): + QDialog(parent), + m_extractMode(true), + m_extractUrl(url) +{ + //setAttribute(Qt::WA_DeleteOnClose); + + setupUi(this); + m_progressTimer = new QTimer; + m_progressTimer->setInterval(800); + m_progressTimer->setSingleShot(false); + connect(m_progressTimer, SIGNAL(timeout()), this, SLOT(slotExtractProgress())); + connect(this, SIGNAL(extractingFinished()), this, SLOT(slotExtractingFinished())); + connect(this, SIGNAL(showMessage(QString,QString)), this, SLOT(slotDisplayMessage(QString,QString))); + + compressed_archive->setHidden(true); + proxy_only->setHidden(true); + project_files->setHidden(true); + files_list->setHidden(true); + label->setText(i18n("Extract to")); + setWindowTitle(i18n("Open Archived Project")); + archive_url->setUrl(KUrl(QDir::homePath())); + buttonBox->button(QDialogButtonBox::Apply)->setText(i18n("Extract")); + connect(buttonBox->button(QDialogButtonBox::Apply), SIGNAL(clicked()), this, SLOT(slotStartExtracting())); + buttonBox->button(QDialogButtonBox::Apply)->setEnabled(true); + adjustSize(); + m_archiveThread = QtConcurrent::run(this, &ArchiveWidget::openArchiveForExtraction); +} + + ArchiveWidget::~ArchiveWidget() { + delete m_extractArchive; + delete m_progressTimer; } -void ArchiveWidget::generateItems(QTreeWidgetItem *parentItem, QStringList items) +void ArchiveWidget::slotDisplayMessage(const QString &icon, const QString &text) +{ + icon_info->setPixmap(KIcon(icon).pixmap(16, 16)); + text_info->setText(text); +} + +void ArchiveWidget::slotJobResult(bool success, const QString &text) +{ +#if KDE_IS_VERSION(4,7,0) + m_infoMessage->setMessageType(success ? KMessageWidget::Positive : KMessageWidget::Warning); + m_infoMessage->setText(text); + m_infoMessage->animatedShow(); +#else + if (success) icon_info->setPixmap(KIcon("dialog-ok").pixmap(16, 16)); + else icon_info->setPixmap(KIcon("dialog-close").pixmap(16, 16)); + text_info->setText(text); +#endif +} + +void ArchiveWidget::openArchiveForExtraction() +{ + emit showMessage("system-run", i18n("Opening archive...")); + m_extractArchive = new KTar(m_extractUrl.path()); + if (!m_extractArchive->isOpen() && !m_extractArchive->open( QIODevice::ReadOnly )) { + emit showMessage("dialog-close", i18n("Cannot open archive file:\n %1", m_extractUrl.path())); + groupBox->setEnabled(false); + return; + } + + // Check that it is a kdenlive project archive + bool isProjectArchive = false; + QStringList files = m_extractArchive->directory()->entries(); + for (int i = 0; i < files.count(); ++i) { + if (files.at(i).endsWith(".kdenlive")) { + m_projectName = files.at(i); + isProjectArchive = true; + break; + } + } + + if (!isProjectArchive) { + emit showMessage("dialog-close", i18n("File %1\n is not an archived Kdenlive project", m_extractUrl.path())); + groupBox->setEnabled(false); + buttonBox->button(QDialogButtonBox::Apply)->setEnabled(false); + return; + } + buttonBox->button(QDialogButtonBox::Apply)->setEnabled(true); + emit showMessage("dialog-ok", i18n("Ready")); +} + +void ArchiveWidget::done ( int r ) +{ + if (closeAccepted()) QDialog::done(r); +} + +void ArchiveWidget::closeEvent ( QCloseEvent * e ) +{ + + if (closeAccepted()) e->accept(); + else e->ignore(); +} + + +bool ArchiveWidget::closeAccepted() +{ + if (!m_extractMode && !archive_url->isEnabled()) { + // Archiving in progress, should we stop? + if (KMessageBox::warningContinueCancel(this, i18n("Archiving in progress, do you want to stop it?"), i18n("Stop Archiving"), KGuiItem(i18n("Stop Archiving"))) != KMessageBox::Continue) { + return false; + } + if (m_copyJob) m_copyJob->kill(); + } + return true; +} + + +void ArchiveWidget::generateItems(QTreeWidgetItem *parentItem, const QStringList& items) { QStringList filesList; QString fileName; @@ -178,17 +343,104 @@ void ArchiveWidget::generateItems(QTreeWidgetItem *parentItem, QStringList items if (slideUrl.fileName().startsWith(".all.")) { // mimetype slideshow (for example *.png) QStringList filters; - QString extension; // TODO: improve jpeg image detection with extension like jpeg, requires change in MLT image producers filters << "*." + slideUrl.fileName().section('.', -1); dir.setNameFilters(filters); QFileInfoList resultList = dir.entryInfoList(QDir::Files); QStringList slideImages; - for (int i = 0; i < resultList.count(); i++) { - m_requestedSize += resultList.at(i).size(); + qint64 totalSize = 0; + for (int i = 0; i < resultList.count(); ++i) { + totalSize += resultList.at(i).size(); + slideImages << resultList.at(i).absoluteFilePath(); + } + item->setData(0, Qt::UserRole + 1, slideImages); + item->setData(0, Qt::UserRole + 3, totalSize); + m_requestedSize += totalSize; + } + else { + // pattern url (like clip%.3d.png) + QStringList result = dir.entryList(QDir::Files); + QString filter = slideUrl.fileName(); + QString ext = filter.section('.', -1); + filter = filter.section('%', 0, -2); + QString regexp = '^' + filter + "\\d+\\." + ext + '$'; + QRegExp rx(regexp); + QStringList slideImages; + QString directory = dir.absolutePath(); + if (!directory.endsWith('/')) directory.append('/'); + qint64 totalSize = 0; + foreach(const QString & path, result) { + if (rx.exactMatch(path)) { + totalSize += QFileInfo(directory + path).size(); + slideImages << directory + path; + } + } + item->setData(0, Qt::UserRole + 1, slideImages); + item->setData(0, Qt::UserRole + 3, totalSize); + m_requestedSize += totalSize; + } + } + else if (filesList.contains(fileName)) { + // we have 2 files with same name + int ix = 0; + QString newFileName = fileName.section('.', 0, -2) + '_' + QString::number(ix) + '.' + fileName.section('.', -1); + while (filesList.contains(newFileName)) { + ix ++; + newFileName = fileName.section('.', 0, -2) + '_' + QString::number(ix) + '.' + fileName.section('.', -1); + } + fileName = newFileName; + item->setData(0, Qt::UserRole, fileName); + } + if (!isSlideshow) { + qint64 fileSize = QFileInfo(file).size(); + if (fileSize <= 0) { + item->setIcon(0, KIcon("edit-delete")); + m_missingClips++; + } + else { + m_requestedSize += fileSize; + item->setData(0, Qt::UserRole + 3, fileSize); + } + filesList << fileName; + } + } +} + +void ArchiveWidget::generateItems(QTreeWidgetItem *parentItem, const QMap & items) +{ + QStringList filesList; + QString fileName; + int ix = 0; + bool isSlideshow = parentItem->data(0, Qt::UserRole).toString() == "slideshows"; + QMap::const_iterator it = items.constBegin(); + while (it != items.constEnd()) { + QString file = it.value(); + QTreeWidgetItem *item = new QTreeWidgetItem(parentItem, QStringList() << file); + // Store the clip's id + item->setData(0, Qt::UserRole + 2, it.key()); + fileName = KUrl(file).fileName(); + if (isSlideshow) { + // we store each slideshow in a separate subdirectory + item->setData(0, Qt::UserRole, ix); + ix++; + KUrl slideUrl(file); + QDir dir(slideUrl.directory(KUrl::AppendTrailingSlash)); + if (slideUrl.fileName().startsWith(".all.")) { + // mimetype slideshow (for example *.png) + QStringList filters; + // TODO: improve jpeg image detection with extension like jpeg, requires change in MLT image producers + filters << "*." + slideUrl.fileName().section('.', -1); + dir.setNameFilters(filters); + QFileInfoList resultList = dir.entryInfoList(QDir::Files); + QStringList slideImages; + qint64 totalSize = 0; + for (int i = 0; i < resultList.count(); ++i) { + totalSize += resultList.at(i).size(); slideImages << resultList.at(i).absoluteFilePath(); } item->setData(0, Qt::UserRole + 1, slideImages); + item->setData(0, Qt::UserRole + 3, totalSize); + m_requestedSize += totalSize; } else { // pattern url (like clip%.3d.png) @@ -196,24 +448,27 @@ void ArchiveWidget::generateItems(QTreeWidgetItem *parentItem, QStringList items QString filter = slideUrl.fileName(); QString ext = filter.section('.', -1); filter = filter.section('%', 0, -2); - QString regexp = "^" + filter + "\\d+\\." + ext + "$"; + QString regexp = '^' + filter + "\\d+\\." + ext + '$'; QRegExp rx(regexp); QStringList slideImages; + qint64 totalSize = 0; QString directory = dir.absolutePath(); if (!directory.endsWith('/')) directory.append('/'); foreach(const QString & path, result) { if (rx.exactMatch(path)) { - m_requestedSize += QFileInfo(directory + path).size(); + totalSize += QFileInfo(directory + path).size(); slideImages << directory + path; } } item->setData(0, Qt::UserRole + 1, slideImages); + item->setData(0, Qt::UserRole + 3, totalSize); + m_requestedSize += totalSize; } } else if (filesList.contains(fileName)) { // we have 2 files with same name int ix = 0; - QString newFileName = fileName.section('.', 0, -2) + "_" + QString::number(ix) + "." + fileName.section('.', -1); + QString newFileName = fileName.section('.', 0, -2) + '_' + QString::number(ix) + '.' + fileName.section('.', -1); while (filesList.contains(newFileName)) { ix ++; newFileName = fileName.section('.', 0, -2) + "_" + QString::number(ix) + "." + fileName.section('.', -1); @@ -222,65 +477,94 @@ void ArchiveWidget::generateItems(QTreeWidgetItem *parentItem, QStringList items item->setData(0, Qt::UserRole, fileName); } if (!isSlideshow) { - m_requestedSize += QFileInfo(file).size(); + qint64 fileSize = QFileInfo(file).size(); + if (fileSize <= 0) { + item->setIcon(0, KIcon("edit-delete")); + m_missingClips++; + } + else { + m_requestedSize += fileSize; + item->setData(0, Qt::UserRole + 3, fileSize); + } filesList << fileName; } + ++it; } } void ArchiveWidget::slotCheckSpace() { KDiskFreeSpaceInfo inf = KDiskFreeSpaceInfo::freeSpaceInfo( archive_url->url().path()); - KIO::filesize_t freeSize = inf.available();; + KIO::filesize_t freeSize = inf.available(); if (freeSize > m_requestedSize) { // everything is ok buttonBox->button(QDialogButtonBox::Apply)->setEnabled(true); - icon_info->setPixmap(KIcon("dialog-ok").pixmap(16, 16)); - text_info->setText(i18n("Available space on drive: %1", KIO::convertSize(freeSize))); + slotDisplayMessage("dialog-ok", i18n("Available space on drive: %1", KIO::convertSize(freeSize))); } else { buttonBox->button(QDialogButtonBox::Apply)->setEnabled(false); - icon_info->setPixmap(KIcon("dialog-close").pixmap(16, 16)); - text_info->setText(i18n("Not enough space on drive, free space: %1", KIO::convertSize(freeSize))); + slotDisplayMessage("dialog-close", i18n("Not enough space on drive, free space: %1", KIO::convertSize(freeSize))); } } bool ArchiveWidget::slotStartArchiving(bool firstPass) { - if (firstPass && m_copyJob) { + if (firstPass && (m_copyJob || m_archiveThread.isRunning())) { // archiving in progress, abort - m_copyJob->kill(KJob::EmitResult); + if (m_copyJob) m_copyJob->kill(KJob::EmitResult); + m_abortArchive = true; return true; } + bool isArchive = compressed_archive->isChecked(); if (!firstPass) m_copyJob = NULL; else { //starting archiving + m_abortArchive = false; m_duplicateFiles.clear(); m_replacementList.clear(); + m_foldersList.clear(); + m_filesList.clear(); + slotDisplayMessage("system-run", i18n("Archiving...")); + repaint(); + archive_url->setEnabled(false); + proxy_only->setEnabled(false); + compressed_archive->setEnabled(false); } KUrl::List files; KUrl destUrl; + QString destPath; QTreeWidgetItem *parentItem; bool isSlideshow = false; - for (int i = 0; i < files_list->topLevelItemCount(); i++) { + int items = 0; + + // We parse all files going into one folder, then start the copy job + for (int i = 0; i < files_list->topLevelItemCount(); ++i) { parentItem = files_list->topLevelItem(i); - if (parentItem->data(0, Qt::UserRole).toString() == "slideshows") { - KIO::NetAccess::mkdir(KUrl(archive_url->url().path(KUrl::AddTrailingSlash) + "slideshows"), this); - isSlideshow = true; + if (parentItem->isDisabled()) { + parentItem->setExpanded(false); + continue; } - if (parentItem->childCount() > 0 && !parentItem->isDisabled()) { + if (parentItem->childCount() > 0) { + if (parentItem->data(0, Qt::UserRole).toString() == "slideshows") { + KUrl slideFolder(archive_url->url().path(KUrl::AddTrailingSlash) + "slideshows"); + if (isArchive) m_foldersList.append("slideshows"); + else KIO::NetAccess::mkdir(slideFolder, this); + isSlideshow = true; + } + else isSlideshow = false; files_list->setCurrentItem(parentItem); - if (!isSlideshow) parentItem->setDisabled(true); - destUrl = KUrl(archive_url->url().path(KUrl::AddTrailingSlash) + parentItem->data(0, Qt::UserRole).toString()); + parentItem->setExpanded(true); + destPath = parentItem->data(0, Qt::UserRole).toString() + '/'; + destUrl = KUrl(archive_url->url().path(KUrl::AddTrailingSlash) + destPath); QTreeWidgetItem *item; for (int j = 0; j < parentItem->childCount(); j++) { item = parentItem->child(j); + if (item->isDisabled()) continue; // Special case: slideshows + items++; if (isSlideshow) { - if (item->isDisabled()) { - continue; - } - destUrl = KUrl(destUrl.path(KUrl::AddTrailingSlash) + item->data(0, Qt::UserRole).toString() + "/"); + destPath += item->data(0, Qt::UserRole).toString() + '/'; + destUrl = KUrl(archive_url->url().path(KUrl::AddTrailingSlash) + destPath); QStringList srcFiles = item->data(0, Qt::UserRole + 1).toStringList(); for (int k = 0; k < srcFiles.count(); k++) { files << KUrl(srcFiles.at(k)); @@ -298,35 +582,54 @@ bool ArchiveWidget::slotStartArchiving(bool firstPass) else { // We must rename the destination file, since another file with same name exists //TODO: monitor progress - m_duplicateFiles.insert(KUrl(item->text(0)), KUrl(destUrl.path(KUrl::AddTrailingSlash) + item->data(0, Qt::UserRole).toString())); + if (isArchive) { + m_filesList.insert(item->text(0), destPath + item->data(0, Qt::UserRole).toString()); + } + else m_duplicateFiles.insert(KUrl(item->text(0)), KUrl(destUrl.path(KUrl::AddTrailingSlash) + item->data(0, Qt::UserRole).toString())); } } + if (!isSlideshow) parentItem->setDisabled(true); break; } } - if (destUrl.isEmpty()) { + if (items == 0) { + // No clips to archive + slotArchivingFinished(NULL, true); + return true; + } + + if (destPath.isEmpty()) { if (m_duplicateFiles.isEmpty()) return false; QMapIterator i(m_duplicateFiles); - KUrl startJob; if (i.hasNext()) { i.next(); - startJob = i.key(); - KIO::CopyJob *job = KIO::copyAs(startJob, i.value(), KIO::HideProgressInfo); - connect(job, SIGNAL(result(KJob *)), this, SLOT(slotArchivingFinished(KJob *))); - connect(job, SIGNAL(processedSize(KJob *, qulonglong)), this, SLOT(slotArchivingProgress(KJob *, qulonglong))); - m_duplicateFiles.remove(startJob); + KUrl startJobSrc = i.key(); + KUrl startJobDst = i.value(); + m_duplicateFiles.remove(startJobSrc); + KIO::CopyJob *job = KIO::copyAs(startJobSrc, startJobDst, KIO::HideProgressInfo); + connect(job, SIGNAL(result(KJob*)), this, SLOT(slotArchivingFinished(KJob*))); + connect(job, SIGNAL(processedSize(KJob*,qulonglong)), this, SLOT(slotArchivingProgress(KJob*,qulonglong))); } return true; } - if (files.isEmpty()) slotStartArchiving(false); - - KIO::NetAccess::mkdir(destUrl, this); - m_copyJob = KIO::copy (files, destUrl, KIO::HideProgressInfo); - connect(m_copyJob, SIGNAL(result(KJob *)), this, SLOT(slotArchivingFinished(KJob *))); - connect(m_copyJob, SIGNAL(processedSize(KJob *, qulonglong)), this, SLOT(slotArchivingProgress(KJob *, qulonglong))); - + if (isArchive) { + m_foldersList.append(destPath); + for (int i = 0; i < files.count(); ++i) { + m_filesList.insert(files.at(i).path(), destPath + files.at(i).fileName()); + } + slotArchivingFinished(); + } + else if (files.isEmpty()) { + slotStartArchiving(false); + } + else { + KIO::NetAccess::mkdir(destUrl, this); + m_copyJob = KIO::copy (files, destUrl, KIO::HideProgressInfo); + connect(m_copyJob, SIGNAL(result(KJob*)), this, SLOT(slotArchivingFinished(KJob*))); + connect(m_copyJob, SIGNAL(processedSize(KJob*,qulonglong)), this, SLOT(slotArchivingProgress(KJob*,qulonglong))); + } if (firstPass) { progressBar->setValue(0); buttonBox->button(QDialogButtonBox::Apply)->setText(i18n("Abort")); @@ -334,34 +637,38 @@ bool ArchiveWidget::slotStartArchiving(bool firstPass) return true; } -void ArchiveWidget::slotArchivingFinished(KJob *job) +void ArchiveWidget::slotArchivingFinished(KJob *job, bool finished) { - if (job->error() == 0) { - if (slotStartArchiving(false)) { + if (job == NULL || job->error() == 0) { + if (!finished && slotStartArchiving(false)) { // We still have files to archive return; } - else { + else if (!compressed_archive->isChecked()) { // Archiving finished progressBar->setValue(100); if (processProjectFile()) { - icon_info->setPixmap(KIcon("dialog-ok").pixmap(16, 16)); - text_info->setText(i18n("Project was successfully archived.")); + slotJobResult(true, i18n("Project was successfully archived.")); } else { - icon_info->setPixmap(KIcon("dialog-close").pixmap(16, 16)); - text_info->setText(i18n("There was an error processing project file")); + slotJobResult(false, i18n("There was an error processing project file")); } - } + } else processProjectFile(); } else { m_copyJob = NULL; - icon_info->setPixmap(KIcon("dialog-close").pixmap(16, 16)); - text_info->setText(i18n("There was an error while copying the files: %1", job->errorString())); + slotJobResult(false, i18n("There was an error while copying the files: %1", job->errorString())); } - buttonBox->button(QDialogButtonBox::Apply)->setText(i18n("Archive")); - for (int i = 0; i < files_list->topLevelItemCount(); i++) { - files_list->topLevelItem(i)->setDisabled(false); + if (!compressed_archive->isChecked()) { + buttonBox->button(QDialogButtonBox::Apply)->setText(i18n("Archive")); + archive_url->setEnabled(true); + proxy_only->setEnabled(true); + compressed_archive->setEnabled(true); + for (int i = 0; i < files_list->topLevelItemCount(); ++i) { + files_list->topLevelItem(i)->setDisabled(false); + for (int j = 0; j < files_list->topLevelItem(i)->childCount(); j++) + files_list->topLevelItem(i)->child(j)->setDisabled(false); + } } } @@ -375,7 +682,9 @@ bool ArchiveWidget::processProjectFile() { KUrl destUrl; QTreeWidgetItem *item; - for (int i = 0; i < files_list->topLevelItemCount(); i++) { + bool isArchive = compressed_archive->isChecked(); + + for (int i = 0; i < files_list->topLevelItemCount(); ++i) { QTreeWidgetItem *parentItem = files_list->topLevelItem(i); if (parentItem->childCount() > 0) { destUrl = KUrl(archive_url->url().path(KUrl::AddTrailingSlash) + parentItem->data(0, Qt::UserRole).toString()); @@ -385,7 +694,7 @@ bool ArchiveWidget::processProjectFile() KUrl src(item->text(0)); KUrl dest = destUrl; if (isSlideshow) { - dest = KUrl(destUrl.path(KUrl::AddTrailingSlash) + item->data(0, Qt::UserRole).toString() + "/" + src.fileName()); + dest = KUrl(destUrl.path(KUrl::AddTrailingSlash) + item->data(0, Qt::UserRole).toString() + '/' + src.fileName()); } else if (item->data(0, Qt::UserRole).isNull()) { dest.addPath(src.fileName()); @@ -394,17 +703,24 @@ bool ArchiveWidget::processProjectFile() dest.addPath(item->data(0, Qt::UserRole).toString()); } m_replacementList.insert(src, dest); - kDebug()<<"___ PROCESS ITEM :"<url().path(KUrl::RemoveTrailingSlash); + mlt.setAttribute("root", basePath); + QDomElement project = mlt.firstChildElement("kdenlivedoc"); + project.setAttribute("projectfolder", basePath); + + // process kdenlive producers QDomNodeList prods = mlt.elementsByTagName("kdenlive_producer"); - for (int i = 0; i < prods.count(); i++) { + for (int i = 0; i < prods.count(); ++i) { QDomElement e = prods.item(i).toElement(); if (e.isNull()) continue; if (e.hasAttribute("resource")) { @@ -421,7 +737,7 @@ bool ArchiveWidget::processProjectFile() // process mlt producers prods = mlt.elementsByTagName("producer"); - for (int i = 0; i < prods.count(); i++) { + for (int i = 0; i < prods.count(); ++i) { QDomElement e = prods.item(i).toElement(); if (e.isNull()) continue; QString src = EffectsList::property(e, "resource"); @@ -436,7 +752,7 @@ bool ArchiveWidget::processProjectFile() // process mlt transitions (for luma files) prods = mlt.elementsByTagName("transition"); QString attribute; - for (int i = 0; i < prods.count(); i++) { + for (int i = 0; i < prods.count(); ++i) { QDomElement e = prods.item(i).toElement(); if (e.isNull()) continue; attribute = "resource"; @@ -451,17 +767,38 @@ bool ArchiveWidget::processProjectFile() } } - QString path = archive_url->url().path(KUrl::AddTrailingSlash) + "project.kdenlive"; + QString playList = m_doc.toString(); + if (isArchive) { + QString startString("\""); + startString.append(archive_url->url().path(KUrl::RemoveTrailingSlash)); + QString endString("\""); + endString.append(basePath); + playList.replace(startString, endString); + startString = '>' + archive_url->url().path(KUrl::RemoveTrailingSlash); + endString = '>' + basePath; + playList.replace(startString, endString); + } + + if (isArchive) { + m_temp = new KTemporaryFile; + if (!m_temp->open()) KMessageBox::error(this, i18n("Cannot create temporary file")); + m_temp->write(playList.toUtf8()); + m_temp->close(); + m_archiveThread = QtConcurrent::run(this, &ArchiveWidget::createArchive); + return true; + } + + QString path = archive_url->url().path(KUrl::AddTrailingSlash) + m_name + ".kdenlive"; QFile file(path); if (!file.open(QIODevice::WriteOnly | QIODevice::Text)) { kWarning() << "////// ERROR writing to file: " << path; - KMessageBox::error(kapp->activeWindow(), i18n("Cannot write to file %1", path)); + KMessageBox::error(this, i18n("Cannot write to file %1", path)); return false; } file.write(m_doc.toString().toUtf8()); if (file.error() != QFile::NoError) { - KMessageBox::error(kapp->activeWindow(), i18n("Cannot write to file %1", path)); + KMessageBox::error(this, i18n("Cannot write to file %1", path)); file.close(); return false; } @@ -469,3 +806,214 @@ bool ArchiveWidget::processProjectFile() return true; } +void ArchiveWidget::createArchive() +{ + QFileInfo dirInfo(archive_url->url().path()); + QString user = dirInfo.owner(); + QString group = dirInfo.group(); + KTar archive(archive_url->url().path(KUrl::AddTrailingSlash) + m_name + ".tar.gz", "application/x-gzip"); + archive.open( QIODevice::WriteOnly ); + + // Create folders + foreach(const QString &path, m_foldersList) { + archive.writeDir(path, user, group); + } + + // Add files + int ix = 0; + QMapIterator i(m_filesList); + while (i.hasNext()) { + i.next(); + archive.addLocalFile(i.key(), i.value()); + emit archiveProgress((int) 100 * ix / m_filesList.count()); + ix++; + } + + // Add project file + bool result = false; + if (m_temp) { + archive.addLocalFile(m_temp->fileName(), m_name + ".kdenlive"); + result = archive.close(); + delete m_temp; + m_temp = 0; + } + emit archivingFinished(result); +} + +void ArchiveWidget::slotArchivingFinished(bool result) +{ + if (result) { + slotJobResult(true, i18n("Project was successfully archived.")); + buttonBox->button(QDialogButtonBox::Apply)->setEnabled(false); + } + else { + slotJobResult(false, i18n("There was an error processing project file")); + } + progressBar->setValue(100); + buttonBox->button(QDialogButtonBox::Apply)->setText(i18n("Archive")); + archive_url->setEnabled(true); + proxy_only->setEnabled(true); + compressed_archive->setEnabled(true); + for (int i = 0; i < files_list->topLevelItemCount(); ++i) { + files_list->topLevelItem(i)->setDisabled(false); + for (int j = 0; j < files_list->topLevelItem(i)->childCount(); j++) + files_list->topLevelItem(i)->child(j)->setDisabled(false); + } +} + +void ArchiveWidget::slotArchivingProgress(int p) +{ + progressBar->setValue(p); +} + +void ArchiveWidget::slotStartExtracting() +{ + if (m_archiveThread.isRunning()) { + //TODO: abort extracting + return; + } + QFileInfo f(m_extractUrl.path()); + m_requestedSize = f.size(); + KIO::NetAccess::mkdir(archive_url->url().path(KUrl::RemoveTrailingSlash), this); + slotDisplayMessage("system-run", i18n("Extracting...")); + buttonBox->button(QDialogButtonBox::Apply)->setText(i18n("Abort")); + m_archiveThread = QtConcurrent::run(this, &ArchiveWidget::doExtracting); + m_progressTimer->start(); +} + +void ArchiveWidget::slotExtractProgress() +{ + KIO::DirectorySizeJob *job = KIO::directorySize(archive_url->url()); + connect(job, SIGNAL(result(KJob*)), this, SLOT(slotGotProgress(KJob*))); +} + +void ArchiveWidget::slotGotProgress(KJob* job) +{ + if (!job->error()) { + KIO::DirectorySizeJob *j = static_cast (job); + progressBar->setValue((int) 100 * j->totalSize() / m_requestedSize); + } + job->deleteLater(); +} + +void ArchiveWidget::doExtracting() +{ + m_extractArchive->directory()->copyTo(archive_url->url().path(KUrl::AddTrailingSlash)); + m_extractArchive->close(); + emit extractingFinished(); +} + +QString ArchiveWidget::extractedProjectFile() const +{ + return archive_url->url().path(KUrl::AddTrailingSlash) + m_projectName; +} + +void ArchiveWidget::slotExtractingFinished() +{ + m_progressTimer->stop(); + // Process project file + QFile file(extractedProjectFile()); + bool error = false; + if (!file.open(QIODevice::ReadOnly | QIODevice::Text)) { + error = true; + } + else { + QString playList = QString::fromUtf8(file.readAll()); + file.close(); + if (playList.isEmpty()) { + error = true; + } + else { + playList.replace("$CURRENTPATH", archive_url->url().path(KUrl::RemoveTrailingSlash)); + if (!file.open(QIODevice::WriteOnly | QIODevice::Text)) { + kWarning() << "////// ERROR writing to file: "; + error = true; + } + else { + file.write(playList.toUtf8()); + if (file.error() != QFile::NoError) { + error = true; + } + file.close(); + } + } + } + if (error) { + KMessageBox::sorry(kapp->activeWindow(), i18n("Cannot open project file %1", extractedProjectFile()), i18n("Cannot open file")); + reject(); + } + else accept(); +} + +void ArchiveWidget::slotProxyOnly(int onlyProxy) +{ + m_requestedSize = 0; + if (onlyProxy == Qt::Checked) { + // Archive proxy clips + QStringList proxyIdList; + QTreeWidgetItem *parentItem = NULL; + + // Build list of existing proxy ids + for (int i = 0; i < files_list->topLevelItemCount(); ++i) { + parentItem = files_list->topLevelItem(i); + if (parentItem->data(0, Qt::UserRole).toString() == "proxy") break; + } + if (!parentItem) return; + int items = parentItem->childCount(); + for (int j = 0; j < items; j++) { + proxyIdList << parentItem->child(j)->data(0, Qt::UserRole + 2).toString(); + } + + // Parse all items to disable original clips for existing proxies + for (int i = 0; i < proxyIdList.count(); ++i) { + QString id = proxyIdList.at(i); + if (id.isEmpty()) continue; + for (int j = 0; j < files_list->topLevelItemCount(); j++) { + parentItem = files_list->topLevelItem(j); + if (parentItem->data(0, Qt::UserRole).toString() == "proxy") continue; + items = parentItem->childCount(); + for (int k = 0; k < items; k++) { + if (parentItem->child(k)->data(0, Qt::UserRole + 2).toString() == id) { + // This item has a proxy, do not archive it + parentItem->child(k)->setFlags(Qt::ItemIsSelectable); + break; + } + } + } + } + } + else { + // Archive all clips + for (int i = 0; i < files_list->topLevelItemCount(); ++i) { + QTreeWidgetItem *parentItem = files_list->topLevelItem(i); + int items = parentItem->childCount(); + for (int j = 0; j < items; j++) { + parentItem->child(j)->setFlags(Qt::ItemIsEnabled | Qt::ItemIsSelectable); + } + } + } + + // Calculate requested size + int total = 0; + for (int i = 0; i < files_list->topLevelItemCount(); ++i) { + QTreeWidgetItem *parentItem = files_list->topLevelItem(i); + int items = parentItem->childCount(); + int itemsCount = 0; + bool isSlideshow = parentItem->data(0, Qt::UserRole).toString() == "slideshows"; + + for (int j = 0; j < items; j++) { + if (!parentItem->child(j)->isDisabled()) { + m_requestedSize += parentItem->child(j)->data(0, Qt::UserRole + 3).toInt(); + if (isSlideshow) total += parentItem->child(j)->data(0, Qt::UserRole + 1).toStringList().count(); + else total ++; + itemsCount ++; + } + } + parentItem->setText(0, parentItem->text(0).section('(', 0, 0) + i18np("(%1 item)", "(%1 items)", itemsCount)); + } + project_files->setText(i18np("%1 file to archive, requires %2", "%1 files to archive, requires %2", total, KIO::convertSize(m_requestedSize))); + slotCheckSpace(); +} + + +#include "archivewidget.moc"