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,
default: false
},
returnIndex: {
type: Boolean,
default: false
},
dropdownPositionX: {
type: String,
default: 'center'
@ -47,11 +51,6 @@ export default Vue.extend({
dropdownValues: {
type: Array,
default: () => { return [] }
},
relatedVideoTitle: {
type: String,
default: () => { return '' },
require: false
}
},
data: function () {
@ -60,18 +59,6 @@ export default Vue.extend({
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 () {
this.id = `iconButton${this._uid}`
},
@ -128,16 +115,12 @@ export default Vue.extend({
},
handleDropdownClick: function (index) {
if (this.relatedVideoTitle !== '') {
this.$emit('click', {
url: this.dropdownValues[index],
title: this.relatedVideoTitle,
extension: this.filesExtensions[index],
folderPath: this.$store.getters.getDownloadFolderPath
})
if (this.returnIndex) {
this.$emit('click', index)
} else {
this.$emit('click', this.dropdownValues[index])
}
this.focusOut()
}
}

View File

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

View File

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

View File

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