imap-backup 5.0.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.
- checksums.yaml +4 -4
- data/README.md +8 -7
- data/bin/imap-backup +4 -0
- data/docs/development.md +10 -4
- data/imap-backup.gemspec +2 -7
- data/lib/cli_coverage.rb +18 -0
- data/lib/imap/backup/account/connection.rb +7 -11
- data/lib/imap/backup/account/folder.rb +0 -16
- data/lib/imap/backup/account.rb +31 -11
- data/lib/imap/backup/cli/folders.rb +3 -3
- data/lib/imap/backup/cli/migrate.rb +3 -3
- data/lib/imap/backup/cli/restore.rb +20 -4
- data/lib/imap/backup/cli/utils.rb +2 -2
- data/lib/imap/backup/cli.rb +6 -7
- data/lib/imap/backup/configuration.rb +1 -11
- data/lib/imap/backup/downloader.rb +13 -9
- data/lib/imap/backup/serializer/directory.rb +37 -0
- data/lib/imap/backup/serializer/imap.rb +120 -0
- data/lib/imap/backup/serializer/mbox.rb +23 -94
- data/lib/imap/backup/serializer/mbox_enumerator.rb +2 -0
- data/lib/imap/backup/serializer.rb +180 -3
- data/lib/imap/backup/setup/account.rb +52 -29
- data/lib/imap/backup/setup/helpers.rb +1 -1
- data/lib/imap/backup/thunderbird/mailbox_exporter.rb +1 -1
- data/lib/imap/backup/version.rb +2 -2
- data/lib/imap/backup.rb +0 -1
- data/spec/features/backup_spec.rb +22 -29
- data/spec/features/restore_spec.rb +8 -6
- data/spec/features/support/aruba.rb +12 -3
- data/spec/features/support/backup_directory.rb +0 -4
- data/spec/features/support/email_server.rb +0 -1
- data/spec/spec_helper.rb +4 -9
- data/spec/unit/imap/backup/account/connection_spec.rb +36 -8
- data/spec/unit/imap/backup/account/folder_spec.rb +18 -16
- data/spec/unit/imap/backup/account_spec.rb +246 -0
- data/spec/unit/imap/backup/cli/accounts_spec.rb +12 -1
- data/spec/unit/imap/backup/cli/backup_spec.rb +19 -0
- data/spec/unit/imap/backup/cli/folders_spec.rb +39 -0
- data/spec/unit/imap/backup/cli/local_spec.rb +26 -7
- data/spec/unit/imap/backup/cli/migrate_spec.rb +80 -0
- data/spec/unit/imap/backup/cli/restore_spec.rb +67 -0
- data/spec/unit/imap/backup/cli/setup_spec.rb +17 -0
- data/spec/unit/imap/backup/cli/utils_spec.rb +68 -5
- data/spec/unit/imap/backup/cli_spec.rb +93 -0
- data/spec/unit/imap/backup/client/apple_mail_spec.rb +9 -0
- data/spec/unit/imap/backup/configuration_spec.rb +2 -2
- data/spec/unit/imap/backup/downloader_spec.rb +60 -8
- data/spec/unit/imap/backup/logger_spec.rb +1 -1
- data/spec/unit/imap/backup/migrator_spec.rb +1 -1
- data/spec/unit/imap/backup/sanitizer_spec.rb +42 -0
- data/spec/unit/imap/backup/serializer/directory_spec.rb +37 -0
- data/spec/unit/imap/backup/serializer/imap_spec.rb +218 -0
- data/spec/unit/imap/backup/serializer/mbox_spec.rb +62 -183
- data/spec/unit/imap/backup/serializer_spec.rb +296 -0
- data/spec/unit/imap/backup/setup/account_spec.rb +120 -25
- data/spec/unit/imap/backup/setup/helpers_spec.rb +15 -0
- data/spec/unit/imap/backup/thunderbird/mailbox_exporter_spec.rb +116 -0
- data/spec/unit/imap/backup/uploader_spec.rb +1 -1
- data/spec/unit/retry_on_error_spec.rb +34 -0
- metadata +44 -37
- data/lib/imap/backup/serializer/mbox_store.rb +0 -217
- data/lib/thunderbird/install.rb +0 -16
- data/lib/thunderbird/local_folder.rb +0 -65
- data/lib/thunderbird/profile.rb +0 -30
- data/lib/thunderbird/profiles.rb +0 -71
- data/lib/thunderbird/subdirectory.rb +0 -93
- data/lib/thunderbird/subdirectory_placeholder.rb +0 -21
- data/lib/thunderbird.rb +0 -14
- data/spec/gather_rspec_coverage.rb +0 -1
- data/spec/unit/imap/backup/serializer/mbox_store_spec.rb +0 -329
@@ -1,21 +0,0 @@
|
|
1
|
-
# Each subdirectory is "accompanied" by a blank
|
2
|
-
# file of the same name (without the '.sbd' extension)
|
3
|
-
class Thunderbird::SubdirectoryPlaceholder
|
4
|
-
attr_reader :path
|
5
|
-
|
6
|
-
def initialize(path)
|
7
|
-
@path = path
|
8
|
-
end
|
9
|
-
|
10
|
-
def exists?
|
11
|
-
File.exist?(path)
|
12
|
-
end
|
13
|
-
|
14
|
-
def regular?
|
15
|
-
File.file?(path)
|
16
|
-
end
|
17
|
-
|
18
|
-
def touch
|
19
|
-
FileUtils.touch path
|
20
|
-
end
|
21
|
-
end
|
data/lib/thunderbird.rb
DELETED
@@ -1,14 +0,0 @@
|
|
1
|
-
require "os"
|
2
|
-
|
3
|
-
class Thunderbird
|
4
|
-
def data_path
|
5
|
-
case
|
6
|
-
when OS.linux?
|
7
|
-
File.join(Dir.home, ".thunderbird")
|
8
|
-
when OS.mac?
|
9
|
-
File.join(Dir.home, "Library", "Thunderbird")
|
10
|
-
when OS.windows?
|
11
|
-
File.join(ENV["APPDATA"].gsub("\\", "/"), "Thunderbird")
|
12
|
-
end
|
13
|
-
end
|
14
|
-
end
|
@@ -1 +0,0 @@
|
|
1
|
-
GATHER_RSPEC_COVERAGE = 1
|
@@ -1,329 +0,0 @@
|
|
1
|
-
describe Imap::Backup::Serializer::MboxStore do
|
2
|
-
subject { described_class.new(base_path, folder) }
|
3
|
-
|
4
|
-
let(:base_path) { "/base/path" }
|
5
|
-
let(:folder) { "the/folder" }
|
6
|
-
let(:folder_path) { File.join(base_path, folder) }
|
7
|
-
let(:imap_pathname) { "#{folder_path}.imap" }
|
8
|
-
let(:imap_exists) { true }
|
9
|
-
let(:imap_file) { instance_double(File, write: nil, close: nil) }
|
10
|
-
let(:mbox_pathname) { "#{folder_path}.mbox" }
|
11
|
-
let(:mbox_exists) { true }
|
12
|
-
let(:mbox_file) { instance_double(File, write: nil, close: nil) }
|
13
|
-
let(:uids) { [3, 2, 1] }
|
14
|
-
let(:imap_content) do
|
15
|
-
{
|
16
|
-
version: Imap::Backup::Serializer::MboxStore::CURRENT_VERSION,
|
17
|
-
uid_validity: uid_validity,
|
18
|
-
uids: uids
|
19
|
-
}.to_json
|
20
|
-
end
|
21
|
-
let(:uid_validity) { 123 }
|
22
|
-
|
23
|
-
before do
|
24
|
-
allow(File).to receive(:exist?).and_call_original
|
25
|
-
allow(File).to receive(:exist?).with(imap_pathname) { imap_exists }
|
26
|
-
allow(File).to receive(:exist?).with(mbox_pathname) { mbox_exists }
|
27
|
-
|
28
|
-
allow(File).to receive(:open).and_call_original
|
29
|
-
allow(File).
|
30
|
-
to receive(:open).with("/base/path/my/folder.imap") { imap_content }
|
31
|
-
allow(File).to receive(:open).with(imap_pathname, "w").and_yield(imap_file)
|
32
|
-
allow(File).to receive(:open).with(mbox_pathname, "w").and_yield(mbox_file)
|
33
|
-
|
34
|
-
allow(File).to receive(:read).and_call_original
|
35
|
-
allow(File).to receive(:read).with(imap_pathname) { imap_content }
|
36
|
-
|
37
|
-
allow(File).to receive(:unlink).and_call_original
|
38
|
-
allow(File).to receive(:unlink).with(imap_pathname)
|
39
|
-
allow(File).to receive(:unlink).with(mbox_pathname)
|
40
|
-
|
41
|
-
allow(FileUtils).to receive(:chmod)
|
42
|
-
end
|
43
|
-
|
44
|
-
describe "#uid_validity=" do
|
45
|
-
let(:new_uid_validity) { "13" }
|
46
|
-
let(:updated_imap_content) do
|
47
|
-
{
|
48
|
-
version: Imap::Backup::Serializer::MboxStore::CURRENT_VERSION,
|
49
|
-
uid_validity: new_uid_validity,
|
50
|
-
uids: uids
|
51
|
-
}.to_json
|
52
|
-
end
|
53
|
-
|
54
|
-
it "sets uid_validity" do
|
55
|
-
subject.uid_validity = new_uid_validity
|
56
|
-
|
57
|
-
expect(subject.uid_validity).to eq(new_uid_validity)
|
58
|
-
end
|
59
|
-
|
60
|
-
it "writes the imap file" do
|
61
|
-
expect(imap_file).to receive(:write).with(updated_imap_content)
|
62
|
-
|
63
|
-
subject.uid_validity = new_uid_validity
|
64
|
-
end
|
65
|
-
end
|
66
|
-
|
67
|
-
describe "#uids" do
|
68
|
-
it "returns the backed-up uids as integers" do
|
69
|
-
expect(subject.uids).to eq(uids.map(&:to_i))
|
70
|
-
end
|
71
|
-
|
72
|
-
context "when the imap file does not exist" do
|
73
|
-
let(:imap_exists) { false }
|
74
|
-
|
75
|
-
it "returns an empty Array" do
|
76
|
-
expect(subject.uids).to eq([])
|
77
|
-
end
|
78
|
-
end
|
79
|
-
|
80
|
-
context "when the imap file is malformed" do
|
81
|
-
before do
|
82
|
-
allow(JSON).to receive(:parse).and_raise(JSON::ParserError)
|
83
|
-
end
|
84
|
-
|
85
|
-
it "returns an empty Array" do
|
86
|
-
expect(subject.uids).to eq([])
|
87
|
-
end
|
88
|
-
|
89
|
-
it "deletes the imap file" do
|
90
|
-
expect(File).to receive(:unlink).with(imap_pathname)
|
91
|
-
|
92
|
-
subject.uids
|
93
|
-
end
|
94
|
-
|
95
|
-
it "deletes the mbox file" do
|
96
|
-
expect(File).to receive(:unlink).with(mbox_pathname)
|
97
|
-
|
98
|
-
subject.uids
|
99
|
-
end
|
100
|
-
|
101
|
-
it "writes a blank mbox file" do
|
102
|
-
expect(mbox_file).to receive(:write).with("")
|
103
|
-
|
104
|
-
subject.uids
|
105
|
-
end
|
106
|
-
end
|
107
|
-
|
108
|
-
context "when the mbox does not exist" do
|
109
|
-
let(:mbox_exists) { false }
|
110
|
-
|
111
|
-
it "returns an empty Array" do
|
112
|
-
expect(subject.uids).to eq([])
|
113
|
-
end
|
114
|
-
end
|
115
|
-
end
|
116
|
-
|
117
|
-
describe "#add" do
|
118
|
-
let(:mbox_formatted_message) { "message in mbox format" }
|
119
|
-
let(:message_uid) { "999" }
|
120
|
-
let(:message) do
|
121
|
-
instance_double(
|
122
|
-
Email::Mboxrd::Message,
|
123
|
-
to_serialized: mbox_formatted_message
|
124
|
-
)
|
125
|
-
end
|
126
|
-
let(:updated_imap_content) do
|
127
|
-
{
|
128
|
-
version: Imap::Backup::Serializer::MboxStore::CURRENT_VERSION,
|
129
|
-
uid_validity: uid_validity,
|
130
|
-
uids: uids + [999]
|
131
|
-
}.to_json
|
132
|
-
end
|
133
|
-
|
134
|
-
before do
|
135
|
-
allow(Email::Mboxrd::Message).to receive(:new) { message }
|
136
|
-
allow(File).to receive(:open).with(mbox_pathname, "ab") { mbox_file }
|
137
|
-
end
|
138
|
-
|
139
|
-
it "saves the message to the mbox" do
|
140
|
-
expect(mbox_file).to receive(:write).with(mbox_formatted_message)
|
141
|
-
|
142
|
-
subject.add(message_uid, "The\nemail\n")
|
143
|
-
end
|
144
|
-
|
145
|
-
it "saves the uid to the imap file" do
|
146
|
-
expect(imap_file).to receive(:write).with(updated_imap_content)
|
147
|
-
|
148
|
-
subject.add(message_uid, "The\nemail\n")
|
149
|
-
end
|
150
|
-
|
151
|
-
context "when the message is already downloaded" do
|
152
|
-
let(:uids) { [999] }
|
153
|
-
|
154
|
-
it "skips the message" do
|
155
|
-
expect(mbox_file).to_not receive(:write)
|
156
|
-
|
157
|
-
subject.add(message_uid, "The\nemail\n")
|
158
|
-
end
|
159
|
-
end
|
160
|
-
|
161
|
-
context "when the message causes parsing errors" do
|
162
|
-
before do
|
163
|
-
allow(message).to receive(:to_serialized).and_raise(ArgumentError)
|
164
|
-
end
|
165
|
-
|
166
|
-
it "skips the message" do
|
167
|
-
expect(mbox_file).to_not receive(:write)
|
168
|
-
|
169
|
-
subject.add(message_uid, "The\nemail\n")
|
170
|
-
end
|
171
|
-
|
172
|
-
it "does not fail" do
|
173
|
-
expect do
|
174
|
-
subject.add(message_uid, "The\nemail\n")
|
175
|
-
end.to_not raise_error
|
176
|
-
end
|
177
|
-
end
|
178
|
-
end
|
179
|
-
|
180
|
-
describe "#load" do
|
181
|
-
let(:uid) { "1" }
|
182
|
-
let(:enumerator) do
|
183
|
-
instance_double(Imap::Backup::Serializer::MboxEnumerator)
|
184
|
-
end
|
185
|
-
let(:enumeration) { instance_double(Enumerator) }
|
186
|
-
|
187
|
-
before do
|
188
|
-
allow(Imap::Backup::Serializer::MboxEnumerator).
|
189
|
-
to receive(:new) { enumerator }
|
190
|
-
allow(enumerator).to receive(:each) { enumeration }
|
191
|
-
allow(enumeration).
|
192
|
-
to receive(:with_index).
|
193
|
-
and_yield("", 0).
|
194
|
-
and_yield("", 1).
|
195
|
-
and_yield("ciao", 2)
|
196
|
-
end
|
197
|
-
|
198
|
-
it "returns the message" do
|
199
|
-
expect(subject.load(uid).supplied_body).to eq("ciao")
|
200
|
-
end
|
201
|
-
|
202
|
-
context "when the UID is unknown" do
|
203
|
-
let(:uid) { "99" }
|
204
|
-
|
205
|
-
it "returns nil" do
|
206
|
-
expect(subject.load(uid)).to be_nil
|
207
|
-
end
|
208
|
-
end
|
209
|
-
end
|
210
|
-
|
211
|
-
describe "#each_message" do
|
212
|
-
let(:enumerator) do
|
213
|
-
instance_double(Imap::Backup::Serializer::MboxEnumerator)
|
214
|
-
end
|
215
|
-
let(:enumeration) { instance_double(Enumerator) }
|
216
|
-
|
217
|
-
before do
|
218
|
-
allow(Imap::Backup::Serializer::MboxEnumerator).
|
219
|
-
to receive(:new) { enumerator }
|
220
|
-
allow(enumerator).to receive(:each) { enumeration }
|
221
|
-
allow(enumeration).
|
222
|
-
to receive(:with_index).
|
223
|
-
and_yield("", 0).
|
224
|
-
and_yield("", 1).
|
225
|
-
and_yield("ciao", 2)
|
226
|
-
end
|
227
|
-
|
228
|
-
it "yields messages" do
|
229
|
-
expect { |b| subject.each_message([1], &b) }.
|
230
|
-
to yield_successive_args([1, instance_of(Email::Mboxrd::Message)])
|
231
|
-
end
|
232
|
-
|
233
|
-
it "yields the requested message uid" do
|
234
|
-
subject.each_message([1]) do |uid, _message|
|
235
|
-
expect(uid).to eq(1)
|
236
|
-
end
|
237
|
-
end
|
238
|
-
|
239
|
-
it "yields the requested message" do
|
240
|
-
subject.each_message([1]) do |_uid, message|
|
241
|
-
expect(message.supplied_body).to eq("ciao")
|
242
|
-
end
|
243
|
-
end
|
244
|
-
|
245
|
-
context "without a block" do
|
246
|
-
it "returns an Enumerator" do
|
247
|
-
expect(subject.each_message([1])).to be_a(Enumerator)
|
248
|
-
end
|
249
|
-
end
|
250
|
-
end
|
251
|
-
|
252
|
-
describe "#update_uid" do
|
253
|
-
let(:old_uid) { "2" }
|
254
|
-
let(:updated_imap_content) do
|
255
|
-
{
|
256
|
-
version: Imap::Backup::Serializer::MboxStore::CURRENT_VERSION,
|
257
|
-
uid_validity: uid_validity,
|
258
|
-
uids: [3, 999, 1]
|
259
|
-
}.to_json
|
260
|
-
end
|
261
|
-
|
262
|
-
it "updates the stored UID" do
|
263
|
-
expect(imap_file).to receive(:write).with(updated_imap_content)
|
264
|
-
|
265
|
-
subject.update_uid(old_uid, "999")
|
266
|
-
end
|
267
|
-
|
268
|
-
context "when the UID is unknown" do
|
269
|
-
let(:old_uid) { "42" }
|
270
|
-
|
271
|
-
it "does nothing" do
|
272
|
-
expect(imap_file).to_not receive(:write)
|
273
|
-
|
274
|
-
subject.update_uid(old_uid, "999")
|
275
|
-
end
|
276
|
-
end
|
277
|
-
end
|
278
|
-
|
279
|
-
describe "#reset" do
|
280
|
-
it "deletes the imap file" do
|
281
|
-
expect(File).to receive(:unlink).with(imap_pathname)
|
282
|
-
|
283
|
-
subject.reset
|
284
|
-
end
|
285
|
-
|
286
|
-
it "deletes the mbox file" do
|
287
|
-
expect(File).to receive(:unlink).with(mbox_pathname)
|
288
|
-
|
289
|
-
subject.reset
|
290
|
-
end
|
291
|
-
|
292
|
-
it "writes a blank mbox file" do
|
293
|
-
expect(mbox_file).to receive(:write).with("")
|
294
|
-
|
295
|
-
subject.reset
|
296
|
-
end
|
297
|
-
end
|
298
|
-
|
299
|
-
describe "#rename" do
|
300
|
-
let(:new_name) { "new_name" }
|
301
|
-
let(:new_folder_path) { File.join(base_path, new_name) }
|
302
|
-
let(:new_imap_name) { "#{new_folder_path}.imap" }
|
303
|
-
let(:new_mbox_name) { "#{new_folder_path}.mbox" }
|
304
|
-
|
305
|
-
before do
|
306
|
-
allow(File).to receive(:rename).and_call_original
|
307
|
-
allow(File).to receive(:rename).with(imap_pathname, new_imap_name)
|
308
|
-
allow(File).to receive(:rename).with(mbox_pathname, new_mbox_name)
|
309
|
-
end
|
310
|
-
|
311
|
-
it "renames the imap file" do
|
312
|
-
expect(File).to receive(:rename).with(imap_pathname, new_imap_name)
|
313
|
-
|
314
|
-
subject.rename(new_name)
|
315
|
-
end
|
316
|
-
|
317
|
-
it "renames the mbox file" do
|
318
|
-
expect(File).to receive(:rename).with(mbox_pathname, new_mbox_name)
|
319
|
-
|
320
|
-
subject.rename(new_name)
|
321
|
-
end
|
322
|
-
|
323
|
-
it "updates the folder name" do
|
324
|
-
subject.rename(new_name)
|
325
|
-
|
326
|
-
expect(subject.folder).to eq(new_name)
|
327
|
-
end
|
328
|
-
end
|
329
|
-
end
|