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"
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(QString projectName, QDomDocument doc, QList <DocClipBase*> list, QStringList luma_list, QWidget * parent) :
48 m_name(projectName.section('.', 0, -2)),
50 m_abortArchive(false),
52 m_extractArchive(NULL),
55 setAttribute(Qt::WA_DeleteOnClose);
57 setWindowTitle(i18n("Archive Project"));
58 archive_url->setUrl(KUrl(QDir::homePath()));
59 connect(archive_url, SIGNAL(textChanged (const QString &)), this, SLOT(slotCheckSpace()));
60 connect(this, SIGNAL(archivingFinished(bool)), this, SLOT(slotArchivingFinished(bool)));
61 connect(this, SIGNAL(archiveProgress(int)), this, SLOT(slotArchivingProgress(int)));
62 connect(proxy_only, SIGNAL(stateChanged(int)), this, SLOT(slotProxyOnly(int)));
65 QTreeWidgetItem *videos = new QTreeWidgetItem(files_list, QStringList() << i18n("Video clips"));
66 videos->setIcon(0, KIcon("video-x-generic"));
67 videos->setData(0, Qt::UserRole, "videos");
68 videos->setExpanded(false);
69 QTreeWidgetItem *sounds = new QTreeWidgetItem(files_list, QStringList() << i18n("Audio clips"));
70 sounds->setIcon(0, KIcon("audio-x-generic"));
71 sounds->setData(0, Qt::UserRole, "sounds");
72 sounds->setExpanded(false);
73 QTreeWidgetItem *images = new QTreeWidgetItem(files_list, QStringList() << i18n("Image clips"));
74 images->setIcon(0, KIcon("image-x-generic"));
75 images->setData(0, Qt::UserRole, "images");
76 images->setExpanded(false);
77 QTreeWidgetItem *slideshows = new QTreeWidgetItem(files_list, QStringList() << i18n("Slideshow clips"));
78 slideshows->setIcon(0, KIcon("image-x-generic"));
79 slideshows->setData(0, Qt::UserRole, "slideshows");
80 slideshows->setExpanded(false);
81 QTreeWidgetItem *texts = new QTreeWidgetItem(files_list, QStringList() << i18n("Text clips"));
82 texts->setIcon(0, KIcon("text-plain"));
83 texts->setData(0, Qt::UserRole, "texts");
84 texts->setExpanded(false);
85 QTreeWidgetItem *playlists = new QTreeWidgetItem(files_list, QStringList() << i18n("Playlist clips"));
86 playlists->setIcon(0, KIcon("video-mlt-playlist"));
87 playlists->setData(0, Qt::UserRole, "playlist");
88 playlists->setExpanded(false);
89 QTreeWidgetItem *others = new QTreeWidgetItem(files_list, QStringList() << i18n("Other clips"));
90 others->setIcon(0, KIcon("unknown"));
91 others->setData(0, Qt::UserRole, "others");
92 others->setExpanded(false);
93 QTreeWidgetItem *lumas = new QTreeWidgetItem(files_list, QStringList() << i18n("Luma files"));
94 lumas->setIcon(0, KIcon("image-x-generic"));
95 lumas->setData(0, Qt::UserRole, "lumas");
96 lumas->setExpanded(false);
98 QTreeWidgetItem *proxies = new QTreeWidgetItem(files_list, QStringList() << i18n("Proxy clips"));
99 proxies->setIcon(0, KIcon("video-x-generic"));
100 proxies->setData(0, Qt::UserRole, "proxy");
101 proxies->setExpanded(false);
104 QStringList allFonts;
106 QStringList fileNames;
107 QStringList extraImageUrls;
108 QStringList otherUrls;
109 generateItems(lumas, luma_list);
111 QMap <QString, QString> slideUrls;
112 QMap <QString, QString> audioUrls;
113 QMap <QString, QString>videoUrls;
114 QMap <QString, QString>imageUrls;
115 QMap <QString, QString>playlistUrls;
116 QMap <QString, QString>proxyUrls;
118 for (int i = 0; i < list.count(); i++) {
119 DocClipBase *clip = list.at(i);
120 CLIPTYPE t = clip->clipType();
121 QString id = clip->getId();
122 if (t == SLIDESHOW) {
123 KUrl slideUrl = clip->fileURL();
124 //TODO: Slideshow files
125 slideUrls.insert(id, slideUrl.path());
127 else if (t == IMAGE) imageUrls.insert(id, clip->fileURL().path());
128 else if (t == TEXT) {
129 QStringList imagefiles = TitleWidget::extractImageList(clip->getProperty("xmldata"));
130 QStringList fonts = TitleWidget::extractFontList(clip->getProperty("xmldata"));
131 extraImageUrls << imagefiles;
133 } else if (t == PLAYLIST) {
134 playlistUrls.insert(id, clip->fileURL().path());
135 QStringList files = ProjectSettings::extractPlaylistUrls(clip->fileURL().path());
138 else if (!clip->fileURL().isEmpty()) {
139 if (t == AUDIO) audioUrls.insert(id, clip->fileURL().path());
141 videoUrls.insert(id, clip->fileURL().path());
142 // Check if we have a proxy
143 QString proxy = clip->getProperty("proxy");
144 if (!proxy.isEmpty() && proxy != "-" && QFile::exists(proxy)) proxyUrls.insert(id, proxy);
149 generateItems(images, extraImageUrls);
150 generateItems(sounds, audioUrls);
151 generateItems(videos, videoUrls);
152 generateItems(images, imageUrls);
153 generateItems(slideshows, slideUrls);
154 generateItems(playlists, playlistUrls);
155 generateItems(others, otherUrls);
156 generateItems(proxies, proxyUrls);
158 allFonts.removeDuplicates();
160 #if KDE_IS_VERSION(4,7,0)
161 m_infoMessage = new KMessageWidget(this);
162 QVBoxLayout *s = static_cast <QVBoxLayout*> (layout());
163 s->insertWidget(5, m_infoMessage);
164 m_infoMessage->setCloseButtonVisible(false);
165 m_infoMessage->setWordWrap(true);
166 m_infoMessage->hide();
169 // missing clips, warn user
170 if (m_missingClips > 0) {
171 QString infoText = i18np("You have %1 missing clip in your project.", "You have %1 missing clips in your project.", m_missingClips);
172 #if KDE_IS_VERSION(4,7,0)
173 m_infoMessage->setMessageType(KMessageWidget::Warning);
174 m_infoMessage->setText(infoText);
175 m_infoMessage->animatedShow();
177 KMessageBox::sorry(this, infoText);
183 // Hide unused categories, add item count
185 for (int i = 0; i < files_list->topLevelItemCount(); i++) {
186 QTreeWidgetItem *parentItem = files_list->topLevelItem(i);
187 int items = parentItem->childCount();
189 files_list->topLevelItem(i)->setHidden(true);
192 if (parentItem->data(0, Qt::UserRole).toString() == "slideshows")
194 // Special case: slideshows contain several files
195 for (int j = 0; j < items; j++) {
196 total += parentItem->child(j)->data(0, Qt::UserRole + 1).toStringList().count();
200 parentItem->setText(0, files_list->topLevelItem(i)->text(0) + " " + i18np("(%1 item)", "(%1 items)", items));
203 if (m_name.isEmpty()) m_name = i18n("Untitled");
204 compressed_archive->setText(compressed_archive->text() + " (" + m_name + ".tar.gz)");
205 project_files->setText(i18np("%1 file to archive, requires %2", "%1 files to archive, requires %2", total, KIO::convertSize(m_requestedSize)));
206 buttonBox->button(QDialogButtonBox::Apply)->setText(i18n("Archive"));
207 connect(buttonBox->button(QDialogButtonBox::Apply), SIGNAL(clicked()), this, SLOT(slotStartArchiving()));
208 buttonBox->button(QDialogButtonBox::Apply)->setEnabled(false);
213 // Constructor for extract widget
214 ArchiveWidget::ArchiveWidget(const KUrl &url, QWidget * parent):
219 //setAttribute(Qt::WA_DeleteOnClose);
222 connect(this, SIGNAL(extractingFinished()), this, SLOT(slotExtractingFinished()));
223 connect(this, SIGNAL(showMessage(const QString &, const QString &)), this, SLOT(slotDisplayMessage(const QString &, const QString &)));
225 compressed_archive->setHidden(true);
226 proxy_only->setHidden(true);
227 project_files->setHidden(true);
228 files_list->setHidden(true);
229 label->setText(i18n("Extract to"));
230 setWindowTitle(i18n("Open Archived Project"));
231 archive_url->setUrl(KUrl(QDir::homePath()));
232 buttonBox->button(QDialogButtonBox::Apply)->setText(i18n("Extract"));
233 connect(buttonBox->button(QDialogButtonBox::Apply), SIGNAL(clicked()), this, SLOT(slotStartExtracting()));
234 buttonBox->button(QDialogButtonBox::Apply)->setEnabled(true);
236 m_archiveThread = QtConcurrent::run(this, &ArchiveWidget::openArchiveForExtraction);
240 ArchiveWidget::~ArchiveWidget()
242 if (m_extractArchive) delete m_extractArchive;
245 void ArchiveWidget::slotDisplayMessage(const QString &icon, const QString &text)
247 icon_info->setPixmap(KIcon(icon).pixmap(16, 16));
248 text_info->setText(text);
251 void ArchiveWidget::slotJobResult(bool success, const QString &text)
253 #if KDE_IS_VERSION(4,7,0)
254 m_infoMessage->setMessageType(success ? KMessageWidget::Positive : KMessageWidget::Warning);
255 m_infoMessage->setText(text);
256 m_infoMessage->animatedShow();
258 if (success) icon_info->setPixmap(KIcon("dialog-ok").pixmap(16, 16));
259 else icon_info->setPixmap(KIcon("dialog-close").pixmap(16, 16));
260 text_info->setText(text);
264 void ArchiveWidget::openArchiveForExtraction()
266 emit showMessage("system-run", i18n("Opening archive..."));
267 m_extractArchive = new KTar(m_extractUrl.path());
268 if (!m_extractArchive->isOpen() && !m_extractArchive->open( QIODevice::ReadOnly )) {
269 emit showMessage("dialog-close", i18n("Cannot open archive file:\n %1", m_extractUrl.path()));
270 groupBox->setEnabled(false);
274 // Check that it is a kdenlive project archive
275 bool isProjectArchive = false;
276 QStringList files = m_extractArchive->directory()->entries();
277 for (int i = 0; i < files.count(); i++) {
278 if (files.at(i).endsWith(".kdenlive")) {
279 m_projectName = files.at(i);
280 isProjectArchive = true;
285 if (!isProjectArchive) {
286 emit showMessage("dialog-close", i18n("File %1\n is not an archived Kdenlive project", m_extractUrl.path()));
287 groupBox->setEnabled(false);
290 buttonBox->button(QDialogButtonBox::Apply)->setEnabled(true);
291 emit showMessage("dialog-ok", i18n("Ready"));
294 void ArchiveWidget::done ( int r )
296 if (closeAccepted()) QDialog::done(r);
299 void ArchiveWidget::closeEvent ( QCloseEvent * e )
302 if (closeAccepted()) e->accept();
307 bool ArchiveWidget::closeAccepted()
309 if (!m_extractMode && !archive_url->isEnabled()) {
310 // Archiving in progress, should we stop?
311 if (KMessageBox::warningContinueCancel(this, i18n("Archiving in progress, do you want to stop it?"), i18n("Stop Archiving"), KGuiItem(i18n("Stop Archiving"))) != KMessageBox::Continue) {
314 if (m_copyJob) m_copyJob->kill();
320 void ArchiveWidget::generateItems(QTreeWidgetItem *parentItem, QStringList items)
322 QStringList filesList;
325 bool isSlideshow = parentItem->data(0, Qt::UserRole).toString() == "slideshows";
326 foreach(const QString & file, items) {
327 QTreeWidgetItem *item = new QTreeWidgetItem(parentItem, QStringList() << file);
328 fileName = KUrl(file).fileName();
330 // we store each slideshow in a separate subdirectory
331 item->setData(0, Qt::UserRole, ix);
334 QDir dir(slideUrl.directory(KUrl::AppendTrailingSlash));
335 if (slideUrl.fileName().startsWith(".all.")) {
336 // mimetype slideshow (for example *.png)
339 // TODO: improve jpeg image detection with extension like jpeg, requires change in MLT image producers
340 filters << "*." + slideUrl.fileName().section('.', -1);
341 dir.setNameFilters(filters);
342 QFileInfoList resultList = dir.entryInfoList(QDir::Files);
343 QStringList slideImages;
344 qint64 totalSize = 0;
345 for (int i = 0; i < resultList.count(); i++) {
346 totalSize += resultList.at(i).size();
347 slideImages << resultList.at(i).absoluteFilePath();
349 item->setData(0, Qt::UserRole + 1, slideImages);
350 item->setData(0, Qt::UserRole + 3, totalSize);
351 m_requestedSize += totalSize;
354 // pattern url (like clip%.3d.png)
355 QStringList result = dir.entryList(QDir::Files);
356 QString filter = slideUrl.fileName();
357 QString ext = filter.section('.', -1);
358 filter = filter.section('%', 0, -2);
359 QString regexp = "^" + filter + "\\d+\\." + ext + "$";
361 QStringList slideImages;
362 QString directory = dir.absolutePath();
363 if (!directory.endsWith('/')) directory.append('/');
364 qint64 totalSize = 0;
365 foreach(const QString & path, result) {
366 if (rx.exactMatch(path)) {
367 totalSize += QFileInfo(directory + path).size();
368 slideImages << directory + path;
371 item->setData(0, Qt::UserRole + 1, slideImages);
372 item->setData(0, Qt::UserRole + 3, totalSize);
373 m_requestedSize += totalSize;
376 else if (filesList.contains(fileName)) {
377 // we have 2 files with same name
379 QString newFileName = fileName.section('.', 0, -2) + "_" + QString::number(ix) + "." + fileName.section('.', -1);
380 while (filesList.contains(newFileName)) {
382 newFileName = fileName.section('.', 0, -2) + "_" + QString::number(ix) + "." + fileName.section('.', -1);
384 fileName = newFileName;
385 item->setData(0, Qt::UserRole, fileName);
388 qint64 fileSize = QFileInfo(file).size();
390 item->setIcon(0, KIcon("edit-delete"));
394 m_requestedSize += fileSize;
395 item->setData(0, Qt::UserRole + 3, fileSize);
397 filesList << fileName;
402 void ArchiveWidget::generateItems(QTreeWidgetItem *parentItem, QMap <QString, QString> items)
404 QStringList filesList;
407 bool isSlideshow = parentItem->data(0, Qt::UserRole).toString() == "slideshows";
408 QMap<QString, QString>::const_iterator it = items.constBegin();
409 while (it != items.constEnd()) {
410 QString file = it.value();
411 QTreeWidgetItem *item = new QTreeWidgetItem(parentItem, QStringList() << file);
412 // Store the clip's id
413 item->setData(0, Qt::UserRole + 2, it.key());
414 fileName = KUrl(file).fileName();
416 // we store each slideshow in a separate subdirectory
417 item->setData(0, Qt::UserRole, ix);
420 QDir dir(slideUrl.directory(KUrl::AppendTrailingSlash));
421 if (slideUrl.fileName().startsWith(".all.")) {
422 // mimetype slideshow (for example *.png)
425 // TODO: improve jpeg image detection with extension like jpeg, requires change in MLT image producers
426 filters << "*." + slideUrl.fileName().section('.', -1);
427 dir.setNameFilters(filters);
428 QFileInfoList resultList = dir.entryInfoList(QDir::Files);
429 QStringList slideImages;
430 qint64 totalSize = 0;
431 for (int i = 0; i < resultList.count(); i++) {
432 totalSize += resultList.at(i).size();
433 slideImages << resultList.at(i).absoluteFilePath();
435 item->setData(0, Qt::UserRole + 1, slideImages);
436 item->setData(0, Qt::UserRole + 3, totalSize);
437 m_requestedSize += totalSize;
440 // pattern url (like clip%.3d.png)
441 QStringList result = dir.entryList(QDir::Files);
442 QString filter = slideUrl.fileName();
443 QString ext = filter.section('.', -1);
444 filter = filter.section('%', 0, -2);
445 QString regexp = "^" + filter + "\\d+\\." + ext + "$";
447 QStringList slideImages;
448 qint64 totalSize = 0;
449 QString directory = dir.absolutePath();
450 if (!directory.endsWith('/')) directory.append('/');
451 foreach(const QString & path, result) {
452 if (rx.exactMatch(path)) {
453 totalSize += QFileInfo(directory + path).size();
454 slideImages << directory + path;
457 item->setData(0, Qt::UserRole + 1, slideImages);
458 item->setData(0, Qt::UserRole + 3, totalSize);
459 m_requestedSize += totalSize;
462 else if (filesList.contains(fileName)) {
463 // we have 2 files with same name
465 QString newFileName = fileName.section('.', 0, -2) + "_" + QString::number(ix) + "." + fileName.section('.', -1);
466 while (filesList.contains(newFileName)) {
468 newFileName = fileName.section('.', 0, -2) + "_" + QString::number(ix) + "." + fileName.section('.', -1);
470 fileName = newFileName;
471 item->setData(0, Qt::UserRole, fileName);
474 qint64 fileSize = QFileInfo(file).size();
476 item->setIcon(0, KIcon("edit-delete"));
480 m_requestedSize += fileSize;
481 item->setData(0, Qt::UserRole + 3, fileSize);
483 filesList << fileName;
489 void ArchiveWidget::slotCheckSpace()
491 KDiskFreeSpaceInfo inf = KDiskFreeSpaceInfo::freeSpaceInfo( archive_url->url().path());
492 KIO::filesize_t freeSize = inf.available();
493 if (freeSize > m_requestedSize) {
495 buttonBox->button(QDialogButtonBox::Apply)->setEnabled(true);
496 slotDisplayMessage("dialog-ok", i18n("Available space on drive: %1", KIO::convertSize(freeSize)));
499 buttonBox->button(QDialogButtonBox::Apply)->setEnabled(false);
500 slotDisplayMessage("dialog-close", i18n("Not enough space on drive, free space: %1", KIO::convertSize(freeSize)));
504 bool ArchiveWidget::slotStartArchiving(bool firstPass)
506 if (firstPass && (m_copyJob || m_archiveThread.isRunning())) {
507 // archiving in progress, abort
508 if (m_copyJob) m_copyJob->kill(KJob::EmitResult);
509 m_abortArchive = true;
512 bool isArchive = compressed_archive->isChecked();
513 if (!firstPass) m_copyJob = NULL;
516 m_abortArchive = false;
517 m_duplicateFiles.clear();
518 m_replacementList.clear();
519 m_foldersList.clear();
521 slotDisplayMessage("system-run", i18n("Archiving..."));
523 archive_url->setEnabled(false);
524 proxy_only->setEnabled(false);
525 compressed_archive->setEnabled(false);
530 QTreeWidgetItem *parentItem;
531 bool isSlideshow = false;
534 // We parse all files going into one folder, then start the copy job
536 for (int i = 0; i < files_list->topLevelItemCount(); i++) {
537 parentItem = files_list->topLevelItem(i);
538 if (parentItem->isDisabled()) {
539 parentItem->setExpanded(false);
542 if (parentItem->childCount() > 0) {
543 if (parentItem->data(0, Qt::UserRole).toString() == "slideshows") {
544 KUrl slideFolder(archive_url->url().path(KUrl::AddTrailingSlash) + "slideshows");
545 if (isArchive) m_foldersList.append("slideshows");
546 else KIO::NetAccess::mkdir(slideFolder, this);
549 else isSlideshow = false;
550 files_list->setCurrentItem(parentItem);
551 parentItem->setExpanded(true);
552 destPath = parentItem->data(0, Qt::UserRole).toString() + "/";
553 destUrl = KUrl(archive_url->url().path(KUrl::AddTrailingSlash) + destPath);
554 QTreeWidgetItem *item;
555 for (int j = 0; j < parentItem->childCount(); j++) {
556 item = parentItem->child(j);
557 if (item->isDisabled()) continue;
558 // Special case: slideshows
561 destPath += item->data(0, Qt::UserRole).toString() + "/";
562 destUrl = KUrl(archive_url->url().path(KUrl::AddTrailingSlash) + destPath);
563 QStringList srcFiles = item->data(0, Qt::UserRole + 1).toStringList();
564 for (int k = 0; k < srcFiles.count(); k++) {
565 files << KUrl(srcFiles.at(k));
567 item->setDisabled(true);
568 if (parentItem->indexOfChild(item) == parentItem->childCount() - 1) {
569 // We have processed all slideshows
570 parentItem->setDisabled(true);
574 else if (item->data(0, Qt::UserRole).isNull()) {
575 files << KUrl(item->text(0));
578 // We must rename the destination file, since another file with same name exists
579 //TODO: monitor progress
581 m_filesList.insert(item->text(0), destPath + item->data(0, Qt::UserRole).toString());
583 else m_duplicateFiles.insert(KUrl(item->text(0)), KUrl(destUrl.path(KUrl::AddTrailingSlash) + item->data(0, Qt::UserRole).toString()));
586 if (!isSlideshow) parentItem->setDisabled(true);
592 // No clips to archive
593 if (isArchive) slotArchivingFinished(NULL, true);
597 if (destPath.isEmpty()) {
598 if (m_duplicateFiles.isEmpty()) return false;
599 QMapIterator<KUrl, KUrl> i(m_duplicateFiles);
602 KUrl startJobSrc = i.key();
603 KUrl startJobDst = i.value();
604 m_duplicateFiles.remove(startJobSrc);
605 KIO::CopyJob *job = KIO::copyAs(startJobSrc, startJobDst, KIO::HideProgressInfo);
606 connect(job, SIGNAL(result(KJob *)), this, SLOT(slotArchivingFinished(KJob *)));
607 connect(job, SIGNAL(processedSize(KJob *, qulonglong)), this, SLOT(slotArchivingProgress(KJob *, qulonglong)));
613 m_foldersList.append(destPath);
614 for (int i = 0; i < files.count(); i++) {
615 m_filesList.insert(files.at(i).path(), destPath + files.at(i).fileName());
617 slotArchivingFinished();
619 else if (files.isEmpty()) {
620 slotStartArchiving(false);
623 KIO::NetAccess::mkdir(destUrl, this);
624 m_copyJob = KIO::copy (files, destUrl, KIO::HideProgressInfo);
625 connect(m_copyJob, SIGNAL(result(KJob *)), this, SLOT(slotArchivingFinished(KJob *)));
626 connect(m_copyJob, SIGNAL(processedSize(KJob *, qulonglong)), this, SLOT(slotArchivingProgress(KJob *, qulonglong)));
629 progressBar->setValue(0);
630 buttonBox->button(QDialogButtonBox::Apply)->setText(i18n("Abort"));
635 void ArchiveWidget::slotArchivingFinished(KJob *job, bool finished)
637 if (job == NULL || job->error() == 0) {
638 if (!finished && slotStartArchiving(false)) {
639 // We still have files to archive
642 else if (!compressed_archive->isChecked()) {
643 // Archiving finished
644 progressBar->setValue(100);
645 if (processProjectFile()) {
646 slotJobResult(true, i18n("Project was successfully archived."));
649 slotJobResult(false, i18n("There was an error processing project file"));
651 } else processProjectFile();
655 slotJobResult(false, i18n("There was an error while copying the files: %1", job->errorString()));
657 if (!compressed_archive->isChecked()) {
658 buttonBox->button(QDialogButtonBox::Apply)->setText(i18n("Archive"));
659 archive_url->setEnabled(true);
660 proxy_only->setEnabled(true);
661 compressed_archive->setEnabled(true);
662 for (int i = 0; i < files_list->topLevelItemCount(); i++) {
663 files_list->topLevelItem(i)->setDisabled(false);
664 for (int j = 0; j < files_list->topLevelItem(i)->childCount(); j++)
665 files_list->topLevelItem(i)->child(j)->setDisabled(false);
670 void ArchiveWidget::slotArchivingProgress(KJob *, qulonglong size)
672 progressBar->setValue((int) 100 * size / m_requestedSize);
676 bool ArchiveWidget::processProjectFile()
679 QTreeWidgetItem *item;
680 bool isArchive = compressed_archive->isChecked();
682 for (int i = 0; i < files_list->topLevelItemCount(); i++) {
683 QTreeWidgetItem *parentItem = files_list->topLevelItem(i);
684 if (parentItem->childCount() > 0) {
685 destUrl = KUrl(archive_url->url().path(KUrl::AddTrailingSlash) + parentItem->data(0, Qt::UserRole).toString());
686 bool isSlideshow = parentItem->data(0, Qt::UserRole).toString() == "slideshows";
687 for (int j = 0; j < parentItem->childCount(); j++) {
688 item = parentItem->child(j);
689 KUrl src(item->text(0));
692 dest = KUrl(destUrl.path(KUrl::AddTrailingSlash) + item->data(0, Qt::UserRole).toString() + "/" + src.fileName());
694 else if (item->data(0, Qt::UserRole).isNull()) {
695 dest.addPath(src.fileName());
698 dest.addPath(item->data(0, Qt::UserRole).toString());
700 m_replacementList.insert(src, dest);
705 QDomElement mlt = m_doc.documentElement();
706 QString root = mlt.attribute("root") + "/";
708 // Adjust global settings
710 if (isArchive) basePath = "$CURRENTPATH";
711 else basePath = archive_url->url().path(KUrl::RemoveTrailingSlash);
712 mlt.setAttribute("root", basePath);
713 QDomElement project = mlt.firstChildElement("kdenlivedoc");
714 project.setAttribute("projectfolder", basePath);
716 // process kdenlive producers
717 QDomNodeList prods = mlt.elementsByTagName("kdenlive_producer");
718 for (int i = 0; i < prods.count(); i++) {
719 QDomElement e = prods.item(i).toElement();
720 if (e.isNull()) continue;
721 if (e.hasAttribute("resource")) {
722 KUrl src(e.attribute("resource"));
723 KUrl dest = m_replacementList.value(src);
724 if (!dest.isEmpty()) e.setAttribute("resource", dest.path());
726 if (e.hasAttribute("proxy") && e.attribute("proxy") != "-") {
727 KUrl src(e.attribute("proxy"));
728 KUrl dest = m_replacementList.value(src);
729 if (!dest.isEmpty()) e.setAttribute("proxy", dest.path());
733 // process mlt producers
734 prods = mlt.elementsByTagName("producer");
735 for (int i = 0; i < prods.count(); i++) {
736 QDomElement e = prods.item(i).toElement();
737 if (e.isNull()) continue;
738 QString src = EffectsList::property(e, "resource");
739 if (!src.isEmpty()) {
740 if (!src.startsWith('/')) src.prepend(root);
742 KUrl dest = m_replacementList.value(src);
743 if (!dest.isEmpty()) EffectsList::setProperty(e, "resource", dest.path());
747 // process mlt transitions (for luma files)
748 prods = mlt.elementsByTagName("transition");
750 for (int i = 0; i < prods.count(); i++) {
751 QDomElement e = prods.item(i).toElement();
752 if (e.isNull()) continue;
753 attribute = "resource";
754 QString src = EffectsList::property(e, attribute);
755 if (src.isEmpty()) attribute = "luma";
756 src = EffectsList::property(e, attribute);
757 if (!src.isEmpty()) {
758 if (!src.startsWith('/')) src.prepend(root);
760 KUrl dest = m_replacementList.value(src);
761 if (!dest.isEmpty()) EffectsList::setProperty(e, attribute, dest.path());
765 QString playList = m_doc.toString();
767 QString startString("\"");
768 startString.append(archive_url->url().path(KUrl::RemoveTrailingSlash));
769 QString endString("\"");
770 endString.append(basePath);
771 playList.replace(startString, endString);
772 startString = ">" + archive_url->url().path(KUrl::RemoveTrailingSlash);
773 endString = ">" + basePath;
774 playList.replace(startString, endString);
778 m_temp = new KTemporaryFile;
779 if (!m_temp->open()) KMessageBox::error(this, i18n("Cannot create temporary file"));
780 m_temp->write(playList.toUtf8());
782 m_archiveThread = QtConcurrent::run(this, &ArchiveWidget::createArchive);
786 QString path = archive_url->url().path(KUrl::AddTrailingSlash) + m_name + ".kdenlive";
788 if (!file.open(QIODevice::WriteOnly | QIODevice::Text)) {
789 kWarning() << "////// ERROR writing to file: " << path;
790 KMessageBox::error(this, i18n("Cannot write to file %1", path));
794 file.write(m_doc.toString().toUtf8());
795 if (file.error() != QFile::NoError) {
796 KMessageBox::error(this, i18n("Cannot write to file %1", path));
804 void ArchiveWidget::createArchive()
806 QFileInfo dirInfo(archive_url->url().path());
807 QString user = dirInfo.owner();
808 QString group = dirInfo.group();
809 KTar archive(archive_url->url().path(KUrl::AddTrailingSlash) + m_name + ".tar.gz", "application/x-gzip");
810 archive.open( QIODevice::WriteOnly );
813 foreach(const QString &path, m_foldersList) {
814 archive.writeDir(path, user, group);
819 QMapIterator<QString, QString> i(m_filesList);
820 while (i.hasNext()) {
822 archive.addLocalFile(i.key(), i.value());
823 emit archiveProgress((int) 100 * ix / m_filesList.count());
828 archive.addLocalFile(m_temp->fileName(), m_name + ".kdenlive");
829 bool result = archive.close();
831 emit archivingFinished(result);
834 void ArchiveWidget::slotArchivingFinished(bool result)
837 slotJobResult(true, i18n("Project was successfully archived."));
838 buttonBox->button(QDialogButtonBox::Apply)->setEnabled(false);
841 slotJobResult(false, i18n("There was an error processing project file"));
843 progressBar->setValue(100);
844 buttonBox->button(QDialogButtonBox::Apply)->setText(i18n("Archive"));
845 archive_url->setEnabled(true);
846 proxy_only->setEnabled(true);
847 compressed_archive->setEnabled(true);
848 for (int i = 0; i < files_list->topLevelItemCount(); i++) {
849 files_list->topLevelItem(i)->setDisabled(false);
850 for (int j = 0; j < files_list->topLevelItem(i)->childCount(); j++)
851 files_list->topLevelItem(i)->child(j)->setDisabled(false);
855 void ArchiveWidget::slotArchivingProgress(int p)
857 progressBar->setValue(p);
860 void ArchiveWidget::slotStartExtracting()
862 if (m_archiveThread.isRunning()) {
863 //TODO: abort extracting
866 QFileInfo f(m_extractUrl.path());
867 m_requestedSize = f.size();
868 KIO::NetAccess::mkdir(archive_url->url().path(KUrl::RemoveTrailingSlash), this);
869 slotDisplayMessage("system-run", i18n("Extracting..."));
870 buttonBox->button(QDialogButtonBox::Apply)->setText(i18n("Abort"));
871 m_progressTimer = new QTimer;
872 m_progressTimer->setInterval(800);
873 m_progressTimer->setSingleShot(false);
874 connect(m_progressTimer, SIGNAL(timeout()), this, SLOT(slotExtractProgress()));
875 m_archiveThread = QtConcurrent::run(this, &ArchiveWidget::doExtracting);
876 m_progressTimer->start();
879 void ArchiveWidget::slotExtractProgress()
881 KIO::DirectorySizeJob *job = KIO::directorySize(archive_url->url());
882 connect(job, SIGNAL(result(KJob*)), this, SLOT(slotGotProgress(KJob*)));
885 void ArchiveWidget::slotGotProgress(KJob* job)
888 KIO::DirectorySizeJob *j = static_cast <KIO::DirectorySizeJob *>(job);
889 progressBar->setValue((int) 100 * j->totalSize() / m_requestedSize);
894 void ArchiveWidget::doExtracting()
896 m_extractArchive->directory()->copyTo(archive_url->url().path(KUrl::AddTrailingSlash));
897 m_extractArchive->close();
898 emit extractingFinished();
901 QString ArchiveWidget::extractedProjectFile()
903 return archive_url->url().path(KUrl::AddTrailingSlash) + m_projectName;
906 void ArchiveWidget::slotExtractingFinished()
908 m_progressTimer->stop();
909 delete m_progressTimer;
910 // Process project file
911 QFile file(extractedProjectFile());
913 if (!file.open(QIODevice::ReadOnly | QIODevice::Text)) {
917 QString playList = file.readAll();
919 if (playList.isEmpty()) {
923 playList.replace("$CURRENTPATH", archive_url->url().path(KUrl::RemoveTrailingSlash));
924 if (!file.open(QIODevice::WriteOnly | QIODevice::Text)) {
925 kWarning() << "////// ERROR writing to file: ";
929 file.write(playList.toUtf8());
930 if (file.error() != QFile::NoError) {
938 KMessageBox::sorry(kapp->activeWindow(), i18n("Cannot open project file %1", extractedProjectFile()), i18n("Cannot open file"));
944 void ArchiveWidget::slotProxyOnly(int onlyProxy)
947 if (onlyProxy == Qt::Checked) {
948 // Archive proxy clips
949 QStringList proxyIdList;
950 QTreeWidgetItem *parentItem = NULL;
952 // Build list of existing proxy ids
953 for (int i = 0; i < files_list->topLevelItemCount(); i++) {
954 parentItem = files_list->topLevelItem(i);
955 if (parentItem->data(0, Qt::UserRole).toString() == "proxy") break;
957 if (!parentItem) return;
958 int items = parentItem->childCount();
959 for (int j = 0; j < items; j++) {
960 proxyIdList << parentItem->child(j)->data(0, Qt::UserRole + 2).toString();
963 // Parse all items to disable original clips for existing proxies
964 for (int i = 0; i < proxyIdList.count(); i++) {
965 QString id = proxyIdList.at(i);
966 if (id.isEmpty()) continue;
967 for (int j = 0; j < files_list->topLevelItemCount(); j++) {
968 parentItem = files_list->topLevelItem(j);
969 if (parentItem->data(0, Qt::UserRole).toString() == "proxy") continue;
970 items = parentItem->childCount();
971 for (int k = 0; k < items; k++) {
972 if (parentItem->child(k)->data(0, Qt::UserRole + 2).toString() == id) {
973 // This item has a proxy, do not archive it
974 parentItem->child(k)->setFlags(Qt::ItemIsSelectable);
983 for (int i = 0; i < files_list->topLevelItemCount(); i++) {
984 QTreeWidgetItem *parentItem = files_list->topLevelItem(i);
985 int items = parentItem->childCount();
986 for (int j = 0; j < items; j++) {
987 parentItem->child(j)->setFlags(Qt::ItemIsEnabled | Qt::ItemIsSelectable);
992 // Calculate requested size
994 for (int i = 0; i < files_list->topLevelItemCount(); i++) {
995 QTreeWidgetItem *parentItem = files_list->topLevelItem(i);
996 int items = parentItem->childCount();
998 bool isSlideshow = parentItem->data(0, Qt::UserRole).toString() == "slideshows";
1000 for (int j = 0; j < items; j++) {
1001 if (!parentItem->child(j)->isDisabled()) {
1002 m_requestedSize += parentItem->child(j)->data(0, Qt::UserRole + 3).toInt();
1003 if (isSlideshow) total += parentItem->child(j)->data(0, Qt::UserRole + 1).toStringList().count();
1008 parentItem->setText(0, parentItem->text(0).section("(", 0, 0) + i18np("(%1 item)", "(%1 items)", itemsCount));
1010 project_files->setText(i18np("%1 file to archive, requires %2", "%1 files to archive, requires %2", total, KIO::convertSize(m_requestedSize)));