workaround progression pipe bug
[booh] / bin / booh
old mode 100755 (executable)
new mode 100644 (file)
index 23c49ea..5766217
--- a/bin/booh
+++ b/bin/booh
@@ -10,7 +10,7 @@
 # called Boo, so this one will be it "Booh". Or whatever.
 #
 #
-# Copyright (c) 2004-2008 Guillaume Cottenceau <http://zarb.org/~gc/resource/gc_mail.png>
+# Copyright (c) 2004-2013 Guillaume Cottenceau <http://zarb.org/~gc/resource/gc_mail.png>
 #
 # This software may be freely redistributed under the terms of the GNU
 # public license version 2.
 # along with this program; if not, write to the Free Software
 # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
 
+begin
+    require 'rubygems'
+rescue LoadError
+end
+
 require 'getoptlong'
 require 'tempfile'
 require 'thread'
@@ -31,7 +36,7 @@ require 'gettext'
 include GetText
 bindtextdomain("booh")
 
-require 'booh/rexml/document'
+require 'rexml/document'
 include REXML
 
 require 'booh/booh-lib'
@@ -74,7 +79,7 @@ def handle_options
             when '--version'
                 puts _("Booh version %s
 
-Copyright (c) 2005-2008 Guillaume Cottenceau.
+Copyright (c) 2005-2013 Guillaume Cottenceau.
 This is free software; see the source for copying conditions.  There is NO
 warranty; not even for MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.") % $VERSION
 
@@ -92,42 +97,56 @@ warranty; not even for MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.") %
     end
 end
 
+def count_cpus
+    cpus = 0
+    for line in IO.readlines('/proc/cpuinfo') do
+        line =~ /^processor/ and cpus += 1
+    end
+    return cpus
+end
+
 def read_config
     $config = {}
     $config_file = File.expand_path('~/.booh-gui-rc')
     if File.readable?($config_file)
-        xmldoc = REXML::Document.new(File.new($config_file))
-        xmldoc.root.elements.each { |element|
-            txt = element.get_text
-            if txt
-                if txt.value =~ /~~~/ || element.name == 'last-opens'
-                    $config[element.name] = txt.value.split(/~~~/)
+        begin
+            xmldoc = REXML::Document.new(File.new($config_file))
+        rescue
+            #- encoding unsupported anymore? file edited manually? ignore then
+            msg 1, "Ignoring #{$config_file}, failed to parse it: #{$!}"
+        end
+        if xmldoc
+            xmldoc.root.elements.each { |element|
+                txt = element.get_text
+                if txt
+                    if txt.value =~ /~~~/ || element.name == 'last-opens'
+                        $config[element.name] = txt.value.split(/~~~/)
+                    else
+                        $config[element.name] = txt.value
+                    end
+                elsif element.elements.size == 0
+                    $config[element.name] = ''
                 else
-                    $config[element.name] = txt.value
+                    $config[element.name] = {}
+                    element.each { |chld|
+                        txt = chld.get_text
+                        $config[element.name][chld.name] = txt ? txt.value : nil
+                    }
                 end
-            elsif element.elements.size == 0
-                $config[element.name] = ''
-            else
-                $config[element.name] = {}
-                element.each { |chld|
-                    txt = chld.get_text
-                    $config[element.name][chld.name] = txt ? txt.value : nil
-                }
-            end
-        }
+            }
+        end
     end
-    $config['video-viewer'] ||= '/usr/bin/mplayer %f'
-    $config['image-editor'] ||= '/usr/bin/gimp-remote %f'
-    $config['browser'] ||= "/usr/bin/mozilla-firefox -remote 'openURL(%f,new-window)' || /usr/bin/mozilla-firefox %f"
+    $config['video-viewer'] ||= '/usr/bin/mplayer %f || /usr/bin/vlc %f'
+    $config['image-editor'] ||= '/usr/bin/gimp-remote %f || /usr/bin/gimp %f'
+    $config['browser'] ||= "/usr/bin/mozilla-firefox -remote 'openURL(%f,new-window)' || /usr/bin/mozilla-firefox %f || /usr/bin/firefox -remote 'openURL(%f,new-window)' || /usr/bin/firefox %f"
+    $config['use-mp4'] ||= "true"
+    $config['mp4-generator'] ||= "/usr/bin/ffmpeg -i %f -b 800k -ar 22050 -ab 32k %o"
     $config['comments-format'] ||= '%t'
     if !FileTest.directory?(File.expand_path('~/.booh'))
         system("mkdir ~/.booh")
     end
     if $config['mproc'].nil?
-        cpus = 0
-        for line in IO.readlines('/proc/cpuinfo') do
-            line =~ /^processor/ and cpus += 1
-        end
+        cpus = count_cpus
         if cpus > 1
             $config['mproc'] = cpus
         end
@@ -137,6 +156,29 @@ def read_config
     $todelete = []
 end
 
+def check_config_preferences_dep
+    viewer_binary = $config['video-viewer'].split.first
+    if viewer_binary && !File.executable?(viewer_binary)
+        show_popup($main_window, utf8(_("The configured video viewer seems to be unavailable.
+You should fix this in Edit/Preferences so that you can view videos.
+
+Problem was: '%s' is not an executable file.
+Hint: don't forget to specify the full path to the executable,
+e.g. '/usr/bin/mplayer' is correct but 'mplayer' only is not.") % viewer_binary), { :pos_centered => true, :not_transient => true })
+    end
+
+    mp4_generator_binary = $config['use-mp4'] == 'true' && $config['mp4-generator'].split.first
+    if mp4_generator_binary && !File.executable?(mp4_generator_binary)
+        show_popup($main_window, utf8(_("The configured .mp4 generator seems to be unavailable.
+You should fix this in Edit/Preferences so that you can have working
+embedded flash videos.
+
+Problem was: '%s' is not an executable file.
+Hint: don't forget to specify the full path to the executable,
+e.g. '/usr/bin/ffmpeg' is correct but 'ffmpeg' only is not.") % mp4_generator_binary), { :pos_centered => true, :not_transient => true })
+    end
+end
+
 def check_config
     if !system("which convert >/dev/null 2>/dev/null")
         show_popup($main_window, utf8(_("The program 'convert' is needed. Please install it.
@@ -155,26 +197,26 @@ It is generally available with the 'ImageMagick' software package.")), { :pos_ce
         show_popup($main_window, utf8(_("The following program(s) are needed to handle videos: '%s'. Videos will be ignored.") % missing.join(', ')), { :pos_centered => true })
     end
 
-    viewer_binary = $config['video-viewer'].split.first
-    if viewer_binary && !File.executable?(viewer_binary)
-        show_popup($main_window, utf8(_("The configured video viewer seems to be unavailable.
-You should fix this in Edit/Preferences so that you can view videos.
+    check_config_preferences_dep
 
-Problem was: '%s' is not an executable file.
-Hint: don't forget to specify the full path to the executable,
-e.g. '/usr/bin/mplayer' is correct but 'mplayer' only is not.") % viewer_binary), { :pos_centered => true, :not_transient => true })
+    cpus = count_cpus
+
+    if $config['cpus'] && cpus > $config['cpus'].to_i
+        show_popup($main_window, utf8(_("It seems you now have more CPUs available than last time booh was run.
+You should probably increase the amount of CPUs configured in Edit/Preferences,
+so that web-albums are generated as fast as possible on this computer.")), { :pos_centered => true, :not_transient => true })
     end
+    $config['cpus'] = cpus
 end
 
 def check_image_editor
-    image_editor_binary = $config['image-editor'].split.first
-    if image_editor_binary && !File.executable?(image_editor_binary)
+    if last_failed_binary = check_multi_binaries($config['image-editor'])
         show_popup($main_window, utf8(_("The configured image editor seems to be unavailable.
 You should fix this in Edit/Preferences so that you can edit photos externally.
 
 Problem was: '%s' is not an executable file.
 Hint: don't forget to specify the full path to the executable,
-e.g. '/usr/bin/gimp-remote' is correct but 'gimp-remote' only is not.") % image_editor_binary), { :pos_centered => true, :not_transient => true })
+e.g. '/usr/bin/gimp-remote' is correct but 'gimp-remote' only is not.") % last_failed_binary), { :pos_centered => true, :not_transient => true })
         return false
     else
         return true
@@ -206,7 +248,7 @@ def write_config
         end
     }
     ios = File.open($config_file, "w")
-    xmldoc.write(ios, 0)
+    xmldoc.write(ios)
     ios.close
 
     $tempfiles.each { |f|
@@ -459,9 +501,9 @@ def update_shown_pixbuf(thumbnail_img, img, desired_x, desired_y)
         pixbuf = rotate_pixbuf(pixbuf, $modified_pixbufs[thumbnail_img][:angle_to_orig])
         msg 3, "sizes: #{pixbuf.width} #{pixbuf.height} - desired #{desired_x}x#{desired_x}"
         if pixbuf.height > desired_y
-            pixbuf = pixbuf.scale(pixbuf.width * (desired_y.to_f/pixbuf.height), desired_y, Gdk::Pixbuf::INTERP_BILINEAR)
+            pixbuf = pixbuf.scale(pixbuf.width * (desired_y.to_f/pixbuf.height), desired_y, :bilinear)
         elsif pixbuf.width < desired_x && pixbuf.height < desired_y
-            pixbuf = pixbuf.scale(desired_x, pixbuf.height * (desired_x.to_f/pixbuf.width), Gdk::Pixbuf::INTERP_BILINEAR)
+            pixbuf = pixbuf.scale(desired_x, pixbuf.height * (desired_x.to_f/pixbuf.width), :bilinear)
         end
     end
 
@@ -1151,7 +1193,7 @@ def add_thumbnail(autotable, filename, type, thumbnail_img, caption)
     }
 
     if type == 'video'
-        pxb = Gdk::Pixbuf.new("#{$FPATH}/images/video_border.png")
+        pxb = GdkPixbuf::Pixbuf.new(:file => "#{$FPATH}/images/video_border.png")
         frame1.add(Gtk::HBox.new.pack_start(da1 = Gtk::DrawingArea.new.set_size_request(pxb.width, -1), false, false).
                                  pack_start(img = Gtk::Image.new).
                                  pack_start(da2 = Gtk::DrawingArea.new.set_size_request(pxb.width, -1), false, false))
@@ -1851,7 +1893,7 @@ def save_current_file
         begin
             begin
                 ios = File.open($filename, "w")
-                $xmldoc.write(ios, 0)
+                $xmldoc.write(ios)
                 ios.close
             rescue Iconv::IllegalSequence
                 #- user might have entered text which cannot be encoded in his default encoding. retry in UTF-8.
@@ -1860,7 +1902,7 @@ def save_current_file
                 end
                 $xmldoc.xml_decl.encoding = 'UTF-8'
                 ios = File.open($filename, "w")
-                $xmldoc.write(ios, 0)
+                $xmldoc.write(ios)
                 ios.close
             end
             return true
@@ -1885,8 +1927,13 @@ def save_current_file_user
 
     msg 3, "performing actual deletion of: " + $todelete.join(', ')
     $todelete.each { |f|
-        File.delete(f)
+        begin
+            File.delete(f)
+        rescue
+            puts "Failed to delete #{f}: #{$!}"
+        end
     }
+    $todelete = []
 end
 
 def mark_document_as_dirty
@@ -1933,7 +1980,7 @@ def ask_save_modifications(msg1, msg2, *options)
                         $xmldoc = REXML::Document.new(File.new($orig_filename))
                         mark_document_as_dirty
                         ios = File.open($orig_filename, "w")
-                        $xmldoc.write(ios, 0)
+                        $xmldoc.write(ios)
                         ios.close
                     rescue Exception
                         puts "exception: #{$!}"
@@ -1943,7 +1990,6 @@ def ask_save_modifications(msg1, msg2, *options)
             if response == Gtk::Dialog::RESPONSE_CANCEL
                 ret = false
             end
-            $todelete = []  #- unconditionally clear the list of images/videos to delete
         }
     end
     return ret
@@ -1976,9 +2022,6 @@ def show_popup(parent, msg, *options)
     if options[0] && options[0][:selectable]
         lbl.selectable = true
     end
-    if options[0] && options[0][:topwidget]
-        dialog.vbox.add(options[0][:topwidget])
-    end
     if options[0] && options[0][:scrolled]
         sw = Gtk::ScrolledWindow.new(nil, nil)
         sw.set_policy(Gtk::POLICY_AUTOMATIC, Gtk::POLICY_AUTOMATIC)
@@ -2083,6 +2126,8 @@ def backend_wait_message(parent, msg, infopipe_path, mode)
     infopipe = File.open(infopipe_path, File::RDONLY | File::NONBLOCK)
     refresh_thread = Thread.new {
         directories_counter = 0
+        #- immediately stops if trying to read before file is written from backend.. simple dirty solution for the moment
+        sleep 1 
         while line = infopipe.gets
             msg 3, "infopipe got data: #{line}"
             if line =~ /^directories: (\d+), sizes: (\d+)/
@@ -2157,11 +2202,9 @@ end
 
 def call_backend(cmd, waitmsg, mode, params)
     pipe = Tempfile.new("boohpipe")
-    Thread.critical = true
     path = pipe.path
     pipe.close!
     system("mkfifo #{path}")
-    Thread.critical = false
     cmd += " --info-pipe #{path}"
     button, w8 = backend_wait_message($main_window, waitmsg, path, mode)
     pid = nil
@@ -2181,7 +2224,9 @@ def call_backend(cmd, waitmsg, mode, params)
                 #- say nothing, user aborted
             else
                 gtk_thread_protect { show_popup($main_window,
-                                                utf8(_("There was something wrong, sorry:\n\n%s") % $diemsg)) }
+                                                utf8($diemsg ? _("Unexpected internal error, sorry:\n\n%s") % $diemsg :
+                                                               _("Unexpected internal error, sorry.\nCheck console for error message."))) }
+                $diemsg = nil
             end
         else
             exec(cmd)
@@ -2316,6 +2361,8 @@ def sort_by_exif_date
                 date_time = Exif.datetimeoriginal(from_utf8($current_path + "/" + f))
                 if ! date_time.nil?
                     dates[f] = date_time
+                elsif f =~ /(20\d{2}).?(\d{2}).?(\d{2}).(\d{2}).?(\d{2}).?(\d{2})/
+                    dates[f] = "#$1:#$2:#$3 #$4:#$5:#$6"
                 end
             end
             if aborted
@@ -2332,6 +2379,8 @@ def sort_by_exif_date
             date_time = Exif.datetimeoriginal(from_utf8($current_path + "/" + f))
             if ! date_time.nil?
                 dates[f] = date_time
+            elsif f =~ /(20\d{2}).?(\d{2}).?(\d{2}).(\d{2}).?(\d{2}).?(\d{2})/
+                dates[f] = "#$1:#$2:#$3 #$4:#$5:#$6"
             end
         }
     end
@@ -2471,7 +2520,8 @@ def change_dir
                             else
                                 tmpimage = "#{tmpdir}/00000001.jpg"
                                 begin
-                                    preview_img.pixbuf = Gdk::Pixbuf.new(tmpimage, 240, 180)
+                                    preview_img.pixbuf = GdkPixbuf::Pixbuf.new(:file => tmpimage, :width => 240,
+                                                                               :height => 180)
                                     fc.preview_widget_active = true
                                 rescue Gdk::PixbufError
                                     fc.preview_widget_active = false
@@ -2483,7 +2533,7 @@ def change_dir
                         end
                     else
                         begin
-                            preview_img.pixbuf = rotate_pixbuf(Gdk::Pixbuf.new(fc.preview_filename, 240, 180), guess_rotate(fc.preview_filename))
+                            preview_img.pixbuf = rotate_pixbuf(GdkPixbuf::Pixbuf.new(:file => fc.preview_filename, :width => 240, :height => 180), guess_rotate(fc.preview_filename))
                             fc.preview_widget_active = true
                         rescue Gdk::PixbufError
                             fc.preview_widget_active = false
@@ -2792,7 +2842,7 @@ end
 
 def pixbuf_or_nil(filename)
     begin
-        return Gdk::Pixbuf.new(filename)
+        return GdkPixbuf::Pixbuf.new(:file => filename)
     rescue
         return nil
     end
@@ -2820,7 +2870,7 @@ def theme_choose(current)
 
     dialog.vbox.add(sw = Gtk::ScrolledWindow.new(nil, nil).add(treeview).set_policy(Gtk::POLICY_NEVER, Gtk::POLICY_AUTOMATIC))
 
-    ([ $FPATH + '/themes/simple' ] + (`find '#{$FPATH}/themes' ~/.booh-themes -mindepth 1 -maxdepth 1 -type d 2>/dev/null`.find_all { |e| e !~ /simple$/ }.sort)).each { |dir|
+    ([ $FPATH + '/themes/gradient' ] + (`find '#{$FPATH}/themes' ~/.booh-themes -mindepth 1 -maxdepth 1 -type d 2>/dev/null`.split("\n").find_all { |e| e !~ /\bgradient\b/ }.sort)).each { |dir|
         dir.chomp!
         iter = model.append
         iter[0] = File.basename(dir)
@@ -2955,12 +3005,12 @@ def open_file(filename)
         return utf8(_("Corrupted booh file..."))
     end
 
-    if $xmldoc.root.attributes['version'] < '0.9.0'
+    if $xmldoc.root.attributes['version'] < $VERSION
         msg 2, _("File's version %s, booh version now %s, marking dirty") % [ $xmldoc.root.attributes['version'], $VERSION ]
         mark_document_as_dirty
         if $xmldoc.root.attributes['version'] < '0.8.4'
             msg 1, _("File's version prior to 0.8.4, migrating directories and filenames in destination directory if needed")
-            `find '#{source}' -type d -follow`.sort.collect { |v| v.chomp }.each { |dir|
+            `find '#{source}' -type d -follow`.split("\n").sort.collect { |v| v.chomp }.each { |dir|
                 old_dest_dir = make_dest_filename_old(dir.sub(/^#{Regexp.quote(source)}/, dest))
                 new_dest_dir = make_dest_filename(dir.sub(/^#{Regexp.quote(source)}/, dest))
                 if old_dest_dir != new_dest_dir
@@ -3001,7 +3051,7 @@ def open_file(filename)
 
     populate_subalbums_treeview(true)
 
-    $save.sensitive = $save_as.sensitive = $merge_current.sensitive = $merge_newsubs.sensitive = $merge.sensitive = $extend.sensitive = $generate.sensitive = $view_wa.sensitive = $properties.sensitive = $remove_all_captions.sensitive = $sort_by_exif_date.sensitive = true
+    $save.sensitive = $save_as.sensitive = $merge_current.sensitive = $merge_newsubs.sensitive = $merge.sensitive = $extend.sensitive = $generate.sensitive = $view_wa.sensitive = $upload.sensitive = $properties.sensitive = $remove_all_captions.sensitive = $sort_by_exif_date.sensitive = true
     return nil
 end
 
@@ -3015,12 +3065,10 @@ def open_file_user(filename)
         $orig_filename = $filename
         $main_window.title = 'booh - ' + File.basename($orig_filename)
         tmp = Tempfile.new("boohtemp")
-        Thread.critical = true
         $filename = tmp.path
         tmp.close!
         #- for security
         ios = File.open($filename, File::RDWR|File::CREAT|File::EXCL)
-        Thread.critical = false
         ios.close
         $tempfiles << $filename << "#{$filename}.backup"
     else
@@ -3076,7 +3124,7 @@ def open_file_popup
     }
     ok = false
     while !ok
-        if fc.run == Gtk::Dialog::RESPONSE_ACCEPT
+        if fc.run == Gtk::Dialog::RESPONSE_ACCEPT && fc.filename
             push_mousecursor_wait(fc)
             msg = open_file_user(fc.filename)
             pop_mousecursor(fc)
@@ -3102,6 +3150,9 @@ def additional_booh_options
     if $config['transcode-videos']
         options += "--transcode-videos '#{$config['transcode-videos']}' "
     end
+    if $config['use-mp4'] == 'true'
+        options += "--mp4-generator '#{$config['mp4-generator']}' "
+    end
     return options
 end
 
@@ -3267,7 +3318,7 @@ def new_album
     tooltips = Gtk::Tooltips.new
     frame2 = Gtk::Frame.new(utf8(_("Configuration"))).add(vb = Gtk::VBox.new)
     vb.add(Gtk::HBox.new(false, 3).pack_start(Gtk::Label.new(utf8(_("Theme: "))), false, false, 0).
-                         pack_start(theme_button = Gtk::Button.new($config['default-theme'] || 'simple'), false, false, 0))
+                         pack_start(theme_button = Gtk::Button.new($config['default-theme'] || 'gradient'), false, false, 0))
     vb.add(Gtk::HBox.new(false, 3).pack_start(Gtk::Label.new(utf8(_("Sizes of images to generate: "))), false, false, 0).
                                    pack_start(sizes = Gtk::HBox.new, false, false, 0))
     vb.add(optimize432 = Gtk::CheckButton.new(utf8(_("Optimize for 3/2 aspect ratio"))).set_active($config['default-optimize32'].to_b))
@@ -3316,39 +3367,69 @@ def new_album
     vb.add(Gtk::HBox.new(false, 3).pack_start(Gtk::Label.new(utf8(_("'Made with' markup on pages bottom: "))), false, false, 0).
                                    pack_start(madewithentry = Gtk::Entry.new.set_text(utf8(_("made with <a href=%booh>booh</a>!"))), true, true, 0))
     tooltips.set_tip(madewithentry, utf8(_("Optional HTML markup to use on pages bottom for a small 'made with' label; %booh is replaced by the website of booh;\nfor example: made with <a href=%booh>booh</a>!")), nil)
+    vb.add(addthis = Gtk::CheckButton.new(utf8(_("Include the 'addthis' bookmarking and sharing button"))).set_active($config['default-addthis'].to_b))
+    vb.add(quotehtml = Gtk::CheckButton.new(utf8(_("Quote HTML markup in captions"))).set_active($config['default-quotehtml'].to_b))
+    tooltips.set_tip(quotehtml, utf8(_("If checked, text using markup special characters such as '<grin>' will be shown properly; if unchecked, markup such as '<a href..' links will be interpreted by the browser properly")), nil)
 
     src_nb_calculated_for = ''
-    src_nb_thread = nil
+    src_nb_process = nil
     process_src_nb = proc {
         if src.text != src_nb_calculated_for
             src_nb_calculated_for = src.text
-            if src_nb_thread
-                Thread.kill(src_nb_thread)
-                src_nb_thread = nil
+            if src_nb_process
+                begin
+                    Process.kill(9, src_nb_process)
+                rescue Errno::ESRCH
+                    #- process doesn't exist anymore - race condition
+                end
             end
             if src_nb_calculated_for != '' && from_utf8_safe(src_nb_calculated_for) == ''
                 src_nb.set_markup(utf8(_("<span size='small'><i>invalid source directory</i></span>")))
             else
                 if File.directory?(from_utf8_safe(src_nb_calculated_for)) && src_nb_calculated_for != '/'
                     if File.readable?(from_utf8_safe(src_nb_calculated_for))
-                        src_nb_thread = Thread.new {
-                            gtk_thread_protect { src_nb.set_markup(utf8(_("<span size='small'><i>processing...</i></span>"))) }
-                            total = { 'image' => 0, 'video' => 0, nil => 0 }
-                            `find '#{from_utf8_safe(src_nb_calculated_for)}' -type d -follow`.each { |dir|
-                                if File.basename(dir) =~ /^\./
-                                    next
-                                else
-                                    begin
-                                        Dir.entries(dir.chomp).each { |file|
-                                            total[entry2type(file)] += 1
-                                        }
-                                    rescue Errno::EACCES, Errno::ENOENT
+                        rd, wr = IO.pipe
+                        if src_nb_process
+                            while src_nb_process
+                                msg 3, "sleeping for completion of previous process"
+                                sleep 0.05
+                            end
+                            gtk_thread_flush  #- flush to avoid race condition in src_nb markup update
+                        end
+                        src_nb.set_markup(utf8(_("<span size='small'><i>processing...</i></span>")))
+                        total = { 'image' => 0, 'video' => 0, nil => 0 }
+                        if src_nb_process = fork
+                            msg 3, "spawned #{src_nb_process} for #{src_nb_calculated_for}"
+                            #- parent
+                            wr.close
+                            Thread.new {
+                                rd.readlines.each { |dir|
+                                    if File.basename(dir) =~ /^\./
+                                        next
+                                    else
+                                        begin
+                                            Dir.entries(dir.chomp).each { |file|
+                                                total[entry2type(file)] += 1
+                                            }
+                                        rescue Errno::EACCES, Errno::ENOENT
+                                        end
                                     end
+                                }
+                                rd.close
+                                msg 3, "ripping #{src_nb_process}"
+                                dummy, exitstatus = Process.waitpid2(src_nb_process)
+                                if exitstatus == 0
+                                    gtk_thread_protect { src_nb.set_markup(utf8(_("<span size='small'><i>%s photos and %s videos</i></span>") % [ total['image'], total['video'] ])) }
                                 end
+                                src_nb_process = nil
                             }
-                            gtk_thread_protect { src_nb.set_markup(utf8(_("<span size='small'><i>%s photos and %s videos</i></span>") % [ total['image'], total['video'] ])) }
-                            src_nb_thread = nil
-                        }
+                            
+                        else
+                            #- child
+                            rd.close
+                            wr.write(`find '#{from_utf8_safe(src_nb_calculated_for)}' -type d -follow`)
+                            Process.exit!(0)  #- _exit
+                        end                       
                     else
                         src_nb.set_markup(utf8(_("<span size='small'><i>permission denied</i></span>")))
                     end
@@ -3370,7 +3451,7 @@ def new_album
                                         nil,
                                         [Gtk::Stock::OPEN, Gtk::Dialog::RESPONSE_ACCEPT], [Gtk::Stock::CANCEL, Gtk::Dialog::RESPONSE_CANCEL])
         fc.transient_for = $main_window
-        if fc.run == Gtk::Dialog::RESPONSE_ACCEPT
+        if fc.run == Gtk::Dialog::RESPONSE_ACCEPT && fc.filename
             src.text = utf8(fc.filename)
             process_src_nb.call
             conf.text = File.expand_path("~/.booh/#{File.basename(src.text)}")
@@ -3385,7 +3466,7 @@ def new_album
                                         nil,
                                         [Gtk::Stock::OPEN, Gtk::Dialog::RESPONSE_ACCEPT], [Gtk::Stock::CANCEL, Gtk::Dialog::RESPONSE_CANCEL])
         fc.transient_for = $main_window
-        if fc.run == Gtk::Dialog::RESPONSE_ACCEPT
+        if fc.run == Gtk::Dialog::RESPONSE_ACCEPT && fc.filename
             dest.text = utf8(fc.filename)
         end
         fc.destroy
@@ -3400,7 +3481,7 @@ def new_album
         fc.transient_for = $main_window
         fc.add_shortcut_folder(File.expand_path("~/.booh"))
         fc.set_current_folder(File.expand_path("~/.booh"))
-        if fc.run == Gtk::Dialog::RESPONSE_ACCEPT
+        if fc.run == Gtk::Dialog::RESPONSE_ACCEPT && fc.filename
             conf.text = utf8(fc.filename)
         end
         fc.destroy
@@ -3518,15 +3599,27 @@ Are you sure you want to continue?")), { :okcancel => true })
         $config['default-theme'] = theme
         $config['default-multi-languages'] = multilanguages_value
         $config['default-optimize32'] = optimize432.active?.to_s
+        $config['default-addthis'] = addthis.active?.to_s
+        $config['default-quotehtml'] = quotehtml.active?.to_s
         sizes = theme_sizes.find_all { |e| e[:widget].active? }.collect { |e| e[:value] }.join(',')
         nperrow = nperrows.find { |e| e[:widget].active? }[:value]
         nperpage = nperpage_model.get_value(nperpagecombo.active_iter, 1)
         opt432 = optimize432.active?
         madewith = madewithentry.text.gsub('\'', '&#39;')  #- because the parameters to booh-backend are between apostrophes
         indexlink = indexlinkentry.text.gsub('\'', '&#39;')
+        athis = addthis.active?
+        qhtml = quotehtml.active?
     end
-    if src_nb_thread
-        Thread.kill(src_nb_thread)
+    if src_nb_process
+        begin
+            Process.kill(9, src_nb_process)
+            while src_nb_process
+                msg 3, "sleeping for completion of previous process"
+                sleep 0.05
+            end
+        rescue Errno::ESRCH
+            #- process doesn't exist
+        end
         gtk_thread_flush  #- needed because we're about to destroy widgets in dialog, for which they may be some pending gtk calls
     end
     dialog.destroy
@@ -3537,7 +3630,8 @@ Are you sure you want to continue?")), { :okcancel => true })
                      "--verbose-level #{$verbose_level} --theme #{theme} --sizes #{sizes} --thumbnails-per-row #{nperrow} " +
                      (nperpage ? "--thumbnails-per-page #{nperpage} " : '') +
                      (multilanguages_value ? "--multi-languages #{multilanguages_value} " : '') +
-                     "#{opt432 ? '--optimize-for-32' : ''} --made-with '#{madewith}' --index-link '#{indexlink}' #{additional_booh_options}",
+                     "#{opt432 ? '--optimize-for-32' : ''} --made-with '#{madewith}' --index-link '#{indexlink}' " +
+                     "#{athis ? '--addthis' : ''} #{qhtml ? '--quote-html' : ''} #{additional_booh_options}",
                      utf8(_("Please wait while scanning source directory...")),
                      'full scan',
                      { :closure_after => proc {
@@ -3566,6 +3660,8 @@ def properties
     end
     madewith = ($xmldoc.root.attributes['made-with'] || '').gsub('&#39;', '\'')
     indexlink = ($xmldoc.root.attributes['index-link'] || '').gsub('&#39;', '\'')
+    athis = !$xmldoc.root.attributes['addthis'].nil?
+    qhtml = !$xmldoc.root.attributes['quote-html'].nil?
     save_multilanguages_value = multilanguages_value = $xmldoc.root.attributes['multi-languages']
 
     tooltips = Gtk::Tooltips.new
@@ -3642,6 +3738,9 @@ def properties
         madewithentry.text = madewith
     end
     tooltips.set_tip(madewithentry, utf8(_('Optional HTML markup to use on pages bottom for a small \'made with\' label; %booh is replaced by the website of booh;\nfor example: made with <a href=%booh>booh</a>!')), nil)
+    vb.add(addthis = Gtk::CheckButton.new(utf8(_("Include the 'addthis' bookmarking and sharing button"))).set_active(athis))
+    vb.add(quotehtml = Gtk::CheckButton.new(utf8(_("Quote HTML markup in captions"))).set_active(qhtml))
+    tooltips.set_tip(quotehtml, utf8(_("If checked, text using markup special characters such as '<grin>' will be shown properly; if unchecked, markup such as '<a href..' links will be interpreted by the browser properly")), nil)
 
     theme_sizes = []
     nperrows = []
@@ -3728,9 +3827,11 @@ def properties
     save_nperpage = nperpage_model.get_value(nperpagecombo.active_iter, 1)
     save_madewith = madewithentry.text.gsub('\'', '&#39;')  #- because the parameters to booh-backend are between apostrophes
     save_indexlink = indexlinkentry.text.gsub('\'', '&#39;')
+    save_addthis = addthis.active?
+    save_quotehtml = quotehtml.active?
     dialog.destroy
     
-    if ok && (save_theme != theme || save_limit_sizes != limit_sizes || save_opt432 != opt432 || save_nperrow != nperrow || save_nperpage != nperpage || save_madewith != madewith || save_indexlink != indexlinkentry || save_multilanguages_value != multilanguages_value)
+    if ok && (save_theme != theme || save_limit_sizes != limit_sizes || save_opt432 != opt432 || save_nperrow != nperrow || save_nperpage != nperpage || save_madewith != madewith || save_indexlink != indexlink || save_multilanguages_value != multilanguages_value || save_quotehtml != qhtml || save_addthis != athis)
         #- some sort of automatic preferences
         if save_theme != theme
             $config['default-theme'] = save_theme
@@ -3741,13 +3842,20 @@ def properties
         if save_opt432 != opt432
             $config['default-optimize32'] = save_opt432.to_s
         end
+        if save_addthis != athis
+            $config['default-addthis'] = save_addthis.to_s
+        end
+        if save_quotehtml != qhtml
+            $config['default-quotehtml'] = save_quotehtml.to_s
+        end
         mark_document_as_dirty
         save_current_file
         call_backend("booh-backend --use-config '#{$filename}' --for-gui --verbose-level #{$verbose_level} " +
                      "--thumbnails-per-row #{save_nperrow} --theme #{save_theme} --sizes #{save_limit_sizes.join(',')} " +
                      (save_nperpage ? "--thumbnails-per-page #{save_nperpage} " : '') +
                      (save_multilanguages_value ? "--multi-languages #{save_multilanguages_value} " : '') +
-                     "#{save_opt432 ? '--optimize-for-32' : ''} --made-with '#{save_madewith}' --index-link '#{save_indexlink}' #{additional_booh_options}",
+                     "#{save_opt432 ? '--optimize-for-32' : ''} --made-with '#{save_madewith}' --index-link '#{save_indexlink}' " +
+                     "#{save_addthis ? '--addthis' : ''} #{save_quotehtml ? '--quote-html' : ''} #{additional_booh_options}",
                      utf8(_("Please wait while scanning source directory...")),
                      'full scan',
                      { :closure_after => proc {
@@ -3824,7 +3932,7 @@ def save_as_do
     fc.add_shortcut_folder(File.expand_path("~/.booh"))
     fc.set_current_folder(File.expand_path("~/.booh"))
     fc.filename = $orig_filename
-    if fc.run == Gtk::Dialog::RESPONSE_ACCEPT
+    if fc.run == Gtk::Dialog::RESPONSE_ACCEPT && fc.filename
         $orig_filename = fc.filename
         if ! save_current_file_user
             fc.destroy
@@ -3843,39 +3951,64 @@ def preferences
                              [Gtk::Stock::OK, Gtk::Dialog::RESPONSE_OK],
                              [Gtk::Stock::CANCEL, Gtk::Dialog::RESPONSE_CANCEL])
 
+    table_counter = 0
     dialog.vbox.add(notebook = Gtk::Notebook.new)
     notebook.append_page(tbl = Gtk::Table.new(0, 0, false), Gtk::Label.new(utf8(_("Options"))))
     tbl.attach(Gtk::Alignment.new(1, 0.5, 0, 0).add(Gtk::Label.new.set_markup(utf8(_("Command for watching videos: ")))),
-               0, 1, 0, 1, Gtk::FILL, Gtk::SHRINK, 2, 2)
+               0, 1, table_counter, table_counter + 1, Gtk::FILL, Gtk::SHRINK, 2, 2)
     tbl.attach(Gtk::Alignment.new(0, 0.5, 1, 0).add(video_viewer_entry = Gtk::Entry.new.set_text($config['video-viewer']).set_size_request(250, -1)),
-               1, 2, 0, 1, Gtk::FILL, Gtk::SHRINK, 2, 2)
+               1, 2, table_counter, table_counter + 1, Gtk::FILL, Gtk::SHRINK, 2, 2)
     tooltips = Gtk::Tooltips.new
     tooltips.set_tip(video_viewer_entry, utf8(_("Use %f to specify the filename;
 for example: /usr/bin/mplayer %f")), nil)
+
+    table_counter += 1
     tbl.attach(Gtk::Alignment.new(1, 0.5, 0, 0).add(Gtk::Label.new.set_markup(utf8(_("Command for editing images: ")))),
-               0, 1, 1, 2, Gtk::FILL, Gtk::SHRINK, 2, 2)
+               0, 1, table_counter, table_counter + 1, Gtk::FILL, Gtk::SHRINK, 2, 2)
     tbl.attach(Gtk::Alignment.new(0, 0.5, 1, 0).add(image_editor_entry = Gtk::Entry.new.set_text($config['image-editor'])),
-               1, 2, 1, 2, Gtk::FILL, Gtk::SHRINK, 2, 2)
+               1, 2, table_counter, table_counter + 1, Gtk::FILL, Gtk::SHRINK, 2, 2)
     tooltips.set_tip(image_editor_entry, utf8(_("Use %f to specify the filename;
 for example: /usr/bin/gimp-remote %f")), nil)
+
+    table_counter += 1
     tbl.attach(Gtk::Alignment.new(1, 0.5, 0, 0).add(Gtk::Label.new.set_markup(utf8(_("Browser's command: ")))),
-               0, 1, 2, 3, Gtk::FILL, Gtk::SHRINK, 2, 2)
+               0, 1, table_counter, table_counter + 1, Gtk::FILL, Gtk::SHRINK, 2, 2)
     tbl.attach(Gtk::Alignment.new(0, 0.5, 1, 0).add(browser_entry = Gtk::Entry.new.set_text($config['browser'])),
-               1, 2, 2, 3, Gtk::FILL, Gtk::SHRINK, 2, 2)
+               1, 2, table_counter, table_counter + 1, Gtk::FILL, Gtk::SHRINK, 2, 2)
     tooltips.set_tip(browser_entry, utf8(_("Use %f to specify the filename;
 for example: /usr/bin/mozilla-firefox -remote 'openURL(%f,new-window)' || /usr/bin/mozilla-firefox %f")), nil)
+
+    table_counter += 1
+    tbl.attach(Gtk::Alignment.new(1, 0.5, 0, 0).add(mp4_check = Gtk::CheckButton.new(utf8(_("Use this .mp4 generator for videos:")))),
+               0, 1, table_counter, table_counter + 1, Gtk::FILL, Gtk::SHRINK, 2, 2)
+    tbl.attach(Gtk::Alignment.new(0, 0.5, 1, 0).add(mp4_generator_entry = Gtk::Entry.new.set_text($config['mp4-generator']).set_sensitive(false)),
+               1, 2, table_counter, table_counter + 1, Gtk::FILL, Gtk::SHRINK, 2, 2)
+    tooltips.set_tip(mp4_generator_entry, utf8(_("Use %f to specify the input filename, %o the output filename;
+for example: /usr/bin/ffmpeg -i %f -b ${i}k -ar 22050 -ab 32k %o")), nil)
+
+    table_counter += 1
     tbl.attach(Gtk::Alignment.new(1, 0.5, 0, 0).add(smp_check = Gtk::CheckButton.new(utf8(_("Use symmetric multi-processing")))),
-               0, 1, 3, 4, Gtk::FILL, Gtk::SHRINK, 2, 2)
+               0, 1, table_counter, table_counter + 1, Gtk::FILL, Gtk::SHRINK, 2, 2)
     tbl.attach(Gtk::Alignment.new(0, 0.5, 1, 0).add(smp_hbox = Gtk::HBox.new.add(smp_spin = Gtk::SpinButton.new(2, 16, 1)).add(Gtk::Label.new(utf8(_("processors")))).set_sensitive(false)),
-               1, 2, 3, 4, Gtk::FILL, Gtk::SHRINK, 2, 2)
+               1, 2, table_counter, table_counter + 1, Gtk::FILL, Gtk::SHRINK, 2, 2)
     tooltips.set_tip(smp_check, utf8(_("When activated, this option allows the thumbnails creation to run faster. However, if you don't have a multi-processor machine, this will only slow down processing!")), nil)
+
+    table_counter += 1
     tbl.attach(nogestures_check = Gtk::CheckButton.new(utf8(_("Disable mouse gestures"))),
-               0, 2, 4, 5, Gtk::FILL, Gtk::SHRINK, 2, 2)
+               0, 2, table_counter, table_counter + 1, Gtk::FILL, Gtk::SHRINK, 2, 2)
     tooltips.set_tip(nogestures_check, utf8(_("Mouse gestures are 'unusual' mouse movements triggering special actions, and are great for speeding up your editions. Get details on available mouse gestures from the Help menu.")), nil)
+
+    table_counter += 1
     tbl.attach(deleteondisk_check = Gtk::CheckButton.new(utf8(_("Delete original photos/videos as well"))),
-               0, 2, 6, 7, Gtk::FILL, Gtk::SHRINK, 2, 2)
+               0, 2, table_counter, table_counter + 1, Gtk::FILL, Gtk::SHRINK, 2, 2)
     tooltips.set_tip(deleteondisk_check, utf8(_("Normally, deleting a photo or video in booh only removes it from the web-album. If you check this option, the original file in source directory will be removed as well. Undo is possible, since actual deletion is performed only when web-album is saved.")), nil)
 
+    mp4_check.signal_connect('toggled') {
+        mp4_generator_entry.sensitive = mp4_check.active?
+    }
+    if $config['use-mp4'] == 'true'
+        mp4_check.active = true
+    end
     smp_check.signal_connect('toggled') {
         smp_hbox.sensitive = smp_check.active?
     }
@@ -4062,6 +4195,12 @@ for example: avi:mencoder -nosound -ovc xvid -xvidencopts bitrate=800:me_quality
             $config['video-viewer'] = from_utf8(video_viewer_entry.text)
             $config['image-editor'] = from_utf8(image_editor_entry.text)
             $config['browser'] = from_utf8(browser_entry.text)
+            if mp4_check.active?
+                $config['use-mp4'] = 'true'
+                $config['mp4-generator'] = from_utf8(mp4_generator_entry.text)
+            else
+                $config['use-mp4'] = 'false'
+            end
             if smp_check.active?
                 $config['mproc'] = smp_spin.value.to_i
             else
@@ -4081,6 +4220,8 @@ for example: avi:mencoder -nosound -ovc xvid -xvidencopts bitrate=800:me_quality
         end
     }
     dialog.destroy
+
+    check_config_preferences_dep
 end
 
 def perform_undo
@@ -4112,6 +4253,173 @@ Click the <span foreground='darkblue'>None</span> icon when you're finished with
 ") % intro), { :pos_centered => true })
 end
 
+def perform_remote_synchronization(url, detail_label, progressbar_window, dialog, read, write)
+    begin
+        gtk_thread_protect {
+            detail_label.set_markup("<i>" + utf8(_("Logging into remote site...")) + "</i>")
+        }
+        lftp_additionals = $config['lftp-additionals']
+        if lftp_additionals
+            msg 3, "Adding lftp additionals:\n" + lftp_additionals
+            write.puts(lftp_additionals)
+        else
+            msg 3, "No lftp additionals"
+        end
+        write.puts("set net:max-retries 1")
+        write.puts("set cmd:fail-exit true")
+        write.puts("open " + url)
+        write.puts("lcd " + File.dirname($xmldoc.root.attributes['destination']))
+        write.puts("ls")  #- force connection and fail on exit, in order to detect problems with host or path
+        write.puts("echo __ls_EOF_1234567890abcdefghijk")  #- detect end
+
+        ok_to_mirror = false
+        while line = read.gets
+            msg 3, "received from lftp (login stage): #{line}"
+            if line == "__ls_EOF_1234567890abcdefghijk\n"
+                ok_to_mirror = true
+                break
+            end
+        end
+        if ! ok_to_mirror
+            gtk_thread_protect {
+                progressbar_window.destroy
+                show_popup(dialog, utf8(_("Failed to connect to specified URL, please check your input.")), { :pos_centered => true })
+            }
+            return
+        end
+
+        msg 3, "lftp login and ls ok, mirroring..."
+        gtk_thread_protect {
+            detail_label.set_markup("<i>" + utf8(_("Mirroring data...")) + "</i>")
+        }
+        mirrored_successfully = false
+        write.puts("set net:max-retries 5")
+        write.puts("mirror -R " + File.basename($xmldoc.root.attributes['destination']))
+        write.puts("echo __finished___ls_EOF_1234567890abcdefghijk")  #- detect end
+        while line = read.gets
+            msg 3, "received from lftp (mirror stage): #{line}"
+            if line == "__finished___ls_EOF_1234567890abcdefghijk\n"
+                mirrored_successfully = true
+                break
+            end
+        end
+
+        write.close
+        read.close
+
+        gtk_thread_protect {
+            progressbar_window.destroy
+            $main_window.urgency_hint = true
+            if mirrored_successfully
+                show_popup(dialog, utf8(_("Successfully mirrored into remote repository.")), { :pos_centered => true })
+            else
+                show_popup(dialog, utf8(_("Failed to mirror into remote repository.")), { :pos_centered => true })
+            end
+        }
+
+    rescue
+        msg 3, "failed lftp dialog: #{$!}"
+    end
+end
+
+def remote_synchronization
+
+    remote_synchro = Gtk::Dialog.new(utf8(_("Upload web-album")),
+                                     $main_window,
+                                     Gtk::Dialog::MODAL | Gtk::Dialog::DESTROY_WITH_PARENT,
+                                     [Gtk::Stock::CLOSE, Gtk::Dialog::RESPONSE_OK])
+    remote_synchro.vbox.add(Gtk::Label.new.set_markup(utf8(_("<b>Upload web-album.</b>
+
+Mirror web-album into remote repository (lftp URL):
+<i>The destination directory '%s' will be created/updated there.</i>") % File.basename($xmldoc.root.attributes['destination']))).set_alignment(0, 0))
+    remote_synchro.vbox.add(Gtk::HBox.new(false, 0).pack_start(repo = Gtk::Entry.new.set_text($xmldoc.root.attributes['remote_synchronization_url'] || ''), true, true).
+                                                    pack_start(mirror = Gtk::Button.new(utf8(_("Upload"))).set_image(Gtk::Image.new("#{$FPATH}/images/stock-upload-16.png")), false, false))
+
+    mirror.signal_connect('clicked') {
+        if $xmldoc.root.attributes['remote_synchronization_url'] != repo.text
+            $modified = true
+            $xmldoc.root.add_attribute('remote_synchronization_url', repo.text)
+        end
+        w = create_window
+        w.set_transient_for(remote_synchro)
+        w.modal = true
+        vb = Gtk::VBox.new(false, 5).set_border_width(5)
+        vb.pack_start(Gtk::Label.new(utf8(_("Please wait, mirroring..."))), false, false)
+        vb.pack_start(detail = Gtk::Label.new.set_markup("<i>" + utf8(_("Initialization...")) + "</i>"), false, false)
+        vb.pack_start(pb = Gtk::ProgressBar.new, false, false)
+        bottom = Gtk::Alignment.new(0.5, 0.5, 0, 0).add(b = Gtk::Button.new(utf8(_("_Abort"))))
+        b.image = Gtk::Image.new("#{$FPATH}/images/stock-close-24.png")
+        vb.pack_end(bottom, false, false)
+        refresh_thread = Thread.new {
+            while true
+                gtk_thread_protect { pb.pulse }
+                sleep 0.5
+            end
+        }
+        w.add(vb)
+        w.signal_connect('delete-event') { w.destroy }
+        w.signal_connect('destroy') {
+            Thread.kill(refresh_thread)
+            gtk_thread_flush  #- needed because we're about to destroy widgets in w, for which they may be some pending gtk calls
+        }
+        w.window_position = Gtk::Window::POS_CENTER
+
+        pid = nil
+        cmd = 'lftp'
+        rd1, wr1 = IO.pipe
+        rd2, wr2 = IO.pipe
+
+        if ! pid = fork
+            rd2.close
+            wr1.close
+            $stdin.reopen(rd1)
+            $stdout.reopen(wr2)
+            $stderr.reopen(wr2)
+            begin
+                exec(cmd)
+            rescue
+                Process.exit!(66)  #- _exit
+            end
+        end
+
+        rd1.close
+        wr2.close
+
+        remote_synchronization_thread = Thread.new {
+            perform_remote_synchronization(repo.text, detail, w, remote_synchro, rd2, wr1)
+            begin
+                Process.kill('SIGTERM', pid)
+            rescue
+            end
+        }
+
+        b.signal_connect('clicked') {
+            Thread.kill(remote_synchronization_thread)
+            begin
+                Process.kill('SIGTERM', pid)
+            rescue
+                #- race condition (process just died)
+            end
+            w.destroy
+        }
+        w.show_all
+
+        Thread.new {
+            id, exitstatus = Process.waitpid2(pid)
+            if exitstatus >> 8 == 66
+                gtk_thread_protect {
+                    w.destroy
+                    show_popup(remote_synchro, utf8(_("Failed to execute 'lftp' program.")), { :pos_centered => true })
+                }
+            end
+        }
+    }
+
+    remote_synchro.window_position = Gtk::Window::POS_CENTER
+    remote_synchro.show_all
+    remote_synchro.run { remote_synchro.destroy }
+end
+
 def create_menu_and_toolbar
     
     #- menu
@@ -4144,6 +4452,8 @@ def create_menu_and_toolbar
     tooltips.set_tip($generate, utf8(_("(Re)generate web-album from latest changes into the destination directory")), nil)
     filesubmenu.append($view_wa = Gtk::ImageMenuItem.new(utf8(_("View web-album with browser"))).set_sensitive(false))
     $view_wa.image = Gtk::Image.new("#{$FPATH}/images/stock-view-webalbum-16.png")
+    filesubmenu.append($upload = Gtk::ImageMenuItem.new(utf8(_("Upload web-album"))).set_sensitive(false))
+    $upload.image = Gtk::Image.new("#{$FPATH}/images/stock-upload-16.png")
     filesubmenu.append(            Gtk::SeparatorMenuItem.new)
     filesubmenu.append($properties = Gtk::ImageMenuItem.new(Gtk::Stock::PROPERTIES).set_sensitive(false))
     tooltips.set_tip($properties, utf8(_("View and modify properties of the web-album")), nil)
@@ -4168,9 +4478,9 @@ def create_menu_and_toolbar
                      { :successmsg => $xmldoc.root.attributes['multi-languages'] ?
                          utf8(_("Your web-album is now ready in directory '%s'.
 As multi-languages is activated, you will not be able to view it
-comfortably in your browser though.") % $xmldoc.root.attributes['destination']) :
+locally in your browser though.") % $xmldoc.root.attributes['destination']) :
                          utf8(_("Your web-album is now ready in directory '%s'.
-Click to view it in your browser:") % $xmldoc.root.attributes['destination']),
+Click to view it in your browser:") % [ $xmldoc.root.attributes['destination'] ]),
                        :successmsg_linkurl => $xmldoc.root.attributes['multi-languages'] ? $xmldoc.root.attributes['destination'] :
                                                                                            $xmldoc.root.attributes['destination'] + '/index.html',
                        :closure_after => proc {
@@ -4194,6 +4504,18 @@ Click to view it in your browser:") % $xmldoc.root.attributes['destination']),
             show_popup($main_window, utf8(_("Seems like you should generate the web-album first.")))
         end
     }
+    $upload.signal_connect('activate') {
+        indexhtml = $xmldoc.root.attributes['destination'] + '/index.html'
+        if File.exists?(indexhtml)
+            if !system("which lftp >/dev/null 2>/dev/null")
+                show_popup($main_window, utf8(_("The program 'lftp' is needed to upload web-albums. Please install it.")), { :pos_centered => true })
+            else
+                remote_synchronization
+            end
+        else
+            show_popup($main_window, utf8(_("Seems like you should generate the web-album first.")))
+        end
+    }
     $properties.signal_connect('activate') { properties }
 
     quit.signal_connect('activate') { try_quit }
@@ -4714,22 +5036,6 @@ def create_main_window
         pop_mousecursor
     }
 
-#    offset = "0:0"
-#    Gtk.timeout_add(1000) {
-#        puts "trying offset #{offset}"
-#        iter = $albums_ts.get_iter(offset)
-#        if iter
-#            puts "...ok at offset #{offset}"
-#            $current_path = $albums_ts.get_value(iter, 1)
-#            change_dir
-#        end
-#        if offset == "0:0"
-#           offset = "0:1"
-#        else   
-#           offset = "0:0"
-#        end
-#    }
-
     $albums_tv.signal_connect('button-release-event') { |w, event|
         if event.event_type == Gdk::Event::BUTTON_RELEASE && event.button == 3 && !$current_path.nil?
             menu = Gtk::Menu.new
@@ -4751,7 +5057,7 @@ def create_main_window
         end
     }
 
-    $albums_ts = Gtk::TreeStore.new(String, String, Gdk::Pixbuf)
+    $albums_ts = Gtk::TreeStore.new(String, String, GdkPixbuf::Pixbuf)
     $albums_tv.set_model($albums_ts)
     $albums_tv.signal_connect('realize') { $albums_tv.grab_focus }
 
@@ -4839,13 +5145,16 @@ if str = Gtk.check_version(2, 8, 0)
     exit
 end
 
+handle_options
+
 binding_version = Gtk::BINDING_VERSION
+msg 3, "binding version: " + binding_version.join('.')
 if binding_version == [ 0, 15, 0 ]
     puts "It seems that we're running ruby-gtk2 0.15.0; this version is known to crash; please upgrade or downgrade."
     exit
 end
 if binding_version == [ 0, 17, 0 ]
-    puts "It seems that we're running ruby-gtk2 0.17.0; this version is known to has a serious memory leak; please upgrade or downgrade."
+    puts "It seems that we're running ruby-gtk2 0.17.0; this version is known to have a serious memory leak; please upgrade or downgrade."
     exit
 end
 if binding_version == [ 0, 18, 0 ]
@@ -4858,7 +5167,6 @@ if binding_version[0] <= 0 && binding_version[1] <= 16 && ruby_version[0] >= 1 &
     exit
 end
 
-handle_options
 Thread.abort_on_exception = true
 read_config