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