imap-backup 4.0.6 → 4.2.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (52) hide show
  1. checksums.yaml +4 -4
  2. data/bin/imap-backup +5 -2
  3. data/lib/email/mboxrd/message.rb +6 -2
  4. data/lib/imap/backup/account/connection.rb +53 -33
  5. data/lib/imap/backup/account/folder.rb +22 -2
  6. data/lib/imap/backup/account.rb +4 -0
  7. data/lib/imap/backup/cli/accounts.rb +43 -0
  8. data/lib/imap/backup/cli/folders.rb +3 -1
  9. data/lib/imap/backup/cli/helpers.rb +8 -9
  10. data/lib/imap/backup/cli/local.rb +4 -2
  11. data/lib/imap/backup/cli/setup.rb +1 -1
  12. data/lib/imap/backup/cli/status.rb +1 -1
  13. data/lib/imap/backup/cli/utils.rb +3 -2
  14. data/lib/imap/backup/{configuration/store.rb → configuration.rb} +16 -3
  15. data/lib/imap/backup/downloader.rb +26 -12
  16. data/lib/imap/backup/logger.rb +42 -0
  17. data/lib/imap/backup/sanitizer.rb +42 -0
  18. data/lib/imap/backup/serializer/mbox_store.rb +2 -2
  19. data/lib/imap/backup/{configuration → setup}/account.rb +29 -19
  20. data/lib/imap/backup/{configuration → setup}/asker.rb +5 -5
  21. data/lib/imap/backup/setup/connection_tester.rb +26 -0
  22. data/lib/imap/backup/{configuration → setup}/folder_chooser.rb +18 -8
  23. data/lib/imap/backup/setup/helpers.rb +15 -0
  24. data/lib/imap/backup/{configuration/setup.rb → setup.rb} +23 -17
  25. data/lib/imap/backup/thunderbird/mailbox_exporter.rb +10 -1
  26. data/lib/imap/backup/uploader.rb +2 -2
  27. data/lib/imap/backup/version.rb +2 -2
  28. data/lib/imap/backup.rb +7 -33
  29. data/lib/retry_on_error.rb +1 -1
  30. data/lib/thunderbird/subdirectory.rb +3 -6
  31. data/spec/features/backup_spec.rb +1 -0
  32. data/spec/features/status_spec.rb +43 -0
  33. data/spec/features/support/email_server.rb +5 -2
  34. data/spec/support/higline_test_helpers.rb +1 -1
  35. data/spec/support/silence_logging.rb +1 -1
  36. data/spec/unit/imap/backup/account/connection_spec.rb +14 -9
  37. data/spec/unit/imap/backup/cli/accounts_spec.rb +47 -0
  38. data/spec/unit/imap/backup/cli/local_spec.rb +7 -3
  39. data/spec/unit/imap/backup/cli/utils_spec.rb +15 -5
  40. data/spec/unit/imap/backup/{configuration/store_spec.rb → configuration_spec.rb} +2 -2
  41. data/spec/unit/imap/backup/downloader_spec.rb +1 -1
  42. data/spec/unit/imap/backup/logger_spec.rb +48 -0
  43. data/spec/unit/imap/backup/{configuration → setup}/account_spec.rb +31 -24
  44. data/spec/unit/imap/backup/{configuration → setup}/asker_spec.rb +2 -2
  45. data/spec/unit/imap/backup/{configuration → setup}/connection_tester_spec.rb +10 -10
  46. data/spec/unit/imap/backup/{configuration → setup}/folder_chooser_spec.rb +8 -8
  47. data/spec/unit/imap/backup/{configuration/setup_spec.rb → setup_spec.rb} +48 -40
  48. metadata +54 -49
  49. data/lib/imap/backup/configuration/connection_tester.rb +0 -14
  50. data/lib/imap/backup/configuration/list.rb +0 -53
  51. data/spec/unit/imap/backup/configuration/list_spec.rb +0 -89
  52. data/spec/unit/imap/backup_spec.rb +0 -28
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 7b543b8f8b913b95b68c9ad9532143c375d90b468b0977982faa3522c0d3932b
4
- data.tar.gz: '0939b42c8dad41d14212a18444d94c209ce025646683faded89b9808e6181416'
3
+ metadata.gz: e0cd29188ed972abfb2b2ce21d237ce3b7aed5b434717ba64fa2e3475da38ded
4
+ data.tar.gz: 597b58cdee1ab1b5881ea29111ac85e7fdedfbaa370824d0d24ba1b19c869dcb
5
5
  SHA512:
6
- metadata.gz: 52f9058554faa9a3d31bef3cb7254408032cdddee98293ea5f7c8ef108d3187b564b7fd71a18bdb1b31829b46ed22bdd51626704f82e98d88076bd4cec887d44
7
- data.tar.gz: 186036e2f11ed8e0ae578ab44d73b2b4c1f89bf9c65eedc66a143b4d1b861adacba1d954190d566c399cc79184ba3645ebb53a3341ee82a03ad9b5d5d7fa777d
6
+ metadata.gz: a3adc1984eea76a5ef993a40e89520a01997e6d01357e0a1e41cd4b58a4efc56801fc3d4f8801b95e828ec3a6a1e7884be974aaabf40f921a3faccdc99cf9b98
7
+ data.tar.gz: 55c0c91fdfb4829ba77be5292b4e475c6ea31e9e6e9ea5abf9702121f8a12c20b9b0105da5e1b97bcb2482105807c614530dea9e058d64af2cde96f7fd19348b
data/bin/imap-backup CHANGED
@@ -2,7 +2,10 @@
2
2
 
3
3
  $LOAD_PATH.unshift(File.expand_path("../lib/", __dir__))
4
4
  require "imap/backup/cli"
5
+ require "imap/backup/logger"
5
6
 
6
- Imap::Backup::Configuration::List.new.setup_logging
7
+ Imap::Backup::Logger.setup_logging
7
8
 
8
- Imap::Backup::CLI.start(ARGV)
9
+ Imap::Backup::Logger.sanitize_stderr do
10
+ Imap::Backup::CLI.start(ARGV)
11
+ end
@@ -10,7 +10,7 @@ module Email::Mboxrd
10
10
 
11
11
  attr_reader :supplied_body
12
12
 
13
- def self.from_serialized(serialized)
13
+ def self.clean_serialized(serialized)
14
14
  cleaned = serialized.gsub(/^>(>*From)/, "\\1")
15
15
  # Serialized messages in this format *should* start with a line
16
16
  # From xxx yy zz
@@ -19,7 +19,11 @@ module Email::Mboxrd
19
19
  cleaned = cleaned.sub(/^From .*[\r\n]*/, "")
20
20
  end
21
21
  # rubocop:enable Style/IfUnlessModifier
22
- new(cleaned)
22
+ cleaned
23
+ end
24
+
25
+ def self.from_serialized(serialized)
26
+ new(clean_serialized(serialized))
23
27
  end
24
28
 
25
29
  def initialize(supplied_body)
@@ -15,44 +15,63 @@ module Imap::Backup
15
15
 
16
16
  def initialize(account)
17
17
  @account = account
18
- @folders = nil
18
+ reset
19
19
  create_account_folder
20
20
  end
21
21
 
22
- def folders
23
- @folders ||=
22
+ # TODO: Make this private once the 'folders' command
23
+ # has been removed.
24
+ def folder_names
25
+ @folder_names ||=
24
26
  begin
25
- folders = client.list
27
+ folder_names = client.list
26
28
 
27
- if folders.empty?
29
+ if folder_names.empty?
28
30
  message = "Unable to get folder list for account #{account.username}"
29
- Imap::Backup.logger.info message
31
+ Imap::Backup::Logger.logger.info message
30
32
  raise message
31
33
  end
32
34
 
33
- folders
35
+ folder_names
36
+ end
37
+ end
38
+
39
+ def backup_folders
40
+ @backup_folders ||=
41
+ begin
42
+ names =
43
+ if account.folders&.any?
44
+ account.folders.map { |af| af[:name] }
45
+ else
46
+ folder_names
47
+ end
48
+
49
+ names.map do |name|
50
+ Account::Folder.new(self, name)
51
+ end
34
52
  end
35
53
  end
36
54
 
37
55
  def status
38
- backup_folders.map do |backup_folder|
39
- f = Account::Folder.new(self, backup_folder[:name])
40
- s = Serializer::Mbox.new(account.local_path, backup_folder[:name])
41
- {name: backup_folder[:name], local: s.uids, remote: f.uids}
56
+ backup_folders.map do |folder|
57
+ s = Serializer::Mbox.new(account.local_path, folder.name)
58
+ {name: folder.name, local: s.uids, remote: folder.uids}
42
59
  end
43
60
  end
44
61
 
45
62
  def run_backup
46
- Imap::Backup.logger.debug "Running backup of account: #{account.username}"
63
+ Imap::Backup::Logger.logger.debug "Running backup of account: #{account.username}"
47
64
  # start the connection so we get logging messages in the right order
48
65
  client
49
66
  each_folder do |folder, serializer|
50
67
  next if !folder.exist?
51
68
 
52
- Imap::Backup.logger.debug "[#{folder.name}] running backup"
69
+ Imap::Backup::Logger.logger.debug "[#{folder.name}] running backup"
53
70
  serializer.apply_uid_validity(folder.uid_validity)
54
71
  begin
55
- Downloader.new(folder, serializer).run
72
+ Downloader.new(
73
+ folder, serializer, block_size: config.download_block_size
74
+ ).run
56
75
  rescue Net::IMAP::ByeResponseError
57
76
  reconnect
58
77
  retry
@@ -81,18 +100,27 @@ module Imap::Backup
81
100
 
82
101
  def disconnect
83
102
  client.disconnect if @client
103
+ reset
84
104
  end
85
105
 
86
106
  def reconnect
87
107
  disconnect
108
+ end
109
+
110
+ def reset
111
+ @backup_folders = nil
88
112
  @client = nil
113
+ @config = nil
114
+ @folder_names = nil
115
+ @provider = nil
116
+ @server = nil
89
117
  end
90
118
 
91
119
  def client
92
120
  @client ||=
93
121
  retry_on_error(errors: LOGIN_RETRY_CLASSES) do
94
122
  options = provider_options
95
- Imap::Backup.logger.debug(
123
+ Imap::Backup::Logger.logger.debug(
96
124
  "Creating IMAP instance: #{server}, options: #{options.inspect}"
97
125
  )
98
126
  client =
@@ -101,9 +129,9 @@ module Imap::Backup
101
129
  else
102
130
  Client::Default.new(server, options)
103
131
  end
104
- Imap::Backup.logger.debug "Logging in: #{account.username}/#{masked_password}"
132
+ Imap::Backup::Logger.logger.debug "Logging in: #{account.username}/#{masked_password}"
105
133
  client.login(account.username, account.password)
106
- Imap::Backup.logger.debug "Login complete"
134
+ Imap::Backup::Logger.logger.debug "Login complete"
107
135
  client
108
136
  end
109
137
  end
@@ -115,9 +143,8 @@ module Imap::Backup
115
143
  private
116
144
 
117
145
  def each_folder
118
- backup_folders.each do |backup_folder|
119
- folder = Account::Folder.new(self, backup_folder[:name])
120
- serializer = Serializer::Mbox.new(account.local_path, backup_folder[:name])
146
+ backup_folders.each do |folder|
147
+ serializer = Serializer::Mbox.new(account.local_path, folder.name)
121
148
  yield folder, serializer
122
149
  end
123
150
  end
@@ -125,13 +152,13 @@ module Imap::Backup
125
152
  def restore_folder(serializer, folder)
126
153
  existing_uids = folder.uids
127
154
  if existing_uids.any?
128
- Imap::Backup.logger.debug(
155
+ Imap::Backup::Logger.logger.debug(
129
156
  "There's already a '#{folder.name}' folder with emails"
130
157
  )
131
158
  new_name = serializer.apply_uid_validity(folder.uid_validity)
132
159
  old_name = serializer.folder
133
160
  if new_name
134
- Imap::Backup.logger.debug(
161
+ Imap::Backup::Logger.logger.debug(
135
162
  "Backup '#{old_name}' renamed and restored to '#{new_name}'"
136
163
  )
137
164
  new_serializer = Serializer::Mbox.new(account.local_path, new_name)
@@ -161,17 +188,6 @@ module Imap::Backup
161
188
  account.password.gsub(/./, "x")
162
189
  end
163
190
 
164
- def backup_folders
165
- @backup_folders ||=
166
- begin
167
- if account.folders&.any?
168
- account.folders
169
- else
170
- folders.map { |name| {name: name} }
171
- end
172
- end
173
- end
174
-
175
191
  def provider
176
192
  @provider ||= Email::Provider.for_address(account.username)
177
193
  end
@@ -179,5 +195,9 @@ module Imap::Backup
179
195
  def provider_options
180
196
  provider.options.merge(account.connection_options || {})
181
197
  end
198
+
199
+ def config
200
+ @config ||= Configuration.new
201
+ end
182
202
  end
183
203
  end
@@ -64,7 +64,7 @@ module Imap::Backup
64
64
  in `search_internal` in stdlib net/imap.rb.
65
65
  This is caused by `@responses["SEARCH"] being unset/undefined
66
66
  MESSAGE
67
- Imap::Backup.logger.warn message
67
+ Imap::Backup::Logger.logger.warn message
68
68
  []
69
69
  end
70
70
 
@@ -84,6 +84,26 @@ module Imap::Backup
84
84
  nil
85
85
  end
86
86
 
87
+ def fetch_multi(uids)
88
+ examine
89
+ fetch_data_items =
90
+ retry_on_error(errors: UID_FETCH_RETRY_CLASSES) do
91
+ client.uid_fetch(uids, [BODY_ATTRIBUTE])
92
+ end
93
+ return nil if fetch_data_items.nil?
94
+
95
+ fetch_data_items.map do |item|
96
+ attributes = item.attr
97
+
98
+ {
99
+ uid: attributes["UID"],
100
+ body: attributes[BODY_ATTRIBUTE]
101
+ }
102
+ end
103
+ rescue FolderNotFound
104
+ nil
105
+ end
106
+
87
107
  def append(message)
88
108
  body = message.imap_body
89
109
  date = message.date&.to_time
@@ -96,7 +116,7 @@ module Imap::Backup
96
116
  def examine
97
117
  client.examine(utf7_encoded_name)
98
118
  rescue Net::IMAP::NoResponseError
99
- Imap::Backup.logger.warn "Folder '#{name}' does not exist on server"
119
+ Imap::Backup::Logger.logger.warn "Folder '#{name}' does not exist on server"
100
120
  raise FolderNotFound, "Folder '#{name}' does not exist on server"
101
121
  end
102
122
 
@@ -20,6 +20,10 @@ module Imap::Backup
20
20
  @marked_for_deletion = false
21
21
  end
22
22
 
23
+ def connection
24
+ Account::Connection.new(self)
25
+ end
26
+
23
27
  def valid?
24
28
  username && password
25
29
  end
@@ -0,0 +1,43 @@
1
+ module Imap::Backup
2
+ class CLI; end
3
+
4
+ class CLI::Accounts
5
+ include Enumerable
6
+
7
+ attr_reader :required_accounts
8
+
9
+ def initialize(required_accounts = [])
10
+ @required_accounts = required_accounts
11
+ end
12
+
13
+ def each(&block)
14
+ return enum_for(:each) if !block
15
+
16
+ accounts.each(&block)
17
+ end
18
+
19
+ private
20
+
21
+ def accounts
22
+ @accounts ||=
23
+ if required_accounts.empty?
24
+ config.accounts
25
+ else
26
+ config.accounts.select do |account|
27
+ required_accounts.include?(account.username)
28
+ end
29
+ end
30
+ end
31
+
32
+ def config
33
+ @config ||= begin
34
+ exists = Configuration.exist?
35
+ if !exists
36
+ path = Configuration.default_pathname
37
+ raise ConfigurationNotFound, "Configuration file '#{path}' not found"
38
+ end
39
+ Configuration.new
40
+ end
41
+ end
42
+ end
43
+ end
@@ -14,7 +14,9 @@ module Imap::Backup
14
14
  def run
15
15
  each_connection(account_names) do |connection|
16
16
  puts connection.username
17
- folders = connection.folders
17
+ # TODO: Make folder_names private once this command
18
+ # has been removed.
19
+ folders = connection.folder_names
18
20
  if folders.nil?
19
21
  warn "Unable to list account folders"
20
22
  return false
@@ -1,4 +1,5 @@
1
1
  require "imap/backup"
2
+ require "imap/backup/cli/accounts"
2
3
 
3
4
  module Imap::Backup::CLI::Helpers
4
5
  def symbolized(options)
@@ -6,8 +7,8 @@ module Imap::Backup::CLI::Helpers
6
7
  end
7
8
 
8
9
  def account(email)
9
- connections = Imap::Backup::Configuration::List.new
10
- account = connections.accounts.find { |a| a.username == email }
10
+ accounts = Imap::Backup::CLI::Accounts.new
11
+ account = accounts.find { |a| a.username == email }
11
12
  raise "#{email} is not a configured account" if !account
12
13
 
13
14
  account
@@ -20,14 +21,12 @@ module Imap::Backup::CLI::Helpers
20
21
  end
21
22
 
22
23
  def each_connection(names)
23
- begin
24
- connections = Imap::Backup::Configuration::List.new(names)
25
- rescue Imap::Backup::ConfigurationNotFound
26
- raise "imap-backup is not configured. Run `imap-backup setup`"
27
- end
24
+ accounts = Imap::Backup::CLI::Accounts.new(names)
28
25
 
29
- connections.each_connection do |connection|
30
- yield connection
26
+ accounts.each do |account|
27
+ yield account.connection
31
28
  end
29
+ rescue Imap::Backup::ConfigurationNotFound
30
+ raise "imap-backup is not configured. Run `imap-backup setup`"
32
31
  end
33
32
  end
@@ -1,3 +1,5 @@
1
+ require "imap/backup/cli/accounts"
2
+
1
3
  module Imap::Backup
2
4
  class CLI::Local < Thor
3
5
  include Thor::Actions
@@ -5,8 +7,8 @@ module Imap::Backup
5
7
 
6
8
  desc "accounts", "List locally backed-up accounts"
7
9
  def accounts
8
- connections = Imap::Backup::Configuration::List.new
9
- connections.accounts.each { |a| Kernel.puts a.username }
10
+ accounts = CLI::Accounts.new
11
+ accounts.each { |a| Kernel.puts a.username }
10
12
  end
11
13
 
12
14
  desc "folders EMAIL", "List account folders"
@@ -7,7 +7,7 @@ class Imap::Backup::CLI::Setup < Thor
7
7
 
8
8
  no_commands do
9
9
  def run
10
- Imap::Backup::Configuration::Setup.new.run
10
+ Imap::Backup::Setup.new.run
11
11
  end
12
12
  end
13
13
  end
@@ -13,7 +13,7 @@ module Imap::Backup
13
13
  no_commands do
14
14
  def run
15
15
  each_connection(account_names) do |connection|
16
- puts connection.username
16
+ puts connection.account.username
17
17
  folders = connection.status
18
18
  folders.each do |f|
19
19
  missing_locally = f[:remote] - f[:local]
@@ -11,9 +11,10 @@ module Imap::Backup
11
11
  def ignore_history(email)
12
12
  connection = connection(email)
13
13
 
14
- connection.local_folders.each do |serializer, folder|
14
+ connection.backup_folders.each do |folder|
15
15
  next if !folder.exist?
16
16
 
17
+ serializer = Serializer::Mbox.new(connection.account.local_path, folder.name)
17
18
  do_ignore_folder_history(folder, serializer)
18
19
  end
19
20
  end
@@ -63,7 +64,7 @@ module Imap::Backup
63
64
  no_commands do
64
65
  def do_ignore_folder_history(folder, serializer)
65
66
  uids = folder.uids - serializer.uids
66
- Imap::Backup.logger.info "Folder '#{folder.name}' - #{uids.length} messages"
67
+ Imap::Backup::Logger.logger.info "Folder '#{folder.name}' - #{uids.length} messages"
67
68
 
68
69
  serializer.apply_uid_validity(folder.uid_validity)
69
70
 
@@ -4,10 +4,9 @@ require "os"
4
4
  require "imap/backup/account"
5
5
 
6
6
  module Imap::Backup
7
- module Configuration; end
8
-
9
- class Configuration::Store
7
+ class Configuration
10
8
  CONFIGURATION_DIRECTORY = File.expand_path("~/.imap-backup")
9
+ DEFAULT_DOWNLOAD_BLOCK_SIZE = 1
11
10
  VERSION = "2.0"
12
11
 
13
12
  attr_reader :pathname
@@ -22,6 +21,7 @@ module Imap::Backup
22
21
 
23
22
  def initialize(pathname = self.class.default_pathname)
24
23
  @pathname = pathname
24
+ @saved_debug = nil
25
25
  @debug = nil
26
26
  end
27
27
 
@@ -42,6 +42,7 @@ module Imap::Backup
42
42
  }
43
43
  File.open(pathname, "w") { |f| f.write(JSON.pretty_generate(save_data)) }
44
44
  FileUtils.chmod(0o600, pathname) if !windows?
45
+ @data = nil
45
46
  end
46
47
 
47
48
  def accounts
@@ -51,8 +52,19 @@ module Imap::Backup
51
52
  end
52
53
  end
53
54
 
55
+ def download_block_size
56
+ size = ENV["DOWNLOAD_BLOCK_SIZE"].to_i
57
+ if size > 0
58
+ size
59
+ else
60
+ DEFAULT_DOWNLOAD_BLOCK_SIZE
61
+ end
62
+ end
63
+
54
64
  def modified?
55
65
  ensure_loaded!
66
+ return true if @saved_debug != @debug
67
+
56
68
  accounts.any? { |a| a.modified? || a.marked_for_deletion? }
57
69
  end
58
70
 
@@ -73,6 +85,7 @@ module Imap::Backup
73
85
 
74
86
  data
75
87
  @debug = data.key?(:debug) ? data[:debug] == true : false
88
+ @saved_debug = @debug
76
89
  true
77
90
  end
78
91
 
@@ -2,27 +2,41 @@ module Imap::Backup
2
2
  class Downloader
3
3
  attr_reader :folder
4
4
  attr_reader :serializer
5
+ attr_reader :block_size
5
6
 
6
- def initialize(folder, serializer)
7
+ def initialize(folder, serializer, block_size: 1)
7
8
  @folder = folder
8
9
  @serializer = serializer
10
+ @block_size = block_size
9
11
  end
10
12
 
11
13
  def run
12
14
  uids = folder.uids - serializer.uids
13
15
  count = uids.count
14
- Imap::Backup.logger.debug "[#{folder.name}] #{count} new messages"
15
- uids.each.with_index do |uid, i|
16
- body = folder.fetch(uid)
17
- log_prefix = "[#{folder.name}] uid: #{uid} (#{i + 1}/#{count}) -"
18
- if body.nil?
19
- Imap::Backup.logger.debug("#{log_prefix} not available - skipped")
20
- next
16
+ Imap::Backup::Logger.logger.debug "[#{folder.name}] #{count} new messages"
17
+ uids.each_slice(block_size).with_index do |block, i|
18
+ offset = i * block_size + 1
19
+ uids_and_bodies = folder.fetch_multi(block)
20
+ if uids_and_bodies.nil?
21
+ if block_size > 1
22
+ Imap::Backup::Logger.logger.debug("[#{folder.name}] Multi fetch failed for UIDs #{block.join(", ")}, switching to single fetches")
23
+ @block_size = 1
24
+ redo
25
+ else
26
+ Imap::Backup::Logger.logger.debug("[#{folder.name}] Fetch failed for UID #{block[0]} - skipping")
27
+ next
28
+ end
29
+ end
30
+
31
+ uids_and_bodies.each.with_index do |uid_and_body, j|
32
+ uid = uid_and_body[:uid]
33
+ body = uid_and_body[:body]
34
+ Imap::Backup::Logger.logger.debug(
35
+ "[#{folder.name}] uid: #{uid} (#{offset +j}/#{count}) - " \
36
+ "#{body.size} bytes"
37
+ )
38
+ serializer.save(uid, body)
21
39
  end
22
- Imap::Backup.logger.debug(
23
- "#{log_prefix} #{body.size} bytes"
24
- )
25
- serializer.save(uid, body)
26
40
  end
27
41
  end
28
42
  end
@@ -0,0 +1,42 @@
1
+ require "logger"
2
+ require "singleton"
3
+
4
+ require "imap/backup/configuration"
5
+ require "imap/backup/sanitizer"
6
+
7
+ module Imap::Backup
8
+ class Logger
9
+ include Singleton
10
+
11
+ def self.logger
12
+ Logger.instance.logger
13
+ end
14
+
15
+ def self.setup_logging(config = Configuration.new)
16
+ logger.level =
17
+ if config.debug?
18
+ ::Logger::Severity::DEBUG
19
+ else
20
+ ::Logger::Severity::ERROR
21
+ end
22
+ Net::IMAP.debug = config.debug?
23
+ end
24
+
25
+ def self.sanitize_stderr
26
+ sanitizer = Sanitizer.new($stdout)
27
+ previous_stderr = $stderr
28
+ $stderr = sanitizer
29
+ yield
30
+ ensure
31
+ sanitizer.flush
32
+ $stderr = previous_stderr
33
+ end
34
+
35
+ attr_reader :logger
36
+
37
+ def initialize
38
+ @logger = ::Logger.new($stdout)
39
+ $stdout.sync = true
40
+ end
41
+ end
42
+ end
@@ -0,0 +1,42 @@
1
+ module Imap::Backup
2
+ class Sanitizer
3
+ attr_reader :output
4
+
5
+ def initialize(output)
6
+ @output = output
7
+ @current = ""
8
+ end
9
+
10
+ def write(*args)
11
+ output.write(*args)
12
+ end
13
+
14
+ def print(*args)
15
+ @current << args.join
16
+ loop do
17
+ line, newline, rest = @current.partition("\n")
18
+ break if newline != "\n"
19
+ clean = sanitize(line)
20
+ output.puts clean
21
+ @current = rest
22
+ end
23
+ end
24
+
25
+ def flush
26
+ return if @current == ""
27
+
28
+ clean = sanitize(@current)
29
+ output.puts clean
30
+ end
31
+
32
+ private
33
+
34
+ def sanitize(t)
35
+ # Hide password in Net::IMAP debug output
36
+ t.gsub(
37
+ /\A(C: RUBY\d+ LOGIN \S+) \S+/,
38
+ "\\1 [PASSWORD REDACTED]"
39
+ )
40
+ end
41
+ end
42
+ end
@@ -46,7 +46,7 @@ module Imap::Backup
46
46
 
47
47
  uid = uid.to_i
48
48
  if uids.include?(uid)
49
- Imap::Backup.logger.debug(
49
+ Imap::Backup::Logger.logger.debug(
50
50
  "[#{folder}] message #{uid} already downloaded - skipping"
51
51
  )
52
52
  return
@@ -65,7 +65,7 @@ module Imap::Backup
65
65
  #{body}. #{e}:
66
66
  #{e.backtrace.join("\n")}"
67
67
  ERROR
68
- Imap::Backup.logger.warn message
68
+ Imap::Backup::Logger.logger.warn message
69
69
  ensure
70
70
  mbox&.close
71
71
  end