file_scheduler 0.0.2
Sign up to get free protection for your applications and to get access to all the features.
- 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
data/.gitignore
ADDED
data/.rspec
ADDED
data/Gemfile
ADDED
data/Guardfile
ADDED
data/Rakefile
ADDED
data/bin/file-scheduler
ADDED
@@ -0,0 +1,19 @@
|
|
1
|
+
#!/usr/bin/env ruby
|
2
|
+
|
3
|
+
$: << File.expand_path('../../lib', __FILE__)
|
4
|
+
|
5
|
+
require 'file_scheduler'
|
6
|
+
|
7
|
+
root = ARGV.shift
|
8
|
+
command = ARGV.shift
|
9
|
+
|
10
|
+
scheduler = FileScheduler::Base.new(root)
|
11
|
+
|
12
|
+
case command
|
13
|
+
when "contents"
|
14
|
+
scheduler.contents.each do |content|
|
15
|
+
puts "#{content} #{content.time_constraints}"
|
16
|
+
end
|
17
|
+
else
|
18
|
+
puts scheduler.send(command)
|
19
|
+
end
|
@@ -0,0 +1,24 @@
|
|
1
|
+
# -*- encoding: utf-8 -*-
|
2
|
+
$:.push File.expand_path("../lib", __FILE__)
|
3
|
+
require "file_scheduler/version"
|
4
|
+
|
5
|
+
Gem::Specification.new do |s|
|
6
|
+
s.name = "file_scheduler"
|
7
|
+
s.version = FileScheduler::VERSION
|
8
|
+
s.authors = ["Alban Peignier"]
|
9
|
+
s.email = ["alban@tryphon.eu"]
|
10
|
+
s.homepage = "http://projects.tryphon.eu/filescheduler"
|
11
|
+
s.summary = %q{Schedule contents from files}
|
12
|
+
s.description = %q{Manage a audio/video program from a simple directory}
|
13
|
+
|
14
|
+
s.rubyforge_project = "file_scheduler"
|
15
|
+
|
16
|
+
s.files = `git ls-files`.split("\n")
|
17
|
+
s.test_files = `git ls-files -- {test,spec,features}/*`.split("\n")
|
18
|
+
s.executables = `git ls-files -- bin/*`.split("\n").map{ |f| File.basename(f) }
|
19
|
+
s.require_paths = ["lib"]
|
20
|
+
|
21
|
+
# specify any dependencies here; for example:
|
22
|
+
# s.add_development_dependency "rspec"
|
23
|
+
# s.add_runtime_dependency "rest-client"
|
24
|
+
end
|
@@ -0,0 +1,21 @@
|
|
1
|
+
require "file_scheduler/version"
|
2
|
+
|
3
|
+
module FileScheduler
|
4
|
+
|
5
|
+
end
|
6
|
+
|
7
|
+
require 'pathname'
|
8
|
+
|
9
|
+
require "file_scheduler/core_ext"
|
10
|
+
require "file_scheduler/time_mark"
|
11
|
+
require "file_scheduler/time_interval"
|
12
|
+
require "file_scheduler/time_parser"
|
13
|
+
require "file_scheduler/time_chain"
|
14
|
+
require "file_scheduler/attributes_parser"
|
15
|
+
require "file_scheduler/content"
|
16
|
+
require "file_scheduler/file"
|
17
|
+
require "file_scheduler/url"
|
18
|
+
require "file_scheduler/playlist"
|
19
|
+
require "file_scheduler/log"
|
20
|
+
require "file_scheduler/scheduling"
|
21
|
+
require "file_scheduler/base"
|
@@ -0,0 +1,40 @@
|
|
1
|
+
module FileScheduler
|
2
|
+
class AttributesParser
|
3
|
+
|
4
|
+
def parse(string)
|
5
|
+
Parsing.new(string).attributes
|
6
|
+
end
|
7
|
+
|
8
|
+
class Parsing
|
9
|
+
|
10
|
+
attr_reader :string
|
11
|
+
|
12
|
+
def initialize(string)
|
13
|
+
@string = string
|
14
|
+
end
|
15
|
+
|
16
|
+
def definition
|
17
|
+
# Matches {...}$ or {...}.ext$
|
18
|
+
if string =~ /\{([^\}]+)\}($|\.[^.]+$)/
|
19
|
+
$1
|
20
|
+
else
|
21
|
+
""
|
22
|
+
end
|
23
|
+
end
|
24
|
+
|
25
|
+
def pairs
|
26
|
+
definition.split(",")
|
27
|
+
end
|
28
|
+
|
29
|
+
def attributes
|
30
|
+
pairs.inject({}) do |attributes, pair|
|
31
|
+
key, value = pair.split("=")
|
32
|
+
attributes[key.strip.to_sym] = value.strip
|
33
|
+
attributes
|
34
|
+
end
|
35
|
+
end
|
36
|
+
|
37
|
+
end
|
38
|
+
|
39
|
+
end
|
40
|
+
end
|
@@ -0,0 +1,62 @@
|
|
1
|
+
module FileScheduler
|
2
|
+
class Base
|
3
|
+
|
4
|
+
attr_accessor :root, :log, :status_file
|
5
|
+
|
6
|
+
def initialize(attributes = {})
|
7
|
+
case attributes
|
8
|
+
when String
|
9
|
+
attributes =
|
10
|
+
{ (attributes.url? ? :playlist : :directory) => attributes }
|
11
|
+
when Pathname
|
12
|
+
attributes = { :directory => attributes }
|
13
|
+
end
|
14
|
+
|
15
|
+
@root =
|
16
|
+
if attributes.has_key?(:directory)
|
17
|
+
FileScheduler::File.new(attributes[:directory])
|
18
|
+
elsif attributes.has_key?(:playlist)
|
19
|
+
FileScheduler::Playlist.new(attributes[:playlist])
|
20
|
+
end
|
21
|
+
end
|
22
|
+
|
23
|
+
def log
|
24
|
+
@log ||= Log.new.tap do |log|
|
25
|
+
log.load(status_file) if status_file
|
26
|
+
end
|
27
|
+
end
|
28
|
+
|
29
|
+
def contents
|
30
|
+
root.contents
|
31
|
+
end
|
32
|
+
|
33
|
+
def scheduling(time = Time.now)
|
34
|
+
Scheduling.new(root, time).tap do |scheduling|
|
35
|
+
scheduling.log = log
|
36
|
+
end
|
37
|
+
end
|
38
|
+
|
39
|
+
def next(time = Time.now)
|
40
|
+
scheduling(time).next
|
41
|
+
end
|
42
|
+
|
43
|
+
def forced_next(time = Time.now)
|
44
|
+
scheduling(time).forced_next
|
45
|
+
end
|
46
|
+
|
47
|
+
def after_next(content)
|
48
|
+
log.save(status_file) if status_file
|
49
|
+
end
|
50
|
+
|
51
|
+
[:next, :forced_next].each do |method|
|
52
|
+
alias_method :"#{method}_without_callback", method
|
53
|
+
define_method("#{method}_with_callback") do |*arguments|
|
54
|
+
content = send "#{method}_without_callback", *arguments
|
55
|
+
after_next content
|
56
|
+
content
|
57
|
+
end
|
58
|
+
alias_method method, :"#{method}_with_callback"
|
59
|
+
end
|
60
|
+
|
61
|
+
end
|
62
|
+
end
|
@@ -0,0 +1,19 @@
|
|
1
|
+
module FileScheduler
|
2
|
+
module Content
|
3
|
+
def forced_started_time?
|
4
|
+
name.start_with?("T")
|
5
|
+
end
|
6
|
+
|
7
|
+
def parser
|
8
|
+
@parser ||= TimeParser.new
|
9
|
+
end
|
10
|
+
|
11
|
+
def attributes_parser
|
12
|
+
@attributes_parser ||= AttributesParser.new
|
13
|
+
end
|
14
|
+
|
15
|
+
def repeat_constraints
|
16
|
+
@repeat_constraints ||= attributes[:repeat].try(:to_i)
|
17
|
+
end
|
18
|
+
end
|
19
|
+
end
|
@@ -0,0 +1,37 @@
|
|
1
|
+
class String
|
2
|
+
|
3
|
+
def to_url
|
4
|
+
URI::parse self
|
5
|
+
rescue URI::InvalidURIError
|
6
|
+
nil
|
7
|
+
end
|
8
|
+
|
9
|
+
def url?
|
10
|
+
start_with? "http://"
|
11
|
+
end
|
12
|
+
|
13
|
+
end
|
14
|
+
|
15
|
+
class Object
|
16
|
+
|
17
|
+
def try(method, *arguments)
|
18
|
+
send method, *arguments
|
19
|
+
end
|
20
|
+
|
21
|
+
end
|
22
|
+
|
23
|
+
class Nil
|
24
|
+
|
25
|
+
def try(method, *arguments)
|
26
|
+
nil
|
27
|
+
end
|
28
|
+
|
29
|
+
end
|
30
|
+
|
31
|
+
class Hash
|
32
|
+
|
33
|
+
def set_attributes(object, defaults = {})
|
34
|
+
defaults.merge(self).each { |k,v| object.send "#{k}=", v }
|
35
|
+
end
|
36
|
+
|
37
|
+
end
|
@@ -0,0 +1,80 @@
|
|
1
|
+
module FileScheduler
|
2
|
+
class File
|
3
|
+
|
4
|
+
attr_accessor :path, :parent
|
5
|
+
|
6
|
+
def initialize(path, parent = nil)
|
7
|
+
@parent = parent
|
8
|
+
@path = Pathname.new(path)
|
9
|
+
end
|
10
|
+
|
11
|
+
def name
|
12
|
+
@name ||= path.basename.to_s
|
13
|
+
end
|
14
|
+
|
15
|
+
def hidden?
|
16
|
+
name.start_with?("_")
|
17
|
+
end
|
18
|
+
|
19
|
+
def local_time_constraints
|
20
|
+
parser.parse(name) if parent
|
21
|
+
end
|
22
|
+
|
23
|
+
def time_constraints
|
24
|
+
@time_constraints ||=
|
25
|
+
if parent and parent.time_constraints
|
26
|
+
if local_time_constraints
|
27
|
+
TimeChain.new parent.time_constraints, local_time_constraints
|
28
|
+
else
|
29
|
+
parent.time_constraints
|
30
|
+
end
|
31
|
+
else
|
32
|
+
local_time_constraints
|
33
|
+
end
|
34
|
+
end
|
35
|
+
|
36
|
+
def file_system_children
|
37
|
+
@file_system_children ||= path.children
|
38
|
+
end
|
39
|
+
|
40
|
+
include FileScheduler::Content
|
41
|
+
|
42
|
+
def children
|
43
|
+
@children ||= file_system_children.collect do |file|
|
44
|
+
File.new(file, self)
|
45
|
+
end.delete_if(&:hidden?)
|
46
|
+
end
|
47
|
+
|
48
|
+
def contents
|
49
|
+
if content?
|
50
|
+
[self]
|
51
|
+
else
|
52
|
+
children.collect(&:contents).flatten
|
53
|
+
end
|
54
|
+
end
|
55
|
+
|
56
|
+
def content?
|
57
|
+
path.file?
|
58
|
+
end
|
59
|
+
|
60
|
+
def ==(other)
|
61
|
+
[:parent, :path].all? do |attribute|
|
62
|
+
other.respond_to?(attribute) and send(attribute) == other.send(attribute)
|
63
|
+
end
|
64
|
+
end
|
65
|
+
|
66
|
+
def to_s
|
67
|
+
path.to_s
|
68
|
+
end
|
69
|
+
|
70
|
+
def local_attributes
|
71
|
+
@local_attributes ||= attributes_parser.parse(name)
|
72
|
+
end
|
73
|
+
|
74
|
+
def attributes
|
75
|
+
@attributes ||=
|
76
|
+
(parent ? parent.attributes : {}).merge(local_attributes)
|
77
|
+
end
|
78
|
+
|
79
|
+
end
|
80
|
+
end
|
@@ -0,0 +1,53 @@
|
|
1
|
+
module FileScheduler
|
2
|
+
class Log
|
3
|
+
|
4
|
+
attr_accessor :max_size
|
5
|
+
|
6
|
+
def initialize(options = {})
|
7
|
+
options.set_attributes self, :max_size => 100
|
8
|
+
clear
|
9
|
+
end
|
10
|
+
|
11
|
+
def distance(content)
|
12
|
+
@contents.index content
|
13
|
+
end
|
14
|
+
|
15
|
+
def log(content)
|
16
|
+
@contents.unshift content
|
17
|
+
@contents = @contents.first(max_size)
|
18
|
+
content
|
19
|
+
end
|
20
|
+
|
21
|
+
def clear
|
22
|
+
@contents = []
|
23
|
+
end
|
24
|
+
|
25
|
+
include Enumerable
|
26
|
+
def each(&block)
|
27
|
+
@contents.each(&block)
|
28
|
+
end
|
29
|
+
|
30
|
+
def load(data)
|
31
|
+
return unless data
|
32
|
+
|
33
|
+
# Marshalled data starts with \004
|
34
|
+
unless data.start_with?("\004")
|
35
|
+
data = IO.read(file) if File.exists?(data)
|
36
|
+
end
|
37
|
+
|
38
|
+
@contents = Marshal.load data
|
39
|
+
self
|
40
|
+
end
|
41
|
+
|
42
|
+
def dump
|
43
|
+
Marshal.dump(@contents)
|
44
|
+
end
|
45
|
+
|
46
|
+
def save(file)
|
47
|
+
File.open(file, "w") do |f|
|
48
|
+
f.write dump
|
49
|
+
end
|
50
|
+
end
|
51
|
+
|
52
|
+
end
|
53
|
+
end
|
@@ -0,0 +1,36 @@
|
|
1
|
+
require 'open-uri'
|
2
|
+
|
3
|
+
module FileScheduler
|
4
|
+
class Playlist
|
5
|
+
|
6
|
+
attr_accessor :url, :content
|
7
|
+
|
8
|
+
def initialize(attributes = {})
|
9
|
+
if String === attributes
|
10
|
+
attributes =
|
11
|
+
{ (attributes.url? ? :url : :content) => attributes }
|
12
|
+
end
|
13
|
+
|
14
|
+
attributes.each { |k,v| send "#{k}=", v }
|
15
|
+
end
|
16
|
+
|
17
|
+
def content
|
18
|
+
@content ||= open(url, &:read)
|
19
|
+
end
|
20
|
+
|
21
|
+
def lines
|
22
|
+
@lines ||= content.split
|
23
|
+
end
|
24
|
+
|
25
|
+
def contents
|
26
|
+
lines.collect do |path|
|
27
|
+
FileScheduler::URL.new :path => path, :url => full_url(path)
|
28
|
+
end.delete_if(&:hidden?)
|
29
|
+
end
|
30
|
+
|
31
|
+
def full_url(path)
|
32
|
+
URI.join url, path if url
|
33
|
+
end
|
34
|
+
|
35
|
+
end
|
36
|
+
end
|