HDK
 All Classes Namespaces Files Functions Variables Typedefs Enumerations Enumerator Friends Macros Groups Pages
FS_HomeHelper.C
Go to the documentation of this file.
1 /*
2  * Copyright (c) 2024
3  * Side Effects Software Inc. All rights reserved.
4  *
5  * Redistribution and use of Houdini Development Kit samples in source and
6  * binary forms, with or without modification, are permitted provided that the
7  * following conditions are met:
8  * 1. Redistributions of source code must retain the above copyright notice,
9  * this list of conditions and the following disclaimer.
10  * 2. The name of Side Effects Software may not be used to endorse or
11  * promote products derived from this software without specific prior
12  * written permission.
13  *
14  * THIS SOFTWARE IS PROVIDED BY SIDE EFFECTS SOFTWARE `AS IS' AND ANY EXPRESS
15  * OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
16  * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN
17  * NO EVENT SHALL SIDE EFFECTS SOFTWARE BE LIABLE FOR ANY DIRECT, INDIRECT,
18  * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
19  * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA,
20  * OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
21  * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
22  * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE,
23  * EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
24  *
25  *----------------------------------------------------------------------------
26  */
27 
28 #include "FS_HomeHelper.h"
29 
30 #include <UT/UT_Assert.h>
31 #include <UT/UT_DirUtil.h>
32 #include <UT/UT_DSOVersion.h>
33 #include <UT/UT_EnvControl.h>
34 #include <UT/UT_FileUtil.h>
35 #include <UT/UT_NTStreamUtil.h>
36 #include <UT/UT_OpUtils.h>
37 #include <UT/UT_SysClone.h>
38 #include <UT/UT_WorkArgs.h>
39 #include <UT/UT_WorkBuffer.h>
40 #include <FS/FS_WriterStream.h>
41 
42 #define HOME_SIGNATURE "home:"
43 #define HOME_SIGNATURE_LEN 5
44 
45 // NB: The compiled dso needs to be installed somewhere in $HOUDINI_DSO_PATH
46 // under the 'fs' subdirectory. For example: $HOME/houdiniX.Y/dso/fs.
47 
48 void
50 {
54 }
55 
56 using namespace HDK_Sample;
57 
58 // ============================================================================
59 // The FS_HOMEREADER_HANDLE_OPTIONS define illustrates how to implement a file
60 // system protocol that uses '?' character in the file paths as a special marker
61 // for its own use, which conflicts with the default interpretation of '?' by
62 // FS library of a section name separator for index files.
63 // For more info, please see FS_Reader.h
64 //
65 // When FS_HOMEREADER_HANDLE_OPTIONS is not defined, the 'home:' protocol
66 // implements a much simpler mechanism that limits itself to replacing
67 // "home:" prefix with the home path. And since that simpified version does not
68 // assign any special meaning to '?' (nor '&'), it does not need to worry about
69 // explicitly handling index files (which is then handled automatically by the
70 // base classes).
71 //
72 // When FS_HOMEREADER_HANDLE_OPTIONS is defined, the 'home:' protocol
73 // implemented in this example uses '?' and '&' markers for delineating file
74 // access options. With '?' redefined as options separator, this protocol
75 // needs to establish a new convention for representing a path to an index file
76 // section (unless the protocol intentionally does not need or does not want to
77 // support direct access to index file sections). However, this example shows
78 // how this can be done, by designating the option "section=" for specifying the
79 // section name inside the index file given by the path portion before the '?'
80 // delimeter.
81 //
82 //
83 // With FS_HOMEREADER_HANDLE_OPTIONS defined, The 'home:' protocol recognizes
84 // the following options:
85 // "version" - to append the given version to the file name
86 // "ext" - to append the given extension to the file name
87 // "section" - to specify the section name in the given index file
88 // E.g.,
89 // "home:/temp?version=1_2&ext=txt" --> "~/temp-1_2.ext"
90 // "home:/temp?section=foo&ext=idx" --> "~/temp.idx?foo"
91 // "home:/temp.idx?section=foo" --> "~/temp.idx?foo"
92 // "home:/no_options.txt" --> "~/no_options.txt"
93 //
94 #define FS_HOMEREADER_HANDLE_OPTIONS
95 
96 #ifdef FS_HOMEREADER_HANDLE_OPTIONS
97 static inline bool
98 fsFindLastOption(const char *option_name, const UT_WorkArgs &options,
99  UT_String *option_value = NULL, int *option_index = NULL)
100 {
101  UT_String option_tag;
102 
103  option_tag = option_name;
104  option_tag += "=";
105 
106  if( option_value )
107  option_value->clear();
108  if( option_index )
109  *option_index = -1;
110  for( int i = options.getArgc()-1; i >= 0; i-- )
111  {
112  UT_String token( options(i) );
113 
114  if( token.startsWith( option_tag ))
115  {
116  if( option_value )
117  token.substr( *option_value, option_tag.length() );
118  if( option_index )
119  *option_index = i;
120  return true;
121  }
122  }
123 
124  return false;
125 }
126 
127 static inline void
128 fsGetFileAndOptions( const char *source,
129  UT_String &file_str, UT_String &options_str )
130 {
131  UT_String source_str( source );
132  UT_WorkArgs list;
133  int count;
134 
135  source_str.tokenize( list, '?' );
136  count = list.getArgc();
137  UT_ASSERT( 0 <= count && count <= 2 );
138  file_str.harden( count > 0 ? list(0) : "" );
139  options_str.harden( count > 1 ? list(1) : "" );
140 }
141 
142 static inline void
143 fsAppendOptionsExceptForSkipped( UT_WorkBuffer &buff,
144  const UT_WorkArgs &options, int skip_index )
145 {
146  bool do_questionmark = true;
147  bool do_ampersand = false;
148  for( int i = 0; i < options.getArgc(); i++ )
149  {
150  if( i == skip_index )
151  continue;
152 
153  if( do_questionmark )
154  {
155  buff.append('?');
156  do_questionmark = false;
157  }
158 
159  if( do_ampersand )
160  buff.append('&');
161 
162  buff.append( options(i) );
163  do_ampersand = true;
164  }
165 }
166 
167 static inline bool
168 fsSplitPathIntoFileAndSection( const char *source,
169  UT_String &file_name, UT_String &section_name)
170 {
171  UT_String file_str, options_str;
172  UT_WorkArgs options;
173  int section_option_index;
174 
175  // Parse the source path and look for 'section' option, but search the list
176  // backwards (ie, get the last section option), which handles nested index
177  // files in similar way as the standard index file notation (ie,
178  // "home:x?section=a&section=b" is equivalent to "$HOME/x?a?b").
179  fsGetFileAndOptions( source, file_str, options_str );
180  options_str.tokenize( options, '&' );
181  fsFindLastOption( "section", options, &section_name, &section_option_index);
182 
183  // Reconstruct the source path, but without the section name, which we
184  // just extracted from the options. This will allow accessing the index file
185  // stream and then forming the substream for that section.
186  UT_WorkBuffer buff;
187  buff.append( file_str );
188  fsAppendOptionsExceptForSkipped( buff, options, section_option_index );
189  buff.copyIntoString( file_name );
190 
191  return section_name.isstring();
192 }
193 
194 static inline void
195 fsCombineFileAndSectionIntoPath( UT_String &source,
196  const char *file_name, const char *section_name)
197 {
199  UT_String file_name_str( file_name );
200  char separator;
201 
202  // Figure out the separator: '?' if no options yet, or '&' if there are.
203  UT_ASSERT( UTisstring( file_name ) && UTisstring( section_name ));
204  separator = file_name_str.findChar('?') ? '&' : '?';
205 
206  // Copy the base file name and append the section option.
207  buffer.strcpy( file_name );
208  buffer.append( separator );
209  buffer.append( "section=" );
210  buffer.append( section_name );
211 
212  buffer.copyIntoString( source );
213 }
214 
215 static inline void
216 fsStripSectionOptions( UT_String &path, UT_StringArray &sections )
217 {
218  UT_String section_name;
219 
220  // Note, we can arbitrarily choose whether to put the innermost sections
221  // at the beginning or the end of 'sections' array, as long as we handle
222  // this order correctly in fsAppendSectionNames().
223  // However, just to be consistent with the convention used by
224  // UT_OpUtils::splitOpIndexFileSectionPath() and
225  // FS_Reader::splitIndexFileSectionPath() we put innermost sections
226  // at the end (in case we ever need to call these static helper functions).
227  while( fsSplitPathIntoFileAndSection( path, path, section_name ))
228  sections.insert( section_name, /*index=*/ 0 );
229 }
230 
231 static inline void
232 fsAppendSectionNames( UT_String &path, UT_StringArray &sections )
233 {
234  // Construct section names using the standard convention for index files
235  // (ie, using '?' as name separator) so that the default writer can
236  // understand the path.
237  // Note, fsStripSectionOptions() puts innermost sections at the end
238  // of the array, so we iterate forward to indeed get the last entry
239  // in the array to be the innermost section.
240  for( int i = 0; i < sections.entries(); i++ )
242  sections(i) );
243 }
244 
245 static inline void
246 fsAppendFileSuffix( UT_String &str,
247  const UT_WorkArgs &options, const char *option_name,
248  const char *separator )
249 {
250  UT_String option_value;
251 
252  if( fsFindLastOption( option_name, options, &option_value ))
253  {
254  str += separator;
255  str += option_value;
256  }
257 }
258 
259 static inline void
260 fsProcessNonSectionOptions( UT_String &source )
261 {
262  UT_String options_str;
263  UT_WorkArgs options;
264 
265  // We follow the description of the options in the comment next to the
266  // FS_HOMEREADER_HANDLE_OPTIONS define.
267  // Split the source path into file and options, and then modify the
268  // final file path according to the options.
269  // Note, there should not be any 'section' option at this point anymore,
270  // since it should have been alrwady stripped in
271  // splitIndexFileSectionPath().
272  fsGetFileAndOptions( source, source, options_str );
273  options_str.tokenize( options, '&' );
274  UT_ASSERT( ! fsFindLastOption( "section", options ));
275  fsAppendFileSuffix( source, options, "version", "-" );
276  fsAppendFileSuffix( source, options, "ext", "." );
277 }
278 
279 #endif // FS_HOMEREADER_HANDLE_OPTIONS
280 
281 // ============================================================================
282 static inline void
283 fsPrefixPathWithHome(UT_String &path)
284 {
285  UT_WorkBuffer buff;
286 
287  // Substitute 'home:' prefix with home path.
288  UT_ASSERT( path.length() >= HOME_SIGNATURE_LEN );
290  if( path(HOME_SIGNATURE_LEN) != '/' )
291  buff.append('/');
292  buff.append( &path.buffer()[HOME_SIGNATURE_LEN] );
293 
294  buff.copyIntoString(path);
295 }
296 
297 static bool
298 fsConvertToStandardPathForWrite(UT_String &destpath, const char *srcpath)
299 {
300  // Handle only the 'home:' protocol paths.
301  if( strncmp(srcpath, HOME_SIGNATURE, HOME_SIGNATURE_LEN) != 0 )
302  return false;
303 
304  destpath = srcpath;
305 #ifdef FS_HOMEREADER_HANDLE_OPTIONS
306  // For writing, we convert the "section=" options for inex files in 'home:'
307  // protocol into the standard notation because the FS_WriterStream class,
308  // used by FS_HomeWriteHelper, can write to index files only when
309  // specified in that standard notation.
310  // Alternatively, we could derive own class from FS_WriterStream and handle
311  // writing to index files there. However, since 'home:' protocol really
312  // refers to the disk files, we take advantage of the FS_WriterStream
313  // base class capabilities.
314  UT_StringArray sections;
315  fsStripSectionOptions( destpath, sections );
316  fsProcessNonSectionOptions( destpath );
317  fsAppendSectionNames( destpath, sections );
318 #endif
319 
320  fsPrefixPathWithHome(destpath);
321  return true;
322 }
323 
324 static bool
325 fsConvertToStandardPathForRead(UT_String &destpath, const char *srcpath)
326 {
327  // Handle only the 'home:' protocol paths.
328  if( strncmp(srcpath, HOME_SIGNATURE, HOME_SIGNATURE_LEN) != 0 )
329  return false;
330 
331  destpath = srcpath;
332 #ifdef FS_HOMEREADER_HANDLE_OPTIONS
333  // For read, no need to handle index file sections, since there should not
334  // be any in the 'srcpath' because they were stripped in
335  // splitIndexFileSectionPath(). So for reading only, there is no need to
336  // implement some of the static functions that are needed only for writing.
337  // In particular, there is no need for fsAppendSectionNames() which builds
338  // the file path and appends section names using standard '?' notation.
339  fsProcessNonSectionOptions( destpath );
340 #endif
341 
342  fsPrefixPathWithHome(destpath);
343  return true;
344 }
345 
346 static bool
347 fsConvertToStandardPathForInfo(UT_String &destpath, const char *srcpath)
348 {
349 #ifdef FS_HOMEREADER_HANDLE_OPTIONS
350  // For info, the FS_Info class already handles index files implicitly
351  // (via FS_ReadHelper), and calls this info helper class only for non-index
352  // files. So here, the srcpath should not be pointing to any index file
353  // section, just like in the case of fsConvertToStandardPathForRead().
354  // So call the conversion function for reading.
355 #endif
356  return fsConvertToStandardPathForRead(destpath, srcpath);
357 }
358 
359 // ============================================================================
361 {
363 }
364 
366 {
367 }
368 
370 FS_HomeReadHelper::createStream(const char *source, const UT_Options *)
371 {
372  FS_ReaderStream *is = 0;
373  UT_String homepath;
374 
375  if( fsConvertToStandardPathForRead(homepath, source) )
376  is = new FS_ReaderStream(homepath);
377 
378  return is;
379 }
380 
381 bool
382 FS_HomeReadHelper::splitIndexFileSectionPath(const char *source_section_path,
383  UT_String &index_file_path,
384  UT_String &section_name)
385 {
386 #ifdef FS_HOMEREADER_HANDLE_OPTIONS
387  // We should be splitting only our own paths.
388  if( !source_section_path ||
389  strncmp(source_section_path, HOME_SIGNATURE, HOME_SIGNATURE_LEN) != 0 )
390  return false;
391 
392  // If the index file section sparator (ie, the question mark '?') has
393  // a special meaning in "home:" protocol, then this virtual overload is
394  // necessary to avoid the default parsing of source path when creating a
395  // stream. See the base class header comments for more details.
396  // Here, '?' separates the file path from file access options such as
397  // file version. Eg, in this example "home:myfile?version=1.2" file version
398  // is simply concatenated to the file name to form "myfile-1.2", but
399  // in principle it could use any other mechanism (like retrieveing it
400  // from a revision system or a database, etc).
401  // To support the index files the protocol has a 'section' option,
402  // "home:myfile?section=mysection", which allows accessing the section
403  // 'mysection' inside 'myfile' (equivalent to "$HOME/myfile?mysection"),
404  // or "home:myfile?version=1.2&section=mysection" (equivalent to
405  // "$HOME/myfile-1.2?mysection").
406  fsSplitPathIntoFileAndSection( source_section_path,
407  index_file_path, section_name );
408 
409  // Return true to indicate we are handling own spltting of section paths,
410  // whether or not the given path actually refers to an index file section.
411  return true;
412 #else
413  // If we are not handling '?' in any special way, then we return false.
414  // We could also remove this method althogehter from this class and
415  // rely on the base class for the default behaviour.
416  return false;
417 #endif // FS_HOMEREADER_HANDLE_OPTIONS
418 }
419 
420 bool
422  const char *index_file_path,
423  const char *section_name)
424 {
425 #ifdef FS_HOMEREADER_HANDLE_OPTIONS
426  // We should be combining only our own paths.
427  if( strncmp(index_file_path, HOME_SIGNATURE, HOME_SIGNATURE_LEN) != 0 )
428  return false;
429 
430  // See the comments in splitIndexFileSectionPath()
431  fsCombineFileAndSectionIntoPath( source_section_path,
432  index_file_path, section_name );
433 
434  // Return true to indicate we are handling own gluing of section paths.
435  return true;
436 #else
437  // If we are not handling '?' in any special way, then we return false.
438  // We could also remove this method althogehter from this class and
439  // rely on the base class for the default behaviour.
440  return false;
441 #endif // FS_HOMEREADER_HANDLE_OPTIONS
442 }
443 
444 
445 // ============================================================================
447 {
449 }
450 
452 {
453 }
454 
457 {
458  FS_WriterStream *os = 0;
459  UT_String homepath;
460 
461  if( fsConvertToStandardPathForWrite(homepath, source) )
462  os = new FS_WriterStream(homepath);
463 
464  return os;
465 }
466 
467 bool
469 {
470  // Same as FS_HomeInfoHelper::canHandle()
471  return (strncmp(source, HOME_SIGNATURE, HOME_SIGNATURE_LEN) == 0);
472 }
473 
474 bool
476  const char *source, mode_t mode, bool ignore_umask)
477 {
478  UT_String homepath;
479  if (!fsConvertToStandardPathForWrite(homepath, source))
480  return false;
481 
482  return UT_FileUtil::makeDirs(homepath, mode, ignore_umask);
483 }
484 
485 // ============================================================================
487 {
489 }
490 
492 {
493 }
494 
495 bool
496 FS_HomeInfoHelper::canHandle(const char *source)
497 {
498  return (strncmp(source, HOME_SIGNATURE, HOME_SIGNATURE_LEN) == 0);
499 }
500 
501 bool
502 FS_HomeInfoHelper::hasAccess(const char *source, int mode)
503 {
504  UT_String homepath;
505 
506  if( fsConvertToStandardPathForInfo(homepath, source) )
507  {
508  FS_Info info(homepath);
509 
510  return info.hasAccess(mode);
511  }
512 
513  return false;
514 }
515 
516 bool
518 {
519  UT_String homepath;
520 
521  if( fsConvertToStandardPathForInfo(homepath, source) )
522  {
523  FS_Info info(homepath);
524 
525  return info.getIsDirectory();
526  }
527 
528  return false;
529 }
530 
531 time_t
532 FS_HomeInfoHelper::getModTime(const char *source)
533 {
534  UT_String homepath;
535 
536  if( fsConvertToStandardPathForInfo(homepath, source) )
537  {
538  FS_Info info(homepath);
539 
540  return info.getModTime();
541  }
542 
543  return 0;
544 }
545 
546 int64
547 FS_HomeInfoHelper::getSize(const char *source)
548 {
549  UT_String homepath;
550 
551  if( fsConvertToStandardPathForInfo(homepath, source) )
552  {
553  FS_Info info(homepath);
554 
555  return info.getFileDataSize();
556  }
557 
558  return 0;
559 }
560 
561 UT_String
563 {
564  UT_String homepath;
565 
566  if( fsConvertToStandardPathForInfo(homepath, source) )
567  {
568 #ifdef FS_HOMEREADER_HANDLE_OPTIONS
569  UT_String filename_str;
570  UT_String options_str;
571  UT_String option_value;
572  UT_WorkArgs options;
573 
574  fsGetFileAndOptions( source, filename_str, options_str );
575  options_str.tokenize( options, '&' );
576  if( fsFindLastOption( "ext", options, &option_value ))
577  {
579  extension += option_value;
580  return extension;
581  }
582 #endif
583  return FS_InfoHelper::getExtension(homepath);
584  }
585 
586  return FS_InfoHelper::getExtension(source);
587 }
588 
589 bool
591  UT_StringArray &contents,
592  UT_StringArray *dirs)
593 {
594  UT_String homepath;
595 
596  if( fsConvertToStandardPathForInfo(homepath, source) )
597  {
598  FS_Info info(homepath);
599 
600  return info.getContents(contents, dirs);
601  }
602 
603  return false;
604 }
605 
bool canHandle(const char *source) override
Determine whether this helper can process the filename.
bool makeDirectory(const char *source, mode_t mode=0777, bool ignore_umask=false) override
Make a directory and all the parent directories needed.
UT_String getExtension(const char *source) override
static const char * getString(UT_StrControl i)
bool hasAccess(int mode=0) const
bool splitIndexFileSectionPath(const char *source_section_path, UT_String &index_file_path, UT_String &section_name) override
bool getIsDirectory(const char *source) override
Return whether the filename is a directory.
bool getIsDirectory() const
Returns if the path is a directory.
GLsizei const GLchar *const * path
Definition: glcorearb.h:3341
FS_ReaderStream * createStream(const char *source, const UT_Options *options) override
If the filename starts with "home:", open an FS_ReaderStream.
SYS_FORCE_INLINE void strcpy(const char *src)
void copyIntoString(UT_String &str) const
int64 getFileDataSize() const
Returns the file size.
void installFSHelpers()
Definition: FS_HomeHelper.C:49
GLuint buffer
Definition: glcorearb.h:660
void clear()
Reset the string to the default constructor.
Definition: UT_String.h:311
unsigned length() const
Return length of string.
Definition: UT_String.h:546
const char * buffer() const
Definition: UT_String.h:509
bool combineIndexFileSectionPath(UT_String &source_section_path, const char *index_file_path, const char *section_name) override
Class to open a file as a write stream. The class tests for a "home:" prefix and replaces it with $HO...
Definition: FS_HomeHelper.h:85
time_t getModTime(const char *source) override
Get the modification timestamp (returns time_t)
bool getContents(UT_StringArray &contents, UT_StringArray *dirs=0, UT_Array< FS_Stat > *stats=0, UT_Array< FS_Stat > *dir_stats=0)
int tokenize(char *argv[], int max_args, char separator)
Definition: UT_String.h:842
bool canMakeDirectory(const char *source) override
Whether this helper supports the given source path for makeDirectory().
GLsizei GLsizei GLchar * source
Definition: glcorearb.h:803
void harden()
Take shallow copy and make it deep.
Definition: UT_String.h:215
long long int64
Definition: SYS_Types.h:116
Class to stat a file. The class tests for a "home:" prefix and replaces it with $HOME.
int getArgc() const
Definition: UT_WorkArgs.h:34
GLenum mode
Definition: glcorearb.h:99
int64 getSize(const char *source) override
Get the file size in bytes.
exint entries() const
Alias of size(). size() is preferred.
Definition: UT_Array.h:648
UT_API void UTaddAbsolutePathPrefix(const char *prefix)
bool hasAccess(const char *source, int mode) override
time_t getModTime() const
A map of string to various well defined value types.
Definition: UT_Options.h:84
int substr(UT_String &buf, int index, int len=0) const
virtual UT_String getExtension(const char *source)
Definition: FS_Info.h:245
SYS_FORCE_INLINE bool UTisstring(const char *s)
SYS_FORCE_INLINE void append(char character)
#define HOME_SIGNATURE_LEN
Definition: FS_HomeHelper.C:43
Class to open a file as a read stream. The class tests for a "home:" prefix and replaces it with $HOM...
Definition: FS_HomeHelper.h:58
bool isstring() const
Definition: UT_String.h:691
FS_WriterStream * createStream(const char *source) override
If the filename begins with "home:" return a new write stream.
#define UT_ASSERT(ZZ)
Definition: UT_Assert.h:156
bool startsWith(const UT_StringView &prefix, bool case_sensitive=true) const
Class for retrieving file information.
Definition: FS_Info.h:99
exint insert(exint index)
Definition: UT_ArrayImpl.h:721
static bool makeDirs(const char *path, mode_t mode=0777, bool ignore_umask=false)
OIIO_UTIL_API std::string extension(string_view filepath, bool include_dot=true) noexcept
GLint GLsizei count
Definition: glcorearb.h:405
#define HOME_SIGNATURE
Definition: FS_HomeHelper.C:42
bool getContents(const char *source, UT_StringArray &contents, UT_StringArray *dirs) override
static void combineStandardIndexFileSectionPath(UT_String &source_section_path, const char *index_file_path, const char *section_name)