1 /***************************************************************************
2 * Copyright (C) 2008 by Jean-Baptiste Mardelle (jb@kdenlive.org) *
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. *
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. *
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 ***************************************************************************/
21 #include "documentchecker.h"
23 #include "definitions.h"
26 #include <KGlobalSettings>
28 #include <KIO/NetAccess>
29 #include <KFileDialog>
30 #include <KApplication>
31 #include <KUrlRequesterDialog>
32 #include <KMessageBox>
34 #include <QTreeWidgetItem>
36 #include <QHeaderView>
40 #include <QCryptographicHash>
42 const int hashRole = Qt::UserRole;
43 const int sizeRole = Qt::UserRole + 1;
44 const int idRole = Qt::UserRole + 2;
45 const int statusRole = Qt::UserRole + 3;
47 const int CLIPMISSING = 0;
49 const int CLIPPLACEHOLDER = 2;
51 DocumentChecker::DocumentChecker(QDomNodeList producers, QDomNodeList infoproducers, QList <QDomElement> missingClips, QDomDocument doc, QWidget * parent) :
55 setFont(KGlobalSettings::toolBarFont());
59 m_view.buttonBox->button(QDialogButtonBox::Ok)->setEnabled(false);
60 for (int i = 0; i < missingClips.count(); i++) {
61 e = missingClips.at(i).toElement();
63 switch (e.attribute("type").toInt()) {
65 clipType = i18n("Video clip");
68 clipType = i18n("Mute video clip");
71 clipType = i18n("Audio clip");
74 clipType = i18n("Playlist clip");
77 clipType = i18n("Image clip");
80 clipType = i18n("Slideshow clip");
83 clipType = i18n("Video clip");
85 QTreeWidgetItem *item = new QTreeWidgetItem(m_view.treeWidget, QStringList() << clipType << e.attribute("resource"));
86 item->setIcon(0, KIcon("dialog-close"));
87 item->setData(0, hashRole, e.attribute("file_hash"));
88 item->setData(0, sizeRole, e.attribute("file_size"));
89 item->setData(0, idRole, e.attribute("id"));
90 item->setData(0, statusRole, CLIPMISSING);
92 connect(m_view.recursiveSearch, SIGNAL(pressed()), this, SLOT(slotSearchClips()));
93 connect(m_view.usePlaceholders, SIGNAL(pressed()), this, SLOT(slotPlaceholders()));
94 connect(m_view.removeSelected, SIGNAL(pressed()), this, SLOT(slotDeleteSelected()));
95 connect(m_view.treeWidget, SIGNAL(itemDoubleClicked(QTreeWidgetItem *, int)), this, SLOT(slotEditItem(QTreeWidgetItem *, int)));
99 DocumentChecker::~DocumentChecker() {}
101 void DocumentChecker::slotSearchClips()
103 QString newpath = KFileDialog::getExistingDirectory(KUrl("kfiledialog:///clipfolder"), kapp->activeWindow(), i18n("Clips folder"));
104 if (newpath.isEmpty()) return;
106 m_view.recursiveSearch->setEnabled(false);
107 QTreeWidgetItem *child = m_view.treeWidget->topLevelItem(ix);
108 while (child && child->data(0, statusRole).toInt() == CLIPMISSING) {
109 QString clipPath = searchFileRecursively(QDir(newpath), child->data(0, sizeRole).toString(), child->data(0, hashRole).toString());
110 if (!clipPath.isEmpty()) {
111 child->setText(1, clipPath);
112 child->setIcon(0, KIcon("dialog-ok"));
113 child->setData(0, statusRole, CLIPOK);
116 child = m_view.treeWidget->topLevelItem(ix);
118 m_view.recursiveSearch->setEnabled(true);
122 QString DocumentChecker::searchFileRecursively(const QDir &dir, const QString &matchSize, const QString &matchHash) const
124 QString foundFileName;
127 QStringList filesAndDirs = dir.entryList(QDir::Files | QDir::Readable);
128 for (int i = 0; i < filesAndDirs.size() && foundFileName.isEmpty(); i++) {
129 QFile file(dir.absoluteFilePath(filesAndDirs.at(i)));
130 if (file.open(QIODevice::ReadOnly)) {
131 if (QString::number(file.size()) == matchSize) {
133 * 1 MB = 1 second per 450 files (or faster)
134 * 10 MB = 9 seconds per 450 files (or faster)
136 if (file.size() > 1000000*2) {
137 fileData = file.read(1000000);
138 if (file.seek(file.size() - 1000000))
139 fileData.append(file.readAll());
141 fileData = file.readAll();
143 fileHash = QCryptographicHash::hash(fileData, QCryptographicHash::Md5);
144 if (QString(fileHash.toHex()) == matchHash)
145 return file.fileName();
148 //kDebug() << filesAndDirs.at(i) << file.size() << fileHash.toHex();
150 filesAndDirs = dir.entryList(QDir::Dirs | QDir::Readable | QDir::Executable | QDir::NoDotAndDotDot);
151 for (int i = 0; i < filesAndDirs.size() && foundFileName.isEmpty(); i++) {
152 foundFileName = searchFileRecursively(dir.absoluteFilePath(filesAndDirs.at(i)), matchSize, matchHash);
153 if (!foundFileName.isEmpty())
156 return foundFileName;
159 void DocumentChecker::slotEditItem(QTreeWidgetItem *item, int)
161 KUrl url = KUrlRequesterDialog::getUrl(item->text(1), this, i18n("Enter new location for file"));
162 if (url.isEmpty()) return;
163 item->setText(1, url.path());
164 if (KIO::NetAccess::exists(url, KIO::NetAccess::SourceSide, 0)) {
165 item->setIcon(0, KIcon("dialog-ok"));
166 item->setData(0, statusRole, CLIPOK);
172 void DocumentChecker::accept()
174 QDomElement e, property;
175 QDomNodeList producers = m_doc.elementsByTagName("producer");
176 QDomNodeList infoproducers = m_doc.elementsByTagName("kdenlive_producer");
177 QDomNodeList properties;
179 QTreeWidgetItem *child = m_view.treeWidget->topLevelItem(ix);
181 if (child->data(0, statusRole).toInt() == CLIPOK) {
182 QString id = child->data(0, idRole).toString();
183 for (int i = 0; i < infoproducers.count(); i++) {
184 e = infoproducers.item(i).toElement();
185 if (e.attribute("id") == id) {
187 e.setAttribute("resource", child->text(1));
191 for (int i = 0; i < producers.count(); i++) {
192 e = producers.item(i).toElement();
193 if (e.attribute("id").section('_', 0, 0) == id) {
195 properties = e.childNodes();
196 for (int j = 0; j < properties.count(); ++j) {
197 property = properties.item(j).toElement();
198 if (property.attribute("name") == "resource") {
199 property.firstChild().setNodeValue(child->text(1));
206 } else if (child->data(0, statusRole).toInt() == CLIPPLACEHOLDER) {
207 QString id = child->data(0, idRole).toString();
208 for (int i = 0; i < infoproducers.count(); i++) {
209 e = infoproducers.item(i).toElement();
210 if (e.attribute("id") == id) {
212 e.setAttribute("placeholder", '1');
218 child = m_view.treeWidget->topLevelItem(ix);
223 void DocumentChecker::slotPlaceholders()
226 QTreeWidgetItem *child = m_view.treeWidget->topLevelItem(ix);
228 if (child->data(0, statusRole).toInt() == CLIPMISSING) {
229 child->setData(0, statusRole, CLIPPLACEHOLDER);
230 child->setIcon(0, KIcon("dialog-ok"));
233 child = m_view.treeWidget->topLevelItem(ix);
239 void DocumentChecker::checkStatus()
243 QTreeWidgetItem *child = m_view.treeWidget->topLevelItem(ix);
245 if (child->data(0, statusRole).toInt() == CLIPMISSING) {
250 child = m_view.treeWidget->topLevelItem(ix);
252 m_view.buttonBox->button(QDialogButtonBox::Ok)->setEnabled(status);
256 void DocumentChecker::slotDeleteSelected()
258 if (KMessageBox::warningContinueCancel(this, i18n("This will remove the selected clips from this project"), i18n("Remove clips")) == KMessageBox::Cancel) return;
260 QStringList deletedIds;
261 QTreeWidgetItem *child = m_view.treeWidget->topLevelItem(ix);
262 QDomNodeList playlists = m_doc.elementsByTagName("playlist");
265 if (child->isSelected()) {
266 QString id = child->data(0, idRole).toString();
267 deletedIds.append(id);
268 for (int j = 0; j < playlists.count(); j++)
269 deletedIds.append(id + '_' + QString::number(j));
272 child = m_view.treeWidget->topLevelItem(ix);
274 kDebug() << "// Clips to delete: " << deletedIds;
276 if (!deletedIds.isEmpty()) {
278 QDomNodeList producers = m_doc.elementsByTagName("producer");
279 QDomNodeList infoproducers = m_doc.elementsByTagName("kdenlive_producer");
281 QDomElement westley = m_doc.firstChildElement("westley");
282 QDomElement kdenlivedoc = westley.firstChildElement("kdenlivedoc");
284 for (int i = 0; i < infoproducers.count(); i++) {
285 e = infoproducers.item(i).toElement();
286 if (deletedIds.contains(e.attribute("id"))) {
288 kdenlivedoc.removeChild(e);
293 for (int i = 0; i < producers.count(); i++) {
294 e = producers.item(i).toElement();
295 if (deletedIds.contains(e.attribute("id"))) {
297 westley.removeChild(e);
302 for (int i = 0; i < playlists.count(); i++) {
303 QDomNodeList entries = playlists.at(i).toElement().elementsByTagName("entry");
304 for (int j = 0; j < playlists.count(); j++) {
305 e = entries.item(j).toElement();
306 if (deletedIds.contains(e.attribute("producer"))) {
307 // Replace clip with blank
308 e.setTagName("blank");
309 e.removeAttribute("producer");
310 int length = e.attribute("out").toInt() - e.attribute("in").toInt();
311 e.setAttribute("length", length);
319 #include "documentchecker.moc"