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