]> git.sesse.net Git - kdenlive/blob - src/archivewidget.cpp
Hide proxy checkbox when decompressing archived project
[kdenlive] / src / archivewidget.cpp
1 /***************************************************************************
2  *   Copyright (C) 2011 by Jean-Baptiste Mardelle (jb@kdenlive.org)        *
3  *                                                                         *
4  *   This program is free software; you can redistribute it and/or modify  *
5  *   it under the terms of the GNU General Public License as published by  *
6  *   the Free Software Foundation; either version 2 of the License, or     *
7  *   (at your option) any later version.                                   *
8  *                                                                         *
9  *   This program is distributed in the hope that it will be useful,       *
10  *   but WITHOUT ANY WARRANTY; without even the implied warranty of        *
11  *   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the         *
12  *   GNU General Public License for more details.                          *
13  *                                                                         *
14  *   You should have received a copy of the GNU General Public License     *
15  *   along with this program; if not, write to the                         *
16  *   Free Software Foundation, Inc.,                                       *
17  *   51 Franklin Street, Fifth Floor, Boston, MA  02110-1301  USA          *
18  ***************************************************************************/
19
20
21 #include "archivewidget.h"
22 #include "titlewidget.h"
23
24 #include <KLocale>
25 #include <KDiskFreeSpaceInfo>
26 #include <KUrlRequester>
27 #include <KFileDialog>
28 #include <KMessageBox>
29 #include <KGuiItem>
30 #include <KIO/NetAccess>
31 #include <KTar>
32 #include <KDebug>
33 #include <KApplication>
34 #include <kio/directorysizejob.h>
35 #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     proxy_only->setHidden(true);
227     project_files->setHidden(true);
228     files_list->setHidden(true);
229     label->setText(i18n("Extract to"));
230     setWindowTitle(i18n("Open Archived Project"));
231     archive_url->setUrl(KUrl(QDir::homePath()));
232     buttonBox->button(QDialogButtonBox::Apply)->setText(i18n("Extract"));
233     connect(buttonBox->button(QDialogButtonBox::Apply), SIGNAL(clicked()), this, SLOT(slotStartExtracting()));
234     buttonBox->button(QDialogButtonBox::Apply)->setEnabled(true);
235     adjustSize();
236     m_archiveThread = QtConcurrent::run(this, &ArchiveWidget::openArchiveForExtraction);
237 }
238
239
240 ArchiveWidget::~ArchiveWidget()
241 {
242     if (m_extractArchive) delete m_extractArchive;
243 }
244
245 void ArchiveWidget::slotDisplayMessage(const QString &icon, const QString &text)
246 {    
247     icon_info->setPixmap(KIcon(icon).pixmap(16, 16));
248     text_info->setText(text);
249 }
250
251 void ArchiveWidget::slotJobResult(bool success, const QString &text)
252 {
253 #if KDE_IS_VERSION(4,7,0)
254     m_infoMessage->setMessageType(success ? KMessageWidget::Positive : KMessageWidget::Warning);
255     m_infoMessage->setText(text);
256     m_infoMessage->animatedShow();
257 #else
258     if (success) icon_info->setPixmap(KIcon("dialog-ok").pixmap(16, 16));
259     else icon_info->setPixmap(KIcon("dialog-close").pixmap(16, 16));
260     text_info->setText(text);
261 #endif
262 }
263
264 void ArchiveWidget::openArchiveForExtraction()
265 {
266     emit showMessage("system-run", i18n("Opening archive..."));
267     m_extractArchive = new KTar(m_extractUrl.path());
268     if (!m_extractArchive->isOpen() && !m_extractArchive->open( QIODevice::ReadOnly )) {
269         emit showMessage("dialog-close", i18n("Cannot open archive file:\n %1", m_extractUrl.path()));
270         groupBox->setEnabled(false);
271         return;
272     }
273
274     // Check that it is a kdenlive project archive
275     bool isProjectArchive = false;
276     QStringList files = m_extractArchive->directory()->entries();
277     for (int i = 0; i < files.count(); i++) {
278         if (files.at(i).endsWith(".kdenlive")) {
279             m_projectName = files.at(i);
280             isProjectArchive = true;
281             break;
282         }
283     }
284
285     if (!isProjectArchive) {
286         emit showMessage("dialog-close", i18n("File %1\n is not an archived Kdenlive project", m_extractUrl.path()));
287         groupBox->setEnabled(false);
288         return;
289     }
290     buttonBox->button(QDialogButtonBox::Apply)->setEnabled(true);
291     emit showMessage("dialog-ok", i18n("Ready"));
292 }
293
294 void ArchiveWidget::done ( int r )
295 {
296     if (closeAccepted()) QDialog::done(r);
297 }
298
299 void ArchiveWidget::closeEvent ( QCloseEvent * e )
300 {
301
302     if (closeAccepted()) e->accept();
303     else e->ignore();
304 }
305
306
307 bool ArchiveWidget::closeAccepted()
308 {
309     if (!m_extractMode && !archive_url->isEnabled()) {
310         // Archiving in progress, should we stop?
311         if (KMessageBox::warningContinueCancel(this, i18n("Archiving in progress, do you want to stop it?"), i18n("Stop Archiving"), KGuiItem(i18n("Stop Archiving"))) != KMessageBox::Continue) {
312             return false;
313         }
314         if (m_copyJob) m_copyJob->kill();
315     }
316     return true;
317 }
318
319
320 void ArchiveWidget::generateItems(QTreeWidgetItem *parentItem, QStringList items)
321 {
322     QStringList filesList;
323     QString fileName;
324     int ix = 0;
325     bool isSlideshow = parentItem->data(0, Qt::UserRole).toString() == "slideshows";
326     foreach(const QString & file, items) {
327         QTreeWidgetItem *item = new QTreeWidgetItem(parentItem, QStringList() << file);
328         fileName = KUrl(file).fileName();
329         if (isSlideshow) {
330             // we store each slideshow in a separate subdirectory
331             item->setData(0, Qt::UserRole, ix);
332             ix++;
333             KUrl slideUrl(file);
334             QDir dir(slideUrl.directory(KUrl::AppendTrailingSlash));
335             if (slideUrl.fileName().startsWith(".all.")) {
336                 // mimetype slideshow (for example *.png)
337                     QStringList filters;
338                     QString extension;
339                     // TODO: improve jpeg image detection with extension like jpeg, requires change in MLT image producers
340                     filters << "*." + slideUrl.fileName().section('.', -1);
341                     dir.setNameFilters(filters);
342                     QFileInfoList resultList = dir.entryInfoList(QDir::Files);
343                     QStringList slideImages;
344                     qint64 totalSize = 0;
345                     for (int i = 0; i < resultList.count(); i++) {
346                         totalSize += resultList.at(i).size();
347                         slideImages << resultList.at(i).absoluteFilePath();
348                     }
349                     item->setData(0, Qt::UserRole + 1, slideImages);
350                     item->setData(0, Qt::UserRole + 3, totalSize);
351                     m_requestedSize += totalSize;
352             }
353             else {
354                 // pattern url (like clip%.3d.png)
355                 QStringList result = dir.entryList(QDir::Files);
356                 QString filter = slideUrl.fileName();
357                 QString ext = filter.section('.', -1);
358                 filter = filter.section('%', 0, -2);
359                 QString regexp = "^" + filter + "\\d+\\." + ext + "$";
360                 QRegExp rx(regexp);
361                 QStringList slideImages;
362                 QString directory = dir.absolutePath();
363                 if (!directory.endsWith('/')) directory.append('/');
364                 qint64 totalSize = 0;
365                 foreach(const QString & path, result) {
366                     if (rx.exactMatch(path)) {
367                         totalSize += QFileInfo(directory + path).size();
368                         slideImages <<  directory + path;
369                     }
370                 }
371                 item->setData(0, Qt::UserRole + 1, slideImages);
372                 item->setData(0, Qt::UserRole + 3, totalSize);
373                 m_requestedSize += totalSize;
374             }                    
375         }
376         else if (filesList.contains(fileName)) {
377             // we have 2 files with same name
378             int ix = 0;
379             QString newFileName = fileName.section('.', 0, -2) + "_" + QString::number(ix) + "." + fileName.section('.', -1);
380             while (filesList.contains(newFileName)) {
381                 ix ++;
382                 newFileName = fileName.section('.', 0, -2) + "_" + QString::number(ix) + "." + fileName.section('.', -1);
383             }
384             fileName = newFileName;
385             item->setData(0, Qt::UserRole, fileName);
386         }
387         if (!isSlideshow) {
388             qint64 fileSize = QFileInfo(file).size();
389             if (fileSize <= 0) {
390                 item->setIcon(0, KIcon("edit-delete"));
391                 m_missingClips++;
392             }
393             else {
394                 m_requestedSize += fileSize;
395                 item->setData(0, Qt::UserRole + 3, fileSize);
396             }
397             filesList << fileName;
398         }
399     }
400 }
401
402 void ArchiveWidget::generateItems(QTreeWidgetItem *parentItem, QMap <QString, QString> items)
403 {
404     QStringList filesList;
405     QString fileName;
406     int ix = 0;
407     bool isSlideshow = parentItem->data(0, Qt::UserRole).toString() == "slideshows";
408     QMap<QString, QString>::const_iterator it = items.constBegin();
409     while (it != items.constEnd()) {
410         QString file = it.value();
411         QTreeWidgetItem *item = new QTreeWidgetItem(parentItem, QStringList() << file);
412         // Store the clip's id
413         item->setData(0, Qt::UserRole + 2, it.key());
414         fileName = KUrl(file).fileName();
415         if (isSlideshow) {
416             // we store each slideshow in a separate subdirectory
417             item->setData(0, Qt::UserRole, ix);
418             ix++;
419             KUrl slideUrl(file);
420             QDir dir(slideUrl.directory(KUrl::AppendTrailingSlash));
421             if (slideUrl.fileName().startsWith(".all.")) {
422                 // mimetype slideshow (for example *.png)
423                     QStringList filters;
424                     QString extension;
425                     // TODO: improve jpeg image detection with extension like jpeg, requires change in MLT image producers
426                     filters << "*." + slideUrl.fileName().section('.', -1);
427                     dir.setNameFilters(filters);
428                     QFileInfoList resultList = dir.entryInfoList(QDir::Files);
429                     QStringList slideImages;
430                     qint64 totalSize = 0;
431                     for (int i = 0; i < resultList.count(); i++) {
432                         totalSize += resultList.at(i).size();
433                         slideImages << resultList.at(i).absoluteFilePath();
434                     }
435                     item->setData(0, Qt::UserRole + 1, slideImages);
436                     item->setData(0, Qt::UserRole + 3, totalSize);
437                     m_requestedSize += totalSize;
438             }
439             else {
440                 // pattern url (like clip%.3d.png)
441                 QStringList result = dir.entryList(QDir::Files);
442                 QString filter = slideUrl.fileName();
443                 QString ext = filter.section('.', -1);
444                 filter = filter.section('%', 0, -2);
445                 QString regexp = "^" + filter + "\\d+\\." + ext + "$";
446                 QRegExp rx(regexp);
447                 QStringList slideImages;
448                 qint64 totalSize = 0;
449                 QString directory = dir.absolutePath();
450                 if (!directory.endsWith('/')) directory.append('/');
451                 foreach(const QString & path, result) {
452                     if (rx.exactMatch(path)) {
453                         totalSize += QFileInfo(directory + path).size();
454                         slideImages <<  directory + path;
455                     }
456                 }
457                 item->setData(0, Qt::UserRole + 1, slideImages);
458                 item->setData(0, Qt::UserRole + 3, totalSize);
459                 m_requestedSize += totalSize;
460             }                    
461         }
462         else if (filesList.contains(fileName)) {
463             // we have 2 files with same name
464             int ix = 0;
465             QString newFileName = fileName.section('.', 0, -2) + "_" + QString::number(ix) + "." + fileName.section('.', -1);
466             while (filesList.contains(newFileName)) {
467                 ix ++;
468                 newFileName = fileName.section('.', 0, -2) + "_" + QString::number(ix) + "." + fileName.section('.', -1);
469             }
470             fileName = newFileName;
471             item->setData(0, Qt::UserRole, fileName);
472         }
473         if (!isSlideshow) {
474             qint64 fileSize = QFileInfo(file).size();
475             if (fileSize <= 0) {
476                 item->setIcon(0, KIcon("edit-delete"));
477                 m_missingClips++;
478             }
479             else {
480                 m_requestedSize += fileSize;
481                 item->setData(0, Qt::UserRole + 3, fileSize);
482             }
483             filesList << fileName;
484         }
485         ++it;
486     }
487 }
488
489 void ArchiveWidget::slotCheckSpace()
490 {
491     KDiskFreeSpaceInfo inf = KDiskFreeSpaceInfo::freeSpaceInfo( archive_url->url().path());
492     KIO::filesize_t freeSize = inf.available();
493     if (freeSize > m_requestedSize) {
494         // everything is ok
495         buttonBox->button(QDialogButtonBox::Apply)->setEnabled(true);
496         slotDisplayMessage("dialog-ok", i18n("Available space on drive: %1", KIO::convertSize(freeSize)));
497     }
498     else {
499         buttonBox->button(QDialogButtonBox::Apply)->setEnabled(false);
500         slotDisplayMessage("dialog-close", i18n("Not enough space on drive, free space: %1", KIO::convertSize(freeSize)));
501     }
502 }
503
504 bool ArchiveWidget::slotStartArchiving(bool firstPass)
505 {
506     if (firstPass && (m_copyJob || m_archiveThread.isRunning())) {
507         // archiving in progress, abort
508         if (m_copyJob) m_copyJob->kill(KJob::EmitResult);
509         m_abortArchive = true;
510         return true;
511     }
512     bool isArchive = compressed_archive->isChecked();
513     if (!firstPass) m_copyJob = NULL;
514     else {
515         //starting archiving
516         m_abortArchive = false;
517         m_duplicateFiles.clear();
518         m_replacementList.clear();
519         m_foldersList.clear();
520         m_filesList.clear();
521         slotDisplayMessage("system-run", i18n("Archiving..."));
522         repaint();
523         archive_url->setEnabled(false);
524         proxy_only->setEnabled(false);
525         compressed_archive->setEnabled(false);
526     }
527     KUrl::List files;
528     KUrl destUrl;
529     QString destPath;
530     QTreeWidgetItem *parentItem;
531     bool isSlideshow = false;
532     
533     // We parse all files going into one folder, then start the copy job
534     
535     for (int i = 0; i < files_list->topLevelItemCount(); i++) {
536         parentItem = files_list->topLevelItem(i);
537         if (parentItem->isDisabled()) {
538             parentItem->setExpanded(false);
539             continue;
540         }
541         if (parentItem->childCount() > 0) {
542             if (parentItem->data(0, Qt::UserRole).toString() == "slideshows") {
543                 KUrl slideFolder(archive_url->url().path(KUrl::AddTrailingSlash) + "slideshows");
544                 if (isArchive) m_foldersList.append("slideshows");
545                 else KIO::NetAccess::mkdir(slideFolder, this);
546                 isSlideshow = true;
547             }
548             else isSlideshow = false;
549             files_list->setCurrentItem(parentItem);
550             parentItem->setExpanded(true);
551             destPath = parentItem->data(0, Qt::UserRole).toString() + "/";
552             destUrl = KUrl(archive_url->url().path(KUrl::AddTrailingSlash) + destPath);
553             QTreeWidgetItem *item;
554             for (int j = 0; j < parentItem->childCount(); j++) {
555                 item = parentItem->child(j);
556                 if (item->isDisabled()) continue;
557                 // Special case: slideshows
558                 if (isSlideshow) {
559                     destPath += item->data(0, Qt::UserRole).toString() + "/";
560                     destUrl = KUrl(archive_url->url().path(KUrl::AddTrailingSlash) + destPath);
561                     QStringList srcFiles = item->data(0, Qt::UserRole + 1).toStringList();
562                     for (int k = 0; k < srcFiles.count(); k++) {
563                         files << KUrl(srcFiles.at(k));
564                     }
565                     item->setDisabled(true);
566                     if (parentItem->indexOfChild(item) == parentItem->childCount() - 1) {
567                         // We have processed all slideshows
568                         parentItem->setDisabled(true);
569                     }
570                     break;
571                 }
572                 else if (item->data(0, Qt::UserRole).isNull()) {
573                     files << KUrl(item->text(0));
574                 }
575                 else {
576                     // We must rename the destination file, since another file with same name exists
577                     //TODO: monitor progress
578                     if (isArchive) {
579                         m_filesList.insert(item->text(0), destPath + item->data(0, Qt::UserRole).toString());
580                     }
581                     else m_duplicateFiles.insert(KUrl(item->text(0)), KUrl(destUrl.path(KUrl::AddTrailingSlash) + item->data(0, Qt::UserRole).toString()));
582                 }
583             }
584             if (!isSlideshow) parentItem->setDisabled(true);
585             break;
586         }
587     }
588
589     if (destPath.isEmpty()) {
590         if (m_duplicateFiles.isEmpty()) return false;        
591         QMapIterator<KUrl, KUrl> i(m_duplicateFiles);
592         if (i.hasNext()) {
593             i.next();
594             KUrl startJobSrc = i.key();
595             KUrl startJobDst = i.value();
596             m_duplicateFiles.remove(startJobSrc);
597             KIO::CopyJob *job = KIO::copyAs(startJobSrc, startJobDst, KIO::HideProgressInfo);
598             connect(job, SIGNAL(result(KJob *)), this, SLOT(slotArchivingFinished(KJob *)));
599             connect(job, SIGNAL(processedSize(KJob *, qulonglong)), this, SLOT(slotArchivingProgress(KJob *, qulonglong)));
600         }
601         return true;
602     }
603
604     if (isArchive) {
605         m_foldersList.append(destPath);
606         for (int i = 0; i < files.count(); i++) {
607             m_filesList.insert(files.at(i).path(), destPath + files.at(i).fileName());
608         }
609         slotArchivingFinished();
610     }
611     else if (files.isEmpty()) {
612         slotStartArchiving(false);
613     }
614     else {
615         KIO::NetAccess::mkdir(destUrl, this);
616         m_copyJob = KIO::copy (files, destUrl, KIO::HideProgressInfo);
617         connect(m_copyJob, SIGNAL(result(KJob *)), this, SLOT(slotArchivingFinished(KJob *)));
618         connect(m_copyJob, SIGNAL(processedSize(KJob *, qulonglong)), this, SLOT(slotArchivingProgress(KJob *, qulonglong)));
619     }
620     if (firstPass) {
621         progressBar->setValue(0);
622         buttonBox->button(QDialogButtonBox::Apply)->setText(i18n("Abort"));
623     }
624     return true;
625 }
626
627 void ArchiveWidget::slotArchivingFinished(KJob *job)
628 {
629     if (job == NULL || job->error() == 0) {
630         if (slotStartArchiving(false)) {
631             // We still have files to archive
632             return;
633         }
634         else if (!compressed_archive->isChecked()) {
635             // Archiving finished
636             progressBar->setValue(100);
637             if (processProjectFile()) {
638                 slotJobResult(true, i18n("Project was successfully archived."));
639             }
640             else {
641                 slotJobResult(false, i18n("There was an error processing project file"));
642             }
643         } else processProjectFile();
644     }
645     else {
646         m_copyJob = NULL;
647         slotJobResult(false, i18n("There was an error while copying the files: %1", job->errorString()));
648     }
649     if (!compressed_archive->isChecked()) {
650         buttonBox->button(QDialogButtonBox::Apply)->setText(i18n("Archive"));
651         archive_url->setEnabled(true);
652         proxy_only->setEnabled(true);
653         compressed_archive->setEnabled(true);
654         for (int i = 0; i < files_list->topLevelItemCount(); i++) {
655             files_list->topLevelItem(i)->setDisabled(false);
656             for (int j = 0; j < files_list->topLevelItem(i)->childCount(); j++)
657                 files_list->topLevelItem(i)->child(j)->setDisabled(false);        
658         }
659     }
660 }
661
662 void ArchiveWidget::slotArchivingProgress(KJob *, qulonglong size)
663 {
664     progressBar->setValue((int) 100 * size / m_requestedSize);
665 }
666
667
668 bool ArchiveWidget::processProjectFile()
669 {
670     KUrl destUrl;
671     QTreeWidgetItem *item;
672     bool isArchive = compressed_archive->isChecked();
673
674     for (int i = 0; i < files_list->topLevelItemCount(); i++) {
675         QTreeWidgetItem *parentItem = files_list->topLevelItem(i);
676         if (parentItem->childCount() > 0) {
677             destUrl = KUrl(archive_url->url().path(KUrl::AddTrailingSlash) + parentItem->data(0, Qt::UserRole).toString());
678             bool isSlideshow = parentItem->data(0, Qt::UserRole).toString() == "slideshows";
679             for (int j = 0; j < parentItem->childCount(); j++) {
680                 item = parentItem->child(j);
681                 KUrl src(item->text(0));
682                 KUrl dest = destUrl;
683                 if (isSlideshow) {
684                     dest = KUrl(destUrl.path(KUrl::AddTrailingSlash) + item->data(0, Qt::UserRole).toString() + "/" + src.fileName());
685                 }
686                 else if (item->data(0, Qt::UserRole).isNull()) {
687                     dest.addPath(src.fileName());
688                 }
689                 else {
690                     dest.addPath(item->data(0, Qt::UserRole).toString());
691                 }
692                 m_replacementList.insert(src, dest);
693             }
694         }
695     }
696     
697     QDomElement mlt = m_doc.documentElement();
698     QString root = mlt.attribute("root") + "/";
699
700     // Adjust global settings
701     QString basePath;
702     if (isArchive) basePath = "$CURRENTPATH";
703     else basePath = archive_url->url().path(KUrl::RemoveTrailingSlash);
704     mlt.setAttribute("root", basePath);
705     QDomElement project = mlt.firstChildElement("kdenlivedoc");
706     project.setAttribute("projectfolder", basePath);
707
708     // process kdenlive producers
709     QDomNodeList prods = mlt.elementsByTagName("kdenlive_producer");
710     for (int i = 0; i < prods.count(); i++) {
711         QDomElement e = prods.item(i).toElement();
712         if (e.isNull()) continue;
713         if (e.hasAttribute("resource")) {
714             KUrl src(e.attribute("resource"));
715             KUrl dest = m_replacementList.value(src);
716             if (!dest.isEmpty()) e.setAttribute("resource", dest.path());
717         }
718         if (e.hasAttribute("proxy") && e.attribute("proxy") != "-") {
719             KUrl src(e.attribute("proxy"));
720             KUrl dest = m_replacementList.value(src);
721             if (!dest.isEmpty()) e.setAttribute("proxy", dest.path());
722         }
723     }
724
725     // process mlt producers
726     prods = mlt.elementsByTagName("producer");
727     for (int i = 0; i < prods.count(); i++) {
728         QDomElement e = prods.item(i).toElement();
729         if (e.isNull()) continue;
730         QString src = EffectsList::property(e, "resource");
731         if (!src.isEmpty()) {
732             if (!src.startsWith('/')) src.prepend(root);
733             KUrl srcUrl(src);
734             KUrl dest = m_replacementList.value(src);
735             if (!dest.isEmpty()) EffectsList::setProperty(e, "resource", dest.path());
736         }
737     }
738
739     // process mlt transitions (for luma files)
740     prods = mlt.elementsByTagName("transition");
741     QString attribute;
742     for (int i = 0; i < prods.count(); i++) {
743         QDomElement e = prods.item(i).toElement();
744         if (e.isNull()) continue;
745         attribute = "resource";
746         QString src = EffectsList::property(e, attribute);
747         if (src.isEmpty()) attribute = "luma";
748         src = EffectsList::property(e, attribute);
749         if (!src.isEmpty()) {
750             if (!src.startsWith('/')) src.prepend(root);
751             KUrl srcUrl(src);
752             KUrl dest = m_replacementList.value(src);
753             if (!dest.isEmpty()) EffectsList::setProperty(e, attribute, dest.path());
754         }
755     }
756
757     QString playList = m_doc.toString();
758     if (isArchive) {
759         QString startString("\"");
760         startString.append(archive_url->url().path(KUrl::RemoveTrailingSlash));
761         QString endString("\"");
762         endString.append(basePath);
763         playList.replace(startString, endString);
764         startString = ">" + archive_url->url().path(KUrl::RemoveTrailingSlash);
765         endString = ">" + basePath;
766         playList.replace(startString, endString);
767     }
768
769     if (isArchive) {
770         m_temp = new KTemporaryFile;
771         if (!m_temp->open()) KMessageBox::error(this, i18n("Cannot create temporary file"));
772         m_temp->write(playList.toUtf8());
773         m_temp->close();
774         m_archiveThread = QtConcurrent::run(this, &ArchiveWidget::createArchive);
775         return true;
776     }
777     
778     QString path = archive_url->url().path(KUrl::AddTrailingSlash) + m_name + ".kdenlive";
779     QFile file(path);
780     if (!file.open(QIODevice::WriteOnly | QIODevice::Text)) {
781         kWarning() << "//////  ERROR writing to file: " << path;
782         KMessageBox::error(this, i18n("Cannot write to file %1", path));
783         return false;
784     }
785
786     file.write(m_doc.toString().toUtf8());
787     if (file.error() != QFile::NoError) {
788         KMessageBox::error(this, i18n("Cannot write to file %1", path));
789         file.close();
790         return false;
791     }
792     file.close();
793     return true;
794 }
795
796 void ArchiveWidget::createArchive()
797 {
798     QFileInfo dirInfo(archive_url->url().path());
799     QString user = dirInfo.owner();
800     QString group = dirInfo.group();
801     KTar archive(archive_url->url().path(KUrl::AddTrailingSlash) + m_name + ".tar.gz", "application/x-gzip");
802     archive.open( QIODevice::WriteOnly );
803
804     // Create folders
805     foreach(const QString &path, m_foldersList) {
806         archive.writeDir(path, user, group);
807     }
808
809     // Add files
810     int ix = 0;
811     QMapIterator<QString, QString> i(m_filesList);
812     while (i.hasNext()) {
813         i.next();
814         archive.addLocalFile(i.key(), i.value());
815         emit archiveProgress((int) 100 * ix / m_filesList.count());
816         ix++;
817     }
818
819     // Add project file
820     archive.addLocalFile(m_temp->fileName(), m_name + ".kdenlive");
821     bool result = archive.close();
822     delete m_temp;
823     emit archivingFinished(result);
824 }
825
826 void ArchiveWidget::slotArchivingFinished(bool result)
827 {
828     if (result) {
829         slotJobResult(true, i18n("Project was successfully archived."));
830         buttonBox->button(QDialogButtonBox::Apply)->setEnabled(false);
831     }
832     else {
833         slotJobResult(false, i18n("There was an error processing project file"));
834     }
835     progressBar->setValue(100);
836     buttonBox->button(QDialogButtonBox::Apply)->setText(i18n("Archive"));
837     archive_url->setEnabled(true);
838     proxy_only->setEnabled(true);
839     compressed_archive->setEnabled(true);
840     for (int i = 0; i < files_list->topLevelItemCount(); i++) {
841         files_list->topLevelItem(i)->setDisabled(false);
842         for (int j = 0; j < files_list->topLevelItem(i)->childCount(); j++)
843             files_list->topLevelItem(i)->child(j)->setDisabled(false);
844     }
845 }
846
847 void ArchiveWidget::slotArchivingProgress(int p)
848 {
849     progressBar->setValue(p);
850 }
851
852 void ArchiveWidget::slotStartExtracting()
853 {
854     if (m_archiveThread.isRunning()) {
855         //TODO: abort extracting
856         return;
857     }
858     QFileInfo f(m_extractUrl.path());
859     m_requestedSize = f.size();
860     KIO::NetAccess::mkdir(archive_url->url().path(KUrl::RemoveTrailingSlash), this);
861     slotDisplayMessage("system-run", i18n("Extracting..."));
862     buttonBox->button(QDialogButtonBox::Apply)->setText(i18n("Abort"));
863     m_progressTimer = new QTimer;
864     m_progressTimer->setInterval(800);
865     m_progressTimer->setSingleShot(false);
866     connect(m_progressTimer, SIGNAL(timeout()), this, SLOT(slotExtractProgress()));
867     m_archiveThread = QtConcurrent::run(this, &ArchiveWidget::doExtracting);
868     m_progressTimer->start();
869 }
870
871 void ArchiveWidget::slotExtractProgress()
872 {
873     KIO::DirectorySizeJob *job = KIO::directorySize(archive_url->url());
874     connect(job, SIGNAL(result(KJob*)), this, SLOT(slotGotProgress(KJob*)));
875 }
876
877 void ArchiveWidget::slotGotProgress(KJob* job)
878 {
879     if (!job->error()) {
880         KIO::DirectorySizeJob *j = static_cast <KIO::DirectorySizeJob *>(job);
881         progressBar->setValue((int) 100 * j->totalSize() / m_requestedSize);
882     }
883     job->deleteLater();
884 }
885
886 void ArchiveWidget::doExtracting()
887 {
888     m_extractArchive->directory()->copyTo(archive_url->url().path(KUrl::AddTrailingSlash));
889     m_extractArchive->close();
890     emit extractingFinished();    
891 }
892
893 QString ArchiveWidget::extractedProjectFile()
894 {
895     return archive_url->url().path(KUrl::AddTrailingSlash) + m_projectName;
896 }
897
898 void ArchiveWidget::slotExtractingFinished()
899 {
900     m_progressTimer->stop();
901     delete m_progressTimer;
902     // Process project file
903     QFile file(extractedProjectFile());
904     bool error = false;
905     if (!file.open(QIODevice::ReadOnly | QIODevice::Text)) {
906         error = true;
907     }
908     else {
909         QString playList = file.readAll();
910         file.close();
911         if (playList.isEmpty()) {
912             error = true;
913         }
914         else {
915             playList.replace("$CURRENTPATH", archive_url->url().path(KUrl::RemoveTrailingSlash));
916             if (!file.open(QIODevice::WriteOnly | QIODevice::Text)) {
917                 kWarning() << "//////  ERROR writing to file: ";
918                 error = true;
919             }
920             else {
921                 file.write(playList.toUtf8());
922                 if (file.error() != QFile::NoError) {
923                     error = true;
924                 }
925                 file.close();
926             }
927         }
928     }
929     if (error) {
930         KMessageBox::sorry(kapp->activeWindow(), i18n("Cannot open project file %1", extractedProjectFile()), i18n("Cannot open file"));
931         reject();
932     }
933     else accept();
934 }
935
936 void ArchiveWidget::slotProxyOnly(int onlyProxy)
937 {
938     m_requestedSize = 0;
939     if (onlyProxy == Qt::Checked) {
940         // Archive proxy clips
941         QStringList proxyIdList;
942         QTreeWidgetItem *parentItem = NULL;
943
944         // Build list of existing proxy ids
945         for (int i = 0; i < files_list->topLevelItemCount(); i++) {
946             parentItem = files_list->topLevelItem(i);
947             if (parentItem->data(0, Qt::UserRole).toString() == "proxy") break;
948         }
949         if (!parentItem) return;
950         int items = parentItem->childCount();
951         for (int j = 0; j < items; j++) {
952             proxyIdList << parentItem->child(j)->data(0, Qt::UserRole + 2).toString();
953         }
954         
955         // Parse all items to disable original clips for existing proxies
956         for (int i = 0; i < proxyIdList.count(); i++) {
957             QString id = proxyIdList.at(i);
958             if (id.isEmpty()) continue;
959             for (int j = 0; j < files_list->topLevelItemCount(); j++) {
960                 parentItem = files_list->topLevelItem(j);
961                 if (parentItem->data(0, Qt::UserRole).toString() == "proxy") continue;
962                 items = parentItem->childCount();
963                 for (int k = 0; k < items; k++) {
964                     if (parentItem->child(k)->data(0, Qt::UserRole + 2).toString() == id) {
965                         // This item has a proxy, do not archive it
966                         parentItem->child(k)->setFlags(Qt::ItemIsSelectable);
967                         break;
968                     }
969                 }
970             }
971         }
972     }
973     else {
974         // Archive all clips
975         for (int i = 0; i < files_list->topLevelItemCount(); i++) {
976             QTreeWidgetItem *parentItem = files_list->topLevelItem(i);
977             int items = parentItem->childCount();
978             for (int j = 0; j < items; j++) {
979                 parentItem->child(j)->setFlags(Qt::ItemIsEnabled | Qt::ItemIsSelectable);
980             }
981         }
982     }
983     
984     // Calculate requested size
985     int total = 0;
986     for (int i = 0; i < files_list->topLevelItemCount(); i++) {
987         QTreeWidgetItem *parentItem = files_list->topLevelItem(i);
988         int items = parentItem->childCount();
989         int itemsCount = 0;
990         bool isSlideshow = parentItem->data(0, Qt::UserRole).toString() == "slideshows";
991         
992         for (int j = 0; j < items; j++) {
993             if (!parentItem->child(j)->isDisabled()) {
994                 m_requestedSize += parentItem->child(j)->data(0, Qt::UserRole + 3).toInt();
995                 if (isSlideshow) total += parentItem->child(j)->data(0, Qt::UserRole + 1).toStringList().count();
996                 else total ++;
997                 itemsCount ++;
998             }
999         }
1000         parentItem->setText(0, parentItem->text(0).section("(", 0, 0) + i18np("(%1 item)", "(%1 items)", itemsCount));
1001     }
1002     project_files->setText(i18np("%1 file to archive, requires %2", "%1 files to archive, requires %2", total, KIO::convertSize(m_requestedSize)));
1003     slotCheckSpace();
1004 }
1005