VideoTools
 All Classes Namespaces Files Functions Variables Typedefs Enumerations Enumerator Macros Pages
gvribbon.cpp
Go to the documentation of this file.
1 //
2 // Copyright (C) 2017 Graeme Walker
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 // gvribbon.cpp
19 //
20 
21 #include "gdef.h"
22 #include "gvribbon.h"
23 #include "gstr.h"
24 #include "gdatetime.h"
25 #include "gdate.h"
26 #include "gtime.h"
27 #include "gfiletree.h"
28 #include "gassert.h"
29 #include <algorithm>
30 #include <string>
31 #include <ctime>
32 
33 int Gv::Ribbon::m_height = 12 ;
34 
36 {
37 }
38 
39 Gv::Ribbon::Ribbon( size_t size , const G::Path & scan_base , const std::string & name , const Gv::Timezone & tz ) :
40  m_scan_base(scan_base) ,
41  m_tz(tz) ,
42  m_list(size)
43 {
44  const std::string prefix = name.empty() ? std::string() : ( name + "." ) ;
45  m_match1 = "/####/##/##/##/##/##/" + prefix + "##" ; // /yyyy/mm/dd/hh/mm/ss/[<name>.]##[.<ext>]
46  m_match2 = "/####/##/##/##/##/##/###/" + prefix + "##" ; // /yyyy/mm/dd/hh/mm/ss/msm/[<name>.]##[.<ext>]
47 }
48 
49 size_t Gv::Ribbon::timepos( const G::Path & path , const std::string & name )
50 {
51  const std::string prefix = name.empty() ? std::string() : ( name + "." ) ;
52  std::string match1 = "/####/##/##/##/##/##/" + prefix + "##" ;
53  std::string match2 = "/####/##/##/##/##/##/###/" + prefix + "##" ;
54  return timeposImp( path , match1 , match2 ) ;
55 }
56 
57 size_t Gv::Ribbon::timepos( const G::Path & path ) const
58 {
59  return timeposImp( path , m_match1 , m_match2 ) ;
60 }
61 
62 size_t Gv::Ribbon::timeposImp( const G::Path & path , const std::string & match1 , const std::string & match2 )
63 {
64  G_ASSERT( path != G::Path() ) ;
65  if( path == G::Path() )
66  return 0U ;
67 
68  std::string path_str = path.withoutExtension().str() ;
69  if( path_str.length() < match1.length() )
70  return 0U ;
71 
72  std::string::iterator const end = path_str.end() ;
73  for( std::string::iterator p = path_str.begin() ; p != end ; ++p )
74  {
75  if( *p == '#' ) *p = '_' ;
76  if( *p >= '0' && *p <= '9' ) *p = '#' ;
77  }
78 
79  size_t pos = path_str.find( match1 ) ;
80  if( pos != std::string::npos )
81  return pos+1U ;
82 
83  pos = path_str.find( match2 ) ;
84  return pos == std::string::npos ? 0U : (pos+1U) ;
85 }
86 
87 void Gv::Ribbon::clear()
88 {
89  m_list.assign( m_list.size() , Item() ) ;
90 }
91 
92 void Gv::Ribbon::scan( const G::Path & trigger_path , size_t tpos )
93 {
94  G_ASSERT( tpos != 0U ) ;
95  scan( RibbonRange(trigger_path,tpos,m_tz) ) ;
96 }
97 
98 void Gv::Ribbon::scan( const Gv::RibbonRange & range )
99 {
100  if( !m_list.empty() )
101  {
102  G::FileTree file_tree ;
103  if( scanStart( file_tree , range ) )
104  scanSome( file_tree , G::EpochTime(0) ) ;
105  }
106 }
107 
108 bool Gv::Ribbon::scanStart( G::FileTree & file_tree , const G::Path & trigger_path , size_t tpos )
109 {
110  G_ASSERT( tpos != 0U ) ;
111  return scanStart( file_tree , RibbonRange(trigger_path,tpos,m_tz) ) ;
112 }
113 
114 bool Gv::Ribbon::scanStart( G::FileTree & file_tree , const Gv::RibbonRange & range )
115 {
116  if( m_list.empty() )
117  {
118  return false ;
119  }
120  else
121  {
122  clear() ;
123  m_range = range ;
124  G::Path start_path = m_range.startpath() ;
125  G_LOG( "Gv::Ribbon::scan: ribbon: starting scan: base=[" << m_scan_base << "] start=[" << start_path << "]" ) ;
126  file_tree.reroot( m_scan_base ) ;
127  return file_tree.reposition( start_path ) ;
128  }
129 }
130 
131 bool Gv::Ribbon::scanSome( G::FileTree & file_tree , G::EpochTime interval )
132 {
133  bool limited = interval != G::EpochTime(0) ;
134  G::EpochTime limit = limited ? ( G::DateTime::now() + interval ) : G::EpochTime(0) ;
135 
136  unsigned int count = 0U ;
137  for( G::Path path = file_tree.current() ; path != G::Path() ; path = file_tree.next() , count++ )
138  {
139  unsigned int ts = m_range.timestamp( path ) ;
140  if( ts == 0U ) continue ;
141  if( ts >= m_range.end() ) break ;
142  m_list.at(bucket(ts)).update( path , ts ) ;
143  if( limited && G::DateTime::now() > limit )
144  {
145  G_LOG( "Gv::Ribbon::scan: ribbon: partial scan: count=" << (count+1U) ) ;
146  file_tree.next() ;
147  return false ;
148  }
149  }
150  G_LOG( "Gv::Ribbon::scan: ribbon: scan complete: count=" << count ) ;
151  return true ;
152 }
153 
154 bool Gv::Ribbon::apply( const G::Path & path )
155 {
156  if( !m_list.empty() )
157  {
158  unsigned int ts = m_range.timestamp( path ) ;
159  if( ts == 0U )
160  return false ;
161 
162  bool outside_range = ts < m_range.start() || ts >= m_range.end() ;
163  if( outside_range )
164  {
165  clear() ;
166  m_range = m_range.around( path ) ;
167  }
168 
169  size_t index = bucket( ts ) ;
170  Item & item = m_list.at( index ) ;
171  item.update( path , ts ) ;
172  if( !item.mark ) unmark() ;
173  item.mark = true ;
174 
175  return outside_range ;
176  }
177  else
178  {
179  return false ;
180  }
181 }
182 
183 void Gv::Ribbon::unmark()
184 {
185  const List::iterator end = m_list.end() ;
186  for( List::iterator p = m_list.begin() ; p != end ; ++p )
187  (*p).mark = false ;
188 }
189 
190 size_t Gv::Ribbon::bucket( unsigned int ts ) const
191 {
192  unsigned int range = m_range.end() - m_range.start() ;
193  size_t index = (ts-m_range.start()) * m_list.size() ;
194  index /= range ;
195  return std::min( index , m_list.size()-1U ) ;
196 }
197 
199 {
200  List::const_iterator p = m_list.begin() ;
201  while( p != m_list.end() && (*p).first == G::Path() )
202  ++p ;
203  return p == m_list.end() ? G::Path() : (*p).first ;
204 }
205 
207 {
208  if( m_list.empty() ) return G::Path() ;
209  List::const_iterator p = m_list.begin() + (m_list.size()-1U) ;
210  while( p != m_list.begin() && (*p).first == G::Path() )
211  --p ;
212  return (*p).last ;
213 }
214 
215 G::Path Gv::Ribbon::find( size_t bucket , bool before ) const
216 {
217  G_ASSERT( bucket < m_list.size() ) ;
218  List::const_iterator p = m_list.begin() + bucket ;
219  if( before )
220  {
221  while( p != m_list.begin() && (*p).first == G::Path() )
222  --p ;
223  return (*p).first == G::Path() ? first() : (*p).last ;
224  }
225  else
226  {
227  while( p != m_list.end() && (*p).first == G::Path() )
228  ++p ;
229  return p == m_list.end() ? last() : (*p).first ;
230  }
231 }
232 
233 size_t Gv::Ribbon::size() const
234 {
235  return m_list.size() ;
236 }
237 
239 {
240  return m_height ;
241 }
242 
243 bool Gv::Ribbon::timestamped( const G::Path & path ) const
244 {
245  return timepos( path ) != 0U ;
246 }
247 
248 Gv::Ribbon::List::const_iterator Gv::Ribbon::begin() const
249 {
250  return m_list.begin() ;
251 }
252 
253 Gv::Ribbon::List::const_iterator Gv::Ribbon::end() const
254 {
255  return m_list.end() ;
256 }
257 
259 {
260  return m_range ;
261 }
262 
264 {
265  return m_range(offset) ;
266 }
267 
268 // ==
269 
271  m_start(0) ,
272  m_end(24U*3600U) ,
273  m_startpath("/2000/01/01/00/00") ,
274  m_endpath("/2000/01/02/00/00")
275 {
276 }
277 
278 Gv::RibbonRange::RibbonRange( const G::Path & trigger_path , size_t tpos , const Gv::Timezone & tz ) :
279  m_tpos(tpos) ,
280  m_tz(tz) ,
281  m_start(0) ,
282  m_end(0)
283 {
284  const unsigned int day_seconds = 24U * 3600U ;
285  m_start = daystamp( bdt(trigger_path.str(),tpos) , tz ) ;
286  m_end = m_start ? (m_start+day_seconds) : 0U ;
287 
288  if( m_start != 0 )
289  {
290  m_startpath = makePath( trigger_path , tpos , G::EpochTime(m_start) ) ;
291  m_endpath = makePath( trigger_path , tpos , G::EpochTime(m_end) ) ;
292  }
293 
294  G_LOG( "Gv::RibbonRange::ctor: ribbon: trigger-path=[" << trigger_path << "] " << "startpath=[" << m_startpath << "]" ) ;
295 }
296 
298 {
299  return RibbonRange( path , m_tpos , m_tz ) ;
300 }
301 
303 {
304  const int day_seconds = 3600*24 ;
305 
306  RibbonRange result ;
307  result.m_start = m_start + ( offset * day_seconds ) ;
308  result.m_end = m_end + ( offset * day_seconds ) ;
309  result.m_startpath = makePath( m_startpath , m_tpos , G::EpochTime(result.m_start) ) ;
310  result.m_endpath = makePath( m_startpath/*sic*/ , m_tpos , G::EpochTime(result.m_end) ) ;
311  return result ;
312 }
313 
314 G::Path Gv::RibbonRange::makePath( const G::Path & trigger_path , size_t tpos , G::EpochTime t )
315 {
316  Tm tm = G::DateTime::utc( t ) ;
317  G::Date date( tm ) ;
318  G::Time time( tm ) ;
319  return trigger_path.str().substr(0U,tpos) + "/" + date.string() + "/" + time.hhmm("/") ;
320 }
321 
322 unsigned int Gv::RibbonRange::start() const
323 {
324  return m_start < epoch_base ? 0U : static_cast<unsigned int>(m_start-epoch_base) ;
325 }
326 
327 unsigned int Gv::RibbonRange::end() const
328 {
329  return m_end < epoch_base ? 0U : static_cast<unsigned int>(m_end-epoch_base) ;
330 }
331 
333 {
334  return m_startpath ;
335 }
336 
338 {
339  return m_endpath ;
340 }
341 
342 unsigned int Gv::RibbonRange::timestamp( const G::Path & path ) const
343 {
344  return timestamp( path , m_tpos ) ;
345 }
346 
347 unsigned int Gv::RibbonRange::timestamp( const G::Path & path , size_t tpos )
348 {
349  if( tpos == 0U )
350  return 0U ;
351 
352  G::DateTime::BrokenDownTime tm = bdt( path.str() , tpos ) ;
353  if( tm.tm_year == 0 )
354  return 0U ;
355 
356  std::time_t t = G::DateTime::epochTime(tm).s ;
357  if( t == std::time_t(-1) || t < epoch_base )
358  return 0U ;
359 
360  return static_cast<unsigned int>(t-epoch_base) ;
361 }
362 
363 std::time_t Gv::RibbonRange::daystamp( G::DateTime::BrokenDownTime tm , const Gv::Timezone & tz )
364 {
365  const std::time_t day_seconds = 3600*24 ;
366  if( tm.tm_year == 0 ) return 0 ;
367  G_ASSERT( tm.tm_hour >= 0 && tm.tm_min >= 0 ) ;
368 
369  // round down
370  std::time_t day_offset = std::time_t(tm.tm_hour)*3600 + std::time_t(tm.tm_min)*60 + tm.tm_sec ;
371  tm.tm_hour = tm.tm_min = tm.tm_sec = 0 ; // reset to start-of-day
372  std::time_t start_of_day = G::DateTime::epochTime(tm).s ;
373  if( start_of_day == std::time_t(-1) ) return 0 ;
374 
375  // adjust
376  start_of_day += tz.seconds() ;
377 
378  // carry
379  if( tz.seconds() > 0 && day_offset < tz.seconds() )
380  start_of_day -= day_seconds ;
381  else if( tz.seconds() < 0 && day_offset > (day_seconds+/*sic*/tz.seconds()) )
382  start_of_day += day_seconds ;
383 
384  return start_of_day ;
385 }
386 
387 G::DateTime::BrokenDownTime Gv::RibbonRange::bdt( const std::string & path , size_t tpos )
388 {
389  static Tm tm_zero ;
390  if( tpos == 0U )
391  return tm_zero ;
392 
393  G_ASSERT( tpos < path.length() ) ;
394  G_ASSERT( path.at(tpos+4U) == '/' && path.at(tpos+7U) == '/' ) ;
395  G_ASSERT( path.at(tpos+10U) == '/' && path.at(tpos+13U) == '/' ) ;
396  G_ASSERT( path.at(tpos+16U) == '/' ) ;
397 
398  const char * p = path.data() + tpos ;
399 
400  unsigned int yy = to_int( p[2] , p[3] ) ;
401  unsigned int mm = to_int( p[5] , p[6] ) ;
402  unsigned int dd = to_int( p[8] , p[9] ) ;
403  unsigned int HH = to_int( p[11], p[12] ) ;
404  unsigned int MM = to_int( p[14], p[15] ) ;
405 
406  const bool valid =
407  yy <= 99U &&
408  mm >= 1U && mm <= 12U &&
409  dd >= 1U && mm <= 31U &&
410  HH <= 23U &&
411  MM <= 59U ;
412 
413  Tm tm = tm_zero ;
414  if( valid )
415  {
416  tm.tm_sec = 0 ;
417  tm.tm_min = MM ;
418  tm.tm_hour = HH ;
419  tm.tm_mday = dd ;
420  tm.tm_mon = mm - 1U ;
421  tm.tm_year = yy + 100U ;
422  tm.tm_wday = 0 ;
423  tm.tm_yday = 0 ;
424  tm.tm_isdst = 0 ;
425  }
426  return tm ;
427 }
428 
429 unsigned int Gv::RibbonRange::to_int( char a , char b , unsigned int error )
430 {
431  if( a < '0' || a > '9' || b < '0' || b > '9' ) return error ;
432  unsigned int hi = static_cast<unsigned int>(a-'0') ;
433  unsigned int lo = static_cast<unsigned int>(b-'0') ;
434  return hi * 10U + lo ;
435 }
436 
437 // ==
438 
439 Gv::Ribbon::Item::Item() :
440  first_ts(0U) ,
441  last_ts(0U) ,
442  mark(false)
443 {
444 }
445 
446 void Gv::Ribbon::Item::update( const G::Path & path , unsigned int ts )
447 {
448  G_ASSERT( path != G::Path() && ts != 0U && (first_ts==0U) == (first==G::Path()) ) ;
449  if( first_ts == 0U )
450  {
451  first = last = path ;
452  first_ts = last_ts = ts ;
453  }
454  else if( ts < first_ts || ( ts == first_ts && path.str() <= first.str() ) )
455  {
456  first = path ;
457  first_ts = ts ;
458  }
459  else if( ts > last_ts || ( ts == last_ts && path.str() > last.str() ) )
460  {
461  last = path ;
462  last_ts = ts ;
463  }
464 }
465 
466 bool Gv::Ribbon::Item::set() const
467 {
468  return first_ts != 0U ;
469 }
470 
471 bool Gv::Ribbon::Item::includes( const G::Path & path ) const
472 {
473  return set() && path.str() >= first.str() && path.str() <= last.str() ;
474 }
475 
476 bool Gv::Ribbon::Item::marked() const
477 {
478  return mark ;
479 }
480 /// \file gvribbon.cpp
static BrokenDownTime utc(EpochTime epoch_time)
Converts from epoch time to UTC broken-down-time.
Definition: gdatetime.cpp:123
std::string str() const
Returns the path string.
Definition: gpath.cpp:290
A subsecond-resolution timestamp based on a time_t.
Definition: gdatetime.h:39
void scan(const G::Path &path, size_t tpos)
Does a complete scan for the day that encompases the given timestamped() file.
Definition: gvribbon.cpp:92
RibbonRange around(const G::Path &) const
Returns a range includes the given timestamped trigger path.
Definition: gvribbon.cpp:297
A date (dd/mm/yyyy) class.
Definition: gdate.h:39
bool apply(const G::Path &)
Called when a possibly-new timestamped file is encountered, allowing the ribbon to update itself for ...
Definition: gvribbon.cpp:154
A class for walking files in a directory tree, with repositioning.
Definition: gfiletree.h:50
Path withoutExtension() const
Returns a path without the basename extension, if any.
Definition: gpath.cpp:328
std::time_t seconds() const
Returns the offset as a signed number of seconds.
Definition: gvtimezone.cpp:63
A representation of a timezone.
Definition: gvtimezone.h:35
RibbonRange()
Default constructor for an unusable object.
Definition: gvribbon.cpp:270
void reroot(const G::Path &root)
Resets the root, as if newly constructed.
Definition: gfiletree.cpp:53
A simple time-of-day (hh/mm/ss) class.
Definition: gtime.h:38
G::Path last() const
Returns the last scanned path.
Definition: gvribbon.cpp:206
bool reposition(const G::Path &path)
Repositions the iterator within the current tree, at or after the given position. ...
Definition: gfiletree.cpp:92
List::const_iterator begin() const
Returns the item list's begin iterator.
Definition: gvribbon.cpp:248
RibbonRange operator()(int offset) const
Returns a range that is offset from the current range.
Definition: gvribbon.cpp:302
static EpochTime now()
Returns the current epoch time.
unsigned int end() const
Returns the timestamp() value for the start of the next day.
Definition: gvribbon.cpp:327
G::Path first() const
Returns the first scanned path.
Definition: gvribbon.cpp:198
Represents a day span within the file store, used by Gv::Ribbon.
Definition: gvribbon.h:40
const RibbonRange & range() const
Returns the current day range.
Definition: gvribbon.cpp:258
static int height()
Returns a suggested height of the ribbon, in pixels.
Definition: gvribbon.cpp:238
static size_t timepos(const G::Path &path, const std::string &name)
Returns the timestamp position within the given path, or zero if none.
Definition: gvribbon.cpp:49
G::Path current() const
Returns the current path.
Definition: gfiletree.cpp:68
size_t size() const
Returns the size of the list, as passed in to the constructor.
Definition: gvribbon.cpp:233
G::Path next()
Moves to the next file in the tree, depth first, and returns the path.
Definition: gfiletree.cpp:73
unsigned int start() const
Returns the timestamp() value for the start of the day.
Definition: gvribbon.cpp:322
bool scanSome(G::FileTree &, G::EpochTime interval)
Does a partial scan, limited by the given time interval.
Definition: gvribbon.cpp:131
G::Path startpath() const
Returns the directory path that is the start of the day at the minutes level (eg. ...
Definition: gvribbon.cpp:332
List::const_iterator end() const
Returns the item list's end iterator.
Definition: gvribbon.cpp:253
static EpochTime epochTime(const BrokenDownTime &broken_down_time)
Converts from UTC broken-down-time to epoch time.
Definition: gdatetime.cpp:83
unsigned int timestamp(const G::Path &) const
Returns the time value for the given path, as seconds within some arbitrary epoch.
Definition: gvribbon.cpp:342
bool scanStart(G::FileTree &, const RibbonRange &range)
Prepares for an iterative scan, with subsequent calls to scanSome().
Definition: gvribbon.cpp:114
G::Path find(size_t offset, bool before=false) const
Finds the first scanned path at-or-after the given bucket position, or returns the last() path if the...
Definition: gvribbon.cpp:215
bool timestamped(const G::Path &path) const
Returns true if the given absolute path has a non-zero timepos().
Definition: gvribbon.cpp:243
G::Path endpath() const
Returns the directory path that is the start of the next day at the minutes level.
Definition: gvribbon.cpp:337
A Path object represents a file system path.
Definition: gpath.h:72
A time bucket within a Gv::Ribbon.
Definition: gvribbon.h:106
Ribbon()
Default constructor for a zero-size() ribbon.
Definition: gvribbon.cpp:35