imap-backup 2.1.1 → 2.2.0
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.
- checksums.yaml +4 -4
- data/.rubocop.yml +2 -0
- data/README.md +6 -2
- data/bin/imap-backup +1 -1
- data/lib/email/mboxrd/message.rb +0 -1
- data/lib/imap/backup/account/connection.rb +28 -21
- data/lib/imap/backup/account/folder.rb +0 -1
- data/lib/imap/backup/serializer/mbox_enumerator.rb +1 -1
- data/lib/imap/backup/uploader.rb +10 -1
- data/lib/imap/backup/version.rb +2 -2
- data/spec/features/restore_spec.rb +75 -27
- data/spec/features/support/email_server.rb +1 -3
- data/spec/features/support/shared/message_fixtures.rb +8 -0
- data/spec/unit/email/mboxrd/message_spec.rb +0 -6
- data/spec/unit/imap/backup/account/connection_spec.rb +58 -43
- data/spec/unit/imap/backup/account/folder_spec.rb +16 -20
- data/spec/unit/imap/backup/configuration/account_spec.rb +31 -25
- data/spec/unit/imap/backup/configuration/asker_spec.rb +20 -17
- data/spec/unit/imap/backup/configuration/connection_tester_spec.rb +6 -10
- data/spec/unit/imap/backup/configuration/folder_chooser_spec.rb +16 -10
- data/spec/unit/imap/backup/configuration/list_spec.rb +6 -3
- data/spec/unit/imap/backup/configuration/setup_spec.rb +40 -25
- data/spec/unit/imap/backup/configuration/store_spec.rb +18 -16
- data/spec/unit/imap/backup/downloader_spec.rb +14 -14
- data/spec/unit/imap/backup/serializer/mbox_enumerator_spec.rb +6 -1
- data/spec/unit/imap/backup/serializer/mbox_spec.rb +54 -40
- data/spec/unit/imap/backup/serializer/mbox_store_spec.rb +49 -31
- data/spec/unit/imap/backup/uploader_spec.rb +20 -7
- data/spec/unit/imap/backup/utils_spec.rb +8 -9
- metadata +2 -2
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 119c39db4e00d91f68a482035050ec8c91606a120cc80f1c514f39d731a30e02
|
4
|
+
data.tar.gz: 451761b7d17bcc664ff6890e6c86bd33628929bfdf665a91b322bcfd92c7d7f3
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 3021adbfe55940b7f0a5d306259c2ba755277105d0139e6b51c18a425cbbf49737bde1fc38c6db00f51e272b71790c2c037838fcfffac7197fd35f429636d4d6
|
7
|
+
data.tar.gz: 6ef608f3bc7655886b6926c746768741ca70fb3b604834fb3750dbb393ae4551c077fa30f47ee8edae24a4684853093697988e1b23f3f50c505e635496d792a8
|
data/.rubocop.yml
CHANGED
data/README.md
CHANGED
@@ -204,8 +204,8 @@ Integration tests (feature specs) are run against a Docker image
|
|
204
204
|
|
205
205
|
In one shell, run the Docker image:
|
206
206
|
|
207
|
-
```
|
208
|
-
docker run \
|
207
|
+
```sh
|
208
|
+
$ docker run \
|
209
209
|
--env MAIL_ADDRESS=address@example.org \
|
210
210
|
--env MAIL_PASS=pass \
|
211
211
|
--env MAILNAME=example.org \
|
@@ -213,6 +213,10 @@ docker run \
|
|
213
213
|
antespi/docker-imap-devel:latest
|
214
214
|
```
|
215
215
|
|
216
|
+
```sh
|
217
|
+
$ rake
|
218
|
+
```
|
219
|
+
|
216
220
|
To exclude Docker-based tests:
|
217
221
|
|
218
222
|
```sh
|
data/bin/imap-backup
CHANGED
data/lib/email/mboxrd/message.rb
CHANGED
@@ -57,27 +57,7 @@ module Imap::Backup
|
|
57
57
|
|
58
58
|
def restore
|
59
59
|
local_folders do |serializer, folder|
|
60
|
-
|
61
|
-
if exists
|
62
|
-
new_name = serializer.apply_uid_validity(folder.uid_validity)
|
63
|
-
old_name = serializer.folder
|
64
|
-
if new_name
|
65
|
-
Imap::Backup.logger.debug(
|
66
|
-
"Backup '#{old_name}' renamed and restored to '#{new_name}'"
|
67
|
-
)
|
68
|
-
new_serializer = Serializer::Mbox.new(local_path, new_name)
|
69
|
-
new_folder = Account::Folder.new(self, new_name)
|
70
|
-
new_folder.create
|
71
|
-
new_serializer.force_uid_validity(new_folder.uid_validity)
|
72
|
-
Uploader.new(new_folder, new_serializer).run
|
73
|
-
else
|
74
|
-
Uploader.new(folder, serializer).run
|
75
|
-
end
|
76
|
-
else
|
77
|
-
folder.create
|
78
|
-
serializer.force_uid_validity(folder.uid_validity)
|
79
|
-
Uploader.new(folder, serializer).run
|
80
|
-
end
|
60
|
+
restore_folder serializer, folder
|
81
61
|
end
|
82
62
|
end
|
83
63
|
|
@@ -114,6 +94,33 @@ module Imap::Backup
|
|
114
94
|
end
|
115
95
|
end
|
116
96
|
|
97
|
+
def restore_folder(serializer, folder)
|
98
|
+
existing_uids = folder.uids
|
99
|
+
if existing_uids.any?
|
100
|
+
Imap::Backup.logger.debug(
|
101
|
+
"There's already a '#{folder.name}' folder with emails"
|
102
|
+
)
|
103
|
+
new_name = serializer.apply_uid_validity(folder.uid_validity)
|
104
|
+
old_name = serializer.folder
|
105
|
+
if new_name
|
106
|
+
Imap::Backup.logger.debug(
|
107
|
+
"Backup '#{old_name}' renamed and restored to '#{new_name}'"
|
108
|
+
)
|
109
|
+
new_serializer = Serializer::Mbox.new(local_path, new_name)
|
110
|
+
new_folder = Account::Folder.new(self, new_name)
|
111
|
+
new_folder.create
|
112
|
+
new_serializer.force_uid_validity(new_folder.uid_validity)
|
113
|
+
Uploader.new(new_folder, new_serializer).run
|
114
|
+
else
|
115
|
+
Uploader.new(folder, serializer).run
|
116
|
+
end
|
117
|
+
else
|
118
|
+
folder.create
|
119
|
+
serializer.force_uid_validity(folder.uid_validity)
|
120
|
+
Uploader.new(folder, serializer).run
|
121
|
+
end
|
122
|
+
end
|
123
|
+
|
117
124
|
def create_account_folder
|
118
125
|
Utils.make_folder(
|
119
126
|
File.dirname(local_path),
|
data/lib/imap/backup/uploader.rb
CHANGED
@@ -9,10 +9,19 @@ module Imap::Backup
|
|
9
9
|
end
|
10
10
|
|
11
11
|
def run
|
12
|
-
missing_uids.
|
12
|
+
count = missing_uids.count
|
13
|
+
return if count.zero?
|
14
|
+
|
15
|
+
Imap::Backup.logger.debug "[#{folder.name}] #{count} to restore"
|
16
|
+
missing_uids.each.with_index do |uid, i|
|
13
17
|
message = serializer.load(uid)
|
14
18
|
next if message.nil?
|
15
19
|
|
20
|
+
log_prefix = "[#{folder.name}] uid: #{uid} (#{i + 1}/#{count}) -"
|
21
|
+
Imap::Backup.logger.debug(
|
22
|
+
"#{log_prefix} #{message.supplied_body.size} bytes"
|
23
|
+
)
|
24
|
+
|
16
25
|
new_uid = folder.append(message)
|
17
26
|
serializer.update_uid(uid, new_uid)
|
18
27
|
end
|
data/lib/imap/backup/version.rb
CHANGED
@@ -72,41 +72,89 @@ RSpec.describe "restore", type: :feature, docker: true do
|
|
72
72
|
end
|
73
73
|
|
74
74
|
context "when the uid_validity doesn't match" do
|
75
|
-
|
76
|
-
|
77
|
-
|
78
|
-
|
79
|
-
let(:new_folder) { "#{folder}.#{uid_validity}" }
|
80
|
-
let(:cleanup) do
|
81
|
-
server_delete_folder new_folder
|
82
|
-
super()
|
83
|
-
end
|
75
|
+
context "when the folder is empty" do
|
76
|
+
let(:pre) do
|
77
|
+
server_create_folder folder
|
78
|
+
end
|
84
79
|
|
85
|
-
|
86
|
-
|
87
|
-
|
88
|
-
|
89
|
-
|
80
|
+
it "sets the backup uid_validity to match the folder" do
|
81
|
+
updated_imap_content = imap_parsed(folder)
|
82
|
+
expect(updated_imap_content[:uid_validity]).
|
83
|
+
to eq(server_uid_validity(folder))
|
84
|
+
end
|
90
85
|
|
91
|
-
|
92
|
-
|
86
|
+
it "uploads to the new folder" do
|
87
|
+
messages = server_messages(folder).map do |m|
|
88
|
+
server_message_to_body(m)
|
89
|
+
end
|
90
|
+
expect(messages).to eq(messages_as_server_messages)
|
91
|
+
end
|
93
92
|
end
|
94
93
|
|
95
|
-
|
96
|
-
|
97
|
-
|
98
|
-
|
94
|
+
context "when the folder has content" do
|
95
|
+
let(:new_folder) { "#{folder}.#{uid_validity}" }
|
96
|
+
let(:cleanup) do
|
97
|
+
server_delete_folder new_folder
|
98
|
+
super()
|
99
|
+
end
|
99
100
|
|
100
|
-
|
101
|
-
|
102
|
-
|
101
|
+
let(:pre) do
|
102
|
+
server_create_folder folder
|
103
|
+
email3
|
104
|
+
end
|
103
105
|
|
104
|
-
|
105
|
-
|
106
|
-
|
106
|
+
it "renames the backup" do
|
107
|
+
expect(mbox_content(new_folder)).to eq(messages_as_mbox)
|
108
|
+
end
|
109
|
+
|
110
|
+
it "leaves the existing folder as is" do
|
111
|
+
messages = server_messages(folder).map do |m|
|
112
|
+
server_message_to_body(m)
|
113
|
+
end
|
114
|
+
expect(messages).to eq([message_as_server_message(msg3)])
|
115
|
+
end
|
116
|
+
|
117
|
+
it "creates the new folder" do
|
118
|
+
expect(server_folders.map(&:name)).to include(new_folder)
|
119
|
+
end
|
120
|
+
|
121
|
+
it "sets the backup uid_validity to match the new folder" do
|
122
|
+
updated_imap_content = imap_parsed(new_folder)
|
123
|
+
expect(updated_imap_content[:uid_validity]).
|
124
|
+
to eq(server_uid_validity(new_folder))
|
125
|
+
end
|
126
|
+
|
127
|
+
it "uploads to the new folder" do
|
128
|
+
messages = server_messages(new_folder).map do |m|
|
129
|
+
server_message_to_body(m)
|
130
|
+
end
|
131
|
+
expect(messages).to eq(messages_as_server_messages)
|
107
132
|
end
|
108
|
-
expect(messages).to eq(messages_as_server_messages)
|
109
133
|
end
|
110
134
|
end
|
111
135
|
end
|
136
|
+
|
137
|
+
context "when non-Unicode encodings are used" do
|
138
|
+
let(:server_message) do
|
139
|
+
message_as_server_message(msg_iso8859)
|
140
|
+
end
|
141
|
+
let(:messages_as_mbox) do
|
142
|
+
message_as_mbox_entry(msg_iso8859)
|
143
|
+
end
|
144
|
+
let(:message_uids) { [uid_iso8859] }
|
145
|
+
let(:uid_validity) { server_uid_validity(folder) }
|
146
|
+
|
147
|
+
let(:pre) do
|
148
|
+
server_create_folder folder
|
149
|
+
uid_validity
|
150
|
+
end
|
151
|
+
|
152
|
+
it "maintains encodings" do
|
153
|
+
message =
|
154
|
+
server_messages(folder).
|
155
|
+
first["RFC822"]
|
156
|
+
|
157
|
+
expect(message).to eq(server_message)
|
158
|
+
end
|
159
|
+
end
|
112
160
|
end
|
@@ -38,9 +38,7 @@ module EmailServerHelpers
|
|
38
38
|
return nil if fetch_data_items.nil?
|
39
39
|
|
40
40
|
fetch_data_item = fetch_data_items[0]
|
41
|
-
|
42
|
-
attributes["RFC822"].force_encoding("utf-8")
|
43
|
-
attributes
|
41
|
+
fetch_data_item.attr
|
44
42
|
end
|
45
43
|
|
46
44
|
def delete_emails(folder)
|
@@ -2,7 +2,15 @@ shared_context "message-fixtures" do
|
|
2
2
|
let(:uid1) { 123 }
|
3
3
|
let(:uid2) { 345 }
|
4
4
|
let(:uid3) { 567 }
|
5
|
+
let(:uid_iso8859) { 890 }
|
5
6
|
let(:msg1) { {uid: uid1, subject: "Test 1", body: "body 1\nHi"} }
|
6
7
|
let(:msg2) { {uid: uid2, subject: "Test 2", body: "body 2"} }
|
7
8
|
let(:msg3) { {uid: uid3, subject: "Test 3", body: "body 3"} }
|
9
|
+
let(:msg_iso8859) do
|
10
|
+
{
|
11
|
+
uid: uid_iso8859,
|
12
|
+
subject: "iso8859 Body",
|
13
|
+
body: "Ma, perchè?".encode(Encoding::ISO_8859_1).force_encoding("binary")
|
14
|
+
}
|
15
|
+
end
|
8
16
|
end
|
@@ -79,12 +79,6 @@ describe Email::Mboxrd::Message do
|
|
79
79
|
allow(Mail).to receive(:new).with(cloned_message_body) { mail }
|
80
80
|
end
|
81
81
|
|
82
|
-
it "does not modify the message" do
|
83
|
-
subject.to_serialized
|
84
|
-
|
85
|
-
expect(message_body).to_not have_received(:force_encoding).with("binary")
|
86
|
-
end
|
87
|
-
|
88
82
|
it "adds a 'From ' line at the start" do
|
89
83
|
expected = "From #{from} #{date.asctime}\n"
|
90
84
|
expect(subject.to_serialized).to start_with(expected)
|
@@ -49,10 +49,6 @@ describe Imap::Backup::Account::Connection do
|
|
49
49
|
end
|
50
50
|
|
51
51
|
shared_examples "connects to IMAP" do
|
52
|
-
it "sets up the IMAP connection" do
|
53
|
-
expect(Net::IMAP).to have_received(:new)
|
54
|
-
end
|
55
|
-
|
56
52
|
it "logs in to the imap server" do
|
57
53
|
expect(imap).to have_received(:login)
|
58
54
|
end
|
@@ -70,8 +66,9 @@ describe Imap::Backup::Account::Connection do
|
|
70
66
|
end
|
71
67
|
|
72
68
|
it "creates the path" do
|
69
|
+
expect(Imap::Backup::Utils).to receive(:make_folder)
|
70
|
+
|
73
71
|
subject.username
|
74
|
-
expect(Imap::Backup::Utils).to have_received(:make_folder)
|
75
72
|
end
|
76
73
|
end
|
77
74
|
|
@@ -102,7 +99,7 @@ describe Imap::Backup::Account::Connection do
|
|
102
99
|
let(:remote_uid) { "remote_uid" }
|
103
100
|
|
104
101
|
before do
|
105
|
-
allow(Imap::Backup::Account::Folder).to receive(:new)
|
102
|
+
allow(Imap::Backup::Account::Folder).to receive(:new) { folder }
|
106
103
|
allow(Imap::Backup::Serializer::Mbox).to receive(:new) { serializer }
|
107
104
|
end
|
108
105
|
|
@@ -140,21 +137,24 @@ describe Imap::Backup::Account::Connection do
|
|
140
137
|
context "with supplied backup_folders" do
|
141
138
|
before do
|
142
139
|
allow(Imap::Backup::Account::Folder).to receive(:new).
|
143
|
-
with(subject, self.class.backup_folder)
|
140
|
+
with(subject, self.class.backup_folder) { folder }
|
144
141
|
allow(Imap::Backup::Serializer::Mbox).to receive(:new).
|
145
|
-
with(local_path, self.class.backup_folder)
|
146
|
-
subject.run_backup
|
142
|
+
with(local_path, self.class.backup_folder) { serializer }
|
147
143
|
end
|
148
144
|
|
149
145
|
it "runs the downloader" do
|
150
|
-
expect(downloader).to
|
146
|
+
expect(downloader).to receive(:run)
|
147
|
+
|
148
|
+
subject.run_backup
|
151
149
|
end
|
152
150
|
|
153
151
|
context "when a folder does not exist" do
|
154
152
|
let(:exists) { false }
|
155
153
|
|
156
154
|
it "does not run the downloader" do
|
157
|
-
expect(downloader).to_not
|
155
|
+
expect(downloader).to_not receive(:run)
|
156
|
+
|
157
|
+
subject.run_backup
|
158
158
|
end
|
159
159
|
end
|
160
160
|
end
|
@@ -166,28 +166,28 @@ describe Imap::Backup::Account::Connection do
|
|
166
166
|
|
167
167
|
before do
|
168
168
|
allow(Imap::Backup::Account::Folder).to receive(:new).
|
169
|
-
with(subject, "foo")
|
169
|
+
with(subject, "foo") { folder }
|
170
170
|
allow(Imap::Backup::Serializer::Mbox).to receive(:new).
|
171
|
-
with(local_path, "foo")
|
171
|
+
with(local_path, "foo") { serializer }
|
172
172
|
end
|
173
173
|
|
174
174
|
context "when supplied backup_folders is nil" do
|
175
175
|
let(:backup_folders) { nil }
|
176
176
|
|
177
|
-
before { subject.run_backup }
|
178
|
-
|
179
177
|
it "runs the downloader for each folder" do
|
180
|
-
expect(downloader).to
|
178
|
+
expect(downloader).to receive(:run).exactly(:once)
|
179
|
+
|
180
|
+
subject.run_backup
|
181
181
|
end
|
182
182
|
end
|
183
183
|
|
184
184
|
context "when supplied backup_folders is an empty list" do
|
185
185
|
let(:backup_folders) { [] }
|
186
186
|
|
187
|
-
before { subject.run_backup }
|
188
|
-
|
189
187
|
it "runs the downloader for each folder" do
|
190
|
-
expect(downloader).to
|
188
|
+
expect(downloader).to receive(:run).exactly(:once)
|
189
|
+
|
190
|
+
subject.run_backup
|
191
191
|
end
|
192
192
|
end
|
193
193
|
|
@@ -207,12 +207,12 @@ describe Imap::Backup::Account::Connection do
|
|
207
207
|
instance_double(
|
208
208
|
Imap::Backup::Account::Folder,
|
209
209
|
create: nil,
|
210
|
-
|
210
|
+
uids: uids,
|
211
211
|
name: "my_folder",
|
212
212
|
uid_validity: uid_validity
|
213
213
|
)
|
214
214
|
end
|
215
|
-
let(:
|
215
|
+
let(:uids) { [99] }
|
216
216
|
let(:uid_validity) { 123 }
|
217
217
|
let(:serialized_folder) { "old name" }
|
218
218
|
let(:uploader) do
|
@@ -249,74 +249,89 @@ describe Imap::Backup::Account::Connection do
|
|
249
249
|
with(updated_folder, updated_serializer) { updated_uploader }
|
250
250
|
allow(Pathname).to receive(:glob).
|
251
251
|
and_yield(Pathname.new(File.join(local_path, "my_folder.imap")))
|
252
|
-
subject.restore
|
253
252
|
end
|
254
253
|
|
255
254
|
it "sets local uid validity" do
|
256
|
-
expect(serializer).
|
257
|
-
|
255
|
+
expect(serializer).to receive(:apply_uid_validity).with(uid_validity)
|
256
|
+
|
257
|
+
subject.restore
|
258
258
|
end
|
259
259
|
|
260
|
-
context "when folders exist" do
|
260
|
+
context "when folders exist with contents" do
|
261
261
|
context "when the local folder is renamed" do
|
262
262
|
let(:new_uid_validity) { "new name" }
|
263
263
|
|
264
264
|
it "creates the new folder" do
|
265
|
-
expect(updated_folder).to
|
265
|
+
expect(updated_folder).to receive(:create)
|
266
|
+
|
267
|
+
subject.restore
|
266
268
|
end
|
267
269
|
|
268
270
|
it "sets the renamed folder's uid validity" do
|
269
271
|
expect(updated_serializer).
|
270
|
-
to
|
272
|
+
to receive(:force_uid_validity).with("new uid validity")
|
273
|
+
|
274
|
+
subject.restore
|
271
275
|
end
|
272
276
|
|
273
277
|
it "creates the uploader with updated folder and serializer" do
|
274
|
-
expect(updated_uploader).to
|
278
|
+
expect(updated_uploader).to receive(:run)
|
279
|
+
|
280
|
+
subject.restore
|
275
281
|
end
|
276
282
|
end
|
277
283
|
|
278
284
|
context "when the local folder is not renamed" do
|
279
285
|
it "runs the uploader" do
|
280
|
-
expect(uploader).to
|
286
|
+
expect(uploader).to receive(:run)
|
287
|
+
|
288
|
+
subject.restore
|
281
289
|
end
|
282
290
|
end
|
283
291
|
end
|
284
292
|
|
285
|
-
context "when folders don't exist" do
|
286
|
-
let(:
|
293
|
+
context "when folders don't exist or are empty" do
|
294
|
+
let(:uids) { [] }
|
287
295
|
|
288
296
|
it "creates the folder" do
|
289
|
-
expect(folder).to
|
297
|
+
expect(folder).to receive(:create)
|
298
|
+
|
299
|
+
subject.restore
|
290
300
|
end
|
291
301
|
|
292
|
-
it "
|
302
|
+
it "forces local uid validity" do
|
303
|
+
expect(serializer).to receive(:force_uid_validity).with(uid_validity)
|
304
|
+
|
305
|
+
subject.restore
|
293
306
|
end
|
294
307
|
|
295
308
|
it "runs the uploader" do
|
296
|
-
expect(uploader).to
|
309
|
+
expect(uploader).to receive(:run)
|
310
|
+
|
311
|
+
subject.restore
|
297
312
|
end
|
298
313
|
end
|
299
314
|
end
|
300
315
|
|
301
316
|
describe "#reconnect" do
|
302
|
-
before { subject.reconnect }
|
303
|
-
|
304
317
|
it "disconnects from the server" do
|
305
|
-
expect(imap).to
|
318
|
+
expect(imap).to receive(:disconnect)
|
319
|
+
|
320
|
+
subject.reconnect
|
306
321
|
end
|
307
322
|
|
308
323
|
it "causes reconnection on future access" do
|
309
|
-
|
310
|
-
|
311
|
-
|
324
|
+
expect(Net::IMAP).to receive(:new)
|
325
|
+
|
326
|
+
subject.reconnect
|
312
327
|
end
|
313
328
|
end
|
314
329
|
|
315
330
|
describe "#disconnect" do
|
316
|
-
before { subject.disconnect }
|
317
|
-
|
318
331
|
it "disconnects from the server" do
|
319
|
-
expect(imap).to
|
332
|
+
expect(imap).to receive(:disconnect)
|
333
|
+
|
334
|
+
subject.disconnect
|
320
335
|
end
|
321
336
|
end
|
322
337
|
end
|