imap-backup 12.0.0 → 13.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: 22cd34d9d0940821677b2007473b2608421dda521dd47f212346385eba4ea6de
4
- data.tar.gz: 3f249c818af7260fa15f8ed1509f2151876bddb28544f401c5264d9c74be1f74
3
+ metadata.gz: ff649779fd21a8a5ab2216b21618da22930c171dbe49fd2cf3aada02e3f2f18b
4
+ data.tar.gz: 7e0ac7d099b71a9404d183cbae3b43b0100ab81c80e69a0485447f7c392b3317
5
5
  SHA512:
6
- metadata.gz: 8773c5878b741fb116bfc617b79b91f536c10c2406668f6ad0f544f2383e9215d163d37b7e8beec6e237310b72455bac63561e3d03b171cb4b11ca37624d6851
7
- data.tar.gz: 3f6113d064ef19931eb32e1c71876e704ec3898f5657154fabe5626f13805ec1b209e3ff56ec2bd4c407f54cb22e0a590064f2ee9713b0dddfd5836c0371e1f9
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 deafult settings.
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
- When not set, each message is written to disk, one at a time.
126
- Doing so means the message itself is appended to the MBox file,
127
- but more importantly, the JSON metadata is rewritten to disk
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
- When in use, all of a mailboxes unbackupped messages are
131
- downloaded first, and then written to disk just once.
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
- This speeds up backup as the metadata file is not rewritten
134
- after each message is added, but it potentially uses much more memory.
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
- If you're not using "Delayed downlaod writes",
144
- using multi-fetch *will* mean that the backup process will use
145
- more memory - equivalent to the size of the greater number
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 setup for developing under any available Ruby version is
10
- available in the container directory.
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 a local IMAP server
17
- controlled by Docker Compose.
18
- Before running the test suite, it needs to be started:
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
- imap = Net::IMAP.new("localhost", {port: 8993, ssl: {verify_mode: 0}})
67
- username = "address@example.com"
68
- imap.login(username, "pass")
68
+ include EmailServerHelpers
69
69
 
70
- message = "From: #{username}\nSubject: Some Subject\n\nHello!\n"
71
- response = imap.append("INBOX", message, nil, nil)
70
+ test_connection = test_server_connection_parameters
72
71
 
73
- imap.examine("INBOX")
74
- uids = imap.uid_search(["ALL"]).sort
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
- fetch_data_items = imap.uid_fetch(uids, ["BODY[]"])
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
- # Older Ruby Versions
78
+ test_imap.examine("INBOX")
79
+ uids = test_imap.uid_search(["ALL"]).sort
80
80
 
81
- Dockerfiles are available for all the supported Ruby versions,
82
- see the `container` directory.
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.6"
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["COVERAGE"]
3
+ return if !ENV.key?("COVERAGE")
4
4
 
5
5
  require "simplecov"
6
6
 
7
7
  # Collect coverage separately
8
- SimpleCov.command_name "#{ENV['COVERAGE']} #{ARGV.join(' ')} coverage"
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 = destination_folder_for(name)
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 destination_folder_for(name)
59
- parts = name.split(source_delimiter)
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
@@ -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
- When one or other account has namespaces (i.e. prefixes like "INBOX"),
87
- use the `--source-prefix=` and/or `--destination-prefix=` options.
106
+ Some configuration may be necessary, as follows:
88
107
 
89
- When one or other account uses a delimiter other than `/` (i.e. `.`),
90
- use the `--source-delimiter=` and/or `--destination-delimiter=` options.
108
+ #{NAMESPACE_CONFIGURATION_DESCRIPTION}
91
109
 
92
- If you you want to delete existing emails in destination folders,
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
- Migrate.new(source_email, destination_email, **non_logging_options).run
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
- Mirror.new(source_email, destination_email, **non_logging_options).run
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"
@@ -20,7 +20,7 @@ module Imap::Backup
20
20
  parts = path.split("/")
21
21
  return if parts.empty?
22
22
 
23
- FileUtils.mkdir_p(full_path) if !File.exist?(full_path)
23
+ FileUtils.mkdir_p(full_path)
24
24
  full = base
25
25
  parts.each do |part|
26
26
  full = File.join(full, part)
@@ -86,7 +86,7 @@ module Imap::Backup
86
86
  def delete
87
87
  return if !exist?
88
88
 
89
- File.unlink(pathname)
89
+ FileUtils.rm(pathname)
90
90
  @loaded = false
91
91
  @messages = nil
92
92
  @uid_validity = nil
@@ -53,7 +53,7 @@ module Imap::Backup
53
53
  def delete
54
54
  return if !exist?
55
55
 
56
- File.unlink(pathname)
56
+ FileUtils.rm(pathname)
57
57
  end
58
58
 
59
59
  def exist?
@@ -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
- This will now be transformed into the version 3 format.
182
+ Migrating to the version 3 format...
183
183
  MESSAGE
184
184
 
185
185
  migrator.run
@@ -68,7 +68,7 @@ module Imap::Backup
68
68
 
69
69
  if force
70
70
  info "Deleting '#{local_folder.msf_path}' as --force option was supplied"
71
- File.unlink local_folder.msf_path
71
+ FileUtils.rm local_folder.msf_path
72
72
  return false
73
73
  end
74
74
 
@@ -1,7 +1,7 @@
1
1
  module Imap; end
2
2
 
3
3
  module Imap::Backup
4
- MAJOR = 12
4
+ MAJOR = 13
5
5
  MINOR = 0
6
6
  REVISION = 0
7
7
  PRE = nil
@@ -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: 12.0.0
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-19 00:00:00.000000000 Z
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.6'
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