Add ability to prune unused people

This commit is contained in:
Mike Dilger 2024-09-19 09:31:54 +12:00
parent 6a63451603
commit 4b847b48e9
6 changed files with 166 additions and 6 deletions

View File

@ -1500,6 +1500,12 @@ impl eframe::App for GossipUi {
return wait_for_data_migration(self, ctx);
}
// If database is being pruned, show that screen
let optstatus = GLOBALS.prune_status.read();
if let Some(status) = optstatus.as_ref() {
return wait_for_prune(self, ctx, status);
}
// Wizard does its own panels
if let Page::Wizard(wp) = self.page {
return wizard::update(self, ctx, frame, wp);
@ -2435,3 +2441,20 @@ fn wait_for_data_migration(app: &mut GossipUi, ctx: &Context) {
});
});
}
fn wait_for_prune(app: &mut GossipUi, ctx: &Context, status: &str) {
egui::CentralPanel::default()
.frame({
let frame = egui::Frame::central_panel(&app.theme.get_style());
frame.inner_margin(egui::Margin {
left: 20.0,
right: 10.0,
top: 10.0,
bottom: 0.0,
})
})
.show(ctx, |ui| {
ui.heading("Please wait for the database prune to complete...");
ui.label(status);
});
}

View File

@ -12,13 +12,13 @@ pub(super) fn update(app: &mut GossipUi, _ctx: &Context, _frame: &mut eframe::Fr
ui.add_space(20.0);
ui.horizontal(|ui| {
ui.label("How long to keep events")
ui.label("When pruning events (below), How long to keep events")
.on_hover_text("Events older than this will be deleted");
ui.add(Slider::new(&mut app.unsaved_settings.prune_period_days, 7..=720).text("days"));
});
ui.horizontal(|ui| {
ui.label("How long to keep downloaded files")
ui.label("When pruning cache (below), How long to keep downloaded files")
.on_hover_text("Cached files older than this will be deleted");
ui.add(
Slider::new(&mut app.unsaved_settings.cache_prune_period_days, 7..=720).text("days"),
@ -29,10 +29,15 @@ pub(super) fn update(app: &mut GossipUi, _ctx: &Context, _frame: &mut eframe::Fr
let stored_settings = UnsavedSettings::load();
if stored_settings == app.unsaved_settings {
ui.add_space(20.0);
if ui.button("Delete Old Events Now").on_hover_text("This will delete events older than the period specified above. but the LMDB files will continue consuming disk space. To compact them, copy withem with `mdb_copy -c` when gossip is not running.").clicked() {
if ui.button("Delete Old Events Now").on_hover_text("This will delete events older than the period specified above. but the LMDB files will continue consuming disk space. To compact them, copy withem with `mdb_copy -c` when gossip is not running (see doc/DATABASE_MAINTENANCE.md).").clicked() {
let _ = GLOBALS.to_overlord.send(ToOverlordMessage::PruneOldEvents);
}
ui.add_space(20.0);
if ui.button("Delete Unused People Now").on_hover_text("This will delete people without useful events and otherwise not referenced, but the LMDB files will continue consuming disk space. To compact them, copy withem with `mdb_copy -c` when gossip is not running (see doc/DATABASE_MAINTENANCE.md).").clicked() {
let _ = GLOBALS.to_overlord.send(ToOverlordMessage::PruneUnusedPeople);
}
ui.add_space(20.0);
if ui.button("Delete Old Downloaded Files").on_hover_text("This will delete cache files with modification times older than the period specified above (unfortunately access times are often unavailable and/or unreliable). Note that this will eventually delete everybody's avatar, even if those are in heavy use.").clicked() {
let _ = GLOBALS.to_overlord.send(ToOverlordMessage::PruneCache);

View File

@ -139,6 +139,9 @@ pub enum ToOverlordMessage {
/// Calls [prune_old_events](crate::Overlord::prune_old_events)
PruneOldEvents,
/// Calls [prune_unused_people](crate::Overlord::prune_unused_people)
PruneUnusedPeople,
/// Calls [push_person_list](crate::Overlord::push_person_list)
PushPersonList(PersonList),

View File

@ -177,6 +177,9 @@ pub struct Globals {
/// Current bookmarks, resolved into a Vec<Id> (updated by tasks)
pub current_bookmarks: PRwLock<Vec<Id>>,
pub recompute_current_bookmarks: Arc<Notify>,
/// If we are doing a long database prune, this will indicate the status
pub prune_status: PRwLock<Option<String>>,
}
lazy_static! {
@ -248,6 +251,7 @@ lazy_static! {
bookmarks: Arc::new(PRwLock::new(BookmarkList::empty())),
current_bookmarks: PRwLock::new(Vec::new()),
recompute_current_bookmarks: Arc::new(Notify::new()),
prune_status: PRwLock::new(None),
}
};
}

View File

@ -708,6 +708,9 @@ impl Overlord {
ToOverlordMessage::PruneOldEvents => {
Self::prune_old_events()?;
}
ToOverlordMessage::PruneUnusedPeople => {
Self::prune_unused_people()?;
}
ToOverlordMessage::PushPersonList(person_list) => {
self.push_person_list(person_list).await?;
}
@ -1878,7 +1881,7 @@ impl Overlord {
GLOBALS
.status_queue
.write()
.write("Pruning database, please be patient..".to_owned());
.write("Pruning old events, please be patient..".to_owned());
let now = Unixtime::now();
let then = now
@ -1896,6 +1899,44 @@ impl Overlord {
Ok(())
}
/// Prune unused people
pub fn prune_unused_people() -> Result<(), Error> {
// Go offline
let mut need_to_go_back_online: bool = false;
if !GLOBALS.db().read_setting_offline() {
need_to_go_back_online = true;
GLOBALS.db().write_setting_offline(&true, None)?;
let _ = GLOBALS.write_runstate.send(RunState::Offline);
}
*GLOBALS.prune_status.write() = Some("pruning...".to_owned());
match GLOBALS.db().prune_unused_people() {
Ok(count) => {
GLOBALS.status_queue.write().write(format!(
"Database has been pruned. {} people removed.",
count
));
}
Err(e) => {
GLOBALS
.status_queue
.write()
.write(format!("Database prune error: {e}"));
}
}
*GLOBALS.prune_status.write() = None;
// Go online
if need_to_go_back_online {
GLOBALS.db().write_setting_offline(&false, None)?;
let _ = GLOBALS.write_runstate.send(RunState::Online);
}
Ok(())
}
/// Publish the user's specified PersonList
pub async fn push_person_list(&mut self, list: PersonList) -> Result<(), Error> {
let metadata = match GLOBALS.db().get_person_list_metadata(list)? {

View File

@ -1,6 +1,8 @@
use super::Storage;
use super::table::Table;
use super::{PersonTable, Storage};
use crate::error::Error;
use nostr_types::{Event, Id, Unixtime};
use crate::globals::GLOBALS;
use nostr_types::{Event, Filter, Id, Unixtime};
use std::collections::HashSet;
impl Storage {
@ -102,4 +104,86 @@ impl Storage {
Ok(ids.len())
}
/// Prune people that are not used:
/// * No feed related events
/// * less than 6 events
/// * not in any lists
/// * not petnamed
/// * no valid nip05,
///
/// Returns number of people deleted
pub fn prune_unused_people<'a>(&'a self) -> Result<usize, Error> {
let mut txn = self.get_write_txn()?;
let ekinds = crate::enabled_event_kinds();
let frkinds = crate::feed_related_event_kinds(true);
let mut filter = Filter::new();
filter.kinds = ekinds;
filter.limit = Some(6);
let mut count = 0;
let loop_txn = self.env.read_txn()?;
for person in PersonTable::iter(&loop_txn)? {
// Keep if they are in a person list
if !self.read_person_lists(&person.pubkey)?.is_empty() {
continue;
}
// Keep if they have a petname
if person.petname.is_some() {
continue;
}
// Keep if they have a valid nip-05
if person.nip05_valid {
continue;
}
// Load up to 6 of their events
filter.authors = vec![person.pubkey.into()];
let events = match self.find_events_by_filter(&filter, |_| true) {
Ok(events) => events,
Err(_) => continue, // some error we can't handle right now
};
// Keep people with at least 6 events
if events.len() >= 6 {
continue;
}
// Keep if any of their events is feed related
if events.iter().any(|e| frkinds.contains(&e.kind)) {
continue;
}
count += 1;
*GLOBALS.prune_status.write() = Some(
person
.pubkey
.as_hex_string()
.get(0..10)
.unwrap_or("?")
.to_owned(),
);
// Delete their events
for event in &events {
self.delete_event(event.id, Some(&mut txn))?;
}
// Delete their person-relay records
self.delete_person_relays(|pr| pr.pubkey == person.pubkey, Some(&mut txn))?;
// Delete their person record
PersonTable::delete_record(person.pubkey, Some(&mut txn))?;
}
tracing::info!("PRUNE: deleted {} records from people", count);
txn.commit()?;
Ok(count)
}
}