imap-backup 4.0.4 → 4.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/bin/imap-backup +5 -2
- data/lib/email/provider/apple_mail.rb +2 -2
- data/lib/email/provider/base.rb +8 -0
- data/lib/email/provider/fastmail.rb +2 -2
- data/lib/email/provider/gmail.rb +2 -2
- data/lib/email/provider/{default.rb → unknown.rb} +2 -3
- data/lib/email/provider.rb +2 -2
- data/lib/imap/backup/account/connection.rb +70 -58
- data/lib/imap/backup/account/folder.rb +23 -3
- data/lib/imap/backup/account.rb +102 -0
- data/lib/imap/backup/cli/accounts.rb +43 -0
- data/lib/imap/backup/cli/folders.rb +3 -1
- data/lib/imap/backup/cli/helpers.rb +8 -9
- data/lib/imap/backup/cli/local.rb +4 -2
- data/lib/imap/backup/cli/setup.rb +1 -1
- data/lib/imap/backup/cli/utils.rb +3 -2
- data/lib/imap/backup/{configuration/store.rb → configuration.rb} +49 -14
- data/lib/imap/backup/downloader.rb +26 -12
- data/lib/imap/backup/logger.rb +42 -0
- data/lib/imap/backup/sanitizer.rb +42 -0
- data/lib/imap/backup/serializer/mbox_store.rb +2 -2
- data/lib/imap/backup/setup/account.rb +177 -0
- data/lib/imap/backup/{configuration → setup}/asker.rb +5 -5
- data/lib/imap/backup/setup/connection_tester.rb +26 -0
- data/lib/imap/backup/{configuration → setup}/folder_chooser.rb +25 -17
- data/lib/imap/backup/setup/helpers.rb +15 -0
- data/lib/imap/backup/{configuration/setup.rb → setup.rb} +33 -25
- data/lib/imap/backup/uploader.rb +2 -2
- data/lib/imap/backup/version.rb +2 -2
- data/lib/imap/backup.rb +7 -33
- data/lib/retry_on_error.rb +1 -1
- data/spec/features/backup_spec.rb +1 -0
- data/spec/features/support/email_server.rb +5 -2
- data/spec/features/support/shared/connection_context.rb +7 -5
- data/spec/support/higline_test_helpers.rb +1 -1
- data/spec/support/silence_logging.rb +1 -1
- data/spec/unit/email/provider/{default_spec.rb → base_spec.rb} +1 -7
- data/spec/unit/email/provider_spec.rb +2 -2
- data/spec/unit/imap/backup/account/connection_spec.rb +22 -26
- data/spec/unit/imap/backup/cli/accounts_spec.rb +47 -0
- data/spec/unit/imap/backup/cli/local_spec.rb +15 -4
- data/spec/unit/imap/backup/cli/utils_spec.rb +54 -42
- data/spec/unit/imap/backup/{configuration/store_spec.rb → configuration_spec.rb} +23 -24
- data/spec/unit/imap/backup/downloader_spec.rb +1 -1
- data/spec/unit/imap/backup/logger_spec.rb +48 -0
- data/spec/unit/imap/backup/{configuration → setup}/account_spec.rb +78 -70
- data/spec/unit/imap/backup/{configuration → setup}/asker_spec.rb +2 -2
- data/spec/unit/imap/backup/{configuration → setup}/connection_tester_spec.rb +10 -10
- data/spec/unit/imap/backup/{configuration → setup}/folder_chooser_spec.rb +25 -26
- data/spec/unit/imap/backup/{configuration/setup_spec.rb → setup_spec.rb} +81 -52
- metadata +54 -51
- data/lib/imap/backup/configuration/account.rb +0 -159
- data/lib/imap/backup/configuration/connection_tester.rb +0 -14
- data/lib/imap/backup/configuration/list.rb +0 -53
- data/spec/support/shared_examples/account_flagging.rb +0 -23
- data/spec/unit/imap/backup/configuration/list_spec.rb +0 -89
- data/spec/unit/imap/backup_spec.rb +0 -28
@@ -1,11 +1,13 @@
|
|
1
1
|
require "json"
|
2
2
|
require "os"
|
3
3
|
|
4
|
-
|
5
|
-
module Configuration; end
|
4
|
+
require "imap/backup/account"
|
6
5
|
|
7
|
-
|
6
|
+
module Imap::Backup
|
7
|
+
class Configuration
|
8
8
|
CONFIGURATION_DIRECTORY = File.expand_path("~/.imap-backup")
|
9
|
+
DEFAULT_DOWNLOAD_BLOCK_SIZE = 1
|
10
|
+
VERSION = "2.0"
|
9
11
|
|
10
12
|
attr_reader :pathname
|
11
13
|
|
@@ -19,6 +21,8 @@ module Imap::Backup
|
|
19
21
|
|
20
22
|
def initialize(pathname = self.class.default_pathname)
|
21
23
|
@pathname = pathname
|
24
|
+
@saved_debug = nil
|
25
|
+
@debug = nil
|
22
26
|
end
|
23
27
|
|
24
28
|
def path
|
@@ -26,53 +30,84 @@ module Imap::Backup
|
|
26
30
|
end
|
27
31
|
|
28
32
|
def save
|
33
|
+
ensure_loaded!
|
29
34
|
FileUtils.mkdir(path) if !File.directory?(path)
|
30
35
|
make_private(path) if !windows?
|
31
36
|
remove_modified_flags
|
32
37
|
remove_deleted_accounts
|
33
|
-
|
38
|
+
save_data = {
|
39
|
+
version: VERSION,
|
40
|
+
accounts: accounts.map(&:to_h),
|
41
|
+
debug: debug?
|
42
|
+
}
|
43
|
+
File.open(pathname, "w") { |f| f.write(JSON.pretty_generate(save_data)) }
|
34
44
|
FileUtils.chmod(0o600, pathname) if !windows?
|
45
|
+
@data = nil
|
35
46
|
end
|
36
47
|
|
37
48
|
def accounts
|
38
|
-
|
49
|
+
@accounts ||= begin
|
50
|
+
ensure_loaded!
|
51
|
+
data[:accounts].map { |data| Account.new(data) }
|
52
|
+
end
|
53
|
+
end
|
54
|
+
|
55
|
+
def download_block_size
|
56
|
+
size = ENV["DOWNLOAD_BLOCK_SIZE"].to_i
|
57
|
+
if size > 0
|
58
|
+
size
|
59
|
+
else
|
60
|
+
DEFAULT_DOWNLOAD_BLOCK_SIZE
|
61
|
+
end
|
39
62
|
end
|
40
63
|
|
41
64
|
def modified?
|
42
|
-
|
65
|
+
ensure_loaded!
|
66
|
+
return true if @saved_debug != @debug
|
67
|
+
|
68
|
+
accounts.any? { |a| a.modified? || a.marked_for_deletion? }
|
43
69
|
end
|
44
70
|
|
45
71
|
def debug?
|
46
|
-
|
72
|
+
ensure_loaded!
|
73
|
+
@debug
|
47
74
|
end
|
48
75
|
|
49
76
|
def debug=(value)
|
50
|
-
|
77
|
+
ensure_loaded!
|
78
|
+
@debug = [true, false].include?(value) ? value : false
|
51
79
|
end
|
52
80
|
|
53
81
|
private
|
54
82
|
|
83
|
+
def ensure_loaded!
|
84
|
+
return true if @data
|
85
|
+
|
86
|
+
data
|
87
|
+
@debug = data.key?(:debug) ? data[:debug] == true : false
|
88
|
+
@saved_debug = @debug
|
89
|
+
true
|
90
|
+
end
|
91
|
+
|
55
92
|
def data
|
56
93
|
@data ||=
|
57
94
|
begin
|
58
95
|
if File.exist?(pathname)
|
59
96
|
Utils.check_permissions(pathname, 0o600) if !windows?
|
60
97
|
contents = File.read(pathname)
|
61
|
-
|
98
|
+
JSON.parse(contents, symbolize_names: true)
|
62
99
|
else
|
63
|
-
|
100
|
+
{accounts: []}
|
64
101
|
end
|
65
|
-
data[:debug] = data.key?(:debug) ? data[:debug] == true : false
|
66
|
-
data
|
67
102
|
end
|
68
103
|
end
|
69
104
|
|
70
105
|
def remove_modified_flags
|
71
|
-
accounts.each { |a| a.
|
106
|
+
accounts.each { |a| a.clear_changes! }
|
72
107
|
end
|
73
108
|
|
74
109
|
def remove_deleted_accounts
|
75
|
-
accounts.reject! { |a| a
|
110
|
+
accounts.reject! { |a| a.marked_for_deletion? }
|
76
111
|
end
|
77
112
|
|
78
113
|
def make_private(path)
|
@@ -2,27 +2,41 @@ module Imap::Backup
|
|
2
2
|
class Downloader
|
3
3
|
attr_reader :folder
|
4
4
|
attr_reader :serializer
|
5
|
+
attr_reader :block_size
|
5
6
|
|
6
|
-
def initialize(folder, serializer)
|
7
|
+
def initialize(folder, serializer, block_size: 1)
|
7
8
|
@folder = folder
|
8
9
|
@serializer = serializer
|
10
|
+
@block_size = block_size
|
9
11
|
end
|
10
12
|
|
11
13
|
def run
|
12
14
|
uids = folder.uids - serializer.uids
|
13
15
|
count = uids.count
|
14
|
-
Imap::Backup.logger.debug "[#{folder.name}] #{count} new messages"
|
15
|
-
uids.
|
16
|
-
|
17
|
-
|
18
|
-
if
|
19
|
-
|
20
|
-
|
16
|
+
Imap::Backup::Logger.logger.debug "[#{folder.name}] #{count} new messages"
|
17
|
+
uids.each_slice(block_size).with_index do |block, i|
|
18
|
+
offset = i * block_size + 1
|
19
|
+
uids_and_bodies = folder.fetch_multi(block)
|
20
|
+
if uids_and_bodies.nil?
|
21
|
+
if block_size > 1
|
22
|
+
Imap::Backup::Logger.logger.debug("[#{folder.name}] Multi fetch failed for UIDs #{block.join(", ")}, switching to single fetches")
|
23
|
+
@block_size = 1
|
24
|
+
redo
|
25
|
+
else
|
26
|
+
Imap::Backup::Logger.logger.debug("[#{folder.name}] Fetch failed for UID #{block[0]} - skipping")
|
27
|
+
next
|
28
|
+
end
|
29
|
+
end
|
30
|
+
|
31
|
+
uids_and_bodies.each.with_index do |uid_and_body, j|
|
32
|
+
uid = uid_and_body[:uid]
|
33
|
+
body = uid_and_body[:body]
|
34
|
+
Imap::Backup::Logger.logger.debug(
|
35
|
+
"[#{folder.name}] uid: #{uid} (#{offset +j}/#{count}) - " \
|
36
|
+
"#{body.size} bytes"
|
37
|
+
)
|
38
|
+
serializer.save(uid, body)
|
21
39
|
end
|
22
|
-
Imap::Backup.logger.debug(
|
23
|
-
"#{log_prefix} #{body.size} bytes"
|
24
|
-
)
|
25
|
-
serializer.save(uid, body)
|
26
40
|
end
|
27
41
|
end
|
28
42
|
end
|
@@ -0,0 +1,42 @@
|
|
1
|
+
require "logger"
|
2
|
+
require "singleton"
|
3
|
+
|
4
|
+
require "imap/backup/configuration"
|
5
|
+
require "imap/backup/sanitizer"
|
6
|
+
|
7
|
+
module Imap::Backup
|
8
|
+
class Logger
|
9
|
+
include Singleton
|
10
|
+
|
11
|
+
def self.logger
|
12
|
+
Logger.instance.logger
|
13
|
+
end
|
14
|
+
|
15
|
+
def self.setup_logging(config = Configuration.new)
|
16
|
+
logger.level =
|
17
|
+
if config.debug?
|
18
|
+
::Logger::Severity::DEBUG
|
19
|
+
else
|
20
|
+
::Logger::Severity::ERROR
|
21
|
+
end
|
22
|
+
Net::IMAP.debug = config.debug?
|
23
|
+
end
|
24
|
+
|
25
|
+
def self.sanitize_stderr
|
26
|
+
sanitizer = Sanitizer.new($stdout)
|
27
|
+
previous_stderr = $stderr
|
28
|
+
$stderr = sanitizer
|
29
|
+
yield
|
30
|
+
ensure
|
31
|
+
sanitizer.flush
|
32
|
+
$stderr = previous_stderr
|
33
|
+
end
|
34
|
+
|
35
|
+
attr_reader :logger
|
36
|
+
|
37
|
+
def initialize
|
38
|
+
@logger = ::Logger.new($stdout)
|
39
|
+
$stdout.sync = true
|
40
|
+
end
|
41
|
+
end
|
42
|
+
end
|
@@ -0,0 +1,42 @@
|
|
1
|
+
module Imap::Backup
|
2
|
+
class Sanitizer
|
3
|
+
attr_reader :output
|
4
|
+
|
5
|
+
def initialize(output)
|
6
|
+
@output = output
|
7
|
+
@current = ""
|
8
|
+
end
|
9
|
+
|
10
|
+
def write(*args)
|
11
|
+
output.write(*args)
|
12
|
+
end
|
13
|
+
|
14
|
+
def print(*args)
|
15
|
+
@current << args.join
|
16
|
+
loop do
|
17
|
+
line, newline, rest = @current.partition("\n")
|
18
|
+
break if newline != "\n"
|
19
|
+
clean = sanitize(line)
|
20
|
+
output.puts clean
|
21
|
+
@current = rest
|
22
|
+
end
|
23
|
+
end
|
24
|
+
|
25
|
+
def flush
|
26
|
+
return if @current == ""
|
27
|
+
|
28
|
+
clean = sanitize(@current)
|
29
|
+
output.puts clean
|
30
|
+
end
|
31
|
+
|
32
|
+
private
|
33
|
+
|
34
|
+
def sanitize(t)
|
35
|
+
# Hide password in Net::IMAP debug output
|
36
|
+
t.gsub(
|
37
|
+
/\A(C: RUBY\d+ LOGIN \S+) \S+/,
|
38
|
+
"\\1 [PASSWORD REDACTED]"
|
39
|
+
)
|
40
|
+
end
|
41
|
+
end
|
42
|
+
end
|
@@ -46,7 +46,7 @@ module Imap::Backup
|
|
46
46
|
|
47
47
|
uid = uid.to_i
|
48
48
|
if uids.include?(uid)
|
49
|
-
Imap::Backup.logger.debug(
|
49
|
+
Imap::Backup::Logger.logger.debug(
|
50
50
|
"[#{folder}] message #{uid} already downloaded - skipping"
|
51
51
|
)
|
52
52
|
return
|
@@ -65,7 +65,7 @@ module Imap::Backup
|
|
65
65
|
#{body}. #{e}:
|
66
66
|
#{e.backtrace.join("\n")}"
|
67
67
|
ERROR
|
68
|
-
Imap::Backup.logger.warn message
|
68
|
+
Imap::Backup::Logger.logger.warn message
|
69
69
|
ensure
|
70
70
|
mbox&.close
|
71
71
|
end
|
@@ -0,0 +1,177 @@
|
|
1
|
+
require "imap/backup/setup/helpers"
|
2
|
+
|
3
|
+
module Imap::Backup
|
4
|
+
class Setup; end
|
5
|
+
|
6
|
+
Setup::Account = Struct.new(:config, :account, :highline) do
|
7
|
+
def initialize(config, account, highline)
|
8
|
+
super
|
9
|
+
end
|
10
|
+
|
11
|
+
def run
|
12
|
+
catch :done do
|
13
|
+
loop do
|
14
|
+
Kernel.system("clear")
|
15
|
+
create_menu
|
16
|
+
end
|
17
|
+
end
|
18
|
+
end
|
19
|
+
|
20
|
+
private
|
21
|
+
|
22
|
+
def create_menu
|
23
|
+
highline.choose do |menu|
|
24
|
+
header menu
|
25
|
+
modify_email menu
|
26
|
+
modify_password menu
|
27
|
+
modify_backup_path menu
|
28
|
+
choose_folders menu
|
29
|
+
modify_server menu
|
30
|
+
modify_connection_options menu
|
31
|
+
test_connection menu
|
32
|
+
delete_account menu
|
33
|
+
menu.choice("(q) return to main menu") { throw :done }
|
34
|
+
menu.hidden("quit") { throw :done }
|
35
|
+
end
|
36
|
+
end
|
37
|
+
|
38
|
+
def header(menu)
|
39
|
+
modified = account.modified? ? "*" : ""
|
40
|
+
connection_options =
|
41
|
+
if account.connection_options
|
42
|
+
escaped =
|
43
|
+
JSON.generate(account.connection_options).
|
44
|
+
gsub('"', '\"')
|
45
|
+
"\n connection options #{escaped}"
|
46
|
+
end
|
47
|
+
menu.header = <<~HEADER.chomp
|
48
|
+
#{helpers.title_prefix} Account#{modified}
|
49
|
+
|
50
|
+
email #{account.username}
|
51
|
+
password #{masked_password}
|
52
|
+
path #{account.local_path}
|
53
|
+
folders #{folders.map { |f| f[:name] }.join(', ')}
|
54
|
+
server #{account.server}#{connection_options}
|
55
|
+
|
56
|
+
Choose an action
|
57
|
+
HEADER
|
58
|
+
end
|
59
|
+
|
60
|
+
def modify_email(menu)
|
61
|
+
menu.choice("modify email") do
|
62
|
+
username = Setup::Asker.email(username)
|
63
|
+
Kernel.puts "username: #{username}"
|
64
|
+
other_accounts = config.accounts.reject { |a| a == account }
|
65
|
+
others = other_accounts.map { |a| a.username }
|
66
|
+
Kernel.puts "others: #{others.inspect}"
|
67
|
+
if others.include?(username)
|
68
|
+
Kernel.puts(
|
69
|
+
"There is already an account set up with that email address"
|
70
|
+
)
|
71
|
+
else
|
72
|
+
account.username = username
|
73
|
+
# rubocop:disable Style/IfUnlessModifier
|
74
|
+
default = default_server(username)
|
75
|
+
if default && (account.server.nil? || (account.server == ""))
|
76
|
+
account.server = default
|
77
|
+
end
|
78
|
+
# rubocop:enable Style/IfUnlessModifier
|
79
|
+
end
|
80
|
+
end
|
81
|
+
end
|
82
|
+
|
83
|
+
def modify_password(menu)
|
84
|
+
menu.choice("modify password") do
|
85
|
+
password = Setup::Asker.password
|
86
|
+
|
87
|
+
account.password = password if !password.nil?
|
88
|
+
end
|
89
|
+
end
|
90
|
+
|
91
|
+
def modify_server(menu)
|
92
|
+
menu.choice("modify server") do
|
93
|
+
server = highline.ask("server: ")
|
94
|
+
account.server = server if !server.nil?
|
95
|
+
end
|
96
|
+
end
|
97
|
+
|
98
|
+
def modify_connection_options(menu)
|
99
|
+
menu.choice("modify connection options") do
|
100
|
+
connection_options = highline.ask("connections options (as JSON): ")
|
101
|
+
account.connection_options = connection_options if !connection_options.nil?
|
102
|
+
end
|
103
|
+
end
|
104
|
+
|
105
|
+
def path_modification_validator(path)
|
106
|
+
same = config.accounts.find do |a|
|
107
|
+
a.username != account.username && a.local_path == path
|
108
|
+
end
|
109
|
+
if same
|
110
|
+
Kernel.puts "The path '#{path}' is used to backup " \
|
111
|
+
"the account '#{same.username}'"
|
112
|
+
false
|
113
|
+
else
|
114
|
+
true
|
115
|
+
end
|
116
|
+
end
|
117
|
+
|
118
|
+
def modify_backup_path(menu)
|
119
|
+
menu.choice("modify backup path") do
|
120
|
+
existing = account.local_path.clone
|
121
|
+
account.local_path = Setup::Asker.backup_path(
|
122
|
+
account.local_path, ->(path) { path_modification_validator(path) }
|
123
|
+
)
|
124
|
+
end
|
125
|
+
end
|
126
|
+
|
127
|
+
def choose_folders(menu)
|
128
|
+
menu.choice("choose backup folders") do
|
129
|
+
Setup::FolderChooser.new(account).run
|
130
|
+
end
|
131
|
+
end
|
132
|
+
|
133
|
+
def test_connection(menu)
|
134
|
+
menu.choice("test connection") do
|
135
|
+
result = Setup::ConnectionTester.new(account).test
|
136
|
+
Kernel.puts result
|
137
|
+
highline.ask "Press a key "
|
138
|
+
end
|
139
|
+
end
|
140
|
+
|
141
|
+
def delete_account(menu)
|
142
|
+
menu.choice("delete") do
|
143
|
+
if highline.agree("Are you sure? (y/n) ")
|
144
|
+
account.mark_for_deletion!
|
145
|
+
throw :done
|
146
|
+
end
|
147
|
+
end
|
148
|
+
end
|
149
|
+
|
150
|
+
def folders
|
151
|
+
account.folders || []
|
152
|
+
end
|
153
|
+
|
154
|
+
def masked_password
|
155
|
+
if (account.password == "") || account.password.nil?
|
156
|
+
"(unset)"
|
157
|
+
else
|
158
|
+
account.password.gsub(/./, "x")
|
159
|
+
end
|
160
|
+
end
|
161
|
+
|
162
|
+
def default_server(username)
|
163
|
+
provider = Email::Provider.for_address(username)
|
164
|
+
|
165
|
+
if provider.is_a?(Email::Provider::Unknown)
|
166
|
+
Kernel.puts "Can't decide provider for email address '#{username}'"
|
167
|
+
return nil
|
168
|
+
end
|
169
|
+
|
170
|
+
provider.host
|
171
|
+
end
|
172
|
+
|
173
|
+
def helpers
|
174
|
+
Setup::Helpers.new
|
175
|
+
end
|
176
|
+
end
|
177
|
+
end
|
@@ -1,7 +1,7 @@
|
|
1
1
|
module Imap::Backup
|
2
|
-
|
2
|
+
class Setup; end
|
3
3
|
|
4
|
-
|
4
|
+
Setup::Asker = Struct.new(:highline) do
|
5
5
|
EMAIL_MATCHER = /^[A-Z0-9._%+-]+@[A-Z0-9.-]+\.[A-Z]+$/i.freeze
|
6
6
|
|
7
7
|
def initialize(highline)
|
@@ -40,15 +40,15 @@ module Imap::Backup
|
|
40
40
|
end
|
41
41
|
|
42
42
|
def self.email(default = "")
|
43
|
-
new(
|
43
|
+
new(Setup.highline).email(default)
|
44
44
|
end
|
45
45
|
|
46
46
|
def self.password
|
47
|
-
new(
|
47
|
+
new(Setup.highline).password
|
48
48
|
end
|
49
49
|
|
50
50
|
def self.backup_path(default, validator)
|
51
|
-
new(
|
51
|
+
new(Setup.highline).backup_path(default, validator)
|
52
52
|
end
|
53
53
|
end
|
54
54
|
end
|
@@ -0,0 +1,26 @@
|
|
1
|
+
module Imap::Backup
|
2
|
+
class Setup; end
|
3
|
+
|
4
|
+
class Setup::ConnectionTester
|
5
|
+
attr_reader :account
|
6
|
+
|
7
|
+
def initialize(account)
|
8
|
+
@account = account
|
9
|
+
end
|
10
|
+
|
11
|
+
def test
|
12
|
+
connection.client
|
13
|
+
"Connection successful"
|
14
|
+
rescue Net::IMAP::NoResponseError
|
15
|
+
"No response"
|
16
|
+
rescue StandardError => e
|
17
|
+
"Unexpected error: #{e}"
|
18
|
+
end
|
19
|
+
|
20
|
+
private
|
21
|
+
|
22
|
+
def connection
|
23
|
+
Account::Connection.new(account)
|
24
|
+
end
|
25
|
+
end
|
26
|
+
end
|
@@ -1,7 +1,9 @@
|
|
1
|
+
require "imap/backup/setup/helpers"
|
2
|
+
|
1
3
|
module Imap::Backup
|
2
|
-
|
4
|
+
class Setup; end
|
3
5
|
|
4
|
-
class
|
6
|
+
class Setup::FolderChooser
|
5
7
|
attr_reader :account
|
6
8
|
|
7
9
|
def initialize(account)
|
@@ -10,13 +12,13 @@ module Imap::Backup
|
|
10
12
|
|
11
13
|
def run
|
12
14
|
if connection.nil?
|
13
|
-
Imap::Backup.logger.warn "Connection failed"
|
15
|
+
Imap::Backup::Logger.logger.warn "Connection failed"
|
14
16
|
highline.ask "Press a key "
|
15
17
|
return
|
16
18
|
end
|
17
19
|
|
18
20
|
if imap_folders.nil?
|
19
|
-
Imap::Backup.logger.warn "Unable to get folder list"
|
21
|
+
Imap::Backup::Logger.logger.warn "Unable to get folder list"
|
20
22
|
highline.ask "Press a key "
|
21
23
|
return
|
22
24
|
end
|
@@ -35,10 +37,14 @@ module Imap::Backup
|
|
35
37
|
|
36
38
|
def show_menu
|
37
39
|
highline.choose do |menu|
|
38
|
-
menu.header =
|
40
|
+
menu.header = <<~MENU.chomp
|
41
|
+
#{helpers.title_prefix} Add/remove folders
|
42
|
+
|
43
|
+
Select a folder (toggles)
|
44
|
+
MENU
|
39
45
|
menu.index = :number
|
40
46
|
add_folders menu
|
41
|
-
menu.choice("return to the account menu") { throw :done }
|
47
|
+
menu.choice("(q) return to the account menu") { throw :done }
|
42
48
|
menu.hidden("quit") { throw :done }
|
43
49
|
end
|
44
50
|
end
|
@@ -53,7 +59,7 @@ module Imap::Backup
|
|
53
59
|
end
|
54
60
|
|
55
61
|
def selected?(folder_name)
|
56
|
-
config_folders = account
|
62
|
+
config_folders = account.folders
|
57
63
|
return false if config_folders.nil?
|
58
64
|
|
59
65
|
config_folders.find { |f| f[:name] == folder_name }
|
@@ -62,7 +68,7 @@ module Imap::Backup
|
|
62
68
|
def remove_missing
|
63
69
|
removed = []
|
64
70
|
config_folders = []
|
65
|
-
account
|
71
|
+
account.folders.each do |f|
|
66
72
|
found = imap_folders.find { |folder| folder == f[:name] }
|
67
73
|
if found
|
68
74
|
config_folders << f
|
@@ -73,8 +79,7 @@ module Imap::Backup
|
|
73
79
|
|
74
80
|
return if removed.empty?
|
75
81
|
|
76
|
-
account
|
77
|
-
account[:modified] = true
|
82
|
+
account.folders = config_folders
|
78
83
|
|
79
84
|
Kernel.puts <<~MESSAGE
|
80
85
|
The following folders have been removed: #{removed.join(', ')}
|
@@ -85,12 +90,11 @@ module Imap::Backup
|
|
85
90
|
|
86
91
|
def toggle_selection(folder_name)
|
87
92
|
if selected?(folder_name)
|
88
|
-
|
89
|
-
account
|
93
|
+
new_list = account.folders.select { |f| f[:name] != folder_name }
|
94
|
+
account.folders = new_list
|
90
95
|
else
|
91
|
-
account
|
92
|
-
account
|
93
|
-
account[:modified] = true
|
96
|
+
existing = account.folders || []
|
97
|
+
account.folders = existing + [{name: folder_name}]
|
94
98
|
end
|
95
99
|
end
|
96
100
|
|
@@ -101,11 +105,15 @@ module Imap::Backup
|
|
101
105
|
end
|
102
106
|
|
103
107
|
def imap_folders
|
104
|
-
@imap_folders ||= connection.
|
108
|
+
@imap_folders ||= connection.folder_names
|
105
109
|
end
|
106
110
|
|
107
111
|
def highline
|
108
|
-
|
112
|
+
Setup.highline
|
113
|
+
end
|
114
|
+
|
115
|
+
def helpers
|
116
|
+
Setup::Helpers.new
|
109
117
|
end
|
110
118
|
end
|
111
119
|
end
|