tor-browser

The Tor Browser
git clone https://git.dasho.dev/tor-browser.git
Log | Files | Refs | README | LICENSE

collecting-file-system-observer.js (5013B)


      1 // Wraps a FileSystemObserver to collect its records until it stops receiving
      2 // them.
      3 //
      4 // To collect records, it sets up a directory to observe and periodically create
      5 // files in it. If no new changes occur (outside of these file creations)
      6 // between two file changes, then it resolves the promise returned by
      7 // getRecords() with the records it collected.
      8 class CollectingFileSystemObserver {
      9  #observer = new FileSystemObserver(this.#collectRecordsCallback.bind(this));
     10  #notificationObserver =
     11      new FileSystemObserver(this.#notificationCallback.bind(this));
     12 
     13  #callback;
     14 
     15  #records_promise_and_resolvers = Promise.withResolvers();
     16  #collected_records = [];
     17 
     18  #notification_dir_handle;
     19  #notification_file_count = 0;
     20  #received_changes_since_last_notification = true;
     21 
     22  constructor(test, root_dir, callback) {
     23    test.add_cleanup(() => {
     24      this.disconnect();
     25      this.#notificationObserver.disconnect();
     26    });
     27 
     28    this.#setupCollectNotification(root_dir);
     29    this.#callback = callback ?? (() => {return {}});
     30  }
     31 
     32  #getCollectNotificationName() {
     33    return `notification_file_${this.#notification_file_count}`;
     34  }
     35 
     36  async #setupCollectNotification(root_dir) {
     37    this.#notification_dir_handle =
     38        await root_dir.getDirectoryHandle(getUniqueName(), {create: true});
     39    await this.#notificationObserver.observe(this.#notification_dir_handle);
     40    await this.#createCollectNotification();
     41  }
     42 
     43  #createCollectNotification() {
     44    this.#notification_file_count++;
     45    return this.#notification_dir_handle.getFileHandle(
     46        this.#getCollectNotificationName(), {create: true});
     47  }
     48 
     49  #finishCollectingIfReady() {
     50    // `records` contains the notification for collecting records. Determine
     51    // if we should finish collecting or create the next notification.
     52    if (this.#received_changes_since_last_notification) {
     53      this.#received_changes_since_last_notification = false;
     54      this.#createCollectNotification();
     55    } else {
     56      this.#records_promise_and_resolvers.resolve(this.#collected_records);
     57    }
     58  }
     59 
     60  #notificationCallback(records) {
     61    this.#finishCollectingIfReady(records);
     62  }
     63 
     64  #collectRecordsCallback(records, observer) {
     65    this.#collected_records.push({
     66      ...this.#callback(records, observer),
     67      records,
     68    });
     69 
     70    this.#received_changes_since_last_notification = true;
     71  }
     72 
     73  async getRecords() {
     74    return (await this.#records_promise_and_resolvers.promise)
     75        .map(record => record.records)
     76        .flat();
     77  }
     78 
     79  getRecordsWithCallbackInfo() {
     80    return this.#records_promise_and_resolvers.promise;
     81  }
     82 
     83  observe(handles, options) {
     84    return Promise.all(
     85        handles.map(handle => this.#observer.observe(handle, options)));
     86  }
     87 
     88  disconnect() {
     89    this.#observer.disconnect();
     90  }
     91 }
     92 
     93 async function assert_records_equal(root, actual, expected) {
     94  assert_equals(
     95      actual.length, expected.length,
     96      'Received an unexpected number of events');
     97 
     98  for (let i = 0; i < actual.length; i++) {
     99    const actual_record = actual[i];
    100    const expected_record = expected[i];
    101 
    102    assert_equals(
    103        actual_record.type, expected_record.type,
    104        'A record\'s type didn\'t match the expected type');
    105 
    106    assert_array_equals(
    107        actual_record.relativePathComponents,
    108        expected_record.relativePathComponents,
    109        'A record\'s relativePathComponents didn\'t match the expected relativePathComponents');
    110 
    111    if (expected_record.relativePathMovedFrom) {
    112      assert_array_equals(
    113          actual_record.relativePathMovedFrom,
    114          expected_record.relativePathMovedFrom,
    115          'A record\'s relativePathMovedFrom didn\'t match the expected relativePathMovedFrom');
    116    } else {
    117      assert_equals(
    118          actual_record.relativePathMovedFrom, null,
    119          'A record\'s relativePathMovedFrom was set when it shouldn\'t be');
    120    }
    121 
    122    if (expected_record.changedHandle) {
    123      assert_true(
    124          await actual_record.changedHandle.isSameEntry(
    125              expected_record.changedHandle),
    126          'A record\'s changedHandle didn\'t match the expected changedHandle');
    127    } else {
    128      assert_equals(
    129          actual_record.changedHandle, null,
    130          'A record\'s changedHandle was set when it shouldn\'t be');
    131    }
    132 
    133    assert_true(
    134        await actual_record.root.isSameEntry(root),
    135        'A record\'s root didn\'t match the expected root');
    136  }
    137 }
    138 
    139 function modifiedEvent(changedHandle, relativePathComponents) {
    140  return {type: 'modified', changedHandle, relativePathComponents};
    141 }
    142 
    143 function appearedEvent(changedHandle, relativePathComponents) {
    144  return {type: 'appeared', changedHandle, relativePathComponents};
    145 }
    146 
    147 function disappearedEvent(relativePathComponents) {
    148  return {type: 'disappeared', changedHandle: null, relativePathComponents};
    149 }
    150 
    151 function movedEvent(
    152    changedHandle, relativePathComponents, relativePathMovedFrom) {
    153  return {
    154    type: 'moved',
    155    changedHandle,
    156    relativePathComponents,
    157    relativePathMovedFrom
    158  };
    159 }