scheduled_resource 0.0.1

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.
@@ -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