imap-backup 11.1.0.rc1 → 12.0.0

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: fce713a5edcf2115c1f8636cfe7b6521aae0403a9bb79dfaf05b484363dbb7ad
4
- data.tar.gz: 68544128dba3ade90077683c14d454596a514368cb40a46094934ec94220a606
3
+ metadata.gz: 22cd34d9d0940821677b2007473b2608421dda521dd47f212346385eba4ea6de
4
+ data.tar.gz: 3f249c818af7260fa15f8ed1509f2151876bddb28544f401c5264d9c74be1f74
5
5
  SHA512:
6
- metadata.gz: f5b467457df7840d8cf69cc4ace9758cfc2b38242acdb3f4c75a9dd0327c996995aa0986f649725c57e8b058ad0be4d386d9760720a20d505c3154e2a82aa59c
7
- data.tar.gz: 3dd8a7f189e0d313e487b63fd64d7c57a647d384360e4128f2787cb7673f1db35a05ffb4853603e7bafbe17537a1b309a43a825180a910e45abc202630283eb9
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
@@ -19,9 +19,6 @@ module Imap::Backup
19
19
  ].freeze
20
20
 
21
21
  attr_reader :pathname
22
- attr_reader :download_strategy
23
- attr_reader :download_strategy_original
24
- attr_reader :download_strategy_modified
25
22
 
26
23
  def self.default_pathname
27
24
  File.join(CONFIGURATION_DIRECTORY, "config.json")
@@ -68,16 +65,28 @@ module Imap::Backup
68
65
  end
69
66
  end
70
67
 
68
+ def download_strategy
69
+ ensure_loaded!
70
+
71
+ @download_strategy
72
+ end
73
+
71
74
  def download_strategy=(value)
72
75
  raise "Unknown strategy '#{value}'" if !DOWNLOAD_STRATEGIES.find { |s| s[:key] == value }
73
76
 
74
77
  ensure_loaded!
75
78
 
76
79
  @download_strategy = value
77
- @download_strategy_modified = value != download_strategy_original
80
+ @download_strategy_modified = value != @download_strategy_original
78
81
  inject_global_attributes(accounts)
79
82
  end
80
83
 
84
+ def download_strategy_modified
85
+ ensure_loaded!
86
+
87
+ @download_strategy_modified
88
+ end
89
+
81
90
  def modified?
82
91
  ensure_loaded!
83
92
 
@@ -114,7 +123,7 @@ module Imap::Backup
114
123
  end
115
124
  data
116
125
  else
117
- {accounts: []}
126
+ {accounts: [], download_strategy: DEFAULT_STRATEGY}
118
127
  end
119
128
  end
120
129
 
@@ -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,9 +1,9 @@
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
- PRE = "rc1".freeze
7
+ PRE = nil
8
8
  VERSION = [MAJOR, MINOR, REVISION, PRE].compact.map(&:to_s).join(".")
9
9
  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: 11.1.0.rc1
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-12 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
@@ -312,9 +313,9 @@ required_ruby_version: !ruby/object:Gem::Requirement
312
313
  version: '2.6'
313
314
  required_rubygems_version: !ruby/object:Gem::Requirement
314
315
  requirements:
315
- - - ">"
316
+ - - ">="
316
317
  - !ruby/object:Gem::Version
317
- version: 1.3.1
318
+ version: '0'
318
319
  requirements: []
319
320
  rubygems_version: 3.3.7
320
321
  signing_key: