imap-backup 10.0.1 → 11.0.0.rc1

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: 7675c421b9705eb8e4543fc9df035c6a9750b291688805e25cc7f6c2537936e8
4
- data.tar.gz: e3027362856d02201bf5d2633804b6744238684bb30979c07d8cc53ef4afaf36
3
+ metadata.gz: e9cff069fefde8147d1995b254f3f0613575ff4f52232c2fc64c91dc00c49bde
4
+ data.tar.gz: a1b8cb3d48f41b2773fd20b842b8d3e3b71e673b6797c83a345e341d087060d8
5
5
  SHA512:
6
- metadata.gz: 34771e1aa4dd8d8444e37b149d3a491b506363d6dfff00cd6ed6a8a7e501f6ac04e5d7ec24774fd36488bda96c6dfa6454b388d6afbe0f86e0b11317877100f0
7
- data.tar.gz: f721a34ed6bf6af08d7a87335241e684a3d878538534300821c9466bdff5215b539f1f9cffebe484567370481460d114922f62fc7d73daba56801594ac3171b0
6
+ metadata.gz: 4e7b83bac1004b853287689e98ed4d03d7c3c87bd05c4a22831ffe592782ff8916ab576778e41de572f872505e3beab5b923ff5d1d78ccf3e1a99d701ff95737
7
+ data.tar.gz: a0cc1cc27b225c315a68743dff999fc2d8b0d0c9fd5d68f03641b59d53ddc491da21f318b9c075927263367b9db0a50e80af727de0445c86a049bc55d2a32fdf
data/README.md CHANGED
@@ -100,6 +100,54 @@ For more information about a command, run
100
100
  imap-backup help COMMAND
101
101
  ```
102
102
 
103
+ # Performace
104
+
105
+ There are a couple of performance tweaks that you can use
106
+ to improve backup speed.
107
+
108
+ These are activated via two settings:
109
+
110
+ * Global setting "Delay download writes"
111
+ * Account setting "Multi-fetch size"
112
+
113
+ As with all performance tweaks, there are trade-offs.
114
+ If you are using a small virtual server or Raspberry Pi
115
+ to run your backups, you will probably want to leave
116
+ the deafult settings.
117
+ If, on the other hand, you are using a computer with a
118
+ fair bit of RAM, and you are dealing with a *lot* of email,
119
+ then changing these settings may be worthwhile.
120
+
121
+ ## Delay download writes
122
+
123
+ This setting affects all account backups.
124
+
125
+ When not set, each message is written to disk, one at a time.
126
+ Doing so means the message itself is appended to the MBox file,
127
+ but more importantly, the JSON metadata is rewritten to disk
128
+ from scratch.
129
+
130
+ When in use, all of a mailboxes unbackupped messages are
131
+ downloaded first, and then written to disk just once.
132
+
133
+ This speeds up backup as the metadata file is not rewritten
134
+ after each message is added, but it potentially uses much more memory.
135
+
136
+ ## Multi-fetch Size
137
+
138
+ By default, during backup, each message is downloaded one-by-one.
139
+
140
+ Using this setting, you can download chunks of emails at a time,
141
+ potentially speeding up the process.
142
+
143
+ If you're not using "Delayed downlaod writes",
144
+ using multi-fetch *will* mean that the backup process will use
145
+ more memory - equivalent to the size of the greater number
146
+ of messages downloaded at a time.
147
+
148
+ This behaviour may also exceed limits on your email provider,
149
+ so it's best to check before cranking it up!
150
+
103
151
  # Troubleshooting
104
152
 
105
153
  If you have problems:
data/docs/development.md CHANGED
@@ -43,6 +43,15 @@ or
43
43
  $ rspec --tag ~docker
44
44
  ```
45
45
 
46
+ # Performance Specs
47
+
48
+ ```sh
49
+ PERFORMANCE=1 rspec --order=defined
50
+ ```
51
+
52
+ Beware: the performance spec (just backup for now) takes a very
53
+ long time to run, approximately 24 hours!
54
+
46
55
  ### Debugging
47
56
 
48
57
  The feature specs are run 'out of process' via the Aruba gem.
@@ -38,12 +38,19 @@ module Imap::Backup
38
38
 
39
39
  Logger.logger.debug "[#{folder.name}] running backup"
40
40
  serializer.apply_uid_validity(folder.uid_validity)
41
- Downloader.new(
41
+ downloader = Downloader.new(
42
42
  folder,
43
43
  serializer,
44
44
  multi_fetch_size: account.multi_fetch_size,
45
45
  reset_seen_flags_after_fetch: account.reset_seen_flags_after_fetch
46
- ).run
46
+ )
47
+ if account.delay_download_writes
48
+ serializer.transaction do
49
+ downloader.run
50
+ end
51
+ else
52
+ downloader.run
53
+ end
47
54
  if account.mirror_mode
48
55
  Logger.logger.info "Mirror mode - Deleting messages only present locally"
49
56
  LocalOnlyMessageDeleter.new(folder, serializer).run
@@ -15,6 +15,7 @@ module Imap::Backup
15
15
  attr_reader :mirror_mode
16
16
  attr_reader :server
17
17
  attr_reader :connection_options
18
+ attr_accessor :delay_download_writes
18
19
  attr_reader :reset_seen_flags_after_fetch
19
20
  attr_reader :changes
20
21
 
@@ -27,7 +28,8 @@ module Imap::Backup
27
28
  @mirror_mode = options[:mirror_mode]
28
29
  @server = options[:server]
29
30
  @connection_options = options[:connection_options]
30
- @multi_fetch_size = options[:multi_fetch_size]
31
+ @delay_download_writes = true
32
+ @multi_fetch_size_orignal = options[:multi_fetch_size]
31
33
  @reset_seen_flags_after_fetch = options[:reset_seen_flags_after_fetch]
32
34
  @client = nil
33
35
  @changes = {}
@@ -42,6 +44,10 @@ module Imap::Backup
42
44
  client.namespace
43
45
  end
44
46
 
47
+ def capabilities
48
+ client.capability
49
+ end
50
+
45
51
  def restore
46
52
  restore = Account::Restore.new(account: self)
47
53
  restore.run
@@ -75,7 +81,8 @@ module Imap::Backup
75
81
  h[:mirror_mode] = true if @mirror_mode
76
82
  h[:server] = @server if @server
77
83
  h[:connection_options] = @connection_options if @connection_options
78
- h[:multi_fetch_size] = multi_fetch_size if @multi_fetch_size
84
+ h[:delay_download_writes] = delay_download_writes
85
+ h[:multi_fetch_size] = multi_fetch_size
79
86
  if @reset_seen_flags_after_fetch
80
87
  h[:reset_seen_flags_after_fetch] = @reset_seen_flags_after_fetch
81
88
  end
@@ -123,11 +130,13 @@ module Imap::Backup
123
130
  end
124
131
 
125
132
  def multi_fetch_size
126
- int = @multi_fetch_size.to_i
127
- if int.positive?
128
- int
129
- else
130
- DEFAULT_MULTI_FETCH_SIZE
133
+ @multi_fetch_size ||= begin
134
+ int = @multi_fetch_size_orignal.to_i
135
+ if int.positive?
136
+ int
137
+ else
138
+ DEFAULT_MULTI_FETCH_SIZE
139
+ end
131
140
  end
132
141
  end
133
142
 
@@ -41,7 +41,6 @@ module Imap::Backup
41
41
  results = requested_accounts(config).map do |account|
42
42
  serialized_folders = Account::SerializedFolders.new(account: account)
43
43
  folder_results = serialized_folders.map do |serializer, _folder|
44
- puts "serializer: #{serializer.inspect}"
45
44
  serializer.check_integrity!
46
45
  {name: serializer.folder, result: "OK"}
47
46
  rescue Serializer::FolderIntegrityError => e
@@ -23,6 +23,22 @@ module Imap::Backup
23
23
  end
24
24
  end
25
25
 
26
+ desc "capabilities EMAIL", "List server capabilities"
27
+ long_desc <<~DESC
28
+ Lists the IMAP capabilities supported by the IMAP server.
29
+ DESC
30
+ config_option
31
+ format_option
32
+ quiet_option
33
+ verbose_option
34
+ def capabilities(email)
35
+ Imap::Backup::Logger.setup_logging options
36
+ config = load_config(**options)
37
+ account = account(config, email)
38
+ capabilities = account.capabilities
39
+ Kernel.puts capabilities.join(", ")
40
+ end
41
+
26
42
  desc "namespaces EMAIL", "List account namespaces"
27
43
  long_desc <<~DESC
28
44
  Lists namespaces defined for an email account.
@@ -9,7 +9,7 @@ module Imap::Backup
9
9
  class Client::Default
10
10
  extend Forwardable
11
11
  def_delegators :imap, *%i(
12
- append authenticate create expunge namespace
12
+ append authenticate capability create expunge namespace
13
13
  responses uid_fetch uid_search uid_store
14
14
  )
15
15
 
@@ -14,6 +14,8 @@ module Imap::Backup
14
14
  VERSION = "2.0".freeze
15
15
 
16
16
  attr_reader :pathname
17
+ attr_reader :delay_download_writes
18
+ attr_reader :delay_download_writes_modified
17
19
 
18
20
  def self.default_pathname
19
21
  File.join(CONFIGURATION_DIRECTORY, "config.json")
@@ -25,6 +27,8 @@ module Imap::Backup
25
27
 
26
28
  def initialize(path: nil)
27
29
  @pathname = path || self.class.default_pathname
30
+ @delay_download_writes = false
31
+ @delay_download_writes_modified = false
28
32
  end
29
33
 
30
34
  def path
@@ -39,7 +43,8 @@ module Imap::Backup
39
43
  remove_deleted_accounts
40
44
  save_data = {
41
45
  version: VERSION,
42
- accounts: accounts.map(&:to_h)
46
+ accounts: accounts.map(&:to_h),
47
+ delay_download_writes: delay_download_writes
43
48
  }
44
49
  File.open(pathname, "w") { |f| f.write(JSON.pretty_generate(save_data)) }
45
50
  FileUtils.chmod(0o600, pathname) if !windows?
@@ -49,13 +54,26 @@ module Imap::Backup
49
54
  def accounts
50
55
  @accounts ||= begin
51
56
  ensure_loaded!
52
- data[:accounts].map { |data| Account.new(data) }
57
+ accounts = data[:accounts].map do |attr|
58
+ Account.new(attr)
59
+ end
60
+ inject_global_attributes(accounts)
53
61
  end
54
62
  end
55
63
 
64
+ def delay_download_writes=(value)
65
+ ensure_loaded!
66
+
67
+ @delay_download_writes = value
68
+ @delay_download_writes_modified = true
69
+ inject_global_attributes(accounts)
70
+ end
71
+
56
72
  def modified?
57
73
  ensure_loaded!
58
74
 
75
+ return true if delay_download_writes_modified
76
+
59
77
  accounts.any? { |a| a.modified? || a.marked_for_deletion? }
60
78
  end
61
79
 
@@ -65,6 +83,7 @@ module Imap::Backup
65
83
  return true if @data
66
84
 
67
85
  data
86
+ @delay_download_writes = data[:delay_download_writes]
68
87
  true
69
88
  end
70
89
 
@@ -83,6 +102,7 @@ module Imap::Backup
83
102
  end
84
103
 
85
104
  def remove_modified_flags
105
+ @delay_download_writes_modified = false
86
106
  accounts.each(&:clear_changes)
87
107
  end
88
108
 
@@ -90,6 +110,13 @@ module Imap::Backup
90
110
  accounts.reject!(&:marked_for_deletion?)
91
111
  end
92
112
 
113
+ def inject_global_attributes(accounts)
114
+ accounts.map do |a|
115
+ a.delay_download_writes = delay_download_writes
116
+ a
117
+ end
118
+ end
119
+
93
120
  def make_private(path)
94
121
  FileUtils.chmod(0o700, path) if FileMode.new(filename: path).mode != 0o700
95
122
  end
@@ -14,7 +14,7 @@ module Imap::Backup
14
14
  @mbox = mbox
15
15
  end
16
16
 
17
- def run(uid:, message:, flags:)
17
+ def single(uid:, message:, flags:)
18
18
  raise "Can't add messages without uid_validity" if !imap.uid_validity
19
19
 
20
20
  uid = uid.to_i
@@ -26,29 +26,43 @@ module Imap::Backup
26
26
  return
27
27
  end
28
28
 
29
- do_append uid, message, flags
29
+ rollback_on_error do
30
+ do_append uid, message, flags
31
+ rescue StandardError => e
32
+ raise <<-ERROR.gsub(/^\s*/m, "")
33
+ [#{folder}] failed to append message #{uid}:
34
+ #{message}. #{e}:
35
+ #{e.backtrace.join("\n")}"
36
+ ERROR
37
+ end
38
+ end
39
+
40
+ def multi(appends)
41
+ rollback_on_error do
42
+ appends.each do |a|
43
+ do_append a[:uid], a[:message], a[:flags]
44
+ end
45
+ end
30
46
  end
31
47
 
32
48
  private
33
49
 
34
50
  def do_append(uid, message, flags)
35
51
  mboxrd_message = Email::Mboxrd::Message.new(message)
36
- initial = mbox.length || 0
37
- mbox_appended = false
38
- begin
39
- serialized = mboxrd_message.to_serialized
40
- mbox.append serialized
41
- mbox_appended = true
42
- imap.append uid, serialized.length, flags
43
- rescue StandardError => e
44
- mbox.rewind(initial) if mbox_appended
52
+ serialized = mboxrd_message.to_serialized
53
+ mbox.append serialized
54
+ imap.append uid, serialized.length, flags: flags
55
+ end
45
56
 
46
- error = <<-ERROR.gsub(/^\s*/m, "")
47
- [#{folder}] failed to append message #{uid}:
48
- #{message}. #{e}:
49
- #{e.backtrace.join("\n")}"
50
- ERROR
51
- Logger.logger.warn error
57
+ def rollback_on_error(&block)
58
+ imap.transaction do
59
+ mbox.transaction do
60
+ block.call
61
+ rescue StandardError => e
62
+ Logger.logger.error e
63
+ imap.rollback
64
+ mbox.rollback
65
+ end
52
66
  end
53
67
  end
54
68
  end
@@ -15,6 +15,26 @@ module Imap::Backup
15
15
  @uid_validity = nil
16
16
  @messages = nil
17
17
  @version = nil
18
+ @savepoint = nil
19
+ end
20
+
21
+ def transaction(&block)
22
+ fail_in_transaction!(message: "Serializer::Imap: nested transactions are not supported")
23
+
24
+ ensure_loaded
25
+ @savepoint = {messages: messages.dup, uid_validity: uid_validity}
26
+
27
+ block.call
28
+
29
+ @savepoint = nil
30
+ save
31
+ end
32
+
33
+ def rollback
34
+ fail_outside_transaction!
35
+
36
+ @messages = @savepoint[:messages]
37
+ @uid_validity = @savepoint[:uid_validity]
18
38
  end
19
39
 
20
40
  def pathname
@@ -33,7 +53,7 @@ module Imap::Backup
33
53
  true
34
54
  end
35
55
 
36
- def append(uid, length, flags = [])
56
+ def append(uid, length, flags: [])
37
57
  offset =
38
58
  if messages.empty?
39
59
  0
@@ -109,14 +129,16 @@ module Imap::Backup
109
129
  end
110
130
 
111
131
  def save
132
+ return if @savepoint
133
+
112
134
  ensure_loaded
113
135
 
114
136
  raise "Cannot save metadata without a uid_validity" if !uid_validity
115
137
 
116
138
  data = {
117
- version: @version,
118
- uid_validity: @uid_validity,
119
- messages: @messages.map(&:to_h)
139
+ version: version,
140
+ uid_validity: uid_validity,
141
+ messages: messages.map(&:to_h)
120
142
  }
121
143
  content = data.to_json
122
144
  File.open(pathname, "w") { |f| f.write content }
@@ -162,5 +184,13 @@ module Imap::Backup
162
184
  def mbox
163
185
  @mbox ||= Serializer::Mbox.new(folder_path)
164
186
  end
187
+
188
+ def fail_in_transaction!(message: "Serializer::Imap: method not supported inside trasactions")
189
+ raise message if @savepoint
190
+ end
191
+
192
+ def fail_outside_transaction!
193
+ raise "This method can only be called inside a transaction" if !@savepoint
194
+ end
165
195
  end
166
196
  end
@@ -3,9 +3,25 @@ module Imap; end
3
3
  module Imap::Backup
4
4
  class Serializer::Mbox
5
5
  attr_reader :folder_path
6
+ attr_reader :savepoint
6
7
 
7
8
  def initialize(folder_path)
8
9
  @folder_path = folder_path
10
+ @savepoint = nil
11
+ end
12
+
13
+ def transaction(&block)
14
+ fail_in_transaction!(message: "Nested transactions are not supported")
15
+
16
+ @savepoint = {length: length}
17
+ block.call
18
+ @savepoint = nil
19
+ end
20
+
21
+ def rollback
22
+ fail_outside_transaction!
23
+
24
+ rewind(@savepoint[:length])
9
25
  end
10
26
 
11
27
  def valid?
@@ -31,6 +47,10 @@ module Imap::Backup
31
47
  File.unlink(pathname)
32
48
  end
33
49
 
50
+ def exist?
51
+ File.exist?(pathname)
52
+ end
53
+
34
54
  def length
35
55
  return nil if !exist?
36
56
 
@@ -51,18 +71,24 @@ module Imap::Backup
51
71
  end
52
72
  end
53
73
 
74
+ def touch
75
+ File.open(pathname, "a") {}
76
+ end
77
+
78
+ private
79
+
54
80
  def rewind(length)
55
81
  File.open(pathname, File::RDWR | File::CREAT, 0o644) do |f|
56
82
  f.truncate(length)
57
83
  end
58
84
  end
59
85
 
60
- def touch
61
- File.open(pathname, "a") {}
86
+ def fail_in_transaction!(message: "Method not supported inside trasactions")
87
+ raise message if savepoint
62
88
  end
63
89
 
64
- def exist?
65
- File.exist?(pathname)
90
+ def fail_outside_transaction!
91
+ raise "This method can only be called inside a transaction" if !savepoint
66
92
  end
67
93
  end
68
94
  end
@@ -28,16 +28,36 @@ module Imap::Backup
28
28
 
29
29
  attr_reader :folder
30
30
  attr_reader :path
31
+ attr_reader :dirty
31
32
 
32
33
  def initialize(path, folder)
33
34
  @path = path
34
35
  @folder = folder
35
36
  @validated = nil
37
+ @dirty = nil
38
+ end
39
+
40
+ def transaction(&block)
41
+ fail_in_transaction!(:transaction, message: "nested transactions are not supported")
42
+
43
+ validate!
44
+ @dirty = {append: []}
45
+
46
+ block.call
47
+
48
+ if dirty[:append].any?
49
+ appender = Serializer::Appender.new(folder: sanitized, imap: imap, mbox: mbox)
50
+ appender.multi(dirty[:append])
51
+ end
52
+
53
+ @dirty = nil
36
54
  end
37
55
 
38
56
  # Returns true if there are existing, valid files
39
57
  # false otherwise (in which case any existing files are deleted)
40
58
  def validate!
59
+ fail_in_transaction!(:validate!)
60
+
41
61
  return true if @validated
42
62
 
43
63
  optionally_migrate2to3
@@ -53,6 +73,8 @@ module Imap::Backup
53
73
  end
54
74
 
55
75
  def check_integrity!
76
+ fail_in_transaction!(:check_integrity!)
77
+
56
78
  if !imap.valid?
57
79
  message = ".imap file '#{imap.pathname}' is corrupt"
58
80
  raise FolderIntegrityError, message
@@ -104,6 +126,8 @@ module Imap::Backup
104
126
  end
105
127
 
106
128
  def delete
129
+ fail_in_transaction!(:delete)
130
+
107
131
  imap.delete
108
132
  @imap = nil
109
133
  mbox.delete
@@ -111,6 +135,7 @@ module Imap::Backup
111
135
  end
112
136
 
113
137
  def apply_uid_validity(value)
138
+ fail_in_transaction!(:apply_uid_validity)
114
139
  validate!
115
140
 
116
141
  case
@@ -126,19 +151,26 @@ module Imap::Backup
126
151
  end
127
152
 
128
153
  def force_uid_validity(value)
154
+ fail_in_transaction!(:force_uid_validity)
129
155
  validate!
130
156
 
131
157
  internal_force_uid_validity(value)
132
158
  end
133
159
 
134
160
  def append(uid, message, flags)
135
- validate!
161
+ if dirty
162
+ dirty[:append] << {uid: uid, message: message, flags: flags}
163
+ else
164
+ validate!
136
165
 
137
- appender = Serializer::Appender.new(folder: sanitized, imap: imap, mbox: mbox)
138
- appender.run(uid: uid, message: message, flags: flags)
166
+ appender = Serializer::Appender.new(folder: sanitized, imap: imap, mbox: mbox)
167
+ appender.single(uid: uid, message: message, flags: flags)
168
+ end
139
169
  end
140
170
 
141
171
  def update(uid, flags: nil)
172
+ fail_in_transaction!(:update)
173
+
142
174
  message = imap.get(uid)
143
175
  return if !message
144
176
 
@@ -147,17 +179,21 @@ module Imap::Backup
147
179
  end
148
180
 
149
181
  def each_message(required_uids = nil, &block)
182
+ fail_in_transaction!(:each_message)
183
+
184
+ return enum_for(:each_message, required_uids) if !block
185
+
150
186
  required_uids ||= uids
151
187
 
152
188
  validate!
153
189
 
154
- return enum_for(:each_message, required_uids) if !block
155
-
156
190
  enumerator = Serializer::MessageEnumerator.new(imap: imap)
157
191
  enumerator.run(uids: required_uids, &block)
158
192
  end
159
193
 
160
194
  def filter(&block)
195
+ fail_in_transaction!(:filter)
196
+
161
197
  temp_name = Serializer::UnusedNameFinder.new(serializer: self).run
162
198
  temp_folder_path = self.class.folder_path_for(path: path, folder: temp_name)
163
199
  new_mbox = Serializer::Mbox.new(temp_folder_path)
@@ -167,7 +203,7 @@ module Imap::Backup
167
203
  enumerator = Serializer::MessageEnumerator.new(imap: imap)
168
204
  enumerator.run(uids: uids) do |message|
169
205
  keep = block.call(message)
170
- appender.run(uid: message.uid, message: message.body, flags: message.flags) if keep
206
+ appender.single(uid: message.uid, message: message.body, flags: message.flags) if keep
171
207
  end
172
208
  imap.delete
173
209
  new_imap.rename imap.folder_path
@@ -251,5 +287,13 @@ module Imap::Backup
251
287
  rename new_name
252
288
  new_name
253
289
  end
290
+
291
+ def fail_in_transaction!(method, message: "not supported inside trasactions")
292
+ raise "Serializer##{method} #{message}" if dirty
293
+ end
294
+
295
+ def fail_outside_transaction!(method)
296
+ raise "Serializer##{method} can only be called inside a transaction" if !dirty
297
+ end
254
298
  end
255
299
  end
@@ -39,6 +39,7 @@ module Imap::Backup
39
39
  MENU
40
40
  account_items menu
41
41
  add_account_item menu
42
+ toggle_delay_download_writes menu
42
43
  if config.modified?
43
44
  menu.choice("save and exit") do
44
45
  config.save
@@ -70,6 +71,16 @@ module Imap::Backup
70
71
  end
71
72
  end
72
73
 
74
+ def toggle_delay_download_writes(menu)
75
+ new_value = config.delay_download_writes ? false : true
76
+ modified = config.delay_download_writes_modified ? " *" : ""
77
+ change = config.delay_download_writes ? "don't delay" : "delay"
78
+ menu_item = "#{change} download writes#{modified}"
79
+ menu.choice(menu_item) do
80
+ config.delay_download_writes = new_value
81
+ end
82
+ end
83
+
73
84
  def default_account_config(username)
74
85
  Imap::Backup::Account.new(
75
86
  username: username,
@@ -1,9 +1,9 @@
1
1
  module Imap; end
2
2
 
3
3
  module Imap::Backup
4
- MAJOR = 10
4
+ MAJOR = 11
5
5
  MINOR = 0
6
- REVISION = 1
7
- PRE = nil
6
+ REVISION = 0
7
+ PRE = "rc1".freeze
8
8
  VERSION = [MAJOR, MINOR, REVISION, PRE].compact.map(&:to_s).join(".")
9
9
  end
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: imap-backup
3
3
  version: !ruby/object:Gem::Version
4
- version: 10.0.1
4
+ version: 11.0.0.rc1
5
5
  platform: ruby
6
6
  authors:
7
7
  - Joe Yates
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2023-07-16 00:00:00.000000000 Z
11
+ date: 2023-07-18 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: highline
@@ -305,9 +305,9 @@ required_ruby_version: !ruby/object:Gem::Requirement
305
305
  version: '2.6'
306
306
  required_rubygems_version: !ruby/object:Gem::Requirement
307
307
  requirements:
308
- - - ">="
308
+ - - ">"
309
309
  - !ruby/object:Gem::Version
310
- version: '0'
310
+ version: 1.3.1
311
311
  requirements: []
312
312
  rubygems_version: 3.3.7
313
313
  signing_key: