diff --git a/src/BaselineOfBasicTools/BaselineOfBasicTools.class.st b/src/BaselineOfBasicTools/BaselineOfBasicTools.class.st index e0a6e51d7bf..50cbc5e448e 100644 --- a/src/BaselineOfBasicTools/BaselineOfBasicTools.class.st +++ b/src/BaselineOfBasicTools/BaselineOfBasicTools.class.st @@ -44,6 +44,7 @@ BaselineOfBasicTools >> baseline: spec [ baseline: 'Debugging' with: [ spec loads: 'Core' from: repository ]; baseline: 'UnifiedFFI' with: [ spec repository: repository ]; baseline: 'ThreadedFFI' with: [ spec repository: repository ]; + baseline: 'NewFiles' with: [ spec repository: repository ]; baseline: 'UIInfrastructure' with: [ spec repository: repository ]; baseline: 'UI' with: [ spec repository: repository ]; baseline: 'Reflectivity' with: [ spec repository: repository ]; diff --git a/src/BaselineOfNewFiles/BaselineOfNewFiles.class.st b/src/BaselineOfNewFiles/BaselineOfNewFiles.class.st new file mode 100644 index 00000000000..479e57e4d67 --- /dev/null +++ b/src/BaselineOfNewFiles/BaselineOfNewFiles.class.st @@ -0,0 +1,19 @@ +Class { + #name : 'BaselineOfNewFiles', + #superclass : 'BaselineOf', + #category : 'BaselineOfNewFiles', + #package : 'BaselineOfNewFiles' +} + +{ #category : 'baselines' } +BaselineOfNewFiles >> baseline: spec [ + + spec for: #'common' do: [ + spec + package: 'NewFiles'; + package: 'NewFiles-Tests'. + spec + group: 'Core' with: #('NewFiles'); + group: 'Tests' with: #('NewFiles-Tests'); + group: 'default' with: #('Core' 'Tests') ] +] diff --git a/src/BaselineOfNewFiles/package.st b/src/BaselineOfNewFiles/package.st new file mode 100644 index 00000000000..f42685da66b --- /dev/null +++ b/src/BaselineOfNewFiles/package.st @@ -0,0 +1 @@ +Package { #name : 'BaselineOfNewFiles' } diff --git a/src/FileSystem-Disk/DiskStore.class.st b/src/FileSystem-Disk/DiskStore.class.st index 0f7bd3aab9d..32db867217a 100644 --- a/src/FileSystem-Disk/DiskStore.class.st +++ b/src/FileSystem-Disk/DiskStore.class.st @@ -447,7 +447,9 @@ DiskStore >> gidOf: aPath [ { #category : 'accessing' } DiskStore >> handleClass [ - ^ FileHandle + ^ (Smalltalk globals at: #FileAPI ifAbsent: [ nil ]) + ifNil: [ FileHandle ] + ifNotNil: [ :fileAPI | fileAPI currentFileClass ] ] { #category : 'comparing' } diff --git a/src/NewFiles-Tests/FileAPIBenchmarks.class.st b/src/NewFiles-Tests/FileAPIBenchmarks.class.st new file mode 100644 index 00000000000..9a65980ca61 --- /dev/null +++ b/src/NewFiles-Tests/FileAPIBenchmarks.class.st @@ -0,0 +1,165 @@ +" +I hold different benchmarks for testing the old file plugin vs the new file plugin. +" +Class { + #name : 'FileAPIBenchmarks', + #superclass : 'Object', + #instVars : [ + 'fileAPI', + 'file', + 'random', + 'randomData', + 'smallRandomData', + 'randomOffsets' + ], + #classVars : [ + 'RandomDataSize', + 'ReadBufferCount', + 'SmallRandomDataSize', + 'SmallReadBufferCount', + 'SmallWriteBufferCount', + 'WriteBufferCount' + ], + #category : 'NewFiles-Tests', + #package : 'NewFiles-Tests' +} + +{ #category : 'class initialization' } +FileAPIBenchmarks class >> initialize [ + RandomDataSize := 1000000. + SmallRandomDataSize := 2048. + + SmallWriteBufferCount := 100000. + SmallReadBufferCount := SmallWriteBufferCount. + + WriteBufferCount := 1000. + ReadBufferCount := WriteBufferCount. + +] + +{ #category : 'as yet unclassified' } +FileAPIBenchmarks class >> runWithFileAPI: aFileAPI [ + ^ self new fileAPI: aFileAPI; run; yourself +] + +{ #category : 'accessing' } +FileAPIBenchmarks >> fileAPI [ + + ^ fileAPI +] + +{ #category : 'accessing' } +FileAPIBenchmarks >> fileAPI: anObject [ + + fileAPI := anObject +] + +{ #category : 'as yet unclassified' } +FileAPIBenchmarks >> generateRandomData [ + random := Random seed: 42. + + randomData := ByteArray new: RandomDataSize. + 1 to: RandomDataSize do: [ :index | + randomData at: index put: (random nextInteger: 255) + ]. + + smallRandomData := ByteArray new: SmallRandomDataSize. + 1 to: SmallRandomDataSize do: [ :index | + smallRandomData at: index put: (random nextInteger: 255) + ]. + + randomOffsets := IntegerArray new: SmallWriteBufferCount. + 1 to: SmallWriteBufferCount do: [ :i | + randomOffsets at: i put: (random nextInteger: RandomDataSize // 10) + ]. +] + +{ #category : 'as yet unclassified' } +FileAPIBenchmarks >> measure: aBlock withSampleCount: sampleCount title: title [ + | samples sampleIOSize sampleTime sampleSpeed average stdev | + samples := Array new: sampleCount. + 1 to: sampleCount do: [ :sampleIndex | + sampleIOSize := 0. + sampleTime := [ + sampleIOSize := aBlock value: sampleIndex + ] microsecondsToRun * 1e-6. + sampleSpeed := sampleIOSize asFloat / (sampleTime max: 1e-6) * 1e-6. + + samples at: sampleIndex put: sampleSpeed + ]. + + average := samples average. + stdev := samples stdev. + Transcript show: title; + show: ' '; show: sampleCount; show: ' samples'; + show: ' speed (MB/s): '; show: average; show: ' +- '; show: stdev; cr. +] + +{ #category : 'writing' } +FileAPIBenchmarks >> readData [ + | readBuffer | + readBuffer := ByteArray new: randomData size. + file position: 0. + self measure: [ :sampleIndex | + | readCount | + readCount := file readInto: readBuffer. + self assert: readCount >= 0. + readCount + ] withSampleCount: ReadBufferCount title: 'readData' + +] + +{ #category : 'running' } +FileAPIBenchmarks >> run [ + | randomDatasetGenerationTime | + file := fileAPI openCreateAlways: fileAPI apiName , '-Benchmarks.bin'. + [ + Transcript show: fileAPI apiName; show: ' Benchmarks'; cr. + + randomDatasetGenerationTime := [ self generateRandomData ] timeToRun asMicroseconds * 0.001. + Transcript show: 'RandomDatasetGenerationTime '; show: randomDatasetGenerationTime asString; show: ' ms'; cr. + + self writeDataAtRandomOffsets. + self writeSmallData. + self writeData. + self readData. + ] ensure: [ + file close + ] + +] + +{ #category : 'writing' } +FileAPIBenchmarks >> writeData [ + file position: 0. + self measure: [ :sampleIndex | + | writeCount | + writeCount := file writeFrom: randomData. + self assert: writeCount >= 0. + writeCount + ] withSampleCount: WriteBufferCount title: 'writeData' +] + +{ #category : 'writing' } +FileAPIBenchmarks >> writeDataAtRandomOffsets [ + self measure: [ :sampleIndex | + | writeCount | + file position: (randomOffsets at: sampleIndex). + writeCount := file writeFrom: smallRandomData. + self assert: writeCount >= 0. + writeCount + ] withSampleCount: randomOffsets size title: 'writeDataAtRandomOffsets'. + +] + +{ #category : 'writing' } +FileAPIBenchmarks >> writeSmallData [ + file position: 0. + self measure: [ :sampleIndex | + | writeCount | + file position: (randomOffsets at: sampleIndex). + writeCount := file writeFrom: smallRandomData. + self assert: writeCount >= 0. + writeCount + ] withSampleCount: SmallWriteBufferCount title: 'writeSmallData'. +] diff --git a/src/NewFiles-Tests/FileAPITest.class.st b/src/NewFiles-Tests/FileAPITest.class.st new file mode 100644 index 00000000000..4ac7186fe7d --- /dev/null +++ b/src/NewFiles-Tests/FileAPITest.class.st @@ -0,0 +1,140 @@ +Class { + #name : 'FileAPITest', + #superclass : 'TestCase', + #category : 'NewFiles-Tests', + #package : 'NewFiles-Tests' +} + +{ #category : 'tests' } +FileAPITest >> testHelloWorld [ + | path text file buffer decodedText | + path := 'test.txt'. + text := 'Hello World'. + + file := FileAPI openCreateAlways: path. + file writeFrom: text utf8Encoded. + file close. + + file := FileAPI openReadOnly: path. + buffer := ByteArray new: file size. + file readInto: buffer. + file close. + + decodedText := buffer utf8Decoded. + self assert: decodedText equals: text. + + FileAPI deleteFile: path. +] + +{ #category : 'tests' } +FileAPITest >> testHelloWorld2 [ + | path text file buffer decodedText | + path := 'test.txt'. + text := 'Hello World'. + + file := FileAPI openCreateAlways: path. + file writeFrom: text utf8Encoded. + + buffer := ByteArray new: file size. + file position: 0. + file readInto: buffer. + file close. + + decodedText := buffer utf8Decoded. + self assert: decodedText equals: text. + + FileAPI deleteFile: path. +] + +{ #category : 'tests' } +FileAPITest >> testHelloWorldAbsolutePath [ + | path text file buffer decodedText | + path := 'test.txt' asFileReference asAbsolute fullName. + text := 'Hello World'. + + file := FileAPI openCreateAlways: path. + file writeFrom: text utf8Encoded. + file close. + + file := FileAPI openReadOnly: path. + buffer := ByteArray new: file size. + file readInto: buffer. + file close. + + decodedText := buffer utf8Decoded. + self assert: decodedText equals: text. + + FileAPI deleteFile: path. +] + +{ #category : 'tests' } +FileAPITest >> testHelloWorldAtFileOffset [ + | path text file buffer decodedText | + path := 'test.txt'. + text := 'Hello World'. + + file := FileAPI openCreateAlways: path. + file writeFrom: text utf8Encoded at: 0. + file close. + + file := FileAPI openReadOnly: path. + buffer := ByteArray new: file size. + file readInto: buffer at: 0. + file close. + + decodedText := buffer utf8Decoded. + self assert: decodedText equals: text. + + FileAPI deleteFile: path. +] + +{ #category : 'tests' } +FileAPITest >> testHelloWorldAtFileOffset2 [ + | path text file buffer decodedText | + path := 'test.txt'. + text := 'Hello World'. + + file := FileAPI openCreateAlways: path. + file writeFrom: text utf8Encoded at: 0. + + buffer := ByteArray new: file size. + file readInto: buffer at: 0. + + file close. + + decodedText := buffer utf8Decoded. + self assert: decodedText equals: text. + + FileAPI deleteFile: path. +] + +{ #category : 'tests' } +FileAPITest >> testHelloWorldContents [ + | path text file buffer decodedText | + path := 'test.txt'. + text := 'Hello World'. + + file := FileAPI openCreateAlways: path. + file writeFrom: text utf8Encoded. + + buffer := file contents. + file close. + + decodedText := buffer utf8Decoded. + self assert: decodedText equals: text. + + FileAPI deleteFile: path. +] + +{ #category : 'tests' } +FileAPITest >> testTruncate [ + | path file | + path := 'test.bin'. + + file := FileAPI openCreateAlways: path. + file truncateToSize: 4096. + self assert: file size equals: 4096. + file close. + + FileAPI deleteFile: path. +] diff --git a/src/NewFiles-Tests/NewBinaryFileStreamTest.class.st b/src/NewFiles-Tests/NewBinaryFileStreamTest.class.st new file mode 100644 index 00000000000..6988e13427c --- /dev/null +++ b/src/NewFiles-Tests/NewBinaryFileStreamTest.class.st @@ -0,0 +1,251 @@ +" +A NewBinaryFileStreamTest is a test class for testing the behavior of NewBinaryFileStream +" +Class { + #name : 'NewBinaryFileStreamTest', + #superclass : 'TestCase', + #category : 'NewFiles-Tests', + #package : 'NewFiles-Tests' +} + +{ #category : 'private' } +NewBinaryFileStreamTest >> fileStreamForFileNamed: fileName [ + ^ (FileAPI openAlways: fileName) binaryStream +] + +{ #category : 'private' } +NewBinaryFileStreamTest >> killTestFile [ + (FileAPI exists: 'testFile') ifTrue: [ + FileAPI deleteFile: 'testFile' + ]. + +] + +{ #category : 'running' } +NewBinaryFileStreamTest >> setUp [ + self killTestFile. + +] + +{ #category : 'running' } +NewBinaryFileStreamTest >> tearDown [ + self killTestFile. + +] + +{ #category : 'tests' } +NewBinaryFileStreamTest >> testEmptyFileIsAtEnd [ + | file | + file := self fileStreamForFileNamed: 'testFile'. + self assert: file atEnd. + file close. +] + +{ #category : 'tests' } +NewBinaryFileStreamTest >> testFileWithSomeBytesSizeIsNotZero [ + + | file | + file := self fileStreamForFileNamed: 'testFile'. + file nextPut: 1. + self assert: file position equals: 1. + file nextPutAll: #[1 2 3]. + self assert: file position equals: 4. + file nextPut: 1. + self assert: file position equals: 5. + file close. +] + +{ #category : 'tests' } +NewBinaryFileStreamTest >> testFullFileIsAtEnd [ + + | file | + + file := self fileStreamForFileNamed: 'testFile'. + file nextPut: 1. + self assert: file atEnd. + file close. +] + +{ #category : 'tests' } +NewBinaryFileStreamTest >> testOpenFile [ + + | file | + + file := self fileStreamForFileNamed: 'testFile'. + file close. + + self assert: (FileAPI exists: 'testFile') +] + +{ #category : 'tests' } +NewBinaryFileStreamTest >> testPeekDoesNotAdvanceTheStream [ + + | file | + file := self fileStreamForFileNamed: 'testFile'. + file nextPut: 1. + file nextPut: 2. + file nextPut: 3. + file close. + + file := self fileStreamForFileNamed: 'testFile'. + self assert: file position equals: 0. + self assert: file peek equals: file peek. + self assert: file position equals: 0. + file close. +] + +{ #category : 'tests' } +NewBinaryFileStreamTest >> testReadFullFileIsAtEnd [ + + | file | + + file := self fileStreamForFileNamed: 'testFile'. + file nextPut: 1. + file close. + + file := self fileStreamForFileNamed: 'testFile'. + file next. + self assert: file atEnd. + file close. +] + +{ #category : 'tests' } +NewBinaryFileStreamTest >> testReadLessThanAvailableYieldsJustRead [ + + | file | + + file := self fileStreamForFileNamed: 'testFile'. + file nextPut: 1. + file nextPut: 2. + file nextPut: 3. + file close. + + file := self fileStreamForFileNamed: 'testFile'. + self assert: (file next:2) equals: #[1 2]. + file close. +] + +{ #category : 'tests' } +NewBinaryFileStreamTest >> testReadMoreThanAvailableYieldsOnlyAvailable [ + + | file | + + file := self fileStreamForFileNamed: 'testFile'. + self assert: (file next:2) equals: #[]. + + "then we put one element and we close it" + file nextPut: 1. + file close. + + file := self fileStreamForFileNamed: 'testFile'. + self assert: (file next:2) equals: #[1]. + file close. +] + +{ #category : 'tests' } +NewBinaryFileStreamTest >> testReadMultipleBytes [ + + | file | + + file := self fileStreamForFileNamed: 'testFile'. + file nextPut: 1. + file nextPut: 2. + file close. + + file := self fileStreamForFileNamed: 'testFile'. + self assert: (file next: 2) equals: #[1 2]. + file close. +] + +{ #category : 'tests' } +NewBinaryFileStreamTest >> testReadWhenNothingAvailableYieldsNil [ + + | file | + + file := self fileStreamForFileNamed: 'testFile'. + self assert: file next equals: nil. + file close. +] + +{ #category : 'tests' } +NewBinaryFileStreamTest >> testSkipLecture [ + + | file | + + file := self fileStreamForFileNamed: 'testFile'. + file nextPut: 1. + file nextPut: 2. + file nextPut: 3. + file close. + + file := self fileStreamForFileNamed: 'testFile'. + file skip: 2. + self assert: file next equals: 3. + file close. +] + +{ #category : 'tests' } +NewBinaryFileStreamTest >> testWriteFromStartingAtCount [ + + | file | + + file := self fileStreamForFileNamed: 'testFile'. + file writeFrom: #[1 2 3] startingAt: 2 count: 2. + file close. + + file := self fileStreamForFileNamed: 'testFile'. + self assert: file next equals: 2. + self assert: file next equals: 3. + file close. +] + +{ #category : 'tests' } +NewBinaryFileStreamTest >> testWriteMultipleBytes [ + + | file | + + file := self fileStreamForFileNamed: 'testFile'. + file nextPutAll: #[1 2]. + file close. + + file := self fileStreamForFileNamed: 'testFile'. + self assert: file next equals: 1. + self assert: file next equals: 2. + file close. +] + +{ #category : 'tests' } +NewBinaryFileStreamTest >> testWriteReadInt [ + + | file | + + file := self fileStreamForFileNamed: 'testFile'. + file nextPut: 1. + file close. + + file := self fileStreamForFileNamed: 'testFile'. + self assert: file next equals: 1. + file close. +] + +{ #category : 'tests' } +NewBinaryFileStreamTest >> testWriteReadInt2 [ + + | file | + + file := self fileStreamForFileNamed: 'testFile'. + file nextPut: 1. + file position: 0. + self assert: file next equals: 1. + file close. +] + +{ #category : 'tests' } +NewBinaryFileStreamTest >> testWriteToClosedFileFails [ + + | fileStream | + + fileStream := self fileStreamForFileNamed: 'testFile'. + fileStream close. + self should: [ fileStream wrappedStream nextPut: 1 ] raise: Error +] diff --git a/src/NewFiles-Tests/NewFileMemoryMappedStreamTest.class.st b/src/NewFiles-Tests/NewFileMemoryMappedStreamTest.class.st new file mode 100644 index 00000000000..f0ee224bec7 --- /dev/null +++ b/src/NewFiles-Tests/NewFileMemoryMappedStreamTest.class.st @@ -0,0 +1,46 @@ +" +A NewFileMemoryMappedStreamTest is a test class for testing the behavior of NewFileMemoryMappedStream +" +Class { + #name : 'NewFileMemoryMappedStreamTest', + #superclass : 'TestCase', + #category : 'NewFiles-Tests', + #package : 'NewFiles-Tests' +} + +{ #category : 'tests' } +NewFileMemoryMappedStreamTest >> testReadOnly [ + | testData mappingView stream testRead | + + testData := 'Hello World' utf8Encoded. + mappingView := NewFileMemoryMappingView readOnlyWithByteArray: testData. + self assert: mappingView size equals: testData size. + + stream := mappingView stream. + self assert: stream next equals: $H asInteger. + + testRead := stream next: 4. + self assert: testRead equals: 'ello' utf8Encoded. + + self should: [ stream nextPut: $A asInteger ] raise: Error. +] + +{ #category : 'tests' } +NewFileMemoryMappedStreamTest >> testReadWrite [ + | testData mappingView stream testRead | + + testData := 'Hello World' utf8Encoded. + mappingView := NewFileMemoryMappingView readWriteWithByteArray: testData. + self assert: mappingView size equals: testData size. + + stream := mappingView stream. + self assert: stream next equals: $H asInteger. + + testRead := stream next: 4. + self assert: testRead equals: 'ello' utf8Encoded. + + stream nextPut: $A asInteger. + stream nextPutAll: 'test' utf8Encoded. + + self assert: testData equals: 'HelloAtestd' utf8Encoded. +] diff --git a/src/NewFiles-Tests/NewFileMemoryMappingViewTest.class.st b/src/NewFiles-Tests/NewFileMemoryMappingViewTest.class.st new file mode 100644 index 00000000000..0bbc932ca68 --- /dev/null +++ b/src/NewFiles-Tests/NewFileMemoryMappingViewTest.class.st @@ -0,0 +1,25 @@ +" +A NewFileMemoryMappingViewTest is a test class for testing the behavior of NewFileMemoryMappingView +" +Class { + #name : 'NewFileMemoryMappingViewTest', + #superclass : 'TestCase', + #category : 'NewFiles-Tests', + #package : 'NewFiles-Tests' +} + +{ #category : 'tests' } +NewFileMemoryMappingViewTest >> testReadOnly [ + | testData mappingView | + testData := 'Hello World' utf8Encoded. + mappingView := NewFileMemoryMappingView readOnlyWithByteArray: testData. + self assert: mappingView size equals: testData size. +] + +{ #category : 'tests' } +NewFileMemoryMappingViewTest >> testReadWrite [ + | testData mappingView | + testData := 'Hello World' utf8Encoded. + mappingView := NewFileMemoryMappingView readWriteWithByteArray: testData. + self assert: mappingView size equals: testData size. +] diff --git a/src/NewFiles-Tests/NewFileTest.class.st b/src/NewFiles-Tests/NewFileTest.class.st new file mode 100644 index 00000000000..dd7716e27e0 --- /dev/null +++ b/src/NewFiles-Tests/NewFileTest.class.st @@ -0,0 +1,180 @@ +" +A NewFileTest is a test class for testing the behavior of NewFile +" +Class { + #name : 'NewFileTest', + #superclass : 'TestCase', + #category : 'NewFiles-Tests', + #package : 'NewFiles-Tests' +} + +{ #category : 'tests' } +NewFileTest >> testHelloWorld [ + | path text file buffer decodedText | + NewFile primitiveIsAvailable ifFalse: [ ^ self skip ]. + + path := 'test.txt'. + text := 'Hello World'. + + file := NewFile openCreateAlways: path. + file writeFrom: text utf8Encoded. + file close. + + file := NewFile openReadOnly: path. + buffer := ByteArray new: file size. + file readInto: buffer. + file close. + + decodedText := buffer utf8Decoded. + self assert: decodedText equals: text. + + NewFile deleteFile: path. +] + +{ #category : 'tests' } +NewFileTest >> testHelloWorld2 [ + | path text file buffer decodedText | + NewFile primitiveIsAvailable ifFalse: [ ^ self skip ]. + + path := 'test.txt'. + text := 'Hello World'. + + file := NewFile openCreateAlways: path. + file writeFrom: text utf8Encoded. + + buffer := ByteArray new: file size. + file position: 0. + file readInto: buffer. + file close. + + decodedText := buffer utf8Decoded. + self assert: decodedText equals: text. + + NewFile deleteFile: path. +] + +{ #category : 'tests' } +NewFileTest >> testHelloWorldAbsolutePath [ + | path text file buffer decodedText | + NewFile primitiveIsAvailable ifFalse: [ ^ self skip ]. + + path := 'test.txt' asFileReference asAbsolute fullName. + text := 'Hello World'. + + file := NewFile openCreateAlways: path. + file writeFrom: text utf8Encoded. + file close. + + file := NewFile openReadOnly: path. + buffer := ByteArray new: file size. + file readInto: buffer. + file close. + + decodedText := buffer utf8Decoded. + self assert: decodedText equals: text. + + NewFile deleteFile: path. +] + +{ #category : 'tests' } +NewFileTest >> testHelloWorldAtFileOffset [ + | path text file buffer decodedText | + NewFile primitiveIsAvailable ifFalse: [ ^ self skip ]. + + path := 'test.txt'. + text := 'Hello World'. + + file := NewFile openCreateAlways: path. + file writeFrom: text utf8Encoded at: 0. + file close. + + file := NewFile openReadOnly: path. + buffer := ByteArray new: file size. + file readInto: buffer at: 0. + file close. + + decodedText := buffer utf8Decoded. + self assert: decodedText equals: text. + + NewFile deleteFile: path. +] + +{ #category : 'tests' } +NewFileTest >> testHelloWorldAtFileOffset2 [ + | path text file buffer decodedText | + NewFile primitiveIsAvailable ifFalse: [ ^ self skip ]. + + path := 'test.txt'. + text := 'Hello World'. + + file := NewFile openCreateAlways: path. + file writeFrom: text utf8Encoded at: 0. + + buffer := ByteArray new: file size. + file readInto: buffer at: 0. + + file close. + + decodedText := buffer utf8Decoded. + self assert: decodedText equals: text. + + NewFile deleteFile: path. +] + +{ #category : 'tests' } +NewFileTest >> testHelloWorldContents [ + | path text file buffer decodedText | + NewFile primitiveIsAvailable ifFalse: [ ^ self skip ]. + + path := 'test.txt'. + text := 'Hello World'. + + file := NewFile openCreateAlways: path. + file writeFrom: text utf8Encoded. + + buffer := file contents. + file close. + + decodedText := buffer utf8Decoded. + self assert: decodedText equals: text. + + NewFile deleteFile: path. +] + +{ #category : 'tests' } +NewFileTest >> testHelloWorldReadOnlyMemoryMapping [ + | path text file buffer decodedText memoryMap | + NewFile primitiveIsAvailable ifFalse: [ ^ self skip ]. + + path := 'test.txt'. + text := 'Hello World'. + + file := NewFile openCreateAlways: path. + file writeFrom: text utf8Encoded. + + memoryMap := file readOnlyMemoryMap. + buffer := memoryMap contents. + + memoryMap unmap. + file close. + + decodedText := buffer utf8Decoded. + self assert: decodedText equals: text. + + NewFile deleteFile: path. +] + +{ #category : 'tests' } +NewFileTest >> testTruncate [ + | path file | + NewFile primitiveIsAvailable ifFalse: [ ^ self skip ]. + + path := 'test.bin'. + + file := NewFile openCreateAlways: path. + file truncateToSize: 4096. + self assert: file size equals: 4096. + file close. + + NewFile deleteFile: path. +] diff --git a/src/NewFiles-Tests/OldFileTest.class.st b/src/NewFiles-Tests/OldFileTest.class.st new file mode 100644 index 00000000000..e267efa91ba --- /dev/null +++ b/src/NewFiles-Tests/OldFileTest.class.st @@ -0,0 +1,140 @@ +Class { + #name : 'OldFileTest', + #superclass : 'TestCase', + #category : 'NewFiles-Tests', + #package : 'NewFiles-Tests' +} + +{ #category : 'tests' } +OldFileTest >> testHelloWorld [ + | path text file buffer decodedText | + path := 'test.txt'. + text := 'Hello World'. + + file := OldFile openCreateAlways: path. + file writeFrom: text utf8Encoded. + file close. + + file := OldFile openReadOnly: path. + buffer := ByteArray new: file size. + file readInto: buffer. + file close. + + decodedText := buffer utf8Decoded. + self assert: decodedText equals: text. + + OldFile deleteFile: path. +] + +{ #category : 'tests' } +OldFileTest >> testHelloWorld2 [ + | path text file buffer decodedText | + path := 'test.txt'. + text := 'Hello World'. + + file := OldFile openCreateAlways: path. + file writeFrom: text utf8Encoded. + + buffer := ByteArray new: file size. + file position: 0. + file readInto: buffer. + file close. + + decodedText := buffer utf8Decoded. + self assert: decodedText equals: text. + + OldFile deleteFile: path. +] + +{ #category : 'tests' } +OldFileTest >> testHelloWorldAbsolutePath [ + | path text file buffer decodedText | + path := 'test.txt' asFileReference asAbsolute fullName. + text := 'Hello World'. + + file := OldFile openCreateAlways: path. + file writeFrom: text utf8Encoded. + file close. + + file := OldFile openReadOnly: path. + buffer := ByteArray new: file size. + file readInto: buffer. + file close. + + decodedText := buffer utf8Decoded. + self assert: decodedText equals: text. + + OldFile deleteFile: path. +] + +{ #category : 'tests' } +OldFileTest >> testHelloWorldAtFileOffset [ + | path text file buffer decodedText | + path := 'test.txt'. + text := 'Hello World'. + + file := OldFile openCreateAlways: path. + file writeFrom: text utf8Encoded at: 0. + file close. + + file := OldFile openReadOnly: path. + buffer := ByteArray new: file size. + file readInto: buffer at: 0. + file close. + + decodedText := buffer utf8Decoded. + self assert: decodedText equals: text. + + OldFile deleteFile: path. +] + +{ #category : 'tests' } +OldFileTest >> testHelloWorldAtFileOffset2 [ + | path text file buffer decodedText | + path := 'test.txt'. + text := 'Hello World'. + + file := OldFile openCreateAlways: path. + file writeFrom: text utf8Encoded at: 0. + + buffer := ByteArray new: file size. + file readInto: buffer at: 0. + + file close. + + decodedText := buffer utf8Decoded. + self assert: decodedText equals: text. + + OldFile deleteFile: path. +] + +{ #category : 'tests' } +OldFileTest >> testHelloWorldContents [ + | path text file buffer decodedText | + path := 'test.txt'. + text := 'Hello World'. + + file := OldFile openCreateAlways: path. + file writeFrom: text utf8Encoded. + + buffer := file contents. + file close. + + decodedText := buffer utf8Decoded. + self assert: decodedText equals: text. + + OldFile deleteFile: path. +] + +{ #category : 'tests' } +OldFileTest >> testTruncate [ + | path file | + path := 'test.bin'. + + file := OldFile openCreateAlways: path. + file truncateToSize: 4096. + self assert: file size equals: 4096. + file close. + + OldFile deleteFile: path. +] diff --git a/src/NewFiles-Tests/package.st b/src/NewFiles-Tests/package.st new file mode 100644 index 00000000000..42e0f5b77bb --- /dev/null +++ b/src/NewFiles-Tests/package.st @@ -0,0 +1 @@ +Package { #name : 'NewFiles-Tests' } diff --git a/src/NewFiles/AbstractFile.class.st b/src/NewFiles/AbstractFile.class.st new file mode 100644 index 00000000000..0c8becba779 --- /dev/null +++ b/src/NewFiles/AbstractFile.class.st @@ -0,0 +1,56 @@ +" +I am an abstract file handle. +" +Class { + #name : 'AbstractFile', + #superclass : 'FFIExternalObject', + #instVars : [ + 'reference' + ], + #pools : [ + 'NewFileConstants' + ], + #category : 'NewFiles', + #package : 'NewFiles' +} + +{ #category : 'public' } +AbstractFile class >> open: aReference writable: aBoolean [ + self subclassResponsibility +] + +{ #category : 'accessing' } +AbstractFile >> binaryReadStream [ + ^ NewBinaryFileStream forFile: self forWrite: false +] + +{ #category : 'accessing' } +AbstractFile >> binaryStream [ + ^ NewBinaryFileStream forFile: self forWrite: self isForWrite +] + +{ #category : 'accessing' } +AbstractFile >> binaryWriteStream [ + self assert: self isForWrite. + ^ NewBinaryFileStream forFile: self forWrite: self isForWrite +] + +{ #category : 'accessing' } +AbstractFile >> fileSystem [ + ^ reference fileSystem +] + +{ #category : 'testing' } +AbstractFile >> isForWrite [ + ^ self subclassResponsibility +] + +{ #category : 'accessing' } +AbstractFile >> reference [ + ^ reference +] + +{ #category : 'accessing' } +AbstractFile >> setReference: newReference [ + reference := newReference +] diff --git a/src/NewFiles/DirectoryAPI.class.st b/src/NewFiles/DirectoryAPI.class.st new file mode 100644 index 00000000000..eaa72a21756 --- /dev/null +++ b/src/NewFiles/DirectoryAPI.class.st @@ -0,0 +1,39 @@ +" +I am a strategy for for choosing between the old and new directory implementation. +" +Class { + #name : 'DirectoryAPI', + #superclass : 'Object', + #category : 'NewFiles', + #package : 'NewFiles' +} + +{ #category : 'api' } +DirectoryAPI class >> contentsOf: path [ + ^ self currentDirectoryClass contentsOf: path +] + +{ #category : 'api' } +DirectoryAPI class >> create: path [ + ^ self currentDirectoryClass create: path +] + +{ #category : 'private' } +DirectoryAPI class >> currentDirectoryClass [ + ^ self useNewDirectory ifTrue: [ NewDirectory ] ifFalse: [ OldDirectory ] +] + +{ #category : 'api' } +DirectoryAPI class >> open: path [ + ^ self currentDirectoryClass open: path +] + +{ #category : 'api' } +DirectoryAPI class >> removeEmpty: path [ + ^ self currentDirectoryClass removeEmpty: path +] + +{ #category : 'private' } +DirectoryAPI class >> useNewDirectory [ + ^ NewFile primitiveIsAvailable +] diff --git a/src/NewFiles/FileAPI.class.st b/src/NewFiles/FileAPI.class.st new file mode 100644 index 00000000000..d69e914a2c0 --- /dev/null +++ b/src/NewFiles/FileAPI.class.st @@ -0,0 +1,44 @@ +" +I am a strategy for for choosing between the old and new file implementation. +" +Class { + #name : 'FileAPI', + #superclass : 'Object', + #category : 'NewFiles', + #package : 'NewFiles' +} + +{ #category : 'private' } +FileAPI class >> currentFileClass [ + ^ self useNewFile ifTrue: [ NewFile ] ifFalse: [ OldFile ] +] + +{ #category : 'file api' } +FileAPI class >> deleteFile: path [ + ^ self currentFileClass deleteFile: path +] + +{ #category : 'file api' } +FileAPI class >> exists: path [ + ^ self currentFileClass exists: path +] + +{ #category : 'file api' } +FileAPI class >> openAlways: path [ + ^ self currentFileClass openAlways: path +] + +{ #category : 'file api' } +FileAPI class >> openCreateAlways: path [ + ^ self currentFileClass openCreateAlways: path +] + +{ #category : 'file api' } +FileAPI class >> openReadOnly: path [ + ^ self currentFileClass openReadOnly: path +] + +{ #category : 'private' } +FileAPI class >> useNewFile [ + ^ NewFile primitiveIsAvailable +] diff --git a/src/NewFiles/NewAbstractBinaryFileStream.class.st b/src/NewFiles/NewAbstractBinaryFileStream.class.st new file mode 100644 index 00000000000..153c74c61b0 --- /dev/null +++ b/src/NewFiles/NewAbstractBinaryFileStream.class.st @@ -0,0 +1,196 @@ +Class { + #name : 'NewAbstractBinaryFileStream', + #superclass : 'Stream', + #instVars : [ + 'file', + 'forWrite' + ], + #pools : [ + 'NewFileConstants' + ], + #category : 'NewFiles-Streams', + #package : 'NewFiles', + #tag : 'Streams' +} + +{ #category : 'construction' } +NewAbstractBinaryFileStream class >> forFile: aFile forWrite: aBoolean [ + ^ self basicNew + initializeWithFile: aFile forWrite: aBoolean; + yourself +] + +{ #category : 'testing' } +NewAbstractBinaryFileStream >> atEnd [ + ^ file atEnd +] + +{ #category : 'open/close' } +NewAbstractBinaryFileStream >> close [ + file ifNotNil: [ file close ]. + file := nil +] + +{ #category : 'accessing' } +NewAbstractBinaryFileStream >> contents [ + ^ file contents +] + +{ #category : 'character writing' } +NewAbstractBinaryFileStream >> cr [ + self nextPut: Character cr asInteger. +] + +{ #category : 'character writing' } +NewAbstractBinaryFileStream >> crlf [ + self nextPutAll: String crlf +] + +{ #category : 'accessing' } +NewAbstractBinaryFileStream >> file [ + + ^ file +] + +{ #category : 'accessing' } +NewAbstractBinaryFileStream >> file: anObject [ + + file := anObject +] + +{ #category : 'flushing' } +NewAbstractBinaryFileStream >> flush [ + self flag: 'Todo implement flushing'. + +] + +{ #category : 'accessing' } +NewAbstractBinaryFileStream >> forWrite [ + + ^ forWrite +] + +{ #category : 'accessing' } +NewAbstractBinaryFileStream >> forWrite: anObject [ + + forWrite := anObject +] + +{ #category : 'initialization' } +NewAbstractBinaryFileStream >> initializeWithFile: aFile forWrite: aBoolean [ + file := aFile. + forWrite := aBoolean. +] + +{ #category : 'testing' } +NewAbstractBinaryFileStream >> isBinary [ + ^ true +] + +{ #category : 'character writing' } +NewAbstractBinaryFileStream >> lf [ + self nextPut: Character lf asInteger. +] + +{ #category : 'accessing' } +NewAbstractBinaryFileStream >> next [ + "Answer the next byte from this file, or nil if at the end of the file." + + ^ (self next: 1) ifEmpty: [ nil ] ifNotEmpty: [ :col | col first ] +] + +{ #category : 'accessing' } +NewAbstractBinaryFileStream >> next: n [ + "Return a string with the next n characters of the filestream in it." + + ^ self next: n into: (ByteArray new: n) +] + +{ #category : 'accessing' } +NewAbstractBinaryFileStream >> next: n into: aBuffer [ + "Return a string with the next n characters of the filestream in it." + | readBuffer read | + readBuffer := aBuffer. + read := file readInto: aBuffer offset: 0 withSize: n. + ^read = n + ifTrue: [ readBuffer ] + ifFalse: [ readBuffer copyFrom: 1 to: read ] +] + +{ #category : 'character writing' } +NewAbstractBinaryFileStream >> next: amount putAll: aByteArray [ + + ^ self next: amount putAll: aByteArray startingAt: 1 +] + +{ #category : 'character writing' } +NewAbstractBinaryFileStream >> next: amount putAll: aByteArray startingAt: startingIndex [ + + forWrite + ifFalse: [ ^ self error: 'Cannot write a read-only file' ]. + file writeFrom: aByteArray offset: startingIndex - 1 withSize: amount. + ^ aByteArray +] + +{ #category : 'accessing' } +NewAbstractBinaryFileStream >> nextInto: aBuffer [ + "Return a string with the next n characters of the filestream in it." + + ^ self next: aBuffer size into: aBuffer +] + +{ #category : 'character writing' } +NewAbstractBinaryFileStream >> nextPut: anInteger [ + + ^ self nextPutAll: (ByteArray with: anInteger asInteger) +] + +{ #category : 'character writing' } +NewAbstractBinaryFileStream >> nextPutAll: aByteArray [ + self next: aByteArray basicSize putAll: aByteArray +] + +{ #category : 'accessing' } +NewAbstractBinaryFileStream >> position [ + ^ file position +] + +{ #category : 'accessing' } +NewAbstractBinaryFileStream >> position: newPosition [ + ^ file position: newPosition +] + +{ #category : 'accessing' } +NewAbstractBinaryFileStream >> readInto: readBuffer startingAt: startIndex count: count [ + ^ file readInto: readBuffer offset: startIndex - 1 withSize: count +] + +{ #category : 'accessing' } +NewAbstractBinaryFileStream >> setToEnd [ + self position: self size +] + +{ #category : 'accessing' } +NewAbstractBinaryFileStream >> size [ + + ^ file size +] + +{ #category : 'accessing' } +NewAbstractBinaryFileStream >> skip: n [ + "Set the character position to n characters from the current position. + Error if not enough characters left in the file. + By default we read n characters and we avoid reading the output" + self next: n +] + +{ #category : 'flushing' } +NewAbstractBinaryFileStream >> sync [ + self flag: 'Todo implement sync'. + +] + +{ #category : 'accessing' } +NewAbstractBinaryFileStream >> writeFrom: writeBuffer startingAt: aNumber count: length [ + ^ file writeFrom: writeBuffer offset: aNumber - 1 withSize: length +] diff --git a/src/NewFiles/NewBinaryFileStream.class.st b/src/NewFiles/NewBinaryFileStream.class.st new file mode 100644 index 00000000000..37f962e8231 --- /dev/null +++ b/src/NewFiles/NewBinaryFileStream.class.st @@ -0,0 +1,35 @@ +Class { + #name : 'NewBinaryFileStream', + #superclass : 'NewAbstractBinaryFileStream', + #category : 'NewFiles-Streams', + #package : 'NewFiles', + #tag : 'Streams' +} + +{ #category : 'accessing' } +NewBinaryFileStream >> peek [ + "Answer what would be returned if the message next were sent to the receiver. If the receiver is at the end, answer nil. " + | next | + self atEnd ifTrue: [^ nil]. + next := self next. + self position: self position - 1. + ^ next +] + +{ #category : 'accessing' } +NewBinaryFileStream >> skip: n [ + "Set the character position to n characters from the current position." + self position: self position + n +] + +{ #category : 'accessing' } +NewBinaryFileStream >> truncate [ + + self truncate: 0 +] + +{ #category : 'accessing' } +NewBinaryFileStream >> truncate: pos [ + "Truncate to this position" + ^ file truncateToSize: pos +] diff --git a/src/NewFiles/NewDirectory.class.st b/src/NewFiles/NewDirectory.class.st new file mode 100644 index 00000000000..1b9a4b43ea6 --- /dev/null +++ b/src/NewFiles/NewDirectory.class.st @@ -0,0 +1,126 @@ +" +I am a directory stream. +" +Class { + #name : 'NewDirectory', + #superclass : 'FFIExternalObject', + #pools : [ + 'NewFileConstants' + ], + #category : 'NewFiles', + #package : 'NewFiles' +} + +{ #category : 'archive operations' } +NewDirectory class >> contentsOf: path [ + | directory | + directory := self open: path. + directory ifNil: [ + self error: 'Failed to open directory ' , path + ]. + + ^ [ + directory contents. + ] ensure: [ + directory close + ] +] + +{ #category : 'creation and destruction' } +NewDirectory class >> create: path [ + + ^ self primitiveFailed +] + +{ #category : 'finalization' } +NewDirectory class >> finalizeResourceData: aHandle [ + aHandle isNull ifTrue: [ ^ self ]. + self primitiveClose: aHandle. + aHandle beNull +] + +{ #category : 'creation and destruction' } +NewDirectory class >> open: path [ + | handle directory | + handle := self primitiveOpen: path. + handle isNull ifTrue: [ ^ nil ]. + + directory := self fromHandle: handle. + ^ directory autoRelease +] + +{ #category : 'private' } +NewDirectory class >> primitiveClose: aDirectory [ + + ^ self primitiveFailed + +] + +{ #category : 'private' } +NewDirectory class >> primitiveOpen: path [ + + ^ self primitiveFailed +] + +{ #category : 'creation and destruction' } +NewDirectory class >> removeEmpty: path [ + + ^ self primitiveFailed + +] + +{ #category : 'initialization' } +NewDirectory >> close [ + handle isNull ifTrue: [ ^ self ]. + self primitiveClose: handle. + handle beNull +] + +{ #category : 'private' } +NewDirectory >> contents [ + ^ Array streamContents: [:out | + | next | + self rewind. + [ (next := self next) ~~ nil ] whileTrue: [ + out nextPut: next + ] + ] +] + +{ #category : 'private' } +NewDirectory >> next [ + | nextNameAddress nextName | + nextNameAddress := self primitiveNext: handle. + nextNameAddress isNull ifTrue: [^ nil ]. + + nextName := nextNameAddress utf8StringFromCString. + nextName ifEmpty: [ ^ nil ]. + + ^ nextName +] + +{ #category : 'private' } +NewDirectory >> primitiveClose: aDirectory [ + + ^ self primitiveFailed + +] + +{ #category : 'private' } +NewDirectory >> primitiveNext: aHandle [ + + ^ self primitiveFailed + +] + +{ #category : 'private' } +NewDirectory >> primitiveRewind: aHandle [ + + ^ self primitiveFailed + +] + +{ #category : 'private' } +NewDirectory >> rewind [ + ^ self primitiveRewind: handle +] diff --git a/src/NewFiles/NewFile.class.st b/src/NewFiles/NewFile.class.st new file mode 100644 index 00000000000..2264e3acae1 --- /dev/null +++ b/src/NewFiles/NewFile.class.st @@ -0,0 +1,349 @@ +" +I am a new file. I support different kinds usages: +1) File stream support. +2) File read/write with explicit file offset. +3) Memory mapping. + +" +Class { + #name : 'NewFile', + #superclass : 'AbstractFile', + #instVars : [ + 'mode' + ], + #pools : [ + 'NewFileConstants' + ], + #category : 'NewFiles', + #package : 'NewFiles' +} + +{ #category : 'as yet unclassified' } +NewFile class >> apiName [ + ^ 'NewFile' +] + +{ #category : 'file primitives' } +NewFile class >> deleteFile: path [ + ^ self primitiveDeleteFile: path +] + +{ #category : 'public' } +NewFile class >> exists: aPath [ + | file | + file := self openReadOnly: aPath. + file ifNil: [ ^ false ]. + file close. + ^ true +] + +{ #category : 'finalization' } +NewFile class >> finalizeResourceData: aHandle [ + aHandle isNull ifTrue: [ ^ self ]. + self primitiveClose: aHandle. + aHandle beNull +] + +{ #category : 'file open' } +NewFile class >> open: path mode: mode creationDisposition: creationDisposition flags: flags [ + | encoded handle file | + encoded := File encodePathString: path. + handle := self primitiveOpen: encoded mode: mode creationDisposition: creationDisposition flags: flags. + handle isNull ifTrue: [ ^ nil ]. + + file := self fromHandle: handle. + + file mode: mode. + ^ file autoRelease +] + +{ #category : 'public' } +NewFile class >> open: aReference writable: isWriteable [ + | path file | + path := aReference fullName. + file := self open: path + mode: (isWriteable + ifTrue: [ NewFileOpenModeReadWrite ] + ifFalse: [NewFileOpenModeReadOnly]) + creationDisposition: NewFileCreationDispositionOpenAlways + flags: NewFileOpenFlagsNone. + + file ifNotNil: [ file setReference: aReference ]. + ^ file +] + +{ #category : 'file open' } +NewFile class >> openAlways: path [ + ^ self open: path mode: NewFileOpenModeReadWrite creationDisposition: NewFileCreationDispositionOpenAlways flags: NewFileOpenFlagsNone +] + +{ #category : 'file open' } +NewFile class >> openCreateAlways: path [ + ^ self open: path mode: NewFileOpenModeReadWrite creationDisposition: NewFileCreationDispositionCreateAlways flags: NewFileOpenFlagsNone +] + +{ #category : 'file open' } +NewFile class >> openCreateNew: path [ + ^ self open: path mode: NewFileOpenModeReadWrite creationDisposition: NewFileCreationDispositionCreateNew flags: NewFileOpenFlagsNone +] + +{ #category : 'file open' } +NewFile class >> openExisting: path [ + ^ self open: path mode: NewFileOpenModeReadWrite creationDisposition: NewFileCreationDispositionOpenExisting flags: NewFileOpenFlagsNone +] + +{ #category : 'file open' } +NewFile class >> openReadOnly: path [ + ^ self open: path mode: NewFileOpenModeReadOnly creationDisposition: NewFileCreationDispositionOpenExisting flags: NewFileOpenFlagsNone +] + +{ #category : 'file open' } +NewFile class >> openTruncateExisting: path [ + ^ self open: path mode: NewFileOpenModeReadWrite creationDisposition: NewFileCreationDispositionTruncateExisting flags: NewFileOpenFlagsNone +] + +{ #category : 'file primitives' } +NewFile class >> primitiveClose: aFile [ + + ^ self primitiveFailed + +] + +{ #category : 'file primitives' } +NewFile class >> primitiveDeleteFile: path [ + + ^ self primitiveFailed + +] + +{ #category : 'file primitives' } +NewFile class >> primitiveIsAvailable [ + + ^ false + +] + +{ #category : 'file primitives' } +NewFile class >> primitiveOpen: path mode: mode creationDisposition: creationDisposition flags: flags [ + + ^ self primitiveFailed +] + +{ #category : 'accessing' } +NewFile >> atEnd [ + ^ self position >= self size +] + +{ #category : 'initialization' } +NewFile >> close [ + handle isNull ifTrue: [ ^ self ]. + self primitiveClose: handle. + handle beNull +] + +{ #category : 'file api' } +NewFile >> contents [ + | buffer | + buffer := ByteArray new: self size. + self readInto: buffer at: 0. + ^ buffer +] + +{ #category : 'testing' } +NewFile >> isForWrite [ + ^ mode = NewFileOpenModeReadWrite +] + +{ #category : 'memory mapping' } +NewFile >> memoryMapWithProtection: protection [ + | mappingAddress | + mappingAddress := self primitiveMemoryMap: handle withProtection: protection. + mappingAddress isNull ifTrue: [ ^ nil ]. + + ^ NewFileMemoryMappingView new + address: mappingAddress; + file: self; + size: self size; + protection: protection; + yourself +] + +{ #category : 'accessing' } +NewFile >> mode [ + + ^ mode +] + +{ #category : 'accessing' } +NewFile >> mode: anObject [ + + mode := anObject +] + +{ #category : 'accessing' } +NewFile >> position [ + ^ self tell +] + +{ #category : 'accessing' } +NewFile >> position: aPosition [ + ^ self seek: aPosition mode: NewFileSeekModeSet +] + +{ #category : 'primitives' } +NewFile >> primitive: aHandle readInto: buffer offset: bufferOffset withSize: readSize [ + + ^ self primitiveFailed +] + +{ #category : 'primitives' } +NewFile >> primitive: aHandle readInto: buffer offset: bufferOffset withSize: readSize at: fileOffset [ + + ^ self primitiveFailed +] + +{ #category : 'primitives' } +NewFile >> primitive: aHandle seek: offset mode: seekMode [ + + ^ self primitiveFailed +] + +{ #category : 'primitives' } +NewFile >> primitive: aHandle writeFrom: buffer offset: bufferOffset withSize: bufferSize [ + + ^ self primitiveFailed +] + +{ #category : 'primitives' } +NewFile >> primitive: aHandle writeFrom: buffer offset: bufferOffset withSize: bufferSize at: fileOffset [ + + ^ self primitiveFailed +] + +{ #category : 'primitives' } +NewFile >> primitiveClose: aFileHandle [ + + ^ self primitiveFailed + +] + +{ #category : 'primitives' } +NewFile >> primitiveGetSize: handle [ + + ^ self primitiveFailed +] + +{ #category : 'primitives' } +NewFile >> primitiveMemoryMap: handle withProtection: protection [ + + ^ self primitiveFailed +] + +{ #category : 'primitives' } +NewFile >> primitiveMemoryUnmap: handle [ + + ^ self primitiveFailed +] + +{ #category : 'primitives' } +NewFile >> primitiveTell: handle [ + + ^ self primitiveFailed +] + +{ #category : 'primitives' } +NewFile >> primitiveTruncate: aHandle toSize: newSize [ + + ^ self primitiveFailed +] + +{ #category : 'file api' } +NewFile >> readInto: buffer [ + ^ self readInto: buffer offset: 0 withSize: buffer size +] + +{ #category : 'file api' } +NewFile >> readInto: buffer at: fileOffset [ + ^ self readInto: buffer offset: 0 withSize: buffer size at: fileOffset +] + +{ #category : 'file api' } +NewFile >> readInto: buffer offset: bufferOffset withSize: readSize [ + ^ self primitive: handle readInto: buffer offset: bufferOffset withSize: readSize +] + +{ #category : 'file api' } +NewFile >> readInto: buffer offset: bufferOffset withSize: readSize at: fileOffset [ + ^ self primitive: handle readInto: buffer offset: bufferOffset withSize: readSize at: fileOffset +] + +{ #category : 'file api' } +NewFile >> readInto: buffer withSize: readSize [ + ^ self readInto: buffer offset: 0 withSize: readSize +] + +{ #category : 'memory mapping' } +NewFile >> readOnlyMemoryMap [ + ^ self memoryMapWithProtection: NewFileMemMapProtectionReadOnly +] + +{ #category : 'memory mapping' } +NewFile >> readWriteMemoryMap [ + ^ self memoryMapWithProtection: NewFileMemMapProtectionReadWrite +] + +{ #category : 'file api' } +NewFile >> seek: offset mode: seekMode [ + ^ self primitive: handle seek: offset mode: seekMode +] + +{ #category : 'file api' } +NewFile >> size [ + ^ self primitiveGetSize: handle +] + +{ #category : 'file api' } +NewFile >> tell [ + ^ self primitiveTell: handle +] + +{ #category : 'file api' } +NewFile >> truncate [ + ^ self truncateToSize: 0 +] + +{ #category : 'file api' } +NewFile >> truncateToSize: newSize [ + ^ self primitiveTruncate: handle toSize: newSize +] + +{ #category : 'memory mapping' } +NewFile >> unmapView: aFileMemoryMappingView [ + self primitiveMemoryUnmap: handle. + aFileMemoryMappingView unmapped +] + +{ #category : 'file api' } +NewFile >> writeFrom: dataBuffer [ + ^ self writeFrom: dataBuffer offset: 0 withSize: dataBuffer size +] + +{ #category : 'file api' } +NewFile >> writeFrom: dataBuffer at: fileOffset [ + ^ self writeFrom: dataBuffer offset: 0 withSize: dataBuffer size at: fileOffset +] + +{ #category : 'file api' } +NewFile >> writeFrom: buffer offset: bufferOffset withSize: bufferSize [ + ^ self primitive: handle writeFrom: buffer offset: bufferOffset withSize: bufferSize +] + +{ #category : 'file api' } +NewFile >> writeFrom: buffer offset: bufferOffset withSize: bufferSize at: fileOffset [ + ^ self primitive: handle writeFrom: buffer offset: bufferOffset withSize: bufferSize at: fileOffset +] + +{ #category : 'file api' } +NewFile >> writeFrom: buffer withSize: bufferSize [ + ^ self writeFrom: buffer offset: 0 withSize: bufferSize +] diff --git a/src/NewFiles/NewFileConstants.class.st b/src/NewFiles/NewFileConstants.class.st new file mode 100644 index 00000000000..89720c0828c --- /dev/null +++ b/src/NewFiles/NewFileConstants.class.st @@ -0,0 +1,50 @@ +" +I hold new file constants. +" +Class { + #name : 'NewFileConstants', + #superclass : 'SharedPool', + #classVars : [ + 'NewFileCreationDispositionCreateAlways', + 'NewFileCreationDispositionCreateNew', + 'NewFileCreationDispositionOpenAlways', + 'NewFileCreationDispositionOpenExisting', + 'NewFileCreationDispositionTruncateExisting', + 'NewFileMemMapProtectionReadOnly', + 'NewFileMemMapProtectionReadWrite', + 'NewFileOpenFlagsAppend', + 'NewFileOpenFlagsNone', + 'NewFileOpenModeReadOnly', + 'NewFileOpenModeReadWrite', + 'NewFileOpenModeWriteOnly', + 'NewFileSeekModeCurrent', + 'NewFileSeekModeEnd', + 'NewFileSeekModeSet' + ], + #category : 'NewFiles', + #package : 'NewFiles' +} + +{ #category : 'class initialization' } +NewFileConstants class >> initialize [ + NewFileOpenModeReadOnly := 0. + NewFileOpenModeWriteOnly := 1. + NewFileOpenModeReadWrite := 2. + + NewFileOpenFlagsNone := 0. + NewFileOpenFlagsAppend := 1<<0. + + NewFileSeekModeSet := 0. + NewFileSeekModeCurrent := 1. + NewFileSeekModeEnd := 2. + + NewFileCreationDispositionCreateNew := 1. + NewFileCreationDispositionCreateAlways := 2. + NewFileCreationDispositionOpenExisting := 3. + NewFileCreationDispositionOpenAlways := 4. + NewFileCreationDispositionTruncateExisting := 5. + + NewFileMemMapProtectionReadOnly := 0. + NewFileMemMapProtectionReadWrite := 1. + +] diff --git a/src/NewFiles/NewFileMemoryMappedStream.class.st b/src/NewFiles/NewFileMemoryMappedStream.class.st new file mode 100644 index 00000000000..af27e86eec6 --- /dev/null +++ b/src/NewFiles/NewFileMemoryMappedStream.class.st @@ -0,0 +1,126 @@ +" +I am streaming interface for memory mapped files. +" +Class { + #name : 'NewFileMemoryMappedStream', + #superclass : 'Stream', + #instVars : [ + 'mappingView', + 'address', + 'position', + 'size', + 'hasWritePermission' + ], + #category : 'NewFiles-Streams', + #package : 'NewFiles', + #tag : 'Streams' +} + +{ #category : 'as yet unclassified' } +NewFileMemoryMappedStream class >> onFileMemoryMappingView: mappingView [ + ^ self basicNew + initializeWithFileMemoryMappingView: mappingView; + yourself +] + +{ #category : 'testing' } +NewFileMemoryMappedStream >> atEnd [ + ^ position >= size +] + +{ #category : 'accessing' } +NewFileMemoryMappedStream >> contents [ + ^ mappingView contents +] + +{ #category : 'initialization' } +NewFileMemoryMappedStream >> initializeWithFileMemoryMappingView: initialMappingView [ + mappingView := initialMappingView. + address := initialMappingView address. + position := 0. + size := initialMappingView size. + hasWritePermission := initialMappingView hasWritePermission. + +] + +{ #category : 'accessing' } +NewFileMemoryMappedStream >> next [ + | result | + position >= size ifTrue: [ ^ nil ]. + result := address uint8AtOffset: position. + position := position + 1. + ^ result +] + +{ #category : 'accessing' } +NewFileMemoryMappedStream >> next: n [ + | remainingSize resultSize buffer | + remainingSize := size - position. + resultSize := n min: remainingSize. + buffer := ByteArray new: n. + 1 to: resultSize do: [:i | + buffer at: i put: (address uint8AtOffset: position). + position := position + 1 + ]. + ^ buffer +] + +{ #category : 'accessing' } +NewFileMemoryMappedStream >> next: amount putAll: aByteArray [ + + ^ self next: amount putAll: aByteArray startingAt: 1 +] + +{ #category : 'accessing' } +NewFileMemoryMappedStream >> next: amount putAll: aByteArray startingAt: startingIndex [ + | remainingSize putAmount | + hasWritePermission ifFalse: [ + ^ self error: 'Cannot write into a read-only file memory mapping' + ]. + remainingSize := size - position. + putAmount := amount min: remainingSize. + putAmount > 0 ifTrue: [ + address replaceFrom: position + 1 to: position + putAmount with: aByteArray startingAt: startingIndex + ]. + ^ aByteArray +] + +{ #category : 'accessing' } +NewFileMemoryMappedStream >> nextPut: value [ + | result | + hasWritePermission ifFalse: [ + ^ self error: 'Cannot write into a read-only file memory mapping' + ]. + + position >= size ifTrue: [ ^ nil ]. + result := address uint8AtOffset: position put: value. + position := position + 1. + ^ result +] + +{ #category : 'accessing' } +NewFileMemoryMappedStream >> nextPutAll: aByteArray [ + aByteArray class = ByteArray ifFalse: [ + ^ super nextPutAll: aByteArray + ]. + + ^ self next: aByteArray basicSize putAll: aByteArray +] + +{ #category : 'accessing' } +NewFileMemoryMappedStream >> position [ + + ^ position +] + +{ #category : 'accessing' } +NewFileMemoryMappedStream >> position: anObject [ + + position := anObject +] + +{ #category : 'accessing' } +NewFileMemoryMappedStream >> size [ + + ^ size +] diff --git a/src/NewFiles/NewFileMemoryMappingView.class.st b/src/NewFiles/NewFileMemoryMappingView.class.st new file mode 100644 index 00000000000..f151bf01d3b --- /dev/null +++ b/src/NewFiles/NewFileMemoryMappingView.class.st @@ -0,0 +1,112 @@ +" +I hold metadata about a specific file memory mapped segment. +" +Class { + #name : 'NewFileMemoryMappingView', + #superclass : 'Object', + #instVars : [ + 'file', + 'size', + 'address', + 'protection' + ], + #pools : [ + 'NewFileConstants' + ], + #category : 'NewFiles', + #package : 'NewFiles' +} + +{ #category : 'mocks' } +NewFileMemoryMappingView class >> readOnlyWithByteArray: byteArray [ + ^ self new + address: byteArray; + size: byteArray size; + protection: NewFileMemMapProtectionReadOnly; + yourself +] + +{ #category : 'mocks' } +NewFileMemoryMappingView class >> readWriteWithByteArray: byteArray [ + ^ self new + address: byteArray; + size: byteArray size; + protection: NewFileMemMapProtectionReadWrite; + yourself +] + +{ #category : 'accessing' } +NewFileMemoryMappingView >> address [ + + ^ address +] + +{ #category : 'accessing' } +NewFileMemoryMappingView >> address: anObject [ + + address := anObject +] + +{ #category : 'accessing' } +NewFileMemoryMappingView >> contents [ + ^ address copyFrom: 1 to: size +] + +{ #category : 'accessing' } +NewFileMemoryMappingView >> file [ + + ^ file +] + +{ #category : 'accessing' } +NewFileMemoryMappingView >> file: anObject [ + + file := anObject +] + +{ #category : 'testing' } +NewFileMemoryMappingView >> hasWritePermission [ + ^ protection = NewFileMemMapProtectionReadWrite +] + +{ #category : 'accessing' } +NewFileMemoryMappingView >> protection [ + + ^ protection +] + +{ #category : 'accessing' } +NewFileMemoryMappingView >> protection: anObject [ + + protection := anObject +] + +{ #category : 'accessing' } +NewFileMemoryMappingView >> size [ + + ^ size +] + +{ #category : 'accessing' } +NewFileMemoryMappingView >> size: anObject [ + + size := anObject +] + +{ #category : 'accessing' } +NewFileMemoryMappingView >> stream [ + ^ NewFileMemoryMappedStream onFileMemoryMappingView: self +] + +{ #category : 'as yet unclassified' } +NewFileMemoryMappingView >> unmap [ + file ifNil: [ ^ self ]. + ^ file unmapView: self +] + +{ #category : 'as yet unclassified' } +NewFileMemoryMappingView >> unmapped [ + address := nil. + file := nil. + size := 0. +] diff --git a/src/NewFiles/OldDirectory.class.st b/src/NewFiles/OldDirectory.class.st new file mode 100644 index 00000000000..d69e2964cd5 --- /dev/null +++ b/src/NewFiles/OldDirectory.class.st @@ -0,0 +1,93 @@ +" +I provide legacy file plugin support. +" +Class { + #name : 'OldDirectory', + #superclass : 'FFIExternalObject', + #instVars : [ + 'dirPointer', + 'entryData' + ], + #pools : [ + 'NewFileConstants' + ], + #category : 'NewFiles', + #package : 'NewFiles' +} + +{ #category : 'creation and destruction' } +OldDirectory class >> contentsOf: path [ + | directory | + directory := self open: path. + directory ifNil: [ + self error: 'Failed to open directory ' , path + ]. + + ^ [ + directory contents. + ] ensure: [ + directory close + ] +] + +{ #category : 'creation and destruction' } +OldDirectory class >> create: path [ + ^ File createDirectory: path +] + +{ #category : 'creation and destruction' } +OldDirectory class >> open: path [ + | dirHandles dirPointer entryData | + dirHandles := File primOpendir: '.'. + dirHandles ifNil: [ ^ nil ]. + + self assert: (dirHandles isArray and: [ dirHandles size = 3 ]). + dirPointer := dirHandles at: 3. + entryData := dirHandles. + + ^ self new + initializeWithDirPointer: dirPointer andEntryData: entryData; + yourself +] + +{ #category : 'creation and destruction' } +OldDirectory class >> removeEmpty: path [ + ^ File deleteDirectory: path +] + +{ #category : 'accessing' } +OldDirectory >> close [ + File primClosedir: dirPointer +] + +{ #category : 'accessing' } +OldDirectory >> contents [ + ^ Array streamContents: [:out | + | next | + [ (next := self next) ~~ nil ] whileTrue: [ + out nextPut: next + ] + ] +] + +{ #category : 'initialization' } +OldDirectory >> initializeWithDirPointer: aDirPointer andEntryData: anEntryData [ + super initialize. + dirPointer := aDirPointer. + entryData := anEntryData.. +] + +{ #category : 'accessing' } +OldDirectory >> next [ + | entryResult | + entryResult := entryData. + entryResult ifNotNil: [ + entryData := nil. + ^ File decodePathString: entryResult first + ]. + + entryResult := File primReaddir: dirPointer. + entryResult ifNil: [ ^ nil ]. + + ^ File decodePathString: entryResult first +] diff --git a/src/NewFiles/OldFile.class.st b/src/NewFiles/OldFile.class.st new file mode 100644 index 00000000000..4d1b338825c --- /dev/null +++ b/src/NewFiles/OldFile.class.st @@ -0,0 +1,209 @@ +" +I provide legacy file plugin support. +" +Class { + #name : 'OldFile', + #superclass : 'AbstractFile', + #instVars : [ + 'isForWrite' + ], + #category : 'NewFiles', + #package : 'NewFiles' +} + +{ #category : 'accessing' } +OldFile class >> apiName [ + ^ 'OldFile' +] + +{ #category : 'primitives - files' } +OldFile class >> checkHandle: handle forPath: path [ + handle ifNil: [ + self error: 'Failed to open file ' , path printString + ]. +] + +{ #category : 'primitives - path' } +OldFile class >> deleteFile: path [ + ^ File deleteFile: path +] + +{ #category : 'public' } +OldFile class >> exists: aPath [ + ^ File exists: aPath +] + +{ #category : 'primitives - files' } +OldFile class >> finalizeResourceData: aHandle [ + (File closed: aHandle ) ifTrue: [ ^ self ]. + File close: aHandle +] + +{ #category : 'primitives - files' } +OldFile class >> makeWithHandle: handle forWrite: isForWrite [ + | file | + file := self fromHandle: handle. + file isForWrite: isForWrite. + ^ file autoRelease +] + +{ #category : 'public' } +OldFile class >> open: aReference writable: isWriteable [ + | path file | + path := aReference fullName. + file := isWriteable + ifTrue: [ self openAlways: path ] + ifFalse: [ self openReadOnly: path]. + file ifNotNil: [ file setReference: aReference ]. + ^ file +] + +{ #category : 'primitives - files' } +OldFile class >> openAlways: path [ + + | encoded handle | + encoded := File encodePathString: path. + handle := File open: encoded writable: true. + self checkHandle: handle forPath: path. + ^ self makeWithHandle: handle forWrite: true +] + +{ #category : 'primitives - files' } +OldFile class >> openCreateAlways: path [ + + | encoded handle | + encoded := File encodePathString: path. + handle := File open: encoded writable: true. + self checkHandle: handle forPath: path. + File truncate: handle to: 0. + ^ self makeWithHandle: handle forWrite: true +] + +{ #category : 'primitives - files' } +OldFile class >> openReadOnly: path [ + | encoded handle | + encoded := File encodePathString: path. + handle := File open: encoded writable: false. + self checkHandle: handle forPath: path. + ^ self makeWithHandle: handle forWrite: false +] + +{ #category : 'accessing' } +OldFile >> atEnd [ + ^ self position >= self size +] + +{ #category : 'initialization' } +OldFile >> close [ + File close: handle +] + +{ #category : 'accessing' } +OldFile >> contents [ + | buffer | + buffer := ByteArray new: self size. + self readInto: buffer at: 0. + ^ buffer +] + +{ #category : 'accessing' } +OldFile >> isForWrite [ + + ^ isForWrite +] + +{ #category : 'accessing' } +OldFile >> isForWrite: anObject [ + + isForWrite := anObject +] + +{ #category : 'accessing' } +OldFile >> position [ + ^ File getPosition: handle +] + +{ #category : 'accessing' } +OldFile >> position: newPosition [ + ^ File setPosition: handle to: newPosition +] + +{ #category : 'write and read' } +OldFile >> readInto: buffer [ + ^ self readInto: buffer offset: 0 withSize: buffer size +] + +{ #category : 'accessing' } +OldFile >> readInto: buffer at: fileOffset [ + ^ self readInto: buffer offset: 0 withSize: buffer size at: fileOffset +] + +{ #category : 'write and read' } +OldFile >> readInto: buffer offset: bufferOffset withSize: readSize [ + ^ File read: handle into: buffer startingAt: bufferOffset + 1 count: readSize +] + +{ #category : 'accessing' } +OldFile >> readInto: buffer offset: bufferOffset withSize: readSize at: fileOffset [ + ^ self withPosition: fileOffset during: [ + self readInto: buffer offset: bufferOffset withSize: readSize + ] +] + +{ #category : 'write and read' } +OldFile >> readInto: buffer withSize: readSize [ + ^ self readInto: buffer offset: 0 withSize: readSize +] + +{ #category : 'accessing' } +OldFile >> size [ + ^ File sizeOf: handle +] + +{ #category : 'accessing' } +OldFile >> truncate [ + ^ self truncateToSize: 0 +] + +{ #category : 'accessing' } +OldFile >> truncateToSize: newSize [ + ^ File truncate: handle to: newSize +] + +{ #category : 'accessing' } +OldFile >> withPosition: newPosition during: aBlock [ + | oldPosition | + oldPosition := self position. + self position: newPosition. + + ^ aBlock ensure: [ + self position: oldPosition + ] +] + +{ #category : 'write and read' } +OldFile >> writeFrom: dataBuffer [ + ^ self writeFrom: dataBuffer offset: 0 withSize: dataBuffer size +] + +{ #category : 'writing' } +OldFile >> writeFrom: dataBuffer at: fileOffset [ + ^ self writeFrom: dataBuffer offset: 0 withSize: dataBuffer size at: fileOffset +] + +{ #category : 'write and read' } +OldFile >> writeFrom: buffer offset: bufferOffset withSize: bufferSize [ + ^ File write: handle from: buffer startingAt: bufferOffset + 1 count: bufferSize +] + +{ #category : 'writing' } +OldFile >> writeFrom: buffer offset: bufferOffset withSize: bufferSize at: fileOffset [ + ^ self withPosition: fileOffset during: [ + self writeFrom: buffer offset: bufferOffset withSize: bufferSize + ] +] + +{ #category : 'write and read' } +OldFile >> writeFrom: buffer withSize: bufferSize [ + ^ self writeFrom: buffer offset: 0 withSize: bufferSize +] diff --git a/src/NewFiles/package.st b/src/NewFiles/package.st new file mode 100644 index 00000000000..5f1b13b8b32 --- /dev/null +++ b/src/NewFiles/package.st @@ -0,0 +1 @@ +Package { #name : 'NewFiles' }