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