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.
- checksums.yaml +4 -4
- data/.rspec +0 -1
- data/.rubocop.yml +5 -2
- data/.rubocop_todo.yml +24 -0
- data/.travis.yml +13 -1
- data/README.md +9 -21
- data/Rakefile +6 -2
- data/imap-backup.gemspec +5 -1
- data/lib/email/mboxrd/message.rb +13 -12
- data/lib/imap/backup/account/connection.rb +3 -2
- data/lib/imap/backup/account/folder.rb +2 -0
- data/lib/imap/backup/configuration/account.rb +20 -16
- data/lib/imap/backup/configuration/asker.rb +1 -1
- data/lib/imap/backup/serializer/mbox.rb +39 -25
- data/lib/imap/backup/serializer/mbox_enumerator.rb +31 -0
- data/lib/imap/backup/serializer/mbox_store.rb +6 -27
- data/lib/imap/backup/version.rb +1 -1
- data/spec/features/support/email_server.rb +3 -0
- data/spec/support/fixtures.rb +1 -1
- data/spec/unit/email/mboxrd/message_spec.rb +20 -3
- data/spec/unit/email/provider_spec.rb +1 -1
- data/spec/unit/imap/backup/account/connection_spec.rb +10 -9
- data/spec/unit/imap/backup/account/folder_spec.rb +24 -10
- data/spec/unit/imap/backup/configuration/account_spec.rb +47 -22
- data/spec/unit/imap/backup/configuration/asker_spec.rb +5 -9
- data/spec/unit/imap/backup/configuration/connection_tester_spec.rb +6 -6
- data/spec/unit/imap/backup/configuration/folder_chooser_spec.rb +6 -6
- data/spec/unit/imap/backup/configuration/list_spec.rb +24 -1
- data/spec/unit/imap/backup/configuration/setup_spec.rb +29 -9
- data/spec/unit/imap/backup/configuration/store_spec.rb +5 -5
- data/spec/unit/imap/backup/downloader_spec.rb +11 -13
- data/spec/unit/imap/backup/serializer/mbox_enumerator_spec.rb +40 -0
- data/spec/unit/imap/backup/serializer/mbox_spec.rb +63 -24
- data/spec/unit/imap/backup/serializer/mbox_store_spec.rb +162 -9
- data/spec/unit/imap/backup/utils_spec.rb +3 -3
- data/spec/unit/imap/backup_spec.rb +28 -0
- 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(
|
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)
|
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
|
-
|
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
|
|
data/lib/imap/backup/version.rb
CHANGED
data/spec/support/fixtures.rb
CHANGED
@@ -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,
|
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
|
-
|
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
|
-
|
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
|
-
|
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
|
@@ -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
|
-
|
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
|
-
|
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
|
-
|
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
|
-
|
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
|
-
|
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
|
-
|
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).
|
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
|
-
|
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
|
-
|
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
|
-
|
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
|
-
|
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 "
|
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 "
|
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
|
-
|
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
|
-
|
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
|
-
|
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
|
-
|
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
|
-
|
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:
|
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
|
-
|
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
|
-
|
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
|
-
|
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
|
-
|
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
|
-
|
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
|
-
|
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
|
-
|
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 "
|
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
|
-
|
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 "
|
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 "
|
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
|
-
|
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
|
-
|
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
|
-
|
260
|
-
|
261
|
-
|
262
|
-
|
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
|
-
|
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
|
-
|
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
|
-
|
325
|
+
describe "deletion" do
|
301
326
|
let(:confirmed) { true }
|
302
327
|
|
303
328
|
before do
|