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

fs events ops #1826

Closed
kitsonk opened this issue Feb 22, 2019 · 15 comments
Closed

fs events ops #1826

kitsonk opened this issue Feb 22, 2019 · 15 comments

Comments

@kitsonk
Copy link
Contributor

kitsonk commented Feb 22, 2019

There are several use cases where the ability to watch a file(s) for changes is an important API. For example, invalidating cache headers for serving static file based content, or processing logs or files, etc.

Many OSes provide a mechanism for this without having to "poll" the stats for the file. Node.js offers fs.watch().

Instead of an event listener though, it feels some sort of async generator would be more idiomatic to the rest of Deno.

@ry
Copy link
Member

ry commented Feb 22, 2019

Yes I would have in core. This looks pretty nice: https://github.com/passcod/notify

@ry
Copy link
Member

ry commented Mar 25, 2019

Here's a potential API, but I leave the exact spelling of these methods to the implementor

  const watcher = await Deno.watch("/home/test/notify", { recursive: true });
  while (true) {
    const event = await watcher.getEvent();
    console.log("event", event)
  }

Here's a brief sketch of how I would do this

  1. Add new ops to cli/msg.fbs
    1A. One to create a new watcher, maybe FSEventsOpen. This would return a new resource id (rid).
    1B. One to poll for a new event, FSEventsPoll. It would take an rid and return a new event.
    1C. Closing the rid does not need a new op. The existing Close op can be used.
  2. Add new handlers for the two new ops in ops.rs
    2A. Add a new Resource type in resources.rs, you can probably follow the example of tokio_process::Child
    2B. Add a new typescript frontend to the op, by adding js/fs_events.ts, follow the example in js/process.ts.
  3. Write some tests.

@balupton
Copy link
Contributor

balupton commented Apr 7, 2019

Having written watchr, I would encourage no built in watching apis, as watching is such a difficult problem. I think in nearly all cases people would be best served by facebook's watchman instead. Which does not use the node apis at all.

@sholladay
Copy link

Personally, I've frequently been annoyed at how file watching was a mess in Node, a situation that is still unresolved today. Yes, it's hard to implement correctly, but that just means we need more eyes on it. Also, a lot of projects need it as a very low-level building block. For context, chokidar has almost 11 million weekly downloads on npm, about as much as React and Express combined (i.e. it is among the most downloaded packages).

I would love to see this in core and exposed as an async iterable.

const watcher = Deno.watch('/home/test/notify', { recursive: true });
for await (const event of watcher) {
    // ...
}

The snippet above is using for-await-of. Node recently started to use this for streams and I've fallen in love with it. It's standard syntax, which aligns nicely with Deno's philosophy.

@jcao219
Copy link
Contributor

jcao219 commented Apr 27, 2019

I can try to see if I can make a prototype of @sholladay 's API into reality.

--

Quick update -- I haven't forgotten about this effort :) I've just been busy with moving to a new continent recently.

--

Getting back to work on this right away!

@Fenzland
Copy link
Contributor

Fenzland commented May 27, 2019

I'm waiting for this feature too. In some using cases, we need to listen to events with different types. Nesting several if or switches in the for is not so good. I have a plan to combine both event listener and for-await-of together:

const watcher = Deno.watch('/path', { recursive: true });
watcher instanceof Watcher;
watcher.addEventListener('add', onAdd);
watcher.addEventListener('modify', ()=> {});
watcher.addEventListener('chmod', ()=> {});
watcher.removeEventListener('add', onAdd);

watcher.close();

What's more, A close() API is necessary.

I'm become agree with zekth at denoland/std#386.
Using for-await-of is not a good idea. It runs serially. Your handler cannot handle the second event until the process of first event has done.

@MarkTiedemann
Copy link
Contributor

MarkTiedemann commented May 27, 2019

What's more, A close() API is necessary.

I think if we use async generators, then we don't need a close API, but can use the built-in return API (https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Generator/return).

let watcher = Deno.watch(/*...*/);
// ...

// some time later
await watcher.return(); // wait for watcher to close

EDIT: Here's an example:

async function* watch() {
  yield { type: "add", path: "/a" };
  yield { type: "delete", path: "/a" };
  yield { type: "add", path: "/b" };
}

async function main() {
  let watcher = watch();
  for await (let event of watcher) {
    switch (event.type) {
      case "add":
        console.log(`+ File '${event.path}' was added.`);
        break;
      case "delete":
        console.error(`- Error: File '${event.path}' was deleted. Aborting!`);
        await watcher.return();
        break;
    }
  }
}

main();
$ deno run watch.ts
+ File '/a' was added.
- Error: File '/a' was deleted. Aborting!

@Fenzland
Copy link
Contributor

Fenzland commented Jun 3, 2019

I think if we use async generators, then we don't need a close API, but can use the built-in return API

That's right. But now, I change my mind and not support for-await-of any more.

@ry ry changed the title API for watching files fs events ops Sep 14, 2019
@ry ry mentioned this issue Sep 14, 2019
43 tasks
@ry
Copy link
Member

ry commented Sep 14, 2019

Added to 1.0 blockers

@bartlomieju
Copy link
Member

@jcao219 I remember you were working on an implementation, any update?

@jcao219
Copy link
Contributor

jcao219 commented Sep 14, 2019

@bartlomieju Hi all, sorry for the long delay.

I have had a working implementation, but it's been a while since I've synced it with master branch, so I'll need to update it for the new JSON message passing changes. Let me do that ASAP and get right back to y'all.

@Dan503
Copy link

Dan503 commented Jan 25, 2020

It would be nice if the watcher could tell the difference between adding/removing a file and simply renaming it.

Chockidar can't tell the difference between renaming and adding/removing so when you rename something it will fire both an "add" event and a "remove" event. This can lead to tasks being run twice when you rename a file.

@bartlomieju
Copy link
Member

Ref #3452

@bartlomieju
Copy link
Member

This feature is almost complete (#3452), only common interface must be established. I'm marking this issue as "help wanted" - if anyone wants to pick up my PR feel free.

@ry
Copy link
Member

ry commented Feb 21, 2020

Fixed in #3452

@ry ry closed this as completed Feb 21, 2020
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

9 participants