Electron实现文件下载并展示下载进度

实现思路

1.在主进程中定义一个全局变量 downloadItems 用于管理所有的下载任务项
2.主进程在接收到下载文件任务的时候,将下载任务添加进入 downloadItems,通过win.webContents.send(‘watch-download-file-state’)把下载过程进度 推送给渲染进程
3.主进程监听渲染进程发送过来的事件,根据下载任务项downloadItems 来获取某个文件下载实例,进行相应的处理。
4.通过预加载脚本向渲染器进程暴露一个全局的 window.electronAPI 变量

downloadFile: (windowName,url,fileName,fingerPrint) => ipcRenderer.send('download-file', windowName,url,fileName,fingerPrint),//下载文件
watchDownloadFileState: (callback) => ipcRenderer.on('watch-download-file-state', callback),//推送给渲染进程 下载过程中的状态
setDownloadFileState: (state,fingerPrint,url) => ipcRenderer.send('set-download-file-state',state,fingerPrint,url),//接收渲染进程 设置下载状态(暂停、恢复、取消) 的通知
getDownloadFileState: (fingerPrint,url,returnType) => ipcRenderer.invoke('get-download-file-state',fingerPrint,url,returnType),//接收渲染进程 获取下载状态 的通知 返回下载状态字符串
getDownloadFileSavepath: (fingerPrint) => ipcRenderer.invoke('get-download-file-savepath',fingerPrint),//接收渲染进程 获取某个下载文件的下载存放路径 的通知 返回下载存放路径
openDir: (path,isOpenFile) => ipcRenderer.invoke('open-dir',path,isOpenFile),//从渲染进程中打开目录,并返回结果
fsExists: (path) => ipcRenderer.invoke('fs-exists',path),//从渲染进程中通知主进程判断一下文件路径是否存在,主进程返回结果

5.渲染进程下载文件逻辑
Electron实现文件下载并展示下载进度_第1张图片

主进程

const { app, BrowserWindow, ipcMain, shell} = require('electron');
const fs = require('fs');
const path = require('path');
//所有窗体
let windows = {
	mainWindow: null, //主窗口
}

//统一管理下载项数组
let downloadItems = [];

/**
 * 下载文件
 * @param {String} windowName 文件下载的窗体
 * @param {String} url 文件下载路径
 * @param {String} fileName  文件名称,包含后缀名(例如:图1.png)
 * @param {String} fingerPrint  文件指纹,唯一标识
 */
function downloadFile(windowName,url,fileName,fingerPrint){

	const currWindow = windows[windowName];
	//根据fileName 拼接 文件下载存放路径
	const filePath = path.join(app.getPath('appData'),`/File`,`${fileName}`)

	//1- 下载文件
	currWindow.webContents.downloadURL(url);
	//2- 准备下载的时候触发
	currWindow.webContents.session.once('will-download',(event,item,webContents) => {
		//(1) 设置下载项的保存文件路径(我这里将下载地址放在了程序根目录下的download文件夹下)
		item.setSavePath(filePath);
		
		downloadItems.push({//存储下载项
			fingerPrint,
			item,
			windowName,
		})

		//(2) 监听updated事件,当下载正在执行时,把下载进度发给渲染进程进行展示
		item.on('updated',(event,state) => {
			switch(state){
				case 'interrupted'://下载中断
					currWindow.webContents.send('watch-download-file-state',state,{//推送给渲染进程 下载过程中 的状态
						fingerPrint:fingerPrint,
					})
					break;
				case 'progressing'://下载停止
					if (item.isPaused()) { // 下载停止
						currWindow.webContents.send('watch-download-file-state','pause',{//推送给渲染进程 下载过程中 的状态
							fingerPrint:fingerPrint,
						})
				    } else if(item.getReceivedBytes() && item.getTotalBytes()) {//下载中
						currWindow.webContents.send('watch-download-file-state',state,{//推送给渲染进程 下载过程中 的状态
							saveFilePath: filePath,
							loaded: item.getReceivedBytes(),
							total: item.getTotalBytes(),
							fingerPrint:fingerPrint,
						})

					}

					break;
			}
		})
		//(3) 监听done事件,在下载完成时打开文件。
		item.once('done',(e,state) => {
			switch(state){
				case 'completed'://下载完成
					shell.openPath(filePath);//打开文件
					currWindow.webContents.send('watch-download-file-state',state,{//推送给渲染进程 下载过程中 的状态
						fingerPrint:fingerPrint,
					})
					break;
				case 'interrupted'://下载中断
					currWindow.webContents.send('watch-download-file-state',state,{//推送给渲染进程 下载过程中 的状态
						fingerPrint:fingerPrint,
					})
					break;
				case 'cancelle'://下载取消
					for(let i = 0; i < downloadItems.length; i++){
						if(fingerPrint == downloadItems[i].fingerPrint && downloadItems[i].item.getURL() === url){
							downloadItems.splice(i,1);
							break;
						}
					}
					currWindow.webContents.send('watch-download-file-state',state,{//推送给渲染进程 下载过程中 的状态
						fingerPrint:fingerPrint,
					})
					break;
			}
		})
	})
}

/**接收渲染进程 下载文件 的通知
 * @param {String} windowName 文件下载的窗体
 * @param {String} url 文件下载路径
 * @param {String} fileName  文件名称,包含后缀名(例如:图1.png)
 * @param {String} fingerPrint  文件指纹,唯一标识
 */
ipcMain.on('download-file', function(event, windowName, url, fileName, fingerPrint) {
    downloadFile(windowName, url, fileName, fingerPrint);
});

/**接收渲染进程 获取下载状态 的通知
 * @param {String} fingerPrint 文件指纹(因为同一个文件即使不同名,下载路径也是一样的,所以需要消息指纹识别)
 * @param {String} url 文件下载路径
 * @param {String (str|obj)} returnType 返回的类型, 值为str是仅仅返回一个状态,值为obj返回一个对象
 */
ipcMain.handle('get-download-file-state', function(event, fingerPrint, url, returnType = 'str') {
    let state = null;
    for (let i = 0; i < downloadItems.length; i++) {
        if (downloadItems[i].fingerPrint === fingerPrint) {
            const item = downloadItems[i].item;
            if (url === item.getURL()) {
                state = item.getState();//返回 string - 当前状态。 可以是 progressing、 completed、 cancelled 或 interrupted。
                if (state === 'progressing') {
                    if (item.isPaused()) {
                        state = 'interrupted';
                    }
                }
                return returnType === 'str' ? state : { state: state, windowName: downloadItems[i].windowName };
                break;
            }
        }
    }
    return returnType === 'str' ? state : { state: state, windowName: '' };
});

/**接收渲染进程 设置下载状态(暂停、恢复、取消) 的通知
 * @param {String} state 需要暂停下载的路径(pause:暂停下载,stop:取消下载,resume:恢复下载)
 * @param {String} fingerPrint 需要暂停下载的文件指纹(因为同一个文件即使不同名,下载路径也是一样的,所以需要消息指纹识别)
 * @param {String} url 需要暂停下载的路径
 *
 */
ipcMain.on('set-download-file-state', function(event, state, fingerPrint, url) {
    for (let i = 0; i < downloadItems.length; i++) {
        if (downloadItems[i].fingerPrint === fingerPrint) {
            const item = downloadItems[i].item;
            if (url === item.getURL()) {
                switch (state) {
                    case 'pause':
                        item.pause();//暂停下载
                        break;
                    case 'cancel':
                        item.cancel();//取消下载
                        downloadItems.splice(i, 1);
                        break;
                    case 'resume':
                        if (item.canResume()) {
                            item.resume();
                        }
                        break;
                }
            }
        }
    }
});

/**接收渲染进程 获取某个下载文件的下载存放路径 的通知 返回下载存放路径
 * @param {String} fingerPrint 文件指纹
 */
ipcMain.handle('get-download-file-savepath', function(event, fingerPrint) {
    let state = null;
    for (let i = 0; i < downloadItems.length; i++) {
        if (downloadItems[i].fingerPrint === fingerPrint) {
            const item = downloadItems[i].item;
            return item.getSavePath();
            break;
        }
    }
    return state;
});

/**接收渲染进程 打开指定路径的本地文件 的通知 并返回结果(路径存在能打开就返回true,路径不存在无法打开就返回false)
 * @param {Object} event
 * @param {String} path
 */
ipcMain.handle('open-dir', function(event, path, isOpenFile) {
    if (!path) return false;
    if (isOpenFile) {
         shell.openPath(filePath);//打开文件
        return true;
    } else {
        const checkPath = fs.existsSync(path);//以同步的方法检测文件路径是否存在。
        if (checkPath) {//文件存在直接打开所在目录
            shell.showItemInFolder(path);
            return true;
        } else { 
            return false;
        }
    }

});

/**
 * 接收渲染进程 判断文件路径是否存在 的通知 返回是否存在的布尔值结果
 */
ipcMain.handle('fs-exists', function(event, path) {
    if (!path) return false;
    const checkPath = fs.existsSync(path);//以同步的方法检测文件路径是否存在。
    return checkPath;
});

预加载脚本

//向渲染器进程暴露一个全局的 window.electronAPI 变量。
const { contextBridge, ipcRenderer, app } = require('electron');
contextBridge.exposeInMainWorld('electronAPI', {
	downloadFile: (windowName,url,fileName,fingerPrint) => ipcRenderer.send('download-file', userId,windowName,url,fileName,fingerPrint),//下载文件
	watchDownloadFileState: (callback) => ipcRenderer.on('watch-download-file-state', callback),//推送给渲染进程 下载过程中的状态
	getDownloadFileState: (fingerPrint,url,returnType) => ipcRenderer.invoke('get-download-file-state',fingerPrint,url,returnType),//接收渲染进程 获取下载状态 的通知 返回下载状态字符串
	setDownloadFileState: (state,fingerPrint,url) => ipcRenderer.send('set-download-file-state',state,fingerPrint,url),//接收渲染进程 设置下载状态(暂停、恢复、取消) 的通知
	getDownloadFileSavepath: (fingerPrint) => ipcRenderer.invoke('get-download-file-savepath',fingerPrint),//接收渲染进程 获取某个下载文件的下载存放路径 的通知 返回下载存放路径
	openDir: (path,isOpenFile) => ipcRenderer.invoke('open-dir',path,isOpenFile),//从渲染进程中打开目录,并返回结果
	fsExists: (path) => ipcRenderer.invoke('fs-exists',path),//从渲染进程中通知主进程判断一下文件路径是否存在,主进程返回结果
})

渲染进程



你可能感兴趣的:(electron,javascript,前端)