Skip to content

Commit 0fbc4ac

Browse files
committed
Use file in XDG_STATE_HOME directory to store last update check timestamp.
1 parent 05811f8 commit 0fbc4ac

File tree

5 files changed

+57
-21
lines changed

5 files changed

+57
-21
lines changed

lib/rubygems/config_file.rb

Lines changed: 27 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -371,23 +371,44 @@ def backtrace
371371
@backtrace || $DEBUG
372372
end
373373

374-
# Check config file is writable. Creates empty file if not present to ensure we can write to it.
375-
def config_file_writable?
376-
if File.exist?(config_file_name)
377-
File.writable?(config_file_name)
374+
# Check state file is writable. Creates empty file if not present to ensure we can write to it.
375+
def state_file_writable?
376+
if File.exist?(state_file_name)
377+
File.writable?(state_file_name)
378378
else
379379
require "fileutils"
380-
FileUtils.mkdir_p File.dirname(config_file_name)
381-
File.open(config_file_name, "w") {}
380+
FileUtils.mkdir_p File.dirname(state_file_name)
381+
File.open(state_file_name, "w") {}
382382
true
383383
end
384+
rescue Errno::EACCES
385+
false
384386
end
385387

386388
# The name of the configuration file.
387389
def config_file_name
388390
@config_file_name || Gem.config_file
389391
end
390392

393+
# The name of the state file.
394+
def state_file_name
395+
@state_file_name || Gem.state_file
396+
end
397+
398+
# Reads time of last update check from state file
399+
def last_update_check
400+
if File.readable?(state_file_name)
401+
File.read(state_file_name).to_i
402+
else
403+
0
404+
end
405+
end
406+
407+
# Writes time of last update check to state file
408+
def last_update_check=(timestamp)
409+
File.write(state_file_name, timestamp.to_s) if state_file_writable?
410+
end
411+
391412
# Delegates to @hash
392413
def each(&block)
393414
hash = @hash.dup

lib/rubygems/defaults.rb

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -133,6 +133,13 @@ def self.config_file
133133
@config_file ||= find_config_file.tap(&Gem::UNTAINT)
134134
end
135135

136+
##
137+
# The path to standard location of the user's state file.
138+
139+
def self.state_file
140+
@state_file ||= File.join(Gem.state_home, "gem", "last_update_check").tap(&Gem::UNTAINT)
141+
end
142+
136143
##
137144
# The path to standard location of the user's cache directory.
138145

@@ -147,6 +154,13 @@ def self.data_home
147154
@data_home ||= (ENV["XDG_DATA_HOME"] || File.join(Gem.user_home, ".local", "share"))
148155
end
149156

157+
##
158+
# The path to standard location of the user's state directory.
159+
160+
def self.state_home
161+
@data_home ||= (ENV["XDG_STATE_HOME"] || File.join(Gem.user_home, ".local", "state"))
162+
end
163+
150164
##
151165
# How String Gem paths should be split. Overridable for esoteric platforms.
152166

lib/rubygems/update_suggestion.rb

Lines changed: 8 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -41,24 +41,22 @@ def eglible_for_update?
4141
return false if Gem.disable_system_update_message
4242
return false if ci?
4343

44-
# check makes sense only when we can store of last try
45-
# otherwise we will not be able to prevent annoying update message
44+
# check makes sense only when we can store timestamp of last try
45+
# otherwise we will not be able to prevent "annoying" update message
4646
# on each command call
47-
return unless Gem.configuration.config_file_writable?
47+
return unless Gem.configuration.state_file_writable?
4848

4949
# load time of last check, ensure the difference is enough to repeat the suggestion
5050
check_time = Time.now.to_i
51-
last_update_check = Gem.configuration[:last_update_check] || 0
51+
last_update_check = Gem.configuration.last_update_check
5252
return false if (check_time - last_update_check) < ONE_WEEK
5353

5454
# compare current and latest version, this is the part where
5555
# latest rubygems spec is fetched from remote
56-
(Gem.rubygems_version < Gem.latest_rubygems_version).tap do |eglible|
57-
if eglible
58-
# store the time of last successful check into config file
59-
Gem.configuration[:last_update_check] = check_time
60-
Gem.configuration.write
61-
end
56+
if (Gem.rubygems_version < Gem.latest_rubygems_version)
57+
# store the time of last successful check into state file
58+
Gem.configuration.last_update_check = check_time
59+
return true
6260
end
6361
rescue # don't block install command on any problem
6462
false

test/rubygems/helper.rb

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -307,6 +307,7 @@ def setup
307307
ENV["XDG_CACHE_HOME"] = nil
308308
ENV["XDG_CONFIG_HOME"] = nil
309309
ENV["XDG_DATA_HOME"] = nil
310+
ENV["XDG_STATE_HOME"] = nil
310311
ENV["SOURCE_DATE_EPOCH"] = nil
311312
ENV["BUNDLER_VERSION"] = nil
312313
ENV["RUBYGEMS_PREVENT_UPDATE_SUGGESTION"] = "true"
@@ -327,6 +328,7 @@ def setup
327328

328329
@gemhome = File.join @tempdir, "gemhome"
329330
@userhome = File.join @tempdir, "userhome"
331+
@statehome = File.join @tempdir, "statehome"
330332
ENV["GEM_SPEC_CACHE"] = File.join @tempdir, "spec_cache"
331333

332334
@orig_ruby = if ENV["RUBY"]
@@ -361,6 +363,7 @@ def setup
361363
Gem.instance_variable_set :@user_home, nil
362364
Gem.instance_variable_set :@config_home, nil
363365
Gem.instance_variable_set :@data_home, nil
366+
Gem.instance_variable_set :@state_home, @statehome
364367
Gem.instance_variable_set :@gemdeps, nil
365368
Gem.instance_variable_set :@env_requirements_by_name, nil
366369
Gem.send :remove_instance_variable, :@ruby_version if

test/rubygems/test_gem_update_suggestion.rb

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -27,7 +27,7 @@ def self.with_eglible_environment(
2727
original_config, Gem.configuration[:prevent_update_suggestion] = Gem.configuration[:prevent_update_suggestion], nil
2828
original_env, ENV["RUBYGEMS_PREVENT_UPDATE_SUGGESTION"] = ENV["RUBYGEMS_PREVENT_UPDATE_SUGGESTION"], nil
2929
original_disable, Gem.disable_system_update_message = Gem.disable_system_update_message, nil
30-
Gem.configuration[:last_update_check] = nil
30+
Gem.configuration.last_update_check = 0
3131

3232
Gem.ui.stub :tty?, tty do
3333
Gem.stub :rubygems_version, rubygems_version do
@@ -61,10 +61,10 @@ def test_eglible_for_update
6161
with_eglible_environment(cmd: @cmd) do
6262
Time.stub :now, 123456789 do
6363
assert @cmd.eglible_for_update?
64-
assert_equal Gem.configuration[:last_update_check], 123456789
64+
assert_equal Gem.configuration.last_update_check, 123456789
6565

6666
# test last check is written to config file
67-
assert File.read(Gem.configuration.config_file_name).match("last_update_check: 123456789")
67+
assert File.read(Gem.configuration.state_file_name).match("123456789")
6868
end
6969
end
7070
end
@@ -122,15 +122,15 @@ def test_eglible_for_update_on_ci
122122

123123
def test_eglible_for_update_unwrittable_config
124124
with_eglible_environment(ci: true, cmd: @cmd) do
125-
Gem.configuration.stub :config_file_writable?, false do
125+
Gem.configuration.stub :state_file_writable?, false do
126126
refute @cmd.eglible_for_update?
127127
end
128128
end
129129
end
130130

131131
def test_eglible_for_update_notification_delay
132132
with_eglible_environment(cmd: @cmd) do
133-
Gem.configuration[:last_update_check] = Time.now.to_i
133+
Gem.configuration.last_update_check = Time.now.to_i
134134
refute @cmd.eglible_for_update?
135135
end
136136
end

0 commit comments

Comments
 (0)