imap-backup 10.0.1 → 11.0.0.rc1
Sign up to get free protection for your applications and to get access to all the features.
- 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:
|