Trying to implement resumable functionality in my DropBox Video uploading webapp in ReactJS

32 Views Asked by At

I am facing an issue while storing the state of the amount of chunks transferred into localStorage and retrieving it so that I can implement resumable functionality into my app using the localStorage of the web browser.

I am making an webapp using ReactJs for the frontend and ExpressJS in the backend. The user can drop multiple files in a dropzone and then can upload those file into the backend server .The uploading of files are done by making chunks of file and then sending those chunks in the backend. These functionailties are working fine. Now I wanna add the Resumable uploading feature. If the connection between user and server get disconnected. User can upload the file from the same point where he/she was last time, instead of the uploading process start from beginning.

I am trying to store the state of uploaded chunks in the localStorage so that when the user come start uploading again, we can retrieve the information of the uploaded chunks state from the localStorage and then resume the upload from the last point where it was , before disconnection.

import { useState, useEffect } from "react";
import axios from "axios";
//chunkSize
// const chunkSize = 10 * 1024; //10kb
const chunkSize = 1 * 1024; //1kb

const ResumableDropBoxUploader = () => {
  const [dropzoneActive, setDropzoneActive] = useState(false);
  const [files, setFiles] = useState([]);
  const [currentFileIndex, setCurrentFileIndex] = useState(null);
  const [lastUploadedFileIndex, setLastUploadedFileIndex] = useState(null);
  const [currentChunkIndex, setCurrentChunkIndex] = useState(null);
  //for resumable uploading
  //storing information of how many chunks have been uploaded in an array
  const [uploadedChunks, setUploadedChunks] = useState([]);

  function handleDrop(e) {
    e.preventDefault();
    console.log(e.dataTransfer.files);
    setFiles([...files, ...e.dataTransfer.files]);
  }
  //function for readAndUpload
  function readAndUploadCurrentChunk() {
    //creating instance of file reader
    const reader = new FileReader();
    // console.log("files: ", files);
    // console.log("currentFileIndex: ", currentFileIndex);
    // console.log("files[currentFileIndex]: ", files[currentFileIndex]);
    const file = files[currentFileIndex];
    console.log("file: ", file);
    if (!files) {
      return;
    }

    const from = currentChunkIndex * chunkSize;
    const to = from + chunkSize;
    //creating blobs to send to the backend
    const blob = file.slice(from, to);

    if (uploadedChunks.includes(currentChunkIndex)) {
      // This chunk has already been uploaded, skip to the next one
      setCurrentChunkIndex(currentChunkIndex + 1);
      return;
    }
    reader.onload = (e) => uploadChunk(e);
    reader.readAsDataURL(blob);
  }

  //function for uploading chunks
  function uploadChunk(readerEvent) {
    const file = files[currentFileIndex];
    const data = readerEvent.target.result;
    console.log("data", data);

    //setting params
    const params = new URLSearchParams();
    params.set("name", file.name);
    params.set("size", file.size);
    params.set("currentChunkIndex", currentChunkIndex);
    params.set("totalChunks", Math.ceil(file.size / chunkSize));

    //setting headers
    const headers = {
      "Content-type": "application/octet-stream",
    };
    const url = "http://localhost:4000/upload?" + params.toString();

    //axios request
    axios.post(url, data, { headers }).then((response) => {
      const file = files[currentFileIndex];
      const fileSize = files[currentFileIndex].size;
      const isLastChunk =
        currentChunkIndex === Math.ceil(fileSize / chunkSize) - 1;

      //for resumable - storing how many chunks get uploaded
      // setUploadedChunks((prevUploadedChunks) => [
      //   ...prevUploadedChunks,
      //   currentChunkIndex,
      // ]);

      //storing info about file uploading into localStorage
      // Store the uploaded chunk in localStorage
      // const uploadedChunks = JSON.parse(localStorage.getItem(fileName)) || {};
      // uploadedChunks[chunkIndex] = true;
      // localStorage.setItem(fileName, JSON.stringify(uploadedChunks));

      //tracking how many bytes have been uploaded into localstrage
      const uploadedChunks = [
        ...new Set([...uploadedChunks, currentChunkIndex]),
      ];
      setUploadedChunks(uploadedChunks);
      console.log("setUploadedChunk called");
      // // localStorage.setItem(file.name, JSON.stringify(uploadedChunks));
      // localStorage.setItem("ChunkInfo", JSON.stringify(uploadedChunks));

      localStorage.setItem(
        "dropboxUploaderState",
        JSON.stringify(uploadedChunks)
      );

      if (isLastChunk) {
        file.finalFilename = response.data.finalFilename;
        setLastUploadedFileIndex(currentFileIndex);
        setCurrentChunkIndex(null);
        //for clearing from lcoalstorage if all the data is uploaded
        // setUploadedChunks([]);
      } else {
        setCurrentChunkIndex(currentChunkIndex + 1);
      }
    });
  }
  //useEffect for running when the file fininsh uploading
  useEffect(() => {
    if (lastUploadedFileIndex === null) {
      return;
    }
    const isLastFile = lastUploadedFileIndex === files.length - 1;
    const nextFileIndex = isLastFile ? null : currentFileIndex + 1;
    setCurrentFileIndex(nextFileIndex);
  }, [lastUploadedFileIndex]);

  //for monitoring the change in files.length
  useEffect(() => {
    if (files.length > 0) {
      if (currentFileIndex === null) {
        setCurrentFileIndex(
          lastUploadedFileIndex === null ? 0 : lastUploadedFileIndex + 1
        );
      }
    }
  }, [files.length]);

  //for monitoring current file
  useEffect(() => {
    if (currentFileIndex !== null) {
      setCurrentChunkIndex(0);
    }
  }, [currentFileIndex]);

  //for monitoring currentChunkIndex
  useEffect(() => {
    if (currentChunkIndex !== null) {
      readAndUploadCurrentChunk();
    }
  }, [currentChunkIndex]);

  //for resumable upload - fetching
  useEffect(() => {
    // Check if there is existing state stored in localStorage
    const storedState = localStorage.getItem("dropboxUploaderState");
    if (storedState) {
      const { lastUploadedFileIndex, uploadedChunks } = JSON.parse(storedState);

      setLastUploadedFileIndex(lastUploadedFileIndex);
      setCurrentFileIndex(lastUploadedFileIndex + 1);
      setUploadedChunks(uploadedChunks);

      // Resume the upload from the last uploaded chunk
      if (
        lastUploadedFileIndex !== null &&
        lastUploadedFileIndex < files.length - 1
      ) {
        setCurrentChunkIndex(uploadedChunks[uploadedChunks.length - 1] + 1);
      }
    }
  }, []);

  return (
    <div>
      <div
        className={"dropzone" + (dropzoneActive ? " active" : "")}
        onDragOver={(e) => {
          setDropzoneActive(true);
          e.preventDefault();
        }}
        onDragLeave={(e) => {
          setDropzoneActive(false);
          e.preventDefault();
        }}
        onDrop={(e) => handleDrop(e)}
      >
        Drop Your Files here
      </div>
      <div className="files">
        {files &&
          files.map((file, fileIndex) => {
            let progress = 0;
            if (file.finalFilename) {
              progress = 100;
            } else {
              const uploading = fileIndex === currentFileIndex;
              const chunks = Math.ceil(file.size / chunkSize);
              if (uploading) {
                progress = Math.round((currentChunkIndex / chunks) * 100);
              } else {
                progress = 0;
              }
            }
            return (
              <a
                className="file"
                target="_blank"
                href={"http://localhost:4000/uploads/" + file.finalFilename}
              >
                <div className="name">{file.name}</div>
                <div
                  className={"progress " + (progress === 100 ? "done" : "")}
                  style={{ width: progress + "%" }}
                >
                  {progress}%
                </div>
              </a>
            );
          })}
      </div>
    </div>
  );
};

export default ResumableDropBoxUploader;

0

There are 0 best solutions below