diff --git a/bin/qasim-gui b/bin/qasim-gui index d21e03c..8383927 100755 --- a/bin/qasim-gui +++ b/bin/qasim-gui @@ -13,289 +13,9 @@ require 'pathname' $:.push "lib" require 'qasim' -require 'qasim/qasim_qrc' +require 'qasim/gui' # QaSiM // Qt Sshfs Mapper -def _ str - Qt::Object.tr(str) -end - - -module Qasim - class QasimApp - def initialize - end - end - - class QasimGui < QasimApp - - class LockError < RuntimeError ; end - - def initialize - @config = Config.new - #@config.parse_cmd_line ARGV - - @map_menu = nil - @context_menu = nil - @connect_error = {} - @connect_running = {} - end - - def dbus_notify title, body, icon - bus = Qt::DBusConnection.sessionBus - if !bus.connected? - $stderr.puts( - "Cannot connect to the D-BUS session bus.\n" \ - "To start it, run:\n" \ - "\teval `dbus-launch --auto-syntax`\n" - ) - exit 1 - end - msg = Qt::DBusMessage.create_method_call( - 'org.freedesktop.Notifications', - '/org/freedesktop/Notifications', - 'org.freedesktop.Notifications', - 'Notify' - ) - msg.arguments = [ APP_NAME, Qt::Variant.from_value( 0, "unsigned int" ), - icon, title, body, [], {}, -1 ] - rep = bus.call( msg ) - # if rep.type == Qt::DBusMessage - - # si.showMessage("Qasim", - # "Sorry dude", 2, 5000 ) - end - - - # - # Rebuild map menu - # - def build_map_menu - # reload maps dynamically - @config.parse_maps - if @map_menu.nil? then - @map_menu = Qt::Menu.new - else - @map_menu.clear - end - - previous_host = nil - @config.maps.sort do |mx,my| - mx.host <=> my.host - end.each do |map| - if map.host != previous_host and not previous_host.nil? then - @map_menu.addSeparator - end - itemx = Qt::Action.new(map.name, @map_menu) - itemx.setCheckable true; - if map.connected? then - itemx.setChecked true - end - itemx.connect(SIGNAL(:triggered)) do - action_trigger_map_item map, itemx - end - @map_menu.addAction itemx; - previous_host = map.host - end - end - - # - # Action when map item triggered - # - def action_trigger_map_item map, item - @connect_error[map.path] = Set.new - @connect_running[map.path] = 0 - method = if map.connected? then :disconnect - else :connect - end - - begin - map.send(method) do |linkname,cmd,cmd_args| - process = Qt::Process.new - process.connect(SIGNAL('finished(int, QProcess::ExitStatus)')) do |exitcode,exitstatus| - #puts "exitcode = %s, exitstatus = %s" % [exitcode, exitstatus] - @connect_running[map.path] -= 1 - if exitcode != 0 then - @connect_error[map.path].add linkname - else - end - if @connect_running[map.path] == 0 then - # display someting - if @connect_error[map.path].empty? then - - dbus_notify "%s (%s)" % [APP_NAME, map.name], - ("Map %sed successfully" % method.to_s), - 'dialog-information' - else - erroneous = @connect_error[map.path].to_a.join(', ') - dbus_notify "%s (%s)" % [APP_NAME, map.name], - ("Unable to %s map
" % method.to_s) + - ("Broken link(s): %s" % erroneous), - 'dialog-error' - end - end - end - @connect_running[map.path] += 1 - process.start cmd, cmd_args - end - - rescue Map::ConnectError => e - puts e.inspect - end - end - - # - # - # - def build_context_menu - @context_menu = Qt::Menu.new - - act_pref = Qt::Action.new _('&Preferences'), @context_menu - act_pref.setIcon( Qt::Icon::fromTheme("configure") ) rescue nil - act_pref.setIconVisibleInMenu true - act_pref.setEnabled false - act_pref.connect(SIGNAL(:triggered)) do - res = @pref_dialog.show - end - @context_menu.addAction act_pref; - - act_about = Qt::Action.new '&About', @context_menu - act_about.setIcon( Qt::Icon::fromTheme("help-about") ) rescue nil - act_about.setIconVisibleInMenu true - #act_about.setEnabled true - act_about.connect(SIGNAL(:triggered)) do - res = @about_dialog.show - end - @context_menu.addAction act_about; - - @context_menu.addSeparator - - act_quit = Qt::Action.new _('Quit'), @context_menu - act_quit.setIcon( Qt::Icon::fromTheme("application-exit") ) rescue nil - act_quit.setIconVisibleInMenu true - act_quit.connect(SIGNAL(:triggered)) { @app.quit } - @context_menu.addAction act_quit - end - - - # - # - # - def build_interface - - @app = Qt::Application.new(ARGV) - #Qt.debug_level = Qt::DebugLevel::High - #Qt.debug_level = Qt::DebugLevel::Extensive - @app.setQuitOnLastWindowClosed false - - @main_win = Qt::MainWindow.new - @systray = Qt::SystemTrayIcon.new @main_win - @about_dialog = Qasim::Ui::About.new @main_win - @pref_dialog = Qasim::Ui::Preferences.new @main_win - - std_icon = Qt::Icon.new( ":/qasim/qasim-icon" ) - alt_icon = Qt::Icon.new - blinking = false - - @systray.icon = std_icon - @systray.show - - - @systray.setToolTip("Qasim %s" % APP_VERSION); - - build_map_menu - build_context_menu - - @systray.contextMenu = @context_menu - - @systray.connect(SIGNAL('activated(QSystemTrayIcon::ActivationReason)')) do |reason| - case reason - when Qt::SystemTrayIcon::Trigger then - build_map_menu - @map_menu.popup(Qt::Cursor::pos()) - #blinking = !blinking - #si.icon = blinking ? alt_icon : std_icon - when Qt::SystemTrayIcon::MiddleClick then - # - when Qt::SystemTrayIcon::Context then - # - when Qt::SystemTrayIcon::DoubleClick then - # - end - end - end - - - def lock_set - begin - # create an exclusive lock file - have_lock = true - - FileUtils.mkdir_p APP_CONFIG_DIR unless File.exist? APP_CONFIG_DIR - lockfname = File.join APP_CONFIG_DIR, "lock" - fd = IO::sysopen( lockfname, - Fcntl::O_WRONLY | Fcntl::O_EXCL | Fcntl::O_CREAT) - f = IO.open(fd) - f.syswrite( "#{Process.pid}\n" ) - f.close - rescue Errno::EEXIST => e - # test if the other process still exist - masterpid = File.read(lockfname).strip - other_path = "/proc/#{masterpid.to_i}" - STDERR.puts "testing %s" % other_path - if File.exist? other_path then - cmdline = File.read( File.join( other_path, 'cmdline' ) ) - if cmdline =~ /qasim/ then - raise LockError, "Another instance of %s is already running." % APP_NAME - end - end - fd = IO::sysopen( lockfname, - Fcntl::O_WRONLY | Fcntl::O_EXCL ) - f = IO.open(fd) - f.syswrite( "#{Process.pid}\n" ) - f.close - end - end - - def lock_unset - # remove lock if it exists - lockfname = File.join APP_CONFIG_DIR, "lock" - return unless File.exist? lockfname - masterpid = File.read(lockfname).strip - if masterpid.to_i == Process.pid then - FileUtils.rm lockfname - end - end - - # - # - # - def run - Process.daemon(true) #FIXME: add foreground mode too - lock_set - @app.exec - exit 0 - rescue LockError - STDERR.puts "Error: %s is already running" % APP_NAME - exit 1 - ensure - lock_unset - end - - - # - # - # - def self.main - qasim = QasimGui.new - qasim.build_interface - qasim.run - end - - end - -end - Qasim::QasimGui::main diff --git a/lib/qasim.rb b/lib/qasim.rb index 94b655b..b20c3f8 100644 --- a/lib/qasim.rb +++ b/lib/qasim.rb @@ -1,6 +1,10 @@ require 'qasim/constants' +def _ str + Qt::Object.tr(str) +end + module Qasim autoload :Config, 'qasim/config' autoload :Map, 'qasim/map' diff --git a/lib/qasim/cli.rb b/lib/qasim/cli.rb index 7a81bcd..327fc9a 100644 --- a/lib/qasim/cli.rb +++ b/lib/qasim/cli.rb @@ -9,7 +9,11 @@ module Qasim desc "list", "list" def list - raise NotImplementedError + @config.maps.sort do |mx,my| + mx.host <=> my.host + end.each do |map| + puts map.name + end end desc "mount MAPS", "mount selected maps" @@ -21,17 +25,17 @@ module Qasim # # # - def initializez + def initialize *opts + super + @all_maps = nil @active_maps = nil - puts "-- sshfs-mapper --" @config = Config.new - @config.parse_cmd_line ARGV - @config.parse_file + @config.parse_maps + #@config.parse_cmd_line ARGV @all_maps = {} - pp @config end @@ -43,7 +47,7 @@ module Qasim def run_mount # asynchronous mount - selected_maps = @config.maps.select do |map| + @config.maps.select do |map| pp map map.online? # if map.available? then @@ -69,8 +73,6 @@ module Qasim else raise RuntimeError, "Unknown action" end - - puts "--run" end end diff --git a/lib/qasim/config.rb b/lib/qasim/config.rb index 7c150a0..4ae9b44 100644 --- a/lib/qasim/config.rb +++ b/lib/qasim/config.rb @@ -27,22 +27,23 @@ module Qasim @debug = false end + # FIXME: move out of config def parse_maps &blk @maps = [] map_dirs = [@config_dir, APP_SYSCONFIG_DIR].select{ |d| File.exist? d and File.directory? d } - Find.find( *map_dirs ) do |path| + Find.find(*map_dirs) do |path| # Skip unwanted files fast next unless File.file? path - next unless File.basename( path ) =~ /.map$/ + next unless File.basename(path) =~ /.map$/ begin map = Map.from_file self, path yield map if block_given? maps.push map - rescue + rescue Map::ParseError raise RuntimeError, "Error while parsing map file" end end diff --git a/lib/qasim/gui.rb b/lib/qasim/gui.rb new file mode 100644 index 0000000..318d496 --- /dev/null +++ b/lib/qasim/gui.rb @@ -0,0 +1,274 @@ +require 'qasim/qasim_qrc' + +module Qasim + class QasimApp + def initialize + end + end + + class QasimGui < QasimApp + + class LockError < RuntimeError ; end + + def initialize + @config = Config.new + #@config.parse_cmd_line ARGV + + @map_menu = nil + @context_menu = nil + @connect_error = {} + @connect_running = {} + end + + def dbus_notify title, body, icon + bus = Qt::DBusConnection.sessionBus + if !bus.connected? + $stderr.puts( + "Cannot connect to the D-BUS session bus.\n" \ + "To start it, run:\n" \ + "\teval `dbus-launch --auto-syntax`\n" + ) + exit 1 + end + msg = Qt::DBusMessage.create_method_call( + 'org.freedesktop.Notifications', + '/org/freedesktop/Notifications', + 'org.freedesktop.Notifications', + 'Notify' + ) + msg.arguments = [ APP_NAME, Qt::Variant.from_value( 0, "unsigned int" ), + icon, title, body, [], {}, -1 ] + rep = bus.call( msg ) + # if rep.type == Qt::DBusMessage + + # si.showMessage("Qasim", + # "Sorry dude", 2, 5000 ) + end + + + # + # Rebuild map menu + # + def build_map_menu + # reload maps dynamically + @config.parse_maps + @map_menu ||= Qt::Menu.new + @map_menu.clear + + previous_host = nil + @config.maps.sort do |mx,my| + mx.host <=> my.host + end.each do |map| + if map.host != previous_host and not previous_host.nil? then + @map_menu.addSeparator + end + itemx = Qt::Action.new(map.name, @map_menu) + itemx.setCheckable true; + if map.connected? then + itemx.setChecked true + end + itemx.connect(SIGNAL(:triggered)) do + action_trigger_map_item map, itemx + end + @map_menu.addAction itemx; + previous_host = map.host + end + end + + # + # Action when map item triggered + # + def action_trigger_map_item map, item + @connect_error[map.path] = Set.new + @connect_running[map.path] = 0 + method = if map.connected? then :disconnect + else :connect + end + + begin + map.send(method) do |linkname,cmd,cmd_args| + process = Qt::Process.new + process.connect(SIGNAL('finished(int, QProcess::ExitStatus)')) do |exitcode,exitstatus| + #puts "exitcode = %s, exitstatus = %s" % [exitcode, exitstatus] + @connect_running[map.path] -= 1 + if exitcode != 0 then + @connect_error[map.path].add linkname + else + end + if @connect_running[map.path] == 0 then + # display someting + if @connect_error[map.path].empty? then + + dbus_notify "%s (%s)" % [APP_NAME, map.name], + ("Map %sed successfully" % method.to_s), + 'dialog-information' + else + erroneous = @connect_error[map.path].to_a.join(', ') + dbus_notify "%s (%s)" % [APP_NAME, map.name], + ("Unable to %s map
" % method.to_s) + + ("Broken link(s): %s" % erroneous), + 'dialog-error' + end + end + end + @connect_running[map.path] += 1 + process.start cmd, cmd_args + end + + rescue Map::ConnectError => e + puts e.inspect + end + end + + # + # + # + def build_context_menu + @context_menu = Qt::Menu.new + + act_pref = Qt::Action.new _('&Preferences'), @context_menu + act_pref.setIcon( Qt::Icon::fromTheme("configure") ) rescue nil + act_pref.setIconVisibleInMenu true + act_pref.setEnabled false + act_pref.connect(SIGNAL(:triggered)) do + res = @pref_dialog.show + end + @context_menu.addAction act_pref; + + act_about = Qt::Action.new '&About', @context_menu + act_about.setIcon( Qt::Icon::fromTheme("help-about") ) rescue nil + act_about.setIconVisibleInMenu true + #act_about.setEnabled true + act_about.connect(SIGNAL(:triggered)) do + res = @about_dialog.show + end + @context_menu.addAction act_about; + + @context_menu.addSeparator + + act_quit = Qt::Action.new _('Quit'), @context_menu + act_quit.setIcon( Qt::Icon::fromTheme("application-exit") ) rescue nil + act_quit.setIconVisibleInMenu true + act_quit.connect(SIGNAL(:triggered)) { @app.quit } + @context_menu.addAction act_quit + end + + + # + # + # + def build_interface + + @app = Qt::Application.new(ARGV) + #Qt.debug_level = Qt::DebugLevel::High + #Qt.debug_level = Qt::DebugLevel::Extensive + @app.setQuitOnLastWindowClosed false + + @main_win = Qt::MainWindow.new + @systray = Qt::SystemTrayIcon.new @main_win + @about_dialog = Qasim::Ui::About.new @main_win + @pref_dialog = Qasim::Ui::Preferences.new @main_win + + std_icon = Qt::Icon.new( ":/qasim/qasim-icon" ) + alt_icon = Qt::Icon.new + blinking = false + + @systray.icon = std_icon + @systray.show + + + @systray.setToolTip("Qasim %s" % APP_VERSION); + + build_map_menu + build_context_menu + + @systray.contextMenu = @context_menu + + @systray.connect(SIGNAL('activated(QSystemTrayIcon::ActivationReason)')) do |reason| + case reason + when Qt::SystemTrayIcon::Trigger then + build_map_menu + @map_menu.popup(Qt::Cursor::pos()) + #blinking = !blinking + #si.icon = blinking ? alt_icon : std_icon + when Qt::SystemTrayIcon::MiddleClick then + # + when Qt::SystemTrayIcon::Context then + # + when Qt::SystemTrayIcon::DoubleClick then + # + end + end + end + + + def lock_set + begin + # create an exclusive lock file + have_lock = true + + FileUtils.mkdir_p APP_CONFIG_DIR unless File.exist? APP_CONFIG_DIR + lockfname = File.join APP_CONFIG_DIR, "lock" + fd = IO::sysopen( lockfname, + Fcntl::O_WRONLY | Fcntl::O_EXCL | Fcntl::O_CREAT) + f = IO.open(fd) + f.syswrite( "#{Process.pid}\n" ) + f.close + rescue Errno::EEXIST => e + # test if the other process still exist + masterpid = File.read(lockfname).strip + other_path = "/proc/#{masterpid.to_i}" + STDERR.puts "testing %s" % other_path + if File.exist? other_path then + cmdline = File.read( File.join( other_path, 'cmdline' ) ) + if cmdline =~ /qasim/ then + raise LockError, "Another instance of %s is already running." % APP_NAME + end + end + fd = IO::sysopen( lockfname, + Fcntl::O_WRONLY | Fcntl::O_EXCL ) + f = IO.open(fd) + f.syswrite( "#{Process.pid}\n" ) + f.close + end + end + + def lock_unset + # remove lock if it exists + lockfname = File.join APP_CONFIG_DIR, "lock" + return unless File.exist? lockfname + masterpid = File.read(lockfname).strip + if masterpid.to_i == Process.pid then + FileUtils.rm lockfname + end + end + + # + # + # + def run + Process.daemon(true) #FIXME: add foreground mode too + lock_set + @app.exec + exit 0 + rescue LockError + STDERR.puts "Error: %s is already running" % APP_NAME + exit 1 + ensure + lock_unset + end + + + # + # + # + def self.main + qasim = QasimGui.new + qasim.build_interface + qasim.run + end + + end + +end + diff --git a/lib/qasim/map.rb b/lib/qasim/map.rb index c20a854..d85bcbb 100644 --- a/lib/qasim/map.rb +++ b/lib/qasim/map.rb @@ -54,8 +54,8 @@ module Qasim ; module Map # Load description from file and create a Map object # def from_file appcfg, filename - config = { - type: :ssh # for config V1, we assume SSHFS + params = { + type: :ssh # for params V1, we assume SSHFS by default } map = nil @@ -66,30 +66,31 @@ module Qasim ; module Map linect += 1 line = env_substitute(line) - + params[:filename] = filename case line when /^\s*TYPE\s*=\s*(.*)\s*$/ then - config[:type] = $1 + params[:type] = $1 when /^\s*REMOTE_USER\s*=\s*(.*)\s*$/ then - config[:user] = $1 + params[:user] = $1 when /^\s*REMOTE_PORT\s*=\s*(.*)\s*$/ then - config[:port] = $1.to_i + params[:port] = $1.to_i when /^\s*REMOTE_HOST\s*=\s*(.*)\s*$/ then - config[:host] = $1 + params[:host] = $1 when /^\s*REMOTE_CYPHER\s*=\s*(.*)\s*$/ then if CYPHERS.map(&:to_s).include? $1 then - config[:cypher] = $1.to_sym + params[:cypher] = $1.to_sym end when /^\s*MAP\s*=\s*(.*)\s+(.*)\s*$/ then - config[:links] ||= {} - config[:links][$1] = $2 + params[:links] ||= {} + params[:links][$1] = $2 when /^\s*$/,/^\s*#/ then else raise MapParseError, "parse error at #{@filename}:#{linect}" end end f.close - map = Ssh.new config, filename + map_class = class_for params[:type] + map = map_class.new appcfg, params return map end diff --git a/lib/qasim/map/generic.rb b/lib/qasim/map/generic.rb index 13a24af..89a013b 100644 --- a/lib/qasim/map/generic.rb +++ b/lib/qasim/map/generic.rb @@ -4,7 +4,17 @@ module Qasim ; module Map end ; end class Qasim::Map::Generic - def initialize params + def initialize app_config, params + @app_config = app_config + #@params = params # FIXME: ? + + @links = params[:links] + params.delete :links + + @filename = params[:filename] + params.delete :filename + + @name = File.basename @filename, '.map' end @@ -17,7 +27,7 @@ class Qasim::Map::Generic def self.parameters { map_name: [nil , true], - map_enabled: [true, false], + map_enable: [true, false], map_mountpoint: [nil, true] } end diff --git a/lib/qasim/map/ssh.rb b/lib/qasim/map/ssh.rb index 09112db..b7a46c1 100644 --- a/lib/qasim/map/ssh.rb +++ b/lib/qasim/map/ssh.rb @@ -34,20 +34,12 @@ class Qasim::Map::Ssh < Qasim::Map::Generic # # Set defaults properties for maps # - def initialize config, map_path + def initialize *opts super - @config = config - @path = map_path - @host = nil - @port = 22 - @enable = false - @user = nil - @cypher = :arcfour - @links = {} - @debug = false - @name = (File.basename map_path).gsub(/\.map$/,'') - - self.load @path + #@host = nil + #@port = 22 + #@user = nil + #@cypher = :arcfour end #