bc2f65c27a53f6213e4200771b375a2732037221
[citadel.git] / citadel / modules / roomchat / serv_roomchat.c
1 /*
2  * This module handles instant messaging between users.
3  * 
4  * Copyright (c) 2012 by the citadel.org team
5  *
6  * This program is open source software; you can redistribute it and/or modify
7  * it under the terms of the GNU General Public License version 3.
8  *
9  * This program is distributed in the hope that it will be useful,
10  * but WITHOUT ANY WARRANTY; without even the implied warranty of
11  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
12  * GNU General Public License for more details.
13  */
14 #include "sysdep.h"
15 #include <stdlib.h>
16 #include <unistd.h>
17 #include <stdio.h>
18 #include <fcntl.h>
19 #include <signal.h>
20 #include <pwd.h>
21 #include <errno.h>
22 #include <sys/types.h>
23 #include <assert.h>
24
25 #if TIME_WITH_SYS_TIME
26 # include <sys/time.h>
27 # include <time.h>
28 #else
29 # if HAVE_SYS_TIME_H
30 #  include <sys/time.h>
31 # else
32 #  include <time.h>
33 # endif
34 #endif
35
36 #include <sys/wait.h>
37 #include <string.h>
38 #include <limits.h>
39 #include <libcitadel.h>
40 #include "citadel.h"
41 #include "server.h"
42 #include "citserver.h"
43 #include "support.h"
44 #include "config.h"
45 #include "msgbase.h"
46 #include "user_ops.h"
47 #include "ctdl_module.h"
48
49 struct chatmsg {
50         struct chatmsg *next;
51         time_t timestamp;
52         int seq;
53         long roomnum;
54         char *sender;
55         char *msgtext;
56 };
57
58 struct chatmsg *first_chat_msg = NULL;
59 struct chatmsg *last_chat_msg = NULL;
60
61
62 /* 
63  * Periodically called for housekeeping.  Expire old chat messages so they don't take up memory forever.
64  */
65 void roomchat_timer(void) {
66         struct chatmsg *ptr;
67
68         begin_critical_section(S_CHATQUEUE);
69
70         while ((first_chat_msg != NULL) && ((time(NULL) - first_chat_msg->timestamp) > 300)) {
71                 ptr = first_chat_msg->next;
72                 free(first_chat_msg->sender);
73                 free(first_chat_msg->msgtext);
74                 free(first_chat_msg);
75                 first_chat_msg = ptr;
76                 if (first_chat_msg == NULL) {
77                         last_chat_msg = NULL;
78                 }
79         }
80
81         end_critical_section(S_CHATQUEUE);
82 }
83
84
85 /*
86  * Perform shutdown-related activities...
87  */
88 void roomchat_shutdown(void) {
89         /* if we ever start logging chats, we have to flush them to disk here .*/
90 }
91
92
93 /*
94  * Add a message into the chat queue
95  */
96 void add_to_chat_queue(char *msg) {
97         static int seq = 0;
98
99         struct chatmsg *m = malloc(sizeof(struct chatmsg));
100         if (!m) return;
101
102         m->next = NULL;
103         m->timestamp = time(NULL);
104         m->roomnum = CC->room.QRnumber;
105         m->sender = strdup(CC->user.fullname);
106         m->msgtext = strdup(msg);
107
108         if ((m->sender == NULL) || (m->msgtext == NULL)) {
109                 free(m->sender);
110                 free(m->msgtext);
111                 free(m);
112                 return;
113         }
114
115         begin_critical_section(S_CHATQUEUE);
116         m->seq = ++seq;
117
118         if (first_chat_msg == NULL) {
119                 assert(last_chat_msg == NULL);
120                 first_chat_msg = m;
121                 last_chat_msg = m;
122         }
123         else {
124                 assert(last_chat_msg != NULL);
125                 assert(last_chat_msg->next == NULL);
126                 last_chat_msg->next = m;
127                 last_chat_msg = m;
128         }
129
130         end_critical_section(S_CHATQUEUE);
131 }
132
133
134 /*
135  * Transmit a message into a room chat
136  */
137 void roomchat_send(char *argbuf) {
138         char buf[1024];
139
140         if ((CC->cs_flags & CS_CHAT) == 0) {
141                 cprintf("%d Session is not in chat mode.\n", ERROR);
142                 return;
143         }
144
145         cprintf("%d send now\n", SEND_LISTING);
146         while (client_getln(buf, sizeof buf) >= 0 && strcmp(buf, "000")) {
147                 add_to_chat_queue(buf);
148         }
149 }
150
151
152 /*
153  * Poll room for incoming chat messages
154  */
155 void roomchat_poll(char *argbuf) {
156         int newer_than = 0;
157         struct chatmsg *found = NULL;
158         struct chatmsg *ptr = NULL;
159
160         newer_than = extract_int(argbuf, 1);
161
162         if ((CC->cs_flags & CS_CHAT) == 0) {
163                 cprintf("%d Session is not in chat mode.\n", ERROR);
164                 return;
165         }
166
167         begin_critical_section(S_CHATQUEUE);
168         for (ptr = first_chat_msg; ((ptr != NULL) && (found == NULL)); ptr = ptr->next) {
169                 if ((ptr->seq > newer_than) && (ptr->roomnum == CC->room.QRnumber)) {
170                         found = ptr;
171                 }
172         }
173         end_critical_section(S_CHATQUEUE);
174
175         if (found == NULL) {
176                 cprintf("%d no messages\n", ERROR + MESSAGE_NOT_FOUND);
177                 return;
178         }
179
180         cprintf("%d %d|%ld|%s\n", LISTING_FOLLOWS, found->seq, found->timestamp, found->sender);
181         cprintf("%s\n", found->msgtext);
182         cprintf("000\n");
183 }
184
185
186
187 /*
188  * list users in chat in this room
189  */
190 void roomchat_rwho(char *argbuf) {
191         struct CitContext *nptr;
192         int nContexts, i;
193
194         if ((CC->cs_flags & CS_CHAT) == 0) {
195                 cprintf("%d Session is not in chat mode.\n", ERROR);
196                 return;
197         }
198
199         cprintf("%d%c \n", LISTING_FOLLOWS, CtdlCheckExpress() );
200         
201         nptr = CtdlGetContextArray(&nContexts) ;                // grab a copy of the wholist
202         if (nptr) {
203                 for (i=0; i<nContexts; i++)  {                  // list the users
204                         if ( (nptr[i].room.QRnumber == CC->room.QRnumber) 
205                            && (nptr[i].cs_flags & CS_CHAT)
206                         ) {
207                                 cprintf("%s\n", nptr[i].user.fullname);
208                         }
209                 }
210                 free(nptr);                                     // free our copy
211         }
212
213         cprintf("000\n");
214 }
215
216
217
218 /*
219  * Participate in real time chat in a room
220  */
221 void cmd_rcht(char *argbuf)
222 {
223         char subcmd[16];
224
225         if (CtdlAccessCheck(ac_logged_in)) return;
226
227         extract_token(subcmd, argbuf, 0, '|', sizeof subcmd);
228
229         if (!strcasecmp(subcmd, "enter")) {
230                 CC->cs_flags |= CS_CHAT;
231                 cprintf("%d Entering chat mode.\n", CIT_OK);
232         }
233         else if (!strcasecmp(subcmd, "exit")) {
234                 CC->cs_flags &= ~CS_CHAT;
235                 cprintf("%d Exiting chat mode.\n", CIT_OK);
236         }
237         else if (!strcasecmp(subcmd, "send")) {
238                 roomchat_send(argbuf);
239         }
240         else if (!strcasecmp(subcmd, "poll")) {
241                 roomchat_poll(argbuf);
242         }
243         else if (!strcasecmp(subcmd, "rwho")) {
244                 roomchat_rwho(argbuf);
245         }
246         else {
247                 cprintf("%d Invalid subcommand\n", ERROR + CMD_NOT_SUPPORTED);
248         }
249 }
250
251
252 CTDL_MODULE_INIT(roomchat)
253 {
254         if (!threading)
255         {
256                 CtdlRegisterProtoHook(cmd_rcht, "RCHT", "Participate in real time chat in a room");
257                 CtdlRegisterSessionHook(roomchat_timer, EVT_TIMER, PRIO_CLEANUP + 400);
258                 CtdlRegisterSessionHook(roomchat_shutdown, EVT_SHUTDOWN, PRIO_SHUTDOWN + 55);
259         }
260         
261         /* return our module name for the log */
262         return "roomchat";
263 }