imap-backup 12.0.0 → 13.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/README.md +14 -14
- data/docs/development.md +19 -19
- data/imap-backup.gemspec +1 -8
- data/lib/cli_coverage.rb +2 -2
- data/lib/imap/backup/account/folder.rb +0 -1
- data/lib/imap/backup/cli/folder_enumerator.rb +3 -3
- data/lib/imap/backup/cli/transfer.rb +163 -0
- data/lib/imap/backup/cli.rb +38 -10
- data/lib/imap/backup/serializer/folder_maker.rb +1 -1
- data/lib/imap/backup/serializer/imap.rb +1 -1
- data/lib/imap/backup/serializer/mbox.rb +1 -1
- data/lib/imap/backup/serializer.rb +1 -1
- data/lib/imap/backup/thunderbird/mailbox_exporter.rb +1 -1
- data/lib/imap/backup/version.rb +1 -1
- data/lib/retry_on_error.rb +2 -1
- metadata +4 -89
- data/lib/imap/backup/cli/migrate.rb +0 -87
- data/lib/imap/backup/cli/mirror.rb +0 -97
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: ff649779fd21a8a5ab2216b21618da22930c171dbe49fd2cf3aada02e3f2f18b
|
4
|
+
data.tar.gz: 7e0ac7d099b71a9404d183cbae3b43b0100ab81c80e69a0485447f7c392b3317
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 8846edccdd5e391d8740533a0a8f1b74b023ed181765e8ff8da4bf7782066b7186449ddf96a765d0a92ced194bf7e5b5f37c30ec2f1ddcab157e1a4b493d53e7
|
7
|
+
data.tar.gz: 064fd1b42bfabfddf9abfe740a21d9f6f5247eb7d58605becaf25a01f2657ea623b37063ca4567715daff684231995e7bd9fde2e5ed72fe68b5e124f3c165787
|
data/README.md
CHANGED
@@ -113,7 +113,7 @@ These are activated via two settings:
|
|
113
113
|
As with all performance tweaks, there are trade-offs.
|
114
114
|
If you are using a small virtual server or Raspberry Pi
|
115
115
|
to run your backups, you will probably want to leave
|
116
|
-
the
|
116
|
+
the default settings.
|
117
117
|
If, on the other hand, you are using a computer with a
|
118
118
|
fair bit of RAM, and you are dealing with a *lot* of email,
|
119
119
|
then changing these settings may be worthwhile.
|
@@ -122,16 +122,17 @@ then changing these settings may be worthwhile.
|
|
122
122
|
|
123
123
|
This setting affects all account backups.
|
124
124
|
|
125
|
-
|
126
|
-
|
127
|
-
|
128
|
-
from scratch.
|
125
|
+
By default, `imap-backup` uses the "delay metadata" strategy.
|
126
|
+
As messages are being backed-up, the message *text*
|
127
|
+
is written to disk, while the related metadata is stored in memory.
|
129
128
|
|
130
|
-
|
131
|
-
|
129
|
+
While this uses a little more memory, it avoids rewiting a growing JSON
|
130
|
+
file for every message, speeding things up and reducing disk wear.
|
132
131
|
|
133
|
-
|
134
|
-
|
132
|
+
The alternative strategy, called "direct", writes everything to disk
|
133
|
+
as it is received. This method is slower, but has the advantage
|
134
|
+
of using slightly less memory, which may be important on very
|
135
|
+
resource-limited systems, like Raspberry Pis.
|
135
136
|
|
136
137
|
## Multi-fetch Size
|
137
138
|
|
@@ -140,12 +141,11 @@ By default, during backup, each message is downloaded one-by-one.
|
|
140
141
|
Using this setting, you can download chunks of emails at a time,
|
141
142
|
potentially speeding up the process.
|
142
143
|
|
143
|
-
|
144
|
-
|
145
|
-
|
146
|
-
of messages downloaded at a time.
|
144
|
+
Using multi-fetch *will* mean that the backup process will use
|
145
|
+
more memory - equivalent to the size of the groups of messages
|
146
|
+
that are downloaded.
|
147
147
|
|
148
|
-
This behaviour may also exceed limits on your email provider,
|
148
|
+
This behaviour may also exceed the rate limits on your email provider,
|
149
149
|
so it's best to check before cranking it up!
|
150
150
|
|
151
151
|
# Troubleshooting
|
data/docs/development.md
CHANGED
@@ -6,25 +6,26 @@
|
|
6
6
|
|
7
7
|
# Development
|
8
8
|
|
9
|
-
A
|
10
|
-
|
9
|
+
A Dockerfile is available to allow testing with all available Ruby versions,
|
10
|
+
see the `dev/container` directory.
|
11
11
|
|
12
12
|
# Testing
|
13
13
|
|
14
14
|
## Feature Specs
|
15
15
|
|
16
|
-
Specs under `specs/features` are integration specs run against
|
17
|
-
controlled by Docker Compose.
|
18
|
-
|
16
|
+
Specs under `specs/features` are integration specs run against
|
17
|
+
two local IMAP servers controlled by Docker Compose.
|
18
|
+
|
19
|
+
Start them before running the test suite
|
19
20
|
|
20
21
|
```sh
|
21
|
-
$ docker-compose up -d
|
22
|
+
$ docker-compose -f dev/docker-compose.yml up -d
|
22
23
|
```
|
23
24
|
|
24
25
|
or, with Podman
|
25
26
|
|
26
27
|
```sh
|
27
|
-
$ podman-compose -f docker-compose.yml up -d
|
28
|
+
$ podman-compose -f dev/docker-compose.yml up -d
|
28
29
|
```
|
29
30
|
|
30
31
|
```sh
|
@@ -62,24 +63,23 @@ use `last_command_started.output`.
|
|
62
63
|
|
63
64
|
```ruby
|
64
65
|
require "net/imap"
|
66
|
+
require_relative "spec/features/support/30_email_server_helpers"
|
65
67
|
|
66
|
-
|
67
|
-
username = "address@example.com"
|
68
|
-
imap.login(username, "pass")
|
68
|
+
include EmailServerHelpers
|
69
69
|
|
70
|
-
|
71
|
-
response = imap.append("INBOX", message, nil, nil)
|
70
|
+
test_connection = test_server_connection_parameters
|
72
71
|
|
73
|
-
|
74
|
-
|
72
|
+
test_imap = Net::IMAP.new(test_connection[:server], test_connection[:connection_options])
|
73
|
+
test_imap.login(test_connection[:username], test_connection[:password])
|
75
74
|
|
76
|
-
|
77
|
-
|
75
|
+
message = "From: #{test_connection[:username]}\nSubject: Some Subject\n\nHello!\n"
|
76
|
+
response = test_imap.append("INBOX", message, nil, nil)
|
78
77
|
|
79
|
-
|
78
|
+
test_imap.examine("INBOX")
|
79
|
+
uids = test_imap.uid_search(["ALL"]).sort
|
80
80
|
|
81
|
-
|
82
|
-
|
81
|
+
fetch_data_items = test_imap.uid_fetch(uids, ["BODY[]"])
|
82
|
+
```
|
83
83
|
|
84
84
|
# Contributing
|
85
85
|
|
data/imap-backup.gemspec
CHANGED
@@ -19,7 +19,7 @@ Gem::Specification.new do |gem|
|
|
19
19
|
|
20
20
|
gem.executables = gem.files.grep(%r{^bin/}).map { |f| File.basename(f) }
|
21
21
|
gem.require_paths = ["lib"]
|
22
|
-
gem.required_ruby_version = ">= 2.
|
22
|
+
gem.required_ruby_version = ">= 2.7"
|
23
23
|
|
24
24
|
gem.add_runtime_dependency "highline"
|
25
25
|
gem.add_runtime_dependency "mail", "2.7.1"
|
@@ -30,13 +30,6 @@ Gem::Specification.new do |gem|
|
|
30
30
|
gem.add_runtime_dependency "thor", "~> 1.1"
|
31
31
|
gem.add_runtime_dependency "thunderbird", ">= 0.0.0"
|
32
32
|
|
33
|
-
gem.add_development_dependency "aruba", ">= 0.0.0"
|
34
|
-
gem.add_development_dependency "pry-byebug"
|
35
|
-
gem.add_development_dependency "rspec", ">= 3.0.0"
|
36
|
-
gem.add_development_dependency "rubocop-rspec"
|
37
|
-
gem.add_development_dependency "simplecov"
|
38
|
-
gem.add_development_dependency "yard"
|
39
|
-
|
40
33
|
gem.metadata = {
|
41
34
|
"rubygems_mfa_required" => "true"
|
42
35
|
}
|
data/lib/cli_coverage.rb
CHANGED
@@ -1,11 +1,11 @@
|
|
1
1
|
class CliCoverage
|
2
2
|
def self.conditionally_activate
|
3
|
-
return if !ENV
|
3
|
+
return if !ENV.key?("COVERAGE")
|
4
4
|
|
5
5
|
require "simplecov"
|
6
6
|
|
7
7
|
# Collect coverage separately
|
8
|
-
SimpleCov.command_name "#{ENV
|
8
|
+
SimpleCov.command_name "#{ENV.fetch('COVERAGE')} #{ARGV.join(' ')} coverage"
|
9
9
|
|
10
10
|
# Silence output
|
11
11
|
SimpleCov.formatter = SimpleCov::Formatter::SimpleFormatter
|
@@ -99,7 +99,6 @@ module Imap::Backup
|
|
99
99
|
flags = message.flags & PERMITTED_FLAGS
|
100
100
|
retry_on_error(errors: APPEND_RETRY_CLASSES, limit: 3) do
|
101
101
|
response = client.append(utf7_encoded_name, body, flags, date)
|
102
|
-
flags = message.flags & PERMITTED_FLAGS
|
103
102
|
extract_uid(response)
|
104
103
|
end
|
105
104
|
end
|
@@ -30,7 +30,7 @@ module Imap::Backup
|
|
30
30
|
Pathname.glob(glob) do |path|
|
31
31
|
name = source_folder_name(path)
|
32
32
|
serializer = Serializer.new(source_local_path, name)
|
33
|
-
folder =
|
33
|
+
folder = destination_folder_for_path(name)
|
34
34
|
yield serializer, folder
|
35
35
|
end
|
36
36
|
end
|
@@ -55,8 +55,8 @@ module Imap::Backup
|
|
55
55
|
end
|
56
56
|
end
|
57
57
|
|
58
|
-
def
|
59
|
-
parts = name.split(
|
58
|
+
def destination_folder_for_path(name)
|
59
|
+
parts = name.split("/")
|
60
60
|
no_source_prefix =
|
61
61
|
if source_prefix_clipped != "" && parts.first == source_prefix_clipped
|
62
62
|
parts[1..]
|
@@ -0,0 +1,163 @@
|
|
1
|
+
require "imap/backup/cli/backup"
|
2
|
+
require "imap/backup/cli/helpers"
|
3
|
+
require "imap/backup/cli/folder_enumerator"
|
4
|
+
require "imap/backup/migrator"
|
5
|
+
require "imap/backup/mirror"
|
6
|
+
|
7
|
+
module Imap; end
|
8
|
+
|
9
|
+
module Imap::Backup
|
10
|
+
class CLI::Transfer < Thor
|
11
|
+
include Thor::Actions
|
12
|
+
include CLI::Helpers
|
13
|
+
|
14
|
+
ACTIONS = %i(migrate mirror).freeze
|
15
|
+
|
16
|
+
attr_reader :action
|
17
|
+
attr_accessor :automatic_namespaces
|
18
|
+
attr_accessor :config_path
|
19
|
+
attr_accessor :destination_delimiter
|
20
|
+
attr_reader :destination_email
|
21
|
+
attr_accessor :destination_prefix
|
22
|
+
attr_reader :options
|
23
|
+
attr_accessor :reset
|
24
|
+
attr_accessor :source_delimiter
|
25
|
+
attr_reader :source_email
|
26
|
+
attr_accessor :source_prefix
|
27
|
+
|
28
|
+
def initialize(action, source_email, destination_email, options)
|
29
|
+
super([])
|
30
|
+
@action = action
|
31
|
+
@source_email = source_email
|
32
|
+
@destination_email = destination_email
|
33
|
+
@options = options
|
34
|
+
@automatic_namespaces = nil
|
35
|
+
@config_path = nil
|
36
|
+
@destination_delimiter = nil
|
37
|
+
@destination_prefix = nil
|
38
|
+
@reset = nil
|
39
|
+
@source_delimiter = nil
|
40
|
+
@source_prefix = nil
|
41
|
+
end
|
42
|
+
|
43
|
+
no_commands do
|
44
|
+
def run
|
45
|
+
raise "Unknown action '#{action}'" if !ACTIONS.include?(action)
|
46
|
+
|
47
|
+
process_options!
|
48
|
+
prepare_mirror if action == :mirror
|
49
|
+
|
50
|
+
folders.each do |serializer, folder|
|
51
|
+
case action
|
52
|
+
when :migrate
|
53
|
+
Migrator.new(serializer, folder, reset: reset).run
|
54
|
+
when :mirror
|
55
|
+
Mirror.new(serializer, folder).run
|
56
|
+
end
|
57
|
+
end
|
58
|
+
end
|
59
|
+
|
60
|
+
def process_options!
|
61
|
+
self.automatic_namespaces = options[:automatic_namespaces] || false
|
62
|
+
self.config_path = options[:config]
|
63
|
+
self.destination_delimiter = options[:destination_delimiter]
|
64
|
+
self.destination_prefix = options[:destination_prefix]
|
65
|
+
self.source_delimiter = options[:source_delimiter]
|
66
|
+
self.source_prefix = options[:source_prefix]
|
67
|
+
self.reset = options[:reset] || false
|
68
|
+
check_accounts!
|
69
|
+
choose_prefixes_and_delimiters!
|
70
|
+
end
|
71
|
+
|
72
|
+
def check_accounts!
|
73
|
+
if destination_email == source_email
|
74
|
+
raise "Source and destination accounts cannot be the same!"
|
75
|
+
end
|
76
|
+
|
77
|
+
raise "Account '#{destination_email}' does not exist" if !destination_account
|
78
|
+
|
79
|
+
raise "Account '#{source_email}' does not exist" if !source_account
|
80
|
+
end
|
81
|
+
|
82
|
+
def choose_prefixes_and_delimiters!
|
83
|
+
if automatic_namespaces
|
84
|
+
ensure_no_prefix_or_delimiter_parameters!
|
85
|
+
query_servers_for_settings
|
86
|
+
else
|
87
|
+
add_prefix_and_delimiter_defaults
|
88
|
+
end
|
89
|
+
end
|
90
|
+
|
91
|
+
def ensure_no_prefix_or_delimiter_parameters!
|
92
|
+
if destination_delimiter
|
93
|
+
raise "--automatic-namespaces is incompatible with --destination-delimiter"
|
94
|
+
end
|
95
|
+
if destination_prefix
|
96
|
+
raise "--automatic-namespaces is incompatible with --destination-prefix"
|
97
|
+
end
|
98
|
+
raise "--automatic-namespaces is incompatible with --source-delimiter" if source_delimiter
|
99
|
+
raise "--automatic-namespaces is incompatible with --source-prefix" if source_prefix
|
100
|
+
end
|
101
|
+
|
102
|
+
def query_servers_for_settings
|
103
|
+
self.destination_prefix, self.destination_delimiter = account_settings(destination_account)
|
104
|
+
self.source_prefix, self.source_delimiter = account_settings(source_account)
|
105
|
+
end
|
106
|
+
|
107
|
+
def account_settings(account)
|
108
|
+
namespaces = account.client.namespace
|
109
|
+
personal = namespaces.personal.first
|
110
|
+
[personal.prefix, personal.delim]
|
111
|
+
end
|
112
|
+
|
113
|
+
def add_prefix_and_delimiter_defaults
|
114
|
+
self.destination_delimiter ||= "/"
|
115
|
+
self.destination_prefix ||= ""
|
116
|
+
self.source_delimiter ||= "/"
|
117
|
+
self.source_prefix ||= ""
|
118
|
+
end
|
119
|
+
|
120
|
+
def prepare_mirror
|
121
|
+
warn_if_source_account_is_not_in_mirror_mode
|
122
|
+
|
123
|
+
CLI::Backup.new(config: config_path, accounts: source_email).run
|
124
|
+
end
|
125
|
+
|
126
|
+
def warn_if_source_account_is_not_in_mirror_mode
|
127
|
+
return if source_account.mirror_mode
|
128
|
+
|
129
|
+
message =
|
130
|
+
"The account '#{source_account.username}' " \
|
131
|
+
"is not set up to make mirror backups"
|
132
|
+
Logger.logger.warn message
|
133
|
+
end
|
134
|
+
|
135
|
+
def config
|
136
|
+
@config ||= load_config(config: config_path)
|
137
|
+
end
|
138
|
+
|
139
|
+
def enumerator_options
|
140
|
+
{
|
141
|
+
destination: destination_account,
|
142
|
+
destination_delimiter: destination_delimiter,
|
143
|
+
destination_prefix: destination_prefix,
|
144
|
+
source: source_account,
|
145
|
+
source_delimiter: source_delimiter,
|
146
|
+
source_prefix: source_prefix
|
147
|
+
}
|
148
|
+
end
|
149
|
+
|
150
|
+
def folders
|
151
|
+
CLI::FolderEnumerator.new(**enumerator_options)
|
152
|
+
end
|
153
|
+
|
154
|
+
def destination_account
|
155
|
+
config.accounts.find { |a| a.username == destination_email }
|
156
|
+
end
|
157
|
+
|
158
|
+
def source_account
|
159
|
+
config.accounts.find { |a| a.username == source_email }
|
160
|
+
end
|
161
|
+
end
|
162
|
+
end
|
163
|
+
end
|
data/lib/imap/backup/cli.rb
CHANGED
@@ -10,18 +10,38 @@ module Imap::Backup
|
|
10
10
|
autoload :Backup, "imap/backup/cli/backup"
|
11
11
|
autoload :Folders, "imap/backup/cli/folders"
|
12
12
|
autoload :Local, "imap/backup/cli/local"
|
13
|
-
autoload :Migrate, "imap/backup/cli/migrate"
|
14
|
-
autoload :Mirror, "imap/backup/cli/mirror"
|
15
13
|
autoload :Remote, "imap/backup/cli/remote"
|
16
14
|
autoload :Restore, "imap/backup/cli/restore"
|
17
15
|
autoload :Setup, "imap/backup/cli/setup"
|
18
16
|
autoload :Stats, "imap/backup/cli/stats"
|
17
|
+
autoload :Transfer, "imap/backup/cli/transfer"
|
19
18
|
autoload :Utils, "imap/backup/cli/utils"
|
20
19
|
|
21
20
|
include Helpers
|
22
21
|
|
23
22
|
VERSION_ARGUMENTS = %w(-v --version).freeze
|
24
23
|
|
24
|
+
NAMESPACE_CONFIGURATION_DESCRIPTION = <<~DESC.freeze
|
25
|
+
Some IMAP servers use namespaces (i.e. prefixes like "INBOX"),
|
26
|
+
while others, while others concatenate the names of subfolders
|
27
|
+
with a charater ("delimiter") other than "/".
|
28
|
+
|
29
|
+
In these cases there are two choices.
|
30
|
+
|
31
|
+
You can use the `--automatic-namespaces` option.
|
32
|
+
This wil query the source and detination servers for their
|
33
|
+
namespace configuration and will adapt paths accordingly.
|
34
|
+
This option requires that both the source and destination
|
35
|
+
servers are available and work with the provided parameters
|
36
|
+
and authentication.
|
37
|
+
|
38
|
+
If automatic configuration does not work as desired, there are the
|
39
|
+
`--source-prefix=`, `--source-delimiter=`,
|
40
|
+
`--destination-prefix=` and `--destination-delimiter=` parameters.
|
41
|
+
To check what values you should use, check the output of the
|
42
|
+
`imap-backup remote namespaces EMAIL` command.
|
43
|
+
DESC
|
44
|
+
|
25
45
|
default_task :backup
|
26
46
|
|
27
47
|
def self.start(*args)
|
@@ -83,19 +103,22 @@ module Imap::Backup
|
|
83
103
|
All emails which have been backed up for the "source account" (SOURCE_EMAIL) are
|
84
104
|
uploaded to the "destination account" (DESTINATION_EMAIL).
|
85
105
|
|
86
|
-
|
87
|
-
use the `--source-prefix=` and/or `--destination-prefix=` options.
|
106
|
+
Some configuration may be necessary, as follows:
|
88
107
|
|
89
|
-
|
90
|
-
use the `--source-delimiter=` and/or `--destination-delimiter=` options.
|
108
|
+
#{NAMESPACE_CONFIGURATION_DESCRIPTION}
|
91
109
|
|
92
|
-
|
110
|
+
Finally, if you want to delete existing emails in destination folders,
|
93
111
|
use the `--reset` option. In this case, all existing emails are
|
94
112
|
deleted before uploading the migrated emails.
|
95
113
|
DESC
|
96
114
|
config_option
|
97
115
|
quiet_option
|
98
116
|
verbose_option
|
117
|
+
method_option(
|
118
|
+
"automatic-namespaces",
|
119
|
+
type: :boolean,
|
120
|
+
desc: "automatically choose delimiters and prefixes"
|
121
|
+
)
|
99
122
|
method_option(
|
100
123
|
"destination-delimiter",
|
101
124
|
type: :string,
|
@@ -126,7 +149,7 @@ module Imap::Backup
|
|
126
149
|
)
|
127
150
|
def migrate(source_email, destination_email)
|
128
151
|
non_logging_options = Imap::Backup::Logger.setup_logging(options)
|
129
|
-
|
152
|
+
Transfer.new(:migrate, source_email, destination_email, non_logging_options).run
|
130
153
|
end
|
131
154
|
|
132
155
|
desc(
|
@@ -140,7 +163,7 @@ module Imap::Backup
|
|
140
163
|
If a folder list is configured for the SOURCE_EMAIL account,
|
141
164
|
only the folders indicated by the setting are copied.
|
142
165
|
|
143
|
-
First, runs the download of the SOURCE_EMAIL account.
|
166
|
+
First, it runs the download of the SOURCE_EMAIL account.
|
144
167
|
If the SOURCE_EMAIL account is **not** configured to be in 'mirror' mode,
|
145
168
|
a warning is printed.
|
146
169
|
|
@@ -152,6 +175,11 @@ module Imap::Backup
|
|
152
175
|
config_option
|
153
176
|
quiet_option
|
154
177
|
verbose_option
|
178
|
+
method_option(
|
179
|
+
"automatic-namespaces",
|
180
|
+
type: :boolean,
|
181
|
+
desc: "automatically choose delimiters and prefixes"
|
182
|
+
)
|
155
183
|
method_option(
|
156
184
|
"destination-delimiter",
|
157
185
|
type: :string,
|
@@ -176,7 +204,7 @@ module Imap::Backup
|
|
176
204
|
)
|
177
205
|
def mirror(source_email, destination_email)
|
178
206
|
non_logging_options = Imap::Backup::Logger.setup_logging(options)
|
179
|
-
|
207
|
+
Transfer.new(:mirror, source_email, destination_email, non_logging_options).run
|
180
208
|
end
|
181
209
|
|
182
210
|
desc "remote SUBCOMMAND [OPTIONS]", "View info about online accounts"
|
@@ -179,7 +179,7 @@ module Imap::Backup
|
|
179
179
|
Logger.logger.info <<~MESSAGE
|
180
180
|
Local metadata for folder '#{folder_path}' is currently stored in the version 2 format.
|
181
181
|
|
182
|
-
|
182
|
+
Migrating to the version 3 format...
|
183
183
|
MESSAGE
|
184
184
|
|
185
185
|
migrator.run
|
data/lib/imap/backup/version.rb
CHANGED
data/lib/retry_on_error.rb
CHANGED
@@ -1,11 +1,12 @@
|
|
1
1
|
module RetryOnError
|
2
|
-
def retry_on_error(errors:, limit: 10)
|
2
|
+
def retry_on_error(errors:, limit: 10, on_error: nil)
|
3
3
|
tries ||= 1
|
4
4
|
yield
|
5
5
|
rescue *errors => e
|
6
6
|
if tries < limit
|
7
7
|
message = "#{e}, attempt #{tries} of #{limit}"
|
8
8
|
Imap::Backup::Logger.logger.debug message
|
9
|
+
on_error&.call
|
9
10
|
tries += 1
|
10
11
|
retry
|
11
12
|
end
|
metadata
CHANGED
@@ -1,14 +1,14 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: imap-backup
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version:
|
4
|
+
version: 13.0.0
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Joe Yates
|
8
8
|
autorequire:
|
9
9
|
bindir: bin
|
10
10
|
cert_chain: []
|
11
|
-
date: 2023-09-
|
11
|
+
date: 2023-09-30 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: highline
|
@@ -122,90 +122,6 @@ dependencies:
|
|
122
122
|
- - ">="
|
123
123
|
- !ruby/object:Gem::Version
|
124
124
|
version: 0.0.0
|
125
|
-
- !ruby/object:Gem::Dependency
|
126
|
-
name: aruba
|
127
|
-
requirement: !ruby/object:Gem::Requirement
|
128
|
-
requirements:
|
129
|
-
- - ">="
|
130
|
-
- !ruby/object:Gem::Version
|
131
|
-
version: 0.0.0
|
132
|
-
type: :development
|
133
|
-
prerelease: false
|
134
|
-
version_requirements: !ruby/object:Gem::Requirement
|
135
|
-
requirements:
|
136
|
-
- - ">="
|
137
|
-
- !ruby/object:Gem::Version
|
138
|
-
version: 0.0.0
|
139
|
-
- !ruby/object:Gem::Dependency
|
140
|
-
name: pry-byebug
|
141
|
-
requirement: !ruby/object:Gem::Requirement
|
142
|
-
requirements:
|
143
|
-
- - ">="
|
144
|
-
- !ruby/object:Gem::Version
|
145
|
-
version: '0'
|
146
|
-
type: :development
|
147
|
-
prerelease: false
|
148
|
-
version_requirements: !ruby/object:Gem::Requirement
|
149
|
-
requirements:
|
150
|
-
- - ">="
|
151
|
-
- !ruby/object:Gem::Version
|
152
|
-
version: '0'
|
153
|
-
- !ruby/object:Gem::Dependency
|
154
|
-
name: rspec
|
155
|
-
requirement: !ruby/object:Gem::Requirement
|
156
|
-
requirements:
|
157
|
-
- - ">="
|
158
|
-
- !ruby/object:Gem::Version
|
159
|
-
version: 3.0.0
|
160
|
-
type: :development
|
161
|
-
prerelease: false
|
162
|
-
version_requirements: !ruby/object:Gem::Requirement
|
163
|
-
requirements:
|
164
|
-
- - ">="
|
165
|
-
- !ruby/object:Gem::Version
|
166
|
-
version: 3.0.0
|
167
|
-
- !ruby/object:Gem::Dependency
|
168
|
-
name: rubocop-rspec
|
169
|
-
requirement: !ruby/object:Gem::Requirement
|
170
|
-
requirements:
|
171
|
-
- - ">="
|
172
|
-
- !ruby/object:Gem::Version
|
173
|
-
version: '0'
|
174
|
-
type: :development
|
175
|
-
prerelease: false
|
176
|
-
version_requirements: !ruby/object:Gem::Requirement
|
177
|
-
requirements:
|
178
|
-
- - ">="
|
179
|
-
- !ruby/object:Gem::Version
|
180
|
-
version: '0'
|
181
|
-
- !ruby/object:Gem::Dependency
|
182
|
-
name: simplecov
|
183
|
-
requirement: !ruby/object:Gem::Requirement
|
184
|
-
requirements:
|
185
|
-
- - ">="
|
186
|
-
- !ruby/object:Gem::Version
|
187
|
-
version: '0'
|
188
|
-
type: :development
|
189
|
-
prerelease: false
|
190
|
-
version_requirements: !ruby/object:Gem::Requirement
|
191
|
-
requirements:
|
192
|
-
- - ">="
|
193
|
-
- !ruby/object:Gem::Version
|
194
|
-
version: '0'
|
195
|
-
- !ruby/object:Gem::Dependency
|
196
|
-
name: yard
|
197
|
-
requirement: !ruby/object:Gem::Requirement
|
198
|
-
requirements:
|
199
|
-
- - ">="
|
200
|
-
- !ruby/object:Gem::Version
|
201
|
-
version: '0'
|
202
|
-
type: :development
|
203
|
-
prerelease: false
|
204
|
-
version_requirements: !ruby/object:Gem::Requirement
|
205
|
-
requirements:
|
206
|
-
- - ">="
|
207
|
-
- !ruby/object:Gem::Version
|
208
|
-
version: '0'
|
209
125
|
description: Backup GMail, or any other IMAP email service, to disk.
|
210
126
|
email:
|
211
127
|
- joe.g.yates@gmail.com
|
@@ -247,12 +163,11 @@ files:
|
|
247
163
|
- lib/imap/backup/cli/helpers.rb
|
248
164
|
- lib/imap/backup/cli/local.rb
|
249
165
|
- lib/imap/backup/cli/local/check.rb
|
250
|
-
- lib/imap/backup/cli/migrate.rb
|
251
|
-
- lib/imap/backup/cli/mirror.rb
|
252
166
|
- lib/imap/backup/cli/remote.rb
|
253
167
|
- lib/imap/backup/cli/restore.rb
|
254
168
|
- lib/imap/backup/cli/setup.rb
|
255
169
|
- lib/imap/backup/cli/stats.rb
|
170
|
+
- lib/imap/backup/cli/transfer.rb
|
256
171
|
- lib/imap/backup/cli/utils.rb
|
257
172
|
- lib/imap/backup/client/apple_mail.rb
|
258
173
|
- lib/imap/backup/client/automatic_login_wrapper.rb
|
@@ -310,7 +225,7 @@ required_ruby_version: !ruby/object:Gem::Requirement
|
|
310
225
|
requirements:
|
311
226
|
- - ">="
|
312
227
|
- !ruby/object:Gem::Version
|
313
|
-
version: '2.
|
228
|
+
version: '2.7'
|
314
229
|
required_rubygems_version: !ruby/object:Gem::Requirement
|
315
230
|
requirements:
|
316
231
|
- - ">="
|
@@ -1,87 +0,0 @@
|
|
1
|
-
require "imap/backup/cli/folder_enumerator"
|
2
|
-
require "imap/backup/migrator"
|
3
|
-
|
4
|
-
module Imap; end
|
5
|
-
|
6
|
-
module Imap::Backup
|
7
|
-
class CLI::Migrate < Thor
|
8
|
-
include Thor::Actions
|
9
|
-
include CLI::Helpers
|
10
|
-
|
11
|
-
attr_reader :destination_delimiter
|
12
|
-
attr_reader :destination_email
|
13
|
-
attr_reader :destination_prefix
|
14
|
-
attr_reader :config_path
|
15
|
-
attr_reader :reset
|
16
|
-
attr_reader :source_delimiter
|
17
|
-
attr_reader :source_email
|
18
|
-
attr_reader :source_prefix
|
19
|
-
|
20
|
-
def initialize(
|
21
|
-
source_email,
|
22
|
-
destination_email,
|
23
|
-
config: nil,
|
24
|
-
destination_delimiter: "/",
|
25
|
-
destination_prefix: "",
|
26
|
-
reset: false,
|
27
|
-
source_delimiter: "/",
|
28
|
-
source_prefix: ""
|
29
|
-
)
|
30
|
-
super([])
|
31
|
-
@destination_delimiter = destination_delimiter
|
32
|
-
@destination_email = destination_email
|
33
|
-
@destination_prefix = destination_prefix
|
34
|
-
@config_path = config
|
35
|
-
@reset = reset
|
36
|
-
@source_delimiter = source_delimiter
|
37
|
-
@source_email = source_email
|
38
|
-
@source_prefix = source_prefix
|
39
|
-
end
|
40
|
-
|
41
|
-
no_commands do
|
42
|
-
def run
|
43
|
-
check_accounts!
|
44
|
-
folders.each do |serializer, folder|
|
45
|
-
Migrator.new(serializer, folder, reset: reset).run
|
46
|
-
end
|
47
|
-
end
|
48
|
-
|
49
|
-
def check_accounts!
|
50
|
-
if destination_email == source_email
|
51
|
-
raise "Source and destination accounts cannot be the same!"
|
52
|
-
end
|
53
|
-
|
54
|
-
raise "Account '#{destination_email}' does not exist" if !destination_account
|
55
|
-
|
56
|
-
raise "Account '#{source_email}' does not exist" if !source_account
|
57
|
-
end
|
58
|
-
|
59
|
-
def config
|
60
|
-
@config ||= load_config(config: config_path)
|
61
|
-
end
|
62
|
-
|
63
|
-
def enumerator_options
|
64
|
-
{
|
65
|
-
destination: destination_account,
|
66
|
-
destination_delimiter: destination_delimiter,
|
67
|
-
destination_prefix: destination_prefix,
|
68
|
-
source: source_account,
|
69
|
-
source_delimiter: source_delimiter,
|
70
|
-
source_prefix: source_prefix
|
71
|
-
}
|
72
|
-
end
|
73
|
-
|
74
|
-
def folders
|
75
|
-
CLI::FolderEnumerator.new(**enumerator_options)
|
76
|
-
end
|
77
|
-
|
78
|
-
def destination_account
|
79
|
-
config.accounts.find { |a| a.username == destination_email }
|
80
|
-
end
|
81
|
-
|
82
|
-
def source_account
|
83
|
-
config.accounts.find { |a| a.username == source_email }
|
84
|
-
end
|
85
|
-
end
|
86
|
-
end
|
87
|
-
end
|
@@ -1,97 +0,0 @@
|
|
1
|
-
require "imap/backup/cli/folder_enumerator"
|
2
|
-
require "imap/backup/mirror"
|
3
|
-
|
4
|
-
module Imap; end
|
5
|
-
|
6
|
-
module Imap::Backup
|
7
|
-
class CLI::Mirror < Thor
|
8
|
-
include Thor::Actions
|
9
|
-
include CLI::Helpers
|
10
|
-
|
11
|
-
attr_reader :destination_delimiter
|
12
|
-
attr_reader :destination_email
|
13
|
-
attr_reader :destination_prefix
|
14
|
-
attr_reader :config_path
|
15
|
-
attr_reader :source_delimiter
|
16
|
-
attr_reader :source_email
|
17
|
-
attr_reader :source_prefix
|
18
|
-
|
19
|
-
def initialize(
|
20
|
-
source_email,
|
21
|
-
destination_email,
|
22
|
-
config: nil,
|
23
|
-
destination_delimiter: "/",
|
24
|
-
destination_prefix: "",
|
25
|
-
source_delimiter: "/",
|
26
|
-
source_prefix: ""
|
27
|
-
)
|
28
|
-
super([])
|
29
|
-
@destination_delimiter = destination_delimiter
|
30
|
-
@destination_email = destination_email
|
31
|
-
@destination_prefix = destination_prefix
|
32
|
-
@config_path = config
|
33
|
-
@source_delimiter = source_delimiter
|
34
|
-
@source_email = source_email
|
35
|
-
@source_prefix = source_prefix
|
36
|
-
end
|
37
|
-
|
38
|
-
no_commands do
|
39
|
-
def run
|
40
|
-
check_accounts!
|
41
|
-
warn_if_source_account_is_not_in_mirror_mode
|
42
|
-
|
43
|
-
CLI::Backup.new(config: config_path, accounts: source_email).run
|
44
|
-
|
45
|
-
folders.each do |serializer, folder|
|
46
|
-
Mirror.new(serializer, folder).run
|
47
|
-
end
|
48
|
-
end
|
49
|
-
|
50
|
-
def check_accounts!
|
51
|
-
if destination_email == source_email
|
52
|
-
raise "Source and destination accounts cannot be the same!"
|
53
|
-
end
|
54
|
-
|
55
|
-
raise "Account '#{destination_email}' does not exist" if !destination_account
|
56
|
-
|
57
|
-
raise "Account '#{source_email}' does not exist" if !source_account
|
58
|
-
end
|
59
|
-
|
60
|
-
def warn_if_source_account_is_not_in_mirror_mode
|
61
|
-
return if source_account.mirror_mode
|
62
|
-
|
63
|
-
message =
|
64
|
-
"The account '#{source_account.username}' " \
|
65
|
-
"is not set up to make mirror backups"
|
66
|
-
Logger.logger.warn message
|
67
|
-
end
|
68
|
-
|
69
|
-
def config
|
70
|
-
@config = load_config(config: config_path)
|
71
|
-
end
|
72
|
-
|
73
|
-
def enumerator_options
|
74
|
-
{
|
75
|
-
destination: destination_account,
|
76
|
-
destination_delimiter: destination_delimiter,
|
77
|
-
destination_prefix: destination_prefix,
|
78
|
-
source: source_account,
|
79
|
-
source_delimiter: source_delimiter,
|
80
|
-
source_prefix: source_prefix
|
81
|
-
}
|
82
|
-
end
|
83
|
-
|
84
|
-
def folders
|
85
|
-
CLI::FolderEnumerator.new(**enumerator_options)
|
86
|
-
end
|
87
|
-
|
88
|
-
def destination_account
|
89
|
-
config.accounts.find { |a| a.username == destination_email }
|
90
|
-
end
|
91
|
-
|
92
|
-
def source_account
|
93
|
-
config.accounts.find { |a| a.username == source_email }
|
94
|
-
end
|
95
|
-
end
|
96
|
-
end
|
97
|
-
end
|