xref: /glogg/src/data/logfiltereddata.cpp (revision 05467f52d72e282658c219cc889ed6bc08e5dc01)
1 /*
2  * Copyright (C) 2009, 2010, 2011, 2012, 2013, 2017 Nicolas Bonnefon and other contributors
3  *
4  * This file is part of glogg.
5  *
6  * glogg is free software: you can redistribute it and/or modify
7  * it under the terms of the GNU General Public License as published by
8  * the Free Software Foundation, either version 3 of the License, or
9  * (at your option) any later version.
10  *
11  * glogg is distributed in the hope that it will be useful,
12  * but WITHOUT ANY WARRANTY; without even the implied warranty of
13  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
14  * GNU General Public License for more details.
15  *
16  * You should have received a copy of the GNU General Public License
17  * along with glogg.  If not, see <http://www.gnu.org/licenses/>.
18  */
19 
20 // This file implements LogFilteredData
21 // It stores a pointer to the LogData that created it,
22 // so should always be destroyed before the LogData.
23 
24 #include "log.h"
25 
26 #include <QString>
27 #include <cassert>
28 #include <limits>
29 
30 #include "utils.h"
31 #include "logdata.h"
32 #include "marks.h"
33 #include "logfiltereddata.h"
34 
35 // Creates an empty set. It must be possible to display it without error.
36 // FIXME
37 LogFilteredData::LogFilteredData() : AbstractLogData(),
38     matching_lines_(),
39     currentRegExp_(),
40     visibility_(),
41     filteredItemsCache_(),
42     workerThread_( nullptr ),
43     marks_()
44 {
45     /* Prevent any more searching */
46     maxLength_ = 0;
47     maxLengthMarks_ = 0;
48     searchDone_ = true;
49     visibility_ = MarksAndMatches;
50 
51     filteredItemsCacheDirty_ = true;
52 }
53 
54 // Usual constructor: just copy the data, the search is started by runSearch()
55 LogFilteredData::LogFilteredData( const LogData* logData )
56     : AbstractLogData(),
57     matching_lines_( SearchResultArray() ),
58     currentRegExp_(),
59     visibility_(),
60     filteredItemsCache_(),
61     workerThread_( logData ),
62     marks_()
63 {
64     // Starts with an empty result list
65     maxLength_ = 0;
66     maxLengthMarks_ = 0;
67     nbLinesProcessed_ = 0;
68 
69     sourceLogData_ = logData;
70 
71     searchDone_ = false;
72 
73     visibility_ = MarksAndMatches;
74 
75     filteredItemsCacheDirty_ = true;
76 
77     // Forward the update signal
78     connect( &workerThread_, SIGNAL( searchProgressed( int, int ) ),
79             this, SLOT( handleSearchProgressed( int, int ) ) );
80 
81     // Starts the worker thread
82     workerThread_.start();
83 }
84 
85 LogFilteredData::~LogFilteredData()
86 {
87     // FIXME
88     // workerThread_.stop();
89 }
90 
91 //
92 // Public functions
93 //
94 
95 // Run the search and send newDataAvailable() signals.
96 void LogFilteredData::runSearch( const QRegExp& regExp )
97 {
98     LOG(logDEBUG) << "Entering runSearch";
99 
100     clearSearch();
101     currentRegExp_ = regExp;
102 
103     workerThread_.search( currentRegExp_ );
104 }
105 
106 void LogFilteredData::updateSearch()
107 {
108     LOG(logDEBUG) << "Entering updateSearch";
109 
110     workerThread_.updateSearch( currentRegExp_, nbLinesProcessed_ );
111 }
112 
113 void LogFilteredData::interruptSearch()
114 {
115     LOG(logDEBUG) << "Entering interruptSearch";
116 
117     workerThread_.interrupt();
118 }
119 
120 void LogFilteredData::clearSearch()
121 {
122     currentRegExp_ = QRegExp();
123     matching_lines_.clear();
124     maxLength_        = 0;
125     maxLengthMarks_   = 0;
126     nbLinesProcessed_ = 0;
127     filteredItemsCacheDirty_ = true;
128 }
129 
130 qint64 LogFilteredData::getMatchingLineNumber( int matchNum ) const
131 {
132     qint64 matchingLine = findLogDataLine( matchNum );
133 
134     return matchingLine;
135 }
136 
137 // Scan the list for the 'lineNumber' passed
138 bool LogFilteredData::isLineInMatchingList( qint64 lineNumber )
139 {
140     int index;                                    // Not used
141     return lookupLineNumber<SearchResultArray>(
142             matching_lines_, lineNumber, &index);
143 }
144 
145 
146 LineNumber LogFilteredData::getNbTotalLines() const
147 {
148     return sourceLogData_->getNbLine();
149 }
150 
151 LineNumber LogFilteredData::getNbMatches() const
152 {
153     return matching_lines_.size();
154 }
155 
156 LineNumber LogFilteredData::getNbMarks() const
157 {
158     return marks_.size();
159 }
160 
161 LogFilteredData::FilteredLineType
162     LogFilteredData::filteredLineTypeByIndex( int index ) const
163 {
164     // If we are only showing one type, the line is there because
165     // it is of this type.
166     if ( visibility_ == MatchesOnly )
167         return Match;
168     else if ( visibility_ == MarksOnly )
169         return Mark;
170     else {
171         // If it is MarksAndMatches, we have to look.
172         // Regenerate the cache if needed
173         if ( filteredItemsCacheDirty_ )
174             regenerateFilteredItemsCache();
175 
176         return filteredItemsCache_[ index ].type();
177     }
178 }
179 
180 // Delegation to our Marks object
181 
182 void LogFilteredData::addMark( qint64 line, QChar mark )
183 {
184     if ( ( line >= 0 ) && ( line < sourceLogData_->getNbLine() ) ) {
185         marks_.addMark( line, mark );
186         maxLengthMarks_ = qMax( maxLengthMarks_,
187                 sourceLogData_->getLineLength( line ) );
188         filteredItemsCacheDirty_ = true;
189     }
190     else
191         LOG(logERROR) << "LogFilteredData::addMark\
192  trying to create a mark outside of the file.";
193 }
194 
195 qint64 LogFilteredData::getMark( QChar mark ) const
196 {
197     return marks_.getMark( mark );
198 }
199 
200 bool LogFilteredData::isLineMarked( qint64 line ) const
201 {
202     return marks_.isLineMarked( line );
203 }
204 
205 qint64 LogFilteredData::getMarkAfter( qint64 line ) const
206 {
207     qint64 marked_line = -1;
208 
209     for ( auto i = marks_.begin(); i != marks_.end(); ++i ) {
210         if ( i->lineNumber() > line ) {
211             marked_line = i->lineNumber();
212             break;
213         }
214     }
215 
216     return marked_line;
217 }
218 
219 qint64 LogFilteredData::getMarkBefore( qint64 line ) const
220 {
221     qint64 marked_line = -1;
222 
223     for ( auto i = marks_.begin(); i != marks_.end(); ++i ) {
224         if ( i->lineNumber() >= line ) {
225             break;
226         }
227         marked_line = i->lineNumber();
228     }
229 
230     return marked_line;
231 }
232 
233 void LogFilteredData::deleteMark( QChar mark )
234 {
235     marks_.deleteMark( mark );
236     filteredItemsCacheDirty_ = true;
237 
238     // FIXME: maxLengthMarks_
239 }
240 
241 void LogFilteredData::deleteMark( qint64 line )
242 {
243     marks_.deleteMark( line );
244     filteredItemsCacheDirty_ = true;
245 
246     // Now update the max length if needed
247     if ( sourceLogData_->getLineLength( line ) >= maxLengthMarks_ ) {
248         LOG(logDEBUG) << "deleteMark recalculating longest mark";
249         maxLengthMarks_ = 0;
250         for ( Marks::const_iterator i = marks_.begin();
251                 i != marks_.end(); ++i ) {
252             LOG(logDEBUG) << "line " << i->lineNumber();
253             maxLengthMarks_ = qMax( maxLengthMarks_,
254                     sourceLogData_->getLineLength( i->lineNumber() ) );
255         }
256     }
257 }
258 
259 void LogFilteredData::clearMarks()
260 {
261     marks_.clear();
262     filteredItemsCacheDirty_ = true;
263     maxLengthMarks_ = 0;
264 }
265 
266 void LogFilteredData::setVisibility( Visibility visi )
267 {
268     visibility_ = visi;
269 }
270 
271 //
272 // Slots
273 //
274 void LogFilteredData::handleSearchProgressed( int nbMatches, int progress )
275 {
276     LOG(logDEBUG) << "LogFilteredData::handleSearchProgressed matches="
277         << nbMatches << " progress=" << progress;
278 
279     // searchDone_ = true;
280     workerThread_.getSearchResult( &maxLength_, &matching_lines_, &nbLinesProcessed_ );
281     filteredItemsCacheDirty_ = true;
282 
283     emit searchProgressed( nbMatches, progress );
284 }
285 
286 LineNumber LogFilteredData::findLogDataLine( LineNumber lineNum ) const
287 {
288     LineNumber line = std::numeric_limits<LineNumber>::max();
289     if ( visibility_ == MatchesOnly ) {
290         if ( lineNum < matching_lines_.size() ) {
291             line = matching_lines_[lineNum].lineNumber();
292         }
293         else {
294             LOG(logERROR) << "Index too big in LogFilteredData: " << lineNum;
295         }
296     }
297     else if ( visibility_ == MarksOnly ) {
298         if ( lineNum < marks_.size() )
299             line = marks_.getLineMarkedByIndex( lineNum );
300         else
301             LOG(logERROR) << "Index too big in LogFilteredData: " << lineNum;
302     }
303     else {
304         // Regenerate the cache if needed
305         if ( filteredItemsCacheDirty_ )
306             regenerateFilteredItemsCache();
307 
308         if ( lineNum < filteredItemsCache_.size() )
309             line = filteredItemsCache_[ lineNum ].lineNumber();
310         else
311             LOG(logERROR) << "Index too big in LogFilteredData: " << lineNum;
312     }
313 
314     return line;
315 }
316 
317 // Implementation of the virtual function.
318 QString LogFilteredData::doGetLineString( qint64 lineNum ) const
319 {
320     qint64 line = findLogDataLine( lineNum );
321 
322     QString string = sourceLogData_->getLineString( line );
323     return string;
324 }
325 
326 // Implementation of the virtual function.
327 QString LogFilteredData::doGetExpandedLineString( qint64 lineNum ) const
328 {
329     qint64 line = findLogDataLine( lineNum );
330 
331     QString string = sourceLogData_->getExpandedLineString( line );
332     return string;
333 }
334 
335 // Implementation of the virtual function.
336 QStringList LogFilteredData::doGetLines( qint64 first_line, int number ) const
337 {
338     QStringList list;
339 
340     for ( int i = first_line; i < first_line + number; i++ ) {
341         list.append( doGetLineString( i ) );
342     }
343 
344     return list;
345 }
346 
347 // Implementation of the virtual function.
348 QStringList LogFilteredData::doGetExpandedLines( qint64 first_line, int number ) const
349 {
350     QStringList list;
351 
352     for ( int i = first_line; i < first_line + number; i++ ) {
353         list.append( doGetExpandedLineString( i ) );
354     }
355 
356     return list;
357 }
358 
359 // Implementation of the virtual function.
360 qint64 LogFilteredData::doGetNbLine() const
361 {
362     qint64 nbLines;
363 
364     if ( visibility_ == MatchesOnly )
365         nbLines = matching_lines_.size();
366     else if ( visibility_ == MarksOnly )
367         nbLines = marks_.size();
368     else {
369         // Regenerate the cache if needed (hopefully most of the time
370         // it won't be necessarily)
371         if ( filteredItemsCacheDirty_ )
372             regenerateFilteredItemsCache();
373         nbLines = filteredItemsCache_.size();
374     }
375 
376     return nbLines;
377 }
378 
379 // Implementation of the virtual function.
380 int LogFilteredData::doGetMaxLength() const
381 {
382     int max_length;
383 
384     if ( visibility_ == MatchesOnly )
385         max_length = maxLength_;
386     else if ( visibility_ == MarksOnly )
387         max_length = maxLengthMarks_;
388     else
389         max_length = qMax( maxLength_, maxLengthMarks_ );
390 
391     return max_length;
392 }
393 
394 // Implementation of the virtual function.
395 int LogFilteredData::doGetLineLength( qint64 lineNum ) const
396 {
397     qint64 line = findLogDataLine( lineNum );
398     return sourceLogData_->getExpandedLineString( line ).length();
399 }
400 
401 void LogFilteredData::doSetDisplayEncoding( const char* encoding )
402 {
403     LOG(logDEBUG) << "AbstractLogData::setDisplayEncoding: " << encoding;
404 }
405 
406 void LogFilteredData::doSetMultibyteEncodingOffsets( int before_cr, int after_cr )
407 {
408 }
409 
410 // TODO: We might be a bit smarter and not regenerate the whole thing when
411 // e.g. stuff is added at the end of the search.
412 void LogFilteredData::regenerateFilteredItemsCache() const
413 {
414     LOG(logDEBUG) << "regenerateFilteredItemsCache";
415 
416     filteredItemsCache_.clear();
417     filteredItemsCache_.reserve( matching_lines_.size() + marks_.size() );
418     // (it's an overestimate but probably not by much so it's fine)
419 
420     auto i = matching_lines_.cbegin();
421     Marks::const_iterator j = marks_.begin();
422 
423     while ( ( i != matching_lines_.cend() ) || ( j != marks_.end() ) ) {
424         qint64 next_mark =
425             ( j != marks_.end() ) ? j->lineNumber() : std::numeric_limits<qint64>::max();
426         qint64 next_match =
427             ( i != matching_lines_.cend() ) ? i->lineNumber() : std::numeric_limits<qint64>::max();
428         // We choose a Mark over a Match if a line is both, just an arbitrary choice really.
429         if ( next_mark <= next_match ) {
430             // LOG(logDEBUG) << "Add mark at " << next_mark;
431             filteredItemsCache_.push_back( FilteredItem( next_mark, Mark ) );
432             if ( j != marks_.end() )
433                 ++j;
434             if ( ( next_mark == next_match ) && ( i != matching_lines_.cend() ) )
435                 ++i;  // Case when it's both match and mark.
436         }
437         else {
438             // LOG(logDEBUG) << "Add match at " << next_match;
439             filteredItemsCache_.push_back( FilteredItem( next_match, Match ) );
440             if ( i != matching_lines_.cend() )
441                 ++i;
442         }
443     }
444 
445     filteredItemsCacheDirty_ = false;
446 
447     LOG(logDEBUG) << "finished regenerateFilteredItemsCache";
448 }
449