1 /***************************************************************************
2 * Copyright (C) 2011 by Jean-Baptiste Mardelle (jb@kdenlive.org) *
4 * This program is free software; you can redistribute it and/or modify *
5 * it under the terms of the GNU General Public License as published by *
6 * the Free Software Foundation; either version 2 of the License, or *
7 * (at your option) any later version. *
9 * This program is distributed in the hope that it will be useful, *
10 * but WITHOUT ANY WARRANTY; without even the implied warranty of *
11 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the *
12 * GNU General Public License for more details. *
14 * You should have received a copy of the GNU General Public License *
15 * along with this program; if not, write to the *
16 * Free Software Foundation, Inc., *
17 * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA *
18 ***************************************************************************/
21 #include "archivewidget.h"
22 #include "titlewidget.h"
24 #include <KLocalizedString>
25 #include <KDiskFreeSpaceInfo>
26 #include <KUrlRequester>
27 #include <KFileDialog>
28 #include <KMessageBox>
30 #include <KIO/NetAccess>
33 #include <KApplication>
34 #include <kio/directorysizejob.h>
35 #if KDE_IS_VERSION(4,7,0)
36 #include <KMessageWidget>
39 #include <QTreeWidget>
40 #include <QtConcurrentRun>
41 #include "projectsettings.h"
44 ArchiveWidget::ArchiveWidget(const QString &projectName, const QDomDocument &doc, const QList <DocClipBase*> &list, const QStringList &luma_list, QWidget * parent) :
48 , m_name(projectName.section('.', 0, -2))
51 , m_abortArchive(false)
52 , m_extractMode(false)
53 , m_progressTimer(NULL)
54 , m_extractArchive(NULL)
57 setAttribute(Qt::WA_DeleteOnClose);
59 setWindowTitle(i18n("Archive Project"));
60 archive_url->setUrl(KUrl(QDir::homePath()));
61 connect(archive_url, SIGNAL(textChanged(QString)), this, SLOT(slotCheckSpace()));
62 connect(this, SIGNAL(archivingFinished(bool)), this, SLOT(slotArchivingFinished(bool)));
63 connect(this, SIGNAL(archiveProgress(int)), this, SLOT(slotArchivingProgress(int)));
64 connect(proxy_only, SIGNAL(stateChanged(int)), this, SLOT(slotProxyOnly(int)));
67 QTreeWidgetItem *videos = new QTreeWidgetItem(files_list, QStringList() << i18n("Video clips"));
68 videos->setIcon(0, KIcon("video-x-generic"));
69 videos->setData(0, Qt::UserRole, "videos");
70 videos->setExpanded(false);
71 QTreeWidgetItem *sounds = new QTreeWidgetItem(files_list, QStringList() << i18n("Audio clips"));
72 sounds->setIcon(0, KIcon("audio-x-generic"));
73 sounds->setData(0, Qt::UserRole, "sounds");
74 sounds->setExpanded(false);
75 QTreeWidgetItem *images = new QTreeWidgetItem(files_list, QStringList() << i18n("Image clips"));
76 images->setIcon(0, KIcon("image-x-generic"));
77 images->setData(0, Qt::UserRole, "images");
78 images->setExpanded(false);
79 QTreeWidgetItem *slideshows = new QTreeWidgetItem(files_list, QStringList() << i18n("Slideshow clips"));
80 slideshows->setIcon(0, KIcon("image-x-generic"));
81 slideshows->setData(0, Qt::UserRole, "slideshows");
82 slideshows->setExpanded(false);
83 QTreeWidgetItem *texts = new QTreeWidgetItem(files_list, QStringList() << i18n("Text clips"));
84 texts->setIcon(0, KIcon("text-plain"));
85 texts->setData(0, Qt::UserRole, "texts");
86 texts->setExpanded(false);
87 QTreeWidgetItem *playlists = new QTreeWidgetItem(files_list, QStringList() << i18n("Playlist clips"));
88 playlists->setIcon(0, KIcon("video-mlt-playlist"));
89 playlists->setData(0, Qt::UserRole, "playlist");
90 playlists->setExpanded(false);
91 QTreeWidgetItem *others = new QTreeWidgetItem(files_list, QStringList() << i18n("Other clips"));
92 others->setIcon(0, KIcon("unknown"));
93 others->setData(0, Qt::UserRole, "others");
94 others->setExpanded(false);
95 QTreeWidgetItem *lumas = new QTreeWidgetItem(files_list, QStringList() << i18n("Luma files"));
96 lumas->setIcon(0, KIcon("image-x-generic"));
97 lumas->setData(0, Qt::UserRole, "lumas");
98 lumas->setExpanded(false);
100 QTreeWidgetItem *proxies = new QTreeWidgetItem(files_list, QStringList() << i18n("Proxy clips"));
101 proxies->setIcon(0, KIcon("video-x-generic"));
102 proxies->setData(0, Qt::UserRole, "proxy");
103 proxies->setExpanded(false);
106 QStringList allFonts;
108 QStringList fileNames;
109 QStringList extraImageUrls;
110 QStringList otherUrls;
111 generateItems(lumas, luma_list);
113 QMap <QString, QString> slideUrls;
114 QMap <QString, QString> audioUrls;
115 QMap <QString, QString>videoUrls;
116 QMap <QString, QString>imageUrls;
117 QMap <QString, QString>playlistUrls;
118 QMap <QString, QString>proxyUrls;
120 for (int i = 0; i < list.count(); ++i) {
121 DocClipBase *clip = list.at(i);
122 CLIPTYPE t = clip->clipType();
123 QString id = clip->getId();
124 if (t == SLIDESHOW) {
125 KUrl slideUrl = clip->fileURL();
126 //TODO: Slideshow files
127 slideUrls.insert(id, slideUrl.path());
129 else if (t == IMAGE) imageUrls.insert(id, clip->fileURL().path());
130 else if (t == TEXT) {
131 QStringList imagefiles = TitleWidget::extractImageList(clip->getProperty("xmldata"));
132 QStringList fonts = TitleWidget::extractFontList(clip->getProperty("xmldata"));
133 extraImageUrls << imagefiles;
135 } else if (t == PLAYLIST) {
136 playlistUrls.insert(id, clip->fileURL().path());
137 QStringList files = ProjectSettings::extractPlaylistUrls(clip->fileURL().path());
140 else if (!clip->fileURL().isEmpty()) {
141 if (t == AUDIO) audioUrls.insert(id, clip->fileURL().path());
143 videoUrls.insert(id, clip->fileURL().path());
144 // Check if we have a proxy
145 QString proxy = clip->getProperty("proxy");
146 if (!proxy.isEmpty() && proxy != "-" && QFile::exists(proxy)) proxyUrls.insert(id, proxy);
151 generateItems(images, extraImageUrls);
152 generateItems(sounds, audioUrls);
153 generateItems(videos, videoUrls);
154 generateItems(images, imageUrls);
155 generateItems(slideshows, slideUrls);
156 generateItems(playlists, playlistUrls);
157 generateItems(others, otherUrls);
158 generateItems(proxies, proxyUrls);
160 allFonts.removeDuplicates();
162 #if KDE_IS_VERSION(4,7,0)
163 m_infoMessage = new KMessageWidget(this);
164 QVBoxLayout *s = static_cast <QVBoxLayout*> (layout());
165 s->insertWidget(5, m_infoMessage);
166 m_infoMessage->setCloseButtonVisible(false);
167 m_infoMessage->setWordWrap(true);
168 m_infoMessage->hide();
171 // missing clips, warn user
172 if (m_missingClips > 0) {
173 QString infoText = i18np("You have %1 missing clip in your project.", "You have %1 missing clips in your project.", m_missingClips);
174 #if KDE_IS_VERSION(4,7,0)
175 m_infoMessage->setMessageType(KMessageWidget::Warning);
176 m_infoMessage->setText(infoText);
177 m_infoMessage->animatedShow();
179 KMessageBox::sorry(this, infoText);
185 // Hide unused categories, add item count
187 for (int i = 0; i < files_list->topLevelItemCount(); ++i) {
188 QTreeWidgetItem *parentItem = files_list->topLevelItem(i);
189 int items = parentItem->childCount();
191 files_list->topLevelItem(i)->setHidden(true);
194 if (parentItem->data(0, Qt::UserRole).toString() == "slideshows")
196 // Special case: slideshows contain several files
197 for (int j = 0; j < items; j++) {
198 total += parentItem->child(j)->data(0, Qt::UserRole + 1).toStringList().count();
202 parentItem->setText(0, files_list->topLevelItem(i)->text(0) + ' ' + i18np("(%1 item)", "(%1 items)", items));
205 if (m_name.isEmpty()) m_name = i18n("Untitled");
206 compressed_archive->setText(compressed_archive->text() + " (" + m_name + ".tar.gz)");
207 project_files->setText(i18np("%1 file to archive, requires %2", "%1 files to archive, requires %2", total, KIO::convertSize(m_requestedSize)));
208 buttonBox->button(QDialogButtonBox::Apply)->setText(i18n("Archive"));
209 connect(buttonBox->button(QDialogButtonBox::Apply), SIGNAL(clicked()), this, SLOT(slotStartArchiving()));
210 buttonBox->button(QDialogButtonBox::Apply)->setEnabled(false);
215 // Constructor for extract widget
216 ArchiveWidget::ArchiveWidget(const KUrl &url, QWidget * parent):
221 //setAttribute(Qt::WA_DeleteOnClose);
224 m_progressTimer = new QTimer;
225 m_progressTimer->setInterval(800);
226 m_progressTimer->setSingleShot(false);
227 connect(m_progressTimer, SIGNAL(timeout()), this, SLOT(slotExtractProgress()));
228 connect(this, SIGNAL(extractingFinished()), this, SLOT(slotExtractingFinished()));
229 connect(this, SIGNAL(showMessage(QString,QString)), this, SLOT(slotDisplayMessage(QString,QString)));
231 compressed_archive->setHidden(true);
232 proxy_only->setHidden(true);
233 project_files->setHidden(true);
234 files_list->setHidden(true);
235 label->setText(i18n("Extract to"));
236 setWindowTitle(i18n("Open Archived Project"));
237 archive_url->setUrl(KUrl(QDir::homePath()));
238 buttonBox->button(QDialogButtonBox::Apply)->setText(i18n("Extract"));
239 connect(buttonBox->button(QDialogButtonBox::Apply), SIGNAL(clicked()), this, SLOT(slotStartExtracting()));
240 buttonBox->button(QDialogButtonBox::Apply)->setEnabled(true);
242 m_archiveThread = QtConcurrent::run(this, &ArchiveWidget::openArchiveForExtraction);
246 ArchiveWidget::~ArchiveWidget()
248 delete m_extractArchive;
249 delete m_progressTimer;
252 void ArchiveWidget::slotDisplayMessage(const QString &icon, const QString &text)
254 icon_info->setPixmap(KIcon(icon).pixmap(16, 16));
255 text_info->setText(text);
258 void ArchiveWidget::slotJobResult(bool success, const QString &text)
260 #if KDE_IS_VERSION(4,7,0)
261 m_infoMessage->setMessageType(success ? KMessageWidget::Positive : KMessageWidget::Warning);
262 m_infoMessage->setText(text);
263 m_infoMessage->animatedShow();
265 if (success) icon_info->setPixmap(KIcon("dialog-ok").pixmap(16, 16));
266 else icon_info->setPixmap(KIcon("dialog-close").pixmap(16, 16));
267 text_info->setText(text);
271 void ArchiveWidget::openArchiveForExtraction()
273 emit showMessage("system-run", i18n("Opening archive..."));
274 m_extractArchive = new KTar(m_extractUrl.path());
275 if (!m_extractArchive->isOpen() && !m_extractArchive->open( QIODevice::ReadOnly )) {
276 emit showMessage("dialog-close", i18n("Cannot open archive file:\n %1", m_extractUrl.path()));
277 groupBox->setEnabled(false);
281 // Check that it is a kdenlive project archive
282 bool isProjectArchive = false;
283 QStringList files = m_extractArchive->directory()->entries();
284 for (int i = 0; i < files.count(); ++i) {
285 if (files.at(i).endsWith(".kdenlive")) {
286 m_projectName = files.at(i);
287 isProjectArchive = true;
292 if (!isProjectArchive) {
293 emit showMessage("dialog-close", i18n("File %1\n is not an archived Kdenlive project", m_extractUrl.path()));
294 groupBox->setEnabled(false);
295 buttonBox->button(QDialogButtonBox::Apply)->setEnabled(false);
298 buttonBox->button(QDialogButtonBox::Apply)->setEnabled(true);
299 emit showMessage("dialog-ok", i18n("Ready"));
302 void ArchiveWidget::done ( int r )
304 if (closeAccepted()) QDialog::done(r);
307 void ArchiveWidget::closeEvent ( QCloseEvent * e )
310 if (closeAccepted()) e->accept();
315 bool ArchiveWidget::closeAccepted()
317 if (!m_extractMode && !archive_url->isEnabled()) {
318 // Archiving in progress, should we stop?
319 if (KMessageBox::warningContinueCancel(this, i18n("Archiving in progress, do you want to stop it?"), i18n("Stop Archiving"), KGuiItem(i18n("Stop Archiving"))) != KMessageBox::Continue) {
322 if (m_copyJob) m_copyJob->kill();
328 void ArchiveWidget::generateItems(QTreeWidgetItem *parentItem, const QStringList& items)
330 QStringList filesList;
333 bool isSlideshow = parentItem->data(0, Qt::UserRole).toString() == "slideshows";
334 foreach(const QString & file, items) {
335 QTreeWidgetItem *item = new QTreeWidgetItem(parentItem, QStringList() << file);
336 fileName = KUrl(file).fileName();
338 // we store each slideshow in a separate subdirectory
339 item->setData(0, Qt::UserRole, ix);
342 QDir dir(slideUrl.directory(KUrl::AppendTrailingSlash));
343 if (slideUrl.fileName().startsWith(".all.")) {
344 // mimetype slideshow (for example *.png)
346 // TODO: improve jpeg image detection with extension like jpeg, requires change in MLT image producers
347 filters << "*." + slideUrl.fileName().section('.', -1);
348 dir.setNameFilters(filters);
349 QFileInfoList resultList = dir.entryInfoList(QDir::Files);
350 QStringList slideImages;
351 qint64 totalSize = 0;
352 for (int i = 0; i < resultList.count(); ++i) {
353 totalSize += resultList.at(i).size();
354 slideImages << resultList.at(i).absoluteFilePath();
356 item->setData(0, Qt::UserRole + 1, slideImages);
357 item->setData(0, Qt::UserRole + 3, totalSize);
358 m_requestedSize += totalSize;
361 // pattern url (like clip%.3d.png)
362 QStringList result = dir.entryList(QDir::Files);
363 QString filter = slideUrl.fileName();
364 QString ext = filter.section('.', -1);
365 filter = filter.section('%', 0, -2);
366 QString regexp = '^' + filter + "\\d+\\." + ext + '$';
368 QStringList slideImages;
369 QString directory = dir.absolutePath();
370 if (!directory.endsWith('/')) directory.append('/');
371 qint64 totalSize = 0;
372 foreach(const QString & path, result) {
373 if (rx.exactMatch(path)) {
374 totalSize += QFileInfo(directory + path).size();
375 slideImages << directory + path;
378 item->setData(0, Qt::UserRole + 1, slideImages);
379 item->setData(0, Qt::UserRole + 3, totalSize);
380 m_requestedSize += totalSize;
383 else if (filesList.contains(fileName)) {
384 // we have 2 files with same name
386 QString newFileName = fileName.section('.', 0, -2) + '_' + QString::number(ix) + '.' + fileName.section('.', -1);
387 while (filesList.contains(newFileName)) {
389 newFileName = fileName.section('.', 0, -2) + '_' + QString::number(ix) + '.' + fileName.section('.', -1);
391 fileName = newFileName;
392 item->setData(0, Qt::UserRole, fileName);
395 qint64 fileSize = QFileInfo(file).size();
397 item->setIcon(0, KIcon("edit-delete"));
401 m_requestedSize += fileSize;
402 item->setData(0, Qt::UserRole + 3, fileSize);
404 filesList << fileName;
409 void ArchiveWidget::generateItems(QTreeWidgetItem *parentItem, const QMap <QString, QString>& items)
411 QStringList filesList;
414 bool isSlideshow = parentItem->data(0, Qt::UserRole).toString() == "slideshows";
415 QMap<QString, QString>::const_iterator it = items.constBegin();
416 while (it != items.constEnd()) {
417 QString file = it.value();
418 QTreeWidgetItem *item = new QTreeWidgetItem(parentItem, QStringList() << file);
419 // Store the clip's id
420 item->setData(0, Qt::UserRole + 2, it.key());
421 fileName = KUrl(file).fileName();
423 // we store each slideshow in a separate subdirectory
424 item->setData(0, Qt::UserRole, ix);
427 QDir dir(slideUrl.directory(KUrl::AppendTrailingSlash));
428 if (slideUrl.fileName().startsWith(".all.")) {
429 // mimetype slideshow (for example *.png)
431 // TODO: improve jpeg image detection with extension like jpeg, requires change in MLT image producers
432 filters << "*." + slideUrl.fileName().section('.', -1);
433 dir.setNameFilters(filters);
434 QFileInfoList resultList = dir.entryInfoList(QDir::Files);
435 QStringList slideImages;
436 qint64 totalSize = 0;
437 for (int i = 0; i < resultList.count(); ++i) {
438 totalSize += resultList.at(i).size();
439 slideImages << resultList.at(i).absoluteFilePath();
441 item->setData(0, Qt::UserRole + 1, slideImages);
442 item->setData(0, Qt::UserRole + 3, totalSize);
443 m_requestedSize += totalSize;
446 // pattern url (like clip%.3d.png)
447 QStringList result = dir.entryList(QDir::Files);
448 QString filter = slideUrl.fileName();
449 QString ext = filter.section('.', -1);
450 filter = filter.section('%', 0, -2);
451 QString regexp = '^' + filter + "\\d+\\." + ext + '$';
453 QStringList slideImages;
454 qint64 totalSize = 0;
455 QString directory = dir.absolutePath();
456 if (!directory.endsWith('/')) directory.append('/');
457 foreach(const QString & path, result) {
458 if (rx.exactMatch(path)) {
459 totalSize += QFileInfo(directory + path).size();
460 slideImages << directory + path;
463 item->setData(0, Qt::UserRole + 1, slideImages);
464 item->setData(0, Qt::UserRole + 3, totalSize);
465 m_requestedSize += totalSize;
468 else if (filesList.contains(fileName)) {
469 // we have 2 files with same name
471 QString newFileName = fileName.section('.', 0, -2) + '_' + QString::number(ix) + '.' + fileName.section('.', -1);
472 while (filesList.contains(newFileName)) {
474 newFileName = fileName.section('.', 0, -2) + "_" + QString::number(ix) + "." + fileName.section('.', -1);
476 fileName = newFileName;
477 item->setData(0, Qt::UserRole, fileName);
480 qint64 fileSize = QFileInfo(file).size();
482 item->setIcon(0, KIcon("edit-delete"));
486 m_requestedSize += fileSize;
487 item->setData(0, Qt::UserRole + 3, fileSize);
489 filesList << fileName;
495 void ArchiveWidget::slotCheckSpace()
497 KDiskFreeSpaceInfo inf = KDiskFreeSpaceInfo::freeSpaceInfo( archive_url->url().path());
498 KIO::filesize_t freeSize = inf.available();
499 if (freeSize > m_requestedSize) {
501 buttonBox->button(QDialogButtonBox::Apply)->setEnabled(true);
502 slotDisplayMessage("dialog-ok", i18n("Available space on drive: %1", KIO::convertSize(freeSize)));
505 buttonBox->button(QDialogButtonBox::Apply)->setEnabled(false);
506 slotDisplayMessage("dialog-close", i18n("Not enough space on drive, free space: %1", KIO::convertSize(freeSize)));
510 bool ArchiveWidget::slotStartArchiving(bool firstPass)
512 if (firstPass && (m_copyJob || m_archiveThread.isRunning())) {
513 // archiving in progress, abort
514 if (m_copyJob) m_copyJob->kill(KJob::EmitResult);
515 m_abortArchive = true;
518 bool isArchive = compressed_archive->isChecked();
519 if (!firstPass) m_copyJob = NULL;
522 m_abortArchive = false;
523 m_duplicateFiles.clear();
524 m_replacementList.clear();
525 m_foldersList.clear();
527 slotDisplayMessage("system-run", i18n("Archiving..."));
529 archive_url->setEnabled(false);
530 proxy_only->setEnabled(false);
531 compressed_archive->setEnabled(false);
536 QTreeWidgetItem *parentItem;
537 bool isSlideshow = false;
540 // We parse all files going into one folder, then start the copy job
541 for (int i = 0; i < files_list->topLevelItemCount(); ++i) {
542 parentItem = files_list->topLevelItem(i);
543 if (parentItem->isDisabled()) {
544 parentItem->setExpanded(false);
547 if (parentItem->childCount() > 0) {
548 if (parentItem->data(0, Qt::UserRole).toString() == "slideshows") {
549 KUrl slideFolder(archive_url->url().path(KUrl::AddTrailingSlash) + "slideshows");
550 if (isArchive) m_foldersList.append("slideshows");
551 else KIO::NetAccess::mkdir(slideFolder, this);
554 else isSlideshow = false;
555 files_list->setCurrentItem(parentItem);
556 parentItem->setExpanded(true);
557 destPath = parentItem->data(0, Qt::UserRole).toString() + '/';
558 destUrl = KUrl(archive_url->url().path(KUrl::AddTrailingSlash) + destPath);
559 QTreeWidgetItem *item;
560 for (int j = 0; j < parentItem->childCount(); j++) {
561 item = parentItem->child(j);
562 if (item->isDisabled()) continue;
563 // Special case: slideshows
566 destPath += item->data(0, Qt::UserRole).toString() + '/';
567 destUrl = KUrl(archive_url->url().path(KUrl::AddTrailingSlash) + destPath);
568 QStringList srcFiles = item->data(0, Qt::UserRole + 1).toStringList();
569 for (int k = 0; k < srcFiles.count(); k++) {
570 files << KUrl(srcFiles.at(k));
572 item->setDisabled(true);
573 if (parentItem->indexOfChild(item) == parentItem->childCount() - 1) {
574 // We have processed all slideshows
575 parentItem->setDisabled(true);
579 else if (item->data(0, Qt::UserRole).isNull()) {
580 files << KUrl(item->text(0));
583 // We must rename the destination file, since another file with same name exists
584 //TODO: monitor progress
586 m_filesList.insert(item->text(0), destPath + item->data(0, Qt::UserRole).toString());
588 else m_duplicateFiles.insert(KUrl(item->text(0)), KUrl(destUrl.path(KUrl::AddTrailingSlash) + item->data(0, Qt::UserRole).toString()));
591 if (!isSlideshow) parentItem->setDisabled(true);
597 // No clips to archive
598 slotArchivingFinished(NULL, true);
602 if (destPath.isEmpty()) {
603 if (m_duplicateFiles.isEmpty()) return false;
604 QMapIterator<KUrl, KUrl> i(m_duplicateFiles);
607 KUrl startJobSrc = i.key();
608 KUrl startJobDst = i.value();
609 m_duplicateFiles.remove(startJobSrc);
610 KIO::CopyJob *job = KIO::copyAs(startJobSrc, startJobDst, KIO::HideProgressInfo);
611 connect(job, SIGNAL(result(KJob*)), this, SLOT(slotArchivingFinished(KJob*)));
612 connect(job, SIGNAL(processedSize(KJob*,qulonglong)), this, SLOT(slotArchivingProgress(KJob*,qulonglong)));
618 m_foldersList.append(destPath);
619 for (int i = 0; i < files.count(); ++i) {
620 m_filesList.insert(files.at(i).path(), destPath + files.at(i).fileName());
622 slotArchivingFinished();
624 else if (files.isEmpty()) {
625 slotStartArchiving(false);
628 KIO::NetAccess::mkdir(destUrl, this);
629 m_copyJob = KIO::copy (files, destUrl, KIO::HideProgressInfo);
630 connect(m_copyJob, SIGNAL(result(KJob*)), this, SLOT(slotArchivingFinished(KJob*)));
631 connect(m_copyJob, SIGNAL(processedSize(KJob*,qulonglong)), this, SLOT(slotArchivingProgress(KJob*,qulonglong)));
634 progressBar->setValue(0);
635 buttonBox->button(QDialogButtonBox::Apply)->setText(i18n("Abort"));
640 void ArchiveWidget::slotArchivingFinished(KJob *job, bool finished)
642 if (job == NULL || job->error() == 0) {
643 if (!finished && slotStartArchiving(false)) {
644 // We still have files to archive
647 else if (!compressed_archive->isChecked()) {
648 // Archiving finished
649 progressBar->setValue(100);
650 if (processProjectFile()) {
651 slotJobResult(true, i18n("Project was successfully archived."));
654 slotJobResult(false, i18n("There was an error processing project file"));
656 } else processProjectFile();
660 slotJobResult(false, i18n("There was an error while copying the files: %1", job->errorString()));
662 if (!compressed_archive->isChecked()) {
663 buttonBox->button(QDialogButtonBox::Apply)->setText(i18n("Archive"));
664 archive_url->setEnabled(true);
665 proxy_only->setEnabled(true);
666 compressed_archive->setEnabled(true);
667 for (int i = 0; i < files_list->topLevelItemCount(); ++i) {
668 files_list->topLevelItem(i)->setDisabled(false);
669 for (int j = 0; j < files_list->topLevelItem(i)->childCount(); j++)
670 files_list->topLevelItem(i)->child(j)->setDisabled(false);
675 void ArchiveWidget::slotArchivingProgress(KJob *, qulonglong size)
677 progressBar->setValue((int) 100 * size / m_requestedSize);
681 bool ArchiveWidget::processProjectFile()
684 QTreeWidgetItem *item;
685 bool isArchive = compressed_archive->isChecked();
687 for (int i = 0; i < files_list->topLevelItemCount(); ++i) {
688 QTreeWidgetItem *parentItem = files_list->topLevelItem(i);
689 if (parentItem->childCount() > 0) {
690 destUrl = KUrl(archive_url->url().path(KUrl::AddTrailingSlash) + parentItem->data(0, Qt::UserRole).toString());
691 bool isSlideshow = parentItem->data(0, Qt::UserRole).toString() == "slideshows";
692 for (int j = 0; j < parentItem->childCount(); j++) {
693 item = parentItem->child(j);
694 KUrl src(item->text(0));
697 dest = KUrl(destUrl.path(KUrl::AddTrailingSlash) + item->data(0, Qt::UserRole).toString() + '/' + src.fileName());
699 else if (item->data(0, Qt::UserRole).isNull()) {
700 dest.addPath(src.fileName());
703 dest.addPath(item->data(0, Qt::UserRole).toString());
705 m_replacementList.insert(src, dest);
710 QDomElement mlt = m_doc.documentElement();
711 QString root = mlt.attribute("root") + '/';
713 // Adjust global settings
715 if (isArchive) basePath = "$CURRENTPATH";
716 else basePath = archive_url->url().path(KUrl::RemoveTrailingSlash);
717 mlt.setAttribute("root", basePath);
718 QDomElement project = mlt.firstChildElement("kdenlivedoc");
719 project.setAttribute("projectfolder", basePath);
721 // process kdenlive producers
722 QDomNodeList prods = mlt.elementsByTagName("kdenlive_producer");
723 for (int i = 0; i < prods.count(); ++i) {
724 QDomElement e = prods.item(i).toElement();
725 if (e.isNull()) continue;
726 if (e.hasAttribute("resource")) {
727 KUrl src(e.attribute("resource"));
728 KUrl dest = m_replacementList.value(src);
729 if (!dest.isEmpty()) e.setAttribute("resource", dest.path());
731 if (e.hasAttribute("proxy") && e.attribute("proxy") != "-") {
732 KUrl src(e.attribute("proxy"));
733 KUrl dest = m_replacementList.value(src);
734 if (!dest.isEmpty()) e.setAttribute("proxy", dest.path());
738 // process mlt producers
739 prods = mlt.elementsByTagName("producer");
740 for (int i = 0; i < prods.count(); ++i) {
741 QDomElement e = prods.item(i).toElement();
742 if (e.isNull()) continue;
743 QString src = EffectsList::property(e, "resource");
744 if (!src.isEmpty()) {
745 if (!src.startsWith('/')) src.prepend(root);
747 KUrl dest = m_replacementList.value(src);
748 if (!dest.isEmpty()) EffectsList::setProperty(e, "resource", dest.path());
752 // process mlt transitions (for luma files)
753 prods = mlt.elementsByTagName("transition");
755 for (int i = 0; i < prods.count(); ++i) {
756 QDomElement e = prods.item(i).toElement();
757 if (e.isNull()) continue;
758 attribute = "resource";
759 QString src = EffectsList::property(e, attribute);
760 if (src.isEmpty()) attribute = "luma";
761 src = EffectsList::property(e, attribute);
762 if (!src.isEmpty()) {
763 if (!src.startsWith('/')) src.prepend(root);
765 KUrl dest = m_replacementList.value(src);
766 if (!dest.isEmpty()) EffectsList::setProperty(e, attribute, dest.path());
770 QString playList = m_doc.toString();
772 QString startString("\"");
773 startString.append(archive_url->url().path(KUrl::RemoveTrailingSlash));
774 QString endString("\"");
775 endString.append(basePath);
776 playList.replace(startString, endString);
777 startString = '>' + archive_url->url().path(KUrl::RemoveTrailingSlash);
778 endString = '>' + basePath;
779 playList.replace(startString, endString);
783 m_temp = new KTemporaryFile;
784 if (!m_temp->open()) KMessageBox::error(this, i18n("Cannot create temporary file"));
785 m_temp->write(playList.toUtf8());
787 m_archiveThread = QtConcurrent::run(this, &ArchiveWidget::createArchive);
791 QString path = archive_url->url().path(KUrl::AddTrailingSlash) + m_name + ".kdenlive";
793 if (!file.open(QIODevice::WriteOnly | QIODevice::Text)) {
794 kWarning() << "////// ERROR writing to file: " << path;
795 KMessageBox::error(this, i18n("Cannot write to file %1", path));
799 file.write(m_doc.toString().toUtf8());
800 if (file.error() != QFile::NoError) {
801 KMessageBox::error(this, i18n("Cannot write to file %1", path));
809 void ArchiveWidget::createArchive()
811 QFileInfo dirInfo(archive_url->url().path());
812 QString user = dirInfo.owner();
813 QString group = dirInfo.group();
814 KTar archive(archive_url->url().path(KUrl::AddTrailingSlash) + m_name + ".tar.gz", "application/x-gzip");
815 archive.open( QIODevice::WriteOnly );
818 foreach(const QString &path, m_foldersList) {
819 archive.writeDir(path, user, group);
824 QMapIterator<QString, QString> i(m_filesList);
825 while (i.hasNext()) {
827 archive.addLocalFile(i.key(), i.value());
828 emit archiveProgress((int) 100 * ix / m_filesList.count());
835 archive.addLocalFile(m_temp->fileName(), m_name + ".kdenlive");
836 result = archive.close();
840 emit archivingFinished(result);
843 void ArchiveWidget::slotArchivingFinished(bool result)
846 slotJobResult(true, i18n("Project was successfully archived."));
847 buttonBox->button(QDialogButtonBox::Apply)->setEnabled(false);
850 slotJobResult(false, i18n("There was an error processing project file"));
852 progressBar->setValue(100);
853 buttonBox->button(QDialogButtonBox::Apply)->setText(i18n("Archive"));
854 archive_url->setEnabled(true);
855 proxy_only->setEnabled(true);
856 compressed_archive->setEnabled(true);
857 for (int i = 0; i < files_list->topLevelItemCount(); ++i) {
858 files_list->topLevelItem(i)->setDisabled(false);
859 for (int j = 0; j < files_list->topLevelItem(i)->childCount(); j++)
860 files_list->topLevelItem(i)->child(j)->setDisabled(false);
864 void ArchiveWidget::slotArchivingProgress(int p)
866 progressBar->setValue(p);
869 void ArchiveWidget::slotStartExtracting()
871 if (m_archiveThread.isRunning()) {
872 //TODO: abort extracting
875 QFileInfo f(m_extractUrl.path());
876 m_requestedSize = f.size();
877 KIO::NetAccess::mkdir(archive_url->url().path(KUrl::RemoveTrailingSlash), this);
878 slotDisplayMessage("system-run", i18n("Extracting..."));
879 buttonBox->button(QDialogButtonBox::Apply)->setText(i18n("Abort"));
880 m_archiveThread = QtConcurrent::run(this, &ArchiveWidget::doExtracting);
881 m_progressTimer->start();
884 void ArchiveWidget::slotExtractProgress()
886 KIO::DirectorySizeJob *job = KIO::directorySize(archive_url->url());
887 connect(job, SIGNAL(result(KJob*)), this, SLOT(slotGotProgress(KJob*)));
890 void ArchiveWidget::slotGotProgress(KJob* job)
893 KIO::DirectorySizeJob *j = static_cast <KIO::DirectorySizeJob *>(job);
894 progressBar->setValue((int) 100 * j->totalSize() / m_requestedSize);
899 void ArchiveWidget::doExtracting()
901 m_extractArchive->directory()->copyTo(archive_url->url().path(KUrl::AddTrailingSlash));
902 m_extractArchive->close();
903 emit extractingFinished();
906 QString ArchiveWidget::extractedProjectFile() const
908 return archive_url->url().path(KUrl::AddTrailingSlash) + m_projectName;
911 void ArchiveWidget::slotExtractingFinished()
913 m_progressTimer->stop();
914 // Process project file
915 QFile file(extractedProjectFile());
917 if (!file.open(QIODevice::ReadOnly | QIODevice::Text)) {
921 QString playList = QString::fromUtf8(file.readAll());
923 if (playList.isEmpty()) {
927 playList.replace("$CURRENTPATH", archive_url->url().path(KUrl::RemoveTrailingSlash));
928 if (!file.open(QIODevice::WriteOnly | QIODevice::Text)) {
929 kWarning() << "////// ERROR writing to file: ";
933 file.write(playList.toUtf8());
934 if (file.error() != QFile::NoError) {
942 KMessageBox::sorry(kapp->activeWindow(), i18n("Cannot open project file %1", extractedProjectFile()), i18n("Cannot open file"));
948 void ArchiveWidget::slotProxyOnly(int onlyProxy)
951 if (onlyProxy == Qt::Checked) {
952 // Archive proxy clips
953 QStringList proxyIdList;
954 QTreeWidgetItem *parentItem = NULL;
956 // Build list of existing proxy ids
957 for (int i = 0; i < files_list->topLevelItemCount(); ++i) {
958 parentItem = files_list->topLevelItem(i);
959 if (parentItem->data(0, Qt::UserRole).toString() == "proxy") break;
961 if (!parentItem) return;
962 int items = parentItem->childCount();
963 for (int j = 0; j < items; j++) {
964 proxyIdList << parentItem->child(j)->data(0, Qt::UserRole + 2).toString();
967 // Parse all items to disable original clips for existing proxies
968 for (int i = 0; i < proxyIdList.count(); ++i) {
969 QString id = proxyIdList.at(i);
970 if (id.isEmpty()) continue;
971 for (int j = 0; j < files_list->topLevelItemCount(); j++) {
972 parentItem = files_list->topLevelItem(j);
973 if (parentItem->data(0, Qt::UserRole).toString() == "proxy") continue;
974 items = parentItem->childCount();
975 for (int k = 0; k < items; k++) {
976 if (parentItem->child(k)->data(0, Qt::UserRole + 2).toString() == id) {
977 // This item has a proxy, do not archive it
978 parentItem->child(k)->setFlags(Qt::ItemIsSelectable);
987 for (int i = 0; i < files_list->topLevelItemCount(); ++i) {
988 QTreeWidgetItem *parentItem = files_list->topLevelItem(i);
989 int items = parentItem->childCount();
990 for (int j = 0; j < items; j++) {
991 parentItem->child(j)->setFlags(Qt::ItemIsEnabled | Qt::ItemIsSelectable);
996 // Calculate requested size
998 for (int i = 0; i < files_list->topLevelItemCount(); ++i) {
999 QTreeWidgetItem *parentItem = files_list->topLevelItem(i);
1000 int items = parentItem->childCount();
1002 bool isSlideshow = parentItem->data(0, Qt::UserRole).toString() == "slideshows";
1004 for (int j = 0; j < items; j++) {
1005 if (!parentItem->child(j)->isDisabled()) {
1006 m_requestedSize += parentItem->child(j)->data(0, Qt::UserRole + 3).toInt();
1007 if (isSlideshow) total += parentItem->child(j)->data(0, Qt::UserRole + 1).toStringList().count();
1012 parentItem->setText(0, parentItem->text(0).section('(', 0, 0) + i18np("(%1 item)", "(%1 items)", itemsCount));
1014 project_files->setText(i18np("%1 file to archive, requires %2", "%1 files to archive, requires %2", total, KIO::convertSize(m_requestedSize)));
1019 #include "archivewidget.moc"