Commit cfbb1326 authored by Jim Nelson's avatar Jim Nelson

#2030: Progress bar now updated as mimic manager generates mimics. Also...

#2030: Progress bar now updated as mimic manager generates mimics.  Also implemented a priority system so the various background tasks can share the progress bar.  (Worth discussing what the priorities should be.)
parent 7bcb22bd
......@@ -240,6 +240,12 @@ public class LibraryMonitor : DirectoryMonitor {
private uint pending_updates_timer_id = 0;
private uint import_queue_timer_id = 0;
public signal void auto_update_progress(int completed_files, int total_files);
public signal void auto_import_preparing();
public signal void auto_import_progress(uint64 completed_bytes, uint64 total_bytes);
public LibraryMonitor(File root, bool recurse, bool monitoring) {
base (root, recurse, monitoring);
......@@ -398,8 +404,7 @@ public class LibraryMonitor : DirectoryMonitor {
discovery_stage_completed();
} else {
mdbg("%d checksum jobs initiated to verify unknown photo files".printf(checksums_total));
LibraryWindow.update_background_progress_bar(_("Updating library..."),
checksums_completed, checksums_total);
auto_update_progress(checksums_completed, checksums_total);
}
}
......@@ -407,13 +412,10 @@ public class LibraryMonitor : DirectoryMonitor {
assert(checksums_completed < checksums_total);
checksums_completed++;
LibraryWindow.update_background_progress_bar(_("Updating library..."),
checksums_completed, checksums_total);
auto_update_progress(checksums_completed, checksums_total);
if (checksums_completed == checksums_total) {
LibraryWindow.clear_background_progress_bar();
if (checksums_completed == checksums_total)
discovery_stage_completed();
}
}
private void on_find_move_completed(BackgroundJob j) {
......@@ -1165,6 +1167,7 @@ public class LibraryMonitor : DirectoryMonitor {
return;
current_batch_import = batch_import_queue[0];
current_batch_import.preparing.connect(on_import_preparing);
current_batch_import.progress.connect(on_import_progress);
current_batch_import.import_complete.connect(on_import_complete);
current_batch_import.schedule();
......@@ -1175,6 +1178,7 @@ public class LibraryMonitor : DirectoryMonitor {
bool removed = batch_import_queue.remove(current_batch_import);
assert(removed);
current_batch_import.preparing.disconnect(on_import_preparing);
current_batch_import.progress.disconnect(on_import_progress);
current_batch_import.import_complete.disconnect(on_import_complete);
current_batch_import = null;
......@@ -1248,9 +1252,12 @@ public class LibraryMonitor : DirectoryMonitor {
assert(removed);
}
private void on_import_preparing() {
auto_import_preparing();
}
private void on_import_progress(uint64 completed_bytes, uint64 total_bytes) {
LibraryWindow.update_background_progress_bar(_("Auto-importing..."),
completed_bytes, total_bytes);
auto_import_progress(completed_bytes, total_bytes);
}
private void on_import_complete(BatchImport batch_import, ImportManifest manifest,
......@@ -1258,8 +1265,7 @@ public class LibraryMonitor : DirectoryMonitor {
assert(batch_import == current_batch_import);
mdbg("auto-import batch completed %d".printf(manifest.all.size));
LibraryWindow.clear_background_progress_bar();
auto_import_progress(0, 0);
foreach (BatchImportResult result in manifest.all) {
if (result.file != null)
......
......@@ -18,7 +18,14 @@ public class LibraryWindow : AppWindow {
"disk:",
"file:"
};
// these values reflect the priority various background operations have when reporting
// progress to the LibraryWindow progress bar ... higher values give priority to those reports
private const int REALTIME_UPDATE_PROGRESS_PRIORITY = 40;
private const int REALTIME_IMPORT_PROGRESS_PRIORITY = 50;
private const int METADATA_WRITER_PROGRESS_PRIORITY = 30;
private const int MIMIC_MANAGER_PROGRESS_PRIORITY = 20;
protected enum TargetType {
URI_LIST,
PHOTO_LIST
......@@ -29,6 +36,10 @@ public class LibraryWindow : AppWindow {
{ "shotwell/photo-id", Gtk.TargetFlags.SAME_APP, TargetType.PHOTO_LIST }
};
// special Yorba-selected sidebar background color for standard themes (humanity,
// clearlooks, etc.); dark themes use the theme's native background color
public const uint16 STANDARD_COMPONENT_MINIMUM = 0xf00;
// In fullscreen mode, want to use LibraryPhotoPage, but fullscreen has different requirements,
// esp. regarding when the widget is realized and when it should first try and throw them image
// on the page. This handles this without introducing lots of special cases in
......@@ -83,7 +94,6 @@ public class LibraryWindow : AppWindow {
// special Yorba-selected sidebar background color for standard themes (humanity,
// clearlooks, etc.); dark themes use the theme's native background color
public static Gdk.Color SIDEBAR_STANDARD_BG_COLOR = parse_color("#EEE");
public const uint16 STANDARD_COMPONENT_MINIMUM = 0xf00;
private string import_dir = Environment.get_home_dir();
......@@ -139,6 +149,7 @@ public class LibraryWindow : AppWindow {
private Gtk.Box layout = new Gtk.VBox(false, 0);
private bool events_sort_ascending = false;
private int current_progress_priority = 0;
public LibraryWindow(ProgressMonitor monitor) {
// prepare the default parent and orphan pages
......@@ -242,6 +253,10 @@ public class LibraryWindow : AppWindow {
sync_videos_visibility();
MetadataWriter.get_instance().progress.connect(on_metadata_writer_progress);
LibraryPhoto.library_monitor.auto_update_progress.connect(on_library_monitor_auto_update_progress);
LibraryPhoto.library_monitor.auto_import_preparing.connect(on_library_monitor_auto_import_preparing);
LibraryPhoto.library_monitor.auto_import_progress.connect(on_library_monitor_auto_import_progress);
LibraryPhoto.mimic_manager.progress.connect(on_mimic_manager_progress);
}
~LibraryWindow() {
......@@ -266,6 +281,10 @@ public class LibraryWindow : AppWindow {
Video.global.trashcan_contents_altered.disconnect(on_trashcan_contents_altered);
MetadataWriter.get_instance().progress.disconnect(on_metadata_writer_progress);
LibraryPhoto.library_monitor.auto_update_progress.disconnect(on_library_monitor_auto_update_progress);
LibraryPhoto.library_monitor.auto_import_preparing.disconnect(on_library_monitor_auto_import_preparing);
LibraryPhoto.library_monitor.auto_import_progress.disconnect(on_library_monitor_auto_import_progress);
LibraryPhoto.mimic_manager.progress.disconnect(on_mimic_manager_progress);
}
private Gtk.ActionEntry[] create_actions() {
......@@ -1426,29 +1445,42 @@ public class LibraryWindow : AppWindow {
sort_events_action.set_active(events_sort_ascending);
}
public static void update_background_progress_bar(string label, double count, double total) {
if (instance == null)
private void pulse_background_progress_bar(string label, int priority) {
if (priority < current_progress_priority)
return;
current_progress_priority = priority;
background_progress_bar.set_text(label);
background_progress_bar.pulse();
show_background_progress_bar();
}
private void update_background_progress_bar(string label, int priority, double count,
double total) {
if (priority < current_progress_priority)
return;
if (total <= 0.0) {
if (count <= 0.0 || total <= 0.0 || count >= total) {
clear_background_progress_bar();
return;
}
current_progress_priority = priority;
double fraction = count / total;
get_app().background_progress_bar.set_fraction(fraction);
get_app().background_progress_bar.set_text(_("%s (%d%%)").printf(label, (int) (fraction * 100.0)));
get_app().show_background_progress_bar();
background_progress_bar.set_fraction(fraction);
background_progress_bar.set_text(_("%s (%d%%)").printf(label, (int) (fraction * 100.0)));
show_background_progress_bar();
}
public static void clear_background_progress_bar() {
if (instance == null)
return;
private void clear_background_progress_bar() {
current_progress_priority = 0;
get_app().background_progress_bar.set_fraction(0.0);
get_app().background_progress_bar.set_text("");
get_app().hide_background_progress_bar();
background_progress_bar.set_fraction(0.0);
background_progress_bar.set_text("");
hide_background_progress_bar();
}
private void show_background_progress_bar() {
......@@ -1466,11 +1498,29 @@ public class LibraryWindow : AppWindow {
}
}
private void on_library_monitor_auto_update_progress(int completed_files, int total_files) {
update_background_progress_bar(_("Updating library..."), REALTIME_UPDATE_PROGRESS_PRIORITY,
completed_files, total_files);
}
private void on_library_monitor_auto_import_preparing() {
pulse_background_progress_bar(_("Preparing to auto-import photos..."),
REALTIME_IMPORT_PROGRESS_PRIORITY);
}
private void on_library_monitor_auto_import_progress(uint64 completed_bytes, uint64 total_bytes) {
update_background_progress_bar(_("Auto-importing photos..."),
REALTIME_IMPORT_PROGRESS_PRIORITY, completed_bytes, total_bytes);
}
private void on_metadata_writer_progress(uint completed, uint total) {
if (completed >= total || completed == 0 || total == 0)
clear_background_progress_bar();
else
update_background_progress_bar(_("Writing metadata to files..."), completed, total);
update_background_progress_bar(_("Writing metadata to files..."),
METADATA_WRITER_PROGRESS_PRIORITY, completed, total);
}
private void on_mimic_manager_progress(int completed, int total) {
update_background_progress_bar(_("Processing RAW files..."),
MIMIC_MANAGER_PROGRESS_PRIORITY, completed, total);
}
private void create_layout(Page start_page) {
......
......@@ -37,7 +37,9 @@ public class MimicManager : Object {
public File file;
public DeleteJob(MimicManager manager, File file) {
base (manager);
base (manager, manager.on_delete_completed, new Cancellable());
set_completion_semaphore(new Semaphore());
this.file = file;
}
......@@ -55,8 +57,13 @@ public class MimicManager : Object {
private File impersonators_dir;
private Workers workers = new Workers(1, false);
private Gee.HashMap<Photo, VerifyJob> verify_jobs = new Gee.HashMap<Photo, VerifyJob>();
private Gee.HashSet<DeleteJob> delete_jobs = new Gee.HashSet<DeleteJob>();
private int pause_count = 0;
private Gee.ArrayList<VerifyJob> paused_list = new Gee.ArrayList<VerifyJob>();
private int completed_jobs = 0;
private int total_jobs = 0;
public signal void progress(int completed, int total);
public MimicManager(SourceCollection sources, File impersonators_dir) {
this.sources = sources;
......@@ -66,15 +73,20 @@ public class MimicManager : Object {
sources.items_added.connect(on_photos_added);
sources.item_destroyed.connect(on_photo_destroyed);
Application.get_instance().exiting.connect(on_application_exiting);
}
~MimicManager() {
sources.items_added.disconnect(on_photos_added);
sources.item_destroyed.disconnect(on_photo_destroyed);
Application.get_instance().exiting.disconnect(on_application_exiting);
}
public void pause() {
pause_count++;
if (pause_count++ == 0)
progress(0, 0);
}
public void resume() {
......@@ -89,7 +101,18 @@ public class MimicManager : Object {
paused_list.clear();
}
private void on_application_exiting(bool panicked) {
foreach (VerifyJob job in verify_jobs.values)
job.cancel();
// wait out all the delete jobs, no way to restart these properly because of the way that
// IDs may be reused after destruction
foreach (DeleteJob job in delete_jobs)
job.wait_for_completion();
}
private void enqueue_verify_job(VerifyJob job) {
total_jobs++;
verify_jobs.set(job.photo, job);
workers.enqueue(job);
}
......@@ -127,7 +150,11 @@ public class MimicManager : Object {
outstanding.cancel();
}
workers.enqueue(new DeleteJob(this, generate_impersonator_file((Photo) source)));
DeleteJob job = new DeleteJob(this, generate_impersonator_file((Photo) source));
total_jobs++;
delete_jobs.add(job);
workers.enqueue(job);
}
private void on_verify_completed(BackgroundJob background_job) {
......@@ -136,6 +163,8 @@ public class MimicManager : Object {
bool removed = verify_jobs.unset(job.photo);
assert(removed);
report_completed_job();
if (job.err != null) {
critical("Unable to generate impersonator for %s: %s", job.photo.to_string(),
job.err.message);
......@@ -146,6 +175,22 @@ public class MimicManager : Object {
job.photo.set_mimic_reader(job.writer.create_reader());
}
private void on_delete_completed(BackgroundJob background_job) {
bool removed = delete_jobs.remove((DeleteJob) background_job);
assert(removed);
report_completed_job();
}
private void report_completed_job() {
if (++completed_jobs >= total_jobs) {
completed_jobs = 0;
total_jobs = 0;
}
progress(completed_jobs, total_jobs);
}
private string generate_impersonator_filepath(Photo photo) {
return generate_impersonator_file(photo).get_path();
}
......
......@@ -25,8 +25,6 @@ public class PixbufCache : Object {
public Gdk.Pixbuf pixbuf = null;
public Error err = null;
private Semaphore completion_semaphore = new Semaphore();
public FetchJob(PixbufCache owner, BackgroundJob.JobPriority priority, Photo photo,
Scaling scaling, CompletionCallback callback) {
base(owner, callback, new Cancellable());
......@@ -35,16 +33,12 @@ public class PixbufCache : Object {
this.photo = photo;
this.scaling = scaling;
set_completion_semaphore(completion_semaphore);
set_completion_semaphore(new Semaphore());
}
public override BackgroundJob.JobPriority get_priority() {
return priority;
}
public void wait_for_completion() {
completion_semaphore.wait();
}
}
private class BaselineFetchJob : FetchJob {
......
......@@ -295,10 +295,22 @@ public abstract class BackgroundJob {
notification_priority = priority;
}
// This method is not thread-safe. Best to set a completion Semaphore before the job is
// enqueued.
// This method is thread-safe, however, because of race conditions between setting a semaphore
// and another thread waiting on it, it's best to set this before enqueuing the job.
public void set_completion_semaphore(AbstractSemaphore semaphore) {
this.semaphore = semaphore;
lock (this.semaphore) {
this.semaphore = semaphore;
}
}
// This method is thread-safe, but only waits if a completion semaphore has been set, otherwise
// exits immediately. Note that blocking for a semaphore does NOT spin the event loop, so a
// thread relying on it to continue should not use this.
public void wait_for_completion() {
lock (semaphore) {
if (semaphore != null)
semaphore.wait();
}
}
public Cancellable? get_cancellable() {
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment