Fossil SCM
Refactored to use a shared query-rendering routine.
Commit
02a7c850b417e4cee4d7068ca8bff77da6e5ae30
Parent
b312f5ff5b0c4ef…
1 file changed
+196
-113
+196
-113
| --- src/tagview.c | ||
| +++ src/tagview.c | ||
| @@ -27,34 +27,161 @@ | ||
| 27 | 27 | */ |
| 28 | 28 | #include <assert.h> |
| 29 | 29 | #include "config.h" |
| 30 | 30 | #include "tagview.h" |
| 31 | 31 | |
| 32 | - | |
| 33 | -/* | |
| 34 | -** Output a single entry for a menu generated using an HTML table. | |
| 35 | -** If zLink is not NULL or an empty string, then it is the page that | |
| 36 | -** the menu entry will hyperlink to. If zLink is NULL or "", then | |
| 37 | -** the menu entry has no hyperlink - it is disabled. | |
| 38 | -*/ | |
| 39 | -void tagview_menu_entry( | |
| 40 | - const char *zTitle, | |
| 41 | - const char *zLink, | |
| 42 | - const char *zDesc | |
| 43 | -){ | |
| 44 | - @ <tr><td valign="top" align="right"> | |
| 45 | - if( zLink && zLink[0] ){ | |
| 46 | - @ <a href="%s(g.zBaseURL)/%s(zLink)">%h(zTitle)</a> | |
| 47 | - }else{ | |
| 48 | - @ %h(zTitle) | |
| 49 | - } | |
| 50 | - @ </td><td valign="top">%h(zDesc)</td></tr> | |
| 51 | -} | |
| 52 | - | |
| 32 | +/** | |
| 33 | +tagview_strxform_f is a typedef for funcs with | |
| 34 | +the following policy: | |
| 35 | + | |
| 36 | +The accept a string which they then transform into | |
| 37 | +some other form. They return a transformed copy, | |
| 38 | +which the caller is responsible for freeing. | |
| 39 | + | |
| 40 | +The intention of this is to provide a way for | |
| 41 | +a generic query routine to format specific column | |
| 42 | +data (e.g. transform an object ID into a link to | |
| 43 | +that object). | |
| 44 | +*/ | |
| 45 | +typedef char * (*tagview_strxform_f)( char const * ); | |
| 46 | + | |
| 47 | +#if 0 | |
| 48 | +/** A no-op transformer which can be used as a placeholder. */ | |
| 49 | +static char * tagview_xf_copy( char const * uuid ) | |
| 50 | +{ | |
| 51 | + int len = strlen(uuid) + 1; | |
| 52 | + char * ret = (char *) malloc( len ); | |
| 53 | + ret[len] = '\0'; | |
| 54 | + strncpy( ret, uuid, len-1 ); | |
| 55 | + return ret; | |
| 56 | +} | |
| 57 | +#endif | |
| 58 | + | |
| 59 | +/** Returns a hyperlink to uuid. */ | |
| 60 | +static char * tagview_xf_link_to_uuid( char const * uuid ) | |
| 61 | +{ | |
| 62 | + const int offset = 10; | |
| 63 | + char shortname[offset+1]; | |
| 64 | + shortname[offset] = '\0'; | |
| 65 | + memcpy( shortname, uuid, offset ); | |
| 66 | + return mprintf( "<tt><a href='%s/vinfo/%s'><strong>%s</strong>%s</a></tt>", | |
| 67 | + g.zBaseURL, uuid, shortname, uuid+offset ); | |
| 68 | +} | |
| 69 | + | |
| 70 | +/** Returns a hyperlink to the given tag. */ | |
| 71 | +static char * tagview_xf_link_to_tagid( char const * tagid ) | |
| 72 | +{ | |
| 73 | + return mprintf( "<a href='%s/tagview?tagid=%s'>%s</a>", | |
| 74 | + g.zBaseURL, tagid, tagid ); | |
| 75 | +} | |
| 76 | + | |
| 77 | +/** Returns a hyperlink to the named tag. */ | |
| 78 | +static char * tagview_xf_link_to_tagname( char const * tagid ) | |
| 79 | +{ | |
| 80 | + return mprintf( "<a href='%s/tagview/%s'>%s</a>", | |
| 81 | + g.zBaseURL, tagid, tagid ); | |
| 82 | +} | |
| 83 | + | |
| 84 | + | |
| 85 | + | |
| 86 | +/** | |
| 87 | +* tagview_run_query(): | |
| 88 | +* | |
| 89 | +* A very primitive helper to run an SQL query and table-ize the | |
| 90 | +* results. | |
| 91 | +* | |
| 92 | +* The sql parameter should be a single, complete SQL statement. | |
| 93 | +* | |
| 94 | +* The coln parameter is optional (it may be 0). If it is 0 then the | |
| 95 | +* column names using in the output will be taken directly from the | |
| 96 | +* SQL. If it is not null then it must have as many entries as the SQL | |
| 97 | +* result has columns. Each entry is a column name for the SQL result | |
| 98 | +* column of the same index. Any given entry may be 0, in which case | |
| 99 | +* the column name from the SQL is used. | |
| 100 | +* | |
| 101 | +* The xform argument is an array of transformation functions (type | |
| 102 | +* tagview_strxform_f). The array, or any single entry, may be 0, but | |
| 103 | +* if the array is non-0 then it must have at least as many entries as | |
| 104 | +* colnames does. Each index corresponds directly to an entry in | |
| 105 | +* colnames and the SQL results. Any given entry may be 0 If it has | |
| 106 | +* fewer, undefined behaviour results. If a column has an entry in | |
| 107 | +* xform, then the xform function will be called to transform the | |
| 108 | +* column data before rendering it. This function takes care of freeing | |
| 109 | +* the strings created by the xform functions. | |
| 110 | +* | |
| 111 | +* Example: | |
| 112 | +* | |
| 113 | +* char const * const colnames[] = { | |
| 114 | +* "Tag ID", "Tag Name", "Something Else", "UUID" | |
| 115 | +* }; | |
| 116 | +* tagview_strxform_f xf[] = { | |
| 117 | +* tagview_xf_link_to_tagid, | |
| 118 | +* tagview_xf_link_to_tagname, | |
| 119 | +* 0, | |
| 120 | +* tagview_xf_link_to_uuid | |
| 121 | +* }; | |
| 122 | +* tagview_run_query( "select a,b,c,d from foo", colnames, xf ); | |
| 123 | +* | |
| 124 | +*/ | |
| 125 | +static void tagview_run_query( | |
| 126 | + char const * sql, | |
| 127 | + char const * const * coln, | |
| 128 | + tagview_strxform_f * xform ) | |
| 129 | +{ | |
| 130 | + | |
| 131 | + Stmt st; | |
| 132 | + @ <table cellpadding='4px' border='1'><tbody> | |
| 133 | + int i = 0; | |
| 134 | + int rc = db_prepare( &st, sql ); | |
| 135 | + /** | |
| 136 | + Achtung: makeheaders apparently can't pull the function | |
| 137 | + name from this: | |
| 138 | + if( SQLITE_OK != db_prepare( &st, sql ) ) | |
| 139 | + */ | |
| 140 | + if( SQLITE_OK != rc ) | |
| 141 | + { | |
| 142 | + @ tagview_run_query(): Error processing SQL: [%s(sql)] | |
| 143 | + return; | |
| 144 | + } | |
| 145 | + int colc = db_column_count(&st); | |
| 146 | + @ <tr> | |
| 147 | + for( i = 0; i < colc; ++i ) { | |
| 148 | + if( coln ) | |
| 149 | + { | |
| 150 | + @ <th>%s(coln[i] ? coln[i] : db_column_name(&st,i))</th> | |
| 151 | + } | |
| 152 | + else | |
| 153 | + { | |
| 154 | + @ <td>%s(db_column_name(&st,i))</td> | |
| 155 | + } | |
| 156 | + } | |
| 157 | + @ </tr> | |
| 158 | + | |
| 159 | + | |
| 160 | + while( SQLITE_ROW == db_step(&st) ){ | |
| 161 | + @ <tr> | |
| 162 | + for( i = 0; i < colc; ++i ) { | |
| 163 | + char * xf = 0; | |
| 164 | + char const * xcf = 0; | |
| 165 | + xcf = (xform && xform[i]) | |
| 166 | + ? (xf=(xform[i])(db_column_text(&st,i))) | |
| 167 | + : db_column_text(&st,i); | |
| 168 | + @ <td>%s(xcf)</td> | |
| 169 | + if( xf ) free( xf ); | |
| 170 | + } | |
| 171 | + @ </tr> | |
| 172 | + } | |
| 173 | + db_finalize( &st ); | |
| 174 | + @ </tbody></table> | |
| 175 | +} | |
| 176 | + | |
| 177 | +/** | |
| 178 | + Lists all tags matching the given LIKE clause (which | |
| 179 | +may be 0). | |
| 180 | +*/ | |
| 53 | 181 | static void tagview_page_list_tags( char const * like ) |
| 54 | 182 | { |
| 55 | - Stmt st; | |
| 56 | 183 | char * likeclause = 0; |
| 57 | 184 | const int limit = 10; |
| 58 | 185 | char * limitstr = 0; |
| 59 | 186 | if( like && strlen(like) ) |
| 60 | 187 | { |
| @@ -64,61 +191,37 @@ | ||
| 64 | 191 | else |
| 65 | 192 | { |
| 66 | 193 | limitstr = mprintf( "LIMIT %d", limit ); |
| 67 | 194 | @ <h2>%d(limit) most recent tags:</h2> |
| 68 | 195 | } |
| 69 | - @ <table cellpadding='4px' border='1'><tbody> | |
| 70 | - @ <tr> | |
| 71 | - @ <th>Tag ID</th> | |
| 72 | - @ <th>Tag name</th> | |
| 73 | - @ <th>Timestamp</th> | |
| 74 | - @ <th>Version</th> | |
| 75 | - @ </tr> | |
| 76 | 196 | char * sql = mprintf( |
| 77 | 197 | "SELECT t.tagid, t.tagname, DATETIME(tx.mtime), b.uuid " |
| 78 | 198 | "FROM tag t, tagxref tx, blob b " |
| 79 | 199 | "WHERE (t.tagid=tx.tagid) and (tx.srcid=b.rid) " |
| 80 | 200 | "AND (tx.tagtype != 0) %s " |
| 81 | 201 | "ORDER BY tx.mtime DESC %s", |
| 82 | 202 | likeclause ? likeclause : " ", |
| 83 | 203 | limitstr ? limitstr : " " |
| 84 | 204 | ); |
| 85 | - db_prepare( &st, sql ); | |
| 86 | - if( likeclause ) free( likeclause ); | |
| 87 | - free( sql ); | |
| 88 | - while( SQLITE_ROW == db_step(&st) ){ | |
| 89 | - int tagid = db_column_int( &st, 0 ); | |
| 90 | - char const * tagname = db_column_text( &st, 1 ); | |
| 91 | - char const * tagtime = db_column_text( &st, 2 ); | |
| 92 | - char const * uuid = db_column_text( &st, 3 ); | |
| 93 | - const int offset = 10; | |
| 94 | - char shortname[offset+1]; | |
| 95 | - shortname[offset] = '\0'; | |
| 96 | - memcpy( shortname, uuid, offset ); | |
| 97 | - @ <tr> | |
| 98 | - @ <td><tt> | |
| 99 | - @ <a href='%s(g.zBaseURL)/tagview?tagid=%d(tagid)'>%d(tagid)</a> | |
| 100 | - @ </tt></td> | |
| 101 | - @ <td><tt><a href='%s(g.zBaseURL)/tagview/%q(tagname)'>%s(tagname)</a></tt></td> | |
| 102 | - @ <td align='center'><tt>%s(tagtime)</tt></td> | |
| 103 | - @ <td><tt> | |
| 104 | - @ <a href='%s(g.zBaseURL)/vinfo/%s(uuid)'><strong>%s(shortname)</strong>%s(uuid+offset)</a> | |
| 105 | - @ </tt></td></tr> | |
| 106 | - } | |
| 107 | - db_finalize( &st ); | |
| 108 | - @ </tbody></table> | |
| 109 | - @ <hr/>TODOs include: | |
| 110 | - @ <ul> | |
| 111 | - @ <li>Page through long tags lists.</li> | |
| 112 | - @ <li>Refactor the internal report routines to be reusable.</li> | |
| 113 | - @ <li>Allow different sorting.</li> | |
| 114 | - @ <li>Selectively filter out wiki/ticket/baseline</li> | |
| 115 | - @ <li>?</li> | |
| 116 | - @ </ul> | |
| 117 | - | |
| 118 | -} | |
| 119 | - | |
| 205 | + /* " AND t.tagname NOT GLOB 'wiki-*'" // Do we want this?? */ | |
| 206 | + | |
| 207 | + char const * const colnames[] = { | |
| 208 | + "Tag ID", "Name", "Timestamp", "Version" | |
| 209 | + }; | |
| 210 | + tagview_strxform_f xf[] = { | |
| 211 | + tagview_xf_link_to_tagid, | |
| 212 | + tagview_xf_link_to_tagname, | |
| 213 | + 0, | |
| 214 | + tagview_xf_link_to_uuid | |
| 215 | + }; | |
| 216 | + tagview_run_query( sql, colnames, xf ); | |
| 217 | + free( sql ); | |
| 218 | +} | |
| 219 | + | |
| 220 | +/** | |
| 221 | +A small search form which forwards to ?like=SEARCH_STRING | |
| 222 | +*/ | |
| 120 | 223 | static void tagview_page_search_miniform(void){ |
| 121 | 224 | char const * like = P("like"); |
| 122 | 225 | @ <div style='font-size:smaller'> |
| 123 | 226 | @ <form action='/tagview' method='post'> |
| 124 | 227 | @ Search for tags: |
| @@ -131,76 +234,56 @@ | ||
| 131 | 234 | |
| 132 | 235 | static void tagview_page_default(void){ |
| 133 | 236 | tagview_page_list_tags( 0 ); |
| 134 | 237 | } |
| 135 | 238 | |
| 239 | +/** | |
| 240 | + Lists all tags matching the given tagid. | |
| 241 | +*/ | |
| 136 | 242 | static void tagview_page_tag_by_id( int tagid ) |
| 137 | 243 | { |
| 138 | - Stmt st; | |
| 244 | + @ <h2>Tag #%d(tagid):</h2> | |
| 139 | 245 | char * sql = mprintf( |
| 140 | 246 | "SELECT DISTINCT (t.tagname), DATETIME(tx.mtime), b.uuid " |
| 141 | 247 | "FROM tag t, tagxref tx, blob b " |
| 142 | 248 | "WHERE (t.tagid=%d) AND (t.tagid=tx.tagid) AND (tx.srcid=b.rid) " |
| 143 | 249 | "ORDER BY tx.mtime DESC", |
| 144 | 250 | tagid); |
| 145 | - db_prepare( &st, sql ); | |
| 146 | - free( sql ); | |
| 147 | - @ <h2>Tag ID %d(tagid):</h2> | |
| 148 | - @ <table cellpadding='4px' border='1'><tbody> | |
| 149 | - @ <tr><th>Tag name</th><th>Timestamp</th><th>Version</th></tr> | |
| 150 | - while( SQLITE_ROW == db_step(&st) ) | |
| 151 | - { | |
| 152 | - char const * tagname = db_column_text( &st, 0 ); | |
| 153 | - char const * tagtime = db_column_text( &st, 1 ); | |
| 154 | - char const * uuid = db_column_text( &st, 2 ); | |
| 155 | - const int offset = 10; | |
| 156 | - char shortname[offset+1]; | |
| 157 | - shortname[offset] = '\0'; | |
| 158 | - memcpy( shortname, uuid, offset ); | |
| 159 | - @ <tr> | |
| 160 | - @ <td><tt><a href='%s(g.zBaseURL)/tagview/%q(tagname)'>%s(tagname)</a></tt></td> | |
| 161 | - @ <td align='center'><tt>%s(tagtime)</tt></td> | |
| 162 | - @ <td><tt> | |
| 163 | - @ <a href='%s(g.zBaseURL)/vinfo/%s(uuid)'><strong>%s(shortname)</strong>%s(uuid+offset)</a> | |
| 164 | - @ </tt></td></tr> | |
| 165 | - } | |
| 166 | - @ </tbody></table> | |
| 167 | - db_finalize( &st ); | |
| 168 | -} | |
| 169 | - | |
| 251 | + char const * const colnames[] = { | |
| 252 | + "Tag Name", "Timestamp", "Version" | |
| 253 | + }; | |
| 254 | + tagview_strxform_f xf[] = { | |
| 255 | + tagview_xf_link_to_tagname, | |
| 256 | + 0, | |
| 257 | + tagview_xf_link_to_uuid | |
| 258 | + }; | |
| 259 | + tagview_run_query( sql, colnames, xf ); | |
| 260 | + free(sql); | |
| 261 | +} | |
| 262 | + | |
| 263 | +/** | |
| 264 | + Lists all tags matching the given tag name. | |
| 265 | +*/ | |
| 170 | 266 | static void tagview_page_tag_by_name( char const * tagname ) |
| 171 | 267 | { |
| 172 | - Stmt st; | |
| 268 | + @ <h2>Tag '%s(tagname)':</h2> | |
| 173 | 269 | char * sql = mprintf( |
| 174 | 270 | "SELECT DISTINCT t.tagid, DATETIME(tx.mtime), b.uuid " |
| 175 | 271 | "FROM tag t, tagxref tx, blob b " |
| 176 | 272 | "WHERE (t.tagname='%q') AND (t.tagid=tx.tagid) AND (tx.srcid=b.rid) " |
| 177 | 273 | "ORDER BY tx.mtime DESC", |
| 178 | 274 | tagname); |
| 179 | - db_prepare( &st, sql ); | |
| 275 | + char const * const colnames[] = { | |
| 276 | + "Tag ID", "Timestamp", "Version" | |
| 277 | + }; | |
| 278 | + tagview_strxform_f xf[] = { | |
| 279 | + tagview_xf_link_to_tagid, | |
| 280 | + 0, | |
| 281 | + tagview_xf_link_to_uuid | |
| 282 | + }; | |
| 283 | + tagview_run_query( sql, colnames, xf ); | |
| 180 | 284 | free( sql ); |
| 181 | - @ <h2>Tag '%s(tagname)':</h2> | |
| 182 | - @ <table cellpadding='4px' border='1'><tbody> | |
| 183 | - @ <tr><th>Tag ID</th><th>Timestamp</th><th>Version</th></tr> | |
| 184 | - while( SQLITE_ROW == db_step(&st) ) | |
| 185 | - { | |
| 186 | - int tagid = db_column_int( &st, 0 ); | |
| 187 | - char const * tagtime = db_column_text( &st, 1 ); | |
| 188 | - char const * uuid = db_column_text( &st, 2 ); | |
| 189 | - const int offset = 10; | |
| 190 | - char shortname[offset+1]; | |
| 191 | - shortname[offset] = '\0'; | |
| 192 | - memcpy( shortname, uuid, offset ); | |
| 193 | - @ <tr> | |
| 194 | - @ <td><tt><a href='%s(g.zBaseURL)/tagview?tagid=%d(tagid)'>%d(tagid)</a></tt></td> | |
| 195 | - @ <td align='center'><tt>%s(tagtime)</tt></td> | |
| 196 | - @ <td><tt> | |
| 197 | - @ <a href='%s(g.zBaseURL)/vinfo/%s(uuid)'><strong>%s(shortname)</strong>%s(uuid+offset)</a> | |
| 198 | - @ </tt></td></tr> | |
| 199 | - } | |
| 200 | - @ </tbody></table> | |
| 201 | - db_finalize( &st ); | |
| 202 | 285 | } |
| 203 | 286 | |
| 204 | 287 | |
| 205 | 288 | /* |
| 206 | 289 | ** WEBPAGE: /tagview |
| 207 | 290 |
| --- src/tagview.c | |
| +++ src/tagview.c | |
| @@ -27,34 +27,161 @@ | |
| 27 | */ |
| 28 | #include <assert.h> |
| 29 | #include "config.h" |
| 30 | #include "tagview.h" |
| 31 | |
| 32 | |
| 33 | /* |
| 34 | ** Output a single entry for a menu generated using an HTML table. |
| 35 | ** If zLink is not NULL or an empty string, then it is the page that |
| 36 | ** the menu entry will hyperlink to. If zLink is NULL or "", then |
| 37 | ** the menu entry has no hyperlink - it is disabled. |
| 38 | */ |
| 39 | void tagview_menu_entry( |
| 40 | const char *zTitle, |
| 41 | const char *zLink, |
| 42 | const char *zDesc |
| 43 | ){ |
| 44 | @ <tr><td valign="top" align="right"> |
| 45 | if( zLink && zLink[0] ){ |
| 46 | @ <a href="%s(g.zBaseURL)/%s(zLink)">%h(zTitle)</a> |
| 47 | }else{ |
| 48 | @ %h(zTitle) |
| 49 | } |
| 50 | @ </td><td valign="top">%h(zDesc)</td></tr> |
| 51 | } |
| 52 | |
| 53 | static void tagview_page_list_tags( char const * like ) |
| 54 | { |
| 55 | Stmt st; |
| 56 | char * likeclause = 0; |
| 57 | const int limit = 10; |
| 58 | char * limitstr = 0; |
| 59 | if( like && strlen(like) ) |
| 60 | { |
| @@ -64,61 +191,37 @@ | |
| 64 | else |
| 65 | { |
| 66 | limitstr = mprintf( "LIMIT %d", limit ); |
| 67 | @ <h2>%d(limit) most recent tags:</h2> |
| 68 | } |
| 69 | @ <table cellpadding='4px' border='1'><tbody> |
| 70 | @ <tr> |
| 71 | @ <th>Tag ID</th> |
| 72 | @ <th>Tag name</th> |
| 73 | @ <th>Timestamp</th> |
| 74 | @ <th>Version</th> |
| 75 | @ </tr> |
| 76 | char * sql = mprintf( |
| 77 | "SELECT t.tagid, t.tagname, DATETIME(tx.mtime), b.uuid " |
| 78 | "FROM tag t, tagxref tx, blob b " |
| 79 | "WHERE (t.tagid=tx.tagid) and (tx.srcid=b.rid) " |
| 80 | "AND (tx.tagtype != 0) %s " |
| 81 | "ORDER BY tx.mtime DESC %s", |
| 82 | likeclause ? likeclause : " ", |
| 83 | limitstr ? limitstr : " " |
| 84 | ); |
| 85 | db_prepare( &st, sql ); |
| 86 | if( likeclause ) free( likeclause ); |
| 87 | free( sql ); |
| 88 | while( SQLITE_ROW == db_step(&st) ){ |
| 89 | int tagid = db_column_int( &st, 0 ); |
| 90 | char const * tagname = db_column_text( &st, 1 ); |
| 91 | char const * tagtime = db_column_text( &st, 2 ); |
| 92 | char const * uuid = db_column_text( &st, 3 ); |
| 93 | const int offset = 10; |
| 94 | char shortname[offset+1]; |
| 95 | shortname[offset] = '\0'; |
| 96 | memcpy( shortname, uuid, offset ); |
| 97 | @ <tr> |
| 98 | @ <td><tt> |
| 99 | @ <a href='%s(g.zBaseURL)/tagview?tagid=%d(tagid)'>%d(tagid)</a> |
| 100 | @ </tt></td> |
| 101 | @ <td><tt><a href='%s(g.zBaseURL)/tagview/%q(tagname)'>%s(tagname)</a></tt></td> |
| 102 | @ <td align='center'><tt>%s(tagtime)</tt></td> |
| 103 | @ <td><tt> |
| 104 | @ <a href='%s(g.zBaseURL)/vinfo/%s(uuid)'><strong>%s(shortname)</strong>%s(uuid+offset)</a> |
| 105 | @ </tt></td></tr> |
| 106 | } |
| 107 | db_finalize( &st ); |
| 108 | @ </tbody></table> |
| 109 | @ <hr/>TODOs include: |
| 110 | @ <ul> |
| 111 | @ <li>Page through long tags lists.</li> |
| 112 | @ <li>Refactor the internal report routines to be reusable.</li> |
| 113 | @ <li>Allow different sorting.</li> |
| 114 | @ <li>Selectively filter out wiki/ticket/baseline</li> |
| 115 | @ <li>?</li> |
| 116 | @ </ul> |
| 117 | |
| 118 | } |
| 119 | |
| 120 | static void tagview_page_search_miniform(void){ |
| 121 | char const * like = P("like"); |
| 122 | @ <div style='font-size:smaller'> |
| 123 | @ <form action='/tagview' method='post'> |
| 124 | @ Search for tags: |
| @@ -131,76 +234,56 @@ | |
| 131 | |
| 132 | static void tagview_page_default(void){ |
| 133 | tagview_page_list_tags( 0 ); |
| 134 | } |
| 135 | |
| 136 | static void tagview_page_tag_by_id( int tagid ) |
| 137 | { |
| 138 | Stmt st; |
| 139 | char * sql = mprintf( |
| 140 | "SELECT DISTINCT (t.tagname), DATETIME(tx.mtime), b.uuid " |
| 141 | "FROM tag t, tagxref tx, blob b " |
| 142 | "WHERE (t.tagid=%d) AND (t.tagid=tx.tagid) AND (tx.srcid=b.rid) " |
| 143 | "ORDER BY tx.mtime DESC", |
| 144 | tagid); |
| 145 | db_prepare( &st, sql ); |
| 146 | free( sql ); |
| 147 | @ <h2>Tag ID %d(tagid):</h2> |
| 148 | @ <table cellpadding='4px' border='1'><tbody> |
| 149 | @ <tr><th>Tag name</th><th>Timestamp</th><th>Version</th></tr> |
| 150 | while( SQLITE_ROW == db_step(&st) ) |
| 151 | { |
| 152 | char const * tagname = db_column_text( &st, 0 ); |
| 153 | char const * tagtime = db_column_text( &st, 1 ); |
| 154 | char const * uuid = db_column_text( &st, 2 ); |
| 155 | const int offset = 10; |
| 156 | char shortname[offset+1]; |
| 157 | shortname[offset] = '\0'; |
| 158 | memcpy( shortname, uuid, offset ); |
| 159 | @ <tr> |
| 160 | @ <td><tt><a href='%s(g.zBaseURL)/tagview/%q(tagname)'>%s(tagname)</a></tt></td> |
| 161 | @ <td align='center'><tt>%s(tagtime)</tt></td> |
| 162 | @ <td><tt> |
| 163 | @ <a href='%s(g.zBaseURL)/vinfo/%s(uuid)'><strong>%s(shortname)</strong>%s(uuid+offset)</a> |
| 164 | @ </tt></td></tr> |
| 165 | } |
| 166 | @ </tbody></table> |
| 167 | db_finalize( &st ); |
| 168 | } |
| 169 | |
| 170 | static void tagview_page_tag_by_name( char const * tagname ) |
| 171 | { |
| 172 | Stmt st; |
| 173 | char * sql = mprintf( |
| 174 | "SELECT DISTINCT t.tagid, DATETIME(tx.mtime), b.uuid " |
| 175 | "FROM tag t, tagxref tx, blob b " |
| 176 | "WHERE (t.tagname='%q') AND (t.tagid=tx.tagid) AND (tx.srcid=b.rid) " |
| 177 | "ORDER BY tx.mtime DESC", |
| 178 | tagname); |
| 179 | db_prepare( &st, sql ); |
| 180 | free( sql ); |
| 181 | @ <h2>Tag '%s(tagname)':</h2> |
| 182 | @ <table cellpadding='4px' border='1'><tbody> |
| 183 | @ <tr><th>Tag ID</th><th>Timestamp</th><th>Version</th></tr> |
| 184 | while( SQLITE_ROW == db_step(&st) ) |
| 185 | { |
| 186 | int tagid = db_column_int( &st, 0 ); |
| 187 | char const * tagtime = db_column_text( &st, 1 ); |
| 188 | char const * uuid = db_column_text( &st, 2 ); |
| 189 | const int offset = 10; |
| 190 | char shortname[offset+1]; |
| 191 | shortname[offset] = '\0'; |
| 192 | memcpy( shortname, uuid, offset ); |
| 193 | @ <tr> |
| 194 | @ <td><tt><a href='%s(g.zBaseURL)/tagview?tagid=%d(tagid)'>%d(tagid)</a></tt></td> |
| 195 | @ <td align='center'><tt>%s(tagtime)</tt></td> |
| 196 | @ <td><tt> |
| 197 | @ <a href='%s(g.zBaseURL)/vinfo/%s(uuid)'><strong>%s(shortname)</strong>%s(uuid+offset)</a> |
| 198 | @ </tt></td></tr> |
| 199 | } |
| 200 | @ </tbody></table> |
| 201 | db_finalize( &st ); |
| 202 | } |
| 203 | |
| 204 | |
| 205 | /* |
| 206 | ** WEBPAGE: /tagview |
| 207 |
| --- src/tagview.c | |
| +++ src/tagview.c | |
| @@ -27,34 +27,161 @@ | |
| 27 | */ |
| 28 | #include <assert.h> |
| 29 | #include "config.h" |
| 30 | #include "tagview.h" |
| 31 | |
| 32 | /** |
| 33 | tagview_strxform_f is a typedef for funcs with |
| 34 | the following policy: |
| 35 | |
| 36 | The accept a string which they then transform into |
| 37 | some other form. They return a transformed copy, |
| 38 | which the caller is responsible for freeing. |
| 39 | |
| 40 | The intention of this is to provide a way for |
| 41 | a generic query routine to format specific column |
| 42 | data (e.g. transform an object ID into a link to |
| 43 | that object). |
| 44 | */ |
| 45 | typedef char * (*tagview_strxform_f)( char const * ); |
| 46 | |
| 47 | #if 0 |
| 48 | /** A no-op transformer which can be used as a placeholder. */ |
| 49 | static char * tagview_xf_copy( char const * uuid ) |
| 50 | { |
| 51 | int len = strlen(uuid) + 1; |
| 52 | char * ret = (char *) malloc( len ); |
| 53 | ret[len] = '\0'; |
| 54 | strncpy( ret, uuid, len-1 ); |
| 55 | return ret; |
| 56 | } |
| 57 | #endif |
| 58 | |
| 59 | /** Returns a hyperlink to uuid. */ |
| 60 | static char * tagview_xf_link_to_uuid( char const * uuid ) |
| 61 | { |
| 62 | const int offset = 10; |
| 63 | char shortname[offset+1]; |
| 64 | shortname[offset] = '\0'; |
| 65 | memcpy( shortname, uuid, offset ); |
| 66 | return mprintf( "<tt><a href='%s/vinfo/%s'><strong>%s</strong>%s</a></tt>", |
| 67 | g.zBaseURL, uuid, shortname, uuid+offset ); |
| 68 | } |
| 69 | |
| 70 | /** Returns a hyperlink to the given tag. */ |
| 71 | static char * tagview_xf_link_to_tagid( char const * tagid ) |
| 72 | { |
| 73 | return mprintf( "<a href='%s/tagview?tagid=%s'>%s</a>", |
| 74 | g.zBaseURL, tagid, tagid ); |
| 75 | } |
| 76 | |
| 77 | /** Returns a hyperlink to the named tag. */ |
| 78 | static char * tagview_xf_link_to_tagname( char const * tagid ) |
| 79 | { |
| 80 | return mprintf( "<a href='%s/tagview/%s'>%s</a>", |
| 81 | g.zBaseURL, tagid, tagid ); |
| 82 | } |
| 83 | |
| 84 | |
| 85 | |
| 86 | /** |
| 87 | * tagview_run_query(): |
| 88 | * |
| 89 | * A very primitive helper to run an SQL query and table-ize the |
| 90 | * results. |
| 91 | * |
| 92 | * The sql parameter should be a single, complete SQL statement. |
| 93 | * |
| 94 | * The coln parameter is optional (it may be 0). If it is 0 then the |
| 95 | * column names using in the output will be taken directly from the |
| 96 | * SQL. If it is not null then it must have as many entries as the SQL |
| 97 | * result has columns. Each entry is a column name for the SQL result |
| 98 | * column of the same index. Any given entry may be 0, in which case |
| 99 | * the column name from the SQL is used. |
| 100 | * |
| 101 | * The xform argument is an array of transformation functions (type |
| 102 | * tagview_strxform_f). The array, or any single entry, may be 0, but |
| 103 | * if the array is non-0 then it must have at least as many entries as |
| 104 | * colnames does. Each index corresponds directly to an entry in |
| 105 | * colnames and the SQL results. Any given entry may be 0 If it has |
| 106 | * fewer, undefined behaviour results. If a column has an entry in |
| 107 | * xform, then the xform function will be called to transform the |
| 108 | * column data before rendering it. This function takes care of freeing |
| 109 | * the strings created by the xform functions. |
| 110 | * |
| 111 | * Example: |
| 112 | * |
| 113 | * char const * const colnames[] = { |
| 114 | * "Tag ID", "Tag Name", "Something Else", "UUID" |
| 115 | * }; |
| 116 | * tagview_strxform_f xf[] = { |
| 117 | * tagview_xf_link_to_tagid, |
| 118 | * tagview_xf_link_to_tagname, |
| 119 | * 0, |
| 120 | * tagview_xf_link_to_uuid |
| 121 | * }; |
| 122 | * tagview_run_query( "select a,b,c,d from foo", colnames, xf ); |
| 123 | * |
| 124 | */ |
| 125 | static void tagview_run_query( |
| 126 | char const * sql, |
| 127 | char const * const * coln, |
| 128 | tagview_strxform_f * xform ) |
| 129 | { |
| 130 | |
| 131 | Stmt st; |
| 132 | @ <table cellpadding='4px' border='1'><tbody> |
| 133 | int i = 0; |
| 134 | int rc = db_prepare( &st, sql ); |
| 135 | /** |
| 136 | Achtung: makeheaders apparently can't pull the function |
| 137 | name from this: |
| 138 | if( SQLITE_OK != db_prepare( &st, sql ) ) |
| 139 | */ |
| 140 | if( SQLITE_OK != rc ) |
| 141 | { |
| 142 | @ tagview_run_query(): Error processing SQL: [%s(sql)] |
| 143 | return; |
| 144 | } |
| 145 | int colc = db_column_count(&st); |
| 146 | @ <tr> |
| 147 | for( i = 0; i < colc; ++i ) { |
| 148 | if( coln ) |
| 149 | { |
| 150 | @ <th>%s(coln[i] ? coln[i] : db_column_name(&st,i))</th> |
| 151 | } |
| 152 | else |
| 153 | { |
| 154 | @ <td>%s(db_column_name(&st,i))</td> |
| 155 | } |
| 156 | } |
| 157 | @ </tr> |
| 158 | |
| 159 | |
| 160 | while( SQLITE_ROW == db_step(&st) ){ |
| 161 | @ <tr> |
| 162 | for( i = 0; i < colc; ++i ) { |
| 163 | char * xf = 0; |
| 164 | char const * xcf = 0; |
| 165 | xcf = (xform && xform[i]) |
| 166 | ? (xf=(xform[i])(db_column_text(&st,i))) |
| 167 | : db_column_text(&st,i); |
| 168 | @ <td>%s(xcf)</td> |
| 169 | if( xf ) free( xf ); |
| 170 | } |
| 171 | @ </tr> |
| 172 | } |
| 173 | db_finalize( &st ); |
| 174 | @ </tbody></table> |
| 175 | } |
| 176 | |
| 177 | /** |
| 178 | Lists all tags matching the given LIKE clause (which |
| 179 | may be 0). |
| 180 | */ |
| 181 | static void tagview_page_list_tags( char const * like ) |
| 182 | { |
| 183 | char * likeclause = 0; |
| 184 | const int limit = 10; |
| 185 | char * limitstr = 0; |
| 186 | if( like && strlen(like) ) |
| 187 | { |
| @@ -64,61 +191,37 @@ | |
| 191 | else |
| 192 | { |
| 193 | limitstr = mprintf( "LIMIT %d", limit ); |
| 194 | @ <h2>%d(limit) most recent tags:</h2> |
| 195 | } |
| 196 | char * sql = mprintf( |
| 197 | "SELECT t.tagid, t.tagname, DATETIME(tx.mtime), b.uuid " |
| 198 | "FROM tag t, tagxref tx, blob b " |
| 199 | "WHERE (t.tagid=tx.tagid) and (tx.srcid=b.rid) " |
| 200 | "AND (tx.tagtype != 0) %s " |
| 201 | "ORDER BY tx.mtime DESC %s", |
| 202 | likeclause ? likeclause : " ", |
| 203 | limitstr ? limitstr : " " |
| 204 | ); |
| 205 | /* " AND t.tagname NOT GLOB 'wiki-*'" // Do we want this?? */ |
| 206 | |
| 207 | char const * const colnames[] = { |
| 208 | "Tag ID", "Name", "Timestamp", "Version" |
| 209 | }; |
| 210 | tagview_strxform_f xf[] = { |
| 211 | tagview_xf_link_to_tagid, |
| 212 | tagview_xf_link_to_tagname, |
| 213 | 0, |
| 214 | tagview_xf_link_to_uuid |
| 215 | }; |
| 216 | tagview_run_query( sql, colnames, xf ); |
| 217 | free( sql ); |
| 218 | } |
| 219 | |
| 220 | /** |
| 221 | A small search form which forwards to ?like=SEARCH_STRING |
| 222 | */ |
| 223 | static void tagview_page_search_miniform(void){ |
| 224 | char const * like = P("like"); |
| 225 | @ <div style='font-size:smaller'> |
| 226 | @ <form action='/tagview' method='post'> |
| 227 | @ Search for tags: |
| @@ -131,76 +234,56 @@ | |
| 234 | |
| 235 | static void tagview_page_default(void){ |
| 236 | tagview_page_list_tags( 0 ); |
| 237 | } |
| 238 | |
| 239 | /** |
| 240 | Lists all tags matching the given tagid. |
| 241 | */ |
| 242 | static void tagview_page_tag_by_id( int tagid ) |
| 243 | { |
| 244 | @ <h2>Tag #%d(tagid):</h2> |
| 245 | char * sql = mprintf( |
| 246 | "SELECT DISTINCT (t.tagname), DATETIME(tx.mtime), b.uuid " |
| 247 | "FROM tag t, tagxref tx, blob b " |
| 248 | "WHERE (t.tagid=%d) AND (t.tagid=tx.tagid) AND (tx.srcid=b.rid) " |
| 249 | "ORDER BY tx.mtime DESC", |
| 250 | tagid); |
| 251 | char const * const colnames[] = { |
| 252 | "Tag Name", "Timestamp", "Version" |
| 253 | }; |
| 254 | tagview_strxform_f xf[] = { |
| 255 | tagview_xf_link_to_tagname, |
| 256 | 0, |
| 257 | tagview_xf_link_to_uuid |
| 258 | }; |
| 259 | tagview_run_query( sql, colnames, xf ); |
| 260 | free(sql); |
| 261 | } |
| 262 | |
| 263 | /** |
| 264 | Lists all tags matching the given tag name. |
| 265 | */ |
| 266 | static void tagview_page_tag_by_name( char const * tagname ) |
| 267 | { |
| 268 | @ <h2>Tag '%s(tagname)':</h2> |
| 269 | char * sql = mprintf( |
| 270 | "SELECT DISTINCT t.tagid, DATETIME(tx.mtime), b.uuid " |
| 271 | "FROM tag t, tagxref tx, blob b " |
| 272 | "WHERE (t.tagname='%q') AND (t.tagid=tx.tagid) AND (tx.srcid=b.rid) " |
| 273 | "ORDER BY tx.mtime DESC", |
| 274 | tagname); |
| 275 | char const * const colnames[] = { |
| 276 | "Tag ID", "Timestamp", "Version" |
| 277 | }; |
| 278 | tagview_strxform_f xf[] = { |
| 279 | tagview_xf_link_to_tagid, |
| 280 | 0, |
| 281 | tagview_xf_link_to_uuid |
| 282 | }; |
| 283 | tagview_run_query( sql, colnames, xf ); |
| 284 | free( sql ); |
| 285 | } |
| 286 | |
| 287 | |
| 288 | /* |
| 289 | ** WEBPAGE: /tagview |
| 290 |