Compare commits

..

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 += --warn-undefined-variables
MAKEFLAGS += --no-builtin-rules MAKEFLAGS += --no-builtin-rules
VERSION_CODE := 8
VERSION_NUMBER := 0.0.8
VERSION_NAME := The secret ingredient is love.
PROJECT = tildefriends PROJECT = tildefriends
BUILD_DIR ?= out BUILD_DIR ?= out
BUILD_TYPES := debug release windebug winrelease androiddebug androidrelease androiddebug-x86_64 androidrelease-x86_64 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)))) $(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. # Android support.
out/res/layout_activity_main.xml.flat: src/android/res/layout/activity_main.xml out/res/layout_activity_main.xml.flat: src/android/res/layout/activity_main.xml
@mkdir -p $(dir $@) @mkdir -p $(dir $@)
@ -491,10 +507,10 @@ out/%.apk: out/apk/%.unsigned.apk
@echo [apksigner] $(notdir $@) @echo [apksigner] $(notdir $@)
@$(ANDROID_BUILD_TOOLS)/apksigner sign --ks keystore.jks --ks-key-alias androidKey --ks-pass pass:android --key-pass pass:android --out $@ $< @$(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 .PHONY: apk
apkgo: out/TildeFriends-debug.apk apkgo: out/TildeFriends-release.apk
@adb install $< @adb install $<
@adb shell am start com.unprompted.tildefriends/.MainActivity @adb shell am start com.unprompted.tildefriends/.MainActivity
.PHONY: apkgo .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_compose from './tf-compose.js';
import * as tf_news from './tf-news.js'; import * as tf_news from './tf-news.js';
import * as tf_profile from './tf-profile.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 from './tf-tab-news.js';
import * as tf_tab_news_feed from './tf-tab-news-feed.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_search from './tf-tab-search.js';
import * as tf_tab_connections from './tf-tab-connections.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}, following: {type: Array},
users: {type: Object}, users: {type: Object},
ids: {type: Array}, ids: {type: Array},
tags: {type: Array},
}; };
} }
@ -32,6 +33,7 @@ class TfElement extends LitElement {
this.following = []; this.following = [];
this.users = {}; this.users = {};
this.loaded = false; this.loaded = false;
this.tags = [];
tfrpc.rpc.getBroadcasts().then(b => { self.broadcasts = b || []; }); tfrpc.rpc.getBroadcasts().then(b => { self.broadcasts = b || []; });
tfrpc.rpc.getConnections().then(c => { self.connections = c || []; }); tfrpc.rpc.getConnections().then(c => { self.connections = c || []; });
tfrpc.rpc.getHash().then(hash => self.set_hash(hash)); tfrpc.rpc.getHash().then(hash => self.set_hash(hash));
@ -64,6 +66,8 @@ class TfElement extends LitElement {
this.tab = 'search'; this.tab = 'search';
} else if (this.hash === '#connections') { } else if (this.hash === '#connections') {
this.tab = 'connections'; this.tab = 'connections';
} else if (this.hash === '#mentions') {
this.tab = 'mentions';
} else { } else {
this.tab = 'news'; 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() { async load() {
let whoami = this.whoami; let whoami = this.whoami;
let tags = this.load_recent_tags();
let [following, users] = await this.following_deep([whoami], 2, {}); let [following, users] = await this.following_deep([whoami], 2, {});
users = await this.fetch_about(following.sort(), users); users = await this.fetch_about(following.sort(), users);
this.following = following; this.following = following;
this.users = users; this.users = users;
await tags;
console.log(`load finished ${whoami} => ${this.whoami}`); console.log(`load finished ${whoami} => ${this.whoami}`);
this.whoami = whoami; this.whoami = whoami;
this.loaded = whoami; this.loaded = whoami;
@ -273,6 +289,10 @@ class TfElement extends LitElement {
return html` return html`
<tf-tab-connections .users=${this.users} .connections=${this.connections} .broadcasts=${this.broadcasts}></tf-tab-connections> <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') { } else if (this.tab === 'search') {
return html` 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> <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('#'); await tfrpc.rpc.setHash('#');
} else if (tab === 'connections') { } else if (tab === 'connections') {
await tfrpc.rpc.setHash('#connections'); await tfrpc.rpc.setHash('#connections');
} else if (tab === 'mentions') {
await tfrpc.rpc.setHash('#mentions');
} }
} }
@ -304,6 +326,7 @@ class TfElement extends LitElement {
<div> <div>
<input type="button" class="tab" value="News" ?disabled=${self.tab == 'news'} @click=${() => self.set_tab('news')}></input> <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="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> <input type="button" class="tab" value="Search" ?disabled=${self.tab == 'search'} @click=${() => self.set_tab('search')}></input>
</div> </div>
`; `;
@ -316,6 +339,7 @@ class TfElement extends LitElement {
return html` return html`
${this.render_id_picker()} ${this.render_id_picker()}
${tabs} ${tabs}
${this.tags.map(x => html`<tf-tag tag=${x.tag} count=${x.count}></tf-tag>`)}
${contents} ${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) { async try_decrypt(content) {
let result = await tfrpc.rpc.try_decrypt(this.whoami, content); let result = await tfrpc.rpc.try_decrypt(this.whoami, content);
if (result) { if (result) {
@ -330,6 +350,7 @@ class TfMessageElement extends LitElement {
`; `;
let content_html = let content_html =
html` html`
${this.render_channels()}
<div @click=${this.body_click}>${body}</div> <div @click=${this.body_click}>${body}</div>
${this.render_mentions()} ${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}, users: {type: Object},
following: {type: Array}, following: {type: Array},
query: {type: String}, query: {type: String},
expanded: {type: Object},
}; };
} }
@ -20,6 +21,7 @@ class TfTabSearchElement extends LitElement {
this.whoami = null; this.whoami = null;
this.users = {}; this.users = {};
this.following = []; this.following = [];
this.expanded = {};
} }
async search(query) { 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() { render() {
if (this.query !== this.last_query) { if (this.query !== this.last_query) {
this.last_query = this.query;
this.search(this.query); this.search(this.query);
} }
let self = this; 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="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> <input type="button" value="Search" @click=${(event) => self.search(self.renderRoot.getElementById('search').value)}></input>
</div> </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}, show_permissions: {type: Boolean},
status: {type: Object}, status: {type: Object},
spark_lines: {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) { get_spark_line(key, options) {
if (!this.spark_lines[key]) { if (!this.spark_lines[key]) {
let spark_line = document.createElement('tf-sparkline'); 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; spark_line.title = key;
if (options) { if (options) {
if (options.max) { if (options.max) {
@ -125,7 +130,8 @@ class TfNavigationElement extends LitElement {
${k_global_style} ${k_global_style}
</style> </style>
<div style="margin: 4px; display: flex; flex-direction: row; flex-wrap: nowrap; gap: 3px"> <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="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="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="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 style="display: inline-block; vertical-align: top; white-space: pre; color: ${this.status.color ?? kErrorColor}">${this.status.message}</span>
<span id="requests"></span> <span id="requests"></span>
${this.render_permissions()} ${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> <span style="flex: 0 0; white-space: nowrap">${this.render_login()}</span>
</div> </div>
`; `;
@ -315,7 +321,7 @@ class TfSparkLineElement extends LitElement {
render() { render() {
let max = Math.round(10.0 * Math.max(...this.lines.map(line => line.values[line.values.length - 1]))) / 10.0; let max = Math.round(10.0 * Math.max(...this.lines.map(line => line.values[line.values.length - 1]))) / 10.0;
return html` 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))} ${this.lines.map(x => this.render_line(x))}
<text x="0" y="1em" style="font: 8px sans-serif; fill: #fff">${max}</text> <text x="0" y="1em" style="font: 8px sans-serif; fill: #fff">${max}</text>
</svg> </svg>
@ -765,6 +771,7 @@ function _receive_websocket_message(message) {
if (window.location.hash) { if (window.location.hash) {
send({event: "hashChange", hash: window.location.hash}); send({event: "hashChange", hash: window.location.hash});
} }
document.getElementsByTagName('tf-navigation')[0].version = message.version;
send({action: 'enableStats', enabled: true}); send({action: 'enableStats', enabled: true});
} else if (message && message.action == "ping") { } else if (message && message.action == "ping") {
send({action: "pong"}); send({action: "pong"});

View File

@ -456,7 +456,7 @@ async function getProcessBlob(blobId, key, options) {
} }
broadcastEvent('onSessionBegin', [getUser(process, process)]); broadcastEvent('onSessionBegin', [getUser(process, process)]);
if (process.app) { if (process.app) {
process.app.send({action: "ready"}); process.app.send({action: "ready", version: version()});
process.sendPermissions(); process.sendPermissions();
} }
await process.task.execute({name: appSourceName, source: appSource}); 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"?> <?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android" <manifest xmlns:android="http://schemas.android.com/apk/res/android"
package="com.unprompted.tildefriends" package="com.unprompted.tildefriends"
versionCode="1" versionCode="8"
versionName="0.0.4"> versionName="0.0.8">
<uses-sdk android:minSdkVersion="16"/> <uses-sdk android:minSdkVersion="26"/>
<uses-permission android:name="android.permission.INTERNET"/> <uses-permission android:name="android.permission.INTERNET"/>
<application android:label="Tilde Friends" android:usesCleartextTraffic="true" android:debuggable="true"> <application android:label="Tilde Friends" android:usesCleartextTraffic="true" android:debuggable="true">
<meta-data android:name="android.max_aspect" android:value="2.1"/> <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 { public class MainActivity extends Activity {
WebView web_view; WebView web_view;
String base_url; String base_url;
Process process;
@Override @Override
protected void onCreate(Bundle savedInstanceState) { protected void onCreate(Bundle savedInstanceState) {
@ -81,6 +82,8 @@ public class MainActivity extends Activity {
String port_file_path = getFilesDir().toString() + "/port.txt"; String port_file_path = getFilesDir().toString() + "/port.txt";
base_url = "http://127.0.0.1:12345/"; base_url = "http://127.0.0.1:12345/";
MainActivity activity = this;
new Thread(new Runnable() { new Thread(new Runnable() {
@Override @Override
public void run() { public void run() {
@ -97,7 +100,9 @@ public class MainActivity extends Activity {
if (event.context().toString().equals("port.txt")) { if (event.context().toString().equals("port.txt")) {
Log.w("tildefriends", "Observed file write: " + event.context().toString()); Log.w("tildefriends", "Observed file write: " + event.context().toString());
base_url = "http://127.0.0.1:" + String.valueOf(read_port(port_file_path)) + "/"; 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(); watcher.close();
break; break;
} }
@ -122,15 +127,11 @@ public class MainActivity extends Activity {
builder.directory(getFilesDir()); builder.directory(getFilesDir());
builder.inheritIO(); builder.inheritIO();
try { try {
builder.start(); process = builder.start();
} catch (java.io.IOException e) { } catch (java.io.IOException e) {
Log.w("tildefriends", "IOException starting process: " + e.toString()); 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().setJavaScriptEnabled(true);
web_view.getSettings().setDatabaseEnabled(true); web_view.getSettings().setDatabaseEnabled(true);
web_view.getSettings().setDomStorageEnabled(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 @Override
protected void onSaveInstanceState(Bundle outState) protected void onSaveInstanceState(Bundle outState)
{ {

View File

@ -19,7 +19,6 @@ typedef struct _database_t
JSContext* context; JSContext* context;
JSValue object; JSValue object;
void* task; void* task;
sqlite3* db;
const char* id; const char* id;
} database_t; } database_t;
@ -62,16 +61,12 @@ static JSValue _database_create(JSContext* context, JSValueConst this_val, int a
++_database_count; ++_database_count;
JSValue object = JS_NewObjectClass(context, _database_class_id); 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_t* database = tf_malloc(sizeof(database_t));
*database = (database_t) *database = (database_t)
{ {
.task = JS_GetContextOpaque(context), .task = JS_GetContextOpaque(context),
.context = context, .context = context,
.object = object, .object = object,
.db = db,
}; };
const char* id = JS_ToCString(context, argv[0]); const char* id = JS_ToCString(context, argv[0]);
database->id = tf_strdup(id); 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); database_t* database = JS_GetOpaque(this_val, _database_class_id);
if (database) if (database)
{ {
tf_ssb_t* ssb = tf_task_get_ssb(database->task);
sqlite3_stmt* statement; 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; size_t length;
const char* keyString = JS_ToCStringLen(context, &length, argv[0]); 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); JS_FreeCString(context, keyString);
sqlite3_finalize(statement); sqlite3_finalize(statement);
} }
tf_ssb_release_db_reader(ssb, db);
} }
return entry; return entry;
} }
@ -129,7 +127,9 @@ JSValue _database_set(JSContext* context, JSValueConst this_val, int argc, JSVal
if (database) if (database)
{ {
sqlite3_stmt* statement; 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; size_t keyLength;
const char* keyString = JS_ToCStringLen(context, &keyLength, argv[0]); 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); JS_FreeCString(context, valueString);
sqlite3_finalize(statement); sqlite3_finalize(statement);
} }
tf_ssb_release_db_writer(ssb, db);
} }
return JS_UNDEFINED; return JS_UNDEFINED;
} }
@ -156,9 +157,11 @@ static JSValue _database_exchange(JSContext* context, JSValueConst this_val, int
if (database) if (database)
{ {
sqlite3_stmt* statement; 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 (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 key_length;
size_t set_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_bind_text(statement, 3, set, set_length, NULL) == SQLITE_OK &&
sqlite3_step(statement) == SQLITE_DONE) 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, key);
JS_FreeCString(context, set); JS_FreeCString(context, set);
sqlite3_finalize(statement); 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 key_length;
size_t expected_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_bind_text(statement, 4, expected, expected_length, NULL) == SQLITE_OK &&
sqlite3_step(statement) == SQLITE_DONE) 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, key);
JS_FreeCString(context, expected); JS_FreeCString(context, expected);
JS_FreeCString(context, set); JS_FreeCString(context, set);
sqlite3_finalize(statement); sqlite3_finalize(statement);
} }
tf_ssb_release_db_writer(ssb, db);
} }
return exchanged; return exchanged;
} }
@ -207,7 +211,9 @@ JSValue _database_remove(JSContext* context, JSValueConst this_val, int argc, JS
if (database) if (database)
{ {
sqlite3_stmt* statement; 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; size_t keyLength;
const char* keyString = JS_ToCStringLen(context, &keyLength, argv[0]); 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); JS_FreeCString(context, keyString);
sqlite3_finalize(statement); sqlite3_finalize(statement);
} }
tf_ssb_release_db_writer(ssb, db);
} }
return JS_UNDEFINED; return JS_UNDEFINED;
} }
@ -230,7 +237,9 @@ JSValue _database_get_all(JSContext* context, JSValueConst this_val, int argc, J
if (database) if (database)
{ {
sqlite3_stmt* statement; 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) 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); sqlite3_finalize(statement);
} }
tf_ssb_release_db_reader(ssb, db);
} }
return array; return array;
} }
@ -254,7 +264,9 @@ JSValue _database_get_like(JSContext* context, JSValueConst this_val, int argc,
if (database) if (database)
{ {
sqlite3_stmt* statement; 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]); const char* pattern = JS_ToCString(context, argv[0]);
if (sqlite3_bind_text(statement, 1, database->id, -1, NULL) == SQLITE_OK && 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); JS_FreeCString(context, pattern);
sqlite3_finalize(statement); sqlite3_finalize(statement);
} }
tf_ssb_release_db_reader(ssb, db);
} }
return result; 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) 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); 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; JSValue array = JS_UNDEFINED;
sqlite3_stmt* statement; sqlite3_stmt* statement;
if (sqlite3_prepare(db, "SELECT DISTINCT id FROM properties WHERE id LIKE ?", -1, &statement, NULL) == SQLITE_OK) 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); JS_FreeCString(context, pattern);
sqlite3_finalize(statement); sqlite3_finalize(statement);
} }
tf_ssb_release_db_reader(ssb, db);
return array; return array;
} }

View File

@ -180,9 +180,10 @@ typedef struct _tf_ssb_t
tf_trace_t* trace; tf_trace_t* trace;
const char* db_path; const char* db_path;
sqlite3* db;
uv_mutex_t db_readers_lock; uv_mutex_t db_readers_lock;
uv_mutex_t db_writer_lock;
sqlite3* db_writer;
sqlite3** db_readers; sqlite3** db_readers;
int db_readers_count; 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) 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)
{ {
if (request->cleanup) 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_readers_lock);
uv_mutex_init(&ssb->db_writer_lock);
JS_NewClassID(&_connection_class_id); JS_NewClassID(&_connection_class_id);
JSClassDef def = 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); JS_NewClass(JS_GetRuntime(ssb->context), _connection_class_id, &def);
ssb->db_path = tf_strdup(db_path); 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); tf_ssb_db_init(ssb);
if (loop) if (loop)
@ -2140,11 +2144,6 @@ tf_ssb_t* tf_ssb_create(uv_loop_t* loop, JSContext* context, const char* db_path
return ssb; 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* tf_ssb_acquire_db_reader(tf_ssb_t* ssb)
{ {
sqlite3* db = NULL; sqlite3* db = NULL;
@ -2155,7 +2154,7 @@ sqlite3* tf_ssb_acquire_db_reader(tf_ssb_t* ssb)
} }
else 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_ssb_db_init_reader(db);
tf_trace_sqlite(ssb->trace, 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); 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) uv_loop_t* tf_ssb_get_loop(tf_ssb_t* ssb)
{ {
return ssb->loop; 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) void tf_ssb_set_trace(tf_ssb_t* ssb, tf_trace_t* trace)
{ {
ssb->trace = 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) 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_FreeContext(ssb->context);
JS_FreeRuntime(ssb->runtime); JS_FreeRuntime(ssb->runtime);
} }
sqlite3_close(ssb->db); sqlite3_close(ssb->db_writer);
while (ssb->broadcasts) while (ssb->broadcasts)
{ {
tf_ssb_broadcast_t* broadcast = 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); tf_free(ssb->db_readers);
uv_mutex_destroy(&ssb->db_readers_lock); uv_mutex_destroy(&ssb->db_readers_lock);
uv_mutex_destroy(&ssb->db_writer_lock);
tf_free((void*)ssb->db_path); tf_free((void*)ssb->db_path);
tf_free(ssb); 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; struct sockaddr server_addr;
int len = (int)sizeof(server_addr); int len = (int)sizeof(server_addr);
if (uv_tcp_getsockname(&ssb->server, &server_addr, &len) != 0 || int r = uv_tcp_getsockname(&ssb->server, &server_addr, &len);
server_addr.sa_family != AF_INET) 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]; 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 = broadcast_addr.sin_addr.s_addr =
(address->sin_addr.s_addr & netmask->sin_addr.s_addr) | (address->sin_addr.s_addr & netmask->sin_addr.s_addr) |
(INADDR_BROADCAST & ~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) if (r < 0)
{ {
char broadcast_str[256] = { 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) 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 = tf_ssb_connection_message_request_t* message_request =
bsearch( bsearch(
author_string, author_string,

View File

@ -16,7 +16,6 @@
typedef struct _tf_ssb_connections_t typedef struct _tf_ssb_connections_t
{ {
tf_ssb_t* ssb; tf_ssb_t* ssb;
sqlite3* db;
uv_timer_t timer; uv_timer_t timer;
} tf_ssb_connections_t; } tf_ssb_connections_t;
@ -55,7 +54,8 @@ static bool _tf_ssb_connections_get_next_connection(tf_ssb_connections_t* connec
{ {
bool result = false; bool result = false;
sqlite3_stmt* statement; 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 && if (sqlite3_bind_int(statement, 1, 60000) == SQLITE_OK &&
sqlite3_step(statement) == SQLITE_ROW) sqlite3_step(statement) == SQLITE_ROW)
@ -69,8 +69,9 @@ static bool _tf_ssb_connections_get_next_connection(tf_ssb_connections_t* connec
} }
else 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; 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)); tf_ssb_connections_t* connections = tf_malloc(sizeof(tf_ssb_connections_t));
memset(connections, 0, sizeof(*connections)); memset(connections, 0, sizeof(*connections));
connections->ssb = ssb; connections->ssb = ssb;
connections->db = tf_ssb_get_db(ssb);
tf_ssb_add_connections_changed_callback(ssb, _tf_ssb_connections_changed_callback, NULL, connections); 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) void tf_ssb_connections_store(tf_ssb_connections_t* connections, const char* host, int port, const char* key)
{ {
sqlite3_stmt* statement; 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 && if (sqlite3_bind_text(statement, 1, host, -1, NULL) == SQLITE_OK &&
sqlite3_bind_int(statement, 2, port) == 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); int r = sqlite3_step(statement);
if (r != SQLITE_DONE) 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); 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) void tf_ssb_connections_set_attempted(tf_ssb_connections_t* connections, const char* host, int port, const char* key)
{ {
sqlite3_stmt* statement; 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 && if (sqlite3_bind_text(statement, 1, host, -1, NULL) == SQLITE_OK &&
sqlite3_bind_int(statement, 2, port) == 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) 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); 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) void tf_ssb_connections_set_succeeded(tf_ssb_connections_t* connections, const char* host, int port, const char* key)
{ {
sqlite3_stmt* statement; 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 && if (sqlite3_bind_text(statement, 1, host, -1, NULL) == SQLITE_OK &&
sqlite3_bind_int(statement, 2, port) == 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) 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); 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) 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_init_internal(db);
_tf_ssb_db_exec(db, _tf_ssb_db_exec(db,
"CREATE TABLE IF NOT EXISTS messages (" "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_printf("Adding sequence_before_author column.\n");
_tf_ssb_db_exec(db, "ALTER TABLE messages ADD COLUMN sequence_before_author INTEGER"); _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) 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 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; bool stored = false;
JSValue previousval = JS_GetPropertyStr(context, val, "previous"); JSValue previousval = JS_GetPropertyStr(context, val, "previous");
const char* previous = JS_IsNull(previousval) ? NULL : JS_ToCString(context, previousval); const char* previous = JS_IsNull(previousval) ? NULL : JS_ToCString(context, previousval);
JS_FreeValue(context, previousval);
JSValue authorval = JS_GetPropertyStr(context, val, "author"); JSValue authorval = JS_GetPropertyStr(context, val, "author");
const char* author = JS_ToCString(context, authorval); const char* author = JS_ToCString(context, authorval);
JS_FreeValue(context, authorval);
int64_t sequence = -1; int64_t sequence = -1;
JSValue sequenceval = JS_GetPropertyStr(context, val, "sequence"); JSValue sequenceval = JS_GetPropertyStr(context, val, "sequence");
JS_ToInt64(context, &sequence, sequenceval); JS_ToInt64(context, &sequence, sequenceval);
JS_FreeValue(context, 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"); sqlite3* db = tf_ssb_acquire_db_writer(ssb);
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_stmt* statement; sqlite3_stmt* statement;
int64_t last_row_id = -1; int64_t last_row_id = -1;
if (_tf_ssb_db_previous_message_exists(db, author, sequence, previous)) 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"; 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) 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 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 else
{ {
tf_printf("Previous message doesn't exist.\n"); tf_printf("Previous message doesn't exist.\n");
} }
if (last_row_id != -1) 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"; 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_prepare(db, query, -1, &statement, NULL) == SQLITE_OK)
{ {
if (sqlite3_bind_int64(statement, 1, last_row_id) == 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) sqlite3_bind_int(statement, 2, k_blob_id_len - 1) == SQLITE_OK)
{ {
int r = SQLITE_OK; int r = SQLITE_OK;
while ((r = sqlite3_step(statement)) == SQLITE_ROW) while ((r = sqlite3_step(statement)) == SQLITE_ROW)
{ {
tf_ssb_notify_blob_want_added(ssb, (const char*)sqlite3_column_text(statement, 0)); tf_ssb_notify_blob_want_added(ssb, (const char*)sqlite3_column_text(statement, 0));
} }
if (r != SQLITE_DONE) if (r != SQLITE_DONE)
{ {
tf_printf("%s\n", sqlite3_errmsg(db)); tf_printf("%s\n", sqlite3_errmsg(db));
} }
} }
sqlite3_finalize(statement); sqlite3_finalize(statement);
} }
else 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);
JS_FreeValue(context, previousval);
JS_FreeCString(context, author); JS_FreeCString(context, author);
JS_FreeValue(context, authorval);
JS_FreeCString(context, previous); JS_FreeCString(context, previous);
JS_FreeCString(context, contentstr);
JS_FreeValue(context, content);
return stored; 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; bool result = false;
sqlite3_stmt* statement; sqlite3_stmt* statement;
sqlite3* db = tf_ssb_acquire_db_reader(ssb);
const char* query = "SELECT content FROM messages WHERE id = ?"; 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 && if (sqlite3_bind_text(statement, 1, id, -1, NULL) == SQLITE_OK &&
sqlite3_step(statement) == SQLITE_ROW) 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); sqlite3_finalize(statement);
} }
tf_ssb_release_db_reader(ssb, db);
return result; return result;
} }
@ -386,8 +394,9 @@ bool tf_ssb_db_blob_has(tf_ssb_t* ssb, const char* id)
{ {
bool result = false; bool result = false;
sqlite3_stmt* statement; sqlite3_stmt* statement;
sqlite3* db = tf_ssb_acquire_db_reader(ssb);
const char* query = "SELECT COUNT(*) FROM blobs WHERE id = $1"; 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 && if (sqlite3_bind_text(statement, 1, id, -1, NULL) == SQLITE_OK &&
sqlite3_step(statement) == SQLITE_ROW) 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); sqlite3_finalize(statement);
} }
tf_ssb_release_db_reader(ssb, db);
return result; 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; bool result = false;
sqlite3_stmt* statement; sqlite3_stmt* statement;
sqlite3* db = tf_ssb_acquire_db_reader(ssb);
const char* query = "SELECT content FROM blobs WHERE id = $1"; 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 && if (sqlite3_bind_text(statement, 1, id, -1, NULL) == SQLITE_OK &&
sqlite3_step(statement) == SQLITE_ROW) 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); sqlite3_finalize(statement);
} }
tf_ssb_release_db_reader(ssb, db);
return result; 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 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; bool result = false;
sqlite3* db = tf_ssb_get_db(ssb); sqlite3* db = tf_ssb_acquire_db_writer(ssb);
sqlite3_stmt* statement; sqlite3_stmt* statement;
uint8_t hash[crypto_hash_sha256_BYTES]; 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 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) 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; bool found = false;
sqlite3_stmt* statement; sqlite3_stmt* statement;
const char* query = "SELECT id, timestamp, content FROM messages WHERE author = $1 AND sequence = $2"; 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 && if (sqlite3_bind_text(statement, 1, author, -1, NULL) == SQLITE_OK &&
sqlite3_bind_int64(statement, 2, sequence) == 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 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; 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; bool found = false;
sqlite3_stmt* statement; 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)"; 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 && if (sqlite3_bind_text(statement, 1, author, -1, NULL) == SQLITE_OK &&
sqlite3_step(statement) == SQLITE_ROW) 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 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; 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 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; 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); JSContext* context = tf_ssb_get_context(ssb);
sqlite3_stmt* statement; sqlite3_stmt* statement;
sqlite3_set_authorizer(db, _tf_ssb_sqlite_authorizer, ssb); 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); result = JS_ThrowInternalError(context, "SQL Error %s: preparing \"%s\".", sqlite3_errmsg(db), query);
} }
sqlite3_set_authorizer(db, NULL, NULL); sqlite3_set_authorizer(db, NULL, NULL);
tf_ssb_release_db_reader(ssb, db);
return result; 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 tf_ssb_db_identity_get_count_for_user(tf_ssb_t* ssb, const char* user)
{ {
int count = 0; int count = 0;
sqlite3* db = tf_ssb_get_db(ssb); sqlite3* db = tf_ssb_acquire_db_reader(ssb);
sqlite3_stmt* statement = NULL; sqlite3_stmt* statement = NULL;
if (sqlite3_prepare(db, "SELECT COUNT(*) FROM identities WHERE user = ?", -1, &statement, NULL) == SQLITE_OK) 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); sqlite3_finalize(statement);
} }
tf_ssb_release_db_reader(ssb, db);
return count; 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 tf_ssb_db_identity_add(tf_ssb_t* ssb, const char* user, const char* public_key, const char* private_key)
{ {
bool added = false; bool added = false;
sqlite3* db = tf_ssb_get_db(ssb); sqlite3* db = tf_ssb_acquire_db_writer(ssb);
sqlite3_stmt* statement = NULL; 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) 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); sqlite3_finalize(statement);
} }
tf_ssb_release_db_writer(ssb, db);
return added; 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) 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; sqlite3_stmt* statement = NULL;
if (sqlite3_prepare(db, "SELECT public_key FROM identities WHERE user = ? ORDER BY public_key", -1, &statement, NULL) == SQLITE_OK) 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); 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) 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; sqlite3_stmt* statement = NULL;
if (sqlite3_prepare(db, "SELECT public_key FROM identities ORDER BY public_key", -1, &statement, NULL) == SQLITE_OK) 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); 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 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; bool success = false;
memset(out_private_key, 0, crypto_sign_SECRETKEYBYTES); 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; sqlite3_stmt* statement = NULL;
if (sqlite3_prepare(db, "SELECT private_key FROM identities WHERE user = ? AND public_key = ?", -1, &statement, NULL) == SQLITE_OK) 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); sqlite3_finalize(statement);
} }
tf_ssb_release_db_reader(ssb, db);
return success; 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) 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; 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) 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); sqlite3_finalize(statement);
} }
tf_ssb_release_db_reader(ssb, db);
} }
return entry; 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; JSValue result = JS_UNDEFINED;
JSContext* context = tf_ssb_get_context(ssb); 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; 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) 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); sqlite3_finalize(statement);
} }
tf_ssb_release_db_reader(ssb, db);
return result; return result;
} }
tf_ssb_db_stored_connection_t* tf_ssb_db_get_stored_connections(tf_ssb_t* ssb, int* out_count) 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; tf_ssb_db_stored_connection_t* result = NULL;
int count = 0; 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); sqlite3_finalize(statement);
} }
tf_ssb_release_db_reader(ssb, db);
*out_count = count; *out_count = count;
return result; 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) 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; sqlite3_stmt* statement;
if (sqlite3_prepare(db, "DELETE FROM connections WHERE host = ? AND port = ? AND key = ?", -1, &statement, NULL) == SQLITE_OK) 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); 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 }; 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; 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 && if (sqlite3_bind_text(statement, 1, user, -1, NULL) == SQLITE_OK &&
sqlite3_bind_text(statement, 2, path, -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); sqlite3_finalize(statement);
} }
tf_ssb_release_db_reader(ssb, db);
if (!*app_blob_id) 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); tf_ssb_t* tf_ssb_create(uv_loop_t* loop, JSContext* context, const char* db_path);
void tf_ssb_destroy(tf_ssb_t* ssb); 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); sqlite3* tf_ssb_acquire_db_reader(tf_ssb_t* ssb);
void tf_ssb_release_db_reader(tf_ssb_t* ssb, sqlite3* db); 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); uv_loop_t* tf_ssb_get_loop(tf_ssb_t* ssb);
void tf_ssb_generate_keys(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; sqlite3_stmt* statement;
JSContext* context = tf_ssb_get_context(ssb); JSContext* context = tf_ssb_get_context(ssb);
JSValue apps = JS_UNDEFINED; 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 && if (sqlite3_bind_text(statement, 1, user, -1, NULL) == SQLITE_OK &&
sqlite3_step(statement) == SQLITE_ROW) 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); JSValue json = JS_JSONStringify(context, out_apps, JS_NULL, JS_NULL);
const char* text = JS_ToCString(context, json); 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 && if (sqlite3_bind_text(statement, 1, user, -1, NULL) == SQLITE_OK &&
sqlite3_bind_text(statement, 2, text, -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); sqlite3_finalize(statement);
} }
tf_ssb_release_db_writer(ssb, db);
JS_FreeCString(context, text); JS_FreeCString(context, text);
JS_FreeValue(context, json); 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) static bool _tf_ssb_register_app(tf_ssb_t* ssb, const char* user, const char* app, const char* id)
{ {
bool result = false; bool result = false;
sqlite3_stmt* statement; 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) 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 && {
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, 2, app, -1, NULL) == SQLITE_OK &&
sqlite3_bind_text(statement, 3, id, -1, NULL) == SQLITE_OK && sqlite3_bind_text(statement, 3, id, -1, NULL) == SQLITE_OK &&
sqlite3_step(statement) == SQLITE_DONE) sqlite3_step(statement) == SQLITE_DONE)
{ {
result = sqlite3_changes(tf_ssb_get_db(ssb)) != 0; result = sqlite3_changes(db) != 0;
} }
sqlite3_finalize(statement); sqlite3_finalize(statement);
} }
return result; 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) 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)) 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); _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) void tf_ssb_import(tf_ssb_t* ssb, const char* user, const char* path)
{ {
uv_fs_t req = { 0 }; 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); int r = uv_fs_scandir(tf_ssb_get_loop(ssb), &req, path, 0, NULL);
if (r >= 0) 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)) 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); _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)) if (JS_IsUndefined(result))
{ {
tf_ssb_t* ssb = JS_GetOpaque(this_val, _tf_ssb_classId); 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 }; uint8_t private_key[crypto_sign_SECRETKEYBYTES] = { 0 };
if (_tf_ssb_get_private_key_curve25519(db, signer_user, signer_identity, private_key)) 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); 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); 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 }; uint8_t private_key[crypto_sign_SECRETKEYBYTES] = { 0 };
tf_ssb_t* ssb = JS_GetOpaque(this_val, _tf_ssb_classId); 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)) if (_tf_ssb_get_private_key_curve25519(db, user, identity, private_key))
{ {
uint8_t* decoded = tf_malloc(message_size); 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); 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, user);
JS_FreeCString(context, identity); JS_FreeCString(context, identity);

View File

@ -1,11 +1,13 @@
#include "ssb.rpc.h" #include "ssb.rpc.h"
#include "log.h"
#include "mem.h" #include "mem.h"
#include "ssb.h" #include "ssb.h"
#include "ssb.db.h" #include "ssb.db.h"
#include "util.js.h" #include "util.js.h"
#include "sqlite3.h" #include "sqlite3.h"
#include "uv.h"
#include <inttypes.h> #include <inttypes.h>
#include <string.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); 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_blob_wants_t* blob_wants = tf_ssb_connection_get_blob_wants_state(connection);
tf_ssb_t* ssb = tf_ssb_connection_get_ssb(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; 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) 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); 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) 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; bool result = default_value;
JSContext* context = tf_ssb_get_context(ssb); 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; sqlite3_stmt* statement;
if (sqlite3_prepare(db, "SELECT value FROM properties WHERE id = 'core' AND key = 'settings'", -1, &statement, NULL) == SQLITE_OK) 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); sqlite3_finalize(statement);
} }
tf_ssb_release_db_reader(ssb, db);
return result; return result;
} }
@ -216,7 +220,7 @@ static bool _get_global_setting_string(tf_ssb_t* ssb, const char* name, char* ou
{ {
bool result = false; bool result = false;
JSContext* context = tf_ssb_get_context(ssb); 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; sqlite3_stmt* statement;
if (sqlite3_prepare(db, "SELECT value FROM properties WHERE id = 'core' AND key = 'settings'", -1, &statement, NULL) == SQLITE_OK) 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); sqlite3_finalize(statement);
} }
tf_ssb_release_db_reader(ssb, db);
return result; return result;
} }
@ -417,9 +422,31 @@ typedef struct _blobs_get_t
char id[k_blob_id_len]; char id[k_blob_id_len];
size_t received; size_t received;
size_t expected_size; size_t expected_size;
char buffer[]; bool done;
tf_ssb_t* ssb;
uv_work_t work;
uint8_t buffer[];
} blobs_get_t; } 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) 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); 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; bool stored = false;
if (JS_ToBool(context, args)) if (JS_ToBool(context, args))
{ {
char id[256]; get->work.data = get;
stored = tf_ssb_db_blob_store(ssb, (uint8_t*)get->buffer, get->expected_size, id, sizeof(id), NULL); 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( tf_ssb_connection_rpc_send(
connection, 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) 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) 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); 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); snprintf(get->id, sizeof(get->id), "%s", blob_id);
memset(get->buffer, 0, size); 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); tf_ssb_t* ssb = tf_ssb_connection_get_ssb(connection);
JSContext* context = tf_ssb_get_context(ssb); 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; sqlite3_stmt* statement;
const int k_max = 32; const int k_max = 32;
int64_t max_sequence_seen = 0; 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); sqlite3_finalize(statement);
} }
tf_ssb_release_db_reader(ssb, db);
if (max_sequence_seen == sequence + k_max - 1) 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_t loop = { 0 };
uv_loop_init(&loop); 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_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_register(tf_ssb_get_context(ssb1), ssb1);
uv_idle_t idle0 = { .data = ssb0 }; 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_t loop = { 0 };
uv_loop_init(&loop); 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_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_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); tf_ssb_register(tf_ssb_get_context(ssb2), ssb2);
uv_idle_t idle0 = { .data = ssb0 }; 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_t loop = { 0 };
uv_loop_init(&loop); 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); tf_ssb_generate_keys(ssb0);
char id0[k_id_base64_len] = { 0 }; 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); 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_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); tf_ssb_generate_keys(ssb1);
uint8_t id0bin[k_id_bin_len]; uint8_t id0bin[k_id_bin_len];
tf_ssb_id_str_to_bin(id0bin, id0); tf_ssb_id_str_to_bin(id0bin, id0);

View File

@ -14,6 +14,7 @@
#include "tlscontext.js.h" #include "tlscontext.js.h"
#include "trace.h" #include "trace.h"
#include "util.js.h" #include "util.js.h"
#include "version.h"
#include "backtrace.h" #include "backtrace.h"
#include "quickjs.h" #include "quickjs.h"
@ -35,8 +36,6 @@
#define _countof(a) ((int)(sizeof((a)) / sizeof(*(a)))) #define _countof(a) ((int)(sizeof((a)) / sizeof(*(a))))
#endif #endif
static const char* k_version = "1.0";
static JSClassID _import_class_id; static JSClassID _import_class_id;
static int _count; 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_task_t* task = JS_GetContextOpaque(context);
tf_trace_begin(task->_trace, __func__); 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); 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) 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"); unlink("out/testdb.sqlite");
char command[256]; 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); tf_printf("%s\n", command);
int result = system(command); int result = system(command);
tf_printf("returned %d\n", WEXITSTATUS(result)); 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 VERSION_NUMBER=`sed -n -e 's/^VERSION_NUMBER := //p' Makefile`
NICKNAME=$2 VERSION_NAME=`sed -n -e 's/^VERSION_NAME := //p' Makefile`
rm -rfv tildefriends-$VERSION rm -rfv tildefriends-$VERSION_NUMBER
svn export . tildefriends-$VERSION svn export . tildefriends-$VERSION_NUMBER
echo "tildefriends-$VERSION: $NICKNAME" > tildefriends-$VERSION/VERSION echo "tildefriends-$VERSION_NUMBER: $VERSION_NAME" > tildefriends-$VERSION_NUMBER/VERSION
tar \ tar \
--exclude=deps/libbacktrace/Isaac.Newton-Opticks.txt \ --exclude=deps/libbacktrace/Isaac.Newton-Opticks.txt \
--exclude=deps/libsodium/builds \ --exclude=deps/libsodium/builds \
@ -17,5 +17,7 @@ tar \
--exclude=deps/sqlite/shell.c \ --exclude=deps/sqlite/shell.c \
--exclude=deps/zlib/contrib/vstudio \ --exclude=deps/zlib/contrib/vstudio \
--exclude=deps/zlib/doc \ --exclude=deps/zlib/doc \
-caf tildefriends-$VERSION.tar.xz tildefriends-$VERSION -caf tildefriends-$VERSION_NUMBER.tar.xz tildefriends-$VERSION_NUMBER
rm -rfv tildefriends-$VERSION 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 VERSION=2.7.5
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 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/ssb/
cp -fv deps/lit/* apps/sneaker/ cp -fv deps/lit/* apps/sneaker/