]> git.sesse.net Git - kdenlive/blob - src/documentchecker.cpp
improve document checker
[kdenlive] / src / documentchecker.cpp
1 /***************************************************************************
2  *   Copyright (C) 2008 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 "documentchecker.h"
22 #include "kthumb.h"
23 #include "definitions.h"
24
25 #include <KDebug>
26 #include <KGlobalSettings>
27 #include <KFileItem>
28 #include <KIO/NetAccess>
29 #include <KFileDialog>
30 #include <KApplication>
31 #include <KUrlRequesterDialog>
32
33 #include <QTreeWidgetItem>
34 #include <QFile>
35 #include <QHeaderView>
36 #include <QIcon>
37 #include <QPixmap>
38 #include <QTimer>
39 #include <QCryptographicHash>
40
41 const int hashRole = Qt::UserRole;
42 const int sizeRole = Qt::UserRole + 1;
43 const int idRole = Qt::UserRole + 2;
44 const int statusRole = Qt::UserRole + 3;
45
46 const int CLIPMISSING = 0;
47 const int CLIPOK = 1;
48 const int CLIPPLACEHOLDER = 2;
49
50 DocumentChecker::DocumentChecker(QDomDocument doc, QWidget * parent) :
51         QDialog(parent), m_doc(doc)
52 {
53     setFont(KGlobalSettings::toolBarFont());
54     m_view.setupUi(this);
55
56     QDomNodeList producers = m_doc.elementsByTagName("producer");
57     QDomNodeList infoproducers = m_doc.elementsByTagName("kdenlive_producer");
58
59     int clipType;
60     QDomElement e;
61     QString id;
62     QString resource;
63     QList <QDomElement> missingClips;
64     for (int i = 0; i < infoproducers.count(); i++) {
65         e = infoproducers.item(i).toElement();
66         clipType = e.attribute("type").toInt();
67         if (clipType == TEXT) continue;
68         id = e.attribute("id");
69         resource = e.attribute("resource");
70         if (clipType == SLIDESHOW) resource = KUrl(resource).directory();
71         if (!KIO::NetAccess::exists(KUrl(resource), KIO::NetAccess::SourceSide, 0)) {
72             // Missing clip found
73             missingClips.append(e);
74         }
75     }
76
77     if (missingClips.isEmpty()) QTimer::singleShot(0, this, SLOT(accept()));
78     m_view.buttonBox->button(QDialogButtonBox::Ok)->setEnabled(false);
79     for (int i = 0; i < missingClips.count(); i++) {
80         e = missingClips.at(i).toElement();
81         QString clipType;
82         switch (e.attribute("type").toInt()) {
83         case AV:
84             clipType = i18n("Video clip");
85             break;
86         case VIDEO:
87             clipType = i18n("Mute video clip");
88             break;
89         case AUDIO:
90             clipType = i18n("Audio clip");
91             break;
92         case PLAYLIST:
93             clipType = i18n("Playlist clip");
94             break;
95         case IMAGE:
96             clipType = i18n("Image clip");
97             break;
98         case SLIDESHOW:
99             clipType = i18n("Slideshow clip");
100             break;
101         default:
102             clipType = i18n("Video clip");
103         }
104         QTreeWidgetItem *item = new QTreeWidgetItem(m_view.treeWidget, QStringList() << clipType << e.attribute("resource"));
105         item->setIcon(0, KIcon("dialog-close"));
106         item->setData(0, hashRole, e.attribute("file_hash"));
107         item->setData(0, sizeRole, e.attribute("file_size"));
108         item->setData(0, idRole, e.attribute("id"));
109         item->setData(0, statusRole, CLIPMISSING);
110     }
111     connect(m_view.recursiveSearch, SIGNAL(pressed()), this, SLOT(slotSearchClips()));
112     connect(m_view.usePlaceholders, SIGNAL(pressed()), this, SLOT(slotPlaceholders()));
113     connect(m_view.treeWidget, SIGNAL(itemDoubleClicked(QTreeWidgetItem *, int)), this, SLOT(slotEditItem(QTreeWidgetItem *, int)));
114     //adjustSize();
115 }
116
117 DocumentChecker::~DocumentChecker() {}
118
119 void DocumentChecker::slotSearchClips()
120 {
121     QString newpath = KFileDialog::getExistingDirectory(KUrl("kfiledialog:///clipfolder"), kapp->activeWindow(), i18n("Clips folder"));
122     if (newpath.isEmpty()) return;
123     int ix = 0;
124     QTreeWidgetItem *child = m_view.treeWidget->topLevelItem(ix);
125     while (child && child->data(0, statusRole).toInt() == CLIPMISSING) {
126         QString clipPath = searchFileRecursively(QDir(newpath), child->data(0, sizeRole).toString(), child->data(0, hashRole).toString());
127         if (!clipPath.isEmpty()) {
128             child->setText(1, clipPath);
129             child->setIcon(0, KIcon("dialog-ok"));
130             child->setData(0, statusRole, CLIPOK);
131         }
132         ix++;
133         child = m_view.treeWidget->topLevelItem(ix);
134     }
135     checkStatus();
136 }
137
138 QString DocumentChecker::searchFileRecursively(const QDir &dir, const QString &matchSize, const QString &matchHash) const
139 {
140     QString foundFileName;
141     QByteArray fileData;
142     QByteArray fileHash;
143     QStringList filesAndDirs = dir.entryList(QDir::Files | QDir::Readable);
144     for (int i = 0; i < filesAndDirs.size() && foundFileName.isEmpty(); i++) {
145         QFile file(dir.absoluteFilePath(filesAndDirs.at(i)));
146         if (file.open(QIODevice::ReadOnly)) {
147             if (QString::number(file.size()) == matchSize) {
148                 /*
149                 * 1 MB = 1 second per 450 files (or faster)
150                 * 10 MB = 9 seconds per 450 files (or faster)
151                 */
152                 if (file.size() > 1000000*2) {
153                     fileData = file.read(1000000);
154                     if (file.seek(file.size() - 1000000))
155                         fileData.append(file.readAll());
156                 } else
157                     fileData = file.readAll();
158                 file.close();
159                 fileHash = QCryptographicHash::hash(fileData, QCryptographicHash::Md5);
160                 if (QString(fileHash.toHex()) == matchHash)
161                     return file.fileName();
162             }
163         }
164         kDebug() << filesAndDirs.at(i) << file.size() << fileHash.toHex();
165     }
166     filesAndDirs = dir.entryList(QDir::Dirs | QDir::Readable | QDir::Executable | QDir::NoDotAndDotDot);
167     for (int i = 0; i < filesAndDirs.size() && foundFileName.isEmpty(); i++) {
168         foundFileName = searchFileRecursively(dir.absoluteFilePath(filesAndDirs.at(i)), matchSize, matchHash);
169         if (!foundFileName.isEmpty())
170             break;
171     }
172     return foundFileName;
173 }
174
175 void DocumentChecker::slotEditItem(QTreeWidgetItem *item, int)
176 {
177     KUrl url = KUrlRequesterDialog::getUrl(item->text(1), this, i18n("Enter new location for file"));
178     if (url.isEmpty()) return;
179     item->setText(1, url.path());
180     if (KIO::NetAccess::exists(url, KIO::NetAccess::SourceSide, 0)) {
181         item->setIcon(0, KIcon("dialog-ok"));
182         item->setData(0, statusRole, CLIPOK);
183         checkStatus();
184     }
185 }
186
187 // virtual
188 void DocumentChecker::accept()
189 {
190     QDomElement e;
191     QDomNodeList producers = m_doc.elementsByTagName("producer");
192     QDomNodeList infoproducers = m_doc.elementsByTagName("kdenlive_producer");
193     int ix = 0;
194     QTreeWidgetItem *child = m_view.treeWidget->topLevelItem(ix);
195     while (child) {
196         if (child->data(0, statusRole).toInt() == CLIPOK) {
197             QString id = child->data(0, idRole).toString();
198             for (int i = 0; i < infoproducers.count(); i++) {
199                 e = infoproducers.item(i).toElement();
200                 if (e.attribute("id") == id) {
201                     // Fix clip
202                     e.setAttribute("resource", child->text(1));
203                     break;
204                 }
205             }
206             for (int i = 0; i < producers.count(); i++) {
207                 e = producers.item(i).toElement();
208                 if (e.attribute("id") == id) {
209                     // Fix clip
210                     e.setAttribute("resource", child->text(1));
211                     break;
212                 }
213             }
214         } else if (child->data(0, statusRole).toInt() == CLIPPLACEHOLDER) {
215             QString id = child->data(0, idRole).toString();
216             for (int i = 0; i < infoproducers.count(); i++) {
217                 e = infoproducers.item(i).toElement();
218                 if (e.attribute("id") == id) {
219                     // Fix clip
220                     e.setAttribute("placeholder", '1');
221                     break;
222                 }
223             }
224         }
225         ix++;
226         child = m_view.treeWidget->topLevelItem(ix);
227     }
228     QDialog::accept();
229 }
230
231 void DocumentChecker::slotPlaceholders()
232 {
233     int ix = 0;
234     QTreeWidgetItem *child = m_view.treeWidget->topLevelItem(ix);
235     while (child) {
236         if (child->data(0, statusRole).toInt() == CLIPMISSING) {
237             child->setData(0, statusRole, CLIPPLACEHOLDER);
238             child->setIcon(0, KIcon("dialog-ok"));
239         }
240         ix++;
241         child = m_view.treeWidget->topLevelItem(ix);
242     }
243     checkStatus();
244 }
245
246
247 void DocumentChecker::checkStatus()
248 {
249     bool status = true;
250     int ix = 0;
251     QTreeWidgetItem *child = m_view.treeWidget->topLevelItem(ix);
252     while (child) {
253         if (child->data(0, statusRole).toInt() == CLIPMISSING) {
254             status = false;
255             break;
256         }
257         ix++;
258         child = m_view.treeWidget->topLevelItem(ix);
259     }
260     m_view.buttonBox->button(QDialogButtonBox::Ok)->setEnabled(status);
261 }
262
263 #include "documentchecker.moc"
264
265