Compare commits
5 Commits
in-progres
...
master
Author | SHA1 | Date |
---|---|---|
John Bintz | be054685ac | |
John Bintz | ce5128b244 | |
John Bintz | 53ab8e23bc | |
John Bintz | 6e291cc583 | |
John Bintz | 1c379bdbe7 |
|
@ -1,2 +1,3 @@
|
||||||
Gemfile.lock
|
Gemfile.lock
|
||||||
|
.DS_Store
|
||||||
|
|
||||||
|
|
4
Gemfile
4
Gemfile
|
@ -1,6 +1,6 @@
|
||||||
source :rubygems
|
source :rubygems
|
||||||
|
gemspec
|
||||||
|
|
||||||
gem 'qtbindings'
|
|
||||||
require 'rbconfig'
|
require 'rbconfig'
|
||||||
|
|
||||||
case RbConfig::CONFIG['host_os']
|
case RbConfig::CONFIG['host_os']
|
||||||
|
@ -10,5 +10,3 @@ when /linux/
|
||||||
gem 'rb-inotify'
|
gem 'rb-inotify'
|
||||||
end
|
end
|
||||||
|
|
||||||
gem 'atomic'
|
|
||||||
gem 'thor'
|
|
||||||
|
|
5
Rakefile
5
Rakefile
|
@ -1,5 +0,0 @@
|
||||||
desc 'Build app'
|
|
||||||
task :build_app do
|
|
||||||
cp_r 'skel/UnisonWatch.app', '.'
|
|
||||||
end
|
|
||||||
|
|
Binary file not shown.
Binary file not shown.
262
bin/unison-watch
262
bin/unison-watch
|
@ -1,259 +1,23 @@
|
||||||
#!/usr/bin/env ruby
|
#!/usr/bin/env ruby
|
||||||
|
|
||||||
|
$: << File.expand_path('../../lib', __FILE__)
|
||||||
|
|
||||||
require 'Qt4'
|
require 'Qt4'
|
||||||
require 'thread'
|
require 'thread'
|
||||||
require 'atomic'
|
require 'atomic'
|
||||||
require 'thor'
|
|
||||||
|
|
||||||
|
require 'unison'
|
||||||
|
require 'unison/watcher'
|
||||||
|
require 'unison/cli'
|
||||||
require 'unison/profile'
|
require 'unison/profile'
|
||||||
|
require 'unison/config'
|
||||||
|
require 'unison/bridge'
|
||||||
|
require 'unison/filesystem_watcher'
|
||||||
|
require 'unison/ui/icon'
|
||||||
|
require 'unison/ui/menu'
|
||||||
|
require 'unison/ui/fileview'
|
||||||
|
require 'unison/ui/preferences'
|
||||||
|
|
||||||
class UnisonWatcher < Qt::Application
|
|
||||||
TRANSFER_LOG = '~/unison.log'
|
|
||||||
SYNC_CHECK_COUNT = 600
|
|
||||||
SYNC_CHECK_TIME = 0.1
|
|
||||||
|
|
||||||
def initialize(profiles, *args)
|
Unison::CLI.start
|
||||||
super(*args)
|
|
||||||
|
|
||||||
@profiles = profiles
|
|
||||||
@queue = Atomic.new([])
|
|
||||||
|
|
||||||
@sync_now = false
|
|
||||||
@exiting = false
|
|
||||||
@active = true
|
|
||||||
end
|
|
||||||
|
|
||||||
def <<(dirs)
|
|
||||||
@queue.update { |q| q += [ dirs ].flatten ; q }
|
|
||||||
end
|
|
||||||
|
|
||||||
def processed_profiles
|
|
||||||
@processed_profiles ||= Unison::Profile.process(@profiles)
|
|
||||||
end
|
|
||||||
|
|
||||||
def watch
|
|
||||||
require 'rbconfig'
|
|
||||||
|
|
||||||
watcher = Thread.new do
|
|
||||||
while !Thread.current[:app]; sleep 0.1; end
|
|
||||||
|
|
||||||
begin
|
|
||||||
@watch = nil
|
|
||||||
|
|
||||||
case RbConfig::CONFIG['host_os']
|
|
||||||
when /darwin/
|
|
||||||
require 'rb-fsevent'
|
|
||||||
@watch = FSEvent.new
|
|
||||||
@watch.watch Thread.current[:paths], :latency => 0.1 do |directories|
|
|
||||||
Thread.current[:app] << directories
|
|
||||||
end
|
|
||||||
when /linux/
|
|
||||||
require 'rb-inotify'
|
|
||||||
@watch = INotify::Notifier.new
|
|
||||||
Thread.current[:paths].each do |path|
|
|
||||||
FileUtils.mkdir_p path
|
|
||||||
|
|
||||||
@watch.watch path, :recursive, :modify, :create, :delete do |event|
|
|
||||||
Thread.current[:app] << event.absolute_name
|
|
||||||
end
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
@watch.run
|
|
||||||
rescue => e
|
|
||||||
puts e.message
|
|
||||||
puts e.backtrace.join("\n")
|
|
||||||
exit
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
watcher[:paths] = processed_profiles.collect(&:paths_with_local_root).flatten
|
|
||||||
watcher[:app] = self
|
|
||||||
end
|
|
||||||
|
|
||||||
def update_status(status)
|
|
||||||
new_status = Qt::Action.new(status, @menu)
|
|
||||||
new_status.enabled = false
|
|
||||||
@menu.insertAction(@active_status, new_status)
|
|
||||||
@menu.removeAction(@status)
|
|
||||||
|
|
||||||
@status = new_status
|
|
||||||
end
|
|
||||||
|
|
||||||
def menu
|
|
||||||
@menu = Qt::Menu.new
|
|
||||||
|
|
||||||
@current_text = 'Unison watch idle.'
|
|
||||||
|
|
||||||
@status = Qt::Action.new(@status_text, @menu)
|
|
||||||
@status.enabled = false
|
|
||||||
|
|
||||||
sync_now = Qt::Action.new("Sync now", @menu)
|
|
||||||
sync_now.connect(SIGNAL :triggered) { @sync_now = true }
|
|
||||||
|
|
||||||
@active_status = Qt::Action.new("Pause syncing", @menu)
|
|
||||||
@active_status.connect(SIGNAL :triggered) { toggle_status }
|
|
||||||
|
|
||||||
@current_log_size = File.size(File.expand_path(TRANSFER_LOG))
|
|
||||||
|
|
||||||
@fileview = Qt::TextEdit.new
|
|
||||||
@fileview.plainText = "Watching for changes..."
|
|
||||||
@fileview.readOnly = true
|
|
||||||
@fileview.resize 800, 600
|
|
||||||
@fileview.connect(SIGNAL :textChanged) { @fileview.moveCursor Qt::TextCursor::End }
|
|
||||||
|
|
||||||
@log = Qt::Action.new("View transfer log", @menu)
|
|
||||||
@log.connect(SIGNAL :triggered) { @fileview.show }
|
|
||||||
|
|
||||||
quit = Qt::Action.new("Quit", @menu)
|
|
||||||
quit.connect(SIGNAL :triggered) { @exiting = true }
|
|
||||||
|
|
||||||
@menu.addAction @active_status
|
|
||||||
@menu.addAction sync_now
|
|
||||||
@menu.addSeparator
|
|
||||||
@menu.addAction @log
|
|
||||||
@menu.addAction quit
|
|
||||||
|
|
||||||
@current_text = 'Unison watch idle.'
|
|
||||||
update_status @current_text
|
|
||||||
|
|
||||||
@menu
|
|
||||||
end
|
|
||||||
|
|
||||||
def update_ui
|
|
||||||
if @current_icon != @prior_icon
|
|
||||||
if !@icons[@current_icon]
|
|
||||||
@icons[@current_icon] = Qt::Icon.new(File.expand_path("../../assets/#{@current_icon}.png", __FILE__))
|
|
||||||
@icons["large-#{@current_icon}"] = Qt::Icon.new(File.expand_path("../../assets/large-#{@current_icon}.png", __FILE__))
|
|
||||||
end
|
|
||||||
|
|
||||||
@icon.icon = @icons[@current_icon]
|
|
||||||
self.windowIcon = @icons["large-#{@current_icon}"]
|
|
||||||
@icon.toolTip = "Unison Agent\nUsing #{@profiles.join(', ')} profile"
|
|
||||||
if !@prior_icon
|
|
||||||
@icon.show
|
|
||||||
end
|
|
||||||
|
|
||||||
@prior_icon = @current_icon
|
|
||||||
end
|
|
||||||
|
|
||||||
if @current_text!= @prior_text
|
|
||||||
update_status @current_text
|
|
||||||
|
|
||||||
@prior_text = @current_text
|
|
||||||
end
|
|
||||||
|
|
||||||
processEvents
|
|
||||||
end
|
|
||||||
|
|
||||||
def ui
|
|
||||||
@icon = Qt::SystemTrayIcon.new
|
|
||||||
@icon.contextMenu = menu
|
|
||||||
|
|
||||||
toggle_status true
|
|
||||||
@prior_icon = nil
|
|
||||||
@prior_text = nil
|
|
||||||
|
|
||||||
@icons = {}
|
|
||||||
|
|
||||||
@remote_sync_check = SYNC_CHECK_COUNT
|
|
||||||
|
|
||||||
while !@exiting
|
|
||||||
check
|
|
||||||
|
|
||||||
update_ui
|
|
||||||
|
|
||||||
sleep SYNC_CHECK_TIME
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
def start
|
|
||||||
self.objectName = "cats"
|
|
||||||
|
|
||||||
watch
|
|
||||||
ui
|
|
||||||
end
|
|
||||||
|
|
||||||
def check
|
|
||||||
begin
|
|
||||||
if @active && (@queue.value.length > 0 || @remote_sync_check == 0 || @sync_now)
|
|
||||||
dir = nil
|
|
||||||
|
|
||||||
@current_text = "Syncing..."
|
|
||||||
|
|
||||||
@done = false
|
|
||||||
|
|
||||||
runner = Thread.new do
|
|
||||||
@profiles.each do |profile|
|
|
||||||
system %{bash -c 'unison -log -logfile #{TRANSFER_LOG} -batch #{profile}'}
|
|
||||||
end
|
|
||||||
@done = true
|
|
||||||
end
|
|
||||||
|
|
||||||
index = 0
|
|
||||||
while !@done
|
|
||||||
@current_icon = "working-#{index + 1}"
|
|
||||||
File.open(File.expand_path(TRANSFER_LOG), 'r') { |fh|
|
|
||||||
fh.seek(@current_log_size)
|
|
||||||
@fileview.plainText = fh.read
|
|
||||||
}
|
|
||||||
|
|
||||||
update_ui
|
|
||||||
|
|
||||||
break if @done
|
|
||||||
|
|
||||||
sleep 0.25
|
|
||||||
index = (index + 1) % 2
|
|
||||||
end
|
|
||||||
|
|
||||||
@current_icon = 'idle'
|
|
||||||
|
|
||||||
@remote_sync_check = SYNC_CHECK_COUNT
|
|
||||||
@sync_now = false
|
|
||||||
@queue.update { [] }
|
|
||||||
|
|
||||||
File.open(File.expand_path(TRANSFER_LOG), 'r') { |fh|
|
|
||||||
fh.seek(@current_log_size)
|
|
||||||
@fileview.plainText = fh.read
|
|
||||||
}
|
|
||||||
end
|
|
||||||
rescue => e
|
|
||||||
puts e.message
|
|
||||||
puts e.backtrace.join("\n")
|
|
||||||
exit
|
|
||||||
end
|
|
||||||
|
|
||||||
@remote_sync_check -= 1
|
|
||||||
|
|
||||||
if @active
|
|
||||||
@current_text = "Next check in #{sprintf("%.0d", SYNC_CHECK_TIME * @remote_sync_check)} secs."
|
|
||||||
else
|
|
||||||
@current_text = "Syncing paused."
|
|
||||||
|
|
||||||
@remote_sync_check = SYNC_CHECK_COUNT
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
def toggle_status(set = nil)
|
|
||||||
@active = set || !@active
|
|
||||||
|
|
||||||
@active_status.text = @active ? "Pause syncing" : "Resume syncing"
|
|
||||||
@current_icon = @active ? 'idle' : 'paused'
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
class UnisonCLI < Thor
|
|
||||||
desc 'start profile <profile> ...', 'Run Unison Watch using the provided profiles'
|
|
||||||
def start(*profiles)
|
|
||||||
UnisonWatcher.new(profiles, ARGV).start
|
|
||||||
end
|
|
||||||
|
|
||||||
default_task :run
|
|
||||||
|
|
||||||
def method_missing(*args)
|
|
||||||
start(*args)
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
UnisonCLI.start
|
|
||||||
|
|
||||||
|
|
|
@ -0,0 +1,6 @@
|
||||||
|
module Unison
|
||||||
|
class << self
|
||||||
|
def root ; File.expand_path('../..', __FILE__) ; end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
|
@ -0,0 +1,32 @@
|
||||||
|
module Unison
|
||||||
|
class Bridge
|
||||||
|
def self.run(*args, &block)
|
||||||
|
new(*args).run(&block)
|
||||||
|
end
|
||||||
|
|
||||||
|
def initialize(config, log)
|
||||||
|
@config, @log = config, log
|
||||||
|
end
|
||||||
|
|
||||||
|
def run(&block)
|
||||||
|
Thread.new do
|
||||||
|
begin
|
||||||
|
@config.profiles.each do |profile|
|
||||||
|
system %{bash -c '#{@config.unison_binary} -ui text -log -logfile #{@log} -batch #{profile}'}
|
||||||
|
|
||||||
|
if $?.exitstatus != 0
|
||||||
|
system %{bash -c '#{@config.unison_binary} -ui graphic #{profile}'}
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
block.call
|
||||||
|
rescue => e
|
||||||
|
puts e.message
|
||||||
|
puts e.backtrace.join("\n")
|
||||||
|
exit 1
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
|
@ -0,0 +1,43 @@
|
||||||
|
require 'thor'
|
||||||
|
|
||||||
|
module Unison
|
||||||
|
class CLI < Thor
|
||||||
|
include Thor::Actions
|
||||||
|
|
||||||
|
desc 'start profile <profile> ...', 'Run Unison Watch using the provided profiles'
|
||||||
|
def start(*profiles)
|
||||||
|
Watcher.new(profiles, ARGV).start
|
||||||
|
end
|
||||||
|
|
||||||
|
default_task :run
|
||||||
|
def method_missing(*args)
|
||||||
|
start(*args)
|
||||||
|
end
|
||||||
|
|
||||||
|
def self.source_root
|
||||||
|
File.join(Unison.root, 'skel')
|
||||||
|
end
|
||||||
|
|
||||||
|
desc 'app-bundle', 'Make an app bundle in the current directory'
|
||||||
|
def app_bundle
|
||||||
|
destination_path = Dir.pwd
|
||||||
|
|
||||||
|
directory 'UnisonWatch.app', 'UnisonWatch.app'
|
||||||
|
Dir['UnisonWatch.app/**/*'].each do |file|
|
||||||
|
if File.directory?(file)
|
||||||
|
File.chmod(0755, file)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
File.chmod(0755, "UnisonWatch.app/Contents/MacOS/UnisonWatch")
|
||||||
|
|
||||||
|
system %{bin/setfileicon assets/unison.icns UnisonWatch.app}
|
||||||
|
end
|
||||||
|
|
||||||
|
no_tasks do
|
||||||
|
def gem_directory
|
||||||
|
Unison.root
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
|
@ -0,0 +1,99 @@
|
||||||
|
require 'yaml'
|
||||||
|
require 'atomic'
|
||||||
|
|
||||||
|
module Unison
|
||||||
|
class Config
|
||||||
|
DEFAULT_TIME_BETWEEN_CHECKS = 60
|
||||||
|
|
||||||
|
def self.ensure(file)
|
||||||
|
if !File.file?(file)
|
||||||
|
File.open(file, 'wb') { |fh| fh.print YAML.dump(skel_data) }
|
||||||
|
end
|
||||||
|
|
||||||
|
new(file)
|
||||||
|
end
|
||||||
|
|
||||||
|
def self.skel_data
|
||||||
|
{
|
||||||
|
'profiles' => [],
|
||||||
|
'time_between_checks' => DEFAULT_TIME_BETWEEN_CHECKS,
|
||||||
|
'unison_binary' => '/usr/bin/unison'
|
||||||
|
}
|
||||||
|
end
|
||||||
|
|
||||||
|
def set_profile(profile, is_set)
|
||||||
|
@data.update do |d|
|
||||||
|
if is_set
|
||||||
|
d['profiles'] << profile
|
||||||
|
else
|
||||||
|
d['profiles'].delete(profile)
|
||||||
|
end
|
||||||
|
|
||||||
|
d['profiles'].uniq!
|
||||||
|
|
||||||
|
d
|
||||||
|
end
|
||||||
|
|
||||||
|
save
|
||||||
|
end
|
||||||
|
|
||||||
|
def on_update(&block)
|
||||||
|
@on_update ||= []
|
||||||
|
@on_update << block
|
||||||
|
end
|
||||||
|
|
||||||
|
def initialize(file)
|
||||||
|
@file = file
|
||||||
|
|
||||||
|
@data = Atomic.new(nil)
|
||||||
|
end
|
||||||
|
|
||||||
|
def active?(profile)
|
||||||
|
profiles.include?(profile)
|
||||||
|
end
|
||||||
|
|
||||||
|
def profiles
|
||||||
|
data['profiles']
|
||||||
|
end
|
||||||
|
|
||||||
|
def time_between_checks
|
||||||
|
data['time_between_checks']
|
||||||
|
end
|
||||||
|
|
||||||
|
def time_between_checks=(time)
|
||||||
|
time = DEFAULT_TIME_BETWEEN_CHECKS if time <= 10
|
||||||
|
|
||||||
|
data['time_between_checks'] = time
|
||||||
|
save
|
||||||
|
end
|
||||||
|
|
||||||
|
def unison_binary
|
||||||
|
data['unison_binary']
|
||||||
|
end
|
||||||
|
|
||||||
|
def unison_binary=(binary)
|
||||||
|
data['unison_binary'] = binary
|
||||||
|
save
|
||||||
|
end
|
||||||
|
|
||||||
|
def data
|
||||||
|
@data.update { |d| d || YAML.load_file(@file) }
|
||||||
|
@data.value
|
||||||
|
end
|
||||||
|
|
||||||
|
def active_profiles?
|
||||||
|
!profiles.empty?
|
||||||
|
end
|
||||||
|
|
||||||
|
def save
|
||||||
|
@data.update do |d|
|
||||||
|
File.open(@file, 'wb') { |fh| fh.print YAML.dump(d) }
|
||||||
|
d
|
||||||
|
end
|
||||||
|
|
||||||
|
if @on_update
|
||||||
|
@on_update.each(&:call)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
|
@ -0,0 +1,58 @@
|
||||||
|
module Unison
|
||||||
|
class FilesystemWatcher
|
||||||
|
class NoWatcherAvailable < StandardError ; end
|
||||||
|
|
||||||
|
def initialize(paths, owner)
|
||||||
|
@paths, @owner = paths, owner
|
||||||
|
end
|
||||||
|
|
||||||
|
def run
|
||||||
|
require 'rbconfig'
|
||||||
|
|
||||||
|
@watcher = Thread.new do
|
||||||
|
while !Thread.current[:app]; sleep 0.1; end
|
||||||
|
|
||||||
|
begin
|
||||||
|
case RbConfig::CONFIG['host_os']
|
||||||
|
when /(darwin|linux)/
|
||||||
|
@watch = send("watcher_for_#{$1}")
|
||||||
|
else
|
||||||
|
raise NoWatcherAvailable.new
|
||||||
|
end
|
||||||
|
|
||||||
|
@watch.run
|
||||||
|
rescue => e
|
||||||
|
puts e.message
|
||||||
|
puts e.backtrace.join("\n")
|
||||||
|
exit
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
@watcher[:paths] = @paths
|
||||||
|
@watcher[:app] = @owner
|
||||||
|
end
|
||||||
|
|
||||||
|
def watcher_for_darwin
|
||||||
|
require 'rb-fsevent'
|
||||||
|
watch = FSEvent.new
|
||||||
|
watch.watch Thread.current[:paths], :latency => 1.0 do |directories|
|
||||||
|
Thread.current[:app] << directories
|
||||||
|
end
|
||||||
|
watch
|
||||||
|
end
|
||||||
|
|
||||||
|
def watcher_for_linux
|
||||||
|
require 'rb-inotify'
|
||||||
|
watch = INotify::Notifier.new
|
||||||
|
Thread.current[:paths].each do |path|
|
||||||
|
FileUtils.mkdir_p path
|
||||||
|
|
||||||
|
watch.watch path, :recursive, :modify, :create, :delete do |event|
|
||||||
|
Thread.current[:app] << event.absolute_name
|
||||||
|
end
|
||||||
|
end
|
||||||
|
watch
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
|
@ -1,9 +1,15 @@
|
||||||
module Unison
|
module Unison
|
||||||
class Profile
|
class Profile
|
||||||
|
PROFILE_DIR = File.expand_path('~/.unison')
|
||||||
|
|
||||||
def self.process(profiles)
|
def self.process(profiles)
|
||||||
profiles.collect { |profile| new(profile) }
|
profiles.collect { |profile| new(profile) }
|
||||||
end
|
end
|
||||||
|
|
||||||
|
def self.available
|
||||||
|
Dir[File.join(PROFILE_DIR, '*.prf')].collect { |file| File.basename(file).gsub('.prf', '') }.sort
|
||||||
|
end
|
||||||
|
|
||||||
def initialize(which)
|
def initialize(which)
|
||||||
@which = which
|
@which = which
|
||||||
end
|
end
|
||||||
|
|
|
@ -0,0 +1,36 @@
|
||||||
|
module Unison
|
||||||
|
module UI
|
||||||
|
class FileView < Qt::TextEdit
|
||||||
|
INITIAL = "Watching for changes..."
|
||||||
|
|
||||||
|
def initialize(file, *args)
|
||||||
|
super(*args)
|
||||||
|
|
||||||
|
@file = file
|
||||||
|
@current_log_size = File.exist?(@file) ? File.size(@file) : 0
|
||||||
|
|
||||||
|
self.plainText = INITIAL
|
||||||
|
self.readOnly = true
|
||||||
|
self.resize 800, 600
|
||||||
|
self.connect(SIGNAL(:textChanged), &method(:on_text_change))
|
||||||
|
end
|
||||||
|
|
||||||
|
def show
|
||||||
|
super
|
||||||
|
self.raise
|
||||||
|
end
|
||||||
|
|
||||||
|
def on_text_change
|
||||||
|
self.moveCursor Qt::TextCursor::End
|
||||||
|
end
|
||||||
|
|
||||||
|
def read!
|
||||||
|
File.open(@file, 'r') { |fh|
|
||||||
|
fh.seek(@current_log_size)
|
||||||
|
self.plainText = fh.read
|
||||||
|
}
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
|
@ -0,0 +1,45 @@
|
||||||
|
module Unison
|
||||||
|
module UI
|
||||||
|
class Icon < Qt::SystemTrayIcon
|
||||||
|
def initialize(menu, window, profiles, icon_source, *args)
|
||||||
|
super(*args)
|
||||||
|
|
||||||
|
self.contextMenu = menu
|
||||||
|
@window, @icon_source, @profiles = window, icon_source, profiles
|
||||||
|
|
||||||
|
@current_icon = nil
|
||||||
|
@icons = {}
|
||||||
|
end
|
||||||
|
|
||||||
|
def qt_icon_for(name)
|
||||||
|
@icons[name] ||= Qt::Icon.new(File.join(@icon_source, "#{name}.png"))
|
||||||
|
end
|
||||||
|
|
||||||
|
def large_qt_icon_for(name)
|
||||||
|
qt_icon_for("large-#{name}")
|
||||||
|
end
|
||||||
|
|
||||||
|
def profiles=(profiles)
|
||||||
|
@profiles = profiles
|
||||||
|
set_tooltip
|
||||||
|
end
|
||||||
|
|
||||||
|
def current_icon=(icon)
|
||||||
|
if icon != @current_icon
|
||||||
|
self.icon = qt_icon_for(icon)
|
||||||
|
@window.windowIcon = large_qt_icon_for(icon)
|
||||||
|
set_tooltip
|
||||||
|
show if !@current_icon
|
||||||
|
|
||||||
|
@current_icon = icon
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
def set_tooltip
|
||||||
|
self.toolTip = "Unison Agent\nUsing #{@profiles.join(', ')} profile"
|
||||||
|
show
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
|
@ -0,0 +1,70 @@
|
||||||
|
module Unison
|
||||||
|
module UI
|
||||||
|
class Menu < Qt::Menu
|
||||||
|
IDLE = 'Unison watch idle.'
|
||||||
|
SYNC_NOW = 'Sync now'
|
||||||
|
PAUSE_SYNCING = 'Pause syncing'
|
||||||
|
VIEW_TRANSFER_LOG = "View transfer log"
|
||||||
|
QUIT = 'Quit'
|
||||||
|
|
||||||
|
def initialize(*args)
|
||||||
|
super(*args)
|
||||||
|
|
||||||
|
@status_text = IDLE
|
||||||
|
end
|
||||||
|
|
||||||
|
def on(event, &block)
|
||||||
|
@on ||= {}
|
||||||
|
@on[event] = block
|
||||||
|
end
|
||||||
|
|
||||||
|
def generate
|
||||||
|
generate_status
|
||||||
|
|
||||||
|
@sync_now = Qt::Action.new(SYNC_NOW, self)
|
||||||
|
@sync_now.connect(SIGNAL(:triggered), &@on[:sync_now])
|
||||||
|
|
||||||
|
@active_status = Qt::Action.new(PAUSE_SYNCING, self)
|
||||||
|
@active_status.connect(SIGNAL(:triggered), &@on[:toggle_status])
|
||||||
|
|
||||||
|
@log = Qt::Action.new(VIEW_TRANSFER_LOG, @menu)
|
||||||
|
@log.connect(SIGNAL(:triggered), &@on[:view_log])
|
||||||
|
|
||||||
|
@preferences = Qt::Action.new('Preferences...', @menu)
|
||||||
|
@preferences.connect(SIGNAL(:triggered), &@on[:preferences])
|
||||||
|
|
||||||
|
@quit = Qt::Action.new(QUIT, @menu)
|
||||||
|
@quit.connect(SIGNAL(:triggered), &@on[:quit])
|
||||||
|
|
||||||
|
addAction @active_status
|
||||||
|
addAction @sync_now
|
||||||
|
addSeparator
|
||||||
|
addAction @log
|
||||||
|
addAction @preferences
|
||||||
|
addAction @quit
|
||||||
|
end
|
||||||
|
|
||||||
|
def status_text=(text)
|
||||||
|
if @status_text != text
|
||||||
|
@status_text = text
|
||||||
|
|
||||||
|
generate_status
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
def active_status_text=(text)
|
||||||
|
@active_status.text = text
|
||||||
|
end
|
||||||
|
|
||||||
|
def generate_status
|
||||||
|
new_status = Qt::Action.new(@status_text, self)
|
||||||
|
new_status.enabled = false
|
||||||
|
insertAction(@active_status, new_status)
|
||||||
|
removeAction(@status)
|
||||||
|
|
||||||
|
@status = new_status
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
|
@ -0,0 +1,60 @@
|
||||||
|
module Unison
|
||||||
|
module UI
|
||||||
|
class Preferences < Qt::Widget
|
||||||
|
def initialize(config, *args)
|
||||||
|
super(*args)
|
||||||
|
|
||||||
|
@config = config
|
||||||
|
|
||||||
|
generate
|
||||||
|
end
|
||||||
|
|
||||||
|
def show
|
||||||
|
super
|
||||||
|
self.raise
|
||||||
|
end
|
||||||
|
|
||||||
|
def generate
|
||||||
|
layout = Qt::GridLayout.new
|
||||||
|
|
||||||
|
profile_group = Qt::GroupBox.new("Profiles")
|
||||||
|
profile_group_layout = Qt::VBoxLayout.new
|
||||||
|
profile_group.setLayout(profile_group_layout)
|
||||||
|
|
||||||
|
Unison::Profile.available.each do |profile|
|
||||||
|
radio = Qt::CheckBox.new(profile)
|
||||||
|
radio.checked = @config.active?(profile)
|
||||||
|
radio.connect(SIGNAL "toggled(bool)") { |checked| @config.set_profile(profile, checked) }
|
||||||
|
profile_group_layout.addWidget(radio)
|
||||||
|
end
|
||||||
|
|
||||||
|
performance_group = Qt::GroupBox.new("Performance")
|
||||||
|
performance_group_layout = Qt::VBoxLayout.new
|
||||||
|
performance_group.setLayout(performance_group_layout)
|
||||||
|
|
||||||
|
fields = Qt::Widget.new
|
||||||
|
fields_layout = Qt::GridLayout.new
|
||||||
|
fields.setLayout(fields_layout)
|
||||||
|
|
||||||
|
count_label = Qt::Label.new("Seconds between checks (min 10s):")
|
||||||
|
count_field = Qt::LineEdit.new(@config.time_between_checks.to_s)
|
||||||
|
count_field.connect(SIGNAL "textChanged(QString)") { |string| @config.time_between_checks = string.to_i }
|
||||||
|
|
||||||
|
binary_label = Qt::Label.new("Unison binary:")
|
||||||
|
binary_field = Qt::LineEdit.new(@config.unison_binary)
|
||||||
|
binary_field.connect(SIGNAL "textChanged(QString)") { |string| @config.unison_binary = string }
|
||||||
|
|
||||||
|
fields_layout.addWidget(count_label, 0, 0)
|
||||||
|
fields_layout.addWidget(count_field, 0, 1)
|
||||||
|
fields_layout.addWidget(binary_label, 1, 0)
|
||||||
|
fields_layout.addWidget(binary_field, 1, 1)
|
||||||
|
performance_group_layout.addWidget(fields)
|
||||||
|
|
||||||
|
layout.addWidget(profile_group, 0, 0)
|
||||||
|
layout.addWidget(performance_group, 0, 1)
|
||||||
|
|
||||||
|
setLayout(layout)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
|
@ -0,0 +1,167 @@
|
||||||
|
require 'forwardable'
|
||||||
|
|
||||||
|
module Unison
|
||||||
|
class Watcher < Qt::Application
|
||||||
|
TRANSFER_LOG = '~/unison.log'
|
||||||
|
SYNC_CHECK_TIME = 0.05
|
||||||
|
|
||||||
|
extend Forwardable
|
||||||
|
|
||||||
|
def initialize(profiles, *args)
|
||||||
|
super(*args)
|
||||||
|
|
||||||
|
@config = Unison::Config.ensure(File.expand_path('~/.unison/watch.yml'))
|
||||||
|
@queue = Atomic.new([])
|
||||||
|
|
||||||
|
@sync_now = false
|
||||||
|
@exiting = false
|
||||||
|
@active = true
|
||||||
|
|
||||||
|
@config.on_update { @remote_sync_check = time_between_checks }
|
||||||
|
end
|
||||||
|
|
||||||
|
def time_between_checks
|
||||||
|
@config.time_between_checks * 20
|
||||||
|
end
|
||||||
|
|
||||||
|
def <<(dirs)
|
||||||
|
@queue.update { |q| q += [ dirs ].flatten ; q }
|
||||||
|
end
|
||||||
|
|
||||||
|
def processed_profiles
|
||||||
|
@processed_profiles ||= Unison::Profile.process(@config.profiles)
|
||||||
|
end
|
||||||
|
|
||||||
|
def watch
|
||||||
|
Unison::FilesystemWatcher.new(paths_to_watch, self)
|
||||||
|
end
|
||||||
|
|
||||||
|
def paths_to_watch
|
||||||
|
processed_profiles.collect(&:paths_with_local_root).flatten
|
||||||
|
end
|
||||||
|
|
||||||
|
def fileview
|
||||||
|
@fileview ||= Unison::UI::FileView.new(File.expand_path(TRANSFER_LOG))
|
||||||
|
end
|
||||||
|
|
||||||
|
def menu
|
||||||
|
return @menu if @menu
|
||||||
|
|
||||||
|
@menu = Unison::UI::Menu.new
|
||||||
|
|
||||||
|
@menu.on(:sync_now) { @sync_now = true }
|
||||||
|
@menu.on(:toggle_status) { toggle_status }
|
||||||
|
@menu.on(:view_log) { fileview.show }
|
||||||
|
@menu.on(:quit) { @exiting = true }
|
||||||
|
@menu.on(:preferences) { preferences.show }
|
||||||
|
@menu.generate
|
||||||
|
|
||||||
|
@menu
|
||||||
|
end
|
||||||
|
|
||||||
|
def update_ui
|
||||||
|
@icon.current_icon = @current_icon
|
||||||
|
@menu.status_text = @current_text
|
||||||
|
|
||||||
|
processEvents
|
||||||
|
end
|
||||||
|
|
||||||
|
def ui
|
||||||
|
@icon = Unison::UI::Icon.new(menu, self, @config.profiles, File.join(Unison.root, 'assets'))
|
||||||
|
@config.on_update { @icon.profiles = @config.profiles }
|
||||||
|
|
||||||
|
@current_icon = 'idle'
|
||||||
|
|
||||||
|
toggle_status true
|
||||||
|
@prior_text = nil
|
||||||
|
|
||||||
|
@icons = {}
|
||||||
|
|
||||||
|
@remote_sync_check = time_between_checks
|
||||||
|
|
||||||
|
while !@exiting
|
||||||
|
check
|
||||||
|
|
||||||
|
update_ui
|
||||||
|
|
||||||
|
sleep SYNC_CHECK_TIME
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
def start
|
||||||
|
if !@config.active_profiles?
|
||||||
|
preferences.show
|
||||||
|
end
|
||||||
|
|
||||||
|
watch
|
||||||
|
ui
|
||||||
|
end
|
||||||
|
|
||||||
|
def preferences
|
||||||
|
@preferences ||= Unison::UI::Preferences.new(@config)
|
||||||
|
end
|
||||||
|
|
||||||
|
def show_working
|
||||||
|
index = 0
|
||||||
|
while true do
|
||||||
|
@current_icon = "working-#{index + 1}"
|
||||||
|
fileview.read!
|
||||||
|
|
||||||
|
update_ui
|
||||||
|
|
||||||
|
break if @done or @exiting
|
||||||
|
|
||||||
|
sleep 0.25
|
||||||
|
index = (index + 1) % 2
|
||||||
|
end
|
||||||
|
|
||||||
|
@current_icon = current_icon
|
||||||
|
fileview.read!
|
||||||
|
end
|
||||||
|
|
||||||
|
def check
|
||||||
|
begin
|
||||||
|
if @active && (@queue.value.length > 0 || @remote_sync_check == 0 || @sync_now)
|
||||||
|
dir = nil
|
||||||
|
|
||||||
|
@current_text = "Syncing..."
|
||||||
|
|
||||||
|
@done = false
|
||||||
|
|
||||||
|
Unison::Bridge.run(@config, TRANSFER_LOG) { @done = true }
|
||||||
|
|
||||||
|
show_working
|
||||||
|
|
||||||
|
@remote_sync_check = time_between_checks
|
||||||
|
@sync_now = false
|
||||||
|
@queue.update { [] }
|
||||||
|
end
|
||||||
|
rescue => e
|
||||||
|
puts e.message
|
||||||
|
puts e.backtrace.join("\n")
|
||||||
|
exit 1
|
||||||
|
end
|
||||||
|
|
||||||
|
@remote_sync_check -= 1
|
||||||
|
|
||||||
|
if @active
|
||||||
|
@current_text = "Next check in #{sprintf("%.0d", SYNC_CHECK_TIME * @remote_sync_check)} secs."
|
||||||
|
else
|
||||||
|
@current_text = "Syncing paused."
|
||||||
|
|
||||||
|
@remote_sync_check = time_between_checks
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
def toggle_status(set = nil)
|
||||||
|
@active = set || !@active
|
||||||
|
|
||||||
|
@menu.active_status_text = @active ? "Pause syncing" : "Resume syncing"
|
||||||
|
@current_icon = current_icon
|
||||||
|
end
|
||||||
|
|
||||||
|
def current_icon
|
||||||
|
@active ? 'idle' : 'paused'
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
|
@ -0,0 +1,24 @@
|
||||||
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
|
<!DOCTYPE plist PUBLIC "-//Apple Computer//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
|
||||||
|
<plist version="1.0">
|
||||||
|
<dict>
|
||||||
|
<key>CFBundleExecutable</key>
|
||||||
|
<string>UnisonWatch</string>
|
||||||
|
<key>CFBundleGetInfoString</key>
|
||||||
|
<string>Unison Watch</string>
|
||||||
|
<key>CFBundleIdentifier</key>
|
||||||
|
<string>unisonwatch</string>
|
||||||
|
<key>CFBundleInfoDictionaryVersion</key>
|
||||||
|
<string>6.0</string>
|
||||||
|
<key>CFBundleName</key>
|
||||||
|
<string>UnisonWatch</string>
|
||||||
|
<key>CFBundlePackageType</key>
|
||||||
|
<string>APPL</string>
|
||||||
|
<key>CFBundleShortVersionString</key>
|
||||||
|
<string>0.3.0</string>
|
||||||
|
<key>CFBundleSignature</key>
|
||||||
|
<string>WRUN</string>
|
||||||
|
<key>CFBundleVersion</key>
|
||||||
|
<string>0.0.1.20070616</string>
|
||||||
|
</dict>
|
||||||
|
</plist>
|
|
@ -0,0 +1,5 @@
|
||||||
|
#!/bin/bash
|
||||||
|
|
||||||
|
cd <%= gem_directory %>
|
||||||
|
GEM_HOME=<%= ENV['GEM_HOME'] %> GEM_PATH=<%= ENV['GEM_PATH'] %> PATH=<%= `which ruby`.gsub(%r{/[^/]+$}, '') %>:$PATH bin/unison-watch
|
||||||
|
|
|
@ -0,0 +1 @@
|
||||||
|
APPLWRUN
|
|
@ -0,0 +1,19 @@
|
||||||
|
Gem::Specification.new do |gem|
|
||||||
|
gem.authors = ["John Bintz"]
|
||||||
|
gem.email = ["john@coswellproductions.com"]
|
||||||
|
gem.description = %q{No-nonsense JavaScript testing solution.}
|
||||||
|
gem.summary = %q{No-nonsense JavaScript testing solution.}
|
||||||
|
gem.homepage = ""
|
||||||
|
|
||||||
|
gem.executables = `git ls-files -- bin/*`.split("\n").map{ |f| File.basename(f) }
|
||||||
|
gem.files = `git ls-files`.split("\n")
|
||||||
|
gem.test_files = `git ls-files -- {test,spec,features}/*`.split("\n")
|
||||||
|
gem.name = "unison-watch"
|
||||||
|
gem.require_paths = ["lib"]
|
||||||
|
gem.version = '0.0.1'
|
||||||
|
|
||||||
|
gem.add_dependency 'qtbindings'
|
||||||
|
gem.add_dependency 'thor'
|
||||||
|
gem.add_dependency 'atomic'
|
||||||
|
end
|
||||||
|
|
Loading…
Reference in New Issue