diff --git a/src/bundles/files/actions.js b/src/bundles/files/actions.js
index 55b80cfa5..73c506429 100644
--- a/src/bundles/files/actions.js
+++ b/src/bundles/files/actions.js
@@ -399,6 +399,54 @@ const actions = () => ({
}
}),
+ /**
+ * Reads a CSV file containing CIDs and adds each one to IPFS at the given root path.
+ * @param {FileStream[]} source - The CSV file containing CIDs
+ * @param {string} root - Destination directory in IPFS
+ */
+ doFilesAddBulkCid: (source, root) => spawn(ACTIONS.ADD_BY_PATH, async function * (ipfs, { store }) {
+ ensureMFS(store)
+
+ if (source.length !== 1) {
+ throw new Error('Please provide exactly one CSV file')
+ }
+
+ // Read the CSV file content
+ const file = source[0]
+ const content = await new Response(file.content).text()
+
+ // Split content into CIDs (assuming one CID per line, comma-separated)
+ const cids = content.split(/[\n,]/).map(cid => cid.trim()).filter(Boolean)
+
+ /** @type {Array<{ path: string, cid: string }>} */
+ const entries = []
+ let progress = 0
+ const totalCids = cids.length
+
+ yield { entries, progress: 0 }
+
+ for (const cid of cids) {
+ try {
+ const src = `/ipfs/${cid}`
+ const dst = realMfsPath(join(root || '/files', cid))
+
+ await ipfs.files.cp(src, dst)
+
+ entries.push({ path: dst, cid })
+ progress = (entries.length / totalCids) * 100
+
+ yield { entries, progress }
+ } catch (err) {
+ console.error(`Failed to add CID ${cid}:`, err)
+ // Continue with next CID even if one fails
+ }
+ }
+
+ yield { entries, progress: 100 }
+ await store.doFilesFetch()
+ return entries
+ }),
+
/**
* Creates a download link for the provided files.
* @param {FileStat[]} files
diff --git a/src/files/FilesPage.js b/src/files/FilesPage.js
index d9a837330..ccad9f994 100644
--- a/src/files/FilesPage.js
+++ b/src/files/FilesPage.js
@@ -21,7 +21,7 @@ import FileImportStatus from './file-import-status/FileImportStatus.js'
import { useExplore } from 'ipld-explorer-components/providers'
const FilesPage = ({
- doFetchPinningServices, doFilesFetch, doPinsFetch, doFilesSizeGet, doFilesDownloadLink, doFilesDownloadCarLink, doFilesWrite, doFilesAddPath, doUpdateHash,
+ doFetchPinningServices, doFilesFetch, doPinsFetch, doFilesSizeGet, doFilesDownloadLink, doFilesDownloadCarLink, doFilesWrite, doFilesAddBulkCid, 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
@@ -72,6 +72,11 @@ const FilesPage = ({
doFilesWrite(raw, root)
}
+ const onAddBulkCid = (raw, root = '') => {
+ if (root === '') root = files.path
+ doFilesAddBulkCid(raw, root)
+ }
+
const onAddByPath = (path, name) => doFilesAddPath(files.path, path, name)
const onInspect = (cid) => doUpdateHash(`/explore/${cid}`)
const showModal = (modal, files = null) => setModals({ show: modal, files })
@@ -204,6 +209,7 @@ const FilesPage = ({
files={files}
onNavigate={doFilesNavigateTo}
onAddFiles={onAddFiles}
+ onAddBulkCid={onAddBulkCid}
onMove={doFilesMove}
onAddByPath={(files) => showModal(ADD_BY_PATH, files)}
onNewFolder={(files) => showModal(NEW_FOLDER, files)}
@@ -277,6 +283,7 @@ export default connect(
'selectFilesSorting',
'selectToursEnabled',
'doFilesWrite',
+ 'doFilesAddBulkCid',
'doFilesDownloadLink',
'doFilesDownloadCarLink',
'doFilesSizeGet',
diff --git a/src/files/file-input/FileInput.js b/src/files/file-input/FileInput.js
index ec9bfb6e6..6a1ba964f 100644
--- a/src/files/file-input/FileInput.js
+++ b/src/files/file-input/FileInput.js
@@ -40,11 +40,21 @@ class FileInput extends React.Component {
return this.filesInput.click()
}
+ onAddBulkCid = async () => {
+ this.toggleDropdown()
+ return this.bulkCidInput.click()
+ }
+
onInputChange = (input) => async () => {
this.props.onAddFiles(normalizeFiles(input.files))
input.value = null
}
+ onBulkCidInputChange = (input) => async () => {
+ this.props.onAddBulkCid(normalizeFiles(input.files))
+ input.value = null
+ }
+
onAddByPath = () => {
this.props.onAddByPath()
this.toggleDropdown()
@@ -92,8 +102,12 @@ class FileInput extends React.Component {