imap-backup 14.6.0 → 15.0.1
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 +4 -4
- data/imap-backup.gemspec +1 -1
- data/lib/imap/backup/account/folder_backup.rb +12 -15
- data/lib/imap/backup/account.rb +18 -8
- data/lib/imap/backup/cli/single/backup.rb +1 -1
- data/lib/imap/backup/client/automatic_login_wrapper.rb +3 -3
- data/lib/imap/backup/naming.rb +1 -1
- data/lib/imap/backup/serializer/delayed_metadata_serializer.rb +33 -5
- data/lib/imap/backup/serializer/imap.rb +23 -3
- data/lib/imap/backup/serializer.rb +2 -13
- data/lib/imap/backup/setup/asker.rb +1 -1
- data/lib/imap/backup/version.rb +3 -3
- metadata +6 -6
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 3504dad0e3f788bcfa95b38065e0ff4035239886461689c478a2e991f5e06cf9
|
4
|
+
data.tar.gz: 7d1151b5fb25f2d84ae2013f8f9f74fee7f998ca36b29333bf44151e52726376
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
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 = ">=
|
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
|
-
|
32
|
+
serializer.transaction do
|
33
33
|
downloader.run
|
34
|
+
FlagRefresher.new(folder, serializer).run if account.mirror_mode || refresh
|
34
35
|
end
|
35
|
-
|
36
|
-
|
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
|
-
|
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
|
73
|
-
@
|
69
|
+
def serializer
|
70
|
+
@serializer ||=
|
74
71
|
case account.download_strategy
|
75
72
|
when "direct"
|
76
|
-
|
73
|
+
raw_serializer
|
77
74
|
when "delay_metadata"
|
78
|
-
Serializer::DelayedMetadataSerializer.new(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
|
85
|
-
@
|
81
|
+
def raw_serializer
|
82
|
+
@raw_serializer ||= Serializer.new(account.local_path, folder.name)
|
86
83
|
end
|
87
84
|
end
|
88
85
|
end
|
data/lib/imap/backup/account.rb
CHANGED
@@ -59,6 +59,7 @@ module Imap::Backup
|
|
59
59
|
attr_reader :reset_seen_flags_after_fetch
|
60
60
|
|
61
61
|
def initialize(options)
|
62
|
+
check_options!(options)
|
62
63
|
@username = options[:username]
|
63
64
|
@password = options[:password]
|
64
65
|
@local_path = options[:local_path]
|
@@ -97,14 +98,6 @@ module Imap::Backup
|
|
97
98
|
client.capability
|
98
99
|
end
|
99
100
|
|
100
|
-
# Indicates whether the account has been configured, and is ready
|
101
|
-
# to be used
|
102
|
-
#
|
103
|
-
# @return [Boolean]
|
104
|
-
def valid?
|
105
|
-
username && password ? true : false
|
106
|
-
end
|
107
|
-
|
108
101
|
def modified?
|
109
102
|
changes.any?
|
110
103
|
end
|
@@ -245,6 +238,23 @@ module Imap::Backup
|
|
245
238
|
|
246
239
|
attr_reader :changes
|
247
240
|
|
241
|
+
REQUIRED_ATTRIBUTES = %i[password username].freeze
|
242
|
+
OPTIONAL_ATTRIBUTES = %i[
|
243
|
+
connection_options download_strategy folders folder_blacklist local_path mirror_mode
|
244
|
+
multi_fetch_size reset_seen_flags_after_fetch server
|
245
|
+
].freeze
|
246
|
+
KNOWN_ATTRIBUTES = REQUIRED_ATTRIBUTES + OPTIONAL_ATTRIBUTES
|
247
|
+
|
248
|
+
def check_options!(options)
|
249
|
+
missing_required = REQUIRED_ATTRIBUTES - options.keys
|
250
|
+
if missing_required.any?
|
251
|
+
raise ArgumentError, "Missing required options: #{missing_required.join(', ')}"
|
252
|
+
end
|
253
|
+
|
254
|
+
unknown = options.keys - KNOWN_ATTRIBUTES
|
255
|
+
raise ArgumentError, "Unknown options: #{unknown.join(', ')}" if unknown.any?
|
256
|
+
end
|
257
|
+
|
248
258
|
def update(field, value)
|
249
259
|
key = :"@#{field}"
|
250
260
|
if changes[field]
|
@@ -28,7 +28,7 @@ module Imap::Backup
|
|
28
28
|
download_strategy: download_strategy,
|
29
29
|
folder_blacklist: folder_blacklist,
|
30
30
|
local_path: local_path,
|
31
|
-
|
31
|
+
mirror_mode: mirror,
|
32
32
|
reset_seen_flags_after_fetch: reset_seen_flags_after_fetch
|
33
33
|
)
|
34
34
|
account.connection_options = connection_options if connection_options
|
@@ -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,
|
26
|
+
def method_missing(method_name, ...)
|
27
27
|
if login_called
|
28
|
-
client.send(method_name,
|
28
|
+
client.send(method_name, ...)
|
29
29
|
else
|
30
30
|
do_first_login
|
31
|
-
client.send(method_name,
|
31
|
+
client.send(method_name, ...) if method_name != :login
|
32
32
|
end
|
33
33
|
end
|
34
34
|
|
data/lib/imap/backup/naming.rb
CHANGED
@@ -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}])
|
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
|
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({
|
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
|
-
#
|
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[:
|
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[:
|
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
|
-
#
|
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
|
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
|
data/lib/imap/backup/version.rb
CHANGED
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:
|
4
|
+
version: 15.0.1
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Joe Yates
|
8
|
-
autorequire:
|
8
|
+
autorequire:
|
9
9
|
bindir: bin
|
10
10
|
cert_chain: []
|
11
|
-
date: 2024-
|
11
|
+
date: 2024-06-04 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: highline
|
@@ -218,7 +218,7 @@ licenses:
|
|
218
218
|
- MIT
|
219
219
|
metadata:
|
220
220
|
rubygems_mfa_required: 'true'
|
221
|
-
post_install_message:
|
221
|
+
post_install_message:
|
222
222
|
rdoc_options: []
|
223
223
|
require_paths:
|
224
224
|
- lib
|
@@ -226,7 +226,7 @@ required_ruby_version: !ruby/object:Gem::Requirement
|
|
226
226
|
requirements:
|
227
227
|
- - ">="
|
228
228
|
- !ruby/object:Gem::Version
|
229
|
-
version: '
|
229
|
+
version: '3.0'
|
230
230
|
required_rubygems_version: !ruby/object:Gem::Requirement
|
231
231
|
requirements:
|
232
232
|
- - ">="
|
@@ -234,7 +234,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
|
|
234
234
|
version: '0'
|
235
235
|
requirements: []
|
236
236
|
rubygems_version: 3.5.3
|
237
|
-
signing_key:
|
237
|
+
signing_key:
|
238
238
|
specification_version: 4
|
239
239
|
summary: Backup GMail (or other IMAP) accounts to disk
|
240
240
|
test_files: []
|