imap-backup 10.0.0 → 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 +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:
|