Skip to content

Commit

Permalink
Add database cleanup background task
Browse files Browse the repository at this point in the history
  • Loading branch information
blissd committed Apr 7, 2024
1 parent de74855 commit 3f167f6
Show file tree
Hide file tree
Showing 4 changed files with 182 additions and 10 deletions.
14 changes: 13 additions & 1 deletion core/src/repo.rs
Original file line number Diff line number Diff line change
Expand Up @@ -142,7 +142,7 @@ impl Repository {
preview_id INTEGER PRIMARY KEY UNIQUE NOT NULL, -- pk for preview
picture_id INTEGER UNIQUE NOT NULL ON CONFLICT IGNORE, -- fk to pictures. Only one preview allowed for now.
full_path TEXT UNIQUE NOT NULL ON CONFLICT IGNORE, -- full path to preview image
FOREIGN KEY (picture_id) REFERENCES pictures (picture_id)
FOREIGN KEY (picture_id) REFERENCES pictures (picture_id) ON DELETE CASCADE
)",
];

Expand Down Expand Up @@ -324,6 +324,18 @@ impl Repository {
let head = iter.flatten().nth(0);
Ok(head)
}

pub fn remove(&mut self, picture_id: PictureId) -> Result<()> {
let mut stmt = self
.con
.prepare("DELETE FROM pictures WHERE picture_id = ?1")
.map_err(|e| RepositoryError(e.to_string()))?;

stmt.execute([picture_id.0])
.map_err(|e| RepositoryError(e.to_string()))?;

Ok(())
}
}

#[cfg(test)]
Expand Down
79 changes: 70 additions & 9 deletions src/app.rs
Original file line number Diff line number Diff line change
Expand Up @@ -57,17 +57,16 @@ use self::components::{
mod background;

use self::background::{
scan_photos::ScanPhotos,
scan_photos::ScanPhotosInput,
scan_photos::ScanPhotosOutput,
generate_previews::GeneratePreviews,
generate_previews::GeneratePreviewsInput,
generate_previews::GeneratePreviewsOutput,
scan_photos::{ScanPhotos, ScanPhotosInput, ScanPhotosOutput},
generate_previews::{GeneratePreviews, GeneratePreviewsInput, GeneratePreviewsOutput},
cleanup::{Cleanup, CleanupInput, CleanupOutput},
};

pub(super) struct App {
scan_photos: WorkerController<ScanPhotos>,
generate_previews: WorkerController<GeneratePreviews>,
cleanup: WorkerController<Cleanup>,

about_dialog: Controller<AboutDialog>,
all_photos: AsyncController<Album>,
month_photos: AsyncController<MonthPhotos>,
Expand Down Expand Up @@ -149,6 +148,11 @@ pub(super) enum AppMsg {
ThumbnailGenerationStarted(usize),
ThumbnailGenerated,
ThumbnailGenerationCompleted,

// Cleanup events
CleanupStarted(usize),
CleanupCleaned,
CleanupCompleted,
}

relm4::new_action_group!(pub(super) WindowActionGroup, "win");
Expand Down Expand Up @@ -429,6 +433,15 @@ impl SimpleComponent for App {
GeneratePreviewsOutput::Completed => AppMsg::ThumbnailGenerationCompleted,
});


let cleanup = Cleanup::builder()
.detach_worker(repo.clone())
.forward(sender.input_sender(), |msg| match msg {
CleanupOutput::Started(count) => AppMsg::CleanupStarted(count),
CleanupOutput::Cleaned => AppMsg::CleanupCleaned,
CleanupOutput::Completed => AppMsg::CleanupCompleted,
});

let all_photos = Album::builder()
.launch((repo.clone(), AlbumFilter::All))
.forward(sender.input_sender(), |msg| match msg {
Expand Down Expand Up @@ -502,6 +515,7 @@ impl SimpleComponent for App {
let model = Self {
scan_photos,
generate_previews,
cleanup,
about_dialog,
all_photos,
month_photos,
Expand Down Expand Up @@ -550,6 +564,7 @@ impl SimpleComponent for App {
model.all_photos.emit(AlbumInput::Refresh);

model.scan_photos.sender().emit(ScanPhotosInput::Start);

// model.selfie_photos.emit(SelfiePhotosInput::Refresh);
// model.month_photos.emit(MonthPhotosInput::Refresh);
// model.year_photos.emit(YearPhotosInput::Refresh);
Expand Down Expand Up @@ -633,7 +648,8 @@ impl SimpleComponent for App {
self.month_photos.emit(MonthPhotosInput::Refresh);
self.year_photos.emit(YearPhotosInput::Refresh);

self.generate_previews.emit(GeneratePreviewsInput::Start);
//self.generate_previews.emit(GeneratePreviewsInput::Start);
self.cleanup.emit(CleanupInput::Start);
},
AppMsg::ThumbnailGenerationStarted(count) => {
println!("Thumbnail generation started.");
Expand All @@ -658,7 +674,7 @@ impl SimpleComponent for App {
AppMsg::ThumbnailGenerated => {
println!("Thumbnail generated.");
self.progress_current_count += 1;
// Show pulsing for first 20 thumbnails so that it catches the eye, then
// Show pulsing for first 20 items so that it catches the eye, then
// switch to fractional view
if self.progress_current_count < 20 {
self.progress_bar.pulse();
Expand All @@ -670,7 +686,52 @@ impl SimpleComponent for App {
self.progress_bar.set_fraction(fraction);
}
},
AppMsg::ThumbnailGenerationCompleted => {
AppMsg::CleanupCompleted => {
println!("Cleanup completed.");
self.spinner.stop();
self.banner.set_revealed(false);
self.banner.set_button_label(None);
self.progress_box.set_visible(false);

self.generate_previews.emit(GeneratePreviewsInput::Start);
},

AppMsg::CleanupStarted(count) => {
println!("Cleanup started.");
self.banner.set_title("Database maintenance.");
// Show button to refresh all photo grids.
//self.banner.set_button_label(Some("Refresh"));
self.banner.set_revealed(true);

self.spinner.start();

let show = self.main_navigation.shows_sidebar();
self.spinner.set_visible(!show);

self.progress_end_count = count;
self.progress_current_count = 0;

self.progress_box.set_visible(true);
self.progress_bar.set_fraction(0.0);
self.progress_bar.set_text(Some("Database maintenance"));
self.progress_bar.set_pulse_step(0.25);
},
AppMsg::CleanupCleaned => {
println!("Cleanup cleaned.");
self.progress_current_count += 1;
// Show pulsing for first 1000 items so that it catches the eye, then
// switch to fractional view
if self.progress_current_count < 1000 {
self.progress_bar.pulse();
} else {
if self.progress_current_count == 1000 {
self.progress_bar.set_text(None);
}
let fraction = self.progress_current_count as f64 / self.progress_end_count as f64;
self.progress_bar.set_fraction(fraction);
}
},
AppMsg::ThumbnailGenerationCompleted => {
println!("Thumbnail generation completed.");
self.spinner.stop();
self.banner.set_revealed(false);
Expand Down
98 changes: 98 additions & 0 deletions src/app/background/cleanup.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,98 @@
// SPDX-FileCopyrightText: © 2024 David Bliss
//
// SPDX-License-Identifier: GPL-3.0-or-later

use relm4::prelude::*;
use relm4::Worker;
use std::sync::{Arc, Mutex};
use photos_core::Result;
use rayon::prelude::*;

#[derive(Debug)]
pub enum CleanupInput {
Start,
}

#[derive(Debug)]
pub enum CleanupOutput {
// Thumbnail generation has started for a given number of images.
Started(usize),

// Thumbnail has been generated for a photo.
Cleaned,

// Thumbnail generation has completed
Completed,

}

pub struct Cleanup {
// Danger! Don't hold the repo mutex for too long as it blocks viewing images.
repo: Arc<Mutex<photos_core::Repository>>,
}

impl Cleanup {

fn cleanup(&self, sender: &ComponentSender<Self>) -> Result<()> {

let start = std::time::Instant::now();

// Scrub pics from database if they no longer exist on the file system.
let pics: Vec<photos_core::repo::Picture> = self.repo
.lock()
.unwrap()
.all()?;

let pics_count = pics.len();

if let Err(e) = sender.output(CleanupOutput::Started(pics_count)){
println!("Failed sending cleanup started: {:?}", e);
}

pics.par_iter()
.for_each(|pic| {
if !pic.path.exists() {
let result = self.repo.lock().unwrap().remove(pic.picture_id);
if let Err(e) = result {
println!("Failed remove {}: {:?}", pic.picture_id, e);
} else {
println!("Removed {}", pic.picture_id);
}
}

if let Err(e) = sender.output(CleanupOutput::Cleaned) {
println!("Failed sending CleanupOutput::Cleaned: {:?}", e);
}
});

println!("Cleaned {} photos in {} seconds.", pics_count, start.elapsed().as_secs());

if let Err(e) = sender.output(CleanupOutput::Completed) {
println!("Failed sending CleanupOutput::Completed: {:?}", e);
}

Ok(())
}
}

impl Worker for Cleanup {
type Init = Arc<Mutex<photos_core::Repository>>;
type Input = CleanupInput;
type Output = CleanupOutput;

fn init(repo: Self::Init, _sender: ComponentSender<Self>) -> Self {
Self { repo }
}

fn update(&mut self, msg: Self::Input, sender: ComponentSender<Self>) {
match msg {
CleanupInput::Start => {
println!("Cleanup...");

if let Err(e) = self.cleanup(&sender) {
println!("Failed to update previews: {}", e);
}
}
};
}
}
1 change: 1 addition & 0 deletions src/app/background/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -4,3 +4,4 @@

pub mod scan_photos;
pub mod generate_previews;
pub mod cleanup;

0 comments on commit 3f167f6

Please sign in to comment.