Fossil SCM

Add the "/doc" method on the server.

drh 2008-05-15 16:58 trunk
Commit 7351b6346d153f984d38ece87b52c222346ed3be
+1
--- src/db.c
+++ src/db.c
@@ -111,10 +111,11 @@
111111
sqlite3_commit_hook(g.db, db_verify_at_commit, 0);
112112
}
113113
nBegin++;
114114
}
115115
void db_end_transaction(int rollbackFlag){
116
+ if( nBegin<=0 ) return;
116117
if( rollbackFlag ) doRollback = 1;
117118
nBegin--;
118119
if( nBegin==0 ){
119120
int i;
120121
for(i=0; doRollback==0 && i<nCommitHook; i++){
121122
--- src/db.c
+++ src/db.c
@@ -111,10 +111,11 @@
111 sqlite3_commit_hook(g.db, db_verify_at_commit, 0);
112 }
113 nBegin++;
114 }
115 void db_end_transaction(int rollbackFlag){
 
116 if( rollbackFlag ) doRollback = 1;
117 nBegin--;
118 if( nBegin==0 ){
119 int i;
120 for(i=0; doRollback==0 && i<nCommitHook; i++){
121
--- src/db.c
+++ src/db.c
@@ -111,10 +111,11 @@
111 sqlite3_commit_hook(g.db, db_verify_at_commit, 0);
112 }
113 nBegin++;
114 }
115 void db_end_transaction(int rollbackFlag){
116 if( nBegin<=0 ) return;
117 if( rollbackFlag ) doRollback = 1;
118 nBegin--;
119 if( nBegin==0 ){
120 int i;
121 for(i=0; doRollback==0 && i<nCommitHook; i++){
122
+187
--- src/info.c
+++ src/info.c
@@ -790,10 +790,197 @@
790790
@ %h(blob_str(&content))
791791
@ </pre></blockquote>
792792
blob_reset(&content);
793793
style_footer();
794794
}
795
+
796
+/*
797
+** Guess the mime-type of a document based on its name.
798
+*/
799
+const char *mimetype_from_name(const char *zName){
800
+ const char *z;
801
+ int i;
802
+ char zSuffix[20];
803
+ static const struct {
804
+ const char *zSuffix;
805
+ const char *zMimetype;
806
+ } aMime[] = {
807
+ { "html", "text/html" },
808
+ { "htm", "text/html" },
809
+ { "wiki", "application/x-fossil-wiki" },
810
+ { "txt", "text/plain" },
811
+ { "jpg", "image/jpeg" },
812
+ { "jpeg", "image/jpeg" },
813
+ { "gif", "image/gif" },
814
+ { "png", "image/png" },
815
+ { "css", "text/css" },
816
+ };
817
+
818
+ z = zName;
819
+ for(i=0; zName[i]; i++){
820
+ if( zName[i]=='.' ) z = &zName[i+1];
821
+ }
822
+ i = strlen(z);
823
+ if( i<sizeof(zSuffix)-1 ){
824
+ strcpy(zSuffix, z);
825
+ for(i=0; zSuffix[i]; i++) zSuffix[i] = tolower(zSuffix[i]);
826
+ for(i=0; i<sizeof(aMime)/sizeof(aMime[0]); i++){
827
+ if( strcmp(zSuffix, aMime[i].zSuffix)==0 ){
828
+ return aMime[i].zMimetype;
829
+ }
830
+ }
831
+ }
832
+ return "application/x-fossil-artifact";
833
+}
834
+
835
+/*
836
+** WEBPAGE: doc
837
+** URL: /doc?name=BASELINE/PATH
838
+**
839
+** BASELINE can be either a baseline uuid prefix or magic words "tip"
840
+** to me the most recently checked in baseline or "ckout" to mean the
841
+** content of the local checkout, if any. PATH is the relative pathname
842
+** of some file. This method returns the file content.
843
+**
844
+** If PATH matches the patterns *.wiki or *.txt then formatting content
845
+** is added before returning the file. For all other names, the content
846
+** is returned straight without any interpretation or processing.
847
+*/
848
+void doc_page(void){
849
+ const char *zName; /* Argument to the /doc page */
850
+ const char *zMime; /* Document MIME type */
851
+ int vid = 0; /* Artifact of baseline */
852
+ int rid = 0; /* Artifact of file */
853
+ int i; /* Loop counter */
854
+ Blob filebody; /* Content of the documentation file */
855
+ char zBaseline[UUID_SIZE+1]; /* Baseline UUID */
856
+
857
+ login_check_credentials();
858
+ if( !g.okRead ){ login_needed(); return; }
859
+ zName = PD("name", "tip/index.wiki");
860
+ for(i=0; zName[i] && zName[i]!='/'; i++){}
861
+ if( zName[i]==0 || i>UUID_SIZE ){
862
+ goto doc_not_found;
863
+ }
864
+ memcpy(zBaseline, zName, i);
865
+ zBaseline[i] = 0;
866
+ zName += i;
867
+ while( zName[0]=='/' ){ zName++; }
868
+ if( !file_is_simple_pathname(zName) ){
869
+ goto doc_not_found;
870
+ }
871
+ if( strcmp(zBaseline,"ckout")==0 ){
872
+ /* Read from the local checkout */
873
+ char *zFullpath;
874
+ db_must_be_within_tree();
875
+ zFullpath = mprintf("%s/%s", g.zLocalRoot, zName);
876
+ if( !file_isfile(zFullpath) ){
877
+ goto doc_not_found;
878
+ }
879
+ if( blob_read_from_file(&filebody, zFullpath)<0 ){
880
+ goto doc_not_found;
881
+ }
882
+ }else{
883
+ db_begin_transaction();
884
+ if( strcmp(zBaseline,"tip")==0 ){
885
+ vid = db_int(0, "SELECT objid FROM event WHERE type='ci'"
886
+ " ORDER BY mtime DESC LIMIT 1");
887
+ }else{
888
+ vid = name_to_rid(zBaseline);
889
+ }
890
+
891
+ /* Create the baseline cache if it does not already exist */
892
+ db_multi_exec(
893
+ "CREATE TABLE IF NOT EXISTS vcache(\n"
894
+ " vid INTEGER, -- baseline ID\n"
895
+ " fname TEXT, -- filename\n"
896
+ " rid INTEGER, -- artifact ID\n"
897
+ " UNIQUE(vid,fname,rid)\n"
898
+ ")"
899
+ );
900
+
901
+ /* Check to see if the documentation file artifact ID is contained
902
+ ** in the baseline cache */
903
+ rid = db_int(0, "SELECT rid FROM vcache"
904
+ " WHERE vid=%d AND fname=%Q", vid, zName);
905
+ if( rid==0 && db_exists("SELECT 1 FROM vcache WHERE vid=%d", vid) ){
906
+ goto doc_not_found;
907
+ }
908
+
909
+ if( rid==0 ){
910
+ Stmt s;
911
+ Blob baseline;
912
+ Manifest m;
913
+
914
+ /* Add the vid baseline to the cache */
915
+ if( db_int(0, "SELECT count(*) FROM vcache")>10000 ){
916
+ db_multi_exec("DELETE FROM vcache");
917
+ }
918
+ if( content_get(vid, &baseline)==0 ){
919
+ goto doc_not_found;
920
+ }
921
+ if( manifest_parse(&m, &baseline)==0 || m.type!=CFTYPE_MANIFEST ){
922
+ goto doc_not_found;
923
+ }
924
+ db_prepare(&s,
925
+ "INSERT INTO vcache(vid,fname,rid)"
926
+ " SELECT %d, :fname, rid FROM blob"
927
+ " WHERE uuid=:uuid",
928
+ vid
929
+ );
930
+ for(i=0; i<m.nFile; i++){
931
+ db_bind_text(&s, ":fname", m.aFile[i].zName);
932
+ db_bind_text(&s, ":uuid", m.aFile[i].zUuid);
933
+ db_step(&s);
934
+ db_reset(&s);
935
+ }
936
+ db_finalize(&s);
937
+ manifest_clear(&m);
938
+
939
+ /* Try again to find the file */
940
+ rid = db_int(0, "SELECT rid FROM vcache"
941
+ " WHERE vid=%d AND fname=%Q", vid, zName);
942
+ }
943
+ if( rid==0 ){
944
+ goto doc_not_found;
945
+ }
946
+
947
+ /* Get the file content */
948
+ if( content_get(rid, &filebody)==0 ){
949
+ goto doc_not_found;
950
+ }
951
+ db_end_transaction(0);
952
+ }
953
+
954
+ /* The file is now contained in the filebody blob. Deliver the
955
+ ** file to the user
956
+ */
957
+ zMime = mimetype_from_name(zName);
958
+ if( strcmp(zMime, "application/x-fossil-wiki")==0 ){
959
+ style_header("Documentation");
960
+ wiki_convert(&filebody, 0, 0);
961
+ style_footer();
962
+ }else if( strcmp(zMime, "text/plain")==0 ){
963
+ style_header("Documentation");
964
+ @ <blockquote><pre>
965
+ @ %h(blob_str(&filebody))
966
+ @ </pre></blockquote>
967
+ style_footer();
968
+ }else{
969
+ cgi_set_content_type(zMime);
970
+ cgi_set_content(&filebody);
971
+ }
972
+ return;
973
+
974
+doc_not_found:
975
+ /* Jump here when unable to locate the document */
976
+ db_end_transaction(0);
977
+ style_header("Document Not Found");
978
+ @ <p>No such document: %h(PD("name","tip/index.wiki"))</p>
979
+ style_footer();
980
+ return;
981
+}
795982
796983
/*
797984
** WEBPAGE: info
798985
** URL: info/UUID
799986
**
800987
--- src/info.c
+++ src/info.c
@@ -790,10 +790,197 @@
790 @ %h(blob_str(&content))
791 @ </pre></blockquote>
792 blob_reset(&content);
793 style_footer();
794 }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
795
796 /*
797 ** WEBPAGE: info
798 ** URL: info/UUID
799 **
800
--- src/info.c
+++ src/info.c
@@ -790,10 +790,197 @@
790 @ %h(blob_str(&content))
791 @ </pre></blockquote>
792 blob_reset(&content);
793 style_footer();
794 }
795
796 /*
797 ** Guess the mime-type of a document based on its name.
798 */
799 const char *mimetype_from_name(const char *zName){
800 const char *z;
801 int i;
802 char zSuffix[20];
803 static const struct {
804 const char *zSuffix;
805 const char *zMimetype;
806 } aMime[] = {
807 { "html", "text/html" },
808 { "htm", "text/html" },
809 { "wiki", "application/x-fossil-wiki" },
810 { "txt", "text/plain" },
811 { "jpg", "image/jpeg" },
812 { "jpeg", "image/jpeg" },
813 { "gif", "image/gif" },
814 { "png", "image/png" },
815 { "css", "text/css" },
816 };
817
818 z = zName;
819 for(i=0; zName[i]; i++){
820 if( zName[i]=='.' ) z = &zName[i+1];
821 }
822 i = strlen(z);
823 if( i<sizeof(zSuffix)-1 ){
824 strcpy(zSuffix, z);
825 for(i=0; zSuffix[i]; i++) zSuffix[i] = tolower(zSuffix[i]);
826 for(i=0; i<sizeof(aMime)/sizeof(aMime[0]); i++){
827 if( strcmp(zSuffix, aMime[i].zSuffix)==0 ){
828 return aMime[i].zMimetype;
829 }
830 }
831 }
832 return "application/x-fossil-artifact";
833 }
834
835 /*
836 ** WEBPAGE: doc
837 ** URL: /doc?name=BASELINE/PATH
838 **
839 ** BASELINE can be either a baseline uuid prefix or magic words "tip"
840 ** to me the most recently checked in baseline or "ckout" to mean the
841 ** content of the local checkout, if any. PATH is the relative pathname
842 ** of some file. This method returns the file content.
843 **
844 ** If PATH matches the patterns *.wiki or *.txt then formatting content
845 ** is added before returning the file. For all other names, the content
846 ** is returned straight without any interpretation or processing.
847 */
848 void doc_page(void){
849 const char *zName; /* Argument to the /doc page */
850 const char *zMime; /* Document MIME type */
851 int vid = 0; /* Artifact of baseline */
852 int rid = 0; /* Artifact of file */
853 int i; /* Loop counter */
854 Blob filebody; /* Content of the documentation file */
855 char zBaseline[UUID_SIZE+1]; /* Baseline UUID */
856
857 login_check_credentials();
858 if( !g.okRead ){ login_needed(); return; }
859 zName = PD("name", "tip/index.wiki");
860 for(i=0; zName[i] && zName[i]!='/'; i++){}
861 if( zName[i]==0 || i>UUID_SIZE ){
862 goto doc_not_found;
863 }
864 memcpy(zBaseline, zName, i);
865 zBaseline[i] = 0;
866 zName += i;
867 while( zName[0]=='/' ){ zName++; }
868 if( !file_is_simple_pathname(zName) ){
869 goto doc_not_found;
870 }
871 if( strcmp(zBaseline,"ckout")==0 ){
872 /* Read from the local checkout */
873 char *zFullpath;
874 db_must_be_within_tree();
875 zFullpath = mprintf("%s/%s", g.zLocalRoot, zName);
876 if( !file_isfile(zFullpath) ){
877 goto doc_not_found;
878 }
879 if( blob_read_from_file(&filebody, zFullpath)<0 ){
880 goto doc_not_found;
881 }
882 }else{
883 db_begin_transaction();
884 if( strcmp(zBaseline,"tip")==0 ){
885 vid = db_int(0, "SELECT objid FROM event WHERE type='ci'"
886 " ORDER BY mtime DESC LIMIT 1");
887 }else{
888 vid = name_to_rid(zBaseline);
889 }
890
891 /* Create the baseline cache if it does not already exist */
892 db_multi_exec(
893 "CREATE TABLE IF NOT EXISTS vcache(\n"
894 " vid INTEGER, -- baseline ID\n"
895 " fname TEXT, -- filename\n"
896 " rid INTEGER, -- artifact ID\n"
897 " UNIQUE(vid,fname,rid)\n"
898 ")"
899 );
900
901 /* Check to see if the documentation file artifact ID is contained
902 ** in the baseline cache */
903 rid = db_int(0, "SELECT rid FROM vcache"
904 " WHERE vid=%d AND fname=%Q", vid, zName);
905 if( rid==0 && db_exists("SELECT 1 FROM vcache WHERE vid=%d", vid) ){
906 goto doc_not_found;
907 }
908
909 if( rid==0 ){
910 Stmt s;
911 Blob baseline;
912 Manifest m;
913
914 /* Add the vid baseline to the cache */
915 if( db_int(0, "SELECT count(*) FROM vcache")>10000 ){
916 db_multi_exec("DELETE FROM vcache");
917 }
918 if( content_get(vid, &baseline)==0 ){
919 goto doc_not_found;
920 }
921 if( manifest_parse(&m, &baseline)==0 || m.type!=CFTYPE_MANIFEST ){
922 goto doc_not_found;
923 }
924 db_prepare(&s,
925 "INSERT INTO vcache(vid,fname,rid)"
926 " SELECT %d, :fname, rid FROM blob"
927 " WHERE uuid=:uuid",
928 vid
929 );
930 for(i=0; i<m.nFile; i++){
931 db_bind_text(&s, ":fname", m.aFile[i].zName);
932 db_bind_text(&s, ":uuid", m.aFile[i].zUuid);
933 db_step(&s);
934 db_reset(&s);
935 }
936 db_finalize(&s);
937 manifest_clear(&m);
938
939 /* Try again to find the file */
940 rid = db_int(0, "SELECT rid FROM vcache"
941 " WHERE vid=%d AND fname=%Q", vid, zName);
942 }
943 if( rid==0 ){
944 goto doc_not_found;
945 }
946
947 /* Get the file content */
948 if( content_get(rid, &filebody)==0 ){
949 goto doc_not_found;
950 }
951 db_end_transaction(0);
952 }
953
954 /* The file is now contained in the filebody blob. Deliver the
955 ** file to the user
956 */
957 zMime = mimetype_from_name(zName);
958 if( strcmp(zMime, "application/x-fossil-wiki")==0 ){
959 style_header("Documentation");
960 wiki_convert(&filebody, 0, 0);
961 style_footer();
962 }else if( strcmp(zMime, "text/plain")==0 ){
963 style_header("Documentation");
964 @ <blockquote><pre>
965 @ %h(blob_str(&filebody))
966 @ </pre></blockquote>
967 style_footer();
968 }else{
969 cgi_set_content_type(zMime);
970 cgi_set_content(&filebody);
971 }
972 return;
973
974 doc_not_found:
975 /* Jump here when unable to locate the document */
976 db_end_transaction(0);
977 style_header("Document Not Found");
978 @ <p>No such document: %h(PD("name","tip/index.wiki"))</p>
979 style_footer();
980 return;
981 }
982
983 /*
984 ** WEBPAGE: info
985 ** URL: info/UUID
986 **
987
--- src/wikiformat.c
+++ src/wikiformat.c
@@ -151,12 +151,12 @@
151151
#define MARKUP_H3 18
152152
#define MARKUP_H4 19
153153
#define MARKUP_H5 20
154154
#define MARKUP_H6 21
155155
#define MARKUP_HR 22
156
-#define MARKUP_IMG 23
157
-#define MARKUP_I 24
156
+#define MARKUP_I 23
157
+#define MARKUP_IMG 24
158158
#define MARKUP_KBD 25
159159
#define MARKUP_LI 26
160160
#define MARKUP_NOBR 27
161161
#define MARKUP_NOWIKI 28
162162
#define MARKUP_OL 29
@@ -233,14 +233,14 @@
233233
{ "h4", MARKUP_H4, MUTYPE_BLOCK, ATTR_ALIGN },
234234
{ "h5", MARKUP_H5, MUTYPE_BLOCK, ATTR_ALIGN },
235235
{ "h6", MARKUP_H6, MUTYPE_BLOCK, ATTR_ALIGN },
236236
{ "hr", MARKUP_HR, MUTYPE_SINGLE,
237237
ATTR_ALIGN|ATTR_COLOR|ATTR_SIZE|ATTR_WIDTH },
238
+ { "i", MARKUP_I, MUTYPE_FONT, 0 },
238239
{ "img", MARKUP_IMG, MUTYPE_SINGLE,
239240
ATTR_ALIGN|ATTR_ALT|ATTR_BORDER|ATTR_HEIGHT|
240241
ATTR_HSPACE|ATTR_SRC|ATTR_VSPACE|ATTR_WIDTH },
241
- { "i", MARKUP_I, MUTYPE_FONT, 0 },
242242
{ "kbd", MARKUP_KBD, MUTYPE_FONT, 0 },
243243
{ "li", MARKUP_LI, MUTYPE_LI,
244244
ATTR_TYPE|ATTR_VALUE },
245245
{ "nobr", MARKUP_NOBR, MUTYPE_FONT, 0 },
246246
{ "nowiki", MARKUP_NOWIKI, MUTYPE_SPECIAL, 0 },
247247
--- src/wikiformat.c
+++ src/wikiformat.c
@@ -151,12 +151,12 @@
151 #define MARKUP_H3 18
152 #define MARKUP_H4 19
153 #define MARKUP_H5 20
154 #define MARKUP_H6 21
155 #define MARKUP_HR 22
156 #define MARKUP_IMG 23
157 #define MARKUP_I 24
158 #define MARKUP_KBD 25
159 #define MARKUP_LI 26
160 #define MARKUP_NOBR 27
161 #define MARKUP_NOWIKI 28
162 #define MARKUP_OL 29
@@ -233,14 +233,14 @@
233 { "h4", MARKUP_H4, MUTYPE_BLOCK, ATTR_ALIGN },
234 { "h5", MARKUP_H5, MUTYPE_BLOCK, ATTR_ALIGN },
235 { "h6", MARKUP_H6, MUTYPE_BLOCK, ATTR_ALIGN },
236 { "hr", MARKUP_HR, MUTYPE_SINGLE,
237 ATTR_ALIGN|ATTR_COLOR|ATTR_SIZE|ATTR_WIDTH },
 
238 { "img", MARKUP_IMG, MUTYPE_SINGLE,
239 ATTR_ALIGN|ATTR_ALT|ATTR_BORDER|ATTR_HEIGHT|
240 ATTR_HSPACE|ATTR_SRC|ATTR_VSPACE|ATTR_WIDTH },
241 { "i", MARKUP_I, MUTYPE_FONT, 0 },
242 { "kbd", MARKUP_KBD, MUTYPE_FONT, 0 },
243 { "li", MARKUP_LI, MUTYPE_LI,
244 ATTR_TYPE|ATTR_VALUE },
245 { "nobr", MARKUP_NOBR, MUTYPE_FONT, 0 },
246 { "nowiki", MARKUP_NOWIKI, MUTYPE_SPECIAL, 0 },
247
--- src/wikiformat.c
+++ src/wikiformat.c
@@ -151,12 +151,12 @@
151 #define MARKUP_H3 18
152 #define MARKUP_H4 19
153 #define MARKUP_H5 20
154 #define MARKUP_H6 21
155 #define MARKUP_HR 22
156 #define MARKUP_I 23
157 #define MARKUP_IMG 24
158 #define MARKUP_KBD 25
159 #define MARKUP_LI 26
160 #define MARKUP_NOBR 27
161 #define MARKUP_NOWIKI 28
162 #define MARKUP_OL 29
@@ -233,14 +233,14 @@
233 { "h4", MARKUP_H4, MUTYPE_BLOCK, ATTR_ALIGN },
234 { "h5", MARKUP_H5, MUTYPE_BLOCK, ATTR_ALIGN },
235 { "h6", MARKUP_H6, MUTYPE_BLOCK, ATTR_ALIGN },
236 { "hr", MARKUP_HR, MUTYPE_SINGLE,
237 ATTR_ALIGN|ATTR_COLOR|ATTR_SIZE|ATTR_WIDTH },
238 { "i", MARKUP_I, MUTYPE_FONT, 0 },
239 { "img", MARKUP_IMG, MUTYPE_SINGLE,
240 ATTR_ALIGN|ATTR_ALT|ATTR_BORDER|ATTR_HEIGHT|
241 ATTR_HSPACE|ATTR_SRC|ATTR_VSPACE|ATTR_WIDTH },
 
242 { "kbd", MARKUP_KBD, MUTYPE_FONT, 0 },
243 { "li", MARKUP_LI, MUTYPE_LI,
244 ATTR_TYPE|ATTR_VALUE },
245 { "nobr", MARKUP_NOBR, MUTYPE_FONT, 0 },
246 { "nowiki", MARKUP_NOWIKI, MUTYPE_SPECIAL, 0 },
247

Keyboard Shortcuts

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