Refactor video download logic

This commit is contained in:
PrestonN 2022-02-01 22:11:38 -05:00
parent e9239ec1b4
commit 36dfb7849d
6 changed files with 95 additions and 121 deletions

View File

@ -32,6 +32,10 @@ export default Vue.extend({
type: Boolean, type: Boolean,
default: false default: false
}, },
returnIndex: {
type: Boolean,
default: false
},
dropdownPositionX: { dropdownPositionX: {
type: String, type: String,
default: 'center' default: 'center'
@ -47,11 +51,6 @@ export default Vue.extend({
dropdownValues: { dropdownValues: {
type: Array, type: Array,
default: () => { return [] } default: () => { return [] }
},
relatedVideoTitle: {
type: String,
default: () => { return '' },
require: false
} }
}, },
data: function () { data: function () {
@ -60,18 +59,6 @@ export default Vue.extend({
id: '' id: ''
} }
}, },
computed: {
filesExtensions: function() {
const regex = /\/(\w*)/i
return this.dropdownNames.slice().map((el) => {
const group = el.match(regex)
if (group === null || group.length === 0) {
return ''
}
return group[1]
})
}
},
mounted: function () { mounted: function () {
this.id = `iconButton${this._uid}` this.id = `iconButton${this._uid}`
}, },
@ -128,16 +115,12 @@ export default Vue.extend({
}, },
handleDropdownClick: function (index) { handleDropdownClick: function (index) {
if (this.relatedVideoTitle !== '') { if (this.returnIndex) {
this.$emit('click', { this.$emit('click', index)
url: this.dropdownValues[index],
title: this.relatedVideoTitle,
extension: this.filesExtensions[index],
folderPath: this.$store.getters.getDownloadFolderPath
})
} else { } else {
this.$emit('click', this.dropdownValues[index]) this.$emit('click', this.dropdownValues[index])
} }
this.focusOut() this.focusOut()
} }
} }

View File

@ -25,13 +25,7 @@ export default Vue.extend({
toast.isOpen = false toast.isOpen = false
}, },
open: function (message, action, time, translate = false, formatArgs = []) { open: function (message, action, time) {
if (translate) {
message = this.$t(message)
for (const arg of formatArgs) {
message = message.replace('$', arg)
}
}
const toast = { message: message, action: action || (() => { }), isOpen: false, timeout: null } const toast = { message: message, action: action || (() => { }), isOpen: false, timeout: null }
toast.timeout = setTimeout(this.close, time || 3000, toast) toast.timeout = setTimeout(this.close, time || 3000, toast)
setImmediate(() => { toast.isOpen = true }) setImmediate(() => { toast.isOpen = true })

View File

@ -408,6 +408,27 @@ export default Vue.extend({
} }
}, },
handleDownload: function (index) {
const url = this.downloadLinkValues[index]
const linkName = this.downloadLinkNames[index]
const extension = this.grabExtensionFromUrl(linkName)
this.downloadMedia({
url: url,
title: this.title,
extension: extension
})
},
grabExtensionFromUrl: function (url) {
const regex = /\/(\w*)/i
const group = url.match(regex)
if (group.length === 0) {
return ''
}
return group[1]
},
addToPlaylist: function () { addToPlaylist: function () {
const videoData = { const videoData = {
videoId: this.id, videoId: this.id,

View File

@ -99,10 +99,10 @@
class="option" class="option"
theme="secondary" theme="secondary"
icon="download" icon="download"
:return-index="true"
:dropdown-names="downloadLinkNames" :dropdown-names="downloadLinkNames"
:dropdown-values="downloadLinkValues" :dropdown-values="downloadLinkValues"
:related-video-title="title" @click="handleDownload"
@click="downloadMedia"
/> />
<ft-icon-button <ft-icon-button
v-if="!isUpcoming" v-if="!isUpcoming"

View File

@ -1,6 +1,7 @@
import IsEqual from 'lodash.isequal' import IsEqual from 'lodash.isequal'
import FtToastEvents from '../../components/ft-toast/ft-toast-events' import FtToastEvents from '../../components/ft-toast/ft-toast-events'
import fs from 'fs' import fs from 'fs'
import i18n from '../../i18n/index'
import { IpcChannels } from '../../../constants' import { IpcChannels } from '../../../constants'
import { ipcRenderer } from 'electron' import { ipcRenderer } from 'electron'
@ -176,113 +177,92 @@ const actions = {
} }
}, },
async downloadMedia({ rootState, dispatch }, { url, title, extension, folderPath, fallingBackPath }) { async downloadMedia({ rootState, dispatch }, { url, title, extension, fallingBackPath }) {
const fileName = `${title}.${extension}`
const usingElectron = rootState.settings.usingElectron const usingElectron = rootState.settings.usingElectron
const askFolderPath = folderPath === '' const locale = i18n._vm.locale
let filePathSelected const translations = i18n._vm.messages[locale]
const successMsg = 'Downloading has completed' const startMessage = translations['Starting download'].replace('$', title)
const completedMessage = translations['Downloading has completed'].replace('$', title)
const errorMessage = translations['Downloading failed'].replace('$', title)
let folderPath = rootState.settings.downloadFolderPath
if (askFolderPath && usingElectron) { if (!usingElectron) {
const resp = await ipcRenderer.invoke( // Add logic here in the future
IpcChannels.SHOW_SAVE_DIALOG, return
{ defaultPath: `${title}.${extension}` }
)
filePathSelected = resp.filePath
} }
if (fallingBackPath !== undefined) { if (folderPath === '') {
dispatch('showToast', { const options = {
message: 'Download folder does not exist', defaultPath: fileName,
translate: true, filters: [
formatArgs: [fallingBackPath] {
}) extensions: [extension]
}
]
}
const response = await dispatch('showSaveDialog', options)
if (response.canceled || response.filePath === '') {
// User canceled the save dialog
return
}
folderPath = response.filePath
} }
dispatch('showToast', { dispatch('showToast', {
message: 'Starting download', translate: true, formatArgs: [title] message: startMessage
})
const response = await fetch(url).catch((error) => {
console.log(error)
dispatch('showToast', {
message: errorMessage
})
}) })
const response = await fetch(url)
// mechanism to show the download progress reference https://javascript.info/fetch-progress
const reader = response.body.getReader() const reader = response.body.getReader()
const contentLength = response.headers.get('Content-Length') const contentLength = response.headers.get('Content-Length')
let receivedLength = 0 let receivedLength = 0
const chunks = [] const chunks = []
// manage frequency notifications to the user
const intervalPercentageNotification = 0.2
let lastPercentageNotification = 0
while (true) { const handleError = (err) => {
const { done, value } = await reader.read() console.log(err)
dispatch('showToast', {
message: errorMessage
})
}
const processText = async ({ done, value }) => {
if (done) { if (done) {
break return
} }
chunks.push(value) chunks.push(value)
receivedLength += value.length receivedLength += value.length
// Can be used in the future to determine download percentage
const percentage = receivedLength / contentLength const percentage = receivedLength / contentLength
if (percentage > (lastPercentageNotification + intervalPercentageNotification)) { await reader.read().then(processText).catch(handleError)
// mechanism kept for an upcoming download page
lastPercentageNotification = percentage
}
} }
const chunksAll = new Uint8Array(receivedLength) await reader.read().then(processText).catch(handleError)
let position = 0
for (const chunk of chunks) {
chunksAll.set(chunk, position)
position += chunk.length
}
// write the file into the hardrive
if (!response.ok) {
console.error(`"Unable to download ${title}, return status code ${response.status}`)
dispatch('showToast', {
message: 'Downloading failed', translate: true, formatArgs: [title, response.status]
})
return
}
const blobFile = new Blob(chunks) const blobFile = new Blob(chunks)
const buffer = await blobFile.arrayBuffer() const buffer = await blobFile.arrayBuffer()
if (usingElectron && !askFolderPath) { fs.writeFile(folderPath, new DataView(buffer), (err) => {
fs.writeFile(`${folderPath}/${title}.${extension}`, new DataView(buffer), (err) => { if (err) {
if (err) { console.error(err)
console.error(err) dispatch('showToast', {
dispatch('updateDownloadFolderPath', '') message: errorMessage
dispatch('downloadMedia', { url: url, title: title, extension: extension, folderPath: '', fallingBackPath: folderPath }) })
} else { } else {
dispatch('showToast', { dispatch('showToast', {
message: successMsg, translate: true, formatArgs: [title] message: completedMessage
}) })
} }
}) })
} else if (usingElectron) {
fs.writeFile(filePathSelected, new DataView(buffer), (err) => {
if (err) {
console.error(err)
if (filePathSelected === '') {
dispatch('showToast', {
message: 'Downloading canceled',
translate: true
})
} else {
dispatch('showToast', {
message: err
})
}
} else {
dispatch('showToast', {
message: successMsg, translate: true, formatArgs: [title]
})
}
})
} else {
// Web placeholder
}
}, },
async getSystemLocale (context) { async getSystemLocale (context) {
@ -790,9 +770,7 @@ const actions = {
}, },
showToast (_, payload) { showToast (_, payload) {
const formatArgs = 'formatArgs' in payload ? payload.formatArgs : [] FtToastEvents.$emit('toast-open', payload.message, payload.action, payload.time)
const translate = 'translate' in payload ? payload.translate : false
FtToastEvents.$emit('toast-open', payload.message, payload.action, payload.time, translate, formatArgs)
}, },
showExternalPlayerUnsupportedActionToast: function ({ dispatch }, payload) { showExternalPlayerUnsupportedActionToast: function ({ dispatch }, payload) {

View File

@ -713,11 +713,9 @@ Default Invidious instance has been cleared: Default Invidious instance has been
'The playlist has ended. Enable loop to continue playing': 'The playlist has ended. Enable 'The playlist has ended. Enable loop to continue playing': 'The playlist has ended. Enable
loop to continue playing' loop to continue playing'
External link opening has been disabled in the general settings: 'External link opening has been disabled in the general settings' External link opening has been disabled in the general settings: 'External link opening has been disabled in the general settings'
Downloading has completed: 'Downloading "$" has completed' Downloading has completed: '"$" has finished downloading'
Starting download: 'Downloading "$" has started' Starting download: 'Starting download of "$"'
Downloading failed: 'Unable to download "$", return http request status code $' Downloading failed: 'There was an issue downloading "$"'
Downloading canceled: The dowload is canceled by the user
Download folder does not exist: The download directory "$" doesn't exist. Falling back to "ask folder" mode.
Yes: Yes Yes: Yes
No: No No: No