scheduled_resource 0.0.1

Sign up to get free protection for your applications and to get access to all the features.
@@ -0,0 +1,262 @@
1
+ # scheduled_resource.rb
2
+ # Copyright (c) 2008-2015 Mike Cannon (michael.j.cannon@gmail.com)
3
+ # (http://github.com/emeyekayee/scheduled_resource)
4
+ # MIT License.
5
+ #
6
+
7
+ require 'scheduled_resource/version'
8
+ require 'scheduled_resource/resource_use_block'
9
+ require 'scheduled_resource/helper'
10
+
11
+ require 'z_time_header'
12
+ require 'z_time_header_day'
13
+ require 'z_time_header_hour'
14
+ require 'z_time_label'
15
+ require 'z_time_label_day'
16
+ require 'z_time_label_hour'
17
+
18
+ # A "scheduled resource" is something that can be used for one thing at a time.
19
+ #
20
+ # Example: A Room (resource) is scheduled for a meeting (resource use block)
21
+ # titled "Weekly Staff Meeting" tomorrow from 9am to 11am.
22
+ #
23
+ # Class SchedResource manages class names, id's and labels for a
24
+ # schedule. An instance ties together:
25
+ #
26
+ # 1. A resource class (eg Room),
27
+ # 2. An id, and
28
+ # 3. Strings / html snippets (eg, label, title) for the DOM.
29
+ #
30
+ # The id (2 above) is used to
31
+ #
32
+ # a) select a resource <em>instance</em> and
33
+ #
34
+ # b) select instances of the <em>resource use block</em> class (eg Meeting).
35
+ #
36
+ # The id <em>may</em> be a database id but need not be.
37
+ # It is used by class methods
38
+ # <tt>get_all_blocks()</tt> of model use-block classes.
39
+ # Not tying this to a database id allows a little extra flexibility in
40
+ # configuration.
41
+ #
42
+ # Items 1 and 2 are are combined (with a '_') to form "tags" (ids) for the DOM.
43
+ #
44
+ # See also: ResourceUseBlock.
45
+ #
46
+ #--
47
+ # Config is loaded from config/schedule.yml:
48
+ # all_resources:: Resources in display order.
49
+ # rsrcs_by_kind:: A hash with resources grouped by kind (resource class).
50
+ # rsrc_of_tag:: Indexed by text tag: kind_subid.
51
+ # \visible_time:: Span of time window.
52
+ #++
53
+ #
54
+ # When queried with an array of ids and a time interval, the class
55
+ # method <tt>get_all_blocks(ids, t1, t2)</tt> of a <em>resource use</em>
56
+ # model returns a list of "use blocks", each with a starttime, endtime
57
+ # and descriptions of that use.
58
+ #
59
+ # This method invokes that method on each of the <em>resource use</em>
60
+ # classes. It returns a hash where:
61
+ # Key is a Resource (rsrc);
62
+ # Value is an array of use-block instances (rubs).
63
+ #
64
+ class ScheduledResource
65
+
66
+ CONFIG_FILE = "config/resource_schedule.yml"
67
+
68
+ class_attribute :config
69
+
70
+ # (SchedResource protocol) Returns a hash where each key is an
71
+ # <tt>rid</tt> and the value is an array of resource use
72
+ # blocks in the interval <tt>t1...t2</tt>, ordered by
73
+ # <tt>starttime</tt>.
74
+ #
75
+ # What <em>in</em> means depends on <em>inc</em>. If inc(remental) is
76
+ # false, the client is building the interval from scratch. If "hi", it is
77
+ # an addition to an existing interval on the high side. Similarly
78
+ # for "lo". This is to avoid re-transmitting blocks that span the
79
+ # current time boundaries on the client.
80
+ #
81
+ # Here the resource is a channel and the use blocks are programs.
82
+ #
83
+ # ==== Parameters
84
+ # * <tt>rids</tt> - A list of schedules resource ids (strings).
85
+ # * <tt>t1</tt> - Start time.
86
+ # * <tt>t2</tt> - End time.
87
+ # * <tt>inc</tt> - One of nil, "lo", "hi" (See above).
88
+ #
89
+ # ==== Returns
90
+ # * <tt>Hash</tt> - Each key is an <tt>rid</tt> and the value is an array of resource use blocks in the interval, ordered by <tt>starttime</tt>.
91
+ def self.get_all_blocks(t1, t2, inc)
92
+ blockss = {}
93
+
94
+ config[:rsrcs_by_kind].each do |kind, rsrcs|
95
+ rub_class = block_class_for_resource_name kind
96
+ rids = rsrcs.map{ |r| r.sub_id }
97
+ ru_blkss = rub_class.get_all_blocks rids, t1, t2, inc
98
+
99
+ add_rubs_of_kind kind, ru_blkss, blockss
100
+ end
101
+
102
+ blockss
103
+ end
104
+
105
+
106
+ # ==== Parameters
107
+ # * <tt>name</tt> - The class name (string) of a schedule resource.
108
+ #
109
+ # ==== Returns
110
+ # * <tt>Class</tt> - The class representing the <em>use</em> of that resource for an interval of time.
111
+ def self.block_class_for_resource_name( name )
112
+ config[:block_class_for_resource_kind][name]
113
+ end
114
+
115
+
116
+ # ==== Returns
117
+ # * <tt>Array[SchedResource]</tt> - List of all configured SchedResources .
118
+ def self.resource_list; config[:all_resources] end
119
+
120
+ # ==== Returns
121
+ # * <tt>Time</tt> - The configured width of the visible time window.
122
+ def self.visible_time; config[:visible_time] end
123
+
124
+
125
+ #--
126
+ # Restore configuration from session.
127
+ #
128
+ # When we depend on data in the configuration to satisfy a query we are not
129
+ # being RESTful. On the other hand we are not maintaining changeable state
130
+ # here -- it's just a cache. If there <em>were</em> changeable state it
131
+ # would likely be kept, eg, in a per-user table in the database.
132
+ #++
133
+ def self.ensure_config( session ) # :nodoc:
134
+ return if (self.config ||= session[:schedule_config])
135
+
136
+ config_from_yaml( session )
137
+ end
138
+
139
+
140
+ # ToDo: Generalize this so configuration can be loaded on per-user.
141
+ # Process configuration file.
142
+ def self.config_from_yaml( session )
143
+ config_from_yaml1
144
+ config_from_yaml2 session
145
+ config
146
+ end
147
+
148
+
149
+ private
150
+ # A caching one-of-each-sort constructor.
151
+ #
152
+ # ==== Parameters
153
+ # * <tt>kind</tt> - Class name (string) of a scheduled resource.
154
+ # * <tt>sub_id</tt> - Id (string), selecting a resource instance. The two are combined and used as a unique tag in the DOM as id and class attributes as well as in server code.
155
+ def self.get_for( kind, sub_id )
156
+ tag = compose_tag( kind, sub_id )
157
+ config[:rsrc_of_tag][ tag ] || self.new( kind, sub_id )
158
+ end
159
+
160
+ def self.compose_tag( kind, sub_id ); "#{kind}_#{sub_id}" end
161
+
162
+ def self.config_from_yaml1()
163
+ self.config = { all_resources: [],
164
+ rsrc_of_tag: {},
165
+ block_class_for_resource_kind: {}
166
+ }
167
+ yml = YAML.load_file CONFIG_FILE
168
+
169
+ yml['ResourceKinds'].each do |key, val| # {"Channel" => <#Class Program>...}
170
+ config[:block_class_for_resource_kind][key] = eval val
171
+ end
172
+
173
+ if (rkls = yml['Resources']) # Resource Kind Lists, eg
174
+ rkls.each do |rkl| # ["TimeheaderHour", "Hour0"]
175
+ rkl = rkl.split(/[, ]+/) # ["Channel", "702", "703",... ]
176
+ rk = rkl.shift
177
+ add_resources rkl.map{ |sub_id| make_resource_of_kind(rk, sub_id) }
178
+ end
179
+ end
180
+
181
+ vt = yml['visibleTime']
182
+ config[:visible_time] = vt ? (eval vt) : 3.hours
183
+
184
+ t0 = yml['timeRangeMin']
185
+ config[:time_range_min] = t0 ? (eval t0) : (Time.now - 1.week)
186
+
187
+ tn = yml['timeRangeMax']
188
+ config[:time_range_max] = tn ? (eval tn) : (Time.now - 1.week)
189
+
190
+ config
191
+ end
192
+
193
+
194
+ def self.add_resources(rsrcs)
195
+ rs = config[:all_resources]
196
+ rsrcs.each{ |rsrc| rs.include?( rsrc ) || rs << rsrc }
197
+ end
198
+
199
+
200
+ def self.config_from_yaml2( session )
201
+ config[:rsrcs_by_kind] = resource_list.group_by{ |r| r.kind }
202
+
203
+ config[:rsrcs_by_kind].each do |kind, rsrcs|
204
+ klass = kind.constantize
205
+ rsrcs.each {|rsrc| klass.decorate_resource rsrc }
206
+ end
207
+
208
+ session[:schedule_config] = config
209
+ end
210
+
211
+ def self.make_resource_of_kind( klass, rid )
212
+ klass = eval klass if klass.class == String
213
+ get_for( klass.name, rid )
214
+ end
215
+
216
+ def self.add_rubs_of_kind( kind, ru_blkss, blockss )
217
+ ru_blkss.each do |rid, blks|
218
+ rsrc = get_for( kind, rid )
219
+ rubs = blks.map{ |blk| ResourceUseBlock.new rsrc, blk }
220
+ blockss[ rsrc ] = rubs
221
+ end
222
+ end
223
+
224
+
225
+ public
226
+
227
+ # Instance methods
228
+ def initialize( kind, sub_id ) # :nodoc:
229
+ @tag = self.class.send( :compose_tag, kind, sub_id )
230
+ @label = @title = nil
231
+ config[:rsrc_of_tag][@tag] = self
232
+ end
233
+
234
+ # ==== Returns
235
+ # * <tt>String</tt> - The class name of the scheduled resource.
236
+ def kind() @tag.sub( /_.*/, '' ) end
237
+
238
+ # ==== Returns
239
+ # * <tt>String</tt> - The <tt>rid</tt> (abstract id) of the SchedResource.
240
+ def sub_id() @tag.sub( /.*_/, '' ) end
241
+
242
+ def to_s() # :nodoc:
243
+ @tag
244
+ end
245
+
246
+ def inspect() # :nodoc:
247
+ "<#SchedResource \"#{@tag}\">"
248
+ end
249
+
250
+ attr_accessor :label, :title
251
+ def label(); @label || @tag end
252
+
253
+ def label=(val)
254
+ @label = val
255
+ end
256
+
257
+ def title(); @title || @tag end
258
+
259
+ # ==== Returns
260
+ # * <tt>String</tt> - CSS classes automatically generated for the DOM row representing this SchedResource.
261
+ def css_classes_for_row(); "rsrcRow #{self.kind}row #{@tag}row #{self.kind}Row #{@tag}Row" end # Row + row -- really?
262
+ end
@@ -0,0 +1,65 @@
1
+ # Using ScheduledResource as a module here for namespacing (in transition).
2
+ class ScheduledResource
3
+
4
+ module Helper
5
+
6
+ def ensure_schedule_config
7
+ meth = params[:reset] ? :config_from_yaml : :ensure_config
8
+ ScheduledResource.send( meth, session )
9
+ end
10
+
11
+ def default_time_param
12
+ t_now = Time.now
13
+ t_now.change :min => (t_now.min/15) * 15
14
+ end
15
+
16
+ def set_schedule_query_params(p = params)
17
+ @t1 = p[:t1] || default_time_param
18
+ @t2 = p[:t2] || @t1 + ScheduledResource.visible_time
19
+ @inc= p[:inc]
20
+ end
21
+
22
+ # Set up instance variables for render templates or returned json
23
+ # params: @t1: time-inverval start
24
+ # @t2: time-inverval end
25
+ # @inc: incremental update? One of: nil, 'lo', 'hi'
26
+ #
27
+ # creates: @rsrcs ordered resource list
28
+ # @blockss: lists of use-blocks, keyed by resource
29
+ def get_data_for_time_span()
30
+ set_schedule_query_params
31
+
32
+ @t1 = Time.at(@t1.to_i)
33
+ @t2 = Time.at(@t2.to_i)
34
+
35
+ @rsrcs = ScheduledResource.resource_list
36
+
37
+ @blockss = ScheduledResource.get_all_blocks(@t1, @t2, @inc)
38
+
39
+ json_adjustments
40
+ end
41
+
42
+ def min_time; ScheduledResource.config[:time_range_min].to_i end
43
+ def max_time; ScheduledResource.config[:time_range_max].to_i end
44
+
45
+ # Always send starttime/endtime attributes as Integer values (UTC) -- they are
46
+ # used to size and place the blocks. Timezone is configured separately in the
47
+ # ZTime* classes (config/resource_schedule.yml).
48
+ def json_adjustments
49
+ @blockss.each do |rsrc, blocks|
50
+ blocks.each do |block|
51
+ block.starttime = block.starttime.to_i
52
+ block.endtime = block.endtime.to_i
53
+ end
54
+ end
55
+ @blockss['meta'] = {
56
+ rsrcs: @rsrcs, min_time: min_time, max_time: max_time,
57
+ t1: @t1.to_i, t2: @t2.to_i, inc: @inc,
58
+ }
59
+ end
60
+
61
+
62
+
63
+ end
64
+
65
+ end
@@ -0,0 +1,33 @@
1
+ # Using ScheduledResource as a module here for namespacing (in transition).
2
+ class ScheduledResource
3
+
4
+ # ResourceUseBlock
5
+ #
6
+ # Represents the USE of a resource for an interval of time.
7
+ #
8
+ # Resource X UseModel X [startime..endtime];
9
+ # | | Example -- tv:
10
+ # | | Program [ belongs_to :channel ]
11
+ # | | Timelabel [ belongs_to :timeheader ]
12
+ # |
13
+ # | Example -- tv: Channel, TimeHeader
14
+ #
15
+ class ResourceUseBlock
16
+
17
+ delegate :kind, :to => :@rsrc
18
+
19
+ delegate :starttime=, :endtime=,
20
+ :starttime, :endtime,
21
+ # No longer used by view templates...
22
+ # :title, :subtitle, :description, :stars,
23
+ # :airdate, :category, :previouslyshown,
24
+ :to => :@blk
25
+
26
+ def initialize(rsrc, blk)
27
+ @rsrc = rsrc
28
+ @blk = blk
29
+ end
30
+
31
+ end
32
+
33
+ end
@@ -0,0 +1,3 @@
1
+ class ScheduledResource
2
+ VERSION = "0.0.1"
3
+ end
@@ -0,0 +1,20 @@
1
+ # This is a base for ScheduledResource classes (see below) for timezone-aware time
2
+ # headers. The timezone is specified by the last component of the resource id
3
+ # (rid). Eg, for 'Day_-8' the timezone is ActiveSupport::TimeZone.new(-8)
4
+
5
+
6
+ class ZTimeHeader
7
+
8
+ # (For ScheduledResource protocol) This method lets us set display attributes
9
+ # on the instance in a way specific to the resource-class and rid.
10
+ #
11
+ # ==== Parameters
12
+ # * <tt>rsrc</tt> - A ScheduledResource instance.
13
+ def self.decorate_resource( rsrc )
14
+ rid = rsrc.sub_id
15
+
16
+ rsrc.label = 'Time' # Overridden, presumably
17
+ rsrc.title = rid
18
+ end
19
+
20
+ end
@@ -0,0 +1,16 @@
1
+
2
+ class ZTimeHeaderDay < ZTimeHeader
3
+
4
+ # (For ScheduledResource protocol) This method lets us set display attributes
5
+ # on the instance in a way specific to the resource-class and rid.
6
+ #
7
+ # ==== Parameters
8
+ # * <tt>rsrc</tt> - A ScheduledResource instance.
9
+ def self.decorate_resource( rsrc )
10
+ rid = rsrc.sub_id
11
+
12
+ rsrc.label = "Day"
13
+ rsrc.title = rid
14
+ end
15
+
16
+ end
@@ -0,0 +1,15 @@
1
+ class ZTimeHeaderHour < ZTimeHeader
2
+
3
+ # (For ScheduledResource protocol) This method lets us set display attributes
4
+ # on the instance in a way specific to the resource-class and rid.
5
+ #
6
+ # ==== Parameters
7
+ # * <tt>rsrc</tt> - A ScheduledResource instance.
8
+ def self.decorate_resource( rsrc )
9
+ rid = rsrc.sub_id
10
+
11
+ rsrc.label = "Hour"
12
+ rsrc.title = rid
13
+ end
14
+
15
+ end
@@ -0,0 +1,88 @@
1
+ # For ScheduledResource protocol. This is a base UseBlock class for ZTimeHeader
2
+ # use-block subclasses. These are timezone-aware time headers (rows) and labels
3
+ # (use-blocks). The timezone is specified by the last component of the resource
4
+ # id (rid). Eg, for '-8' the timezone is ActiveSupport::TimeZone.new(-8)
5
+
6
+ class ZTimeLabel
7
+ attr_accessor :starttime, :endtime, :css_classes, :title
8
+
9
+ class_attribute :label, :format, :t_block # t_block (if used) set by subclass
10
+
11
+ # Parameters are TimeWithZone or similar
12
+ def initialize( starttime, endtime )
13
+ @starttime, @endtime = starttime, endtime
14
+ end
15
+
16
+
17
+ # (ScheduledResource protocol) Returns a hash where each key is an
18
+ # <tt>rid</tt> and the value is an array of ZTimeLabels (use
19
+ # blocks) in the interval <tt>t1...t2</tt>, ordered by
20
+ # <tt>starttime</tt>.
21
+ #
22
+ # What <em>in</em> means depends on *inc*. If inc(remental) is
23
+ # nil/false, client is building the interval from scratch. If "hi", it is
24
+ # an addition to an existing interval on the high side. Similarly
25
+ # for "lo". This is to avoid re-transmitting blocks that span the
26
+ # current time boundaries on the client.
27
+ #
28
+ # Here the resource is a ZTimeHeader and the use-blocks are ZTimeLabels.
29
+ #
30
+ # ==== Parameters
31
+ # * <tt>rids</tt> - A list of schedules resource ids (strings).
32
+ # * <tt>t1</tt> - Start time.
33
+ # * <tt>t2</tt> - End time.
34
+ # * <tt>inc</tt> - One of nil, "lo", "hi" (See above).
35
+ #
36
+ # ==== Returns
37
+ # * <tt>Hash</tt> - Each key is a <tt>rid</tt> such as Hour0
38
+ # and the value is an array of Timelabels in the interval, ordered by
39
+ # <tt>starttime</tt>.
40
+ def self.get_all_blocks(ids, t1, t2, inc)
41
+ h = {}
42
+ ids.each{|id| h[id] = get_timeblocks(id, t1, t2, inc)}
43
+ h
44
+ end
45
+
46
+
47
+ def self.get_timeblocks(id, t1, t2, inc)
48
+ tz = tz_from_rid( id )
49
+
50
+ t1 = floor( tz.at(t1) )
51
+ t1 = end_for_start(t1) if inc == 'hi'
52
+
53
+ t2 = tz.at(t2)
54
+ t2 = floor( t2 ) - 1 if inc == 'lo'
55
+
56
+ enum_for( :time_blocks_starting_through, t1, t2 ).to_a
57
+ end
58
+
59
+
60
+ def self.time_blocks_starting_through( starttime, limit )
61
+ while starttime <= limit do # Hmm... I would have guessed '<' over '<='...
62
+ endtime = end_for_start starttime
63
+ yield new( starttime, endtime )
64
+ starttime = endtime
65
+ end
66
+ end
67
+
68
+
69
+ TZ_INT_MAP = {
70
+ -8 => ActiveSupport::TimeZone.new('Pacific Time (US & Canada)' ), # P
71
+ -7 => ActiveSupport::TimeZone.new('Mountain Time (US & Canada)'), # M
72
+ -6 => ActiveSupport::TimeZone.new('Central Time (US & Canada)' ), # C
73
+ -5 => ActiveSupport::TimeZone.new('Eastern Time (US & Canada)' ), # E
74
+ }
75
+
76
+ def self.tz_from_rid( rid )
77
+ # ActiveSupport::TimeZone.new offset_from_rid(rid)
78
+ TZ_INT_MAP[ offset_from_rid(rid) ] || TZ_INT_MAP[-8]
79
+ end
80
+
81
+ def self.offset_from_rid( rid )
82
+ (rid.split('_').last || '').to_i
83
+ end
84
+
85
+ def self.end_for_start(t) t + 1 end # Overridden.
86
+ def self.floor(t) t end # Overridden.
87
+
88
+ end