1+ use core:: cell:: RefCell ;
2+ use core:: ffi:: { c_int, c_void} ;
3+
4+ use alloc:: boxed:: Box ;
5+ use alloc:: rc:: Rc ;
6+ use alloc:: string:: ToString ;
17use alloc:: { string:: String , vec:: Vec } ;
28use serde:: Serialize ;
9+ use sqlite:: { ResultCode , Value } ;
10+ use sqlite_nostd:: { self as sqlite, ColumnType } ;
11+ use sqlite_nostd:: { Connection , Context } ;
12+
13+ use crate :: error:: SQLiteError ;
14+ use crate :: util:: context_set_error;
15+
16+ use super :: streaming_sync:: SyncClient ;
17+ use super :: sync_status:: DownloadSyncStatus ;
18+
19+ pub enum SyncControlRequest < ' a > {
20+ StartSyncStream {
21+ parameters : Option < serde_json:: Map < String , serde_json:: Value > > ,
22+ } ,
23+ StopSyncStream ,
24+ SyncEvent ( SyncEvent < ' a > ) ,
25+ }
326
427pub enum SyncEvent < ' a > {
5- StartSyncStream ,
6- SyncStreamClosed { error : bool } ,
28+ Initialize ,
29+ TearDown ,
730 TextLine { data : & ' a str } ,
831 BinaryLine { data : & ' a [ u8 ] } ,
932}
1033
1134/// An instruction sent by the core extension to the SDK.
1235#[ derive( Serialize ) ]
1336pub enum Instruction {
14- LogLine { severity : LogSeverity , line : String } ,
15- EstablishSyncStream { request : StreamingSyncRequest } ,
37+ LogLine {
38+ severity : LogSeverity ,
39+ line : String ,
40+ } ,
41+ UpdateSyncStatus {
42+ status : Rc < RefCell < DownloadSyncStatus > > ,
43+ } ,
44+ EstablishSyncStream {
45+ request : StreamingSyncRequest ,
46+ } ,
47+ FlushFileSystem ,
1648 CloseSyncStream ,
1749}
1850
@@ -37,3 +69,81 @@ pub struct BucketRequest {
3769 pub name : String ,
3870 pub after : String ,
3971}
72+
73+ struct SqlController {
74+ client : SyncClient ,
75+ }
76+
77+ pub fn register ( db : * mut sqlite:: sqlite3 ) -> Result < ( ) , ResultCode > {
78+ extern "C" fn control (
79+ ctx : * mut sqlite:: context ,
80+ argc : c_int ,
81+ argv : * mut * mut sqlite:: value ,
82+ ) -> ( ) {
83+ let result = ( || -> Result < ( ) , SQLiteError > {
84+ let controller = unsafe { ctx. user_data ( ) . cast :: < SqlController > ( ) . as_ref ( ) }
85+ . ok_or_else ( || SQLiteError :: from ( ResultCode :: INTERNAL ) ) ?;
86+
87+ let args = sqlite:: args!( argc, argv) ;
88+ let [ op, payload] = args else {
89+ return Err ( ResultCode :: MISUSE . into ( ) ) ;
90+ } ;
91+
92+ if op. value_type ( ) != ColumnType :: Text {
93+ return Err ( SQLiteError (
94+ ResultCode :: MISUSE ,
95+ Some ( "First argument must be a string" . to_string ( ) ) ,
96+ ) ) ;
97+ }
98+
99+ let op = op. text ( ) ;
100+ let event = match op {
101+ "start" => SyncControlRequest :: StartSyncStream {
102+ parameters : if payload. value_type ( ) == ColumnType :: Text {
103+ Some ( serde_json:: from_str ( payload. text ( ) ) ?)
104+ } else {
105+ None
106+ } ,
107+ } ,
108+ "stop" => SyncControlRequest :: StopSyncStream ,
109+ _ => {
110+ return Err ( SQLiteError (
111+ ResultCode :: MISUSE ,
112+ Some ( "Unknown operation" . to_string ( ) ) ,
113+ ) )
114+ }
115+ } ;
116+
117+ let instructions = controller. client . push_event ( event) ?;
118+ let formatted = serde_json:: to_string ( & instructions) ?;
119+ ctx. result_text_transient ( & formatted) ;
120+
121+ Ok ( ( ) )
122+ } ) ( ) ;
123+
124+ if let Err ( e) = result {
125+ context_set_error ( ctx, e, "powersync_control" ) ;
126+ }
127+ }
128+
129+ unsafe extern "C" fn destroy ( ptr : * mut c_void ) {
130+ drop ( Box :: from_raw ( ptr. cast :: < SqlController > ( ) ) ) ;
131+ }
132+
133+ let controller = Box :: new ( SqlController {
134+ client : SyncClient :: new ( db) ,
135+ } ) ;
136+
137+ db. create_function_v2 (
138+ "powersync_control" ,
139+ 2 ,
140+ sqlite:: UTF8 | sqlite:: DIRECTONLY ,
141+ Some ( Box :: into_raw ( controller) . cast ( ) ) ,
142+ Some ( control) ,
143+ None ,
144+ None ,
145+ Some ( destroy) ,
146+ ) ?;
147+
148+ Ok ( ( ) )
149+ }
0 commit comments