mirror of
https://github.com/mikedilger/gossip.git
synced 2024-09-29 08:21:47 +00:00
Merge remote-tracking branch 'dilger/unstable' into feature/warn-no-audio-device
This commit is contained in:
commit
21101d8f41
@ -16,6 +16,7 @@ video-ffmpeg = [ "egui-video", "sdl2" ]
|
||||
native-tls = [ "gossip-lib/native-tls" ]
|
||||
rustls-tls = [ "gossip-lib/rustls-tls" ]
|
||||
rustls-tls-native = [ "gossip-lib/rustls-tls-native" ]
|
||||
appimage = [ "gossip-lib/appimage" ]
|
||||
|
||||
[dependencies]
|
||||
bech32 = "0.9"
|
||||
|
@ -29,7 +29,7 @@ impl Command {
|
||||
}
|
||||
}
|
||||
|
||||
const COMMANDS: [Command; 30] = [
|
||||
const COMMANDS: [Command; 31] = [
|
||||
Command {
|
||||
cmd: "oneshot",
|
||||
usage_params: "{depends}",
|
||||
@ -45,6 +45,11 @@ const COMMANDS: [Command; 30] = [
|
||||
usage_params: "<listname>",
|
||||
desc: "add a new person list with the given name",
|
||||
},
|
||||
Command {
|
||||
cmd: "backdate_eose",
|
||||
usage_params: "",
|
||||
desc: "backdate last_general_eose_at by 24 hours for every relay",
|
||||
},
|
||||
Command {
|
||||
cmd: "bech32_decode",
|
||||
usage_params: "<bech32string>",
|
||||
@ -202,6 +207,7 @@ pub fn handle_command(mut args: env::Args, runtime: &Runtime) -> Result<bool, Er
|
||||
"oneshot" => oneshot(command, args)?,
|
||||
"add_person_relay" => add_person_relay(command, args)?,
|
||||
"add_person_list" => add_person_list(command, args)?,
|
||||
"backdate_eose" => backdate_eose()?,
|
||||
"bech32_decode" => bech32_decode(command, args)?,
|
||||
"bech32_encode_event_addr" => bech32_encode_event_addr(command, args)?,
|
||||
"decrypt" => decrypt(command, args)?,
|
||||
@ -307,6 +313,24 @@ pub fn add_person_list(cmd: Command, mut args: env::Args) -> Result<(), Error> {
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub fn backdate_eose() -> Result<(), Error> {
|
||||
let now = Unixtime::now().unwrap();
|
||||
let ago = (now.0 - 60 * 60 * 24) as u64;
|
||||
|
||||
GLOBALS.storage.modify_all_relays(
|
||||
|relay| {
|
||||
if let Some(eose) = relay.last_general_eose_at {
|
||||
if eose > ago {
|
||||
relay.last_general_eose_at = Some(ago);
|
||||
}
|
||||
}
|
||||
},
|
||||
None,
|
||||
)?;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub fn bech32_decode(cmd: Command, mut args: env::Args) -> Result<(), Error> {
|
||||
let mut param = match args.next() {
|
||||
Some(s) => s,
|
||||
|
@ -256,7 +256,10 @@ fn render_a_feed(
|
||||
is_thread: threaded,
|
||||
};
|
||||
|
||||
let feed_newest_at_bottom = GLOBALS.storage.read_setting_feed_newest_at_bottom();
|
||||
|
||||
app.vert_scroll_area()
|
||||
.stick_to_bottom(feed_newest_at_bottom)
|
||||
.id_source(scroll_area_id)
|
||||
.show(ui, |ui| {
|
||||
egui::Frame::none()
|
||||
@ -264,66 +267,82 @@ fn render_a_feed(
|
||||
.fill(app.theme.feed_scroll_fill(&feed_properties))
|
||||
.stroke(app.theme.feed_scroll_stroke(&feed_properties))
|
||||
.show(ui, |ui| {
|
||||
let iter = feed.iter();
|
||||
let first = feed.first();
|
||||
let last = feed.last();
|
||||
for id in iter {
|
||||
render_note_maybe_fake(
|
||||
app,
|
||||
ctx,
|
||||
ui,
|
||||
FeedNoteParams {
|
||||
id: *id,
|
||||
indent: 0,
|
||||
as_reply_to: false,
|
||||
threaded,
|
||||
is_first: Some(id) == first,
|
||||
is_last: Some(id) == last,
|
||||
},
|
||||
);
|
||||
}
|
||||
|
||||
let recomputing = GLOBALS
|
||||
.feed
|
||||
.recompute_lock
|
||||
.load(std::sync::atomic::Ordering::Relaxed);
|
||||
|
||||
if !recomputing && offer_load_more {
|
||||
if feed_newest_at_bottom {
|
||||
ui.add_space(50.0);
|
||||
if offer_load_more {
|
||||
render_load_more(app, ui)
|
||||
}
|
||||
ui.add_space(50.0);
|
||||
ui.with_layout(
|
||||
egui::Layout::top_down(egui::Align::Center)
|
||||
.with_cross_align(egui::Align::Center),
|
||||
|ui| {
|
||||
app.theme.accent_button_1_style(ui.style_mut());
|
||||
ui.spacing_mut().button_padding.x *= 3.0;
|
||||
ui.spacing_mut().button_padding.y *= 2.0;
|
||||
let response = ui.add(egui::Button::new("Load More"));
|
||||
if response.clicked() {
|
||||
let _ = GLOBALS
|
||||
.to_overlord
|
||||
.send(ToOverlordMessage::LoadMoreCurrentFeed);
|
||||
}
|
||||
|
||||
// draw some nice lines left and right of the button
|
||||
let stroke = egui::Stroke::new(1.5, ui.visuals().extreme_bg_color);
|
||||
let width =
|
||||
(ui.available_width() - response.rect.width()) / 2.0 - 20.0;
|
||||
let left_start =
|
||||
response.rect.left_center() - egui::vec2(10.0, 0.0);
|
||||
let left_end = left_start - egui::vec2(width, 0.0);
|
||||
ui.painter().line_segment([left_start, left_end], stroke);
|
||||
let right_start =
|
||||
response.rect.right_center() + egui::vec2(10.0, 0.0);
|
||||
let right_end = right_start + egui::vec2(width, 0.0);
|
||||
ui.painter().line_segment([right_start, right_end], stroke);
|
||||
},
|
||||
);
|
||||
for id in feed.iter().rev() {
|
||||
render_note_maybe_fake(
|
||||
app,
|
||||
ctx,
|
||||
ui,
|
||||
FeedNoteParams {
|
||||
id: *id,
|
||||
indent: 0,
|
||||
as_reply_to: false,
|
||||
threaded,
|
||||
is_first: Some(id) == feed.last(),
|
||||
is_last: Some(id) == feed.first(),
|
||||
},
|
||||
);
|
||||
}
|
||||
} else {
|
||||
for id in feed.iter() {
|
||||
render_note_maybe_fake(
|
||||
app,
|
||||
ctx,
|
||||
ui,
|
||||
FeedNoteParams {
|
||||
id: *id,
|
||||
indent: 0,
|
||||
as_reply_to: false,
|
||||
threaded,
|
||||
is_first: Some(id) == feed.first(),
|
||||
is_last: Some(id) == feed.last(),
|
||||
},
|
||||
);
|
||||
}
|
||||
|
||||
ui.add_space(50.0);
|
||||
if offer_load_more {
|
||||
render_load_more(app, ui)
|
||||
}
|
||||
ui.add_space(50.0);
|
||||
}
|
||||
ui.add_space(100.0);
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
fn render_load_more(app: &mut GossipUi, ui: &mut Ui) {
|
||||
ui.with_layout(
|
||||
egui::Layout::top_down(egui::Align::Center).with_cross_align(egui::Align::Center),
|
||||
|ui| {
|
||||
app.theme.accent_button_1_style(ui.style_mut());
|
||||
ui.spacing_mut().button_padding.x *= 3.0;
|
||||
ui.spacing_mut().button_padding.y *= 2.0;
|
||||
let response = ui.add(egui::Button::new("Load More"));
|
||||
if response.clicked() {
|
||||
let _ = GLOBALS
|
||||
.to_overlord
|
||||
.send(ToOverlordMessage::LoadMoreCurrentFeed);
|
||||
}
|
||||
|
||||
// draw some nice lines left and right of the button
|
||||
let stroke = egui::Stroke::new(1.5, ui.visuals().extreme_bg_color);
|
||||
let width = (ui.available_width() - response.rect.width()) / 2.0 - 20.0;
|
||||
let left_start = response.rect.left_center() - egui::vec2(10.0, 0.0);
|
||||
let left_end = left_start - egui::vec2(width, 0.0);
|
||||
ui.painter().line_segment([left_start, left_end], stroke);
|
||||
let right_start = response.rect.right_center() + egui::vec2(10.0, 0.0);
|
||||
let right_end = right_start + egui::vec2(width, 0.0);
|
||||
ui.painter().line_segment([right_start, right_end], stroke);
|
||||
},
|
||||
);
|
||||
}
|
||||
|
||||
fn render_note_maybe_fake(
|
||||
app: &mut GossipUi,
|
||||
ctx: &Context,
|
||||
|
@ -153,11 +153,14 @@ pub(super) fn render_note(
|
||||
|
||||
// scroll to this note if it's the main note of a thread and the user hasn't scrolled yet
|
||||
if is_main_event && app.feeds.thread_needs_scroll {
|
||||
// keep auto-scrolling untill user scrolls
|
||||
// keep auto-scrolling until user scrolls
|
||||
if app.current_scroll_offset != 0.0 {
|
||||
app.feeds.thread_needs_scroll = false;
|
||||
}
|
||||
inner_response.response.scroll_to_me(Some(Align::Center));
|
||||
// only request scrolling if the note is not completely visible
|
||||
if !ui.clip_rect().contains_rect(inner_response.response.rect) {
|
||||
inner_response.response.scroll_to_me(Some(Align::Center));
|
||||
}
|
||||
}
|
||||
|
||||
// Mark post as viewed if hovered AND we are not scrolling
|
||||
|
@ -629,6 +629,7 @@ impl GossipUi {
|
||||
let mut wizard_state: WizardState = Default::default();
|
||||
let wizard_complete = GLOBALS.storage.get_flag_wizard_complete();
|
||||
if !wizard_complete {
|
||||
wizard_state.init();
|
||||
if let Some(wp) = wizard::start_wizard_page(&mut wizard_state) {
|
||||
start_page = Page::Wizard(wp);
|
||||
}
|
||||
@ -1172,9 +1173,20 @@ impl GossipUi {
|
||||
|
||||
// ---- "plus icon" ----
|
||||
if !self.show_post_area_fn() && self.page.show_post_icon() {
|
||||
let bottom_right = ui.ctx().screen_rect().right_bottom();
|
||||
let pos = bottom_right
|
||||
+ Vec2::new(-crate::AVATAR_SIZE_F32 * 2.0, -crate::AVATAR_SIZE_F32 * 2.0);
|
||||
let feed_newest_at_bottom =
|
||||
GLOBALS.storage.read_setting_feed_newest_at_bottom();
|
||||
let pos = if feed_newest_at_bottom {
|
||||
let top_right = ui.ctx().screen_rect().right_top();
|
||||
top_right
|
||||
+ Vec2::new(-crate::AVATAR_SIZE_F32 * 2.0, crate::AVATAR_SIZE_F32 * 2.0)
|
||||
} else {
|
||||
let bottom_right = ui.ctx().screen_rect().right_bottom();
|
||||
bottom_right
|
||||
+ Vec2::new(
|
||||
-crate::AVATAR_SIZE_F32 * 2.0,
|
||||
-crate::AVATAR_SIZE_F32 * 2.0,
|
||||
)
|
||||
};
|
||||
|
||||
egui::Area::new(ui.next_auto_id())
|
||||
.movable(false)
|
||||
@ -1194,6 +1206,15 @@ impl GossipUi {
|
||||
} else {
|
||||
RichText::new("\u{1f513}").size(20.0)
|
||||
};
|
||||
let fill_color = {
|
||||
let fill_color_tuple = self.theme.accent_color().to_tuple();
|
||||
Color32::from_rgba_premultiplied(
|
||||
fill_color_tuple.0,
|
||||
fill_color_tuple.1,
|
||||
fill_color_tuple.2,
|
||||
128, // half transparent
|
||||
)
|
||||
};
|
||||
let response = ui.add_sized(
|
||||
[crate::AVATAR_SIZE_F32, crate::AVATAR_SIZE_F32],
|
||||
egui::Button::new(
|
||||
@ -1201,7 +1222,7 @@ impl GossipUi {
|
||||
)
|
||||
.stroke(egui::Stroke::NONE)
|
||||
.rounding(egui::Rounding::same(crate::AVATAR_SIZE_F32))
|
||||
.fill(self.theme.accent_color()),
|
||||
.fill(fill_color),
|
||||
);
|
||||
if response.clicked() {
|
||||
self.show_post_area = true;
|
||||
@ -1592,21 +1613,14 @@ impl GossipUi {
|
||||
}
|
||||
}
|
||||
if !followed && ui.button("Follow").clicked() {
|
||||
let _ = GLOBALS.people.follow(
|
||||
&person.pubkey,
|
||||
true,
|
||||
PersonList::Followed,
|
||||
true,
|
||||
true,
|
||||
);
|
||||
let _ = GLOBALS
|
||||
.people
|
||||
.follow(&person.pubkey, true, PersonList::Followed, true);
|
||||
} else if followed && ui.button("Unfollow").clicked() {
|
||||
let _ = GLOBALS.people.follow(
|
||||
&person.pubkey,
|
||||
false,
|
||||
PersonList::Followed,
|
||||
true,
|
||||
false,
|
||||
);
|
||||
let _ =
|
||||
GLOBALS
|
||||
.people
|
||||
.follow(&person.pubkey, false, PersonList::Followed, true);
|
||||
}
|
||||
|
||||
// Do not show 'Mute' if this is yourself
|
||||
|
@ -3,7 +3,7 @@ use std::time::{Duration, Instant};
|
||||
use super::{GossipUi, Page};
|
||||
use crate::ui::widgets;
|
||||
use crate::AVATAR_SIZE_F32;
|
||||
use eframe::egui;
|
||||
use eframe::egui::{self, Label, Sense};
|
||||
use egui::{Context, RichText, Ui, Vec2};
|
||||
use egui_winit::egui::text::LayoutJob;
|
||||
use egui_winit::egui::text_edit::TextEditOutput;
|
||||
@ -27,6 +27,7 @@ pub(in crate::ui) struct ListUi {
|
||||
add_contact_searched: Option<String>,
|
||||
add_contact_search_results: Vec<(String, PublicKey)>,
|
||||
add_contact_search_selected: Option<usize>,
|
||||
add_contact_error: Option<String>,
|
||||
|
||||
entering_follow_someone_on_list: bool,
|
||||
clear_list_needs_confirm: bool,
|
||||
@ -49,6 +50,7 @@ impl ListUi {
|
||||
add_contact_searched: None,
|
||||
add_contact_search_results: Vec::new(),
|
||||
add_contact_search_selected: None,
|
||||
add_contact_error: None,
|
||||
|
||||
entering_follow_someone_on_list: false,
|
||||
clear_list_needs_confirm: false,
|
||||
@ -218,7 +220,7 @@ pub(super) fn update(
|
||||
app.placeholder_avatar.clone()
|
||||
};
|
||||
|
||||
let avatar_response =
|
||||
let mut response =
|
||||
widgets::paint_avatar(ui, person, &avatar, widgets::AvatarSize::Feed);
|
||||
|
||||
ui.add_space(20.0);
|
||||
@ -226,7 +228,11 @@ pub(super) fn update(
|
||||
ui.vertical(|ui| {
|
||||
ui.add_space(5.0);
|
||||
ui.horizontal(|ui| {
|
||||
ui.label(RichText::new(person.best_name()).size(15.5));
|
||||
response |= ui.add(
|
||||
Label::new(RichText::new(person.best_name()).size(15.5))
|
||||
.selectable(false)
|
||||
.sense(Sense::click()),
|
||||
);
|
||||
|
||||
ui.add_space(10.0);
|
||||
|
||||
@ -235,14 +241,22 @@ pub(super) fn update(
|
||||
.have_persons_relays(person.pubkey)
|
||||
.unwrap_or(false)
|
||||
{
|
||||
ui.label(
|
||||
RichText::new("Relay list not found")
|
||||
.color(app.theme.warning_marker_text_color()),
|
||||
response |= ui.add(
|
||||
Label::new(
|
||||
RichText::new("Relay list not found")
|
||||
.color(app.theme.warning_marker_text_color()),
|
||||
)
|
||||
.selectable(false)
|
||||
.sense(Sense::click()),
|
||||
);
|
||||
}
|
||||
});
|
||||
ui.add_space(3.0);
|
||||
ui.label(GossipUi::richtext_from_person_nip05(person).weak());
|
||||
response |= ui.add(
|
||||
Label::new(GossipUi::richtext_from_person_nip05(person).weak())
|
||||
.selectable(false)
|
||||
.sense(Sense::click()),
|
||||
);
|
||||
});
|
||||
|
||||
ui.vertical(|ui| {
|
||||
@ -266,7 +280,7 @@ pub(super) fn update(
|
||||
|
||||
if list != PersonList::Followed {
|
||||
// private / public switch
|
||||
ui.label("Private");
|
||||
ui.add(Label::new("Private").selectable(false));
|
||||
if ui
|
||||
.add(widgets::Switch::onoff(&app.theme, &mut private))
|
||||
.clicked()
|
||||
@ -282,17 +296,21 @@ pub(super) fn update(
|
||||
}
|
||||
},
|
||||
);
|
||||
});
|
||||
if avatar_response.clicked() {
|
||||
app.set_page(ctx, Page::Person(person.pubkey));
|
||||
}
|
||||
});
|
||||
response
|
||||
})
|
||||
.inner
|
||||
})
|
||||
.inner
|
||||
},
|
||||
);
|
||||
if row_response
|
||||
.response
|
||||
.on_hover_cursor(egui::CursorIcon::PointingHand)
|
||||
.clicked()
|
||||
|| row_response
|
||||
.inner
|
||||
.on_hover_cursor(egui::CursorIcon::PointingHand)
|
||||
.clicked()
|
||||
{
|
||||
app.set_page(ctx, Page::Person(person.pubkey));
|
||||
}
|
||||
@ -350,7 +368,7 @@ fn render_add_contact_popup(
|
||||
list: PersonList,
|
||||
metadata: &PersonListMetadata,
|
||||
) {
|
||||
const DLG_SIZE: Vec2 = vec2(400.0, 240.0);
|
||||
const DLG_SIZE: Vec2 = vec2(400.0, 260.0);
|
||||
let ret = crate::ui::widgets::modal_popup(ui, DLG_SIZE, DLG_SIZE, true, |ui| {
|
||||
let enter_key;
|
||||
(app.people_list.add_contact_search_selected, enter_key) =
|
||||
@ -367,6 +385,18 @@ fn render_add_contact_popup(
|
||||
ui.heading("Add contact to the list");
|
||||
ui.add_space(8.0);
|
||||
|
||||
// error block
|
||||
ui.label(
|
||||
RichText::new(
|
||||
app.people_list
|
||||
.add_contact_error
|
||||
.as_ref()
|
||||
.unwrap_or(&"".to_string()),
|
||||
)
|
||||
.color(app.theme.warning_marker_text_color()),
|
||||
);
|
||||
ui.add_space(8.0);
|
||||
|
||||
ui.label("Search for known contacts to add");
|
||||
ui.add_space(8.0);
|
||||
|
||||
@ -466,10 +496,7 @@ fn render_add_contact_popup(
|
||||
mark_refresh(app);
|
||||
} else {
|
||||
add_failed = true;
|
||||
GLOBALS
|
||||
.status_queue
|
||||
.write()
|
||||
.write("Invalid pubkey.".to_string());
|
||||
app.people_list.add_contact_error = Some("Invalid pubkey.".to_string());
|
||||
}
|
||||
if !add_failed {
|
||||
app.add_contact = "".to_owned();
|
||||
@ -684,6 +711,11 @@ pub(super) fn render_more_list_actions(
|
||||
count: usize,
|
||||
on_list: bool,
|
||||
) {
|
||||
// do not show for "Following" and "Muted"
|
||||
if !on_list && !matches!(list, PersonList::Custom(_)) {
|
||||
return;
|
||||
}
|
||||
|
||||
if on_list {
|
||||
app.theme.accent_button_1_style(ui.style_mut());
|
||||
}
|
||||
@ -697,12 +729,6 @@ pub(super) fn render_more_list_actions(
|
||||
app.theme.accent_button_1_style(ui.style_mut());
|
||||
ui.spacing_mut().item_spacing.y = 15.0;
|
||||
}
|
||||
if !on_list {
|
||||
if ui.button("View Contacts").clicked() {
|
||||
app.set_page(ui.ctx(), Page::PeopleList(list));
|
||||
*is_open = false;
|
||||
}
|
||||
}
|
||||
if matches!(list, PersonList::Custom(_)) {
|
||||
if ui.button("Rename").clicked() {
|
||||
app.deleting_list = None;
|
||||
|
@ -2,7 +2,7 @@ use std::cmp::Ordering;
|
||||
|
||||
use super::{GossipUi, Page};
|
||||
use crate::ui::widgets;
|
||||
use eframe::egui;
|
||||
use eframe::egui::{self, Sense};
|
||||
use egui::{Context, Ui};
|
||||
use egui_winit::egui::{Label, RichText};
|
||||
use gossip_lib::{PersonList, PersonListMetadata, GLOBALS};
|
||||
@ -55,22 +55,39 @@ pub(super) fn update(app: &mut GossipUi, ctx: &Context, _frame: &mut eframe::Fra
|
||||
|
||||
ui.vertical(|ui| {
|
||||
ui.horizontal(|ui| {
|
||||
ui.add(Label::new(
|
||||
RichText::new(&metadata.title).heading().color(color),
|
||||
));
|
||||
ui.label(format!("({})", metadata.len));
|
||||
let mut response = ui.add(
|
||||
Label::new(
|
||||
RichText::new(&metadata.title).heading().color(color),
|
||||
)
|
||||
.selectable(false)
|
||||
.sense(egui::Sense::click()),
|
||||
);
|
||||
|
||||
response |= ui.add(
|
||||
Label::new(format!("({})", metadata.len))
|
||||
.selectable(false)
|
||||
.sense(Sense::click()),
|
||||
);
|
||||
if metadata.favorite {
|
||||
ui.add(Label::new(
|
||||
RichText::new("★")
|
||||
.size(18.0)
|
||||
.color(app.theme.accent_complementary_color()),
|
||||
));
|
||||
response |= ui.add(
|
||||
Label::new(
|
||||
RichText::new("★")
|
||||
.size(18.0)
|
||||
.color(app.theme.accent_complementary_color()),
|
||||
)
|
||||
.selectable(false)
|
||||
.sense(Sense::click()),
|
||||
);
|
||||
}
|
||||
if metadata.private {
|
||||
ui.add(Label::new(
|
||||
RichText::new("😎")
|
||||
.color(app.theme.accent_complementary_color()),
|
||||
));
|
||||
response |= ui.add(
|
||||
Label::new(
|
||||
RichText::new("😎")
|
||||
.color(app.theme.accent_complementary_color()),
|
||||
)
|
||||
.selectable(false)
|
||||
.sense(Sense::click()),
|
||||
);
|
||||
}
|
||||
|
||||
ui.with_layout(
|
||||
@ -87,14 +104,21 @@ pub(super) fn update(app: &mut GossipUi, ctx: &Context, _frame: &mut eframe::Fra
|
||||
);
|
||||
},
|
||||
);
|
||||
});
|
||||
});
|
||||
response
|
||||
})
|
||||
.inner
|
||||
})
|
||||
.inner
|
||||
},
|
||||
);
|
||||
if row_response
|
||||
.response
|
||||
.on_hover_cursor(egui::CursorIcon::PointingHand)
|
||||
.clicked()
|
||||
|| row_response
|
||||
.inner
|
||||
.on_hover_cursor(egui::CursorIcon::PointingHand)
|
||||
.clicked()
|
||||
{
|
||||
app.set_page(ctx, Page::PeopleList(list));
|
||||
}
|
||||
|
@ -15,6 +15,10 @@ pub(super) fn update(app: &mut GossipUi, ctx: &Context, _frame: &mut eframe::Fra
|
||||
&mut app.unsaved_settings.posting_area_at_top,
|
||||
"Show posting area at the top instead of the bottom",
|
||||
);
|
||||
ui.checkbox(
|
||||
&mut app.unsaved_settings.feed_newest_at_bottom,
|
||||
"Order feed with newest at bottom (intead of top)",
|
||||
);
|
||||
|
||||
ui.add_space(20.0);
|
||||
ui.horizontal(|ui| {
|
||||
|
@ -14,7 +14,7 @@ use std::collections::BTreeMap;
|
||||
|
||||
const MIN_OUTBOX: usize = 3;
|
||||
const MIN_INBOX: usize = 2;
|
||||
const MIN_DISCOVERY: usize = 1;
|
||||
const MIN_DISCOVERY: usize = 4;
|
||||
|
||||
pub(super) fn update(app: &mut GossipUi, ctx: &Context, _frame: &mut eframe::Frame, ui: &mut Ui) {
|
||||
let read_relay = |url: &RelayUrl| GLOBALS.storage.read_or_create_relay(url, None).unwrap();
|
||||
@ -135,7 +135,7 @@ pub(super) fn update(app: &mut GossipUi, ctx: &Context, _frame: &mut eframe::Fra
|
||||
ui.label(RichText::new(" - OK").color(Color32::GREEN));
|
||||
} else {
|
||||
ui.label(
|
||||
RichText::new(" - You should have one")
|
||||
RichText::new(" - We suggest 4")
|
||||
.color(app.theme.warning_marker_text_color()),
|
||||
);
|
||||
}
|
||||
|
@ -76,6 +76,13 @@ impl Default for WizardState {
|
||||
}
|
||||
}
|
||||
impl WizardState {
|
||||
pub fn init(&mut self) {
|
||||
if self.need_discovery_relays() {
|
||||
let purplepages = RelayUrl::try_from_str("wss://purplepag.es/").unwrap();
|
||||
super::modify_relay(&purplepages, |relay| relay.set_usage_bits(Relay::DISCOVER));
|
||||
}
|
||||
}
|
||||
|
||||
pub fn update(&mut self) {
|
||||
self.follow_only = GLOBALS.storage.get_flag_following_only();
|
||||
|
||||
@ -112,11 +119,6 @@ impl WizardState {
|
||||
.map(|(pk, _)| (Some(pk), None))
|
||||
.collect();
|
||||
|
||||
if self.need_discovery_relays() {
|
||||
let purplepages = RelayUrl::try_from_str("wss://purplepag.es/").unwrap();
|
||||
super::modify_relay(&purplepages, |relay| relay.set_usage_bits(Relay::DISCOVER));
|
||||
}
|
||||
|
||||
// Copy any new status queue messages into our local error variable
|
||||
let last_status_queue_message = GLOBALS.status_queue.read().read_last();
|
||||
if last_status_queue_message != self.last_status_queue_message {
|
||||
|
@ -89,6 +89,7 @@ pub struct UnsavedSettings {
|
||||
pub follow_os_dark_mode: bool,
|
||||
pub override_dpi: Option<u32>,
|
||||
pub highlight_unread_events: bool,
|
||||
pub feed_newest_at_bottom: bool,
|
||||
pub posting_area_at_top: bool,
|
||||
pub status_bar: bool,
|
||||
pub image_resize_algorithm: String,
|
||||
@ -172,6 +173,7 @@ impl Default for UnsavedSettings {
|
||||
follow_os_dark_mode: default_setting!(follow_os_dark_mode),
|
||||
override_dpi: default_setting!(override_dpi),
|
||||
highlight_unread_events: default_setting!(highlight_unread_events),
|
||||
feed_newest_at_bottom: default_setting!(feed_newest_at_bottom),
|
||||
posting_area_at_top: default_setting!(posting_area_at_top),
|
||||
status_bar: default_setting!(status_bar),
|
||||
image_resize_algorithm: default_setting!(image_resize_algorithm),
|
||||
@ -257,6 +259,7 @@ impl UnsavedSettings {
|
||||
follow_os_dark_mode: load_setting!(follow_os_dark_mode),
|
||||
override_dpi: load_setting!(override_dpi),
|
||||
highlight_unread_events: load_setting!(highlight_unread_events),
|
||||
feed_newest_at_bottom: load_setting!(feed_newest_at_bottom),
|
||||
posting_area_at_top: load_setting!(posting_area_at_top),
|
||||
status_bar: load_setting!(status_bar),
|
||||
image_resize_algorithm: load_setting!(image_resize_algorithm),
|
||||
@ -338,6 +341,7 @@ impl UnsavedSettings {
|
||||
save_setting!(follow_os_dark_mode, self, txn);
|
||||
save_setting!(override_dpi, self, txn);
|
||||
save_setting!(highlight_unread_events, self, txn);
|
||||
save_setting!(feed_newest_at_bottom, self, txn);
|
||||
save_setting!(posting_area_at_top, self, txn);
|
||||
save_setting!(status_bar, self, txn);
|
||||
save_setting!(image_resize_algorithm, self, txn);
|
||||
|
@ -35,6 +35,9 @@ rustls-tls-native = [
|
||||
"tokio-tungstenite/rustls-tls-native-roots"
|
||||
]
|
||||
|
||||
# Make tweaks for AppImage
|
||||
appimage = []
|
||||
|
||||
[dependencies]
|
||||
async-recursion = "1.0"
|
||||
async-trait = "0.1"
|
||||
|
@ -143,7 +143,7 @@ pub enum ToOverlordMessage {
|
||||
RankRelay(RelayUrl, u8),
|
||||
|
||||
/// internal (the overlord sends messages to itself sometimes!)
|
||||
ReengageMinion(RelayUrl, Vec<RelayJob>),
|
||||
ReengageMinion(RelayUrl),
|
||||
|
||||
/// Calls [refresh_scores_and_pick_relays](crate::Overlord::refresh_scores_and_pick_relays)
|
||||
RefreshScoresAndPickRelays,
|
||||
@ -235,7 +235,7 @@ pub(crate) struct ToMinionPayload {
|
||||
pub detail: ToMinionPayloadDetail,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone)]
|
||||
#[derive(Debug, Clone, PartialEq)]
|
||||
pub(crate) enum ToMinionPayloadDetail {
|
||||
AdvertiseRelayList(Box<Event>),
|
||||
AuthApproved,
|
||||
@ -354,3 +354,10 @@ pub struct RelayJob {
|
||||
// overlord.minions_task_url
|
||||
// GLOBALS.relay_picker
|
||||
}
|
||||
|
||||
impl RelayJob {
|
||||
// This is like equality, but ignores the random job id
|
||||
pub fn matches(&self, other: &RelayJob) -> bool {
|
||||
self.reason == other.reason && self.payload.detail == other.payload.detail
|
||||
}
|
||||
}
|
||||
|
@ -5,19 +5,19 @@ use crate::feed::Feed;
|
||||
use crate::fetcher::Fetcher;
|
||||
use crate::gossip_identity::GossipIdentity;
|
||||
use crate::media::Media;
|
||||
use crate::misc::ZapState;
|
||||
use crate::nip46::ParsedCommand;
|
||||
use crate::pending::Pending;
|
||||
use crate::people::{People, Person};
|
||||
use crate::relay::Relay;
|
||||
use crate::relay_picker_hooks::Hooks;
|
||||
use crate::seeker::Seeker;
|
||||
use crate::status::StatusQueue;
|
||||
use crate::storage::Storage;
|
||||
use crate::RunState;
|
||||
use dashmap::{DashMap, DashSet};
|
||||
use gossip_relay_picker::RelayPicker;
|
||||
use nostr_types::{
|
||||
Event, Id, PayRequestData, Profile, PublicKey, RelayUrl, RelayUsage, UncheckedUrl,
|
||||
};
|
||||
use nostr_types::{Event, Id, Profile, PublicKey, RelayUrl, RelayUsage};
|
||||
use parking_lot::RwLock as PRwLock;
|
||||
use regex::Regex;
|
||||
use rhai::{Engine, AST};
|
||||
@ -28,16 +28,6 @@ use tokio::sync::watch::Receiver as WatchReceiver;
|
||||
use tokio::sync::watch::Sender as WatchSender;
|
||||
use tokio::sync::{broadcast, mpsc, Mutex, Notify, RwLock};
|
||||
|
||||
/// The state that a Zap is in (it moves through 5 states before it is complete)
|
||||
#[derive(Debug, Clone)]
|
||||
pub enum ZapState {
|
||||
None,
|
||||
CheckingLnurl(Id, PublicKey, UncheckedUrl),
|
||||
SeekingAmount(Id, PublicKey, PayRequestData, UncheckedUrl),
|
||||
LoadingInvoice(Id, PublicKey),
|
||||
ReadyToPay(Id, String), // String is the Zap Invoice as a string, to be shown as a QR code
|
||||
}
|
||||
|
||||
/// Global data shared between threads. Access via the static ref `GLOBALS`.
|
||||
pub struct Globals {
|
||||
/// This is a broadcast channel. All Minions should listen on it.
|
||||
@ -68,9 +58,15 @@ pub struct Globals {
|
||||
/// All nostr people records currently loaded into memory, keyed by pubkey
|
||||
pub people: People,
|
||||
|
||||
/// The relays currently connected to
|
||||
/// The relays currently connected to. It tracks the jobs that relay is assigned.
|
||||
/// As the minion completes jobs, code modifies this jobset, removing those completed
|
||||
/// jobs.
|
||||
pub connected_relays: DashMap<RelayUrl, Vec<RelayJob>>,
|
||||
|
||||
/// The relays not connected to, and which we will not connect to again until some
|
||||
/// time passes, but which we still have jobs for
|
||||
pub penalty_box_relays: DashMap<RelayUrl, Vec<RelayJob>>,
|
||||
|
||||
/// The relay picker, used to pick the next relay
|
||||
pub relay_picker: RelayPicker<Hooks>,
|
||||
|
||||
@ -86,6 +82,9 @@ pub struct Globals {
|
||||
/// Fetcher
|
||||
pub fetcher: Fetcher,
|
||||
|
||||
/// Seeker
|
||||
pub seeker: Seeker,
|
||||
|
||||
/// Failed Avatars
|
||||
/// If in this map, the avatar failed to load or process and is unrecoverable
|
||||
/// (but we will take them out and try again if new metadata flows in)
|
||||
@ -197,11 +196,13 @@ lazy_static! {
|
||||
tmp_overlord_receiver: Mutex::new(Some(tmp_overlord_receiver)),
|
||||
people: People::new(),
|
||||
connected_relays: DashMap::new(),
|
||||
penalty_box_relays: DashMap::new(),
|
||||
relay_picker: Default::default(),
|
||||
identity: GossipIdentity::default(),
|
||||
dismissed: RwLock::new(Vec::new()),
|
||||
feed: Feed::new(),
|
||||
fetcher: Fetcher::new(),
|
||||
seeker: Seeker::new(),
|
||||
failed_avatars: RwLock::new(HashSet::new()),
|
||||
pixels_per_point_times_100: AtomicU32::new(139), // 100 dpi, 1/72th inch => 1.38888
|
||||
status_queue: PRwLock::new(StatusQueue::new(
|
||||
|
@ -90,7 +90,7 @@ pub use fetcher::Fetcher;
|
||||
mod filter;
|
||||
|
||||
mod globals;
|
||||
pub use globals::{Globals, ZapState, GLOBALS};
|
||||
pub use globals::{Globals, GLOBALS};
|
||||
|
||||
mod gossip_identity;
|
||||
pub use gossip_identity::GossipIdentity;
|
||||
@ -98,6 +98,9 @@ pub use gossip_identity::GossipIdentity;
|
||||
mod media;
|
||||
pub use media::Media;
|
||||
|
||||
mod misc;
|
||||
pub use misc::ZapState;
|
||||
|
||||
/// Rendering various names of users
|
||||
pub mod names;
|
||||
|
||||
@ -133,6 +136,9 @@ pub use relay::Relay;
|
||||
mod relay_picker_hooks;
|
||||
pub use relay_picker_hooks::Hooks;
|
||||
|
||||
mod seeker;
|
||||
pub use seeker::Seeker;
|
||||
|
||||
mod status;
|
||||
pub use status::StatusQueue;
|
||||
|
||||
@ -240,11 +246,15 @@ pub async fn run() {
|
||||
// Start the fetcher
|
||||
crate::fetcher::Fetcher::start();
|
||||
|
||||
// Start the seeker
|
||||
crate::seeker::Seeker::start();
|
||||
|
||||
// Start periodic tasks in people manager (after signer)
|
||||
crate::people::People::start();
|
||||
|
||||
// Start periodic tasks in pending
|
||||
crate::pending::start();
|
||||
// DISABLED until the UI is ready to implement.
|
||||
// crate::pending::start();
|
||||
|
||||
// Start long-lived subscriptions
|
||||
// (this also does a relay_picker init)
|
||||
|
18
gossip-lib/src/misc.rs
Normal file
18
gossip-lib/src/misc.rs
Normal file
@ -0,0 +1,18 @@
|
||||
use nostr_types::{Id, PayRequestData, PublicKey, UncheckedUrl};
|
||||
|
||||
/// The state that a Zap is in (it moves through 5 states before it is complete)
|
||||
#[derive(Debug, Clone)]
|
||||
pub enum ZapState {
|
||||
None,
|
||||
CheckingLnurl(Id, PublicKey, UncheckedUrl),
|
||||
SeekingAmount(Id, PublicKey, PayRequestData, UncheckedUrl),
|
||||
LoadingInvoice(Id, PublicKey),
|
||||
ReadyToPay(Id, String), // String is the Zap Invoice as a string, to be shown as a QR code
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
|
||||
pub enum Freshness {
|
||||
NeverSought,
|
||||
Stale,
|
||||
Fresh,
|
||||
}
|
@ -122,7 +122,7 @@ pub async fn get_and_follow_nip05(
|
||||
update_relays(&nip05, nip05file, &pubkey).await?;
|
||||
|
||||
// Follow
|
||||
GLOBALS.people.follow(&pubkey, true, list, public, true)?;
|
||||
GLOBALS.people.follow(&pubkey, true, list, public)?;
|
||||
|
||||
tracing::info!("Followed {}", &nip05);
|
||||
|
||||
|
@ -29,32 +29,6 @@ pub fn general_feed(
|
||||
filters
|
||||
}
|
||||
|
||||
pub fn relay_lists(authors: &[PublicKey]) -> Vec<Filter> {
|
||||
// Try to find where people post.
|
||||
// Subscribe to kind-10002 `RelayList`s to see where people post.
|
||||
// Subscribe to ContactLists so we can look at the contents and
|
||||
// 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<PublicKeyHex> = GLOBALS
|
||||
.people
|
||||
.get_subscribed_pubkeys_needing_relay_lists(authors)
|
||||
.drain(..)
|
||||
.map(|pk| pk.into())
|
||||
.collect();
|
||||
|
||||
if !keys_needing_relay_lists.is_empty() {
|
||||
vec![Filter {
|
||||
authors: keys_needing_relay_lists,
|
||||
kinds: vec![EventKind::RelayList, EventKind::ContactList],
|
||||
// No since. These are replaceable events, we should only get 1 per person.
|
||||
..Default::default()
|
||||
}]
|
||||
} else {
|
||||
vec![]
|
||||
}
|
||||
}
|
||||
|
||||
pub fn augments(ids: &[IdHex]) -> Vec<Filter> {
|
||||
let event_kinds = crate::feed::feed_augment_event_kinds();
|
||||
|
||||
@ -176,6 +150,8 @@ pub fn outbox(since: Unixtime) -> Vec<Filter> {
|
||||
}
|
||||
}
|
||||
|
||||
// This FORCES the fetch of relay lists without checking if we need them.
|
||||
// See also relay_lists() which checks if they are needed first.
|
||||
pub fn discover(pubkeys: &[PublicKey]) -> Vec<Filter> {
|
||||
let pkp: Vec<PublicKeyHex> = pubkeys.iter().map(|pk| pk.into()).collect();
|
||||
vec![Filter {
|
||||
|
@ -40,7 +40,7 @@ impl Minion {
|
||||
}
|
||||
}
|
||||
if !it_matches {
|
||||
tracing::info!(
|
||||
tracing::debug!(
|
||||
"{} sent event that does not match filters on subscription {}: {}",
|
||||
self.url,
|
||||
handle,
|
||||
|
@ -638,9 +638,7 @@ impl Minion {
|
||||
}
|
||||
};
|
||||
|
||||
let mut filters = filter_fns::general_feed(&self.general_feed_keys, since, None);
|
||||
let filters2 = filter_fns::relay_lists(&self.general_feed_keys);
|
||||
filters.extend(filters2);
|
||||
let filters = filter_fns::general_feed(&self.general_feed_keys, since, None);
|
||||
|
||||
if filters.is_empty() {
|
||||
self.unsubscribe("general_feed").await?;
|
||||
@ -682,9 +680,7 @@ impl Minion {
|
||||
None => self.compute_since(GLOBALS.storage.read_setting_feed_chunk()),
|
||||
};
|
||||
|
||||
let mut filters = filter_fns::general_feed(&new_keys, since, None);
|
||||
let filters2 = filter_fns::relay_lists(&new_keys);
|
||||
filters.extend(filters2);
|
||||
let filters = filter_fns::general_feed(&new_keys, since, None);
|
||||
|
||||
if !filters.is_empty() {
|
||||
self.subscribe(filters, "temp_general_feed_update", job_id)
|
||||
|
@ -7,7 +7,8 @@ use crate::comms::{
|
||||
use crate::dm_channel::DmChannel;
|
||||
use crate::error::{Error, ErrorKind};
|
||||
use crate::feed::FeedKind;
|
||||
use crate::globals::{Globals, ZapState, GLOBALS};
|
||||
use crate::globals::{Globals, GLOBALS};
|
||||
use crate::misc::ZapState;
|
||||
use crate::nip46::{Approval, ParsedCommand};
|
||||
use crate::people::{Person, PersonList};
|
||||
use crate::person_relay::PersonRelay;
|
||||
@ -362,6 +363,14 @@ impl Overlord {
|
||||
}
|
||||
refmut.value_mut().push(job);
|
||||
}
|
||||
} else if GLOBALS.penalty_box_relays.contains_key(&url) {
|
||||
// It is in the penalty box.
|
||||
// To avoid a race condition with the task that removes it from the penalty
|
||||
// box we have to use entry to make sure it was still there
|
||||
GLOBALS
|
||||
.penalty_box_relays
|
||||
.entry(url)
|
||||
.and_modify(|existing_jobs| Self::extend_jobs(existing_jobs, jobs));
|
||||
} else {
|
||||
// Start up the minion
|
||||
let mut minion = Minion::new(url.clone()).await?;
|
||||
@ -400,11 +409,15 @@ impl Overlord {
|
||||
// Remove from our hashmap
|
||||
self.minions_task_url.remove(&id);
|
||||
|
||||
// Set to not connected
|
||||
let relayjobs = GLOBALS.connected_relays.remove(&url).map(|(_, v)| v);
|
||||
// Set to not connected, and take any unfinished jobs
|
||||
let mut relayjobs = match GLOBALS.connected_relays.remove(&url).map(|(_, v)| v) {
|
||||
Some(jobs) => jobs,
|
||||
None => vec![],
|
||||
};
|
||||
|
||||
// Exclusion will be non-zero if there was a failure. It will be zero if we
|
||||
// succeeded
|
||||
let mut exclusion: u64;
|
||||
let mut completed: bool = false;
|
||||
|
||||
match join_result {
|
||||
Err(join_error) => {
|
||||
@ -421,36 +434,37 @@ impl Overlord {
|
||||
}
|
||||
exclusion = match exitreason {
|
||||
MinionExitReason::GotDisconnected => 120,
|
||||
MinionExitReason::GotShutdownMessage => 0,
|
||||
MinionExitReason::GotWSClose => 120,
|
||||
MinionExitReason::LostOverlord => 0,
|
||||
MinionExitReason::SubscriptionsHaveCompleted => {
|
||||
relayjobs = vec![];
|
||||
0
|
||||
}
|
||||
MinionExitReason::Unknown => 120,
|
||||
MinionExitReason::SubscriptionsHaveCompleted => 5,
|
||||
_ => 5,
|
||||
};
|
||||
|
||||
// Remember if the relay says all the jobs have completed
|
||||
if matches!(exitreason, MinionExitReason::SubscriptionsHaveCompleted) {
|
||||
completed = true;
|
||||
}
|
||||
}
|
||||
Err(e) => {
|
||||
Self::bump_failure_count(&url);
|
||||
tracing::error!("Minion {} completed with error: {}", &url, e);
|
||||
exclusion = 120;
|
||||
if let ErrorKind::RelayRejectedUs = e.kind {
|
||||
exclusion = 60 * 60 * 24 * 365; // don't connect again, practically
|
||||
exclusion = u64::MAX;
|
||||
} else if let ErrorKind::ReqwestHttpError(_) = e.kind {
|
||||
exclusion = u64::MAX;
|
||||
} else if let ErrorKind::Websocket(wserror) = e.kind {
|
||||
if let tungstenite::error::Error::Http(response) = wserror {
|
||||
exclusion = match response.status() {
|
||||
StatusCode::MOVED_PERMANENTLY => 60 * 60 * 24,
|
||||
StatusCode::PERMANENT_REDIRECT => 60 * 60 * 24,
|
||||
StatusCode::UNAUTHORIZED => 60 * 60 * 24,
|
||||
StatusCode::PAYMENT_REQUIRED => 60 * 60 * 24,
|
||||
StatusCode::FORBIDDEN => 60 * 60 * 24,
|
||||
StatusCode::NOT_FOUND => 60 * 60 * 24,
|
||||
StatusCode::PROXY_AUTHENTICATION_REQUIRED => 60 * 60 * 24,
|
||||
StatusCode::UNAVAILABLE_FOR_LEGAL_REASONS => 60 * 60 * 24,
|
||||
StatusCode::NOT_IMPLEMENTED => 60 * 60 * 24,
|
||||
StatusCode::BAD_GATEWAY => 60 * 60 * 24,
|
||||
StatusCode::MOVED_PERMANENTLY => u64::MAX,
|
||||
StatusCode::PERMANENT_REDIRECT => u64::MAX,
|
||||
StatusCode::UNAUTHORIZED => u64::MAX,
|
||||
StatusCode::PAYMENT_REQUIRED => u64::MAX,
|
||||
StatusCode::FORBIDDEN => u64::MAX,
|
||||
StatusCode::NOT_FOUND => u64::MAX,
|
||||
StatusCode::PROXY_AUTHENTICATION_REQUIRED => u64::MAX,
|
||||
StatusCode::UNAVAILABLE_FOR_LEGAL_REASONS => u64::MAX,
|
||||
StatusCode::NOT_IMPLEMENTED => u64::MAX,
|
||||
StatusCode::BAD_GATEWAY => u64::MAX,
|
||||
s if s.as_u16() >= 400 => 120,
|
||||
_ => 120,
|
||||
};
|
||||
@ -471,9 +485,9 @@ impl Overlord {
|
||||
},
|
||||
};
|
||||
|
||||
// We might need to act upon this minion exiting
|
||||
// Act upon this minion exiting, unless we are quitting
|
||||
if self.read_runstate.borrow().going_online() {
|
||||
self.recover_from_minion_exit(url, relayjobs, exclusion, completed)
|
||||
self.recover_from_minion_exit(url, relayjobs, exclusion)
|
||||
.await;
|
||||
}
|
||||
}
|
||||
@ -481,9 +495,8 @@ impl Overlord {
|
||||
async fn recover_from_minion_exit(
|
||||
&mut self,
|
||||
url: RelayUrl,
|
||||
jobs: Option<Vec<RelayJob>>,
|
||||
jobs: Vec<RelayJob>,
|
||||
exclusion: u64,
|
||||
completed: bool,
|
||||
) {
|
||||
// Let the relay picker know it disconnected
|
||||
GLOBALS
|
||||
@ -496,38 +509,52 @@ impl Overlord {
|
||||
}
|
||||
self.pick_relays().await;
|
||||
|
||||
if let Some(mut jobs) = jobs {
|
||||
// Remove any advertise jobs from the active set
|
||||
for job in &jobs {
|
||||
GLOBALS.active_advertise_jobs.remove(&job.payload.job_id);
|
||||
}
|
||||
|
||||
if completed {
|
||||
return;
|
||||
}
|
||||
|
||||
// If we have any persistent jobs, restart after a delay
|
||||
let persistent_jobs: Vec<RelayJob> = jobs
|
||||
.drain(..)
|
||||
.filter(|job| job.reason.persistent())
|
||||
.collect();
|
||||
|
||||
if !persistent_jobs.is_empty() {
|
||||
// Do it after a delay
|
||||
std::mem::drop(tokio::spawn(async move {
|
||||
// Delay for exclusion first
|
||||
tracing::info!(
|
||||
"Minion {} will restart in {} seconds to continue persistent jobs",
|
||||
&url,
|
||||
exclusion
|
||||
);
|
||||
tokio::time::sleep(Duration::new(exclusion, 0)).await;
|
||||
let _ = GLOBALS
|
||||
.to_overlord
|
||||
.send(ToOverlordMessage::ReengageMinion(url, persistent_jobs));
|
||||
}));
|
||||
}
|
||||
if exclusion == 0 {
|
||||
return;
|
||||
}
|
||||
|
||||
// Remove any advertise jobs from the active set
|
||||
for job in &jobs {
|
||||
GLOBALS.active_advertise_jobs.remove(&job.payload.job_id);
|
||||
}
|
||||
|
||||
if jobs.is_empty() {
|
||||
return;
|
||||
}
|
||||
|
||||
// OK we have an exclusion and unfinished jobs.
|
||||
//
|
||||
// Add this relay to the penalty box, and setup a task to reengage
|
||||
// it after the exclusion completes
|
||||
let exclusion = exclusion.max(10); // safety catch, minimum exclusion is 10s
|
||||
|
||||
GLOBALS.penalty_box_relays.insert(url.clone(), jobs);
|
||||
|
||||
tracing::info!(
|
||||
"Minion {} will restart in {} seconds to continue persistent jobs",
|
||||
&url,
|
||||
exclusion
|
||||
);
|
||||
|
||||
if exclusion != u64::MAX {
|
||||
// Re-engage after the delay
|
||||
std::mem::drop(tokio::spawn(async move {
|
||||
tokio::time::sleep(Duration::new(exclusion, 0)).await;
|
||||
let _ = GLOBALS
|
||||
.to_overlord
|
||||
.send(ToOverlordMessage::ReengageMinion(url));
|
||||
}));
|
||||
}
|
||||
// otherwise leave it in the penalty box forever
|
||||
}
|
||||
|
||||
async fn reengage_minion(&mut self, url: RelayUrl) -> Result<(), Error> {
|
||||
// Take from penalty box
|
||||
if let Some(pair) = GLOBALS.penalty_box_relays.remove(&url) {
|
||||
self.engage_minion(url, pair.1).await?;
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn bump_failure_count(url: &RelayUrl) {
|
||||
@ -537,6 +564,14 @@ impl Overlord {
|
||||
}
|
||||
}
|
||||
|
||||
fn extend_jobs(jobs: &mut Vec<RelayJob>, mut more: Vec<RelayJob>) {
|
||||
for newjob in more.drain(..) {
|
||||
if !jobs.iter().any(|job| job.matches(&newjob)) {
|
||||
jobs.push(newjob)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
async fn handle_message(&mut self, message: ToOverlordMessage) -> Result<(), Error> {
|
||||
match message {
|
||||
ToOverlordMessage::AddPubkeyRelay(pubkey, relayurl) => {
|
||||
@ -683,8 +718,8 @@ impl Overlord {
|
||||
ToOverlordMessage::RankRelay(relay_url, rank) => {
|
||||
Self::rank_relay(relay_url, rank)?;
|
||||
}
|
||||
ToOverlordMessage::ReengageMinion(url, persistent_jobs) => {
|
||||
self.engage_minion(url, persistent_jobs).await?;
|
||||
ToOverlordMessage::ReengageMinion(url) => {
|
||||
self.reengage_minion(url).await?;
|
||||
}
|
||||
ToOverlordMessage::RefreshSubscribedMetadata => {
|
||||
self.refresh_subscribed_metadata().await?;
|
||||
@ -1319,7 +1354,21 @@ impl Overlord {
|
||||
}
|
||||
|
||||
/// Fetch an event from a specific relay by event `Id`
|
||||
pub async fn fetch_event(&mut self, id: Id, relay_urls: Vec<RelayUrl>) -> Result<(), Error> {
|
||||
pub async fn fetch_event(
|
||||
&mut self,
|
||||
id: Id,
|
||||
mut relay_urls: Vec<RelayUrl>,
|
||||
) -> Result<(), Error> {
|
||||
// Use READ relays if relays are unknown
|
||||
if relay_urls.is_empty() {
|
||||
relay_urls = GLOBALS
|
||||
.storage
|
||||
.filter_relays(|r| r.has_usage_bits(Relay::READ) && r.rank != 0)?
|
||||
.iter()
|
||||
.map(|relay| relay.url.clone())
|
||||
.collect();
|
||||
}
|
||||
|
||||
// Don't do this if we already have the event
|
||||
if !GLOBALS.storage.has_event(id)? {
|
||||
// Note: minions will remember if they get the same id multiple times
|
||||
@ -1371,7 +1420,7 @@ impl Overlord {
|
||||
list: PersonList,
|
||||
public: bool,
|
||||
) -> Result<(), Error> {
|
||||
GLOBALS.people.follow(&pubkey, true, list, public, true)?;
|
||||
GLOBALS.people.follow(&pubkey, true, list, public)?;
|
||||
tracing::debug!("Followed {}", &pubkey.as_hex_string());
|
||||
Ok(())
|
||||
}
|
||||
@ -1415,7 +1464,7 @@ impl Overlord {
|
||||
// Follow
|
||||
GLOBALS
|
||||
.people
|
||||
.follow(&nprofile.pubkey, true, list, public, true)?;
|
||||
.follow(&nprofile.pubkey, true, list, public)?;
|
||||
|
||||
GLOBALS
|
||||
.status_queue
|
||||
@ -2766,8 +2815,8 @@ 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_subscribed_pubkeys();
|
||||
// who needs to seek a relay list again.
|
||||
let followed = GLOBALS.people.get_subscribed_pubkeys_needing_relay_lists();
|
||||
self.subscribe_discover(followed, None).await?;
|
||||
|
||||
// Separately subscribe to our outbox events on our write relays
|
||||
@ -2825,11 +2874,27 @@ impl Overlord {
|
||||
|
||||
/// Subscribe to the multiple user's relay lists (optionally on the given relays, otherwise using
|
||||
/// theconfigured discover relays)
|
||||
///
|
||||
/// Caller should probably check Person.relay_list_last_sought first to make sure we don't
|
||||
/// already have an in-flight request doing this. This can be done with:
|
||||
/// GLOBALS.people.person_needs_relay_list()
|
||||
/// GLOBALS.people.get_subscribed_pubkeys_needing_relay_lists()
|
||||
pub async fn subscribe_discover(
|
||||
&mut self,
|
||||
pubkeys: Vec<PublicKey>,
|
||||
relays: Option<Vec<RelayUrl>>,
|
||||
) -> Result<(), Error> {
|
||||
// Mark for each person that we are seeking their relay list
|
||||
// so that we don't repeat this for a while
|
||||
let now = Unixtime::now().unwrap();
|
||||
let mut txn = GLOBALS.storage.get_write_txn()?;
|
||||
for pk in pubkeys.iter() {
|
||||
let mut person = GLOBALS.storage.read_or_create_person(pk, Some(&mut txn))?;
|
||||
person.relay_list_last_sought = now.0;
|
||||
GLOBALS.storage.write_person(&person, Some(&mut txn))?;
|
||||
}
|
||||
txn.commit()?;
|
||||
|
||||
// Discover their relays
|
||||
let discover_relay_urls: Vec<RelayUrl> = match relays {
|
||||
Some(r) => r,
|
||||
|
@ -78,6 +78,7 @@ impl Pending {
|
||||
}
|
||||
}
|
||||
|
||||
#[allow(dead_code)]
|
||||
pub fn start() {
|
||||
task::spawn(async {
|
||||
let mut read_runstate = GLOBALS.read_runstate.clone();
|
||||
|
@ -1,6 +1,7 @@
|
||||
use crate::comms::ToOverlordMessage;
|
||||
use crate::error::{Error, ErrorKind};
|
||||
use crate::globals::GLOBALS;
|
||||
use crate::misc::Freshness;
|
||||
use dashmap::{DashMap, DashSet};
|
||||
use image::RgbaImage;
|
||||
use nostr_types::{
|
||||
@ -106,11 +107,19 @@ impl People {
|
||||
}
|
||||
|
||||
/// Get all the pubkeys that the user subscribes to in any list
|
||||
/// (We also force the current user into this 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,
|
||||
Ok(mut people) => {
|
||||
if let Some(pk) = GLOBALS.identity.public_key() {
|
||||
if !people.contains(&pk) {
|
||||
people.push(pk);
|
||||
}
|
||||
}
|
||||
people
|
||||
}
|
||||
Err(e) => {
|
||||
tracing::error!("{}", e);
|
||||
vec![]
|
||||
@ -128,27 +137,46 @@ impl People {
|
||||
}
|
||||
|
||||
/// Get all the pubkeys that need relay lists (from the given set)
|
||||
pub fn get_subscribed_pubkeys_needing_relay_lists(
|
||||
&self,
|
||||
among_these: &[PublicKey],
|
||||
) -> Vec<PublicKey> {
|
||||
pub fn get_subscribed_pubkeys_needing_relay_lists(&self) -> Vec<PublicKey> {
|
||||
let stale = Unixtime::now().unwrap().0
|
||||
- 60 * 60
|
||||
* GLOBALS
|
||||
.storage
|
||||
.read_setting_relay_list_becomes_stale_hours() as i64;
|
||||
|
||||
if let Ok(vec) = GLOBALS.storage.filter_people(|p| {
|
||||
p.is_subscribed_to()
|
||||
&& p.relay_list_last_received < stale
|
||||
&& among_these.contains(&p.pubkey)
|
||||
}) {
|
||||
if let Ok(vec) = GLOBALS
|
||||
.storage
|
||||
.filter_people(|p| p.is_subscribed_to() && p.relay_list_last_sought < stale)
|
||||
{
|
||||
vec.iter().map(|p| p.pubkey).collect()
|
||||
} else {
|
||||
vec![]
|
||||
}
|
||||
}
|
||||
|
||||
/// Get if a person needs a relay list
|
||||
pub fn person_needs_relay_list(pubkey: PublicKey) -> Freshness {
|
||||
let staletime = Unixtime::now().unwrap().0
|
||||
- 60 * 60
|
||||
* GLOBALS
|
||||
.storage
|
||||
.read_setting_relay_list_becomes_stale_hours() as i64;
|
||||
|
||||
match GLOBALS.storage.read_person(&pubkey) {
|
||||
Err(_) => Freshness::NeverSought,
|
||||
Ok(None) => Freshness::NeverSought,
|
||||
Ok(Some(p)) => {
|
||||
if p.relay_list_last_sought == 0 {
|
||||
Freshness::NeverSought
|
||||
} else if p.relay_list_last_sought < staletime {
|
||||
Freshness::Stale
|
||||
} else {
|
||||
Freshness::Fresh
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Create person record for this pubkey, if missing
|
||||
pub fn create_if_missing(&self, pubkey: PublicKey) {
|
||||
if let Err(e) = self.create_all_if_missing(&[pubkey]) {
|
||||
@ -729,7 +757,6 @@ impl People {
|
||||
follow: bool,
|
||||
list: PersonList,
|
||||
public: bool,
|
||||
discover: bool, // if you also want to subscribe to their relay list
|
||||
) -> Result<(), Error> {
|
||||
if follow {
|
||||
GLOBALS
|
||||
@ -738,6 +765,18 @@ impl People {
|
||||
|
||||
// Add to the relay picker. If they are already there, it will be ok.
|
||||
GLOBALS.relay_picker.add_someone(*pubkey)?;
|
||||
|
||||
// Maybe seek relay list (if needed)
|
||||
let seek_relay_list = match Self::person_needs_relay_list(*pubkey) {
|
||||
Freshness::NeverSought => true,
|
||||
Freshness::Stale => true,
|
||||
Freshness::Fresh => false,
|
||||
};
|
||||
if seek_relay_list {
|
||||
let _ = GLOBALS
|
||||
.to_overlord
|
||||
.send(ToOverlordMessage::SubscribeDiscover(vec![*pubkey], None));
|
||||
};
|
||||
} else {
|
||||
GLOBALS
|
||||
.storage
|
||||
@ -753,12 +792,6 @@ impl People {
|
||||
.to_overlord
|
||||
.send(ToOverlordMessage::RefreshScoresAndPickRelays);
|
||||
|
||||
if follow && discover {
|
||||
let _ = GLOBALS
|
||||
.to_overlord
|
||||
.send(ToOverlordMessage::SubscribeDiscover(vec![*pubkey], None));
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
@ -817,8 +850,6 @@ impl People {
|
||||
pubkey: PublicKey,
|
||||
created_at: i64,
|
||||
) -> Result<bool, Error> {
|
||||
let now = Unixtime::now().unwrap().0;
|
||||
|
||||
let mut retval = false;
|
||||
|
||||
let mut person = match GLOBALS.storage.read_person(&pubkey)? {
|
||||
@ -826,7 +857,6 @@ impl People {
|
||||
None => Person::new(pubkey),
|
||||
};
|
||||
|
||||
person.relay_list_last_received = now;
|
||||
if let Some(old_at) = person.relay_list_created_at {
|
||||
if created_at < old_at {
|
||||
retval = false;
|
||||
|
@ -239,6 +239,9 @@ pub async fn process_new_event(
|
||||
// Invalidate UI events indicated by those relationships
|
||||
GLOBALS.ui_notes_to_invalidate.write().extend(&invalid_ids);
|
||||
|
||||
// Let seeker know about this event id (in case it was sought)
|
||||
GLOBALS.seeker.found_or_cancel(event.id);
|
||||
|
||||
// If metadata, update person
|
||||
if event.kind == EventKind::Metadata {
|
||||
let metadata: Metadata = serde_json::from_str(&event.content)?;
|
||||
@ -273,26 +276,43 @@ pub async fn process_new_event(
|
||||
} else if event.kind == EventKind::RelayList {
|
||||
GLOBALS.storage.process_relay_list(event)?;
|
||||
|
||||
// Let the seeker know we now have relays for this author, in case the seeker
|
||||
// wants to update it's state
|
||||
// (we might not, but by this point we have tried)
|
||||
GLOBALS.seeker.found_author_relays(event.pubkey);
|
||||
|
||||
// the following also refreshes scores before it picks relays
|
||||
let _ = GLOBALS
|
||||
.to_overlord
|
||||
.send(ToOverlordMessage::RefreshScoresAndPickRelays);
|
||||
} else if event.kind == EventKind::Repost {
|
||||
// If the content is a repost, seek the event it reposts
|
||||
for eref in event.mentions().iter() {
|
||||
match eref {
|
||||
EventReference::Id(id, optrelay, _marker) => {
|
||||
if let Some(rurl) = optrelay {
|
||||
let _ = GLOBALS
|
||||
.to_overlord
|
||||
.send(ToOverlordMessage::FetchEvent(*id, vec![rurl.to_owned()]));
|
||||
// If it has a json encoded inner event
|
||||
if let Ok(inner_event) = serde_json::from_str::<Event>(&event.content) {
|
||||
// Seek that event by id and author
|
||||
GLOBALS
|
||||
.seeker
|
||||
.seek_id_and_author(inner_event.id, inner_event.pubkey)?;
|
||||
} else {
|
||||
// If the content is a repost, seek the event it reposts
|
||||
for eref in event.mentions().iter() {
|
||||
match eref {
|
||||
EventReference::Id(id, optrelay, _marker) => {
|
||||
if let Some(rurl) = optrelay {
|
||||
GLOBALS
|
||||
.seeker
|
||||
.seek_id_and_relays(*id, vec![rurl.to_owned()]);
|
||||
} else {
|
||||
// Even if the event tags the author, we have no way to coorelate
|
||||
// the nevent with that tag.
|
||||
GLOBALS.seeker.seek_id(*id);
|
||||
}
|
||||
}
|
||||
}
|
||||
EventReference::Addr(ea) => {
|
||||
if !ea.relays.is_empty() {
|
||||
let _ = GLOBALS
|
||||
.to_overlord
|
||||
.send(ToOverlordMessage::FetchEventAddr(ea.clone()));
|
||||
EventReference::Addr(ea) => {
|
||||
if !ea.relays.is_empty() {
|
||||
let _ = GLOBALS
|
||||
.to_overlord
|
||||
.send(ToOverlordMessage::FetchEventAddr(ea.clone()));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -30,6 +30,13 @@ pub struct Profile {
|
||||
|
||||
impl Profile {
|
||||
fn new() -> Result<Profile, Error> {
|
||||
if cfg!(feature = "appimage") {
|
||||
// Because AppImage only changes $HOME (and not $XDG_DATA_HOME), we unset
|
||||
// $XDG_DATA_HOME and let it use the changed $HOME on linux to find the
|
||||
// data directory
|
||||
std::env::remove_var("XDG_DATA_HOME");
|
||||
}
|
||||
|
||||
// Get system standard directory for user data
|
||||
let data_dir = dirs::data_dir()
|
||||
.ok_or::<Error>("Cannot find a directory to store application data.".into())?;
|
||||
|
234
gossip-lib/src/seeker.rs
Normal file
234
gossip-lib/src/seeker.rs
Normal file
@ -0,0 +1,234 @@
|
||||
use crate::comms::ToOverlordMessage;
|
||||
use crate::error::Error;
|
||||
use crate::globals::GLOBALS;
|
||||
use crate::misc::Freshness;
|
||||
use crate::people::People;
|
||||
use dashmap::DashMap;
|
||||
use nostr_types::{Id, PublicKey, RelayUrl, RelayUsage, Unixtime};
|
||||
use std::time::Duration;
|
||||
use tokio::time::Instant;
|
||||
|
||||
#[derive(Debug, Clone)]
|
||||
pub enum SeekState {
|
||||
WaitingRelayList(Unixtime, PublicKey),
|
||||
WaitingEvent(Unixtime),
|
||||
}
|
||||
|
||||
#[derive(Debug, Default)]
|
||||
pub struct Seeker {
|
||||
events: DashMap<Id, SeekState>,
|
||||
}
|
||||
|
||||
impl Seeker {
|
||||
pub(crate) fn new() -> Seeker {
|
||||
Seeker {
|
||||
..Default::default()
|
||||
}
|
||||
}
|
||||
|
||||
fn get_relays(author: PublicKey) -> Result<Vec<RelayUrl>, Error> {
|
||||
Ok(GLOBALS
|
||||
.storage
|
||||
.get_best_relays(author, RelayUsage::Outbox)?
|
||||
.iter()
|
||||
.map(|(r, _)| r.to_owned())
|
||||
.collect())
|
||||
}
|
||||
|
||||
fn seek_relay_list(author: PublicKey) {
|
||||
let _ = GLOBALS
|
||||
.to_overlord
|
||||
.send(ToOverlordMessage::SubscribeDiscover(vec![author], None));
|
||||
}
|
||||
|
||||
fn seek_event_at_our_read_relays(id: Id) {
|
||||
let _ = GLOBALS
|
||||
.to_overlord
|
||||
.send(ToOverlordMessage::FetchEvent(id, vec![]));
|
||||
}
|
||||
|
||||
fn seek_event_at_relays(id: Id, relays: Vec<RelayUrl>) {
|
||||
let _ = GLOBALS
|
||||
.to_overlord
|
||||
.send(ToOverlordMessage::FetchEvent(id, relays));
|
||||
}
|
||||
|
||||
/// Seek an event when you only have the `Id`
|
||||
pub(crate) fn seek_id(&self, id: Id) {
|
||||
if self.events.get(&id).is_some() {
|
||||
return; // we are already seeking this event
|
||||
}
|
||||
|
||||
tracing::debug!("Seeking id={}", id.as_hex_string());
|
||||
|
||||
Self::seek_event_at_our_read_relays(id);
|
||||
|
||||
// Remember when we asked
|
||||
let now = Unixtime::now().unwrap();
|
||||
self.events.insert(id, SeekState::WaitingEvent(now));
|
||||
}
|
||||
|
||||
/// Seek an event when you have the `Id` and the author `PublicKey`
|
||||
pub(crate) fn seek_id_and_author(&self, id: Id, author: PublicKey) -> Result<(), Error> {
|
||||
if self.events.get(&id).is_some() {
|
||||
return Ok(()); // we are already seeking this event
|
||||
}
|
||||
|
||||
tracing::debug!(
|
||||
"Seeking id={} with author={}",
|
||||
id.as_hex_string(),
|
||||
author.as_hex_string()
|
||||
);
|
||||
|
||||
let when = Unixtime::now().unwrap();
|
||||
|
||||
// Check if we have the author's relay list
|
||||
match People::person_needs_relay_list(author) {
|
||||
Freshness::NeverSought => {
|
||||
Self::seek_relay_list(author);
|
||||
self.events
|
||||
.insert(id, SeekState::WaitingRelayList(when, author));
|
||||
}
|
||||
Freshness::Stale => {
|
||||
// Seek the relay list because it is stale, but don't let that hold us up
|
||||
// using the stale data
|
||||
Self::seek_relay_list(author);
|
||||
|
||||
let relays = Self::get_relays(author)?;
|
||||
Self::seek_event_at_relays(id, relays);
|
||||
self.events.insert(id, SeekState::WaitingEvent(when));
|
||||
}
|
||||
Freshness::Fresh => {
|
||||
let relays = Self::get_relays(author)?;
|
||||
Self::seek_event_at_relays(id, relays);
|
||||
self.events.insert(id, SeekState::WaitingEvent(when));
|
||||
}
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Seek an event when you have the `Id` and the relays to seek from
|
||||
pub(crate) fn seek_id_and_relays(&self, id: Id, relays: Vec<RelayUrl>) {
|
||||
if let Some(existing) = self.events.get(&id) {
|
||||
if matches!(existing.value(), SeekState::WaitingEvent(_)) {
|
||||
return; // Already seeking it
|
||||
}
|
||||
}
|
||||
let when = Unixtime::now().unwrap();
|
||||
Self::seek_event_at_relays(id, relays);
|
||||
self.events.insert(id, SeekState::WaitingEvent(when));
|
||||
}
|
||||
|
||||
/// Inform the seeker that an author's relay list has just arrived
|
||||
pub(crate) fn found_author_relays(&self, pubkey: PublicKey) {
|
||||
// Instead of updating the map while we iterate (which could deadlock)
|
||||
// we save updates here and apply when the iterator is finished.
|
||||
let mut updates: Vec<(Id, SeekState)> = Vec::new();
|
||||
|
||||
for refmutmulti in self.events.iter_mut() {
|
||||
if let SeekState::WaitingRelayList(_when, author) = refmutmulti.value() {
|
||||
if *author == pubkey {
|
||||
let now = Unixtime::now().unwrap();
|
||||
let id = *refmutmulti.key();
|
||||
if let Ok(relays) = Self::get_relays(*author) {
|
||||
Self::seek_event_at_relays(id, relays);
|
||||
updates.push((id, SeekState::WaitingEvent(now)));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
for (id, state) in updates.drain(..) {
|
||||
let _ = self.events.insert(id, state);
|
||||
}
|
||||
}
|
||||
|
||||
pub(crate) fn found_or_cancel(&self, id: Id) {
|
||||
self.events.remove(&id);
|
||||
}
|
||||
|
||||
pub(crate) fn start() {
|
||||
// Setup periodic queue management
|
||||
tokio::task::spawn(async move {
|
||||
let mut read_runstate = GLOBALS.read_runstate.clone();
|
||||
read_runstate.mark_unchanged();
|
||||
if read_runstate.borrow().going_offline() {
|
||||
return;
|
||||
}
|
||||
|
||||
let sleep = tokio::time::sleep(Duration::from_millis(1000));
|
||||
tokio::pin!(sleep);
|
||||
|
||||
loop {
|
||||
tokio::select! {
|
||||
_ = &mut sleep => {
|
||||
sleep.as_mut().reset(Instant::now() + Duration::from_millis(1000));
|
||||
},
|
||||
_ = read_runstate.wait_for(|runstate| runstate.going_offline()) => break,
|
||||
}
|
||||
|
||||
GLOBALS.seeker.run_once().await;
|
||||
}
|
||||
|
||||
tracing::info!("Seeker shutdown");
|
||||
});
|
||||
}
|
||||
|
||||
pub(crate) async fn run_once(&self) {
|
||||
if self.events.is_empty() {
|
||||
return;
|
||||
}
|
||||
|
||||
// Instead of updating the map while we iterate (which could deadlock)
|
||||
// we save updates here and apply when the iterator is finished.
|
||||
let mut updates: Vec<(Id, Option<SeekState>)> = Vec::new();
|
||||
|
||||
let now = Unixtime::now().unwrap();
|
||||
|
||||
for refmulti in self.events.iter() {
|
||||
let id = *refmulti.key();
|
||||
match refmulti.value() {
|
||||
SeekState::WaitingRelayList(when, author) => {
|
||||
// Check if we have their relays yet
|
||||
match People::person_needs_relay_list(*author) {
|
||||
Freshness::Fresh | Freshness::Stale => {
|
||||
if let Ok(relays) = Self::get_relays(*author) {
|
||||
Self::seek_event_at_relays(id, relays);
|
||||
updates.push((id, Some(SeekState::WaitingEvent(now))));
|
||||
continue;
|
||||
}
|
||||
}
|
||||
_ => {}
|
||||
}
|
||||
|
||||
// If it has been 15 seconds, give up the wait and seek from our READ relays
|
||||
if now - *when > Duration::from_secs(15) {
|
||||
Self::seek_event_at_our_read_relays(id);
|
||||
updates.push((id, Some(SeekState::WaitingEvent(now))));
|
||||
}
|
||||
|
||||
// Otherwise keep waiting
|
||||
}
|
||||
SeekState::WaitingEvent(when) => {
|
||||
if now - *when > Duration::from_secs(15) {
|
||||
tracing::debug!("Failed to find id={}", id.as_hex_string());
|
||||
updates.push((id, None));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Apply updates
|
||||
for (id, replacement) in updates.drain(..) {
|
||||
match replacement {
|
||||
Some(state) => {
|
||||
let _ = self.events.insert(id, state);
|
||||
}
|
||||
None => {
|
||||
let _ = self.events.remove(&id);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
@ -32,7 +32,7 @@ impl Storage {
|
||||
nip05_valid: person1.nip05_valid,
|
||||
nip05_last_checked: person1.nip05_last_checked,
|
||||
relay_list_created_at: person1.relay_list_created_at,
|
||||
relay_list_last_received: person1.relay_list_last_received,
|
||||
relay_list_last_sought: person1.relay_list_last_received,
|
||||
};
|
||||
self.write_person2(&person2, Some(txn))?;
|
||||
count += 1;
|
||||
|
@ -791,6 +791,7 @@ impl Storage {
|
||||
bool,
|
||||
true
|
||||
);
|
||||
def_setting!(feed_newest_at_bottom, b"feed_newest_at_bottom", bool, false);
|
||||
def_setting!(posting_area_at_top, b"posting_area_at_top", bool, true);
|
||||
def_setting!(status_bar, b"status_bar", bool, false);
|
||||
def_setting!(
|
||||
@ -1100,7 +1101,7 @@ impl Storage {
|
||||
|
||||
//// Modify all relay records
|
||||
#[inline]
|
||||
pub(crate) fn modify_all_relays<'a, M>(
|
||||
pub fn modify_all_relays<'a, M>(
|
||||
&'a self,
|
||||
modify: M,
|
||||
rw_txn: Option<&mut RwTxn<'a>>,
|
||||
@ -1163,16 +1164,16 @@ impl Storage {
|
||||
pub fn process_relay_list(&self, event: &Event) -> Result<(), Error> {
|
||||
let mut txn = self.env.write_txn()?;
|
||||
|
||||
// Check if this relay list is newer than the stamp we have for its author
|
||||
if let Some(mut person) = self.read_person(&event.pubkey)? {
|
||||
// Mark that we received it (changes fetch duration for next time)
|
||||
person.relay_list_last_received = Unixtime::now().unwrap().0;
|
||||
|
||||
// Check if this relay list is newer than the stamp we have for its author
|
||||
if let Some(previous_at) = person.relay_list_created_at {
|
||||
if event.created_at.0 <= previous_at {
|
||||
// This list is old. But let's save the last_received setting:
|
||||
self.write_person(&person, Some(&mut txn))?;
|
||||
return Ok(());
|
||||
}
|
||||
}
|
||||
// If we got here, the list is new.
|
||||
|
||||
// Mark when it was created
|
||||
person.relay_list_created_at = Some(event.created_at.0);
|
||||
@ -2017,6 +2018,23 @@ impl Storage {
|
||||
self.read_person2(pubkey)
|
||||
}
|
||||
|
||||
/// Read a person record, create if missing
|
||||
#[inline]
|
||||
pub fn read_or_create_person<'a>(
|
||||
&'a self,
|
||||
pubkey: &PublicKey,
|
||||
rw_txn: Option<&mut RwTxn<'a>>,
|
||||
) -> Result<Person, Error> {
|
||||
match self.read_person(pubkey)? {
|
||||
Some(p) => Ok(p),
|
||||
None => {
|
||||
let person = Person::new(pubkey.to_owned());
|
||||
self.write_person(&person, rw_txn)?;
|
||||
Ok(person)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Write a new person record only if missing
|
||||
pub fn write_person_if_missing<'a>(
|
||||
&'a self,
|
||||
|
@ -34,9 +34,10 @@ pub struct Person2 {
|
||||
/// for an update)
|
||||
pub relay_list_created_at: Option<i64>,
|
||||
|
||||
/// When their relay list was last received (to determine if we need to
|
||||
/// When their relay list was last sought (to determine if we need to
|
||||
/// check for an update)
|
||||
pub relay_list_last_received: i64,
|
||||
#[serde(rename = "relay_list_last_received")]
|
||||
pub relay_list_last_sought: i64,
|
||||
}
|
||||
|
||||
impl Person2 {
|
||||
@ -50,7 +51,7 @@ impl Person2 {
|
||||
nip05_valid: false,
|
||||
nip05_last_checked: None,
|
||||
relay_list_created_at: None,
|
||||
relay_list_last_received: 0,
|
||||
relay_list_last_sought: 0,
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -18,18 +18,44 @@ trap cleanup EXIT
|
||||
cd ../..
|
||||
|
||||
RUSTFLAGS="-C target-cpu=native --cfg tokio_unstable"
|
||||
cargo build --features=lang-cjk --release
|
||||
cargo build --features=lang-cjk,appimage --release
|
||||
|
||||
cd target/
|
||||
rm -rf ./appimage
|
||||
mkdir -p appimage
|
||||
cd appimage/
|
||||
|
||||
wget https://github.com/linuxdeploy/linuxdeploy/releases/download/continuous/linuxdeploy-x86_64.AppImage
|
||||
chmod +x linuxdeploy-x86_64.AppImage
|
||||
# Build AppDir
|
||||
mkdir -p AppDir/usr/bin/
|
||||
mkdir -p AppDir/usr/lib/
|
||||
mkdir -p AppDir/usr/share/applications/
|
||||
mkdir -p AppDir/usr/share/icons/hicolor/scalable/apps/
|
||||
mkdir -p AppDir/usr/share/icons/hicolor/256x256/apps/
|
||||
mkdir -p AppDir/usr/share/icons/hicolor/128x128/apps/
|
||||
mkdir -p AppDir/usr/share/icons/hicolor/64x64/apps/
|
||||
mkdir -p AppDir/usr/share/icons/hicolor/32x32/apps/
|
||||
mkdir -p AppDir/usr/share/icons/hicolor/16x16/apps/
|
||||
cp ../release/gossip AppDir/usr/bin/gossip
|
||||
strip AppDir/usr/bin/gossip
|
||||
cp ../../logo/gossip.png AppDir/usr/share/icons/hicolor/128x128/apps/gossip.png
|
||||
cp ../../logo/gossip.svg AppDir/usr/share/icons/hicolor/scalable/apps/gossip.svg
|
||||
cp ../../packaging/debian/gossip.desktop AppDir/usr/share/applications/gossip.desktop
|
||||
ln -s usr/bin/gossip AppDir/AppRun
|
||||
ln -s gossip.png AppDir/.DirIcon
|
||||
ln -s usr/share/applications/gossip.desktop AppDir/gossip.desktop
|
||||
ln -s usr/share/icons/hicolor/128x128/apps/gossip.png AppDir/gossip.png
|
||||
|
||||
./linuxdeploy-x86_64.AppImage --appdir AppDir -e ../release/gossip -i ../../logo/gossip.png -d ../../packaging/debian/gossip.desktop --output=appimage
|
||||
# Get appimagetool
|
||||
wget "https://github.com/AppImage/AppImageKit/releases/download/continuous/appimagetool-x86_64.AppImage"
|
||||
chmod a+x appimagetool-x86_64.AppImage
|
||||
|
||||
echo "AppImage is at ../../target/appimage/gossip-x86_64.AppImage"
|
||||
# Use appimagetool to build the AppImage
|
||||
./appimagetool-x86_64.AppImage AppDir
|
||||
|
||||
# Bundle for portable mode
|
||||
mkdir -p gossip-x86_64.AppImage.home/.local/share
|
||||
tar cvf - gossip-x86_64.AppImage gossip-x86_64.AppImage.home | gzip -c > gossip-x86_64.AppImage.tar.gz
|
||||
|
||||
echo "Portable AppImage is at ../../target/appimage/gossip-x86_64.AppImage.tar.gz"
|
||||
|
||||
exit 0
|
||||
|
Loading…
Reference in New Issue
Block a user