summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rwxr-xr-xinstall_prepare.sh2
-rw-r--r--modules/email/email.js12
-rw-r--r--modules/nlen/English-Dutch-mistranslations.json8
-rw-r--r--modules/proxy/proxy.js8
-rw-r--r--modules/statusbot/statusbot.js185
-rw-r--r--modules/unicode/aliases.txt1
-rw-r--r--modules/up_log/.gitignore2
-rw-r--r--modules/up_log/up_log.js29
-rw-r--r--modules/zelfoverhoor/zelfoverhoor.js10
-rw-r--r--publications.json16
-rwxr-xr-xwebserver.js8
11 files changed, 230 insertions, 51 deletions
diff --git a/install_prepare.sh b/install_prepare.sh
index 7889ade..0fa736c 100755
--- a/install_prepare.sh
+++ b/install_prepare.sh
@@ -6,7 +6,7 @@ test -f "$abbrgen_execname" || \
g++ -Wall -Wextra -std=c++11 -O3 -o "$abbrgen_execname" modules/abbrgen/abbreviation_gen.cpp
test -f modules/unicode/UnicodeData.txt || \
- curl -Ls 'https://www.unicode.org/Public/15.0.0/ucd/UnicodeData-15.0.0d5.txt' >modules/unicode/UnicodeData.txt
+ curl -Ls 'https://www.unicode.org/Public/UCD/latest/ucd/UnicodeData.txt' >modules/unicode/UnicodeData.txt
mkdir -p static/fonts
test -f static/fonts/mononoki-Regular.woff || {
diff --git a/modules/email/email.js b/modules/email/email.js
index e4d342b..d7fe171 100644
--- a/modules/email/email.js
+++ b/modules/email/email.js
@@ -41,10 +41,16 @@ function sendEmail(recip, text) {
}
module.exports = function(app, io, moddir) {
- var allowedRecipients =
- fs.readFileSync(moddir + "/allowed_recipients.txt").toString().trim().split("\n");
+ var allowedRecipients, password;
- var password = fs.readFileSync(moddir + "/password.txt").toString().trim();
+ try {
+ allowedRecipients =
+ fs.readFileSync(moddir + "/allowed_recipients.txt").toString().trim().split("\n");
+ password = fs.readFileSync(moddir + "/password.txt").toString().trim();
+ } catch (e) {
+ console.error(e);
+ return false;
+ }
app.post("/email", bodyParser.json(), function(req, res) {
var body = req.body;
diff --git a/modules/nlen/English-Dutch-mistranslations.json b/modules/nlen/English-Dutch-mistranslations.json
index c4f0d2f..9aa919c 100644
--- a/modules/nlen/English-Dutch-mistranslations.json
+++ b/modules/nlen/English-Dutch-mistranslations.json
@@ -19,7 +19,7 @@
{"nl":"zachte goederen","en":"software"},
{"nl":"harde goederen","en":"hardware"},
{"nl":"Hyper-Tekst Markeer-Op Taal","en":"HTML"},
-{"nl":"client","en":"cliënt"},
+{"nl":"klant","en":"client"},
{"nl":"serveerder","en":"server"},
{"nl":"databasis","en":"database"},
{"nl":"voorhalen","en":"pre-fetch"},
@@ -29,8 +29,8 @@
{"nl":"Toepassing-Programmeer-Tussengezicht","en":"API"},
{"nl":"Principes van Meteoor\nData op het Snoer. Meteor stuurt geen HTMOT (Hyper Tekst Markeer-Op Taal) over het netwerk. De server stuurt gegevens en laat de cliënt deze weergeven.\nÉén Taal. Meteoor laat je beide de cliënt- en de serveerderdelen van je toepassing in JavaScript schrijven.\nDatabasis Overal. Je kan dezelfde methodes gebruiken om je databasis te bereiken van de cliënt of de serveerder.\nWachttijdcompensatie. Op de cliënt, Meteoor voorhaalt gegevens op en simuleert modellen om het te maken lijken alsof de serveerder methode roepingen instantaan terugkomen.\nVolle-stapel reagerendheid. In Meteoor, werkelijktijd is de standaard. Alle lagen, van databasis tot blauwdruk, vernieuwen zichzelf automatisch wanneer benodigd.\nOmarm het ecosysteem. Meteoor is open bron en integreert met existerende open bron werktuigen en raamwerken.\nEenvoudigheid Evenaart Productiviteit. De beste weg om iets simpel te laten lijken is om het werkelijk simpel te hebben zijn. Meteoor's hoofdfunctionaliteit heeft schone, klassiek-mooie TPT's (Toepassing Programmeer Tussengezichten).","en":"Principles of Meteor\nData on the Wire. Meteor doesn't send HTML over the network. The server sends data and lets the client render it.\nOne Language. Meteor lets you write both the client and the server parts of your application in JavaScript.\nDatabase Everywhere. You can use the same methods to access your database from the client or the server.\nLatency Compensation. On the client, Meteor prefetches data and simulates models to make it look like server method calls return instantly.\nFull Stack Reactivity. In Meteor, realtime is the default. All layers, from database to template, update themselves automatically when necessary.\nEmbrace the Ecosystem. Meteor is open source and integrates with existing open source tools and frameworks.\nSimplicity Equals Productivity. The best way to make something seem simple is to have it actually be simple. Meteor's main functionality has clean, classically beautiful APIs."},
{"nl":"opladen","en":"to upload"},
-{"nl":"Breedte-eerst zoekopdracht","en":"Breadth-first search"},
-{"nl":"zwevende-punt rekenen","en":"floating-point arithmetic"},
-{"nl":"multikernprocessor","en":"multi-core processor"},
+{"nl":"Breedte-eerstzoektocht","en":"Breadth-first search"},
+{"nl":"zwevende-komma rekenen","en":"floating-point arithmetic"},
+{"nl":"meerkernverwerker","en":"multi-core processor"},
{"nl":"automatisch differentiëren in omgekeerde modus","en":"reverse-mode automatic differentiation"}
]
diff --git a/modules/proxy/proxy.js b/modules/proxy/proxy.js
index 49caf54..ccec53a 100644
--- a/modules/proxy/proxy.js
+++ b/modules/proxy/proxy.js
@@ -29,9 +29,15 @@ function objectFromList(l){
}
module.exports=function(app,io,moddir){
- var iddict=objectFromList(
+ var iddict;
+ try {
+ iddict=objectFromList(
fs.readFileSync(moddir+"/idfile.txt").toString()
.split("\n").map(function(r){return r.split(" ");}));
+ } catch (e) {
+ console.error(e);
+ return false;
+ }
app.all("/proxy/:id/*",function(req,res){
var id=req.params.id;
diff --git a/modules/statusbot/statusbot.js b/modules/statusbot/statusbot.js
index 0fd9ce5..a518b17 100644
--- a/modules/statusbot/statusbot.js
+++ b/modules/statusbot/statusbot.js
@@ -11,7 +11,7 @@
// POST /statusbot
// { "sender": "who are you", "text": "content of the status message" }
-// State in this file: the above matrix.json plus:
+// State in this file (gstate): the above matrix.json plus:
// {
// config: the above matrix.json,
// home_server: "tomsmeding.com", // nominal server in the user id
@@ -46,6 +46,8 @@ if (!persistTokens) {
persist.setItemSync("access_tokens", persistTokens);
}
+let gstate = null;
+
// cb: (status: int, body: string) => ()
function fetch(method, headers, hostname, path, data, cb) {
const req = https.request({method, headers, hostname, path}, res => {
@@ -58,6 +60,86 @@ function fetch(method, headers, hostname, path, data, cb) {
req.end();
}
+// ms: int (milliseconds)
+// f: ((...Args) => ()) => ()
+// cb: (true, ...Args) => () | (false) => ()
+// Callback is invoked with 'false' if the timeout expired, or with 'true' if 'f' finished before the timeout.
+function ptimeout(ms, f, cb) {
+ let cbinvoked = false;
+
+ const tmout = setTimeout(() => {
+ if (cbinvoked) return;
+ cbinvoked = true;
+ cb(false);
+ }, ms);
+
+ f((...args) => {
+ if (cbinvoked) return;
+ cbinvoked = true;
+ clearTimeout(tmout);
+ cb(true, ...args);
+ });
+}
+
+class PRatelimit {
+ constructor(mindelayms) {
+ this._mindelayms = mindelayms;
+ this._queue = []; // [(() => ()) => ()]
+ this._ready = true; // true: should run job immediately; false: should enqueue
+ }
+
+ // f: (() => ()) => ()
+ submit(f) {
+ if (this._ready) {
+ this._ready = false;
+ f(() => this.startWaiting());
+ } else {
+ this._queue.push(f);
+ }
+ }
+
+ startWaiting() {
+ setTimeout(() => {
+ if (this._queue.length > 0) {
+ const f = this._queue.shift();
+ f(() => this.startWaiting());
+ } else {
+ this._ready = true;
+ }
+ }, this._mindelayms);
+ }
+}
+
+// f: ((true, ...Args) => () | (false) => ()) => ()
+// cb: (true, ...Args) => () | (false) => ()
+// The callback to f should be invoked with 'true' plus further arguments if it
+// succeeded, and 'false' if it did not.
+// Based on this info, if f failed, f is retried a few times. Once ntimes has
+// been exhausted or a call succeeds, cb is invoked with respectively either
+// (false) or (true, ...args).
+function pretry(interval, multiplier, ntimes, f, cb) {
+ let tm = interval;
+ let ncalled = 0;
+
+ function schedule() {
+ setTimeout(() => {
+ f((success, ...args) => {
+ if (success) {
+ cb(true, ...args);
+ return;
+ }
+
+ ncalled++;
+ tm *= multiplier;
+ if (ncalled >= ntimes) cb(false);
+ else schedule();
+ });
+ }, tm);
+ }
+
+ schedule();
+}
+
// cb: (state) => ()
function augmentConfig(config, cb) {
const m = config.user_id.match(/^@[^:]+:(.*)$/);
@@ -73,40 +155,50 @@ function augmentConfig(config, cb) {
[access_token, req_counter] = persistTokens[pkey];
}
- fetch("GET", {}, home_server, "/.well-known/matrix/server", "", (status, body) => {
- if (status != 200) {
- throw new Error(`statusbot: Failed getting https://${home_server}/.well-known/matrix/server`);
- }
- const mserver = JSON.parse(body)["m.server"];
- const m = mserver.match(/^([^:]*)(:443)?$/);
- if (!m) {
- throw new Error(`statusbot: Matrix server port not 443 (sorry): <${mserver}>`);
+ // wait a bit with requesting this in case the homeserver in question is the same server as us
+ pretry(500, 1.2, 10,
+ cb => {
+ fetch("GET", {}, home_server, "/.well-known/matrix/server", "", (status, body) => {
+ if (status != 200) {
+ cb(false); // attempt failed (perhaps a later attempt will succeed)
+ }
+ const mserver = JSON.parse(body)["m.server"];
+ const m = mserver.match(/^([^:]*)(:443)?$/);
+ if (!m) {
+ throw new Error(`statusbot: Matrix server port not 443 (sorry): <${mserver}>`);
+ }
+ const matrix_server = m[1];
+ // attempt succeeded!
+ cb(true, {config, home_server, matrix_server, access_token, req_counter});
+ });
+ },
+ (success, state) => {
+ if (success) cb(state);
+ else throw new Error(`statusbot: Failed getting https://${home_server}/.well-known/matrix/server`);
}
- const matrix_server = m[1];
- cb({config, home_server, matrix_server, access_token, req_counter});
- });
+ );
}
-function updatePersist(state) {
- persistTokens[`${state.config.user_id}|${state.config.room_id}`] = [state.access_token, state.req_counter];
+function updatePersist() {
+ persistTokens[`${gstate.config.user_id}|${gstate.config.room_id}`] = [gstate.access_token, gstate.req_counter];
persist.setItemSync("access_tokens", persistTokens);
}
// Sets access token in state.
// cb: (success: bool, body: string) => ()
-function matrixLogin(state, cb) {
+function matrixLogin(cb) {
const data = JSON.stringify({
type: "m.login.password",
- identifier: {type: "m.id.user", user: state.config.user_id},
- password: state.config.password,
+ identifier: {type: "m.id.user", user: gstate.config.user_id},
+ password: gstate.config.password,
});
- fetch("POST", {}, state.matrix_server, "/_matrix/client/v3/login", data, (status, body) => {
+ fetch("POST", {}, gstate.matrix_server, "/_matrix/client/v3/login", data, (status, body) => {
if (status != 200) { cb(false, body); return; }
try {
const response = JSON.parse(body);
- state.access_token = response.access_token;
- state.req_counter = 1;
- updatePersist(state);
+ gstate.access_token = response.access_token;
+ gstate.req_counter = 1;
+ updatePersist();
cb(true, body);
} catch (e) {
cb(false, body);
@@ -118,20 +210,20 @@ function matrixLogin(state, cb) {
// Status 401: access token invalid
// Status 200: success
// Anything else: ? (see body?)
-function matrixSendMsg(state, text, cb) {
- if (state.access_token == undefined) { cb(401); return; }
+function matrixSendMsg(text, cb) {
+ if (gstate.access_token == undefined) { cb(401); return; }
- const headers = {Authorization: `Bearer ${state.access_token}`};
- const url = `/_matrix/client/v3/rooms/${state.config.room_id}/send/m.room.message/${state.req_counter}`;
- state.req_counter++;
- updatePersist(state); // req_counter changed
+ const headers = {Authorization: `Bearer ${gstate.access_token}`};
+ const url = `/_matrix/client/v3/rooms/${gstate.config.room_id}/send/m.room.message/${gstate.req_counter}`;
+ gstate.req_counter++;
+ updatePersist(); // req_counter changed
const data = JSON.stringify({
msgtype: "m.text",
body: text,
});
- fetch("PUT", headers, state.matrix_server, url, data, (status, body) => {
+ fetch("PUT", headers, gstate.matrix_server, url, data, (status, body) => {
cb(status, body);
});
}
@@ -146,21 +238,21 @@ function logFailure(message, cb) {
// Tries to send a message, trying login if the access token is invalid.
// cb: (success: bool) => ()
-function matrixSendMsgLogin(state, text, cb) {
- matrixSendMsg(state, text, (status, body) => {
+function matrixSendMsgLogin(text, cb) {
+ matrixSendMsg(text, (status, body) => {
switch (status) {
case 200:
cb(true);
break;
case 401:
- matrixLogin(state, (success, body) => {
+ matrixLogin((success, body) => {
if (!success) {
logFailure(`Failed to log in: ${body}`, () => cb(false));
return;
}
- matrixSendMsg(state, text, (status, body) => {
+ matrixSendMsg(text, (status, body) => {
switch (status) {
case 200: cb(true); return;
case 401: logFailure(`401 even after login: ${body}`, () => cb(false)); break;
@@ -180,17 +272,36 @@ function matrixSendMsgLogin(state, text, cb) {
module.exports = function(app, io, _moddir) {
moddir = _moddir;
- const config = require("./matrix.json");
- const accounts = require("./accounts.json");
+ let config, accounts;
+ try {
+ config = require("./matrix.json");
+ accounts = require("./accounts.json");
+ } catch (e) {
+ console.error(e);
+ return false;
+ }
+
+ const ratelimit = new PRatelimit(1000);
augmentConfig(config, state => {
+ gstate = state;
+
app.post("/statusbot", bodyParser.json(), cmn.authgen(accounts), (req, res) => {
if (typeof req.body.sender != "string" || typeof req.body.text != "string") {
return res.sendStatus(400);
}
- matrixSendMsgLogin(state, `[${req.body.sender}] ${req.body.text}`, success => {
- if (success) res.sendStatus(200);
- else res.sendStatus(503); // service unavailable
+ ratelimit.submit(rlcb => {
+ ptimeout(5000,
+ cb => matrixSendMsgLogin(`[${req.body.sender}] ${req.body.text}`, cb),
+ (finished, success) => {
+ if (!finished) {
+ res.sendStatus(504); // gateway timeout
+ logFailure(`Timed out on message: [${req.body.sender}] ${req.body.text}`, () => {});
+ } else if (!success) res.sendStatus(503); // service unavailable
+ else res.sendStatus(200);
+ rlcb();
+ }
+ );
});
});
});
diff --git a/modules/unicode/aliases.txt b/modules/unicode/aliases.txt
index ea23cab..04bed7f 100644
--- a/modules/unicode/aliases.txt
+++ b/modules/unicode/aliases.txt
@@ -1,3 +1,4 @@
0x039B lambda
0x03BB lambda
0x2218 composition
+0x2248 approximately equal to
diff --git a/modules/up_log/.gitignore b/modules/up_log/.gitignore
new file mode 100644
index 0000000..955d2cc
--- /dev/null
+++ b/modules/up_log/.gitignore
@@ -0,0 +1,2 @@
+accounts.json
+log.txt
diff --git a/modules/up_log/up_log.js b/modules/up_log/up_log.js
new file mode 100644
index 0000000..609d705
--- /dev/null
+++ b/modules/up_log/up_log.js
@@ -0,0 +1,29 @@
+const cmn = require("../$common.js");
+const fs = require("fs");
+const bodyParser = require("body-parser");
+
+let moddir = null;
+
+module.exports=function(app,io,_moddir){
+ moddir = _moddir;
+
+ let config, accounts;
+ try {
+ accounts = require("./accounts.json");
+ } catch (e) {
+ console.error(e);
+ return false;
+ }
+
+ app.post("/up_log/log", bodyParser.json(), cmn.authgen(accounts), (req,res) => {
+ if (typeof req.body.machine != "string") {
+ return res.sendStatus(400);
+ }
+
+ const machine = req.body.machine.replace(/[^a-zA-Z0-9._-]/g, "");
+
+ fs.appendFileSync(moddir + "/log.txt", new Date().toISOString() + " machine=" + machine + "\n");
+
+ res.status(200).end();
+ });
+};
diff --git a/modules/zelfoverhoor/zelfoverhoor.js b/modules/zelfoverhoor/zelfoverhoor.js
index 03de63c..0acb322 100644
--- a/modules/zelfoverhoor/zelfoverhoor.js
+++ b/modules/zelfoverhoor/zelfoverhoor.js
@@ -6,7 +6,12 @@ var moddir;
var dbdir=cmn.persistdir+"/zelfoverhoor";
mkdirp.sync(dbdir);
-var accounts=require("./accounts.json");
+var accounts=null;
+try {
+ accounts=require("./accounts.json");
+} catch (e) {
+ console.error(e);
+}
var SHUFFLE_QUESTIONS=false;
@@ -97,6 +102,9 @@ function canAccessFile(fname){
module.exports=function(app,io,_moddir){
moddir=_moddir;
+ // Failed to load accounts.json
+ if(accounts==null)return false;
+
if(canAccessFile(dbdir+"/questiondb.json")&&canAccessFile(dbdir+"/questionsets.json")&&canAccessFile(dbdir+"/userlists.json")){
questiondb=JSON.parse(fs.readFileSync(dbdir+"/questiondb.json"));
questionsets=JSON.parse(fs.readFileSync(dbdir+"/questionsets.json"));
diff --git a/publications.json b/publications.json
index 3f1cc2c..63c59e3 100644
--- a/publications.json
+++ b/publications.json
@@ -6,6 +6,18 @@
"pubs": [
{
"year": 2024,
+ "month": 5,
+ "authors": ["tom", "matthijs"],
+ "title": "Parallel Dual-Numbers Reverse AD",
+ "preprint": true,
+ "note": "Extended version of 2023-01, submitted to JFP.",
+ "links": [
+ {"type": "arxiv", "id": "2207.03418"},
+ {"type": "code", "url": "https://github.com/tomsmeding/ad-dualrev-th"}
+ ]
+ },
+ {
+ "year": 2024,
"month": 1,
"authors": ["tom", "matthijs"],
"title": "Efficient CHAD",
@@ -23,9 +35,9 @@
"title": "Efficient Dual-Numbers Reverse AD via Well-Known Program Transformations",
"conference": "POPL 2023",
"links": [
- {"type": "arxiv", "id": "2207.03418", "note": "with appendices"},
+ {"type": "arxiv", "id": "2207.03418v2", "note": "with appendices"},
{"type": "doi", "id": "10.1145/3571247"},
- {"type": "code", "url": "https://github.com/tomsmeding/ad-dualrev-th"}
+ {"type": "code", "url": "https://github.com/tomsmeding/ad-dualrev-th/tree/artifact"}
]
},
{
diff --git a/webserver.js b/webserver.js
index 71f6bcd..39f2410 100755
--- a/webserver.js
+++ b/webserver.js
@@ -87,8 +87,12 @@ const module_list = fs.readdirSync("modules").filter(function (f) {
});
for (let i = 0; i < module_list.length; i++) {
const mod = require("./modules/" + module_list[i] + "/" + module_list[i] + ".js");
- mod(app, io, cmn.serverdir + "/modules/" + module_list[i]);
- console.log("Loaded module " + module_list[i]);
+ const ret = mod(app, io, cmn.serverdir + "/modules/" + module_list[i]);
+ if (ret === false) {
+ console.log("FAILED LOADING module " + module_list[i]);
+ } else {
+ console.log("Loaded module " + module_list[i]);
+ }
}