imap-backup 5.2.0 → 6.0.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/README.md +9 -2
- data/docs/development.md +10 -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 +17 -105
- data/lib/imap/backup/account/folder.rb +9 -6
- data/lib/imap/backup/account.rb +36 -16
- data/lib/imap/backup/cli/backup.rb +1 -3
- data/lib/imap/backup/cli/folders.rb +3 -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 +5 -11
- 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 +6 -8
- data/lib/imap/backup/cli.rb +24 -3
- data/lib/imap/backup/configuration.rb +9 -21
- data/lib/imap/backup/downloader.rb +56 -34
- 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/directory.rb +37 -0
- data/lib/imap/backup/serializer/imap.rb +144 -0
- data/lib/imap/backup/serializer/mbox.rb +33 -88
- data/lib/imap/backup/serializer/mbox_enumerator.rb +2 -0
- 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 +160 -3
- data/lib/imap/backup/setup/account/header.rb +75 -0
- data/lib/imap/backup/setup/account.rb +41 -95
- 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 +2 -2
- data/lib/imap/backup/setup.rb +5 -4
- data/lib/imap/backup/thunderbird/mailbox_exporter.rb +41 -22
- data/lib/imap/backup/uploader.rb +46 -8
- data/lib/imap/backup/utils.rb +1 -1
- data/lib/imap/backup/version.rb +3 -3
- data/lib/imap/backup.rb +0 -2
- metadata +31 -105
- data/lib/imap/backup/serializer/mbox_store.rb +0 -217
- data/spec/features/backup_spec.rb +0 -108
- 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 -77
- 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 -405
- data/spec/unit/imap/backup/account/folder_spec.rb +0 -251
- data/spec/unit/imap/backup/cli/accounts_spec.rb +0 -47
- data/spec/unit/imap/backup/cli/helpers_spec.rb +0 -87
- data/spec/unit/imap/backup/cli/local_spec.rb +0 -81
- data/spec/unit/imap/backup/cli/utils_spec.rb +0 -62
- 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 -44
- 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/serializer/mbox_enumerator_spec.rb +0 -45
- data/spec/unit/imap/backup/serializer/mbox_spec.rb +0 -222
- data/spec/unit/imap/backup/serializer/mbox_store_spec.rb +0 -329
- data/spec/unit/imap/backup/setup/account_spec.rb +0 -366
- 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_spec.rb +0 -301
- data/spec/unit/imap/backup/uploader_spec.rb +0 -54
- data/spec/unit/imap/backup/utils_spec.rb +0 -92
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: 6d4b9386097e77af34706992fb42b6ff9edd3a62a3cfb98e0b3363d864ee75dc
|
|
4
|
+
data.tar.gz: d72ec8493ea2436ccb033f2957d44236b71d3c607e25b15b0b755e93f011da10
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: ab38ba548fa009c6f718f8da9b9fbb971751297eac03d2839e14f247c06d6858a155350291619090ef8165ffeb865674a19ef51c13dbf1220928106abfc79fa5
|
|
7
|
+
data.tar.gz: ad4e17a2b6b0b5a9ee1cc4737c324981069525a129018278514e51e8664008c71fc610539df58a2da590084bb7e3c46cd635fe1409ba0b53ad56bd241edfb9c8
|
data/README.md
CHANGED
|
@@ -1,3 +1,8 @@
|
|
|
1
|
+

|
|
2
|
+
[][CI Status]
|
|
3
|
+

|
|
4
|
+

|
|
5
|
+
|
|
1
6
|
# imap-backup
|
|
2
7
|
|
|
3
8
|
*Backup GMail (or other IMAP) accounts to disk*
|
|
@@ -5,10 +10,12 @@
|
|
|
5
10
|
* [Source Code]
|
|
6
11
|
* [API documentation]
|
|
7
12
|
* [Rubygem]
|
|
13
|
+
* [CI Status]
|
|
8
14
|
|
|
9
15
|
[Source Code]: https://github.com/joeyates/imap-backup "Source code at GitHub"
|
|
10
|
-
[API documentation]:
|
|
11
|
-
[Rubygem]:
|
|
16
|
+
[API documentation]: https://rubydoc.info/gems/imap-backup/frames "RDoc API Documentation at Rubydoc.info"
|
|
17
|
+
[Rubygem]: https://rubygems.org/gems/imap-backup "Ruby gem at rubygems.org"
|
|
18
|
+
[CI Status]: https://github.com/joeyates/imap-backup/actions/workflows/main.yml
|
|
12
19
|
|
|
13
20
|
# Installation
|
|
14
21
|
|
data/docs/development.md
CHANGED
|
@@ -1,10 +1,10 @@
|
|
|
1
1
|
# Testing
|
|
2
2
|
|
|
3
|
-
##
|
|
3
|
+
## Feature Specs
|
|
4
4
|
|
|
5
|
-
|
|
6
|
-
controlled by Docker Compose
|
|
7
|
-
|
|
5
|
+
Specs under `specs/features` are integration specs run against a local IMAP server
|
|
6
|
+
controlled by Docker Compose.
|
|
7
|
+
Before running the test suite, it needs to be started:
|
|
8
8
|
|
|
9
9
|
```sh
|
|
10
10
|
$ docker-compose up -d
|
|
@@ -26,6 +26,12 @@ or
|
|
|
26
26
|
$ rspec --tag ~docker
|
|
27
27
|
```
|
|
28
28
|
|
|
29
|
+
### Debugging
|
|
30
|
+
|
|
31
|
+
The feature specs are run 'out of process' via the Aruba gem.
|
|
32
|
+
In order to see debugging output from the process,
|
|
33
|
+
use `last_command_started.output`.
|
|
34
|
+
|
|
29
35
|
## Access Docker imap server
|
|
30
36
|
|
|
31
37
|
```ruby
|
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']} 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,16 +1,15 @@
|
|
|
1
|
+
require "email/provider"
|
|
1
2
|
require "imap/backup/client/apple_mail"
|
|
2
3
|
require "imap/backup/client/default"
|
|
3
|
-
|
|
4
|
-
require "
|
|
4
|
+
require "imap/backup/account/connection/backup_folders"
|
|
5
|
+
require "imap/backup/account/connection/client_factory"
|
|
6
|
+
require "imap/backup/account/connection/folder_names"
|
|
7
|
+
require "imap/backup/serializer/directory"
|
|
5
8
|
|
|
6
9
|
module Imap::Backup
|
|
7
10
|
class Account; end
|
|
8
11
|
|
|
9
12
|
class Account::Connection
|
|
10
|
-
include RetryOnError
|
|
11
|
-
|
|
12
|
-
LOGIN_RETRY_CLASSES = [EOFError, Errno::ECONNRESET, SocketError].freeze
|
|
13
|
-
|
|
14
13
|
attr_reader :account
|
|
15
14
|
|
|
16
15
|
def initialize(account)
|
|
@@ -19,57 +18,35 @@ module Imap::Backup
|
|
|
19
18
|
end
|
|
20
19
|
|
|
21
20
|
def folder_names
|
|
22
|
-
@folder_names ||=
|
|
23
|
-
begin
|
|
24
|
-
folder_names = client.list
|
|
25
|
-
|
|
26
|
-
if folder_names.empty?
|
|
27
|
-
message = "Unable to get folder list for account #{account.username}"
|
|
28
|
-
Imap::Backup::Logger.logger.info message
|
|
29
|
-
raise message
|
|
30
|
-
end
|
|
31
|
-
|
|
32
|
-
folder_names
|
|
33
|
-
end
|
|
21
|
+
@folder_names ||= Account::Connection::FolderNames.new(client: client, account: account).run
|
|
34
22
|
end
|
|
35
23
|
|
|
36
24
|
def backup_folders
|
|
37
25
|
@backup_folders ||=
|
|
38
|
-
|
|
39
|
-
names =
|
|
40
|
-
if account.folders&.any?
|
|
41
|
-
account.folders.map { |af| af[:name] }
|
|
42
|
-
else
|
|
43
|
-
folder_names
|
|
44
|
-
end
|
|
45
|
-
|
|
46
|
-
names.map do |name|
|
|
47
|
-
Account::Folder.new(self, name)
|
|
48
|
-
end
|
|
49
|
-
end
|
|
26
|
+
Account::Connection::BackupFolders.new(client: client, account: account).run
|
|
50
27
|
end
|
|
51
28
|
|
|
52
29
|
def status
|
|
53
30
|
ensure_account_folder
|
|
54
31
|
backup_folders.map do |folder|
|
|
55
|
-
s = Serializer
|
|
32
|
+
s = Serializer.new(account.local_path, folder.name)
|
|
56
33
|
{name: folder.name, local: s.uids, remote: folder.uids}
|
|
57
34
|
end
|
|
58
35
|
end
|
|
59
36
|
|
|
60
37
|
def run_backup
|
|
61
|
-
|
|
38
|
+
Logger.logger.debug "Running backup of account: #{account.username}"
|
|
62
39
|
# start the connection so we get logging messages in the right order
|
|
63
40
|
client
|
|
64
41
|
ensure_account_folder
|
|
65
42
|
each_folder do |folder, serializer|
|
|
66
43
|
next if !folder.exist?
|
|
67
44
|
|
|
68
|
-
|
|
45
|
+
Logger.logger.debug "[#{folder.name}] running backup"
|
|
69
46
|
serializer.apply_uid_validity(folder.uid_validity)
|
|
70
47
|
begin
|
|
71
48
|
Downloader.new(
|
|
72
|
-
folder, serializer,
|
|
49
|
+
folder, serializer, multi_fetch_size: account.multi_fetch_size
|
|
73
50
|
).run
|
|
74
51
|
rescue Net::IMAP::ByeResponseError
|
|
75
52
|
reconnect
|
|
@@ -86,7 +63,7 @@ module Imap::Backup
|
|
|
86
63
|
base = Pathname.new(account.local_path)
|
|
87
64
|
Pathname.glob(glob) do |path|
|
|
88
65
|
name = path.relative_path_from(base).to_s[0..-6]
|
|
89
|
-
serializer = Serializer
|
|
66
|
+
serializer = Serializer.new(account.local_path, name)
|
|
90
67
|
folder = Account::Folder.new(self, name)
|
|
91
68
|
yield serializer, folder
|
|
92
69
|
end
|
|
@@ -94,7 +71,7 @@ module Imap::Backup
|
|
|
94
71
|
|
|
95
72
|
def restore
|
|
96
73
|
local_folders do |serializer, folder|
|
|
97
|
-
|
|
74
|
+
Uploader.new(folder, serializer).run
|
|
98
75
|
end
|
|
99
76
|
end
|
|
100
77
|
|
|
@@ -110,94 +87,29 @@ module Imap::Backup
|
|
|
110
87
|
def reset
|
|
111
88
|
@backup_folders = nil
|
|
112
89
|
@client = nil
|
|
113
|
-
@config = 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
|
|
144
99
|
|
|
145
100
|
def each_folder
|
|
146
101
|
backup_folders.each do |folder|
|
|
147
|
-
serializer = Serializer
|
|
102
|
+
serializer = Serializer.new(account.local_path, folder.name)
|
|
148
103
|
yield folder, serializer
|
|
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::Mbox.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),
|
|
182
110
|
File.basename(account.local_path),
|
|
183
|
-
Serializer::DIRECTORY_PERMISSIONS
|
|
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
|
-
|
|
199
|
-
def config
|
|
200
|
-
@config ||= Configuration.new
|
|
201
|
-
end
|
|
202
114
|
end
|
|
203
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
|
@@ -1,5 +1,7 @@
|
|
|
1
1
|
module Imap::Backup
|
|
2
2
|
class Account
|
|
3
|
+
DEFAULT_MULTI_FETCH_SIZE = 1
|
|
4
|
+
|
|
3
5
|
attr_reader :username
|
|
4
6
|
attr_reader :password
|
|
5
7
|
attr_reader :local_path
|
|
@@ -7,7 +9,6 @@ module Imap::Backup
|
|
|
7
9
|
attr_reader :server
|
|
8
10
|
attr_reader :connection_options
|
|
9
11
|
attr_reader :changes
|
|
10
|
-
attr_reader :marked_for_deletion
|
|
11
12
|
|
|
12
13
|
def initialize(options)
|
|
13
14
|
@username = options[:username]
|
|
@@ -16,27 +17,29 @@ module Imap::Backup
|
|
|
16
17
|
@folders = options[:folders]
|
|
17
18
|
@server = options[:server]
|
|
18
19
|
@connection_options = options[:connection_options]
|
|
20
|
+
@multi_fetch_size = options[:multi_fetch_size]
|
|
21
|
+
@connection = nil
|
|
19
22
|
@changes = {}
|
|
20
23
|
@marked_for_deletion = false
|
|
21
24
|
end
|
|
22
25
|
|
|
23
26
|
def connection
|
|
24
|
-
Account::Connection.new(self)
|
|
27
|
+
@connection ||= Account::Connection.new(self)
|
|
25
28
|
end
|
|
26
29
|
|
|
27
30
|
def valid?
|
|
28
|
-
username && password
|
|
31
|
+
username && password ? true : false
|
|
29
32
|
end
|
|
30
33
|
|
|
31
34
|
def modified?
|
|
32
35
|
changes.any?
|
|
33
36
|
end
|
|
34
37
|
|
|
35
|
-
def clear_changes
|
|
38
|
+
def clear_changes
|
|
36
39
|
@changes = {}
|
|
37
40
|
end
|
|
38
41
|
|
|
39
|
-
def mark_for_deletion
|
|
42
|
+
def mark_for_deletion
|
|
40
43
|
@marked_for_deletion = true
|
|
41
44
|
end
|
|
42
45
|
|
|
@@ -45,14 +48,12 @@ module Imap::Backup
|
|
|
45
48
|
end
|
|
46
49
|
|
|
47
50
|
def to_h
|
|
48
|
-
h = {
|
|
49
|
-
username: @username,
|
|
50
|
-
password: @password,
|
|
51
|
-
}
|
|
51
|
+
h = {username: @username, password: @password}
|
|
52
52
|
h[:local_path] = @local_path if @local_path
|
|
53
53
|
h[:folders] = @folders if @folders
|
|
54
54
|
h[:server] = @server if @server
|
|
55
55
|
h[:connection_options] = @connection_options if @connection_options
|
|
56
|
+
h[:multi_fetch_size] = multi_fetch_size if @multi_fetch_size
|
|
56
57
|
h
|
|
57
58
|
end
|
|
58
59
|
|
|
@@ -70,6 +71,7 @@ module Imap::Backup
|
|
|
70
71
|
|
|
71
72
|
def folders=(value)
|
|
72
73
|
raise "folders must be an Array" if !value.is_a?(Array)
|
|
74
|
+
|
|
73
75
|
update(:folders, value)
|
|
74
76
|
end
|
|
75
77
|
|
|
@@ -82,20 +84,38 @@ module Imap::Backup
|
|
|
82
84
|
update(:connection_options, parsed)
|
|
83
85
|
end
|
|
84
86
|
|
|
87
|
+
def multi_fetch_size
|
|
88
|
+
int = @multi_fetch_size.to_i
|
|
89
|
+
if int.positive?
|
|
90
|
+
int
|
|
91
|
+
else
|
|
92
|
+
DEFAULT_MULTI_FETCH_SIZE
|
|
93
|
+
end
|
|
94
|
+
end
|
|
95
|
+
|
|
96
|
+
def multi_fetch_size=(value)
|
|
97
|
+
parsed = value.to_i
|
|
98
|
+
parsed = DEFAULT_MULTI_FETCH_SIZE if !parsed.positive?
|
|
99
|
+
update(:multi_fetch_size, parsed)
|
|
100
|
+
end
|
|
101
|
+
|
|
85
102
|
private
|
|
86
103
|
|
|
87
104
|
def update(field, value)
|
|
105
|
+
key = :"@#{field}"
|
|
88
106
|
if changes[field]
|
|
89
107
|
change = changes[field]
|
|
90
|
-
|
|
108
|
+
if change[:from] == value
|
|
109
|
+
changes.delete(field)
|
|
110
|
+
else
|
|
111
|
+
change[:to] = value
|
|
112
|
+
end
|
|
113
|
+
else
|
|
114
|
+
current = instance_variable_get(key)
|
|
115
|
+
changes[field] = {from: current, to: value} if value != current
|
|
91
116
|
end
|
|
92
|
-
set_field!(field, value)
|
|
93
|
-
end
|
|
94
117
|
|
|
95
|
-
|
|
96
|
-
key = :"@#{field}"
|
|
97
|
-
current = instance_variable_get(key)
|
|
98
|
-
changes[field] = {from: current, to: value}
|
|
118
|
+
@connection = nil
|
|
99
119
|
instance_variable_set(key, value)
|
|
100
120
|
end
|
|
101
121
|
end
|
|
@@ -13,15 +13,15 @@ module Imap::Backup
|
|
|
13
13
|
no_commands do
|
|
14
14
|
def run
|
|
15
15
|
each_connection(account_names) do |connection|
|
|
16
|
-
puts connection.account.username
|
|
16
|
+
Kernel.puts connection.account.username
|
|
17
17
|
# TODO: Make folder_names private once this command
|
|
18
18
|
# has been removed.
|
|
19
19
|
folders = connection.folder_names
|
|
20
20
|
if folders.nil?
|
|
21
|
-
warn "Unable to list account folders"
|
|
21
|
+
Kernel.warn "Unable to list account folders"
|
|
22
22
|
return false
|
|
23
23
|
end
|
|
24
|
-
folders.each { |f| puts "\t#{f}" }
|
|
24
|
+
folders.each { |f| Kernel.puts "\t#{f}" }
|
|
25
25
|
end
|
|
26
26
|
end
|
|
27
27
|
end
|