lipsiadmin 3.3.4 → 3.4.0

Sign up to get free protection for your applications and to get access to all the features.
@@ -0,0 +1,101 @@
1
+ require 'loops/worker'
2
+ require 'loops/worker_pool'
3
+
4
+ module Lipsiadmin
5
+ module Loops
6
+ class ProcessManager#:nodoc:
7
+ attr_reader :logger
8
+
9
+ def initialize(config, logger)
10
+ @config = {
11
+ 'poll_period' => 1,
12
+ 'workers_engine' => 'fork'
13
+ }.merge(config)
14
+
15
+ @logger = logger
16
+ @worker_pools = {}
17
+ @shutdown = false
18
+ end
19
+
20
+ def start_workers(name, number, &blk)
21
+ raise "Need a worker block!" unless block_given?
22
+
23
+ logger.debug("Creating a workers pool of #{number} workers for #{name} loop...")
24
+ @worker_pools[name] = Lipsiadmin::Loops::WorkerPool.new(name, logger, @config['workers_engine'], &blk)
25
+ @worker_pools[name].start_workers(number)
26
+ end
27
+
28
+ def monitor_workers
29
+ setup_signals
30
+
31
+ logger.debug('Starting workers monitoring code...')
32
+ loop do
33
+ logger.debug('Checking workers\' health...')
34
+ @worker_pools.each do |name, pool|
35
+ break if @shutdown
36
+ pool.check_workers
37
+ end
38
+
39
+ break if @shutdown
40
+ logger.debug("Sleeping for #{@config['poll_period']} seconds...")
41
+ sleep(@config['poll_period'])
42
+ end
43
+ ensure
44
+ unless wait_for_workers(10)
45
+ logger.debug("Some workers are still alive after 10 seconds of waiting. Killing them...")
46
+ stop_workers(true)
47
+ wait_for_workers(5)
48
+ end
49
+ end
50
+
51
+ def setup_signals
52
+ # Zombie rippers
53
+ trap('CHLD') {}
54
+ trap('EXIT') {}
55
+ end
56
+
57
+ def wait_for_workers(seconds)
58
+ seconds.times do
59
+ logger.debug("Shutting down... waiting for workers to die (we have #{seconds} seconds)...")
60
+ running_total = 0
61
+
62
+ @worker_pools.each do |name, pool|
63
+ running_total += pool.wait_workers
64
+ end
65
+
66
+ if running_total.zero?
67
+ logger.debug("All workers are dead. Exiting...")
68
+ return true
69
+ end
70
+
71
+ logger.debug("#{running_total} workers are still running! Sleeping for a second...")
72
+ sleep(1)
73
+ end
74
+
75
+ return false
76
+ end
77
+
78
+ def stop_workers(force = false)
79
+ return unless start_shutdown || force
80
+ logger.debug("Stopping workers#{force ? '(forced)' : ''}...")
81
+
82
+ # Termination loop
83
+ @worker_pools.each do |name, pool|
84
+ pool.stop_workers(force)
85
+ end
86
+ end
87
+
88
+ def stop_workers!
89
+ return unless start_shutdown
90
+ stop_workers(false)
91
+ sleep(1)
92
+ stop_workers(true)
93
+ end
94
+
95
+ def start_shutdown
96
+ return false if @shutdown
97
+ @shutdown = true
98
+ end
99
+ end
100
+ end
101
+ end
@@ -0,0 +1,81 @@
1
+ module Lipsiadmin
2
+ module Loops
3
+ class Worker#:nodoc:
4
+ attr_reader :logger
5
+ attr_reader :name
6
+ attr_reader :pid
7
+
8
+ def initialize(name, logger, engine, &blk)
9
+ raise "Need a worker block!" unless block_given?
10
+
11
+ @name = name
12
+ @logger = logger
13
+ @engine = engine
14
+ @worker_block = blk
15
+
16
+ @shutdown = false
17
+ end
18
+
19
+ def shutdown?
20
+ @shutdown
21
+ end
22
+
23
+ def run
24
+ return if shutdown?
25
+ if @engine == 'fork'
26
+ @pid = Kernel.fork do
27
+ $0 = "loop worker: #{@name}\0"
28
+ @pid = Process.pid
29
+ @worker_block.call
30
+ exit(0)
31
+ end
32
+ elsif @engine == 'thread'
33
+ @thread = Thread.start do
34
+ @worker_block.call
35
+ end
36
+ else
37
+ raise "Invalid engine name: #{@engine}"
38
+ end
39
+ rescue Exception => e
40
+ logger.error("Exception from worker: #{e} at #{e.backtrace.first}")
41
+ end
42
+
43
+ def running?(verbose = false)
44
+ return false if shutdown?
45
+ if @engine == 'fork'
46
+ return false unless @pid
47
+ begin
48
+ Process.waitpid(@pid, Process::WNOHANG)
49
+ res = Process.kill(0, @pid)
50
+ logger.debug("KILL(#{@pid}) = #{res}") if verbose
51
+ return true
52
+ rescue Errno::ESRCH, Errno::ECHILD, Errno::EPERM => e
53
+ logger.error("Exception from kill: #{e} at #{e.backtrace.first}") if verbose
54
+ return false
55
+ end
56
+ elsif @engine == 'thread'
57
+ @thread && @thread.alive?
58
+ else
59
+ raise "Invalid engine name: #{@engine}"
60
+ end
61
+ end
62
+
63
+ def stop(force = false)
64
+ @shutdown = true
65
+ if @engine == 'fork'
66
+ begin
67
+ sig = force ? 'SIGKILL' : 'SIGTERM'
68
+ logger.debug("Sending #{sig} to ##{@pid}")
69
+ Process.kill(sig, @pid)
70
+ rescue Errno::ESRCH, Errno::ECHILD, Errno::EPERM=> e
71
+ logger.error("Exception from kill: #{e} at #{e.backtrace.first}")
72
+ end
73
+ elsif @engine == 'thread'
74
+ force && !defined?(::JRuby) ? @thread.kill! : @thread.kill
75
+ else
76
+ raise "Invalid engine name: #{@engine}"
77
+ end
78
+ end
79
+ end
80
+ end
81
+ end
@@ -0,0 +1,53 @@
1
+ module Lipsiadmin
2
+ module Loops
3
+ class WorkerPool#:nodoc:
4
+ attr_reader :logger
5
+ attr_reader :name
6
+
7
+ def initialize(name, logger, engine, &blk)
8
+ @name = name
9
+ @logger = logger
10
+ @worker_block = blk
11
+ @shutdown = false
12
+ @engine = engine
13
+ @workers = []
14
+ end
15
+
16
+ def start_workers(number)
17
+ logger.debug("Creating #{number} workers for #{name} loop...")
18
+ number.times do
19
+ @workers << Lipsiadmin::Loops::Worker.new(name, logger, @engine, &@worker_block)
20
+ end
21
+ end
22
+
23
+ def check_workers
24
+ logger.debug("Checking loop #{name} workers...")
25
+ @workers.each do |worker|
26
+ next if worker.running? || worker.shutdown?
27
+ logger.debug("Worker #{worker.name} is not running. Restart!")
28
+ worker.run
29
+ end
30
+ end
31
+
32
+ def wait_workers
33
+ running = 0
34
+ @workers.each do |worker|
35
+ next unless worker.running?
36
+ running += 1
37
+ logger.debug("Worker #{name} is still running (#{worker.pid})")
38
+ end
39
+ return running
40
+ end
41
+
42
+ def stop_workers(force)
43
+ return if @shutdown
44
+ @shutdown = false
45
+ logger.debug("Stopping loop #{name} workers...")
46
+ @workers.each do |worker|
47
+ next unless worker.running?
48
+ worker.stop(force)
49
+ end
50
+ end
51
+ end
52
+ end
53
+ end
@@ -1,3 +1,4 @@
1
+ # coding: utf-8
1
2
  module Lipsiadmin
2
3
  module Utils
3
4
  # Convert common utf-8 chars to html entities, this is beautifull (but the code not)
data/lib/version.rb CHANGED
@@ -1,8 +1,8 @@
1
1
  module Lipsiadmin
2
2
  module VERSION #:nodoc:
3
3
  MAJOR = 3
4
- MINOR = 3
5
- TINY = 4
4
+ MINOR = 4
5
+ TINY = 0
6
6
 
7
7
  STRING = [MAJOR, MINOR, TINY].join('.')
8
8
  end
@@ -22,9 +22,12 @@ module Lipsiadmin
22
22
 
23
23
  # This method add tab for in your view
24
24
  def tab(name, padding=true, options={}, &block)
25
- options[:style] = "padding:10px;#{options[:style]}" if padding
26
- options[:id] ||= name.to_s.downcase.gsub(/[^a-z0-9]+/, '_').gsub(/-+$/, '').gsub(/^-+$/, '')
27
- concat content_tag(:div, capture(&block), { :id => options[:id], :class => "x-hide-display", :style => options[:style], :tabbed => true, :title => name })
25
+ options[:id] ||= name.to_s.downcase.gsub(/[^a-z0-9]+/, '_').gsub(/-+$/, '').gsub(/^-+$/, '')
26
+ options[:style] = "padding:10px;#{options[:style]}" if padding
27
+ options[:title] = name
28
+ options[:tabbed] = true
29
+ options[:class] = "x-hide-display"
30
+ concat content_tag(:div, capture(&block), options)
28
31
  end
29
32
 
30
33
  # Set the title of the page
@@ -49,7 +52,6 @@ module Lipsiadmin
49
52
  # Example:
50
53
  #
51
54
  # # in app/views/dossiers/_form.html.haml
52
-
53
55
  # %tr
54
56
  # %td{:style=>"width:100px"}
55
57
  # %b Customer:
@@ -178,6 +178,7 @@ module Lipsiadmin#:nodoc:
178
178
 
179
179
  def add_object(name, object)
180
180
  if object.class == Component || object.class.superclass == Component
181
+ @before.delete_if { |b| b.start_with?("var #{object.get_var} = new") }
181
182
  @before << object.to_s
182
183
  @config[name.to_sym] = l(object.get_var)
183
184
  else
@@ -157,22 +157,20 @@ module Lipsiadmin
157
157
  end
158
158
 
159
159
  # Generate or set a new Ext.grid.GroupingView
160
- # You can pass tbar :default options that will autocreate a correct GroupingView
160
+ # You can pass view :default options that will autocreate a correct GroupingView
161
161
  #
162
162
  # Examples:
163
163
  # view: new Ext.grid.GroupingView({
164
164
  # forceFit:true,
165
165
  # groupTextTpl: '{text} ({[values.rs.length]} {[values.rs.length > 1 ? "Foo" : "Bar"]})'
166
166
  # })
167
- # view :forceFit => true, :groupTextTpl => "{text} ({[values.rs.length]} {[values.rs.length > 1 ? "Foo" : "Bar"]})"
167
+ # view :forceFit => true, :groupTextTpl => '{text} ({[values.rs.length]} {[values.rs.length > 1 ? "Foo" : "Bar"]})'
168
168
  #
169
169
  def view(object=nil, &block)
170
- if object == :default
171
- view = Component.new("Ext.grid.GroupingView", { :forceFit => true })
172
- elsif value.is_a?(Hash)
173
- view = Component.new("Ext.grid.GroupingView", { :forceFit => true }.merge(object))
174
- else
175
- view = object
170
+ view = case object
171
+ when :default then Component.new("Ext.grid.GroupingView", { :forceFit => true })
172
+ when Hash then Component.new("Ext.grid.GroupingView", { :forceFit => true }.merge(object))
173
+ else object
176
174
  end
177
175
  add_object(:view, view)
178
176
  end
@@ -14,6 +14,6 @@ Extra:
14
14
  in config/environment.rb
15
15
 
16
16
  - config.active_record.timestamped_migrations = false
17
- - comment config.time_zone = 'UTC'
17
+ - config.time_zone = 'Rome'
18
18
 
19
19
  ================================================================
@@ -30,6 +30,7 @@ class BackendGenerator < Rails::Generator::Base
30
30
  m.create_all("models", "app/models")
31
31
  m.create_all("views", "app/views")
32
32
  m.create_all("config", "config")
33
+ m.create_all("test", "test")
33
34
 
34
35
  # Using this for prevent raising errors
35
36
  migration = Dir.glob("db/migrate/[0-9]*_*.rb").grep(/[0-9]+_create_accounts.rb$/)
@@ -78,7 +78,7 @@ class Account < ActiveRecord::Base
78
78
  end
79
79
 
80
80
  def authenticated?(password)
81
- crypted_password == encrypt(password)
81
+ crypted_password.chomp == encrypt(password).chomp
82
82
  end
83
83
 
84
84
 
@@ -0,0 +1,8 @@
1
+ admin:
2
+ id: 1
3
+ name: Davide
4
+ surname: D'Agostino
5
+ email: d.dagostino@lipsiasoft.com
6
+ salt: 066f31caf14fdf474c6b5586738be7cc190d8b33
7
+ crypted_password: UlMOC7qYsxw= #Admin
8
+ role: administrator
@@ -0,0 +1,10 @@
1
+ require 'test_helper'
2
+
3
+ class AccountTest < ActiveSupport::TestCase
4
+ # Replace this with your real tests.
5
+ test "should be valid" do
6
+ Account.all.each do |a|
7
+ assert a.valid?
8
+ end
9
+ end
10
+ end
@@ -0,0 +1,27 @@
1
+ # This generator bootstraps a Rails project for use with loops
2
+ class LoopsGenerator < Rails::Generator::Base
3
+ def manifest
4
+ record do |m|
5
+ # Generate app/loops directory and an example loop files
6
+ m.directory 'app'
7
+ m.directory 'app/loops'
8
+ m.file 'app/loops/APP_README', 'app/loops/README'
9
+ m.file 'app/loops/simple_loop.rb', 'app/loops/simple_loop.rb'
10
+
11
+ # Generate script/loops file
12
+ m.directory 'script'
13
+ m.file 'script/loops', 'script/loops', :chmod => 0755
14
+
15
+ # Generate config/loops.yml file
16
+ m.directory 'config'
17
+ m.file 'config/loops.yml', 'config/loops.yml'
18
+ end
19
+ end
20
+
21
+ protected
22
+
23
+ def banner
24
+ "Usage: #{$0} loops"
25
+ end
26
+
27
+ end
@@ -0,0 +1 @@
1
+ This directory should be used to hold all application loops.
@@ -0,0 +1,9 @@
1
+ # Simple loop with its own custom run method
2
+ #
3
+ # Does nothing aside from printing loop's name, pid and current time every second
4
+ #
5
+ class SimpleLoop < Lipsiadmin::Loops::Base
6
+ def run
7
+ debug(Time.now)
8
+ end
9
+ end
@@ -0,0 +1,17 @@
1
+ # This file is a configuration file for loops rails plugin
2
+ #
3
+
4
+ # This section is used to control loops manager
5
+ global:
6
+ logger: stdout
7
+ poll_period: 5
8
+ workers_engine: fork
9
+
10
+ # Each record in this section represents one loop which could be ran using loops plugin.
11
+ # Each loop should have a file in app/loops directory with the same name as its config record.
12
+ loops:
13
+
14
+ # Simple time printing loop
15
+ simple_loop:
16
+ workers_number: 1
17
+
@@ -0,0 +1,125 @@
1
+ #!/usr/bin/env ruby
2
+
3
+ require 'optparse'
4
+ require 'rubygems'
5
+
6
+ # We need this to be able to start the process w/o being
7
+ # in the RAILS_ROOT directory
8
+ ROOT_DIR = File.expand_path('..', File.dirname(__FILE__))
9
+ Dir.chdir(ROOT_DIR)
10
+
11
+ # Default options
12
+ options = {
13
+ :daemonize => false,
14
+ :loops => [],
15
+ :all_loops => false,
16
+ :list_loops => false,
17
+ :pid_file => nil,
18
+ :stop => false,
19
+ :framework => 'rails',
20
+ :environment => nil
21
+ }
22
+
23
+ # Parse command line options
24
+ opts = OptionParser.new do |opt|
25
+ opt.banner = "Usage: loops [options]"
26
+ opt.separator ""
27
+ opt.separator "Specific options:"
28
+
29
+ opt.on('-d', '--daemonize', 'Daemonize when all loops started') { |v| options[:daemonize] = v }
30
+ opt.on('-s', '--stop', 'Stop daemonized loops if running.') { |v| options[:stop] = v }
31
+ opt.on('-p', '--pid=file', 'Override loops.yml pid_file option') { |v| options[:pid_file] = v }
32
+ opt.on('-l', '--loop=loop_name', 'Start specified loop(s) only') { |v| options[:loops] << v }
33
+ opt.on('-a', '--all', 'Start all loops') { |v| options[:all_loops] = v }
34
+ opt.on('-L', '--list', 'Shows all available loops with their options') { |v| options[:list_loops] = v }
35
+ opt.on('-f', '--framework=name', 'Starts within a Rails (rails) or Merb (merb) project. Default is rails.') { |v| options[:framework] = v }
36
+ opt.on('-e', '--environment=env', 'Set RAILS_ENV value') { |v| options[:environment] = v }
37
+
38
+ opt.on_tail("-h", "--help", "Show this message") do
39
+ puts(opt)
40
+ exit(0)
41
+ end
42
+
43
+ opt.parse!(ARGV)
44
+ end
45
+
46
+ ENV['RAILS_ENV'] = options[:environment] if options[:environment]
47
+
48
+ # Bootstrap Rails
49
+ require File.join(ROOT_DIR, 'config/boot')
50
+ require File.join(ROOT_DIR, 'config/environment')
51
+
52
+ # Loops default logger
53
+ LOOPS_DEFAULT_LOGGER = Rails.logger
54
+
55
+ #$LOAD_PATH.unshift("vendor/plugins/loops/lib")
56
+
57
+ LOOPS_ROOT = ROOT_DIR
58
+ LOOPS_CONFIG_FILE = File.join(LOOPS_ROOT, 'config/loops.yml')
59
+
60
+ require 'loops'
61
+ require 'loops/daemonize'
62
+
63
+ # Load config file
64
+ puts "Loading config..."
65
+ Lipsiadmin::Loops.load_config(LOOPS_CONFIG_FILE)
66
+ options[:pid_file] ||= (Lipsiadmin::Loops.global_config['pid_file'] || "#{Rails.root}/tmp/loops.pid")
67
+ options[:pid_file] = File.join(LOOPS_ROOT, options[:pid_file]) unless options[:pid_file] =~ /^\//
68
+
69
+ # Stop/kill loops if requested
70
+ if options[:stop]
71
+ STDOUT.sync = true
72
+ raise "No pid file or a stale pid file!" unless Lipsiadmin::Loops::Daemonize.check_pid(options[:pid_file])
73
+ pid = Lipsiadmin::Loops::Daemonize.read_pid(options[:pid_file])
74
+ print "Killing the process: #{pid}: "
75
+
76
+ loop do
77
+ Process.kill('SIGTERM', pid)
78
+ sleep(1)
79
+ break unless Lipsiadmin::Loops::Daemonize.check_pid(options[:pid_file])
80
+ print(".")
81
+ end
82
+
83
+ puts " Done!"
84
+ exit(0)
85
+ end
86
+
87
+ # List loops if requested
88
+ if options[:list_loops]
89
+ puts "Available loops:"
90
+ Lipsiadmin::Loops.loops_config.each do |name, config|
91
+ puts "Loop: #{name}" + (config['disabled'] ? ' (disabled)' : '')
92
+ config.each do |k,v|
93
+ puts " - #{k}: #{v}"
94
+ end
95
+ end
96
+ puts
97
+ exit(0)
98
+ end
99
+
100
+ # Ignore --loop options if --all parameter passed
101
+ options[:loops] = :all if options[:all_loops]
102
+
103
+ # Check what loops we gonna run
104
+ if options[:loops] == []
105
+ puts opts
106
+ exit(0)
107
+ end
108
+
109
+ # Pid file check
110
+ raise "Can't start, another process exists!" if Lipsiadmin::Loops::Daemonize.check_pid(options[:pid_file])
111
+
112
+ # Daemonization
113
+ app_name = "loops monitor: #{ options[:loops].join(' ') rescue 'all' }\0"
114
+ Lipsiadmin::Loops::Daemonize.daemonize(app_name) if options[:daemonize]
115
+
116
+ # Pid file creation
117
+ Lipsiadmin::Loops::Daemonize.create_pid(options[:pid_file])
118
+
119
+ # Workers processing
120
+ puts "Starting workers"
121
+ Lipsiadmin::Loops.start_loops!(options[:loops])
122
+
123
+ # Workers exited, cleaning up
124
+ puts "Cleaning pid file..."
125
+ File.delete(options[:pid_file]) rescue nil