Skip to content

Fix: Prevent accidental deletion of user album files in camera & screen capabilities#85

Open
wuchenxiuwu wants to merge 3 commits intomithun50:mainfrom
wuchenxiuwu:main
Open

Fix: Prevent accidental deletion of user album files in camera & screen capabilities#85
wuchenxiuwu wants to merge 3 commits intomithun50:mainfrom
wuchenxiuwu:main

Conversation

@wuchenxiuwu
Copy link
Copy Markdown

@wuchenxiuwu wuchenxiuwu commented Mar 22, 2026

Summary

This PR fixes the issue where temporary files from camera and screen recording could accidentally delete user album files. Now we only delete files that were generated during the current session.

Changes

  • CameraCapability & ScreenCapability
    • Added _cameraFiles / _screenFiles list to track generated files with unique ID.
    • Added _generateId() to create unique identifiers.
    • Modified _snap, _clip, and _record methods: record file path before deletion, verify file belongs to current session before deleting.
    • Added exception cleanup to remove stale records.
    • Added dispose() in CameraCapability to clean up leftover temp files on app exit.

Testing

  • Tested on real device, confirmed only session‑created files are removed; user albums remain untouched.

Summary by cubic

Fixes accidental deletion of user album photos and videos by camera and screen recording. We now only delete temp files created in this session and retry cleanup safely on exit.

  • Bug Fixes
    • Track session-created camera/screen files via _cameraFiles/_screenFiles with unique IDs from _generateId().
    • Update CameraCapability._snap, _clip, and ScreenCapability._record to delete only if the file matches the session (id + path).
    • Remove tracking entries on success; clean records on errors; keep entries when delete fails so dispose() can retry.
    • Add CameraCapability.dispose() to remove leftover temp files on app exit.

Written for commit 5c495e8. Summary will update on new commits.

Summary by CodeRabbit

  • Bug Fixes

    • Prevents accumulation of orphaned temporary files from camera and screen captures by tracking and conditionally deleting generated files.
    • Ensures inventory entries are removed on capture/read/processing errors to avoid stale records.
  • Improvements

    • More reliable cleanup during app lifecycle shutdown.
    • Stronger resource management for camera and screen recording flows.

This modification
 -----------------
 1. Added _ cameraFiles list ( List < Map < String, String > > ) to record the photo / video files generated by this session ( including unique ID and absolute path ).
 2. The _ generateId() method is added to generate a unique identifier based on timestamp + random number.
 3. _ snap and _ clip logical reconstruction
 4. Generate fileId and save { id, path } to _ cameraFiles immediately after obtaining the file.
 5. Before deleting a file, first search in the list through fileId and path, confirm that the file belongs to this session, and then delete it to avoid deleting the file in the user's photo album by mistake.
 6. The deletion operation is uniformly placed at the end of the try block, and a delete Error is added to ignore the deletion failure ( the file may have been removed, etc. ).
 7. In the catch block, if fileId is not empty, the corresponding record will be removed from _ cameraFiles to prevent residue.
 8. New resource cleanup method
 9. dispose() is empty in the original version, traverse _ cameraFiles in the modified version and try to delete all remaining temporary files, and finally empty the list.  Ensure that temporary files are not left behind to occupy storage space when an application exits or capability is uninstalled.
This modification is as follows
------------------
1. Added file tracking and unique identifier mechanism
-------------------
1.1 Added _screenFiles list: List<Map<String, String>> to record screen recording files generated during the current session (including unique ID and absolute path).
Added _generateId() method: generates a unique identifier based on timestamp + random number to distinguish different files.
--------------------
2. Modified _record method logic
---------------------
2.1 File tracking: after obtaining the file path, immediately generate a fileId and store {id, path} into _screenFiles.
2.2 Pre-deletion validation: after reading the file content, look up the corresponding record in the list using fileId and path to confirm the file belongs to the current session before deleting, preventing accidental deletion of user album files.
2.3 Post-deletion cleanup: after successful deletion, remove the record from the list.
2.4 Exception cleanup: in the catch block, if fileId is not null, remove the corresponding record from _screenFiles to prevent leftover entries.
2.5 Removed direct deletion from the original version: the original version directly called file.delete() after reading the file without any ownership validation; the modified version now deletes only after validation through the list.
2.6 Permission methods remain unchanged: checkPermission and requestPermission still return true directly, as screen recording permissions are handled via the system consent dialog each time it is invoked, so no changes are required.
@coderabbitai
Copy link
Copy Markdown

coderabbitai bot commented Mar 22, 2026

📝 Walkthrough

Walkthrough

Session-level tracking of generated camera images and screen recordings was added: each output gets a unique file ID, deletion is conditional on inventory matches, errors remove stale entries, and camera capability now overrides dispose() to synchronously delete remaining tracked files.

Changes

Cohort / File(s) Summary
Camera capability
flutter_app/lib/services/capabilities/camera_capability.dart
Added _cameraFiles inventory and _generateId(); record {id,path} on capture/clip; conditional deletion only if inventory entry matches; remove inventory on errors; added dispose() override to synchronously delete remaining tracked files.
Screen capability
flutter_app/lib/services/capabilities/screen_capability.dart
Added _screenFiles inventory and per-record fileId; simplified checkPermission() and handle() guard; record {id,path} on record; conditional deletion only when inventory matches; remove inventory entry on errors.

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~45 minutes

Poem

🐇 I hop and note each frame and file,

ids that sparkle, tidied with style,
No stray bytes lost where shadows creep,
Dispose comes by to hush and keep,
A tidy burrow — quick and neat.

🚥 Pre-merge checks | ✅ 3
✅ Passed checks (3 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title clearly and specifically summarizes the main change: preventing accidental deletion of user album files by implementing session-level file tracking in camera and screen capabilities.
Docstring Coverage ✅ Passed No functions found in the changed files to evaluate docstring coverage. Skipping docstring coverage check.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

Tip

CodeRabbit can use TruffleHog to scan for secrets in your code with verification capabilities.

Add a TruffleHog config file (e.g. trufflehog-config.yml, trufflehog.yml) to your project to customize detectors and scanning behavior. The tool runs only when a config file is present.

Copy link
Copy Markdown

@cubic-dev-ai cubic-dev-ai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

3 issues found across 2 files

Prompt for AI agents (unresolved issues)

Check if these issues are valid — if so, understand the root cause of each and fix them. If appropriate, use sub-agents to investigate and fix each issue separately.


<file name="flutter_app/lib/services/capabilities/camera_capability.dart">

<violation number="1" location="flutter_app/lib/services/capabilities/camera_capability.dart:126">
P2: The new deletion guard is tautological: it re-checks an entry that was just inserted, so it does not actually validate file safety/origin before deletion.</violation>

<violation number="2" location="flutter_app/lib/services/capabilities/camera_capability.dart:128">
P2: File tracking is removed before delete succeeds, so failed deletions are silently left on disk and no longer eligible for later cleanup.</violation>
</file>

<file name="flutter_app/lib/services/capabilities/screen_capability.dart">

<violation number="1" location="flutter_app/lib/services/capabilities/screen_capability.dart:72">
P2: Deletion ownership check is self-approving: file path is whitelisted immediately before the check, so it does not truly verify safe-origin files.</violation>
</file>

Since this is your first cubic review, here's how it works:

  • cubic automatically reviews your code and comments on bugs and improvements
  • Teach cubic by replying to its comments. cubic learns from your replies and gets better over time
  • Add one-off context when rerunning by tagging @cubic-dev-ai with guidance or docs links (including llms.txt)
  • Ask questions if you need clarification on any suggestion

Reply with feedback, questions, or to request a fix. Tag @cubic-dev-ai to re-run a review.

Copy link
Copy Markdown

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 2

🧹 Nitpick comments (1)
flutter_app/lib/services/capabilities/camera_capability.dart (1)

198-209: Promote cleanup to the capability contract.

CameraCapability now has a lifecycle hook, but flutter_app/lib/services/capabilities/capability_handler.dart:1-41 still doesn't expose dispose(). That makes teardown easy to miss for siblings—ScreenCapability already has session state but no matching cleanup. A default no-op dispose() on the base class would let providers dispose capabilities uniformly.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@flutter_app/lib/services/capabilities/camera_capability.dart` around lines
198 - 209, The Capability base in capability_handler.dart should declare a
default no-op dispose() so all capability implementations (e.g.,
CameraCapability.dispose and ScreenCapability) can be called uniformly; update
the Capability class to include a void dispose() {} method and ensure any code
that tears down capabilities calls capability.dispose() so CameraCapability's
existing file cleanup runs via the contract rather than being easily missed.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@flutter_app/lib/services/capabilities/camera_capability.dart`:
- Around line 125-130: The code removes entries from the _cameraFiles list
before the file I/O operations complete, so failures in File(path).delete() or
readAsBytes() will leave orphaned files; change the logic in the methods that
touch _cameraFiles (the blocks using _cameraFiles.indexWhere,
File(path).delete(), and readAsBytes()) to perform the file I/O inside a
try/catch and only call _cameraFiles.removeAt(index) after the operation
succeeds; on catch do not remove the entry (optionally log or rethrow the error)
so dispose() can retry later—specifically update the code paths that use
_cameraFiles.indexWhere(...), File(path).delete(), and readAsBytes() to defer
untracking until success and preserve the entry on failure.

In `@flutter_app/lib/services/capabilities/screen_capability.dart`:
- Around line 8-13: ScreenCapability currently records entries into the
_screenFiles list before checking storage.exists() and removes entries from the
list before the asynchronous delete() completes, which can leave stale session
entries and orphaned recordings; fix by (1) moving the population of
_screenFiles to after a successful exists() check (or only push once delete is
confirmed unnecessary), (2) only remove the entry from _screenFiles after
delete() completes successfully (handle delete() rejection by keeping the
entry), and (3) add a dispose() method on ScreenCapability that retries pending
deletions (or persists _screenFiles to retry on next startup) and ensure
node_provider disposes the _screenCapability (mirroring the pattern in
CameraCapability) so cleanup gets a retry path. Ensure changes reference the
_screenFiles field, exists(), delete(), the new dispose() method, and
node_provider's lifecycle handling.

---

Nitpick comments:
In `@flutter_app/lib/services/capabilities/camera_capability.dart`:
- Around line 198-209: The Capability base in capability_handler.dart should
declare a default no-op dispose() so all capability implementations (e.g.,
CameraCapability.dispose and ScreenCapability) can be called uniformly; update
the Capability class to include a void dispose() {} method and ensure any code
that tears down capabilities calls capability.dispose() so CameraCapability's
existing file cleanup runs via the contract rather than being easily missed.

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: bd883ba8-7e1d-405e-a442-06a710340cec

📥 Commits

Reviewing files that changed from the base of the PR and between 1cbb6c1 and 6e43ae4.

📒 Files selected for processing (2)
  • flutter_app/lib/services/capabilities/camera_capability.dart
  • flutter_app/lib/services/capabilities/screen_capability.dart

Comment on lines +125 to +130
// Only delete files when they are on the list to prevent accidental deletion of the album
final index = _cameraFiles.indexWhere((item) => item['id'] == fileId && item['path'] == path);
if (index != -1) {
_cameraFiles.removeAt(index);
await File(path).delete().catchError((_) => File(path));
}
Copy link
Copy Markdown

@coderabbitai coderabbitai bot Mar 22, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major

Don't untrack temp files before cleanup succeeds.

Both methods remove the _cameraFiles entry before delete() completes, and the catch blocks drop the entry without deleting the file at all. If readAsBytes() or delete() fails, the media stays on disk but dispose() has nothing left to retry.

Also applies to: 139-142, 171-176, 185-188

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@flutter_app/lib/services/capabilities/camera_capability.dart` around lines
125 - 130, The code removes entries from the _cameraFiles list before the file
I/O operations complete, so failures in File(path).delete() or readAsBytes()
will leave orphaned files; change the logic in the methods that touch
_cameraFiles (the blocks using _cameraFiles.indexWhere, File(path).delete(), and
readAsBytes()) to perform the file I/O inside a try/catch and only call
_cameraFiles.removeAt(index) after the operation succeeds; on catch do not
remove the entry (optionally log or rethrow the error) so dispose() can retry
later—specifically update the code paths that use _cameraFiles.indexWhere(...),
File(path).delete(), and readAsBytes() to defer untracking until success and
preserve the entry on failure.

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thank you for your careful examination. However, here I would like to explain why the current design is more realistic and that the changes suggested by AI may introduce unnecessary complexity.

  1. Deletion failure is an extreme case
    The files generated by the camera and screen recording are saved in the private directory of the application, and the deletion operation is almost always successful ( unless the file is locked by other processes, which hardly happens after a single photo / screen recording ). Even if the removal fails in rare cases, what remains is a temporary file that does not affect user data, and the file size is limited and does not place significant strain on storage.

  2. The current sequence maintains logical simplicity
    Removing the list record before deleting the file ensures that no invalid path remains in the list, regardless of whether the deletion is successful. If the record is deleted and then removed as recommended, once the deletion fails, the record will remain permanently in the list, causing dispose() to try to delete again when the application exits. However, the calling timing of dispose() does not always cover all failure scenarios ( such as forced killing of processes by users ), but may make the list hold invalid entries for a long time.

  3. Build Process Robustness
    Our code was actually tested to clean up files correctly in all normal scenarios without causing any build errors. If the changes recommended by AI introduce more complex retry logic, it may introduce unused code paths because some functions ( such as commands ) are not called under specific build configurations, which may cause warnings or false positives in strict build processes.

To sum up, the current design has struck a balance between safety and simplicity, and the risk of edge situations is acceptable. Therefore, I chose to leave the existing implementation intact.😋

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@wuchenxiuwu, thank you for the thorough explanation. Your reasoning is well-grounded:

  • Files in the app's private directory are practically always deletable immediately after a single capture operation, so the orphan-file risk is indeed minimal in practice.
  • Removing the tracking entry before the delete() call does keep the list clean regardless of outcome, which is a valid simplicity trade-off.
  • The dispose() retry path is a best-effort safety net rather than a critical recovery mechanism, so keeping it lean is reasonable.

The concern I raised is a real theoretical edge case, but given your testing and the constraints of the environment, the current design is a justifiable balance. I'll respect your decision to keep the existing implementation as-is. 👍

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thank you for your approval. Just to be clear, by " testing" I mean validating the design in practice, but decisions are not based on a specific environment. The fact that the file path is in an application's private directory, that deletion operations are almost always successful, and that user data is not affected even if it remains - these are objective facts at the system level, regardless of the test environment. The current design is a reasonable trade-off between security and simplicity, which I believe applies to most real-world scenarios.

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@wuchenxiuwu, that's a fair and important distinction — the justification stands on system-level properties (private app directory, near-certain deletability of freshly created files, no user-data exposure on residual temp files) rather than being environment-specific. That makes the trade-off argument more broadly applicable and robust. Thanks for the clarification!

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thank you for your understanding. I'm glad we can agree.

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@wuchenxiuwu, likewise! It was a productive discussion, and the reasoning behind the design is solid and well-articulated. Best of luck with the rest of the PR! 🎉

Co-authored-by: cubic-dev-ai[bot] <191113872+cubic-dev-ai[bot]@users.noreply.github.com>
Copy link
Copy Markdown

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 1

♻️ Duplicate comments (1)
flutter_app/lib/services/capabilities/camera_capability.dart (1)

142-146: ⚠️ Potential issue | 🟠 Major

Don't untrack files until cleanup succeeds.

_snap still untracks on downstream I/O failure, and _clip still untracks before delete() has succeeded. If readAsBytes(), image decoding, or delete() fails, the temp file stays on disk with nothing left for dispose() to retry.

🧹 Minimal fix
-      if (index != -1) {
-        _cameraFiles.removeAt(index);
-        await File(path).delete().catchError((_) => File(path));
-      }
+      if (index != -1) {
+        try {
+          await File(path).delete();
+          _cameraFiles.removeWhere(
+            (item) => item['id'] == fileId && item['path'] == path,
+          );
+        } catch (_) {
+          // Keep tracking entry so dispose() can retry cleanup later.
+        }
+      }

Apply the same rule to Lines 142-146 and 188-192: once the file exists, leave it tracked unless deletion has already succeeded.

Also applies to: 176-180, 188-192

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@flutter_app/lib/services/capabilities/camera_capability.dart` around lines
142 - 146, The code currently removes entries from _cameraFiles in catch blocks
of _snap and _clip before downstream cleanup (readAsBytes/image decode/delete)
has succeeded, leaving temp files orphaned if I/O fails; change the error
handling so you only call _cameraFiles.removeWhere(...) after the
cleanup/delete() call has completed successfully (i.e., move/remove the
untracking code to the success path or after await delete() resolves) and do not
untrack on exceptions during readAsBytes or image decoding; apply the same
adjustment to the other catch sites mentioned (the blocks around lines 176-180
and 188-192) so dispose() can still see and retry leftover files until deletion
actually succeeds.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@flutter_app/lib/services/capabilities/camera_capability.dart`:
- Around line 202-212: The dispose() method must coordinate with in-flight
_snap() and _clip() operations to avoid races: add a disposing flag (e.g., bool
_disposing) and an in-flight counter or Set<Future>/Completer (e.g.,
_pendingOperations) that _snap() and _clip() register and await; when dispose()
is called set _disposing = true, await/abort outstanding pending operations (or
signal them to cancel), and only then perform the file sweep over _cameraFiles;
also update _snap()/_clip() to check _disposing early and, if disposal has
started, delete/abort any partial outputs and avoid adding or clearing entries
from _cameraFiles so the cleanup can reliably retry/delete files.

---

Duplicate comments:
In `@flutter_app/lib/services/capabilities/camera_capability.dart`:
- Around line 142-146: The code currently removes entries from _cameraFiles in
catch blocks of _snap and _clip before downstream cleanup (readAsBytes/image
decode/delete) has succeeded, leaving temp files orphaned if I/O fails; change
the error handling so you only call _cameraFiles.removeWhere(...) after the
cleanup/delete() call has completed successfully (i.e., move/remove the
untracking code to the success path or after await delete() resolves) and do not
untrack on exceptions during readAsBytes or image decoding; apply the same
adjustment to the other catch sites mentioned (the blocks around lines 176-180
and 188-192) so dispose() can still see and retry leftover files until deletion
actually succeeds.

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: 6c5bebc7-ec68-48c6-9db3-4fb16451141d

📥 Commits

Reviewing files that changed from the base of the PR and between 6e43ae4 and 5c495e8.

📒 Files selected for processing (1)
  • flutter_app/lib/services/capabilities/camera_capability.dart

Comment on lines +202 to +212
//Clean up all undeleted temporary files when the application exits
void dispose() {
// No persistent controller to clean up anymore
for (final item in _cameraFiles) {
final path = item['path'];
if (path != null) {
try {
File(path).deleteSync();
} catch (_) {}
}
}
_cameraFiles.clear();
Copy link
Copy Markdown

@coderabbitai coderabbitai bot Mar 22, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major

Coordinate dispose() with in-flight _snap() / _clip() calls.

flutter_app/lib/providers/node_provider.dart:313-323 calls this synchronously, but _snap() and _clip() keep running asynchronously. If teardown starts during capture/recording, this sweep can finish before the output is tracked, or deleteSync() can fail while the file is still in use and _cameraFiles.clear() then drops the only retry handle. Please gate teardown with a disposing flag and have in-flight operations delete/abort their output once disposal starts.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@flutter_app/lib/services/capabilities/camera_capability.dart` around lines
202 - 212, The dispose() method must coordinate with in-flight _snap() and
_clip() operations to avoid races: add a disposing flag (e.g., bool _disposing)
and an in-flight counter or Set<Future>/Completer (e.g., _pendingOperations)
that _snap() and _clip() register and await; when dispose() is called set
_disposing = true, await/abort outstanding pending operations (or signal them to
cancel), and only then perform the file sweep over _cameraFiles; also update
_snap()/_clip() to check _disposing early and, if disposal has started,
delete/abort any partial outputs and avoid adding or clearing entries from
_cameraFiles so the cleanup can reliably retry/delete files.

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The condition for triggering OK will be very strict. Do not use theoretical knowledge to make random changes, as it may cause unnecessary confusion. It will only appear when the user just triggers the photo/video and immediately exits the application. Even if it happens, it's just a residual annoyance

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@wuchenxiuwu, that's a fair point — the primary goal of this PR (preventing accidental deletion of user album files) is already well-addressed. The race window is indeed extremely narrow (capture in-flight + immediate app exit), and the worst outcome is a harmless leftover temp file rather than any data loss. Acknowledged, and I won't push further on this.

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Acknowledged, thanks for the review and understanding. I'll keep the edge case in mind for future iterations.😊

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@wuchenxiuwu, glad we're on the same page! 😊 The fix looks solid overall — great work protecting user album files. Looking forward to future iterations!

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant