Skip to content

Commit

Permalink
feat: download folders and its children in buckets
Browse files Browse the repository at this point in the history
  • Loading branch information
prosfus committed Jan 9, 2025
1 parent 023711d commit 657b6b4
Show file tree
Hide file tree
Showing 2 changed files with 127 additions and 34 deletions.
115 changes: 100 additions & 15 deletions src/contexts/Minio/MinioContext.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@ import {
} from "@aws-sdk/client-s3";
import getSystemConfigApi from "@/api/config/getSystemConfig";
import { alert } from "@/lib/alert";
import JSZip from "jszip";

export type MinioProviderData = {
providerInfo: MinioStorageProvider;
Expand All @@ -42,6 +43,12 @@ export type MinioProviderData = {
uploadFile: (bucketName: string, path: string, file: File) => Promise<void>;
deleteFile: (bucketName: string, path: string) => Promise<void>;
getFileUrl: (bucketName: string, path: string) => Promise<string | undefined>;
listObjects: (bucketName: string, path: string) => Promise<_Object[]>;
downloadAndZipFolders: (
bucketName: string,
folders: CommonPrefix[],
singleFiles: _Object[]
) => Promise<Blob | undefined>;
};

export const MinioContext = createContext({} as MinioProviderData);
Expand Down Expand Up @@ -112,17 +119,6 @@ export const MinioProvider = ({ children }: { children: React.ReactNode }) => {
}
}

useEffect(() => {
async function getProviderInfo() {
const config = await getSystemConfigApi();
if (!config) return;

setProviderInfo(config.minio_provider);
}

getProviderInfo();
}, []);

async function updateBuckets() {
if (!client) return;

Expand All @@ -133,10 +129,6 @@ export const MinioProvider = ({ children }: { children: React.ReactNode }) => {
setBuckets(buckets);
}

useEffect(() => {
updateBuckets();
}, [client]);

async function createBucket(bucketName: string) {
if (!client) return;

Expand Down Expand Up @@ -251,6 +243,97 @@ export const MinioProvider = ({ children }: { children: React.ReactNode }) => {
return url;
}

async function listObjects(bucketName: string, path: string = "") {
if (!client) return [];

let objects: _Object[] = [];
let continuationToken: string | undefined = undefined;

do {
const params: ListObjectsV2CommandInput = {
Bucket: bucketName,
Prefix: path,
ContinuationToken: continuationToken,
};

const response = await client.send(new ListObjectsV2Command(params));
if (response.Contents) {
objects = objects.concat(response.Contents);
}
continuationToken = response.NextContinuationToken;
} while (continuationToken);

return objects;
}

// Función para descargar un archivo como ArrayBuffer
async function downloadFile(bucketName: string, key: string) {
const params = { Bucket: bucketName, Key: key };
const data = await client?.send(new GetObjectCommand(params));
if (!data?.Body) return undefined;
return await data.Body.transformToByteArray();
}

async function downloadAndZipFolders(
bucketName: string,
folders: CommonPrefix[],
singleFiles: _Object[]
) {
const zip = new JSZip();

try {
for (const folder of folders) {
const objects = await listObjects(bucketName, folder.Prefix!);

for (const object of objects) {
const relativePath = object.Key!.replace(folder.Prefix!, "");
if (!relativePath) continue; // Ignorar carpetas vacías

const fileData = await downloadFile(bucketName, object.Key!);

// Añadir archivo al ZIP
if (fileData) {
zip.file(`${folder.Prefix}${relativePath}`, new Blob([fileData]));
} else {
throw new Error(`Error al descargar el archivo: ${object.Key}`);
}
}
}

for (const file of singleFiles) {
const fileData = await downloadFile(bucketName, file.Key!);
if (fileData) {
zip.file(file.Key!, new Blob([fileData]));
} else {
throw new Error(`Error al descargar el archivo: ${file.Key}`);
}
}

// Generar el ZIP y descargarlo
const zipBlob = await zip.generateAsync({ type: "blob" });
return zipBlob;
} catch (err) {
alert.error(
err instanceof Error ? err.message : "Error durante la descarga"
);
}
}

useEffect(() => {
async function getProviderInfo() {
const config = await getSystemConfigApi();
if (!config) return;

setProviderInfo(config.minio_provider);
}

getProviderInfo();
}, []);

useEffect(() => {
updateBuckets();
}, [client]);

return (
<MinioContext.Provider
value={{
Expand All @@ -266,6 +349,8 @@ export const MinioProvider = ({ children }: { children: React.ReactNode }) => {
uploadFile,
deleteFile,
getFileUrl,
listObjects,
downloadAndZipFolders,
}}
>
{children}
Expand Down
46 changes: 27 additions & 19 deletions src/pages/ui/minio/components/BucketContent/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,6 @@ import useSelectedBucket from "../../hooks/useSelectedBucket";
import { Button } from "@/components/ui/button";
import DeleteDialog from "@/components/DeleteDialog";
import FilePreviewModal from "./FilePreview";
import JSZip from "jszip";
import {
Tooltip,
TooltipContent,
Expand All @@ -42,8 +41,14 @@ export type BucketItem =
export default function BucketContent() {
const { name: bucketName, path } = useSelectedBucket();

const { getBucketItems, buckets, uploadFile, deleteFile, getFileUrl } =
useMinio();
const {
getBucketItems,
downloadAndZipFolders,
buckets,
uploadFile,
deleteFile,
getFileUrl,
} = useMinio();

const [items, setItems] = useState<BucketItem[]>([]);

Expand Down Expand Up @@ -120,27 +125,30 @@ export default function BucketContent() {
};

const handleBulkDownload = async (items: BucketItem[]) => {
const zip = new JSZip();
const bucketName = items[0].BucketName;

for (const item of items) {
if (item.Type === "file") {
const url = await getFileUrl(item.BucketName, item.Key.Key!);
if (url) {
const response = await fetch(url);
const blob = await response.blob();
zip.file(item.Name, blob);
}
}
}
const folders = items
.filter((item) => item.Type === "folder")
.map((item) => item.Key as CommonPrefix);

const singleFiles = items
.filter((item) => item.Type === "file")
.map((item) => item.Key as _Object);

zip.generateAsync({ type: "blob" }).then((content) => {
const zipBlob = await downloadAndZipFolders(
bucketName,
folders,
singleFiles
);

if (zipBlob) {
const a = document.createElement("a");
a.href = URL.createObjectURL(content);
a.href = URL.createObjectURL(zipBlob);
a.download = `${bucketName}_files.zip`;
document.body.appendChild(a);
a.click();
document.body.removeChild(a);
});
}
};

return (
Expand Down Expand Up @@ -299,9 +307,9 @@ export default function BucketContent() {
<Button
className="mt-[2px]"
onClick={() => handleBulkDownload(items)}
disabled={items.some(
/* disabled={items.some(
(item) => item.Type === "folder"
)}
)} */
>
<DownloadIcon className="w-4 h-4 mr-2" />
Download
Expand Down

0 comments on commit 657b6b4

Please sign in to comment.