Skip to content

Commit

Permalink
feat: download as CAR via the context menu
Browse files Browse the repository at this point in the history
License: MIT
Signed-off-by: Henrique Dias <hacdias@gmail.com>
  • Loading branch information
hacdias committed Jun 9, 2022
1 parent e77afcb commit 6266f9b
Show file tree
Hide file tree
Showing 7 changed files with 65 additions and 6 deletions.
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",
"exportDag": "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.exportDag')}
</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
} 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

0 comments on commit 6266f9b

Please sign in to comment.