imap-backup 10.0.0 → 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 +11 -2
- data/lib/imap/backup/account/backup_folders.rb +2 -0
- data/lib/imap/backup/account/client_factory.rb +2 -0
- data/lib/imap/backup/account/folder.rb +2 -0
- data/lib/imap/backup/account/folder_ensurer.rb +2 -0
- data/lib/imap/backup/account/local_only_folder_deleter.rb +2 -0
- data/lib/imap/backup/account/restore.rb +2 -0
- data/lib/imap/backup/account/serialized_folders.rb +2 -0
- data/lib/imap/backup/account.rb +16 -7
- data/lib/imap/backup/cli/backup.rb +2 -0
- data/lib/imap/backup/cli/folder_enumerator.rb +2 -0
- data/lib/imap/backup/cli/helpers.rb +2 -0
- data/lib/imap/backup/cli/local.rb +2 -1
- data/lib/imap/backup/cli/migrate.rb +2 -0
- data/lib/imap/backup/cli/mirror.rb +2 -0
- data/lib/imap/backup/cli/remote.rb +18 -0
- data/lib/imap/backup/cli/restore.rb +2 -0
- data/lib/imap/backup/cli/setup.rb +2 -0
- data/lib/imap/backup/cli/stats.rb +2 -0
- data/lib/imap/backup/cli/utils.rb +2 -0
- data/lib/imap/backup/client/apple_mail.rb +2 -0
- data/lib/imap/backup/client/default.rb +3 -1
- data/lib/imap/backup/configuration.rb +31 -2
- data/lib/imap/backup/downloader.rb +2 -0
- data/lib/imap/backup/file_mode.rb +2 -0
- data/lib/imap/backup/flag_refresher.rb +2 -0
- data/lib/imap/backup/local_only_message_deleter.rb +2 -0
- data/lib/imap/backup/logger.rb +2 -0
- data/lib/imap/backup/migrator.rb +2 -0
- data/lib/imap/backup/mirror/map.rb +2 -0
- data/lib/imap/backup/mirror.rb +2 -0
- data/lib/imap/backup/naming.rb +2 -0
- data/lib/imap/backup/serializer/appender.rb +33 -17
- data/lib/imap/backup/serializer/directory.rb +2 -0
- data/lib/imap/backup/serializer/folder_maker.rb +2 -0
- data/lib/imap/backup/serializer/imap.rb +36 -4
- data/lib/imap/backup/serializer/mbox.rb +32 -4
- data/lib/imap/backup/serializer/message.rb +2 -0
- data/lib/imap/backup/serializer/message_enumerator.rb +2 -0
- data/lib/imap/backup/serializer/permission_checker.rb +2 -0
- data/lib/imap/backup/serializer/unused_name_finder.rb +2 -0
- data/lib/imap/backup/serializer/version2_migrator.rb +2 -0
- data/lib/imap/backup/serializer.rb +52 -6
- data/lib/imap/backup/setup/account/header.rb +2 -0
- data/lib/imap/backup/setup/account.rb +2 -0
- data/lib/imap/backup/setup/asker.rb +2 -0
- data/lib/imap/backup/setup/backup_path.rb +2 -0
- data/lib/imap/backup/setup/connection_tester.rb +2 -0
- data/lib/imap/backup/setup/email.rb +2 -0
- data/lib/imap/backup/setup/folder_chooser.rb +2 -0
- data/lib/imap/backup/setup/helpers.rb +2 -0
- data/lib/imap/backup/setup.rb +13 -0
- data/lib/imap/backup/thunderbird/mailbox_exporter.rb +2 -0
- data/lib/imap/backup/uploader.rb +2 -0
- data/lib/imap/backup/version.rb +2 -2
- data/lib/imap/backup.rb +2 -2
- 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.
|
@@ -5,6 +5,8 @@ require "imap/backup/downloader"
|
|
5
5
|
require "imap/backup/flag_refresher"
|
6
6
|
require "imap/backup/local_only_message_deleter"
|
7
7
|
|
8
|
+
module Imap; end
|
9
|
+
|
8
10
|
module Imap::Backup
|
9
11
|
class Account; end
|
10
12
|
|
@@ -36,12 +38,19 @@ module Imap::Backup
|
|
36
38
|
|
37
39
|
Logger.logger.debug "[#{folder.name}] running backup"
|
38
40
|
serializer.apply_uid_validity(folder.uid_validity)
|
39
|
-
Downloader.new(
|
41
|
+
downloader = Downloader.new(
|
40
42
|
folder,
|
41
43
|
serializer,
|
42
44
|
multi_fetch_size: account.multi_fetch_size,
|
43
45
|
reset_seen_flags_after_fetch: account.reset_seen_flags_after_fetch
|
44
|
-
)
|
46
|
+
)
|
47
|
+
if account.delay_download_writes
|
48
|
+
serializer.transaction do
|
49
|
+
downloader.run
|
50
|
+
end
|
51
|
+
else
|
52
|
+
downloader.run
|
53
|
+
end
|
45
54
|
if account.mirror_mode
|
46
55
|
Logger.logger.info "Mirror mode - Deleting messages only present locally"
|
47
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
|
|
@@ -1,3 +1,5 @@
|
|
1
|
+
module Imap; end
|
2
|
+
|
1
3
|
module Imap::Backup
|
2
4
|
class CLI::Local < Thor
|
3
5
|
include Thor::Actions
|
@@ -39,7 +41,6 @@ module Imap::Backup
|
|
39
41
|
results = requested_accounts(config).map do |account|
|
40
42
|
serialized_folders = Account::SerializedFolders.new(account: account)
|
41
43
|
folder_results = serialized_folders.map do |serializer, _folder|
|
42
|
-
puts "serializer: #{serializer.inspect}"
|
43
44
|
serializer.check_integrity!
|
44
45
|
{name: serializer.folder, result: "OK"}
|
45
46
|
rescue Serializer::FolderIntegrityError => e
|
@@ -1,5 +1,7 @@
|
|
1
1
|
require "imap/backup/logger"
|
2
2
|
|
3
|
+
module Imap; end
|
4
|
+
|
3
5
|
module Imap::Backup
|
4
6
|
class CLI::Remote < Thor
|
5
7
|
include Thor::Actions
|
@@ -21,6 +23,22 @@ module Imap::Backup
|
|
21
23
|
end
|
22
24
|
end
|
23
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
|
+
|
24
42
|
desc "namespaces EMAIL", "List account namespaces"
|
25
43
|
long_desc <<~DESC
|
26
44
|
Lists namespaces defined for an email account.
|
@@ -1,13 +1,15 @@
|
|
1
1
|
require "forwardable"
|
2
2
|
require "net/imap"
|
3
3
|
|
4
|
+
module Imap; end
|
5
|
+
|
4
6
|
module Imap::Backup
|
5
7
|
module Client; end
|
6
8
|
|
7
9
|
class Client::Default
|
8
10
|
extend Forwardable
|
9
11
|
def_delegators :imap, *%i(
|
10
|
-
append authenticate create expunge namespace
|
12
|
+
append authenticate capability create expunge namespace
|
11
13
|
responses uid_fetch uid_search uid_store
|
12
14
|
)
|
13
15
|
|
@@ -6,12 +6,16 @@ require "imap/backup/account"
|
|
6
6
|
require "imap/backup/file_mode"
|
7
7
|
require "imap/backup/serializer/permission_checker"
|
8
8
|
|
9
|
+
module Imap; end
|
10
|
+
|
9
11
|
module Imap::Backup
|
10
12
|
class Configuration
|
11
13
|
CONFIGURATION_DIRECTORY = File.expand_path("~/.imap-backup")
|
12
14
|
VERSION = "2.0".freeze
|
13
15
|
|
14
16
|
attr_reader :pathname
|
17
|
+
attr_reader :delay_download_writes
|
18
|
+
attr_reader :delay_download_writes_modified
|
15
19
|
|
16
20
|
def self.default_pathname
|
17
21
|
File.join(CONFIGURATION_DIRECTORY, "config.json")
|
@@ -23,6 +27,8 @@ module Imap::Backup
|
|
23
27
|
|
24
28
|
def initialize(path: nil)
|
25
29
|
@pathname = path || self.class.default_pathname
|
30
|
+
@delay_download_writes = false
|
31
|
+
@delay_download_writes_modified = false
|
26
32
|
end
|
27
33
|
|
28
34
|
def path
|
@@ -37,7 +43,8 @@ module Imap::Backup
|
|
37
43
|
remove_deleted_accounts
|
38
44
|
save_data = {
|
39
45
|
version: VERSION,
|
40
|
-
accounts: accounts.map(&:to_h)
|
46
|
+
accounts: accounts.map(&:to_h),
|
47
|
+
delay_download_writes: delay_download_writes
|
41
48
|
}
|
42
49
|
File.open(pathname, "w") { |f| f.write(JSON.pretty_generate(save_data)) }
|
43
50
|
FileUtils.chmod(0o600, pathname) if !windows?
|
@@ -47,13 +54,26 @@ module Imap::Backup
|
|
47
54
|
def accounts
|
48
55
|
@accounts ||= begin
|
49
56
|
ensure_loaded!
|
50
|
-
data[:accounts].map
|
57
|
+
accounts = data[:accounts].map do |attr|
|
58
|
+
Account.new(attr)
|
59
|
+
end
|
60
|
+
inject_global_attributes(accounts)
|
51
61
|
end
|
52
62
|
end
|
53
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
|
+
|
54
72
|
def modified?
|
55
73
|
ensure_loaded!
|
56
74
|
|
75
|
+
return true if delay_download_writes_modified
|
76
|
+
|
57
77
|
accounts.any? { |a| a.modified? || a.marked_for_deletion? }
|
58
78
|
end
|
59
79
|
|
@@ -63,6 +83,7 @@ module Imap::Backup
|
|
63
83
|
return true if @data
|
64
84
|
|
65
85
|
data
|
86
|
+
@delay_download_writes = data[:delay_download_writes]
|
66
87
|
true
|
67
88
|
end
|
68
89
|
|
@@ -81,6 +102,7 @@ module Imap::Backup
|
|
81
102
|
end
|
82
103
|
|
83
104
|
def remove_modified_flags
|
105
|
+
@delay_download_writes_modified = false
|
84
106
|
accounts.each(&:clear_changes)
|
85
107
|
end
|
86
108
|
|
@@ -88,6 +110,13 @@ module Imap::Backup
|
|
88
110
|
accounts.reject!(&:marked_for_deletion?)
|
89
111
|
end
|
90
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
|
+
|
91
120
|
def make_private(path)
|
92
121
|
FileUtils.chmod(0o700, path) if FileMode.new(filename: path).mode != 0o700
|
93
122
|
end
|
data/lib/imap/backup/logger.rb
CHANGED
data/lib/imap/backup/migrator.rb
CHANGED
data/lib/imap/backup/mirror.rb
CHANGED
data/lib/imap/backup/naming.rb
CHANGED
@@ -1,3 +1,5 @@
|
|
1
|
+
module Imap; end
|
2
|
+
|
1
3
|
module Imap::Backup
|
2
4
|
class Serializer; end
|
3
5
|
|
@@ -12,7 +14,7 @@ module Imap::Backup
|
|
12
14
|
@mbox = mbox
|
13
15
|
end
|
14
16
|
|
15
|
-
def
|
17
|
+
def single(uid:, message:, flags:)
|
16
18
|
raise "Can't add messages without uid_validity" if !imap.uid_validity
|
17
19
|
|
18
20
|
uid = uid.to_i
|
@@ -24,29 +26,43 @@ module Imap::Backup
|
|
24
26
|
return
|
25
27
|
end
|
26
28
|
|
27
|
-
|
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
|
28
46
|
end
|
29
47
|
|
30
48
|
private
|
31
49
|
|
32
50
|
def do_append(uid, message, flags)
|
33
51
|
mboxrd_message = Email::Mboxrd::Message.new(message)
|
34
|
-
|
35
|
-
|
36
|
-
|
37
|
-
|
38
|
-
mbox.append serialized
|
39
|
-
mbox_appended = true
|
40
|
-
imap.append uid, serialized.length, flags
|
41
|
-
rescue StandardError => e
|
42
|
-
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
|
43
56
|
|
44
|
-
|
45
|
-
|
46
|
-
|
47
|
-
|
48
|
-
|
49
|
-
|
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
|
50
66
|
end
|
51
67
|
end
|
52
68
|
end
|
@@ -1,5 +1,7 @@
|
|
1
1
|
require "json"
|
2
2
|
|
3
|
+
module Imap; end
|
4
|
+
|
3
5
|
module Imap::Backup
|
4
6
|
class Serializer::Imap
|
5
7
|
CURRENT_VERSION = 3
|
@@ -13,6 +15,26 @@ module Imap::Backup
|
|
13
15
|
@uid_validity = nil
|
14
16
|
@messages = nil
|
15
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]
|
16
38
|
end
|
17
39
|
|
18
40
|
def pathname
|
@@ -31,7 +53,7 @@ module Imap::Backup
|
|
31
53
|
true
|
32
54
|
end
|
33
55
|
|
34
|
-
def append(uid, length, flags
|
56
|
+
def append(uid, length, flags: [])
|
35
57
|
offset =
|
36
58
|
if messages.empty?
|
37
59
|
0
|
@@ -107,14 +129,16 @@ module Imap::Backup
|
|
107
129
|
end
|
108
130
|
|
109
131
|
def save
|
132
|
+
return if @savepoint
|
133
|
+
|
110
134
|
ensure_loaded
|
111
135
|
|
112
136
|
raise "Cannot save metadata without a uid_validity" if !uid_validity
|
113
137
|
|
114
138
|
data = {
|
115
|
-
version:
|
116
|
-
uid_validity:
|
117
|
-
messages:
|
139
|
+
version: version,
|
140
|
+
uid_validity: uid_validity,
|
141
|
+
messages: messages.map(&:to_h)
|
118
142
|
}
|
119
143
|
content = data.to_json
|
120
144
|
File.open(pathname, "w") { |f| f.write content }
|
@@ -160,5 +184,13 @@ module Imap::Backup
|
|
160
184
|
def mbox
|
161
185
|
@mbox ||= Serializer::Mbox.new(folder_path)
|
162
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
|
163
195
|
end
|
164
196
|
end
|
@@ -1,9 +1,27 @@
|
|
1
|
+
module Imap; end
|
2
|
+
|
1
3
|
module Imap::Backup
|
2
4
|
class Serializer::Mbox
|
3
5
|
attr_reader :folder_path
|
6
|
+
attr_reader :savepoint
|
4
7
|
|
5
8
|
def initialize(folder_path)
|
6
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])
|
7
25
|
end
|
8
26
|
|
9
27
|
def valid?
|
@@ -29,6 +47,10 @@ module Imap::Backup
|
|
29
47
|
File.unlink(pathname)
|
30
48
|
end
|
31
49
|
|
50
|
+
def exist?
|
51
|
+
File.exist?(pathname)
|
52
|
+
end
|
53
|
+
|
32
54
|
def length
|
33
55
|
return nil if !exist?
|
34
56
|
|
@@ -49,18 +71,24 @@ module Imap::Backup
|
|
49
71
|
end
|
50
72
|
end
|
51
73
|
|
74
|
+
def touch
|
75
|
+
File.open(pathname, "a") {}
|
76
|
+
end
|
77
|
+
|
78
|
+
private
|
79
|
+
|
52
80
|
def rewind(length)
|
53
81
|
File.open(pathname, File::RDWR | File::CREAT, 0o644) do |f|
|
54
82
|
f.truncate(length)
|
55
83
|
end
|
56
84
|
end
|
57
85
|
|
58
|
-
def
|
59
|
-
|
86
|
+
def fail_in_transaction!(message: "Method not supported inside trasactions")
|
87
|
+
raise message if savepoint
|
60
88
|
end
|
61
89
|
|
62
|
-
def
|
63
|
-
|
90
|
+
def fail_outside_transaction!
|
91
|
+
raise "This method can only be called inside a transaction" if !savepoint
|
64
92
|
end
|
65
93
|
end
|
66
94
|
end
|
@@ -10,6 +10,8 @@ require "imap/backup/serializer/message_enumerator"
|
|
10
10
|
require "imap/backup/serializer/version2_migrator"
|
11
11
|
require "imap/backup/serializer/unused_name_finder"
|
12
12
|
|
13
|
+
module Imap; end
|
14
|
+
|
13
15
|
module Imap::Backup
|
14
16
|
class Serializer
|
15
17
|
def self.folder_path_for(path:, folder:)
|
@@ -26,16 +28,36 @@ module Imap::Backup
|
|
26
28
|
|
27
29
|
attr_reader :folder
|
28
30
|
attr_reader :path
|
31
|
+
attr_reader :dirty
|
29
32
|
|
30
33
|
def initialize(path, folder)
|
31
34
|
@path = path
|
32
35
|
@folder = folder
|
33
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
|
34
54
|
end
|
35
55
|
|
36
56
|
# Returns true if there are existing, valid files
|
37
57
|
# false otherwise (in which case any existing files are deleted)
|
38
58
|
def validate!
|
59
|
+
fail_in_transaction!(:validate!)
|
60
|
+
|
39
61
|
return true if @validated
|
40
62
|
|
41
63
|
optionally_migrate2to3
|
@@ -51,6 +73,8 @@ module Imap::Backup
|
|
51
73
|
end
|
52
74
|
|
53
75
|
def check_integrity!
|
76
|
+
fail_in_transaction!(:check_integrity!)
|
77
|
+
|
54
78
|
if !imap.valid?
|
55
79
|
message = ".imap file '#{imap.pathname}' is corrupt"
|
56
80
|
raise FolderIntegrityError, message
|
@@ -102,6 +126,8 @@ module Imap::Backup
|
|
102
126
|
end
|
103
127
|
|
104
128
|
def delete
|
129
|
+
fail_in_transaction!(:delete)
|
130
|
+
|
105
131
|
imap.delete
|
106
132
|
@imap = nil
|
107
133
|
mbox.delete
|
@@ -109,6 +135,7 @@ module Imap::Backup
|
|
109
135
|
end
|
110
136
|
|
111
137
|
def apply_uid_validity(value)
|
138
|
+
fail_in_transaction!(:apply_uid_validity)
|
112
139
|
validate!
|
113
140
|
|
114
141
|
case
|
@@ -124,19 +151,26 @@ module Imap::Backup
|
|
124
151
|
end
|
125
152
|
|
126
153
|
def force_uid_validity(value)
|
154
|
+
fail_in_transaction!(:force_uid_validity)
|
127
155
|
validate!
|
128
156
|
|
129
157
|
internal_force_uid_validity(value)
|
130
158
|
end
|
131
159
|
|
132
160
|
def append(uid, message, flags)
|
133
|
-
|
161
|
+
if dirty
|
162
|
+
dirty[:append] << {uid: uid, message: message, flags: flags}
|
163
|
+
else
|
164
|
+
validate!
|
134
165
|
|
135
|
-
|
136
|
-
|
166
|
+
appender = Serializer::Appender.new(folder: sanitized, imap: imap, mbox: mbox)
|
167
|
+
appender.single(uid: uid, message: message, flags: flags)
|
168
|
+
end
|
137
169
|
end
|
138
170
|
|
139
171
|
def update(uid, flags: nil)
|
172
|
+
fail_in_transaction!(:update)
|
173
|
+
|
140
174
|
message = imap.get(uid)
|
141
175
|
return if !message
|
142
176
|
|
@@ -145,17 +179,21 @@ module Imap::Backup
|
|
145
179
|
end
|
146
180
|
|
147
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
|
+
|
148
186
|
required_uids ||= uids
|
149
187
|
|
150
188
|
validate!
|
151
189
|
|
152
|
-
return enum_for(:each_message, required_uids) if !block
|
153
|
-
|
154
190
|
enumerator = Serializer::MessageEnumerator.new(imap: imap)
|
155
191
|
enumerator.run(uids: required_uids, &block)
|
156
192
|
end
|
157
193
|
|
158
194
|
def filter(&block)
|
195
|
+
fail_in_transaction!(:filter)
|
196
|
+
|
159
197
|
temp_name = Serializer::UnusedNameFinder.new(serializer: self).run
|
160
198
|
temp_folder_path = self.class.folder_path_for(path: path, folder: temp_name)
|
161
199
|
new_mbox = Serializer::Mbox.new(temp_folder_path)
|
@@ -165,7 +203,7 @@ module Imap::Backup
|
|
165
203
|
enumerator = Serializer::MessageEnumerator.new(imap: imap)
|
166
204
|
enumerator.run(uids: uids) do |message|
|
167
205
|
keep = block.call(message)
|
168
|
-
appender.
|
206
|
+
appender.single(uid: message.uid, message: message.body, flags: message.flags) if keep
|
169
207
|
end
|
170
208
|
imap.delete
|
171
209
|
new_imap.rename imap.folder_path
|
@@ -249,5 +287,13 @@ module Imap::Backup
|
|
249
287
|
rename new_name
|
250
288
|
new_name
|
251
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
|
252
298
|
end
|
253
299
|
end
|
data/lib/imap/backup/setup.rb
CHANGED
@@ -4,6 +4,8 @@ require "email/provider"
|
|
4
4
|
require "imap/backup/account"
|
5
5
|
require "imap/backup/setup/helpers"
|
6
6
|
|
7
|
+
module Imap; end
|
8
|
+
|
7
9
|
module Imap::Backup
|
8
10
|
class Setup
|
9
11
|
class << self
|
@@ -37,6 +39,7 @@ module Imap::Backup
|
|
37
39
|
MENU
|
38
40
|
account_items menu
|
39
41
|
add_account_item menu
|
42
|
+
toggle_delay_download_writes menu
|
40
43
|
if config.modified?
|
41
44
|
menu.choice("save and exit") do
|
42
45
|
config.save
|
@@ -68,6 +71,16 @@ module Imap::Backup
|
|
68
71
|
end
|
69
72
|
end
|
70
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
|
+
|
71
84
|
def default_account_config(username)
|
72
85
|
Imap::Backup::Account.new(
|
73
86
|
username: username,
|
data/lib/imap/backup/uploader.rb
CHANGED
data/lib/imap/backup/version.rb
CHANGED
data/lib/imap/backup.rb
CHANGED
@@ -1,5 +1,3 @@
|
|
1
|
-
module Imap; end
|
2
|
-
|
3
1
|
require "imap/backup/account/folder"
|
4
2
|
require "imap/backup/configuration"
|
5
3
|
require "imap/backup/downloader"
|
@@ -13,6 +11,8 @@ require "imap/backup/setup/connection_tester"
|
|
13
11
|
require "imap/backup/setup/folder_chooser"
|
14
12
|
require "imap/backup/version"
|
15
13
|
|
14
|
+
module Imap; end
|
15
|
+
|
16
16
|
module Imap::Backup
|
17
17
|
class ConfigurationNotFound < StandardError; end
|
18
18
|
end
|
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:
|