imap-backup 6.0.0.rc2 → 6.1.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.
Files changed (112) hide show
  1. checksums.yaml +4 -4
  2. data/imap-backup.gemspec +5 -1
  3. data/lib/cli_coverage.rb +11 -11
  4. data/lib/email/provider/apple_mail.rb +4 -0
  5. data/lib/email/provider/base.rb +6 -0
  6. data/lib/email/provider/purelymail.rb +11 -0
  7. data/lib/email/provider/unknown.rb +2 -0
  8. data/lib/email/provider.rb +5 -0
  9. data/lib/imap/backup/account/connection/backup_folders.rb +27 -0
  10. data/lib/imap/backup/account/connection/client_factory.rb +55 -0
  11. data/lib/imap/backup/account/connection/folder_names.rb +26 -0
  12. data/lib/imap/backup/account/connection.rb +16 -96
  13. data/lib/imap/backup/account/folder.rb +31 -9
  14. data/lib/imap/backup/account.rb +15 -6
  15. data/lib/imap/backup/cli/backup.rb +1 -3
  16. data/lib/imap/backup/cli/helpers.rb +24 -22
  17. data/lib/imap/backup/cli/local.rb +20 -13
  18. data/lib/imap/backup/cli/migrate.rb +4 -10
  19. data/lib/imap/backup/cli/restore.rb +8 -7
  20. data/lib/imap/backup/cli/setup.rb +10 -8
  21. data/lib/imap/backup/cli/stats.rb +78 -0
  22. data/lib/imap/backup/cli/status.rb +2 -2
  23. data/lib/imap/backup/cli/utils.rb +4 -6
  24. data/lib/imap/backup/cli.rb +24 -3
  25. data/lib/imap/backup/configuration.rb +9 -11
  26. data/lib/imap/backup/downloader.rb +75 -31
  27. data/lib/imap/backup/migrator.rb +5 -5
  28. data/lib/imap/backup/sanitizer.rb +3 -2
  29. data/lib/imap/backup/serializer/appender.rb +49 -0
  30. data/lib/imap/backup/serializer/imap.rb +27 -3
  31. data/lib/imap/backup/serializer/mbox.rb +18 -2
  32. data/lib/imap/backup/serializer/message_enumerator.rb +29 -0
  33. data/lib/imap/backup/serializer/unused_name_finder.rb +25 -0
  34. data/lib/imap/backup/serializer.rb +64 -84
  35. data/lib/imap/backup/setup/account/header.rb +81 -0
  36. data/lib/imap/backup/setup/account.rb +28 -91
  37. data/lib/imap/backup/setup/asker.rb +4 -15
  38. data/lib/imap/backup/setup/backup_path.rb +45 -0
  39. data/lib/imap/backup/setup/email.rb +45 -0
  40. data/lib/imap/backup/setup/folder_chooser.rb +3 -3
  41. data/lib/imap/backup/setup/helpers.rb +1 -1
  42. data/lib/imap/backup/setup.rb +7 -6
  43. data/lib/imap/backup/thunderbird/mailbox_exporter.rb +39 -20
  44. data/lib/imap/backup/uploader.rb +46 -8
  45. data/lib/imap/backup/utils.rb +1 -1
  46. data/lib/imap/backup/version.rb +2 -2
  47. data/lib/imap/backup.rb +0 -1
  48. metadata +32 -134
  49. data/spec/features/backup_spec.rb +0 -100
  50. data/spec/features/configuration/minimal_configuration.rb +0 -15
  51. data/spec/features/configuration/missing_configuration.rb +0 -14
  52. data/spec/features/folders_spec.rb +0 -36
  53. data/spec/features/helper.rb +0 -2
  54. data/spec/features/local/list_accounts_spec.rb +0 -12
  55. data/spec/features/local/list_emails_spec.rb +0 -21
  56. data/spec/features/local/list_folders_spec.rb +0 -21
  57. data/spec/features/local/show_an_email_spec.rb +0 -34
  58. data/spec/features/migrate_spec.rb +0 -35
  59. data/spec/features/remote/list_account_folders_spec.rb +0 -16
  60. data/spec/features/restore_spec.rb +0 -162
  61. data/spec/features/status_spec.rb +0 -43
  62. data/spec/features/support/aruba.rb +0 -78
  63. data/spec/features/support/backup_directory.rb +0 -43
  64. data/spec/features/support/email_server.rb +0 -110
  65. data/spec/features/support/shared/connection_context.rb +0 -14
  66. data/spec/features/support/shared/message_fixtures.rb +0 -16
  67. data/spec/fixtures/connection.yml +0 -7
  68. data/spec/spec_helper.rb +0 -15
  69. data/spec/support/fixtures.rb +0 -11
  70. data/spec/support/higline_test_helpers.rb +0 -8
  71. data/spec/support/silence_logging.rb +0 -7
  72. data/spec/unit/email/mboxrd/message_spec.rb +0 -177
  73. data/spec/unit/email/provider/apple_mail_spec.rb +0 -7
  74. data/spec/unit/email/provider/base_spec.rb +0 -11
  75. data/spec/unit/email/provider/fastmail_spec.rb +0 -7
  76. data/spec/unit/email/provider/gmail_spec.rb +0 -7
  77. data/spec/unit/email/provider_spec.rb +0 -27
  78. data/spec/unit/imap/backup/account/connection_spec.rb +0 -433
  79. data/spec/unit/imap/backup/account/folder_spec.rb +0 -261
  80. data/spec/unit/imap/backup/account_spec.rb +0 -246
  81. data/spec/unit/imap/backup/cli/accounts_spec.rb +0 -58
  82. data/spec/unit/imap/backup/cli/backup_spec.rb +0 -19
  83. data/spec/unit/imap/backup/cli/folders_spec.rb +0 -39
  84. data/spec/unit/imap/backup/cli/helpers_spec.rb +0 -87
  85. data/spec/unit/imap/backup/cli/local_spec.rb +0 -100
  86. data/spec/unit/imap/backup/cli/migrate_spec.rb +0 -80
  87. data/spec/unit/imap/backup/cli/restore_spec.rb +0 -67
  88. data/spec/unit/imap/backup/cli/setup_spec.rb +0 -17
  89. data/spec/unit/imap/backup/cli/utils_spec.rb +0 -125
  90. data/spec/unit/imap/backup/cli_spec.rb +0 -93
  91. data/spec/unit/imap/backup/client/apple_mail_spec.rb +0 -9
  92. data/spec/unit/imap/backup/client/default_spec.rb +0 -22
  93. data/spec/unit/imap/backup/configuration_spec.rb +0 -238
  94. data/spec/unit/imap/backup/downloader_spec.rb +0 -96
  95. data/spec/unit/imap/backup/logger_spec.rb +0 -48
  96. data/spec/unit/imap/backup/migrator_spec.rb +0 -58
  97. data/spec/unit/imap/backup/sanitizer_spec.rb +0 -42
  98. data/spec/unit/imap/backup/serializer/directory_spec.rb +0 -37
  99. data/spec/unit/imap/backup/serializer/imap_spec.rb +0 -218
  100. data/spec/unit/imap/backup/serializer/mbox_enumerator_spec.rb +0 -45
  101. data/spec/unit/imap/backup/serializer/mbox_spec.rb +0 -101
  102. data/spec/unit/imap/backup/serializer_spec.rb +0 -296
  103. data/spec/unit/imap/backup/setup/account_spec.rb +0 -461
  104. data/spec/unit/imap/backup/setup/asker_spec.rb +0 -137
  105. data/spec/unit/imap/backup/setup/connection_tester_spec.rb +0 -51
  106. data/spec/unit/imap/backup/setup/folder_chooser_spec.rb +0 -146
  107. data/spec/unit/imap/backup/setup/helpers_spec.rb +0 -15
  108. data/spec/unit/imap/backup/setup_spec.rb +0 -301
  109. data/spec/unit/imap/backup/thunderbird/mailbox_exporter_spec.rb +0 -116
  110. data/spec/unit/imap/backup/uploader_spec.rb +0 -54
  111. data/spec/unit/imap/backup/utils_spec.rb +0 -92
  112. data/spec/unit/retry_on_error_spec.rb +0 -34
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 4f9cac7966fac9858fdc380d8e9638c8d479035ee4053444a104b8c3eb5a304c
4
- data.tar.gz: 5d9785b5e34d90535c8052d216d9b3dbf46ea743672f05129604729662f42f34
3
+ metadata.gz: 2a05fedc57275cadd2a1bd22efbe88ef5ec6ee72843400499cac8b8df8e80c35
4
+ data.tar.gz: f0e8e859b5fb1bb8480730b3ea62abc7470f5d0ddb469113bbdac0e4f2a88d1a
5
5
  SHA512:
6
- metadata.gz: a4595e4316dcdc20123a8ed2bec524d37c1de95da49281a30484349c317a996cf3c6946617a489880a0c41db41698b2ee862d9aee4f9c9b8fa34d67707ba924c
7
- data.tar.gz: ccee268de9cb6225c72fe0cb11a2552fc793f664429d8dfa918e5b4697961c8e5e15e47ed653dac241f377385806443f17683614b8ff76075a8ff2f12e07f98c
6
+ metadata.gz: bfb4de1a3048a9f29ee30fb56c7493524a7d688a6eab2ec3307005ba3c8d341a99c961acd1d8f588384dc91571b4528482931e52724df42dff19a26553568fb0
7
+ data.tar.gz: a411ad26b4ce3eb631c97d94c9242c1eb48fd57d23cffcec84f5c829d9354b506115b6c8278ee38430e27f0d6ca1400d55f74adce5eb99f94db2963f235c6d7a
data/imap-backup.gemspec CHANGED
@@ -18,7 +18,6 @@ Gem::Specification.new do |gem|
18
18
  gem.files += %w[LICENSE README.md]
19
19
 
20
20
  gem.executables = gem.files.grep(%r{^bin/}).map { |f| File.basename(f) }
21
- gem.test_files = Dir.glob("spec/**/*{.rb,.yml}")
22
21
  gem.require_paths = ["lib"]
23
22
  gem.required_ruby_version = ">= 2.5"
24
23
 
@@ -34,4 +33,9 @@ Gem::Specification.new do |gem|
34
33
  gem.add_development_dependency "rspec", ">= 3.0.0"
35
34
  gem.add_development_dependency "rubocop-rspec"
36
35
  gem.add_development_dependency "simplecov"
36
+ gem.add_development_dependency "yard"
37
+
38
+ gem.metadata = {
39
+ "rubygems_mfa_required" => "true"
40
+ }
37
41
  end
data/lib/cli_coverage.rb CHANGED
@@ -1,18 +1,18 @@
1
1
  class CliCoverage
2
2
  def self.conditionally_activate
3
- if ENV["COVERAGE"]
4
- require "simplecov"
3
+ return if !ENV["COVERAGE"]
5
4
 
6
- # Collect coverage separately
7
- SimpleCov.command_name "#{ENV['COVERAGE']} #{ARGV.join(' ')} coverage"
5
+ require "simplecov"
8
6
 
9
- # Silence output
10
- SimpleCov.formatter = SimpleCov::Formatter::SimpleFormatter
11
- SimpleCov.print_error_status = false
7
+ # Collect coverage separately
8
+ SimpleCov.command_name "#{ENV['COVERAGE']} #{ARGV.join(' ')} coverage"
12
9
 
13
- # Ensure SimpleCov doesn't filter out all out code
14
- project_root = File.expand_path("..", __dir__)
15
- SimpleCov.root project_root
16
- end
10
+ # Silence output
11
+ SimpleCov.formatter = SimpleCov::Formatter::SimpleFormatter
12
+ SimpleCov.print_error_status = false
13
+
14
+ # Ensure SimpleCov doesn't filter out all out code
15
+ project_root = File.expand_path("..", __dir__)
16
+ SimpleCov.root project_root
17
17
  end
18
18
  end
@@ -4,4 +4,8 @@ class Email::Provider::AppleMail < Email::Provider::Base
4
4
  def host
5
5
  "imap.mail.me.com"
6
6
  end
7
+
8
+ def sets_seen_flags_on_fetch?
9
+ true
10
+ end
7
11
  end
@@ -3,6 +3,12 @@ class Email::Provider; end
3
3
 
4
4
  class Email::Provider::Base
5
5
  def options
6
+ # rubocop:disable Naming/VariableNumber
6
7
  {port: 993, ssl: {ssl_version: :TLSv1_2}}
8
+ # rubocop:enable Naming/VariableNumber
9
+ end
10
+
11
+ def sets_seen_flags_on_fetch?
12
+ false
7
13
  end
8
14
  end
@@ -0,0 +1,11 @@
1
+ require "email/provider/base"
2
+
3
+ class Email::Provider::Purelymail < Email::Provider::Base
4
+ def host
5
+ "mailserver.purelymail.com"
6
+ end
7
+
8
+ def sets_seen_flags_on_fetch?
9
+ true
10
+ end
11
+ end
@@ -6,6 +6,8 @@ class Email::Provider::Unknown < Email::Provider::Base
6
6
  end
7
7
 
8
8
  def options
9
+ # rubocop:disable Naming/VariableNumber
9
10
  {port: 993, ssl: {ssl_version: :TLSv1_2}}
11
+ # rubocop:enable Naming/VariableNumber
10
12
  end
11
13
  end
@@ -1,12 +1,14 @@
1
1
  require "email/provider/apple_mail"
2
2
  require "email/provider/fastmail"
3
3
  require "email/provider/gmail"
4
+ require "email/provider/purelymail"
4
5
  require "email/provider/unknown"
5
6
 
6
7
  module Email; end
7
8
 
8
9
  class Email::Provider
9
10
  def self.for_address(address)
11
+ # rubocop:disable Lint/DuplicateBranch
10
12
  case
11
13
  when address.end_with?("@fastmail.com")
12
14
  Email::Provider::Fastmail.new
@@ -20,8 +22,11 @@ class Email::Provider
20
22
  Email::Provider::AppleMail.new
21
23
  when address.end_with?("@me.com")
22
24
  Email::Provider::AppleMail.new
25
+ when address.end_with?("@purelymail.com")
26
+ Email::Provider::Purelymail.new
23
27
  else
24
28
  Email::Provider::Unknown.new
25
29
  end
30
+ # rubocop:enable Lint/DuplicateBranch
26
31
  end
27
32
  end
@@ -0,0 +1,27 @@
1
+ module Imap::Backup
2
+ class Account; end
3
+ class Account::Connection; end
4
+
5
+ class Account::Connection::BackupFolders
6
+ attr_reader :account
7
+ attr_reader :client
8
+
9
+ def initialize(client:, account:)
10
+ @client = client
11
+ @account = account
12
+ end
13
+
14
+ def run
15
+ names =
16
+ if account.folders&.any?
17
+ account.folders.map { |af| af[:name] }
18
+ else
19
+ Account::Connection::FolderNames.new(client: client, account: account).run
20
+ end
21
+
22
+ names.map do |name|
23
+ Account::Folder.new(account.connection, name)
24
+ end
25
+ end
26
+ end
27
+ end
@@ -0,0 +1,55 @@
1
+ require "email/provider"
2
+ require "retry_on_error"
3
+
4
+ module Imap::Backup
5
+ class Account::Connection::ClientFactory
6
+ include RetryOnError
7
+
8
+ LOGIN_RETRY_CLASSES = [EOFError, Errno::ECONNRESET, SocketError].freeze
9
+
10
+ attr_reader :account
11
+
12
+ def initialize(account:)
13
+ @account = account
14
+ @provider = nil
15
+ @server = nil
16
+ end
17
+
18
+ def run
19
+ retry_on_error(errors: LOGIN_RETRY_CLASSES) do
20
+ options = provider_options
21
+ Logger.logger.debug(
22
+ "Creating IMAP instance: #{server}, options: #{options.inspect}"
23
+ )
24
+ client =
25
+ if provider.is_a?(Email::Provider::AppleMail)
26
+ Client::AppleMail.new(server, options)
27
+ else
28
+ Client::Default.new(server, options)
29
+ end
30
+ Logger.logger.debug "Logging in: #{account.username}/#{masked_password}"
31
+ client.login(account.username, account.password)
32
+ Logger.logger.debug "Login complete"
33
+ client
34
+ end
35
+ end
36
+
37
+ private
38
+
39
+ def masked_password
40
+ account.password.gsub(/./, "x")
41
+ end
42
+
43
+ def provider
44
+ @provider ||= Email::Provider.for_address(account.username)
45
+ end
46
+
47
+ def provider_options
48
+ provider.options.merge(account.connection_options || {})
49
+ end
50
+
51
+ def server
52
+ @server ||= account.server || provider.host
53
+ end
54
+ end
55
+ end
@@ -0,0 +1,26 @@
1
+ module Imap::Backup
2
+ class Account; end
3
+ class Account::Connection; end
4
+
5
+ class Account::Connection::FolderNames
6
+ attr_reader :account
7
+ attr_reader :client
8
+
9
+ def initialize(client:, account:)
10
+ @client = client
11
+ @account = account
12
+ end
13
+
14
+ def run
15
+ folder_names = client.list
16
+
17
+ if folder_names.empty?
18
+ message = "Unable to get folder list for account #{account.username}"
19
+ Logger.logger.info message
20
+ raise message
21
+ end
22
+
23
+ folder_names
24
+ end
25
+ end
26
+ end
@@ -1,17 +1,14 @@
1
1
  require "imap/backup/client/apple_mail"
2
2
  require "imap/backup/client/default"
3
+ require "imap/backup/account/connection/backup_folders"
4
+ require "imap/backup/account/connection/client_factory"
5
+ require "imap/backup/account/connection/folder_names"
3
6
  require "imap/backup/serializer/directory"
4
7
 
5
- require "retry_on_error"
6
-
7
8
  module Imap::Backup
8
9
  class Account; end
9
10
 
10
11
  class Account::Connection
11
- include RetryOnError
12
-
13
- LOGIN_RETRY_CLASSES = [EOFError, Errno::ECONNRESET, SocketError].freeze
14
-
15
12
  attr_reader :account
16
13
 
17
14
  def initialize(account)
@@ -20,34 +17,12 @@ module Imap::Backup
20
17
  end
21
18
 
22
19
  def folder_names
23
- @folder_names ||=
24
- begin
25
- folder_names = client.list
26
-
27
- if folder_names.empty?
28
- message = "Unable to get folder list for account #{account.username}"
29
- Imap::Backup::Logger.logger.info message
30
- raise message
31
- end
32
-
33
- folder_names
34
- end
20
+ @folder_names ||= Account::Connection::FolderNames.new(client: client, account: account).run
35
21
  end
36
22
 
37
23
  def backup_folders
38
24
  @backup_folders ||=
39
- begin
40
- names =
41
- if account.folders&.any?
42
- account.folders.map { |af| af[:name] }
43
- else
44
- folder_names
45
- end
46
-
47
- names.map do |name|
48
- Account::Folder.new(self, name)
49
- end
50
- end
25
+ Account::Connection::BackupFolders.new(client: client, account: account).run
51
26
  end
52
27
 
53
28
  def status
@@ -59,18 +34,21 @@ module Imap::Backup
59
34
  end
60
35
 
61
36
  def run_backup
62
- Imap::Backup::Logger.logger.debug "Running backup of account: #{account.username}"
37
+ Logger.logger.debug "Running backup of account: #{account.username}"
63
38
  # start the connection so we get logging messages in the right order
64
39
  client
65
40
  ensure_account_folder
66
41
  each_folder do |folder, serializer|
67
42
  next if !folder.exist?
68
43
 
69
- Imap::Backup::Logger.logger.debug "[#{folder.name}] running backup"
44
+ Logger.logger.debug "[#{folder.name}] running backup"
70
45
  serializer.apply_uid_validity(folder.uid_validity)
71
46
  begin
72
47
  Downloader.new(
73
- folder, serializer, multi_fetch_size: account.multi_fetch_size
48
+ folder,
49
+ serializer,
50
+ multi_fetch_size: account.multi_fetch_size,
51
+ reset_seen_flags_after_fetch: account.reset_seen_flags_after_fetch
74
52
  ).run
75
53
  rescue Net::IMAP::ByeResponseError
76
54
  reconnect
@@ -95,7 +73,7 @@ module Imap::Backup
95
73
 
96
74
  def restore
97
75
  local_folders do |serializer, folder|
98
- restore_folder serializer, folder
76
+ Uploader.new(folder, serializer).run
99
77
  end
100
78
  end
101
79
 
@@ -112,32 +90,11 @@ module Imap::Backup
112
90
  @backup_folders = nil
113
91
  @client = nil
114
92
  @folder_names = nil
115
- @provider = nil
116
- @server = nil
117
93
  end
118
94
 
95
+ # TODO: make this private
119
96
  def client
120
- @client ||=
121
- retry_on_error(errors: LOGIN_RETRY_CLASSES) do
122
- options = provider_options
123
- Imap::Backup::Logger.logger.debug(
124
- "Creating IMAP instance: #{server}, options: #{options.inspect}"
125
- )
126
- client =
127
- if provider.is_a?(Email::Provider::AppleMail)
128
- Client::AppleMail.new(server, options)
129
- else
130
- Client::Default.new(server, options)
131
- end
132
- Imap::Backup::Logger.logger.debug "Logging in: #{account.username}/#{masked_password}"
133
- client.login(account.username, account.password)
134
- Imap::Backup::Logger.logger.debug "Login complete"
135
- client
136
- end
137
- end
138
-
139
- def server
140
- @server ||= account.server || provider.host
97
+ @client ||= Account::Connection::ClientFactory.new(account: account).run
141
98
  end
142
99
 
143
100
  private
@@ -149,51 +106,14 @@ module Imap::Backup
149
106
  end
150
107
  end
151
108
 
152
- def restore_folder(serializer, folder)
153
- existing_uids = folder.uids
154
- if existing_uids.any?
155
- Imap::Backup::Logger.logger.debug(
156
- "There's already a '#{folder.name}' folder with emails"
157
- )
158
- new_name = serializer.apply_uid_validity(folder.uid_validity)
159
- old_name = serializer.folder
160
- if new_name
161
- Imap::Backup::Logger.logger.debug(
162
- "Backup '#{old_name}' renamed and restored to '#{new_name}'"
163
- )
164
- new_serializer = Serializer.new(account.local_path, new_name)
165
- new_folder = Account::Folder.new(self, new_name)
166
- new_folder.create
167
- new_serializer.force_uid_validity(new_folder.uid_validity)
168
- Uploader.new(new_folder, new_serializer).run
169
- else
170
- Uploader.new(folder, serializer).run
171
- end
172
- else
173
- folder.create
174
- serializer.force_uid_validity(folder.uid_validity)
175
- Uploader.new(folder, serializer).run
176
- end
177
- end
178
-
179
109
  def ensure_account_folder
110
+ raise "The backup path for #{account.username} is not set" if !account.local_path
111
+
180
112
  Utils.make_folder(
181
113
  File.dirname(account.local_path),
182
114
  File.basename(account.local_path),
183
115
  Serializer::Directory::DIRECTORY_PERMISSIONS
184
116
  )
185
117
  end
186
-
187
- def masked_password
188
- account.password.gsub(/./, "x")
189
- end
190
-
191
- def provider
192
- @provider ||= Email::Provider.for_address(account.username)
193
- end
194
-
195
- def provider_options
196
- provider.options.merge(account.connection_options || {})
197
- end
198
118
  end
199
119
  end
@@ -12,7 +12,8 @@ module Imap::Backup
12
12
  include RetryOnError
13
13
 
14
14
  BODY_ATTRIBUTE = "BODY[]".freeze
15
- UID_FETCH_RETRY_CLASSES = [EOFError].freeze
15
+ UID_FETCH_RETRY_CLASSES = [EOFError, Errno::ECONNRESET, IOError].freeze
16
+ APPEND_RETRY_CLASSES = [Net::IMAP::BadResponseError].freeze
16
17
 
17
18
  attr_reader :connection
18
19
  attr_reader :name
@@ -64,7 +65,7 @@ module Imap::Backup
64
65
  in `search_internal` in stdlib net/imap.rb.
65
66
  This is caused by `@responses["SEARCH"] being unset/undefined
66
67
  MESSAGE
67
- Imap::Backup::Logger.logger.warn message
68
+ Logger.logger.warn message
68
69
  []
69
70
  end
70
71
 
@@ -91,29 +92,50 @@ module Imap::Backup
91
92
  def append(message)
92
93
  body = message.imap_body
93
94
  date = message.date&.to_time
94
- response = client.append(utf7_encoded_name, body, nil, date)
95
- extract_uid(response)
95
+ retry_on_error(errors: APPEND_RETRY_CLASSES, limit: 3) do
96
+ response = client.append(utf7_encoded_name, body, nil, date)
97
+ extract_uid(response)
98
+ end
96
99
  end
97
100
 
98
- def clear
99
- existing = uids
101
+ def set_flags(uids, flags)
100
102
  # Use read-write access, via `select`
101
103
  client.select(utf7_encoded_name)
102
- client.uid_store(existing, "+FLAGS", [:Deleted])
104
+ client.uid_store(uids, "+FLAGS", flags)
105
+ end
106
+
107
+ def unset_flags(uids, flags)
108
+ client.select(utf7_encoded_name)
109
+ client.uid_store(uids, "-FLAGS", flags)
110
+ end
111
+
112
+ def clear
113
+ set_flags(uids, [:Deleted])
103
114
  client.expunge
104
115
  end
105
116
 
117
+ def unseen(uids)
118
+ messages = uids.map(&:to_s).join(",")
119
+ examine
120
+ client.uid_search([messages, "UNSEEN"])
121
+ rescue NoMethodError
122
+ # Apple Mail returns an empty response when searches have no results
123
+ []
124
+ rescue FolderNotFound
125
+ nil
126
+ end
127
+
106
128
  private
107
129
 
108
130
  def examine
109
131
  client.examine(utf7_encoded_name)
110
132
  rescue Net::IMAP::NoResponseError
111
- Imap::Backup::Logger.logger.warn "Folder '#{name}' does not exist on server"
133
+ Logger.logger.warn "Folder '#{name}' does not exist on server"
112
134
  raise FolderNotFound, "Folder '#{name}' does not exist on server"
113
135
  end
114
136
 
115
137
  def extract_uid(response)
116
- @uid_validity, uid = response.data.code.data.split(" ").map(&:to_i)
138
+ @uid_validity, uid = response.data.code.data.split.map(&:to_i)
117
139
  uid
118
140
  end
119
141
 
@@ -8,6 +8,7 @@ module Imap::Backup
8
8
  attr_reader :folders
9
9
  attr_reader :server
10
10
  attr_reader :connection_options
11
+ attr_reader :reset_seen_flags_after_fetch
11
12
  attr_reader :changes
12
13
 
13
14
  def initialize(options)
@@ -18,12 +19,14 @@ module Imap::Backup
18
19
  @server = options[:server]
19
20
  @connection_options = options[:connection_options]
20
21
  @multi_fetch_size = options[:multi_fetch_size]
22
+ @reset_seen_flags_after_fetch = options[:reset_seen_flags_after_fetch]
23
+ @connection = nil
21
24
  @changes = {}
22
25
  @marked_for_deletion = false
23
26
  end
24
27
 
25
28
  def connection
26
- Account::Connection.new(self)
29
+ @connection ||= Account::Connection.new(self)
27
30
  end
28
31
 
29
32
  def valid?
@@ -47,15 +50,15 @@ module Imap::Backup
47
50
  end
48
51
 
49
52
  def to_h
50
- h = {
51
- username: @username,
52
- password: @password,
53
- }
53
+ h = {username: @username, password: @password}
54
54
  h[:local_path] = @local_path if @local_path
55
55
  h[:folders] = @folders if @folders
56
56
  h[:server] = @server if @server
57
57
  h[:connection_options] = @connection_options if @connection_options
58
58
  h[:multi_fetch_size] = multi_fetch_size if @multi_fetch_size
59
+ if @reset_seen_flags_after_fetch
60
+ h[:reset_seen_flags_after_fetch] = @reset_seen_flags_after_fetch
61
+ end
59
62
  h
60
63
  end
61
64
 
@@ -73,6 +76,7 @@ module Imap::Backup
73
76
 
74
77
  def folders=(value)
75
78
  raise "folders must be an Array" if !value.is_a?(Array)
79
+
76
80
  update(:folders, value)
77
81
  end
78
82
 
@@ -100,6 +104,10 @@ module Imap::Backup
100
104
  update(:multi_fetch_size, parsed)
101
105
  end
102
106
 
107
+ def reset_seen_flags_after_fetch=(value)
108
+ update(:reset_seen_flags_after_fetch, value)
109
+ end
110
+
103
111
  private
104
112
 
105
113
  def update(field, value)
@@ -113,9 +121,10 @@ module Imap::Backup
113
121
  end
114
122
  else
115
123
  current = instance_variable_get(key)
116
- changes[field] = {from: current, to: value}
124
+ changes[field] = {from: current, to: value} if value != current
117
125
  end
118
126
 
127
+ @connection = nil
119
128
  instance_variable_set(key, value)
120
129
  end
121
130
  end
@@ -12,9 +12,7 @@ module Imap::Backup
12
12
 
13
13
  no_commands do
14
14
  def run
15
- each_connection(account_names) do |connection|
16
- connection.run_backup
17
- end
15
+ each_connection(account_names, &:run_backup)
18
16
  end
19
17
  end
20
18
  end
@@ -1,35 +1,37 @@
1
1
  require "imap/backup"
2
2
  require "imap/backup/cli/accounts"
3
3
 
4
- module Imap::Backup::CLI::Helpers
5
- def symbolized(options)
6
- options.each.with_object({}) do |(k, v), acc|
7
- key = k.gsub("-", "_").intern
8
- acc[key] = v
4
+ module Imap::Backup
5
+ module CLI::Helpers
6
+ def symbolized(options)
7
+ options.each.with_object({}) do |(k, v), acc|
8
+ key = k.gsub("-", "_").intern
9
+ acc[key] = v
10
+ end
9
11
  end
10
- end
11
12
 
12
- def account(email)
13
- accounts = Imap::Backup::CLI::Accounts.new
14
- account = accounts.find { |a| a.username == email }
15
- raise "#{email} is not a configured account" if !account
13
+ def account(email)
14
+ accounts = CLI::Accounts.new
15
+ account = accounts.find { |a| a.username == email }
16
+ raise "#{email} is not a configured account" if !account
16
17
 
17
- account
18
- end
18
+ account
19
+ end
19
20
 
20
- def connection(email)
21
- account = account(email)
21
+ def connection(email)
22
+ account = account(email)
22
23
 
23
- Imap::Backup::Account::Connection.new(account)
24
- end
24
+ Account::Connection.new(account)
25
+ end
25
26
 
26
- def each_connection(names)
27
- accounts = Imap::Backup::CLI::Accounts.new(names)
27
+ def each_connection(names)
28
+ accounts = CLI::Accounts.new(names)
28
29
 
29
- accounts.each do |account|
30
- yield account.connection
30
+ accounts.each do |account|
31
+ yield account.connection
32
+ end
33
+ rescue ConfigurationNotFound
34
+ raise "imap-backup is not configured. Run `imap-backup setup`"
31
35
  end
32
- rescue Imap::Backup::ConfigurationNotFound
33
- raise "imap-backup is not configured. Run `imap-backup setup`"
34
36
  end
35
37
  end