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