rivendell-import 0.0.1 → 0.0.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- data/Gemfile +1 -1
- data/db/migrate/20121004194900_add_delete_file_to_task.rb +9 -0
- data/db/migrate/2012100620361349548586_add_xport_options_to_task.rb +9 -0
- data/examples/config.rb +9 -0
- data/lib/rivendell/import.rb +2 -0
- data/lib/rivendell/import/application.rb +53 -0
- data/lib/rivendell/import/base.rb +6 -0
- data/lib/rivendell/import/cart.rb +9 -1
- data/lib/rivendell/import/cli.rb +41 -5
- data/lib/rivendell/import/config.rb +22 -4
- data/lib/rivendell/import/config_loader.rb +43 -0
- data/lib/rivendell/import/file.rb +5 -0
- data/lib/rivendell/import/notifier/base.rb +7 -2
- data/lib/rivendell/import/static/rivendell-import.png +0 -0
- data/lib/rivendell/import/static/screen.css +137 -0
- data/lib/rivendell/import/task.rb +25 -2
- data/lib/rivendell/import/tasking/file.rb +6 -0
- data/lib/rivendell/import/tasking/status.rb +22 -1
- data/lib/rivendell/import/tasks.rb +0 -4
- data/lib/rivendell/import/version.rb +1 -1
- data/lib/rivendell/import/views/index.erb +56 -0
- data/rivendell-import.gemspec +5 -1
- data/spec/rivendell/import/base_spec.rb +11 -0
- data/spec/rivendell/import/cart_spec.rb +33 -2
- data/spec/rivendell/import/cli_spec.rb +47 -3
- data/spec/rivendell/import/config_spec.rb +24 -2
- data/spec/rivendell/import/notifier/base_spec.rb +15 -1
- data/spec/rivendell/import/task_spec.rb +70 -4
- data/spec/rivendell/import/tasks_spec.rb +0 -13
- data/spec/rivendell/import/worker_spec.rb +1 -0
- data/spec/support/database_cleaner.rb +2 -1
- metadata +61 -6
data/Gemfile
CHANGED
@@ -2,7 +2,7 @@ source "http://rubygems.org"
|
|
2
2
|
|
3
3
|
gemspec
|
4
4
|
|
5
|
-
|
5
|
+
gem "rivendell-api" # , :git => "git://projects.tryphon.priv/rivendell-api", :ref => "b98a46fbb0c31d965853206a56500cea4a340044" # , :path => "~/Projects/RivendellApi"
|
6
6
|
|
7
7
|
if RUBY_PLATFORM =~ /linux/
|
8
8
|
gem 'libnotify'
|
data/examples/config.rb
CHANGED
@@ -15,17 +15,26 @@ end
|
|
15
15
|
Rivendell::Import::Notifier::Mail.from = "root@tryphon.eu"
|
16
16
|
|
17
17
|
Rivendell::Import.config do |config|
|
18
|
+
|
19
|
+
config.rivendell.host = "localhost"
|
20
|
+
config.rivendell.login_name = "user"
|
21
|
+
config.rivendell.password = ""
|
22
|
+
|
18
23
|
config.to_prepare do |file|
|
19
24
|
file.in("music") do
|
20
25
|
cart.group = "MUSIC"
|
21
26
|
end
|
22
27
|
|
23
28
|
file.in("pad") do
|
29
|
+
cart.clear_cuts!
|
24
30
|
cart.find_by_title file.basename
|
25
31
|
end
|
26
32
|
|
27
33
|
cart.group ||= "TEST"
|
28
34
|
|
35
|
+
# To delete file when task is completed
|
36
|
+
#task.delete_file!
|
37
|
+
|
29
38
|
notify 'alban@tryphon.eu', :by => :email
|
30
39
|
end
|
31
40
|
end
|
data/lib/rivendell/import.rb
CHANGED
@@ -34,6 +34,8 @@ ActiveRecord::Base.include_root_in_json = false
|
|
34
34
|
|
35
35
|
require "rivendell/api"
|
36
36
|
|
37
|
+
require "rivendell/import/config"
|
38
|
+
require "rivendell/import/config_loader"
|
37
39
|
require "rivendell/import/worker"
|
38
40
|
require "rivendell/import/task"
|
39
41
|
require "rivendell/import/tasks"
|
@@ -0,0 +1,53 @@
|
|
1
|
+
require 'sinatra'
|
2
|
+
|
3
|
+
require 'will_paginate'
|
4
|
+
require 'will_paginate/active_record'
|
5
|
+
|
6
|
+
module Rivendell::Import
|
7
|
+
class Application < Sinatra::Application
|
8
|
+
|
9
|
+
set :public_folder, ::File.expand_path('static', ::File.dirname(__FILE__))
|
10
|
+
# set :static_cache_control, [:public, :max_age => 3600]
|
11
|
+
|
12
|
+
get '/' do
|
13
|
+
redirect "/tasks", 302
|
14
|
+
end
|
15
|
+
|
16
|
+
get '/tasks' do
|
17
|
+
erb :index, :locals => { :tasks => tasks.paginate(:page => params[:page], :per_page => 15) }
|
18
|
+
end
|
19
|
+
|
20
|
+
get '/tasks.json' do
|
21
|
+
tasks.to_json
|
22
|
+
end
|
23
|
+
|
24
|
+
def tasks
|
25
|
+
Task.order("updated_at DESC")
|
26
|
+
end
|
27
|
+
|
28
|
+
helpers do
|
29
|
+
def distance_of_time_in_words_from_now(time)
|
30
|
+
distance = Time.now - time
|
31
|
+
|
32
|
+
if distance < 43200 # less than 12 hours
|
33
|
+
time.strftime("%H:%M")
|
34
|
+
else
|
35
|
+
time.strftime("%d/%m")
|
36
|
+
end
|
37
|
+
end
|
38
|
+
|
39
|
+
def truncate_filename(path, length)
|
40
|
+
path = path.to_s
|
41
|
+
|
42
|
+
if path.size < length
|
43
|
+
path
|
44
|
+
else
|
45
|
+
path[0..length] + "..."
|
46
|
+
end
|
47
|
+
end
|
48
|
+
|
49
|
+
end
|
50
|
+
|
51
|
+
end
|
52
|
+
end
|
53
|
+
|
@@ -10,7 +10,13 @@ module Rivendell::Import
|
|
10
10
|
@workers = []
|
11
11
|
end
|
12
12
|
|
13
|
+
@@default_to_prepare = nil
|
14
|
+
cattr_accessor :default_to_prepare
|
15
|
+
|
13
16
|
attr_accessor :to_prepare
|
17
|
+
def to_prepare
|
18
|
+
@to_prepare or default_to_prepare
|
19
|
+
end
|
14
20
|
|
15
21
|
def listen(directory, options = {})
|
16
22
|
workers << Worker.new(self).start unless options[:dry_run]
|
@@ -5,7 +5,7 @@ module Rivendell::Import
|
|
5
5
|
include ActiveModel::Serializers::JSON
|
6
6
|
|
7
7
|
def attributes
|
8
|
-
%w{number group}.inject({}) do |map, attribute|
|
8
|
+
%w{number group clear_cuts?}.inject({}) do |map, attribute|
|
9
9
|
value = send attribute
|
10
10
|
map[attribute] = value if value
|
11
11
|
map
|
@@ -48,6 +48,7 @@ module Rivendell::Import
|
|
48
48
|
raise "File #{file.path} not found" unless file.exists?
|
49
49
|
|
50
50
|
cut.create
|
51
|
+
xport.clear_cuts number if clear_cuts?
|
51
52
|
xport.import number, cut.number, file.path
|
52
53
|
cut.update
|
53
54
|
end
|
@@ -58,6 +59,13 @@ module Rivendell::Import
|
|
58
59
|
end
|
59
60
|
end
|
60
61
|
|
62
|
+
attr_accessor :clear_cuts
|
63
|
+
alias_method :clear_cuts?, :clear_cuts
|
64
|
+
|
65
|
+
def clear_cuts!
|
66
|
+
self.clear_cuts = true
|
67
|
+
end
|
68
|
+
|
61
69
|
def carts_cache
|
62
70
|
@carts_cache ||= Rivendell::Import::CartsCache.new(xport)
|
63
71
|
end
|
data/lib/rivendell/import/cli.rb
CHANGED
@@ -1,5 +1,6 @@
|
|
1
1
|
require 'trollop'
|
2
2
|
require 'logger'
|
3
|
+
require 'syslog/logger'
|
3
4
|
|
4
5
|
module Rivendell::Import
|
5
6
|
class CLI
|
@@ -27,6 +28,10 @@ module Rivendell::Import
|
|
27
28
|
options[:debug]
|
28
29
|
end
|
29
30
|
|
31
|
+
def syslog?
|
32
|
+
options[:syslog]
|
33
|
+
end
|
34
|
+
|
30
35
|
def database
|
31
36
|
options[:database]
|
32
37
|
end
|
@@ -37,6 +42,7 @@ module Rivendell::Import
|
|
37
42
|
opt :listen, "Wait for files in given directory"
|
38
43
|
opt :dry_run, "Just create tasks without executing them"
|
39
44
|
opt :debug, "Enable debug messages (in stderr)"
|
45
|
+
opt :syslog, "Log messages to syslog"
|
40
46
|
opt :database, "The database file used to store tasks", :type => String
|
41
47
|
end
|
42
48
|
end
|
@@ -62,8 +68,39 @@ module Rivendell::Import
|
|
62
68
|
parsed_parser.leftovers
|
63
69
|
end
|
64
70
|
|
71
|
+
def setup_logger
|
72
|
+
new_logger =
|
73
|
+
if syslog?
|
74
|
+
Syslog::Logger.new('rivendell-import').tap do |syslog|
|
75
|
+
syslog.level = debug? ? Logger::DEBUG : Logger::INFO
|
76
|
+
end
|
77
|
+
elsif debug?
|
78
|
+
Logger.new($stderr)
|
79
|
+
end
|
80
|
+
|
81
|
+
Rivendell::Import.logger = new_logger if new_logger
|
82
|
+
end
|
83
|
+
|
84
|
+
def start_webserver
|
85
|
+
require 'rivendell/import/application'
|
86
|
+
|
87
|
+
Rivendell::Import.logger.debug "Start webserver"
|
88
|
+
|
89
|
+
Thread.new do
|
90
|
+
Rivendell::Import::Application.run!
|
91
|
+
|
92
|
+
# FIXME we don't see difference between normal quit and start error (EADDRINUSE, ...)
|
93
|
+
Rivendell::Import.logger.debug "Webserver is stopped"
|
94
|
+
exit 0
|
95
|
+
end
|
96
|
+
end
|
97
|
+
|
98
|
+
def config_loader
|
99
|
+
ConfigLoader.new(config_file, listen_mode?)
|
100
|
+
end
|
101
|
+
|
65
102
|
def run
|
66
|
-
|
103
|
+
setup_logger
|
67
104
|
|
68
105
|
if database
|
69
106
|
Rivendell::Import.establish_connection database
|
@@ -71,12 +108,11 @@ module Rivendell::Import
|
|
71
108
|
Rivendell::Import.establish_connection
|
72
109
|
end
|
73
110
|
|
74
|
-
|
75
|
-
load config_file
|
76
|
-
import.to_prepare = Rivendell::Import.config.to_prepare
|
77
|
-
end
|
111
|
+
config_loader.load
|
78
112
|
|
79
113
|
if listen_mode?
|
114
|
+
start_webserver
|
115
|
+
|
80
116
|
listen_options = {}
|
81
117
|
listen_options[:dry_run] = true if dry_run?
|
82
118
|
|
@@ -2,11 +2,29 @@ module Rivendell::Import
|
|
2
2
|
class Config
|
3
3
|
|
4
4
|
def to_prepare(&block)
|
5
|
-
|
6
|
-
|
7
|
-
|
8
|
-
|
5
|
+
Base.default_to_prepare = block
|
6
|
+
end
|
7
|
+
|
8
|
+
def rivendell
|
9
|
+
@rivendell ||= Rivendell.new
|
10
|
+
end
|
11
|
+
alias_method :xport_options, :rivendell
|
12
|
+
|
13
|
+
class Rivendell
|
14
|
+
|
15
|
+
def host=(host)
|
16
|
+
Task.default_xport_options[:host] = host
|
9
17
|
end
|
18
|
+
|
19
|
+
def login_name=(login_name)
|
20
|
+
Task.default_xport_options[:login_name] = login_name
|
21
|
+
end
|
22
|
+
alias_method :user=, :login_name=
|
23
|
+
|
24
|
+
def password=(password)
|
25
|
+
Task.default_xport_options[:password] = password
|
26
|
+
end
|
27
|
+
|
10
28
|
end
|
11
29
|
|
12
30
|
end
|
@@ -0,0 +1,43 @@
|
|
1
|
+
module Rivendell::Import
|
2
|
+
class ConfigLoader
|
3
|
+
|
4
|
+
attr_accessor :file, :auto_reload
|
5
|
+
alias_method :auto_reload?, :auto_reload
|
6
|
+
|
7
|
+
def initialize(file, auto_reload = false)
|
8
|
+
self.file = file
|
9
|
+
self.auto_reload = auto_reload
|
10
|
+
|
11
|
+
listen_file if auto_reload?
|
12
|
+
end
|
13
|
+
|
14
|
+
def load
|
15
|
+
Kernel.load file
|
16
|
+
end
|
17
|
+
|
18
|
+
def listen_file
|
19
|
+
callback = Proc.new do |modified, added, removed|
|
20
|
+
if modified.include? absolute_path
|
21
|
+
Rivendell::Import.logger.info "Configuration changed, reload it"
|
22
|
+
load
|
23
|
+
end
|
24
|
+
end
|
25
|
+
|
26
|
+
Rivendell::Import.logger.info "Listen to config file changes (#{file})"
|
27
|
+
Listen.to(directory).filter(/^#{basename}$/).change(&callback).start(false)
|
28
|
+
end
|
29
|
+
|
30
|
+
def absolute_path
|
31
|
+
::File.expand_path(file)
|
32
|
+
end
|
33
|
+
|
34
|
+
def basename
|
35
|
+
::File.basename(file)
|
36
|
+
end
|
37
|
+
|
38
|
+
def directory
|
39
|
+
::File.dirname(file)
|
40
|
+
end
|
41
|
+
|
42
|
+
end
|
43
|
+
end
|
@@ -58,10 +58,15 @@ module Rivendell::Import::Notifier
|
|
58
58
|
|
59
59
|
after_initialize :read_parameters
|
60
60
|
|
61
|
+
def parameters_hash
|
62
|
+
parameters_as_string = parameters.sort_by { |p| p.first.to_s }.flatten.join('-')
|
63
|
+
Digest::SHA256.hexdigest parameters_as_string
|
64
|
+
end
|
65
|
+
|
61
66
|
def write_parameters
|
62
67
|
if parameters.present?
|
63
68
|
write_attribute :parameters, parameters.to_json
|
64
|
-
write_attribute :key,
|
69
|
+
write_attribute :key, parameters_hash
|
65
70
|
else
|
66
71
|
write_attribute :parameters, nil
|
67
72
|
write_attribute :key, nil
|
@@ -76,7 +81,7 @@ module Rivendell::Import::Notifier
|
|
76
81
|
Mail.new options.merge(:to => target)
|
77
82
|
end
|
78
83
|
|
79
|
-
key = new_notifier.
|
84
|
+
key = new_notifier.parameters_hash
|
80
85
|
if existing_notifier = where(:type => new_notifier.type, :key => key).first
|
81
86
|
existing_notifier
|
82
87
|
else
|
Binary file
|
@@ -0,0 +1,137 @@
|
|
1
|
+
html {color:#000;}
|
2
|
+
body,div,ul,li,p,th,td{margin:0;padding:0;}
|
3
|
+
table {border-collapse:collapse;border-spacing:0;}
|
4
|
+
img {border:0;}
|
5
|
+
strong,b,i,em,th {font-style:normal;font-weight:normal;}
|
6
|
+
li {list-style:none;}
|
7
|
+
th {text-align:left;}
|
8
|
+
body {
|
9
|
+
margin: 0;
|
10
|
+
padding: 0;
|
11
|
+
font-size: 14px;
|
12
|
+
font-family: Trebuchet MS, Verdana, sans-serif;
|
13
|
+
}
|
14
|
+
a {
|
15
|
+
color: #5884a6;
|
16
|
+
text-decoration: none;
|
17
|
+
}
|
18
|
+
a:hover { text-decoration: underline; }
|
19
|
+
hr {
|
20
|
+
display: none;
|
21
|
+
clear: both;
|
22
|
+
}
|
23
|
+
img {
|
24
|
+
border: none;
|
25
|
+
vertical-align: middle;
|
26
|
+
}
|
27
|
+
strong { font-weight: bold; }
|
28
|
+
em { font-style: italic; }
|
29
|
+
#access { display: none; }
|
30
|
+
#header {
|
31
|
+
border-bottom: 4px solid #cccccc;
|
32
|
+
background: #333333;
|
33
|
+
margin-bottom: 20px;
|
34
|
+
}
|
35
|
+
#header div {
|
36
|
+
position: relative;
|
37
|
+
height: 100px;
|
38
|
+
width: 750px;
|
39
|
+
margin: 0 auto;
|
40
|
+
background: url("rivendell-import.png") no-repeat 0px 30px;
|
41
|
+
}
|
42
|
+
#menu {
|
43
|
+
position: absolute;
|
44
|
+
bottom: 0;
|
45
|
+
right: 0;
|
46
|
+
}
|
47
|
+
#menu li {
|
48
|
+
float: left;
|
49
|
+
margin: 0 0 0 8px;
|
50
|
+
background-color: #7196c4;
|
51
|
+
}
|
52
|
+
#menu li a {
|
53
|
+
display: block;
|
54
|
+
padding: 6px 10px;
|
55
|
+
font-size: 105%;
|
56
|
+
color: #ffffff;
|
57
|
+
}
|
58
|
+
#menu li a:hover {
|
59
|
+
color: #333333;
|
60
|
+
text-decoration: none;
|
61
|
+
}
|
62
|
+
#frame {
|
63
|
+
width: 750px;
|
64
|
+
margin: 0 auto;
|
65
|
+
}
|
66
|
+
#frame:after {
|
67
|
+
content: ".";
|
68
|
+
display: block;
|
69
|
+
height: 0;
|
70
|
+
clear: both;
|
71
|
+
visibility: hidden;
|
72
|
+
}
|
73
|
+
#content {
|
74
|
+
padding-top: 5px;
|
75
|
+
}
|
76
|
+
#content .empty {
|
77
|
+
margin-top: 10px;
|
78
|
+
font-style: italic;
|
79
|
+
}
|
80
|
+
#content .pagination {
|
81
|
+
clear: both;
|
82
|
+
padding: 4px 0;
|
83
|
+
font-size: 90%;
|
84
|
+
text-align: right;
|
85
|
+
}
|
86
|
+
#content .pagination .disabled { display: none; }
|
87
|
+
#flash div {
|
88
|
+
margin: 6px 0;
|
89
|
+
padding: 12px 0;
|
90
|
+
font-size: 115%;
|
91
|
+
font-weight: bold;
|
92
|
+
text-indent: 54px;
|
93
|
+
border-top: 1px solid #979797;
|
94
|
+
border-bottom: 1px solid #979797;
|
95
|
+
background: #e7e7e7 url("../images/ui/notice.png") no-repeat 28px;
|
96
|
+
}
|
97
|
+
#flash .success {
|
98
|
+
color: #1e7a1b;
|
99
|
+
border-color: #4bb426;
|
100
|
+
background: #b7e1af url("../images/ui/success.png") no-repeat 28px;
|
101
|
+
}
|
102
|
+
#flash .failure {
|
103
|
+
color: #c01100;
|
104
|
+
border-color: #ca4f35;
|
105
|
+
background: #ffbdbd url("../images/ui/failure.png") no-repeat 28px;
|
106
|
+
}
|
107
|
+
#flash .warning {
|
108
|
+
color: #c67109;
|
109
|
+
border-color: #c67109;
|
110
|
+
background: #f8e694 url("../images/ui/warning.png") no-repeat 28px;
|
111
|
+
}
|
112
|
+
#footer {
|
113
|
+
display: none;
|
114
|
+
clear: both;
|
115
|
+
color: #333333;
|
116
|
+
line-height: 18px;
|
117
|
+
margin: 0;
|
118
|
+
padding: 24px 0;
|
119
|
+
background: #e7db9f;
|
120
|
+
}
|
121
|
+
#footer div {
|
122
|
+
width: 760px;
|
123
|
+
margin: 0 auto;
|
124
|
+
text-align: left;
|
125
|
+
}
|
126
|
+
#content table {
|
127
|
+
width: 100%;
|
128
|
+
}
|
129
|
+
#content table th {
|
130
|
+
font-weight: bold;
|
131
|
+
}
|
132
|
+
#content table td, #content table th {
|
133
|
+
padding: 5px 2px;
|
134
|
+
}
|
135
|
+
#content table tr.odd {
|
136
|
+
background-color: #EAEAEA;
|
137
|
+
}
|
@@ -24,8 +24,24 @@ module Rivendell::Import
|
|
24
24
|
status.completed? or status.failed?
|
25
25
|
end
|
26
26
|
|
27
|
+
@@default_xport_options = {}
|
28
|
+
cattr_accessor :default_xport_options
|
29
|
+
|
30
|
+
def raw_xport_options
|
31
|
+
read_attribute :xport_options
|
32
|
+
end
|
33
|
+
|
34
|
+
def xport_options
|
35
|
+
@xport_options ||= (raw_xport_options ? JSON.parse(raw_xport_options).with_indifferent_access : default_xport_options.dup)
|
36
|
+
end
|
37
|
+
|
38
|
+
def write_xport_options
|
39
|
+
write_attribute :xport_options, (xport_options.present? ? xport_options.to_json : nil)
|
40
|
+
end
|
41
|
+
before_save :write_xport_options
|
42
|
+
|
27
43
|
def xport
|
28
|
-
@xport ||= Rivendell::API::Xport.new
|
44
|
+
@xport ||= Rivendell::API::Xport.new(xport_options)
|
29
45
|
end
|
30
46
|
|
31
47
|
def prepare(&block)
|
@@ -53,6 +69,11 @@ module Rivendell::Import
|
|
53
69
|
notifier.notify
|
54
70
|
end
|
55
71
|
end
|
72
|
+
after_status_changed :notify!, :on => [:completed, :failed]
|
73
|
+
|
74
|
+
def destroy_file!
|
75
|
+
file.destroy! if delete_file?
|
76
|
+
end
|
56
77
|
|
57
78
|
def run
|
58
79
|
logger.debug "Run #{self.inspect}"
|
@@ -64,16 +85,18 @@ module Rivendell::Import
|
|
64
85
|
cart.import file
|
65
86
|
cart.update
|
66
87
|
|
67
|
-
|
88
|
+
destroy_file!
|
68
89
|
change_status! :completed
|
69
90
|
|
70
91
|
logger.info "Imported Cart #{cart.number}"
|
71
92
|
rescue Exception => e
|
72
93
|
logger.error "Task failed : #{e}"
|
94
|
+
logger.debug e.backtrace.join("\n")
|
73
95
|
ensure
|
74
96
|
unless status.completed?
|
75
97
|
change_status! :failed
|
76
98
|
end
|
99
|
+
save!
|
77
100
|
end
|
78
101
|
|
79
102
|
end
|
@@ -1,5 +1,6 @@
|
|
1
1
|
module Rivendell::Import::Tasking
|
2
2
|
module File
|
3
|
+
|
3
4
|
def file=(file)
|
4
5
|
if file
|
5
6
|
@file = file
|
@@ -16,5 +17,10 @@ module Rivendell::Import::Tasking
|
|
16
17
|
def file
|
17
18
|
@file ||= Rivendell::Import::File.new(file_name, :path => file_path)
|
18
19
|
end
|
20
|
+
|
21
|
+
def delete_file!
|
22
|
+
self.delete_file = true
|
23
|
+
end
|
24
|
+
|
19
25
|
end
|
20
26
|
end
|
@@ -9,6 +9,7 @@ module Rivendell::Import::Tasking
|
|
9
9
|
base.class_eval do
|
10
10
|
after_initialize :define_default_status
|
11
11
|
end
|
12
|
+
base.extend ClassMethods
|
12
13
|
end
|
13
14
|
|
14
15
|
def raw_status
|
@@ -22,7 +23,27 @@ module Rivendell::Import::Tasking
|
|
22
23
|
def change_status!(status)
|
23
24
|
logger.debug "Change status to #{status}"
|
24
25
|
update_attribute :status, status.to_s
|
25
|
-
notify! if ran?
|
26
|
+
# notify! if ran?
|
27
|
+
invoke_status_changed_callbacks
|
28
|
+
end
|
29
|
+
|
30
|
+
def invoke_status_changed_callbacks
|
31
|
+
callbacks = self.class.status_changed_callbacks.values_at(:all, status.to_sym).flatten
|
32
|
+
callbacks.each { |method| send method }
|
33
|
+
end
|
34
|
+
|
35
|
+
module ClassMethods
|
36
|
+
|
37
|
+
def status_changed_callbacks
|
38
|
+
@status_changed_callbacks ||= Hash.new { |h,k| h[k] = [] }
|
39
|
+
end
|
40
|
+
|
41
|
+
def after_status_changed(method, options = {})
|
42
|
+
Array(options[:on] || :all).each do |status|
|
43
|
+
status_changed_callbacks[status] << method
|
44
|
+
end
|
45
|
+
end
|
46
|
+
|
26
47
|
end
|
27
48
|
|
28
49
|
end
|
@@ -0,0 +1,56 @@
|
|
1
|
+
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd">
|
2
|
+
<html xmlns="http://www.w3.org/1999/xhtml" lang="fr">
|
3
|
+
<head>
|
4
|
+
<title>Rivendell Import</title>
|
5
|
+
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8" />
|
6
|
+
<link rel="shortcut icon" href="/favicon.ico" />
|
7
|
+
<link href="/screen.css" media="screen" rel="stylesheet" type="text/css" />
|
8
|
+
</head>
|
9
|
+
<body class="tasks index">
|
10
|
+
<ul id="access">
|
11
|
+
<li><a href="#content">Aller au contenu</a></li>
|
12
|
+
<li><a href="#sidebar">Aller au menu</a></li>
|
13
|
+
</ul>
|
14
|
+
<hr/>
|
15
|
+
<div id="header">
|
16
|
+
<div>
|
17
|
+
<ul id="menu">
|
18
|
+
<li><a href="http://wiki.tryphon.eu/rivendell-import/">Help</a></li>
|
19
|
+
</ul>
|
20
|
+
</div>
|
21
|
+
</div>
|
22
|
+
<hr/>
|
23
|
+
<div id="frame">
|
24
|
+
<div id="content">
|
25
|
+
<%= will_paginate tasks %>
|
26
|
+
|
27
|
+
<table>
|
28
|
+
<tr>
|
29
|
+
<th>#</th>
|
30
|
+
<th>Status</th>
|
31
|
+
<th>Since</th>
|
32
|
+
<th>File</th>
|
33
|
+
<th>Destination</th>
|
34
|
+
<th>Priority</th>
|
35
|
+
<tr/>
|
36
|
+
<% tasks.each_with_index do |task, index| %>
|
37
|
+
<tr class="<%= 'odd' if index.odd? %>">
|
38
|
+
<td><%= task.id %></td>
|
39
|
+
<td><%= task.status %></td>
|
40
|
+
<td><%= distance_of_time_in_words_from_now(task.updated_at) %></td>
|
41
|
+
<td><%= truncate_filename(task.file, 60) %></td>
|
42
|
+
<td><%= task.destination %></td>
|
43
|
+
<td>default</td>
|
44
|
+
</tr>
|
45
|
+
<% end %>
|
46
|
+
</table>
|
47
|
+
</div>
|
48
|
+
</div>
|
49
|
+
<hr/>
|
50
|
+
<div id="footer">
|
51
|
+
<div>
|
52
|
+
|
53
|
+
</div>
|
54
|
+
</div>
|
55
|
+
</body>
|
56
|
+
</html>
|
data/rivendell-import.gemspec
CHANGED
@@ -17,12 +17,16 @@ Gem::Specification.new do |gem|
|
|
17
17
|
|
18
18
|
gem.add_runtime_dependency 'listen'
|
19
19
|
gem.add_runtime_dependency 'httmultiparty'
|
20
|
-
gem.add_runtime_dependency 'rivendell-api', '~> 0.0.
|
20
|
+
gem.add_runtime_dependency 'rivendell-api', '~> 0.0.3'
|
21
21
|
gem.add_runtime_dependency 'trollop'
|
22
22
|
gem.add_runtime_dependency 'activerecord', '~> 3.2.8'
|
23
23
|
gem.add_runtime_dependency 'activesupport', '~> 3.2.8'
|
24
24
|
gem.add_runtime_dependency 'mail'
|
25
25
|
gem.add_runtime_dependency 'sqlite3'
|
26
|
+
gem.add_runtime_dependency 'SyslogLogger', '~> 2.0'
|
27
|
+
|
28
|
+
gem.add_runtime_dependency 'sinatra'
|
29
|
+
gem.add_runtime_dependency 'will_paginate', '~> 3.0.0'
|
26
30
|
|
27
31
|
gem.add_development_dependency "simplecov"
|
28
32
|
gem.add_development_dependency "rspec"
|
@@ -14,6 +14,17 @@ describe Rivendell::Import::Base do
|
|
14
14
|
|
15
15
|
end
|
16
16
|
|
17
|
+
describe "#to_prepare" do
|
18
|
+
|
19
|
+
let(:block) { mock }
|
20
|
+
|
21
|
+
it "should use default_to_prepare if not defined" do
|
22
|
+
subject.stub :default_to_prepare => block
|
23
|
+
subject.to_prepare.should == block
|
24
|
+
end
|
25
|
+
|
26
|
+
end
|
27
|
+
|
17
28
|
describe "#create_task" do
|
18
29
|
|
19
30
|
let(:file) { Rivendell::Import::File.new "dummy.wav" }
|
@@ -83,8 +83,7 @@ describe Rivendell::Import::Cart do
|
|
83
83
|
|
84
84
|
before(:each) do
|
85
85
|
subject.number = 123
|
86
|
-
subject.stub :
|
87
|
-
subject.cut.stub :create => true, :number => 1, :update => true
|
86
|
+
subject.stub :cut => mock.as_null_object, :xport => mock.as_null_object
|
88
87
|
end
|
89
88
|
|
90
89
|
it "should create Cut" do
|
@@ -102,6 +101,25 @@ describe Rivendell::Import::Cart do
|
|
102
101
|
subject.import file
|
103
102
|
end
|
104
103
|
|
104
|
+
context "clear_cuts has been defined" do
|
105
|
+
before do
|
106
|
+
subject.clear_cuts!
|
107
|
+
end
|
108
|
+
|
109
|
+
it "should invoke Xport#clear_cuts before create a new cut" do
|
110
|
+
subject.xport.should_receive(:clear_cuts).ordered.with(subject.number)
|
111
|
+
subject.xport.should_receive(:import).ordered
|
112
|
+
subject.import file
|
113
|
+
end
|
114
|
+
end
|
115
|
+
|
116
|
+
context "clear_cuts hasn't been defined" do
|
117
|
+
it "should not invoke Xport#clear_cuts" do
|
118
|
+
subject.xport.should_not_receive(:create_cuts).with(subject.number)
|
119
|
+
subject.import file
|
120
|
+
end
|
121
|
+
end
|
122
|
+
|
105
123
|
end
|
106
124
|
|
107
125
|
describe "#find_by_title" do
|
@@ -129,4 +147,17 @@ describe Rivendell::Import::Cart do
|
|
129
147
|
|
130
148
|
end
|
131
149
|
|
150
|
+
describe "#clear_cuts!" do
|
151
|
+
|
152
|
+
before do
|
153
|
+
subject.number = 123
|
154
|
+
end
|
155
|
+
|
156
|
+
it "should set flag clear_cuts" do
|
157
|
+
subject.clear_cuts!
|
158
|
+
subject.clear_cuts.should be_true
|
159
|
+
end
|
160
|
+
|
161
|
+
end
|
162
|
+
|
132
163
|
end
|
@@ -40,6 +40,15 @@ describe Rivendell::Import::CLI do
|
|
40
40
|
|
41
41
|
end
|
42
42
|
|
43
|
+
describe "syslog?" do
|
44
|
+
|
45
|
+
it "should return true when --syslog is specified" do
|
46
|
+
subject.arguments << "--syslog"
|
47
|
+
subject.should be_syslog
|
48
|
+
end
|
49
|
+
|
50
|
+
end
|
51
|
+
|
43
52
|
describe "#import" do
|
44
53
|
|
45
54
|
it "should return a Rivendell::Import::Base instance" do
|
@@ -71,13 +80,18 @@ describe Rivendell::Import::CLI do
|
|
71
80
|
|
72
81
|
before(:each) do
|
73
82
|
subject.stub :paths => %w{file1 file2}
|
83
|
+
subject.stub :start_webserver
|
84
|
+
subject.stub :config_loader => mock(:load => true)
|
74
85
|
subject.import.tasks.stub :run => true
|
75
86
|
end
|
76
87
|
|
77
|
-
it "should
|
78
|
-
subject.
|
79
|
-
subject.
|
88
|
+
it "should setup logger" do
|
89
|
+
subject.should_receive(:setup_logger)
|
90
|
+
subject.run
|
91
|
+
end
|
80
92
|
|
93
|
+
it "should load config_file" do
|
94
|
+
subject.config_loader.should_receive(:load)
|
81
95
|
subject.run
|
82
96
|
end
|
83
97
|
|
@@ -146,4 +160,34 @@ describe Rivendell::Import::CLI do
|
|
146
160
|
|
147
161
|
end
|
148
162
|
|
163
|
+
describe "#setup_logger" do
|
164
|
+
|
165
|
+
context " with syslog option" do
|
166
|
+
|
167
|
+
before do
|
168
|
+
subject.stub :syslog? => true
|
169
|
+
end
|
170
|
+
|
171
|
+
it "should use a SyslogLogger" do
|
172
|
+
Rivendell::Import.should_receive(:logger=).with(kind_of(Syslog::Logger))
|
173
|
+
subject.setup_logger
|
174
|
+
end
|
175
|
+
|
176
|
+
end
|
177
|
+
|
178
|
+
end
|
179
|
+
|
180
|
+
describe "#config_loader" do
|
181
|
+
|
182
|
+
it "should use config_file" do
|
183
|
+
subject.config_loader.file.should == subject.config_file
|
184
|
+
end
|
185
|
+
|
186
|
+
it "should be auto_reload if listen mode is enabled" do
|
187
|
+
subject.stub :listen_mode? => true, :config_file => "dummy"
|
188
|
+
subject.config_loader.should be_auto_reload
|
189
|
+
end
|
190
|
+
|
191
|
+
end
|
192
|
+
|
149
193
|
end
|
@@ -1,16 +1,38 @@
|
|
1
1
|
require 'spec_helper'
|
2
2
|
|
3
3
|
describe Rivendell::Import::Config do
|
4
|
+
|
5
|
+
let(:config) { Rivendell::Import::Config.new }
|
4
6
|
|
5
7
|
describe "#to_prepare" do
|
6
8
|
|
7
9
|
let(:user_block) { Proc.new {} }
|
8
10
|
|
9
|
-
it "should define
|
11
|
+
it "should define Base.default_to_prepare with given block" do
|
10
12
|
subject.to_prepare(&user_block)
|
11
|
-
|
13
|
+
Rivendell::Import::Base.default_to_prepare.should == user_block
|
12
14
|
end
|
13
15
|
|
14
16
|
end
|
15
17
|
|
18
|
+
describe "#rivendell" do
|
19
|
+
|
20
|
+
subject { config.rivendell }
|
21
|
+
|
22
|
+
def self.it_should_define_task_default_xport_option(attribute)
|
23
|
+
describe "#{attribute}=" do
|
24
|
+
attribute = attribute.to_sym
|
25
|
+
it "should define Task.default_xport_options[:#{attribute}]" do
|
26
|
+
subject.send "#{attribute}=", "dummy"
|
27
|
+
Rivendell::Import::Task.default_xport_options[attribute].should == "dummy"
|
28
|
+
end
|
29
|
+
end
|
30
|
+
end
|
31
|
+
|
32
|
+
it_should_define_task_default_xport_option :host
|
33
|
+
it_should_define_task_default_xport_option :login_name
|
34
|
+
it_should_define_task_default_xport_option :password
|
35
|
+
|
36
|
+
end
|
37
|
+
|
16
38
|
end
|
@@ -55,9 +55,23 @@ describe Rivendell::Import::Notifier::Base do
|
|
55
55
|
subject.key = nil
|
56
56
|
subject.stub :parameters => { :dummy => true }
|
57
57
|
subject.save!
|
58
|
-
subject.key.should == subject.
|
58
|
+
subject.key.should == subject.parameters_hash
|
59
59
|
end
|
60
60
|
|
61
61
|
end
|
62
62
|
|
63
|
+
describe "#parameters_hash" do
|
64
|
+
|
65
|
+
let(:other) { Rivendell::Import::Notifier::Test.new }
|
66
|
+
|
67
|
+
it "should be identical when parameters are identical" do
|
68
|
+
subject.stub :parameters => { :first => 1, :second => 2 }
|
69
|
+
other.stub :parameters => { :second => 2, :first => 1 }
|
70
|
+
|
71
|
+
subject.parameters_hash.should == other.parameters_hash
|
72
|
+
end
|
73
|
+
|
74
|
+
|
75
|
+
end
|
76
|
+
|
63
77
|
end
|
@@ -27,6 +27,26 @@ describe Rivendell::Import::Task do
|
|
27
27
|
subject.xport.should be_instance_of(Rivendell::API::Xport)
|
28
28
|
end
|
29
29
|
|
30
|
+
it "should use xport_options" do
|
31
|
+
subject.xport_options[:host] = "dummy"
|
32
|
+
subject.xport.host.should == "dummy"
|
33
|
+
end
|
34
|
+
|
35
|
+
end
|
36
|
+
|
37
|
+
describe "#xport_options" do
|
38
|
+
|
39
|
+
it "should use #default_xport_options" do
|
40
|
+
subject.stub :default_xport_options => { :host => "dummy" }
|
41
|
+
subject.xport_options.should == { :host => "dummy" }
|
42
|
+
end
|
43
|
+
|
44
|
+
it "should not modified #default_xport_options" do
|
45
|
+
subject.stub :default_xport_options => { :host => "dummy" }
|
46
|
+
subject.xport_options[:host] = "other"
|
47
|
+
subject.default_xport_options.should == { :host => "dummy" }
|
48
|
+
end
|
49
|
+
|
30
50
|
end
|
31
51
|
|
32
52
|
describe "#prepare" do
|
@@ -119,16 +139,29 @@ describe Rivendell::Import::Task do
|
|
119
139
|
subject.destination.should == Rivendell::Import::Task.find(subject).destination
|
120
140
|
end
|
121
141
|
|
142
|
+
def reloaded_task
|
143
|
+
subject.save
|
144
|
+
Rivendell::Import::Task.find(subject)
|
145
|
+
end
|
146
|
+
|
122
147
|
it "should store tags separated with commas" do
|
123
148
|
subject.tags << "tag1" << "tag2"
|
124
|
-
|
125
|
-
Rivendell::Import::Task.find(subject).raw_tags.should == "tag1,tag2"
|
149
|
+
reloaded_task.raw_tags.should == "tag1,tag2"
|
126
150
|
end
|
127
151
|
|
128
152
|
it "should store cart" do
|
129
153
|
subject.cart.number = 123
|
130
|
-
|
131
|
-
|
154
|
+
reloaded_task.cart.number.should == 123
|
155
|
+
end
|
156
|
+
|
157
|
+
it "should store cart" do
|
158
|
+
subject.cart.number = 123
|
159
|
+
reloaded_task.cart.number.should == 123
|
160
|
+
end
|
161
|
+
|
162
|
+
it "should store cart" do
|
163
|
+
subject.xport_options[:host] = "dummy"
|
164
|
+
reloaded_task.xport_options[:host].should == "dummy"
|
132
165
|
end
|
133
166
|
|
134
167
|
end
|
@@ -214,4 +247,37 @@ describe Rivendell::Import::Task do
|
|
214
247
|
|
215
248
|
end
|
216
249
|
|
250
|
+
describe "#delete_file!" do
|
251
|
+
|
252
|
+
it "should set flag delete_file" do
|
253
|
+
subject.delete_file!
|
254
|
+
subject.delete_file.should be_true
|
255
|
+
end
|
256
|
+
|
257
|
+
context "defined" do
|
258
|
+
|
259
|
+
before do
|
260
|
+
subject.stub :cart => mock.as_null_object
|
261
|
+
end
|
262
|
+
|
263
|
+
|
264
|
+
it "should destroy! file when task is completed" do
|
265
|
+
subject.delete_file!
|
266
|
+
subject.file.should_receive(:destroy!)
|
267
|
+
subject.run
|
268
|
+
end
|
269
|
+
|
270
|
+
end
|
271
|
+
|
272
|
+
context "not defined" do
|
273
|
+
|
274
|
+
it "should destroy! file when task is completed" do
|
275
|
+
subject.file.should_not_receive(:destroy!)
|
276
|
+
subject.run
|
277
|
+
end
|
278
|
+
|
279
|
+
end
|
280
|
+
|
281
|
+
end
|
282
|
+
|
217
283
|
end
|
@@ -4,19 +4,6 @@ describe Rivendell::Import::Tasks do
|
|
4
4
|
|
5
5
|
let(:file) { Rivendell::Import::File.new "dummy.wav" }
|
6
6
|
|
7
|
-
describe "#pending?" do
|
8
|
-
|
9
|
-
it "should be true when a task is pending" do
|
10
|
-
subject.create file
|
11
|
-
subject.should be_pending
|
12
|
-
end
|
13
|
-
|
14
|
-
it "should be false when queue is empty?" do
|
15
|
-
subject.should_not be_pending
|
16
|
-
end
|
17
|
-
|
18
|
-
end
|
19
|
-
|
20
7
|
describe "#run" do
|
21
8
|
|
22
9
|
it "should run each task" do
|
metadata
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: rivendell-import
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.0.
|
4
|
+
version: 0.0.2
|
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: 2012-10-
|
12
|
+
date: 2012-10-07 00:00:00.000000000 Z
|
13
13
|
dependencies:
|
14
14
|
- !ruby/object:Gem::Dependency
|
15
15
|
name: listen
|
@@ -50,7 +50,7 @@ dependencies:
|
|
50
50
|
requirements:
|
51
51
|
- - ~>
|
52
52
|
- !ruby/object:Gem::Version
|
53
|
-
version: 0.0.
|
53
|
+
version: 0.0.3
|
54
54
|
type: :runtime
|
55
55
|
prerelease: false
|
56
56
|
version_requirements: !ruby/object:Gem::Requirement
|
@@ -58,7 +58,7 @@ dependencies:
|
|
58
58
|
requirements:
|
59
59
|
- - ~>
|
60
60
|
- !ruby/object:Gem::Version
|
61
|
-
version: 0.0.
|
61
|
+
version: 0.0.3
|
62
62
|
- !ruby/object:Gem::Dependency
|
63
63
|
name: trollop
|
64
64
|
requirement: !ruby/object:Gem::Requirement
|
@@ -139,6 +139,54 @@ dependencies:
|
|
139
139
|
- - ! '>='
|
140
140
|
- !ruby/object:Gem::Version
|
141
141
|
version: '0'
|
142
|
+
- !ruby/object:Gem::Dependency
|
143
|
+
name: SyslogLogger
|
144
|
+
requirement: !ruby/object:Gem::Requirement
|
145
|
+
none: false
|
146
|
+
requirements:
|
147
|
+
- - ~>
|
148
|
+
- !ruby/object:Gem::Version
|
149
|
+
version: '2.0'
|
150
|
+
type: :runtime
|
151
|
+
prerelease: false
|
152
|
+
version_requirements: !ruby/object:Gem::Requirement
|
153
|
+
none: false
|
154
|
+
requirements:
|
155
|
+
- - ~>
|
156
|
+
- !ruby/object:Gem::Version
|
157
|
+
version: '2.0'
|
158
|
+
- !ruby/object:Gem::Dependency
|
159
|
+
name: sinatra
|
160
|
+
requirement: !ruby/object:Gem::Requirement
|
161
|
+
none: false
|
162
|
+
requirements:
|
163
|
+
- - ! '>='
|
164
|
+
- !ruby/object:Gem::Version
|
165
|
+
version: '0'
|
166
|
+
type: :runtime
|
167
|
+
prerelease: false
|
168
|
+
version_requirements: !ruby/object:Gem::Requirement
|
169
|
+
none: false
|
170
|
+
requirements:
|
171
|
+
- - ! '>='
|
172
|
+
- !ruby/object:Gem::Version
|
173
|
+
version: '0'
|
174
|
+
- !ruby/object:Gem::Dependency
|
175
|
+
name: will_paginate
|
176
|
+
requirement: !ruby/object:Gem::Requirement
|
177
|
+
none: false
|
178
|
+
requirements:
|
179
|
+
- - ~>
|
180
|
+
- !ruby/object:Gem::Version
|
181
|
+
version: 3.0.0
|
182
|
+
type: :runtime
|
183
|
+
prerelease: false
|
184
|
+
version_requirements: !ruby/object:Gem::Requirement
|
185
|
+
none: false
|
186
|
+
requirements:
|
187
|
+
- - ~>
|
188
|
+
- !ruby/object:Gem::Version
|
189
|
+
version: 3.0.0
|
142
190
|
- !ruby/object:Gem::Dependency
|
143
191
|
name: simplecov
|
144
192
|
requirement: !ruby/object:Gem::Requirement
|
@@ -285,6 +333,8 @@ files:
|
|
285
333
|
- db/migrate/20120920712200_create_tasks.rb
|
286
334
|
- db/migrate/20120927194300_create_notifiers.rb
|
287
335
|
- db/migrate/20120927203600_create_notifications.rb
|
336
|
+
- db/migrate/20121004194900_add_delete_file_to_task.rb
|
337
|
+
- db/migrate/2012100620361349548586_add_xport_options_to_task.rb
|
288
338
|
- examples/.gitignore
|
289
339
|
- examples/config.rb
|
290
340
|
- features/manage_cart_attributes.feature
|
@@ -293,11 +343,13 @@ files:
|
|
293
343
|
- features/support/env.rb
|
294
344
|
- features/support/mock_xport.rb
|
295
345
|
- lib/rivendell/import.rb
|
346
|
+
- lib/rivendell/import/application.rb
|
296
347
|
- lib/rivendell/import/base.rb
|
297
348
|
- lib/rivendell/import/cart.rb
|
298
349
|
- lib/rivendell/import/carts_cache.rb
|
299
350
|
- lib/rivendell/import/cli.rb
|
300
351
|
- lib/rivendell/import/config.rb
|
352
|
+
- lib/rivendell/import/config_loader.rb
|
301
353
|
- lib/rivendell/import/context.rb
|
302
354
|
- lib/rivendell/import/cut.rb
|
303
355
|
- lib/rivendell/import/file.rb
|
@@ -307,6 +359,8 @@ files:
|
|
307
359
|
- lib/rivendell/import/notifier/mail-subject.erb
|
308
360
|
- lib/rivendell/import/notifier/mail.rb
|
309
361
|
- lib/rivendell/import/notifiers.rb
|
362
|
+
- lib/rivendell/import/static/rivendell-import.png
|
363
|
+
- lib/rivendell/import/static/screen.css
|
310
364
|
- lib/rivendell/import/task.rb
|
311
365
|
- lib/rivendell/import/tasking/cart.rb
|
312
366
|
- lib/rivendell/import/tasking/destination.rb
|
@@ -315,6 +369,7 @@ files:
|
|
315
369
|
- lib/rivendell/import/tasking/tags.rb
|
316
370
|
- lib/rivendell/import/tasks.rb
|
317
371
|
- lib/rivendell/import/version.rb
|
372
|
+
- lib/rivendell/import/views/index.erb
|
318
373
|
- lib/rivendell/import/worker.rb
|
319
374
|
- log/.gitkeep
|
320
375
|
- rivendell-import.gemspec
|
@@ -356,7 +411,7 @@ required_ruby_version: !ruby/object:Gem::Requirement
|
|
356
411
|
version: '0'
|
357
412
|
segments:
|
358
413
|
- 0
|
359
|
-
hash:
|
414
|
+
hash: 900421457418897316
|
360
415
|
required_rubygems_version: !ruby/object:Gem::Requirement
|
361
416
|
none: false
|
362
417
|
requirements:
|
@@ -365,7 +420,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
|
|
365
420
|
version: '0'
|
366
421
|
segments:
|
367
422
|
- 0
|
368
|
-
hash:
|
423
|
+
hash: 900421457418897316
|
369
424
|
requirements: []
|
370
425
|
rubyforge_project:
|
371
426
|
rubygems_version: 1.8.23
|