forked from cory/tildefriends
Compare commits
31 Commits
Author | SHA1 | Date | |
---|---|---|---|
9cbe895cb8 | |||
b0b0f74e83 | |||
d9eaa92c37 | |||
566d07117e | |||
2bffdb1168 | |||
1359b48c9f | |||
a69fb5eeac | |||
38e313350e | |||
5052dc04f2 | |||
9ef3a3aca0 | |||
7b91a2ec37 | |||
2926f855a1 | |||
639419db60 | |||
54747c127c | |||
791c3dd787 | |||
b00d75ab7c | |||
956ea0df56 | |||
30014040e7 | |||
ab055c3394 | |||
1e37eeea05 | |||
84aec0278d | |||
06642f58c5 | |||
e6d44b32f4 | |||
1f3f6e2b92 | |||
8f2d3e3bcd | |||
2df2fc5792 | |||
20b0337e0a | |||
e86b9dae48 | |||
71de897419 | |||
3edfaf9137 | |||
19c1784864 |
4
Makefile
4
Makefile
@ -114,6 +114,7 @@ $(APP_OBJS): CFLAGS += \
|
||||
-Ideps/sqlite \
|
||||
-Ideps/valgrind \
|
||||
-Ideps/xopt \
|
||||
-Wdouble-promotion \
|
||||
-Werror
|
||||
|
||||
BLOWFISH_SOURCES := \
|
||||
@ -457,6 +458,7 @@ PACKAGE_DIRS := \
|
||||
apps/ \
|
||||
core/ \
|
||||
deps/codemirror/ \
|
||||
deps/lit/ \
|
||||
deps/split/ \
|
||||
deps/smoothie/
|
||||
|
||||
@ -478,7 +480,7 @@ out/%.unsigned.apk:
|
||||
@cp out/apk/res.apk $@
|
||||
@cp out/apk/classes.dex out/apk$(BUILD_TYPE)/
|
||||
@cd out/apk$(BUILD_TYPE) && zip -u ../../$@ -q -9 -r . && cd ../../
|
||||
@zip -u $@ -q -9 -r $(PACKAGE_DIRS) $(RAW_FILES)
|
||||
@zip -u $@ -q -9 -x '*.map' -r $(PACKAGE_DIRS) $(RAW_FILES)
|
||||
|
||||
out/%.apk: out/apk/%.unsigned.apk
|
||||
@echo [apksigner] $(notdir $@)
|
||||
|
@ -1,4 +1,3 @@
|
||||
var global = Function('return this')();
|
||||
function treeify(o) {
|
||||
if (typeof(o) == 'object') {
|
||||
return Object.fromEntries(Object.keys(o).map(x => [x, treeify(o[x])]));
|
||||
@ -8,4 +7,4 @@ function treeify(o) {
|
||||
return o;
|
||||
}
|
||||
}
|
||||
app.setDocument(`<pre style="color:#fff">${JSON.stringify(treeify(global), null, 2)}</pre>`);
|
||||
app.setDocument(`<pre style="color:#fff">${JSON.stringify(treeify(globalThis), null, 2)}</pre>`);
|
@ -70,7 +70,7 @@ async function main() {
|
||||
populate_apps('apps', '${core.user.credentials?.session?.name}', ${JSON.stringify(apps)});
|
||||
populate_apps('core_apps', 'core', ${JSON.stringify(core_apps)});
|
||||
</script>
|
||||
</html>`
|
||||
</html>`;
|
||||
app.setDocument(doc);
|
||||
}
|
||||
|
||||
|
@ -1,3 +1,4 @@
|
||||
{
|
||||
"type": "tildefriends-app"
|
||||
"type": "tildefriends-app",
|
||||
"emoji": "🛍"
|
||||
}
|
@ -20,7 +20,7 @@ async function database_list() {
|
||||
}
|
||||
populate_dbs('dbs', ${JSON.stringify(dbs)});
|
||||
</script>
|
||||
</html>`
|
||||
</html>`;
|
||||
app.setDocument(doc);
|
||||
}
|
||||
|
||||
@ -47,7 +47,7 @@ async function key_list(db) {
|
||||
}
|
||||
populate_dbs('keys', ${JSON.stringify(object)});
|
||||
</script>
|
||||
</html>`
|
||||
</html>`;
|
||||
app.setDocument(doc);
|
||||
}
|
||||
|
||||
|
@ -1,5 +1,3 @@
|
||||
"use strict";
|
||||
|
||||
var g_following_cache = {};
|
||||
var g_following_deep_cache = {};
|
||||
var g_about_cache = {};
|
||||
|
@ -79,7 +79,7 @@ export function picker(callback, anchor) {
|
||||
emoji.onclick = function() {
|
||||
callback(entry);
|
||||
cleanup();
|
||||
}
|
||||
};
|
||||
emoji.title = entry.name;
|
||||
emoji.appendChild(document.createTextNode(entry.emoji));
|
||||
list.appendChild(emoji);
|
||||
|
50
apps/ssb/lit-all.min.js
vendored
50
apps/ssb/lit-all.min.js
vendored
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
@ -32,8 +32,8 @@ class TfElement extends LitElement {
|
||||
this.following = [];
|
||||
this.users = {};
|
||||
this.loaded = false;
|
||||
tfrpc.rpc.getBroadcasts().then(b => { self.broadcasts = b || [] });
|
||||
tfrpc.rpc.getConnections().then(c => { self.connections = c || [] });
|
||||
tfrpc.rpc.getBroadcasts().then(b => { self.broadcasts = b || []; });
|
||||
tfrpc.rpc.getConnections().then(c => { self.connections = c || []; });
|
||||
tfrpc.rpc.getHash().then(hash => self.set_hash(hash));
|
||||
tfrpc.register(function hashChanged(hash) {
|
||||
self.set_hash(hash);
|
||||
|
@ -13,7 +13,7 @@ class TfComposeElement extends LitElement {
|
||||
branch: {type: String},
|
||||
apps: {type: Object},
|
||||
drafts: {type: Object},
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
static styles = styles;
|
||||
@ -56,7 +56,7 @@ class TfComposeElement extends LitElement {
|
||||
if (!draft.mentions[link]) {
|
||||
draft.mentions[link] = {
|
||||
link: link,
|
||||
}
|
||||
};
|
||||
}
|
||||
draft.mentions[link].name = name.startsWith('@') ? name.substring(1) : name;
|
||||
updated = true;
|
||||
@ -111,7 +111,7 @@ class TfComposeElement extends LitElement {
|
||||
let data_url = canvas.toDataURL(mime_type);
|
||||
let result = atob(data_url.split(',')[1]).split('').map(x => x.charCodeAt(0));
|
||||
resolve(result);
|
||||
}
|
||||
};
|
||||
img.onerror = function(event) {
|
||||
reject(new Error('Failed to load image.'));
|
||||
};
|
||||
@ -283,11 +283,11 @@ class TfComposeElement extends LitElement {
|
||||
};
|
||||
}
|
||||
}
|
||||
let draft = this.get_draft();
|
||||
let draft = self.get_draft();
|
||||
draft.mentions = Object.assign(draft.mentions || {}, mentions);
|
||||
this.requestUpdate();
|
||||
this.notify(draft);
|
||||
this.apps = null;
|
||||
self.requestUpdate();
|
||||
self.notify(draft);
|
||||
self.apps = null;
|
||||
}
|
||||
|
||||
if (this.apps) {
|
||||
@ -304,13 +304,14 @@ class TfComposeElement extends LitElement {
|
||||
}
|
||||
|
||||
render_attach_app_button() {
|
||||
let self = this;
|
||||
async function attach_app() {
|
||||
this.apps = await tfrpc.rpc.apps();
|
||||
self.apps = await tfrpc.rpc.apps();
|
||||
}
|
||||
if (!this.apps) {
|
||||
return html`<input type="button" value="Attach App" @click=${attach_app}></input>`
|
||||
return html`<input type="button" value="Attach App" @click=${attach_app}></input>`;
|
||||
} else {
|
||||
return html`<input type="button" value="Discard App" @click=${() => this.apps = null}></input>`
|
||||
return html`<input type="button" value="Discard App" @click=${() => this.apps = null}></input>`;
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -9,7 +9,7 @@ class TfIdentityPickerElement extends LitElement {
|
||||
return {
|
||||
ids: {type: Array},
|
||||
selected: {type: String},
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
constructor() {
|
||||
|
@ -14,7 +14,7 @@ class TfMessageElement extends LitElement {
|
||||
raw: {type: Boolean},
|
||||
blog_data: {type: String},
|
||||
expanded: {type: Object},
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
static styles = styles;
|
||||
@ -69,8 +69,8 @@ class TfMessageElement extends LitElement {
|
||||
hash: this.message?.hash,
|
||||
content: this.message?.content,
|
||||
signature: this.message?.signature,
|
||||
}
|
||||
return html`<div style="white-space: pre-wrap">${JSON.stringify(raw, null, 2)}</div>`
|
||||
};
|
||||
return html`<div style="white-space: pre-wrap">${JSON.stringify(raw, null, 2)}</div>`;
|
||||
}
|
||||
|
||||
vote(emoji) {
|
||||
@ -127,6 +127,13 @@ class TfMessageElement extends LitElement {
|
||||
body_click(event) {
|
||||
if (event.srcElement.tagName == 'IMG') {
|
||||
this.show_image(event.srcElement.src);
|
||||
} else if (event.srcElement.tagName == 'DIV' && event.srcElement.classList.contains('img_caption')) {
|
||||
let next = event.srcElement.nextSibling;
|
||||
if (next.style.display == 'block') {
|
||||
next.style.display = 'none';
|
||||
} else {
|
||||
next.style.display = 'block';
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -168,10 +175,7 @@ class TfMessageElement extends LitElement {
|
||||
|
||||
render_mentions() {
|
||||
let mentions = this.message?.content?.mentions || [];
|
||||
mentions = mentions.filter(x =>
|
||||
x.name?.startsWith('audio:') ||
|
||||
x.name?.startsWith('video:') ||
|
||||
this.message?.content?.text?.indexOf(x.link) === -1);
|
||||
mentions = mentions.filter(x => this.message?.content?.text?.indexOf(x.link) === -1);
|
||||
if (mentions.length) {
|
||||
let self = this;
|
||||
return html`
|
||||
@ -229,7 +233,7 @@ class TfMessageElement extends LitElement {
|
||||
${self.raw ? self.render_raw() : inner}
|
||||
${self.render_votes()}
|
||||
</div>
|
||||
`
|
||||
`;
|
||||
}
|
||||
if (this.message?.type === 'contact_group') {
|
||||
return html`
|
||||
@ -265,7 +269,7 @@ class TfMessageElement extends LitElement {
|
||||
<div style="flex: 1 0 50%; overflow-wrap: anywhere">
|
||||
<div>${unsafeHTML(tfutils.markdown(content.description))}</div>
|
||||
</div>
|
||||
`
|
||||
`;
|
||||
}
|
||||
let update = content.about == this.message.author ?
|
||||
html`<div style="font-weight: bold">Updated profile.</div>` :
|
||||
|
@ -11,7 +11,7 @@ class TfNewsElement extends LitElement {
|
||||
following: {type: Array},
|
||||
drafts: {type: Object},
|
||||
expanded: {type: Object},
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
static styles = styles;
|
||||
|
@ -11,7 +11,7 @@ class TfProfileElement extends LitElement {
|
||||
id: {type: String},
|
||||
users: {type: Object},
|
||||
size: {type: Number},
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
static styles = styles;
|
||||
@ -33,7 +33,7 @@ class TfProfileElement extends LitElement {
|
||||
contact: this.id,
|
||||
}, change)).catch(function(error) {
|
||||
alert(error?.message);
|
||||
})
|
||||
});
|
||||
}
|
||||
|
||||
follow() {
|
||||
|
@ -36,4 +36,13 @@ img {
|
||||
padding: 8px;
|
||||
margin: 4px;
|
||||
}
|
||||
|
||||
div.img_caption {
|
||||
color: #888;
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
div.img_caption::after {
|
||||
content: ' ±';
|
||||
}
|
||||
`;
|
@ -9,7 +9,7 @@ class TfTabConnectionsElement extends LitElement {
|
||||
connections: {type: Array},
|
||||
stored_connections: {type: Array},
|
||||
users: {type: Object},
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
constructor() {
|
||||
@ -71,7 +71,7 @@ class TfTabConnectionsElement extends LitElement {
|
||||
<tf-user id=${connection.pubkey} .users=${this.users}></tf-user>
|
||||
${this.render_connection_summary(connection)}
|
||||
</li>
|
||||
`
|
||||
`;
|
||||
}
|
||||
|
||||
async forget_stored_connection(connection) {
|
||||
|
@ -12,7 +12,7 @@ class TfTabNewsFeedElement extends LitElement {
|
||||
messages: {type: Array},
|
||||
drafts: {type: Object},
|
||||
expanded: {type: Object},
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
static styles = styles;
|
||||
@ -120,7 +120,7 @@ class TfTabNewsElement extends LitElement {
|
||||
following: {type: Array},
|
||||
drafts: {type: Object},
|
||||
expanded: {type: Object},
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
static styles = styles;
|
||||
|
@ -9,7 +9,7 @@ class TfTabSearchElement extends LitElement {
|
||||
users: {type: Object},
|
||||
following: {type: Array},
|
||||
query: {type: String},
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
static styles = styles;
|
||||
|
@ -7,7 +7,7 @@ class TfUserElement extends LitElement {
|
||||
return {
|
||||
id: {type: String},
|
||||
users: {type: Object},
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
static styles = styles;
|
||||
|
@ -1,9 +1,55 @@
|
||||
import * as linkify from './commonmark-linkify.js';
|
||||
import * as hashtagify from './commonmark-hashtag.js';
|
||||
|
||||
|
||||
function image(node, entering) {
|
||||
if (node.firstChild?.type === 'text' &&
|
||||
node.firstChild.literal.startsWith('video:')) {
|
||||
if (entering) {
|
||||
this.lit('<video style="max-width: 100%; max-height: 480px" title="' + this.esc(node.firstChild?.literal) + '" controls>');
|
||||
this.lit('<source src="' + this.esc(node.destination) + '"></source>');
|
||||
this.disableTags += 1;
|
||||
} else {
|
||||
this.disableTags -= 1;
|
||||
this.lit('</video>');
|
||||
}
|
||||
} else if (node.firstChild?.type === 'text' &&
|
||||
node.firstChild.literal.startsWith('audio:')) {
|
||||
if (entering) {
|
||||
this.lit('<audio style="height: 32px; max-width: 100%" title="' + this.esc(node.firstChild?.literal) + '" controls>');
|
||||
this.lit('<source src="' + this.esc(node.destination) + '"></source>');
|
||||
this.disableTags += 1;
|
||||
} else {
|
||||
this.disableTags -= 1;
|
||||
this.lit('</audio>');
|
||||
}
|
||||
} else {
|
||||
if (entering) {
|
||||
if (this.disableTags === 0) {
|
||||
this.lit('<div class="img_caption">' + this.esc(node.firstChild?.literal || node.destination) + '</div>');
|
||||
if (this.options.safe && potentiallyUnsafe(node.destination)) {
|
||||
this.lit('<img src="" alt="');
|
||||
} else {
|
||||
this.lit('<img src="' + this.esc(node.destination) + '" alt="');
|
||||
}
|
||||
}
|
||||
this.disableTags += 1;
|
||||
} else {
|
||||
this.disableTags -= 1;
|
||||
if (this.disableTags === 0) {
|
||||
if (node.title) {
|
||||
this.lit('" title="' + this.esc(node.title));
|
||||
}
|
||||
this.lit('" />');
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
export function markdown(md) {
|
||||
var reader = new commonmark.Parser({safe: true});
|
||||
var writer = new commonmark.HtmlRenderer();
|
||||
writer.image = image;
|
||||
var parsed = reader.parse(md || '');
|
||||
parsed = linkify.transform(parsed);
|
||||
parsed = hashtagify.transform(parsed);
|
||||
|
2
apps/ssb/update.sh
Normal file
2
apps/ssb/update.sh
Normal file
@ -0,0 +1,2 @@
|
||||
wget https://cdn.jsdelivr.net/gh/lit/dist@2.7.2/all/lit-all.min.js -O lit-all.min.js
|
||||
wget https://cdn.jsdelivr.net/gh/lit/dist@2.7.2/all/lit-all.min.js.map -O lit-all.min.js.map
|
@ -114,13 +114,12 @@ class TodoListElement extends LitElement {
|
||||
@change=${event => self.input_change(event, item)}
|
||||
@keydown=${event => self.input_keydown(event, item)}
|
||||
@blur=${x => self.input_blur(item)}></input>
|
||||
<span @click=${x => self.remove_item(item)}>x</span></div>
|
||||
<span @click=${x => self.remove_item(item)} style="cursor: pointer">❎</span></div>
|
||||
`;
|
||||
} else {
|
||||
return html`
|
||||
<div><input type="checkbox" ?checked=${item.x} @change=${x => self.handle_check(x, item)}></input>
|
||||
<span @click=${x => self.editing = index}>${item.text}</span>
|
||||
<span @click=${x => self.remove_item(item)} style="cursor: pointer">❎</span></div>
|
||||
`;
|
||||
}
|
||||
}
|
||||
@ -175,7 +174,8 @@ class TodoListElement extends LitElement {
|
||||
return html`
|
||||
<div style="border: 3px solid black; padding: 8px; margin: 8px; border-radius: 8px; background-color: #444">
|
||||
${name}
|
||||
${(this.items || []).map(x => self.render_item(x))}
|
||||
${(this.items || []).filter(item => !item.x).map(x => self.render_item(x))}
|
||||
${(this.items || []).filter(item => item.x).map(x => self.render_item(x))}
|
||||
<button @click=${self.add_item}>+ Item</button>
|
||||
<button @click=${self.remove_list}>- List</button>
|
||||
</div>
|
||||
|
426
core/client.js
426
core/client.js
@ -1,6 +1,6 @@
|
||||
import {LitElement, html, css, svg} from '/static/lit/lit-all.min.js';
|
||||
|
||||
let gSocket;
|
||||
let gCredentials;
|
||||
let gPermissions;
|
||||
|
||||
let gCurrentFile;
|
||||
let gFiles = {};
|
||||
@ -26,6 +26,246 @@ const k_api = {
|
||||
setHash: {args: ['hash'], func: api_setHash},
|
||||
};
|
||||
|
||||
const k_global_style = css`
|
||||
a:link {
|
||||
color: #268bd2;
|
||||
}
|
||||
|
||||
a:visited {
|
||||
color: #6c71c4;
|
||||
}
|
||||
|
||||
a:hover {
|
||||
color: #859900;
|
||||
}
|
||||
|
||||
a:active {
|
||||
color: #2aa198;
|
||||
}
|
||||
`;
|
||||
|
||||
class TfNavigationElement extends LitElement {
|
||||
static get properties() {
|
||||
return {
|
||||
credentials: {type: Object},
|
||||
permissions: {type: Object},
|
||||
show_permissions: {type: Boolean},
|
||||
status: {type: Object},
|
||||
spark_lines: {type: Object},
|
||||
};
|
||||
}
|
||||
|
||||
constructor() {
|
||||
super();
|
||||
this.permissions = {};
|
||||
this.show_permissions = false;
|
||||
this.status = {};
|
||||
this.spark_lines = {};
|
||||
}
|
||||
|
||||
toggle_edit(event) {
|
||||
event.preventDefault();
|
||||
if (editing()) {
|
||||
closeEditor();
|
||||
} else {
|
||||
edit();
|
||||
}
|
||||
}
|
||||
|
||||
reset_permission(key) {
|
||||
send({action: "resetPermission", permission: key});
|
||||
}
|
||||
|
||||
get_spark_line(key, options) {
|
||||
if (!this.spark_lines[key]) {
|
||||
let spark_line = document.createElement('tf-sparkline');
|
||||
spark_line.title = key;
|
||||
if (options) {
|
||||
if (options.max) {
|
||||
spark_line.max = options.max;
|
||||
}
|
||||
}
|
||||
this.spark_lines[key] = spark_line;
|
||||
this.requestUpdate();
|
||||
}
|
||||
return this.spark_lines[key];
|
||||
}
|
||||
|
||||
render_login() {
|
||||
if (this?.credentials?.session?.name) {
|
||||
return html`<a href="/login/logout?return=${url() + hash()}">logout ${this.credentials.session.name}</a>`;
|
||||
} else {
|
||||
return html`<a href="/login?return=${url() + hash()}">login</a>`;
|
||||
}
|
||||
}
|
||||
|
||||
render_permissions() {
|
||||
if (this.show_permissions) {
|
||||
return html`
|
||||
<div style="position: absolute; top: 0; padding: 0; margin: 0; z-index: 100; display: flex; justify-content: center; width: 100%">
|
||||
<div style="background-color: #444; padding: 1em; margin: 0 auto; border-left: 4px solid #fff; border-right: 4px solid #fff; border-bottom: 4px solid #fff">
|
||||
<div>This app has the following permissions:</div>
|
||||
${Object.keys(this.permissions).map(key => html`
|
||||
<div>
|
||||
<span>${key}</span>: ${this.permissions[key] ? '✅ Allowed' : '❌ Denied'}
|
||||
<button @click=${() => this.reset_permission(key)}>Reset</button>
|
||||
</div>
|
||||
`)}
|
||||
<button @click=${() => this.show_permissions = false}>Close</button>
|
||||
</div>
|
||||
</div>
|
||||
`;
|
||||
}
|
||||
}
|
||||
|
||||
render() {
|
||||
let self = this;
|
||||
return html`
|
||||
<style>
|
||||
${k_global_style}
|
||||
</style>
|
||||
<div style="margin: 4px; display: flex; flex-direction: row; flex-wrap: nowrap; gap: 3px">
|
||||
<span>😎</span>
|
||||
<a accesskey="h" data-tip="Open home app." href="/" style="color: #fff; white-space: nowrap">TF</a>
|
||||
<a accesskey="a" data-tip="Open apps list." href="/~core/apps/">apps</a>
|
||||
<a accesskey="e" data-tip="Toggle the app editor." href="#" @click=${this.toggle_edit}>edit</a>
|
||||
<a accesskey="p" data-tip="View and change permissions." href="#" @click=${() => self.show_permissions = !self.show_permissions}>🎛️</a>
|
||||
<span style="display: inline-block; vertical-align: top; white-space: pre; color: ${this.status.color ?? kErrorColor}">${this.status.message}</span>
|
||||
<span id="requests"></span>
|
||||
${this.render_permissions()}
|
||||
<span style="flex: 1; white-space: nowrap; overflow: hidden; margin: 0; padding: 0">${Object.keys(this.spark_lines).sort().map(x => this.spark_lines[x]).map(x => [x.dataset.emoji, x])}</span>
|
||||
<span style="flex: 0 0; white-space: nowrap">${this.render_login()}</span>
|
||||
</div>
|
||||
`;
|
||||
}
|
||||
}
|
||||
customElements.define('tf-navigation', TfNavigationElement);
|
||||
|
||||
class TfFilesElement extends LitElement {
|
||||
static get properties() {
|
||||
return {
|
||||
current: {type: String},
|
||||
files: {type: Object},
|
||||
};
|
||||
}
|
||||
|
||||
constructor() {
|
||||
super();
|
||||
this.files = {};
|
||||
}
|
||||
|
||||
file_click(file) {
|
||||
this.dispatchEvent(new CustomEvent('file_click', {
|
||||
detail: {
|
||||
file: file,
|
||||
},
|
||||
bubbles: true,
|
||||
composed: true,
|
||||
}));
|
||||
}
|
||||
|
||||
render_file(file) {
|
||||
let classes = ['file'];
|
||||
if (file == this.current) {
|
||||
classes.push('current');
|
||||
}
|
||||
if (!this.files[file].clean) {
|
||||
classes.push('dirty');
|
||||
}
|
||||
return html`<div class="${classes.join(' ')}" @click=${x => this.file_click(file)}>${file}</div>`;
|
||||
}
|
||||
|
||||
render() {
|
||||
let self = this;
|
||||
return html`
|
||||
<style>
|
||||
div.file {
|
||||
padding: 0.5em;
|
||||
cursor: pointer;
|
||||
}
|
||||
div.file:hover {
|
||||
background-color: #1a9188;
|
||||
}
|
||||
div.file::before {
|
||||
content: '📄 ';
|
||||
}
|
||||
|
||||
div.file.current {
|
||||
font-weight: bold;
|
||||
background-color: #2aa198;
|
||||
}
|
||||
|
||||
div.file.dirty::after {
|
||||
content: '*';
|
||||
}
|
||||
</style>
|
||||
<div>
|
||||
${Object.keys(this.files).sort().map(x => self.render_file(x))}
|
||||
</div>
|
||||
`;
|
||||
}
|
||||
}
|
||||
customElements.define('tf-files', TfFilesElement);
|
||||
|
||||
class TfSparkLineElement extends LitElement {
|
||||
static get properties() {
|
||||
return {
|
||||
lines: {type: Array},
|
||||
min: {type: Number},
|
||||
max: {type: Number},
|
||||
};
|
||||
}
|
||||
|
||||
constructor() {
|
||||
super();
|
||||
this.min = 0;
|
||||
this.max = 1.0;
|
||||
this.lines = [];
|
||||
}
|
||||
|
||||
append(key, value) {
|
||||
let line = null;
|
||||
for (let it of this.lines) {
|
||||
if (it.name == key) {
|
||||
line = it;
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (!line) {
|
||||
const k_colors = ['#0f0', '#88f', '#ff0', '#f0f', '#0ff', '#f00', '#888'];
|
||||
line = {
|
||||
name: key,
|
||||
style: k_colors[this.lines.length % k_colors.length],
|
||||
values: [],
|
||||
};
|
||||
this.lines.push(line);
|
||||
}
|
||||
line.values.push(value);
|
||||
if (line.values.length > 100) {
|
||||
line.values.shift();
|
||||
}
|
||||
this.requestUpdate();
|
||||
}
|
||||
|
||||
render_line(line) {
|
||||
if (line?.values?.length >= 2) {
|
||||
let points = [].concat(...line.values.map((x, i) => [100.0 * i / (line.values.length - 1), 10.0 - 10.0 * (x - this.min) / (this.max - this.min)]));
|
||||
return svg`
|
||||
<polyline points=${points.join(' ')} stroke=${line.style} fill="none"/>
|
||||
`;
|
||||
}
|
||||
}
|
||||
|
||||
render() {
|
||||
return html`
|
||||
<svg style="width: 10em; height: 1.4em; vertical-align: top; margin: 0; padding: 0; background: #000" viewBox="0 0 100 10" preserveAspectRatio="none" xmlns="http://www.w3.org/2000/svg">
|
||||
${this.lines.map(x => this.render_line(x))}
|
||||
</svg>
|
||||
`;
|
||||
}
|
||||
}
|
||||
customElements.define('tf-sparkline', TfSparkLineElement);
|
||||
|
||||
window.addEventListener("keydown", function(event) {
|
||||
if (event.keyCode == 83 && (event.altKey || event.ctrlKey)) {
|
||||
if (editing()) {
|
||||
@ -81,14 +321,6 @@ function editing() {
|
||||
return document.getElementById("editPane").style.display != 'none';
|
||||
}
|
||||
|
||||
function toggleEdit() {
|
||||
if (editing()) {
|
||||
closeEditor();
|
||||
} else {
|
||||
edit();
|
||||
}
|
||||
}
|
||||
|
||||
function edit() {
|
||||
if (editing()) {
|
||||
return;
|
||||
@ -107,6 +339,7 @@ function edit() {
|
||||
{tagName: "link", attributes: {rel: "stylesheet", href: "/codemirror/matchesonscrollbar.min.css"}},
|
||||
{tagName: "link", attributes: {rel: "stylesheet", href: "/codemirror/dialog.min.css"}},
|
||||
{tagName: "link", attributes: {rel: "stylesheet", href: "/codemirror/codemirror.min.css"}},
|
||||
{tagName: "link", attributes: {rel: "stylesheet", href: "/codemirror/lint.css"}},
|
||||
{tagName: "script", attributes: {src: "/codemirror/trailingspace.min.js"}},
|
||||
{tagName: "script", attributes: {src: "/codemirror/dialog.min.js"}},
|
||||
{tagName: "script", attributes: {src: "/codemirror/search.min.js"}},
|
||||
@ -118,6 +351,9 @@ function edit() {
|
||||
{tagName: "script", attributes: {src: "/codemirror/css.min.js"}},
|
||||
{tagName: "script", attributes: {src: "/codemirror/xml.min.js"}},
|
||||
{tagName: "script", attributes: {src: "/codemirror/htmlmixed.min.js"}},
|
||||
{tagName: "script", attributes: {src: "/codemirror/lint.js"}},
|
||||
{tagName: "script", attributes: {src: "/codemirror/jshint.js"}},
|
||||
{tagName: "script", attributes: {src: "/codemirror/javascript-lint.min.js"}},
|
||||
], function() {
|
||||
load().catch(function(error) {
|
||||
alert(error);
|
||||
@ -143,13 +379,11 @@ function trace() {
|
||||
function stats() {
|
||||
window.localStorage.setItem('stats', '1');
|
||||
document.getElementById("statsPane").style.display = 'flex';
|
||||
send({action: 'enableStats', enabled: true});
|
||||
}
|
||||
|
||||
function closeStats() {
|
||||
window.localStorage.setItem('stats', '0');
|
||||
document.getElementById("statsPane").style.display = 'none';
|
||||
send({action: 'enableStats', enabled: false});
|
||||
}
|
||||
|
||||
function toggleStats() {
|
||||
@ -200,6 +434,13 @@ function load(path) {
|
||||
'indentUnit': 4,
|
||||
'indentWithTabs': true,
|
||||
'showTrailingSpace': true,
|
||||
'gutters': ['CodeMirror-lint-markers'],
|
||||
'mode': {'js': 'javascript'}[(path || url()).split('.').pop()],
|
||||
'lint': {
|
||||
'options': {
|
||||
'esversion': 2021,
|
||||
},
|
||||
},
|
||||
});
|
||||
gEditor.on('changes', function() {
|
||||
updateFiles();
|
||||
@ -401,7 +642,8 @@ function api_localStorageGet(key) {
|
||||
}
|
||||
|
||||
function api_requestPermission(permission, id) {
|
||||
let permissions = document.getElementById('permissions');
|
||||
let outer = document.createElement('div');
|
||||
outer.classList.add('permissions');
|
||||
|
||||
let container = document.createElement('div');
|
||||
container.classList.add('permissions_contents');
|
||||
@ -445,17 +687,14 @@ function api_requestPermission(permission, id) {
|
||||
button.innerText = option.text;
|
||||
button.onclick = function() {
|
||||
resolve(option.grant[check.checked ? 1 : 0]);
|
||||
while (permissions.firstChild) {
|
||||
permissions.removeChild(permissions.firstChild);
|
||||
}
|
||||
permissions.style.visibility = 'hidden';
|
||||
document.body.removeChild(outer);
|
||||
}
|
||||
div.appendChild(button);
|
||||
}
|
||||
container.appendChild(div);
|
||||
outer.appendChild(container);
|
||||
|
||||
permissions.appendChild(container);
|
||||
permissions.style.visibility = 'visible';
|
||||
document.body.appendChild(outer);
|
||||
});
|
||||
}
|
||||
|
||||
@ -467,85 +706,18 @@ function api_setHash(hash) {
|
||||
window.location.hash = hash;
|
||||
}
|
||||
|
||||
function hidePermissions() {
|
||||
let permissions = document.getElementById('permissions_settings');
|
||||
while (permissions.firstChild) {
|
||||
permissions.removeChild(permissions.firstChild);
|
||||
}
|
||||
permissions.style.visibility = 'hidden';
|
||||
}
|
||||
|
||||
function showPermissions() {
|
||||
let permissions = document.getElementById('permissions_settings');
|
||||
|
||||
let container = document.createElement('div');
|
||||
container.classList.add('permissions_contents');
|
||||
|
||||
let div = document.createElement('div');
|
||||
div.appendChild(document.createTextNode('This app has the following permission:'));
|
||||
for (let key of Object.keys(gPermissions || {})) {
|
||||
let row = document.createElement('div');
|
||||
|
||||
let span = document.createElement('span');
|
||||
span.appendChild(document.createTextNode(key));
|
||||
row.appendChild(span);
|
||||
|
||||
span = document.createElement('span');
|
||||
span.appendChild(document.createTextNode(': '));
|
||||
row.appendChild(span);
|
||||
|
||||
span = document.createElement('span');
|
||||
span.appendChild(document.createTextNode(gPermissions[key] ? '✅ Allowed' : '❌ Denied'));
|
||||
row.appendChild(span);
|
||||
|
||||
span = document.createElement('span');
|
||||
span.appendChild(document.createTextNode(' '));
|
||||
row.appendChild(span);
|
||||
|
||||
let button = document.createElement('button');
|
||||
button.innerText = 'Reset';
|
||||
button.onclick = function() {
|
||||
send({action: "resetPermission", permission: key});
|
||||
};
|
||||
row.appendChild(button);
|
||||
div.appendChild(row);
|
||||
}
|
||||
container.appendChild(div);
|
||||
|
||||
div = document.createElement('div');
|
||||
let button = document.createElement('button');
|
||||
button.innerText = 'Close';
|
||||
button.onclick = function() {
|
||||
hidePermissions();
|
||||
}
|
||||
div.appendChild(button);
|
||||
container.appendChild(div);
|
||||
|
||||
permissions.appendChild(container);
|
||||
permissions.style.visibility = 'visible';
|
||||
}
|
||||
|
||||
function _receive_websocket_message(message) {
|
||||
if (message && message.action == "session") {
|
||||
setStatusMessage("🟢 Executing...", kStatusColor);
|
||||
gCredentials = message.credentials;
|
||||
updateLogin();
|
||||
document.getElementsByTagName('tf-navigation')[0].credentials = message.credentials;
|
||||
} else if (message && message.action == 'permissions') {
|
||||
gPermissions = message.permissions;
|
||||
let permissions = document.getElementById('permissions_settings');
|
||||
if (permissions.firstChild) {
|
||||
hidePermissions();
|
||||
showPermissions();
|
||||
}
|
||||
document.getElementsByTagName('tf-navigation')[0].permissions = message.permissions ?? {};
|
||||
} else if (message && message.action == "ready") {
|
||||
setStatusMessage(null);
|
||||
if (window.location.hash) {
|
||||
send({event: "hashChange", hash: window.location.hash});
|
||||
}
|
||||
if (window.localStorage.getItem('stats') == '1') {
|
||||
/* Stats were opened before we connected. */
|
||||
send({action: 'enableStats', enabled: true});
|
||||
}
|
||||
send({action: 'enableStats', enabled: true});
|
||||
} else if (message && message.action == "ping") {
|
||||
send({action: "pong"});
|
||||
} else if (message && message.action == "stats") {
|
||||
@ -566,8 +738,8 @@ function _receive_websocket_message(message) {
|
||||
tls_malloc_percent: {group: 'memory', name: 'tls'},
|
||||
uv_malloc_percent: {group: 'memory', name: 'uv'},
|
||||
|
||||
messages_stored: {group: 'stored', name: 'messages'},
|
||||
blobs_stored: {group: 'stored', name: 'blobs'},
|
||||
messages_stored: {group: 'store', name: 'messages'},
|
||||
blobs_stored: {group: 'store', name: 'blobs'},
|
||||
|
||||
socket_count: {group: 'socket', name: 'total'},
|
||||
socket_open_count: {group: 'socket', name: 'open'},
|
||||
@ -627,6 +799,16 @@ function _receive_websocket_message(message) {
|
||||
}
|
||||
}
|
||||
timeseries.append(now, message.stats[key]);
|
||||
|
||||
if (graph_key == 'cpu' || graph_key == 'rpc' || graph_key == 'store') {
|
||||
let line = document.getElementsByTagName('tf-navigation')[0].get_spark_line(graph_key, { max: 100 });
|
||||
line.dataset.emoji = {
|
||||
'cpu': '💻',
|
||||
'rpc': '🔁',
|
||||
'store': '💾',
|
||||
}[graph_key];
|
||||
line.append(key, message.stats[key]);
|
||||
}
|
||||
}
|
||||
} else if (message &&
|
||||
message.message === 'tfrpc' &&
|
||||
@ -665,14 +847,7 @@ function keyEvent(event) {
|
||||
}
|
||||
|
||||
function setStatusMessage(message, color) {
|
||||
let node = document.getElementById("status");
|
||||
while (node.firstChild) {
|
||||
node.removeChild(node.firstChild);
|
||||
}
|
||||
if (message) {
|
||||
node.appendChild(document.createTextNode(message));
|
||||
node.setAttribute("style", "display: inline-block; vertical-align: top; white-space: pre; color: " + (color || kErrorColor));
|
||||
}
|
||||
document.getElementsByTagName('tf-navigation')[0].status = {message: message, color: color};
|
||||
}
|
||||
|
||||
function send(value) {
|
||||
@ -685,23 +860,6 @@ function send(value) {
|
||||
}
|
||||
}
|
||||
|
||||
function updateLogin() {
|
||||
let login = document.getElementById("login");
|
||||
while (login.firstChild) {
|
||||
login.removeChild(login.firstChild);
|
||||
}
|
||||
|
||||
let a = document.createElement("a");
|
||||
if (gCredentials && gCredentials.session) {
|
||||
a.appendChild(document.createTextNode("logout " + gCredentials.session.name));
|
||||
a.setAttribute("href", "/login/logout?return=" + encodeURIComponent(url() + hash()));
|
||||
} else {
|
||||
a.appendChild(document.createTextNode("login"));
|
||||
a.setAttribute("href", "/login?return=" + encodeURIComponent(url() + hash()));
|
||||
}
|
||||
login.appendChild(a);
|
||||
}
|
||||
|
||||
function dragHover(event) {
|
||||
event.stopPropagation();
|
||||
event.preventDefault();
|
||||
@ -911,28 +1069,12 @@ function openFile(name) {
|
||||
gEditor.focus();
|
||||
}
|
||||
|
||||
function onFileClicked(event) {
|
||||
openFile(event.target.textContent);
|
||||
}
|
||||
|
||||
function updateFiles() {
|
||||
let node = document.getElementById("files");
|
||||
while (node.firstChild) {
|
||||
node.removeChild(node.firstChild);
|
||||
}
|
||||
|
||||
for (let file of Object.keys(gFiles).sort()) {
|
||||
let li = document.createElement("li");
|
||||
li.onclick = onFileClicked;
|
||||
li.appendChild(document.createTextNode(file));
|
||||
if (file == gCurrentFile) {
|
||||
li.classList.add("current");
|
||||
}
|
||||
if (!gFiles[file].doc.isClean(gFiles[file].generation)) {
|
||||
li.classList.add("dirty");
|
||||
}
|
||||
node.appendChild(li);
|
||||
}
|
||||
let files = document.getElementById("files_list");
|
||||
files.files = Object.fromEntries(Object.keys(gFiles).map(file => [file, {
|
||||
clean: gFiles[file].doc.isClean(gFiles[file].generation),
|
||||
}]));
|
||||
files.current = gCurrentFile;
|
||||
|
||||
gEditor.focus();
|
||||
}
|
||||
@ -966,11 +1108,6 @@ window.addEventListener("load", function() {
|
||||
window.addEventListener("message", message, false);
|
||||
window.addEventListener("online", connectSocket);
|
||||
document.getElementById("name").value = window.location.pathname;
|
||||
document.getElementById('edit_link').addEventListener('click', function(event) {
|
||||
event.preventDefault();
|
||||
toggleEdit();
|
||||
});
|
||||
document.getElementById('show_permissions_link').addEventListener('click', () => showPermissions());
|
||||
document.getElementById('files_hide').addEventListener('click', () => hideFiles());
|
||||
document.getElementById('files_show').addEventListener('click', () => showFiles());
|
||||
document.getElementById('closeStats').addEventListener('click', () => closeStats());
|
||||
@ -979,11 +1116,11 @@ window.addEventListener("load", function() {
|
||||
document.getElementById('icon').addEventListener('click', () => changeIcon());
|
||||
document.getElementById('delete').addEventListener('click', () => deleteApp());
|
||||
document.getElementById('trace_button').addEventListener('click', function(event) {
|
||||
event.preventDefault();
|
||||
event.preventDefault();
|
||||
trace();
|
||||
});
|
||||
document.getElementById('stats_button').addEventListener('click', function(event) {
|
||||
event.preventDefault();
|
||||
event.preventDefault();
|
||||
toggleStats();
|
||||
});
|
||||
document.getElementById('new_file_button').addEventListener('click', () => newFile());
|
||||
@ -1030,4 +1167,5 @@ window.addEventListener("load", function() {
|
||||
} else {
|
||||
closeStats();
|
||||
}
|
||||
document.getElementById('files_list').addEventListener('file_click', event => openFile(event.detail.file));
|
||||
});
|
||||
|
@ -837,6 +837,8 @@ loadSettings().then(function() {
|
||||
return blobHandler(request, response, match[1], match[2]);
|
||||
} else if (match = /^\/([&\%][^\.]{44}(?:\.\w+)?)(\/?.*)/.exec(request.uri)) {
|
||||
return blobHandler(request, response, match[1], match[2]);
|
||||
} else if (match = /^\/static\/lit\/([\.\w-/]*)$/.exec(request.uri)) {
|
||||
return staticDirectoryHandler(request, response, 'deps/lit/', match[1]);
|
||||
} else if (match = /^\/static(\/.*)/.exec(request.uri)) {
|
||||
return staticFileHandler(request, response, null, match[1]);
|
||||
} else if (match = /^\/codemirror\/([\.\w-/]*)$/.exec(request.uri)) {
|
||||
@ -861,6 +863,10 @@ loadSettings().then(function() {
|
||||
let data = JSON.stringify(getDebug(), null, 2);
|
||||
response.writeHead(200, {"Content-Type": "application/json; charset=utf-8", "Content-Length": data.length.toString()});
|
||||
return response.end(data);
|
||||
} else if (match = /^\/hitches$/.exec(request.uri)) {
|
||||
let data = JSON.stringify(getHitches(), null, 2);
|
||||
response.writeHead(200, {"Content-Type": "application/json; charset=utf-8", "Content-Length": data.length.toString()});
|
||||
return response.end(data);
|
||||
} else if (match = /^\/mem$/.exec(request.uri)) {
|
||||
let data = JSON.stringify(getAllocations(), null, 2);
|
||||
response.writeHead(200, {
|
||||
|
@ -7,18 +7,7 @@
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1">
|
||||
</head>
|
||||
<body style="display: flex; flex-flow: column">
|
||||
<div class="navigation">
|
||||
<span>😎</span>
|
||||
<a accesskey="h" data-tip="Open home app." href="/" style="color: #fff">Tilde Friends</a>
|
||||
<a accesskey="a" data-tip="Open apps list." href="/~core/apps/">apps</a>
|
||||
<a accesskey="e" data-tip="Toggle the app editor." href="#" id="edit_link">edit</a>
|
||||
<a accesskey="p" data-tip="View and change permissions." href="#" id="show_permissions_link">🎛️</a>
|
||||
<span id="status"></span>
|
||||
<span id="requests"></span>
|
||||
<span id="permissions_settings"></span>
|
||||
<span id="permissions"></span>
|
||||
<span id="login"></span>
|
||||
</div>
|
||||
<tf-navigation></tf-navigation>
|
||||
<div id="content" class="hbox" style="flex: 1 1; width: 100%">
|
||||
<div id="statsPane" class="vbox" style="display: none; flex 1 1">
|
||||
<div class="hbox">
|
||||
@ -44,6 +33,7 @@
|
||||
<span id="files_show">»</span>
|
||||
</div>
|
||||
<div id="files_content">
|
||||
<tf-files id="files_list"></tf-files>
|
||||
<ul id="files"></ul>
|
||||
<br>
|
||||
<div><button id="new_file_button">New File</button></div>
|
||||
|
@ -15,11 +15,6 @@ body {
|
||||
margin: 0;
|
||||
}
|
||||
|
||||
.navigation {
|
||||
height: auto;
|
||||
margin: 4px;
|
||||
}
|
||||
|
||||
a:link {
|
||||
color: #268bd2;
|
||||
}
|
||||
@ -207,25 +202,6 @@ a:active {
|
||||
display: block;
|
||||
}
|
||||
|
||||
#files {
|
||||
list-style-type: none;
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
}
|
||||
|
||||
#files > li {
|
||||
padding: 0.5em;
|
||||
}
|
||||
|
||||
#files > li.current {
|
||||
font-weight: bold;
|
||||
background-color: #2aa198;
|
||||
}
|
||||
|
||||
#files > li.dirty::after {
|
||||
content: '*';
|
||||
}
|
||||
|
||||
.tooltip {
|
||||
position: absolute;
|
||||
z-index: 1;
|
||||
@ -254,8 +230,7 @@ kbd {
|
||||
white-space: nowrap;
|
||||
}
|
||||
|
||||
#permissions, #permissions_settings {
|
||||
visibility: hidden;
|
||||
.permissions {
|
||||
position: absolute;
|
||||
display: block;
|
||||
top: 0;
|
||||
|
2
deps/codemirror/codemirror.min.js
vendored
2
deps/codemirror/codemirror.min.js
vendored
File diff suppressed because one or more lines are too long
1
deps/codemirror/javascript-lint.min.js
vendored
Normal file
1
deps/codemirror/javascript-lint.min.js
vendored
Normal file
@ -0,0 +1 @@
|
||||
!function(e){"object"==typeof exports&&"object"==typeof module?e(require("../../lib/codemirror")):"function"==typeof define&&define.amd?define(["../../lib/codemirror"],e):e(CodeMirror)}(function(a){"use strict";a.registerHelper("lint","javascript",function(e,r){if(!window.JSHINT)return window.console&&window.console.error("Error: window.JSHINT not defined, CodeMirror JavaScript linting cannot run."),[];if(r.indent||(r.indent=1),JSHINT(e,r,r.globals),e=JSHINT.data().errors,r=[],e)for(var n=e,o=r,i=0;i<n.length;i++){var t,d,s,c=n[i];c&&(c.line<=0?window.console&&window.console.warn("Cannot display JSHint error (invalid line "+c.line+")",c):(t=c.character-1,d=1+t,c.evidence&&-1<(s=c.evidence.substring(t).search(/.\b/))&&(d+=s),s={message:c.reason,severity:c.code&&c.code.startsWith("W")?"warning":"error",from:a.Pos(c.line-1,t),to:a.Pos(c.line-1,d)},o.push(s)))}return r})});
|
2
deps/codemirror/javascript.min.js
vendored
2
deps/codemirror/javascript.min.js
vendored
File diff suppressed because one or more lines are too long
32076
deps/codemirror/jshint.js
vendored
Normal file
32076
deps/codemirror/jshint.js
vendored
Normal file
File diff suppressed because one or more lines are too long
80
deps/codemirror/lint.css
vendored
Normal file
80
deps/codemirror/lint.css
vendored
Normal file
@ -0,0 +1,80 @@
|
||||
/* The lint marker gutter */
|
||||
.CodeMirror-lint-markers {
|
||||
width: 16px;
|
||||
}
|
||||
|
||||
.CodeMirror-lint-tooltip {
|
||||
background-color: #ffd;
|
||||
border: 1px solid black;
|
||||
border-radius: 4px 4px 4px 4px;
|
||||
color: black;
|
||||
font-family: monospace;
|
||||
font-size: 10pt;
|
||||
overflow: hidden;
|
||||
padding: 2px 5px;
|
||||
position: fixed;
|
||||
white-space: pre;
|
||||
white-space: pre-wrap;
|
||||
z-index: 100;
|
||||
max-width: 600px;
|
||||
opacity: 0;
|
||||
transition: opacity .4s;
|
||||
-moz-transition: opacity .4s;
|
||||
-webkit-transition: opacity .4s;
|
||||
-o-transition: opacity .4s;
|
||||
-ms-transition: opacity .4s;
|
||||
}
|
||||
|
||||
.CodeMirror-lint-mark {
|
||||
background-position: left bottom;
|
||||
background-repeat: repeat-x;
|
||||
}
|
||||
|
||||
.CodeMirror-lint-mark-warning {
|
||||
background-image: url("data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAQAAAADCAYAAAC09K7GAAAAAXNSR0IArs4c6QAAAAZiS0dEAP8A/wD/oL2nkwAAAAlwSFlzAAALEwAACxMBAJqcGAAAAAd0SU1FB9sJFhQXEbhTg7YAAAAZdEVYdENvbW1lbnQAQ3JlYXRlZCB3aXRoIEdJTVBXgQ4XAAAAMklEQVQI12NkgIIvJ3QXMjAwdDN+OaEbysDA4MPAwNDNwMCwiOHLCd1zX07o6kBVGQEAKBANtobskNMAAAAASUVORK5CYII=");
|
||||
}
|
||||
|
||||
.CodeMirror-lint-mark-error {
|
||||
background-image: url("data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAQAAAADCAYAAAC09K7GAAAAAXNSR0IArs4c6QAAAAZiS0dEAP8A/wD/oL2nkwAAAAlwSFlzAAALEwAACxMBAJqcGAAAAAd0SU1FB9sJDw4cOCW1/KIAAAAZdEVYdENvbW1lbnQAQ3JlYXRlZCB3aXRoIEdJTVBXgQ4XAAAAHElEQVQI12NggIL/DAz/GdA5/xkY/qPKMDAwAADLZwf5rvm+LQAAAABJRU5ErkJggg==");
|
||||
}
|
||||
|
||||
.CodeMirror-lint-marker {
|
||||
background-position: center center;
|
||||
background-repeat: no-repeat;
|
||||
cursor: pointer;
|
||||
display: inline-block;
|
||||
height: 16px;
|
||||
width: 16px;
|
||||
vertical-align: middle;
|
||||
position: relative;
|
||||
}
|
||||
|
||||
.CodeMirror-lint-message {
|
||||
padding-left: 18px;
|
||||
background-position: top left;
|
||||
background-repeat: no-repeat;
|
||||
}
|
||||
|
||||
.CodeMirror-lint-marker-warning, .CodeMirror-lint-message-warning {
|
||||
background-image: url("data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAMAAAAoLQ9TAAAANlBMVEX/uwDvrwD/uwD/uwD/uwD/uwD/uwD/uwD/uwD6twD/uwAAAADurwD2tQD7uAD+ugAAAAD/uwDhmeTRAAAADHRSTlMJ8mN1EYcbmiixgACm7WbuAAAAVklEQVR42n3PUQqAIBBFUU1LLc3u/jdbOJoW1P08DA9Gba8+YWJ6gNJoNYIBzAA2chBth5kLmG9YUoG0NHAUwFXwO9LuBQL1giCQb8gC9Oro2vp5rncCIY8L8uEx5ZkAAAAASUVORK5CYII=");
|
||||
}
|
||||
|
||||
.CodeMirror-lint-marker-error, .CodeMirror-lint-message-error {
|
||||
background-image: url("data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAMAAAAoLQ9TAAAAHlBMVEW7AAC7AACxAAC7AAC7AAAAAAC4AAC5AAD///+7AAAUdclpAAAABnRSTlMXnORSiwCK0ZKSAAAATUlEQVR42mWPOQ7AQAgDuQLx/z8csYRmPRIFIwRGnosRrpamvkKi0FTIiMASR3hhKW+hAN6/tIWhu9PDWiTGNEkTtIOucA5Oyr9ckPgAWm0GPBog6v4AAAAASUVORK5CYII=");
|
||||
}
|
||||
|
||||
.CodeMirror-lint-marker-multiple {
|
||||
background-image: url("data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAcAAAAHCAMAAADzjKfhAAAACVBMVEUAAAAAAAC/v7914kyHAAAAAXRSTlMAQObYZgAAACNJREFUeNo1ioEJAAAIwmz/H90iFFSGJgFMe3gaLZ0od+9/AQZ0ADosbYraAAAAAElFTkSuQmCC");
|
||||
background-repeat: no-repeat;
|
||||
background-position: right bottom;
|
||||
width: 100%; height: 100%;
|
||||
}
|
||||
|
||||
.CodeMirror-lint-line-error {
|
||||
background-color: rgba(183, 76, 81, 0.08);
|
||||
}
|
||||
|
||||
.CodeMirror-lint-line-warning {
|
||||
background-color: rgba(255, 211, 0, 0.1);
|
||||
}
|
||||
|
292
deps/codemirror/lint.js
vendored
Normal file
292
deps/codemirror/lint.js
vendored
Normal file
@ -0,0 +1,292 @@
|
||||
// CodeMirror, copyright (c) by Marijn Haverbeke and others
|
||||
// Distributed under an MIT license: https://codemirror.net/5/LICENSE
|
||||
|
||||
(function(mod) {
|
||||
if (typeof exports == "object" && typeof module == "object") // CommonJS
|
||||
mod(require("../../lib/codemirror"));
|
||||
else if (typeof define == "function" && define.amd) // AMD
|
||||
define(["../../lib/codemirror"], mod);
|
||||
else // Plain browser env
|
||||
mod(CodeMirror);
|
||||
})(function(CodeMirror) {
|
||||
"use strict";
|
||||
var GUTTER_ID = "CodeMirror-lint-markers";
|
||||
var LINT_LINE_ID = "CodeMirror-lint-line-";
|
||||
|
||||
function showTooltip(cm, e, content) {
|
||||
var tt = document.createElement("div");
|
||||
tt.className = "CodeMirror-lint-tooltip cm-s-" + cm.options.theme;
|
||||
tt.appendChild(content.cloneNode(true));
|
||||
if (cm.state.lint.options.selfContain)
|
||||
cm.getWrapperElement().appendChild(tt);
|
||||
else
|
||||
document.body.appendChild(tt);
|
||||
|
||||
function position(e) {
|
||||
if (!tt.parentNode) return CodeMirror.off(document, "mousemove", position);
|
||||
tt.style.top = Math.max(0, e.clientY - tt.offsetHeight - 5) + "px";
|
||||
tt.style.left = (e.clientX + 5) + "px";
|
||||
}
|
||||
CodeMirror.on(document, "mousemove", position);
|
||||
position(e);
|
||||
if (tt.style.opacity != null) tt.style.opacity = 1;
|
||||
return tt;
|
||||
}
|
||||
function rm(elt) {
|
||||
if (elt.parentNode) elt.parentNode.removeChild(elt);
|
||||
}
|
||||
function hideTooltip(tt) {
|
||||
if (!tt.parentNode) return;
|
||||
if (tt.style.opacity == null) rm(tt);
|
||||
tt.style.opacity = 0;
|
||||
setTimeout(function() { rm(tt); }, 600);
|
||||
}
|
||||
|
||||
function showTooltipFor(cm, e, content, node) {
|
||||
var tooltip = showTooltip(cm, e, content);
|
||||
function hide() {
|
||||
CodeMirror.off(node, "mouseout", hide);
|
||||
if (tooltip) { hideTooltip(tooltip); tooltip = null; }
|
||||
}
|
||||
var poll = setInterval(function() {
|
||||
if (tooltip) for (var n = node;; n = n.parentNode) {
|
||||
if (n && n.nodeType == 11) n = n.host;
|
||||
if (n == document.body) return;
|
||||
if (!n) { hide(); break; }
|
||||
}
|
||||
if (!tooltip) return clearInterval(poll);
|
||||
}, 400);
|
||||
CodeMirror.on(node, "mouseout", hide);
|
||||
}
|
||||
|
||||
function LintState(cm, conf, hasGutter) {
|
||||
this.marked = [];
|
||||
if (conf instanceof Function) conf = {getAnnotations: conf};
|
||||
if (!conf || conf === true) conf = {};
|
||||
this.options = {};
|
||||
this.linterOptions = conf.options || {};
|
||||
for (var prop in defaults) this.options[prop] = defaults[prop];
|
||||
for (var prop in conf) {
|
||||
if (defaults.hasOwnProperty(prop)) {
|
||||
if (conf[prop] != null) this.options[prop] = conf[prop];
|
||||
} else if (!conf.options) {
|
||||
this.linterOptions[prop] = conf[prop];
|
||||
}
|
||||
}
|
||||
this.timeout = null;
|
||||
this.hasGutter = hasGutter;
|
||||
this.onMouseOver = function(e) { onMouseOver(cm, e); };
|
||||
this.waitingFor = 0
|
||||
}
|
||||
|
||||
var defaults = {
|
||||
highlightLines: false,
|
||||
tooltips: true,
|
||||
delay: 500,
|
||||
lintOnChange: true,
|
||||
getAnnotations: null,
|
||||
async: false,
|
||||
selfContain: null,
|
||||
formatAnnotation: null,
|
||||
onUpdateLinting: null
|
||||
}
|
||||
|
||||
function clearMarks(cm) {
|
||||
var state = cm.state.lint;
|
||||
if (state.hasGutter) cm.clearGutter(GUTTER_ID);
|
||||
if (state.options.highlightLines) clearErrorLines(cm);
|
||||
for (var i = 0; i < state.marked.length; ++i)
|
||||
state.marked[i].clear();
|
||||
state.marked.length = 0;
|
||||
}
|
||||
|
||||
function clearErrorLines(cm) {
|
||||
cm.eachLine(function(line) {
|
||||
var has = line.wrapClass && /\bCodeMirror-lint-line-\w+\b/.exec(line.wrapClass);
|
||||
if (has) cm.removeLineClass(line, "wrap", has[0]);
|
||||
})
|
||||
}
|
||||
|
||||
function makeMarker(cm, labels, severity, multiple, tooltips) {
|
||||
var marker = document.createElement("div"), inner = marker;
|
||||
marker.className = "CodeMirror-lint-marker CodeMirror-lint-marker-" + severity;
|
||||
if (multiple) {
|
||||
inner = marker.appendChild(document.createElement("div"));
|
||||
inner.className = "CodeMirror-lint-marker CodeMirror-lint-marker-multiple";
|
||||
}
|
||||
|
||||
if (tooltips != false) CodeMirror.on(inner, "mouseover", function(e) {
|
||||
showTooltipFor(cm, e, labels, inner);
|
||||
});
|
||||
|
||||
return marker;
|
||||
}
|
||||
|
||||
function getMaxSeverity(a, b) {
|
||||
if (a == "error") return a;
|
||||
else return b;
|
||||
}
|
||||
|
||||
function groupByLine(annotations) {
|
||||
var lines = [];
|
||||
for (var i = 0; i < annotations.length; ++i) {
|
||||
var ann = annotations[i], line = ann.from.line;
|
||||
(lines[line] || (lines[line] = [])).push(ann);
|
||||
}
|
||||
return lines;
|
||||
}
|
||||
|
||||
function annotationTooltip(ann) {
|
||||
var severity = ann.severity;
|
||||
if (!severity) severity = "error";
|
||||
var tip = document.createElement("div");
|
||||
tip.className = "CodeMirror-lint-message CodeMirror-lint-message-" + severity;
|
||||
if (typeof ann.messageHTML != 'undefined') {
|
||||
tip.innerHTML = ann.messageHTML;
|
||||
} else {
|
||||
tip.appendChild(document.createTextNode(ann.message));
|
||||
}
|
||||
return tip;
|
||||
}
|
||||
|
||||
function lintAsync(cm, getAnnotations) {
|
||||
var state = cm.state.lint
|
||||
var id = ++state.waitingFor
|
||||
function abort() {
|
||||
id = -1
|
||||
cm.off("change", abort)
|
||||
}
|
||||
cm.on("change", abort)
|
||||
getAnnotations(cm.getValue(), function(annotations, arg2) {
|
||||
cm.off("change", abort)
|
||||
if (state.waitingFor != id) return
|
||||
if (arg2 && annotations instanceof CodeMirror) annotations = arg2
|
||||
cm.operation(function() {updateLinting(cm, annotations)})
|
||||
}, state.linterOptions, cm);
|
||||
}
|
||||
|
||||
function startLinting(cm) {
|
||||
var state = cm.state.lint;
|
||||
if (!state) return;
|
||||
var options = state.options;
|
||||
/*
|
||||
* Passing rules in `options` property prevents JSHint (and other linters) from complaining
|
||||
* about unrecognized rules like `onUpdateLinting`, `delay`, `lintOnChange`, etc.
|
||||
*/
|
||||
var getAnnotations = options.getAnnotations || cm.getHelper(CodeMirror.Pos(0, 0), "lint");
|
||||
if (!getAnnotations) return;
|
||||
if (options.async || getAnnotations.async) {
|
||||
lintAsync(cm, getAnnotations)
|
||||
} else {
|
||||
var annotations = getAnnotations(cm.getValue(), state.linterOptions, cm);
|
||||
if (!annotations) return;
|
||||
if (annotations.then) annotations.then(function(issues) {
|
||||
cm.operation(function() {updateLinting(cm, issues)})
|
||||
});
|
||||
else cm.operation(function() {updateLinting(cm, annotations)})
|
||||
}
|
||||
}
|
||||
|
||||
function updateLinting(cm, annotationsNotSorted) {
|
||||
var state = cm.state.lint;
|
||||
if (!state) return;
|
||||
var options = state.options;
|
||||
clearMarks(cm);
|
||||
|
||||
var annotations = groupByLine(annotationsNotSorted);
|
||||
|
||||
for (var line = 0; line < annotations.length; ++line) {
|
||||
var anns = annotations[line];
|
||||
if (!anns) continue;
|
||||
|
||||
// filter out duplicate messages
|
||||
var message = [];
|
||||
anns = anns.filter(function(item) { return message.indexOf(item.message) > -1 ? false : message.push(item.message) });
|
||||
|
||||
var maxSeverity = null;
|
||||
var tipLabel = state.hasGutter && document.createDocumentFragment();
|
||||
|
||||
for (var i = 0; i < anns.length; ++i) {
|
||||
var ann = anns[i];
|
||||
var severity = ann.severity;
|
||||
if (!severity) severity = "error";
|
||||
maxSeverity = getMaxSeverity(maxSeverity, severity);
|
||||
|
||||
if (options.formatAnnotation) ann = options.formatAnnotation(ann);
|
||||
if (state.hasGutter) tipLabel.appendChild(annotationTooltip(ann));
|
||||
|
||||
if (ann.to) state.marked.push(cm.markText(ann.from, ann.to, {
|
||||
className: "CodeMirror-lint-mark CodeMirror-lint-mark-" + severity,
|
||||
__annotation: ann
|
||||
}));
|
||||
}
|
||||
// use original annotations[line] to show multiple messages
|
||||
if (state.hasGutter)
|
||||
cm.setGutterMarker(line, GUTTER_ID, makeMarker(cm, tipLabel, maxSeverity, annotations[line].length > 1,
|
||||
options.tooltips));
|
||||
|
||||
if (options.highlightLines)
|
||||
cm.addLineClass(line, "wrap", LINT_LINE_ID + maxSeverity);
|
||||
}
|
||||
if (options.onUpdateLinting) options.onUpdateLinting(annotationsNotSorted, annotations, cm);
|
||||
}
|
||||
|
||||
function onChange(cm) {
|
||||
var state = cm.state.lint;
|
||||
if (!state) return;
|
||||
clearTimeout(state.timeout);
|
||||
state.timeout = setTimeout(function(){startLinting(cm);}, state.options.delay);
|
||||
}
|
||||
|
||||
function popupTooltips(cm, annotations, e) {
|
||||
var target = e.target || e.srcElement;
|
||||
var tooltip = document.createDocumentFragment();
|
||||
for (var i = 0; i < annotations.length; i++) {
|
||||
var ann = annotations[i];
|
||||
tooltip.appendChild(annotationTooltip(ann));
|
||||
}
|
||||
showTooltipFor(cm, e, tooltip, target);
|
||||
}
|
||||
|
||||
function onMouseOver(cm, e) {
|
||||
var target = e.target || e.srcElement;
|
||||
if (!/\bCodeMirror-lint-mark-/.test(target.className)) return;
|
||||
var box = target.getBoundingClientRect(), x = (box.left + box.right) / 2, y = (box.top + box.bottom) / 2;
|
||||
var spans = cm.findMarksAt(cm.coordsChar({left: x, top: y}, "client"));
|
||||
|
||||
var annotations = [];
|
||||
for (var i = 0; i < spans.length; ++i) {
|
||||
var ann = spans[i].__annotation;
|
||||
if (ann) annotations.push(ann);
|
||||
}
|
||||
if (annotations.length) popupTooltips(cm, annotations, e);
|
||||
}
|
||||
|
||||
CodeMirror.defineOption("lint", false, function(cm, val, old) {
|
||||
if (old && old != CodeMirror.Init) {
|
||||
clearMarks(cm);
|
||||
if (cm.state.lint.options.lintOnChange !== false)
|
||||
cm.off("change", onChange);
|
||||
CodeMirror.off(cm.getWrapperElement(), "mouseover", cm.state.lint.onMouseOver);
|
||||
clearTimeout(cm.state.lint.timeout);
|
||||
delete cm.state.lint;
|
||||
}
|
||||
|
||||
if (val) {
|
||||
var gutters = cm.getOption("gutters"), hasLintGutter = false;
|
||||
for (var i = 0; i < gutters.length; ++i) if (gutters[i] == GUTTER_ID) hasLintGutter = true;
|
||||
var state = cm.state.lint = new LintState(cm, val, hasLintGutter);
|
||||
if (state.options.lintOnChange)
|
||||
cm.on("change", onChange);
|
||||
if (state.options.tooltips != false && state.options.tooltips != "gutter")
|
||||
CodeMirror.on(cm.getWrapperElement(), "mouseover", state.onMouseOver);
|
||||
|
||||
startLinting(cm);
|
||||
}
|
||||
});
|
||||
|
||||
CodeMirror.defineExtension("performLint", function() {
|
||||
startLinting(this);
|
||||
});
|
||||
});
|
||||
|
22
deps/codemirror/update.sh
vendored
Executable file
22
deps/codemirror/update.sh
vendored
Executable file
@ -0,0 +1,22 @@
|
||||
LINKS="
|
||||
https://cdnjs.cloudflare.com/ajax/libs/codemirror/5.65.12/addon/dialog/dialog.min.css
|
||||
https://cdnjs.cloudflare.com/ajax/libs/codemirror/5.65.12/addon/dialog/dialog.min.js
|
||||
https://cdnjs.cloudflare.com/ajax/libs/codemirror/5.65.12/addon/edit/trailingspace.min.js
|
||||
https://cdnjs.cloudflare.com/ajax/libs/codemirror/5.65.12/addon/lint/javascript-lint.min.js
|
||||
https://cdnjs.cloudflare.com/ajax/libs/codemirror/5.65.12/addon/scroll/annotatescrollbar.min.js
|
||||
https://cdnjs.cloudflare.com/ajax/libs/codemirror/5.65.12/addon/search/matchesonscrollbar.min.css
|
||||
https://cdnjs.cloudflare.com/ajax/libs/codemirror/5.65.12/addon/search/matchesonscrollbar.min.js
|
||||
https://cdnjs.cloudflare.com/ajax/libs/codemirror/5.65.12/addon/search/search.min.js
|
||||
https://cdnjs.cloudflare.com/ajax/libs/codemirror/5.65.12/addon/search/searchcursor.min.js
|
||||
https://cdnjs.cloudflare.com/ajax/libs/codemirror/5.65.12/codemirror.min.css
|
||||
https://cdnjs.cloudflare.com/ajax/libs/codemirror/5.65.12/codemirror.min.js
|
||||
https://cdnjs.cloudflare.com/ajax/libs/codemirror/5.65.12/mode/css/css.min.js
|
||||
https://cdnjs.cloudflare.com/ajax/libs/codemirror/5.65.12/mode/htmlmixed/htmlmixed.min.js
|
||||
https://cdnjs.cloudflare.com/ajax/libs/codemirror/5.65.12/mode/javascript/javascript.min.js
|
||||
https://cdnjs.cloudflare.com/ajax/libs/codemirror/5.65.12/mode/xml/xml.min.js
|
||||
https://cdnjs.cloudflare.com/ajax/libs/codemirror/5.65.12/theme/base16-dark.min.css
|
||||
"
|
||||
|
||||
for link in $LINKS; do
|
||||
wget $link -O `basename $link`
|
||||
done
|
126
deps/lit/lit-all.min.js
vendored
Normal file
126
deps/lit/lit-all.min.js
vendored
Normal file
File diff suppressed because one or more lines are too long
1
deps/lit/lit-all.min.js.map
vendored
Normal file
1
deps/lit/lit-all.min.js.map
vendored
Normal file
File diff suppressed because one or more lines are too long
10
deps/sqlite/shell.c
vendored
10
deps/sqlite/shell.c
vendored
@ -13023,10 +13023,14 @@ static int dbdataNext(sqlite3_vtab_cursor *pCursor){
|
||||
if( pCsr->bOnePage==0 && pCsr->iPgno>pCsr->szDb ) return SQLITE_OK;
|
||||
rc = dbdataLoadPage(pCsr, pCsr->iPgno, &pCsr->aPage, &pCsr->nPage);
|
||||
if( rc!=SQLITE_OK ) return rc;
|
||||
if( pCsr->aPage ) break;
|
||||
if( pCsr->aPage && pCsr->nPage>=256 ) break;
|
||||
sqlite3_free(pCsr->aPage);
|
||||
pCsr->aPage = 0;
|
||||
if( pCsr->bOnePage ) return SQLITE_OK;
|
||||
pCsr->iPgno++;
|
||||
}
|
||||
|
||||
assert( iOff+3+2<=pCsr->nPage );
|
||||
pCsr->iCell = pTab->bPtr ? -2 : 0;
|
||||
pCsr->nCell = get_uint16(&pCsr->aPage[iOff+3]);
|
||||
}
|
||||
@ -13261,8 +13265,7 @@ static int dbdataGetEncoding(DbdataCursor *pCsr){
|
||||
int nPg1 = 0;
|
||||
u8 *aPg1 = 0;
|
||||
rc = dbdataLoadPage(pCsr, 1, &aPg1, &nPg1);
|
||||
assert( rc!=SQLITE_OK || nPg1==0 || nPg1>=512 );
|
||||
if( rc==SQLITE_OK && nPg1>0 ){
|
||||
if( rc==SQLITE_OK && nPg1>=(56+4) ){
|
||||
pCsr->enc = get_uint32(&aPg1[56]);
|
||||
}
|
||||
sqlite3_free(aPg1);
|
||||
@ -17921,6 +17924,7 @@ static char *shell_error_context(const char *zSql, sqlite3 *db){
|
||||
if( db==0
|
||||
|| zSql==0
|
||||
|| (iOffset = sqlite3_error_offset(db))<0
|
||||
|| iOffset>=strlen(zSql)
|
||||
){
|
||||
return sqlite3_mprintf("");
|
||||
}
|
||||
|
100
deps/sqlite/sqlite3.c
vendored
100
deps/sqlite/sqlite3.c
vendored
@ -1,6 +1,6 @@
|
||||
/******************************************************************************
|
||||
** This file is an amalgamation of many separate C source files from SQLite
|
||||
** version 3.41.1. By combining all the individual C code files into this
|
||||
** version 3.41.2. By combining all the individual C code files into this
|
||||
** single large file, the entire code can be compiled as a single translation
|
||||
** unit. This allows many compilers to do optimizations that would not be
|
||||
** possible if the files were compiled separately. Performance improvements
|
||||
@ -452,9 +452,9 @@ extern "C" {
|
||||
** [sqlite3_libversion_number()], [sqlite3_sourceid()],
|
||||
** [sqlite_version()] and [sqlite_source_id()].
|
||||
*/
|
||||
#define SQLITE_VERSION "3.41.1"
|
||||
#define SQLITE_VERSION_NUMBER 3041001
|
||||
#define SQLITE_SOURCE_ID "2023-03-10 12:13:52 20399f3eda5ec249d147ba9e48da6e87f969d7966a9a896764ca437ff7e737ff"
|
||||
#define SQLITE_VERSION "3.41.2"
|
||||
#define SQLITE_VERSION_NUMBER 3041002
|
||||
#define SQLITE_SOURCE_ID "2023-03-22 11:56:21 0d1fc92f94cb6b76bffe3ec34d69cffde2924203304e8ffc4155597af0c191da"
|
||||
|
||||
/*
|
||||
** CAPI3REF: Run-Time Library Version Numbers
|
||||
@ -16592,7 +16592,7 @@ struct PgHdr {
|
||||
** private to pcache.c and should not be accessed by other modules.
|
||||
** pCache is grouped with the public elements for efficiency.
|
||||
*/
|
||||
i16 nRef; /* Number of users of this page */
|
||||
i64 nRef; /* Number of users of this page */
|
||||
PgHdr *pDirtyNext; /* Next element in list of dirty pages */
|
||||
PgHdr *pDirtyPrev; /* Previous element in list of dirty pages */
|
||||
/* NB: pDirtyNext and pDirtyPrev are undefined if the
|
||||
@ -16673,12 +16673,12 @@ SQLITE_PRIVATE void sqlite3PcacheClearSyncFlags(PCache *);
|
||||
SQLITE_PRIVATE void sqlite3PcacheClear(PCache*);
|
||||
|
||||
/* Return the total number of outstanding page references */
|
||||
SQLITE_PRIVATE int sqlite3PcacheRefCount(PCache*);
|
||||
SQLITE_PRIVATE i64 sqlite3PcacheRefCount(PCache*);
|
||||
|
||||
/* Increment the reference count of an existing page */
|
||||
SQLITE_PRIVATE void sqlite3PcacheRef(PgHdr*);
|
||||
|
||||
SQLITE_PRIVATE int sqlite3PcachePageRefcount(PgHdr*);
|
||||
SQLITE_PRIVATE i64 sqlite3PcachePageRefcount(PgHdr*);
|
||||
|
||||
/* Return the total number of pages stored in the cache */
|
||||
SQLITE_PRIVATE int sqlite3PcachePagecount(PCache*);
|
||||
@ -18837,7 +18837,7 @@ struct NameContext {
|
||||
#define NC_HasAgg 0x000010 /* One or more aggregate functions seen */
|
||||
#define NC_IdxExpr 0x000020 /* True if resolving columns of CREATE INDEX */
|
||||
#define NC_SelfRef 0x00002e /* Combo: PartIdx, isCheck, GenCol, and IdxExpr */
|
||||
#define NC_VarSelect 0x000040 /* A correlated subquery has been seen */
|
||||
#define NC_Subquery 0x000040 /* A subquery has been seen */
|
||||
#define NC_UEList 0x000080 /* True if uNC.pEList is used */
|
||||
#define NC_UAggInfo 0x000100 /* True if uNC.pAggInfo is used */
|
||||
#define NC_UUpsert 0x000200 /* True if uNC.pUpsert is used */
|
||||
@ -19208,6 +19208,9 @@ struct Parse {
|
||||
u8 withinRJSubrtn; /* Nesting level for RIGHT JOIN body subroutines */
|
||||
#if defined(SQLITE_DEBUG) || defined(SQLITE_COVERAGE_TEST)
|
||||
u8 earlyCleanup; /* OOM inside sqlite3ParserAddCleanup() */
|
||||
#endif
|
||||
#ifdef SQLITE_DEBUG
|
||||
u8 ifNotExists; /* Might be true if IF NOT EXISTS. Assert()s only */
|
||||
#endif
|
||||
int nRangeReg; /* Size of the temporary register block */
|
||||
int iRangeReg; /* First register in temporary register block */
|
||||
@ -52654,7 +52657,7 @@ bitvec_end:
|
||||
struct PCache {
|
||||
PgHdr *pDirty, *pDirtyTail; /* List of dirty pages in LRU order */
|
||||
PgHdr *pSynced; /* Last synced page in dirty page list */
|
||||
int nRefSum; /* Sum of ref counts over all pages */
|
||||
i64 nRefSum; /* Sum of ref counts over all pages */
|
||||
int szCache; /* Configured cache size */
|
||||
int szSpill; /* Size before spilling occurs */
|
||||
int szPage; /* Size of every page in this cache */
|
||||
@ -52684,7 +52687,7 @@ struct PCache {
|
||||
unsigned char *a;
|
||||
int j;
|
||||
pPg = (PgHdr*)pLower->pExtra;
|
||||
printf("%3d: nRef %2d flgs %02x data ", i, pPg->nRef, pPg->flags);
|
||||
printf("%3lld: nRef %2d flgs %02x data ", i, pPg->nRef, pPg->flags);
|
||||
a = (unsigned char *)pLower->pBuf;
|
||||
for(j=0; j<12; j++) printf("%02x", a[j]);
|
||||
printf(" ptr %p\n", pPg);
|
||||
@ -53428,14 +53431,14 @@ SQLITE_PRIVATE PgHdr *sqlite3PcacheDirtyList(PCache *pCache){
|
||||
** This is not the total number of pages referenced, but the sum of the
|
||||
** reference count for all pages.
|
||||
*/
|
||||
SQLITE_PRIVATE int sqlite3PcacheRefCount(PCache *pCache){
|
||||
SQLITE_PRIVATE i64 sqlite3PcacheRefCount(PCache *pCache){
|
||||
return pCache->nRefSum;
|
||||
}
|
||||
|
||||
/*
|
||||
** Return the number of references to the page supplied as an argument.
|
||||
*/
|
||||
SQLITE_PRIVATE int sqlite3PcachePageRefcount(PgHdr *p){
|
||||
SQLITE_PRIVATE i64 sqlite3PcachePageRefcount(PgHdr *p){
|
||||
return p->nRef;
|
||||
}
|
||||
|
||||
@ -74505,7 +74508,7 @@ static SQLITE_NOINLINE int btreeNext(BtCursor *pCur){
|
||||
|
||||
pPage = pCur->pPage;
|
||||
idx = ++pCur->ix;
|
||||
if( NEVER(!pPage->isInit) || sqlite3FaultSim(412) ){
|
||||
if( !pPage->isInit || sqlite3FaultSim(412) ){
|
||||
return SQLITE_CORRUPT_BKPT;
|
||||
}
|
||||
|
||||
@ -77634,6 +77637,7 @@ SQLITE_PRIVATE int sqlite3BtreeInsert(
|
||||
assert( szNew==pPage->xCellSize(pPage, newCell) );
|
||||
assert( szNew <= MX_CELL_SIZE(p->pBt) );
|
||||
idx = pCur->ix;
|
||||
pCur->info.nSize = 0;
|
||||
if( loc==0 ){
|
||||
CellInfo info;
|
||||
assert( idx>=0 );
|
||||
@ -77706,7 +77710,6 @@ SQLITE_PRIVATE int sqlite3BtreeInsert(
|
||||
** larger than the largest existing key, it is possible to insert the
|
||||
** row without seeking the cursor. This can be a big performance boost.
|
||||
*/
|
||||
pCur->info.nSize = 0;
|
||||
if( pPage->nOverflow ){
|
||||
assert( rc==SQLITE_OK );
|
||||
pCur->curFlags &= ~(BTCF_ValidNKey);
|
||||
@ -104800,8 +104803,8 @@ static int resolveExprStep(Walker *pWalker, Expr *pExpr){
|
||||
assert( pNC->nRef>=nRef );
|
||||
if( nRef!=pNC->nRef ){
|
||||
ExprSetProperty(pExpr, EP_VarSelect);
|
||||
pNC->ncFlags |= NC_VarSelect;
|
||||
}
|
||||
pNC->ncFlags |= NC_Subquery;
|
||||
}
|
||||
break;
|
||||
}
|
||||
@ -109550,6 +109553,7 @@ SQLITE_PRIVATE void sqlite3ExprCodeGeneratedColumn(
|
||||
){
|
||||
int iAddr;
|
||||
Vdbe *v = pParse->pVdbe;
|
||||
int nErr = pParse->nErr;
|
||||
assert( v!=0 );
|
||||
assert( pParse->iSelfTab!=0 );
|
||||
if( pParse->iSelfTab>0 ){
|
||||
@ -109562,6 +109566,7 @@ SQLITE_PRIVATE void sqlite3ExprCodeGeneratedColumn(
|
||||
sqlite3VdbeAddOp4(v, OP_Affinity, regOut, 1, 0, &pCol->affinity, 1);
|
||||
}
|
||||
if( iAddr ) sqlite3VdbeJumpHere(v, iAddr);
|
||||
if( pParse->nErr>nErr ) pParse->db->errByteOffset = -1;
|
||||
}
|
||||
#endif /* SQLITE_OMIT_GENERATED_COLUMNS */
|
||||
|
||||
@ -109578,6 +109583,7 @@ SQLITE_PRIVATE void sqlite3ExprCodeGetColumnOfTable(
|
||||
Column *pCol;
|
||||
assert( v!=0 );
|
||||
assert( pTab!=0 );
|
||||
assert( iCol!=XN_EXPR );
|
||||
if( iCol<0 || iCol==pTab->iPKey ){
|
||||
sqlite3VdbeAddOp2(v, OP_Rowid, iTabCur, regOut);
|
||||
VdbeComment((v, "%s.rowid", pTab->zName));
|
||||
@ -118933,7 +118939,7 @@ SQLITE_PRIVATE void sqlite3AddReturning(Parse *pParse, ExprList *pList){
|
||||
if( pParse->pNewTrigger ){
|
||||
sqlite3ErrorMsg(pParse, "cannot use RETURNING in a trigger");
|
||||
}else{
|
||||
assert( pParse->bReturning==0 );
|
||||
assert( pParse->bReturning==0 || pParse->ifNotExists );
|
||||
}
|
||||
pParse->bReturning = 1;
|
||||
pRet = sqlite3DbMallocZero(db, sizeof(*pRet));
|
||||
@ -118959,7 +118965,8 @@ SQLITE_PRIVATE void sqlite3AddReturning(Parse *pParse, ExprList *pList){
|
||||
pRet->retTStep.pTrig = &pRet->retTrig;
|
||||
pRet->retTStep.pExprList = pList;
|
||||
pHash = &(db->aDb[1].pSchema->trigHash);
|
||||
assert( sqlite3HashFind(pHash, RETURNING_TRIGGER_NAME)==0 || pParse->nErr );
|
||||
assert( sqlite3HashFind(pHash, RETURNING_TRIGGER_NAME)==0
|
||||
|| pParse->nErr || pParse->ifNotExists );
|
||||
if( sqlite3HashInsert(pHash, RETURNING_TRIGGER_NAME, &pRet->retTrig)
|
||||
==&pRet->retTrig ){
|
||||
sqlite3OomFault(db);
|
||||
@ -124201,7 +124208,7 @@ SQLITE_PRIVATE void sqlite3DeleteFrom(
|
||||
#endif /* SQLITE_OMIT_TRUNCATE_OPTIMIZATION */
|
||||
{
|
||||
u16 wcf = WHERE_ONEPASS_DESIRED|WHERE_DUPLICATES_OK;
|
||||
if( sNC.ncFlags & NC_VarSelect ) bComplex = 1;
|
||||
if( sNC.ncFlags & NC_Subquery ) bComplex = 1;
|
||||
wcf |= (bComplex ? 0 : WHERE_ONEPASS_MULTIROW);
|
||||
if( HasRowid(pTab) ){
|
||||
/* For a rowid table, initialize the RowSet to an empty set */
|
||||
@ -142131,7 +142138,9 @@ static Expr *substExpr(
|
||||
sqlite3VectorErrorMsg(pSubst->pParse, pCopy);
|
||||
}else{
|
||||
sqlite3 *db = pSubst->pParse->db;
|
||||
if( pSubst->isOuterJoin ){
|
||||
if( pSubst->isOuterJoin
|
||||
&& (pCopy->op!=TK_COLUMN || pCopy->iTable!=pSubst->iNewTable)
|
||||
){
|
||||
memset(&ifNullRow, 0, sizeof(ifNullRow));
|
||||
ifNullRow.op = TK_IF_NULL_ROW;
|
||||
ifNullRow.pLeft = pCopy;
|
||||
@ -146893,6 +146902,7 @@ SQLITE_PRIVATE void sqlite3BeginTrigger(
|
||||
}else{
|
||||
assert( !db->init.busy );
|
||||
sqlite3CodeVerifySchema(pParse, iDb);
|
||||
VVA_ONLY( pParse->ifNotExists = 1; )
|
||||
}
|
||||
goto trigger_cleanup;
|
||||
}
|
||||
@ -148896,12 +148906,22 @@ SQLITE_PRIVATE void sqlite3Update(
|
||||
/* Begin the database scan.
|
||||
**
|
||||
** Do not consider a single-pass strategy for a multi-row update if
|
||||
** there are any triggers or foreign keys to process, or rows may
|
||||
** be deleted as a result of REPLACE conflict handling. Any of these
|
||||
** things might disturb a cursor being used to scan through the table
|
||||
** or index, causing a single-pass approach to malfunction. */
|
||||
** there is anything that might disrupt the cursor being used to do
|
||||
** the UPDATE:
|
||||
** (1) This is a nested UPDATE
|
||||
** (2) There are triggers
|
||||
** (3) There are FOREIGN KEY constraints
|
||||
** (4) There are REPLACE conflict handlers
|
||||
** (5) There are subqueries in the WHERE clause
|
||||
*/
|
||||
flags = WHERE_ONEPASS_DESIRED;
|
||||
if( !pParse->nested && !pTrigger && !hasFK && !chngKey && !bReplace ){
|
||||
if( !pParse->nested
|
||||
&& !pTrigger
|
||||
&& !hasFK
|
||||
&& !chngKey
|
||||
&& !bReplace
|
||||
&& (sNC.ncFlags & NC_Subquery)==0
|
||||
){
|
||||
flags |= WHERE_ONEPASS_MULTIROW;
|
||||
}
|
||||
pWInfo = sqlite3WhereBegin(pParse, pTabList, pWhere,0,0,0,flags,iIdxCur);
|
||||
@ -150866,7 +150886,9 @@ static int vtabCallConstructor(
|
||||
sCtx.pPrior = db->pVtabCtx;
|
||||
sCtx.bDeclared = 0;
|
||||
db->pVtabCtx = &sCtx;
|
||||
pTab->nTabRef++;
|
||||
rc = xConstruct(db, pMod->pAux, nArg, azArg, &pVTable->pVtab, &zErr);
|
||||
sqlite3DeleteTable(db, pTab);
|
||||
db->pVtabCtx = sCtx.pPrior;
|
||||
if( rc==SQLITE_NOMEM ) sqlite3OomFault(db);
|
||||
assert( sCtx.pTab==pTab );
|
||||
@ -158035,6 +158057,10 @@ static SQLITE_NOINLINE void sqlite3ConstructBloomFilter(
|
||||
Vdbe *v = pParse->pVdbe; /* VDBE under construction */
|
||||
WhereLoop *pLoop = pLevel->pWLoop; /* The loop being coded */
|
||||
int iCur; /* Cursor for table getting the filter */
|
||||
IndexedExpr *saved_pIdxEpr; /* saved copy of Parse.pIdxEpr */
|
||||
|
||||
saved_pIdxEpr = pParse->pIdxEpr;
|
||||
pParse->pIdxEpr = 0;
|
||||
|
||||
assert( pLoop!=0 );
|
||||
assert( v!=0 );
|
||||
@ -158091,9 +158117,8 @@ static SQLITE_NOINLINE void sqlite3ConstructBloomFilter(
|
||||
int r1 = sqlite3GetTempRange(pParse, n);
|
||||
int jj;
|
||||
for(jj=0; jj<n; jj++){
|
||||
int iCol = pIdx->aiColumn[jj];
|
||||
assert( pIdx->pTable==pItem->pTab );
|
||||
sqlite3ExprCodeGetColumnOfTable(v, pIdx->pTable, iCur, iCol,r1+jj);
|
||||
sqlite3ExprCodeLoadIndexColumn(pParse, pIdx, iCur, jj, r1+jj);
|
||||
}
|
||||
sqlite3VdbeAddOp4Int(v, OP_FilterAdd, pLevel->regFilter, 0, r1, n);
|
||||
sqlite3ReleaseTempRange(pParse, r1, n);
|
||||
@ -158124,6 +158149,7 @@ static SQLITE_NOINLINE void sqlite3ConstructBloomFilter(
|
||||
}
|
||||
}while( iLevel < pWInfo->nLevel );
|
||||
sqlite3VdbeJumpHere(v, addrOnce);
|
||||
pParse->pIdxEpr = saved_pIdxEpr;
|
||||
}
|
||||
|
||||
|
||||
@ -158423,6 +158449,7 @@ static int whereKeyStats(
|
||||
assert( pIdx->nSample>0 );
|
||||
assert( pRec->nField>0 );
|
||||
|
||||
|
||||
/* Do a binary search to find the first sample greater than or equal
|
||||
** to pRec. If pRec contains a single field, the set of samples to search
|
||||
** is simply the aSample[] array. If the samples in aSample[] contain more
|
||||
@ -158467,7 +158494,12 @@ static int whereKeyStats(
|
||||
** it is extended to two fields. The duplicates that this creates do not
|
||||
** cause any problems.
|
||||
*/
|
||||
nField = MIN(pRec->nField, pIdx->nSample);
|
||||
if( !HasRowid(pIdx->pTable) && IsPrimaryKeyIndex(pIdx) ){
|
||||
nField = pIdx->nKeyCol;
|
||||
}else{
|
||||
nField = pIdx->nColumn;
|
||||
}
|
||||
nField = MIN(pRec->nField, nField);
|
||||
iCol = 0;
|
||||
iSample = pIdx->nSample * nField;
|
||||
do{
|
||||
@ -162195,6 +162227,10 @@ static int wherePathSolver(WhereInfo *pWInfo, LogEst nRowEst){
|
||||
if( pFrom->isOrdered==pWInfo->pOrderBy->nExpr ){
|
||||
pWInfo->eDistinct = WHERE_DISTINCT_ORDERED;
|
||||
}
|
||||
if( pWInfo->pSelect->pOrderBy
|
||||
&& pWInfo->nOBSat > pWInfo->pSelect->pOrderBy->nExpr ){
|
||||
pWInfo->nOBSat = pWInfo->pSelect->pOrderBy->nExpr;
|
||||
}
|
||||
}else{
|
||||
pWInfo->revMask = pFrom->revLoop;
|
||||
if( pWInfo->nOBSat<=0 ){
|
||||
@ -193071,16 +193107,18 @@ static int fts3MsrBufferData(
|
||||
char *pList,
|
||||
i64 nList
|
||||
){
|
||||
if( nList>pMsr->nBuffer ){
|
||||
if( (nList+FTS3_NODE_PADDING)>pMsr->nBuffer ){
|
||||
char *pNew;
|
||||
pMsr->nBuffer = nList*2;
|
||||
pNew = (char *)sqlite3_realloc64(pMsr->aBuffer, pMsr->nBuffer);
|
||||
int nNew = nList*2 + FTS3_NODE_PADDING;
|
||||
pNew = (char *)sqlite3_realloc64(pMsr->aBuffer, nNew);
|
||||
if( !pNew ) return SQLITE_NOMEM;
|
||||
pMsr->aBuffer = pNew;
|
||||
pMsr->nBuffer = nNew;
|
||||
}
|
||||
|
||||
assert( nList>0 );
|
||||
memcpy(pMsr->aBuffer, pList, nList);
|
||||
memset(&pMsr->aBuffer[nList], 0, FTS3_NODE_PADDING);
|
||||
return SQLITE_OK;
|
||||
}
|
||||
|
||||
@ -240224,7 +240262,7 @@ static void fts5SourceIdFunc(
|
||||
){
|
||||
assert( nArg==0 );
|
||||
UNUSED_PARAM2(nArg, apUnused);
|
||||
sqlite3_result_text(pCtx, "fts5: 2023-03-10 12:13:52 20399f3eda5ec249d147ba9e48da6e87f969d7966a9a896764ca437ff7e737ff", -1, SQLITE_TRANSIENT);
|
||||
sqlite3_result_text(pCtx, "fts5: 2023-03-22 11:56:21 0d1fc92f94cb6b76bffe3ec34d69cffde2924203304e8ffc4155597af0c191da", -1, SQLITE_TRANSIENT);
|
||||
}
|
||||
|
||||
/*
|
||||
|
6
deps/sqlite/sqlite3.h
vendored
6
deps/sqlite/sqlite3.h
vendored
@ -146,9 +146,9 @@ extern "C" {
|
||||
** [sqlite3_libversion_number()], [sqlite3_sourceid()],
|
||||
** [sqlite_version()] and [sqlite_source_id()].
|
||||
*/
|
||||
#define SQLITE_VERSION "3.41.1"
|
||||
#define SQLITE_VERSION_NUMBER 3041001
|
||||
#define SQLITE_SOURCE_ID "2023-03-10 12:13:52 20399f3eda5ec249d147ba9e48da6e87f969d7966a9a896764ca437ff7e737ff"
|
||||
#define SQLITE_VERSION "3.41.2"
|
||||
#define SQLITE_VERSION_NUMBER 3041002
|
||||
#define SQLITE_SOURCE_ID "2023-03-22 11:56:21 0d1fc92f94cb6b76bffe3ec34d69cffde2924203304e8ffc4155597af0c191da"
|
||||
|
||||
/*
|
||||
** CAPI3REF: Run-Time Library Version Numbers
|
||||
|
@ -6,7 +6,10 @@
|
||||
<uses-sdk android:minSdkVersion="16"/>
|
||||
<uses-permission android:name="android.permission.INTERNET"/>
|
||||
<application android:label="Tilde Friends" android:usesCleartextTraffic="true" android:debuggable="true">
|
||||
<activity android:name=".MainActivity">
|
||||
<meta-data android:name="android.max_aspect" android:value="2.1"/>
|
||||
<activity
|
||||
android:name=".MainActivity"
|
||||
android:configChanges="orientation|screenSize">
|
||||
<intent-filter>
|
||||
<action android:name="android.intent.action.MAIN"/>
|
||||
<category android:name="android.intent.category.LAUNCHER"/>
|
||||
|
@ -182,6 +182,20 @@ public class MainActivity extends Activity {
|
||||
});
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onSaveInstanceState(Bundle outState)
|
||||
{
|
||||
super.onSaveInstanceState(outState);
|
||||
web_view.saveState(outState);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onRestoreInstanceState(Bundle savedInstanceState)
|
||||
{
|
||||
super.onRestoreInstanceState(savedInstanceState);
|
||||
web_view.restoreState(savedInstanceState);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean onKeyDown(int keyCode, KeyEvent event) {
|
||||
if (keyCode == KeyEvent.KEYCODE_BACK && web_view.canGoBack()) {
|
||||
|
303
src/file.js.c
303
src/file.js.c
@ -17,14 +17,10 @@
|
||||
#include <unistd.h>
|
||||
#endif
|
||||
|
||||
static JSValue _file_make_directory(JSContext* context, JSValueConst this_val, int argc, JSValueConst* argv);
|
||||
static JSValue _file_remove_directory(JSContext* context, JSValueConst this_val, int argc, JSValueConst* argv);
|
||||
static JSValue _file_read_file(JSContext* context, JSValueConst this_val, int argc, JSValueConst* argv);
|
||||
static JSValue _file_read_file_zip(JSContext* context, JSValueConst this_val, int argc, JSValueConst* argv);
|
||||
static JSValue _file_rename_file(JSContext* context, JSValueConst this_val, int argc, JSValueConst* argv);
|
||||
static JSValue _file_stat(JSContext* context, JSValueConst this_val, int argc, JSValueConst* argv);
|
||||
static JSValue _file_stat_zip(JSContext* context, JSValueConst this_val, int argc, JSValueConst* argv);
|
||||
static JSValue _file_unlink_file(JSContext* context, JSValueConst this_val, int argc, JSValueConst* argv);
|
||||
static JSValue _file_write_file(JSContext* context, JSValueConst this_val, int argc, JSValueConst* argv);
|
||||
|
||||
static double _time_spec_to_double(const uv_timespec_t* time_spec);
|
||||
@ -53,10 +49,6 @@ void tf_file_register(JSContext* context)
|
||||
JS_SetPropertyStr(context, global, "File", file);
|
||||
JS_SetPropertyStr(context, file, "readFile", JS_NewCFunction(context, zip ? _file_read_file_zip : _file_read_file, "readFile", 1));
|
||||
JS_SetPropertyStr(context, file, "writeFile", JS_NewCFunction(context, _file_write_file, "writeFile", 2));
|
||||
JS_SetPropertyStr(context, file, "makeDirectory", JS_NewCFunction(context, _file_make_directory, "makeDirectory", 1));
|
||||
JS_SetPropertyStr(context, file, "removeDirectory", JS_NewCFunction(context, _file_remove_directory, "removeDirectory", 1));
|
||||
JS_SetPropertyStr(context, file, "unlinkFile", JS_NewCFunction(context, _file_unlink_file, "unlinkFile", 1));
|
||||
JS_SetPropertyStr(context, file, "renameFile", JS_NewCFunction(context, _file_rename_file, "renameFile", 2));
|
||||
JS_SetPropertyStr(context, file, "stat", JS_NewCFunction(context, zip ? _file_stat_zip : _file_stat, "stat", 1));
|
||||
JS_FreeValue(context, global);
|
||||
}
|
||||
@ -69,6 +61,103 @@ static void _file_async_close_callback(uv_fs_t* req)
|
||||
tf_free(req);
|
||||
}
|
||||
|
||||
static void _file_write_write_callback(uv_fs_t* req)
|
||||
{
|
||||
uv_fs_req_cleanup(req);
|
||||
fs_req_t* fsreq = (fs_req_t*)req;
|
||||
tf_task_t* task = req->loop->data;
|
||||
JSContext* context = tf_task_get_context(task);
|
||||
promiseid_t promise = (promiseid_t)(intptr_t)req->data;
|
||||
if (req->result >= 0)
|
||||
{
|
||||
tf_task_resolve_promise(task, promise, JS_NewInt64(context, req->result));
|
||||
}
|
||||
else
|
||||
{
|
||||
tf_task_reject_promise(task, promise, JS_ThrowInternalError(context, "%s", uv_strerror(req->result)));
|
||||
}
|
||||
int result = uv_fs_close(req->loop, req, fsreq->file, _file_async_close_callback);
|
||||
if (result < 0)
|
||||
{
|
||||
uv_fs_req_cleanup(req);
|
||||
tf_free(fsreq);
|
||||
}
|
||||
}
|
||||
|
||||
static void _file_write_open_callback(uv_fs_t* req)
|
||||
{
|
||||
fs_req_t* fsreq = (fs_req_t*)req;
|
||||
uv_fs_req_cleanup(req);
|
||||
tf_task_t* task = req->loop->data;
|
||||
JSContext* context = tf_task_get_context(task);
|
||||
promiseid_t promise = (promiseid_t)(intptr_t)req->data;
|
||||
if (req->result >= 0)
|
||||
{
|
||||
uv_buf_t buf = { .base = fsreq->buffer, .len = fsreq->size };
|
||||
fsreq->file = req->result;
|
||||
int result = uv_fs_write(req->loop, req, fsreq->file, &buf, 1, 0, _file_write_write_callback);
|
||||
if (result < 0)
|
||||
{
|
||||
tf_task_reject_promise(task, promise, JS_ThrowInternalError(context, "%s", uv_strerror(result)));
|
||||
result = uv_fs_close(req->loop, req, fsreq->file, _file_async_close_callback);
|
||||
if (result < 0)
|
||||
{
|
||||
uv_fs_req_cleanup(req);
|
||||
tf_free(fsreq);
|
||||
}
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
tf_task_reject_promise(task, promise, JS_ThrowInternalError(context, "%s", uv_strerror(req->result)));
|
||||
uv_fs_req_cleanup(req);
|
||||
tf_free(req);
|
||||
}
|
||||
}
|
||||
|
||||
static JSValue _file_write_file(JSContext* context, JSValueConst this_val, int argc, JSValueConst* argv)
|
||||
{
|
||||
void* task = JS_GetContextOpaque(context);
|
||||
const char* file_name = JS_ToCString(context, argv[0]);
|
||||
|
||||
size_t size;
|
||||
uint8_t* buffer = tf_util_try_get_array_buffer(context, &size, argv[1]);
|
||||
bool is_array_buffer = false;
|
||||
if (buffer)
|
||||
{
|
||||
is_array_buffer = true;
|
||||
}
|
||||
else
|
||||
{
|
||||
buffer = (uint8_t*)JS_ToCStringLen(context, &size, argv[1]);
|
||||
}
|
||||
|
||||
promiseid_t promise = -1;
|
||||
JSValue promise_value = tf_task_allocate_promise(task, &promise);
|
||||
fs_req_t* req = tf_malloc(sizeof(fs_req_t) + size);
|
||||
*req = (fs_req_t)
|
||||
{
|
||||
.fs =
|
||||
{
|
||||
.data = (void*)(intptr_t)promise,
|
||||
},
|
||||
.size = size,
|
||||
};
|
||||
memcpy(req->buffer, buffer, size);
|
||||
if (!is_array_buffer)
|
||||
{
|
||||
JS_FreeCString(context, (const char*)buffer);
|
||||
}
|
||||
|
||||
int result = uv_fs_open(tf_task_get_loop(task), &req->fs, file_name, UV_FS_O_CREAT | UV_FS_O_WRONLY, 0644, _file_write_open_callback);
|
||||
if (result < 0)
|
||||
{
|
||||
tf_task_reject_promise(task, promise, JS_ThrowInternalError(context, "%s", uv_strerror(result)));
|
||||
}
|
||||
JS_FreeCString(context, file_name);
|
||||
return promise_value;
|
||||
}
|
||||
|
||||
static void _file_read_read_callback(uv_fs_t* req)
|
||||
{
|
||||
uv_fs_req_cleanup(req);
|
||||
@ -258,204 +347,6 @@ static JSValue _file_read_file_zip(JSContext* context, JSValueConst this_val, in
|
||||
return promise_value;
|
||||
}
|
||||
|
||||
static void _file_write_write_callback(uv_fs_t* req)
|
||||
{
|
||||
uv_fs_req_cleanup(req);
|
||||
fs_req_t* fsreq = (fs_req_t*)req;
|
||||
tf_task_t* task = req->loop->data;
|
||||
JSContext* context = tf_task_get_context(task);
|
||||
promiseid_t promise = (promiseid_t)(intptr_t)req->data;
|
||||
if (req->result >= 0)
|
||||
{
|
||||
tf_task_resolve_promise(task, promise, JS_NewInt64(context, req->result));
|
||||
}
|
||||
else
|
||||
{
|
||||
tf_task_reject_promise(task, promise, JS_ThrowInternalError(context, "%s", uv_strerror(req->result)));
|
||||
}
|
||||
int result = uv_fs_close(req->loop, req, fsreq->file, _file_async_close_callback);
|
||||
if (result < 0)
|
||||
{
|
||||
uv_fs_req_cleanup(req);
|
||||
tf_free(fsreq);
|
||||
}
|
||||
}
|
||||
|
||||
static void _file_write_open_callback(uv_fs_t* req)
|
||||
{
|
||||
fs_req_t* fsreq = (fs_req_t*)req;
|
||||
uv_fs_req_cleanup(req);
|
||||
tf_task_t* task = req->loop->data;
|
||||
JSContext* context = tf_task_get_context(task);
|
||||
promiseid_t promise = (promiseid_t)(intptr_t)req->data;
|
||||
if (req->result >= 0)
|
||||
{
|
||||
uv_buf_t buf = { .base = fsreq->buffer, .len = fsreq->size };
|
||||
fsreq->file = req->result;
|
||||
int result = uv_fs_write(req->loop, req, fsreq->file, &buf, 1, 0, _file_write_write_callback);
|
||||
if (result < 0)
|
||||
{
|
||||
tf_task_reject_promise(task, promise, JS_ThrowInternalError(context, "%s", uv_strerror(result)));
|
||||
result = uv_fs_close(req->loop, req, fsreq->file, _file_async_close_callback);
|
||||
if (result < 0)
|
||||
{
|
||||
uv_fs_req_cleanup(req);
|
||||
tf_free(fsreq);
|
||||
}
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
tf_task_reject_promise(task, promise, JS_ThrowInternalError(context, "%s", uv_strerror(req->result)));
|
||||
uv_fs_req_cleanup(req);
|
||||
tf_free(req);
|
||||
}
|
||||
}
|
||||
|
||||
static JSValue _file_write_file(JSContext* context, JSValueConst this_val, int argc, JSValueConst* argv)
|
||||
{
|
||||
void* task = JS_GetContextOpaque(context);
|
||||
const char* file_name = JS_ToCString(context, argv[0]);
|
||||
|
||||
size_t size;
|
||||
uint8_t* buffer = tf_util_try_get_array_buffer(context, &size, argv[1]);
|
||||
bool is_array_buffer = false;
|
||||
if (buffer)
|
||||
{
|
||||
is_array_buffer = true;
|
||||
}
|
||||
else
|
||||
{
|
||||
buffer = (uint8_t*)JS_ToCStringLen(context, &size, argv[1]);
|
||||
}
|
||||
|
||||
promiseid_t promise = -1;
|
||||
JSValue promise_value = tf_task_allocate_promise(task, &promise);
|
||||
fs_req_t* req = tf_malloc(sizeof(fs_req_t) + size);
|
||||
*req = (fs_req_t)
|
||||
{
|
||||
.fs =
|
||||
{
|
||||
.data = (void*)(intptr_t)promise,
|
||||
},
|
||||
.size = size,
|
||||
};
|
||||
memcpy(req->buffer, buffer, size);
|
||||
if (!is_array_buffer)
|
||||
{
|
||||
JS_FreeCString(context, (const char*)buffer);
|
||||
}
|
||||
|
||||
int result = uv_fs_open(tf_task_get_loop(task), &req->fs, file_name, UV_FS_O_CREAT | UV_FS_O_WRONLY, 0644, _file_write_open_callback);
|
||||
if (result < 0)
|
||||
{
|
||||
tf_task_reject_promise(task, promise, JS_ThrowInternalError(context, "%s", uv_strerror(result)));
|
||||
}
|
||||
JS_FreeCString(context, file_name);
|
||||
return promise_value;
|
||||
}
|
||||
|
||||
static void _file_async_callback(uv_fs_t* req)
|
||||
{
|
||||
uv_fs_req_cleanup(req);
|
||||
tf_task_t* task = req->loop->data;
|
||||
JSContext* context = tf_task_get_context(task);
|
||||
promiseid_t promise = (promiseid_t)(intptr_t)req->data;
|
||||
if (req->result == 0)
|
||||
{
|
||||
tf_task_resolve_promise(task, promise, JS_NewInt32(context, req->result));
|
||||
}
|
||||
else
|
||||
{
|
||||
tf_task_reject_promise(task, promise, JS_NewInt32(context, req->result));
|
||||
}
|
||||
tf_free(req);
|
||||
}
|
||||
|
||||
static JSValue _file_rename_file(JSContext* context, JSValueConst this_val, int argc, JSValueConst* argv)
|
||||
{
|
||||
void* task = JS_GetContextOpaque(context);
|
||||
const char* old_name = JS_ToCString(context, argv[0]);
|
||||
const char* new_name = JS_ToCString(context, argv[1]);
|
||||
promiseid_t promise = -1;
|
||||
JSValue promise_value = tf_task_allocate_promise(task, &promise);
|
||||
uv_fs_t* req = tf_malloc(sizeof(uv_fs_t));
|
||||
*req = (uv_fs_t)
|
||||
{
|
||||
.data = (void*)(intptr_t)promise,
|
||||
};
|
||||
int result = uv_fs_rename(tf_task_get_loop(task), req, old_name, new_name,_file_async_callback);
|
||||
JS_FreeCString(context, old_name);
|
||||
JS_FreeCString(context, new_name);
|
||||
if (result < 0)
|
||||
{
|
||||
tf_task_reject_promise(task, promise, JS_NewInt32(context, result));
|
||||
}
|
||||
return promise_value;
|
||||
}
|
||||
|
||||
static JSValue _file_unlink_file(JSContext* context, JSValueConst this_val, int argc, JSValueConst* argv)
|
||||
{
|
||||
void* task = JS_GetContextOpaque(context);
|
||||
const char* file_name = JS_ToCString(context, argv[0]);
|
||||
promiseid_t promise = -1;
|
||||
JSValue promise_value = tf_task_allocate_promise(task, &promise);
|
||||
uv_fs_t* req = tf_malloc(sizeof(uv_fs_t));
|
||||
*req = (uv_fs_t)
|
||||
{
|
||||
.data = (void*)(intptr_t)promise,
|
||||
};
|
||||
int result = uv_fs_unlink(tf_task_get_loop(task), req, file_name, _file_async_callback);
|
||||
JS_FreeCString(context, file_name);
|
||||
if (result < 0)
|
||||
{
|
||||
tf_task_reject_promise(task, promise, JS_NewInt32(context, result));
|
||||
}
|
||||
return promise_value;
|
||||
}
|
||||
|
||||
JSValue _file_make_directory(JSContext* context, JSValueConst this_val, int argc, JSValueConst* argv)
|
||||
{
|
||||
void* task = JS_GetContextOpaque(context);
|
||||
const char* directory = JS_ToCString(context, argv[0]);
|
||||
|
||||
promiseid_t promise = -1;
|
||||
JSValue promise_value = tf_task_allocate_promise(task, &promise);
|
||||
uv_fs_t* req = tf_malloc(sizeof(uv_fs_t));
|
||||
*req = (uv_fs_t)
|
||||
{
|
||||
.data = (void*)(intptr_t)promise,
|
||||
};
|
||||
int result = uv_fs_mkdir(tf_task_get_loop(task), req, directory, 0755, _file_async_callback);
|
||||
JS_FreeCString(context, directory);
|
||||
if (result < 0)
|
||||
{
|
||||
tf_task_reject_promise(task, promise, JS_NewInt32(context, result));
|
||||
}
|
||||
return promise_value;
|
||||
}
|
||||
|
||||
JSValue _file_remove_directory(JSContext* context, JSValueConst this_val, int argc, JSValueConst* argv)
|
||||
{
|
||||
void* task = JS_GetContextOpaque(context);
|
||||
const char* directory = JS_ToCString(context, argv[0]);
|
||||
|
||||
promiseid_t promise = -1;
|
||||
JSValue promise_value = tf_task_allocate_promise(task, &promise);
|
||||
uv_fs_t* req = tf_malloc(sizeof(uv_fs_t));
|
||||
*req = (uv_fs_t)
|
||||
{
|
||||
.data = (void*)(intptr_t)promise,
|
||||
};
|
||||
int result = uv_fs_rmdir(tf_task_get_loop(task), req, directory, _file_async_callback);
|
||||
JS_FreeCString(context, directory);
|
||||
if (result < 0)
|
||||
{
|
||||
tf_task_reject_promise(task, promise, JS_NewInt32(context, result));
|
||||
}
|
||||
return promise_value;
|
||||
}
|
||||
|
||||
JSValue _file_stat(JSContext* context, JSValueConst this_val, int argc, JSValueConst* argv)
|
||||
{
|
||||
void* task = JS_GetContextOpaque(context);
|
||||
|
@ -288,6 +288,7 @@ static int _tf_command_export(const char* file, int argc, char* argv[])
|
||||
"admin",
|
||||
"api",
|
||||
"apps",
|
||||
"appstore",
|
||||
"db",
|
||||
"docs",
|
||||
"follow",
|
||||
|
@ -1113,7 +1113,7 @@ JSValue _sockets_get(JSContext* context, JSValueConst this_val, int argc, JSValu
|
||||
JS_SetPropertyStr(context, entry, "listening", JS_NewBool(context, s->_listening));
|
||||
JS_SetPropertyStr(context, entry, "connected", JS_NewBool(context, s->_connected));
|
||||
JS_SetPropertyStr(context, entry, "tls", JS_NewBool(context, s->_tls != NULL));
|
||||
JS_SetPropertyStr(context, entry, "age_seconds", JS_NewFloat64(context, (uv_now(tf_task_get_loop(s->_task)) - s->created_ms) / 1000.f));
|
||||
JS_SetPropertyStr(context, entry, "age_seconds", JS_NewFloat64(context, (uv_now(tf_task_get_loop(s->_task)) - s->created_ms) / 1000.0));
|
||||
JS_SetPropertyStr(context, entry, "info", JS_DupValue(context, s->_info));
|
||||
JS_SetPropertyUint32(context, array, i, entry);
|
||||
}
|
||||
|
57
src/ssb.c
57
src/ssb.c
@ -41,6 +41,9 @@
|
||||
#define CYAN "\e[1;36m"
|
||||
#define RESET "\e[0m"
|
||||
|
||||
#define PRE_CALLBACK(ssb, cb) uint64_t pre_callback_hrtime_ns = _tf_ssb_callback_pre(ssb)
|
||||
#define POST_CALLBACK(ssb, cb) _tf_ssb_callback_post(ssb, cb, pre_callback_hrtime_ns)
|
||||
|
||||
static_assert(k_id_base64_len == sodium_base64_ENCODED_LEN(9 + crypto_box_PUBLICKEYBYTES, sodium_base64_VARIANT_ORIGINAL), "k_id_base64_len");
|
||||
static_assert(k_id_bin_len == crypto_box_PUBLICKEYBYTES, "k_id_bin_len");
|
||||
static_assert(k_blob_id_len == (sodium_base64_ENCODED_LEN(crypto_hash_sha256_BYTES, sodium_base64_VARIANT_ORIGINAL) + 8), "k_blob_id_len");
|
||||
@ -229,6 +232,9 @@ typedef struct _tf_ssb_t
|
||||
|
||||
tf_thread_work_time_t* thread_time;
|
||||
int thread_time_count;
|
||||
|
||||
void (*hitch_callback)(const char* name, uint64_t duration, void* user_data);
|
||||
void* hitch_user_data;
|
||||
} tf_ssb_t;
|
||||
|
||||
typedef struct _tf_ssb_connection_message_request_t
|
||||
@ -1008,6 +1014,22 @@ bool tf_ssb_id_str_to_bin(uint8_t* bin, const char* str)
|
||||
return tf_base64_decode(author_id, type - author_id, bin, crypto_box_PUBLICKEYBYTES) != 0;
|
||||
}
|
||||
|
||||
static uint64_t _tf_ssb_callback_pre(tf_ssb_t* ssb)
|
||||
{
|
||||
return uv_hrtime();
|
||||
}
|
||||
|
||||
static void _tf_ssb_callback_post(tf_ssb_t* ssb, void* callback, uint64_t pre)
|
||||
{
|
||||
if (ssb->hitch_callback)
|
||||
{
|
||||
uint64_t post = uv_hrtime();
|
||||
const char* name = tf_util_function_to_string(callback);
|
||||
ssb->hitch_callback(name, post - pre, ssb->hitch_user_data);
|
||||
tf_free((void*)name);
|
||||
}
|
||||
}
|
||||
|
||||
static void _tf_ssb_notify_connections_changed(tf_ssb_t* ssb, tf_ssb_change_t change, tf_ssb_connection_t* connection)
|
||||
{
|
||||
tf_ssb_connections_changed_callback_node_t* next = NULL;
|
||||
@ -1015,7 +1037,9 @@ static void _tf_ssb_notify_connections_changed(tf_ssb_t* ssb, tf_ssb_change_t ch
|
||||
{
|
||||
next = node->next;
|
||||
tf_trace_begin(ssb->trace, "connections_changed");
|
||||
PRE_CALLBACK(ssb, node->callback);
|
||||
node->callback(ssb, change, connection, node->user_data);
|
||||
POST_CALLBACK(ssb, node->callback);
|
||||
tf_trace_end(ssb->trace);
|
||||
}
|
||||
}
|
||||
@ -1451,7 +1475,9 @@ static void _tf_ssb_connection_rpc_recv(tf_ssb_connection_t* connection, uint8_t
|
||||
if (_tf_ssb_name_equals(context, val, it->name))
|
||||
{
|
||||
tf_trace_begin(connection->ssb->trace, it->flattened_name);
|
||||
PRE_CALLBACK(connection->ssb, it->callback);
|
||||
it->callback(connection, flags, request_number, val, message, size, it->user_data);
|
||||
POST_CALLBACK(connection->ssb, it->callback);
|
||||
tf_trace_end(connection->ssb->trace);
|
||||
found = true;
|
||||
break;
|
||||
@ -1469,7 +1495,9 @@ static void _tf_ssb_connection_rpc_recv(tf_ssb_connection_t* connection, uint8_t
|
||||
char buffer[64];
|
||||
snprintf(buffer, sizeof(buffer), "request %d", request_number);
|
||||
tf_trace_begin(connection->ssb->trace, buffer);
|
||||
PRE_CALLBACK(connection->ssb, callback);
|
||||
callback(connection, flags, request_number, val, message, size, user_data);
|
||||
POST_CALLBACK(connection->ssb, callback);
|
||||
tf_trace_end(connection->ssb->trace);
|
||||
}
|
||||
}
|
||||
@ -1501,7 +1529,9 @@ static void _tf_ssb_connection_rpc_recv(tf_ssb_connection_t* connection, uint8_t
|
||||
char buffer[64];
|
||||
snprintf(buffer, sizeof(buffer), "request %d", request_number);
|
||||
tf_trace_begin(connection->ssb->trace, buffer);
|
||||
PRE_CALLBACK(connection->ssb, callback);
|
||||
callback(connection, flags, request_number, JS_UNDEFINED, message, size, user_data);
|
||||
POST_CALLBACK(connection->ssb, callback);
|
||||
tf_trace_end(connection->ssb->trace);
|
||||
}
|
||||
}
|
||||
@ -2081,6 +2111,11 @@ tf_ssb_t* tf_ssb_create(uv_loop_t* loop, JSContext* context, const char* db_path
|
||||
ssb->broadcast_timer.data = ssb;
|
||||
uv_timer_init(ssb->loop, &ssb->broadcast_timer);
|
||||
|
||||
ssb->trace_timer.data = ssb;
|
||||
uv_timer_init(ssb->loop, &ssb->trace_timer);
|
||||
uv_timer_start(&ssb->trace_timer, _tf_ssb_trace_timer, 100, 100);
|
||||
uv_unref((uv_handle_t*)&ssb->trace_timer);
|
||||
|
||||
if (!_tf_ssb_load_keys(ssb))
|
||||
{
|
||||
tf_printf("Generating a new keypair.\n");
|
||||
@ -2587,7 +2622,9 @@ static void _tf_ssb_send_broadcast(tf_ssb_t* ssb, struct sockaddr_in* address, s
|
||||
int r = uv_udp_try_send(&ssb->broadcast_sender, &buf, 1, (struct sockaddr*)&broadcast_addr);
|
||||
if (r < 0)
|
||||
{
|
||||
tf_printf("failed to send broadcast for %s (%d): %s\n", address_str, r, uv_strerror(r));
|
||||
char broadcast_str[256] = { 0 };
|
||||
uv_ip4_name(&broadcast_addr, broadcast_str, sizeof(broadcast_str));
|
||||
tf_printf("failed to send broadcast for %s via %s (%d): %s\n", address_str, broadcast_str, r, uv_strerror(r));
|
||||
}
|
||||
}
|
||||
|
||||
@ -2642,9 +2679,6 @@ void tf_ssb_server_open(tf_ssb_t* ssb, int port)
|
||||
/* TODO: cleanup */
|
||||
return;
|
||||
}
|
||||
|
||||
tf_printf("Starting broadcasts.\n");
|
||||
uv_timer_start(&ssb->broadcast_timer, _tf_ssb_broadcast_timer, 2000, 2000);
|
||||
}
|
||||
|
||||
void tf_ssb_server_close(tf_ssb_t* ssb)
|
||||
@ -2881,17 +2915,16 @@ void tf_ssb_broadcast_sender_start(tf_ssb_t* ssb)
|
||||
|
||||
ssb->broadcast_sender.data = ssb;
|
||||
uv_udp_init(ssb->loop, &ssb->broadcast_sender);
|
||||
struct sockaddr_in broadcast_from = {
|
||||
struct sockaddr_in broadcast_from =
|
||||
{
|
||||
.sin_family = AF_INET,
|
||||
.sin_addr.s_addr = INADDR_ANY,
|
||||
};
|
||||
uv_udp_bind(&ssb->broadcast_sender, (struct sockaddr*)&broadcast_from, 0);
|
||||
uv_udp_set_broadcast(&ssb->broadcast_sender, 1);
|
||||
|
||||
ssb->trace_timer.data = ssb;
|
||||
uv_timer_init(ssb->loop, &ssb->trace_timer);
|
||||
uv_timer_start(&ssb->trace_timer, _tf_ssb_trace_timer, 100, 100);
|
||||
uv_unref((uv_handle_t*)&ssb->trace_timer);
|
||||
tf_printf("Starting broadcasts.\n");
|
||||
uv_timer_start(&ssb->broadcast_timer, _tf_ssb_broadcast_timer, 2000, 2000);
|
||||
}
|
||||
|
||||
void tf_ssb_append_post(tf_ssb_t* ssb, const char* text)
|
||||
@ -3462,3 +3495,9 @@ uint64_t tf_ssb_get_average_thread_time(tf_ssb_t* ssb)
|
||||
}
|
||||
return ssb->thread_time_count ? total / ssb->thread_time_count : 0;
|
||||
}
|
||||
|
||||
void tf_ssb_set_hitch_callback(tf_ssb_t* ssb, void (*callback)(const char* name, uint64_t duration_ns, void* user_data), void* user_data)
|
||||
{
|
||||
ssb->hitch_callback = callback;
|
||||
ssb->hitch_user_data = user_data;
|
||||
}
|
||||
|
@ -189,3 +189,5 @@ JSValue tf_ssb_get_disconnection_debug(tf_ssb_t* ssb, JSContext* context);
|
||||
|
||||
void tf_ssb_record_thread_time(tf_ssb_t* ssb, int64_t thread_id, uint64_t hrtime);
|
||||
uint64_t tf_ssb_get_average_thread_time(tf_ssb_t* ssb);
|
||||
|
||||
void tf_ssb_set_hitch_callback(tf_ssb_t* ssb, void (*callback)(const char* name, uint64_t duration_ns, void* user_data), void* user_data);
|
||||
|
40
src/ssb.js.c
40
src/ssb.js.c
@ -1046,45 +1046,6 @@ static JSValue _tf_ssb_createTunnel(JSContext* context, JSValueConst this_val, i
|
||||
return result;
|
||||
}
|
||||
|
||||
static JSValue _tf_ssb_followingDeep(JSContext* context, JSValueConst this_val, int argc, JSValueConst* argv)
|
||||
{
|
||||
int depth = 2;
|
||||
if (!JS_IsArray(context, argv[0]))
|
||||
{
|
||||
return JS_ThrowTypeError(context, "Expected argument 1 to be an array of ids.");
|
||||
}
|
||||
if (JS_ToInt32(context, &depth, argv[1]))
|
||||
{
|
||||
return JS_ThrowTypeError(context, "Could not convert argument 2 to integer.");
|
||||
}
|
||||
|
||||
int count = tf_util_get_length(context, argv[0]);
|
||||
const char** ids = tf_malloc(count * sizeof(char*));
|
||||
for (int i = 0; i < count; i++)
|
||||
{
|
||||
JSValue id = JS_GetPropertyUint32(context, argv[0], i);
|
||||
ids[i] = JS_ToCString(context, id);
|
||||
JS_FreeValue(context, id);
|
||||
}
|
||||
|
||||
tf_ssb_t* ssb = JS_GetOpaque(this_val, _tf_ssb_classId);
|
||||
const char** following_deep = tf_ssb_db_following_deep(ssb, ids, count, depth);
|
||||
JSValue result = JS_NewArray(context);
|
||||
int index = 0;
|
||||
for (const char** id = following_deep; *id; id++)
|
||||
{
|
||||
JS_SetPropertyUint32(context, result, index++, JS_NewString(context, *id));
|
||||
}
|
||||
tf_free(following_deep);
|
||||
|
||||
for (int i = 0; i < count; i++)
|
||||
{
|
||||
JS_FreeCString(context, ids[i]);
|
||||
}
|
||||
tf_free(ids);
|
||||
return result;
|
||||
}
|
||||
|
||||
enum { k_max_private_message_recipients = 8 };
|
||||
|
||||
static bool _tf_ssb_get_private_key_curve25519(sqlite3* db, const char* user, const char* identity, uint8_t out_private_key[static crypto_sign_SECRETKEYBYTES])
|
||||
@ -1357,7 +1318,6 @@ void tf_ssb_register(JSContext* context, tf_ssb_t* ssb)
|
||||
JS_SetPropertyStr(context, object, "getBroadcasts", JS_NewCFunction(context, _tf_ssb_getBroadcasts, "getBroadcasts", 0));
|
||||
JS_SetPropertyStr(context, object, "connect", JS_NewCFunction(context, _tf_ssb_connect, "connect", 1));
|
||||
JS_SetPropertyStr(context, object, "createTunnel", JS_NewCFunction(context, _tf_ssb_createTunnel, "createTunnel", 3));
|
||||
JS_SetPropertyStr(context, object, "followingDeep", JS_NewCFunction(context, _tf_ssb_followingDeep, "followingDeep", 2));
|
||||
|
||||
/* Should be trusted only. */
|
||||
JS_SetPropertyStr(context, object, "addEventListener", JS_NewCFunction(context, _tf_ssb_add_event_listener, "addEventListener", 2));
|
||||
|
@ -16,6 +16,7 @@
|
||||
#endif
|
||||
|
||||
static void _tf_ssb_connection_send_history_stream(tf_ssb_connection_t* connection, int32_t request_number, const char* author, int64_t sequence, bool keys, bool live);
|
||||
static void _tf_ssb_connection_send_history_stream_internal(tf_ssb_connection_t* connection, int32_t request_number, const char* author, int64_t sequence, bool keys, bool live);
|
||||
|
||||
static void _tf_ssb_rpc_gossip_ping_callback(tf_ssb_connection_t* connection, uint8_t flags, int32_t request_number, JSValue args, const uint8_t* message, size_t size, void* user_data)
|
||||
{
|
||||
@ -688,12 +689,12 @@ static void _tf_ssb_connection_send_history_stream_callback(tf_ssb_connection_t*
|
||||
tf_ssb_connection_send_history_stream_t* request = user_data;
|
||||
if (tf_ssb_connection_is_connected(connection))
|
||||
{
|
||||
_tf_ssb_connection_send_history_stream(connection, request->request_number, request->author, request->sequence, request->keys, request->live);
|
||||
_tf_ssb_connection_send_history_stream_internal(connection, request->request_number, request->author, request->sequence, request->keys, request->live);
|
||||
}
|
||||
tf_free(request);
|
||||
}
|
||||
|
||||
static void _tf_ssb_connection_send_history_stream(tf_ssb_connection_t* connection, int32_t request_number, const char* author, int64_t sequence, bool keys, bool live)
|
||||
static void _tf_ssb_connection_send_history_stream_internal(tf_ssb_connection_t* connection, int32_t request_number, const char* author, int64_t sequence, bool keys, bool live)
|
||||
{
|
||||
tf_ssb_t* ssb = tf_ssb_connection_get_ssb(connection);
|
||||
JSContext* context = tf_ssb_get_context(ssb);
|
||||
@ -742,16 +743,7 @@ static void _tf_ssb_connection_send_history_stream(tf_ssb_connection_t* connecti
|
||||
|
||||
if (max_sequence_seen == sequence + k_max - 1)
|
||||
{
|
||||
tf_ssb_connection_send_history_stream_t* async = tf_malloc(sizeof(tf_ssb_connection_send_history_stream_t));
|
||||
*async = (tf_ssb_connection_send_history_stream_t)
|
||||
{
|
||||
.request_number = request_number,
|
||||
.sequence = max_sequence_seen + 1,
|
||||
.keys = keys,
|
||||
.live = live,
|
||||
};
|
||||
snprintf(async->author, sizeof(async->author), "%s", author);
|
||||
tf_ssb_connection_schedule_idle(connection, _tf_ssb_connection_send_history_stream_callback, async);
|
||||
_tf_ssb_connection_send_history_stream(connection, request_number, author, max_sequence_seen + 1, keys, live);
|
||||
}
|
||||
else if (!live)
|
||||
{
|
||||
@ -767,6 +759,20 @@ static void _tf_ssb_connection_send_history_stream(tf_ssb_connection_t* connecti
|
||||
}
|
||||
}
|
||||
|
||||
static void _tf_ssb_connection_send_history_stream(tf_ssb_connection_t* connection, int32_t request_number, const char* author, int64_t sequence, bool keys, bool live)
|
||||
{
|
||||
tf_ssb_connection_send_history_stream_t* async = tf_malloc(sizeof(tf_ssb_connection_send_history_stream_t));
|
||||
*async = (tf_ssb_connection_send_history_stream_t)
|
||||
{
|
||||
.request_number = request_number,
|
||||
.sequence = sequence,
|
||||
.keys = keys,
|
||||
.live = live,
|
||||
};
|
||||
snprintf(async->author, sizeof(async->author), "%s", author);
|
||||
tf_ssb_connection_schedule_idle(connection, _tf_ssb_connection_send_history_stream_callback, async);
|
||||
}
|
||||
|
||||
static void _tf_ssb_rpc_createHistoryStream(tf_ssb_connection_t* connection, uint8_t flags, int32_t request_number, JSValue args, const uint8_t* message, size_t size, void* user_data)
|
||||
{
|
||||
tf_ssb_t* ssb = tf_ssb_connection_get_ssb(connection);
|
||||
@ -816,6 +822,7 @@ static void _tf_ssb_rpc_ebt_replicate_send_clock(tf_ssb_connection_t* connection
|
||||
tf_ssb_db_get_latest_message_by_author(ssb, visible[i], &sequence, NULL, 0);
|
||||
JS_SetPropertyStr(context, full_clock, visible[i], JS_NewInt64(context, sequence == -1 ? -1 : (sequence << 1)));
|
||||
}
|
||||
tf_free(visible);
|
||||
|
||||
/* Ask about the incoming connection, too. */
|
||||
char id[k_id_base64_len] = "";
|
||||
@ -861,8 +868,6 @@ static void _tf_ssb_rpc_ebt_replicate_send_clock(tf_ssb_connection_t* connection
|
||||
js_free(context, ptab);
|
||||
}
|
||||
|
||||
tf_free(visible);
|
||||
|
||||
tf_ssb_connection_rpc_send_json(connection, k_ssb_rpc_flag_stream, -request_number, full_clock, NULL, NULL, NULL);
|
||||
JS_FreeValue(context, full_clock);
|
||||
}
|
||||
|
@ -542,7 +542,7 @@ void tf_ssb_test_bench(const tf_test_options_t* options)
|
||||
tf_ssb_append_post(ssb0, "Hello, world!");
|
||||
}
|
||||
clock_gettime(CLOCK_REALTIME, &end_time);
|
||||
tf_printf("insert = %f seconds\n", (float)(end_time.tv_sec - start_time.tv_sec) + (end_time.tv_nsec - start_time.tv_nsec) / 1e9f);
|
||||
tf_printf("insert = %f seconds\n", (end_time.tv_sec - start_time.tv_sec) + (end_time.tv_nsec - start_time.tv_nsec) / 1e9);
|
||||
|
||||
tf_ssb_t* ssb1 = tf_ssb_create(&loop, NULL, ":memory:");
|
||||
tf_ssb_generate_keys(ssb1);
|
||||
@ -571,7 +571,7 @@ void tf_ssb_test_bench(const tf_test_options_t* options)
|
||||
}
|
||||
clock_gettime(CLOCK_REALTIME, &end_time);
|
||||
tf_printf("Done.\n");
|
||||
tf_printf("replicate = %f seconds\n", (float)(end_time.tv_sec - start_time.tv_sec) + (end_time.tv_nsec - start_time.tv_nsec) / 1e9f);
|
||||
tf_printf("replicate = %f seconds\n", (end_time.tv_sec - start_time.tv_sec) + (end_time.tv_nsec - start_time.tv_nsec) / 1e9);
|
||||
|
||||
tf_ssb_send_close(ssb1);
|
||||
tf_ssb_server_close(ssb0);
|
||||
|
64
src/task.c
64
src/task.c
@ -34,6 +34,10 @@
|
||||
#include <unistd.h>
|
||||
#endif
|
||||
|
||||
#if !defined(_countof)
|
||||
#define _countof(a) ((int)(sizeof((a)) / sizeof(*(a))))
|
||||
#endif
|
||||
|
||||
static const char* k_version = "1.0";
|
||||
|
||||
static JSClassID _import_class_id;
|
||||
@ -71,6 +75,12 @@ typedef struct _promise_stack_t
|
||||
int count;
|
||||
} promise_stack_t;
|
||||
|
||||
typedef struct _hitch_t
|
||||
{
|
||||
char name[256];
|
||||
uint64_t duration_ns;
|
||||
} hitch_t;
|
||||
|
||||
typedef struct _tf_task_t
|
||||
{
|
||||
taskid_t _nextTask;
|
||||
@ -130,6 +140,8 @@ typedef struct _tf_task_t
|
||||
|
||||
promise_stack_t* _promise_stacks;
|
||||
int _promise_stack_count;
|
||||
|
||||
hitch_t hitches[32];
|
||||
} tf_task_t;
|
||||
|
||||
typedef struct _export_record_t
|
||||
@ -755,21 +767,21 @@ static JSValue _tf_task_getStats(JSContext* context, JSValueConst this_val, int
|
||||
JS_SetPropertyStr(context, result, "export_count", JS_NewInt32(context, task->_export_count));
|
||||
JS_SetPropertyStr(context, result, "promise_count", JS_NewInt32(context, task->_promise_count));
|
||||
|
||||
JS_SetPropertyStr(context, result, "cpu_percent", JS_NewFloat64(context, 100.0f - task->idle_percent));
|
||||
JS_SetPropertyStr(context, result, "thread_percent", JS_NewFloat64(context, task->thread_percent));
|
||||
JS_SetPropertyStr(context, result, "cpu_percent", JS_NewFloat64(context, 100.0 - (double)task->idle_percent));
|
||||
JS_SetPropertyStr(context, result, "thread_percent", JS_NewFloat64(context, (double)task->thread_percent));
|
||||
|
||||
uint64_t total_memory = uv_get_total_memory();
|
||||
size_t rss;
|
||||
if (uv_resident_set_memory(&rss) == 0)
|
||||
{
|
||||
JS_SetPropertyStr(context, result, "memory_percent", JS_NewFloat64(context, 100.0f * rss / total_memory));
|
||||
JS_SetPropertyStr(context, result, "memory_percent", JS_NewFloat64(context, 100.0 * rss / total_memory));
|
||||
}
|
||||
|
||||
JS_SetPropertyStr(context, result, "sqlite3_memory_percent", JS_NewFloat64(context, 100.0f * tf_mem_get_sqlite_malloc_size() / total_memory));
|
||||
JS_SetPropertyStr(context, result, "js_malloc_percent", JS_NewFloat64(context, 100.0f * tf_mem_get_js_malloc_size() / total_memory));
|
||||
JS_SetPropertyStr(context, result, "uv_malloc_percent", JS_NewFloat64(context, 100.0f * tf_mem_get_uv_malloc_size() / total_memory));
|
||||
JS_SetPropertyStr(context, result, "tls_malloc_percent", JS_NewFloat64(context, 100.0f * tf_mem_get_tls_malloc_size() / total_memory));
|
||||
JS_SetPropertyStr(context, result, "tf_malloc_percent", JS_NewFloat64(context, 100.0f * tf_mem_get_tf_malloc_size() / total_memory));
|
||||
JS_SetPropertyStr(context, result, "sqlite3_memory_percent", JS_NewFloat64(context, 100.0 * tf_mem_get_sqlite_malloc_size() / total_memory));
|
||||
JS_SetPropertyStr(context, result, "js_malloc_percent", JS_NewFloat64(context, 100.0 * tf_mem_get_js_malloc_size() / total_memory));
|
||||
JS_SetPropertyStr(context, result, "uv_malloc_percent", JS_NewFloat64(context, 100.0 * tf_mem_get_uv_malloc_size() / total_memory));
|
||||
JS_SetPropertyStr(context, result, "tls_malloc_percent", JS_NewFloat64(context, 100.0 * tf_mem_get_tls_malloc_size() / total_memory));
|
||||
JS_SetPropertyStr(context, result, "tf_malloc_percent", JS_NewFloat64(context, 100.0 * tf_mem_get_tf_malloc_size() / total_memory));
|
||||
|
||||
JS_SetPropertyStr(context, result, "socket_count", JS_NewInt32(context, tf_socket_get_count()));
|
||||
JS_SetPropertyStr(context, result, "socket_open_count", JS_NewInt32(context, tf_socket_get_open_count()));
|
||||
@ -870,6 +882,22 @@ static JSValue _tf_task_getDebug(JSContext* context, JSValueConst this_val, int
|
||||
return result;
|
||||
}
|
||||
|
||||
static JSValue _tf_task_getHitches(JSContext* context, JSValueConst this_val, int argc, JSValueConst* argv)
|
||||
{
|
||||
tf_task_t* task = JS_GetContextOpaque(context);
|
||||
tf_trace_begin(task->_trace, __func__);
|
||||
JSValue result = JS_NewObject(context);
|
||||
for (int i = 0; i < (int)_countof(task->hitches); i++)
|
||||
{
|
||||
if (*task->hitches[i].name)
|
||||
{
|
||||
JS_SetPropertyStr(context, result, task->hitches[i].name, JS_NewFloat64(context, task->hitches[i].duration_ns / 1e9));
|
||||
}
|
||||
}
|
||||
tf_trace_end(task->_trace);
|
||||
return result;
|
||||
}
|
||||
|
||||
static JSValue _tf_task_getAllocations(JSContext* context, JSValueConst this_val, int argc, JSValueConst* argv)
|
||||
{
|
||||
tf_task_t* task = JS_GetContextOpaque(context);
|
||||
@ -1576,6 +1604,24 @@ static void _tf_task_trace_to_parent(tf_trace_t* trace, const char* buffer, size
|
||||
tf_packetstream_send(tf_taskstub_get_stream(task->_parent), kTaskTrace, buffer, size);
|
||||
}
|
||||
|
||||
static void _tf_task_record_hitch(const char* name, uint64_t duration_ns, void* user_data)
|
||||
{
|
||||
tf_task_t* task = user_data;
|
||||
for (int i = 0; i < (int)_countof(task->hitches); i++)
|
||||
{
|
||||
if (duration_ns > task->hitches[i].duration_ns)
|
||||
{
|
||||
if (i + 1 < (int)_countof(task->hitches))
|
||||
{
|
||||
memmove(task->hitches + i + 1, task->hitches + i, sizeof(hitch_t) * ((int)_countof(task->hitches) - i - 1));
|
||||
}
|
||||
snprintf(task->hitches[i].name, sizeof(task->hitches[i].name), "%s", name);
|
||||
task->hitches[i].duration_ns = duration_ns;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void tf_task_activate(tf_task_t* task)
|
||||
{
|
||||
assert(!task->_activated);
|
||||
@ -1643,6 +1689,7 @@ void tf_task_activate(tf_task_t* task)
|
||||
task->_ssb = tf_ssb_create(&task->_loop, task->_context, task->_db_path);
|
||||
tf_ssb_set_trace(task->_ssb, task->_trace);
|
||||
tf_ssb_register(context, task->_ssb);
|
||||
tf_ssb_set_hitch_callback(task->_ssb, _tf_task_record_hitch, task);
|
||||
|
||||
if (task->_ssb_port)
|
||||
{
|
||||
@ -1654,6 +1701,7 @@ void tf_task_activate(tf_task_t* task)
|
||||
JS_SetPropertyStr(context, global, "trace", JS_NewCFunction(context, _tf_task_trace, "trace", 1));
|
||||
JS_SetPropertyStr(context, global, "getStats", JS_NewCFunction(context, _tf_task_getStats, "getStats", 0));
|
||||
JS_SetPropertyStr(context, global, "getDebug", JS_NewCFunction(context, _tf_task_getDebug, "getDebug", 0));
|
||||
JS_SetPropertyStr(context, global, "getHitches", JS_NewCFunction(context, _tf_task_getHitches, "getHitches", 0));
|
||||
JS_SetPropertyStr(context, global, "getAllocations", JS_NewCFunction(context, _tf_task_getAllocations, "getAllocations", 0));
|
||||
JS_SetPropertyStr(context, global, "disconnectionsDebug", JS_NewCFunction(context, _tf_task_disconnectionsDebug, "disconnectionsDebug", 0));
|
||||
}
|
||||
|
30
src/tests.c
30
src/tests.c
@ -542,36 +542,6 @@ static void _test_file(const tf_test_options_t* options)
|
||||
" exit(1);\n"
|
||||
"}).catch(function(error) {\n"
|
||||
" print('expected error', error);\n"
|
||||
"});\n"
|
||||
"File.unlinkFile('out/test/new.txt').finally(function() {\n"
|
||||
" return File.removeDirectory('out/test');\n"
|
||||
"}).finally(function() {\n"
|
||||
" return File.makeDirectory('out/test').then(function() {\n"
|
||||
" return File.writeFile('out/test/new.txt', 'hello').then(function(result) {\n"
|
||||
" return File.readFile('out/test/new.txt').then(function(data) {\n"
|
||||
" if (utf8Decode(data) != 'hello') {\n"
|
||||
" print('READ', utf8Decode(data));\n"
|
||||
" exit(1);\n"
|
||||
" }\n"
|
||||
" }).catch(function(error) {\n"
|
||||
" print('unexpected read error', error);\n"
|
||||
" exit(1);\n"
|
||||
" });\n"
|
||||
" }).catch(function(error) {\n"
|
||||
" print('unexpected write error', error);\n"
|
||||
" exit(1);\n"
|
||||
" });\n"
|
||||
" }).catch(function(error) {\n"
|
||||
" print('unexpected make directory error', error);\n"
|
||||
" exit(1);\n"
|
||||
" });\n"
|
||||
"}).finally(function() {\n"
|
||||
" return File.renameFile('out/test/new.txt', 'out/test/renamed.txt').catch(x => print(x)).finally(function() {\n"
|
||||
" return File.unlinkFile('out/test/renamed.txt').catch(x => print(x)).finally(function() {\n"
|
||||
" print('removing directory');\n"
|
||||
" return File.removeDirectory('out/test').catch(x => print(x));\n"
|
||||
" });\n"
|
||||
" });\n"
|
||||
"});\n");
|
||||
fclose(file);
|
||||
|
||||
|
@ -14,11 +14,11 @@
|
||||
|
||||
#include <string.h>
|
||||
|
||||
#ifndef _WIN32
|
||||
#ifndef __ANDROID__
|
||||
#if defined(__ANDROID__)
|
||||
#include <unwind.h>
|
||||
#elif !defined(_WIN32)
|
||||
#include <execinfo.h>
|
||||
#endif
|
||||
#endif
|
||||
|
||||
static JSValue _util_utf8_encode(JSContext* context, JSValueConst this_val, int argc, JSValueConst* argv)
|
||||
{
|
||||
@ -462,6 +462,30 @@ const char* tf_util_backtrace_to_string(void* const* buffer, int count)
|
||||
return string;
|
||||
}
|
||||
|
||||
static int _tf_util_backtrace_single_callback(void* data, uintptr_t pc, const char* filename, int line_number, const char* function)
|
||||
{
|
||||
char** stack = data;
|
||||
char line[256];
|
||||
int length = snprintf(line, sizeof(line), "%s", function);
|
||||
int current = *stack ? strlen(*stack) : 0;
|
||||
*stack = tf_resize_vec(*stack, current + length + 1);
|
||||
memcpy(*stack + current, line, length + 1);
|
||||
return 0;
|
||||
}
|
||||
|
||||
const char* tf_util_function_to_string(void* function)
|
||||
{
|
||||
extern struct backtrace_state* g_backtrace_state;
|
||||
char* string = NULL;
|
||||
backtrace_pcinfo(
|
||||
g_backtrace_state,
|
||||
(uintptr_t)function,
|
||||
_tf_util_backtrace_single_callback,
|
||||
_tf_util_backtrace_error,
|
||||
&string);
|
||||
return string;
|
||||
}
|
||||
|
||||
const char* tf_util_backtrace_string()
|
||||
{
|
||||
void* buffer[32];
|
||||
@ -469,13 +493,41 @@ const char* tf_util_backtrace_string()
|
||||
return tf_util_backtrace_to_string(buffer, count);
|
||||
}
|
||||
|
||||
#if defined(__ANDROID__)
|
||||
typedef struct _android_backtrace_t
|
||||
{
|
||||
void** current;
|
||||
void** end;
|
||||
} android_backtrace_t;
|
||||
|
||||
static _Unwind_Reason_Code _android_unwind_callback(struct _Unwind_Context* context, void* arg)
|
||||
{
|
||||
android_backtrace_t* state = arg;
|
||||
uintptr_t pc = _Unwind_GetIP(context);
|
||||
if (pc)
|
||||
{
|
||||
if (state->current == state->end)
|
||||
{
|
||||
return _URC_END_OF_STACK;
|
||||
}
|
||||
else
|
||||
{
|
||||
*state->current++ = (void*)pc;
|
||||
}
|
||||
}
|
||||
return _URC_NO_REASON;
|
||||
}
|
||||
#endif
|
||||
|
||||
int tf_util_backtrace(void** buffer, int count)
|
||||
{
|
||||
#ifdef _WIN32
|
||||
return CaptureStackBackTrace(0, count, buffer, NULL);
|
||||
#elif !defined(__ANDROID__)
|
||||
return backtrace(buffer, count);
|
||||
#elif defined(__ANDROID__)
|
||||
android_backtrace_t state = { .current = buffer, .end = buffer + count };
|
||||
_Unwind_Backtrace(_android_unwind_callback, &state);
|
||||
return state.current - buffer;
|
||||
#else
|
||||
return 0;
|
||||
return backtrace(buffer, count);
|
||||
#endif
|
||||
}
|
||||
|
@ -19,3 +19,5 @@ size_t tf_base64_decode(const char* source, size_t source_length, uint8_t* out,
|
||||
int tf_util_backtrace(void** buffer, int count);
|
||||
const char* tf_util_backtrace_to_string(void* const* buffer, int count);
|
||||
const char* tf_util_backtrace_string();
|
||||
|
||||
const char* tf_util_function_to_string(void* function);
|
||||
|
Reference in New Issue
Block a user