archivewidget.cpp 44.9 KB
Newer Older
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20
/***************************************************************************
 *   Copyright (C) 2011 by Jean-Baptiste Mardelle (jb@kdenlive.org)        *
 *                                                                         *
 *   This program is free software; you can redistribute it and/or modify  *
 *   it under the terms of the GNU General Public License as published by  *
 *   the Free Software Foundation; either version 2 of the License, or     *
 *   (at your option) any later version.                                   *
 *                                                                         *
 *   This program is distributed in the hope that it will be useful,       *
 *   but WITHOUT ANY WARRANTY; without even the implied warranty of        *
 *   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the         *
 *   GNU General Public License for more details.                          *
 *                                                                         *
 *   You should have received a copy of the GNU General Public License     *
 *   along with this program; if not, write to the                         *
 *   Free Software Foundation, Inc.,                                       *
 *   51 Franklin Street, Fifth Floor, Boston, MA  02110-1301  USA          *
 ***************************************************************************/

#include "archivewidget.h"
Vincent Pinon's avatar
Vincent Pinon committed
21 22
#include "projectsettings.h"
#include "titler/titlewidget.h"
23
#include "mltcontroller/clipcontroller.h"
24

25
#include <klocalizedstring.h>
26
#include <KDiskFreeSpaceInfo>
27
#include <KMessageBox>
28
#include <KGuiItem>
29
#include <KTar>
Laurent Montel's avatar
Laurent Montel committed
30
#include "kdenlive_debug.h"
31
#include <kio/directorysizejob.h>
32
#include <KMessageWidget>
33

34
#include <QTreeWidget>
35
#include <QtConcurrent>
36

Laurent Montel's avatar
Laurent Montel committed
37
ArchiveWidget::ArchiveWidget(const QString &projectName, const QDomDocument &doc, const QList<ClipController *> &list, const QStringList &luma_list, QWidget *parent) :
Laurent Montel's avatar
Laurent Montel committed
38 39
    QDialog(parent)
    , m_requestedSize(0)
Laurent Montel's avatar
Laurent Montel committed
40
    , m_copyJob(nullptr)
41
    , m_name(projectName.section(QLatin1Char('.'), 0, -2))
Laurent Montel's avatar
Laurent Montel committed
42
    , m_doc(doc)
Laurent Montel's avatar
Laurent Montel committed
43
    , m_temp(nullptr)
Laurent Montel's avatar
Laurent Montel committed
44 45
    , m_abortArchive(false)
    , m_extractMode(false)
Laurent Montel's avatar
Laurent Montel committed
46 47
    , m_progressTimer(nullptr)
    , m_extractArchive(nullptr)
Laurent Montel's avatar
Laurent Montel committed
48
    , m_missingClips(0)
49
{
50
    setAttribute(Qt::WA_DeleteOnClose);
51 52
    setupUi(this);
    setWindowTitle(i18n("Archive Project"));
53
    archive_url->setUrl(QUrl::fromLocalFile(QDir::homePath()));
Laurent Montel's avatar
Laurent Montel committed
54
    connect(archive_url, &KUrlRequester::textChanged, this, &ArchiveWidget::slotCheckSpace);
55
    connect(this, SIGNAL(archivingFinished(bool)), this, SLOT(slotArchivingFinished(bool)));
56
    connect(this, SIGNAL(archiveProgress(int)), this, SLOT(slotArchivingProgress(int)));
Laurent Montel's avatar
Laurent Montel committed
57
    connect(proxy_only, &QCheckBox::stateChanged, this, &ArchiveWidget::slotProxyOnly);
58

59 60
    // Setup categories
    QTreeWidgetItem *videos = new QTreeWidgetItem(files_list, QStringList() << i18n("Video clips"));
61
    videos->setIcon(0, QIcon::fromTheme(QStringLiteral("video-x-generic")));
Laurent Montel's avatar
Laurent Montel committed
62
    videos->setData(0, Qt::UserRole, QStringLiteral("videos"));
63 64
    videos->setExpanded(false);
    QTreeWidgetItem *sounds = new QTreeWidgetItem(files_list, QStringList() << i18n("Audio clips"));
65
    sounds->setIcon(0, QIcon::fromTheme(QStringLiteral("audio-x-generic")));
Laurent Montel's avatar
Laurent Montel committed
66
    sounds->setData(0, Qt::UserRole, QStringLiteral("sounds"));
67 68
    sounds->setExpanded(false);
    QTreeWidgetItem *images = new QTreeWidgetItem(files_list, QStringList() << i18n("Image clips"));
69
    images->setIcon(0, QIcon::fromTheme(QStringLiteral("image-x-generic")));
Laurent Montel's avatar
Laurent Montel committed
70
    images->setData(0, Qt::UserRole, QStringLiteral("images"));
71 72
    images->setExpanded(false);
    QTreeWidgetItem *slideshows = new QTreeWidgetItem(files_list, QStringList() << i18n("Slideshow clips"));
73
    slideshows->setIcon(0, QIcon::fromTheme(QStringLiteral("image-x-generic")));
Laurent Montel's avatar
Laurent Montel committed
74
    slideshows->setData(0, Qt::UserRole, QStringLiteral("slideshows"));
75 76
    slideshows->setExpanded(false);
    QTreeWidgetItem *texts = new QTreeWidgetItem(files_list, QStringList() << i18n("Text clips"));
77
    texts->setIcon(0, QIcon::fromTheme(QStringLiteral("text-plain")));
Laurent Montel's avatar
Laurent Montel committed
78
    texts->setData(0, Qt::UserRole, QStringLiteral("texts"));
79
    texts->setExpanded(false);
80
    QTreeWidgetItem *playlists = new QTreeWidgetItem(files_list, QStringList() << i18n("Playlist clips"));
81
    playlists->setIcon(0, QIcon::fromTheme(QStringLiteral("video-mlt-playlist")));
Laurent Montel's avatar
Laurent Montel committed
82
    playlists->setData(0, Qt::UserRole, QStringLiteral("playlist"));
83
    playlists->setExpanded(false);
84
    QTreeWidgetItem *others = new QTreeWidgetItem(files_list, QStringList() << i18n("Other clips"));
85
    others->setIcon(0, QIcon::fromTheme(QStringLiteral("unknown")));
Laurent Montel's avatar
Laurent Montel committed
86
    others->setData(0, Qt::UserRole, QStringLiteral("others"));
87 88
    others->setExpanded(false);
    QTreeWidgetItem *lumas = new QTreeWidgetItem(files_list, QStringList() << i18n("Luma files"));
89
    lumas->setIcon(0, QIcon::fromTheme(QStringLiteral("image-x-generic")));
Laurent Montel's avatar
Laurent Montel committed
90
    lumas->setData(0, Qt::UserRole, QStringLiteral("lumas"));
91
    lumas->setExpanded(false);
Laurent Montel's avatar
Laurent Montel committed
92

93
    QTreeWidgetItem *proxies = new QTreeWidgetItem(files_list, QStringList() << i18n("Proxy clips"));
94
    proxies->setIcon(0, QIcon::fromTheme(QStringLiteral("video-x-generic")));
Laurent Montel's avatar
Laurent Montel committed
95
    proxies->setData(0, Qt::UserRole, QStringLiteral("proxy"));
96
    proxies->setExpanded(false);
Laurent Montel's avatar
Laurent Montel committed
97

98 99
    // process all files
    QStringList allFonts;
100 101
    QStringList extraImageUrls;
    QStringList otherUrls;
102 103
    generateItems(lumas, luma_list);

Laurent Montel's avatar
Laurent Montel committed
104 105 106 107 108 109
    QMap<QString, QString> slideUrls;
    QMap<QString, QString> audioUrls;
    QMap<QString, QString>videoUrls;
    QMap<QString, QString>imageUrls;
    QMap<QString, QString>playlistUrls;
    QMap<QString, QString>proxyUrls;
110

Laurent Montel's avatar
Laurent Montel committed
111
    for (int i = 0; i < list.count(); ++i) {
112
        ClipController *clip = list.at(i);
113
        ClipType t = clip->clipType();
114
        QString id = clip->clipId();
115 116 117
        if (t == Color) {
            continue;
        }
118
        if (t == SlideShow) {
119
            //TODO: Slideshow files
120
            slideUrls.insert(id, clip->clipUrl());
121
        }
Jean-Baptiste Mardelle's avatar
Jean-Baptiste Mardelle committed
122 123 124 125
        else if (t == Image) {
            imageUrls.insert(id, clip->clipUrl());
        }
        else if (t == QText) {
Laurent Montel's avatar
Laurent Montel committed
126
            allFonts << clip->property(QStringLiteral("family"));
Jean-Baptiste Mardelle's avatar
Jean-Baptiste Mardelle committed
127
        }
128
        else if (t == Text || t == TextTemplate) {
129 130
            QStringList imagefiles = TitleWidget::extractImageList(clip->property(QStringLiteral("xmldata")));
            QStringList fonts = TitleWidget::extractFontList(clip->property(QStringLiteral("xmldata")));
131
            extraImageUrls << imagefiles;
132
            allFonts << fonts;
133
        } else if (t == Playlist) {
134 135
            playlistUrls.insert(id, clip->clipUrl());
            QStringList files = ProjectSettings::extractPlaylistUrls(clip->clipUrl());
136
            otherUrls << files;
Laurent Montel's avatar
Laurent Montel committed
137 138
        } else if (!clip->clipUrl().isEmpty()) {
            if (t == Audio) {
Jean-Baptiste Mardelle's avatar
Jean-Baptiste Mardelle committed
139
                audioUrls.insert(id, clip->clipUrl());
Laurent Montel's avatar
Laurent Montel committed
140
            } else {
141
                videoUrls.insert(id, clip->clipUrl());
142
                // Check if we have a proxy
143
                QString proxy = clip->property(QStringLiteral("kdenlive:proxy"));
Laurent Montel's avatar
Laurent Montel committed
144 145 146
                if (!proxy.isEmpty() && proxy != QLatin1String("-") && QFile::exists(proxy)) {
                    proxyUrls.insert(id, proxy);
                }
147 148
            }
        }
149
    }
150

151
    generateItems(images, extraImageUrls);
152 153 154
    generateItems(sounds, audioUrls);
    generateItems(videos, videoUrls);
    generateItems(images, imageUrls);
155
    generateItems(slideshows, slideUrls);
156
    generateItems(playlists, playlistUrls);
157
    generateItems(others, otherUrls);
158
    generateItems(proxies, proxyUrls);
Laurent Montel's avatar
Laurent Montel committed
159

160
    allFonts.removeDuplicates();
161

162
    m_infoMessage = new KMessageWidget(this);
Laurent Montel's avatar
Laurent Montel committed
163
    QVBoxLayout *s =  static_cast <QVBoxLayout *>(layout());
164 165 166 167
    s->insertWidget(5, m_infoMessage);
    m_infoMessage->setCloseButtonVisible(false);
    m_infoMessage->setWordWrap(true);
    m_infoMessage->hide();
Laurent Montel's avatar
Laurent Montel committed
168 169

    // missing clips, warn user
170 171 172 173 174 175 176
    if (m_missingClips > 0) {
        QString infoText = i18np("You have %1 missing clip in your project.", "You have %1 missing clips in your project.", m_missingClips);
        m_infoMessage->setMessageType(KMessageWidget::Warning);
        m_infoMessage->setText(infoText);
        m_infoMessage->animatedShow();
    }

177 178 179 180
    //TODO: fonts

    // Hide unused categories, add item count
    int total = 0;
Laurent Montel's avatar
Laurent Montel committed
181
    for (int i = 0; i < files_list->topLevelItemCount(); ++i) {
182 183
        QTreeWidgetItem *parentItem = files_list->topLevelItem(i);
        int items = parentItem->childCount();
184 185
        if (items == 0) {
            files_list->topLevelItem(i)->setHidden(true);
Laurent Montel's avatar
Laurent Montel committed
186 187
        } else {
            if (parentItem->data(0, Qt::UserRole).toString() == QLatin1String("slideshows")) {
188
                // Special case: slideshows contain several files
189
                for (int j = 0; j < items; ++j) {
190 191
                    total += parentItem->child(j)->data(0, Qt::UserRole + 1).toStringList().count();
                }
Laurent Montel's avatar
Laurent Montel committed
192 193
            } else {
                total += items;
194
            }
Laurent Montel's avatar
Laurent Montel committed
195
            parentItem->setText(0, files_list->topLevelItem(i)->text(0) + QLatin1Char(' ') + i18np("(%1 item)", "(%1 items)", items));
196 197
        }
    }
Laurent Montel's avatar
Laurent Montel committed
198 199 200
    if (m_name.isEmpty()) {
        m_name = i18n("Untitled");
    }
Laurent Montel's avatar
Laurent Montel committed
201
    compressed_archive->setText(compressed_archive->text() + QStringLiteral(" (") + m_name + QStringLiteral(".tar.gz)"));
202
    project_files->setText(i18np("%1 file to archive, requires %2", "%1 files to archive, requires %2", total, KIO::convertSize(m_requestedSize)));
203
    buttonBox->button(QDialogButtonBox::Apply)->setText(i18n("Archive"));
Laurent Montel's avatar
Laurent Montel committed
204
    connect(buttonBox->button(QDialogButtonBox::Apply), &QAbstractButton::clicked, this, &ArchiveWidget::slotStartArchiving);
205
    buttonBox->button(QDialogButtonBox::Apply)->setEnabled(false);
Laurent Montel's avatar
Laurent Montel committed
206

207 208 209
    slotCheckSpace();
}

210
// Constructor for extract widget
Laurent Montel's avatar
Laurent Montel committed
211
ArchiveWidget::ArchiveWidget(const QUrl &url, QWidget *parent):
212
    QDialog(parent),
Vincent Pinon's avatar
Vincent Pinon committed
213
    m_requestedSize(0),
Laurent Montel's avatar
Laurent Montel committed
214 215
    m_copyJob(nullptr),
    m_temp(nullptr),
Vincent Pinon's avatar
Vincent Pinon committed
216
    m_abortArchive(false),
217
    m_extractMode(true),
Vincent Pinon's avatar
Vincent Pinon committed
218
    m_extractUrl(url),
Laurent Montel's avatar
Laurent Montel committed
219
    m_extractArchive(nullptr),
Vincent Pinon's avatar
Vincent Pinon committed
220
    m_missingClips(0),
Laurent Montel's avatar
Laurent Montel committed
221
    m_infoMessage(nullptr)
222 223 224 225
{
    //setAttribute(Qt::WA_DeleteOnClose);

    setupUi(this);
Jean-Baptiste Mardelle's avatar
Jean-Baptiste Mardelle committed
226 227 228
    m_progressTimer = new QTimer;
    m_progressTimer->setInterval(800);
    m_progressTimer->setSingleShot(false);
Laurent Montel's avatar
Laurent Montel committed
229 230 231
    connect(m_progressTimer, &QTimer::timeout, this, &ArchiveWidget::slotExtractProgress);
    connect(this, &ArchiveWidget::extractingFinished, this, &ArchiveWidget::slotExtractingFinished);
    connect(this, &ArchiveWidget::showMessage, this, &ArchiveWidget::slotDisplayMessage);
Laurent Montel's avatar
Laurent Montel committed
232

233
    compressed_archive->setHidden(true);
234
    proxy_only->setHidden(true);
235 236 237 238
    project_files->setHidden(true);
    files_list->setHidden(true);
    label->setText(i18n("Extract to"));
    setWindowTitle(i18n("Open Archived Project"));
239
    archive_url->setUrl(QUrl::fromLocalFile(QDir::homePath()));
240
    buttonBox->button(QDialogButtonBox::Apply)->setText(i18n("Extract"));
Laurent Montel's avatar
Laurent Montel committed
241
    connect(buttonBox->button(QDialogButtonBox::Apply), &QAbstractButton::clicked, this, &ArchiveWidget::slotStartExtracting);
242
    buttonBox->button(QDialogButtonBox::Apply)->setEnabled(true);
243 244
    adjustSize();
    m_archiveThread = QtConcurrent::run(this, &ArchiveWidget::openArchiveForExtraction);
245 246
}

247 248
ArchiveWidget::~ArchiveWidget()
{
Laurent Montel's avatar
Laurent Montel committed
249 250
    delete m_extractArchive;
    delete m_progressTimer;
251 252
}

253
void ArchiveWidget::slotDisplayMessage(const QString &icon, const QString &text)
Laurent Montel's avatar
Laurent Montel committed
254
{
255
    icon_info->setPixmap(QIcon::fromTheme(icon).pixmap(16, 16));
256 257 258
    text_info->setText(text);
}

259 260 261 262 263 264 265
void ArchiveWidget::slotJobResult(bool success, const QString &text)
{
    m_infoMessage->setMessageType(success ? KMessageWidget::Positive : KMessageWidget::Warning);
    m_infoMessage->setText(text);
    m_infoMessage->animatedShow();
}

266 267
void ArchiveWidget::openArchiveForExtraction()
{
268
    emit showMessage(QStringLiteral("system-run"), i18n("Opening archive..."));
269
    m_extractArchive = new KTar(m_extractUrl.toLocalFile());
Laurent Montel's avatar
Laurent Montel committed
270
    if (!m_extractArchive->isOpen() && !m_extractArchive->open(QIODevice::ReadOnly)) {
271
        emit showMessage(QStringLiteral("dialog-close"), i18n("Cannot open archive file:\n %1", m_extractUrl.toLocalFile()));
272 273 274 275 276 277 278
        groupBox->setEnabled(false);
        return;
    }

    // Check that it is a kdenlive project archive
    bool isProjectArchive = false;
    QStringList files = m_extractArchive->directory()->entries();
279
    for (int i = 0; i < files.count(); ++i) {
280
        if (files.at(i).endsWith(QLatin1String(".kdenlive"))) {
281 282 283 284 285 286 287
            m_projectName = files.at(i);
            isProjectArchive = true;
            break;
        }
    }

    if (!isProjectArchive) {
288
        emit showMessage(QStringLiteral("dialog-close"), i18n("File %1\n is not an archived Kdenlive project", m_extractUrl.toLocalFile()));
289
        groupBox->setEnabled(false);
Laurent Montel's avatar
Laurent Montel committed
290
        buttonBox->button(QDialogButtonBox::Apply)->setEnabled(false);
291 292 293
        return;
    }
    buttonBox->button(QDialogButtonBox::Apply)->setEnabled(true);
294
    emit showMessage(QStringLiteral("dialog-ok"), i18n("Ready"));
295 296
}

Laurent Montel's avatar
Laurent Montel committed
297
void ArchiveWidget::done(int r)
298
{
Laurent Montel's avatar
Laurent Montel committed
299 300 301
    if (closeAccepted()) {
        QDialog::done(r);
    }
302 303
}

Laurent Montel's avatar
Laurent Montel committed
304
void ArchiveWidget::closeEvent(QCloseEvent *e)
305 306
{

Laurent Montel's avatar
Laurent Montel committed
307 308 309 310 311
    if (closeAccepted()) {
        e->accept();
    } else {
        e->ignore();
    }
312 313 314 315
}

bool ArchiveWidget::closeAccepted()
{
316
    if (!m_extractMode && !archive_url->isEnabled()) {
317 318 319 320
        // Archiving in progress, should we stop?
        if (KMessageBox::warningContinueCancel(this, i18n("Archiving in progress, do you want to stop it?"), i18n("Stop Archiving"), KGuiItem(i18n("Stop Archiving"))) != KMessageBox::Continue) {
            return false;
        }
Laurent Montel's avatar
Laurent Montel committed
321 322 323
        if (m_copyJob) {
            m_copyJob->kill();
        }
324 325 326 327
    }
    return true;
}

Laurent Montel's avatar
Laurent Montel committed
328
void ArchiveWidget::generateItems(QTreeWidgetItem *parentItem, const QStringList &items)
329 330 331
{
    QStringList filesList;
    QString fileName;
332
    int ix = 0;
333
    bool isSlideshow = parentItem->data(0, Qt::UserRole).toString() == QLatin1String("slideshows");
Laurent Montel's avatar
Laurent Montel committed
334
    for (const QString &file : items) {
335
        QTreeWidgetItem *item = new QTreeWidgetItem(parentItem, QStringList() << file);
336
        fileName = QUrl::fromLocalFile(file).fileName();
337 338 339 340
        if (isSlideshow) {
            // we store each slideshow in a separate subdirectory
            item->setData(0, Qt::UserRole, ix);
            ix++;
341
            QUrl slideUrl = QUrl::fromLocalFile(file);
342
            QDir dir(slideUrl.adjusted(QUrl::RemoveFilename).toLocalFile());
343
            if (slideUrl.fileName().startsWith(QLatin1String(".all."))) {
344
                // mimetype slideshow (for example *.png)
Laurent Montel's avatar
Laurent Montel committed
345 346
                QStringList filters;
                // TODO: improve jpeg image detection with extension like jpeg, requires change in MLT image producers
Laurent Montel's avatar
Laurent Montel committed
347
                filters << QStringLiteral("*.") + slideUrl.fileName().section(QLatin1Char('.'), -1);
Laurent Montel's avatar
Laurent Montel committed
348 349 350 351 352 353 354 355 356 357 358 359
                dir.setNameFilters(filters);
                QFileInfoList resultList = dir.entryInfoList(QDir::Files);
                QStringList slideImages;
                qint64 totalSize = 0;
                for (int i = 0; i < resultList.count(); ++i) {
                    totalSize += resultList.at(i).size();
                    slideImages << resultList.at(i).absoluteFilePath();
                }
                item->setData(0, Qt::UserRole + 1, slideImages);
                item->setData(0, Qt::UserRole + 3, totalSize);
                m_requestedSize += totalSize;
            } else {
360 361 362
                // pattern url (like clip%.3d.png)
                QStringList result = dir.entryList(QDir::Files);
                QString filter = slideUrl.fileName();
363
                QString ext = filter.section(QLatin1Char('.'), -1);
Laurent Montel's avatar
Laurent Montel committed
364 365
                filter = filter.section(QLatin1Char('%'), 0, -2);
                QString regexp = QLatin1Char('^') + filter + QStringLiteral("\\d+\\.") + ext + QLatin1Char('$');
366 367 368
                QRegExp rx(regexp);
                QStringList slideImages;
                QString directory = dir.absolutePath();
Laurent Montel's avatar
Laurent Montel committed
369 370
                if (!directory.endsWith(QLatin1Char('/'))) {
                    directory.append(QLatin1Char('/'));
Laurent Montel's avatar
Laurent Montel committed
371
                }
372
                qint64 totalSize = 0;
Laurent Montel's avatar
Laurent Montel committed
373
                foreach (const QString &path, result) {
374 375 376 377 378 379 380 381
                    if (rx.exactMatch(path)) {
                        totalSize += QFileInfo(directory + path).size();
                        slideImages <<  directory + path;
                    }
                }
                item->setData(0, Qt::UserRole + 1, slideImages);
                item->setData(0, Qt::UserRole + 3, totalSize);
                m_requestedSize += totalSize;
Laurent Montel's avatar
Laurent Montel committed
382 383
            }
        } else if (filesList.contains(fileName)) {
384
            // we have 2 files with same name
385 386
            int i = 0;
            QString newFileName = fileName.section(QLatin1Char('.'), 0, -2) + QLatin1Char('_') + QString::number(i) + QLatin1Char('.') + fileName.section(QLatin1Char('.'), -1);
387
            while (filesList.contains(newFileName)) {
388 389
                i ++;
                newFileName = fileName.section(QLatin1Char('.'), 0, -2) + QLatin1Char('_') + QString::number(i) + QLatin1Char('.') + fileName.section(QLatin1Char('.'), -1);
390 391 392 393 394 395 396
            }
            fileName = newFileName;
            item->setData(0, Qt::UserRole, fileName);
        }
        if (!isSlideshow) {
            qint64 fileSize = QFileInfo(file).size();
            if (fileSize <= 0) {
397
                item->setIcon(0, QIcon::fromTheme(QStringLiteral("edit-delete")));
398
                m_missingClips++;
Laurent Montel's avatar
Laurent Montel committed
399
            } else {
400 401 402 403 404 405 406 407
                m_requestedSize += fileSize;
                item->setData(0, Qt::UserRole + 3, fileSize);
            }
            filesList << fileName;
        }
    }
}

Laurent Montel's avatar
Laurent Montel committed
408
void ArchiveWidget::generateItems(QTreeWidgetItem *parentItem, const QMap<QString, QString> &items)
409 410 411 412
{
    QStringList filesList;
    QString fileName;
    int ix = 0;
413
    bool isSlideshow = parentItem->data(0, Qt::UserRole).toString() == QLatin1String("slideshows");
414 415 416 417 418 419
    QMap<QString, QString>::const_iterator it = items.constBegin();
    while (it != items.constEnd()) {
        QString file = it.value();
        QTreeWidgetItem *item = new QTreeWidgetItem(parentItem, QStringList() << file);
        // Store the clip's id
        item->setData(0, Qt::UserRole + 2, it.key());
420
        fileName = QUrl::fromLocalFile(file).fileName();
421 422 423 424
        if (isSlideshow) {
            // we store each slideshow in a separate subdirectory
            item->setData(0, Qt::UserRole, ix);
            ix++;
425
            QUrl slideUrl = QUrl::fromLocalFile(file);
426
            QDir dir(slideUrl.adjusted(QUrl::RemoveFilename).toLocalFile());
427
            if (slideUrl.fileName().startsWith(QLatin1String(".all."))) {
428
                // mimetype slideshow (for example *.png)
Laurent Montel's avatar
Laurent Montel committed
429 430
                QStringList filters;
                // TODO: improve jpeg image detection with extension like jpeg, requires change in MLT image producers
Laurent Montel's avatar
Laurent Montel committed
431
                filters << QStringLiteral("*.") + slideUrl.fileName().section(QLatin1Char('.'), -1);
Laurent Montel's avatar
Laurent Montel committed
432 433 434 435 436 437 438 439 440 441 442 443
                dir.setNameFilters(filters);
                QFileInfoList resultList = dir.entryInfoList(QDir::Files);
                QStringList slideImages;
                qint64 totalSize = 0;
                for (int i = 0; i < resultList.count(); ++i) {
                    totalSize += resultList.at(i).size();
                    slideImages << resultList.at(i).absoluteFilePath();
                }
                item->setData(0, Qt::UserRole + 1, slideImages);
                item->setData(0, Qt::UserRole + 3, totalSize);
                m_requestedSize += totalSize;
            } else {
444 445 446
                // pattern url (like clip%.3d.png)
                QStringList result = dir.entryList(QDir::Files);
                QString filter = slideUrl.fileName();
447
                QString ext = filter.section(QLatin1Char('.'), -1).section(QLatin1Char('?'), 0, 0);
Laurent Montel's avatar
Laurent Montel committed
448 449
                filter = filter.section(QLatin1Char('%'), 0, -2);
                QString regexp = QLatin1Char('^') + filter + QStringLiteral("\\d+\\.") + ext + QLatin1Char('$');
450 451
                QRegExp rx(regexp);
                QStringList slideImages;
452
                qint64 totalSize = 0;
Laurent Montel's avatar
Laurent Montel committed
453
                foreach (const QString &path, result) {
454
                    if (rx.exactMatch(path)) {
455 456
                        totalSize += QFileInfo(dir.absoluteFilePath(path)).size();
                        slideImages <<  dir.absoluteFilePath(path);
457 458 459
                    }
                }
                item->setData(0, Qt::UserRole + 1, slideImages);
460 461
                item->setData(0, Qt::UserRole + 3, totalSize);
                m_requestedSize += totalSize;
462
            }
Laurent Montel's avatar
Laurent Montel committed
463
        } else if (filesList.contains(fileName)) {
464
            // we have 2 files with same name
Laurent Montel's avatar
Laurent Montel committed
465
            int index2 = 0;
Laurent Montel's avatar
Laurent Montel committed
466
            QString newFileName = fileName.section(QLatin1Char('.'), 0, -2) + QLatin1Char('_') + QString::number(index2) + QLatin1Char('.') + fileName.section(QLatin1Char('.'), -1);
467
            while (filesList.contains(newFileName)) {
Laurent Montel's avatar
Laurent Montel committed
468
                index2 ++;
Laurent Montel's avatar
Laurent Montel committed
469
                newFileName = fileName.section(QLatin1Char('.'), 0, -2) + QLatin1Char('_') + QString::number(index2) + QLatin1Char('.') + fileName.section(QLatin1Char('.'), -1);
470 471 472 473
            }
            fileName = newFileName;
            item->setData(0, Qt::UserRole, fileName);
        }
474
        if (!isSlideshow) {
475 476
            qint64 fileSize = QFileInfo(file).size();
            if (fileSize <= 0) {
477
                item->setIcon(0, QIcon::fromTheme(QStringLiteral("edit-delete")));
478
                m_missingClips++;
Laurent Montel's avatar
Laurent Montel committed
479
            } else {
480 481 482
                m_requestedSize += fileSize;
                item->setData(0, Qt::UserRole + 3, fileSize);
            }
483 484
            filesList << fileName;
        }
485
        ++it;
486 487 488
    }
}

489 490
void ArchiveWidget::slotCheckSpace()
{
Jean-Baptiste Mardelle's avatar
Jean-Baptiste Mardelle committed
491
    KDiskFreeSpaceInfo inf = KDiskFreeSpaceInfo::freeSpaceInfo(archive_url->url().toLocalFile());
492
    KIO::filesize_t freeSize = inf.available();
493 494 495
    if (freeSize > m_requestedSize) {
        // everything is ok
        buttonBox->button(QDialogButtonBox::Apply)->setEnabled(true);
496
        slotDisplayMessage(QStringLiteral("dialog-ok"), i18n("Available space on drive: %1", KIO::convertSize(freeSize)));
Laurent Montel's avatar
Laurent Montel committed
497
    } else {
498
        buttonBox->button(QDialogButtonBox::Apply)->setEnabled(false);
499
        slotDisplayMessage(QStringLiteral("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
Laurent Montel's avatar
Laurent Montel committed
507 508 509
        if (m_copyJob) {
            m_copyJob->kill(KJob::EmitResult);
        }
510
        m_abortArchive = true;
511 512
        return true;
    }
513
    bool isArchive = compressed_archive->isChecked();
Laurent Montel's avatar
Laurent Montel committed
514
    if (!firstPass) {
Laurent Montel's avatar
Laurent Montel committed
515
        m_copyJob = nullptr;
Laurent Montel's avatar
Laurent Montel committed
516
    } else {
517
        //starting archiving
518
        m_abortArchive = false;
519 520
        m_duplicateFiles.clear();
        m_replacementList.clear();
521 522
        m_foldersList.clear();
        m_filesList.clear();
523
        slotDisplayMessage(QStringLiteral("system-run"), i18n("Archiving..."));
524
        repaint();
525
        archive_url->setEnabled(false);
526
        proxy_only->setEnabled(false);
527
        compressed_archive->setEnabled(false);
528
    }
Laurent Montel's avatar
Laurent Montel committed
529
    QList<QUrl> files;
530
    QUrl destUrl;
531
    QString destPath;
532 533
    QTreeWidgetItem *parentItem;
    bool isSlideshow = false;
534
    int items = 0;
535

536
    // We parse all files going into one folder, then start the copy job
537
    for (int i = 0; i < files_list->topLevelItemCount(); ++i) {
538
        parentItem = files_list->topLevelItem(i);
539 540 541 542 543
        if (parentItem->isDisabled()) {
            parentItem->setExpanded(false);
            continue;
        }
        if (parentItem->childCount() > 0) {
544
            if (parentItem->data(0, Qt::UserRole).toString() == QLatin1String("slideshows")) {
Laurent Montel's avatar
Laurent Montel committed
545
                QUrl slideFolder = QUrl::fromLocalFile(archive_url->url().toLocalFile() + QStringLiteral("/slideshows"));
Laurent Montel's avatar
Laurent Montel committed
546 547 548
                if (isArchive) {
                    m_foldersList.append(QStringLiteral("slideshows"));
                } else {
549
                    QDir dir(slideFolder.toLocalFile());
550
                    if (!dir.mkpath(QStringLiteral("."))) {
551
                        KMessageBox::sorry(this, i18n("Cannot create directory %1", slideFolder.toLocalFile()));
552 553
                    }
                }
554
                isSlideshow = true;
Laurent Montel's avatar
Laurent Montel committed
555 556
            } else {
                isSlideshow = false;
557
            }
558
            files_list->setCurrentItem(parentItem);
559
            parentItem->setExpanded(true);
Laurent Montel's avatar
Laurent Montel committed
560 561
            destPath = parentItem->data(0, Qt::UserRole).toString() + QLatin1Char('/');
            destUrl = QUrl::fromLocalFile(archive_url->url().toLocalFile() + QLatin1Char('/') + destPath);
562
            QTreeWidgetItem *item;
563
            for (int j = 0; j < parentItem->childCount(); ++j) {
564
                item = parentItem->child(j);
Laurent Montel's avatar
Laurent Montel committed
565 566 567
                if (item->isDisabled()) {
                    continue;
                }
568
                // Special case: slideshows
Laurent Montel's avatar
Laurent Montel committed
569
                items++;
570
                if (isSlideshow) {
Laurent Montel's avatar
Laurent Montel committed
571
                    destPath += item->data(0, Qt::UserRole).toString() + QLatin1Char('/');
572
                    destUrl = QUrl::fromLocalFile(archive_url->url().toLocalFile() + QDir::separator() + destPath);
573
                    QStringList srcFiles = item->data(0, Qt::UserRole + 1).toStringList();
574
                    for (int k = 0; k < srcFiles.count(); ++k) {
575
                        files << QUrl::fromLocalFile(srcFiles.at(k));
576 577 578 579 580 581 582
                    }
                    item->setDisabled(true);
                    if (parentItem->indexOfChild(item) == parentItem->childCount() - 1) {
                        // We have processed all slideshows
                        parentItem->setDisabled(true);
                    }
                    break;
Laurent Montel's avatar
Laurent Montel committed
583
                } else if (item->data(0, Qt::UserRole).isNull()) {
584
                    files << QUrl::fromLocalFile(item->text(0));
Laurent Montel's avatar
Laurent Montel committed
585
                } else {
586 587
                    // We must rename the destination file, since another file with same name exists
                    //TODO: monitor progress
588 589
                    if (isArchive) {
                        m_filesList.insert(item->text(0), destPath + item->data(0, Qt::UserRole).toString());
Laurent Montel's avatar
Laurent Montel committed
590
                    } else {
Laurent Montel's avatar
Laurent Montel committed
591
                        m_duplicateFiles.insert(QUrl::fromLocalFile(item->text(0)), QUrl::fromLocalFile(destUrl.toLocalFile() + QLatin1Char('/') + item->data(0, Qt::UserRole).toString()));
592
                    }
593 594
                }
            }
Laurent Montel's avatar
Laurent Montel committed
595 596 597
            if (!isSlideshow) {
                parentItem->setDisabled(true);
            }
598 599
            break;
        }
600
    }
601

602
    if (items == 0) {
Laurent Montel's avatar
Laurent Montel committed
603
        // No clips to archive
Laurent Montel's avatar
Laurent Montel committed
604
        slotArchivingFinished(nullptr, true);
Laurent Montel's avatar
Laurent Montel committed
605
        return true;
606
    }
607

608
    if (destPath.isEmpty()) {
Laurent Montel's avatar
Laurent Montel committed
609 610 611
        if (m_duplicateFiles.isEmpty()) {
            return false;
        }
612
        QMapIterator<QUrl, QUrl> i(m_duplicateFiles);
613 614
        if (i.hasNext()) {
            i.next();
615 616
            QUrl startJobSrc = i.key();
            QUrl startJobDst = i.value();
617 618
            m_duplicateFiles.remove(startJobSrc);
            KIO::CopyJob *job = KIO::copyAs(startJobSrc, startJobDst, KIO::HideProgressInfo);
Laurent Montel's avatar
Laurent Montel committed
619 620
            connect(job, SIGNAL(result(KJob *)), this, SLOT(slotArchivingFinished(KJob *)));
            connect(job, SIGNAL(processedSize(KJob *, qulonglong)), this, SLOT(slotArchivingProgress(KJob *, qulonglong)));
621 622 623 624
        }
        return true;
    }

625 626
    if (isArchive) {
        m_foldersList.append(destPath);
627
        for (int i = 0; i < files.count(); ++i) {
628
            m_filesList.insert(files.at(i).toLocalFile(), destPath + files.at(i).fileName());
629 630
        }
        slotArchivingFinished();
Laurent Montel's avatar
Laurent Montel committed
631
    } else if (files.isEmpty()) {
632
        slotStartArchiving(false);
Laurent Montel's avatar
Laurent Montel committed
633
    } else {
634
        QDir dir(destUrl.toLocalFile());
635
        if (!dir.mkpath(QStringLiteral("."))) {
636
            KMessageBox::sorry(this, i18n("Cannot create directory %1", destUrl.toLocalFile()));
637
        }
Laurent Montel's avatar
Laurent Montel committed
638
        m_copyJob = KIO::copy(files, destUrl, KIO::HideProgressInfo);
Laurent Montel's avatar
Laurent Montel committed
639 640
        connect(m_copyJob, SIGNAL(result(KJob *)), this, SLOT(slotArchivingFinished(KJob *)));
        connect(m_copyJob, SIGNAL(processedSize(KJob *, qulonglong)), this, SLOT(slotArchivingProgress(KJob *, qulonglong)));
641
    }
642 643 644 645 646
    if (firstPass) {
        progressBar->setValue(0);
        buttonBox->button(QDialogButtonBox::Apply)->setText(i18n("Abort"));
    }
    return true;
647 648
}

649
void ArchiveWidget::slotArchivingFinished(KJob *job, bool finished)
650
{
Laurent Montel's avatar
Laurent Montel committed
651
    if (job == nullptr || job->error() == 0) {
652
        if (!finished && slotStartArchiving(false)) {
653 654
            // We still have files to archive
            return;
Laurent Montel's avatar
Laurent Montel committed
655
        } else if (!compressed_archive->isChecked()) {
656 657 658
            // Archiving finished
            progressBar->setValue(100);
            if (processProjectFile()) {
659
                slotJobResult(true, i18n("Project was successfully archived."));
Laurent Montel's avatar
Laurent Montel committed
660
            } else {
661
                slotJobResult(false, i18n("There was an error processing project file"));
662
            }
Laurent Montel's avatar
Laurent Montel committed
663 664 665 666
        } else {
            processProjectFile();
        }
    } else {
Laurent Montel's avatar
Laurent Montel committed
667
        m_copyJob = nullptr;
668
        slotJobResult(false, i18n("There was an error while copying the files: %1", job->errorString()));
669
    }
670 671 672
    if (!compressed_archive->isChecked()) {
        buttonBox->button(QDialogButtonBox::Apply)->setText(i18n("Archive"));
        archive_url->setEnabled(true);
673
        proxy_only->setEnabled(true);
674
        compressed_archive->setEnabled(true);
675
        for (int i = 0; i < files_list->topLevelItemCount(); ++i) {
676
            files_list->topLevelItem(i)->setDisabled(false);
Laurent Montel's avatar
Laurent Montel committed
677 678 679
            for (int j = 0; j < files_list->topLevelItem(i)->childCount(); ++j) {
                files_list->topLevelItem(i)->child(j)->setDisabled(false);
            }
680
        }
681
    }
682 683 684 685
}

void ArchiveWidget::slotArchivingProgress(KJob *, qulonglong size)
{
686
    progressBar->setValue(100 * size / m_requestedSize);
687 688
}