Index: src/OFFileURLHandler.m ================================================================== --- src/OFFileURLHandler.m +++ src/OFFileURLHandler.m @@ -22,12 +22,17 @@ #ifdef HAVE_DIRENT_H # include #endif #include "unistd_wrapper.h" +#import "platform.h" #ifdef HAVE_SYS_STAT_H # include +#endif +#include +#ifdef OF_WINDOWS +# include #endif #ifdef HAVE_PWD_H # include #endif @@ -107,10 +112,11 @@ #if !defined(HAVE_READDIR_R) && defined(OF_HAVE_THREADS) && !defined(OF_WINDOWS) static OFMutex *readdirMutex; #endif #ifdef OF_WINDOWS +static int (*func__wutime64)(const wchar_t *, struct __utimbuf64 *); static WINAPI BOOLEAN (*func_CreateSymbolicLinkW)(LPCWSTR, LPCWSTR, DWORD); static WINAPI BOOLEAN (*func_CreateHardLinkW)(LPCWSTR, LPCWSTR, LPSECURITY_ATTRIBUTES); #endif @@ -511,10 +517,14 @@ #if !defined(HAVE_READDIR_R) && !defined(OF_WINDOWS) && defined(OF_HAVE_THREADS) readdirMutex = [[OFMutex alloc] init]; #endif #ifdef OF_WINDOWS + if ((module = LoadLibrary("msvcrt.dll")) != NULL) + func__wutime64 = (int (*)(const wchar_t *, + struct __utimbuf64 *))GetProcAddress(module, "_wutime64"); + if ((module = LoadLibrary("kernel32.dll")) != NULL) { func_CreateSymbolicLinkW = (WINAPI BOOLEAN (*)(LPCWSTR, LPCWSTR, DWORD)) GetProcAddress(module, "CreateSymbolicLinkW"); func_CreateHardLinkW = @@ -595,10 +605,89 @@ objc_autoreleasePoolPop(pool); return ret; } + +- (void)of_setLastAccessDate: (OFDate *)lastAccessDate + andModificationDate: (OFDate *)modificationDate + ofItemAtURL: (OFURL *)URL + attributes: (of_file_attributes_t)attributes +{ + OFString *path = URL.fileSystemRepresentation; + of_file_attribute_key_t attributeKey = (modificationDate != nil + ? of_file_attribute_key_modification_date + : of_file_attribute_key_last_access_date); + + if (lastAccessDate == nil) + lastAccessDate = modificationDate; + if (modificationDate == nil) + modificationDate = lastAccessDate; + +#ifdef OF_WINDOWS + if (func__wutime64 != NULL) { + struct __utimbuf64 times = { + .actime = + (__time64_t)lastAccessDate.timeIntervalSince1970, + .modtime = + (__time64_t)modificationDate.timeIntervalSince1970 + }; + + if (func__wutime64([path UTF16String], ×) != 0) + @throw [OFSetItemAttributesFailedException + exceptionWithURL: URL + attributes: attributes + failedAttribute: attributeKey + errNo: errno]; + } else { + struct _utimbuf times = { + .actime = (time_t)lastAccessDate.timeIntervalSince1970, + .modtime = + (time_t)modificationDate.timeIntervalSince1970 + }; + int status; + + if ([OFSystemInfo isWindowsNT]) + status = _wutime([path UTF16String], ×); + else + status = _utime( + [path cStringWithEncoding: [OFLocale encoding]], + ×); + + if (status != 0) + @throw [OFSetItemAttributesFailedException + exceptionWithURL: URL + attributes: attributes + failedAttribute: attributeKey + errNo: errno]; + } +#else + of_time_interval_t lastAccessTime = + lastAccessDate.timeIntervalSince1970; + of_time_interval_t modificationTime = + modificationDate.timeIntervalSince1970; + struct timeval times[2] = { + { + .tv_sec = (time_t)lastAccessTime, + .tv_usec = + (int)((lastAccessTime - times[0].tv_sec) * 1000) + }, + { + .tv_sec = (time_t)modificationTime, + .tv_usec = + (int)((modificationTime - times[1].tv_sec) * 1000) + }, + }; + + if (utimes([path cStringWithEncoding: [OFLocale encoding]], times) != 0) + @throw [OFSetItemAttributesFailedException + exceptionWithURL: URL + attributes: attributes + failedAttribute: attributeKey + errNo: errno]; +#endif +} - (void)of_setPOSIXPermissions: (OFNumber *)permissions ofItemAtURL: (OFURL *)URL attributes: (of_file_attributes_t)attributes { @@ -697,10 +786,11 @@ void *pool = objc_autoreleasePoolPush(); OFEnumerator OF_GENERIC(of_file_attribute_key_t) *keyEnumerator; OFEnumerator *objectEnumerator; of_file_attribute_key_t key; id object; + OFDate *lastAccessDate, *modificationDate; if (URL == nil) @throw [OFInvalidArgumentException exception]; if (![URL.scheme isEqual: _scheme]) @@ -709,11 +799,14 @@ keyEnumerator = [attributes keyEnumerator]; objectEnumerator = [attributes objectEnumerator]; while ((key = [keyEnumerator nextObject]) != nil && (object = [objectEnumerator nextObject]) != nil) { - if ([key isEqual: of_file_attribute_key_posix_permissions]) + if ([key isEqual: of_file_attribute_key_modification_date] || + [key isEqual: of_file_attribute_key_last_access_date]) + continue; + else if ([key isEqual: of_file_attribute_key_posix_permissions]) [self of_setPOSIXPermissions: object ofItemAtURL: URL attributes: attributes]; else if ([key isEqual: of_file_attribute_key_owner]) [self of_setOwner: object @@ -730,10 +823,21 @@ else @throw [OFNotImplementedException exceptionWithSelector: _cmd object: self]; } + + lastAccessDate = [attributes + objectForKey: of_file_attribute_key_last_access_date]; + modificationDate = [attributes + objectForKey: of_file_attribute_key_modification_date]; + + if (lastAccessDate != nil || modificationDate != nil) + [self of_setLastAccessDate: lastAccessDate + andModificationDate: modificationDate + ofItemAtURL: URL + attributes: attributes]; objc_autoreleasePoolPop(pool); } - (bool)fileExistsAtURL: (OFURL *)URL Index: utils/ofarc/LHAArchive.m ================================================================== --- utils/ofarc/LHAArchive.m +++ utils/ofarc/LHAArchive.m @@ -53,10 +53,34 @@ [[OFFileManager defaultManager] setAttributes: attributes ofItemAtPath: path]; #endif } + +static void +setModificationDate(OFString *path, OFLHAArchiveEntry *entry) +{ + OFDate *modificationDate = entry.modificationDate; + of_file_attributes_t attributes; + + if (modificationDate == nil) { + /* + * Fall back to the original date if we have no modification + * date, as the modification date is a UNIX extension. + */ + modificationDate = entry.date; + + if (modificationDate == nil) + return; + } + + attributes = [OFDictionary + dictionaryWithObject: modificationDate + forKey: of_file_attribute_key_modification_date]; + [[OFFileManager defaultManager] setAttributes: attributes + ofItemAtPath: path]; +} @implementation LHAArchive + (void)initialize { if (self == [LHAArchive class]) @@ -283,10 +307,11 @@ if ([fileName hasSuffix: @"/"]) { [fileManager createDirectoryAtPath: outFileName createParents: true]; setPermissions(outFileName, entry); + setModificationDate(outFileName, entry); if (app->_outputLevel >= 0) { [of_stdout writeString: @"\r"]; [of_stdout writeLine: OF_LOCALIZED( @"extracting_file_done", @@ -339,10 +364,13 @@ @"file", fileName, @"percent", percentString)]; } } + [output close]; + setModificationDate(outFileName, entry); + if (app->_outputLevel >= 0) { [of_stdout writeString: @"\r"]; [of_stdout writeLine: OF_LOCALIZED( @"extracting_file_done", @"Extracting %[file]... done", Index: utils/ofarc/TarArchive.m ================================================================== --- utils/ofarc/TarArchive.m +++ utils/ofarc/TarArchive.m @@ -41,10 +41,26 @@ [[OFFileManager defaultManager] setAttributes: attributes ofItemAtPath: path]; #endif } + +static void +setModificationDate(OFString *path, OFTarArchiveEntry *entry) +{ + OFDate *modificationDate = entry.modificationDate; + of_file_attributes_t attributes; + + if (modificationDate == nil) + return; + + attributes = [OFDictionary + dictionaryWithObject: modificationDate + forKey: of_file_attribute_key_modification_date]; + [[OFFileManager defaultManager] setAttributes: attributes + ofItemAtPath: path]; +} @implementation TarArchive + (void)initialize { if (self == [TarArchive class]) @@ -304,10 +320,11 @@ (type == OF_TAR_ARCHIVE_ENTRY_TYPE_FILE && [fileName hasSuffix: @"/"])) { [fileManager createDirectoryAtPath: outFileName createParents: true]; setPermissions(outFileName, entry); + setModificationDate(outFileName, entry); if (app->_outputLevel >= 0) { [of_stdout writeString: @"\r"]; [of_stdout writeLine: OF_LOCALIZED( @"extracting_file_done", @@ -360,10 +377,13 @@ @"file", fileName, @"percent", percentString)]; } } + [output close]; + setModificationDate(outFileName, entry); + if (app->_outputLevel >= 0) { [of_stdout writeString: @"\r"]; [of_stdout writeLine: OF_LOCALIZED( @"extracting_file_done", @"Extracting %[file]... done", Index: utils/ofarc/ZIPArchive.m ================================================================== --- utils/ofarc/ZIPArchive.m +++ utils/ofarc/ZIPArchive.m @@ -54,10 +54,26 @@ [[OFFileManager defaultManager] setAttributes: attributes ofItemAtPath: path]; } #endif } + +static void +setModificationDate(OFString *path, OFZIPArchiveEntry *entry) +{ + OFDate *modificationDate = entry.modificationDate; + of_file_attributes_t attributes; + + if (modificationDate == nil) + return; + + attributes = [OFDictionary + dictionaryWithObject: modificationDate + forKey: of_file_attribute_key_modification_date]; + [[OFFileManager defaultManager] setAttributes: attributes + ofItemAtPath: path]; +} @implementation ZIPArchive + (void)initialize { if (self == [ZIPArchive class]) @@ -260,10 +276,11 @@ if ([fileName hasSuffix: @"/"]) { [fileManager createDirectoryAtPath: outFileName createParents: true]; setPermissions(outFileName, entry); + setModificationDate(outFileName, entry); if (app->_outputLevel >= 0) { [of_stdout writeString: @"\r"]; [of_stdout writeLine: OF_LOCALIZED( @"extracting_file_done", @@ -316,10 +333,13 @@ @"file", fileName, @"percent", percentString)]; } } + [output close]; + setModificationDate(outFileName, entry); + if (app->_outputLevel >= 0) { [of_stdout writeString: @"\r"]; [of_stdout writeLine: OF_LOCALIZED( @"extracting_file_done", @"Extracting %[file]... done",