Fossil SCM
minor cleanups and doc additions to the json/artifact handling.
Commit
6d0667831a0aa39f1bbf248f1869769b35463eea
Parent
c9261700345bb9f…
2 files changed
+41
-35
+20
-6
+41
-35
| --- src/json.c | ||
| +++ src/json.c | ||
| @@ -751,42 +751,49 @@ | ||
| 751 | 751 | |
| 752 | 752 | /* |
| 753 | 753 | ** Splits zStr (which must not be NULL) into tokens separated by the |
| 754 | 754 | ** given separator character. If doDeHttp is true then each element |
| 755 | 755 | ** will be passed through dehttpize(), otherwise they are used |
| 756 | -** as-is. Each new element is appended to the given target array | |
| 757 | -** object, which must not be NULL and ownership of it is not changed | |
| 758 | -** by this call. | |
| 759 | -** | |
| 760 | -** On success, returns the number of items appended to target. On | |
| 761 | -** error a NEGATIVE number is returned - its absolute value is the | |
| 762 | -** number of items inserted before the error occurred. There is a | |
| 763 | -** corner case here if we fail on the 1st element, which will cause 0 | |
| 764 | -** to be returned, which the client cannot immediately distinguish as | |
| 765 | -** success or error. | |
| 766 | -** | |
| 767 | -** Achtung: leading whitespace of elements is NOT elided. We should | |
| 768 | -** add an option to do that, but don't have one yet. | |
| 769 | -** | |
| 770 | -** Achtung: empty elements will be skipped, meaning consecutive | |
| 771 | -** empty elements are collapsed. | |
| 756 | +** as-is. Note that tokenization happens before dehttpize(), | |
| 757 | +** which is significant if the ENcoded tokens might contain the | |
| 758 | +** separator character. | |
| 759 | +** | |
| 760 | +** Each new element is appended to the given target array object, | |
| 761 | +** which must not be NULL and ownership of it is not changed by this | |
| 762 | +** call. | |
| 763 | +** | |
| 764 | +** On success, returns the number of tokens _encountered_. On error a | |
| 765 | +** NEGATIVE number is returned - its absolute value is the number of | |
| 766 | +** tokens encountered (i.e. it reveals which token in zStr was | |
| 767 | +** problematic). | |
| 768 | +** | |
| 769 | +** Achtung: leading and trailing whitespace of elements are elided. | |
| 770 | +** | |
| 771 | +** Achtung: empty elements will be skipped, meaning consecutive empty | |
| 772 | +** elements are collapsed. | |
| 772 | 773 | */ |
| 773 | 774 | int json_string_split( char const * zStr, |
| 774 | 775 | char separator, |
| 775 | 776 | char doDeHttp, |
| 776 | 777 | cson_array * target ){ |
| 777 | 778 | char const * p = zStr /* current byte */; |
| 778 | - char const * head = p /* current start-of-token */; | |
| 779 | + char const * head /* current start-of-token */; | |
| 779 | 780 | unsigned int len = 0 /* current token's length */; |
| 780 | 781 | int rc = 0 /* return code (number of added elements)*/; |
| 782 | + char skipWs = fossil_isspace(separator) ? 0 : 1; | |
| 781 | 783 | assert( zStr && target ); |
| 784 | + while( fossil_isspace(*p) ){ | |
| 785 | + ++p; | |
| 786 | + } | |
| 787 | + head = p; | |
| 782 | 788 | for( ; ; ++p){ |
| 783 | 789 | if( !*p || (separator == *p) ){ |
| 784 | 790 | if( len ){/* append head..(head+len) as next array |
| 785 | 791 | element. */ |
| 786 | 792 | cson_value * part = NULL; |
| 787 | 793 | char * zPart = NULL; |
| 794 | + ++rc; | |
| 788 | 795 | assert( head != p ); |
| 789 | 796 | zPart = (char*)malloc(len+1); |
| 790 | 797 | assert( (zPart != NULL) && "malloc failure" ); |
| 791 | 798 | memcpy(zPart, head, len); |
| 792 | 799 | zPart[len] = 0; |
| @@ -793,15 +800,13 @@ | ||
| 793 | 800 | if(doDeHttp){ |
| 794 | 801 | dehttpize(zPart); |
| 795 | 802 | } |
| 796 | 803 | if( *zPart ){ /* should only fail if someone manages to url-encoded a NUL byte */ |
| 797 | 804 | part = cson_value_new_string(zPart, strlen(zPart)); |
| 798 | - if( 0 == cson_array_append( target, part ) ){ | |
| 799 | - ++rc; | |
| 800 | - }else{ | |
| 805 | + if( 0 != cson_array_append( target, part ) ){ | |
| 801 | 806 | cson_value_free(part); |
| 802 | - rc = rc ? -rc : 0; | |
| 807 | + rc = -rc; | |
| 803 | 808 | break; |
| 804 | 809 | } |
| 805 | 810 | }else{ |
| 806 | 811 | assert(0 && "i didn't think this was possible!"); |
| 807 | 812 | fprintf(stderr,"%s:%d: My God! It's full of stars!\n", |
| @@ -818,10 +823,17 @@ | ||
| 818 | 823 | } |
| 819 | 824 | if( !*p ){ |
| 820 | 825 | break; |
| 821 | 826 | } |
| 822 | 827 | head = p+1; |
| 828 | + while( *head && fossil_isspace(*head) ){ | |
| 829 | + ++head; | |
| 830 | + ++p; | |
| 831 | + } | |
| 832 | + if(!*head){ | |
| 833 | + break; | |
| 834 | + } | |
| 823 | 835 | continue; |
| 824 | 836 | } |
| 825 | 837 | ++len; |
| 826 | 838 | } |
| 827 | 839 | return rc; |
| @@ -1328,29 +1340,23 @@ | ||
| 1328 | 1340 | |
| 1329 | 1341 | /* |
| 1330 | 1342 | ** If the given rid has any tags associated with it, this function |
| 1331 | 1343 | ** returns a JSON Array containing the tag names, else it returns |
| 1332 | 1344 | ** NULL. |
| 1345 | +** | |
| 1346 | +** See info_tags_of_checkin() for more details (this is simply a JSON | |
| 1347 | +** wrapper for that function). | |
| 1333 | 1348 | */ |
| 1334 | -cson_value * json_tags_for_rid(int rid){ | |
| 1349 | +cson_value * json_tags_for_rid(int rid, char propagatingOnly){ | |
| 1335 | 1350 | cson_value * v = NULL; |
| 1336 | - Stmt q; | |
| 1337 | - assert((rid>0) && "rid is invalid"); | |
| 1338 | - db_prepare(&q,"SELECT group_concat(substr(tagname,5), ',')" | |
| 1339 | - " FROM tag t, tagxref x " | |
| 1340 | - " WHERE x.rid=%d " | |
| 1341 | - " AND tagname GLOB 'sym-*' " | |
| 1342 | - " AND t.tagid=x.tagid AND x.tagtype>0", | |
| 1343 | - rid); | |
| 1344 | - if( (SQLITE_ROW==db_step(&q)) ){ | |
| 1345 | - char const * tags; | |
| 1346 | - tags = db_column_text(&q,0); | |
| 1347 | - if(tags && *tags){ | |
| 1351 | + char * tags = info_tags_of_checkin(rid, propagatingOnly); | |
| 1352 | + if(tags){ | |
| 1353 | + if(*tags){ | |
| 1348 | 1354 | v = json_string_split2(tags,',',0); |
| 1349 | 1355 | } |
| 1356 | + free(tags); | |
| 1350 | 1357 | } |
| 1351 | - db_finalize(&q); | |
| 1352 | 1358 | return v; |
| 1353 | 1359 | } |
| 1354 | 1360 | |
| 1355 | 1361 | |
| 1356 | 1362 | /* |
| 1357 | 1363 |
| --- src/json.c | |
| +++ src/json.c | |
| @@ -751,42 +751,49 @@ | |
| 751 | |
| 752 | /* |
| 753 | ** Splits zStr (which must not be NULL) into tokens separated by the |
| 754 | ** given separator character. If doDeHttp is true then each element |
| 755 | ** will be passed through dehttpize(), otherwise they are used |
| 756 | ** as-is. Each new element is appended to the given target array |
| 757 | ** object, which must not be NULL and ownership of it is not changed |
| 758 | ** by this call. |
| 759 | ** |
| 760 | ** On success, returns the number of items appended to target. On |
| 761 | ** error a NEGATIVE number is returned - its absolute value is the |
| 762 | ** number of items inserted before the error occurred. There is a |
| 763 | ** corner case here if we fail on the 1st element, which will cause 0 |
| 764 | ** to be returned, which the client cannot immediately distinguish as |
| 765 | ** success or error. |
| 766 | ** |
| 767 | ** Achtung: leading whitespace of elements is NOT elided. We should |
| 768 | ** add an option to do that, but don't have one yet. |
| 769 | ** |
| 770 | ** Achtung: empty elements will be skipped, meaning consecutive |
| 771 | ** empty elements are collapsed. |
| 772 | */ |
| 773 | int json_string_split( char const * zStr, |
| 774 | char separator, |
| 775 | char doDeHttp, |
| 776 | cson_array * target ){ |
| 777 | char const * p = zStr /* current byte */; |
| 778 | char const * head = p /* current start-of-token */; |
| 779 | unsigned int len = 0 /* current token's length */; |
| 780 | int rc = 0 /* return code (number of added elements)*/; |
| 781 | assert( zStr && target ); |
| 782 | for( ; ; ++p){ |
| 783 | if( !*p || (separator == *p) ){ |
| 784 | if( len ){/* append head..(head+len) as next array |
| 785 | element. */ |
| 786 | cson_value * part = NULL; |
| 787 | char * zPart = NULL; |
| 788 | assert( head != p ); |
| 789 | zPart = (char*)malloc(len+1); |
| 790 | assert( (zPart != NULL) && "malloc failure" ); |
| 791 | memcpy(zPart, head, len); |
| 792 | zPart[len] = 0; |
| @@ -793,15 +800,13 @@ | |
| 793 | if(doDeHttp){ |
| 794 | dehttpize(zPart); |
| 795 | } |
| 796 | if( *zPart ){ /* should only fail if someone manages to url-encoded a NUL byte */ |
| 797 | part = cson_value_new_string(zPart, strlen(zPart)); |
| 798 | if( 0 == cson_array_append( target, part ) ){ |
| 799 | ++rc; |
| 800 | }else{ |
| 801 | cson_value_free(part); |
| 802 | rc = rc ? -rc : 0; |
| 803 | break; |
| 804 | } |
| 805 | }else{ |
| 806 | assert(0 && "i didn't think this was possible!"); |
| 807 | fprintf(stderr,"%s:%d: My God! It's full of stars!\n", |
| @@ -818,10 +823,17 @@ | |
| 818 | } |
| 819 | if( !*p ){ |
| 820 | break; |
| 821 | } |
| 822 | head = p+1; |
| 823 | continue; |
| 824 | } |
| 825 | ++len; |
| 826 | } |
| 827 | return rc; |
| @@ -1328,29 +1340,23 @@ | |
| 1328 | |
| 1329 | /* |
| 1330 | ** If the given rid has any tags associated with it, this function |
| 1331 | ** returns a JSON Array containing the tag names, else it returns |
| 1332 | ** NULL. |
| 1333 | */ |
| 1334 | cson_value * json_tags_for_rid(int rid){ |
| 1335 | cson_value * v = NULL; |
| 1336 | Stmt q; |
| 1337 | assert((rid>0) && "rid is invalid"); |
| 1338 | db_prepare(&q,"SELECT group_concat(substr(tagname,5), ',')" |
| 1339 | " FROM tag t, tagxref x " |
| 1340 | " WHERE x.rid=%d " |
| 1341 | " AND tagname GLOB 'sym-*' " |
| 1342 | " AND t.tagid=x.tagid AND x.tagtype>0", |
| 1343 | rid); |
| 1344 | if( (SQLITE_ROW==db_step(&q)) ){ |
| 1345 | char const * tags; |
| 1346 | tags = db_column_text(&q,0); |
| 1347 | if(tags && *tags){ |
| 1348 | v = json_string_split2(tags,',',0); |
| 1349 | } |
| 1350 | } |
| 1351 | db_finalize(&q); |
| 1352 | return v; |
| 1353 | } |
| 1354 | |
| 1355 | |
| 1356 | /* |
| 1357 |
| --- src/json.c | |
| +++ src/json.c | |
| @@ -751,42 +751,49 @@ | |
| 751 | |
| 752 | /* |
| 753 | ** Splits zStr (which must not be NULL) into tokens separated by the |
| 754 | ** given separator character. If doDeHttp is true then each element |
| 755 | ** will be passed through dehttpize(), otherwise they are used |
| 756 | ** as-is. Note that tokenization happens before dehttpize(), |
| 757 | ** which is significant if the ENcoded tokens might contain the |
| 758 | ** separator character. |
| 759 | ** |
| 760 | ** Each new element is appended to the given target array object, |
| 761 | ** which must not be NULL and ownership of it is not changed by this |
| 762 | ** call. |
| 763 | ** |
| 764 | ** On success, returns the number of tokens _encountered_. On error a |
| 765 | ** NEGATIVE number is returned - its absolute value is the number of |
| 766 | ** tokens encountered (i.e. it reveals which token in zStr was |
| 767 | ** problematic). |
| 768 | ** |
| 769 | ** Achtung: leading and trailing whitespace of elements are elided. |
| 770 | ** |
| 771 | ** Achtung: empty elements will be skipped, meaning consecutive empty |
| 772 | ** elements are collapsed. |
| 773 | */ |
| 774 | int json_string_split( char const * zStr, |
| 775 | char separator, |
| 776 | char doDeHttp, |
| 777 | cson_array * target ){ |
| 778 | char const * p = zStr /* current byte */; |
| 779 | char const * head /* current start-of-token */; |
| 780 | unsigned int len = 0 /* current token's length */; |
| 781 | int rc = 0 /* return code (number of added elements)*/; |
| 782 | char skipWs = fossil_isspace(separator) ? 0 : 1; |
| 783 | assert( zStr && target ); |
| 784 | while( fossil_isspace(*p) ){ |
| 785 | ++p; |
| 786 | } |
| 787 | head = p; |
| 788 | for( ; ; ++p){ |
| 789 | if( !*p || (separator == *p) ){ |
| 790 | if( len ){/* append head..(head+len) as next array |
| 791 | element. */ |
| 792 | cson_value * part = NULL; |
| 793 | char * zPart = NULL; |
| 794 | ++rc; |
| 795 | assert( head != p ); |
| 796 | zPart = (char*)malloc(len+1); |
| 797 | assert( (zPart != NULL) && "malloc failure" ); |
| 798 | memcpy(zPart, head, len); |
| 799 | zPart[len] = 0; |
| @@ -793,15 +800,13 @@ | |
| 800 | if(doDeHttp){ |
| 801 | dehttpize(zPart); |
| 802 | } |
| 803 | if( *zPart ){ /* should only fail if someone manages to url-encoded a NUL byte */ |
| 804 | part = cson_value_new_string(zPart, strlen(zPart)); |
| 805 | if( 0 != cson_array_append( target, part ) ){ |
| 806 | cson_value_free(part); |
| 807 | rc = -rc; |
| 808 | break; |
| 809 | } |
| 810 | }else{ |
| 811 | assert(0 && "i didn't think this was possible!"); |
| 812 | fprintf(stderr,"%s:%d: My God! It's full of stars!\n", |
| @@ -818,10 +823,17 @@ | |
| 823 | } |
| 824 | if( !*p ){ |
| 825 | break; |
| 826 | } |
| 827 | head = p+1; |
| 828 | while( *head && fossil_isspace(*head) ){ |
| 829 | ++head; |
| 830 | ++p; |
| 831 | } |
| 832 | if(!*head){ |
| 833 | break; |
| 834 | } |
| 835 | continue; |
| 836 | } |
| 837 | ++len; |
| 838 | } |
| 839 | return rc; |
| @@ -1328,29 +1340,23 @@ | |
| 1340 | |
| 1341 | /* |
| 1342 | ** If the given rid has any tags associated with it, this function |
| 1343 | ** returns a JSON Array containing the tag names, else it returns |
| 1344 | ** NULL. |
| 1345 | ** |
| 1346 | ** See info_tags_of_checkin() for more details (this is simply a JSON |
| 1347 | ** wrapper for that function). |
| 1348 | */ |
| 1349 | cson_value * json_tags_for_rid(int rid, char propagatingOnly){ |
| 1350 | cson_value * v = NULL; |
| 1351 | char * tags = info_tags_of_checkin(rid, propagatingOnly); |
| 1352 | if(tags){ |
| 1353 | if(*tags){ |
| 1354 | v = json_string_split2(tags,',',0); |
| 1355 | } |
| 1356 | free(tags); |
| 1357 | } |
| 1358 | return v; |
| 1359 | } |
| 1360 | |
| 1361 | |
| 1362 | /* |
| 1363 |
+20
-6
| --- src/json_artifact.c | ||
| +++ src/json_artifact.c | ||
| @@ -24,27 +24,38 @@ | ||
| 24 | 24 | |
| 25 | 25 | /* |
| 26 | 26 | ** Internal callback for /json/artifact handlers. rid refers to |
| 27 | 27 | ** the rid of a given type of artifact, and each callback is |
| 28 | 28 | ** specialized to return a JSON form of one type of artifact. |
| 29 | +** | |
| 30 | +** Implementations may assert() that rid refers to requested artifact | |
| 31 | +** type, since mismatches in the artifact types come from | |
| 32 | +** json_page_artifact() as opposed to client data. | |
| 29 | 33 | */ |
| 30 | 34 | typedef cson_value * (*artifact_f)( int rid ); |
| 31 | 35 | |
| 36 | +/* | |
| 37 | +** Internal per-artifact-type dispatching helper. | |
| 38 | +*/ | |
| 32 | 39 | typedef struct ArtifactDispatchEntry { |
| 33 | 40 | /** |
| 34 | - Artifact type name, e.g. "checkin". | |
| 41 | + Artifact type name, e.g. "checkin", "ticket", "wiki". | |
| 35 | 42 | */ |
| 36 | 43 | char const * name; |
| 44 | + | |
| 37 | 45 | /** |
| 38 | - JSON construction callback. | |
| 39 | - */ | |
| 46 | + JSON construction callback. Creates the contents for the | |
| 47 | + payload.artifact property of /json/artifact responses. | |
| 48 | + */ | |
| 40 | 49 | artifact_f func; |
| 50 | + | |
| 41 | 51 | /** |
| 42 | 52 | Must return true if g.perm has the proper permissions to fetch |
| 43 | 53 | this info, else false. If it returns false, func() is skipped |
| 44 | - (producing no extra payload output). | |
| 45 | - */ | |
| 54 | + (producing no extra payload output) and an access error is | |
| 55 | + generated. | |
| 56 | + */ | |
| 46 | 57 | char (*permCheck)(); |
| 47 | 58 | } ArtifactDispatchEntry; |
| 48 | 59 | |
| 49 | 60 | |
| 50 | 61 | /* |
| @@ -131,11 +142,11 @@ | ||
| 131 | 142 | |
| 132 | 143 | if(zParent){ |
| 133 | 144 | SET("parentUuid", json_new_string(zParent)); |
| 134 | 145 | } |
| 135 | 146 | |
| 136 | - tmpV = json_tags_for_rid(rid); | |
| 147 | + tmpV = json_tags_for_rid(rid,0); | |
| 137 | 148 | if(tmpV){ |
| 138 | 149 | SET("tags",tmpV); |
| 139 | 150 | } |
| 140 | 151 | |
| 141 | 152 | if( showFiles ){ |
| @@ -157,10 +168,13 @@ | ||
| 157 | 168 | */ |
| 158 | 169 | static cson_value * json_artifact_ci( int rid ){ |
| 159 | 170 | return json_artifact_for_ci(rid, 1); |
| 160 | 171 | } |
| 161 | 172 | |
| 173 | +/* | |
| 174 | +** Permissions callback func for ArtifactDispatchEntry. | |
| 175 | +*/ | |
| 162 | 176 | static char perms_can_read(){ |
| 163 | 177 | return g.perm.Read ? 1 : 0; |
| 164 | 178 | } |
| 165 | 179 | |
| 166 | 180 | static ArtifactDispatchEntry ArtifactDispatchList[] = { |
| 167 | 181 |
| --- src/json_artifact.c | |
| +++ src/json_artifact.c | |
| @@ -24,27 +24,38 @@ | |
| 24 | |
| 25 | /* |
| 26 | ** Internal callback for /json/artifact handlers. rid refers to |
| 27 | ** the rid of a given type of artifact, and each callback is |
| 28 | ** specialized to return a JSON form of one type of artifact. |
| 29 | */ |
| 30 | typedef cson_value * (*artifact_f)( int rid ); |
| 31 | |
| 32 | typedef struct ArtifactDispatchEntry { |
| 33 | /** |
| 34 | Artifact type name, e.g. "checkin". |
| 35 | */ |
| 36 | char const * name; |
| 37 | /** |
| 38 | JSON construction callback. |
| 39 | */ |
| 40 | artifact_f func; |
| 41 | /** |
| 42 | Must return true if g.perm has the proper permissions to fetch |
| 43 | this info, else false. If it returns false, func() is skipped |
| 44 | (producing no extra payload output). |
| 45 | */ |
| 46 | char (*permCheck)(); |
| 47 | } ArtifactDispatchEntry; |
| 48 | |
| 49 | |
| 50 | /* |
| @@ -131,11 +142,11 @@ | |
| 131 | |
| 132 | if(zParent){ |
| 133 | SET("parentUuid", json_new_string(zParent)); |
| 134 | } |
| 135 | |
| 136 | tmpV = json_tags_for_rid(rid); |
| 137 | if(tmpV){ |
| 138 | SET("tags",tmpV); |
| 139 | } |
| 140 | |
| 141 | if( showFiles ){ |
| @@ -157,10 +168,13 @@ | |
| 157 | */ |
| 158 | static cson_value * json_artifact_ci( int rid ){ |
| 159 | return json_artifact_for_ci(rid, 1); |
| 160 | } |
| 161 | |
| 162 | static char perms_can_read(){ |
| 163 | return g.perm.Read ? 1 : 0; |
| 164 | } |
| 165 | |
| 166 | static ArtifactDispatchEntry ArtifactDispatchList[] = { |
| 167 |
| --- src/json_artifact.c | |
| +++ src/json_artifact.c | |
| @@ -24,27 +24,38 @@ | |
| 24 | |
| 25 | /* |
| 26 | ** Internal callback for /json/artifact handlers. rid refers to |
| 27 | ** the rid of a given type of artifact, and each callback is |
| 28 | ** specialized to return a JSON form of one type of artifact. |
| 29 | ** |
| 30 | ** Implementations may assert() that rid refers to requested artifact |
| 31 | ** type, since mismatches in the artifact types come from |
| 32 | ** json_page_artifact() as opposed to client data. |
| 33 | */ |
| 34 | typedef cson_value * (*artifact_f)( int rid ); |
| 35 | |
| 36 | /* |
| 37 | ** Internal per-artifact-type dispatching helper. |
| 38 | */ |
| 39 | typedef struct ArtifactDispatchEntry { |
| 40 | /** |
| 41 | Artifact type name, e.g. "checkin", "ticket", "wiki". |
| 42 | */ |
| 43 | char const * name; |
| 44 | |
| 45 | /** |
| 46 | JSON construction callback. Creates the contents for the |
| 47 | payload.artifact property of /json/artifact responses. |
| 48 | */ |
| 49 | artifact_f func; |
| 50 | |
| 51 | /** |
| 52 | Must return true if g.perm has the proper permissions to fetch |
| 53 | this info, else false. If it returns false, func() is skipped |
| 54 | (producing no extra payload output) and an access error is |
| 55 | generated. |
| 56 | */ |
| 57 | char (*permCheck)(); |
| 58 | } ArtifactDispatchEntry; |
| 59 | |
| 60 | |
| 61 | /* |
| @@ -131,11 +142,11 @@ | |
| 142 | |
| 143 | if(zParent){ |
| 144 | SET("parentUuid", json_new_string(zParent)); |
| 145 | } |
| 146 | |
| 147 | tmpV = json_tags_for_rid(rid,0); |
| 148 | if(tmpV){ |
| 149 | SET("tags",tmpV); |
| 150 | } |
| 151 | |
| 152 | if( showFiles ){ |
| @@ -157,10 +168,13 @@ | |
| 168 | */ |
| 169 | static cson_value * json_artifact_ci( int rid ){ |
| 170 | return json_artifact_for_ci(rid, 1); |
| 171 | } |
| 172 | |
| 173 | /* |
| 174 | ** Permissions callback func for ArtifactDispatchEntry. |
| 175 | */ |
| 176 | static char perms_can_read(){ |
| 177 | return g.perm.Read ? 1 : 0; |
| 178 | } |
| 179 | |
| 180 | static ArtifactDispatchEntry ArtifactDispatchList[] = { |
| 181 |