From a22ffa0ee4514c2c5ada14605d6c5b07487ac97d Mon Sep 17 00:00:00 2001 From: gc Date: Sat, 23 Feb 2008 15:13:01 +0000 Subject: [PATCH] remove multithreading; load only thumbnails to quicken loading --- bin/booh-classifier | 537 +++++++++++++++++++++++--------------------- 1 file changed, 282 insertions(+), 255 deletions(-) diff --git a/bin/booh-classifier b/bin/booh-classifier index c2a17c3..ff6d7af 100644 --- a/bin/booh-classifier +++ b/bin/booh-classifier @@ -21,7 +21,6 @@ require 'getoptlong' require 'tempfile' -require 'thread' require 'gtk2' require 'booh/libadds' @@ -46,6 +45,8 @@ $options = [ [ '--sort-by-exif-date', '-s', GetoptLong::NO_ARGUMENT, _("Sort entries by EXIF date") ], ] +$preloader_allowed = false + def usage puts _("Usage: %s [OPTION]...") % File.basename($0) $options.each { |ary| @@ -244,86 +245,97 @@ class Entry if @@max_width.nil? @@max_width = $main_window.root_window.size[0] - $labels_vbox.allocation.width - ( $videoborder_pixbuf.width + MainView.borders_thickness) * 2 end - @protect_cleanup = Mutex.new end def pixbuf_full - @protect_cleanup.synchronize { - if @pixbuf_full.nil? - msg 3, ">>> pixbuf_full #{path}" - load_into_pixbuf_full - end - return @pixbuf_full - } + if @pixbuf_full.nil? + msg 3, ">>> pixbuf_full #{path}" + load_into_pixbuf_full + end + return @pixbuf_full end def free_pixbuf_full - @protect_cleanup.synchronize { + if @pixbuf_full.nil? + return false + else + msg 3, ">>> free_pixbuf_full #{path}" + @pixbuf_full = nil + return true + end + end + def pixbuf_main + width, height = $mainview.window.size + width = MainView.get_usable_width(width) + height = MainView.get_usable_height(height) + if @pixbuf_main.nil? || width != @width || height != @height + msg 3, ">>> pixbuf_main #{path}" + @width = width + @height = height + load_into_pixbuf_full #- make sure it is loaded if @pixbuf_full.nil? - return false - else - msg 3, ">>> free_pixbuf_full #{path}" - @pixbuf_full = nil - return true + return end - } - end - def pixbuf_main(width, height) - @protect_cleanup.synchronize { - if @pixbuf_main.nil? || width != @width || height != @height - msg 3, ">>> pixbuf_main #{path}" - @width = width - @height = height - load_into_pixbuf_full #- make sure it is loaded - if @pixbuf_full.width.to_f / @pixbuf_full.height > width.to_f / height - resized_height = @pixbuf_full.height * (width.to_f/@pixbuf_full.width) - if @pixbuf_full.width > width || @pixbuf_full.height > resized_height - @pixbuf_main = @pixbuf_full.scale(width, resized_height, Gdk::Pixbuf::INTERP_BILINEAR) - else - @pixbuf_main = @pixbuf_full - end + if @pixbuf_full.width.to_f / @pixbuf_full.height > width.to_f / height + resized_height = @pixbuf_full.height * (width.to_f/@pixbuf_full.width) + if @pixbuf_full.width > width || @pixbuf_full.height > resized_height + @pixbuf_main = @pixbuf_full.scale(width, resized_height, Gdk::Pixbuf::INTERP_BILINEAR) else - resized_width = @pixbuf_full.width * (height.to_f/@pixbuf_full.height) - if @pixbuf_full.width > resized_width || @pixbuf_full.height > height - @pixbuf_main = @pixbuf_full.scale(resized_width, height, Gdk::Pixbuf::INTERP_BILINEAR) - else - @pixbuf_main = @pixbuf_full - end + @pixbuf_main = @pixbuf_full + end + else + resized_width = @pixbuf_full.width * (height.to_f/@pixbuf_full.height) + if @pixbuf_full.width > resized_width || @pixbuf_full.height > height + @pixbuf_main = @pixbuf_full.scale(resized_width, height, Gdk::Pixbuf::INTERP_BILINEAR) + else + @pixbuf_main = @pixbuf_full end end - return @pixbuf_main - } + end + return @pixbuf_main end def free_pixbuf_main - @protect_cleanup.synchronize { - if @pixbuf_main.nil? - return false - else - msg 3, ">>> free_pixbuf_main #{path}" - @pixbuf_main = nil - return true - end - } + if @pixbuf_main.nil? + return false + else + msg 3, ">>> free_pixbuf_main #{path}" + @pixbuf_main = nil + return true + end end def pixbuf_thumbnail - return @protect_cleanup.synchronize { - if @pixbuf_thumbnail.nil? - msg 3, ">>> pixbuf_thumbnail #{path}" - load_into_pixbuf_full #- make sure it is loaded - @pixbuf_thumbnail = @pixbuf_full.scale(@pixbuf_full.width * (@@thumbnails_height.to_f/@pixbuf_full.height), @@thumbnails_height, Gdk::Pixbuf::INTERP_BILINEAR) + if @pixbuf_thumbnail.nil? + if @pixbuf_main + msg 3, ">>> pixbuf_thumbnail from main #{path}" + @pixbuf_thumbnail = @pixbuf_main.scale(@pixbuf_main.width * (@@thumbnails_height.to_f/@pixbuf_main.height), @@thumbnails_height, Gdk::Pixbuf::INTERP_BILINEAR) + else + msg 3, ">>> pixbuf_thumbnail from file #{path}" + @pixbuf_thumbnail = load_into_pixbuf_at_size { |w, h| + if @angle == 0 + if h > @@thumbnails_height + [ w * @@thumbnails_height.to_f/h, @@thumbnails_height ] + else + [ w, h ] + end + else + if w > @@thumbnails_height + [ @@thumbnails_height, h * @@thumbnails_height.to_f/w ] + else + [ w, h ] + end + end + } end - return @pixbuf_thumbnail - } + end + return @pixbuf_thumbnail end def free_pixbuf_thumbnail - @protect_cleanup.synchronize { - if @pixbuf_thumbnail.nil? - return false - else - msg 3, ">>> free_pixbuf_thumbnail #{path}" - @pixbuf_thumbnail = nil - return true - end - } + if @pixbuf_thumbnail.nil? + return false + else + msg 3, ">>> free_pixbuf_thumbnail #{path}" + @pixbuf_thumbnail = nil + return true + end end def outline_color @@ -370,66 +382,86 @@ class Entry def load_into_pixbuf_full if @pixbuf_full.nil? msg 3, ">>> load_into_pixbuf_full #{path}" - if @type == 'video' - tmp = Tempfile.new("boohclassifiertemp") - tmp.close! - Dir.mkdir(dest_dir = tmp.path) - orig_base = File.basename(path) - tmpdir = gen_video_thumbnail(path, false, 0) - if tmpdir.nil? - return - end - image_path = "#{tmpdir}/00000001.jpg" - else - image_path = @path - end - begin - #- use a pixbuf loader to allow the loading thread to be interrupted by the main thread more often, to keep the UI responsive even - #- if loaded pictures are several MBs large - loader = Gdk::PixbufLoader.new - loader.signal_connect('size-prepared') { |l,w,h| - maxdim = w > h ? w : h - if maxdim > @@max_width + @pixbuf_full = load_into_pixbuf_at_size { |w, h| + if @angle == 0 + if w > @@max_width #- save memory and speedup (+35%) loading - loader.set_size(w * (factor = @@max_width.to_f/maxdim), h * factor) + [ w * (factor = @@max_width.to_f/w), h * factor ] + else + [ w, h ] end - } - loader.signal_connect('area-prepared') { @pixbuf_full = loader.pixbuf } - file = File.new(image_path) - while (chunk = file.read(65536)) != nil - loader.write(chunk) - end - file.close - loader.close - if @pixbuf_full.nil? - raise "Loaded pixbuf nil - #{path} #{image_path}" - end - rescue Gdk::PixbufError - msg 0, "Cannot load #{image_path}: #{$!}" - return - ensure - if @type == 'video' - File.delete(image_path) - Dir.rmdir(tmpdir) - end - end - if @pixbuf_full - if @angle.nil? - if @type == 'image' - @angle = guess_rotate(image_path) + else + if h > @@max_width + [ w * (factor = @@max_width.to_f/h), h * factor ] else - @angle = 0 + [ w, h ] end end - if @angle != 0 - msg 3, ">>> load_into_pixbuf_full #{image_path} => rotate #{@angle}" - @pixbuf_full = rotate_pixbuf(@pixbuf_full, @angle) - end + } + end + end + + def load_into_pixbuf_at_size(&specify_size) + pixbuf = nil + if @type == 'video' + tmp = Tempfile.new("boohclassifiertemp") + tmp.close! + Dir.mkdir(dest_dir = tmp.path) + orig_base = File.basename(path) + tmpdir = gen_video_thumbnail(path, false, 0) + if tmpdir.nil? + return + end + image_path = "#{tmpdir}/00000001.jpg" + else + image_path = @path + end + if @angle.nil? + if @type == 'image' + @angle = guess_rotate(image_path) + else + @angle = 0 end + end + begin + #- use a pixbuf loader and trigger Gtk.main_iteration on each chunk if needed, to keep the UI responsive even + #- if loaded pictures are several MBs large + loader = Gdk::PixbufLoader.new + loader.signal_connect('size-prepared') { |l, w, h| + r = specify_size.call(w, h) + msg 3, "specified sizes: #{r[0]} #{r[1]}" + loader.set_size(*specify_size.call(w, h)) + } + loader.signal_connect('area-prepared') { pixbuf = loader.pixbuf } + file = File.new(image_path) + while (chunk = file.read(4096)) != nil + loader.write(chunk) + Gtk.main_iteration while Gtk.events_pending? + end + file.close + loader.close + if pixbuf.nil? + raise "Loaded pixbuf nil - #{path} #{image_path}" + end + rescue Gdk::PixbufError + msg 0, "Cannot load #{image_path}: #{$!}" + return + ensure if @type == 'video' - cleanup_dir(dest_dir) + File.delete(image_path) + Dir.rmdir(tmpdir) end end + if pixbuf + if @angle != 0 + msg 3, ">>> load_into_pixbuf_full #{image_path} => rotate #{@angle}" + pixbuf = rotate_pixbuf(pixbuf, @angle) + end + end + if @type == 'video' + cleanup_dir(dest_dir) + end + return pixbuf end def to_s @@ -439,6 +471,54 @@ end $allentries = [] +def run_preloader_real + msg 3, "*** >> main preloading triggered..." + if $preloader_running + msg 3, "*** >>>>>> already running, return <<<<<<<<" + return + end + $preloader_running = true + if $mainview.get_shown_entry + mem = get_mem + if mem > $config['cache-memory-use-figure'] + msg 3, "too much RSS, stopping preloading, triggering GC" + $preloader_running = false + GC.start + msg 3, "GC finished" + return + end + index = $allentries.index($mainview.get_shown_entry) + for j in 1 .. $config['preload-distance'].to_i + i = index + j + if i < $allentries.size + $allentries[i].pixbuf_main + end + i = index - j + if i >= 0 + $allentries[i].pixbuf_main + end + end + check_memory_free_cache_if_needed + end + $preloader_running = false + msg 3, "*** << main preloading finished" + #- if we're already on a different image, rerun the preloader + if index != $allentries.index($mainview.get_shown_entry) + run_preloader_real + end +end + +def run_preloader + if ! $preloader_allowed + msg 3, "*** preloader not yet allowed" + return + end + Gtk.timeout_add(10) { + run_preloader_real + false + } +end + class MainView < Gtk::DrawingArea @@borders_thickness = 5 @@ -459,44 +539,12 @@ class MainView < Gtk::DrawingArea def initialize super() signal_connect('expose-event') { draw } - signal_connect('configure-event') { update_shown; GC.start } - signal_connect('realize') { - @preloader = Thread.new { - #- background preloading - while true - msg 3, "*** >> background main preloading triggered..." - if ! @entry.nil? - index = $allentries.index(@entry) - w, h = window.size - w = MainView.get_usable_width(w) - h = MainView.get_usable_height(h) - for j in 1 .. $config['preload-distance'].to_i - i = index + j - if i < $allentries.size - $allentries[i].pixbuf_main(w, h) - end - i = index - j - if i >= 0 - $allentries[i].pixbuf_main(w, h) - end - GC.start - mem = get_mem - if mem > $config['cache-memory-use-figure'] - msg 3, "too much RSS, stopping main preloading" - break - end - end - check_memory_free_cache_if_needed - end - msg 3, "*** << background main preloading stopping" - Thread.stop - end - } - @preloader.priority = -2 - } + signal_connect('configure-event') { update_shown } + @preloader_running = false end def set_shown_entry(entry) + t1 = Time.now if ! entry.nil? && entry == @entry return end @@ -510,7 +558,8 @@ class MainView < Gtk::DrawingArea end @entry = entry redraw - @preloader.run + run_preloader + msg 3, "entry shown in: #{Time.now - t1} s" end def get_shown_entry @@ -535,13 +584,10 @@ class MainView < Gtk::DrawingArea def update_shown if @entry + @pixbuf = @entry.pixbuf_main width, height = window.size - usable_width = MainView.get_usable_width(width) - usable_height = MainView.get_usable_height(height) - @pixbuf = @entry.pixbuf_main(usable_width, usable_height) @xpos = (width - @pixbuf.width)/2 @ypos = (height - @pixbuf.height)/2 - @preloader.run else @pixbuf = nil end @@ -580,28 +626,25 @@ class MainView < Gtk::DrawingArea end def check_memory_free_cache_if_needed - GC.start - mem = get_mem i = $allentries.index($mainview.get_shown_entry) - msg 3, "mem: #{mem} index: #{i}" return if i.nil? + if get_mem < $config['cache-memory-use-figure'] * 2 / 3 + return + end + msg 3, "too much RSS, triggering GC" + GC.start + msg 3, "GC finished" ($allentries.size - 1).downto(1) { |j| - if mem < $config['cache-memory-use-figure'] * 2 / 3 + if get_mem < $config['cache-memory-use-figure'] / 2 break end index = i + j msg 3, "too much RSS, freeing full size of #{i+j} and #{i-j}..." - freedsomething = false if i + j < $allentries.size - freedsomething |= $allentries[i+j].free_pixbuf_full + $allentries[i+j].free_pixbuf_full end if i - j > 0 - freedsomething |= $allentries[i-j].free_pixbuf_full - end - if freedsomething - GC.start - mem = get_mem - msg 3, "\tmem now: #{mem}" + $allentries[i-j].free_pixbuf_full end } end @@ -885,75 +928,86 @@ def sb_msg(msg) end end -def real_show_entry(entry, tooltips, grab_focus) - #- for scoping the actual reference to 'entry' - gtk_thread_protect(proc { - msg 3, "using entry #{entry}" - entry.image = Gtk::Image.new(entry.pixbuf_thumbnail) - if entry.type == 'video' - entry.button = Gtk::Button.new.add(Gtk::HBox.new.pack_start(da1 = Gtk::DrawingArea.new.set_size_request($videoborder_pixbuf.width, -1), false, false). - pack_start(entry.image). - pack_start(da2 = Gtk::DrawingArea.new.set_size_request($videoborder_pixbuf.width, -1), false, false)) - da1.signal_connect('realize') { da1.window.set_back_pixmap($videoborder_pixmap, false) } - da2.signal_connect('realize') { da2.window.set_back_pixmap($videoborder_pixmap, false) } - else - entry.button = Gtk::Button.new.set_image(entry.image) +def show_entry(entry, i) + #- scope entry + msg 3, "showing entry #{entry}" + entry.image = Gtk::Image.new(entry.pixbuf_thumbnail) + if entry.type == 'video' + entry.button = Gtk::Button.new.add(Gtk::HBox.new.pack_start(da1 = Gtk::DrawingArea.new.set_size_request($videoborder_pixbuf.width, -1), false, false). + pack_start(entry.image). + pack_start(da2 = Gtk::DrawingArea.new.set_size_request($videoborder_pixbuf.width, -1), false, false)) + da1.signal_connect('realize') { da1.window.set_back_pixmap($videoborder_pixmap, false) } + da2.signal_connect('realize') { da2.window.set_back_pixmap($videoborder_pixmap, false) } + else + entry.button = Gtk::Button.new.set_image(entry.image) + end + Gtk::Tooltips.new.set_tip(entry.button, entry.get_beautified_name, nil) + $imagesline.pack_start(entry.alignment = Gtk::Alignment.new(0.5, 1, 0, 0).add(entry.button).show_all, false, false) + entry.button.signal_connect('clicked') { + if (last_shown = $mainview.get_shown_entry) != entry + $mainview.set_shown_entry(entry) + entry.alignment.set(0.5, 0, 0, 0) + sb_msg(_("Selected %s") % entry.get_beautified_name) + last_shown and last_shown.alignment.set(0.5, 1, 0, 0) end - tooltips.set_tip(entry.button, entry.get_beautified_name, nil) - $imagesline.pack_start(entry.alignment = Gtk::Alignment.new(0.5, 1, 0, 0).add(entry.button).show_all, false, false) - entry.button.signal_connect('clicked') { - if (last_shown = $mainview.get_shown_entry) != entry - $mainview.set_shown_entry(entry) - entry.alignment.set(0.5, 0, 0, 0) - sb_msg(_("Selected %s") % entry.get_beautified_name) - last_shown.nil? or last_shown.alignment.set(0.5, 1, 0, 0) - end - } - entry.button.signal_connect('button-press-event') { |w, event| - if entry.type == 'video' && event.event_type == Gdk::Event::BUTTON2_PRESS - video_view(entry) - end - } - entry.button.signal_connect('focus-in-event') { entry.button.clicked; autoscroll_if_needed(entry.button) } - entry.button.signal_connect('key-press-event') { |w, e| thumbnail_keypressed(entry, e) } - if grab_focus - entry.button.grab_focus + } + entry.button.signal_connect('button-press-event') { |w, event| + if entry.type == 'video' && event.event_type == Gdk::Event::BUTTON2_PRESS + video_view(entry) end - update_visibility(entry) - }) + } + entry.button.signal_connect('focus-in-event') { entry.button.clicked; autoscroll_if_needed(entry.button) } + entry.button.signal_connect('key-press-event') { |w, e| thumbnail_keypressed(entry, e) } + if i == 0 + entry.button.grab_focus + end + update_visibility(entry) + Gtk.main_iteration while Gtk.events_pending? end def show_entries - $images_loader = Thread.new { - t1 = Time.now - sb_msg(_("Loading images...")) - tooltips = Gtk::Tooltips.new - counter = 0 - toremove = [] - grab_focus = true - i = 0 - total_size = 0 - while i < $allentries.size - entry = $allentries[i] - if entry.pixbuf_full - total_size += file_size(entry.path) - entry.pixbuf_thumbnail - real_show_entry(entry, tooltips, grab_focus) - grab_focus = false - if i % 4 == 0 - check_memory_free_cache_if_needed - end - counter += 1 - i = i + 1 + sb_msg(_("Loading images...")) + t1 = Time.now + total_loaded_files = 0 + total_loaded_size = 0 + i = 0 + while i < $allentries.size +# printf "%d %s\n", i, __LINE__ + entry = $allentries[i] + if i == 0 + loaded_pixbuf = entry.pixbuf_main + else + loaded_pixbuf = entry.pixbuf_thumbnail + end - else - $allentries.delete_at(i) + if loaded_pixbuf + show_entry(entry, i) + + total_loaded_size += file_size(entry.path) + if i % 4 == 0 + check_memory_free_cache_if_needed end + total_loaded_files += 1 + i += 1 + if i > $config['preload-distance'].to_i && i <= $config['preload-distance'].to_i * 2 + #- when we're at preload distance, beging preloading to preload distance + $allentries[i - $config['preload-distance'].to_i].pixbuf_main + end + if i == $config['preload-distance'].to_i * 2 + 1 + #- when we're after double preload distance, activate normal preloading + $preloader_allowed = true + end + + else + $allentries.delete_at(i) end - check_memory_free_cache_if_needed - sb_msg(_("%d images of total %s kB loaded in %3.2f seconds.") % [ counter, commify(total_size / 1024), Time.now - t1 ]) - } - $images_loader.priority = -1 + end + if i <= $config['preload-distance'].to_i * 2 + #- not yet preloaded correctly + $preloader_allowed = true + run_preloader + end + sb_msg(_("%d images of total %s kB loaded in %3.2f seconds.") % [ total_loaded_files, commify(total_loaded_size / 1024), Time.now - t1 ]) end def reset_all @@ -1042,25 +1096,6 @@ def open_dir_popup fc.destroy end -def gtk_thread_protect(proc, *params) - if Thread.current == Thread.main - proc.call(*params) - else - $protect_gtk_pending_calls.synchronize { - $gtk_pending_calls << [ proc, params ] - } - end -end - -def gtk_thread_flush - $protect_gtk_pending_calls.synchronize { - if $gtk_pending_calls.size > 0 - elem = $gtk_pending_calls.shift - elem[0].call(*elem[1]) - end - } -end - def try_quit(*options) Gtk.main_quit end @@ -1548,19 +1583,11 @@ def create_main_window false } - $protect_gtk_pending_calls = Mutex.new - $gtk_pending_calls = [] - Gtk.timeout_add(50) { - gtk_thread_flush - true - } - $main_window.show_all end handle_options -Thread.abort_on_exception = true read_config Gtk.init -- 2.30.4