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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: fdcbbac2458c28626fdf8be328db6f949be5cf981fde3438df03356b28849ebf
4
- data.tar.gz: 1ed5fc6db436f57c30e03a5036a61c2fa21853cd2d7ae08b861b491b04b6d12b
3
+ metadata.gz: 22cd34d9d0940821677b2007473b2608421dda521dd47f212346385eba4ea6de
4
+ data.tar.gz: 3f249c818af7260fa15f8ed1509f2151876bddb28544f401c5264d9c74be1f74
5
5
  SHA512:
6
- metadata.gz: a60997a5fdee3274d726e54c413c7b0e38df97246f4619358128a6786f5a148414d7050a6d498b805cb5376fca3d555f232e90c10d15d092ea49dce6007272e7
7
- data.tar.gz: 41b3038e096c34486670e423e6231cb4dd6eb8c39b035f8ce7580fb406bc6210ba8e436a51d51c61e67f9b5c3af3a971ad6ce016e1904850bdb14bc52e150f9c
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`), 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!
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
- 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).
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. 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:
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
- 1. Modify your configuration file manually (i.e. not via `imap-backup setup`) and rename your account to `mymail-old@domain.com`:
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
- ```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
- ```
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
- 1. Rename the backup directory from `mymail_domain.com` to `mymail-old_domain.com`.
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` 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:
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
- retry_on_error(errors: LOGIN_RETRY_CLASSES) do
28
- options = provider_options
29
- Logger.logger.debug(
30
- "Creating IMAP instance: #{server}, options: #{options.inspect}"
31
- )
32
- client =
33
- if provider.is_a?(Email::Provider::AppleMail)
34
- Client::AppleMail.new(server, account, options)
35
- else
36
- Client::Default.new(server, account, options)
37
- end
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,
@@ -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 emails that are yet to be downloaded "server",
215
- are downloaded (exist on server and locally) "both" and those which
216
- are only present in the backup (as they have been deleted on the server) "local".
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.length, flags: flags
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.length, flags: flags}
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
- if mbox.length < last.offset + last.length
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 > last.offset + last.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:, highline:)
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:, highline:)
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, highline: Setup.highline).run
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
@@ -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, highline: Setup.highline).run
79
+ GlobalOptions.new(config: config).run
80
80
  end
81
81
  end
82
82
 
@@ -1,8 +1,8 @@
1
1
  module Imap; end
2
2
 
3
3
  module Imap::Backup
4
- MAJOR = 11
5
- MINOR = 1
4
+ MAJOR = 12
5
+ MINOR = 0
6
6
  REVISION = 0
7
7
  PRE = nil
8
8
  VERSION = [MAJOR, MINOR, REVISION, PRE].compact.map(&:to_s).join(".")
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: 11.1.0
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-08-31 00:00:00.000000000 Z
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