xref: /glogg/src/data/logfiltereddata.cpp (revision 3ff6c9412a34ab93ae45b207ae002f2a78470619)
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 QRegularExpression& 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_ = QRegularExpression();
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 int LogFilteredData::getLineIndexNumber( quint64 lineNumber ) const
146 {
147     int lineIndex = findFilteredLine( lineNumber );
148     return lineIndex;
149 }
150 
151 LineNumber LogFilteredData::getNbTotalLines() const
152 {
153     return sourceLogData_->getNbLine();
154 }
155 
156 LineNumber LogFilteredData::getNbMatches() const
157 {
158     return matching_lines_.size();
159 }
160 
161 LineNumber LogFilteredData::getNbMarks() const
162 {
163     return marks_.size();
164 }
165 
166 LogFilteredData::FilteredLineType
167     LogFilteredData::filteredLineTypeByIndex( int index ) const
168 {
169     // If we are only showing one type, the line is there because
170     // it is of this type.
171     if ( visibility_ == MatchesOnly )
172         return Match;
173     else if ( visibility_ == MarksOnly )
174         return Mark;
175     else {
176         // If it is MarksAndMatches, we have to look.
177         // Regenerate the cache if needed
178         if ( filteredItemsCacheDirty_ )
179             regenerateFilteredItemsCache();
180 
181         return filteredItemsCache_[ index ].type();
182     }
183 }
184 
185 // Delegation to our Marks object
186 
187 void LogFilteredData::addMark( qint64 line, QChar mark )
188 {
189     if ( ( line >= 0 ) && ( line < sourceLogData_->getNbLine() ) ) {
190         marks_.addMark( line, mark );
191         maxLengthMarks_ = qMax( maxLengthMarks_,
192                 sourceLogData_->getLineLength( line ) );
193         filteredItemsCacheDirty_ = true;
194     }
195     else
196         LOG(logERROR) << "LogFilteredData::addMark\
197  trying to create a mark outside of the file.";
198 }
199 
200 qint64 LogFilteredData::getMark( QChar mark ) const
201 {
202     return marks_.getMark( mark );
203 }
204 
205 bool LogFilteredData::isLineMarked( qint64 line ) const
206 {
207     return marks_.isLineMarked( line );
208 }
209 
210 qint64 LogFilteredData::getMarkAfter( qint64 line ) const
211 {
212     qint64 marked_line = -1;
213 
214     for ( auto i = marks_.begin(); i != marks_.end(); ++i ) {
215         if ( i->lineNumber() > line ) {
216             marked_line = i->lineNumber();
217             break;
218         }
219     }
220 
221     return marked_line;
222 }
223 
224 qint64 LogFilteredData::getMarkBefore( qint64 line ) const
225 {
226     qint64 marked_line = -1;
227 
228     for ( auto i = marks_.begin(); i != marks_.end(); ++i ) {
229         if ( i->lineNumber() >= line ) {
230             break;
231         }
232         marked_line = i->lineNumber();
233     }
234 
235     return marked_line;
236 }
237 
238 void LogFilteredData::deleteMark( QChar mark )
239 {
240     marks_.deleteMark( mark );
241     filteredItemsCacheDirty_ = true;
242 
243     // FIXME: maxLengthMarks_
244 }
245 
246 void LogFilteredData::deleteMark( qint64 line )
247 {
248     marks_.deleteMark( line );
249     filteredItemsCacheDirty_ = true;
250 
251     // Now update the max length if needed
252     if ( sourceLogData_->getLineLength( line ) >= maxLengthMarks_ ) {
253         LOG(logDEBUG) << "deleteMark recalculating longest mark";
254         maxLengthMarks_ = 0;
255         for ( Marks::const_iterator i = marks_.begin();
256                 i != marks_.end(); ++i ) {
257             LOG(logDEBUG) << "line " << i->lineNumber();
258             maxLengthMarks_ = qMax( maxLengthMarks_,
259                     sourceLogData_->getLineLength( i->lineNumber() ) );
260         }
261     }
262 }
263 
264 void LogFilteredData::clearMarks()
265 {
266     marks_.clear();
267     filteredItemsCacheDirty_ = true;
268     maxLengthMarks_ = 0;
269 }
270 
271 void LogFilteredData::setVisibility( Visibility visi )
272 {
273     visibility_ = visi;
274 }
275 
276 //
277 // Slots
278 //
279 void LogFilteredData::handleSearchProgressed( int nbMatches, int progress )
280 {
281     LOG(logDEBUG) << "LogFilteredData::handleSearchProgressed matches="
282         << nbMatches << " progress=" << progress;
283 
284     // searchDone_ = true;
285     workerThread_.getSearchResult( &maxLength_, &matching_lines_, &nbLinesProcessed_ );
286     filteredItemsCacheDirty_ = true;
287 
288     emit searchProgressed( nbMatches, progress );
289 }
290 
291 LineNumber LogFilteredData::findLogDataLine( LineNumber lineNum ) const
292 {
293     LineNumber line = std::numeric_limits<LineNumber>::max();
294     if ( visibility_ == MatchesOnly ) {
295         if ( lineNum < matching_lines_.size() ) {
296             line = matching_lines_[lineNum].lineNumber();
297         }
298         else {
299             LOG(logERROR) << "Index too big in LogFilteredData: " << lineNum;
300         }
301     }
302     else if ( visibility_ == MarksOnly ) {
303         if ( lineNum < marks_.size() )
304             line = marks_.getLineMarkedByIndex( lineNum );
305         else
306             LOG(logERROR) << "Index too big in LogFilteredData: " << lineNum;
307     }
308     else {
309         // Regenerate the cache if needed
310         if ( filteredItemsCacheDirty_ )
311             regenerateFilteredItemsCache();
312 
313         if ( lineNum < filteredItemsCache_.size() )
314             line = filteredItemsCache_[ lineNum ].lineNumber();
315         else
316             LOG(logERROR) << "Index too big in LogFilteredData: " << lineNum;
317     }
318 
319     return line;
320 }
321 
322 LineNumber LogFilteredData::findFilteredLine( LineNumber lineNum ) const
323 {
324     LineNumber lineIndex = std::numeric_limits<LineNumber>::max();
325 
326     if ( visibility_ == MatchesOnly ) {
327         lineIndex = lookupLineNumber( matching_lines_.begin(),
328                                       matching_lines_.end(),
329                                       lineNum );
330     }
331     else if ( visibility_ == MarksOnly ) {
332         lineIndex = lookupLineNumber( marks_.begin(),
333                                       marks_.end(),
334                                       lineNum );
335     }
336     else {
337       // Regenerate the cache if needed
338         if ( filteredItemsCacheDirty_ ) {
339             regenerateFilteredItemsCache();
340         }
341 
342         lineIndex = lookupLineNumber( filteredItemsCache_.begin(),
343                                       filteredItemsCache_.end(),
344                                       lineNum );
345     }
346 
347     return lineIndex;
348 }
349 
350 // Implementation of the virtual function.
351 QString LogFilteredData::doGetLineString( qint64 lineNum ) const
352 {
353     qint64 line = findLogDataLine( lineNum );
354 
355     QString string = sourceLogData_->getLineString( line );
356     return string;
357 }
358 
359 // Implementation of the virtual function.
360 QString LogFilteredData::doGetExpandedLineString( qint64 lineNum ) const
361 {
362     qint64 line = findLogDataLine( lineNum );
363 
364     QString string = sourceLogData_->getExpandedLineString( line );
365     return string;
366 }
367 
368 // Implementation of the virtual function.
369 QStringList LogFilteredData::doGetLines( qint64 first_line, int number ) const
370 {
371     QStringList list;
372 
373     for ( int i = first_line; i < first_line + number; i++ ) {
374         list.append( doGetLineString( i ) );
375     }
376 
377     return list;
378 }
379 
380 // Implementation of the virtual function.
381 QStringList LogFilteredData::doGetExpandedLines( qint64 first_line, int number ) const
382 {
383     QStringList list;
384 
385     for ( int i = first_line; i < first_line + number; i++ ) {
386         list.append( doGetExpandedLineString( i ) );
387     }
388 
389     return list;
390 }
391 
392 // Implementation of the virtual function.
393 qint64 LogFilteredData::doGetNbLine() const
394 {
395     qint64 nbLines;
396 
397     if ( visibility_ == MatchesOnly )
398         nbLines = matching_lines_.size();
399     else if ( visibility_ == MarksOnly )
400         nbLines = marks_.size();
401     else {
402         // Regenerate the cache if needed (hopefully most of the time
403         // it won't be necessarily)
404         if ( filteredItemsCacheDirty_ )
405             regenerateFilteredItemsCache();
406         nbLines = filteredItemsCache_.size();
407     }
408 
409     return nbLines;
410 }
411 
412 // Implementation of the virtual function.
413 int LogFilteredData::doGetMaxLength() const
414 {
415     int max_length;
416 
417     if ( visibility_ == MatchesOnly )
418         max_length = maxLength_;
419     else if ( visibility_ == MarksOnly )
420         max_length = maxLengthMarks_;
421     else
422         max_length = qMax( maxLength_, maxLengthMarks_ );
423 
424     return max_length;
425 }
426 
427 // Implementation of the virtual function.
428 int LogFilteredData::doGetLineLength( qint64 lineNum ) const
429 {
430     qint64 line = findLogDataLine( lineNum );
431     return sourceLogData_->getExpandedLineString( line ).length();
432 }
433 
434 void LogFilteredData::doSetDisplayEncoding( Encoding encoding )
435 {
436     LOG(logDEBUG) << "AbstractLogData::setDisplayEncoding: " << static_cast<int>( encoding );
437 }
438 
439 void LogFilteredData::doSetMultibyteEncodingOffsets( int, int )
440 {
441 }
442 
443 // TODO: We might be a bit smarter and not regenerate the whole thing when
444 // e.g. stuff is added at the end of the search.
445 void LogFilteredData::regenerateFilteredItemsCache() const
446 {
447     LOG(logDEBUG) << "regenerateFilteredItemsCache";
448 
449     filteredItemsCache_.clear();
450     filteredItemsCache_.reserve( matching_lines_.size() + marks_.size() );
451     // (it's an overestimate but probably not by much so it's fine)
452 
453     auto i = matching_lines_.cbegin();
454     Marks::const_iterator j = marks_.begin();
455 
456     while ( ( i != matching_lines_.cend() ) || ( j != marks_.end() ) ) {
457         qint64 next_mark =
458             ( j != marks_.end() ) ? j->lineNumber() : std::numeric_limits<qint64>::max();
459         qint64 next_match =
460             ( i != matching_lines_.cend() ) ? i->lineNumber() : std::numeric_limits<qint64>::max();
461         // We choose a Mark over a Match if a line is both, just an arbitrary choice really.
462         if ( next_mark <= next_match ) {
463             // LOG(logDEBUG) << "Add mark at " << next_mark;
464             filteredItemsCache_.push_back( FilteredItem( next_mark, Mark ) );
465             if ( j != marks_.end() )
466                 ++j;
467             if ( ( next_mark == next_match ) && ( i != matching_lines_.cend() ) )
468                 ++i;  // Case when it's both match and mark.
469         }
470         else {
471             // LOG(logDEBUG) << "Add match at " << next_match;
472             filteredItemsCache_.push_back( FilteredItem( next_match, Match ) );
473             if ( i != matching_lines_.cend() )
474                 ++i;
475         }
476     }
477 
478     filteredItemsCacheDirty_ = false;
479 
480     LOG(logDEBUG) << "finished regenerateFilteredItemsCache";
481 }
482