yandex_client 0.1.0 → 1.0.1

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.
@@ -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: []