aspera-cli 4.18.1 → 4.19.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +4 -4
 - checksums.yaml.gz.sig +0 -0
 - data/CHANGELOG.md +13 -0
 - data/CONTRIBUTING.md +5 -12
 - data/README.md +60 -29
 - data/examples/build_exec +85 -0
 - data/lib/aspera/agent/base.rb +2 -0
 - data/lib/aspera/agent/direct.rb +108 -104
 - data/lib/aspera/api/aoc.rb +2 -2
 - data/lib/aspera/api/httpgw.rb +91 -56
 - data/lib/aspera/ascp/installation.rb +47 -32
 - data/lib/aspera/ascp/management.rb +4 -1
 - data/lib/aspera/ascp/products.rb +1 -7
 - data/lib/aspera/cli/formatter.rb +24 -18
 - data/lib/aspera/cli/manager.rb +10 -10
 - data/lib/aspera/cli/plugin.rb +2 -2
 - data/lib/aspera/cli/plugin_factory.rb +10 -1
 - data/lib/aspera/cli/plugins/config.rb +15 -10
 - data/lib/aspera/cli/plugins/node.rb +4 -3
 - data/lib/aspera/cli/plugins/server.rb +1 -1
 - data/lib/aspera/cli/plugins/shares.rb +11 -7
 - data/lib/aspera/cli/sync_actions.rb +72 -31
 - data/lib/aspera/cli/transfer_agent.rb +1 -0
 - data/lib/aspera/cli/transfer_progress.rb +1 -1
 - data/lib/aspera/cli/version.rb +1 -1
 - data/lib/aspera/environment.rb +43 -10
 - data/lib/aspera/faspex_gw.rb +1 -1
 - data/lib/aspera/keychain/encrypted_hash.rb +2 -0
 - data/lib/aspera/log.rb +1 -0
 - data/lib/aspera/node_simulator.rb +1 -1
 - data/lib/aspera/oauth/jwt.rb +1 -1
 - data/lib/aspera/oauth/url_json.rb +2 -0
 - data/lib/aspera/oauth/web.rb +5 -4
 - data/lib/aspera/secret_hider.rb +3 -2
 - data/lib/aspera/ssh.rb +1 -1
 - data/lib/aspera/transfer/faux_file.rb +7 -5
 - data/lib/aspera/transfer/parameters.rb +27 -19
 - data/lib/aspera/transfer/spec.rb +8 -10
 - data/lib/aspera/transfer/sync.rb +52 -47
 - data/lib/aspera/web_auth.rb +0 -1
 - data/lib/aspera/web_server_simple.rb +24 -13
 - data.tar.gz.sig +0 -0
 - metadata +3 -3
 - metadata.gz.sig +0 -0
 - data/examples/rubyc +0 -24
 
    
        data/lib/aspera/agent/direct.rb
    CHANGED
    
    | 
         @@ -19,9 +19,10 @@ module Aspera 
     | 
|
| 
       19 
19 
     | 
    
         
             
                # executes a local "ascp", connects mgt port, equivalent of "Fasp Manager"
         
     | 
| 
       20 
20 
     | 
    
         
             
                class Direct < Base
         
     | 
| 
       21 
21 
     | 
    
         
             
                  LISTEN_LOCAL_ADDRESS = '127.0.0.1'
         
     | 
| 
       22 
     | 
    
         
            -
                   
     | 
| 
      
 22 
     | 
    
         
            +
                  # 0 means: select an available port
         
     | 
| 
      
 23 
     | 
    
         
            +
                  SELECT_AVAILABLE_PORT = 0
         
     | 
| 
       23 
24 
     | 
    
         
             
                  # spellchecker: enable
         
     | 
| 
       24 
     | 
    
         
            -
                  private_constant :LISTEN_LOCAL_ADDRESS, : 
     | 
| 
      
 25 
     | 
    
         
            +
                  private_constant :LISTEN_LOCAL_ADDRESS, :SELECT_AVAILABLE_PORT
         
     | 
| 
       25 
26 
     | 
    
         | 
| 
       26 
27 
     | 
    
         
             
                  # method of Base
         
     | 
| 
       27 
28 
     | 
    
         
             
                  # start ascp transfer(s) (non blocking), single or multi-session
         
     | 
| 
         @@ -93,12 +94,12 @@ module Aspera 
     | 
|
| 
       93 
94 
     | 
    
         
             
                        # do deep copy (each thread has its own copy because it is modified here below and in thread)
         
     | 
| 
       94 
95 
     | 
    
         
             
                        this_session = session.clone
         
     | 
| 
       95 
96 
     | 
    
         
             
                        this_session[:ts] = this_session[:ts].clone
         
     | 
| 
       96 
     | 
    
         
            -
                        this_session[:env_args] = this_session[:env_args].clone
         
     | 
| 
       97 
     | 
    
         
            -
                         
     | 
| 
      
 97 
     | 
    
         
            +
                        env_args = this_session[:env_args] = this_session[:env_args].clone
         
     | 
| 
      
 98 
     | 
    
         
            +
                        args = env_args[:args] = env_args[:args].clone
         
     | 
| 
       98 
99 
     | 
    
         
             
                        # set multi session part
         
     | 
| 
       99 
     | 
    
         
            -
                         
     | 
| 
      
 100 
     | 
    
         
            +
                        args.unshift("-C#{i}:#{multi_session_info[:count]}")
         
     | 
| 
       100 
101 
     | 
    
         
             
                        # option: increment (default as per ascp manual) or not (cluster on other side ?)
         
     | 
| 
       101 
     | 
    
         
            -
                         
     | 
| 
      
 102 
     | 
    
         
            +
                        args.unshift('-O', (multi_session_info[:udp_base] + i - 1).to_s) if @multi_incr_udp
         
     | 
| 
       102 
103 
     | 
    
         
             
                        # finally start the thread
         
     | 
| 
       103 
104 
     | 
    
         
             
                        this_session[:thread] = Thread.new(this_session) {|session_info|transfer_thread_entry(session_info)}
         
     | 
| 
       104 
105 
     | 
    
         
             
                        @sessions.push(this_session)
         
     | 
| 
         @@ -129,81 +130,46 @@ module Aspera 
     | 
|
| 
       129 
130 
     | 
    
         
             
                    Log.log.debug('fasp local shutdown')
         
     | 
| 
       130 
131 
     | 
    
         
             
                  end
         
     | 
| 
       131 
132 
     | 
    
         | 
| 
       132 
     | 
    
         
            -
                  # @ 
     | 
| 
       133 
     | 
    
         
            -
                  def  
     | 
| 
       134 
     | 
    
         
            -
                     
     | 
| 
       135 
     | 
    
         
            -
                    case event['Type']
         
     | 
| 
       136 
     | 
    
         
            -
                    when 'INIT'
         
     | 
| 
       137 
     | 
    
         
            -
                      @pre_calc_sent = false
         
     | 
| 
       138 
     | 
    
         
            -
                      @pre_calc_last_size = nil
         
     | 
| 
       139 
     | 
    
         
            -
                      notify_progress(session_id: session_id, type: :session_start)
         
     | 
| 
       140 
     | 
    
         
            -
                    when 'NOTIFICATION' # sent from remote
         
     | 
| 
       141 
     | 
    
         
            -
                      if event.key?('PreTransferBytes')
         
     | 
| 
       142 
     | 
    
         
            -
                        @pre_calc_sent = true
         
     | 
| 
       143 
     | 
    
         
            -
                        notify_progress(session_id: session_id, type: :session_size, info: event['PreTransferBytes'])
         
     | 
| 
       144 
     | 
    
         
            -
                      end
         
     | 
| 
       145 
     | 
    
         
            -
                    when 'STATS' # during transfer
         
     | 
| 
       146 
     | 
    
         
            -
                      @pre_calc_last_size = event['TransferBytes'].to_i + event['StartByte'].to_i
         
     | 
| 
       147 
     | 
    
         
            -
                      notify_progress(session_id: session_id, type: :transfer, info: @pre_calc_last_size)
         
     | 
| 
       148 
     | 
    
         
            -
                    when 'DONE', 'ERROR' # end of session
         
     | 
| 
       149 
     | 
    
         
            -
                      total_size = event['TransferBytes'].to_i + event['StartByte'].to_i
         
     | 
| 
       150 
     | 
    
         
            -
                      if !@pre_calc_sent && !total_size.zero?
         
     | 
| 
       151 
     | 
    
         
            -
                        notify_progress(session_id: session_id, type: :session_size, info: total_size)
         
     | 
| 
       152 
     | 
    
         
            -
                      end
         
     | 
| 
       153 
     | 
    
         
            -
                      if @pre_calc_last_size != total_size
         
     | 
| 
       154 
     | 
    
         
            -
                        notify_progress(session_id: session_id, type: :transfer, info: total_size)
         
     | 
| 
       155 
     | 
    
         
            -
                      end
         
     | 
| 
       156 
     | 
    
         
            -
                      notify_progress(session_id: session_id, type: :end)
         
     | 
| 
       157 
     | 
    
         
            -
                      # cspell:disable
         
     | 
| 
       158 
     | 
    
         
            -
                    when 'SESSION'
         
     | 
| 
       159 
     | 
    
         
            -
                    when 'ARGSTOP'
         
     | 
| 
       160 
     | 
    
         
            -
                    when 'FILEERROR'
         
     | 
| 
       161 
     | 
    
         
            -
                    when 'STOP'
         
     | 
| 
       162 
     | 
    
         
            -
                      # cspell:enable
         
     | 
| 
       163 
     | 
    
         
            -
                      # stop event when one file is completed
         
     | 
| 
       164 
     | 
    
         
            -
                    else
         
     | 
| 
       165 
     | 
    
         
            -
                      Log.log.debug{"unknown event type #{event['Type']}"}
         
     | 
| 
       166 
     | 
    
         
            -
                    end
         
     | 
| 
      
 133 
     | 
    
         
            +
                  # @return [Array] list of sessions for a job
         
     | 
| 
      
 134 
     | 
    
         
            +
                  def sessions_by_job(job_id)
         
     | 
| 
      
 135 
     | 
    
         
            +
                    @sessions.select{|session_info| session_info[:job_id].eql?(job_id)}
         
     | 
| 
       167 
136 
     | 
    
         
             
                  end
         
     | 
| 
       168 
137 
     | 
    
         | 
| 
       169 
     | 
    
         
            -
                  # This is the low level method to start the  
     | 
| 
       170 
     | 
    
         
            -
                  #  
     | 
| 
       171 
     | 
    
         
            -
                  #  
     | 
| 
      
 138 
     | 
    
         
            +
                  # This is the low level method to start the transfer process
         
     | 
| 
      
 139 
     | 
    
         
            +
                  # Start process with management port.
         
     | 
| 
      
 140 
     | 
    
         
            +
                  # returns
         
     | 
| 
       172 
141 
     | 
    
         
             
                  # raises FaspError on error
         
     | 
| 
       173 
     | 
    
         
            -
                  #  
     | 
| 
       174 
     | 
    
         
            -
                  #  
     | 
| 
       175 
     | 
    
         
            -
                  # @param  
     | 
| 
       176 
     | 
    
         
            -
                  # @param  
     | 
| 
       177 
     | 
    
         
            -
                  #  
     | 
| 
       178 
     | 
    
         
            -
                  def  
     | 
| 
       179 
     | 
    
         
            -
                     
     | 
| 
      
 142 
     | 
    
         
            +
                  # @param session this session information, keys :io and :token_regenerator
         
     | 
| 
      
 143 
     | 
    
         
            +
                  # @param env  [Hash]   environment variables
         
     | 
| 
      
 144 
     | 
    
         
            +
                  # @param name [Symbol] name of executable: :ascp, :ascp4 or :async
         
     | 
| 
      
 145 
     | 
    
         
            +
                  # @param args [Array]  command line arguments
         
     | 
| 
      
 146 
     | 
    
         
            +
                  # @return [nil] when process has exited
         
     | 
| 
      
 147 
     | 
    
         
            +
                  def start_and_monitor_process(
         
     | 
| 
      
 148 
     | 
    
         
            +
                    session:,
         
     | 
| 
      
 149 
     | 
    
         
            +
                    env:,
         
     | 
| 
      
 150 
     | 
    
         
            +
                    name:,
         
     | 
| 
      
 151 
     | 
    
         
            +
                    args:
         
     | 
| 
      
 152 
     | 
    
         
            +
                  )
         
     | 
| 
       180 
153 
     | 
    
         
             
                    Aspera.assert_type(session, Hash)
         
     | 
| 
       181 
     | 
    
         
            -
                    Log.log.debug{"env_args=#{env_args.inspect}"}
         
     | 
| 
       182 
154 
     | 
    
         
             
                    notify_progress(session_id: nil, type: :pre_start, info: 'starting')
         
     | 
| 
       183 
155 
     | 
    
         
             
                    begin
         
     | 
| 
       184 
     | 
    
         
            -
                       
     | 
| 
      
 156 
     | 
    
         
            +
                      command_pid = nil
         
     | 
| 
       185 
157 
     | 
    
         
             
                      # we use Socket directly, instead of TCPServer, as it gives access to lower level options
         
     | 
| 
       186 
     | 
    
         
            -
                      socket_class =  
     | 
| 
      
 158 
     | 
    
         
            +
                      socket_class = defined?(JRUBY_VERSION) ? ServerSocket : Socket
         
     | 
| 
       187 
159 
     | 
    
         
             
                      mgt_server_socket = socket_class.new(Socket::AF_INET, Socket::SOCK_STREAM, 0)
         
     | 
| 
       188 
     | 
    
         
            -
                      # open any available (0) local TCP port for use as  
     | 
| 
       189 
     | 
    
         
            -
                      mgt_server_socket.bind(Addrinfo.tcp(LISTEN_LOCAL_ADDRESS,  
     | 
| 
      
 160 
     | 
    
         
            +
                      # open any available (0) local TCP port for use as management port
         
     | 
| 
      
 161 
     | 
    
         
            +
                      mgt_server_socket.bind(Addrinfo.tcp(LISTEN_LOCAL_ADDRESS, SELECT_AVAILABLE_PORT))
         
     | 
| 
       190 
162 
     | 
    
         
             
                      # build arguments and add mgt port
         
     | 
| 
       191 
     | 
    
         
            -
                       
     | 
| 
       192 
     | 
    
         
            -
             
     | 
| 
       193 
     | 
    
         
            -
                       
     | 
| 
       194 
     | 
    
         
            -
             
     | 
| 
       195 
     | 
    
         
            -
                      Log.log.debug do
         
     | 
| 
       196 
     | 
    
         
            -
                        [
         
     | 
| 
       197 
     | 
    
         
            -
                          'execute:'.red,
         
     | 
| 
       198 
     | 
    
         
            -
                          env_args[:env].map{|k, v| "#{k}=#{Shellwords.shellescape(v)}"},
         
     | 
| 
       199 
     | 
    
         
            -
                          Shellwords.shellescape(ascp_path),
         
     | 
| 
       200 
     | 
    
         
            -
                          ascp_arguments.map{|a|Shellwords.shellescape(a)}
         
     | 
| 
       201 
     | 
    
         
            -
                        ].flatten.join(' ')
         
     | 
| 
      
 163 
     | 
    
         
            +
                      command_arguments = if name.eql?(:async)
         
     | 
| 
      
 164 
     | 
    
         
            +
                        ["--exclusive-mgmt-port=#{mgt_server_socket.local_address.ip_port}"]
         
     | 
| 
      
 165 
     | 
    
         
            +
                      else
         
     | 
| 
      
 166 
     | 
    
         
            +
                        ['-M', mgt_server_socket.local_address.ip_port.to_s]
         
     | 
| 
       202 
167 
     | 
    
         
             
                      end
         
     | 
| 
       203 
     | 
    
         
            -
                       
     | 
| 
       204 
     | 
    
         
            -
                       
     | 
| 
       205 
     | 
    
         
            -
                       
     | 
| 
       206 
     | 
    
         
            -
                       
     | 
| 
      
 168 
     | 
    
         
            +
                      command_arguments.concat(args)
         
     | 
| 
      
 169 
     | 
    
         
            +
                      # get location of command executable (ascp, async)
         
     | 
| 
      
 170 
     | 
    
         
            +
                      command_path = Ascp::Installation.instance.path(name)
         
     | 
| 
      
 171 
     | 
    
         
            +
                      command_pid = Environment.secure_spawn(env: env, exec: command_path, args: command_arguments)
         
     | 
| 
      
 172 
     | 
    
         
            +
                      notify_progress(session_id: nil, type: :pre_start, info: "waiting for #{name} to start")
         
     | 
| 
       207 
173 
     | 
    
         
             
                      mgt_server_socket.listen(1)
         
     | 
| 
       208 
174 
     | 
    
         
             
                      # TODO: timeout does not work when Process.spawn is used... until process exits, then it works
         
     | 
| 
       209 
175 
     | 
    
         
             
                      Log.log.debug{"before select, timeout: #{@spawn_timeout_sec}"}
         
     | 
| 
         @@ -213,15 +179,15 @@ module Aspera 
     | 
|
| 
       213 
179 
     | 
    
         
             
                      # There is a connection to accept
         
     | 
| 
       214 
180 
     | 
    
         
             
                      client_socket, _client_addrinfo = mgt_server_socket.accept
         
     | 
| 
       215 
181 
     | 
    
         
             
                      Log.log.debug('after accept')
         
     | 
| 
       216 
     | 
    
         
            -
                       
     | 
| 
      
 182 
     | 
    
         
            +
                      management_port_io = client_socket.to_io
         
     | 
| 
       217 
183 
     | 
    
         
             
                      # management messages include file names which may be utf8
         
     | 
| 
       218 
184 
     | 
    
         
             
                      # by default socket is US-ASCII
         
     | 
| 
       219 
185 
     | 
    
         
             
                      # TODO: use same value as Encoding.default_external
         
     | 
| 
       220 
     | 
    
         
            -
                       
     | 
| 
       221 
     | 
    
         
            -
                      session[:io] =  
     | 
| 
      
 186 
     | 
    
         
            +
                      management_port_io.set_encoding(Encoding::UTF_8)
         
     | 
| 
      
 187 
     | 
    
         
            +
                      session[:io] = management_port_io
         
     | 
| 
       222 
188 
     | 
    
         
             
                      processor = Ascp::Management.new
         
     | 
| 
       223 
189 
     | 
    
         
             
                      # read management port, until socket is closed (gets returns nil)
         
     | 
| 
       224 
     | 
    
         
            -
                      while (line =  
     | 
| 
      
 190 
     | 
    
         
            +
                      while (line = management_port_io.gets)
         
     | 
| 
       225 
191 
     | 
    
         
             
                        event = processor.process_line(line.chomp)
         
     | 
| 
       226 
192 
     | 
    
         
             
                        next unless event
         
     | 
| 
       227 
193 
     | 
    
         
             
                        # event is ready
         
     | 
| 
         @@ -241,7 +207,7 @@ module Aspera 
     | 
|
| 
       241 
207 
     | 
    
         
             
                            # regenerate token here, expired, or error on it
         
     | 
| 
       242 
208 
     | 
    
         
             
                            # Note: in multi-session, each session will have a different one.
         
     | 
| 
       243 
209 
     | 
    
         
             
                            Log.log.warn('Regenerating token for transfer')
         
     | 
| 
       244 
     | 
    
         
            -
                             
     | 
| 
      
 210 
     | 
    
         
            +
                            env['ASPERA_SCP_TOKEN'] = session[:token_regenerator].refreshed_transfer_token
         
     | 
| 
       245 
211 
     | 
    
         
             
                          end
         
     | 
| 
       246 
212 
     | 
    
         
             
                          raise Transfer::Error.new(last_event['Description'], last_event['Code'].to_i)
         
     | 
| 
       247 
213 
     | 
    
         
             
                        when 'DONE'
         
     | 
| 
         @@ -257,16 +223,16 @@ module Aspera 
     | 
|
| 
       257 
223 
     | 
    
         
             
                      raise Transfer::Error, 'transfer interrupted by user'
         
     | 
| 
       258 
224 
     | 
    
         
             
                    ensure
         
     | 
| 
       259 
225 
     | 
    
         
             
                      mgt_server_socket.close
         
     | 
| 
       260 
     | 
    
         
            -
                      # if  
     | 
| 
       261 
     | 
    
         
            -
                      unless  
     | 
| 
      
 226 
     | 
    
         
            +
                      # if command was successfully started, check its status
         
     | 
| 
      
 227 
     | 
    
         
            +
                      unless command_pid.nil?
         
     | 
| 
       262 
228 
     | 
    
         
             
                        # "wait" for process to avoid zombie
         
     | 
| 
       263 
     | 
    
         
            -
                        Process.wait( 
     | 
| 
      
 229 
     | 
    
         
            +
                        Process.wait(command_pid)
         
     | 
| 
       264 
230 
     | 
    
         
             
                        status = $CHILD_STATUS
         
     | 
| 
       265 
     | 
    
         
            -
                         
     | 
| 
      
 231 
     | 
    
         
            +
                        # command_pid = nil
         
     | 
| 
       266 
232 
     | 
    
         
             
                        session.delete(:io)
         
     | 
| 
       267 
     | 
    
         
            -
                        # status is nil if an exception occurred before starting  
     | 
| 
      
 233 
     | 
    
         
            +
                        # status is nil if an exception occurred before starting command
         
     | 
| 
       268 
234 
     | 
    
         
             
                        if !status&.success?
         
     | 
| 
       269 
     | 
    
         
            -
                          message = status.nil? ?  
     | 
| 
      
 235 
     | 
    
         
            +
                          message = status.nil? ? "#{name} not started" : "#{name} failed (#{status})"
         
     | 
| 
       270 
236 
     | 
    
         
             
                          # raise error only if there was not already an exception (ERROR_INFO)
         
     | 
| 
       271 
237 
     | 
    
         
             
                          raise Transfer::Error, message unless $ERROR_INFO
         
     | 
| 
       272 
238 
     | 
    
         
             
                          # else display this message also, as main exception is already here
         
     | 
| 
         @@ -274,11 +240,47 @@ module Aspera 
     | 
|
| 
       274 
240 
     | 
    
         
             
                        end
         
     | 
| 
       275 
241 
     | 
    
         
             
                      end
         
     | 
| 
       276 
242 
     | 
    
         
             
                    end
         
     | 
| 
      
 243 
     | 
    
         
            +
                    nil
         
     | 
| 
       277 
244 
     | 
    
         
             
                  end
         
     | 
| 
       278 
245 
     | 
    
         | 
| 
       279 
     | 
    
         
            -
                   
     | 
| 
       280 
     | 
    
         
            -
             
     | 
| 
       281 
     | 
    
         
            -
             
     | 
| 
      
 246 
     | 
    
         
            +
                  private
         
     | 
| 
      
 247 
     | 
    
         
            +
             
     | 
| 
      
 248 
     | 
    
         
            +
                  # notify progress to callback
         
     | 
| 
      
 249 
     | 
    
         
            +
                  # @param event management port event
         
     | 
| 
      
 250 
     | 
    
         
            +
                  def process_progress(event)
         
     | 
| 
      
 251 
     | 
    
         
            +
                    session_id = event['SessionId']
         
     | 
| 
      
 252 
     | 
    
         
            +
                    case event['Type']
         
     | 
| 
      
 253 
     | 
    
         
            +
                    when 'INIT'
         
     | 
| 
      
 254 
     | 
    
         
            +
                      @pre_calc_sent = false
         
     | 
| 
      
 255 
     | 
    
         
            +
                      @pre_calc_last_size = nil
         
     | 
| 
      
 256 
     | 
    
         
            +
                      notify_progress(session_id: session_id, type: :session_start)
         
     | 
| 
      
 257 
     | 
    
         
            +
                    when 'NOTIFICATION' # sent from remote
         
     | 
| 
      
 258 
     | 
    
         
            +
                      if event.key?('PreTransferBytes')
         
     | 
| 
      
 259 
     | 
    
         
            +
                        @pre_calc_sent = true
         
     | 
| 
      
 260 
     | 
    
         
            +
                        notify_progress(session_id: session_id, type: :session_size, info: event['PreTransferBytes'])
         
     | 
| 
      
 261 
     | 
    
         
            +
                      end
         
     | 
| 
      
 262 
     | 
    
         
            +
                    when 'STATS' # during transfer
         
     | 
| 
      
 263 
     | 
    
         
            +
                      @pre_calc_last_size = event['TransferBytes'].to_i + event['StartByte'].to_i
         
     | 
| 
      
 264 
     | 
    
         
            +
                      notify_progress(session_id: session_id, type: :transfer, info: @pre_calc_last_size)
         
     | 
| 
      
 265 
     | 
    
         
            +
                    when 'DONE', 'ERROR' # end of session
         
     | 
| 
      
 266 
     | 
    
         
            +
                      total_size = event['TransferBytes'].to_i + event['StartByte'].to_i
         
     | 
| 
      
 267 
     | 
    
         
            +
                      if !@pre_calc_sent && !total_size.zero?
         
     | 
| 
      
 268 
     | 
    
         
            +
                        notify_progress(session_id: session_id, type: :session_size, info: total_size)
         
     | 
| 
      
 269 
     | 
    
         
            +
                      end
         
     | 
| 
      
 270 
     | 
    
         
            +
                      if @pre_calc_last_size != total_size
         
     | 
| 
      
 271 
     | 
    
         
            +
                        notify_progress(session_id: session_id, type: :transfer, info: total_size)
         
     | 
| 
      
 272 
     | 
    
         
            +
                      end
         
     | 
| 
      
 273 
     | 
    
         
            +
                      notify_progress(session_id: session_id, type: :end)
         
     | 
| 
      
 274 
     | 
    
         
            +
                      # cspell:disable
         
     | 
| 
      
 275 
     | 
    
         
            +
                    when 'SESSION'
         
     | 
| 
      
 276 
     | 
    
         
            +
                    when 'ARGSTOP'
         
     | 
| 
      
 277 
     | 
    
         
            +
                    when 'FILEERROR'
         
     | 
| 
      
 278 
     | 
    
         
            +
                    when 'STOP'
         
     | 
| 
      
 279 
     | 
    
         
            +
                      # cspell:enable
         
     | 
| 
      
 280 
     | 
    
         
            +
                      # stop event when one file is completed
         
     | 
| 
      
 281 
     | 
    
         
            +
                    else
         
     | 
| 
      
 282 
     | 
    
         
            +
                      Log.log.debug{"unknown event type #{event['Type']}"}
         
     | 
| 
      
 283 
     | 
    
         
            +
                    end
         
     | 
| 
       282 
284 
     | 
    
         
             
                  end
         
     | 
| 
       283 
285 
     | 
    
         | 
| 
       284 
286 
     | 
    
         
             
                  # @return [Hash] session information
         
     | 
| 
         @@ -289,7 +291,7 @@ module Aspera 
     | 
|
| 
       289 
291 
     | 
    
         
             
                    return matches.first
         
     | 
| 
       290 
292 
     | 
    
         
             
                  end
         
     | 
| 
       291 
293 
     | 
    
         | 
| 
       292 
     | 
    
         
            -
                  # send command  
     | 
| 
      
 294 
     | 
    
         
            +
                  # send command to management port of command (used in `asession)
         
     | 
| 
       293 
295 
     | 
    
         
             
                  # @param job_id identified transfer process
         
     | 
| 
       294 
296 
     | 
    
         
             
                  # @param session_index index of session (for multi session)
         
     | 
| 
       295 
297 
     | 
    
         
             
                  # @param data command on mgt port, examples:
         
     | 
| 
         @@ -309,47 +311,49 @@ module Aspera 
     | 
|
| 
       309 
311 
     | 
    
         
             
                  end
         
     | 
| 
       310 
312 
     | 
    
         
             
                  attr_reader :sessions
         
     | 
| 
       311 
313 
     | 
    
         | 
| 
       312 
     | 
    
         
            -
                  private
         
     | 
| 
       313 
     | 
    
         
            -
             
     | 
| 
       314 
314 
     | 
    
         
             
                  # options for initialize (same as values in option transfer_info)
         
     | 
| 
       315 
     | 
    
         
            -
                  # @param wss [Boolean] true: if both SSH and wss in ts: prefer wss
         
     | 
| 
       316 
315 
     | 
    
         
             
                  # @param ascp_args [Array] additional arguments to ascp
         
     | 
| 
       317 
     | 
    
         
            -
                  # @param  
     | 
| 
       318 
     | 
    
         
            -
                  # @param spawn_delay_sec [Integer] optional delay to start between sessions
         
     | 
| 
       319 
     | 
    
         
            -
                  # @param multi_incr_udp [Boolean] true: increment udp port for each session
         
     | 
| 
       320 
     | 
    
         
            -
                  # @param trusted_certs [Array] list of files with trusted certificates (stores)
         
     | 
| 
       321 
     | 
    
         
            -
                  # @param resume [Hash] resume policy
         
     | 
| 
      
 316 
     | 
    
         
            +
                  # @param wss [Boolean] true: if both SSH and wss in ts: prefer wss
         
     | 
| 
       322 
317 
     | 
    
         
             
                  # @param quiet [Boolean] by default no native ascp progress bar
         
     | 
| 
      
 318 
     | 
    
         
            +
                  # @param trusted_certs [Array,NilClass] list of files with trusted certificates (stores)
         
     | 
| 
      
 319 
     | 
    
         
            +
                  # @param client_ssh_key [String] client ssh key option (from CLIENT_SSH_KEY_OPTIONS)
         
     | 
| 
       323 
320 
     | 
    
         
             
                  # @param check_ignore_cb [Proc] callback with host,port
         
     | 
| 
      
 321 
     | 
    
         
            +
                  # @param spawn_timeout_sec [Integer] timeout for ascp spawn
         
     | 
| 
      
 322 
     | 
    
         
            +
                  # @param spawn_delay_sec [Integer] optional delay to start between sessions
         
     | 
| 
      
 323 
     | 
    
         
            +
                  # @param multi_incr_udp [Boolean,NilClass] true: increment udp port for each session
         
     | 
| 
      
 324 
     | 
    
         
            +
                  # @param resume [Hash,NilClass] resume policy
         
     | 
| 
       324 
325 
     | 
    
         
             
                  # @param management_cb [Proc] callback for management events
         
     | 
| 
       325 
326 
     | 
    
         
             
                  # @param base_options [Hash] other options for base class
         
     | 
| 
       326 
327 
     | 
    
         
             
                  def initialize(
         
     | 
| 
      
 328 
     | 
    
         
            +
                    ascp_args:         nil,
         
     | 
| 
       327 
329 
     | 
    
         
             
                    wss:               true,
         
     | 
| 
       328 
     | 
    
         
            -
                    ascp_args:         [],
         
     | 
| 
       329 
     | 
    
         
            -
                    spawn_timeout_sec: 2,
         
     | 
| 
       330 
     | 
    
         
            -
                    spawn_delay_sec:   2,
         
     | 
| 
       331 
     | 
    
         
            -
                    multi_incr_udp:    true,
         
     | 
| 
       332 
     | 
    
         
            -
                    trusted_certs:     [],
         
     | 
| 
       333 
     | 
    
         
            -
                    resume:            {},
         
     | 
| 
       334 
330 
     | 
    
         
             
                    quiet:             true,
         
     | 
| 
      
 331 
     | 
    
         
            +
                    trusted_certs:     nil,
         
     | 
| 
      
 332 
     | 
    
         
            +
                    client_ssh_key:    nil,
         
     | 
| 
       335 
333 
     | 
    
         
             
                    check_ignore_cb:   nil,
         
     | 
| 
      
 334 
     | 
    
         
            +
                    spawn_timeout_sec: 2,
         
     | 
| 
      
 335 
     | 
    
         
            +
                    spawn_delay_sec:   2,
         
     | 
| 
      
 336 
     | 
    
         
            +
                    multi_incr_udp:    nil,
         
     | 
| 
      
 337 
     | 
    
         
            +
                    resume:            nil,
         
     | 
| 
       336 
338 
     | 
    
         
             
                    management_cb:     nil,
         
     | 
| 
       337 
339 
     | 
    
         
             
                    **base_options
         
     | 
| 
       338 
340 
     | 
    
         
             
                  )
         
     | 
| 
       339 
341 
     | 
    
         
             
                    super(**base_options)
         
     | 
| 
      
 342 
     | 
    
         
            +
                    # special transfer parameters
         
     | 
| 
       340 
343 
     | 
    
         
             
                    @tr_opts = {
         
     | 
| 
       341 
344 
     | 
    
         
             
                      ascp_args:       ascp_args,
         
     | 
| 
       342 
345 
     | 
    
         
             
                      wss:             wss,
         
     | 
| 
       343 
346 
     | 
    
         
             
                      quiet:           quiet,
         
     | 
| 
       344 
347 
     | 
    
         
             
                      trusted_certs:   trusted_certs,
         
     | 
| 
      
 348 
     | 
    
         
            +
                      client_ssh_key:  client_ssh_key,
         
     | 
| 
       345 
349 
     | 
    
         
             
                      check_ignore_cb: check_ignore_cb
         
     | 
| 
       346 
350 
     | 
    
         
             
                    }
         
     | 
| 
       347 
351 
     | 
    
         
             
                    @spawn_timeout_sec = spawn_timeout_sec
         
     | 
| 
       348 
352 
     | 
    
         
             
                    @spawn_delay_sec = spawn_delay_sec
         
     | 
| 
       349 
     | 
    
         
            -
                     
     | 
| 
       350 
     | 
    
         
            -
                    @ 
     | 
| 
      
 353 
     | 
    
         
            +
                    # default is true on windows, false on other platforms
         
     | 
| 
      
 354 
     | 
    
         
            +
                    @multi_incr_udp = multi_incr_udp.nil? ? Environment.os.eql?(Environment::OS_WINDOWS) : multi_incr_udp
         
     | 
| 
       351 
355 
     | 
    
         
             
                    @management_cb = management_cb
         
     | 
| 
       352 
     | 
    
         
            -
                    @resume_policy = Resumer.new( 
     | 
| 
      
 356 
     | 
    
         
            +
                    @resume_policy = Resumer.new(resume.nil? ? {} : resume.symbolize_keys)
         
     | 
| 
       353 
357 
     | 
    
         
             
                    # all transfer jobs, key = SecureRandom.uuid, protected by mutex, cond var on change
         
     | 
| 
       354 
358 
     | 
    
         
             
                    @sessions = []
         
     | 
| 
       355 
359 
     | 
    
         
             
                    # mutex protects global data accessed by threads
         
     | 
| 
         @@ -365,7 +369,7 @@ module Aspera 
     | 
|
| 
       365 
369 
     | 
    
         
             
                      Log.log.debug{"ENTER (#{Thread.current[:name]})"}
         
     | 
| 
       366 
370 
     | 
    
         
             
                      # start transfer with selected resumer policy
         
     | 
| 
       367 
371 
     | 
    
         
             
                      @resume_policy.execute_with_resume do
         
     | 
| 
       368 
     | 
    
         
            -
                         
     | 
| 
      
 372 
     | 
    
         
            +
                        start_and_monitor_process(session: session, **session[:env_args])
         
     | 
| 
       369 
373 
     | 
    
         
             
                      end
         
     | 
| 
       370 
374 
     | 
    
         
             
                      Log.log.debug('transfer ok'.bg_green)
         
     | 
| 
       371 
375 
     | 
    
         
             
                    rescue StandardError => e
         
     | 
    
        data/lib/aspera/api/aoc.rb
    CHANGED
    
    | 
         @@ -439,7 +439,7 @@ module Aspera 
     | 
|
| 
       439 
439 
     | 
    
         
             
                  # create a package
         
     | 
| 
       440 
440 
     | 
    
         
             
                  # @param package_data [Hash] package creation (with extensions...)
         
     | 
| 
       441 
441 
     | 
    
         
             
                  # @param validate_meta [TrueClass,FalseClass] true to validate parameters locally
         
     | 
| 
       442 
     | 
    
         
            -
                  # @param new_user_option [Hash] options if an unknown user is specified
         
     | 
| 
      
 442 
     | 
    
         
            +
                  # @param new_user_option [Hash,NilClass] options if an unknown user is specified
         
     | 
| 
       443 
443 
     | 
    
         
             
                  # @return transfer spec, node api and package information
         
     | 
| 
       444 
444 
     | 
    
         
             
                  def create_package_simple(package_data, validate_meta, new_user_option)
         
     | 
| 
       445 
445 
     | 
    
         
             
                    update_package_metadata_for_api(package_data)
         
     | 
| 
         @@ -476,7 +476,7 @@ module Aspera 
     | 
|
| 
       476 
476 
     | 
    
         
             
                  # callback in Node (transfer_spec_gen4)
         
     | 
| 
       477 
477 
     | 
    
         
             
                  def add_ts_tags(transfer_spec:, app_info:)
         
     | 
| 
       478 
478 
     | 
    
         
             
                    # translate transfer direction to upload/download
         
     | 
| 
       479 
     | 
    
         
            -
                    transfer_type = Transfer::Spec. 
     | 
| 
      
 479 
     | 
    
         
            +
                    transfer_type = Transfer::Spec.direction_to_transfer_type(transfer_spec['direction'])
         
     | 
| 
       480 
480 
     | 
    
         
             
                    # Analytics tags
         
     | 
| 
       481 
481 
     | 
    
         
             
                    ################
         
     | 
| 
       482 
482 
     | 
    
         
             
                    transfer_spec.deep_merge!({
         
     | 
    
        data/lib/aspera/api/httpgw.rb
    CHANGED
    
    | 
         @@ -45,7 +45,7 @@ module Aspera 
     | 
|
| 
       45 
45 
     | 
    
         
             
                    else
         
     | 
| 
       46 
46 
     | 
    
         
             
                      @shared_info[:count][:sent_general] += 1
         
     | 
| 
       47 
47 
     | 
    
         
             
                    end
         
     | 
| 
       48 
     | 
    
         
            -
                    Log.log. 
     | 
| 
      
 48 
     | 
    
         
            +
                    Log.log.trace1 do
         
     | 
| 
       49 
49 
     | 
    
         
             
                      log_data = payload.dup
         
     | 
| 
       50 
50 
     | 
    
         
             
                      log_data[:data] = "[data #{log_data[:data].length} bytes]" if log_data.key?(:data)
         
     | 
| 
       51 
51 
     | 
    
         
             
                      "#{LOG_WS_SEND}json: #{msg_type}: #{JSON.generate(log_data)}"
         
     | 
| 
         @@ -55,7 +55,7 @@ module Aspera 
     | 
|
| 
       55 
55 
     | 
    
         | 
| 
       56 
56 
     | 
    
         
             
                  # send data on http gw web socket
         
     | 
| 
       57 
57 
     | 
    
         
             
                  def ws_send(ws_type:, data:)
         
     | 
| 
       58 
     | 
    
         
            -
                    Log.log. 
     | 
| 
      
 58 
     | 
    
         
            +
                    Log.log.trace1{"#{LOG_WS_SEND}sending: #{ws_type} (#{data&.length || 0} bytes)"}
         
     | 
| 
       59 
59 
     | 
    
         
             
                    @shared_info[:count][:sent_general] += 1 if ws_type.eql?(:binary)
         
     | 
| 
       60 
60 
     | 
    
         
             
                    frame_generator = ::WebSocket::Frame::Outgoing::Client.new(data: data, type: ws_type, version: @ws_handshake.version)
         
     | 
| 
       61 
61 
     | 
    
         
             
                    @ws_io.write(frame_generator.to_s)
         
     | 
| 
         @@ -68,13 +68,13 @@ module Aspera 
     | 
|
| 
       68 
68 
     | 
    
         
             
                            (((@shared_info[:count][:sent_general] - @shared_info[:count][:received_general]) > 1) ||
         
     | 
| 
       69 
69 
     | 
    
         
             
                              ((@shared_info[:count][:received_v2_delimiter] - @shared_info[:count][:sent_v2_delimiter]) > 1))
         
     | 
| 
       70 
70 
     | 
    
         
             
                          if !@shared_info[:cond_var].wait(@shared_info[:mutex], 2.0)
         
     | 
| 
       71 
     | 
    
         
            -
                            Log.log. 
     | 
| 
      
 71 
     | 
    
         
            +
                            Log.log.trace1{"#{LOG_WS_SEND}#{'timeout'.blue}: #{@shared_info[:count]}"}
         
     | 
| 
       72 
72 
     | 
    
         
             
                          end
         
     | 
| 
       73 
73 
     | 
    
         
             
                        end
         
     | 
| 
       74 
74 
     | 
    
         
             
                      end
         
     | 
| 
       75 
75 
     | 
    
         
             
                    end
         
     | 
| 
       76 
76 
     | 
    
         
             
                    raise @shared_info[:read_exception] unless @shared_info[:read_exception].nil?
         
     | 
| 
       77 
     | 
    
         
            -
                    Log.log. 
     | 
| 
      
 77 
     | 
    
         
            +
                    Log.log.trace2{"#{LOG_WS_SEND}counts: #{@shared_info[:count]}"}
         
     | 
| 
       78 
78 
     | 
    
         
             
                  end
         
     | 
| 
       79 
79 
     | 
    
         | 
| 
       80 
80 
     | 
    
         
             
                  # message processing for read thread
         
     | 
| 
         @@ -114,12 +114,12 @@ module Aspera 
     | 
|
| 
       114 
114 
     | 
    
         
             
                        # ready byte by byte until frame is ready
         
     | 
| 
       115 
115 
     | 
    
         
             
                        # blocking read
         
     | 
| 
       116 
116 
     | 
    
         
             
                        byte = @ws_io.read(1)
         
     | 
| 
       117 
     | 
    
         
            -
                        Log.log. 
     | 
| 
      
 117 
     | 
    
         
            +
                        Log.log.trace2{"#{LOG_WS_RECV}read: #{byte} (#{byte.class}) eof=#{@ws_io.eof?}"}
         
     | 
| 
       118 
118 
     | 
    
         
             
                        frame_parser << byte
         
     | 
| 
       119 
119 
     | 
    
         
             
                        frame_ok = frame_parser.next
         
     | 
| 
       120 
120 
     | 
    
         
             
                        next if frame_ok.nil?
         
     | 
| 
       121 
121 
     | 
    
         
             
                        process_received_message(frame_ok.data.to_s)
         
     | 
| 
       122 
     | 
    
         
            -
                        Log.log. 
     | 
| 
      
 122 
     | 
    
         
            +
                        Log.log.trace2{"#{LOG_WS_RECV}counts: #{@shared_info[:count]}"}
         
     | 
| 
       123 
123 
     | 
    
         
             
                      rescue => e
         
     | 
| 
       124 
124 
     | 
    
         
             
                        Log.log.debug{"#{LOG_WS_RECV}Exception: #{e}"}
         
     | 
| 
       125 
125 
     | 
    
         
             
                        @shared_info[:mutex].synchronize do
         
     | 
| 
         @@ -139,34 +139,12 @@ module Aspera 
     | 
|
| 
       139 
139 
     | 
    
         
             
                    # identify this session uniquely
         
     | 
| 
       140 
140 
     | 
    
         
             
                    session_id = SecureRandom.uuid
         
     | 
| 
       141 
141 
     | 
    
         
             
                    @notify_cb&.call(session_id: nil, type: :pre_start, info: 'starting')
         
     | 
| 
       142 
     | 
    
         
            -
                    #  
     | 
| 
       143 
     | 
    
         
            -
                     
     | 
| 
       144 
     | 
    
         
            -
                    #  
     | 
| 
       145 
     | 
    
         
            -
                     
     | 
| 
       146 
     | 
    
         
            -
                     
     | 
| 
       147 
     | 
    
         
            -
                     
     | 
| 
       148 
     | 
    
         
            -
                    # source root is ignored by GW, used only here
         
     | 
| 
       149 
     | 
    
         
            -
                    transfer_spec.delete('source_root')
         
     | 
| 
       150 
     | 
    
         
            -
                    # compute total size of files to upload (for progress)
         
     | 
| 
       151 
     | 
    
         
            -
                    # modify transfer spec to be suitable for GW
         
     | 
| 
       152 
     | 
    
         
            -
                    transfer_spec['paths'].each do |item|
         
     | 
| 
       153 
     | 
    
         
            -
                      # save actual file location to be able read contents later
         
     | 
| 
       154 
     | 
    
         
            -
                      file_to_add = Transfer::FauxFile.open(item['source'])
         
     | 
| 
       155 
     | 
    
         
            -
                      if file_to_add
         
     | 
| 
       156 
     | 
    
         
            -
                        item['source'] = file_to_add.path
         
     | 
| 
       157 
     | 
    
         
            -
                        item['file_size'] = file_to_add.size
         
     | 
| 
       158 
     | 
    
         
            -
                      else
         
     | 
| 
       159 
     | 
    
         
            -
                        file_to_add = item['source']
         
     | 
| 
       160 
     | 
    
         
            -
                        # add source root if needed
         
     | 
| 
       161 
     | 
    
         
            -
                        file_to_add = File.join(source_root, file_to_add) unless source_root.nil?
         
     | 
| 
       162 
     | 
    
         
            -
                        # GW expects a simple file name in 'source' but if user wants to change the name, we take it
         
     | 
| 
       163 
     | 
    
         
            -
                        item['source'] = File.basename(item['destination'].nil? ? item['source'] : item['destination'])
         
     | 
| 
       164 
     | 
    
         
            -
                        item['file_size'] = File.size(file_to_add)
         
     | 
| 
       165 
     | 
    
         
            -
                      end
         
     | 
| 
       166 
     | 
    
         
            -
                      # save so that we can actually read the file later
         
     | 
| 
       167 
     | 
    
         
            -
                      files_to_read.push(file_to_add)
         
     | 
| 
       168 
     | 
    
         
            -
                      total_bytes_to_transfer += item['file_size']
         
     | 
| 
       169 
     | 
    
         
            -
                    end
         
     | 
| 
      
 142 
     | 
    
         
            +
                    # process files to send, modify `paths` in transfer_spec
         
     | 
| 
      
 143 
     | 
    
         
            +
                    files_to_send = process_upload_list(transfer_spec)
         
     | 
| 
      
 144 
     | 
    
         
            +
                    # total size of all files is last element
         
     | 
| 
      
 145 
     | 
    
         
            +
                    total_bytes_to_transfer = files_to_send.pop
         
     | 
| 
      
 146 
     | 
    
         
            +
                    Log.log.trace1{Log.dump(:modified_tspec, transfer_spec)}
         
     | 
| 
      
 147 
     | 
    
         
            +
                    Log.log.trace1{Log.dump(:files_to_send, files_to_send)}
         
     | 
| 
       170 
148 
     | 
    
         
             
                    # TODO: check that this is available in endpoints: @api_info['endpoints']
         
     | 
| 
       171 
149 
     | 
    
         
             
                    upload_url = File.join(@gw_root_url, @upload_version, 'upload')
         
     | 
| 
       172 
150 
     | 
    
         
             
                    @notify_cb&.call(session_id: nil, type: :pre_start, info: 'connecting wss')
         
     | 
| 
         @@ -180,11 +158,6 @@ module Aspera 
     | 
|
| 
       180 
158 
     | 
    
         
             
                    @ws_handshake << @ws_io.readuntil("\r\n\r\n")
         
     | 
| 
       181 
159 
     | 
    
         
             
                    Aspera.assert(@ws_handshake.finished?){'Error in websocket handshake'}
         
     | 
| 
       182 
160 
     | 
    
         
             
                    Log.log.debug{"#{LOG_WS_SEND}handshake success"}
         
     | 
| 
       183 
     | 
    
         
            -
                    # start read thread after handshake
         
     | 
| 
       184 
     | 
    
         
            -
                    @ws_read_thread = Thread.new {process_read_thread}
         
     | 
| 
       185 
     | 
    
         
            -
                    @notify_cb&.call(session_id: session_id, type: :session_start)
         
     | 
| 
       186 
     | 
    
         
            -
                    @notify_cb&.call(session_id: session_id, type: :session_size, info: total_bytes_to_transfer)
         
     | 
| 
       187 
     | 
    
         
            -
                    sleep(1)
         
     | 
| 
       188 
161 
     | 
    
         
             
                    # data shared between main thread and read thread
         
     | 
| 
       189 
162 
     | 
    
         
             
                    @shared_info = {
         
     | 
| 
       190 
163 
     | 
    
         
             
                      read_exception: nil, # error message if any in callback
         
     | 
| 
         @@ -197,34 +170,34 @@ module Aspera 
     | 
|
| 
       197 
170 
     | 
    
         
             
                      mutex:          Mutex.new,
         
     | 
| 
       198 
171 
     | 
    
         
             
                      cond_var:       ConditionVariable.new
         
     | 
| 
       199 
172 
     | 
    
         
             
                    }
         
     | 
| 
      
 173 
     | 
    
         
            +
                    # start read thread after handshake
         
     | 
| 
      
 174 
     | 
    
         
            +
                    @ws_read_thread = Thread.new {process_read_thread}
         
     | 
| 
      
 175 
     | 
    
         
            +
                    @notify_cb&.call(session_id: session_id, type: :session_start)
         
     | 
| 
      
 176 
     | 
    
         
            +
                    @notify_cb&.call(session_id: session_id, type: :session_size, info: total_bytes_to_transfer)
         
     | 
| 
      
 177 
     | 
    
         
            +
                    sleep(1)
         
     | 
| 
       200 
178 
     | 
    
         
             
                    # notify progress bar
         
     | 
| 
       201 
179 
     | 
    
         
             
                    @notify_cb&.call(type: :session_size, session_id: session_id, info: total_bytes_to_transfer)
         
     | 
| 
       202 
180 
     | 
    
         
             
                    # first step send transfer spec
         
     | 
| 
       203 
     | 
    
         
            -
                    Log.log.debug{Log.dump(:ws_spec, transfer_spec)}
         
     | 
| 
       204 
181 
     | 
    
         
             
                    ws_snd_json(MSG_SEND_TRANSFER_SPEC, transfer_spec)
         
     | 
| 
       205 
182 
     | 
    
         
             
                    # current file index
         
     | 
| 
       206 
183 
     | 
    
         
             
                    file_index = 0
         
     | 
| 
       207 
184 
     | 
    
         
             
                    # aggregate size sent
         
     | 
| 
       208 
185 
     | 
    
         
             
                    session_sent_bytes = 0
         
     | 
| 
       209 
186 
     | 
    
         
             
                    # process each file
         
     | 
| 
       210 
     | 
    
         
            -
                     
     | 
| 
      
 187 
     | 
    
         
            +
                    files_to_send.each do |file_to_send|
         
     | 
| 
      
 188 
     | 
    
         
            +
                      last_slice = (file_to_send[:size] - 1) / @upload_chunk_size
         
     | 
| 
       211 
189 
     | 
    
         
             
                      slice_info = {
         
     | 
| 
       212 
     | 
    
         
            -
                        name: 
     | 
| 
      
 190 
     | 
    
         
            +
                        name:         file_to_send[:name],
         
     | 
| 
       213 
191 
     | 
    
         
             
                        # TODO: get mime type?
         
     | 
| 
       214 
     | 
    
         
            -
                        type: 
     | 
| 
       215 
     | 
    
         
            -
                        size: 
     | 
| 
       216 
     | 
    
         
            -
                        slice: 
     | 
| 
      
 192 
     | 
    
         
            +
                        type:         'application/octet-stream',
         
     | 
| 
      
 193 
     | 
    
         
            +
                        size:         file_to_send[:size],
         
     | 
| 
      
 194 
     | 
    
         
            +
                        slice:        0, # current slice index
         
     | 
| 
       217 
195 
     | 
    
         
             
                        # index of last slice (i.e number of slices - 1)
         
     | 
| 
       218 
     | 
    
         
            -
                         
     | 
| 
       219 
     | 
    
         
            -
                        fileIndex: 
     | 
| 
      
 196 
     | 
    
         
            +
                        total_slices: last_slice + 1,
         
     | 
| 
      
 197 
     | 
    
         
            +
                        fileIndex:    file_index
         
     | 
| 
       220 
198 
     | 
    
         
             
                      }
         
     | 
| 
       221 
     | 
    
         
            -
                      file =  
     | 
| 
       222 
     | 
    
         
            -
                       
     | 
| 
       223 
     | 
    
         
            -
                        slice_info[:name] = file.path
         
     | 
| 
       224 
     | 
    
         
            -
                      else
         
     | 
| 
       225 
     | 
    
         
            -
                        file = File.open(file)
         
     | 
| 
       226 
     | 
    
         
            -
                        slice_info[:name] = File.basename(item[item['destination'].nil? ? 'source' : 'destination'])
         
     | 
| 
       227 
     | 
    
         
            -
                      end
         
     | 
| 
      
 199 
     | 
    
         
            +
                      file = file_to_send[:file]
         
     | 
| 
      
 200 
     | 
    
         
            +
                      file = File.open(file) unless file.is_a?(Transfer::FauxFile)
         
     | 
| 
       228 
201 
     | 
    
         
             
                      begin
         
     | 
| 
       229 
202 
     | 
    
         
             
                        until file.eof?
         
     | 
| 
       230 
203 
     | 
    
         
             
                          slice_bin_data = file.read(@upload_chunk_size)
         
     | 
| 
         @@ -238,9 +211,9 @@ module Aspera 
     | 
|
| 
       238 
211 
     | 
    
         
             
                              # send once, before data, at beginning
         
     | 
| 
       239 
212 
     | 
    
         
             
                              ws_snd_json(MSG_SEND_SLICE_UPLOAD, slice_info) if slice_info[:slice].eql?(0)
         
     | 
| 
       240 
213 
     | 
    
         
             
                              ws_send(ws_type: :binary, data: slice_bin_data)
         
     | 
| 
       241 
     | 
    
         
            -
                              Log.log. 
     | 
| 
      
 214 
     | 
    
         
            +
                              Log.log.trace1{"#{LOG_WS_SEND}buffer: file: #{file_index}, slice: #{slice_info[:slice]}/#{last_slice}"}
         
     | 
| 
       242 
215 
     | 
    
         
             
                              # send once, after data, at end
         
     | 
| 
       243 
     | 
    
         
            -
                              ws_snd_json(MSG_SEND_SLICE_UPLOAD, slice_info) if slice_info[:slice].eql?( 
     | 
| 
      
 216 
     | 
    
         
            +
                              ws_snd_json(MSG_SEND_SLICE_UPLOAD, slice_info) if slice_info[:slice].eql?(last_slice)
         
     | 
| 
       244 
217 
     | 
    
         
             
                            end
         
     | 
| 
       245 
218 
     | 
    
         
             
                          rescue Errno::EPIPE => e
         
     | 
| 
       246 
219 
     | 
    
         
             
                            raise @shared_info[:read_exception] unless @shared_info[:read_exception].nil?
         
     | 
| 
         @@ -337,6 +310,68 @@ module Aspera 
     | 
|
| 
       337 
310 
     | 
    
         
             
                      end
         
     | 
| 
       338 
311 
     | 
    
         
             
                    end
         
     | 
| 
       339 
312 
     | 
    
         
             
                  end
         
     | 
| 
      
 313 
     | 
    
         
            +
             
     | 
| 
      
 314 
     | 
    
         
            +
                  private
         
     | 
| 
      
 315 
     | 
    
         
            +
             
     | 
| 
      
 316 
     | 
    
         
            +
                  # compute total size of files to upload (for progress)
         
     | 
| 
      
 317 
     | 
    
         
            +
                  # modify transfer spec to be suitable for HTTPGW
         
     | 
| 
      
 318 
     | 
    
         
            +
                  # @param transfer_spec [Hash] transfer specification
         
     | 
| 
      
 319 
     | 
    
         
            +
                  # @return [Array] info on files to send
         
     | 
| 
      
 320 
     | 
    
         
            +
                  def process_upload_list(transfer_spec)
         
     | 
| 
      
 321 
     | 
    
         
            +
                    total_bytes_to_transfer = 0
         
     | 
| 
      
 322 
     | 
    
         
            +
                    source_prefix = transfer_spec.key?('source_root') && !transfer_spec['source_root'].empty? ? transfer_spec['source_root'] + '/' : ''
         
     | 
| 
      
 323 
     | 
    
         
            +
                    files_to_send = []
         
     | 
| 
      
 324 
     | 
    
         
            +
                    transfer_spec['paths'].each do |one_path|
         
     | 
| 
      
 325 
     | 
    
         
            +
                      source_path = source_prefix + one_path['source']
         
     | 
| 
      
 326 
     | 
    
         
            +
                      faux_file = Transfer::FauxFile.create(source_path)
         
     | 
| 
      
 327 
     | 
    
         
            +
                      if faux_file
         
     | 
| 
      
 328 
     | 
    
         
            +
                        total_bytes_to_transfer += faux_file.size
         
     | 
| 
      
 329 
     | 
    
         
            +
                        files_to_send.push({
         
     | 
| 
      
 330 
     | 
    
         
            +
                          file: faux_file,
         
     | 
| 
      
 331 
     | 
    
         
            +
                          name: faux_file.path,
         
     | 
| 
      
 332 
     | 
    
         
            +
                          size: faux_file.size
         
     | 
| 
      
 333 
     | 
    
         
            +
                        })
         
     | 
| 
      
 334 
     | 
    
         
            +
                      elsif File.file?(source_path)
         
     | 
| 
      
 335 
     | 
    
         
            +
                        # regular file
         
     | 
| 
      
 336 
     | 
    
         
            +
                        file_size = File.size(source_path)
         
     | 
| 
      
 337 
     | 
    
         
            +
                        total_bytes_to_transfer += file_size
         
     | 
| 
      
 338 
     | 
    
         
            +
                        files_to_send.push({
         
     | 
| 
      
 339 
     | 
    
         
            +
                          file: source_path,
         
     | 
| 
      
 340 
     | 
    
         
            +
                          # GW expects a simple file name in 'source' but if user wants to change the name, we take it
         
     | 
| 
      
 341 
     | 
    
         
            +
                          name: File.basename(one_path['destination'].nil? ? source_path : one_path['destination']),
         
     | 
| 
      
 342 
     | 
    
         
            +
                          size: file_size
         
     | 
| 
      
 343 
     | 
    
         
            +
                        })
         
     | 
| 
      
 344 
     | 
    
         
            +
                      elsif File.directory?(source_path)
         
     | 
| 
      
 345 
     | 
    
         
            +
                        folders_to_process = [source_path]
         
     | 
| 
      
 346 
     | 
    
         
            +
                        until folders_to_process.empty?
         
     | 
| 
      
 347 
     | 
    
         
            +
                          folder = folders_to_process.shift
         
     | 
| 
      
 348 
     | 
    
         
            +
                          # read all entries
         
     | 
| 
      
 349 
     | 
    
         
            +
                          Dir.entries(folder).each do |entry|
         
     | 
| 
      
 350 
     | 
    
         
            +
                            next if entry.eql?('.') || entry.eql?('..')
         
     | 
| 
      
 351 
     | 
    
         
            +
                            entry_path = File.join(folder, entry)
         
     | 
| 
      
 352 
     | 
    
         
            +
                            if File.directory?(entry_path)
         
     | 
| 
      
 353 
     | 
    
         
            +
                              folders_to_process.push(entry_path)
         
     | 
| 
      
 354 
     | 
    
         
            +
                            elsif File.file?(entry_path)
         
     | 
| 
      
 355 
     | 
    
         
            +
                              file_size = File.size(entry_path)
         
     | 
| 
      
 356 
     | 
    
         
            +
                              total_bytes_to_transfer += file_size
         
     | 
| 
      
 357 
     | 
    
         
            +
                              files_to_send.push({
         
     | 
| 
      
 358 
     | 
    
         
            +
                                file: entry_path,
         
     | 
| 
      
 359 
     | 
    
         
            +
                                name: entry_path,
         
     | 
| 
      
 360 
     | 
    
         
            +
                                size: file_size
         
     | 
| 
      
 361 
     | 
    
         
            +
                              })
         
     | 
| 
      
 362 
     | 
    
         
            +
                            else
         
     | 
| 
      
 363 
     | 
    
         
            +
                              Log.log.warn{"Ignoring non file/directory: #{entry_path}"}
         
     | 
| 
      
 364 
     | 
    
         
            +
                            end
         
     | 
| 
      
 365 
     | 
    
         
            +
                          end
         
     | 
| 
      
 366 
     | 
    
         
            +
                        end
         
     | 
| 
      
 367 
     | 
    
         
            +
                      else
         
     | 
| 
      
 368 
     | 
    
         
            +
                        raise "File not found: #{source_path}"
         
     | 
| 
      
 369 
     | 
    
         
            +
                      end
         
     | 
| 
      
 370 
     | 
    
         
            +
                    end
         
     | 
| 
      
 371 
     | 
    
         
            +
                    transfer_spec['paths'] = files_to_send.map{|i|{'source' => i[:name]}}
         
     | 
| 
      
 372 
     | 
    
         
            +
                    files_to_send.push(total_bytes_to_transfer)
         
     | 
| 
      
 373 
     | 
    
         
            +
                    return files_to_send
         
     | 
| 
      
 374 
     | 
    
         
            +
                  end
         
     | 
| 
       340 
375 
     | 
    
         
             
                end
         
     | 
| 
       341 
376 
     | 
    
         
             
              end
         
     | 
| 
       342 
377 
     | 
    
         
             
            end
         
     |