imap-backup 3.0.0 → 3.1.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/lib/imap/backup/account/connection.rb +28 -20
- data/lib/imap/backup/account/folder.rb +10 -5
- data/lib/imap/backup/configuration/folder_chooser.rb +14 -15
- data/lib/imap/backup/version.rb +1 -1
- data/spec/unit/imap/backup/account/connection_spec.rb +25 -14
- data/spec/unit/imap/backup/account/folder_spec.rb +25 -10
- data/spec/unit/imap/backup/configuration/folder_chooser_spec.rb +7 -13
- metadata +1 -1
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: a62efa6b4c2e63180629e4a9770cf6b636eb48fa1aaad0177d7bab975ce3f251
|
4
|
+
data.tar.gz: ab0dee2784879df462e8fa1d622436b4a23f332dc731dd34ce353451a7164ce9
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: ffe7443b8450980b05b170e8b4ed2d8cdaa093b17a60bf72c6c87c07e30752de1010aa444da3d4aea1b4348cbdd7b4fff0f2793e100f66de592796a8fa9885b6
|
7
|
+
data.tar.gz: 5779c9d8bc98070cba06a68c571b849cdb33fb994954f6f3cf320642d1d49023ab9db5c6f713db26c413703caf046745589a61e5d4cde2e2c6bce751e7d6f3a1
|
@@ -18,7 +18,7 @@ module Imap::Backup
|
|
18
18
|
@username = options[:username]
|
19
19
|
@password = options[:password]
|
20
20
|
@local_path = options[:local_path]
|
21
|
-
@
|
21
|
+
@config_folders = options[:folders]
|
22
22
|
@server = options[:server]
|
23
23
|
@connection_options = options[:connection_options] || {}
|
24
24
|
@folders = nil
|
@@ -29,21 +29,24 @@ module Imap::Backup
|
|
29
29
|
@folders ||=
|
30
30
|
begin
|
31
31
|
root = provider_root
|
32
|
-
|
33
|
-
|
34
|
-
|
35
|
-
|
36
|
-
|
32
|
+
mailbox_lists = imap.list(root, "*")
|
33
|
+
|
34
|
+
if mailbox_lists.nil?
|
35
|
+
message = "Unable to get folder list for account #{username}"
|
36
|
+
Imap::Backup.logger.info message
|
37
|
+
raise message
|
37
38
|
end
|
38
|
-
|
39
|
+
|
40
|
+
utf7_encoded = mailbox_lists.map(&:name)
|
41
|
+
utf7_encoded.map { |n| Net::IMAP.decode_utf7(n) }
|
39
42
|
end
|
40
43
|
end
|
41
44
|
|
42
45
|
def status
|
43
|
-
backup_folders.map do |
|
44
|
-
f = Account::Folder.new(self,
|
45
|
-
s = Serializer::Mbox.new(local_path,
|
46
|
-
{name:
|
46
|
+
backup_folders.map do |backup_folder|
|
47
|
+
f = Account::Folder.new(self, backup_folder[:name])
|
48
|
+
s = Serializer::Mbox.new(local_path, backup_folder[:name])
|
49
|
+
{name: backup_folder[:name], local: s.uids, remote: f.uids}
|
47
50
|
end
|
48
51
|
end
|
49
52
|
|
@@ -105,18 +108,12 @@ module Imap::Backup
|
|
105
108
|
@server = provider.host
|
106
109
|
end
|
107
110
|
|
108
|
-
def backup_folders
|
109
|
-
return @backup_folders if @backup_folders && !@backup_folders.empty?
|
110
|
-
|
111
|
-
(folders || []).map { |f| {name: f.name} }
|
112
|
-
end
|
113
|
-
|
114
111
|
private
|
115
112
|
|
116
113
|
def each_folder
|
117
|
-
backup_folders.each do |
|
118
|
-
folder = Account::Folder.new(self,
|
119
|
-
serializer = Serializer::Mbox.new(local_path,
|
114
|
+
backup_folders.each do |backup_folder|
|
115
|
+
folder = Account::Folder.new(self, backup_folder[:name])
|
116
|
+
serializer = Serializer::Mbox.new(local_path, backup_folder[:name])
|
120
117
|
yield folder, serializer
|
121
118
|
end
|
122
119
|
end
|
@@ -175,6 +172,17 @@ module Imap::Backup
|
|
175
172
|
end
|
176
173
|
end
|
177
174
|
|
175
|
+
def backup_folders
|
176
|
+
@backup_folders ||=
|
177
|
+
begin
|
178
|
+
if @config_folders && @config_folders.any?
|
179
|
+
@config_folders
|
180
|
+
else
|
181
|
+
folders.map { |name| {name: name} }
|
182
|
+
end
|
183
|
+
end
|
184
|
+
end
|
185
|
+
|
178
186
|
def provider
|
179
187
|
@provider ||= Email::Provider.for_address(username)
|
180
188
|
end
|
@@ -36,7 +36,7 @@ module Imap::Backup
|
|
36
36
|
def create
|
37
37
|
return if exist?
|
38
38
|
|
39
|
-
imap.create(
|
39
|
+
imap.create(utf7_encoded_name)
|
40
40
|
end
|
41
41
|
|
42
42
|
def uid_validity
|
@@ -81,22 +81,27 @@ module Imap::Backup
|
|
81
81
|
def append(message)
|
82
82
|
body = message.imap_body
|
83
83
|
date = message.date&.to_time
|
84
|
-
response = imap.append(
|
84
|
+
response = imap.append(utf7_encoded_name, body, nil, date)
|
85
85
|
extract_uid(response)
|
86
86
|
end
|
87
87
|
|
88
88
|
private
|
89
89
|
|
90
90
|
def examine
|
91
|
-
imap.examine(
|
91
|
+
imap.examine(utf7_encoded_name)
|
92
92
|
rescue Net::IMAP::NoResponseError
|
93
|
-
Imap::Backup.logger.warn "Folder '#{name}' does not exist"
|
94
|
-
raise FolderNotFound, "Folder '#{name}' does not exist"
|
93
|
+
Imap::Backup.logger.warn "Folder '#{name}' does not exist on server"
|
94
|
+
raise FolderNotFound, "Folder '#{name}' does not exist on server"
|
95
95
|
end
|
96
96
|
|
97
97
|
def extract_uid(response)
|
98
98
|
@uid_validity, uid = response.data.code.data.split(" ").map(&:to_i)
|
99
99
|
uid
|
100
100
|
end
|
101
|
+
|
102
|
+
def utf7_encoded_name
|
103
|
+
@utf7_encoded_name ||=
|
104
|
+
Net::IMAP.encode_utf7(name).force_encoding("ASCII-8BIT")
|
105
|
+
end
|
101
106
|
end
|
102
107
|
end
|
@@ -15,7 +15,7 @@ module Imap::Backup
|
|
15
15
|
return
|
16
16
|
end
|
17
17
|
|
18
|
-
if
|
18
|
+
if imap_folders.nil?
|
19
19
|
Imap::Backup.logger.warn "Unable to get folder list"
|
20
20
|
highline.ask "Press a key "
|
21
21
|
return
|
@@ -44,29 +44,28 @@ module Imap::Backup
|
|
44
44
|
end
|
45
45
|
|
46
46
|
def add_folders(menu)
|
47
|
-
|
48
|
-
|
49
|
-
mark
|
50
|
-
|
51
|
-
toggle_selection name
|
47
|
+
imap_folders.each do |folder|
|
48
|
+
mark = selected?(folder) ? "+" : "-"
|
49
|
+
menu.choice("#{mark} #{folder}") do
|
50
|
+
toggle_selection folder
|
52
51
|
end
|
53
52
|
end
|
54
53
|
end
|
55
54
|
|
56
55
|
def selected?(folder_name)
|
57
|
-
|
58
|
-
return false if
|
56
|
+
config_folders = account[:folders]
|
57
|
+
return false if config_folders.nil?
|
59
58
|
|
60
|
-
|
59
|
+
config_folders.find { |f| f[:name] == folder_name }
|
61
60
|
end
|
62
61
|
|
63
62
|
def remove_missing
|
64
63
|
removed = []
|
65
|
-
|
64
|
+
config_folders = []
|
66
65
|
account[:folders].each do |f|
|
67
|
-
found =
|
66
|
+
found = imap_folders.find { |folder| folder == f[:name] }
|
68
67
|
if found
|
69
|
-
|
68
|
+
config_folders << f
|
70
69
|
else
|
71
70
|
removed << f[:name]
|
72
71
|
end
|
@@ -74,7 +73,7 @@ module Imap::Backup
|
|
74
73
|
|
75
74
|
return if removed.empty?
|
76
75
|
|
77
|
-
account[:folders] =
|
76
|
+
account[:folders] = config_folders
|
78
77
|
account[:modified] = true
|
79
78
|
|
80
79
|
Kernel.puts <<~MESSAGE
|
@@ -101,8 +100,8 @@ module Imap::Backup
|
|
101
100
|
nil
|
102
101
|
end
|
103
102
|
|
104
|
-
def
|
105
|
-
@
|
103
|
+
def imap_folders
|
104
|
+
@imap_folders ||= connection.folders
|
106
105
|
end
|
107
106
|
|
108
107
|
def highline
|
data/lib/imap/backup/version.rb
CHANGED
@@ -23,11 +23,11 @@ describe Imap::Backup::Account::Connection do
|
|
23
23
|
username: USERNAME,
|
24
24
|
password: PASSWORD,
|
25
25
|
local_path: LOCAL_PATH,
|
26
|
-
folders:
|
26
|
+
folders: config_folders,
|
27
27
|
server: server
|
28
28
|
}
|
29
29
|
end
|
30
|
-
let(:
|
30
|
+
let(:config_folders) { [FOLDER_CONFIG] }
|
31
31
|
let(:root_info) do
|
32
32
|
instance_double(Net::IMAP::MailboxList, name: ROOT_NAME)
|
33
33
|
end
|
@@ -62,7 +62,6 @@ describe Imap::Backup::Account::Connection do
|
|
62
62
|
[:username, USERNAME],
|
63
63
|
[:password, PASSWORD],
|
64
64
|
[:local_path, LOCAL_PATH],
|
65
|
-
[:backup_folders, [FOLDER_CONFIG]],
|
66
65
|
[:server, SERVER]
|
67
66
|
].each do |attr, expected|
|
68
67
|
it "expects #{attr}" do
|
@@ -144,11 +143,21 @@ describe Imap::Backup::Account::Connection do
|
|
144
143
|
|
145
144
|
describe "#folders" do
|
146
145
|
let(:imap_folders) do
|
147
|
-
[instance_double(Net::IMAP::MailboxList)]
|
146
|
+
[instance_double(Net::IMAP::MailboxList, name: BACKUP_FOLDER)]
|
148
147
|
end
|
149
148
|
|
150
149
|
it "returns the list of folders" do
|
151
|
-
expect(subject.folders).to eq(
|
150
|
+
expect(subject.folders).to eq([BACKUP_FOLDER])
|
151
|
+
end
|
152
|
+
|
153
|
+
context "with non-ASCII folder names" do
|
154
|
+
let(:imap_folders) do
|
155
|
+
[instance_double(Net::IMAP::MailboxList, name: "Gel&APY-scht")]
|
156
|
+
end
|
157
|
+
|
158
|
+
it "converts them to UTF-8" do
|
159
|
+
expect(subject.folders).to eq(["Gelöscht"])
|
160
|
+
end
|
152
161
|
end
|
153
162
|
end
|
154
163
|
|
@@ -198,7 +207,7 @@ describe Imap::Backup::Account::Connection do
|
|
198
207
|
with(LOCAL_PATH, BACKUP_FOLDER) { serializer }
|
199
208
|
end
|
200
209
|
|
201
|
-
context "with supplied
|
210
|
+
context "with supplied config_folders" do
|
202
211
|
it "runs the downloader" do
|
203
212
|
expect(downloader).to receive(:run)
|
204
213
|
|
@@ -216,7 +225,7 @@ describe Imap::Backup::Account::Connection do
|
|
216
225
|
end
|
217
226
|
end
|
218
227
|
|
219
|
-
context "without supplied
|
228
|
+
context "without supplied config_folders" do
|
220
229
|
let(:imap_folders) do
|
221
230
|
[instance_double(Net::IMAP::MailboxList, name: ROOT_NAME)]
|
222
231
|
end
|
@@ -228,8 +237,8 @@ describe Imap::Backup::Account::Connection do
|
|
228
237
|
with(LOCAL_PATH, ROOT_NAME) { serializer }
|
229
238
|
end
|
230
239
|
|
231
|
-
context "when supplied
|
232
|
-
let(:
|
240
|
+
context "when supplied config_folders is nil" do
|
241
|
+
let(:config_folders) { nil }
|
233
242
|
|
234
243
|
it "runs the downloader for each folder" do
|
235
244
|
expect(downloader).to receive(:run).exactly(:once)
|
@@ -238,8 +247,8 @@ describe Imap::Backup::Account::Connection do
|
|
238
247
|
end
|
239
248
|
end
|
240
249
|
|
241
|
-
context "when supplied
|
242
|
-
let(:
|
250
|
+
context "when supplied config_folders is an empty list" do
|
251
|
+
let(:config_folders) { [] }
|
243
252
|
|
244
253
|
it "runs the downloader for each folder" do
|
245
254
|
expect(downloader).to receive(:run).exactly(:once)
|
@@ -249,11 +258,13 @@ describe Imap::Backup::Account::Connection do
|
|
249
258
|
end
|
250
259
|
|
251
260
|
context "when the imap server doesn't return folders" do
|
252
|
-
let(:
|
261
|
+
let(:config_folders) { nil }
|
253
262
|
let(:imap_folders) { nil }
|
254
263
|
|
255
|
-
it "
|
256
|
-
expect
|
264
|
+
it "fails" do
|
265
|
+
expect do
|
266
|
+
subject.run_backup
|
267
|
+
end.to raise_error(RuntimeError, /Unable to get folder list/)
|
257
268
|
end
|
258
269
|
end
|
259
270
|
end
|
@@ -1,7 +1,10 @@
|
|
1
1
|
# rubocop:disable RSpec/PredicateMatcher
|
2
2
|
|
3
3
|
describe Imap::Backup::Account::Folder do
|
4
|
-
|
4
|
+
FOLDER_NAME = "Gelöscht"
|
5
|
+
ENCODED_FOLDER_NAME = "Gel&APY-scht"
|
6
|
+
|
7
|
+
subject { described_class.new(connection, FOLDER_NAME) }
|
5
8
|
|
6
9
|
let(:imap) do
|
7
10
|
instance_double(
|
@@ -16,7 +19,7 @@ describe Imap::Backup::Account::Folder do
|
|
16
19
|
instance_double(Imap::Backup::Account::Connection, imap: imap)
|
17
20
|
end
|
18
21
|
let(:missing_mailbox_data) do
|
19
|
-
OpenStruct.new(text: "Unknown Mailbox:
|
22
|
+
OpenStruct.new(text: "Unknown Mailbox: #{FOLDER_NAME}")
|
20
23
|
end
|
21
24
|
let(:missing_mailbox_response) { OpenStruct.new(data: missing_mailbox_data) }
|
22
25
|
let(:missing_mailbox_error) do
|
@@ -36,7 +39,8 @@ describe Imap::Backup::Account::Folder do
|
|
36
39
|
|
37
40
|
context "with missing mailboxes" do
|
38
41
|
before do
|
39
|
-
allow(imap).to receive(:examine).
|
42
|
+
allow(imap).to receive(:examine).
|
43
|
+
with(ENCODED_FOLDER_NAME).and_raise(missing_mailbox_error)
|
40
44
|
end
|
41
45
|
|
42
46
|
it "returns an empty array" do
|
@@ -50,7 +54,8 @@ describe Imap::Backup::Account::Folder do
|
|
50
54
|
end
|
51
55
|
|
52
56
|
before do
|
53
|
-
allow(imap).to receive(:examine).
|
57
|
+
allow(imap).to receive(:examine).
|
58
|
+
with(ENCODED_FOLDER_NAME).and_raise(missing_mailbox_error)
|
54
59
|
end
|
55
60
|
|
56
61
|
it "returns an empty array" do
|
@@ -82,7 +87,8 @@ describe Imap::Backup::Account::Folder do
|
|
82
87
|
|
83
88
|
context "when the mailbox doesn't exist" do
|
84
89
|
before do
|
85
|
-
allow(imap).to receive(:examine).
|
90
|
+
allow(imap).to receive(:examine).
|
91
|
+
with(ENCODED_FOLDER_NAME).and_raise(missing_mailbox_error)
|
86
92
|
end
|
87
93
|
|
88
94
|
it "is nil" do
|
@@ -101,7 +107,7 @@ describe Imap::Backup::Account::Folder do
|
|
101
107
|
|
102
108
|
describe "#folder" do
|
103
109
|
it "is the name" do
|
104
|
-
expect(subject.folder).to eq(
|
110
|
+
expect(subject.folder).to eq(FOLDER_NAME)
|
105
111
|
end
|
106
112
|
end
|
107
113
|
|
@@ -114,7 +120,8 @@ describe Imap::Backup::Account::Folder do
|
|
114
120
|
|
115
121
|
context "when the folder doesn't exist" do
|
116
122
|
before do
|
117
|
-
allow(imap).to receive(:examine).
|
123
|
+
allow(imap).to receive(:examine).
|
124
|
+
with(ENCODED_FOLDER_NAME).and_raise(missing_mailbox_error)
|
118
125
|
end
|
119
126
|
|
120
127
|
it "is false" do
|
@@ -134,14 +141,21 @@ describe Imap::Backup::Account::Folder do
|
|
134
141
|
|
135
142
|
context "when the folder doesn't exist" do
|
136
143
|
before do
|
137
|
-
allow(imap).to receive(:examine).
|
144
|
+
allow(imap).to receive(:examine).
|
145
|
+
with(ENCODED_FOLDER_NAME).and_raise(missing_mailbox_error)
|
138
146
|
end
|
139
147
|
|
140
|
-
it "
|
148
|
+
it "creates the folder" do
|
141
149
|
expect(imap).to receive(:create)
|
142
150
|
|
143
151
|
subject.create
|
144
152
|
end
|
153
|
+
|
154
|
+
it "encodes the folder name" do
|
155
|
+
expect(imap).to receive(:create).with(ENCODED_FOLDER_NAME)
|
156
|
+
|
157
|
+
subject.create
|
158
|
+
end
|
145
159
|
end
|
146
160
|
end
|
147
161
|
|
@@ -154,7 +168,8 @@ describe Imap::Backup::Account::Folder do
|
|
154
168
|
|
155
169
|
context "when the folder doesn't exist" do
|
156
170
|
before do
|
157
|
-
allow(imap).to receive(:examine).
|
171
|
+
allow(imap).to receive(:examine).
|
172
|
+
with(ENCODED_FOLDER_NAME).and_raise(missing_mailbox_error)
|
158
173
|
end
|
159
174
|
|
160
175
|
it "raises an error" do
|
@@ -6,11 +6,11 @@ describe Imap::Backup::Configuration::FolderChooser do
|
|
6
6
|
|
7
7
|
let(:connection) do
|
8
8
|
instance_double(
|
9
|
-
Imap::Backup::Account::Connection, folders:
|
9
|
+
Imap::Backup::Account::Connection, folders: connection_folders
|
10
10
|
)
|
11
11
|
end
|
12
12
|
let(:account) { {folders: []} }
|
13
|
-
let(:
|
13
|
+
let(:connection_folders) { [] }
|
14
14
|
let!(:highline_streams) { prepare_highline }
|
15
15
|
let(:input) { highline_streams[0] }
|
16
16
|
let(:output) { highline_streams[1] }
|
@@ -37,15 +37,9 @@ describe Imap::Backup::Configuration::FolderChooser do
|
|
37
37
|
|
38
38
|
describe "folder listing" do
|
39
39
|
let(:account) { {folders: [{name: "my_folder"}]} }
|
40
|
-
let(:
|
41
|
-
#
|
42
|
-
|
43
|
-
Imap::Backup::Account::Folder, name: "my_folder"
|
44
|
-
)
|
45
|
-
folder2 = instance_double(
|
46
|
-
Imap::Backup::Account::Folder, name: "another_folder"
|
47
|
-
)
|
48
|
-
[folder1, folder2]
|
40
|
+
let(:connection_folders) do
|
41
|
+
# N.B. my_folder is already backed up
|
42
|
+
%w(my_folder another_folder)
|
49
43
|
end
|
50
44
|
|
51
45
|
describe "display" do
|
@@ -93,7 +87,7 @@ describe Imap::Backup::Configuration::FolderChooser do
|
|
93
87
|
let(:account) do
|
94
88
|
{folders: [{name: "on_server"}, {name: "not_on_server"}]}
|
95
89
|
end
|
96
|
-
let(:
|
90
|
+
let(:connection_folders) do
|
97
91
|
[
|
98
92
|
instance_double(Imap::Backup::Account::Folder, name: "on_server")
|
99
93
|
]
|
@@ -112,7 +106,7 @@ describe Imap::Backup::Configuration::FolderChooser do
|
|
112
106
|
end
|
113
107
|
|
114
108
|
context "when folders are not available" do
|
115
|
-
let(:
|
109
|
+
let(:connection_folders) { nil }
|
116
110
|
|
117
111
|
before do
|
118
112
|
allow(Imap::Backup::Configuration::Setup.highline).
|