upleb.uk

Public git repos — served from a NIP-34 GRASP relay at git.upleb.uk

summaryrefslogtreecommitdiff
path: root/src
diff options
context:
space:
mode:
Diffstat (limited to 'src')
-rw-r--r--src/client.rs156
-rw-r--r--src/git.rs8
-rw-r--r--src/login.rs124
-rw-r--r--src/sub_commands/fetch.rs11
4 files changed, 165 insertions, 134 deletions
diff --git a/src/client.rs b/src/client.rs
index 835d69e..b6e93d6 100644
--- a/src/client.rs
+++ b/src/client.rs
@@ -35,6 +35,7 @@ use nostr_sqlite::SQLiteDatabase;
35 35
36use crate::{ 36use crate::{
37 config::get_dirs, 37 config::get_dirs,
38 login::get_logged_in_user_and_relays_from_cache,
38 repo_ref::{RepoRef, REPO_REF_KIND}, 39 repo_ref::{RepoRef, REPO_REF_KIND},
39 sub_commands::{ 40 sub_commands::{
40 list::status_kinds, 41 list::status_kinds,
@@ -77,7 +78,7 @@ pub trait Connect {
77 &self, 78 &self,
78 git_repo_path: &Path, 79 git_repo_path: &Path,
79 repo_coordinates: &HashSet<Coordinate>, 80 repo_coordinates: &HashSet<Coordinate>,
80 ) -> Result<FetchReport>; 81 ) -> Result<(Vec<Result<FetchReport>>, MultiProgress)>;
81 async fn fetch_all_from_relay( 82 async fn fetch_all_from_relay(
82 &self, 83 &self,
83 git_repo_path: &Path, 84 git_repo_path: &Path,
@@ -287,8 +288,7 @@ impl Connect for Client {
287 &self, 288 &self,
288 git_repo_path: &Path, 289 git_repo_path: &Path,
289 repo_coordinates: &HashSet<Coordinate>, 290 repo_coordinates: &HashSet<Coordinate>,
290 ) -> Result<FetchReport> { 291 ) -> Result<(Vec<Result<FetchReport>>, MultiProgress)> {
291 println!("fetching updates...");
292 let fallback_relays = &self 292 let fallback_relays = &self
293 .fallback_relays 293 .fallback_relays
294 .iter() 294 .iter()
@@ -305,6 +305,23 @@ impl Connect for Client {
305 let mut relay_reports: Vec<Result<FetchReport>> = vec![]; 305 let mut relay_reports: Vec<Result<FetchReport>> = vec![];
306 306
307 loop { 307 loop {
308 let relays = request
309 .relays
310 .union(&request.current_user_write_relays)
311 // don't look for events on blaster
312 .filter(|&r| !r.as_str().contains("nostr.mutinywallet.com"))
313 .cloned()
314 .collect::<HashSet<Url>>()
315 .difference(&processed_relays)
316 .cloned()
317 .collect::<HashSet<Url>>();
318 if relays.is_empty() {
319 break;
320 }
321 let only_user_relays = request
322 .current_user_write_relays
323 .difference(&request.relays)
324 .collect::<HashSet<&Url>>();
308 for relay in &request.relays { 325 for relay in &request.relays {
309 self.client 326 self.client
310 .add_relay(relay.as_str()) 327 .add_relay(relay.as_str())
@@ -314,14 +331,26 @@ impl Connect for Client {
314 331
315 let dim = Style::new().color256(247); 332 let dim = Style::new().color256(247);
316 333
317 let futures: Vec<_> = request 334 let futures: Vec<_> = relays
318 .relays
319 .iter() 335 .iter()
320 // don't look for events on blaster 336 .map(|r| {
321 .filter(|r| !r.as_str().contains("nostr.mutinywallet.com")) 337 if only_user_relays.contains(r) {
322 .map(|r| FetchRequest { 338 // if user write relay isn't a repo relay, just filter for user profile
323 selected_relay: Some(r.clone()), 339 FetchRequest {
324 ..request.clone() 340 selected_relay: Some(r.to_owned()),
341 repo_coordinates: vec![],
342 proposals: HashSet::new(),
343 missing_contributor_profiles: HashSet::from_iter(vec![
344 request.current_user.unwrap(),
345 ]),
346 ..request.clone()
347 }
348 } else {
349 FetchRequest {
350 selected_relay: Some(r.to_owned()),
351 ..request.clone()
352 }
353 }
325 }) 354 })
326 .map(|request| async { 355 .map(|request| async {
327 let relay_column_width = request.relay_column_width; 356 let relay_column_width = request.relay_column_width;
@@ -381,31 +410,20 @@ impl Connect for Client {
381 { 410 {
382 relay_reports.push(report); 411 relay_reports.push(report);
383 } 412 }
384 413 processed_relays.extend(relays.clone());
385 for relay in &request.relays {
386 processed_relays.insert(relay.clone());
387 }
388 414
389 if let Ok(repo_ref) = get_repo_ref_from_cache(git_repo_path, repo_coordinates).await { 415 if let Ok(repo_ref) = get_repo_ref_from_cache(git_repo_path, repo_coordinates).await {
390 request.relays = repo_ref 416 request.relays = repo_ref
391 .relays 417 .relays
392 .iter() 418 .iter()
393 .filter_map(|r| Url::parse(r).ok()) 419 .filter_map(|r| Url::parse(r).ok())
394 .filter(|r| !processed_relays.contains(r))
395 .collect(); 420 .collect();
396 if request.relays.is_empty() {
397 break;
398 }
399 } 421 }
422 let (_, current_user_write_relays) =
423 get_logged_in_user_and_relays_from_cache(git_repo_path).await?;
424 request.current_user_write_relays = current_user_write_relays;
400 } 425 }
401 let report = consolidate_fetch_reports(relay_reports); 426 Ok((relay_reports, progress_reporter))
402
403 if report.to_string().is_empty() {
404 println!("no updates");
405 } else {
406 println!("updates: {report}");
407 }
408 Ok(report)
409 } 427 }
410 428
411 async fn fetch_all_from_relay( 429 async fn fetch_all_from_relay(
@@ -420,6 +438,9 @@ impl Connect for Client {
420 } 438 }
421 let mut fresh_proposal_roots = request.proposals.clone(); 439 let mut fresh_proposal_roots = request.proposals.clone();
422 let mut fresh_contributors = request.missing_contributor_profiles.clone(); 440 let mut fresh_contributors = request.missing_contributor_profiles.clone();
441 if let Some(user) = request.current_user {
442 fresh_contributors.insert(user);
443 }
423 444
424 let mut report = FetchReport::default(); 445 let mut report = FetchReport::default();
425 446
@@ -486,9 +507,9 @@ impl Connect for Client {
486 "{: <relay_column_width$} {}", 507 "{: <relay_column_width$} {}",
487 relay_url, 508 relay_url,
488 if report.to_string().is_empty() { 509 if report.to_string().is_empty() {
489 "no updates".to_string() 510 "no new events".to_string()
490 } else { 511 } else {
491 format!("updates: {report}") 512 format!("new events: {report}")
492 }, 513 },
493 )) 514 ))
494 .to_string(), 515 .to_string(),
@@ -761,37 +782,6 @@ async fn create_relays_request(
761) -> Result<FetchRequest> { 782) -> Result<FetchRequest> {
762 let repo_ref = get_repo_ref_from_cache(git_repo_path, repo_coordinates).await; 783 let repo_ref = get_repo_ref_from_cache(git_repo_path, repo_coordinates).await;
763 784
764 let relays = {
765 let mut relays = fallback_relays;
766 if let Ok(repo_ref) = &repo_ref {
767 for r in &repo_ref.relays {
768 if let Ok(url) = Url::parse(r) {
769 relays.insert(url);
770 }
771 }
772 }
773 relays
774 };
775
776 let relay_column_width = relays
777 .iter()
778 .reduce(|a, r| {
779 if r.to_string()
780 .chars()
781 .count()
782 .gt(&a.to_string().chars().count())
783 {
784 r
785 } else {
786 a
787 }
788 })
789 .unwrap()
790 .to_string()
791 .chars()
792 .count()
793 + 2;
794
795 let repo_coordinates = if let Ok(repo_ref) = &repo_ref { 785 let repo_coordinates = if let Ok(repo_ref) = &repo_ref {
796 repo_ref.coordinates() 786 repo_ref.coordinates()
797 } else { 787 } else {
@@ -802,7 +792,7 @@ async fn create_relays_request(
802 let mut missing_contributor_profiles: HashSet<PublicKey> = HashSet::new(); 792 let mut missing_contributor_profiles: HashSet<PublicKey> = HashSet::new();
803 let mut contributors: HashSet<PublicKey> = HashSet::new(); 793 let mut contributors: HashSet<PublicKey> = HashSet::new();
804 794
805 { 795 if !repo_coordinates.is_empty() {
806 if let Ok(repo_ref) = &repo_ref { 796 if let Ok(repo_ref) = &repo_ref {
807 for m in &repo_ref.maintainers { 797 for m in &repo_ref.maintainers {
808 contributors.insert(m.to_owned()); 798 contributors.insert(m.to_owned());
@@ -848,6 +838,12 @@ async fn create_relays_request(
848 } 838 }
849 } 839 }
850 840
841 let (current_user, current_user_write_relays) =
842 get_logged_in_user_and_relays_from_cache(git_repo_path).await?;
843 if let Some(current_user) = current_user {
844 missing_contributor_profiles.insert(current_user);
845 }
846
851 let existing_events: HashSet<EventId> = { 847 let existing_events: HashSet<EventId> = {
852 let mut existing_events: HashSet<EventId> = HashSet::new(); 848 let mut existing_events: HashSet<EventId> = HashSet::new();
853 for filter in 849 for filter in
@@ -863,6 +859,38 @@ async fn create_relays_request(
863 } 859 }
864 existing_events 860 existing_events
865 }; 861 };
862
863 let relays = {
864 let mut relays = fallback_relays;
865 if let Ok(repo_ref) = &repo_ref {
866 for r in &repo_ref.relays {
867 if let Ok(url) = Url::parse(r) {
868 relays.insert(url);
869 }
870 }
871 }
872 relays
873 };
874
875 let relay_column_width = relays
876 .union(&current_user_write_relays)
877 .reduce(|a, r| {
878 if r.to_string()
879 .chars()
880 .count()
881 .gt(&a.to_string().chars().count())
882 {
883 r
884 } else {
885 a
886 }
887 })
888 .unwrap()
889 .to_string()
890 .chars()
891 .count()
892 + 2;
893
866 Ok(FetchRequest { 894 Ok(FetchRequest {
867 selected_relay: None, 895 selected_relay: None,
868 relays, 896 relays,
@@ -876,6 +904,8 @@ async fn create_relays_request(
876 contributors, 904 contributors,
877 missing_contributor_profiles, 905 missing_contributor_profiles,
878 existing_events, 906 existing_events,
907 current_user,
908 current_user_write_relays,
879 }) 909 })
880} 910}
881 911
@@ -972,7 +1002,7 @@ async fn process_fetched_events(
972 Ok(()) 1002 Ok(())
973} 1003}
974 1004
975fn consolidate_fetch_reports(reports: Vec<Result<FetchReport>>) -> FetchReport { 1005pub fn consolidate_fetch_reports(reports: Vec<Result<FetchReport>>) -> FetchReport {
976 let mut report = FetchReport::default(); 1006 let mut report = FetchReport::default();
977 for relay_report in reports.into_iter().flatten() { 1007 for relay_report in reports.into_iter().flatten() {
978 for c in relay_report.repo_coordinates { 1008 for c in relay_report.repo_coordinates {
@@ -1136,7 +1166,7 @@ impl Display for FetchReport {
1136 } 1166 }
1137 if !self.contributor_profiles.is_empty() { 1167 if !self.contributor_profiles.is_empty() {
1138 display_items.push(format!( 1168 display_items.push(format!(
1139 "{} contributor profile{}", 1169 "{} user profile{}",
1140 self.contributor_profiles.len(), 1170 self.contributor_profiles.len(),
1141 if self.contributor_profiles.len() > 1 { 1171 if self.contributor_profiles.len() > 1 {
1142 "s" 1172 "s"
@@ -1159,4 +1189,6 @@ pub struct FetchRequest {
1159 contributors: HashSet<PublicKey>, 1189 contributors: HashSet<PublicKey>,
1160 missing_contributor_profiles: HashSet<PublicKey>, 1190 missing_contributor_profiles: HashSet<PublicKey>,
1161 existing_events: HashSet<EventId>, 1191 existing_events: HashSet<EventId>,
1192 current_user: Option<PublicKey>,
1193 current_user_write_relays: HashSet<Url>,
1162} 1194}
diff --git a/src/git.rs b/src/git.rs
index bb943a9..c13b46d 100644
--- a/src/git.rs
+++ b/src/git.rs
@@ -1,6 +1,7 @@
1#[cfg(test)] 1use std::{
2use std::path::PathBuf; 2 env::current_dir,
3use std::{env::current_dir, path::Path}; 3 path::{Path, PathBuf},
4};
4 5
5use anyhow::{bail, Context, Result}; 6use anyhow::{bail, Context, Result};
6use git2::{DiffOptions, Oid, Revwalk}; 7use git2::{DiffOptions, Oid, Revwalk};
@@ -18,7 +19,6 @@ impl Repo {
18 git_repo: git2::Repository::discover(current_dir()?)?, 19 git_repo: git2::Repository::discover(current_dir()?)?,
19 }) 20 })
20 } 21 }
21 #[cfg(test)]
22 pub fn from_path(path: &PathBuf) -> Result<Self> { 22 pub fn from_path(path: &PathBuf) -> Result<Self> {
23 Ok(Self { 23 Ok(Self {
24 git_repo: git2::Repository::open(path)?, 24 git_repo: git2::Repository::open(path)?,
diff --git a/src/login.rs b/src/login.rs
index d580969..0a20a90 100644
--- a/src/login.rs
+++ b/src/login.rs
@@ -1,17 +1,14 @@
1use std::{fs::create_dir_all, str::FromStr, time::Duration}; 1use std::{collections::HashSet, path::Path, str::FromStr, time::Duration};
2 2
3use anyhow::{bail, Context, Result}; 3use anyhow::{bail, Context, Result};
4use nostr::{ 4use nostr::{
5 nips::{nip05::get_nip46, nip46::NostrConnectURI}, 5 nips::{nip05::get_nip46, nip46::NostrConnectURI},
6 PublicKey, 6 PublicKey,
7}; 7};
8use nostr_database::Order;
9use nostr_sdk::{ 8use nostr_sdk::{
10 Alphabet, FromBech32, JsonUtil, Keys, Kind, NostrDatabase, NostrSigner, SingleLetterTag, 9 Alphabet, FromBech32, JsonUtil, Keys, Kind, NostrSigner, SingleLetterTag, ToBech32, Url,
11 ToBech32,
12}; 10};
13use nostr_signer::Nip46Signer; 11use nostr_signer::Nip46Signer;
14use nostr_sqlite::SQLiteDatabase;
15 12
16#[cfg(not(test))] 13#[cfg(not(test))]
17use crate::client::Client; 14use crate::client::Client;
@@ -21,8 +18,8 @@ use crate::{
21 cli_interactor::{ 18 cli_interactor::{
22 Interactor, InteractorPrompt, PromptConfirmParms, PromptInputParms, PromptPasswordParms, 19 Interactor, InteractorPrompt, PromptConfirmParms, PromptInputParms, PromptPasswordParms,
23 }, 20 },
24 client::{fetch_public_key, Connect}, 21 client::{fetch_public_key, get_event_from_global_cache, Connect},
25 config::{get_dirs, UserMetadata, UserRef, UserRelayRef, UserRelays}, 22 config::{UserMetadata, UserRef, UserRelayRef, UserRelays},
26 git::{Repo, RepoActions}, 23 git::{Repo, RepoActions},
27 key_handling::encryption::{decrypt_key, encrypt_key}, 24 key_handling::encryption::{decrypt_key, encrypt_key},
28}; 25};
@@ -107,7 +104,7 @@ pub async fn launch(
107 104
108fn print_logged_in_as(user_ref: &UserRef, offline_mode: bool) -> Result<()> { 105fn print_logged_in_as(user_ref: &UserRef, offline_mode: bool) -> Result<()> {
109 if !offline_mode && user_ref.metadata.created_at.eq(&0) { 106 if !offline_mode && user_ref.metadata.created_at.eq(&0) {
110 println!("cannot find your account metadata (name, etc) on relays"); 107 println!("cannot find profile...");
111 } else if !offline_mode && user_ref.metadata.name.eq(&user_ref.public_key.to_bech32()?) { 108 } else if !offline_mode && user_ref.metadata.name.eq(&user_ref.public_key.to_bech32()?) {
112 println!("cannot extract account name from account metadata..."); 109 println!("cannot extract account name from account metadata...");
113 } else if !offline_mode && user_ref.relays.created_at.eq(&0) { 110 } else if !offline_mode && user_ref.relays.created_at.eq(&0) {
@@ -615,20 +612,6 @@ async fn get_user_details(
615 #[cfg(not(test))] client: Option<&Client>, 612 #[cfg(not(test))] client: Option<&Client>,
616 git_repo: &Repo, 613 git_repo: &Repo,
617) -> Result<UserRef> { 614) -> Result<UserRef> {
618 if client.is_some() {
619 println!("searching for profile and relay updates...");
620 }
621 let database = SQLiteDatabase::open(if std::env::var("NGITTEST").is_err() {
622 create_dir_all(get_dirs()?.config_dir()).context(format!(
623 "cannot create cache directory in: {:?}",
624 get_dirs()?.config_dir()
625 ))?;
626 get_dirs()?.config_dir().join("cache.sqlite")
627 } else {
628 git_repo.get_path()?.join(".git/test-global-cache.sqlite")
629 })
630 .await?;
631 let mut events: Vec<nostr::Event> = vec![];
632 let filters = vec![ 615 let filters = vec![
633 nostr::Filter::default() 616 nostr::Filter::default()
634 .author(*public_key) 617 .author(*public_key)
@@ -637,54 +620,63 @@ async fn get_user_details(
637 .author(*public_key) 620 .author(*public_key)
638 .kind(Kind::RelayList), 621 .kind(Kind::RelayList),
639 ]; 622 ];
640 if let Ok(cached_events) = database.query(filters.clone(), Order::Asc).await {
641 for event in cached_events {
642 events.push(event);
643 }
644 }
645 let mut relays_to_search = if let Some(client) = client {
646 client.get_fallback_relays().clone()
647 } else {
648 vec![]
649 };
650 let mut relays_searched = vec![];
651 let user_ref = loop {
652 if let Some(client) = client {
653 for event in client
654 .get_events(relays_to_search.clone(), filters.clone())
655 .await
656 .unwrap_or(vec![])
657 {
658 let _ = database.save_event(&event).await;
659 events.push(event);
660 }
661 }
662 623
663 #[allow(clippy::clone_on_copy)] 624 let mut events = get_event_from_global_cache(git_repo.get_path()?, filters.clone()).await?;
664 let user_ref = UserRef {
665 public_key: public_key.clone(),
666 metadata: extract_user_metadata(public_key, &events)?,
667 relays: extract_user_relays(public_key, &events),
668 };
669 625
670 if client.is_none() { 626 if let Some(client) = client {
671 break user_ref; 627 if events.is_empty() {
672 } 628 let term = console::Term::stderr();
673 for r in &relays_to_search { 629 term.write_line("searching for profile...")?;
674 relays_searched.push(r.clone()); 630 let (_, progress_reporter) = client
631 .fetch_all(git_repo.get_path()?, &HashSet::new())
632 .await?;
633 events = get_event_from_global_cache(git_repo.get_path()?, filters).await?;
634 if !events.is_empty() {
635 progress_reporter.clear()?;
636 // term.clear_last_lines(1)?;
637 }
675 } 638 }
639 }
676 640
677 relays_to_search = user_ref 641 Ok(UserRef {
678 .relays 642 public_key: public_key.to_owned(),
679 .write() 643 metadata: extract_user_metadata(public_key, &events)?,
680 .iter() 644 relays: extract_user_relays(public_key, &events),
681 .filter(|r| !relays_searched.iter().any(|or| r.eq(&or))) 645 })
682 .map(std::clone::Clone::clone) 646}
683 .collect(); 647
684 if !relays_to_search.is_empty() { 648pub async fn get_logged_in_user_and_relays_from_cache(
685 continue; 649 git_repo_path: &Path,
650) -> Result<(Option<PublicKey>, HashSet<Url>)> {
651 let git_repo = Repo::from_path(&git_repo_path.to_path_buf())?;
652 let current_user = if let Some(npub) = git_repo.get_git_config_item("nostr.npub", None)? {
653 if let Ok(pubic_key) = PublicKey::parse(npub) {
654 Some(pubic_key)
655 } else {
656 None
686 } 657 }
687 break user_ref; 658 } else {
659 None
660 };
661 let relays = if let Some(current_user) = current_user {
662 extract_user_relays(
663 &current_user,
664 &get_event_from_global_cache(
665 git_repo.get_path()?,
666 vec![
667 nostr::Filter::default()
668 .author((*current_user).into())
669 .kind(Kind::RelayList),
670 ],
671 )
672 .await?,
673 )
674 .write()
675 .iter()
676 .filter_map(|r| Url::parse(r).ok())
677 .collect::<HashSet<Url>>()
678 } else {
679 HashSet::new()
688 }; 680 };
689 Ok(user_ref) 681 Ok((current_user, relays))
690} 682}
diff --git a/src/sub_commands/fetch.rs b/src/sub_commands/fetch.rs
index 07fd6f9..5a6850c 100644
--- a/src/sub_commands/fetch.rs
+++ b/src/sub_commands/fetch.rs
@@ -9,7 +9,7 @@ use crate::client::Client;
9#[cfg(test)] 9#[cfg(test)]
10use crate::client::MockConnect; 10use crate::client::MockConnect;
11use crate::{ 11use crate::{
12 client::Connect, 12 client::{consolidate_fetch_reports, Connect},
13 git::{Repo, RepoActions}, 13 git::{Repo, RepoActions},
14 repo_ref::get_repo_coordinates, 14 repo_ref::get_repo_coordinates,
15 Cli, 15 Cli,
@@ -38,9 +38,16 @@ pub async fn launch(args: &Cli, command_args: &SubCommandArgs) -> Result<()> {
38 } 38 }
39 repo_coordinates 39 repo_coordinates
40 }; 40 };
41 client 41 println!("fetching updates...");
42 let (relay_reports, _) = client
42 .fetch_all(git_repo.get_path()?, &repo_coordinates) 43 .fetch_all(git_repo.get_path()?, &repo_coordinates)
43 .await?; 44 .await?;
45 let report = consolidate_fetch_reports(relay_reports);
46 if report.to_string().is_empty() {
47 println!("no updates");
48 } else {
49 println!("updates: {report}");
50 }
44 client.disconnect().await?; 51 client.disconnect().await?;
45 Ok(()) 52 Ok(())
46} 53}