imap-backup 12.0.0 → 13.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: 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