# encoding: utf-8 require 'restclient' require 'open-uri' module Upyun class Rest include Utils attr_reader :options def initialize(bucket, operator, password, options={timeout: 60}, endpoint=Upyun::ED_AUTO) @bucket = bucket @operator = operator @password = md5(password) @options = options @endpoint = endpoint end def put(path, file, headers={}) headers = headers.merge({'mkdir' => true}) unless headers.key?('mkdir') body = file.respond_to?(:read) ? IO.binread(file) : file options = {body: body, length: size(file), headers: headers} # If the type of current bucket is Picture, # put an image maybe return a set of headers # represent the image's metadata # x-upyun-width # x-upyun-height # x-upyun-frames # x-upyun-file-type res = request(:put, path, options) do |hds| hds.select { |k| k.to_s.match(/^x_upyun_/i) }.reduce({}) do |memo, (k, v)| memo.merge!({k[8..-1].to_sym => /^\d+$/.match(v) ? v.to_i : v}) end end res == {} ? true : res ensure file.close if file.respond_to?(:close) end def get(path, savepath=nil) res = request(:get, path) return res if res.is_a?(Hash) || !savepath dir = File.dirname(savepath) FileUtils.mkdir_p(dir) unless File.directory?(dir) File.write(savepath, res) end def getinfo(path) request(:head, path) do |hds| # File info: # x-upyun-file-type # x-upyun-file-size # x-upyun-file-date hds.select { |k| k.to_s.match(/^x_upyun_file/i) }.reduce({}) do |memo, (k, v)| memo.merge!({k[8..-1].to_sym => /^\d+$/.match(v) ? v.to_i : v}) end end end alias :head :getinfo def delete(path) request(:delete, path) end def mkdir(path) request(:post, path, {headers: {folder: true, mkdir: true}}) end def getlist(path='/') res = request(:get, path) return res if res.is_a?(Hash) res.split("\n").map do |f| attrs = f.split("\t") { name: attrs[0], type: attrs[1] == 'N' ? :file : :folder, length: attrs[2].to_i, last_modified: attrs[3].to_i } end end def usage res = request(:get, '/', {query: 'usage'}) return res if res.is_a?(Hash) # RestClient has a bug, body.to_i returns the code instead of body, # see more on https://github.com/rest-client/rest-client/pull/103 res.dup.to_i end private def fullpath(path) decoded = URI::encode(URI::decode(path.to_s.force_encoding('utf-8'))) "/#{@bucket}#{decoded.start_with?('/') ? decoded : '/' + decoded}" end def request(method, path, options={}, &block) fullpath = fullpath(path) query = options[:query] fullpath_query = "#{fullpath}#{query.nil? ? '' : '?' + query}" headers = options[:headers] || {} date = gmdate length = options[:length] || 0 headers.merge!({ 'User-Agent' => "Upyun-Ruby-SDK-#{VERSION}", 'Date' => date, 'Authorization' => sign(method, date, fullpath, length) }) if [:post, :patch, :put].include? method body = options[:body].nil? ? '' : options[:body] rest_client[fullpath_query].send(method, body, headers) do |res| if res.code / 100 == 2 block_given? ? yield(res.headers) : true else { request_id: res.headers[:x_request_id], error: {code: res.code, message: res.body} } end end else rest_client[fullpath_query].send(method, headers) do |res| if res.code / 100 == 2 case method when :get res.body when :head yield(res.headers) else true end else { request_id: res.headers[:x_request_id], error: {code: res.code, message: res.body} } end end end end def rest_client @rest_clint ||= RestClient::Resource.new("http://#{@endpoint}", options) end def gmdate Time.now.utc.strftime('%a, %d %b %Y %H:%M:%S GMT') end def sign(method, date, path, length) sign = "#{method.to_s.upcase}&#{path}&#{date}&#{length}&#{@password}" "UpYun #{@operator}:#{md5(sign)}" end def size(param) if param.respond_to?(:size) param.size elsif param.is_a?(IO) param.stat.size end end end end