Rewrite maps (work in progress).

This commit is contained in:
Glenn Y. Rolland 2015-08-17 23:00:47 +02:00
parent 6472372d53
commit baee60c15c
8 changed files with 323 additions and 319 deletions

View file

@ -13,289 +13,9 @@ require 'pathname'
$:.push "lib" $:.push "lib"
require 'qasim' require 'qasim'
require 'qasim/qasim_qrc' require 'qasim/gui'
# QaSiM // Qt Sshfs Mapper # 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],
("<b>Map %sed successfully<b>" % method.to_s),
'dialog-information'
else
erroneous = @connect_error[map.path].to_a.join(', ')
dbus_notify "%s (%s)" % [APP_NAME, map.name],
("<b>Unable to %s map</b><br>" % 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 Qasim::QasimGui::main

View file

@ -1,6 +1,10 @@
require 'qasim/constants' require 'qasim/constants'
def _ str
Qt::Object.tr(str)
end
module Qasim module Qasim
autoload :Config, 'qasim/config' autoload :Config, 'qasim/config'
autoload :Map, 'qasim/map' autoload :Map, 'qasim/map'

View file

@ -9,7 +9,11 @@ module Qasim
desc "list", "list" desc "list", "list"
def list def list
raise NotImplementedError @config.maps.sort do |mx,my|
mx.host <=> my.host
end.each do |map|
puts map.name
end
end end
desc "mount MAPS", "mount selected maps" desc "mount MAPS", "mount selected maps"
@ -21,17 +25,17 @@ module Qasim
# #
# #
# #
def initializez def initialize *opts
super
@all_maps = nil @all_maps = nil
@active_maps = nil @active_maps = nil
puts "-- sshfs-mapper --"
@config = Config.new @config = Config.new
@config.parse_cmd_line ARGV @config.parse_maps
@config.parse_file #@config.parse_cmd_line ARGV
@all_maps = {} @all_maps = {}
pp @config
end end
@ -43,7 +47,7 @@ module Qasim
def run_mount def run_mount
# asynchronous mount # asynchronous mount
selected_maps = @config.maps.select do |map| @config.maps.select do |map|
pp map pp map
map.online? map.online?
# if map.available? then # if map.available? then
@ -69,8 +73,6 @@ module Qasim
else else
raise RuntimeError, "Unknown action" raise RuntimeError, "Unknown action"
end end
puts "--run"
end end
end end

View file

@ -27,22 +27,23 @@ module Qasim
@debug = false @debug = false
end end
# FIXME: move out of config
def parse_maps &blk def parse_maps &blk
@maps = [] @maps = []
map_dirs = [@config_dir, APP_SYSCONFIG_DIR].select{ |d| map_dirs = [@config_dir, APP_SYSCONFIG_DIR].select{ |d|
File.exist? d and File.directory? d File.exist? d and File.directory? d
} }
Find.find( *map_dirs ) do |path| Find.find(*map_dirs) do |path|
# Skip unwanted files fast # Skip unwanted files fast
next unless File.file? path next unless File.file? path
next unless File.basename( path ) =~ /.map$/ next unless File.basename(path) =~ /.map$/
begin begin
map = Map.from_file self, path map = Map.from_file self, path
yield map if block_given? yield map if block_given?
maps.push map maps.push map
rescue rescue Map::ParseError
raise RuntimeError, "Error while parsing map file" raise RuntimeError, "Error while parsing map file"
end end
end end

274
lib/qasim/gui.rb Normal file
View file

@ -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],
("<b>Map %sed successfully<b>" % method.to_s),
'dialog-information'
else
erroneous = @connect_error[map.path].to_a.join(', ')
dbus_notify "%s (%s)" % [APP_NAME, map.name],
("<b>Unable to %s map</b><br>" % 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

View file

@ -54,8 +54,8 @@ module Qasim ; module Map
# Load description from file and create a Map object # Load description from file and create a Map object
# #
def from_file appcfg, filename def from_file appcfg, filename
config = { params = {
type: :ssh # for config V1, we assume SSHFS type: :ssh # for params V1, we assume SSHFS by default
} }
map = nil map = nil
@ -66,30 +66,31 @@ module Qasim ; module Map
linect += 1 linect += 1
line = env_substitute(line) line = env_substitute(line)
params[:filename] = filename
case line case line
when /^\s*TYPE\s*=\s*(.*)\s*$/ then when /^\s*TYPE\s*=\s*(.*)\s*$/ then
config[:type] = $1 params[:type] = $1
when /^\s*REMOTE_USER\s*=\s*(.*)\s*$/ then when /^\s*REMOTE_USER\s*=\s*(.*)\s*$/ then
config[:user] = $1 params[:user] = $1
when /^\s*REMOTE_PORT\s*=\s*(.*)\s*$/ then 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 when /^\s*REMOTE_HOST\s*=\s*(.*)\s*$/ then
config[:host] = $1 params[:host] = $1
when /^\s*REMOTE_CYPHER\s*=\s*(.*)\s*$/ then when /^\s*REMOTE_CYPHER\s*=\s*(.*)\s*$/ then
if CYPHERS.map(&:to_s).include? $1 then if CYPHERS.map(&:to_s).include? $1 then
config[:cypher] = $1.to_sym params[:cypher] = $1.to_sym
end end
when /^\s*MAP\s*=\s*(.*)\s+(.*)\s*$/ then when /^\s*MAP\s*=\s*(.*)\s+(.*)\s*$/ then
config[:links] ||= {} params[:links] ||= {}
config[:links][$1] = $2 params[:links][$1] = $2
when /^\s*$/,/^\s*#/ then when /^\s*$/,/^\s*#/ then
else else
raise MapParseError, "parse error at #{@filename}:#{linect}" raise MapParseError, "parse error at #{@filename}:#{linect}"
end end
end end
f.close f.close
map = Ssh.new config, filename map_class = class_for params[:type]
map = map_class.new appcfg, params
return map return map
end end

View file

@ -4,7 +4,17 @@ module Qasim ; module Map
end ; end end ; end
class Qasim::Map::Generic 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 end
@ -17,7 +27,7 @@ class Qasim::Map::Generic
def self.parameters def self.parameters
{ {
map_name: [nil , true], map_name: [nil , true],
map_enabled: [true, false], map_enable: [true, false],
map_mountpoint: [nil, true] map_mountpoint: [nil, true]
} }
end end

View file

@ -34,20 +34,12 @@ class Qasim::Map::Ssh < Qasim::Map::Generic
# #
# Set defaults properties for maps # Set defaults properties for maps
# #
def initialize config, map_path def initialize *opts
super super
@config = config #@host = nil
@path = map_path #@port = 22
@host = nil #@user = nil
@port = 22 #@cypher = :arcfour
@enable = false
@user = nil
@cypher = :arcfour
@links = {}
@debug = false
@name = (File.basename map_path).gsub(/\.map$/,'')
self.load @path
end end
# #