This splits the fileStorage archive into two sagas to separate concerns.
fileStorage now just gives a dump of the database and explorer deals
with the user interaction and zipping.
This breaks down reading and writing into low-level and high level sagas.
Opening now takes a lock on the file so that it can only have one writer
or have multiple readers. The low-level read and write sagas use the
file descriptor to determine if the file is still open before actually
performing the action.
Since we are using the web locks api, these locks should work across
browser tabs/windows.
The high-level read/write sagas perform the low-level open, read/write,
close operations in a single call.
The high-level delete and rename sagas are also updated to take locks
on the files while performing the respective operations and will fail
if the file is already being used somewhere else.
This changes how files work. There is now an open function that
translates a path to a file handle (id). Then this handle is used
to perform other actions on the file. The file contents are moved
to a separate table so that the actual file storage is independent
of the database (e.g. in the future, we may use File Access API).
We store a hash of the file contents in the metadata file so we can
detect file changes without having to compare file contents.
We also separate the change and add actions.
This gives us more fine-grained control over database transaction and
cross-tab notifications and will facilitate upgrading the database with
new metadata in the future.
The editor saga was assuming that main.py always exists, which is not
the case for the first use of the app. This fixes the issue by checking
to see if the file exists before attempting to open it. This also
revealed a bug in fileStorage where the list of files was not populated
during initialization.
This removes use of the pseudo-internal toString() function and also
removes extraneous uses of ReturnType<>. Also, a new when() method
is added to simplify additional uses.
The pattern we were using for actions required actions to be defined in
three places, an enum member containing the type string, an type
definition and a function. This combines all three of these into one by
using a helper function based on the ideas from [1].
[1]: https://phryneas.de/redux-typescript-no-discriminating-union
This replaces the localStorage file storage with a new backend that uses
localForage (indexeddb) for storing the file contents. This will also
allow storing multiple files.