diff options
Diffstat (limited to 'src/lib/client.rs')
| -rw-r--r-- | src/lib/client.rs | 273 |
1 files changed, 266 insertions, 7 deletions
diff --git a/src/lib/client.rs b/src/lib/client.rs index abde217..ace880b 100644 --- a/src/lib/client.rs +++ b/src/lib/client.rs | |||
| @@ -21,8 +21,11 @@ use std::{ | |||
| 21 | use anyhow::{bail, Context, Result}; | 21 | use anyhow::{bail, Context, Result}; |
| 22 | use async_trait::async_trait; | 22 | use async_trait::async_trait; |
| 23 | use console::Style; | 23 | use console::Style; |
| 24 | use futures::stream::{self, StreamExt}; | 24 | use futures::{ |
| 25 | use indicatif::{MultiProgress, ProgressBar, ProgressState, ProgressStyle}; | 25 | future::join_all, |
| 26 | stream::{self, StreamExt}, | ||
| 27 | }; | ||
| 28 | use indicatif::{MultiProgress, ProgressBar, ProgressDrawTarget, ProgressState, ProgressStyle}; | ||
| 26 | #[cfg(test)] | 29 | #[cfg(test)] |
| 27 | use mockall::*; | 30 | use mockall::*; |
| 28 | use nostr::{nips::nip01::Coordinate, Event}; | 31 | use nostr::{nips::nip01::Coordinate, Event}; |
| @@ -34,14 +37,13 @@ use nostr_sdk::{ | |||
| 34 | use nostr_sqlite::SQLiteDatabase; | 37 | use nostr_sqlite::SQLiteDatabase; |
| 35 | 38 | ||
| 36 | use crate::{ | 39 | use crate::{ |
| 37 | config::get_dirs, | 40 | get_dirs, |
| 41 | git_events::{ | ||
| 42 | event_is_cover_letter, event_is_patch_set_root, event_is_revision_root, status_kinds, | ||
| 43 | }, | ||
| 38 | login::{get_logged_in_user, get_user_ref_from_cache}, | 44 | login::{get_logged_in_user, get_user_ref_from_cache}, |
| 39 | repo_ref::RepoRef, | 45 | repo_ref::RepoRef, |
| 40 | repo_state::RepoState, | 46 | repo_state::RepoState, |
| 41 | sub_commands::{ | ||
| 42 | list::status_kinds, | ||
| 43 | send::{event_is_patch_set_root, event_is_revision_root}, | ||
| 44 | }, | ||
| 45 | }; | 47 | }; |
| 46 | 48 | ||
| 47 | #[allow(clippy::struct_field_names)] | 49 | #[allow(clippy::struct_field_names)] |
| @@ -1478,3 +1480,260 @@ pub async fn fetching_with_report( | |||
| 1478 | } | 1480 | } |
| 1479 | Ok(report) | 1481 | Ok(report) |
| 1480 | } | 1482 | } |
| 1483 | |||
| 1484 | pub async fn get_proposals_and_revisions_from_cache( | ||
| 1485 | git_repo_path: &Path, | ||
| 1486 | repo_coordinates: HashSet<Coordinate>, | ||
| 1487 | ) -> Result<Vec<nostr::Event>> { | ||
| 1488 | let mut proposals = get_events_from_cache( | ||
| 1489 | git_repo_path, | ||
| 1490 | vec![ | ||
| 1491 | nostr::Filter::default() | ||
| 1492 | .kind(nostr::Kind::GitPatch) | ||
| 1493 | .custom_tag( | ||
| 1494 | nostr::SingleLetterTag::lowercase(nostr_sdk::Alphabet::A), | ||
| 1495 | repo_coordinates | ||
| 1496 | .iter() | ||
| 1497 | .map(std::string::ToString::to_string) | ||
| 1498 | .collect::<Vec<String>>(), | ||
| 1499 | ), | ||
| 1500 | ], | ||
| 1501 | ) | ||
| 1502 | .await? | ||
| 1503 | .iter() | ||
| 1504 | .filter(|e| event_is_patch_set_root(e)) | ||
| 1505 | .cloned() | ||
| 1506 | .collect::<Vec<nostr::Event>>(); | ||
| 1507 | proposals.sort_by_key(|e| e.created_at); | ||
| 1508 | proposals.reverse(); | ||
| 1509 | Ok(proposals) | ||
| 1510 | } | ||
| 1511 | |||
| 1512 | pub async fn get_all_proposal_patch_events_from_cache( | ||
| 1513 | git_repo_path: &Path, | ||
| 1514 | repo_ref: &RepoRef, | ||
| 1515 | proposal_id: &nostr::EventId, | ||
| 1516 | ) -> Result<Vec<nostr::Event>> { | ||
| 1517 | let mut commit_events = get_events_from_cache( | ||
| 1518 | git_repo_path, | ||
| 1519 | vec![ | ||
| 1520 | nostr::Filter::default() | ||
| 1521 | .kind(nostr::Kind::GitPatch) | ||
| 1522 | .event(*proposal_id), | ||
| 1523 | nostr::Filter::default() | ||
| 1524 | .kind(nostr::Kind::GitPatch) | ||
| 1525 | .id(*proposal_id), | ||
| 1526 | ], | ||
| 1527 | ) | ||
| 1528 | .await?; | ||
| 1529 | |||
| 1530 | let permissioned_users: HashSet<PublicKey> = [ | ||
| 1531 | repo_ref.maintainers.clone(), | ||
| 1532 | vec![ | ||
| 1533 | commit_events | ||
| 1534 | .iter() | ||
| 1535 | .find(|e| e.id().eq(proposal_id)) | ||
| 1536 | .context("proposal not in cache")? | ||
| 1537 | .author(), | ||
| 1538 | ], | ||
| 1539 | ] | ||
| 1540 | .concat() | ||
| 1541 | .iter() | ||
| 1542 | .copied() | ||
| 1543 | .collect(); | ||
| 1544 | commit_events.retain(|e| permissioned_users.contains(&e.author())); | ||
| 1545 | |||
| 1546 | let revision_roots: HashSet<nostr::EventId> = commit_events | ||
| 1547 | .iter() | ||
| 1548 | .filter(|e| event_is_revision_root(e)) | ||
| 1549 | .map(nostr::Event::id) | ||
| 1550 | .collect(); | ||
| 1551 | |||
| 1552 | if !revision_roots.is_empty() { | ||
| 1553 | for event in get_events_from_cache( | ||
| 1554 | git_repo_path, | ||
| 1555 | vec![ | ||
| 1556 | nostr::Filter::default() | ||
| 1557 | .kind(nostr::Kind::GitPatch) | ||
| 1558 | .events(revision_roots) | ||
| 1559 | .authors(permissioned_users.clone()), | ||
| 1560 | ], | ||
| 1561 | ) | ||
| 1562 | .await? | ||
| 1563 | { | ||
| 1564 | commit_events.push(event); | ||
| 1565 | } | ||
| 1566 | } | ||
| 1567 | |||
| 1568 | Ok(commit_events | ||
| 1569 | .iter() | ||
| 1570 | .filter(|e| !event_is_cover_letter(e) && permissioned_users.contains(&e.author())) | ||
| 1571 | .cloned() | ||
| 1572 | .collect()) | ||
| 1573 | } | ||
| 1574 | |||
| 1575 | #[allow(clippy::module_name_repetitions)] | ||
| 1576 | #[allow(clippy::too_many_lines)] | ||
| 1577 | pub async fn send_events( | ||
| 1578 | #[cfg(test)] client: &crate::client::MockConnect, | ||
| 1579 | #[cfg(not(test))] client: &Client, | ||
| 1580 | git_repo_path: &Path, | ||
| 1581 | events: Vec<nostr::Event>, | ||
| 1582 | my_write_relays: Vec<String>, | ||
| 1583 | repo_read_relays: Vec<String>, | ||
| 1584 | animate: bool, | ||
| 1585 | silent: bool, | ||
| 1586 | ) -> Result<()> { | ||
| 1587 | let fallback = [ | ||
| 1588 | client.get_fallback_relays().clone(), | ||
| 1589 | if events | ||
| 1590 | .iter() | ||
| 1591 | .any(|e| e.kind().eq(&Kind::GitRepoAnnouncement)) | ||
| 1592 | { | ||
| 1593 | client.get_blaster_relays().clone() | ||
| 1594 | } else { | ||
| 1595 | vec![] | ||
| 1596 | }, | ||
| 1597 | ] | ||
| 1598 | .concat(); | ||
| 1599 | let mut relays: Vec<&String> = vec![]; | ||
| 1600 | |||
| 1601 | let all = &[ | ||
| 1602 | repo_read_relays.clone(), | ||
| 1603 | my_write_relays.clone(), | ||
| 1604 | fallback.clone(), | ||
| 1605 | ] | ||
| 1606 | .concat(); | ||
| 1607 | // add duplicates first | ||
| 1608 | for r in &repo_read_relays { | ||
| 1609 | let r_clean = remove_trailing_slash(r); | ||
| 1610 | if !my_write_relays | ||
| 1611 | .iter() | ||
| 1612 | .filter(|x| r_clean.eq(&remove_trailing_slash(x))) | ||
| 1613 | .count() | ||
| 1614 | > 1 | ||
| 1615 | && !relays.iter().any(|x| r_clean.eq(&remove_trailing_slash(x))) | ||
| 1616 | { | ||
| 1617 | relays.push(r); | ||
| 1618 | } | ||
| 1619 | } | ||
| 1620 | |||
| 1621 | for r in all { | ||
| 1622 | let r_clean = remove_trailing_slash(r); | ||
| 1623 | if !relays.iter().any(|x| r_clean.eq(&remove_trailing_slash(x))) { | ||
| 1624 | relays.push(r); | ||
| 1625 | } | ||
| 1626 | } | ||
| 1627 | |||
| 1628 | let m = if silent { | ||
| 1629 | MultiProgress::with_draw_target(ProgressDrawTarget::hidden()) | ||
| 1630 | } else { | ||
| 1631 | MultiProgress::new() | ||
| 1632 | }; | ||
| 1633 | let pb_style = ProgressStyle::with_template(if animate { | ||
| 1634 | " {spinner} {prefix} {bar} {pos}/{len} {msg}" | ||
| 1635 | } else { | ||
| 1636 | " - {prefix} {bar} {pos}/{len} {msg}" | ||
| 1637 | })? | ||
| 1638 | .progress_chars("##-"); | ||
| 1639 | |||
| 1640 | let pb_after_style = | ||
| 1641 | |symbol| ProgressStyle::with_template(format!(" {symbol} {}", "{prefix} {msg}",).as_str()); | ||
| 1642 | let pb_after_style_succeeded = pb_after_style(if animate { | ||
| 1643 | console::style("✔".to_string()) | ||
| 1644 | .for_stderr() | ||
| 1645 | .green() | ||
| 1646 | .to_string() | ||
| 1647 | } else { | ||
| 1648 | "y".to_string() | ||
| 1649 | })?; | ||
| 1650 | |||
| 1651 | let pb_after_style_failed = pb_after_style(if animate { | ||
| 1652 | console::style("✘".to_string()) | ||
| 1653 | .for_stderr() | ||
| 1654 | .red() | ||
| 1655 | .to_string() | ||
| 1656 | } else { | ||
| 1657 | "x".to_string() | ||
| 1658 | })?; | ||
| 1659 | |||
| 1660 | #[allow(clippy::borrow_deref_ref)] | ||
| 1661 | join_all(relays.iter().map(|&relay| async { | ||
| 1662 | let relay_clean = remove_trailing_slash(&*relay); | ||
| 1663 | let details = format!( | ||
| 1664 | "{}{}{} {}", | ||
| 1665 | if my_write_relays | ||
| 1666 | .iter() | ||
| 1667 | .any(|r| relay_clean.eq(&remove_trailing_slash(r))) | ||
| 1668 | { | ||
| 1669 | " [my-relay]" | ||
| 1670 | } else { | ||
| 1671 | "" | ||
| 1672 | }, | ||
| 1673 | if repo_read_relays | ||
| 1674 | .iter() | ||
| 1675 | .any(|r| relay_clean.eq(&remove_trailing_slash(r))) | ||
| 1676 | { | ||
| 1677 | " [repo-relay]" | ||
| 1678 | } else { | ||
| 1679 | "" | ||
| 1680 | }, | ||
| 1681 | if fallback | ||
| 1682 | .iter() | ||
| 1683 | .any(|r| relay_clean.eq(&remove_trailing_slash(r))) | ||
| 1684 | { | ||
| 1685 | " [default]" | ||
| 1686 | } else { | ||
| 1687 | "" | ||
| 1688 | }, | ||
| 1689 | relay_clean, | ||
| 1690 | ); | ||
| 1691 | let pb = m.add( | ||
| 1692 | ProgressBar::new(events.len() as u64) | ||
| 1693 | .with_prefix(details.to_string()) | ||
| 1694 | .with_style(pb_style.clone()), | ||
| 1695 | ); | ||
| 1696 | if animate { | ||
| 1697 | pb.enable_steady_tick(Duration::from_millis(300)); | ||
| 1698 | } | ||
| 1699 | pb.inc(0); // need to make pb display intially | ||
| 1700 | let mut failed = false; | ||
| 1701 | for event in &events { | ||
| 1702 | match client | ||
| 1703 | .send_event_to(git_repo_path, relay.as_str(), event.clone()) | ||
| 1704 | .await | ||
| 1705 | { | ||
| 1706 | Ok(_) => pb.inc(1), | ||
| 1707 | Err(e) => { | ||
| 1708 | pb.set_style(pb_after_style_failed.clone()); | ||
| 1709 | pb.finish_with_message( | ||
| 1710 | console::style( | ||
| 1711 | e.to_string() | ||
| 1712 | .replace("relay pool error:", "error:") | ||
| 1713 | .replace("event not published: ", "error: "), | ||
| 1714 | ) | ||
| 1715 | .for_stderr() | ||
| 1716 | .red() | ||
| 1717 | .to_string(), | ||
| 1718 | ); | ||
| 1719 | failed = true; | ||
| 1720 | break; | ||
| 1721 | } | ||
| 1722 | }; | ||
| 1723 | } | ||
| 1724 | if !failed { | ||
| 1725 | pb.set_style(pb_after_style_succeeded.clone()); | ||
| 1726 | pb.finish_with_message(""); | ||
| 1727 | } | ||
| 1728 | })) | ||
| 1729 | .await; | ||
| 1730 | Ok(()) | ||
| 1731 | } | ||
| 1732 | |||
| 1733 | fn remove_trailing_slash(s: &String) -> String { | ||
| 1734 | match s.as_str().strip_suffix('/') { | ||
| 1735 | Some(s) => s, | ||
| 1736 | None => s, | ||
| 1737 | } | ||
| 1738 | .to_string() | ||
| 1739 | } | ||