]> vaikene.ee Git - evaf/blob - src/apps/FileFinder/Engine/engine.cpp
Warning fixes and copyright update.
[evaf] / src / apps / FileFinder / Engine / engine.cpp
1 /**
2 * @file FileFinder/Engine/engine.h
3 * @brief Module for the FileFinder application that searches for files
4 * @author Enar Vaikene
5 *
6 * Copyright (c) 2011-2019 Enar Vaikene
7 *
8 * This file is part of the eVaf C++ cross-platform application development framework.
9 *
10 * This file can be used under the terms of the GNU General Public License
11 * version 3.0 as published by the Free Software Foundation and appearing in
12 * the file LICENSE included in the packaging of this file. Please review the
13 * the following information to ensure the GNU General Public License version
14 * 3.0 requirements will be met: http://www.gnu.org/copyleft/gpl.html.
15 *
16 * Alternatively, this file may be used in accordance with the Commercial License
17 * Agreement provided with the Software.
18 */
19
20
21 #include "engine.h"
22
23 #include <Common/iLogger>
24 #include <Common/iRegistry>
25
26 #include <QtCore>
27
28 VER_EXPORT_VERSION_INFO()
29
30 using namespace eVaf;
31 using namespace eVaf::FileFinder;
32 using namespace eVaf::FileFinder::Engine;
33
34 //-------------------------------------------------------------------
35
36 Module::Module()
37 : Plugins::iPlugin()
38 , mReady(false)
39 {
40 setObjectName(QString("%1.Module").arg(VER_MODULE_NAME_STR));
41
42 mEngine = new Internal::Engine;
43
44 EVAF_INFO("%s created", qPrintable(objectName()));
45 }
46
47 Module::~Module()
48 {
49 delete mEngine;
50
51 EVAF_INFO("%s destroyed", qPrintable(objectName()));
52 }
53
54 bool Module::init(QString const & args)
55 {
56 Q_UNUSED(args)
57
58 if (!mEngine->init())
59 return false;
60
61 mReady = true;
62
63 EVAF_INFO("%s initialized", qPrintable(objectName()));
64
65 return true;
66 }
67
68 void Module::done()
69 {
70 mReady = false;
71
72 mEngine->done();
73
74 EVAF_INFO("%s finalized", qPrintable(objectName()));
75 }
76
77
78 //-------------------------------------------------------------------
79
80 Internal::Engine::Engine()
81 : iFileFinder()
82 , mWorker(nullptr)
83 {
84 setObjectName(QString("%1.Engine").arg(VER_MODULE_NAME_STR));
85
86 EVAF_INFO("%s created", qPrintable(objectName()));
87 }
88
89 Internal::Engine::~Engine()
90 {
91 EVAF_INFO("%s destroyed", qPrintable(objectName()));
92 }
93
94 bool Internal::Engine::init()
95 {
96 // Register the iFileFinder interface
97 Common::iRegistry::instance()->registerInterface("iFileFinder", this);
98
99 mWorker = new Internal::Worker(this);
100 connect(mWorker, SIGNAL(found(QString, QString)), this, SIGNAL(found(QString,QString)));
101 connect(mWorker, SIGNAL(finished(bool)), this, SIGNAL(finished(bool)));
102 mWorker->start();
103
104 EVAF_INFO("%s initialized", qPrintable(objectName()));
105
106 return true;
107 }
108
109 void Internal::Engine::done()
110 {
111 if (mWorker != nullptr) {
112 mWorker->stop();
113 delete mWorker;
114 mWorker = nullptr;
115 }
116
117 EVAF_INFO("%s finalized", qPrintable(objectName()));
118 }
119
120 void Internal::Engine::search(QString const & dir, bool recursive, Filter const & filter)
121 {
122 if (mWorker != nullptr)
123 mWorker->search(dir, recursive, filter);
124 }
125
126 bool Internal::Engine::busy() const
127 {
128 if (mWorker != nullptr)
129 return mWorker->busy();
130 return false;
131 }
132
133 void Internal::Engine::cancel()
134 {
135 if (mWorker != nullptr)
136 mWorker->cancel();
137 }
138
139
140 //-------------------------------------------------------------------
141
142 Internal::RegExpChain::~RegExpChain()
143 {
144 clear();
145 }
146
147 void Internal::RegExpChain::clear()
148 {
149 foreach (QRegExp * pattern, mPatterns) {
150 delete pattern;
151 }
152 mPatterns.clear();
153 mValid = false;
154 }
155
156 void Internal::RegExpChain::setPattern(QString const & pattern)
157 {
158 clear();
159
160 QStringList patterns = split(pattern);
161
162 foreach (QString s, patterns) {
163 if (s.isEmpty())
164 continue;
165 QRegExp * rx = new QRegExp(s, Qt::CaseSensitive, QRegExp::WildcardUnix);
166 if (!rx->isValid()) {
167 delete rx;
168 continue;
169 }
170 mPatterns.append(rx);
171 }
172
173 mValid = !mPatterns.isEmpty();
174 }
175
176 bool Internal::RegExpChain::exactMatch(const QString& str) const
177 {
178 if (mPatterns.isEmpty())
179 return false;
180
181 foreach (QRegExp * pattern, mPatterns) {
182 if (pattern->exactMatch(str))
183 return true;
184 }
185 return false;
186 }
187
188 QStringList Internal::RegExpChain::split(const QString & pattern)
189 {
190 QStringList rval;
191 int offset = 0;
192 int sz = pattern.size();
193 QString s;
194 bool e = false;
195
196 while (offset < sz) {
197 QChar const ch = pattern.at(offset++);
198 if (e) {
199 e = false;
200 if (ch == '*' || ch == '?' || ch == '[' || ch == ']')
201 s.append('\\');
202 s.append(ch);
203 }
204 else {
205 if (ch == '\\')
206 e = true;
207 else if (ch == ',') {
208 rval.append(s);
209 s.clear();
210 }
211 else
212 s.append(ch);
213 }
214 }
215 if (!s.isEmpty())
216 rval.append(s);
217
218 return rval;
219 }
220
221
222 //-------------------------------------------------------------------
223
224 int const Internal::Worker::ReadBufferSize = 4096;
225
226 Internal::Worker::Worker(QObject * parent)
227 : QThread(parent)
228 , mNewRecursive(false)
229 , mRecursive(false)
230 , mDoSearch(false)
231 , mDoTerminate(false)
232 , mDoCancel(false)
233 , mBusy(false)
234 {
235 setObjectName(QString("%1.Worker").arg(VER_MODULE_NAME_STR));
236
237 EVAF_INFO("%s created", qPrintable(objectName()));
238 }
239
240 Internal::Worker::~Worker()
241 {
242 EVAF_INFO("%s destroyed", qPrintable(objectName()));
243 }
244
245 void Internal::Worker::cancel()
246 {
247 mLock.lock();
248 mDoCancel = true;
249 mSomethingToDo.wakeAll();
250 mLock.unlock();
251 }
252
253 void Internal::Worker::stop()
254 {
255 mLock.lock();
256 mDoTerminate = true;
257 mDoCancel = true;
258 mSomethingToDo.wakeAll();
259 mLock.unlock();
260 wait();
261 }
262
263 void Internal::Worker::search(QString const & dir, bool recursive, Filter const & filter)
264 {
265 mLock.lock();
266 mDoCancel = true;
267 mNewDir = dir;
268 mNewRecursive = recursive;
269 mNewFilter = filter;
270 mDoSearch = true;
271 mSomethingToDo.wakeAll();
272 mLock.unlock();
273 }
274
275 bool Internal::Worker::busy() const
276 {
277 QMutexLocker l(&mLock);
278 return mBusy;
279 }
280
281 void Internal::Worker::run()
282 {
283 forever {
284 QMutexLocker lock(&mLock);
285
286 if (mDoTerminate)
287 break;
288
289 mSomethingToDo.wait(&mLock);
290
291 if (mDoTerminate)
292 break;
293
294 mDoCancel = false;
295
296 if (mDoSearch) {
297 mDoSearch = false;
298 mBusy = true;
299
300 // Copy search arguments
301 mDir.setPath(mNewDir);
302 mRecursive = mNewRecursive;
303 mRxIncludeNames.setPattern(mNewFilter.includeNames);
304 mRxExcludeNames.setPattern(mNewFilter.excludeNames);
305 mRxIncludeContent.setPattern(mNewFilter.includeContent);
306 mRxExcludeContent.setPattern(mNewFilter.excludeContent);
307
308 lock.unlock();
309
310 // Perform the actual search
311 recursiveSearch(mDir.path());
312
313 lock.relock();
314 mBusy = false;
315 bool c = mDoCancel;
316 lock.unlock();
317
318 emit finished(c);
319 }
320 }
321 }
322
323 void Internal::Worker::recursiveSearch(QString const & path)
324 {
325 QString l = path;
326 if (!l.endsWith(QChar('/')))
327 l.append(QChar('/'));
328 QDir dir(l);
329
330 // Get the list of files in this directory
331 QStringList const files = dir.entryList(QDir::Files | QDir::NoSymLinks);
332 foreach (QString const & file, files) {
333
334 // Check for the cancel flag
335 {
336 QMutexLocker l(&mLock);
337 if (mDoCancel)
338 return;
339 }
340
341 // Check for the file name to match the include filter and not the exclude filter
342 if (mRxIncludeNames.isValid() && !mRxIncludeNames.isEmpty()) {
343 if (!mRxIncludeNames.exactMatch(file))
344 continue;
345 }
346 if (mRxExcludeNames.isValid() && !mRxExcludeNames.isEmpty()) {
347 if (mRxExcludeNames.exactMatch(file))
348 continue;
349 }
350
351 // Check for the file content to match the include filter and not the exclude filter
352 if ((mRxIncludeContent.isValid() && !mRxIncludeContent.isEmpty()) ||
353 (mRxExcludeContent.isValid() && !mRxExcludeContent.isEmpty())) {
354
355 QFile f(l + file);
356 if (!f.open(QFile::ReadOnly))
357 continue; // Ignore silently if opening fails
358
359 int includeFilterMatched = 0;
360 if (!mRxIncludeContent.isValid() || mRxIncludeContent.isEmpty())
361 includeFilterMatched = 1;
362 int excludeFilterMatched = 0;
363 if (!mRxExcludeContent.isValid() || mRxExcludeContent.isEmpty())
364 excludeFilterMatched = -1;
365 QByteArray buf;
366 while (!f.atEnd() && (includeFilterMatched <= 0 || excludeFilterMatched <= 0)) {
367
368 // Check for the cancel flag
369 {
370 QMutexLocker l(&mLock);
371 if (mDoCancel)
372 return;
373 }
374
375 /* We read ReadBufferSize bytes from the file and append to the buffer.
376 * We keep max 2 x ReadBufferSize bytes in the buffer and throw away the oldest
377 * ReadBufferSize bytes of data. Every block is checked twice, but we make sure that
378 * also strings that stretch from one block to another are checked.
379 */
380 QByteArray const b = f.read(ReadBufferSize);
381 buf.append(b);
382 if (buf.size() > (2 * ReadBufferSize))
383 buf.remove(0, ReadBufferSize);
384 if (includeFilterMatched == 0 && mRxIncludeContent.indexIn(buf) >= 0)
385 ++includeFilterMatched;
386 if (excludeFilterMatched == 0 && mRxExcludeContent.indexIn(buf) >= 0)
387 ++excludeFilterMatched;
388
389 }
390
391 if (includeFilterMatched == 0 || excludeFilterMatched > 0)
392 continue;
393 }
394
395 // Found a file
396 emit found(mDir.relativeFilePath(l + file), mDir.path());
397 }
398
399 // Process sub-directories
400 if (mRecursive) {
401 QStringList const dirs = dir.entryList(QDir::Dirs | QDir::NoDotAndDotDot | QDir::NoSymLinks);
402 foreach (QString const & directory, dirs) {
403
404 // Check for the cancel flag
405 {
406 QMutexLocker l(&mLock);
407 if (mDoCancel)
408 return;
409 }
410
411 if (mRxExcludeNames.isValid() && !mRxExcludeNames.isEmpty() && mRxExcludeNames.exactMatch(directory))
412 continue;
413
414 recursiveSearch(l + directory);
415 }
416 }
417 }