'From Cuis 1.0 of 28 November 2009 [latest update: #337] on 7 December 2009 at 2:32:56 pm'!

!StandardFileStream commentStamp: 'ul 12/6/2009 05:13' prior: 0!
Provides a simple, platform-independent, interface to a file system. The instance variable rwmode, inherited from class PositionableStream, here is used to hold a Boolean -- true means opened for read-write, false means opened for read-only.  2/12/96 sw

I implement a simple read buffering scheme with the variables defined in PositionableStream (which are unused in me otherwise) in the following way:
	collection	<ByteString> or <ByteArray>	This is the buffer.
	position	<Integer>	The relative position in the buffer. Greater or equal to zero.
	readLimit	<Integer>	The number of bytes buffered. Greater or equal to zero.
Read buffering is enabled with #enableReadBuffering, disabled with #disableReadBuffering and it is enabled by default. The buffer is filled when a read attempt of an unbuffered absolute position is requested, or when a negative repositioning is made (with #position: with an argument < than the current absolute position) to an absolute position which is not buffered. In the first case, the buffer is positioned to the given absolute position. In the latter case the repositioning is made to the requested absolute position minus fourth of the buffer size. This means that further small negative repositionings won't result in buffer flushing. This is really useful when filing in code.
The read buffer is flushed (#flushReadBuffer) whenever a write attempt is made.
The buffer state is valid if and only if collection is not nil and position < readLimit.!


!ByteArray methodsFor: 'converting' stamp: 'jcg 9/1/2009 00:30'!
base64Encoded
	"Encode the receiver as base64"
	"'Hello World' asByteArray base64Encoded"
	^(Base64MimeConverter mimeEncode: self readStream) contents! !


!FileDirectory methodsFor: 'enumeration' stamp: 'stephaneducasse 2/4/2006 20:31'!
withAllSubdirectoriesCollect: aBlock
	"Evaluate aBlock with each of the directories in the subtree of the file system whose root is this directory.
	Answer the results of these evaluations."

	| result todo dir |
	result := OrderedCollection new: 100.
	todo := OrderedCollection with: self.
	[todo size > 0] whileTrue: [
		dir := todo removeFirst.
		result add: (aBlock value: dir).
		dir directoryNames do: [:n | todo add: (dir directoryNamed: n)]].
	^ result
! !

!FileDirectory methodsFor: 'comparing' stamp: 'ar 10/4/2009 22:22'!
= aDirectory
	"Compare two FileDirectory instances."
	^(pathName asString 
			compare: aDirectory pathName asString 
			caseSensitive: (self isCaseSensitive | aDirectory isCaseSensitive)) = 2! !

!FileDirectory methodsFor: 'comparing' stamp: 'cwp 11/16/2009 22:10'!
hash
	"Hash is reimplemented because #= is reimplemented"
	^pathName asString asLowercase hash! !

!FileDirectory methodsFor: 'searching' stamp: 'stephaneducasse 2/4/2006 20:31'!
filesContaining: searchString caseSensitive: aBoolean
	| aList |
	"Search the contents of all files in the receiver and its subdirectories for the search string.  Return a list of paths found.  Make the search case sensitive if aBoolean is true."

	aList := OrderedCollection new.
	self withAllFilesDo: [:stream |
			(stream contentsOfEntireFile includesSubstring: searchString caseSensitive: aBoolean)
				ifTrue:	[aList add: stream name]]
		andDirectoriesDo: [:d | d pathName].
	^ aList

"FileDirectory default filesContaining: 'includesSubstring:'  caseSensitive: true"! !

!FileDirectory methodsFor: 'searching' stamp: 'jmv 12/6/2009 22:48'!
withAllFilesDo: fileStreamBlock andDirectoriesDo: directoryBlock

	"For the receiver and all it's subdirectories evaluate directoryBlock.
	For a read only file stream on each file within the receiver 
	and it's subdirectories evaluate fileStreamBlock."

	| todo dir |

	todo := OrderedCollection with: self.
	[todo size > 0] whileTrue: [
		dir := todo removeFirst.
		directoryBlock value: dir.
		dir fileNames do: [: n |
			"For example, files inside a Mac .app file are not accessible, and the stream is nil"
			(FileStream oldFileOrNoneNamed: (dir fullNameFor: n))
				ifNotNil: [ :fileStream | fileStreamBlock value: fileStream ]].
		dir directoryNames do: [: n | 
			todo add: (dir directoryNamed: n)]]

! !


!FileDirectory class methodsFor: 'name utilities' stamp: 'jmv 12/6/2009 22:49'!
searchAllFilesForAString

	"Prompt the user for a search string, and a starting directory. Search the contents of all files in the starting directory and its subdirectories for the search string (case-insensitive search.)
	List the paths of files in which it is found on the Transcript.
	By Stewart MacLean 5/00; subsequently moved to FileDirectory class-side, and refactored to call FileDirectory.filesContaining:caseSensitive:"

	| searchString dir |

	searchString _ FillInTheBlank request: 'Enter search string'.
	searchString isEmpty ifTrue: [^nil].
	Transcript cr; show: 'Searching for ', searchString printString, ' ...'.
	(dir _ FileList2 modalFolderSelector) ifNotNil: [
		(dir filesContaining: searchString caseSensitive: false) do: [ :pathname |
			Transcript cr; show: pathname]].
	Transcript cr; show: 'Finished searching for ', searchString printString

	"FileDirectory searchAllFilesForAString"! !


!RemoteString methodsFor: 'accessing' stamp: 'ls 9/4/2003 12:37'!
text 
	"Answer the receiver's string asText if remote files are enabled."
	| theFile |
	(sourceFileNumber == nil or: [(SourceFiles at: sourceFileNumber) == nil]) ifTrue: [^ nil].
	theFile := SourceFiles at: sourceFileNumber.
	theFile position: filePositionHi.
	theFile position > theFile size ifTrue: [
		self error: 'RemoteString past end of file' ].
	^ theFile nextChunkText! !

!RemoteString methodsFor: 'private' stamp: 'jcg 9/1/2009 00:28'!
checkSum: aString
	"Construct a checksum of the string.  A three byte number represented as Base64 characters."
	| sum shift bytes |
	sum := aString size.
	shift := 0.
	aString do: [:char |
		(shift := shift + 7) > 16 ifTrue: [shift := shift - 17].
			"shift by 7 to keep a change of adjacent chars from xoring to same value"
		sum := sum bitXor: (char asInteger bitShift: shift)
	].
	bytes := ByteArray new: 3.
	sum := sum + 16r10000000000.
	1 to: 3 do: [:ind | bytes at: ind put: (sum digitAt: ind)].
	^bytes base64Encoded! !


!StandardFileStream methodsFor: 'open/close' stamp: 'ul 12/4/2009 07:18'!
close
	"Close this file."

	fileID ifNotNil: [
		collection ifNotNil: [
			readLimit := position := 0 ].
		self primClose: fileID.
		self unregister.
		fileID := nil].
! !

!StandardFileStream methodsFor: 'open/close' stamp: 'ul 12/6/2009 02:45'!
closed
	"Answer true if this file is closed."

	^ fileID == nil or: [ (self primSizeNoError: fileID) == nil ]
! !

!StandardFileStream methodsFor: 'open/close' stamp: 'jmv 12/6/2009 23:18'!
open: fileName forWrite: writeMode
	"Open the file with the given name. If writeMode is true, allow writing, otherwise open the file in read-only mode."
	"Changed to do a GC and retry before failing ar 3/21/98 17:25"
	fileID _ StandardFileStream retryWithGC:[self primOpen: fileName writable: writeMode] 
					until:[:id| id notNil] 
					forFileNamed: fileName.
	fileID ifNil: [^ nil].  "allows sender to detect failure"
	self register.
	name _ fileName.
	rwmode _ writeMode.
	buffer1 _ String new: 1.
	self enableReadBuffering! !

!StandardFileStream methodsFor: 'open/close' stamp: 'ul 12/6/2009 04:11'!
reopen
	"Close and reopen this file. The file position is reset to zero."
	"Details: Files that were open when a snapshot occurs are no longer valid when the snapshot is resumed. This operation re-opens the file if that has happened."

	fileID ifNotNil: [
		collection ifNotNil: [
			position < readLimit ifTrue: [
				self flushReadBuffer ] ].
		self primCloseNoError: fileID ].
	self open: name forWrite: rwmode.
! !

!StandardFileStream methodsFor: 'properties-setting' stamp: 'ul 12/3/2009 02:17'!
ascii
	"opposite of binary"
	buffer1 := String new: 1.
	collection ifNotNil: [ collection := collection asString ]! !

!StandardFileStream methodsFor: 'properties-setting' stamp: 'ul 12/3/2009 02:18'!
binary
	buffer1 := ByteArray new: 1.
	collection ifNotNil: [ collection := collection asByteArray ]! !

!StandardFileStream methodsFor: 'read, write, position' stamp: 'ul 12/3/2009 06:44'!
atEnd
	"Answer whether the receiver is at its end.  "
	
	collection ifNotNil: [
		position < readLimit ifTrue: [ ^false ] ].
	^self primAtEnd: fileID! !

!StandardFileStream methodsFor: 'read, write, position' stamp: 'ul 12/6/2009 04:10'!
basicNext
	"Answer the next byte from this file, or nil if at the end of the file."
	
	| count |
	<primitive: 65>
	collection ifNotNil: [
		position < readLimit 
			ifFalse: [ 
				readLimit := self primRead: fileID into: collection startingAt: 1 count: collection size.
				position := 0.
				readLimit = 0 ifTrue: [ ^nil ] ].
		^collection at: (position := position + 1) ].	
	count := self primRead: fileID into: buffer1 startingAt: 1 count: 1.
	count = 1
		ifTrue: [ ^buffer1 at: 1 ]
		ifFalse: [ ^nil ]! !

!StandardFileStream methodsFor: 'read, write, position' stamp: 'ul 12/5/2009 07:19'!
next: n
	"Return a string with the next n characters of the filestream in it.  1/31/96 sw"
	^ self nextInto: (self collectionSpecies new: n)! !

!StandardFileStream methodsFor: 'read, write, position' stamp: 'ul 12/6/2009 04:14'!
next: n into: aString startingAt: startIndex
	"Read n bytes into the given string.
	Return aString or a partial copy if less than
	n elements have been read."
	
	| count  newN newStartIndex |
	collection 
		ifNil: [ 
			newN := n.
			newStartIndex := startIndex ]
		ifNotNil: [
			aString class isBytes 
				ifFalse: [ 
					position < readLimit ifTrue: [ self flushReadBuffer ].
					newN := n.
					newStartIndex := startIndex ]
				ifTrue: [
					| available |
					(available := readLimit - position) > 0 
						ifFalse: [ available := 0 ]
						ifTrue: [
							| bufferedCount |
							bufferedCount := n min: available.
							aString
								replaceFrom: startIndex
								to: startIndex + bufferedCount - 1
								with: collection
								startingAt: position + 1.
							position := position + bufferedCount.
							bufferedCount = n ifTrue: [ ^aString ] ].
					newN := n - available.
					newStartIndex := startIndex + available ] ].
	count := self primRead: fileID into: aString
				startingAt: newStartIndex count: newN.
	count = newN
		ifTrue:[ ^aString ]
		ifFalse:[ ^aString copyFrom: 1 to: newStartIndex + count - 1 ]! !

!StandardFileStream methodsFor: 'read, write, position' stamp: 'ul 12/6/2009 04:11'!
next: anInteger putAll: aString startingAt: startIndex
	"Store the next anInteger elements from the given collection."
	rwmode ifFalse: [^ self error: 'Cannot write a read-only file'].
	collection ifNotNil: [
		position < readLimit ifTrue: [ self flushReadBuffer ] ].	
	self primWrite: fileID from: aString startingAt: startIndex count: anInteger.
	^aString! !

!StandardFileStream methodsFor: 'read, write, position' stamp: 'ul 12/6/2009 04:11'!
nextPut: char
	"Write the given character to this file."

	rwmode ifFalse: [^ self error: 'Cannot write a read-only file'].
	collection ifNotNil: [ 
		position < readLimit ifTrue: [ self flushReadBuffer ] ].
	buffer1 at: 1 put: char.
	self primWrite: fileID from: buffer1 startingAt: 1 count: 1.
	^ char
! !

!StandardFileStream methodsFor: 'read, write, position' stamp: 'ul 12/6/2009 04:11'!
nextPutAll: aString
	"Write all the characters of the given string to this file."

	rwmode ifFalse: [^ self error: 'Cannot write a read-only file'].
	collection ifNotNil: [ 
		position < readLimit ifTrue: [ self flushReadBuffer ] ].
	self primWrite: fileID from: aString startingAt: 1 count: aString basicSize.
	^ aString
! !

!StandardFileStream methodsFor: 'read, write, position' stamp: 'ul 12/5/2009 07:21'!
padToEndWith: aChar
	"On the Mac, files do not truncate.  One can delete the old file and write a new one, but sometime deletion fails (file still open? file stale?).  This is a sad compromise.  Just let the file be the same length but pad it with a harmless character."

	| pad |
	self atEnd ifTrue: [^ self].
	pad := self isBinary 
		ifTrue: [aChar asCharacter asciiValue]	"ok for char or number"
		ifFalse: [aChar asCharacter].
	self nextPutAll: (self collectionSpecies new: ((self size - self position) min: 20000) 
							withAll: pad).! !

!StandardFileStream methodsFor: 'read, write, position' stamp: 'ul 12/4/2009 06:59'!
position
	"Return the receiver's current file position.  2/12/96 sw"

	collection ifNotNil: [
		position < readLimit ifTrue: [
			^(self primGetPosition: fileID) - readLimit + position ] ].
	^self primGetPosition: fileID! !

!StandardFileStream methodsFor: 'read, write, position' stamp: 'ul 12/6/2009 04:08'!
position: pos
	"Set the receiver's position as indicated.  2/12/96 sw"

	collection ifNotNil: [
		position < readLimit ifTrue: [
			| newPosition |
			newPosition := pos - (self primGetPosition: fileID) + readLimit.
			newPosition < 0 ifTrue: [
					| offsetPos |
					self primSetPosition: fileID to: (offsetPos := pos - (collection size // 4) max: 0).
					readLimit := self primRead: fileID into: collection startingAt: 1 count: collection size.
					position := pos - offsetPos.
					^self ].
			newPosition < readLimit 
				ifTrue: [
					position := newPosition.
					^self ]
				ifFalse: [
					readLimit := position := 0 ] ] ].
	^self primSetPosition: fileID to: pos! !

!StandardFileStream methodsFor: 'read, write, position' stamp: 'jmv 12/7/2009 14:30'!
readInto: byteArray startingAt: startIndex count: count
	"Read into the given array as specified, and return the count
	actually transferred.  index and count are in units of bytes or
	longs depending on whether the array is Bitmap, String or ByteArray"

	^(self next: count into: byteArray startingAt: startIndex) size - startIndex + 1! !

!StandardFileStream methodsFor: 'read, write, position' stamp: 'yo 10/31/2002 22:33'!
readOnlyCopy

	^ self class readOnlyFileNamed: self name.
! !

!StandardFileStream methodsFor: 'read, write, position' stamp: 'ul 12/4/2009 19:19'!
upTo: delim 
	"Fast version to speed up nextChunk"
	| pos buffer count |
	collection ifNotNil: [
		(position < readLimit and: [
			(count := collection indexOf: delim startingAt: position + 1) <= readLimit and: [
				count > 0 ] ]) ifTrue: [
					^collection copyFrom: position + 1 to: (position := position + count) ] ].
	pos := self position.
	buffer := self next: 2000.
	(count := buffer indexOf: delim) > 0 ifTrue: 
		["Found the delimiter part way into buffer"
		self position: pos + count.
		^ buffer copyFrom: 1 to: count - 1].
	self atEnd ifTrue:
		["Never found it, and hit end of file"
		^ buffer].
	"Never found it, but there's more..."
	^ buffer , (self upTo: delim)! !

!StandardFileStream methodsFor: 'read, write, position' stamp: 'ul 12/3/2009 06:42'!
upToEnd
	"Answer a subcollection from the current access position through the last element of the receiver."

	^self collectionSpecies streamContents: [ :newStream |
		| next |
		[ (next := self next) == nil ] whileFalse: [
			newStream nextPut: next ] ]! !

!StandardFileStream methodsFor: 'browser requests' stamp: 'jmv 12/6/2009 23:19'!
post: data target: target url: url ifError: errorBlock
	"Post data to the given URL. The returned file stream contains the reply of the server.
	If Squeak is not running in a browser evaluate errorBlock"
	| sema index request result |
	self waitBrowserReadyFor: self defaultBrowserReadyWait ifFail: [^errorBlock value].
	sema _ Semaphore new.
	index _ Smalltalk registerExternalObject: sema.
	request _ self primURLPost: url target: target data: data semaIndex: index.
	request == nil ifTrue:[
	
	Smalltalk unregisterExternalObject: sema.
		^errorBlock value.
	] ifFalse:[
		[sema wait. "until something happens"
		result _ self primURLRequestState: request.
		result == nil] whileTrue.
		result ifTrue:[fileID _ self primURLRequestFileHandle: request].
		self primURLRequestDestroy: request.
	].
	Smalltalk unregisterExternalObject: sema.
	fileID == nil ifTrue:[^nil].
	self register.
	name _ url.
	rwmode _ false.
	buffer1 _ String new: 1.
	self enableReadBuffering! !

!StandardFileStream methodsFor: 'browser requests' stamp: 'jmv 12/6/2009 23:17'!
requestURL: url target: target ifError: errorBlock
	"Request to go to the target for the given URL.
	If Squeak is not running in a browser evaluate errorBlock"

	| sema index request result |
	self waitBrowserReadyFor: self defaultBrowserReadyWait ifFail: [^errorBlock value].
	sema _ Semaphore new.
	index _ Smalltalk registerExternalObject: sema.
	request _ self primURLRequest: url target: target semaIndex: index.
	request == nil ifTrue:[
	
	Smalltalk unregisterExternalObject: sema.
		^errorBlock value.
	] ifFalse:[
		[sema wait. "until something happens"
		result _ self primURLRequestState: request.
		result == nil] whileTrue.
		self primURLRequestDestroy: request.
	].
	Smalltalk unregisterExternalObject: sema.
	fileID == nil ifTrue:[^nil].
	self register.
	name _ url.
	rwmode _ false.
	buffer1 _ String new: 1.
	self enableReadBuffering! !

!StandardFileStream methodsFor: 'browser requests' stamp: 'jmv 12/6/2009 23:17'!
requestURLStream: url ifError: errorBlock
	"Request a FileStream for the given URL.
	If Squeak is not running in a browser evaluate errorBlock"
	"FileStream requestURLStream:'http://www.squeak.org'"
	| sema index request result |
	self waitBrowserReadyFor: self defaultBrowserReadyWait ifFail: [^errorBlock value].
	sema _ Semaphore new.
	index _ Smalltalk registerExternalObject: sema.
	request _ self primURLRequest: url semaIndex: index.
	request == nil ifTrue:[
	
	Smalltalk unregisterExternalObject: sema.
		^errorBlock value.
	] ifFalse:[
		[sema wait. "until something happens"
		result _ self primURLRequestState: request.
		result == nil] whileTrue.
		result ifTrue:[fileID _ self primURLRequestFileHandle: request].
		self primURLRequestDestroy: request.
	].
	Smalltalk unregisterExternalObject: sema.
	fileID == nil ifTrue:[^nil].
	self register.
	name _ url.
	rwmode _ false.
	buffer1 _ String new: 1.
	self enableReadBuffering! !

!StandardFileStream methodsFor: 'dnd requests' stamp: 'jmv 12/6/2009 23:16'!
requestDropStream: dropIndex
	"Return a read-only stream for some file the user has just dropped onto Squeak."
	name _ self primDropRequestFileName: dropIndex.
	fileID _ self primDropRequestFileHandle: dropIndex.
	fileID == nil ifTrue:[^nil].
	self register.
	rwmode _ false.
	buffer1 _ String new: 1.
	self enableReadBuffering! !

!StandardFileStream methodsFor: 'private' stamp: 'nice 11/22/2009 18:03'!
collectionSpecies
	"Answer the species of collection into which the receiver can stream"
	
	^buffer1 species! !

!StandardFileStream methodsFor: 'private' stamp: 'ul 12/6/2009 04:12'!
disableReadBuffering

	collection ifNotNil: [
		position < readLimit
			ifTrue: [
				| currentPosition |
				currentPosition := self position.
				collection := readLimit := position := nil.
				self position: currentPosition ]
			ifFalse: [
				collection := readLimit := position := nil ] ]
		! !

!StandardFileStream methodsFor: 'private' stamp: 'ul 12/6/2009 02:49'!
enableReadBuffering

	collection ifNil: [
		buffer1 ifNotNil: [
			collection := self collectionSpecies new: 2048 ] ].
	readLimit := position := 0! !

!StandardFileStream methodsFor: 'private' stamp: 'ul 12/6/2009 04:11'!
flushReadBuffer

	collection ifNotNil: [
		position < readLimit ifTrue: [
			| currentPosition |
			currentPosition := self position.
			position := readLimit := 0.
			self primSetPosition: fileID to: currentPosition ] ]! !


!StandardSourceFileArray class methodsFor: 'initialize-release' stamp: 'nk 7/30/2004 21:50'!
install
	"Replace SourceFiles by an instance of me with the standard sources and changes files.
	This only works if SourceFiles is either an Array or an instance of this class"

	"StandardSourceFileArray install"

	SourceFiles := self new! !


!String methodsFor: 'comparing' stamp: 'ar 4/10/2005 16:42'!
compare: aString caseSensitive: aBool
	"Answer a comparison code telling how the receiver sorts relative to aString:
		1 - before
		2 - equal
		3 - after.
	"
	| map |
	map := aBool ifTrue:[CaseSensitiveOrder] ifFalse:[CaseInsensitiveOrder].
	^self compare: self with: aString collated: map! !

!String methodsFor: 'converting' stamp: 'ar 7/22/2009 22:00'!
base64Decoded
	"Decode the receiver from base 64"
	"'SGVsbG8gV29ybGQ=' base64Decoded"
	^(Base64MimeConverter mimeDecode: self as: self class)! !

!String methodsFor: 'converting' stamp: 'ar 7/22/2009 22:00'!
base64Encoded
	"Encode the receiver as base64"
	"'Hello World' base64Encoded"
	^(Base64MimeConverter mimeEncode: (ReadStream on: self)) contents! !

FileDirectory removeSelector: #upLoadProject:named:resourceUrl:retry:!
"Postscript:
Leave the line above, and replace the rest of this comment by a useful one.
Executable statements should follow this comment, and should
be separated by periods, with no exclamation points (!!).
Be sure to put any further comments in double-quotes, like this one."
StandardFileStream allSubInstancesDo: [ :each | each enableReadBuffering ]!

