imap-backup 16.3.0 → 16.4.1
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 +3 -92
- data/docs/development.md +8 -0
- data/docs/testing.md +93 -0
- data/imap-backup.gemspec +12 -11
- data/lib/imap/backup/account/backup.rb +32 -10
- data/lib/imap/backup/account/backup_folders.rb +2 -0
- data/lib/imap/backup/account/client_factory.rb +1 -0
- data/lib/imap/backup/account/folder.rb +9 -0
- data/lib/imap/backup/account/folder_backup.rb +3 -0
- data/lib/imap/backup/account/folder_ensurer.rb +1 -0
- data/lib/imap/backup/account/folder_mapper.rb +6 -0
- data/lib/imap/backup/account/local_only_folder_deleter.rb +1 -0
- data/lib/imap/backup/account/locker.rb +40 -0
- data/lib/imap/backup/account/restore.rb +13 -2
- data/lib/imap/backup/account/serialized_folders.rb +1 -0
- data/lib/imap/backup/account.rb +50 -18
- data/lib/imap/backup/cli/backup.rb +6 -0
- data/lib/imap/backup/cli/local/check.rb +5 -0
- data/lib/imap/backup/cli/local.rb +1 -0
- data/lib/imap/backup/cli/options.rb +1 -0
- data/lib/imap/backup/cli/remote.rb +5 -2
- data/lib/imap/backup/cli/restore.rb +12 -5
- data/lib/imap/backup/cli/setup.rb +3 -0
- data/lib/imap/backup/cli/single/backup.rb +20 -0
- data/lib/imap/backup/cli/stats.rb +11 -0
- data/lib/imap/backup/cli/transfer.rb +32 -8
- data/lib/imap/backup/cli/utils.rb +17 -9
- data/lib/imap/backup/client/automatic_login_wrapper.rb +1 -0
- data/lib/imap/backup/client/default.rb +2 -0
- data/lib/imap/backup/configuration.rb +5 -3
- data/lib/imap/backup/downloader.rb +4 -0
- data/lib/imap/backup/email/mboxrd/message.rb +1 -0
- data/lib/imap/backup/file_mode.rb +1 -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/lockfile.rb +94 -0
- data/lib/imap/backup/logger.rb +1 -1
- data/lib/imap/backup/migrator.rb +3 -0
- data/lib/imap/backup/mirror/map.rb +2 -1
- data/lib/imap/backup/mirror.rb +6 -2
- data/lib/imap/backup/serializer/mbox.rb +1 -1
- data/lib/imap/backup/serializer/message.rb +1 -1
- data/lib/imap/backup/serializer/permission_checker.rb +1 -1
- data/lib/imap/backup/serializer/version2_migrator.rb +1 -1
- data/lib/imap/backup/setup/asker.rb +1 -0
- data/lib/imap/backup/setup.rb +1 -0
- data/lib/imap/backup/thunderbird/mailbox_exporter.rb +1 -0
- data/lib/imap/backup/version.rb +2 -2
- metadata +20 -2
|
@@ -109,9 +109,12 @@ module Imap::Backup
|
|
|
109
109
|
Kernel.puts list.to_json
|
|
110
110
|
end
|
|
111
111
|
|
|
112
|
+
NAMESPACE_TEMPLATE = "%-10<name>s %-10<prefix>s %<delim>s".freeze
|
|
113
|
+
private_constant :NAMESPACE_TEMPLATE
|
|
114
|
+
|
|
112
115
|
def list_namespaces(namespaces)
|
|
113
116
|
Kernel.puts format(
|
|
114
|
-
|
|
117
|
+
NAMESPACE_TEMPLATE,
|
|
115
118
|
{name: "Name", prefix: "Prefix", delim: "Delimiter"}
|
|
116
119
|
)
|
|
117
120
|
list_namespace namespaces, :personal
|
|
@@ -122,7 +125,7 @@ module Imap::Backup
|
|
|
122
125
|
def list_namespace(namespaces, name)
|
|
123
126
|
info = namespace_info(namespaces.send(name).first, quote: true)
|
|
124
127
|
if info
|
|
125
|
-
Kernel.puts format(
|
|
128
|
+
Kernel.puts format(NAMESPACE_TEMPLATE, name: name, **info)
|
|
126
129
|
else
|
|
127
130
|
Kernel.puts format("%-10<name>s (Not defined)", name: name)
|
|
128
131
|
end
|
|
@@ -14,6 +14,13 @@ module Imap::Backup
|
|
|
14
14
|
include Thor::Actions
|
|
15
15
|
include CLI::Helpers
|
|
16
16
|
|
|
17
|
+
# @param email [String, nil] optional email address identifying the account to restore
|
|
18
|
+
# @param options [Hash] CLI options controlling output
|
|
19
|
+
# @option opts [String] :config (nil) the path to the configuration file
|
|
20
|
+
# @option opts [String] :erb_configuration (nil) the path to the ERB configuration file
|
|
21
|
+
# @option opts [Array<String>] :accounts (nil) the accounts to restore
|
|
22
|
+
# @option opts [String] :delimiter ("/") the destination folder delimiter
|
|
23
|
+
# @option opts [String] :prefix ("") a prefix applied to restored folder names
|
|
17
24
|
def initialize(email = nil, options)
|
|
18
25
|
super([])
|
|
19
26
|
@email = email
|
|
@@ -30,9 +37,6 @@ module Imap::Backup
|
|
|
30
37
|
when email && !options.key?(:accounts)
|
|
31
38
|
account = account(config, email)
|
|
32
39
|
restore(account, **restore_options)
|
|
33
|
-
when !email && !options.key?(:accounts)
|
|
34
|
-
Logger.logger.info "Calling restore without an EMAIL parameter is deprecated"
|
|
35
|
-
config.accounts.each { |a| restore(a) }
|
|
36
40
|
when email && options.key?(:accounts)
|
|
37
41
|
raise "Missing EMAIL parameter"
|
|
38
42
|
when !email && options.key?(:accounts)
|
|
@@ -41,6 +45,9 @@ module Imap::Backup
|
|
|
41
45
|
"please pass a single EMAIL parameter"
|
|
42
46
|
)
|
|
43
47
|
requested_accounts(config).each { |a| restore(a) }
|
|
48
|
+
else
|
|
49
|
+
Logger.logger.info "Calling restore without an EMAIL parameter is deprecated"
|
|
50
|
+
config.accounts.each { |a| restore(a) }
|
|
44
51
|
end
|
|
45
52
|
end
|
|
46
53
|
end
|
|
@@ -50,8 +57,8 @@ module Imap::Backup
|
|
|
50
57
|
attr_reader :email
|
|
51
58
|
attr_reader :options
|
|
52
59
|
|
|
53
|
-
def restore(account, **
|
|
54
|
-
restore = Account::Restore.new(account: account, **
|
|
60
|
+
def restore(account, **)
|
|
61
|
+
restore = Account::Restore.new(account: account, **)
|
|
55
62
|
restore.run
|
|
56
63
|
end
|
|
57
64
|
|
|
@@ -13,6 +13,9 @@ module Imap::Backup
|
|
|
13
13
|
include Thor::Actions
|
|
14
14
|
include CLI::Helpers
|
|
15
15
|
|
|
16
|
+
# @param options [Hash] CLI options controlling output
|
|
17
|
+
# @option opts [String] :config (nil) the path to the configuration file
|
|
18
|
+
# @option opts [String] :erb_configuration (nil) the path to the ERB configuration file
|
|
16
19
|
def initialize(options)
|
|
17
20
|
super([])
|
|
18
21
|
@options = options
|
|
@@ -12,6 +12,26 @@ module Imap::Backup
|
|
|
12
12
|
|
|
13
13
|
# Runs a backup without relying on existing configuration
|
|
14
14
|
class CLI::Single::Backup
|
|
15
|
+
# @param options [Hash] CLI options controlling output
|
|
16
|
+
# @option opts [String] :config (nil) the path to the configuration file
|
|
17
|
+
# @option opts [String] :erb_configuration (nil) the path to the ERB configuration file
|
|
18
|
+
# @option opts [String] :email the email address identifying the account to backup
|
|
19
|
+
# @option opts [String] :password (nil) the password for the account
|
|
20
|
+
# @option opts [String] :password_environment_variable (nil) the name of an environment variable
|
|
21
|
+
# containing the password
|
|
22
|
+
# @option opts [String] :password_file (nil) the path to a file containing the password
|
|
23
|
+
# @option opts [String] :server the IMAP server address
|
|
24
|
+
# @option opts [String] :download_strategy (nil) the download strategy to use, either "delay"
|
|
25
|
+
# or "direct"
|
|
26
|
+
# @option opts [Boolean] :folder_blacklist (false) whether to treat folder list as a blacklist
|
|
27
|
+
# @option opts [Array<String>] :folder ([]) the folders to backup
|
|
28
|
+
# @option opts [String] :path (nil) the local path to store the backup
|
|
29
|
+
# @option opts [Boolean] :mirror (false) whether to run in mirror mode
|
|
30
|
+
# @option opts [Integer] :multi_fetch_size (nil) the number of messages to fetch in a single
|
|
31
|
+
# operation
|
|
32
|
+
# @option opts [Boolean] :refresh (false) whether to force refresh of folder metadata
|
|
33
|
+
# @option opts [Boolean] :reset_seen_flags_after_fetch (false) whether to reset seen flags
|
|
34
|
+
# after fetching messages
|
|
15
35
|
def initialize(options)
|
|
16
36
|
@options = options
|
|
17
37
|
@password = nil
|
|
@@ -1,14 +1,24 @@
|
|
|
1
|
+
require "thor"
|
|
2
|
+
|
|
1
3
|
require "imap/backup/account/backup_folders"
|
|
4
|
+
require "imap/backup/cli/helpers"
|
|
2
5
|
require "imap/backup/serializer"
|
|
3
6
|
|
|
4
7
|
module Imap; end
|
|
5
8
|
|
|
6
9
|
module Imap::Backup
|
|
10
|
+
class CLI < Thor; end
|
|
11
|
+
|
|
7
12
|
# Prints various statistics about an account and its backup
|
|
8
13
|
class CLI::Stats < Thor
|
|
9
14
|
include Thor::Actions
|
|
10
15
|
include CLI::Helpers
|
|
11
16
|
|
|
17
|
+
# @param email [String] the email address identifying the account to inspect
|
|
18
|
+
# @param options [Hash] CLI options controlling output
|
|
19
|
+
# @option opts [String] :config (nil) the path to the configuration file
|
|
20
|
+
# @option opts [String] :erb_configuration (nil) the path to the ERB configuration file
|
|
21
|
+
# @option opts [String] :format ("text") the output format, either "text" or "json"
|
|
12
22
|
def initialize(email, options)
|
|
13
23
|
super([])
|
|
14
24
|
@email = email
|
|
@@ -37,6 +47,7 @@ module Imap::Backup
|
|
|
37
47
|
{name: :local, width: 8, alignment: :right}
|
|
38
48
|
].freeze
|
|
39
49
|
ALIGNMENT_FORMAT_SYMBOL = {left: "-", right: " "}.freeze
|
|
50
|
+
private_constant :TEXT_COLUMNS, :ALIGNMENT_FORMAT_SYMBOL
|
|
40
51
|
|
|
41
52
|
attr_reader :email
|
|
42
53
|
attr_reader :options
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
require "imap/backup/account/folder_mapper"
|
|
2
|
+
require "imap/backup/account/locker"
|
|
2
3
|
require "imap/backup/cli/backup"
|
|
3
4
|
require "imap/backup/cli/helpers"
|
|
4
5
|
require "imap/backup/logger"
|
|
@@ -15,6 +16,9 @@ module Imap::Backup
|
|
|
15
16
|
# The possible values for the action parameter
|
|
16
17
|
ACTIONS = %i(copy migrate mirror).freeze
|
|
17
18
|
|
|
19
|
+
# @param action [Symbol] one of ACTIONS describing the transfer mode
|
|
20
|
+
# @param source_email [String] the email of the source account
|
|
21
|
+
# @param destination_email [String] the email of the destination account
|
|
18
22
|
def initialize(action, source_email, destination_email, options)
|
|
19
23
|
@action = action
|
|
20
24
|
@source_email = source_email
|
|
@@ -23,9 +27,11 @@ module Imap::Backup
|
|
|
23
27
|
@automatic_namespaces = nil
|
|
24
28
|
@config_path = nil
|
|
25
29
|
@destination_delimiter = nil
|
|
30
|
+
@destination_locker = nil
|
|
26
31
|
@destination_prefix = nil
|
|
27
32
|
@reset = nil
|
|
28
33
|
@source_delimiter = nil
|
|
34
|
+
@source_locker = nil
|
|
29
35
|
@source_prefix = nil
|
|
30
36
|
end
|
|
31
37
|
|
|
@@ -34,22 +40,19 @@ module Imap::Backup
|
|
|
34
40
|
# or the source and destination accounts are the same,
|
|
35
41
|
# or either of the accounts is not configured,
|
|
36
42
|
# or incompatible namespace/delimiter parameters have been supplied
|
|
43
|
+
# or one or both of the accounts is locked by another process.
|
|
37
44
|
# @return [void]
|
|
38
45
|
def run
|
|
39
46
|
raise "Unknown action '#{action}'" if !ACTIONS.include?(action)
|
|
40
47
|
|
|
41
48
|
process_options!
|
|
42
49
|
warn_if_source_account_is_not_in_mirror_mode if action == :mirror
|
|
50
|
+
|
|
43
51
|
run_backup if %i(copy mirror).include?(action)
|
|
44
52
|
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
Mirror.new(serializer, folder, reset: false).run
|
|
49
|
-
when :migrate
|
|
50
|
-
Migrator.new(serializer, folder, reset: reset).run
|
|
51
|
-
when :mirror
|
|
52
|
-
Mirror.new(serializer, folder, reset: true).run
|
|
53
|
+
source_locker.with_lock do
|
|
54
|
+
destination_locker.with_lock do
|
|
55
|
+
perform_action_on_folders
|
|
53
56
|
end
|
|
54
57
|
end
|
|
55
58
|
end
|
|
@@ -68,6 +71,19 @@ module Imap::Backup
|
|
|
68
71
|
attr_reader :source_email
|
|
69
72
|
attr_accessor :source_prefix
|
|
70
73
|
|
|
74
|
+
def perform_action_on_folders
|
|
75
|
+
folders.each do |serializer, folder|
|
|
76
|
+
case action
|
|
77
|
+
when :copy
|
|
78
|
+
Mirror.new(serializer, folder, reset: false).run
|
|
79
|
+
when :migrate
|
|
80
|
+
Migrator.new(serializer, folder, reset: reset).run
|
|
81
|
+
when :mirror
|
|
82
|
+
Mirror.new(serializer, folder, reset: true).run
|
|
83
|
+
end
|
|
84
|
+
end
|
|
85
|
+
end
|
|
86
|
+
|
|
71
87
|
def process_options!
|
|
72
88
|
self.automatic_namespaces = options[:automatic_namespaces] || false
|
|
73
89
|
self.config_path = options[:config]
|
|
@@ -175,5 +191,13 @@ module Imap::Backup
|
|
|
175
191
|
def source_account
|
|
176
192
|
config.accounts.find { |a| a.username == source_email }
|
|
177
193
|
end
|
|
194
|
+
|
|
195
|
+
def source_locker
|
|
196
|
+
@source_locker ||= Account::Locker.new(account: source_account)
|
|
197
|
+
end
|
|
198
|
+
|
|
199
|
+
def destination_locker
|
|
200
|
+
@destination_locker ||= Account::Locker.new(account: destination_account)
|
|
201
|
+
end
|
|
178
202
|
end
|
|
179
203
|
end
|
|
@@ -33,15 +33,10 @@ module Imap::Backup
|
|
|
33
33
|
Logger.setup_logging options
|
|
34
34
|
config = load_config(**options)
|
|
35
35
|
account = account(config, email)
|
|
36
|
+
locker ||= Account::Locker.new(account: account)
|
|
36
37
|
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
)
|
|
40
|
-
backup_folders.each do |folder|
|
|
41
|
-
next if !folder.exist?
|
|
42
|
-
|
|
43
|
-
serializer = Serializer.new(account.local_path, folder.name)
|
|
44
|
-
do_ignore_folder_history(folder, serializer)
|
|
38
|
+
locker.with_lock do
|
|
39
|
+
ignore_account_history(account)
|
|
45
40
|
end
|
|
46
41
|
end
|
|
47
42
|
|
|
@@ -99,8 +94,21 @@ module Imap::Backup
|
|
|
99
94
|
private
|
|
100
95
|
|
|
101
96
|
FAKE_EMAIL = "fake@email.com".freeze
|
|
97
|
+
private_constant :FAKE_EMAIL
|
|
98
|
+
|
|
99
|
+
def ignore_account_history(account)
|
|
100
|
+
backup_folders = Account::BackupFolders.new(
|
|
101
|
+
client: account.client, account: account
|
|
102
|
+
)
|
|
103
|
+
backup_folders.each do |folder|
|
|
104
|
+
next if !folder.exist?
|
|
105
|
+
|
|
106
|
+
serializer = Serializer.new(account.local_path, folder.name)
|
|
107
|
+
ignore_folder_history(folder, serializer)
|
|
108
|
+
end
|
|
109
|
+
end
|
|
102
110
|
|
|
103
|
-
def
|
|
111
|
+
def ignore_folder_history(folder, serializer)
|
|
104
112
|
uids = folder.uids - serializer.uids
|
|
105
113
|
Logger.logger.info "Folder '#{folder.name}' - #{uids.length} messages"
|
|
106
114
|
|
|
@@ -13,11 +13,13 @@ module Imap::Backup
|
|
|
13
13
|
# Tracks the latest folder selection in order to avoid repeated calls
|
|
14
14
|
class Client::Default
|
|
15
15
|
extend Forwardable
|
|
16
|
+
|
|
16
17
|
def_delegators :imap, *%i(
|
|
17
18
|
append authenticate capability create expunge namespace
|
|
18
19
|
responses uid_fetch uid_search uid_store
|
|
19
20
|
)
|
|
20
21
|
|
|
22
|
+
# @param account [Account] the account whose server is accessed
|
|
21
23
|
def initialize(account)
|
|
22
24
|
@account = account
|
|
23
25
|
@state = nil
|
|
@@ -32,6 +32,7 @@ module Imap::Backup
|
|
|
32
32
|
File.exist?(path || default_pathname)
|
|
33
33
|
end
|
|
34
34
|
|
|
35
|
+
# @param path [String, nil] optional path to the configuration file
|
|
35
36
|
def initialize(path: nil)
|
|
36
37
|
@pathname = path || self.class.default_pathname
|
|
37
38
|
@download_strategy = nil
|
|
@@ -68,7 +69,10 @@ module Imap::Backup
|
|
|
68
69
|
ensure_loaded!
|
|
69
70
|
accounts = data[:accounts].map do |attr|
|
|
70
71
|
Account.new(attr)
|
|
71
|
-
|
|
72
|
+
rescue ArgumentError => e
|
|
73
|
+
Logger.logger.error("Skipping invalid account in config: #{e.message}")
|
|
74
|
+
nil
|
|
75
|
+
end.compact
|
|
72
76
|
inject_global_attributes(accounts)
|
|
73
77
|
end
|
|
74
78
|
end
|
|
@@ -108,8 +112,6 @@ module Imap::Backup
|
|
|
108
112
|
|
|
109
113
|
private
|
|
110
114
|
|
|
111
|
-
VERSION_2_1 = "2.1".freeze
|
|
112
|
-
|
|
113
115
|
attr_reader :pathname
|
|
114
116
|
|
|
115
117
|
def ensure_loaded!
|
|
@@ -8,6 +8,10 @@ module Imap::Backup
|
|
|
8
8
|
# @private
|
|
9
9
|
class MultiFetchFailedError < StandardError; end
|
|
10
10
|
|
|
11
|
+
# @param folder [Account::Folder] the remote folder to download from
|
|
12
|
+
# @param serializer [Serializer] the local serializer that stores messages
|
|
13
|
+
# @param multi_fetch_size [Integer] how many messages to fetch per batch
|
|
14
|
+
# @param reset_seen_flags_after_fetch [Boolean] true to restore unseen flags after fetch
|
|
11
15
|
def initialize(folder, serializer, multi_fetch_size: 1, reset_seen_flags_after_fetch: false)
|
|
12
16
|
@folder = folder
|
|
13
17
|
@serializer = serializer
|
|
@@ -6,6 +6,8 @@ module Imap::Backup
|
|
|
6
6
|
# The number of messages to process at a time
|
|
7
7
|
CHUNK_SIZE = 100
|
|
8
8
|
|
|
9
|
+
# @param folder [Account::Folder] the remote folder whose flags are refreshed
|
|
10
|
+
# @param serializer [Serializer] the local serializer providing stored UIDs
|
|
9
11
|
def initialize(folder, serializer)
|
|
10
12
|
@folder = folder
|
|
11
13
|
@serializer = serializer
|
|
@@ -5,6 +5,8 @@ module Imap; end
|
|
|
5
5
|
module Imap::Backup
|
|
6
6
|
# Deletes locally backed-up emails that are no longer on the server
|
|
7
7
|
class LocalOnlyMessageDeleter
|
|
8
|
+
# @param folder [Account::Folder] the remote folder used to detect missing UIDs
|
|
9
|
+
# @param serializer [Serializer] the local serializer that may contain stale messages
|
|
8
10
|
def initialize(folder, serializer)
|
|
9
11
|
@folder = folder
|
|
10
12
|
@serializer = serializer
|
|
@@ -0,0 +1,94 @@
|
|
|
1
|
+
require "sys/proctable"
|
|
2
|
+
require "json"
|
|
3
|
+
|
|
4
|
+
module Imap; end
|
|
5
|
+
|
|
6
|
+
module Imap::Backup
|
|
7
|
+
class Lockfile
|
|
8
|
+
# An error that is thrown if a lockfile already exists
|
|
9
|
+
class LockfileExistsError < StandardError; end
|
|
10
|
+
class ProcessStartTimeUnavailableError < StandardError; end
|
|
11
|
+
|
|
12
|
+
attr_reader :path
|
|
13
|
+
|
|
14
|
+
# Initializes a new Lockfile instance.
|
|
15
|
+
# @param path [String] the path to the lockfile
|
|
16
|
+
def initialize(path:)
|
|
17
|
+
@path = path
|
|
18
|
+
end
|
|
19
|
+
|
|
20
|
+
# Creates the lockfile, yields to the given block
|
|
21
|
+
# and ensures the lockfile is removed afterwards.
|
|
22
|
+
def with_lock(&block)
|
|
23
|
+
raise LockfileExistsError, "Lockfile already exists at #{path}" if exists?
|
|
24
|
+
|
|
25
|
+
begin
|
|
26
|
+
create
|
|
27
|
+
block.call
|
|
28
|
+
ensure
|
|
29
|
+
remove
|
|
30
|
+
end
|
|
31
|
+
end
|
|
32
|
+
|
|
33
|
+
# Checks if the lockfile exists.
|
|
34
|
+
# @return [Boolean] true if the lockfile exists, false otherwise
|
|
35
|
+
def exists?
|
|
36
|
+
File.exist?(path)
|
|
37
|
+
end
|
|
38
|
+
|
|
39
|
+
# Removes the lockfile.
|
|
40
|
+
def remove
|
|
41
|
+
FileUtils.rm_f(path)
|
|
42
|
+
end
|
|
43
|
+
|
|
44
|
+
# Checks if the lockfile is stale (i.e., the process that created it is no longer running).
|
|
45
|
+
# @return [Boolean] true if the lockfile is stale, false otherwise
|
|
46
|
+
def stale?
|
|
47
|
+
return false if !exists?
|
|
48
|
+
|
|
49
|
+
file_content = File.read(path)
|
|
50
|
+
data = JSON.parse(file_content, symbolize_names: true)
|
|
51
|
+
pid = data[:pid]
|
|
52
|
+
starttime = data[:starttime]
|
|
53
|
+
proc_table_entry = Sys::ProcTable.ps(pid: pid)
|
|
54
|
+
|
|
55
|
+
return true if proc_table_entry.nil?
|
|
56
|
+
|
|
57
|
+
other_starttime = starttime(proc_table_entry)
|
|
58
|
+
|
|
59
|
+
other_starttime != starttime
|
|
60
|
+
end
|
|
61
|
+
|
|
62
|
+
private
|
|
63
|
+
|
|
64
|
+
def create
|
|
65
|
+
pid = Process.pid
|
|
66
|
+
proc_table_entry = Sys::ProcTable.ps(pid: pid)
|
|
67
|
+
|
|
68
|
+
if proc_table_entry.nil?
|
|
69
|
+
raise ProcessStartTimeUnavailableError, "Unable to get process info for PID #{pid}"
|
|
70
|
+
end
|
|
71
|
+
|
|
72
|
+
starttime = starttime(proc_table_entry)
|
|
73
|
+
|
|
74
|
+
data = {
|
|
75
|
+
pid: pid,
|
|
76
|
+
starttime: starttime
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
json_data = JSON.generate(data)
|
|
80
|
+
File.write(path, json_data)
|
|
81
|
+
end
|
|
82
|
+
|
|
83
|
+
def starttime(proc_table_entry)
|
|
84
|
+
case
|
|
85
|
+
when proc_table_entry.respond_to?(:starttime)
|
|
86
|
+
proc_table_entry.starttime
|
|
87
|
+
when proc_table_entry.respond_to?(:start_tvsec)
|
|
88
|
+
proc_table_entry.start_tvsec
|
|
89
|
+
else
|
|
90
|
+
raise ProcessStartTimeUnavailableError, "Proctable entry structure unknown"
|
|
91
|
+
end
|
|
92
|
+
end
|
|
93
|
+
end
|
|
94
|
+
end
|
data/lib/imap/backup/logger.rb
CHANGED
|
@@ -49,7 +49,7 @@ module Imap::Backup
|
|
|
49
49
|
end
|
|
50
50
|
|
|
51
51
|
# Wraps a block, filtering output to standard error,
|
|
52
|
-
#
|
|
52
|
+
# hiding passwords, and outputs the results to standard out
|
|
53
53
|
# @return [void]
|
|
54
54
|
def self.sanitize_stderr(&block)
|
|
55
55
|
sanitizer = Text::Sanitizer.new($stdout)
|
data/lib/imap/backup/migrator.rb
CHANGED
|
@@ -5,6 +5,9 @@ module Imap; end
|
|
|
5
5
|
module Imap::Backup
|
|
6
6
|
# Copies a folder of backed-up emails to an online folder
|
|
7
7
|
class Migrator
|
|
8
|
+
# @param serializer [Serializer] the local folder to migrate
|
|
9
|
+
# @param folder [Account::Folder] the destination folder on the server
|
|
10
|
+
# @param reset [Boolean] true to clear the destination before uploading
|
|
8
11
|
def initialize(serializer, folder, reset: false)
|
|
9
12
|
@folder = folder
|
|
10
13
|
@reset = reset
|
|
@@ -7,6 +7,8 @@ module Imap::Backup
|
|
|
7
7
|
|
|
8
8
|
# Keeps track of the mapping between source and destination UIDs
|
|
9
9
|
class Mirror::Map
|
|
10
|
+
# @param pathname [String] the path to the on-disk UID map
|
|
11
|
+
# @param destination [String] the destination account identifier
|
|
10
12
|
def initialize(pathname:, destination:)
|
|
11
13
|
@pathname = pathname
|
|
12
14
|
@destination = destination
|
|
@@ -18,7 +20,6 @@ module Imap::Backup
|
|
|
18
20
|
end
|
|
19
21
|
|
|
20
22
|
# @return [Boolean] whether the supplied values match the existing
|
|
21
|
-
# UID validity values
|
|
22
23
|
def check_uid_validities(source:, destination:)
|
|
23
24
|
store
|
|
24
25
|
return false if source != source_uid_validity
|
data/lib/imap/backup/mirror.rb
CHANGED
|
@@ -5,16 +5,19 @@ module Imap; end
|
|
|
5
5
|
module Imap::Backup
|
|
6
6
|
# Synchronises a folder between a source and destination
|
|
7
7
|
class Mirror
|
|
8
|
+
# @param serializer [Serializer] the source of backed-up messages
|
|
9
|
+
# @param folder [Account::Folder] the destination folder to mirror into
|
|
10
|
+
# @param reset [Boolean] true to delete destination-only messages first
|
|
8
11
|
def initialize(serializer, folder, reset: false)
|
|
9
12
|
@serializer = serializer
|
|
10
13
|
@folder = folder
|
|
11
14
|
@reset = reset
|
|
12
15
|
end
|
|
13
16
|
|
|
14
|
-
# If necessary,
|
|
17
|
+
# If necessary, creates the destination folder,
|
|
15
18
|
# then deletes any messages in the destination folder
|
|
16
19
|
# that are not in the local store,
|
|
17
|
-
# sets existing messages'
|
|
20
|
+
# sets existing messages' flags
|
|
18
21
|
# then appends any missing messages
|
|
19
22
|
# and saves the mapping file
|
|
20
23
|
# @return [void]
|
|
@@ -29,6 +32,7 @@ module Imap::Backup
|
|
|
29
32
|
private
|
|
30
33
|
|
|
31
34
|
CHUNK_SIZE = 100
|
|
35
|
+
private_constant :CHUNK_SIZE
|
|
32
36
|
|
|
33
37
|
attr_reader :serializer
|
|
34
38
|
attr_reader :folder
|
|
@@ -12,7 +12,7 @@ module Imap::Backup
|
|
|
12
12
|
# @return [Array[Symbol]] the message's flags
|
|
13
13
|
attr_accessor :flags
|
|
14
14
|
# @return [Integer] the length of the message (as stored on disk)
|
|
15
|
-
|
|
15
|
+
attr_accessor :length
|
|
16
16
|
# @return [Integer] the start of the message inside the mailbox file
|
|
17
17
|
attr_reader :offset
|
|
18
18
|
# @return [Integer] the message's UID
|
data/lib/imap/backup/setup.rb
CHANGED