VideoTools
 All Classes Namespaces Files Functions Variables Typedefs Enumerations Enumerator Macros Pages
gvimageoutput.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 // gvimageoutput.cpp
19 //
20 
21 #include "gdef.h"
22 #include "gvimageoutput.h"
23 #include "grimagetype.h"
24 #include "gprocess.h"
25 #include "gdatetime.h"
26 #include "gstr.h"
27 #include "groot.h"
28 #include "gfile.h"
29 #include "gpath.h"
30 #include "gassert.h"
31 #include "gtest.h"
32 #include "genvironment.h"
33 #include "glogoutput.h"
34 #include "glog.h"
35 #include "garg.h"
36 #include <unistd.h> // fork()
37 #include <stdexcept>
38 #include <iomanip>
39 #include <iostream>
40 #include <sstream>
41 #include <fstream>
42 
43 namespace
44 {
45  struct Args
46  {
47  G::StringArray list ;
48  std::vector<char*> pointers ;
49  void add( const std::string & s ) { list.push_back(s) ; }
50  void finish()
51  {
52  for( G::StringArray::iterator p = list.begin() ; p != list.end() ; ++p )
53  pointers.push_back( const_cast<char*>((*p).c_str()) ) ;
54  pointers.push_back(nullptr) ;
55  }
56  } ;
57 }
58 
59 static pid_t fork_()
60 {
61  pid_t pid = fork() ;
62  if( pid == static_cast<pid_t>(-1) )
63  throw std::runtime_error( "fork error" ) ;
64  return pid ;
65 }
66 
68  m_fast(false) ,
69  m_tz(0) ,
70  m_save_test_mode(false) ,
71  m_viewer_up(false)
72 {
73 }
74 
76  m_fast(false) ,
77  m_tz(0) ,
78  m_save_test_mode(false) ,
79  m_viewer_up(false)
80 {
81  m_ping_timer_ptr.reset( new GNet::Timer<ImageOutput>(*this,&ImageOutput::onPingTimeout,handler) ) ;
82 }
83 
84 void Gv::ImageOutput::startPublisher( const std::string & channel )
85 {
86  G::Item info = G::Item::map() ;
87  if( !info.has("type") )
88  info.add( "type" , G::Str::tail(G::Path(G::Arg::v0()).basename(),"-",false) ) ;
89 
90  m_publisher.reset( new G::Publisher(channel,info) ) ;
91 }
92 
93 void Gv::ImageOutput::startPublisher( const std::string & channel , const G::Item & info_in )
94 {
95  G::Item info( info_in ) ;
96  if( !info.has("type") )
97  info.add( "type" , G::Str::tail(G::Path(G::Arg::v0()).basename(),"-",false) ) ;
98 
99  m_publisher.reset( new G::Publisher(channel,info) ) ;
100 }
101 
103 {
104  m_publisher.reset( p ) ;
105 }
106 
107 void Gv::ImageOutput::startViewer( const std::string & title , unsigned int viewer_scale , const std::string & event_channel )
108 {
109  const bool debug = G::Test::enabled("viewer-debug") ;
110  const bool qforquit = !G::Environment::get("VT_VIEWER_Q","").empty() ;
111  const bool syslog = G::LogOutput::instance() && G::LogOutput::instance()->syslog() ;
112 
113  std::string viewer_exe ;
114  if( G::Test::enabled("viewer-path-from-environment") ) // security risk if environment is poisoned
115  {
116  viewer_exe = G::Environment::get( "VT_VIEWER" , "" ) ;
117  }
118  if( viewer_exe.empty() )
119  {
120  std::string this_exe = G::Arg::exe() ; // throws on error eg. if no procfs
121  viewer_exe = viewer( this_exe ) ;
122  }
123 
124  m_fat_pipe.reset( new G::FatPipe ) ;
125  if( 0 == fork_() )
126  {
127  m_fat_pipe->doChild() ;
128  std::string sscale = G::Str::fromUInt(viewer_scale) ;
129  Args args ;
130  args.add( viewer_exe ) ;
131  if( qforquit ) { args.add( "--quit" ) ; }
132  if( debug ) { args.add( "--debug" ) ; }
133  if( syslog ) { args.add( "--syslog" ) ; }
134  if( !title.empty() ) { args.add( "--title=" + sanitise(title) ) ; }
135  if( viewer_scale != 1U ) { args.add( "--scale=" + sanitise(sscale) ) ; }
136  if( !event_channel.empty() ) { args.add( "--channel=" + sanitise(event_channel) ) ; }
137  args.add( m_fat_pipe->shmemfd() ) ;
138  args.add( m_fat_pipe->pipefd() ) ;
139  args.finish() ;
140  ::execv( viewer_exe.c_str() , &args.pointers[0] ) ;
141  int e = errno ;
142  std::cerr << "error: exec failed for [" << viewer_exe << "]: " << G::Process::strerror(e) << "\n" ;
143  _exit( 1 ) ;
144  }
145  m_fat_pipe->doParent() ;
146 
147  if( m_ping_timer_ptr.get() != nullptr )
148  m_ping_timer_ptr->startTimer( 1U ) ;
149 }
150 
151 std::string Gv::ImageOutput::sanitise( const std::string & s_in )
152 {
153  std::string meta_in = G::Str::meta() ;
154  std::string meta_out( meta_in.size() , '.' ) ;
155  return G::Str::escaped( G::Str::printable(s_in) , '\\' , meta_in , meta_out ) ;
156 }
157 
159 {
160  return m_fat_pipe.get() != nullptr ;
161 }
162 
164 {
165  return m_fat_pipe.get() ? m_fat_pipe->ping() : false ;
166 }
167 
168 void Gv::ImageOutput::onPingTimeout()
169 {
170  G_ASSERT( m_ping_timer_ptr.get() != nullptr ) ;
171  pingViewerCheck() ;
172  m_ping_timer_ptr->startTimer( 1U ) ;
173 }
174 
176 {
177  if( pingViewer() )
178  m_viewer_up = true ;
179  else if( m_viewer_up )
180  throw std::runtime_error( "viewer has gone away" ) ;
181 }
182 
183 void Gv::ImageOutput::saveTo( const std::string & base_dir , const std::string & name , bool fast , const Gv::Timezone & tz , bool test_mode )
184 {
185  m_base_dir = base_dir ;
186  m_name = name ;
187  m_fast = fast ;
188  m_tz = tz ;
189  m_save_test_mode = test_mode ;
190 }
191 
192 const std::string & Gv::ImageOutput::dir() const
193 {
194  return m_base_dir ;
195 }
196 
197 G::Path Gv::ImageOutput::send( const char * p , size_t n , Gr::ImageType type , G::EpochTime time )
198 {
199  Gr::ImageType::String type_str ; type.set( type_str ) ;
200 
201  if( m_publisher.get() )
202  m_publisher->publish( p , n , type_str.c_str() ) ;
203 
204  if( m_fat_pipe.get() )
205  m_fat_pipe->send( p , n , type_str.c_str() ) ;
206 
207  if( m_base_dir.empty() )
208  return G::Path() ;
209  else
210  return save( p , n , type , time ) ;
211 }
212 
214 {
215  Gr::ImageType::String type_str ; type.set( type_str ) ;
216 
217  if( m_publisher.get() )
218  m_publisher->publish( buffer , type_str.c_str() ) ;
219 
220  if( m_fat_pipe.get() )
221  m_fat_pipe->send( buffer , type_str.c_str() ) ;
222 
223  if( m_base_dir.empty() )
224  return G::Path() ;
225  else
226  return save( buffer , type , time ) ;
227 }
228 
229 void Gv::ImageOutput::sendText( const char * p , size_t n , const std::string & type )
230 {
231  if( m_publisher.get() )
232  m_publisher->publish( p , n , type.c_str() ) ;
233 
234  if( m_fat_pipe.get() )
235  m_fat_pipe->send( p , n , type.c_str() ) ;
236 }
237 
238 std::string Gv::ImageOutput::viewer( const G::Path & this_exe )
239 {
240  G_ASSERT( this_exe != G::Path() ) ;
241  G::Path dir = this_exe.dirname() ;
242  std::string this_name = this_exe.basename() ;
243  size_t pos = this_name.find_last_of( "-_" ) ;
244  std::string name =
245  pos == std::string::npos ?
246  std::string("viewer") :
247  ( G::Str::head(this_name,pos,std::string()) + this_name.at(pos) + "viewer" ) ;
248  std::string result = G::Path(dir,name).str() ;
249  G_DEBUG( "Gv::ImageOutput::viewerExecutable: viewer-exe=[" << result << "]" ) ;
250  return result ;
251 }
252 
253 namespace
254 {
255  void append( std::string & s , short n , char c = '\0' )
256  {
257  char cc[3] ;
258  cc[0] = '0' + (n/10) ;
259  cc[1] = '0' + (n%10) ;
260  cc[2] = c ;
261  s.append( cc , c ? 3U : 2U ) ;
262  }
263  void append3( std::string & s , short n , char c = '\0' )
264  {
265  char cc[4] ;
266  cc[0] = '0' + ((n/100)%10) ;
267  cc[1] = '0' + ((n/10)%10) ;
268  cc[2] = '0' + (n%10) ;
269  cc[3] = c ;
270  s.append( cc , c ? 4U : 3U ) ;
271  }
272  void append4( std::string & s , short n , char c = '\0' )
273  {
274  char cc[5] ;
275  cc[0] = '0' + ((n/1000)%10) ;
276  cc[1] = '0' + ((n/100)%10) ;
277  cc[2] = '0' + ((n/10)%10) ;
278  cc[3] = '0' + (n%10) ;
279  cc[4] = c ;
280  s.append( cc , c ? 5U : 4U ) ;
281  }
282 }
283 
284 void Gv::ImageOutput::path( std::string & out , const std::string & base_dir , const std::string & name ,
285  G::EpochTime time , Gr::ImageType type , bool fast , const Gv::Timezone & tz , bool test_mode )
286 {
287  G_ASSERT( name.find('/') == std::string::npos ) ;
288 
289  out.reserve( base_dir.length() + name.length() + 30U ) ;
290  out.assign( base_dir ) ;
291  out.append( 1U , '/' ) ;
292 
293  G::DateTime::BrokenDownTime tm = G::DateTime::utc(time+tz.seconds()) ;
294 
295  if( test_mode )
296  {
297  G_WARNING_ONCE( "Gv::ImageOutput::path: saving in test mode: date fixed to 2000" ) ;
298  tm.tm_year = 100 ;
299  tm.tm_mon = 0 ;
300  tm.tm_mday = 1 + (tm.tm_mday & 1) ;
301  }
302 
303  append4( out , tm.tm_year+1900 , '/' ) ;
304  append( out , tm.tm_mon+1 , '/' ) ;
305  append( out , tm.tm_mday , '/' ) ;
306  append( out , tm.tm_hour , '/' ) ;
307  append( out , tm.tm_min , '/' ) ;
308  if( fast )
309  {
310  append( out , tm.tm_sec , '/' ) ;
311  out.append( name ) ;
312  if( !name.empty() ) out.append( 1U , '.' ) ;
313  append3( out , time.us >> 10 ) ; // 0..976
314  }
315  else
316  {
317  out.append( name ) ;
318  if( !name.empty() ) out.append( 1U , '.' ) ;
319  append( out , tm.tm_sec , '\0' ) ;
320  }
321 
322  if( type.isJpeg() )
323  out.append( ".jpg" ) ;
324  else if( type.isRaw() )
325  out.append( type.channels() == 1 ? ".pgm" : ".ppm" ) ;
326  else
327  out.append( ".dat" ) ;
328 }
329 
330 std::string Gv::ImageOutput::path( G::EpochTime time , Gr::ImageType type , bool fast ) const
331 {
332  std::string result ;
333  path( result , m_base_dir , m_name , time , type , fast , m_tz , m_save_test_mode ) ;
334  return result ;
335 }
336 
337 G::Path Gv::ImageOutput::save( const char * p , size_t n , Gr::ImageType type , G::EpochTime time )
338 {
339  std::ofstream file ;
340  G::Path path = openFile( file , type , time ) ;
341  if( path != G::Path() )
342  {
343  file.write( p , n ) ;
344  commitFile( file , path ) ;
345  }
346  return path ;
347 }
348 
349 G::Path Gv::ImageOutput::save( const Gr::ImageBuffer & image_buffer , Gr::ImageType type , G::EpochTime time )
350 {
351  std::ofstream file ;
352  G::Path path = openFile( file , type , time ) ;
353  if( path != G::Path() )
354  {
355  file << image_buffer ;
356  commitFile( file , path ) ;
357  }
358  return path ;
359 }
360 
361 G::Path Gv::ImageOutput::openFile( std::ofstream & file , Gr::ImageType type , G::EpochTime time )
362 {
363  if( m_base_dir.empty() )
364  return G::Path() ;
365 
366  // prepare the filename
367  if( time.s == 0 ) time = G::DateTime::now() ;
368  G::Path path = this->path( time , type , m_fast ) ;
369 
370  // if the same filename as last time keep the old contents
371  if( path == m_old_path )
372  return G::Path() ;
373 
374  // create the directory if necessary
375  {
376  G::Path dir = path.dirname() ;
377  if( m_old_dir != dir )
378  {
379  bool ok = false ;
380  {
381  G::Root claim_root ;
382  ok = G::File::mkdirs( dir , G::File::NoThrow() ) ;
383  }
384  if( !ok )
385  m_old_dir = dir ;
386  }
387  }
388 
389  // create the file
390  {
391  std::string path_str = path.str() ;
392  G::Root claim_root ;
393  file.open( path_str.c_str() ) ;
394  }
395 
396  // for raw images write out the header
397  if( type.isRaw() )
398  {
399  // prefix with 'portable-anymap' header
400  file
401  << (type.channels()==1?"P5":"P6") << "\n"
402  << type.dx() << " " << type.dy() << "\n"
403  << "255\n" ;
404  }
405 
406  return path ;
407 }
408 
409 void Gv::ImageOutput::commitFile( std::ofstream & file , const G::Path & path )
410 {
411  file.close() ;
412  if( file.fail() ) // ie. badbit or failbit
413  G_ERROR( "Gv::ImageOutput::save: file write error [" << path << "]" ) ;
414  else
415  m_old_path = path ;
416 }
417 
419 {
420  return m_fast ;
421 }
422 
424 {
425 }
426 
427 /// \file gvimageoutput.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
bool isRaw() const
Returns true if a raw image type.
void startViewer(const std::string &title=std::string(), unsigned int scale=1U, const std::string &output_channel=std::string())
Starts the viewer.
A subsecond-resolution timestamp based on a time_t.
Definition: gdatetime.h:39
static std::string get(const std::string &name, const std::string &default_)
Returns the environment variable value or the given default.
static std::string printable(const std::string &in, char escape= '\\')
Returns a printable represention of the given input string.
Definition: gstr.cpp:663
bool syslog() const
Returns true if syslog output is enabled.
Definition: glogoutput.cpp:302
const std::string & dir() const
Returns the saveTo() path.
An abstract interface for handling exceptions thrown out of event-loop callbacks (socket events and t...
Definition: geventhandler.h:44
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
bool isJpeg() const
Returns true if a jpeg image type.
int channels() const
Returns the number of channels.
Definition: grimagetype.h:197
std::string basename() const
Returns the rightmost part of the path, ignoring "." parts.
Definition: gpath.cpp:310
G::Path send(const char *data, size_t size, Gr::ImageType type, G::EpochTime=G::EpochTime(0))
Emits an image. Returns the path if saveTo()d the filesystem.
static Item map()
Factory function for a map item.
Definition: gitem.cpp:33
std::vector< std::string > StringArray
A std::vector of std::strings.
Definition: gstrings.h:33
static LogOutput * instance()
Returns a pointer to the controlling LogOutput object.
Definition: glogoutput.cpp:109
An encapsulation of image type, including width, height and number of channels, with support for a st...
Definition: grimagetype.h:43
A class which acquires the process's special privileges on construction and releases them on destruct...
Definition: groot.h:49
Vectors ImageBuffer
An ImageBuffer is used to hold raw image data, typically in more than one chunk.
Definition: grimagebuffer.h:47
String & set(String &out) const
Returns str() by reference.
static std::string exe(bool do_throw=true)
Returns Process::exe() or an absolute path constructed from v0().
Definition: garg.cpp:248
static std::string tail(const std::string &in, std::string::size_type pos, const std::string &default_=std::string())
Returns the last part of the string after the given position.
Definition: gstr.cpp:1051
static void path(std::string &out, const std::string &base_dir, const std::string &name, G::EpochTime, Gr::ImageType type, bool fast, const Gv::Timezone &tz, bool test_mode)
Returns by reference the filesystem path corresponding to the given base directory and timestamp...
static EpochTime now()
Returns the current epoch time.
void saveTo(const std::string &base_dir, const std::string &name, bool fast, const Gv::Timezone &tz, bool test_mode=false)
Saves images to the filesystem inside a deeply-nested directory hierarchy with the given base directo...
bool fast() const
Returns the saveTo() fast flag.
static std::string escaped(const std::string &, char c_escape, const std::string &specials_in, const std::string &specials_out)
Returns the escape()d string.
Definition: gstr.cpp:41
~ImageOutput()
Destructor.
static bool mkdirs(const Path &dir, const NoThrow &, int=100)
Creates a directory and all necessary parents.
Definition: gfile.cpp:172
static std::string fromUInt(unsigned int ui)
Converts unsigned int 'ui' to a string.
Definition: gstr.cpp:315
int dy() const
Returns the image height.
Definition: grimagetype.h:191
bool viewing() const
Returns true if startViewer() has been called.
static bool enabled()
Returns true if test features are enabled.
Definition: gtest.cpp:49
ImageOutput()
Constructor.
Path dirname() const
Returns the path without the rightmost part, ignoring "." parts.
Definition: gpath.cpp:318
void pingViewerCheck()
Checks that the viewer is running if it should be (see pingViewer()), and throws an exeception if not...
A variant class holding a string, an item map keyed by name, or an ordered list of items...
Definition: gitem.h:41
An overload discriminator class for File methods.
Definition: gfile.h:53
A broadcast communication channel between unrelated processes using shared memory.
Definition: gpublisher.h:67
int dx() const
Returns the image width.
Definition: grimagetype.h:185
void startPublisher(const std::string &channel)
Starts the publisher channel.
bool pingViewer()
Returns true if the viewer seems to be running.
void add(const std::string &s)
Adds a string item to this list item.
Definition: gitem.cpp:108
A timer class template in which the timeout is delivered to the specified method. ...
Definition: gtimer.h:110
A small-string class used for stringised Gr::ImageType instances.
Definition: grimagetype.h:46
bool has(const std::string &k) const
Returns true if this map item has the named key.
Definition: gitem.cpp:146
static std::string v0()
Returns a copy of argv[0] from the first call to the constructor that takes argc/argv.
Definition: garg.cpp:88
A one-way, unreliable-datagram communication channel from a parent process to a child process...
Definition: gfatpipe.h:78
A Path object represents a file system path.
Definition: gpath.h:72
static std::string meta()
Returns a list of shell meta-characters, typically used with escape().
Definition: gstr.cpp:1032
void sendText(const char *data, size_t size, const std::string &type)
Emits a non-image message. Non-image messages are not saveTo()d the filesystem.