Skip to content

Commit

Permalink
feat: use direct links to download all files (#1894)
Browse files Browse the repository at this point in the history
* fix: use direct link to gateway to download files

License: MIT
Signed-off-by: Henrique Dias <hacdias@gmail.com>

* feat: massively simplify file downloading

* fix: download filename

* refactor: remove download progress things

* refactor: remove return await

* refactor: use window.location.href instead

Signed-off-by: Henrique Dias <hacdias@gmail.com>
Co-authored-by: Russell Dempsey <1173416+SgtPooki@users.noreply.github.com>
  • Loading branch information
hacdias and SgtPooki committed Nov 22, 2022
1 parent bd038cd commit d1bcbbf
Show file tree
Hide file tree
Showing 6 changed files with 24 additions and 153 deletions.
7 changes: 3 additions & 4 deletions src/bundles/files/actions.js
Expand Up @@ -396,9 +396,8 @@ const actions = () => ({
* @param {FileStat[]} files
*/
doFilesDownloadLink: (files) => perform(ACTIONS.DOWNLOAD_LINK, async (ipfs, { store }) => {
const apiUrl = store.selectApiUrl()
const gatewayUrl = store.selectGatewayUrl()
return await getDownloadLink(files, gatewayUrl, apiUrl, ipfs)
return getDownloadLink(files, gatewayUrl, ipfs)
}),

/**
Expand All @@ -407,7 +406,7 @@ const actions = () => ({
*/
doFilesDownloadCarLink: (files) => perform(ACTIONS.DOWNLOAD_LINK, async (ipfs, { store }) => {
const gatewayUrl = store.selectGatewayUrl()
return await getCarLink(files, gatewayUrl, ipfs)
return getCarLink(files, gatewayUrl, ipfs)
}),

/**
Expand All @@ -417,7 +416,7 @@ const actions = () => ({
doFilesShareLink: (files) => perform(ACTIONS.SHARE_LINK, async (ipfs, { store }) => {
// ensureMFS deliberately omitted here, see https://github.com/ipfs/ipfs-webui/issues/1744 for context.
const publicGateway = store.selectPublicGateway()
return await getShareableLink(files, publicGateway, ipfs)
return getShareableLink(files, publicGateway, ipfs)
}),

/**
Expand Down
29 changes: 3 additions & 26 deletions src/files/FilesPage.js
Expand Up @@ -6,7 +6,6 @@ import { withTranslation, Trans } from 'react-i18next'
import ReactJoyride from 'react-joyride'
// Lib
import { filesTour } from '../lib/tours'
import downloadFile from './download-file'
// Components
import ContextMenu from './context-menu/ContextMenu'
import withTour from '../components/tour/withTour'
Expand All @@ -27,8 +26,6 @@ const FilesPage = ({
files, filesPathInfo, pinningServices, toursEnabled, handleJoyrideCallback, isCliTutorModeEnabled, cliOptions, t
}) => {
const contextMenuRef = useRef()
const [downloadAbort, setDownloadAbort] = useState(null)
const [downloadProgress, setDownloadProgress] = useState(null)
const [modals, setModals] = useState({ show: null, files: null })
const [contextMenu, setContextMenu] = useState({
isOpen: false,
Expand Down Expand Up @@ -58,32 +55,13 @@ const FilesPage = ({
*/

const onDownload = async (files) => {
if (downloadProgress !== null) {
return downloadAbort()
}

const { url, filename, method } = await doFilesDownloadLink(files)

if (method === 'GET') {
const link = document.createElement('a')
link.href = url
link.click()
} else {
const updater = (v) => setDownloadProgress(v)
const { abort } = await downloadFile(url, filename, updater, method)
setDownloadAbort(() => abort)
}
const url = await doFilesDownloadLink(files)
window.location.href = url
}

const onDownloadCar = async (files) => {
if (downloadProgress !== null) {
return downloadAbort()
}

const url = await doFilesDownloadCarLink(files)
const link = document.createElement('a')
link.href = url
link.click()
window.location.href = url
}

const onAddFiles = (raw, root = '') => {
Expand Down Expand Up @@ -167,7 +145,6 @@ const FilesPage = ({
pendingPins={pendingPins}
failedPins={failedPins}
upperDir={files.upper}
downloadProgress={downloadProgress}
onShare={(files) => showModal(SHARE, files)}
onRename={(files) => showModal(RENAME, files)}
onRemove={(files) => showModal(DELETE, files)}
Expand Down
44 changes: 0 additions & 44 deletions src/files/download-file.js

This file was deleted.

4 changes: 1 addition & 3 deletions src/files/files-list/FilesList.js
Expand Up @@ -51,7 +51,7 @@ const mergeRemotePinsIntoFiles = (files, remotePins = [], pendingPins = [], fail
}

export const FilesList = ({
className, files, pins, pinningServices, remotePins, pendingPins, failedPins, filesSorting, updateSorting, downloadProgress, filesIsFetching, filesPathInfo, showLoadingAnimation,
className, files, pins, pinningServices, remotePins, pendingPins, failedPins, filesSorting, updateSorting, filesIsFetching, filesPathInfo, showLoadingAnimation,
onShare, onSetPinning, onInspect, onDownload, onRemove, onRename, onNavigate, onRemotePinClick, onAddFiles, onMove, doFetchRemotePins, doDismissFailedPin, handleContextMenuClick, t
}) => {
const [selected, setSelected] = useState([])
Expand Down Expand Up @@ -355,7 +355,6 @@ export const FilesList = ({
inspect={() => onInspect(selectedFiles[0].cid)}
count={selectedFiles.length}
isMfs={filesPathInfo.isMfs}
downloadProgress={downloadProgress}
size={selectedFiles.reduce((a, b) => a + (b.size || 0), 0)} />
}
</Fragment> }
Expand All @@ -374,7 +373,6 @@ FilesList.propTypes = {
asc: PropTypes.bool.isRequired
}),
updateSorting: PropTypes.func.isRequired,
downloadProgress: PropTypes.number,
filesIsFetching: PropTypes.bool,
filesPathInfo: PropTypes.object,
// Actions
Expand Down
30 changes: 2 additions & 28 deletions src/files/selected-actions/SelectedActions.js
Expand Up @@ -55,7 +55,6 @@ class SelectedActions extends React.Component {
download: PropTypes.func.isRequired,
rename: PropTypes.func.isRequired,
inspect: PropTypes.func.isRequired,
downloadProgress: PropTypes.number,
t: PropTypes.func.isRequired,
tReady: PropTypes.bool.isRequired,
isMfs: PropTypes.bool.isRequired,
Expand All @@ -70,37 +69,12 @@ class SelectedActions extends React.Component {
force100: false
}

componentDidUpdate (prev) {
if (this.props.downloadProgress === 100 && prev.downloadProgress !== 100) {
this.setState({ force100: true })
setTimeout(() => {
this.setState({ force100: false })
}, 2000)
}
}

componentDidMount () {
this.containerRef.current && this.containerRef.current.focus()
}

get downloadText () {
if (this.state.force100) {
return this.props.t('finished')
}

if (!this.props.downloadProgress) {
return this.props.t('app:actions.download')
}

if (this.props.downloadProgress === 100) {
return this.props.t('finished')
}

return this.props.downloadProgress.toFixed(0) + '%'
}

render () {
const { t, tReady, animateOnStart, count, size, unselect, remove, share, setPinning, download, downloadProgress, rename, inspect, className, style, isMfs, ...props } = this.props
const { t, tReady, animateOnStart, count, size, unselect, remove, share, setPinning, download, rename, inspect, className, style, isMfs, ...props } = this.props

const isSingle = count === 1

Expand Down Expand Up @@ -131,7 +105,7 @@ class SelectedActions extends React.Component {
</button>
<button role="menuitem" className='tc mh2' onClick={download}>
<StrokeDownload className='w3 hover-fill-navy-muted' fill='#A4BFCC' aria-hidden="true"/>
<p className='ma0 f6'>{this.downloadText}</p>
<p className='ma0 f6'>{t('app:actions.download')}</p>
</button>
<button role="menuitem" className={classNames('tc mh2', classes.action(isMfs))} onClick={isMfs ? remove : null}>
<StrokeTrash className={classes.svg(isMfs)} fill='#A4BFCC' aria-hidden="true"/>
Expand Down
63 changes: 15 additions & 48 deletions src/lib/files.js
Expand Up @@ -35,31 +35,20 @@ export function normalizeFiles (files) {
}

/**
* @typedef {Object} FileDownload
* @property {string} url
* @property {string} filename
* @property {string} method
*
* @param {FileStat} file
* @param {string} type
* @param {string} name
* @param {CID} cid
* @param {string} gatewayUrl
* @param {string} apiUrl
* @returns {Promise<FileDownload>}
* @returns {string}
*/
async function downloadSingle (file, gatewayUrl, apiUrl) {
let url, filename, method

if (file.type === 'directory') {
const name = file.name || `download_${file.cid}` // Name is not always available.
url = `${apiUrl}/api/v0/get?arg=${file.cid}&archive=true&compress=true`
filename = `${name}.tar.gz`
method = 'POST' // API is POST-only
function getDownloadURL (type, name, cid, gatewayUrl) {
if (type === 'directory') {
const filename = `${name || `download_${cid.toString()}`}.tar`
return `${gatewayUrl}/ipfs/${cid.toString()}?download=true&format=tar&filename=${filename}`
} else {
url = `${gatewayUrl}/ipfs/${file.cid}?download=true&filename=${file.name}`
filename = file.name
method = 'GET'
const filename = `${name || cid}`
return `${gatewayUrl}/ipfs/${cid.toString()}?download=true&filename=${filename}`
}

return { url, filename, method }
}

/**
Expand Down Expand Up @@ -87,42 +76,20 @@ export async function makeCIDFromFiles (files, ipfs) {
return stat.cid
}

/**
*
* @param {FileStat[]} files
* @param {string} apiUrl
* @param {IPFSService} ipfs
* @returns {Promise<FileDownload>}
*/
async function downloadMultiple (files, apiUrl, ipfs) {
if (!apiUrl) {
const e = new Error('api url undefined')
return Promise.reject(e)
}

const cid = await makeCIDFromFiles(files, ipfs)

return {
url: `${apiUrl}/api/v0/get?arg=${cid}&archive=true&compress=true`,
filename: `download_${cid}.tar.gz`,
method: 'POST' // API is POST-only
}
}

/**
*
* @param {FileStat[]} files
* @param {string} gatewayUrl
* @param {string} apiUrl
* @param {IPFSService} ipfs
* @returns {Promise<FileDownload>}
* @returns {Promise<string>}
*/
export async function getDownloadLink (files, gatewayUrl, apiUrl, ipfs) {
export async function getDownloadLink (files, gatewayUrl, ipfs) {
if (files.length === 1) {
return downloadSingle(files[0], gatewayUrl, apiUrl)
return getDownloadURL(files[0].type, files[0].name, files[0].cid, gatewayUrl)
}

return downloadMultiple(files, apiUrl, ipfs)
const cid = await makeCIDFromFiles(files, ipfs)
return getDownloadURL('directory', '', cid, gatewayUrl)
}

/**
Expand Down

0 comments on commit d1bcbbf

Please sign in to comment.