imap-backup 11.1.0 → 12.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/docs/migrate-server-keep-address.md +38 -20
- data/lib/imap/backup/account/backup.rb +1 -1
- data/lib/imap/backup/account/client_factory.rb +12 -19
- data/lib/imap/backup/cli/stats.rb +2 -0
- data/lib/imap/backup/cli.rb +8 -3
- data/lib/imap/backup/client/automatic_login_wrapper.rb +43 -0
- data/lib/imap/backup/serializer/appender.rb +1 -1
- data/lib/imap/backup/serializer/delayed_metadata_serializer.rb +1 -1
- data/lib/imap/backup/serializer/integrity_checker.rb +14 -2
- data/lib/imap/backup/setup/global_options/download_strategy_chooser.rb +5 -3
- data/lib/imap/backup/setup/global_options.rb +6 -4
- data/lib/imap/backup/setup.rb +1 -1
- data/lib/imap/backup/version.rb +2 -2
- metadata +3 -2
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 22cd34d9d0940821677b2007473b2608421dda521dd47f212346385eba4ea6de
|
4
|
+
data.tar.gz: 3f249c818af7260fa15f8ed1509f2151876bddb28544f401c5264d9c74be1f74
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 8773c5878b741fb116bfc617b79b91f536c10c2406668f6ad0f544f2383e9215d163d37b7e8beec6e237310b72455bac63561e3d03b171cb4b11ca37624d6851
|
7
|
+
data.tar.gz: 3f6113d064ef19931eb32e1c71876e704ec3898f5657154fabe5626f13805ec1b209e3ff56ec2bd4c407f54cb22e0a590064f2ee9713b0dddfd5836c0371e1f9
|
@@ -1,29 +1,47 @@
|
|
1
1
|
# Migrate to a new e-mail server while keeping your existing address
|
2
2
|
|
3
|
-
While switching e-mail provider (from provider `A` to `B`),
|
3
|
+
While switching e-mail provider (from provider `A` to `B`),
|
4
|
+
you might want to keep the same address (`mymail@domain.com`),
|
5
|
+
and copy all your existing e-mails to your new server `B`.
|
6
|
+
`imap-backup` can do that too!
|
4
7
|
|
5
|
-
|
8
|
+
It is best to use [`imap-backup migrate`](/docs/commands/migrate.md)
|
9
|
+
and not [`imap-backup restore`](/docs/commands/restore.md) here because
|
10
|
+
`migrate` simply copies emails to folders with the same name as the ones
|
11
|
+
they were downloaded from, while `restore` changes the names of restored
|
12
|
+
folders if folders with the same name already exist on the destination server.
|
13
|
+
|
14
|
+
1. Backup your e-mails: use [`imap-backup setup`](/docs/commands/setup.md)
|
15
|
+
to setup connection to your old provider `A`,
|
16
|
+
then launch [`imap-backup backup`](/docs/commands/backup.md).
|
6
17
|
1. Actually switch your e-mail service provider (update your DNS MX and all that...).
|
7
|
-
1.
|
18
|
+
1. As both the source and the destination have the same address,
|
19
|
+
you need to manually rename your old account first:
|
8
20
|
|
9
|
-
|
21
|
+
1. Modify your configuration file manually
|
22
|
+
(i.e. not via `imap-backup setup`) and
|
23
|
+
rename your account to `mymail-old@domain.com`:
|
10
24
|
|
11
|
-
|
12
|
-
|
13
|
-
|
14
|
-
|
15
|
-
|
16
|
-
|
17
|
-
|
18
|
-
|
19
|
-
|
20
|
-
|
21
|
-
|
22
|
-
|
25
|
+
```diff
|
26
|
+
"accounts": [
|
27
|
+
{
|
28
|
+
- "username": "mymail@domain.com",
|
29
|
+
+ "username": "mymail-old@domain.com",
|
30
|
+
"password": "...",
|
31
|
+
- "local_path": "/some/path/.imap-backup/mymail_domain.com",
|
32
|
+
+ "local_path": "/some/path/.imap-backup/mymail-old_domain.com",
|
33
|
+
"folders": [...],
|
34
|
+
"server": "..."
|
35
|
+
}
|
36
|
+
```
|
23
37
|
|
24
|
-
|
38
|
+
1. Rename the backup directory from `mymail_domain.com`
|
39
|
+
to `mymail-old_domain.com`.
|
25
40
|
|
26
|
-
1. Set up a new account giving access to the new provider `B`
|
27
|
-
|
41
|
+
1. Set up a new account giving access to the new provider `B`
|
42
|
+
using `imap-backup setup`.
|
43
|
+
1. Now you can use `imap-backup migrate`, optionally adapting
|
44
|
+
[delimiters and prefixes configuration](/docs/delimiters-and-prefixes.md)
|
45
|
+
if need be:
|
28
46
|
|
29
|
-
imap-backup migrate mymail-old@domain.com mymail@domain.com [options]
|
47
|
+
imap-backup migrate mymail-old@domain.com mymail@domain.com [options]
|
@@ -20,7 +20,7 @@ module Imap::Backup
|
|
20
20
|
def run
|
21
21
|
Logger.logger.info "Running backup of account: #{account.username}"
|
22
22
|
# start the connection so we get logging messages in the right order
|
23
|
-
account.client
|
23
|
+
account.client.login
|
24
24
|
|
25
25
|
Account::FolderEnsurer.new(account: account).run
|
26
26
|
Account::LocalOnlyFolderDeleter.new(account: account).run if account.mirror_mode
|
@@ -2,8 +2,8 @@ require "socket"
|
|
2
2
|
|
3
3
|
require "email/provider"
|
4
4
|
require "imap/backup/client/apple_mail"
|
5
|
+
require "imap/backup/client/automatic_login_wrapper"
|
5
6
|
require "imap/backup/client/default"
|
6
|
-
require "retry_on_error"
|
7
7
|
|
8
8
|
module Imap; end
|
9
9
|
|
@@ -11,10 +11,6 @@ module Imap::Backup
|
|
11
11
|
class Account; end
|
12
12
|
|
13
13
|
class Account::ClientFactory
|
14
|
-
include RetryOnError
|
15
|
-
|
16
|
-
LOGIN_RETRY_CLASSES = [::EOFError, ::Errno::ECONNRESET, ::SocketError].freeze
|
17
|
-
|
18
14
|
attr_reader :account
|
19
15
|
|
20
16
|
def initialize(account:)
|
@@ -24,20 +20,17 @@ module Imap::Backup
|
|
24
20
|
end
|
25
21
|
|
26
22
|
def run
|
27
|
-
|
28
|
-
|
29
|
-
|
30
|
-
|
31
|
-
|
32
|
-
|
33
|
-
|
34
|
-
|
35
|
-
|
36
|
-
|
37
|
-
|
38
|
-
client.login
|
39
|
-
client
|
40
|
-
end
|
23
|
+
options = provider_options
|
24
|
+
Logger.logger.debug(
|
25
|
+
"Creating IMAP instance: #{server}, options: #{options.inspect}"
|
26
|
+
)
|
27
|
+
client =
|
28
|
+
if provider.is_a?(Email::Provider::AppleMail)
|
29
|
+
Client::AppleMail.new(server, account, options)
|
30
|
+
else
|
31
|
+
Client::Default.new(server, account, options)
|
32
|
+
end
|
33
|
+
Client::AutomaticLoginWrapper.new(client: client)
|
41
34
|
end
|
42
35
|
|
43
36
|
private
|
@@ -35,6 +35,7 @@ module Imap::Backup
|
|
35
35
|
end
|
36
36
|
|
37
37
|
def stats
|
38
|
+
Logger.logger.debug("[Stats] loading configuration")
|
38
39
|
config = load_config(**options)
|
39
40
|
account = account(config, email)
|
40
41
|
|
@@ -46,6 +47,7 @@ module Imap::Backup
|
|
46
47
|
|
47
48
|
serializer = Serializer.new(account.local_path, folder.name)
|
48
49
|
local_uids = serializer.uids
|
50
|
+
Logger.logger.debug("[Stats] fetching email list for '#{folder.name}'")
|
49
51
|
remote_uids = folder.uids
|
50
52
|
{
|
51
53
|
folder: folder.name,
|
data/lib/imap/backup/cli.rb
CHANGED
@@ -211,9 +211,14 @@ module Imap::Backup
|
|
211
211
|
|
212
212
|
desc "stats EMAIL [OPTIONS]", "Print stats for each account folder"
|
213
213
|
long_desc <<~DESC
|
214
|
-
For each account folder, lists
|
215
|
-
|
216
|
-
|
214
|
+
For each account folder, lists three counts of emails:
|
215
|
+
|
216
|
+
1. "server" - those yet to be downloaded,
|
217
|
+
|
218
|
+
2. "both" - those that exist on server and are backed up,
|
219
|
+
|
220
|
+
3. "local" - those which are only present in the backup (as they have been deleted
|
221
|
+
on the server).
|
217
222
|
DESC
|
218
223
|
config_option
|
219
224
|
format_option
|
@@ -0,0 +1,43 @@
|
|
1
|
+
require "retry_on_error"
|
2
|
+
|
3
|
+
module Imap; end
|
4
|
+
|
5
|
+
module Imap::Backup
|
6
|
+
module Client; end
|
7
|
+
|
8
|
+
class Client::AutomaticLoginWrapper
|
9
|
+
include RetryOnError
|
10
|
+
|
11
|
+
LOGIN_RETRY_CLASSES = [::EOFError, ::Errno::ECONNRESET, ::SocketError].freeze
|
12
|
+
|
13
|
+
attr_reader :client
|
14
|
+
attr_reader :login_called
|
15
|
+
|
16
|
+
def initialize(client:)
|
17
|
+
@client = client
|
18
|
+
@login_called = false
|
19
|
+
end
|
20
|
+
|
21
|
+
def method_missing(method_name, *arguments, &block)
|
22
|
+
if login_called
|
23
|
+
client.send(method_name, *arguments, &block)
|
24
|
+
else
|
25
|
+
do_first_login
|
26
|
+
client.send(method_name, *arguments, &block) if method_name != :login
|
27
|
+
end
|
28
|
+
end
|
29
|
+
|
30
|
+
def respond_to_missing?(method_name, _include_private = false)
|
31
|
+
client.respond_to?(method_name)
|
32
|
+
end
|
33
|
+
|
34
|
+
private
|
35
|
+
|
36
|
+
def do_first_login
|
37
|
+
retry_on_error(errors: LOGIN_RETRY_CLASSES) do
|
38
|
+
client.login
|
39
|
+
@login_called = true
|
40
|
+
end
|
41
|
+
end
|
42
|
+
end
|
43
|
+
end
|
@@ -29,7 +29,7 @@ module Imap::Backup
|
|
29
29
|
rollback_on_error do
|
30
30
|
serialized = to_serialized(message)
|
31
31
|
mbox.append serialized
|
32
|
-
imap.append uid, serialized.
|
32
|
+
imap.append uid, serialized.bytesize, flags: flags
|
33
33
|
rescue StandardError => e
|
34
34
|
raise <<-ERROR.gsub(/^\s*/m, "")
|
35
35
|
[#{folder}] failed to append message #{uid}: #{message}.
|
@@ -33,7 +33,7 @@ module Imap::Backup
|
|
33
33
|
tsx.fail_outside_transaction!(:append)
|
34
34
|
mboxrd_message = Email::Mboxrd::Message.new(message)
|
35
35
|
serialized = mboxrd_message.to_serialized
|
36
|
-
tsx.data[:metadata] << {uid: uid, length: serialized.
|
36
|
+
tsx.data[:metadata] << {uid: uid, length: serialized.bytesize, flags: flags}
|
37
37
|
mbox.append(serialized)
|
38
38
|
end
|
39
39
|
|
@@ -15,6 +15,9 @@ module Imap::Backup
|
|
15
15
|
end
|
16
16
|
|
17
17
|
def run
|
18
|
+
Logger.logger.debug(
|
19
|
+
"[IntegrityChecker] checking '#{imap.pathname}' against '#{mbox.pathname}'"
|
20
|
+
)
|
18
21
|
if !imap.valid?
|
19
22
|
message = ".imap file '#{imap.pathname}' is corrupt"
|
20
23
|
raise Serializer::FolderIntegrityError, message
|
@@ -56,14 +59,18 @@ module Imap::Backup
|
|
56
59
|
def check_mbox_length!
|
57
60
|
last = imap.messages[-1]
|
58
61
|
|
59
|
-
|
62
|
+
expected = last.offset + last.length
|
63
|
+
Logger.logger.debug(
|
64
|
+
"[IntegrityChecker] mbox length is #{mbox.length}, expected length is #{expected}"
|
65
|
+
)
|
66
|
+
if mbox.length < expected
|
60
67
|
message =
|
61
68
|
".mbox file '#{mbox.pathname}' is shorter than indicated by " \
|
62
69
|
".imap file '#{imap.pathname}'"
|
63
70
|
raise Serializer::FolderIntegrityError, message
|
64
71
|
end
|
65
72
|
|
66
|
-
if mbox.length >
|
73
|
+
if mbox.length > expected
|
67
74
|
message =
|
68
75
|
".mbox file '#{mbox.pathname}' is longer than indicated by " \
|
69
76
|
".imap file '#{imap.pathname}'"
|
@@ -77,6 +84,11 @@ module Imap::Backup
|
|
77
84
|
|
78
85
|
next if text.start_with?("From ")
|
79
86
|
|
87
|
+
Logger.logger.debug(
|
88
|
+
"[IntegrityChecker] looking for message with UID #{m.uid} " \
|
89
|
+
"at offset #{m.offset}, " \
|
90
|
+
"mbox starts with '#{text[0..200]}', expecting 'From '"
|
91
|
+
)
|
80
92
|
message =
|
81
93
|
"Message #{m.uid} not found at expected offset #{m.offset} " \
|
82
94
|
"in file '#{mbox.pathname}'"
|
@@ -7,11 +7,9 @@ class Imap::Backup::Setup; end
|
|
7
7
|
class Imap::Backup::Setup::GlobalOptions
|
8
8
|
class DownloadStrategyChooser
|
9
9
|
attr_reader :config
|
10
|
-
attr_reader :highline
|
11
10
|
|
12
|
-
def initialize(config
|
11
|
+
def initialize(config:)
|
13
12
|
@config = config
|
14
|
-
@highline = highline
|
15
13
|
end
|
16
14
|
|
17
15
|
def run
|
@@ -81,5 +79,9 @@ class Imap::Backup::Setup::GlobalOptions
|
|
81
79
|
highline.ask "Press a key "
|
82
80
|
end
|
83
81
|
end
|
82
|
+
|
83
|
+
def highline
|
84
|
+
Imap::Backup::Setup.highline
|
85
|
+
end
|
84
86
|
end
|
85
87
|
end
|
@@ -8,11 +8,9 @@ module Imap::Backup
|
|
8
8
|
|
9
9
|
class Setup::GlobalOptions
|
10
10
|
attr_reader :config
|
11
|
-
attr_reader :highline
|
12
11
|
|
13
|
-
def initialize(config
|
12
|
+
def initialize(config:)
|
14
13
|
@config = config
|
15
|
-
@highline = highline
|
16
14
|
end
|
17
15
|
|
18
16
|
def run
|
@@ -46,8 +44,12 @@ module Imap::Backup
|
|
46
44
|
current = strategies.find { |s| s[:key] == config.download_strategy }
|
47
45
|
changed = config.download_strategy_modified ? " *" : ""
|
48
46
|
menu.choice("change download strategy (currently: '#{current[:description]}')#{changed}") do
|
49
|
-
DownloadStrategyChooser.new(config: config
|
47
|
+
DownloadStrategyChooser.new(config: config).run
|
50
48
|
end
|
51
49
|
end
|
50
|
+
|
51
|
+
def highline
|
52
|
+
Imap::Backup::Setup.highline
|
53
|
+
end
|
52
54
|
end
|
53
55
|
end
|
data/lib/imap/backup/setup.rb
CHANGED
@@ -76,7 +76,7 @@ module Imap::Backup
|
|
76
76
|
def modify_global_options(menu)
|
77
77
|
changed = config.modified? ? " *" : ""
|
78
78
|
menu.choice("modify global options#{changed}") do
|
79
|
-
GlobalOptions.new(config: config
|
79
|
+
GlobalOptions.new(config: config).run
|
80
80
|
end
|
81
81
|
end
|
82
82
|
|
data/lib/imap/backup/version.rb
CHANGED
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: 12.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-
|
11
|
+
date: 2023-09-19 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: highline
|
@@ -255,6 +255,7 @@ files:
|
|
255
255
|
- lib/imap/backup/cli/stats.rb
|
256
256
|
- lib/imap/backup/cli/utils.rb
|
257
257
|
- lib/imap/backup/client/apple_mail.rb
|
258
|
+
- lib/imap/backup/client/automatic_login_wrapper.rb
|
258
259
|
- lib/imap/backup/client/default.rb
|
259
260
|
- lib/imap/backup/configuration.rb
|
260
261
|
- lib/imap/backup/downloader.rb
|