This commit is contained in:
Mike Dilger 2023-01-23 10:27:46 +13:00
parent 35dde666f4
commit 6d89afdf44
9 changed files with 161 additions and 21 deletions

View File

@ -103,13 +103,16 @@ macro_rules! apply_sql {
}
fn upgrade(db: &Connection, mut version: u16) -> Result<(), Error> {
let current_version = 15;
let current_version = 16;
if version > current_version {
panic!(
"Database version {} is newer than this binary which expects version {}.",
version, current_version
);
}
// note to developers: we cannot make this into a loop because include_str! included
// by apply_sql! requires a static string, not a dynamically formatted one.
apply_sql!(db, version, 1, "schema1.sql");
apply_sql!(db, version, 2, "schema2.sql");
apply_sql!(db, version, 3, "schema3.sql");
@ -125,6 +128,7 @@ fn upgrade(db: &Connection, mut version: u16) -> Result<(), Error> {
apply_sql!(db, version, 13, "schema13.sql");
apply_sql!(db, version, 14, "schema14.sql");
apply_sql!(db, version, 15, "schema15.sql");
apply_sql!(db, version, 16, "schema16.sql");
tracing::info!("Database is at version {}", version);
Ok(())
}

2
src/db/schema16.sql Normal file
View File

@ -0,0 +1,2 @@
ALTER TABLE person ADD COLUMN
muted INTEGER DEFAULT 0;

View File

@ -740,6 +740,7 @@ impl Overlord {
}
// Add all the 'p' tags from the note we are replying to (except our own)
// FIXME: Should we avoid taging people who are muted?
for tag in &event.tags {
if let Tag::Pubkey { pubkey, .. } = tag {
if pubkey.0 != public_key.as_hex_string() {

View File

@ -23,6 +23,7 @@ pub struct DbPerson {
pub nip05_last_checked: Option<u64>,
pub followed: u8,
pub followed_last_updated: i64,
pub muted: u8,
}
impl DbPerson {
@ -35,6 +36,7 @@ impl DbPerson {
nip05_last_checked: None,
followed: 0,
followed_last_updated: 0,
muted: 0,
}
}
@ -263,8 +265,11 @@ impl People {
));
}
// NOTE: We also load all muted people, so that we can render the list of people
// who are muted, so they can be found and unmuted as necessary.
let sql = "SELECT pubkey, metadata, metadata_at, nip05_valid, nip05_last_checked, \
followed, followed_last_updated FROM person WHERE followed=1"
followed, followed_last_updated, muted FROM person WHERE followed=1 OR muted=1"
.to_owned();
let output: Result<Vec<DbPerson>, Error> = task::spawn_blocking(move || {
@ -288,6 +293,7 @@ impl People {
nip05_last_checked: row.get(4)?,
followed: row.get(5)?,
followed_last_updated: row.get(6)?,
muted: row.get(7)?,
});
}
Ok(output)
@ -560,7 +566,7 @@ impl People {
}
pub async fn async_follow(&self, pubkeyhex: &PublicKeyHex, follow: bool) -> Result<(), Error> {
let f: u8 = u8::from(follow);
let follow: u8 = u8::from(follow);
// Follow in database
let sql = "INSERT INTO PERSON (pubkey, followed) values (?, ?) \
@ -570,14 +576,14 @@ impl People {
let maybe_db = GLOBALS.db.blocking_lock();
let db = maybe_db.as_ref().unwrap();
let mut stmt = db.prepare(sql)?;
stmt.execute((&pubkeyhex2.0, &f, &f))?;
stmt.execute((&pubkeyhex2.0, &follow, &follow))?;
Ok::<(), Error>(())
})
.await??;
// Make sure memory matches
if let Some(mut dbperson) = self.people.get_mut(pubkeyhex) {
dbperson.followed = f;
dbperson.followed = follow;
} else {
// load
if let Some(person) = Self::fetch_one(pubkeyhex).await? {
@ -605,7 +611,8 @@ impl People {
// Follow in database
let sql = format!(
"UPDATE person SET followed=1, followed_last_updated=? WHERE pubkey IN ({}) and followed_last_updated<?",
"UPDATE person SET followed=1, followed_last_updated=? \
WHERE pubkey IN ({}) and followed_last_updated<?",
repeat_vars(pubkeys.len())
);
@ -630,7 +637,8 @@ impl People {
if !merge {
// Unfollow in database
let sql = format!(
"UPDATE person SET followed=0, followed_last_updated=? WHERE pubkey NOT IN ({}) and followed_last_updated<?",
"UPDATE person SET followed=0, followed_last_updated=? \
WHERE pubkey NOT IN ({}) and followed_last_updated<?",
repeat_vars(pubkeys.len())
);
@ -669,6 +677,45 @@ impl People {
Ok(())
}
pub fn mute(&self, pubkeyhex: &PublicKeyHex, mute: bool) {
// We can't do it now, but we spawn a task to do it soon
let pubkeyhex = pubkeyhex.to_owned();
tokio::spawn(async move {
if let Err(e) = GLOBALS.people.async_mute(&pubkeyhex, mute).await {
tracing::error!("{}", e);
}
});
}
pub async fn async_mute(&self, pubkeyhex: &PublicKeyHex, mute: bool) -> Result<(), Error> {
let mute: u8 = u8::from(mute);
// Mute in database
let sql = "INSERT INTO PERSON (pubkey, muted) values (?, ?) \
ON CONFLICT(pubkey) DO UPDATE SET muted=?";
let pubkeyhex2 = pubkeyhex.to_owned();
task::spawn_blocking(move || {
let maybe_db = GLOBALS.db.blocking_lock();
let db = maybe_db.as_ref().unwrap();
let mut stmt = db.prepare(sql)?;
stmt.execute((&pubkeyhex2.0, &mute, &mute))?;
Ok::<(), Error>(())
})
.await??;
// Make sure memory matches
if let Some(mut dbperson) = self.people.get_mut(pubkeyhex) {
dbperson.muted = mute;
} else {
// load
if let Some(person) = Self::fetch_one(pubkeyhex).await? {
self.people.insert(pubkeyhex.to_owned(), person);
}
}
Ok(())
}
pub async fn update_nip05_last_checked(&self, pubkeyhex: PublicKeyHex) -> Result<(), Error> {
let maybe_db = GLOBALS.db.lock().await;
let db = maybe_db.as_ref().unwrap();
@ -735,7 +782,7 @@ impl People {
async fn fetch(criteria: Option<&str>) -> Result<Vec<DbPerson>, Error> {
let sql = "SELECT pubkey, metadata, metadata_at, \
nip05_valid, nip05_last_checked, \
followed, followed_last_updated FROM person"
followed, followed_last_updated, muted FROM person"
.to_owned();
let sql = match criteria {
None => sql,
@ -763,6 +810,7 @@ impl People {
nip05_last_checked: row.get(4)?,
followed: row.get(5)?,
followed_last_updated: row.get(6)?,
muted: row.get(7)?,
});
}
Ok(output)
@ -786,7 +834,7 @@ impl People {
let sql = format!(
"SELECT pubkey, metadata, metadata_at, \
nip05_valid, nip05_last_checked, \
followed, followed_last_updated \
followed, followed_last_updated, muted \
FROM person WHERE pubkey IN ({})",
repeat_vars(pubkeys.len())
);
@ -821,6 +869,7 @@ impl People {
nip05_last_checked: row.get(4)?,
followed: row.get(5)?,
followed_last_updated: row.get(6)?,
muted: row.get(7)?,
});
}
@ -834,8 +883,8 @@ impl People {
/*
async fn insert(person: DbPerson) -> Result<(), Error> {
let sql = "INSERT OR IGNORE INTO person (pubkey, metadata, metadata_at, \
nip05_valid, nip05_last_checked, followed, followed_last_updated) \
VALUES (?1, ?2, ?3, ?4, ?5, ?6, ?7)";
nip05_valid, nip05_last_checked, followed, followed_last_updated, muted) \
VALUES (?1, ?2, ?3, ?4, ?5, ?6, ?7, ?8)";
task::spawn_blocking(move || {
let maybe_db = GLOBALS.db.blocking_lock();
@ -856,6 +905,7 @@ impl People {
&person.nip05_last_checked,
&person.followed,
&person.followed_last_updated,
&person.muted,
))?;
Ok::<(), Error>(())
})

View File

@ -273,6 +273,13 @@ fn render_post_maybe_fake(
}
let event = maybe_event.unwrap();
// Do not render muted people, not even fake-renders offscreen
if let Some(person) = GLOBALS.people.get(&event.pubkey.into()) {
if person.muted > 0 {
return;
}
};
let screen_rect = ctx.input().screen_rect; // Rect
let pos2 = ui.next_widget_position();
@ -503,6 +510,14 @@ fn render_post_actual(
if ui.button("Dismiss").clicked() {
GLOBALS.dismissed.blocking_write().push(event.id);
}
if ui.button("Mute").clicked() {
GLOBALS.people.mute(&event.pubkey.into(), true);
}
if person.followed == 0 && ui.button("Follow").clicked() {
GLOBALS.people.follow(&event.pubkey.into(), true);
} else if ui.button("Unfollow").clicked() {
GLOBALS.people.follow(&event.pubkey.into(), false);
}
if ui.button("Update Metadata").clicked() {
let _ = GLOBALS
.to_overlord

View File

@ -59,6 +59,7 @@ enum Page {
Feed(FeedKind),
PeopleList,
PeopleFollow,
PeopleMuted,
Person(PublicKeyHex),
YourKeys,
YourMetadata,
@ -284,6 +285,7 @@ impl eframe::App for GossipUi {
.add(SelectableLabel::new(
self.page == Page::PeopleList
|| self.page == Page::PeopleFollow
|| self.page == Page::PeopleMuted
|| matches!(self.page, Page::Person(_)),
"People",
))
@ -350,7 +352,7 @@ impl eframe::App for GossipUi {
egui::CentralPanel::default().show(ctx, |ui| match self.page {
Page::Feed(_) => feed::update(self, ctx, frame, ui),
Page::PeopleList | Page::PeopleFollow | Page::Person(_) => {
Page::PeopleList | Page::PeopleFollow | Page::PeopleMuted | Page::Person(_) => {
people::update(self, ctx, frame, ui)
}
Page::YourKeys | Page::YourMetadata => you::update(self, ctx, frame, ui),

View File

@ -8,6 +8,7 @@ use egui::{Context, Image, RichText, ScrollArea, Sense, Ui, Vec2};
use std::sync::atomic::Ordering;
mod follow;
mod muted;
mod person;
pub(super) fn update(app: &mut GossipUi, ctx: &Context, _frame: &mut eframe::Frame, ui: &mut Ui) {
@ -21,6 +22,8 @@ pub(super) fn update(app: &mut GossipUi, ctx: &Context, _frame: &mut eframe::Fra
ui.separator();
ui.selectable_value(&mut app.page, Page::PeopleFollow, "Follow Someone New");
ui.separator();
ui.selectable_value(&mut app.page, Page::PeopleMuted, "Muted");
ui.separator();
if let Some(person) = &maybe_person {
ui.selectable_value(
&mut app.page,
@ -106,6 +109,8 @@ pub(super) fn update(app: &mut GossipUi, ctx: &Context, _frame: &mut eframe::Fra
});
} else if app.page == Page::PeopleFollow {
follow::update(app, ctx, _frame, ui);
} else if app.page == Page::PeopleMuted {
muted::update(app, ctx, _frame, ui);
} else if matches!(app.page, Page::Person(_)) {
person::update(app, ctx, _frame, ui);
}

60
src/ui/people/muted.rs Normal file
View File

@ -0,0 +1,60 @@
use super::{GossipUi, Page};
use crate::globals::GLOBALS;
use crate::people::DbPerson;
use crate::AVATAR_SIZE_F32;
use eframe::egui;
use egui::{Context, Image, RichText, ScrollArea, Sense, Ui, Vec2};
use std::sync::atomic::Ordering;
pub(super) fn update(app: &mut GossipUi, ctx: &Context, _frame: &mut eframe::Frame, ui: &mut Ui) {
ui.add_space(30.0);
let people: Vec<DbPerson> = GLOBALS
.people
.get_all()
.drain(..)
.filter(|p| p.muted == 1)
.collect();
ui.heading(format!("People who are Muted ({})", people.len()));
ui.add_space(10.0);
ScrollArea::vertical().show(ui, |ui| {
for person in people.iter() {
if person.muted != 1 {
continue;
}
ui.horizontal(|ui| {
// Avatar first
let avatar = if let Some(avatar) = app.try_get_avatar(ctx, &person.pubkey) {
avatar
} else {
app.placeholder_avatar.clone()
};
let size = AVATAR_SIZE_F32
* GLOBALS.pixels_per_point_times_100.load(Ordering::Relaxed) as f32
/ 100.0;
if ui
.add(Image::new(&avatar, Vec2 { x: size, y: size }).sense(Sense::click()))
.clicked()
{
app.set_page(Page::Person(person.pubkey.clone()));
};
ui.vertical(|ui| {
ui.label(RichText::new(GossipUi::hex_pubkey_short(&person.pubkey)).weak());
GossipUi::render_person_name_line(ui, person);
if ui.button("UNMUTE").clicked() {
GLOBALS.people.mute(&person.pubkey, false);
}
});
});
ui.add_space(4.0);
ui.separator();
}
});
}

View File

@ -60,15 +60,16 @@ fn content(
ui.label(RichText::new(GossipUi::hex_pubkey_short(&pubkeyhex)).weak());
GossipUi::render_person_name_line(ui, &person);
#[allow(clippy::collapsible_else_if)]
if person.followed == 0 {
if ui.button("FOLLOW").clicked() {
GLOBALS.people.follow(&pubkeyhex, true);
}
} else {
if ui.button("UNFOLLOW").clicked() {
GLOBALS.people.follow(&pubkeyhex, false);
}
if person.followed == 0 && ui.button("FOLLOW").clicked() {
GLOBALS.people.follow(&pubkeyhex, true);
} else if person.followed == 1 && ui.button("UNFOLLOW").clicked() {
GLOBALS.people.follow(&pubkeyhex, false);
}
if person.muted == 0 && ui.button("MUTE").clicked() {
GLOBALS.people.mute(&pubkeyhex, true);
} else if person.muted == 1 && ui.button("UNMUTE").clicked() {
GLOBALS.people.mute(&pubkeyhex, false);
}
if ui.button("UPDATE METADATA").clicked() {