imap-backup 16.4.0 → 16.4.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (40) hide show
  1. checksums.yaml +4 -4
  2. data/README.md +3 -92
  3. data/docs/development.md +8 -0
  4. data/docs/testing.md +93 -0
  5. data/lib/imap/backup/account/backup.rb +2 -0
  6. data/lib/imap/backup/account/backup_folders.rb +2 -0
  7. data/lib/imap/backup/account/client_factory.rb +1 -0
  8. data/lib/imap/backup/account/folder.rb +2 -0
  9. data/lib/imap/backup/account/folder_backup.rb +3 -0
  10. data/lib/imap/backup/account/folder_ensurer.rb +1 -0
  11. data/lib/imap/backup/account/folder_mapper.rb +6 -0
  12. data/lib/imap/backup/account/local_only_folder_deleter.rb +1 -0
  13. data/lib/imap/backup/account/locker.rb +6 -1
  14. data/lib/imap/backup/account/restore.rb +3 -0
  15. data/lib/imap/backup/account/serialized_folders.rb +1 -0
  16. data/lib/imap/backup/account.rb +34 -16
  17. data/lib/imap/backup/cli/backup.rb +4 -0
  18. data/lib/imap/backup/cli/local/check.rb +5 -0
  19. data/lib/imap/backup/cli/options.rb +1 -0
  20. data/lib/imap/backup/cli/restore.rb +10 -3
  21. data/lib/imap/backup/cli/setup.rb +3 -0
  22. data/lib/imap/backup/cli/single/backup.rb +20 -0
  23. data/lib/imap/backup/cli/stats.rb +10 -0
  24. data/lib/imap/backup/cli/transfer.rb +3 -0
  25. data/lib/imap/backup/client/automatic_login_wrapper.rb +1 -0
  26. data/lib/imap/backup/client/default.rb +1 -0
  27. data/lib/imap/backup/configuration.rb +1 -0
  28. data/lib/imap/backup/downloader.rb +4 -0
  29. data/lib/imap/backup/email/mboxrd/message.rb +1 -0
  30. data/lib/imap/backup/file_mode.rb +1 -0
  31. data/lib/imap/backup/flag_refresher.rb +2 -0
  32. data/lib/imap/backup/local_only_message_deleter.rb +2 -0
  33. data/lib/imap/backup/lockfile.rb +19 -3
  34. data/lib/imap/backup/logger.rb +1 -1
  35. data/lib/imap/backup/migrator.rb +3 -0
  36. data/lib/imap/backup/mirror/map.rb +2 -1
  37. data/lib/imap/backup/mirror.rb +3 -0
  38. data/lib/imap/backup/serializer/message.rb +1 -1
  39. data/lib/imap/backup/version.rb +1 -1
  40. metadata +4 -2
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 9ec15a683d70eadabab4271afcd2231c723344e79dde55fadeea091056201fc8
4
- data.tar.gz: 946f1074220df2af38c92a5ff7f5dda8bcdcef3dc20f48d9f2c8206e29a8cd6e
3
+ metadata.gz: f3ffe8d1bd32587d59e12a514b005d68175244e2cb73a9d138dd3a5d273c785f
4
+ data.tar.gz: 62d6364641799f1ce20466affc34052f8614a2a2d5633f53a05b4cac31a4e0f3
5
5
  SHA512:
6
- metadata.gz: 7c33112eb20f168c453f0cc52e86497c7a44459afa718ac12ff1ca71cf990d9566514598aae25fc22117fb30478db649bb76a1aff697d40dabb788cc54fa1508
7
- data.tar.gz: ea2dd0c673c603c71708ba550cbdf98c218c51b8ff076ba031d40a7ae33f80ff830ada659b7cc300e8511cc3da207665e6f2114c231e985a0b716e2844f7fe7a
6
+ metadata.gz: f4fdd4254948ef5dd7fba17e3683aa13bf84a19a77ffd935bad3f830355d4bf0d14ff48c731be2542ce56332ed8b4903b1b7971a16e7e947f4406ba4473b4a44
7
+ data.tar.gz: 4ca3ca9bb9eda64a9b8b9a6cc0889e5815f446e2a798f762f1d55e228928e99988c186bdaad4dfd7d033cd21667de7d906ea3cbd1f11d947dc77d68f4bbd1090
data/README.md CHANGED
@@ -25,102 +25,13 @@ The {file:CHANGELOG.md CHANGELOG} has a history of the changes to the program.
25
25
  * Restartable - calculate start point based on already downloaded messages
26
26
  * Standalone - do not rely on an email client or MTA
27
27
 
28
- # Repository
28
+ # Development
29
29
 
30
- After cloning the repo, run the following command to get
31
- better `git blame` output:
32
-
33
- ```sh
34
- git config --local blame.ignoreRevsFile .git-blame-ignore-revs
35
- ```
30
+ See the [development documentation](/docs/development.md).
36
31
 
37
32
  # Testing
38
33
 
39
- ## Feature Specs
40
-
41
- Specs under `specs/features` are integration specs.
42
- Some of these specs run against two local IMAP servers
43
- controlled by Podman (or Docker) Compose.
44
-
45
- Start them before running the test suite
46
-
47
- ```sh
48
- $ podman-compose -f dev/compose.yml up -d
49
- ```
50
-
51
- or, with Docker
52
-
53
- ```sh
54
- $ docker-compose -f dev/compose.yml up -d
55
- ```
56
-
57
- Then, run all specs
58
-
59
- ```sh
60
- $ rspec
61
- ```
62
-
63
- To exclude container-based tests
64
-
65
- ```sh
66
- $ rspec --tag ~container
67
- ```
68
-
69
- To run **just** the feature specs
70
-
71
- ```sh
72
- rspec spec/features/**/*_spec.rb
73
- ```
74
-
75
- ## Full Test Run
76
-
77
- The full test run includes RSpec specs **and** Rubocop checks
78
-
79
- ```sh
80
- rake
81
- ```
82
-
83
- # Test Debugging
84
-
85
- The feature specs are run 'out of process' via the Aruba gem.
86
- In order to see debugging output from the process,
87
- use `last_command_started.output`.
88
-
89
- # Older Rubies
90
-
91
- A Containerfile is available to allow testing with all available Ruby versions,
92
- see the README in the `dev` directory.
93
-
94
- # Performance Specs
95
-
96
- ```sh
97
- PERFORMANCE=1 rspec --order=defined
98
- ```
99
-
100
- Beware: the performance spec (just backup for now) takes a very
101
- long time to run, approximately 24 hours!
102
-
103
- # Access Docker imap server
104
-
105
- ```ruby
106
- require "net/imap"
107
- require_relative "spec/features/support/30_email_server_helpers"
108
-
109
- include EmailServerHelpers
110
-
111
- test_connection = test_server_connection_parameters
112
-
113
- test_imap = Net::IMAP.new(test_connection[:server], test_connection[:connection_options])
114
- test_imap.login(test_connection[:username], test_connection[:password])
115
-
116
- message = "From: #{test_connection[:username]}\nSubject: Some Subject\n\nHello!\n"
117
- response = test_imap.append("INBOX", message, nil, nil)
118
-
119
- test_imap.examine("INBOX")
120
- uids = test_imap.uid_search(["ALL"]).sort
121
-
122
- fetch_data_items = test_imap.uid_fetch(uids, ["BODY[]"])
123
- ```
34
+ See the [testing documentation](/docs/testing.md).
124
35
 
125
36
  # Contributing
126
37
 
@@ -0,0 +1,8 @@
1
+ # Repository
2
+
3
+ After cloning the repo, run the following command to get
4
+ better `git blame` output:
5
+
6
+ ```sh
7
+ git config --local blame.ignoreRevsFile .git-blame-ignore-revs
8
+ ```
data/docs/testing.md ADDED
@@ -0,0 +1,93 @@
1
+ # Setup
2
+
3
+ Specs under `spec/features` are integration specs.
4
+ Some of these specs run against two local IMAP servers
5
+ controlled by Podman (or Docker) Compose.
6
+
7
+ Start them before running the test suite
8
+
9
+ ```sh
10
+ $ podman-compose -f dev/compose.yml up -d
11
+ ```
12
+
13
+ or, with Docker
14
+
15
+ ```sh
16
+ $ docker-compose -f dev/compose.yml up -d
17
+ ```
18
+
19
+ # Invocations
20
+
21
+ Run all specs
22
+
23
+ ```sh
24
+ $ rake spec
25
+ ```
26
+
27
+ Run **just** the unit specs
28
+
29
+ ```sh
30
+ $ rake spec_unit
31
+ ```
32
+
33
+ Run **just** the feature specs
34
+
35
+ ```sh
36
+ $ rake spec_feature
37
+ ```
38
+
39
+ To exclude the slow container-based tests
40
+
41
+ ```sh
42
+ $ rake spec_non_container
43
+ ```
44
+
45
+ ## Full Test Run
46
+
47
+ The full test run includes RSpec specs **and** Rubocop checks
48
+
49
+ ```sh
50
+ rake test
51
+ ```
52
+
53
+ # Debugging
54
+
55
+ The feature specs are run 'out of process' via the Aruba gem.
56
+ In order to see debugging output from the process,
57
+ use `last_command_started.output`.
58
+
59
+ # Older Rubies
60
+
61
+ A Containerfile is available to allow testing with all available Ruby versions,
62
+ see the README in the `dev` directory.
63
+
64
+ # Performance Specs
65
+
66
+ ```sh
67
+ PERFORMANCE=1 rspec --order=defined
68
+ ```
69
+
70
+ Beware: the performance spec (just backup for now) takes a very
71
+ long time to run, approximately 24 hours!
72
+
73
+ # Access Docker imap server
74
+
75
+ ```ruby
76
+ require "net/imap"
77
+ require_relative "spec/features/support/30_email_server_helpers"
78
+
79
+ include EmailServerHelpers
80
+
81
+ test_connection = test_server_connection_parameters
82
+
83
+ test_imap = Net::IMAP.new(test_connection[:server], test_connection[:connection_options])
84
+ test_imap.login(test_connection[:username], test_connection[:password])
85
+
86
+ message = "From: #{test_connection[:username]}\nSubject: Some Subject\n\nHello!\n"
87
+ response = test_imap.append("INBOX", message, nil, nil)
88
+
89
+ test_imap.examine("INBOX")
90
+ uids = test_imap.uid_search(["ALL"]).sort
91
+
92
+ fetch_data_items = test_imap.uid_fetch(uids, ["BODY[]"])
93
+ ```
@@ -11,6 +11,8 @@ module Imap::Backup
11
11
 
12
12
  # Carries out the backup of the configured folders of the account
13
13
  class Account::Backup
14
+ # @param account [Account] the account to back up
15
+ # @param refresh [Boolean] true to refresh folder metadata even if mirror is disabled
14
16
  def initialize(account:, refresh: false)
15
17
  @account = account
16
18
  @refresh = refresh
@@ -9,6 +9,8 @@ module Imap::Backup
9
9
  class Account::BackupFolders
10
10
  include Enumerable
11
11
 
12
+ # @param client [Client::AutomaticLoginWrapper] the IMAP client that lists folders
13
+ # @param account [Account] the account whose folders are being backed up
12
14
  def initialize(client:, account:)
13
15
  @client = client
14
16
  @account = account
@@ -10,6 +10,7 @@ module Imap::Backup
10
10
 
11
11
  # Returns an IMAP client set up for the supplied account
12
12
  class Account::ClientFactory
13
+ # @param account [Account] the account whose credentials configure the client
13
14
  def initialize(account:)
14
15
  @account = account
15
16
  end
@@ -23,6 +23,8 @@ module Imap::Backup
23
23
  # @return [String] the name of the folder
24
24
  attr_reader :name
25
25
 
26
+ # @param client [Client::Default] the IMAP client used to talk to the server
27
+ # @param name [String] the UTF-7 encoded folder name
26
28
  def initialize(client, name)
27
29
  @client = client
28
30
  @name = name
@@ -12,6 +12,9 @@ module Imap::Backup
12
12
 
13
13
  # Implements backup for a single folder
14
14
  class Account::FolderBackup
15
+ # @param account [Account] the account that owns the folder
16
+ # @param folder [Account::Folder] the online folder being backed up
17
+ # @param refresh [Boolean] true to refresh metadata regardless of mirror mode
15
18
  def initialize(account:, folder:, refresh: false)
16
19
  @account = account
17
20
  @folder = folder
@@ -8,6 +8,7 @@ module Imap::Backup
8
8
 
9
9
  # Handles creation of directories for backup storage
10
10
  class Account::FolderEnsurer
11
+ # @param account [Account] the account to check
11
12
  def initialize(account:)
12
13
  @account = account
13
14
  end
@@ -11,6 +11,12 @@ module Imap::Backup
11
11
 
12
12
  # Implements a folder enumerator for backed-up accounts
13
13
  class Account::FolderMapper
14
+ # @param account [Account] the account whose local folders are being iterated
15
+ # @param destination [Account] the destination account
16
+ # @param destination_delimiter [String] the delimiter to use for destination folder names
17
+ # @param destination_prefix [String] a prefix applied to destination folder names
18
+ # @param source_delimiter [String] the delimiter used in the source account
19
+ # @param source_prefix [String] a prefix applied to source folder names
14
20
  def initialize(
15
21
  account:,
16
22
  destination:,
@@ -10,6 +10,7 @@ module Imap::Backup
10
10
  # This is used in mirror mode, where local copies are only kept as long as they
11
11
  # exist on the server.
12
12
  class Account::LocalOnlyFolderDeleter
13
+ # @param account [Account] the account whose serialized folders are being pruned
13
14
  def initialize(account:)
14
15
  @account = account
15
16
  end
@@ -9,6 +9,7 @@ module Imap::Backup
9
9
  class Account::Locker
10
10
  attr_reader :account
11
11
 
12
+ # @param account [Account] the account whose backup must be locked
12
13
  def initialize(account:)
13
14
  @account = account
14
15
  end
@@ -27,7 +28,11 @@ module Imap::Backup
27
28
  Account::FolderEnsurer.new(account: account).run
28
29
  end
29
30
 
30
- lockfile.with_lock do
31
+ begin
32
+ lockfile.with_lock do
33
+ block.call
34
+ end
35
+ rescue Lockfile::ProcessStartTimeUnavailableError
31
36
  block.call
32
37
  end
33
38
  end
@@ -9,6 +9,9 @@ module Imap::Backup
9
9
 
10
10
  # Restores all backed up folders to the server
11
11
  class Account::Restore
12
+ # @param account [Account] the account whose backups will be restored
13
+ # @param delimiter [String] the destination folder delimiter
14
+ # @param prefix [String] a prefix applied to restored folder names
12
15
  def initialize(account:, delimiter: "/", prefix: "")
13
16
  @account = account
14
17
  @destination_delimiter = delimiter
@@ -13,6 +13,7 @@ module Imap::Backup
13
13
  class Account::SerializedFolders
14
14
  include Enumerable
15
15
 
16
+ # @param account [Account] the account whose serialized folders are iterated
16
17
  def initialize(account:)
17
18
  @account = account
18
19
  end
@@ -65,6 +65,25 @@ module Imap::Backup
65
65
  # @return [String] one of "active" (the default), "archived", or "offline"
66
66
  attr_reader :status
67
67
 
68
+ # @param options [Hash] Account attributes
69
+ # @option opts [String] :username the username of the account (usually the same as the email
70
+ # address)
71
+ # @option opts [String] :password the password of the account
72
+ # @option opts [String] :local_path the path where backups will be saved
73
+ # @option opts [Array<String>] :folders (nil) the list of folders that have been configured for
74
+ # the Account
75
+ # @option opts [Boolean] :folder_blacklist (false) whether the folders attribute is a blacklist
76
+ # @option opts [Boolean] :mirror_mode (false) whether to run in mirror mode
77
+ # @option opts [String] :server the address of the IMAP server
78
+ # @option opts [Hash] :connection_options (nil) additional connection options for the IMAP
79
+ # server
80
+ # @option opts [String] :download_strategy (nil) the name of the download strategy to adopt
81
+ # during backups
82
+ # @option opts [Integer] :multi_fetch_size (nil) the number of emails to fetch from the IMAP
83
+ # server at a time
84
+ # @option opts [Boolean] :reset_seen_flags_after_fetch (false) whether to reset seen flags after
85
+ # fetching messages
86
+ # @option opts [String] :status ("active") the status of the account
68
87
  def initialize(options)
69
88
  check_options!(options)
70
89
  @username = options[:username]
@@ -131,19 +150,19 @@ module Imap::Backup
131
150
  # @return [Hash] all Account data for serialization
132
151
  def to_h
133
152
  h = {
134
- username: @username,
135
- password: @password,
153
+ username: username,
154
+ password: password,
136
155
  status: status
137
156
  }
138
- h[:local_path] = @local_path if @local_path
139
- h[:folders] = @folders if @folders
140
- h[:folder_blacklist] = true if @folder_blacklist
141
- h[:mirror_mode] = true if @mirror_mode
142
- h[:server] = @server if @server
143
- h[:connection_options] = @connection_options if connection_options
157
+ h[:local_path] = local_path if local_path
158
+ h[:folders] = folders if folders
159
+ h[:folder_blacklist] = true if folder_blacklist
160
+ h[:mirror_mode] = true if mirror_mode
161
+ h[:server] = server if server
162
+ h[:connection_options] = connection_options if connection_options
144
163
  h[:multi_fetch_size] = multi_fetch_size
145
- if @reset_seen_flags_after_fetch
146
- h[:reset_seen_flags_after_fetch] = @reset_seen_flags_after_fetch
164
+ if reset_seen_flags_after_fetch
165
+ h[:reset_seen_flags_after_fetch] = reset_seen_flags_after_fetch
147
166
  end
148
167
  h
149
168
  end
@@ -216,12 +235,11 @@ module Imap::Backup
216
235
  def connection_options=(value)
217
236
  # Ensure we've loaded the connection_options
218
237
  connection_options
219
- parsed =
220
- if value == ""
221
- nil
222
- else
223
- JSON.parse(value, symbolize_names: true)
224
- end
238
+ parsed = if value == ""
239
+ nil
240
+ else
241
+ JSON.parse(value, symbolize_names: true)
242
+ end
225
243
  update(:connection_options, parsed)
226
244
  end
227
245
 
@@ -15,6 +15,10 @@ module Imap::Backup
15
15
  include Thor::Actions
16
16
  include CLI::Helpers
17
17
 
18
+ # @param options [Hash] CLI options controlling output
19
+ # @option opts [String] :config (nil) the path to the configuration file
20
+ # @option opts [String] :erb_configuration (nil) the path to the ERB configuration file
21
+ # @option opts [Boolean] :refresh (false) whether to force refresh of folder metadata
18
22
  def initialize(options)
19
23
  super([])
20
24
  @options = options
@@ -14,6 +14,11 @@ module Imap::Backup
14
14
  class CLI::Local::Check
15
15
  include CLI::Helpers
16
16
 
17
+ # @param options [Hash] CLI options controlling output
18
+ # @option opts [String] :config (nil) the path to the configuration file
19
+ # @option opts [String] :erb_configuration (nil) the path to the ERB configuration file
20
+ # @option opts [Boolean] :delete_corrupt (false) whether to delete corrupt folders
21
+ # @option opts [String] :format ("text") the output format, either "text" or "json"
17
22
  def initialize(options)
18
23
  @options = options
19
24
  end
@@ -70,6 +70,7 @@ module Imap::Backup
70
70
  }
71
71
  ].freeze
72
72
 
73
+ # @param base [Thor] the command class receiving the options
73
74
  def initialize(base:)
74
75
  @base = base
75
76
  end
@@ -14,6 +14,13 @@ module Imap::Backup
14
14
  include Thor::Actions
15
15
  include CLI::Helpers
16
16
 
17
+ # @param email [String, nil] optional email address identifying the account to restore
18
+ # @param options [Hash] CLI options controlling output
19
+ # @option opts [String] :config (nil) the path to the configuration file
20
+ # @option opts [String] :erb_configuration (nil) the path to the ERB configuration file
21
+ # @option opts [Array<String>] :accounts (nil) the accounts to restore
22
+ # @option opts [String] :delimiter ("/") the destination folder delimiter
23
+ # @option opts [String] :prefix ("") a prefix applied to restored folder names
17
24
  def initialize(email = nil, options)
18
25
  super([])
19
26
  @email = email
@@ -30,9 +37,6 @@ module Imap::Backup
30
37
  when email && !options.key?(:accounts)
31
38
  account = account(config, email)
32
39
  restore(account, **restore_options)
33
- when !email && !options.key?(:accounts)
34
- Logger.logger.info "Calling restore without an EMAIL parameter is deprecated"
35
- config.accounts.each { |a| restore(a) }
36
40
  when email && options.key?(:accounts)
37
41
  raise "Missing EMAIL parameter"
38
42
  when !email && options.key?(:accounts)
@@ -41,6 +45,9 @@ module Imap::Backup
41
45
  "please pass a single EMAIL parameter"
42
46
  )
43
47
  requested_accounts(config).each { |a| restore(a) }
48
+ else
49
+ Logger.logger.info "Calling restore without an EMAIL parameter is deprecated"
50
+ config.accounts.each { |a| restore(a) }
44
51
  end
45
52
  end
46
53
  end
@@ -13,6 +13,9 @@ module Imap::Backup
13
13
  include Thor::Actions
14
14
  include CLI::Helpers
15
15
 
16
+ # @param options [Hash] CLI options controlling output
17
+ # @option opts [String] :config (nil) the path to the configuration file
18
+ # @option opts [String] :erb_configuration (nil) the path to the ERB configuration file
16
19
  def initialize(options)
17
20
  super([])
18
21
  @options = options
@@ -12,6 +12,26 @@ module Imap::Backup
12
12
 
13
13
  # Runs a backup without relying on existing configuration
14
14
  class CLI::Single::Backup
15
+ # @param options [Hash] CLI options controlling output
16
+ # @option opts [String] :config (nil) the path to the configuration file
17
+ # @option opts [String] :erb_configuration (nil) the path to the ERB configuration file
18
+ # @option opts [String] :email the email address identifying the account to backup
19
+ # @option opts [String] :password (nil) the password for the account
20
+ # @option opts [String] :password_environment_variable (nil) the name of an environment variable
21
+ # containing the password
22
+ # @option opts [String] :password_file (nil) the path to a file containing the password
23
+ # @option opts [String] :server the IMAP server address
24
+ # @option opts [String] :download_strategy (nil) the download strategy to use, either "delay"
25
+ # or "direct"
26
+ # @option opts [Boolean] :folder_blacklist (false) whether to treat folder list as a blacklist
27
+ # @option opts [Array<String>] :folder ([]) the folders to backup
28
+ # @option opts [String] :path (nil) the local path to store the backup
29
+ # @option opts [Boolean] :mirror (false) whether to run in mirror mode
30
+ # @option opts [Integer] :multi_fetch_size (nil) the number of messages to fetch in a single
31
+ # operation
32
+ # @option opts [Boolean] :refresh (false) whether to force refresh of folder metadata
33
+ # @option opts [Boolean] :reset_seen_flags_after_fetch (false) whether to reset seen flags
34
+ # after fetching messages
15
35
  def initialize(options)
16
36
  @options = options
17
37
  @password = nil
@@ -1,14 +1,24 @@
1
+ require "thor"
2
+
1
3
  require "imap/backup/account/backup_folders"
4
+ require "imap/backup/cli/helpers"
2
5
  require "imap/backup/serializer"
3
6
 
4
7
  module Imap; end
5
8
 
6
9
  module Imap::Backup
10
+ class CLI < Thor; end
11
+
7
12
  # Prints various statistics about an account and its backup
8
13
  class CLI::Stats < Thor
9
14
  include Thor::Actions
10
15
  include CLI::Helpers
11
16
 
17
+ # @param email [String] the email address identifying the account to inspect
18
+ # @param options [Hash] CLI options controlling output
19
+ # @option opts [String] :config (nil) the path to the configuration file
20
+ # @option opts [String] :erb_configuration (nil) the path to the ERB configuration file
21
+ # @option opts [String] :format ("text") the output format, either "text" or "json"
12
22
  def initialize(email, options)
13
23
  super([])
14
24
  @email = email
@@ -16,6 +16,9 @@ module Imap::Backup
16
16
  # The possible values for the action parameter
17
17
  ACTIONS = %i(copy migrate mirror).freeze
18
18
 
19
+ # @param action [Symbol] one of ACTIONS describing the transfer mode
20
+ # @param source_email [String] the email of the source account
21
+ # @param destination_email [String] the email of the destination account
19
22
  def initialize(action, source_email, destination_email, options)
20
23
  @action = action
21
24
  @source_email = source_email
@@ -15,6 +15,7 @@ module Imap::Backup
15
15
  # @return [Client]
16
16
  attr_reader :client
17
17
 
18
+ # @param client [Client::Default] the client to wrap and lazily log in
18
19
  def initialize(client:)
19
20
  @client = client
20
21
  @login_called = false
@@ -19,6 +19,7 @@ module Imap::Backup
19
19
  responses uid_fetch uid_search uid_store
20
20
  )
21
21
 
22
+ # @param account [Account] the account whose server is accessed
22
23
  def initialize(account)
23
24
  @account = account
24
25
  @state = nil
@@ -32,6 +32,7 @@ module Imap::Backup
32
32
  File.exist?(path || default_pathname)
33
33
  end
34
34
 
35
+ # @param path [String, nil] optional path to the configuration file
35
36
  def initialize(path: nil)
36
37
  @pathname = path || self.class.default_pathname
37
38
  @download_strategy = nil
@@ -8,6 +8,10 @@ module Imap::Backup
8
8
  # @private
9
9
  class MultiFetchFailedError < StandardError; end
10
10
 
11
+ # @param folder [Account::Folder] the remote folder to download from
12
+ # @param serializer [Serializer] the local serializer that stores messages
13
+ # @param multi_fetch_size [Integer] how many messages to fetch per batch
14
+ # @param reset_seen_flags_after_fetch [Boolean] true to restore unseen flags after fetch
11
15
  def initialize(folder, serializer, multi_fetch_size: 1, reset_seen_flags_after_fetch: false)
12
16
  @folder = folder
13
17
  @serializer = serializer
@@ -35,6 +35,7 @@ module Imap::Backup
35
35
  # @return [String] the original message body
36
36
  attr_reader :supplied_body
37
37
 
38
+ # @param supplied_body [String] the original RFC 2822 message text
38
39
  def initialize(supplied_body)
39
40
  @supplied_body = supplied_body.clone
40
41
  end
@@ -3,6 +3,7 @@ module Imap; end
3
3
  module Imap::Backup
4
4
  # Accesses a file's access permissions
5
5
  class FileMode
6
+ # @param filename [String] the file whose permissions will be checked
6
7
  def initialize(filename:)
7
8
  @filename = filename
8
9
  end
@@ -6,6 +6,8 @@ module Imap::Backup
6
6
  # The number of messages to process at a time
7
7
  CHUNK_SIZE = 100
8
8
 
9
+ # @param folder [Account::Folder] the remote folder whose flags are refreshed
10
+ # @param serializer [Serializer] the local serializer providing stored UIDs
9
11
  def initialize(folder, serializer)
10
12
  @folder = folder
11
13
  @serializer = serializer
@@ -5,6 +5,8 @@ module Imap; end
5
5
  module Imap::Backup
6
6
  # Deletes locally backed-up emails that are no longer on the server
7
7
  class LocalOnlyMessageDeleter
8
+ # @param folder [Account::Folder] the remote folder used to detect missing UIDs
9
+ # @param serializer [Serializer] the local serializer that may contain stale messages
8
10
  def initialize(folder, serializer)
9
11
  @folder = folder
10
12
  @serializer = serializer
@@ -7,6 +7,7 @@ module Imap::Backup
7
7
  class Lockfile
8
8
  # An error that is thrown if a lockfile already exists
9
9
  class LockfileExistsError < StandardError; end
10
+ class ProcessStartTimeUnavailableError < StandardError; end
10
11
 
11
12
  attr_reader :path
12
13
 
@@ -53,7 +54,9 @@ module Imap::Backup
53
54
 
54
55
  return true if proc_table_entry.nil?
55
56
 
56
- proc_table_entry.starttime != starttime
57
+ other_starttime = starttime(proc_table_entry)
58
+
59
+ other_starttime != starttime
57
60
  end
58
61
 
59
62
  private
@@ -62,9 +65,11 @@ module Imap::Backup
62
65
  pid = Process.pid
63
66
  proc_table_entry = Sys::ProcTable.ps(pid: pid)
64
67
 
65
- raise "Unable to get process info for PID #{pid}" if proc_table_entry.nil?
68
+ if proc_table_entry.nil?
69
+ raise ProcessStartTimeUnavailableError, "Unable to get process info for PID #{pid}"
70
+ end
66
71
 
67
- starttime = proc_table_entry.starttime
72
+ starttime = starttime(proc_table_entry)
68
73
 
69
74
  data = {
70
75
  pid: pid,
@@ -74,5 +79,16 @@ module Imap::Backup
74
79
  json_data = JSON.generate(data)
75
80
  File.write(path, json_data)
76
81
  end
82
+
83
+ def starttime(proc_table_entry)
84
+ case
85
+ when proc_table_entry.respond_to?(:starttime)
86
+ proc_table_entry.starttime
87
+ when proc_table_entry.respond_to?(:start_tvsec)
88
+ proc_table_entry.start_tvsec
89
+ else
90
+ raise ProcessStartTimeUnavailableError, "Proctable entry structure unknown"
91
+ end
92
+ end
77
93
  end
78
94
  end
@@ -49,7 +49,7 @@ module Imap::Backup
49
49
  end
50
50
 
51
51
  # Wraps a block, filtering output to standard error,
52
- # hidng passwords and outputs the results to standard out
52
+ # hiding passwords, and outputs the results to standard out
53
53
  # @return [void]
54
54
  def self.sanitize_stderr(&block)
55
55
  sanitizer = Text::Sanitizer.new($stdout)
@@ -5,6 +5,9 @@ module Imap; end
5
5
  module Imap::Backup
6
6
  # Copies a folder of backed-up emails to an online folder
7
7
  class Migrator
8
+ # @param serializer [Serializer] the local folder to migrate
9
+ # @param folder [Account::Folder] the destination folder on the server
10
+ # @param reset [Boolean] true to clear the destination before uploading
8
11
  def initialize(serializer, folder, reset: false)
9
12
  @folder = folder
10
13
  @reset = reset
@@ -7,6 +7,8 @@ module Imap::Backup
7
7
 
8
8
  # Keeps track of the mapping between source and destination UIDs
9
9
  class Mirror::Map
10
+ # @param pathname [String] the path to the on-disk UID map
11
+ # @param destination [String] the destination account identifier
10
12
  def initialize(pathname:, destination:)
11
13
  @pathname = pathname
12
14
  @destination = destination
@@ -18,7 +20,6 @@ module Imap::Backup
18
20
  end
19
21
 
20
22
  # @return [Boolean] whether the supplied values match the existing
21
- # UID validity values
22
23
  def check_uid_validities(source:, destination:)
23
24
  store
24
25
  return false if source != source_uid_validity
@@ -5,6 +5,9 @@ module Imap; end
5
5
  module Imap::Backup
6
6
  # Synchronises a folder between a source and destination
7
7
  class Mirror
8
+ # @param serializer [Serializer] the source of backed-up messages
9
+ # @param folder [Account::Folder] the destination folder to mirror into
10
+ # @param reset [Boolean] true to delete destination-only messages first
8
11
  def initialize(serializer, folder, reset: false)
9
12
  @serializer = serializer
10
13
  @folder = folder
@@ -12,7 +12,7 @@ module Imap::Backup
12
12
  # @return [Array[Symbol]] the message's flags
13
13
  attr_accessor :flags
14
14
  # @return [Integer] the length of the message (as stored on disk)
15
- attr_reader :length
15
+ attr_accessor :length
16
16
  # @return [Integer] the start of the message inside the mailbox file
17
17
  attr_reader :offset
18
18
  # @return [Integer] the message's UID
@@ -6,7 +6,7 @@ module Imap::Backup
6
6
  # @private
7
7
  MINOR = 4
8
8
  # @private
9
- REVISION = 0
9
+ REVISION = 1
10
10
  # @private
11
11
  PRE = nil
12
12
  # The application version
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: imap-backup
3
3
  version: !ruby/object:Gem::Version
4
- version: 16.4.0
4
+ version: 16.4.1
5
5
  platform: ruby
6
6
  authors:
7
7
  - Joe Yates
@@ -175,8 +175,10 @@ files:
175
175
  - README.md
176
176
  - bin/imap-backup
177
177
  - docs/delimiters-and-prefixes.md
178
+ - docs/development.md
178
179
  - docs/documentation.md
179
180
  - docs/performance.md
181
+ - docs/testing.md
180
182
  - imap-backup.gemspec
181
183
  - lib/imap/backup/account.rb
182
184
  - lib/imap/backup/account/backup.rb
@@ -277,7 +279,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
277
279
  - !ruby/object:Gem::Version
278
280
  version: '0'
279
281
  requirements: []
280
- rubygems_version: 4.0.3
282
+ rubygems_version: 3.6.7
281
283
  specification_version: 4
282
284
  summary: Backup GMail (or other IMAP) accounts to disk
283
285
  test_files: []