Merge remote-tracking branch 'dilger/unstable' into feature/profiles-ui-restyle

This commit is contained in:
Bu5hm4nn 2023-10-18 19:46:54 -06:00
commit e0963a37dc
33 changed files with 939 additions and 496 deletions

386
Cargo.lock generated

File diff suppressed because it is too large Load Diff

View File

@ -1,11 +1,13 @@
# Developing
![Gossip Architecture](./assets/architecture.png)
Gossip is architected with the following components:
- A User Interface thread, synchronous
- Tokio asynchronous runtime running
- An overlord (handles most jobs)
- A set of minions (each one handles one relay)
- An overlord: handles any operation that involves talking to relays, and a few more
- A set of minions: each one contacts a proper relay to get data, composing the filter and sending it to the relay
## Keeping the UI responsive
@ -48,7 +50,7 @@ The flow generally happens like this:
## Pull Requests
I prefer that you run and make pass:
Before issuing a Pull Request, please run and make pass:
````bash
cargo clippy
@ -60,6 +62,4 @@ and then
cargo fmt
````
before you issue a pull request. Otherwise I'll have to do it for you.
Also, I don't like branches that have a lot of commits that are messed up, but happen to end up in a good state due to the last commit. If you have a branch like this, create a new branch and one-by-one create a series of commits, each one a single logical step, each one compiling, each one passing clippy and rustfmt, each one documented, and each one doing something useful, such that this series of commits ends up where you originally got to (or somewhere even better). This not only makes it much easier to evaluate the PR, but it makes it possible to revert logical units later on if anything needs to be reverted, without necessarily reverting the entire branch.
Avoid branches that have a lot of commits that are messed up, but happen to end up in a good state due to the last commit. If you have a branch like this, create a new branch and one-by-one create a series of commits, each one a single logical step, each one compiling, each one passing clippy and rustfmt, each one documented, and each one doing something useful, such that this series of commits ends up where you originally got to (or somewhere even better). This not only makes it much easier to evaluate the PR, but it makes it possible to revert logical units later on if anything needs to be reverted, without necessarily reverting the entire branch.

Binary file not shown.

After

Width:  |  Height:  |  Size: 53 KiB

View File

@ -0,0 +1,104 @@
<mxfile host="app.diagrams.net" modified="2023-10-14T10:22:26.711Z" agent="Mozilla/5.0 (X11; Linux x86_64; rv:109.0) Gecko/20100101 Firefox/118.0" etag="dnj9X1hDH0qO7vgzD22C" version="22.0.4" type="device">
<diagram name="Page-1" id="TgtwNu3fVf1KPcH3ozeP">
<mxGraphModel dx="1434" dy="714" grid="1" gridSize="10" guides="1" tooltips="1" connect="1" arrows="1" fold="1" page="1" pageScale="1" pageWidth="850" pageHeight="1100" math="0" shadow="0">
<root>
<mxCell id="0" />
<mxCell id="1" parent="0" />
<mxCell id="np8rZvkhgI0D0TGchNWL-17" value="amuses with gossip" style="edgeStyle=orthogonalEdgeStyle;rounded=0;orthogonalLoop=1;jettySize=auto;html=1;" parent="1" edge="1">
<mxGeometry x="0.1286" y="20" relative="1" as="geometry">
<mxPoint x="61" y="370" as="sourcePoint" />
<mxPoint x="201" y="370" as="targetPoint" />
<mxPoint as="offset" />
</mxGeometry>
</mxCell>
<mxCell id="np8rZvkhgI0D0TGchNWL-1" value="User" style="shape=umlActor;verticalLabelPosition=bottom;verticalAlign=top;html=1;outlineConnect=0;" parent="1" vertex="1">
<mxGeometry x="30" y="340" width="30" height="60" as="geometry" />
</mxCell>
<mxCell id="np8rZvkhgI0D0TGchNWL-9" style="edgeStyle=orthogonalEdgeStyle;rounded=0;orthogonalLoop=1;jettySize=auto;html=1;entryX=0;entryY=0.5;entryDx=0;entryDy=0;exitX=1;exitY=0.5;exitDx=0;exitDy=0;" parent="1" target="np8rZvkhgI0D0TGchNWL-3" edge="1">
<mxGeometry relative="1" as="geometry">
<mxPoint x="320" y="370" as="sourcePoint" />
<Array as="points" />
</mxGeometry>
</mxCell>
<mxCell id="np8rZvkhgI0D0TGchNWL-10" value="send messages" style="edgeLabel;html=1;align=center;verticalAlign=middle;resizable=0;points=[];" parent="np8rZvkhgI0D0TGchNWL-9" vertex="1" connectable="0">
<mxGeometry x="0.1677" y="4" relative="1" as="geometry">
<mxPoint x="-101" y="-16" as="offset" />
</mxGeometry>
</mxCell>
<mxCell id="np8rZvkhgI0D0TGchNWL-26" value="&lt;div&gt;renders data&lt;/div&gt;" style="edgeStyle=orthogonalEdgeStyle;rounded=0;orthogonalLoop=1;jettySize=auto;html=1;entryX=0;entryY=0.5;entryDx=0;entryDy=0;exitX=0.5;exitY=1;exitDx=0;exitDy=0;" parent="1" source="np8rZvkhgI0D0TGchNWL-32" target="np8rZvkhgI0D0TGchNWL-8" edge="1">
<mxGeometry x="-0.875" y="50" relative="1" as="geometry">
<mxPoint x="260" y="420" as="sourcePoint" />
<mxPoint x="260" y="640" as="targetPoint" />
<Array as="points">
<mxPoint x="260" y="640" />
</Array>
<mxPoint as="offset" />
</mxGeometry>
</mxCell>
<mxCell id="np8rZvkhgI0D0TGchNWL-12" style="edgeStyle=orthogonalEdgeStyle;rounded=0;orthogonalLoop=1;jettySize=auto;html=1;exitX=1;exitY=0.5;exitDx=0;exitDy=0;" parent="1" source="np8rZvkhgI0D0TGchNWL-3" target="np8rZvkhgI0D0TGchNWL-4" edge="1">
<mxGeometry relative="1" as="geometry">
<mxPoint x="830" y="460" as="targetPoint" />
<mxPoint x="730" y="370" as="sourcePoint" />
<Array as="points">
<mxPoint x="831" y="370" />
</Array>
</mxGeometry>
</mxCell>
<mxCell id="np8rZvkhgI0D0TGchNWL-13" value="engages async workers (called minions)" style="edgeLabel;html=1;align=center;verticalAlign=middle;resizable=0;points=[];" parent="np8rZvkhgI0D0TGchNWL-12" vertex="1" connectable="0">
<mxGeometry x="-0.4226" y="-5" relative="1" as="geometry">
<mxPoint x="75" y="-25" as="offset" />
</mxGeometry>
</mxCell>
<mxCell id="np8rZvkhgI0D0TGchNWL-3" value="Overload" style="rounded=1;whiteSpace=wrap;html=1;fillColor=#f8cecc;strokeColor=#b85450;" parent="1" vertex="1">
<mxGeometry x="580" y="340" width="120" height="60" as="geometry" />
</mxCell>
<mxCell id="np8rZvkhgI0D0TGchNWL-7" value="" style="group;" parent="1" vertex="1" connectable="0">
<mxGeometry x="750" y="420" width="160" height="100" as="geometry" />
</mxCell>
<mxCell id="np8rZvkhgI0D0TGchNWL-4" value="" style="shape=parallelogram;perimeter=parallelogramPerimeter;whiteSpace=wrap;html=1;fixedSize=1;fillColor=#e3c800;fontColor=#000000;strokeColor=#B09500;" parent="np8rZvkhgI0D0TGchNWL-7" vertex="1">
<mxGeometry width="120" height="60" as="geometry" />
</mxCell>
<mxCell id="np8rZvkhgI0D0TGchNWL-5" value="" style="shape=parallelogram;perimeter=parallelogramPerimeter;whiteSpace=wrap;html=1;fixedSize=1;fillColor=#d5e8d4;strokeColor=#82b366;" parent="np8rZvkhgI0D0TGchNWL-7" vertex="1">
<mxGeometry x="20" y="20" width="120" height="60" as="geometry" />
</mxCell>
<mxCell id="np8rZvkhgI0D0TGchNWL-6" value="Minions" style="shape=parallelogram;perimeter=parallelogramPerimeter;whiteSpace=wrap;html=1;fixedSize=1;fillColor=#e1d5e7;strokeColor=#9673a6;" parent="np8rZvkhgI0D0TGchNWL-7" vertex="1">
<mxGeometry x="40" y="40" width="120" height="60" as="geometry" />
</mxCell>
<mxCell id="np8rZvkhgI0D0TGchNWL-8" value="Globals" style="rounded=1;whiteSpace=wrap;html=1;fillColor=#d5e8d4;strokeColor=#82b366;" parent="1" vertex="1">
<mxGeometry x="340" y="610" width="120" height="60" as="geometry" />
</mxCell>
<mxCell id="np8rZvkhgI0D0TGchNWL-23" value="callback" style="edgeStyle=orthogonalEdgeStyle;rounded=0;orthogonalLoop=1;jettySize=auto;html=1;entryX=0.75;entryY=1;entryDx=0;entryDy=0;dashed=1;dashPattern=8 8;" parent="1" source="np8rZvkhgI0D0TGchNWL-4" edge="1">
<mxGeometry x="-0.0909" y="10" relative="1" as="geometry">
<mxPoint x="700" y="400" as="targetPoint" />
<mxPoint as="offset" />
</mxGeometry>
</mxCell>
<mxCell id="np8rZvkhgI0D0TGchNWL-29" value="stores data" style="edgeStyle=orthogonalEdgeStyle;rounded=0;orthogonalLoop=1;jettySize=auto;html=1;entryX=1;entryY=0.5;entryDx=0;entryDy=0;" parent="1" source="np8rZvkhgI0D0TGchNWL-27" target="np8rZvkhgI0D0TGchNWL-8" edge="1">
<mxGeometry x="-0.1667" y="-10" relative="1" as="geometry">
<Array as="points">
<mxPoint x="640" y="640" />
</Array>
<mxPoint as="offset" />
</mxGeometry>
</mxCell>
<mxCell id="np8rZvkhgI0D0TGchNWL-27" value="Process.rs" style="rounded=1;whiteSpace=wrap;html=1;fillColor=#fff2cc;strokeColor=#d6b656;" parent="1" vertex="1">
<mxGeometry x="580" y="610" width="120" height="60" as="geometry" />
</mxCell>
<mxCell id="np8rZvkhgI0D0TGchNWL-30" value="Architecture Overview" style="text;html=1;strokeColor=none;fillColor=none;align=center;verticalAlign=middle;whiteSpace=wrap;rounded=0;fontSize=31;" parent="1" vertex="1">
<mxGeometry x="280" y="200" width="510" height="30" as="geometry" />
</mxCell>
<mxCell id="np8rZvkhgI0D0TGchNWL-32" value="GUI" style="rounded=1;whiteSpace=wrap;html=1;fillColor=#dae8fc;strokeColor=#6c8ebf;" parent="1" vertex="1">
<mxGeometry x="200" y="340" width="120" height="60" as="geometry" />
</mxCell>
<mxCell id="2C8rZJ-Ew4Z57BIN0wND-1" value="events flow from minions" style="edgeStyle=orthogonalEdgeStyle;rounded=0;orthogonalLoop=1;jettySize=auto;html=1;entryX=1;entryY=0.5;entryDx=0;entryDy=0;" edge="1" parent="1" source="np8rZvkhgI0D0TGchNWL-6" target="np8rZvkhgI0D0TGchNWL-27">
<mxGeometry x="-0.5556" y="90" relative="1" as="geometry">
<Array as="points">
<mxPoint x="850" y="640" />
</Array>
<mxPoint as="offset" />
</mxGeometry>
</mxCell>
</root>
</mxGraphModel>
</diagram>
</mxfile>

View File

@ -21,13 +21,13 @@ bech32 = "0.9"
eframe = { git = "https://github.com/mikedilger/egui", rev = "50393e4f34ac6246b8c2424e42fbe5b95e4b4452", features = [ "persistence", "wayland" ] }
egui-winit = { git = "https://github.com/mikedilger/egui", rev = "50393e4f34ac6246b8c2424e42fbe5b95e4b4452", features = [ "default" ] }
egui-video = { git = "https://github.com/mikedilger/egui-video", rev = "81cc3ee58818754272582397161cc55ff11bde18", features = [ "from_bytes" ], optional = true }
gossip-relay-picker = { git = "https://github.com/mikedilger/gossip-relay-picker", rev = "2971fff6f3e72d5998dd9ecea9b029abcce86c2f" }
gossip-relay-picker = { git = "https://github.com/mikedilger/gossip-relay-picker", rev = "4f1ade0e491889544c331633df9a275f59ccaffd" }
gossip-lib = { path = "../gossip-lib" }
humansize = "2.1"
image = { version = "0.24.6", features = [ "png", "jpeg" ] }
lazy_static = "1.4"
memoize = "0.4"
nostr-types = { git = "https://github.com/mikedilger/nostr-types", rev = "976bbba43404ee9130a6f459bdca4564e9355c8c", features = [ "speedy" ] }
nostr-types = { git = "https://github.com/mikedilger/nostr-types", rev = "321e8b778f632fe084d45b5be9062e88b9742f92", features = [ "speedy" ] }
qrcode = { git = "https://github.com/mikedilger/qrcode-rust", rev = "519b77b3efa3f84961169b47d3de08c5ddd86548" }
resvg = "0.35.0"
rpassword = "7.2"

View File

@ -438,7 +438,9 @@ pub fn print_relays(_cmd: Command) -> Result<(), Error> {
}
pub fn print_followed(_cmd: Command) -> Result<(), Error> {
let pubkeys = GLOBALS.storage.get_people_in_list(PersonList::Followed)?;
let pubkeys = GLOBALS
.storage
.get_people_in_list(PersonList::Followed, None)?;
for pk in &pubkeys {
println!("{}", pk.as_hex_string());
}
@ -446,7 +448,9 @@ pub fn print_followed(_cmd: Command) -> Result<(), Error> {
}
pub fn print_muted(_cmd: Command) -> Result<(), Error> {
let pubkeys = GLOBALS.storage.get_people_in_list(PersonList::Muted)?;
let pubkeys = GLOBALS
.storage
.get_people_in_list(PersonList::Muted, None)?;
for pk in &pubkeys {
println!("{}", pk.as_hex_string());
}

View File

@ -52,16 +52,20 @@ pub(super) fn update(app: &mut GossipUi, ctx: &Context, frame: &mut eframe::Fram
let feed_kind = GLOBALS.feed.get_feed_kind();
match feed_kind {
FeedKind::Followed(with_replies) => {
FeedKind::List(list, with_replies) => {
let feed = GLOBALS.feed.get_followed();
let id = if with_replies { "main" } else { "general" };
let id = format!(
"{} {}",
Into::<u8>::into(list),
if with_replies { "main" } else { "general" }
);
ui.add_space(10.0);
ui.allocate_ui_with_layout(
Vec2::new(ui.available_width(), ui.spacing().interact_size.y),
egui::Layout::left_to_right(egui::Align::Center),
|ui| {
add_left_space(ui);
ui.heading("Main feed");
ui.heading(list.name());
recompute_btn(app, ui);
ui.with_layout(egui::Layout::right_to_left(egui::Align::Center), |ui| {
@ -75,7 +79,8 @@ pub(super) fn update(app: &mut GossipUi, ctx: &Context, frame: &mut eframe::Fram
)
.clicked()
{
app.set_page(Page::Feed(FeedKind::Followed(
app.set_page(Page::Feed(FeedKind::List(
list,
app.mainfeed_include_nonroot,
)));
ctx.data_mut(|d| {
@ -90,7 +95,7 @@ pub(super) fn update(app: &mut GossipUi, ctx: &Context, frame: &mut eframe::Fram
},
);
ui.add_space(6.0);
render_a_feed(app, ctx, frame, ui, feed, false, id);
render_a_feed(app, ctx, frame, ui, feed, false, &id);
}
FeedKind::Inbox(indirect) => {
if app.settings.public_key.is_none() {

View File

@ -1,5 +1,6 @@
use gossip_lib::GLOBALS;
use gossip_lib::{Person, PersonList};
use std::collections::HashMap;
use nostr_types::{
ContentSegment, Event, EventDelegation, EventKind, Id, MilliSatoshi, NostrBech32, PublicKey,
@ -27,7 +28,7 @@ pub(super) struct NoteData {
/// Author of this note (considers delegation)
pub(super) author: Person,
/// Lists the author is on
pub(super) lists: Vec<PersonList>,
pub(super) lists: HashMap<PersonList, bool>,
/// Deletion reason if any
pub(super) deletion: Option<String>,
/// Do we consider this note as being a repost of another?
@ -197,7 +198,7 @@ impl NoteData {
let lists = match GLOBALS.storage.read_person_lists(&author_pubkey) {
Ok(lists) => lists,
_ => vec![],
_ => HashMap::new(),
};
NoteData {
@ -232,10 +233,10 @@ impl NoteData {
#[allow(dead_code)]
pub(super) fn followed(&self) -> bool {
self.lists.contains(&PersonList::Followed)
self.lists.contains_key(&PersonList::Followed)
}
pub(super) fn muted(&self) -> bool {
self.lists.contains(&PersonList::Muted)
self.lists.contains_key(&PersonList::Muted)
}
}

View File

@ -1,7 +1,7 @@
use super::{GossipUi, Page};
use eframe::egui;
use egui::{Context, Ui};
use gossip_lib::FeedKind;
use gossip_lib::{FeedKind, PersonList};
mod about;
mod stats;
@ -103,7 +103,7 @@ pub(super) fn update(app: &mut GossipUi, ctx: &Context, _frame: &mut eframe::Fra
ui.horizontal_wrapped(|ui| {
ui.label("On the");
if ui.link("Feed > Following").clicked() {
app.set_page(Page::Feed(FeedKind::Followed(app.mainfeed_include_nonroot)));
app.set_page(Page::Feed(FeedKind::List(PersonList::Followed, app.mainfeed_include_nonroot)));
}
ui.label("page, enjoy the feed.");
});
@ -199,7 +199,7 @@ pub(super) fn update(app: &mut GossipUi, ctx: &Context, _frame: &mut eframe::Fra
ui.horizontal_wrapped(|ui| {
ui.label("On the");
if ui.link("Feed > Following").clicked() {
app.set_page(Page::Feed(FeedKind::Followed(app.mainfeed_include_nonroot)));
app.set_page(Page::Feed(FeedKind::List(PersonList::Followed, app.mainfeed_include_nonroot)));
}
ui.label("page, enjoy the feed.");
});

View File

@ -42,7 +42,7 @@ use gossip_lib::Settings;
use gossip_lib::{DmChannel, DmChannelData};
use gossip_lib::{Person, PersonList};
use gossip_lib::{ZapState, GLOBALS};
use nostr_types::{Id, IdHex, Metadata, MilliSatoshi, Profile, PublicKey, UncheckedUrl, Url};
use nostr_types::{Id, Metadata, MilliSatoshi, Profile, PublicKey, UncheckedUrl, Url};
use std::collections::{HashMap, HashSet};
#[cfg(feature = "video-ffmpeg")]
use std::rc::Rc;
@ -550,7 +550,7 @@ impl GossipUi {
None => (false, dpi),
};
let mut start_page = Page::Feed(FeedKind::Followed(false));
let mut start_page = Page::Feed(FeedKind::List(PersonList::Followed, false));
// Possibly enter the wizard instead
let mut wizard_state: WizardState = Default::default();
@ -683,8 +683,8 @@ impl GossipUi {
fn set_page_inner(&mut self, page: Page) {
// Setting the page often requires some associated actions:
match &page {
Page::Feed(FeedKind::Followed(with_replies)) => {
GLOBALS.feed.set_feed_to_followed(*with_replies);
Page::Feed(FeedKind::List(list, with_replies)) => {
GLOBALS.feed.set_feed_to_main(*list, *with_replies);
}
Page::Feed(FeedKind::Inbox(indirect)) => {
GLOBALS.feed.set_feed_to_inbox(*indirect);
@ -763,8 +763,8 @@ impl GossipUi {
ui.separator();
ui.add_space(4.0);
if self.add_selected_label(ui, matches!(self.page, Page::Feed(FeedKind::Followed(_))), "Main Feed").clicked() {
self.set_page(Page::Feed(FeedKind::Followed(self.mainfeed_include_nonroot)));
if self.add_selected_label(ui, matches!(self.page, Page::Feed(FeedKind::List(PersonList::Followed, _))), "Main Feed").clicked() {
self.set_page(Page::Feed(FeedKind::List(PersonList::Followed, self.mainfeed_include_nonroot)));
}
if let Some(pubkey) = GLOBALS.signer.public_key() {
if self.add_selected_label(ui, matches!(&self.page, Page::Feed(FeedKind::Person(key)) if *key == pubkey), "My Notes").clicked() {
@ -1167,16 +1167,16 @@ impl GossipUi {
}
}
if !followed && ui.button("Follow").clicked() {
let _ = GLOBALS.people.follow(&person.pubkey, true);
let _ = GLOBALS.people.follow(&person.pubkey, true, true);
} else if followed && ui.button("Unfollow").clicked() {
let _ = GLOBALS.people.follow(&person.pubkey, false);
let _ = GLOBALS.people.follow(&person.pubkey, false, true);
}
// Do not show 'Mute' if this is yourself
if muted || !is_self {
let mute_label = if muted { "Unmute" } else { "Mute" };
if ui.button(mute_label).clicked() {
let _ = GLOBALS.people.mute(&person.pubkey, !muted);
let _ = GLOBALS.people.mute(&person.pubkey, !muted, true);
app.notes.cache_invalidate_person(&person.pubkey);
}
}
@ -1497,7 +1497,7 @@ impl GossipUi {
"VISIBLE = {:?}",
self.visible_note_ids
.iter()
.map(|id| Into::<IdHex>::into(*id).prefix(10).into_string())
.map(|id| id.as_hex_string().as_str().get(0..10).unwrap().to_owned())
.collect::<Vec<_>>()
);

View File

@ -38,19 +38,20 @@ pub(super) fn update(app: &mut GossipUi, _ctx: &Context, _frame: &mut eframe::Fr
if let Ok(pubkey) = PublicKey::try_from_bech32_string(app.follow_someone.trim(), true) {
let _ = GLOBALS
.to_overlord
.send(ToOverlordMessage::FollowPubkey(pubkey));
.send(ToOverlordMessage::FollowPubkey(pubkey, true));
} else if let Ok(pubkey) = PublicKey::try_from_hex_string(app.follow_someone.trim(), true) {
let _ = GLOBALS
.to_overlord
.send(ToOverlordMessage::FollowPubkey(pubkey));
.send(ToOverlordMessage::FollowPubkey(pubkey, true));
} else if let Ok(profile) = Profile::try_from_bech32_string(app.follow_someone.trim(), true)
{
let _ = GLOBALS
.to_overlord
.send(ToOverlordMessage::FollowNprofile(profile));
.send(ToOverlordMessage::FollowNprofile(profile, true));
} else if gossip_lib::nip05::parse_nip05(app.follow_someone.trim()).is_ok() {
let _ = GLOBALS.to_overlord.send(ToOverlordMessage::FollowNip05(
app.follow_someone.trim().to_owned(),
true,
));
} else {
GLOBALS

View File

@ -112,7 +112,7 @@ pub(super) fn update(app: &mut GossipUi, ctx: &Context, _frame: &mut eframe::Fra
{
let _ = GLOBALS
.to_overlord
.send(ToOverlordMessage::RefreshFollowedMetadata);
.send(ToOverlordMessage::RefreshSubscribedMetadata);
}
});

View File

@ -157,7 +157,7 @@ pub(super) fn update(app: &mut GossipUi, ctx: &Context, _frame: &mut eframe::Fra
GossipUi::render_person_name_line(app, ui, person, false);
if ui.button("UNMUTE").clicked() {
let _ = GLOBALS.people.mute(&person.pubkey, false);
let _ = GLOBALS.people.mute(&person.pubkey, false, true);
}
});
});

View File

@ -86,12 +86,10 @@ pub(super) fn set_important_button_visuals(ui: &mut Ui, app: &GossipUi) {
let visuals = ui.visuals_mut();
visuals.widgets.inactive.weak_bg_fill = app.theme.accent_color();
visuals.widgets.inactive.fg_stroke.width = 1.0;
visuals.widgets.inactive.fg_stroke.color =
app.theme.get_style().visuals.extreme_bg_color;
visuals.widgets.inactive.fg_stroke.color = app.theme.get_style().visuals.extreme_bg_color;
visuals.widgets.hovered.weak_bg_fill = app.theme.navigation_text_color();
visuals.widgets.hovered.fg_stroke.color = app.theme.accent_color();
visuals.widgets.inactive.fg_stroke.color =
app.theme.get_style().visuals.extreme_bg_color;
visuals.widgets.inactive.fg_stroke.color = app.theme.get_style().visuals.extreme_bg_color;
}
// /// UTF-8 safe truncate (String::truncate() can panic)

View File

@ -357,7 +357,15 @@ impl RelayEntry {
let is_personal = self.relay.usage_bits != 0;
let id = self.make_id("remove_link");
let text = "Remove from personal list";
let response = draw_link_at(ui, id, pos, text.into(), Align::Min, self.enabled && is_personal, true);
let response = draw_link_at(
ui,
id,
pos,
text.into(),
Align::Min,
self.enabled && is_personal,
true,
);
if response.clicked() {
let _ = GLOBALS.storage.modify_relay(
&self.relay.url,
@ -379,15 +387,8 @@ impl RelayEntry {
let id = self.make_id("disconnect_link");
let text = "Force disconnect";
let can_disconnect = self.enabled && self.connected;
let disconnect_response = draw_link_at(
ui,
id,
pos,
text.into(),
Align::Min,
can_disconnect,
true,
);
let disconnect_response =
draw_link_at(ui, id, pos, text.into(), Align::Min, can_disconnect, true);
if can_disconnect && disconnect_response.clicked() {
let _ = GLOBALS
.to_overlord

View File

@ -3,9 +3,7 @@ use crate::ui::{GossipUi, Page};
use eframe::egui;
use egui::{Context, RichText, Ui};
use gossip_lib::comms::ToOverlordMessage;
use gossip_lib::FeedKind;
use gossip_lib::Person;
use gossip_lib::GLOBALS;
use gossip_lib::{FeedKind, Person, PersonList, GLOBALS};
use gossip_relay_picker::Direction;
use nostr_types::{Profile, PublicKey};
@ -96,22 +94,23 @@ pub(super) fn update(app: &mut GossipUi, _ctx: &Context, _frame: &mut eframe::Fr
if let Ok(pubkey) = PublicKey::try_from_bech32_string(app.follow_someone.trim(), true) {
let _ = GLOBALS
.to_overlord
.send(ToOverlordMessage::FollowPubkey(pubkey));
.send(ToOverlordMessage::FollowPubkey(pubkey, true));
} else if let Ok(pubkey) =
PublicKey::try_from_hex_string(app.follow_someone.trim(), true)
{
let _ = GLOBALS
.to_overlord
.send(ToOverlordMessage::FollowPubkey(pubkey));
.send(ToOverlordMessage::FollowPubkey(pubkey, true));
} else if let Ok(profile) =
Profile::try_from_bech32_string(app.follow_someone.trim(), true)
{
let _ = GLOBALS
.to_overlord
.send(ToOverlordMessage::FollowNprofile(profile));
.send(ToOverlordMessage::FollowNprofile(profile, true));
} else if gossip_lib::nip05::parse_nip05(app.follow_someone.trim()).is_ok() {
let _ = GLOBALS.to_overlord.send(ToOverlordMessage::FollowNip05(
app.follow_someone.trim().to_owned(),
true,
));
} else {
app.wizard_state.error = Some("ERROR: Invalid pubkey".to_owned());
@ -143,7 +142,7 @@ pub(super) fn update(app: &mut GossipUi, _ctx: &Context, _frame: &mut eframe::Fr
let _ = GLOBALS.to_overlord.send(ToOverlordMessage::PushFollow);
let _ = GLOBALS.storage.write_wizard_complete(true, None);
app.page = Page::Feed(FeedKind::Followed(false));
app.page = Page::Feed(FeedKind::List(PersonList::Followed, false));
}
ui.add_space(20.0);
@ -153,7 +152,7 @@ pub(super) fn update(app: &mut GossipUi, _ctx: &Context, _frame: &mut eframe::Fr
}
if ui.button(label).clicked() {
let _ = GLOBALS.storage.write_wizard_complete(true, None);
app.page = Page::Feed(FeedKind::Followed(false));
app.page = Page::Feed(FeedKind::List(PersonList::Followed, false));
}
} else {
ui.add_space(20.0);
@ -161,7 +160,7 @@ pub(super) fn update(app: &mut GossipUi, _ctx: &Context, _frame: &mut eframe::Fr
label = label.color(app.theme.accent_color());
if ui.button(label).clicked() {
let _ = GLOBALS.storage.write_wizard_complete(true, None);
app.page = Page::Feed(FeedKind::Followed(false));
app.page = Page::Feed(FeedKind::List(PersonList::Followed, false));
}
}
}

View File

@ -2,9 +2,7 @@ use crate::ui::{GossipUi, Page};
use eframe::egui;
use egui::widgets::{Button, Slider};
use egui::{Align, Context, Layout};
use gossip_lib::FeedKind;
use gossip_lib::Relay;
use gossip_lib::GLOBALS;
use gossip_lib::{FeedKind, PersonList, Relay, GLOBALS};
mod follow_people;
mod import_keys;
@ -23,23 +21,23 @@ static DEFAULT_RELAYS: [&str; 20] = [
"wss://nostr.einundzwanzig.space/",
"wss://nostr.mutinywallet.com/",
"wss://relay.nostrplebs.com/",
"wss://christpill.nostr1.com/",
"wss://nostr-pub.wellorder.net/",
"wss://relay.damus.io/",
"wss://bevo.nostr1.com/",
"wss://relay.snort.social/",
"wss://public.relaying.io/",
"wss://nostrue.com/",
"wss://relay.snort.social/",
"wss://relay.noswhere.com/",
"wss://relay.primal.net/",
"wss://relay.nostr.jabber.ch/",
"wss://relay.nostr.band/",
"wss://relay.wellorder.net/",
"wss://nostr.coinfundit.com/",
"wss://relay.nostrich.de/",
"wss://verbiricha.nostr1.com/",
"wss://relay.nostr.band/",
"wss://nostr21.com/",
"wss://relayable.org/",
"wss://nostr.bitcoiner.social/",
"wss://offchain.pub/",
"wss://nostr.coinfundit.com/",
"wss://nstr.pjv.me/",
"wss://welcome.nostr.wine/",
];
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
@ -201,7 +199,7 @@ pub(super) fn update(app: &mut GossipUi, ctx: &Context, frame: &mut eframe::Fram
if wp != WizardPage::FollowPeople {
if ui.button(" X Exit this Wizard").clicked() {
let _ = GLOBALS.storage.write_wizard_complete(true, None);
app.page = Page::Feed(FeedKind::Followed(false));
app.page = Page::Feed(FeedKind::List(PersonList::Followed, false));
}
}

View File

@ -53,22 +53,12 @@ pub(super) fn update(app: &mut GossipUi, _ctx: &Context, _frame: &mut eframe::Fr
.cloned()
.collect();
let mut discovery_relays: Vec<Relay> = relays
let discovery_relays: Vec<Relay> = relays
.iter()
.filter(|relay| relay.has_usage_bits(Relay::DISCOVER))
.cloned()
.collect();
if !discovery_relays
.iter()
.any(|r| r.url.as_str() == "wss://purplepag.es/")
{
let mut purple_pages = read_relay(&RelayUrl::try_from_str("wss://purplepag.es/").unwrap());
purple_pages.set_usage_bits(Relay::DISCOVER);
let _ = GLOBALS.storage.write_relay(&purple_pages, None);
discovery_relays.push(purple_pages);
}
let mut need_more = false;
ui.add_space(20.0);
@ -141,7 +131,9 @@ pub(super) fn update(app: &mut GossipUi, _ctx: &Context, _frame: &mut eframe::Fr
for relay in discovery_relays.iter() {
ui.horizontal(|ui| {
if ui.button("🗑").clicked() {
unimplemented!();
let mut r = relay.clone();
r.clear_usage_bits(Relay::DISCOVER | Relay::ADVERTISE);
let _ = GLOBALS.storage.write_relay(&r, None);
}
ui.label(relay.url.as_str());
});

View File

@ -27,7 +27,7 @@ fallible-iterator = "0.2"
filetime = "0.2"
futures = "0.3"
futures-util = "0.3"
gossip-relay-picker = { git = "https://github.com/mikedilger/gossip-relay-picker", rev = "2971fff6f3e72d5998dd9ecea9b029abcce86c2f" }
gossip-relay-picker = { git = "https://github.com/mikedilger/gossip-relay-picker", rev = "4f1ade0e491889544c331633df9a275f59ccaffd" }
heed = { git = "https://github.com/meilisearch/heed", rev = "02030e3bf3d26ee98d4f5343fc086a7b63289159" }
hex = "0.4"
http = "0.2"
@ -36,7 +36,7 @@ kamadak-exif = "0.5"
lazy_static = "1.4"
linkify = "0.9"
mime = "0.3"
nostr-types = { git = "https://github.com/mikedilger/nostr-types", rev = "976bbba43404ee9130a6f459bdca4564e9355c8c", features = [ "speedy" ] }
nostr-types = { git = "https://github.com/mikedilger/nostr-types", rev = "321e8b778f632fe084d45b5be9062e88b9742f92", features = [ "speedy" ] }
parking_lot = "0.12"
paste = "1.0"
rand = "0.8"

View File

@ -52,13 +52,13 @@ pub enum ToOverlordMessage {
FetchEventAddr(EventAddr),
/// Calls [follow_pubkey](crate::Overlord::follow_pubkey)
FollowPubkey(PublicKey),
FollowPubkey(PublicKey, bool),
/// Calls [follow_nip05](crate::Overlord::follow_nip05)
FollowNip05(String),
FollowNip05(String, bool),
/// Calls [follow_nprofile](crate::Overlord::follow_nprofile)
FollowNprofile(Profile),
FollowNprofile(Profile, bool),
/// Calls [generate_private_key](crate::Overlord::generate_private_key)
GeneratePrivateKey(String),
@ -79,9 +79,6 @@ pub enum ToOverlordMessage {
/// Calls [like](crate::Overlord::like)
Like(Id, PublicKey),
/// internal (minions use this channel too)
MinionIsReady,
/// internal (minions use this channel too)
MinionJobComplete(RelayUrl, u64),
@ -120,8 +117,8 @@ pub enum ToOverlordMessage {
/// internal (the overlord sends messages to itself sometimes!)
ReengageMinion(RelayUrl, Vec<RelayJob>),
/// Calls [reresh_followed_metadata](crate::Overlord::refresh_followed_metadata)
RefreshFollowedMetadata,
/// Calls [reresh_subscribed_metadata](crate::Overlord::refresh_subscribed_metadata)
RefreshSubscribedMetadata,
/// Calls [repost](crate::Overlord::repost)
Repost(Id),

View File

@ -2,6 +2,7 @@ use crate::comms::{ToMinionMessage, ToMinionPayload, ToMinionPayloadDetail, ToOv
use crate::dm_channel::DmChannel;
use crate::error::Error;
use crate::globals::GLOBALS;
use crate::people::PersonList;
use nostr_types::{Event, EventKind, Id, PublicKey, PublicKeyHex, RelayUrl, Unixtime};
use parking_lot::RwLock;
use std::collections::HashSet;
@ -12,8 +13,8 @@ use tokio::task;
/// Kinds of feeds, with configuration parameteers
#[derive(Clone, Debug, PartialEq, Eq)]
pub enum FeedKind {
Followed(bool), // with replies
Inbox(bool), // indirect
List(PersonList, bool), // with replies
Inbox(bool), // indirect
Thread {
id: Id,
referenced_by: Id,
@ -27,7 +28,7 @@ impl std::fmt::Display for FeedKind {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
match self {
FeedKind::DmChat(channel) => write!(f, "{}", channel.name()),
FeedKind::Followed(_) => write!(f, "Following"),
FeedKind::List(pl, _) => write!(f, "{}", pl.name()),
FeedKind::Inbox(_) => write!(f, "Inbox"),
FeedKind::Thread {
id: _,
@ -69,7 +70,7 @@ impl Feed {
pub(crate) fn new() -> Feed {
Feed {
recompute_lock: AtomicBool::new(false),
current_feed_kind: RwLock::new(FeedKind::Followed(false)),
current_feed_kind: RwLock::new(FeedKind::List(PersonList::Followed, false)),
followed_feed: RwLock::new(Vec::new()),
inbox_feed: RwLock::new(Vec::new()),
person_feed: RwLock::new(Vec::new()),
@ -108,12 +109,12 @@ impl Feed {
}
}
/// Change the feed to the main `followed` feed
pub fn set_feed_to_followed(&self, with_replies: bool) {
/// Change the feed to the main feed
pub fn set_feed_to_main(&self, list: PersonList, with_replies: bool) {
// We are always subscribed to the general feed. Don't resubscribe here
// because it won't have changed, but the relays will shower you with
// all those events again.
*self.current_feed_kind.write() = FeedKind::Followed(with_replies);
*self.current_feed_kind.write() = FeedKind::List(list, with_replies);
*self.thread_parent.write() = None;
// Recompute as they switch
@ -301,11 +302,11 @@ impl Feed {
let current_feed_kind = self.current_feed_kind.read().to_owned();
match current_feed_kind {
FeedKind::Followed(with_replies) => {
let mut followed_pubkeys: Vec<PublicKey> = GLOBALS.people.get_followed_pubkeys();
FeedKind::List(list, with_replies) => {
let mut pubkeys: Vec<PublicKey> = GLOBALS.storage.get_people_in_list(list, None)?;
if let Some(pubkey) = GLOBALS.signer.public_key() {
followed_pubkeys.push(pubkey); // add the user
pubkeys.push(pubkey); // add the user
}
let since = now - Duration::from_secs(GLOBALS.storage.read_setting_feed_chunk());
@ -313,11 +314,11 @@ impl Feed {
// FIXME we don't include delegated events. We should look for all events
// delegated to people we follow and include those in the feed too.
let followed_events: Vec<Id> = GLOBALS
let events: Vec<Id> = GLOBALS
.storage
.find_events(
&kinds_without_dms,
&followed_pubkeys, // pubkeys
&pubkeys, // pubkeys
Some(since),
|e| {
e.created_at <= now // no future events
@ -336,7 +337,7 @@ impl Feed {
.map(|e| e.id)
.collect();
*self.followed_feed.write() = followed_events;
*self.followed_feed.write() = events;
}
FeedKind::Inbox(indirect) => {
if let Some(my_pubkey) = GLOBALS.signer.public_key() {

View File

@ -91,7 +91,7 @@ pub async fn validate_nip05(person: Person) -> Result<(), Error> {
Ok(())
}
pub async fn get_and_follow_nip05(nip05: String) -> Result<(), Error> {
pub async fn get_and_follow_nip05(nip05: String, public: bool) -> Result<(), Error> {
// Split their DNS ID
let (user, domain) = parse_nip05(&nip05)?;
@ -115,8 +115,8 @@ pub async fn get_and_follow_nip05(nip05: String) -> Result<(), Error> {
)
.await?;
// Mark as followed
GLOBALS.people.follow(&pubkey, true)?;
// Mark as followed, publicly
GLOBALS.people.follow(&pubkey, true, public)?;
tracing::info!("Followed {}", &nip05);

View File

@ -16,8 +16,8 @@ use http::uri::{Parts, Scheme};
use http::Uri;
use mime::Mime;
use nostr_types::{
ClientMessage, EventAddr, EventKind, Filter, Id, IdHex, IdHexPrefix, PublicKey, PublicKeyHex,
PublicKeyHexPrefix, RelayInformationDocument, RelayUrl, Unixtime,
ClientMessage, EventAddr, EventKind, Filter, Id, IdHex, PublicKey, PublicKeyHex,
RelayInformationDocument, RelayUrl, Unixtime,
};
use reqwest::Response;
use std::borrow::Cow;
@ -240,9 +240,6 @@ impl Minion {
self.handle_overlord_message(message).await?;
}
// Tell the overlord we are ready to receive commands
self.tell_overlord_we_are_ready().await?;
'relayloop: loop {
match self.loop_handler().await {
Ok(_) => {
@ -431,11 +428,6 @@ impl Minion {
Ok(())
}
async fn tell_overlord_we_are_ready(&self) -> Result<(), Error> {
self.to_overlord.send(ToOverlordMessage::MinionIsReady)?;
Ok(())
}
async fn subscribe_augments(&mut self, job_id: u64, ids: Vec<IdHex>) -> Result<(), Error> {
let mut event_kinds = crate::feed::feed_related_event_kinds(false);
event_kinds.retain(|f| f.augments_feed_related());
@ -492,10 +484,7 @@ impl Minion {
let event_kinds = crate::feed::feed_related_event_kinds(true);
if !followed_pubkeys.is_empty() {
let pkp: Vec<PublicKeyHexPrefix> = followed_pubkeys
.iter()
.map(|pk| Into::<PublicKeyHex>::into(*pk).prefix(16)) // quarter-size
.collect();
let pkp: Vec<PublicKeyHex> = followed_pubkeys.iter().map(|pk| pk.into()).collect();
// feed related by people followed
filters.push(Filter {
@ -511,11 +500,11 @@ impl Minion {
// divine relays people write to (if using a client that does that).
// BUT ONLY for people where this kind of data hasn't been received
// in the last 8 hours (so we don't do it every client restart).
let keys_needing_relay_lists: Vec<PublicKeyHexPrefix> = GLOBALS
let keys_needing_relay_lists: Vec<PublicKeyHex> = GLOBALS
.people
.get_followed_pubkeys_needing_relay_lists(&followed_pubkeys)
.get_subscribed_pubkeys_needing_relay_lists(&followed_pubkeys)
.drain(..)
.map(|pk| Into::<PublicKeyHex>::into(pk).prefix(16)) // quarter-size
.map(|pk| pk.into())
.collect();
if !keys_needing_relay_lists.is_empty() {
@ -633,7 +622,7 @@ impl Minion {
let filters: Vec<Filter> = vec![
// Actual config stuff
Filter {
authors: vec![pkh.clone().into()],
authors: vec![pkh.clone()],
kinds: vec![
EventKind::Metadata,
//EventKind::RecommendRelay,
@ -646,21 +635,21 @@ impl Minion {
},
// GiftWraps to me, recent only
Filter {
authors: vec![pkh.clone().into()],
authors: vec![pkh.clone()],
kinds: vec![EventKind::GiftWrap],
since: Some(giftwrap_since),
..Default::default()
},
// Posts I wrote recently
Filter {
authors: vec![pkh.into()],
authors: vec![pkh],
kinds: crate::feed::feed_related_event_kinds(false), // not DMs
since: Some(since),
..Default::default()
},
];
self.subscribe(filters, "temp_config_feed", job_id).await?;
self.subscribe(filters, "config_feed", job_id).await?;
}
Ok(())
@ -673,10 +662,7 @@ impl Minion {
pubkeys: Vec<PublicKey>,
) -> Result<(), Error> {
if !pubkeys.is_empty() {
let pkp: Vec<PublicKeyHexPrefix> = pubkeys
.iter()
.map(|pk| Into::<PublicKeyHex>::into(*pk).prefix(16))
.collect(); // quarter-size prefix
let pkp: Vec<PublicKeyHex> = pubkeys.iter().map(|pk| pk.into()).collect();
let filters: Vec<Filter> = vec![Filter {
authors: pkp,
@ -700,7 +686,7 @@ impl Minion {
let event_kinds = crate::feed::feed_displayable_event_kinds(false);
let filters: Vec<Filter> = vec![Filter {
authors: vec![Into::<PublicKeyHex>::into(pubkey).prefix(16)],
authors: vec![pubkey.into()],
kinds: event_kinds,
// No since, just a limit on quantity of posts
limit: Some(25),
@ -733,16 +719,9 @@ impl Minion {
let mut filters: Vec<Filter> = Vec::new();
if !vec_ids.is_empty() {
let idhp: Vec<IdHexPrefix> = vec_ids
.iter()
.map(
|id| id.prefix(16), // quarter-size
)
.collect();
// Get ancestors we know of so far
filters.push(Filter {
ids: idhp,
ids: vec_ids.clone(),
..Default::default()
});
@ -783,13 +762,8 @@ impl Minion {
// note: giftwraps can't be subscribed by channel. they are subscribed more
// globally, and have to be limited to recent ones.
let mut authors: Vec<PublicKeyHexPrefix> = dmchannel
.keys()
.iter()
.map(Into::<PublicKeyHex>::into)
.map(|k| k.prefix(32))
.collect();
authors.push(pkh.prefix(32)); // add the user themselves
let mut authors: Vec<PublicKeyHex> = dmchannel.keys().iter().map(|k| k.into()).collect();
authors.push(pkh);
let filters: Vec<Filter> = vec![Filter {
authors,
@ -823,7 +797,7 @@ impl Minion {
// create the filter
let mut filter = Filter::new();
filter.ids = ids.drain(..).map(|idhex| idhex.into()).collect();
filter.ids = ids;
tracing::trace!("{}: Event Filter: {} events", &self.url, filter.ids.len());
@ -862,7 +836,7 @@ impl Minion {
// build the filter
let mut filter = Filter::new();
let pkh: PublicKeyHex = ea.author.into();
filter.authors = vec![pkh.prefix(32)]; // half-size
filter.authors = vec![pkh];
filter.kinds = vec![ea.kind];
filter.d = vec![ea.d];
@ -874,12 +848,7 @@ impl Minion {
job_id: u64,
mut pubkeys: Vec<PublicKey>,
) -> Result<(), Error> {
let pkhp: Vec<PublicKeyHexPrefix> = pubkeys
.drain(..)
.map(
|pk| Into::<PublicKeyHex>::into(pk).prefix(16), // quarter-size
)
.collect();
let pkhp: Vec<PublicKeyHex> = pubkeys.drain(..).map(|pk| pk.into()).collect();
tracing::trace!("Temporarily subscribing to metadata on {}", &self.url);

View File

@ -166,7 +166,7 @@ impl Overlord {
// Separately subscribe to RelayList discovery for everyone we follow
// We just do this once at startup. Relay lists don't change that frequently.
let followed = GLOBALS.people.get_followed_pubkeys();
let followed = GLOBALS.people.get_subscribed_pubkeys();
self.subscribe_discover(followed, None).await?;
// Separately subscribe to our outbox events on our write relays
@ -561,14 +561,14 @@ impl Overlord {
ToOverlordMessage::FetchEventAddr(ea) => {
self.fetch_event_addr(ea).await?;
}
ToOverlordMessage::FollowPubkey(pubkey) => {
self.follow_pubkey(pubkey).await?;
ToOverlordMessage::FollowPubkey(pubkey, public) => {
self.follow_pubkey(pubkey, public).await?;
}
ToOverlordMessage::FollowNip05(nip05) => {
Self::follow_nip05(nip05).await?;
ToOverlordMessage::FollowNip05(nip05, public) => {
Self::follow_nip05(nip05, public).await?;
}
ToOverlordMessage::FollowNprofile(nprofile) => {
self.follow_nprofile(nprofile).await?;
ToOverlordMessage::FollowNprofile(nprofile, public) => {
self.follow_nprofile(nprofile, public).await?;
}
ToOverlordMessage::GeneratePrivateKey(password) => {
Self::generate_private_key(password).await?;
@ -585,10 +585,6 @@ impl Overlord {
ToOverlordMessage::Like(id, pubkey) => {
self.like(id, pubkey).await?;
}
ToOverlordMessage::MinionIsReady => {
// internal
// currently ignored
}
ToOverlordMessage::MinionJobComplete(url, job_id) => {
self.finish_job(url, Some(job_id), None)?;
}
@ -642,8 +638,8 @@ impl Overlord {
ToOverlordMessage::ReengageMinion(url, persistent_jobs) => {
self.engage_minion(url, persistent_jobs).await?;
}
ToOverlordMessage::RefreshFollowedMetadata => {
self.refresh_followed_metadata().await?;
ToOverlordMessage::RefreshSubscribedMetadata => {
self.refresh_subscribed_metadata().await?;
}
ToOverlordMessage::Repost(id) => {
self.repost(id).await?;
@ -1002,17 +998,17 @@ impl Overlord {
}
/// Follow a person by `PublicKey`
pub async fn follow_pubkey(&mut self, pubkey: PublicKey) -> Result<(), Error> {
GLOBALS.people.follow(&pubkey, true)?;
pub async fn follow_pubkey(&mut self, pubkey: PublicKey, public: bool) -> Result<(), Error> {
GLOBALS.people.follow(&pubkey, true, public)?;
self.subscribe_discover(vec![pubkey], None).await?;
tracing::debug!("Followed {}", &pubkey.as_hex_string());
Ok(())
}
/// Follow a person by a nip-05 address
pub async fn follow_nip05(nip05: String) -> Result<(), Error> {
pub async fn follow_nip05(nip05: String, public: bool) -> Result<(), Error> {
std::mem::drop(tokio::spawn(async move {
if let Err(e) = crate::nip05::get_and_follow_nip05(nip05).await {
if let Err(e) = crate::nip05::get_and_follow_nip05(nip05, public).await {
tracing::error!("{}", e);
}
}));
@ -1020,8 +1016,8 @@ impl Overlord {
}
/// Follow a person by a `Profile` (nprofile1...)
pub async fn follow_nprofile(&mut self, nprofile: Profile) -> Result<(), Error> {
GLOBALS.people.follow(&nprofile.pubkey, true)?;
pub async fn follow_nprofile(&mut self, nprofile: Profile, public: bool) -> Result<(), Error> {
GLOBALS.people.follow(&nprofile.pubkey, true, public)?;
// Set their relays
for relay in nprofile.relays.iter() {
@ -1692,8 +1688,8 @@ impl Overlord {
/// Refresh metadata for everybody who is followed
/// This gets it whether we had it or not. Because it might have changed.
pub async fn refresh_followed_metadata(&mut self) -> Result<(), Error> {
let mut pubkeys = GLOBALS.people.get_followed_pubkeys();
pub async fn refresh_subscribed_metadata(&mut self) -> Result<(), Error> {
let mut pubkeys = GLOBALS.people.get_subscribed_pubkeys();
// add own pubkey as well
if let Some(pubkey) = GLOBALS.signer.public_key() {
@ -2315,8 +2311,8 @@ impl Overlord {
txn.commit()?;
// Follow all those pubkeys, and unfollow everbody else if merge=false
GLOBALS.people.follow_all(&pubkeys, merge)?;
// Follow all those pubkeys publicly, and unfollow everbody else if merge=false
GLOBALS.people.follow_all(&pubkeys, merge, true)?;
// Update last_contact_list_edit
let last_edit = if merge {
@ -2432,8 +2428,8 @@ impl Overlord {
}
}
// Mute all those pubkeys, and unmute everbody else if merge=false
GLOBALS.people.mute_all(&pubkeys, merge)?;
// Mute all those pubkeys publicly, and unmute everbody else if merge=false
GLOBALS.people.mute_all(&pubkeys, merge, true)?;
// Update last_must_list_edit
let last_edit = if merge {

View File

@ -155,10 +155,28 @@ impl People {
});
}
/// Get all the pubkeys that the user follows
/// Get all the pubkeys that the user subscribes to in any list
pub fn get_subscribed_pubkeys(&self) -> Vec<PublicKey> {
// We subscribe to all people in all lists.
// This is no longer synonomous with the ContactList list
match GLOBALS.storage.get_people_in_all_followed_lists() {
Ok(people) => people,
Err(e) => {
tracing::error!("{}", e);
vec![]
}
}
}
/// Get all the pubkeys in the Followed list
pub fn get_followed_pubkeys(&self) -> Vec<PublicKey> {
match GLOBALS.storage.get_people_in_list(PersonList::Followed) {
Ok(list) => list,
// We subscribe to all people in all lists.
// This is no longer synonomous with the ContactList list
match GLOBALS
.storage
.get_people_in_list(PersonList::Followed, None)
{
Ok(people) => people,
Err(e) => {
tracing::error!("{}", e);
vec![]
@ -168,8 +186,8 @@ impl People {
/// Get all the pubkeys that the user mutes
pub fn get_muted_pubkeys(&self) -> Vec<PublicKey> {
match GLOBALS.storage.get_people_in_list(PersonList::Muted) {
Ok(list) => list,
match GLOBALS.storage.get_people_in_list(PersonList::Muted, None) {
Ok(people) => people,
Err(e) => {
tracing::error!("{}", e);
vec![]
@ -179,11 +197,25 @@ impl People {
/// Is the given pubkey followed?
pub fn is_followed(&self, pubkey: &PublicKey) -> bool {
self.get_followed_pubkeys().contains(pubkey)
match GLOBALS
.storage
.is_person_in_list(pubkey, PersonList::Followed)
{
Ok(answer) => answer,
_ => false,
}
}
/// Is the given pubkey muted?
pub fn is_muted(&self, pubkey: &PublicKey) -> bool {
match GLOBALS.storage.is_person_in_list(pubkey, PersonList::Muted) {
Ok(answer) => answer,
_ => false,
}
}
/// Get all the pubkeys that need relay lists (from the given set)
pub fn get_followed_pubkeys_needing_relay_lists(
pub fn get_subscribed_pubkeys_needing_relay_lists(
&self,
among_these: &[PublicKey],
) -> Vec<PublicKey> {
@ -670,13 +702,16 @@ impl People {
}
/// Follow (or unfollow) the public key
pub fn follow(&self, pubkey: &PublicKey, follow: bool) -> Result<(), Error> {
pub fn follow(&self, pubkey: &PublicKey, follow: bool, public: bool) -> Result<(), Error> {
let mut txn = GLOBALS.storage.get_write_txn()?;
if follow {
GLOBALS
.storage
.add_person_to_list(pubkey, PersonList::Followed, Some(&mut txn))?;
GLOBALS.storage.add_person_to_list(
pubkey,
PersonList::Followed,
public,
Some(&mut txn),
)?;
} else {
GLOBALS.storage.remove_person_from_list(
pubkey,
@ -697,7 +732,12 @@ impl People {
/// Follow all these public keys.
/// This does not publish any events.
pub(crate) fn follow_all(&self, pubkeys: &[PublicKey], merge: bool) -> Result<(), Error> {
pub(crate) fn follow_all(
&self,
pubkeys: &[PublicKey],
public: bool,
merge: bool,
) -> Result<(), Error> {
let mut txn = GLOBALS.storage.get_write_txn()?;
if !merge {
@ -707,9 +747,12 @@ impl People {
}
for pubkey in pubkeys {
GLOBALS
.storage
.add_person_to_list(pubkey, PersonList::Followed, Some(&mut txn))?;
GLOBALS.storage.add_person_to_list(
pubkey,
PersonList::Followed,
public,
Some(&mut txn),
)?;
GLOBALS.ui_people_to_invalidate.write().push(*pubkey);
}
@ -766,7 +809,7 @@ impl People {
}
/// Mute (or unmute) a public key
pub fn mute(&self, pubkey: &PublicKey, mute: bool) -> Result<(), Error> {
pub fn mute(&self, pubkey: &PublicKey, mute: bool, public: bool) -> Result<(), Error> {
let mut txn = GLOBALS.storage.get_write_txn()?;
if mute {
@ -776,9 +819,12 @@ impl People {
}
}
GLOBALS
.storage
.add_person_to_list(pubkey, PersonList::Muted, Some(&mut txn))?;
GLOBALS.storage.add_person_to_list(
pubkey,
PersonList::Muted,
public,
Some(&mut txn),
)?;
} else {
GLOBALS
.storage
@ -796,7 +842,12 @@ impl People {
Ok(())
}
pub(crate) fn mute_all(&self, pubkeys: &[PublicKey], merge: bool) -> Result<(), Error> {
pub(crate) fn mute_all(
&self,
pubkeys: &[PublicKey],
merge: bool,
public: bool,
) -> Result<(), Error> {
let mut txn = GLOBALS.storage.get_write_txn()?;
if !merge {
@ -806,9 +857,12 @@ impl People {
}
for pubkey in pubkeys {
GLOBALS
.storage
.add_person_to_list(pubkey, PersonList::Muted, Some(&mut txn))?;
GLOBALS.storage.add_person_to_list(
pubkey,
PersonList::Muted,
public,
Some(&mut txn),
)?;
GLOBALS.ui_people_to_invalidate.write().push(*pubkey);
}

View File

@ -75,7 +75,8 @@ pub async fn process_new_event(
return Ok(());
}
EventFilterAction::MuteAuthor => {
GLOBALS.people.mute(&event.pubkey, true)?;
let public = true;
GLOBALS.people.mute(&event.pubkey, true, public)?;
return Ok(());
}
}

View File

@ -46,10 +46,10 @@ impl RelayPickerHooks for Hooks {
}
/// Returns the public keys of all the people followed
// this API name has become difficult..
fn get_followed_pubkeys(&self) -> Vec<PublicKey> {
// CHANGE TO GET THIS FROM THE CURRENT FEED
GLOBALS.people.get_followed_pubkeys()
// ..We actually want all the people subscribed, which is a bigger list
GLOBALS.people.get_subscribed_pubkeys()
}
/// Adjusts the score for a given relay, perhaps based on relay-specific metrics

View File

@ -57,6 +57,7 @@ pub struct Settings {
pub replies_chunk: u64,
pub person_feed_chunk: u64,
pub overlap: u64,
pub custom_person_list_names: [String; 10],
// Event Selection
pub reposts: bool,
@ -141,6 +142,7 @@ impl Default for Settings {
replies_chunk: default_setting!(replies_chunk),
person_feed_chunk: default_setting!(person_feed_chunk),
overlap: default_setting!(overlap),
custom_person_list_names: default_setting!(custom_person_list_names),
reposts: default_setting!(reposts),
show_long_form: default_setting!(show_long_form),
show_mentions: default_setting!(show_mentions),
@ -221,6 +223,7 @@ impl Settings {
replies_chunk: load_setting!(replies_chunk),
person_feed_chunk: load_setting!(person_feed_chunk),
overlap: load_setting!(overlap),
custom_person_list_names: load_setting!(custom_person_list_names),
reposts: load_setting!(reposts),
show_long_form: load_setting!(show_long_form),
show_mentions: load_setting!(show_mentions),
@ -297,6 +300,7 @@ impl Settings {
save_setting!(replies_chunk, self, txn);
save_setting!(person_feed_chunk, self, txn);
save_setting!(overlap, self, txn);
save_setting!(custom_person_list_names, self, txn);
save_setting!(reposts, self, txn);
save_setting!(show_long_form, self, txn);
save_setting!(show_mentions, self, txn);

View File

@ -1,14 +1,16 @@
use super::types::{Person2, PersonRelay1, Settings1, Settings2, Theme1, ThemeVariant1};
use super::types::{
Person2, PersonList1, PersonRelay1, Settings1, Settings2, Theme1, ThemeVariant1,
};
use super::Storage;
use crate::error::{Error, ErrorKind};
use crate::people::PersonList;
use heed::types::UnalignedSlice;
use heed::{DatabaseFlags, RwTxn};
use nostr_types::{Event, Id, RelayUrl, Signature};
use nostr_types::{Event, Id, PublicKey, RelayUrl, Signature};
use speedy::{Readable, Writable};
use std::collections::HashMap;
impl Storage {
const MAX_MIGRATION_LEVEL: u32 = 12;
const MAX_MIGRATION_LEVEL: u32 = 13;
pub(super) fn migrate(&self, mut level: u32) -> Result<(), Error> {
if level > Self::MAX_MIGRATION_LEVEL {
@ -64,6 +66,10 @@ impl Storage {
let _ = self.db_events1()?;
let _ = self.db_event_tag_index1()?;
}
12 => {
let _ = self.db_person_lists1()?;
let _ = self.db_person_lists2()?;
}
_ => {}
};
Ok(())
@ -123,6 +129,10 @@ impl Storage {
tracing::info!("{prefix}: removing now unused event_references_person index...");
self.remove_event_references_person(txn)?;
}
12 => {
tracing::info!("{prefix}: migrating lists...");
self.migrate_lists(txn)?;
}
_ => panic!("Unreachable migration level"),
};
@ -404,13 +414,13 @@ impl Storage {
let mut count: usize = 0;
let mut followed_count: usize = 0;
for person1 in self.filter_people1(|_| true)?.iter() {
let mut lists: Vec<PersonList> = Vec::new();
let mut lists: Vec<PersonList1> = Vec::new();
if person1.followed {
lists.push(PersonList::Followed);
lists.push(PersonList1::Followed);
followed_count += 1;
}
if person1.muted {
lists.push(PersonList::Muted);
lists.push(PersonList1::Muted);
}
if !lists.is_empty() {
self.write_person_lists1(&person1.pubkey, lists, Some(txn))?;
@ -557,4 +567,28 @@ impl Storage {
Ok(())
}
pub fn migrate_lists<'a>(&'a self, txn: &mut RwTxn<'a>) -> Result<(), Error> {
let loop_txn = self.env.read_txn()?;
for result in self.db_person_lists1()?.iter(&loop_txn)? {
let (key, val) = result?;
let pubkey = PublicKey::from_bytes(key, true)?;
let mut person_lists = val
.iter()
.map(|u| (*u).into())
.collect::<Vec<PersonList1>>();
let new_person_lists: HashMap<PersonList1, bool> =
person_lists.drain(..).map(|l| (l, true)).collect();
self.write_person_lists2(&pubkey, new_person_lists, Some(txn))?;
}
// remove db_person_lists1
{
self.db_person_lists1()?.clear(txn)?;
// heed doesn't expose mdb_drop(1) yet, so we can't actually remove this database.
}
Ok(())
}
}

View File

@ -27,6 +27,7 @@ mod hashtags1;
mod people1;
mod people2;
mod person_lists1;
mod person_lists2;
mod person_relays1;
mod relationships1;
mod relays1;
@ -95,7 +96,7 @@ macro_rules! def_setting {
match self.general.get(&txn, $string) {
Err(_) => $default,
Ok(None) => $default,
Ok(Some(bytes)) => match $type::read_from_buffer(bytes) {
Ok(Some(bytes)) => match <$type>::read_from_buffer(bytes) {
Ok(val) => val,
Err(_) => $default,
}
@ -134,7 +135,7 @@ impl Storage {
pub(crate) fn new() -> Result<Storage, Error> {
let mut builder = EnvOpenOptions::new();
unsafe {
builder.flags(EnvFlags::NO_SYNC | EnvFlags::NO_TLS);
builder.flags(EnvFlags::NO_TLS);
}
// builder.max_readers(126); // this is the default
builder.max_dbs(32);
@ -284,7 +285,7 @@ impl Storage {
#[inline]
pub(crate) fn db_person_lists(&self) -> Result<RawDatabase, Error> {
self.db_person_lists1()
self.db_person_lists2()
}
// Database length functions ---------------------------------
@ -722,6 +723,23 @@ impl Storage {
60 * 60 * 24 * 30
);
def_setting!(overlap, b"overlap", u64, 300);
def_setting!(
custom_person_list_names,
b"custom_person_list_names",
[String; 10],
[
"Priority".to_owned(),
"Custom List 2".to_owned(),
"Custom List 3".to_owned(),
"Custom List 4".to_owned(),
"Custom List 5".to_owned(),
"Custom List 6".to_owned(),
"Custom List 7".to_owned(),
"Custom List 8".to_owned(),
"Custom List 9".to_owned(),
"Custom List 10".to_owned(),
]
);
def_setting!(reposts, b"reposts", bool, true);
def_setting!(show_long_form, b"show_long_form", bool, false);
def_setting!(show_mentions, b"show_mentions", bool, true);
@ -1777,10 +1795,19 @@ impl Storage {
// Whether or not the Gossip user already reacted to this event
let mut self_already_reacted = false;
// Get the event (once self-reactions get deleted we can remove this)
let maybe_target_event = self.read_event(id)?;
// Collect up to one reaction per pubkey
let mut phase1: HashMap<PublicKey, char> = HashMap::new();
for (_, rel) in self.find_relationships(id)? {
if let Relationship::Reaction(pubkey, reaction) = rel {
if let Some(target_event) = &maybe_target_event {
if target_event.pubkey == pubkey {
// Do not let people like their own post
continue;
}
}
let symbol: char = if let Some(ch) = reaction.chars().next() {
ch
} else {
@ -1820,9 +1847,19 @@ impl Storage {
/// Get whether an event was deleted, and if so the optional reason
pub fn get_deletion(&self, id: Id) -> Result<Option<String>, Error> {
for (_, rel) in self.find_relationships(id)? {
for (target_id, rel) in self.find_relationships(id)? {
if let Relationship::Deletion(deletion) = rel {
return Ok(Some(deletion));
if let Some(delete_event) = self.read_event(id)? {
if let Some(target_event) = self.read_event(target_id)? {
// Only if the authors match
if target_event.pubkey == delete_event.pubkey {
return Ok(Some(deletion));
}
} else {
// presume the authors will match for now
return Ok(Some(deletion));
}
}
}
}
Ok(None)
@ -1844,30 +1881,58 @@ impl Storage {
}
// reacts to
if let Some((id, reaction, _maybe_url)) = event.reacts_to() {
self.write_relationship(
id,
event.id,
Relationship::Reaction(event.pubkey, reaction),
Some(txn),
)?;
invalidate.push(id);
if let Some((reacted_to_id, reaction, _maybe_url)) = event.reacts_to() {
if let Some(reacted_to_event) = self.read_event(reacted_to_id)? {
// Only if they are different people (no liking your own posts)
if reacted_to_event.pubkey != event.pubkey {
self.write_relationship(
reacted_to_id, // event reacted to
event.id, // the reaction event id
Relationship::Reaction(event.pubkey, reaction),
Some(txn),
)?;
}
invalidate.push(reacted_to_id);
} else {
// Store the reaction to the event we dont have yet.
// We filter bad ones when reading them back too, so even if this
// turns out to be a reaction by the author, they can't like
// their own post
self.write_relationship(
reacted_to_id, // event reacted to
event.id, // the reaction event id
Relationship::Reaction(event.pubkey, reaction),
Some(txn),
)?;
invalidate.push(reacted_to_id);
}
}
// deletes
if let Some((ids, reason)) = event.deletes() {
invalidate.extend(&ids);
for id in ids {
if let Some((deleted_event_ids, reason)) = event.deletes() {
invalidate.extend(&deleted_event_ids);
for deleted_event_id in deleted_event_ids {
// since it is a delete, we don't actually desire the event.
self.write_relationship(
id,
event.id,
Relationship::Deletion(reason.clone()),
Some(txn),
)?;
if let Some(deleted_event) = self.read_event(deleted_event_id)? {
// Only if it is the same author
if deleted_event.pubkey == event.pubkey {
self.write_relationship(
deleted_event_id,
event.id,
Relationship::Deletion(reason.clone()),
Some(txn),
)?;
}
} else {
// We don't have the deleted event. Presume it is okay. We check again
// when we read these back
self.write_relationship(
deleted_event_id,
event.id,
Relationship::Deletion(reason.clone()),
Some(txn),
)?;
}
}
}
@ -2090,7 +2155,12 @@ impl Storage {
let mut map: HashMap<DmChannel, DmChannelData> = HashMap::new();
for event in &events {
let unread = 1 - self.is_event_viewed(event.id)? as usize;
let unread: usize = if event.pubkey == my_pubkey {
// Do not count self-authored events as unread, irrespective of whether they are viewed
0
} else {
1 - self.is_event_viewed(event.id)? as usize
};
if event.kind == EventKind::EncryptedDirectMessage {
let time = event.created_at;
let dmchannel = match DmChannel::from_event(event, Some(my_pubkey)) {
@ -2260,23 +2330,34 @@ impl Storage {
}
/// Read person lists
pub fn read_person_lists(&self, pubkey: &PublicKey) -> Result<Vec<PersonList>, Error> {
self.read_person_lists1(pubkey)
pub fn read_person_lists(
&self,
pubkey: &PublicKey,
) -> Result<HashMap<PersonList, bool>, Error> {
self.read_person_lists2(pubkey)
}
/// Write person lists
pub fn write_person_lists<'a>(
&'a self,
pubkey: &PublicKey,
lists: Vec<PersonList>,
lists: HashMap<PersonList, bool>,
rw_txn: Option<&mut RwTxn<'a>>,
) -> Result<(), Error> {
self.write_person_lists1(pubkey, lists, rw_txn)
self.write_person_lists2(pubkey, lists, rw_txn)
}
/// Get people in a person list
pub fn get_people_in_list(&self, list: PersonList) -> Result<Vec<PublicKey>, Error> {
self.get_people_in_list1(list)
pub fn get_people_in_list(
&self,
list: PersonList,
public: Option<bool>,
) -> Result<Vec<PublicKey>, Error> {
self.get_people_in_list2(list, public)
}
pub fn get_people_in_all_followed_lists(&self) -> Result<Vec<PublicKey>, Error> {
self.get_people_in_all_followed_lists2()
}
/// Empty a person list
@ -2286,13 +2367,19 @@ impl Storage {
list: PersonList,
rw_txn: Option<&mut RwTxn<'a>>,
) -> Result<(), Error> {
self.clear_person_list1(list, rw_txn)
self.clear_person_list2(list, rw_txn)
}
/// Is a person in a list?
pub fn is_person_in_list(&self, pubkey: &PublicKey, list: PersonList) -> Result<bool, Error> {
let lists = self.read_person_lists(pubkey)?;
Ok(lists.contains(&list))
let map = self.read_person_lists(pubkey)?;
Ok(map.contains_key(&list))
}
/// Is the person in any list we subscribe to?
pub fn is_person_subscribed_to(&self, pubkey: &PublicKey) -> Result<bool, Error> {
let map = self.read_person_lists(pubkey)?;
Ok(map.iter().any(|l| l.0.subscribe()))
}
/// Add a person to a list
@ -2300,13 +2387,12 @@ impl Storage {
&'a self,
pubkey: &PublicKey,
list: PersonList,
public: bool,
rw_txn: Option<&mut RwTxn<'a>>,
) -> Result<(), Error> {
let mut lists = self.read_person_lists(pubkey)?;
if !lists.iter().any(|s| *s == list) {
lists.push(list.to_owned());
}
self.write_person_lists(pubkey, lists, rw_txn)
let mut map = self.read_person_lists(pubkey)?;
map.insert(list, public);
self.write_person_lists(pubkey, map, rw_txn)
}
/// Remove a person from a list
@ -2316,8 +2402,8 @@ impl Storage {
list: PersonList,
rw_txn: Option<&mut RwTxn<'a>>,
) -> Result<(), Error> {
let mut lists = self.read_person_lists(pubkey)?;
let lists: Vec<PersonList> = lists.drain(..).filter(|s| *s != list).collect();
self.write_person_lists(pubkey, lists, rw_txn)
let mut map = self.read_person_lists(pubkey)?;
map.remove(&list);
self.write_person_lists(pubkey, map, rw_txn)
}
}

View File

@ -43,15 +43,6 @@ impl Storage {
}
}
pub(crate) fn read_person_lists1(&self, pubkey: &PublicKey) -> Result<Vec<PersonList>, Error> {
let key: Vec<u8> = pubkey.to_bytes();
let txn = self.env.read_txn()?;
Ok(match self.db_person_lists1()?.get(&txn, &key)? {
Some(bytes) => bytes.iter().map(|u| (*u).into()).collect(),
None => vec![],
})
}
pub(crate) fn write_person_lists1<'a>(
&'a self,
pubkey: &PublicKey,
@ -77,62 +68,4 @@ impl Storage {
Ok(())
}
pub(crate) fn get_people_in_list1(&self, list: PersonList) -> Result<Vec<PublicKey>, Error> {
let txn = self.env.read_txn()?;
let mut pubkeys: Vec<PublicKey> = Vec::new();
for result in self.db_person_lists1()?.iter(&txn)? {
let (key, val) = result?;
let pubkey = PublicKey::from_bytes(key, true)?;
let person_lists = val.iter().map(|u| (*u).into()).collect::<Vec<PersonList>>();
if person_lists.iter().any(|s| *s == list) {
pubkeys.push(pubkey);
}
}
Ok(pubkeys)
}
pub(crate) fn clear_person_list1<'a>(
&'a self,
list: PersonList,
rw_txn: Option<&mut RwTxn<'a>>,
) -> Result<(), Error> {
let f = |txn: &mut RwTxn<'a>| -> Result<(), Error> {
let mut fixed: Vec<(PublicKey, Vec<u8>)> = Vec::new();
// Collect records that require changing
for result in self.db_person_lists1()?.iter(txn)? {
let (key, val) = result?;
let pubkey = PublicKey::from_bytes(key, true)?;
let mut person_lists = val.iter().map(|u| (*u).into()).collect::<Vec<PersonList>>();
if person_lists.contains(&list) {
person_lists = person_lists.drain(..).filter(|l| *l != list).collect();
let bytes = person_lists
.iter()
.map(|l| (*l).into())
.collect::<Vec<u8>>();
fixed.push((pubkey, bytes));
}
}
// Change them
for (pubkey, bytes) in fixed.drain(..) {
let key: Vec<u8> = pubkey.to_bytes();
self.db_person_lists1()?.put(txn, &key, &bytes)?;
}
Ok(())
};
match rw_txn {
Some(txn) => f(txn)?,
None => {
let mut txn = self.env.write_txn()?;
f(&mut txn)?;
txn.commit()?;
}
};
Ok(())
}
}

View File

@ -0,0 +1,172 @@
use super::types::PersonList1;
use crate::error::Error;
use crate::storage::{RawDatabase, Storage};
use heed::types::UnalignedSlice;
use heed::RwTxn;
use nostr_types::PublicKey;
use speedy::{Readable, Writable};
use std::collections::HashMap;
use std::sync::Mutex;
// Pubkey -> Vec<(PersonList, bool)> // bool is if private or not
// key: pubkey.as_bytes()
static PERSON_LISTS2_DB_CREATE_LOCK: Mutex<()> = Mutex::new(());
static mut PERSON_LISTS2_DB: Option<RawDatabase> = None;
impl Storage {
pub(super) fn db_person_lists2(&self) -> Result<RawDatabase, Error> {
unsafe {
if let Some(db) = PERSON_LISTS2_DB {
Ok(db)
} else {
// Lock. This drops when anything returns.
let _lock = PERSON_LISTS2_DB_CREATE_LOCK.lock();
// In case of a race, check again
if let Some(db) = PERSON_LISTS2_DB {
return Ok(db);
}
// Create it. We know that nobody else is doing this and that
// it cannot happen twice.
let mut txn = self.env.write_txn()?;
let db = self
.env
.database_options()
.types::<UnalignedSlice<u8>, UnalignedSlice<u8>>()
// no .flags needed
.name("person_lists_2")
.create(&mut txn)?;
txn.commit()?;
PERSON_LISTS2_DB = Some(db);
Ok(db)
}
}
}
pub(crate) fn read_person_lists2(
&self,
pubkey: &PublicKey,
) -> Result<HashMap<PersonList1, bool>, Error> {
let key: Vec<u8> = pubkey.to_bytes();
let txn = self.env.read_txn()?;
Ok(match self.db_person_lists2()?.get(&txn, &key)? {
None => HashMap::new(),
Some(bytes) => HashMap::<PersonList1, bool>::read_from_buffer(bytes)?,
})
}
pub(crate) fn write_person_lists2<'a>(
&'a self,
pubkey: &PublicKey,
map: HashMap<PersonList1, bool>,
rw_txn: Option<&mut RwTxn<'a>>,
) -> Result<(), Error> {
let key: Vec<u8> = pubkey.to_bytes();
let bytes: Vec<u8> = map.write_to_vec()?;
let f = |txn: &mut RwTxn<'a>| -> Result<(), Error> {
self.db_person_lists2()?.put(txn, &key, &bytes)?;
Ok(())
};
match rw_txn {
Some(txn) => f(txn)?,
None => {
let mut txn = self.env.write_txn()?;
f(&mut txn)?;
txn.commit()?;
}
};
Ok(())
}
pub(crate) fn get_people_in_all_followed_lists2(&self) -> Result<Vec<PublicKey>, Error> {
let txn = self.env.read_txn()?;
let mut pubkeys: Vec<PublicKey> = Vec::new();
for result in self.db_person_lists2()?.iter(&txn)? {
let (key, val) = result?;
let pubkey = PublicKey::from_bytes(key, true)?;
let map = HashMap::<PersonList1, bool>::read_from_buffer(val)?;
if map.keys().any(|list| list.subscribe()) {
pubkeys.push(pubkey);
}
}
Ok(pubkeys)
}
pub(crate) fn get_people_in_list2(
&self,
list: PersonList1,
public: Option<bool>,
) -> Result<Vec<PublicKey>, Error> {
let txn = self.env.read_txn()?;
let mut pubkeys: Vec<PublicKey> = Vec::new();
for result in self.db_person_lists2()?.iter(&txn)? {
let (key, val) = result?;
let pubkey = PublicKey::from_bytes(key, true)?;
let map = HashMap::<PersonList1, bool>::read_from_buffer(val)?;
if let Some(p) = map.get(&list) {
match public {
Some(p2) => {
if p2 == *p {
pubkeys.push(pubkey);
}
}
None => {
pubkeys.push(pubkey);
}
}
}
}
Ok(pubkeys)
}
pub(crate) fn clear_person_list2<'a>(
&'a self,
list: PersonList1,
rw_txn: Option<&mut RwTxn<'a>>,
) -> Result<(), Error> {
let f = |txn: &mut RwTxn<'a>| -> Result<(), Error> {
let mut fixed: Vec<(PublicKey, HashMap<PersonList1, bool>)> = Vec::new();
// Collect records that require changing
// (lmdb doesn't like changing them in place while iterating)
for result in self.db_person_lists2()?.iter(txn)? {
let (key, val) = result?;
let pubkey = PublicKey::from_bytes(key, true)?;
let mut map = HashMap::<PersonList1, bool>::read_from_buffer(val)?;
if map.contains_key(&list) {
map.remove(&list);
fixed.push((pubkey, map));
}
}
// Change them
for (pubkey, map) in fixed.drain(..) {
let key: Vec<u8> = pubkey.to_bytes();
if map.is_empty() {
self.db_person_lists2()?.delete(txn, &key)?;
} else {
let bytes: Vec<u8> = map.write_to_vec()?;
self.db_person_lists2()?.put(txn, &key, &bytes)?;
}
}
Ok(())
};
match rw_txn {
Some(txn) => f(txn)?,
None => {
let mut txn = self.env.write_txn()?;
f(&mut txn)?;
txn.commit()?;
}
};
Ok(())
}
}

View File

@ -1,10 +1,14 @@
use crate::globals::GLOBALS;
use speedy::{Readable, Writable};
/// Lists people can be added to
#[derive(Debug, Clone, Copy, Hash, PartialEq, Eq)]
#[derive(Debug, Clone, Copy, Hash, PartialEq, Eq, Readable, Writable)]
#[repr(u8)]
pub enum PersonList1 {
Muted = 0,
Followed = 1,
Priority = 2,
// custom starts at 10 to leave space
Custom(u8),
}
@ -13,7 +17,6 @@ impl From<u8> for PersonList1 {
match u {
0 => PersonList1::Muted,
1 => PersonList1::Followed,
2 => PersonList1::Priority,
u => PersonList1::Custom(u),
}
}
@ -24,8 +27,42 @@ impl From<PersonList1> for u8 {
match e {
PersonList1::Muted => 0,
PersonList1::Followed => 1,
PersonList1::Priority => 2,
PersonList1::Custom(u) => u,
}
}
}
impl PersonList1 {
/// Custom lists (from 0-9, humans number them from 1-10)
pub fn custom(index: u8) -> Option<PersonList1> {
if index > 9 {
None
} else {
Some(PersonList1::Custom(index + 10))
}
}
pub fn name(&self) -> String {
match *self {
PersonList1::Muted => "Muted".to_string(),
PersonList1::Followed => "Followed".to_string(),
PersonList1::Custom(u) => {
if (10..=19).contains(&u) {
GLOBALS.storage.read_setting_custom_person_list_names()[u as usize - 10].clone()
} else if u > 19 {
format!("Custom List {}", u - 9) // humans count from 1
} else {
format!("Undefined list in slot={}", u)
}
}
}
}
pub fn subscribe(&self) -> bool {
match *self {
PersonList1::Muted => false,
PersonList1::Followed => true,
PersonList1::Custom(_) => true,
}
}
}