imap-backup 14.6.0 → 15.0.1
Sign up to get free protection for your applications and to get access to all the features.
- 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: []
|