fix(ios): stop dropping the security-scoped download folder bookmark

Fixes #439, likely also #302.

Reported: on iOS, the download folder silently reverts to the default
SpotiFLAC folder after starting a download (and stays reverted after
closing and reopening the app).

_processQueue ran an iCloud/writable-path shape check against the
persisted downloadDirectory string and, if it looked like iCloud Drive
or failed the structural writable-path check, called
setDownloadDirectory() to reset it to the default folder. That call
passes no iosBookmark argument, so it also wipes the user's
security-scoped bookmark in the persisted settings - permanently, not
just for the current run.

This check ran unconditionally, even when a bookmark was already
present for that folder. A folder picked from Files can legitimately
have a persisted path that looks like iCloud Drive, or that fails this
generic structural check, while the bookmark itself still grants real
write access - the bookmark is the actual source of truth, and the
queue already has a separate, correct bookmark-resolution step right
after this one (StartAccessingIosBookmark, with its own proper
failure handling that fails the queued items with a clear message
instead of destroying the setting). The earlier path-shape check just
never deferred to it.

lib/utils/file_access.dart: extract the run/skip decision into
shouldValidateIosOutputDir(isIOS, isSafMode, outputDir,
downloadDirectoryBookmark) - skips whenever a bookmark is present -
and use it in download_queue_provider.dart in place of the inline
condition. Kept as a plain function taking isIOS as a parameter since
Platform.isIOS itself can't be exercised from a host test run.

test/file_access_ios_test.dart covers: runs for a plain app-folder
path, skips when a bookmark is present (the exact #439 case), skips
off iOS, skips in SAF mode, and skips when there's no output dir yet.

Verification: flutter analyze and flutter test (224 tests, all green,
including the 5 new ones) both clean.
This commit is contained in:
Abelardo Ramirez
2026-08-11 17:28:56 +07:00
committed by zarzet
parent 95f2879110
commit 555fd77cc8
3 changed files with 109 additions and 2 deletions
+76
View File
@@ -0,0 +1,76 @@
import 'package:flutter_test/flutter_test.dart';
import 'package:spotiflac_android/utils/file_access.dart';
void main() {
group('shouldValidateIosOutputDir', () {
test('validates a non-empty app-folder path on iOS', () {
expect(
shouldValidateIosOutputDir(
isIOS: true,
isSafMode: false,
outputDir:
'/private/var/mobile/Containers/Data/Application/ABC/Documents/SpotiFLAC',
downloadDirectoryBookmark: '',
),
isTrue,
);
});
test(
'skips validation when a security-scoped bookmark is present (#439)',
() {
// The persisted path may look like iCloud Drive or fail the
// structural writable-path check even though the bookmark grants
// real write access - the bookmark, not this path shape, is
// authoritative. Running the check anyway is what silently reset
// and dropped the user's chosen folder.
expect(
shouldValidateIosOutputDir(
isIOS: true,
isSafMode: false,
outputDir:
'/private/var/mobile/Library/Mobile Documents/com~apple~CloudDocs/Music',
downloadDirectoryBookmark: 'bookmark-bytes',
),
isFalse,
);
},
);
test('skips validation off iOS', () {
expect(
shouldValidateIosOutputDir(
isIOS: false,
isSafMode: false,
outputDir: '/data/user/0/com.zarz.spotiflac/files/SpotiFLAC',
downloadDirectoryBookmark: '',
),
isFalse,
);
});
test('skips validation in SAF mode', () {
expect(
shouldValidateIosOutputDir(
isIOS: true,
isSafMode: true,
outputDir: '/some/path',
downloadDirectoryBookmark: '',
),
isFalse,
);
});
test('skips validation when there is no output directory yet', () {
expect(
shouldValidateIosOutputDir(
isIOS: true,
isSafMode: false,
outputDir: '',
downloadDirectoryBookmark: '',
),
isFalse,
);
});
});
}