Skip to content

Commit 593c558

Browse files
committed
fix(default-apps): missing and wrong associations
* update the cosmic-mime-apps crate with fixes to app association detection * actually use cosmic-mime-apps for significantly faster association lookups
1 parent 60a7de5 commit 593c558

3 files changed

Lines changed: 91 additions & 61 deletions

File tree

Cargo.lock

Lines changed: 4 additions & 2 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

cosmic-settings/Cargo.toml

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -63,7 +63,8 @@ libcosmic.workspace = true
6363
iced_winit.workspace = true
6464
locale1 = { git = "https://github.com/pop-os/dbus-settings-bindings", optional = true }
6565
sysinfo = { version = "=0.38.0", optional = true }
66-
mime-apps = { package = "cosmic-mime-apps", git = "https://github.com/pop-os/cosmic-mime-apps", optional = true }
66+
mime-apps = { package = "cosmic-mime-apps", git = "https://github.com/pop-os/cosmic-mime-apps", features = ["tokio"], optional = true }
67+
# mime-apps = { package = "cosmic-mime-apps", path = "../../../cosmic-mime-apps", features = ["tokio"], optional = true }
6768
notify = "8.2.0"
6869
regex = "1.12.3"
6970
ron = "0.12"

cosmic-settings/src/pages/applications/default_apps.rs

Lines changed: 85 additions & 58 deletions
Original file line numberDiff line numberDiff line change
@@ -2,11 +2,6 @@
22
// Copyright 2024 bbb651 <bar.ye651@gmail.com>
33
// SPDX-License-Identifier: GPL-3.0-only
44

5-
use std::borrow::Cow;
6-
use std::collections::{BTreeMap, BTreeSet};
7-
use std::path::{Path, PathBuf};
8-
use std::sync::Arc;
9-
105
use cosmic::iced::{Alignment, Length};
116
use cosmic::widget::{self, dropdown, icon, settings};
127
use cosmic::{Apply, Element, Task, surface};
@@ -17,8 +12,16 @@ use cosmic_settings_page::{self as page, Section, section};
1712
use freedesktop_desktop_entry::{
1813
DesktopEntry, Iter as DesktopEntryIter, default_paths, get_languages_from_env,
1914
};
15+
use mime::Mime;
2016
use mime_apps::App;
2117
use slotmap::SlotMap;
18+
use std::borrow::Cow;
19+
use std::collections::{BTreeMap, BTreeSet};
20+
use std::io::SeekFrom;
21+
use std::path::PathBuf;
22+
use std::str::FromStr;
23+
use std::sync::Arc;
24+
use tokio::io::{AsyncSeekExt, AsyncWriteExt};
2225

2326
const DROPDOWN_WEB_BROWSER: usize = 0;
2427
const DROPDOWN_FILE_MANAGER: usize = 1;
@@ -47,7 +50,7 @@ pub enum Category {
4750
#[derive(Clone, Debug)]
4851
pub enum Message {
4952
SetDefault(Category, usize),
50-
Update(CachedMimeApps),
53+
Update(Arc<CachedMimeApps>),
5154
Surface(surface::Action),
5255
}
5356

@@ -63,13 +66,11 @@ impl From<Message> for crate::pages::Message {
6366
}
6467
}
6568

66-
#[derive(Clone, Debug)]
69+
#[derive(Debug)]
6770
pub struct CachedMimeApps {
68-
pub list: mime_apps::List,
69-
pub local_list: mime_apps::List,
70-
pub apps: Vec<AppMeta>,
71-
pub known_mimes: BTreeSet<mime::Mime>,
72-
pub config_path: Box<Path>,
71+
local_list: Option<(mime_apps::List, tokio::fs::File)>,
72+
apps: Vec<AppMeta>,
73+
known_mimes: BTreeSet<mime::Mime>,
7374
}
7475

7576
#[derive(Clone, Debug)]
@@ -80,11 +81,12 @@ pub struct AppMeta {
8081
icons: Vec<icon::Handle>,
8182
}
8283

83-
#[derive(Clone, Debug, Default)]
84+
#[derive(Debug, Default)]
8485
pub struct Page {
8586
on_enter_handle: Option<cosmic::iced::task::Handle>,
8687
mime_apps: Option<CachedMimeApps>,
8788
shortcuts_config: Option<cosmic_config::Config>,
89+
update_config: Option<tokio::task::JoinHandle<()>>,
8890
}
8991

9092
impl page::AutoBind<crate::pages::Message> for Page {}
@@ -108,46 +110,45 @@ impl page::Page<crate::pages::Message> for Page {
108110
handle.abort();
109111
}
110112

113+
let config_update_handle = self.update_config.take();
114+
111115
if self.shortcuts_config.is_none() {
112116
self.shortcuts_config = cosmic_settings_config::shortcuts::context().ok();
113117
}
114118

115119
let (task, on_enter_handle) = Task::future(async move {
116-
let mut list = mime_apps::List::default();
117-
list.load_from_paths(&mime_apps::list_paths());
118-
119-
let mut local_list = mime_apps::List::default();
120-
121-
if let Some(path) = mime_apps::local_list_path()
122-
&& let Ok(buffer) = std::fs::read_to_string(&path)
123-
{
124-
local_list.load_from(&buffer);
120+
// Wait for previous copy operation to complete.
121+
if let Some(handle) = config_update_handle {
122+
_ = handle.await;
125123
}
126124

127-
let assocs = mime_apps::associations::by_app();
125+
let local_list = mime_apps::load_user_mimeapps().await.ok();
126+
127+
let mut list = mime_apps::List::default();
128+
list.load_from_paths(&mime_apps::list_paths());
129+
let assocs = mime_apps::associations::by_app(&list);
128130

129131
let apps = vec![
130-
load_defaults(&assocs, &["x-scheme-handler/http"]).await,
131-
load_defaults(&assocs, &["inode/directory"]).await,
132-
load_defaults(&assocs, &["x-scheme-handler/mailto"]).await,
133-
load_defaults(&assocs, &["audio/mp3", "application/ogg", "video/mp4"]).await,
134-
load_defaults(&assocs, &["video/mp4"]).await,
135-
load_defaults(&assocs, &["image/png"]).await,
136-
load_defaults(&assocs, &["text/calendar"]).await,
132+
load_defaults(&list, &assocs, &["x-scheme-handler/http"]),
133+
load_defaults(&list, &assocs, &["inode/directory"]),
134+
load_defaults(&list, &assocs, &["x-scheme-handler/mailto"]),
135+
load_defaults(
136+
&list,
137+
&assocs,
138+
&["audio/mpeg", "application/ogg", "audio/x-flac"],
139+
),
140+
load_defaults(&list, &assocs, &["video/mp4"]),
141+
load_defaults(&list, &assocs, &["image/png"]),
142+
load_defaults(&list, &assocs, &["text/calendar"]),
137143
load_terminal_apps(&assocs).await,
138-
load_defaults(&assocs, &["text/plain"]).await,
144+
load_defaults(&list, &assocs, &["text/plain"]),
139145
];
140146

141-
Message::Update(CachedMimeApps {
147+
Message::Update(Arc::new(CachedMimeApps {
142148
apps,
143-
list,
144149
local_list,
145150
known_mimes: mime_apps::mime_info::mime_types(),
146-
config_path: dirs::config_dir()
147-
.expect("config dir not found")
148-
.join("mimeapps.list")
149-
.into(),
150-
})
151+
}))
151152
.into()
152153
})
153154
.abortable();
@@ -188,6 +189,7 @@ impl Page {
188189
"application/ogg",
189190
"application/x-cue",
190191
"application/x-ogg",
192+
"audio/mpeg",
191193
"audio/mp3",
192194
"x-content/audio-cdda",
193195
])
@@ -238,7 +240,7 @@ impl Page {
238240

239241
let meta = &mut mime_apps.apps[category_id];
240242

241-
if meta.selected != Some(id) {
243+
if meta.selected.is_none_or(|selected| selected != id) {
242244
meta.selected = Some(id);
243245
let appid = &meta.app_ids[id];
244246

@@ -249,24 +251,40 @@ impl Page {
249251
assign_default_terminal(config, appid);
250252
}
251253

252-
for mime in mime_types {
253-
if let Ok(mime) = mime.parse() {
254-
mime_apps
255-
.local_list
256-
.set_default_app(mime, [appid, ".desktop"].concat());
257-
};
254+
if let Some((local_list, local_file)) = mime_apps.local_list.as_mut() {
255+
for mime in mime_types {
256+
if let Ok(mime) = mime.parse() {
257+
tracing::info!(target: "default-apps", ?mime, appid, "setting default for mime");
258+
local_list.set_default_app(mime, [appid, ".desktop"].concat());
259+
};
260+
}
261+
262+
if let Some(config_update_handle) = self.update_config.take() {
263+
config_update_handle.abort();
264+
}
265+
266+
let mut buffer = local_list.to_string();
267+
buffer.push('\n');
268+
269+
if let Ok(mut local_file) =
270+
futures::executor::block_on(local_file.try_clone())
271+
{
272+
self.update_config = Some(tokio::spawn(async move {
273+
tracing::debug!(target: "default-apps", buffer, "writing to mimeapps config");
274+
_ = local_file.seek(SeekFrom::Start(0)).await;
275+
_ = local_file.set_len(buffer.len() as u64).await;
276+
_ = local_file.write_all(buffer.as_bytes()).await;
277+
_ = local_file.flush().await;
278+
_ = local_file.seek(SeekFrom::Start(0)).await;
279+
_ = tokio::process::Command::new("update-desktop-database")
280+
.status()
281+
.await;
282+
}));
283+
}
258284
}
259-
260-
let mut buffer = mime_apps.local_list.to_string();
261-
buffer.push('\n');
262-
263-
_ = std::fs::write(&mime_apps.config_path, buffer);
264-
_ = std::process::Command::new("update-desktop-database").status();
265285
}
266286
}
267-
Message::Update(mime_apps) => {
268-
self.mime_apps = Some(mime_apps);
269-
}
287+
Message::Update(mime_apps) => self.mime_apps = Arc::into_inner(mime_apps),
270288
Message::Surface(a) => {
271289
return cosmic::task::message(crate::app::Message::Surface(a));
272290
}
@@ -286,7 +304,7 @@ fn app_item(meta: &AppMeta, label: String, category: Category) -> widget::FlexRo
286304
} else {
287305
dropdown::popup_dropdown(
288306
&meta.apps,
289-
meta.selected,
307+
Some(meta.selected.unwrap_or(0)),
290308
move |id| Message::SetDefault(category, id),
291309
cosmic::iced::window::Id::RESERVED,
292310
Message::Surface,
@@ -393,12 +411,16 @@ fn assign_default_terminal(config: &cosmic_config::Config, appid: &str) {
393411
}
394412
}
395413

396-
async fn load_defaults(assocs: &BTreeMap<Arc<str>, Arc<App>>, for_mimes: &[&str]) -> AppMeta {
414+
fn load_defaults(
415+
list: &mime_apps::List,
416+
assocs: &BTreeMap<Arc<str>, Arc<App>>,
417+
for_mimes: &[&str],
418+
) -> AppMeta {
397419
let mut unsorted = Vec::new();
398420
let mut current_app = None;
399421

400422
for for_mime in for_mimes {
401-
let Ok(mime) = for_mime.parse() else {
423+
let Ok(mime) = Mime::from_str(for_mime) else {
402424
return AppMeta {
403425
selected: None,
404426
app_ids: Vec::new(),
@@ -407,7 +429,12 @@ async fn load_defaults(assocs: &BTreeMap<Arc<str>, Arc<App>>, for_mimes: &[&str]
407429
};
408430
};
409431

410-
let current_app_entry = xdg_mime_query_default(for_mime).await;
432+
let current_app_entry = dbg!(
433+
list.default_app_for(&mime)
434+
.and_then(|entries| entries.first())
435+
);
436+
437+
tracing::info!(target: "default-apps", ?mime, ?current_app_entry, "entry for mime");
411438
let current_appid = current_app_entry
412439
.as_ref()
413440
.and_then(|entry| entry.strip_suffix(".desktop"));

0 commit comments

Comments
 (0)