xref: /glogg/src/data/logfiltereddata.cpp (revision c9a9366412fcda118aaadfe3742db8727a163a0f)
1 /*
2  * Copyright (C) 2009, 2010, 2011, 2012, 2013 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 void LogFilteredData::deleteMark( QChar mark )
206 {
207     marks_.deleteMark( mark );
208     filteredItemsCacheDirty_ = true;
209 
210     // FIXME: maxLengthMarks_
211 }
212 
213 void LogFilteredData::deleteMark( qint64 line )
214 {
215     marks_.deleteMark( line );
216     filteredItemsCacheDirty_ = true;
217 
218     // Now update the max length if needed
219     if ( sourceLogData_->getLineLength( line ) >= maxLengthMarks_ ) {
220         LOG(logDEBUG) << "deleteMark recalculating longest mark";
221         maxLengthMarks_ = 0;
222         for ( Marks::const_iterator i = marks_.begin();
223                 i != marks_.end(); ++i ) {
224             LOG(logDEBUG) << "line " << i->lineNumber();
225             maxLengthMarks_ = qMax( maxLengthMarks_,
226                     sourceLogData_->getLineLength( i->lineNumber() ) );
227         }
228     }
229 }
230 
231 void LogFilteredData::clearMarks()
232 {
233     marks_.clear();
234     filteredItemsCacheDirty_ = true;
235     maxLengthMarks_ = 0;
236 }
237 
238 void LogFilteredData::setVisibility( Visibility visi )
239 {
240     visibility_ = visi;
241 }
242 
243 //
244 // Slots
245 //
246 void LogFilteredData::handleSearchProgressed( int nbMatches, int progress )
247 {
248     LOG(logDEBUG) << "LogFilteredData::handleSearchProgressed matches="
249         << nbMatches << " progress=" << progress;
250 
251     // searchDone_ = true;
252     workerThread_.getSearchResult( &maxLength_, &matching_lines_, &nbLinesProcessed_ );
253     filteredItemsCacheDirty_ = true;
254 
255     emit searchProgressed( nbMatches, progress );
256 }
257 
258 LineNumber LogFilteredData::findLogDataLine( LineNumber lineNum ) const
259 {
260     LineNumber line = std::numeric_limits<LineNumber>::max();
261     if ( visibility_ == MatchesOnly ) {
262         if ( lineNum < matching_lines_.size() ) {
263             line = matching_lines_[lineNum].lineNumber();
264         }
265         else {
266             LOG(logERROR) << "Index too big in LogFilteredData: " << lineNum;
267         }
268     }
269     else if ( visibility_ == MarksOnly ) {
270         if ( lineNum < marks_.size() )
271             line = marks_.getLineMarkedByIndex( lineNum );
272         else
273             LOG(logERROR) << "Index too big in LogFilteredData: " << lineNum;
274     }
275     else {
276         // Regenerate the cache if needed
277         if ( filteredItemsCacheDirty_ )
278             regenerateFilteredItemsCache();
279 
280         if ( lineNum < filteredItemsCache_.size() )
281             line = filteredItemsCache_[ lineNum ].lineNumber();
282         else
283             LOG(logERROR) << "Index too big in LogFilteredData: " << lineNum;
284     }
285 
286     return line;
287 }
288 
289 // Implementation of the virtual function.
290 QString LogFilteredData::doGetLineString( qint64 lineNum ) const
291 {
292     qint64 line = findLogDataLine( lineNum );
293 
294     QString string = sourceLogData_->getLineString( line );
295     return string;
296 }
297 
298 // Implementation of the virtual function.
299 QString LogFilteredData::doGetExpandedLineString( qint64 lineNum ) const
300 {
301     qint64 line = findLogDataLine( lineNum );
302 
303     QString string = sourceLogData_->getExpandedLineString( line );
304     return string;
305 }
306 
307 // Implementation of the virtual function.
308 QStringList LogFilteredData::doGetLines( qint64 first_line, int number ) const
309 {
310     QStringList list;
311 
312     for ( int i = first_line; i < first_line + number; i++ ) {
313         list.append( doGetLineString( i ) );
314     }
315 
316     return list;
317 }
318 
319 // Implementation of the virtual function.
320 QStringList LogFilteredData::doGetExpandedLines( qint64 first_line, int number ) const
321 {
322     QStringList list;
323 
324     for ( int i = first_line; i < first_line + number; i++ ) {
325         list.append( doGetExpandedLineString( i ) );
326     }
327 
328     return list;
329 }
330 
331 // Implementation of the virtual function.
332 qint64 LogFilteredData::doGetNbLine() const
333 {
334     qint64 nbLines;
335 
336     if ( visibility_ == MatchesOnly )
337         nbLines = matching_lines_.size();
338     else if ( visibility_ == MarksOnly )
339         nbLines = marks_.size();
340     else {
341         // Regenerate the cache if needed (hopefully most of the time
342         // it won't be necessarily)
343         if ( filteredItemsCacheDirty_ )
344             regenerateFilteredItemsCache();
345         nbLines = filteredItemsCache_.size();
346     }
347 
348     return nbLines;
349 }
350 
351 // Implementation of the virtual function.
352 int LogFilteredData::doGetMaxLength() const
353 {
354     int max_length;
355 
356     if ( visibility_ == MatchesOnly )
357         max_length = maxLength_;
358     else if ( visibility_ == MarksOnly )
359         max_length = maxLengthMarks_;
360     else
361         max_length = qMax( maxLength_, maxLengthMarks_ );
362 
363     return max_length;
364 }
365 
366 // Implementation of the virtual function.
367 int LogFilteredData::doGetLineLength( qint64 lineNum ) const
368 {
369     qint64 line = findLogDataLine( lineNum );
370     return sourceLogData_->getExpandedLineString( line ).length();
371 }
372 
373 void LogFilteredData::doSetDisplayEncoding( const char* encoding )
374 {
375     LOG(logDEBUG) << "AbstractLogData::setDisplayEncoding: " << encoding;
376 }
377 
378 // TODO: We might be a bit smarter and not regenerate the whole thing when
379 // e.g. stuff is added at the end of the search.
380 void LogFilteredData::regenerateFilteredItemsCache() const
381 {
382     LOG(logDEBUG) << "regenerateFilteredItemsCache";
383 
384     filteredItemsCache_.clear();
385     filteredItemsCache_.reserve( matching_lines_.size() + marks_.size() );
386     // (it's an overestimate but probably not by much so it's fine)
387 
388     auto i = matching_lines_.cbegin();
389     Marks::const_iterator j = marks_.begin();
390 
391     while ( ( i != matching_lines_.cend() ) || ( j != marks_.end() ) ) {
392         qint64 next_mark =
393             ( j != marks_.end() ) ? j->lineNumber() : std::numeric_limits<qint64>::max();
394         qint64 next_match =
395             ( i != matching_lines_.cend() ) ? i->lineNumber() : std::numeric_limits<qint64>::max();
396         // We choose a Mark over a Match if a line is both, just an arbitrary choice really.
397         if ( next_mark <= next_match ) {
398             // LOG(logDEBUG) << "Add mark at " << next_mark;
399             filteredItemsCache_.push_back( FilteredItem( next_mark, Mark ) );
400             if ( j != marks_.end() )
401                 ++j;
402             if ( ( next_mark == next_match ) && ( i != matching_lines_.cend() ) )
403                 ++i;  // Case when it's both match and mark.
404         }
405         else {
406             // LOG(logDEBUG) << "Add match at " << next_match;
407             filteredItemsCache_.push_back( FilteredItem( next_match, Match ) );
408             if ( i != matching_lines_.cend() )
409                 ++i;
410         }
411     }
412 
413     filteredItemsCacheDirty_ = false;
414 
415     LOG(logDEBUG) << "finished regenerateFilteredItemsCache";
416 }
417