Continuing to chip away at moving ssb.js to C. This time, following.

git-svn-id: https://www.unprompted.com/svn/projects/tildefriends/trunk@4096 ed5197a5-7fde-0310-b194-c3ffbd925b24
This commit is contained in:
Cory McWilliams 2022-12-31 21:44:48 +00:00
parent a9f6593979
commit 120ed36552
4 changed files with 167 additions and 50 deletions

View File

@ -5,54 +5,6 @@ let g_attendants = {};
const k_use_create_history_stream = false; const k_use_create_history_stream = false;
const k_blobs_concurrent_target = 8; const k_blobs_concurrent_target = 8;
function following(db, id) {
var o = db.get(id + ":following");
const k_version = 5;
var f = o ? JSON.parse(o) : o;
if (!f || f.version != k_version) {
f = {users: [], sequence: 0, version: k_version};
}
f.users = new Set(f.users);
ssb.sqlStream(
"SELECT "+
" sequence, "+
" json_extract(content, '$.contact') AS contact, "+
" json_extract(content, '$.following') AS following "+
"FROM messages "+
"WHERE "+
" author = ?1 AND "+
" sequence > ?2 AND "+
" json_extract(content, '$.type') = 'contact' "+
"UNION SELECT MAX(sequence) AS sequence, NULL, NULL FROM messages WHERE author = ?1 "+
"ORDER BY sequence",
[id, f.sequence],
function(row) {
if (row.following) {
f.users.add(row.contact);
} else {
f.users.delete(row.contact);
}
f.sequence = row.sequence;
});
f.users = Array.from(f.users).sort();
var j = JSON.stringify(f);
if (o != j) {
db.set(id + ":following", j);
}
return f.users;
}
function followingDeep(db, seed_ids, depth) {
if (depth <= 0) {
return seed_ids;
}
var f = seed_ids.map(x => following(db, x));
var ids = [].concat(...f);
var x = followingDeep(db, [...new Set(ids)].sort(), depth - 1);
x = [...new Set([].concat(...x, ...seed_ids))].sort();
return x;
}
function get_latest_sequence_for_author(author) { function get_latest_sequence_for_author(author) {
var sequence = 0; var sequence = 0;
ssb.sqlStream( ssb.sqlStream(
@ -140,7 +92,7 @@ ssb.addEventListener('connections', function on_connections_changed(change, conn
if (k_use_create_history_stream) { if (k_use_create_history_stream) {
connection.send_json({'name': ['createHistoryStream'], 'type': 'source', 'args': [{'id': connection.id, 'seq': sequence, 'live': true, 'keys': false}]}, storeMessage); connection.send_json({'name': ['createHistoryStream'], 'type': 'source', 'args': [{'id': connection.id, 'seq': sequence, 'live': true, 'keys': false}]}, storeMessage);
var identities = ssb.getAllIdentities(); var identities = ssb.getAllIdentities();
followingDeep(g_database, identities, 2).then(function(ids) { ssb.followingDeep(identities, 2).then(function(ids) {
for (let id of ids) { for (let id of ids) {
if (identities.indexOf(id) != -1) { if (identities.indexOf(id) != -1) {
continue; continue;
@ -226,7 +178,7 @@ function ebtReplicateSendClock(request, have) {
var identities = ssb.getAllIdentities(); var identities = ssb.getAllIdentities();
var message = {}; var message = {};
var last_sent = request.connection.sent_clock || {}; var last_sent = request.connection.sent_clock || {};
var ids = followingDeep(g_database, identities, 2).concat([request.connection.id]); var ids = ssb.followingDeep(identities, 2).concat([request.connection.id]);
if (!Object.keys(last_sent).length) { if (!Object.keys(last_sent).length) {
for (let id of ids) { for (let id of ids) {
message[id] = get_latest_sequence_for_author(id); message[id] = get_latest_sequence_for_author(id);

View File

@ -973,6 +973,129 @@ bool tf_ssb_db_identity_get_private_key(tf_ssb_t* ssb, const char* user, const c
return success; return success;
} }
typedef struct _following_t following_t;
typedef struct _following_t
{
char id[k_id_base64_len];
following_t** following;
following_t** blocking;
int following_count;
int blocking_count;
int depth;
} following_t;
static int _following_compare(const void* a, const void* b)
{
const char* ida = a;
const following_t* const* followingb = b;
return strcmp(ida, (*followingb)->id);
}
static void _add_following_entry(following_t*** list, int* count, following_t* add)
{
int index = tf_util_insert_index(add->id, *list, *count, sizeof(following_t*), _following_compare);
if (index >= *count || strcmp(add->id, (*list)[index]->id) == 0)
{
*list = tf_resize_vec(*list, sizeof(**list) * (*count + 1));
if (*count - index)
{
memmove(*list + index + 1, *list + index, sizeof(following_t*) * (*count - index));
}
(*list)[index] = add;
}
}
static following_t* _get_following(tf_ssb_t* ssb, const char* id, following_t*** following, int* following_count, int depth, int max_depth)
{
int index = tf_util_insert_index(id, *following, *following_count, sizeof(following_t*), _following_compare);
following_t* entry = NULL;
if (index < *following_count && strcmp(id, (*following)[index]->id) == 0)
{
entry = (*following)[index];
}
else
{
*following = tf_resize_vec(*following, sizeof(*following) * (*following_count + 1));
if (*following_count - index)
{
memmove(*following + index + 1, *following + index, sizeof(following_t*) * (*following_count - index));
}
entry = tf_malloc(sizeof(following_t));
(*following)[index] = entry;
(*following_count)++;
*entry = (following_t) { 0 };
snprintf(entry->id, sizeof(entry->id), "%s", id);
entry->depth = depth;
if (depth < max_depth)
{
sqlite3* db = tf_ssb_get_db(ssb);
sqlite3_stmt* statement = NULL;
if (sqlite3_prepare(db, "SELECT json_extract(content, '$.contact'), json_extract(content, '$.following'), json_extract(content, '$.blocking') FROM messages WHERE author = ? AND json_extract(content, '$.type') = 'contact' ORDER BY sequence", -1, &statement, NULL) == SQLITE_OK)
{
if (sqlite3_bind_text(statement, 1, id, -1, NULL) == SQLITE_OK)
{
while (sqlite3_step(statement) == SQLITE_ROW)
{
const char* contact = (const char*)sqlite3_column_text(statement, 0);
if (sqlite3_column_type(statement, 1) != SQLITE_NULL)
{
bool is_following = sqlite3_column_int(statement, 1);
following_t* next = _get_following(ssb, contact, following, following_count, depth + 1, max_depth);
if (is_following)
{
_add_following_entry(&entry->following, &entry->following_count, next);
}
}
if (sqlite3_column_type(statement, 2) != SQLITE_NULL)
{
bool is_blocking = sqlite3_column_int(statement, 2);
following_t* next = _get_following(ssb, contact, following, following_count, depth + 1, 0 /* don't dig deeper into blocked users */);
if (is_blocking)
{
_add_following_entry(&entry->blocking, &entry->blocking_count, next);
}
}
}
}
sqlite3_finalize(statement);
}
}
}
return entry;
}
const char** tf_ssb_db_following_deep(tf_ssb_t* ssb, const char** ids, int count, int depth)
{
following_t** following = NULL;
int following_count = 0;
for (int i = 0; i < count; i++)
{
_get_following(ssb, ids[i], &following, &following_count, 0, depth);
}
char** result = tf_malloc(sizeof(char*) * (following_count + 1) + k_id_base64_len * following_count);
char* result_ids = (char*)result + sizeof(char*) * (following_count + 1);
for (int i = 0; i < following_count; i++)
{
result[i] = result_ids + k_id_base64_len * i;
snprintf(result[i], k_id_base64_len, "%s", following[i]->id);
}
result[following_count] = NULL;
for (int i = 0; i < following_count; i++)
{
tf_free(following[i]->following);
tf_free(following[i]->blocking);
tf_free(following[i]);
}
tf_free(following);
return (const char**)result;
}
static void _test_private(sqlite3* db, const uint8_t* private_key) static void _test_private(sqlite3* db, const uint8_t* private_key)
{ {
sqlite3_stmt* statement = NULL; sqlite3_stmt* statement = NULL;

View File

@ -26,4 +26,6 @@ void tf_ssb_db_identity_visit(tf_ssb_t* ssb, const char* user, void (*callback)(
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);
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);
const char** tf_ssb_db_following_deep(tf_ssb_t* ssb, const char** ids, int count, int depth);
void tf_ssb_db_private(sqlite3* db); void tf_ssb_db_private(sqlite3* db);

View File

@ -966,6 +966,45 @@ static JSValue _tf_ssb_connectionSendJson(JSContext* context, JSValueConst this_
return JS_NewInt32(context, request_number); return JS_NewInt32(context, request_number);
} }
static JSValue _tf_ssb_followingDeep(JSContext* context, JSValueConst this_val, int argc, JSValueConst* argv)
{
int depth = 2;
if (!JS_IsArray(context, argv[0]))
{
return JS_ThrowTypeError(context, "Expected argument 1 to be an array of ids.");
}
if (JS_ToInt32(context, &depth, argv[1]))
{
return JS_ThrowTypeError(context, "Could not convert argument 2 to integer.");
}
int count = tf_util_get_length(context, argv[0]);
const char** ids = tf_malloc(count * sizeof(char*));
for (int i = 0; i < count; i++)
{
JSValue id = JS_GetPropertyUint32(context, argv[0], i);
ids[i] = JS_ToCString(context, id);
JS_FreeValue(context, id);
}
tf_ssb_t* ssb = JS_GetOpaque(this_val, _tf_ssb_classId);
const char** following_deep = tf_ssb_db_following_deep(ssb, ids, count, depth);
JSValue result = JS_NewArray(context);
int index = 0;
for (const char** id = following_deep; *id; id++)
{
JS_SetPropertyUint32(context, result, index++, JS_NewString(context, *id));
}
tf_free(following_deep);
for (int i = 0; i < count; i++)
{
JS_FreeCString(context, ids[i]);
}
tf_free(ids);
return result;
}
void tf_ssb_register(JSContext* context, tf_ssb_t* ssb) void tf_ssb_register(JSContext* context, tf_ssb_t* ssb)
{ {
JS_NewClassID(&_tf_ssb_classId); JS_NewClassID(&_tf_ssb_classId);
@ -1005,6 +1044,7 @@ void tf_ssb_register(JSContext* context, tf_ssb_t* ssb)
JS_SetPropertyStr(context, object, "getBroadcasts", JS_NewCFunction(context, _tf_ssb_getBroadcasts, "getBroadcasts", 0)); JS_SetPropertyStr(context, object, "getBroadcasts", JS_NewCFunction(context, _tf_ssb_getBroadcasts, "getBroadcasts", 0));
JS_SetPropertyStr(context, object, "connect", JS_NewCFunction(context, _tf_ssb_connect, "connect", 1)); JS_SetPropertyStr(context, object, "connect", JS_NewCFunction(context, _tf_ssb_connect, "connect", 1));
JS_SetPropertyStr(context, object, "createTunnel", JS_NewCFunction(context, _tf_ssb_createTunnel, "createTunnel", 3)); JS_SetPropertyStr(context, object, "createTunnel", JS_NewCFunction(context, _tf_ssb_createTunnel, "createTunnel", 3));
JS_SetPropertyStr(context, object, "followingDeep", JS_NewCFunction(context, _tf_ssb_followingDeep, "followingDeep", 2));
/* Should be trusted only. */ /* Should be trusted only. */
JS_SetPropertyStr(context, object, "addRpc", JS_NewCFunction(context, _tf_ssb_add_rpc, "addRpc", 2)); JS_SetPropertyStr(context, object, "addRpc", JS_NewCFunction(context, _tf_ssb_add_rpc, "addRpc", 2));