Files
jitsi-meet/react/features/file-sharing/reducer.ts
Saúl Ibarra Corretgé 26423f8e76 fix(file-sharing) rework sync
Rework sync so uploading multiple files at once or several moderators
uploading files simultaneously doesn't break synchronization.

The current room metadata plugin operates on <key,value> pairs and we
were using a generic "files" key and using a nested object as our value.
Since with every operation the entire object is replaced it's easy to
get out of sync because one needs to be sure to have the full state
before overwriting it.

This is not realistic.

We'll look into making the metadata plugin more flexible in order to
support add / delete operations also on nested objects, but for the time
being the following will suffice:

Use a key prefix, so each file has en entry in the room metadata, like
so: "files.<the file ID> -> file metadata". This means that when a file
is deleted we just empty the metadata. The metadata plugin doesn't
currently support removing existing keys.
2025-05-22 12:18:12 +02:00

97 lines
2.3 KiB
TypeScript

import { isEqual } from 'lodash-es';
import { UPDATE_CONFERENCE_METADATA } from '../base/conference/actionTypes';
import ReducerRegistry from '../base/redux/ReducerRegistry';
import {
ADD_FILE,
REMOVE_FILE,
UPDATE_FILE_UPLOAD_PROGRESS
} from './actionTypes';
import { FILE_SHARING_PREFIX } from './constants';
import { IFileMetadata } from './types';
export interface IFileSharingState {
files: Map<string, IFileMetadata>;
}
const DEFAULT_STATE = {
files: new Map<string, IFileMetadata>()
};
ReducerRegistry.register<IFileSharingState>('features/file-sharing',
(state = DEFAULT_STATE, action): IFileSharingState => {
switch (action.type) {
case ADD_FILE: {
const newFiles = new Map(state.files);
newFiles.set(action.file.fileId, action.file);
return {
files: newFiles
};
}
case REMOVE_FILE: {
const newFiles = new Map(state.files);
newFiles.delete(action.fileId);
return {
files: newFiles
};
}
case UPDATE_FILE_UPLOAD_PROGRESS: {
const newFiles = new Map(state.files);
const file = newFiles.get(action.fileId);
if (file) {
newFiles.set(action.fileId, { ...file, progress: action.progress });
}
return {
files: newFiles
};
}
case UPDATE_CONFERENCE_METADATA: {
const { metadata } = action;
const files = new Map();
for (const [ key, value ] of Object.entries(metadata)) {
if (key.startsWith(FILE_SHARING_PREFIX)) {
const fileId = key.substring(FILE_SHARING_PREFIX.length + 1);
files.set(fileId, value);
}
}
if (files.size === 0) {
return state;
}
const newFiles: Map<string, IFileMetadata> = new Map(state.files);
for (const [ key, value ] of files) {
// Deleted files will not have fileId.
if (!value.fileId) {
newFiles.delete(key);
} else {
newFiles.set(key, value);
}
}
if (isEqual(newFiles, state.files)) {
return state;
}
return {
files: newFiles
};
}
default:
return state;
}
});