更换下载库
parent
967093328f
commit
6d935d8589
|
@ -1,6 +1,6 @@
|
||||||
{
|
{
|
||||||
"name": "lx-music-desktop",
|
"name": "lx-music-desktop",
|
||||||
"version": "0.10.0",
|
"version": "0.11.0",
|
||||||
"lockfileVersion": 1,
|
"lockfileVersion": 1,
|
||||||
"requires": true,
|
"requires": true,
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
|
@ -5296,9 +5296,9 @@
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"core-js": {
|
"core-js": {
|
||||||
"version": "3.4.0",
|
"version": "3.4.1",
|
||||||
"resolved": "https://registry.npm.taobao.org/core-js/download/core-js-3.4.0.tgz?cache=0&other_urls=https%3A%2F%2Fregistry.npm.taobao.org%2Fcore-js%2Fdownload%2Fcore-js-3.4.0.tgz",
|
"resolved": "https://registry.npm.taobao.org/core-js/download/core-js-3.4.1.tgz?cache=0&other_urls=https%3A%2F%2Fregistry.npm.taobao.org%2Fcore-js%2Fdownload%2Fcore-js-3.4.1.tgz",
|
||||||
"integrity": "sha1-KepHhgF4nHLyl46buY9DVG+J06o=",
|
"integrity": "sha1-dt1oKEEpAKsnyM4LIuYRTXziGxg=",
|
||||||
"dev": true
|
"dev": true
|
||||||
},
|
},
|
||||||
"core-js-compat": {
|
"core-js-compat": {
|
||||||
|
@ -11101,11 +11101,6 @@
|
||||||
"lower-case": "^1.1.1"
|
"lower-case": "^1.1.1"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node-downloader-helper": {
|
|
||||||
"version": "1.0.10",
|
|
||||||
"resolved": "https://registry.npm.taobao.org/node-downloader-helper/download/node-downloader-helper-1.0.10.tgz",
|
|
||||||
"integrity": "sha1-bt5ymVH45yl/HlbkHDgrH7DSPw0="
|
|
||||||
},
|
|
||||||
"node-forge": {
|
"node-forge": {
|
||||||
"version": "0.9.0",
|
"version": "0.9.0",
|
||||||
"resolved": "https://registry.npm.taobao.org/node-forge/download/node-forge-0.9.0.tgz?cache=0&sync_timestamp=1569524876130&other_urls=https%3A%2F%2Fregistry.npm.taobao.org%2Fnode-forge%2Fdownload%2Fnode-forge-0.9.0.tgz",
|
"resolved": "https://registry.npm.taobao.org/node-forge/download/node-forge-0.9.0.tgz?cache=0&sync_timestamp=1569524876130&other_urls=https%3A%2F%2Fregistry.npm.taobao.org%2Fnode-forge%2Fdownload%2Fnode-forge-0.9.0.tgz",
|
||||||
|
@ -15342,9 +15337,9 @@
|
||||||
"dev": true
|
"dev": true
|
||||||
},
|
},
|
||||||
"vuex": {
|
"vuex": {
|
||||||
"version": "3.1.1",
|
"version": "3.1.2",
|
||||||
"resolved": "https://registry.npm.taobao.org/vuex/download/vuex-3.1.1.tgz",
|
"resolved": "https://registry.npm.taobao.org/vuex/download/vuex-3.1.2.tgz?cache=0&other_urls=https%3A%2F%2Fregistry.npm.taobao.org%2Fvuex%2Fdownload%2Fvuex-3.1.2.tgz",
|
||||||
"integrity": "sha1-DCZL/jDNvM+Wq52zF30hGCilkQ4="
|
"integrity": "sha1-ooY/QAWqc/JYflXD+t8/AfacfU0="
|
||||||
},
|
},
|
||||||
"vuex-electron": {
|
"vuex-electron": {
|
||||||
"version": "1.0.3",
|
"version": "1.0.3",
|
||||||
|
|
|
@ -147,7 +147,7 @@
|
||||||
"chalk": "^3.0.0",
|
"chalk": "^3.0.0",
|
||||||
"changelog-parser": "^2.8.0",
|
"changelog-parser": "^2.8.0",
|
||||||
"copy-webpack-plugin": "^5.0.5",
|
"copy-webpack-plugin": "^5.0.5",
|
||||||
"core-js": "^3.4.0",
|
"core-js": "^3.4.1",
|
||||||
"cos-nodejs-sdk-v5": "^2.5.14",
|
"cos-nodejs-sdk-v5": "^2.5.14",
|
||||||
"cross-env": "^6.0.3",
|
"cross-env": "^6.0.3",
|
||||||
"css-loader": "^3.2.0",
|
"css-loader": "^3.2.0",
|
||||||
|
@ -203,13 +203,12 @@
|
||||||
"flac-metadata": "^0.1.1",
|
"flac-metadata": "^0.1.1",
|
||||||
"js-htmlencode": "^0.3.0",
|
"js-htmlencode": "^0.3.0",
|
||||||
"lrc-file-parser": "^0.1.14",
|
"lrc-file-parser": "^0.1.14",
|
||||||
"node-downloader-helper": "^1.0.10",
|
|
||||||
"node-id3": "^0.1.12",
|
"node-id3": "^0.1.12",
|
||||||
"request": "^2.88.0",
|
"request": "^2.88.0",
|
||||||
"vue": "^2.6.10",
|
"vue": "^2.6.10",
|
||||||
"vue-electron": "^1.0.6",
|
"vue-electron": "^1.0.6",
|
||||||
"vue-router": "^3.1.3",
|
"vue-router": "^3.1.3",
|
||||||
"vuex": "^3.1.1",
|
"vuex": "^3.1.2",
|
||||||
"vuex-electron": "^1.0.3",
|
"vuex-electron": "^1.0.3",
|
||||||
"vuex-router-sync": "^5.0.0"
|
"vuex-router-sync": "^5.0.0"
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,9 +1,16 @@
|
||||||
|
由于新下载库仍然没有完成,但下载功能已经可用,so 移除之前使用的第三方下载库,暂时把新下载库的下载模块直接加入本程序,若出现下载问题欢迎反馈!
|
||||||
|
|
||||||
### 新增
|
### 新增
|
||||||
|
|
||||||
- 新增歌曲缓冲定时器,尝试用于解决网络正常但是歌曲缓冲过久的问题
|
- 新增下载功能对代理设置的支持,现在若在软件设置了代理服务器,下载功能也将会走代理网络了
|
||||||
- 新增下载管理的任务状态分类
|
|
||||||
- 添加**杀毒软件提示有病毒或恶意行为**的说明,可到**常见问题**拉到最后查看(常见问题可在开源地址找到)
|
|
||||||
|
|
||||||
### 优化
|
### 优化
|
||||||
|
|
||||||
- 优化更新弹窗机制及其内容描述,对于可以自动更新的版本,现在可以看到软件的下载进度了
|
- 新下载模块将对恢复下载的任务进行字节校验,用于解决下载进度超过100%后仍然下载的问题
|
||||||
|
- 注意:目前仍然无法暂停处于**链接获取**状态中的任务
|
||||||
|
|
||||||
|
### 修复
|
||||||
|
|
||||||
|
- 修复下载列表歌曲状态分类列表操作Bug
|
||||||
|
- 修复歌曲封面下载失败时仍然执行嵌入封面操作导致报错的问题
|
||||||
|
- 跳过重复添加**相同歌曲名与扩展名的歌曲**,例如你之前下载了A歌曲的128k音质,现在想要下载它的320k音质,但由于两者都是MP3格式,会因为重名导致之前的128k音质被覆盖但列表中仍然显示两种音质的问题(但实际上都是指向后面的320k音质)
|
||||||
|
|
|
@ -53,7 +53,7 @@ const getExt = type => {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
const checkList = (list, musicInfo, type) => list.some(s => s.musicInfo.songmid === musicInfo.songmid && s.type === type)
|
const checkList = (list, musicInfo, type, ext) => list.some(s => s.musicInfo.songmid === musicInfo.songmid && (s.type === type || s.ext === ext))
|
||||||
|
|
||||||
const getStartTask = (list, downloadStatus, maxDownloadNum) => {
|
const getStartTask = (list, downloadStatus, maxDownloadNum) => {
|
||||||
let downloadCount = 0
|
let downloadCount = 0
|
||||||
|
@ -121,11 +121,24 @@ const downloadLyric = (downloadInfo, filePath) => {
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const refreshUrl = function(commit, downloadInfo) {
|
||||||
|
commit('setStatusText', { downloadInfo, text: '链接失效,正在刷新链接' })
|
||||||
|
getUrl(downloadInfo, true).then(result => {
|
||||||
|
commit('updateUrl', { downloadInfo, url: result.url })
|
||||||
|
commit('setStatusText', { downloadInfo, text: '链接刷新成功' })
|
||||||
|
dls[downloadInfo.key].refreshUrl(result.url)
|
||||||
|
dls[downloadInfo.key].start()
|
||||||
|
}).catch(err => {
|
||||||
|
console.log(err)
|
||||||
|
this.dispatch('download/startTask')
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
// actions
|
// actions
|
||||||
const actions = {
|
const actions = {
|
||||||
createDownload({ state, rootState, commit }, { musicInfo, type }) {
|
createDownload({ state, rootState, commit }, { musicInfo, type }) {
|
||||||
if (checkList(state.list, musicInfo, type)) return
|
|
||||||
let ext = getExt(type)
|
let ext = getExt(type)
|
||||||
|
if (checkList(state.list, musicInfo, type, ext)) return
|
||||||
const downloadInfo = {
|
const downloadInfo = {
|
||||||
isComplate: false,
|
isComplate: false,
|
||||||
status: state.downloadStatus.WAITING,
|
status: state.downloadStatus.WAITING,
|
||||||
|
@ -146,6 +159,11 @@ const actions = {
|
||||||
}
|
}
|
||||||
downloadInfo.filePath = path.join(rootState.setting.download.savePath, downloadInfo.fileName)
|
downloadInfo.filePath = path.join(rootState.setting.download.savePath, downloadInfo.fileName)
|
||||||
commit('addTask', downloadInfo)
|
commit('addTask', downloadInfo)
|
||||||
|
try { // 删除同路径下的同名文件
|
||||||
|
fs.unlinkSync(downloadInfo.filePath)
|
||||||
|
} catch (err) {
|
||||||
|
if (err.code !== 'ENOENT') return commit('setStatusText', { downloadInfo, text: '文件删除失败' })
|
||||||
|
}
|
||||||
if (dls[downloadInfo.key]) {
|
if (dls[downloadInfo.key]) {
|
||||||
dls[downloadInfo.key].stop().finally(() => {
|
dls[downloadInfo.key].stop().finally(() => {
|
||||||
delete dls[downloadInfo.key]
|
delete dls[downloadInfo.key]
|
||||||
|
@ -167,7 +185,7 @@ const actions = {
|
||||||
if (!downloadInfo) downloadInfo = result
|
if (!downloadInfo) downloadInfo = result
|
||||||
|
|
||||||
// 开始任务
|
// 开始任务
|
||||||
commit('onDownload', downloadInfo)
|
commit('onStart', downloadInfo)
|
||||||
commit('setStatusText', { downloadInfo, text: '任务初始化中' })
|
commit('setStatusText', { downloadInfo, text: '任务初始化中' })
|
||||||
let msg = checkPath(rootState.setting.download.savePath)
|
let msg = checkPath(rootState.setting.download.savePath)
|
||||||
if (msg) return commit('setStatusText', '检查下载目录出错: ' + msg)
|
if (msg) return commit('setStatusText', '检查下载目录出错: ' + msg)
|
||||||
|
@ -178,12 +196,12 @@ const actions = {
|
||||||
fileName: downloadInfo.fileName,
|
fileName: downloadInfo.fileName,
|
||||||
method: 'get',
|
method: 'get',
|
||||||
override: true,
|
override: true,
|
||||||
onEnd() {
|
onCompleted() {
|
||||||
if (downloadInfo.progress.progress != '100.00') {
|
// if (downloadInfo.progress.progress != '100.00') {
|
||||||
delete dls[downloadInfo.key]
|
// delete dls[downloadInfo.key]
|
||||||
return this.dispatch('download/startTask', downloadInfo)
|
// return this.dispatch('download/startTask', downloadInfo)
|
||||||
}
|
// }
|
||||||
commit('onEnd', downloadInfo)
|
commit('onCompleted', downloadInfo)
|
||||||
_this.dispatch('download/startTask')
|
_this.dispatch('download/startTask')
|
||||||
const filePath = path.join(options.path, options.fileName)
|
const filePath = path.join(options.path, options.fileName)
|
||||||
|
|
||||||
|
@ -199,54 +217,40 @@ const actions = {
|
||||||
_this.dispatch('download/startTask')
|
_this.dispatch('download/startTask')
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
let code
|
if (err.code == 'ENOTFOUND') {
|
||||||
if (err.message.includes('Response status was')) {
|
refreshUrl.call(_this, commit, downloadInfo)
|
||||||
code = err.message.replace(/Response status was (\d+)$/, '$1')
|
|
||||||
} else if (err.code === 'ETIMEDOUT' || err.code == 'ENOTFOUND') {
|
|
||||||
code = err.code
|
|
||||||
} else {
|
} else {
|
||||||
console.log('Download failed, Attempting Retry')
|
console.log('Download failed, Attempting Retry')
|
||||||
dls[downloadInfo.key].resume()
|
dls[downloadInfo.key].start()
|
||||||
commit('setStatusText', { downloadInfo, text: '正在重试' })
|
commit('setStatusText', { downloadInfo, text: '正在重试' })
|
||||||
return
|
|
||||||
}
|
|
||||||
switch (code) {
|
|
||||||
case '401':
|
|
||||||
case '403':
|
|
||||||
case '410':
|
|
||||||
case 'ETIMEDOUT':
|
|
||||||
case 'ENOTFOUND':
|
|
||||||
commit('setStatusText', { downloadInfo, text: '链接失效,正在刷新链接' })
|
|
||||||
getUrl(downloadInfo, true).then(result => {
|
|
||||||
commit('updateUrl', { downloadInfo, url: result.url })
|
|
||||||
commit('setStatusText', { downloadInfo, text: '链接刷新成功' })
|
|
||||||
dls[downloadInfo.key].url = dls[downloadInfo.key].requestURL = result.url
|
|
||||||
dls[downloadInfo.key].__initProtocol(result.url)
|
|
||||||
dls[downloadInfo.key].resume()
|
|
||||||
}).catch(err => {
|
|
||||||
console.log(err)
|
|
||||||
_this.dispatch('download/startTask')
|
|
||||||
})
|
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
// onStateChanged(state) {
|
onFail(response) {
|
||||||
// console.log(state)
|
commit('onError', downloadInfo)
|
||||||
// },
|
|
||||||
onDownload() {
|
if (++tryNum[downloadInfo.key] > 2) {
|
||||||
commit('onDownload', downloadInfo)
|
_this.dispatch('download/startTask')
|
||||||
console.log('on download')
|
return
|
||||||
|
}
|
||||||
|
switch (response.statusCode) {
|
||||||
|
case 401:
|
||||||
|
case 403:
|
||||||
|
case 410:
|
||||||
|
refreshUrl.call(_this, commit, downloadInfo)
|
||||||
|
}
|
||||||
|
},
|
||||||
|
onStart() {
|
||||||
|
commit('onStart', downloadInfo)
|
||||||
|
console.log('on start')
|
||||||
},
|
},
|
||||||
onProgress(status) {
|
onProgress(status) {
|
||||||
commit('onProgress', { downloadInfo, status })
|
commit('onProgress', { downloadInfo, status })
|
||||||
console.log(status)
|
console.log(status)
|
||||||
},
|
},
|
||||||
onPause() {
|
onStop() {
|
||||||
commit('pauseTask', downloadInfo)
|
commit('pauseTask', downloadInfo)
|
||||||
_this.dispatch('download/startTask')
|
_this.dispatch('download/startTask')
|
||||||
},
|
},
|
||||||
onResume() {
|
|
||||||
commit('resumeTask', downloadInfo)
|
|
||||||
},
|
|
||||||
}
|
}
|
||||||
commit('setStatusText', { downloadInfo, text: '获取URL中...' })
|
commit('setStatusText', { downloadInfo, text: '获取URL中...' })
|
||||||
let p = options.url ? Promise.resolve() : getUrl(downloadInfo).then(result => {
|
let p = options.url ? Promise.resolve() : getUrl(downloadInfo).then(result => {
|
||||||
|
@ -315,9 +319,6 @@ const mutations = {
|
||||||
downloadInfo.status = state.downloadStatus.PAUSE
|
downloadInfo.status = state.downloadStatus.PAUSE
|
||||||
downloadInfo.statusText = '暂停下载'
|
downloadInfo.statusText = '暂停下载'
|
||||||
},
|
},
|
||||||
resumeTask(state, downloadInfo) {
|
|
||||||
downloadInfo.statusText = '开始下载'
|
|
||||||
},
|
|
||||||
setStatusText(state, { downloadInfo, index, text }) { // 设置状态文本
|
setStatusText(state, { downloadInfo, index, text }) { // 设置状态文本
|
||||||
if (downloadInfo) {
|
if (downloadInfo) {
|
||||||
downloadInfo.statusText = text
|
downloadInfo.statusText = text
|
||||||
|
@ -352,7 +353,7 @@ const mutations = {
|
||||||
state.list[index].status = status
|
state.list[index].status = status
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
onEnd(state, downloadInfo) {
|
onCompleted(state, downloadInfo) {
|
||||||
downloadInfo.isComplate = true
|
downloadInfo.isComplate = true
|
||||||
downloadInfo.status = state.downloadStatus.COMPLETED
|
downloadInfo.status = state.downloadStatus.COMPLETED
|
||||||
downloadInfo.statusText = '下载完成'
|
downloadInfo.statusText = '下载完成'
|
||||||
|
@ -361,7 +362,7 @@ const mutations = {
|
||||||
downloadInfo.status = state.downloadStatus.ERROR
|
downloadInfo.status = state.downloadStatus.ERROR
|
||||||
downloadInfo.statusText = '任务出错'
|
downloadInfo.statusText = '任务出错'
|
||||||
},
|
},
|
||||||
onDownload(state, downloadInfo) {
|
onStart(state, downloadInfo) {
|
||||||
downloadInfo.status = state.downloadStatus.RUN
|
downloadInfo.status = state.downloadStatus.RUN
|
||||||
downloadInfo.statusText = '正在下载'
|
downloadInfo.statusText = '正在下载'
|
||||||
},
|
},
|
||||||
|
|
|
@ -0,0 +1,306 @@
|
||||||
|
import fs from 'fs'
|
||||||
|
import path from 'path'
|
||||||
|
import request from 'request'
|
||||||
|
import { EventEmitter } from 'events'
|
||||||
|
import { performance } from 'perf_hooks'
|
||||||
|
import { STATUS } from './util'
|
||||||
|
|
||||||
|
|
||||||
|
const defaultChunkInfo = {
|
||||||
|
path: null,
|
||||||
|
startByte: 0,
|
||||||
|
endByte: '',
|
||||||
|
}
|
||||||
|
|
||||||
|
const defaultRequestOptions = {
|
||||||
|
method: 'GET',
|
||||||
|
headers: {},
|
||||||
|
}
|
||||||
|
const defaultOptions = {
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
class Task extends EventEmitter {
|
||||||
|
/**
|
||||||
|
*
|
||||||
|
* @param {String} url download url
|
||||||
|
* @param {Object} chunkInfo
|
||||||
|
* @param {Object} options
|
||||||
|
*/
|
||||||
|
constructor(url, savePath, filename, options = {}) {
|
||||||
|
super()
|
||||||
|
|
||||||
|
this.resumeLastChunk = null
|
||||||
|
this.downloadUrl = url
|
||||||
|
this.chunkInfo = Object.assign({}, defaultChunkInfo, {
|
||||||
|
path: path.join(savePath, filename),
|
||||||
|
startByte: 0,
|
||||||
|
})
|
||||||
|
if (!this.chunkInfo.endByte) this.chunkInfo.endByte = ''
|
||||||
|
|
||||||
|
this.options = Object.assign({}, defaultOptions, options)
|
||||||
|
|
||||||
|
this.requestOptions = Object.assign({}, defaultRequestOptions, this.options.requestOptions || {})
|
||||||
|
if (!this.requestOptions.headers) this.requestOptions.headers = {}
|
||||||
|
|
||||||
|
this.progress = {
|
||||||
|
total: 0,
|
||||||
|
downloaded: 0,
|
||||||
|
speed: 0,
|
||||||
|
}
|
||||||
|
this.statsEstimate = {
|
||||||
|
time: 0,
|
||||||
|
bytes: 0,
|
||||||
|
prevBytes: 0,
|
||||||
|
}
|
||||||
|
this.status = STATUS.idle
|
||||||
|
}
|
||||||
|
|
||||||
|
__init() {
|
||||||
|
this.status = STATUS.init
|
||||||
|
const { path, startByte, endByte } = this.chunkInfo
|
||||||
|
if (startByte) this.requestOptions.headers.range = `bytes=${startByte}-${endByte}`
|
||||||
|
return new Promise((resolve, reject) => {
|
||||||
|
if (!path) return resolve()
|
||||||
|
fs.stat(path, (errStat, stats) => {
|
||||||
|
if (errStat) {
|
||||||
|
if (errStat.code !== 'ENOENT') {
|
||||||
|
this.__handleError(errStat)
|
||||||
|
reject(errStat)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
} else if (stats.size >= 10) {
|
||||||
|
fs.open(path, 'r', (errOpen, fd) => {
|
||||||
|
if (errOpen) {
|
||||||
|
this.__handleError(errOpen)
|
||||||
|
reject(errOpen)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
fs.read(fd, Buffer.alloc(10), 0, 10, stats.size - 10, (errRead, bytesRead, buffer) => {
|
||||||
|
if (errRead) {
|
||||||
|
this.__handleError(errRead)
|
||||||
|
reject(errRead)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
fs.close(fd, errClose => {
|
||||||
|
if (errClose) {
|
||||||
|
this.__handleError(errClose)
|
||||||
|
reject(errClose)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// resume download
|
||||||
|
// console.log(buffer)
|
||||||
|
this.resumeLastChunk = buffer
|
||||||
|
this.progress.downloaded = stats.size
|
||||||
|
this.requestOptions.headers.range = `bytes=${stats.size - 10}-${endByte || ''}`
|
||||||
|
resolve()
|
||||||
|
})
|
||||||
|
})
|
||||||
|
})
|
||||||
|
return
|
||||||
|
}
|
||||||
|
resolve()
|
||||||
|
})
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
__httpFetch(url, options) {
|
||||||
|
// console.log(options)
|
||||||
|
this.request = request(url, options)
|
||||||
|
.on('response', response => {
|
||||||
|
if (response.statusCode !== 200 && response.statusCode !== 206) {
|
||||||
|
this.status = STATUS.failed
|
||||||
|
this.emit('fail', response)
|
||||||
|
this.__closeRequest()
|
||||||
|
this.__closeWriteStream()
|
||||||
|
return
|
||||||
|
}
|
||||||
|
this.emit('response', response)
|
||||||
|
try {
|
||||||
|
this.__initDownload(response)
|
||||||
|
} catch (error) {
|
||||||
|
return this.__handleError(error)
|
||||||
|
}
|
||||||
|
this.status = STATUS.running
|
||||||
|
response
|
||||||
|
.on('data', this.__handleWriteData.bind(this))
|
||||||
|
.on('error', err => this.__handleError(err))
|
||||||
|
.on('end', () => this.__handleComplete())
|
||||||
|
})
|
||||||
|
.on('error', err => this.__handleError(err))
|
||||||
|
.on('close', () => this.__closeWriteStream())
|
||||||
|
}
|
||||||
|
|
||||||
|
__initDownload(response) {
|
||||||
|
this.progress.total = parseInt(response.headers['content-length'] || 0)
|
||||||
|
let options = {}
|
||||||
|
let isResumable = this.options.forceResume || response.headers['accept-ranges'] !== 'none'
|
||||||
|
if (isResumable) {
|
||||||
|
options.flags = 'a'
|
||||||
|
if (this.progress.downloaded) this.progress.total -= 10
|
||||||
|
} else {
|
||||||
|
if (this.chunkInfo.startByte > 0) return this.__handleError(new Error('The resource cannot be resumed download.'))
|
||||||
|
}
|
||||||
|
this.progress.total += this.progress.downloaded
|
||||||
|
this.statsEstimate.prevBytes = this.progress.downloaded
|
||||||
|
if (!this.chunkInfo.path) return this.__handleError(new Error('Chunk save Path is not set.'))
|
||||||
|
this.ws = fs.createWriteStream(this.chunkInfo.path, options)
|
||||||
|
|
||||||
|
this.ws.on('finish', () => this.__closeWriteStream())
|
||||||
|
this.ws.on('error', async err => {
|
||||||
|
await this.__handleError(err)
|
||||||
|
fs.unlink(this.chunkInfo.path, () => this.__handleError(err))
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
__handleComplete() {
|
||||||
|
if (this.status == STATUS.error) return
|
||||||
|
this.__closeWriteStream().then(() => {
|
||||||
|
if (this.progress.downloaded == this.progress.total) {
|
||||||
|
this.status = STATUS.completed
|
||||||
|
this.emit('completed')
|
||||||
|
} else {
|
||||||
|
this.status = STATUS.stopped
|
||||||
|
this.emit('stop')
|
||||||
|
}
|
||||||
|
})
|
||||||
|
console.log('end')
|
||||||
|
}
|
||||||
|
|
||||||
|
__handleError(error) {
|
||||||
|
if (this.status == STATUS.error) return
|
||||||
|
this.status = STATUS.error
|
||||||
|
this.__closeRequest()
|
||||||
|
this.__closeWriteStream()
|
||||||
|
this.emit('error', error)
|
||||||
|
}
|
||||||
|
|
||||||
|
__closeWriteStream() {
|
||||||
|
return new Promise((resolve, reject) => {
|
||||||
|
if (!this.ws) return resolve()
|
||||||
|
console.log('close write stream')
|
||||||
|
this.ws.close(err => {
|
||||||
|
if (err) {
|
||||||
|
this.status = STATUS.error
|
||||||
|
this.emit('error', err)
|
||||||
|
reject(err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
this.ws = null
|
||||||
|
resolve()
|
||||||
|
})
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
__closeRequest() {
|
||||||
|
if (!this.request) return
|
||||||
|
console.log('close request')
|
||||||
|
this.request.abort()
|
||||||
|
this.request = null
|
||||||
|
}
|
||||||
|
|
||||||
|
__handleWriteData(chunk) {
|
||||||
|
if (this.resumeLastChunk) {
|
||||||
|
chunk = this.__handleDiffChunk(chunk)
|
||||||
|
if (!chunk) {
|
||||||
|
this.__handleError(new Error('Resume failed, response chunk does not match.'))
|
||||||
|
this.stop()
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// console.log('data', chunk)
|
||||||
|
if (this.status == STATUS.stopped || this.ws == null) return console.log('cancel write')
|
||||||
|
this.__calculateProgress(chunk.length)
|
||||||
|
this.ws.write(chunk, err => {
|
||||||
|
if (!err) return
|
||||||
|
console.log(err)
|
||||||
|
this.__handleError(err)
|
||||||
|
this.stop()
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
__handleDiffChunk(chunk) {
|
||||||
|
// console.log('diff', chunk)
|
||||||
|
let resumeLastChunkLen = this.resumeLastChunk.length
|
||||||
|
let chunkLen = chunk.length
|
||||||
|
let isOk
|
||||||
|
if (chunkLen >= resumeLastChunkLen) {
|
||||||
|
isOk = chunk.slice(0, resumeLastChunkLen).toString('hex') === this.resumeLastChunk.toString('hex')
|
||||||
|
if (!isOk) return null
|
||||||
|
|
||||||
|
this.resumeLastChunk = null
|
||||||
|
return chunk.slice(resumeLastChunkLen)
|
||||||
|
} else {
|
||||||
|
isOk = chunk.slice(0, chunkLen).toString('hex') === this.resumeLastChunk.slice(0, chunkLen).toString('hex')
|
||||||
|
if (!isOk) return null
|
||||||
|
this.resumeLastChunk = this.resumeLastChunk.slice(chunkLen)
|
||||||
|
return chunk.slice(chunkLen)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
__handleStop() {
|
||||||
|
return new Promise((resolve, reject) => {
|
||||||
|
if (this.request) {
|
||||||
|
this.request.abort()
|
||||||
|
this.request = null
|
||||||
|
}
|
||||||
|
if (this.ws) {
|
||||||
|
this.ws.close(err => {
|
||||||
|
if (err) {
|
||||||
|
reject(err)
|
||||||
|
this.emit('error', err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
this.ws = null
|
||||||
|
resolve()
|
||||||
|
})
|
||||||
|
} else {
|
||||||
|
resolve()
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
__calculateProgress(receivedBytes) {
|
||||||
|
const currentTime = performance.now()
|
||||||
|
const elaspsedTime = currentTime - this.statsEstimate.time
|
||||||
|
|
||||||
|
const progress = this.progress
|
||||||
|
progress.downloaded += receivedBytes
|
||||||
|
progress.progress = progress.total ? (progress.downloaded / progress.total) * 100 : -1
|
||||||
|
|
||||||
|
|
||||||
|
// emit the progress every second or if finished
|
||||||
|
if (progress.downloaded === progress.total || elaspsedTime > 1000) {
|
||||||
|
this.statsEstimate.time = currentTime
|
||||||
|
this.statsEstimate.bytes = progress.downloaded - this.statsEstimate.prevBytes
|
||||||
|
this.statsEstimate.prevBytes = progress.downloaded
|
||||||
|
this.emit('progress', {
|
||||||
|
total: progress.total,
|
||||||
|
downloaded: progress.downloaded,
|
||||||
|
progress: progress.progress,
|
||||||
|
speed: this.statsEstimate.bytes,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
async start() {
|
||||||
|
this.status = STATUS.running
|
||||||
|
await this.__init()
|
||||||
|
this.__httpFetch(this.downloadUrl, this.requestOptions)
|
||||||
|
this.emit('start')
|
||||||
|
}
|
||||||
|
|
||||||
|
async stop() {
|
||||||
|
if (this.status === STATUS.stopped) return
|
||||||
|
this.status = STATUS.stopped
|
||||||
|
await this.__handleStop()
|
||||||
|
this.emit('stop')
|
||||||
|
}
|
||||||
|
|
||||||
|
refreshUrl(url) {
|
||||||
|
this.downloadUrl = url
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export default Task
|
|
@ -1,6 +1,6 @@
|
||||||
import { DownloaderHelper } from 'node-downloader-helper'
|
import Downloader from './Downloader'
|
||||||
// import { pauseResumeTimer } from './util'
|
// import { pauseResumeTimer } from './util'
|
||||||
import { sizeFormate } from '../index'
|
import { sizeFormate, getProxyInfo } from '../index'
|
||||||
import { debugDownload } from '../env'
|
import { debugDownload } from '../env'
|
||||||
|
|
||||||
// these are the default options
|
// these are the default options
|
||||||
|
@ -21,38 +21,34 @@ export default ({
|
||||||
fileName,
|
fileName,
|
||||||
method = 'get',
|
method = 'get',
|
||||||
headers,
|
headers,
|
||||||
override,
|
|
||||||
forceResume,
|
forceResume,
|
||||||
// resumeTime = 5000,
|
// resumeTime = 5000,
|
||||||
onEnd = () => {},
|
onCompleted = () => {},
|
||||||
onError = () => {},
|
onError = () => {},
|
||||||
onStateChanged = () => {},
|
onFail = () => {},
|
||||||
onDownload = () => {},
|
onStart = () => {},
|
||||||
onPause = () => {},
|
onStop = () => {},
|
||||||
onResume = () => {},
|
|
||||||
onProgress = () => {},
|
onProgress = () => {},
|
||||||
resumeInfo,
|
|
||||||
} = {}) => {
|
} = {}) => {
|
||||||
const dl = new DownloaderHelper(url, path, {
|
const dl = new Downloader(url, path, fileName, {
|
||||||
fileName,
|
requestOptions: {
|
||||||
method,
|
method,
|
||||||
headers,
|
headers,
|
||||||
override,
|
proxy: getProxyInfo(),
|
||||||
|
},
|
||||||
|
|
||||||
forceResume,
|
forceResume,
|
||||||
})
|
})
|
||||||
|
|
||||||
dl.on('end', () => {
|
dl.on('completed', () => {
|
||||||
onEnd()
|
onCompleted()
|
||||||
debugDownload && console.log('Download Completed')
|
debugDownload && console.log('Download Completed')
|
||||||
}).on('error', err => {
|
}).on('error', err => {
|
||||||
if (err.message === 'socket hang up') return
|
if (err.message === 'socket hang up') return
|
||||||
onError(err)
|
onError(err)
|
||||||
debugDownload && console.error('Something happend', err)
|
debugDownload && console.error('Something happend', err)
|
||||||
}).on('stateChanged', state => {
|
}).on('start', () => {
|
||||||
onStateChanged(state)
|
onStart()
|
||||||
debugDownload && console.log('State: ', state)
|
|
||||||
}).on('download', () => {
|
|
||||||
onDownload()
|
|
||||||
// pauseResumeTimer(dl, resumeTime)
|
// pauseResumeTimer(dl, resumeTime)
|
||||||
}).on('progress', stats => {
|
}).on('progress', stats => {
|
||||||
const progress = stats.progress.toFixed(2)
|
const progress = stats.progress.toFixed(2)
|
||||||
|
@ -68,24 +64,17 @@ export default ({
|
||||||
const total = sizeFormate(stats.total)
|
const total = sizeFormate(stats.total)
|
||||||
console.log(`${speed}/s - ${progress}% [${downloaded}/${total}]`)
|
console.log(`${speed}/s - ${progress}% [${downloaded}/${total}]`)
|
||||||
}
|
}
|
||||||
}).on('pause', () => {
|
}).on('stop', () => {
|
||||||
onPause()
|
onStop()
|
||||||
debugDownload && console.log('paused')
|
debugDownload && console.log('paused')
|
||||||
}).on('resume', () => {
|
}).on('fail', resp => {
|
||||||
onResume()
|
onFail(resp)
|
||||||
debugDownload && console.log('resume')
|
debugDownload && console.log('fail')
|
||||||
})
|
})
|
||||||
|
|
||||||
debugDownload && console.log('Downloading: ', url)
|
debugDownload && console.log('Downloading: ', url)
|
||||||
|
|
||||||
if (resumeInfo) {
|
dl.start()
|
||||||
dl.__total = resumeInfo.totalFileSize // <--- Workaround
|
|
||||||
// dl.__filePath = resumeInfo.filePath // <--- Workaround
|
|
||||||
dl.__isResumable = true // <--- Workaround
|
|
||||||
dl.resume()
|
|
||||||
} else {
|
|
||||||
dl.start()
|
|
||||||
}
|
|
||||||
|
|
||||||
return dl
|
return dl
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,24 +1,11 @@
|
||||||
import { DH_STATES } from 'node-downloader-helper'
|
|
||||||
|
|
||||||
|
exports.STATUS = {
|
||||||
export const pauseResumeTimer = (_dl, wait) => {
|
idle: 'IDLE',
|
||||||
setTimeout(() => {
|
init: 'INIT',
|
||||||
if (_dl.state === DH_STATES.FINISHED || _dl.state === DH_STATES.FAILED) {
|
running: 'RUNNING',
|
||||||
return
|
paused: 'PAUSED',
|
||||||
}
|
stopped: 'STOPPED',
|
||||||
|
completed: 'COMPLETED',
|
||||||
_dl
|
error: 'ERROR',
|
||||||
.pause()
|
failed: 'FAILED',
|
||||||
.then(() => console.log(`Paused for ${wait / 1000} seconds`))
|
|
||||||
.then(() =>
|
|
||||||
setTimeout(() => {
|
|
||||||
if (!_dl.isResumable()) {
|
|
||||||
console.warn(
|
|
||||||
"This URL doesn't support resume, it will start from the beginning",
|
|
||||||
)
|
|
||||||
}
|
|
||||||
return _dl.resume()
|
|
||||||
}, wait),
|
|
||||||
)
|
|
||||||
}, wait)
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -374,3 +374,8 @@ export const clearCache = () => rendererInvoke('clearCache')
|
||||||
* @param {*} height
|
* @param {*} height
|
||||||
*/
|
*/
|
||||||
export const setWindowSize = (width, height) => rendererSend('setWindowSize', { width, height })
|
export const setWindowSize = (width, height) => rendererSend('setWindowSize', { width, height })
|
||||||
|
|
||||||
|
|
||||||
|
export const getProxyInfo = () => window.globalObj.proxy.enable
|
||||||
|
? `http://${window.globalObj.proxy.username}:${window.globalObj.proxy.password}@${window.globalObj.proxy.host}:${window.globalObj.proxy.port};`
|
||||||
|
: undefined
|
||||||
|
|
|
@ -4,6 +4,7 @@ import { debugRequest } from './env'
|
||||||
import { requestMsg } from './message'
|
import { requestMsg } from './message'
|
||||||
import { bHh } from './music/options'
|
import { bHh } from './music/options'
|
||||||
import { deflateRawSync } from 'zlib'
|
import { deflateRawSync } from 'zlib'
|
||||||
|
import { getProxyInfo } from './index'
|
||||||
// import fs from 'fs'
|
// import fs from 'fs'
|
||||||
|
|
||||||
const defaultHeaders = {
|
const defaultHeaders = {
|
||||||
|
@ -219,10 +220,6 @@ export const http_jsonp = (url, options, callback) => {
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
const getProxyInfo = () => window.globalObj.proxy.enable
|
|
||||||
? `http://${window.globalObj.proxy.username}:${window.globalObj.proxy.password}@${window.globalObj.proxy.host}:${window.globalObj.proxy.port};`
|
|
||||||
: undefined
|
|
||||||
|
|
||||||
const regx = /(?:\d\w)+/g
|
const regx = /(?:\d\w)+/g
|
||||||
|
|
||||||
const fetchData = (url, method, {
|
const fetchData = (url, method, {
|
||||||
|
|
|
@ -80,9 +80,11 @@ export default {
|
||||||
return this.listId == 'download'
|
return this.listId == 'download'
|
||||||
},
|
},
|
||||||
playListIndex() {
|
playListIndex() {
|
||||||
if (this.listId != 'download') return
|
if (this.listId != 'download' || !this.list.length) return
|
||||||
let path = this.list[this.playIndex].filePath
|
let info = this.list[this.playIndex]
|
||||||
return this.showList.findIndex(i => i.filePath == path)
|
if (!info) return -1
|
||||||
|
let key = info.key
|
||||||
|
return this.showList.findIndex(i => i.key == key)
|
||||||
},
|
},
|
||||||
showList() {
|
showList() {
|
||||||
switch (this.tabId) {
|
switch (this.tabId) {
|
||||||
|
@ -120,16 +122,16 @@ export default {
|
||||||
...mapMutations('player', ['setList']),
|
...mapMutations('player', ['setList']),
|
||||||
...mapMutations('download', ['pauseTask']),
|
...mapMutations('download', ['pauseTask']),
|
||||||
handlePauseTask(index) {
|
handlePauseTask(index) {
|
||||||
let info = this.showList[index]
|
let info = this.list[index]
|
||||||
let dl = this.dls[info.key]
|
let dl = this.dls[info.key]
|
||||||
dl ? dl.pause() : this.pauseTask(info)
|
dl ? dl.stop() : this.pauseTask(info)
|
||||||
console.log('pause')
|
console.log('pause')
|
||||||
},
|
},
|
||||||
handleStartTask(index) {
|
handleStartTask(index) {
|
||||||
console.log('start')
|
console.log('start')
|
||||||
let info = this.showList[index]
|
let info = this.list[index]
|
||||||
let dl = this.dls[info.key]
|
let dl = this.dls[info.key]
|
||||||
dl ? dl.resume() : this.startTask(info)
|
dl ? dl.start() : this.startTask(info)
|
||||||
},
|
},
|
||||||
handleDoubleClick(index) {
|
handleDoubleClick(index) {
|
||||||
if (
|
if (
|
||||||
|
@ -145,7 +147,9 @@ export default {
|
||||||
this.clickIndex = -1
|
this.clickIndex = -1
|
||||||
},
|
},
|
||||||
handleClick(index) {
|
handleClick(index) {
|
||||||
let info = this.showList[index]
|
const key = this.showList[index].key
|
||||||
|
index = this.list.findIndex(i => i.key === key)
|
||||||
|
let info = this.list[index]
|
||||||
if (info.isComplate) {
|
if (info.isComplate) {
|
||||||
this.handlePlay(index)
|
this.handlePlay(index)
|
||||||
} else if (info.status === this.downloadStatus.RUN) {
|
} else if (info.status === this.downloadStatus.RUN) {
|
||||||
|
@ -155,26 +159,28 @@ export default {
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
handlePlay(index) {
|
handlePlay(index) {
|
||||||
if (!checkPath(this.showList[index].filePath)) return
|
if (!checkPath(this.list[index].filePath)) return
|
||||||
let path = this.showList[index].filePath
|
let path = this.list[index].filePath
|
||||||
this.setList({ list: this.list, listId: 'download', index: this.list.findIndex(i => i.filePath === path) })
|
this.setList({ list: this.list, listId: 'download', index: this.list.findIndex(i => i.filePath === path) })
|
||||||
},
|
},
|
||||||
handleListBtnClick(info) {
|
handleListBtnClick(info) {
|
||||||
|
const key = this.showList[info.index].key
|
||||||
|
let index = this.list.findIndex(i => i.key === key)
|
||||||
switch (info.action) {
|
switch (info.action) {
|
||||||
case 'play':
|
case 'play':
|
||||||
this.handlePlay(info.index)
|
this.handlePlay(index)
|
||||||
break
|
break
|
||||||
case 'start':
|
case 'start':
|
||||||
this.handleStartTask(info.index)
|
this.handleStartTask(index)
|
||||||
break
|
break
|
||||||
case 'pause':
|
case 'pause':
|
||||||
this.handlePauseTask(info.index)
|
this.handlePauseTask(index)
|
||||||
break
|
break
|
||||||
case 'remove':
|
case 'remove':
|
||||||
this.removeTask(info.index)
|
this.removeTask(index)
|
||||||
break
|
break
|
||||||
case 'file':
|
case 'file':
|
||||||
this.handleOpenFolder(info.index)
|
this.handleOpenFolder(index)
|
||||||
break
|
break
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
@ -190,7 +196,7 @@ export default {
|
||||||
case 'start':
|
case 'start':
|
||||||
this.selectdData.forEach(item => {
|
this.selectdData.forEach(item => {
|
||||||
if (item.isComplate || item.status == this.downloadStatus.RUN) return
|
if (item.isComplate || item.status == this.downloadStatus.RUN) return
|
||||||
let index = this.showList.indexOf(item)
|
let index = this.list.indexOf(item)
|
||||||
if (index < 0) return
|
if (index < 0) return
|
||||||
this.handleStartTask(index)
|
this.handleStartTask(index)
|
||||||
})
|
})
|
||||||
|
@ -200,13 +206,13 @@ export default {
|
||||||
this.selectdData.forEach(item => {
|
this.selectdData.forEach(item => {
|
||||||
if (item.isComplate || item.status == this.downloadStatus.PAUSE) return
|
if (item.isComplate || item.status == this.downloadStatus.PAUSE) return
|
||||||
if (item.status == this.downloadStatus.RUN) return runs.push(item)
|
if (item.status == this.downloadStatus.RUN) return runs.push(item)
|
||||||
let index = this.showList.indexOf(item)
|
let index = this.list.indexOf(item)
|
||||||
if (index < 0) return
|
if (index < 0) return
|
||||||
this.handlePauseTask(index)
|
this.handlePauseTask(index)
|
||||||
})
|
})
|
||||||
runs.forEach(item => {
|
runs.forEach(item => {
|
||||||
if (item.isComplate || item.status == this.downloadStatus.PAUSE) return
|
if (item.isComplate || item.status == this.downloadStatus.PAUSE) return
|
||||||
let index = this.showList.indexOf(item)
|
let index = this.list.indexOf(item)
|
||||||
if (index < 0) return
|
if (index < 0) return
|
||||||
this.handlePauseTask(index)
|
this.handlePauseTask(index)
|
||||||
})
|
})
|
||||||
|
@ -219,7 +225,7 @@ export default {
|
||||||
this.resetSelect()
|
this.resetSelect()
|
||||||
},
|
},
|
||||||
handleOpenFolder(index) {
|
handleOpenFolder(index) {
|
||||||
let path = this.showList[index].filePath
|
let path = this.list[index].filePath
|
||||||
if (!checkPath(path)) return
|
if (!checkPath(path)) return
|
||||||
openDirInExplorer(path)
|
openDirInExplorer(path)
|
||||||
},
|
},
|
||||||
|
|
|
@ -78,7 +78,7 @@ div.scroll(:class="$style.setting")
|
||||||
material-checkbox(id="setting_download_isDownloadLrc" v-model="current_setting.download.isDownloadLrc" label="是否启用")
|
material-checkbox(id="setting_download_isDownloadLrc" v-model="current_setting.download.isDownloadLrc" label="是否启用")
|
||||||
dt 网络设置
|
dt 网络设置
|
||||||
dd
|
dd
|
||||||
h3 代理设置(歌曲下载暂不支持代理)
|
h3 代理设置
|
||||||
div
|
div
|
||||||
p
|
p
|
||||||
material-checkbox(id="setting_network_proxy_enable" v-model="current_setting.network.proxy.enable" @change="handleProxyChange('enable')" label="是否启用")
|
material-checkbox(id="setting_network_proxy_enable" v-model="current_setting.network.proxy.enable" @change="handleProxyChange('enable')" label="是否启用")
|
||||||
|
@ -108,7 +108,7 @@ div.scroll(:class="$style.setting")
|
||||||
material-btn(:class="[$style.btn, $style.gapLeft]" min @click="handleExportAllData") 导出
|
material-btn(:class="[$style.btn, $style.gapLeft]" min @click="handleExportAllData") 导出
|
||||||
dt 其他
|
dt 其他
|
||||||
dd
|
dd
|
||||||
h3 缓存大小(清理缓存后图片等资源将需要重新下载)
|
h3 缓存大小(清理缓存后图片等资源将需要重新下载,不建议清除,软件会自动将大小维持在200M左右)
|
||||||
div
|
div
|
||||||
p
|
p
|
||||||
| 软件已使用缓存大小:
|
| 软件已使用缓存大小:
|
||||||
|
|
Loading…
Reference in New Issue