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-
105use cosmic:: iced:: { Alignment , Length } ;
116use cosmic:: widget:: { self , dropdown, icon, settings} ;
127use cosmic:: { Apply , Element , Task , surface} ;
@@ -17,8 +12,16 @@ use cosmic_settings_page::{self as page, Section, section};
1712use freedesktop_desktop_entry:: {
1813 DesktopEntry , Iter as DesktopEntryIter , default_paths, get_languages_from_env,
1914} ;
15+ use mime:: Mime ;
2016use mime_apps:: App ;
2117use 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
2326const DROPDOWN_WEB_BROWSER : usize = 0 ;
2427const DROPDOWN_FILE_MANAGER : usize = 1 ;
@@ -47,7 +50,7 @@ pub enum Category {
4750#[ derive( Clone , Debug ) ]
4851pub 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 ) ]
6770pub 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 ) ]
8485pub 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
9092impl 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