#charset "us-ascii"
#pragma once

/*
 *   Copyright (c) 2001, 2006 Michael J. Roberts
 *   
 *   This file is part of TADS 3.
 *   
 *   This header defines the File intrinsic class.  
 */


/* include our base class definition */
#include "systype.h"

/*
 *   File methods use the CharacterSet and ByteArray intrinsic
 *   classes, so include their headers to make sure they're available to
 *   File users.  
 */
#include "charset.h"
#include "bytearr.h"

/* if we're using File objects, we probably want FileName objects as well */
#include "filename.h"

/* ------------------------------------------------------------------------ */
/*
 *   File access modes.  These are used when calling the file open methods
 *   to specify how the file is to be accessed.
 */

/* 
 *   Read mode - the file is opened for reading (writing is not allowed).
 *   When opened in this mode, the file must exist, or a
 *   FileNotFoundException is thrown from the open method. 
 */
#define FileAccessRead            0x0001

/*
 *   Write mode - the file is opened for writing (reading is not allowed).
 *   When opened in this mode, if the file doesn't already exist, a new file
 *   is created; if the file does already exist, the existing data in the
 *   file are discarded (i.e., the file is truncated to zero length) on
 *   open. 
 */
#define FileAccessWrite           0x0002

/*
 *   Read/write mode, keeping existing contents - the file is opened for
 *   both reading and writing.  If the file does not exist, a new file is
 *   created.  If the file does already exist, the existing contents of the
 *   file are kept intact on open.  
 */
#define FileAccessReadWriteKeep   0x0003

/*
 *   Read/write mode, truncating existing contents - the file is opened for
 *   both reading and writing.  If the file does not exist, a new file is
 *   created.  If the file does already exist, the existing contents of the
 *   file are discarded (i.e., the file is truncated to zero length) on
 *   open.  
 */
#define FileAccessReadWriteTrunc  0x0004


/* ------------------------------------------------------------------------ */
/*
 *   File mode constants.  These are returned from getFileMode() to indicate
 *   the mode used to open the file.  
 */

/* text mode */
#define FileModeText  1

/* "data" mode */
#define FileModeData  2

/* "raw" mode */
#define FileModeRaw   3


/* ------------------------------------------------------------------------ */
/*
 *   Special file identifiers.  These identifiers can be passed to the 'open'
 *   routines in place of the filename string argument.
 *   
 *   The actual name and location of a special file is determined by the
 *   interpreter.  Since games use these internal identifiers rather than the
 *   actual system filenames when accessing special files, different
 *   interpreters can adapt to different local conventions without bothering
 *   the game code with the details.  The game code simply refers to the file
 *   it wants using the virtual identifier, and the interpreter takes care of
 *   the rest.
 *   
 *   Note that special files generally bypass the interpreter "file safety"
 *   settings.  This is important because it allows the library and games a
 *   degree of controlled access to the file system, even when the file
 *   safety settings wouldn't normally allow similar access for arbitrary
 *   file operations.  Even though this special file access can bypass the
 *   file safety level, it doesn't compromise security, because the
 *   interpreter has exclusive control over the names and locations of the
 *   special files - thus a game can only access the particular files that
 *   the interpreter designates as special, and can't use special files to
 *   access arbitrary file system entities.  
 */

/*
 *   The library defaults file.  This is the special file where the library
 *   stores user-controlled start-up default settings.  
 */
#define LibraryDefaultsFile    0x0001

/*
 *   Web UI preference settings file.  This is the special file where we
 *   store display style settings for the Web UI.  
 */
#define WebUIPrefsFile         0x0002


/* ------------------------------------------------------------------------ */
/*
 *   The File intrinsic class provides access to files in the external file
 *   system.  This lets you create, read, and write files.  The class
 *   supports text files (with translations to and from local character
 *   sets), "data" files (using the special TADS 2 binary file format), and
 *   "raw" files (this mode lets you manipulate files in arbitrary text or
 *   binary formats by giving you direct access to the raw bytes in the
 *   file).  
 */
intrinsic class File 'file/030003': Object
{
    /*
     *   File has no constructors.  Instead of using 'new', you create a File
     *   object by opening a file through one of the openXxxFile methods:
     *   
     *   f = File.openTextFile()
     *   
     *   All of the open methods have a 'filename' argument giving the name
     *   of the file to open.  This is usually a string giving a file name in
     *   the local file system.  You can also use a TemporaryFile object in
     *   place of a filename, to open a temporary file.
     *   
     *   All of the open methods throw exceptions if the open fails:
     *   
     *   FileNotFoundException - indicates that the requested file doesn't
     *   exist.  This is thrown when the access mode requires an existing
     *   file but the named file does not exist.
     *   
     *   FileCreationException - indicates that the requested file couldn't
     *   be created.  This is thrown when the access mode requires creating a
     *   new file but the named file cannot be created.
     *   
     *   FileOpenException - indicates that the requested file couldn't be
     *   opened.  This is thrown when the access mode allows either an
     *   existing file to be opened or a new file to be created, but neither
     *   could be accomplished.
     *   
     *   FileSafetyException - the requested access mode isn't allowed for
     *   the given file due to the current file safety level set by the user.
     *   Users can set the file safety level (through command-line switches
     *   or other preference mechanisms which vary by interpreter) to
     *   restrict the types of file operations that applications are allowed
     *   to perform, in order to protect their systems from malicious
     *   programs.  This exception indicates that the user has set a safety
     *   level that is too restrictive for the requested operation.  
     */

    /*
     *   Static creator method: open a text file.  Returns a File object that
     *   can be used to read or write the file.
     *   
     *   'filename' is the name of the file to open.  This is a string giving
     *   the name of a file in the local file system.  It can alternatively
     *   be a TemporaryFile object, to open a temporary file.
     *   
     *   'access' is the read/write mode, and must be one of FileAccessRead
     *   or FileAccessWrite.
     *   
     *   'charset' is a CharacterSet object, or can optionally be a string
     *   naming a character set, in which case a CharacterSet object for the
     *   named character set will automatically be created.  If 'charset' is
     *   omitted, the local system's default character set for file contents
     *   is used.
     *   
     *   When a file is opened in text mode for reading, each call to
     *   readFile() reads and returns a line of text from the file.  When a
     *   file is opened in text mode for writing, any existing file is
     *   discarded and replaced with the new data.  Each read and write to a
     *   text file is mapped through the CharacterSet in effect at the time
     *   of the read or write.  
     */
    static openTextFile(filename, access, charset?);

    /*
     *   Static creator method: open a file in 'data' mode.  Returns a File
     *   object that can be used to read or write the file.
     *   
     *   'filename' is a string giving the file name in the local file
     *   system, or a TemporaryFile object.
     *   
     *   'access' indicates the desired read/write access and the disposition
     *   of any existing file; any of the FileAccessXxx modes can be used.
     *   
     *   When a file is opened in data mode, you can read and write integers,
     *   strings, and 'true' values to the file, and the values in the file
     *   are marked with their datatype in a private data format.  Because
     *   the file uses a tads-specific format, this mode cannot be used to
     *   read files created by other applications or write files for use by
     *   other applications; however, this storage format is convenient for
     *   storing simple data values because the File object takes care of
     *   converting to and from a portable binary format.  
     */
    static openDataFile(filename, access);

    /*
     *   Static creator method: open a file in 'raw' mode.  Returns a File
     *   object that can be used to read or write the file.
     *   
     *   'filename' is a string giving the name of the file in the local file
     *   system, or a TemporaryFile object.
     *   
     *   'access' indicates the desired read/write access mode and the
     *   disposition of any existing file; any of the FileAccessXxx modes can
     *   be used.
     *   
     *   When a file is opened in raw mode, only ByteArray values can be read
     *   and written.  The File object performs no translations of the bytes
     *   read or written.  This mode requires the calling program itself to
     *   perform all data conversions to and from a raw byte format, but the
     *   benefit of this extra work is that this mode can be used to read and
     *   write files in arbitrary data formats, including formats defined by
     *   other applications.  
     */
    static openRawFile(filename, access);

    /* 
     *   get the CharacterSet object the File is currently using; returns
     *   nil for a non-text file 
     */
    getCharacterSet();

    /*
     *   Set the CharacterSet object the File is to use from now on.  This
     *   isn't meaningful except for text files.  'charset' can be a
     *   CharacterSet object, a string giving the name of a character mapping
     *   (in which case a CharacterSet object is automatically created based
     *   on the name), or nil (in which case the local system's default
     *   character set for text files is used).  
     */
    setCharacterSet(charset);

    /*
     *   Close the file.  Flushes any buffered information to the underlying
     *   system file and releases any system resources (such as share locks
     *   or system buffers) associated with the file.  After this routine is
     *   called, no further operations on the file can be performed (a
     *   FileClosedException will be thrown if any subsequent operations are
     *   attempted).
     *   
     *   If the game is running in web server mode, the file might be on a
     *   remote storage server.  In this case, if the file was opened with
     *   write access, closing it will send the file to the storage server.
     *   
     *   Note that this method can throw an error, so you shouldn't consider
     *   updates to the file to be "safe" until this method returns
     *   successfully.  On many systems, writes are buffered in memory, so
     *   closing the file can involve flushing buffers, which can trigger the
     *   same sorts of errors that can happen with ordinary writes (running
     *   out of disk space, physical media defects, etc).  In addition, when
     *   the file is on a remote network storage server, closing a file
     *   opened with write access transmits the file to the storage server,
     *   which can encounter network errors.
     *   
     *   You should always explicitly close files when done with them.  This
     *   is especially important when writing to a file, because many systems
     *   buffer written data in memory and don't write changes to the
     *   physical media until the file is closed.  This means that updates
     *   can be lost if the program crashes (or the computer loses power,
     *   etc) while the file is still open.  Closing the file as soon as
     *   you're done with it reduces the chances of this kind of data loss.
     *   It also helps overall system performance to release resources back
     *   to the operating system as soon as you're done with them.
     *   
     *   If you *don't* close a file, though, the system will close it
     *   automatically when the File object becomes unreachable and is
     *   deleted by the garbage collector.  It's considered bad form to
     *   depend on this for the reasons above, and it's also problematic
     *   because you won't have any way of finding out if an error should
     *   happen on close.  
     */
    closeFile();

    /*
     *   Read from the file.  Returns a data value that depends on the file
     *   mode, as described below, or nil at end of file.
     *   
     *   If the file is open in text mode, this reads a line of text from the
     *   file and returns a string with the text of the line read.  A line of
     *   text is a sequence of characters terminated with a line-ending
     *   sequence, which is a carriage return, line feed, CR/LF pair, LF/CR
     *   pair, or a Unicode line terminator character (0x2028) if the file is
     *   being read with one of the Unicode encodings.  If the line read ends
     *   in a line-ending sequence, the returned text will end in a '\n'
     *   character, regardless of which of the possible line-ending sequences
     *   is actually in the file, so the caller need not worry about the
     *   details of the external file's format.  Every line read from the
     *   file will end in a '\n' except possibly the last line - if the file
     *   doesn't end with a line-ending sequence, then the last line read
     *   from the file won't end in a '\n' character.  All bytes read from
     *   the file will be mapped to characters through the CharacterSet
     *   object currently in effect in the file, so the returned string will
     *   always be a standard Unicode string, regardless of the byte encoding
     *   of the file.
     *   
     *   If the file is open in 'data' mode, this reads one data element
     *   using the private tads-specific data format.  The result is a value
     *   of one of the types writable with writeFile() in 'data' mode.  In
     *   order to read a 'data' file, the file must have been previously
     *   written in 'data' mode.  
     */
    readFile();

    /*
     *   Write to the file.  Writes the given value to the file in a format
     *   that depends on the file mode, as described below.  No return
     *   value; if an error occurs writing the data, this throws a
     *   FileIOException.
     *   
     *   If the file is open in text mode, this writes text to the file,
     *   converting the given value to a string if necessary (and throwing
     *   an error if such a conversion isn't possible), and translating the
     *   string to be written to bytes by mapping the string through the
     *   CharacterSet object currently in effect for the file.  Note that no
     *   line-ending characters are automatically added to the output, so if
     *   the caller wishes to write line terminators, it should simply
     *   include a '\n' character at the end of each line.
     *   
     *   If the file is open in 'data' mode, this writes the value, which
     *   must be a string, integer, enum, or 'true' value, in a private
     *   tads-specific data format that can later be read using the same
     *   format.  The values are converted to the private binary format,
     *   which is portable across platforms: a file written in 'data' mode
     *   on one machine can be copied (byte-for-byte) to another machine,
     *   even one that uses different hardware and a different operating
     *   system, and read back in 'data' mode on the new machine to yield
     *   the original values written.  
     */
    writeFile(val);

    /*
     *   Read bytes from the file into the given ByteArray object.  This can
     *   only be used for a file opened in 'raw' mode.  If 'start' and 'cnt'
     *   are given, they give the starting index in the byte array at which
     *   the bytes read are to be stored, and the number of bytes to read,
     *   respectively; if these are omitted, one byte is read from the file
     *   for each byte in the byte array.
     *   
     *   Returns the number of bytes actually read into the byte array,
     *   which will be less than or equal to the number requested.  If the
     *   number read is less than the number requested, it means that the
     *   end of the file was encountered, and only the returned number of
     *   bytes were available.  
     */
    readBytes(byteArr, start?, cnt?);

    /*
     *   Write bytes from the given source object into the file.  This can
     *   only be used for a file opened in 'raw' mode.
     *   
     *   The source object must be one of the following object types:
     *   
     *   File: the contents of the given source file are copied to 'self'.
     *   'start' is the starting seek position in the source file; if
     *   omitted, the current seek position is the default.  'cnt' is the
     *   number of bytes to copy; if omitted, the file is copied from the
     *   given starting position to the end of the file.
     *   
     *   ByteArray: the bytes of the byte array are copied to the file.
     *   'start' is the starting index in the byte array; if omitted, the
     *   default is the first byte (index 1).  'cnt' is the number of bytes
     *   to copy; if omitted, bytes are copied from the start position to the
     *   end of the array.
     *   
     *   No return value; if an error occurs writing the data, a
     *   FileIOException is thrown.  
     */
    writeBytes(source, start?, cnt?);

    /*
     *   Get the current read/write position in the file.  Returns the byte
     *   offset in the file of the next byte to be read or written.  Note
     *   that this value is an offset, so 0 is the offset of the first byte
     *   in the file.  
     */
    getPos();

    /*
     *   Set the current read/write position in the file.  'pos' is a byte
     *   offset in the file; 0 is the offset of the first byte.
     *   
     *   For files in 'text' and 'data' modes, a caller should NEVER set the
     *   file position to any value other than a value previously returned
     *   by getPos(), because other positions might violate the format
     *   constraints.  For example, if you move the file position to a byte
     *   in the middle of a line-ending sequence in a text file, subsequent
     *   reading from the file might misinterpret the sequence as something
     *   other than a line ending, or as an extra line ending.  If you move
     *   the position in a 'data' file to a byte in the middle of an integer
     *   value, reading from the file would misinterpret as a data type tag
     *   a byte that is part of the integer value instead.  So it is never
     *   meaningful or safe to set an arbitrary byte offset in these file
     *   formats; only values known to be valid by virtue of having been
     *   returned from getPos() can be used here in these modes.  
     */
    setPos(pos);

    /*
     *   Set the current read/write position to the end of the file.  This
     *   can be used, for example, to open a 'data' mode file for
     *   read/write/keep access (keeping the contents of an existing file)
     *   and then adding more data after all of the existing data in the
     *   file.  
     */
    setPosEnd();

    /*
     *   Static creator method: open a resource in 'text' mode.  This acts
     *   like openTextFile(), but rather than opening an ordinary file, this
     *   method opens a resource.  Resources differ from ordinary files in
     *   two important respects.  First, a resource is named with a
     *   URL-style path rather than a local file system name.  Second, a
     *   resource can be embedded in the program's executable (.t3) file, or
     *   can be embedded in an external resource bundle (.3r0, etc) file.
     *   
     *   Resources are read-only, so the access mode is implicitly
     *   FileAccessRead.  
     */
    static openTextResource(resname, charset?);

    /*
     *   Static creator method: open a resource in 'raw' mode.  This acts
     *   like openRawFile(), but opens a resource rather than an ordinary
     *   file.
     *   
     *   Resources are read-only, so the access mode is implicitly
     *   FileAccessRead.  
     */
    static openRawResource(resname);

    /* get the size in bytes of the file */
    getFileSize();

    /*
     *   Get the file mode.  This returns one of the FileModeXxx constants,
     *   indicating the mode used to open the file (text, data, raw).  
     */
    getFileMode();

    /*
     *   Extract the file's "root name" from the given filename string.  This
     *   returns a new string giving the portion of the filename excluding
     *   any directory path.  This parses the filename according to the local
     *   file system's syntax rules.  For example, given the filename
     *   'a/b/c.txt', if you're running on a Unix or Linux machine, the
     *   function returns 'c.txt'.
     *   
     *   Note that this function doesn't attempt to open the file or check
     *   for its existence or validity; it simply parses the name according
     *   to the local syntax conventions.
     *   
     *   (It's recommended that you use the newer FileName.getBaseName() in
     *   place of this function.)
     */
    static getRootName(filename);

    /*
     *   Delete the file with the given name.  This erases the file from
     *   disk.  'filename' is a string giving the name of the file to delete,
     *   or one of the special file identifier values.
     *   
     *   The file can only be deleted if the file safety level would allow
     *   you to write to the file; if not, a file safety exception is thrown.
     *   
     *   (It's recommended that you use the newer FileName.deleteFile() in
     *   place of this function.)
     */
    static deleteFile(filename);

    /*
     *   Change the file mode.  'mode' is a FileModeXxx value giving the
     *   desired new file mode.
     *   
     *   If the mode is FileModeText, 'charset' is the character set mapping
     *   to use for the file; this can be given as a CharacterSet object, or
     *   as a string giving the name of a character set.  If the value is nil
     *   or the argument is omitted, the local system's default character for
     *   file contents is used.  The 'charset' parameter is ignored for other
     *   modes.
     */
    setFileMode(mode, charset?);

    /*
     *   Pack the given data values into bytes according to a format
     *   definition string, and write the packed bytes to the file.  This
     *   function is designed to simplify writing files that use structured
     *   binary formats defined by third parties, such as JPEG or PDF.  The
     *   function translates native TADS data values into selected binary
     *   formats, and writes the resulting bytes to the file, all in a single
     *   operation.
     *   
     *   'format' is the format string, and the remaining arguments are the
     *   values to be packed.
     *   
     *   Returns the number of bytes written to the file.  (More precisely,
     *   returns the final file position as a byte offset from the starting
     *   file pointer.  If a positioning code like @ or X is used in the
     *   string, it's possible that more bytes were actually written.)
     *   
     *   See Byte Packing in the System Manual for details.  
     */
    packBytes(format, ...);

    /*
     *   Read bytes and unpack into a data structure, according to the format
     *   description string 'desc'.
     *   
     *   'format' is the format string.  The function reads bytes from the
     *   current location in the file and translates them into data values
     *   according to the format string, returning a list of the unpacked
     *   values.
     *   
     *   Refer to Byte Packing in the System Manual for details.  
     */
    unpackBytes(format);

    /*
     *   Calculate the 256-bit SHA-2 hash of bytes read from the file,
     *   starting at the current seek location and continuing for the given
     *   number of bytes.  If the length is omitted, the whole rest of the
     *   file is hashed.  This has the side effect of reading the given
     *   number of bytes from the file, so it leaves the seek position set to
     *   the next byte after the bytes hashed.
     *   
     *   Returns a string of 64 hex digits giving the hash result.
     *   
     *   This can only be used on files opened in raw mode with read access.
     */
    sha256(length?);

    /*
     *   Calculate the MD5 digest of bytes read from the file, starting at
     *   the current seek location and continuing for the given number of
     *   bytes.  If the length is omitted, the whole rest of the file is
     *   digested.  This has the side effect of reading the given number of
     *   bytes from the file, so it leaves the seek position set to the next
     *   byte after the bytes digested.
     *   
     *   Returns a string of 32 hex digits giving the digest result.
     *   
     *   This can only be used on files opened in raw mode with read access. 
     */
    digestMD5(length?);
}


/* ------------------------------------------------------------------------ */
/*
 *   The TemporaryFile intrinsic class represents a temporary file name in
 *   the local file system.  Temporary files can be used to store data too
 *   large to conveniently store in memory.
 *   
 *   You create a temporary file with 'new TemporaryFile()'.  This
 *   automatically assigns the object a unique filename in the local file
 *   system, typically in a system directory reserved for temporary files.
 *   The local file can then be opened, read, written, and otherwise
 *   manipulated via the File class, just like any other file.  Simply pass
 *   the TemporaryFile object in place of a filename to the File.openXxx
 *   methods.  
 *   
 *   The underlying file system file will be deleted automatically when the
 *   TemporaryFile object is collected by the garbage collector (or when the
 *   program terminates).  This means that you don't have to worry about
 *   cleaning up the file system space used by the file; it'll be released
 *   automatically when the file is no longer needed.  However, you can call
 *   the deleteFile() method to explicitly release the file when you're done
 *   with it, if you want to ensure that the resource is returned to the
 *   operating system as soon as possible.
 *   
 *   TemporaryFile objects are inherently transient - they're only valid for
 *   the current session on the current local system, so they can't be saved
 *   or restored.
 *   
 *   Temporary files are exempt from the file safety level settings, because
 *   the inherent restrictions on temporary files provide the same system
 *   protections that the safety level settings provide for ordinary files.
 */
intrinsic class TemporaryFile 'tempfile/030000': Object
{
    /*
     *   Get the name of the underlying file system object.  This returns a
     *   string with the local filename.  This is mostly for debugging
     *   purposes or for displaying to the user.  You can't necessarily use
     *   this filename in a call to File.openXxxFile, because the file path
     *   is usually in a system directory reserved for temporary files, and
     *   the file safety level settings often prohibit opening files outside
     *   of the program's own home directory.  To open the temp file, you
     *   should always pass the TemporaryFile object itself in place of the
     *   filename.
     */
    getFilename();

    /*
     *   Delete the underlying file system object.  This deletes the
     *   temporary file and marks the TemporaryFile object as invalid.  After
     *   calling this, you can no longer open the file via the
     *   File.openXxxFile methods.
     *   
     *   This method allows you to release the underlying file system
     *   resources as soon as you're done with the temp file.  It's never
     *   necessary to do this.  TADS automatically deletes the underlying
     *   file system resources when the TemporaryFile object is deleted by
     *   the garbage collector (or when the program terminates), so the
     *   operating system file will be deleted eventually whether you call
     *   this method or not.  The point of this method is to let you tell the
     *   system *exactly* when you're done with the file, so that the
     *   resources can be released earlier than if we waited for garbage
     *   collection to take care of it.  This should make little difference
     *   in most situations, but in a program that will run for a long time
     *   and use a lot of temporary files, it might be worthwhile to release
     *   resources manually as soon as possible.  
     */
    deleteFile();
}


/* ------------------------------------------------------------------------ */
/*
 *   The filename passed to the File "open" methods, as well as to most
 *   system functions that accept filename arguments, can be a TadsObject
 *   object in lieu of a string.  Such an object must implement the following
 *   methods:
 *   
 *   getFilename() - return the actual filename to use, which must be a
 *   string or TemporaryFile object.
 *   
 *   closeFile() - optional.  This is called just after the underlying system
 *   file is closed, allowing the program to perform any desired
 *   post-processing on the file.  
 */
export getFilename 'FileSpec.getFilename';
export closeFile 'FileSpec.closeFile';

