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