]> git.sesse.net Git - kdenlive/blob - src/archivewidget.cpp
We can now open archived projects
[kdenlive] / src / archivewidget.cpp
1 /***************************************************************************
2  *   Copyright (C) 2011 by Jean-Baptiste Mardelle (jb@kdenlive.org)        *
3  *                                                                         *
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.                                   *
8  *                                                                         *
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.                          *
13  *                                                                         *
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  ***************************************************************************/
19
20
21 #include "archivewidget.h"
22 #include "titlewidget.h"
23
24 #include <KLocale>
25 #include <KDiskFreeSpaceInfo>
26 #include <KUrlRequester>
27 #include <KFileDialog>
28 #include <KMessageBox>
29 #include <KGuiItem>
30 #include <KIO/NetAccess>
31 #include <KTar>
32 #include <KDebug>
33 #include <KApplication>
34
35 #include <QTreeWidget>
36 #include <QtConcurrentRun>
37 #include "projectsettings.h"
38
39
40 ArchiveWidget::ArchiveWidget(QString projectName, QDomDocument doc, QList <DocClipBase*> list, QStringList luma_list, QWidget * parent) :
41         QDialog(parent),
42         m_requestedSize(0),
43         m_copyJob(NULL),
44         m_name(projectName.section('.', 0, -2)),
45         m_doc(doc),
46         m_abortArchive(false),
47         m_extractMode(false)
48 {
49     setAttribute(Qt::WA_DeleteOnClose);
50     setupUi(this);
51     setWindowTitle(i18n("Archive Project"));
52     archive_url->setUrl(KUrl(QDir::homePath()));
53     connect(archive_url, SIGNAL(textChanged (const QString &)), this, SLOT(slotCheckSpace()));
54     connect(this, SIGNAL(archivingFinished(bool)), this, SLOT(slotArchivingFinished(bool)));
55     connect(this, SIGNAL(archiveProgress(int)), this, SLOT(slotArchivingProgress(int)));
56
57     // Setup categories
58     QTreeWidgetItem *videos = new QTreeWidgetItem(files_list, QStringList() << i18n("Video clips"));
59     videos->setIcon(0, KIcon("video-x-generic"));
60     videos->setData(0, Qt::UserRole, "videos");
61     videos->setExpanded(false);
62     QTreeWidgetItem *sounds = new QTreeWidgetItem(files_list, QStringList() << i18n("Audio clips"));
63     sounds->setIcon(0, KIcon("audio-x-generic"));
64     sounds->setData(0, Qt::UserRole, "sounds");
65     sounds->setExpanded(false);
66     QTreeWidgetItem *images = new QTreeWidgetItem(files_list, QStringList() << i18n("Image clips"));
67     images->setIcon(0, KIcon("image-x-generic"));
68     images->setData(0, Qt::UserRole, "images");
69     images->setExpanded(false);
70     QTreeWidgetItem *slideshows = new QTreeWidgetItem(files_list, QStringList() << i18n("Slideshow clips"));
71     slideshows->setIcon(0, KIcon("image-x-generic"));
72     slideshows->setData(0, Qt::UserRole, "slideshows");
73     slideshows->setExpanded(false);
74     QTreeWidgetItem *texts = new QTreeWidgetItem(files_list, QStringList() << i18n("Text clips"));
75     texts->setIcon(0, KIcon("text-plain"));
76     texts->setData(0, Qt::UserRole, "texts");
77     texts->setExpanded(false);
78     QTreeWidgetItem *others = new QTreeWidgetItem(files_list, QStringList() << i18n("Other clips"));
79     others->setIcon(0, KIcon("unknown"));
80     others->setData(0, Qt::UserRole, "others");
81     others->setExpanded(false);
82     QTreeWidgetItem *lumas = new QTreeWidgetItem(files_list, QStringList() << i18n("Luma files"));
83     lumas->setIcon(0, KIcon("image-x-generic"));
84     lumas->setData(0, Qt::UserRole, "lumas");
85     lumas->setExpanded(false);
86     
87     QTreeWidgetItem *proxies = new QTreeWidgetItem(files_list, QStringList() << i18n("Proxy clips"));
88     proxies->setIcon(0, KIcon("video-x-generic"));
89     proxies->setData(0, Qt::UserRole, "proxy");
90     proxies->setExpanded(false);
91     
92     // process all files
93     QStringList allFonts;
94     KUrl::List fileUrls;
95     QStringList fileNames;
96     generateItems(lumas, luma_list);
97
98     QStringList slideUrls;
99     QStringList audioUrls;
100     QStringList videoUrls;
101     QStringList imageUrls;
102     QStringList otherUrls;
103     QStringList proxyUrls;
104
105     for (int i = 0; i < list.count(); i++) {
106         DocClipBase *clip = list.at(i);
107         CLIPTYPE t = clip->clipType();
108         if (t == SLIDESHOW) {
109             KUrl slideUrl = clip->fileURL();
110             //TODO: Slideshow files
111             slideUrls << slideUrl.path();
112         }
113         else if (t == IMAGE) imageUrls << clip->fileURL().path();
114         else if (t == TEXT) {
115             QStringList imagefiles = TitleWidget::extractImageList(clip->getProperty("xmldata"));
116             QStringList fonts = TitleWidget::extractFontList(clip->getProperty("xmldata"));
117             imageUrls << imagefiles;
118             allFonts << fonts;
119         } else if (t == PLAYLIST) {
120             QStringList files = ProjectSettings::extractPlaylistUrls(clip->fileURL().path());
121             otherUrls << files;
122         }
123         else if (!clip->fileURL().isEmpty()) {
124             if (t == AUDIO) audioUrls << clip->fileURL().path();
125             else {
126                 videoUrls << clip->fileURL().path();
127                 // Check if we have a proxy
128                 QString proxy = clip->getProperty("proxy");
129                 if (!proxy.isEmpty() && proxy != "-" && QFile::exists(proxy)) proxyUrls << proxy;
130             }
131         }
132     }
133
134     generateItems(sounds, audioUrls);
135     generateItems(videos, videoUrls);
136     generateItems(images, imageUrls);
137     generateItems(slideshows, slideUrls);
138     generateItems(others, otherUrls);
139     generateItems(proxies, proxyUrls);
140     
141 #if QT_VERSION >= 0x040500
142     allFonts.removeDuplicates();
143 #endif
144
145     //TODO: fonts
146
147     // Hide unused categories, add item count
148     int total = 0;
149     for (int i = 0; i < files_list->topLevelItemCount(); i++) {
150         QTreeWidgetItem *parentItem = files_list->topLevelItem(i);
151         int items = parentItem->childCount();
152         if (items == 0) {
153             files_list->topLevelItem(i)->setHidden(true);
154         }
155         else {
156             if (parentItem->data(0, Qt::UserRole).toString() == "slideshows")
157             {
158                 // Special case: slideshows contain several files
159                 for (int j = 0; j < items; j++) {
160                     total += parentItem->child(j)->data(0, Qt::UserRole + 1).toStringList().count();
161                 }
162             }
163             else total += items;
164             parentItem->setText(0, files_list->topLevelItem(i)->text(0) + " " + i18np("(%1 item)", "(%1 items)", items));
165         }
166     }
167
168     compressed_archive->setText(compressed_archive->text() + " (" + m_name + ".tar.gz)");
169     project_files->setText(i18np("%1 file to archive, requires %2", "%1 files to archive, requires %2", total, KIO::convertSize(m_requestedSize)));
170     buttonBox->button(QDialogButtonBox::Apply)->setText(i18n("Archive"));
171     connect(buttonBox->button(QDialogButtonBox::Apply), SIGNAL(clicked()), this, SLOT(slotStartArchiving()));
172     buttonBox->button(QDialogButtonBox::Apply)->setEnabled(false);
173     
174     slotCheckSpace();
175 }
176
177 // Constructor for extract widget
178 ArchiveWidget::ArchiveWidget(const KUrl &url, QWidget * parent):
179     QDialog(parent),
180     m_extractMode(true),
181     m_extractUrl(url)
182 {
183     //setAttribute(Qt::WA_DeleteOnClose);
184     KTar archive(url.path());
185     archive.open( QIODevice::ReadOnly );
186
187     // Check that it is a kdenlive project archive
188     bool isProjectArchive = false;
189     QStringList files = archive.directory()->entries();
190     for (int i = 0; i < files.count(); i++) {
191         if (files.at(i).endsWith(".kdenlive")) {
192             m_projectName = files.at(i);
193             isProjectArchive = true;
194             break;
195         }
196     }
197     archive.close();
198
199     if (!isProjectArchive) {
200         KMessageBox::sorry(kapp->activeWindow(), i18n("%1 is not an archived Kdenlive project", url.path(), i18n("Cannot open file")));
201         hide();
202         //HACK: find a better way to terminate the dialog
203         QTimer::singleShot(50, this, SLOT(reject()));
204         return;
205     }
206     setupUi(this);
207     connect(this, SIGNAL(extractingFinished()), this, SLOT(slotExtractingFinished()));
208     
209     compressed_archive->setHidden(true);
210     project_files->setHidden(true);
211     files_list->setHidden(true);
212     label->setText(i18n("Extract to"));
213     setWindowTitle(i18n("Open Archived Project"));
214     archive_url->setUrl(KUrl(QDir::homePath()));
215     buttonBox->button(QDialogButtonBox::Apply)->setText(i18n("Extract"));
216     connect(buttonBox->button(QDialogButtonBox::Apply), SIGNAL(clicked()), this, SLOT(slotStartExtracting()));
217 }
218
219
220 ArchiveWidget::~ArchiveWidget()
221 {
222 }
223
224 void ArchiveWidget::done ( int r )
225 {
226     if (closeAccepted()) QDialog::done(r);
227 }
228
229 void ArchiveWidget::closeEvent ( QCloseEvent * e )
230 {
231
232     if (closeAccepted()) e->accept();
233     else e->ignore();
234 }
235
236
237 bool ArchiveWidget::closeAccepted()
238 {
239     if (!m_extractMode && !archive_url->isEnabled()) {
240         // Archiving in progress, should we stop?
241         if (KMessageBox::warningContinueCancel(this, i18n("Archiving in progress, do you want to stop it?"), i18n("Stop Archiving"), KGuiItem(i18n("Stop Archiving"))) != KMessageBox::Continue) {
242             return false;
243         }
244         if (m_copyJob) m_copyJob->kill();
245     }
246     return true;
247 }
248
249
250 void ArchiveWidget::generateItems(QTreeWidgetItem *parentItem, QStringList items)
251 {
252     QStringList filesList;
253     QString fileName;
254     int ix = 0;
255     bool isSlideshow = parentItem->data(0, Qt::UserRole).toString() == "slideshows";
256     foreach(const QString & file, items) {
257         QTreeWidgetItem *item = new QTreeWidgetItem(parentItem, QStringList() << file);
258         fileName = KUrl(file).fileName();
259         if (isSlideshow) {
260             // we store each slideshow in a separate subdirectory
261             item->setData(0, Qt::UserRole, ix);
262             ix++;
263             KUrl slideUrl(file);
264             QDir dir(slideUrl.directory(KUrl::AppendTrailingSlash));
265             if (slideUrl.fileName().startsWith(".all.")) {
266                 // mimetype slideshow (for example *.png)
267                     QStringList filters;
268                     QString extension;
269                     // TODO: improve jpeg image detection with extension like jpeg, requires change in MLT image producers
270                     filters << "*." + slideUrl.fileName().section('.', -1);
271                     dir.setNameFilters(filters);
272                     QFileInfoList resultList = dir.entryInfoList(QDir::Files);
273                     QStringList slideImages;
274                     for (int i = 0; i < resultList.count(); i++) {
275                         m_requestedSize += resultList.at(i).size();
276                         slideImages << resultList.at(i).absoluteFilePath();
277                     }
278                     item->setData(0, Qt::UserRole + 1, slideImages);
279             }
280             else {
281                 // pattern url (like clip%.3d.png)
282                 QStringList result = dir.entryList(QDir::Files);
283                 QString filter = slideUrl.fileName();
284                 QString ext = filter.section('.', -1);
285                 filter = filter.section('%', 0, -2);
286                 QString regexp = "^" + filter + "\\d+\\." + ext + "$";
287                 QRegExp rx(regexp);
288                 QStringList slideImages;
289                 QString directory = dir.absolutePath();
290                 if (!directory.endsWith('/')) directory.append('/');
291                 foreach(const QString & path, result) {
292                     if (rx.exactMatch(path)) {
293                         m_requestedSize += QFileInfo(directory + path).size();
294                         slideImages <<  directory + path;
295                     }
296                 }
297                 item->setData(0, Qt::UserRole + 1, slideImages);
298             }                    
299         }
300         else if (filesList.contains(fileName)) {
301             // we have 2 files with same name
302             int ix = 0;
303             QString newFileName = fileName.section('.', 0, -2) + "_" + QString::number(ix) + "." + fileName.section('.', -1);
304             while (filesList.contains(newFileName)) {
305                 ix ++;
306                 newFileName = fileName.section('.', 0, -2) + "_" + QString::number(ix) + "." + fileName.section('.', -1);
307             }
308             fileName = newFileName;
309             item->setData(0, Qt::UserRole, fileName);
310         }
311         if (!isSlideshow) {
312             m_requestedSize += QFileInfo(file).size();
313             filesList << fileName;
314         }
315     }
316 }
317
318 void ArchiveWidget::slotCheckSpace()
319 {
320     KDiskFreeSpaceInfo inf = KDiskFreeSpaceInfo::freeSpaceInfo( archive_url->url().path());
321     KIO::filesize_t freeSize = inf.available();;
322     if (freeSize > m_requestedSize) {
323         // everything is ok
324         buttonBox->button(QDialogButtonBox::Apply)->setEnabled(true);
325         icon_info->setPixmap(KIcon("dialog-ok").pixmap(16, 16));
326         text_info->setText(i18n("Available space on drive: %1", KIO::convertSize(freeSize)));
327     }
328     else {
329         buttonBox->button(QDialogButtonBox::Apply)->setEnabled(false);
330         icon_info->setPixmap(KIcon("dialog-close").pixmap(16, 16));
331         text_info->setText(i18n("Not enough space on drive, free space: %1", KIO::convertSize(freeSize)));
332     }
333 }
334
335 bool ArchiveWidget::slotStartArchiving(bool firstPass)
336 {
337     if (firstPass && (m_copyJob || m_archiveThread.isRunning())) {
338         // archiving in progress, abort
339         if (m_copyJob) m_copyJob->kill(KJob::EmitResult);
340         m_abortArchive = true;
341         return true;
342     }
343     bool isArchive = compressed_archive->isChecked();
344     if (!firstPass) m_copyJob = NULL;
345     else {
346         //starting archiving
347         m_abortArchive = false;
348         m_duplicateFiles.clear();
349         m_replacementList.clear();
350         m_foldersList.clear();
351         m_filesList.clear();
352         icon_info->setPixmap(KIcon("system-run").pixmap(16, 16));
353         text_info->setText(i18n("Archiving..."));
354         repaint();
355         archive_url->setEnabled(false);
356         compressed_archive->setEnabled(false);
357     }
358     KUrl::List files;
359     KUrl destUrl;
360     QString destPath;
361     QTreeWidgetItem *parentItem;
362     bool isSlideshow = false;
363     for (int i = 0; i < files_list->topLevelItemCount(); i++) {
364         parentItem = files_list->topLevelItem(i);
365         if (parentItem->childCount() > 0 && !parentItem->isDisabled()) {
366             if (parentItem->data(0, Qt::UserRole).toString() == "slideshows") {
367                 KUrl slideFolder(archive_url->url().path(KUrl::AddTrailingSlash) + "slideshows");
368                 if (isArchive) m_foldersList.append("slideshows");
369                 else KIO::NetAccess::mkdir(slideFolder, this);
370                 isSlideshow = true;
371             }
372             files_list->setCurrentItem(parentItem);
373             if (!isSlideshow) parentItem->setDisabled(true);
374             destPath = parentItem->data(0, Qt::UserRole).toString() + "/";
375             destUrl = KUrl(archive_url->url().path(KUrl::AddTrailingSlash) + destPath);
376             QTreeWidgetItem *item;
377             for (int j = 0; j < parentItem->childCount(); j++) {
378                 item = parentItem->child(j);
379                 // Special case: slideshows
380                 if (isSlideshow) {
381                     if (item->isDisabled()) {
382                         continue;
383                     }
384                     destPath.append(item->data(0, Qt::UserRole).toString() + "/");
385                     destUrl = KUrl(archive_url->url().path(KUrl::AddTrailingSlash) + destPath);
386                     QStringList srcFiles = item->data(0, Qt::UserRole + 1).toStringList();
387                     for (int k = 0; k < srcFiles.count(); k++) {
388                         files << KUrl(srcFiles.at(k));
389                     }
390                     item->setDisabled(true);
391                     if (parentItem->indexOfChild(item) == parentItem->childCount() - 1) {
392                         // We have processed all slideshows
393                         parentItem->setDisabled(true);
394                     }
395                     break;
396                 }
397                 else if (item->data(0, Qt::UserRole).isNull()) {
398                     files << KUrl(item->text(0));
399                 }
400                 else {
401                     // We must rename the destination file, since another file with same name exists
402                     //TODO: monitor progress
403                     if (isArchive) {
404                         m_filesList.insert(item->text(0), destPath + item->data(0, Qt::UserRole).toString());
405                     }
406                     else m_duplicateFiles.insert(KUrl(item->text(0)), KUrl(destUrl.path(KUrl::AddTrailingSlash) + item->data(0, Qt::UserRole).toString()));
407                 }
408             }
409             break;
410         }
411     }
412
413     if (destPath.isEmpty()) {
414         if (m_duplicateFiles.isEmpty()) return false;        
415         QMapIterator<KUrl, KUrl> i(m_duplicateFiles);
416         if (i.hasNext()) {
417             i.next();
418             KUrl startJobSrc = i.key();
419             KUrl startJobDst = i.value();
420             m_duplicateFiles.remove(startJobSrc);
421             KIO::CopyJob *job = KIO::copyAs(startJobSrc, startJobDst, KIO::HideProgressInfo);
422             connect(job, SIGNAL(result(KJob *)), this, SLOT(slotArchivingFinished(KJob *)));
423             connect(job, SIGNAL(processedSize(KJob *, qulonglong)), this, SLOT(slotArchivingProgress(KJob *, qulonglong)));
424         }
425         return true;
426     }
427
428     if (isArchive) {
429         m_foldersList.append(destPath);
430         for (int i = 0; i < files.count(); i++) {
431             m_filesList.insert(files.at(i).path(), destPath + files.at(i).fileName());
432         }
433         slotArchivingFinished();
434     }
435     else if (files.isEmpty()) {
436         slotStartArchiving(false);
437     }
438     else {
439         KIO::NetAccess::mkdir(destUrl, this);
440         m_copyJob = KIO::copy (files, destUrl, KIO::HideProgressInfo);
441         connect(m_copyJob, SIGNAL(result(KJob *)), this, SLOT(slotArchivingFinished(KJob *)));
442         connect(m_copyJob, SIGNAL(processedSize(KJob *, qulonglong)), this, SLOT(slotArchivingProgress(KJob *, qulonglong)));
443     }
444     if (firstPass) {
445         progressBar->setValue(0);
446         buttonBox->button(QDialogButtonBox::Apply)->setText(i18n("Abort"));
447     }
448     return true;
449 }
450
451 void ArchiveWidget::slotArchivingFinished(KJob *job)
452 {
453     if (job == NULL || job->error() == 0) {
454         if (slotStartArchiving(false)) {
455             // We still have files to archive
456             return;
457         }
458         else if (!compressed_archive->isChecked()) {
459             // Archiving finished
460             progressBar->setValue(100);
461             if (processProjectFile()) {
462                 icon_info->setPixmap(KIcon("dialog-ok").pixmap(16, 16));
463                 text_info->setText(i18n("Project was successfully archived."));
464             }
465             else {
466                 icon_info->setPixmap(KIcon("dialog-close").pixmap(16, 16));
467                 text_info->setText(i18n("There was an error processing project file"));
468             }
469         } else processProjectFile();
470     }
471     else {
472         m_copyJob = NULL;
473         icon_info->setPixmap(KIcon("dialog-close").pixmap(16, 16));
474         text_info->setText(i18n("There was an error while copying the files: %1", job->errorString()));
475     }
476     if (!compressed_archive->isChecked()) {
477         buttonBox->button(QDialogButtonBox::Apply)->setText(i18n("Archive"));
478         archive_url->setEnabled(true);
479         compressed_archive->setEnabled(true);
480         for (int i = 0; i < files_list->topLevelItemCount(); i++) {
481             files_list->topLevelItem(i)->setDisabled(false);
482             for (int j = 0; j < files_list->topLevelItem(i)->childCount(); j++)
483                 files_list->topLevelItem(i)->child(j)->setDisabled(false);        
484         }
485     }
486 }
487
488 void ArchiveWidget::slotArchivingProgress(KJob *, qulonglong size)
489 {
490     progressBar->setValue((int) 100 * size / m_requestedSize);
491 }
492
493
494 bool ArchiveWidget::processProjectFile()
495 {
496     KUrl destUrl;
497     QTreeWidgetItem *item;
498     bool isArchive = compressed_archive->isChecked();
499
500     for (int i = 0; i < files_list->topLevelItemCount(); i++) {
501         QTreeWidgetItem *parentItem = files_list->topLevelItem(i);
502         if (parentItem->childCount() > 0) {
503             destUrl = KUrl(archive_url->url().path(KUrl::AddTrailingSlash) + parentItem->data(0, Qt::UserRole).toString());
504             bool isSlideshow = parentItem->data(0, Qt::UserRole).toString() == "slideshows";
505             for (int j = 0; j < parentItem->childCount(); j++) {
506                 item = parentItem->child(j);
507                 KUrl src(item->text(0));
508                 KUrl dest = destUrl;
509                 if (isSlideshow) {
510                     dest = KUrl(destUrl.path(KUrl::AddTrailingSlash) + item->data(0, Qt::UserRole).toString() + "/" + src.fileName());
511                 }
512                 else if (item->data(0, Qt::UserRole).isNull()) {
513                     dest.addPath(src.fileName());
514                 }
515                 else {
516                     dest.addPath(item->data(0, Qt::UserRole).toString());
517                 }
518                 m_replacementList.insert(src, dest);
519             }
520         }
521     }
522     
523     QDomElement mlt = m_doc.documentElement();
524     QString root = mlt.attribute("root") + "/";
525
526     // Adjust global settings
527     QString basePath;
528     if (isArchive) basePath = "$CURRENTPATH";
529     else basePath = archive_url->url().path(KUrl::RemoveTrailingSlash);
530     mlt.setAttribute("root", basePath);
531     QDomElement project = mlt.elementsByTagName("kdenlivedoc").at(0).toElement();
532     project.setAttribute("projectfolder", basePath);
533
534     // process kdenlive producers
535     QDomNodeList prods = mlt.elementsByTagName("kdenlive_producer");
536     for (int i = 0; i < prods.count(); i++) {
537         QDomElement e = prods.item(i).toElement();
538         if (e.isNull()) continue;
539         if (e.hasAttribute("resource")) {
540             KUrl src(e.attribute("resource"));
541             KUrl dest = m_replacementList.value(src);
542             if (!dest.isEmpty()) e.setAttribute("resource", dest.path());
543         }
544         if (e.hasAttribute("proxy") && e.attribute("proxy") != "-") {
545             KUrl src(e.attribute("proxy"));
546             KUrl dest = m_replacementList.value(src);
547             if (!dest.isEmpty()) e.setAttribute("proxy", dest.path());
548         }
549     }
550
551     // process mlt producers
552     prods = mlt.elementsByTagName("producer");
553     for (int i = 0; i < prods.count(); i++) {
554         QDomElement e = prods.item(i).toElement();
555         if (e.isNull()) continue;
556         QString src = EffectsList::property(e, "resource");
557         if (!src.isEmpty()) {
558             if (!src.startsWith('/')) src.prepend(root);
559             KUrl srcUrl(src);
560             KUrl dest = m_replacementList.value(src);
561             if (!dest.isEmpty()) EffectsList::setProperty(e, "resource", dest.path());
562         }
563     }
564
565     // process mlt transitions (for luma files)
566     prods = mlt.elementsByTagName("transition");
567     QString attribute;
568     for (int i = 0; i < prods.count(); i++) {
569         QDomElement e = prods.item(i).toElement();
570         if (e.isNull()) continue;
571         attribute = "resource";
572         QString src = EffectsList::property(e, attribute);
573         if (src.isEmpty()) attribute = "luma";
574         src = EffectsList::property(e, attribute);
575         if (!src.isEmpty()) {
576             if (!src.startsWith('/')) src.prepend(root);
577             KUrl srcUrl(src);
578             KUrl dest = m_replacementList.value(src);
579             if (!dest.isEmpty()) EffectsList::setProperty(e, attribute, dest.path());
580         }
581     }
582
583     QString playList = m_doc.toString();
584     if (isArchive) {
585         QString startString("\"");
586         startString.append(archive_url->url().path(KUrl::RemoveTrailingSlash));
587         QString endString("\"");
588         endString.append(basePath);
589         playList.replace(startString, endString);
590         startString = ">" + archive_url->url().path(KUrl::RemoveTrailingSlash);
591         endString = ">" + basePath;
592         playList.replace(startString, endString);
593     }
594
595     if (isArchive) {
596         m_temp = new KTemporaryFile;
597         if (!m_temp->open()) KMessageBox::error(this, i18n("Cannot create temporary file"));
598         m_temp->write(playList.toUtf8());
599         m_temp->close();
600         m_archiveThread = QtConcurrent::run(this, &ArchiveWidget::createArchive);
601         return true;
602     }
603     
604     QString path = archive_url->url().path(KUrl::AddTrailingSlash) + m_name + ".kdenlive";
605     QFile file(path);
606     if (!file.open(QIODevice::WriteOnly | QIODevice::Text)) {
607         kWarning() << "//////  ERROR writing to file: " << path;
608         KMessageBox::error(this, i18n("Cannot write to file %1", path));
609         return false;
610     }
611
612     file.write(m_doc.toString().toUtf8());
613     if (file.error() != QFile::NoError) {
614         KMessageBox::error(this, i18n("Cannot write to file %1", path));
615         file.close();
616         return false;
617     }
618     file.close();
619     return true;
620 }
621
622 void ArchiveWidget::createArchive()
623 {
624     QFileInfo dirInfo(archive_url->url().path());
625     QString user = dirInfo.owner();
626     QString group = dirInfo.group();
627     KTar archive(archive_url->url().path(KUrl::AddTrailingSlash) + m_name + ".tar.gz", "application/x-gzip");
628     archive.open( QIODevice::WriteOnly );
629
630     // Create folders
631     foreach(const QString &path, m_foldersList) {
632         archive.writeDir(path, user, group);
633     }
634
635     // Add files
636     int ix = 0;
637     QMapIterator<QString, QString> i(m_filesList);
638     while (i.hasNext()) {
639         i.next();
640         archive.addLocalFile(i.key(), i.value());
641         emit archiveProgress((int) 100 * ix / m_filesList.count());
642         ix++;
643     }
644
645     // Add project file
646     archive.addLocalFile(m_temp->fileName(), m_name + ".kdenlive");
647     bool result = archive.close();
648     delete m_temp;
649     emit archivingFinished(result);
650 }
651
652 void ArchiveWidget::slotArchivingFinished(bool result)
653 {
654     if (result) {
655         icon_info->setPixmap(KIcon("dialog-ok").pixmap(16, 16));
656         text_info->setText(i18n("Project was successfully archived."));
657     }
658     else {
659         icon_info->setPixmap(KIcon("dialog-close").pixmap(16, 16));
660         text_info->setText(i18n("There was an error processing project file"));
661     }
662     progressBar->setValue(100);
663     buttonBox->button(QDialogButtonBox::Apply)->setText(i18n("Archive"));
664     archive_url->setEnabled(true);
665     compressed_archive->setEnabled(true);
666     for (int i = 0; i < files_list->topLevelItemCount(); i++) {
667         files_list->topLevelItem(i)->setDisabled(false);
668         for (int j = 0; j < files_list->topLevelItem(i)->childCount(); j++)
669             files_list->topLevelItem(i)->child(j)->setDisabled(false);
670     }
671 }
672
673 void ArchiveWidget::slotArchivingProgress(int p)
674 {
675     progressBar->setValue(p);
676 }
677
678 void ArchiveWidget::slotStartExtracting()
679 {
680     KIO::NetAccess::mkdir(archive_url->url().path(KUrl::RemoveTrailingSlash), this);
681     m_archiveThread = QtConcurrent::run(this, &ArchiveWidget::doExtracting);
682 }
683
684 void ArchiveWidget::doExtracting()
685 {
686     KTar archive(m_extractUrl.path());
687     archive.open( QIODevice::ReadOnly );
688     archive.directory()->copyTo(archive_url->url().path(KUrl::AddTrailingSlash));
689     archive.close();
690     emit extractingFinished();    
691 }
692
693 QString ArchiveWidget::extractedProjectFile()
694 {
695     return archive_url->url().path(KUrl::AddTrailingSlash) + m_projectName;
696 }
697
698 void ArchiveWidget::slotExtractingFinished()
699 {
700     // Process project file
701     QFile file(extractedProjectFile());
702     bool error = false;
703     if (!file.open(QIODevice::ReadOnly | QIODevice::Text)) {
704         error = true;
705     }
706     else {
707         QString playList = file.readAll();
708         file.close();
709         if (playList.isEmpty()) {
710             error = true;
711         }
712         else {
713             playList.replace("$CURRENTPATH", archive_url->url().path(KUrl::RemoveTrailingSlash));
714             if (!file.open(QIODevice::WriteOnly | QIODevice::Text)) {
715                 kWarning() << "//////  ERROR writing to file: ";
716                 error = true;
717             }
718             else {
719                 file.write(playList.toUtf8());
720                 if (file.error() != QFile::NoError) {
721                     error = true;
722                 }
723                 file.close();
724             }
725         }
726     }
727     if (error) {
728         KMessageBox::sorry(kapp->activeWindow(), i18n("Cannot open project file %1", extractedProjectFile()), i18n("Cannot open file"));
729         reject();
730     }
731     else accept();
732 }