Fossil SCM
Add attachment size info to the attachments-as-json state and filter empty files from that list (the UI has never allowed empty attachments and size 0 denotes 'deleted'). Add attachment_resolve_target().
Commit
3bc2b92104c3518a2e9f26c117eb0e89d56b84ba8f2955ee7aca8afe5e53cbac
Parent
275c586426871f1…
1 file changed
+130
-19
+130
-19
| --- src/attach.c | ||
| +++ src/attach.c | ||
| @@ -63,10 +63,66 @@ | ||
| 63 | 63 | rc = db_column_int(&q, 0); |
| 64 | 64 | } |
| 65 | 65 | db_reset(&q); |
| 66 | 66 | return rc; |
| 67 | 67 | } |
| 68 | + | |
| 69 | +/* | |
| 70 | +** Given a full attachment target ID, returns its blob.rid. zTarget | |
| 71 | +** must be a full wiki page name, tech-note ID, ticket ID, or forum | |
| 72 | +** post hash. Returns 0 if no match is found. | |
| 73 | +*/ | |
| 74 | +int attachment_resolve_target(const char *zTarget){ | |
| 75 | + int rid = 0; | |
| 76 | + int eType = attachment_target_type(zTarget); | |
| 77 | + switch(eType){ | |
| 78 | + case CFTYPE_EVENT: | |
| 79 | + rid = db_int( | |
| 80 | + 0, "SELECT b.rid FROM blob b, tag t, tagxref x\n" | |
| 81 | + "WHERE tagname='event-%q'\n" | |
| 82 | + "AND x.tagtype>0\n" | |
| 83 | + "AND x.tagid=t.tagid\n" | |
| 84 | + "AND x.rid=b.rid\n" | |
| 85 | + "ORDER BY x.mtime DESC", | |
| 86 | + zTarget | |
| 87 | + ); | |
| 88 | + break; | |
| 89 | + case CFTYPE_FORUM: | |
| 90 | + rid = db_int( | |
| 91 | + 0, "SELECT f.fpid FROM forumpost f, blob b\n" | |
| 92 | + "WHERE f.fpid=b.rid\n" | |
| 93 | + "AND b.uuid=%Q", | |
| 94 | + zTarget | |
| 95 | + ); | |
| 96 | + break; | |
| 97 | + case CFTYPE_TICKET: | |
| 98 | + rid = db_int( | |
| 99 | + 0, "SELECT b.rid FROM blob b, tag t, tagxref x\n" | |
| 100 | + "WHERE tagname='tkt-%q'\n" | |
| 101 | + "AND x.tagtype>0\n" | |
| 102 | + "AND x.tagid=t.tagid\n" | |
| 103 | + "AND x.rid=b.rid\n" | |
| 104 | + "ORDER BY x.mtime DESC", | |
| 105 | + zTarget | |
| 106 | + ); | |
| 107 | + break; | |
| 108 | + case CFTYPE_WIKI: | |
| 109 | + rid = db_int( | |
| 110 | + 0, "SELECT b.rid FROM blob b, tag t, tagxref x\n" | |
| 111 | + "WHERE tagname='wiki-%q'\n" | |
| 112 | + "AND x.tagtype>0\n" | |
| 113 | + "AND x.tagid=t.tagid\n" | |
| 114 | + "AND x.rid=b.rid\n" | |
| 115 | + "ORDER BY x.mtime DESC", | |
| 116 | + zTarget | |
| 117 | + ); | |
| 118 | + break; | |
| 119 | + default: | |
| 120 | + break; | |
| 121 | + } | |
| 122 | + return rid; | |
| 123 | +} | |
| 68 | 124 | |
| 69 | 125 | /* |
| 70 | 126 | ** For a given aritfact ID and type (from the CFTYPE_xyz enum), |
| 71 | 127 | ** returns true if the current user could hypothetically attach |
| 72 | 128 | ** something to it, else returns 0. |
| @@ -511,11 +567,11 @@ | ||
| 511 | 567 | const char *aContent; |
| 512 | 568 | const char *zName; |
| 513 | 569 | const char *zComment; |
| 514 | 570 | const char *zTarget; |
| 515 | 571 | const char *zFrom; /* Origin page - redirect here after saving */ |
| 516 | - char * zTo = 0; /* Optionally redirect here after saving */ | |
| 572 | + char *zTo = 0; /* Optionally redirect here after saving */ | |
| 517 | 573 | char *zTargetType = 0; |
| 518 | 574 | char *zExtraFree = 0; |
| 519 | 575 | int szContent; |
| 520 | 576 | int goodCaptcha = 1; |
| 521 | 577 | int szLimit = 0; |
| @@ -1222,11 +1278,11 @@ | ||
| 1222 | 1278 | const char *zHeader, /* Header to display with attachments */ |
| 1223 | 1279 | const int flags /* ATTACHLIST_... flags */ |
| 1224 | 1280 | ){ |
| 1225 | 1281 | int cnt = 0; |
| 1226 | 1282 | char szBuf[36] = {0}; /* scratchpad for attachment size value */ |
| 1227 | - const char * zLinkTgt = (ATTACHLIST_TARGET_BLANK & flags) | |
| 1283 | + const char *zLinkTgt = (ATTACHLIST_TARGET_BLANK & flags) | |
| 1228 | 1284 | ? " target=\"_blank\"" : ""; |
| 1229 | 1285 | Stmt q; |
| 1230 | 1286 | db_prepare(&q, |
| 1231 | 1287 | "SELECT datetime(mtime,toLocal()), filename, user," |
| 1232 | 1288 | " (SELECT uuid FROM blob WHERE rid=attachid), src, target, " |
| @@ -1435,14 +1491,14 @@ | ||
| 1435 | 1491 | } |
| 1436 | 1492 | for(i = 2; i < g.argc; ++i){ |
| 1437 | 1493 | const char *zPage = g.argv[i]; |
| 1438 | 1494 | db_bind_text(&q, ":tgtname", zPage); |
| 1439 | 1495 | while(SQLITE_ROW == db_step(&q)){ |
| 1440 | - const char * zTime = db_column_text(&q, 0); | |
| 1441 | - const char * zSrc = db_column_text(&q, 1); | |
| 1442 | - const char * zTarget = db_column_text(&q, 2); | |
| 1443 | - const char * zName = db_column_text(&q, 3); | |
| 1496 | + const char *zTime = db_column_text(&q, 0); | |
| 1497 | + const char *zSrc = db_column_text(&q, 1); | |
| 1498 | + const char *zTarget = db_column_text(&q, 2); | |
| 1499 | + const char *zName = db_column_text(&q, 3); | |
| 1444 | 1500 | printf("%-20s %s %.12s %s\n", zTarget, zTime, zSrc, zName); |
| 1445 | 1501 | } |
| 1446 | 1502 | db_reset(&q); |
| 1447 | 1503 | } |
| 1448 | 1504 | db_finalize(&q); |
| @@ -1495,36 +1551,49 @@ | ||
| 1495 | 1551 | case CFTYPE_TICKET: zTgt = pManifest->zTicketUuid; break; |
| 1496 | 1552 | default: |
| 1497 | 1553 | goto empty_result; |
| 1498 | 1554 | } |
| 1499 | 1555 | db_prepare(&q, |
| 1500 | - "SELECT datetime(mtime), src, target, filename, isLatest," | |
| 1501 | - " (SELECT uuid FROM blob WHERE rid=attachid) uuid" | |
| 1502 | - " FROM attachment" | |
| 1503 | - " WHERE target=%Q" | |
| 1504 | - " AND (isLatest OR %d)" | |
| 1505 | - " ORDER BY target, isLatest DESC, mtime DESC", | |
| 1556 | + "SELECT datetime(mtime), a.src, a.target, a.filename, a.isLatest,\n" | |
| 1557 | + " b2.size, b1.uuid, a.comment\n" | |
| 1558 | + " FROM attachment a, blob b1, blob b2\n" | |
| 1559 | + " WHERE a.target=%Q\n" | |
| 1560 | + " AND b1.rid=a.attachid\n" | |
| 1561 | + " AND b2.uuid=a.src\n" | |
| 1562 | + " AND b2.size>0\n" | |
| 1563 | + " AND (a.isLatest OR %d)\n" | |
| 1564 | + " ORDER BY a.target, a.isLatest DESC, a.mtime DESC\n", | |
| 1506 | 1565 | zTgt, !bLatestOnly |
| 1507 | 1566 | ); |
| 1508 | 1567 | while(SQLITE_ROW == db_step(&q)){ |
| 1509 | - const char * zTime = db_column_text(&q, 0); | |
| 1510 | - const char * zSrc = db_column_text(&q, 1); | |
| 1511 | - const char * zTarget = db_column_text(&q, 2); | |
| 1512 | - const char * zName = db_column_text(&q, 3); | |
| 1568 | + const char *zTime = db_column_text(&q, 0); | |
| 1569 | + const char *zSrc = db_column_text(&q, 1); | |
| 1570 | + const char *zTarget = db_column_text(&q, 2); | |
| 1571 | + const char *zName = db_column_text(&q, 3); | |
| 1513 | 1572 | const int isLatest = db_column_int(&q, 4); |
| 1514 | - const char * zUuid = db_column_text(&q, 5); | |
| 1573 | + const int sz = db_column_int(&q, 5); | |
| 1574 | + const char *zUuid = db_column_text(&q, 6); | |
| 1575 | + const char *zComment = db_column_text(&q, 7); | |
| 1515 | 1576 | if(!i++){ |
| 1516 | 1577 | blob_append_char(pOut, '['); |
| 1517 | 1578 | }else{ |
| 1518 | 1579 | blob_append_char(pOut, ','); |
| 1519 | 1580 | } |
| 1520 | 1581 | blob_appendf( |
| 1521 | 1582 | pOut, |
| 1522 | 1583 | "{\"uuid\": %!j, \"src\": %!j, \"target\": %!j, " |
| 1523 | - "\"filename\": %!j, \"mtime\": %!j, \"isLatest\": %s}", | |
| 1584 | + "\"filename\": %!j, \"size\":%d, \"mtime\": %!j, " | |
| 1585 | + "\"isLatest\": %s,\"comment\": ", | |
| 1524 | 1586 | zUuid, zSrc, zTarget, |
| 1525 | - zName, zTime, isLatest ? "true" : "false"); | |
| 1587 | + zName, sz, zTime, isLatest ? "true" : "false" | |
| 1588 | + ); | |
| 1589 | + if( zComment && zComment[0] ){ | |
| 1590 | + blob_appendf(pOut, "%!j", zComment); | |
| 1591 | + }else{ | |
| 1592 | + blob_append_literal(pOut, "null"); | |
| 1593 | + } | |
| 1594 | + blob_append_char(pOut, '}'); | |
| 1526 | 1595 | } |
| 1527 | 1596 | fossil_free(zToFree); |
| 1528 | 1597 | db_finalize(&q); |
| 1529 | 1598 | if(!i){ |
| 1530 | 1599 | empty_result: |
| @@ -1536,5 +1605,47 @@ | ||
| 1536 | 1605 | }else{ |
| 1537 | 1606 | blob_append_char(pOut, ']'); |
| 1538 | 1607 | } |
| 1539 | 1608 | return i; |
| 1540 | 1609 | } |
| 1610 | + | |
| 1611 | +/* | |
| 1612 | +** COMMAND: test-attachments-to-json | |
| 1613 | +** | |
| 1614 | +** Usage: %fossil test-attachments-to-json FULL_TARGET_ID | |
| 1615 | +** | |
| 1616 | +** Options: | |
| 1617 | +** --old List all versions of attachments. Default is to | |
| 1618 | +** list only the latest. | |
| 1619 | +** | |
| 1620 | +** Emits a JSON array of attachments for the given attachment target. | |
| 1621 | +** The given ID must be a full wiki page name, ticket hash, tech-note | |
| 1622 | +** hash, or forum post hash. It does not accept partial prefixes. | |
| 1623 | +*/ | |
| 1624 | +void test_attachments_to_json_cmd(void){ | |
| 1625 | + Manifest *pManifest; | |
| 1626 | + Blob b = BLOB_INITIALIZER; | |
| 1627 | + int bLatestOnly = find_option("old",0,0)==0; | |
| 1628 | + int emptyPolicy = 1; | |
| 1629 | + int rid; | |
| 1630 | + const char *zTarget; | |
| 1631 | + verify_all_options(); | |
| 1632 | + db_find_and_open_repository(0, 0); | |
| 1633 | + if( g.argc<3 ){ | |
| 1634 | + usage("test-attachments-to-json TARGET_ID"); | |
| 1635 | + return; | |
| 1636 | + } | |
| 1637 | + zTarget = g.argv[2]; | |
| 1638 | + rid = attachment_resolve_target(zTarget); | |
| 1639 | + if( 0==rid ){ | |
| 1640 | + fossil_fatal("Cannot resolve ID."); | |
| 1641 | + } | |
| 1642 | + pManifest = manifest_get(rid, CFTYPE_ANY, NULL); | |
| 1643 | + attachments_to_json(pManifest, &b, bLatestOnly, emptyPolicy); | |
| 1644 | + if( b.nUsed ){ | |
| 1645 | + char *zPretty = db_text(0,"SELECT json_pretty(%B)", &b); | |
| 1646 | + fossil_print("%s\n", zPretty); | |
| 1647 | + fossil_free(zPretty); | |
| 1648 | + } | |
| 1649 | + blob_reset(&b); | |
| 1650 | + manifest_destroy(pManifest); | |
| 1651 | +} | |
| 1541 | 1652 |
| --- src/attach.c | |
| +++ src/attach.c | |
| @@ -63,10 +63,66 @@ | |
| 63 | rc = db_column_int(&q, 0); |
| 64 | } |
| 65 | db_reset(&q); |
| 66 | return rc; |
| 67 | } |
| 68 | |
| 69 | /* |
| 70 | ** For a given aritfact ID and type (from the CFTYPE_xyz enum), |
| 71 | ** returns true if the current user could hypothetically attach |
| 72 | ** something to it, else returns 0. |
| @@ -511,11 +567,11 @@ | |
| 511 | const char *aContent; |
| 512 | const char *zName; |
| 513 | const char *zComment; |
| 514 | const char *zTarget; |
| 515 | const char *zFrom; /* Origin page - redirect here after saving */ |
| 516 | char * zTo = 0; /* Optionally redirect here after saving */ |
| 517 | char *zTargetType = 0; |
| 518 | char *zExtraFree = 0; |
| 519 | int szContent; |
| 520 | int goodCaptcha = 1; |
| 521 | int szLimit = 0; |
| @@ -1222,11 +1278,11 @@ | |
| 1222 | const char *zHeader, /* Header to display with attachments */ |
| 1223 | const int flags /* ATTACHLIST_... flags */ |
| 1224 | ){ |
| 1225 | int cnt = 0; |
| 1226 | char szBuf[36] = {0}; /* scratchpad for attachment size value */ |
| 1227 | const char * zLinkTgt = (ATTACHLIST_TARGET_BLANK & flags) |
| 1228 | ? " target=\"_blank\"" : ""; |
| 1229 | Stmt q; |
| 1230 | db_prepare(&q, |
| 1231 | "SELECT datetime(mtime,toLocal()), filename, user," |
| 1232 | " (SELECT uuid FROM blob WHERE rid=attachid), src, target, " |
| @@ -1435,14 +1491,14 @@ | |
| 1435 | } |
| 1436 | for(i = 2; i < g.argc; ++i){ |
| 1437 | const char *zPage = g.argv[i]; |
| 1438 | db_bind_text(&q, ":tgtname", zPage); |
| 1439 | while(SQLITE_ROW == db_step(&q)){ |
| 1440 | const char * zTime = db_column_text(&q, 0); |
| 1441 | const char * zSrc = db_column_text(&q, 1); |
| 1442 | const char * zTarget = db_column_text(&q, 2); |
| 1443 | const char * zName = db_column_text(&q, 3); |
| 1444 | printf("%-20s %s %.12s %s\n", zTarget, zTime, zSrc, zName); |
| 1445 | } |
| 1446 | db_reset(&q); |
| 1447 | } |
| 1448 | db_finalize(&q); |
| @@ -1495,36 +1551,49 @@ | |
| 1495 | case CFTYPE_TICKET: zTgt = pManifest->zTicketUuid; break; |
| 1496 | default: |
| 1497 | goto empty_result; |
| 1498 | } |
| 1499 | db_prepare(&q, |
| 1500 | "SELECT datetime(mtime), src, target, filename, isLatest," |
| 1501 | " (SELECT uuid FROM blob WHERE rid=attachid) uuid" |
| 1502 | " FROM attachment" |
| 1503 | " WHERE target=%Q" |
| 1504 | " AND (isLatest OR %d)" |
| 1505 | " ORDER BY target, isLatest DESC, mtime DESC", |
| 1506 | zTgt, !bLatestOnly |
| 1507 | ); |
| 1508 | while(SQLITE_ROW == db_step(&q)){ |
| 1509 | const char * zTime = db_column_text(&q, 0); |
| 1510 | const char * zSrc = db_column_text(&q, 1); |
| 1511 | const char * zTarget = db_column_text(&q, 2); |
| 1512 | const char * zName = db_column_text(&q, 3); |
| 1513 | const int isLatest = db_column_int(&q, 4); |
| 1514 | const char * zUuid = db_column_text(&q, 5); |
| 1515 | if(!i++){ |
| 1516 | blob_append_char(pOut, '['); |
| 1517 | }else{ |
| 1518 | blob_append_char(pOut, ','); |
| 1519 | } |
| 1520 | blob_appendf( |
| 1521 | pOut, |
| 1522 | "{\"uuid\": %!j, \"src\": %!j, \"target\": %!j, " |
| 1523 | "\"filename\": %!j, \"mtime\": %!j, \"isLatest\": %s}", |
| 1524 | zUuid, zSrc, zTarget, |
| 1525 | zName, zTime, isLatest ? "true" : "false"); |
| 1526 | } |
| 1527 | fossil_free(zToFree); |
| 1528 | db_finalize(&q); |
| 1529 | if(!i){ |
| 1530 | empty_result: |
| @@ -1536,5 +1605,47 @@ | |
| 1536 | }else{ |
| 1537 | blob_append_char(pOut, ']'); |
| 1538 | } |
| 1539 | return i; |
| 1540 | } |
| 1541 |
| --- src/attach.c | |
| +++ src/attach.c | |
| @@ -63,10 +63,66 @@ | |
| 63 | rc = db_column_int(&q, 0); |
| 64 | } |
| 65 | db_reset(&q); |
| 66 | return rc; |
| 67 | } |
| 68 | |
| 69 | /* |
| 70 | ** Given a full attachment target ID, returns its blob.rid. zTarget |
| 71 | ** must be a full wiki page name, tech-note ID, ticket ID, or forum |
| 72 | ** post hash. Returns 0 if no match is found. |
| 73 | */ |
| 74 | int attachment_resolve_target(const char *zTarget){ |
| 75 | int rid = 0; |
| 76 | int eType = attachment_target_type(zTarget); |
| 77 | switch(eType){ |
| 78 | case CFTYPE_EVENT: |
| 79 | rid = db_int( |
| 80 | 0, "SELECT b.rid FROM blob b, tag t, tagxref x\n" |
| 81 | "WHERE tagname='event-%q'\n" |
| 82 | "AND x.tagtype>0\n" |
| 83 | "AND x.tagid=t.tagid\n" |
| 84 | "AND x.rid=b.rid\n" |
| 85 | "ORDER BY x.mtime DESC", |
| 86 | zTarget |
| 87 | ); |
| 88 | break; |
| 89 | case CFTYPE_FORUM: |
| 90 | rid = db_int( |
| 91 | 0, "SELECT f.fpid FROM forumpost f, blob b\n" |
| 92 | "WHERE f.fpid=b.rid\n" |
| 93 | "AND b.uuid=%Q", |
| 94 | zTarget |
| 95 | ); |
| 96 | break; |
| 97 | case CFTYPE_TICKET: |
| 98 | rid = db_int( |
| 99 | 0, "SELECT b.rid FROM blob b, tag t, tagxref x\n" |
| 100 | "WHERE tagname='tkt-%q'\n" |
| 101 | "AND x.tagtype>0\n" |
| 102 | "AND x.tagid=t.tagid\n" |
| 103 | "AND x.rid=b.rid\n" |
| 104 | "ORDER BY x.mtime DESC", |
| 105 | zTarget |
| 106 | ); |
| 107 | break; |
| 108 | case CFTYPE_WIKI: |
| 109 | rid = db_int( |
| 110 | 0, "SELECT b.rid FROM blob b, tag t, tagxref x\n" |
| 111 | "WHERE tagname='wiki-%q'\n" |
| 112 | "AND x.tagtype>0\n" |
| 113 | "AND x.tagid=t.tagid\n" |
| 114 | "AND x.rid=b.rid\n" |
| 115 | "ORDER BY x.mtime DESC", |
| 116 | zTarget |
| 117 | ); |
| 118 | break; |
| 119 | default: |
| 120 | break; |
| 121 | } |
| 122 | return rid; |
| 123 | } |
| 124 | |
| 125 | /* |
| 126 | ** For a given aritfact ID and type (from the CFTYPE_xyz enum), |
| 127 | ** returns true if the current user could hypothetically attach |
| 128 | ** something to it, else returns 0. |
| @@ -511,11 +567,11 @@ | |
| 567 | const char *aContent; |
| 568 | const char *zName; |
| 569 | const char *zComment; |
| 570 | const char *zTarget; |
| 571 | const char *zFrom; /* Origin page - redirect here after saving */ |
| 572 | char *zTo = 0; /* Optionally redirect here after saving */ |
| 573 | char *zTargetType = 0; |
| 574 | char *zExtraFree = 0; |
| 575 | int szContent; |
| 576 | int goodCaptcha = 1; |
| 577 | int szLimit = 0; |
| @@ -1222,11 +1278,11 @@ | |
| 1278 | const char *zHeader, /* Header to display with attachments */ |
| 1279 | const int flags /* ATTACHLIST_... flags */ |
| 1280 | ){ |
| 1281 | int cnt = 0; |
| 1282 | char szBuf[36] = {0}; /* scratchpad for attachment size value */ |
| 1283 | const char *zLinkTgt = (ATTACHLIST_TARGET_BLANK & flags) |
| 1284 | ? " target=\"_blank\"" : ""; |
| 1285 | Stmt q; |
| 1286 | db_prepare(&q, |
| 1287 | "SELECT datetime(mtime,toLocal()), filename, user," |
| 1288 | " (SELECT uuid FROM blob WHERE rid=attachid), src, target, " |
| @@ -1435,14 +1491,14 @@ | |
| 1491 | } |
| 1492 | for(i = 2; i < g.argc; ++i){ |
| 1493 | const char *zPage = g.argv[i]; |
| 1494 | db_bind_text(&q, ":tgtname", zPage); |
| 1495 | while(SQLITE_ROW == db_step(&q)){ |
| 1496 | const char *zTime = db_column_text(&q, 0); |
| 1497 | const char *zSrc = db_column_text(&q, 1); |
| 1498 | const char *zTarget = db_column_text(&q, 2); |
| 1499 | const char *zName = db_column_text(&q, 3); |
| 1500 | printf("%-20s %s %.12s %s\n", zTarget, zTime, zSrc, zName); |
| 1501 | } |
| 1502 | db_reset(&q); |
| 1503 | } |
| 1504 | db_finalize(&q); |
| @@ -1495,36 +1551,49 @@ | |
| 1551 | case CFTYPE_TICKET: zTgt = pManifest->zTicketUuid; break; |
| 1552 | default: |
| 1553 | goto empty_result; |
| 1554 | } |
| 1555 | db_prepare(&q, |
| 1556 | "SELECT datetime(mtime), a.src, a.target, a.filename, a.isLatest,\n" |
| 1557 | " b2.size, b1.uuid, a.comment\n" |
| 1558 | " FROM attachment a, blob b1, blob b2\n" |
| 1559 | " WHERE a.target=%Q\n" |
| 1560 | " AND b1.rid=a.attachid\n" |
| 1561 | " AND b2.uuid=a.src\n" |
| 1562 | " AND b2.size>0\n" |
| 1563 | " AND (a.isLatest OR %d)\n" |
| 1564 | " ORDER BY a.target, a.isLatest DESC, a.mtime DESC\n", |
| 1565 | zTgt, !bLatestOnly |
| 1566 | ); |
| 1567 | while(SQLITE_ROW == db_step(&q)){ |
| 1568 | const char *zTime = db_column_text(&q, 0); |
| 1569 | const char *zSrc = db_column_text(&q, 1); |
| 1570 | const char *zTarget = db_column_text(&q, 2); |
| 1571 | const char *zName = db_column_text(&q, 3); |
| 1572 | const int isLatest = db_column_int(&q, 4); |
| 1573 | const int sz = db_column_int(&q, 5); |
| 1574 | const char *zUuid = db_column_text(&q, 6); |
| 1575 | const char *zComment = db_column_text(&q, 7); |
| 1576 | if(!i++){ |
| 1577 | blob_append_char(pOut, '['); |
| 1578 | }else{ |
| 1579 | blob_append_char(pOut, ','); |
| 1580 | } |
| 1581 | blob_appendf( |
| 1582 | pOut, |
| 1583 | "{\"uuid\": %!j, \"src\": %!j, \"target\": %!j, " |
| 1584 | "\"filename\": %!j, \"size\":%d, \"mtime\": %!j, " |
| 1585 | "\"isLatest\": %s,\"comment\": ", |
| 1586 | zUuid, zSrc, zTarget, |
| 1587 | zName, sz, zTime, isLatest ? "true" : "false" |
| 1588 | ); |
| 1589 | if( zComment && zComment[0] ){ |
| 1590 | blob_appendf(pOut, "%!j", zComment); |
| 1591 | }else{ |
| 1592 | blob_append_literal(pOut, "null"); |
| 1593 | } |
| 1594 | blob_append_char(pOut, '}'); |
| 1595 | } |
| 1596 | fossil_free(zToFree); |
| 1597 | db_finalize(&q); |
| 1598 | if(!i){ |
| 1599 | empty_result: |
| @@ -1536,5 +1605,47 @@ | |
| 1605 | }else{ |
| 1606 | blob_append_char(pOut, ']'); |
| 1607 | } |
| 1608 | return i; |
| 1609 | } |
| 1610 | |
| 1611 | /* |
| 1612 | ** COMMAND: test-attachments-to-json |
| 1613 | ** |
| 1614 | ** Usage: %fossil test-attachments-to-json FULL_TARGET_ID |
| 1615 | ** |
| 1616 | ** Options: |
| 1617 | ** --old List all versions of attachments. Default is to |
| 1618 | ** list only the latest. |
| 1619 | ** |
| 1620 | ** Emits a JSON array of attachments for the given attachment target. |
| 1621 | ** The given ID must be a full wiki page name, ticket hash, tech-note |
| 1622 | ** hash, or forum post hash. It does not accept partial prefixes. |
| 1623 | */ |
| 1624 | void test_attachments_to_json_cmd(void){ |
| 1625 | Manifest *pManifest; |
| 1626 | Blob b = BLOB_INITIALIZER; |
| 1627 | int bLatestOnly = find_option("old",0,0)==0; |
| 1628 | int emptyPolicy = 1; |
| 1629 | int rid; |
| 1630 | const char *zTarget; |
| 1631 | verify_all_options(); |
| 1632 | db_find_and_open_repository(0, 0); |
| 1633 | if( g.argc<3 ){ |
| 1634 | usage("test-attachments-to-json TARGET_ID"); |
| 1635 | return; |
| 1636 | } |
| 1637 | zTarget = g.argv[2]; |
| 1638 | rid = attachment_resolve_target(zTarget); |
| 1639 | if( 0==rid ){ |
| 1640 | fossil_fatal("Cannot resolve ID."); |
| 1641 | } |
| 1642 | pManifest = manifest_get(rid, CFTYPE_ANY, NULL); |
| 1643 | attachments_to_json(pManifest, &b, bLatestOnly, emptyPolicy); |
| 1644 | if( b.nUsed ){ |
| 1645 | char *zPretty = db_text(0,"SELECT json_pretty(%B)", &b); |
| 1646 | fossil_print("%s\n", zPretty); |
| 1647 | fossil_free(zPretty); |
| 1648 | } |
| 1649 | blob_reset(&b); |
| 1650 | manifest_destroy(pManifest); |
| 1651 | } |
| 1652 |