!=========================================================================
! Copyright (C) VMware, Inc. 1986-2011.  All Rights Reserved.
!
! $Id: gsfile.gs,v 1.14 2008-01-09 22:50:11 stever Exp $
!
! Superclass Hierarchy:
!   GsFile, Object.
!
!=========================================================================

! Gs64 v2.1, major edits to change semantics of isClient instVar to fix 35450.
!  35450 is caused by a open of a transient GsFile probing C state for isOpen
!  and objId reuse causing reuse of C state of a previous instance. This
! can happen only if an open transient GsFile was garbage collected without
!  having been explicitly closed prior to GC .
!  
! state 		     	isClient instVar values
! -----				----------------------
! committed pre-v2.1 instance   true or false
! temp , open 			true or false
! temp , closed                 0 or 1
! temp , closed, access error   2 or 3 
! committed v2.1              any of   true/false, 0/1, 2/3 
!
!  instance creation must produce "closed" state .
!  open of a transient instance must not probe C state if the
!    Smalltalk state is "closed" , rather it must delete previous
!    C state and create new C state.
!  close of any instance deletes C state .
!  close of a transient instance must change state to "closed"
!  attempt to access a closed transient instance must change state to
!   "closed, access error"

expectvalue %String
run
| oldCls |
oldCls := Globals at:#GsFile otherwise: nil .
oldCls ~~ nil ifTrue:[
   "change constraint on isClient to Object "
   | constrs |
   constrs := GsFile allConstraints .  
   constrs at: 1 put: Object  .
   GsFile _unsafeAt:5 put: constrs .
   ^ 'fixed up constraint for isClient'
] ifFalse:[
  ^ Object _newKernelSubclass: 'GsFile'
        instVarNames: #(  
          #isClient "has following stats and values
            committed pre-v2.1 instance   true or false
            temp , open                   true or false
            temp , closed                 0 or 1
            temp , closed, access error   2 or 3  
            committed v2.1              any of   true/false, 0/1, 2/3   "

          #pathName  "a String, directory plus file name"
          #mode      "a String")

        classVars: #()
        classInstVars: #()
        poolDictionaries: #[]
        inDictionary: Globals
        constraints: #[#[ #isClient, Object ],
                       #[ #pathName, String ], 
		       #[ #mode, String ]  ]
        instancesInvariant: false
        isModifiable: false
        reservedOop: 899
]
%

! Remove existing behavior from GsFile
removeallmethods GsFile
removeallclassmethods GsFile

category: 'For Documentation Installation only'
classmethod: GsFile
installDocumentation

| doc txt |
doc := GsClassDocumentation newForClass: self.

txt := (GsDocText new) details:
'GsFile provides the means for creating and accessing non-GemStone files.  Such
 files reside in the file system on either the machine that is running the
 current session''s Gem process (the server machine) or the machine that is
 running the client application (the client machine).  The files may be of any
 type, textual or binary, though separate protocol is provided for reading and
 writing these types of data.

                              Warning:
    Do not retain an instance of GsFile from one session to another.
    Instances of GsFile are intended to exist only within a given GemStone
    session.  GsFiles that are used across sessions always generate an error.'.
doc documentClassWith: txt.

txt := (GsDocText new) details:
'a Boolean or SmallInteger,  with following stats and values
   committed pre-v2.1 instance   true or false
   temp , open                   true or false
   temp , closed                 0 or 1
   temp , closed, access error   2 or 3
   committed v2.1              any of   true/false, 0/1, 2/3   ' .
doc documentInstVar: #isClient with: txt.

txt := (GsDocText new) details:
'A String that gives an absolute path name to the file.' .
doc documentInstVar: #pathName with: txt.

txt := (GsDocText new) details:
'A String that gives the file open mode for the file, as defined for the
 C-language open function.' .
doc documentInstVar: #mode with: txt.

self description: doc.
%

! ------------------- Class methods for GsFile
category: 'Drastic Measures'
classmethod: GsFile
closeAll

"Closes all open files on the client machine except stdin/stdout/stderr.
 Returns the receiver if successful, nil if not."

"make sure the sources file has been explicitly closed"

^ self classUserAction: #GsfCloseAll onClient: true
%

category: 'Drastic Measures'
classmethod: GsFile
closeAllOnServer

"Closes all open files on the server machine except stdin/stdout/stderr.
 Returns the receiver if successful, nil if not."

^ self classUserAction: #GsfCloseAll onClient: false
%

category: 'Garbage Collection'
classmethod: GsFile
finalizeAll

"Closes all open files on the client machine that were opened by a
 instance of GsFile but that are no longer being used by an instance
 of GsFile. The need for finalization is caused by instances being
 garbage collected before they are sent close.
 Returns nil if an error occurs.
 Returns the total number of GsFile instances that were not finalized
 because they were still in use."

System _generationScavenge.
^ self classUserAction: #GsfFinalizeAll onClient: true
%

category: 'Garbage Collection'
classmethod: GsFile
finalizeAllOnServer

"Closes all open files on the server machine that were opened by a
 instance of GsFile but that are no longer being used by an instance
 of GsFile. The need for finalization is caused by instances being
 garbage collected before they are sent close.
 Returns nil if an error occurs.
 Returns the total number of GsFile instances that were not finalized
 because they were still in use."

System _generationScavenge.
^ self classUserAction: #GsfFinalizeAll onClient: false
%

category: 'Directory Operations'
classmethod: GsFile
contentsAndTypesOfDirectory: dirSpec onClient: bool

"Returns an Array of objects describing the contents of the given directory.
 The argument bool indicates the location of the directory as on the client or
 the server.  Successive pairs of elements of the Array are each the name of an
 entry, and a Boolean - true if the entry is a file, and false if not.

 Sample:  #['file.c', true, 'subdir', false, ...]

 Returns anArray if successful, nil if not."

| aLine dirContents result |

dirContents := self _directory: dirSpec onClient: bool .
dirContents == nil ifTrue:[ ^ nil ].

result := Array new .
[ aLine := dirContents nextLine .  aLine == nil ] whileFalse: [ 
  aLine isEmpty
    ifFalse: [
      aLine last == Character lf ifTrue:[ aLine size: (aLine size - 1) ].
      result add: aLine .
      result add: (self _fileKind: aLine onClient: bool) == 0  .
    ]
  ] .
dirContents close .
self _removeFile: dirContents pathName onClient: bool .
^ result
%

category: 'Private'
classmethod: GsFile
_fileKind: aPathName onClient: clientBool

"Returns a SmallInteger representing the kind of the file, or returns nil if an
 error occurs, in which case the class error buffer contains the error.

 The file kinds are enumerated as follows:

    0. file
    1. directory
    2. character device
    3. block device
    4. symbolic link
    5. other
    6. error"

^ self classUserAction: #GsfFileKind onClient: clientBool with: aPathName
%

category: 'Directory Operations'
classmethod: GsFile
contentsOfDirectory: dirSpec onClient: bool

"Returns an Array of Strings describing the contents of the given directory.
 The argument bool indicates the location of the directory as on the client or
 the server.  Each element of the Array is the name of an entry.

 Returns anArray if successful, nil if not."

| aLine dirContents result |

dirContents := self _directory: dirSpec onClient: bool .
dirContents == nil ifTrue:[ ^ nil ].

result := Array new .
[ aLine := dirContents nextLine .  aLine == nil ] whileFalse: [ 
  aLine isEmpty
    ifFalse: [
      aLine last == Character lf ifTrue:[ aLine size: (aLine size - 1) ].
      result add: aLine 
    ]
  ] .
dirContents close .
self _removeFile: dirContents pathName onClient: bool .
^ result
%

category: 'Private'
classmethod: GsFile
_directory: dirSpec onClient: clientBool

"Returns an opened temporary text file containing the contents of the specified
 directory, one line per directory entry."

| dirFileName |

dirFileName := self classUserAction: #GsfDirectory onClient: clientBool
                                                   with: dirSpec .
dirFileName == nil ifTrue: [ ^ nil ].
^ self open: dirFileName mode: 'r' onClient: clientBool.
%

category: 'File Operations'
classmethod: GsFile
exists: aPathName

"Returns true if the given path points to a file on the client,
 false if not, and nil if an error occurs trying to find out."

^ self _exists: aPathName onClient: true
%

category: 'File Operations'
classmethod: GsFile
existsOnServer: aPathName

"Returns true if the given path points to a file, on the server,
 false if not, and nil if an error occurs trying to find out."

^ self _exists: aPathName onClient: false
%

category: 'Private'
classmethod: GsFile
_exists: aPathName onClient: clientBool

"Returns true if the given path points to a file, false if not, and nil if an
 error other than file-does-not-exist occurs trying to find out."

| result |
"GsfSize returns -1 for non-existant file , nil for other error ,
 >= 0 for a file that exists "
result := self classUserAction: #GsfSize onClient: clientBool with: aPathName .
result == -1 ifTrue:[ ^ false "file does not exist" ].
result == nil ifTrue:[ ^ nil "some other error" ] .
^ true
%

category: 'Instance Creation'
classmethod: GsFile
open: aPathName mode: openMode 

"Creates an instance of the receiver and opens a file on the client machine.

 The openMode argument must be a String that that is equal to one of the
 following: 'r', 'w', 'a', 'r+', 'w+', 'a+', 'rb', 'wb', 'ab', 'r+b', 'w+b',
 'a+b', 'rb+', 'wb+', 'ab+'.  The mode has the same meaning as it does for the
 C library function, fopen().

 Returns a GsFile if successful, nil if an error occurs."

^ self open: aPathName mode: openMode onClient: true
%

category: 'Instance Creation'
classmethod: GsFile
newWithFilePtr: filePtr pathname: aPathName mode: openMode onClient: clientBool 

"Creates an instance of the receiver using an already open FILE*: filePtr.
 aPathName, openMode, and clientBool describe the already open filePtr.

 The filePtr should come from a C user action, and must have
 been converted from a C pointer of type FILE* to a ByteArray
 using GciPointerToByteArray.
 
 Returns a GsFile if successful, nil if an error occurs."

| inst res |

inst := super new.
inst _open: aPathName mode: openMode onClient: clientBool.
res := inst userAction: #GsfAssociate onClient: clientBool with: filePtr .
res == nil ifFalse:[ inst _setIsOpen  ].
^ res
%

category: 'Instance Creation'
classmethod: GsFile
open: aPathName mode: openMode  onClient: clientBool 

"Creates an instance of the receiver and opens a file on the client machine
 (if clientBool is true) or the server machine (if clientBool is false).

 The openMode argument must be a String that that is equal to one of the
 following: 'r', 'w', 'a', 'r+', 'w+', 'a+', 'rb', 'wb', 'ab', 'r+b', 'w+b',
 'a+b', 'rb+', 'wb+', 'ab+'.  The mode has the same meaning as it does for the
 C library function, fopen().

 Returns a GsFile if successful, nil if an error occurs."

  | inst |

(#('r'  'w'  'a'  'r+'  'w+'  'a+'
   'rb' 'wb' 'ab' 'r+b' 'w+b' 'a+b'
                  'rb+' 'wb+' 'ab+') includesValue: openMode) ifFalse: [
  self _setLastError: 'errno=22,EINVAL, Invalid argument (programmer error)' 
	onClient: clientBool .
  ^nil
].
inst := super new.
inst _open: aPathName mode: openMode onClient: clientBool.
^ inst open
%

category: 'Instance Creation'
classmethod: GsFile
openOnServer: aPathName mode: openMode

"Creates an instance of the receiver and opens a file on the server machine.

 The openMode argument must be a String that that is equal to one of the
 following: 'r', 'w', 'a', 'r+', 'w+', 'a+', 'rb', 'wb', 'ab', 'r+b', 'w+b',
 'a+b', 'rb+', 'wb+', 'ab+'.  The mode has the same meaning as it does for the
 C library function, fopen().

 Returns a GsFile if successful, nil if an error occurs."

^ self open: aPathName mode: openMode onClient: false
%

category: 'Instance Creation'
classmethod: GsFile
openAppend: aPathName

"Opens a text file on the client machine for writing.
 If the file does not exist it is created.
 The file position indicator is positioned at the end of the file
 before each write operation.
 Read operations are not allowed.
 Returns aGsFile if successful, nil if an error occurs."

^self open: aPathName mode: 'a' onClient: true
%

category: 'Instance Creation'
classmethod: GsFile
openRead: aPathName

"Opens an existing text file on the client machine for reading.
 Write operations are not allowed.
 Returns aGsFile if successful, nil if an error occurs."

^self open: aPathName mode: 'r' onClient: true
%

category: 'Instance Creation'
classmethod: GsFile
openUpdate: aPathName

"Opens an existing text file on the client machine for reading and writing.
 Returns aGsFile if successful, nil if an error occurs."

^self open: aPathName mode: 'r+' onClient: true
%

category: 'Instance Creation'
classmethod: GsFile
openWrite: aPathName

"Opens a text file on the client machine for writing.
 If the file exists it is truncated to zero length.
 If the file does not exist it is created.
 Read operations are not allowed.
 Returns aGsFile if successful, nil if an error occurs."

^self open: aPathName mode: 'w' onClient: true
%

category: 'Instance Creation'
classmethod: GsFile
openAppendOnServer: aPathName

"Opens a text file on the server machine for writing.
 If the file does not exist it is created.
 The file position indicator is positioned at the end of the file
 before each write operation.
 Read operations are not allowed.
 Returns aGsFile if successful, nil if an error occurs."

^self open: aPathName mode: 'a' onClient: false
%

category: 'Instance Creation'
classmethod: GsFile
openReadOnServer: aPathName

"Opens an existing text file on the server machine for reading.
 Write operations are not allowed.
 Returns aGsFile if successful, nil if an error occurs."

^self open: aPathName mode: 'r' onClient: false
%

category: 'Instance Creation'
classmethod: GsFile
openUpdateOnServer: aPathName

"Opens an existing text file on the server machine for reading and writing.
 Returns aGsFile if successful, nil if an error occurs."

^self open: aPathName mode: 'r+' onClient: false
%

category: 'Instance Creation'
classmethod: GsFile
openWriteOnServer: aPathName

"Opens a text file on the server machine for writing.
 If the file exists it is truncated to zero length.
 If the file does not exist it is created.
 Read operations are not allowed.
 Returns aGsFile if successful, nil if an error occurs."

^self open: aPathName mode: 'w' onClient: false
%

category: 'File Operations'
classmethod: GsFile
removeClientFile: aPathName

"Removes the named file from the client machine's file system.
 Returns the receiver if the file was deleted, nil if an error occurs."

^ self _removeFile: aPathName onClient: true .
%

category: 'File Operations'
classmethod: GsFile
removeServerFile: aPathName

"Removes the named file from the server machine.  Returns the receiver if the
 file was deleted, nil if an error occurs."

^ self _removeFile: aPathName onClient: false .
%

category: 'Private'
classmethod: GsFile
_removeFile: aPathName onClient: clientBool

"Returns receiver if removal succeeds.  Returns nil if an error occurs,
 in which case the class error buffer contains the error."

^ self classUserAction: #GsfUnlink onClient: clientBool with: aPathName
%

category: 'Directory Operations'
classmethod: GsFile
removeClientDirectory: aPathName

"Removes the named directory from the client machine's file system.
 Returns the receiver if the directory was deleted, nil if an error occurs.
 Note that this method will fail if the directory is not empty."

^ self _removeDirectory: aPathName onClient: true .
%

category: 'Directory Operations'
classmethod: GsFile
removeServerDirectory: aPathName

"Removes the named directory from the server machine.
 Returns the receiver if the directory was deleted, nil if an error occurs.
 Note that this method will fail if the directory is not empty."

^ self _removeDirectory: aPathName onClient: false .
%

category: 'Private'
classmethod: GsFile
_removeDirectory: aPathName onClient: clientBool

"Returns receiver if removal succeeds.  Returns nil if an error occurs,
 in which case the class error buffer contains the error."

^ self classUserAction: #GsfRmdir onClient: clientBool with: aPathName
%

category: 'Directory Operations'
classmethod: GsFile
isClientDirectory: aPathName
"Returns true if 'aPathName' names an existing directory on the client
 machine.
 Returns false if it is not a directory.
 Returns nil if an error occurs."

^ self _isDirectory: aPathName onClient: true .
%

category: 'Directory Operations'
classmethod: GsFile
isServerDirectory: aPathName
"Returns true if 'aPathName' names an existing directory on the server
 machine.
 Returns false if it is not a directory.
 Returns nil if an error occurs."

^ self _isDirectory: aPathName onClient: false .
%

category: 'Private'
classmethod: GsFile
_isDirectory: aPathName onClient: clientBool

"Returns true if 'aPathName' names an existing directory.
 Returns false if it is not a directory.
 Returns nil if an error occurs."

^ self classUserAction: #GsfIsDir onClient: clientBool with: aPathName
%

category: 'Directory Operations'
classmethod: GsFile
createClientDirectory: aPathName

"Creates the named directory on the client machine's file system.
 Returns the receiver if the directory was created, nil if an error occurs."

^ self _createDirectory: aPathName onClient: true .
%

category: 'Directory Operations'
classmethod: GsFile
createServerDirectory: aPathName

"Creates the named directory on the server machine.
 Returns the receiver if the directory was created, nil if an error occurs."

^ self _createDirectory: aPathName onClient: false .
%

category: 'Private'
classmethod: GsFile
_createDirectory: aPathName onClient: clientBool

"Returns receiver if creation succeeds.  Returns nil if an error occurs,
 in which case the class error buffer contains the error."

^ self classUserAction: #GsfMkdir onClient: clientBool with: aPathName
%

category: 'File Operations'
classmethod: GsFile
lastModificationOfClientFile: aPathName

"Gets the date and time the named file on the client machine was last
 modified. Returns a DateTime if successful, nil if an error occurs."

^ self _fileModTime: aPathName onClient: true .
%

category: 'File Operations'
classmethod: GsFile
lastModificationOfServerFile: aPathName

"Gets the date and time the named file on the server machine was last 
 modified. Returns a DateTime if successful, nil if an error occurs."

^ self _fileModTime: aPathName onClient: false .
%

category: 'Accessing'
method: GsFile
lastModified
"Returns a DateTime that represents the last time the receiver's file
 was modified. Returns nil if an error occurs."

^ GsFile _fileModTime: (self pathName) onClient: isClient .
%

category: 'Private'
classmethod: GsFile
_fileModTime: aPathName onClient: clientBool

"Returns DateTime of files last modification time if successful.
 Returns nil if an error occurs,
 in which case the class error buffer contains the error."

^ self classUserAction: #GsfModTime onClient: clientBool with: aPathName
%

! documentation comments changed in v2.1 to match existing behavior
category: 'File Operations'
classmethod: GsFile
sizeOf: aPathName

"Returns the size in bytes of the given client file, false if
 file does not exist, or nil if any other error occurs."

^ self _sizeOf: aPathName onClient: true
%

! documentation comments changed in v2.1 to match existing behavior
category: 'File Operations'
classmethod: GsFile
sizeOfOnServer: aPathName

"Returns the size in bytes of the given server file, false if
 file does not exist, or nil if any other error occurs."

^ self _sizeOf: aPathName onClient: false
%

category: 'Private'
classmethod: GsFile
_sizeOf: aPathName onClient: clientBool

"Returns size of the file in bytes.  Returns nil if an error occurs,
 in which case the class error buffer contains the error."

| result |

"GsfSize returns -1 for non-existant file , nil for other error ,
 >= 0 for a file that exists "
result := self classUserAction: #GsfSize onClient: clientBool with: aPathName .
result == nil ifTrue:[ ^ nil ] .
result == -1 ifTrue:[ ^ false "file does not exist" ].
^ result
%

category: 'Standard Files'
classmethod: GsFile
stdin

"Returns an instance of the receiver that is set up to read the standard input
 of the client process, or nil if an error occurs."

^self _getStdFile: 0 onClient: true
%

category: 'Standard Files'
classmethod: GsFile
stdout

"Returns an instance of the receiver that is set up to write to the standard
 output of the client process, or nil if an error occurs."

^self _getStdFile: 1 onClient: true
%

! stdoutServer added for gemstone64 1.0
category: 'Standard Files'
classmethod: GsFile
stdoutServer

"Returns an instance of the receiver that is set up to write to the standard
 output of the server process, or nil if an error occurs."

^self _getStdFile: 1 onClient: false
%


category: 'Standard Files'
classmethod: GsFile
stderr

"Returns an instance of the receiver that is set up to write to the standard
 error output of the client process, or nil if an error occurs."

^self _getStdFile: 2 onClient: true
%

category: 'Private'
classmethod: GsFile
_getStdFile: stdId onClient: clientBool

"Returns an instance of the receiver that is set up to operate on a standard
 file of the client or nil if an error occurs.  stdId can be 0 (stdin),
 1 (stdout), or 2 (stderr)."

|result stdIdCache virtualId |

stdIdCache := System _sessionStateAt: 18.
(stdIdCache == nil) ifTrue: [
  stdIdCache := Array new: 6.
  System _sessionStateAt: 18 put: stdIdCache.
].
clientBool ifTrue:[   virtualId := stdId + 1 ]
          ifFalse:[  virtualId := stdId + 4 ].
result := stdIdCache at: virtualId .
result == nil ifTrue: [ | ok |
  "It has not yet been cached so create it"
  result := super new.
  result _newStdFile: stdId isClient: clientBool .
  "tell the client about it"
  ok := result userAction: #GsfCreateStdFile onClient: clientBool with: stdId.
  ok ifTrue:[ stdIdCache at: virtualId put: result.]
    ifFalse:[ result := nil. ] .
].

^ result
%

category: 'Error Reporting'
classmethod: GsFile
_setLastError: aString onClient: clientBool

self classUserAction: #GsfClassError onClient: clientBool with: aString
%

category: 'Error Reporting'
classmethod: GsFile
lastErrorString

"Returns the currently posted error string, for class operations on the
 client, or nil if no error has occurred.  Clears the error string."

^ self classUserAction: #GsfClassError onClient: true with: nil
%

category: 'Error Reporting'
classmethod: GsFile
serverErrorString

"Returns the currently posted error string, for class operations on the
 server, or nil if no error has occurred.  Clears the error string."

^ self classUserAction: #GsfClassError onClient: false with: nil
%

category: 'Private'
classmethod: GsFile
_clientVersionAt: verParamId

"Returns client process version information.  verInfoId should be one of the
 values in the global dictionary VersionParameterDict."

^ self classUserAction: #GsfVersionParam onClient: true with: verParamId
%

category: 'Private'
classmethod: GsFile
_expandEnvVariable: varName isClient: clientBool

"Expands the environment variable named varName in either the GemBuilder for C
 or Gem process, returning a String. varName should be a kind of String.

 Returns nil if any of the following are true
    varName is not a byte format object.
    there is no environment variable defined with name  varName,
    the value of the environment variable is more than approximately 8000 bytes,
    the size of varName exceeds approximately 8000 bytes."

^ self classUserAction: #GsfExpandEnvVar onClient: clientBool with: varName
%

category: 'Private'
classmethod: GsFile
classUserAction: actionName onClient: onClient

"Executes a GsFile user action, passing it the following arguments:
    self
    any arguments to the primitive (see other uses of primitive 396)
 Maximum of 6 arguments (6 with: keywords) for this primitive.
 actionName must be a Symbol"

<primitive: 396>

^ self _primitiveFailed: #classUserAction:onClient:
%

category: 'Private'
classmethod: GsFile
classUserAction: actionName onClient: onClient with: arg1

"See GsFile | classUserAction:onClient: for documentation."

<primitive: 396>

^ self _primitiveFailed: #classUserAction:onClient:with:
%

category: 'Private'
classmethod: GsFile
_log: string
  "Convenience method to print to stdout just like log: does."

  self stdout log: string
%

! ------------------- Instance methods for GsFile

category: 'Private'
method: GsFile
_open: aPath mode: aMode  onClient:  clientBool 

  "Initialize the receiver as specified by the arguments."

  "Does not actually open the underlying file."

  clientBool ifTrue:[ isClient := 3 ] ifFalse:[ isClient := 2 ].
  pathName := aPath .
  mode := aMode .
%

category: 'File Operations'
method: GsFile
fileSize

"Returns the size in bytes of the receiver, or nil if an error occurs.

 Note that the value returned is independent of the open mode used to
 create the receiver."

^ self class _sizeOf: pathName onClient: isClient
%

category: 'Writing'
method: GsFile
+ collection

"Writes the contents of the given collection to the receiver's file at
 the current position. The argument must be a kind of CharacterCollection
 with byte format.  Returns a count of bytes added, or nil if an error occurs."

^  self nextPutAll: collection
%

category: 'Writing'
method: GsFile
add: char

"Writes the given Character to the receiver's file at the current position. 
 Returns true, or nil if an error occurs."

^ self nextPut: char
%

category: 'Writing'
method: GsFile
addAll: collection

"Writes the contents of the given collection to the receiver's file at
 the current position. The argument must be a kind of CharacterCollection
 with byte format.  Returns a count of bytes added, or nil if an error occurs."

^ self nextPutAll: collection
%

category: 'Positioning'
method: GsFile
atEnd

"Returns true if the receiver is currently positioned at the end of its
 file, false if not, or nil if an error occurs."

(isClient _isSmallInteger _and:[ self isCommitted == false]) ifTrue:[ ^ self _setNotOpenError ].
^ self userAction: #GsfAtend onClient: isClient
%

category: 'File Operations'
method: GsFile
close

"Closes the receiver's file.  Returns the receiver, or nil if an error occurs.
 If receiver is a standard file, has no effect and returns receiver."

 | res stdIdCache |
 self isOpen ifFalse: [^self].
 stdIdCache := System _sessionStateAt: 18.
 stdIdCache == nil ifFalse: [
   (stdIdCache includesIdentical: self) ifTrue:[
     ^ self
   ]
 ]. 
 res := self userAction: #GsfClose onClient: isClient . 
 res == nil ifFalse:[ self _setNotOpened ].
 ^ res
%

category: 'Reading'
method: GsFile
contents

"Returns a String containing the contents of the receiver from the current
 position to the end of the file.  Returns nil if an error occurs."

| mySize myPosition |

mySize := self fileSize .
mySize == nil ifTrue:[ ^ nil ] .
myPosition := self position .
myPosition == nil ifTrue:[ ^ nil ] .
^ self next: ( mySize - myPosition ) 
%

category: 'Private'
method: GsFile
_isBinary

"Returns true if receiver is a binary file. Otherwise returns false."
(mode == nil) ifTrue: [ ^ false].
^ mode includesValue: $b
%

category: 'Writing'
method: GsFile
cr

"If the receiver is a text file then writes an end-of-line sequence to it.
 If the receiver is a binary file then writes a carriage return to it.
 Returns a count of bytes added, or nil if an error occurs."

(self _isBinary) ifTrue: [
  (isClient _isSmallInteger _and:[ self isCommitted == false]) ifTrue:[ ^ self _setNotOpenError ].
  ^ self userAction: #GsfPutC onClient: isClient with: 13 "ASCII carriage return"
] ifFalse: [
  "ANSI-C text files will write eoln when they see a single lf character."
  ^ self lf
].
%

category: 'Error Reporting'
method: GsFile
lastErrorString

"Returns the currently posted error string, or nil if no error has occurred.
 Clears the error string and error code.  
 If the last error was a failure during reopen of a closed file,
 will retrieve and clear the error string from the class."

 isClient _isSmallInteger ifFalse:[
   "a pre v2.1 committed or open instance, get result from C"
   ^ self userAction: #GsfErrorString onClient: isClient
 ].
 self isCommitted ifTrue:[
   "v2.1 committed instance in any state, get result from C"
   ^ self userAction: #GsfErrorString onClient: isClient
 ].
 "at this point we have a v2.1 transient instance."
 isClient >= 2 ifTrue:[
   "change state from 'closed, access error' to 'closed' and return err string."
   isClient := isClient - 2 .
   ^ 'attempt to access a GsFile that was never opened or has been closed'.
 ].
 "get class' error string for last unsuccessful open/reopen."
 ^ self class lastErrorString .
%

category: 'Error Reporting'
method: GsFile
lastErrorCode

"Returns the currently posted error code, or zero if no error has occurred.
 A result of -1  means 
   'attempt to access a GsFile that was never opened or has been closed' .
 Does not clear the error code or error string.

 If the last error was a failure during reopen of a closed file, 
 this method returns zero;  you must use lastErrorString to get information 
 on such reopen  failures.   This situation applies to errors produce by
 sending  open   or   open:mode:    to an instance of GsFile. "

 isClient _isSmallInteger ifFalse:[
   "a pre v2.1 committed or open instance, get result from C"
   ^ self userAction: #GsfErrorCode onClient: isClient 
 ].
 self isCommitted ifTrue:[
   "v2.1 committed instance, get result from C"
   ^ self userAction: #GsfErrorCode onClient: isClient 
 ].
 "at this point we have a v2.1 transient instance."
 isClient >= 2 ifTrue:[ 
   ^ -1 "attempt to access a GsFile that was never opened or has been closed."
 ].
 ^ 0
%

category: 'Writing'
method: GsFile
ff

"Writes a form-feed (page break) to the receiver's file.
 Returns true, or nil if an error occurs."

^ self nextPut:  Character newPage
%

category: 'File Operations'
method: GsFile
flush

"Flushes all written bytes to the file.  Returns the receiver, or nil if an
 error occurs."

(isClient _isSmallInteger _and:[ self isCommitted == false]) ifTrue:[ ^ self _setNotOpenError ].
^ self userAction: #GsfFlush onClient: isClient
%

category: 'File Operations'
method: GsFile
head: lineCount

"Returns a String containing the first lineCount lines from the
 receiver's file, or nil if an error occurs."

| result line |

self position: 0.
result := String new.
lineCount timesRepeat: [
  line := self nextLine.
  line ~~ nil ifTrue: [
    result addAll: line.
  ]
  ifFalse: [
    ^result
  ]
].
^result
%

category: 'Testing'
method: GsFile
isExternal

"Is the source for this stream is external to GemStone Smalltalk."

^true
%

category: 'Testing'
method: GsFile
isClient

"Returns true if the receiver's file is a client file, or nil if an error
 occurs."

isClient _isSmallInteger ifTrue:[ ^ (isClient bitAnd:1) == 1 ].
^ isClient  
%

category: 'Private'
method: GsFile
_setNotOpenError

  "set state of a transient instance to 'closed, access error' "

  self isCommitted ifFalse:[
    isClient _isSmallInteger ifFalse:[
      self error:'invalid state' .
      self _uncontinuableError 
    ].
    isClient < 2 ifTrue:[ isClient := isClient + 2 ].
  ].
  ^ nil
%

category: 'Private'
method: GsFile
_setIsOpen

  "for a transient instance, change state to 'open' "
  self isCommitted ifFalse:[
    isClient := (isClient bitAnd: 1) == 1  "convert 0/1, 2/3 to false/true"
  ] 
%

category: 'Private'
method: GsFile
_setNotOpened

  "for a transient instance,  change state to 'closed' "
  self isCommitted ifFalse:[
    isClient _isSmallInteger ifFalse:[
       isClient ifTrue:[ isClient:= 1 ] ifFalse:[ isClient:=0 ].
    ].
  ].
%

category: 'Testing'
method: GsFile
isOpen

"Returns true if the receiver's file is open, or nil if an error occurs."

 ^ (self id ~~ 0)
%

category: 'Writing'
method: GsFile
lf

"If the receiver is a text file then writes an end-of-line sequence to it.
 If the receiver is a binary file then write a line-feed to it.
 Returns a count of bytes added, or nil if an error occurs."

(isClient _isSmallInteger _and:[ self isCommitted == false]) ifTrue:[ ^ self _setNotOpenError ].
^ self userAction: #GsfPutC onClient: isClient with: 10
                                                  "ASCII line-feed Character"
%

category: 'Writing'
method: GsFile
log: string

"Writes the contents of the given collection to the receiver's file at the
 current position.  Appends a newline to the file if the string does not end
 with one.  The argument must be a kind of CharacterCollection with byte format.

 Returns the receiver if successful; returns nil otherwise."

| lf result |

result := self nextPutAll: string.
(result ~~ nil _and:[ result > 0]) ifTrue:[
  lf := Character lf.
  string last == lf ifFalse: [ result := self nextPut: lf ]
  ].
result == nil ifTrue: [ ^nil ].
self flush.
^ self
%

! gemstone64, added gciLogServer: , gciLogClient:
category: 'Writing'
classmethod: GsFile
gciLogServer: aString

"Passes the contents of the given String to the GciGetLogger() function
 of the server process. 
 If the collection has size > 0 and does not
 end in an Ascii LineFeed, a LineFeed is appended to the bytes passed
 to the logging function."

^ self gciLog: aString onClient: false
%

category: 'Writing'
classmethod: GsFile
gciLogClient: aString

"Passes the contents of the given collection to the GciGetLogger() function
 of the client process.
 If the collection has size > 0 and does not
 end in an Ascii LineFeed, a LineFeed is appended to the bytes passed
 to the logging function."

^ self gciLog: aString onClient: true
%

category: 'Writing'
classmethod: GsFile
gciLog: aString onClient: clientBool

"Passes the contents of the given collection to a GciGetLogger() function.
 The clientBool must be a Boolean , true means use GciGetLogger() on
 the client process, false means server process .
 If the collection has size > 0 and does not
 end in an Ascii LineFeed, a LineFeed is appended to the bytes passed
 to the logging function."


^ self classUserAction: #GsfGciLog  onClient: clientBool with: aString

%


category: 'Accessing'
method: GsFile
mode

"Returns the access mode of the receiver's file."

^mode
%

category: 'Accessing'
method: GsFile
name

"Returns the receiver's file path name."

^ pathName
%

category: 'Reading'
method: GsFile
next

"Returns the next Character from the receiver's file, or nil if an error
 occurs."

(isClient _isSmallInteger _and:[ self isCommitted == false]) ifTrue:[ ^ self _setNotOpenError ].
^ self userAction: #GsfGetC onClient: isClient
%

category: 'Reading'
method: GsFile
next: numberOfBytes

"Returns a String containing the next numberOfBytes Characters from the
 receiver's file, or nil if an error occurs."

| result |
result := String new.
(self next: numberOfBytes ofSize: 1 into: result ) == nil ifTrue:[ ^ nil ].
^ result
%

category: 'Reading'
method: GsFile
next: amount byteStringsInto: byteObj

"Reads bytes written by printBytes: into the given byte object.
 Returns count of bytes read, or nil if an error occurs."

(isClient _isSmallInteger _and:[ self isCommitted == false]) ifTrue:[ ^ self _setNotOpenError ].
^ self userAction: #GsfLoadByteStrings onClient: isClient with: byteObj with: amount
%

category: 'Reading'
method: GsFile
next: numberOfBytes into: aCharacterCollection

"Reads the next numberOfBytes into the given collection object. The
 object's size is truncated to the amount of data actually read.
 Returns a count of bytes read, or nil if an error occurs."

^ self next: numberOfBytes ofSize: 1 into: aCharacterCollection 
%

category: 'Reading'
method: GsFile
_next: numberOfBytes basicInto: aCharacterCollection

""

"GsFile doesn't distinguish Strings from EUCStrings, so no reimplementation
 needed."

^ self next: numberOfBytes into: aCharacterCollection
%

category: 'Reading'
method: GsFile
next: numberOfItems ofSize: bytesPerItem into: byteObj

"Reads bytes for the next numberOfItems of the given bytesPerItem into 
 the given collection object. The object's size is truncated to the 
 amount of data actually read.  bytesPerItem must between 1 and 4096 inclusive.

 Returns a count of bytes read, or nil if an error occurs."

(isClient _isSmallInteger _and:[ self isCommitted == false]) ifTrue:[ ^ self _setNotOpenError ].
^ self userAction: #GsfRead onClient: isClient  
       with: byteObj with: bytesPerItem with: numberOfItems
%

category: 'Reading'
method: GsFile
nextByte

"Returns the next byte (integer) from the receiver's file, or nil if an
error occurs."

^ self next asciiValue 
%

category: 'Reading'
method: GsFile
nextLine

"Returns a String containing the next line from the receiver's file. The String
 will be terminated with a newline, unless the end of file is reached and there
 is no line terminator.  If the receiver is positioned at the end of the file,
 returns an empty String.  Returns nil if an error occurs."

"There is no limit on line size."

| result |
result := String new .
(self nextLineInto: result startingAt: 1) == nil ifTrue:[ ^ nil ] .
^ result .
%

category: 'Reading'
method: GsFile
nextLineInto: str startingAt: pos

"Reads the next line from the receiver's file into the given collection object,
 starting at the given position in the collection. The collection will be
 terminated with a newline, unless the end of file is reached and there is no
 line terminator.  If the receiver is positioned at the end of the file, nothing
 is written.  Returns a count of bytes read, or nil if an error occurs."

"There is no limit on line size."

(isClient _isSmallInteger _and:[ self isCommitted == false]) ifTrue:[ ^ self _setNotOpenError ].
^ self userAction: #GsfGetLine onClient: isClient with: str with: pos.
%

category: 'Writing'
method: GsFile
nextPut: aByte

"Writes the given byte to the receiver's file at the current position.
 aByte must be a Character or a SmallInteger in the range 0..255.

 If aByte is a SmallInteger, it will be interpreted logically as

 Character withValue: aByte 
   
 Returns true, or nil if an error occurs."


(isClient _isSmallInteger _and:[ self isCommitted == false]) ifTrue:[ ^ self _setNotOpenError ].
^ self userAction: #GsfPutC onClient: isClient with: aByte
%

category: 'Writing'
method: GsFile
nextPutAll: collection

"Writes the contents of the given collection to the receiver's file at the
 current position. The argument must be a kind of CharacterCollection with byte
 format.  Returns a count of bytes added, or nil if an error occurs."

^ self write: collection itemCount: (collection _basicSize) ofSize: 1 
%

category: 'Writing'
method: GsFile
nextPutAllBytes: collection

"Writes the byte contents of the given collection to the receiver's file at the
 current position.  The argument must be a kind of CharacterCollection with byte
 format.  Returns a count of bytes added, or nil if an error occurs."

^ self nextPutAll: collection
%

category: 'File Operations'
method: GsFile
open

"If the receiver is not open, open it using the existing mode.
 Returns the receiver, or nil if an error occurs.
 If an error occurs,  there is no error information associated with
 the instance; you must use    GsFile lastErrorString   to retrieve the error. "

| res |
(pathName == nil _or: [mode == nil]) ifTrue: [ ^nil ].
(self isOpen) ifTrue: [ ^self ].
res := self userAction: #GsfOpen onClient: isClient 
	         with: pathName with: mode .
res == nil ifFalse:[ self _setIsOpen  ].
^ res .
%

category: 'File Operations'
method: GsFile
open: aPathName mode: openMode

"Opens the receiver's file with the given mode.  If the file is already open,
 it is closed and reopened.

 The openMode argument must be a String that that is equal to one of the
 following: 'r', 'w', 'a', 'r+', 'w+', 'a+', 'rb', 'wb', 'ab', 'r+b', 'w+b',
 'a+b', 'rb+', 'wb+', 'ab+'.  The mode has the same meaning as it does for the
 C library function, fopen().

 Returns the receiver if successful, or nil if an error occurs.
 If an error occurs,  there is no error information associated with
 the instance; you must use   GsFile lastErrorString to retrieve the error. "

self isOpen ifTrue:[ self close ].
mode := openMode.
pathName := aPathName.
^self open
%

category: 'Accessing'
method: GsFile
pathName

"Returns the receiver's file path name."

^pathName
%

category: 'Reading'
method: GsFile
peek

"Returns the next byte in the receiver's file, without advancing the current
 pointer.  Returns nil if an error occurs."

| pos result |

pos := self position.
result := self next.
self position: pos.
^result
%

category: 'Reading'
method: GsFile
peek2

"Returns the next byte plus one in the receiver's file, without advancing the
 current pointer.  Returns nil if an error occurs."

| pos result |

pos := self position.
result := self next; next.
self position: pos.
^result
%

category: 'Positioning'
method: GsFile
position
  
"Returns the current position of the receiver's file, or nil if an error
 occurs."

(isClient _isSmallInteger _and:[ self isCommitted == false]) ifTrue:[ ^ self _setNotOpenError ].
^ self userAction: #GsfPosition onClient: isClient
%

category: 'Positioning'
method: GsFile
position: offset

"Changes the receiver's position in its file by the given offset, which may be
 negative or zero.  Returns the new position, or nil if an error occurs."

^ self _seekTo: offset opcode: 0 .
%

category: 'Writing'
method: GsFile
printBytes: byteObj

"Prints the bytes from the given byte object in decimal notation with
 line breaks to keep output lines from being too long.

 Returns the receiver, or nil if an error occurs.

 See also the method next:byteStringsInto:."

(isClient _isSmallInteger _and:[ self isCommitted == false]) ifTrue:[ ^ self _setNotOpenError ].
^ self userAction: #GsfPrintBytes onClient: isClient with: byteObj
%

category: 'Positioning'
method: GsFile
rewind

"Repositions the receiver's file to the beginning.  Returns 0, or nil if an
 error occurs."

self position: 0
%

category: 'Positioning'
method: GsFile
seekFromBeginning: offset

"Moves the receiver's position in its file to the given offset from the
 beginning of the file, which may be positive or zero, but not negative.
 Returns the new position, or nil if an error occurs."

^ self _seekTo: offset opcode: 0
%

category: 'Positioning'
method: GsFile
seekFromCurrent: offset

"Changes the receiver's position in its file by the given offset, which
 may be negative or zero.  Returns the new position, or nil if an error occurs."

^ self _seekTo: offset opcode: 1 
%

category: 'Positioning'
method: GsFile
seekFromEnd: offset

"Moves the receiver's position in its file to the given offset from the
 end of the file, which may be negative or zero, but not positive.
 Returns the new position, or nil if an error occurs."

^ self _seekTo: offset opcode: 2
%

category: 'Reading'
method: GsFile
skip: count

"Changes the receiver's position in its file by the given offset, which may be
 zero, but not negative.  Returns the new position, or nil if an error occurs."

| pos |
pos := self position.
^ self position: pos + count
%

category: 'Private'
method: GsFile
_newStdFile: stdId isClient: clientBool

  "Initialize the receiver to represent a standard file on the client."

  clientBool ifTrue:[ isClient := true ] ifFalse:[ isClient := false ].
  stdId == 0 ifTrue: [mode := 'r'. pathName := 'stdin']
          ifFalse:[mode := 'w'.
                   stdId == 1 ifTrue: [pathName := 'stdout']
                             ifFalse:[pathName := 'stderr'].
                  ].

%

category: 'Writing'
method: GsFile
write: byteObj itemCount: itemCount ofSize: bytesPerItem

"Writes itemCount items of size bytesPerItem from the given byte object to
 the receiver's file.  bytesPerItem must be between 1 and 4096 inclusive.
 Returns a count of bytes written, or nil if an error occurs."

(isClient _isSmallInteger _and:[ self isCommitted == false]) ifTrue:[ ^ self _setNotOpenError ].
^ self userAction: #GsfWrite onClient: isClient
       with: byteObj with: bytesPerItem with: itemCount
%

category: 'Private'
method: GsFile
_seekTo: offset opcode: seekKind

"seekKind  function
   0        seek to  start of file + offset
   1        seek  offset from current position
   2        seek to  end of file - offset

 Returns the new position, or nil if an error occurs."

(isClient _isSmallInteger _and:[ self isCommitted == false]) ifTrue:[ ^ self _setNotOpenError ].
^ self userAction: #GsfSeek onClient: isClient with: offset with: seekKind
%

category: 'Comparing'
method: GsFile
hash

"Returns a SmallInteger related to the value of the receiver.  If two instances
 of GsFile are equal (as compared by the = method), then they must have the same
 hash value."

^ self id hash
%

category: 'Comparing'
method: GsFile
= aFile

"Returns true if the receiver and aFile represent the same file system file.
 Returns false otherwise.

 Result is always false if either receiver or argument are not open."

| idSelf |
(self == aFile) ifTrue: [^true].
aFile == nil ifTrue: [^false].
(aFile isKindOf: self class) ifFalse: [^false].
idSelf := self id.
(idSelf == 0) ifTrue: [^false].
^idSelf == (aFile id)
%

category: 'Accessing'
method: GsFile
id

"Returns the C-language file pointer to the standard I/O used by the receiver.

 The result will be a ByteArray created by GciPointerToByteArray,
 and holding the value of a C pointer. 

 Returns SmallInteger 0 if none exists."

 self isCommitted ifFalse:[
   isClient _isSmallInteger ifTrue:[
     ^ 0   "instance is transient and 'closed' "
   ].
 ].
 ^ self userAction: #GsfGetId onClient: isClient
%

category: 'Comparing'
method: GsFile
~= aFile

"Returns false if the receiver and aFile represent the same file system file.
 Returns true otherwise."

^ (self = aFile) not
%

category: 'Private'
method: GsFile
userAction: actionName onClient: onClient

"Executes a GsFile user action, passing it the following arguments:
    self
    any arguments to the primitive (see other uses of primitive 396)
 Maximum of 6 arguments (6 with: keywords) for this primitive.
 actionName must be a Symbol "

<primitive: 396>

^ self _primitiveFailed: #userAction:onClient:
%

category: 'Private'
method: GsFile
userAction: actionName onClient: onClient with: arg1

"See GsFile | userAction:onClient: for documentation."

<primitive: 396>

^ self _primitiveFailed: #userAction:onClient:with:
%

category: 'Private'
method: GsFile
userAction: actionName onClient: onClient with: arg1 with: arg2

"See GsFile | userAction:onClient: for documentation."

<primitive: 396>

^ self _primitiveFailed: #userAction:onClient:with:with:
%

category: 'Private'
method: GsFile
userAction: actionName onClient: onClient with: arg1 with: arg2 with: arg3

"See GsFile | userAction:onClient: for documentation."

<primitive: 396>

^ self _primitiveFailed: #userAction:onClient:with:with:with:
%
