better synchronization and threads use
[booh] / bin / booh
index 980c456921168ed2b4cd3385cd5eb60abc176d9b..5a3a8db503f75af6229afcc37546a710efddb6dd 100755 (executable)
--- a/bin/booh
+++ b/bin/booh
@@ -37,7 +37,6 @@ include REXML
 require 'booh/booh-lib'
 include Booh
 require 'booh/UndoHandler'
-require 'booh/Synchronizator'
 
 
 #- options
@@ -49,7 +48,6 @@ $options = [
 
 #- default values for some globals 
 $xmldir = nil
-$xmlaccesslock = Object.new
 $modified = false
 $current_cursor = nil
 $ignore_videos = false
@@ -518,33 +516,41 @@ end
 
 def color_swap(xmldir, attributes_prefix)
     $modified = true
-    if xmldir.attributes["#{attributes_prefix}color-swap"]
-        xmldir.delete_attribute("#{attributes_prefix}color-swap")
-    else
-        xmldir.add_attribute("#{attributes_prefix}color-swap", '1')
-    end
+    rexml_thread_protect {
+        if xmldir.attributes["#{attributes_prefix}color-swap"]
+            xmldir.delete_attribute("#{attributes_prefix}color-swap")
+        else
+            xmldir.add_attribute("#{attributes_prefix}color-swap", '1')
+        end
+    }
 end
 
 def enhance(xmldir, attributes_prefix)
     $modified = true
-    if xmldir.attributes["#{attributes_prefix}enhance"]
-        xmldir.delete_attribute("#{attributes_prefix}enhance")
-    else
-        xmldir.add_attribute("#{attributes_prefix}enhance", '1')
-    end
+    rexml_thread_protect {
+        if xmldir.attributes["#{attributes_prefix}enhance"]
+            xmldir.delete_attribute("#{attributes_prefix}enhance")
+        else
+            xmldir.add_attribute("#{attributes_prefix}enhance", '1')
+        end
+    }
 end
 
 def change_seektime(xmldir, attributes_prefix, value)
     $modified = true
-    xmldir.add_attribute("#{attributes_prefix}seektime", value)
+    rexml_thread_protect {
+        xmldir.add_attribute("#{attributes_prefix}seektime", value)
+    }
 end
 
 def ask_new_seektime(xmldir, attributes_prefix)
-    if xmldir
-        value = xmldir.attributes["#{attributes_prefix}seektime"]
-    else
-        value = ''
-    end
+    rexml_thread_protect {
+        if xmldir
+            value = xmldir.attributes["#{attributes_prefix}seektime"]
+        else
+            value = ''
+        end
+    }
 
     dialog = Gtk::Dialog.new(utf8(_("Change seek time")),
                              $main_window,
@@ -589,19 +595,23 @@ end
 
 def change_pano_amount(xmldir, attributes_prefix, value)
     $modified = true
-    if value.nil?
-        xmldir.delete_attribute("#{attributes_prefix}pano-amount")
-    else
-        xmldir.add_attribute("#{attributes_prefix}pano-amount", value.to_s)
-    end
+    rexml_thread_protect {
+        if value.nil?
+            xmldir.delete_attribute("#{attributes_prefix}pano-amount")
+        else
+            xmldir.add_attribute("#{attributes_prefix}pano-amount", value.to_s)
+        end
+    }
 end
 
 def ask_new_pano_amount(xmldir, attributes_prefix)
-    if xmldir
-        value = xmldir.attributes["#{attributes_prefix}pano-amount"]
-    else
-        value = nil
-    end
+    rexml_thread_protect {
+        if xmldir
+            value = xmldir.attributes["#{attributes_prefix}pano-amount"]
+        else
+            value = nil
+        end
+    }
 
     dialog = Gtk::Dialog.new(utf8(_("Specify panorama amount")),
                              $main_window,
@@ -846,16 +856,40 @@ def gen_real_thumbnail_core(type, origfile, destfile, xmldir, size, infotype)
     end
 end
 
+$max_gen_thumbnail_threads = nil
+$current_gen_thumbnail_threads = 0
+$gen_thumbnail_monitor = Monitor.new
+
 def gen_real_thumbnail(type, origfile, destfile, xmldir, size, img, infotype)
-    Thread.new {
+    if $max_gen_thumbnail_threads.nil?
+        $max_gen_thumbnail_threads = 1 + $config['mproc'].to_i || 1
+    end
+    genproc = Proc.new { 
         push_mousecursor_wait
-        gen_real_thumbnail_core(type, origfile, destfile, Synchronizator.new(xmldir, $xmlaccesslock), size, infotype)
+        gen_real_thumbnail_core(type, origfile, destfile, xmldir, size, infotype)
         gtk_thread_protect {
             img.set(destfile)
             $modified_pixbufs[destfile] = { :orig => img.pixbuf, :pixbuf => img.pixbuf, :angle_to_orig => 0 }
         }
         pop_mousecursor
     }
+    usethread = false
+    $gen_thumbnail_monitor.synchronize {
+        if $current_gen_thumbnail_threads < $max_gen_thumbnail_threads
+            $current_gen_thumbnail_threads += 1
+            usethread = true
+        end
+    }
+    if usethread
+        Thread.new {
+            genproc.call
+            $gen_thumbnail_monitor.synchronize {
+                $current_gen_thumbnail_threads -= 1
+            }
+        }
+    else
+        genproc.call
+    end
 end
 
 def popup_thumbnail_menu(event, optionals, fullpath, type, xmldir, attributes_prefix, possible_actions, closures)
@@ -1000,8 +1034,8 @@ def popup_thumbnail_menu(event, optionals, fullpath, type, xmldir, attributes_pr
         end
     }
     if !possible_actions[:can_multiple] || $selected_elements.length == 0
-        menu.append(enhance = Gtk::ImageMenuItem.new(utf8(xmldir.attributes["#{attributes_prefix}enhance"] ? _("Original contrast") :
-                                                                                                             _("Enhance constrast"))))
+        menu.append(enhance = Gtk::ImageMenuItem.new(utf8(rexml_thread_protect { xmldir.attributes["#{attributes_prefix}enhance"] } ? _("Original contrast") :
+                                                                                                                                      _("Enhance constrast"))))
     else
         menu.append(enhance = Gtk::ImageMenuItem.new(utf8(_("Toggle contrast enhancement"))))
     end
@@ -1170,13 +1204,17 @@ def add_thumbnail(autotable, filename, type, thumbnail_img, caption)
         cleanup_all_thumbnails.call
         #- refresh is not undoable and doesn't change the album, however we must regenerate all thumbnails when generating the album
         $modified = true
-        $xmldir.delete_attribute('already-generated')
+        rexml_thread_protect {
+            $xmldir.delete_attribute('already-generated')
+        }
         my_gen_real_thumbnail.call
     }
  
     rotate_and_cleanup = proc { |angle|
         cleanup_all_thumbnails.call
-        rotate(angle, thumbnail_img, img, $xmldir.elements["*[@filename='#{filename}']"], '', $default_thumbnails[:x], $default_thumbnails[:y])
+        rexml_thread_protect {
+            rotate(angle, thumbnail_img, img, $xmldir.elements["*[@filename='#{filename}']"], '', $default_thumbnails[:x], $default_thumbnails[:y])
+        }
     }
 
     move = proc { |direction|
@@ -1207,7 +1245,9 @@ def add_thumbnail(autotable, filename, type, thumbnail_img, caption)
     color_swap_and_cleanup = proc {
         perform_color_swap_and_cleanup = proc {
             cleanup_all_thumbnails.call
-            color_swap($xmldir.elements["*[@filename='#{filename}']"], '')
+            rexml_thread_protect {
+                color_swap($xmldir.elements["*[@filename='#{filename}']"], '')
+            }
             my_gen_real_thumbnail.call
         }
 
@@ -1231,7 +1271,9 @@ def add_thumbnail(autotable, filename, type, thumbnail_img, caption)
     change_seektime_and_cleanup_real = proc { |values|
         perform_change_seektime_and_cleanup = proc { |val|
             cleanup_all_thumbnails.call
-            change_seektime($xmldir.elements["*[@filename='#{filename}']"], '', val)
+            rexml_thread_protect {
+                change_seektime($xmldir.elements["*[@filename='#{filename}']"], '', val)
+            }
             my_gen_real_thumbnail.call
         }
         perform_change_seektime_and_cleanup.call(values[:new])
@@ -1252,15 +1294,19 @@ def add_thumbnail(autotable, filename, type, thumbnail_img, caption)
     }
 
     change_seektime_and_cleanup = proc {
-        if values = ask_new_seektime($xmldir.elements["*[@filename='#{filename}']"], '')
-            change_seektime_and_cleanup_real.call(values)
-        end
+        rexml_thread_protect {
+            if values = ask_new_seektime($xmldir.elements["*[@filename='#{filename}']"], '')
+                change_seektime_and_cleanup_real.call(values)
+            end
+        }
     }
 
     change_pano_amount_and_cleanup_real = proc { |values|
         perform_change_pano_amount_and_cleanup = proc { |val|
             cleanup_all_thumbnails.call
-            change_pano_amount($xmldir.elements["*[@filename='#{filename}']"], '', val)
+            rexml_thread_protect {
+                change_pano_amount($xmldir.elements["*[@filename='#{filename}']"], '', val)
+            }
         }
         perform_change_pano_amount_and_cleanup.call(values[:new])
         
@@ -1280,17 +1326,21 @@ def add_thumbnail(autotable, filename, type, thumbnail_img, caption)
     }
 
     change_pano_amount_and_cleanup = proc {
-        if values = ask_new_pano_amount($xmldir.elements["*[@filename='#{filename}']"], '')
-            change_pano_amount_and_cleanup_real.call(values)
-        end
+        rexml_thread_protect {
+            if values = ask_new_pano_amount($xmldir.elements["*[@filename='#{filename}']"], '')
+                change_pano_amount_and_cleanup_real.call(values)
+            end
+        }
     }
 
     whitebalance_and_cleanup_real = proc { |values|
         perform_change_whitebalance_and_cleanup = proc { |val|
             cleanup_all_thumbnails.call
-            change_whitebalance($xmldir.elements["*[@filename='#{filename}']"], '', val)
-            recalc_whitebalance(val, fullpath, thumbnail_img, img,
-                                $xmldir.elements["*[@filename='#{filename}']"], '', $default_thumbnails[:x], $default_thumbnails[:y], '')
+            rexml_thread_protect {
+                change_whitebalance($xmldir.elements["*[@filename='#{filename}']"], '', val)
+                recalc_whitebalance(val, fullpath, thumbnail_img, img,
+                                    $xmldir.elements["*[@filename='#{filename}']"], '', $default_thumbnails[:x], $default_thumbnails[:y], '')
+            }
         }
         perform_change_whitebalance_and_cleanup.call(values[:new])
 
@@ -1310,18 +1360,22 @@ def add_thumbnail(autotable, filename, type, thumbnail_img, caption)
     }
 
     whitebalance_and_cleanup = proc {
-        if values = ask_whitebalance(fullpath, thumbnail_img, img,
-                                     $xmldir.elements["*[@filename='#{filename}']"], '', $default_thumbnails[:x], $default_thumbnails[:y], '')
-            whitebalance_and_cleanup_real.call(values)
-        end
+        rexml_thread_protect {
+            if values = ask_whitebalance(fullpath, thumbnail_img, img,
+                                         $xmldir.elements["*[@filename='#{filename}']"], '', $default_thumbnails[:x], $default_thumbnails[:y], '')
+                whitebalance_and_cleanup_real.call(values)
+            end
+        }
     }
 
     gammacorrect_and_cleanup_real = proc { |values|
         perform_change_gammacorrect_and_cleanup = Proc.new { |val|
             cleanup_all_thumbnails.call
-            change_gammacorrect($xmldir.elements["*[@filename='#{filename}']"], '', val)
-            recalc_gammacorrect(val, fullpath, thumbnail_img, img,
-                                $xmldir.elements["*[@filename='#{filename}']"], '', $default_thumbnails[:x], $default_thumbnails[:y], '')
+            rexml_thread_protect {
+                change_gammacorrect($xmldir.elements["*[@filename='#{filename}']"], '', val)
+                recalc_gammacorrect(val, fullpath, thumbnail_img, img,
+                                    $xmldir.elements["*[@filename='#{filename}']"], '', $default_thumbnails[:x], $default_thumbnails[:y], '')
+            }
         }
         perform_change_gammacorrect_and_cleanup.call(values[:new])
         
@@ -1341,16 +1395,20 @@ def add_thumbnail(autotable, filename, type, thumbnail_img, caption)
     }
     
     gammacorrect_and_cleanup = Proc.new {
-        if values = ask_gammacorrect(fullpath, thumbnail_img, img,
-                                     $xmldir.elements["*[@filename='#{filename}']"], '', $default_thumbnails[:x], $default_thumbnails[:y], '')
-            gammacorrect_and_cleanup_real.call(values)
-        end
+        rexml_thread_protect {
+            if values = ask_gammacorrect(fullpath, thumbnail_img, img,
+                                         $xmldir.elements["*[@filename='#{filename}']"], '', $default_thumbnails[:x], $default_thumbnails[:y], '')
+                gammacorrect_and_cleanup_real.call(values)
+            end
+        }
     }
     
     enhance_and_cleanup = proc {
         perform_enhance_and_cleanup = proc {
             cleanup_all_thumbnails.call
-            enhance($xmldir.elements["*[@filename='#{filename}']"], '')
+            rexml_thread_protect {
+                enhance($xmldir.elements["*[@filename='#{filename}']"], '')
+            }
             my_gen_real_thumbnail.call
         }
         
@@ -1562,7 +1620,7 @@ def add_thumbnail(autotable, filename, type, thumbnail_img, caption)
             if !$ignore_next_release
                 x, y = autotable.get_current_pos(vbox)
                 next_ = autotable.get_next_widget(vbox)
-                popup_thumbnail_menu(event, ['delete'], fullpath, type, $xmldir.elements["*[@filename='#{filename}']"], '',
+                popup_thumbnail_menu(event, ['delete'], fullpath, type, rexml_thread_protect { $xmldir.elements["*[@filename='#{filename}']"] }, '',
                                      { :can_left => x > 0, :can_right => next_ && autotable.get_current_pos(next_)[0] > x,
                                        :can_up => y > 0, :can_down => y < autotable.get_max_y, :can_multiple => true, :can_panorama => true },
                                      { :rotate => rotate_and_cleanup, :move => move, :color_swap => color_swap_and_cleanup, :enhance => enhance_and_cleanup,
@@ -1868,7 +1926,7 @@ def ask_save_modifications(msg1, msg2, *options)
                 #- already-generated markers in original file
                 if $generated_outofline
                     begin
-                        $xmldoc = Synchronizator.new(REXML::Document.new(File.new($orig_filename)), $xmlaccesslock)
+                        $xmldoc = REXML::Document.new(File.new($orig_filename))
                         mark_document_as_dirty
                         ios = File.open($orig_filename, "w")
                         $xmldoc.write(ios, 0)
@@ -2215,10 +2273,12 @@ def sort_by_exif_date
     $modified = true
     save_changes
     current_order = []
-    $xmldir.elements.each { |element|
-        if element.name == 'image' || element.name == 'video'
-            current_order << element.attributes['filename']
-        end
+    rexml_thread_protect {
+        $xmldir.elements.each { |element|
+            if element.name == 'image' || element.name == 'video'
+                current_order << element.attributes['filename']
+            end
+        }
     }
 
     #- look for EXIF dates
@@ -2272,16 +2332,20 @@ def sort_by_exif_date
     end
 
     saves = {}
-    $xmldir.elements.each { |element|
-        if element.name == 'image' || element.name == 'video'
-            saves[element.attributes['filename']] = element.remove
-        end
+    rexml_thread_protect {
+        $xmldir.elements.each { |element|
+            if element.name == 'image' || element.name == 'video'
+                saves[element.attributes['filename']] = element.remove
+            end
+        }
     }
 
     neworder = smartsort(current_order, dates)
 
-    neworder.each { |f|
-        $xmldir.add_element(saves[f].name, saves[f].attributes)
+    rexml_thread_protect {
+        neworder.each { |f|
+            $xmldir.add_element(saves[f].name, saves[f].attributes)
+        }
     }
 
     #- let the auto-table reflect new ordering
@@ -2335,7 +2399,7 @@ def change_dir
     $subalbums = Gtk::Table.new(0, 0, true)
     current_y_sub_albums = 0
 
-    $xmldir = Synchronizator.new($xmldoc.elements["//dir[@path='#{$current_path}']"], $xmlaccesslock)
+    $xmldir = $xmldoc.elements["//dir[@path='#{$current_path}']"]
     $subalbums_edits = {}
     subalbums_counter = 0
     subalbums_edits_bypos = {}
@@ -2839,7 +2903,7 @@ def open_file(filename)
     end
 
     begin
-        $xmldoc = Synchronizator.new(REXML::Document.new(File.new(filename)), $xmlaccesslock)
+        $xmldoc = REXML::Document.new(File.new(filename))
     rescue Exception
         $xmldoc = nil
     end
@@ -4290,23 +4354,24 @@ def gtk_thread_protect(&proc)
     if Thread.current == Thread.main
         proc.call
     else
-        $protect_gtk_pending_calls.synchronize {
+        $gtk_pending_calls.synchronize {
             $gtk_pending_calls << proc
         }
     end
 end
 
 def gtk_thread_flush
-    #- try to lock. we cannot synchronize blindly because this might be called from
-    #- within the timeout flushing procs. if this is the case, not doing anything
-    #- should be ok since the timeout is already flushing them all.
-    if $protect_gtk_pending_calls.try_lock
-        for closure in $gtk_pending_calls
+    closure = nil
+    continue = true
+    begin
+        $gtk_pending_calls.synchronize {
+            closure = $gtk_pending_calls.shift
+            continue = $gtk_pending_calls.size > 0
+        }
+        if closure
             closure.call
         end
-        $gtk_pending_calls = []
-        $protect_gtk_pending_calls.unlock
-    end
+    end while continue
 end
 
 def ask_password_protect
@@ -4560,15 +4625,10 @@ def create_main_window
         false
     }
 
-    $protect_gtk_pending_calls = Mutex.new
     $gtk_pending_calls = []
+    $gtk_pending_calls.extend(MonitorMixin)
     Gtk.timeout_add(100) {
-        $protect_gtk_pending_calls.synchronize {
-            for closure in $gtk_pending_calls
-                closure.call
-            end
-            $gtk_pending_calls = []
-        }
+        gtk_thread_flush
         true
     }