December Adventure Day 19
Working Drag-and-Drop File Transfers
After taking up altogether too much of my December Adventure, day 19 finally brought working drag-and-drop file transfer to Reconnect, my Psion connectivity software for macOS. 🎉
There’s not much to add technically beyond what I’ve covered in previous posts, so feel free to check out my write-ups from day 15 and day 18 if you’re curious about the details.
I’ve yet to add support for dragging folders which will require some refactoring: folders are currently handled as a collection of individual file transfers and that will need to change to allow me to track the overall progress and provide a single completion for the NSItemProvider
-based drag operation. With that in place, I’ll be ready to push a release. Lots to keep me busy on day 20!
December Adventure Day 18
Investigations
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. 🫠
December Adventure Day 17
Design Updates
Life got in the way of day 17 of my December Adventure making for a slow day with little progress. Still, I managed to find the time to correct my design for a replacement MNT Pocket Reform backplate and post it on GitHub.
Thankfully I spotted yesterday’s mistake fast enough that PCBWay hadn’t started manufacture and I was able to simply provide them with the updated designs. Hopefully I’ve not missed anything this time around. 🤞🏻
If you’re thinking about trying these designs out for yourself, I encourage you to wait until I’ve received the parts from PCBWay and can confirm they fit correctly.