imap-backup 3.1.0 → 3.2.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
  SHA256:
3
- metadata.gz: a62efa6b4c2e63180629e4a9770cf6b636eb48fa1aaad0177d7bab975ce3f251
4
- data.tar.gz: ab0dee2784879df462e8fa1d622436b4a23f332dc731dd34ce353451a7164ce9
3
+ metadata.gz: b8661c03b8bf66e47c88fb69c476105945dad6fed867c510a151d62d0c401139
4
+ data.tar.gz: 8964a8ef14fd1f8b04d23745513b3bdb8ad8283d3875bd6d559ef00e5092d1f2
5
5
  SHA512:
6
- metadata.gz: ffe7443b8450980b05b170e8b4ed2d8cdaa093b17a60bf72c6c87c07e30752de1010aa444da3d4aea1b4348cbdd7b4fff0f2793e100f66de592796a8fa9885b6
7
- data.tar.gz: 5779c9d8bc98070cba06a68c571b849cdb33fb994954f6f3cf320642d1d49023ab9db5c6f713db26c413703caf046745589a61e5d4cde2e2c6bce751e7d6f3a1
6
+ metadata.gz: 8da502abc6b622f9261a223001a594e5e528d4f8871d209dd14a3bfd34693415bf5317b89d3bce892b0190027579d43f5c23f106f634423cb8fbf03fb82839e9
7
+ data.tar.gz: 610d08941b592e2cbdd29b46404f1e31f83bb31c5c83f57994cbef576ab9ec31b1f11d92851fec55810242661fc31cdb0fffe0816357cfe4177c29ba7c1d3134
@@ -2,6 +2,7 @@ require "net/imap"
2
2
  require "gmail_xoauth"
3
3
 
4
4
  require "gmail/authenticator"
5
+ require "retry_on_error"
5
6
 
6
7
  module Imap::Backup
7
8
  module Account; end
@@ -9,6 +10,10 @@ module Imap::Backup
9
10
  class Account::Connection
10
11
  class InvalidGmailOauth2RefreshToken < StandardError; end
11
12
 
13
+ include RetryOnError
14
+
15
+ LOGIN_RETRY_CLASSES = [EOFError, Errno::ECONNRESET, SocketError].freeze
16
+
12
17
  attr_reader :connection_options
13
18
  attr_reader :local_path
14
19
  attr_reader :password
@@ -79,26 +84,27 @@ module Imap::Backup
79
84
  end
80
85
 
81
86
  def imap
82
- return @imap unless @imap.nil?
83
-
84
- options = provider_options
85
- Imap::Backup.logger.debug(
86
- "Creating IMAP instance: #{server}, options: #{options.inspect}"
87
- )
88
- @imap = Net::IMAP.new(server, options)
89
- if gmail? && Gmail::Authenticator.refresh_token?(password)
90
- authenticator = Gmail::Authenticator.new(email: username, token: password)
91
- credentials = authenticator.credentials
92
- raise InvalidGmailOauth2RefreshToken if !credentials
93
-
94
- Imap::Backup.logger.debug "Logging in with OAuth2 token: #{username}"
95
- @imap.authenticate("XOAUTH2", username, credentials.access_token)
96
- else
97
- Imap::Backup.logger.debug "Logging in: #{username}/#{masked_password}"
98
- @imap.login(username, password)
99
- end
100
- Imap::Backup.logger.debug "Login complete"
101
- @imap
87
+ @imap ||=
88
+ retry_on_error(errors: LOGIN_RETRY_CLASSES) do
89
+ options = provider_options
90
+ Imap::Backup.logger.debug(
91
+ "Creating IMAP instance: #{server}, options: #{options.inspect}"
92
+ )
93
+ imap = Net::IMAP.new(server, options)
94
+ if gmail? && Gmail::Authenticator.refresh_token?(password)
95
+ authenticator = Gmail::Authenticator.new(email: username, token: password)
96
+ credentials = authenticator.credentials
97
+ raise InvalidGmailOauth2RefreshToken if !credentials
98
+
99
+ Imap::Backup.logger.debug "Logging in with OAuth2 token: #{username}"
100
+ imap.authenticate("XOAUTH2", username, credentials.access_token)
101
+ else
102
+ Imap::Backup.logger.debug "Logging in: #{username}/#{masked_password}"
103
+ imap.login(username, password)
104
+ end
105
+ Imap::Backup.logger.debug "Login complete"
106
+ imap
107
+ end
102
108
  end
103
109
 
104
110
  def server
@@ -175,7 +181,7 @@ module Imap::Backup
175
181
  def backup_folders
176
182
  @backup_folders ||=
177
183
  begin
178
- if @config_folders && @config_folders.any?
184
+ if @config_folders&.any?
179
185
  @config_folders
180
186
  else
181
187
  folders.map { |name| {name: name} }
@@ -1,5 +1,7 @@
1
1
  require "forwardable"
2
2
 
3
+ require "retry_on_error"
4
+
3
5
  module Imap::Backup
4
6
  module Account; end
5
7
 
@@ -7,8 +9,10 @@ module Imap::Backup
7
9
 
8
10
  class Account::Folder
9
11
  extend Forwardable
12
+ include RetryOnError
10
13
 
11
14
  REQUESTED_ATTRIBUTES = %w[RFC822 FLAGS INTERNALDATE].freeze
15
+ UID_FETCH_RETRY_CLASSES = [EOFError].freeze
12
16
 
13
17
  attr_reader :connection
14
18
  attr_reader :name
@@ -66,7 +70,10 @@ module Imap::Backup
66
70
 
67
71
  def fetch(uid)
68
72
  examine
69
- fetch_data_items = imap.uid_fetch([uid.to_i], REQUESTED_ATTRIBUTES)
73
+ fetch_data_items =
74
+ retry_on_error(errors: UID_FETCH_RETRY_CLASSES) do
75
+ imap.uid_fetch([uid.to_i], REQUESTED_ATTRIBUTES)
76
+ end
70
77
  return nil if fetch_data_items.nil?
71
78
 
72
79
  fetch_data_item = fetch_data_items[0]
@@ -51,18 +51,18 @@ module Imap::Backup
51
51
  private
52
52
 
53
53
  def data
54
- return @data if @data
55
-
56
- if File.exist?(pathname)
57
- Utils.check_permissions pathname, 0o600
58
- contents = File.read(pathname)
59
- @data = JSON.parse(contents, symbolize_names: true)
60
- else
61
- @data = {accounts: []}
62
- end
63
- @data[:debug] = false unless @data.include?(:debug)
64
- @data[:debug] = false unless [true, false].include?(@data[:debug])
65
- @data
54
+ @data ||=
55
+ begin
56
+ if File.exist?(pathname)
57
+ Utils.check_permissions pathname, 0o600
58
+ contents = File.read(pathname)
59
+ data = JSON.parse(contents, symbolize_names: true)
60
+ else
61
+ data = {accounts: []}
62
+ end
63
+ data[:debug] = data.key?(:debug) ? data[:debug] == true : false
64
+ data
65
+ end
66
66
  end
67
67
 
68
68
  def remove_modified_flags
@@ -161,7 +161,7 @@ module Imap::Backup
161
161
  def load_nth(index)
162
162
  enumerator = Serializer::MboxEnumerator.new(mbox_pathname)
163
163
  enumerator.each.with_index do |raw, i|
164
- next unless i == index
164
+ next if i != index
165
165
 
166
166
  return Email::Mboxrd::Message.from_serialized(raw)
167
167
  end
@@ -169,7 +169,7 @@ module Imap::Backup
169
169
  end
170
170
 
171
171
  def imap_looks_like_json?
172
- return false unless imap_exist?
172
+ return false if !imap_exist?
173
173
 
174
174
  content = File.read(imap_pathname)
175
175
  content.start_with?("{")
@@ -2,7 +2,7 @@ module Imap; end
2
2
 
3
3
  module Imap::Backup
4
4
  MAJOR = 3
5
- MINOR = 1
5
+ MINOR = 2
6
6
  REVISION = 0
7
7
  PRE = nil
8
8
  VERSION = [MAJOR, MINOR, REVISION, PRE].compact.map(&:to_s).join(".")
@@ -0,0 +1,14 @@
1
+ module RetryOnError
2
+ def retry_on_error(errors:, limit: 10)
3
+ tries ||= 1
4
+ yield
5
+ rescue *errors => e
6
+ if tries < limit
7
+ message = "#{e}, attempt #{tries} of #{limit}"
8
+ Imap::Backup.logger.debug message
9
+ tries += 1
10
+ retry
11
+ end
12
+ raise e
13
+ end
14
+ end
@@ -77,13 +77,15 @@ describe Imap::Backup::Account::Connection do
77
77
  end
78
78
 
79
79
  describe "#imap" do
80
- let!(:result) { subject.imap }
80
+ let(:result) { subject.imap }
81
81
 
82
82
  it "returns the IMAP connection" do
83
83
  expect(result).to eq(imap)
84
84
  end
85
85
 
86
86
  it "uses the password" do
87
+ result
88
+
87
89
  expect(imap).to have_received(:login).with(USERNAME, PASSWORD)
88
90
  end
89
91
 
@@ -138,7 +140,24 @@ describe Imap::Backup::Account::Connection do
138
140
  end
139
141
  end
140
142
 
141
- include_examples "connects to IMAP"
143
+ context "when the first login attempt fails" do
144
+ before do
145
+ outcomes = [-> { raise EOFError }, -> { true }]
146
+ allow(imap).to receive(:login) { outcomes.shift.call }
147
+ end
148
+
149
+ it "retries" do
150
+ subject.imap
151
+
152
+ expect(imap).to have_received(:login).twice
153
+ end
154
+ end
155
+
156
+ context "when run" do
157
+ before { subject.imap }
158
+
159
+ include_examples "connects to IMAP"
160
+ end
142
161
  end
143
162
 
144
163
  describe "#folders" do
@@ -1,8 +1,8 @@
1
1
  # rubocop:disable RSpec/PredicateMatcher
2
2
 
3
3
  describe Imap::Backup::Account::Folder do
4
- FOLDER_NAME = "Gelöscht"
5
- ENCODED_FOLDER_NAME = "Gel&APY-scht"
4
+ FOLDER_NAME = "Gelöscht".freeze
5
+ ENCODED_FOLDER_NAME = "Gel&APY-scht".freeze
6
6
 
7
7
  subject { described_class.new(connection, FOLDER_NAME) }
8
8
 
@@ -103,6 +103,23 @@ describe Imap::Backup::Account::Folder do
103
103
  expect(subject.fetch(123)).to be_nil
104
104
  end
105
105
  end
106
+
107
+ context "when the first fetch_uid attempts fail" do
108
+ before do
109
+ outcomes = [-> { raise EOFError }, -> { [fetch_data_item] }]
110
+ allow(imap).to receive(:uid_fetch) { outcomes.shift.call }
111
+ end
112
+
113
+ it "retries" do
114
+ subject.fetch(123)
115
+
116
+ expect(imap).to have_received(:uid_fetch).twice
117
+ end
118
+
119
+ it "succeeds" do
120
+ subject.fetch(123)
121
+ end
122
+ end
106
123
  end
107
124
 
108
125
  describe "#folder" do
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: imap-backup
3
3
  version: !ruby/object:Gem::Version
4
- version: 3.1.0
4
+ version: 3.2.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Joe Yates
@@ -221,6 +221,7 @@ files:
221
221
  - lib/imap/backup/uploader.rb
222
222
  - lib/imap/backup/utils.rb
223
223
  - lib/imap/backup/version.rb
224
+ - lib/retry_on_error.rb
224
225
  - spec/features/backup_spec.rb
225
226
  - spec/features/helper.rb
226
227
  - spec/features/restore_spec.rb