55using SpacetimeDB ;
66using SpacetimeDB . Types ;
77
8- const string HOST = "http://localhost:3000" ;
9- const string DBNAME = "chatqs" ;
108
119// our local client SpacetimeDB identity
1210Identity ? local_identity = null ;
11+
1312// declare a thread safe queue to store commands
1413var input_queue = new ConcurrentQueue < ( string Command , string Args ) > ( ) ;
1514
1615void Main ( )
1716{
17+ // Initialize the `AuthToken` module
1818 AuthToken . Init ( ".spacetime_csharp_quickstart" ) ;
19-
20- // TODO: just do `var conn = DbConnection...` when OnConnect signature is fixed.
19+ // Builds and connects to the database
2120 DbConnection ? conn = null ;
21+ conn = ConnectToDB ( ) ;
22+ // Registers callbacks to run in response to database events.
23+ RegisterCallbacks ( conn ) ;
24+ // Declare a threadsafe cancel token to cancel the process loop
25+ var cancellationTokenSource = new CancellationTokenSource ( ) ;
26+ // Spawn a thread to call process updates and process commands
27+ var thread = new Thread ( ( ) => ProcessThread ( conn , cancellationTokenSource . Token ) ) ;
28+ thread . Start ( ) ;
29+ // Handles CLI input
30+ InputLoop ( ) ;
31+ // This signals the ProcessThread to stop
32+ cancellationTokenSource . Cancel ( ) ;
33+ thread . Join ( ) ;
34+ }
35+
36+ /// The URI of the SpacetimeDB instance hosting our chat module.
37+ const string HOST = "http://localhost:3000" ;
38+
39+ /// The module name we chose when we published our module.
40+ const string DBNAME = "quickstart-chat" ;
2241
42+ /// Load credentials from a file and connect to the database.
43+ DbConnection ConnectToDB ( )
44+ {
45+ DbConnection ? conn = null ;
2346 conn = DbConnection . Builder ( )
2447 . WithUri ( HOST )
2548 . WithModuleName ( DBNAME )
26- // .WithToken(AuthToken.Token)
27- . OnConnect ( OnConnect )
49+ . WithToken ( AuthToken . Token )
50+ . OnConnect ( OnConnected )
2851 . OnConnectError ( OnConnectError )
29- . OnDisconnect ( OnDisconnect )
52+ . OnDisconnect ( OnDisconnected )
3053 . Build ( ) ;
54+ return conn ;
55+ }
3156
57+ /// Our `OnConnect` callback: save our credentials to a file.
58+ void OnConnected ( DbConnection conn , Identity identity , string authToken )
59+ {
60+ local_identity = identity ;
61+ AuthToken . SaveToken ( authToken ) ;
62+
63+ conn . SubscriptionBuilder ( )
64+ . OnApplied ( OnSubscriptionApplied )
65+ . SubscribeToAllTables ( ) ;
66+ }
67+
68+ /// Our `OnConnectError` callback: print the error, then exit the process.
69+ void OnConnectError ( Exception e )
70+ {
71+ Console . Write ( $ "Error while connecting: { e } ") ;
72+ }
73+
74+ /// Our `OnDisconnect` callback: print a note, then exit the process.
75+ void OnDisconnected ( DbConnection conn , Exception ? e )
76+ {
77+ if ( e != null )
78+ {
79+ Console . Write ( $ "Disconnected abnormally: { e } ") ;
80+ }
81+ else
82+ {
83+ Console . Write ( $ "Disconnected normally.") ;
84+ }
85+ }
86+
87+ /// Register all the callbacks our app will use to respond to database events.
88+ void RegisterCallbacks ( DbConnection conn )
89+ {
3290 conn . Db . User . OnInsert += User_OnInsert ;
3391 conn . Db . User . OnUpdate += User_OnUpdate ;
3492
3593 conn . Db . Message . OnInsert += Message_OnInsert ;
3694
3795 conn . Reducers . OnSetName += Reducer_OnSetNameEvent ;
3896 conn . Reducers . OnSendMessage += Reducer_OnSendMessageEvent ;
39-
40- // declare a threadsafe cancel token to cancel the process loop
41- var cancellationTokenSource = new CancellationTokenSource ( ) ;
42-
43- // spawn a thread to call process updates and process commands
44- var thread = new Thread ( ( ) => ProcessThread ( conn , cancellationTokenSource . Token ) ) ;
45- thread . Start ( ) ;
46-
47- InputLoop ( ) ;
48-
49- // this signals the ProcessThread to stop
50- cancellationTokenSource . Cancel ( ) ;
51- thread . Join ( ) ;
5297}
5398
99+ /// If the user has no set name, use the first 8 characters from their identity.
54100string UserNameOrIdentity ( User user ) => user . Name ?? user . Identity . ToString ( ) [ ..8 ] ;
55101
102+ /// Our `User.OnInsert` callback: if the user is online, print a notification.
56103void User_OnInsert ( EventContext ctx , User insertedValue )
57104{
58105 if ( insertedValue . Online )
@@ -61,6 +108,8 @@ void User_OnInsert(EventContext ctx, User insertedValue)
61108 }
62109}
63110
111+ /// Our `User.OnUpdate` callback:
112+ /// print a notification about name and status changes.
64113void User_OnUpdate ( EventContext ctx , User oldValue , User newValue )
65114{
66115 if ( oldValue . Name != newValue . Name )
@@ -80,6 +129,18 @@ void User_OnUpdate(EventContext ctx, User oldValue, User newValue)
80129 }
81130}
82131
132+ /// Our `Message.OnInsert` callback: print new messages.
133+ void Message_OnInsert ( EventContext ctx , Message insertedValue )
134+ {
135+ // We are filtering out messages inserted during the subscription being applied,
136+ // since we will be printing those in the OnSubscriptionApplied callback,
137+ // where we will be able to first sort the messages before printing.
138+ if ( ctx . Event is not Event < Reducer > . SubscribeApplied )
139+ {
140+ PrintMessage ( ctx . Db , insertedValue ) ;
141+ }
142+ }
143+
83144void PrintMessage ( RemoteTables tables , Message message )
84145{
85146 var sender = tables . User . Identity . Find ( message . Sender ) ;
@@ -92,15 +153,7 @@ void PrintMessage(RemoteTables tables, Message message)
92153 Console . WriteLine ( $ "{ senderName } : { message . Text } ") ;
93154}
94155
95- void Message_OnInsert ( EventContext ctx , Message insertedValue )
96- {
97-
98- if ( ctx . Event is not Event < Reducer > . SubscribeApplied )
99- {
100- PrintMessage ( ctx . Db , insertedValue ) ;
101- }
102- }
103-
156+ /// Our `OnSetNameEvent` callback: print a warning if the reducer failed.
104157void Reducer_OnSetNameEvent ( ReducerEventContext ctx , string name )
105158{
106159 var e = ctx . Event ;
@@ -110,6 +163,7 @@ void Reducer_OnSetNameEvent(ReducerEventContext ctx, string name)
110163 }
111164}
112165
166+ /// Our `OnSendMessageEvent` callback: print a warning if the reducer failed.
113167void Reducer_OnSendMessageEvent ( ReducerEventContext ctx , string text )
114168{
115169 var e = ctx . Event ;
@@ -119,34 +173,12 @@ void Reducer_OnSendMessageEvent(ReducerEventContext ctx, string text)
119173 }
120174}
121175
122- void OnConnect ( DbConnection conn , Identity identity , string authToken )
123- {
124- local_identity = identity ;
125- AuthToken . SaveToken ( authToken ) ;
126-
127- var subscription = conn . SubscriptionBuilder ( )
128- . OnApplied ( OnSubscriptionApplied )
129- . Subscribe ( new string [ ] {
130- "SELECT * FROM user" ,
131- "SELECT * FROM message" ,
132- // It is legal to have redundant subscriptions.
133- // However, keep in mind that data will be sent over the wire multiple times,
134- // once for each subscriptions. This can cause slowdowns if you aren't careful.
135- "SELECT * FROM message" } ) ;
136-
137- // You can also use SubscribeToAllTables, but it should be avoided if you have any large tables:
138- // conn.SubscriptionBuilder().OnApplied(OnSubscriptionApplied).SubscribeToAllTables();
139-
140- }
141-
142- void OnConnectError ( Exception e )
143- {
144-
145- }
146-
147- void OnDisconnect ( DbConnection conn , Exception ? e )
176+ /// Our `OnSubscriptionApplied` callback:
177+ /// sort all past messages and print them in timestamp order.
178+ void OnSubscriptionApplied ( SubscriptionEventContext ctx )
148179{
149-
180+ Console . WriteLine ( "Connected" ) ;
181+ PrintMessagesInOrder ( ctx . Db ) ;
150182}
151183
152184void PrintMessagesInOrder ( RemoteTables tables )
@@ -157,12 +189,7 @@ void PrintMessagesInOrder(RemoteTables tables)
157189 }
158190}
159191
160- void OnSubscriptionApplied ( SubscriptionEventContext ctx )
161- {
162- Console . WriteLine ( "Connected" ) ;
163- PrintMessagesInOrder ( ctx . Db ) ;
164- }
165-
192+ /// Our separate thread from main, where we can call process updates and process commands without blocking the main thread.
166193void ProcessThread ( DbConnection conn , CancellationToken ct )
167194{
168195 try
@@ -183,6 +210,7 @@ void ProcessThread(DbConnection conn, CancellationToken ct)
183210 }
184211}
185212
213+ /// Read each line of standard input, and either set our name or send a message as appropriate.
186214void InputLoop ( )
187215{
188216 while ( true )
0 commit comments