Disk Filesystem

A while ago Colin Putney announced the Filesystem framework, a nice and extensible replacement for the ugly FileDirectory class in Pharo. While all core classes are well commented, there is a quick start missing that explains how end users are supposed to adopt the framework. This blog post should fill that gap.

First we need to load the package:

 Gofer new
wiresong: 'mc';
package: 'Filesystem';
load.

The framework supports different kinds of filesystems that can be used interchangeably and that can transparently work with each other. The most obvious one is the filesystem on your hard disk. We are going to work with that one for now:

 working := FSDiskFilesystem current working.

Put the above code into a workspace and evaluate it. It assigns a reference of the current working directory to the variable working. References are the central object of the framework and provide the primary mechanism of working with files and directories. All code below works on FSReference instances.

Navigating the Filesystem

Now lets do some more interesting things. To list all children of your working directory evaluate the following expression:

 working children.

To iterate over all children recursively evaluate:

 working allChildren.

To get a reference to a specific file or directory within your working directory use the slash operator:

 cache := working / 'package-cache'.

Navigating back to the parent is easy:

 cache parent.

You can check for various properties of the cache directory by evaluating the following expressions:

 cache exists.             "--> true"
cache isFile. "--> false"
cache isDirectory. "--> true"
cache basename. "--> 'package-cache'"

To get additional information about the filesystem entry evaluate:

 cache entry creation.     "--> 2010-02-14T10:34:31+00:00"
cache entry modification. "--> 2010-02-14T10:34:31+00:00"
cache entry size. "--> 0 (directories have size 0)"

The framework also supports locations, late-bound references that point to a file or directory. When asking to perform a concrete operation, a location behaves the same way as a reference. Currently the following locations are supported:

 FSLocator desktop.
FSLocator home.
FSLocator image.
FSLocator vmBinary.
FSLocator vmDirectory.

If you save a location with your image and move the image to a different machine or operating system, a location will dynamically adapt and always point to the place you would expect.

Opening Read- and Write-Streams

To open a file-stream on a file ask the reference for a read- or write-stream:

 stream := (working / 'foo.txt') writeStream.
stream nextPutAll: 'Hello World'.
stream close.
 stream := (working / 'foo.txt') readStream.
stream contents.
stream close.

Please note that #writeStream overrides any existing file and #readStream throws an exception if the file does not exist. There are also short forms available:

 working / 'foo.txt' writeStreamDo: [ :stream | stream nextPutAll: 'Hello World' ].
 working / 'foo.txt' readStreamDo: [ :stream | stream contents ].

Have a look at the streams protocol of FSReference for other convenience methods.

Renaming, Copying and Deleting Files and Directories

You can also copy and rename files by evaluating:

 (working / 'foo.txt') copyTo: (working / 'bar.txt').

To create a directory evaluate:

 backup := working / 'cache-backup'.
backup createDirectory.

And then to copy the contents of the complete package-cache to that directory simply evaluate:

 cache copyAllTo: backup.

Note, that the target directory would be automatically created, if it was not there before.

To delete a single file evaluate:

 (working / 'bar.txt') delete.

To delete a complete directory tree use the following expression. Be careful with that one though.

 backup deleteAll.

That’s the basic API of the Filesystem library. If there is interest we can have a look at other features and other filesystem types in a next iteration.

Posted by Lukas Renggli at 14 February 2010, 2:10 pm with tags filesystem, pharo, smalltalk, tutorial link

Comments

Nice post!

I just spotted two typos. The blocks should be switched between the two instructions:

 working / 'foo.txt' readStreamDo: [ :stream | stream nextPutAll: 'Hello World' ].
working / 'foo.txt' writeStreamDo: [ :stream | stream contents ].
Posted by Tudor Girba at 15 February 2010, 10:07 am link

Oups, thank you for pointing out. I fixed the messed up code.

Posted by Lukas Renggli at 15 February 2010, 10:48 am link

Hey Lukas, glad you posted about this. I’m using it and I’ve solved with this an issue when managing files with a seaside app in a linux server (filenames with diacritics). So good timming on this one. It would be nice though, that it become able to rename files. The API is great. I just have one comment about it: I’ve missed a #fromString: aPathString method. After a while I’ve found #referenceFromString: in the instance side which is not as great as it can be.

Posted by sebastian at 16 February 2010, 12:11 pm link

Great post!

One thing confused me, a location is said to “still resolve to the expected directory” across an image save … shouldn’t that be the other way round, that it points to the new path of the location and not still the same one!?

Posted by Adrian at 21 February 2010, 7:00 pm link

Yeah, you are right. I rephrased that sentence slightly.

Posted by Lukas Renggli at 21 February 2010, 7:27 pm link