Back to index

webcit  8.12-dfsg
blogview_renderer.c
Go to the documentation of this file.
00001 /* 
00002  * Blog view renderer module for WebCit
00003  *
00004  * Copyright (c) 1996-2012 by the citadel.org team
00005  *
00006  * This program is open source software.  You can redistribute it and/or
00007  * modify it under the terms of the GNU General Public License, version 3.
00008  *
00009  * This program is distributed in the hope that it will be useful,
00010  * but WITHOUT ANY WARRANTY; without even the implied warranty of
00011  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
00012  * GNU General Public License for more details.
00013  */
00014 
00015 #include "webcit.h"
00016 #include "webserver.h"
00017 #include "dav.h"
00018 
00019 
00020 /*
00021  * Generate a permalink for a post
00022  * (Call with NULL arguments to make this function wcprintf() the permalink
00023  * instead of writing it to the template)
00024  */
00025 void tmplput_blog_permalink(StrBuf *Target, WCTemplputParams *TP) {
00026        char perma[SIZ];
00027        
00028        strcpy(perma, "/readfwd?go=");
00029        urlesc(&perma[strlen(perma)], sizeof(perma)-strlen(perma), (char *)ChrPtr(WC->CurRoom.name));
00030        snprintf(&perma[strlen(perma)], sizeof(perma)-strlen(perma), "?p=%d", WC->bptlid);
00031        if (!Target) {
00032               wc_printf("%s", perma);
00033        }
00034        else {
00035               StrBufAppendPrintf(Target, "%s", perma);
00036        }
00037 }
00038 
00039 
00040 /*
00041  * Render a single blog post and (optionally) its comments
00042  */
00043 void blogpost_render(struct blogpost *bp, int with_comments)
00044 {
00045        const StrBuf *Mime;
00046        int i;
00047 
00048        WC->bptlid = bp->top_level_id;     /* This is used in templates; do not remove it */
00049 
00050        /* Always show the top level post, unless we somehow ended up with an empty list */
00051        if (bp->num_msgs > 0) {
00052               read_message(WC->WBuf, HKEY("view_blog_post"), bp->msgs[0], NULL, &Mime);
00053        }
00054 
00055        if (with_comments) {
00056               /* Show any existing comments, then offer the comment box */
00057               wc_printf("<a name=\"comments\"></a>\n");
00058               wc_printf(_("%d comments"), bp->num_msgs - 1);
00059               wc_printf(" | <a href=\"");
00060               tmplput_blog_permalink(NULL, NULL);
00061               wc_printf("\">%s</a>", _("permalink"));
00062               wc_printf("<br>\n");
00063               for (i=1; i<bp->num_msgs; ++i) {
00064                      read_message(WC->WBuf, HKEY("view_blog_comment"), bp->msgs[i], NULL, &Mime);
00065               }
00066               do_template("view_blog_comment_box");
00067        }
00068 
00069        else {
00070               /* Show only the number of comments */
00071               wc_printf("<a href=\"readfwd?p=%d?go=", bp->top_level_id);
00072               urlescputs(ChrPtr(WC->CurRoom.name));
00073               wc_printf("#comments\">");
00074               wc_printf(_("%d comments"), bp->num_msgs - 1);
00075               wc_printf("</a> | <a href=\"");
00076               tmplput_blog_permalink(NULL, NULL);
00077               wc_printf("\">%s</a>", _("permalink"));
00078               wc_printf("<br><br><br>\n");
00079        }
00080 }
00081 
00082 
00083 /*
00084  * Destructor for "struct blogpost"
00085  */
00086 void blogpost_destroy(struct blogpost *bp) {
00087        if (bp->alloc_msgs > 0) {
00088               free(bp->msgs);
00089        }
00090        free(bp);
00091 }
00092 
00093 
00094 /*
00095  * Entry point for message read operations.
00096  */
00097 int blogview_GetParamsGetServerCall(SharedMessageStatus *Stat, 
00098                                void **ViewSpecific, 
00099                                long oper, 
00100                                char *cmd, 
00101                                 long len,
00102                                 char *filter,
00103                                 long flen)
00104 {
00105        HashList *BLOG = NewHash(1, NULL);
00106        *ViewSpecific = BLOG;
00107 
00108        Stat->startmsg = (-1);                                  /* not used here */
00109        Stat->sortit = 1;                                /* not used here */
00110        Stat->num_displayed = DEFAULT_MAXMSGS;                  /* not used here */
00111        if (Stat->maxmsgs == 0) Stat->maxmsgs = DEFAULT_MAXMSGS;
00112        
00113        /* perform a "read all" call to fetch the message list -- we'll cut it down later */
00114        rlid[2].cmd(cmd, len);
00115        
00116        return 200;
00117 }
00118 
00119 
00120 /*
00121  * Given a msgnum, populate the id and refs fields of
00122  * a "struct bltr" by fetching them from the Citadel server
00123  */
00124 struct bltr blogview_learn_thread_references(long msgnum)
00125 {
00126        StrBuf *Buf;
00127        StrBuf *r;
00128        int len;
00129        struct bltr bltr = { 0, 0 } ;
00130        Buf = NewStrBuf();
00131        r = NewStrBuf();
00132        serv_printf("MSG0 %ld|1", msgnum);        /* top level citadel headers only */
00133        StrBuf_ServGetln(Buf);
00134        if (GetServerStatus(Buf, NULL) == 1) {
00135               while (len = StrBuf_ServGetln(Buf), 
00136                      ((len >= 0) && 
00137                      ((len != 3) || strcmp(ChrPtr(Buf), "000") )))
00138               {
00139                      if (!strncasecmp(ChrPtr(Buf), "msgn=", 5)) {
00140                             StrBufCutLeft(Buf, 5);
00141                             bltr.id = ThreadIdHash(Buf);
00142                      }
00143                      else if (!strncasecmp(ChrPtr(Buf), "wefw=", 5)) {
00144                             StrBufCutLeft(Buf, 5);             /* trim the field name */
00145                             StrBufExtract_token(r, Buf, 0, '|');
00146                             bltr.refs = ThreadIdHash(r);
00147                      }
00148               }
00149        }
00150        FreeStrBuf(&Buf);
00151        FreeStrBuf(&r);
00152        return(bltr);
00153 }
00154 
00155 
00156 /*
00157  * This function is called for every message in the list.
00158  */
00159 int blogview_LoadMsgFromServer(SharedMessageStatus *Stat, 
00160                            void **ViewSpecific, 
00161                            message_summary* Msg, 
00162                            int is_new, 
00163                            int i)
00164 {
00165        HashList *BLOG = (HashList *) *ViewSpecific;
00166        struct bltr b;
00167        struct blogpost *bp = NULL;
00168        int p = 0;
00169 
00170        b = blogview_learn_thread_references(Msg->msgnum);
00171 
00172        /* Stop processing if the viewer is only interested in a single post and
00173         * that message ID is neither the id nor the refs.
00174         */
00175        p = atoi(BSTR("p")); /* are we looking for a specific post? */
00176        if ((p != 0) && (p != b.id) && (p != b.refs)) {
00177               return 200;
00178        }
00179 
00180        /*
00181         * Add our little bundle of blogworthy wonderfulness to the hash table
00182         */
00183        if (b.refs == 0) {
00184               bp = malloc(sizeof(struct blogpost));
00185               if (!bp) return(200);
00186               memset(bp, 0, sizeof (struct blogpost));
00187               bp->top_level_id = b.id;
00188               Put(BLOG, (const char *)&b.id, sizeof(b.id), bp, (DeleteHashDataFunc)blogpost_destroy);
00189        }
00190        else {
00191               GetHash(BLOG, (const char *)&b.refs , sizeof(b.refs), (void *)&bp);
00192        }
00193 
00194        /*
00195         * Now we have a 'struct blogpost' to which we can add a message.  It's either the
00196         * blog post itself or a comment attached to it; either way, the code is the same from
00197         * this point onward.
00198         */
00199        if (bp != NULL) {
00200               if (bp->alloc_msgs == 0) {
00201                      bp->alloc_msgs = 1000;
00202                      bp->msgs = malloc(bp->alloc_msgs * sizeof(long));
00203                      memset(bp->msgs, 0, (bp->alloc_msgs * sizeof(long)) );
00204               }
00205               if (bp->num_msgs >= bp->alloc_msgs) {
00206                      bp->alloc_msgs *= 2;
00207                      bp->msgs = realloc(bp->msgs, (bp->alloc_msgs * sizeof(long)));
00208                      memset(&bp->msgs[bp->num_msgs], 0,
00209                             ((bp->alloc_msgs - bp->num_msgs) * sizeof(long)) );
00210               }
00211               bp->msgs[bp->num_msgs++] = Msg->msgnum;
00212        }
00213        else {
00214               syslog(LOG_DEBUG, "** comment %ld is unparented", Msg->msgnum);
00215        }
00216 
00217        return 200;
00218 }
00219 
00220 
00221 /*
00222  * Sort a list of 'struct blogpost' pointers by newest-to-oldest msgnum.
00223  * With big thanks to whoever wrote http://www.c.happycodings.com/Sorting_Searching/code14.html
00224  */
00225 static int blogview_sortfunc(const void *a, const void *b) { 
00226        struct blogpost * const *one = a;
00227        struct blogpost * const *two = b;
00228 
00229        if ( (*one)->msgs[0] > (*two)->msgs[0] ) return(-1);
00230        if ( (*one)->msgs[0] < (*two)->msgs[0] ) return(+1);
00231        return(0);
00232 }
00233 
00234 
00235 /*
00236  * All blogpost entries are now in the hash list.
00237  * Sort them, select the desired range, and render what we want to see.
00238  */
00239 int blogview_render(SharedMessageStatus *Stat, void **ViewSpecific, long oper)
00240 {
00241        HashList *BLOG = (HashList *) *ViewSpecific;
00242        HashPos *it;
00243        const char *Key;
00244        void *Data;
00245        long len;
00246        int i;
00247        struct blogpost **blogposts = NULL;
00248        int num_blogposts = 0;
00249        int num_blogposts_alloc = 0;
00250        int with_comments = 0;
00251        int firstp = 0;
00252        int maxp = 0;
00253 
00254        /* Comments are shown if we are only viewing a single blog post */
00255        if (atoi(BSTR("p"))) with_comments = 1;
00256 
00257        firstp = atoi(BSTR("firstp"));     /* start reading at... */
00258        maxp = atoi(BSTR("maxp"));  /* max posts to show... */
00259        if (maxp < 1) maxp = 5;            /* default; move somewhere else? */
00260 
00261        /* Iterate through the hash list and copy the data pointers into an array */
00262        it = GetNewHashPos(BLOG, 0);
00263        while (GetNextHashPos(BLOG, it, &len, &Key, &Data)) {
00264               if (num_blogposts >= num_blogposts_alloc) {
00265                      if (num_blogposts_alloc == 0) {
00266                             num_blogposts_alloc = 100;
00267                      }
00268                      else {
00269                             num_blogposts_alloc *= 2;
00270                      }
00271                      blogposts = realloc(blogposts, (num_blogposts_alloc * sizeof (struct blogpost *)));
00272               }
00273               blogposts[num_blogposts++] = (struct blogpost *) Data;
00274        }
00275        DeleteHashPos(&it);
00276 
00277        /* Now we have our array.  It is ONLY an array of pointers.  The objects to
00278         * which they point are still owned by the hash list.
00279         */
00280        if (num_blogposts > 0) {
00281               int start_here = 0;
00282               /* Sort newest-to-oldest */
00283               qsort(blogposts, num_blogposts, sizeof(void *), blogview_sortfunc);
00284 
00285               /* allow the user to select a starting point in the list */
00286               for (i=0; i<num_blogposts; ++i) {
00287                      if (blogposts[i]->top_level_id == firstp) {
00288                             start_here = i;
00289                      }
00290               }
00291 
00292               /* FIXME -- allow the user (or a default setting) to select a maximum number of posts to display */
00293 
00294               /* Now go through the list and render what we've got */
00295               for (i=start_here; i<num_blogposts; ++i) {
00296                      if ((i > 0) && (i == start_here)) {
00297                             int j = i - maxp;
00298                             if (j < 0) j = 0;
00299                             wc_printf("<div class=\"newer_blog_posts\"><a href=\"readfwd?go=");
00300                             urlescputs(ChrPtr(WC->CurRoom.name));
00301                             wc_printf("?firstp=%d?maxp=%d\">", blogposts[j]->top_level_id, maxp);
00302                             wc_printf("%s →</a></div>\n", _("Newer posts"));
00303                      }
00304                      if (i < (start_here + maxp)) {
00305                             blogpost_render(blogposts[i], with_comments);
00306                      }
00307                      else if (i == (start_here + maxp)) {
00308                             wc_printf("<div class=\"older_blog_posts\"><a href=\"readfwd?go=");
00309                             urlescputs(ChrPtr(WC->CurRoom.name));
00310                             wc_printf("?firstp=%d?maxp=%d\">", blogposts[i]->top_level_id, maxp);
00311                             wc_printf("← %s</a></div>\n", _("Older posts"));
00312                      }
00313               }
00314 
00315               /* Done.  We are only freeing the array of pointers; the data itself
00316                * will be freed along with the hash list.
00317                */
00318               free(blogposts);
00319        }
00320 
00321        return(0);
00322 }
00323 
00324 
00325 int blogview_Cleanup(void **ViewSpecific)
00326 {
00327        HashList *BLOG = (HashList *) *ViewSpecific;
00328 
00329        DeleteHash(&BLOG);
00330 
00331        wDumpContent(1);
00332        return 0;
00333 }
00334 
00335 
00336 void 
00337 InitModule_BLOGVIEWRENDERERS
00338 (void)
00339 {
00340        RegisterReadLoopHandlerset(
00341               VIEW_BLOG,
00342               blogview_GetParamsGetServerCall,
00343               NULL,
00344               NULL,
00345               NULL, 
00346               blogview_LoadMsgFromServer,
00347               blogview_render,
00348               blogview_Cleanup
00349        );
00350        RegisterNamespace("BLOG:PERMALINK", 0, 0, tmplput_blog_permalink, NULL, CTX_NONE);
00351 }