imap-backup 12.0.0 → 13.0.0
Sign up to get free protection for your applications and to get access to all the features.
- 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
|