Skip to content

feat: Add Internxt Virtual Drive extension for Nemo file manager#409

Open
egalvis27 wants to merge 1 commit into
mainfrom
feat/add-nemo-file-system-extension
Open

feat: Add Internxt Virtual Drive extension for Nemo file manager#409
egalvis27 wants to merge 1 commit into
mainfrom
feat/add-nemo-file-system-extension

Conversation

@egalvis27

@egalvis27 egalvis27 commented Jul 16, 2026

Copy link
Copy Markdown

What is Changed / Added

We've added unified file manager extension support for both Nautilus and Nemo. The app now automatically detects which manager you have installed and handles everything transparently. Created a new file-manager-extension module with detection logic, installation orchestration, and version tracking. Also fixed GTK compatibility issues (was 4.0, now 3.0) so Nemo works properly on Linux Mint. Updated IPC handlers, config store, and all components to use the new system. Includes 28 comprehensive tests covering both managers, and fixed a global mock issue that was breaking other tests.

Why

Users on Linux Mint and other Cinnamon-based distros couldn't use the file manager integration since we only supported Nautilus. This unifies both managers under a single detection point, so the same extension system works whether you use Nautilus, Nemo, or switch between them. Makes the codebase cleaner too—one module handling both cases instead of duplicated logic everywhere.

Summary by CodeRabbit

  • New Features
    • Added virtual drive integration for supported Linux file managers.
    • Users can make files locally available, return them to online-only status, view sync status, and copy file links from the file manager.
    • Automatically detects the available file manager and installs or updates the integration.
  • Bug Fixes
    • File-manager availability warnings now work across supported file managers.
    • Integration is removed when users sign out.
  • Chores
    • Extension version information is now saved and restored with app configuration.

@coderabbitai

coderabbitai Bot commented Jul 16, 2026

Copy link
Copy Markdown

Review Change Stack

📝 Walkthrough

Walkthrough

Adds support for installing and managing a file-manager extension for Nautilus or Nemo, introduces Nemo virtual-drive actions and sync status UI, persists extension versions, and replaces Nautilus-specific availability checks with generic file-manager detection.

Changes

File-manager extension support

Layer / File(s) Summary
File-manager detection and service
src/backend/features/file-manager-extension/...
Detects Nautilus or Nemo, resolves manager-specific paths and reload commands, and manages extension files.
Installation lifecycle and persistence
src/backend/features/file-manager-extension/..., src/core/bootstrap/..., src/core/electron/store/..., src/apps/main/config*, knip.json
Installs, upgrades, and removes the extension, persists its version, and wires lifecycle operations into startup and logout.
Nemo virtual-drive integration
assets/python-nemo/internxt-virtual-drive.py
Adds virtual-drive menus, hydration-service actions, sync-status metadata, emblems, and Internxt link copying.
Availability IPC and renderer integration
src/core/bootstrap/register-main-ipc-handlers.ts, src/apps/main/preload*, src/apps/main/interface.d.ts, src/apps/renderer/..., vitest.setup.main.ts
Replaces Nautilus-specific availability checks with generic file-manager availability detection.

Estimated code review effort: 4 (Complex) | ~45 minutes

Sequence Diagram(s)

sequenceDiagram
  participant App
  participant MainIPC
  participant detectAvailableFileManager
  participant FileManager
  participant Renderer
  App->>MainIPC: request file-manager availability
  MainIPC->>detectAvailableFileManager: detect default manager and binaries
  detectAvailableFileManager->>FileManager: inspect xdg-mime and command availability
  FileManager-->>detectAvailableFileManager: return supported manager or null
  detectAvailableFileManager-->>MainIPC: return availability boolean
  MainIPC-->>Renderer: resolve availability result
Loading

Possibly related PRs

Suggested reviewers: alexismora

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 0.00% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (4 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title matches the main change: adding the Internxt Virtual Drive file manager extension, with Nemo-specific support called out.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.
✨ Finishing Touches
📝 Generate docstrings
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch feat/add-nemo-file-system-extension

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.

@sonarqubecloud

Copy link
Copy Markdown

Quality Gate Failed Quality Gate failed

Failed conditions
69.1% Coverage on New Code (required ≥ 80%)

See analysis details on SonarQube Cloud

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Actionable comments posted: 5

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (1)
src/apps/renderer/pages/Widget/InfoBanners/Banners/NautilusUnavailable.tsx (1)

12-18: 🎯 Functional Correctness | 🟡 Minor | ⚡ Quick win

Complete the migration from Nautilus-specific terminology.

The new contract is file-manager agnostic, but the UI and tests still describe only Nautilus.

  • src/apps/renderer/pages/Widget/InfoBanners/Banners/NautilusUnavailable.tsx#L12-L18: rename the state/copy to generic file-manager terminology.
  • src/apps/renderer/pages/Widget/InfoBanners/Banners/NautilusUnavailable.test.tsx#L9-L20: update test descriptions to refer to supported file managers rather than Nautilus.
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/apps/renderer/pages/Widget/InfoBanners/Banners/NautilusUnavailable.tsx`
around lines 12 - 18, Complete the terminology migration in
NautilusUnavailable.tsx by renaming Nautilus-specific state and user-facing copy
to generic file-manager terminology while preserving behavior. In
NautilusUnavailable.test.tsx, update test descriptions and assertions that
reference Nautilus to describe supported file managers; apply the requested
changes at both listed files and ranges.
🧹 Nitpick comments (3)
src/apps/renderer/pages/Widget/InfoBanners/Banners/NautilusUnavailable.test.tsx (1)

9-20: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win

Rename the test descriptions to match the generic API.

These tests now exercise getFileManagerAvailability, so titles such as “when Nautilus is available” are misleading. Use “when a supported file manager is available/unavailable” to keep the tests aligned with the new contract.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In
`@src/apps/renderer/pages/Widget/InfoBanners/Banners/NautilusUnavailable.test.tsx`
around lines 9 - 20, Rename the two test descriptions in the NautilusUnavailable
test suite to refer to “a supported file manager” being available or
unavailable, matching the getFileManagerAvailability API contract while leaving
the test behavior unchanged.
assets/python-nemo/internxt-virtual-drive.py (2)

76-76: 📐 Maintainability & Code Quality | 🔵 Trivial | 💤 Low value

Remove unnecessary semicolon.

Python does not require semicolons to terminate statements.

♻️ Proposed refactor
-        file_uri = file.get_uri();
+        file_uri = file.get_uri()
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@assets/python-nemo/internxt-virtual-drive.py` at line 76, Remove the
unnecessary semicolon from the file.get_uri() statement in the surrounding code,
keeping the assignment behavior unchanged.

Source: Linters/SAST tools


80-80: 📐 Maintainability & Code Quality | 🔵 Trivial | 💤 Low value

Remove unnecessary semicolon.

Python does not require semicolons to terminate statements.

♻️ Proposed refactor
-        file_uri = file.get_uri();
+        file_uri = file.get_uri()
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@assets/python-nemo/internxt-virtual-drive.py` at line 80, Remove the
unnecessary semicolon from the statement assigning the URI in the surrounding
file-handling code, leaving the file.get_uri() call and resulting assignment
unchanged.

Source: Linters/SAST tools

🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@assets/python-nemo/internxt-virtual-drive.py`:
- Line 106: Update all external requests calls in
assets/python-nemo/internxt-virtual-drive.py to include a reasonable timeout,
such as 2 seconds: requests.get in _get_availability (106-106), requests.post in
_make_locally_available (195-195) and _copy_internxt_link (208-208), and
requests.delete in _make_remote_only (240-240). Ensure every listed call uses
the timeout consistently.
- Around line 175-181: Update _encode_file_path to URL-decode file.get_uri()
before deriving the relative path, then remove self.root_folder from the decoded
value instead of replacing self.file_base_dir on the encoded URI. Preserve the
existing UTF-8 and base64 encoding flow.

In `@src/backend/features/file-manager-extension/install.ts`:
- Around line 59-66: Update the installed-version replacement flow around
deleteExtensionFile and install so the new extension is first staged in the
destination directory, then atomically renamed over the existing extension only
after installation succeeds. Preserve the current working extension when staging
or copying fails, and remove the pre-install deletion step.

In `@src/backend/features/file-manager-extension/service.ts`:
- Around line 89-97: Update deleteExtensionFile to clean up both known
file-manager destination paths instead of relying on getFileManagerConfig’s
currently detected manager. Enumerate the Nautilus and Nemo destinations, check
each with doesFileExist, and remove every existing extension file via fs.rm
while preserving the no-op behavior when neither is installed.
- Around line 110-128: Update the Promise wrapping the reload command in the
file-manager reload service to enforce a short timeout for the exec process.
When the timeout is exceeded, terminate the child process if possible and reject
the Promise, while preserving the existing handling for exit code 255, stderr,
and successful completion.

---

Outside diff comments:
In `@src/apps/renderer/pages/Widget/InfoBanners/Banners/NautilusUnavailable.tsx`:
- Around line 12-18: Complete the terminology migration in
NautilusUnavailable.tsx by renaming Nautilus-specific state and user-facing copy
to generic file-manager terminology while preserving behavior. In
NautilusUnavailable.test.tsx, update test descriptions and assertions that
reference Nautilus to describe supported file managers; apply the requested
changes at both listed files and ranges.

---

Nitpick comments:
In `@assets/python-nemo/internxt-virtual-drive.py`:
- Line 76: Remove the unnecessary semicolon from the file.get_uri() statement in
the surrounding code, keeping the assignment behavior unchanged.
- Line 80: Remove the unnecessary semicolon from the statement assigning the URI
in the surrounding file-handling code, leaving the file.get_uri() call and
resulting assignment unchanged.

In
`@src/apps/renderer/pages/Widget/InfoBanners/Banners/NautilusUnavailable.test.tsx`:
- Around line 9-20: Rename the two test descriptions in the NautilusUnavailable
test suite to refer to “a supported file manager” being available or
unavailable, matching the getFileManagerAvailability API contract while leaving
the test behavior unchanged.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: 848e88cd-965e-4df2-83ac-968d7767f691

📥 Commits

Reviewing files that changed from the base of the PR and between e6b1171 and e5dddc3.

📒 Files selected for processing (23)
  • assets/python-nemo/internxt-virtual-drive.py
  • knip.json
  • src/apps/main/auth/service.test.ts
  • src/apps/main/config.ts
  • src/apps/main/config/save-config.ts
  • src/apps/main/interface.d.ts
  • src/apps/main/preload.d.ts
  • src/apps/main/preload.js
  • src/apps/renderer/pages/Widget/InfoBanners/Banners/NautilusUnavailable.test.tsx
  • src/apps/renderer/pages/Widget/InfoBanners/Banners/NautilusUnavailable.tsx
  • src/backend/features/file-manager-extension/detect-available.test.ts
  • src/backend/features/file-manager-extension/detect-available.ts
  • src/backend/features/file-manager-extension/install.test.ts
  • src/backend/features/file-manager-extension/install.ts
  • src/backend/features/file-manager-extension/service.test.ts
  • src/backend/features/file-manager-extension/service.ts
  • src/backend/features/file-manager-extension/version.ts
  • src/core/bootstrap/register-app-ready-flow.ts
  • src/core/bootstrap/register-main-ipc-handlers.ts
  • src/core/bootstrap/register-session-event-handlers.ts
  • src/core/electron/store/app-store.interface.ts
  • src/core/electron/store/defaults.ts
  • vitest.setup.main.ts

url = base_url + 'files/' + base64_encoded


response = requests.get(url)

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

🩺 Stability & Availability | 🟠 Major | ⚡ Quick win

Add timeouts to all external requests calls.

Synchronous requests without timeouts will block the Nemo extension thread indefinitely if the local hydration server hangs or becomes unavailable. Since file managers run these extensions frequently (especially _get_availability which evaluates file status for the UI), a blocked request can cause the entire file manager to hang. Always specify a reasonable timeout (e.g., timeout=2).

  • assets/python-nemo/internxt-virtual-drive.py#L106-L106: pass a timeout argument to requests.get() in _get_availability.
  • assets/python-nemo/internxt-virtual-drive.py#L195-L195: pass a timeout argument to requests.post() in _make_locally_available.
  • assets/python-nemo/internxt-virtual-drive.py#L208-L208: pass a timeout argument to requests.post() in _copy_internxt_link.
  • assets/python-nemo/internxt-virtual-drive.py#L240-L240: pass a timeout argument to requests.delete() in _make_remote_only.
🧰 Tools
🪛 Ruff (0.15.21)

[error] 106-106: Probable use of requests call without timeout

(S113)

📍 Affects 1 file
  • assets/python-nemo/internxt-virtual-drive.py#L106-L106 (this comment)
  • assets/python-nemo/internxt-virtual-drive.py#L195-L195
  • assets/python-nemo/internxt-virtual-drive.py#L208-L208
  • assets/python-nemo/internxt-virtual-drive.py#L240-L240
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@assets/python-nemo/internxt-virtual-drive.py` at line 106, Update all
external requests calls in assets/python-nemo/internxt-virtual-drive.py to
include a reasonable timeout, such as 2 seconds: requests.get in
_get_availability (106-106), requests.post in _make_locally_available (195-195)
and _copy_internxt_link (208-208), and requests.delete in _make_remote_only
(240-240). Ensure every listed call uses the timeout consistently.

Comment on lines +175 to +181
def _encode_file_path(self, file):
relative_path = file.get_uri().replace(self.file_base_dir, '')

parsed = urllib.parse.unquote(relative_path)

bytes_data = parsed.encode('utf-8')
return base64.b64encode(bytes_data).decode('utf-8')

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

🎯 Functional Correctness | 🟠 Major | ⚡ Quick win

Fix fragile URI prefix replacement.

If the user's home directory path contains spaces or special characters (e.g., /home/john doe/), file.get_uri() will return a URL-encoded string (e.g., file:///home/john%20doe/...), while self.file_base_dir will be unencoded (file:///home/john doe/...). The .replace() will fail, leaving the full URI as the relative_path.

Consider unquoting the URI to a full path first, then replacing the self.root_folder string.

🐛 Proposed fix
-    def _encode_file_path(self, file):
-      relative_path = file.get_uri().replace(self.file_base_dir, '')
-
-      parsed = urllib.parse.unquote(relative_path)
-
-      bytes_data = parsed.encode('utf-8')
-      return base64.b64encode(bytes_data).decode('utf-8')
+    def _encode_file_path(self, file):
+      full_path = urllib.parse.unquote(file.get_uri().replace('file://', ''))
+      relative_path = full_path.replace(self.root_folder, '')
+
+      bytes_data = relative_path.encode('utf-8')
+      return base64.b64encode(bytes_data).decode('utf-8')
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
def _encode_file_path(self, file):
relative_path = file.get_uri().replace(self.file_base_dir, '')
parsed = urllib.parse.unquote(relative_path)
bytes_data = parsed.encode('utf-8')
return base64.b64encode(bytes_data).decode('utf-8')
def _encode_file_path(self, file):
full_path = urllib.parse.unquote(file.get_uri().replace('file://', ''))
relative_path = full_path.replace(self.root_folder, '')
bytes_data = relative_path.encode('utf-8')
return base64.b64encode(bytes_data).decode('utf-8')
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@assets/python-nemo/internxt-virtual-drive.py` around lines 175 - 181, Update
_encode_file_path to URL-decode file.get_uri() before deriving the relative
path, then remove self.root_folder from the decoded value instead of replacing
self.file_base_dir on the encoded URI. Preserve the existing UTF-8 and base64
encoding flow.

Comment on lines +59 to +66
if (installed && !hasLatestVersion) {
logger.debug({
msg: '[FILE_MANAGER_EXTENSION] There is a newer version to be installed',
});

await deleteExtensionFile();
await install();
await reloadFileManager().catch((reloadError) => {

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

🩺 Stability & Availability | 🟠 Major | 🏗️ Heavy lift

Do not delete the working extension before staging its replacement.

If install() fails after Line 64, the previous extension is already gone. Stage the new asset in the destination directory and atomically rename it over the existing file, preserving the old version if copying fails.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/backend/features/file-manager-extension/install.ts` around lines 59 - 66,
Update the installed-version replacement flow around deleteExtensionFile and
install so the new extension is first staged in the destination directory, then
atomically renamed over the existing extension only after installation succeeds.
Preserve the current working extension when staging or copying fails, and remove
the pre-install deletion step.

Comment on lines +89 to +97
export async function deleteExtensionFile(): Promise<void> {
const config = await getFileManagerConfig();
if (!config) return;

const destination = path.join(config.destinationDir, extensionFileName);
const isThere = await doesFileExist(destination);
if (!isThere) return;

await fs.rm(destination);

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

🔒 Security & Privacy | 🟠 Major | ⚡ Quick win

Remove extensions for both managers during cleanup.

This only deletes from the currently detected manager. If a user installs under Nautilus, switches to Nemo, then logs out, the Nautilus extension remains installed and active. Cleanup should inspect and remove both known destination paths without depending on current detection.

🧰 Tools
🪛 ast-grep (0.44.1)

[warning] Importing child_process exposes a command-execution surface; ensure any command/argument built from input is validated, and prefer execFile/spawn with an argument array over exec.
Context: import { exec } from 'node:child_process';
Note: [CWE-78] Improper Neutralization of Special Elements used in an OS Command ('OS Command Injection').

(detect-child-process-typescript)

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/backend/features/file-manager-extension/service.ts` around lines 89 - 97,
Update deleteExtensionFile to clean up both known file-manager destination paths
instead of relying on getFileManagerConfig’s currently detected manager.
Enumerate the Nautilus and Nemo destinations, check each with doesFileExist, and
remove every existing extension file via fs.rm while preserving the no-op
behavior when neither is installed.

Comment on lines +110 to +128
return new Promise((resolve, reject) => {
exec(config.reloadCommand, (error, _, stderr) => {
if (error) {
if (error.code === 255) {
// nautilus -q and nemo -q typically return 255 status
resolve();
return;
}
reject(error);
return;
}

if (stderr) {
reject(new Error(stderr));
return;
}

resolve();
});

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

🩺 Stability & Availability | 🟠 Major | ⚡ Quick win

Bound the file-manager reload process with a timeout.

This child process is awaited during startup and logout but has no timeout, so a stuck nautilus -q or nemo -q can block either flow indefinitely. Add a short timeout and reject or kill the process when exceeded.

🧰 Tools
🪛 ast-grep (0.44.1)

[warning] Importing child_process exposes a command-execution surface; ensure any command/argument built from input is validated, and prefer execFile/spawn with an argument array over exec.
Context: import { exec } from 'node:child_process';
Note: [CWE-78] Improper Neutralization of Special Elements used in an OS Command ('OS Command Injection').

(detect-child-process-typescript)

🪛 OpenGrep (1.25.0)

[ERROR] 111-128: Dynamic command passed to child_process.exec/execSync. Use child_process.execFile or spawn with an argument array instead.

(coderabbit.command-injection.exec-js)

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/backend/features/file-manager-extension/service.ts` around lines 110 -
128, Update the Promise wrapping the reload command in the file-manager reload
service to enforce a short timeout for the exec process. When the timeout is
exceeded, terminate the child process if possible and reject the Promise, while
preserving the existing handling for exit code 255, stderr, and successful
completion.

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