Run a service when a file changes
What you'll see
You want something to happen when a file changes: make thumbnails when an image is uploaded, rebuild an index when an article is saved, or clean up when a file is deleted. Polling the folder from a scheduled service is slow and wasteful. The Events section of #/Services.json runs a script for exactly those changes.
Events need the subscription feature File system events. Without it, the Services tab in the developer console shows the section as Not in subscription. While your Docly server does not yet have the release that runs events, the section is accepted but nothing runs, and the console says File events are not executed yet.
What's actually happening
Type decides which changes the event listens to. Use one value, several separated by commas ("Created, Changed"), or * for all three. Case does not matter.
Created: a file appears at the path. This covers a new file, an upload, a copy, a move or rename into the path, and a restore from the recycle bin.Changed: the content of an existing file is saved, from the editor, an overwriting upload, the drive or the CLI. Changing only tags or sharing does not count.Deleted: the file is deleted, or moved or renamed away from the path. Deleting a folder gives oneDeletedper file in it, up to 1000.
An unknown value, such as "Modified", disables only that event. The console marks it Invalid and gives the reason, and the rest of the file keeps running.
Paths are glob patterns relative to the site root. * matches within one folder, ** matches zero or more folders, and ? matches one character. Matching ignores case, and one matching pattern is enough. Empty Paths makes the event invalid, so write "**" to match every file. ** does not match the system folder #/: a pattern must itself start with #/ to see it. #/Services.json, #/Services/** and #/Logs/** never fire.
When the script runs:
- The change is queued when the save is complete, and the script runs in the background as the site owner, like a scheduled service. It does not run as the user who made the change.
- A path must be quiet for 2 seconds, and never waits more than 10 seconds. Changes to the same path are merged: create + save is one
Created, delete + create (an upload that replaces a file) is oneChanged, and create + delete is nothing. Paths that are ready at the same time run in one call. - Only one run per event at a time. Changes that arrive during a run are collected into a single new run afterwards, so they are not lost.
- Before the script starts, each change is checked against the database. A file that was rolled back, or deleted again, is left out.
- Files the event script writes itself never trigger file events, so there are no chains. Scheduled services and pages can trigger file events.
- Unzip, package install, transfer and sync between servers, cloning, and moving or renaming a folder do not trigger events.
- Limits: at most 60 runs per site per minute (the rest wait), and at most 1000 changes per run (the rest are counted as skipped).
Status and errors work as for scheduled services. Each event has a status file, #/Logs/File event - {Name}.json. It holds the last run, the last result, the error counter, what triggered the last run, the number of runs and what was skipped. throw and validation errors are warnings and do not count. A script error counts towards Errors.MaxRetries (default 3). After that the event is suspended, a mail goes to ReportTo or to the site owner, and new changes are only counted. There is no automatic retry and PauseInterval does not apply: the next file change is the next attempt.
What to do
Give every event a unique Name. It is the name in the console and the key of the status file. Without it the name is the Type plus the first path, so the status starts over when you change the pattern.
{
"Events": [
{
"Name": "Thumbnails",
"Description": "Creates thumbnails for new images",
"Type": "Created, Changed",
"Paths": [ "images/**/*.jpg", "images/**/*.png" ],
"Task": { "Path": "thumbs.js", "TimeOut": "00:02:00" }
}
],
"Errors": { "MaxRetries": 3, "ReportTo": "ops@example.com" }
}The script is an anonymous function in #/Services/thumbs.js. Read service.Changes, not only service.Path: one run can carry several files.
function (service, ev) {
// service.ChangeType, Path, OldPath, Guid, ChangedBy = the last change in this run
// service.Changes = every change: [{ ChangeType, Path, OldPath, Guid, ChangedBy }]
for (var c of service.Changes) {
if (c.ChangeType != "Deleted") makeThumbnail(c.Path);
}
return "ok";
}- Paths are relative to the site root, the same form as
docly.getFile.OldPathis set after a move or rename, otherwise it is null.ChangedByis the user's e-mail, or null for the system. - The script sees the file as it is when the script runs, not as it was at the moment of the change.
- To start a suspended event again, delete or edit its status file. The Services tab shows every event with its status, last run and last trigger.
- A changed
Services.jsontakes effect within a minute. - Queued changes are held in memory, so changes made seconds before a server restart can be lost. If a change must never be missed, also run a scheduled service that catches up.