[electron playground series] file download

Time:2021-4-28

Author:long.woo

File download is a common business requirement in our development, such as exporting excel.

There are some limitations in downloading web application files. Usually, the backend changes the response header toContent-Disposition: attachment; filename=xxx.pdfTo trigger the download behavior of the browser.

The download behavior in electron will trigger the start of sessionwill-downloadevent. You can get thedownloadItemObject, viadownloadItemObject to implement a simple file download manager
demo.gif

1. How to trigger Download

Because electron is based on chromium, by calling thedownloadURLMethod, which is equivalent to calling the download of chromium underlying implementation, will ignore the response header information and triggerwill-downloadevent.

//Trigger Download
win.webContents.downloadURL(url)

//Monitoring will download
session.defaultSession.on('will-download', (event, item, webContents) => {})

2. Download process

flow_chart.png

3. Function design

Implement a simple file download manager, including the following functions:

  • Set save path
  • Pause / resume and cancel
  • Download progress
  • Download speed
  • Download complete
  • Open the file and where to open it
  • File Icon
  • Download record

3.1 setting save path

If the save path is not set, the save dialog box will pop up automatically. If you do not want to use the save dialog box of the system, you can use thesetSavePathMethod, when there are duplicate files, the download will be directly overwritten.

item.setSavePath(path)

In order to improve the user experience, users can choose their own save location. When you click the position input box, the rendering process communicates with the main process through IPC to open the system file selection dialog box.
select_path.gif
Main process implementation code:

/**
 *Open the file selection box
 *@ param oldpath - last opened path
 */
const openFileDialog = async (oldPath: string = app.getPath('downloads')) => {
  if (!win) return oldPath

  const { canceled, filePaths } = await dialog.showOpenDialog(win, {
    Title: 'select Save Location',
    properties: ['openDirectory', 'createDirectory'],
    defaultPath: oldPath,
  })

  return !canceled ? filePaths[0] : oldPath
}

ipcMain.handle('openFileDialog', (event, oldPath?: string) => openFileDialog(oldPath))

Render process code:

const path = await ipcRenderer.invoke('openFileDialog', 'PATH')

3.2 suspension / resumption and cancellation

Get itdownloadItemAfter that, pause, resume and cancel are called respectivelypauseresumeandcancelmethod. When we want to delete the downloaded items in the list, we need to call the cancel method to cancel the download.

3.3 download progress

By monitoring the updated event in the downloaditem, the downloaded byte data can be obtained in real time to calculate the download progress and download speed per second.

//Calculate download progress
const progress = item.getReceivedBytes() / item.getTotalBytes()

download_progress.png
When downloading, you want to display the download information in the dock of the MAC system and the taskbar of the windows system, such as:

  • Downloads: via appbadgeCountProperty, when it is 0, it will not be displayed. You can also use thesetBadgeMethod setting, which supports string. If it is not displayed, it needs to be set to ”.
  • Download progress: through thesetProgressBarMethod settings.

Due to the differences between MAC and windows systems, the number of downloads only takes effect in MAC systems. Add the process. Platform =’darwin ‘condition to avoid abnormal errors in non Mac and Linux systems.

Download progress (Windows system taskbar, MAC system dock) display effect:
windows_progress.png
mac_download_progress.png

//Mac dock shows the number of downloads:
//Mode one
app.badgeCount = 1
//Mode 2
app.dock.setBadge('1')

//Mac dock and windows taskbar display progress
win.setProgressBar(progress)

3.4 download speed

becausedownloadItemThere is no method or property provided for us to get the download speed, so we need to implement it by ourselves.

Idea: in the updated event, get the byte data downloaded this time minus the byte data downloaded last time by getreceivedbytes method.

//Record the last downloaded byte data
let prevReceivedBytes = 0

item.on('updated', (e, state) => {
  const receivedBytes = item.getReceivedBytes()
  //Calculate the download speed per second
  downloadItem.speed = receivedBytes - prevReceivedBytes
  prevReceivedBytes = receivedBytes
})

It should be noted that the updated event takes about 500ms to execute.

updated_event.png

3.5 download complete

When a file download is completed, interrupted or cancelled, you need to inform the rendering process to modify the statusdownloadItemThe done event of.

item.on('done', (e, state) => {
  downloadItem.state = state
  downloadItem.receivedBytes = item.getReceivedBytes()
  downloadItem.lastModifiedTime = item.getLastModifiedTime()

  //Notify the rendering process to update the download status
  webContents.send('downloadItemDone', downloadItem)
})

3.6 open file and location of open file

Use the shell module of electron to open the file (openpath) and the location of the file (showiteminfolder).

Because the openpath method supports return valuesPromiseWhen the open file is not supported, the system will give a corresponding prompt, and the return value of showiteminfolder method isvoid. If you need a better user experience, you can use the FS module of nodejs to check whether the file exists first.

import fs from 'fs'

//Open file
const openFile = (path: string): boolean => {
  if (!fs.existsSync(path)) return false

  shell.openPath(path)
  return true
}

//Open file所在位置
const openFileInFolder = (path: string): boolean => {
  if (!fs.existsSync(path)) return false

  shell.showItemInFolder(path)
  return true
}

3.7 File Icon

It’s very convenient to use the app modulegetFileIconMethod to get the file icon associated with the systemPromiseType, we can use todataurl method to convert to Base64, we don’t need to deal with different file types to display different icons.

const getFileIcon = async (path: string) => {
  const iconDefault = './icon_default.png'
  if (!path) Promise.resolve(iconDefault)

  const icon = await app.getFileIcon(path, {
    size: 'normal'
  })

  return icon.toDataURL()
}

3.8 download record

As more and more historical data is downloaded, the use ofelectron-storeSave the download record locally.


Interested in electron? Please pay attention to our open source projectsElectron PlaygroundTo get you started, electron.

Every Friday, we will select some interesting articles and news to share with you, to nuggets to pay attention to our workXiaoqian weekly


We are the front-end technical team of tal.
We will often share with you the latest and coolest technical knowledge of the industry.
WelcomeZhihuNuggetsSegmentfaultCSDNBrief bookOpen source ChinaBlog GardenFocus on us.

Recommended Today

Implementation example of go operation etcd

etcdIt is an open-source, distributed key value pair data storage system, which provides shared configuration, service registration and discovery. This paper mainly introduces the installation and use of etcd. Etcdetcd introduction etcdIt is an open source and highly available distributed key value storage system developed with go language, which can be used to configure sharing […]