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.
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?