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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: ab73da055618c822a57f404e489972a67c828310caaae944f6229cf8cf400874
4
- data.tar.gz: 6f341e29ce87d8aa06a614840ab53cc2939b3864cab9dfafe38bc0fc1b254830
3
+ metadata.gz: e3b0b8586fd6f2fb9764507c0641a3318c4f8c7752e7c1af18fbf329e26f7ca5
4
+ data.tar.gz: 6784a396e69e8c2f6cea67094722c94eeea2f642a365982c0675b6ed5107ccd5
5
5
  SHA512:
6
- metadata.gz: 77d25d52d97237a7edf2b23d9fd09a9e2497f02cc184f1133f812a8b8bf0df3b5a2234d9b3d48b22db563da0e76a793f0b6acc12ea23d019996f9621971ff9fb
7
- data.tar.gz: dc6ce34cf3e3a1b25ef24c121ce56c728eb02f23acb750a17aa03a4330dba8d20dc72037f67731062133884ffa7e6d4dba27747958267ef137b23511e7d93b9a
6
+ metadata.gz: 83d60541c9466223a15ca582828631720cd65219a96b17c6167375dfa3d0afa31f19f459f3830c944bdb198c2ee39c0cba42bf5184412cc50cf02bfa8f40bf07
7
+ data.tar.gz: 27a626e1f8eb23b41d738ea3af0b2e64fc2f5d53f0c8fd7960accdd49820bc7b71df11add0b635422f599d101900dde46b9b1260d0e56bfac263c546bf993345
@@ -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 Wildcard
5
- def self.parse_to_regex(str)
6
- escaped = Regexp.escape(str).gsub('\*','.*?')
7
- Regexp.new "^#{escaped}$", Regexp::IGNORECASE
8
- end
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
- def initialize(str)
11
- @regex = self.class.parse_to_regex str
12
- end
10
+ def initialize(str)
11
+ @regex = self.class.parse_to_regex str
12
+ end
13
13
 
14
- def =~(str)
15
- !!(str =~ @regex)
16
- end
14
+ def =~(str)
15
+ !!(str =~ @regex)
17
16
  end
17
+ end
18
18
 
19
19
 
@@ -94,9 +94,9 @@ class GoogleDrive
94
94
  end
95
95
 
96
96
  # expected values in JSON
97
- @client_id = api_hash["DRIVEAPI_CLIENT_ID"]
98
- @client_secret = api_hash["DRIVEAPI_CLIENT_SECRET"]
99
- @refresh_token = api_hash["DRIVEAPI_REFRESH_TOKEN"]
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
- end
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.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 00:00:00.000000000 Z
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