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

Storage: Download functionality on bucket files #1424

Merged
merged 12 commits into from
Aug 11, 2021
16 changes: 8 additions & 8 deletions packages/storage-ui/src/Components/Pages/BucketPage.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,7 @@ import { useLocalStorage } from "@chainsafe/browser-storage-hooks"
import { DISMISSED_SURVEY_KEY } from "../Modules/SurveyBanner"

const BucketPage: React.FC<IFileBrowserModuleProps> = () => {
const { storageBuckets, uploadFiles, uploadsInProgress, getStorageSummary } = useStorage()
const { storageBuckets, uploadFiles, uploadsInProgress, getStorageSummary, downloadFile } = useStorage()
const { storageApiClient } = useStorageApi()
const { addToastMessage } = useToaster()
const [loadingCurrentPath, setLoadingCurrentPath] = useState(false)
Expand Down Expand Up @@ -137,15 +137,15 @@ const BucketPage: React.FC<IFileBrowserModuleProps> = () => {
}, [addToastMessage, pathContents, refreshContents, storageApiClient, bucket, currentPath])

const handleDownload = useCallback(async (
//cid: string
toDownload: ISelectedFile
) => {
throw new Error("Not implemented")
// const itemToDownload = pathContents.find(item => item.cid === cid)
// if (!itemToDownload || !bucket) return
// throw new Error("Not implemented")
Copy link
Collaborator

Choose a reason for hiding this comment

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

Suggested change
// throw new Error("Not implemented")

Copy link
Contributor Author

Choose a reason for hiding this comment

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

@Tbaut What was discussed regarding downloads? The ticket seemed straighforward

Copy link
Collaborator

Choose a reason for hiding this comment

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

Check the comments, the description was subsequently updated. #1418

Copy link
Contributor Author

Choose a reason for hiding this comment

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

So I should hide the progress bar?

Copy link
Collaborator

@Tbaut Tbaut Aug 6, 2021

Choose a reason for hiding this comment

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

Nope, the idea was to not handle the download in js, but rather download the file using a gateway directly. So get the cid, triggers a browser download (with a <a download= or something similar).

Copy link
Collaborator

Choose a reason for hiding this comment

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

But again, I'm not willing to fight for this. Feel free to re-open, I'd approve the PR.

const itemToDownload = pathContents.find(item => item.cid === toDownload.cid)
if (!itemToDownload || !bucket) return

// downloadFile(bucket.id, itemToDownload, currentPath)
downloadFile(bucket.id, itemToDownload, currentPath)
}, [
//pathContents, currentPath, bucket
pathContents, currentPath, bucket, downloadFile
])

// Breadcrumbs/paths
Expand Down Expand Up @@ -198,7 +198,7 @@ const BucketPage: React.FC<IFileBrowserModuleProps> = () => {
[CONTENT_TYPES.Image]: [],
[CONTENT_TYPES.Pdf]: [],
[CONTENT_TYPES.Text]: [],
[CONTENT_TYPES.File]: ["delete", "move"],
[CONTENT_TYPES.File]: ["delete", "move", "download"],
RyRy79261 marked this conversation as resolved.
Show resolved Hide resolved
[CONTENT_TYPES.Directory]: ["delete", "move"]
}), [])

Expand Down
98 changes: 97 additions & 1 deletion packages/storage-ui/src/Contexts/StorageContext.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,8 @@ import { useStorageApi } from "./StorageApiContext"
import { v4 as uuidv4 } from "uuid"
import { t } from "@lingui/macro"
import { readFileAsync } from "../Utils/Helpers"
import axios, { CancelToken } from "axios"
import { getPathWithFile } from "../Utils/pathUtils"

type StorageContextProps = {
children: React.ReactNode | React.ReactNode[]
Expand All @@ -42,13 +44,22 @@ export type DownloadProgress = {
complete: boolean
}

interface GetFileContentParams {
cid: string
cancelToken?: CancelToken
onDownloadProgress?: (progressEvent: ProgressEvent<EventTarget>) => void
file: FileSystemItem
path: string
}

type StorageContext = {
pins: PinStatus[]
uploadsInProgress: UploadProgress[]
downloadsInProgress: DownloadProgress[]
storageSummary: BucketSummaryResponse | undefined
getStorageSummary: () => Promise<void>
uploadFiles: (bucketId: string, files: File[], path: string) => Promise<void>
downloadFile: (bucketId: string, itemToDownload: FileSystemItem, path: string) => void
addPin: (cid: string) => Promise<PinStatus>
refreshPins: () => void
unpin: (requestId: string) => void
Expand Down Expand Up @@ -132,7 +143,7 @@ const StorageProvider = ({ children }: StorageContextProps) => {
[]
)

const [downloadsInProgress] = useReducer(
const [downloadsInProgress, dispatchDownloadsInProgress] = useReducer(
downloadsInProgressReducer,
[]
)
Expand Down Expand Up @@ -257,6 +268,90 @@ const StorageProvider = ({ children }: StorageContextProps) => {
}
}, [storageBuckets, storageApiClient, getStorageSummary])

const getFileContent = useCallback(async (
bucketId: string,
{ cid, cancelToken, onDownloadProgress, file, path }: GetFileContentParams
) => {

// when a file is accessed from the search page, a file and a path are passed in
// because the current path will not reflect the right state of the app
const fileToGet = file

if (!fileToGet) {
console.error("No file passed, and no file found for cid:", cid, "in pathContents:", path)
throw new Error("No file found.")
}
Tbaut marked this conversation as resolved.
Show resolved Hide resolved

try {
const result = await storageApiClient.getBucketObjectContent(
bucketId,
{ path: path },
cancelToken,
onDownloadProgress
)

return result.data

} catch (error) {
if (axios.isCancel(error)) {
return Promise.reject()
} else {
console.error(error)
return Promise.reject(error)
}
}
}, [storageApiClient])

const downloadFile = useCallback(async (bucketId: string, itemToDownload: FileSystemItem, path: string) => {
const toastId = uuidv4()
try {
const downloadProgress: DownloadProgress = {
id: toastId,
fileName: itemToDownload.name,
complete: false,
error: false,
progress: 0
}
dispatchDownloadsInProgress({ type: "add", payload: downloadProgress })
const result = await getFileContent(bucketId, {
cid: itemToDownload.cid,
file: itemToDownload,
path: getPathWithFile(path, itemToDownload.name),
onDownloadProgress: (progressEvent) => {
dispatchDownloadsInProgress({
type: "progress",
payload: {
id: toastId,
progress: Math.ceil(
(progressEvent.loaded / itemToDownload.size) * 100
)
}
})
}
})
if (!result) return
const link = document.createElement("a")
link.href = URL.createObjectURL(result)
link.download = itemToDownload?.name || "file"
link.click()
dispatchDownloadsInProgress({
type: "complete",
payload: { id: toastId }
})
URL.revokeObjectURL(link.href)
setTimeout(() => {
dispatchDownloadsInProgress({
type: "remove",
payload: { id: toastId }
})
}, REMOVE_UPLOAD_PROGRESS_DELAY)
return Promise.resolve()
} catch (error) {
dispatchDownloadsInProgress({ type: "error", payload: { id: toastId } })
return Promise.reject()
}
}, [getFileContent])

return (
<StorageContext.Provider
value={{
Expand All @@ -269,6 +364,7 @@ const StorageProvider = ({ children }: StorageContextProps) => {
refreshPins,
unpin,
storageBuckets,
downloadFile,
createBucket,
removeBucket,
uploadFiles
Expand Down