From 25bb46294ecb1bdb05a556787a7caf5a05d4ea1f Mon Sep 17 00:00:00 2001 From: Young <24crazyoung@gmail.com> Date: Mon, 26 Dec 2022 22:08:39 +0900 Subject: [PATCH 1/2] notify files to peers when uploading them --- .../MainPage/UploadFilesComponent.js | 44 +++++++++++++++++-- client/src/utils/commonUtil.js | 13 ++++++ client/src/utils/database/schema.js | 1 + client/src/utils/localApi.js | 29 +++++++----- client/src/utils/peerConnection.js | 1 + 5 files changed, 75 insertions(+), 13 deletions(-) diff --git a/client/src/components/MainPage/UploadFilesComponent.js b/client/src/components/MainPage/UploadFilesComponent.js index 045ca0e..85afa35 100644 --- a/client/src/components/MainPage/UploadFilesComponent.js +++ b/client/src/components/MainPage/UploadFilesComponent.js @@ -2,6 +2,10 @@ import React, { useCallback } from "react"; import styled from "styled-components"; import { useDropzone } from "react-dropzone"; import { Fragment } from "react"; +import { generateFingerPrint, getSubtypeOfMIMEtypes } from "../../utils/commonUtil"; +import FingerprintedFile from "../../utils/dataSchema/FingerprintedFile"; +import { addFingerPrintedFiles, createTableSharingData, notifySharingData } from "../../utils/localApi"; +import { DATATYPE_FILE } from "../../constants/constant"; const UploadFiles = styled.div` display: flex; @@ -51,11 +55,45 @@ const Description = styled.div` function UploadFilesComponent(props) { const { className } = props; - const onDrop = useCallback((acceptedFiles) => { - // TODO(young): handle files later + const onDrop = useCallback(async (acceptedFiles) => { // store File object in somehwere; store file related data to db; // display upload process - 0.3~0.5sec animation is okay - console.log("acceptedFiles:", acceptedFiles); + if (acceptedFiles?.length === 0) { + return; + } + + const fingerprintedFiles = []; + for (let i = 0; i < acceptedFiles.length; i++) { + fingerprintedFiles.push( + new FingerprintedFile({ + file: acceptedFiles[i], + // TODO(young): use generateSharingDataUUID later and unify terminology + // fingerprint (x), data UUID (o) + fingerprint: generateFingerPrint(), + }) + ); + } + + // save to database + const sharingDataList = await Promise.all(fingerprintedFiles.map(fingerprintedFile => { + const dataID = fingerprintedFile.fingerprint; + const fileName = fingerprintedFile.file.name; + const fileType = getSubtypeOfMIMEtypes(fingerprintedFile.file.type) || "unknown"; + const sizeInBytes = fingerprintedFile.file.size; + + return createTableSharingData({ dataID, type: DATATYPE_FILE, + name: fileName, size: sizeInBytes, extension: fileType}); + })); + + // store in redux + addFingerPrintedFiles(fingerprintedFiles); + + // notify to other peers + if (sharingDataList?.length > 0) { + for (const sharingData of sharingDataList) { + await notifySharingData(sharingData); + } + } }, []); const { getRootProps, getInputProps, isDragActive } = useDropzone({ onDrop, diff --git a/client/src/utils/commonUtil.js b/client/src/utils/commonUtil.js index 7722315..7d52581 100644 --- a/client/src/utils/commonUtil.js +++ b/client/src/utils/commonUtil.js @@ -97,6 +97,18 @@ function getRandomNumber(max) { return Math.floor(Math.random() * max); } +// ex) "image/png", "image/png;param=hoho" +function getSubtypeOfMIMEtypes(types) { + const regex = /(.*){1}\/([^;]*){1}(;.*)*/; + const matched = types.match(regex); + + if (matched.length >= 3) { + return matched[2]; + } + + return null; +} + export { getSizeString, getCurrentTime, @@ -105,4 +117,5 @@ export { convertTimestampReadable, generateUserProfile, generateSharingDataUUID, + getSubtypeOfMIMEtypes, }; diff --git a/client/src/utils/database/schema.js b/client/src/utils/database/schema.js index 8ff35bc..3291b0f 100644 --- a/client/src/utils/database/schema.js +++ b/client/src/utils/database/schema.js @@ -15,6 +15,7 @@ const TABLE_SHARING_DATA = { // file related fields name: "name", + // size in bytes size: "size", extension: "extension", diff --git a/client/src/utils/localApi.js b/client/src/utils/localApi.js index 61c7eae..8095d49 100644 --- a/client/src/utils/localApi.js +++ b/client/src/utils/localApi.js @@ -25,6 +25,7 @@ import { updateTableNotifications as updateTableNotificationsCounter, updateTableSharingData as updateTableSharingDataCounter, updateSenderID, + addFiles, } from "../redux/action"; import { parseChunkAndHeader } from "./peerMessage"; import { @@ -156,6 +157,10 @@ function addMessagePacket(message) { store.dispatch(addMessage(message)); } +function addFingerPrintedFiles(files) { + store.dispatch(addFiles(files)) +} + // createMyUserInfo generate my user info and update my UUID async function createMyUserInfo(uuid) { // create user! @@ -397,20 +402,23 @@ async function patchTableUsersByID({ name, profile }, userID) { return result?.[0]?.rows; } -async function createTableSharingData({ type, name, size, extension, text }) { - const id = generateSharingDataUUID(); +async function createTableSharingData({ dataID, type, name, size, extension, text }) { + const id = dataID || generateSharingDataUUID(); const uploader_id = getMyUUID(); const uploaded_at = new Date().toISOString(); console.log(id, uploader_id, uploaded_at); - if (type === DATATYPE_FILE) { - console.log("shraing file is not supported yet!"); - return; - } - - const query = `INSERT INTO ${TABLE_SHARING_DATA.name} VALUES ( - "${id}", NULL, 0, NULL, "${text}", "${DATATYPE_LINK}", 0, false, - "${uploader_id}", "${uploaded_at}");`; + const query = (() => { + if (type === DATATYPE_FILE) { + return `INSERT INTO ${TABLE_SHARING_DATA.name} VALUES( + "${id}", "${name}", ${size}, "${extension}", NULL, "${DATATYPE_FILE}", 0, false, + "${uploader_id}", "${uploaded_at}");`; + } else { + return `INSERT INTO ${TABLE_SHARING_DATA.name} VALUES ( + "${id}", NULL, 0, NULL, "${text}", "${DATATYPE_LINK}", 0, false, + "${uploader_id}", "${uploaded_at}");`; + } + })(); console.log("query:", query); @@ -667,6 +675,7 @@ export { addJoinedPeers, deleteLeavedPeers, addMessagePacket, + addFingerPrintedFiles, createMyUserInfo, getPeerUUID, getMyUUID, diff --git a/client/src/utils/peerConnection.js b/client/src/utils/peerConnection.js index 6fa28c9..7a13c32 100644 --- a/client/src/utils/peerConnection.js +++ b/client/src/utils/peerConnection.js @@ -609,6 +609,7 @@ function addMessageTypeEventListener(peerConnectionManager) { peerConnectionManager.uuid = uuid; // TODO(young): consider in case of calling this event handler more than twice. + // TODO(young): create my user info regardless of signaling server connection. await createMyUserInfo(uuid); }); From 65a2cb7a5d4a174e881e3b86d765da23a7dd146c Mon Sep 17 00:00:00 2001 From: Young <24crazyoung@gmail.com> Date: Tue, 27 Dec 2022 20:00:30 +0900 Subject: [PATCH 2/2] support file download feature --- .../components/MainPage/UploadFilesComponent.js | 2 +- .../ActivityRow/ActionButtonComponent.js | 4 ++-- .../ActivityRow/ActivityRowComponent.js | 16 +++++++++++++--- client/src/utils/downloadManager.js | 15 +++++++++------ client/src/utils/localApi.js | 13 ++++++++++--- 5 files changed, 35 insertions(+), 15 deletions(-) diff --git a/client/src/components/MainPage/UploadFilesComponent.js b/client/src/components/MainPage/UploadFilesComponent.js index 85afa35..43d54ff 100644 --- a/client/src/components/MainPage/UploadFilesComponent.js +++ b/client/src/components/MainPage/UploadFilesComponent.js @@ -85,7 +85,7 @@ function UploadFilesComponent(props) { name: fileName, size: sizeInBytes, extension: fileType}); })); - // store in redux + // save in store addFingerPrintedFiles(fingerprintedFiles); // notify to other peers diff --git a/client/src/components/MyProfileAndActivityPage/ActivityRow/ActionButtonComponent.js b/client/src/components/MyProfileAndActivityPage/ActivityRow/ActionButtonComponent.js index 0592972..2b7f726 100644 --- a/client/src/components/MyProfileAndActivityPage/ActivityRow/ActionButtonComponent.js +++ b/client/src/components/MyProfileAndActivityPage/ActivityRow/ActionButtonComponent.js @@ -24,10 +24,10 @@ const ActionButton = styled.div` // TODO(young): implement onClick function later function ActionButtonComponent(props) { - const { type } = props; + const { type, onClick } = props; return ( - + ); diff --git a/client/src/components/MyProfileAndActivityPage/ActivityRow/ActivityRowComponent.js b/client/src/components/MyProfileAndActivityPage/ActivityRow/ActivityRowComponent.js index af60a3a..d03132e 100644 --- a/client/src/components/MyProfileAndActivityPage/ActivityRow/ActivityRowComponent.js +++ b/client/src/components/MyProfileAndActivityPage/ActivityRow/ActivityRowComponent.js @@ -13,6 +13,7 @@ import HandsUpIcon from "../../../assets/HandsUpIcon"; import { checkHandsUpTableSharingData, patchTableSharingDataByID, + requestDownloadFile, selectTableSharingDataByID, } from "../../../utils/localApi"; import { shallowEqual, useSelector } from "react-redux"; @@ -147,6 +148,7 @@ function renderAction( isHandsUpRow, onClick, onClickHandsUp, + onClickDonwloadButton, onCancelHandsUp ) { if (isEditMode) { @@ -159,8 +161,6 @@ function renderAction( ); } - console.log("isHandsUpRow:", isHandsUpRow, isMyProfileRow); - const renderIcon = () => { if (isMyProfileRow && isHandsUpRow) { return ; @@ -171,6 +171,7 @@ function renderAction( ) : ( ); }; @@ -225,6 +226,12 @@ function ActivityRowComponent(props) { event?.stopPropagation(); } + async function onClickDonwloadButton(event) { + event?.stopPropagation(); + // TODO(young): fingerprint should be renamed to sharingDataID + await requestDownloadFile(senderID, { fingerprint: rowID}); + } + async function onClickHandsUp(event) { event?.stopPropagation(); @@ -242,6 +249,8 @@ function ActivityRowComponent(props) { await patchTableSharingDataByID({ handsUp: false }, rowID); } + + return ( diff --git a/client/src/utils/downloadManager.js b/client/src/utils/downloadManager.js index 77aeeb4..e098f67 100644 --- a/client/src/utils/downloadManager.js +++ b/client/src/utils/downloadManager.js @@ -1,9 +1,9 @@ import { parsePeerChunk, - getMessagePacket, sendErrorToPeer, getMyUUID, writeSystemMessage, + selectTableSharingDataByID, } from "./localApi"; import { HEADER_SIZE_IN_BYTES, @@ -59,12 +59,15 @@ async function accumulateChunk(chunkWithHeader, uuid) { }; if (!chunkStore[fingerprint].downloadWriter) { - const messagePacket = getMessagePacket(fingerprint); + const sharingData = await selectTableSharingDataByID(fingerprint); - const filename = - (messagePacket && messagePacket.data.message) || - "localdrop_download_file"; - const fileSize = (messagePacket && messagePacket.data.size) || 0; + if (!sharingData) { + // TODO(young): handle error case + return; + } + + const filename = sharingData.name; + const fileSize = sharingData.size; const options = { pathname: fingerprint, size: fileSize }; chunkStore[fingerprint].size = fileSize; diff --git a/client/src/utils/localApi.js b/client/src/utils/localApi.js index 8095d49..8c7279f 100644 --- a/client/src/utils/localApi.js +++ b/client/src/utils/localApi.js @@ -220,6 +220,7 @@ async function transferFileToPeer(fingerprint, uuid) { } } +// TODO(young): deprecate messagePacket function getMessagePacket(fingerprint) { // TODO: Make O(1) @@ -432,6 +433,7 @@ async function createTableSharingData({ dataID, type, name, size, extension, tex return data; } +// TODO(young): unify insert/create sharing data or refactor them together async function insertTableSharingData({ sharingData }) { const id = sharingData[TABLE_SHARING_DATA.fields.id]; const name = sharingData[TABLE_SHARING_DATA.fields.name]; @@ -681,7 +683,6 @@ export { getMyUUID, getFileToTransfer, transferFileToPeer, - getMessagePacket, parsePeerChunk, writePeerChunk, writeSystemMessage, @@ -691,8 +692,7 @@ export { updateSender, // db interfaces updateTableUsers, - updateTableSharingData, - updateTableCommentMetadata, + updateTableNotifications, upsertTableUser, deleteTableUserByID, @@ -701,15 +701,22 @@ export { selectTableUsersMyself, selectTableUsersWithLatestSharingDataTypeExcludingMyself, patchTableUsersByID, + createTableSharingData, insertTableSharingData, + updateTableSharingData, + selectTableSharingDataByID, selectTableSharingDataWithCommentCount, selectTableSharingDataWithCommentCountOrderBy, checkHandsUpTableSharingData, patchTableSharingDataByID, deleteTableSharingDataByIDs, + selectTableCommentsByDataID, + selectTableCommentMetadataByDataID, + updateTableCommentMetadata, + selectTableNotifications, selecTableNotificationsWithUserAndSharingData, };