Skip to content

Instantly share code, notes, and snippets.

@murachue
Last active August 29, 2015 14:06
Show Gist options
  • Save murachue/e7b26cec27ff9d21bd74 to your computer and use it in GitHub Desktop.
Save murachue/e7b26cec27ff9d21bd74 to your computer and use it in GitHub Desktop.
Timestamp modifier for Windows, like "touch" but this also can change creation time and directory's. fiddleの練習も兼ねて適当に書いた。
# coding: utf-8
require "fiddle/import"
require "time"
require "optparse"
module Kernel32
extend Fiddle::Importer
dlload "kernel32.dll"
extern "int CreateFile(void*, unsigned int, unsigned int, void*, unsigned int, unsigned int, int)", :stdcall
extern "int SystemTimeToFileTime(void*, void*)", :stdcall
extern "int SetFileTime(unsigned int, unsigned long long*, unsigned long long*, unsigned long long*)", :stdcall
extern "int CloseHandle(int)", :stdcall
extern "unsigned int GetLastError()", :stdcall
GENERIC_READ = 0x80000000
GENERIC_WRITE = 0x40000000
FILE_WRITE_ATTRIBUTES = 0x00000100
FILE_SHARE_READ = 0x00000001
OPEN_EXISTING = 0x00000003
FILE_ATTRIBUTE_NORMAL = 0x00000080
FILE_FLAG_BACKUP_SEMANTICS = 0x02000000
INVALID_HANDLE_VALUE = -1
SYSTEMTIME = struct([
"unsigned short wYear",
"unsigned short wMonth",
"unsigned short wDayOfWeek",
"unsigned short wDay",
"unsigned short wHour",
"unsigned short wMinute",
"unsigned short wSecond",
"unsigned short wMilliseconds",
])
SizeofFILETIME = sizeof("unsigned long long")
end
include Kernel32
# TODO: Why I need to specify Kernel32. in some cases (call func) even after included it?
def set_entry_time fn, tt
#puts proc{|s| s.methods.select{|e|e.to_s.end_with? "time"}.map{|e|"%s=%s" % [e, s.send(e)]}.join(" ")}.call File.stat(fn)
hd = Kernel32.CreateFile(fn, FILE_WRITE_ATTRIBUTES, FILE_SHARE_READ, 0, OPEN_EXISTING, FILE_FLAG_BACKUP_SEMANTICS, 0)
if hd == INVALID_HANDLE_VALUE
puts "CreateFile returns %08X: %08X" % [hd, Kernel32.GetLastError]
return false
end
begin
st = SYSTEMTIME.malloc
st.wYear, st.wMonth, st.wDay, st.wHour, st.wMinute, st.wSecond, st.wMilliseconds = tt.year, tt.mon, tt.day, tt.hour, tt.min, tt.sec, tt.usec / 1000
ft = " " * SizeofFILETIME # prepare buffer
r = Kernel32.SystemTimeToFileTime(st, ft)
#puts "%s => %d" % [tt.to_s, ft.unpack("Q")[0]]
r = Kernel32.SetFileTime(hd, ft, ft, ft)
if r == 0
puts "SetFileTime returns %08X: %08X" % [r, Kernel32.GetLastError]
return false
end
ensure
r = Kernel32.CloseHandle(hd)
if r == 0
puts "CloseHandle returns %08X: %08X" % [r, Kernel32.GetLastError]
return false
end
end
#puts proc{|s| s.methods.select{|e|e.to_s.end_with? "time"}.map{|e|"%s=%s" % [e, s.send(e)]}.join(" ")}.call File.stat(fn)
return true
end
def set_entries_time fn, tt
if FileTest.directory? fn
# set inner file time first (or overriding already set directory time by rewriting files in the directory...?)
if @recurse
Dir.entries(fn).each { |efn|
next if efn == "." or efn == ".."
set_entries_time fn + "\\" + efn, tt # delimiter is "\\" because that path also used by Win32API, thanks for accepting "\\" on Ruby-win32!
}
end
end
if not set_entry_time fn, tt
if @abortonerror
raise "Error occurred while setting time of #{fn}; aborting."
else
$stderr.puts "Error occurred while setting time of #{fn}; skipping."
end
end
end
def main
@recurse = false
@abortonerror = true
opts = OptionParser.new
opts.on("-r", "--recurse") { |v| @recurse = true }
opts.on("-i", "--ignore-error") { |v| @abortonerror = false }
opts.parse!(ARGV)
set_entries_time ARGV[0], Time.parse(ARGV[1]).utc
end
main
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment