file_scheduler 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/.gitignore +6 -0
- data/.rspec +2 -0
- data/Gemfile +9 -0
- data/Guardfile +8 -0
- data/Rakefile +10 -0
- data/bin/file-scheduler +19 -0
- data/file_scheduler.gemspec +24 -0
- data/lib/file_scheduler.rb +21 -0
- data/lib/file_scheduler/attributes_parser.rb +40 -0
- data/lib/file_scheduler/base.rb +62 -0
- data/lib/file_scheduler/content.rb +19 -0
- data/lib/file_scheduler/core_ext.rb +37 -0
- data/lib/file_scheduler/file.rb +80 -0
- data/lib/file_scheduler/log.rb +53 -0
- data/lib/file_scheduler/playlist.rb +36 -0
- data/lib/file_scheduler/scheduling.rb +59 -0
- data/lib/file_scheduler/time_chain.rb +19 -0
- data/lib/file_scheduler/time_interval.rb +38 -0
- data/lib/file_scheduler/time_mark.rb +66 -0
- data/lib/file_scheduler/time_parser.rb +37 -0
- data/lib/file_scheduler/url.rb +56 -0
- data/lib/file_scheduler/version.rb +3 -0
- data/spec/lib/file_scheduler/attributes_parser_spec.rb +33 -0
- data/spec/lib/file_scheduler/base_spec.rb +75 -0
- data/spec/lib/file_scheduler/content_shared_examples.rb +12 -0
- data/spec/lib/file_scheduler/file_spec.rb +104 -0
- data/spec/lib/file_scheduler/log_spec.rb +50 -0
- data/spec/lib/file_scheduler/playlist_spec.rb +17 -0
- data/spec/lib/file_scheduler/scheduling_spec.rb +114 -0
- data/spec/lib/file_scheduler/time_interval_spec.rb +46 -0
- data/spec/lib/file_scheduler/time_mark_spec.rb +51 -0
- data/spec/lib/file_scheduler/time_parser_spec.rb +33 -0
- data/spec/lib/file_scheduler/url_spec.rb +33 -0
- data/spec/lib/file_scheduler_spec.rb +117 -0
- data/spec/spec_helper.rb +12 -0
- data/spec/support/pathname.rb +23 -0
- data/spec/support/time.rb +19 -0
- data/spec/support/tmpdir.rb +1 -0
- metadata +118 -0
@@ -0,0 +1,59 @@
|
|
1
|
+
module FileScheduler
|
2
|
+
class Scheduling
|
3
|
+
|
4
|
+
attr_accessor :root, :time, :log
|
5
|
+
|
6
|
+
def initialize(root, time)
|
7
|
+
@root = root
|
8
|
+
@time = time
|
9
|
+
end
|
10
|
+
|
11
|
+
def log
|
12
|
+
@log ||= FileScheduler::Log.new
|
13
|
+
end
|
14
|
+
|
15
|
+
def schedulable?(content)
|
16
|
+
schedulable_by_time?(content) and
|
17
|
+
schedulable_by_repeat?(content)
|
18
|
+
end
|
19
|
+
|
20
|
+
def schedulable_by_time?(content)
|
21
|
+
content.time_constraints.nil? or
|
22
|
+
content.time_constraints.matches?(time)
|
23
|
+
end
|
24
|
+
|
25
|
+
def schedulable_by_repeat?(content)
|
26
|
+
content.repeat_constraints.nil? or
|
27
|
+
log.distance(content).nil? or
|
28
|
+
log.distance(content) >= content.repeat_constraints
|
29
|
+
end
|
30
|
+
|
31
|
+
def schedulable_contents
|
32
|
+
@schedulable_contents ||= root.contents.select do |content|
|
33
|
+
schedulable? content
|
34
|
+
end
|
35
|
+
end
|
36
|
+
|
37
|
+
def forced_contents
|
38
|
+
schedulable_contents.select &:forced_started_time?
|
39
|
+
end
|
40
|
+
|
41
|
+
def forced_next
|
42
|
+
forced_contents.first
|
43
|
+
end
|
44
|
+
|
45
|
+
def schedulable_next
|
46
|
+
schedulable_contents.shuffle.first
|
47
|
+
end
|
48
|
+
|
49
|
+
def next_without_log
|
50
|
+
forced_next or schedulable_next
|
51
|
+
end
|
52
|
+
|
53
|
+
def next_with_log
|
54
|
+
log.log(next_without_log)
|
55
|
+
end
|
56
|
+
alias_method :next, :next_with_log
|
57
|
+
|
58
|
+
end
|
59
|
+
end
|
@@ -0,0 +1,19 @@
|
|
1
|
+
module FileScheduler
|
2
|
+
class TimeChain
|
3
|
+
|
4
|
+
attr_reader :parts
|
5
|
+
|
6
|
+
def initialize(*parts)
|
7
|
+
@parts = parts.flatten
|
8
|
+
end
|
9
|
+
|
10
|
+
def ==(other)
|
11
|
+
other.respond_to?(:parts) and parts == other.parts
|
12
|
+
end
|
13
|
+
|
14
|
+
def to_s
|
15
|
+
parts.join('/')
|
16
|
+
end
|
17
|
+
|
18
|
+
end
|
19
|
+
end
|
@@ -0,0 +1,38 @@
|
|
1
|
+
require 'set'
|
2
|
+
|
3
|
+
module FileScheduler
|
4
|
+
class TimeInterval
|
5
|
+
|
6
|
+
attr_reader :from, :to
|
7
|
+
|
8
|
+
def initialize(from, to)
|
9
|
+
@from, @to = from, to
|
10
|
+
end
|
11
|
+
|
12
|
+
def include?(time)
|
13
|
+
if from > to
|
14
|
+
not time.between?(to, from)
|
15
|
+
else
|
16
|
+
time.between?(from, to)
|
17
|
+
end
|
18
|
+
end
|
19
|
+
|
20
|
+
alias_method :matches?, :include?
|
21
|
+
|
22
|
+
def ==(other)
|
23
|
+
[:from, :to].all? do |attribute|
|
24
|
+
other.respond_to?(attribute) and send(attribute) == other.send(attribute)
|
25
|
+
end
|
26
|
+
end
|
27
|
+
|
28
|
+
def to_s
|
29
|
+
"#{from}-#{to}"
|
30
|
+
end
|
31
|
+
|
32
|
+
def inspect
|
33
|
+
to_s
|
34
|
+
end
|
35
|
+
|
36
|
+
end
|
37
|
+
|
38
|
+
end
|
@@ -0,0 +1,66 @@
|
|
1
|
+
class Time
|
2
|
+
alias_method :minute, :min
|
3
|
+
alias_method :week_day, :wday
|
4
|
+
|
5
|
+
alias_method :compare_to_without_time_mark_support, :<=>
|
6
|
+
|
7
|
+
def <=>(other)
|
8
|
+
if FileScheduler::TimeMark === other
|
9
|
+
-(other <=> self)
|
10
|
+
else
|
11
|
+
compare_to_without_time_mark_support other
|
12
|
+
end
|
13
|
+
end
|
14
|
+
end
|
15
|
+
|
16
|
+
module FileScheduler
|
17
|
+
class TimeMark
|
18
|
+
|
19
|
+
attr_reader :attributes
|
20
|
+
|
21
|
+
def initialize(attributes = {})
|
22
|
+
@attributes = attributes
|
23
|
+
end
|
24
|
+
|
25
|
+
def matches?(time)
|
26
|
+
attributes.all? do |attribute, value|
|
27
|
+
time.send(attribute) == value
|
28
|
+
end
|
29
|
+
end
|
30
|
+
|
31
|
+
def [](attribute)
|
32
|
+
attributes[attribute]
|
33
|
+
end
|
34
|
+
|
35
|
+
[:year, :month, :day, :hour, :minute, :week_day].each do |attribute|
|
36
|
+
define_method(attribute) do
|
37
|
+
attributes[attribute]
|
38
|
+
end
|
39
|
+
end
|
40
|
+
|
41
|
+
def <=>(time)
|
42
|
+
[:year, :month, :day, :week_day, :hour, :minute].each do |attribute|
|
43
|
+
value = attributes[attribute]
|
44
|
+
if value
|
45
|
+
comparaison = value <=> time.send(attribute)
|
46
|
+
return comparaison unless comparaison == 0
|
47
|
+
end
|
48
|
+
end
|
49
|
+
|
50
|
+
0
|
51
|
+
end
|
52
|
+
|
53
|
+
include Comparable
|
54
|
+
|
55
|
+
def to_s
|
56
|
+
[:year, :month, :day, :week_day, :hour, :minute].collect do |attribute|
|
57
|
+
value = attributes[attribute]
|
58
|
+
if value
|
59
|
+
suffix = attribute == :month ? "M" : attribute.to_s.chars.first
|
60
|
+
"#{value}#{suffix}"
|
61
|
+
end
|
62
|
+
end.join
|
63
|
+
end
|
64
|
+
|
65
|
+
end
|
66
|
+
end
|
@@ -0,0 +1,37 @@
|
|
1
|
+
module FileScheduler
|
2
|
+
class TimeParser
|
3
|
+
|
4
|
+
@@time_mark_fields =
|
5
|
+
[["y", 4], ["M", 2], ["d", 2], ["w", 1], ["h", 2], ["m", 2]]
|
6
|
+
|
7
|
+
def self.time_mark_format
|
8
|
+
@@time_mark_fields.collect do |field|
|
9
|
+
"(\\d{#{field[1]}}#{field[0]})?"
|
10
|
+
end.join
|
11
|
+
end
|
12
|
+
|
13
|
+
def parse(string)
|
14
|
+
parsed_fields = string.scan(/^T?#{self.class.time_mark_format}(-#{self.class.time_mark_format})?/).first.map do |field|
|
15
|
+
field.to_i if field
|
16
|
+
end
|
17
|
+
|
18
|
+
from = time_mark(parsed_fields.first(6))
|
19
|
+
to = time_mark(parsed_fields.last(6))
|
20
|
+
|
21
|
+
if to
|
22
|
+
TimeInterval.new from, to
|
23
|
+
else
|
24
|
+
from
|
25
|
+
end
|
26
|
+
end
|
27
|
+
|
28
|
+
def time_mark(parsed_fields)
|
29
|
+
unless parsed_fields.all?(&:nil?)
|
30
|
+
attributes = Hash[ [[:year, :month, :day, :week_day, :hour, :minute], parsed_fields].transpose ]
|
31
|
+
attributes.delete_if { |k,v| v.nil? }
|
32
|
+
TimeMark.new attributes
|
33
|
+
end
|
34
|
+
end
|
35
|
+
|
36
|
+
end
|
37
|
+
end
|
@@ -0,0 +1,56 @@
|
|
1
|
+
module FileScheduler
|
2
|
+
class URL
|
3
|
+
include FileScheduler::Content
|
4
|
+
|
5
|
+
attr_accessor :path, :url
|
6
|
+
|
7
|
+
def initialize(attributes = {})
|
8
|
+
if String === attributes
|
9
|
+
attributes = { :path => attributes }
|
10
|
+
end
|
11
|
+
|
12
|
+
attributes.each { |k,v| send "#{k}=", v }
|
13
|
+
end
|
14
|
+
|
15
|
+
def ==(other)
|
16
|
+
other.respond_to?(:path) and path == other.path
|
17
|
+
end
|
18
|
+
|
19
|
+
def hidden?
|
20
|
+
path_parts.any? do |part|
|
21
|
+
part.start_with?("_")
|
22
|
+
end
|
23
|
+
end
|
24
|
+
|
25
|
+
def path_parts
|
26
|
+
@path_parts ||= path.split("/")
|
27
|
+
end
|
28
|
+
|
29
|
+
def name
|
30
|
+
@path_parts.last
|
31
|
+
end
|
32
|
+
|
33
|
+
def time_constraints
|
34
|
+
part_constraints = path_parts.collect do |part|
|
35
|
+
parser.parse(part)
|
36
|
+
end.compact
|
37
|
+
|
38
|
+
if part_constraints.size > 1
|
39
|
+
FileScheduler::TimeChain.new part_constraints
|
40
|
+
else
|
41
|
+
part_constraints.first
|
42
|
+
end
|
43
|
+
end
|
44
|
+
|
45
|
+
def to_s
|
46
|
+
(url and url.to_s) or path
|
47
|
+
end
|
48
|
+
|
49
|
+
def attributes
|
50
|
+
@attributes ||= path_parts.inject({}) do |attributes, part|
|
51
|
+
attributes.merge attributes_parser.parse(part)
|
52
|
+
end
|
53
|
+
end
|
54
|
+
|
55
|
+
end
|
56
|
+
end
|
@@ -0,0 +1,33 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
|
3
|
+
describe FileScheduler::AttributesParser do
|
4
|
+
|
5
|
+
it "should be empty without {}" do
|
6
|
+
subject.parse("dummy").should be_empty
|
7
|
+
end
|
8
|
+
|
9
|
+
it "should be empty when {} is empty" do
|
10
|
+
subject.parse("dummy{}").should be_empty
|
11
|
+
end
|
12
|
+
|
13
|
+
it "should use {...} at the end of name" do
|
14
|
+
subject.parse("dummy{key=value}").should == { :key => "value" }
|
15
|
+
end
|
16
|
+
|
17
|
+
it "should use {...} before file extension" do
|
18
|
+
subject.parse("dummy{key=value}.wav").should == { :key => "value" }
|
19
|
+
end
|
20
|
+
|
21
|
+
it "should not use {...} before a simple dot" do
|
22
|
+
subject.parse("dummy{key=value}.otherpart.wav").should be_empty
|
23
|
+
end
|
24
|
+
|
25
|
+
it "should support several attributes" do
|
26
|
+
subject.parse("dummy{key1=value1,key2=value2}").should == { :key1 => "value1", :key2 => "value2" }
|
27
|
+
end
|
28
|
+
|
29
|
+
it "should strip spaces" do
|
30
|
+
subject.parse("dummy{ key1 = value1 , key2 = value2 }").should == { :key1 => "value1", :key2 => "value2" }
|
31
|
+
end
|
32
|
+
|
33
|
+
end
|
@@ -0,0 +1,75 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
|
3
|
+
describe FileScheduler::Base do
|
4
|
+
|
5
|
+
before(:each) do
|
6
|
+
subject.root = mock(:contents => mock)
|
7
|
+
end
|
8
|
+
|
9
|
+
let(:content) { mock }
|
10
|
+
|
11
|
+
describe "#contents" do
|
12
|
+
|
13
|
+
it "should return root contents" do
|
14
|
+
subject.contents.should == subject.root.contents
|
15
|
+
end
|
16
|
+
|
17
|
+
end
|
18
|
+
|
19
|
+
describe "scheduling" do
|
20
|
+
|
21
|
+
let(:time) { Time.now }
|
22
|
+
|
23
|
+
it "should return a Scheduling with specified time" do
|
24
|
+
subject.scheduling(time).time.should == time
|
25
|
+
end
|
26
|
+
|
27
|
+
it "should return a Scheduling with the same root" do
|
28
|
+
subject.scheduling.root.should == subject.root
|
29
|
+
end
|
30
|
+
|
31
|
+
it "should return a Scheduling with the same log" do
|
32
|
+
subject.scheduling.log.should == subject.log
|
33
|
+
end
|
34
|
+
|
35
|
+
end
|
36
|
+
|
37
|
+
describe "next" do
|
38
|
+
|
39
|
+
let(:next_content) { content }
|
40
|
+
|
41
|
+
before(:each) do
|
42
|
+
subject.stub :scheduling => mock(:next => content)
|
43
|
+
end
|
44
|
+
|
45
|
+
it "should return the next content choosed by scheduling" do
|
46
|
+
subject.next.should == next_content
|
47
|
+
end
|
48
|
+
|
49
|
+
it "should invoke after_next with selected content" do
|
50
|
+
subject.should_receive(:after_next).with(next_content)
|
51
|
+
subject.next
|
52
|
+
end
|
53
|
+
|
54
|
+
end
|
55
|
+
|
56
|
+
describe "forced_next" do
|
57
|
+
|
58
|
+
let(:forced_content) { content }
|
59
|
+
|
60
|
+
before(:each) do
|
61
|
+
subject.stub :scheduling => mock(:forced_next => content)
|
62
|
+
end
|
63
|
+
|
64
|
+
it "should return the forced content choosed by scheduling" do
|
65
|
+
subject.forced_next.should == forced_content
|
66
|
+
end
|
67
|
+
|
68
|
+
it "should invoke after_next with selected content" do
|
69
|
+
subject.should_receive(:after_next).with(forced_content)
|
70
|
+
subject.forced_next
|
71
|
+
end
|
72
|
+
|
73
|
+
end
|
74
|
+
|
75
|
+
end
|
@@ -0,0 +1,104 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
|
3
|
+
describe FileScheduler::File do
|
4
|
+
|
5
|
+
subject { file "dummy" }
|
6
|
+
|
7
|
+
def file(file, parent = nil)
|
8
|
+
FileScheduler::File.new file, parent
|
9
|
+
end
|
10
|
+
|
11
|
+
describe "#name" do
|
12
|
+
|
13
|
+
it "should use path basename" do
|
14
|
+
file("/path/to/basename").name.should == "basename"
|
15
|
+
end
|
16
|
+
|
17
|
+
end
|
18
|
+
|
19
|
+
describe "#local_time_constraints" do
|
20
|
+
|
21
|
+
it "should parse the time specifications in the name" do
|
22
|
+
file("/path/to/12h30m-dummy",mock).local_time_constraints.should == FileScheduler::TimeMark.new(:hour => 12, :minute => 30)
|
23
|
+
end
|
24
|
+
|
25
|
+
it "should be nil when parent isn't defined (root directory)" do
|
26
|
+
file("/path/to/12h30m-dummy").local_time_constraints.should be_nil
|
27
|
+
end
|
28
|
+
|
29
|
+
end
|
30
|
+
|
31
|
+
describe "#time_constraints" do
|
32
|
+
|
33
|
+
it "should concat with parent constraints" do
|
34
|
+
parent = mock(:time_constraints => FileScheduler::TimeMark.new(:week_day => 1))
|
35
|
+
file("/path/1w/12h30m-dummy", parent).time_constraints.should == FileScheduler::TimeChain.new(parent.time_constraints, FileScheduler::TimeMark.new(:hour => 12, :minute => 30))
|
36
|
+
end
|
37
|
+
|
38
|
+
end
|
39
|
+
|
40
|
+
describe "file_system_children" do
|
41
|
+
|
42
|
+
it "should return pathnames of files and directories present under file" do
|
43
|
+
Dir.mktmpdir do |base|
|
44
|
+
base = Pathname.new(base)
|
45
|
+
|
46
|
+
file = base.touch "file"
|
47
|
+
directory = base.mkpath "directory"
|
48
|
+
|
49
|
+
file(base).file_system_children.should include(file, directory)
|
50
|
+
end
|
51
|
+
end
|
52
|
+
|
53
|
+
end
|
54
|
+
|
55
|
+
describe "children" do
|
56
|
+
|
57
|
+
it "should return children created from files and directories present under path" do
|
58
|
+
Dir.mktmpdir do |base|
|
59
|
+
base = Pathname.new(base)
|
60
|
+
|
61
|
+
file = base.touch "file"
|
62
|
+
directory = base.mkpath "directory"
|
63
|
+
|
64
|
+
parent = file(base)
|
65
|
+
parent.children.should include(file(file, parent))
|
66
|
+
parent.children.should include(file(directory, parent))
|
67
|
+
end
|
68
|
+
end
|
69
|
+
|
70
|
+
it "should ignore files prefixed with underscore" do
|
71
|
+
Dir.mktmpdir do |base|
|
72
|
+
base = Pathname.new(base)
|
73
|
+
|
74
|
+
file = base.touch "_ignored_file"
|
75
|
+
directory = base.mkpath "_ignored_directory"
|
76
|
+
|
77
|
+
file(base).children.should be_empty
|
78
|
+
end
|
79
|
+
end
|
80
|
+
|
81
|
+
end
|
82
|
+
|
83
|
+
describe "local_attributes" do
|
84
|
+
|
85
|
+
it "should use attributes found in name" do
|
86
|
+
file("dummy{key=value}.wav").local_attributes.should == { :key => "value" }
|
87
|
+
end
|
88
|
+
|
89
|
+
end
|
90
|
+
|
91
|
+
describe "#attributes" do
|
92
|
+
|
93
|
+
it "should merge parent attributes with local attributes" do
|
94
|
+
parent = mock(:attributes => {:key1 => "parent_value1", :key2 => "value2"})
|
95
|
+
file("dummy{key1=value1}.wav",parent).attributes.should == { :key1 => "value1", :key2 => "value2" }
|
96
|
+
end
|
97
|
+
|
98
|
+
end
|
99
|
+
|
100
|
+
require File.expand_path("../content_shared_examples", __FILE__)
|
101
|
+
|
102
|
+
it_behaves_like "a content"
|
103
|
+
|
104
|
+
end
|