imap-backup 14.4.4 → 14.4.5
Sign up to get free protection for your applications and to get access to all the features.
- 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
@@ -3,15 +3,11 @@ require "imap/backup/email/provider/base"
|
|
3
3
|
module Imap; end
|
4
4
|
|
5
5
|
module Imap::Backup
|
6
|
+
# Provides overrides when the IMAP provider is not known
|
6
7
|
class Email::Provider::Unknown < Email::Provider::Base
|
7
8
|
# We don't know how to guess the IMAP server
|
9
|
+
# @return [nil]
|
8
10
|
def host
|
9
11
|
end
|
10
|
-
|
11
|
-
def options
|
12
|
-
# rubocop:disable Naming/VariableNumber
|
13
|
-
{port: 993, ssl: {ssl_version: :TLSv1_2}}
|
14
|
-
# rubocop:enable Naming/VariableNumber
|
15
|
-
end
|
16
12
|
end
|
17
13
|
end
|
@@ -9,7 +9,12 @@ module Imap; end
|
|
9
9
|
module Imap::Backup
|
10
10
|
module Email; end
|
11
11
|
|
12
|
+
# Provides a class factory for email account providers
|
12
13
|
class Email::Provider
|
14
|
+
# @param address [String] an email address
|
15
|
+
# @return [Email::Provider::Fastmail, Email::Provider::GMail, Email::Provider::AppleMail,
|
16
|
+
# Email::Provider::Purelymail, Email::Provider::Unknown]
|
17
|
+
# an instance supplying default values for the email's account set up
|
13
18
|
def self.for_address(address)
|
14
19
|
# rubocop:disable Lint/DuplicateBranch
|
15
20
|
case
|
@@ -1,11 +1,13 @@
|
|
1
1
|
module Imap; end
|
2
2
|
|
3
3
|
module Imap::Backup
|
4
|
+
# Accesses a file's access permissions
|
4
5
|
class FileMode
|
5
6
|
def initialize(filename:)
|
6
7
|
@filename = filename
|
7
8
|
end
|
8
9
|
|
10
|
+
# @return [Integer, nil] The user, group and "other" part of the file's "mode"
|
9
11
|
def mode
|
10
12
|
return nil if !File.exist?(filename)
|
11
13
|
|
@@ -1,7 +1,9 @@
|
|
1
1
|
module Imap; end
|
2
2
|
|
3
3
|
module Imap::Backup
|
4
|
+
# Updates the flags on backed-up emails
|
4
5
|
class FlagRefresher
|
6
|
+
# The number of messages to process at a time
|
5
7
|
CHUNK_SIZE = 100
|
6
8
|
|
7
9
|
def initialize(folder, serializer)
|
@@ -9,6 +11,8 @@ module Imap::Backup
|
|
9
11
|
@serializer = serializer
|
10
12
|
end
|
11
13
|
|
14
|
+
# Runs the update
|
15
|
+
# @return [void]
|
12
16
|
def run
|
13
17
|
uids = serializer.uids.clone
|
14
18
|
|
@@ -3,6 +3,7 @@ require "imap/backup/logger"
|
|
3
3
|
module Imap; end
|
4
4
|
|
5
5
|
module Imap::Backup
|
6
|
+
# Deletes locally backed-up emails that are no longer on the server
|
6
7
|
class LocalOnlyMessageDeleter
|
7
8
|
def initialize(folder, serializer)
|
8
9
|
@folder = folder
|
@@ -12,6 +13,7 @@ module Imap::Backup
|
|
12
13
|
# TODO: this method is very slow as it copies all messages.
|
13
14
|
# A quicker method would only remove UIDs from the .imap file,
|
14
15
|
# but that would require a garbage collection later.
|
16
|
+
# @return [void]
|
15
17
|
def run
|
16
18
|
local_only_uids = serializer.uids - folder.uids
|
17
19
|
if local_only_uids.empty?
|
data/lib/imap/backup/logger.rb
CHANGED
@@ -7,13 +7,26 @@ require "imap/backup/text/sanitizer"
|
|
7
7
|
module Imap; end
|
8
8
|
|
9
9
|
module Imap::Backup
|
10
|
+
# Wraps the standard logger, providing configuration and sanitization
|
10
11
|
class Logger
|
11
12
|
include Singleton
|
12
13
|
|
14
|
+
# @return [Imap::Backup::Logger] the singleton instance of this class
|
13
15
|
def self.logger
|
14
16
|
Logger.instance.logger
|
15
17
|
end
|
16
18
|
|
19
|
+
# @param options [Hash] command-line options
|
20
|
+
# @option options [Boolean] :quiet (false) if true, no output will be written
|
21
|
+
# @option options [Array<Boolean>] :verbose ([]) counts how many `--verbose`
|
22
|
+
# parameters were passed (and, potentially subtracts the number of
|
23
|
+
# `--no-verbose` parameters).
|
24
|
+
# If the result is 0, does normal info-level logging,
|
25
|
+
# If the result is 1, does debug logging,
|
26
|
+
# If the result is 2, does debug logging and client-server debug logging.
|
27
|
+
# This option is overridden by the `:verbose` option.
|
28
|
+
#
|
29
|
+
# @return [Hash] the options without the :quiet and :verbose keys
|
17
30
|
def self.setup_logging(options = {})
|
18
31
|
copy = options.clone
|
19
32
|
quiet = copy.delete(:quiet)
|
@@ -35,6 +48,9 @@ module Imap::Backup
|
|
35
48
|
copy
|
36
49
|
end
|
37
50
|
|
51
|
+
# Wraps a block, filtering output to standard error,
|
52
|
+
# hidng passwords and outputs the results to standard out
|
53
|
+
# @return [void]
|
38
54
|
def self.sanitize_stderr
|
39
55
|
sanitizer = Text::Sanitizer.new($stdout)
|
40
56
|
previous_stderr = $stderr
|
@@ -45,10 +61,12 @@ module Imap::Backup
|
|
45
61
|
$stderr = previous_stderr
|
46
62
|
end
|
47
63
|
|
64
|
+
# @private
|
48
65
|
def self.count(verbose)
|
49
66
|
verbose.reduce(1) { |acc, v| acc + (v ? 1 : -1) }
|
50
67
|
end
|
51
68
|
|
69
|
+
# @return [Logger] the configured Logger
|
52
70
|
attr_reader :logger
|
53
71
|
|
54
72
|
def initialize
|
data/lib/imap/backup/migrator.rb
CHANGED
@@ -3,6 +3,7 @@ require "imap/backup/logger"
|
|
3
3
|
module Imap; end
|
4
4
|
|
5
5
|
module Imap::Backup
|
6
|
+
# Copies a folder of backed-up emails to an online folder
|
6
7
|
class Migrator
|
7
8
|
def initialize(serializer, folder, reset: false)
|
8
9
|
@folder = folder
|
@@ -10,6 +11,8 @@ module Imap::Backup
|
|
10
11
|
@serializer = serializer
|
11
12
|
end
|
12
13
|
|
14
|
+
# Runs the migration
|
15
|
+
# @return [void]
|
13
16
|
def run
|
14
17
|
count = serializer.uids.count
|
15
18
|
folder.create
|
@@ -5,6 +5,7 @@ module Imap; end
|
|
5
5
|
module Imap::Backup
|
6
6
|
class Mirror; end
|
7
7
|
|
8
|
+
# Keeps track of the mapping between source and destination UIDs
|
8
9
|
class Mirror::Map
|
9
10
|
def initialize(pathname:, destination:)
|
10
11
|
@pathname = pathname
|
@@ -16,6 +17,8 @@ module Imap::Backup
|
|
16
17
|
@map = nil
|
17
18
|
end
|
18
19
|
|
20
|
+
# @return [Boolean] whether the supplied values match the existing
|
21
|
+
# UID validity values
|
19
22
|
def check_uid_validities(source:, destination:)
|
20
23
|
store
|
21
24
|
return false if source != source_uid_validity
|
@@ -24,6 +27,8 @@ module Imap::Backup
|
|
24
27
|
true
|
25
28
|
end
|
26
29
|
|
30
|
+
# Sets, or resets to an empty state
|
31
|
+
# @return [void]
|
27
32
|
def reset(source_uid_validity:, destination_uid_validity:)
|
28
33
|
destination_store["source_uid_validity"] = source_uid_validity
|
29
34
|
@source_uid_validity = nil
|
@@ -33,6 +38,11 @@ module Imap::Backup
|
|
33
38
|
@map = nil
|
34
39
|
end
|
35
40
|
|
41
|
+
# @param destination_uid [Integer] a message UID from the destination server
|
42
|
+
#
|
43
|
+
# @raise [RuntimeError] if the UID validity is not set
|
44
|
+
# @return [Integer, nil] the source UID that is equivalent to the given destination UID
|
45
|
+
# or nil if it is not found
|
36
46
|
def source_uid(destination_uid)
|
37
47
|
if destination_store == {}
|
38
48
|
raise "Assign UID validities with #reset before calling #source_uid"
|
@@ -41,6 +51,11 @@ module Imap::Backup
|
|
41
51
|
map.key(destination_uid)
|
42
52
|
end
|
43
53
|
|
54
|
+
# @param source_uid [Integer] a message UID from the source server
|
55
|
+
#
|
56
|
+
# @raise [RuntimeError] if the UID validity is not set
|
57
|
+
# @return [Integer, nil] the destination UID that is equivalent to the given source UID
|
58
|
+
# or nil if it is not found
|
44
59
|
def destination_uid(source_uid)
|
45
60
|
if destination_store == {}
|
46
61
|
raise "Assign UID validities with #reset before calling #destination_uid"
|
@@ -49,12 +64,18 @@ module Imap::Backup
|
|
49
64
|
map[source_uid]
|
50
65
|
end
|
51
66
|
|
67
|
+
# Creates a mapping between message UIDs on the source
|
68
|
+
# and destination servers
|
69
|
+
# @raise [RuntimeError] if the UID validity is not set
|
70
|
+
# @return [void]
|
52
71
|
def map_uids(source:, destination:)
|
53
72
|
raise "Assign UID validities with #reset before calling #map_uids" if destination_store == {}
|
54
73
|
|
55
74
|
map[source] = destination
|
56
75
|
end
|
57
76
|
|
77
|
+
# Saves the map to disk as JSON
|
78
|
+
# @return [void]
|
58
79
|
def save
|
59
80
|
File.write(pathname, store.to_json)
|
60
81
|
end
|
data/lib/imap/backup/mirror.rb
CHANGED
@@ -3,12 +3,20 @@ require "imap/backup/mirror/map"
|
|
3
3
|
module Imap; end
|
4
4
|
|
5
5
|
module Imap::Backup
|
6
|
+
# Synchronises a folder between a source and destination
|
6
7
|
class Mirror
|
7
8
|
def initialize(serializer, folder)
|
8
9
|
@serializer = serializer
|
9
10
|
@folder = folder
|
10
11
|
end
|
11
12
|
|
13
|
+
# If necessary, reates the destination folder,
|
14
|
+
# then deletes any messages in the destination folder
|
15
|
+
# that are not in the local store,
|
16
|
+
# sets existing messages' flas
|
17
|
+
# then appends any missing messages
|
18
|
+
# and saves the mapping file
|
19
|
+
# @return [void]
|
12
20
|
def run
|
13
21
|
ensure_destination_folder
|
14
22
|
delete_destination_only_emails
|
data/lib/imap/backup/naming.rb
CHANGED
@@ -1,11 +1,17 @@
|
|
1
1
|
module Imap; end
|
2
2
|
|
3
3
|
module Imap::Backup
|
4
|
+
# Maps between server and file system folder names
|
5
|
+
# `/` is treated as an acceptable character
|
4
6
|
class Naming
|
7
|
+
# The characters that cannot be used in file names
|
5
8
|
INVALID_FILENAME_CHARACTERS = ":%;".freeze
|
9
|
+
# A regular expression that captures each disallowed character
|
6
10
|
INVALID_FILENAME_CHARACTER_MATCH = /([#{INVALID_FILENAME_CHARACTERS}])/.freeze
|
7
11
|
|
8
|
-
#
|
12
|
+
# @param name [String] a folder name
|
13
|
+
# @return [String] the supplied string iwth disallowed characters replaced
|
14
|
+
# by their hexadecimal representation
|
9
15
|
def self.to_local_path(name)
|
10
16
|
name.gsub(INVALID_FILENAME_CHARACTER_MATCH) do |character|
|
11
17
|
hex =
|
@@ -16,6 +22,9 @@ module Imap::Backup
|
|
16
22
|
end
|
17
23
|
end
|
18
24
|
|
25
|
+
# @param name [String] a serialized folder name
|
26
|
+
# @return the supplied string with hexadecimal codes ('%xx') replaced with
|
27
|
+
# the characters they represent
|
19
28
|
def self.from_local_path(name)
|
20
29
|
name.gsub(/%(.*?);/) do
|
21
30
|
::Regexp.last_match(1).
|
@@ -3,7 +3,16 @@ require "imap/backup/logger"
|
|
3
3
|
module Imap; end
|
4
4
|
|
5
5
|
module Imap::Backup
|
6
|
+
# Provides a mechanism for retrying blocks of code which often throw errors
|
6
7
|
module RetryOnError
|
8
|
+
# Calls the supplied block,
|
9
|
+
# traps the given types of errors
|
10
|
+
# retrying up to a given number of times
|
11
|
+
# @param errors [Array<Exception>] the exceptions to trap
|
12
|
+
# @param limit [Integer] the maximum number of retries
|
13
|
+
# @param on_error [Proc] a block to call when an error occurs
|
14
|
+
# @raise any error ocurring more than `limit` times
|
15
|
+
# @return the result of any successful completion of the block
|
7
16
|
def retry_on_error(errors:, limit: 10, on_error: nil)
|
8
17
|
tries ||= 1
|
9
18
|
yield
|
@@ -7,6 +7,9 @@ module Imap::Backup
|
|
7
7
|
|
8
8
|
# Appends messages to the local store
|
9
9
|
class Serializer::Appender
|
10
|
+
# @param folder [String] the name of the folder
|
11
|
+
# @param imap [Serializer::Imap] the metadata serializer for the folder
|
12
|
+
# @param mbox [Serializer::Mbox] the folder's mailbox
|
10
13
|
def initialize(folder:, imap:, mbox:)
|
11
14
|
@folder = folder
|
12
15
|
@imap = imap
|
@@ -15,6 +18,12 @@ module Imap::Backup
|
|
15
18
|
|
16
19
|
# Adds a message to the metadata file and the mailbox.
|
17
20
|
# Wraps any errors with information about the message that caused them.
|
21
|
+
# @raise [RuntimeError] if the UID validity is not set
|
22
|
+
# or when an error occurs during serialization
|
23
|
+
# @param uid [Integer] the message's UID
|
24
|
+
# @param message [String] the on-disk version of the message
|
25
|
+
# @param flags [Array[Symbol]] the message's flags
|
26
|
+
# @return [void]
|
18
27
|
def append(uid:, message:, flags:)
|
19
28
|
raise "Can't add messages without uid_validity" if !imap.uid_validity
|
20
29
|
|
@@ -8,11 +8,13 @@ require "imap/backup/serializer/transaction"
|
|
8
8
|
module Imap; end
|
9
9
|
|
10
10
|
module Imap::Backup
|
11
|
+
# Wraps the Serializer, delaying metadata appends
|
11
12
|
class Serializer::DelayedMetadataSerializer
|
12
13
|
extend Forwardable
|
13
14
|
|
14
15
|
def_delegator :serializer, :uids
|
15
16
|
|
17
|
+
# @param serializer [Serializer] the serializer for a folder
|
16
18
|
def initialize(serializer:)
|
17
19
|
@serializer = serializer
|
18
20
|
@tsx = nil
|
@@ -20,7 +22,9 @@ module Imap::Backup
|
|
20
22
|
|
21
23
|
# Initializes the metadata and mailbox transactions, then calls the supplied block.
|
22
24
|
# Once the block has finished, commits changes to metadata
|
25
|
+
# @param block [block] the block that is wrapped by the transaction
|
23
26
|
#
|
27
|
+
# @raise any error ocurring during the commit phase
|
24
28
|
# @return [void]
|
25
29
|
def transaction(&block)
|
26
30
|
tsx.fail_in_transaction!(:transaction, message: "nested transactions are not supported")
|
@@ -12,6 +12,7 @@ module Imap::Backup
|
|
12
12
|
# The version number to store in the metadata file
|
13
13
|
CURRENT_VERSION = 3
|
14
14
|
|
15
|
+
# @return [String] The path of the imap metadata file, without the '.imap' extension
|
15
16
|
attr_reader :folder_path
|
16
17
|
|
17
18
|
# @param folder_path [String] The path of the imap metadata file, without the '.imap' extension
|
@@ -24,6 +25,10 @@ module Imap::Backup
|
|
24
25
|
@tsx = nil
|
25
26
|
end
|
26
27
|
|
28
|
+
# Opens a transaction
|
29
|
+
# @param block [block] the block that is wrapped by the transaction
|
30
|
+
# @raise any exception ocurring in the block
|
31
|
+
# @return [void]
|
27
32
|
def transaction(&block)
|
28
33
|
tsx.fail_in_transaction!(:transaction, message: "nested transactions are not supported")
|
29
34
|
|
@@ -41,6 +46,8 @@ module Imap::Backup
|
|
41
46
|
# rubocop:enable Lint/RescueException
|
42
47
|
end
|
43
48
|
|
49
|
+
# Discards stored changes to the data
|
50
|
+
# @return [void]
|
44
51
|
def rollback
|
45
52
|
tsx.fail_outside_transaction!(:rollback)
|
46
53
|
|
@@ -50,6 +57,7 @@ module Imap::Backup
|
|
50
57
|
tsx.clear
|
51
58
|
end
|
52
59
|
|
60
|
+
# @return [String] The full path name of the metadata file
|
53
61
|
def pathname
|
54
62
|
"#{folder_path}.imap"
|
55
63
|
end
|
@@ -66,6 +74,11 @@ module Imap::Backup
|
|
66
74
|
true
|
67
75
|
end
|
68
76
|
|
77
|
+
# Append message metadata
|
78
|
+
# @param uid [Integer] the message's UID
|
79
|
+
# @param length [Integer] the length of the message (as stored on disk)
|
80
|
+
# @param flags [Array[Symbol]] the message's flags
|
81
|
+
# @return [void]
|
69
82
|
def append(uid, length, flags: [])
|
70
83
|
offset =
|
71
84
|
if messages.empty?
|
@@ -82,10 +95,16 @@ module Imap::Backup
|
|
82
95
|
save
|
83
96
|
end
|
84
97
|
|
98
|
+
# Get message metadata
|
99
|
+
# @param uid [Integer] a message UID
|
100
|
+
# @return [Serializer::Message]
|
85
101
|
def get(uid)
|
86
102
|
messages.find { |m| m.uid == uid }
|
87
103
|
end
|
88
104
|
|
105
|
+
# Deletes the metadata file
|
106
|
+
# and discards stored attributes
|
107
|
+
# @return [void]
|
89
108
|
def delete
|
90
109
|
return if !exist?
|
91
110
|
|
@@ -96,6 +115,10 @@ module Imap::Backup
|
|
96
115
|
@version = nil
|
97
116
|
end
|
98
117
|
|
118
|
+
# Renames the metadata file, if it exists,
|
119
|
+
# otherwise, simply stores the new name
|
120
|
+
# @param new_path [String] the new path (without extension)
|
121
|
+
# @return [void]
|
99
122
|
def rename(new_path)
|
100
123
|
if exist?
|
101
124
|
old_pathname = pathname
|
@@ -106,28 +129,36 @@ module Imap::Backup
|
|
106
129
|
end
|
107
130
|
end
|
108
131
|
|
132
|
+
# @return [Integer] the UID validity for the folder
|
109
133
|
def uid_validity
|
110
134
|
ensure_loaded
|
111
135
|
@uid_validity
|
112
136
|
end
|
113
137
|
|
138
|
+
# Sets the folder's UID validity and saves the metadata file
|
139
|
+
# @param value [Integer] the new UID validity
|
140
|
+
# @return [void]
|
114
141
|
def uid_validity=(value)
|
115
142
|
ensure_loaded
|
116
143
|
@uid_validity = value
|
117
144
|
save
|
118
145
|
end
|
119
146
|
|
120
|
-
#
|
147
|
+
# @return [Array<Hash>]
|
121
148
|
def messages
|
122
149
|
ensure_loaded
|
123
150
|
@messages
|
124
151
|
end
|
125
152
|
|
126
|
-
#
|
153
|
+
# @return [Array<Integer>] The uids of all messages
|
127
154
|
def uids
|
128
155
|
messages.map(&:uid)
|
129
156
|
end
|
130
157
|
|
158
|
+
# Update a message's metadata, replacing its UID
|
159
|
+
# @param old [Integer] the existing message UID
|
160
|
+
# @param new [Integer] the new UID to apply to the message
|
161
|
+
# @return [void]
|
131
162
|
def update_uid(old, new)
|
132
163
|
index = messages.find_index { |m| m.uid == old }
|
133
164
|
return if index.nil?
|
@@ -136,11 +167,16 @@ module Imap::Backup
|
|
136
167
|
save
|
137
168
|
end
|
138
169
|
|
170
|
+
# @return [String] The format version for the metadata file
|
139
171
|
def version
|
140
172
|
ensure_loaded
|
141
173
|
@version
|
142
174
|
end
|
143
175
|
|
176
|
+
# Saves the file,
|
177
|
+
# except in a transaction when it does nothing
|
178
|
+
# @raise [RuntimeError] if UID validity has not been set
|
179
|
+
# @return [void]
|
144
180
|
def save
|
145
181
|
return if tsx.in_transaction?
|
146
182
|
|
@@ -3,14 +3,21 @@ require "imap/backup/serializer/transaction"
|
|
3
3
|
module Imap; end
|
4
4
|
|
5
5
|
module Imap::Backup
|
6
|
+
# Stores messages
|
6
7
|
class Serializer::Mbox
|
8
|
+
# @return [String] The path of the mailbox file, without the '.mbox' extension
|
7
9
|
attr_reader :folder_path
|
8
10
|
|
11
|
+
# @param folder_path [String] The path of the mailbox file, without the '.mbox' extension
|
9
12
|
def initialize(folder_path)
|
10
13
|
@folder_path = folder_path
|
11
14
|
@tsx = nil
|
12
15
|
end
|
13
16
|
|
17
|
+
# Starts a transaction
|
18
|
+
# @param block [block] the block that is wrapped by the transaction
|
19
|
+
# @raise re-raises errors which occur in the block
|
20
|
+
# @return [void]
|
14
21
|
def transaction(&block)
|
15
22
|
tsx.fail_in_transaction!(:transaction, message: "nested transactions are not supported")
|
16
23
|
|
@@ -26,6 +33,8 @@ module Imap::Backup
|
|
26
33
|
end
|
27
34
|
end
|
28
35
|
|
36
|
+
# Returns to the pre-transaction state
|
37
|
+
# @return [void]
|
29
38
|
def rollback
|
30
39
|
tsx.fail_outside_transaction!(:rollback)
|
31
40
|
|
@@ -36,12 +45,19 @@ module Imap::Backup
|
|
36
45
|
exist?
|
37
46
|
end
|
38
47
|
|
48
|
+
# Serializes a message
|
49
|
+
# @param message [String] the message text
|
50
|
+
# @return [void]
|
39
51
|
def append(message)
|
40
52
|
File.open(pathname, "ab") do |file|
|
41
53
|
file.write message
|
42
54
|
end
|
43
55
|
end
|
44
56
|
|
57
|
+
# Reads a message from disk
|
58
|
+
# @param offset [Integer] the start of the message inside the mailbox file
|
59
|
+
# @param length [Integer] the length of the message (as stored on disk)
|
60
|
+
# @return [String] the message
|
45
61
|
def read(offset, length)
|
46
62
|
File.open(pathname, "rb") do |f|
|
47
63
|
f.seek offset
|
@@ -49,6 +65,8 @@ module Imap::Backup
|
|
49
65
|
end
|
50
66
|
end
|
51
67
|
|
68
|
+
# Deletes the mailbox
|
69
|
+
# @return [void]
|
52
70
|
def delete
|
53
71
|
return if !exist?
|
54
72
|
|
@@ -59,16 +77,22 @@ module Imap::Backup
|
|
59
77
|
File.exist?(pathname)
|
60
78
|
end
|
61
79
|
|
80
|
+
# @return [Integer] The lsize of the disk file
|
62
81
|
def length
|
63
82
|
return nil if !exist?
|
64
83
|
|
65
84
|
File.stat(pathname).size
|
66
85
|
end
|
67
86
|
|
87
|
+
# @return [String] The full path name of the mailbox
|
68
88
|
def pathname
|
69
89
|
"#{folder_path}.mbox"
|
70
90
|
end
|
71
91
|
|
92
|
+
# Renames the mailbox, if it exists,
|
93
|
+
# otherwise, simply stores the new name
|
94
|
+
# @param new_path [String] the new path (without extension)
|
95
|
+
# @return [void]
|
72
96
|
def rename(new_path)
|
73
97
|
if exist?
|
74
98
|
old_pathname = pathname
|
@@ -79,6 +103,8 @@ module Imap::Backup
|
|
79
103
|
end
|
80
104
|
end
|
81
105
|
|
106
|
+
# Sets the mailbox file's updated time to the current time
|
107
|
+
# @return [void]
|
82
108
|
def touch
|
83
109
|
File.open(pathname, "a") {}
|
84
110
|
end
|
@@ -5,10 +5,15 @@ require "imap/backup/email/mboxrd/message"
|
|
5
5
|
module Imap; end
|
6
6
|
|
7
7
|
module Imap::Backup
|
8
|
+
# Represents a stored message
|
8
9
|
class Serializer::Message
|
10
|
+
# @return [Array[Symbol]] the message's flags
|
9
11
|
attr_accessor :flags
|
12
|
+
# @return [Integer] the length of the message (as stored on disk)
|
10
13
|
attr_reader :length
|
14
|
+
# @return [Integer] the start of the message inside the mailbox file
|
11
15
|
attr_reader :offset
|
16
|
+
# @return [Integer] the message's UID
|
12
17
|
attr_accessor :uid
|
13
18
|
|
14
19
|
extend Forwardable
|
@@ -16,6 +21,11 @@ module Imap::Backup
|
|
16
21
|
def_delegator :message, :supplied_body, :body
|
17
22
|
def_delegators :message, :imap_body, :date, :subject
|
18
23
|
|
24
|
+
# @param uid [Integer] the message's UID
|
25
|
+
# @param offset [Integer] the start of the message inside the mailbox file
|
26
|
+
# @param length [Integer] the length of the message (as stored on disk)
|
27
|
+
# @param mbox [Serializer::Mbox] the mailbox containing the message
|
28
|
+
# @param flags [Array[Symbol]] the message's flags
|
19
29
|
def initialize(uid:, offset:, length:, mbox:, flags: [])
|
20
30
|
@uid = uid
|
21
31
|
@offset = offset
|
@@ -24,6 +34,7 @@ module Imap::Backup
|
|
24
34
|
@flags = flags.map(&:to_sym)
|
25
35
|
end
|
26
36
|
|
37
|
+
# @return [Hash] the message metadata
|
27
38
|
def to_h
|
28
39
|
{
|
29
40
|
uid: uid,
|
@@ -33,6 +44,8 @@ module Imap::Backup
|
|
33
44
|
}
|
34
45
|
end
|
35
46
|
|
47
|
+
# Reads the message text and returns the original form
|
48
|
+
# @return [String] the message
|
36
49
|
def message
|
37
50
|
@message =
|
38
51
|
begin
|
@@ -1,13 +1,17 @@
|
|
1
1
|
module Imap; end
|
2
2
|
|
3
3
|
module Imap::Backup
|
4
|
+
# Enumerates over a list of stores messages
|
4
5
|
class Serializer::MessageEnumerator
|
5
|
-
|
6
|
-
|
6
|
+
# @param imap [Serializer::Imap] the metadata serializer for the folder
|
7
7
|
def initialize(imap:)
|
8
8
|
@imap = imap
|
9
9
|
end
|
10
10
|
|
11
|
+
# Enumerates over the messages
|
12
|
+
# @param uids [Array<Integer>] the message UIDs of the messages to iterate over
|
13
|
+
# @yieldparam message [Serializer::Message]
|
14
|
+
# @return [void]
|
11
15
|
def run(uids:)
|
12
16
|
uids.each do |uid_maybe_string|
|
13
17
|
uid = uid_maybe_string.to_i
|
@@ -18,5 +22,9 @@ module Imap::Backup
|
|
18
22
|
yield message
|
19
23
|
end
|
20
24
|
end
|
25
|
+
|
26
|
+
private
|
27
|
+
|
28
|
+
attr_reader :imap
|
21
29
|
end
|
22
30
|
end
|
@@ -3,12 +3,18 @@ require "imap/backup/file_mode"
|
|
3
3
|
module Imap; end
|
4
4
|
|
5
5
|
module Imap::Backup
|
6
|
+
# Ensures a file has the desired permissions
|
6
7
|
class Serializer::PermissionChecker
|
8
|
+
# @param filename [String] the file name
|
9
|
+
# @param limit [Integer] the maximum permission that should be set
|
7
10
|
def initialize(filename:, limit:)
|
8
11
|
@filename = filename
|
9
12
|
@limit = limit
|
10
13
|
end
|
11
14
|
|
15
|
+
# Runs the check
|
16
|
+
# @raise [RuntimeError] if the permissions are incorrect
|
17
|
+
# @return [void]
|
12
18
|
def run
|
13
19
|
actual = FileMode.new(filename: filename).mode
|
14
20
|
return nil if actual.nil?
|