librelist archives

« back to archive

Green Shoes: exception handling

Green Shoes: exception handling

From:
Ola Leifler
Date:
2013-04-24 @ 18:55
Hi,

Another issue that I've encountered concerns exception handling. It seems 
as if I cannot catch exceptions gracefully in Green Shoes to display 
dialogs.

This behavior can be seen with the sample code I've supplied previously, 
with the following modifications:

******************************************************************************************
class DbUpgrade

# ....

  def run
    # Mockup DB updater
    @num_upgrade_files.times do |i| 
      callback.call "#{i}_upgrade.sql" if callback
      sleep 0.1
      if i==11
        raise "this exception should be caught"
      end
    end
  end

end

# UpgradeGUI#run_upgrade

    @upgrade_thread=Thread.new do 
      begin
        upgrader.run 
      rescue Exception => e
        # This won't work, why?
        # alert e.message
        # This works fine, however
        @update_file_name=e.message
        @step=@num_steps
      end
    end 

******************************************************************************************
This complete source code for this example:


******************************************************************************************
# -*- coding: utf-8 -*-
require 'green_shoes'
require 'optparse'

class Conf
  class << self
    attr_accessor :server, :db, :username, :password, :file_name

    def product_name
      "My product"
    end

    def product
      options[:product] || "product"
    end

    def server
      @server ||= options[:server]
    end

    def db
      @db ||= options[:db]
    end

    def username
      @username ||= options[:username]
    end

    def password
      @password ||= options[:password]
    end

    def sspi?
      options[:sspi]
    end

    def batch?
      options[:batch]
    end
    
    def backup_dir
      Etc.systmpdir
    end

    def sql_dir
      "."
    end

    def options
      @options ||= {}
      # Ignore command line arguments when running this script as a
      # standalone executable compiled by Ocra, in which case the
      # environment variable OCRA_EXECUTABLE is set to $0
      if @options.empty? && !ENV["OCRA_EXECUTABLE"]
        OptionParser.new do |opts|
          opts.banner = "Usage: ruby #{__FILE__} [options]"      
          opts.on("-p", "--product PRODUCT", "Select product") do |p|
            options[:product] = p
          end
          opts.on("-s", "--server SERVER", "Select server") do |p|
            options[:server] = p
          end
          opts.on("-d", "--database DATABASE", "Select database") do |p|
            options[:db] = p
          end
          opts.on("-u", "--username USERNAME", "SQL Server username") do |p|
            options[:username] = p
          end
          opts.on("-P", "--password PASSWORD", "SQL Server password") do |p|
            options[:password] = p
          end
          opts.on("-E", "--sspi", "Use Windows Authentication (SSPI)") do |p|
            options[:sspi] = true
          end
          opts.on("-b", "--batch", "Upgrade and close immediately") do |p|
            options[:batch] = true
          end
        end.parse!
      end
      @options
    end

  end
end

class Shoes
  class App   
    def icon filename=nil
      filename.nil? ? win.icon : win.icon = filename
      Gtk::Window.set_default_icon(win.icon)
    end
  end
end

# Only call Gtk.main_quit once to avoid warnings or errors related to
# "no Gtk main loop running"
class << Gtk; attr_accessor :running; end

class Object

  # Make sure Green Shoes does not crash on exit when exit is called with status
  def exit(status=true)
    if Gtk.running
      Gtk.running=false
      Gtk.main_quit
      File.delete Shoes::TMP_PNG_FILE if File.exist? Shoes::TMP_PNG_FILE
    end
  end
  
  # Override Green Shoes "confirm" method to provide dialog with a custom title
  def confirm title, msg
    $dde = true
    dialog = Gtk::Dialog.new(
      title, 
      get_win,
      Gtk::Dialog::MODAL | Gtk::Dialog::DESTROY_WITH_PARENT,
      [Gtk::Stock::OK, Gtk::Dialog::RESPONSE_ACCEPT],
      [Gtk::Stock::CANCEL, Gtk::Dialog::RESPONSE_REJECT]
    )
    dialog.vbox.add Gtk::Label.new msg
    dialog.set_size_request 500, 100
    dialog.show_all
    ret = dialog.run == Gtk::Dialog::RESPONSE_ACCEPT
    dialog.destroy
    ret
  end

end

class DbUpgrade

  class << self
    def create(conf,product)
      new(conf,product)
    end
  end

  attr_accessor :callback, :num_upgrade_files

  def initialize(conf,product)
    @num_upgrade_files=1_500
  end

  def run
    # Mockup DB updater
    @num_upgrade_files.times do |i| 
      callback.call "#{i}_upgrade.sql" if callback
      sleep 0.1
      if i==11
        raise "this exception should be caught"
      end
    end
  end

end

class UpgradeGUI < Shoes
  url "/", :select
  url "/progress", :progress_window

  def current_setting
    "'#{Conf.server}/#{Conf.db}' for #{Conf.product_name}"
  end
  
  def initialize
    @step=0
    Gtk.running=true
  end

  def select
    background rgb(70,21,107)
    # Skip the form if command line parameters are provided
    if Conf.server && 
        Conf.db && 
        (Conf.sspi? || (Conf.username && Conf.password))
      visit "/progress"
    end
    flow do
      # Absolute positioning seems necessary here, why?
      stack left: 10, top: 100 do
        para "Server"
        @server = edit_line
        @server.text=Conf.server if Conf.server
        para "DB"
        @db = edit_line
        @db.text=Conf.db if Conf.db

        authentication_form

        button "Upgrade" do       
          Conf.server=@server.text
          Conf.db=@db.text
          if ! @use_sspi.checked?
            Conf.username=@username.text
            Conf.password=@password.text
          end
          if confirm("Confirm upgrade", 
                     "Upgrade DB #{current_setting}?")
            visit "/progress"
          end
        end
      end
    end
  end

  def progress_window
    
    background rgb(70,21,107)
    stack do
      para "Upgrading the DB #{current_setting}"
      @upgrade_file=stack { para "..." }
      # Absolute positioning seems necessary here, why?
      @p = progress top: 300, left: 50, width: 400
      
    end # stack
    
    run_upgrade
    
  end # progress_window

  def run_upgrade
    upgrader = DbUpgrade.create Conf, Conf.product
    @num_steps = upgrader.num_upgrade_files
    upgrader.callback = lambda do |file|
      @step+=1
      @update_file_name = file
    end
    @upgrade_thread=Thread.new do 
      begin
        upgrader.run 
      rescue Exception => e
        # This won't work, why?
        # alert e.message
        # This works fine, however
        @update_file_name=e.message
        @step=@num_steps
      end
    end 
    @animate = animate do
      @p.fraction = @step / @num_steps.to_f
      update_name @update_file_name
      if @p.fraction==1.0
        finish_upgrade
      end # if              
    end # animate

  end

  def username_password
    para "Username"
    @username = edit_line
    @username.text=Conf.username if Conf.username
    para "Password"
    @password = edit_line :secret => true
    @password.text=Conf.password if Conf.password
  end

  def para(text)
    # there seems to be a bug related to setting font styles, so I
    # cannot set styles on a slot but must set it on all elements
    super text, stroke: white, family: "Calibri"
  end

  def authentication_form
    flow do
      para "Windows authentication"
      @use_sspi = check do |c|
        if ! c.checked?
          @user_pwd.append do
            stack do
              username_password
            end
          end
        else
          @user_pwd.clear
        end
      end
      @use_sspi.checked=Conf.sspi?
      # Hide the username/password form if Windows authentication is
      # to be used
      @user_pwd = @use_sspi.checked? ? stack : stack { username_password }
    end
  end

  def update_name(name)
    @upgrade_file.clear
    @upgrade_file.append  { para name }
  end

  def finish_upgrade
    @animate.stop
    @upgrade_thread.join

    # Quit immediately when invoked by Ocra, so we can compile
    # this script without manual intervention
    
    if Conf.batch?
      exit
    end

    # update_name "Done!"
    flow do
      # Absolute positioning seems necessary here due to layout 
limitations for progress bars
      button "Close", top: 400, left: 150  do
        exit
      end
    end # flow 
    
  end
  
end

if $0 == __FILE__
  Shoes.app(title: "DB Upgrade tool for #{Conf.product_name}", 
            width: 400, 
            height: 500)
end

Re: [shoes] Green Shoes: exception handling

From:
ashbb
Date:
2013-04-25 @ 13:33
Hi Ola,

> It seems as if I cannot catch exceptions gracefully in Green Shoes
> to display dialogs.
Umm,.. I ran your code and I could see the message "this exception should
be caught" on the window as I expeced...

I'm using Green Shoes 1.1.367 and Ruby 1.9.3p392 on Windows 7.

ashbb

Re: [shoes] Green Shoes: exception handling

From:
Sebastjan Hribar
Date:
2013-04-25 @ 13:39
Hi,
It worked for me as well.
Regards
Seba
Dne 25. apr. 2013 15:33 je "ashbb" <ashbbb@gmail.com> napisal/-a:

> Hi Ola,
>
> > It seems as if I cannot catch exceptions gracefully in Green Shoes
> > to display dialogs.
> Umm,.. I ran your code and I could see the message "this exception should
> be caught" on the window as I expeced...
>
> I'm using Green Shoes 1.1.367 and Ruby 1.9.3p392 on Windows 7.
>
> ashbb
>

Re: [shoes] Green Shoes: exception handling

From:
Ola Leifler
Date:
2013-04-25 @ 18:31
Maybe my temporary fix for this (updating the text instead of displaying a
popup dialog) was confusing.

In my code, I had the following:

# UpgradeGUI#run_upgrade

   @upgrade_thread=Thread.new do 
     begin
       upgrader.run 
     rescue Exception => e
       # This won't work, why?
       # alert e.message
       # This works fine, however
       @update_file_name=e.message
       @step=@num_steps
     end
   end 

So, my question is, why can I not use the "alert" method to display a 
message when handling an exception? Is it due to handling an exception or 
running in a thread other than the Main Gtk update loop?

/Ola

25 apr 2013 kl. 15.39 skrev Sebastjan Hribar:

> Hi,
> It worked for me as well.
> Regards
> Seba
> 
> Dne 25. apr. 2013 15:33 je "ashbb" <ashbbb@gmail.com> napisal/-a:
> Hi Ola,
> 
> > It seems as if I cannot catch exceptions gracefully in Green Shoes
> > to display dialogs.
> Umm,.. I ran your code and I could see the message "this exception 
should be caught" on the window as I expeced...
> 
> I'm using Green Shoes 1.1.367 and Ruby 1.9.3p392 on Windows 7.
> 
> ashbb

Re: [shoes] Green Shoes: exception handling

From:
Steve Klabnik
Date:
2013-04-25 @ 18:34
I've been just wrapping everything in

begin
....
rescue Exception => e
  puts e.inspect
  puts e.backtrace
end

Not optimal, but gets it done.

Re: [shoes] Green Shoes: exception handling

From:
Ola Leifler
Date:
2013-04-26 @ 19:51
Hi,

It has to do with threading issues: if dialog boxes are called from a 
thread other than the main GUI update thread, the redrawing logic seems to
be deadlocked after creating the dialog frame.

Try this:

require 'green_shoes'
Shoes.app do

  button "Boom!" do 
    Thread.new { alert "Hello world!" }
  end
end

/Ola

25 apr 2013 kl. 20.34 skrev Steve Klabnik:

> I've been just wrapping everything in
> 
> begin
> ....
> rescue Exception => e
>  puts e.inspect
>  puts e.backtrace
> end
> 
> Not optimal, but gets it done.

Re: [shoes] Green Shoes: exception handling

From:
ashbb
Date:
2013-04-28 @ 06:05
Hi Ola,

Yeah, that is one of restrictions for all Shoes (i.e. Shoes 3, Shoes 4 and
Green Shoes). You can't open a new window (including dialog boxes) within
Thread.new block.

ashbb