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(files): CAR Import #2323

Merged
merged 41 commits into from
Mar 5, 2025
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
41 commits
Select commit Hold shift + click to select a range
bc3c29d
feat: add dropdown menu for import car
badgooooor Feb 1, 2025
d97c867
feat: add modal for import car
badgooooor Feb 1, 2025
a3d7418
locale: add en locale
badgooooor Feb 1, 2025
9488c3d
refactor,feat: change to functional component, add check file, rename…
badgooooor Feb 1, 2025
f0b44df
feat: wiring handling uploading car dag file
badgooooor Feb 1, 2025
742de35
locale: add placeholder
badgooooor Feb 3, 2025
e381d4e
feat: add import car file
badgooooor Feb 3, 2025
40f58d0
refactor: clear unused params
badgooooor Feb 3, 2025
5e324f3
update dependency
badgooooor Feb 4, 2025
ce09977
refactor: cleanup modal
badgooooor Feb 4, 2025
e7001ec
feat: update types
badgooooor Feb 4, 2025
f1abecf
feat: wip on dag import
badgooooor Feb 4, 2025
1eb486f
feat: add util
badgooooor Feb 6, 2025
0e6bdd4
feat: fix parse file for dag import
badgooooor Feb 6, 2025
f2271cc
docs: Add AddByCarModal as storybook
badgooooor Feb 6, 2025
d234a60
fix: fix typecheck error
badgooooor Feb 6, 2025
ab8b11f
fix: revert back
badgooooor Feb 7, 2025
3820dad
feat: update types of IPFSService
badgooooor Feb 10, 2025
e5b82d2
feat: revert types to use modified type
badgooooor Feb 10, 2025
fb82d3c
refactor: remove unused
badgooooor Feb 10, 2025
0576322
refactor: move definition from imports to local
badgooooor Feb 11, 2025
20c87e9
Merge branch 'main' into feat/import-dag-car-file
badgooooor Feb 22, 2025
23ab754
refactor: change to pass stream to dag import
badgooooor Feb 24, 2025
fb0f1c2
refactor: remove unused util
badgooooor Feb 24, 2025
2b16988
typo: Update wording
badgooooor Feb 27, 2025
9277e50
typo: Update wording in select file button
badgooooor Feb 27, 2025
979eba8
type: Update i18n, use wording for title in i18n
badgooooor Feb 27, 2025
f886da9
fix: fix logic on file name
badgooooor Feb 27, 2025
0ea5a82
fix: fix import to relative path within mfs
badgooooor Feb 27, 2025
8263556
Merge branch 'main' into feat/import-dag-car-file
badgooooor Feb 28, 2025
9ef6095
fix: icon, label, and styling
lidel Mar 4, 2025
b23ef89
docs: cli tutor for cat import
lidel Mar 4, 2025
a229aa9
chore: throw helpful error if name exists
2color Mar 5, 2025
5b35fec
fix: dont throw error when importing car if destination doesnt exist
SgtPooki Mar 5, 2025
a3abcce
fix: swallow error when file doesn't exist
2color Mar 5, 2025
bda1817
chore: remove unnecessary changes
SgtPooki Mar 5, 2025
838aae0
chore: use kubo-rpc-client types for IPFSService
SgtPooki Mar 5, 2025
d1ae76f
chore: minor ux fixes
SgtPooki Mar 5, 2025
2b929fc
fix: dont error if path doesn't exist
SgtPooki Mar 5, 2025
34f15dc
Merge branch 'main' into feat/import-dag-car-file
badgooooor Mar 5, 2025
8c5ff77
fix: remove duplicate catch block
SgtPooki Mar 5, 2025
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
2 changes: 2 additions & 0 deletions @types/ipfs/index.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,13 +2,15 @@ declare module 'ipfs' {
import type { CID } from 'multiformats/cid'
import type { Multiaddr } from '@multiformats/multiaddr'
import type { Buffer } from 'buffer'
import type { KuboRPCClient } from 'kubo-rpc-client'

declare export interface IPFSService extends CoreService {
pin: PinService;
files: FileService;
name: NameService;
object: ObjectService;
config: ConfigService;
dag: KuboRPCClient['dag'];
Copy link
Member

Choose a reason for hiding this comment

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

note that I removed the custom defined types here so we don't have to manage them later. Ideally this whole IPFSService will be going away and we'll be using types from kubo-rpc-client going forward.


stop(options?: TimeoutOptions): Promise<void>
}
Expand Down
313 changes: 299 additions & 14 deletions package-lock.json

Large diffs are not rendered by default.

2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -73,7 +73,7 @@
"it-first": "^1.0.6",
"it-last": "^1.0.5",
"it-map": "^1.0.5",
"kubo-rpc-client": "^4.1.1",
"kubo-rpc-client": "^5.0.2",
"milliseconds": "^1.0.3",
"money-clip": "^3.0.5",
"multiformats": "^13.0.1",
Expand Down
2 changes: 1 addition & 1 deletion public/locales/en/app.json
Original file line number Diff line number Diff line change
Expand Up @@ -30,7 +30,7 @@
"unselectAll": "Unselect all",
"generate": "Generate",
"publish": "Publish",
"downloadCar": "Download as CAR",
"downloadCar": "Export CAR",
"done": "Done"
},
"cliModal": {
Expand Down
8 changes: 8 additions & 0 deletions public/locales/en/files.json
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@
"openWithLocalAndPublicGateway": "Try opening it instead with your <1>local gateway</1> or <3>public gateway</3>.",
"cantBePreviewed": "Sorry, this file can’t be previewed",
"addByPath": "From IPFS",
"addByCar": "From CAR",
"bulkImport": "Bulk import",
"newFolder": "New folder",
"generating": "Generating…",
Expand Down Expand Up @@ -60,6 +61,13 @@
"namePlaceholder": "Name (optional)",
"examples": "Examples:"
},
"addByCarModal": {
"title": "CAR Import",
"description": "Choose a CAR file to import a DAG from and specify a name for it to be imported into the current directory.",
"selectCARButtonText": "Select CAR…",
"namePlaceholder": "Name",
"renameImportPath": "Rename import"
Copy link
Member

Choose a reason for hiding this comment

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

new label for rename input

},
"bulkImportModal": {
"title": "Bulk import with text file",
"description": "Upload a text file with a list of CIDs (names are optional). Example:",
Expand Down
54 changes: 50 additions & 4 deletions src/bundles/files/actions.js
Original file line number Diff line number Diff line change
Expand Up @@ -399,10 +399,56 @@ const actions = () => ({
}),

/**
* Reads a text file containing CIDs and adds each one to IPFS at the given root path.
* @param {FileStream[]} source - The text file containing CIDs
* @param {string} root - Destination directory in IPFS
*/
* Adds CAR file. On completion will trigger `doFilesFetch` to update the state.
* @param {string} root
* @param {FileStream} carFile
* @param {string} name
*/
doAddCarFile: (root, carFile, name = '') => perform(ACTIONS.ADD_CAR_FILE, async (/** @type {IPFSService} */ ipfs, { store }) => {
ensureMFS(store)

const stream = carFile.content.stream()
try {
// @ts-expect-error - https://github.com/ipfs/js-kubo-rpc-client/issues/278
const result = await all(ipfs.dag.import(stream, {
Comment on lines +412 to +413
Copy link
Member

Choose a reason for hiding this comment

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

kubo-rpc-client types need updated.

pinRoots: true
}))
const cid = result[0].root.cid
const src = `/ipfs/${cid}`
const dst = realMfsPath(join(root, name))
let dstExists = false

// Check if destination path already exists
await ipfs.files.stat(dst).then(() => {
dstExists = true
}).catch(() => {
// Swallow error. We can add the file to the dst path
})

if (dstExists) {
throw new Error(`The name "${name}" already exists in the current directory. Try importing with a different name.`)
}

try {
await ipfs.files.cp(src, dst)
} catch (/** @type {any} */ err) {
// TODO: Not sure why we do this. Perhaps a generic error is used
// to avoid leaking private information via Countly?
throw Object.assign(new Error('ipfs.files.cp call failed'), {
Comment on lines +435 to +437
Copy link
Member

Choose a reason for hiding this comment

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

FYI we no longer use countly.. we need to strip it from this repo

code: 'ERR_FILES_CP_FAILED'
})
}
return carFile
} finally {
await store.doFilesFetch()
}
}),

/**
* Reads a text file containing CIDs and adds each one to IPFS at the given root path.
* @param {FileStream[]} source - The text file containing CIDs
* @param {string} root - Destination directory in IPFS
*/
doFilesBulkCidImport: (source, root) => perform(ACTIONS.BULK_CID_IMPORT, async function (ipfs, { store }) {
ensureMFS(store)

Expand Down
7 changes: 7 additions & 0 deletions src/bundles/files/consts.js
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,8 @@ export const ACTIONS = {
SHARE_LINK: ('FILES_SHARE_LINK'),
/** @type {'FILES_ADDBYPATH'} */
ADD_BY_PATH: ('FILES_ADDBYPATH'),
/** @type {'FILES_ADD_CAR'} */
ADD_CAR_FILE: ('FILES_ADD_CAR'),
/** @type {'FILES_BULK_CID_IMPORT'} */
BULK_CID_IMPORT: ('FILES_BULK_CID_IMPORT'),
/** @type {'FILES_PIN_ADD'} */
Expand Down Expand Up @@ -80,6 +82,7 @@ export const cliCmdKeys = {
ADD_DIRECTORY: 'addNewDirectory',
CREATE_NEW_DIRECTORY: 'createNewDirectory',
FROM_IPFS: 'fromIpfs',
FROM_CAR: 'fromCar',
ADD_NEW_PEER: 'addNewPeer',
PUBLISH_WITH_IPNS: 'publishWithIPNS',
DOWNLOAD_CAR_COMMAND: 'downloadCarCommand'
Expand Down Expand Up @@ -128,6 +131,10 @@ export const cliCommandList = {
* @param {string} path
*/
[cliCmdKeys.FROM_IPFS]: (path) => `ipfs files cp /ipfs/<cid> "${path}/<dest-name>"`,
/**
* @param {string} path
*/
[cliCmdKeys.FROM_CAR]: (path) => `ipfs dag import file.car && ipfs files cp /ipfs/<imported-cid> "${path}/<dest-name>"`,
[cliCmdKeys.ADD_NEW_PEER]: () => 'ipfs swarm connect <peer-multiaddr>',
/**
* @param {string} ipfsPath
Expand Down
1 change: 1 addition & 0 deletions src/bundles/files/utils.js
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ import * as Task from '../task.js'

/**
* @typedef {import('ipfs').IPFSService} IPFSService
* @typedef {import('../../lib/files').FileStream} FileStream
* @typedef {import('./actions').Ext} Ext
* @typedef {import('./actions').Extra} Extra
*/
Expand Down
15 changes: 13 additions & 2 deletions src/files/FilesPage.js
Original file line number Diff line number Diff line change
Expand Up @@ -15,13 +15,13 @@ import FilesList from './files-list/FilesList.js'
import { getJoyrideLocales } from '../helpers/i8n.js'

// Icons
import Modals, { DELETE, NEW_FOLDER, SHARE, RENAME, ADD_BY_PATH, BULK_CID_IMPORT, CLI_TUTOR_MODE, PINNING, PUBLISH } from './modals/Modals.js'
import Modals, { DELETE, NEW_FOLDER, ADD_BY_CAR, SHARE, RENAME, ADD_BY_PATH, BULK_CID_IMPORT, CLI_TUTOR_MODE, PINNING, PUBLISH } from './modals/Modals.js'
import Header from './header/Header.js'
import FileImportStatus from './file-import-status/FileImportStatus.js'
import { useExplore } from 'ipld-explorer-components/providers'

const FilesPage = ({
doFetchPinningServices, doFilesFetch, doPinsFetch, doFilesSizeGet, doFilesDownloadLink, doFilesDownloadCarLink, doFilesWrite, doFilesBulkCidImport, doFilesAddPath, doUpdateHash,
doFetchPinningServices, doFilesFetch, doPinsFetch, doFilesSizeGet, doFilesDownloadLink, doFilesDownloadCarLink, doFilesWrite, doAddCarFile, doFilesBulkCidImport, doFilesAddPath, doUpdateHash,
doFilesUpdateSorting, doFilesNavigateTo, doFilesMove, doSetCliOptions, doFetchRemotePins, remotePins, pendingPins, failedPins,
ipfsProvider, ipfsConnected, doFilesMakeDir, doFilesShareLink, doFilesDelete, doSetPinning, onRemotePinClick, doPublishIpnsKey,
files, filesPathInfo, pinningServices, toursEnabled, handleJoyrideCallback, isCliTutorModeEnabled, cliOptions, t
Expand Down Expand Up @@ -79,6 +79,14 @@ const FilesPage = ({
}

const onAddByPath = (path, name) => doFilesAddPath(files.path, path, name)
/**
*
* @param {File} file
* @param {string} name
*/
const onAddByCar = (file, name) => {
doAddCarFile(files.path, file, name)
}
const onInspect = (cid) => doUpdateHash(`/explore/${cid}`)
const showModal = (modal, files = null) => setModals({ show: modal, files })
const hideModal = () => setModals({})
Expand Down Expand Up @@ -212,6 +220,7 @@ const FilesPage = ({
onAddFiles={onAddFiles}
onMove={doFilesMove}
onAddByPath={(files) => showModal(ADD_BY_PATH, files)}
onAddByCar={(files) => showModal(ADD_BY_CAR, files)}
onBulkCidImport={(files) => showModal(BULK_CID_IMPORT, files)}
onNewFolder={(files) => showModal(NEW_FOLDER, files)}
onCliTutorMode={() => showModal(CLI_TUTOR_MODE)}
Expand All @@ -233,6 +242,7 @@ const FilesPage = ({
onShareLink={doFilesShareLink}
onRemove={doFilesDelete}
onAddByPath={onAddByPath}
onAddByCar={onAddByCar}
onBulkCidImport={onBulkCidImport}
onPinningSet={doSetPinning}
onPublish={doPublishIpnsKey}
Expand Down Expand Up @@ -280,6 +290,7 @@ export default connect(
'doFilesShareLink',
'doFilesDelete',
'doFilesAddPath',
'doAddCarFile',
'doFilesNavigateTo',
'doFilesUpdateSorting',
'selectFilesSorting',
Expand Down
22 changes: 17 additions & 5 deletions src/files/file-input/FileInput.js
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ import DocumentIcon from '../../icons/StrokeDocument.js'
import FolderIcon from '../../icons/StrokeFolder.js'
import NewFolderIcon from '../../icons/StrokeNewFolder.js'
import DecentralizationIcon from '../../icons/StrokeDecentralization.js'
import DataIcon from '../../icons/StrokeData.js'
// Components
import { Dropdown, DropdownMenu, Option } from '../dropdown/Dropdown.js'
import Button from '../../components/button/button.tsx'
Expand Down Expand Up @@ -50,6 +51,11 @@ class FileInput extends React.Component {
this.toggleDropdown()
}

onAddByCar = () => {
this.props.onAddByCar()
this.toggleDropdown()
}

onBulkCidImport = () => {
this.props.onBulkCidImport()
this.toggleDropdown()
Expand Down Expand Up @@ -87,15 +93,20 @@ class FileInput extends React.Component {
<FolderIcon className='fill-aqua w2 mr1' />
{t('app:terms.folder')}
</Option>
<Option onClick={this.onNewFolder} id='add-new-folder' onCliTutorMode={() => this.onCliTutorMode(cliCmdKeys.CREATE_NEW_DIRECTORY)}
isCliTutorModeEnabled={isCliTutorModeEnabled}>
<NewFolderIcon className='fill-aqua w2 h2 mr1' />
{t('newFolder')}
</Option>
<Option onClick={this.onAddByPath} id='add-by-path' onCliTutorMode={() => this.onCliTutorMode(cliCmdKeys.FROM_IPFS)}
isCliTutorModeEnabled={isCliTutorModeEnabled}>
<DecentralizationIcon className='fill-aqua w2 mr1' />
{t('addByPath')}
</Option>
<Option onClick={this.onNewFolder} id='add-new-folder' onCliTutorMode={() => this.onCliTutorMode(cliCmdKeys.CREATE_NEW_DIRECTORY)}
<Option onClick={this.onAddByCar} id='add-by-car' onCliTutorMode={() => this.onCliTutorMode(cliCmdKeys.FROM_CAR)}
isCliTutorModeEnabled={isCliTutorModeEnabled}>
<NewFolderIcon className='fill-aqua w2 h2 mr1' />
{t('newFolder')}
<DataIcon className='fill-aqua w2 mr1' />
{t('addByCar')}
</Option>
<Option onClick={this.onBulkCidImport} id='bulk-cid-import'>
<DocumentIcon className='fill-aqua w2 mr1' />
Expand Down Expand Up @@ -129,8 +140,9 @@ FileInput.propTypes = {
t: PropTypes.func.isRequired,
onAddFiles: PropTypes.func.isRequired,
onAddByPath: PropTypes.func.isRequired,
onNewFolder: PropTypes.func.isRequired,
onBulkCidImport: PropTypes.func.isRequired
onAddByCar: PropTypes.func.isRequired,
onBulkCidImport: PropTypes.func.isRequired,
onNewFolder: PropTypes.func.isRequired
}

export default connect(
Expand Down
1 change: 1 addition & 0 deletions src/files/header/Header.js
Original file line number Diff line number Diff line change
Expand Up @@ -93,6 +93,7 @@ class Header extends React.Component {
onNewFolder={this.props.onNewFolder}
onAddFiles={this.props.onAddFiles}
onAddByPath={this.props.onAddByPath}
onAddByCar={this.props.onAddByCar}
onBulkCidImport={this.props.onBulkCidImport}
onCliTutorMode={this.props.onCliTutorMode}
/>
Expand Down
15 changes: 15 additions & 0 deletions src/files/modals/Modals.js
Original file line number Diff line number Diff line change
Expand Up @@ -15,12 +15,14 @@ import PublishModal from './publish-modal/PublishModal.js'
import CliTutorMode from '../../components/cli-tutor-mode/CliTutorMode.js'
import { cliCommandList, cliCmdKeys } from '../../bundles/files/consts.js'
import { realMfsPath } from '../../bundles/files/actions.js'
import AddByCarModal from './add-by-car-modal/AddByCarModal.js'
// Constants
const NEW_FOLDER = 'new_folder'
const SHARE = 'share'
const RENAME = 'rename'
const DELETE = 'delete'
const ADD_BY_PATH = 'add_by_path'
const ADD_BY_CAR = 'add_by_car'
const BULK_CID_IMPORT = 'bulk_cid_import'
const CLI_TUTOR_MODE = 'cli_tutor_mode'
const PINNING = 'pinning'
Expand All @@ -32,6 +34,7 @@ export {
RENAME,
DELETE,
ADD_BY_PATH,
ADD_BY_CAR,
BULK_CID_IMPORT,
CLI_TUTOR_MODE,
PINNING,
Expand Down Expand Up @@ -66,6 +69,11 @@ class Modals extends React.Component {
this.leave()
}

onAddByCar = (file, name) => {
this.props.onAddByCar(file, name)
this.leave()
}

onBulkCidImport = (files, root) => {
this.props.onBulkCidImport(files, root)
this.leave()
Expand Down Expand Up @@ -158,6 +166,7 @@ class Modals extends React.Component {
}
case NEW_FOLDER:
case ADD_BY_PATH:
case ADD_BY_CAR:
this.setState({ readyToShow: true })
break
case BULK_CID_IMPORT:
Expand Down Expand Up @@ -208,6 +217,7 @@ class Modals extends React.Component {
case cliCmdKeys.ADD_DIRECTORY:
case cliCmdKeys.CREATE_NEW_DIRECTORY:
case cliCmdKeys.FROM_IPFS:
case cliCmdKeys.FROM_CAR:
return cliCommandList[action](root.substring('/files'.length))
case cliCmdKeys.DELETE_FILE_FROM_IPFS:
case cliCmdKeys.REMOVE_FILE_FROM_IPFS:
Expand Down Expand Up @@ -265,6 +275,10 @@ class Modals extends React.Component {
onCancel={this.leave} />
</Overlay>

<Overlay show={show === ADD_BY_CAR && readyToShow} onLeave={this.leave}>
<AddByCarModal className='outline-0' onSubmit={this.onAddByCar} onCancel={this.leave} />
</Overlay>

<Overlay show={show === BULK_CID_IMPORT && readyToShow} onLeave={this.leave}>
<BulkImportModal
className='outline-0'
Expand Down Expand Up @@ -301,6 +315,7 @@ Modals.propTypes = {
show: PropTypes.string,
files: PropTypes.array,
onAddByPath: PropTypes.func.isRequired,
onAddByCar: PropTypes.func.isRequired,
onMove: PropTypes.func.isRequired,
onMakeDir: PropTypes.func.isRequired,
onShareLink: PropTypes.func.isRequired,
Expand Down
Loading