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