imap-backup 5.2.0 → 6.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.
Files changed (102) hide show
  1. checksums.yaml +4 -4
  2. data/README.md +9 -2
  3. data/docs/development.md +10 -4
  4. data/imap-backup.gemspec +5 -1
  5. data/lib/cli_coverage.rb +11 -11
  6. data/lib/email/provider/base.rb +2 -0
  7. data/lib/email/provider/unknown.rb +2 -0
  8. data/lib/email/provider.rb +2 -0
  9. data/lib/imap/backup/account/connection/backup_folders.rb +27 -0
  10. data/lib/imap/backup/account/connection/client_factory.rb +54 -0
  11. data/lib/imap/backup/account/connection/folder_names.rb +26 -0
  12. data/lib/imap/backup/account/connection.rb +17 -105
  13. data/lib/imap/backup/account/folder.rb +9 -6
  14. data/lib/imap/backup/account.rb +36 -16
  15. data/lib/imap/backup/cli/backup.rb +1 -3
  16. data/lib/imap/backup/cli/folders.rb +3 -3
  17. data/lib/imap/backup/cli/helpers.rb +24 -22
  18. data/lib/imap/backup/cli/local.rb +20 -13
  19. data/lib/imap/backup/cli/migrate.rb +5 -11
  20. data/lib/imap/backup/cli/restore.rb +8 -7
  21. data/lib/imap/backup/cli/setup.rb +10 -8
  22. data/lib/imap/backup/cli/stats.rb +78 -0
  23. data/lib/imap/backup/cli/status.rb +2 -2
  24. data/lib/imap/backup/cli/utils.rb +6 -8
  25. data/lib/imap/backup/cli.rb +24 -3
  26. data/lib/imap/backup/configuration.rb +9 -21
  27. data/lib/imap/backup/downloader.rb +56 -34
  28. data/lib/imap/backup/migrator.rb +5 -5
  29. data/lib/imap/backup/sanitizer.rb +3 -2
  30. data/lib/imap/backup/serializer/appender.rb +49 -0
  31. data/lib/imap/backup/serializer/directory.rb +37 -0
  32. data/lib/imap/backup/serializer/imap.rb +144 -0
  33. data/lib/imap/backup/serializer/mbox.rb +33 -88
  34. data/lib/imap/backup/serializer/mbox_enumerator.rb +2 -0
  35. data/lib/imap/backup/serializer/message_enumerator.rb +29 -0
  36. data/lib/imap/backup/serializer/unused_name_finder.rb +25 -0
  37. data/lib/imap/backup/serializer.rb +160 -3
  38. data/lib/imap/backup/setup/account/header.rb +75 -0
  39. data/lib/imap/backup/setup/account.rb +41 -95
  40. data/lib/imap/backup/setup/asker.rb +4 -15
  41. data/lib/imap/backup/setup/backup_path.rb +41 -0
  42. data/lib/imap/backup/setup/email.rb +45 -0
  43. data/lib/imap/backup/setup/folder_chooser.rb +3 -3
  44. data/lib/imap/backup/setup/helpers.rb +2 -2
  45. data/lib/imap/backup/setup.rb +5 -4
  46. data/lib/imap/backup/thunderbird/mailbox_exporter.rb +41 -22
  47. data/lib/imap/backup/uploader.rb +46 -8
  48. data/lib/imap/backup/utils.rb +1 -1
  49. data/lib/imap/backup/version.rb +3 -3
  50. data/lib/imap/backup.rb +0 -2
  51. metadata +31 -105
  52. data/lib/imap/backup/serializer/mbox_store.rb +0 -217
  53. data/spec/features/backup_spec.rb +0 -108
  54. data/spec/features/configuration/minimal_configuration.rb +0 -15
  55. data/spec/features/configuration/missing_configuration.rb +0 -14
  56. data/spec/features/folders_spec.rb +0 -36
  57. data/spec/features/helper.rb +0 -2
  58. data/spec/features/local/list_accounts_spec.rb +0 -12
  59. data/spec/features/local/list_emails_spec.rb +0 -21
  60. data/spec/features/local/list_folders_spec.rb +0 -21
  61. data/spec/features/local/show_an_email_spec.rb +0 -34
  62. data/spec/features/migrate_spec.rb +0 -35
  63. data/spec/features/remote/list_account_folders_spec.rb +0 -16
  64. data/spec/features/restore_spec.rb +0 -162
  65. data/spec/features/status_spec.rb +0 -43
  66. data/spec/features/support/aruba.rb +0 -77
  67. data/spec/features/support/backup_directory.rb +0 -43
  68. data/spec/features/support/email_server.rb +0 -110
  69. data/spec/features/support/shared/connection_context.rb +0 -14
  70. data/spec/features/support/shared/message_fixtures.rb +0 -16
  71. data/spec/fixtures/connection.yml +0 -7
  72. data/spec/spec_helper.rb +0 -15
  73. data/spec/support/fixtures.rb +0 -11
  74. data/spec/support/higline_test_helpers.rb +0 -8
  75. data/spec/support/silence_logging.rb +0 -7
  76. data/spec/unit/email/mboxrd/message_spec.rb +0 -177
  77. data/spec/unit/email/provider/apple_mail_spec.rb +0 -7
  78. data/spec/unit/email/provider/base_spec.rb +0 -11
  79. data/spec/unit/email/provider/fastmail_spec.rb +0 -7
  80. data/spec/unit/email/provider/gmail_spec.rb +0 -7
  81. data/spec/unit/email/provider_spec.rb +0 -27
  82. data/spec/unit/imap/backup/account/connection_spec.rb +0 -405
  83. data/spec/unit/imap/backup/account/folder_spec.rb +0 -251
  84. data/spec/unit/imap/backup/cli/accounts_spec.rb +0 -47
  85. data/spec/unit/imap/backup/cli/helpers_spec.rb +0 -87
  86. data/spec/unit/imap/backup/cli/local_spec.rb +0 -81
  87. data/spec/unit/imap/backup/cli/utils_spec.rb +0 -62
  88. data/spec/unit/imap/backup/client/default_spec.rb +0 -22
  89. data/spec/unit/imap/backup/configuration_spec.rb +0 -238
  90. data/spec/unit/imap/backup/downloader_spec.rb +0 -44
  91. data/spec/unit/imap/backup/logger_spec.rb +0 -48
  92. data/spec/unit/imap/backup/migrator_spec.rb +0 -58
  93. data/spec/unit/imap/backup/serializer/mbox_enumerator_spec.rb +0 -45
  94. data/spec/unit/imap/backup/serializer/mbox_spec.rb +0 -222
  95. data/spec/unit/imap/backup/serializer/mbox_store_spec.rb +0 -329
  96. data/spec/unit/imap/backup/setup/account_spec.rb +0 -366
  97. data/spec/unit/imap/backup/setup/asker_spec.rb +0 -137
  98. data/spec/unit/imap/backup/setup/connection_tester_spec.rb +0 -51
  99. data/spec/unit/imap/backup/setup/folder_chooser_spec.rb +0 -146
  100. data/spec/unit/imap/backup/setup_spec.rb +0 -301
  101. data/spec/unit/imap/backup/uploader_spec.rb +0 -54
  102. data/spec/unit/imap/backup/utils_spec.rb +0 -92
@@ -1,251 +0,0 @@
1
- # rubocop:disable RSpec/PredicateMatcher
2
-
3
- describe Imap::Backup::Account::Folder do
4
- FOLDER_NAME = "Gelöscht".freeze
5
- ENCODED_FOLDER_NAME = "Gel&APY-scht".freeze
6
-
7
- subject { described_class.new(connection, FOLDER_NAME) }
8
-
9
- let(:client) do
10
- instance_double(
11
- Imap::Backup::Client::Default,
12
- append: append_response,
13
- create: nil,
14
- examine: nil,
15
- expunge: nil,
16
- responses: responses,
17
- select: nil,
18
- uid_store: nil
19
- )
20
- end
21
- let(:connection) do
22
- instance_double(Imap::Backup::Account::Connection, client: client)
23
- end
24
- let(:missing_mailbox_data) do
25
- OpenStruct.new(text: "Unknown Mailbox: #{FOLDER_NAME}")
26
- end
27
- let(:missing_mailbox_response) { OpenStruct.new(data: missing_mailbox_data) }
28
- let(:missing_mailbox_error) do
29
- Net::IMAP::NoResponseError.new(missing_mailbox_response)
30
- end
31
- let(:responses) { [] }
32
- let(:append_response) { nil }
33
- let(:uids) { [5678, 123] }
34
-
35
- before { allow(client).to receive(:uid_search) { uids } }
36
-
37
- describe "#uids" do
38
- it "lists available messages" do
39
- expect(subject.uids).to eq(uids.reverse)
40
- end
41
-
42
- context "with missing mailboxes" do
43
- before do
44
- allow(client).to receive(:examine).
45
- with(ENCODED_FOLDER_NAME).and_raise(missing_mailbox_error)
46
- end
47
-
48
- it "returns an empty array" do
49
- expect(subject.uids).to eq([])
50
- end
51
- end
52
-
53
- context "with no SEARCH response in Net::IMAP" do
54
- let(:no_method_error) do
55
- NoMethodError.new("Somethimes SEARCH responses come out undefined")
56
- end
57
-
58
- before do
59
- allow(client).to receive(:examine).
60
- with(ENCODED_FOLDER_NAME).and_raise(missing_mailbox_error)
61
- end
62
-
63
- it "returns an empty array" do
64
- expect(subject.uids).to eq([])
65
- end
66
- end
67
- end
68
-
69
- describe "#fetch_multi" do
70
- let(:message_body) { instance_double(String, force_encoding: nil) }
71
- let(:attributes) { {"UID" => "uid", "BODY[]" => message_body, "other" => "xxx"} }
72
- let(:fetch_data_item) do
73
- instance_double(Net::IMAP::FetchData, attr: attributes)
74
- end
75
-
76
- before { allow(client).to receive(:uid_fetch) { [fetch_data_item] } }
77
-
78
- it "returns the uid and message" do
79
- expect(subject.fetch_multi([123])).to eq([{uid: "uid", body: message_body}])
80
- end
81
-
82
- context "when the server responds with nothing" do
83
- before { allow(client).to receive(:uid_fetch) { nil } }
84
-
85
- it "is nil" do
86
- expect(subject.fetch_multi([123])).to be_nil
87
- end
88
- end
89
-
90
- context "when the mailbox doesn't exist" do
91
- before do
92
- allow(client).to receive(:examine).
93
- with(ENCODED_FOLDER_NAME).and_raise(missing_mailbox_error)
94
- end
95
-
96
- it "is nil" do
97
- expect(subject.fetch_multi([123])).to be_nil
98
- end
99
- end
100
-
101
- context "when the first fetch_uid attempts fail" do
102
- before do
103
- outcomes = [-> { raise EOFError }, -> { [fetch_data_item] }]
104
- allow(client).to receive(:uid_fetch) { outcomes.shift.call }
105
- end
106
-
107
- it "retries" do
108
- subject.fetch_multi([123])
109
-
110
- expect(client).to have_received(:uid_fetch).twice
111
- end
112
-
113
- it "succeeds" do
114
- subject.fetch_multi([123])
115
- end
116
- end
117
- end
118
-
119
- describe "#folder" do
120
- it "is the name" do
121
- expect(subject.folder).to eq(FOLDER_NAME)
122
- end
123
- end
124
-
125
- describe "#exist?" do
126
- context "when the folder exists" do
127
- it "is true" do
128
- expect(subject.exist?).to be_truthy
129
- end
130
- end
131
-
132
- context "when the folder doesn't exist" do
133
- before do
134
- allow(client).to receive(:examine).
135
- with(ENCODED_FOLDER_NAME).and_raise(missing_mailbox_error)
136
- end
137
-
138
- it "is false" do
139
- expect(subject.exist?).to be_falsey
140
- end
141
- end
142
- end
143
-
144
- describe "#create" do
145
- context "when the folder exists" do
146
- it "is does not create the folder" do
147
- expect(client).to_not receive(:create)
148
-
149
- subject.create
150
- end
151
- end
152
-
153
- context "when the folder doesn't exist" do
154
- before do
155
- allow(client).to receive(:examine).
156
- with(ENCODED_FOLDER_NAME).and_raise(missing_mailbox_error)
157
- end
158
-
159
- it "creates the folder" do
160
- expect(client).to receive(:create)
161
-
162
- subject.create
163
- end
164
-
165
- it "encodes the folder name" do
166
- expect(client).to receive(:create).with(ENCODED_FOLDER_NAME)
167
-
168
- subject.create
169
- end
170
- end
171
- end
172
-
173
- describe "#uid_validity" do
174
- let(:responses) { {"UIDVALIDITY" => ["x", "uid validity"]} }
175
-
176
- it "is returned" do
177
- expect(subject.uid_validity).to eq("uid validity")
178
- end
179
-
180
- context "when the folder doesn't exist" do
181
- before do
182
- allow(client).to receive(:examine).
183
- with(ENCODED_FOLDER_NAME).and_raise(missing_mailbox_error)
184
- end
185
-
186
- it "raises an error" do
187
- expect do
188
- subject.uid_validity
189
- end.to raise_error(Imap::Backup::FolderNotFound)
190
- end
191
- end
192
- end
193
-
194
- describe "#append" do
195
- let(:message) do
196
- instance_double(
197
- Email::Mboxrd::Message,
198
- imap_body: "imap body",
199
- date: message_date
200
- )
201
- end
202
- let(:message_date) { Time.new(2010, 10, 10, 9, 15, 22, 0) }
203
- let(:append_response) do
204
- OpenStruct.new(data: OpenStruct.new(code: OpenStruct.new(data: "1 2")))
205
- end
206
-
207
- it "appends the message" do
208
- expect(client).to receive(:append)
209
-
210
- subject.append(message)
211
- end
212
-
213
- it "sets the date and time" do
214
- expect(client).to receive(:append).
215
- with(anything, anything, anything, message_date)
216
-
217
- subject.append(message)
218
- end
219
-
220
- it "returns the new uid" do
221
- expect(subject.append(message)).to eq(2)
222
- end
223
-
224
- it "set the new uid validity" do
225
- subject.append(message)
226
-
227
- expect(subject.uid_validity).to eq(1)
228
- end
229
- end
230
-
231
- describe "#clear" do
232
- before do
233
- subject.clear
234
- end
235
-
236
- it "uses select to have read-write access" do
237
- expect(client).to have_received(:select)
238
- end
239
-
240
- it "marks all emails as deleted" do
241
- expect(client).
242
- to have_received(:uid_store).with(uids.sort, "+FLAGS", [:Deleted])
243
- end
244
-
245
- it "deletes marked emails" do
246
- expect(client).to have_received(:expunge)
247
- end
248
- end
249
- end
250
-
251
- # rubocop:enable RSpec/PredicateMatcher
@@ -1,47 +0,0 @@
1
- require "imap/backup/cli/accounts"
2
-
3
- describe Imap::Backup::CLI::Accounts do
4
- subject { described_class.new }
5
-
6
- let(:accounts) { [account1, account2] }
7
- let(:account1) do
8
- instance_double(
9
- Imap::Backup::Account,
10
- username: "a1@example.com"
11
- )
12
- end
13
- let(:account2) do
14
- instance_double(
15
- Imap::Backup::Account,
16
- username: "a2@example.com"
17
- )
18
- end
19
- let(:store) do
20
- instance_double(Imap::Backup::Configuration, accounts: accounts)
21
- end
22
- let(:exists) { true }
23
-
24
- before do
25
- allow(Imap::Backup::Configuration).to receive(:new) { store }
26
- allow(Imap::Backup::Configuration).
27
- to receive(:exist?) { exists }
28
- end
29
-
30
- describe "#each" do
31
- specify "calls the block with each account" do
32
- result = subject.map { |a| a }
33
-
34
- expect(result).to eq(accounts)
35
- end
36
-
37
- context "when the configuration file is missing" do
38
- let(:exists) { false }
39
-
40
- it "fails" do
41
- expect do
42
- subject.each {}
43
- end.to raise_error(Imap::Backup::ConfigurationNotFound, /not found/)
44
- end
45
- end
46
- end
47
- end
@@ -1,87 +0,0 @@
1
- require "imap/backup/cli/helpers"
2
-
3
- module Imap::Backup
4
- class WithHelpers
5
- include CLI::Helpers
6
- end
7
-
8
- RSpec.describe CLI::Helpers do
9
- subject { WithHelpers.new }
10
-
11
- let(:accounts) { instance_double(CLI::Accounts) }
12
- let(:email) { "email@example.com" }
13
- let(:account1) { instance_double(Account, username: email, connection: "c1") }
14
- let(:account2) { instance_double(Account, username: "foo", connection: "c2") }
15
- let(:items) { [account1, account2] }
16
-
17
- before do
18
- allow(CLI::Accounts).to receive(:new) { accounts }
19
- allow(accounts).to receive(:each).
20
- and_yield(account1).
21
- and_yield(account2)
22
- allow(accounts).to receive(:find) do |&block|
23
- items.find { |a| block.call(a) }
24
- end
25
- end
26
-
27
- describe ".symbolized" do
28
- let(:arguments) { {"foo" => 1, "bar" => 2} }
29
- let(:result) { subject.symbolized(arguments) }
30
-
31
- it "converts string keys to symbols" do
32
- expect(result.keys).to eq([:foo, :bar])
33
- end
34
-
35
- context "when keys have hyphens" do
36
- let(:arguments) { {"some-option"=> 3} }
37
-
38
- it "replaces them with underscores" do
39
- expect(result.keys).to eq([:some_option])
40
- end
41
- end
42
- end
43
-
44
- describe ".account" do
45
- it "returns any account with a matching username" do
46
- expect(subject.account(email)).to eq(account1)
47
- end
48
-
49
- context "when no match is found" do
50
- let(:items) { [account2] }
51
- it "fails" do
52
- expect do
53
- subject.account(email)
54
- end.to raise_error(RuntimeError, /not a configured account/)
55
- end
56
- end
57
- end
58
-
59
- describe ".connection" do
60
- it "returns the connection for any account with a matching username" do
61
- result = subject.connection(email)
62
- expect(result).to be_a(Account::Connection)
63
- expect(result.account).to eq(account1)
64
- end
65
- end
66
-
67
- describe ".each_connection" do
68
- it "yields each connection" do
69
- expect { |b| subject.each_connection([email, "foo"], &b) }.
70
- to yield_successive_args("c1", "c2")
71
- end
72
-
73
- context "when there is no configuration" do
74
- before do
75
- allow(accounts).to receive(:each).
76
- and_raise(Imap::Backup::ConfigurationNotFound)
77
- end
78
-
79
- it "fails" do
80
- expect do
81
- subject.each_connection([email])
82
- end.to raise_error(RuntimeError, /not configured/)
83
- end
84
- end
85
- end
86
- end
87
- end
@@ -1,81 +0,0 @@
1
- describe Imap::Backup::CLI::Local do
2
- let(:accounts) do
3
- instance_double(
4
- Imap::Backup::CLI::Accounts,
5
- find: ->(&block) { [account].find { |a| block.call(a) } }
6
- )
7
- end
8
- let(:account) do
9
- instance_double(
10
- Imap::Backup::Account,
11
- username: email,
12
- marked_for_deletion?: false,
13
- modified?: false
14
- )
15
- end
16
- let(:connection) do
17
- instance_double(
18
- Imap::Backup::Account::Connection,
19
- local_folders: local_folders
20
- )
21
- end
22
- let(:local_folders) { [[serializer, folder]] }
23
- let(:folder) { instance_double(Imap::Backup::Account::Folder, name: "bar") }
24
- let(:serializer) do
25
- instance_double(
26
- Imap::Backup::Serializer::Mbox,
27
- uids: uids,
28
- each_message: [[123, message]]
29
- )
30
- end
31
- let(:uids) { ["123"] }
32
- let(:message) do
33
- instance_double(
34
- Email::Mboxrd::Message,
35
- date: Date.today,
36
- subject: "Ciao",
37
- supplied_body: "Supplied"
38
- )
39
- end
40
- let(:email) { "foo@example.com" }
41
-
42
- before do
43
- allow(Kernel).to receive(:puts)
44
- allow(Imap::Backup::CLI::Accounts).to receive(:new) { accounts }
45
- allow(Imap::Backup::Account::Connection).to receive(:new) { connection }
46
- allow(Mail).to receive(:new) { mail }
47
- allow(accounts).to receive(:each).and_yield(account)
48
- end
49
-
50
- describe "accounts" do
51
- it "lists configured emails" do
52
- subject.accounts
53
-
54
- expect(Kernel).to have_received(:puts).with(email)
55
- end
56
- end
57
-
58
- describe "folders" do
59
- it "lists downloaded folders in quotes" do
60
- subject.folders(email)
61
-
62
- expect(Kernel).to have_received(:puts).with(%("bar"))
63
- end
64
- end
65
-
66
- describe "list" do
67
- it "lists downloaded emails" do
68
- subject.list(email, "bar")
69
-
70
- expect(Kernel).to have_received(:puts).with(/Ciao/)
71
- end
72
- end
73
-
74
- describe "show" do
75
- it "prints a downloaded email" do
76
- subject.show(email, "bar", "123")
77
-
78
- expect(Kernel).to have_received(:puts).with("Supplied")
79
- end
80
- end
81
- end
@@ -1,62 +0,0 @@
1
- module Imap::Backup
2
- describe CLI::Utils do
3
- let(:accounts) do
4
- instance_double(
5
- CLI::Accounts,
6
- find: ->(&block) { [account].find { |a| block.call(a) } }
7
- )
8
- end
9
- let(:account) { instance_double(Account, username: email) }
10
- let(:connection) do
11
- instance_double(
12
- Account::Connection,
13
- account: account,
14
- backup_folders: [folder]
15
- )
16
- end
17
- let(:account) do
18
- instance_double(
19
- Account,
20
- local_path: "path"
21
- )
22
- end
23
- let(:folder) do
24
- instance_double(
25
- Account::Folder,
26
- exist?: true,
27
- name: "name",
28
- uid_validity: "uid_validity",
29
- uids: %w(123 456)
30
- )
31
- end
32
- let(:serializer) do
33
- instance_double(
34
- Serializer::Mbox,
35
- uids: %w(123 789),
36
- apply_uid_validity: nil,
37
- save: nil
38
- )
39
- end
40
- let(:email) { "foo@example.com" }
41
-
42
- before do
43
- allow(CLI::Accounts).to receive(:new) { accounts }
44
- allow(Account::Connection).to receive(:new) { connection }
45
- allow(Serializer::Mbox).to receive(:new) { serializer }
46
- end
47
-
48
- describe "ignore_history" do
49
- it "ensures the local UID validity matches the server" do
50
- subject.ignore_history(email)
51
-
52
- expect(serializer).to have_received(:apply_uid_validity).with("uid_validity")
53
- end
54
-
55
- it "fills the local folder with fake emails" do
56
- subject.ignore_history(email)
57
-
58
- expect(serializer).to have_received(:save).with("456", /From: fake@email.com/)
59
- end
60
- end
61
- end
62
- end
@@ -1,22 +0,0 @@
1
- describe Imap::Backup::Client::Default do
2
- subject { described_class.new("server", {}) }
3
-
4
- let(:imap) { instance_double(Net::IMAP, list: imap_folders) }
5
- let(:imap_folders) { [] }
6
-
7
- before do
8
- allow(Net::IMAP).to receive(:new) { imap }
9
- end
10
-
11
- describe "#list" do
12
- context "with non-ASCII folder names" do
13
- let(:imap_folders) do
14
- [instance_double(Net::IMAP::MailboxList, name: "Gel&APY-scht")]
15
- end
16
-
17
- it "converts them to UTF-8" do
18
- expect(subject.list).to eq(["Gelöscht"])
19
- end
20
- end
21
- end
22
- end