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