gpopstore.cpp
Go to the documentation of this file.
1 //
2 // Copyright (C) 2001-2013 Graeme Walker <graeme_walker@users.sourceforge.net>
3 //
4 // This program is free software: you can redistribute it and/or modify
5 // it under the terms of the GNU General Public License as published by
6 // the Free Software Foundation, either version 3 of the License, or
7 // (at your option) any later version.
8 //
9 // This program is distributed in the hope that it will be useful,
10 // but WITHOUT ANY WARRANTY; without even the implied warranty of
11 // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 // GNU General Public License for more details.
13 //
14 // You should have received a copy of the GNU General Public License
15 // along with this program. If not, see <http://www.gnu.org/licenses/>.
16 // ===
17 //
18 // gpopstore.cpp
19 //
20 
21 #include "gdef.h"
22 #include "gpop.h"
23 #include "gpopstore.h"
24 #include "gstr.h"
25 #include "gfile.h"
26 #include "gdirectory.h"
27 #include "gmemory.h"
28 #include "gtest.h"
29 #include "groot.h"
30 #include "gassert.h"
31 #include <sstream>
32 #include <fstream>
33 
34 namespace GPop
35 {
36  struct FileReader ;
37  struct DirectoryReader ;
38  struct FileDeleter ;
39 }
40 
46 {
48 } ;
49 
55 {
57 } ;
58 
63 struct GPop::FileDeleter : private G::Root
64 {
65 } ;
66 
67 // ==
68 
69 GPop::Store::Store( G::Path path , bool by_name , bool allow_delete ) :
70  m_path(path) ,
71  m_by_name(by_name) ,
72  m_allow_delete(allow_delete)
73 {
74  checkPath( path , by_name , allow_delete ) ;
75 }
76 
78 {
79  return m_path ;
80 }
81 
83 {
84  return m_allow_delete ;
85 }
86 
87 bool GPop::Store::byName() const
88 {
89  return m_by_name ;
90 }
91 
92 void GPop::Store::checkPath( G::Path dir_path , bool by_name , bool allow_delete )
93 {
94  if( by_name )
95  {
96  if( !valid(dir_path,false) )
97  throw InvalidDirectory() ;
98 
99  G::DirectoryList iter ;
100  {
101  DirectoryReader claim_reader ;
102  iter.readAll( dir_path ) ;
103  }
104 
105  int n = 0 ;
106  while( iter.more() )
107  {
108  if( iter.isDir() )
109  {
110  n++ ;
111  if( !valid(iter.filePath(),allow_delete) )
112  {
113  ; // no-op -- warning only
114  }
115  }
116  }
117  if( n == 0 )
118  {
119  G_WARNING( "GPop::Store: no sub-directories for pop-by-name found in \"" << dir_path << "\": "
120  << "create one sub-directory for each authorised pop account" ) ;
121  }
122  }
123  else if( !valid(dir_path,allow_delete) )
124  {
125  throw InvalidDirectory() ;
126  }
127 }
128 
129 bool GPop::Store::valid( G::Path dir_path , bool allow_delete )
130 {
131  G::Directory dir_test( dir_path ) ;
132  bool ok = false ;
133  if( allow_delete )
134  {
135  std::string tmp = G::Directory::tmp() ;
136  FileDeleter claim_deleter ;
137  ok = dir_test.valid() && dir_test.writeable(tmp) ;
138  }
139  else
140  {
141  FileReader claim_reader ;
142  ok = dir_test.valid() ;
143  }
144  if( !ok )
145  {
146  const char * op = allow_delete ? "writing" : "reading" ;
147  G_WARNING( "GPop::Store: directory not valid for " << op << ": \"" << dir_path << "\"" ) ;
148  }
149  return ok ;
150 }
151 
152 // ===
153 
154 GPop::StoreLock::File::File( const G::Path & content_path ) :
155  name(content_path.basename()) ,
156  size(toSize(G::File::sizeString(content_path.str())))
157 {
158 }
159 
160 GPop::StoreLock::File::File( const std::string & content_name , const std::string & size_string ) :
161  name(content_name) ,
162  size(toSize(size_string))
163 {
164 }
165 
166 bool GPop::StoreLock::File::operator<( const File & rhs ) const
167 {
168  return name < rhs.name ;
169 }
170 
171 GPop::StoreLock::Size GPop::StoreLock::File::toSize( const std::string & s )
172 {
173  return G::Str::toULong( s , true ) ;
174 }
175 
176 // ===
177 
179  m_store(&store)
180 {
181 }
182 
183 void GPop::StoreLock::lock( const std::string & user )
184 {
185  G_ASSERT( ! locked() ) ;
186  G_ASSERT( ! user.empty() ) ;
187  G_ASSERT( m_store != NULL ) ;
188 
189  m_user = user ;
190  m_dir = m_store->dir() ;
191  if( m_store->byName() )
192  m_dir.pathAppend( user ) ;
193 
194  // build a read-only list of files (inc. file sizes)
195  {
196  DirectoryReader claim_reader ;
197  G::DirectoryList iter ;
198  iter.readType( m_dir , ".envelope" ) ;
199  while( iter.more() )
200  {
201  File file( contentPath(iter.fileName().str()) ) ;
202  m_initial.insert( file ) ;
203  }
204  }
205 
206  if( G::Test::enabled("large-pop-list") )
207  {
208  // create a larger list
209  size_t limit = m_initial.size() * 1000U ;
210  for( size_t i = 0U ; i < limit ; i++ )
211  {
212  std::ostringstream ss ;
213  ss << "dummy." << i << ".content" ;
214  m_initial.insert( File(ss.str()) ) ;
215  }
216  }
217 
218  // take a mutable copy
219  m_current = m_initial ;
220 
221  G_ASSERT( locked() ) ;
222 }
223 
225 {
226  return m_store != NULL && ! m_user.empty() ;
227 }
228 
230 {
231 }
232 
234 {
235  G_ASSERT( locked() ) ;
236  return m_current.size() ;
237 }
238 
240 {
241  G_ASSERT( locked() ) ;
242  Size total = 0 ;
243  for( Set::const_iterator p = m_current.begin() ; p != m_current.end() ; ++p )
244  total += (*p).size ;
245  return total ;
246 }
247 
248 bool GPop::StoreLock::valid( int id ) const
249 {
250  G_ASSERT( locked() ) ;
251  return id >= 1 && id <= static_cast<int>(m_initial.size()) ;
252 }
253 
254 GPop::StoreLock::Set::iterator GPop::StoreLock::find( int id )
255 {
256  G_ASSERT( valid(id) ) ;
257  Set::iterator initial_p = m_initial.begin() ;
258  for( int i = 1 ; i < id && initial_p != m_initial.end() ; i++ , ++initial_p ) ;
259  return initial_p ;
260 }
261 
262 GPop::StoreLock::Set::const_iterator GPop::StoreLock::find( int id ) const
263 {
264  G_ASSERT( valid(id) ) ;
265  Set::const_iterator initial_p = m_initial.begin() ;
266  for( int i = 1 ; i < id && initial_p != m_initial.end() ; i++ , ++initial_p ) ;
267  return initial_p ;
268 }
269 
270 GPop::StoreLock::Set::iterator GPop::StoreLock::find( const std::string & name )
271 {
272  Set::iterator current_p = m_current.begin() ;
273  for( ; current_p != m_current.end() ; ++current_p )
274  {
275  if( (*current_p).name == name )
276  break ;
277  }
278  return current_p ;
279 }
280 
282 {
283  G_ASSERT( locked() ) ;
284  return (*find(id)).size ;
285 }
286 
288 {
289  G_ASSERT( locked() ) ;
290  List list ;
291  int i = 1 ;
292  for( Set::const_iterator p = m_current.begin() ; p != m_current.end() ; ++p , i++ )
293  {
294  if( id == -1 || id == i )
295  list.push_back( Entry(i,(*p).size,(*p).name) ) ;
296  }
297  return list ;
298 }
299 
300 std::auto_ptr<std::istream> GPop::StoreLock::get( int id ) const
301 {
302  G_ASSERT( locked() ) ;
303  G_ASSERT( valid(id) ) ;
304 
305  G_DEBUG( "GPop::StoreLock::get: " << id << ": " << path(id) ) ;
306 
307  std::auto_ptr<std::ifstream> file ;
308  {
309  FileReader claim_reader ;
310  file <<= new std::ifstream( path(id).str().c_str() , std::ios_base::binary | std::ios_base::in ) ;
311  }
312 
313  if( ! file->good() )
314  throw CannotRead( path(id).str() ) ;
315 
316  return std::auto_ptr<std::istream>( file.release() ) ;
317 }
318 
320 {
321  G_ASSERT( locked() ) ;
322  G_ASSERT( valid(id) ) ;
323 
324  Set::iterator initial_p = find( id ) ;
325  Set::iterator current_p = find( (*initial_p).name ) ;
326  if( current_p != m_current.end() )
327  {
328  m_deleted.insert( *initial_p ) ;
329  m_current.erase( current_p ) ;
330  }
331 }
332 
334 {
335  G_ASSERT( locked() ) ;
336  if( m_store )
337  {
338  Store * store = m_store ;
339  m_store = NULL ;
340  doCommit( *store ) ;
341  }
342  m_store = NULL ;
343 }
344 
345 void GPop::StoreLock::doCommit( Store & store ) const
346 {
347  bool all_ok = true ;
348  for( Set::const_iterator p = m_deleted.begin() ; p != m_deleted.end() ; ++p )
349  {
350  if( store.allowDelete() )
351  {
352  deleteFile( envelopePath(*p) , all_ok ) ;
353  if( unlinked(store,*p) ) // race condition could leave content files undeleted
354  deleteFile( contentPath(*p) , all_ok ) ;
355  }
356  else
357  {
358  G_DEBUG( "StoreLock::doCommit: not deleting \"" << (*p).name << "\"" ) ;
359  }
360  }
361  if( ! all_ok )
362  throw CannotDelete() ;
363 }
364 
365 void GPop::StoreLock::deleteFile( const G::Path & path , bool & all_ok ) const
366 {
367  bool ok = false ;
368  {
369  FileDeleter claim_deleter ;
370  ok = G::File::remove( path , G::File::NoThrow() ) ;
371  }
372  all_ok = ok && all_ok ;
373  if( ! ok )
374  G_ERROR( "StoreLock::remove: failed to delete " << path ) ;
375 }
376 
377 std::string GPop::StoreLock::uidl( int id ) const
378 {
379  G_ASSERT( valid(id) ) ;
380  Set::const_iterator p = find(id) ;
381  return (*p).name ;
382 }
383 
384 G::Path GPop::StoreLock::path( int id ) const
385 {
386  G_ASSERT( valid(id) ) ;
387  Set::const_iterator p = find(id) ;
388  const File & file = (*p) ;
389  return contentPath( file ) ;
390 }
391 
392 G::Path GPop::StoreLock::path( const std::string & filename , bool fallback ) const
393 {
394  // expected path
395  G::Path path_1 = m_dir ;
396  path_1.pathAppend( filename ) ;
397 
398  // or fallback to the parent directory
399  G::Path path_2 = m_dir ; path_2.pathAppend("..") ;
400  path_2.pathAppend( filename ) ;
401 
402  return ( fallback && !G::File::exists(path_1,G::File::NoThrow()) ) ? path_2 : path_1 ;
403 }
404 
405 std::string GPop::StoreLock::envelopeName( const std::string & content_name ) const
406 {
407  std::string filename = content_name ;
408  G::Str::replace( filename , "content" , "envelope" ) ;
409  return filename ;
410 }
411 
412 std::string GPop::StoreLock::contentName( const std::string & envelope_name ) const
413 {
414  std::string filename = envelope_name ;
415  G::Str::replace( filename , "envelope" , "content" ) ;
416  return filename ;
417 }
418 
419 G::Path GPop::StoreLock::contentPath( const std::string & envelope_name ) const
420 {
421  const bool try_parent_directory = true ;
422  return path( contentName(envelope_name) , try_parent_directory ) ;
423 }
424 
425 G::Path GPop::StoreLock::contentPath( const File & file ) const
426 {
427  const bool try_parent_directory = true ;
428  return path( file.name , try_parent_directory ) ;
429 }
430 
431 G::Path GPop::StoreLock::envelopePath( const File & file ) const
432 {
433  const bool try_parent_directory = false ;
434  return path( envelopeName(file.name) , try_parent_directory ) ;
435 }
436 
438 {
439  G_ASSERT( locked() ) ;
440  m_deleted.clear() ;
441  m_current = m_initial ;
442 }
443 
444 bool GPop::StoreLock::unlinked( Store & store , const File & file ) const
445 {
446  if( !store.byName() )
447  {
448  G_DEBUG( "StoreLock::unlinked: unlinked since not pop-by-name: " << file.name ) ;
449  return true ;
450  }
451 
452  G::Path normal_content_path = m_dir ; normal_content_path.pathAppend( file.name ) ;
453  if( G::File::exists(normal_content_path,G::File::NoThrow()) )
454  {
455  G_DEBUG( "StoreLock::unlinked: unlinked since in its own directory: " << normal_content_path ) ;
456  return true ;
457  }
458 
459  // look for corresponding envelopes in all child directories
460  bool found = false ;
461  {
462  G::DirectoryList iter ;
463  {
464  DirectoryReader claim_reader ;
465  iter.readAll( store.dir() ) ;
466  }
467  while( iter.more() )
468  {
469  if( ! iter.isDir() ) continue ;
470  G_DEBUG( "Store::unlinked: checking sub-directory: " << iter.fileName() ) ;
471  G::Path envelope_path = iter.filePath() ; envelope_path.pathAppend(envelopeName(file.name)) ;
472  if( G::File::exists(envelope_path,G::File::NoThrow()) )
473  {
474  G_DEBUG( "StoreLock::unlinked: still in use: envelope exists: " << envelope_path ) ;
475  found = true ;
476  break ;
477  }
478  }
479  }
480 
481  if( ! found )
482  {
483  G_DEBUG( "StoreLock::unlinked: unlinked since no envelope found in any sub-directory" ) ;
484  return true ;
485  }
486 
487  return false ;
488 }
489 
std::string str() const
Returns the path string.
Definition: gpath.cpp:135
A trivial class which is used like G::Root by GPop::Store for reading files.
Definition: gpopstore.cpp:45
G::Path dir() const
Returns the spool directory path.
Definition: gpopstore.cpp:77
void rollback()
Rolls back remove()als but retains the lock.
Definition: gpopstore.cpp:437
bool more()
Returns true if more and advances by one.
Definition: gdirectory.cpp:110
bool locked() const
Returns true if locked.
Definition: gpopstore.cpp:224
A trivial specialisation of G::Root used by GPop::Store for deleting files.
Definition: gpopstore.cpp:63
StoreLockEntry::Size Size
Definition: gpopstore.h:102
G::Path filePath() const
Returns the current path.
Definition: gdirectory.cpp:131
A class which acquires the process's special privileges on construction and releases them on destruct...
Definition: groot.h:49
void readAll(const Path &dir)
An initialiser that is to be used after default construction.
Definition: gdirectory.cpp:82
Store(G::Path spool_dir, bool by_name, bool allow_delete)
Constructor.
Definition: gpopstore.cpp:69
Size messageCount() const
Returns the store's message count.
Definition: gpopstore.cpp:233
Represents a file in the GPop::Store.
Definition: gpopstore.h:80
bool allowDelete() const
Returns true if files can be deleted.
Definition: gpopstore.cpp:82
A message store.
Definition: gpopstore.h:46
void readType(const Path &dir, const std::string &suffix, unsigned int limit=0U)
An initialiser that is to be used after default construction.
Definition: gdirectory.cpp:87
An encapsulation of a file system directory which allows for iterating through the set of contained f...
Definition: gdirectory.h:46
void remove(int)
Marks the message for removal.
Definition: gpopstore.cpp:319
Size totalByteCount() const
Returns the store's total byte count.
Definition: gpopstore.cpp:239
A trivial class which is used like G::Root by GPop::Store for reading directory listings.
Definition: gpopstore.cpp:54
#define G_ASSERT(test)
Definition: gassert.h:30
static bool replace(std::string &s, const std::string &from, const std::string &to, size_type *pos_p=NULL)
Replaces 'from' with 'to', starting at offset '*pos_p'.
Definition: gstr.cpp:55
Low-level classes.
static bool enabled()
Returns true if test features are enabled.
Definition: gtest.cpp:46
~StoreLock()
Destructor.
Definition: gpopstore.cpp:229
#define G_ERROR(expr)
Definition: glog.h:108
A Directory iterator that does all file i/o in one go.
Definition: gdirectory.h:164
static bool exists(const Path &file)
Returns true if the file (directory, link, device etc.) exists.
Definition: gfile.cpp:130
#define G_DEBUG(expr)
Definition: glog.h:95
static bool remove(const Path &path, const NoThrow &)
Deletes the file or directory. Returns false on error.
Definition: gfile.cpp:29
std::list< Entry > List
Definition: gpopstore.h:106
static std::string tmp()
A convenience function for constructing a filename for writeable().
An overload discriminator class for File methods.
Definition: gfile.h:56
void lock(const std::string &user)
Initialisation.
Definition: gpopstore.cpp:183
std::auto_ptr< std::istream > get(int id) const
Retrieves the message content.
Definition: gpopstore.cpp:300
Size byteCount(int id) const
Returns a message size.
Definition: gpopstore.cpp:281
void commit()
Commits remove()als.
Definition: gpopstore.cpp:333
void pathAppend(const std::string &tail)
Appends a filename to the path.
Definition: gpath.cpp:301
G::Path fileName() const
Returns the current filename.
Definition: gdirectory.cpp:136
bool byName() const
Returns true if the spool directory is affected by the user name.
Definition: gpopstore.cpp:87
static unsigned long toULong(const std::string &s, bool limited=false)
Converts string 's' to an unsigned long.
Definition: gstr.cpp:362
bool valid(int id) const
Validates a message number.
Definition: gpopstore.cpp:248
bool isDir() const
Returns true if the current item is a directory.
Definition: gdirectory.cpp:126
POP3 classes.
StoreLock(Store &store)
Constructor.
Definition: gpopstore.cpp:178
A Path object represents a file system path.
Definition: gpath.h:44
List list(int id=-1) const
Lists messages in the store.
Definition: gpopstore.cpp:287
#define G_WARNING(expr)
Definition: glog.h:107
std::string uidl(int id) const
Returns a message's unique id.
Definition: gpopstore.cpp:377