2853: Merge branch 'master' into 2853-rendezvous
[arvados.git] / apps / workbench / app / models / arvados_resource_list.rb
1 class ArvadosResourceList
2   include ArvadosApiClientHelper
3   include Enumerable
4
5   def initialize resource_class=nil
6     @resource_class = resource_class
7     @fetch_multiple_pages = true
8     @arvados_api_token = Thread.current[:arvados_api_token]
9     @reader_tokens = Thread.current[:reader_tokens]
10   end
11
12   def eager(bool=true)
13     @eager = bool
14     self
15   end
16
17   def limit(max_results)
18     if not max_results.nil? and not max_results.is_a? Integer
19       raise ArgumentError("argument to limit() must be an Integer or nil")
20     end
21     @limit = max_results
22     self
23   end
24
25   def offset(skip)
26     @offset = skip
27     self
28   end
29
30   def order(orderby_spec)
31     @orderby_spec = orderby_spec
32     self
33   end
34
35   def select(columns=nil)
36     # If no column arguments were given, invoke Enumerable#select.
37     if columns.nil?
38       super()
39     else
40       @select ||= []
41       @select += columns
42       self
43     end
44   end
45
46   def filter _filters
47     @filters ||= []
48     @filters += _filters
49     self
50   end
51
52   def where(cond)
53     @cond = cond.dup
54     @cond.keys.each do |uuid_key|
55       if @cond[uuid_key] and (@cond[uuid_key].is_a? Array or
56                              @cond[uuid_key].is_a? ArvadosBase)
57         # Coerce cond[uuid_key] to an array of uuid strings.  This
58         # allows caller the convenience of passing an array of real
59         # objects and uuids in cond[uuid_key].
60         if !@cond[uuid_key].is_a? Array
61           @cond[uuid_key] = [@cond[uuid_key]]
62         end
63         @cond[uuid_key] = @cond[uuid_key].collect do |item|
64           if item.is_a? ArvadosBase
65             item.uuid
66           else
67             item
68           end
69         end
70       end
71     end
72     @cond.keys.select { |x| x.match /_kind$/ }.each do |kind_key|
73       if @cond[kind_key].is_a? Class
74         @cond = @cond.merge({ kind_key => 'arvados#' + arvados_api_client.class_kind(@cond[kind_key]) })
75       end
76     end
77     self
78   end
79
80   def fetch_multiple_pages(f)
81     @fetch_multiple_pages = f
82     self
83   end
84
85   def results
86     if !@results
87       @results = []
88       self.each_page do |r|
89         @results.concat r
90       end
91     end
92     @results
93   end
94
95   def results=(r)
96     @results = r
97     @items_available = r.items_available if r.respond_to? :items_available
98     @result_limit = r.limit if r.respond_to? :limit
99     @result_offset = r.offset if r.respond_to? :offset
100     @results
101   end
102
103   def to_ary
104     results
105   end
106
107   def each(&block)
108     if not @results.nil?
109       @results.each &block
110     else
111       self.each_page do |items|
112         items.each do |i|
113           block.call i
114         end
115       end
116     end
117     self
118   end
119
120   def first
121     results.first
122   end
123
124   def last
125     results.last
126   end
127
128   def [](*x)
129     results.send('[]', *x)
130   end
131
132   def |(x)
133     if x.is_a? Hash
134       self.to_hash | x
135     else
136       results | x.to_ary
137     end
138   end
139
140   def to_hash
141     Hash[self.collect { |x| [x.uuid, x] }]
142   end
143
144   def empty?
145     self.first.nil?
146   end
147
148   def items_available
149     @items_available
150   end
151
152   def result_limit
153     @result_limit
154   end
155
156   def result_offset
157     @result_offset
158   end
159
160   # Obsolete method retained during api transition.
161   def links_for item_or_uuid, link_class=false
162     []
163   end
164
165   protected
166
167   def each_page
168     api_params = {
169       _method: 'GET'
170     }
171     api_params[:where] = @cond if @cond
172     api_params[:eager] = '1' if @eager
173     api_params[:select] = @select if @select
174     api_params[:order] = @orderby_spec if @orderby_spec
175     api_params[:filters] = @filters if @filters
176
177
178     item_count = 0
179     offset = @offset || 0
180     @result_limit = nil
181     @result_offset = nil
182
183     begin
184       api_params[:offset] = offset
185       api_params[:limit] = (@limit - item_count) if @limit
186
187       res = arvados_api_client.api(@resource_class, '', api_params,
188                                    arvados_api_token: @arvados_api_token,
189                                    reader_tokens: @reader_tokens)
190       items = arvados_api_client.unpack_api_response res
191
192       break if items.nil? or not items.any?
193
194       @items_available = items.items_available if items.respond_to?(:items_available)
195       @result_limit = items.limit if (@fetch_multiple_pages == false) and items.respond_to?(:limit)
196       @result_offset = items.offset if (@fetch_multiple_pages == false) and items.respond_to?(:offset)
197
198       item_count += items.size
199       if items.respond_to?(:offset)
200         offset = items.offset + items.size
201       else
202         offset = item_count
203       end
204
205       yield items
206
207       break if @limit and item_count >= @limit
208       break if items.respond_to? :items_available and offset >= items.items_available
209     end while @fetch_multiple_pages
210     self
211   end
212
213 end