imap-backup 2.1.0 → 2.1.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.
Files changed (37) hide show
  1. checksums.yaml +4 -4
  2. data/.rspec +0 -1
  3. data/.rubocop.yml +5 -2
  4. data/.rubocop_todo.yml +24 -0
  5. data/.travis.yml +13 -1
  6. data/README.md +9 -21
  7. data/Rakefile +6 -2
  8. data/imap-backup.gemspec +5 -1
  9. data/lib/email/mboxrd/message.rb +13 -12
  10. data/lib/imap/backup/account/connection.rb +3 -2
  11. data/lib/imap/backup/account/folder.rb +2 -0
  12. data/lib/imap/backup/configuration/account.rb +20 -16
  13. data/lib/imap/backup/configuration/asker.rb +1 -1
  14. data/lib/imap/backup/serializer/mbox.rb +39 -25
  15. data/lib/imap/backup/serializer/mbox_enumerator.rb +31 -0
  16. data/lib/imap/backup/serializer/mbox_store.rb +6 -27
  17. data/lib/imap/backup/version.rb +1 -1
  18. data/spec/features/support/email_server.rb +3 -0
  19. data/spec/support/fixtures.rb +1 -1
  20. data/spec/unit/email/mboxrd/message_spec.rb +20 -3
  21. data/spec/unit/email/provider_spec.rb +1 -1
  22. data/spec/unit/imap/backup/account/connection_spec.rb +10 -9
  23. data/spec/unit/imap/backup/account/folder_spec.rb +24 -10
  24. data/spec/unit/imap/backup/configuration/account_spec.rb +47 -22
  25. data/spec/unit/imap/backup/configuration/asker_spec.rb +5 -9
  26. data/spec/unit/imap/backup/configuration/connection_tester_spec.rb +6 -6
  27. data/spec/unit/imap/backup/configuration/folder_chooser_spec.rb +6 -6
  28. data/spec/unit/imap/backup/configuration/list_spec.rb +24 -1
  29. data/spec/unit/imap/backup/configuration/setup_spec.rb +29 -9
  30. data/spec/unit/imap/backup/configuration/store_spec.rb +5 -5
  31. data/spec/unit/imap/backup/downloader_spec.rb +11 -13
  32. data/spec/unit/imap/backup/serializer/mbox_enumerator_spec.rb +40 -0
  33. data/spec/unit/imap/backup/serializer/mbox_spec.rb +63 -24
  34. data/spec/unit/imap/backup/serializer/mbox_store_spec.rb +162 -9
  35. data/spec/unit/imap/backup/utils_spec.rb +3 -3
  36. data/spec/unit/imap/backup_spec.rb +28 -0
  37. metadata +8 -2
@@ -1,6 +1,7 @@
1
1
  require "json"
2
2
 
3
3
  require "email/mboxrd/message"
4
+ require "imap/backup/serializer/mbox_enumerator"
4
5
 
5
6
  module Imap::Backup
6
7
  class Serializer::MboxStore
@@ -71,8 +72,9 @@ module Imap::Backup
71
72
  end
72
73
  end
73
74
 
74
- def load(uid)
75
+ def load(uid_maybe_string)
75
76
  do_load if !loaded
77
+ uid = uid_maybe_string.to_i
76
78
  message_index = uids.find_index(uid)
77
79
  return nil if message_index.nil?
78
80
 
@@ -103,16 +105,12 @@ module Imap::Backup
103
105
  @folder = new_name
104
106
  end
105
107
 
106
- def relative_path
107
- File.dirname(folder)
108
- end
109
-
110
108
  private
111
109
 
112
110
  def do_load
113
111
  data = imap_data
114
112
  if data
115
- @uids = data[:uids].map(&:to_i).sort
113
+ @uids = data[:uids].map(&:to_i)
116
114
  @uid_validity = data[:uid_validity]
117
115
  @loaded = true
118
116
  else
@@ -145,7 +143,8 @@ module Imap::Backup
145
143
  end
146
144
 
147
145
  def load_nth(index)
148
- each_mbox_message.with_index do |raw, i|
146
+ enumerator = Serializer::MboxEnumerator.new(mbox_pathname)
147
+ enumerator.each.with_index do |raw, i|
149
148
  next unless i == index
150
149
 
151
150
  return Email::Mboxrd::Message.from_serialized(raw)
@@ -153,26 +152,6 @@ module Imap::Backup
153
152
  nil
154
153
  end
155
154
 
156
- def each_mbox_message
157
- Enumerator.new do |e|
158
- File.open(mbox_pathname) do |f|
159
- lines = []
160
-
161
- loop do
162
- line = f.gets
163
- break if !line
164
- if line.start_with?("From ")
165
- e.yield lines.join if lines.count.positive?
166
- lines = [line]
167
- else
168
- lines << line
169
- end
170
- end
171
- e.yield lines.join if lines.count.positive?
172
- end
173
- end
174
- end
175
-
176
155
  def imap_looks_like_json?
177
156
  return false unless imap_exist?
178
157
 
@@ -3,6 +3,6 @@ module Imap; end
3
3
  module Imap::Backup
4
4
  MAJOR = 2
5
5
  MINOR = 1
6
- REVISION = 0
6
+ REVISION = 1
7
7
  VERSION = [MAJOR, MINOR, REVISION].compact.map(&:to_s).join(".")
8
8
  end
@@ -85,6 +85,9 @@ module EmailServerHelpers
85
85
  def server_delete_folder(folder)
86
86
  imap.delete folder
87
87
  imap.disconnect
88
+ rescue StandardError => e
89
+ puts e.to_s
90
+ ensure
88
91
  @imap = nil
89
92
  end
90
93
 
@@ -2,5 +2,5 @@ def fixture(name)
2
2
  spec_root = File.expand_path("..", File.dirname(__FILE__))
3
3
  fixture_path = File.join(spec_root, "fixtures", name + ".yml")
4
4
  fixture = File.read(fixture_path)
5
- YAML.safe_load(fixture, permitted_classes: [Symbol])
5
+ YAML.safe_load(fixture, [Symbol])
6
6
  end
@@ -72,7 +72,7 @@ describe Email::Mboxrd::Message do
72
72
  end
73
73
  end
74
74
 
75
- context "#to_serialized" do
75
+ describe "#to_serialized" do
76
76
  let(:mail) { instance_double(Mail::Message, from: [from], date: date) }
77
77
 
78
78
  before do
@@ -107,7 +107,7 @@ describe Email::Mboxrd::Message do
107
107
  end
108
108
  end
109
109
 
110
- context "#from" do
110
+ describe "From" do
111
111
  before do
112
112
  # call original for these tests because we want to test the behaviour of
113
113
  # class-under-test given different behaviour of the Mail parser
@@ -148,7 +148,7 @@ describe Email::Mboxrd::Message do
148
148
  end
149
149
  end
150
150
 
151
- context "#date" do
151
+ describe "#date" do
152
152
  let(:message_body) { msg_good }
153
153
 
154
154
  it "returns the date" do
@@ -163,4 +163,21 @@ describe Email::Mboxrd::Message do
163
163
  end
164
164
  end
165
165
  end
166
+
167
+ describe "#imap_body" do
168
+ let(:message_body) { "Ciao" }
169
+
170
+ it "returns the supplied body" do
171
+ expect(subject.imap_body).to eq(message_body)
172
+ end
173
+
174
+ context "when newlines are not IMAP standard" do
175
+ let(:message_body) { "Ciao\nHello" }
176
+ let(:corrected) { "Ciao\r\nHello" }
177
+
178
+ it "corrects them" do
179
+ expect(subject.imap_body).to eq(corrected)
180
+ end
181
+ end
182
+ end
166
183
  end
@@ -1,6 +1,6 @@
1
1
  describe Email::Provider do
2
2
  describe ".for_address" do
3
- context "known providers" do
3
+ context "with known providers" do
4
4
  [
5
5
  ["gmail.com", :gmail],
6
6
  ["fastmail.fm", :fastmail]
@@ -33,7 +33,7 @@ describe Imap::Backup::Account::Connection do
33
33
  Imap::Backup::Serializer::Mbox,
34
34
  folder: serialized_folder,
35
35
  force_uid_validity: nil,
36
- set_uid_validity: new_uid_validity,
36
+ apply_uid_validity: new_uid_validity,
37
37
  uids: [local_uid]
38
38
  )
39
39
  end
@@ -58,7 +58,7 @@ describe Imap::Backup::Account::Connection do
58
58
  end
59
59
  end
60
60
 
61
- context "#initialize" do
61
+ describe "#initialize" do
62
62
  [
63
63
  [:username, "username@gmail.com"],
64
64
  [:local_path, "local_path"],
@@ -85,7 +85,7 @@ describe Imap::Backup::Account::Connection do
85
85
  include_examples "connects to IMAP"
86
86
  end
87
87
 
88
- context "#folders" do
88
+ describe "#folders" do
89
89
  let(:imap_folders) do
90
90
  [instance_double(Net::IMAP::MailboxList)]
91
91
  end
@@ -95,7 +95,7 @@ describe Imap::Backup::Account::Connection do
95
95
  end
96
96
  end
97
97
 
98
- context "#status" do
98
+ describe "#status" do
99
99
  let(:folder) do
100
100
  instance_double(Imap::Backup::Account::Folder, uids: [remote_uid])
101
101
  end
@@ -119,7 +119,7 @@ describe Imap::Backup::Account::Connection do
119
119
  end
120
120
  end
121
121
 
122
- context "#run_backup" do
122
+ describe "#run_backup" do
123
123
  let(:folder) do
124
124
  instance_double(
125
125
  Imap::Backup::Account::Folder,
@@ -202,7 +202,7 @@ describe Imap::Backup::Account::Connection do
202
202
  end
203
203
  end
204
204
 
205
- context "#restore" do
205
+ describe "#restore" do
206
206
  let(:folder) do
207
207
  instance_double(
208
208
  Imap::Backup::Account::Folder,
@@ -253,7 +253,8 @@ describe Imap::Backup::Account::Connection do
253
253
  end
254
254
 
255
255
  it "sets local uid validity" do
256
- expect(serializer).to have_received(:set_uid_validity).with(uid_validity)
256
+ expect(serializer).
257
+ to have_received(:apply_uid_validity).with(uid_validity)
257
258
  end
258
259
 
259
260
  context "when folders exist" do
@@ -297,7 +298,7 @@ describe Imap::Backup::Account::Connection do
297
298
  end
298
299
  end
299
300
 
300
- context "#reconnect" do
301
+ describe "#reconnect" do
301
302
  before { subject.reconnect }
302
303
 
303
304
  it "disconnects from the server" do
@@ -311,7 +312,7 @@ describe Imap::Backup::Account::Connection do
311
312
  end
312
313
  end
313
314
 
314
- context "#disconnect" do
315
+ describe "#disconnect" do
315
316
  before { subject.disconnect }
316
317
 
317
318
  it "disconnects from the server" do
@@ -25,7 +25,7 @@ describe Imap::Backup::Account::Folder do
25
25
  let(:responses) { [] }
26
26
  let(:append_response) { nil }
27
27
 
28
- context "#uids" do
28
+ describe "#uids" do
29
29
  let(:uids) { [5678, 123] }
30
30
 
31
31
  before { allow(imap).to receive(:uid_search).and_return(uids) }
@@ -45,7 +45,7 @@ describe Imap::Backup::Account::Folder do
45
45
  end
46
46
  end
47
47
 
48
- context "#fetch" do
48
+ describe "#fetch" do
49
49
  let(:message_body) { instance_double(String, force_encoding: nil) }
50
50
  let(:attributes) { {"RFC822" => message_body, "other" => "xxx"} }
51
51
  let(:fetch_data_item) do
@@ -58,7 +58,7 @@ describe Imap::Backup::Account::Folder do
58
58
  expect(subject.fetch(123)).to eq(attributes)
59
59
  end
60
60
 
61
- context "if the server responds with nothing" do
61
+ context "when the server responds with nothing" do
62
62
  before { allow(imap).to receive(:uid_fetch) { nil } }
63
63
 
64
64
  it "is nil" do
@@ -66,7 +66,7 @@ describe Imap::Backup::Account::Folder do
66
66
  end
67
67
  end
68
68
 
69
- context "if the mailbox doesn't exist" do
69
+ context "when the mailbox doesn't exist" do
70
70
  before do
71
71
  allow(imap).to receive(:examine).and_raise(missing_mailbox_error)
72
72
  end
@@ -76,6 +76,14 @@ describe Imap::Backup::Account::Folder do
76
76
  end
77
77
  end
78
78
 
79
+ context "when the response doesn't have RFC822" do
80
+ let(:attributes) { {} }
81
+
82
+ it "is nil" do
83
+ expect(subject.fetch(123)).to be_nil
84
+ end
85
+ end
86
+
79
87
  it "sets the encoding on the message" do
80
88
  subject.fetch(123)
81
89
 
@@ -83,13 +91,13 @@ describe Imap::Backup::Account::Folder do
83
91
  end
84
92
  end
85
93
 
86
- context "#folder" do
94
+ describe "#folder" do
87
95
  it "is the name" do
88
96
  expect(subject.folder).to eq("my_folder")
89
97
  end
90
98
  end
91
99
 
92
- context "#exist?" do
100
+ describe "#exist?" do
93
101
  context "when the folder exists" do
94
102
  it "is true" do
95
103
  expect(subject.exist?).to be_truthy
@@ -107,7 +115,7 @@ describe Imap::Backup::Account::Folder do
107
115
  end
108
116
  end
109
117
 
110
- context "#create" do
118
+ describe "#create" do
111
119
  context "when the folder exists" do
112
120
  before { subject.create }
113
121
 
@@ -128,7 +136,7 @@ describe Imap::Backup::Account::Folder do
128
136
  end
129
137
  end
130
138
 
131
- context "#uid_validity" do
139
+ describe "#uid_validity" do
132
140
  let(:responses) { {"UIDVALIDITY" => ["x", "uid validity"]} }
133
141
 
134
142
  it "is returned" do
@@ -148,14 +156,15 @@ describe Imap::Backup::Account::Folder do
148
156
  end
149
157
  end
150
158
 
151
- context "#append" do
159
+ describe "#append" do
152
160
  let(:message) do
153
161
  instance_double(
154
162
  Email::Mboxrd::Message,
155
163
  imap_body: "imap body",
156
- date: Time.now
164
+ date: message_date
157
165
  )
158
166
  end
167
+ let(:message_date) { Time.new(2010, 10, 10, 9, 15, 22, 0) }
159
168
  let(:append_response) do
160
169
  OpenStruct.new(data: OpenStruct.new(code: OpenStruct.new(data: "1 2")))
161
170
  end
@@ -169,6 +178,11 @@ describe Imap::Backup::Account::Folder do
169
178
  expect(imap).to have_received(:append)
170
179
  end
171
180
 
181
+ it "sets the date and time" do
182
+ expect(imap).to have_received(:append).
183
+ with(anything, anything, anything, message_date)
184
+ end
185
+
172
186
  it "returns the new uid" do
173
187
  expect(result).to eq(2)
174
188
  end
@@ -18,7 +18,7 @@ describe Imap::Backup::Configuration::Account do
18
18
  end
19
19
  end
20
20
 
21
- context "#initialize" do
21
+ describe "#initialize" do
22
22
  subject { described_class.new(store, account, highline) }
23
23
 
24
24
  let(:store) { "store" }
@@ -32,7 +32,7 @@ describe Imap::Backup::Configuration::Account do
32
32
  end
33
33
  end
34
34
 
35
- context "#run" do
35
+ describe "#run" do
36
36
  subject { described_class.new(store, account, highline) }
37
37
 
38
38
  let(:highline) { instance_double(HighLine) }
@@ -72,21 +72,21 @@ describe Imap::Backup::Configuration::Account do
72
72
  end
73
73
  end
74
74
 
75
- context "preparation" do
75
+ describe "preparation" do
76
76
  before { subject.run }
77
77
 
78
78
  it "clears the screen" do
79
79
  expect(Kernel).to have_received(:system).with("clear")
80
80
  end
81
81
 
82
- context "menu" do
82
+ describe "menu" do
83
83
  it "shows the menu" do
84
84
  expect(highline).to have_received(:choose)
85
85
  end
86
86
  end
87
87
  end
88
88
 
89
- context "menu" do
89
+ describe "menu" do
90
90
  [
91
91
  "modify email",
92
92
  "modify password",
@@ -106,7 +106,7 @@ describe Imap::Backup::Configuration::Account do
106
106
  end
107
107
  end
108
108
 
109
- context "account details" do
109
+ describe "account details" do
110
110
  [
111
111
  ["email", /email:\s+user@example.com/],
112
112
  ["server", /server:\s+imap.example.com/],
@@ -132,7 +132,7 @@ describe Imap::Backup::Configuration::Account do
132
132
  end
133
133
  end
134
134
 
135
- context "email" do
135
+ describe "email" do
136
136
  before do
137
137
  allow(Imap::Backup::Configuration::Asker).
138
138
  to receive(:email) { new_email }
@@ -140,7 +140,7 @@ describe Imap::Backup::Configuration::Account do
140
140
  menu.choices["modify email"].call
141
141
  end
142
142
 
143
- context "if the server is blank" do
143
+ context "when the server is blank" do
144
144
  [
145
145
  ["GMail", "foo@gmail.com", "imap.gmail.com"],
146
146
  ["Fastmail", "bar@fastmail.fm", "imap.fastmail.com"],
@@ -166,9 +166,24 @@ describe Imap::Backup::Configuration::Account do
166
166
  end
167
167
  end
168
168
  end
169
+
170
+ context "when the domain is unrecognized" do
171
+ let(:existing_server) { nil }
172
+ let(:provider) do
173
+ instance_double(Email::Provider, provider: :default)
174
+ end
175
+
176
+ before do
177
+ allow(Email::Provider).to receive(:for_address) { provider }
178
+ end
179
+
180
+ it "does not set a default server" do
181
+ expect(account[:server]).to be_nil
182
+ end
183
+ end
169
184
  end
170
185
 
171
- context "the email is new" do
186
+ context "when the email is new" do
172
187
  it "modifies the email address" do
173
188
  expect(account[:username]).to eq(new_email)
174
189
  end
@@ -176,7 +191,7 @@ describe Imap::Backup::Configuration::Account do
176
191
  include_examples "it flags the account as modified"
177
192
  end
178
193
 
179
- context "the email already exists" do
194
+ context "when the email already exists" do
180
195
  let(:new_email) { other_email }
181
196
 
182
197
  it "indicates the error" do
@@ -192,7 +207,7 @@ describe Imap::Backup::Configuration::Account do
192
207
  end
193
208
  end
194
209
 
195
- context "password" do
210
+ describe "password" do
196
211
  let(:new_password) { "new_password" }
197
212
 
198
213
  before do
@@ -202,7 +217,7 @@ describe Imap::Backup::Configuration::Account do
202
217
  menu.choices["modify password"].call
203
218
  end
204
219
 
205
- context "if the user enters a password" do
220
+ context "when the user enters a password" do
206
221
  it "updates the password" do
207
222
  expect(account[:password]).to eq(new_password)
208
223
  end
@@ -210,7 +225,7 @@ describe Imap::Backup::Configuration::Account do
210
225
  include_examples "it flags the account as modified"
211
226
  end
212
227
 
213
- context "if the user cancels" do
228
+ context "when the user cancels" do
214
229
  let(:new_password) { nil }
215
230
 
216
231
  it "does nothing" do
@@ -221,7 +236,7 @@ describe Imap::Backup::Configuration::Account do
221
236
  end
222
237
  end
223
238
 
224
- context "server" do
239
+ describe "server" do
225
240
  let(:server) { "server" }
226
241
 
227
242
  before do
@@ -237,7 +252,7 @@ describe Imap::Backup::Configuration::Account do
237
252
  include_examples "it flags the account as modified"
238
253
  end
239
254
 
240
- context "backup_path" do
255
+ describe "backup_path" do
241
256
  let(:new_backup_path) { "/new/path" }
242
257
 
243
258
  before do
@@ -256,16 +271,26 @@ describe Imap::Backup::Configuration::Account do
256
271
  expect(account[:local_path]).to eq(new_backup_path)
257
272
  end
258
273
 
259
- it "validates that the path is not used by other backups" do
260
- # rubocop:disable RSpec/InstanceVariable
261
- expect(@validator.call(other_existing_path)).to be_falsey
262
- # rubocop:enable RSpec/InstanceVariable
274
+ context "when the path is not used by other backups" do
275
+ it "is accepts it" do
276
+ # rubocop:disable RSpec/InstanceVariable
277
+ expect(@validator.call("/unknown/path")).to be_truthy
278
+ # rubocop:enable RSpec/InstanceVariable
279
+ end
280
+ end
281
+
282
+ context "when the path is used by other backups" do
283
+ it "fails validation" do
284
+ # rubocop:disable RSpec/InstanceVariable
285
+ expect(@validator.call(other_existing_path)).to be_falsey
286
+ # rubocop:enable RSpec/InstanceVariable
287
+ end
263
288
  end
264
289
 
265
290
  include_examples "it flags the account as modified"
266
291
  end
267
292
 
268
- context "folders" do
293
+ describe "folders" do
269
294
  let(:chooser) do
270
295
  instance_double(Imap::Backup::Configuration::FolderChooser, run: nil)
271
296
  end
@@ -282,7 +307,7 @@ describe Imap::Backup::Configuration::Account do
282
307
  end
283
308
  end
284
309
 
285
- context "connection test" do
310
+ describe "connection test" do
286
311
  before do
287
312
  allow(Imap::Backup::Configuration::ConnectionTester).
288
313
  to receive(:test).and_return("All fine")
@@ -297,7 +322,7 @@ describe Imap::Backup::Configuration::Account do
297
322
  end
298
323
  end
299
324
 
300
- context "deletion" do
325
+ describe "deletion" do
301
326
  let(:confirmed) { true }
302
327
 
303
328
  before do