文件管理器很龐大,這幾年在開源 nemo 的基礎(chǔ)上烫映,也算是解決了不少內(nèi)部測試報(bào)出來的問題锭沟,但是對(duì)它的認(rèn)識(shí)一直都很片面,并不能從整體上對(duì)它有個(gè)完整的認(rèn)識(shí)辫红。下面幾個(gè)還算比較熟悉:
窗口左邊欄 NemoPlacesSidebar
窗口視圖和右鍵菜單 NemoView及其子類
窗口容器 NemoIconContainer
計(jì)算機(jī)頁面 computer:/// 的顯示
窗口面板和地址欄 NemoWindowPane、NemoLocationEntry
插件機(jī)制
以上這幾個(gè)都還只是 GUI 顯示切油,沒有涉及到 Nemo 的 I/O澎胡。文件管理器分為兩大部分攻谁,用戶交互的 GUI 部分戚宦,是GTK 開發(fā)的受楼,顯示的實(shí)際數(shù)據(jù)(也就是文件)艳汽,是通過 gio/glib 庫拿到的骚灸。寫這邊文章的目的是希望能好好梳理整體流程,將雙擊目錄圖標(biāo)蝶柿,請(qǐng)求數(shù)據(jù)非驮,顯示圖標(biāo)名稱的整個(gè)過程描述清楚交汤。
以打開計(jì)算機(jī)頁面為例芙扎,希望能梳理出從我雙擊計(jì)算機(jī)圖標(biāo)到提取文件信息戒洼,再到請(qǐng)求 computer:/// 中所有文件的圖標(biāo)圈浇、名稱、大小等召耘,最終把圖標(biāo)文件名等顯示在視圖中的一整套過程褐隆。增加對(duì)下面這幾個(gè)對(duì)象的認(rèn)識(shí)
NemoFile NemoDirectory
NemoIconInfo
EelCanvasItem
Nemo 的 GUI 部分
GUI 包括工具欄衫贬、狀態(tài)欄虫埂、左邊欄和視圖區(qū)掉伏,其中視圖分為兩大類:圖標(biāo)視圖和列表視圖斧散,而圖標(biāo)視圖又分為大、中栈暇、小圖標(biāo)和緊湊視圖源祈。這部分代碼基本都在 src 目錄下香缺。
圖標(biāo)視圖中所有顯示出來的文件目錄等图张,數(shù)據(jù)內(nèi)容存在 NemoView 或者子類 NemoIconView祸轮,交互事件響應(yīng)适袜,圖標(biāo)位置的更新等在 NemoIconContainer痪蝇。容器 NemoIconContainer 中最重要的就是 NemoIconCanvasItem躏啰。這部分代碼基本都在 libnemo-private。
NemoIconView 和 NemoIconContainer
NemoIconView 是圖標(biāo)視圖毫捣,與列表視圖不一樣蔓同,列表視圖其實(shí)是 GTK 的控件斑粱,所以它不需要 NemoIconContainer 容器则北,緊湊視圖尚揣、大中小三個(gè)圖標(biāo)視圖都是 NemoIconView快骗,這類視圖對(duì)象有個(gè)成員是 NemoIconContainer方篮,通過它來響應(yīng)用戶雙擊恭取、選中、右鍵以及拷貝刪除容器類中對(duì)象的各個(gè)信號(hào)裕照。比如有個(gè)很神奇的功能晋南,這次分析代碼才發(fā)現(xiàn)羔砾,如果 click-double-parent-folder 設(shè)置為 true,雙擊圖標(biāo)視圖的空白處趾访,可以回到父目錄扼鞋,這個(gè)功能在 NemoIconView 中通過響應(yīng) NemoIconContainer 的 button_press_event 信號(hào)來實(shí)現(xiàn)云头。
Nemo 的 I/O 部分
NemoFile 和 NemoDirectory
- NemoFile 主要用途:封裝了調(diào)度異步 I/O 的功能溃槐。
NemoFile 提供了一系列異步I/O的API 來應(yīng)對(duì)文件信息尚未確定的情況昏滴。就比如通過 nemo_file_get 接口拿到某個(gè)文件和目錄結(jié)構(gòu)(注意此時(shí)不會(huì)有任何I/O)影涉,我們想知道這個(gè)文件的類型 nemo_file_get_file_type蟹倾,但是對(duì)于新建的文件很可能一開始還并不能確定類型是什么,這種情況該怎么處理培慌?
用 nemo_file_monitor_add 和 nemo_file_call_when_ready吵护。
比如加載目錄時(shí)祥诽,我們表示想要知道屬性
attributes =
NEMO_FILE_ATTRIBUTE_INFO |
NEMO_FILE_ATTRIBUTE_MOUNT |
NEMO_FILE_ATTRIBUTE_FILESYSTEM_INFO;
該怎么做雄坪?
答案是通過在 load_directory 中調(diào)用函數(shù)加關(guān)注维哈,
nemo_file_monitor_add (view->details->directory_as_file,
&view->details->directory_as_file,
attributes);
當(dāng)這幾個(gè)屬性準(zhǔn)備好了阔挠,就會(huì)自動(dòng)去執(zhí)行call_when_ready 的回調(diào):
nemo_file_call_when_ready
(view->details->directory_as_file,
attributes,
metadata_for_directory_as_file_ready_callback, view);
一旦我們調(diào)用了 nemo_file_monitor_add 就表示關(guān)聯(lián)了 NemoFile 的 changed 信號(hào)竭宰,nemo-monitor.c 中
g_signal_connect (ret->monitor, "changed",
G_CALLBACK (dir_changed), ret);
這個(gè) ret->monitor 成員就是一個(gè) GFileMonitor 對(duì)象份招。所以我們不需要再去 connect 文件的 changed 信號(hào)廓旬,一旦屬性發(fā)生了變化孕豹,nemo_file_call_when_ready 注冊的回調(diào)函數(shù)就會(huì)自動(dòng)被調(diào)用。(不管是 monitor 還是 callback 都是可以取消的)
on an ongoing basis 持續(xù)進(jìn)行的
Something which occurs on an ‘ongoing basis’ is something which is occurring and which will continue to occur, without a definite ending. The sun rises and the sun sets ‘on an on-going basis’. Your boss expects you to show up five days a week at 8 AM on an ‘on-going basis’.
2十气、NemoDirectory 用于管理一組文件励背,當(dāng)目錄內(nèi)任何一個(gè)文件修改了,都會(huì)有 files_changed 信號(hào)砸西,所以我們不需要像上面操作 NemoFile 一樣挨個(gè)去 monitor叶眉,去 callback when ready。如果我們想要一次性操作目錄下的所有文件芹枷,只需要 NemoDirectory對(duì)象衅疙,在這個(gè)對(duì)象里面我們可以一次性監(jiān)控某個(gè)目錄下的所有 NemoFile 對(duì)象,只要我們監(jiān)聽一個(gè) files_changed 信號(hào)鸳慈,當(dāng)目錄下任意一個(gè)文件被修改了都能感應(yīng)到,而不需要對(duì)每個(gè)文件都添加 monitor嗽上,可以省很多事情挪圾。
從文件管理器角度來說,用戶點(diǎn)擊某個(gè)文件夾圖標(biāo),或者在地址欄輸入某個(gè) uri丧肴,回車跳轉(zhuǎn)到對(duì)應(yīng)的地址纸巷,這些UI層的操作竖伯,執(zhí)行到最后,都是從文件系統(tǒng)中去獲取文件肩袍,它使用的是 gio-2.0 庫的接口。
nemo 調(diào)用 gio 庫,實(shí)際上是 gvfs 實(shí)現(xiàn)出來的。 glib 中定義接口GFileIface夕冲,并提供接口方法卜高,gvfs 實(shí)現(xiàn)這個(gè)接口。
Glib 接口 | gvfs 實(shí)現(xiàn) |
---|---|
GFileIface | GDaemonFile |
g_file_enumerate_children_async | g_daemon_file_enumerate_children_async |
g_file_enumerator_next_files_finish | g_daemon_file_enumerator_next_files_finish |
…… | …… |
GFile 接口主要用于操作虛擬文件系統(tǒng)上文件和目錄的一個(gè)高層抽象,并不代表實(shí)際的文件,文件內(nèi)容那些的操作都交給 GInputStream、GOutputStream。
總結(jié)在文件管理器中打開一個(gè)文件窗口的各種方式,比如通過書簽的方式打開
- activate_bookmark_in_menu_item (參數(shù)有個(gè) location)
location = nemo_bookmark_get_location (holder->bookmark);
通過在 entry 中輸入地址回車的方式打開:
- navigation_bar_location_changed_callback (參數(shù)也有個(gè) location)
nemo_window_slot_open_location_full
上面兩種方式都會(huì)進(jìn)入 begin_location_change 函數(shù),再接著往下走。在這個(gè)函數(shù)里,slot->determine_view_file = nemo_file_get (location); 用來拿到所選視圖的所有信息惠遏。然后:
nemo_file_call_when_ready (slot->determine_view_file,
NEMO_FILE_ATTRIBUTE_INFO |
NEMO_FILE_ATTRIBUTE_MOUNT,
got_file_info_for_view_selection_callback,
slot);
在回調(diào)函數(shù)中,獲取view_id,判斷是哪種視圖?圖標(biāo)還是列表?比較特殊的是 computer:/// 為了實(shí)現(xiàn)類似于windows 的首頁,我們定制了一個(gè)全新的視圖。
各種信息準(zhǔn)備完成后,函數(shù)調(diào)用如下:
create_content_view
load_new_location
nemo_view_load_location
load_directory
3.雙擊文件圖標(biāo)打開:
雙擊的操作作用于 NemoIconContainer 對(duì)象栗涂,也就是調(diào)用activate_selected_items暖释,然后發(fā)出 activate 信號(hào)。NemoIconView 收到這個(gè)信號(hào)后式矫,調(diào)用 nemo_view_activate_files,然后就走到 nemo_mime_activate_files, NemoIconView 作為 NemoView 的子類,會(huì)向上鏈接執(zhí)行父類的函數(shù)喘沿,也就是說 NemoView 的 load_directory 去各 uri 加載文件睁宰。
這三種方式,盡管開頭不一樣,但是最終都需要 load_directory 去加載目錄中的文件虹蒋。
梳理 Nemo 的 I/O
Nemo 的主線程上沒有任何的 I/O 操作,如果主線程上有 I/O扛吞,那么用戶在操作文件管理器過程中不可避免地會(huì)卡住,這種體驗(yàn)挺讓人崩潰的郁竟,所以文件管理器在設(shè)計(jì)的時(shí)候就保證主線程不會(huì)被阻塞勒虾,也就是說不會(huì)在nemo主線程上做任何磁盤I/O操作。
Nemo的多線程通過專門的異步對(duì)象來處理潭陪,需要仔細(xì)研讀代碼nemo-directory-async.c枝秤,它封裝了許多對(duì) NemoDirectory 的操作菌赖,最重要的就是 nemo_directory_async_state_changed 特姐,這個(gè)函數(shù)在非常非常頻繁的被調(diào)用脑题,他就是 Nemo I/O 操作的核心,每當(dāng)I/O完成或者 monitor涩笤、call_when_reay 發(fā)生變化都會(huì)調(diào)用它翔始。這個(gè)函數(shù)主要工作就是 start_or_stop_io。
比如查詢文件信息的接口:nemo_directory_get_info_for_new_files漫萄,它會(huì)去調(diào)用 g_file_query_info_async窿撬,這個(gè) glib 接口的具體實(shí)現(xiàn)在 gvfs跛璧,假如是個(gè)本地文件,接口可能在 gvfs/gdaemonfile.c 中的函數(shù)g_daemon_file_query_info_async戏锹。它會(huì)異步調(diào)用 gvfs 的 QueryInfo dbus 接口。
以上面介紹的第三種方式雙擊文件夾為例京髓,雙擊的操作作用于 NemoIconContainer 對(duì)象揽涮,也就是調(diào)用activate_selected_items溉跃,然后發(fā)出 activate 信號(hào)。NemoIconView 收到這個(gè)信號(hào)后奢方,調(diào)用 nemo_view_activate_files忠聚,然后就走到 nemo_mime_activate_files, NemoIconView 作為 NemoView 的子類欢瞪,會(huì)向上鏈接執(zhí)行父類的函數(shù)重贺,也就是說 NemoView 的 load_directory 去各 uri 加載文件下來骑祟。加載時(shí)關(guān)心的屬性有:
attributes =
NEMO_FILE_ATTRIBUTE_INFO |
NEMO_FILE_ATTRIBUTE_MOUNT |
NEMO_FILE_ATTRIBUTE_FILESYSTEM_INFO;
view->details->metadata_for_directory_as_file_pending = TRUE;
view->details->metadata_for_files_in_directory_pending = TRUE;
nemo_file_call_when_ready
(view->details->directory_as_file,
attributes,
metadata_for_directory_as_file_ready_callback, view);
nemo_directory_call_when_ready
(view->details->model,
attributes,
FALSE,
metadata_for_files_in_directory_ready_callback, view);
/* If capabilities change, then we need to update the menus
* because of New Folder, and relative emblems.
*/
attributes =
NEMO_FILE_ATTRIBUTE_INFO |
NEMO_FILE_ATTRIBUTE_FILESYSTEM_INFO;
**nemo_file_monitor_add** (view->details->directory_as_file,
&view->details->directory_as_file,
attributes);
這三個(gè)屬性拿到后吧凉,分別執(zhí)行目錄和文件的 call_when_ready 函數(shù)饲鄙,這里的目錄就是我們想要加載的目錄本身忍级,這里的文件同樣也是這個(gè)目錄,只不過作為文件而已。當(dāng)目錄作為目錄時(shí)挪略,它需要關(guān)心所有子文件和子目錄的元數(shù)據(jù)是否完成,當(dāng)目錄作為文件時(shí)磨淌,只需要關(guān)心它自己的元數(shù)據(jù)有沒有準(zhǔn)備好就可以了疲憋。不管怎么說,這兩個(gè)回調(diào)都會(huì)執(zhí)行 finish_loading梁只,而在這個(gè)函數(shù)里面缚柳,關(guān)心的屬性
attributes =
NEMO_FILE_ATTRIBUTES_FOR_ICON |
NEMO_FILE_ATTRIBUTE_DIRECTORY_ITEM_COUNT |
NEMO_FILE_ATTRIBUTE_INFO |
NEMO_FILE_ATTRIBUTE_LINK_INFO |
NEMO_FILE_ATTRIBUTE_MOUNT |
NEMO_FILE_ATTRIBUTE_EXTENSION_INFO;
比如文件圖標(biāo)、目錄下的文件個(gè)數(shù)以及一些擴(kuò)展信息等搪锣。拿到這些信息秋忙,執(zhí)行回調(diào) files_added_callback,這樣走下去我們就能把它在容器中顯示出來了构舟。調(diào)用函數(shù)清單:
queue_pending_files
schedule_timeout_display_of_pending_files 開始逐步加載文件
display_pending_callback
display_pending_files
process_new_files 和 process_old_files
在process_old_files 函數(shù)中遍歷 files_added 鏈表灰追,發(fā)出 add_file 信號(hào),關(guān)聯(lián)子類的 *_add_file 回調(diào)狗超,比如 nemo_icon_view_add_file弹澎,最后調(diào)用 nemo_icon_container_add 將圖標(biāo)顯示出來。
在這個(gè)流程中努咐,如果文件內(nèi)容比較多苦蒿,不能馬上加載完成,加載時(shí)會(huì)在窗口右下角展示正在加載的轉(zhuǎn)圈( finish_loading)渗稍。這里涉及到一個(gè)設(shè)計(jì)模式佩迟,MVC, NemoIconView 顧名思義竿屹,就是視圖报强;M 其實(shí)是 NemoDirectory,這個(gè)對(duì)象負(fù)責(zé)數(shù)據(jù)的加載拱燃。NemoDirectory 發(fā)出 DONE_LOADING 的信號(hào)秉溉,此時(shí)才是所有文件都加載完成了。
另外扼雏,需要注意的是這個(gè)函數(shù) nemo_directory_async_state_changed坚嗜,注釋說每當(dāng) monitor 或者 call_when_ready 列表發(fā)生變化夯膀,又或者每個(gè) I/O 活動(dòng)完成诗充,比如 query_info_callback,都會(huì)調(diào)用這個(gè)函數(shù)诱建。我們姑且認(rèn)為蝴蜓,在 nemo_file_monitor_add 和那兩個(gè) call_when_ready 函數(shù)首先觸發(fā)了這個(gè)函數(shù)。這個(gè)函數(shù)里面,開始了及其重要的 start_or_stop_io 函數(shù)茎匠。在這里我們大概能看出來有多少 I/O格仲,比如 file-list、file-info诵冒、directory-count凯肋、deep-count、mime-list汽馋、thumbnail侮东、filesystem-info、extension-info 等等豹芯。
以 file-list 為例悄雅,start_monitoring_file_list 調(diào)用 g_file_enumerate_children_asyc,這個(gè) async 的回調(diào) enumerate_children_callback 調(diào)用 g_file_enumerator_next_files_finish铁蹈, 對(duì)應(yīng)上一個(gè) async宽闲。并且調(diào)用 g_file_enumerator_next_files_async, 它的 more files callback 再去調(diào)用 g_file_enumerator_next_files_finish握牧。
這些都是一些很典型的 gio/glib 函數(shù)容诬, _async 表示開啟異步操作蜡歹,_finish 表示拿到結(jié)果款咖。從文件管理器角度來說握恳,所以加載過程都是異步執(zhí)行的函筋。nemo 拿到數(shù)據(jù)后(包括需要渲染到 GTK 的 GIcon)闯睹,將它們一一顯示到前端界面呻澜。
** nemo_file_monitor_add 4346
** nemo_directory_async_state_changed 4418 ** start_or_stop_io 4342
** Message: starting file list in 0x2620ae0
** Message: load_directory called to monitor file list of 0x2620ae0
** nemo_directory_async_state_changed 4424 ** start_or_stop_io 4342
** Message: starting file list in 0x2620900
** Message: load_directory called to monitor file list of 0x2620900
** nemo_directory_async_state_changed 4424 ** start_or_stop_io 4342
** nemo_directory_force_reload_internal 2271
** nemo_directory_async_state_changed 4424 ** start_or_stop_io 4342
** nemo_directory_async_state_changed 4424 ** start_or_stop_io 4342
上面打印出來了一些函數(shù)調(diào)用順序攒霹,起點(diǎn)函數(shù)就是 nemo_file_monitor_add.基本的 nemo I/O 梳理也就差不多了珊拼。
圖標(biāo)視圖中 icon 的顯示
這部分需要從 NemoView 和 NemoIconContainer 來介紹厘托,在process_old_files 函數(shù)友雳,遍歷 files_added 鏈表,發(fā)出 add_file 信號(hào)铅匹,關(guān)聯(lián)子類的 *_add_file 回調(diào)押赊,比如 nemo_icon_view_add_file,最后調(diào)用 nemo_icon_container_add 將圖標(biāo)顯示出來包斑。
nemo_icon_container_add 的工作內(nèi)容包括:新建 NemoIcon 對(duì)象流礁,成員包括x、y坐標(biāo)罗丰,預(yù)設(shè)值 UNPOSITIONED神帅,scale、 NemoIconCanvasItem),新建的這個(gè) icon 被加到 container->details->icons萌抵,準(zhǔn)備好了之后找御,在閑時(shí)將圖標(biāo)添加到容器中顯示出來元镀。調(diào)用函數(shù):
schedule_redo_layout redo_layout_callback redo_layout_internal finish_adding_new_icons
其中 finish_adding_new_icons 的主要工作如下:
for item in container->details->new_icons:
do
nemo_icon_container_update_icon
semi_position and no_position
finish_adding_icon
done
for item in semi_position_icons:
do
find_empty_location
icon_set_position
done
我對(duì) semi_position_icon 的理解就是需要重新計(jì)算坐標(biāo)位置的圖標(biāo)們,不知道對(duì)不對(duì)霎桅。判斷依據(jù):is_old_or_unknown_icon_data栖疑。 圖標(biāo)的 pixbuf 更新,文件名顯示 editable_text, additional_text, embeded_text 等滔驶,都在函數(shù) nemo_icon_container_update_icon 中完成遇革。 要顯示的圖標(biāo)從這里來:
icon_info = nemo_icon_container_get_icon_images (container, icon->data, icon_size,
&embedded_text,
icon == details->drop_target,
large_embedded_text, &embedded_text_needs_loading,
&has_open_window);
if(icon_info){
if (container->details->forced_icon_size > 0) {
pixbuf = nemo_icon_info_get_pixbuf_at_size (icon_info, icon_size);
} else {
pixbuf = nemo_icon_info_get_pixbuf (icon_info);
}
nemo_icon_info_get_attach_points (icon_info, &attach_points, &n_attach_points);
has_embedded_text_rect = nemo_icon_info_get_embedded_rect (icon_info,
&embedded_text_rect);
g_object_unref (icon_info);
}
圖標(biāo)視圖中 icon 的銷毀
當(dāng)我們需要切換到其他目錄時(shí),當(dāng)前的 nemo-icon-container 需要先清空揭糕,然后再加載新的目錄澳淑,如果不清空,可能就導(dǎo)致上一次目錄的文件圖標(biāo)和新目錄的文件圖標(biāo)重疊了插佛。 nemo-icon-container 在清空時(shí)杠巡,會(huì)遍歷當(dāng)前 container 中所有所有的 icons,挨個(gè)去釋放這個(gè) icon 的資源雇寇。 我們已經(jīng)知道 NemoIcon 除了 icon 本身氢拥,還有一個(gè) NemoIconCanvasItem 對(duì)象需要釋放。這個(gè) item 在新建的時(shí)候都會(huì)掛靠在一個(gè) parent 下面锨侯,container->root.代碼在 nemo_icon_container_add 中嫩海,
icon->item = NEMO_ICON_CANVAS_ITEM
(eel_canvas_item_new (EEL_CANVAS_GROUP (EEL_CANVAS (container)->root),
nemo_icon_canvas_item_get_type (),
"visible", FALSE,
NULL));
當(dāng)切換目錄的時(shí)候,需要銷毀當(dāng)前容器中的所有圖標(biāo)對(duì)象囚痴,于是item 在 dispose 階段叁怪,每個(gè) item 請(qǐng)求 canvas 畫布重繪這個(gè) item 所在的位置;執(zhí)行 unmap深滚、unrealize 等操作奕谭。最重要的是,在它的 parent 不為空時(shí)痴荐,執(zhí)行 group_remove血柳,將自己這個(gè) item 從它的 parent 容器中刪除。
這就會(huì)導(dǎo)致一個(gè)問題生兆,我在分析 nemo 最新版 4.6.2 時(shí)(2020.7),發(fā)現(xiàn)如果打開一個(gè)巨大的目錄难捌,下有子文件十萬個(gè),當(dāng)全部顯示出來鸦难,我想切換到其他目錄根吁,需要等待40秒。于是在切換過程中合蔽,gdb 發(fā)現(xiàn)在切換時(shí)等待的時(shí)間內(nèi)的堆棧如下:
group_remove
eel_canvas_item_destroy
icon_free
nemo_icon_container_clear
nemo_icon_view_clear_full
load_directory
nemo_view_load_location
load_directory 會(huì)執(zhí)行 nemo_view_stop_loading 停止前一次的加載击敌,并且發(fā)出 view 的clear 信號(hào),從而開始 nemo_icon_view_clear 接著定位到時(shí)間最長的調(diào)用是在 nemo_icon_container_clear 中的
for (p = details->icons; p != NULL; p = p->next)
{
icon_free (p->data);
}
也就是說辈末,因?yàn)?EelCanvasItem 在 destroy 時(shí)耗時(shí)過久愚争,導(dǎo)致切換目錄需要四十多秒。 在eel_canvas_item_dispose 函數(shù)中
if (item->parent)
group_remove (EEL_CANVAS_GROUP (item->parent), item);
問題就在于這個(gè)函數(shù)挤聘,在分析 group_remove 函數(shù)時(shí)轰枝,發(fā)現(xiàn)這部分邏輯的確有很大的優(yōu)化空間。從 parent 中刪除一個(gè)子對(duì)象组去,就是從一個(gè)鏈表中刪除一個(gè)對(duì)象鞍陨,也就意味著又是遍歷鏈表。group_remove 會(huì)遍歷這個(gè) parent 的所有子節(jié)點(diǎn)从隆,找到當(dāng)前自己這個(gè) item诚撵,把它從 parent 容器中移除。 這里就是雙循環(huán)乘法键闺,雖然內(nèi)部循環(huán)在找到 item 就會(huì) break寿烟,不至于N * N,但是 N * lgN 還是有的辛燥,其中 N 為 10w 數(shù)量級(jí)筛武。 一次計(jì)算的微秒數(shù)可以忽略不計(jì),但是 10w× lg10w 的時(shí)間還是很可觀的挎塌,當(dāng)我們從一個(gè) 10w 文件的目錄切換到其他目錄徘六,就需要等待 40s,此時(shí)文件管理器完全沒辦法操作榴都,CPU 處于瘋狂計(jì)算狀態(tài)待锈。
不過,具體怎么優(yōu)化嘴高,我還沒想好竿音,待補(bǔ)充。
異步I/O的壞處拴驮?
是否會(huì)讓我們把圖標(biāo)視圖中的圖標(biāo)重復(fù)畫多次谍失?比如我們在拿到文件信息之后立馬把文件顯示出來,但很可能一開始我們拿到的信息并不知道文件類型莹汤,而是把什么未知文件類型的圖標(biāo)顯示出來了快鱼。然而在極短的時(shí)間后(ms級(jí))我們拿到了關(guān)于這個(gè)文件的所有信息,包括文件圖標(biāo)纲岭,所以又畫了一次抹竹。為了避免出現(xiàn)這樣的問題,我們可以稍微加一點(diǎn)點(diǎn)延遲止潮,等到拿到我們需要的基本信息之后再去顯示文件圖標(biāo)等信息窃判。從總的加載時(shí)間來看,這種異步操作還算比較快喇闸,但是在用戶看起來并不快袄琳,因?yàn)樗麄儾荒芰ⅠR看到數(shù)據(jù)询件。是否有更好的方法?
關(guān)于緩存
1唆樊、如果 NemoFile 緩存了錯(cuò)誤的數(shù)據(jù)宛琅,我們需要能再去抓取新的信息,
我們可以用nemo_file_invalidate_attributes逗旁、nemo_file_invalidate_all_attributes 和nemo_directory_force_reload 這幾個(gè)函數(shù)嘿辟。加入我們在 Nemo 中寫了一段代碼知道肯定會(huì)影響到已經(jīng)緩存的信息,就可以調(diào)用這仨函數(shù)中的某個(gè)來更新數(shù)據(jù)片效。
2红伦、我們很難去決定一個(gè)文件信息要不要緩存,所以基于這么一個(gè)約定:如果NemoFile 的索引數(shù)清零信息就不會(huì)緩存淀衣。這也就意味著其他擁有我們 NemoFile 對(duì)象的程序決定了我們能不能拿到最新的文件信息昙读。如果其他應(yīng)用引用了我們的 NemoFile 對(duì)象,我們再去調(diào)用 nemo_file_call_when_ready 或者 nemo_file_monitor_add 只會(huì)拿到已經(jīng)緩存過了的信息膨桥。
錯(cuò)誤的緩存會(huì)引發(fā)很奇怪的問題箕戳,比如出現(xiàn)過samba 連接卸載后就無法再訪問,就是因?yàn)橛益I菜單插件中獲取了 nemo_file_info_get_location 但是用完了沒有釋放国撵,導(dǎo)致下次再連接還是上一次斷開的 url陵吸,無法再連接上。
問題描述:
用戶連接 samba介牙,進(jìn)入samba子目錄后壮虫,點(diǎn)擊 location bar 回到上一級(jí)目錄,再點(diǎn)擊卸載 samba环础,之后就再也無法連接 samba囚似,需要注銷再登錄。
問題原因:
文件壓縮在實(shí)現(xiàn)文件管理器右鍵壓縮功能插件時(shí)线得,沒有釋放內(nèi)存導(dǎo)致的問題饶唤。
問題定位:
這個(gè)問題最開始定位是 nemo 或者 gvfs 的問題,特別是對(duì)于 samba贯钩、ftp 這類連接來說募狂,nemo 這邊請(qǐng)求文件信息,實(shí)際上是向 gvfs 請(qǐng)求角雷。用戶點(diǎn)擊側(cè)邊欄書簽或者在地址欄輸入 smb 地址祸穷,最終走向都是 begin_location_change。在這個(gè)函數(shù)中勺三,它會(huì)去 query file info雷滚,nemo 中調(diào)用的是抽象的 glib 接口,具體實(shí)現(xiàn)在 gvfs 中吗坚。正巧之前為了分析其他問題祈远,我在 gvfs 中 gdaemonfile.c 的 query_info 接口添加了打印信息呆万,每次打開 samba,進(jìn)入子目錄车份,回到上級(jí)目錄等行為都會(huì)打印 query info 的信息谋减,當(dāng)出現(xiàn)問題的時(shí)候,就不能正常的去請(qǐng)求文件信息躬充,卡住了。所以我先 gdb nemo 設(shè)置 break point 為 g_daemon_file_query_info 查看它的調(diào)用堆棧讨便。
858 gdaemonfile.c: No such file or directory.
(gdb) bt
0 g_daemon_file_query_info (file=0x7fffdc091300, attributes=0x7ffff4e2cddb "standard::type", flags=G_FILE_QUERY_INFO_NONE,
cancellable=0x0, error=0x0) at gdaemonfile.c:858
1 0x00007ffff4d5dd91 in g_file_query_file_type () from /usr/lib/x86_64-linux-gnu/libgio-2.0.so.0
2 0x00000000004cd98c in get_is_dir_hack (file=<optimized out>) at nemo-action.c:1333
3 nemo_action_get_visibility (action=action@entry=0xfb00c0, selection=selection@entry=0xa124c0, parent=parent@entry=0xac7d50) at nemo-action.c:1431
4 0x00000000004aa878 in determine_visibility (data=<optimized out>, callback_data=<optimized out>) at nemo-view.c:6436
5 0x00007ffff47f2a9d in g_list_foreach () from /lib/x86_64-linux-gnu/libglib-2.0.so.0
6 0x00000000004aca55 in update_actions_visibility (view=<optimized out>) at nemo-view.c:6455
7 real_update_menus (view=<optimized out>) at nemo-view.c:10439
8 0x000000000044cbce in nemo_icon_view_update_menus (view=0x11490f0) at nemo-icon-view.c:1609
9 0x00000000004a427f in nemo_view_update_menus (view=0x11490f0) at nemo-view.c:814
10 update_menus_timeout_callback (data=<optimized out>) at nemo-view.c:3907
11 0x00007ffff47f7113 in ?? () from /lib/x86_64-linux-gnu/libglib-2.0.so.0
12 0x00007ffff47f669a in g_main_context_dispatch () from /lib/x86_64-linux-gnu/libglib-2.0.so.0
13 0x00007ffff47f6a50 in ?? () from /lib/x86_64-linux-gnu/libglib-2.0.so.0
14 0x00007ffff47f6afc in g_main_context_iteration () from /lib/x86_64-linux-gnu/libglib-2.0.so.0
15 0x00007ffff4db270d in g_application_run () from /usr/lib/x86_64-linux-gnu/libgio-2.0.so.0
16 0x0000000000431c79 in main (argc=2, argv=0x7fffffffe108) at nemo-main.c:136
根據(jù)這個(gè)調(diào)用堆棧充甚,得到實(shí)際引發(fā)問題的函數(shù)是 real_update_menus,這個(gè)函數(shù)超級(jí)長霸褒,就不列出了伴找。下一步就是需要找出是哪個(gè)函數(shù)導(dǎo)致無法請(qǐng)求正確的 file info,通過二分返回废菱,定位到出問題的地方是:
reset_extension_actions_menu (view, selection);
get_all_extension_menu_items
nemo_menu_provider_get_file_items
實(shí)際出問題的就是這個(gè) get_file_items, 找遍了 nemo 的代碼技矮,也沒有發(fā)現(xiàn)實(shí)現(xiàn)的地方。后來看到 libnemo-extension 目錄殊轴,才想起來 nemo 的插件機(jī)制衰倦,它只提供接口,實(shí)際的實(shí)現(xiàn)是插件去做的旁理。我們系統(tǒng)中文件管理器的插件只有兩個(gè):文件共享和文件壓縮樊零。
通過卸載 nemo-share 和 nfs-file-compress,確定問題出在 nfs-file-compress孽文。
[補(bǔ)充說明:get_file_items 的功能就是當(dāng)你想開發(fā)一個(gè)插件驻襟,加到文件的右鍵菜單中,為了能操作你右鍵的那幾個(gè)文件芋哭,需要實(shí)現(xiàn)文件管理器的接口來拿到文件信息沉衣,也就是這個(gè) get_file_items。]
用戶右鍵某個(gè)文件减牺,想要壓縮這個(gè)文件豌习,就需要拿到文件的信息,名稱拔疚、類型斑鸦、大小等。分析文件壓縮的代碼草雕,在它實(shí)現(xiàn) >get_file_items 接口中巷屿,./nemo/nemo-filecompress.c:1149:
iface->get_file_items = nemo_fr_get_file_items;
在 nemo_fr_get_file_items 中看到的非常眼熟的打印信息
printf("...dir...name....%s\n",file_name_changed);
吐槽一下:這個(gè) ...dir...name.... 簡直無處不在,每次選中一個(gè)文件墩虹,都會(huì)打印這條嘱巾,一直都想去掉這個(gè)打印信息憨琳,但是nemo 代碼中卻找不到,原來是在這里旬昭。
再仔細(xì)查看代碼篙螟,
if(g_file_get_path(nemo_file_info_get_location(file))==NULL){...}
連續(xù)兩個(gè)接口 g_file_get_path、nemo_file_info_get_location都是需要釋放內(nèi)存的地方问拘,就這么扔在那里遍略,還有
parent = nemo_file_info_get_parent_info (files->data);
parent = nemo_file_info_get_parent_info (file);
把這幾個(gè)需要釋放內(nèi)存的對(duì)象分別做 free 、unref 之后骤坐,問題就解決了绪杏。之所以會(huì)出現(xiàn)再也連接不上samba 的原因是該釋放的內(nèi)存沒有釋放,下一個(gè)加載 location uri 時(shí)纽绍,還是嘗試還是去加載已經(jīng)斷掉的那個(gè)地址對(duì)象蕾久,所以連不上,此問題充分說明了內(nèi)存管理的重要性拌夏。