The new extract/verify/swap flow surfaced a long-standing bug in
Utils.clean_dir(). On macOS, browsing a folder in the Finder drops a
hidden .DS_Store file in it, and clean_dir() had two flaws around that:
- DirAccess listings skip hidden files by default, so .DS_Store was
never listed nor removed and the directory was never actually empty.
- The return value of dir.remove(subfolder) was ignored, so the failed
removal of a subdirectory (non-empty because of its own hidden file)
was silently swallowed and clean_dir() still reported success.
As a result, delete_directory_recursive() emptied the old language pack
but could not delete its directory, the rename of the new pack onto
that still-existing directory failed, and the downloader showed the
download-error popup. Since the version on disk was never updated, the
app re-downloaded and failed again in a loop on every launch. The old
pre-swap flow had the same deletion failure but masked it: it extracted
directly into the existing directory and ignored rename errors.
- clean_dir() now lists hidden files (include_hidden) and checks every
removal, trying to remove everything (best effort, so callers like
clear_all_local_data still wipe as much as possible) and reporting
the first error encountered instead of a false OK.
- The downloader verifies that the previous pack directory is really
gone before renaming the new one into place; if not, it aborts the
swap, keeps the temporary directory and shows the error instead of
attempting a rename that cannot succeed.
- Add regression tests covering hidden-file deletion, full content
cleanup and the missing-directory error path.
Verified with the GUT test suite (65/65 passing).