Class: MrMurano::Webservice::File

Inherits:
WebserviceBase show all
Defined in:
lib/MrMurano/Webservice-File.rb

Overview

Static File content

Defined Under Namespace

Classes: FileItem

Constant Summary

Constants included from Verbose

Verbose::TABULARIZE_DATA_FORMAT_ERROR

Constants included from SolutionId

SolutionId::INVALID_API_ID, SolutionId::UNEXPECTED_TYPE_OR_ERROR_MSG

Constants included from AccountBase

AccountBase::LOGIN_ADVICE, AccountBase::LOGIN_NOTICE

Instance Attribute Summary

Attributes included from SolutionId

#api_id, #sid, #valid_api_id

Class Method Summary collapse

Instance Method Summary collapse

Methods inherited from WebserviceBase

#endpoint

Methods included from SyncUpDown

#config_vars_decode, #config_vars_encode, #debug_print_localitems, #diff_download, #diff_item_write, #download, #ignore?, #ignoring, #localitems, #locallist, #locallist_add_item, #locallist_complain_missing, #locallist_mark_seen, #location, #match, #remove_or_clear, #removelocal, #resolve_config_var_usage!, #resurrect_undeletables, #searchFor, #syncdown_after, #syncdown_before, #syncup_after, #syncup_before, #tolocalpath, #update_mtime

Methods included from Verbose

ask_yes_no, #ask_yes_no, #assert, assert, cmd_confirm_delete!, #cmd_confirm_delete!, debug, #debug, dump_file_json, dump_file_plain, dump_file_yaml, #dump_output_file, #error, error, #error_file_format!, fancy_ticks, #fancy_ticks, #load_file_json, #load_file_plain, #load_file_yaml, #load_input_file, outf, #outf, #outformat_engine, #pluralize?, pluralize?, #prepare_hash_csv, #read_hashf!, #tabularize, tabularize, verbose, #verbose, warning, #warning, #whirly_interject, whirly_interject, #whirly_linger, whirly_linger, #whirly_msg, whirly_msg, #whirly_pause, whirly_pause, #whirly_start, whirly_start, #whirly_stop, whirly_stop, #whirly_unpause, whirly_unpause

Methods included from SyncCore

#debug_selected, #dodiff, #dodiff_build_cmd, #dodiff_cull_tempfile_paths, #dodiff_do_diff, #dodiff_download_remote, #dodiff_flexible, #dodiff_header_aware, #dodiff_local_to_tempfile, #dodiff_prepare_local_and_diff, #dodiff_resolve_localname, #dodiff_tempfile_paths, #filter_solution, #init_mods_and_chgs_arrs, #item_dirty_set_status, #item_local_there_merged, #item_merged_diff_status, #item_merged_set_status, #item_select_selected!, #items_classify_and_find_duplicates, #items_cull_clashes!, #items_lists, #items_log_duplicates, #items_log_duplicates_there_local, #items_mods_and_chgs!, #items_new_and_old!, #select_selected!, #sort_by_name, #status, #sync_update_progress, #syncable_validate_api_id, #syncdown, #syncdown_item, #syncup, #syncup_item

Methods included from SyncAllowed

#download_item_allowed, #remove_item_allowed, #removelocal_item_allowed, #sync_item_allowed, #upload_item_allowed

Methods included from SolutionId

#affirm_valid, #api_id?, #endpoint, #init_api_id!, #valid_api_id?

Methods included from AccountBase

#add_headers, #ask_for_password!, #ask_for_user!, #cfg_clear_user_and_business, #credentials_reset, #get, #invalidate_token, #login_info, #logout, #must_prompt_if_logged_off!, #token, #token_reset, #verify_cfg_auth!, #verify_cfg_auth_persist, #verify_cfg_auth_scheme!, #verify_cfg_auth_ttl, #verify_set, warn_configfile_env_maybe

Methods included from Http

#add_headers, curldebug_after, curldebug_elapsed, curldebug_log, #delete, #endpoint, #get, #host, #http, #http_reset, #isJSON, #json_opts, #patch, #post, #postf, #put, #showHttpError, #user, #workit, #workit_response

Constructor Details

#initializeFile

Returns a new instance of File.



54
55
56
57
58
59
# File 'lib/MrMurano/Webservice-File.rb', line 54

def initialize
  super
  @uriparts << :file
  @itemkey = :path
  @project_section = :assets
end

Class Method Details

.descriptionObject



61
62
63
64
65
66
# File 'lib/MrMurano/Webservice-File.rb', line 61

def self.description
  # 2017-08-07: The UI calls these ASSETS in the tab
  # (and refers to "Static file hosting").
  #%(Static File)
  %(Asset)
end

Instance Method Details

#curldebug(request) ⇒ Object



113
114
115
116
117
# File 'lib/MrMurano/Webservice-File.rb', line 113

def curldebug(request)
  # The upload will get printed out inside of upload.
  # Because we don't have the correct info here.
  super(request) if request.method != 'PUT'
end

#docmp(item_a, item_b) ⇒ Object

Compare items.

Parameters:



296
297
298
299
300
301
302
# File 'lib/MrMurano/Webservice-File.rb', line 296

def docmp(item_a, item_b)
  (
    item_a[:mime_type] != item_b[:mime_type] ||
    item_a[:checksum] != item_b[:checksum] ||
    item_a[:size] != item_b[:size]
  )
end

#dump_upload_curl(request, local) ⇒ Object



172
173
174
175
176
177
178
179
180
181
# File 'lib/MrMurano/Webservice-File.rb', line 172

def dump_upload_curl(request, local)
  a = []
  a << %(curl -s -H 'Authorization: #{request['authorization']}')
  a << %(-H 'User-Agent: #{request['User-Agent']}')
  a << %(-X #{request.method})
  a << %('#{request.uri}')
  a << %(-F file=@#{local})
  ccmd = a.join(' ')
  MrMurano::Http.curldebug_log(ccmd, stamp_it: true)
end

#ensure_nonempty_upload(local) ⇒ Object



119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
# File 'lib/MrMurano/Webservice-File.rb', line 119

def ensure_nonempty_upload(local)
  # FIXME/MUR-6479/MUR-6477: (lb): Remove this function when backend fixed.
  #
  #     {"statusCode":400,
  #      "message":"The \"data\" argument must be one of type
  #        string, TypedArray, or DataView",
  #      "code":"BadRequestError"
  #     }
  local = Pathname.new(local) unless local.is_a? Pathname
  if !local.size.zero?
    yield local
  else
    # Cannot use Tempfile.open because it make filename unique, e.g.,
    #   Tempfile.open(local.basename.to_s) { |tmpf| puts tmpf.path }
    #   # /tmp/index.html20180413-15436-5i0s0u
    Dir.mktmpdir do |tmpdir|
      # NOTE: Because our class is named File, we have to descope ::File.
      ::File.open(::File.join(tmpdir, local.basename.to_s), 'w') do |tmpf|
        tmpf << "\n"
        tmpf.flush
        local = Pathname.new(tmpf.path)
        yield local
      end
    end
  end
end

#fetch(path, _untainted = false, &block) ⇒ Object

Get one item of the static content.



82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
# File 'lib/MrMurano/Webservice-File.rb', line 82

def fetch(path, _untainted=false, &block)
  path = path[1..-1] if path[0] == '/'
  path = '/' + URI.encode_www_form_component(path)
  get(path) do |request, http|
    http.request(request) do |resp|
      case resp
      when Net::HTTPSuccess
        if block_given?
          resp.read_body(&block)
        else
          resp.read_body do |chunk|
            $stdout.write chunk
          end
        end
      else
        showHttpError(request, resp)
      end
    end
    nil
  end
end

#listArray<FileItem>

Get a list of all of the static content

Returns:

  • (Array<FileItem>)

    List of items on server



71
72
73
74
75
76
77
78
# File 'lib/MrMurano/Webservice-File.rb', line 71

def list
  ret = get
  return [] unless ret.is_a?(Array)
  ret.map { |i| FileItem.new(i) }
  # MAYBE/2017-08-17:
  #   ret.map! { |i| FileItem.new(i) }
  #   sort_by_name(ret)
end

#prepare_upload_form(local, remote) ⇒ Object



157
158
159
160
161
162
163
164
165
166
167
168
169
170
# File 'lib/MrMurano/Webservice-File.rb', line 157

def prepare_upload_form(local, remote)
  # http://stackoverflow.com/questions/184178/
  #   ruby-how-to-post-a-file-via-http-as-multipart-form-data
  #
  # Look at: https://github.com/httprb/http
  # If it works well, consider porting over to it.
  #
  # Or just: https://github.com/httprb/form_data.rb ?
  #
  # Most of these pull into ram.  So maybe just go with that. Would guess that
  # truely large static content is rare, and we can optimize/fix that later.
  file = HTTP::FormData::File.new(local.to_s, content_type: remote[:mime_type])
  HTTP::FormData.create(file: file)
end

#prepare_upload_req(remote) ⇒ Object



146
147
148
149
150
151
152
153
154
155
# File 'lib/MrMurano/Webservice-File.rb', line 146

def prepare_upload_req(remote)
  path = remote[:path]
  path = path[1..-1] if path[0] == '/'
  # JANKIE: The endpoint ends in /file, and this makes it /fileupload/, ha!
  uri = endpoint('upload/' + URI.encode_www_form_component(path))
  # kludge past for a bit.
  #`curl -s -H 'Authorization: token #{@token}' '#{uri.to_s}'    #  -F file=@#{local.to_s}`
  Net::HTTP::Put.new(uri)
end

#remote_item_checksum_md5(path) ⇒ Object



283
284
285
# File 'lib/MrMurano/Webservice-File.rb', line 283

def remote_item_checksum_md5(path)
  Digest::MD5.file(path.to_s)
end

#remote_item_checksum_sha1(path) ⇒ Object



269
270
271
272
273
274
275
276
277
278
279
280
281
# File 'lib/MrMurano/Webservice-File.rb', line 269

def remote_item_checksum_sha1(path)
  # NOTE: It does not actually take the SHA1 of the file. It first
  #  converts the file to hex, then takes the SHA1 of that string.
  xsum = Digest::SHA1.new
  path.open('rb:ASCII-8BIT') do |io|
    # rubocop:disable Lint/AssignmentInCondition
    #  "Assignment in condition - you probably meant to use ==."
    while chunk = io.read(1_048_576)
      xsum << Digest.hexencode(chunk)
    end
  end
  xsum
end

#remove(path) ⇒ Object

Delete a file

Parameters:

  • path (String)

    The identifying key for this item



107
108
109
110
111
# File 'lib/MrMurano/Webservice-File.rb', line 107

def remove(path)
  path = path[1..-1] if path[0] == '/'
  return unless remove_item_allowed(path)
  delete('/' + URI.encode_www_form_component(path))
end

#synckey(item) ⇒ Object

Returns The object to use a comparison key.

Parameters:

  • item (FileItem)

    The item to get a key from

Returns:

  • (Object)

    The object to use a comparison key



289
290
291
# File 'lib/MrMurano/Webservice-File.rb', line 289

def synckey(item)
  item[:path]
end

#to_remote_items(from, path) ⇒ Item

Returns hash of the details for the remote item for this path.

Parameters:

  • root (Pathname, String)

    Root path for this resource type from config files

  • path (Pathname, String)

    Path to local item

Returns:

  • (Item)

    hash of the details for the remote item for this path



230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
# File 'lib/MrMurano/Webservice-File.rb', line 230

def to_remote_items(from, path)
  items = super(from, path)

  name = items[0][:name]
  name = '/' if name == $cfg['files.default_page']
  name = "/#{name}" unless name.chars.first == '/'

  mime = MIME::Types.type_for(path.to_s)[0]
  mime ||= MIME::Types['application/octet-stream'][0]

  file_size = path.size

  # FIXME/2018-04-13: (lb): When did platform switch from SHA1 to MD5?
  #   Leaving this dead code block as a reminder to verify the new
  #   behavior is the intended behavior.
  xsum_is_sha1 = false
  if xsum_is_sha1
    digobj = remote_item_checksum_sha1(path)
  elsif file_size.zero?
    # MD5 of the empty string is not empty (it's d41d8cd98f00b204e9800998ecf8427e)
    # but BizAPI (or Pegasus; whomever) sends an empty string for an empty file.
    checksum = ''
    digobj = nil
  else
    digobj = remote_item_checksum_md5(path)
  end

  checksum = digobj.hexdigest unless digobj.nil?
  debug "Checking #{name} (#{mime.simplified} #{checksum})"

  item = FileItem.new(
    path: name,
    mime_type: mime.simplified,
    checksum: checksum,
    size: file_size,
  )
  [item]
end

#tolocalname(item, key) ⇒ Object

Parameters:

  • item (Item)

    listing details for the item.

  • itemkey (Symbol)

    Key for look up.



220
221
222
223
224
225
# File 'lib/MrMurano/Webservice-File.rb', line 220

def tolocalname(item, key)
  name = item[key]
  # If service says '/', assume filename is e.g., 'index.html'.
  name = $cfg['files.default_page'] if name == '/'
  name
end

#try_upload(local, remote) ⇒ Object



201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
# File 'lib/MrMurano/Webservice-File.rb', line 201

def try_upload(local, remote)
  req = prepare_upload_req(remote)
  form = prepare_upload_form(local, remote)
  add_headers(req)

  return [nil, nil] unless upload_item_allowed(remote[@itemkey])

  workit(req) do |request, http|
    request.content_type = form.content_type
    request.content_length = form.content_length
    request.body = form.to_s
    dump_upload_curl(request, local) if $cfg['tool.curldebug']
    response = http.request(request)
    [request, response]
  end
end

#upload(local, remote, _modify) ⇒ Object

Upload a file

Parameters:

  • src (Pathname)

    Full path of where to upload from

  • item (Hash)

    The item details to upload

  • modify (Boolean)

    True if item exists and this is changing it



188
189
190
191
192
193
194
195
196
197
198
199
# File 'lib/MrMurano/Webservice-File.rb', line 188

def upload(local, remote, _modify)
  local = Pathname.new(local) unless local.is_a? Pathname
  request, response = try_upload(local, remote)
  return if response.nil?
  if response.is_a?(Net::HTTPBadRequest) && local.size.zero?
    ensure_nonempty_upload(local) do |nonempty_local|
      request, response = try_upload(nonempty_local, remote)
    end
  end
  return if response.is_a?(Net::HTTPSuccess)
  showHttpError(request, response)
end