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.
Files changed (82) hide show
  1. checksums.yaml +4 -4
  2. data/README.md +67 -137
  3. data/docs/documentation.md +19 -0
  4. data/lib/imap/backup/account/backup.rb +2 -0
  5. data/lib/imap/backup/account/backup_folders.rb +7 -1
  6. data/lib/imap/backup/account/client_factory.rb +1 -0
  7. data/lib/imap/backup/account/folder.rb +26 -0
  8. data/lib/imap/backup/account/folder_backup.rb +4 -1
  9. data/lib/imap/backup/account/folder_ensurer.rb +3 -0
  10. data/lib/imap/backup/account/local_only_folder_deleter.rb +2 -0
  11. data/lib/imap/backup/account/restore.rb +2 -0
  12. data/lib/imap/backup/account/serialized_folders.rb +5 -1
  13. data/lib/imap/backup/account.rb +15 -11
  14. data/lib/imap/backup/cli/backup.rb +3 -0
  15. data/lib/imap/backup/cli/folder_enumerator.rb +6 -0
  16. data/lib/imap/backup/cli/helpers.rb +13 -0
  17. data/lib/imap/backup/cli/local/check.rb +3 -0
  18. data/lib/imap/backup/cli/local.rb +13 -0
  19. data/lib/imap/backup/cli/remote.rb +7 -0
  20. data/lib/imap/backup/cli/restore.rb +4 -0
  21. data/lib/imap/backup/cli/setup.rb +3 -0
  22. data/lib/imap/backup/cli/single/backup.rb +3 -0
  23. data/lib/imap/backup/cli/single.rb +4 -0
  24. data/lib/imap/backup/cli/stats.rb +3 -0
  25. data/lib/imap/backup/cli/transfer.rb +8 -0
  26. data/lib/imap/backup/cli/utils.rb +6 -0
  27. data/lib/imap/backup/cli.rb +8 -0
  28. data/lib/imap/backup/client/apple_mail.rb +2 -0
  29. data/lib/imap/backup/client/automatic_login_wrapper.rb +9 -1
  30. data/lib/imap/backup/client/default.rb +15 -4
  31. data/lib/imap/backup/configuration.rb +13 -0
  32. data/lib/imap/backup/configuration_not_found.rb +1 -0
  33. data/lib/imap/backup/downloader.rb +4 -0
  34. data/lib/imap/backup/email/mboxrd/message.rb +14 -0
  35. data/lib/imap/backup/email/provider/apple_mail.rb +2 -0
  36. data/lib/imap/backup/email/provider/base.rb +2 -0
  37. data/lib/imap/backup/email/provider/fastmail.rb +2 -0
  38. data/lib/imap/backup/email/provider/gmail.rb +2 -0
  39. data/lib/imap/backup/email/provider/purelymail.rb +2 -0
  40. data/lib/imap/backup/email/provider/unknown.rb +2 -6
  41. data/lib/imap/backup/email/provider.rb +5 -0
  42. data/lib/imap/backup/file_mode.rb +2 -0
  43. data/lib/imap/backup/flag_refresher.rb +4 -0
  44. data/lib/imap/backup/local_only_message_deleter.rb +2 -0
  45. data/lib/imap/backup/logger.rb +18 -0
  46. data/lib/imap/backup/migrator.rb +3 -0
  47. data/lib/imap/backup/mirror/map.rb +21 -0
  48. data/lib/imap/backup/mirror.rb +8 -0
  49. data/lib/imap/backup/naming.rb +10 -1
  50. data/lib/imap/backup/retry_on_error.rb +9 -0
  51. data/lib/imap/backup/serializer/appender.rb +9 -0
  52. data/lib/imap/backup/serializer/delayed_metadata_serializer.rb +4 -0
  53. data/lib/imap/backup/serializer/folder_maker.rb +1 -0
  54. data/lib/imap/backup/serializer/imap.rb +38 -2
  55. data/lib/imap/backup/serializer/integrity_checker.rb +1 -0
  56. data/lib/imap/backup/serializer/mbox.rb +26 -0
  57. data/lib/imap/backup/serializer/message.rb +13 -0
  58. data/lib/imap/backup/serializer/message_enumerator.rb +10 -2
  59. data/lib/imap/backup/serializer/permission_checker.rb +6 -0
  60. data/lib/imap/backup/serializer/transaction.rb +18 -0
  61. data/lib/imap/backup/serializer/unused_name_finder.rb +4 -0
  62. data/lib/imap/backup/serializer/version2_migrator.rb +4 -0
  63. data/lib/imap/backup/serializer.rb +56 -2
  64. data/lib/imap/backup/setup/account/header.rb +6 -0
  65. data/lib/imap/backup/setup/account.rb +6 -0
  66. data/lib/imap/backup/setup/asker.rb +16 -0
  67. data/lib/imap/backup/setup/backup_path.rb +6 -0
  68. data/lib/imap/backup/setup/connection_tester.rb +5 -0
  69. data/lib/imap/backup/setup/email_changer.rb +6 -0
  70. data/lib/imap/backup/setup/folder_chooser.rb +4 -0
  71. data/lib/imap/backup/setup/global_options/download_strategy_chooser.rb +4 -0
  72. data/lib/imap/backup/setup/global_options.rb +4 -0
  73. data/lib/imap/backup/setup/helpers.rb +3 -0
  74. data/lib/imap/backup/setup.rb +5 -0
  75. data/lib/imap/backup/text/sanitizer.rb +9 -0
  76. data/lib/imap/backup/thunderbird/mailbox_exporter.rb +7 -0
  77. data/lib/imap/backup/uploader.rb +5 -0
  78. data/lib/imap/backup/version.rb +6 -1
  79. metadata +3 -5
  80. data/docs/api.md +0 -20
  81. data/docs/development.md +0 -110
  82. 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?
@@ -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
@@ -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
@@ -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
@@ -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
- # `*_path` functions treat `/` as an acceptable character
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")
@@ -18,6 +18,7 @@ module Imap::Backup
18
18
 
19
19
  # Creates the directory and any missing parent directories,
20
20
  # ensuring the desired permissions.
21
+ # @return [void]
21
22
  def run
22
23
  parts = path.split("/")
23
24
  return if parts.empty?
@@ -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
- # Make private
147
+ # @return [Array<Hash>]
121
148
  def messages
122
149
  ensure_loaded
123
150
  @messages
124
151
  end
125
152
 
126
- # Deprecated
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,6 +3,7 @@ module Imap; end
3
3
  module Imap::Backup
4
4
  class Serializer; end
5
5
 
6
+ # An error indicating the folder is not serialized correctly
6
7
  class Serializer::FolderIntegrityError < StandardError; end
7
8
 
8
9
  # Checks that both the mailbox and its associated metadata file match
@@ -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
- attr_reader :imap
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?