Merge branch '8784-dir-listings'
[arvados.git] / apps / workbench / app / models / arvados_resource_list.rb
index 3164c790d0701a4f6d67ff797e01f4da91e3d11f..9ba61eaba08ef1d9e008233cf4f06e6c9f8ac97f 100644 (file)
@@ -1,9 +1,18 @@
+# Copyright (C) The Arvados Authors. All rights reserved.
+#
+# SPDX-License-Identifier: AGPL-3.0
+
 class ArvadosResourceList
   include ArvadosApiClientHelper
   include Enumerable
 
+  attr_reader :resource_class
+
   def initialize resource_class=nil
     @resource_class = resource_class
+    @fetch_multiple_pages = true
+    @arvados_api_token = Thread.current[:arvados_api_token]
+    @reader_tokens = Thread.current[:reader_tokens]
   end
 
   def eager(bool=true)
@@ -11,7 +20,25 @@ class ArvadosResourceList
     self
   end
 
+  def distinct(bool=true)
+    @distinct = bool
+    self
+  end
+
+  def include_trash(option=nil)
+    @include_trash = option
+    self
+  end
+
+  def recursive(option=nil)
+    @recursive = option
+    self
+  end
+
   def limit(max_results)
+    if not max_results.nil? and not max_results.is_a? Integer
+      raise ArgumentError("argument to limit() must be an Integer or nil")
+    end
     @limit = max_results
     self
   end
@@ -26,10 +53,15 @@ class ArvadosResourceList
     self
   end
 
-  def select(columns)
-    @select ||= []
-    @select += columns
-    self
+  def select(columns=nil)
+    # If no column arguments were given, invoke Enumerable#select.
+    if columns.nil?
+      super()
+    else
+      @select ||= []
+      @select += columns
+      self
+    end
   end
 
   def filter _filters
@@ -39,17 +71,17 @@ class ArvadosResourceList
   end
 
   def where(cond)
-    cond = cond.dup
-    cond.keys.each do |uuid_key|
-      if cond[uuid_key] and (cond[uuid_key].is_a? Array or
-                             cond[uuid_key].is_a? ArvadosBase)
+    @cond = cond.dup
+    @cond.keys.each do |uuid_key|
+      if @cond[uuid_key] and (@cond[uuid_key].is_a? Array or
+                             @cond[uuid_key].is_a? ArvadosBase)
         # Coerce cond[uuid_key] to an array of uuid strings.  This
         # allows caller the convenience of passing an array of real
         # objects and uuids in cond[uuid_key].
-        if !cond[uuid_key].is_a? Array
-          cond[uuid_key] = [cond[uuid_key]]
+        if !@cond[uuid_key].is_a? Array
+          @cond[uuid_key] = [@cond[uuid_key]]
         end
-        cond[uuid_key] = cond[uuid_key].collect do |item|
+        @cond[uuid_key] = @cond[uuid_key].collect do |item|
           if item.is_a? ArvadosBase
             item.uuid
           else
@@ -58,52 +90,61 @@ class ArvadosResourceList
         end
       end
     end
-    cond.keys.select { |x| x.match /_kind$/ }.each do |kind_key|
-      if cond[kind_key].is_a? Class
-        cond = cond.merge({ kind_key => 'arvados#' + arvados_api_client.class_kind(cond[kind_key]) })
+    @cond.keys.select { |x| x.match /_kind$/ }.each do |kind_key|
+      if @cond[kind_key].is_a? Class
+        @cond = @cond.merge({ kind_key => 'arvados#' + arvados_api_client.class_kind(@cond[kind_key]) })
       end
     end
-    api_params = {
-      _method: 'GET',
-      where: cond
-    }
-    api_params[:eager] = '1' if @eager
-    api_params[:limit] = @limit if @limit
-    api_params[:offset] = @offset if @offset
-    api_params[:select] = @select if @select
-    api_params[:order] = @orderby_spec if @orderby_spec
-    api_params[:filters] = @filters if @filters
-    res = arvados_api_client.api @resource_class, '', api_params
-    @results = arvados_api_client.unpack_api_response res
+    self
+  end
+
+  # with_count sets the 'count' parameter to 'exact' or 'none' -- see
+  # https://doc.arvados.org/api/methods.html#index
+  def with_count(count_param='exact')
+    @count = count_param
+    self
+  end
+
+  def fetch_multiple_pages(f)
+    @fetch_multiple_pages = f
     self
   end
 
   def results
-    self.where({}) if !@results
+    if !@results
+      @results = []
+      self.each_page do |r|
+        @results.concat r
+      end
+    end
     @results
   end
 
   def results=(r)
     @results = r
+    @items_available = r.items_available if r.respond_to? :items_available
+    @result_limit = r.limit if r.respond_to? :limit
+    @result_offset = r.offset if r.respond_to? :offset
+    @results
   end
 
-  def all
-    where({})
+  def to_ary
+    results
   end
 
   def each(&block)
-    results.each do |m|
-      block.call m
+    if not @results.nil?
+      @results.each &block
+    else
+      self.each_page do |items|
+        items.each do |i|
+          block.call i
+        end
+      end
     end
     self
   end
 
-  def collect
-    results.collect do |m|
-      yield m
-    end
-  end
-
   def first
     results.first
   end
@@ -124,62 +165,86 @@ class ArvadosResourceList
     end
   end
 
-  def to_ary
-    results
-  end
-
   def to_hash
-    Hash[results.collect { |x| [x.uuid, x] }]
+    Hash[self.collect { |x| [x.uuid, x] }]
   end
 
   def empty?
-    results.empty?
+    self.first.nil?
   end
 
   def items_available
-    results.items_available if results.respond_to? :items_available
+    results
+    @items_available
   end
 
   def result_limit
-    results.limit if results.respond_to? :limit
+    results
+    @result_limit
   end
 
   def result_offset
-    results.offset if results.respond_to? :offset
+    results
+    @result_offset
   end
 
-  def result_links
-    results.links if results.respond_to? :links
+  # Obsolete method retained during api transition.
+  def links_for item_or_uuid, link_class=false
+    []
   end
 
-  # Return links provided with API response that point to the
-  # specified object, and have the specified link_class. If link_class
-  # is false or omitted, return all links pointing to the specified
-  # object.
-  def links_for item_or_uuid, link_class=false
-    return [] if !result_links
-    unless @links_for_uuid
-      @links_for_uuid = {}
-      result_links.each do |link|
-        if link.respond_to? :head_uuid
-          @links_for_uuid[link.head_uuid] ||= []
-          @links_for_uuid[link.head_uuid] << link
-        end
-      end
-    end
-    if item_or_uuid.respond_to? :uuid
-      uuid = item_or_uuid.uuid
-    else
-      uuid = item_or_uuid
-    end
-    (@links_for_uuid[uuid] || []).select do |link|
-      link_class == false or link.link_class == link_class
+  protected
+
+  def each_page
+    api_params = {
+      _method: 'GET'
+    }
+    api_params[:count] = @count if @count
+    api_params[:where] = @cond if @cond
+    api_params[:eager] = '1' if @eager
+    api_params[:select] = @select if @select
+    api_params[:order] = @orderby_spec if @orderby_spec
+    api_params[:filters] = @filters if @filters
+    api_params[:distinct] = @distinct if @distinct
+    api_params[:include_trash] = @include_trash if @include_trash
+    if @fetch_multiple_pages
+      # Default limit to (effectively) api server's MAX_LIMIT
+      api_params[:limit] = 2**(0.size*8 - 1) - 1
     end
-  end
 
-  # Note: this arbitrarily chooses one of (possibly) multiple names.
-  def name_for item_or_uuid
-    links_for(item_or_uuid, 'name').first.andand.name
+    item_count = 0
+    offset = @offset || 0
+    @result_limit = nil
+    @result_offset = nil
+
+    begin
+      api_params[:offset] = offset
+      api_params[:limit] = (@limit - item_count) if @limit
+
+      res = arvados_api_client.api(@resource_class, '', api_params,
+                                   arvados_api_token: @arvados_api_token,
+                                   reader_tokens: @reader_tokens)
+      items = arvados_api_client.unpack_api_response res
+
+      @items_available = items.items_available if items.respond_to?(:items_available)
+      @result_limit = items.limit if (@fetch_multiple_pages == false) and items.respond_to?(:limit)
+      @result_offset = items.offset if (@fetch_multiple_pages == false) and items.respond_to?(:offset)
+
+      break if items.nil? or not items.any?
+
+      item_count += items.size
+      if items.respond_to?(:offset)
+        offset = items.offset + items.size
+      else
+        offset = item_count
+      end
+
+      yield items
+
+      break if @limit and item_count >= @limit
+      break if items.respond_to? :items_available and offset >= items.items_available
+    end while @fetch_multiple_pages
+    self
   end
 
 end