imap-backup 11.0.0 → 11.1.0.rc1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 1392301e243a9affe750a9361f14622228879596b8cb0a29e4944dd91af52d44
4
- data.tar.gz: d85f59f5387d5f3c5ac1f6ce97a4a2e5a685ca675b02be3982c914774485d5fc
3
+ metadata.gz: fce713a5edcf2115c1f8636cfe7b6521aae0403a9bb79dfaf05b484363dbb7ad
4
+ data.tar.gz: 68544128dba3ade90077683c14d454596a514368cb40a46094934ec94220a606
5
5
  SHA512:
6
- metadata.gz: ef636f637cf6ab48a173439e6d4c908fa7bf0b8a171a7ba60a80b83062e95d30aafba0e52a4647b5c74b23a40b7504132803ad4ddc57907e3b08298e11b9a26c
7
- data.tar.gz: 1274d7462c5e302bd1ea84529b12d74e6cc0ebe47ba6d755e2f23cdb5829dfde6fdde858c2640abea5f6f13eab8e5867334e82f5e4b7649ae41385a49f5706ca
6
+ metadata.gz: f5b467457df7840d8cf69cc4ace9758cfc2b38242acdb3f4c75a9dd0327c996995aa0986f649725c57e8b058ad0be4d386d9760720a20d505c3154e2a82aa59c
7
+ data.tar.gz: 3dd8a7f189e0d313e487b63fd64d7c57a647d384360e4128f2787cb7673f1db35a05ffb4853603e7bafbe17537a1b309a43a825180a910e45abc202630283eb9
@@ -28,7 +28,9 @@ module Email::Mboxrd
28
28
  end
29
29
 
30
30
  def to_serialized
31
- "From #{from}\n" + mboxrd_body
31
+ from_line = "From #{from}\n"
32
+ body = mboxrd_body.dup.force_encoding(Encoding::UTF_8)
33
+ from_line + body
32
34
  end
33
35
 
34
36
  def date
@@ -1,10 +1,7 @@
1
+ require "imap/backup/account/backup_folders"
2
+ require "imap/backup/account/folder_backup"
1
3
  require "imap/backup/account/folder_ensurer"
2
4
  require "imap/backup/account/local_only_folder_deleter"
3
- require "imap/backup/account/serialized_folders"
4
- require "imap/backup/serializer/delayed_metadata_serializer"
5
- require "imap/backup/downloader"
6
- require "imap/backup/flag_refresher"
7
- require "imap/backup/local_only_message_deleter"
8
5
 
9
6
  module Imap; end
10
7
 
@@ -27,66 +24,15 @@ module Imap::Backup
27
24
 
28
25
  Account::FolderEnsurer.new(account: account).run
29
26
  Account::LocalOnlyFolderDeleter.new(account: account).run if account.mirror_mode
30
- each_folder do |folder, serializer|
31
- begin
32
- next if !folder.exist?
33
- rescue Encoding::UndefinedConversionError
34
- message = "Skipping backup for '#{folder.name}' " \
35
- "as it is not UTF-7 encoded correctly"
36
- Logger.logger.info message
37
- next
38
- end
39
-
40
- Logger.logger.debug "[#{folder.name}] running backup"
41
-
42
- serializer.apply_uid_validity(folder.uid_validity)
43
-
44
- download_serializer =
45
- case account.download_strategy
46
- when "direct"
47
- serializer
48
- when "delay_metadata"
49
- Serializer::DelayedMetadataSerializer.new(serializer: serializer)
50
- else
51
- raise "Unknown download strategy '#{account.download_strategy}'"
52
- end
53
-
54
- downloader = Downloader.new(
55
- folder,
56
- download_serializer,
57
- multi_fetch_size: account.multi_fetch_size,
58
- reset_seen_flags_after_fetch: account.reset_seen_flags_after_fetch
59
- )
60
- # rubocop:disable Lint/RescueException
61
- download_serializer.transaction do
62
- downloader.run
63
- rescue Exception => e
64
- message = <<~ERROR
65
- #{self.class} error #{e}
66
- #{e.backtrace.join("\n")}
67
- ERROR
68
- Logger.logger.error message
69
- download_serializer.rollback
70
- raise e
71
- end
72
- # rubocop:enable Lint/RescueException
73
- if account.mirror_mode
74
- Logger.logger.info "Mirror mode - Deleting messages only present locally"
75
- LocalOnlyMessageDeleter.new(folder, serializer).run
76
- end
77
- FlagRefresher.new(folder, serializer).run if account.mirror_mode || refresh
78
- end
79
- end
80
-
81
- private
82
-
83
- def each_folder
84
27
  backup_folders = Account::BackupFolders.new(
85
28
  client: account.client, account: account
86
29
  )
30
+ if backup_folders.none?
31
+ Logger.logger.warn "Account #{account.username}: No folders found to backup"
32
+ return
33
+ end
87
34
  backup_folders.each do |folder|
88
- serializer = Serializer.new(account.local_path, folder.name)
89
- yield folder, serializer
35
+ Account::FolderBackup.new(account: account, folder: folder, refresh: refresh).run
90
36
  end
91
37
  end
92
38
  end
@@ -4,6 +4,8 @@ module Imap::Backup
4
4
  class Account; end
5
5
 
6
6
  class Account::BackupFolders
7
+ include Enumerable
8
+
7
9
  attr_reader :account
8
10
  attr_reader :client
9
11
 
@@ -0,0 +1,85 @@
1
+ require "imap/backup/serializer/delayed_metadata_serializer"
2
+ require "imap/backup/downloader"
3
+ require "imap/backup/flag_refresher"
4
+ require "imap/backup/local_only_message_deleter"
5
+
6
+ module Imap; end
7
+
8
+ module Imap::Backup
9
+ class Account; end
10
+
11
+ class Account::FolderBackup
12
+ attr_reader :account
13
+ attr_reader :folder
14
+ attr_reader :refresh
15
+
16
+ def initialize(account:, folder:, refresh: false)
17
+ @account = account
18
+ @folder = folder
19
+ @refresh = refresh
20
+ end
21
+
22
+ def run
23
+ folder_ok = folder_ok?
24
+ return if !folder_ok
25
+
26
+ Logger.logger.debug "[#{folder.name}] running backup"
27
+
28
+ serializer.apply_uid_validity(folder.uid_validity)
29
+
30
+ download_serializer.transaction do
31
+ downloader.run
32
+ end
33
+
34
+ clean_up
35
+ end
36
+
37
+ private
38
+
39
+ def folder_ok?
40
+ begin
41
+ return false if !folder.exist?
42
+ rescue Encoding::UndefinedConversionError
43
+ message = "Skipping backup for '#{folder.name}' " \
44
+ "as it is not UTF-7 encoded correctly"
45
+ Logger.logger.info message
46
+ return false
47
+ end
48
+
49
+ true
50
+ end
51
+
52
+ def clean_up
53
+ if account.mirror_mode
54
+ Logger.logger.info "Mirror mode - Deleting messages only present locally"
55
+ LocalOnlyMessageDeleter.new(folder, serializer).run
56
+ end
57
+ FlagRefresher.new(folder, serializer).run if account.mirror_mode || refresh
58
+ end
59
+
60
+ def downloader
61
+ @downloader ||= Downloader.new(
62
+ folder,
63
+ download_serializer,
64
+ multi_fetch_size: account.multi_fetch_size,
65
+ reset_seen_flags_after_fetch: account.reset_seen_flags_after_fetch
66
+ )
67
+ end
68
+
69
+ def download_serializer
70
+ @download_serializer ||=
71
+ case account.download_strategy
72
+ when "direct"
73
+ serializer
74
+ when "delay_metadata"
75
+ Serializer::DelayedMetadataSerializer.new(serializer: serializer)
76
+ else
77
+ raise "Unknown download strategy '#{account.download_strategy}'"
78
+ end
79
+ end
80
+
81
+ def serializer
82
+ @serializer ||= Serializer.new(account.local_path, folder.name)
83
+ end
84
+ end
85
+ end
@@ -19,7 +19,12 @@ module Imap::Backup
19
19
  def run
20
20
  config = load_config(**options)
21
21
  exit_code = nil
22
- requested_accounts(config).each do |account|
22
+ accounts = requested_accounts(config)
23
+ if accounts.none?
24
+ Logger.logger.warn "No matching accounts found to backup"
25
+ return
26
+ end
27
+ accounts.each do |account|
23
28
  backup = Account::Backup.new(account: account, refresh: refresh)
24
29
  backup.run
25
30
  rescue StandardError => e
@@ -0,0 +1,62 @@
1
+ module Imap; end
2
+
3
+ module Imap::Backup
4
+ class CLI; end
5
+ class CLI::Local < Thor; end
6
+
7
+ class CLI::Local::Check
8
+ include CLI::Helpers
9
+
10
+ attr_reader :options
11
+
12
+ def initialize(options)
13
+ @options = options
14
+ end
15
+
16
+ def run
17
+ results = requested_accounts(config).map do |account|
18
+ serialized_folders = Account::SerializedFolders.new(account: account)
19
+ folder_results = serialized_folders.map do |serializer, _folder|
20
+ serializer.check_integrity!
21
+ {name: serializer.folder, result: "OK"}
22
+ rescue Serializer::FolderIntegrityError => e
23
+ message = e.to_s
24
+ if options[:delete_corrupt]
25
+ serializer.delete
26
+ message << " and has been deleted"
27
+ end
28
+
29
+ {
30
+ name: serializer.folder,
31
+ result: message
32
+ }
33
+ end
34
+ {account: account.username, folders: folder_results}
35
+ end
36
+
37
+ case options[:format]
38
+ when "json"
39
+ print_check_results_as_json(results)
40
+ else
41
+ print_check_results_as_text(results)
42
+ end
43
+ end
44
+
45
+ def print_check_results_as_json(results)
46
+ Kernel.puts results.to_json
47
+ end
48
+
49
+ def print_check_results_as_text(results)
50
+ results.each do |account_results|
51
+ Kernel.puts "Account: #{account_results[:account]}"
52
+ account_results[:folders].each do |folder_results|
53
+ Kernel.puts "\t#{folder_results[:name]}: #{folder_results[:result]}"
54
+ end
55
+ end
56
+ end
57
+
58
+ def config
59
+ @config ||= load_config(**options)
60
+ end
61
+ end
62
+ end
@@ -1,3 +1,6 @@
1
+ require "imap/backup/account/serialized_folders"
2
+ require "imap/backup/cli/local/check"
3
+
1
4
  module Imap; end
2
5
 
3
6
  module Imap::Backup
@@ -38,32 +41,8 @@ module Imap::Backup
38
41
  quiet_option
39
42
  verbose_option
40
43
  def check
41
- results = requested_accounts(config).map do |account|
42
- serialized_folders = Account::SerializedFolders.new(account: account)
43
- folder_results = serialized_folders.map do |serializer, _folder|
44
- serializer.check_integrity!
45
- {name: serializer.folder, result: "OK"}
46
- rescue Serializer::FolderIntegrityError => e
47
- message = e.to_s
48
- if options[:delete_corrupt]
49
- serializer.delete
50
- message << " and has been deleted"
51
- end
52
-
53
- {
54
- name: serializer.folder,
55
- result: message
56
- }
57
- end
58
- {account: account.username, folders: folder_results}
59
- end
60
-
61
- case options[:format]
62
- when "json"
63
- print_check_results_as_json(results)
64
- else
65
- print_check_results_as_text(results)
66
- end
44
+ non_logging_options = Imap::Backup::Logger.setup_logging(options)
45
+ Check.new(non_logging_options).run
67
46
  end
68
47
 
69
48
  desc "folders EMAIL", "List backed up folders"
@@ -138,19 +117,6 @@ module Imap::Backup
138
117
  end
139
118
 
140
119
  no_commands do
141
- def print_check_results_as_json(results)
142
- Kernel.puts results.to_json
143
- end
144
-
145
- def print_check_results_as_text(results)
146
- results.each do |account_results|
147
- Kernel.puts "Account: #{account_results[:account]}"
148
- account_results[:folders].each do |folder_results|
149
- Kernel.puts "\t#{folder_results[:name]}: #{folder_results[:result]}"
150
- end
151
- end
152
- end
153
-
154
120
  def list_emails_as_json(serializer)
155
121
  emails = serializer.each_message.map do |message|
156
122
  {
@@ -20,23 +20,13 @@ module Imap::Backup
20
20
  def transaction(&block)
21
21
  tsx.fail_in_transaction!(:transaction, message: "nested transactions are not supported")
22
22
 
23
- # rubocop:disable Lint/RescueException
24
23
  tsx.begin({metadata: []}) do
25
24
  mbox.transaction do
26
25
  block.call
27
26
 
28
27
  commit
29
- rescue Exception => e
30
- message = <<~ERROR
31
- #{self.class} error #{e}
32
- #{e.backtrace.join("\n")}
33
- ERROR
34
- Logger.logger.error message
35
- mbox.rollback
36
- raise e
37
28
  end
38
29
  end
39
- # rubocop:enable Lint/RescueException
40
30
  end
41
31
 
42
32
  def append(uid, message, flags)
@@ -56,6 +46,7 @@ module Imap::Backup
56
46
  imap.append m[:uid], m[:length], flags: m[:flags]
57
47
  end
58
48
  rescue Exception => e
49
+ Logger.logger.error "#{self.class} handling #{e.class}"
59
50
  imap.rollback
60
51
  raise e
61
52
  end
@@ -31,6 +31,7 @@ module Imap::Backup
31
31
 
32
32
  save_internal(version: version, uid_validity: uid_validity, messages: messages) if tsx.data
33
33
  rescue Exception => e
34
+ Logger.logger.error "#{self.class} handling #{e.class}"
34
35
  rollback
35
36
  raise e
36
37
  end
@@ -15,26 +15,22 @@ module Imap::Backup
15
15
  def transaction(&block)
16
16
  tsx.fail_in_transaction!(:transaction, message: "nested transactions are not supported")
17
17
 
18
- # rubocop:disable Lint/RescueException
19
18
  tsx.begin({savepoint: {length: length}}) do
20
19
  block.call
21
- rescue Exception => e
22
- message = <<~ERROR
23
- #{self.class} error #{e}
24
- #{e.backtrace.join("\n")}
25
- ERROR
26
- Logger.logger.error message
20
+ rescue StandardError => e
21
+ rollback
22
+ raise e
23
+ rescue SignalException => e
24
+ Logger.logger.error "#{self.class} handling #{e.class}"
27
25
  rollback
28
26
  raise e
29
27
  end
30
- # rubocop:enable Lint/RescueException
31
28
  end
32
29
 
33
30
  def rollback
34
31
  tsx.fail_outside_transaction!(:rollback)
35
32
 
36
33
  rewind(tsx.data[:savepoint][:length])
37
- tsx.clear
38
34
  end
39
35
 
40
36
  def valid?
@@ -37,9 +37,6 @@ module Imap::Backup
37
37
  block.call
38
38
  end
39
39
 
40
- def rollback
41
- end
42
-
43
40
  # Returns true if there are existing, valid files
44
41
  # false otherwise (in which case any existing files are deleted)
45
42
  def validate!
@@ -2,8 +2,8 @@ module Imap; end
2
2
 
3
3
  module Imap::Backup
4
4
  MAJOR = 11
5
- MINOR = 0
5
+ MINOR = 1
6
6
  REVISION = 0
7
- PRE = nil
7
+ PRE = "rc1".freeze
8
8
  VERSION = [MAJOR, MINOR, REVISION, PRE].compact.map(&:to_s).join(".")
9
9
  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: 11.0.0
4
+ version: 11.1.0.rc1
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-07-25 00:00:00.000000000 Z
11
+ date: 2023-08-12 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: highline
@@ -236,6 +236,7 @@ files:
236
236
  - lib/imap/backup/account/backup_folders.rb
237
237
  - lib/imap/backup/account/client_factory.rb
238
238
  - lib/imap/backup/account/folder.rb
239
+ - lib/imap/backup/account/folder_backup.rb
239
240
  - lib/imap/backup/account/folder_ensurer.rb
240
241
  - lib/imap/backup/account/local_only_folder_deleter.rb
241
242
  - lib/imap/backup/account/restore.rb
@@ -245,6 +246,7 @@ files:
245
246
  - lib/imap/backup/cli/folder_enumerator.rb
246
247
  - lib/imap/backup/cli/helpers.rb
247
248
  - lib/imap/backup/cli/local.rb
249
+ - lib/imap/backup/cli/local/check.rb
248
250
  - lib/imap/backup/cli/migrate.rb
249
251
  - lib/imap/backup/cli/mirror.rb
250
252
  - lib/imap/backup/cli/remote.rb
@@ -310,9 +312,9 @@ required_ruby_version: !ruby/object:Gem::Requirement
310
312
  version: '2.6'
311
313
  required_rubygems_version: !ruby/object:Gem::Requirement
312
314
  requirements:
313
- - - ">="
315
+ - - ">"
314
316
  - !ruby/object:Gem::Version
315
- version: '0'
317
+ version: 1.3.1
316
318
  requirements: []
317
319
  rubygems_version: 3.3.7
318
320
  signing_key: