]> vaikene.ee Git - evaf/blob - src/apps/FileFinder/Engine/engine.cpp
Added the FileFinder application.
[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
240 void Internal::Worker::cancel()
241 {
242 mLock.lock();
243 mDoCancel = true;
244 mSomethingToDo.wakeAll();
245 mLock.unlock();
246 }
247
248 void Internal::Worker::stop()
249 {
250 mLock.lock();
251 mDoTerminate = true;
252 mDoCancel = true;
253 mSomethingToDo.wakeAll();
254 mLock.unlock();
255 wait();
256 }
257
258 void Internal::Worker::search(QString const & dir, bool recursive, Filter const & filter)
259 {
260 mLock.lock();
261 mDoCancel = true;
262 mNewDir = dir;
263 mNewRecursive = recursive;
264 mNewFilter = filter;
265 mDoSearch = true;
266 mSomethingToDo.wakeAll();
267 mLock.unlock();
268 }
269
270 bool Internal::Worker::busy() const
271 {
272 QMutexLocker l(&mLock);
273 return mBusy;
274 }
275
276 void Internal::Worker::run()
277 {
278 forever {
279 QMutexLocker lock(&mLock);
280
281 mSomethingToDo.wait(&mLock);
282
283 if (mDoTerminate)
284 break;
285
286 mDoCancel = false;
287
288 if (mDoSearch) {
289 mDoSearch = false;
290 mBusy = true;
291
292 // Copy search arguments
293 mDir.setPath(mNewDir);
294 mRecursive = mNewRecursive;
295 mRxIncludeNames.setPattern(mNewFilter.includeNames);
296 mRxExcludeNames.setPattern(mNewFilter.excludeNames);
297 mRxIncludeContent.setPattern(mNewFilter.includeContent);
298 mRxExcludeContent.setPattern(mNewFilter.excludeContent);
299
300 lock.unlock();
301
302 // Perform the actual search
303 recursiveSearch(mDir.path());
304
305 lock.relock();
306 mBusy = false;
307
308 emit finished(mDoCancel);
309 }
310 }
311 }
312
313 void Internal::Worker::recursiveSearch(QString const & path)
314 {
315 QString l = path;
316 if (!l.endsWith(QChar('/')))
317 l.append(QChar('/'));
318 QDir dir(l);
319
320 // Get the list of files in this directory
321 QStringList files = dir.entryList(QDir::Files | QDir::NoSymLinks);
322 foreach (QString const & file, files) {
323
324 // Check for the cancel flag
325 {
326 QMutexLocker l(&mLock);
327 if (mDoCancel)
328 break;
329 }
330
331 // Check for the file name to match the include filter and not the exclude filter
332 if (mRxIncludeNames.isValid() && !mRxIncludeNames.isEmpty()) {
333 if (!mRxIncludeNames.exactMatch(file))
334 continue;
335 }
336 if (mRxExcludeNames.isValid() && !mRxExcludeNames.isEmpty()) {
337 if (mRxExcludeNames.exactMatch(file))
338 continue;
339 }
340
341 // Check for the file content to match the include filter and not the exclude filter
342 if ((mRxIncludeContent.isValid() && !mRxIncludeContent.isEmpty()) ||
343 (mRxExcludeContent.isValid() && !mRxExcludeContent.isEmpty())) {
344
345 QFile f(l + file);
346 if (!f.open(QFile::ReadOnly))
347 continue; // Ignore silently if opening fails
348
349 int includeFilterMatched = 0;
350 if (!mRxIncludeContent.isValid() || mRxIncludeContent.isEmpty())
351 includeFilterMatched = 1;
352 int excludeFilterMatched = 0;
353 if (!mRxExcludeContent.isValid() || mRxExcludeContent.isEmpty())
354 excludeFilterMatched = -1;
355 QByteArray buf;
356 while (!f.atEnd() && (includeFilterMatched <= 0 || excludeFilterMatched <= 0)) {
357
358 /* We read ReadBufferSize bytes from the file and append to the buffer.
359 * We keep max 2 x ReadBufferSize bytes in the buffer and throw away the oldest
360 * ReadBufferSize bytes of data. Every block is checked twice, but we make sure that
361 * also strings that stretch from one block to another are checked.
362 */
363 QByteArray b = f.read(ReadBufferSize);
364 buf.append(b);
365 if (buf.size() > (2 * ReadBufferSize))
366 buf.remove(0, ReadBufferSize);
367 if (includeFilterMatched == 0 && mRxIncludeContent.indexIn(buf) >= 0)
368 ++includeFilterMatched;
369 if (excludeFilterMatched == 0 && mRxExcludeContent.indexIn(buf) >= 0)
370 ++excludeFilterMatched;
371
372 }
373
374 if (includeFilterMatched == 0 || excludeFilterMatched > 0)
375 continue;
376 }
377
378 // Found a file
379 emit found(mDir.relativeFilePath(l + file), mDir.path());
380 }
381
382 // Process sub-directories
383 if (mRecursive) {
384 QStringList dirs = dir.entryList(QDir::Dirs | QDir::NoDotAndDotDot | QDir::NoSymLinks);
385 foreach (QString const & directory, dirs) {
386
387 // Check for the cancel flag
388 {
389 QMutexLocker l(&mLock);
390 if (mDoCancel)
391 break;
392 }
393
394 recursiveSearch(l + directory);
395 }
396 }
397 }