addons/isl-server/src/edenFsNotifications.tsblame
View source
b69ab311/**
b69ab312 * Copyright (c) Meta Platforms, Inc. and affiliates.
b69ab313 *
b69ab314 * This source code is licensed under the MIT license found in the
b69ab315 * LICENSE file in the root directory of this source tree.
b69ab316 */
b69ab317
b69ab318import type {Logger} from './logger';
b69ab319
b69ab3110import path from 'node:path';
b69ab3111// Import from the generated files
b69ab3112import {
b69ab3113 EdenFSNotificationsClient,
b69ab3114 type EdenFSSubscription,
b69ab3115 type SubscriptionCallback,
b69ab3116 type SubscriptionOptions,
b69ab3117} from './__generated__/node-edenfs-notifications-client/index.js';
b69ab3118
b69ab3119export type EdenFSSubscriptionManager = {
b69ab3120 path: string;
b69ab3121 name: string;
b69ab3122 subscriptionCount: number;
b69ab3123 internalSubscription: EdenFSSubscription;
b69ab3124};
b69ab3125
b69ab3126/**
b69ab3127 * Provides directory watching functionality using EdenFS notify API.
b69ab3128 */
b69ab3129export class EdenFSNotifications {
b69ab3130 private client: EdenFSNotificationsClient;
b69ab3131 private subscriptions: Map<string, EdenFSSubscriptionManager> = new Map();
b69ab3132
b69ab3133 constructor(
b69ab3134 private logger: Logger,
b69ab3135 private mountPoint: string,
b69ab3136 ) {
b69ab3137 this.client = new EdenFSNotificationsClient({
b69ab3138 mountPoint: this.mountPoint,
b69ab3139 timeout: 30000,
b69ab3140 edenBinaryPath: process.env.EDEN_PATH ? process.env.EDEN_PATH : 'eden',
b69ab3141 });
b69ab3142 }
b69ab3143
b69ab3144 /**
b69ab3145 * Watch a directory recursively for changes using EdenFS notifications.
b69ab3146 */
b69ab3147 public async watchDirectoryRecursive(
b69ab3148 localDirectoryPath: string,
b69ab3149 rawSubscriptionName: string,
b69ab3150 subscriptionOptions: SubscriptionOptions,
b69ab3151 callback: SubscriptionCallback,
b69ab3152 ): Promise<EdenFSSubscription> {
b69ab3153 // Subscriptions should be unique by name and by folder
b69ab3154 const subscriptionName = this.fixupName(localDirectoryPath, rawSubscriptionName);
b69ab3155 const existingSubscription = this.getSubscription(subscriptionName);
b69ab3156
b69ab3157 if (existingSubscription) {
b69ab3158 existingSubscription.subscriptionCount++;
b69ab3159 return existingSubscription.internalSubscription;
b69ab3160 }
b69ab3161
b69ab3162 const internalSubscription = this.client.subscribe(subscriptionOptions, callback);
b69ab3163
b69ab3164 const subscription: EdenFSSubscriptionManager = {
b69ab3165 path: localDirectoryPath,
b69ab3166 name: subscriptionName,
b69ab3167 subscriptionCount: 1,
b69ab3168 internalSubscription,
b69ab3169 };
b69ab3170
b69ab3171 this.setSubscription(subscriptionName, subscription);
b69ab3172 await internalSubscription.start();
b69ab3173 this.logger.log(`edenfs subscription started: ${subscriptionName}`);
b69ab3174 return internalSubscription;
b69ab3175 }
b69ab3176
b69ab3177 private getSubscription(entryPath: string): EdenFSSubscriptionManager | undefined {
b69ab3178 return this.subscriptions.get(path.normalize(entryPath));
b69ab3179 }
b69ab3180
b69ab3181 private setSubscription(entryPath: string, subscription: EdenFSSubscriptionManager): void {
b69ab3182 const key = path.normalize(entryPath);
b69ab3183 this.subscriptions.set(key, subscription);
b69ab3184 }
b69ab3185
b69ab3186 private deleteSubscription(entryPath: string): void {
b69ab3187 const key = path.normalize(entryPath);
b69ab3188 this.subscriptions.delete(key);
b69ab3189 }
b69ab3190
b69ab3191 private fixupName(path: string, name: string): string {
b69ab3192 const refinedPath = path.replace(/\\/g, '-').replace(/\//g, '-');
b69ab3193 return `${refinedPath}-${name}`;
b69ab3194 }
b69ab3195
b69ab3196 /**
b69ab3197 * Remove a subscription (unwatch)
b69ab3198 */
b69ab3199 public unwatch(path: string, name: string) {
b69ab31100 const subscriptionName = this.fixupName(path, name);
b69ab31101 const subscription = this.getSubscription(subscriptionName);
b69ab31102
b69ab31103 if (subscription == null) {
b69ab31104 this.logger.error(`No watcher entity found with path [${path}] name [${name}]`);
b69ab31105 return;
b69ab31106 }
b69ab31107
b69ab31108 if (--subscription.subscriptionCount === 0) {
b69ab31109 subscription.internalSubscription.stop();
b69ab31110 this.deleteSubscription(subscriptionName);
b69ab31111 this.logger.log(`edenfs subscription destroyed: ${subscriptionName}`);
b69ab31112 }
b69ab31113 }
b69ab31114}