]> git.sesse.net Git - kdenlive/blob - src/archivewidget.cpp
Some progress on archiving feature
[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 <KApplication>
30 #include <KIO/NetAccess>
31
32 #include <KDebug>
33 #include <QTreeWidget>
34 #include "projectsettings.h"
35
36
37 ArchiveWidget::ArchiveWidget(QDomDocument doc, QList <DocClipBase*> list, QStringList luma_list, QWidget * parent) :
38         QDialog(parent),
39         m_requestedSize(0),
40         m_copyJob(NULL),
41         m_doc(doc)
42 {
43     setupUi(this);
44     setWindowTitle(i18n("Archive Project"));
45     archive_url->setUrl(KUrl(QDir::homePath()));
46     connect(archive_url, SIGNAL(textChanged (const QString &)), this, SLOT(slotCheckSpace()));
47
48
49     // Setup categories
50     QTreeWidgetItem *videos = new QTreeWidgetItem(files_list, QStringList() << i18n("Video clips"));
51     videos->setIcon(0, KIcon("video-x-generic"));
52     videos->setData(0, Qt::UserRole, i18n("videos"));
53     videos->setExpanded(false);
54     QTreeWidgetItem *sounds = new QTreeWidgetItem(files_list, QStringList() << i18n("Audio clips"));
55     sounds->setIcon(0, KIcon("audio-x-generic"));
56     sounds->setData(0, Qt::UserRole, i18n("sounds"));
57     sounds->setExpanded(false);
58     QTreeWidgetItem *images = new QTreeWidgetItem(files_list, QStringList() << i18n("Image clips"));
59     images->setIcon(0, KIcon("image-x-generic"));
60     images->setData(0, Qt::UserRole, i18n("images"));
61     images->setExpanded(false);
62     QTreeWidgetItem *slideshows = new QTreeWidgetItem(files_list, QStringList() << i18n("Slideshow clips"));
63     slideshows->setIcon(0, KIcon("image-x-generic"));
64     slideshows->setData(0, Qt::UserRole, i18n("slideshows"));
65     slideshows->setExpanded(false);
66     QTreeWidgetItem *texts = new QTreeWidgetItem(files_list, QStringList() << i18n("Text clips"));
67     texts->setIcon(0, KIcon("text-plain"));
68     texts->setData(0, Qt::UserRole, i18n("texts"));
69     texts->setExpanded(false);
70     QTreeWidgetItem *others = new QTreeWidgetItem(files_list, QStringList() << i18n("Other clips"));
71     others->setIcon(0, KIcon("unknown"));
72     others->setData(0, Qt::UserRole, i18n("others"));
73     others->setExpanded(false);
74     QTreeWidgetItem *lumas = new QTreeWidgetItem(files_list, QStringList() << i18n("Luma files"));
75     lumas->setIcon(0, KIcon("image-x-generic"));
76     lumas->setData(0, Qt::UserRole, i18n("lumas"));
77     lumas->setExpanded(false);
78
79     // process all files
80     QStringList allFonts;
81     KUrl::List fileUrls;
82     QStringList fileNames;
83     generateItems(lumas, luma_list);
84
85     QStringList slideUrls;
86     QStringList audioUrls;
87     QStringList videoUrls;
88     QStringList imageUrls;
89     QStringList otherUrls;
90
91     for (int i = 0; i < list.count(); i++) {
92         DocClipBase *clip = list.at(i);
93         if (clip->clipType() == SLIDESHOW) {    
94             QStringList subfiles = ProjectSettings::extractSlideshowUrls(clip->fileURL());
95             foreach(const QString & file, subfiles) {
96                 kDebug()<<"SLIDE: "<<file;
97                 new QTreeWidgetItem(slideshows, QStringList() << file);
98                 m_requestedSize += QFileInfo(file).size();
99             }
100         } else if (!clip->fileURL().isEmpty()) {
101             if (clip->clipType() == AUDIO) audioUrls << clip->fileURL().path();
102             else videoUrls << clip->fileURL().path();
103         }
104         if (clip->clipType() == TEXT) {
105             QStringList imagefiles = TitleWidget::extractImageList(clip->getProperty("xmldata"));
106             QStringList fonts = TitleWidget::extractFontList(clip->getProperty("xmldata"));
107             imageUrls << imagefiles;
108             allFonts << fonts;
109         } else if (clip->clipType() == PLAYLIST) {
110             QStringList files = ProjectSettings::extractPlaylistUrls(clip->fileURL().path());
111             otherUrls << files;
112         }
113     }
114
115     generateItems(sounds, audioUrls);
116     generateItems(videos, videoUrls);
117     generateItems(images, imageUrls);
118     //generateItems(slideshows, slideUrls);
119     generateItems(others, otherUrls);
120     
121 #if QT_VERSION >= 0x040500
122     allFonts.removeDuplicates();
123 #endif
124
125     //TODO: fonts
126
127     // Hide unused categories, add item count
128     int total = 0;
129     for (int i = 0; i < files_list->topLevelItemCount(); i++) {
130         int items = files_list->topLevelItem(i)->childCount();
131         if (items == 0) {
132             files_list->topLevelItem(i)->setHidden(true);
133         }
134         else {
135             total += items;
136             files_list->topLevelItem(i)->setText(0, files_list->topLevelItem(i)->text(0) + " " + i18n("(%1 items)", items));
137         }
138     }
139     
140     project_files->setText(i18n("%1 files to archive, requires %2", total, KIO::convertSize(m_requestedSize)));
141     buttonBox->button(QDialogButtonBox::Apply)->setText(i18n("Archive"));
142     connect(buttonBox->button(QDialogButtonBox::Apply), SIGNAL(clicked()), this, SLOT(slotStartArchiving()));
143     buttonBox->button(QDialogButtonBox::Apply)->setEnabled(false);
144     
145     slotCheckSpace();
146 }
147
148 ArchiveWidget::~ArchiveWidget()
149 {
150 }
151
152 void ArchiveWidget::generateItems(QTreeWidgetItem *parentItem, QStringList items)
153 {
154     QStringList filesList;
155     QString fileName;
156     foreach(const QString & file, items) {
157         QTreeWidgetItem *item = new QTreeWidgetItem(parentItem, QStringList() << file);
158         fileName = KUrl(file).fileName();
159         if (filesList.contains(fileName)) {
160             // we have 2 files with same name
161             int ix = 0;
162             QString newFileName = fileName.section('.', 0, -2) + "_" + QString::number(ix) + "." + fileName.section('.', -1);
163             while (filesList.contains(newFileName)) {
164                 ix ++;
165                 newFileName = fileName.section('.', 0, -2) + "_" + QString::number(ix) + "." + fileName.section('.', -1);
166             }
167             fileName = newFileName;
168             item->setData(0, Qt::UserRole, fileName);
169         }
170         filesList << fileName;
171         m_requestedSize += QFileInfo(file).size();
172     }
173 }
174
175 void ArchiveWidget::slotCheckSpace()
176 {
177     KDiskFreeSpaceInfo inf = KDiskFreeSpaceInfo::freeSpaceInfo( archive_url->url().path());
178     KIO::filesize_t freeSize = inf.available();;
179     if (freeSize > m_requestedSize) {
180         // everything is ok
181         buttonBox->button(QDialogButtonBox::Apply)->setEnabled(true);
182         icon_info->setPixmap(KIcon("dialog-ok").pixmap(16, 16));
183         text_info->setText(i18n("Available space on drive: %1", KIO::convertSize(freeSize)));
184     }
185     else {
186         buttonBox->button(QDialogButtonBox::Apply)->setEnabled(false);
187         icon_info->setPixmap(KIcon("dialog-close").pixmap(16, 16));
188         text_info->setText(i18n("Not enough space on drive, free space: %1", KIO::convertSize(freeSize)));
189     }
190 }
191
192 bool ArchiveWidget::slotStartArchiving(bool firstPass)
193 {
194     if (firstPass && m_copyJob) {
195         // archiving in progress, abort
196         m_copyJob->kill(KJob::EmitResult);
197         return true;
198     }
199     if (!firstPass) m_copyJob = NULL;
200     else {
201         //starting archiving
202         m_duplicateFiles.clear();
203         m_replacementList.clear();
204     }
205     KUrl::List files;
206     KUrl destUrl;
207     for (int i = 0; i < files_list->topLevelItemCount(); i++) {
208         if (files_list->topLevelItem(i)->childCount() > 0 && !files_list->topLevelItem(i)->isDisabled()) {
209             files_list->setCurrentItem(files_list->topLevelItem(i));
210             files_list->topLevelItem(i)->setDisabled(true);
211             destUrl = KUrl(archive_url->url().path(KUrl::AddTrailingSlash) + files_list->topLevelItem(i)->data(0, Qt::UserRole).toString());
212             KIO::NetAccess::mkdir(destUrl, this);
213             QTreeWidgetItem *item;
214             for (int j = 0; j < files_list->topLevelItem(i)->childCount(); j++) {
215                 item = files_list->topLevelItem(i)->child(j);
216                 if (item->data(0, Qt::UserRole).isNull()) {
217                     files << KUrl(item->text(0));
218                 }
219                 else {
220                     // We must rename the destination file, since another file with same name exists
221                     //TODO: monitor progress
222                     m_duplicateFiles.insert(KUrl(item->text(0)), KUrl(destUrl.path(KUrl::AddTrailingSlash) + item->data(0, Qt::UserRole).toString()));
223                 }
224             }
225             break;
226         }
227     }
228
229     if (destUrl.isEmpty()) {
230         if (m_duplicateFiles.isEmpty()) return false;        
231         QMapIterator<KUrl, KUrl> i(m_duplicateFiles);
232         KUrl startJob;
233         if (i.hasNext()) {
234             i.next();
235             startJob = i.key();
236             KIO::CopyJob *job = KIO::copyAs(startJob, i.value(), KIO::HideProgressInfo);
237             connect(job, SIGNAL(result(KJob *)), this, SLOT(slotArchivingFinished(KJob *)));
238             connect(job, SIGNAL(processedSize(KJob *, qulonglong)), this, SLOT(slotArchivingProgress(KJob *, qulonglong)));
239             m_duplicateFiles.remove(startJob);
240         }
241         return true;
242     }
243
244     m_copyJob = KIO::copy (files, destUrl, KIO::HideProgressInfo);
245     connect(m_copyJob, SIGNAL(result(KJob *)), this, SLOT(slotArchivingFinished(KJob *)));
246     connect(m_copyJob, SIGNAL(processedSize(KJob *, qulonglong)), this, SLOT(slotArchivingProgress(KJob *, qulonglong)));
247
248     if (firstPass) {
249         progressBar->setValue(0);
250         buttonBox->button(QDialogButtonBox::Apply)->setText(i18n("Abort"));
251     }
252     return true;
253 }
254
255 void ArchiveWidget::slotArchivingFinished(KJob *job)
256 {
257     if (job->error() == 0) {
258         if (slotStartArchiving(false)) {
259             // We still have files to archive
260             return;
261         }
262         else {
263             // Archiving finished
264             progressBar->setValue(100);
265             if (processProjectFile()) {
266                 icon_info->setPixmap(KIcon("dialog-ok").pixmap(16, 16));
267                 text_info->setText(i18n("Project was successfully archived."));
268             }
269             else {
270                 icon_info->setPixmap(KIcon("dialog-close").pixmap(16, 16));
271                 text_info->setText(i18n("There was an error processing project file"));
272             }
273         }
274     }
275     else {
276         m_copyJob = NULL;
277         icon_info->setPixmap(KIcon("dialog-close").pixmap(16, 16));
278         text_info->setText(i18n("There was an error while copying the files: %1", job->errorString()));
279     }
280     buttonBox->button(QDialogButtonBox::Apply)->setText(i18n("Archive"));
281     for (int i = 0; i < files_list->topLevelItemCount(); i++) {
282         files_list->topLevelItem(i)->setDisabled(false);
283     }
284 }
285
286 void ArchiveWidget::slotArchivingProgress(KJob *, qulonglong size)
287 {
288     progressBar->setValue((int) 100 * size / m_requestedSize);
289 }
290
291
292 bool ArchiveWidget::processProjectFile()
293 {
294     KUrl destUrl;
295     QTreeWidgetItem *item;
296     for (int i = 0; i < files_list->topLevelItemCount(); i++) {
297         if (files_list->topLevelItem(i)->childCount() > 0) {
298             destUrl = KUrl(archive_url->url().path(KUrl::AddTrailingSlash) + files_list->topLevelItem(i)->data(0, Qt::UserRole).toString());
299             for (int j = 0; j < files_list->topLevelItem(i)->childCount(); j++) {
300                 item = files_list->topLevelItem(i)->child(j);
301                 KUrl src(item->text(0));
302                 KUrl dest = destUrl;
303                 if (item->data(0, Qt::UserRole).isNull()) {
304                     dest.addPath(src.fileName());
305                 }
306                 else {
307                     dest.addPath(item->data(0, Qt::UserRole).toString());
308                 }
309                 m_replacementList.insert(src, dest);
310             }
311         }
312     }
313     // process kdenlive producers
314             
315     QDomElement mlt = m_doc.documentElement();
316     QDomNodeList prods = mlt.elementsByTagName("kdenlive_producer");
317     for (int i = 0; i < prods.count(); i++) {
318         QDomElement e = prods.item(i).toElement();
319         if (e.isNull()) continue;
320         if (e.hasAttribute("resource")) {
321             KUrl src(e.attribute("resource"));
322             KUrl dest = m_replacementList.value(src);
323             if (!dest.isEmpty()) e.setAttribute("resource", dest.path());
324         }
325     }
326
327     QString path = archive_url->url().path(KUrl::AddTrailingSlash) + "project.kdenlive";
328     QFile file(path);
329     if (!file.open(QIODevice::WriteOnly | QIODevice::Text)) {
330         kWarning() << "//////  ERROR writing to file: " << path;
331         KMessageBox::error(kapp->activeWindow(), i18n("Cannot write to file %1", path));
332         return false;
333     }
334
335     file.write(m_doc.toString().toUtf8());
336     if (file.error() != QFile::NoError) {
337         KMessageBox::error(kapp->activeWindow(), i18n("Cannot write to file %1", path));
338         file.close();
339         return false;
340     }
341     file.close();
342     return true;
343 }
344