imap-backup 7.0.0.rc1 → 7.0.2

Sign up to get free protection for your applications and to get access to all the features.
Files changed (35) hide show
  1. checksums.yaml +4 -4
  2. data/README.md +17 -21
  3. data/bin/imap-backup +0 -2
  4. data/docs/development.md +1 -1
  5. data/lib/email/mboxrd/message.rb +4 -3
  6. data/lib/imap/backup/account/connection.rb +16 -2
  7. data/lib/imap/backup/account/folder.rb +27 -12
  8. data/lib/imap/backup/account.rb +10 -1
  9. data/lib/imap/backup/cli/backup.rb +5 -1
  10. data/lib/imap/backup/cli/helpers.rb +22 -0
  11. data/lib/imap/backup/cli/local.rb +7 -7
  12. data/lib/imap/backup/cli/mirror.rb +118 -0
  13. data/lib/imap/backup/cli/utils.rb +6 -0
  14. data/lib/imap/backup/cli.rb +59 -0
  15. data/lib/imap/backup/configuration.rb +1 -17
  16. data/lib/imap/backup/flag_refresher.rb +32 -0
  17. data/lib/imap/backup/local_only_message_deleter.rb +23 -0
  18. data/lib/imap/backup/logger.rb +10 -5
  19. data/lib/imap/backup/migrator.rb +4 -4
  20. data/lib/imap/backup/mirror/map.rb +85 -0
  21. data/lib/imap/backup/mirror.rb +101 -0
  22. data/lib/imap/backup/serializer/appender.rb +4 -3
  23. data/lib/imap/backup/serializer/imap.rb +27 -32
  24. data/lib/imap/backup/serializer/message.rb +54 -0
  25. data/lib/imap/backup/serializer/message_enumerator.rb +2 -10
  26. data/lib/imap/backup/serializer/version2_migrator.rb +38 -15
  27. data/lib/imap/backup/serializer.rb +41 -27
  28. data/lib/imap/backup/setup/account/header.rb +9 -1
  29. data/lib/imap/backup/setup/account.rb +18 -1
  30. data/lib/imap/backup/setup.rb +0 -10
  31. data/lib/imap/backup/thunderbird/mailbox_exporter.rb +2 -4
  32. data/lib/imap/backup/uploader.rb +7 -7
  33. data/lib/imap/backup/version.rb +2 -2
  34. metadata +14 -9
  35. data/lib/imap/backup/serializer/mbox_enumerator.rb +0 -39
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: d858a14c5bbb9300fd99fe6338cc2ff83fd596071dc2db48ca792f45eee430a3
4
- data.tar.gz: c28f18cd459cd9c2c9a62fb5a55234615218bcff2c3c46e0422af9c6893e6043
3
+ metadata.gz: cb709e6431c47afc24c55d39ef7d9213fc0c27b42929c0692843a254cfd83c1d
4
+ data.tar.gz: 702d93af210afa826e64c0b5b1a4d1e16780c6078201e4a8db13dd4703ac62d3
5
5
  SHA512:
6
- metadata.gz: c669d9a170ec1d500e898324bbdc2072cc42597e9c2d30b6b284b7095151afecd0451c9fa7cb0adb93bbf46f1f2b1ae188554507141161acb6ba125349c1cee9
7
- data.tar.gz: c53ecb54ed6d33ab574caa18e62ae57e214e76975af65c72b11b8b2612d45487422be234c025966c792260d86d20e5431bf27845772e40d82cc8fea0132f9963
6
+ metadata.gz: 04a0c173fea0dd80e87443db040383402eb9eacbc7f557b4db8339cfd16c20153b068da8f4ade84d82643e3e9d3b9a76f04d654cbbc931620ef197546b24492a
7
+ data.tar.gz: e3cb1c12f73c421a66e41016b826ecdf3d199fd5920f9e5cbb04e0ef41619984791c8ce436283a91e6264707b04db7ecef94703f5df9af4c64cbcbb75c0fd7d7
data/README.md CHANGED
@@ -11,21 +11,15 @@ The backups can then be restored, used to migrate to another service,
11
11
  inspected or exported.
12
12
 
13
13
  * [Source Code]
14
- * [Documentation]
14
+ * [Code Documentation]
15
15
  * [Rubygem]
16
16
  * [CI Status]
17
17
 
18
18
  [Source Code]: https://github.com/joeyates/imap-backup "Source code at GitHub"
19
- [Documentation]: https://rubydoc.info/gems/imap-backup/frames "RDoc API Documentation at Rubydoc.info"
19
+ [Code Documentation]: https://rubydoc.info/gems/imap-backup/frames "Code Documentation at Rubydoc.info"
20
20
  [Rubygem]: https://rubygems.org/gems/imap-backup "Ruby gem at rubygems.org"
21
21
  [CI Status]: https://github.com/joeyates/imap-backup/actions/workflows/main.yml
22
22
 
23
- # Backup Emails
24
-
25
- imap-backup downloads emails and stores them on disk.
26
-
27
- The backup is incremental and interruptable, so backups won't get messed if your connection goes down during an operation.
28
-
29
23
  # Installation
30
24
 
31
25
  ## Homebrew (macOS)
@@ -63,7 +57,7 @@ imap-backup setup
63
57
 
64
58
  To use imap-backup with GMail, you will need to enable 'App passwords' on your account.
65
59
 
66
- # Run Backup
60
+ # Backup
67
61
 
68
62
  Manually, from the command line:
69
63
 
@@ -73,14 +67,22 @@ imap-backup
73
67
 
74
68
  Alternatively, add it to your crontab.
75
69
 
76
- Emails are stored on disk in [Mbox files](./docs/files/mboxrd.md) for each folder, with metadata
77
- stored in [Imap files](./docs/files/imap.md).
78
-
79
70
  # Commands
80
71
 
81
- * [folders](./commands/folders.md)
82
- * [restore](./commands/restore.md)
83
- * [status](./commands/status.md)
72
+ * [backup](docs/commands/backup.md)
73
+ * [folders](docs/commands/folders.md)
74
+ * [local accounts](docs/commands/local-accounts.md)
75
+ * [local folders](docs/commands/local-folders.md)
76
+ * [local list](docs/commands/local-list.md)
77
+ * [local show](docs/commands/local-show.md)
78
+ * [migrate](docs/commands/migrate.md)
79
+ * [mirror](docs/commands/mirror.md)
80
+ * [remote folders](docs/commands/remote-folders.md)
81
+ * [restore](docs/commands/restore.md)
82
+ * [setup](docs/commands/setup.md)
83
+ * [status](docs/commands/status.md)
84
+ * [utils export-to-thunderbird](docs/commands/utils-export-to-thunderbird.md)
85
+ * [utils ignore-history](docs/commands/utils-ignore-history.md)
84
86
 
85
87
  For a full list of available commands, run
86
88
 
@@ -94,12 +96,6 @@ For more information about a command, run
94
96
  imap-backup help COMMAND
95
97
  ```
96
98
 
97
- ## Configuration
98
-
99
- `imap-backup setup` creates the file `~/.imap-backup/config.json`.
100
-
101
- [More information about configuration is available in the specific documentation](./docs/configuration.md).
102
-
103
99
  # Troubleshooting
104
100
 
105
101
  If you have problems:
data/bin/imap-backup CHANGED
@@ -8,8 +8,6 @@ CliCoverage.conditionally_activate
8
8
  require "imap/backup/cli"
9
9
  require "imap/backup/logger"
10
10
 
11
- Imap::Backup::Logger.setup_logging
12
-
13
11
  Imap::Backup::Logger.sanitize_stderr do
14
12
  Imap::Backup::CLI.start(ARGV)
15
13
  end
data/docs/development.md CHANGED
@@ -44,7 +44,7 @@ use `last_command_started.output`.
44
44
  require "net/imap"
45
45
 
46
46
  imap = Net::IMAP.new("localhost", {port: 8993, ssl: {verify_mode: 0}})
47
- username = "address@example.org"
47
+ username = "address@example.com"
48
48
  imap.login(username, "pass")
49
49
 
50
50
  message = "From: #{username}\nSubject: Some Subject\n\nHello!\n"
@@ -5,9 +5,6 @@ module Email; end
5
5
 
6
6
  module Email::Mboxrd
7
7
  class Message
8
- extend Forwardable
9
- def_delegators :@parsed, :subject
10
-
11
8
  attr_reader :supplied_body
12
9
 
13
10
  def self.clean_serialized(serialized)
@@ -40,6 +37,10 @@ module Email::Mboxrd
40
37
  nil
41
38
  end
42
39
 
40
+ def subject
41
+ parsed.subject
42
+ end
43
+
43
44
  def imap_body
44
45
  supplied_body.gsub(/(?<!\r)\n/, "\r\n")
45
46
  end
@@ -3,6 +3,8 @@ require "imap/backup/client/default"
3
3
  require "imap/backup/account/connection/backup_folders"
4
4
  require "imap/backup/account/connection/client_factory"
5
5
  require "imap/backup/account/connection/folder_names"
6
+ require "imap/backup/flag_refresher"
7
+ require "imap/backup/local_only_message_deleter"
6
8
  require "imap/backup/serializer/directory"
7
9
 
8
10
  module Imap::Backup
@@ -33,11 +35,18 @@ module Imap::Backup
33
35
  end
34
36
  end
35
37
 
36
- def run_backup
37
- Logger.logger.debug "Running backup of account: #{account.username}"
38
+ def run_backup(refresh: false)
39
+ Logger.logger.info "Running backup of account: #{account.username}"
38
40
  # start the connection so we get logging messages in the right order
39
41
  client
40
42
  ensure_account_folder
43
+ if account.mirror_mode
44
+ # Delete serialized folders that are not to be backed up
45
+ wanted = backup_folders.map(&:name)
46
+ local_folders do |serializer, _folder|
47
+ serializer.delete if !wanted.include?(serializer.folder)
48
+ end
49
+ end
41
50
  each_folder do |folder, serializer|
42
51
  begin
43
52
  next if !folder.exist?
@@ -57,6 +66,11 @@ module Imap::Backup
57
66
  multi_fetch_size: account.multi_fetch_size,
58
67
  reset_seen_flags_after_fetch: account.reset_seen_flags_after_fetch
59
68
  ).run
69
+ if account.mirror_mode
70
+ Logger.logger.info "Mirror mode - Deleting messages only present locally"
71
+ LocalOnlyMessageDeleter.new(folder, serializer).run
72
+ end
73
+ FlagRefresher.new(folder, serializer).run if account.mirror_mode || refresh
60
74
  rescue Net::IMAP::ByeResponseError
61
75
  reconnect
62
76
  retry
@@ -58,22 +58,22 @@ module Imap::Backup
58
58
  rescue FolderNotFound
59
59
  []
60
60
  rescue NoMethodError
61
- message = <<~MESSAGE
62
- Folder '#{name}' caused NoMethodError
63
- probably
64
- `undefined method `[]' for nil:NilClass (NoMethodError)`
65
- in `search_internal` in stdlib net/imap.rb.
66
- This is caused by `@responses["SEARCH"] being unset/undefined
67
- MESSAGE
61
+ message =
62
+ "Folder '#{name}' caused a NoMethodError. " \
63
+ "Probably this was `undefined method `[]' for nil:NilClass (NoMethodError)` " \
64
+ "in `search_internal` in stdlib net/imap.rb. " \
65
+ 'This is caused by `@responses["SEARCH"] being unset/undefined. ' \
66
+ "Among others, Apple Mail servers send empty responses when " \
67
+ "folders are empty, causing this error."
68
68
  Logger.logger.warn message
69
69
  []
70
70
  end
71
71
 
72
- def fetch_multi(uids)
72
+ def fetch_multi(uids, attr = [BODY_ATTRIBUTE, "FLAGS"])
73
73
  examine
74
74
  fetch_data_items =
75
75
  retry_on_error(errors: UID_FETCH_RETRY_CLASSES) do
76
- client.uid_fetch(uids, [BODY_ATTRIBUTE, "FLAGS"])
76
+ client.uid_fetch(uids, attr)
77
77
  end
78
78
  return nil if fetch_data_items.nil?
79
79
 
@@ -90,18 +90,30 @@ module Imap::Backup
90
90
  nil
91
91
  end
92
92
 
93
- def append(message, flags: nil)
93
+ def append(message)
94
94
  body = message.imap_body
95
95
  date = message.date&.to_time
96
96
  retry_on_error(errors: APPEND_RETRY_CLASSES, limit: 3) do
97
- response = client.append(utf7_encoded_name, body, flags, date)
97
+ response = client.append(utf7_encoded_name, body, message.flags, date)
98
98
  extract_uid(response)
99
99
  end
100
100
  end
101
101
 
102
+ def delete_multi(uids)
103
+ set_flags(uids, [:Deleted])
104
+ client.expunge
105
+ end
106
+
107
+ def apply_flags(uids, flags)
108
+ client.select(utf7_encoded_name)
109
+ flags.reject! { |f| f == :Recent }
110
+ client.uid_store(uids, "FLAGS", flags)
111
+ end
112
+
102
113
  def set_flags(uids, flags)
103
114
  # Use read-write access, via `select`
104
115
  client.select(utf7_encoded_name)
116
+ flags.reject! { |f| f == :Recent }
105
117
  client.uid_store(uids, "+FLAGS", flags)
106
118
  end
107
119
 
@@ -111,7 +123,10 @@ module Imap::Backup
111
123
  end
112
124
 
113
125
  def clear
114
- set_flags(uids, [:Deleted])
126
+ existing = uids
127
+ return if existing.empty?
128
+
129
+ set_flags(existing, [:Deleted])
115
130
  client.expunge
116
131
  end
117
132
 
@@ -1,3 +1,5 @@
1
+ module Imap; end
2
+
1
3
  module Imap::Backup
2
4
  class Account
3
5
  DEFAULT_MULTI_FETCH_SIZE = 1
@@ -6,6 +8,7 @@ module Imap::Backup
6
8
  attr_reader :password
7
9
  attr_reader :local_path
8
10
  attr_reader :folders
11
+ attr_reader :mirror_mode
9
12
  attr_reader :server
10
13
  attr_reader :connection_options
11
14
  attr_reader :reset_seen_flags_after_fetch
@@ -16,6 +19,7 @@ module Imap::Backup
16
19
  @password = options[:password]
17
20
  @local_path = options[:local_path]
18
21
  @folders = options[:folders]
22
+ @mirror_mode = options[:mirror_mode]
19
23
  @server = options[:server]
20
24
  @connection_options = options[:connection_options]
21
25
  @multi_fetch_size = options[:multi_fetch_size]
@@ -53,6 +57,7 @@ module Imap::Backup
53
57
  h = {username: @username, password: @password}
54
58
  h[:local_path] = @local_path if @local_path
55
59
  h[:folders] = @folders if @folders
60
+ h[:mirror_mode] = true if @mirror_mode
56
61
  h[:server] = @server if @server
57
62
  h[:connection_options] = @connection_options if @connection_options
58
63
  h[:multi_fetch_size] = multi_fetch_size if @multi_fetch_size
@@ -80,12 +85,16 @@ module Imap::Backup
80
85
  update(:folders, value)
81
86
  end
82
87
 
88
+ def mirror_mode=(value)
89
+ update(:mirror_mode, value)
90
+ end
91
+
83
92
  def server=(value)
84
93
  update(:server, value)
85
94
  end
86
95
 
87
96
  def connection_options=(value)
88
- parsed = JSON.parse(value)
97
+ parsed = JSON.parse(value, symbolize_names: true)
89
98
  update(:connection_options, parsed)
90
99
  end
91
100
 
@@ -4,15 +4,19 @@ module Imap::Backup
4
4
  include CLI::Helpers
5
5
 
6
6
  attr_reader :account_names
7
+ attr_reader :refresh
7
8
 
8
9
  def initialize(options)
9
10
  super([])
10
11
  @account_names = (options[:accounts] || "").split(",")
12
+ @refresh = options.key?(:refresh) ? !!options[:refresh] : false
11
13
  end
12
14
 
13
15
  no_commands do
14
16
  def run
15
- each_connection(account_names, &:run_backup)
17
+ each_connection(account_names) do |connection|
18
+ connection.run_backup(refresh: refresh)
19
+ end
16
20
  end
17
21
  end
18
22
  end
@@ -3,6 +3,28 @@ require "imap/backup/cli/accounts"
3
3
 
4
4
  module Imap::Backup
5
5
  module CLI::Helpers
6
+ def self.included(base)
7
+ base.class_eval do
8
+ def self.verbose_option
9
+ method_option(
10
+ "verbose",
11
+ type: :boolean,
12
+ desc: "increase the amount of logging",
13
+ aliases: ["-v"]
14
+ )
15
+ end
16
+
17
+ def self.quiet_option
18
+ method_option(
19
+ "quiet",
20
+ type: :boolean,
21
+ desc: "silence all output",
22
+ aliases: ["-q"]
23
+ )
24
+ end
25
+ end
26
+ end
27
+
6
28
  def symbolized(options)
7
29
  options.each.with_object({}) do |(k, v), acc|
8
30
  key = k.gsub("-", "_").intern
@@ -39,8 +39,8 @@ module Imap::Backup
39
39
 
40
40
  uids = folder_serializer.uids
41
41
 
42
- folder_serializer.each_message(uids).map do |uid, message|
43
- list_message uid, message
42
+ folder_serializer.each_message(uids).map do |message|
43
+ list_message message
44
44
  end
45
45
  end
46
46
 
@@ -59,22 +59,22 @@ module Imap::Backup
59
59
  raise "Folder '#{folder_name}' not found" if !folder_serializer
60
60
 
61
61
  uid_list = uids.split(",")
62
- folder_serializer.each_message(uid_list).each do |uid, message|
62
+ folder_serializer.each_message(uid_list).each do |message|
63
63
  if uid_list.count > 1
64
64
  Kernel.puts <<~HEADER
65
65
  #{'-' * 80}
66
- #{format('| UID: %-71s |', uid)}
66
+ #{format('| UID: %-71s |', message.uid)}
67
67
  #{'-' * 80}
68
68
  HEADER
69
69
  end
70
- Kernel.puts message.supplied_body
70
+ Kernel.puts message.body
71
71
  end
72
72
  end
73
73
 
74
74
  no_commands do
75
- def list_message(uid, message)
75
+ def list_message(message)
76
76
  m = {
77
- uid: uid,
77
+ uid: message.uid,
78
78
  date: message.date.to_s,
79
79
  subject: message.subject || ""
80
80
  }
@@ -0,0 +1,118 @@
1
+ require "imap/backup/mirror"
2
+
3
+ module Imap::Backup
4
+ class CLI::Mirror < Thor
5
+ include Thor::Actions
6
+
7
+ attr_reader :destination_email
8
+ attr_reader :destination_prefix
9
+ attr_reader :source_email
10
+ attr_reader :source_prefix
11
+
12
+ def initialize(
13
+ source_email,
14
+ destination_email,
15
+ destination_prefix: "",
16
+ source_prefix: ""
17
+ )
18
+ super([])
19
+ @destination_email = destination_email
20
+ @destination_prefix = destination_prefix
21
+ @source_email = source_email
22
+ @source_prefix = source_prefix
23
+ end
24
+
25
+ no_commands do
26
+ def run
27
+ check_accounts!
28
+ warn_if_source_account_is_not_in_mirror_mode
29
+
30
+ CLI::Backup.new(accounts: source_email).run
31
+
32
+ folders.each do |serializer, folder|
33
+ Mirror.new(serializer, folder).run
34
+ end
35
+ end
36
+
37
+ def check_accounts!
38
+ if destination_email == source_email
39
+ raise "Source and destination accounts cannot be the same!"
40
+ end
41
+
42
+ raise "Account '#{destination_email}' does not exist" if !destination_account
43
+
44
+ raise "Account '#{source_email}' does not exist" if !source_account
45
+ end
46
+
47
+ def warn_if_source_account_is_not_in_mirror_mode
48
+ return if source_account.mirror_mode
49
+
50
+ message =
51
+ "The account '#{source_account.username}' " \
52
+ "is not set up to make mirror backups"
53
+ Logger.logger.info message
54
+ end
55
+
56
+ def config
57
+ Configuration.new
58
+ end
59
+
60
+ def destination_account
61
+ config.accounts.find { |a| a.username == destination_email }
62
+ end
63
+
64
+ def folders
65
+ return enum_for(:folders) if !block_given?
66
+
67
+ glob = File.join(source_local_path, "**", "*.imap")
68
+ Pathname.glob(glob) do |path|
69
+ name = source_folder_name(path)
70
+ serializer = Serializer.new(source_local_path, name)
71
+ folder = folder_for(name)
72
+ yield serializer, folder
73
+ end
74
+ end
75
+
76
+ def folder_for(source_folder)
77
+ no_source_prefix =
78
+ if source_prefix != "" && source_folder.start_with?(source_prefix)
79
+ source_folder.delete_prefix(source_prefix)
80
+ else
81
+ source_folder.to_s
82
+ end
83
+
84
+ with_destination_prefix =
85
+ if destination_prefix && destination_prefix != ""
86
+ destination_prefix + no_source_prefix
87
+ else
88
+ no_source_prefix
89
+ end
90
+
91
+ Account::Folder.new(
92
+ destination_account.connection,
93
+ with_destination_prefix
94
+ )
95
+ end
96
+
97
+ def source_local_path
98
+ source_account.local_path
99
+ end
100
+
101
+ def source_account
102
+ config.accounts.find { |a| a.username == source_email }
103
+ end
104
+
105
+ def source_folder_name(imap_pathname)
106
+ base = Pathname.new(source_local_path)
107
+ imap_name = imap_pathname.relative_path_from(base).to_s
108
+ dir = File.dirname(imap_name)
109
+ stripped = File.basename(imap_name, ".imap")
110
+ if dir == "."
111
+ stripped
112
+ else
113
+ File.join(dir, stripped)
114
+ end
115
+ end
116
+ end
117
+ end
118
+ end
@@ -8,7 +8,10 @@ module Imap::Backup
8
8
  FAKE_EMAIL = "fake@email.com".freeze
9
9
 
10
10
  desc "ignore-history EMAIL", "Skip downloading emails up to today for all configured folders"
11
+ verbose_option
12
+ quiet_option
11
13
  def ignore_history(email)
14
+ Imap::Backup::Logger.setup_logging symbolized(options)
12
15
  connection = connection(email)
13
16
 
14
17
  connection.backup_folders.each do |folder|
@@ -26,6 +29,8 @@ module Imap::Backup
26
29
  A folder called 'imap-backup/EMAIL' is created under 'Local Folders'.
27
30
  DOC
28
31
  )
32
+ verbose_option
33
+ quiet_option
29
34
  method_option(
30
35
  "force",
31
36
  type: :boolean,
@@ -40,6 +45,7 @@ module Imap::Backup
40
45
  )
41
46
  def export_to_thunderbird(email)
42
47
  opts = symbolized(options)
48
+ Imap::Backup::Logger.setup_logging opts
43
49
  force = opts.key?(:force) ? opts[:force] : false
44
50
  profile_name = opts[:profile]
45
51
 
@@ -1,4 +1,5 @@
1
1
  require "thor"
2
+ require "imap/backup/logger"
2
3
 
3
4
  module Imap; end
4
5
 
@@ -10,6 +11,7 @@ module Imap::Backup
10
11
  autoload :Folders, "imap/backup/cli/folders"
11
12
  autoload :Local, "imap/backup/cli/local"
12
13
  autoload :Migrate, "imap/backup/cli/migrate"
14
+ autoload :Mirror, "imap/backup/cli/mirror"
13
15
  autoload :Remote, "imap/backup/cli/remote"
14
16
  autoload :Restore, "imap/backup/cli/restore"
15
17
  autoload :Setup, "imap/backup/cli/setup"
@@ -43,7 +45,16 @@ module Imap::Backup
43
45
  The setup tool can be used to choose a specific list of folders to back up.
44
46
  DESC
45
47
  accounts_option
48
+ verbose_option
49
+ quiet_option
50
+ method_option(
51
+ "refresh",
52
+ type: :boolean,
53
+ desc: "in 'keep all emails' mode, update flags for messages that are already downloaded",
54
+ aliases: ["-r"]
55
+ )
46
56
  def backup
57
+ Imap::Backup::Logger.setup_logging symbolized(options)
47
58
  Backup.new(symbolized(options)).run
48
59
  end
49
60
 
@@ -85,6 +96,8 @@ module Imap::Backup
85
96
  use the `--reset` option. In this case, all existing emails are
86
97
  deleted before uploading the migrated emails.
87
98
  DESC
99
+ verbose_option
100
+ quiet_option
88
101
  method_option(
89
102
  "destination-prefix",
90
103
  type: :string,
@@ -104,9 +117,49 @@ module Imap::Backup
104
117
  aliases: ["-s"]
105
118
  )
106
119
  def migrate(source_email, destination_email)
120
+ Imap::Backup::Logger.setup_logging symbolized(options)
107
121
  Migrate.new(source_email, destination_email, **symbolized(options)).run
108
122
  end
109
123
 
124
+ desc(
125
+ "mirror SOURCE_EMAIL DESTINATION_EMAIL [OPTIONS]",
126
+ "Keeps the DESTINATION_EMAIL account aligned with the SOURCE_EMAIL account"
127
+ )
128
+ long_desc <<~DESC
129
+ This command updates the DESTINATION_EMAIL account's folders to have the same contents
130
+ as those on the SOURCE_EMAIL account.
131
+
132
+ If a folder list is configured for the SOURCE_EMAIL account,
133
+ only the folders indicated by the setting are copied.
134
+
135
+ First, runs the download of the SOURCE_EMAIL account.
136
+ If the SOURCE_EMAIL account is **not** configured to be in 'mirror' mode,
137
+ a warning is printed.
138
+
139
+ When the mirror command is used, for each folder that is processed,
140
+ a new file is created alongside the normal backup files (.imap and .mbox)
141
+ This file has a '.mirror' extension. This file contains a mapping of
142
+ the known UIDs on the source account to those on the destination account.
143
+ DESC
144
+ verbose_option
145
+ quiet_option
146
+ method_option(
147
+ "destination-prefix",
148
+ type: :string,
149
+ desc: "the prefix (namespace) to add to destination folder names",
150
+ aliases: ["-d"]
151
+ )
152
+ method_option(
153
+ "source-prefix",
154
+ type: :string,
155
+ desc: "the prefix (namespace) to strip from source folder names",
156
+ aliases: ["-s"]
157
+ )
158
+ def mirror(source_email, destination_email)
159
+ Imap::Backup::Logger.setup_logging symbolized(options)
160
+ Mirror.new(source_email, destination_email, **symbolized(options)).run
161
+ end
162
+
110
163
  desc "remote SUBCOMMAND [OPTIONS]", "View info about online accounts"
111
164
  subcommand "remote", Remote
112
165
 
@@ -116,7 +169,10 @@ module Imap::Backup
116
169
  their original server.
117
170
  DESC
118
171
  accounts_option
172
+ verbose_option
173
+ quiet_option
119
174
  def restore(email = nil)
175
+ Imap::Backup::Logger.setup_logging symbolized(options)
120
176
  Restore.new(email, symbolized(options)).run
121
177
  end
122
178
 
@@ -125,7 +181,10 @@ module Imap::Backup
125
181
  A menu-driven command-line application used to configure imap-backup.
126
182
  Configure email accounts to back up.
127
183
  DESC
184
+ verbose_option
185
+ quiet_option
128
186
  def setup
187
+ Imap::Backup::Logger.setup_logging symbolized(options)
129
188
  Setup.new.run
130
189
  end
131
190
 
@@ -20,8 +20,6 @@ module Imap::Backup
20
20
 
21
21
  def initialize(pathname = self.class.default_pathname)
22
22
  @pathname = pathname
23
- @saved_debug = nil
24
- @debug = nil
25
23
  end
26
24
 
27
25
  def path
@@ -36,8 +34,7 @@ module Imap::Backup
36
34
  remove_deleted_accounts
37
35
  save_data = {
38
36
  version: VERSION,
39
- accounts: accounts.map(&:to_h),
40
- debug: debug?
37
+ accounts: accounts.map(&:to_h)
41
38
  }
42
39
  File.open(pathname, "w") { |f| f.write(JSON.pretty_generate(save_data)) }
43
40
  FileUtils.chmod(0o600, pathname) if !windows?
@@ -53,29 +50,16 @@ module Imap::Backup
53
50
 
54
51
  def modified?
55
52
  ensure_loaded!
56
- return true if @saved_debug != @debug
57
53
 
58
54
  accounts.any? { |a| a.modified? || a.marked_for_deletion? }
59
55
  end
60
56
 
61
- def debug?
62
- ensure_loaded!
63
- @debug
64
- end
65
-
66
- def debug=(value)
67
- ensure_loaded!
68
- @debug = [true, false].include?(value) ? value : false
69
- end
70
-
71
57
  private
72
58
 
73
59
  def ensure_loaded!
74
60
  return true if @data
75
61
 
76
62
  data
77
- @debug = data.key?(:debug) ? data[:debug] == true : false
78
- @saved_debug = @debug
79
63
  true
80
64
  end
81
65