Changed 'free software' to 'open source' to piss off Richard Stallman
[citadel.git] / webcit / blogview_renderer.c
1 /* 
2  * Blog view renderer module for WebCit
3  *
4  * Copyright (c) 1996-2010 by the citadel.org team
5  *
6  * This program is open source software.  You can redistribute it and/or
7  * modify it under the terms of the GNU General Public License as
8  * published by the Free Software Foundation; either version 3 of the
9  * License, or (at your option) any later version.
10  *
11  * This program is distributed in the hope that it will be useful,
12  * but WITHOUT ANY WARRANTY; without even the implied warranty of
13  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
14  * GNU General Public License for more details.
15  *
16  * You should have received a copy of the GNU General Public License
17  * along with this program; if not, write to the Free Software
18  * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
19  */
20
21 #include "webcit.h"
22 #include "webserver.h"
23 #include "groupdav.h"
24
25
26 /* 
27  * Array type for a blog post.  The first message is the post; the rest are comments
28  */
29 struct blogpost {
30         int top_level_id;
31         long *msgs;             /* Array of msgnums for messages we are displaying */
32         int num_msgs;           /* Number of msgnums stored in 'msgs' */
33         int alloc_msgs;         /* Currently allocated size of array */
34 };
35
36
37 /*
38  * Destructor for 'struct blogpost' which does the rendering first.
39  * By rendering from here, we eliminate the need for a separate iterator, although
40  * we might run into trouble when we get around to displaying newest-to-oldest...
41  */
42 void blogpost_render_and_destroy(struct blogpost *bp) {
43         const StrBuf *Mime;
44         int p = 0;
45         int i;
46
47         p = atoi(BSTR("p"));    /* are we looking for a specific post? */
48         WC->bptlid = bp->top_level_id;
49
50         if ( ((p == 0) || (p == bp->top_level_id)) && (bp->num_msgs > 0) ) {
51                 /* Show the top level post */
52                 read_message(WC->WBuf, HKEY("view_blog_post"), bp->msgs[0], NULL, &Mime);
53
54                 if (p == 0) {
55                         /* Show the number of comments */
56                         wc_printf("<a href=\"readfwd?p=%d?gotofirst=", bp->top_level_id);
57                         urlescputs(ChrPtr(WC->CurRoom.name));
58                         wc_printf("#comments\">");
59                         wc_printf(_("%d comments"), bp->num_msgs - 1);
60                         wc_printf("</a>");
61                 }
62                 else if (bp->num_msgs < 2) {
63                         wc_printf(_("%d comments"), 0);
64                 }
65                 else {
66                         wc_printf("<a name=\"comments\"></a>\n");
67                         wc_printf(_("%d comments"), bp->num_msgs - 1);
68                         wc_printf("<br>\n");
69                         for (i=1; i<bp->num_msgs; ++i) {
70                                 read_message(WC->WBuf, HKEY("view_blog_comment"), bp->msgs[i], NULL, &Mime);
71                         }
72                 }
73         }
74
75
76         if (bp->alloc_msgs > 0) {
77                 free(bp->msgs);
78         }
79         free(bp);
80 }
81
82
83 /*
84  * Data which gets returned from a call to blogview_learn_thread_references()
85  */
86 struct bltr {
87         int id;
88         int refs;
89 };
90
91
92 /*
93  * Entry point for message read operations.
94  */
95 int blogview_GetParamsGetServerCall(SharedMessageStatus *Stat, 
96                                    void **ViewSpecific, 
97                                    long oper, 
98                                    char *cmd, 
99                                    long len)
100 {
101         HashList *BLOG = NewHash(1, NULL);
102         *ViewSpecific = BLOG;
103
104         Stat->startmsg = (-1);                                  /* not used here */
105         Stat->sortit = 1;                                       /* not used here */
106         Stat->num_displayed = DEFAULT_MAXMSGS;                  /* not used here */
107         if (Stat->maxmsgs == 0) Stat->maxmsgs = DEFAULT_MAXMSGS;
108         
109         /* perform a "read all" call to fetch the message list -- we'll cut it down later */
110         rlid[2].cmd(cmd, len);
111         
112         return 200;
113 }
114
115
116 /*
117  * Given a 'struct blogpost' containing a msgnum, populate the id
118  * and refs fields by fetching them from the Citadel server
119  */
120 struct bltr blogview_learn_thread_references(long msgnum)
121 {
122         StrBuf *Buf;
123         StrBuf *r;
124         struct bltr bltr = { 0, 0 } ;
125         Buf = NewStrBuf();
126         r = NewStrBuf();
127         serv_printf("MSG0 %ld|1", msgnum);              /* top level citadel headers only */
128         StrBuf_ServGetln(Buf);
129         if (GetServerStatus(Buf, NULL) == 1) {
130                 while (StrBuf_ServGetln(Buf), strcmp(ChrPtr(Buf), "000")) {
131                         if (!strncasecmp(ChrPtr(Buf), "msgn=", 5)) {
132                                 StrBufCutLeft(Buf, 5);
133                                 bltr.id = HashLittle(ChrPtr(Buf), StrLength(Buf));
134                         }
135                         else if (!strncasecmp(ChrPtr(Buf), "wefw=", 5)) {
136                                 StrBufCutLeft(Buf, 5);          /* trim the field name */
137                                 StrBufExtract_token(r, Buf, 0, '|');
138                                 bltr.refs = HashLittle(ChrPtr(r), StrLength(r));
139                         }
140                 }
141         }
142         FreeStrBuf(&Buf);
143         FreeStrBuf(&r);
144         return(bltr);
145 }
146
147
148 /*
149  * This function is called for every message in the list.
150  */
151 int blogview_LoadMsgFromServer(SharedMessageStatus *Stat, 
152                               void **ViewSpecific, 
153                               message_summary* Msg, 
154                               int is_new, 
155                               int i)
156 {
157         HashList *BLOG = (HashList *) *ViewSpecific;
158         struct bltr b;
159         struct blogpost *bp = NULL;
160
161         b = blogview_learn_thread_references(Msg->msgnum);
162
163         /* FIXME an optimization here -- one we ought to perform -- is to exit this
164          * function immediately if the viewer is only interested in a single post and
165          * that message ID is neither the id nor the refs.  Actually, that might *be*
166          * the way to display only a single message (with or without comments).
167          */
168
169         if (b.refs == 0) {
170                 bp = malloc(sizeof(struct blogpost));
171                 if (!bp) return(200);
172                 memset(bp, 0, sizeof (struct blogpost));
173                 bp->top_level_id = b.id;
174                 Put(BLOG, (const char *)&b.id, sizeof(b.id), bp,
175                                         (DeleteHashDataFunc)blogpost_render_and_destroy);
176         }
177         else {
178                 GetHash(BLOG, (const char *)&b.refs , sizeof(b.refs), (void *)&bp);
179         }
180
181         /*
182          * Now we have a 'struct blogpost' to which we can add a message.  It's either the
183          * blog post itself or a comment attached to it; either way, the code is the same from
184          * this point onward.
185          */
186         if (bp != NULL) {
187                 if (bp->alloc_msgs == 0) {
188                         bp->alloc_msgs = 1000;
189                         bp->msgs = malloc(bp->alloc_msgs * sizeof(long));
190                         memset(bp->msgs, 0, (bp->alloc_msgs * sizeof(long)) );
191                 }
192                 if (bp->num_msgs >= bp->alloc_msgs) {
193                         bp->alloc_msgs *= 2;
194                         bp->msgs = realloc(bp->msgs, (bp->alloc_msgs * sizeof(long)));
195                         memset(&bp->msgs[bp->num_msgs], 0,
196                                 ((bp->alloc_msgs - bp->num_msgs) * sizeof(long)) );
197                 }
198                 bp->msgs[bp->num_msgs++] = Msg->msgnum;
199         }
200
201         return 200;
202 }
203
204
205 /*
206  * Sort a list of 'struct blogpost' objects by newest-to-oldest msgnum.
207  */
208 int blogview_sortfunc(const void *s1, const void *s2) {
209         long *l1 = (long *)(s1);
210         long *l2 = (long *)(s2);
211
212         if (*l1 > *l2) return(-1);
213         if (*l1 < *l2) return(+1);
214         return(0);
215 }
216
217
218 int blogview_render(SharedMessageStatus *Stat, void **ViewSpecific, long oper)
219 {
220         /*HashList *BLOG = (HashList *) *ViewSpecific;*/
221
222         /*
223          * No code needed here -- we render during disposition.
224          * Maybe this is the location where we want to handle pretty permalinks.
225          */
226
227         return(0);
228 }
229
230
231 int blogview_Cleanup(void **ViewSpecific)
232 {
233         HashList *BLOG = (HashList *) *ViewSpecific;
234
235         DeleteHash(&BLOG);
236
237         wDumpContent(1);
238         return 0;
239 }
240
241 /*
242  * Generate a permalink for a post
243  */
244 void tmplput_blog_permalink(StrBuf *Target, WCTemplputParams *TP) {
245         char perma[SIZ];
246         char encoded_perma[SIZ];
247         
248         strcpy(perma, "/readfwd?gotofirst=");
249         urlesc(&perma[strlen(perma)], sizeof(perma)-strlen(perma), ChrPtr(WC->CurRoom.name));
250         snprintf(&perma[strlen(perma)], sizeof(perma)-strlen(perma), "?p=%d", WC->bptlid);
251
252         CtdlEncodeBase64(encoded_perma, perma, strlen(perma), 0);
253         StrBufAppendPrintf(Target, "/B64%s", encoded_perma);
254 }
255
256
257 void 
258 InitModule_BLOGVIEWRENDERERS
259 (void)
260 {
261         RegisterReadLoopHandlerset(
262                 VIEW_BLOG,
263                 blogview_GetParamsGetServerCall,
264                 NULL,
265                 NULL, 
266                 blogview_LoadMsgFromServer,
267                 blogview_render,
268                 blogview_Cleanup
269         );
270         RegisterNamespace("BLOG:PERMALINK", 0, 0, tmplput_blog_permalink, NULL, CTX_NONE);
271 }