imap-backup 14.6.1 → 15.0.1

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: cf35a029c76b09db247f5d45e6624fe6d0d1f1453ee29edd1e02e6621eb42710
4
- data.tar.gz: 2238c658e5306848de14ff2edffbf1275576ff1b27c0aa04c642a7c2de9d7fba
3
+ metadata.gz: 3504dad0e3f788bcfa95b38065e0ff4035239886461689c478a2e991f5e06cf9
4
+ data.tar.gz: 7d1151b5fb25f2d84ae2013f8f9f74fee7f998ca36b29333bf44151e52726376
5
5
  SHA512:
6
- metadata.gz: d066629d29da0114e76d95b137caa7ba4ac26d91bda560a8a2fced551ad88f22477a67a0cfc9130365dafe7a4a60928b2ebedbc301ab6371d4aeade2de6131ab
7
- data.tar.gz: 75ac7182884ca791e30a7058940b40c4940f4ad515677f081de8f29e7c9da72061d3ee6eee9fce02efc734803f31ab2f2a66ae25356d284ce38f66d3af2ee8eb
6
+ metadata.gz: 3ec40ec413bd6bedce02d4c823ae1654c06585223532d1260bf01fe4ef0e8ba6e0b8c25c7adf0d2b39f8ff29c5199251d07fb9234a75a3ac50856075c91ccc52
7
+ data.tar.gz: d31020db8b37295d8d7209758161ce4fe5d16869a6976f3538e12b2d7a74f3cb363328d0dcd79ce665a2b113cf7f6289794ee328ded6e8a73cb544c4d0897428
data/imap-backup.gemspec CHANGED
@@ -18,7 +18,7 @@ Gem::Specification.new do |gem|
18
18
 
19
19
  gem.executables = gem.files.grep(%r{^bin/}).map { |f| File.basename(f) }
20
20
  gem.require_paths = ["lib"]
21
- gem.required_ruby_version = ">= 2.7"
21
+ gem.required_ruby_version = ">= 3.0"
22
22
 
23
23
  gem.add_runtime_dependency "highline"
24
24
  gem.add_runtime_dependency "mail", "2.7.1"
@@ -29,11 +29,13 @@ module Imap::Backup
29
29
 
30
30
  serializer.apply_uid_validity(folder.uid_validity)
31
31
 
32
- download_serializer.transaction do
32
+ serializer.transaction do
33
33
  downloader.run
34
+ FlagRefresher.new(folder, serializer).run if account.mirror_mode || refresh
34
35
  end
35
-
36
- clean_up
36
+ # After the transaction the serializer will have any appended messages
37
+ # so we can check differences between the server and the local backup
38
+ LocalOnlyMessageDeleter.new(folder, raw_serializer).run if account.mirror_mode
37
39
  end
38
40
 
39
41
  private
@@ -55,34 +57,29 @@ module Imap::Backup
55
57
  true
56
58
  end
57
59
 
58
- def clean_up
59
- LocalOnlyMessageDeleter.new(folder, serializer).run if account.mirror_mode
60
- FlagRefresher.new(folder, serializer).run if account.mirror_mode || refresh
61
- end
62
-
63
60
  def downloader
64
61
  @downloader ||= Downloader.new(
65
62
  folder,
66
- download_serializer,
63
+ serializer,
67
64
  multi_fetch_size: account.multi_fetch_size,
68
65
  reset_seen_flags_after_fetch: account.reset_seen_flags_after_fetch
69
66
  )
70
67
  end
71
68
 
72
- def download_serializer
73
- @download_serializer ||=
69
+ def serializer
70
+ @serializer ||=
74
71
  case account.download_strategy
75
72
  when "direct"
76
- serializer
73
+ raw_serializer
77
74
  when "delay_metadata"
78
- Serializer::DelayedMetadataSerializer.new(serializer: serializer)
75
+ Serializer::DelayedMetadataSerializer.new(serializer: raw_serializer)
79
76
  else
80
77
  raise "Unknown download strategy '#{account.download_strategy}'"
81
78
  end
82
79
  end
83
80
 
84
- def serializer
85
- @serializer ||= Serializer.new(account.local_path, folder.name)
81
+ def raw_serializer
82
+ @raw_serializer ||= Serializer.new(account.local_path, folder.name)
86
83
  end
87
84
  end
88
85
  end
@@ -23,12 +23,12 @@ module Imap::Backup
23
23
  # Proxies calls to the client.
24
24
  # Before the first call does login
25
25
  # @return the return value of the client method called
26
- def method_missing(method_name, *arguments, &block)
26
+ def method_missing(method_name, ...)
27
27
  if login_called
28
- client.send(method_name, *arguments, &block)
28
+ client.send(method_name, ...)
29
29
  else
30
30
  do_first_login
31
- client.send(method_name, *arguments, &block) if method_name != :login
31
+ client.send(method_name, ...) if method_name != :login
32
32
  end
33
33
  end
34
34
 
@@ -7,7 +7,7 @@ module Imap::Backup
7
7
  # The characters that cannot be used in file names
8
8
  INVALID_FILENAME_CHARACTERS = ":%;".freeze
9
9
  # A regular expression that captures each disallowed character
10
- INVALID_FILENAME_CHARACTER_MATCH = /([#{INVALID_FILENAME_CHARACTERS}])/.freeze
10
+ INVALID_FILENAME_CHARACTER_MATCH = /([#{INVALID_FILENAME_CHARACTERS}])/
11
11
 
12
12
  # @param name [String] a folder name
13
13
  # @return [String] the supplied string iwth disallowed characters replaced
@@ -10,7 +10,7 @@ module Imap; end
10
10
  module Imap::Backup
11
11
  class Serializer; end
12
12
 
13
- # Wraps the Serializer, delaying metadata appends
13
+ # Wraps the Serializer, delaying metadata changes
14
14
  class Serializer::DelayedMetadataSerializer
15
15
  extend Forwardable
16
16
 
@@ -31,7 +31,7 @@ module Imap::Backup
31
31
  def transaction(&block)
32
32
  tsx.fail_in_transaction!(:transaction, message: "nested transactions are not supported")
33
33
 
34
- tsx.begin({metadata: []}) do
34
+ tsx.begin({appends: [], updates: []}) do
35
35
  mbox.transaction do
36
36
  block.call
37
37
 
@@ -42,7 +42,21 @@ module Imap::Backup
42
42
  end
43
43
  end
44
44
 
45
- # Appends a message to the mbox file and adds the metadata
45
+ # Sets the folder's UID validity via the serializer
46
+ #
47
+ # @param uid_validity [Integer] the UID validity to apply
48
+ # @raise [RuntimeError] if called inside a transaction
49
+ # @return [void]
50
+ def apply_uid_validity(uid_validity)
51
+ tsx.fail_in_transaction!(
52
+ :transaction,
53
+ message: "UID validity cannot be changed in a transaction"
54
+ )
55
+
56
+ serializer.apply_uid_validity(uid_validity)
57
+ end
58
+
59
+ # Appends a message to the mbox file and adds the appended message's metadata
46
60
  # to the transaction
47
61
  #
48
62
  # @param uid [Integer] the UID of the message
@@ -53,10 +67,21 @@ module Imap::Backup
53
67
  tsx.fail_outside_transaction!(:append)
54
68
  mboxrd_message = Email::Mboxrd::Message.new(message)
55
69
  serialized = mboxrd_message.to_serialized
56
- tsx.data[:metadata] << {uid: uid, length: serialized.bytesize, flags: flags}
70
+ tsx.data[:appends] << {uid: uid, length: serialized.bytesize, flags: flags}
57
71
  mbox.append(serialized)
58
72
  end
59
73
 
74
+ # Stores changes to a message's metadata for later update
75
+ #
76
+ # @param uid [Integer] the UID of the message
77
+ # @param length [Integer] the length of the message
78
+ # @param flags [Array<Symbol>] the flags for the message
79
+ # @return [void]
80
+ def update(uid, length: nil, flags: nil)
81
+ tsx.fail_outside_transaction!(:update)
82
+ tsx.data[:updates] << {uid: uid, length: length, flags: flags}
83
+ end
84
+
60
85
  private
61
86
 
62
87
  attr_reader :serializer
@@ -64,9 +89,12 @@ module Imap::Backup
64
89
  def commit
65
90
  # rubocop:disable Lint/RescueException
66
91
  imap.transaction do
67
- tsx.data[:metadata].each do |m|
92
+ tsx.data[:appends].each do |m|
68
93
  imap.append m[:uid], m[:length], flags: m[:flags]
69
94
  end
95
+ tsx.data[:updates].each do |m|
96
+ imap.update m[:uid], length: m[:length], flags: m[:flags]
97
+ end
70
98
  rescue Exception => e
71
99
  Logger.logger.error "#{self.class} handling #{e.class}"
72
100
  imap.rollback
@@ -97,11 +97,27 @@ module Imap::Backup
97
97
  save
98
98
  end
99
99
 
100
- # Get message metadata
100
+ # Updates a message's length and/or flags
101
+ # @param uid [Integer] the existing message's UID
102
+ # @param length [Integer] the length of the message (as stored on disk)
103
+ # @param flags [Array[Symbol]] the message's flags
104
+ # @raise [RuntimeError] if the UID does not exist
105
+ # @return [void]
106
+ def update(uid, length: nil, flags: nil)
107
+ index = messages.find_index { |m| m.uid == uid }
108
+ raise "UID #{uid} not found" if !index
109
+
110
+ messages[index].length = length if length
111
+ messages[index].flags = flags if flags
112
+ save
113
+ end
114
+
115
+ # Get a copy of message metadata
101
116
  # @param uid [Integer] a message UID
102
117
  # @return [Serializer::Message]
103
118
  def get(uid)
104
- messages.find { |m| m.uid == uid }
119
+ message = messages.find { |m| m.uid == uid }
120
+ message&.dup
105
121
  end
106
122
 
107
123
  # Deletes the metadata file
@@ -158,11 +174,15 @@ module Imap::Backup
158
174
  messages.map(&:uid)
159
175
  end
160
176
 
161
- # Update a message's metadata, replacing its UID
177
+ # Update a message's UID
162
178
  # @param old [Integer] the existing message UID
163
179
  # @param new [Integer] the new UID to apply to the message
180
+ # @raise [RuntimeError] if the new UID already exists
164
181
  # @return [void]
165
182
  def update_uid(old, new)
183
+ existing = messages.find_index { |m| m.uid == new }
184
+ raise "UID #{new} already exists" if existing
185
+
166
186
  index = messages.find_index { |m| m.uid == old }
167
187
  return if index.nil?
168
188
 
@@ -28,6 +28,7 @@ module Imap::Backup
28
28
  extend Forwardable
29
29
 
30
30
  def_delegator :mbox, :pathname, :mbox_pathname
31
+ def_delegator :imap, :update
31
32
 
32
33
  # Get message metadata
33
34
  # @param uid [Integer] a message UID
@@ -77,7 +78,7 @@ module Imap::Backup
77
78
  @validated = nil
78
79
  end
79
80
 
80
- # Calls the supplied block.
81
+ # Calls the supplied block without implementing transactional behaviour.
81
82
  # This method is present so that this class implements the same
82
83
  # interface as {DelayedMetadataSerializer}
83
84
  # @param block [block] the block that is wrapped by the transaction
@@ -177,18 +178,6 @@ module Imap::Backup
177
178
  appender.append(uid: uid, message: message, flags: flags)
178
179
  end
179
180
 
180
- # Updates a messages flags
181
- # @param uid [Integer] the message's UID
182
- # @param flags [Array<Symbol>] the flags to set on the message
183
- # @return [void]
184
- def update(uid, flags: nil)
185
- message = imap.get(uid)
186
- return if !message
187
-
188
- message.flags = flags if flags
189
- imap.save
190
- end
191
-
192
181
  # Enumerates over a series of messages.
193
182
  # When called without a block, returns an Enumerator
194
183
  # @param required_uids [Array<Integer>] the UIDs of the message to enumerate over
@@ -55,7 +55,7 @@ module Imap::Backup
55
55
 
56
56
  private
57
57
 
58
- EMAIL_MATCHER = /^[A-Z0-9._%+-]+@[A-Z0-9.-]+\.[A-Z]+$/i.freeze
58
+ EMAIL_MATCHER = /^[A-Z0-9._%+-]+@[A-Z0-9.-]+\.[A-Z]+$/i
59
59
 
60
60
  attr_reader :highline
61
61
  end
@@ -2,9 +2,9 @@ module Imap; end
2
2
 
3
3
  module Imap::Backup
4
4
  # @private
5
- MAJOR = 14
5
+ MAJOR = 15
6
6
  # @private
7
- MINOR = 6
7
+ MINOR = 0
8
8
  # @private
9
9
  REVISION = 1
10
10
  # @private
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: 14.6.1
4
+ version: 15.0.1
5
5
  platform: ruby
6
6
  authors:
7
7
  - Joe Yates
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2024-03-30 00:00:00.000000000 Z
11
+ date: 2024-06-04 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: highline
@@ -226,7 +226,7 @@ required_ruby_version: !ruby/object:Gem::Requirement
226
226
  requirements:
227
227
  - - ">="
228
228
  - !ruby/object:Gem::Version
229
- version: '2.7'
229
+ version: '3.0'
230
230
  required_rubygems_version: !ruby/object:Gem::Requirement
231
231
  requirements:
232
232
  - - ">="