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