imap-backup 6.0.0.rc2 → 6.1.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/apple_mail.rb +4 -0
- data/lib/email/provider/base.rb +6 -0
- data/lib/email/provider/purelymail.rb +11 -0
- data/lib/email/provider/unknown.rb +2 -0
- data/lib/email/provider.rb +5 -0
- data/lib/imap/backup/account/connection/backup_folders.rb +27 -0
- data/lib/imap/backup/account/connection/client_factory.rb +55 -0
- data/lib/imap/backup/account/connection/folder_names.rb +26 -0
- data/lib/imap/backup/account/connection.rb +16 -96
- data/lib/imap/backup/account/folder.rb +31 -9
- data/lib/imap/backup/account.rb +15 -6
- 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 +75 -31
- 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/imap.rb +27 -3
- data/lib/imap/backup/serializer/mbox.rb +18 -2
- data/lib/imap/backup/serializer/message_enumerator.rb +29 -0
- data/lib/imap/backup/serializer/unused_name_finder.rb +25 -0
- data/lib/imap/backup/serializer.rb +64 -84
- data/lib/imap/backup/setup/account/header.rb +81 -0
- data/lib/imap/backup/setup/account.rb +28 -91
- data/lib/imap/backup/setup/asker.rb +4 -15
- data/lib/imap/backup/setup/backup_path.rb +45 -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 +7 -6
- 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 +2 -2
- data/lib/imap/backup.rb +0 -1
- metadata +32 -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: 2a05fedc57275cadd2a1bd22efbe88ef5ec6ee72843400499cac8b8df8e80c35
|
|
4
|
+
data.tar.gz: f0e8e859b5fb1bb8480730b3ea62abc7470f5d0ddb469113bbdac0e4f2a88d1a
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: bfb4de1a3048a9f29ee30fb56c7493524a7d688a6eab2ec3307005ba3c8d341a99c961acd1d8f588384dc91571b4528482931e52724df42dff19a26553568fb0
|
|
7
|
+
data.tar.gz: a411ad26b4ce3eb631c97d94c9242c1eb48fd57d23cffcec84f5c829d9354b506115b6c8278ee38430e27f0d6ca1400d55f74adce5eb99f94db2963f235c6d7a
|
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
|
@@ -3,6 +3,12 @@ class Email::Provider; end
|
|
|
3
3
|
|
|
4
4
|
class Email::Provider::Base
|
|
5
5
|
def options
|
|
6
|
+
# rubocop:disable Naming/VariableNumber
|
|
6
7
|
{port: 993, ssl: {ssl_version: :TLSv1_2}}
|
|
8
|
+
# rubocop:enable Naming/VariableNumber
|
|
9
|
+
end
|
|
10
|
+
|
|
11
|
+
def sets_seen_flags_on_fetch?
|
|
12
|
+
false
|
|
7
13
|
end
|
|
8
14
|
end
|
data/lib/email/provider.rb
CHANGED
|
@@ -1,12 +1,14 @@
|
|
|
1
1
|
require "email/provider/apple_mail"
|
|
2
2
|
require "email/provider/fastmail"
|
|
3
3
|
require "email/provider/gmail"
|
|
4
|
+
require "email/provider/purelymail"
|
|
4
5
|
require "email/provider/unknown"
|
|
5
6
|
|
|
6
7
|
module Email; end
|
|
7
8
|
|
|
8
9
|
class Email::Provider
|
|
9
10
|
def self.for_address(address)
|
|
11
|
+
# rubocop:disable Lint/DuplicateBranch
|
|
10
12
|
case
|
|
11
13
|
when address.end_with?("@fastmail.com")
|
|
12
14
|
Email::Provider::Fastmail.new
|
|
@@ -20,8 +22,11 @@ class Email::Provider
|
|
|
20
22
|
Email::Provider::AppleMail.new
|
|
21
23
|
when address.end_with?("@me.com")
|
|
22
24
|
Email::Provider::AppleMail.new
|
|
25
|
+
when address.end_with?("@purelymail.com")
|
|
26
|
+
Email::Provider::Purelymail.new
|
|
23
27
|
else
|
|
24
28
|
Email::Provider::Unknown.new
|
|
25
29
|
end
|
|
30
|
+
# rubocop:enable Lint/DuplicateBranch
|
|
26
31
|
end
|
|
27
32
|
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,55 @@
|
|
|
1
|
+
require "email/provider"
|
|
2
|
+
require "retry_on_error"
|
|
3
|
+
|
|
4
|
+
module Imap::Backup
|
|
5
|
+
class Account::Connection::ClientFactory
|
|
6
|
+
include RetryOnError
|
|
7
|
+
|
|
8
|
+
LOGIN_RETRY_CLASSES = [EOFError, Errno::ECONNRESET, SocketError].freeze
|
|
9
|
+
|
|
10
|
+
attr_reader :account
|
|
11
|
+
|
|
12
|
+
def initialize(account:)
|
|
13
|
+
@account = account
|
|
14
|
+
@provider = nil
|
|
15
|
+
@server = nil
|
|
16
|
+
end
|
|
17
|
+
|
|
18
|
+
def run
|
|
19
|
+
retry_on_error(errors: LOGIN_RETRY_CLASSES) do
|
|
20
|
+
options = provider_options
|
|
21
|
+
Logger.logger.debug(
|
|
22
|
+
"Creating IMAP instance: #{server}, options: #{options.inspect}"
|
|
23
|
+
)
|
|
24
|
+
client =
|
|
25
|
+
if provider.is_a?(Email::Provider::AppleMail)
|
|
26
|
+
Client::AppleMail.new(server, options)
|
|
27
|
+
else
|
|
28
|
+
Client::Default.new(server, options)
|
|
29
|
+
end
|
|
30
|
+
Logger.logger.debug "Logging in: #{account.username}/#{masked_password}"
|
|
31
|
+
client.login(account.username, account.password)
|
|
32
|
+
Logger.logger.debug "Login complete"
|
|
33
|
+
client
|
|
34
|
+
end
|
|
35
|
+
end
|
|
36
|
+
|
|
37
|
+
private
|
|
38
|
+
|
|
39
|
+
def masked_password
|
|
40
|
+
account.password.gsub(/./, "x")
|
|
41
|
+
end
|
|
42
|
+
|
|
43
|
+
def provider
|
|
44
|
+
@provider ||= Email::Provider.for_address(account.username)
|
|
45
|
+
end
|
|
46
|
+
|
|
47
|
+
def provider_options
|
|
48
|
+
provider.options.merge(account.connection_options || {})
|
|
49
|
+
end
|
|
50
|
+
|
|
51
|
+
def server
|
|
52
|
+
@server ||= account.server || provider.host
|
|
53
|
+
end
|
|
54
|
+
end
|
|
55
|
+
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,14 @@
|
|
|
1
1
|
require "imap/backup/client/apple_mail"
|
|
2
2
|
require "imap/backup/client/default"
|
|
3
|
+
require "imap/backup/account/connection/backup_folders"
|
|
4
|
+
require "imap/backup/account/connection/client_factory"
|
|
5
|
+
require "imap/backup/account/connection/folder_names"
|
|
3
6
|
require "imap/backup/serializer/directory"
|
|
4
7
|
|
|
5
|
-
require "retry_on_error"
|
|
6
|
-
|
|
7
8
|
module Imap::Backup
|
|
8
9
|
class Account; end
|
|
9
10
|
|
|
10
11
|
class Account::Connection
|
|
11
|
-
include RetryOnError
|
|
12
|
-
|
|
13
|
-
LOGIN_RETRY_CLASSES = [EOFError, Errno::ECONNRESET, SocketError].freeze
|
|
14
|
-
|
|
15
12
|
attr_reader :account
|
|
16
13
|
|
|
17
14
|
def initialize(account)
|
|
@@ -20,34 +17,12 @@ module Imap::Backup
|
|
|
20
17
|
end
|
|
21
18
|
|
|
22
19
|
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
|
|
20
|
+
@folder_names ||= Account::Connection::FolderNames.new(client: client, account: account).run
|
|
35
21
|
end
|
|
36
22
|
|
|
37
23
|
def backup_folders
|
|
38
24
|
@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
|
|
25
|
+
Account::Connection::BackupFolders.new(client: client, account: account).run
|
|
51
26
|
end
|
|
52
27
|
|
|
53
28
|
def status
|
|
@@ -59,18 +34,21 @@ module Imap::Backup
|
|
|
59
34
|
end
|
|
60
35
|
|
|
61
36
|
def run_backup
|
|
62
|
-
|
|
37
|
+
Logger.logger.debug "Running backup of account: #{account.username}"
|
|
63
38
|
# start the connection so we get logging messages in the right order
|
|
64
39
|
client
|
|
65
40
|
ensure_account_folder
|
|
66
41
|
each_folder do |folder, serializer|
|
|
67
42
|
next if !folder.exist?
|
|
68
43
|
|
|
69
|
-
|
|
44
|
+
Logger.logger.debug "[#{folder.name}] running backup"
|
|
70
45
|
serializer.apply_uid_validity(folder.uid_validity)
|
|
71
46
|
begin
|
|
72
47
|
Downloader.new(
|
|
73
|
-
folder,
|
|
48
|
+
folder,
|
|
49
|
+
serializer,
|
|
50
|
+
multi_fetch_size: account.multi_fetch_size,
|
|
51
|
+
reset_seen_flags_after_fetch: account.reset_seen_flags_after_fetch
|
|
74
52
|
).run
|
|
75
53
|
rescue Net::IMAP::ByeResponseError
|
|
76
54
|
reconnect
|
|
@@ -95,7 +73,7 @@ module Imap::Backup
|
|
|
95
73
|
|
|
96
74
|
def restore
|
|
97
75
|
local_folders do |serializer, folder|
|
|
98
|
-
|
|
76
|
+
Uploader.new(folder, serializer).run
|
|
99
77
|
end
|
|
100
78
|
end
|
|
101
79
|
|
|
@@ -112,32 +90,11 @@ module Imap::Backup
|
|
|
112
90
|
@backup_folders = nil
|
|
113
91
|
@client = nil
|
|
114
92
|
@folder_names = nil
|
|
115
|
-
@provider = nil
|
|
116
|
-
@server = nil
|
|
117
93
|
end
|
|
118
94
|
|
|
95
|
+
# TODO: make this private
|
|
119
96
|
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
|
|
97
|
+
@client ||= Account::Connection::ClientFactory.new(account: account).run
|
|
141
98
|
end
|
|
142
99
|
|
|
143
100
|
private
|
|
@@ -149,51 +106,14 @@ module Imap::Backup
|
|
|
149
106
|
end
|
|
150
107
|
end
|
|
151
108
|
|
|
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
109
|
def ensure_account_folder
|
|
110
|
+
raise "The backup path for #{account.username} is not set" if !account.local_path
|
|
111
|
+
|
|
180
112
|
Utils.make_folder(
|
|
181
113
|
File.dirname(account.local_path),
|
|
182
114
|
File.basename(account.local_path),
|
|
183
115
|
Serializer::Directory::DIRECTORY_PERMISSIONS
|
|
184
116
|
)
|
|
185
117
|
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
118
|
end
|
|
199
119
|
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,29 +92,50 @@ 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
|
-
def
|
|
99
|
-
existing = uids
|
|
101
|
+
def set_flags(uids, flags)
|
|
100
102
|
# Use read-write access, via `select`
|
|
101
103
|
client.select(utf7_encoded_name)
|
|
102
|
-
client.uid_store(
|
|
104
|
+
client.uid_store(uids, "+FLAGS", flags)
|
|
105
|
+
end
|
|
106
|
+
|
|
107
|
+
def unset_flags(uids, flags)
|
|
108
|
+
client.select(utf7_encoded_name)
|
|
109
|
+
client.uid_store(uids, "-FLAGS", flags)
|
|
110
|
+
end
|
|
111
|
+
|
|
112
|
+
def clear
|
|
113
|
+
set_flags(uids, [:Deleted])
|
|
103
114
|
client.expunge
|
|
104
115
|
end
|
|
105
116
|
|
|
117
|
+
def unseen(uids)
|
|
118
|
+
messages = uids.map(&:to_s).join(",")
|
|
119
|
+
examine
|
|
120
|
+
client.uid_search([messages, "UNSEEN"])
|
|
121
|
+
rescue NoMethodError
|
|
122
|
+
# Apple Mail returns an empty response when searches have no results
|
|
123
|
+
[]
|
|
124
|
+
rescue FolderNotFound
|
|
125
|
+
nil
|
|
126
|
+
end
|
|
127
|
+
|
|
106
128
|
private
|
|
107
129
|
|
|
108
130
|
def examine
|
|
109
131
|
client.examine(utf7_encoded_name)
|
|
110
132
|
rescue Net::IMAP::NoResponseError
|
|
111
|
-
|
|
133
|
+
Logger.logger.warn "Folder '#{name}' does not exist on server"
|
|
112
134
|
raise FolderNotFound, "Folder '#{name}' does not exist on server"
|
|
113
135
|
end
|
|
114
136
|
|
|
115
137
|
def extract_uid(response)
|
|
116
|
-
@uid_validity, uid = response.data.code.data.split
|
|
138
|
+
@uid_validity, uid = response.data.code.data.split.map(&:to_i)
|
|
117
139
|
uid
|
|
118
140
|
end
|
|
119
141
|
|
data/lib/imap/backup/account.rb
CHANGED
|
@@ -8,6 +8,7 @@ module Imap::Backup
|
|
|
8
8
|
attr_reader :folders
|
|
9
9
|
attr_reader :server
|
|
10
10
|
attr_reader :connection_options
|
|
11
|
+
attr_reader :reset_seen_flags_after_fetch
|
|
11
12
|
attr_reader :changes
|
|
12
13
|
|
|
13
14
|
def initialize(options)
|
|
@@ -18,12 +19,14 @@ module Imap::Backup
|
|
|
18
19
|
@server = options[:server]
|
|
19
20
|
@connection_options = options[:connection_options]
|
|
20
21
|
@multi_fetch_size = options[:multi_fetch_size]
|
|
22
|
+
@reset_seen_flags_after_fetch = options[:reset_seen_flags_after_fetch]
|
|
23
|
+
@connection = nil
|
|
21
24
|
@changes = {}
|
|
22
25
|
@marked_for_deletion = false
|
|
23
26
|
end
|
|
24
27
|
|
|
25
28
|
def connection
|
|
26
|
-
Account::Connection.new(self)
|
|
29
|
+
@connection ||= Account::Connection.new(self)
|
|
27
30
|
end
|
|
28
31
|
|
|
29
32
|
def valid?
|
|
@@ -47,15 +50,15 @@ module Imap::Backup
|
|
|
47
50
|
end
|
|
48
51
|
|
|
49
52
|
def to_h
|
|
50
|
-
h = {
|
|
51
|
-
username: @username,
|
|
52
|
-
password: @password,
|
|
53
|
-
}
|
|
53
|
+
h = {username: @username, password: @password}
|
|
54
54
|
h[:local_path] = @local_path if @local_path
|
|
55
55
|
h[:folders] = @folders if @folders
|
|
56
56
|
h[:server] = @server if @server
|
|
57
57
|
h[:connection_options] = @connection_options if @connection_options
|
|
58
58
|
h[:multi_fetch_size] = multi_fetch_size if @multi_fetch_size
|
|
59
|
+
if @reset_seen_flags_after_fetch
|
|
60
|
+
h[:reset_seen_flags_after_fetch] = @reset_seen_flags_after_fetch
|
|
61
|
+
end
|
|
59
62
|
h
|
|
60
63
|
end
|
|
61
64
|
|
|
@@ -73,6 +76,7 @@ module Imap::Backup
|
|
|
73
76
|
|
|
74
77
|
def folders=(value)
|
|
75
78
|
raise "folders must be an Array" if !value.is_a?(Array)
|
|
79
|
+
|
|
76
80
|
update(:folders, value)
|
|
77
81
|
end
|
|
78
82
|
|
|
@@ -100,6 +104,10 @@ module Imap::Backup
|
|
|
100
104
|
update(:multi_fetch_size, parsed)
|
|
101
105
|
end
|
|
102
106
|
|
|
107
|
+
def reset_seen_flags_after_fetch=(value)
|
|
108
|
+
update(:reset_seen_flags_after_fetch, value)
|
|
109
|
+
end
|
|
110
|
+
|
|
103
111
|
private
|
|
104
112
|
|
|
105
113
|
def update(field, value)
|
|
@@ -113,9 +121,10 @@ module Imap::Backup
|
|
|
113
121
|
end
|
|
114
122
|
else
|
|
115
123
|
current = instance_variable_get(key)
|
|
116
|
-
changes[field] = {from: current, to: value}
|
|
124
|
+
changes[field] = {from: current, to: value} if value != current
|
|
117
125
|
end
|
|
118
126
|
|
|
127
|
+
@connection = nil
|
|
119
128
|
instance_variable_set(key, value)
|
|
120
129
|
end
|
|
121
130
|
end
|
|
@@ -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
|