7709: Merge branch 'master' into 7709-api-rails4
[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   # with_count sets the 'count' parameter to 'exact' or 'none' -- see
88   # https://doc.arvados.org/api/methods.html#index
89   def with_count(count_param='exact')
90     @count = count_param
91     self
92   end
93
94   def fetch_multiple_pages(f)
95     @fetch_multiple_pages = f
96     self
97   end
98
99   def results
100     if !@results
101       @results = []
102       self.each_page do |r|
103         @results.concat r
104       end
105     end
106     @results
107   end
108
109   def results=(r)
110     @results = r
111     @items_available = r.items_available if r.respond_to? :items_available
112     @result_limit = r.limit if r.respond_to? :limit
113     @result_offset = r.offset if r.respond_to? :offset
114     @results
115   end
116
117   def to_ary
118     results
119   end
120
121   def each(&block)
122     if not @results.nil?
123       @results.each &block
124     else
125       self.each_page do |items|
126         items.each do |i|
127           block.call i
128         end
129       end
130     end
131     self
132   end
133
134   def first
135     results.first
136   end
137
138   def last
139     results.last
140   end
141
142   def [](*x)
143     results.send('[]', *x)
144   end
145
146   def |(x)
147     if x.is_a? Hash
148       self.to_hash | x
149     else
150       results | x.to_ary
151     end
152   end
153
154   def to_hash
155     Hash[self.collect { |x| [x.uuid, x] }]
156   end
157
158   def empty?
159     self.first.nil?
160   end
161
162   def items_available
163     results
164     @items_available
165   end
166
167   def result_limit
168     results
169     @result_limit
170   end
171
172   def result_offset
173     results
174     @result_offset
175   end
176
177   # Obsolete method retained during api transition.
178   def links_for item_or_uuid, link_class=false
179     []
180   end
181
182   protected
183
184   def each_page
185     api_params = {
186       _method: 'GET'
187     }
188     api_params[:count] = @count if @count
189     api_params[:where] = @cond if @cond
190     api_params[:eager] = '1' if @eager
191     api_params[:select] = @select if @select
192     api_params[:order] = @orderby_spec if @orderby_spec
193     api_params[:filters] = @filters if @filters
194     api_params[:distinct] = @distinct if @distinct
195     if @fetch_multiple_pages
196       # Default limit to (effectively) api server's MAX_LIMIT
197       api_params[:limit] = 2**(0.size*8 - 1) - 1
198     end
199
200     item_count = 0
201     offset = @offset || 0
202     @result_limit = nil
203     @result_offset = nil
204
205     begin
206       api_params[:offset] = offset
207       api_params[:limit] = (@limit - item_count) if @limit
208
209       res = arvados_api_client.api(@resource_class, '', api_params,
210                                    arvados_api_token: @arvados_api_token,
211                                    reader_tokens: @reader_tokens)
212       items = arvados_api_client.unpack_api_response res
213
214       @items_available = items.items_available if items.respond_to?(:items_available)
215       @result_limit = items.limit if (@fetch_multiple_pages == false) and items.respond_to?(:limit)
216       @result_offset = items.offset if (@fetch_multiple_pages == false) and items.respond_to?(:offset)
217
218       break if items.nil? or not items.any?
219
220       item_count += items.size
221       if items.respond_to?(:offset)
222         offset = items.offset + items.size
223       else
224         offset = item_count
225       end
226
227       yield items
228
229       break if @limit and item_count >= @limit
230       break if items.respond_to? :items_available and offset >= items.items_available
231     end while @fetch_multiple_pages
232     self
233   end
234
235 end