Author: long. Woo

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

There are some limitations to web application file downloads, usually by having the back end change the header of the response to Content-Disposition: attachment; Filename =xxx. PDF triggers the browser to download the file.

The download in ELECTRON triggers the sessionwill-downloadEvents. This is available in the eventdownloadItemObject, throughdownloadItemObject implements a simple file download manager:

1. How do I trigger the download

Since electron is implemented based on Chromium, invoking the downloadURL method of webContents is equivalent to invoking the download of the underlying implementation of Chromium. The response header information will be ignored and the will-download event will be triggered.

// Trigger the download
win.webContents.downloadURL(url)

/ / to monitor will - download
session.defaultSession.on('will-download'.(event, item, webContents) = > {})
Copy the code

2. Download process

3. Functional design

Implementing a simple file download manager includes:

  • Setting the Save Path
  • Pause/resume and cancel
  • Download progress
  • Download speed
  • The download is complete
  • Open the file and open the file location
  • File icon
  • Download the record

3.1 Setting the Save Path

If the save path is not set, the electron will automatically pop up the system save dialog box. Instead of using the system’s save dialog, you can use the setSavePath method to overwrite the download if there is a file with the same name.

item.setSavePath(path)
Copy the code

For better user experience, users can choose their own save location operation. When the location input box is clicked, the renderer communicates with the main process via IPC, opening the system file selection dialog.Main process implementation code:

/** * Open the file selection box *@param oldPath- Last open 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))
Copy the code

Renderer code:

const path = await ipcRenderer.invoke('openFileDialog'.'PATH')
Copy the code

3.2 Pause/Resume and cancel

Once you get the downloadItem, pause, resume, and cancel calls to the Pause, Resume, and Cancel methods, respectively. When we want to delete an item that is being downloaded from the list, we need to call the cancel method to cancel the download.

3.3 Download Progress

Listen for the updated event in DownloadItem to obtain the downloaded bytes in real time to calculate the download progress and download speed per second.

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

When downloading, you want to display the download information in the Mac dock and the Windows taskbar, for example:

  • Number of downloads: Set by app’s badgeCount attribute. When it is 0, it will not be displayed. It can also be set by the Dock setBadge method, which supports strings. If not displayed, it needs to be set to “”.
  • Download progress: Set using the setProgressBar method of the window.

Note The number of downloads takes effect only on the Mac operating system because of the difference between Mac operating systems and Windows operating systems. Add process.platform === ‘Darwin’ condition to avoid exception errors on non-Mac and Linux systems.

Download progress (Windows system taskbar, Mac system program dock)

// MAC app dock displays the number of downloads:
/ / way
app.badgeCount = 1
2 / / way
app.dock.setBadge('1')

// MAC program dock, Windows taskbar display progress
win.setProgressBar(progress)
Copy the code

3.4 Download Speed

Since downloadItem doesn’t directly provide us with methods or attributes to get download speeds, we need to implement it ourselves.

The updated event uses the getReceivedBytes method to get the last downloaded bytes minus the current downloaded bytes.

// Records the last downloaded bytes of 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
})
Copy the code

Note that updated events are executed about 500ms at a time.

3.5 Download Complete

When a file is downloaded, interrupted, or cancelled, the renderer needs to be notified to modify the status by listening for the downloadItem done event.

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

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

3.6 Open the File and the File Location

Electron’s shell module is used to open the file (openPath) and open the file location (showItemInFolder).

The openPath method supports the return value Promise

. If open files are not supported, the system prompts. The showItemInFolder method returns a void value. If you need a better user experience, you can use the FS module of NodeJS to check whether the file exists.

import fs from 'fs'

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

  shell.openPath(path)
  return true
}

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

  shell.showItemInFolder(path)
  return true
}
Copy the code

3.7 File ICONS

It is very convenient to use the getFileIcon method of app module to obtain the file icon associated with the system, which returns the Promise

type. We can use toDataURL method to convert it into Base64. We don’t have to deal with displaying different ICONS for different file types.

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()
}
Copy the code

3.8 Download History

As more and more historical data is downloaded, use the electron store to save the download records locally.

In order to learn electron better, we have created a series at present, if you are interested, you can have a look

  • The Electron playground series
  • [Electron playground series] Menu
  • 【Electron Playground series 】Dialog with file selection
  • [Electron playground series] Agreement
  • 【Electron Playground series 】 The tray
  • How long does it take to write a screen recording tool?

For more complete documentation, please refer to the documentation below

Electron-Playground official document

Github address portal: github.com/tal-tech/el…