Skip to content

Commit

Permalink
feat: drive realtime events
Browse files Browse the repository at this point in the history
  • Loading branch information
Dwynr committed Apr 22, 2024
1 parent 7001ef2 commit c6897e9
Show file tree
Hide file tree
Showing 5 changed files with 261 additions and 10 deletions.
8 changes: 4 additions & 4 deletions package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@
"@alptugidin/react-circular-progress-bar": "^1.1.2",
"@emoji-mart/data": "^1.1.2",
"@emoji-mart/react": "^1.1.1",
"@filen/sdk": "^0.1.54",
"@filen/sdk": "^0.1.55",
"@radix-ui/react-alert-dialog": "^1.0.5",
"@radix-ui/react-avatar": "^1.0.4",
"@radix-ui/react-context-menu": "^2.1.5",
Expand Down
248 changes: 246 additions & 2 deletions src/components/drive/list/index.tsx
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { memo, useEffect, useRef, useMemo, useTransition } from "react"
import { memo, useEffect, useRef, useMemo, useTransition, useCallback } from "react"
import { useDriveItemsStore, useDriveSharedStore } from "@/stores/drive.store"
import worker from "@/lib/worker"
import { useQuery } from "@tanstack/react-query"
Expand All @@ -8,6 +8,9 @@ import ListList from "./list"
import { useLocalStorage } from "@uidotdev/usehooks"
import GridList from "./grid"
import { orderItemsByType } from "../utils"
import { type SocketEvent, type FileEncryptionVersion } from "@filen/sdk"
import socket from "@/lib/socket"
import { convertTimestampToMs } from "@/utils"

export const List = memo(() => {
const { items, setItems, searchTerm } = useDriveItemsStore()
Expand All @@ -16,7 +19,7 @@ export const List = memo(() => {
const lastPathname = useRef<string>("")
const [, startTransition] = useTransition()
const [listType] = useLocalStorage<Record<string, "grid" | "list">>("listType", {})
const { currentReceiverId } = useDriveSharedStore()
const { currentReceiverId, currentReceiverEmail, currentSharerEmail, currentSharerId, currentReceivers } = useDriveSharedStore()
const queryUpdatedAtRef = useRef<number>(-1)

const query = useQuery({
Expand Down Expand Up @@ -49,6 +52,239 @@ export const List = memo(() => {
return itemsOrdered.filter(item => item.name.toLowerCase().includes(searchTermLowered))
}, [itemsOrdered, searchTerm])

const socketEventListener = useCallback(
async (event: SocketEvent) => {
try {
if (
event.type === "fileArchived" ||
event.type === "fileTrash" ||
event.type === "folderTrash" ||
event.type === "fileMove" ||
event.type === "folderMove"
) {
startTransition(() => {
setItems(prev => prev.filter(i => i.uuid !== event.data.uuid))
})
} else if (event.type === "trashEmpty") {
if (!location.includes("trash")) {
return
}

startTransition(() => {
setItems([])
})

await query.refetch()
} else if (event.type === "fileNew") {
if (event.data.parent !== parent) {
return
}

const metadata = await worker.decryptFileMetadata({ metadata: event.data.metadata })

startTransition(() => {
setItems(prev => [
...prev.filter(i => i.uuid !== event.data.uuid),
{
type: "file",
uuid: event.data.uuid,
timestamp: convertTimestampToMs(event.data.timestamp),
lastModified: metadata.lastModified,
creation: metadata.creation,
hash: metadata.hash,
name: metadata.name,
key: metadata.key,
mime: metadata.mime,
size: metadata.size,
parent: event.data.parent,
chunks: event.data.chunks,
sharerId: currentSharerId,
sharerEmail: currentSharerEmail,
receiverEmail: currentReceiverEmail,
receiverId: currentReceiverId,
receivers: currentReceivers,
favorited: event.data.favorited === 1,
rm: event.data.rm,
region: event.data.region,
bucket: event.data.bucket,
version: event.data.version as FileEncryptionVersion,
selected: false
}
])
})
} else if (event.type === "folderSubCreated") {
if (event.data.parent !== parent) {
return
}

const metadata = await worker.decryptFolderMetadata({ metadata: event.data.name })

startTransition(() => {
setItems(prev => [
...prev.filter(i => i.uuid !== event.data.uuid),
{
type: "directory",
uuid: event.data.uuid,
timestamp: convertTimestampToMs(event.data.timestamp),
lastModified: convertTimestampToMs(event.data.timestamp),
name: metadata.name,
size: 0,
color: null,
parent: event.data.parent,
sharerId: currentSharerId,
sharerEmail: currentSharerEmail,
receiverEmail: currentReceiverEmail,
receiverId: currentReceiverId,
receivers: currentReceivers,
favorited: event.data.favorited === 1,
selected: false
}
])
})
} else if (event.type === "fileRename") {
const metadata = await worker.decryptFileMetadata({ metadata: event.data.metadata })

startTransition(() => {
setItems(prev => prev.map(item => (item.uuid === event.data.uuid ? { ...item, name: metadata.name } : item)))
})
} else if (event.type === "folderRename") {
const metadata = await worker.decryptFolderMetadata({ metadata: event.data.name })

startTransition(() => {
setItems(prev => prev.map(item => (item.uuid === event.data.uuid ? { ...item, name: metadata.name } : item)))
})
} else if (event.type === "fileRestore") {
if (location.includes("trash")) {
startTransition(() => {
setItems(prev => prev.filter(i => i.uuid !== event.data.uuid))
})
}

if (event.data.parent !== parent) {
return
}

const metadata = await worker.decryptFileMetadata({ metadata: event.data.metadata })

startTransition(() => {
setItems(prev => [
...prev.filter(i => i.uuid !== event.data.uuid),
{
type: "file",
uuid: event.data.uuid,
timestamp: convertTimestampToMs(event.data.timestamp),
lastModified: metadata.lastModified,
creation: metadata.creation,
hash: metadata.hash,
name: metadata.name,
key: metadata.key,
mime: metadata.mime,
size: metadata.size,
parent: event.data.parent,
chunks: event.data.chunks,
sharerId: currentSharerId,
sharerEmail: currentSharerEmail,
receiverEmail: currentReceiverEmail,
receiverId: currentReceiverId,
receivers: currentReceivers,
favorited: event.data.favorited === 1,
rm: event.data.rm,
region: event.data.region,
bucket: event.data.bucket,
version: event.data.version as FileEncryptionVersion,
selected: false
}
])
})
} else if (event.type === "folderRestore") {
if (location.includes("trash")) {
startTransition(() => {
setItems(prev => prev.filter(i => i.uuid !== event.data.uuid))
})
}

if (event.data.parent !== parent) {
return
}

const metadata = await worker.decryptFolderMetadata({ metadata: event.data.name })

startTransition(() => {
setItems(prev => [
...prev.filter(i => i.uuid !== event.data.uuid),
{
type: "directory",
uuid: event.data.uuid,
timestamp: convertTimestampToMs(event.data.timestamp),
lastModified: convertTimestampToMs(event.data.timestamp),
name: metadata.name,
size: 0,
color: null,
parent: event.data.parent,
sharerId: currentSharerId,
sharerEmail: currentSharerEmail,
receiverEmail: currentReceiverEmail,
receiverId: currentReceiverId,
receivers: currentReceivers,
favorited: event.data.favorited === 1,
selected: false
}
])
})
} else if (event.type === "fileArchiveRestored") {
if (event.data.parent !== parent) {
return
}

const metadata = await worker.decryptFileMetadata({ metadata: event.data.metadata })

startTransition(() => {
setItems(prev => [
...prev.filter(
i =>
i.uuid !== event.data.currentUUID &&
i.name.toLowerCase() !== metadata.name.toLowerCase() &&
i.uuid !== event.data.uuid
),
{
type: "file",
uuid: event.data.uuid,
timestamp: convertTimestampToMs(event.data.timestamp),
lastModified: metadata.lastModified,
creation: metadata.creation,
hash: metadata.hash,
name: metadata.name,
key: metadata.key,
mime: metadata.mime,
size: metadata.size,
parent: event.data.parent,
chunks: event.data.chunks,
sharerId: currentSharerId,
sharerEmail: currentSharerEmail,
receiverEmail: currentReceiverEmail,
receiverId: currentReceiverId,
receivers: currentReceivers,
favorited: event.data.favorited === 1,
rm: event.data.rm,
region: event.data.region,
bucket: event.data.bucket,
version: event.data.version as FileEncryptionVersion,
selected: false
}
])
})
} else if (event.type === "folderColorChanged") {
startTransition(() => {
setItems(prev => prev.map(item => (item.uuid === event.data.uuid ? { ...item, color: event.data.color } : item)))
})
}
} catch (e) {
console.error(e)
}
},
[setItems, location, query, parent, currentReceiverEmail, currentReceiverId, currentReceivers, currentSharerEmail, currentSharerId]
)

useEffect(() => {
if (query.isSuccess && queryUpdatedAtRef.current !== query.dataUpdatedAt) {
queryUpdatedAtRef.current = query.dataUpdatedAt
Expand All @@ -70,6 +306,14 @@ export const List = memo(() => {
}
}, [location, query, setItems])

useEffect(() => {
socket.addListener("socketEvent", socketEventListener)

return () => {
socket.removeListener("socketEvent", socketEventListener)
}
}, [socketEventListener])

return listType[parent] === "grid" ? (
<GridList
items={itemsFiltered}
Expand Down
5 changes: 2 additions & 3 deletions src/components/drive/list/item/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,6 @@ import { Heart } from "lucide-react"
import useMountedEffect from "@/hooks/useMountedEffect"
import { type CloudItemReceiver } from "@filen/sdk/dist/types/cloud"
import { THUMBNAIL_MAX_FETCH_SIZE } from "@/constants"
import { type DirColors } from "@filen/sdk/dist/types/api/v3/dir/color"

let draggedItems: DriveCloudItem[] = []

Expand Down Expand Up @@ -307,7 +306,7 @@ export const ListItem = memo(
<ColoredFolderSVGIcon
width="1.75rem"
height="1.75rem"
color={item.color as unknown as DirColors}
color={item.color}
/>
) : (
<img
Expand Down Expand Up @@ -361,7 +360,7 @@ export const ListItem = memo(
<ColoredFolderSVGIcon
width="4rem"
height="4rem"
color={item.color as unknown as DirColors}
color={item.color}
/>
) : (
<img
Expand Down
8 changes: 8 additions & 0 deletions src/lib/worker/worker.ts
Original file line number Diff line number Diff line change
Expand Up @@ -1642,3 +1642,11 @@ export async function deleteChatConversation({ conversation }: { conversation: s
export async function chatRemoveParticipant({ conversation, userId }: { conversation: string; userId: number }): Promise<void> {
return await SDK.chats().removeParticipant({ conversation, userId })
}

export async function decryptFileMetadata({ metadata }: { metadata: string }): Promise<FileMetadata> {
return await SDK.crypto().decrypt().fileMetadata({ metadata })
}

export async function decryptFolderMetadata({ metadata }: { metadata: string }): Promise<FolderMetadata> {
return await SDK.crypto().decrypt().folderMetadata({ metadata })
}

0 comments on commit c6897e9

Please sign in to comment.