imap-backup 6.0.0.rc2 → 6.0.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/imap-backup.gemspec +5 -1
- data/lib/cli_coverage.rb +11 -11
- data/lib/email/provider/base.rb +2 -0
- data/lib/email/provider/unknown.rb +2 -0
- data/lib/email/provider.rb +2 -0
- data/lib/imap/backup/account/connection/backup_folders.rb +27 -0
- data/lib/imap/backup/account/connection/client_factory.rb +54 -0
- data/lib/imap/backup/account/connection/folder_names.rb +26 -0
- data/lib/imap/backup/account/connection.rb +11 -95
- data/lib/imap/backup/account/folder.rb +9 -6
- data/lib/imap/backup/account.rb +3 -5
- data/lib/imap/backup/cli/backup.rb +1 -3
- data/lib/imap/backup/cli/helpers.rb +24 -22
- data/lib/imap/backup/cli/local.rb +20 -13
- data/lib/imap/backup/cli/migrate.rb +4 -10
- data/lib/imap/backup/cli/restore.rb +8 -7
- data/lib/imap/backup/cli/setup.rb +10 -8
- data/lib/imap/backup/cli/stats.rb +78 -0
- data/lib/imap/backup/cli/status.rb +2 -2
- data/lib/imap/backup/cli/utils.rb +4 -6
- data/lib/imap/backup/cli.rb +24 -3
- data/lib/imap/backup/configuration.rb +9 -11
- data/lib/imap/backup/downloader.rb +48 -30
- data/lib/imap/backup/migrator.rb +5 -5
- data/lib/imap/backup/sanitizer.rb +3 -2
- data/lib/imap/backup/serializer/appender.rb +49 -0
- data/lib/imap/backup/serializer/message_enumerator.rb +29 -0
- data/lib/imap/backup/serializer/unused_name_finder.rb +27 -0
- data/lib/imap/backup/serializer.rb +24 -78
- data/lib/imap/backup/setup/account/header.rb +75 -0
- data/lib/imap/backup/setup/account.rb +14 -91
- data/lib/imap/backup/setup/asker.rb +4 -15
- data/lib/imap/backup/setup/backup_path.rb +41 -0
- data/lib/imap/backup/setup/email.rb +45 -0
- data/lib/imap/backup/setup/folder_chooser.rb +3 -3
- data/lib/imap/backup/setup/helpers.rb +1 -1
- data/lib/imap/backup/setup.rb +5 -4
- data/lib/imap/backup/thunderbird/mailbox_exporter.rb +39 -20
- data/lib/imap/backup/uploader.rb +46 -8
- data/lib/imap/backup/utils.rb +1 -1
- data/lib/imap/backup/version.rb +1 -1
- data/lib/imap/backup.rb +0 -1
- metadata +31 -134
- data/spec/features/backup_spec.rb +0 -100
- data/spec/features/configuration/minimal_configuration.rb +0 -15
- data/spec/features/configuration/missing_configuration.rb +0 -14
- data/spec/features/folders_spec.rb +0 -36
- data/spec/features/helper.rb +0 -2
- data/spec/features/local/list_accounts_spec.rb +0 -12
- data/spec/features/local/list_emails_spec.rb +0 -21
- data/spec/features/local/list_folders_spec.rb +0 -21
- data/spec/features/local/show_an_email_spec.rb +0 -34
- data/spec/features/migrate_spec.rb +0 -35
- data/spec/features/remote/list_account_folders_spec.rb +0 -16
- data/spec/features/restore_spec.rb +0 -162
- data/spec/features/status_spec.rb +0 -43
- data/spec/features/support/aruba.rb +0 -78
- data/spec/features/support/backup_directory.rb +0 -43
- data/spec/features/support/email_server.rb +0 -110
- data/spec/features/support/shared/connection_context.rb +0 -14
- data/spec/features/support/shared/message_fixtures.rb +0 -16
- data/spec/fixtures/connection.yml +0 -7
- data/spec/spec_helper.rb +0 -15
- data/spec/support/fixtures.rb +0 -11
- data/spec/support/higline_test_helpers.rb +0 -8
- data/spec/support/silence_logging.rb +0 -7
- data/spec/unit/email/mboxrd/message_spec.rb +0 -177
- data/spec/unit/email/provider/apple_mail_spec.rb +0 -7
- data/spec/unit/email/provider/base_spec.rb +0 -11
- data/spec/unit/email/provider/fastmail_spec.rb +0 -7
- data/spec/unit/email/provider/gmail_spec.rb +0 -7
- data/spec/unit/email/provider_spec.rb +0 -27
- data/spec/unit/imap/backup/account/connection_spec.rb +0 -433
- data/spec/unit/imap/backup/account/folder_spec.rb +0 -261
- data/spec/unit/imap/backup/account_spec.rb +0 -246
- data/spec/unit/imap/backup/cli/accounts_spec.rb +0 -58
- data/spec/unit/imap/backup/cli/backup_spec.rb +0 -19
- data/spec/unit/imap/backup/cli/folders_spec.rb +0 -39
- data/spec/unit/imap/backup/cli/helpers_spec.rb +0 -87
- data/spec/unit/imap/backup/cli/local_spec.rb +0 -100
- data/spec/unit/imap/backup/cli/migrate_spec.rb +0 -80
- data/spec/unit/imap/backup/cli/restore_spec.rb +0 -67
- data/spec/unit/imap/backup/cli/setup_spec.rb +0 -17
- data/spec/unit/imap/backup/cli/utils_spec.rb +0 -125
- data/spec/unit/imap/backup/cli_spec.rb +0 -93
- data/spec/unit/imap/backup/client/apple_mail_spec.rb +0 -9
- data/spec/unit/imap/backup/client/default_spec.rb +0 -22
- data/spec/unit/imap/backup/configuration_spec.rb +0 -238
- data/spec/unit/imap/backup/downloader_spec.rb +0 -96
- data/spec/unit/imap/backup/logger_spec.rb +0 -48
- data/spec/unit/imap/backup/migrator_spec.rb +0 -58
- data/spec/unit/imap/backup/sanitizer_spec.rb +0 -42
- data/spec/unit/imap/backup/serializer/directory_spec.rb +0 -37
- data/spec/unit/imap/backup/serializer/imap_spec.rb +0 -218
- data/spec/unit/imap/backup/serializer/mbox_enumerator_spec.rb +0 -45
- data/spec/unit/imap/backup/serializer/mbox_spec.rb +0 -101
- data/spec/unit/imap/backup/serializer_spec.rb +0 -296
- data/spec/unit/imap/backup/setup/account_spec.rb +0 -461
- data/spec/unit/imap/backup/setup/asker_spec.rb +0 -137
- data/spec/unit/imap/backup/setup/connection_tester_spec.rb +0 -51
- data/spec/unit/imap/backup/setup/folder_chooser_spec.rb +0 -146
- data/spec/unit/imap/backup/setup/helpers_spec.rb +0 -15
- data/spec/unit/imap/backup/setup_spec.rb +0 -301
- data/spec/unit/imap/backup/thunderbird/mailbox_exporter_spec.rb +0 -116
- data/spec/unit/imap/backup/uploader_spec.rb +0 -54
- data/spec/unit/imap/backup/utils_spec.rb +0 -92
- data/spec/unit/retry_on_error_spec.rb +0 -34
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: 00576d7b3a0482f2e2ede92e545a5bbebcdf5ab2a688feb8e0c54281a1d9ba9a
|
|
4
|
+
data.tar.gz: e8bc14ff755f817cb08f4b979b6c689d4ee87c1b9d96acca4d0dc2a8297a40a7
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: 155476a2d92177685b58802d1431a15975161fcee512f43ee79cb332078faba8e0247f81020eb4c92c20fdb308ec6bb1650136cf1d92f468e4ece68142717e67
|
|
7
|
+
data.tar.gz: ddd269797169fdbc255ef907bb30a9c66775709ad32bc10f2a3d6ec75da441625269db307ed3bfa8e06dd3f076ff313e6124d47be73654c1f2b2cc8202e1f55a
|
data/imap-backup.gemspec
CHANGED
|
@@ -18,7 +18,6 @@ Gem::Specification.new do |gem|
|
|
|
18
18
|
gem.files += %w[LICENSE README.md]
|
|
19
19
|
|
|
20
20
|
gem.executables = gem.files.grep(%r{^bin/}).map { |f| File.basename(f) }
|
|
21
|
-
gem.test_files = Dir.glob("spec/**/*{.rb,.yml}")
|
|
22
21
|
gem.require_paths = ["lib"]
|
|
23
22
|
gem.required_ruby_version = ">= 2.5"
|
|
24
23
|
|
|
@@ -34,4 +33,9 @@ Gem::Specification.new do |gem|
|
|
|
34
33
|
gem.add_development_dependency "rspec", ">= 3.0.0"
|
|
35
34
|
gem.add_development_dependency "rubocop-rspec"
|
|
36
35
|
gem.add_development_dependency "simplecov"
|
|
36
|
+
gem.add_development_dependency "yard"
|
|
37
|
+
|
|
38
|
+
gem.metadata = {
|
|
39
|
+
"rubygems_mfa_required" => "true"
|
|
40
|
+
}
|
|
37
41
|
end
|
data/lib/cli_coverage.rb
CHANGED
|
@@ -1,18 +1,18 @@
|
|
|
1
1
|
class CliCoverage
|
|
2
2
|
def self.conditionally_activate
|
|
3
|
-
if ENV["COVERAGE"]
|
|
4
|
-
require "simplecov"
|
|
3
|
+
return if !ENV["COVERAGE"]
|
|
5
4
|
|
|
6
|
-
|
|
7
|
-
SimpleCov.command_name "#{ENV['COVERAGE']} #{ARGV.join(' ')} coverage"
|
|
5
|
+
require "simplecov"
|
|
8
6
|
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
SimpleCov.print_error_status = false
|
|
7
|
+
# Collect coverage separately
|
|
8
|
+
SimpleCov.command_name "#{ENV['COVERAGE']} #{ARGV.join(' ')} coverage"
|
|
12
9
|
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
10
|
+
# Silence output
|
|
11
|
+
SimpleCov.formatter = SimpleCov::Formatter::SimpleFormatter
|
|
12
|
+
SimpleCov.print_error_status = false
|
|
13
|
+
|
|
14
|
+
# Ensure SimpleCov doesn't filter out all out code
|
|
15
|
+
project_root = File.expand_path("..", __dir__)
|
|
16
|
+
SimpleCov.root project_root
|
|
17
17
|
end
|
|
18
18
|
end
|
data/lib/email/provider/base.rb
CHANGED
data/lib/email/provider.rb
CHANGED
|
@@ -7,6 +7,7 @@ module Email; end
|
|
|
7
7
|
|
|
8
8
|
class Email::Provider
|
|
9
9
|
def self.for_address(address)
|
|
10
|
+
# rubocop:disable Lint/DuplicateBranch
|
|
10
11
|
case
|
|
11
12
|
when address.end_with?("@fastmail.com")
|
|
12
13
|
Email::Provider::Fastmail.new
|
|
@@ -23,5 +24,6 @@ class Email::Provider
|
|
|
23
24
|
else
|
|
24
25
|
Email::Provider::Unknown.new
|
|
25
26
|
end
|
|
27
|
+
# rubocop:enable Lint/DuplicateBranch
|
|
26
28
|
end
|
|
27
29
|
end
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
module Imap::Backup
|
|
2
|
+
class Account; end
|
|
3
|
+
class Account::Connection; end
|
|
4
|
+
|
|
5
|
+
class Account::Connection::BackupFolders
|
|
6
|
+
attr_reader :account
|
|
7
|
+
attr_reader :client
|
|
8
|
+
|
|
9
|
+
def initialize(client:, account:)
|
|
10
|
+
@client = client
|
|
11
|
+
@account = account
|
|
12
|
+
end
|
|
13
|
+
|
|
14
|
+
def run
|
|
15
|
+
names =
|
|
16
|
+
if account.folders&.any?
|
|
17
|
+
account.folders.map { |af| af[:name] }
|
|
18
|
+
else
|
|
19
|
+
Account::Connection::FolderNames.new(client: client, account: account).run
|
|
20
|
+
end
|
|
21
|
+
|
|
22
|
+
names.map do |name|
|
|
23
|
+
Account::Folder.new(account.connection, name)
|
|
24
|
+
end
|
|
25
|
+
end
|
|
26
|
+
end
|
|
27
|
+
end
|
|
@@ -0,0 +1,54 @@
|
|
|
1
|
+
require "retry_on_error"
|
|
2
|
+
|
|
3
|
+
module Imap::Backup
|
|
4
|
+
class Account::Connection::ClientFactory
|
|
5
|
+
include RetryOnError
|
|
6
|
+
|
|
7
|
+
LOGIN_RETRY_CLASSES = [EOFError, Errno::ECONNRESET, SocketError].freeze
|
|
8
|
+
|
|
9
|
+
attr_reader :account
|
|
10
|
+
|
|
11
|
+
def initialize(account:)
|
|
12
|
+
@account = account
|
|
13
|
+
@provider = nil
|
|
14
|
+
@server = nil
|
|
15
|
+
end
|
|
16
|
+
|
|
17
|
+
def run
|
|
18
|
+
retry_on_error(errors: LOGIN_RETRY_CLASSES) do
|
|
19
|
+
options = provider_options
|
|
20
|
+
Logger.logger.debug(
|
|
21
|
+
"Creating IMAP instance: #{server}, options: #{options.inspect}"
|
|
22
|
+
)
|
|
23
|
+
client =
|
|
24
|
+
if provider.is_a?(Email::Provider::AppleMail)
|
|
25
|
+
Client::AppleMail.new(server, options)
|
|
26
|
+
else
|
|
27
|
+
Client::Default.new(server, options)
|
|
28
|
+
end
|
|
29
|
+
Logger.logger.debug "Logging in: #{account.username}/#{masked_password}"
|
|
30
|
+
client.login(account.username, account.password)
|
|
31
|
+
Logger.logger.debug "Login complete"
|
|
32
|
+
client
|
|
33
|
+
end
|
|
34
|
+
end
|
|
35
|
+
|
|
36
|
+
private
|
|
37
|
+
|
|
38
|
+
def masked_password
|
|
39
|
+
account.password.gsub(/./, "x")
|
|
40
|
+
end
|
|
41
|
+
|
|
42
|
+
def provider
|
|
43
|
+
@provider ||= Email::Provider.for_address(account.username)
|
|
44
|
+
end
|
|
45
|
+
|
|
46
|
+
def provider_options
|
|
47
|
+
provider.options.merge(account.connection_options || {})
|
|
48
|
+
end
|
|
49
|
+
|
|
50
|
+
def server
|
|
51
|
+
@server ||= account.server || provider.host
|
|
52
|
+
end
|
|
53
|
+
end
|
|
54
|
+
end
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
module Imap::Backup
|
|
2
|
+
class Account; end
|
|
3
|
+
class Account::Connection; end
|
|
4
|
+
|
|
5
|
+
class Account::Connection::FolderNames
|
|
6
|
+
attr_reader :account
|
|
7
|
+
attr_reader :client
|
|
8
|
+
|
|
9
|
+
def initialize(client:, account:)
|
|
10
|
+
@client = client
|
|
11
|
+
@account = account
|
|
12
|
+
end
|
|
13
|
+
|
|
14
|
+
def run
|
|
15
|
+
folder_names = client.list
|
|
16
|
+
|
|
17
|
+
if folder_names.empty?
|
|
18
|
+
message = "Unable to get folder list for account #{account.username}"
|
|
19
|
+
Logger.logger.info message
|
|
20
|
+
raise message
|
|
21
|
+
end
|
|
22
|
+
|
|
23
|
+
folder_names
|
|
24
|
+
end
|
|
25
|
+
end
|
|
26
|
+
end
|
|
@@ -1,17 +1,15 @@
|
|
|
1
|
+
require "email/provider"
|
|
1
2
|
require "imap/backup/client/apple_mail"
|
|
2
3
|
require "imap/backup/client/default"
|
|
4
|
+
require "imap/backup/account/connection/backup_folders"
|
|
5
|
+
require "imap/backup/account/connection/client_factory"
|
|
6
|
+
require "imap/backup/account/connection/folder_names"
|
|
3
7
|
require "imap/backup/serializer/directory"
|
|
4
8
|
|
|
5
|
-
require "retry_on_error"
|
|
6
|
-
|
|
7
9
|
module Imap::Backup
|
|
8
10
|
class Account; end
|
|
9
11
|
|
|
10
12
|
class Account::Connection
|
|
11
|
-
include RetryOnError
|
|
12
|
-
|
|
13
|
-
LOGIN_RETRY_CLASSES = [EOFError, Errno::ECONNRESET, SocketError].freeze
|
|
14
|
-
|
|
15
13
|
attr_reader :account
|
|
16
14
|
|
|
17
15
|
def initialize(account)
|
|
@@ -20,34 +18,12 @@ module Imap::Backup
|
|
|
20
18
|
end
|
|
21
19
|
|
|
22
20
|
def folder_names
|
|
23
|
-
@folder_names ||=
|
|
24
|
-
begin
|
|
25
|
-
folder_names = client.list
|
|
26
|
-
|
|
27
|
-
if folder_names.empty?
|
|
28
|
-
message = "Unable to get folder list for account #{account.username}"
|
|
29
|
-
Imap::Backup::Logger.logger.info message
|
|
30
|
-
raise message
|
|
31
|
-
end
|
|
32
|
-
|
|
33
|
-
folder_names
|
|
34
|
-
end
|
|
21
|
+
@folder_names ||= Account::Connection::FolderNames.new(client: client, account: account).run
|
|
35
22
|
end
|
|
36
23
|
|
|
37
24
|
def backup_folders
|
|
38
25
|
@backup_folders ||=
|
|
39
|
-
|
|
40
|
-
names =
|
|
41
|
-
if account.folders&.any?
|
|
42
|
-
account.folders.map { |af| af[:name] }
|
|
43
|
-
else
|
|
44
|
-
folder_names
|
|
45
|
-
end
|
|
46
|
-
|
|
47
|
-
names.map do |name|
|
|
48
|
-
Account::Folder.new(self, name)
|
|
49
|
-
end
|
|
50
|
-
end
|
|
26
|
+
Account::Connection::BackupFolders.new(client: client, account: account).run
|
|
51
27
|
end
|
|
52
28
|
|
|
53
29
|
def status
|
|
@@ -59,14 +35,14 @@ module Imap::Backup
|
|
|
59
35
|
end
|
|
60
36
|
|
|
61
37
|
def run_backup
|
|
62
|
-
|
|
38
|
+
Logger.logger.debug "Running backup of account: #{account.username}"
|
|
63
39
|
# start the connection so we get logging messages in the right order
|
|
64
40
|
client
|
|
65
41
|
ensure_account_folder
|
|
66
42
|
each_folder do |folder, serializer|
|
|
67
43
|
next if !folder.exist?
|
|
68
44
|
|
|
69
|
-
|
|
45
|
+
Logger.logger.debug "[#{folder.name}] running backup"
|
|
70
46
|
serializer.apply_uid_validity(folder.uid_validity)
|
|
71
47
|
begin
|
|
72
48
|
Downloader.new(
|
|
@@ -95,7 +71,7 @@ module Imap::Backup
|
|
|
95
71
|
|
|
96
72
|
def restore
|
|
97
73
|
local_folders do |serializer, folder|
|
|
98
|
-
|
|
74
|
+
Uploader.new(folder, serializer).run
|
|
99
75
|
end
|
|
100
76
|
end
|
|
101
77
|
|
|
@@ -112,32 +88,11 @@ module Imap::Backup
|
|
|
112
88
|
@backup_folders = nil
|
|
113
89
|
@client = nil
|
|
114
90
|
@folder_names = nil
|
|
115
|
-
@provider = nil
|
|
116
|
-
@server = nil
|
|
117
91
|
end
|
|
118
92
|
|
|
93
|
+
# TODO: make this private
|
|
119
94
|
def client
|
|
120
|
-
@client ||=
|
|
121
|
-
retry_on_error(errors: LOGIN_RETRY_CLASSES) do
|
|
122
|
-
options = provider_options
|
|
123
|
-
Imap::Backup::Logger.logger.debug(
|
|
124
|
-
"Creating IMAP instance: #{server}, options: #{options.inspect}"
|
|
125
|
-
)
|
|
126
|
-
client =
|
|
127
|
-
if provider.is_a?(Email::Provider::AppleMail)
|
|
128
|
-
Client::AppleMail.new(server, options)
|
|
129
|
-
else
|
|
130
|
-
Client::Default.new(server, options)
|
|
131
|
-
end
|
|
132
|
-
Imap::Backup::Logger.logger.debug "Logging in: #{account.username}/#{masked_password}"
|
|
133
|
-
client.login(account.username, account.password)
|
|
134
|
-
Imap::Backup::Logger.logger.debug "Login complete"
|
|
135
|
-
client
|
|
136
|
-
end
|
|
137
|
-
end
|
|
138
|
-
|
|
139
|
-
def server
|
|
140
|
-
@server ||= account.server || provider.host
|
|
95
|
+
@client ||= Account::Connection::ClientFactory.new(account: account).run
|
|
141
96
|
end
|
|
142
97
|
|
|
143
98
|
private
|
|
@@ -149,33 +104,6 @@ module Imap::Backup
|
|
|
149
104
|
end
|
|
150
105
|
end
|
|
151
106
|
|
|
152
|
-
def restore_folder(serializer, folder)
|
|
153
|
-
existing_uids = folder.uids
|
|
154
|
-
if existing_uids.any?
|
|
155
|
-
Imap::Backup::Logger.logger.debug(
|
|
156
|
-
"There's already a '#{folder.name}' folder with emails"
|
|
157
|
-
)
|
|
158
|
-
new_name = serializer.apply_uid_validity(folder.uid_validity)
|
|
159
|
-
old_name = serializer.folder
|
|
160
|
-
if new_name
|
|
161
|
-
Imap::Backup::Logger.logger.debug(
|
|
162
|
-
"Backup '#{old_name}' renamed and restored to '#{new_name}'"
|
|
163
|
-
)
|
|
164
|
-
new_serializer = Serializer.new(account.local_path, new_name)
|
|
165
|
-
new_folder = Account::Folder.new(self, new_name)
|
|
166
|
-
new_folder.create
|
|
167
|
-
new_serializer.force_uid_validity(new_folder.uid_validity)
|
|
168
|
-
Uploader.new(new_folder, new_serializer).run
|
|
169
|
-
else
|
|
170
|
-
Uploader.new(folder, serializer).run
|
|
171
|
-
end
|
|
172
|
-
else
|
|
173
|
-
folder.create
|
|
174
|
-
serializer.force_uid_validity(folder.uid_validity)
|
|
175
|
-
Uploader.new(folder, serializer).run
|
|
176
|
-
end
|
|
177
|
-
end
|
|
178
|
-
|
|
179
107
|
def ensure_account_folder
|
|
180
108
|
Utils.make_folder(
|
|
181
109
|
File.dirname(account.local_path),
|
|
@@ -183,17 +111,5 @@ module Imap::Backup
|
|
|
183
111
|
Serializer::Directory::DIRECTORY_PERMISSIONS
|
|
184
112
|
)
|
|
185
113
|
end
|
|
186
|
-
|
|
187
|
-
def masked_password
|
|
188
|
-
account.password.gsub(/./, "x")
|
|
189
|
-
end
|
|
190
|
-
|
|
191
|
-
def provider
|
|
192
|
-
@provider ||= Email::Provider.for_address(account.username)
|
|
193
|
-
end
|
|
194
|
-
|
|
195
|
-
def provider_options
|
|
196
|
-
provider.options.merge(account.connection_options || {})
|
|
197
|
-
end
|
|
198
114
|
end
|
|
199
115
|
end
|
|
@@ -12,7 +12,8 @@ module Imap::Backup
|
|
|
12
12
|
include RetryOnError
|
|
13
13
|
|
|
14
14
|
BODY_ATTRIBUTE = "BODY[]".freeze
|
|
15
|
-
UID_FETCH_RETRY_CLASSES = [EOFError].freeze
|
|
15
|
+
UID_FETCH_RETRY_CLASSES = [EOFError, Errno::ECONNRESET, IOError].freeze
|
|
16
|
+
APPEND_RETRY_CLASSES = [Net::IMAP::BadResponseError].freeze
|
|
16
17
|
|
|
17
18
|
attr_reader :connection
|
|
18
19
|
attr_reader :name
|
|
@@ -64,7 +65,7 @@ module Imap::Backup
|
|
|
64
65
|
in `search_internal` in stdlib net/imap.rb.
|
|
65
66
|
This is caused by `@responses["SEARCH"] being unset/undefined
|
|
66
67
|
MESSAGE
|
|
67
|
-
|
|
68
|
+
Logger.logger.warn message
|
|
68
69
|
[]
|
|
69
70
|
end
|
|
70
71
|
|
|
@@ -91,8 +92,10 @@ module Imap::Backup
|
|
|
91
92
|
def append(message)
|
|
92
93
|
body = message.imap_body
|
|
93
94
|
date = message.date&.to_time
|
|
94
|
-
|
|
95
|
-
|
|
95
|
+
retry_on_error(errors: APPEND_RETRY_CLASSES, limit: 3) do
|
|
96
|
+
response = client.append(utf7_encoded_name, body, nil, date)
|
|
97
|
+
extract_uid(response)
|
|
98
|
+
end
|
|
96
99
|
end
|
|
97
100
|
|
|
98
101
|
def clear
|
|
@@ -108,12 +111,12 @@ module Imap::Backup
|
|
|
108
111
|
def examine
|
|
109
112
|
client.examine(utf7_encoded_name)
|
|
110
113
|
rescue Net::IMAP::NoResponseError
|
|
111
|
-
|
|
114
|
+
Logger.logger.warn "Folder '#{name}' does not exist on server"
|
|
112
115
|
raise FolderNotFound, "Folder '#{name}' does not exist on server"
|
|
113
116
|
end
|
|
114
117
|
|
|
115
118
|
def extract_uid(response)
|
|
116
|
-
@uid_validity, uid = response.data.code.data.split
|
|
119
|
+
@uid_validity, uid = response.data.code.data.split.map(&:to_i)
|
|
117
120
|
uid
|
|
118
121
|
end
|
|
119
122
|
|
data/lib/imap/backup/account.rb
CHANGED
|
@@ -47,10 +47,7 @@ module Imap::Backup
|
|
|
47
47
|
end
|
|
48
48
|
|
|
49
49
|
def to_h
|
|
50
|
-
h = {
|
|
51
|
-
username: @username,
|
|
52
|
-
password: @password,
|
|
53
|
-
}
|
|
50
|
+
h = {username: @username, password: @password}
|
|
54
51
|
h[:local_path] = @local_path if @local_path
|
|
55
52
|
h[:folders] = @folders if @folders
|
|
56
53
|
h[:server] = @server if @server
|
|
@@ -73,6 +70,7 @@ module Imap::Backup
|
|
|
73
70
|
|
|
74
71
|
def folders=(value)
|
|
75
72
|
raise "folders must be an Array" if !value.is_a?(Array)
|
|
73
|
+
|
|
76
74
|
update(:folders, value)
|
|
77
75
|
end
|
|
78
76
|
|
|
@@ -113,7 +111,7 @@ module Imap::Backup
|
|
|
113
111
|
end
|
|
114
112
|
else
|
|
115
113
|
current = instance_variable_get(key)
|
|
116
|
-
changes[field] = {from: current, to: value}
|
|
114
|
+
changes[field] = {from: current, to: value} if value != current
|
|
117
115
|
end
|
|
118
116
|
|
|
119
117
|
instance_variable_set(key, value)
|
|
@@ -1,35 +1,37 @@
|
|
|
1
1
|
require "imap/backup"
|
|
2
2
|
require "imap/backup/cli/accounts"
|
|
3
3
|
|
|
4
|
-
module Imap::Backup
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
4
|
+
module Imap::Backup
|
|
5
|
+
module CLI::Helpers
|
|
6
|
+
def symbolized(options)
|
|
7
|
+
options.each.with_object({}) do |(k, v), acc|
|
|
8
|
+
key = k.gsub("-", "_").intern
|
|
9
|
+
acc[key] = v
|
|
10
|
+
end
|
|
9
11
|
end
|
|
10
|
-
end
|
|
11
12
|
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
13
|
+
def account(email)
|
|
14
|
+
accounts = CLI::Accounts.new
|
|
15
|
+
account = accounts.find { |a| a.username == email }
|
|
16
|
+
raise "#{email} is not a configured account" if !account
|
|
16
17
|
|
|
17
|
-
|
|
18
|
-
|
|
18
|
+
account
|
|
19
|
+
end
|
|
19
20
|
|
|
20
|
-
|
|
21
|
-
|
|
21
|
+
def connection(email)
|
|
22
|
+
account = account(email)
|
|
22
23
|
|
|
23
|
-
|
|
24
|
-
|
|
24
|
+
Account::Connection.new(account)
|
|
25
|
+
end
|
|
25
26
|
|
|
26
|
-
|
|
27
|
-
|
|
27
|
+
def each_connection(names)
|
|
28
|
+
accounts = CLI::Accounts.new(names)
|
|
28
29
|
|
|
29
|
-
|
|
30
|
-
|
|
30
|
+
accounts.each do |account|
|
|
31
|
+
yield account.connection
|
|
32
|
+
end
|
|
33
|
+
rescue ConfigurationNotFound
|
|
34
|
+
raise "imap-backup is not configured. Run `imap-backup setup`"
|
|
31
35
|
end
|
|
32
|
-
rescue Imap::Backup::ConfigurationNotFound
|
|
33
|
-
raise "imap-backup is not configured. Run `imap-backup setup`"
|
|
34
36
|
end
|
|
35
37
|
end
|
|
@@ -5,6 +5,8 @@ module Imap::Backup
|
|
|
5
5
|
include Thor::Actions
|
|
6
6
|
include CLI::Helpers
|
|
7
7
|
|
|
8
|
+
MAX_SUBJECT = 60
|
|
9
|
+
|
|
8
10
|
desc "accounts", "List locally backed-up accounts"
|
|
9
11
|
def accounts
|
|
10
12
|
accounts = CLI::Accounts.new
|
|
@@ -29,26 +31,16 @@ module Imap::Backup
|
|
|
29
31
|
end
|
|
30
32
|
raise "Folder '#{folder_name}' not found" if !folder_serializer
|
|
31
33
|
|
|
32
|
-
max_subject = 60
|
|
33
34
|
Kernel.puts format(
|
|
34
|
-
"%-10<uid>s %-#{
|
|
35
|
+
"%-10<uid>s %-#{MAX_SUBJECT}<subject>s - %<date>s",
|
|
35
36
|
{uid: "UID", subject: "Subject", date: "Date"}
|
|
36
37
|
)
|
|
37
|
-
Kernel.puts "-" * (12 +
|
|
38
|
+
Kernel.puts "-" * (12 + MAX_SUBJECT + 28)
|
|
38
39
|
|
|
39
40
|
uids = folder_serializer.uids
|
|
40
41
|
|
|
41
42
|
folder_serializer.each_message(uids).map do |uid, message|
|
|
42
|
-
|
|
43
|
-
uid: uid,
|
|
44
|
-
date: message.date.to_s,
|
|
45
|
-
subject: message.subject || ""
|
|
46
|
-
}
|
|
47
|
-
if m[:subject].length > max_subject
|
|
48
|
-
Kernel.puts format("% 10<uid>u: %.#{max_subject - 3}<subject>s... - %<date>s", m)
|
|
49
|
-
else
|
|
50
|
-
Kernel.puts format("% 10<uid>u: %-#{max_subject}<subject>s - %<date>s", m)
|
|
51
|
-
end
|
|
43
|
+
list_message uid, message
|
|
52
44
|
end
|
|
53
45
|
end
|
|
54
46
|
|
|
@@ -78,5 +70,20 @@ module Imap::Backup
|
|
|
78
70
|
Kernel.puts message.supplied_body
|
|
79
71
|
end
|
|
80
72
|
end
|
|
73
|
+
|
|
74
|
+
no_commands do
|
|
75
|
+
def list_message(uid, message)
|
|
76
|
+
m = {
|
|
77
|
+
uid: uid,
|
|
78
|
+
date: message.date.to_s,
|
|
79
|
+
subject: message.subject || ""
|
|
80
|
+
}
|
|
81
|
+
if m[:subject].length > MAX_SUBJECT
|
|
82
|
+
Kernel.puts format("% 10<uid>u: %.#{MAX_SUBJECT - 3}<subject>s... - %<date>s", m)
|
|
83
|
+
else
|
|
84
|
+
Kernel.puts format("% 10<uid>u: %-#{MAX_SUBJECT}<subject>s - %<date>s", m)
|
|
85
|
+
end
|
|
86
|
+
end
|
|
87
|
+
end
|
|
81
88
|
end
|
|
82
89
|
end
|
|
@@ -38,13 +38,9 @@ module Imap::Backup
|
|
|
38
38
|
raise "Source and destination accounts cannot be the same!"
|
|
39
39
|
end
|
|
40
40
|
|
|
41
|
-
if !destination_account
|
|
42
|
-
raise "Account '#{destination_email}' does not exist"
|
|
43
|
-
end
|
|
41
|
+
raise "Account '#{destination_email}' does not exist" if !destination_account
|
|
44
42
|
|
|
45
|
-
if !source_account
|
|
46
|
-
raise "Account '#{source_email}' does not exist"
|
|
47
|
-
end
|
|
43
|
+
raise "Account '#{source_email}' does not exist" if !source_account
|
|
48
44
|
end
|
|
49
45
|
|
|
50
46
|
def config
|
|
@@ -69,16 +65,14 @@ module Imap::Backup
|
|
|
69
65
|
|
|
70
66
|
def folder_for(source_folder)
|
|
71
67
|
no_source_prefix =
|
|
72
|
-
if source_prefix != "" &&
|
|
73
|
-
source_folder.start_with?(source_prefix)
|
|
68
|
+
if source_prefix != "" && source_folder.start_with?(source_prefix)
|
|
74
69
|
source_folder.delete_prefix(source_prefix)
|
|
75
70
|
else
|
|
76
71
|
source_folder.to_s
|
|
77
72
|
end
|
|
78
73
|
|
|
79
74
|
with_destination_prefix =
|
|
80
|
-
if destination_prefix &&
|
|
81
|
-
destination_prefix != ""
|
|
75
|
+
if destination_prefix && destination_prefix != ""
|
|
82
76
|
destination_prefix + no_source_prefix
|
|
83
77
|
else
|
|
84
78
|
no_source_prefix
|
|
@@ -7,11 +7,9 @@ module Imap::Backup
|
|
|
7
7
|
attr_reader :account_names
|
|
8
8
|
|
|
9
9
|
def initialize(email = nil, options)
|
|
10
|
+
super([])
|
|
10
11
|
@email = email
|
|
11
|
-
@account_names =
|
|
12
|
-
if options.key?(:accounts)
|
|
13
|
-
options[:accounts].split(",")
|
|
14
|
-
end
|
|
12
|
+
@account_names = options[:accounts].split(",") if options.key?(:accounts)
|
|
15
13
|
end
|
|
16
14
|
|
|
17
15
|
no_commands do
|
|
@@ -22,12 +20,15 @@ module Imap::Backup
|
|
|
22
20
|
connection.restore
|
|
23
21
|
when !email && !account_names
|
|
24
22
|
Logger.logger.info "Calling restore without an EMAIL parameter is deprecated"
|
|
25
|
-
each_connection([]
|
|
23
|
+
each_connection([], &:restore)
|
|
26
24
|
when email && account_names.any?
|
|
27
25
|
raise "Pass either an email or the --accounts option, not both"
|
|
28
26
|
when account_names.any?
|
|
29
|
-
Logger.logger.info
|
|
30
|
-
|
|
27
|
+
Logger.logger.info(
|
|
28
|
+
"Calling restore with the --account option is deprected, " \
|
|
29
|
+
"please pass a single EMAIL argument"
|
|
30
|
+
)
|
|
31
|
+
each_connection(account_names, &:restore)
|
|
31
32
|
end
|
|
32
33
|
end
|
|
33
34
|
end
|
|
@@ -1,13 +1,15 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
1
|
+
module Imap::Backup
|
|
2
|
+
class CLI::Setup < Thor
|
|
3
|
+
include Thor::Actions
|
|
3
4
|
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
5
|
+
def initialize
|
|
6
|
+
super([])
|
|
7
|
+
end
|
|
7
8
|
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
9
|
+
no_commands do
|
|
10
|
+
def run
|
|
11
|
+
Setup.new.run
|
|
12
|
+
end
|
|
11
13
|
end
|
|
12
14
|
end
|
|
13
15
|
end
|