import { ipcMain, shell } from 'electron'
|
import fs from 'fs'
|
import os from 'os'
|
import axios from 'axios'
|
import path from 'path'
|
import moment from 'moment'
|
import { uuid } from './toolClass'
|
|
const Store = require('electron-store')
|
const FileDownloadTasks = new Store('FileDownloadTasks')
|
const DownloadConfig = new Store('DownloadConfig')
|
const DOWNLOAD_SAVE_FOLDER = 'FileDownloadSaveFolder'
|
const DOWNLOAD_TASKS = 'FileDownloadTasks'
|
// 下载列表
|
let downloadTasks = []
|
|
// 默认路径
|
const homeDir = os.homedir()
|
let DOWNLOAD_PATH = ''
|
const FileDownloadSaveFolder = DownloadConfig.get(DOWNLOAD_SAVE_FOLDER)
|
if (FileDownloadSaveFolder) {
|
DOWNLOAD_PATH = FileDownloadSaveFolder
|
} else {
|
if (os.platform() === 'win32') {
|
// Windows 系统下的 "Downloads" 文件夹路径
|
DOWNLOAD_PATH = path.join(homeDir, 'Downloads')
|
} else {
|
// macOS 和 Linux 系统下的 "Downloads" 文件夹路径
|
DOWNLOAD_PATH = path.join(homeDir, 'Downloads')
|
}
|
}
|
|
let mainWindow: any = null
|
let request: any = null
|
|
let token = ''
|
|
export const init = (win, req) => {
|
mainWindow = win
|
request = req
|
loadTasks()
|
}
|
|
// 下载任务实例
|
class DownloadTask {
|
constructor(data) {
|
this.initData(data)
|
updateTask(this.data)
|
}
|
|
public data
|
|
// 初始化数据
|
initData(data) {
|
if (data.id) {
|
// id:唯一标识
|
// state:状态
|
// savePath:储存路径
|
// fileInfoMap:所有下载文件信息map
|
// name:任务名称
|
// size:任务大小
|
// offset:任务已完成偏移量
|
// msg:任务消息
|
// md5:下载文件md5
|
// taskDesc:任务描述,与md5二选一,任务描述会以文件夹形式下载并储存
|
// 例如:
|
// [
|
// {
|
// name: '文件夹1',
|
// type: 'folder',
|
// childer: [
|
// {
|
// name: '文件夹2',
|
// type: 'folder',
|
// childer: [
|
// {
|
// name: '文件1',
|
// type: 'file',
|
// md5: "a654f654das6f5465ds4f65sa4f"
|
// }
|
// ]
|
// },
|
// {
|
// name: '文件2',
|
// type: 'file',
|
// md5: "asdfsadf564s644544er4g65f4h"
|
// }
|
// ]
|
// }
|
// ]
|
|
this.data = { ...data }
|
} else {
|
this.data = {
|
id: uuid(8),
|
state: 'init',
|
fileState: 'init',
|
savePath: '',
|
fileInfoMap: {},
|
name: data.name ? data.name : data.taskDesc ? data.taskDesc[0].name : '文件',
|
size: 0,
|
offset: 0,
|
msg: '',
|
md5: data.md5 ? data.md5 : null,
|
taskDesc: data.taskDesc ? data.taskDesc : null,
|
oldTaskDesc: data.taskDesc ? JSON.stringify(data.taskDesc) : null,
|
createDate: moment().format('YYYY-MM-DD HH:mm:ss'),
|
completeDate: ''
|
}
|
}
|
}
|
|
async start() {
|
if (this.data.md5) {
|
// 单个下载
|
if (!this.data.fileInfoMap[this.data.md5]) {
|
// 没有文件信息时获取
|
this.data.state = 'getFileInfo'
|
const info: any = await this.getFileInfo(this.data.md5)
|
if (info) {
|
this.data.fileInfoMap[this.data.md5] = info
|
this.data.size = info.size
|
this.data.name = info.name + '.' + info.extension
|
}
|
}
|
if (this.data.name) {
|
if (fs.existsSync(path.join(DOWNLOAD_PATH, this.data.name))) {
|
// 文件已存在
|
const stats = fs.statSync(path.join(DOWNLOAD_PATH, this.data.name))
|
if (stats.size == this.data.size) {
|
// 文件已下载完成
|
mainWindow.webContents.send(
|
'showMessage',
|
JSON.stringify({
|
type: 'warning',
|
msg: '文件已下载完成,请在已完成或下载目录中查看。'
|
})
|
)
|
// 删除当前下载任务
|
this.data = false
|
return false
|
} else {
|
// 存在旧的文件未完成或正在下载的任务
|
const oldTask = downloadTasks.find(
|
(item) =>
|
item.md5 == this.data.md5 &&
|
item.id != this.data.id &&
|
(item.state == 'download' || item.state == 'pause')
|
)
|
if (oldTask) {
|
// 存在任务
|
if (oldTask.state == 'download') {
|
mainWindow.webContents.send(
|
'showMessage',
|
JSON.stringify({
|
type: 'warning',
|
msg: '已存在相同的下载任务'
|
})
|
)
|
// 删除当前下载任务
|
this.data = false
|
return false
|
}
|
if (oldTask.state == 'pause') {
|
const task = new DownloadTask(oldTask)
|
task.start()
|
mainWindow.webContents.send(
|
'showMessage',
|
JSON.stringify({
|
type: 'warning',
|
msg: '已存在相同的下载任务,已自动开始下载'
|
})
|
)
|
// 删除当前下载任务
|
this.data = false
|
return false
|
}
|
}
|
}
|
} else {
|
// 文件不存在
|
const oldTasks = downloadTasks.filter(
|
(item) =>
|
item.md5 == this.data.md5 &&
|
item.id != this.data.id &&
|
(item.state == 'download' || item.state == 'pause')
|
)
|
if (oldTasks.length) {
|
// 存在相同文件的任务,分析这些任务状态,并标记这些任务的文件已不存在
|
for (let i = 0; i < oldTasks.length; i++) {
|
const oldTask = oldTasks[i]
|
if (oldTask.state == 'pause') {
|
const task = new DownloadTask(oldTask)
|
task.start()
|
mainWindow.webContents.send(
|
'showMessage',
|
JSON.stringify({
|
type: 'warning',
|
msg: '已存在相同的下载任务,已自动开始下载'
|
})
|
)
|
// 删除当前下载任务
|
this.data = false
|
return false
|
}
|
if (oldTask.state == 'success') {
|
oldTask.fileState = 'fileMissing'
|
}
|
}
|
} else {
|
this.data.offset = 0
|
}
|
}
|
|
this.data.savePath = path.join(DOWNLOAD_PATH, this.data.name)
|
this.data.state = 'download'
|
this.data.fileState = 'download'
|
this.data.fileInfoMap[this.data.md5].downloadState = 'download'
|
updateTask(this.data)
|
this.download(this.data, (type) => {
|
this.data.state = type
|
this.data.fileInfoMap[this.data.md5].downloadState = type
|
if (type == 'success') {
|
mainWindow.webContents.send(
|
'showMessage',
|
JSON.stringify({
|
type: 'success',
|
msg: `${this.data.name}已下载完成,请在已完成或下载目录中查看。`
|
})
|
)
|
this.data.completeDate = moment().format('YYYY-MM-DD HH:mm:ss')
|
}
|
})
|
}
|
} else if (this.data.taskDesc && this.data.taskDesc.length) {
|
// 文件夹下载
|
this.data.state = 'download'
|
this.data.offset = 0
|
this.data.name = this.data.taskDesc[0].name
|
this.data.savePath = path.join(DOWNLOAD_PATH, this.data.name)
|
// 存在旧的文件未完成或正在下载的任务
|
const oldTask = downloadTasks.find(
|
(item) =>
|
item.taskDesc &&
|
item.oldTaskDesc == this.data.oldTaskDesc &&
|
item.id != this.data.id &&
|
(item.state == 'download' || item.state == 'pause')
|
)
|
if (oldTask) {
|
// 存在任务
|
if (oldTask.state == 'download') {
|
mainWindow.webContents.send(
|
'showMessage',
|
JSON.stringify({
|
type: 'warning',
|
msg: '已存在相同的下载任务'
|
})
|
)
|
// 删除当前下载任务
|
this.data = false
|
return false
|
}
|
if (oldTask.state == 'pause') {
|
const task = new DownloadTask(oldTask)
|
task.start()
|
mainWindow.webContents.send(
|
'showMessage',
|
JSON.stringify({
|
type: 'warning',
|
msg: '已存在相同的下载任务,已自动开始下载'
|
})
|
)
|
// 删除当前下载任务
|
this.data = false
|
return false
|
}
|
}
|
await this.handleFolderData(this.data.taskDesc, DOWNLOAD_PATH)
|
await this.handleFolderDownload(this.data.taskDesc, DOWNLOAD_PATH)
|
}
|
return false
|
}
|
|
// 先处理总体大小,不然会导致下载过程中判断为已完成
|
async handleFolderData(dataList, savePath) {
|
for (let i = 0; i < dataList.length; i++) {
|
const dataItem = dataList[i]
|
if (!dataItem.md5) {
|
// 文件夹
|
dataItem.type = 'folder'
|
// 判断文件夹是否存在,创建文件夹
|
const folderPath = path.join(savePath, dataItem.name)
|
if (!fs.existsSync(folderPath)) {
|
fs.mkdirSync(folderPath)
|
}
|
// 处理子
|
if (dataItem.childer && dataItem.childer.length) {
|
await this.handleFolderData(dataItem.childer, folderPath)
|
}
|
} else {
|
// 文件
|
dataItem.type = 'file'
|
if (!this.data.fileInfoMap[dataItem.md5]) {
|
// 没有文件信息时获取
|
dataItem.state = 'getFileInfo'
|
const info: any = await this.getFileInfo(dataItem.md5)
|
if (info) {
|
this.data.fileInfoMap[dataItem.md5] = info
|
dataItem.size = info.size
|
dataItem.name = info.name + '.' + info.extension
|
this.data.size += dataItem.size
|
}
|
}
|
if (dataItem.name) {
|
dataItem.offset = 0
|
dataItem.savePath = path.join(savePath, dataItem.name)
|
if (fs.existsSync(dataItem.savePath)) {
|
// 文件已存在
|
const stats = fs.statSync(dataItem.savePath)
|
if (stats.size == this.data.size) {
|
// 已完成
|
dataItem.state = 'success'
|
this.data.fileInfoMap[dataItem.md5].downloadState = 'success'
|
} else {
|
// 未完成
|
dataItem.state = 'download'
|
this.data.fileInfoMap[dataItem.md5].downloadState = 'download'
|
dataItem.offset = stats.size
|
this.data.offset += dataItem.offset
|
}
|
} else {
|
// 文件不存在
|
dataItem.state = 'download'
|
this.data.fileInfoMap[dataItem.md5].downloadState = 'download'
|
}
|
updateTask(this.data)
|
}
|
}
|
}
|
}
|
|
// 处理folder数据
|
async handleFolderDownload(dataList, savePath) {
|
for (let i = 0; i < dataList.length; i++) {
|
const dataItem = dataList[i]
|
if (!dataItem.md5) {
|
// 文件夹
|
if (dataItem.childer && dataItem.childer.length) {
|
await this.handleFolderDownload(dataItem.childer, path.join(savePath, dataItem.name))
|
}
|
} else {
|
// 文件
|
this.download(
|
dataItem,
|
(type) => {
|
this.data.fileInfoMap[dataItem.md5].downloadState = type
|
dataItem.state = type
|
if (type == 'success' && this.data.state != 'success') {
|
if (this.data.offset == this.data.size) {
|
this.data.state = type
|
this.data.completeDate = moment().format('YYYY-MM-DD HH:mm:ss')
|
mainWindow.webContents.send(
|
'showMessage',
|
JSON.stringify({
|
type: 'success',
|
msg: `${this.data.name}已下载完成,请在已完成或下载目录中查看。`
|
})
|
)
|
}
|
} else {
|
this.data.state = type
|
}
|
},
|
(length) => {
|
this.data.offset += length
|
}
|
)
|
}
|
}
|
}
|
|
pause() {
|
if (this.data.md5) {
|
// 暂停单个文件下载
|
if (this.data.cancelToken && this.data.cancelToken.cancel) this.data.cancelToken.cancel()
|
if (this.data.cancelPipe) this.data.cancelPipe()
|
this.data.status = 'pause'
|
if (this.data.fileInfoMap[this.data.md5])
|
this.data.fileInfoMap[this.data.md5].downloadState = 'pause'
|
updateTask(this.data)
|
} else {
|
// 暂停整本下载
|
this.pauseAll(this.data.taskDesc)
|
}
|
}
|
|
pauseAll(data) {
|
for (let i = 0; i < data.length; i++) {
|
const item = data[i]
|
if (item.md5) {
|
if (item.cancelToken && item.cancelToken.cancel) item.cancelToken.cancel()
|
if (item.cancelPipe) item.cancelPipe()
|
item.status = 'pause'
|
if (this.data.fileInfoMap[item.md5]) {
|
this.data.fileInfoMap[item.md5].downloadState = 'pause'
|
}
|
}
|
if (item.childer && item.childer.length) {
|
this.pauseAll(item.childer)
|
}
|
}
|
}
|
|
// 获取文件信息
|
async getFileInfo(md5) {
|
try {
|
const resp = await request({
|
url: `/file/FileUpload/GetFileInfo`,
|
method: 'post',
|
headers: {
|
Authorization: 'bearer ' + token
|
},
|
data: {
|
md5: md5
|
}
|
})
|
return resp.data
|
} catch (error) {
|
this.data.state = 'error'
|
updateTask(this.data)
|
return false
|
}
|
}
|
|
async download(fileItem, endCallback, ongoingCallback?) {
|
try {
|
if (fileItem.offset === 0) fs.unlink(fileItem.savePath, () => {})
|
fileItem.cancelToken = axios.CancelToken.source()
|
const resp = await request({
|
url: `http://182.92.203.7:5004/file/FileDownload/Download?md5=${fileItem.md5}&offset=${fileItem.offset}`,
|
method: 'get',
|
cancelToken: fileItem.cancelToken.token,
|
responseType: 'stream',
|
headers: {
|
Authorization: 'bearer ' + token
|
}
|
})
|
const writer = fs.createWriteStream(fileItem.savePath, {
|
start: fileItem.offset,
|
flags: 'a+',
|
autoClose: true
|
})
|
resp.pipe(writer)
|
fileItem.cancelPipe = () => {
|
resp.unpipe(writer)
|
writer.end('停止写入!')
|
}
|
resp.on('data', (data) => {
|
fileItem.offset += data.length
|
// 调用渲染进程
|
updateTask(this.data)
|
if (ongoingCallback) ongoingCallback(data.length)
|
})
|
|
resp.on('end', () => {
|
if (fileItem.offset == fileItem.size) {
|
endCallback('success')
|
} else {
|
// 完成后没有达到实际大小
|
endCallback('error')
|
}
|
})
|
resp.on('error', (err) => {
|
if (err.name == 'CanceledError') {
|
// 暂停,取消请求
|
endCallback('pause')
|
} else {
|
// 下载错误
|
endCallback('error')
|
}
|
updateTask(this.data)
|
})
|
resp.on('close', () => {
|
updateTask(this.data)
|
})
|
} catch (error) {
|
updateTask(this.data)
|
// 下载错误
|
endCallback('error')
|
}
|
}
|
}
|
|
// 本地缓存加载任务列表
|
const loadTasks = () => {
|
const txt = FileDownloadTasks.get(DOWNLOAD_TASKS, '[]')
|
try {
|
downloadTasks = JSON.parse(txt)
|
} catch (error) {
|
downloadTasks = []
|
}
|
for (let i = 0; i < downloadTasks.length; i++) {
|
const item = downloadTasks[i]
|
if (item.state == 'download') item.state = 'pause'
|
}
|
}
|
|
// 本地缓存记录任务列表
|
const saveTasks = () => {
|
FileDownloadTasks.set(DOWNLOAD_TASKS, JSON.stringify(downloadTasks))
|
}
|
|
const updateTask = (data?) => {
|
// 调用渲染进程
|
if (data) {
|
mainWindow.webContents.send('downloadTaskChange', JSON.stringify(data))
|
const index = downloadTasks.findIndex((item) => item.id == data.id)
|
downloadTasks[index] = data
|
} else {
|
mainWindow.webContents.send('downloadTaskChange')
|
}
|
saveTasks()
|
}
|
|
// 获取任务列表
|
ipcMain.on('getDownloadTasks', (ev, data) => {
|
// 页面会先获取任务列表,在获取列表时将token挂载
|
mainWindow.webContents
|
.executeJavaScript('localStorage.getItem("token");', true)
|
.then((result) => {
|
token = result
|
})
|
const returnData = JSON.parse(JSON.stringify(downloadTasks))
|
ev.returnValue = returnData.map((item: any) => {
|
if (item.md5) {
|
return {
|
...item,
|
cancelToken: '', // 去掉方法,通信避免报错
|
cancelPipe: '' // 去掉方法,通信避免报错
|
}
|
} else {
|
return {
|
...item,
|
taskDesc: handleTasksInfo(item.taskDesc)
|
}
|
}
|
})
|
})
|
|
const handleTasksInfo = (dataList) => {
|
for (let i = 0; i < dataList.length; i++) {
|
const item = dataList[i]
|
if (item.cancelToken) item.cancelToken = ''
|
if (item.cancelPipe) item.cancelPipe = ''
|
if (item.childer) {
|
item.childer = handleTasksInfo(item.childer)
|
}
|
}
|
return dataList
|
}
|
|
// 新建任务
|
ipcMain.on('newDownloadTask', async (ev, data) => {
|
// 判断下载目录是否存在
|
if (!fs.existsSync(DOWNLOAD_PATH)) {
|
// 未获取到默认下载目录
|
mainWindow.webContents.send(
|
'showMessage',
|
JSON.stringify({
|
type: 'notDownloadFolder',
|
msg: JSON.stringify(data)
|
})
|
)
|
} else {
|
mainWindow.webContents.send(
|
'showMessage',
|
JSON.stringify({
|
type: 'showState',
|
msg: '检测到下载任务,正在创建...'
|
})
|
)
|
const task = new DownloadTask(data)
|
if (!data.id && data.autoPlay) {
|
await task.start()
|
}
|
if (task.data) {
|
downloadTasks.push(task.data)
|
updateTask(task)
|
}
|
mainWindow.webContents.send(
|
'showMessage',
|
JSON.stringify({
|
type: 'showState',
|
msg: ''
|
})
|
)
|
}
|
})
|
|
// 暂停任务
|
ipcMain.on('pauseTask', (ev, id) => {
|
const index = downloadTasks.findIndex((item) => item.id == id)
|
if (index === -1) {
|
ev.returnValue = false
|
} else {
|
const task = new DownloadTask(downloadTasks[index])
|
task.pause()
|
ev.returnValue = true
|
}
|
})
|
ipcMain.on('pauseAllTask', (ev) => {
|
for (let i = 0; i < downloadTasks.length; i++) {
|
const item = downloadTasks[i]
|
if (item.state == 'download') {
|
const task = new DownloadTask(item)
|
task.pause()
|
}
|
}
|
ev.returnValue = true
|
})
|
|
// 开始任务
|
ipcMain.on('startTask', (ev, id) => {
|
const index = downloadTasks.findIndex((item) => item.id == id)
|
if (index === -1) {
|
ev.returnValue = false
|
} else {
|
const task = new DownloadTask(downloadTasks[index])
|
task.start()
|
ev.returnValue = true
|
}
|
})
|
ipcMain.on('startAllTask', (ev) => {
|
for (let i = 0; i < downloadTasks.length; i++) {
|
const item = downloadTasks[i]
|
if (item.state != 'success' && item.state != 'download') {
|
const task = new DownloadTask(item)
|
task.start()
|
}
|
}
|
ev.returnValue = true
|
})
|
|
ipcMain.on('cleanTask', (ev, id) => {
|
const item = downloadTasks.find((item) => item.id == id)
|
if (item.state == 'download') {
|
const task = new DownloadTask(item)
|
task.pause()
|
}
|
|
downloadTasks = downloadTasks.filter((item) => item.id != id)
|
updateTask()
|
ev.returnValue = true
|
})
|
|
ipcMain.on('cleanAll', (ev) => {
|
for (let i = 0; i < downloadTasks.length; i++) {
|
const item = downloadTasks[i]
|
if (item.state == 'download') {
|
const task = new DownloadTask(item)
|
task.pause()
|
}
|
}
|
|
downloadTasks = downloadTasks.filter((item) => item.state == 'success')
|
updateTask()
|
ev.returnValue = true
|
})
|
|
ipcMain.on('openPath', (ev, path) => {
|
if (fs.existsSync(path)) {
|
shell.openPath(path)
|
ev.returnValue = { state: true }
|
} else {
|
ev.returnValue = { state: false, msg: '目录不存在!' }
|
}
|
})
|
|
ipcMain.on('openPathByTaskId', (ev, id) => {
|
const item = downloadTasks.find((item) => item.id == id)
|
if (fs.existsSync(item.savePath)) {
|
if (item.md5) {
|
shell.showItemInFolder(item.savePath)
|
} else {
|
shell.openPath(item.savePath)
|
}
|
} else {
|
mainWindow.webContents.send(
|
'showMessage',
|
JSON.stringify({
|
type: 'warning',
|
msg: '本地文件目录不存在或者已被删除'
|
})
|
)
|
}
|
ev.returnValue = true
|
})
|
|
ipcMain.on('getDownloadPath', async (ev) => {
|
ev.returnValue = DOWNLOAD_PATH
|
})
|
|
ipcMain.on('setDownloadPath', async (ev, data) => {
|
if (fs.existsSync(data.path)) {
|
// 设置下载目录
|
DOWNLOAD_PATH = data.path
|
DownloadConfig.set(DOWNLOAD_SAVE_FOLDER, data.path)
|
if (data.dataStr) {
|
// 如果有任务,开始下载任务
|
mainWindow.webContents.send(
|
'showMessage',
|
JSON.stringify({
|
type: 'showState',
|
msg: '检测到下载任务,正在创建...'
|
})
|
)
|
const taskData = JSON.parse(data.dataStr)
|
const task = new DownloadTask(taskData)
|
if (!taskData.id && taskData.autoPlay) {
|
await task.start()
|
}
|
if (task.data) {
|
downloadTasks.push(task.data)
|
updateTask(task)
|
}
|
mainWindow.webContents.send(
|
'showMessage',
|
JSON.stringify({
|
type: 'showState',
|
msg: ''
|
})
|
)
|
}
|
ev.returnValue = { state: true }
|
} else {
|
ev.returnValue = { state: false, msg: '目录不存在!' }
|
}
|
})
|