I’ve been thinking of a plugin framework of sorts, to enable some weird behaviors that people keep asking for, or more generally for doing things we haven’t anticipated in the main code. I figured I’d write up something like a proposal before starting to hack on it to see what others think.
Plugins are written in Javascript, interpreted by Goja (https://github.com/dop251/goja) or possibly Otto (https://github.com/robertkrimen/otto). Another option is Starlight (https://github.com/starlight-go/starlight) with Python-like syntax. (The principles are the same regardless of what syntax we might choose in the end, probably it’d boil down to which package is better in other respects.)
Plugin scripts are loaded from somewhere in the config dir ($CONFIGDIR/plugins/foo.plugin.js
) and apply to all folders, or somewhere in the folder (.stfolder/foo.plugin.js
) and then apply to that folder only. We provide a number of methods that can be used to register functionality in the plugin. For example:
onShouldIgnore(function (fileInfo) {
// "this" on folder callbacks is the FolderConfiguration object
// The fileInfo parameter is a FileInfo object.
if (this.folderID == "small-files-folder" && fileInfo.size > 1<<30) {
// we ignore files larger than a gigabyte
return true
}
if (this.folderID == "noexec-folder"
&& fileInfo.type == 0 // FILE_INFO_TYPE_FILE
&& fileInfo.permissions & 0111 != 0) {
// we ignore executable files
return true
}
if (this.folderID == "stupid-folder" && fileInfo.name.length % 2 != 0) {
// we ignore files with names of uneven length
return true
}
return false
})
// assume predeclared constants
// FolderIdle = 0
// FolderScanning = 1
// ...
onFolderStateChanged(function (oldState, newState) {
if (this.folderID != "hooks-folder"
|| newState != FolderIdle
|| oldState != FolderSyncing) {
return
}
// We just completed syncing on hooks-folder, let's commit the changes.
// Let's hope there's an "os" module provided and that Syncthing sets the
// working directory to the folder path.
os.Execute("git add -A ; git commit -m Syncthing")
})
One could imagine other potential callbacks; onDeviceConnected
(can accept or refuse connections), onIncomingUpdate
to tweak or transform incoming fileinfo updates, onConfigLoaded
to transform the configuration, etc.
We would need to provide some functionality for use in the plugin:
- “os” or something to execute commands
- “fs” to perform file operations (copy files perhaps)
- “console.log” which should go to our log
- maybe, possibly, an HTTP module so that webhooks could be called
- possibly other things as we go along
I think all of this could be implemented in a fairly self contained manner, passing along a plugin object that we’d call callbacks on at strategic points in the existing code.
Clearly there’d be a performance cost to some of these things (like onIncomingUpdate
which might get called thousands of times when exchanging indexes etc), but ideally that cost would be near zero when plugins aren’t used.
Also clearly this is a huge footgun. If we did have onIncomingUpdate
for example and allowed changes to FileInfos there that’s very powerful – you could implement things like ignore deletes and similar, but if you change things that shouldn’t be changed (file name or size, etc) you will obviously break syncing.
Similarly onConfigLoaded
could be used to implement mandatory policies (all folders shall have ignore-permissions and modtime-window=2s set, for example, or all devices get an allowed-networks setting) but means what you see is no longer what you get, necessarily…