googledrive-easy 0.0.0 → 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 +4 -4
- data/lib/googledrive/wildcard.rb +12 -12
- data/lib/googledrive-easy.rb +303 -8
- metadata +2 -2
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: e3b0b8586fd6f2fb9764507c0641a3318c4f8c7752e7c1af18fbf329e26f7ca5
|
4
|
+
data.tar.gz: 6784a396e69e8c2f6cea67094722c94eeea2f642a365982c0675b6ed5107ccd5
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 83d60541c9466223a15ca582828631720cd65219a96b17c6167375dfa3d0afa31f19f459f3830c944bdb198c2ee39c0cba42bf5184412cc50cf02bfa8f40bf07
|
7
|
+
data.tar.gz: 27a626e1f8eb23b41d738ea3af0b2e64fc2f5d53f0c8fd7960accdd49820bc7b71df11add0b635422f599d101900dde46b9b1260d0e56bfac263c546bf993345
|
data/lib/googledrive/wildcard.rb
CHANGED
@@ -1,19 +1,19 @@
|
|
1
|
-
#https://stackoverflow.com/questions/6449373/wildcard-string-matching-in-ruby
|
1
|
+
# https://stackoverflow.com/questions/6449373/wildcard-string-matching-in-ruby
|
2
2
|
|
3
3
|
# wildcard search. For file matching.
|
4
|
-
class
|
5
|
-
|
6
|
-
|
7
|
-
|
8
|
-
|
4
|
+
class GoogleDriveWildcard
|
5
|
+
def self.parse_to_regex(str)
|
6
|
+
escaped = Regexp.escape(str).gsub('\*','.*?')
|
7
|
+
Regexp.new "^#{escaped}$", Regexp::IGNORECASE
|
8
|
+
end
|
9
9
|
|
10
|
-
|
11
|
-
|
12
|
-
|
10
|
+
def initialize(str)
|
11
|
+
@regex = self.class.parse_to_regex str
|
12
|
+
end
|
13
13
|
|
14
|
-
|
15
|
-
|
16
|
-
end
|
14
|
+
def =~(str)
|
15
|
+
!!(str =~ @regex)
|
17
16
|
end
|
17
|
+
end
|
18
18
|
|
19
19
|
|
data/lib/googledrive-easy.rb
CHANGED
@@ -94,9 +94,9 @@ class GoogleDrive
|
|
94
94
|
end
|
95
95
|
|
96
96
|
# expected values in JSON
|
97
|
-
@client_id = api_hash["
|
98
|
-
@client_secret = api_hash["
|
99
|
-
@refresh_token = api_hash["
|
97
|
+
@client_id = api_hash["CLIENT_ID"]
|
98
|
+
@client_secret = api_hash["CLIENT_SECRET"]
|
99
|
+
@refresh_token = api_hash["REFRESH_TOKEN"]
|
100
100
|
|
101
101
|
if !@client_id || !@client_secret || !@refresh_token
|
102
102
|
if @raise_error; raise "Not all tokens provided." end
|
@@ -106,12 +106,8 @@ class GoogleDrive
|
|
106
106
|
return generate_access_token()
|
107
107
|
end
|
108
108
|
|
109
|
-
# def self.hi
|
110
|
-
# puts "Hello world!"
|
111
|
-
# end
|
112
109
|
|
113
110
|
def generate_access_token
|
114
|
-
|
115
111
|
# Refresh auth token from google_oauth2 and then requeue the job.
|
116
112
|
# https://stackoverflow.com/questions/12792326/how-do-i-refresh-my-google-oauth2-access-token-using-my-refresh-token
|
117
113
|
options = {
|
@@ -135,4 +131,303 @@ class GoogleDrive
|
|
135
131
|
return true
|
136
132
|
end
|
137
133
|
|
138
|
-
|
134
|
+
# parentfolderid: "root" gets the root directory. Not all folders are under the root. Has to do with permissions
|
135
|
+
# and how Google Drive works.
|
136
|
+
def get_all_files(justfiles: false, justfolders: false, parentfolderid: nil, name: nil)
|
137
|
+
|
138
|
+
# Number of files/directories to be returned each call to /files.
|
139
|
+
# multiple page sizes are handled with the pageToken return value.
|
140
|
+
# 100 is default from google.
|
141
|
+
pageSize = 100
|
142
|
+
|
143
|
+
files = [ ]
|
144
|
+
nextPageToken = nil
|
145
|
+
loop do
|
146
|
+
headers = {
|
147
|
+
"GData-Version" => "3.0",
|
148
|
+
"Authorization" => "Bearer #{@access_token}"
|
149
|
+
}
|
150
|
+
|
151
|
+
url = "https://www.googleapis.com/drive/v3/files"
|
152
|
+
url = url + "?pageSize=#{pageSize}"
|
153
|
+
|
154
|
+
# If a query, it must be appended to all URL
|
155
|
+
# From SO: https://stackoverflow.com/questions/62069155/how-to-filter-google-drive-api-v3-mimetype
|
156
|
+
#var query = "('GoogleDriveFolderKey01' in parents or 'GoogleDriveFolderKey02' in parents) and trashed = false and mimeType = 'application/vnd.google-apps.folder'"
|
157
|
+
|
158
|
+
# default query is non-trashed folders
|
159
|
+
query = "(trashed = false)"
|
160
|
+
|
161
|
+
if justfiles && !justfolders
|
162
|
+
query = query + " and (mimeType != 'application/vnd.google-apps.folder')"
|
163
|
+
end
|
164
|
+
|
165
|
+
if justfolders && !justfiles
|
166
|
+
query = query + " and (mimeType = 'application/vnd.google-apps.folder')"
|
167
|
+
end
|
168
|
+
|
169
|
+
if parentfolderid # parent folder has to be surrounded by single quotes in query
|
170
|
+
query = query + " and ('#{parentfolderid}' in parents)"
|
171
|
+
end
|
172
|
+
|
173
|
+
if name # filename has to be surrounded by single quotes in query
|
174
|
+
if name =~ /\*/ # if name contains wildcard
|
175
|
+
query = query + " and (name contains '#{name}')"
|
176
|
+
else
|
177
|
+
query = query + " and (name = '#{name}')"
|
178
|
+
end
|
179
|
+
end
|
180
|
+
|
181
|
+
url = url + "&q=" + URI.escape(query)
|
182
|
+
|
183
|
+
if nextPageToken
|
184
|
+
url = url + "&pageSize=1&pageToken=#{nextPageToken}"
|
185
|
+
end
|
186
|
+
|
187
|
+
response = HTTParty.get(url, :headers => headers)
|
188
|
+
|
189
|
+
if response.code == 404 # file not found, not an error
|
190
|
+
break
|
191
|
+
elsif response.code != 200
|
192
|
+
if @raise_error; raise "Non-200 HTTP error code: #{response.code}: " + response.parsed_response.inspect end
|
193
|
+
return false
|
194
|
+
end
|
195
|
+
|
196
|
+
if response.parsed_response.has_key?("nextPageToken")
|
197
|
+
nextPageToken = response.parsed_response["nextPageToken"]
|
198
|
+
else
|
199
|
+
nextPageToken = nil
|
200
|
+
end
|
201
|
+
|
202
|
+
if !response.parsed_response.has_key?("files")
|
203
|
+
if @raise_error; raise "Required key not in response: 'files'." end
|
204
|
+
return false
|
205
|
+
end
|
206
|
+
|
207
|
+
# clean google drive response and only return relavent information.
|
208
|
+
response.parsed_response["files"].each do |gd_file_entry|
|
209
|
+
# each entry has the following fields: name, kind, mimeType, id
|
210
|
+
file_hash = { }
|
211
|
+
file_hash[:name] = gd_file_entry["name"]
|
212
|
+
file_hash[:id] = gd_file_entry["id"]
|
213
|
+
file_hash[:isfolder] = false
|
214
|
+
if gd_file_entry["mimeType"] == 'application/vnd.google-apps.folder'
|
215
|
+
file_hash[:isfolder] = true
|
216
|
+
end
|
217
|
+
|
218
|
+
files << file_hash
|
219
|
+
end
|
220
|
+
|
221
|
+
break if nextPageToken.nil?
|
222
|
+
end
|
223
|
+
|
224
|
+
# we have additional processing to do it a wildcard character was passed. Because Google Drive "contains" returns all portions of it.
|
225
|
+
# so we need to filter here
|
226
|
+
if name =~ /\*/ # if name contains wildcard
|
227
|
+
ret_files = [ ]
|
228
|
+
files.each do |file|
|
229
|
+
if GoogleDriveWildcard.new(name) =~ file[:name]
|
230
|
+
ret_files << file
|
231
|
+
end
|
232
|
+
end
|
233
|
+
return ret_files
|
234
|
+
else
|
235
|
+
return files
|
236
|
+
end
|
237
|
+
end
|
238
|
+
|
239
|
+
# returns all files by default in all folders
|
240
|
+
def find_files(name: "*", parentfolderid: nil)
|
241
|
+
return get_all_files(justfiles: true, parentfolderid: parentfolderid, name: name)
|
242
|
+
end
|
243
|
+
|
244
|
+
|
245
|
+
def get_file_info(fileid)
|
246
|
+
|
247
|
+
headers = {
|
248
|
+
"GData-Version" => "3.0",
|
249
|
+
"Authorization" => "Bearer #{@access_token}"
|
250
|
+
}
|
251
|
+
|
252
|
+
url = "https://www.googleapis.com/drive/v3/files/#{fileid}"
|
253
|
+
response = HTTParty.get(url, :headers => headers)
|
254
|
+
|
255
|
+
if response.code == 404 # not found. Could be normal
|
256
|
+
#puts "404 not found"
|
257
|
+
return false
|
258
|
+
elsif response.code != 200
|
259
|
+
if @raise_error; raise "Non-200 HTTP error code: #{response.code}: " + response.parsed_response.inspect end
|
260
|
+
return false
|
261
|
+
end
|
262
|
+
|
263
|
+
if !response.parsed_response.has_key?("kind")
|
264
|
+
return false
|
265
|
+
elsif response.parsed_response["kind"] != "drive#file"
|
266
|
+
return false
|
267
|
+
elsif !response.parsed_response.has_key?("name")
|
268
|
+
return false
|
269
|
+
end
|
270
|
+
|
271
|
+
# returns the following keys: kind, id, name, mimeType
|
272
|
+
file_hash = { }
|
273
|
+
file_hash[:name] = response.parsed_response["name"]
|
274
|
+
file_hash[:id] = response.parsed_response["id"]
|
275
|
+
file_hash[:isfolder] = false
|
276
|
+
if response.parsed_response["mimeType"] == 'application/vnd.google-apps.folder'
|
277
|
+
file_hash[:isfolder] = true
|
278
|
+
end
|
279
|
+
|
280
|
+
return file_hash
|
281
|
+
end
|
282
|
+
|
283
|
+
def find_directory_id(directory_name, parentfolderid: nil)
|
284
|
+
|
285
|
+
file_list = get_all_files(justfolders: true, name: directory_name, parentfolderid: parentfolderid)
|
286
|
+
if !file_list
|
287
|
+
return false
|
288
|
+
end
|
289
|
+
|
290
|
+
file_list.each do | item |
|
291
|
+
return item["id"]
|
292
|
+
end
|
293
|
+
|
294
|
+
if @raise_error; raise "Directory not found." end
|
295
|
+
|
296
|
+
return false
|
297
|
+
end
|
298
|
+
|
299
|
+
def upload_file(file, directory_id = nil)
|
300
|
+
|
301
|
+
file_basename = File.basename(file)
|
302
|
+
|
303
|
+
# see if file exists on Drive
|
304
|
+
file_list = driveapi_get_all_files(access_token, justfiles: true, parentfolderid: directory_id, name: file_basename)
|
305
|
+
if file_list.count > 0
|
306
|
+
if @raise_error; raise "ERROR: File '#{file_basename}' already exists." end
|
307
|
+
return false
|
308
|
+
end
|
309
|
+
|
310
|
+
metadata = "name : '#{file_basename}'"
|
311
|
+
|
312
|
+
if !directory_id.nil? # if directory if specified, add it to the parent
|
313
|
+
metadata = metadata + ", parents : [ '#{directory_id}' ]"
|
314
|
+
end
|
315
|
+
|
316
|
+
metadata = "{#{metadata}}" # wrap metadata in braces
|
317
|
+
|
318
|
+
# Upload with CURL. HTTPParty and RestClient seem to be incompatible.
|
319
|
+
cmd = "curl -X POST -L " +
|
320
|
+
"--silent " +
|
321
|
+
"-H \"Authorization: Bearer #{@access_token}\" " +
|
322
|
+
"-F \"metadata=#{metadata};type=application/json;charset=UTF-8\" " +
|
323
|
+
"-F \"file=@#{file};\" " +
|
324
|
+
"https://www.googleapis.com/upload/drive/v3/files?uploadType=multipart"
|
325
|
+
|
326
|
+
# puts "cmd:\n#{cmd}"
|
327
|
+
|
328
|
+
response = `#{cmd} 2>/dev/null`
|
329
|
+
exit_status = $?.exitstatus
|
330
|
+
|
331
|
+
if exit_status != 0
|
332
|
+
if @raise_error; raise "CURL existed with non-0 error code: #{exit_status}." end
|
333
|
+
return false
|
334
|
+
end
|
335
|
+
|
336
|
+
begin
|
337
|
+
response = JSON.load(response)
|
338
|
+
rescue => e
|
339
|
+
if @raise_error; raise "CURL response is not in valid JSON." end
|
340
|
+
return false
|
341
|
+
end
|
342
|
+
|
343
|
+
# example of response.
|
344
|
+
# {
|
345
|
+
# "kind": "drive#file",
|
346
|
+
# "id": "1Zw9YD3TXci3Ja_wU0g7f30DHpsEbk2zf",
|
347
|
+
# "name": "ruby-3.1.0.tar.gz",
|
348
|
+
# "mimeType": "application/gzip"
|
349
|
+
# }
|
350
|
+
|
351
|
+
|
352
|
+
# check that name = filename
|
353
|
+
# check that kind = drive#file
|
354
|
+
if !response.key?("name") # name key does not exist
|
355
|
+
if @raise_error; raise "no name key specified in response." end
|
356
|
+
return false
|
357
|
+
elsif !response.key?("kind") # kind key does not exist
|
358
|
+
if @raise_error; raise "no kind key specified in response." end
|
359
|
+
return false
|
360
|
+
elsif response["kind"] != "drive#file" # Not of file type
|
361
|
+
if @raise_error; raise "kind is of non-file type." end
|
362
|
+
return false
|
363
|
+
elsif response["name"] != file_basename # file name mismatch
|
364
|
+
if @raise_error; raise "file name mismatch." end
|
365
|
+
return false
|
366
|
+
end
|
367
|
+
|
368
|
+
return true
|
369
|
+
end
|
370
|
+
|
371
|
+
# returns full path of downloaded file
|
372
|
+
def download_file(file_name_or_id, parentfolderid: nil, file_path: nil)
|
373
|
+
|
374
|
+
# https://stackoverflow.com/questions/60608901/how-to-download-a-big-file-from-google-drive-via-curl-in-bash
|
375
|
+
# curl -H "Authorization: Bearer $token" "https://www.googleapis.com/drive/v3/files/$id?alt=media" -o "$file"
|
376
|
+
|
377
|
+
# if file path passed, check it is valid.
|
378
|
+
if file_path && !Dir.exist?(file_path)
|
379
|
+
if @raise_error; raise "File path '#{file_path}' does not exist." end
|
380
|
+
return false
|
381
|
+
elsif !file_path # no path passed, use current directory
|
382
|
+
file_path = Dir.getwd
|
383
|
+
end
|
384
|
+
|
385
|
+
# path passed and valid. Append forward slash if not already there.
|
386
|
+
file_path = file_path.gsub(/\/$/, '') + "/"
|
387
|
+
|
388
|
+
# 1) assume file_name_or_id is a filename
|
389
|
+
files = find_files(name: file_name_or_id, parentfolderid: parentfolderid)
|
390
|
+
if files && (files.count == 1)
|
391
|
+
file_info = files.first
|
392
|
+
elsif files && (files.count > 1)
|
393
|
+
if @raise_error; raise "Multiple files with name '#{file_name_or_id}' exist. dowload_file() can only handle a single filename." end
|
394
|
+
return false
|
395
|
+
else # either files is false or count is 0. assume file_name_or_id is an id.
|
396
|
+
file_info = get_file_info(file_name_or_id)
|
397
|
+
if !file_info
|
398
|
+
if @raise_error; raise "No file with ID '#{file_name_or_id}' exist." end
|
399
|
+
return false
|
400
|
+
end
|
401
|
+
end
|
402
|
+
|
403
|
+
file_name = file_info[:name]
|
404
|
+
|
405
|
+
# Delete local file if it exists
|
406
|
+
`rm #{file_path + file_name} > /dev/null 2>&1`
|
407
|
+
|
408
|
+
# temp file is automatically unlinked at end of function.
|
409
|
+
output_file = Tempfile.new('driveapi_').path
|
410
|
+
|
411
|
+
url = "https://www.googleapis.com/drive/v3/files/#{file_info[:id]}?alt=media"
|
412
|
+
|
413
|
+
# --write-out \"%{http_code}\" \"$@\" returns http code in stdout
|
414
|
+
cmd = "curl --silent --write-out \"%{http_code}\" \"$@\" -H \"Authorization: Bearer #{@access_token}\" \"#{url}\" -o \"#{output_file}\""
|
415
|
+
response = `#{cmd} 2>/dev/null` # this is the http code as string.
|
416
|
+
exit_status = $?.exitstatus
|
417
|
+
|
418
|
+
if exit_status != 0
|
419
|
+
if @raise_error; raise "non-0 exit status #{exit_status}." end
|
420
|
+
return false
|
421
|
+
elsif response.to_i != 200
|
422
|
+
if @raise_error; raise "Non HTTP 200 code for download: '#{response}'" end
|
423
|
+
return false
|
424
|
+
end
|
425
|
+
|
426
|
+
# file temp file to file
|
427
|
+
`cp #{output_file} #{file_path + file_name}`
|
428
|
+
|
429
|
+
return file_path + file_name
|
430
|
+
end
|
431
|
+
end # end of class GoogleDrive
|
432
|
+
|
433
|
+
|
metadata
CHANGED
@@ -1,14 +1,14 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: googledrive-easy
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.0.
|
4
|
+
version: 0.0.1
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Mike Bullock
|
8
8
|
autorequire:
|
9
9
|
bindir: bin
|
10
10
|
cert_chain: []
|
11
|
-
date: 2022-02-
|
11
|
+
date: 2022-02-15 00:00:00.000000000 Z
|
12
12
|
dependencies: []
|
13
13
|
description: Easy File interface to Google Drive
|
14
14
|
email: jmb@rgnets.com
|