1 /* prefixdb.cc
2 * This file belongs to Worker, a file manager for UN*X/X11.
3 * Copyright (C) 2010-2019 Ralf Hoffmann.
4 * You can contact me at: ralf@boomerangsworld.de
5 * or http://www.boomerangsworld.de/worker
6 *
7 * This program is free software; you can redistribute it and/or modify
8 * it under the terms of the GNU General Public License as published by
9 * the Free Software Foundation; either version 2 of the License, or
10 * (at your option) any later version.
11 *
12 * This program is distributed in the hope that it will be useful,
13 * but WITHOUT ANY WARRANTY; without even the implied warranty of
14 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
15 * GNU General Public License for more details.
16 *
17 * You should have received a copy of the GNU General Public License
18 * along with this program; if not, write to the Free Software
19 * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
20 */
21
22 #include "prefixdb.hh"
23 #include <algorithm>
24 #include <iostream>
25 #include <fstream>
26 #include "nwc_fsentry.hh"
27 #include "datei.h"
28 #include "configtokens.h"
29 #include "configheader.h"
30 #include "configparser.hh"
31 #include "aguix/util.h"
32 #include "filelock.hh"
33
PrefixDB(const std::string & filename)34 PrefixDB::PrefixDB( const std::string &filename ) : m_next_aging( 0 ),
35 m_filename( filename ),
36 m_lastmod( 0 ),
37 m_lastsize( 0 )
38
39 {
40 time_t now = time( NULL );
41
42 m_next_aging = now + 24 * 60 * 60;
43 }
44
~PrefixDB()45 PrefixDB::~PrefixDB()
46 {
47 }
48
getBestHit(const std::string & prefix,time_t & return_last_use)49 std::string PrefixDB::getBestHit( const std::string &prefix, time_t &return_last_use )
50 {
51 read();
52
53 std::vector< PrefixDBEntry >::const_iterator it1;
54
55 PrefixDBEntry pdb_test( prefix );
56
57 // m_db needs to be sorted
58
59 it1 = std::lower_bound( m_db.begin(),
60 m_db.end(),
61 pdb_test );
62
63 // now it1 is the first entry which is not lower than pdb_test
64
65 return_last_use = 0;
66 if ( it1 == m_db.end() ) return "";
67
68 std::string best_hit;
69 float best_hit_count = -1.0;
70 time_t best_last_use = 0;
71
72 // for exact hit consider only this entry
73 if ( it1->getPrefix() == prefix ) {
74 return it1->getBestHit( best_hit_count,
75 return_last_use );
76 }
77
78 // otherwise search all entries for which prefix is a prefix
79
80 for ( ;
81 it1 != m_db.end();
82 it1++ ) {
83 if ( it1->getPrefix().compare( 0, prefix.length(), prefix ) == 0 ) {
84 if ( best_hit_count < 0 ) {
85 best_hit = it1->getBestHit( best_hit_count, best_last_use );
86 } else {
87 float t1;
88 std::string s1;
89 time_t t2;
90
91 s1 = it1->getBestHit( t1, t2 );
92
93 if ( t1 > best_hit_count ) {
94 best_hit = s1;
95 best_hit_count = t1;
96 best_last_use = t2;
97 }
98 }
99 } else {
100 // since m_db is sorted we can stop here
101 break;
102 }
103 }
104
105 return_last_use = best_last_use;
106
107 return best_hit;
108 }
109
pushAccess(const std::string & prefix,const std::string & value,time_t last_use)110 void PrefixDB::pushAccess( const std::string &prefix,
111 const std::string &value,
112 time_t last_use )
113 {
114 read();
115
116 std::vector< PrefixDBEntry >::iterator it1;
117
118 PrefixDBEntry pdb_test( prefix );
119
120 // m_db needs to be sorted
121
122 it1 = std::lower_bound( m_db.begin(),
123 m_db.end(),
124 pdb_test );
125
126 // now it1 is the first entry which is not lower than pdb_test
127
128 if ( it1 == m_db.end() ||
129 it1->getPrefix() != prefix ) {
130 PrefixDBEntry pe( prefix );
131 pe.storeAccess( value, last_use );
132 m_db.push_back( pe );
133 std::sort( m_db.begin(), m_db.end() );
134 } else {
135 it1->storeAccess( value, last_use );
136 }
137
138 write();
139 }
140
read()141 void PrefixDB::read()
142 {
143 NWC::FSEntry fe( m_filename );
144
145 if ( ! fe.entryExists() ) return;
146
147 if ( fe.isLink() ) {
148 if ( fe.stat_dest_lastmod() == m_lastmod &&
149 fe.stat_dest_size() == m_lastsize ) return;
150 } else {
151 if ( fe.stat_lastmod() == m_lastmod &&
152 fe.stat_size() == m_lastsize ) return;
153 }
154
155 m_db.clear();
156
157 if ( fe.isLink() ) {
158 m_lastmod = fe.stat_dest_lastmod();
159 m_lastsize = fe.stat_dest_size();
160 } else {
161 m_lastmod = fe.stat_lastmod();
162 m_lastsize = fe.stat_size();
163 }
164
165 std::string lockname = m_filename;
166 lockname += ".lock";
167
168 FileLock fl( lockname );
169
170 if ( fl.lock_retry() ) {
171 #if 0
172 // this is a working version with plain key value pairs
173 // it uses no scanner but is much stricter
174 std::string myfilename = m_filename;
175 myfilename += "-plain_kv";
176 std::ifstream ifile( myfilename.c_str() );
177 std::string line;
178
179 if ( ifile.is_open() ) {
180 bool failed = true;
181 if ( std::getline( ifile, line ) ) {
182 if ( line.compare( 0, std::string( "next_aging=" ).length(), "next_aging=" ) == 0 ) {
183 m_next_aging = atoi( line.c_str() + std::string( "next_aging=" ).length() );
184 failed = false;
185 }
186 }
187
188 if ( ! failed ) {
189 while ( ! failed && std::getline( ifile, line ) ) {
190 if ( line == "begin_entry" ) {
191 PrefixDBEntry pe( "" );
192 failed = pe.read( ifile );
193 if ( ! failed ) {
194 m_db.push_back( pe );
195 }
196 }
197 }
198 }
199
200 if ( failed ) {
201 std::cout << "read failed" << std::endl;
202 }
203 }
204 #else
205 FILE *fp;
206 int found_error = 0;
207
208 fp = worker_fopen( m_filename.c_str(), "r" );
209 if ( fp != NULL ) {
210 yyrestart( fp );
211 readtoken();
212 for (;;) {
213 if ( worker_token == NEXTAGING_WCP ) {
214 readtoken();
215
216 if ( worker_token != '=' ) {
217 found_error = 1;
218 break;
219 }
220 readtoken();
221
222 if ( worker_token == STRING_WCP ) {
223 if ( ! AGUIXUtils::convertFromString( yylval.strptr,
224 m_next_aging ) ) {
225 fprintf( stderr, "Worker:unable to parse nextaging\n" );
226 m_next_aging = 0;
227 //TODO abort parsing?
228 }
229 } else {
230 found_error = 1;
231 break;
232 }
233 readtoken();
234
235 if ( worker_token != ';' ) {
236 found_error = 1;
237 break;
238 }
239 readtoken();
240 } else if ( worker_token == ENTRY_WCP ) {
241 readtoken();
242
243 if ( worker_token != LEFTBRACE_WCP ) {
244 found_error = 1;
245 break;
246 }
247 readtoken();
248
249 PrefixDBEntry pe( "" );
250 bool failed = pe.read( fp );
251 if ( ! failed ) {
252 m_db.push_back( pe );
253 } else {
254 found_error = 1;
255 break;
256 }
257
258 if ( worker_token != RIGHTBRACE_WCP ) {
259 found_error = 1;
260 break;
261 }
262 readtoken();
263 } else if ( worker_token != 0 ) {
264 // parse error
265 found_error = 1;
266 break;
267 } else break; // end
268 }
269 if ( found_error != 0 ) {
270 //TODO: Requester zeigen?
271 // Eigentlich kann der User eh nicht viel machen
272 // denn beim Beenden wird neue Datei geschrieben
273 fprintf( stderr, "Worker:error in prefixdb\n" );
274 }
275 worker_fclose( fp );
276 }
277 #endif
278
279 fl.unlock();
280 } else {
281 fprintf( stderr, "Worker:locking file %s failed\n", lockname.c_str() );
282 }
283
284 std::sort( m_db.begin(), m_db.end() );
285 }
286
write()287 void PrefixDB::write()
288 {
289 time_t now = time( NULL );
290
291 if ( now >= m_next_aging ) {
292 age();
293 m_next_aging = now + 24 * 60 * 60;
294 }
295
296 std::string lockname = m_filename;
297 lockname += ".lock";
298
299 FileLock fl( lockname );
300
301 if ( fl.lock_retry() ) {
302 #if 0
303 std::string myfilename = m_filename;
304 myfilename += "-plain_kv";
305 std::ofstream ofile( myfilename.c_str() );
306
307 ofile << "next_aging=" << m_next_aging << std::endl;
308
309 std::vector< PrefixDBEntry >::iterator it1;
310
311 for ( it1 = m_db.begin();
312 it1 != m_db.end();
313 it1++ ) {
314 ofile << "begin_entry" << std::endl;
315
316 it1->write( ofile );
317
318 ofile << "end_entry" << std::endl;
319 }
320 #else
321 Datei fh;
322
323 std::string temp_target_file = m_filename;
324 temp_target_file += ".new";
325
326 if ( fh.open( temp_target_file.c_str(), "w" ) == 0 ) {
327 fh.configPutPairString( "nextaging", AGUIXUtils::convertToString( m_next_aging ).c_str() );
328
329 std::vector< PrefixDBEntry >::iterator it1;
330
331 for ( it1 = m_db.begin();
332 it1 != m_db.end();
333 it1++ ) {
334 fh.configOpenSection( "entry" );
335
336 it1->write( fh );
337
338 fh.configCloseSection();
339 }
340 fh.close();
341
342 if ( fh.errors() ) {
343 worker_unlink( temp_target_file.c_str() );
344 } else {
345 worker_rename( temp_target_file.c_str(), m_filename.c_str() );
346 }
347 }
348 #endif
349
350 fl.unlock();
351 } else {
352 fprintf( stderr, "Worker:locking file %s failed\n", lockname.c_str() );
353 }
354 //TODO set lastsize and lastmod to avoid next read?
355 }
356
age(float factor)357 void PrefixDB::age( float factor )
358 {
359 std::vector< PrefixDBEntry >::iterator it1;
360
361 for ( it1 = m_db.begin();
362 it1 != m_db.end();
363 it1++ ) {
364 it1->age( factor );
365 }
366 }
367
getAllStrings()368 std::set< std::string > PrefixDB::getAllStrings()
369 {
370 std::set< std::string > strings;
371
372 read();
373
374 for ( auto &dbe : m_db ) {
375 for ( auto &s1 : dbe.getStrings() ) {
376 strings.insert( std::get<0>( s1 ) );
377 }
378 }
379
380 return strings;
381 }
382
getAllBestHits()383 std::map< std::string, std::tuple< std::string, float, time_t > > PrefixDB::getAllBestHits()
384 {
385 std::map< std::string, std::tuple< std::string, float, time_t > > res;
386
387 read();
388
389 for ( auto &dbe : m_db ) {
390 float f = 1.234;
391 time_t last_use = 0;
392 std::string p = dbe.getBestHit( f, last_use );
393
394 if ( ! p.empty() ) {
395 res[ dbe.getPrefix() ] = std::make_tuple( p, f, last_use );
396 }
397 }
398
399 return res;
400 }
401
removeEntry(const std::string & prefix)402 void PrefixDB::removeEntry( const std::string &prefix )
403 {
404 read();
405
406 auto it = std::find_if( m_db.begin(),
407 m_db.end(),
408 [prefix] ( const PrefixDBEntry &e ) {
409 return e.getPrefix() == prefix;
410 } );
411
412 if ( it == m_db.end() ) {
413 return;
414 }
415
416 m_db.erase( it );
417
418 write();
419 }
420
getLastSize() const421 loff_t PrefixDB::getLastSize() const
422 {
423 return m_lastsize;
424 }
425