googledriver 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 +7 -0
- data/lib/googledriver/authorizer.rb +90 -0
- data/lib/googledriver/uploader.rb +194 -0
- data/lib/googledriver.rb +13 -0
- metadata +140 -0
checksums.yaml
ADDED
@@ -0,0 +1,7 @@
|
|
1
|
+
---
|
2
|
+
SHA1:
|
3
|
+
metadata.gz: 3faab8a54083aaa61627247349a97ef48b725687
|
4
|
+
data.tar.gz: da7832b7385d3681c243e279c10343550bb15d47
|
5
|
+
SHA512:
|
6
|
+
metadata.gz: 03675c73e61060c1515e12a4ee1ad4521e60aaf98801965e2bd0871a61432aeb3b0090fe4a4f8e4b25fd228fe6774774ed9c1552c408a136b713ca06b9979f58
|
7
|
+
data.tar.gz: 24a986d20f2a878456bf36a21f7165ce225a1e54bcc632c13e68e95deabf733cc76f8167da6fdceb3521afc47de1282c9c7cee32dc462fa7adcba1a2c514bb9d
|
@@ -0,0 +1,90 @@
|
|
1
|
+
module Googledriver
|
2
|
+
# Authorizes a user to Google Drive and generates access tokens
|
3
|
+
class Authorizer
|
4
|
+
# Authorization scope which only allows program to manipulate files it
|
5
|
+
# created.
|
6
|
+
SCOPE = 'https://www.googleapis.com/auth/drive.file'.freeze
|
7
|
+
|
8
|
+
# Standard redirect uri for authorization.
|
9
|
+
REDIRECT_URI = 'urn:ietf:wg:oauth:2.0:oob'.freeze # standard redirect uri
|
10
|
+
|
11
|
+
# The access token created during authorization which is needed to perform
|
12
|
+
# uploads to Google Drive.
|
13
|
+
attr_reader :access_token
|
14
|
+
|
15
|
+
# The lifetime of an access token in seconds which dictates how often a
|
16
|
+
# token needs to be refreshed.
|
17
|
+
attr_reader :token_lifetime
|
18
|
+
|
19
|
+
# The time of birth of the current access token.
|
20
|
+
attr_reader :token_tob
|
21
|
+
|
22
|
+
# Constructs a new Authorizer by reading client data from a file and
|
23
|
+
# creating a REST resource.
|
24
|
+
def initialize(client_secrets_path)
|
25
|
+
@client_secrets_path = client_secrets_path
|
26
|
+
update_client_data
|
27
|
+
create_refresh_resource
|
28
|
+
end
|
29
|
+
|
30
|
+
# Generates an authorization url for the user in order to obtain an initial
|
31
|
+
# refresh token.
|
32
|
+
def create_refresh_token
|
33
|
+
client = OAuth2::Client.new(@client_id, @client_secret,
|
34
|
+
authorize_url: '/o/oauth2/auth',
|
35
|
+
token_url: '/o/oauth2/token',
|
36
|
+
site: 'https://accounts.google.com')
|
37
|
+
url = client.auth_code.authorize_url(redirect_uri: REDIRECT_URI,
|
38
|
+
scope: SCOPE, access_type: 'offline')
|
39
|
+
puts "Open the following link and follow the onscreen instructions #{url}"
|
40
|
+
code = gets
|
41
|
+
token = client.auth_code.get_token(code, redirect_uri: REDIRECT_URI)
|
42
|
+
@refresh_token = token.refresh_token
|
43
|
+
end
|
44
|
+
|
45
|
+
# Refreshes the access token and updates resources appropriately.
|
46
|
+
def refresh_access_token
|
47
|
+
@token_tob = Time.now
|
48
|
+
|
49
|
+
begin
|
50
|
+
refresh = @refresh_manager.post(
|
51
|
+
build_refresh_payload
|
52
|
+
)
|
53
|
+
rescue StandardError => error
|
54
|
+
warn "#{error}; METHOD #{__callee__}; RESOURCE #{@refresh_manager}"
|
55
|
+
retry
|
56
|
+
end
|
57
|
+
|
58
|
+
update_refresh_data(refresh)
|
59
|
+
end
|
60
|
+
|
61
|
+
private
|
62
|
+
|
63
|
+
def update_client_data
|
64
|
+
file_content = File.read(@client_secrets_path)
|
65
|
+
client_data = JSON.parse(file_content)['installed']
|
66
|
+
@client_id = client_data['client_id']
|
67
|
+
@client_secret = client_data['client_secret']
|
68
|
+
end
|
69
|
+
|
70
|
+
def create_refresh_resource
|
71
|
+
@refresh_manager = RestClient::Resource.new(
|
72
|
+
'https://www.googleapis.com/oauth2/v4/token',
|
73
|
+
headers: { 'Content-Type' => 'application/x-www-form-urlencoded' }
|
74
|
+
)
|
75
|
+
end
|
76
|
+
|
77
|
+
def build_refresh_payload
|
78
|
+
payload = { 'refresh_token' => @refresh_token, 'client_id' => @client_id,
|
79
|
+
'client_secret' => @client_secret,
|
80
|
+
'grant_type' => 'refresh_token' }
|
81
|
+
payload
|
82
|
+
end
|
83
|
+
|
84
|
+
def update_refresh_data(refresh_data)
|
85
|
+
processed_data = JSON.parse(refresh_data)
|
86
|
+
@token_lifetime = processed_data['expires_in']
|
87
|
+
@access_token = processed_data['access_token']
|
88
|
+
end
|
89
|
+
end
|
90
|
+
end
|
@@ -0,0 +1,194 @@
|
|
1
|
+
module Googledriver
|
2
|
+
|
3
|
+
# Uploads a filesystem to Google Drive and saves file ids
|
4
|
+
class Uploader
|
5
|
+
# Constructs a new Uploader by building an empty hash for file ids and
|
6
|
+
# making an Authorizer object to handle the creation of REST resources.
|
7
|
+
def initialize(client_secrets_path)
|
8
|
+
@file_ids = {}
|
9
|
+
@authorizer = Googledriver::Authorizer.new(client_secrets_path)
|
10
|
+
@authorizer.create_refresh_token
|
11
|
+
@authorizer.refresh_access_token
|
12
|
+
@access_token = @authorizer.access_token
|
13
|
+
@token_tob = @authorizer.token_tob
|
14
|
+
create_manager_resource
|
15
|
+
@token_lifetime = @authorizer.token_lifetime
|
16
|
+
create_uploader_resource
|
17
|
+
end
|
18
|
+
|
19
|
+
# Recursively uploads a local filesystem specified by a file path to a given
|
20
|
+
# folder in Google Drive.
|
21
|
+
def upload_filesystem(directory: '', upload_dest: 'root')
|
22
|
+
directory = "#{directory}/" unless directory[-1] == '/'
|
23
|
+
|
24
|
+
Dir[directory + '*'].each do |object|
|
25
|
+
if File.directory?(object)
|
26
|
+
folder_name = File.basename(object)
|
27
|
+
folder_id = upload_folder(folder_name, location: upload_dest)
|
28
|
+
upload_filesystem(upload_dest: folder_id,
|
29
|
+
directory: "#{directory}#{folder_name}/")
|
30
|
+
else
|
31
|
+
file_name = File.basename(object)
|
32
|
+
file_id = upload_file(object, file_name, location: upload_dest)
|
33
|
+
@file_ids[object] = file_id
|
34
|
+
end
|
35
|
+
end
|
36
|
+
end
|
37
|
+
|
38
|
+
# Uploads a local folder specified by a file path to a given folder in
|
39
|
+
# Google Drive.
|
40
|
+
def upload_folder(folder_name, location: 'root')
|
41
|
+
begin
|
42
|
+
update_refresh_token if refresh_due?
|
43
|
+
upload = @drive_manager.post(
|
44
|
+
{ 'name' => folder_name,
|
45
|
+
'mimeType' => 'application/vnd.google-apps.folder' }.to_json
|
46
|
+
)
|
47
|
+
rescue StandardError => error
|
48
|
+
warn "#{error}; METHOD #{__callee__}; RESOURCE #{folder_name}"
|
49
|
+
retry
|
50
|
+
end
|
51
|
+
|
52
|
+
folder_id = JSON.parse(upload)['id']
|
53
|
+
|
54
|
+
if location != 'root'
|
55
|
+
begin
|
56
|
+
update_refresh_token if refresh_due?
|
57
|
+
@drive_manager[folder_id + '?addParents=' + location +
|
58
|
+
'&removeParents=root&alt=json'].patch(
|
59
|
+
{ 'uploadType' => 'resumable' }.to_json
|
60
|
+
)
|
61
|
+
rescue StandardError => error
|
62
|
+
warn "#{error}; METHOD #{__callee__}; RESOURCE #{folder_name}"
|
63
|
+
retry
|
64
|
+
end
|
65
|
+
end
|
66
|
+
|
67
|
+
folder_id
|
68
|
+
end
|
69
|
+
|
70
|
+
# Uploads a local file specified by a file path to a given folder in
|
71
|
+
# Google Drive.
|
72
|
+
def upload_file(file_path, file_name, location: 'root')
|
73
|
+
begin
|
74
|
+
payload = File.open(file_path)
|
75
|
+
rescue StandardError => error
|
76
|
+
warn "#{error}; METHOD #{__callee__}; RESOURCE #{file_path}"
|
77
|
+
return
|
78
|
+
end
|
79
|
+
|
80
|
+
begin
|
81
|
+
update_refresh_token if refresh_due?
|
82
|
+
upload = @drive_uploader.post(
|
83
|
+
payload
|
84
|
+
)
|
85
|
+
rescue StandardError => error
|
86
|
+
warn "#{error}; METHOD #{__callee__}; RESOURCE #{file_path}"
|
87
|
+
retry
|
88
|
+
end
|
89
|
+
|
90
|
+
file_id = JSON.parse(upload)['id']
|
91
|
+
@file_ids[file_path] = file_id
|
92
|
+
|
93
|
+
begin
|
94
|
+
update_refresh_token if refresh_due?
|
95
|
+
@drive_manager[file_id + '?addParents=' + location +
|
96
|
+
'&removeParents=root&alt=json'].patch(
|
97
|
+
{ 'uploadType' => 'resumable',
|
98
|
+
'name' => file_name }.to_json
|
99
|
+
)
|
100
|
+
rescue StandardError => error
|
101
|
+
warn "#{error}; METHOD #{__callee__}; RESOURCE #{file_path}"
|
102
|
+
retry
|
103
|
+
end
|
104
|
+
|
105
|
+
payload.close
|
106
|
+
file_id
|
107
|
+
end
|
108
|
+
|
109
|
+
# Saves the file ids hash to a json file in the working directory.
|
110
|
+
def archive_file_ids
|
111
|
+
archive = File.open('drive_file_ids.json', 'w')
|
112
|
+
File.write('drive_file_ids.json', @file_ids.to_json)
|
113
|
+
archive.close
|
114
|
+
end
|
115
|
+
|
116
|
+
# Returns the metadata of a given file.
|
117
|
+
def obtain_file_metadata(file_id)
|
118
|
+
begin
|
119
|
+
metadata = @drive_manager[file_id].get
|
120
|
+
rescue StandardError => error
|
121
|
+
warn "#{error}; METHOD #{__callee__}; RESOURCE #{file_path}"
|
122
|
+
return
|
123
|
+
end
|
124
|
+
|
125
|
+
metadata = JSON.parse(metadata)
|
126
|
+
metadata
|
127
|
+
end
|
128
|
+
|
129
|
+
# Updates a piece of metadata for a given file.
|
130
|
+
def update_file_metadata(file_id, element, new_data)
|
131
|
+
payload = { 'uploadType' => 'resumable', element => new_data }.to_json
|
132
|
+
|
133
|
+
begin
|
134
|
+
update = @drive_manager[file_id].patch(
|
135
|
+
payload
|
136
|
+
)
|
137
|
+
rescue StandardError => error
|
138
|
+
warn "#{error}; METHOD #{__callee__}; RESOURCE #{file_path}"
|
139
|
+
return
|
140
|
+
end
|
141
|
+
|
142
|
+
update
|
143
|
+
end
|
144
|
+
|
145
|
+
# Shares a given file with an individual or group email address.
|
146
|
+
def update_file_permission(file_id, email)
|
147
|
+
payload = { 'role' => 'writer', 'type' => 'group',
|
148
|
+
'emailAddress' => email }.to_json
|
149
|
+
|
150
|
+
begin
|
151
|
+
update = @drive_manager[file_id + '/permissions'].post(
|
152
|
+
payload
|
153
|
+
)
|
154
|
+
rescue StandardError => error
|
155
|
+
warn "#{error}; METHOD #{__callee__}; RESOURCE #{file_path}"
|
156
|
+
return
|
157
|
+
end
|
158
|
+
|
159
|
+
update
|
160
|
+
end
|
161
|
+
|
162
|
+
private
|
163
|
+
|
164
|
+
def create_manager_resource
|
165
|
+
@drive_manager = RestClient::Resource.new(
|
166
|
+
'https://www.googleapis.com/drive/v3/files/',
|
167
|
+
headers: { 'Authorization' => "Bearer #{@access_token}",
|
168
|
+
'Content-Type' => 'application/json' }
|
169
|
+
)
|
170
|
+
end
|
171
|
+
|
172
|
+
def create_uploader_resource
|
173
|
+
@drive_uploader = RestClient::Resource.new(
|
174
|
+
'https://www.googleapis.com/upload/drive/v3/files',
|
175
|
+
headers: { 'Authorization' => "Bearer #{@access_token}" }
|
176
|
+
)
|
177
|
+
end
|
178
|
+
|
179
|
+
def update_refresh_token
|
180
|
+
@authorizer.refresh_access_token
|
181
|
+
@access_token = @authorizer.access_token
|
182
|
+
@token_lifetime = @authorizer.token_lifetime
|
183
|
+
@token_tob = @authorizer.token_tob
|
184
|
+
create_manager_resource
|
185
|
+
create_uploader_resource
|
186
|
+
end
|
187
|
+
|
188
|
+
def refresh_due?
|
189
|
+
token_timer = Time.now - @token_tob
|
190
|
+
return false unless token_timer > @token_lifetime * 0.9
|
191
|
+
true
|
192
|
+
end
|
193
|
+
end
|
194
|
+
end
|
data/lib/googledriver.rb
ADDED
@@ -0,0 +1,13 @@
|
|
1
|
+
require 'json'
|
2
|
+
require 'oauth2'
|
3
|
+
require 'rest-client'
|
4
|
+
require File.dirname(__FILE__) + '/googledriver/uploader.rb'
|
5
|
+
require File.dirname(__FILE__) + '/googledriver/authorizer.rb'
|
6
|
+
|
7
|
+
# This module is used to authorize a Google account and upload files to its
|
8
|
+
# Drive. It also supports the management of uploaded files through changing the
|
9
|
+
# permissions and metadata of a file. Before use, a secrets file must be
|
10
|
+
# downloaded by following the wizard here
|
11
|
+
# https://console.developers.google.com/start/api?id=drive. See README.md for
|
12
|
+
# usage guide.
|
13
|
+
module Googledriver; end
|
metadata
ADDED
@@ -0,0 +1,140 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: googledriver
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
version: 0.0.1
|
5
|
+
platform: ruby
|
6
|
+
authors:
|
7
|
+
- Peter Giordano
|
8
|
+
autorequire:
|
9
|
+
bindir: bin
|
10
|
+
cert_chain: []
|
11
|
+
date: 2017-08-24 00:00:00.000000000 Z
|
12
|
+
dependencies:
|
13
|
+
- !ruby/object:Gem::Dependency
|
14
|
+
name: json
|
15
|
+
requirement: !ruby/object:Gem::Requirement
|
16
|
+
requirements:
|
17
|
+
- - "~>"
|
18
|
+
- !ruby/object:Gem::Version
|
19
|
+
version: '2.1'
|
20
|
+
- - ">="
|
21
|
+
- !ruby/object:Gem::Version
|
22
|
+
version: 2.1.0
|
23
|
+
type: :runtime
|
24
|
+
prerelease: false
|
25
|
+
version_requirements: !ruby/object:Gem::Requirement
|
26
|
+
requirements:
|
27
|
+
- - "~>"
|
28
|
+
- !ruby/object:Gem::Version
|
29
|
+
version: '2.1'
|
30
|
+
- - ">="
|
31
|
+
- !ruby/object:Gem::Version
|
32
|
+
version: 2.1.0
|
33
|
+
- !ruby/object:Gem::Dependency
|
34
|
+
name: oauth2
|
35
|
+
requirement: !ruby/object:Gem::Requirement
|
36
|
+
requirements:
|
37
|
+
- - "~>"
|
38
|
+
- !ruby/object:Gem::Version
|
39
|
+
version: '1.4'
|
40
|
+
- - ">="
|
41
|
+
- !ruby/object:Gem::Version
|
42
|
+
version: 1.4.0
|
43
|
+
type: :runtime
|
44
|
+
prerelease: false
|
45
|
+
version_requirements: !ruby/object:Gem::Requirement
|
46
|
+
requirements:
|
47
|
+
- - "~>"
|
48
|
+
- !ruby/object:Gem::Version
|
49
|
+
version: '1.4'
|
50
|
+
- - ">="
|
51
|
+
- !ruby/object:Gem::Version
|
52
|
+
version: 1.4.0
|
53
|
+
- !ruby/object:Gem::Dependency
|
54
|
+
name: rest-client
|
55
|
+
requirement: !ruby/object:Gem::Requirement
|
56
|
+
requirements:
|
57
|
+
- - "~>"
|
58
|
+
- !ruby/object:Gem::Version
|
59
|
+
version: '2.0'
|
60
|
+
- - ">="
|
61
|
+
- !ruby/object:Gem::Version
|
62
|
+
version: 2.0.2
|
63
|
+
type: :runtime
|
64
|
+
prerelease: false
|
65
|
+
version_requirements: !ruby/object:Gem::Requirement
|
66
|
+
requirements:
|
67
|
+
- - "~>"
|
68
|
+
- !ruby/object:Gem::Version
|
69
|
+
version: '2.0'
|
70
|
+
- - ">="
|
71
|
+
- !ruby/object:Gem::Version
|
72
|
+
version: 2.0.2
|
73
|
+
- !ruby/object:Gem::Dependency
|
74
|
+
name: rdoc
|
75
|
+
requirement: !ruby/object:Gem::Requirement
|
76
|
+
requirements:
|
77
|
+
- - "~>"
|
78
|
+
- !ruby/object:Gem::Version
|
79
|
+
version: '5.1'
|
80
|
+
- - ">="
|
81
|
+
- !ruby/object:Gem::Version
|
82
|
+
version: 5.1.0
|
83
|
+
type: :development
|
84
|
+
prerelease: false
|
85
|
+
version_requirements: !ruby/object:Gem::Requirement
|
86
|
+
requirements:
|
87
|
+
- - "~>"
|
88
|
+
- !ruby/object:Gem::Version
|
89
|
+
version: '5.1'
|
90
|
+
- - ">="
|
91
|
+
- !ruby/object:Gem::Version
|
92
|
+
version: 5.1.0
|
93
|
+
- !ruby/object:Gem::Dependency
|
94
|
+
name: rubocop
|
95
|
+
requirement: !ruby/object:Gem::Requirement
|
96
|
+
requirements:
|
97
|
+
- - "~>"
|
98
|
+
- !ruby/object:Gem::Version
|
99
|
+
version: 0.49.1
|
100
|
+
type: :development
|
101
|
+
prerelease: false
|
102
|
+
version_requirements: !ruby/object:Gem::Requirement
|
103
|
+
requirements:
|
104
|
+
- - "~>"
|
105
|
+
- !ruby/object:Gem::Version
|
106
|
+
version: 0.49.1
|
107
|
+
description:
|
108
|
+
email: pgg505@york.ac.uk
|
109
|
+
executables: []
|
110
|
+
extensions: []
|
111
|
+
extra_rdoc_files: []
|
112
|
+
files:
|
113
|
+
- lib/googledriver.rb
|
114
|
+
- lib/googledriver/authorizer.rb
|
115
|
+
- lib/googledriver/uploader.rb
|
116
|
+
homepage: http://rubygems.org/gems/googledriver
|
117
|
+
licenses:
|
118
|
+
- MIT
|
119
|
+
metadata: {}
|
120
|
+
post_install_message:
|
121
|
+
rdoc_options: []
|
122
|
+
require_paths:
|
123
|
+
- lib
|
124
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
125
|
+
requirements:
|
126
|
+
- - ">="
|
127
|
+
- !ruby/object:Gem::Version
|
128
|
+
version: 2.0.0
|
129
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
130
|
+
requirements:
|
131
|
+
- - ">="
|
132
|
+
- !ruby/object:Gem::Version
|
133
|
+
version: '0'
|
134
|
+
requirements: []
|
135
|
+
rubyforge_project:
|
136
|
+
rubygems_version: 2.5.1
|
137
|
+
signing_key:
|
138
|
+
specification_version: 4
|
139
|
+
summary: Authorize a Google account and upload files to its Drive.
|
140
|
+
test_files: []
|