imap-backup 5.2.0 → 6.0.0.rc2

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 (52) hide show
  1. checksums.yaml +4 -4
  2. data/README.md +9 -2
  3. data/docs/development.md +10 -4
  4. data/lib/cli_coverage.rb +1 -1
  5. data/lib/imap/backup/account/connection.rb +7 -11
  6. data/lib/imap/backup/account.rb +31 -11
  7. data/lib/imap/backup/cli/folders.rb +3 -3
  8. data/lib/imap/backup/cli/migrate.rb +3 -3
  9. data/lib/imap/backup/cli/utils.rb +2 -2
  10. data/lib/imap/backup/configuration.rb +1 -11
  11. data/lib/imap/backup/downloader.rb +13 -9
  12. data/lib/imap/backup/serializer/directory.rb +37 -0
  13. data/lib/imap/backup/serializer/imap.rb +120 -0
  14. data/lib/imap/backup/serializer/mbox.rb +23 -94
  15. data/lib/imap/backup/serializer/mbox_enumerator.rb +2 -0
  16. data/lib/imap/backup/serializer.rb +180 -3
  17. data/lib/imap/backup/setup/account.rb +52 -29
  18. data/lib/imap/backup/setup/helpers.rb +1 -1
  19. data/lib/imap/backup/thunderbird/mailbox_exporter.rb +1 -1
  20. data/lib/imap/backup/version.rb +3 -3
  21. data/lib/imap/backup.rb +0 -1
  22. data/spec/features/backup_spec.rb +8 -16
  23. data/spec/features/support/aruba.rb +4 -3
  24. data/spec/unit/imap/backup/account/connection_spec.rb +36 -8
  25. data/spec/unit/imap/backup/account/folder_spec.rb +10 -0
  26. data/spec/unit/imap/backup/account_spec.rb +246 -0
  27. data/spec/unit/imap/backup/cli/accounts_spec.rb +12 -1
  28. data/spec/unit/imap/backup/cli/backup_spec.rb +19 -0
  29. data/spec/unit/imap/backup/cli/folders_spec.rb +39 -0
  30. data/spec/unit/imap/backup/cli/local_spec.rb +26 -7
  31. data/spec/unit/imap/backup/cli/migrate_spec.rb +80 -0
  32. data/spec/unit/imap/backup/cli/restore_spec.rb +67 -0
  33. data/spec/unit/imap/backup/cli/setup_spec.rb +17 -0
  34. data/spec/unit/imap/backup/cli/utils_spec.rb +68 -5
  35. data/spec/unit/imap/backup/cli_spec.rb +93 -0
  36. data/spec/unit/imap/backup/client/apple_mail_spec.rb +9 -0
  37. data/spec/unit/imap/backup/configuration_spec.rb +2 -2
  38. data/spec/unit/imap/backup/downloader_spec.rb +59 -7
  39. data/spec/unit/imap/backup/migrator_spec.rb +1 -1
  40. data/spec/unit/imap/backup/sanitizer_spec.rb +42 -0
  41. data/spec/unit/imap/backup/serializer/directory_spec.rb +37 -0
  42. data/spec/unit/imap/backup/serializer/imap_spec.rb +218 -0
  43. data/spec/unit/imap/backup/serializer/mbox_spec.rb +62 -183
  44. data/spec/unit/imap/backup/serializer_spec.rb +296 -0
  45. data/spec/unit/imap/backup/setup/account_spec.rb +120 -25
  46. data/spec/unit/imap/backup/setup/helpers_spec.rb +15 -0
  47. data/spec/unit/imap/backup/thunderbird/mailbox_exporter_spec.rb +116 -0
  48. data/spec/unit/imap/backup/uploader_spec.rb +1 -1
  49. data/spec/unit/retry_on_error_spec.rb +34 -0
  50. metadata +36 -7
  51. data/lib/imap/backup/serializer/mbox_store.rb +0 -217
  52. data/spec/unit/imap/backup/serializer/mbox_store_spec.rb +0 -329
@@ -11,10 +11,11 @@ describe Imap::Backup::Setup::Account do
11
11
  Imap::Backup::Account,
12
12
  username: existing_email,
13
13
  password: existing_password,
14
- server: current_server,
15
- connection_options: nil,
16
14
  local_path: "/backup/path",
17
15
  folders: [{name: "my_folder"}],
16
+ multi_fetch_size: multi_fetch_size,
17
+ server: current_server,
18
+ connection_options: connection_options,
18
19
  modified?: false
19
20
  )
20
21
  end
@@ -28,10 +29,12 @@ describe Imap::Backup::Setup::Account do
28
29
  let(:accounts) { [account, account1] }
29
30
  let(:existing_email) { "user@example.com" }
30
31
  let(:new_email) { "foo@example.com" }
31
- let(:current_server) { "imap.example.com" }
32
32
  let(:existing_password) { "password" }
33
33
  let(:other_email) { "other@example.com" }
34
34
  let(:other_existing_path) { "/other/existing/path" }
35
+ let(:multi_fetch_size) { 1 }
36
+ let(:current_server) { "imap.example.com" }
37
+ let(:connection_options) { nil }
35
38
 
36
39
  let(:highline) { HIGHLINE }
37
40
  let(:config) { CONFIG }
@@ -99,9 +102,11 @@ describe Imap::Backup::Setup::Account do
99
102
  [
100
103
  "modify email",
101
104
  "modify password",
102
- "modify server",
103
105
  "modify backup path",
104
106
  "choose backup folders",
107
+ "modify multi-fetch size (number of emails to fetch at a time)",
108
+ "modify server",
109
+ "modify connection options",
105
110
  "test connection",
106
111
  "delete",
107
112
  "(q) return to main menu",
@@ -136,7 +141,23 @@ describe Imap::Backup::Setup::Account do
136
141
  before { subject.run }
137
142
 
138
143
  it "indicates that a password is not set" do
139
- expect(menu.header).to include("password (unset)")
144
+ expect(menu.header).to match(/^password\s+\(unset\)/)
145
+ end
146
+ end
147
+
148
+ context "with multi_fetch_size" do
149
+ let(:multi_fetch_size) { 4 }
150
+
151
+ it "shows the size" do
152
+ expect(menu.header).to match(/^multi-fetch\s+4/)
153
+ end
154
+ end
155
+
156
+ context "with connection_options" do
157
+ let(:connection_options) { {some: "option"} }
158
+
159
+ it "shows the options" do
160
+ expect(menu.header).to match(/^connection options\s+'{"some":"option"}'/)
140
161
  end
141
162
  end
142
163
  end
@@ -240,23 +261,6 @@ describe Imap::Backup::Setup::Account do
240
261
  end
241
262
  end
242
263
 
243
- describe "choosing 'modify server'" do
244
- let(:server) { "server" }
245
-
246
- before do
247
- allow(account).to receive(:"server=")
248
- allow(highline).to receive(:ask).with("server: ") { server }
249
-
250
- subject.run
251
-
252
- menu.choices["modify server"].call
253
- end
254
-
255
- it "updates the server" do
256
- expect(account).to have_received(:"server=").with(server)
257
- end
258
- end
259
-
260
264
  describe "choosing 'modify backup path'" do
261
265
  let(:new_backup_path) { "/new/path" }
262
266
 
@@ -311,6 +315,97 @@ describe Imap::Backup::Setup::Account do
311
315
  end
312
316
  end
313
317
 
318
+ describe "choosing 'modify multi-fetch size'" do
319
+ let(:supplied) { "10" }
320
+
321
+ before do
322
+ allow(account).to receive(:multi_fetch_size=)
323
+ allow(highline).to receive(:ask).with("size: ") { supplied }
324
+
325
+ subject.run
326
+ menu.choices[
327
+ "modify multi-fetch size (number of emails to fetch at a time)"
328
+ ].call
329
+ end
330
+
331
+ it "sets the multi-fetch size" do
332
+ expect(account).to have_received(:multi_fetch_size=).with(10)
333
+ end
334
+
335
+ context "when the supplied value is not a number" do
336
+ let(:supplied) { "wrong!" }
337
+
338
+ it "does nothing" do
339
+ expect(account).to_not have_received(:multi_fetch_size=)
340
+ end
341
+ end
342
+
343
+ context "when the supplied value is not a positive number" do
344
+ let(:supplied) { "0" }
345
+
346
+ it "does nothing" do
347
+ expect(account).to_not have_received(:multi_fetch_size=)
348
+ end
349
+ end
350
+ end
351
+
352
+ describe "choosing 'modify server'" do
353
+ let(:server) { "server" }
354
+
355
+ before do
356
+ allow(account).to receive(:"server=")
357
+ allow(highline).to receive(:ask).with("server: ") { server }
358
+
359
+ subject.run
360
+
361
+ menu.choices["modify server"].call
362
+ end
363
+
364
+ it "updates the server" do
365
+ expect(account).to have_received(:"server=").with(server)
366
+ end
367
+ end
368
+
369
+ describe "choosing 'modify connection options'" do
370
+ context "when the JSON is well formed" do
371
+ let(:json) { "{}" }
372
+
373
+ before do
374
+ allow(highline).to receive(:ask).with("connections options (as JSON): ") { json }
375
+ allow(account).to receive(:"connection_options=")
376
+
377
+ subject.run
378
+
379
+ menu.choices["modify connection options"].call
380
+ end
381
+
382
+ it "updates the connection options" do
383
+ expect(account).to have_received(:"connection_options=").with(json)
384
+ end
385
+ end
386
+
387
+ context "when the JSON is malformed" do
388
+ before do
389
+ allow(highline).to receive(:ask).with("connections options (as JSON): ") { "xx" }
390
+ allow(account).to receive(:"connection_options=").and_raise(JSON::ParserError)
391
+ allow(highline).to receive(:ask).with("Press a key ")
392
+
393
+ subject.run
394
+
395
+ menu.choices["modify connection options"].call
396
+ end
397
+
398
+ it "does not fail" do
399
+ subject.run
400
+ end
401
+
402
+ it "reports the problem" do
403
+ expect(Kernel).to have_received(:puts).
404
+ with(/Malformed/)
405
+ end
406
+ end
407
+ end
408
+
314
409
  describe "choosing 'test connection'" do
315
410
  let(:connection_tester) do
316
411
  instance_double(
@@ -336,7 +431,7 @@ describe Imap::Backup::Setup::Account do
336
431
  let(:confirmed) { true }
337
432
 
338
433
  before do
339
- allow(account).to receive(:mark_for_deletion!)
434
+ allow(account).to receive(:mark_for_deletion)
340
435
  allow(highline).to receive(:agree) { confirmed }
341
436
  subject.run
342
437
  catch :done do
@@ -350,7 +445,7 @@ describe Imap::Backup::Setup::Account do
350
445
 
351
446
  context "when the user confirms deletion" do
352
447
  it "flags the account to be deleted" do
353
- expect(account).to have_received(:mark_for_deletion!)
448
+ expect(account).to have_received(:mark_for_deletion)
354
449
  end
355
450
  end
356
451
 
@@ -358,7 +453,7 @@ describe Imap::Backup::Setup::Account do
358
453
  let(:confirmed) { false }
359
454
 
360
455
  it "doesn't flag the account to be deleted" do
361
- expect(account).to_not have_received(:mark_for_deletion!)
456
+ expect(account).to_not have_received(:mark_for_deletion)
362
457
  end
363
458
  end
364
459
  end
@@ -0,0 +1,15 @@
1
+ module Imap::Backup
2
+ RSpec.describe Setup::Helpers do
3
+ describe "#title_prefix" do
4
+ it "is a string" do
5
+ expect(subject.title_prefix).to eq("imap-backup –")
6
+ end
7
+ end
8
+
9
+ describe "#version" do
10
+ it "is a version string" do
11
+ expect(subject.version).to match(/\A\d+\.\d+\.\d+(\.\w+)?\z/)
12
+ end
13
+ end
14
+ end
15
+ end
@@ -0,0 +1,116 @@
1
+ module Imap::Backup
2
+ describe Thunderbird::MailboxExporter do
3
+ subject { described_class.new("email", serializer, "profile", **args) }
4
+
5
+ let(:args) { {} }
6
+ let(:serializer) do
7
+ instance_double(
8
+ Serializer,
9
+ folder: "folder",
10
+ mbox_pathname: "mbox_pathname"
11
+ )
12
+ end
13
+ let(:local_folder) do
14
+ instance_double(
15
+ Thunderbird::LocalFolder,
16
+ exists?: local_folder_exists,
17
+ full_path: "full_path",
18
+ msf_exists?: msf_exists,
19
+ msf_path: "msf_path",
20
+ path: "path",
21
+ set_up: set_up_result
22
+ )
23
+ end
24
+ let(:local_folder_exists) { false }
25
+ let(:msf_exists) { false }
26
+ let(:set_up_result) { true }
27
+ let(:file) { instance_double(File, write: nil) }
28
+ let(:enumerator) { instance_double(Serializer::MboxEnumerator) }
29
+
30
+ before do
31
+ allow(File).to receive(:open).with("full_path", "w").and_yield(file)
32
+ allow(File).to receive(:unlink)
33
+ allow(Thunderbird::LocalFolder).to receive(:new) { local_folder }
34
+ allow(Serializer::MboxEnumerator).to receive(:new) { enumerator }
35
+ allow(enumerator).to receive(:each) { ["message"].enum_for(:each) }
36
+ allow(Email::Mboxrd::Message).to receive(:clean_serialized) { "cleaned" }
37
+ allow(Kernel).to receive(:puts)
38
+ end
39
+
40
+ describe "#run" do
41
+ let!(:result) { subject.run }
42
+
43
+ context "when the destination folder cannot be set up" do
44
+ let(:set_up_result) { false }
45
+
46
+ it "doesn't copy the mailbox" do
47
+ expect(file).to_not have_received(:write)
48
+ end
49
+
50
+ it "returns false" do
51
+ expect(result).to be false
52
+ end
53
+ end
54
+
55
+ context "when the .msf file exists" do
56
+ let(:msf_exists) { true }
57
+
58
+ context "when 'force' is set" do
59
+ let(:args) { {force: true} }
60
+
61
+ it "deletes the file" do
62
+ expect(File).to have_received(:unlink).with("msf_path")
63
+ end
64
+ end
65
+
66
+ context "when 'force' isn't set" do
67
+ it "doesn't copy the mailbox" do
68
+ expect(file).to_not have_received(:write)
69
+ end
70
+
71
+ it "returns false" do
72
+ expect(result).to be false
73
+ end
74
+ end
75
+ end
76
+
77
+ context "when the destination mailbox exists" do
78
+ let(:local_folder_exists) { true }
79
+
80
+ context "when 'force' is set" do
81
+ let(:args) { {force: true} }
82
+
83
+ it "writes the message" do
84
+ expect(file).to have_received(:write)
85
+ end
86
+
87
+ it "returns true" do
88
+ expect(result).to be true
89
+ end
90
+ end
91
+
92
+ context "when 'force' isn't set" do
93
+ it "doesn't copy the mailbox" do
94
+ expect(file).to_not have_received(:write)
95
+ end
96
+
97
+ it "returns false" do
98
+ expect(result).to be false
99
+ end
100
+ end
101
+ end
102
+
103
+ it "adds a 'From' line" do
104
+ expect(file).to have_received(:write).with(/From - \w+ \w+ \d+ \d+:\d+:\d+/)
105
+ end
106
+
107
+ it "writes the cleaned message" do
108
+ expect(file).to have_received(:write).with(/cleaned/)
109
+ end
110
+
111
+ it "returns true" do
112
+ expect(result).to be true
113
+ end
114
+ end
115
+ end
116
+ end
@@ -8,7 +8,7 @@ describe Imap::Backup::Uploader do
8
8
  end
9
9
  let(:serializer) do
10
10
  instance_double(
11
- Imap::Backup::Serializer::Mbox,
11
+ Imap::Backup::Serializer,
12
12
  uids: [1, 2],
13
13
  update_uid: nil
14
14
  )
@@ -0,0 +1,34 @@
1
+ class Retrier
2
+ class FooError < StandardError; end
3
+
4
+ include RetryOnError
5
+
6
+ def do_stuff(errors:, limit:)
7
+ calls = 0
8
+
9
+ retry_on_error(errors: errors, limit: limit) do
10
+ calls += 1
11
+ raise FooError if calls < 3
12
+
13
+ 42
14
+ end
15
+ end
16
+ end
17
+
18
+ RSpec.describe RetryOnError do
19
+ describe "#retry_on_error" do
20
+ subject { Retrier.new }
21
+
22
+ it "retries" do
23
+ expect(subject.do_stuff(errors: [Retrier::FooError], limit: 3)).to eq(42)
24
+ end
25
+
26
+ context "when the block fails more than the limit" do
27
+ it "fails" do
28
+ expect do
29
+ subject.do_stuff(errors: [Retrier::FooError], limit: 1)
30
+ end.to raise_error(Retrier::FooError)
31
+ end
32
+ end
33
+ end
34
+ end
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: imap-backup
3
3
  version: !ruby/object:Gem::Version
4
- version: 5.2.0
4
+ version: 6.0.0.rc2
5
5
  platform: ruby
6
6
  authors:
7
7
  - Joe Yates
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2022-02-24 00:00:00.000000000 Z
11
+ date: 2022-03-04 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: highline
@@ -211,9 +211,10 @@ files:
211
211
  - lib/imap/backup/migrator.rb
212
212
  - lib/imap/backup/sanitizer.rb
213
213
  - lib/imap/backup/serializer.rb
214
+ - lib/imap/backup/serializer/directory.rb
215
+ - lib/imap/backup/serializer/imap.rb
214
216
  - lib/imap/backup/serializer/mbox.rb
215
217
  - lib/imap/backup/serializer/mbox_enumerator.rb
216
- - lib/imap/backup/serializer/mbox_store.rb
217
218
  - lib/imap/backup/setup.rb
218
219
  - lib/imap/backup/setup/account.rb
219
220
  - lib/imap/backup/setup/asker.rb
@@ -256,25 +257,39 @@ files:
256
257
  - spec/unit/email/provider_spec.rb
257
258
  - spec/unit/imap/backup/account/connection_spec.rb
258
259
  - spec/unit/imap/backup/account/folder_spec.rb
260
+ - spec/unit/imap/backup/account_spec.rb
259
261
  - spec/unit/imap/backup/cli/accounts_spec.rb
262
+ - spec/unit/imap/backup/cli/backup_spec.rb
263
+ - spec/unit/imap/backup/cli/folders_spec.rb
260
264
  - spec/unit/imap/backup/cli/helpers_spec.rb
261
265
  - spec/unit/imap/backup/cli/local_spec.rb
266
+ - spec/unit/imap/backup/cli/migrate_spec.rb
267
+ - spec/unit/imap/backup/cli/restore_spec.rb
268
+ - spec/unit/imap/backup/cli/setup_spec.rb
262
269
  - spec/unit/imap/backup/cli/utils_spec.rb
270
+ - spec/unit/imap/backup/cli_spec.rb
271
+ - spec/unit/imap/backup/client/apple_mail_spec.rb
263
272
  - spec/unit/imap/backup/client/default_spec.rb
264
273
  - spec/unit/imap/backup/configuration_spec.rb
265
274
  - spec/unit/imap/backup/downloader_spec.rb
266
275
  - spec/unit/imap/backup/logger_spec.rb
267
276
  - spec/unit/imap/backup/migrator_spec.rb
277
+ - spec/unit/imap/backup/sanitizer_spec.rb
278
+ - spec/unit/imap/backup/serializer/directory_spec.rb
279
+ - spec/unit/imap/backup/serializer/imap_spec.rb
268
280
  - spec/unit/imap/backup/serializer/mbox_enumerator_spec.rb
269
281
  - spec/unit/imap/backup/serializer/mbox_spec.rb
270
- - spec/unit/imap/backup/serializer/mbox_store_spec.rb
282
+ - spec/unit/imap/backup/serializer_spec.rb
271
283
  - spec/unit/imap/backup/setup/account_spec.rb
272
284
  - spec/unit/imap/backup/setup/asker_spec.rb
273
285
  - spec/unit/imap/backup/setup/connection_tester_spec.rb
274
286
  - spec/unit/imap/backup/setup/folder_chooser_spec.rb
287
+ - spec/unit/imap/backup/setup/helpers_spec.rb
275
288
  - spec/unit/imap/backup/setup_spec.rb
289
+ - spec/unit/imap/backup/thunderbird/mailbox_exporter_spec.rb
276
290
  - spec/unit/imap/backup/uploader_spec.rb
277
291
  - spec/unit/imap/backup/utils_spec.rb
292
+ - spec/unit/retry_on_error_spec.rb
278
293
  homepage: https://github.com/joeyates/imap-backup
279
294
  licenses:
280
295
  - MIT
@@ -290,9 +305,9 @@ required_ruby_version: !ruby/object:Gem::Requirement
290
305
  version: '2.5'
291
306
  required_rubygems_version: !ruby/object:Gem::Requirement
292
307
  requirements:
293
- - - ">="
308
+ - - ">"
294
309
  - !ruby/object:Gem::Version
295
- version: '0'
310
+ version: 1.3.1
296
311
  requirements: []
297
312
  rubygems_version: 3.1.6
298
313
  signing_key:
@@ -300,18 +315,27 @@ specification_version: 4
300
315
  summary: Backup GMail (or other IMAP) accounts to disk
301
316
  test_files:
302
317
  - spec/spec_helper.rb
318
+ - spec/unit/imap/backup/account_spec.rb
319
+ - spec/unit/imap/backup/serializer/imap_spec.rb
320
+ - spec/unit/imap/backup/serializer/directory_spec.rb
303
321
  - spec/unit/imap/backup/serializer/mbox_spec.rb
304
- - spec/unit/imap/backup/serializer/mbox_store_spec.rb
305
322
  - spec/unit/imap/backup/serializer/mbox_enumerator_spec.rb
323
+ - spec/unit/imap/backup/cli_spec.rb
306
324
  - spec/unit/imap/backup/setup_spec.rb
307
325
  - spec/unit/imap/backup/logger_spec.rb
308
326
  - spec/unit/imap/backup/configuration_spec.rb
309
327
  - spec/unit/imap/backup/setup/account_spec.rb
310
328
  - spec/unit/imap/backup/setup/connection_tester_spec.rb
311
329
  - spec/unit/imap/backup/setup/asker_spec.rb
330
+ - spec/unit/imap/backup/setup/helpers_spec.rb
312
331
  - spec/unit/imap/backup/setup/folder_chooser_spec.rb
332
+ - spec/unit/imap/backup/cli/folders_spec.rb
333
+ - spec/unit/imap/backup/cli/setup_spec.rb
334
+ - spec/unit/imap/backup/cli/backup_spec.rb
335
+ - spec/unit/imap/backup/cli/restore_spec.rb
313
336
  - spec/unit/imap/backup/cli/helpers_spec.rb
314
337
  - spec/unit/imap/backup/cli/accounts_spec.rb
338
+ - spec/unit/imap/backup/cli/migrate_spec.rb
315
339
  - spec/unit/imap/backup/cli/utils_spec.rb
316
340
  - spec/unit/imap/backup/cli/local_spec.rb
317
341
  - spec/unit/imap/backup/downloader_spec.rb
@@ -320,7 +344,12 @@ test_files:
320
344
  - spec/unit/imap/backup/account/folder_spec.rb
321
345
  - spec/unit/imap/backup/account/connection_spec.rb
322
346
  - spec/unit/imap/backup/utils_spec.rb
347
+ - spec/unit/imap/backup/serializer_spec.rb
323
348
  - spec/unit/imap/backup/client/default_spec.rb
349
+ - spec/unit/imap/backup/client/apple_mail_spec.rb
350
+ - spec/unit/imap/backup/thunderbird/mailbox_exporter_spec.rb
351
+ - spec/unit/imap/backup/sanitizer_spec.rb
352
+ - spec/unit/retry_on_error_spec.rb
324
353
  - spec/unit/email/mboxrd/message_spec.rb
325
354
  - spec/unit/email/provider/gmail_spec.rb
326
355
  - spec/unit/email/provider/apple_mail_spec.rb