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.
- checksums.yaml +4 -4
- data/.github/workflows/main.yml +30 -0
- data/.gitignore +2 -0
- data/.pryrc +6 -0
- data/.rubocop.yml +36 -0
- data/Gemfile +6 -3
- data/README.md +32 -83
- data/Rakefile +5 -3
- data/dip.yml +9 -8
- data/docker/Dockerfile.dip +8 -0
- data/docker/docker-compose.yml +16 -8
- data/docker/prepare_env.sh +11 -0
- data/lib/yandex_client/auth.rb +62 -0
- data/lib/yandex_client/configurable.rb +26 -0
- data/lib/yandex_client/dav/prop_find_response.rb +70 -0
- data/lib/yandex_client/dav.rb +104 -0
- data/lib/yandex_client/disk.rb +60 -0
- data/lib/yandex_client/error_handler.rb +20 -0
- data/lib/yandex_client/passport.rb +45 -0
- data/lib/yandex_client/version.rb +3 -1
- data/lib/yandex_client.rb +39 -18
- data/yandex_client.gemspec +9 -6
- metadata +34 -18
- data/.travis.yml +0 -12
- data/docker/Dockerfile.2.6 +0 -9
- data/docker/docker-compose.development.yml +0 -18
- data/lib/yandex_client/api_request_error.rb +0 -13
- data/lib/yandex_client/auth/client.rb +0 -55
- data/lib/yandex_client/client.rb +0 -97
- data/lib/yandex_client/dav/client.rb +0 -115
- data/lib/yandex_client/dav/propfind_parser.rb +0 -48
- data/lib/yandex_client/not_found_error.rb +0 -4
- data/lib/yandex_client/passport/client.rb +0 -34
@@ -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
|
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
|
14
|
+
Config = Struct.new \
|
15
|
+
:api_key,
|
16
|
+
:api_secret,
|
17
|
+
:logger,
|
18
|
+
:read_timeout,
|
19
|
+
:write_timeout,
|
20
|
+
:connect_timeout
|
5
21
|
|
6
|
-
|
7
|
-
|
8
|
-
end
|
22
|
+
class ApiRequestError < StandardError
|
23
|
+
attr_reader :http_code
|
9
24
|
|
10
|
-
|
11
|
-
|
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
|
-
|
15
|
-
autoload :ApiRequestError, 'yandex_client/api_request_error'
|
16
|
-
autoload :NotFoundError, 'yandex_client/not_found_error'
|
36
|
+
NotFoundError = Class.new(ApiRequestError)
|
17
37
|
|
18
|
-
|
19
|
-
|
20
|
-
|
38
|
+
class << self
|
39
|
+
def config
|
40
|
+
@config ||= Config.new(nil, nil, nil, 120, 600, 5)
|
41
|
+
end
|
21
42
|
|
22
|
-
|
23
|
-
|
24
|
-
|
43
|
+
def configure
|
44
|
+
yield config
|
45
|
+
end
|
25
46
|
|
26
|
-
|
27
|
-
|
28
|
-
|
47
|
+
def auth
|
48
|
+
@auth ||= Auth.new
|
49
|
+
end
|
29
50
|
end
|
30
51
|
end
|
data/yandex_client.gemspec
CHANGED
@@ -1,4 +1,6 @@
|
|
1
|
-
|
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 =
|
12
|
-
spec.description =
|
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(
|
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 '
|
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.
|
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
|
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:
|
11
|
+
date: 2021-08-27 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
|
-
name:
|
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
|
-
- ".
|
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.
|
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/
|
133
|
-
- lib/yandex_client/
|
134
|
-
- lib/yandex_client/
|
135
|
-
- lib/yandex_client/dav/
|
136
|
-
- lib/yandex_client/
|
137
|
-
- lib/yandex_client/
|
138
|
-
- lib/yandex_client/passport
|
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.
|
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.
|
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: []
|