imap-backup 14.4.0 → 14.4.3
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/bin/imap-backup +2 -2
- data/docs/api.md +20 -0
- data/docs/development.md +18 -7
- data/lib/imap/backup/account/backup.rb +6 -3
- data/lib/imap/backup/account/backup_folders.rb +6 -3
- data/lib/imap/backup/account/client_factory.rb +3 -2
- data/lib/imap/backup/account/folder.rb +10 -9
- data/lib/imap/backup/account/folder_backup.rb +5 -4
- data/lib/imap/backup/account/folder_ensurer.rb +5 -2
- data/lib/imap/backup/account/local_only_folder_deleter.rb +7 -3
- data/lib/imap/backup/account/restore.rb +5 -2
- data/lib/imap/backup/account/serialized_folders.rb +3 -2
- data/lib/imap/backup/account.rb +85 -0
- data/lib/imap/backup/cli/backup.rb +14 -12
- data/lib/imap/backup/cli/folder_enumerator.rb +5 -5
- data/lib/imap/backup/cli/local/check.rb +4 -2
- data/lib/imap/backup/cli/local.rb +50 -49
- data/lib/imap/backup/cli/remote.rb +46 -46
- data/lib/imap/backup/cli/restore.rb +5 -3
- data/lib/imap/backup/cli/setup.rb +4 -2
- data/lib/imap/backup/cli/single/backup.rb +3 -3
- data/lib/imap/backup/cli/stats.rb +54 -52
- data/lib/imap/backup/cli/transfer.rb +93 -93
- data/lib/imap/backup/cli/utils.rb +28 -28
- data/lib/imap/backup/cli.rb +12 -0
- data/lib/imap/backup/client/default.rb +5 -5
- data/lib/imap/backup/configuration.rb +5 -4
- data/lib/imap/backup/downloader.rb +6 -14
- data/lib/imap/backup/email/mboxrd/message.rb +2 -3
- data/lib/imap/backup/file_mode.rb +4 -2
- data/lib/imap/backup/flag_refresher.rb +3 -3
- data/lib/imap/backup/local_only_message_deleter.rb +5 -3
- data/lib/imap/backup/migrator.rb +6 -4
- data/lib/imap/backup/mirror/map.rb +3 -3
- data/lib/imap/backup/mirror.rb +5 -5
- data/lib/imap/backup/serializer/appender.rb +7 -4
- data/lib/imap/backup/serializer/delayed_metadata_serializer.rb +15 -2
- data/lib/imap/backup/serializer/directory.rb +12 -3
- data/lib/imap/backup/serializer/folder_maker.rb +12 -4
- data/lib/imap/backup/serializer/imap.rb +5 -1
- data/lib/imap/backup/serializer/integrity_checker.rb +10 -3
- data/lib/imap/backup/serializer/mbox.rb +2 -1
- data/lib/imap/backup/serializer/message.rb +10 -18
- data/lib/imap/backup/serializer/permission_checker.rb +5 -3
- data/lib/imap/backup/serializer/transaction.rb +4 -1
- data/lib/imap/backup/serializer/unused_name_finder.rb +4 -2
- data/lib/imap/backup/serializer/version2_migrator.rb +2 -2
- data/lib/imap/backup/serializer.rb +5 -0
- data/lib/imap/backup/setup/account/header.rb +3 -3
- data/lib/imap/backup/setup/account.rb +6 -6
- data/lib/imap/backup/setup/asker.rb +6 -4
- data/lib/imap/backup/setup/backup_path.rb +3 -4
- data/lib/imap/backup/setup/connection_tester.rb +4 -2
- data/lib/imap/backup/setup/{email.rb → email_changer.rb} +4 -4
- data/lib/imap/backup/setup/folder_chooser.rb +2 -2
- data/lib/imap/backup/setup/global_options/download_strategy_chooser.rb +2 -2
- data/lib/imap/backup/setup/global_options.rb +2 -2
- data/lib/imap/backup/setup.rb +2 -2
- data/lib/imap/backup/text/sanitizer.rb +2 -2
- data/lib/imap/backup/thunderbird/mailbox_exporter.rb +10 -8
- data/lib/imap/backup/uploader.rb +3 -3
- data/lib/imap/backup/version.rb +1 -1
- metadata +4 -4
- data/lib/imap/backup/cli_coverage.rb +0 -21
@@ -17,8 +17,6 @@ module Imap::Backup
|
|
17
17
|
include Thor::Actions
|
18
18
|
include CLI::Helpers
|
19
19
|
|
20
|
-
FAKE_EMAIL = "fake@email.com".freeze
|
21
|
-
|
22
20
|
desc "ignore-history EMAIL", "Skip downloading emails up to today for all configured folders"
|
23
21
|
config_option
|
24
22
|
quiet_option
|
@@ -87,38 +85,40 @@ module Imap::Backup
|
|
87
85
|
end
|
88
86
|
end
|
89
87
|
|
90
|
-
|
91
|
-
def do_ignore_folder_history(folder, serializer)
|
92
|
-
uids = folder.uids - serializer.uids
|
93
|
-
Logger.logger.info "Folder '#{folder.name}' - #{uids.length} messages"
|
88
|
+
private
|
94
89
|
|
95
|
-
|
90
|
+
FAKE_EMAIL = "fake@email.com".freeze
|
96
91
|
|
97
|
-
|
98
|
-
|
99
|
-
|
100
|
-
Subject: Message #{uid} not backed up
|
101
|
-
Skipped #{uid}
|
102
|
-
MESSAGE
|
92
|
+
def do_ignore_folder_history(folder, serializer)
|
93
|
+
uids = folder.uids - serializer.uids
|
94
|
+
Logger.logger.info "Folder '#{folder.name}' - #{uids.length} messages"
|
103
95
|
|
104
|
-
|
105
|
-
|
96
|
+
serializer.apply_uid_validity(folder.uid_validity)
|
97
|
+
|
98
|
+
uids.each do |uid|
|
99
|
+
message = <<~MESSAGE
|
100
|
+
From: #{FAKE_EMAIL}
|
101
|
+
Subject: Message #{uid} not backed up
|
102
|
+
Skipped #{uid}
|
103
|
+
MESSAGE
|
104
|
+
|
105
|
+
serializer.append uid, message, []
|
106
106
|
end
|
107
|
+
end
|
107
108
|
|
108
|
-
|
109
|
-
|
110
|
-
|
111
|
-
|
112
|
-
|
113
|
-
|
114
|
-
|
115
|
-
|
116
|
-
|
117
|
-
|
118
|
-
end
|
119
|
-
|
120
|
-
profiles.installs[0].default
|
109
|
+
def thunderbird_profile(name = nil)
|
110
|
+
profiles = ::Thunderbird::Profiles.new
|
111
|
+
if name
|
112
|
+
profiles.profile(name)
|
113
|
+
else
|
114
|
+
if profiles.installs.count > 1
|
115
|
+
raise <<~MESSAGE
|
116
|
+
Thunderbird has multiple installs, so no default profile exists.
|
117
|
+
Please supply a profile name
|
118
|
+
MESSAGE
|
121
119
|
end
|
120
|
+
|
121
|
+
profiles.installs[0].default
|
122
122
|
end
|
123
123
|
end
|
124
124
|
end
|
data/lib/imap/backup/cli.rb
CHANGED
@@ -6,6 +6,7 @@ require "imap/backup/version"
|
|
6
6
|
module Imap; end
|
7
7
|
|
8
8
|
module Imap::Backup
|
9
|
+
# Top-level cli call handler
|
9
10
|
class CLI < Thor
|
10
11
|
require "imap/backup/cli/helpers"
|
11
12
|
|
@@ -41,9 +42,12 @@ module Imap::Backup
|
|
41
42
|
To check what values you should use, check the output of the
|
42
43
|
`imap-backup remote namespaces EMAIL` command.
|
43
44
|
DESC
|
45
|
+
private_constant :NAMESPACE_CONFIGURATION_DESCRIPTION
|
44
46
|
|
45
47
|
default_task :backup
|
46
48
|
|
49
|
+
# Overrides {https://www.rubydoc.info/gems/thor/Thor%2FBase%2FClassMethods:start Thor's method}
|
50
|
+
# to handle '--version' and rearrange parameters if 'help' is passed
|
47
51
|
def self.start(args)
|
48
52
|
if args.include?("--version")
|
49
53
|
new.version
|
@@ -64,6 +68,7 @@ module Imap::Backup
|
|
64
68
|
super
|
65
69
|
end
|
66
70
|
|
71
|
+
# see {https://www.rubydoc.info/gems/thor/Thor/Base/ClassMethods#exit_on_failure%3F-instance_method Thor documentation}
|
67
72
|
def self.exit_on_failure?
|
68
73
|
true
|
69
74
|
end
|
@@ -81,6 +86,7 @@ module Imap::Backup
|
|
81
86
|
quiet_option
|
82
87
|
refresh_option
|
83
88
|
verbose_option
|
89
|
+
# Runs account backups
|
84
90
|
def backup
|
85
91
|
non_logging_options = Imap::Backup::Logger.setup_logging(options)
|
86
92
|
Backup.new(non_logging_options).run
|
@@ -141,6 +147,7 @@ module Imap::Backup
|
|
141
147
|
desc: "the prefix (namespace) to strip from source folder names",
|
142
148
|
aliases: ["-s"]
|
143
149
|
)
|
150
|
+
# Migrates emails from one account to another
|
144
151
|
def migrate(source_email, destination_email)
|
145
152
|
non_logging_options = Imap::Backup::Logger.setup_logging(options)
|
146
153
|
Transfer.new(:migrate, source_email, destination_email, non_logging_options).run
|
@@ -200,6 +207,7 @@ module Imap::Backup
|
|
200
207
|
desc: "the prefix (namespace) to strip from source folder names",
|
201
208
|
aliases: ["-s"]
|
202
209
|
)
|
210
|
+
# Keeps one email account in line with another
|
203
211
|
def mirror(source_email, destination_email)
|
204
212
|
non_logging_options = Imap::Backup::Logger.setup_logging(options)
|
205
213
|
Transfer.new(:mirror, source_email, destination_email, non_logging_options).run
|
@@ -217,6 +225,7 @@ module Imap::Backup
|
|
217
225
|
config_option
|
218
226
|
quiet_option
|
219
227
|
verbose_option
|
228
|
+
# Restores backed up meails to an account
|
220
229
|
def restore(email = nil)
|
221
230
|
non_logging_options = Imap::Backup::Logger.setup_logging(options)
|
222
231
|
Restore.new(email, non_logging_options).run
|
@@ -230,6 +239,7 @@ module Imap::Backup
|
|
230
239
|
config_option
|
231
240
|
quiet_option
|
232
241
|
verbose_option
|
242
|
+
# Runs the menu-driven setup program
|
233
243
|
def setup
|
234
244
|
non_logging_options = Imap::Backup::Logger.setup_logging(options)
|
235
245
|
CLI::Setup.new(non_logging_options).run
|
@@ -253,6 +263,7 @@ module Imap::Backup
|
|
253
263
|
format_option
|
254
264
|
quiet_option
|
255
265
|
verbose_option
|
266
|
+
# Prints various statistics about a configured account
|
256
267
|
def stats(email)
|
257
268
|
non_logging_options = Imap::Backup::Logger.setup_logging(options)
|
258
269
|
Stats.new(email, non_logging_options).run
|
@@ -262,6 +273,7 @@ module Imap::Backup
|
|
262
273
|
subcommand "utils", Utils
|
263
274
|
|
264
275
|
desc "version", "Print the imap-backup version"
|
276
|
+
# Prints the program version
|
265
277
|
def version
|
266
278
|
Kernel.puts "imap-backup #{Imap::Backup::VERSION}"
|
267
279
|
end
|
@@ -15,11 +15,6 @@ module Imap::Backup
|
|
15
15
|
responses uid_fetch uid_search uid_store
|
16
16
|
)
|
17
17
|
|
18
|
-
attr_reader :account
|
19
|
-
attr_reader :options
|
20
|
-
attr_reader :server
|
21
|
-
attr_accessor :state
|
22
|
-
|
23
18
|
def initialize(server, account, options)
|
24
19
|
@account = account
|
25
20
|
@options = options
|
@@ -75,6 +70,11 @@ module Imap::Backup
|
|
75
70
|
|
76
71
|
private
|
77
72
|
|
73
|
+
attr_reader :account
|
74
|
+
attr_reader :options
|
75
|
+
attr_reader :server
|
76
|
+
attr_accessor :state
|
77
|
+
|
78
78
|
def imap
|
79
79
|
@imap ||= Net::IMAP.new(server, options)
|
80
80
|
end
|
@@ -11,15 +11,12 @@ module Imap; end
|
|
11
11
|
module Imap::Backup
|
12
12
|
class Configuration
|
13
13
|
CONFIGURATION_DIRECTORY = File.expand_path("~/.imap-backup")
|
14
|
-
VERSION_2_1 = "2.1".freeze
|
15
|
-
VERSION = "2.2".freeze
|
16
14
|
DEFAULT_STRATEGY = "delay_metadata".freeze
|
17
15
|
DOWNLOAD_STRATEGIES = [
|
18
16
|
{key: "direct", description: "write straight to disk"},
|
19
17
|
{key: DEFAULT_STRATEGY, description: "delay writing metadata"}
|
20
18
|
].freeze
|
21
|
-
|
22
|
-
attr_reader :pathname
|
19
|
+
VERSION = "2.2".freeze
|
23
20
|
|
24
21
|
def self.default_pathname
|
25
22
|
File.join(CONFIGURATION_DIRECTORY, "config.json")
|
@@ -98,6 +95,10 @@ module Imap::Backup
|
|
98
95
|
|
99
96
|
private
|
100
97
|
|
98
|
+
VERSION_2_1 = "2.1".freeze
|
99
|
+
|
100
|
+
attr_reader :pathname
|
101
|
+
|
101
102
|
def ensure_loaded!
|
102
103
|
return true if @data
|
103
104
|
|
@@ -3,21 +3,8 @@ require "net/imap"
|
|
3
3
|
module Imap; end
|
4
4
|
|
5
5
|
module Imap::Backup
|
6
|
-
class MultiFetchFailedError < StandardError; end
|
7
|
-
|
8
6
|
class Downloader
|
9
|
-
|
10
|
-
attr_reader :serializer
|
11
|
-
attr_reader :multi_fetch_size
|
12
|
-
# Some IMAP providers, notably Apple Mail, set the '\Seen' flag
|
13
|
-
# on emails when they are fetched. By setting `:reset_seen_flags_after_fetch`,
|
14
|
-
# a workaround is activated which checks which emails are 'unseen' before
|
15
|
-
# and after the fetch, and removes the '\Seen' flag from those which have changed.
|
16
|
-
# As this check is susceptible to 'race conditions', i.e. when a different
|
17
|
-
# client sets the '\Seen' flag while imap-backup is fetching, it is best
|
18
|
-
# to only use it when required (i.e. for IMAP providers which always
|
19
|
-
# mark messages as '\Seen' when accessed).
|
20
|
-
attr_reader :reset_seen_flags_after_fetch
|
7
|
+
class MultiFetchFailedError < StandardError; end
|
21
8
|
|
22
9
|
def initialize(folder, serializer, multi_fetch_size: 1, reset_seen_flags_after_fetch: false)
|
23
10
|
@folder = folder
|
@@ -46,6 +33,11 @@ module Imap::Backup
|
|
46
33
|
|
47
34
|
private
|
48
35
|
|
36
|
+
attr_reader :folder
|
37
|
+
attr_reader :serializer
|
38
|
+
attr_reader :multi_fetch_size
|
39
|
+
attr_reader :reset_seen_flags_after_fetch
|
40
|
+
|
49
41
|
def download_block(block, index)
|
50
42
|
uids_and_bodies =
|
51
43
|
if reset_seen_flags_after_fetch
|
@@ -1,4 +1,3 @@
|
|
1
|
-
require "forwardable"
|
2
1
|
require "mail"
|
3
2
|
|
4
3
|
module Imap; end
|
@@ -8,8 +7,6 @@ module Imap::Backup
|
|
8
7
|
|
9
8
|
module Email::Mboxrd
|
10
9
|
class Message
|
11
|
-
attr_reader :supplied_body
|
12
|
-
|
13
10
|
def self.clean_serialized(serialized)
|
14
11
|
cleaned = serialized.gsub(/^>(>*From)/, "\\1")
|
15
12
|
# Serialized messages in this format *should* start with a line
|
@@ -26,6 +23,8 @@ module Imap::Backup
|
|
26
23
|
new(clean_serialized(serialized))
|
27
24
|
end
|
28
25
|
|
26
|
+
attr_reader :supplied_body
|
27
|
+
|
29
28
|
def initialize(supplied_body)
|
30
29
|
@supplied_body = supplied_body.clone
|
31
30
|
end
|
@@ -2,8 +2,6 @@ module Imap; end
|
|
2
2
|
|
3
3
|
module Imap::Backup
|
4
4
|
class FileMode
|
5
|
-
attr_reader :filename
|
6
|
-
|
7
5
|
def initialize(filename:)
|
8
6
|
@filename = filename
|
9
7
|
end
|
@@ -14,5 +12,9 @@ module Imap::Backup
|
|
14
12
|
stat = File.stat(filename)
|
15
13
|
stat.mode & 0o777
|
16
14
|
end
|
15
|
+
|
16
|
+
private
|
17
|
+
|
18
|
+
attr_reader :filename
|
17
19
|
end
|
18
20
|
end
|
@@ -2,9 +2,6 @@ module Imap; end
|
|
2
2
|
|
3
3
|
module Imap::Backup
|
4
4
|
class FlagRefresher
|
5
|
-
attr_reader :folder
|
6
|
-
attr_reader :serializer
|
7
|
-
|
8
5
|
CHUNK_SIZE = 100
|
9
6
|
|
10
7
|
def initialize(folder, serializer)
|
@@ -22,6 +19,9 @@ module Imap::Backup
|
|
22
19
|
|
23
20
|
private
|
24
21
|
|
22
|
+
attr_reader :folder
|
23
|
+
attr_reader :serializer
|
24
|
+
|
25
25
|
def refresh_block(uids)
|
26
26
|
uids_and_flags = folder.fetch_multi(uids, ["FLAGS"])
|
27
27
|
uids_and_flags.each do |uid_and_flags|
|
@@ -4,9 +4,6 @@ module Imap; end
|
|
4
4
|
|
5
5
|
module Imap::Backup
|
6
6
|
class LocalOnlyMessageDeleter
|
7
|
-
attr_reader :folder
|
8
|
-
attr_reader :serializer
|
9
|
-
|
10
7
|
def initialize(folder, serializer)
|
11
8
|
@folder = folder
|
12
9
|
@serializer = serializer
|
@@ -29,5 +26,10 @@ module Imap::Backup
|
|
29
26
|
!local_only_uids.include?(message.uid)
|
30
27
|
end
|
31
28
|
end
|
29
|
+
|
30
|
+
private
|
31
|
+
|
32
|
+
attr_reader :folder
|
33
|
+
attr_reader :serializer
|
32
34
|
end
|
33
35
|
end
|
data/lib/imap/backup/migrator.rb
CHANGED
@@ -4,10 +4,6 @@ module Imap; end
|
|
4
4
|
|
5
5
|
module Imap::Backup
|
6
6
|
class Migrator
|
7
|
-
attr_reader :folder
|
8
|
-
attr_reader :reset
|
9
|
-
attr_reader :serializer
|
10
|
-
|
11
7
|
def initialize(serializer, folder, reset: false)
|
12
8
|
@folder = folder
|
13
9
|
@reset = reset
|
@@ -35,5 +31,11 @@ module Imap::Backup
|
|
35
31
|
end
|
36
32
|
end
|
37
33
|
end
|
34
|
+
|
35
|
+
private
|
36
|
+
|
37
|
+
attr_reader :folder
|
38
|
+
attr_reader :reset
|
39
|
+
attr_reader :serializer
|
38
40
|
end
|
39
41
|
end
|
@@ -6,9 +6,6 @@ module Imap::Backup
|
|
6
6
|
class Mirror; end
|
7
7
|
|
8
8
|
class Mirror::Map
|
9
|
-
attr_reader :pathname
|
10
|
-
attr_reader :destination
|
11
|
-
|
12
9
|
def initialize(pathname:, destination:)
|
13
10
|
@pathname = pathname
|
14
11
|
@destination = destination
|
@@ -64,6 +61,9 @@ module Imap::Backup
|
|
64
61
|
|
65
62
|
private
|
66
63
|
|
64
|
+
attr_reader :pathname
|
65
|
+
attr_reader :destination
|
66
|
+
|
67
67
|
def store
|
68
68
|
@store ||=
|
69
69
|
if File.exist?(pathname)
|
data/lib/imap/backup/mirror.rb
CHANGED
@@ -4,11 +4,6 @@ module Imap; end
|
|
4
4
|
|
5
5
|
module Imap::Backup
|
6
6
|
class Mirror
|
7
|
-
attr_reader :serializer
|
8
|
-
attr_reader :folder
|
9
|
-
|
10
|
-
CHUNK_SIZE = 100
|
11
|
-
|
12
7
|
def initialize(serializer, folder)
|
13
8
|
@serializer = serializer
|
14
9
|
@folder = folder
|
@@ -24,6 +19,11 @@ module Imap::Backup
|
|
24
19
|
|
25
20
|
private
|
26
21
|
|
22
|
+
CHUNK_SIZE = 100
|
23
|
+
|
24
|
+
attr_reader :serializer
|
25
|
+
attr_reader :folder
|
26
|
+
|
27
27
|
def ensure_destination_folder
|
28
28
|
return if folder.exist?
|
29
29
|
|
@@ -5,17 +5,16 @@ module Imap; end
|
|
5
5
|
module Imap::Backup
|
6
6
|
class Serializer; end
|
7
7
|
|
8
|
+
# Appends messages to the local store
|
8
9
|
class Serializer::Appender
|
9
|
-
attr_reader :imap
|
10
|
-
attr_reader :folder
|
11
|
-
attr_reader :mbox
|
12
|
-
|
13
10
|
def initialize(folder:, imap:, mbox:)
|
14
11
|
@folder = folder
|
15
12
|
@imap = imap
|
16
13
|
@mbox = mbox
|
17
14
|
end
|
18
15
|
|
16
|
+
# Adds a message to the metadata file and the mailbox.
|
17
|
+
# Wraps any errors with information about the message that caused them.
|
19
18
|
def append(uid:, message:, flags:)
|
20
19
|
raise "Can't add messages without uid_validity" if !imap.uid_validity
|
21
20
|
|
@@ -56,6 +55,10 @@ module Imap::Backup
|
|
56
55
|
|
57
56
|
private
|
58
57
|
|
58
|
+
attr_reader :imap
|
59
|
+
attr_reader :folder
|
60
|
+
attr_reader :mbox
|
61
|
+
|
59
62
|
def wrap_error(error:, note:, folder:, uid:, message:)
|
60
63
|
<<-ERROR.gsub(/^\s*/m, "")
|
61
64
|
[#{folder}] #{note} #{uid}: #{message}.
|
@@ -1,3 +1,5 @@
|
|
1
|
+
require "forwardable"
|
2
|
+
|
1
3
|
require "imap/backup/email/mboxrd/message"
|
2
4
|
require "imap/backup/serializer/imap"
|
3
5
|
require "imap/backup/serializer/mbox"
|
@@ -9,8 +11,6 @@ module Imap::Backup
|
|
9
11
|
class Serializer::DelayedMetadataSerializer
|
10
12
|
extend Forwardable
|
11
13
|
|
12
|
-
attr_reader :serializer
|
13
|
-
|
14
14
|
def_delegator :serializer, :uids
|
15
15
|
|
16
16
|
def initialize(serializer:)
|
@@ -18,6 +18,10 @@ module Imap::Backup
|
|
18
18
|
@tsx = nil
|
19
19
|
end
|
20
20
|
|
21
|
+
# Initializes the metadata and mailbox transactions, then calls the supplied block.
|
22
|
+
# Once the block has finished, commits changes to metadata
|
23
|
+
#
|
24
|
+
# @return [void]
|
21
25
|
def transaction(&block)
|
22
26
|
tsx.fail_in_transaction!(:transaction, message: "nested transactions are not supported")
|
23
27
|
|
@@ -32,6 +36,13 @@ module Imap::Backup
|
|
32
36
|
end
|
33
37
|
end
|
34
38
|
|
39
|
+
# Appends a message to the mbox file and adds the metadata
|
40
|
+
# to the transaction
|
41
|
+
#
|
42
|
+
# @param uid [Integer] the UID of the message
|
43
|
+
# @param message [String] the message
|
44
|
+
# @param flags [Array<Symbol>] the flags for the message
|
45
|
+
# @return [void]
|
35
46
|
def append(uid, message, flags)
|
36
47
|
tsx.fail_outside_transaction!(:append)
|
37
48
|
mboxrd_message = Email::Mboxrd::Message.new(message)
|
@@ -42,6 +53,8 @@ module Imap::Backup
|
|
42
53
|
|
43
54
|
private
|
44
55
|
|
56
|
+
attr_reader :serializer
|
57
|
+
|
45
58
|
def commit
|
46
59
|
# rubocop:disable Lint/RescueException
|
47
60
|
imap.transaction do
|
@@ -8,17 +8,23 @@ module Imap; end
|
|
8
8
|
module Imap::Backup
|
9
9
|
class Serializer; end
|
10
10
|
|
11
|
+
# Ensures that serialization directories exist and have the correct permissions.
|
11
12
|
class Serializer::Directory
|
13
|
+
# The desired permissions for all directories that store backups
|
12
14
|
DIRECTORY_PERMISSIONS = 0o700
|
13
15
|
|
14
|
-
|
15
|
-
|
16
|
-
|
16
|
+
# @param path [String] The base path of the account backup
|
17
|
+
# @param relative [String] The path relative from the base
|
18
|
+
#
|
19
|
+
# @return [void]
|
17
20
|
def initialize(path, relative)
|
18
21
|
@path = path
|
19
22
|
@relative = relative
|
20
23
|
end
|
21
24
|
|
25
|
+
# Creates the directory, if present and sets it's access permissions
|
26
|
+
#
|
27
|
+
# @return [void]
|
22
28
|
def ensure_exists
|
23
29
|
if !File.directory?(full_path)
|
24
30
|
Serializer::FolderMaker.new(
|
@@ -34,6 +40,9 @@ module Imap::Backup
|
|
34
40
|
|
35
41
|
private
|
36
42
|
|
43
|
+
attr_reader :relative
|
44
|
+
attr_reader :path
|
45
|
+
|
37
46
|
def full_path
|
38
47
|
containing_directory = File.join(path, relative)
|
39
48
|
File.expand_path(containing_directory)
|
@@ -5,17 +5,19 @@ module Imap; end
|
|
5
5
|
module Imap::Backup
|
6
6
|
class Serializer; end
|
7
7
|
|
8
|
+
# Creates directories
|
8
9
|
class Serializer::FolderMaker
|
9
|
-
|
10
|
-
|
11
|
-
|
12
|
-
|
10
|
+
# @param base [String] The base directory of the account
|
11
|
+
# @param path [String] The path to the folder, relative to the base
|
12
|
+
# @param permissions [Integer] The permissions to set on the folder
|
13
13
|
def initialize(base:, path:, permissions:)
|
14
14
|
@base = base
|
15
15
|
@path = path
|
16
16
|
@permissions = permissions
|
17
17
|
end
|
18
18
|
|
19
|
+
# Creates the directory and any missing parent directories,
|
20
|
+
# ensuring the desired permissions.
|
19
21
|
def run
|
20
22
|
parts = path.split("/")
|
21
23
|
return if parts.empty?
|
@@ -28,6 +30,12 @@ module Imap::Backup
|
|
28
30
|
end
|
29
31
|
end
|
30
32
|
|
33
|
+
private
|
34
|
+
|
35
|
+
attr_reader :base
|
36
|
+
attr_reader :path
|
37
|
+
attr_reader :permissions
|
38
|
+
|
31
39
|
def full_path
|
32
40
|
File.join(base, path)
|
33
41
|
end
|
@@ -7,12 +7,14 @@ require "imap/backup/serializer/transaction"
|
|
7
7
|
module Imap; end
|
8
8
|
|
9
9
|
module Imap::Backup
|
10
|
+
# Stores message metadata
|
10
11
|
class Serializer::Imap
|
12
|
+
# The version number to store in the metadata file
|
11
13
|
CURRENT_VERSION = 3
|
12
14
|
|
13
15
|
attr_reader :folder_path
|
14
|
-
attr_reader :loaded
|
15
16
|
|
17
|
+
# @param folder_path [String] The path of the imap metadata file, without the '.imap' extension
|
16
18
|
def initialize(folder_path)
|
17
19
|
@folder_path = folder_path
|
18
20
|
@loaded = false
|
@@ -149,6 +151,8 @@ module Imap::Backup
|
|
149
151
|
|
150
152
|
private
|
151
153
|
|
154
|
+
attr_reader :loaded
|
155
|
+
|
152
156
|
def save_internal(version:, uid_validity:, messages:)
|
153
157
|
raise "Cannot save metadata without a uid_validity" if !uid_validity
|
154
158
|
|
@@ -5,15 +5,19 @@ module Imap::Backup
|
|
5
5
|
|
6
6
|
class Serializer::FolderIntegrityError < StandardError; end
|
7
7
|
|
8
|
+
# Checks that both the mailbox and its associated metadata file match
|
8
9
|
class Serializer::IntegrityChecker
|
9
|
-
|
10
|
-
|
11
|
-
|
10
|
+
# @param imap [Imap]
|
11
|
+
# @param mbox [Mbox]
|
12
12
|
def initialize(imap:, mbox:)
|
13
13
|
@imap = imap
|
14
14
|
@mbox = mbox
|
15
15
|
end
|
16
16
|
|
17
|
+
# Runs the integrity check
|
18
|
+
#
|
19
|
+
# @raise [FolderIntegrityError] if the files do not match
|
20
|
+
# @return [void]
|
17
21
|
def run
|
18
22
|
Logger.logger.debug(
|
19
23
|
"[IntegrityChecker] checking '#{imap.pathname}' against '#{mbox.pathname}'"
|
@@ -47,6 +51,9 @@ module Imap::Backup
|
|
47
51
|
|
48
52
|
private
|
49
53
|
|
54
|
+
attr_reader :imap
|
55
|
+
attr_reader :mbox
|
56
|
+
|
50
57
|
def check_offset_ordering!
|
51
58
|
offsets = imap.messages.map(&:offset)
|
52
59
|
|
@@ -5,7 +5,6 @@ module Imap; end
|
|
5
5
|
module Imap::Backup
|
6
6
|
class Serializer::Mbox
|
7
7
|
attr_reader :folder_path
|
8
|
-
attr_reader :savepoint
|
9
8
|
|
10
9
|
def initialize(folder_path)
|
11
10
|
@folder_path = folder_path
|
@@ -86,6 +85,8 @@ module Imap::Backup
|
|
86
85
|
|
87
86
|
private
|
88
87
|
|
88
|
+
attr_reader :savepoint
|
89
|
+
|
89
90
|
def rewind(length)
|
90
91
|
File.open(pathname, File::RDWR | File::CREAT, 0o644) do |f|
|
91
92
|
f.truncate(length)
|
@@ -1,16 +1,20 @@
|
|
1
|
+
require "forwardable"
|
2
|
+
|
1
3
|
require "imap/backup/email/mboxrd/message"
|
2
4
|
|
3
5
|
module Imap; end
|
4
6
|
|
5
7
|
module Imap::Backup
|
6
8
|
class Serializer::Message
|
7
|
-
attr_accessor :uid
|
8
9
|
attr_accessor :flags
|
9
|
-
attr_reader :offset
|
10
10
|
attr_reader :length
|
11
|
-
attr_reader :
|
11
|
+
attr_reader :offset
|
12
|
+
attr_accessor :uid
|
13
|
+
|
14
|
+
extend Forwardable
|
12
15
|
|
13
|
-
|
16
|
+
def_delegator :message, :supplied_body, :body
|
17
|
+
def_delegators :message, :imap_body, :date, :subject
|
14
18
|
|
15
19
|
def initialize(uid:, offset:, length:, mbox:, flags: [])
|
16
20
|
@uid = uid
|
@@ -37,20 +41,8 @@ module Imap::Backup
|
|
37
41
|
end
|
38
42
|
end
|
39
43
|
|
40
|
-
|
41
|
-
@body ||= message.supplied_body
|
42
|
-
end
|
43
|
-
|
44
|
-
def imap_body
|
45
|
-
@imap_body ||= message.imap_body
|
46
|
-
end
|
44
|
+
private
|
47
45
|
|
48
|
-
|
49
|
-
@date ||= message.date
|
50
|
-
end
|
51
|
-
|
52
|
-
def subject
|
53
|
-
@subject ||= message.subject
|
54
|
-
end
|
46
|
+
attr_reader :mbox
|
55
47
|
end
|
56
48
|
end
|