这一个章节会用到我写的一个插件,electron-auto-setting设置与信号中断 - 图1 当我们传递配置项,它可以自动生成页面。

它的原理其实就是从配置项里面读取到表单的选项,构造出表单,然后当修改的时候,通过统一的 IPC 信道,将修改的 key 和 value 发送回去。

安装依赖

  1. npm install electron-auto-setting --save

新建定义文件

  1. declare module 'electron-auto-setting' {
  2. const all: any
  3. const init: any
  4. const store: any
  5. export { init, store }
  6. export default all
  7. }

store 的方法全部来自于 electron-store设置与信号中断 - 图2

实现逻辑

新建 tray.ts 文件,将配置项传递给 init,而 create 则是创建窗口的函数,第一个是参数配置项,第二个参数是是否开启 DEBUG 模式,这里我们开启它。

  1. import create, { init, store } from 'electron-auto-setting'
  2. import { app, Tray, Menu } from 'electron'
  3. import { resolve } from 'path'
  4. import { fromEvent } from 'rxjs'
  5. let win = null
  6. let tray = null
  7. let setting = [
  8. {
  9. icon: 'icon-setting',
  10. label: '普通设置',
  11. configs: {
  12. SAVA_PATH: {
  13. type: 'path',
  14. label: ' 输出路径',
  15. defaultValue: resolve(app.getPath('home'), 'xiaoshuo')
  16. },
  17. CONCURRENCE: {
  18. type: 'string',
  19. label: '下载并发量',
  20. defaultValue: 5
  21. }
  22. }
  23. },
  24. {
  25. icon: 'icon-download',
  26. label: 'API 设置',
  27. configs: {
  28. APP_ID: {
  29. type: 'string',
  30. label: '应用 ID',
  31. defaultValue: ''
  32. },
  33. API_KEY: {
  34. type: 'string',
  35. label: '应用秘钥',
  36. defaultValue: ''
  37. },
  38. SECRET_KEY: {
  39. type: 'string',
  40. label: '加密秘钥',
  41. defaultValue: ''
  42. }
  43. }
  44. }
  45. ]
  46. init(setting)
  47. function opensetting() {
  48. win = create({}, true)
  49. }
  50. fromEvent(<any>app, 'ready').subscribe(() => {
  51. tray = new Tray(resolve(__dirname, 'tray_w24h24.png'))
  52. const contextMenu = Menu.buildFromTemplate([
  53. { label: '设置', click: opensetting }
  54. ])
  55. tray.setContextMenu(contextMenu)
  56. })
  57. export { store, tray, win }

app ready 的时候,我们创建一个通知栏的小图标,这个小图标是 24x24 大小的 png 图片,我是从 http://www.iconfont.cn/ 下载的,大家可以自行下载。

然后设置通知栏菜单,通过 setContextMenu 进行初始化,这是 Electron API 提供的方法。

读取配置

在 index.ts 中 ,记得使用了 await ,一定要给函数加上 async 哦。此时可以把 on 函数提取到 helper 里面去,具体请看仓库源码。

  1. import { store } from './tray'
  2. const { folderName, url } = args
  3. const savePath = resolve(
  4. store.get('SAVA_PATH', app.getPath('music')),
  5. folderName
  6. )
  7. await download(url, plugin, { path: savePath })
  8. await transform(savePath)

修改 download.ts ,让默认的配置项可以读取到并发量。

  1. opts = Object.assign(
  2. {},
  3. {
  4. concurrence: store.get('CONCURRENCE', 5)
  5. },
  6. defaultOptions,
  7. crawl.opts,
  8. opts
  9. )

修改 TTS.ts ,我们不希望每次设置完要重启,这里我们订阅一下更新。

  1. let APP_ID = store.get('APP_ID')
  2. let API_KEY = store.get('API_KEY')
  3. let SECRET_KEY = store.get('SECRET_KEY')
  4. // 每次修改更新值
  5. store.onDidChange('APP_ID', (newValue: any) => (APP_ID = newValue))
  6. store.onDidChange('API_KEY', (newValue: any) => (API_KEY = newValue))
  7. store.onDidChange('SECRET_KEY', (newValue: any) => (SECRET_KEY = newValue))
  8. let client: any = null
  9. try {
  10. client = new speech(APP_ID, API_KEY, SECRET_KEY)
  11. } catch (e) {
  12. log({ type: 'audio', step: 'new_speech_error', message: e.message })
  13. }

前端界面

新建 Download.svelte , 然后像之前一样,在 App.svlete 里面引入并设置值。

  1. <input bind:value=folderName />
  2. <input bind:value=url />
  3. <button on:click="download()">下载</button>
  4. <button on:click="stop()">中断</button>
  5. <script>
  6. import {
  7. ipcRenderer
  8. } from 'electron'
  9. export default {
  10. data() {
  11. return {
  12. url: '',
  13. folderName: ''
  14. }
  15. },
  16. methods: {
  17. download() {
  18. const {
  19. url,
  20. folderName
  21. } = this.get()
  22. if (url.length && folderName.length) {
  23. console.log(url);
  24. console.log(folderName);
  25. ipcRenderer.send('download', {
  26. url,
  27. folderName
  28. })
  29. }
  30. },
  31. stop() {
  32. ipcRenderer.send('stop') // 发送停止事件信号
  33. }
  34. }
  35. }
  36. </script>

此时我们是没有办法对正在下载的进行中断想要实现中断,必须要有一个信号量来控制跳出循环,我们可以把这个信号量放到 store 里面去,只要我们不配置 setting 的配置项,其实他是不会显示出来的。

中断处理函数

修改 helper.ts,此时你的 helper 应该会像下面这样,中断了的时候,我们应该把下载的给删除,这样可以保证目录的干净。

  1. import { Subject } from 'rxjs'
  2. import { ipcMain as ipc } from 'electron'
  3. import { store } from './tray'
  4. import { remove } from 'fs-extra'
  5. import log from './statusLog'
  6. function ready() {
  7. let resolveFN, rejectFN
  8. let promise = new Promise(
  9. (resolve, reject) => ([resolveFN, rejectFN] = [resolve, reject])
  10. )
  11. return [resolveFN, rejectFN, promise]
  12. }
  13. interface CombineEvent {
  14. event: any
  15. args: any
  16. }
  17. function on(channel: string): Subject<CombineEvent> {
  18. const eventListner = new Subject<CombineEvent>()
  19. ipc.on(channel, (event: any, args: any) => eventListner.next({ event, args }))
  20. return eventListner
  21. }
  22. async function handleStop(path: string, logInfo: any) {
  23. if (store.get('stop')) {
  24. path && (await remove(path)) // 删除目录
  25. log(logInfo)
  26. return true
  27. }
  28. return false
  29. }
  30. export { ready, on, handleStop }

修改 download.ts 在循环里面添加跳出代码,并发送消息显示给前端

  1. if (
  2. await handleStop(resolve(path, 'text'), {
  3. type: 'stop',
  4. message: '已停止该队列'
  5. })
  6. ) {
  7. // 每次循环都要判断是否有停止信号量。
  8. break
  9. }

同样修改 TTS.ts ,添加跳出代码

  1. if (
  2. await handleStop(chapterSavePath, {
  3. type: 'stop',
  4. message: '已停止该队列'
  5. })
  6. ) {
  7. break
  8. }

那么在开始与结束的时候应该添加重置信号量的代码

  1. store.set('stop', false)
  2. await download(url, plugin, { path: savePath })
  3. await transform(savePath)
  4. store.set('stop', false)

以及设置信号量为停止的代码

  1. on('stop').subscribe(() => {
  2. store.set('stop', true)
  3. })