Add functionality to restore file timestamps.

There has been an option in `Zip::File` (`:restore_times`) for a long
time, but it seems it has never worked. Firstly the actual timestamp of
an added file wasn't being saved, and secondly an extracted file wasn't
having its timestamp set correctly.

This commit fixes both of those issues, and adds tests to make sure.
This commit is contained in:
Robert Haines 2019-10-06 19:37:03 +01:00
parent 378293539d
commit 8c694d38ee
2 changed files with 70 additions and 6 deletions

View File

@ -406,16 +406,22 @@ module Zip
@unix_uid = stat.uid @unix_uid = stat.uid
@unix_gid = stat.gid @unix_gid = stat.gid
@unix_perms = stat.mode & 0o7777 @unix_perms = stat.mode & 0o7777
mtime = stat.mtime
@time = ::Zip::DOSTime.local(mtime.year, mtime.month, mtime.day, mtime.hour, mtime.min, mtime.sec)
end end
def set_unix_permissions_on_path(dest_path) def set_unix_attributes_on_path(dest_path)
# BUG: does not update timestamps into account
# ignore setuid/setgid bits by default. honor if @restore_ownership # ignore setuid/setgid bits by default. honor if @restore_ownership
unix_perms_mask = 0o1777 unix_perms_mask = 0o1777
unix_perms_mask = 0o7777 if @restore_ownership unix_perms_mask = 0o7777 if @restore_ownership
::FileUtils.chmod(@unix_perms & unix_perms_mask, dest_path) if @restore_permissions && @unix_perms ::FileUtils.chmod(@unix_perms & unix_perms_mask, dest_path) if @restore_permissions && @unix_perms
::FileUtils.chown(@unix_uid, @unix_gid, dest_path) if @restore_ownership && @unix_uid && @unix_gid && ::Process.egid == 0 ::FileUtils.chown(@unix_uid, @unix_gid, dest_path) if @restore_ownership && @unix_uid && @unix_gid && ::Process.egid == 0
# File::utimes()
# Restore the timestamp on a file. This will either have come from the
# original source file that was copied into the archive, or from the
# creation date of the archive if there was no original source file.
::FileUtils.touch(dest_path, mtime: time) if @restore_times
end end
def set_extra_attributes_on_path(dest_path) # :nodoc: def set_extra_attributes_on_path(dest_path) # :nodoc:
@ -423,7 +429,7 @@ module Zip
case @fstype case @fstype
when ::Zip::FSTYPE_UNIX when ::Zip::FSTYPE_UNIX
set_unix_permissions_on_path(dest_path) set_unix_attributes_on_path(dest_path)
end end
end end
@ -601,8 +607,6 @@ module Zip
end end
::File.open(dest_path, 'wb') do |os| ::File.open(dest_path, 'wb') do |os|
get_input_stream do |is| get_input_stream do |is|
set_extra_attributes_on_path(dest_path)
bytes_written = 0 bytes_written = 0
warned = false warned = false
buf = ''.dup buf = ''.dup
@ -621,6 +625,8 @@ module Zip
end end
end end
end end
set_extra_attributes_on_path(dest_path)
end end
def create_directory(dest_path) def create_directory(dest_path)

58
test/file_options_test.rb Normal file
View File

@ -0,0 +1,58 @@
require 'test_helper'
class FileOptionsTest < MiniTest::Test
ZIPPATH = ::File.join(Dir.tmpdir, 'options.zip').freeze
TXTPATH = ::File.expand_path(::File.join('data', 'file1.txt'), __dir__).freeze
EXTPATH_1 = ::File.join(Dir.tmpdir, 'extracted_1.txt').freeze
EXTPATH_2 = ::File.join(Dir.tmpdir, 'extracted_2.txt').freeze
ENTRY_1 = 'entry_1.txt'.freeze
ENTRY_2 = 'entry_2.txt'.freeze
def teardown
::File.unlink(ZIPPATH) if ::File.exist?(ZIPPATH)
::File.unlink(EXTPATH_1) if ::File.exist?(EXTPATH_1)
::File.unlink(EXTPATH_2) if ::File.exist?(EXTPATH_2)
end
def test_restore_times_true
::Zip::File.open(ZIPPATH, true) do |zip|
zip.add(ENTRY_1, TXTPATH)
zip.add_stored(ENTRY_2, TXTPATH)
end
::Zip::File.open(ZIPPATH, false, restore_times: true) do |zip|
zip.extract(ENTRY_1, EXTPATH_1)
zip.extract(ENTRY_2, EXTPATH_2)
end
assert_time_equal(::File.mtime(TXTPATH), ::File.mtime(EXTPATH_1))
assert_time_equal(::File.mtime(TXTPATH), ::File.mtime(EXTPATH_2))
end
def test_restore_times_false
::Zip::File.open(ZIPPATH, true) do |zip|
zip.add(ENTRY_1, TXTPATH)
zip.add_stored(ENTRY_2, TXTPATH)
end
::Zip::File.open(ZIPPATH, false, restore_times: false) do |zip|
zip.extract(ENTRY_1, EXTPATH_1)
zip.extract(ENTRY_2, EXTPATH_2)
end
assert_time_equal(::Time.now, ::File.mtime(EXTPATH_1))
assert_time_equal(::Time.now, ::File.mtime(EXTPATH_2))
end
private
# Method to compare file times. DOS times only have 2 second accuracy.
def assert_time_equal(expected, actual)
assert_equal(expected.year, actual.year)
assert_equal(expected.month, actual.month)
assert_equal(expected.day, actual.day)
assert_equal(expected.hour, actual.hour)
assert_equal(expected.min, actual.min)
assert_in_delta(expected.sec, actual.sec, 1)
end
end