From: Patrik Sundberg Date: 2002-03-24T22:52:22+09:00 Subject: Re: ruby-gtk fatal error On Sun, Mar 24, 2002 at 11:23:35AM +0900, Neil Conway wrote: > On Sat, 2002-03-23 at 17:24, Patrik Sundberg wrote: > > I can't seem to come up with a small script exhibiting this behaviour.. My > > application uses libglade and is about 600 lines of code. I tried adding the > > code which seems to make my app break to helloworld.rb but it doesn't behave > > similair.. Would you like to see the code which do show the problem even > > though it is a bit larger? > > Yes, please. ok. here it goes, but I don't think it is very easy to follow. as you can see this isn't very easy to debug. I have a server listening on a remote computer which gives outside access to a Samba network and a gui which uses this gateway to get files from the samba network. it is an application to search for files on a student network when not on this network (since i live in a non-student apartment). perhaps it can also have something to do with the module mp3list which handles searches to a BIG list of files (the file is around 25Mb) which mmaps the indexfile in a c-module for performance reasons. [outsider.rb]: #!/usr/local/bin/ruby require 'gtk' require 'lglade' require 'gdk_imlib' require 'gnome' require 'zip' require 'mp3list' require 'smbgw/client' # constants GLADEFILE='/usr/local/share/examples/ruby/outsider' LISTFILENAME = 'SUBnets MP3Lista - MP3Get' LISTFILEMIRRORS = ['//Perry/MP3 Listan/', '//Proffe/MP3Listan/', '//n243/MP3Lista', '//area26/mp3listan/'] UPDATE_INTERVAL = 60*60*24*3 BLOCKSIZE = 1024*400 class Download NATIVE = 0 SAMBA = 1 def initialize(opened_file, type, local_path) @remote_file, @type = opened_file, type @local_file = File.new(local_path, "w") @cancelled = false end def start(blocksize=8192) end def cancel @cancelled = true end def cancelled? @cancelled end end class SearchResultMenu def initialize(selected_files) @selected_files = selected_files @glade = GladeXML.new($OUTSIDERGLADEFILE, "searchresult_menu") {|h| method(h) } @menu = @glade.getWidget('searchresult_menu') @menu.popup(nil,nil,nil,3,Time.now.to_i) end private def download print "download\n" if $global_config.use_smb_gw and $global_config.gw_type = 'UNIX' smb_url = "smb:"+(@selected_files[0].path+@selected_files[0].filename).gsub(/\\/, '/') $pending_downloads << smb_url elsif $global_config.use_smb_gw and $global_config.gw_type = 'WIN32' $stdout.print "Not yet implemented!\n" elsif not $global_config.use_smb_gw and $global_config.gw_type = 'UNIX' $stdout.print "Not yet implemented!\n" elsif not $global_config.use_smb_gw and $global_config.gw_type = 'WIN32' $stdout.print "Not yet implemented!\n" end end def download_one_of print "download_one_of\n" end def list_directory print "list_directory\n" end def download_directory print "download_directory\n" end def resume_incomplete_download print "resume_incomplete\n" end end class DownloadMenu def initialize(downloads) p downloads end end class MainWindow def initialize @glade = GladeXML.new($OUTSIDERGLADEFILE, "main_window") {|h| method(h) } @main_window = @glade.getWidget('main_window') @search_e = @glade.getWidget('search_e') @g = @glade.getWidget('maxresults_sb') @maxdownloads_sb = @glade.getWidget('maxdownloads_sb') @re_exact_match_cb = @glade.getWidget('re_exact_match_cb') @re_case_sensitive_cb = @glade.getWidget('re_case_sensitive_cb') @filetype_e = @glade.getWidget('filetype_e') @files_searchtype_rb = @glade.getWidget('files') @dirs_searchtype_rb = @glade.getWidget('dirs') @both_searchtype_rb = @glade.getWidget('both') @search_results = @glade.getWidget('search_results') @downloads = @glade.getWidget('downloads') @statusbar = @glade.getWidget('statusbar') @sb_context = @statusbar.get_context_id('MainWin') update_gui end def update_gui @g.set_value($global_config.maxresults) @maxdownloads_sb.set_value($global_config.maxdownloads) @re_exact_match_cb.active = $global_config.re_exact_match @re_case_sensitive_cb.active = $global_config.re_case_sensitive end def show filename = ENV['HOME']+'/.outsider/'+LISTFILENAME+'.txt' if not test(?e, filename) update_mp3list end @list = Mp3List::List.new(filename) if Time.now - File.stat(filename).mtime > UPDATE_INTERVAL update_mp3list end @main_window.show end def update_downloadlist print "update_downloadlist\n" end protected def update_config $global_config.re_exact_match = @re_exact_match_cb.active $global_config.re_case_sensitive = @re_case_sensitive_cb.active $global_config.maxresults = @g.get_value_as_int() $global_config.maxdownloads = @maxdownloads_sb.get_value_as_int() end def quit exit(0) #Gtk.main_quit end def resume_incomplete_downloads print "resume_incomplete\n" end def config update_config config_window = ConfigWindow.new config_window.show end def searchresult_clicked(widget, event, data) return unless event.button == 3 print "searchresult_clicked\n" selected_files = Array.new @search_results.each_selection {|row| result = @search_results.get_row_data(row) selected_files << result } if selected_files.size == 0 return end SearchResultMenu.new(selected_files) @search_results.unselect_all end def download_clicked print "download_clicked\n" downloads = Array.new @downloads.each_selection {|row| download = @downloads.get_row_data(row) downloads << download } if downloads.size == 0 return end DownloadMenu.new(downloads) end def about print "about\n" end def search @statusbar.push(@sb_context, "Searching..") @search_results.clear @timeout_id = Gtk.timeout_add(100) { real_search } end def real_search Gtk.timeout_remove(@timeout_id) update_config if $global_config.re_exact_match expression = @search_e.get_text() else #InfoBox.new('Non-exact regular expression searches aren\' supported yet.') $stderr.print 'Non-exact regular expression searches aren\' supported yet.\n' return end if @files_searchtype_rb.active search_type = Mp3List::SEARCH_FILES elsif @dirs_searchtype_rb.active search_type = Mp3List::SEARCH_DIRS else search_type = Mp3List::SEARCH_BOTH end results = Mp3List::Search.new(@list, expression, $global_config.re_case_sensitive, search_type, Mp3List::TYPE_ANY, 0, $global_config.maxresults) i = 0 results.each {|result| @search_results.append([result.filename, result.size.to_s, result.path]) @search_results.set_row_data(i, result) i += 1 } @statusbar.pop(@sb_context) @statusbar.push(@sb_context, "Found #{results.size} results.") end def re_exact_match_toggle print "re_exact_match_toggle\n" end def re_case_sensitive_toggle print "re_case_sensitive_toggle\n" end def update_mp3list print "update_mp3list\n" glade = GladeXML.new($OUTSIDERGLADEFILE, "update_window") {|h| method(h) } @update_window = glade.getWidget('update_window') @progressbar = glade.getWidget('progressbar') @progressbar.set_percentage(0.0) @update_window.show @timeout_id = Gtk.timeout_add(100) { real_update_mp3list } end def real_update_mp3list Gtk.timeout_remove(@timeout_id) Thread.new { filename = ENV['HOME']+'/.outsider/'+LISTFILENAME+'.zip' gw = SMBGw::Gateway.new($global_config.gw_host, $global_config.gw_user, $global_config.gw_password) remote = nil LISTFILEMIRRORS.each {|dir| begin remote = SMBGw::File.new(gw,'smb:'+dir+LISTFILENAME+'.zip',"r") break rescue Exception => e next end } if remote.nil? print "Couldn't update list!\n" return end @list.destroy =begin local = File.new(filename, "w") lsize = 0 rsize = remote.size until remote.eof? written = local.write(remote.read(BLOCKSIZE)) lsize += written @progressbar.set_percentage(lsize.to_f/rsize.to_f) $stdout.flush end =end remote.close # local.close p 'innan zip' p "/usr/local/bin/unzip -o #{filename}" p 'efter zip' p filename[0..-4]+'txt' @list = Mp3List::List.new(filename[0..-4]+'txt') p 'ny lista..' } end end class Config attr_accessor :dlpath, :incompletepath, :maxresults, :maxdownloads, :re_exact_match, :re_case_sensitive attr_accessor :use_smb_gw, :gw_host, :gw_port, :gw_user, :gw_password, :gw_type def initialize @dlpath = ENV['HOME']+'/mp3/dl' @incompletepath = ENV['HOME']+'/mp3/dl/incomplete' @maxresults = 300 @maxdownloads = 1 @re_exact_match = true @re_case_sensitive = false @use_smb_gw = true @gw_host = 'smbgw.ryd.student.liu.se' @gw_port = 31337 @gw_type = 'UNIX' @gw_user = 'yourUsername' @gw_password = 'secretPassword' end def gw_object SMBGw::Gateway.new(@gw_host, @gw_user, @gw_password, @gw_port) end end class PathDialog def initialize(parent, msg, current_file) @glade = GladeXML.new($OUTSIDERGLADEFILE, "path_dialog") {|h| method(h) } @parent, @msg, @current_file = parent, msg, current_file @dialog = @glade.getWidget("path_dialog") @dialog.set_filename(@current_file) end def show @dialog.show end def close_window @dialog.destroy end protected def ok @parent.send @msg, @dialog.get_filename close_window end def cancel @parent.send @msg, @current_file close_window end end class ConfigWindow def initialize # created from FirstTimeWindow or setupGUI() ? if defined? $global_config @first_time = false else @first_time = true $global_config = Config.new end # get widgets @glade = GladeXML.new($OUTSIDERGLADEFILE, "config_window") {|h| method(h) } @config_window = @glade.getWidget('config_window') @dlpath_e = @glade.getWidget('dlpath_e') @incompletepath_e = @glade.getWidget('incompletepath_e') @maxresults_sb = @glade.getWidget('maxresults_sb') @maxdownloads_sb = @glade.getWidget('maxdownloads_sb') @re_exact_match_cb = @glade.getWidget('re_exact_match_cb') @re_case_sensitive_cb = @glade.getWidget('re_case_sensitive_cb') @use_smb_gw_cb = @glade.getWidget('use_smb_gw_cb') @gw_host_e = @glade.getWidget('gw_host_e') @gw_port_e = @glade.getWidget('gw_port_e') @gw_type_e = @glade.getWidget('gw_type_e') @gw_user_e = @glade.getWidget('gw_user_e') @gw_password_e = @glade.getWidget('gw_password_e') # update with relevant configuration @dlpath_e.set_text($global_config.dlpath) @incompletepath_e.set_text($global_config.incompletepath) @maxresults_sb.set_value($global_config.maxresults) @maxdownloads_sb.set_value($global_config.maxdownloads) @re_exact_match_cb.active = $global_config.re_exact_match @re_case_sensitive_cb.active = $global_config.re_case_sensitive @use_smb_gw_cb.active = $global_config.use_smb_gw @gw_host_e.set_text($global_config.gw_host) @gw_port_e.set_text($global_config.gw_port.to_s) @gw_type_e.set_text($global_config.gw_type) @gw_user_e.set_text($global_config.gw_user) @gw_password_e.set_text($global_config.gw_password) end def show @config_window.show end def update_downloadpath(path) @dlpath_e.set_text(File.dirname(path)) end def update_incompletepath(path) @incompletepath_e.set_text(File.dirname(path)) end protected def ok $global_config.dlpath = @dlpath_e.get_text() $global_config.incompletepath = @incompletepath_e.get_text() $global_config.maxresults = @maxresults_sb.get_value_as_int() $global_config.maxdownloads = @maxdownloads_sb.get_value_as_int() $global_config.re_exact_match = @re_exact_match_cb.active $global_config.re_case_sensitive = @re_case_sensitive_cb.active $global_config.use_smb_gw = @use_smb_gw_cb.active $global_config.gw_host = @gw_host_e.get_text() $global_config.gw_port = @gw_port_e.get_text() $global_config.gw_type = @gw_type_e.get_text() $global_config.gw_user = @gw_user_e.get_text() $global_config.gw_password = @gw_password_e.get_text() save_config() if @first_time $main_window = MainWindow.new $main_window.show else $main_window.update_gui end @config_window.destroy end def cancel Gtk.main_quit if @first_time @config_window.destroy end def get_downloadpath PathDialog.new(self, :update_downloadpath, $global_config.dlpath).show end def get_incompletepath PathDialog.new(self, :update_incompletepath, $global_config.incompletepath).show end end class FirstTimeWindow def initialize @glade = GladeXML.new($OUTSIDERGLADEFILE, "first_time_window") {|h| method(h) } @first_time_window = @glade.getWidget('first_time_window') $config_window = ConfigWindow.new @first_time_window.show end def show_config_window $config_window.show @first_time_window.destroy end def close Gtk.main_quit end end def save_config() if not test(?e, ENV['HOME']+'/.outsider') Dir.mkdir(ENV['HOME']+'/.outsider') end File.open(ENV['HOME']+'/.outsider/conf', "w") {|f| f.puts("# Automatically generated config file - do not edit!") f.puts("dlpath = #{$global_config.dlpath}") f.puts("incompletepath = #{$global_config.incompletepath}") f.puts("maxresults = #{$global_config.maxresults}") f.puts("maxdownloads = #{$global_config.maxdownloads}") f.puts("re_exact_match = #{$global_config.re_exact_match}") f.puts("re_case_sensitive = #{$global_config.re_case_sensitive}") f.puts("use_smb_gw = #{$global_config.use_smb_gw}") f.puts("gw_host = #{$global_config.gw_host}") f.puts("gw_port = #{$global_config.gw_port}") f.puts("gw_type = #{$global_config.gw_type}") f.puts("gw_user = #{$global_config.gw_user}") f.puts("gw_password = #{$global_config.gw_password}") f.puts("# End of configuration file") } end def parse_config(file) while file.gets case $_ when /dlpath\s*=\s*(.*)/ dlpath = $1 when /incompletepath\s*=\s*(.*)/ incompletepath = $1 when /maxresults\s*=\s*(.*)/ maxresults = $1.to_i when /maxdownloads\s*=\s*(.*)/ maxdownloads = $1.to_i when /re_exact_match\s*=\s*(.*)/ re_exact_match = ($1 == 'true') when /re_case_sensitive\s*=\s*(.*)/ re_case_sensitive = ($1 == 'true') when /use_smb_gw\s*=\s*(.*)/ use_smb_gw = ($1 == 'true') when /gw_host\s*=\s*(.*)/ gw_host = $1 when /gw_port\s*=\s*(.*)/ gw_port = $1.to_i when /gw_type\s*=\s*(.*)/ gw_type = $1 when /gw_user\s*=\s*(.*)/ gw_user = $1 when /gw_password\s*=\s*(.*)/ gw_password = $1 when /^\s+|^\#/ next else $stderr.print "Syntax error in con/figuration file at line "+ "#{$.}:\n==> #{$_}\nPlease correct manually or remove configfile.\n" exit(1) end end begin $global_config = Config.new $global_config.dlpath = dlpath $global_config.incompletepath = incompletepath $global_config.maxresults = maxresults $global_config.maxdownloads = maxdownloads $global_config.re_exact_match = re_exact_match $global_config.re_case_sensitive = re_case_sensitive $global_config.use_smb_gw = use_smb_gw $global_config.gw_host = gw_host $global_config.gw_port = gw_port $global_config.gw_user = gw_user $global_config.gw_password = gw_password rescue $stderr.print "Configuration item missing! Either fix by hand or delete ~/.outsider/conf" exit(1) end end # EXECUTION STARTS HERE! if test(?e, GLADEFILE) $OUTSIDERGLADEFILE = GLADEFILE elsif test(?e, "./outsider.glade") $OUTSIDERGLADEFILE = "./outsider.glade" elsif test(?e, "../../share/outsider/outsider.glade") $OUTSIDERGLADEFILE = "../../share/outsider/outsider.glade" end if $OUTSIDERGLADEFILE.nil? raise "Couldn't find outsider.glade file. This should be located at #{GLADEFILE} or ./outsider.glade" end configfile_path = ENV['HOME']+'/.outsider/conf' if test(?e, configfile_path) configfile = File.open(configfile_path, "r") parse_config(configfile) configfile.close $main_window = MainWindow.new $main_window.show else FirstTimeWindow.new end $download_threads = Hash.new $pending_downloads = [] # the download queu-handler thread Thread.new { loop { if $pending_downloads.size > 0 $pending_downloads.each {|url| # Create download thread $download_threads[url] = Thread.new(url) {|url| $pending_downloads.delete(url) gw = SMBGw::Gateway.new($global_config.gw_host, $global_config.gw_user, $global_config.gw_password) remote = SMBGw::File.new(gw, url, "r") local = File.new(url.split('/')[-1], "w") until remote.eof? local.write(remote.read(BLOCKSIZE)) print "#" $stdout.flush Thread.pass end remote.close local.close $download_threads.delete(url) } } end sleep(1) } } Gtk.main [end of outsider.rb] the utility-file used which contains the socket operations: [smbgw/client.rb] #- # Copyright (c) 2001 Patrik Sundberg, Raditex AB # All rights reserved. # # Redistribution and use in source and binary forms, with or without # modification, are permitted provided that the following conditions # are met: # 1. Redistributions of source code must retain the above copyright # notice, this list of conditions and the following disclaimer. # 2. Redistributions in binary form must reproduce the above copyright # notice, this list of conditions and the following disclaimer in the # documentation and/or other materials provided with the distribution. # # THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND # ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE # IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE # ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE # FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL # DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS # OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) # HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT # LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY # OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF # SUCH DAMAGE. # # $Id: client.rb,v 1.2 2002/03/24 00:05:09 ps Exp $ require 'socket' module SMBGw class Gateway attr_accessor :host, :username, :password, :port def initialize(host, username, password, port=31337) @host, @username, @password, @port = host, username, password, port end end class File def initialize(gw, url, mode) @socket = TCPSocket.new(gw.host, gw.port) @socket.puts("auth #{gw.username} #{gw.password}") res = @socket.gets.chomp if res != 'OK' raise "Failed to authenticate user with gateway!" end @socket.puts("open_file #{mode} #{url}") error_check(@socket.gets.chomp) end def read(blocksize=1024) @socket.puts("read #{blocksize.to_s}") res = @socket.gets.chomp unless error_check(res) size = @socket.gets.chomp.split[1].to_i return @socket.read(size) end end def write(data) end def eof? @socket.puts('eof?') res = @socket.gets.chomp res == 'TRUE' unless error_check(res) end def pos @socket.puts('pos') res = @socket.gets.chomp res unless error_check(res) end def pos=(position) @socket.puts("pos= #{position.to_s}") res = @socket.gets.chomp true unless error_check(res) end def close @socket.puts('close') res = @socket.gets.chomp @socket.close true unless error_check(res) end def size @socket.puts('size') res = @socket.gets.chomp res.to_i unless error_check(res) end private def error_check(msg) if msg =~ /ERROR\s+(.*)/ raise "SMBGw::File error: #{$1}" else return false end end end class Dir end end [end of client.rb] -- ---------------------------------------------------------------------------. Patrik Sundberg Address: Rydsv�gen 100C, 584 31 Link�ping, Sweden | Email: ps@raditex.se || ps@radiac.mine.nu | Phone: +46 13 178567 || +46 707 602240 | .---> Applied Physics and Electrical Engineering student | |-----> Master of Science in Business Administration and Economics student | |-------> UNIX/Programming consultant | `--------------------------------------------------------------------------'