@ -838,7 +838,7 @@ static int md_auto_download(struct UTUN_INSTANCE* inst,
/* ─── db_sync callback ─── */
/* ─── db_sync callback ─── */
void on_msg_inserted ( struct DB_SYNC_INSTANCE * si , uint64_t record_ts , const char * data , size_t len , uint64_t author , void * arg ) {
void on_msg_inserted ( struct DB_SYNC_INSTANCE * si , uint64_t record_ts , const char * data , size_t len , uint64_t author , int initial_sync , void * arg ) {
( void ) si ;
( void ) si ;
struct msg_insert_arg * ia = ( struct msg_insert_arg * ) arg ;
struct msg_insert_arg * ia = ( struct msg_insert_arg * ) arg ;
struct UTUN_INSTANCE * inst = ia ? ia - > inst : NULL ;
struct UTUN_INSTANCE * inst = ia ? ia - > inst : NULL ;
@ -849,8 +849,14 @@ void on_msg_inserted(struct DB_SYNC_INSTANCE* si, uint64_t record_ts, const char
memcpy ( evt + 1 + cl , & author , 8 ) ;
memcpy ( evt + 1 + cl , & author , 8 ) ;
chat_event_post ( inst , CHAT_EVT_MSG_RECEIVED , evt , 1 + cl + 8 ) ;
chat_event_post ( inst , CHAT_EVT_MSG_RECEIVED , evt , 1 + cl + 8 ) ;
/* auto-download media if message contains media metadata */
/* auto-download media if message contains media metadata (live PUSH/local only).
if ( data & & len > 0 & & author ! = cc - > my_node_id ) {
initial - sync ( SEND_DATA ) media д о к а ч и в а е т с я п о с л е sync ч е р е з per - channel backfill . */
if ( initial_sync & & data & & len > 0 & & author ! = cc - > my_node_id ) {
char dbg [ 4096 ] ; size_t dbgl = len < sizeof ( dbg ) - 1 ? len : sizeof ( dbg ) - 1 ;
memcpy ( dbg , data , dbgl ) ; dbg [ dbgl ] = ' \0 ' ;
if ( strstr ( dbg , " \" ct \" : \" " ) & & strstr ( dbg , " \" d \" : \" " ) )
DEBUG_DEBUG ( DEBUG_CATEGORY_MEDIA , " %s: media download deferred (initial sync) ch=%s " , CC_ID , ch_id ) ;
} else if ( data & & len > 0 & & author ! = cc - > my_node_id ) {
/* data from db_sync may not be null-terminated; copy to local buffer */
/* data from db_sync may not be null-terminated; copy to local buffer */
char buf [ 4096 ] ; size_t blen = len < sizeof ( buf ) - 1 ? len : sizeof ( buf ) - 1 ;
char buf [ 4096 ] ; size_t blen = len < sizeof ( buf ) - 1 ? len : sizeof ( buf ) - 1 ;
memcpy ( buf , data , blen ) ; buf [ blen ] = ' \0 ' ;
memcpy ( buf , data , blen ) ; buf [ blen ] = ' \0 ' ;
@ -1114,14 +1120,15 @@ static int bf_cmp_ts_desc(const void* a, const void* b) {
return 0 ;
return 0 ;
}
}
/* Собирает все медиасообщения всех каналов (presence + размеры + контекст докачки). */
/* Собирает медиасообщения (presence + размеры + контекст докачки). only_ch_id != NULL — только этот канал . */
static void bf_collect ( struct chat_core_ctx * cc , const char * media_base , struct bf_list * l ) {
static void bf_collect ( struct chat_core_ctx * cc , const char * media_base , const char * only_ch_id , struct bf_list * l ) {
sqlite3_stmt * cs = NULL ;
sqlite3_stmt * cs = NULL ;
if ( sqlite3_prepare_v2 ( cc - > db , " SELECT channel_id FROM channels " , - 1 , & cs , NULL ) ! = SQLITE_OK ) return ;
if ( sqlite3_prepare_v2 ( cc - > db , " SELECT channel_id FROM channels " , - 1 , & cs , NULL ) ! = SQLITE_OK ) return ;
while ( sqlite3_step ( cs ) = = SQLITE_ROW ) {
while ( sqlite3_step ( cs ) = = SQLITE_ROW ) {
const char * ch_id = ( const char * ) sqlite3_column_text ( cs , 0 ) ;
const char * ch_id = ( const char * ) sqlite3_column_text ( cs , 0 ) ;
if ( ! ch_id | | ! ch_id [ 0 ] ) continue ;
if ( ! ch_id | | ! ch_id [ 0 ] ) continue ;
if ( only_ch_id & & strcmp ( only_ch_id , ch_id ) ! = 0 ) continue ;
char tbl [ 80 ] ; msg_table_name ( ch_id , tbl , sizeof ( tbl ) ) ;
char tbl [ 80 ] ; msg_table_name ( ch_id , tbl , sizeof ( tbl ) ) ;
char sql [ 256 ] ;
char sql [ 256 ] ;
@ -1221,7 +1228,7 @@ void chat_media_autodownload_backfill(struct UTUN_INSTANCE* inst) {
else snprintf ( media_base , sizeof ( media_base ) , " %s " , cc - > db_path ) ;
else snprintf ( media_base , sizeof ( media_base ) , " %s " , cc - > db_path ) ;
struct bf_list list = { 0 } ;
struct bf_list list = { 0 } ;
bf_collect ( cc , media_base , & list ) ;
bf_collect ( cc , media_base , NULL , & list ) ;
if ( list . count > 1 ) qsort ( list . items , ( size_t ) list . count , sizeof ( struct bf_entry ) , bf_cmp_ts_desc ) ;
if ( list . count > 1 ) qsort ( list . items , ( size_t ) list . count , sizeof ( struct bf_entry ) , bf_cmp_ts_desc ) ;
/* инициализация счётчика: сумма всех present-файлов */
/* инициализация счётчика: сумма всех present-файлов */
@ -1273,6 +1280,51 @@ void chat_media_autodownload_backfill(struct UTUN_INSTANCE* inst) {
chat_media_cache_reconcile ( inst ) ;
chat_media_cache_reconcile ( inst ) ;
}
}
/* Докачка медиа одного канала после завершения его chat_sync.
П о р я д о к — о т п о с л е д н и х с о о б щ е н и й н а з а д ( с п и с о к с о р т и р у е т с я desc п о ts ) . */
void chat_media_autodownload_backfill_channel ( struct UTUN_INSTANCE * inst , const char * ch_id ) {
struct chat_core_ctx * cc = CC ( inst ) ;
if ( ! cc | | ! cc - > initialized | | ! cc - > db | | ! inst | | ! ch_id | | ! ch_id [ 0 ] ) return ;
uint64_t limit = inst - > config - > global . chatserver_storage_total_size ;
int autoload = chat_setting_get_int ( inst , " storage_autoload " , 1 ) ;
int days = chat_setting_get_int ( inst , " storage_backfill_days " , 7 ) ;
uint64_t now_ms = ( uint64_t ) ntp_time_get_seconds ( inst ) * 1000ULL ;
uint64_t cutoff_ms = now_ms - ( uint64_t ) days * 86400ULL * 1000ULL ;
char media_base [ 512 ] ;
const char * last_slash = strrchr ( cc - > db_path , ' / ' ) ;
if ( last_slash ) snprintf ( media_base , sizeof ( media_base ) , " %.*s " , ( int ) ( last_slash - cc - > db_path ) , cc - > db_path ) ;
else snprintf ( media_base , sizeof ( media_base ) , " %s " , cc - > db_path ) ;
struct bf_list list = { 0 } ;
bf_collect ( cc , media_base , ch_id , & list ) ;
if ( list . count > 1 ) qsort ( list . items , ( size_t ) list . count , sizeof ( struct bf_entry ) , bf_cmp_ts_desc ) ;
uint64_t used = cc - > media_cache_bytes ;
int downloaded = 0 , skipped = 0 ;
for ( int i = 0 ; i < list . count ; i + + ) {
struct bf_entry * e = & list . items [ i ] ;
if ( e - > actual_size > 0 ) continue ;
if ( ! autoload | | e - > author = = cc - > my_node_id ) { skipped + + ; continue ; }
if ( e - > ts_ms < cutoff_ms ) { skipped + + ; continue ; }
if ( limit > 0 & & used + ( uint64_t ) e - > claimed_size > limit ) { skipped + + ; continue ; }
if ( md_auto_download ( inst , e - > body , e - > body_len , e - > ch_id , e - > base_filename ,
e - > ts_ms , e - > author_sig , e - > author , e - > msg_id , e - > content_type ) ) {
downloaded + + ;
used + = ( uint64_t ) e - > claimed_size ;
} else {
skipped + + ;
}
}
DEBUG_INFO ( DEBUG_CATEGORY_GENERAL , " %s: channel backfill done ch=%s downloaded=%d skipped=%d " ,
CC_ID , ch_id , downloaded , skipped ) ;
for ( int i = 0 ; i < list . count ; i + + ) if ( list . items [ i ] . body ) u_free ( list . items [ i ] . body ) ;
u_free ( list . items ) ;
}
/* ─── Кеш медиафайлов: очистка сирот + лимит storage_total_size ─── */
/* ─── Кеш медиафайлов: очистка сирот + лимит storage_total_size ─── */
struct media_msg_file {
struct media_msg_file {
@ -1559,22 +1611,57 @@ static void media_startup_backfill_done_cb(struct DB_SYNC_INSTANCE* si, uint64_t
media_startup_backfill_timer_cb , " chat_media_bl " ) ;
media_startup_backfill_timer_cb , " chat_media_bl " ) ;
}
}
/* per-channel: после SYNC_DONE канала — докачка его медиа (дебаунс 2s, от новых к старым) */
static void media_channel_backfill_timer_cb ( void * arg ) {
struct UTUN_INSTANCE * inst = ( struct UTUN_INSTANCE * ) arg ;
struct chat_core_ctx * cc = CC ( inst ) ;
cc - > media_channel_backfill_timer = NULL ;
for ( int i = 0 ; i < cc - > media_channel_backfill_pending ; i + + )
chat_media_autodownload_backfill_channel ( inst , cc - > media_channel_backfill [ i ] ) ;
cc - > media_channel_backfill_pending = 0 ;
}
static void media_channel_backfill_done_cb ( struct DB_SYNC_INSTANCE * si , uint64_t peer_node_id , void * arg ) {
struct UTUN_INSTANCE * inst = ( struct UTUN_INSTANCE * ) arg ;
struct chat_core_ctx * cc = CC ( inst ) ;
( void ) peer_node_id ;
if ( ! cc | | ! si ) return ;
char ch_id [ 32 ] ; snprintf ( ch_id , sizeof ( ch_id ) , " %llu " , ( unsigned long long ) db_sync_instance_group_id ( si ) ) ;
for ( int i = 0 ; i < cc - > media_channel_backfill_pending ; i + + )
if ( strcmp ( cc - > media_channel_backfill [ i ] , ch_id ) = = 0 ) return ;
if ( cc - > media_channel_backfill_pending < ( int ) ( sizeof ( cc - > media_channel_backfill ) / sizeof ( cc - > media_channel_backfill [ 0 ] ) ) )
snprintf ( cc - > media_channel_backfill [ cc - > media_channel_backfill_pending + + ] ,
sizeof ( cc - > media_channel_backfill [ 0 ] ) , " %s " , ch_id ) ;
if ( cc - > media_channel_backfill_timer ) uasync_cancel_timeout ( inst - > ua , cc - > media_channel_backfill_timer ) ;
cc - > media_channel_backfill_timer = uasync_set_timeout ( inst - > ua , 20000 , inst ,
media_channel_backfill_timer_cb , " chat_md_chbl " ) ;
}
void chat_media_startup_backfill_init ( struct UTUN_INSTANCE * inst ) {
void chat_media_startup_backfill_init ( struct UTUN_INSTANCE * inst ) {
struct chat_core_ctx * cc = CC ( inst ) ;
struct chat_core_ctx * cc = CC ( inst ) ;
if ( ! inst | | ! cc ) return ;
if ( ! inst | | ! cc ) return ;
cc - > media_backfill_inst = inst ;
cc - > media_backfill_inst = inst ;
cc - > media_backfill_ran = 0 ;
cc - > media_backfill_ran = 0 ;
cc - > media_channel_backfill_pending = 0 ;
cc - > media_channel_backfill_timer = NULL ;
db_sync_add_done_cbk ( inst , media_startup_backfill_done_cb , inst ) ;
db_sync_add_done_cbk ( inst , media_startup_backfill_done_cb , inst ) ;
db_sync_add_done_cbk ( inst , media_channel_backfill_done_cb , inst ) ;
}
}
void chat_media_startup_backfill_destroy ( struct UTUN_INSTANCE * inst ) {
void chat_media_startup_backfill_destroy ( struct UTUN_INSTANCE * inst ) {
struct chat_core_ctx * cc = CC ( inst ) ;
struct chat_core_ctx * cc = CC ( inst ) ;
if ( ! inst | | ! cc ) return ;
if ( ! inst | | ! cc ) return ;
db_sync_remove_done_cbk ( inst , media_startup_backfill_done_cb , inst ) ;
db_sync_remove_done_cbk ( inst , media_startup_backfill_done_cb , inst ) ;
db_sync_remove_done_cbk ( inst , media_channel_backfill_done_cb , inst ) ;
if ( cc - > media_backfill_timer ) {
if ( cc - > media_backfill_timer ) {
uasync_cancel_timeout ( inst - > ua , cc - > media_backfill_timer ) ;
uasync_cancel_timeout ( inst - > ua , cc - > media_backfill_timer ) ;
cc - > media_backfill_timer = NULL ;
cc - > media_backfill_timer = NULL ;
}
}
if ( cc - > media_channel_backfill_timer ) {
uasync_cancel_timeout ( inst - > ua , cc - > media_channel_backfill_timer ) ;
cc - > media_channel_backfill_timer = NULL ;
}
cc - > media_channel_backfill_pending = 0 ;
cc - > media_backfill_inst = NULL ;
cc - > media_backfill_inst = NULL ;
cc - > media_backfill_ran = 0 ;
cc - > media_backfill_ran = 0 ;
}
}