Skip to content

Commit 770a399

Browse files
author
doubleface
committed
feat: Add support for shared drive files search
1 parent e656dba commit 770a399

File tree

8 files changed

+189
-37
lines changed

8 files changed

+189
-37
lines changed

package.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -22,7 +22,7 @@
2222
"cozy-logger": "^1.10.4",
2323
"cozy-minilog": "^3.3.1",
2424
"cozy-pouch-link": "^60.10.1",
25-
"cozy-realtime": "^5.7.0",
25+
"cozy-realtime": "^5.8.0",
2626
"cozy-tsconfig": "^1.2.0",
2727
"flexsearch": "^0.7.43",
2828
"lodash": "^4.17.21",

src/@types/cozy-client.d.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -165,6 +165,7 @@ declare module 'cozy-client' {
165165
addReferencesTo: (references: object, dirs: object[]) => Promise<void>
166166
// eslint-disable-next-line @typescript-eslint/no-explicit-any
167167
launch: (trigger: any) => any
168+
fetchSharedDrives: () => Promise<{ data: Array<{ id: string }> }>
168169
}
169170

170171
export default class CozyClient {

src/consts.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,5 +8,6 @@ export const ACCOUNTS_DOCTYPE = 'io.cozy.accounts'
88
export const KONNECTORS_DOCTYPE = 'io.cozy.konnectors'
99
export const TRIGGERS_DOCTYPE = 'io.cozy.triggers'
1010
export const SETTINGS_DOCTYPE = 'io.cozy.settings'
11+
export const SHARED_DRIVE_FILE_DOCTYPE = 'io.cozy.files.shareddrives'
1112

1213
export const LOCALSTORAGE_KEY_DELETING_DATA = 'deletingLocalData'

src/dataproxy/common/DataProxyInterface.ts

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -13,13 +13,18 @@ export interface SearchOptions {
1313

1414
export interface DataProxyWorker {
1515
search: (query: string, options: SearchOptions) => unknown
16-
setup: (clientData: ClientData) => Promise<void>
16+
setup: (
17+
clientData: ClientData,
18+
options?: { sharedDriveIds: string[] }
19+
) => Promise<void>
1720
forceSyncPouch: () => void
1821
requestLink: (
1922
definition: QueryDefinition | Mutation,
2023
options?: QueryOptions | MutationOptions
2124
) => Promise<unknown>
2225
reconnectRealtime: () => void
26+
addSharedDrive: (driveId: string) => void
27+
removeSharedDrive: (driveId: string) => void
2328
}
2429

2530
export interface DataProxyWorkerPartialState {

src/dataproxy/worker/SharedWorkerProvider.tsx

Lines changed: 110 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,47 @@ import { removeStaleLocalData } from '@/dataproxy/worker/data'
1414

1515
const log = Minilog('👷‍♂️ [SharedWorkerProvider]')
1616

17+
// Event payload received from cozy realtime for io.cozy.files
18+
type FilesRealtimeEvent = {
19+
dir_id: string
20+
class: string
21+
referenced_by?: Array<{ id?: string }>
22+
}
23+
24+
function isFilesRealtimeEvent(value: unknown): value is FilesRealtimeEvent {
25+
if (!value || typeof value !== 'object') return false
26+
const v = value as Record<string, unknown>
27+
return (
28+
typeof v.dir_id === 'string' &&
29+
typeof v.class === 'string' &&
30+
(v.referenced_by === undefined || Array.isArray(v.referenced_by))
31+
)
32+
}
33+
34+
interface Realtime {
35+
subscribe: (
36+
event: 'created' | 'deleted' | 'updated',
37+
doctype: string,
38+
handler: (event: unknown) => void
39+
) => void
40+
unsubscribe: (
41+
event: 'created' | 'deleted' | 'updated',
42+
doctype: string,
43+
handler: (event: unknown) => void
44+
) => void
45+
}
46+
47+
function hasRealtimePlugin(
48+
plugins: unknown
49+
): plugins is { realtime: Realtime } {
50+
return (
51+
!!plugins &&
52+
typeof plugins === 'object' &&
53+
'realtime' in (plugins as Record<string, unknown>) &&
54+
typeof (plugins as { realtime?: unknown }).realtime === 'object'
55+
)
56+
}
57+
1758
export const SharedWorkerContext = React.createContext<
1859
DataProxyWorkerContext | undefined
1960
>(undefined)
@@ -41,13 +82,71 @@ export const SharedWorkerProvider = React.memo(
4182
const [tabCount, setTabCount] = useState(0)
4283
const client = useClient()
4384

85+
useEffect(() => {
86+
if (!client) return
87+
88+
if (!hasRealtimePlugin(client.plugins)) {
89+
throw new Error(
90+
'You must include the realtime plugin to use RealTimeQueries'
91+
)
92+
}
93+
const realtime = client.plugins.realtime
94+
95+
if (!realtime) {
96+
throw new Error(
97+
'You must include the realtime plugin to use RealTimeQueries'
98+
)
99+
}
100+
101+
const handleFileCreated = (event: unknown): void => {
102+
if (!isFilesRealtimeEvent(event)) return
103+
if (
104+
event.dir_id === 'io.cozy.files.shared-drives-dir' &&
105+
event.class === 'shortcut'
106+
) {
107+
const driveId = event?.referenced_by?.[0]?.id
108+
if (driveId) {
109+
log.info(`Shared drive ${driveId} created`)
110+
worker?.addSharedDrive(driveId)
111+
}
112+
}
113+
}
114+
const handleFileDeleted = (event: unknown): void => {
115+
if (!isFilesRealtimeEvent(event)) return
116+
if (
117+
event.dir_id === 'io.cozy.files.shared-drives-dir' &&
118+
event.class === 'shortcut'
119+
) {
120+
const driveId = event?.referenced_by?.[0]?.id
121+
if (driveId) {
122+
log.info(`Shared drive ${driveId} deleted`)
123+
worker?.removeSharedDrive(driveId)
124+
}
125+
}
126+
}
127+
realtime.subscribe('created', 'io.cozy.files', handleFileCreated)
128+
realtime.subscribe('deleted', 'io.cozy.files', handleFileDeleted)
129+
return (): void => {
130+
realtime.unsubscribe('created', 'io.cozy.files', handleFileCreated)
131+
realtime.unsubscribe('deleted', 'io.cozy.files', handleFileDeleted)
132+
}
133+
}, [client, worker])
134+
44135
useEffect(() => {
45136
if (!client) return
46137

47138
const doAsync = async (): Promise<void> => {
48139
// Cleanup any remaining local data
49140
await removeStaleLocalData()
50141

142+
const { data: sharedDrives } = await client
143+
.collection('io.cozy.sharings')
144+
.fetchSharedDrives()
145+
146+
const sharedDriveIds: string[] = sharedDrives.map(
147+
(drive: { id: string }) => drive.id
148+
)
149+
51150
log.debug('Init SharedWorker')
52151

53152
let obj: Comlink.Remote<DataProxyWorker>
@@ -78,14 +177,17 @@ export const SharedWorkerProvider = React.memo(
78177
log.debug('Provide CozyClient data to Worker')
79178
const { uri, token } = client.getStackClient()
80179

81-
await obj.setup({
82-
uri,
83-
token: token.token,
84-
instanceOptions: client.instanceOptions,
85-
capabilities: client.capabilities,
86-
useRemoteData
87-
})
88-
setWorker(() => obj)
180+
await obj.setup(
181+
{
182+
uri,
183+
token: token.token,
184+
instanceOptions: client.instanceOptions,
185+
capabilities: client.capabilities,
186+
useRemoteData
187+
},
188+
{ sharedDriveIds } as { sharedDriveIds: string[] }
189+
)
190+
setWorker((): Comlink.Remote<DataProxyWorker> => obj)
89191
}
90192

91193
doAsync()

src/dataproxy/worker/worker.ts

Lines changed: 64 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,7 @@ import {
1919
KONNECTORS_DOCTYPE,
2020
TRIGGERS_DOCTYPE,
2121
SETTINGS_DOCTYPE,
22+
SHARED_DRIVE_FILE_DOCTYPE,
2223
REPLICATION_DEBOUNCE,
2324
REPLICATION_DEBOUNCE_MAX_DELAY
2425
} from '@/consts'
@@ -45,7 +46,12 @@ let searchEngine: SearchEngine
4546
const broadcastChannel = new BroadcastChannel('DATA_PROXY_BROADCAST_CHANANEL')
4647

4748
const dataProxy: DataProxyWorker = {
48-
setup: async (clientData: ClientData) => {
49+
setup: async (
50+
clientData: ClientData,
51+
options: { sharedDriveIds: string[] } = { sharedDriveIds: [] } as {
52+
sharedDriveIds: string[]
53+
}
54+
) => {
4955
log.debug('Received data for setting up client')
5056
if (client) return
5157
updateState()
@@ -71,28 +77,23 @@ const dataProxy: DataProxyWorker = {
7177
adapter: 'indexeddb'
7278
}
7379
},
74-
doctypesReplicationOptions: {
75-
[FILES_DOCTYPE]: {
76-
strategy: 'fromRemote'
77-
},
78-
[CONTACTS_DOCTYPE]: {
79-
strategy: 'fromRemote'
80-
},
81-
[APPS_DOCTYPE]: {
82-
strategy: 'fromRemote'
83-
},
84-
[ACCOUNTS_DOCTYPE]: {
85-
strategy: 'fromRemote'
86-
},
87-
[KONNECTORS_DOCTYPE]: {
88-
strategy: 'fromRemote'
89-
},
90-
[TRIGGERS_DOCTYPE]: {
91-
strategy: 'fromRemote'
92-
},
93-
[SETTINGS_DOCTYPE]: {
94-
strategy: 'fromRemote'
95-
}
80+
doctypesReplicationOptions: {}
81+
}
82+
83+
if (options?.sharedDriveIds?.length > 0) {
84+
pouchLinkOptions.doctypes.push(
85+
...options?.sharedDriveIds?.map(
86+
id => `${SHARED_DRIVE_FILE_DOCTYPE}-${id}`
87+
)
88+
)
89+
pouchLinkOptions.doctypesReplicationOptions = {
90+
...pouchLinkOptions.doctypesReplicationOptions,
91+
...Object.fromEntries(
92+
options?.sharedDriveIds?.map(id => [
93+
`${SHARED_DRIVE_FILE_DOCTYPE}-${id}`,
94+
{ strategy: 'fromRemote', driveId: id }
95+
])
96+
)
9697
}
9798
}
9899

@@ -185,6 +186,46 @@ const dataProxy: DataProxyWorker = {
185186
dataProxy.forceSyncPouch()
186187
searchEngine.init() // Init again to refresh indexes
187188
}
189+
},
190+
191+
addSharedDrive: async (driveId: string) => {
192+
log.debug(`Worker: Adding shared drive ${driveId}`)
193+
if (!client) {
194+
throw new Error('Client is required to add a shared drive')
195+
}
196+
const pouchLink = getPouchLink(client)
197+
if (pouchLink) {
198+
const doctype = `${SHARED_DRIVE_FILE_DOCTYPE}-${driveId}`
199+
const options = { strategy: 'fromRemote', driveId }
200+
if (pouchLink.addDoctype) {
201+
pouchLink.addDoctype(doctype, options)
202+
pouchLink.startReplicationWithDebounce()
203+
}
204+
}
205+
206+
if (!searchEngine) {
207+
throw new Error('SearchEngine is not initialized')
208+
}
209+
210+
searchEngine.addSharedDrive(driveId)
211+
},
212+
213+
removeSharedDrive: (driveId: string) => {
214+
log.debug(`Worker: Removing shared drive ${driveId}`)
215+
if (!client) {
216+
throw new Error('Client is required to remove a shared drive')
217+
}
218+
const pouchLink = getPouchLink(client)
219+
if (pouchLink) {
220+
const doctype = `${SHARED_DRIVE_FILE_DOCTYPE}-${driveId}`
221+
pouchLink.removeDoctype(doctype)
222+
}
223+
224+
if (!searchEngine) {
225+
throw new Error('SearchEngine is not initialized')
226+
}
227+
228+
searchEngine.removeSharedDrive(driveId)
188229
}
189230
}
190231

src/targets/browser/setupApp.ts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@ import { createRoot } from 'react-dom/client'
33

44
import CozyClient from 'cozy-client'
55
import flag from 'cozy-flags'
6+
import { RealtimePlugin } from 'cozy-realtime'
67

78
import schema from '@/doctypes'
89

@@ -59,6 +60,7 @@ export const setupApp = memoize(() => {
5960
const root = createRoot(container)
6061
const client = makeClient(container)
6162
client.registerPlugin(flag.plugin, null)
63+
client.registerPlugin(RealtimePlugin)
6264

6365
return { root, client }
6466
})

yarn.lock

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -2385,10 +2385,10 @@ cozy-pouch-link@^60.10.1:
23852385
pouchdb-browser "^7.2.2"
23862386
pouchdb-find "^7.2.2"
23872387

2388-
cozy-realtime@^5.7.0:
2389-
version "5.7.0"
2390-
resolved "https://registry.yarnpkg.com/cozy-realtime/-/cozy-realtime-5.7.0.tgz#df3bf3124d8291fd2bb0ec2dd69addd8ec0b7a63"
2391-
integrity sha512-v1iev1qydt8jgWpjdmI2XsDMghzQvQESi4po6jnW3IISJzLKffJwKxySKtz2OSngRZ7l2wEznLTgALv2sL8y4A==
2388+
cozy-realtime@^5.8.0:
2389+
version "5.8.0"
2390+
resolved "https://registry.yarnpkg.com/cozy-realtime/-/cozy-realtime-5.8.0.tgz#88af55ce680da4e263b9514e20062fb5097a4c11"
2391+
integrity sha512-B/308fMd+RFKj4VBsefqmaCnbata+ToJxCXAqMaD9iYbkW1sE7BoLN7CqyU9YPrQUZsZAqZ2/7rc30ugh4zaGw==
23922392
dependencies:
23932393
"@cozy/minilog" "^1.0.0"
23942394

0 commit comments

Comments
 (0)