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