Day 18 of my December Adventure brought a new wave of pain (I strongly advise against fracturing your foot, however exciting it might seem), so everything went slowly and there’s not too much to report—I mostly spent my time thinking about how things needed to work instead of doing. Still, in the interests of journaling the progress I do make, here’s a little update.

Reconnect

I’m still committed to adding full support for drag and drop to Reconnect, my Psion connectivity sofware for macOS. The remaining work entails figuring out how drag-and-drop operations should interact with the Transfers window—it feels like the correct place to show progress for drag-initiated transfers, but it’s not currently set up for that.

With drag operations, apps don’t know the file’s destination—they pass the data to the OS and it does what’s necessary to create/copy/move the file. That means the model object that backs the Transfers window and manages on-going transfer operations needs to expose a slightly lower level API that can be used for both the regular transfers from the Psion to the user’s ‘Downloads’ directory, and the NSItemProvider implementation used for drag-and-drop.

Right now, the API to download a file from the Psion looks like this:

@MainActor @Observable
class TransfersModel {

    func download(from source: FileServer.DirectoryEntry,
                  to destinationURL: URL? = nil,
                  convertFiles: Bool) async throws

}

The implementation always defaults to using the users ‘Downloads’ directory if destinationURL is nil:

let fileManager = FileManager.default
let downloadsURL = fileManager.urls(for: .downloadsDirectory,
                                    in: .userDomainMask)[0]
let destinationURL = destinationURL ?? downloadsURL.appendingPathComponent(source.name)
let temporaryURL = fileManager.temporaryDirectory.appendingPathComponent((UUID().uuidString))
...
try fileManager.moveItem(temporaryURL, to: destinationURL)
...
return destinationURL

Instead, of this, it needs to return the resulting URL to the caller and only move the temporary file used for transfers if an explicit destination is specified. The prototype needs to look something like this:

func download(from source: FileServer.DirectoryEntry,
              to destinationURL: URL? = nil,
              convertFiles: Bool) async throws -> URL

And internally, the behavior needs to be a little more like this, moving the temporary file only if destinationURL is non-nil:

...
let destinationURL = if let destinationURL {
    try fileManager.moveItem(temporaryURL, to: destinationURL)
    return destinationURL
} else {
    return temporaryURL
}
...
return destinationURL

I’m sure there’s a cleaner way to write this—I’m still getting used to Swift’s new conditional assignments.

This small change of should allow me to build on the example code from day 15 as follows:

TableRow(file)
    .itemProvider {
        let provider = NSItemProvider()
        provider.suggestedName = file.name
        provider.registerFileRepresentation(for: .data) { completion in
            Task {
                let url = await self.browserModel.download([file.id], convertFiles: false)
                let data = try Data(contentsOf: url)
                completion(data, false, nil)
            }
            return nil
        }
        return provider
    }

I still need to work out how to update the suggested filename if it changes during file conversion (e.g., ‘image.mbm’ to ‘image.tiff’) and if possible I’d like to make that conversion decision a user-interactive part of the file transfer.

The legacy download behavior (when the user clicks the download toolbar button) can now be achieved by explicitly passing the downloads directory:

Task {
    let downloadsURL = FileManager.default.urls(for: .downloadsDirectory,
                                                in: .userDomainMask)[0]
    try? await transfersModel.download(from: file,
                                       to: downloadsURL,
                                       convertFiles: convertFiles)
}

Psion Emulation

While it feels like I’m rapidly running out of days in December, I’m still keen to contribute a little to the Psion emulation story: I’d like to finish my brief write-up explaining some of the emulation options available in MAME, and I’d really love to get a handle on what would be involved in producing something like Infinite Mac—a website showcasing emulators for every Mac—for Psions.

With my sights set on—shall we call it ‘Infinite Psion’?—I had a go at compiling MAME for the web using Emscripten. MAME has some instructions for doing just this, and the Emscripten Getting Started seems pretty accessible. It should be as simple as:

cd ~/Projects
git clone https://github.com/emscripten-core/emsdk.git
cd emsdk
./emsdk install latest
./emsdk activate latest
source ./emsdk_env.sh

cd ~/Projects
git clone git@github.com:mamedev/mame.git
cd mame
emmake make SUBTARGET=psion3a SOURCES=psion/psion3a.cpp

Needless to say, it didn’t just work out of the box, and I now have a number of errors to contend with which, I suspect, require me to think all-too hard about what’s going on under the hood in JavaScript and WASM-land. 🫠