filecluster 0.1.6 → 0.1.7

Sign up to get free protection for your applications and to get access to all the features.
@@ -8,12 +8,12 @@ require 'filecluster'
8
8
  require 'utils'
9
9
  require 'daemon'
10
10
 
11
- $storages = []
12
- $tasks = {} # tasks by storage name
13
- $curr_task = {} # task by storage name
14
- $tasks_threads = {} # threads by storage name
15
- $check_threads = {} # threads by storage name
16
- $exit_signal = false
11
+ $storages = [] # storages on current host
12
+ $tasks = {} # tasks by storage name
13
+ $curr_tasks = [] # current tasks
14
+ $tasks_threads = {} # threads by storage name
15
+ $check_threads = {} # threads by storage name
16
+ $exit_signal = false
17
17
  $global_daemon_thread = nil
18
18
 
19
19
  default_db_config = File.expand_path(File.dirname(__FILE__))+'/db.yml'
@@ -17,12 +17,12 @@ end
17
17
 
18
18
  def run_global_daemon(timeout)
19
19
  $log.debug('Run global daemon check')
20
- r = FC::DB.connect.query("SELECT #{FC::DB.prefix}vars.*, UNIX_TIMESTAMP() as curr_time FROM #{FC::DB.prefix}vars WHERE name='global_daemon_host'").first
20
+ r = FC::DB.query("SELECT #{FC::DB.prefix}vars.*, UNIX_TIMESTAMP() as curr_time FROM #{FC::DB.prefix}vars WHERE name='global_daemon_host'").first
21
21
  if !r || r['curr_time'].to_i - r['time'].to_i > timeout
22
22
  $log.debug('Set global daemon host to current')
23
- FC::DB.connect.query("REPLACE #{FC::DB.prefix}vars SET val='#{FC::Storage.curr_host}', name='global_daemon_host'")
23
+ FC::DB.query("REPLACE #{FC::DB.prefix}vars SET val='#{FC::Storage.curr_host}', name='global_daemon_host'")
24
24
  sleep 1
25
- r = FC::DB.connect.query("SELECT #{FC::DB.prefix}vars.*, UNIX_TIMESTAMP() as curr_time FROM #{FC::DB.prefix}vars WHERE name='global_daemon_host'").first
25
+ r = FC::DB.query("SELECT #{FC::DB.prefix}vars.*, UNIX_TIMESTAMP() as curr_time FROM #{FC::DB.prefix}vars WHERE name='global_daemon_host'").first
26
26
  end
27
27
  if r['val'] == FC::Storage.curr_host
28
28
  if !$global_daemon_thread || !$global_daemon_thread.alive?
@@ -66,9 +66,10 @@ def update_tasks
66
66
  cond = "storage_name in (#{storages_names}) AND status='#{type.to_s}'"
67
67
  ids = $tasks.map{|storage_name, storage_tasks| storage_tasks.select{|task| task[:action] == type}}.
68
68
  flatten.map{|task| task[:item_storage].id}
69
- $curr_task.map{|storage_name, task| ids << task[:item_storage].id if task && task[:action] == type}
69
+ ids += $curr_tasks.select{|task| task[:action] == type}
70
70
 
71
71
  cond << "AND id not in (#{ids.join(',')})" if (ids.length > 0)
72
+ cond << "LIMIT 1000"
72
73
  FC::ItemStorage.where(cond).each do |item_storage|
73
74
  $tasks[item_storage.storage_name] = [] unless $tasks[item_storage.storage_name]
74
75
  $tasks[item_storage.storage_name] << {:action => type, :item_storage => item_storage}
@@ -83,10 +84,17 @@ end
83
84
  def run_tasks
84
85
  $log.debug('Run tasks')
85
86
  $storages.each do |storage|
86
- thread = $tasks_threads[storage.name]
87
- if (!thread || !thread.alive?) && $tasks[storage.name] && $tasks[storage.name].size > 0
87
+ $tasks_threads[storage.name] = [] unless $tasks_threads[storage.name]
88
+ $tasks_threads[storage.name].delete_if {|thread| !thread.alive?}
89
+ tasks_count = $tasks[storage.name] ? $tasks[storage.name].size : 0
90
+ threads_count = $tasks_threads[storage.name].count
91
+ # 10 tasks per thread, maximum 3 tasks
92
+ run_threads_count = (tasks_count/10.0).ceil - threads_count
93
+ run_threads_count = 3 if run_threads_count > 3
94
+ $log.debug("tasks_count: #{tasks_count}, threads_count: #{threads_count}, run_threads_count: #{run_threads_count}")
95
+ run_threads_count.times do
88
96
  $log.debug("spawn TaskThread for #{storage.name}")
89
- $tasks_threads[storage.name] = TaskThread.new(storage.name)
97
+ $tasks_threads[storage.name] << TaskThread.new(storage.name)
90
98
  end
91
99
  end
92
100
  end
@@ -6,9 +6,9 @@ class GlobalDaemonThread < BaseThread
6
6
  sleep timeout.to_f/2
7
7
  exit if $exit_signal
8
8
 
9
- r = FC::DB.connect.query("SELECT #{FC::DB.prefix}vars.*, UNIX_TIMESTAMP() as curr_time FROM #{FC::DB.prefix}vars WHERE name='global_daemon_host'").first
9
+ r = FC::DB.query("SELECT #{FC::DB.prefix}vars.*, UNIX_TIMESTAMP() as curr_time FROM #{FC::DB.prefix}vars WHERE name='global_daemon_host'").first
10
10
  if r['val'] == FC::Storage.curr_host
11
- FC::DB.connect.query("UPDATE #{FC::DB.prefix}vars SET val='#{FC::Storage.curr_host}' WHERE name='global_daemon_host'")
11
+ FC::DB.query("UPDATE #{FC::DB.prefix}vars SET val='#{FC::Storage.curr_host}' WHERE name='global_daemon_host'")
12
12
  else
13
13
  $log.info("Exit from GlobalDaemonThread: global daemon already running on #{r['val']}")
14
14
  FC::DB.close
@@ -40,7 +40,7 @@ class GlobalDaemonThread < BaseThread
40
40
  sql = "SELECT i.id as item_id, i.size, i.copies as item_copies, GROUP_CONCAT(ist.storage_name ORDER BY ist.id) as storages, p.id as policy_id, p.copies as policy_copies "+
41
41
  "FROM #{FC::Item.table_name} as i, #{FC::Policy.table_name} as p, #{FC::ItemStorage.table_name} as ist WHERE "+
42
42
  "i.policy_id = p.id AND ist.item_id = i.id AND i.copies > 0 AND i.copies < p.copies AND i.status = 'ready' AND ist.status <> 'delete' GROUP BY i.id LIMIT 1000"
43
- r = FC::DB.connect.query(sql)
43
+ r = FC::DB.query(sql)
44
44
  r.each do |row|
45
45
  $log.info("GlobalDaemonThread: new item_storage for item #{row['item_id']}")
46
46
  item_storages = row['storages'].split(',')
@@ -61,11 +61,11 @@ class GlobalDaemonThread < BaseThread
61
61
  def delete_deleted_items
62
62
  $log.debug("GlobalDaemonThread: delete_deleted_items")
63
63
 
64
- r = FC::DB.connect.query("SELECT i.id FROM #{FC::Item.table_name} as i LEFT JOIN #{FC::ItemStorage.table_name} as ist ON i.id=ist.item_id WHERE i.status = 'delete' AND ist.id IS NULL")
64
+ r = FC::DB.query("SELECT i.id FROM #{FC::Item.table_name} as i LEFT JOIN #{FC::ItemStorage.table_name} as ist ON i.id=ist.item_id WHERE i.status = 'delete' AND ist.id IS NULL")
65
65
  ids = r.map{|row| row['id']}
66
66
  if ids.count > 0
67
67
  ids = ids.join(',')
68
- FC::DB.connect.query("DELETE FROM #{FC::Item.table_name} WHERE id in (#{ids})")
68
+ FC::DB.query("DELETE FROM #{FC::Item.table_name} WHERE id in (#{ids})")
69
69
  $log.info("GlobalDaemonThread: delete items #{ids}")
70
70
  end
71
71
  end
@@ -1,38 +1,48 @@
1
1
  class TaskThread < BaseThread
2
2
  def go(storage_name)
3
+ return unless $tasks[storage_name]
3
4
  while task = $tasks[storage_name].shift do
4
- $curr_task[storage_name] = task
5
+ $curr_tasks << task
5
6
  $log.debug("TaskThread(#{storage_name}): run task type=#{task[:action]}, item_storage=#{task[:item_storage].id}")
6
7
  if task[:action] == :delete
7
- make_delete(task[:item_storage])
8
+ make_delete(task)
8
9
  elsif task[:action] == :copy
9
- make_copy(task[:item_storage])
10
+ make_copy(task)
10
11
  else
11
12
  error "Unknown task action: #{task[:action]}"
12
13
  end
13
- $curr_task[storage_name] = nil
14
+ $curr_tasks.delete(task)
14
15
  $log.debug("TaskThread(#{storage_name}): Finish task type=#{task[:action]}, item_storage=#{task[:item_storage].id}")
15
16
  end
16
17
  end
17
18
 
18
- def make_delete(item_storage)
19
+ def make_delete(task)
20
+ item_storage = task[:item_storage]
21
+ # TODO: не лазить в базу за item
19
22
  storage = $storages.detect{|s| s.name == item_storage.storage_name}
20
23
  item = FC::Item.find(item_storage.item_id)
21
24
  storage.delete_file(item.name)
22
25
  item_storage.delete
23
26
  rescue Exception => e
24
27
  error "Delete item_storage error: #{e.message}; #{e.backtrace.join(', ')}", :item_id => item_storage.item_id, :item_storage_id => item_storage.id
28
+ $curr_tasks.delete(task)
25
29
  end
26
30
 
27
- def make_copy(item_storage)
31
+ def make_copy(task)
32
+ item_storage = task[:item_storage]
28
33
  # TODO: не лазить в базу за item, item_storages - перенести на стадию подготовки task-а
29
34
  storage = $storages.detect{|s| s.name == item_storage.storage_name}
30
35
  item = FC::Item.find(item_storage.item_id)
31
36
  src_item_storage = FC::ItemStorage.where("item_id = ? AND status = 'ready'", item.id).sample
37
+ unless src_item_storage
38
+ $log.info("Item ##{item.id} #{item.name} has no ready item_storage")
39
+ return nil
40
+ end
32
41
  src_storage = $all_storages.detect{|s| s.name == src_item_storage.storage_name}
33
42
  $log.debug("Copy from #{src_storage.name} to #{storage.name} #{storage.path}#{item.name}")
34
43
  item.copy_item_storage(src_storage, storage, item_storage)
35
44
  rescue Exception => e
36
45
  error "Copy item_storage error: #{e.message}; #{e.backtrace.join(', ')}", :item_id => item_storage.item_id, :item_storage_id => item_storage.id
46
+ $curr_tasks.delete(task)
37
47
  end
38
48
  end
@@ -34,7 +34,7 @@ module FC
34
34
 
35
35
  # get element by id
36
36
  def self.find(id)
37
- r = FC::DB.connect.query("SELECT * FROM #{self.table_name} WHERE id=#{id.to_i}")
37
+ r = FC::DB.query("SELECT * FROM #{self.table_name} WHERE id=#{id.to_i}")
38
38
  raise "Record not found (#{self.table_name}.id=#{id})" if r.count == 0
39
39
  self.create_from_fiels(r.first)
40
40
  end
@@ -43,7 +43,7 @@ module FC
43
43
  def self.where(cond = "1", *params)
44
44
  i = -1
45
45
  sql = "SELECT * FROM #{self.table_name} WHERE #{cond.gsub('?'){i+=1; "'#{Mysql2::Client.escape(params[i].to_s)}'"}}"
46
- r = FC::DB.connect.query(sql)
46
+ r = FC::DB.query(sql)
47
47
  r.map{|data| self.create_from_fiels(data)}
48
48
  end
49
49
 
@@ -63,7 +63,7 @@ module FC
63
63
  if fields.length > 0
64
64
  sql << fields.join(',')
65
65
  sql << " WHERE id=#{@id.to_i}" if @id
66
- FC::DB.connect.query(sql)
66
+ FC::DB.query(sql)
67
67
  @id = FC::DB.connect.last_id unless @id
68
68
  self.class.table_fields.each do |key|
69
69
  @database_fields[key] = self.send(key)
@@ -81,7 +81,7 @@ module FC
81
81
 
82
82
  # delete object from DB
83
83
  def delete
84
- FC::DB.connect.query("DELETE FROM #{self.class.table_name} WHERE id=#{@id.to_i}") if @id
84
+ FC::DB.query("DELETE FROM #{self.class.table_name} WHERE id=#{@id.to_i}") if @id
85
85
  end
86
86
 
87
87
  end
@@ -35,6 +35,19 @@ module FC
35
35
  end
36
36
  end
37
37
 
38
+ # connect.query with deadlock solution
39
+ def self.query(sql)
40
+ FC::DB.connect.query(sql)
41
+ rescue Mysql2::Error => e
42
+ if e.message.match('Deadlock found when trying to get lock')
43
+ puts "Deadlock"
44
+ sleep 0.1
45
+ self.query(sql)
46
+ else
47
+ raise e
48
+ end
49
+ end
50
+
38
51
  def DB.init_db
39
52
  FC::DB.connect.query(%{
40
53
  CREATE TABLE #{@prefix}items (
@@ -34,7 +34,7 @@ module FC
34
34
  if item
35
35
  if options[:replace] || storage
36
36
  # mark delete item_storages on replace
37
- FC::DB.connect.query("UPDATE #{FC::ItemStorage.table_name} SET status='delete' WHERE item_id = #{item.id}") if options[:replace] && !storage
37
+ FC::DB.query("UPDATE #{FC::ItemStorage.table_name} SET status='delete' WHERE item_id = #{item.id}") if options[:replace] && !storage
38
38
  # replace all fields
39
39
  item_params.each{|key, val| item.send("#{key}=", val)}
40
40
  else
@@ -101,7 +101,7 @@ module FC
101
101
 
102
102
  # mark items_storages for delete
103
103
  def mark_deleted
104
- FC::DB.connect.query("UPDATE #{FC::ItemStorage.table_name} SET status='delete' WHERE item_id = #{id}")
104
+ FC::DB.query("UPDATE #{FC::ItemStorage.table_name} SET status='delete' WHERE item_id = #{id}")
105
105
  self.status = 'delete'
106
106
  save
107
107
  end
@@ -115,7 +115,7 @@ module FC
115
115
  end
116
116
 
117
117
  def get_available_storages
118
- r = FC::DB.connect.query("SELECT st.* FROM #{FC::Storage.table_name} as st, #{FC::ItemStorage.table_name} as ist WHERE
118
+ r = FC::DB.query("SELECT st.* FROM #{FC::Storage.table_name} as st, #{FC::ItemStorage.table_name} as ist WHERE
119
119
  ist.item_id = #{id} AND ist.status='ready' AND ist.storage_name = st.name")
120
120
  r.map{|data| FC::Storage.create_from_fiels(data)}.select {|storage| storage.up? }
121
121
  end
@@ -1,3 +1,3 @@
1
1
  module FC
2
- VERSION = "0.1.6"
2
+ VERSION = "0.1.7"
3
3
  end
@@ -11,7 +11,7 @@ end
11
11
 
12
12
  def policies_show
13
13
  if policy = find_policy
14
- count = FC::DB.connect.query("SELECT count(*) as cnt FROM #{FC::Item.table_name} WHERE policy_id = #{policy.id}").first['cnt']
14
+ count = FC::DB.query("SELECT count(*) as cnt FROM #{FC::Item.table_name} WHERE policy_id = #{policy.id}").first['cnt']
15
15
  puts %Q{Policy
16
16
  ID: #{policy.id}
17
17
  Name: #{policy.name}
@@ -3,7 +3,7 @@ def show_current_host
3
3
  end
4
4
 
5
5
  def show_global_daemon
6
- r = FC::DB.connect.query("SELECT #{FC::DB.prefix}vars.*, UNIX_TIMESTAMP() as curr_time FROM #{FC::DB.prefix}vars WHERE name='global_daemon_host'").first
6
+ r = FC::DB.query("SELECT #{FC::DB.prefix}vars.*, UNIX_TIMESTAMP() as curr_time FROM #{FC::DB.prefix}vars WHERE name='global_daemon_host'").first
7
7
  if r
8
8
  puts "Global daemon run on #{r['val']}\nLast run #{r['curr_time']-r['time']} seconds ago."
9
9
  else
@@ -31,7 +31,7 @@ def show_host_info
31
31
  else
32
32
  puts "Info for host #{host}"
33
33
  storages.each do |storage|
34
- counts = FC::DB.connect.query("SELECT status, count(*) as cnt FROM #{FC::ItemStorage.table_name} WHERE storage_name='#{Mysql2::Client.escape(storage.name)}' GROUP BY status")
34
+ counts = FC::DB.query("SELECT status, count(*) as cnt FROM #{FC::ItemStorage.table_name} WHERE storage_name='#{Mysql2::Client.escape(storage.name)}' GROUP BY status")
35
35
  str = "#{storage.name} #{size_to_human(storage.size)}/#{size_to_human(storage.size_limit)} "
36
36
  str += "#{storage.up? ? colorize_string('UP', :green) : colorize_string('DOWN', :red)}"
37
37
  str += " #{storage.check_time_delay} seconds ago" if storage.check_time
@@ -46,17 +46,17 @@ end
46
46
 
47
47
  def show_items_info
48
48
  puts "Items by status:"
49
- counts = FC::DB.connect.query("SELECT status, count(*) as cnt FROM #{FC::Item.table_name} WHERE 1 GROUP BY status")
49
+ counts = FC::DB.query("SELECT status, count(*) as cnt FROM #{FC::Item.table_name} WHERE 1 GROUP BY status")
50
50
  counts.each do |r|
51
51
  puts " #{r['status']}: #{r['cnt']}"
52
52
  end
53
53
  puts "Items storages by status:"
54
- counts = FC::DB.connect.query("SELECT status, count(*) as cnt FROM #{FC::ItemStorage.table_name} WHERE 1 GROUP BY status")
54
+ counts = FC::DB.query("SELECT status, count(*) as cnt FROM #{FC::ItemStorage.table_name} WHERE 1 GROUP BY status")
55
55
  counts.each do |r|
56
56
  puts " #{r['status']}: #{r['cnt']}"
57
57
  end
58
- count = FC::DB.connect.query("SELECT count(*) as cnt FROM #{FC::Item.table_name} as i, #{FC::Policy.table_name} as p WHERE i.policy_id = p.id AND i.copies > 0 AND i.copies < p.copies AND i.status = 'ready'").first['cnt']
58
+ count = FC::DB.query("SELECT count(*) as cnt FROM #{FC::Item.table_name} as i, #{FC::Policy.table_name} as p WHERE i.policy_id = p.id AND i.copies > 0 AND i.copies < p.copies AND i.status = 'ready'").first['cnt']
59
59
  puts "Items to copy: #{count}"
60
- count = FC::DB.connect.query("SELECT count(*) as cnt FROM #{FC::Item.table_name} as i, #{FC::Policy.table_name} as p WHERE i.policy_id = p.id AND i.copies > p.copies AND i.status = 'ready'").first['cnt']
60
+ count = FC::DB.query("SELECT count(*) as cnt FROM #{FC::Item.table_name} as i, #{FC::Policy.table_name} as p WHERE i.policy_id = p.id AND i.copies > p.copies AND i.status = 'ready'").first['cnt']
61
61
  puts "Items to delete: #{count}"
62
62
  end
@@ -14,7 +14,7 @@ end
14
14
 
15
15
  def storages_show
16
16
  if storage = find_storage
17
- count = FC::DB.connect.query("SELECT count(*) as cnt FROM #{FC::ItemStorage.table_name} WHERE storage_name='#{Mysql2::Client.escape(storage.name)}'").first['cnt']
17
+ count = FC::DB.query("SELECT count(*) as cnt FROM #{FC::ItemStorage.table_name} WHERE storage_name='#{Mysql2::Client.escape(storage.name)}'").first['cnt']
18
18
  puts %Q{Storage
19
19
  Name: #{storage.name}
20
20
  Host: #{storage.host}
@@ -3,7 +3,7 @@ require 'helper'
3
3
  class BaseTest < Test::Unit::TestCase
4
4
  class << self
5
5
  def shutdown
6
- FC::DB.connect.query("DELETE FROM items")
6
+ FC::DB.query("DELETE FROM items")
7
7
  end
8
8
  end
9
9
 
@@ -59,10 +59,10 @@ class DaemonTest < Test::Unit::TestCase
59
59
 
60
60
  def shutdown
61
61
  Process.kill("KILL", @@pid)
62
- FC::DB.connect.query("DELETE FROM items_storages")
63
- FC::DB.connect.query("DELETE FROM items")
64
- FC::DB.connect.query("DELETE FROM policies")
65
- FC::DB.connect.query("DELETE FROM storages")
62
+ FC::DB.query("DELETE FROM items_storages")
63
+ FC::DB.query("DELETE FROM items")
64
+ FC::DB.query("DELETE FROM policies")
65
+ FC::DB.query("DELETE FROM storages")
66
66
  `rm -rf /tmp/host*-sd*`
67
67
  `rm -rf #{@@test_file_path}`
68
68
  `rm -rf #{@@test_dir_path}`
@@ -34,10 +34,10 @@ class DbTest < Test::Unit::TestCase
34
34
  @@item_storages_ids = item_storages.map{|is| is.save; is.id }
35
35
  end
36
36
  def shutdown
37
- FC::DB.connect.query("DELETE FROM items_storages")
38
- FC::DB.connect.query("DELETE FROM items")
39
- FC::DB.connect.query("DELETE FROM policies")
40
- FC::DB.connect.query("DELETE FROM storages")
37
+ FC::DB.query("DELETE FROM items_storages")
38
+ FC::DB.query("DELETE FROM items")
39
+ FC::DB.query("DELETE FROM policies")
40
+ FC::DB.query("DELETE FROM storages")
41
41
  end
42
42
  end
43
43
  def setup
@@ -33,10 +33,10 @@ class FunctionalTest < Test::Unit::TestCase
33
33
  @@policies.each { |policy| policy.save}
34
34
  end
35
35
  def shutdown
36
- FC::DB.connect.query("DELETE FROM items_storages")
37
- FC::DB.connect.query("DELETE FROM items")
38
- FC::DB.connect.query("DELETE FROM policies")
39
- FC::DB.connect.query("DELETE FROM storages")
36
+ FC::DB.query("DELETE FROM items_storages")
37
+ FC::DB.query("DELETE FROM items")
38
+ FC::DB.query("DELETE FROM policies")
39
+ FC::DB.query("DELETE FROM storages")
40
40
  `rm -rf /tmp/host*-sd*`
41
41
  `rm -rf #{@@test_file_path}`
42
42
  `rm -rf #{@@test_dir_path}`
@@ -10,8 +10,8 @@ TEST_USER = 'root'
10
10
  TEST_PASSWORD = ''
11
11
 
12
12
  FC::DB.connect_by_config(:username => TEST_USER, :password => TEST_PASSWORD)
13
- FC::DB.connect.query("DROP DATABASE IF EXISTS #{TEST_DATABASE}")
14
- FC::DB.connect.query("CREATE DATABASE #{TEST_DATABASE}")
15
- FC::DB.connect.query("USE #{TEST_DATABASE}")
13
+ FC::DB.query("DROP DATABASE IF EXISTS #{TEST_DATABASE}")
14
+ FC::DB.query("CREATE DATABASE #{TEST_DATABASE}")
15
+ FC::DB.query("USE #{TEST_DATABASE}")
16
16
  FC::DB.init_db
17
17
  FC::DB.options[:database] = TEST_DATABASE
@@ -17,9 +17,9 @@ class ItemTest < Test::Unit::TestCase
17
17
  end
18
18
  end
19
19
  def shutdown
20
- FC::DB.connect.query("DELETE FROM items_storages")
21
- FC::DB.connect.query("DELETE FROM items")
22
- FC::DB.connect.query("DELETE FROM storages")
20
+ FC::DB.query("DELETE FROM items_storages")
21
+ FC::DB.query("DELETE FROM items")
22
+ FC::DB.query("DELETE FROM storages")
23
23
  end
24
24
  end
25
25
 
@@ -14,8 +14,8 @@ class PolicyTest < Test::Unit::TestCase
14
14
  @@policy.save
15
15
  end
16
16
  def shutdown
17
- FC::DB.connect.query("DELETE FROM policies")
18
- FC::DB.connect.query("DELETE FROM storages")
17
+ FC::DB.query("DELETE FROM policies")
18
+ FC::DB.query("DELETE FROM storages")
19
19
  end
20
20
  end
21
21
 
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: filecluster
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.1.6
4
+ version: 0.1.7
5
5
  prerelease:
6
6
  platform: ruby
7
7
  authors:
@@ -9,7 +9,7 @@ authors:
9
9
  autorequire:
10
10
  bindir: bin
11
11
  cert_chain: []
12
- date: 2013-05-14 00:00:00.000000000 Z
12
+ date: 2013-05-22 00:00:00.000000000 Z
13
13
  dependencies:
14
14
  - !ruby/object:Gem::Dependency
15
15
  name: rb-readline
@@ -186,7 +186,7 @@ required_ruby_version: !ruby/object:Gem::Requirement
186
186
  version: '0'
187
187
  segments:
188
188
  - 0
189
- hash: 789694035
189
+ hash: -778580397
190
190
  required_rubygems_version: !ruby/object:Gem::Requirement
191
191
  none: false
192
192
  requirements:
@@ -195,7 +195,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
195
195
  version: '0'
196
196
  segments:
197
197
  - 0
198
- hash: 789694035
198
+ hash: -778580397
199
199
  requirements: []
200
200
  rubyforge_project:
201
201
  rubygems_version: 1.8.24