imap-backup 9.2.0 → 9.3.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 +1 -0
- data/docs/migrate-server-keep-address.md +29 -0
- data/lib/imap/backup/account/folder.rb +8 -2
- data/lib/imap/backup/cli/helpers.rb +2 -0
- data/lib/imap/backup/cli/local.rb +58 -0
- data/lib/imap/backup/cli/utils.rb +1 -1
- data/lib/imap/backup/cli.rb +15 -0
- data/lib/imap/backup/serializer/imap.rb +8 -8
- data/lib/imap/backup/serializer/mbox.rb +0 -2
- data/lib/imap/backup/serializer.rb +53 -0
- 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: 0f01e9469da5b4990bdcc90f5e019b0cf5e9cf06203f29980503edabdec4abb8
|
4
|
+
data.tar.gz: 93501685a083a9b3edc8f1bb606decf7bcac965773063eb9e59863f3afa43113
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 494089b738a8ca9080c1057b4bdc5a8ebe8d2900e756fbd2c16445d856e3939da759bd8bc644585f8d6591055a79656b92a44b60cb0a522a87023d30f6b49ca9
|
7
|
+
data.tar.gz: fa3e100e307103808360797853953cfec677bbd6ee3b9b36330f2449ef065a5e128660154e6d3593a34652f6753a553bf4697e145c60a3e728645acb67477de3
|
data/README.md
CHANGED
@@ -76,6 +76,7 @@ and exported via [utils export-to-thunderbird](docs/commands/utils-export-to-thu
|
|
76
76
|
|
77
77
|
* [backup](docs/commands/backup.md)
|
78
78
|
* [local accounts](docs/commands/local-accounts.md)
|
79
|
+
* [local check](docs/commands/local-check.md)
|
79
80
|
* [local folders](docs/commands/local-folders.md)
|
80
81
|
* [local list](docs/commands/local-list.md)
|
81
82
|
* [local show](docs/commands/local-show.md)
|
@@ -0,0 +1,29 @@
|
|
1
|
+
# Migrate to a new e-mail server while keeping your existing address
|
2
|
+
|
3
|
+
While switching e-mail provider (from provider `A` to `B`), you might want to keep the same address (`mymail@domain.com`), and restrieve all your existing e-mails on your new server `B`. `imap-backup` can do that too!
|
4
|
+
|
5
|
+
1. Backup your e-mails: use [`imap-backup setup`](/docs/commands/setup.md) to setup connection to your old provider `A`, then launch [`imap-backup backup`](/docs/commands/backup.md).
|
6
|
+
1. Actually switch your e-mail service provider (update your DNS MX and all that...).
|
7
|
+
1. It is best to use [`imap-backup migrate`](/docs/commands/migrate.md) and not [`imap-backup restore`](/docs/commands/restore.md) here, but both the source and the destination have the same address... You need to manually rename your old account first:
|
8
|
+
|
9
|
+
1. Modify your configuration file manually (i.e. not via `imap-backup setup`) and rename your account to `mymail-old@domain.com`:
|
10
|
+
|
11
|
+
```diff
|
12
|
+
"accounts": [
|
13
|
+
{
|
14
|
+
- "username": "mymail@domain.com",
|
15
|
+
+ "username": "mymail-old@domain.com",
|
16
|
+
"password": "...",
|
17
|
+
- "local_path": "/some/path/.imap-backup/mymail_domain.com",
|
18
|
+
+ "local_path": "/some/path/.imap-backup/mymail-old_domain.com",
|
19
|
+
"folders": [...],
|
20
|
+
"server": "..."
|
21
|
+
}
|
22
|
+
```
|
23
|
+
|
24
|
+
1. Rename the backup directory from `mymail_domain.com` to `mymail-old_domain.com`.
|
25
|
+
|
26
|
+
1. Set up a new account giving access to the new provider `B` using `imap-backup setup`.
|
27
|
+
1. Now you can use `imap-backup migrate`, optionnally adapting [delimiters and prefixes configuration](/docs/delimiters-and-prefixes.md) if need be:
|
28
|
+
|
29
|
+
imap-backup migrate mymail-old@domain.com mymail@domain.com [options]
|
@@ -14,6 +14,8 @@ module Imap::Backup
|
|
14
14
|
BODY_ATTRIBUTE = "BODY[]".freeze
|
15
15
|
UID_FETCH_RETRY_CLASSES = [EOFError, Errno::ECONNRESET, IOError].freeze
|
16
16
|
APPEND_RETRY_CLASSES = [Net::IMAP::BadResponseError].freeze
|
17
|
+
CREATE_RETRY_CLASSES = [Net::IMAP::BadResponseError].freeze
|
18
|
+
EXAMINE_RETRY_CLASSES = [Net::IMAP::BadResponseError].freeze
|
17
19
|
PERMITTED_FLAGS = %i(Answered Draft Flagged Seen).freeze
|
18
20
|
|
19
21
|
attr_reader :connection
|
@@ -33,7 +35,9 @@ module Imap::Backup
|
|
33
35
|
end
|
34
36
|
|
35
37
|
def exist?
|
36
|
-
|
38
|
+
retry_on_error(errors: EXAMINE_RETRY_CLASSES) do
|
39
|
+
examine
|
40
|
+
end
|
37
41
|
true
|
38
42
|
rescue FolderNotFound
|
39
43
|
false
|
@@ -42,7 +46,9 @@ module Imap::Backup
|
|
42
46
|
def create
|
43
47
|
return if exist?
|
44
48
|
|
45
|
-
|
49
|
+
retry_on_error(errors: CREATE_RETRY_CLASSES) do
|
50
|
+
client.create(utf7_encoded_name)
|
51
|
+
end
|
46
52
|
end
|
47
53
|
|
48
54
|
def uid_validity
|
@@ -20,6 +20,47 @@ module Imap::Backup
|
|
20
20
|
end
|
21
21
|
end
|
22
22
|
|
23
|
+
desc(
|
24
|
+
"check",
|
25
|
+
"Check the integrity of backups for all accounts (or the selected account(s))"
|
26
|
+
)
|
27
|
+
method_option(
|
28
|
+
"delete_corrupt",
|
29
|
+
type: :boolean,
|
30
|
+
desc: "deletes any corrupted folders - USE WITH CAUTION!"
|
31
|
+
)
|
32
|
+
config_option
|
33
|
+
format_option
|
34
|
+
def check
|
35
|
+
config = load_config(**options)
|
36
|
+
results = each_connection(config, emails).map do |connection|
|
37
|
+
folders = connection.local_folders
|
38
|
+
folder_results = folders.map do |serializer|
|
39
|
+
serializer.check_integrity!
|
40
|
+
{name: serializer.folder, result: "OK"}
|
41
|
+
rescue Serializer::FolderIntegrityError => e
|
42
|
+
message = e.to_s
|
43
|
+
if options[:delete_corrupt]
|
44
|
+
serializer.delete
|
45
|
+
message << " and has been deleted"
|
46
|
+
end
|
47
|
+
|
48
|
+
{
|
49
|
+
name: serializer.folder,
|
50
|
+
result: message
|
51
|
+
}
|
52
|
+
end
|
53
|
+
{account: connection.account.username, folders: folder_results}
|
54
|
+
end
|
55
|
+
|
56
|
+
case options[:format]
|
57
|
+
when "json"
|
58
|
+
print_check_results_as_json(results)
|
59
|
+
else
|
60
|
+
print_check_results_as_text(results)
|
61
|
+
end
|
62
|
+
end
|
63
|
+
|
23
64
|
desc "folders EMAIL", "List backed up folders"
|
24
65
|
config_option
|
25
66
|
format_option
|
@@ -87,6 +128,19 @@ module Imap::Backup
|
|
87
128
|
end
|
88
129
|
|
89
130
|
no_commands do
|
131
|
+
def print_check_results_as_json(results)
|
132
|
+
Kernel.puts results.to_json
|
133
|
+
end
|
134
|
+
|
135
|
+
def print_check_results_as_text(results)
|
136
|
+
results.each do |account_results|
|
137
|
+
Kernel.puts "Account: #{account_results[:account]}"
|
138
|
+
account_results[:folders].each do |folder_results|
|
139
|
+
Kernel.puts "\t#{folder_results[:name]}: #{folder_results[:result]}"
|
140
|
+
end
|
141
|
+
end
|
142
|
+
end
|
143
|
+
|
90
144
|
def list_emails_as_json(serializer)
|
91
145
|
emails = serializer.each_message.map do |message|
|
92
146
|
{
|
@@ -142,6 +196,10 @@ module Imap::Backup
|
|
142
196
|
Kernel.puts message.body
|
143
197
|
end
|
144
198
|
end
|
199
|
+
|
200
|
+
def emails
|
201
|
+
(options[:accounts] || "").split(",")
|
202
|
+
end
|
145
203
|
end
|
146
204
|
end
|
147
205
|
end
|
data/lib/imap/backup/cli.rb
CHANGED
@@ -22,6 +22,21 @@ module Imap::Backup
|
|
22
22
|
|
23
23
|
default_task :backup
|
24
24
|
|
25
|
+
def self.start(*args)
|
26
|
+
# By default, commands like `imap-backup help foo bar`
|
27
|
+
# are handled by listing all `foo` methods, whereas the user
|
28
|
+
# probably wants the detailed help for the `bar` method.
|
29
|
+
# Move initial "help" argument to after any subcommand,
|
30
|
+
# so we get help for the requested subcommand method.
|
31
|
+
first_argument_is_help = ARGV[0] == "help"
|
32
|
+
second_argument_is_subcommand = subcommands.include?(ARGV[1])
|
33
|
+
if first_argument_is_help && second_argument_is_subcommand
|
34
|
+
help, subcommand = ARGV.shift(2)
|
35
|
+
ARGV.unshift(subcommand, help)
|
36
|
+
end
|
37
|
+
super
|
38
|
+
end
|
39
|
+
|
25
40
|
def self.exit_on_failure?
|
26
41
|
true
|
27
42
|
end
|
@@ -15,6 +15,14 @@ module Imap::Backup
|
|
15
15
|
@version = nil
|
16
16
|
end
|
17
17
|
|
18
|
+
def pathname
|
19
|
+
"#{folder_path}.imap"
|
20
|
+
end
|
21
|
+
|
22
|
+
def exist?
|
23
|
+
File.exist?(pathname)
|
24
|
+
end
|
25
|
+
|
18
26
|
def valid?
|
19
27
|
return false if !exist?
|
20
28
|
return false if version != CURRENT_VERSION
|
@@ -114,14 +122,6 @@ module Imap::Backup
|
|
114
122
|
|
115
123
|
private
|
116
124
|
|
117
|
-
def pathname
|
118
|
-
"#{folder_path}.imap"
|
119
|
-
end
|
120
|
-
|
121
|
-
def exist?
|
122
|
-
File.exist?(pathname)
|
123
|
-
end
|
124
|
-
|
125
125
|
def ensure_loaded
|
126
126
|
return if loaded
|
127
127
|
|
@@ -19,6 +19,8 @@ module Imap::Backup
|
|
19
19
|
|
20
20
|
extend Forwardable
|
21
21
|
|
22
|
+
class FolderIntegrityError < StandardError; end
|
23
|
+
|
22
24
|
def_delegator :mbox, :pathname, :mbox_pathname
|
23
25
|
def_delegators :imap, :get, :messages, :uid_validity, :uids, :update_uid
|
24
26
|
|
@@ -42,6 +44,57 @@ module Imap::Backup
|
|
42
44
|
false
|
43
45
|
end
|
44
46
|
|
47
|
+
def check_integrity!
|
48
|
+
if !imap.valid?
|
49
|
+
message = ".imap file '#{imap.pathname}' is corrupt"
|
50
|
+
raise FolderIntegrityError, message
|
51
|
+
end
|
52
|
+
|
53
|
+
if !mbox.exist?
|
54
|
+
message = ".mbox file '#{mbox.pathname}' is missing"
|
55
|
+
raise FolderIntegrityError, message
|
56
|
+
end
|
57
|
+
|
58
|
+
return if imap.messages.empty?
|
59
|
+
|
60
|
+
offsets = imap.messages.map(&:offset)
|
61
|
+
|
62
|
+
if offsets != offsets.sort
|
63
|
+
message = ".imap file '#{imap.pathname}' has offset data which is out of order"
|
64
|
+
raise FolderIntegrityError, message
|
65
|
+
end
|
66
|
+
|
67
|
+
if mbox.length < offsets[-1]
|
68
|
+
message =
|
69
|
+
".imap file '#{imap.pathname}' has offsets past the end " \
|
70
|
+
"of .mbox file '#{mbox.pathname}'"
|
71
|
+
raise FolderIntegrityError, message
|
72
|
+
end
|
73
|
+
|
74
|
+
imap.messages.each do |m|
|
75
|
+
text = mbox.read(m.offset, m.length)
|
76
|
+
if text.length < m.length
|
77
|
+
message = "Message #{m.uid} is incomplete in file '#{mbox.pathname}'"
|
78
|
+
raise FolderIntegrityError, message
|
79
|
+
end
|
80
|
+
|
81
|
+
next if text.start_with?("From ")
|
82
|
+
|
83
|
+
message =
|
84
|
+
"Message #{m.uid} not found at expected offset #{m.offset} " \
|
85
|
+
"in file '#{mbox.pathname}'"
|
86
|
+
raise FolderIntegrityError, message
|
87
|
+
end
|
88
|
+
|
89
|
+
last = imap.messages.last
|
90
|
+
expected_length = last.offset + last.length
|
91
|
+
actual_length = mbox.length
|
92
|
+
return if actual_length == expected_length
|
93
|
+
|
94
|
+
message = "Mbox file '#{mbox.pathname}' contains unexpected trailing data"
|
95
|
+
raise FolderIntegrityError, message
|
96
|
+
end
|
97
|
+
|
45
98
|
def delete
|
46
99
|
imap.delete
|
47
100
|
@imap = nil
|
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: 9.
|
4
|
+
version: 9.3.1
|
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-05-12 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: highline
|
@@ -219,6 +219,7 @@ files:
|
|
219
219
|
- bin/imap-backup
|
220
220
|
- docs/delimiters-and-prefixes.md
|
221
221
|
- docs/development.md
|
222
|
+
- docs/migrate-server-keep-address.md
|
222
223
|
- imap-backup.gemspec
|
223
224
|
- lib/cli_coverage.rb
|
224
225
|
- lib/email/mboxrd/message.rb
|