Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: use direct links to download all files #1894

Merged
merged 7 commits into from Nov 22, 2022
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
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
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Can we sort these alphabetically?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Unless you know how to do it automatically, please don't make me do it 😅

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

eslint can do it automatically, we should not be forcing devs to do code style management


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)
lidel marked this conversation as resolved.
Show resolved Hide resolved
}

/**
Expand Down