16 Commits

Author SHA1 Message Date
f0452704a1 speedscope-1.15.2.zip
git-svn-id: https://www.unprompted.com/svn/projects/tildefriends/trunk@4335 ed5197a5-7fde-0310-b194-c3ffbd925b24
2023-06-29 00:25:25 +00:00
b8b1f1ba80 Confused by this message. Add more context.
git-svn-id: https://www.unprompted.com/svn/projects/tildefriends/trunk@4334 ed5197a5-7fde-0310-b194-c3ffbd925b24
2023-06-29 00:17:32 +00:00
caf7478da4 Ugg, release .apk pls.
git-svn-id: https://www.unprompted.com/svn/projects/tildefriends/trunk@4333 ed5197a5-7fde-0310-b194-c3ffbd925b24
2023-06-28 23:29:56 +00:00
0e40ba78a4 Update lit to 2.7.5, and make building the .apk part of the release.
git-svn-id: https://www.unprompted.com/svn/projects/tildefriends/trunk@4332 ed5197a5-7fde-0310-b194-c3ffbd925b24
2023-06-28 23:28:59 +00:00
d1eac6c9eb Hook up android version numbers, too.
git-svn-id: https://www.unprompted.com/svn/projects/tildefriends/trunk@4331 ed5197a5-7fde-0310-b194-c3ffbd925b24
2023-06-28 23:23:29 +00:00
8f5201b2bc Show a version number in the UI. Automate things so that the version number originates from the Makefile. Get ready for 0.0.8.
git-svn-id: https://www.unprompted.com/svn/projects/tildefriends/trunk@4330 ed5197a5-7fde-0310-b194-c3ffbd925b24
2023-06-28 23:00:34 +00:00
6022001d66 Primitive display of recent channels/tags and the same on messages.
git-svn-id: https://www.unprompted.com/svn/projects/tildefriends/trunk@4329 ed5197a5-7fde-0310-b194-c3ffbd925b24
2023-06-22 00:27:27 +00:00
f018c367ed Don't automatically add mentions for incomplete &/@/% links.
git-svn-id: https://www.unprompted.com/svn/projects/tildefriends/trunk@4328 ed5197a5-7fde-0310-b194-c3ffbd925b24
2023-06-17 14:30:17 +00:00
48c47f097a This seems to fix losing sizes when attaching files.
git-svn-id: https://www.unprompted.com/svn/projects/tildefriends/trunk@4327 ed5197a5-7fde-0310-b194-c3ffbd925b24
2023-06-17 14:23:32 +00:00
39ac215b5a Store blobs from the worker threads. Let's see if this is a good idea.
git-svn-id: https://www.unprompted.com/svn/projects/tildefriends/trunk@4326 ed5197a5-7fde-0310-b194-c3ffbd925b24
2023-06-17 14:05:23 +00:00
7d562ce85c Allow the DB writer to be used from a worker thread. Not well tested, just still trying to charge forward on moving all blocking work off the main thread.
git-svn-id: https://www.unprompted.com/svn/projects/tildefriends/trunk@4325 ed5197a5-7fde-0310-b194-c3ffbd925b24
2023-06-15 00:27:49 +00:00
51b317233a First rough-out of a mentions tab in the SSB app.
git-svn-id: https://www.unprompted.com/svn/projects/tildefriends/trunk@4324 ed5197a5-7fde-0310-b194-c3ffbd925b24
2023-06-14 22:51:58 +00:00
87ce715011 This appears to let me shrink the sparkline graphs. Freaking CSS.
git-svn-id: https://www.unprompted.com/svn/projects/tildefriends/trunk@4323 ed5197a5-7fde-0310-b194-c3ffbd925b24
2023-06-14 22:23:22 +00:00
ef5afc1e23 Minor cleanup while pondering syncing faster.
git-svn-id: https://www.unprompted.com/svn/projects/tildefriends/trunk@4322 ed5197a5-7fde-0310-b194-c3ffbd925b24
2023-06-14 21:59:04 +00:00
486212f22a Fix expanding messages on the search tab. Maybe this should happen at another level.
git-svn-id: https://www.unprompted.com/svn/projects/tildefriends/trunk@4321 ed5197a5-7fde-0310-b194-c3ffbd925b24
2023-06-14 16:39:08 +00:00
0e8867dd6e Attempt to tie subprocess lifetime to the android activity.
git-svn-id: https://www.unprompted.com/svn/projects/tildefriends/trunk@4320 ed5197a5-7fde-0310-b194-c3ffbd925b24
2023-06-08 00:51:34 +00:00
39 changed files with 506 additions and 193 deletions

View File

@ -3,6 +3,10 @@
MAKEFLAGS += --warn-undefined-variables
MAKEFLAGS += --no-builtin-rules
VERSION_CODE := 8
VERSION_NUMBER := 0.0.8
VERSION_NAME := The secret ingredient is love.
PROJECT = tildefriends
BUILD_DIR ?= out
BUILD_TYPES := debug release windebug winrelease androiddebug androidrelease androiddebug-x86_64 androidrelease-x86_64
@ -432,6 +436,18 @@ endef
$(foreach build_type,$(BUILD_TYPES),$(eval $(call build_rules,$(build_type))))
src/version.h : $(firstword $(MAKEFILE_LIST))
@echo [version] $@
@echo "#define VERSION_NUMBER \"$(VERSION_NUMBER)\"\n#define VERSION_NAME \"$(VERSION_NAME)\"\n" > $@
src/android/AndroidManifest.xml : $(firstword $(MAKEFILE_LIST))
@echo [android_version] $@
@sed -i \
-e 's/versionCode=".*"/versionCode="$(VERSION_CODE)"/' \
-e 's/versionName=".*"/versionName="$(VERSION_NUMBER)"/' \
-e 's/android:minSdkVersion=".*"/android:minSdkVersion="$(ANDROID_MIN_SDK_VERSION)"/' \
$@
# Android support.
out/res/layout_activity_main.xml.flat: src/android/res/layout/activity_main.xml
@mkdir -p $(dir $@)
@ -491,10 +507,10 @@ out/%.apk: out/apk/%.unsigned.apk
@echo [apksigner] $(notdir $@)
@$(ANDROID_BUILD_TOOLS)/apksigner sign --ks keystore.jks --ks-key-alias androidKey --ks-pass pass:android --key-pass pass:android --out $@ $<
apk: out/TildeFriends-debug.apk
apk: out/TildeFriends-release.apk
.PHONY: apk
apkgo: out/TildeFriends-debug.apk
apkgo: out/TildeFriends-release.apk
@adb install $<
@adb shell am start com.unprompted.tildefriends/.MainActivity
.PHONY: apkgo

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

View File

@ -8,7 +8,9 @@ import * as tf_user from './tf-user.js';
import * as tf_compose from './tf-compose.js';
import * as tf_news from './tf-news.js';
import * as tf_profile from './tf-profile.js';
import * as tf_tab_mentions from './tf-tab-mentions.js';
import * as tf_tab_news from './tf-tab-news.js';
import * as tf_tab_news_feed from './tf-tab-news-feed.js';
import * as tf_tab_search from './tf-tab-search.js';
import * as tf_tab_connections from './tf-tab-connections.js';
import * as tf_tag from './tf-tag.js';

View File

@ -16,6 +16,7 @@ class TfElement extends LitElement {
following: {type: Array},
users: {type: Object},
ids: {type: Array},
tags: {type: Array},
};
}
@ -32,6 +33,7 @@ class TfElement extends LitElement {
this.following = [];
this.users = {};
this.loaded = false;
this.tags = [];
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));
@ -64,6 +66,8 @@ class TfElement extends LitElement {
this.tab = 'search';
} else if (this.hash === '#connections') {
this.tab = 'connections';
} else if (this.hash === '#mentions') {
this.tab = 'mentions';
} else {
this.tab = 'news';
}
@ -251,12 +255,24 @@ class TfElement extends LitElement {
`;
}
async load_recent_tags() {
this.tags = await tfrpc.rpc.query(`
WITH recent AS (SELECT '#' || json_extract(content, '$.channel') AS tag
FROM messages
WHERE json_extract(content, '$.channel') IS NOT NULL
ORDER BY timestamp DESC LIMIT 100)
SELECT tag, COUNT(*) AS count FROM recent GROUP BY tag ORDER BY count DESC LIMIT 10
`, []);
}
async load() {
let whoami = this.whoami;
let tags = this.load_recent_tags();
let [following, users] = await this.following_deep([whoami], 2, {});
users = await this.fetch_about(following.sort(), users);
this.following = following;
this.users = users;
await tags;
console.log(`load finished ${whoami} => ${this.whoami}`);
this.whoami = whoami;
this.loaded = whoami;
@ -273,6 +289,10 @@ class TfElement extends LitElement {
return html`
<tf-tab-connections .users=${this.users} .connections=${this.connections} .broadcasts=${this.broadcasts}></tf-tab-connections>
`;
} else if (this.tab === 'mentions') {
return html`
<tf-tab-mentions .following=${this.following} whoami=${this.whoami} .users=${this.users}}></tf-tab-mentions>
`;
} else if (this.tab === 'search') {
return html`
<tf-tab-search .following=${this.following} whoami=${this.whoami} .users=${this.users} query=${this.hash?.startsWith('#q=') ? decodeURIComponent(this.hash.substring(3)) : null}></tf-tab-search>
@ -286,6 +306,8 @@ class TfElement extends LitElement {
await tfrpc.rpc.setHash('#');
} else if (tab === 'connections') {
await tfrpc.rpc.setHash('#connections');
} else if (tab === 'mentions') {
await tfrpc.rpc.setHash('#mentions');
}
}
@ -304,6 +326,7 @@ class TfElement extends LitElement {
<div>
<input type="button" class="tab" value="News" ?disabled=${self.tab == 'news'} @click=${() => self.set_tab('news')}></input>
<input type="button" class="tab" value="Connections" ?disabled=${self.tab == 'connections'} @click=${() => self.set_tab('connections')}></input>
<input type="button" class="tab" value="Mentions" ?disabled=${self.tab == 'mentions'} @click=${() => self.set_tab('mentions')}></input>
<input type="button" class="tab" value="Search" ?disabled=${self.tab == 'search'} @click=${() => self.set_tab('search')}></input>
</div>
`;
@ -316,6 +339,7 @@ class TfElement extends LitElement {
return html`
${this.render_id_picker()}
${tabs}
${this.tags.map(x => html`<tf-tag tag=${x.tag} count=${x.count}></tf-tag>`)}
${contents}
`;
}

View File

@ -220,6 +220,26 @@ class TfMessageElement extends LitElement {
}
}
render_channels() {
let content = this.message?.content;
if (this.decrypted?.type == 'post') {
content = this.decrypted;
}
let channels = [];
if (typeof content.channel === 'string') {
channels.push(`#${content.channel}`);
}
if (Array.isArray(content.mentions)) {
for (let mention of content.mentions) {
if (typeof mention?.link === 'string' &&
mention.link.startsWith('#')) {
channels.push(mention.link);
}
}
}
return channels.map(x => html`<tf-tag tag=${x}></tf-tag>`);
}
async try_decrypt(content) {
let result = await tfrpc.rpc.try_decrypt(this.whoami, content);
if (result) {
@ -330,6 +350,7 @@ class TfMessageElement extends LitElement {
`;
let content_html =
html`
${this.render_channels()}
<div @click=${this.body_click}>${body}</div>
${this.render_mentions()}
`;

View File

@ -0,0 +1,65 @@
import {LitElement, html, unsafeHTML} from './lit-all.min.js';
import * as tfrpc from '/static/tfrpc.js';
import {styles} from './tf-styles.js';
class TfTabMentionsElement extends LitElement {
static get properties() {
return {
whoami: {type: String},
users: {type: Object},
following: {type: Array},
expanded: {type: Object},
messages: {type: Array},
};
}
static styles = styles;
constructor() {
super();
let self = this;
this.whoami = null;
this.users = {};
this.following = [];
this.expanded = {};
this.messages = [];
}
async load() {
console.log('Loading...', this.whoami);
let results = await tfrpc.rpc.query(`
SELECT messages.*
FROM messages_fts(?)
JOIN messages ON messages.rowid = messages_fts.rowid
JOIN json_each(?) AS following ON messages.author = following.value
WHERE messages.author != ?
ORDER BY timestamp DESC limit 20
`,
['"' + this.whoami.replace('"', '""') + '"', JSON.stringify(this.following), this.whoami]);
console.log('Done.');
this.messages = results;
}
on_expand(event) {
if (event.detail.expanded) {
let expand = {};
expand[event.detail.id] = true;
this.expanded = Object.assign({}, this.expanded, expand);
} else {
delete this.expanded[event.detail.id];
this.expanded = Object.assign({}, this.expanded);
}
}
render() {
let self = this;
if (!this.loading) {
this.loading = true;
this.load();
}
return html`
<tf-news id="news" whoami=${this.whoami} .messages=${this.messages} .users=${this.users} .expanded=${this.expanded} @tf-expand=${this.on_expand}></tf-news>
`;
}
}
customElements.define('tf-tab-mentions', TfTabMentionsElement);

View File

@ -9,6 +9,7 @@ class TfTabSearchElement extends LitElement {
users: {type: Object},
following: {type: Array},
query: {type: String},
expanded: {type: Object},
};
}
@ -20,6 +21,7 @@ class TfTabSearchElement extends LitElement {
this.whoami = null;
this.users = {};
this.following = [];
this.expanded = {};
}
async search(query) {
@ -55,8 +57,20 @@ class TfTabSearchElement extends LitElement {
}
}
on_expand(event) {
if (event.detail.expanded) {
let expand = {};
expand[event.detail.id] = true;
this.expanded = Object.assign({}, this.expanded, expand);
} else {
delete this.expanded[event.detail.id];
this.expanded = Object.assign({}, this.expanded);
}
}
render() {
if (this.query !== this.last_query) {
this.last_query = this.query;
this.search(this.query);
}
let self = this;
@ -65,7 +79,7 @@ class TfTabSearchElement extends LitElement {
<input type="text" id="search" value=${this.query} style="flex: 1" @keydown=${this.search_keydown}></input>
<input type="button" value="Search" @click=${(event) => self.search(self.renderRoot.getElementById('search').value)}></input>
</div>
<tf-news id="news" whoami=${this.whoami} .messages=${this.messages} .users=${this.users}></tf-news>
<tf-news id="news" whoami=${this.whoami} .messages=${this.messages} .users=${this.users} .expanded=${this.expanded} @tf-expand=${this.on_expand}></tf-news>
`;
}
}

24
apps/ssb/tf-tag.js Normal file
View File

@ -0,0 +1,24 @@
import {LitElement, html, unsafeHTML} from './lit-all.min.js';
import {styles} from './tf-styles.js';
class TfTagElement extends LitElement {
static get properties() {
return {
tag: {type: String},
count: {type: Number},
};
}
static styles = styles;
constructor() {
super();
}
render() {
let number = this.count ? html` (${this.count})` : undefined;
return html`<a href="#q=${this.tag}" style="display: inline-block; margin: 3px; border: 1px solid black; background-color: #444; padding: 4px; border-radius: 3px">${this.tag}${number}</a>`;
}
}
customElements.define('tf-tag', TfTagElement);

View File

@ -52,6 +52,8 @@ class TfNavigationElement extends LitElement {
show_permissions: {type: Boolean},
status: {type: Object},
spark_lines: {type: Object},
version: {type: Object},
show_version: {type: Boolean},
};
}
@ -79,6 +81,9 @@ class TfNavigationElement extends LitElement {
get_spark_line(key, options) {
if (!this.spark_lines[key]) {
let spark_line = document.createElement('tf-sparkline');
spark_line.style.display = 'flex';
spark_line.style.flexDirection = 'row';
spark_line.style.flex = '0 100 10em';
spark_line.title = key;
if (options) {
if (options.max) {
@ -125,7 +130,8 @@ class TfNavigationElement extends LitElement {
${k_global_style}
</style>
<div style="margin: 4px; display: flex; flex-direction: row; flex-wrap: nowrap; gap: 3px">
<span>😎</span>
<span style="cursor: pointer" @click=${() => this.show_version = !this.show_version}>😎</span>
<span ?hidden=${!this.show_version} style="flex: 0 0; white-space: nowrap" title=${this.version?.name}>${this.version?.number}</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>
@ -133,7 +139,7 @@ class TfNavigationElement extends LitElement {
<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: 1 1; display: flex; flex-direction: row; white-space: nowrap; 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>
`;
@ -315,7 +321,7 @@ class TfSparkLineElement extends LitElement {
render() {
let max = Math.round(10.0 * Math.max(...this.lines.map(line => line.values[line.values.length - 1]))) / 10.0;
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">
<svg style="width-auto: object-fit: cover; 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))}
<text x="0" y="1em" style="font: 8px sans-serif; fill: #fff">${max}</text>
</svg>
@ -765,6 +771,7 @@ function _receive_websocket_message(message) {
if (window.location.hash) {
send({event: "hashChange", hash: window.location.hash});
}
document.getElementsByTagName('tf-navigation')[0].version = message.version;
send({action: 'enableStats', enabled: true});
} else if (message && message.action == "ping") {
send({action: "pong"});

View File

@ -456,7 +456,7 @@ async function getProcessBlob(blobId, key, options) {
}
broadcastEvent('onSessionBegin', [getUser(process, process)]);
if (process.app) {
process.app.send({action: "ready"});
process.app.send({action: "ready", version: version()});
process.sendPermissions();
}
await process.task.execute({name: appSourceName, source: appSource});

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

View File

@ -1 +0,0 @@
{"version":3,"sources":["reset.css"],"names":[],"mappings":"AAIA,2ZAaC,QAAS,CACT,SAAU,CACV,QAAS,CACT,cAAe,CACf,YAAa,CACb,sBACD,CAEA,8EAEC,aACD,CACA,KACC,aACD,CACA,MACC,eACD,CACA,aACC,WACD,CACA,oDAEC,UAAW,CACX,YACD,CACA,MACC,wBAAyB,CACzB,gBACD,CAIA,KACI,eAEJ,CACA,UAFI,WAKJ,CAHA,KAEI,aACJ","file":"reset.8c46b7a1.css","sourceRoot":"../../assets","sourcesContent":["/* http://meyerweb.com/eric/tools/css/reset/\n v2.0 | 20110126\n License: none (public domain)\n*/\nhtml, body, div, span, applet, object, iframe,\nh1, h2, h3, h4, h5, h6, p, blockquote, pre,\na, abbr, acronym, address, big, cite, code,\ndel, dfn, em, img, ins, kbd, q, s, samp,\nsmall, strike, strong, sub, sup, tt, var,\nb, u, i, center,\ndl, dt, dd, ol, ul, li,\nfieldset, form, label, legend,\ntable, caption, tbody, tfoot, thead, tr, th, td,\narticle, aside, canvas, details, embed,\nfigure, figcaption, footer, header, hgroup,\nmenu, nav, output, ruby, section, summary,\ntime, mark, audio, video {\n\tmargin: 0;\n\tpadding: 0;\n\tborder: 0;\n\tfont-size: 100%;\n\tfont: inherit;\n\tvertical-align: baseline;\n}\n/* HTML5 display-role reset for older browsers */\narticle, aside, details, figcaption, figure,\nfooter, header, hgroup, menu, nav, section {\n\tdisplay: block;\n}\nbody {\n\tline-height: 1;\n}\nol, ul {\n\tlist-style: none;\n}\nblockquote, q {\n\tquotes: none;\n}\nblockquote:before, blockquote:after,\nq:before, q:after {\n\tcontent: '';\n\tcontent: none;\n}\ntable {\n\tborder-collapse: collapse;\n\tborder-spacing: 0;\n}\n\n/* Prevent overscrolling */\n/* https://stackoverflow.com/a/17899813 */\nhtml {\n overflow: hidden;\n height: 100%;\n}\nbody {\n height: 100%;\n overflow: auto;\n}"]}

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

View File

@ -1,9 +1,9 @@
<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
package="com.unprompted.tildefriends"
versionCode="1"
versionName="0.0.4">
<uses-sdk android:minSdkVersion="16"/>
versionCode="8"
versionName="0.0.8">
<uses-sdk android:minSdkVersion="26"/>
<uses-permission android:name="android.permission.INTERNET"/>
<application android:label="Tilde Friends" android:usesCleartextTraffic="true" android:debuggable="true">
<meta-data android:name="android.max_aspect" android:value="2.1"/>

View File

@ -38,6 +38,7 @@ import java.util.zip.ZipInputStream;
public class MainActivity extends Activity {
WebView web_view;
String base_url;
Process process;
@Override
protected void onCreate(Bundle savedInstanceState) {
@ -81,6 +82,8 @@ public class MainActivity extends Activity {
String port_file_path = getFilesDir().toString() + "/port.txt";
base_url = "http://127.0.0.1:12345/";
MainActivity activity = this;
new Thread(new Runnable() {
@Override
public void run() {
@ -97,7 +100,9 @@ public class MainActivity extends Activity {
if (event.context().toString().equals("port.txt")) {
Log.w("tildefriends", "Observed file write: " + event.context().toString());
base_url = "http://127.0.0.1:" + String.valueOf(read_port(port_file_path)) + "/";
web_view.loadUrl(base_url);
activity.runOnUiThread(() -> {
web_view.loadUrl(base_url);
});
watcher.close();
break;
}
@ -122,15 +127,11 @@ public class MainActivity extends Activity {
builder.directory(getFilesDir());
builder.inheritIO();
try {
builder.start();
process = builder.start();
} catch (java.io.IOException e) {
Log.w("tildefriends", "IOException starting process: " + e.toString());
}
try {
Thread.sleep(1000);
} catch (java.lang.InterruptedException e) {
}
web_view.getSettings().setJavaScriptEnabled(true);
web_view.getSettings().setDatabaseEnabled(true);
web_view.getSettings().setDomStorageEnabled(true);
@ -188,6 +189,18 @@ public class MainActivity extends Activity {
});
}
@Override
protected void onDestroy()
{
if (process != null) {
Log.w("tildefriends", "Killing process.");
process.destroyForcibly();
Log.w("tildefriends", "Process killed.");
process = null;
}
super.onDestroy();
}
@Override
protected void onSaveInstanceState(Bundle outState)
{

View File

@ -19,7 +19,6 @@ typedef struct _database_t
JSContext* context;
JSValue object;
void* task;
sqlite3* db;
const char* id;
} database_t;
@ -62,16 +61,12 @@ static JSValue _database_create(JSContext* context, JSValueConst this_val, int a
++_database_count;
JSValue object = JS_NewObjectClass(context, _database_class_id);
tf_task_t* task = tf_task_get(context);
sqlite3* db = tf_ssb_get_db(tf_task_get_ssb(task));
database_t* database = tf_malloc(sizeof(database_t));
*database = (database_t)
{
.task = JS_GetContextOpaque(context),
.context = context,
.object = object,
.db = db,
};
const char* id = JS_ToCString(context, argv[0]);
database->id = tf_strdup(id);
@ -105,8 +100,10 @@ static JSValue _database_get(JSContext* context, JSValueConst this_val, int argc
database_t* database = JS_GetOpaque(this_val, _database_class_id);
if (database)
{
tf_ssb_t* ssb = tf_task_get_ssb(database->task);
sqlite3_stmt* statement;
if (sqlite3_prepare(database->db, "SELECT value FROM properties WHERE id = ? AND key = ?", -1, &statement, NULL) == SQLITE_OK)
sqlite3* db = tf_ssb_acquire_db_reader(ssb);
if (sqlite3_prepare(db, "SELECT value FROM properties WHERE id = ? AND key = ?", -1, &statement, NULL) == SQLITE_OK)
{
size_t length;
const char* keyString = JS_ToCStringLen(context, &length, argv[0]);
@ -119,6 +116,7 @@ static JSValue _database_get(JSContext* context, JSValueConst this_val, int argc
JS_FreeCString(context, keyString);
sqlite3_finalize(statement);
}
tf_ssb_release_db_reader(ssb, db);
}
return entry;
}
@ -129,7 +127,9 @@ JSValue _database_set(JSContext* context, JSValueConst this_val, int argc, JSVal
if (database)
{
sqlite3_stmt* statement;
if (sqlite3_prepare(database->db, "INSERT OR REPLACE INTO properties (id, key, value) VALUES ($1, $2, $3)", -1, &statement, NULL) == SQLITE_OK)
tf_ssb_t* ssb = tf_task_get_ssb(database->task);
sqlite3* db = tf_ssb_acquire_db_writer(ssb);
if (sqlite3_prepare(db, "INSERT OR REPLACE INTO properties (id, key, value) VALUES ($1, $2, $3)", -1, &statement, NULL) == SQLITE_OK)
{
size_t keyLength;
const char* keyString = JS_ToCStringLen(context, &keyLength, argv[0]);
@ -145,6 +145,7 @@ JSValue _database_set(JSContext* context, JSValueConst this_val, int argc, JSVal
JS_FreeCString(context, valueString);
sqlite3_finalize(statement);
}
tf_ssb_release_db_writer(ssb, db);
}
return JS_UNDEFINED;
}
@ -156,9 +157,11 @@ static JSValue _database_exchange(JSContext* context, JSValueConst this_val, int
if (database)
{
sqlite3_stmt* statement;
tf_ssb_t* ssb = tf_task_get_ssb(database->task);
sqlite3* db = tf_ssb_acquire_db_writer(ssb);
if (JS_IsNull(argv[1]) || JS_IsUndefined(argv[1]))
{
if (sqlite3_prepare(database->db, "INSERT INTO properties (id, key, value) VALUES ($1, $2, $3) ON CONFLICT DO NOTHING", -1, &statement, NULL) == SQLITE_OK)
if (sqlite3_prepare(db, "INSERT INTO properties (id, key, value) VALUES ($1, $2, $3) ON CONFLICT DO NOTHING", -1, &statement, NULL) == SQLITE_OK)
{
size_t key_length;
size_t set_length;
@ -169,14 +172,14 @@ static JSValue _database_exchange(JSContext* context, JSValueConst this_val, int
sqlite3_bind_text(statement, 3, set, set_length, NULL) == SQLITE_OK &&
sqlite3_step(statement) == SQLITE_DONE)
{
exchanged = sqlite3_changes(database->db) != 0 ? JS_TRUE : JS_FALSE;
exchanged = sqlite3_changes(db) != 0 ? JS_TRUE : JS_FALSE;
}
JS_FreeCString(context, key);
JS_FreeCString(context, set);
sqlite3_finalize(statement);
}
}
else if (sqlite3_prepare(database->db, "UPDATE properties SET value = $1 WHERE id = $2 AND key = $3 AND value = $4", -1, &statement, NULL) == SQLITE_OK)
else if (sqlite3_prepare(db, "UPDATE properties SET value = $1 WHERE id = $2 AND key = $3 AND value = $4", -1, &statement, NULL) == SQLITE_OK)
{
size_t key_length;
size_t expected_length;
@ -190,13 +193,14 @@ static JSValue _database_exchange(JSContext* context, JSValueConst this_val, int
sqlite3_bind_text(statement, 4, expected, expected_length, NULL) == SQLITE_OK &&
sqlite3_step(statement) == SQLITE_DONE)
{
exchanged = sqlite3_changes(database->db) != 0 ? JS_TRUE : JS_FALSE;
exchanged = sqlite3_changes(db) != 0 ? JS_TRUE : JS_FALSE;
}
JS_FreeCString(context, key);
JS_FreeCString(context, expected);
JS_FreeCString(context, set);
sqlite3_finalize(statement);
}
tf_ssb_release_db_writer(ssb, db);
}
return exchanged;
}
@ -207,7 +211,9 @@ JSValue _database_remove(JSContext* context, JSValueConst this_val, int argc, JS
if (database)
{
sqlite3_stmt* statement;
if (sqlite3_prepare(database->db, "DELETE FROM properties WHERE id = $1 AND key = $2", -1, &statement, NULL) == SQLITE_OK)
tf_ssb_t* ssb = tf_task_get_ssb(database->task);
sqlite3* db = tf_ssb_acquire_db_writer(ssb);
if (sqlite3_prepare(db, "DELETE FROM properties WHERE id = $1 AND key = $2", -1, &statement, NULL) == SQLITE_OK)
{
size_t keyLength;
const char* keyString = JS_ToCStringLen(context, &keyLength, argv[0]);
@ -219,6 +225,7 @@ JSValue _database_remove(JSContext* context, JSValueConst this_val, int argc, JS
JS_FreeCString(context, keyString);
sqlite3_finalize(statement);
}
tf_ssb_release_db_writer(ssb, db);
}
return JS_UNDEFINED;
}
@ -230,7 +237,9 @@ JSValue _database_get_all(JSContext* context, JSValueConst this_val, int argc, J
if (database)
{
sqlite3_stmt* statement;
if (sqlite3_prepare(database->db, "SELECT key, value FROM properties WHERE id = $1", -1, &statement, NULL) == SQLITE_OK)
tf_ssb_t* ssb = tf_task_get_ssb(database->task);
sqlite3* db = tf_ssb_acquire_db_reader(ssb);
if (sqlite3_prepare(db, "SELECT key, value FROM properties WHERE id = $1", -1, &statement, NULL) == SQLITE_OK)
{
if (sqlite3_bind_text(statement, 1, database->id, -1, NULL) == SQLITE_OK)
{
@ -243,6 +252,7 @@ JSValue _database_get_all(JSContext* context, JSValueConst this_val, int argc, J
}
sqlite3_finalize(statement);
}
tf_ssb_release_db_reader(ssb, db);
}
return array;
}
@ -254,7 +264,9 @@ JSValue _database_get_like(JSContext* context, JSValueConst this_val, int argc,
if (database)
{
sqlite3_stmt* statement;
if (sqlite3_prepare(database->db, "SELECT key, value FROM properties WHERE id = ? AND KEY LIKE ?", -1, &statement, NULL) == SQLITE_OK)
tf_ssb_t* ssb = tf_task_get_ssb(database->task);
sqlite3* db = tf_ssb_acquire_db_reader(ssb);
if (sqlite3_prepare(db, "SELECT key, value FROM properties WHERE id = ? AND KEY LIKE ?", -1, &statement, NULL) == SQLITE_OK)
{
const char* pattern = JS_ToCString(context, argv[0]);
if (sqlite3_bind_text(statement, 1, database->id, -1, NULL) == SQLITE_OK &&
@ -273,6 +285,7 @@ JSValue _database_get_like(JSContext* context, JSValueConst this_val, int argc,
JS_FreeCString(context, pattern);
sqlite3_finalize(statement);
}
tf_ssb_release_db_reader(ssb, db);
}
return result;
}
@ -280,8 +293,8 @@ JSValue _database_get_like(JSContext* context, JSValueConst this_val, int argc,
static JSValue _databases_list(JSContext* context, JSValueConst this_val, int argc, JSValueConst* argv, int magic, JSValue* data)
{
tf_task_t* task = tf_task_get(context);
sqlite3* db = tf_ssb_get_db(tf_task_get_ssb(task));
tf_ssb_t* ssb = tf_task_get_ssb(task);
sqlite3* db = tf_ssb_acquire_db_reader(ssb);
JSValue array = JS_UNDEFINED;
sqlite3_stmt* statement;
if (sqlite3_prepare(db, "SELECT DISTINCT id FROM properties WHERE id LIKE ?", -1, &statement, NULL) == SQLITE_OK)
@ -299,5 +312,6 @@ static JSValue _databases_list(JSContext* context, JSValueConst this_val, int ar
JS_FreeCString(context, pattern);
sqlite3_finalize(statement);
}
tf_ssb_release_db_reader(ssb, db);
return array;
}

View File

@ -180,9 +180,10 @@ typedef struct _tf_ssb_t
tf_trace_t* trace;
const char* db_path;
sqlite3* db;
uv_mutex_t db_readers_lock;
uv_mutex_t db_writer_lock;
sqlite3* db_writer;
sqlite3** db_readers;
int db_readers_count;
@ -681,7 +682,9 @@ void tf_ssb_connection_remove_new_message_request(tf_ssb_connection_t* connectio
void tf_ssb_connection_remove_request(tf_ssb_connection_t* connection, int32_t request_number)
{
tf_ssb_request_t* request = bsearch(&request_number, connection->requests, connection->requests_count, sizeof(tf_ssb_request_t), _request_compare);
tf_ssb_request_t* request = connection->requests_count ?
bsearch(&request_number, connection->requests, connection->requests_count, sizeof(tf_ssb_request_t), _request_compare) :
NULL;
if (request)
{
if (request->cleanup)
@ -2097,6 +2100,7 @@ tf_ssb_t* tf_ssb_create(uv_loop_t* loop, JSContext* context, const char* db_path
}
uv_mutex_init(&ssb->db_readers_lock);
uv_mutex_init(&ssb->db_writer_lock);
JS_NewClassID(&_connection_class_id);
JSClassDef def =
@ -2107,7 +2111,7 @@ tf_ssb_t* tf_ssb_create(uv_loop_t* loop, JSContext* context, const char* db_path
JS_NewClass(JS_GetRuntime(ssb->context), _connection_class_id, &def);
ssb->db_path = tf_strdup(db_path);
sqlite3_open(db_path, &ssb->db);
sqlite3_open_v2(db_path, &ssb->db_writer, SQLITE_OPEN_READWRITE | SQLITE_OPEN_URI, NULL);
tf_ssb_db_init(ssb);
if (loop)
@ -2140,11 +2144,6 @@ tf_ssb_t* tf_ssb_create(uv_loop_t* loop, JSContext* context, const char* db_path
return ssb;
}
sqlite3* tf_ssb_get_db(tf_ssb_t* ssb)
{
return ssb->db;
}
sqlite3* tf_ssb_acquire_db_reader(tf_ssb_t* ssb)
{
sqlite3* db = NULL;
@ -2155,7 +2154,7 @@ sqlite3* tf_ssb_acquire_db_reader(tf_ssb_t* ssb)
}
else
{
sqlite3_open_v2(ssb->db_path, &db, SQLITE_OPEN_READONLY, NULL);
sqlite3_open_v2(ssb->db_path, &db, SQLITE_OPEN_READONLY | SQLITE_OPEN_URI, NULL);
tf_ssb_db_init_reader(db);
tf_trace_sqlite(ssb->trace, db);
}
@ -2171,6 +2170,22 @@ void tf_ssb_release_db_reader(tf_ssb_t* ssb, sqlite3* db)
uv_mutex_unlock(&ssb->db_readers_lock);
}
sqlite3* tf_ssb_acquire_db_writer(tf_ssb_t* ssb)
{
uv_mutex_lock(&ssb->db_writer_lock);
sqlite3* writer = ssb->db_writer;
assert(writer);
ssb->db_writer = NULL;
return writer;
}
void tf_ssb_release_db_writer(tf_ssb_t* ssb, sqlite3* db)
{
assert(ssb->db_writer == NULL);
ssb->db_writer = db;
uv_mutex_unlock(&ssb->db_writer_lock);
}
uv_loop_t* tf_ssb_get_loop(tf_ssb_t* ssb)
{
return ssb->loop;
@ -2203,10 +2218,12 @@ void tf_ssb_get_private_key(tf_ssb_t* ssb, uint8_t* out_private, size_t private_
void tf_ssb_set_trace(tf_ssb_t* ssb, tf_trace_t* trace)
{
ssb->trace = trace;
if (trace && ssb->db)
sqlite3* db = tf_ssb_acquire_db_writer(ssb);
if (trace && db)
{
tf_trace_sqlite(trace, ssb->db);
tf_trace_sqlite(trace, db);
}
tf_ssb_release_db_writer(ssb, db);
}
tf_trace_t* tf_ssb_get_trace(tf_ssb_t* ssb)
@ -2334,7 +2351,7 @@ void tf_ssb_destroy(tf_ssb_t* ssb)
JS_FreeContext(ssb->context);
JS_FreeRuntime(ssb->runtime);
}
sqlite3_close(ssb->db);
sqlite3_close(ssb->db_writer);
while (ssb->broadcasts)
{
tf_ssb_broadcast_t* broadcast = ssb->broadcasts;
@ -2355,6 +2372,7 @@ void tf_ssb_destroy(tf_ssb_t* ssb)
}
tf_free(ssb->db_readers);
uv_mutex_destroy(&ssb->db_readers_lock);
uv_mutex_destroy(&ssb->db_writer_lock);
tf_free((void*)ssb->db_path);
tf_free(ssb);
}
@ -2610,10 +2628,14 @@ static void _tf_ssb_send_broadcast(tf_ssb_t* ssb, struct sockaddr_in* address, s
{
struct sockaddr server_addr;
int len = (int)sizeof(server_addr);
if (uv_tcp_getsockname(&ssb->server, &server_addr, &len) != 0 ||
server_addr.sa_family != AF_INET)
int r = uv_tcp_getsockname(&ssb->server, &server_addr, &len);
if (r != 0)
{
tf_printf("Unable to get server's address.\n");
tf_printf("Unable to get server's address: %s.\n", uv_strerror(r));
}
else if (server_addr.sa_family != AF_INET)
{
tf_printf("Unexpected address family: %d\n", server_addr.sa_family);
}
char address_str[256];
@ -2636,7 +2658,7 @@ static void _tf_ssb_send_broadcast(tf_ssb_t* ssb, struct sockaddr_in* address, s
broadcast_addr.sin_addr.s_addr =
(address->sin_addr.s_addr & netmask->sin_addr.s_addr) |
(INADDR_BROADCAST & ~netmask->sin_addr.s_addr);
int r = uv_udp_try_send(&ssb->broadcast_sender, &buf, 1, (struct sockaddr*)&broadcast_addr);
r = uv_udp_try_send(&ssb->broadcast_sender, &buf, 1, (struct sockaddr*)&broadcast_addr);
if (r < 0)
{
char broadcast_str[256] = { 0 };
@ -3218,6 +3240,10 @@ void tf_ssb_notify_message_added(tf_ssb_t* ssb, const char* id)
for (tf_ssb_connection_t* connection = ssb->connections; connection; connection = connection->next)
{
if (!connection->message_requests_count)
{
continue;
}
tf_ssb_connection_message_request_t* message_request =
bsearch(
author_string,

View File

@ -16,7 +16,6 @@
typedef struct _tf_ssb_connections_t
{
tf_ssb_t* ssb;
sqlite3* db;
uv_timer_t timer;
} tf_ssb_connections_t;
@ -55,7 +54,8 @@ static bool _tf_ssb_connections_get_next_connection(tf_ssb_connections_t* connec
{
bool result = false;
sqlite3_stmt* statement;
if (sqlite3_prepare(connections->db, "SELECT host, port, key FROM connections WHERE last_attempt IS NULL OR (strftime('%s', 'now') - last_attempt > $1) ORDER BY last_attempt LIMIT 1", -1, &statement, NULL) == SQLITE_OK)
sqlite3* db = tf_ssb_acquire_db_reader(connections->ssb);
if (sqlite3_prepare(db, "SELECT host, port, key FROM connections WHERE last_attempt IS NULL OR (strftime('%s', 'now') - last_attempt > $1) ORDER BY last_attempt LIMIT 1", -1, &statement, NULL) == SQLITE_OK)
{
if (sqlite3_bind_int(statement, 1, 60000) == SQLITE_OK &&
sqlite3_step(statement) == SQLITE_ROW)
@ -69,8 +69,9 @@ static bool _tf_ssb_connections_get_next_connection(tf_ssb_connections_t* connec
}
else
{
tf_printf("prepare: %s\n", sqlite3_errmsg(connections->db));
tf_printf("prepare: %s\n", sqlite3_errmsg(db));
}
tf_ssb_release_db_reader(connections->ssb, db);
return result;
}
@ -100,7 +101,6 @@ tf_ssb_connections_t* tf_ssb_connections_create(tf_ssb_t* ssb)
tf_ssb_connections_t* connections = tf_malloc(sizeof(tf_ssb_connections_t));
memset(connections, 0, sizeof(*connections));
connections->ssb = ssb;
connections->db = tf_ssb_get_db(ssb);
tf_ssb_add_connections_changed_callback(ssb, _tf_ssb_connections_changed_callback, NULL, connections);
@ -128,7 +128,8 @@ void tf_ssb_connections_destroy(tf_ssb_connections_t* connections)
void tf_ssb_connections_store(tf_ssb_connections_t* connections, const char* host, int port, const char* key)
{
sqlite3_stmt* statement;
if (sqlite3_prepare(connections->db, "INSERT INTO connections (host, port, key) VALUES ($1, $2, $3) ON CONFLICT DO NOTHING", -1, &statement, NULL) == SQLITE_OK)
sqlite3* db = tf_ssb_acquire_db_writer(connections->ssb);
if (sqlite3_prepare(db, "INSERT INTO connections (host, port, key) VALUES ($1, $2, $3) ON CONFLICT DO NOTHING", -1, &statement, NULL) == SQLITE_OK)
{
if (sqlite3_bind_text(statement, 1, host, -1, NULL) == SQLITE_OK &&
sqlite3_bind_int(statement, 2, port) == SQLITE_OK &&
@ -137,17 +138,19 @@ void tf_ssb_connections_store(tf_ssb_connections_t* connections, const char* hos
int r = sqlite3_step(statement);
if (r != SQLITE_DONE)
{
tf_printf("tf_ssb_connections_store: %d, %s.\n", r, sqlite3_errmsg(connections->db));
tf_printf("tf_ssb_connections_store: %d, %s.\n", r, sqlite3_errmsg(db));
}
}
sqlite3_finalize(statement);
}
tf_ssb_release_db_writer(connections->ssb, db);
}
void tf_ssb_connections_set_attempted(tf_ssb_connections_t* connections, const char* host, int port, const char* key)
{
sqlite3_stmt* statement;
if (sqlite3_prepare(connections->db, "UPDATE connections SET last_attempt = strftime('%s', 'now') WHERE host = $1 AND port = $2 AND key = $3", -1, &statement, NULL) == SQLITE_OK)
sqlite3* db = tf_ssb_acquire_db_writer(connections->ssb);
if (sqlite3_prepare(db, "UPDATE connections SET last_attempt = strftime('%s', 'now') WHERE host = $1 AND port = $2 AND key = $3", -1, &statement, NULL) == SQLITE_OK)
{
if (sqlite3_bind_text(statement, 1, host, -1, NULL) == SQLITE_OK &&
sqlite3_bind_int(statement, 2, port) == SQLITE_OK &&
@ -155,17 +158,19 @@ void tf_ssb_connections_set_attempted(tf_ssb_connections_t* connections, const c
{
if (sqlite3_step(statement) != SQLITE_DONE)
{
tf_printf("tf_ssb_connections_set_attempted: %s.\n", sqlite3_errmsg(connections->db));
tf_printf("tf_ssb_connections_set_attempted: %s.\n", sqlite3_errmsg(db));
}
}
sqlite3_finalize(statement);
}
tf_ssb_release_db_writer(connections->ssb, db);
}
void tf_ssb_connections_set_succeeded(tf_ssb_connections_t* connections, const char* host, int port, const char* key)
{
sqlite3_stmt* statement;
if (sqlite3_prepare(connections->db, "UPDATE connections SET last_success = strftime('%s', 'now') WHERE host = $1 AND port = $2 AND key = $3", -1, &statement, NULL) == SQLITE_OK)
sqlite3* db = tf_ssb_acquire_db_writer(connections->ssb);
if (sqlite3_prepare(db, "UPDATE connections SET last_success = strftime('%s', 'now') WHERE host = $1 AND port = $2 AND key = $3", -1, &statement, NULL) == SQLITE_OK)
{
if (sqlite3_bind_text(statement, 1, host, -1, NULL) == SQLITE_OK &&
sqlite3_bind_int(statement, 2, port) == SQLITE_OK &&
@ -173,9 +178,10 @@ void tf_ssb_connections_set_succeeded(tf_ssb_connections_t* connections, const c
{
if (sqlite3_step(statement) != SQLITE_DONE)
{
tf_printf("tf_ssb_connections_set_succeeded: %s.\n", sqlite3_errmsg(connections->db));
tf_printf("tf_ssb_connections_set_succeeded: %s.\n", sqlite3_errmsg(db));
}
}
sqlite3_finalize(statement);
}
tf_ssb_release_db_writer(connections->ssb, db);
}

View File

@ -67,7 +67,7 @@ void tf_ssb_db_init_reader(sqlite3* db)
void tf_ssb_db_init(tf_ssb_t* ssb)
{
sqlite3* db = tf_ssb_get_db(ssb);
sqlite3* db = tf_ssb_acquire_db_writer(ssb);
_tf_ssb_db_init_internal(db);
_tf_ssb_db_exec(db,
"CREATE TABLE IF NOT EXISTS messages ("
@ -222,6 +222,7 @@ void tf_ssb_db_init(tf_ssb_t* ssb)
tf_printf("Adding sequence_before_author column.\n");
_tf_ssb_db_exec(db, "ALTER TABLE messages ADD COLUMN sequence_before_author INTEGER");
}
tf_ssb_release_db_writer(ssb, db);
}
static bool _tf_ssb_db_previous_message_exists(sqlite3* db, const char* author, int64_t sequence, const char* previous)
@ -252,31 +253,37 @@ static bool _tf_ssb_db_previous_message_exists(sqlite3* db, const char* author,
bool tf_ssb_db_store_message(tf_ssb_t* ssb, JSContext* context, const char* id, JSValue val, const char* signature, bool sequence_before_author)
{
bool stored = false;
JSValue previousval = JS_GetPropertyStr(context, val, "previous");
const char* previous = JS_IsNull(previousval) ? NULL : JS_ToCString(context, previousval);
JS_FreeValue(context, previousval);
JSValue authorval = JS_GetPropertyStr(context, val, "author");
const char* author = JS_ToCString(context, authorval);
JS_FreeValue(context, authorval);
int64_t sequence = -1;
JSValue sequenceval = JS_GetPropertyStr(context, val, "sequence");
JS_ToInt64(context, &sequence, sequenceval);
JS_FreeValue(context, sequenceval);
double timestamp = -1.0;
JSValue timestampval = JS_GetPropertyStr(context, val, "timestamp");
JS_ToFloat64(context, &timestamp, timestampval);
JS_FreeValue(context, timestampval);
JSValue contentval = JS_GetPropertyStr(context, val, "content");
JSValue content = JS_JSONStringify(context, contentval, JS_NULL, JS_NULL);
size_t content_len;
const char* contentstr = JS_ToCStringLen(context, &content_len, content);
JS_FreeValue(context, contentval);
sqlite3* db = tf_ssb_get_db(ssb);
sqlite3* db = tf_ssb_acquire_db_writer(ssb);
sqlite3_stmt* statement;
int64_t last_row_id = -1;
if (_tf_ssb_db_previous_message_exists(db, author, sequence, previous))
{
double timestamp = -1.0;
JSValue timestampval = JS_GetPropertyStr(context, val, "timestamp");
JS_ToFloat64(context, &timestamp, timestampval);
JS_FreeValue(context, timestampval);
JSValue contentval = JS_GetPropertyStr(context, val, "content");
JSValue content = JS_JSONStringify(context, contentval, JS_NULL, JS_NULL);
size_t content_len;
const char* contentstr = JS_ToCStringLen(context, &content_len, content);
JS_FreeValue(context, contentval);
const char* query = "INSERT INTO messages (id, previous, author, sequence, timestamp, content, hash, signature, sequence_before_author) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?) ON CONFLICT DO NOTHING";
if (sqlite3_prepare(db, query, -1, &statement, NULL) == SQLITE_OK)
{
@ -309,47 +316,46 @@ bool tf_ssb_db_store_message(tf_ssb_t* ssb, JSContext* context, const char* id,
}
else
{
tf_printf("prepare failed: %s\n", sqlite3_errmsg(db));
tf_printf("%s: prepare failed: %s\n", __FUNCTION__, sqlite3_errmsg(db));
}
JS_FreeCString(context, contentstr);
JS_FreeValue(context, content);
}
else
{
tf_printf("Previous message doesn't exist.\n");
}
if (last_row_id != -1)
{
const char* query = "SELECT DISTINCT json.value FROM messages, json_tree(messages.content) AS json LEFT OUTER JOIN blobs ON json.value = blobs.id WHERE messages.rowid = ?1 AND json.value LIKE '&%%.sha256' AND length(json.value) = ?2 AND blobs.content IS NULL";
if (sqlite3_prepare(db, query, -1, &statement, NULL) == SQLITE_OK)
{
if (sqlite3_bind_int64(statement, 1, last_row_id) == SQLITE_OK &&
sqlite3_bind_int(statement, 2, k_blob_id_len - 1) == SQLITE_OK)
{
int r = SQLITE_OK;
while ((r = sqlite3_step(statement)) == SQLITE_ROW)
{
tf_ssb_notify_blob_want_added(ssb, (const char*)sqlite3_column_text(statement, 0));
}
if (r != SQLITE_DONE)
{
tf_printf("%s\n", sqlite3_errmsg(db));
}
}
sqlite3_finalize(statement);
}
else
{
tf_printf("prepare failed: %s\n", sqlite3_errmsg(db));
}
}
if (last_row_id != -1)
{
const char* query = "SELECT DISTINCT json.value FROM messages, json_tree(messages.content) AS json LEFT OUTER JOIN blobs ON json.value = blobs.id WHERE messages.rowid = ?1 AND json.value LIKE '&%%.sha256' AND length(json.value) = ?2 AND blobs.content IS NULL";
if (sqlite3_prepare(db, query, -1, &statement, NULL) == SQLITE_OK)
{
if (sqlite3_bind_int64(statement, 1, last_row_id) == SQLITE_OK &&
sqlite3_bind_int(statement, 2, k_blob_id_len - 1) == SQLITE_OK)
{
int r = SQLITE_OK;
while ((r = sqlite3_step(statement)) == SQLITE_ROW)
{
tf_ssb_notify_blob_want_added(ssb, (const char*)sqlite3_column_text(statement, 0));
}
if (r != SQLITE_DONE)
{
tf_printf("%s\n", sqlite3_errmsg(db));
}
}
sqlite3_finalize(statement);
}
else
{
tf_printf("%s: prepare failed: %s\n", __FUNCTION__, sqlite3_errmsg(db));
}
}
tf_ssb_release_db_writer(ssb, db);
JS_FreeValue(context, previousval);
JS_FreeCString(context, author);
JS_FreeValue(context, authorval);
JS_FreeCString(context, previous);
JS_FreeCString(context, contentstr);
JS_FreeValue(context, content);
return stored;
}
@ -357,8 +363,9 @@ bool tf_ssb_db_message_content_get(tf_ssb_t* ssb, const char* id, uint8_t** out_
{
bool result = false;
sqlite3_stmt* statement;
sqlite3* db = tf_ssb_acquire_db_reader(ssb);
const char* query = "SELECT content FROM messages WHERE id = ?";
if (sqlite3_prepare(tf_ssb_get_db(ssb), query, -1, &statement, NULL) == SQLITE_OK)
if (sqlite3_prepare(db, query, -1, &statement, NULL) == SQLITE_OK)
{
if (sqlite3_bind_text(statement, 1, id, -1, NULL) == SQLITE_OK &&
sqlite3_step(statement) == SQLITE_ROW)
@ -379,6 +386,7 @@ bool tf_ssb_db_message_content_get(tf_ssb_t* ssb, const char* id, uint8_t** out_
}
sqlite3_finalize(statement);
}
tf_ssb_release_db_reader(ssb, db);
return result;
}
@ -386,8 +394,9 @@ bool tf_ssb_db_blob_has(tf_ssb_t* ssb, const char* id)
{
bool result = false;
sqlite3_stmt* statement;
sqlite3* db = tf_ssb_acquire_db_reader(ssb);
const char* query = "SELECT COUNT(*) FROM blobs WHERE id = $1";
if (sqlite3_prepare(tf_ssb_get_db(ssb), query, -1, &statement, NULL) == SQLITE_OK)
if (sqlite3_prepare(db, query, -1, &statement, NULL) == SQLITE_OK)
{
if (sqlite3_bind_text(statement, 1, id, -1, NULL) == SQLITE_OK &&
sqlite3_step(statement) == SQLITE_ROW)
@ -396,6 +405,7 @@ bool tf_ssb_db_blob_has(tf_ssb_t* ssb, const char* id)
}
sqlite3_finalize(statement);
}
tf_ssb_release_db_reader(ssb, db);
return result;
}
@ -403,8 +413,9 @@ bool tf_ssb_db_blob_get(tf_ssb_t* ssb, const char* id, uint8_t** out_blob, size_
{
bool result = false;
sqlite3_stmt* statement;
sqlite3* db = tf_ssb_acquire_db_reader(ssb);
const char* query = "SELECT content FROM blobs WHERE id = $1";
if (sqlite3_prepare(tf_ssb_get_db(ssb), query, -1, &statement, NULL) == SQLITE_OK)
if (sqlite3_prepare(db, query, -1, &statement, NULL) == SQLITE_OK)
{
if (sqlite3_bind_text(statement, 1, id, -1, NULL) == SQLITE_OK &&
sqlite3_step(statement) == SQLITE_ROW)
@ -428,13 +439,14 @@ bool tf_ssb_db_blob_get(tf_ssb_t* ssb, const char* id, uint8_t** out_blob, size_
}
sqlite3_finalize(statement);
}
tf_ssb_release_db_reader(ssb, db);
return result;
}
bool tf_ssb_db_blob_store(tf_ssb_t* ssb, const uint8_t* blob, size_t size, char* out_id, size_t out_id_size, bool* out_new)
{
bool result = false;
sqlite3* db = tf_ssb_get_db(ssb);
sqlite3* db = tf_ssb_acquire_db_writer(ssb);
sqlite3_stmt* statement;
uint8_t hash[crypto_hash_sha256_BYTES];
@ -464,8 +476,9 @@ bool tf_ssb_db_blob_store(tf_ssb_t* ssb, const uint8_t* blob, size_t size, char*
}
else
{
tf_printf("prepare failed: %s\n", sqlite3_errmsg(db));
tf_printf("%s: prepare failed: %s\n", __FUNCTION__, sqlite3_errmsg(db));
}
tf_ssb_release_db_writer(ssb, db);
if (rows)
{
@ -492,7 +505,8 @@ bool tf_ssb_db_get_message_by_author_and_sequence(tf_ssb_t* ssb, const char* aut
bool found = false;
sqlite3_stmt* statement;
const char* query = "SELECT id, timestamp, content FROM messages WHERE author = $1 AND sequence = $2";
if (sqlite3_prepare(tf_ssb_get_db(ssb), query, -1, &statement, NULL) == SQLITE_OK)
sqlite3* db = tf_ssb_acquire_db_reader(ssb);
if (sqlite3_prepare(db, query, -1, &statement, NULL) == SQLITE_OK)
{
if (sqlite3_bind_text(statement, 1, author, -1, NULL) == SQLITE_OK &&
sqlite3_bind_int64(statement, 2, sequence) == SQLITE_OK &&
@ -516,8 +530,9 @@ bool tf_ssb_db_get_message_by_author_and_sequence(tf_ssb_t* ssb, const char* aut
}
else
{
tf_printf("prepare failed: %s\n", sqlite3_errmsg(tf_ssb_get_db(ssb)));
tf_printf("%s: prepare failed: %s\n", __FUNCTION__, sqlite3_errmsg(db));
}
tf_ssb_release_db_reader(ssb, db);
return found;
}
@ -525,8 +540,9 @@ bool tf_ssb_db_get_latest_message_by_author(tf_ssb_t* ssb, const char* author, i
{
bool found = false;
sqlite3_stmt* statement;
sqlite3* db = tf_ssb_acquire_db_reader(ssb);
const char* query = "SELECT id, sequence FROM messages WHERE author = $1 AND sequence = (SELECT MAX(sequence) FROM messages WHERE author = $1)";
if (sqlite3_prepare(tf_ssb_get_db(ssb), query, -1, &statement, NULL) == SQLITE_OK)
if (sqlite3_prepare(db, query, -1, &statement, NULL) == SQLITE_OK)
{
if (sqlite3_bind_text(statement, 1, author, -1, NULL) == SQLITE_OK &&
sqlite3_step(statement) == SQLITE_ROW)
@ -545,8 +561,9 @@ bool tf_ssb_db_get_latest_message_by_author(tf_ssb_t* ssb, const char* author, i
}
else
{
tf_printf("prepare failed: %s\n", sqlite3_errmsg(tf_ssb_get_db(ssb)));
tf_printf("%s: prepare failed: %s\n", __FUNCTION__, sqlite3_errmsg(db));
}
tf_ssb_release_db_reader(ssb, db);
return found;
}
@ -682,7 +699,7 @@ static int _tf_ssb_sqlite_authorizer(void* user_data, int action_code, const cha
JSValue tf_ssb_db_visit_query(tf_ssb_t* ssb, const char* query, const JSValue binds, void (*callback)(JSValue row, void* user_data), void* user_data)
{
JSValue result = JS_UNDEFINED;
sqlite3* db = tf_ssb_get_db(ssb);
sqlite3* db = tf_ssb_acquire_db_reader(ssb);
JSContext* context = tf_ssb_get_context(ssb);
sqlite3_stmt* statement;
sqlite3_set_authorizer(db, _tf_ssb_sqlite_authorizer, ssb);
@ -717,6 +734,7 @@ JSValue tf_ssb_db_visit_query(tf_ssb_t* ssb, const char* query, const JSValue bi
result = JS_ThrowInternalError(context, "SQL Error %s: preparing \"%s\".", sqlite3_errmsg(db), query);
}
sqlite3_set_authorizer(db, NULL, NULL);
tf_ssb_release_db_reader(ssb, db);
return result;
}
@ -870,7 +888,7 @@ bool tf_ssb_db_check(sqlite3* db, const char* check_author)
int tf_ssb_db_identity_get_count_for_user(tf_ssb_t* ssb, const char* user)
{
int count = 0;
sqlite3* db = tf_ssb_get_db(ssb);
sqlite3* db = tf_ssb_acquire_db_reader(ssb);
sqlite3_stmt* statement = NULL;
if (sqlite3_prepare(db, "SELECT COUNT(*) FROM identities WHERE user = ?", -1, &statement, NULL) == SQLITE_OK)
{
@ -883,6 +901,7 @@ int tf_ssb_db_identity_get_count_for_user(tf_ssb_t* ssb, const char* user)
}
sqlite3_finalize(statement);
}
tf_ssb_release_db_reader(ssb, db);
return count;
}
@ -907,7 +926,7 @@ bool tf_ssb_db_identity_create(tf_ssb_t* ssb, const char* user, uint8_t* out_pub
bool tf_ssb_db_identity_add(tf_ssb_t* ssb, const char* user, const char* public_key, const char* private_key)
{
bool added = false;
sqlite3* db = tf_ssb_get_db(ssb);
sqlite3* db = tf_ssb_acquire_db_writer(ssb);
sqlite3_stmt* statement = NULL;
if (sqlite3_prepare(db, "INSERT INTO identities (user, public_key, private_key) VALUES (?, ?, ?) ON CONFLICT DO NOTHING", -1, &statement, NULL) == SQLITE_OK)
{
@ -925,12 +944,13 @@ bool tf_ssb_db_identity_add(tf_ssb_t* ssb, const char* user, const char* public_
}
sqlite3_finalize(statement);
}
tf_ssb_release_db_writer(ssb, db);
return added;
}
void tf_ssb_db_identity_visit(tf_ssb_t* ssb, const char* user, void (*callback)(const char* identity, void* user_data), void* user_data)
{
sqlite3* db = tf_ssb_get_db(ssb);
sqlite3* db = tf_ssb_acquire_db_reader(ssb);
sqlite3_stmt* statement = NULL;
if (sqlite3_prepare(db, "SELECT public_key FROM identities WHERE user = ? ORDER BY public_key", -1, &statement, NULL) == SQLITE_OK)
{
@ -943,11 +963,12 @@ void tf_ssb_db_identity_visit(tf_ssb_t* ssb, const char* user, void (*callback)(
}
sqlite3_finalize(statement);
}
tf_ssb_release_db_reader(ssb, db);
}
void tf_ssb_db_identity_visit_all(tf_ssb_t* ssb, void (*callback)(const char* identity, void* user_data), void* user_data)
{
sqlite3* db = tf_ssb_get_db(ssb);
sqlite3* db = tf_ssb_acquire_db_reader(ssb);
sqlite3_stmt* statement = NULL;
if (sqlite3_prepare(db, "SELECT public_key FROM identities ORDER BY public_key", -1, &statement, NULL) == SQLITE_OK)
{
@ -957,13 +978,14 @@ void tf_ssb_db_identity_visit_all(tf_ssb_t* ssb, void (*callback)(const char* id
}
sqlite3_finalize(statement);
}
tf_ssb_release_db_reader(ssb, db);
}
bool tf_ssb_db_identity_get_private_key(tf_ssb_t* ssb, const char* user, const char* public_key, uint8_t* out_private_key, size_t private_key_size)
{
bool success = false;
memset(out_private_key, 0, crypto_sign_SECRETKEYBYTES);
sqlite3* db = tf_ssb_get_db(ssb);
sqlite3* db = tf_ssb_acquire_db_reader(ssb);
sqlite3_stmt* statement = NULL;
if (sqlite3_prepare(db, "SELECT private_key FROM identities WHERE user = ? AND public_key = ?", -1, &statement, NULL) == SQLITE_OK)
{
@ -979,6 +1001,7 @@ bool tf_ssb_db_identity_get_private_key(tf_ssb_t* ssb, const char* user, const c
}
sqlite3_finalize(statement);
}
tf_ssb_release_db_reader(ssb, db);
return success;
}
@ -1046,7 +1069,7 @@ static following_t* _get_following(tf_ssb_t* ssb, const char* id, following_t***
if (depth < max_depth && !already_populated)
{
sqlite3* db = tf_ssb_get_db(ssb);
sqlite3* db = tf_ssb_acquire_db_reader(ssb);
sqlite3_stmt* statement = NULL;
if (sqlite3_prepare(db, "SELECT json_extract(content, '$.contact'), json_extract(content, '$.following'), json_extract(content, '$.blocking') FROM messages WHERE author = ? AND json_extract(content, '$.type') = 'contact' ORDER BY sequence", -1, &statement, NULL) == SQLITE_OK)
{
@ -1077,6 +1100,7 @@ static following_t* _get_following(tf_ssb_t* ssb, const char* id, following_t***
}
sqlite3_finalize(statement);
}
tf_ssb_release_db_reader(ssb, db);
}
return entry;
}
@ -1143,7 +1167,7 @@ JSValue tf_ssb_db_get_message_by_id( tf_ssb_t* ssb, const char* id, bool is_keys
{
JSValue result = JS_UNDEFINED;
JSContext* context = tf_ssb_get_context(ssb);
sqlite3* db = tf_ssb_get_db(ssb);
sqlite3* db = tf_ssb_acquire_db_reader(ssb);
sqlite3_stmt* statement;
if (sqlite3_prepare(db, "SELECT previous, author, id, sequence, timestamp, hash, content, signature, sequence_before_author FROM messages WHERE id = ?", -1, &statement, NULL) == SQLITE_OK)
{
@ -1178,12 +1202,13 @@ JSValue tf_ssb_db_get_message_by_id( tf_ssb_t* ssb, const char* id, bool is_keys
}
sqlite3_finalize(statement);
}
tf_ssb_release_db_reader(ssb, db);
return result;
}
tf_ssb_db_stored_connection_t* tf_ssb_db_get_stored_connections(tf_ssb_t* ssb, int* out_count)
{
sqlite3* db = tf_ssb_get_db(ssb);
sqlite3* db = tf_ssb_acquire_db_reader(ssb);
tf_ssb_db_stored_connection_t* result = NULL;
int count = 0;
@ -1203,6 +1228,7 @@ tf_ssb_db_stored_connection_t* tf_ssb_db_get_stored_connections(tf_ssb_t* ssb, i
}
sqlite3_finalize(statement);
}
tf_ssb_release_db_reader(ssb, db);
*out_count = count;
return result;
@ -1210,7 +1236,7 @@ tf_ssb_db_stored_connection_t* tf_ssb_db_get_stored_connections(tf_ssb_t* ssb, i
void tf_ssb_db_forget_stored_connection(tf_ssb_t* ssb, const char* address, int port, const char* pubkey)
{
sqlite3* db = tf_ssb_get_db(ssb);
sqlite3* db = tf_ssb_acquire_db_writer(ssb);
sqlite3_stmt* statement;
if (sqlite3_prepare(db, "DELETE FROM connections WHERE host = ? AND port = ? AND key = ?", -1, &statement, NULL) == SQLITE_OK)
{
@ -1223,4 +1249,5 @@ void tf_ssb_db_forget_stored_connection(tf_ssb_t* ssb, const char* address, int
}
sqlite3_finalize(statement);
}
tf_ssb_release_db_writer(ssb, db);
}

View File

@ -87,9 +87,10 @@ void tf_ssb_export(tf_ssb_t* ssb, const char* key)
}
char app_blob_id[64] = { 0 };
sqlite3_busy_timeout(tf_ssb_get_db(ssb), 10000);
sqlite3* db = tf_ssb_acquire_db_reader(ssb);
sqlite3_busy_timeout(db, 10000);
sqlite3_stmt* statement;
if (sqlite3_prepare(tf_ssb_get_db(ssb), "SELECT value FROM properties WHERE id = $1 AND key = 'path:' || $2", -1, &statement, NULL) == SQLITE_OK)
if (sqlite3_prepare(db, "SELECT value FROM properties WHERE id = $1 AND key = 'path:' || $2", -1, &statement, NULL) == SQLITE_OK)
{
if (sqlite3_bind_text(statement, 1, user, -1, NULL) == SQLITE_OK &&
sqlite3_bind_text(statement, 2, path, -1, NULL) == SQLITE_OK &&
@ -105,6 +106,7 @@ void tf_ssb_export(tf_ssb_t* ssb, const char* key)
}
sqlite3_finalize(statement);
}
tf_ssb_release_db_reader(ssb, db);
if (!*app_blob_id)
{

View File

@ -69,9 +69,10 @@ typedef struct _tf_ssb_blob_wants_t
tf_ssb_t* tf_ssb_create(uv_loop_t* loop, JSContext* context, const char* db_path);
void tf_ssb_destroy(tf_ssb_t* ssb);
sqlite3* tf_ssb_get_db(tf_ssb_t* ssb);
sqlite3* tf_ssb_acquire_db_reader(tf_ssb_t* ssb);
void tf_ssb_release_db_reader(tf_ssb_t* ssb, sqlite3* db);
sqlite3* tf_ssb_acquire_db_writer(tf_ssb_t* ssb);
void tf_ssb_release_db_writer(tf_ssb_t* ssb, sqlite3* db);
uv_loop_t* tf_ssb_get_loop(tf_ssb_t* ssb);
void tf_ssb_generate_keys(tf_ssb_t* ssb);

View File

@ -20,7 +20,8 @@ static void _tf_ssb_import_add_app(tf_ssb_t* ssb, const char* user, const char*
sqlite3_stmt* statement;
JSContext* context = tf_ssb_get_context(ssb);
JSValue apps = JS_UNDEFINED;
if (sqlite3_prepare(tf_ssb_get_db(ssb), "SELECT value FROM properties WHERE id = $1 AND key = 'apps'", -1, &statement, NULL) == SQLITE_OK)
sqlite3* db = tf_ssb_acquire_db_writer(ssb);
if (sqlite3_prepare(db, "SELECT value FROM properties WHERE id = $1 AND key = 'apps'", -1, &statement, NULL) == SQLITE_OK)
{
if (sqlite3_bind_text(statement, 1, user, -1, NULL) == SQLITE_OK &&
sqlite3_step(statement) == SQLITE_ROW)
@ -68,7 +69,7 @@ static void _tf_ssb_import_add_app(tf_ssb_t* ssb, const char* user, const char*
JSValue json = JS_JSONStringify(context, out_apps, JS_NULL, JS_NULL);
const char* text = JS_ToCString(context, json);
if (sqlite3_prepare(tf_ssb_get_db(ssb), "INSERT OR REPLACE INTO properties (id, key, value) VALUES ($1, 'apps', $2)", -1, &statement, NULL) == SQLITE_OK)
if (sqlite3_prepare(db, "INSERT OR REPLACE INTO properties (id, key, value) VALUES ($1, 'apps', $2)", -1, &statement, NULL) == SQLITE_OK)
{
if (sqlite3_bind_text(statement, 1, user, -1, NULL) == SQLITE_OK &&
sqlite3_bind_text(statement, 2, text, -1, NULL) == SQLITE_OK &&
@ -77,6 +78,7 @@ static void _tf_ssb_import_add_app(tf_ssb_t* ssb, const char* user, const char*
}
sqlite3_finalize(statement);
}
tf_ssb_release_db_writer(ssb, db);
JS_FreeCString(context, text);
JS_FreeValue(context, json);
@ -160,19 +162,21 @@ static void _tf_ssb_import_recursive_add_files(tf_ssb_t* ssb, uv_loop_t* loop, J
static bool _tf_ssb_register_app(tf_ssb_t* ssb, const char* user, const char* app, const char* id)
{
bool result = false;
sqlite3_stmt* statement;
if (sqlite3_prepare(tf_ssb_get_db(ssb), "INSERT OR REPLACE INTO properties (id, key, value) VALUES ($1, 'path:' || $2, $3)", -1, &statement, NULL) == SQLITE_OK)
{
if (sqlite3_bind_text(statement, 1, user, -1, NULL) == SQLITE_OK &&
sqlite3_stmt* statement;
sqlite3* db = tf_ssb_acquire_db_writer(ssb);
if (sqlite3_prepare(db, "INSERT OR REPLACE INTO properties (id, key, value) VALUES ($1, 'path:' || $2, $3)", -1, &statement, NULL) == SQLITE_OK)
{
if (sqlite3_bind_text(statement, 1, user, -1, NULL) == SQLITE_OK &&
sqlite3_bind_text(statement, 2, app, -1, NULL) == SQLITE_OK &&
sqlite3_bind_text(statement, 3, id, -1, NULL) == SQLITE_OK &&
sqlite3_step(statement) == SQLITE_DONE)
{
result = sqlite3_changes(tf_ssb_get_db(ssb)) != 0;
}
sqlite3_finalize(statement);
}
return result;
{
result = sqlite3_changes(db) != 0;
}
sqlite3_finalize(statement);
}
tf_ssb_release_db_writer(ssb, db);
return result;
}
static void _tf_ssb_import_app_json(tf_ssb_t* ssb, uv_loop_t* loop, JSContext* context, const char* user, const char* path)
@ -206,7 +210,7 @@ static void _tf_ssb_import_app_json(tf_ssb_t* ssb, uv_loop_t* loop, JSContext* c
if (_tf_ssb_register_app(ssb, user, app, id))
{
tf_printf("Registered %s path:%s as %s.\n", user, app, id);
tf_printf("Registered %s path:%s as %s.\n", user, app, id);
_tf_ssb_import_add_app(ssb, user, app);
}
}
@ -229,7 +233,6 @@ static void _tf_ssb_import_app_json(tf_ssb_t* ssb, uv_loop_t* loop, JSContext* c
void tf_ssb_import(tf_ssb_t* ssb, const char* user, const char* path)
{
uv_fs_t req = { 0 };
sqlite3_busy_timeout(tf_ssb_get_db(ssb), 10000);
int r = uv_fs_scandir(tf_ssb_get_loop(ssb), &req, path, 0, NULL);
if (r >= 0)
{
@ -334,7 +337,7 @@ static void _tf_ssb_import_app_json_from_zip(tf_ssb_t* ssb, unzFile zip, JSConte
if (_tf_ssb_register_app(ssb, user, app, id))
{
tf_printf("Registered %s path:%s as %s.\n", user, app, id);
tf_printf("Registered %s path:%s as %s.\n", user, app, id);
_tf_ssb_import_add_app(ssb, user, app);
}
}

View File

@ -1091,7 +1091,7 @@ static JSValue _tf_ssb_private_message_encrypt(JSContext* context, JSValueConst
if (JS_IsUndefined(result))
{
tf_ssb_t* ssb = JS_GetOpaque(this_val, _tf_ssb_classId);
sqlite3* db = tf_ssb_get_db(ssb);
sqlite3* db = tf_ssb_acquire_db_reader(ssb);
uint8_t private_key[crypto_sign_SECRETKEYBYTES] = { 0 };
if (_tf_ssb_get_private_key_curve25519(db, signer_user, signer_identity, private_key))
@ -1167,6 +1167,7 @@ static JSValue _tf_ssb_private_message_encrypt(JSContext* context, JSValueConst
{
result = JS_ThrowInternalError(context, "Unable to get key for ID %s of user %s.", signer_identity, signer_user);
}
tf_ssb_release_db_reader(ssb, db);
}
JS_FreeCString(context, signer_user);
@ -1187,7 +1188,7 @@ static JSValue _tf_ssb_private_message_decrypt(JSContext* context, JSValueConst
uint8_t private_key[crypto_sign_SECRETKEYBYTES] = { 0 };
tf_ssb_t* ssb = JS_GetOpaque(this_val, _tf_ssb_classId);
sqlite3* db = tf_ssb_get_db(ssb);
sqlite3* db = tf_ssb_acquire_db_reader(ssb);
if (_tf_ssb_get_private_key_curve25519(db, user, identity, private_key))
{
uint8_t* decoded = tf_malloc(message_size);
@ -1238,6 +1239,7 @@ static JSValue _tf_ssb_private_message_decrypt(JSContext* context, JSValueConst
{
result = JS_ThrowInternalError(context, "Private key not found for user %s with id %s.", user, identity);
}
tf_ssb_release_db_reader(ssb, db);
JS_FreeCString(context, user);
JS_FreeCString(context, identity);

View File

@ -1,11 +1,13 @@
#include "ssb.rpc.h"
#include "log.h"
#include "mem.h"
#include "ssb.h"
#include "ssb.db.h"
#include "util.js.h"
#include "sqlite3.h"
#include "uv.h"
#include <inttypes.h>
#include <string.h>
@ -141,7 +143,7 @@ static void _tf_ssb_rpc_request_more_blobs(tf_ssb_connection_t* connection)
JSContext* context = tf_ssb_connection_get_context(connection);
tf_ssb_blob_wants_t* blob_wants = tf_ssb_connection_get_blob_wants_state(connection);
tf_ssb_t* ssb = tf_ssb_connection_get_ssb(connection);
sqlite3* db = tf_ssb_get_db(ssb);
sqlite3* db = tf_ssb_acquire_db_reader(ssb);
sqlite3_stmt* statement;
if (sqlite3_prepare(db, "SELECT id FROM blob_wants_view WHERE id > ? ORDER BY id LIMIT 32", -1, &statement, NULL) == SQLITE_OK)
{
@ -160,6 +162,7 @@ static void _tf_ssb_rpc_request_more_blobs(tf_ssb_connection_t* connection)
}
sqlite3_finalize(statement);
}
tf_ssb_release_db_reader(ssb, db);
}
static void _tf_ssb_rpc_blobs_createWants(tf_ssb_connection_t* connection, uint8_t flags, int32_t request_number, JSValue args, const uint8_t* message, size_t size, void* user_data)
@ -192,7 +195,7 @@ static bool _get_global_setting_bool(tf_ssb_t* ssb, const char* name, bool defau
{
bool result = default_value;
JSContext* context = tf_ssb_get_context(ssb);
sqlite3* db = tf_ssb_get_db(ssb);
sqlite3* db = tf_ssb_acquire_db_reader(ssb);
sqlite3_stmt* statement;
if (sqlite3_prepare(db, "SELECT value FROM properties WHERE id = 'core' AND key = 'settings'", -1, &statement, NULL) == SQLITE_OK)
{
@ -209,6 +212,7 @@ static bool _get_global_setting_bool(tf_ssb_t* ssb, const char* name, bool defau
}
sqlite3_finalize(statement);
}
tf_ssb_release_db_reader(ssb, db);
return result;
}
@ -216,7 +220,7 @@ static bool _get_global_setting_string(tf_ssb_t* ssb, const char* name, char* ou
{
bool result = false;
JSContext* context = tf_ssb_get_context(ssb);
sqlite3* db = tf_ssb_get_db(ssb);
sqlite3* db = tf_ssb_acquire_db_reader(ssb);
sqlite3_stmt* statement;
if (sqlite3_prepare(db, "SELECT value FROM properties WHERE id = 'core' AND key = 'settings'", -1, &statement, NULL) == SQLITE_OK)
{
@ -236,6 +240,7 @@ static bool _get_global_setting_string(tf_ssb_t* ssb, const char* name, char* ou
}
sqlite3_finalize(statement);
}
tf_ssb_release_db_reader(ssb, db);
return result;
}
@ -417,9 +422,31 @@ typedef struct _blobs_get_t
char id[k_blob_id_len];
size_t received;
size_t expected_size;
char buffer[];
bool done;
tf_ssb_t* ssb;
uv_work_t work;
uint8_t buffer[];
} blobs_get_t;
static void _tf_ssb_rpc_blob_store_work(uv_work_t* work)
{
blobs_get_t* get = work->data;
tf_ssb_db_blob_store(get->ssb, get->buffer, get->received, NULL, 0, NULL);
}
static void _tf_ssb_rpc_blob_store_after_work(uv_work_t* work, int status)
{
blobs_get_t* get = work->data;
if (status != 0)
{
tf_printf("uv_queue_work failed: %s\n", uv_strerror(status));
}
if (get->done)
{
tf_free(get);
}
}
static void _tf_ssb_rpc_connection_blobs_get_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)
{
tf_ssb_t* ssb = tf_ssb_connection_get_ssb(connection);
@ -435,8 +462,17 @@ static void _tf_ssb_rpc_connection_blobs_get_callback(tf_ssb_connection_t* conne
bool stored = false;
if (JS_ToBool(context, args))
{
char id[256];
stored = tf_ssb_db_blob_store(ssb, (uint8_t*)get->buffer, get->expected_size, id, sizeof(id), NULL);
get->work.data = get;
int r = uv_queue_work(tf_ssb_get_loop(ssb), &get->work, _tf_ssb_rpc_blob_store_work, _tf_ssb_rpc_blob_store_after_work);
if (r)
{
tf_printf("uv_queue_work failed: %s\n", uv_strerror(r));
get->work.data = NULL;
}
else
{
stored = true;
}
}
tf_ssb_connection_rpc_send(
connection,
@ -452,13 +488,18 @@ static void _tf_ssb_rpc_connection_blobs_get_callback(tf_ssb_connection_t* conne
static void _tf_ssb_rpc_connection_blobs_get_cleanup(tf_ssb_t* ssb, void* user_data)
{
tf_free(user_data);
blobs_get_t* get = user_data;
get->done = true;
if (!get->work.data)
{
tf_free(get);
}
}
static void _tf_ssb_rpc_connection_blobs_get(tf_ssb_connection_t* connection, const char* blob_id, size_t size)
{
blobs_get_t* get = tf_malloc(sizeof(blobs_get_t) + size);
*get = (blobs_get_t) { .expected_size = size };
*get = (blobs_get_t) { .ssb = tf_ssb_connection_get_ssb(connection), .expected_size = size };
snprintf(get->id, sizeof(get->id), "%s", blob_id);
memset(get->buffer, 0, size);
@ -698,7 +739,7 @@ static void _tf_ssb_connection_send_history_stream_internal(tf_ssb_connection_t*
{
tf_ssb_t* ssb = tf_ssb_connection_get_ssb(connection);
JSContext* context = tf_ssb_get_context(ssb);
sqlite3* db = tf_ssb_get_db(ssb);
sqlite3* db = tf_ssb_acquire_db_reader(ssb);
sqlite3_stmt* statement;
const int k_max = 32;
int64_t max_sequence_seen = 0;
@ -740,6 +781,7 @@ static void _tf_ssb_connection_send_history_stream_internal(tf_ssb_connection_t*
}
sqlite3_finalize(statement);
}
tf_ssb_release_db_reader(ssb, db);
if (max_sequence_seen == sequence + k_max - 1)
{

View File

@ -127,9 +127,9 @@ void tf_ssb_test_ssb(const tf_test_options_t* options)
uv_loop_t loop = { 0 };
uv_loop_init(&loop);
tf_ssb_t* ssb0 = tf_ssb_create(&loop, NULL, ":memory:");
tf_ssb_t* ssb0 = tf_ssb_create(&loop, NULL, "file:db0?mode=memory&cache=shared");
tf_ssb_register(tf_ssb_get_context(ssb0), ssb0);
tf_ssb_t* ssb1 = tf_ssb_create(&loop, NULL, ":memory:");
tf_ssb_t* ssb1 = tf_ssb_create(&loop, NULL, "file:db1?mode=memory&cache=shared");
tf_ssb_register(tf_ssb_get_context(ssb1), ssb1);
uv_idle_t idle0 = { .data = ssb0 };
@ -311,11 +311,11 @@ void tf_ssb_test_rooms(const tf_test_options_t* options)
uv_loop_t loop = { 0 };
uv_loop_init(&loop);
tf_ssb_t* ssb0 = tf_ssb_create(&loop, NULL, ":memory:");
tf_ssb_t* ssb0 = tf_ssb_create(&loop, NULL, "file:db0?mode=memory&cache=shared");
tf_ssb_register(tf_ssb_get_context(ssb0), ssb0);
tf_ssb_t* ssb1 = tf_ssb_create(&loop, NULL, ":memory:");
tf_ssb_t* ssb1 = tf_ssb_create(&loop, NULL, "file:db1?mode=memory&cache=shared");
tf_ssb_register(tf_ssb_get_context(ssb1), ssb1);
tf_ssb_t* ssb2 = tf_ssb_create(&loop, NULL, ":memory:");
tf_ssb_t* ssb2 = tf_ssb_create(&loop, NULL, "file:db2?mode=memory&cache=shared");
tf_ssb_register(tf_ssb_get_context(ssb2), ssb2);
uv_idle_t idle0 = { .data = ssb0 };
@ -555,7 +555,7 @@ void tf_ssb_test_bench(const tf_test_options_t* options)
uv_loop_t loop = { 0 };
uv_loop_init(&loop);
tf_ssb_t* ssb0 = tf_ssb_create(&loop, NULL, ":memory:");
tf_ssb_t* ssb0 = tf_ssb_create(&loop, NULL, "file:db0?mode=memory&cache=shared");
tf_ssb_generate_keys(ssb0);
char id0[k_id_base64_len] = { 0 };
@ -579,7 +579,7 @@ void tf_ssb_test_bench(const tf_test_options_t* options)
clock_gettime(CLOCK_REALTIME, &end_time);
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_t* ssb1 = tf_ssb_create(&loop, NULL, "file:db1?mode=memory&cache=shared");
tf_ssb_generate_keys(ssb1);
uint8_t id0bin[k_id_bin_len];
tf_ssb_id_str_to_bin(id0bin, id0);

View File

@ -14,6 +14,7 @@
#include "tlscontext.js.h"
#include "trace.h"
#include "util.js.h"
#include "version.h"
#include "backtrace.h"
#include "quickjs.h"
@ -35,8 +36,6 @@
#define _countof(a) ((int)(sizeof((a)) / sizeof(*(a))))
#endif
static const char* k_version = "1.0";
static JSClassID _import_class_id;
static int _count;
@ -675,9 +674,11 @@ static JSValue _tf_task_version(JSContext* context, JSValueConst this_val, int a
{
tf_task_t* task = JS_GetContextOpaque(context);
tf_trace_begin(task->_trace, __func__);
JSValue result = JS_NewString(task->_context, k_version);
JSValue version = JS_NewObject(context);
JS_SetPropertyStr(context, version, "number", JS_NewString(context, VERSION_NUMBER));
JS_SetPropertyStr(context, version, "name", JS_NewString(context, VERSION_NAME));
tf_trace_end(task->_trace);
return result;
return version;
}
exportid_t tf_task_export_function(tf_task_t* task, tf_taskstub_t* to, JSValue function)

View File

@ -236,7 +236,7 @@ static void _test_database(const tf_test_options_t* options)
unlink("out/testdb.sqlite");
char command[256];
snprintf(command, sizeof(command), "%s run --ssb-port=0 --db-path=:memory: --db-path=out/testdb.sqlite -s out/test.js", options->exe_path);
snprintf(command, sizeof(command), "%s run --ssb-port=0 --db-path=file:db?mode=memory\\&cache=shared -s out/test.js", options->exe_path);
tf_printf("%s\n", command);
int result = system(command);
tf_printf("returned %d\n", WEXITSTATUS(result));

3
src/version.h Normal file
View File

@ -0,0 +1,3 @@
#define VERSION_NUMBER "0.0.8"
#define VERSION_NAME "The secret ingredient is love."

View File

@ -1,10 +1,10 @@
#!/bin/bash
#!/bin/bash -e
VERSION=$1
NICKNAME=$2
rm -rfv tildefriends-$VERSION
svn export . tildefriends-$VERSION
echo "tildefriends-$VERSION: $NICKNAME" > tildefriends-$VERSION/VERSION
VERSION_NUMBER=`sed -n -e 's/^VERSION_NUMBER := //p' Makefile`
VERSION_NAME=`sed -n -e 's/^VERSION_NAME := //p' Makefile`
rm -rfv tildefriends-$VERSION_NUMBER
svn export . tildefriends-$VERSION_NUMBER
echo "tildefriends-$VERSION_NUMBER: $VERSION_NAME" > tildefriends-$VERSION_NUMBER/VERSION
tar \
--exclude=deps/libbacktrace/Isaac.Newton-Opticks.txt \
--exclude=deps/libsodium/builds \
@ -17,5 +17,7 @@ tar \
--exclude=deps/sqlite/shell.c \
--exclude=deps/zlib/contrib/vstudio \
--exclude=deps/zlib/doc \
-caf tildefriends-$VERSION.tar.xz tildefriends-$VERSION
rm -rfv tildefriends-$VERSION
-caf tildefriends-$VERSION_NUMBER.tar.xz tildefriends-$VERSION_NUMBER
rm -rfv tildefriends-$VERSION_NUMBER
make apk
cp out/TildeFriends-release.apk TildeFriends-$VERSION_NUMBER.apk

View File

@ -1,4 +1,5 @@
wget https://cdn.jsdelivr.net/gh/lit/dist@2.7.4/all/lit-all.min.js -O deps/lit/lit-all.min.js
wget https://cdn.jsdelivr.net/gh/lit/dist@2.7.4/all/lit-all.min.js.map -O deps/lit/lit-all.min.js.map
VERSION=2.7.5
wget https://cdn.jsdelivr.net/gh/lit/dist@$VERSION/all/lit-all.min.js -O deps/lit/lit-all.min.js
wget https://cdn.jsdelivr.net/gh/lit/dist@$VERSION/all/lit-all.min.js.map -O deps/lit/lit-all.min.js.map
cp -fv deps/lit/* apps/ssb/
cp -fv deps/lit/* apps/sneaker/