imap-backup 10.0.1 → 11.0.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 +4 -4
- data/README.md +48 -0
- data/docs/development.md +9 -0
- data/lib/imap/backup/account/backup.rb +9 -2
- data/lib/imap/backup/account.rb +16 -7
- data/lib/imap/backup/cli/local.rb +0 -1
- data/lib/imap/backup/cli/remote.rb +16 -0
- data/lib/imap/backup/client/default.rb +1 -1
- data/lib/imap/backup/configuration.rb +29 -2
- data/lib/imap/backup/serializer/appender.rb +31 -17
- data/lib/imap/backup/serializer/imap.rb +34 -4
- data/lib/imap/backup/serializer/mbox.rb +30 -4
- data/lib/imap/backup/serializer.rb +50 -6
- data/lib/imap/backup/setup.rb +11 -0
- data/lib/imap/backup/version.rb +3 -3
- metadata +4 -4
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: e9cff069fefde8147d1995b254f3f0613575ff4f52232c2fc64c91dc00c49bde
|
4
|
+
data.tar.gz: a1b8cb3d48f41b2773fd20b842b8d3e3b71e673b6797c83a345e341d087060d8
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 4e7b83bac1004b853287689e98ed4d03d7c3c87bd05c4a22831ffe592782ff8916ab576778e41de572f872505e3beab5b923ff5d1d78ccf3e1a99d701ff95737
|
7
|
+
data.tar.gz: a0cc1cc27b225c315a68743dff999fc2d8b0d0c9fd5d68f03641b59d53ddc491da21f318b9c075927263367b9db0a50e80af727de0445c86a049bc55d2a32fdf
|
data/README.md
CHANGED
@@ -100,6 +100,54 @@ For more information about a command, run
|
|
100
100
|
imap-backup help COMMAND
|
101
101
|
```
|
102
102
|
|
103
|
+
# Performace
|
104
|
+
|
105
|
+
There are a couple of performance tweaks that you can use
|
106
|
+
to improve backup speed.
|
107
|
+
|
108
|
+
These are activated via two settings:
|
109
|
+
|
110
|
+
* Global setting "Delay download writes"
|
111
|
+
* Account setting "Multi-fetch size"
|
112
|
+
|
113
|
+
As with all performance tweaks, there are trade-offs.
|
114
|
+
If you are using a small virtual server or Raspberry Pi
|
115
|
+
to run your backups, you will probably want to leave
|
116
|
+
the deafult settings.
|
117
|
+
If, on the other hand, you are using a computer with a
|
118
|
+
fair bit of RAM, and you are dealing with a *lot* of email,
|
119
|
+
then changing these settings may be worthwhile.
|
120
|
+
|
121
|
+
## Delay download writes
|
122
|
+
|
123
|
+
This setting affects all account backups.
|
124
|
+
|
125
|
+
When not set, each message is written to disk, one at a time.
|
126
|
+
Doing so means the message itself is appended to the MBox file,
|
127
|
+
but more importantly, the JSON metadata is rewritten to disk
|
128
|
+
from scratch.
|
129
|
+
|
130
|
+
When in use, all of a mailboxes unbackupped messages are
|
131
|
+
downloaded first, and then written to disk just once.
|
132
|
+
|
133
|
+
This speeds up backup as the metadata file is not rewritten
|
134
|
+
after each message is added, but it potentially uses much more memory.
|
135
|
+
|
136
|
+
## Multi-fetch Size
|
137
|
+
|
138
|
+
By default, during backup, each message is downloaded one-by-one.
|
139
|
+
|
140
|
+
Using this setting, you can download chunks of emails at a time,
|
141
|
+
potentially speeding up the process.
|
142
|
+
|
143
|
+
If you're not using "Delayed downlaod writes",
|
144
|
+
using multi-fetch *will* mean that the backup process will use
|
145
|
+
more memory - equivalent to the size of the greater number
|
146
|
+
of messages downloaded at a time.
|
147
|
+
|
148
|
+
This behaviour may also exceed limits on your email provider,
|
149
|
+
so it's best to check before cranking it up!
|
150
|
+
|
103
151
|
# Troubleshooting
|
104
152
|
|
105
153
|
If you have problems:
|
data/docs/development.md
CHANGED
@@ -43,6 +43,15 @@ or
|
|
43
43
|
$ rspec --tag ~docker
|
44
44
|
```
|
45
45
|
|
46
|
+
# Performance Specs
|
47
|
+
|
48
|
+
```sh
|
49
|
+
PERFORMANCE=1 rspec --order=defined
|
50
|
+
```
|
51
|
+
|
52
|
+
Beware: the performance spec (just backup for now) takes a very
|
53
|
+
long time to run, approximately 24 hours!
|
54
|
+
|
46
55
|
### Debugging
|
47
56
|
|
48
57
|
The feature specs are run 'out of process' via the Aruba gem.
|
@@ -38,12 +38,19 @@ module Imap::Backup
|
|
38
38
|
|
39
39
|
Logger.logger.debug "[#{folder.name}] running backup"
|
40
40
|
serializer.apply_uid_validity(folder.uid_validity)
|
41
|
-
Downloader.new(
|
41
|
+
downloader = Downloader.new(
|
42
42
|
folder,
|
43
43
|
serializer,
|
44
44
|
multi_fetch_size: account.multi_fetch_size,
|
45
45
|
reset_seen_flags_after_fetch: account.reset_seen_flags_after_fetch
|
46
|
-
)
|
46
|
+
)
|
47
|
+
if account.delay_download_writes
|
48
|
+
serializer.transaction do
|
49
|
+
downloader.run
|
50
|
+
end
|
51
|
+
else
|
52
|
+
downloader.run
|
53
|
+
end
|
47
54
|
if account.mirror_mode
|
48
55
|
Logger.logger.info "Mirror mode - Deleting messages only present locally"
|
49
56
|
LocalOnlyMessageDeleter.new(folder, serializer).run
|
data/lib/imap/backup/account.rb
CHANGED
@@ -15,6 +15,7 @@ module Imap::Backup
|
|
15
15
|
attr_reader :mirror_mode
|
16
16
|
attr_reader :server
|
17
17
|
attr_reader :connection_options
|
18
|
+
attr_accessor :delay_download_writes
|
18
19
|
attr_reader :reset_seen_flags_after_fetch
|
19
20
|
attr_reader :changes
|
20
21
|
|
@@ -27,7 +28,8 @@ module Imap::Backup
|
|
27
28
|
@mirror_mode = options[:mirror_mode]
|
28
29
|
@server = options[:server]
|
29
30
|
@connection_options = options[:connection_options]
|
30
|
-
@
|
31
|
+
@delay_download_writes = true
|
32
|
+
@multi_fetch_size_orignal = options[:multi_fetch_size]
|
31
33
|
@reset_seen_flags_after_fetch = options[:reset_seen_flags_after_fetch]
|
32
34
|
@client = nil
|
33
35
|
@changes = {}
|
@@ -42,6 +44,10 @@ module Imap::Backup
|
|
42
44
|
client.namespace
|
43
45
|
end
|
44
46
|
|
47
|
+
def capabilities
|
48
|
+
client.capability
|
49
|
+
end
|
50
|
+
|
45
51
|
def restore
|
46
52
|
restore = Account::Restore.new(account: self)
|
47
53
|
restore.run
|
@@ -75,7 +81,8 @@ module Imap::Backup
|
|
75
81
|
h[:mirror_mode] = true if @mirror_mode
|
76
82
|
h[:server] = @server if @server
|
77
83
|
h[:connection_options] = @connection_options if @connection_options
|
78
|
-
h[:
|
84
|
+
h[:delay_download_writes] = delay_download_writes
|
85
|
+
h[:multi_fetch_size] = multi_fetch_size
|
79
86
|
if @reset_seen_flags_after_fetch
|
80
87
|
h[:reset_seen_flags_after_fetch] = @reset_seen_flags_after_fetch
|
81
88
|
end
|
@@ -123,11 +130,13 @@ module Imap::Backup
|
|
123
130
|
end
|
124
131
|
|
125
132
|
def multi_fetch_size
|
126
|
-
|
127
|
-
|
128
|
-
int
|
129
|
-
|
130
|
-
|
133
|
+
@multi_fetch_size ||= begin
|
134
|
+
int = @multi_fetch_size_orignal.to_i
|
135
|
+
if int.positive?
|
136
|
+
int
|
137
|
+
else
|
138
|
+
DEFAULT_MULTI_FETCH_SIZE
|
139
|
+
end
|
131
140
|
end
|
132
141
|
end
|
133
142
|
|
@@ -41,7 +41,6 @@ module Imap::Backup
|
|
41
41
|
results = requested_accounts(config).map do |account|
|
42
42
|
serialized_folders = Account::SerializedFolders.new(account: account)
|
43
43
|
folder_results = serialized_folders.map do |serializer, _folder|
|
44
|
-
puts "serializer: #{serializer.inspect}"
|
45
44
|
serializer.check_integrity!
|
46
45
|
{name: serializer.folder, result: "OK"}
|
47
46
|
rescue Serializer::FolderIntegrityError => e
|
@@ -23,6 +23,22 @@ module Imap::Backup
|
|
23
23
|
end
|
24
24
|
end
|
25
25
|
|
26
|
+
desc "capabilities EMAIL", "List server capabilities"
|
27
|
+
long_desc <<~DESC
|
28
|
+
Lists the IMAP capabilities supported by the IMAP server.
|
29
|
+
DESC
|
30
|
+
config_option
|
31
|
+
format_option
|
32
|
+
quiet_option
|
33
|
+
verbose_option
|
34
|
+
def capabilities(email)
|
35
|
+
Imap::Backup::Logger.setup_logging options
|
36
|
+
config = load_config(**options)
|
37
|
+
account = account(config, email)
|
38
|
+
capabilities = account.capabilities
|
39
|
+
Kernel.puts capabilities.join(", ")
|
40
|
+
end
|
41
|
+
|
26
42
|
desc "namespaces EMAIL", "List account namespaces"
|
27
43
|
long_desc <<~DESC
|
28
44
|
Lists namespaces defined for an email account.
|
@@ -14,6 +14,8 @@ module Imap::Backup
|
|
14
14
|
VERSION = "2.0".freeze
|
15
15
|
|
16
16
|
attr_reader :pathname
|
17
|
+
attr_reader :delay_download_writes
|
18
|
+
attr_reader :delay_download_writes_modified
|
17
19
|
|
18
20
|
def self.default_pathname
|
19
21
|
File.join(CONFIGURATION_DIRECTORY, "config.json")
|
@@ -25,6 +27,8 @@ module Imap::Backup
|
|
25
27
|
|
26
28
|
def initialize(path: nil)
|
27
29
|
@pathname = path || self.class.default_pathname
|
30
|
+
@delay_download_writes = false
|
31
|
+
@delay_download_writes_modified = false
|
28
32
|
end
|
29
33
|
|
30
34
|
def path
|
@@ -39,7 +43,8 @@ module Imap::Backup
|
|
39
43
|
remove_deleted_accounts
|
40
44
|
save_data = {
|
41
45
|
version: VERSION,
|
42
|
-
accounts: accounts.map(&:to_h)
|
46
|
+
accounts: accounts.map(&:to_h),
|
47
|
+
delay_download_writes: delay_download_writes
|
43
48
|
}
|
44
49
|
File.open(pathname, "w") { |f| f.write(JSON.pretty_generate(save_data)) }
|
45
50
|
FileUtils.chmod(0o600, pathname) if !windows?
|
@@ -49,13 +54,26 @@ module Imap::Backup
|
|
49
54
|
def accounts
|
50
55
|
@accounts ||= begin
|
51
56
|
ensure_loaded!
|
52
|
-
data[:accounts].map
|
57
|
+
accounts = data[:accounts].map do |attr|
|
58
|
+
Account.new(attr)
|
59
|
+
end
|
60
|
+
inject_global_attributes(accounts)
|
53
61
|
end
|
54
62
|
end
|
55
63
|
|
64
|
+
def delay_download_writes=(value)
|
65
|
+
ensure_loaded!
|
66
|
+
|
67
|
+
@delay_download_writes = value
|
68
|
+
@delay_download_writes_modified = true
|
69
|
+
inject_global_attributes(accounts)
|
70
|
+
end
|
71
|
+
|
56
72
|
def modified?
|
57
73
|
ensure_loaded!
|
58
74
|
|
75
|
+
return true if delay_download_writes_modified
|
76
|
+
|
59
77
|
accounts.any? { |a| a.modified? || a.marked_for_deletion? }
|
60
78
|
end
|
61
79
|
|
@@ -65,6 +83,7 @@ module Imap::Backup
|
|
65
83
|
return true if @data
|
66
84
|
|
67
85
|
data
|
86
|
+
@delay_download_writes = data[:delay_download_writes]
|
68
87
|
true
|
69
88
|
end
|
70
89
|
|
@@ -83,6 +102,7 @@ module Imap::Backup
|
|
83
102
|
end
|
84
103
|
|
85
104
|
def remove_modified_flags
|
105
|
+
@delay_download_writes_modified = false
|
86
106
|
accounts.each(&:clear_changes)
|
87
107
|
end
|
88
108
|
|
@@ -90,6 +110,13 @@ module Imap::Backup
|
|
90
110
|
accounts.reject!(&:marked_for_deletion?)
|
91
111
|
end
|
92
112
|
|
113
|
+
def inject_global_attributes(accounts)
|
114
|
+
accounts.map do |a|
|
115
|
+
a.delay_download_writes = delay_download_writes
|
116
|
+
a
|
117
|
+
end
|
118
|
+
end
|
119
|
+
|
93
120
|
def make_private(path)
|
94
121
|
FileUtils.chmod(0o700, path) if FileMode.new(filename: path).mode != 0o700
|
95
122
|
end
|
@@ -14,7 +14,7 @@ module Imap::Backup
|
|
14
14
|
@mbox = mbox
|
15
15
|
end
|
16
16
|
|
17
|
-
def
|
17
|
+
def single(uid:, message:, flags:)
|
18
18
|
raise "Can't add messages without uid_validity" if !imap.uid_validity
|
19
19
|
|
20
20
|
uid = uid.to_i
|
@@ -26,29 +26,43 @@ module Imap::Backup
|
|
26
26
|
return
|
27
27
|
end
|
28
28
|
|
29
|
-
|
29
|
+
rollback_on_error do
|
30
|
+
do_append uid, message, flags
|
31
|
+
rescue StandardError => e
|
32
|
+
raise <<-ERROR.gsub(/^\s*/m, "")
|
33
|
+
[#{folder}] failed to append message #{uid}:
|
34
|
+
#{message}. #{e}:
|
35
|
+
#{e.backtrace.join("\n")}"
|
36
|
+
ERROR
|
37
|
+
end
|
38
|
+
end
|
39
|
+
|
40
|
+
def multi(appends)
|
41
|
+
rollback_on_error do
|
42
|
+
appends.each do |a|
|
43
|
+
do_append a[:uid], a[:message], a[:flags]
|
44
|
+
end
|
45
|
+
end
|
30
46
|
end
|
31
47
|
|
32
48
|
private
|
33
49
|
|
34
50
|
def do_append(uid, message, flags)
|
35
51
|
mboxrd_message = Email::Mboxrd::Message.new(message)
|
36
|
-
|
37
|
-
|
38
|
-
|
39
|
-
|
40
|
-
mbox.append serialized
|
41
|
-
mbox_appended = true
|
42
|
-
imap.append uid, serialized.length, flags
|
43
|
-
rescue StandardError => e
|
44
|
-
mbox.rewind(initial) if mbox_appended
|
52
|
+
serialized = mboxrd_message.to_serialized
|
53
|
+
mbox.append serialized
|
54
|
+
imap.append uid, serialized.length, flags: flags
|
55
|
+
end
|
45
56
|
|
46
|
-
|
47
|
-
|
48
|
-
|
49
|
-
|
50
|
-
|
51
|
-
|
57
|
+
def rollback_on_error(&block)
|
58
|
+
imap.transaction do
|
59
|
+
mbox.transaction do
|
60
|
+
block.call
|
61
|
+
rescue StandardError => e
|
62
|
+
Logger.logger.error e
|
63
|
+
imap.rollback
|
64
|
+
mbox.rollback
|
65
|
+
end
|
52
66
|
end
|
53
67
|
end
|
54
68
|
end
|
@@ -15,6 +15,26 @@ module Imap::Backup
|
|
15
15
|
@uid_validity = nil
|
16
16
|
@messages = nil
|
17
17
|
@version = nil
|
18
|
+
@savepoint = nil
|
19
|
+
end
|
20
|
+
|
21
|
+
def transaction(&block)
|
22
|
+
fail_in_transaction!(message: "Serializer::Imap: nested transactions are not supported")
|
23
|
+
|
24
|
+
ensure_loaded
|
25
|
+
@savepoint = {messages: messages.dup, uid_validity: uid_validity}
|
26
|
+
|
27
|
+
block.call
|
28
|
+
|
29
|
+
@savepoint = nil
|
30
|
+
save
|
31
|
+
end
|
32
|
+
|
33
|
+
def rollback
|
34
|
+
fail_outside_transaction!
|
35
|
+
|
36
|
+
@messages = @savepoint[:messages]
|
37
|
+
@uid_validity = @savepoint[:uid_validity]
|
18
38
|
end
|
19
39
|
|
20
40
|
def pathname
|
@@ -33,7 +53,7 @@ module Imap::Backup
|
|
33
53
|
true
|
34
54
|
end
|
35
55
|
|
36
|
-
def append(uid, length, flags
|
56
|
+
def append(uid, length, flags: [])
|
37
57
|
offset =
|
38
58
|
if messages.empty?
|
39
59
|
0
|
@@ -109,14 +129,16 @@ module Imap::Backup
|
|
109
129
|
end
|
110
130
|
|
111
131
|
def save
|
132
|
+
return if @savepoint
|
133
|
+
|
112
134
|
ensure_loaded
|
113
135
|
|
114
136
|
raise "Cannot save metadata without a uid_validity" if !uid_validity
|
115
137
|
|
116
138
|
data = {
|
117
|
-
version:
|
118
|
-
uid_validity:
|
119
|
-
messages:
|
139
|
+
version: version,
|
140
|
+
uid_validity: uid_validity,
|
141
|
+
messages: messages.map(&:to_h)
|
120
142
|
}
|
121
143
|
content = data.to_json
|
122
144
|
File.open(pathname, "w") { |f| f.write content }
|
@@ -162,5 +184,13 @@ module Imap::Backup
|
|
162
184
|
def mbox
|
163
185
|
@mbox ||= Serializer::Mbox.new(folder_path)
|
164
186
|
end
|
187
|
+
|
188
|
+
def fail_in_transaction!(message: "Serializer::Imap: method not supported inside trasactions")
|
189
|
+
raise message if @savepoint
|
190
|
+
end
|
191
|
+
|
192
|
+
def fail_outside_transaction!
|
193
|
+
raise "This method can only be called inside a transaction" if !@savepoint
|
194
|
+
end
|
165
195
|
end
|
166
196
|
end
|
@@ -3,9 +3,25 @@ module Imap; end
|
|
3
3
|
module Imap::Backup
|
4
4
|
class Serializer::Mbox
|
5
5
|
attr_reader :folder_path
|
6
|
+
attr_reader :savepoint
|
6
7
|
|
7
8
|
def initialize(folder_path)
|
8
9
|
@folder_path = folder_path
|
10
|
+
@savepoint = nil
|
11
|
+
end
|
12
|
+
|
13
|
+
def transaction(&block)
|
14
|
+
fail_in_transaction!(message: "Nested transactions are not supported")
|
15
|
+
|
16
|
+
@savepoint = {length: length}
|
17
|
+
block.call
|
18
|
+
@savepoint = nil
|
19
|
+
end
|
20
|
+
|
21
|
+
def rollback
|
22
|
+
fail_outside_transaction!
|
23
|
+
|
24
|
+
rewind(@savepoint[:length])
|
9
25
|
end
|
10
26
|
|
11
27
|
def valid?
|
@@ -31,6 +47,10 @@ module Imap::Backup
|
|
31
47
|
File.unlink(pathname)
|
32
48
|
end
|
33
49
|
|
50
|
+
def exist?
|
51
|
+
File.exist?(pathname)
|
52
|
+
end
|
53
|
+
|
34
54
|
def length
|
35
55
|
return nil if !exist?
|
36
56
|
|
@@ -51,18 +71,24 @@ module Imap::Backup
|
|
51
71
|
end
|
52
72
|
end
|
53
73
|
|
74
|
+
def touch
|
75
|
+
File.open(pathname, "a") {}
|
76
|
+
end
|
77
|
+
|
78
|
+
private
|
79
|
+
|
54
80
|
def rewind(length)
|
55
81
|
File.open(pathname, File::RDWR | File::CREAT, 0o644) do |f|
|
56
82
|
f.truncate(length)
|
57
83
|
end
|
58
84
|
end
|
59
85
|
|
60
|
-
def
|
61
|
-
|
86
|
+
def fail_in_transaction!(message: "Method not supported inside trasactions")
|
87
|
+
raise message if savepoint
|
62
88
|
end
|
63
89
|
|
64
|
-
def
|
65
|
-
|
90
|
+
def fail_outside_transaction!
|
91
|
+
raise "This method can only be called inside a transaction" if !savepoint
|
66
92
|
end
|
67
93
|
end
|
68
94
|
end
|
@@ -28,16 +28,36 @@ module Imap::Backup
|
|
28
28
|
|
29
29
|
attr_reader :folder
|
30
30
|
attr_reader :path
|
31
|
+
attr_reader :dirty
|
31
32
|
|
32
33
|
def initialize(path, folder)
|
33
34
|
@path = path
|
34
35
|
@folder = folder
|
35
36
|
@validated = nil
|
37
|
+
@dirty = nil
|
38
|
+
end
|
39
|
+
|
40
|
+
def transaction(&block)
|
41
|
+
fail_in_transaction!(:transaction, message: "nested transactions are not supported")
|
42
|
+
|
43
|
+
validate!
|
44
|
+
@dirty = {append: []}
|
45
|
+
|
46
|
+
block.call
|
47
|
+
|
48
|
+
if dirty[:append].any?
|
49
|
+
appender = Serializer::Appender.new(folder: sanitized, imap: imap, mbox: mbox)
|
50
|
+
appender.multi(dirty[:append])
|
51
|
+
end
|
52
|
+
|
53
|
+
@dirty = nil
|
36
54
|
end
|
37
55
|
|
38
56
|
# Returns true if there are existing, valid files
|
39
57
|
# false otherwise (in which case any existing files are deleted)
|
40
58
|
def validate!
|
59
|
+
fail_in_transaction!(:validate!)
|
60
|
+
|
41
61
|
return true if @validated
|
42
62
|
|
43
63
|
optionally_migrate2to3
|
@@ -53,6 +73,8 @@ module Imap::Backup
|
|
53
73
|
end
|
54
74
|
|
55
75
|
def check_integrity!
|
76
|
+
fail_in_transaction!(:check_integrity!)
|
77
|
+
|
56
78
|
if !imap.valid?
|
57
79
|
message = ".imap file '#{imap.pathname}' is corrupt"
|
58
80
|
raise FolderIntegrityError, message
|
@@ -104,6 +126,8 @@ module Imap::Backup
|
|
104
126
|
end
|
105
127
|
|
106
128
|
def delete
|
129
|
+
fail_in_transaction!(:delete)
|
130
|
+
|
107
131
|
imap.delete
|
108
132
|
@imap = nil
|
109
133
|
mbox.delete
|
@@ -111,6 +135,7 @@ module Imap::Backup
|
|
111
135
|
end
|
112
136
|
|
113
137
|
def apply_uid_validity(value)
|
138
|
+
fail_in_transaction!(:apply_uid_validity)
|
114
139
|
validate!
|
115
140
|
|
116
141
|
case
|
@@ -126,19 +151,26 @@ module Imap::Backup
|
|
126
151
|
end
|
127
152
|
|
128
153
|
def force_uid_validity(value)
|
154
|
+
fail_in_transaction!(:force_uid_validity)
|
129
155
|
validate!
|
130
156
|
|
131
157
|
internal_force_uid_validity(value)
|
132
158
|
end
|
133
159
|
|
134
160
|
def append(uid, message, flags)
|
135
|
-
|
161
|
+
if dirty
|
162
|
+
dirty[:append] << {uid: uid, message: message, flags: flags}
|
163
|
+
else
|
164
|
+
validate!
|
136
165
|
|
137
|
-
|
138
|
-
|
166
|
+
appender = Serializer::Appender.new(folder: sanitized, imap: imap, mbox: mbox)
|
167
|
+
appender.single(uid: uid, message: message, flags: flags)
|
168
|
+
end
|
139
169
|
end
|
140
170
|
|
141
171
|
def update(uid, flags: nil)
|
172
|
+
fail_in_transaction!(:update)
|
173
|
+
|
142
174
|
message = imap.get(uid)
|
143
175
|
return if !message
|
144
176
|
|
@@ -147,17 +179,21 @@ module Imap::Backup
|
|
147
179
|
end
|
148
180
|
|
149
181
|
def each_message(required_uids = nil, &block)
|
182
|
+
fail_in_transaction!(:each_message)
|
183
|
+
|
184
|
+
return enum_for(:each_message, required_uids) if !block
|
185
|
+
|
150
186
|
required_uids ||= uids
|
151
187
|
|
152
188
|
validate!
|
153
189
|
|
154
|
-
return enum_for(:each_message, required_uids) if !block
|
155
|
-
|
156
190
|
enumerator = Serializer::MessageEnumerator.new(imap: imap)
|
157
191
|
enumerator.run(uids: required_uids, &block)
|
158
192
|
end
|
159
193
|
|
160
194
|
def filter(&block)
|
195
|
+
fail_in_transaction!(:filter)
|
196
|
+
|
161
197
|
temp_name = Serializer::UnusedNameFinder.new(serializer: self).run
|
162
198
|
temp_folder_path = self.class.folder_path_for(path: path, folder: temp_name)
|
163
199
|
new_mbox = Serializer::Mbox.new(temp_folder_path)
|
@@ -167,7 +203,7 @@ module Imap::Backup
|
|
167
203
|
enumerator = Serializer::MessageEnumerator.new(imap: imap)
|
168
204
|
enumerator.run(uids: uids) do |message|
|
169
205
|
keep = block.call(message)
|
170
|
-
appender.
|
206
|
+
appender.single(uid: message.uid, message: message.body, flags: message.flags) if keep
|
171
207
|
end
|
172
208
|
imap.delete
|
173
209
|
new_imap.rename imap.folder_path
|
@@ -251,5 +287,13 @@ module Imap::Backup
|
|
251
287
|
rename new_name
|
252
288
|
new_name
|
253
289
|
end
|
290
|
+
|
291
|
+
def fail_in_transaction!(method, message: "not supported inside trasactions")
|
292
|
+
raise "Serializer##{method} #{message}" if dirty
|
293
|
+
end
|
294
|
+
|
295
|
+
def fail_outside_transaction!(method)
|
296
|
+
raise "Serializer##{method} can only be called inside a transaction" if !dirty
|
297
|
+
end
|
254
298
|
end
|
255
299
|
end
|
data/lib/imap/backup/setup.rb
CHANGED
@@ -39,6 +39,7 @@ module Imap::Backup
|
|
39
39
|
MENU
|
40
40
|
account_items menu
|
41
41
|
add_account_item menu
|
42
|
+
toggle_delay_download_writes menu
|
42
43
|
if config.modified?
|
43
44
|
menu.choice("save and exit") do
|
44
45
|
config.save
|
@@ -70,6 +71,16 @@ module Imap::Backup
|
|
70
71
|
end
|
71
72
|
end
|
72
73
|
|
74
|
+
def toggle_delay_download_writes(menu)
|
75
|
+
new_value = config.delay_download_writes ? false : true
|
76
|
+
modified = config.delay_download_writes_modified ? " *" : ""
|
77
|
+
change = config.delay_download_writes ? "don't delay" : "delay"
|
78
|
+
menu_item = "#{change} download writes#{modified}"
|
79
|
+
menu.choice(menu_item) do
|
80
|
+
config.delay_download_writes = new_value
|
81
|
+
end
|
82
|
+
end
|
83
|
+
|
73
84
|
def default_account_config(username)
|
74
85
|
Imap::Backup::Account.new(
|
75
86
|
username: username,
|
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: 11.0.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-
|
11
|
+
date: 2023-07-18 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: highline
|
@@ -305,9 +305,9 @@ required_ruby_version: !ruby/object:Gem::Requirement
|
|
305
305
|
version: '2.6'
|
306
306
|
required_rubygems_version: !ruby/object:Gem::Requirement
|
307
307
|
requirements:
|
308
|
-
- - "
|
308
|
+
- - ">"
|
309
309
|
- !ruby/object:Gem::Version
|
310
|
-
version:
|
310
|
+
version: 1.3.1
|
311
311
|
requirements: []
|
312
312
|
rubygems_version: 3.3.7
|
313
313
|
signing_key:
|