yandex_client 0.1.0 → 1.0.1

Sign up to get free protection for your applications and to get access to all the features.
@@ -0,0 +1,70 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'ox'
4
+ require 'cgi'
5
+
6
+ module YandexClient
7
+ class Dav
8
+ class PropFindResponse
9
+ include Enumerable
10
+
11
+ SUCCESS_STATUS = 'HTTP/1.1 200 OK'
12
+
13
+ class Item
14
+ attr_reader :name, :created_at, :last_modified, :content_type
15
+
16
+ def initialize(name, node)
17
+ @name = name
18
+ @folder = node[:'d:resourcetype'].is_a?(Hash)
19
+
20
+ load_node_data(node)
21
+ end
22
+
23
+ def folder?
24
+ @folder
25
+ end
26
+
27
+ def file?
28
+ !@folder
29
+ end
30
+
31
+ private
32
+
33
+ def load_node_data(node)
34
+ @created_at = node.fetch(:'d:creationdate')
35
+ @last_modified = node.fetch(:'d:getlastmodified')
36
+
37
+ return if folder?
38
+
39
+ @etag = node.fetch(:'d:getetag')
40
+ @size = node.fetch(:'d:getcontentlength').to_i
41
+ @content_type = node.fetch(:'d:getcontenttype')
42
+
43
+ define_singleton_method(:etag) { @etag }
44
+ define_singleton_method(:size) { @size }
45
+ define_singleton_method(:content_type) { @content_type }
46
+ end
47
+ end
48
+
49
+ def initialize(xml)
50
+ @doc = Ox.load(
51
+ xml.dup.force_encoding('UTF-8'),
52
+ mode: :hash
53
+ )
54
+ end
55
+
56
+ def each
57
+ @doc[:'d:multistatus'].each do |node|
58
+ next if (response = node[:'d:response']).nil?
59
+
60
+ name = CGI.unescape(response.fetch(:'d:href'))
61
+ node_data = response.fetch(:'d:propstat')
62
+
63
+ raise ParseError unless node_data.fetch(:'d:status') == SUCCESS_STATUS
64
+
65
+ yield Item.new(name, node_data.fetch(:'d:prop'))
66
+ end
67
+ end
68
+ end
69
+ end
70
+ end
@@ -0,0 +1,104 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'digest'
4
+ require 'http'
5
+ require 'yandex_client/dav/prop_find_response'
6
+
7
+ module YandexClient
8
+ # https://tech.yandex.ru/disk/doc/dg/reference/put-docpage/
9
+ #
10
+ # Chainable client:
11
+ #
12
+ # YandexClient::Dav[access_token]
13
+ # .put('1.txt', '/a/b/c/1.txt')
14
+ # .put(File.open('1.txt', 'rb'), '/path/to/1.txt')
15
+ # .put(Tempfile.new.tap { |t| t.write('say ni'); t.rewind }, '/path/to/tmp.txt')
16
+ # .delete('/a/b/c/1.txt')
17
+ # .mkcol('/a/b/c')
18
+ # .propfind('/a/dip.yml', depth: 0)
19
+ # .propfind('/a', depth: 1)
20
+ # .propfind.to_a
21
+ class Dav
22
+ include Configurable
23
+ include ErrorHandler
24
+
25
+ ACTION_URL = 'https://webdav.yandex.ru'
26
+ PROPFIND_QUERY = '<?xml version="1.0" encoding="utf-8"?><propfind xmlns="DAV:"></propfind>'
27
+
28
+ attr_reader :token
29
+
30
+ class << self
31
+ def with_token(token)
32
+ new(token)
33
+ end
34
+
35
+ alias [] with_token
36
+ end
37
+
38
+ def initialize(token)
39
+ @token = token
40
+ end
41
+
42
+ def put(file, dest, etag: nil, sha256: nil, size: nil)
43
+ io = file.is_a?(String) ? File.open(file, 'rb') : file
44
+
45
+ headers = auth_headers.merge!(
46
+ 'Etag' => etag || Digest::MD5.file(io.path),
47
+ 'Sha256' => sha256 || Digest::SHA256.file(io.path),
48
+ 'Expect' => '100-continue',
49
+ 'Content-Length' => (size || File.size(io.path)).to_s
50
+ )
51
+
52
+ process_response with_config.put(url_for(dest), body: io, headers: headers)
53
+ end
54
+
55
+ def delete(dest)
56
+ process_response with_config.delete(
57
+ url_for(dest),
58
+ headers: auth_headers
59
+ )
60
+ end
61
+
62
+ def mkcol(dest)
63
+ process_response with_config.request(
64
+ :mkcol,
65
+ url_for(dest),
66
+ headers: auth_headers
67
+ )
68
+ end
69
+
70
+ def propfind(dest = '', depth = 1)
71
+ headers = auth_headers.merge!(
72
+ Depth: depth.to_s,
73
+ 'Content-Type' => 'application/x-www-form-urlencoded',
74
+ 'Content-Length' => PROPFIND_QUERY.length
75
+ )
76
+
77
+ response = with_config.request(:propfind, url_for(dest), headers: headers, body: PROPFIND_QUERY)
78
+ process_response(response)
79
+
80
+ PropFindResponse.new(response.body.to_s)
81
+ end
82
+
83
+ private
84
+
85
+ def auth_headers
86
+ {Authorization: "OAuth #{token}"}
87
+ end
88
+
89
+ def url_for(dest)
90
+ URI.parse(ACTION_URL).tap do |uri|
91
+ path = dest
92
+ path = "/#{path}" unless path.match?(%r{^/})
93
+
94
+ uri.path = path
95
+ end
96
+ end
97
+
98
+ def process_response(response)
99
+ return self if response.status.success?
100
+
101
+ error_for_response(response)
102
+ end
103
+ end
104
+ end
@@ -0,0 +1,60 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'json'
4
+
5
+ module YandexClient
6
+ # https://yandex.ru/dev/disk/api/reference/capacity-docpage/
7
+ # https://yandex.ru/dev/disk/api/reference/content-docpage/
8
+ #
9
+ # YandexClient::Disk[access_token].info
10
+ # YandexClient::Disk[access_token].download_url('path/to/file')
11
+ class Disk
12
+ include Configurable
13
+ include ErrorHandler
14
+
15
+ ACTION_URL = 'https://cloud-api.yandex.net'
16
+
17
+ attr_reader :token
18
+
19
+ class << self
20
+ def with_token(token)
21
+ new(token)
22
+ end
23
+
24
+ alias [] with_token
25
+ end
26
+
27
+ def initialize(token)
28
+ @token = token
29
+ end
30
+
31
+ def info
32
+ process_response(
33
+ with_config.get("#{ACTION_URL}/v1/disk/", headers: auth_headers)
34
+ )
35
+ end
36
+
37
+ def download_url(path)
38
+ url = URI.parse(ACTION_URL).tap do |uri|
39
+ uri.path = '/v1/disk/resources/download'
40
+ uri.query = URI.encode_www_form(path: path)
41
+ end
42
+
43
+ process_response(
44
+ with_config.get(url, headers: auth_headers)
45
+ ).fetch(:href)
46
+ end
47
+
48
+ private
49
+
50
+ def auth_headers
51
+ {Authorization: "OAuth #{@token}"}
52
+ end
53
+
54
+ def process_response(response)
55
+ return JSON.parse(response.body, symbolize_names: true) if response.status.success?
56
+
57
+ error_for_response(response)
58
+ end
59
+ end
60
+ end
@@ -0,0 +1,20 @@
1
+ # frozen_string_literal: true
2
+
3
+ module YandexClient
4
+ module ErrorHandler
5
+ private
6
+
7
+ def error_for_response(response)
8
+ msg = response.body.empty? ? response.status.to_s : response.body.to_s.force_encoding('UTF-8')
9
+ err =
10
+ case response.code
11
+ when 404
12
+ NotFoundError.new(msg, http_code: 404)
13
+ else
14
+ ApiRequestError.new(msg, http_code: response.code)
15
+ end
16
+
17
+ raise err
18
+ end
19
+ end
20
+ end
@@ -0,0 +1,45 @@
1
+ # frozen_string_literal: true
2
+
3
+ module YandexClient
4
+ # https://tech.yandex.ru/passport/doc/dg/reference/request-docpage/
5
+ #
6
+ # Example:
7
+ # YandexClient::Passport::Client[Token.first.access_token].info
8
+ class Passport
9
+ include Configurable
10
+ include ErrorHandler
11
+
12
+ ACTION_URL = 'https://login.yandex.ru/info'
13
+
14
+ attr_reader :token
15
+
16
+ class << self
17
+ def with_token(token)
18
+ new(token)
19
+ end
20
+
21
+ alias [] with_token
22
+ end
23
+
24
+ def initialize(token)
25
+ @token = token
26
+ end
27
+
28
+ def info
29
+ process_response \
30
+ with_config.get(ACTION_URL, headers: auth_headers)
31
+ end
32
+
33
+ private
34
+
35
+ def auth_headers
36
+ {Authorization: "Bearer #{@token}"}
37
+ end
38
+
39
+ def process_response(response)
40
+ return JSON.parse(response.body, symbolize_names: true) if response.status.success?
41
+
42
+ error_for_response(response)
43
+ end
44
+ end
45
+ end
@@ -1,3 +1,5 @@
1
+ # frozen_string_literal: true
2
+
1
3
  module YandexClient
2
- VERSION = '0.1.0'.freeze
4
+ VERSION = '1.0.1'
3
5
  end
data/lib/yandex_client.rb CHANGED
@@ -1,30 +1,51 @@
1
+ # frozen_string_literal: true
2
+
1
3
  require 'yandex_client/version'
2
4
 
5
+ require 'yandex_client/error_handler'
6
+ require 'yandex_client/configurable'
7
+
8
+ require 'yandex_client/dav'
9
+ require 'yandex_client/disk'
10
+ require 'yandex_client/auth'
11
+ require 'yandex_client/passport'
12
+
3
13
  module YandexClient
4
- Config = Struct.new(:api_key, :api_secret, :logger)
14
+ Config = Struct.new \
15
+ :api_key,
16
+ :api_secret,
17
+ :logger,
18
+ :read_timeout,
19
+ :write_timeout,
20
+ :connect_timeout
5
21
 
6
- def self.config
7
- @config ||= Config.new
8
- end
22
+ class ApiRequestError < StandardError
23
+ attr_reader :http_code
9
24
 
10
- def self.configure
11
- yield config
25
+ def initialize(msg = nil, http_code: nil)
26
+ if http_code
27
+ super "#{msg}, (http code #{http_code})"
28
+ else
29
+ super msg
30
+ end
31
+
32
+ @http_code = http_code
33
+ end
12
34
  end
13
35
 
14
- autoload :Client, 'yandex_client/client'
15
- autoload :ApiRequestError, 'yandex_client/api_request_error'
16
- autoload :NotFoundError, 'yandex_client/not_found_error'
36
+ NotFoundError = Class.new(ApiRequestError)
17
37
 
18
- module Auth
19
- autoload :Client, 'yandex_client/auth/client'
20
- end
38
+ class << self
39
+ def config
40
+ @config ||= Config.new(nil, nil, nil, 120, 600, 5)
41
+ end
21
42
 
22
- module Passport
23
- autoload :Client, 'yandex_client/passport/client'
24
- end
43
+ def configure
44
+ yield config
45
+ end
25
46
 
26
- module Dav
27
- autoload :Client, 'yandex_client/dav/client'
28
- autoload :PropfindParser, 'yandex_client/dav/propfind_parser'
47
+ def auth
48
+ @auth ||= Auth.new
49
+ end
29
50
  end
30
51
  end
@@ -1,4 +1,6 @@
1
- lib = File.expand_path("../lib", __FILE__)
1
+ # frozen_string_literal: true
2
+
3
+ lib = File.expand_path('lib', __dir__)
2
4
  $LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
3
5
  require 'yandex_client/version'
4
6
 
@@ -8,12 +10,12 @@ Gem::Specification.new do |spec|
8
10
  spec.authors = ['Maxim Tretyakov']
9
11
  spec.email = ['max@tretyakov-ma.ru']
10
12
 
11
- spec.summary = %q{Yandex Client}
12
- spec.description = %q{Allows to use yandex disk as webdav nodes}
13
+ spec.summary = 'Yandex Client'
14
+ spec.description = 'Allows to use yandex disk as webdav nodes'
13
15
  spec.homepage = 'https://github.com/yamax2/yandex_client'
14
16
  spec.license = 'MIT'
15
17
 
16
- spec.files = Dir.chdir(File.expand_path('..', __FILE__)) do
18
+ spec.files = Dir.chdir(File.expand_path(__dir__)) do
17
19
  `git ls-files -z`.split("\x0").reject { |f| f.match(%r{^(test|spec|features)/}) }
18
20
  end
19
21
 
@@ -21,14 +23,15 @@ Gem::Specification.new do |spec|
21
23
  spec.executables = spec.files.grep(%r{^exe/}) { |f| File.basename(f) }
22
24
  spec.require_paths = ['lib']
23
25
 
24
- spec.add_runtime_dependency 'oj'
26
+ spec.add_runtime_dependency 'http'
25
27
  spec.add_runtime_dependency 'ox'
26
28
 
27
29
  spec.add_development_dependency 'bundler'
28
30
  spec.add_development_dependency 'rake', '>= 10'
29
31
  spec.add_development_dependency 'rspec', '>= 3.0'
32
+ spec.add_development_dependency 'simplecov', '>= 0.9'
30
33
  spec.add_development_dependency 'vcr'
31
34
  spec.add_development_dependency 'webmock'
32
35
 
33
- spec.required_ruby_version = '>= 2.3'
36
+ spec.required_ruby_version = '>= 2.4'
34
37
  end
metadata CHANGED
@@ -1,17 +1,17 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: yandex_client
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.1.0
4
+ version: 1.0.1
5
5
  platform: ruby
6
6
  authors:
7
7
  - Maxim Tretyakov
8
- autorequire:
8
+ autorequire:
9
9
  bindir: exe
10
10
  cert_chain: []
11
- date: 2019-07-31 00:00:00.000000000 Z
11
+ date: 2021-08-27 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
- name: oj
14
+ name: http
15
15
  requirement: !ruby/object:Gem::Requirement
16
16
  requirements:
17
17
  - - ">="
@@ -80,6 +80,20 @@ dependencies:
80
80
  - - ">="
81
81
  - !ruby/object:Gem::Version
82
82
  version: '3.0'
83
+ - !ruby/object:Gem::Dependency
84
+ name: simplecov
85
+ requirement: !ruby/object:Gem::Requirement
86
+ requirements:
87
+ - - ">="
88
+ - !ruby/object:Gem::Version
89
+ version: '0.9'
90
+ type: :development
91
+ prerelease: false
92
+ version_requirements: !ruby/object:Gem::Requirement
93
+ requirements:
94
+ - - ">="
95
+ - !ruby/object:Gem::Version
96
+ version: '0.9'
83
97
  - !ruby/object:Gem::Dependency
84
98
  name: vcr
85
99
  requirement: !ruby/object:Gem::Requirement
@@ -115,9 +129,11 @@ executables: []
115
129
  extensions: []
116
130
  extra_rdoc_files: []
117
131
  files:
132
+ - ".github/workflows/main.yml"
118
133
  - ".gitignore"
134
+ - ".pryrc"
119
135
  - ".rspec"
120
- - ".travis.yml"
136
+ - ".rubocop.yml"
121
137
  - Gemfile
122
138
  - LICENSE.txt
123
139
  - README.md
@@ -125,24 +141,24 @@ files:
125
141
  - bin/console
126
142
  - bin/setup
127
143
  - dip.yml
128
- - docker/Dockerfile.2.6
129
- - docker/docker-compose.development.yml
144
+ - docker/Dockerfile.dip
130
145
  - docker/docker-compose.yml
146
+ - docker/prepare_env.sh
131
147
  - lib/yandex_client.rb
132
- - lib/yandex_client/api_request_error.rb
133
- - lib/yandex_client/auth/client.rb
134
- - lib/yandex_client/client.rb
135
- - lib/yandex_client/dav/client.rb
136
- - lib/yandex_client/dav/propfind_parser.rb
137
- - lib/yandex_client/not_found_error.rb
138
- - lib/yandex_client/passport/client.rb
148
+ - lib/yandex_client/auth.rb
149
+ - lib/yandex_client/configurable.rb
150
+ - lib/yandex_client/dav.rb
151
+ - lib/yandex_client/dav/prop_find_response.rb
152
+ - lib/yandex_client/disk.rb
153
+ - lib/yandex_client/error_handler.rb
154
+ - lib/yandex_client/passport.rb
139
155
  - lib/yandex_client/version.rb
140
156
  - yandex_client.gemspec
141
157
  homepage: https://github.com/yamax2/yandex_client
142
158
  licenses:
143
159
  - MIT
144
160
  metadata: {}
145
- post_install_message:
161
+ post_install_message:
146
162
  rdoc_options: []
147
163
  require_paths:
148
164
  - lib
@@ -150,15 +166,15 @@ required_ruby_version: !ruby/object:Gem::Requirement
150
166
  requirements:
151
167
  - - ">="
152
168
  - !ruby/object:Gem::Version
153
- version: '2.3'
169
+ version: '2.4'
154
170
  required_rubygems_version: !ruby/object:Gem::Requirement
155
171
  requirements:
156
172
  - - ">="
157
173
  - !ruby/object:Gem::Version
158
174
  version: '0'
159
175
  requirements: []
160
- rubygems_version: 3.0.4
161
- signing_key:
176
+ rubygems_version: 3.2.26
177
+ signing_key:
162
178
  specification_version: 4
163
179
  summary: Yandex Client
164
180
  test_files: []