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