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