[Multiple-Profile]feat: allow profile deletion#20658
[Multiple-Profile]feat: allow profile deletion#20658criticalAY wants to merge 1 commit intoankidroid:mainfrom
Conversation
|
Why is this a bulk operation? |
|
What if user wants to delete 3-4 profiles in one go? |
|
I doubt that's a realistic use case, and upstream doesn't support it. (Maybe when we have analytics back 😉). I might be one of the few people where this would make sense (I might have 50 profiles). At this point, I don't know what each profile contains, so I'd still want to go one-by-one |
|
I actually did it for one profile at a time but I am okay with reverting to my original idea. |
|
For what it's worth, I disabled multi-delete for tags - mixing filtering and deleting felt dangerous from a UI perspective As deleting a profile is so risky, you might want explicit confirmation (type the profile name to delete it). Does this delete backups? |
This comment was marked as resolved.
This comment was marked as resolved.
4df4d66 to
7e2d1e0
Compare
This comment was marked as off-topic.
This comment was marked as off-topic.
|
We can do that, but is that related to this PR? |
| "Cannot delete the currently active profile (${profileId.value}). Switch first." | ||
| } | ||
|
|
||
| profileRegistry.removeProfile(profileId) |
There was a problem hiding this comment.
Think about the ordering here. Assume the user can kill the operation at any time. Is it better to have the files left on disk?
|
I think a doc in /docs/AnkiDroid would be useful, just with the directory structure which was agreed upon (and the names of the concepts: profile directory etc...) This would be a useful reference now to be sure all directories are handled and deleted |
7e2d1e0 to
30a1d3f
Compare
There was a problem hiding this comment.
My comment with a doc on the profile data/directory structure stands, it is ideal to have a single source of truth for the directories under a profile, and the global directories, so we can determine what should be deleted when a profile is deleted, to ensure there's no dangling files/directories
| * Scope of deletion is limited to the profile's private-storage | ||
| * directory (under `/storage/emulated/0/Android/data/com.ichi2.anki/files/...`): | ||
| * the collection database and any files the app writes inside that | ||
| * directory. Backups live in a separate, user-accessible location | ||
| * (`/storage/emulated/0/AnkiDroid/backups`) and are **not** touched | ||
| * here — their lifecycle is managed by the collection-folder guard | ||
| * elsewhere in the app. Deletion is irreversible. |
There was a problem hiding this comment.
Backups live in a separate, user-accessible location (
/storage/emulated/0/AnkiDroid/backups)
No they don't. They're under /storage/emulated/0/Android/data/com.ichi2.anki/files/ on a Google Play build
There was a problem hiding this comment.
Ahh thanks, i found a bug thanks to this comment, default profile would have wiped all profiles, I have fixed that now added a test
30a1d3f to
ef4d854
Compare
ef4d854 to
59d9ea9
Compare
| * BACKUPS & PRIVACY: | ||
| * On Google Play builds, backups are stored within the app's internal scoped storage |
There was a problem hiding this comment.
On all builds. Backups live in the profile-specific directory
| /** | ||
| * Permanently deletes a profile, removing its on-disk data and registry entry. | ||
| * | ||
| * SCOPE OF DELETION: |
There was a problem hiding this comment.
These headers are unusual, no need for them
Introduce deleteProfile() for removing a single user profile. Deletion removes both the registry entry (SharedPreferences) and the on-disk data directory. The only guard is: the currently active profile cannot be deleted, all other profiles including default are deletable.
59d9ea9 to
ebe616e
Compare
| if (appDataRoot != null) { | ||
| val sharedPrefsDir = File(appDataRoot, "shared_prefs") | ||
| if (sharedPrefsDir.exists() && sharedPrefsDir.isDirectory) { | ||
| val prefix = "profile_${profileId.value}_" | ||
| sharedPrefsDir.listFiles()?.forEach { file -> | ||
| if (file.name.startsWith(prefix)) { | ||
| file.delete() | ||
| } | ||
| } | ||
| } |
There was a problem hiding this comment.
extract to a method - allows for early returns to simplify logic
| * - External Storage (/storage/emulated/0/Android/data/.../files): Deletes the | ||
| * AnkiDroid collection directory, media, and backups for this profile. | ||
| */ | ||
| fun deleteProfile(profileId: ProfileId) { |
There was a problem hiding this comment.
If a user can select an arbitrary directory: do we want to be more careful?
| val externalFilesDir = appContext.getExternalFilesDir(null) | ||
| if (externalFilesDir != null) { | ||
| val collectionFolderName = if (profileId.isDefault()) "AnkiDroid" else profileId.value | ||
| val collectionDir = File(externalFilesDir, collectionFolderName) | ||
|
|
||
| if (collectionDir.exists()) { | ||
| collectionDir.deleteRecursively() | ||
| } | ||
| } |
There was a problem hiding this comment.
This is the first time AnkiDroid allows deleting a folder like this.
What if a user puts AnkiDroid in /Pictures/ - we likely want to selectively delete the known AnkiDroid files, rather than everything
Purpose / Description
Allow profile deletion -> Adding a method in PrpfileManager to allow deletion or profiles except the active
Fixes
Approach
See commit
How Has This Been Tested?
Unit test
Learning (optional, can help others)
NA
Checklist
Please, go through these checks before submitting the PR.