backup-pcs 0.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.
checksums.yaml ADDED
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA1:
3
+ metadata.gz: 049ec16812b9a9610beaf7dcbb21fff5cfeb4072
4
+ data.tar.gz: 42ae0e0c689a2a90e70a64ebdad97b49181664b9
5
+ SHA512:
6
+ metadata.gz: 48a5abf2774d5820571acf27583bf50fbeb9b897625ae363f5a366102b6fcbf143a1717f870843c4d62ec3111936d3bc2df50ac25da28fb7b2687d454eec1c8d
7
+ data.tar.gz: b592117bfd34d9cfeb38002898a84e9fcc99366b811b60f200daf8dc7d6993f66b1839b6f694a568234683a7ba9889da211b7048fb2768b3dd4a30c841a8a5f0
data/.gitignore ADDED
@@ -0,0 +1,17 @@
1
+ *.gem
2
+ *.rbc
3
+ .bundle
4
+ .config
5
+ .yardoc
6
+ Gemfile.lock
7
+ InstalledFiles
8
+ _yardoc
9
+ coverage
10
+ doc/
11
+ lib/bundler/man
12
+ pkg
13
+ rdoc
14
+ spec/reports
15
+ test/tmp
16
+ test/version_tmp
17
+ tmp
data/.rspec ADDED
@@ -0,0 +1,2 @@
1
+ --color
2
+ --format progress
data/.travis.yml ADDED
@@ -0,0 +1,16 @@
1
+ language: ruby
2
+ bundler_args: --without development
3
+ script: 'bundle exec rake spec'
4
+
5
+ rvm:
6
+ - 1.9.3
7
+ - 2.0.0
8
+ - rbx-2.1.1
9
+ - jruby-19mode
10
+ - ruby-head
11
+
12
+ matrix:
13
+ allow_failures:
14
+ - rvm: rbx-2.1.1
15
+ - rvm: jruby-19mode
16
+ - rvm: ruby-head
data/Gemfile ADDED
@@ -0,0 +1,16 @@
1
+ source 'https://rubygems.org'
2
+
3
+ group :development do
4
+ gem 'guard-rspec'
5
+ gem 'terminal-notifier-guard' if /darwin/ =~ RUBY_PLATFORM
6
+ end
7
+
8
+ group :test, :development do
9
+ gem 'rake'
10
+ gem 'rspec'
11
+ end
12
+
13
+ gem 'rubysl', platforms: :rbx
14
+
15
+ # Specify your gem's dependencies in backup-pcs.gemspec
16
+ gemspec
data/Guardfile ADDED
@@ -0,0 +1,6 @@
1
+ guard :rspec, all_on_start: true, all_after_pass: true do
2
+ watch(%r{^spec/.+_spec\.rb$})
3
+ watch(%r{^lib/(.+)\.rb$}) { |m| "spec/lib/#{m[1]}_spec.rb" }
4
+ watch('spec/spec_helper.rb') { "spec" }
5
+ end
6
+
data/LICENSE.txt ADDED
@@ -0,0 +1,22 @@
1
+ Copyright (c) 2013 Lonre Wang
2
+
3
+ MIT License
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining
6
+ a copy of this software and associated documentation files (the
7
+ "Software"), to deal in the Software without restriction, including
8
+ without limitation the rights to use, copy, modify, merge, publish,
9
+ distribute, sublicense, and/or sell copies of the Software, and to
10
+ permit persons to whom the Software is furnished to do so, subject to
11
+ the following conditions:
12
+
13
+ The above copyright notice and this permission notice shall be
14
+ included in all copies or substantial portions of the Software.
15
+
16
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
17
+ EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
18
+ MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
19
+ NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
20
+ LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
21
+ OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
22
+ WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
data/README.md ADDED
@@ -0,0 +1,51 @@
1
+ # Baidu PCS Storage for Backup [![Build Status](https://travis-ci.org/lonre/backup-pcs.png?branch=master)](https://travis-ci.org/lonre/backup-pcs)
2
+
3
+ `PCS(Personal Cloud Storage)`是百度推出的针对个人用户的云存储服务
4
+
5
+ 本 Gem 为 [Backup](https://github.com/meskyanichi/backup) Storage 插件,可以将数据备份到 `PCS`
6
+
7
+ 使用 `backup-pcs` 前必须到 [百度开发者中心](http://developer.baidu.com/console) 开启指定应用的 PCS API 权限,参考 [开通PCS API权限](http://developer.baidu.com/wiki/index.php?title=docs/pcs/guide/api_approve),并获取所设置的文件目录
8
+
9
+ ## 安装
10
+
11
+ ```
12
+ $ gem install backup-pcs
13
+ ```
14
+ `backup-pcs` 当前支持 Ruby(MRI) 版本:1.9.3, 2.0.0
15
+
16
+ ## 用法
17
+
18
+ 此说明只提供了本 Gem 的配置使用方法,关于 Backup 的详细使用方法,请参考 [Backup GitHub Page](https://github.com/meskyanichi/backup)
19
+
20
+ 用你喜欢的文本编辑器,打开 Backup model 文件,如:`~/Backup/models/mysite.rb`
21
+
22
+ ### 引入依赖
23
+
24
+ 在头部添加依赖
25
+
26
+ ```ruby
27
+ require 'backup/pcs'
28
+ ```
29
+
30
+ ### 配置
31
+
32
+ 在主体部分添加如下配置:
33
+
34
+ ```ruby
35
+ store_with :PCS do |p|
36
+ p.client_id = 'a_client_id'
37
+ p.client_secret = 'a_cliet_secret'
38
+ p.dir_name = 'Backups' # 开通 PCS API 权限时所设置的文件目录
39
+ p.path = 'data' # 保存路径,从 dir_name 算起
40
+ # p.keep = 2
41
+ # p.max_retries = 10 # 出错后重试次数,默认 10 次
42
+ # p.retry_waitsec = 30 # 出错后重试等待秒数,默认 30 秒
43
+ end
44
+ ```
45
+
46
+ ### 注意
47
+
48
+ 在配置完成之后,您必须手动在系统中运行一次 `Backup`,根据终端的提示完成授权。在授权完毕之后,授权信息会被缓存,这样,当后续自动任务触发 `Backup` 时,会自动加载此缓存授权信息。
49
+
50
+ ## Copyright
51
+ MIT License. Copyright (c) 2013 Lonre Wang.
data/Rakefile ADDED
@@ -0,0 +1,4 @@
1
+ require "bundler/gem_tasks"
2
+ require 'rspec/core/rake_task'
3
+
4
+ RSpec::Core::RakeTask.new(:spec)
@@ -0,0 +1,24 @@
1
+ # coding: utf-8
2
+ lib = File.expand_path('../lib', __FILE__)
3
+ $LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
4
+ require 'backup/pcs/version'
5
+
6
+ Gem::Specification.new do |spec|
7
+ spec.name = "backup-pcs"
8
+ spec.version = Backup::PCS::VERSION
9
+ spec.authors = ["Lonre Wang"]
10
+ spec.email = ["me@wanglong.me"]
11
+ spec.description = %q{Backup Storage for supporting Baidu Personal Cloud Storage(PCS)}
12
+ spec.summary = %q{Baidu PCS Storage for Backup}
13
+ spec.homepage = "https://github.com/lonre/backup-pcs"
14
+ spec.license = "MIT"
15
+
16
+ spec.files = `git ls-files`.split($/)
17
+ spec.executables = spec.files.grep(%r{^bin/}) { |f| File.basename(f) }
18
+ spec.test_files = spec.files.grep(%r{^(test|spec|features)/})
19
+ spec.require_paths = ["lib"]
20
+
21
+ spec.add_runtime_dependency "baidu-sdk", "~> 0.0.1"
22
+ spec.add_runtime_dependency "backup", "~> 3.9.0"
23
+ spec.add_development_dependency "bundler", "~> 1.3"
24
+ end
@@ -0,0 +1,5 @@
1
+ module Backup
2
+ module PCS
3
+ VERSION = "0.0.1"
4
+ end
5
+ end
data/lib/backup/pcs.rb ADDED
@@ -0,0 +1,3 @@
1
+ require "backup"
2
+
3
+ Backup::Storage.autoload(:PCS, File.join(File.dirname(__FILE__), 'storage/pcs'))
@@ -0,0 +1,131 @@
1
+ require 'base64'
2
+ require 'json'
3
+ require 'baidu/oauth'
4
+ require 'baidu/pcs'
5
+
6
+ module Backup
7
+ module Storage
8
+ class PCS < Base
9
+ class Error < Backup::Error; end
10
+
11
+ attr_accessor :client_id, :client_secret, :dir_name, :max_retries, :retry_waitsec
12
+
13
+ def initialize(model, storage_id=nil)
14
+ super
15
+
16
+ @path ||= 'backups'
17
+ @max_retries ||= 10
18
+ @retry_waitsec ||= 30
19
+ end
20
+
21
+ private
22
+
23
+ def transfer!
24
+ package.filenames.each do |filename|
25
+ src = File.join(Config.tmp_path, filename)
26
+ dest = File.join(remote_path, filename)
27
+ Logger.info "Storing '#{ dest }'..."
28
+ auto_refresh_token do
29
+ File.open(src, 'r') do |file|
30
+ client.upload file, path: dest, block_upload: true,
31
+ retry_times: @max_retries,
32
+ retry_waitsec: @retry_waitsec
33
+ end
34
+ end
35
+ end
36
+ rescue => err
37
+ raise Error.wrap(err, 'Upload Failed!')
38
+ end
39
+
40
+ def remove!(package)
41
+ Logger.info "Removing backup package dated #{ package.time }..."
42
+
43
+ auto_refresh_token do
44
+ client.delete(remote_path_for(package))
45
+ end
46
+ end
47
+
48
+ # create or get Baidu PCS client
49
+ def client
50
+ return @client if @client
51
+
52
+ unless session = cached_session
53
+ Logger.info "Creating a new Baidu session!"
54
+ session = create_session!
55
+ end
56
+
57
+ @client = Baidu::PCS::Client.new(session, dir_name)
58
+ rescue => err
59
+ raise Error.wrap(err, 'Authorization Failed.')
60
+ end
61
+
62
+ # cache Baidu::Session
63
+ def cached_session
64
+ session = false
65
+ if File.exist?(cached_file)
66
+ begin
67
+ content = Base64.decode64(File.read(cached_file))
68
+ session = Marshal.load(content)
69
+ Logger.info "Baidu session data loaded from cache!"
70
+ rescue => err
71
+ Logger.warn Error.wrap(err, "Cached file: #{cached_file} might be corrupt.")
72
+ end
73
+ end
74
+ session
75
+ end
76
+
77
+ def auto_refresh_token
78
+ tries = 0
79
+ begin
80
+ yield
81
+ rescue Baidu::Errors::AuthError => e
82
+ raise Error, 'Too many auth errors' if (tries += 1) > 5
83
+ Logger.info "Refreshing Baidu session..."
84
+ session = oauth_client.refresh(cached_session.refresh_token)
85
+ raise Error.wrap(e, 'Authorization Failed!') unless session
86
+ write_cache! session
87
+ @client = nil
88
+ retry
89
+ end
90
+ end
91
+
92
+ def cached_file
93
+ File.join(Config.cache_path, "pcs_#{storage_id}_#{client_id}")
94
+ end
95
+
96
+ def write_cache!(session)
97
+ FileUtils.mkdir_p(Config.cache_path)
98
+ File.open(cached_file, "w") do |cache_file|
99
+ cache_file.write(Base64.encode64(Marshal.dump(session)))
100
+ end
101
+ end
102
+
103
+ def oauth_client
104
+ Baidu::OAuth::Client.new(client_id, client_secret)
105
+ end
106
+
107
+ def create_session!
108
+ require 'timeout'
109
+
110
+ device_flow = oauth_client.device_flow
111
+ rest = device_flow.user_and_device_code('netdisk')
112
+
113
+ STDOUT.puts "1. Visit verification url: #{rest[:verification_url]}"
114
+ STDOUT.puts "2. Type user code below in the form"
115
+ STDOUT.puts "\t #{rest[:user_code]}"
116
+ STDOUT.puts "3. Hit 'Enter/Return' once you're authorized."
117
+
118
+ Timeout::timeout(300) { STDIN.gets }
119
+
120
+ session = device_flow.get_token(rest[:device_code])
121
+
122
+ write_cache!(session)
123
+ STDOUT.puts "Baidu session cached: #{cached_file}"
124
+
125
+ session
126
+ rescue => err
127
+ raise Error.wrap(err, 'Could not create a new session!')
128
+ end
129
+ end
130
+ end
131
+ end
data/spec/pcs_spec.rb ADDED
@@ -0,0 +1,412 @@
1
+ # encoding: UTF-8
2
+
3
+ require 'backup/pcs'
4
+
5
+ module Backup
6
+ describe Storage::PCS do
7
+ let(:model) { Model.new(:test_trigger, 'test label') }
8
+ let(:storage) { Storage::PCS.new(model) }
9
+
10
+ describe '#initialize' do
11
+ it 'is a Storage::Base' do
12
+ expect(storage).to be_a(Storage::Base)
13
+ end
14
+
15
+ it 'has default config' do
16
+ expect(storage.storage_id ).to be_nil
17
+ expect(storage.keep ).to be_nil
18
+ expect(storage.client_id ).to be_nil
19
+ expect(storage.client_secret).to be_nil
20
+ expect(storage.dir_name ).to be_nil
21
+ expect(storage.path ).to eq 'backups'
22
+ expect(storage.max_retries ).to be 10
23
+ expect(storage.retry_waitsec).to be 30
24
+ end
25
+
26
+ it 'inits with config' do
27
+ storage = Storage::PCS.new(model, 'sid') do |c|
28
+ c.keep = 1
29
+ c.client_id = 'ci'
30
+ c.client_secret = 'cs'
31
+ c.dir_name = 'dn'
32
+ c.path = 'myback'
33
+ c.max_retries = 2
34
+ c.retry_waitsec = 3
35
+ end
36
+
37
+ expect(storage.storage_id ).to eq('sid')
38
+ expect(storage.keep ).to eq(1)
39
+ expect(storage.client_id ).to eq('ci')
40
+ expect(storage.client_secret).to eq('cs')
41
+ expect(storage.dir_name ).to eq('dn')
42
+ expect(storage.path ).to eq 'myback'
43
+ expect(storage.max_retries ).to be(2)
44
+ expect(storage.retry_waitsec).to be(3)
45
+ end
46
+ end
47
+
48
+ describe '#transfer!' do
49
+ let(:timestamp) { Time.now.strftime("%Y.%m.%d.%H.%M.%S") }
50
+ let(:remote_path) { File.join('myback/test_trigger', timestamp) }
51
+ let(:package) { double }
52
+ let(:file) { double }
53
+ let(:client) { double }
54
+ let(:cached_session) { double }
55
+ let(:storage) {
56
+ Storage::PCS.new(model, 'sid') do |c|
57
+ c.keep = 1
58
+ c.client_id = 'ci'
59
+ c.client_secret = 'cs'
60
+ c.dir_name = 'dn'
61
+ c.path = 'myback'
62
+ c.max_retries = 2
63
+ c.retry_waitsec = 3
64
+ end
65
+ }
66
+
67
+ before do
68
+ allow(storage).to receive(:package).and_return(package)
69
+ allow(package).to receive(:trigger).and_return('test_trigger')
70
+ allow(package).to receive(:time).and_return(timestamp)
71
+ allow(file).to receive(:size).and_return(68*1024*1024)
72
+ allow(storage).to receive(:client).and_return(client)
73
+ allow(storage).to receive(:cached_session).and_return(cached_session)
74
+ end
75
+
76
+ it 'transfers files' do
77
+ allow(client).to receive(:upload)
78
+ allow(package).to receive(:filenames).
79
+ and_return(['test_trigger.tar_aa', 'test_trigger.tar_ab'])
80
+ src = File.join(Config.tmp_path, 'test_trigger.tar_aa')
81
+ dest = File.join(remote_path, 'test_trigger.tar_aa')
82
+ expect(File).to receive(:open).with(src, 'r').and_yield(file)
83
+ expect(client).to receive(:upload).with(file, path: dest, block_upload: true,
84
+ retry_times: 2,
85
+ retry_waitsec: 3)
86
+
87
+ src = File.join(Config.tmp_path, 'test_trigger.tar_ab')
88
+ dest = File.join(remote_path, 'test_trigger.tar_ab')
89
+ expect(File).to receive(:open).with(src, 'r').and_yield(file)
90
+ expect(client).to receive(:upload).with(file, path: dest, block_upload: true,
91
+ retry_times: 2,
92
+ retry_waitsec: 3)
93
+ expect(storage).to receive(:auto_refresh_token).and_yield.twice
94
+ storage.send(:transfer!)
95
+ end
96
+
97
+ describe 'when access token expired' do
98
+ let(:oauth_client) { double }
99
+ let(:session) { double }
100
+
101
+ before do
102
+ allow(package).to receive(:filenames).and_return(['test_trigger.tar_aa'])
103
+ allow(client).to receive(:upload).and_raise(Baidu::Errors::AuthError, 'expired')
104
+ allow(storage).to receive(:write_cache!).with(session) do
105
+ allow(client).to receive(:upload).and_return(nil) # do not raise like before
106
+ end
107
+ allow(storage).to receive(:oauth_client).and_return(oauth_client)
108
+ allow(cached_session).to receive(:refresh_token)
109
+ end
110
+
111
+ it 'refreshes access token automatically with valid refresh token' do
112
+ allow(oauth_client).to receive(:refresh).and_return(session)
113
+
114
+ src = File.join(Config.tmp_path, 'test_trigger.tar_aa')
115
+ dest = File.join(remote_path, 'test_trigger.tar_aa')
116
+ expect(Logger).to receive(:info).with("Storing '#{dest}'...")
117
+ expect(File).to receive(:open).with(src, 'r').and_yield(file).twice
118
+ expect(client).to receive(:upload).with(file, path: dest, block_upload: true,
119
+ retry_times: 2,
120
+ retry_waitsec: 3).twice
121
+ expect(Logger).to receive(:info).with('Refreshing Baidu session...')
122
+ storage.send(:transfer!)
123
+ end
124
+
125
+ it 'refreshes access token automatically with invalid refresh token' do
126
+ allow(cached_session).to receive(:refresh_token).and_return(nil)
127
+ allow(oauth_client).to receive(:refresh).and_return(nil)
128
+
129
+ src = File.join(Config.tmp_path, 'test_trigger.tar_aa')
130
+ dest = File.join(remote_path, 'test_trigger.tar_aa')
131
+ expect(Logger).to receive(:info).with("Storing '#{dest}'...")
132
+ expect(File).to receive(:open).with(src, 'r').and_yield(file).once
133
+ expect(Logger).to receive(:info).with('Refreshing Baidu session...')
134
+ expect(oauth_client).to receive(:refresh)
135
+ expect(Kernel).to receive(:raise)
136
+ expect(storage).to receive(:write_cache!).never
137
+ expect {
138
+ storage.send(:transfer!)
139
+ }.to raise_error(Storage::PCS::Error)
140
+ end
141
+ end
142
+ end
143
+
144
+ describe '#remove!' do
145
+ let(:timestamp) { Time.now.strftime("%Y.%m.%d.%H.%M.%S") }
146
+ let(:remote_path) { File.join('myback/test_trigger', timestamp) }
147
+ let(:package) { double }
148
+ let(:client) { double }
149
+ let(:storage) {
150
+ Storage::PCS.new(model, 'sid') do |c|
151
+ c.keep = 1
152
+ c.client_id = 'ci'
153
+ c.client_secret = 'cs'
154
+ c.dir_name = 'dn'
155
+ c.path = 'myback'
156
+ c.max_retries = 2
157
+ c.retry_waitsec = 3
158
+ end
159
+ }
160
+
161
+ before do
162
+ allow(storage).to receive(:package).and_return(package)
163
+ allow(package).to receive(:trigger).and_return('test_trigger')
164
+ allow(package).to receive(:time).and_return(timestamp)
165
+ allow(storage).to receive(:client).and_return(client)
166
+ end
167
+
168
+ it 'removes package' do
169
+ expect(Logger).to receive(:info).with("Removing backup package dated #{ timestamp }...")
170
+ expect(client).to receive(:delete).with(remote_path)
171
+
172
+ storage.send(:remove!, package)
173
+ end
174
+ end
175
+
176
+ describe '#client' do
177
+ let(:storage) {
178
+ Storage::PCS.new(model, 'sid') do |c|
179
+ c.keep = 1
180
+ c.client_id = 'ci'
181
+ c.client_secret = 'cs'
182
+ c.dir_name = 'dn'
183
+ c.path = 'myback'
184
+ c.max_retries = 2
185
+ c.retry_waitsec = 3
186
+ end
187
+ }
188
+
189
+ it 'creates new session' do
190
+ session = double
191
+ client = double
192
+ allow(storage).to receive(:cached_session).and_return(nil)
193
+ allow(storage).to receive(:create_session!).and_return(session)
194
+ allow(Baidu::PCS::Client).to receive(:new).with(session, 'dn').and_return(client)
195
+
196
+ expect(Logger).to receive(:info).with('Creating a new Baidu session!')
197
+ expect(storage).to receive(:create_session!).once.and_return(session)
198
+
199
+ storage.send(:client)
200
+ expect(storage.instance_variable_get(:@client)).to eq(client)
201
+ end
202
+
203
+ it 'uses cached session' do
204
+ session = double
205
+ client = double
206
+ allow(storage).to receive(:cached_session).and_return(session)
207
+ allow(Baidu::PCS::Client).to receive(:new).with(session, 'dn').and_return(client)
208
+
209
+ expect(storage).to receive(:create_session!).never
210
+
211
+ storage.send(:client)
212
+ expect(storage.instance_variable_get(:@client)).to eq(client)
213
+ end
214
+ end
215
+
216
+ describe '#cached_session' do
217
+ let(:storage) {
218
+ Storage::PCS.new(model, 'sid') do |c|
219
+ c.keep = 1
220
+ c.client_id = 'ci'
221
+ c.client_secret = 'cs'
222
+ c.dir_name = 'dn'
223
+ c.path = 'myback'
224
+ c.max_retries = 2
225
+ c.retry_waitsec = 3
226
+ end
227
+ }
228
+
229
+ it 'has no cached file' do
230
+ expect(File).to receive(:exist?).and_return(false)
231
+ expect(storage.send(:cached_session)).to eq(false)
232
+ end
233
+
234
+ it 'loads cached session' do
235
+ session = double
236
+ content = double
237
+ base64_c = double
238
+ cached_file = storage.send(:cached_file)
239
+ expect(File).to receive(:exist?).with(cached_file).and_return(true)
240
+ expect(File).to receive(:read).with(cached_file).and_return(content)
241
+ expect(Base64).to receive(:decode64).with(content).and_return(base64_c)
242
+ expect(Marshal).to receive(:load).with(base64_c).and_return(session)
243
+ expect(Logger).to receive(:info).with('Baidu session data loaded from cache!')
244
+
245
+ expect(storage.send(:cached_session)).to eq(session)
246
+ end
247
+
248
+ it 'loads cached session failed' do
249
+ expect(File).to receive(:exist?).and_return(true)
250
+ expect(File).to receive(:read).and_raise('error.')
251
+ expect(Logger).to receive(:warn)
252
+ expect(storage.send(:cached_session)).to eq(false)
253
+ end
254
+ end
255
+
256
+ describe '#auto_refresh_token' do
257
+ let(:session) { double }
258
+ let(:oauth_client) { double }
259
+ let(:cached_session) { double }
260
+ let(:storage) {
261
+ Storage::PCS.new(model, 'sid') do |c|
262
+ c.keep = 1
263
+ c.client_id = 'ci'
264
+ c.client_secret = 'cs'
265
+ c.dir_name = 'dn'
266
+ c.path = 'myback'
267
+ c.max_retries = 2
268
+ c.retry_waitsec = 3
269
+ end
270
+ }
271
+
272
+ before do
273
+ allow(storage).to receive(:oauth_client).and_return(oauth_client)
274
+ allow(storage).to receive(:cached_session).and_return(cached_session)
275
+ allow(cached_session).to receive(:refresh_token)
276
+ end
277
+
278
+ it 'does not raise autu error' do
279
+ expect(Logger).to receive(:info).with('Refreshing Baidu session...').never
280
+ expect(oauth_client).to receive(:refresh).never
281
+ expect(storage).to receive(:write_cache!).never
282
+
283
+ storage.send(:auto_refresh_token) { }
284
+ end
285
+
286
+ it 'raises auth error with valid refresh token' do
287
+ work_p = proc { raise Baidu::Errors::AuthError, 'expired' }
288
+ allow(oauth_client).to receive(:refresh).and_return(session)
289
+ allow(storage).to receive(:write_cache!) do
290
+ work_p = proc { }
291
+ end
292
+
293
+ expect(Logger).to receive(:info).with('Refreshing Baidu session...')
294
+ expect(oauth_client).to receive(:refresh)
295
+ expect(storage).to receive(:write_cache!).with(session)
296
+
297
+ storage.send(:auto_refresh_token) { work_p.call }
298
+ end
299
+
300
+ it 'raises auth error with invalid refresh token' do
301
+ work_p = proc { raise Baidu::Errors::AuthError, 'expired' }
302
+ allow(oauth_client).to receive(:refresh).and_return(nil)
303
+ allow(storage).to receive(:write_cache!) do
304
+ work_p = proc { }
305
+ end
306
+
307
+ expect(Logger).to receive(:info).with('Refreshing Baidu session...')
308
+ expect(oauth_client).to receive(:refresh)
309
+ expect(storage).to receive(:write_cache!).never
310
+
311
+ expect {
312
+ storage.send(:auto_refresh_token) { work_p.call }
313
+ }.to raise_error(Storage::PCS::Error)
314
+ end
315
+
316
+ it 'raises when there are too many auth errors' do
317
+ work_p = proc { raise Baidu::Errors::AuthError, 'expired' }
318
+ allow(oauth_client).to receive(:refresh).and_return(session)
319
+ expect {
320
+ storage.send(:auto_refresh_token) { work_p.call }
321
+ }.to raise_error(Storage::PCS::Error, 'Storage::PCS::Error: Too many auth errors')
322
+ end
323
+ end
324
+
325
+ describe '#cached_file' do
326
+ it 'has right cached file path' do
327
+ path1 = Storage::PCS.new(model, 'sid') do |c|
328
+ c.client_id = 'ci'
329
+ end.send(:cached_file)
330
+ expect(path1).to eq(Config.cache_path + '/pcs_sid_ci')
331
+
332
+ path2 = Storage::PCS.new(model) do |c|
333
+ c.client_id = 'ci2'
334
+ end.send(:cached_file)
335
+ expect(path2).to eq(Config.cache_path + '/pcs__ci2')
336
+ end
337
+ end
338
+
339
+ describe '#write_cache!' do
340
+ let(:cached_file) { double }
341
+ let(:session) { double }
342
+
343
+ before do
344
+ storage.client_id = 'ci'
345
+ allow(FileUtils).to receive(:mkdir_p)
346
+ allow(storage).to receive(:cached_file).and_return(cached_file)
347
+ end
348
+
349
+ it 'writes cached file' do
350
+ expect(FileUtils).to receive(:mkdir_p).with(Config.cache_path)
351
+ expect(File).to receive(:open).with(cached_file, 'w').and_yield(cached_file)
352
+ data = Base64.encode64(Marshal.dump(session))
353
+ expect(cached_file).to receive(:write).with(data)
354
+
355
+ storage.send(:write_cache!, session)
356
+ end
357
+ end
358
+
359
+ describe '#oauth' do
360
+ before do
361
+ storage.client_id = 'ci'
362
+ storage.client_secret = 'cs'
363
+ end
364
+
365
+ it 'creates oauth client' do
366
+ oclient = double
367
+ expect(Baidu::OAuth::Client).to receive(:new).with('ci', 'cs').and_return(oclient)
368
+ expect(storage.send(:oauth_client)).to eq(oclient)
369
+ end
370
+ end
371
+
372
+ describe '#create_session!' do
373
+ let(:rest) { Hash.new }
374
+ before do
375
+ storage.client_id = 'ci'
376
+ storage.client_secret = 'cs'
377
+ end
378
+
379
+ it 'is authorized successfully' do
380
+ device_flow = double
381
+ oauth_client = double
382
+ session = double
383
+ expect(storage).to receive(:oauth_client).and_return(oauth_client)
384
+ expect(oauth_client).to receive(:device_flow).and_return(device_flow)
385
+ expect(device_flow).to receive(:user_and_device_code).with('netdisk').and_return(rest)
386
+
387
+ rest[:verification_url] = 'https://example.com/verification_url'
388
+ rest[:user_code] = 'xxxyyy'
389
+ rest[:device_code] = 'zzzzzz'
390
+
391
+ expect(STDOUT).to receive(:puts).with('1. Visit verification url: https://example.com/verification_url')
392
+ expect(STDOUT).to receive(:puts).with('2. Type user code below in the form')
393
+ expect(STDOUT).to receive(:puts).with("\t xxxyyy")
394
+ expect(STDOUT).to receive(:puts).with("3. Hit 'Enter/Return' once you're authorized.")
395
+ expect(STDIN).to receive(:gets)
396
+ expect(STDOUT).to receive(:puts).with('Baidu session cached: ' + storage.send(:cached_file))
397
+
398
+ expect(device_flow).to receive(:get_token).with(rest[:device_code]).and_return(session)
399
+ expect(storage).to receive(:write_cache!).with(session)
400
+
401
+ expect(storage.send(:create_session!)).to eq(session)
402
+ end
403
+
404
+ it 'is not authorized' do
405
+ allow(storage).to receive(:oauth_client).and_raise('any error')
406
+ expect {
407
+ storage.send(:create_session!)
408
+ }.to raise_error(Storage::PCS::Error)
409
+ end
410
+ end
411
+ end
412
+ end
@@ -0,0 +1,17 @@
1
+ # This file was generated by the `rspec --init` command. Conventionally, all
2
+ # specs live under a `spec` directory, which RSpec adds to the `$LOAD_PATH`.
3
+ # Require this file using `require "spec_helper"` to ensure that it is only
4
+ # loaded once.
5
+ #
6
+ # See http://rubydoc.info/gems/rspec-core/RSpec/Core/Configuration
7
+ RSpec.configure do |config|
8
+ config.treat_symbols_as_metadata_keys_with_true_values = true
9
+ config.run_all_when_everything_filtered = true
10
+ config.filter_run :focus
11
+
12
+ # Run specs in random order to surface order dependencies. If you find an
13
+ # order dependency and want to debug it, you can fix the order by providing
14
+ # the seed, which is printed after each run.
15
+ # --seed 1234
16
+ config.order = 'random'
17
+ end
metadata ADDED
@@ -0,0 +1,103 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: backup-pcs
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.0.1
5
+ platform: ruby
6
+ authors:
7
+ - Lonre Wang
8
+ autorequire:
9
+ bindir: bin
10
+ cert_chain: []
11
+ date: 2013-12-01 00:00:00.000000000 Z
12
+ dependencies:
13
+ - !ruby/object:Gem::Dependency
14
+ name: baidu-sdk
15
+ requirement: !ruby/object:Gem::Requirement
16
+ requirements:
17
+ - - "~>"
18
+ - !ruby/object:Gem::Version
19
+ version: 0.0.1
20
+ type: :runtime
21
+ prerelease: false
22
+ version_requirements: !ruby/object:Gem::Requirement
23
+ requirements:
24
+ - - "~>"
25
+ - !ruby/object:Gem::Version
26
+ version: 0.0.1
27
+ - !ruby/object:Gem::Dependency
28
+ name: backup
29
+ requirement: !ruby/object:Gem::Requirement
30
+ requirements:
31
+ - - "~>"
32
+ - !ruby/object:Gem::Version
33
+ version: 3.9.0
34
+ type: :runtime
35
+ prerelease: false
36
+ version_requirements: !ruby/object:Gem::Requirement
37
+ requirements:
38
+ - - "~>"
39
+ - !ruby/object:Gem::Version
40
+ version: 3.9.0
41
+ - !ruby/object:Gem::Dependency
42
+ name: bundler
43
+ requirement: !ruby/object:Gem::Requirement
44
+ requirements:
45
+ - - "~>"
46
+ - !ruby/object:Gem::Version
47
+ version: '1.3'
48
+ type: :development
49
+ prerelease: false
50
+ version_requirements: !ruby/object:Gem::Requirement
51
+ requirements:
52
+ - - "~>"
53
+ - !ruby/object:Gem::Version
54
+ version: '1.3'
55
+ description: Backup Storage for supporting Baidu Personal Cloud Storage(PCS)
56
+ email:
57
+ - me@wanglong.me
58
+ executables: []
59
+ extensions: []
60
+ extra_rdoc_files: []
61
+ files:
62
+ - ".gitignore"
63
+ - ".rspec"
64
+ - ".travis.yml"
65
+ - Gemfile
66
+ - Guardfile
67
+ - LICENSE.txt
68
+ - README.md
69
+ - Rakefile
70
+ - backup-pcs.gemspec
71
+ - lib/backup/pcs.rb
72
+ - lib/backup/pcs/version.rb
73
+ - lib/backup/storage/pcs.rb
74
+ - spec/pcs_spec.rb
75
+ - spec/spec_helper.rb
76
+ homepage: https://github.com/lonre/backup-pcs
77
+ licenses:
78
+ - MIT
79
+ metadata: {}
80
+ post_install_message:
81
+ rdoc_options: []
82
+ require_paths:
83
+ - lib
84
+ required_ruby_version: !ruby/object:Gem::Requirement
85
+ requirements:
86
+ - - ">="
87
+ - !ruby/object:Gem::Version
88
+ version: '0'
89
+ required_rubygems_version: !ruby/object:Gem::Requirement
90
+ requirements:
91
+ - - ">="
92
+ - !ruby/object:Gem::Version
93
+ version: '0'
94
+ requirements: []
95
+ rubyforge_project:
96
+ rubygems_version: 2.1.11
97
+ signing_key:
98
+ specification_version: 4
99
+ summary: Baidu PCS Storage for Backup
100
+ test_files:
101
+ - spec/pcs_spec.rb
102
+ - spec/spec_helper.rb
103
+ has_rdoc: