@c AnsiCmd Library User's Guide  - - - - - - - - - - - - - - - - - - - - - - - -
@c 10.0 TMFTest Demo App - - - - - - - - - - - - - - - - - - - - - - - - - - - -
@c      Exercise the TFMan class(Temp FIle Manager)  - - - - - - - - - - - - - -
@node TFMTest Demo App
@unnumbered TFMTest Demo App
@cindex 10.0.00 TFMTest Demo App

@b{@t{'TFMTest' (tfmtest)}} is a simple test and demo application 
supporting the TFMan (@b{T}emporary @b{F}ile @b{Man}agement) class.

@menu
* The TFMTest Application::   Testing platform for TFMan class
* TFMan Class::               Description of public methods
@end menu

@c - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
@lowersections
@c - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
@c Chapter 10.01 The TFMTest Application - - - - - - - - - - - - - - - - - - - -
@c               Testing platform for TFMan class  - - - - - - - - - - - - - - -
@c - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
@node The TFMTest Application
@unnumbered The TFMTest Application
@cindex 10.01 The TFMTest Application
@cindex temporary files
@cindex TFMTest app

@t{TFMTest (tfmtest)} demonstrates the use of the @t{TFMan} class, which 
encapsulates creation and management of any temporary files required 
by an application.@*
The next chapter, @ref{TFMan Class} describes the class itself.

@itemize @EMDASH
@item @t{TFMTest} is a stand-alone utility designed for stress-testing 
the methods of the @t{TFMan class}.
@item This application @b{@emph{does not}} use the AnsiCmd library. 
Output is written directly to @t{stdout}.
@item Each of the public methods of the TFMan class is exercised 
by this application.
@item Text analysis and formatting within the application is done through 
the author's gString class.@*
@xref{gString Text Tool} for more information.
@item Support for C++17 (g++ v:8 and higher) is required.@*
Please @pxref{Building TFMTest} below for details on building the application.
@end itemize
@sp 1
@hr20

@anchor{Invoking TFMTest}
@cindex invoking TFMTest
@H5heading{Invoking TFMTest,▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔}

@t{TFMTest ('tfmtest')} requires one option, either @b{@t{--basic}} or 
@b{@t{--full}}. Other options are optional :-).

@itemize @SDIAMOND
@item @b{@t{--basic}}@*
Perform basic tests of the temporary-file system. This ensures that the 
application and the @t{TFMan} class are functioning and communicating 
with the system as expected.

@item @b{@t{--full}}@*
First, the sequence of basic tests is performed, and then additional 
tests are performed to ensure that all public methods of the @t{TFMan} 
class are functional and that the C-standard library functions are 
interacting with the system as expected.

@item @b{@t{--verbose}}@*
Output verbose diagnostics for each non-trivial test.@*
Significant redundancy is built into these tests to ensure that the 
system is not lying to us. Verbose output simply reports the information 
generated by this redundancy, for example, the timestamps of the files 
which are created.

@item @b{@t{--redirect}}@*
The application writes directly to @t{stdout} by default; however, the output 
may be rediected (piped) to the @t{'less'} utility so that the data may 
be scrolled for easy viewing.

This redirection is not actually necessary because the full, verbose 
report only requires approximately 25 lines, and should fit comfortably in 
a typical terminal window. The pipe mechanism is used here to fulfill 
a student request for a simple example of how pipes work. @w{Enjoy, Sarah!}

@item @b{@t{--help}} (or @b{@t{-h}})@*
List the available command-line options, (this list).

@item @b{@t{--version}}@*
Report the application version number and copyright notice.@*
The report also includes the version numbers of the @t{TFMan} class and 
the @t{gString} class.
@end itemize
@sp 1
@hr20

@anchor{Building TFMTest}
@H5heading{Building TFMTest,▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔}

@verbatim
Development Tools:
------------------
   g++ (GCC) 14.3.1 20251022 (C++17)
   Fedora Linux v:42
   GNOME Terminal v:3.54.5 (GNOME 47)
@end verbatim

@enumerate 1
@item Open a terminal window.@*
The @t{TFMTest} application source is located in a subdirectory of the 
@t{AnsiCmd} library distribution archive. Download the archive, open a 
terminal window and navigate to the directory containing the archive.@*
Example:@*
@b{@verb{|    cd ~/sam/Documents/Software|}}@*
Unpack the archive:@* 
@b{@verb{|    tar -xjvf ansicmd-x.x.xx.tar.bz2|}}

@item Navigate to the directory containing the TFMTest source.@*
@b{@verb{|    cd AnsiCmd/TFMTest|}}

@item The directory should contain two files:@*
@verb{|    TFMTest.cpp  // Application source code.|}@*
@verb{|    Makefile     // Build the TFMTest application|}@*
@b{@verb{|    ls -l|}}@*
Additional files will be copied from the parent directory during 
the build sequence.

@item Be sure that your compiler version is 8.0 or greater (or Clang v:5).
@b{@verb{|    g++ --version|}}@*
@verb{|    g++ (GCC) 14.3.1 20251022 (Red Hat 14.3.1-4)|}

@item Build the @t{'tfmtest'} application.@*
@b{@verb{|    gmake all|}}@*
Three files will be copied from the parent directory:@*
@verb{|    TFMan.hpp|}@*
@verb{|    gString.cpp|}@*
@verb{|    gString.hpp|}

The @t{TFMTest and gString} modules will be compiled and then 
linked to create the @t{'tfmtest'} executable.

The build should be clean with no errors and no warnings.
If there are errors, then the most likely cause would be that the 
compiler cannot find a necessary header file or library.
Check your LIB_PATH and other environment variables.

@item Once you have a clean build, invoke the application with a 
request for version number.@*
@b{@verb{|    ./tfmtest --version|}}

@enumerate a
@item If the application successfully reports the version number and 
copyright notice, then the application has been built correctly.
@item If the application does not run, then verify that all necessary libraries 
are installed on your system. Run the 'ldd' command to list the 
necessary libraries. The needed libraries will look something like 
the following. These are all standard libraries which should be 
installed on your system by default.

@verbatim
[TFMan]$ ldd tfmtest
   linux-vdso.so.1 (0x00007f9ca33f5000)
   libstdc++.so.6 => /lib64/libstdc++.so.6 (0x00007f9ca3000000)
   libm.so.6 => /lib64/libm.so.6 (0x00007f9ca32ef000)
   libgcc_s.so.1 => /lib64/libgcc_s.so.1 (0x00007f9ca32c0000)
   libc.so.6 => /lib64/libc.so.6 (0x00007f9ca2e0d000)
   /lib64/ld-linux-x86-64.so.2 (0x00007f9ca33f7000)
@end verbatim
@end enumerate
@end enumerate
@sp 2

@c - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
@c Chapter 10.02 TFMan Class - - - - - - - - - - - - - - - - - - - - - - - - - -
@c               Description of public methods - - - - - - - - - - - - - - - - -
@c - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
@node TFMan Class
@unnumbered TFMan Class
@cindex 10.02 TFMan Class
@cindex temporary files

The @b{@t{TFMan}} (@b{T}emporary @b{F}ile @b{Man}agement) class is desigined 
to fascilitate creation, deletion and general management of temporary files 
used within an application.

Temporary files created by the system are defined as uniquely named files 
created within the system's shared temporary-file directory. Standard 
library functions are available for creating these files, but the author's 
experience in the classroom setting has shown that application management 
of these files can be haphazard (to put it politely). The @t{TFMan class} 
was created to inject a bit of organization into this chaos.

@H5heading{System Interface,▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔}

The system interface functions of the C++ standard library used by the TFMan 
class require C++17 support. Be sure that your compiler version is 8.0 or 
greater (or Clang v:5).

These functions are available in C++17 (released Jan. 2018):@*
@verb{|   const char* fs::temp_directory_path ( void ) ;|{@*
@verb{|   const char* fs::temp_directory_path ( std::error_code& ec ) ;|}@*

These functions return the filespec of a directory suitable for temporary 
files. The path is guaranteed to exist and to be a directory. The overload 
that takes error_code& argument returns an empty path on error. 
 
On POSIX systems, the path may be specified in an environment variable:
@t{TMPDIR, TMP, TEMP, TEMPDIR,} and, if none of them are specified, 
the path @t{"/tmp"} is returned by the system call. 

Please visit: @t{<http://en.cppreference.com>} for more information on 
the library functions.

In the author's FileMangler application, which was developed well before 
C++17 was released, we implemented a @emph{literal} interpretaton of the above 
description. Now that the development community has had time to upgrade 
their compilers, (gcc v:8 and above) we are able to use the library function 
to obtain the temp-directory path. 

Note that the TFMan class does not accept the system's assurances that the 
target exists and is a directory. That information, including user's 
read/write access, is independently verified.
@hr40

@H5heading{Requesting a unique hack-resistant filename for a temporary file,
           ▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔}
The library function:@*
@verb{|   char * mkdtemp (char *TEMPLATE);|}@*
is used to create a subdirectory within the system temp-file directory. 
This function returns the uniquely-named directory in which all other 
application temporary files will be located.@*
This makes cleanup of temporary file during application exit as simple as 
calling the TFMan destructor. 

@enumerate a
@item The 'mkdtemp' function returns a random sequence of characters for the 
filename: Example: @t{"sakJhr"} 
@item A constant prefix may be attached to the provided character sequence to 
enable easy identification of the subdirectory during application 
development. Example: @t{"ANSICMD_sakJhr"}. 
@item An alternate prefix string may be specified using the @t{'prefix'} 
parameter. Example prefix: @t{"SAM"} would yield the subdirectory name: 
@t{"SAMsakJhr".} 
@end enumerate

The library function used to create the individual temp files is:@*
@verb{|   int mkstemp (char *TEMPLATE);|}
 
Note that both @t{mkdtemp} and @t{mkstemp} are @t{"atomic"}, that is, 
file creation by one process may not be interrupted by another process. 
 
@b{Technical Note:} It is assumed that only non-special filename characters 
will be used on the temp-file-path. "Special characters" are those which 
may be interpreted by the shell or other system functionality as requiring 
special handling.

@verbatim
List of Special Characters:
  '\'  '"'  ''' '?'  ':'  ';'  '&'  '>'  '<'  '|'  '*'  '`'  '$',
  '#'  '+'  '=' '%'  '!'  '~'  '{'  '}'  '@'  '('  ')'  '['  ']',  ' '
@end verbatim

These special character should not be used in filenames. The algorithm for 
escaping these characters is slow and probably not worth the 
complexity in this context.@*
Please see notes in the author's FileMangler application. FMgr-class on 
using @t{'rmdir'} and @t{'unlink'} on filenames which contain @t{"special"} 
characters. 

Within the application's temp-file subdirectory, temporary files take the 
form: "TF_xxxxxx" where "TF_" is the specified prefix and the "xxxxxx" 
sequence is replaced by the unique system-provided sequence.
@hr40

@Subsubheading{Public Methods of the TFMan Class,
               .................................}

@t{TFMan} is a very simple class with the following public methods.

@itemize @MCIRCLE
@item @b{TFMan ( void );}
@verbatim
  Input  : none
  Returns: implicitly returns pointer to object
@end verbatim

Default constructor.@*
Create a uniquely named temp-file subdirectory within the system's 
temp-file directory. (Prefix for temp-file subdirectory name is 
set to default value: "TFMAN_".)

Example tree structure: @verb{|  /tmp/TFMAN_sakJhr|}@*
@spi

@item @b{TFMan ( const char* prefix );}
@verbatim
  Input  : 
     prefix : pointer to a prefix for temp-file subdirectory name.
              -- Valid filename characters only
              -- Maximum of 16 bytes including null terminator
  Returns: implicitly returns pointer to object
@end verbatim

Initialization constructor.@*
Create a uniquely named temp-file subdirectory within the system's 
temp-file directory using the provided prefix string for the subdirectory 
name.

Example:@*
@verb{|instance: TFMan* tfm = new TFMan( "MYAPP" );|}@*
@verb{|  yields: /tmp/MYAPPsakJhr|}@*
@spi

@item @b{~TFMan ( void );}
@verbatim
  Input  : none
  Returns: nothing
@end verbatim

Destructor.@*
Delete all temporary files located within the subdirectory created 
by the TFMan constructor, then delete the subdirectory itself.

The destructor is invoked in different ways, depending upon whether the  
TFMan object was created in @t{'stack'} (automatic) memory or from 
@t{'heap'} memory.

If the TFMan object is created on the @t{'stack'}, then the destructor 
is called implicitly when the object goes out-of-scope.

@smallexample
   @b{int doStuff ( void ) ;

   int main ( int argc, char* argv[], char* argenv[] )
   @{
      tfm = new TFMan ( "SAM_" ) ;

      doStuff () ;

      if ( tfm != NULL )
         delete tfm ;
   @}
   int doStuff ( void )
   @{
      TFMan tfm ( "SAM_" ) ;
      int status = 0 ;
      . . .
      return status ;
      // (the object goes out-of-scope here)
   @}}
@end smallexample
@hr20d

If the TFMan object is created in @t{'heap'} memory (dynamic allocation), 
then the object should be deleted explicity when it is no longer needed.

@smallexample
   @b{int doStuff ( void ) ;
   static TFMan *tfm = NULL ;

   int main ( int argc, char* argv[], char* argenv[] )
   @{
      tfm = new TFMan ( "SAM_" ) ;

      doStuff () ;

      if ( tfm != NULL )
         delete tfm ;
   @}}
@end smallexample

@ifnothtml
@example
┌─────────────────────────────────────────────────────────────┐
│ Teacher's Rant:                                             │
│ Note that the application's heap memory is automatically    │
│ returned to the system when the application exits; HOWEVER, │
│ it is sloppy programming, indeed to simply abandon objects  │
│ on the heap. Objects on the heap should always be deleted   │
│ explicity before exiting the application.                   │
└─────────────────────────────────────────────────────────────┘
@end example
@end ifnothtml
@ifhtml
@sp 1
@CartHtml{ @b{Teacher's Rant:}
Note that the application's heap memory is automatically returned to the system 
when the application exits; @i{HOWEVER}, it is sloppy programming, indeed to 
simply abandon objects on the heap. Objects on the heap should always be deleted 
explicity before exiting the application.}
@spi
@end ifhtml
@spi

@item @b{bool createTempFile ( gString& tmpPath );}

@verbatim
  Input  :
     tmpPath : (by reference) gString object to receive filespec
  Returns: 
     'true'  if file successfully created:
             (file exists, and contains no data)
     'false' if system call failed
@end verbatim

Create a uniquely-named temporary file.@*
The filename takes the form: @t{"TF_xxxxxx"} where @t{xxxxxx} is the 
six-character token which uniquely identifies the file created by the system.

@example
Example:
If the TFMan constructor has created the application subdirectory:
   /tmp/TFMAN_rQabnE
then the filespec of the temporary file would be of the form:
   /tmp/TFMAN_rQabnE/TF_saNAar
@end example

The full filespec is then returned to caller in @t{'tmpPath'}.
The application can then open the file in the usual way 
and write data to the (empty) file.@*

@example
ofstream ofs ( tmpPath.ustr(), ofstream::out | ofstream::app ) ;
if ( (ofs.is_open()) )
@{
   ofs << "Write some data to the temp file." << endl ;
   ofs.close() ;
@}
@end example

Notice that the gString object's UTF-8 (byte-oriented) text is used 
for the system call. Note also that because the file has been created 
by the call, the file may be opened either for @LDQ{}@b{append}@RDQ{}, 
or for @LDQ{}@b{trunc}@RDQ{} output.@*
Please @pxref{Data Access, gString Public Methods} for a discussion 
of the string data stored in the gString class.

Please see above for a description of how the system creates 
uniquely-named temporary files, or refer to the C standard library 
documentation for the @t{'mkstemp'} function.
@spi

@item @b{bool deleteTempFile ( const gString& tmpPath );}

@verbatim
  Input  :
     tmpPath : (by reference) filespec of file to be deleted
  Returns:
     'true'  if target file successfully deleted
     'false' if library call failed (invalid filespec)
@end verbatim

Delete a previously created temporary file.

@t{deleteTempFile} will delete the specified file @emph{ONLY} if 
the file resides within the temporary subdirectory created by the 
@t{TFMan} constructor.
@spi

@item @b{bool clearTempFiles ( void );}

@verbatim
  Input  : none
  Returns:
     'true'  if all target files successfully deleted
     'false' if unable to delete one or more target files
@end verbatim

Delete all files in the application's temp-file container subdirectory.@*
Only files created within the subdirectory created by the TFMan-class 
constructor are affected. The application-specific subdirectory created 
by the TFMan constructor @b{@emph{IS NOT}} deleted by this method.
@spi

@item @b{const char* getTempPath ( void );}

@verbatim
  Input  : none
  Returns: pointer to const buffer containing filespec
@end verbatim

Returns a pointer to the filespec of temp-file subdirectory created 
by the TFMan constructor. This is a read-only pointer!

This method may be used during application development to locate the 
application's temp files.
@spi

@item @b{bool isActive ( void );}

@verbatim
  Input  : none
  Returns:
     'true'  if temp-file manager initialized and functional
     'false' if initialization failed
@end verbatim

Report the active/inactive status of the TFMan object.@*
Although it is unlikely that the constructor will fail to create the 
target subdirectory, if the system call fails, the constructor has 
no way to report the error. This method may be used to verify that 
the TFMan temp-file manager is active and functional.

@verbatim
Example:
  gString gsFilespec ;
  TFMan* tfm = new TFMan( "MyApp" ) ;
  if ( (tfm->isActive()) )
  {
     tfm->createTempFile ( gsFilespec ) ;
     . . .
     tfm->deleteTempFile ( gsFilespec ) ;
  }
  delete tfm ;
@end verbatim
@spi

@item @b{const char* tfmVersion ( void );}
@verbatim
  Input  : none
  Returns: pointer to constant string of the form: "M.m.ss"
           indicating the major/minor/step versions
@end verbatim

Report the TFMan-class version number.

@verbatim
Example:
  TFMan* tfm = new TFMan( "SAM_" ) ;
  cout < "TFMan class version: " << (tfm->tfmVersion()) << endl ;
  . . .
  delete tfm ;

Output:
  TFMan class version: 0.0.02
@end verbatim

@end itemize
@spi

@c - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
@raisesections
@sp 2

