Fossil SCM

The emailblob table holds content a little while before deleting it. Change the /test-emailblob page to /emailblob, though it is still only accessible by the administrator.

drh 2018-07-17 19:45 trunk
Commit 15a533086928eb88a3448390e752e0588346cde2caaee8ae39abb08eda07a567
2 files changed +25 -8 +15 -8
+25 -8
--- src/smtp.c
+++ src/smtp.c
@@ -637,11 +637,11 @@
637637
*/
638638
static const char zEmailSchema[] =
639639
@ -- bulk storage is in a separate table. This table can store either
640640
@ -- the body of email messages or transcripts of smtp sessions.
641641
@ CREATE TABLE IF NOT EXISTS repository.emailblob(
642
-@ emailid INTEGER PRIMARY KEY, -- numeric idea for the entry
642
+@ emailid INTEGER PRIMARY KEY AUTOINCREMENT, -- numeric idea for the entry
643643
@ enref INT, -- Number of references to this blob
644644
@ ets INT, -- Corresponding transcript, or NULL
645645
@ etime INT, -- insertion time, secs since 1970
646646
@ etxt TEXT -- content of this entry
647647
@ );
@@ -680,23 +680,24 @@
680680
@ -- Triggers to automatically keep the emailblob.enref field up to date
681681
@ -- as entries in the emailblob, emailbox, and emailoutq tables are
682682
@ -- deleted.
683683
@ CREATE TRIGGER IF NOT EXISTS repository.emailblob_d1
684684
@ AFTER DELETE ON emailblob BEGIN
685
-@ DELETE FROM emailblob WHERE enref<=1 AND emailid=old.ets;
686685
@ UPDATE emailblob SET enref=enref-1 WHERE emailid=old.ets;
687686
@ END;
688687
@ CREATE TRIGGER IF NOT EXISTS repository.emailbox_d1
689688
@ AFTER DELETE ON emailbox BEGIN
690
-@ DELETE FROM emailblob WHERE enref<=1 AND emailid=old.emsgid;
691689
@ UPDATE emailblob SET enref=enref-1 WHERE emailid=old.emsgid;
692690
@ END;
693691
@ CREATE TRIGGER IF NOT EXISTS repository.emailoutq_d1
694692
@ AFTER DELETE ON emailoutq BEGIN
695
-@ DELETE FROM emailblob WHERE enref<=1 AND emailid IN (old.ets,old.emsgid);
696693
@ UPDATE emailblob SET enref=enref-1 WHERE emailid IN (old.ets,old.emsgid);
697694
@ END;
695
+@
696
+@ -- An index on the emailblob entries which are unreferenced.
697
+@ CREATE INDEX IF NOT EXISTS repository.emailblob_nref ON emailblob(enref)
698
+@ WHERE enref<=0;
698699
;
699700
700701
/*
701702
** Code used to delete the email tables.
702703
*/
@@ -1058,10 +1059,11 @@
10581059
** Add this email to the database.
10591060
*/
10601061
static void smtp_server_route_incoming(SmtpServer *p, int bFinish){
10611062
Stmt s;
10621063
int i;
1064
+ int nEtsStart = p->nEts;
10631065
if( p->zFrom
10641066
&& p->nTo
10651067
&& blob_size(&p->msg)
10661068
&& (p->srvrFlags & SMTPSRV_DRYRUN)==0
10671069
){
@@ -1093,12 +1095,12 @@
10931095
p->idTranscript = db_last_insert_rowid();
10941096
db_multi_exec(
10951097
"UPDATE emailblob SET enref=%d WHERE emailid=%lld",
10961098
p->nEts, p->idTranscript);
10971099
}
1098
- smtp_server_send(p, "221-Transcript id %lld nref %d\r\n",
1099
- p->idTranscript, p->nEts);
1100
+ /* smtp_server_send(p, "221-Transcript id %lld nref %d\r\n",
1101
+ ** p->idTranscript, p->nEts); */
11001102
}
11011103
db_bind_int64(&s, ":ets", p->idTranscript);
11021104
db_bind_str(&s, ":etxt", &p->msg);
11031105
db_step(&s);
11041106
db_finalize(&s);
@@ -1119,10 +1121,16 @@
11191121
);
11201122
}else{
11211123
db_multi_exec(
11221124
"DELETE FROM emailblob WHERE emailid=%lld", p->idMsg
11231125
);
1126
+ p->nEts = nEtsStart;
1127
+ }
1128
+
1129
+ /* Clean out legacy entries */
1130
+ if( bFinish ){
1131
+ db_multi_exec("DELETE FROM emailblob WHERE enref<=0");
11241132
}
11251133
11261134
/* Finish the transaction after all changes are implemented */
11271135
db_commit_transaction();
11281136
}
@@ -1130,26 +1138,29 @@
11301138
}
11311139
11321140
/*
11331141
** COMMAND: test-emailblob-refcheck
11341142
**
1135
-** Usage: %fossil test-emailblob-refcheck [--repair] [--full]
1143
+** Usage: %fossil test-emailblob-refcheck [--repair] [--full] [--clean]
11361144
**
11371145
** Verify that the emailblob.enref field is correct. Report any errors.
11381146
** Use the --repair command to fix up the enref field. The --full option
11391147
** gives a full report showing the enref value on all entries in the
1140
-** emailblob table.
1148
+** emailblob table. If the --clean flags is used together with --repair,
1149
+** then emailblob table entires with enref==0 are removed.
11411150
*/
11421151
void test_refcheck_emailblob(void){
11431152
int doRepair;
11441153
int fullReport;
1154
+ int doClean;
11451155
Blob sql;
11461156
Stmt q;
11471157
int nErr = 0;
11481158
db_find_and_open_repository(0, 0);
11491159
fullReport = find_option("full",0,0)!=0;
11501160
doRepair = find_option("repair",0,0)!=0;
1161
+ doClean = find_option("clean",0,0)!=0;
11511162
verify_all_options();
11521163
if( !db_table_exists("repository","emailblob") ){
11531164
fossil_print("emailblob table is not configured - nothing to check\n");
11541165
return;
11551166
}
@@ -1166,10 +1177,16 @@
11661177
);
11671178
if( doRepair ){
11681179
db_multi_exec(
11691180
"UPDATE emailblob SET enref=(SELECT n FROM refcnt WHERE id=emailid)"
11701181
);
1182
+ if( doClean ){
1183
+ db_multi_exec(
1184
+ "UPDATE emailblob SET ets=NULL WHERE enref<=0;"
1185
+ "DELETE FROM emailblob WHERE enref<=0;"
1186
+ );
1187
+ }
11711188
}
11721189
blob_init(&sql, 0, 0);
11731190
blob_append_sql(&sql,
11741191
"SELECT a.emailid, a.enref, b.n"
11751192
" FROM emailblob AS a JOIN refcnt AS b ON a.emailid=b.id"
11761193
--- src/smtp.c
+++ src/smtp.c
@@ -637,11 +637,11 @@
637 */
638 static const char zEmailSchema[] =
639 @ -- bulk storage is in a separate table. This table can store either
640 @ -- the body of email messages or transcripts of smtp sessions.
641 @ CREATE TABLE IF NOT EXISTS repository.emailblob(
642 @ emailid INTEGER PRIMARY KEY, -- numeric idea for the entry
643 @ enref INT, -- Number of references to this blob
644 @ ets INT, -- Corresponding transcript, or NULL
645 @ etime INT, -- insertion time, secs since 1970
646 @ etxt TEXT -- content of this entry
647 @ );
@@ -680,23 +680,24 @@
680 @ -- Triggers to automatically keep the emailblob.enref field up to date
681 @ -- as entries in the emailblob, emailbox, and emailoutq tables are
682 @ -- deleted.
683 @ CREATE TRIGGER IF NOT EXISTS repository.emailblob_d1
684 @ AFTER DELETE ON emailblob BEGIN
685 @ DELETE FROM emailblob WHERE enref<=1 AND emailid=old.ets;
686 @ UPDATE emailblob SET enref=enref-1 WHERE emailid=old.ets;
687 @ END;
688 @ CREATE TRIGGER IF NOT EXISTS repository.emailbox_d1
689 @ AFTER DELETE ON emailbox BEGIN
690 @ DELETE FROM emailblob WHERE enref<=1 AND emailid=old.emsgid;
691 @ UPDATE emailblob SET enref=enref-1 WHERE emailid=old.emsgid;
692 @ END;
693 @ CREATE TRIGGER IF NOT EXISTS repository.emailoutq_d1
694 @ AFTER DELETE ON emailoutq BEGIN
695 @ DELETE FROM emailblob WHERE enref<=1 AND emailid IN (old.ets,old.emsgid);
696 @ UPDATE emailblob SET enref=enref-1 WHERE emailid IN (old.ets,old.emsgid);
697 @ END;
 
 
 
 
698 ;
699
700 /*
701 ** Code used to delete the email tables.
702 */
@@ -1058,10 +1059,11 @@
1058 ** Add this email to the database.
1059 */
1060 static void smtp_server_route_incoming(SmtpServer *p, int bFinish){
1061 Stmt s;
1062 int i;
 
1063 if( p->zFrom
1064 && p->nTo
1065 && blob_size(&p->msg)
1066 && (p->srvrFlags & SMTPSRV_DRYRUN)==0
1067 ){
@@ -1093,12 +1095,12 @@
1093 p->idTranscript = db_last_insert_rowid();
1094 db_multi_exec(
1095 "UPDATE emailblob SET enref=%d WHERE emailid=%lld",
1096 p->nEts, p->idTranscript);
1097 }
1098 smtp_server_send(p, "221-Transcript id %lld nref %d\r\n",
1099 p->idTranscript, p->nEts);
1100 }
1101 db_bind_int64(&s, ":ets", p->idTranscript);
1102 db_bind_str(&s, ":etxt", &p->msg);
1103 db_step(&s);
1104 db_finalize(&s);
@@ -1119,10 +1121,16 @@
1119 );
1120 }else{
1121 db_multi_exec(
1122 "DELETE FROM emailblob WHERE emailid=%lld", p->idMsg
1123 );
 
 
 
 
 
 
1124 }
1125
1126 /* Finish the transaction after all changes are implemented */
1127 db_commit_transaction();
1128 }
@@ -1130,26 +1138,29 @@
1130 }
1131
1132 /*
1133 ** COMMAND: test-emailblob-refcheck
1134 **
1135 ** Usage: %fossil test-emailblob-refcheck [--repair] [--full]
1136 **
1137 ** Verify that the emailblob.enref field is correct. Report any errors.
1138 ** Use the --repair command to fix up the enref field. The --full option
1139 ** gives a full report showing the enref value on all entries in the
1140 ** emailblob table.
 
1141 */
1142 void test_refcheck_emailblob(void){
1143 int doRepair;
1144 int fullReport;
 
1145 Blob sql;
1146 Stmt q;
1147 int nErr = 0;
1148 db_find_and_open_repository(0, 0);
1149 fullReport = find_option("full",0,0)!=0;
1150 doRepair = find_option("repair",0,0)!=0;
 
1151 verify_all_options();
1152 if( !db_table_exists("repository","emailblob") ){
1153 fossil_print("emailblob table is not configured - nothing to check\n");
1154 return;
1155 }
@@ -1166,10 +1177,16 @@
1166 );
1167 if( doRepair ){
1168 db_multi_exec(
1169 "UPDATE emailblob SET enref=(SELECT n FROM refcnt WHERE id=emailid)"
1170 );
 
 
 
 
 
 
1171 }
1172 blob_init(&sql, 0, 0);
1173 blob_append_sql(&sql,
1174 "SELECT a.emailid, a.enref, b.n"
1175 " FROM emailblob AS a JOIN refcnt AS b ON a.emailid=b.id"
1176
--- src/smtp.c
+++ src/smtp.c
@@ -637,11 +637,11 @@
637 */
638 static const char zEmailSchema[] =
639 @ -- bulk storage is in a separate table. This table can store either
640 @ -- the body of email messages or transcripts of smtp sessions.
641 @ CREATE TABLE IF NOT EXISTS repository.emailblob(
642 @ emailid INTEGER PRIMARY KEY AUTOINCREMENT, -- numeric idea for the entry
643 @ enref INT, -- Number of references to this blob
644 @ ets INT, -- Corresponding transcript, or NULL
645 @ etime INT, -- insertion time, secs since 1970
646 @ etxt TEXT -- content of this entry
647 @ );
@@ -680,23 +680,24 @@
680 @ -- Triggers to automatically keep the emailblob.enref field up to date
681 @ -- as entries in the emailblob, emailbox, and emailoutq tables are
682 @ -- deleted.
683 @ CREATE TRIGGER IF NOT EXISTS repository.emailblob_d1
684 @ AFTER DELETE ON emailblob BEGIN
 
685 @ UPDATE emailblob SET enref=enref-1 WHERE emailid=old.ets;
686 @ END;
687 @ CREATE TRIGGER IF NOT EXISTS repository.emailbox_d1
688 @ AFTER DELETE ON emailbox BEGIN
 
689 @ UPDATE emailblob SET enref=enref-1 WHERE emailid=old.emsgid;
690 @ END;
691 @ CREATE TRIGGER IF NOT EXISTS repository.emailoutq_d1
692 @ AFTER DELETE ON emailoutq BEGIN
 
693 @ UPDATE emailblob SET enref=enref-1 WHERE emailid IN (old.ets,old.emsgid);
694 @ END;
695 @
696 @ -- An index on the emailblob entries which are unreferenced.
697 @ CREATE INDEX IF NOT EXISTS repository.emailblob_nref ON emailblob(enref)
698 @ WHERE enref<=0;
699 ;
700
701 /*
702 ** Code used to delete the email tables.
703 */
@@ -1058,10 +1059,11 @@
1059 ** Add this email to the database.
1060 */
1061 static void smtp_server_route_incoming(SmtpServer *p, int bFinish){
1062 Stmt s;
1063 int i;
1064 int nEtsStart = p->nEts;
1065 if( p->zFrom
1066 && p->nTo
1067 && blob_size(&p->msg)
1068 && (p->srvrFlags & SMTPSRV_DRYRUN)==0
1069 ){
@@ -1093,12 +1095,12 @@
1095 p->idTranscript = db_last_insert_rowid();
1096 db_multi_exec(
1097 "UPDATE emailblob SET enref=%d WHERE emailid=%lld",
1098 p->nEts, p->idTranscript);
1099 }
1100 /* smtp_server_send(p, "221-Transcript id %lld nref %d\r\n",
1101 ** p->idTranscript, p->nEts); */
1102 }
1103 db_bind_int64(&s, ":ets", p->idTranscript);
1104 db_bind_str(&s, ":etxt", &p->msg);
1105 db_step(&s);
1106 db_finalize(&s);
@@ -1119,10 +1121,16 @@
1121 );
1122 }else{
1123 db_multi_exec(
1124 "DELETE FROM emailblob WHERE emailid=%lld", p->idMsg
1125 );
1126 p->nEts = nEtsStart;
1127 }
1128
1129 /* Clean out legacy entries */
1130 if( bFinish ){
1131 db_multi_exec("DELETE FROM emailblob WHERE enref<=0");
1132 }
1133
1134 /* Finish the transaction after all changes are implemented */
1135 db_commit_transaction();
1136 }
@@ -1130,26 +1138,29 @@
1138 }
1139
1140 /*
1141 ** COMMAND: test-emailblob-refcheck
1142 **
1143 ** Usage: %fossil test-emailblob-refcheck [--repair] [--full] [--clean]
1144 **
1145 ** Verify that the emailblob.enref field is correct. Report any errors.
1146 ** Use the --repair command to fix up the enref field. The --full option
1147 ** gives a full report showing the enref value on all entries in the
1148 ** emailblob table. If the --clean flags is used together with --repair,
1149 ** then emailblob table entires with enref==0 are removed.
1150 */
1151 void test_refcheck_emailblob(void){
1152 int doRepair;
1153 int fullReport;
1154 int doClean;
1155 Blob sql;
1156 Stmt q;
1157 int nErr = 0;
1158 db_find_and_open_repository(0, 0);
1159 fullReport = find_option("full",0,0)!=0;
1160 doRepair = find_option("repair",0,0)!=0;
1161 doClean = find_option("clean",0,0)!=0;
1162 verify_all_options();
1163 if( !db_table_exists("repository","emailblob") ){
1164 fossil_print("emailblob table is not configured - nothing to check\n");
1165 return;
1166 }
@@ -1166,10 +1177,16 @@
1177 );
1178 if( doRepair ){
1179 db_multi_exec(
1180 "UPDATE emailblob SET enref=(SELECT n FROM refcnt WHERE id=emailid)"
1181 );
1182 if( doClean ){
1183 db_multi_exec(
1184 "UPDATE emailblob SET ets=NULL WHERE enref<=0;"
1185 "DELETE FROM emailblob WHERE enref<=0;"
1186 );
1187 }
1188 }
1189 blob_init(&sql, 0, 0);
1190 blob_append_sql(&sql,
1191 "SELECT a.emailid, a.enref, b.n"
1192 " FROM emailblob AS a JOIN refcnt AS b ON a.emailid=b.id"
1193
+15 -8
--- src/webmail.c
+++ src/webmail.c
@@ -759,11 +759,11 @@
759759
@ </script>
760760
db_end_transaction(0);
761761
}
762762
763763
/*
764
-** WEBPAGE: test-emailblob
764
+** WEBPAGE: emailblob
765765
**
766766
** This page, accessible only to administrators, allows easy viewing of
767767
** the emailblob table - the table that contains the text of email messages
768768
** both inbound and outbound, and transcripts of SMTP sessions.
769769
**
@@ -779,16 +779,16 @@
779779
return;
780780
}
781781
add_content_sql_commands(g.db);
782782
style_header("emailblob table");
783783
if( id>0 ){
784
- style_submenu_element("Index", "%R/test-emailblob");
784
+ style_submenu_element("Index", "%R/emailblob");
785785
@ <ul>
786786
db_prepare(&q, "SELECT emailid FROM emailblob WHERE ets=%d", id);
787787
while( db_step(&q)==SQLITE_ROW ){
788788
int id = db_column_int(&q, 0);
789
- @ <li> <a href="%R/test-emailblob?id=%d(id)">emailblob entry %d(id)</a>
789
+ @ <li> <a href="%R/emailblob?id=%d(id)">emailblob entry %d(id)</a>
790790
}
791791
db_finalize(&q);
792792
db_prepare(&q, "SELECT euser, estate FROM emailbox WHERE emsgid=%d", id);
793793
while( db_step(&q)==SQLITE_ROW ){
794794
const char *zUser = db_column_text(&q, 0);
@@ -819,26 +819,33 @@
819819
@ <pre>%h(zContent)</pre>
820820
}
821821
db_finalize(&q);
822822
}else{
823823
db_prepare(&q,
824
- "SELECT emailid, enref, ets, datetime(etime,'unixepoch')"
824
+ "SELECT emailid, enref, ets, datetime(etime,'unixepoch'),"
825
+ " length(etxt)"
825826
" FROM emailblob ORDER BY etime DESC, emailid DESC");
826827
@ <table border="1" cellpadding="5" cellspacing="0">
827
- @ <tr><th> emailid <th> enref <th> ets <th> etime</tr>
828
+ @ <tr><th> emailid <th> enref <th> ets <th> etime <th> size </tr>
828829
while( db_step(&q)==SQLITE_ROW ){
829830
int id = db_column_int(&q, 0);
830831
int nref = db_column_int(&q, 1);
831832
int ets = db_column_int(&q, 2);
832833
const char *zDate = db_column_text(&q, 3);
834
+ int sz = db_column_int(&q,4);
833835
@ <tr>
834
- @ <td><a href="%R/test-emailblob?id=%d(id)">%d(id)</a>
835
- @ <td>%d(nref)</td>
836
- @ <td>%d(ets)</td>
836
+ @ <td align="right"><a href="%R/emailblob?id=%d(id)">%d(id)</a>
837
+ @ <td align="right">%d(nref)</td>
838
+ if( ets>0 ){
839
+ @ <td align="right">%d(ets)</td>
840
+ }else{
841
+ @ <td>&nbsp;</td>
842
+ }
837843
@ <td>%h(zDate)</td>
844
+ @ <td align="right">%,d(sz)</td>
838845
@ </tr>
839846
}
840847
@ </table>
841848
db_finalize(&q);
842849
}
843850
style_footer();
844851
}
845852
--- src/webmail.c
+++ src/webmail.c
@@ -759,11 +759,11 @@
759 @ </script>
760 db_end_transaction(0);
761 }
762
763 /*
764 ** WEBPAGE: test-emailblob
765 **
766 ** This page, accessible only to administrators, allows easy viewing of
767 ** the emailblob table - the table that contains the text of email messages
768 ** both inbound and outbound, and transcripts of SMTP sessions.
769 **
@@ -779,16 +779,16 @@
779 return;
780 }
781 add_content_sql_commands(g.db);
782 style_header("emailblob table");
783 if( id>0 ){
784 style_submenu_element("Index", "%R/test-emailblob");
785 @ <ul>
786 db_prepare(&q, "SELECT emailid FROM emailblob WHERE ets=%d", id);
787 while( db_step(&q)==SQLITE_ROW ){
788 int id = db_column_int(&q, 0);
789 @ <li> <a href="%R/test-emailblob?id=%d(id)">emailblob entry %d(id)</a>
790 }
791 db_finalize(&q);
792 db_prepare(&q, "SELECT euser, estate FROM emailbox WHERE emsgid=%d", id);
793 while( db_step(&q)==SQLITE_ROW ){
794 const char *zUser = db_column_text(&q, 0);
@@ -819,26 +819,33 @@
819 @ <pre>%h(zContent)</pre>
820 }
821 db_finalize(&q);
822 }else{
823 db_prepare(&q,
824 "SELECT emailid, enref, ets, datetime(etime,'unixepoch')"
 
825 " FROM emailblob ORDER BY etime DESC, emailid DESC");
826 @ <table border="1" cellpadding="5" cellspacing="0">
827 @ <tr><th> emailid <th> enref <th> ets <th> etime</tr>
828 while( db_step(&q)==SQLITE_ROW ){
829 int id = db_column_int(&q, 0);
830 int nref = db_column_int(&q, 1);
831 int ets = db_column_int(&q, 2);
832 const char *zDate = db_column_text(&q, 3);
 
833 @ <tr>
834 @ <td><a href="%R/test-emailblob?id=%d(id)">%d(id)</a>
835 @ <td>%d(nref)</td>
836 @ <td>%d(ets)</td>
 
 
 
 
837 @ <td>%h(zDate)</td>
 
838 @ </tr>
839 }
840 @ </table>
841 db_finalize(&q);
842 }
843 style_footer();
844 }
845
--- src/webmail.c
+++ src/webmail.c
@@ -759,11 +759,11 @@
759 @ </script>
760 db_end_transaction(0);
761 }
762
763 /*
764 ** WEBPAGE: emailblob
765 **
766 ** This page, accessible only to administrators, allows easy viewing of
767 ** the emailblob table - the table that contains the text of email messages
768 ** both inbound and outbound, and transcripts of SMTP sessions.
769 **
@@ -779,16 +779,16 @@
779 return;
780 }
781 add_content_sql_commands(g.db);
782 style_header("emailblob table");
783 if( id>0 ){
784 style_submenu_element("Index", "%R/emailblob");
785 @ <ul>
786 db_prepare(&q, "SELECT emailid FROM emailblob WHERE ets=%d", id);
787 while( db_step(&q)==SQLITE_ROW ){
788 int id = db_column_int(&q, 0);
789 @ <li> <a href="%R/emailblob?id=%d(id)">emailblob entry %d(id)</a>
790 }
791 db_finalize(&q);
792 db_prepare(&q, "SELECT euser, estate FROM emailbox WHERE emsgid=%d", id);
793 while( db_step(&q)==SQLITE_ROW ){
794 const char *zUser = db_column_text(&q, 0);
@@ -819,26 +819,33 @@
819 @ <pre>%h(zContent)</pre>
820 }
821 db_finalize(&q);
822 }else{
823 db_prepare(&q,
824 "SELECT emailid, enref, ets, datetime(etime,'unixepoch'),"
825 " length(etxt)"
826 " FROM emailblob ORDER BY etime DESC, emailid DESC");
827 @ <table border="1" cellpadding="5" cellspacing="0">
828 @ <tr><th> emailid <th> enref <th> ets <th> etime <th> size </tr>
829 while( db_step(&q)==SQLITE_ROW ){
830 int id = db_column_int(&q, 0);
831 int nref = db_column_int(&q, 1);
832 int ets = db_column_int(&q, 2);
833 const char *zDate = db_column_text(&q, 3);
834 int sz = db_column_int(&q,4);
835 @ <tr>
836 @ <td align="right"><a href="%R/emailblob?id=%d(id)">%d(id)</a>
837 @ <td align="right">%d(nref)</td>
838 if( ets>0 ){
839 @ <td align="right">%d(ets)</td>
840 }else{
841 @ <td>&nbsp;</td>
842 }
843 @ <td>%h(zDate)</td>
844 @ <td align="right">%,d(sz)</td>
845 @ </tr>
846 }
847 @ </table>
848 db_finalize(&q);
849 }
850 style_footer();
851 }
852

Keyboard Shortcuts

Open search /
Next entry (timeline) j
Previous entry (timeline) k
Open focused entry Enter
Show this help ?
Toggle theme Top nav button