imap-backup 14.4.4 → 14.4.5
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 +67 -137
- data/docs/documentation.md +19 -0
- data/lib/imap/backup/account/backup.rb +2 -0
- data/lib/imap/backup/account/backup_folders.rb +7 -1
- data/lib/imap/backup/account/client_factory.rb +1 -0
- data/lib/imap/backup/account/folder.rb +26 -0
- data/lib/imap/backup/account/folder_backup.rb +4 -1
- data/lib/imap/backup/account/folder_ensurer.rb +3 -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 +5 -1
- data/lib/imap/backup/account.rb +15 -11
- data/lib/imap/backup/cli/backup.rb +3 -0
- data/lib/imap/backup/cli/folder_enumerator.rb +6 -0
- data/lib/imap/backup/cli/helpers.rb +13 -0
- data/lib/imap/backup/cli/local/check.rb +3 -0
- data/lib/imap/backup/cli/local.rb +13 -0
- data/lib/imap/backup/cli/remote.rb +7 -0
- data/lib/imap/backup/cli/restore.rb +4 -0
- data/lib/imap/backup/cli/setup.rb +3 -0
- data/lib/imap/backup/cli/single/backup.rb +3 -0
- data/lib/imap/backup/cli/single.rb +4 -0
- data/lib/imap/backup/cli/stats.rb +3 -0
- data/lib/imap/backup/cli/transfer.rb +8 -0
- data/lib/imap/backup/cli/utils.rb +6 -0
- data/lib/imap/backup/cli.rb +8 -0
- data/lib/imap/backup/client/apple_mail.rb +2 -0
- data/lib/imap/backup/client/automatic_login_wrapper.rb +9 -1
- data/lib/imap/backup/client/default.rb +15 -4
- data/lib/imap/backup/configuration.rb +13 -0
- data/lib/imap/backup/configuration_not_found.rb +1 -0
- data/lib/imap/backup/downloader.rb +4 -0
- data/lib/imap/backup/email/mboxrd/message.rb +14 -0
- data/lib/imap/backup/email/provider/apple_mail.rb +2 -0
- data/lib/imap/backup/email/provider/base.rb +2 -0
- data/lib/imap/backup/email/provider/fastmail.rb +2 -0
- data/lib/imap/backup/email/provider/gmail.rb +2 -0
- data/lib/imap/backup/email/provider/purelymail.rb +2 -0
- data/lib/imap/backup/email/provider/unknown.rb +2 -6
- data/lib/imap/backup/email/provider.rb +5 -0
- data/lib/imap/backup/file_mode.rb +2 -0
- data/lib/imap/backup/flag_refresher.rb +4 -0
- data/lib/imap/backup/local_only_message_deleter.rb +2 -0
- data/lib/imap/backup/logger.rb +18 -0
- data/lib/imap/backup/migrator.rb +3 -0
- data/lib/imap/backup/mirror/map.rb +21 -0
- data/lib/imap/backup/mirror.rb +8 -0
- data/lib/imap/backup/naming.rb +10 -1
- data/lib/imap/backup/retry_on_error.rb +9 -0
- data/lib/imap/backup/serializer/appender.rb +9 -0
- data/lib/imap/backup/serializer/delayed_metadata_serializer.rb +4 -0
- data/lib/imap/backup/serializer/folder_maker.rb +1 -0
- data/lib/imap/backup/serializer/imap.rb +38 -2
- data/lib/imap/backup/serializer/integrity_checker.rb +1 -0
- data/lib/imap/backup/serializer/mbox.rb +26 -0
- data/lib/imap/backup/serializer/message.rb +13 -0
- data/lib/imap/backup/serializer/message_enumerator.rb +10 -2
- data/lib/imap/backup/serializer/permission_checker.rb +6 -0
- data/lib/imap/backup/serializer/transaction.rb +18 -0
- data/lib/imap/backup/serializer/unused_name_finder.rb +4 -0
- data/lib/imap/backup/serializer/version2_migrator.rb +4 -0
- data/lib/imap/backup/serializer.rb +56 -2
- data/lib/imap/backup/setup/account/header.rb +6 -0
- data/lib/imap/backup/setup/account.rb +6 -0
- data/lib/imap/backup/setup/asker.rb +16 -0
- data/lib/imap/backup/setup/backup_path.rb +6 -0
- data/lib/imap/backup/setup/connection_tester.rb +5 -0
- data/lib/imap/backup/setup/email_changer.rb +6 -0
- data/lib/imap/backup/setup/folder_chooser.rb +4 -0
- data/lib/imap/backup/setup/global_options/download_strategy_chooser.rb +4 -0
- data/lib/imap/backup/setup/global_options.rb +4 -0
- data/lib/imap/backup/setup/helpers.rb +3 -0
- data/lib/imap/backup/setup.rb +5 -0
- data/lib/imap/backup/text/sanitizer.rb +9 -0
- data/lib/imap/backup/thunderbird/mailbox_exporter.rb +7 -0
- data/lib/imap/backup/uploader.rb +5 -0
- data/lib/imap/backup/version.rb +6 -1
- metadata +3 -5
- data/docs/api.md +0 -20
- data/docs/development.md +0 -110
- data/docs/migrate-server-keep-address.md +0 -47
@@ -8,6 +8,7 @@ module Imap; end
|
|
8
8
|
module Imap::Backup
|
9
9
|
class CLI < Thor; end
|
10
10
|
|
11
|
+
# Provides helper methods for CLI classes
|
11
12
|
module CLI::Helpers
|
12
13
|
def self.included(base)
|
13
14
|
base.class_eval do
|
@@ -73,6 +74,10 @@ module Imap::Backup
|
|
73
74
|
end
|
74
75
|
end
|
75
76
|
|
77
|
+
# Processes command-line parameters
|
78
|
+
# @return [Hash] the supplied command-line parameters with
|
79
|
+
# with hyphens in keys replaced by underscores
|
80
|
+
# and the keys converted to Symbols
|
76
81
|
def options
|
77
82
|
@symbolized_options ||= # rubocop:disable Naming/MemoizedInstanceVariableName
|
78
83
|
begin
|
@@ -89,6 +94,9 @@ module Imap::Backup
|
|
89
94
|
end
|
90
95
|
end
|
91
96
|
|
97
|
+
# Loads the application configuration
|
98
|
+
# @raise [ConfigurationNotFound] if the configuration file does not exist
|
99
|
+
# @return [Configuration]
|
92
100
|
def load_config(**options)
|
93
101
|
path = options[:config]
|
94
102
|
require_exists = options.key?(:require_exists) ? options[:require_exists] : true
|
@@ -102,6 +110,8 @@ module Imap::Backup
|
|
102
110
|
Configuration.new(path: path)
|
103
111
|
end
|
104
112
|
|
113
|
+
# @raise [RuntimeError] if the account does not exist
|
114
|
+
# @return [Account] the Account information for the email address
|
105
115
|
def account(config, email)
|
106
116
|
account = config.accounts.find { |a| a.username == email }
|
107
117
|
raise "#{email} is not a configured account" if !account
|
@@ -109,6 +119,9 @@ module Imap::Backup
|
|
109
119
|
account
|
110
120
|
end
|
111
121
|
|
122
|
+
# @return [Array<Account>] If email addresses have been specified
|
123
|
+
# returns the Account configurations for them.
|
124
|
+
# If non have been specified, returns all account configurations
|
112
125
|
def requested_accounts(config)
|
113
126
|
emails = (options[:accounts] || "").split(",")
|
114
127
|
if emails.any?
|
@@ -10,6 +10,7 @@ module Imap::Backup
|
|
10
10
|
class CLI; end
|
11
11
|
class CLI::Local < Thor; end
|
12
12
|
|
13
|
+
# Runs integrity check on local backups
|
13
14
|
class CLI::Local::Check
|
14
15
|
include CLI::Helpers
|
15
16
|
|
@@ -17,6 +18,8 @@ module Imap::Backup
|
|
17
18
|
@options = options
|
18
19
|
end
|
19
20
|
|
21
|
+
# Runs the check
|
22
|
+
# @return [void]
|
20
23
|
def run
|
21
24
|
results = requested_accounts(config).map do |account|
|
22
25
|
serialized_folders = Account::SerializedFolders.new(account: account)
|
@@ -10,6 +10,7 @@ module Imap; end
|
|
10
10
|
module Imap::Backup
|
11
11
|
class CLI < Thor; end
|
12
12
|
|
13
|
+
# Implements the CLI functions relating to local storage
|
13
14
|
class CLI::Local < Thor
|
14
15
|
include Thor::Actions
|
15
16
|
include CLI::Helpers
|
@@ -19,6 +20,8 @@ module Imap::Backup
|
|
19
20
|
format_option
|
20
21
|
quiet_option
|
21
22
|
verbose_option
|
23
|
+
# Lists configured accounts
|
24
|
+
# @return [void]
|
22
25
|
def accounts
|
23
26
|
names = config.accounts.map(&:username)
|
24
27
|
case options[:format]
|
@@ -44,6 +47,8 @@ module Imap::Backup
|
|
44
47
|
format_option
|
45
48
|
quiet_option
|
46
49
|
verbose_option
|
50
|
+
# Runs integrity checks on backups
|
51
|
+
# @return [void]
|
47
52
|
def check
|
48
53
|
non_logging_options = Imap::Backup::Logger.setup_logging(options)
|
49
54
|
Check.new(non_logging_options).run
|
@@ -54,6 +59,8 @@ module Imap::Backup
|
|
54
59
|
format_option
|
55
60
|
quiet_option
|
56
61
|
verbose_option
|
62
|
+
# Lists backed-up folders for an account
|
63
|
+
# @return [void]
|
57
64
|
def folders(email)
|
58
65
|
account = account(config, email)
|
59
66
|
|
@@ -74,6 +81,9 @@ module Imap::Backup
|
|
74
81
|
format_option
|
75
82
|
quiet_option
|
76
83
|
verbose_option
|
84
|
+
# Lists backed-up emails for an account folder
|
85
|
+
# @raise [RuntimeError] if the folder does not exist
|
86
|
+
# @return [void]
|
77
87
|
def list(email, folder_name)
|
78
88
|
account = account(config, email)
|
79
89
|
|
@@ -101,6 +111,9 @@ module Imap::Backup
|
|
101
111
|
format_option
|
102
112
|
quiet_option
|
103
113
|
verbose_option
|
114
|
+
# Shows the content of one or more backed-up email messages
|
115
|
+
# @raise [RuntimeError] if the folder does not exist
|
116
|
+
# @return [void]
|
104
117
|
def show(email, folder_name, uids)
|
105
118
|
account = account(config, email)
|
106
119
|
|
@@ -8,6 +8,7 @@ module Imap; end
|
|
8
8
|
module Imap::Backup
|
9
9
|
class CLI < Thor; end
|
10
10
|
|
11
|
+
# Implements the CLI functions relating to configured online accounts
|
11
12
|
class CLI::Remote < Thor
|
12
13
|
include Thor::Actions
|
13
14
|
include CLI::Helpers
|
@@ -17,6 +18,8 @@ module Imap::Backup
|
|
17
18
|
format_option
|
18
19
|
quiet_option
|
19
20
|
verbose_option
|
21
|
+
# Prints an account's folders
|
22
|
+
# @return [void]
|
20
23
|
def folders(email)
|
21
24
|
Imap::Backup::Logger.setup_logging options
|
22
25
|
folder_names = folder_names(email)
|
@@ -36,6 +39,8 @@ module Imap::Backup
|
|
36
39
|
format_option
|
37
40
|
quiet_option
|
38
41
|
verbose_option
|
42
|
+
# Prints an account's IMAP capabilities
|
43
|
+
# @return [void]
|
39
44
|
def capabilities(email)
|
40
45
|
Imap::Backup::Logger.setup_logging options
|
41
46
|
config = load_config(**options)
|
@@ -55,6 +60,8 @@ module Imap::Backup
|
|
55
60
|
format_option
|
56
61
|
quiet_option
|
57
62
|
verbose_option
|
63
|
+
# Prints an account's IMAP namespaces
|
64
|
+
# @return [void]
|
58
65
|
def namespaces(email)
|
59
66
|
Imap::Backup::Logger.setup_logging options
|
60
67
|
config = load_config(**options)
|
@@ -8,6 +8,7 @@ module Imap; end
|
|
8
8
|
module Imap::Backup
|
9
9
|
class CLI < Thor; end
|
10
10
|
|
11
|
+
# Restores backups for one or more accounts
|
11
12
|
class CLI::Restore < Thor
|
12
13
|
include Thor::Actions
|
13
14
|
include CLI::Helpers
|
@@ -18,6 +19,9 @@ module Imap::Backup
|
|
18
19
|
@options = options
|
19
20
|
end
|
20
21
|
|
22
|
+
# @!method run
|
23
|
+
# @raise [RuntimeError] if no email is specified
|
24
|
+
# @return [void]
|
21
25
|
no_commands do
|
22
26
|
def run
|
23
27
|
config = load_config(**options)
|
@@ -8,6 +8,7 @@ module Imap; end
|
|
8
8
|
module Imap::Backup
|
9
9
|
class CLI < Thor; end
|
10
10
|
|
11
|
+
# Runs the menu-driven setup program
|
11
12
|
class CLI::Setup < Thor
|
12
13
|
include Thor::Actions
|
13
14
|
include CLI::Helpers
|
@@ -17,6 +18,8 @@ module Imap::Backup
|
|
17
18
|
@options = options
|
18
19
|
end
|
19
20
|
|
21
|
+
# @!method run
|
22
|
+
# @return [void]
|
20
23
|
no_commands do
|
21
24
|
def run
|
22
25
|
config = load_config(**options, require_exists: false)
|
@@ -10,12 +10,15 @@ module Imap::Backup
|
|
10
10
|
class CLI < Thor; end
|
11
11
|
class CLI::Single < Thor; end
|
12
12
|
|
13
|
+
# Runs a backup without relying on existing configuration
|
13
14
|
class CLI::Single::Backup
|
14
15
|
def initialize(options)
|
15
16
|
@options = options
|
16
17
|
@password = nil
|
17
18
|
end
|
18
19
|
|
20
|
+
# Runs the backup
|
21
|
+
# @return [void]
|
19
22
|
def run
|
20
23
|
process_options!
|
21
24
|
account = Account.new(
|
@@ -9,6 +9,8 @@ module Imap; end
|
|
9
9
|
module Imap::Backup
|
10
10
|
class CLI < Thor; end
|
11
11
|
|
12
|
+
# Processes parameters to run a backup via command-line parameters
|
13
|
+
# (without using a configuration file)
|
12
14
|
class CLI::Single < Thor
|
13
15
|
include CLI::Helpers
|
14
16
|
|
@@ -170,6 +172,8 @@ module Imap::Backup
|
|
170
172
|
)
|
171
173
|
quiet_option
|
172
174
|
verbose_option
|
175
|
+
# Launches the backup procedure
|
176
|
+
# @return [void]
|
173
177
|
def backup
|
174
178
|
non_logging_options = Imap::Backup::Logger.setup_logging(options)
|
175
179
|
direct = Backup.new(non_logging_options)
|
@@ -4,6 +4,7 @@ require "imap/backup/serializer"
|
|
4
4
|
module Imap; end
|
5
5
|
|
6
6
|
module Imap::Backup
|
7
|
+
# Prints various statistics about an account and its backup
|
7
8
|
class CLI::Stats < Thor
|
8
9
|
include Thor::Actions
|
9
10
|
include CLI::Helpers
|
@@ -14,6 +15,8 @@ module Imap::Backup
|
|
14
15
|
@options = options
|
15
16
|
end
|
16
17
|
|
18
|
+
# @!method run
|
19
|
+
# @return [void]
|
17
20
|
no_commands do
|
18
21
|
def run
|
19
22
|
case options[:format]
|
@@ -8,10 +8,12 @@ require "imap/backup/mirror"
|
|
8
8
|
module Imap; end
|
9
9
|
|
10
10
|
module Imap::Backup
|
11
|
+
# Implements migration and mirroring
|
11
12
|
class CLI::Transfer < Thor
|
12
13
|
include Thor::Actions
|
13
14
|
include CLI::Helpers
|
14
15
|
|
16
|
+
# The possible vaues for the action parameter
|
15
17
|
ACTIONS = %i(migrate mirror).freeze
|
16
18
|
|
17
19
|
def initialize(action, source_email, destination_email, options)
|
@@ -29,6 +31,12 @@ module Imap::Backup
|
|
29
31
|
@source_prefix = nil
|
30
32
|
end
|
31
33
|
|
34
|
+
# @!method run
|
35
|
+
# @raise [RuntimeError] if the indicated action is unknown,
|
36
|
+
# or the source and destination accounts are the same,
|
37
|
+
# or either of the accounts is not configured,
|
38
|
+
# or incompatible namespace/delimter parameters have been supplied
|
39
|
+
# @return [void]
|
32
40
|
no_commands do
|
33
41
|
def run
|
34
42
|
raise "Unknown action '#{action}'" if !ACTIONS.include?(action)
|
@@ -13,6 +13,7 @@ module Imap; end
|
|
13
13
|
module Imap::Backup
|
14
14
|
class CLI < Thor; end
|
15
15
|
|
16
|
+
# Implements the CLI utility functions
|
16
17
|
class CLI::Utils < Thor
|
17
18
|
include Thor::Actions
|
18
19
|
include CLI::Helpers
|
@@ -21,6 +22,9 @@ module Imap::Backup
|
|
21
22
|
config_option
|
22
23
|
quiet_option
|
23
24
|
verbose_option
|
25
|
+
# Creates fake downloaded emails so that only the account's future emails
|
26
|
+
# will really get backed up
|
27
|
+
# @return [void]
|
24
28
|
def ignore_history(email)
|
25
29
|
Logger.setup_logging options
|
26
30
|
config = load_config(**options)
|
@@ -59,6 +63,8 @@ module Imap::Backup
|
|
59
63
|
banner: "the name of the Thunderbird profile to copy emails to",
|
60
64
|
aliases: ["-p"]
|
61
65
|
)
|
66
|
+
# Exports the account's emails to Thunderbird
|
67
|
+
# @return [void]
|
62
68
|
def export_to_thunderbird(email)
|
63
69
|
Imap::Backup::Logger.setup_logging options
|
64
70
|
force = options.key?(:force) ? options[:force] : false
|
data/lib/imap/backup/cli.rb
CHANGED
@@ -48,6 +48,7 @@ module Imap::Backup
|
|
48
48
|
|
49
49
|
# Overrides {https://www.rubydoc.info/gems/thor/Thor%2FBase%2FClassMethods:start Thor's method}
|
50
50
|
# to handle '--version' and rearrange parameters if 'help' is passed
|
51
|
+
# @return [void]
|
51
52
|
def self.start(args)
|
52
53
|
if args.include?("--version")
|
53
54
|
new.version
|
@@ -87,6 +88,7 @@ module Imap::Backup
|
|
87
88
|
refresh_option
|
88
89
|
verbose_option
|
89
90
|
# Runs account backups
|
91
|
+
# @return [void]
|
90
92
|
def backup
|
91
93
|
non_logging_options = Imap::Backup::Logger.setup_logging(options)
|
92
94
|
Backup.new(non_logging_options).run
|
@@ -148,6 +150,7 @@ module Imap::Backup
|
|
148
150
|
aliases: ["-s"]
|
149
151
|
)
|
150
152
|
# Migrates emails from one account to another
|
153
|
+
# @return [void]
|
151
154
|
def migrate(source_email, destination_email)
|
152
155
|
non_logging_options = Imap::Backup::Logger.setup_logging(options)
|
153
156
|
Transfer.new(:migrate, source_email, destination_email, non_logging_options).run
|
@@ -208,6 +211,7 @@ module Imap::Backup
|
|
208
211
|
aliases: ["-s"]
|
209
212
|
)
|
210
213
|
# Keeps one email account in line with another
|
214
|
+
# @return [void]
|
211
215
|
def mirror(source_email, destination_email)
|
212
216
|
non_logging_options = Imap::Backup::Logger.setup_logging(options)
|
213
217
|
Transfer.new(:mirror, source_email, destination_email, non_logging_options).run
|
@@ -226,6 +230,7 @@ module Imap::Backup
|
|
226
230
|
quiet_option
|
227
231
|
verbose_option
|
228
232
|
# Restores backed up meails to an account
|
233
|
+
# @return [void]
|
229
234
|
def restore(email = nil)
|
230
235
|
non_logging_options = Imap::Backup::Logger.setup_logging(options)
|
231
236
|
Restore.new(email, non_logging_options).run
|
@@ -240,6 +245,7 @@ module Imap::Backup
|
|
240
245
|
quiet_option
|
241
246
|
verbose_option
|
242
247
|
# Runs the menu-driven setup program
|
248
|
+
# @return [void]
|
243
249
|
def setup
|
244
250
|
non_logging_options = Imap::Backup::Logger.setup_logging(options)
|
245
251
|
CLI::Setup.new(non_logging_options).run
|
@@ -264,6 +270,7 @@ module Imap::Backup
|
|
264
270
|
quiet_option
|
265
271
|
verbose_option
|
266
272
|
# Prints various statistics about a configured account
|
273
|
+
# @return [void]
|
267
274
|
def stats(email)
|
268
275
|
non_logging_options = Imap::Backup::Logger.setup_logging(options)
|
269
276
|
Stats.new(email, non_logging_options).run
|
@@ -274,6 +281,7 @@ module Imap::Backup
|
|
274
281
|
|
275
282
|
desc "version", "Print the imap-backup version"
|
276
283
|
# Prints the program version
|
284
|
+
# @return [void]
|
277
285
|
def version
|
278
286
|
Kernel.puts "imap-backup #{Imap::Backup::VERSION}"
|
279
287
|
end
|
@@ -3,9 +3,11 @@ require "imap/backup/client/default"
|
|
3
3
|
module Imap; end
|
4
4
|
|
5
5
|
module Imap::Backup
|
6
|
+
# Overrides default IMAP client behaviour for Apple Mail accounts
|
6
7
|
class Client::AppleMail < Client::Default
|
7
8
|
# With Apple Mails's IMAP, passing "/" to list
|
8
9
|
# results in an empty list
|
10
|
+
# @return [String] the value to use when requesting the list of account folders
|
9
11
|
def provider_root
|
10
12
|
""
|
11
13
|
end
|
@@ -5,19 +5,24 @@ module Imap; end
|
|
5
5
|
module Imap::Backup
|
6
6
|
module Client; end
|
7
7
|
|
8
|
+
# Transparently wraps a client instance, while delaying login until it becomes necessary
|
8
9
|
class Client::AutomaticLoginWrapper
|
9
10
|
include RetryOnError
|
10
11
|
|
12
|
+
# @private
|
11
13
|
LOGIN_RETRY_CLASSES = [::EOFError, ::Errno::ECONNRESET, ::SocketError].freeze
|
12
14
|
|
15
|
+
# @return [Client]
|
13
16
|
attr_reader :client
|
14
|
-
attr_reader :login_called
|
15
17
|
|
16
18
|
def initialize(client:)
|
17
19
|
@client = client
|
18
20
|
@login_called = false
|
19
21
|
end
|
20
22
|
|
23
|
+
# Proxies calls to the client.
|
24
|
+
# Before the first call does login
|
25
|
+
# @return the return value of the client method called
|
21
26
|
def method_missing(method_name, *arguments, &block)
|
22
27
|
if login_called
|
23
28
|
client.send(method_name, *arguments, &block)
|
@@ -27,12 +32,15 @@ module Imap::Backup
|
|
27
32
|
end
|
28
33
|
end
|
29
34
|
|
35
|
+
# @return [Boolean] whether the client responds to the method
|
30
36
|
def respond_to_missing?(method_name, _include_private = false)
|
31
37
|
client.respond_to?(method_name)
|
32
38
|
end
|
33
39
|
|
34
40
|
private
|
35
41
|
|
42
|
+
attr_reader :login_called
|
43
|
+
|
36
44
|
def do_first_login
|
37
45
|
retry_on_error(errors: LOGIN_RETRY_CLASSES) do
|
38
46
|
client.login
|
@@ -8,6 +8,8 @@ module Imap; end
|
|
8
8
|
module Imap::Backup
|
9
9
|
module Client; end
|
10
10
|
|
11
|
+
# Wraps a Net::IMAP instance
|
12
|
+
# Tracks the latest folder selection in order to avoid repeated calls
|
11
13
|
class Client::Default
|
12
14
|
extend Forwardable
|
13
15
|
def_delegators :imap, *%i(
|
@@ -22,6 +24,7 @@ module Imap::Backup
|
|
22
24
|
@state = nil
|
23
25
|
end
|
24
26
|
|
27
|
+
# @return [Array<String>] the account folders
|
25
28
|
def list
|
26
29
|
root = provider_root
|
27
30
|
mailbox_lists = imap.list(root, "*")
|
@@ -31,36 +34,44 @@ module Imap::Backup
|
|
31
34
|
mailbox_lists.map { |ml| extract_name(ml) }
|
32
35
|
end
|
33
36
|
|
37
|
+
# Logs in to the account on the IMAP server
|
38
|
+
# @return [void]
|
34
39
|
def login
|
35
40
|
Logger.logger.debug "Logging in: #{account.username}/#{masked_password}"
|
36
41
|
imap.login(account.username, account.password)
|
37
42
|
Logger.logger.debug "Login complete"
|
38
43
|
end
|
39
44
|
|
45
|
+
# Logs out and back in to the server
|
46
|
+
# @return [void]
|
40
47
|
def reconnect
|
41
48
|
disconnect
|
42
49
|
login
|
43
50
|
end
|
44
51
|
|
52
|
+
# @return [String] the account username
|
45
53
|
def username
|
46
54
|
account.username
|
47
55
|
end
|
48
56
|
|
49
|
-
#
|
50
|
-
|
57
|
+
# Disconects from the server
|
58
|
+
# @return [void]
|
51
59
|
def disconnect
|
52
60
|
imap.disconnect
|
53
61
|
self.state = nil
|
54
62
|
end
|
55
63
|
|
64
|
+
# Prepares read-only access to a folder
|
65
|
+
# @return [void]
|
56
66
|
def examine(mailbox)
|
57
67
|
return if state == [:examine, mailbox]
|
58
68
|
|
59
|
-
|
69
|
+
imap.examine(mailbox)
|
60
70
|
self.state = [:examine, mailbox]
|
61
|
-
result
|
62
71
|
end
|
63
72
|
|
73
|
+
# Prepares read-write access to a folder
|
74
|
+
# @return [void]
|
64
75
|
def select(mailbox)
|
65
76
|
return if state == [:select, mailbox]
|
66
77
|
|
@@ -9,15 +9,21 @@ require "imap/backup/serializer/permission_checker"
|
|
9
9
|
module Imap; end
|
10
10
|
|
11
11
|
module Imap::Backup
|
12
|
+
# Handles the application's configuration file
|
12
13
|
class Configuration
|
14
|
+
# The default directory of the configuration file
|
13
15
|
CONFIGURATION_DIRECTORY = File.expand_path("~/.imap-backup")
|
16
|
+
# The default download strategy key
|
14
17
|
DEFAULT_STRATEGY = "delay_metadata".freeze
|
18
|
+
# The available download strategies
|
15
19
|
DOWNLOAD_STRATEGIES = [
|
16
20
|
{key: "direct", description: "write straight to disk"},
|
17
21
|
{key: DEFAULT_STRATEGY, description: "delay writing metadata"}
|
18
22
|
].freeze
|
23
|
+
# The current file version
|
19
24
|
VERSION = "2.2".freeze
|
20
25
|
|
26
|
+
# @return [String] the default configuration file path
|
21
27
|
def self.default_pathname
|
22
28
|
File.join(CONFIGURATION_DIRECTORY, "config.json")
|
23
29
|
end
|
@@ -33,10 +39,13 @@ module Imap::Backup
|
|
33
39
|
@download_strategy_modified = false
|
34
40
|
end
|
35
41
|
|
42
|
+
# @return [String] the directory containing the configuration file
|
36
43
|
def path
|
37
44
|
File.dirname(pathname)
|
38
45
|
end
|
39
46
|
|
47
|
+
# Saves the configuration file in JSON format
|
48
|
+
# @return [void]
|
40
49
|
def save
|
41
50
|
ensure_loaded!
|
42
51
|
FileUtils.mkdir_p(path) if !File.directory?(path)
|
@@ -53,6 +62,7 @@ module Imap::Backup
|
|
53
62
|
@data = nil
|
54
63
|
end
|
55
64
|
|
65
|
+
# @return [Array<Account>] the configured accounts
|
56
66
|
def accounts
|
57
67
|
@accounts ||= begin
|
58
68
|
ensure_loaded!
|
@@ -63,12 +73,15 @@ module Imap::Backup
|
|
63
73
|
end
|
64
74
|
end
|
65
75
|
|
76
|
+
# @return [String] the cofigured download strategy
|
66
77
|
def download_strategy
|
67
78
|
ensure_loaded!
|
68
79
|
|
69
80
|
@download_strategy
|
70
81
|
end
|
71
82
|
|
83
|
+
# @param value [String] the new strategy
|
84
|
+
# @return [void]
|
72
85
|
def download_strategy=(value)
|
73
86
|
raise "Unknown strategy '#{value}'" if !DOWNLOAD_STRATEGIES.find { |s| s[:key] == value }
|
74
87
|
|
@@ -3,7 +3,9 @@ require "net/imap"
|
|
3
3
|
module Imap; end
|
4
4
|
|
5
5
|
module Imap::Backup
|
6
|
+
# Downloads as yet undownloaded emails from an account's server
|
6
7
|
class Downloader
|
8
|
+
# @private
|
7
9
|
class MultiFetchFailedError < StandardError; end
|
8
10
|
|
9
11
|
def initialize(folder, serializer, multi_fetch_size: 1, reset_seen_flags_after_fetch: false)
|
@@ -14,6 +16,8 @@ module Imap::Backup
|
|
14
16
|
@uids = nil
|
15
17
|
end
|
16
18
|
|
19
|
+
# Runs the downloader
|
20
|
+
# @return [void]
|
17
21
|
def run
|
18
22
|
info("#{uids.count} new messages") if uids.any?
|
19
23
|
|
@@ -6,7 +6,13 @@ module Imap::Backup
|
|
6
6
|
module Email; end
|
7
7
|
|
8
8
|
module Email::Mboxrd
|
9
|
+
# Handles serialization and deserialization of messages
|
9
10
|
class Message
|
11
|
+
# @param serialized [String] an email message
|
12
|
+
#
|
13
|
+
# @return [String] The message without the initial 'From ' line
|
14
|
+
# and with one level of '>' quoting removed from other lines
|
15
|
+
# that start with 'From'
|
10
16
|
def self.clean_serialized(serialized)
|
11
17
|
cleaned = serialized.gsub(/^>(>*From)/, "\\1")
|
12
18
|
# Serialized messages in this format *should* start with a line
|
@@ -19,32 +25,40 @@ module Imap::Backup
|
|
19
25
|
cleaned
|
20
26
|
end
|
21
27
|
|
28
|
+
# @param serialized [String] the on-disk version of the message
|
29
|
+
#
|
30
|
+
# @return [Message] the original message
|
22
31
|
def self.from_serialized(serialized)
|
23
32
|
new(clean_serialized(serialized))
|
24
33
|
end
|
25
34
|
|
35
|
+
# @return [String] the original message body
|
26
36
|
attr_reader :supplied_body
|
27
37
|
|
28
38
|
def initialize(supplied_body)
|
29
39
|
@supplied_body = supplied_body.clone
|
30
40
|
end
|
31
41
|
|
42
|
+
# @return [String] the message with an initial 'From ADDRESS' line
|
32
43
|
def to_serialized
|
33
44
|
from_line = "From #{from}\n"
|
34
45
|
body = mboxrd_body.dup.force_encoding(Encoding::UTF_8)
|
35
46
|
from_line + body
|
36
47
|
end
|
37
48
|
|
49
|
+
# @return [Date, nil] the date of the message
|
38
50
|
def date
|
39
51
|
parsed.date
|
40
52
|
rescue StandardError
|
41
53
|
nil
|
42
54
|
end
|
43
55
|
|
56
|
+
# @return [String] the message's subject line
|
44
57
|
def subject
|
45
58
|
parsed.subject
|
46
59
|
end
|
47
60
|
|
61
|
+
# @return [String] the original message ready for transmission to an IMAP server
|
48
62
|
def imap_body
|
49
63
|
supplied_body.gsub(/(?<!\r)\n/, "\r\n")
|
50
64
|
end
|
@@ -3,7 +3,9 @@ require "imap/backup/email/provider/base"
|
|
3
3
|
module Imap; end
|
4
4
|
|
5
5
|
module Imap::Backup
|
6
|
+
# Provides overrides for Apple mail accounts
|
6
7
|
class Email::Provider::AppleMail < Email::Provider::Base
|
8
|
+
# @return [String] the Apple Mail IMAP server host name
|
7
9
|
def host
|
8
10
|
"imap.mail.me.com"
|
9
11
|
end
|
@@ -4,7 +4,9 @@ module Imap::Backup
|
|
4
4
|
module Email; end
|
5
5
|
class Email::Provider; end
|
6
6
|
|
7
|
+
# Supplies defaults for email provider behaviour
|
7
8
|
class Email::Provider::Base
|
9
|
+
# @return [Hash] defaults for the Net::IMAP connection
|
8
10
|
def options
|
9
11
|
# rubocop:disable Naming/VariableNumber
|
10
12
|
{port: 993, ssl: {ssl_version: :TLSv1_2}}
|
@@ -3,7 +3,9 @@ require "imap/backup/email/provider/base"
|
|
3
3
|
module Imap; end
|
4
4
|
|
5
5
|
module Imap::Backup
|
6
|
+
# Provides overrides for Fastmail accounts
|
6
7
|
class Email::Provider::Fastmail < Email::Provider::Base
|
8
|
+
# @return [String] the Fastmail IMAP server host name
|
7
9
|
def host
|
8
10
|
"imap.fastmail.com"
|
9
11
|
end
|
@@ -3,7 +3,9 @@ require "imap/backup/email/provider/base"
|
|
3
3
|
module Imap; end
|
4
4
|
|
5
5
|
module Imap::Backup
|
6
|
+
# Provides overrides for GMail accounts
|
6
7
|
class Email::Provider::GMail < Email::Provider::Base
|
8
|
+
# @return [String] the GMail IMAP server host name
|
7
9
|
def host
|
8
10
|
"imap.gmail.com"
|
9
11
|
end
|
@@ -3,7 +3,9 @@ require "imap/backup/email/provider/base"
|
|
3
3
|
module Imap; end
|
4
4
|
|
5
5
|
module Imap::Backup
|
6
|
+
# Provides overrides for Purelymail accounts
|
6
7
|
class Email::Provider::Purelymail < Email::Provider::Base
|
8
|
+
# @return [String] The Purelymail IMAP server host name
|
7
9
|
def host
|
8
10
|
"mailserver.purelymail.com"
|
9
11
|
end
|