Reputation: 13
First of all: I'm not a programmer, just an amateur with some lights about Squeak/Pharo. So Please be gentle with the technical explanations...
I have a personal library with thousands of books, articles, etc., some on paper, some on digital support. All of this is garbage unless I can organize and classify everything.
I thought this would be an easy task with Smalltalk.
I created 3 main classes:
Object>>Cards
inst. var: name, localAdress, notes, tags, ...
Cards>>Work
inst. var: authors "aSet of Cards"
Cards>>Author
Object>>MyLibrary
inst var: works "aSet of Cards" authors "aSet of Cards" myLibraryBase "aString? aPath?"
Plain simple. Instances of MyLibrary #initialize with a UIManager chooseDirectory, to establish definitely where the files must be found.
Now here is my problem: I should create a method for MyLibrary to «scan» all the relevant files (suffix) in all the folders and subfolders under myLibraryBase. Meaning, the method should answer a collection of files. This method or another method should further create a new Card for each document found in the collection, registering its localAdress.
I simply cannot figure out how to do it. I tried, under FileSystem, #allDirectories, but it doesn't work. Also, I can't figure out how to force the FileSystem to start at myLibraryBase, instead of root, workingDirectory, etc.
Can someone point me in the right direction? Thanks
Upvotes: 1
Views: 251
Reputation: 136
Assuming you have your root directory (I think you are keeping it as myLibraryBase?) then you could use the FileDirectory>>#withAllFilesDo:andDirectoriesDo: method for this.
Starting from a directory this recurses down the tree and runs a block with each file found and a separate block with each directory. It's a bit of a hammer to crush a snail in some ways but it works nicely so why reinvent the screwdriver?
As an example -
| list |
list := OrderedCollection new: 100.
FileDirectory default
withAllFilesDo:[:fs| list add: fs name]
andDirectoriesDo:[:d| list add: d pathName].
list
Inspect that and you should see a list of directory pathnames starting from your default directory intermingled with the full names of the files found in each directory in that tree. It's reasonably fast too; on my Pi 5 with all the files on a very ordinary microSD card it takes 1.5 seconds to build the list of 36,000 files in the directories under my Squeak home directory.
For your kind of usage it is worth remembering that the file block gets a filestream for the found file and so reading it and doing your Card building in one go would be more efficient than building a list of names and then iterating that to (re)open the file and process it.
So I would guess that
FileDirectory default
withAllFilesDo:[:fs| Cards add: (Card readFrom: fs)]
andDirectoriesDo:[:d|"do nothing with directory"]
would likely be a good move.
Upvotes: 0
Reputation: 3141
Complementing @MartinW's answer with one for Squeak:
@MartinW's answer uses the FileSystem programming interface, which is not available in Squeak out of the box, but can be installed as an extension: https://github.com/squeak-smalltalk/squeak-filesystem
With plain Squeak, one has to use the FileDirectory programming interface instead. It has similar messages, but they handle differently than the allChildren
mentioned in the other answer.
For example, there is a message called withAllSubdirectoriesCollect:
,
which evaluates a code block for every directory in the tree, and from
the directory you can get all the contained files.
(UIManager default
chooseDirectory: 'Select directory of the collection'
from: FileDirectory default) "This will result in a FileDirectory object unless you cancel the dialog."
withAllSubdirectoriesCollect:
[:eachDirectory | "eachDirectory is a FileDirectory object."
eachDirectory fileEntries do: "This loops through all the files (not subdirectories) in eachDirectory."
[:eachFileEntry | "eachFileEntry is a DirectoryEntry object."
Transcript show: eachFileEntry fullName; cr]]
Please look up the FileDirectory and DirectoryEntry classes in Squeak to find out what you can do with them, and ask any further questions that you might have.
Instead of writing the file names to the transcript, you could also put the directory entries into a collection of yours for later processing, or first convert them to Cards and add them to your library directly.
There is another message that also includes all the files in the
traversal directly, not only directories (so you do not need two
nested blocks of code), but its interface may seem a bit surprising: it is called
directoryTreeDo:
and it evaluates a code block for each file and
directory. As an argument to this code block, it provides a list of
the DirectoryEntries that lead to this file or directory... But if you just look at the last
entry of the provided list, you should be able to get all that you
need for each file.
Here is an example:
(UIManager default
chooseDirectory: 'Select directory of the collection'
from: FileDirectory default)
directoryTreeDo: [:each | Transcript show: each last fullName; cr]
each last
in the block is the DirectoryEntry for the current file or
directory. For example, you can seek out only the files by testing
with each last isFile
...
directoryTreeDo:
[:each |
each last isFile ifTrue:
[Transcript show: each last fullName; cr]]
Upvotes: 1
Reputation: 5041
In Pharo, you could try this for a start. I used jpg
extension in the sample. If you use children
you get the current folder. If you use allChildren
, subfolders are included:
| folder imageFiles |
folder := '/path/to/your/folder' asFileReference.
imageFiles := folder allChildren select: [ :each | each basename endsWith: 'jpg' ].
This documentation is a bit dated, but I think it mostly still applies: https://eng.libretexts.org/Bookshelves/Computer_Science/Programming_Languages/Book%3A_Deep_into_Pharo_(Bergel_Cassou_Ducasse_and_Laval)/02%3A_Files_with_FileSystem
Upvotes: 0