# define _GNU_SOURCE
# include "status.h"
# include "status/react.h"
// Submodules
# include "json/json.h"
# include "json/layout.h"
# include "ffdb/fs_list.h"
# include "ffdb/hash_index.h"
# include "ffdb/trie.h"
# include "sha256/sha256.h"
# include "collections/array.h"
# include "http/client/client.h"
# include "util/format.h"
# include "ap/object.h"
// Model
# include "model/server.h"
# include "model/account.h"
# include "model/activity.h"
# include "model/notification.h"
# include "model/timeline.h"
# include "model/emoji.h"
# include "model/media.h"
// Standard Library
# include <stdio.h>
# include <string.h>
# include <stdlib.h>
# include <stddef.h>
# include <sys/stat.h>
# define OBJ_TYPE struct status
static struct json_object_field status_layout [ ] = {
JSON_FIELD_INTEGER ( account_id , true ) ,
JSON_FIELD_INTEGER ( activity_id , false ) ,
JSON_FIELD_STRING ( url , false ) ,
JSON_FIELD_BOOL ( stub , false ) ,
JSON_FIELD_BOOL ( remote , false ) ,
JSON_FIELD_STRING ( content , false ) ,
JSON_FIELD_STRING ( source , false ) ,
JSON_FIELD_BOOL ( pinned , false ) ,
JSON_FIELD_BOOL ( bookmarked , false ) ,
JSON_FIELD_DATETIME ( published , false ) ,
JSON_FIELD_INTEGER ( in_reply_to , false ) ,
JSON_FIELD_INTEGER ( repost_id , false ) ,
JSON_FIELD_INTEGER ( root_status_id , false ) ,
JSON_FIELD_INTEGER ( reposted_status_id , false ) ,
JSON_FIELD_ARRAY_OF_STRINGS ( media , false ) ,
JSON_FIELD_ARRAY_OF_TYPE ( media2 , false , media_type ) ,
JSON_FIELD_ARRAY_OF_TYPE ( reacts , false , status_react_type ) ,
JSON_FIELD_ARRAY_OF_INTS ( likes , false ) ,
JSON_FIELD_ARRAY_OF_INTS ( replies , false ) ,
JSON_FIELD_ARRAY_OF_INTS ( reposts , false ) ,
JSON_FIELD_ARRAY_OF_INTS ( mentions , false ) ,
JSON_FIELD_ARRAY_OF_TYPE ( emoji , false , emoji_type ) ,
JSON_FIELD_BOOL ( sensitive , true ) ,
JSON_FIELD_END
} ;
# undef OBJ_TYPE
void * allocate ( size_t s )
{
void * ptr = malloc ( s ) ;
memset ( ptr , 0 , s ) ;
return ptr ;
}
static const char * get_status_data_filename ( unsigned int id , char * filename , int size )
{
int millions = id / 1000000 ;
int thousands = ( id % 1000000 ) / 1000 ;
int ones = id % 1000 ;
mkdir ( format ( filename , size , " data/statuses/%03dm " , millions ) , 0755 ) ;
mkdir ( format ( filename , size , " data/statuses/%03dm/%03dk " , millions , thousands ) , 0755 ) ;
return format ( filename , size , " data/statuses/%03dm/%03dk/%03d.json " , millions , thousands , ones ) ;
}
static FILE * open_status_data_file ( unsigned int id , const char * mode )
{
char filename [ 512 ] ;
FILE * f = NULL ;
f = fopen ( get_status_data_filename ( id , filename , sizeof ( filename ) ) , mode ) ;
if ( f ) {
//printf( "Using new location at %s\n", filename );
return f ;
}
f = fopen ( format ( filename , sizeof ( filename ) , " data/statuses/%d.json " , id ) , mode ) ;
if ( f ) {
//printf( "Using existing location at %s\n", filename );
return f ;
}
return NULL ;
}
struct status * status_from_id ( unsigned int id )
{
if ( id = = 0 ) { return NULL ; }
struct status * s = NULL ;
FILE * f = open_status_data_file ( id , " r " ) ;
if ( ! f ) { return NULL ; }
s = allocate ( sizeof ( struct status ) ) ;
s - > id = id ;
if ( ! json_read_object_layout_from_FILE ( f , status_layout , s ) ) {
printf ( " Failed to load status %d \n " , id ) ;
status_free ( s ) ;
return NULL ;
}
// Convert media to media2
if ( s - > media . count ! = 0 ) {
for ( int i = 0 ; i < s - > media . count ; + + i ) {
struct media * local = media_from_local_uri ( s - > media . items [ i ] ) ;
if ( local ) {
array_append ( & s - > media2 , sizeof ( local ) , & local ) ;
continue ;
}
struct media * remote = media_new ( ) ;
remote - > preview_url = strdup ( s - > media . items [ i ] ) ;
remote - > remote_url = strdup ( s - > media . items [ i ] ) ;
remote - > content_type = strdup ( " image/* " ) ;
array_append ( & s - > media2 , sizeof ( remote ) , & remote ) ;
free ( s - > media . items [ i ] ) ;
}
free ( s - > media . items ) ;
memset ( & s - > media , 0 , sizeof ( s - > media ) ) ;
status_save ( s ) ;
}
if ( ! s - > source ) {
s - > source = strdup ( " " ) ;
}
if ( ! s - > remote & & ! s - > url ) {
s - > url = aformat ( " https://%s/note/%d " , g_server - > domain , s - > id ) ;
}
if ( s - > account_id = = owner_account_id ) {
s - > remote = false ;
}
return s ;
}
struct status * status_new_repost ( struct status * s , struct account * a )
{
struct status * repost ;
repost = malloc ( sizeof ( * repost ) ) ;
memset ( repost , 0 , sizeof ( * repost ) ) ;
repost - > account_id = a - > id ;
repost - > repost_id = s - > id ;
repost - > published = time ( NULL ) ;
status_save_new ( repost ) ;
return repost ;
}
static struct status * status_from_local_uri ( const char * uri )
{
if ( 0 ! = strncmp ( " https:// " , uri , 8 ) ) { return NULL ; }
uri + = 8 ;
int server_name_length = strlen ( g_server - > domain ) ;
if ( 0 ! = strncmp ( g_server - > domain , uri , server_name_length ) ) { return NULL ; }
uri + = server_name_length ;
if ( 0 ! = strncmp ( " /note/ " , uri , 6 ) ) { return NULL ; }
uri + = 6 ;
// Note: zero is never a valid status id
int id = atoi ( uri ) ;
if ( id = = 0 ) { return NULL ; }
return status_from_id ( id ) ;
}
struct status * status_from_uri ( const char * uri )
{
// Check for local
struct status * s = status_from_local_uri ( uri ) ;
if ( s ) { return s ; }
int id = - 1 ;
if ( ! hash_index_get ( " data/statuses/uri " , uri , & id ) ) { return NULL ; }
return status_from_id ( id ) ;
}
void status_add_reply ( struct status * s , struct status * child )
{
if ( child - > id = = 0 ) { return ; }
for ( int i = 0 ; i < s - > replies . count ; + + i ) {
if ( s - > replies . items [ i ] = = child - > id ) {
return ;
}
}
int id = child - > id ;
array_append ( & s - > replies , sizeof ( id ) , & id ) ;
status_save ( s ) ;
}
void status_add_mention ( struct status * s , int id )
{
for ( int i = 0 ; i < s - > mentions . count ; + + i ) {
if ( s - > mentions . items [ i ] = = id ) {
// Already mentioned, don't add a second time
return ;
}
}
array_append ( & s - > mentions , sizeof ( id ) , & id ) ;
}
void status_add_repost ( struct status * s , struct status * repost )
{
// TODO: implement
}
bool status_sync_from_activity_pub ( struct status * s , struct ap_object * act )
{
if ( ! act - > actor & & act - > attributed_to ) {
act - > actor = strdup ( act - > attributed_to ) ;
}
printf ( " Syncing status from activity %s \n " , act - > id ) ;
ap_object_write_to_FILE ( act , stdout ) ;
bool result = false ;
struct account * a = account_from_uri_or_fetch ( act - > actor ) ;
if ( ! a ) {
printf ( " ! Unable to get account for %s \n " , act - > actor ) ;
goto failed ;
}
s - > account_id = a - > id ;
s - > published = act - > published ;
s - > remote = true ;
s - > stub = false ;
s - > sensitive = act - > sensitive ;
if ( act - > in_reply_to ) {
struct status * parent = status_from_uri_or_fetch ( act - > in_reply_to ) ;
if ( parent ) {
status_make_reply_to ( s , parent - > id ) ;
// DO NOT SAVE parent! This is done inside status_make_reply_to
status_free ( parent ) ;
}
}
for ( int i = 0 ; i < s - > emoji . count ; + + i ) {
emoji_free ( s - > emoji . items [ i ] ) ;
}
s - > emoji . count = 0 ;
s - > mentions . count = 0 ;
for ( int i = 0 ; i < act - > tags . count ; + + i ) {
struct ap_activity_tag * tag = act - > tags . items [ i ] ;
if ( tag - > type = = aptag_mention ) {
struct account * a = account_from_uri_or_fetch ( tag - > href ) ;
if ( a ) {
int item = a - > id ;
array_append ( & s - > mentions , sizeof ( item ) , & item ) ;
account_free ( a ) ;
}
} else if ( tag - > type = = aptag_emoji ) {
struct emoji * e ;
e = malloc ( sizeof ( * e ) ) ;
memset ( e , 0 , sizeof ( * e ) ) ;
e - > url = safe_strdup ( tag - > icon . url ) ;
e - > shortcode = safe_strdup ( & tag - > name [ 1 ] ) ;
e - > shortcode [ strlen ( e - > shortcode ) - 1 ] = ' \0 ' ;
array_append ( & s - > emoji , sizeof ( e ) , & e ) ;
}
}
free ( s - > source ) ;
s - > source = safe_strdup ( act - > source . content ) ;
free ( s - > url ) ;
s - > url = strdup ( act - > id ) ;
if ( ! s - > source ) {
printf ( " ? No source, using content directly \n " ) ;
free ( s - > content ) ;
s - > content = safe_strdup ( act - > content . content ) ;
}
// Erase existing media
for ( int i = 0 ; i < s - > media . count ; + + i ) {
free ( s - > media . items [ i ] ) ;
}
free ( s - > media . items ) ;
memset ( & s - > media , 0 , sizeof ( s - > media ) ) ;
for ( int i = 0 ; i < s - > media2 . count ; + + i ) {
media_free ( s - > media2 . items [ i ] ) ;
}
free ( s - > media2 . items ) ;
memset ( & s - > media2 , 0 , sizeof ( s - > media2 ) ) ;
// Recreate the media field
for ( int i = 0 ; i < act - > attachments . count ; + + i ) {
struct ap_object * att = act - > attachments . items [ i ] ;
if ( att & & att - > url ) {
struct media * media = media_new ( ) ;
media - > remote_url = strdup ( att - > url ) ;
media - > content_type = strdup ( att - > media_type ) ;
array_append ( & s - > media2 , sizeof ( char * ) , & media ) ;
}
}
status_save ( s ) ;
status_add_to_timeline ( s , a - > id ) ;
result = true ;
cleanup :
account_free ( a ) ;
return result ;
failed :
result = false ;
goto cleanup ;
} ;
bool pull_remote_file ( const char * filename , const char * uri )
{
printf ( " * Fetching %s \n " , uri ) ;
char tmp_filename [ 512 ] ;
FILE * f = fopen ( format ( tmp_filename , 512 , " %s.tmp " , filename ) , " w " ) ;
long status_code = - 1 ;
const void * request [ ] = {
HTTP_REQ_URL , uri ,
HTTP_REQ_HEADER , " Accept: application/ld+json; profile= \" https://www.w3.org/ns/activitystreams \" " ,
HTTP_REQ_OUTFILE , f ,
HTTP_REQ_RESULT_STATUS , & status_code ,
NULL ,
} ;
if ( ! http_client_do ( request ) ) {
printf ( " ! Unable to fetch %s \n " , uri ) ;
fclose ( f ) ;
return false ;
}
printf ( " status_code = %ld \n " , status_code ) ;
if ( status_code = = 200 ) {
// success
fclose ( f ) ;
rename ( tmp_filename , filename ) ;
return true ;
} else if ( status_code = = 401 ) {
// Not Authorized
// TODO: perform signed fetch
fclose ( f ) ;
return false ;
}
// Failure
fclose ( f ) ;
return false ;
}
bool status_sync_from_uri ( struct status * s , const char * uri )
{
struct ap_object * act = NULL ;
FILE * f = NULL ;
bool result = false ;
mkdir_p ( " data/statuses/ap " , 0755 ) ;
// Fetch the object from the remote server
char filename [ 512 ] ;
snprintf ( filename , sizeof ( filename ) , " data/statuses/ap/%d.json " , s - > id ) ;
f = fopen ( filename , " r " ) ;
if ( ! f ) {
if ( ! pull_remote_file ( filename , uri ) ) { goto failed ; }
f = fopen ( filename , " r " ) ;
if ( ! f ) { goto failed ; }
}
// Load the activity and sync status
act = ap_object_from_FILE ( f ) ; f = NULL ;
if ( ! act ) { goto failed ; }
if ( ! status_sync_from_activity_pub ( s , act ) ) { goto failed ; }
result = true ;
cleanup :
if ( f ) { fclose ( f ) ; }
ap_object_free ( act ) ;
return result ;
failed :
if ( s ) {
printf ( " Creating stub status for later sync \n " ) ;
char filename [ 512 ] ;
ffdb_trie_set ( " data/statuses/stubs " , format ( filename , 512 , " %d " , s - > id ) , " T " ) ;
goto cleanup ;
}
status_free ( s ) ;
result = false ;
goto cleanup ;
}
bool status_sync ( struct status * s )
{
return status_sync_from_uri ( s , s - > url ) ;
}
struct status * status_from_uri_or_fetch ( const char * uri )
{
struct status * s = status_from_uri ( uri ) ;
if ( s ) { return s ; }
return status_fetch_from_uri ( uri ) ;
}
struct status * status_fetch_from_uri ( const char * uri )
{
struct status * s = status_from_uri ( uri ) ;
if ( ! s ) {
s = malloc ( sizeof ( * s ) ) ;
memset ( s , 0 , sizeof ( * s ) ) ;
s - > remote = true ;
s - > account_id = - 1 ;
s - > stub = true ;
s - > published = time ( NULL ) ;
free ( s - > url ) ;
s - > url = strdup ( uri ) ;
status_save_new ( s ) ;
hash_index_set ( " data/statuses/uri " , uri , s - > id ) ;
}
if ( ! status_sync_from_uri ( s , uri ) ) {
status_free ( s ) ;
return NULL ;
}
return s ;
}
struct status * status_new_system_unfollow ( int account_id )
{
struct account * a = account_from_id ( account_id ) ;
if ( ! a ) { return NULL ; }
struct status * s ;
s = malloc ( sizeof ( * s ) ) ;
memset ( s , 0 , sizeof ( * s ) ) ;
s - > id = - 1 ;
s - > published = time ( NULL ) ;
s - > account_id = - 1 ;
asprintf ( & s - > content , " %s unfollowed you \n " , a - > display_name ) ;
s - > sensitive = true ;
account_free ( a ) ;
return s ;
}
struct status * status_new_system_block ( int account_id )
{
struct account * a = account_from_id ( account_id ) ;
if ( ! a ) { return NULL ; }
struct status * s ;
s = malloc ( sizeof ( * s ) ) ;
memset ( s , 0 , sizeof ( * s ) ) ;
s - > id = - 1 ;
s - > published = time ( NULL ) ;
s - > account_id = - 1 ;
asprintf ( & s - > content , " %s blocked you. View their account <a href='%s'>here</a>. " , a - > display_name , a - > account_url ) ;
s - > sensitive = false ;
account_free ( a ) ;
return s ;
}
struct status * status_new_system_stub ( struct status * stub )
{
struct status * s ;
s = malloc ( sizeof ( * s ) ) ;
memset ( s , 0 , sizeof ( * s ) ) ;
s - > id = stub - > id ; //-stub->id - 50;
s - > account_id = - 1 ;
s - > published = time ( NULL ) ;
asprintf ( & s - > content , " Unable to load status #%d. View on original server at <a href='%s'>%s</a> " ,
stub - > id ,
stub - > url , stub - > url
) ;
s - > sensitive = false ;
return s ;
}
struct status * status_from_activity ( struct ap_object * act )
{
struct status * s ;
s = malloc ( sizeof ( * s ) ) ;
memset ( s , 0 , sizeof ( * s ) ) ;
if ( ! status_sync_from_activity_pub ( s , act ) ) {
status_free ( s ) ;
return NULL ;
}
return s ;
}
void status_assign_local_id ( struct status * s )
{
if ( s - > id ) { return ; }
mkdir_p ( " data/statuses " , 0755 ) ;
int head = fs_list_get ( " data/statuses/HEAD " ) + 1 ;
fs_list_set ( " data/statuses/HEAD " , head ) ;
s - > id = head ;
s - > root_status_id = s - > id ;
}
bool status_save_new ( struct status * s )
{
status_assign_local_id ( s ) ;
status_save ( s ) ;
return true ;
}
void status_save ( struct status * s )
{
char filename [ 512 ] ;
json_write_object_layout_to_file ( get_status_data_filename ( s - > id , filename , sizeof ( filename ) ) , " \t " , status_layout , s ) ;
// Index the status
if ( s - > url ) {
hash_index_set ( " data/statuses/uri " , s - > url , s - > id ) ;
}
if ( s - > stub ) {
mkdir_p ( " data/statuses/stubs " , 0755 ) ;
ffdb_trie_set ( " data/statuses/stubs " , format ( filename , 512 , " %d " , s - > id ) , " T " ) ;
} else {
ffdb_trie_remove ( " data/statuses/stubs " , format ( filename , 512 , " %d " , s - > id ) ) ;
}
}
void status_write_to_FILE ( struct status * s , FILE * f )
{
json_write_object_layout_to_FILE ( f , " \t " , status_layout , s ) ;
}
void status_free ( struct status * s )
{
if ( ! s ) { return ; }
free ( s - > url ) ;
free ( s - > content ) ;
free ( s - > source ) ;
// Free media
for ( int i = 0 ; i < s - > media . count ; + + i ) {
free ( s - > media . items [ i ] ) ;
}
free ( s - > media . items ) ;
for ( int i = 0 ; i < s - > media2 . count ; + + i ) {
media_free ( s - > media2 . items [ i ] ) ;
}
free ( s - > media2 . items ) ;
// Free reactions
for ( int i = 0 ; i < s - > reacts . count ; + + i ) {
status_react_free ( s - > reacts . items [ i ] ) ;
}
free ( s - > reacts . items ) ;
free ( s - > likes . items ) ;
free ( s - > replies . items ) ;
free ( s - > reposts . items ) ;
free ( s - > mentions . items ) ;
for ( int i = 0 ; i < s - > emoji . count ; + + i ) {
emoji_free ( s - > emoji . items [ i ] ) ;
}
free ( s - > emoji . items ) ;
free ( s ) ;
}
void status_delete ( struct status * s )
{
int ids [ ] = {
s - > account_id ,
home_timeline_id ,
public_timeline_id
} ;
for ( int i = 0 ; i < sizeof ( ids ) / sizeof ( ids [ 0 ] ) ; + + i ) {
struct timeline * tl = timeline_from_id ( ids [ i ] ) ;
if ( tl ) {
timeline_remove_post ( tl , s ) ;
timeline_free ( tl ) ;
}
}
char filename [ 512 ] ;
ffdb_trie_remove ( " data/statuses/stubs " , format ( filename , 512 , " %d " , s - > id ) ) ;
get_status_data_filename ( s - > id , filename , sizeof ( filename ) ) ;
remove ( filename ) ;
status_free ( s ) ;
}
void status_add_to_timeline ( struct status * s , int timeline_id )
{
struct timeline * tl = timeline_from_id ( timeline_id ) ;
timeline_add_post ( tl , s ) ;
timeline_free ( tl ) ;
}
void status_make_reply_to ( struct status * s , int in_reply_to_id )
{
// Add this status to the other
struct status * in_reply_to = status_from_id ( in_reply_to_id ) ;
if ( ! in_reply_to ) {
s - > in_reply_to = 0 ;
} else {
// Setup this status's reply fields
s - > in_reply_to = in_reply_to_id ;
s - > root_status_id = in_reply_to - > root_status_id ;
// Record in parent as reply
status_add_reply ( in_reply_to , s ) ;
// Mention the account that made the post being replied to
status_add_mention ( s , in_reply_to - > account_id ) ;
// Mention everytone else in that post
for ( int i = 0 ; i < in_reply_to - > mentions . count ; + + i ) {
status_add_mention ( s , in_reply_to - > mentions . items [ i ] ) ;
}
status_save ( in_reply_to ) ;
}
status_free ( in_reply_to ) ;
}
void status_get_context ( struct status * s , void * ancestors_ptr , void * replies_ptr )
{
struct array_of_status {
struct status * * items ;
int count ;
} ;
struct array_of_status * ancestors = ancestors_ptr ;
struct array_of_status * replies = replies_ptr ;
memset ( ancestors , 0 , sizeof ( * ancestors ) ) ;
memset ( replies , 0 , sizeof ( * replies ) ) ;
struct status * parent = NULL ;
for ( int i = s - > in_reply_to ; i ! = 0 ; i = parent - > in_reply_to ) {
parent = status_from_id ( i ) ;
array_append ( ancestors , sizeof ( parent ) , & parent ) ;
} ;
// reverse ancestors
for ( int i = 0 ; i < ancestors - > count / 2 ; + + i ) {
struct status * a = ancestors - > items [ i ] ;
struct status * b = ancestors - > items [ ancestors - > count - i - 1 ] ;
ancestors - > items [ i ] = b ;
ancestors - > items [ ancestors - > count - i - 1 ] = a ;
}
for ( int i = 0 ; i < s - > replies . count ; + + i ) {
struct status * reply = status_from_id ( s - > replies . items [ i ] ) ;
array_append ( replies , sizeof ( reply ) , & reply ) ;
}
}
void status_add_react ( struct status * s , const char * react , struct account * a )
{
// generate outbox element
if ( a - > id = = owner_account_id ) {
if ( s - > account_id = = owner_account_id ) {
// De
printf ( " TODO: generate outbox activity for adding reaction '%s' to status #%u by account #%u, deliver to followers \n " , react , s - > id , a - > id ) ;
} else {
// Deliver react to post owner
//printf( "TODO: generate outbox activity for adding reaction '%s' to status #%d by account #%d, deliver to account #%d\n", react, s->id, a->id, s->account_id );
activity_react ( s , react ) ;
}
}
if ( ! s - > remote & & a - > id ! = owner_account_id ) { //&& s->account_id == owner_account_id && a->id != owner_account_id ) {
if ( s - > id ! = 0 ) {
// Create notification for liking the owner's post
struct notification * note = notification_new ( ) ;
note - > debug = 1 ;
note - > type = nt_react ;
note - > status_id = s - > id ;
note - > account_id = a - > id ;
note - > react = strdup ( react ) ;
note - > created_at = time ( NULL ) ;
notification_save ( note ) ;
notification_free ( note ) ;
}
}
struct status_react * re = NULL ;
for ( int i = 0 ; i < s - > reacts . count ; + + i ) {
if ( 0 = = strcmp ( s - > reacts . items [ i ] - > code , react ) ) {
re = s - > reacts . items [ i ] ;
goto update_entry ;
}
}
re = malloc ( sizeof ( * re ) ) ;
memset ( re , 0 , sizeof ( * re ) ) ;
re - > code = strdup ( react ) ;
array_append ( & s - > reacts , sizeof ( re ) , & re ) ;
update_entry :
for ( int i = 0 ; i < re - > accounts . count ; + + i ) {
if ( re - > accounts . items [ i ] = = a - > id ) {
// Already present
goto done ;
}
}
array_append ( & re - > accounts , sizeof ( a - > id ) , & a - > id ) ;
done :
status_save ( s ) ;
}
void status_remove_react ( struct status * s , const char * react , struct account * a )
{
// TODO: generate outbox element
printf ( " TODO: generate outbox activity for removing reaction '%s' to status #%d by account #%d \n " , react , s - > id , a - > id ) ;
/// Find react activity we need to Undo
/// Generate Undo Activity
// TODO: generate notification
/*
if ( s - > account_id = = owner_account_id & & a - > id ! = owner_account_id ) {
// Create notification for liking the owner's post
struct notification * note = notification_new ( ) ;
note - > debug = 2 ;
note - > type = nt_react ;
note - > account_id = a - > id ;
note - > react = strdup ( react ) ;
note - > created_at = time ( NULL ) ;
notification_save ( note ) ;
notification_free ( note ) ;
} */
struct status_react * re = NULL ;
for ( int i = 0 ; i < s - > reacts . count ; + + i ) {
if ( 0 = = strcmp ( s - > reacts . items [ i ] - > code , react ) ) {
re = s - > reacts . items [ i ] ;
goto update_entry ;
}
}
return ;
update_entry :
array_delete ( & re - > accounts , sizeof ( a - > id ) , & a - > id ) ;
if ( re - > accounts . count = = 0 ) {
array_delete ( & s - > reacts , sizeof ( re ) , & re ) ;
status_react_free ( re ) ;
}
status_save ( s ) ;
}
void status_add_like ( struct status * s , struct account * a )
{
for ( int i = 0 ; i < s - > likes . count ; + + i ) {
if ( s - > likes . items [ i ] = = a - > id ) {
return ;
}
}
int id = a - > id ;
array_append ( & s - > likes , sizeof ( id ) , & id ) ;
if ( ! s - > stub ) {
if ( s - > account_id = = owner_account_id ) {
if ( a - > id ! = owner_account_id ) {
// Create notification for liking the owner's post
struct notification * note = notification_new ( ) ;
note - > debug = 3 ;
note - > type = nt_like ;
note - > account_id = a - > id ;
note - > status_id = s - > id ;
note - > created_at = time ( NULL ) ;
notification_save ( note ) ;
notification_free ( note ) ;
}
}
if ( a - > id = = owner_account_id ) {
activity_like ( s ) ;
}
}
status_save ( s ) ;
}
void status_remove_like ( struct status * s , struct account * a )
{
for ( int i = 0 ; i < s - > likes . count ; + + i ) {
if ( s - > likes . items [ i ] = = a - > id ) {
// Swap with last element
s - > likes . items [ i ] = s - > likes . items [ s - > likes . count - 1 ] ;
// Then discard the last element
s - > likes . count - = 1 ;
break ;
}
}
if ( ! s - > stub ) {
if ( a - > id = = owner_account_id ) {
// TODO: undo activity
// activity_like( s );
}
}
status_save ( s ) ;
}
void status_set_bookmark ( struct status * s )
{
s - > bookmarked = true ;
mkdir_p ( " data/bookmarks " , 0755 ) ;
char key [ 32 ] ;
ffdb_trie_set ( " data/owner/bookmarks " , format ( key , 32 , " %d " , s - > id ) , " T " ) ;
status_save ( s ) ;
}
void status_clear_bookmark ( struct status * s )
{
s - > bookmarked = false ;
char key [ 32 ] ;
ffdb_trie_set ( " data/owner/bookmarks " , format ( key , 32 , " %d " , s - > id ) , NULL ) ;
status_save ( s ) ;
}
void status_get_bookmarks ( int offset , int limit , void * results_ptr )
{
struct {
char * * items ;
int count ;
} keys ;
memset ( & keys , 0 , sizeof ( keys ) ) ;
struct {
struct status * * items ;
int count ;
} * results = results_ptr ;
results - > count = 0 ;
ffdb_trie_list ( " data/owner/bookmarks " , offset , limit , & keys , NULL ) ;
for ( int i = 0 ; i < keys . count ; + + i ) {
int id ;
if ( 1 = = sscanf ( keys . items [ i ] , " %d " , & id ) ) {
struct status * s = status_from_id ( id ) ;
if ( s ) {
array_append ( results_ptr , sizeof ( s ) , & s ) ;
}
}
free ( keys . items [ i ] ) ;
}
free ( keys . items ) ;
}