]> git.sesse.net Git - kdenlive/blob - src/archivewidget.cpp
Merge branch 'master' into buildsystem
[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     allFonts.removeDuplicates();
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     if (m_name.isEmpty()) m_name = i18n("Untitled");
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
185     setupUi(this);
186     connect(this, SIGNAL(extractingFinished()), this, SLOT(slotExtractingFinished()));
187     connect(this, SIGNAL(showMessage(const QString &, const QString &)), this, SLOT(slotDisplayMessage(const QString &, const QString &)));
188     
189     compressed_archive->setHidden(true);
190     project_files->setHidden(true);
191     files_list->setHidden(true);
192     label->setText(i18n("Extract to"));
193     setWindowTitle(i18n("Open Archived Project"));
194     archive_url->setUrl(KUrl(QDir::homePath()));
195     buttonBox->button(QDialogButtonBox::Apply)->setText(i18n("Extract"));
196     connect(buttonBox->button(QDialogButtonBox::Apply), SIGNAL(clicked()), this, SLOT(slotStartExtracting()));
197     buttonBox->button(QDialogButtonBox::Apply)->setEnabled(false);
198     adjustSize();
199     m_archiveThread = QtConcurrent::run(this, &ArchiveWidget::openArchiveForExtraction);
200 }
201
202
203 ArchiveWidget::~ArchiveWidget()
204 {
205     if (m_extractArchive) delete m_extractArchive;
206 }
207
208 void ArchiveWidget::slotDisplayMessage(const QString &icon, const QString &text)
209 {
210     icon_info->setPixmap(KIcon(icon).pixmap(16, 16));
211     text_info->setText(text);
212 }
213
214 void ArchiveWidget::openArchiveForExtraction()
215 {
216     emit showMessage("system-run", i18n("Opening archive..."));
217     m_extractArchive = new KTar(m_extractUrl.path());
218     if (!m_extractArchive->isOpen() && !m_extractArchive->open( QIODevice::ReadOnly )) {
219         emit showMessage("dialog-close", i18n("Cannot open archive file:\n %1", m_extractUrl.path()));
220         groupBox->setEnabled(false);
221         return;
222     }
223
224     // Check that it is a kdenlive project archive
225     bool isProjectArchive = false;
226     QStringList files = m_extractArchive->directory()->entries();
227     for (int i = 0; i < files.count(); i++) {
228         if (files.at(i).endsWith(".kdenlive")) {
229             m_projectName = files.at(i);
230             isProjectArchive = true;
231             break;
232         }
233     }
234
235     if (!isProjectArchive) {
236         emit showMessage("dialog-close", i18n("File %1\n is not an archived Kdenlive project", m_extractUrl.path()));
237         groupBox->setEnabled(false);
238         return;
239     }
240     buttonBox->button(QDialogButtonBox::Apply)->setEnabled(true);
241     emit showMessage("dialog-ok", i18n("Ready"));
242 }
243
244 void ArchiveWidget::done ( int r )
245 {
246     if (closeAccepted()) QDialog::done(r);
247 }
248
249 void ArchiveWidget::closeEvent ( QCloseEvent * e )
250 {
251
252     if (closeAccepted()) e->accept();
253     else e->ignore();
254 }
255
256
257 bool ArchiveWidget::closeAccepted()
258 {
259     if (!m_extractMode && !archive_url->isEnabled()) {
260         // Archiving in progress, should we stop?
261         if (KMessageBox::warningContinueCancel(this, i18n("Archiving in progress, do you want to stop it?"), i18n("Stop Archiving"), KGuiItem(i18n("Stop Archiving"))) != KMessageBox::Continue) {
262             return false;
263         }
264         if (m_copyJob) m_copyJob->kill();
265     }
266     return true;
267 }
268
269
270 void ArchiveWidget::generateItems(QTreeWidgetItem *parentItem, QStringList items)
271 {
272     QStringList filesList;
273     QString fileName;
274     int ix = 0;
275     bool isSlideshow = parentItem->data(0, Qt::UserRole).toString() == "slideshows";
276     foreach(const QString & file, items) {
277         QTreeWidgetItem *item = new QTreeWidgetItem(parentItem, QStringList() << file);
278         fileName = KUrl(file).fileName();
279         if (isSlideshow) {
280             // we store each slideshow in a separate subdirectory
281             item->setData(0, Qt::UserRole, ix);
282             ix++;
283             KUrl slideUrl(file);
284             QDir dir(slideUrl.directory(KUrl::AppendTrailingSlash));
285             if (slideUrl.fileName().startsWith(".all.")) {
286                 // mimetype slideshow (for example *.png)
287                     QStringList filters;
288                     QString extension;
289                     // TODO: improve jpeg image detection with extension like jpeg, requires change in MLT image producers
290                     filters << "*." + slideUrl.fileName().section('.', -1);
291                     dir.setNameFilters(filters);
292                     QFileInfoList resultList = dir.entryInfoList(QDir::Files);
293                     QStringList slideImages;
294                     for (int i = 0; i < resultList.count(); i++) {
295                         m_requestedSize += resultList.at(i).size();
296                         slideImages << resultList.at(i).absoluteFilePath();
297                     }
298                     item->setData(0, Qt::UserRole + 1, slideImages);
299             }
300             else {
301                 // pattern url (like clip%.3d.png)
302                 QStringList result = dir.entryList(QDir::Files);
303                 QString filter = slideUrl.fileName();
304                 QString ext = filter.section('.', -1);
305                 filter = filter.section('%', 0, -2);
306                 QString regexp = "^" + filter + "\\d+\\." + ext + "$";
307                 QRegExp rx(regexp);
308                 QStringList slideImages;
309                 QString directory = dir.absolutePath();
310                 if (!directory.endsWith('/')) directory.append('/');
311                 foreach(const QString & path, result) {
312                     if (rx.exactMatch(path)) {
313                         m_requestedSize += QFileInfo(directory + path).size();
314                         slideImages <<  directory + path;
315                     }
316                 }
317                 item->setData(0, Qt::UserRole + 1, slideImages);
318             }                    
319         }
320         else if (filesList.contains(fileName)) {
321             // we have 2 files with same name
322             int ix = 0;
323             QString newFileName = fileName.section('.', 0, -2) + "_" + QString::number(ix) + "." + fileName.section('.', -1);
324             while (filesList.contains(newFileName)) {
325                 ix ++;
326                 newFileName = fileName.section('.', 0, -2) + "_" + QString::number(ix) + "." + fileName.section('.', -1);
327             }
328             fileName = newFileName;
329             item->setData(0, Qt::UserRole, fileName);
330         }
331         if (!isSlideshow) {
332             m_requestedSize += QFileInfo(file).size();
333             filesList << fileName;
334         }
335     }
336 }
337
338 void ArchiveWidget::slotCheckSpace()
339 {
340     KDiskFreeSpaceInfo inf = KDiskFreeSpaceInfo::freeSpaceInfo( archive_url->url().path());
341     KIO::filesize_t freeSize = inf.available();;
342     if (freeSize > m_requestedSize) {
343         // everything is ok
344         buttonBox->button(QDialogButtonBox::Apply)->setEnabled(true);
345         slotDisplayMessage("dialog-ok", i18n("Available space on drive: %1", KIO::convertSize(freeSize)));
346     }
347     else {
348         buttonBox->button(QDialogButtonBox::Apply)->setEnabled(false);
349         slotDisplayMessage("dialog-close", i18n("Not enough space on drive, free space: %1", KIO::convertSize(freeSize)));
350     }
351 }
352
353 bool ArchiveWidget::slotStartArchiving(bool firstPass)
354 {
355     if (firstPass && (m_copyJob || m_archiveThread.isRunning())) {
356         // archiving in progress, abort
357         if (m_copyJob) m_copyJob->kill(KJob::EmitResult);
358         m_abortArchive = true;
359         return true;
360     }
361     bool isArchive = compressed_archive->isChecked();
362     if (!firstPass) m_copyJob = NULL;
363     else {
364         //starting archiving
365         m_abortArchive = false;
366         m_duplicateFiles.clear();
367         m_replacementList.clear();
368         m_foldersList.clear();
369         m_filesList.clear();
370         slotDisplayMessage("system-run", i18n("Archiving..."));
371         repaint();
372         archive_url->setEnabled(false);
373         compressed_archive->setEnabled(false);
374     }
375     KUrl::List files;
376     KUrl destUrl;
377     QString destPath;
378     QTreeWidgetItem *parentItem;
379     bool isSlideshow = false;
380     for (int i = 0; i < files_list->topLevelItemCount(); i++) {
381         parentItem = files_list->topLevelItem(i);
382         if (parentItem->childCount() > 0 && !parentItem->isDisabled()) {
383             if (parentItem->data(0, Qt::UserRole).toString() == "slideshows") {
384                 KUrl slideFolder(archive_url->url().path(KUrl::AddTrailingSlash) + "slideshows");
385                 if (isArchive) m_foldersList.append("slideshows");
386                 else KIO::NetAccess::mkdir(slideFolder, this);
387                 isSlideshow = true;
388             }
389             files_list->setCurrentItem(parentItem);
390             if (!isSlideshow) parentItem->setDisabled(true);
391             destPath = parentItem->data(0, Qt::UserRole).toString() + "/";
392             destUrl = KUrl(archive_url->url().path(KUrl::AddTrailingSlash) + destPath);
393             QTreeWidgetItem *item;
394             for (int j = 0; j < parentItem->childCount(); j++) {
395                 item = parentItem->child(j);
396                 // Special case: slideshows
397                 if (isSlideshow) {
398                     if (item->isDisabled()) {
399                         continue;
400                     }
401                     destPath.append(item->data(0, Qt::UserRole).toString() + "/");
402                     destUrl = KUrl(archive_url->url().path(KUrl::AddTrailingSlash) + destPath);
403                     QStringList srcFiles = item->data(0, Qt::UserRole + 1).toStringList();
404                     for (int k = 0; k < srcFiles.count(); k++) {
405                         files << KUrl(srcFiles.at(k));
406                     }
407                     item->setDisabled(true);
408                     if (parentItem->indexOfChild(item) == parentItem->childCount() - 1) {
409                         // We have processed all slideshows
410                         parentItem->setDisabled(true);
411                     }
412                     break;
413                 }
414                 else if (item->data(0, Qt::UserRole).isNull()) {
415                     files << KUrl(item->text(0));
416                 }
417                 else {
418                     // We must rename the destination file, since another file with same name exists
419                     //TODO: monitor progress
420                     if (isArchive) {
421                         m_filesList.insert(item->text(0), destPath + item->data(0, Qt::UserRole).toString());
422                     }
423                     else m_duplicateFiles.insert(KUrl(item->text(0)), KUrl(destUrl.path(KUrl::AddTrailingSlash) + item->data(0, Qt::UserRole).toString()));
424                 }
425             }
426             break;
427         }
428     }
429
430     if (destPath.isEmpty()) {
431         if (m_duplicateFiles.isEmpty()) return false;        
432         QMapIterator<KUrl, KUrl> i(m_duplicateFiles);
433         if (i.hasNext()) {
434             i.next();
435             KUrl startJobSrc = i.key();
436             KUrl startJobDst = i.value();
437             m_duplicateFiles.remove(startJobSrc);
438             KIO::CopyJob *job = KIO::copyAs(startJobSrc, startJobDst, KIO::HideProgressInfo);
439             connect(job, SIGNAL(result(KJob *)), this, SLOT(slotArchivingFinished(KJob *)));
440             connect(job, SIGNAL(processedSize(KJob *, qulonglong)), this, SLOT(slotArchivingProgress(KJob *, qulonglong)));
441         }
442         return true;
443     }
444
445     if (isArchive) {
446         m_foldersList.append(destPath);
447         for (int i = 0; i < files.count(); i++) {
448             m_filesList.insert(files.at(i).path(), destPath + files.at(i).fileName());
449         }
450         slotArchivingFinished();
451     }
452     else if (files.isEmpty()) {
453         slotStartArchiving(false);
454     }
455     else {
456         KIO::NetAccess::mkdir(destUrl, this);
457         m_copyJob = KIO::copy (files, destUrl, KIO::HideProgressInfo);
458         connect(m_copyJob, SIGNAL(result(KJob *)), this, SLOT(slotArchivingFinished(KJob *)));
459         connect(m_copyJob, SIGNAL(processedSize(KJob *, qulonglong)), this, SLOT(slotArchivingProgress(KJob *, qulonglong)));
460     }
461     if (firstPass) {
462         progressBar->setValue(0);
463         buttonBox->button(QDialogButtonBox::Apply)->setText(i18n("Abort"));
464     }
465     return true;
466 }
467
468 void ArchiveWidget::slotArchivingFinished(KJob *job)
469 {
470     if (job == NULL || job->error() == 0) {
471         if (slotStartArchiving(false)) {
472             // We still have files to archive
473             return;
474         }
475         else if (!compressed_archive->isChecked()) {
476             // Archiving finished
477             progressBar->setValue(100);
478             if (processProjectFile()) {
479                 slotDisplayMessage("dialog-ok", i18n("Project was successfully archived."));
480             }
481             else {
482                 slotDisplayMessage("dialog-close", i18n("There was an error processing project file"));
483             }
484         } else processProjectFile();
485     }
486     else {
487         m_copyJob = NULL;
488         slotDisplayMessage("dialog-close", i18n("There was an error while copying the files: %1", job->errorString()));
489     }
490     if (!compressed_archive->isChecked()) {
491         buttonBox->button(QDialogButtonBox::Apply)->setText(i18n("Archive"));
492         archive_url->setEnabled(true);
493         compressed_archive->setEnabled(true);
494         for (int i = 0; i < files_list->topLevelItemCount(); i++) {
495             files_list->topLevelItem(i)->setDisabled(false);
496             for (int j = 0; j < files_list->topLevelItem(i)->childCount(); j++)
497                 files_list->topLevelItem(i)->child(j)->setDisabled(false);        
498         }
499     }
500 }
501
502 void ArchiveWidget::slotArchivingProgress(KJob *, qulonglong size)
503 {
504     progressBar->setValue((int) 100 * size / m_requestedSize);
505 }
506
507
508 bool ArchiveWidget::processProjectFile()
509 {
510     KUrl destUrl;
511     QTreeWidgetItem *item;
512     bool isArchive = compressed_archive->isChecked();
513
514     for (int i = 0; i < files_list->topLevelItemCount(); i++) {
515         QTreeWidgetItem *parentItem = files_list->topLevelItem(i);
516         if (parentItem->childCount() > 0) {
517             destUrl = KUrl(archive_url->url().path(KUrl::AddTrailingSlash) + parentItem->data(0, Qt::UserRole).toString());
518             bool isSlideshow = parentItem->data(0, Qt::UserRole).toString() == "slideshows";
519             for (int j = 0; j < parentItem->childCount(); j++) {
520                 item = parentItem->child(j);
521                 KUrl src(item->text(0));
522                 KUrl dest = destUrl;
523                 if (isSlideshow) {
524                     dest = KUrl(destUrl.path(KUrl::AddTrailingSlash) + item->data(0, Qt::UserRole).toString() + "/" + src.fileName());
525                 }
526                 else if (item->data(0, Qt::UserRole).isNull()) {
527                     dest.addPath(src.fileName());
528                 }
529                 else {
530                     dest.addPath(item->data(0, Qt::UserRole).toString());
531                 }
532                 m_replacementList.insert(src, dest);
533             }
534         }
535     }
536     
537     QDomElement mlt = m_doc.documentElement();
538     QString root = mlt.attribute("root") + "/";
539
540     // Adjust global settings
541     QString basePath;
542     if (isArchive) basePath = "$CURRENTPATH";
543     else basePath = archive_url->url().path(KUrl::RemoveTrailingSlash);
544     mlt.setAttribute("root", basePath);
545     QDomElement project = mlt.firstChildElement("kdenlivedoc");
546     project.setAttribute("projectfolder", basePath);
547
548     // process kdenlive producers
549     QDomNodeList prods = mlt.elementsByTagName("kdenlive_producer");
550     for (int i = 0; i < prods.count(); i++) {
551         QDomElement e = prods.item(i).toElement();
552         if (e.isNull()) continue;
553         if (e.hasAttribute("resource")) {
554             KUrl src(e.attribute("resource"));
555             KUrl dest = m_replacementList.value(src);
556             if (!dest.isEmpty()) e.setAttribute("resource", dest.path());
557         }
558         if (e.hasAttribute("proxy") && e.attribute("proxy") != "-") {
559             KUrl src(e.attribute("proxy"));
560             KUrl dest = m_replacementList.value(src);
561             if (!dest.isEmpty()) e.setAttribute("proxy", dest.path());
562         }
563     }
564
565     // process mlt producers
566     prods = mlt.elementsByTagName("producer");
567     for (int i = 0; i < prods.count(); i++) {
568         QDomElement e = prods.item(i).toElement();
569         if (e.isNull()) continue;
570         QString src = EffectsList::property(e, "resource");
571         if (!src.isEmpty()) {
572             if (!src.startsWith('/')) src.prepend(root);
573             KUrl srcUrl(src);
574             KUrl dest = m_replacementList.value(src);
575             if (!dest.isEmpty()) EffectsList::setProperty(e, "resource", dest.path());
576         }
577     }
578
579     // process mlt transitions (for luma files)
580     prods = mlt.elementsByTagName("transition");
581     QString attribute;
582     for (int i = 0; i < prods.count(); i++) {
583         QDomElement e = prods.item(i).toElement();
584         if (e.isNull()) continue;
585         attribute = "resource";
586         QString src = EffectsList::property(e, attribute);
587         if (src.isEmpty()) attribute = "luma";
588         src = EffectsList::property(e, attribute);
589         if (!src.isEmpty()) {
590             if (!src.startsWith('/')) src.prepend(root);
591             KUrl srcUrl(src);
592             KUrl dest = m_replacementList.value(src);
593             if (!dest.isEmpty()) EffectsList::setProperty(e, attribute, dest.path());
594         }
595     }
596
597     QString playList = m_doc.toString();
598     if (isArchive) {
599         QString startString("\"");
600         startString.append(archive_url->url().path(KUrl::RemoveTrailingSlash));
601         QString endString("\"");
602         endString.append(basePath);
603         playList.replace(startString, endString);
604         startString = ">" + archive_url->url().path(KUrl::RemoveTrailingSlash);
605         endString = ">" + basePath;
606         playList.replace(startString, endString);
607     }
608
609     if (isArchive) {
610         m_temp = new KTemporaryFile;
611         if (!m_temp->open()) KMessageBox::error(this, i18n("Cannot create temporary file"));
612         m_temp->write(playList.toUtf8());
613         m_temp->close();
614         m_archiveThread = QtConcurrent::run(this, &ArchiveWidget::createArchive);
615         return true;
616     }
617     
618     QString path = archive_url->url().path(KUrl::AddTrailingSlash) + m_name + ".kdenlive";
619     QFile file(path);
620     if (!file.open(QIODevice::WriteOnly | QIODevice::Text)) {
621         kWarning() << "//////  ERROR writing to file: " << path;
622         KMessageBox::error(this, i18n("Cannot write to file %1", path));
623         return false;
624     }
625
626     file.write(m_doc.toString().toUtf8());
627     if (file.error() != QFile::NoError) {
628         KMessageBox::error(this, i18n("Cannot write to file %1", path));
629         file.close();
630         return false;
631     }
632     file.close();
633     return true;
634 }
635
636 void ArchiveWidget::createArchive()
637 {
638     QFileInfo dirInfo(archive_url->url().path());
639     QString user = dirInfo.owner();
640     QString group = dirInfo.group();
641     KTar archive(archive_url->url().path(KUrl::AddTrailingSlash) + m_name + ".tar.gz", "application/x-gzip");
642     archive.open( QIODevice::WriteOnly );
643
644     // Create folders
645     foreach(const QString &path, m_foldersList) {
646         archive.writeDir(path, user, group);
647     }
648
649     // Add files
650     int ix = 0;
651     QMapIterator<QString, QString> i(m_filesList);
652     while (i.hasNext()) {
653         i.next();
654         archive.addLocalFile(i.key(), i.value());
655         emit archiveProgress((int) 100 * ix / m_filesList.count());
656         ix++;
657     }
658
659     // Add project file
660     archive.addLocalFile(m_temp->fileName(), m_name + ".kdenlive");
661     bool result = archive.close();
662     delete m_temp;
663     emit archivingFinished(result);
664 }
665
666 void ArchiveWidget::slotArchivingFinished(bool result)
667 {
668     if (result) {
669         slotDisplayMessage("dialog-ok", i18n("Project was successfully archived."));
670     }
671     else {
672         slotDisplayMessage("dialog-close", i18n("There was an error processing project file"));
673     }
674     progressBar->setValue(100);
675     buttonBox->button(QDialogButtonBox::Apply)->setText(i18n("Archive"));
676     archive_url->setEnabled(true);
677     compressed_archive->setEnabled(true);
678     for (int i = 0; i < files_list->topLevelItemCount(); i++) {
679         files_list->topLevelItem(i)->setDisabled(false);
680         for (int j = 0; j < files_list->topLevelItem(i)->childCount(); j++)
681             files_list->topLevelItem(i)->child(j)->setDisabled(false);
682     }
683 }
684
685 void ArchiveWidget::slotArchivingProgress(int p)
686 {
687     progressBar->setValue(p);
688 }
689
690 void ArchiveWidget::slotStartExtracting()
691 {
692     if (m_archiveThread.isRunning()) {
693         //TODO: abort extracting
694         return;
695     }
696     QFileInfo f(m_extractUrl.path());
697     m_requestedSize = f.size();
698     KIO::NetAccess::mkdir(archive_url->url().path(KUrl::RemoveTrailingSlash), this);
699     slotDisplayMessage("system-run", i18n("Extracting..."));
700     buttonBox->button(QDialogButtonBox::Apply)->setText(i18n("Abort"));
701     m_progressTimer = new QTimer;
702     m_progressTimer->setInterval(800);
703     m_progressTimer->setSingleShot(false);
704     connect(m_progressTimer, SIGNAL(timeout()), this, SLOT(slotExtractProgress()));
705     m_archiveThread = QtConcurrent::run(this, &ArchiveWidget::doExtracting);
706     m_progressTimer->start();
707 }
708
709 void ArchiveWidget::slotExtractProgress()
710 {
711     KIO::DirectorySizeJob *job = KIO::directorySize(archive_url->url());
712     connect(job, SIGNAL(result(KJob*)), this, SLOT(slotGotProgress(KJob*)));
713 }
714
715 void ArchiveWidget::slotGotProgress(KJob* job)
716 {
717     if (!job->error()) {
718         KIO::DirectorySizeJob *j = static_cast <KIO::DirectorySizeJob *>(job);
719         progressBar->setValue((int) 100 * j->totalSize() / m_requestedSize);
720     }
721     job->deleteLater();
722 }
723
724 void ArchiveWidget::doExtracting()
725 {
726     m_extractArchive->directory()->copyTo(archive_url->url().path(KUrl::AddTrailingSlash));
727     m_extractArchive->close();
728     emit extractingFinished();    
729 }
730
731 QString ArchiveWidget::extractedProjectFile()
732 {
733     return archive_url->url().path(KUrl::AddTrailingSlash) + m_projectName;
734 }
735
736 void ArchiveWidget::slotExtractingFinished()
737 {
738     m_progressTimer->stop();
739     delete m_progressTimer;
740     // Process project file
741     QFile file(extractedProjectFile());
742     bool error = false;
743     if (!file.open(QIODevice::ReadOnly | QIODevice::Text)) {
744         error = true;
745     }
746     else {
747         QString playList = file.readAll();
748         file.close();
749         if (playList.isEmpty()) {
750             error = true;
751         }
752         else {
753             playList.replace("$CURRENTPATH", archive_url->url().path(KUrl::RemoveTrailingSlash));
754             if (!file.open(QIODevice::WriteOnly | QIODevice::Text)) {
755                 kWarning() << "//////  ERROR writing to file: ";
756                 error = true;
757             }
758             else {
759                 file.write(playList.toUtf8());
760                 if (file.error() != QFile::NoError) {
761                     error = true;
762                 }
763                 file.close();
764             }
765         }
766     }
767     if (error) {
768         KMessageBox::sorry(kapp->activeWindow(), i18n("Cannot open project file %1", extractedProjectFile()), i18n("Cannot open file"));
769         reject();
770     }
771     else accept();
772 }