Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[MS] Use WorkspaceWatchedEntryChanged to refresh the files list instead of a fixed timer #7610

Merged
merged 1 commit into from
Jul 9, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
10 changes: 9 additions & 1 deletion client/src/parsec/file.ts
Original file line number Diff line number Diff line change
Expand Up @@ -165,7 +165,15 @@ export async function statFolderChildren(
path: FsPath,
): Promise<Result<Array<EntryStat>, WorkspaceStatFolderChildrenError>> {
if (!needsMocks()) {
const result = await libparsec.workspaceStatFolderChildren(workspaceHandle, path);
const watchResult = await libparsec.workspaceWatchEntryOneshot(workspaceHandle, path);

let result;
if (!watchResult.ok) {
result = await libparsec.workspaceStatFolderChildren(workspaceHandle, path);
} else {
result = await libparsec.workspaceStatFolderChildrenById(workspaceHandle, watchResult.value);
}

if (!result.ok) {
return result;
}
Expand Down
24 changes: 12 additions & 12 deletions client/src/parsec/login.ts
Original file line number Diff line number Diff line change
Expand Up @@ -41,8 +41,6 @@ import { DateTime } from 'luxon';
export interface LoggedInDeviceInfo {
handle: ConnectionHandle;
device: AvailableDevice;
// Used to simulate update events, remove when we have real events
intervalId: any;
}

const loggedInDevices: Array<LoggedInDeviceInfo> = [];
Expand Down Expand Up @@ -159,6 +157,15 @@ export async function login(
case ClientEventTag.WorkspacesSelfListChanged:
eventDistributor.dispatchEvent(Events.WorkspaceUpdated);
break;
case ClientEventTag.WorkspaceWatchedEntryChanged:
eventDistributor.dispatchEvent(Events.EntryUpdated, undefined, 300);
break;
case ClientEventTag.WorkspaceOpsInboundSyncDone:
eventDistributor.dispatchEvent(Events.EntrySynced, { workspaceId: event.realmId, entryId: event.entryId });
break;
case ClientEventTag.WorkspaceOpsOutboundSyncDone:
eventDistributor.dispatchEvent(Events.EntrySynced, { workspaceId: event.realmId, entryId: event.entryId });
break;
default:
window.electronAPI.log('debug', `Unhandled event ${event.tag}`);
break;
Expand All @@ -177,19 +184,15 @@ export async function login(
const clientConfig = getClientConfig();
const result = await libparsec.clientStart(clientConfig, callback, accessStrategy);
if (result.ok) {
// Simulate an update event every 10s to force a refresh
const intervalId = setInterval(() => {
eventDistributor.dispatchEvent(Events.EntryUpdated);
}, 10000);
loggedInDevices.push({ handle: result.value, device: device, intervalId: intervalId });
loggedInDevices.push({ handle: result.value, device: device });
}
return result;
} else {
if (
accessStrategy.tag === DeviceAccessStrategyTag.Password &&
['P@ssw0rd.', 'AVeryL0ngP@ssw0rd'].includes((accessStrategy as DeviceAccessStrategyPassword).password)
) {
loggedInDevices.push({ handle: DEFAULT_HANDLE, device: device, intervalId: null });
loggedInDevices.push({ handle: DEFAULT_HANDLE, device: device });
return { ok: true, value: DEFAULT_HANDLE };
}
return {
Expand All @@ -212,10 +215,7 @@ export async function logout(handle?: ConnectionHandle | undefined | null): Prom
if (result.ok) {
const index = loggedInDevices.findIndex((info) => info.handle === handle);
if (index !== -1) {
const removed = loggedInDevices.splice(index, 1);
if (removed && removed.length > 0) {
clearInterval(removed[0].intervalId);
}
loggedInDevices.splice(index, 1);
}
}
return result;
Expand Down
49 changes: 42 additions & 7 deletions client/src/services/eventDistributor.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
// Parsec Cloud (https://parsec.cloud) Copyright (c) BUSL-1.1 2016-present Scille SAS

import { InvitationStatus, InvitationToken, WorkspaceID } from '@/parsec';
import { EntryID, InvitationStatus, InvitationToken, WorkspaceID } from '@/parsec';
import { v4 as uuid4 } from 'uuid';

export const EventDistributorKey = 'eventDistributor';
Expand All @@ -17,6 +17,7 @@ enum Events {
UpdateAvailability = 1 << 8,
WorkspaceUpdated = 1 << 9,
EntryUpdated = 1 << 10,
EntrySynced = 1 << 11,
}

interface WorkspaceCreatedData {
Expand All @@ -33,7 +34,12 @@ interface UpdateAvailabilityData {
version?: string;
}

type EventData = WorkspaceCreatedData | InvitationUpdatedData | UpdateAvailabilityData;
interface EntrySyncedData {
workspaceId: WorkspaceID;
entryId: EntryID;
}

type EventData = WorkspaceCreatedData | InvitationUpdatedData | UpdateAvailabilityData | EntrySyncedData;

interface Callback {
id: string;
Expand All @@ -43,16 +49,45 @@ interface Callback {

class EventDistributor {
private callbacks: Array<Callback>;
private timeouts: Map<Events, number>;

constructor() {
this.callbacks = [];
this.timeouts = new Map<number, Events>();
}

async dispatchEvent(event: Events, data?: EventData): Promise<void> {
for (const cb of this.callbacks) {
if (event & cb.events) {
await cb.funct(event, data);
async dispatchEvent(event: Events, data?: EventData, aggregateTime?: number): Promise<void> {
async function sendToAll(callbacks: Array<Callback>, event: Events, data?: EventData): Promise<void> {
for (const cb of callbacks) {
if (event & cb.events) {
await cb.funct(event, data);
}
}
}

// In some cases, events can occur very close to each other, leading to some heavy operations.
// We can aggregate those cases in order to distribute only one event if multiple occur in a short
// time lapse.
if (aggregateTime !== undefined) {
if (data) {
// Can't have data with an aggregateTime, we wouldn't know what data to use
console.warn('Cannot have an aggregate time with data, ignoring this event.');
return;
}
// Clear previous interval if any
if (this.timeouts.has(event)) {
const interval = this.timeouts.get(event);
this.timeouts.delete(event);
window.clearInterval(interval);
}
// Create a new timeout
const interval = window.setTimeout(async () => {
await sendToAll(this.callbacks, event, undefined);
}, aggregateTime);
// Add it to the list
this.timeouts.set(event, interval);
} else {
await sendToAll(this.callbacks, event, data);
}
}

Expand All @@ -67,4 +102,4 @@ class EventDistributor {
}
}

export { EventData, EventDistributor, Events, InvitationUpdatedData, UpdateAvailabilityData, WorkspaceCreatedData };
export { EntrySyncedData, EventData, EventDistributor, Events, InvitationUpdatedData, UpdateAvailabilityData, WorkspaceCreatedData };
18 changes: 15 additions & 3 deletions client/src/views/files/FoldersPage.vue
Original file line number Diff line number Diff line change
Expand Up @@ -242,7 +242,7 @@ import FileDetailsModal from '@/views/files/FileDetailsModal.vue';
import { IonContent, IonPage, IonText, modalController, popoverController } from '@ionic/vue';
import { arrowRedo, copy, folderOpen, informationCircle, link, pencil, trashBin } from 'ionicons/icons';
import { Ref, computed, inject, onMounted, onUnmounted, ref } from 'vue';
import { EventData, EventDistributor, EventDistributorKey, Events } from '@/services/eventDistributor';
import { EntrySyncedData, EventData, EventDistributor, EventDistributorKey, Events } from '@/services/eventDistributor';

interface FoldersPageSavedData {
displayState?: DisplayState;
Expand Down Expand Up @@ -400,12 +400,24 @@ onMounted(async () => {
await defineShortcuts();

eventCbId = await eventDistributor.registerCallback(
Events.EntryUpdated | Events.WorkspaceUpdated,
async (event: Events, _data?: EventData) => {
Events.EntryUpdated | Events.WorkspaceUpdated | Events.EntrySynced,
async (event: Events, data?: EventData) => {
if (event === Events.EntryUpdated) {
await listFolder();
} else if (event === Events.WorkspaceUpdated && workspaceInfo.value) {
await updateWorkspaceInfo(workspaceInfo.value.id);
} else if (event === Events.EntrySynced) {
const syncedData = data as EntrySyncedData;
if (!workspaceInfo.value || workspaceInfo.value.id !== syncedData.workspaceId) {
return;
}
let entry: EntryModel | undefined = files.value.getEntries().find((e) => e.id === syncedData.entryId);
if (!entry) {
entry = folders.value.getEntries().find((e) => e.id === syncedData.entryId);
}
if (entry) {
entry.needSync = false;
}
}
},
);
Expand Down