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: download as CAR via the context menu #1837

Merged
merged 3 commits into from
Jun 9, 2022
Merged
Show file tree
Hide file tree
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 2 additions & 1 deletion public/locales/en/app.json
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,8 @@
"setPinning": "Set pinning",
"submit": "Submit",
"unpin": "Unpin",
"unselectAll": "Unselect all"
"unselectAll": "Unselect all",
"downloadCar": "Download as CAR"
},
"cliModal": {
"description": "Paste the following into your terminal to do this task in IPFS via the command line. Remember that you'll need to replace placeholders with your specific parameters."
Expand Down
11 changes: 10 additions & 1 deletion src/bundles/files/actions.js
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
/* eslint-disable require-yield */

import { join, dirname, basename } from 'path'
import { getDownloadLink, getShareableLink } from '../../lib/files'
import { getDownloadLink, getShareableLink, getCarLink } from '../../lib/files'
import countDirs from '../../lib/count-dirs'
import memoize from 'p-memoize'
import all from 'it-all'
Expand Down Expand Up @@ -423,6 +423,15 @@ const actions = () => ({
return await getDownloadLink(files, gatewayUrl, apiUrl, ipfs)
}),

/**
* Creates a download link for the DAG CAR.
* @param {FileStat[]} files
*/
doFilesDownloadCarLink: (files) => perform(ACTIONS.DOWNLOAD_LINK, async (ipfs, { store }) => {
const gatewayUrl = store.selectGatewayUrl()
return await getCarLink(files, gatewayUrl, ipfs)
}),

/**
* Generates sharable link for the provided files.
* @param {FileStat[]} files
Expand Down
9 changes: 7 additions & 2 deletions src/bundles/files/consts.js
Original file line number Diff line number Diff line change
Expand Up @@ -78,7 +78,8 @@ export const cliCmdKeys = {
ADD_DIRECTORY: 'addNewDirectory',
CREATE_NEW_DIRECTORY: 'createNewDirectory',
FROM_IPFS: 'fromIpfs',
ADD_NEW_PEER: 'addNewPeer'
ADD_NEW_PEER: 'addNewPeer',
DOWNLOAD_CAR_COMMAND: 'downloadCarCommand'
}

export const cliCmdPrefixes = {
Expand Down Expand Up @@ -124,5 +125,9 @@ export const cliCommandList = {
* @param {string} path
*/
[cliCmdKeys.FROM_IPFS]: (path) => `ipfs files cp /ipfs/<cid> "${path}/<dest-name>"`,
[cliCmdKeys.ADD_NEW_PEER]: () => 'ipfs swarm connect <peer-multiaddr>'
[cliCmdKeys.ADD_NEW_PEER]: () => 'ipfs swarm connect <peer-multiaddr>',
/**
* @param {string} cid
*/
[cliCmdKeys.DOWNLOAD_CAR_COMMAND]: (cid) => `ipfs dag export ${cid}`
}
16 changes: 15 additions & 1 deletion src/files/FilesPage.js
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,7 @@ import Header from './header/Header'
import FileImportStatus from './file-import-status/FileImportStatus'

const FilesPage = ({
doFetchPinningServices, doFilesFetch, doPinsFetch, doFilesSizeGet, doFilesDownloadLink, doFilesWrite, doFilesAddPath, doUpdateHash,
doFetchPinningServices, doFilesFetch, doPinsFetch, doFilesSizeGet, doFilesDownloadLink, doFilesDownloadCarLink, doFilesWrite, doFilesAddPath, doUpdateHash,
doFilesUpdateSorting, doFilesNavigateTo, doFilesMove, doSetCliOptions, doFetchRemotePins, remotePins, doExploreUserProvidedPath,
ipfsProvider, ipfsConnected, doFilesMakeDir, doFilesShareLink, doFilesDelete, doSetPinning, onRemotePinClick,
files, filesPathInfo, pinningServices, toursEnabled, handleJoyrideCallback, isCliTutorModeEnabled, cliOptions, t
Expand Down Expand Up @@ -67,6 +67,18 @@ const FilesPage = ({
const { abort } = await downloadFile(url, filename, updater, method)
setDownloadAbort(() => abort)
}

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

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

const onAddFiles = (raw, root = '') => {
if (root === '') root = files.path

Expand Down Expand Up @@ -202,6 +214,7 @@ const FilesPage = ({
onRename={() => showModal(RENAME, [contextMenu.file])}
onInspect={() => onInspect(contextMenu.file.cid)}
onDownload={() => onDownload([contextMenu.file])}
onDownloadCar={() => onDownloadCar([contextMenu.file])}
onPinning={() => showModal(PINNING, [contextMenu.file])}
isCliTutorModeEnabled={isCliTutorModeEnabled}
onCliTutorMode={() => showModal(CLI_TUTOR_MODE, [contextMenu.file])}
Expand Down Expand Up @@ -274,6 +287,7 @@ export default connect(
'selectToursEnabled',
'doFilesWrite',
'doFilesDownloadLink',
'doFilesDownloadCarLink',
'doExploreUserProvidedPath',
'doFilesSizeGet',
'selectIsCliTutorModeEnabled',
Expand Down
11 changes: 10 additions & 1 deletion src/files/context-menu/ContextMenu.js
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ import StrokePencil from '../../icons/StrokePencil'
import StrokeIpld from '../../icons/StrokeIpld'
import StrokeTrash from '../../icons/StrokeTrash'
import StrokeDownload from '../../icons/StrokeDownload'
import StrokeData from '../../icons/StrokeData'
import StrokePin from '../../icons/StrokePin'
import { cliCmdKeys } from '../../bundles/files/consts'

Expand Down Expand Up @@ -43,7 +44,7 @@ class ContextMenu extends React.Component {

render () {
const {
t, onRename, onRemove, onDownload, onInspect, onShare,
t, onRename, onRemove, onDownload, onInspect, onShare, onDownloadCar,
translateX, translateY, className, isMfs, isUnknown, isCliTutorModeEnabled
} = this.props
return (
Expand Down Expand Up @@ -87,6 +88,13 @@ class ContextMenu extends React.Component {
{t('app:actions.download')}
</Option>
}
{ !isUnknown && onDownloadCar &&
<Option onClick={this.wrap('onDownloadCar')} isCliTutorModeEnabled={isCliTutorModeEnabled}
onCliTutorMode={this.wrap('onCliTutorMode', cliCmdKeys.DOWNLOAD_CAR_COMMAND)}>
<StrokeData className='w2 mr2 fill-aqua' />
{t('app:actions.downloadCar')}
</Option>
}
{ !isUnknown && isMfs && onRename &&
<Option onClick={this.wrap('onRename')} isCliTutorModeEnabled={isCliTutorModeEnabled}
onCliTutorMode={this.wrap('onCliTutorMode', cliCmdKeys.RENAME_IPFS_OBJECT)}>
Expand Down Expand Up @@ -120,6 +128,7 @@ ContextMenu.propTypes = {
onRemove: PropTypes.func,
onRename: PropTypes.func,
onDownload: PropTypes.func,
onDownloadCar: PropTypes.func,
onInspect: PropTypes.func,
onShare: PropTypes.func,
className: PropTypes.string,
Expand Down
1 change: 1 addition & 0 deletions src/files/context-menu/ContextMenu.stories.js
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@ storiesOf('Files', module)
onInspect={action('Inspect')}
onRename={action('Rename')}
onDownload={action('Download')}
onDownloadCar={action('Download CAR')}
onRemove={action('Remove')}
handleClick={action('Handle Click')}
onNavigate={action('Navigate')}
Expand Down
20 changes: 20 additions & 0 deletions src/lib/files.js
Original file line number Diff line number Diff line change
Expand Up @@ -147,6 +147,26 @@ export async function getShareableLink (files, gatewayUrl, ipfs) {
return `${gatewayUrl}/ipfs/${cid}${filename || ''}`
}

/**
*
* @param {FileStat[]} files
* @param {string} gatewayUrl
* @param {IPFSService} ipfs
* @returns {Promise<string>}
*/
export async function getCarLink (files, gatewayUrl, ipfs) {
let cid, filename

if (files.length === 1) {
cid = files[0].cid
filename = files[0].name
lidel marked this conversation as resolved.
Show resolved Hide resolved
} else {
cid = await makeCIDFromFiles(files, ipfs)
}

return `${gatewayUrl}/ipfs/${cid}?format=car&filename=${filename || cid}.car`
}

/**
* @param {number} size in bytes
* @param {object} opts format customization
Expand Down