imap-backup 11.0.0 → 11.1.0.rc1

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: 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: