摘要:作者鐘離,酷家樂客戶端負責人原文地址酷家樂客戶端下載地址文章背景在酷家樂客戶端在改版成功后,我們積累了許多的寶貴的經驗和最佳實踐。
背景作者:鐘離,酷家樂PC客戶端負責人
原文地址:https://webfe.kujiale.com/electron-ku-jia-le-ke-hu-duan-kai-fa-shi-jian-fen-xiang-jin-cheng-tong-xin/
酷家樂客戶端:下載地址 https://www.kujiale.com/activity/136
文章背景:在酷家樂客戶端在V12改版成功后,我們積累了許多的寶貴的經驗和最佳實踐。前端社區里關于Electron知識相對較少,因此希望將這些內容以系列文章的形式分享出來。
系列文章:【Electron】酷家樂客戶端開發實踐分享 — 入坑篇
【Electron】酷家樂客戶端開發實踐分享 — 軟件自動更新
【Electron】酷家樂客戶端開發實踐分享 — 瀏覽器啟動客戶端
【Electron】酷家樂客戶端開發實踐分享 — 進程通信
【Electron】酷家樂客戶端開發實踐分享 — 下載管理器
不定期更新...
打開酷家樂客戶端,可以在左下角的更多菜單中找到下載管理這個功能,今天我們就來看看在Electron中如何實現一個下載管理器。
如何觸發下載行為由于Electron渲染層是基于chromium的,觸發下載的邏輯和chromium是一致的,頁面中的a標簽或者js跳轉等等行為都可能觸發下載,具體視訪問的資源而定。什么樣的資源會觸發瀏覽器的下載行為呢?
response header中的Content-Disposition為attachment。參考MDN Content-Disposition
response header中的Content-Type,是瀏覽器無法直接打開的文件類型,例如application/octet-stream,此時取決于瀏覽器的具體實現了。例子: IE無法打開pdf文件,chrome可以直接打開pdf文件,因此pdf類型的url在chrome上可以直接打開,而在IE下會觸發下載行為。
在Electron中還有一種方法可以觸發下載: webContents.download。相當于直接調用chromium底層的下載邏輯,忽略headers中的那些判斷,直接下載。
上述兩種下載行為,都會觸發session的will-download事件,在這里可以獲取到關鍵的downloadItem對象
整體流程 設置文件路徑如果不做任何處理的話,觸發下載行為時Electron會彈出一個系統dialog,讓用戶來選擇文件存放的目錄。這個體驗并不好,因此我們首先需要把這個系統dialog去掉。使用downloadItem.savePath即可。
// Set the save path, making Electron not to prompt a save dialog. downloadItem.setSavePath("/tmp/save.pdf");
為文件設置默認下載路徑,就需要考慮文件名重復的情況,一般來說會使用文件名自增的邏輯,例如:test.jpg、test.jpg(1)這種格式。文件默認存放目錄,也是一個問題,我們統一使用app.getPath("downloads")作為文件下載目錄。為了用戶體驗,后續提供修改文件下載目錄功能即可。
// in main.js 主進程中 const { session } = require("electron"); session.defaultSession.on("will-download", async (event, item) => { const fileName = item.getFilename(); const url = item.getURL(); const startTime = item.getStartTime(); const initialState = item.getState(); const downloadPath = app.getPath("downloads"); let fileNum = 0; let savePath = path.join(downloadPath, fileName); // savePath基礎信息 const ext = path.extname(savePath); const name = path.basename(savePath, ext); const dir = path.dirname(savePath); // 文件名自增邏輯 while (fs.pathExistsSync(savePath)) { fileNum += 1; savePath = path.format({ dir, ext, name: `${name}(${fileNum})`, }); } // 設置下載目錄,阻止系統dialog的出現 item.setSavePath(savePath); // 通知渲染進程,有一個新的下載任務 win.webContents.send("new-download-item", { savePath, url, startTime, state: initialState, paused: item.isPaused(), totalBytes: item.getTotalBytes(), receivedBytes: item.getReceivedBytes(), }); // 下載任務更新 item.on("updated", (e, state) => { // eslint-disable-line win.webContents.send("download-item-updated", { startTime, state, totalBytes: item.getTotalBytes(), receivedBytes: item.getReceivedBytes(), paused: item.isPaused(), }); }); // 下載任務完成 item.on("done", (e, state) => { // eslint-disable-line win.webContents.send("download-item-done", { startTime, state, }); }); });
現在觸發下載行為,文件就已經會下載到Downloads目錄了,文件名帶有自增邏輯。同時,對下載窗口發送了關鍵事件,下載窗口可以根據這些事件和數據,創建、更新下載任務。
上述步驟在渲染進程使用remote實現會有問題,無法獲取到實時的下載數據。因此建議在主進程實現。下載記錄
下載功能需要緩存下載歷史在本地,下載歷史的數據比較多,因此我們使用nedb作為本地數據庫。
// 初始化 nedb 數據庫 const db = nedbStore({ filename, autoload: true }); ipcRenderer.on("new-download-item", (e, item) => { // 數據庫新增一條新紀錄 db.insert(item); // UI中新增一條下載任務 this.addItem(item); }) // 更新下載窗口的任務進度 ipcRenderer.on("download-item-updated", (e, item) => { this.updateItem(item) }) // 下載結束,更新數據 ipcRenderer.on("download-item-done", (e, item) => { // 更新數據庫 db.update(item); // 更新UI中下載任務狀態 this.updateItem(item); });
此時本地數據庫中的數據,是這樣的:
{"paused":false,"receivedBytes":0,"savePath":"/Users/ww/Downloads/酷家樂裝修網-保利金色佳苑-戶型圖.jpg","startTime":1560415098.731598,"state":"completed","totalBytes":236568,"url":"https://qhtbdoss.kujiale.com/fpimgnew/prod/3FO4EGX11S9S/op/LUBAVDQKN4BE6AABAAAAACY8.jpg?kpm=9V8.32a74ad82d44e7d0.3dba44f.1560415094020","_id":"6AorFZvpI0N8Yzw9"} {"paused":false,"receivedBytes":0,"savePath":"/Users/ww/Downloads/Kujiale-12.0.2-stable(1).dmg","startTime":1560415129.488072,"state":"progressing","totalBytes":80762523,"url":"https://qhstaticssl.kujiale.com/download/kjl-software12/Kujiale-12.0.2-stable.dmg?timestamp=1560415129351","_id":"YAeWIy2xoeWTw0Ht"} {"paused":false,"receivedBytes":0,"savePath":"/Users/ww/Downloads/酷家樂裝修網-保利金色佳苑-戶型圖(1).jpg","startTime":1560418413.240669,"state":"progressing","totalBytes":236568,"url":"https://qhtbdoss.kujiale.com/fpimgnew/prod/3FO4EGX11S9S/op/LUBBLFYKN4BE6AABAAAAADY8.jpg?kpm=9V8.32a74ad82d44e7d0.3dba44f.1560418409875","_id":"obFLotKillhzTw09"} {"paused":false,"receivedBytes":0,"savePath":"/Users/ww/Downloads/酷家樂裝修網-保利金色佳苑-戶型圖(1).jpg","startTime":1560418413.240669,"state":"completed","totalBytes":236568,"url":"https://qhtbdoss.kujiale.com/fpimgnew/prod/3FO4EGX11S9S/op/LUBBLFYKN4BE6AABAAAAADY8.jpg?kpm=9V8.32a74ad82d44e7d0.3dba44f.1560418409875","_id":"obFLotKillhzTw09"}
在渲染進程初始化的時候,需要讀取下載記錄,數據按下載時間倒序。讀取數量需要做一下限制,否則會影響性能,暫時限制50條。
// 渲染進程中 const db = nedbStore({ filename, autoload: true }); // 讀取歷史數據 const downloadHistory = await db.cfind({}).sort({ startTime: -1, }).limit(50).exec() .catch(err => logger.error(err)); if (downloadHistory) { this.setList(downloadHistory.map((d) => { const item = d; // 歷史記錄中,只有需要未完成和完成兩個狀態 if (item.state !== "completed") { item.state = "cancelled"; } return item; })); }自定義下載目錄
默認下載目錄在Electron默認為本機上的Downloads目錄,提供用戶設置下載目錄的功能,就需要在本地緩存用戶自定義的下載目錄。這種基礎配置我們使用electron-store來實現
// in config.json { "downloadsPath": "/Users/ww/Downloads/歸檔" }
在窗口初始化的時候,檢查緩存中是否有自定義下載目錄,如果有則更改app的默認下載目錄
componentDidMount() { const downloadsPath = store.get("downloadsPath"); if (downloadsPath) { app.setPath("downloads", downloadsPath); // app.getPath("downloads"); -> /Users/ww/Downloads/歸檔 } }
用戶點擊更換下載目錄,此時需要以下步驟:
彈出文件目錄選擇dialog,使用dialog.showOpenDialog實現
更新本地緩存中的自定義下載目錄
修改當前app的默認下載目錄
更新下載窗口中的下載目錄文案
// 用戶點擊更改下載目錄的回調 changeDoiwnloadHandler = () => { const paths = dialog.showOpenDialog({ title: "選擇文件存放目錄", properties: ["openDirectory"], }); if (paths && paths.length) { // 先更新一下本地緩存 store.set("downloadsPath", paths[0]); // 更新當前的下載目錄 app.setPath("downloads", paths[0]); // 更新下載目錄文案 this.updateDownloadsPath(); } }計算下載進度
拿到downloadItem之后,可以獲取到已下載的字節數和文件的總字節數,以此來計算下載進度。
const percent = item.getReceivedBytes() / item.getTotalBytes();操作文件
在下載管理窗口中,雙擊下載任務可以打開該文件,點擊查看按鈕可以打開文件所在目錄。我們統一使用Electron的shell模塊來實現。
openFile = (path) => { if (!fs.pathExistsSync) return; // 文件不存在的情況 shell.openItem(path); // 打開文件 } openFileFolder = async (path) => { if (!fs.pathExistsSync(path)) { // 文件不存在 return; } shell.showItemInFolder(path); // 打開文件所在文件夾 }獲取文件關聯圖標
仔細觀察下載管理窗口我們可以發現,文件的圖標都是從系統獲取的,和我們在文件管理器中看到的文件圖標一致。
上圖中dmg、jpg文件都展示了系統關聯的文件圖標,用戶體驗很好。我們可以使用getFileIcon來獲取系統圖標,以下是具體實現代碼。
const { app } = require("electron").remote; // 封裝一個函數 const getFileIcon = (path) => { return new Promise((resolve) => { const defaultIcon = "some-default.jpg"; if (!path) return resolve(defaultIcon); return app.getFileIcon(path, (err, nativeImage) => { if (err) { return resolve(defaultIcon); } return resolve(nativeImage.toDataURL()); // 使用base64展示圖標 }); }); }; // 獲取圖標 const imgSrc = await getFileIcon("./test.jpg");最后
歡迎大家在評論區討論,技術交流 & 內推 -> zhongli@qunhemail.com
文章版權歸作者所有,未經允許請勿轉載,若此文章存在違規行為,您可以聯系管理員刪除。
轉載請注明本文地址:http://specialneedsforspecialkids.com/yun/54036.html
摘要:作者鐘離,酷家樂客戶端負責人原文地址酷家樂客戶端下載地址文章背景在酷家樂客戶端在改版成功后,我們積累了許多的寶貴的經驗和最佳實踐。 作者:鐘離,酷家樂PC客戶端負責人原文地址:https://webfe.kujiale.com/electron-ku-jia-le-ke-hu-duan-kai-fa-shi-jian-fen-xiang-jin-cheng-tong-xin/酷家樂客...
摘要:作者鐘離,酷家樂客戶端負責人原文地址酷家樂客戶端下載地址文章背景在酷家樂客戶端在改版成功后,我們積累了許多的寶貴的經驗和最佳實踐。用戶在電腦上安裝客戶端,實際上會將客戶端代碼文件持久儲存到本機。通常我們會在軟件啟動時檢查更新。 作者:鐘離,酷家樂PC客戶端負責人原文地址:https://webfe.kujiale.com/electron-autoupdate/酷家樂客戶端:下載地址...
摘要:作者鐘離,酷家樂客戶端負責人原文地址酷家樂客戶端下載地址文章背景在酷家樂客戶端在改版成功后,我們積累了許多的寶貴的經驗和最佳實踐。用戶在電腦上安裝客戶端,實際上會將客戶端代碼文件持久儲存到本機。通常我們會在軟件啟動時檢查更新。 作者:鐘離,酷家樂PC客戶端負責人原文地址:https://webfe.kujiale.com/electron-autoupdate/酷家樂客戶端:下載地址...
摘要:作者鐘離,酷家樂客戶端負責人原文地址酷家樂客戶端下載地址文章背景在酷家樂客戶端在改版成功后,我們積累了許多的寶貴的經驗和最佳實踐。鐘離可以注冊多個協議接收參數協議注冊完畢之后,我們已經可以在瀏覽器中,通過訪問自定義協議來啟動客戶端了。 作者:鐘離,酷家樂PC客戶端負責人原文地址:https://webfe.kujiale.com/browser-to-client/酷家樂客戶端:下載...
閱讀 2632·2021-11-18 10:07
閱讀 1086·2021-08-03 14:04
閱讀 730·2019-08-30 13:08
閱讀 2583·2019-08-29 15:33
閱讀 1096·2019-08-29 14:07
閱讀 2992·2019-08-29 14:04
閱讀 1443·2019-08-29 11:19
閱讀 1150·2019-08-29 10:59