imap-backup 16.3.0 → 16.4.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.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 1dbb91a882b2679944560fe5d0f16fd11b59dce626386626d151463a8354a981
4
- data.tar.gz: 7b57013410b24ed4625a0405cc9b950f6cdf528e5d50d51b2919a81048f92947
3
+ metadata.gz: 9ec15a683d70eadabab4271afcd2231c723344e79dde55fadeea091056201fc8
4
+ data.tar.gz: 946f1074220df2af38c92a5ff7f5dda8bcdcef3dc20f48d9f2c8206e29a8cd6e
5
5
  SHA512:
6
- metadata.gz: '04508eb2303ed44b9fdd8cd361958ed66eb5279a91dc310e10e404432a9e504094b4ff2d3972a645dc35ea2eff47f79f408466e3cf23cf6ac761ef1e5d3601fe'
7
- data.tar.gz: a96050615e046863111e79a3350c38dc3d6e3e05f7be967fdd5003e295c1d896f07b48d0aad719c0d2571f0f7aa315833f343bb7b0432c0e3cef556f915b1cfc
6
+ metadata.gz: 7c33112eb20f168c453f0cc52e86497c7a44459afa718ac12ff1ca71cf990d9566514598aae25fc22117fb30478db649bb76a1aff697d40dabb788cc54fa1508
7
+ data.tar.gz: ea2dd0c673c603c71708ba550cbdf98c218c51b8ff076ba031d40a7ae33f80ff830ada659b7cc300e8511cc3da207665e6f2114c231e985a0b716e2844f7fe7a
data/imap-backup.gemspec CHANGED
@@ -18,18 +18,19 @@ Gem::Specification.new do |gem|
18
18
 
19
19
  gem.executables = gem.files.grep(%r{^bin/}).map { |f| File.basename(f) }
20
20
  gem.require_paths = ["lib"]
21
- gem.required_ruby_version = ">= 3.0"
21
+ gem.required_ruby_version = ">= 3.2"
22
22
 
23
- gem.add_runtime_dependency "highline"
24
- gem.add_runtime_dependency "logger"
25
- gem.add_runtime_dependency "mail", "2.7.1"
26
- gem.add_runtime_dependency "net-imap", ">= 0.3.2"
27
- gem.add_runtime_dependency "net-smtp"
28
- gem.add_runtime_dependency "os"
29
- gem.add_runtime_dependency "ostruct"
30
- gem.add_runtime_dependency "rake"
31
- gem.add_runtime_dependency "thor", "~> 1.1"
32
- gem.add_runtime_dependency "thunderbird", "0.3.0"
23
+ gem.add_dependency "highline"
24
+ gem.add_dependency "logger"
25
+ gem.add_dependency "mail", "2.7.1"
26
+ gem.add_dependency "net-imap", ">= 0.3.2"
27
+ gem.add_dependency "net-smtp"
28
+ gem.add_dependency "os"
29
+ gem.add_dependency "ostruct"
30
+ gem.add_dependency "rake"
31
+ gem.add_dependency "sys-proctable"
32
+ gem.add_dependency "thor", "~> 1.1"
33
+ gem.add_dependency "thunderbird", "0.3.0"
33
34
 
34
35
  gem.metadata = {
35
36
  "rubygems_mfa_required" => "true"
@@ -2,6 +2,7 @@ require "imap/backup/account/backup_folders"
2
2
  require "imap/backup/account/folder_backup"
3
3
  require "imap/backup/account/folder_ensurer"
4
4
  require "imap/backup/account/local_only_folder_deleter"
5
+ require "imap/backup/account/locker"
5
6
 
6
7
  module Imap; end
7
8
 
@@ -22,19 +23,17 @@ module Imap::Backup
22
23
  # start the connection so we get logging messages in the right order
23
24
  account.client.login
24
25
 
25
- run_pre_backup_tasks
26
- backup_folders = Account::BackupFolders.new(
27
- client: account.client, account: account
28
- ).to_a
26
+ ensure_folder
27
+ delete_local_only_folders if account.mirror_mode
28
+
29
29
  if backup_folders.none?
30
30
  Logger.logger.warn "No folders found to backup for account '#{account.username}'"
31
31
  return
32
32
  end
33
- Logger.logger.debug "Starting backup of #{backup_folders.count} folders"
34
- backup_folders.each do |folder|
35
- Account::FolderBackup.new(account: account, folder: folder, refresh: refresh).run
33
+
34
+ locker.with_lock do
35
+ perform_backup
36
36
  end
37
- Logger.logger.debug "Backup of account '#{account.username}' complete"
38
37
  end
39
38
 
40
39
  private
@@ -42,9 +41,30 @@ module Imap::Backup
42
41
  attr_reader :account
43
42
  attr_reader :refresh
44
43
 
45
- def run_pre_backup_tasks
44
+ def backup_folders
45
+ @backup_folders ||= Account::BackupFolders.new(
46
+ client: account.client, account: account
47
+ ).to_a
48
+ end
49
+
50
+ def delete_local_only_folders
51
+ Account::LocalOnlyFolderDeleter.new(account: account).run
52
+ end
53
+
54
+ def ensure_folder
46
55
  Account::FolderEnsurer.new(account: account).run
47
- Account::LocalOnlyFolderDeleter.new(account: account).run if account.mirror_mode
56
+ end
57
+
58
+ def locker
59
+ @locker ||= Account::Locker.new(account: account)
60
+ end
61
+
62
+ def perform_backup
63
+ Logger.logger.debug "Starting backup of #{backup_folders.count} folders"
64
+ backup_folders.each do |folder|
65
+ Account::FolderBackup.new(account: account, folder: folder, refresh: refresh).run
66
+ end
67
+ Logger.logger.debug "Backup of account '#{account.username}' complete"
48
68
  end
49
69
  end
50
70
  end
@@ -184,6 +184,13 @@ module Imap::Backup
184
184
  CREATE_RETRY_CLASSES = [::Net::IMAP::BadResponseError].freeze
185
185
  EXAMINE_RETRY_CLASSES = [::Net::IMAP::BadResponseError].freeze
186
186
  PERMITTED_FLAGS = %i(Answered Draft Flagged Seen).freeze
187
+ private_constant :BODY_ATTRIBUTE,
188
+ :UID_FETCH_RETRY_CLASSES,
189
+ :UID_SEARCH_RETRY_CLASSES,
190
+ :APPEND_RETRY_CLASSES,
191
+ :CREATE_RETRY_CLASSES,
192
+ :EXAMINE_RETRY_CLASSES,
193
+ :PERMITTED_FLAGS
187
194
 
188
195
  def examine
189
196
  client.examine(utf7_encoded_name)
@@ -0,0 +1,35 @@
1
+ require "imap/backup/account/folder_ensurer"
2
+ require "imap/backup/lockfile"
3
+
4
+ module Imap; end
5
+
6
+ module Imap::Backup
7
+ class Account; end
8
+
9
+ class Account::Locker
10
+ attr_reader :account
11
+
12
+ def initialize(account:)
13
+ @account = account
14
+ end
15
+
16
+ def with_lock(&block)
17
+ lockfile = Lockfile.new(path: account.lockfile_path)
18
+ if lockfile.exists?
19
+ if !lockfile.stale?
20
+ raise Lockfile::LockfileExistsError,
21
+ "Lockfile '#{account.lockfile_path}' exists and is not stale."
22
+ end
23
+
24
+ Logger.logger.info("Stale lockfile '#{account.lockfile_path}' found. Removing it.")
25
+ lockfile.remove
26
+ else
27
+ Account::FolderEnsurer.new(account: account).run
28
+ end
29
+
30
+ lockfile.with_lock do
31
+ block.call
32
+ end
33
+ end
34
+ end
35
+ end
@@ -1,4 +1,5 @@
1
1
  require "imap/backup/account/folder_mapper"
2
+ require "imap/backup/account/locker"
2
3
  require "imap/backup/uploader"
3
4
 
4
5
  module Imap; end
@@ -12,13 +13,16 @@ module Imap::Backup
12
13
  @account = account
13
14
  @destination_delimiter = delimiter
14
15
  @destination_prefix = prefix
16
+ @locker = nil
15
17
  end
16
18
 
17
19
  # Runs the restore operation
18
20
  # @return [void]
19
21
  def run
20
- folders.each do |serializer, folder|
21
- Uploader.new(folder, serializer).run
22
+ locker.with_lock do
23
+ folders.each do |serializer, folder|
24
+ Uploader.new(folder, serializer).run
25
+ end
22
26
  end
23
27
  end
24
28
 
@@ -40,5 +44,9 @@ module Imap::Backup
40
44
  def folders
41
45
  Account::FolderMapper.new(**enumerator_options)
42
46
  end
47
+
48
+ def locker
49
+ @locker ||= Account::Locker.new(account: account)
50
+ end
43
51
  end
44
52
  end
@@ -4,6 +4,7 @@ require "imap/backup/account/client_factory"
4
4
 
5
5
  module Imap; end
6
6
 
7
+ # rubocop:disable Metrics/ClassLength
7
8
  module Imap::Backup
8
9
  # Contains the attributes relating to an email account.
9
10
  class Account
@@ -168,6 +169,14 @@ module Imap::Backup
168
169
  update(:local_path, value)
169
170
  end
170
171
 
172
+ # @raise [RuntimeError] if the local_path is not set
173
+ # @return [String] the path to the lockfile for the account
174
+ def lockfile_path
175
+ raise "local_path is not set" if !local_path
176
+
177
+ File.join(local_path, "imap-backup.lock")
178
+ end
179
+
171
180
  # @raise [RuntimeError] if the supplied value is not an Array
172
181
  # @return [void]
173
182
  def folders=(value)
@@ -283,14 +292,19 @@ module Imap::Backup
283
292
 
284
293
  attr_reader :changes
285
294
 
286
- REQUIRED_ATTRIBUTES = %i[password username].freeze
295
+ REQUIRED_ATTRIBUTES = %i[password username local_path].freeze
287
296
  OPTIONAL_ATTRIBUTES = %i[
288
- connection_options download_strategy folders folder_blacklist local_path mirror_mode
297
+ connection_options download_strategy folders folder_blacklist mirror_mode
289
298
  multi_fetch_size reset_seen_flags_after_fetch server status
290
299
  ].freeze
291
300
  KNOWN_ATTRIBUTES = REQUIRED_ATTRIBUTES + OPTIONAL_ATTRIBUTES
292
301
  VALID_STATUSES = %w[active archived offline].freeze
293
302
  DEFAULT_STATUS = "active".freeze
303
+ private_constant :REQUIRED_ATTRIBUTES,
304
+ :OPTIONAL_ATTRIBUTES,
305
+ :KNOWN_ATTRIBUTES,
306
+ :VALID_STATUSES,
307
+ :DEFAULT_STATUS
294
308
 
295
309
  def check_options!(options)
296
310
  missing_required = REQUIRED_ATTRIBUTES - options.keys
@@ -64,6 +64,8 @@ module Imap::Backup
64
64
  case exception
65
65
  when Net::IMAP::NoResponseError, Errno::ECONNREFUSED
66
66
  111
67
+ when Lockfile::LockfileExistsError
68
+ 112
67
69
  else
68
70
  1
69
71
  end
@@ -141,6 +141,7 @@ module Imap::Backup
141
141
  private
142
142
 
143
143
  MAX_SUBJECT = 60
144
+ private_constant :MAX_SUBJECT
144
145
 
145
146
  def list_emails_as_json(serializer)
146
147
  emails = serializer.each_message.map do |message|
@@ -109,9 +109,12 @@ module Imap::Backup
109
109
  Kernel.puts list.to_json
110
110
  end
111
111
 
112
+ NAMESPACE_TEMPLATE = "%-10<name>s %-10<prefix>s %<delim>s".freeze
113
+ private_constant :NAMESPACE_TEMPLATE
114
+
112
115
  def list_namespaces(namespaces)
113
116
  Kernel.puts format(
114
- "%-10<name>s %-10<prefix>s %<delim>s",
117
+ NAMESPACE_TEMPLATE,
115
118
  {name: "Name", prefix: "Prefix", delim: "Delimiter"}
116
119
  )
117
120
  list_namespace namespaces, :personal
@@ -122,7 +125,7 @@ module Imap::Backup
122
125
  def list_namespace(namespaces, name)
123
126
  info = namespace_info(namespaces.send(name).first, quote: true)
124
127
  if info
125
- Kernel.puts format("%-10<name>s %-10<prefix>s %<delim>s", name: name, **info)
128
+ Kernel.puts format(NAMESPACE_TEMPLATE, name: name, **info)
126
129
  else
127
130
  Kernel.puts format("%-10<name>s (Not defined)", name: name)
128
131
  end
@@ -50,8 +50,8 @@ module Imap::Backup
50
50
  attr_reader :email
51
51
  attr_reader :options
52
52
 
53
- def restore(account, **options)
54
- restore = Account::Restore.new(account: account, **options)
53
+ def restore(account, **)
54
+ restore = Account::Restore.new(account: account, **)
55
55
  restore.run
56
56
  end
57
57
 
@@ -37,6 +37,7 @@ module Imap::Backup
37
37
  {name: :local, width: 8, alignment: :right}
38
38
  ].freeze
39
39
  ALIGNMENT_FORMAT_SYMBOL = {left: "-", right: " "}.freeze
40
+ private_constant :TEXT_COLUMNS, :ALIGNMENT_FORMAT_SYMBOL
40
41
 
41
42
  attr_reader :email
42
43
  attr_reader :options
@@ -1,4 +1,5 @@
1
1
  require "imap/backup/account/folder_mapper"
2
+ require "imap/backup/account/locker"
2
3
  require "imap/backup/cli/backup"
3
4
  require "imap/backup/cli/helpers"
4
5
  require "imap/backup/logger"
@@ -23,9 +24,11 @@ module Imap::Backup
23
24
  @automatic_namespaces = nil
24
25
  @config_path = nil
25
26
  @destination_delimiter = nil
27
+ @destination_locker = nil
26
28
  @destination_prefix = nil
27
29
  @reset = nil
28
30
  @source_delimiter = nil
31
+ @source_locker = nil
29
32
  @source_prefix = nil
30
33
  end
31
34
 
@@ -34,22 +37,19 @@ module Imap::Backup
34
37
  # or the source and destination accounts are the same,
35
38
  # or either of the accounts is not configured,
36
39
  # or incompatible namespace/delimiter parameters have been supplied
40
+ # or one or both of the accounts is locked by another process.
37
41
  # @return [void]
38
42
  def run
39
43
  raise "Unknown action '#{action}'" if !ACTIONS.include?(action)
40
44
 
41
45
  process_options!
42
46
  warn_if_source_account_is_not_in_mirror_mode if action == :mirror
47
+
43
48
  run_backup if %i(copy mirror).include?(action)
44
49
 
45
- folders.each do |serializer, folder|
46
- case action
47
- when :copy
48
- Mirror.new(serializer, folder, reset: false).run
49
- when :migrate
50
- Migrator.new(serializer, folder, reset: reset).run
51
- when :mirror
52
- Mirror.new(serializer, folder, reset: true).run
50
+ source_locker.with_lock do
51
+ destination_locker.with_lock do
52
+ perform_action_on_folders
53
53
  end
54
54
  end
55
55
  end
@@ -68,6 +68,19 @@ module Imap::Backup
68
68
  attr_reader :source_email
69
69
  attr_accessor :source_prefix
70
70
 
71
+ def perform_action_on_folders
72
+ folders.each do |serializer, folder|
73
+ case action
74
+ when :copy
75
+ Mirror.new(serializer, folder, reset: false).run
76
+ when :migrate
77
+ Migrator.new(serializer, folder, reset: reset).run
78
+ when :mirror
79
+ Mirror.new(serializer, folder, reset: true).run
80
+ end
81
+ end
82
+ end
83
+
71
84
  def process_options!
72
85
  self.automatic_namespaces = options[:automatic_namespaces] || false
73
86
  self.config_path = options[:config]
@@ -175,5 +188,13 @@ module Imap::Backup
175
188
  def source_account
176
189
  config.accounts.find { |a| a.username == source_email }
177
190
  end
191
+
192
+ def source_locker
193
+ @source_locker ||= Account::Locker.new(account: source_account)
194
+ end
195
+
196
+ def destination_locker
197
+ @destination_locker ||= Account::Locker.new(account: destination_account)
198
+ end
178
199
  end
179
200
  end
@@ -33,15 +33,10 @@ module Imap::Backup
33
33
  Logger.setup_logging options
34
34
  config = load_config(**options)
35
35
  account = account(config, email)
36
+ locker ||= Account::Locker.new(account: account)
36
37
 
37
- backup_folders = Account::BackupFolders.new(
38
- client: account.client, account: account
39
- )
40
- backup_folders.each do |folder|
41
- next if !folder.exist?
42
-
43
- serializer = Serializer.new(account.local_path, folder.name)
44
- do_ignore_folder_history(folder, serializer)
38
+ locker.with_lock do
39
+ ignore_account_history(account)
45
40
  end
46
41
  end
47
42
 
@@ -99,8 +94,21 @@ module Imap::Backup
99
94
  private
100
95
 
101
96
  FAKE_EMAIL = "fake@email.com".freeze
97
+ private_constant :FAKE_EMAIL
98
+
99
+ def ignore_account_history(account)
100
+ backup_folders = Account::BackupFolders.new(
101
+ client: account.client, account: account
102
+ )
103
+ backup_folders.each do |folder|
104
+ next if !folder.exist?
105
+
106
+ serializer = Serializer.new(account.local_path, folder.name)
107
+ ignore_folder_history(folder, serializer)
108
+ end
109
+ end
102
110
 
103
- def do_ignore_folder_history(folder, serializer)
111
+ def ignore_folder_history(folder, serializer)
104
112
  uids = folder.uids - serializer.uids
105
113
  Logger.logger.info "Folder '#{folder.name}' - #{uids.length} messages"
106
114
 
@@ -13,6 +13,7 @@ module Imap::Backup
13
13
  # Tracks the latest folder selection in order to avoid repeated calls
14
14
  class Client::Default
15
15
  extend Forwardable
16
+
16
17
  def_delegators :imap, *%i(
17
18
  append authenticate capability create expunge namespace
18
19
  responses uid_fetch uid_search uid_store
@@ -68,7 +68,10 @@ module Imap::Backup
68
68
  ensure_loaded!
69
69
  accounts = data[:accounts].map do |attr|
70
70
  Account.new(attr)
71
- end
71
+ rescue ArgumentError => e
72
+ Logger.logger.error("Skipping invalid account in config: #{e.message}")
73
+ nil
74
+ end.compact
72
75
  inject_global_attributes(accounts)
73
76
  end
74
77
  end
@@ -108,8 +111,6 @@ module Imap::Backup
108
111
 
109
112
  private
110
113
 
111
- VERSION_2_1 = "2.1".freeze
112
-
113
114
  attr_reader :pathname
114
115
 
115
116
  def ensure_loaded!
@@ -0,0 +1,78 @@
1
+ require "sys/proctable"
2
+ require "json"
3
+
4
+ module Imap; end
5
+
6
+ module Imap::Backup
7
+ class Lockfile
8
+ # An error that is thrown if a lockfile already exists
9
+ class LockfileExistsError < StandardError; end
10
+
11
+ attr_reader :path
12
+
13
+ # Initializes a new Lockfile instance.
14
+ # @param path [String] the path to the lockfile
15
+ def initialize(path:)
16
+ @path = path
17
+ end
18
+
19
+ # Creates the lockfile, yields to the given block
20
+ # and ensures the lockfile is removed afterwards.
21
+ def with_lock(&block)
22
+ raise LockfileExistsError, "Lockfile already exists at #{path}" if exists?
23
+
24
+ begin
25
+ create
26
+ block.call
27
+ ensure
28
+ remove
29
+ end
30
+ end
31
+
32
+ # Checks if the lockfile exists.
33
+ # @return [Boolean] true if the lockfile exists, false otherwise
34
+ def exists?
35
+ File.exist?(path)
36
+ end
37
+
38
+ # Removes the lockfile.
39
+ def remove
40
+ FileUtils.rm_f(path)
41
+ end
42
+
43
+ # Checks if the lockfile is stale (i.e., the process that created it is no longer running).
44
+ # @return [Boolean] true if the lockfile is stale, false otherwise
45
+ def stale?
46
+ return false if !exists?
47
+
48
+ file_content = File.read(path)
49
+ data = JSON.parse(file_content, symbolize_names: true)
50
+ pid = data[:pid]
51
+ starttime = data[:starttime]
52
+ proc_table_entry = Sys::ProcTable.ps(pid: pid)
53
+
54
+ return true if proc_table_entry.nil?
55
+
56
+ proc_table_entry.starttime != starttime
57
+ end
58
+
59
+ private
60
+
61
+ def create
62
+ pid = Process.pid
63
+ proc_table_entry = Sys::ProcTable.ps(pid: pid)
64
+
65
+ raise "Unable to get process info for PID #{pid}" if proc_table_entry.nil?
66
+
67
+ starttime = proc_table_entry.starttime
68
+
69
+ data = {
70
+ pid: pid,
71
+ starttime: starttime
72
+ }
73
+
74
+ json_data = JSON.generate(data)
75
+ File.write(path, json_data)
76
+ end
77
+ end
78
+ end
@@ -11,10 +11,10 @@ module Imap::Backup
11
11
  @reset = reset
12
12
  end
13
13
 
14
- # If necessary, reates the destination folder,
14
+ # If necessary, creates the destination folder,
15
15
  # then deletes any messages in the destination folder
16
16
  # that are not in the local store,
17
- # sets existing messages' flas
17
+ # sets existing messages' flags
18
18
  # then appends any missing messages
19
19
  # and saves the mapping file
20
20
  # @return [void]
@@ -29,6 +29,7 @@ module Imap::Backup
29
29
  private
30
30
 
31
31
  CHUNK_SIZE = 100
32
+ private_constant :CHUNK_SIZE
32
33
 
33
34
  attr_reader :serializer
34
35
  attr_reader :folder
@@ -109,7 +109,7 @@ module Imap::Backup
109
109
  # Sets the mailbox file's updated time to the current time
110
110
  # @return [void]
111
111
  def touch
112
- File.open(pathname, "a") {}
112
+ FileUtils.touch(pathname)
113
113
  end
114
114
 
115
115
  private
@@ -22,7 +22,7 @@ module Imap::Backup
22
22
  return nil if actual.nil?
23
23
 
24
24
  mask = ~limit & 0o777
25
- return if (actual & mask).zero?
25
+ return if actual.nobits?(mask)
26
26
 
27
27
  message = format(
28
28
  "Permissions on '%<filename>s' " \
@@ -101,7 +101,7 @@ module Imap::Backup
101
101
  end
102
102
  end
103
103
 
104
- next if lines.count.zero?
104
+ next if lines.none?
105
105
 
106
106
  message = {
107
107
  uid: uids[messages.count],
@@ -56,6 +56,7 @@ module Imap::Backup
56
56
  private
57
57
 
58
58
  EMAIL_MATCHER = /^[A-Z0-9._%+-]+@[A-Z0-9.-]+\.[A-Z]+$/i
59
+ private_constant :EMAIL_MATCHER
59
60
 
60
61
  attr_reader :highline
61
62
  end
@@ -91,6 +91,7 @@ module Imap::Backup
91
91
  Imap::Backup::Account.new(
92
92
  username: username,
93
93
  password: "",
94
+ local_path: nil,
94
95
  folders: []
95
96
  ).tap do |a|
96
97
  provider = Imap::Backup::Email::Provider.for_address(username)
@@ -51,6 +51,7 @@ module Imap::Backup
51
51
  private
52
52
 
53
53
  EXPORT_PREFIX = "imap-backup".freeze
54
+ private_constant :EXPORT_PREFIX
54
55
 
55
56
  attr_reader :email
56
57
  attr_reader :serializer
@@ -4,7 +4,7 @@ module Imap::Backup
4
4
  # @private
5
5
  MAJOR = 16
6
6
  # @private
7
- MINOR = 3
7
+ MINOR = 4
8
8
  # @private
9
9
  REVISION = 0
10
10
  # @private
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.3.0
4
+ version: 16.4.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Joe Yates
@@ -121,6 +121,20 @@ dependencies:
121
121
  - - ">="
122
122
  - !ruby/object:Gem::Version
123
123
  version: '0'
124
+ - !ruby/object:Gem::Dependency
125
+ name: sys-proctable
126
+ requirement: !ruby/object:Gem::Requirement
127
+ requirements:
128
+ - - ">="
129
+ - !ruby/object:Gem::Version
130
+ version: '0'
131
+ type: :runtime
132
+ prerelease: false
133
+ version_requirements: !ruby/object:Gem::Requirement
134
+ requirements:
135
+ - - ">="
136
+ - !ruby/object:Gem::Version
137
+ version: '0'
124
138
  - !ruby/object:Gem::Dependency
125
139
  name: thor
126
140
  requirement: !ruby/object:Gem::Requirement
@@ -173,6 +187,7 @@ files:
173
187
  - lib/imap/backup/account/folder_ensurer.rb
174
188
  - lib/imap/backup/account/folder_mapper.rb
175
189
  - lib/imap/backup/account/local_only_folder_deleter.rb
190
+ - lib/imap/backup/account/locker.rb
176
191
  - lib/imap/backup/account/restore.rb
177
192
  - lib/imap/backup/account/serialized_folders.rb
178
193
  - lib/imap/backup/cli.rb
@@ -207,6 +222,7 @@ files:
207
222
  - lib/imap/backup/file_mode.rb
208
223
  - lib/imap/backup/flag_refresher.rb
209
224
  - lib/imap/backup/local_only_message_deleter.rb
225
+ - lib/imap/backup/lockfile.rb
210
226
  - lib/imap/backup/logger.rb
211
227
  - lib/imap/backup/migrator.rb
212
228
  - lib/imap/backup/mirror.rb
@@ -254,14 +270,14 @@ required_ruby_version: !ruby/object:Gem::Requirement
254
270
  requirements:
255
271
  - - ">="
256
272
  - !ruby/object:Gem::Version
257
- version: '3.0'
273
+ version: '3.2'
258
274
  required_rubygems_version: !ruby/object:Gem::Requirement
259
275
  requirements:
260
276
  - - ">="
261
277
  - !ruby/object:Gem::Version
262
278
  version: '0'
263
279
  requirements: []
264
- rubygems_version: 3.6.7
280
+ rubygems_version: 4.0.3
265
281
  specification_version: 4
266
282
  summary: Backup GMail (or other IMAP) accounts to disk
267
283
  test_files: []