backup-googledrive 0.0.2 → 0.1.0

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA1:
3
- metadata.gz: 6909a4f268118492f668382203b3c332e19a80c8
4
- data.tar.gz: eac80a0ce6a50a9314f253a86f93c9c2b9b083ae
3
+ metadata.gz: 45c9de7b9020a457a977dc6b804b3bed1598c661
4
+ data.tar.gz: deb947444c13d07f39a145529f4c275527d90f6a
5
5
  SHA512:
6
- metadata.gz: c0b37e31e8bf83fde792b284d30b564426425b3603596b73c89ef71426574b5e99b25d5bb058a73b1a9b865d20d5688d8e0a93bacf795675d34abda55c3fb116
7
- data.tar.gz: 9a3693d57cb08c8d594b0903a075e919db6184f00196a56ab26118a16cd1e73d9abbbeda94739827af671b5239d3d3047e7c301b719702b79bb3f450c39fb078
6
+ metadata.gz: 775d57b43a92e45393fdb893fe809e9186909575718d651ff13d003c8a0724457c96496f85e73b283d54f8f19fd70fce1e480b41de13285a03c8918dd180ed14
7
+ data.tar.gz: 0def6331e624ae9d26add60519e035b571e11a78370217652c667d3074ee4ca81279d64eb65951be726da1b6d0320211b6bbf952afaebdf0a4205fcecafa8fcd
@@ -1,6 +1,216 @@
1
+ require 'backup'
2
+
1
3
  module Backup
2
4
  module Storage
3
- autoload :GoogleDrive, File.join(File.dirname(__FILE__), 'storage', 'googledrive')
4
- Backup::Config::DSL.const_set('GoogleDrive', Module.new)
5
+ class GoogleDrive < Base
6
+ include Storage::Cycler
7
+ class Error < Backup::Error; end
8
+
9
+ ##
10
+ # Path to the rclone executable
11
+ attr_accessor :rclone_exec
12
+
13
+ ##
14
+ # Path to the rclone config file
15
+ attr_accessor :rclone_config
16
+
17
+ ##
18
+ # Name of the rclone remote drive
19
+ attr_accessor :rclone_drive
20
+
21
+ ##
22
+ # Chunk size, specified in bytes (power of two, i.e. 8M = 8 * 1024 * 1024)
23
+ attr_accessor :chunk_size
24
+
25
+ ##
26
+ # Number of times to retry failed operations.
27
+ #
28
+ # Default: 10
29
+ attr_accessor :max_retries
30
+
31
+ ##
32
+ # Creates a new instance of the storage object
33
+ def initialize(model, storage_id = nil)
34
+ super
35
+ @rclone_exec ||= "/usr/bin/rclone"
36
+ @rclone_config ||= "/root/.config/rclone/rclone.conf"
37
+ @rclone_drive ||= "remote"
38
+ @path ||= ""
39
+ @chunk_size ||= 8388608
40
+ @max_retries ||= 3
41
+ @cmd_base = "#{rclone_exec} --config #{rclone_config} --retries #{max_retries} --drive-chunk-size=#{chunk_size} --drive-use-trash=false"
42
+ path.sub!(/^\//, "")
43
+ end
44
+
45
+ private
46
+
47
+
48
+ def transfer!
49
+ package.filenames.each do |filename|
50
+ src = File.join(Config.tmp_path, filename)
51
+ dest = File.join(remote_path, filename)
52
+ Logger.info "Storing '#{dest}'..."
53
+ puts `#{@cmd_base} copy #{src} #{rclone_drive}:#{dest}`
54
+ end
55
+ end
56
+
57
+ # Called by the Cycler.
58
+ # Any error raised will be logged as a warning.
59
+ def remove!(package)
60
+ Logger.info "Removing backup package dated #{package.time}..."
61
+
62
+ remote_path = remote_path_for(package)
63
+ package.filenames.each do |filename|
64
+ puts `#{@cmd_base} purge #{rclone_drive}:#{remote_path}`
65
+ end
66
+ end
67
+
68
+
69
+
70
+ ##
71
+ # The initial connection to Dropbox will provide the user with an
72
+ # authorization url. The user must open this URL and confirm that the
73
+ # authorization successfully took place. If this is the case, then the
74
+ # user hits 'enter' and the session will be properly established.
75
+ # Immediately after establishing the session, the session will be
76
+ # serialized and written to a cache file in +cache_path+.
77
+ # The cached file will be used from that point on to re-establish a
78
+ # connection with Dropbox at a later time. This allows the user to avoid
79
+ # having to go to a new Dropbox URL to authorize over and over again.
80
+ # def connection
81
+ # return @connection if @connection
82
+ #
83
+ # unless session = cached_session
84
+ # Logger.info "Creating a new session!"
85
+ # session = create_write_and_return_new_session!
86
+ # end
87
+ #
88
+ # # will raise an error if session not authorized
89
+ # @connection = DropboxClient.new(session, access_type)
90
+ # rescue => err
91
+ # raise Error.wrap(err, "Authorization Failed")
92
+ # end
93
+ #
94
+ # ##
95
+ # # Attempt to load a cached session
96
+ # def cached_session
97
+ # session = false
98
+ # if File.exist?(cached_file)
99
+ # begin
100
+ # session = DropboxSession.deserialize(File.read(cached_file))
101
+ # Logger.info "Session data loaded from cache!"
102
+ # rescue => err
103
+ # Logger.warn Error.wrap(err, <<-EOS)
104
+ # Could not read session data from cache.
105
+ # Cache data might be corrupt.
106
+ # EOS
107
+ # end
108
+ # end
109
+ # session
110
+ # end
111
+ #
112
+ # ##
113
+ # # Transfer each of the package files to Dropbox in chunks of +chunk_size+.
114
+ # # Each chunk will be retried +chunk_retries+ times, pausing +retry_waitsec+
115
+ # # between retries, if errors occur.
116
+ # def transfer!
117
+ # package.filenames.each do |filename|
118
+ # src = File.join(Config.tmp_path, filename)
119
+ # dest = File.join(remote_path, filename)
120
+ # Logger.info "Storing '#{dest}'..."
121
+ #
122
+ # uploader = nil
123
+ # File.open(src, "r") do |file|
124
+ # uploader = connection.get_chunked_uploader(file, file.stat.size)
125
+ # while uploader.offset < uploader.total_size
126
+ # with_retries do
127
+ # uploader.upload(1024**2 * chunk_size)
128
+ # end
129
+ # end
130
+ # end
131
+ #
132
+ # with_retries do
133
+ # uploader.finish(dest)
134
+ # end
135
+ # end
136
+ # rescue => err
137
+ # raise Error.wrap(err, "Upload Failed!")
138
+ # end
139
+ #
140
+ # def with_retries
141
+ # retries = 0
142
+ # begin
143
+ # yield
144
+ # rescue StandardError => err
145
+ # retries += 1
146
+ # raise if retries > max_retries
147
+ #
148
+ # Logger.info Error.wrap(err, "Retry ##{retries} of #{max_retries}.")
149
+ # sleep(retry_waitsec)
150
+ # retry
151
+ # end
152
+ # end
153
+ #
154
+ # # Called by the Cycler.
155
+ # # Any error raised will be logged as a warning.
156
+ # def remove!(package)
157
+ # Logger.info "Removing backup package dated #{package.time}..."
158
+ #
159
+ # connection.file_delete(remote_path_for(package))
160
+ # end
161
+ #
162
+ # def cached_file
163
+ # path = cache_path.start_with?("/") ?
164
+ # cache_path : File.join(Config.root_path, cache_path)
165
+ # File.join(path, api_key + api_secret)
166
+ # end
167
+ #
168
+ # ##
169
+ # # Serializes and writes the Dropbox session to a cache file
170
+ # def write_cache!(session)
171
+ # FileUtils.mkdir_p File.dirname(cached_file)
172
+ # File.open(cached_file, "w") do |cache_file|
173
+ # cache_file.write(session.serialize)
174
+ # end
175
+ # end
176
+ #
177
+ # ##
178
+ # # Create a new session, write a serialized version of it to the
179
+ # # .cache directory, and return the session object
180
+ # def create_write_and_return_new_session!
181
+ # require "timeout"
182
+ #
183
+ # session = DropboxSession.new(api_key, api_secret)
184
+ #
185
+ # # grab the request token for session
186
+ # session.get_request_token
187
+ #
188
+ # template = Backup::Template.new(
189
+ # session: session, cached_file: cached_file
190
+ # )
191
+ # template.render("storage/dropbox/authorization_url.erb")
192
+ #
193
+ # # wait for user to hit 'return' to continue
194
+ # Timeout.timeout(180) { STDIN.gets }
195
+ #
196
+ # # this will raise an error if the user did not
197
+ # # visit the authorization_url and grant access
198
+ # #
199
+ # # get the access token from the server
200
+ # # this will be stored with the session in the cache file
201
+ # session.get_access_token
202
+ #
203
+ # template.render("storage/dropbox/authorized.erb")
204
+ # write_cache!(session)
205
+ # template.render("storage/dropbox/cache_file_written.erb")
206
+ #
207
+ # session
208
+ # rescue => err
209
+ # raise Error.wrap(err, "Could not create or authenticate a new session")
210
+ # end
211
+ end
5
212
  end
6
213
  end
214
+
215
+ # Add our module name to the Backup DSL
216
+ Backup::Config::DSL.const_set("GoogleDrive", Module.new)
metadata CHANGED
@@ -1,24 +1,43 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: backup-googledrive
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.0.2
4
+ version: 0.1.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Joseph Rafferty
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2016-10-10 00:00:00.000000000 Z
12
- dependencies: []
13
- description: Adds the GoogleDrive storage engine to the Backup ruby gem.
14
- email: oraff@gmail.comj
11
+ date: 2018-04-27 00:00:00.000000000 Z
12
+ dependencies:
13
+ - !ruby/object:Gem::Dependency
14
+ name: backup
15
+ requirement: !ruby/object:Gem::Requirement
16
+ requirements:
17
+ - - "~>"
18
+ - !ruby/object:Gem::Version
19
+ version: '4.4'
20
+ - - ">="
21
+ - !ruby/object:Gem::Version
22
+ version: 4.4.1
23
+ type: :runtime
24
+ prerelease: false
25
+ version_requirements: !ruby/object:Gem::Requirement
26
+ requirements:
27
+ - - "~>"
28
+ - !ruby/object:Gem::Version
29
+ version: '4.4'
30
+ - - ">="
31
+ - !ruby/object:Gem::Version
32
+ version: 4.4.1
33
+ description:
34
+ email: joraff@gmail.com
15
35
  executables: []
16
36
  extensions: []
17
37
  extra_rdoc_files: []
18
38
  files:
19
39
  - lib/backup-googledrive.rb
20
- - lib/storage/googledrive.rb
21
- homepage: http://github.com/joraff/backup-googledrive
40
+ homepage: https://github.com/joraff/backup-googledrive
22
41
  licenses:
23
42
  - MIT
24
43
  metadata: {}
@@ -28,19 +47,18 @@ require_paths:
28
47
  - lib
29
48
  required_ruby_version: !ruby/object:Gem::Requirement
30
49
  requirements:
31
- - - '>='
50
+ - - ">="
32
51
  - !ruby/object:Gem::Version
33
52
  version: '0'
34
53
  required_rubygems_version: !ruby/object:Gem::Requirement
35
54
  requirements:
36
- - - '>='
55
+ - - ">="
37
56
  - !ruby/object:Gem::Version
38
57
  version: '0'
39
58
  requirements: []
40
59
  rubyforge_project:
41
- rubygems_version: 2.0.14.1
60
+ rubygems_version: 2.5.2
42
61
  signing_key:
43
62
  specification_version: 4
44
- summary: GoogleDrive storage engine to Backup
63
+ summary: Adds Google Drive as a storage option for Backup
45
64
  test_files: []
46
- has_rdoc:
@@ -1,149 +0,0 @@
1
- # encoding: utf-8
2
-
3
- module Backup
4
- module Storage
5
- class GoogleDrive < Base
6
-
7
- include Storage::Cycler
8
- class Error < Backup::Error; end
9
-
10
- # This adapter uses gdrive (https://github.com/prasmussen/gdrive) to handle all API interactions.
11
- # Fortunately for us gdrive handles things like timeouts, error retries and large file chunking, too!
12
- # I found gdrive's defaults to be acceptable, but should be easy to add accessors to customize if needed
13
-
14
- # Path to gdrive executable
15
- attr_accessor :gdrive_exe
16
-
17
- # Use the gdrive executable to obtain a refresh token. Add that token to your backup model.
18
- # The gdrive exe will handle refresing the access tokens
19
- attr_accessor :refresh_token
20
-
21
- ##
22
- # Creates a new instance of the storage object
23
- def initialize(model, storage_id = nil)
24
- super
25
-
26
- @path ||= 'backups'
27
- path.sub!(/^\//, '')
28
-
29
- required = %w{ refresh_token }
30
- raise Error, "Configuration Error: a refresh_token is required" if refresh_token.nil?
31
-
32
- raise Error, "Configuration Error: gdrive executable is required." if gdrive_exe.nil?
33
- end
34
-
35
- # private
36
-
37
-
38
- ##
39
- # Transfer each of the package files to Dropbox in chunks of +chunk_size+.
40
- # Each chunk will be retried +chunk_retries+ times, pausing +retry_waitsec+
41
- # between retries, if errors occur.
42
- def transfer!
43
- package.filenames.each do |filename|
44
- src = File.join(Config.tmp_path, filename)
45
- dest = File.join(remote_path, filename)
46
- Logger.info "Storing '#{ dest }'..."
47
-
48
- parent_id = find_id_from_path(remote_path)
49
- gdrive_upload(src, parent_id)
50
- end
51
- end
52
-
53
- # # Called by the Cycler.
54
- # # Any error raised will be logged as a warning.
55
- def remove!(package)
56
- Logger.info "Removing backup package dated #{ package.time }..."
57
- id = find_id_from_path(remote_path_for(package), false)
58
- if id.to_s.empty?
59
- raise Error, "Backup packge #{ package.time } not found in Google Drive"
60
- else
61
- gdrive_delete(id)
62
- end
63
- end
64
-
65
- def find_id_from_path(path = remote_path, create = true)
66
- parent = nil
67
- path.split('/').each do |path_part|
68
- id = get_folder_id(path_part, parent)
69
- if id.to_s.empty? && create
70
- id = gdrive_mkdir(path_part, parent)
71
- end
72
- parent = id
73
- end
74
- return parent
75
- end
76
-
77
-
78
- def get_folder_id(name, parent = nil)
79
- parent = parent ? parent : 'root'
80
- gdrive_list("name = '#{name}' and '#{parent}' in parents")
81
- end
82
-
83
- def gdrive_list(query)
84
- unless query.empty?
85
- cmd = "gdrive --refresh-token '#{refresh_token}' list --no-header -q \"#{query}\""
86
- output = `#{cmd}`
87
- if output.downcase.include? "error"
88
- raise Error, "Could not list or find the object with query string '#{query}'. gdrive output: #{output}"
89
- elsif output.empty?
90
- return nil
91
- else
92
- begin
93
- return /^([^ ]*).*/.match(output)[1] # will return an empty string on no match
94
- rescue => err
95
- return nil
96
- end
97
- end
98
- else
99
- raise Error, "A search query is required to list/find a file or folder"
100
- end
101
- end
102
-
103
- def gdrive_mkdir(name, parent = nil)
104
- unless name.empty?
105
- parent = parent ? parent : 'root'
106
- cmd = "gdrive --refresh-token '#{refresh_token}' mkdir -p '#{parent}' '#{name}'"
107
- output = `#{cmd}`
108
- if output.downcase.include? "error"
109
- raise Error, "Could not create the directory '#{name}' with parent '#{parent}'. gdrive output: #{output}"
110
- else
111
- id = /^Directory (.*?) created/.match(output)[1]
112
- raise Error, "Could not determine ID of newly created folder. See gdrive output: #{output}" if id.to_s.empty?
113
- Logger.info "Created folder #{name} successfully with id '#{id}'"
114
- return id
115
- end
116
- else
117
- raise Error, "Name parameter is required to make a directory"
118
- end
119
- end
120
-
121
- def gdrive_upload(src, parent = nil)
122
- parent = parent ? parent : 'root'
123
- cmd = "gdrive --refresh-token '#{refresh_token}' upload -p '#{parent}' '#{src}'"
124
- output = `#{cmd}`
125
- if ( ["error", "failed"].any? {|s| output.downcase.include? s } )
126
- raise Error, "Could not upload file. See gdrive output: #{output}"
127
- else
128
- begin
129
- id = /.*Uploaded (.*?) .*/.match(output)[1]
130
- raise Error, "empty id" if id.to_s.empty?
131
- Logger.info "Uploaded #{src} into parent folder '#{parent}' successfully. Google Drive file_id: #{ id }"
132
- rescue => err
133
- raise Error.wrap(err, "Could not determine ID of newly created folder. See gdrive output: #{output}")
134
- end
135
- end
136
- end
137
-
138
- def gdrive_delete(id, recursive = true)
139
- cmd = "gdrive --refresh-token '#{refresh_token}' delete #{'-r' if recursive} '#{id}'"
140
- output = `#{cmd}`
141
- if output.downcase.include? "error"
142
- raise Error, "Could not delete object with id: #{id}. See gdrive output: #{output}"
143
- else
144
- Logger.info output
145
- end
146
- end
147
- end
148
- end
149
- end