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