imap-backup 14.4.4 → 14.5.0

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 (83) hide show
  1. checksums.yaml +4 -4
  2. data/README.md +67 -137
  3. data/docs/documentation.md +19 -0
  4. data/imap-backup.gemspec +1 -1
  5. data/lib/imap/backup/account/backup.rb +2 -0
  6. data/lib/imap/backup/account/backup_folders.rb +7 -1
  7. data/lib/imap/backup/account/client_factory.rb +1 -0
  8. data/lib/imap/backup/account/folder.rb +26 -0
  9. data/lib/imap/backup/account/folder_backup.rb +4 -1
  10. data/lib/imap/backup/account/folder_ensurer.rb +3 -0
  11. data/lib/imap/backup/account/local_only_folder_deleter.rb +3 -1
  12. data/lib/imap/backup/account/restore.rb +2 -0
  13. data/lib/imap/backup/account/serialized_folders.rb +31 -1
  14. data/lib/imap/backup/account.rb +34 -18
  15. data/lib/imap/backup/cli/backup.rb +3 -0
  16. data/lib/imap/backup/cli/folder_enumerator.rb +6 -0
  17. data/lib/imap/backup/cli/helpers.rb +13 -0
  18. data/lib/imap/backup/cli/local/check.rb +3 -0
  19. data/lib/imap/backup/cli/local.rb +14 -1
  20. data/lib/imap/backup/cli/remote.rb +7 -0
  21. data/lib/imap/backup/cli/restore.rb +4 -0
  22. data/lib/imap/backup/cli/setup.rb +3 -0
  23. data/lib/imap/backup/cli/single/backup.rb +3 -0
  24. data/lib/imap/backup/cli/single.rb +4 -0
  25. data/lib/imap/backup/cli/stats.rb +3 -0
  26. data/lib/imap/backup/cli/transfer.rb +8 -0
  27. data/lib/imap/backup/cli/utils.rb +7 -1
  28. data/lib/imap/backup/cli.rb +8 -0
  29. data/lib/imap/backup/client/apple_mail.rb +2 -0
  30. data/lib/imap/backup/client/automatic_login_wrapper.rb +9 -1
  31. data/lib/imap/backup/client/default.rb +15 -4
  32. data/lib/imap/backup/configuration.rb +13 -0
  33. data/lib/imap/backup/configuration_not_found.rb +1 -0
  34. data/lib/imap/backup/downloader.rb +4 -0
  35. data/lib/imap/backup/email/mboxrd/message.rb +14 -0
  36. data/lib/imap/backup/email/provider/apple_mail.rb +2 -0
  37. data/lib/imap/backup/email/provider/base.rb +3 -3
  38. data/lib/imap/backup/email/provider/fastmail.rb +2 -0
  39. data/lib/imap/backup/email/provider/gmail.rb +2 -0
  40. data/lib/imap/backup/email/provider/purelymail.rb +2 -0
  41. data/lib/imap/backup/email/provider/unknown.rb +2 -6
  42. data/lib/imap/backup/email/provider.rb +5 -0
  43. data/lib/imap/backup/file_mode.rb +2 -0
  44. data/lib/imap/backup/flag_refresher.rb +4 -0
  45. data/lib/imap/backup/local_only_message_deleter.rb +2 -0
  46. data/lib/imap/backup/logger.rb +18 -0
  47. data/lib/imap/backup/migrator.rb +3 -0
  48. data/lib/imap/backup/mirror/map.rb +21 -0
  49. data/lib/imap/backup/mirror.rb +8 -0
  50. data/lib/imap/backup/naming.rb +10 -1
  51. data/lib/imap/backup/retry_on_error.rb +9 -0
  52. data/lib/imap/backup/serializer/appender.rb +9 -0
  53. data/lib/imap/backup/serializer/delayed_metadata_serializer.rb +4 -0
  54. data/lib/imap/backup/serializer/folder_maker.rb +1 -0
  55. data/lib/imap/backup/serializer/imap.rb +38 -2
  56. data/lib/imap/backup/serializer/integrity_checker.rb +1 -0
  57. data/lib/imap/backup/serializer/mbox.rb +26 -0
  58. data/lib/imap/backup/serializer/message.rb +13 -0
  59. data/lib/imap/backup/serializer/message_enumerator.rb +10 -2
  60. data/lib/imap/backup/serializer/permission_checker.rb +6 -0
  61. data/lib/imap/backup/serializer/transaction.rb +18 -0
  62. data/lib/imap/backup/serializer/unused_name_finder.rb +4 -0
  63. data/lib/imap/backup/serializer/version2_migrator.rb +4 -0
  64. data/lib/imap/backup/serializer.rb +56 -2
  65. data/lib/imap/backup/setup/account/header.rb +6 -0
  66. data/lib/imap/backup/setup/account.rb +6 -0
  67. data/lib/imap/backup/setup/asker.rb +16 -0
  68. data/lib/imap/backup/setup/backup_path.rb +6 -0
  69. data/lib/imap/backup/setup/connection_tester.rb +5 -0
  70. data/lib/imap/backup/setup/email_changer.rb +6 -0
  71. data/lib/imap/backup/setup/folder_chooser.rb +4 -0
  72. data/lib/imap/backup/setup/global_options/download_strategy_chooser.rb +4 -0
  73. data/lib/imap/backup/setup/global_options.rb +4 -0
  74. data/lib/imap/backup/setup/helpers.rb +3 -0
  75. data/lib/imap/backup/setup.rb +5 -0
  76. data/lib/imap/backup/text/sanitizer.rb +9 -0
  77. data/lib/imap/backup/thunderbird/mailbox_exporter.rb +8 -1
  78. data/lib/imap/backup/uploader.rb +5 -0
  79. data/lib/imap/backup/version.rb +7 -2
  80. metadata +11 -13
  81. data/docs/api.md +0 -20
  82. data/docs/development.md +0 -110
  83. data/docs/migrate-server-keep-address.md +0 -47
@@ -9,15 +9,21 @@ require "imap/backup/serializer/permission_checker"
9
9
  module Imap; end
10
10
 
11
11
  module Imap::Backup
12
+ # Handles the application's configuration file
12
13
  class Configuration
14
+ # The default directory of the configuration file
13
15
  CONFIGURATION_DIRECTORY = File.expand_path("~/.imap-backup")
16
+ # The default download strategy key
14
17
  DEFAULT_STRATEGY = "delay_metadata".freeze
18
+ # The available download strategies
15
19
  DOWNLOAD_STRATEGIES = [
16
20
  {key: "direct", description: "write straight to disk"},
17
21
  {key: DEFAULT_STRATEGY, description: "delay writing metadata"}
18
22
  ].freeze
23
+ # The current file version
19
24
  VERSION = "2.2".freeze
20
25
 
26
+ # @return [String] the default configuration file path
21
27
  def self.default_pathname
22
28
  File.join(CONFIGURATION_DIRECTORY, "config.json")
23
29
  end
@@ -33,10 +39,13 @@ module Imap::Backup
33
39
  @download_strategy_modified = false
34
40
  end
35
41
 
42
+ # @return [String] the directory containing the configuration file
36
43
  def path
37
44
  File.dirname(pathname)
38
45
  end
39
46
 
47
+ # Saves the configuration file in JSON format
48
+ # @return [void]
40
49
  def save
41
50
  ensure_loaded!
42
51
  FileUtils.mkdir_p(path) if !File.directory?(path)
@@ -53,6 +62,7 @@ module Imap::Backup
53
62
  @data = nil
54
63
  end
55
64
 
65
+ # @return [Array<Account>] the configured accounts
56
66
  def accounts
57
67
  @accounts ||= begin
58
68
  ensure_loaded!
@@ -63,12 +73,15 @@ module Imap::Backup
63
73
  end
64
74
  end
65
75
 
76
+ # @return [String] the cofigured download strategy
66
77
  def download_strategy
67
78
  ensure_loaded!
68
79
 
69
80
  @download_strategy
70
81
  end
71
82
 
83
+ # @param value [String] the new strategy
84
+ # @return [void]
72
85
  def download_strategy=(value)
73
86
  raise "Unknown strategy '#{value}'" if !DOWNLOAD_STRATEGIES.find { |s| s[:key] == value }
74
87
 
@@ -1,5 +1,6 @@
1
1
  module Imap; end
2
2
 
3
3
  module Imap::Backup
4
+ # Thrown when no configuration file is found
4
5
  class ConfigurationNotFound < StandardError; end
5
6
  end
@@ -3,7 +3,9 @@ require "net/imap"
3
3
  module Imap; end
4
4
 
5
5
  module Imap::Backup
6
+ # Downloads as yet undownloaded emails from an account's server
6
7
  class Downloader
8
+ # @private
7
9
  class MultiFetchFailedError < StandardError; end
8
10
 
9
11
  def initialize(folder, serializer, multi_fetch_size: 1, reset_seen_flags_after_fetch: false)
@@ -14,6 +16,8 @@ module Imap::Backup
14
16
  @uids = nil
15
17
  end
16
18
 
19
+ # Runs the downloader
20
+ # @return [void]
17
21
  def run
18
22
  info("#{uids.count} new messages") if uids.any?
19
23
 
@@ -6,7 +6,13 @@ module Imap::Backup
6
6
  module Email; end
7
7
 
8
8
  module Email::Mboxrd
9
+ # Handles serialization and deserialization of messages
9
10
  class Message
11
+ # @param serialized [String] an email message
12
+ #
13
+ # @return [String] The message without the initial 'From ' line
14
+ # and with one level of '>' quoting removed from other lines
15
+ # that start with 'From'
10
16
  def self.clean_serialized(serialized)
11
17
  cleaned = serialized.gsub(/^>(>*From)/, "\\1")
12
18
  # Serialized messages in this format *should* start with a line
@@ -19,32 +25,40 @@ module Imap::Backup
19
25
  cleaned
20
26
  end
21
27
 
28
+ # @param serialized [String] the on-disk version of the message
29
+ #
30
+ # @return [Message] the original message
22
31
  def self.from_serialized(serialized)
23
32
  new(clean_serialized(serialized))
24
33
  end
25
34
 
35
+ # @return [String] the original message body
26
36
  attr_reader :supplied_body
27
37
 
28
38
  def initialize(supplied_body)
29
39
  @supplied_body = supplied_body.clone
30
40
  end
31
41
 
42
+ # @return [String] the message with an initial 'From ADDRESS' line
32
43
  def to_serialized
33
44
  from_line = "From #{from}\n"
34
45
  body = mboxrd_body.dup.force_encoding(Encoding::UTF_8)
35
46
  from_line + body
36
47
  end
37
48
 
49
+ # @return [Date, nil] the date of the message
38
50
  def date
39
51
  parsed.date
40
52
  rescue StandardError
41
53
  nil
42
54
  end
43
55
 
56
+ # @return [String] the message's subject line
44
57
  def subject
45
58
  parsed.subject
46
59
  end
47
60
 
61
+ # @return [String] the original message ready for transmission to an IMAP server
48
62
  def imap_body
49
63
  supplied_body.gsub(/(?<!\r)\n/, "\r\n")
50
64
  end
@@ -3,7 +3,9 @@ require "imap/backup/email/provider/base"
3
3
  module Imap; end
4
4
 
5
5
  module Imap::Backup
6
+ # Provides overrides for Apple mail accounts
6
7
  class Email::Provider::AppleMail < Email::Provider::Base
8
+ # @return [String] the Apple Mail IMAP server host name
7
9
  def host
8
10
  "imap.mail.me.com"
9
11
  end
@@ -4,11 +4,11 @@ module Imap::Backup
4
4
  module Email; end
5
5
  class Email::Provider; end
6
6
 
7
+ # Supplies defaults for email provider behaviour
7
8
  class Email::Provider::Base
9
+ # @return [Hash] defaults for the Net::IMAP connection
8
10
  def options
9
- # rubocop:disable Naming/VariableNumber
10
- {port: 993, ssl: {ssl_version: :TLSv1_2}}
11
- # rubocop:enable Naming/VariableNumber
11
+ {port: 993, ssl: {min_version: OpenSSL::SSL::TLS1_2_VERSION}}
12
12
  end
13
13
 
14
14
  def sets_seen_flags_on_fetch?
@@ -3,7 +3,9 @@ require "imap/backup/email/provider/base"
3
3
  module Imap; end
4
4
 
5
5
  module Imap::Backup
6
+ # Provides overrides for Fastmail accounts
6
7
  class Email::Provider::Fastmail < Email::Provider::Base
8
+ # @return [String] the Fastmail IMAP server host name
7
9
  def host
8
10
  "imap.fastmail.com"
9
11
  end
@@ -3,7 +3,9 @@ require "imap/backup/email/provider/base"
3
3
  module Imap; end
4
4
 
5
5
  module Imap::Backup
6
+ # Provides overrides for GMail accounts
6
7
  class Email::Provider::GMail < Email::Provider::Base
8
+ # @return [String] the GMail IMAP server host name
7
9
  def host
8
10
  "imap.gmail.com"
9
11
  end
@@ -3,7 +3,9 @@ require "imap/backup/email/provider/base"
3
3
  module Imap; end
4
4
 
5
5
  module Imap::Backup
6
+ # Provides overrides for Purelymail accounts
6
7
  class Email::Provider::Purelymail < Email::Provider::Base
8
+ # @return [String] The Purelymail IMAP server host name
7
9
  def host
8
10
  "mailserver.purelymail.com"
9
11
  end
@@ -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