fix: infinite dialogs with network mount failure (#1956)

Selecting a broken network mount triggers an infinite loop of dialogs
that requires killing the app:

1. Click invalid network mount
2. Try to mount network path
3. Show failure dialog
4. Trigger `NetworkDriveOpenEntityAfterMount`
* BUG: This fires even if the mount failed or the user canceled the
dialog!
6. Goto 2

Fix by having `network_drive(...)` return a Boolean indicating whether
the mount actually happened and using that to break the infinite error
loop.

Fixes #1629 

Sample broken network path that triggers the infinite dialog when
selected:

**~/.config/cosmic/com.system76.CosmicFiles/v1/favorites**

```ron
[
    Home,
    Documents,
    Downloads,
    Network(
        uri: "sftp://myserver/shared",
        name: "",
        path: "/tmp/fake-network/sftp-host=myserver/mnt",
    ),
]
```

- [x] I have disclosed use of any AI generated code in my commit
messages.
- [x] I understand these changes in full and will be able to respond to
review comments.
- [x] My change is accurately described in the commit message.
- [x] My contribution is tested and working as described.
- [x] I have read the [Developer Certificate of
Origin](https://developercertificate.org/) and certify my contribution
under its conditions.
This commit is contained in:
Jeremy Soller 2026-08-11 07:46:35 -06:00 • committed by GitHub
commit 5bdfffa494
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
3 changed files with 19 additions and 9 deletions

View file

@ -2678,10 +2678,14 @@ impl Application for App {
}) })
&& let Some(mounter) = MOUNTERS.get(&key) && let Some(mounter) = MOUNTERS.get(&key)
{ {
return mounter.network_drive(uri.clone()).map(move |()| { return mounter.network_drive(uri.clone()).map(move |mounted| {
cosmic::Action::App(Message::NetworkDriveOpenEntityAfterMount { if mounted {
entity, cosmic::Action::App(Message::NetworkDriveOpenEntityAfterMount {
}) entity,
})
} else {
cosmic::action::none()
}
}); });
} }
@ -3586,7 +3590,7 @@ impl Application for App {
Some((*mounter_key, self.network_drive_input.clone())); Some((*mounter_key, self.network_drive_input.clone()));
return mounter return mounter
.network_drive(self.network_drive_input.clone()) .network_drive(self.network_drive_input.clone())
.map(|()| cosmic::action::none()); .map(|_| cosmic::action::none());
} }
log::warn!( log::warn!(
"no mounter found for connecting to {:?}", "no mounter found for connecting to {:?}",

View file

@ -659,7 +659,7 @@ impl Mounter for Gvfs {
) )
} }
fn network_drive(&self, uri: String) -> Task<()> { fn network_drive(&self, uri: String) -> Task<bool> {
let command_tx = self.command_tx.clone(); let command_tx = self.command_tx.clone();
Task::perform( Task::perform(
async move { async move {
@ -668,9 +668,15 @@ impl Mounter for Gvfs {
command_tx.send(Cmd::NetworkDrive(uri, res_tx)).unwrap(); command_tx.send(Cmd::NetworkDrive(uri, res_tx)).unwrap();
res_rx.await res_rx.await
}, },
|x| { |result| match result {
if let Err(err) = x { Ok(Ok(())) => true,
Ok(Err(err)) => {
log::error!("{err:?}"); log::error!("{err:?}");
false
}
Err(err) => {
log::error!("{err:?}");
false
} }
}, },
) )

View file

@ -114,7 +114,7 @@ pub trait Mounter: Send + Sync {
fn items(&self, sizes: IconSizes) -> Option<MounterItems>; fn items(&self, sizes: IconSizes) -> Option<MounterItems>;
//TODO: send result //TODO: send result
fn mount(&self, item: MounterItem) -> Task<()>; fn mount(&self, item: MounterItem) -> Task<()>;
fn network_drive(&self, uri: String) -> Task<()>; fn network_drive(&self, uri: String) -> Task<bool>;
fn network_scan(&self, uri: &str, sizes: IconSizes) -> Option<Result<Vec<tab::Item>, String>>; fn network_scan(&self, uri: &str, sizes: IconSizes) -> Option<Result<Vec<tab::Item>, String>>;
fn dir_info(&self, uri: &str) -> Option<(String, String, Option<PathBuf>)>; fn dir_info(&self, uri: &str) -> Option<(String, String, Option<PathBuf>)>;
fn unmount(&self, item: MounterItem) -> Task<()>; fn unmount(&self, item: MounterItem) -> Task<()>;