From 8613843db626fa8b75a22872b2c7fc19e74665bf Mon Sep 17 00:00:00 2001 From: Gwen Mittertreiner Date: Tue, 11 Jun 2019 12:51:55 -0700 Subject: [PATCH] Implement the _.hidden File Attributes on Windows Windows has a specific attribute to mark a file as hidden as opposed to prefixing ".". This allows setting and getting the attribute. --- Foundation/FileManager.swift | 26 ++++++++++++++++++-- TestFoundation/TestFileManager.swift | 36 ++++++++++++++++++++++++---- 2 files changed, 56 insertions(+), 6 deletions(-) diff --git a/Foundation/FileManager.swift b/Foundation/FileManager.swift index 48a492f0b6..45ca0e2d5f 100644 --- a/Foundation/FileManager.swift +++ b/Foundation/FileManager.swift @@ -16,6 +16,9 @@ fileprivate let UF_HIDDEN: Int32 = 1 #endif import CoreFoundation +#if os(Windows) +import MSVCRT +#endif open class FileManager : NSObject { @@ -400,7 +403,6 @@ open class FileManager : NSObject { newAccessDate = providedDate } #endif - case .immutable: fallthrough case ._userImmutable: prepareToSetOrUnsetFlag(UF_IMMUTABLE) @@ -412,7 +414,21 @@ open class FileManager : NSObject { prepareToSetOrUnsetFlag(UF_APPEND) case ._hidden: +#if os(Windows) + let attrs = try windowsFileAttributes(atPath: path).dwFileAttributes + guard let isHidden = attributeValues[attribute] as? Bool else { + fatalError("Can't set \(attribute) to \(attributeValues[attribute] as Any?)") + } + + let hiddenAttrs = isHidden + ? attrs | DWORD(FILE_ATTRIBUTE_HIDDEN) + : attrs & DWORD(bitPattern: ~FILE_ATTRIBUTE_HIDDEN) + guard path.withCString(encodedAs: UTF16.self, { SetFileAttributesW($0, hiddenAttrs) }) else { + fatalError("Couldn't set \(path) to be hidden") + } +#else prepareToSetOrUnsetFlag(UF_HIDDEN) +#endif // FIXME: On Darwin, these can be set with setattrlist(); and of course chown/chgrp on other OSes. case .ownerAccountID: fallthrough @@ -531,7 +547,8 @@ open class FileManager : NSObject { #if os(Windows) result[.deviceIdentifier] = NSNumber(value: UInt64(s.st_rdev)) - let type = FileAttributeType(attributes: try windowsFileAttributes(atPath: path), atPath: path) + let attributes = try windowsFileAttributes(atPath: path) + let type = FileAttributeType(attributes: attributes, atPath: path) #else if let pwd = getpwuid(s.st_uid), pwd.pointee.pw_name != nil { let name = String(cString: pwd.pointee.pw_name) @@ -566,6 +583,11 @@ open class FileManager : NSObject { result[.appendOnly] = NSNumber(value: true) } #endif + +#if os(Windows) + let attrs = attributes.dwFileAttributes + result[._hidden] = attrs & DWORD(FILE_ATTRIBUTE_HIDDEN) != 0 +#endif result[.ownerAccountID] = NSNumber(value: UInt64(s.st_uid)) result[.groupOwnerAccountID] = NSNumber(value: UInt64(s.st_gid)) diff --git a/TestFoundation/TestFileManager.swift b/TestFoundation/TestFileManager.swift index 0c97944443..ccf4625edf 100644 --- a/TestFoundation/TestFileManager.swift +++ b/TestFoundation/TestFileManager.swift @@ -496,15 +496,18 @@ class TestFileManager : XCTestCase { func test_directoryEnumerator() { let fm = FileManager.default let basePath = NSTemporaryDirectory() + "testdir\(NSUUID().uuidString)/" - let subDirs1 = basePath + "subdir1/subdir2/.hiddenDir/subdir3/" + let hiddenDir1 = basePath + "subdir1/subdir2/.hiddenDir/" + let subDirs1 = hiddenDir1 + "subdir3/" let itemPath1 = basePath + "itemFile1" #if os(Windows) // Filenames ending with '.' are not valid on Windows, so don't bother testing them - let subDirs2 = basePath + "subdir1/subdir2/subdir4.app/subdir5/.subdir6.ext/subdir7.ext/" + let hiddenDir2 = basePath + "subdir1/subdir2/subdir4.app/subdir5/.subdir6.ext/" + let subDirs2 = hiddenDir2 + "subdir7.ext/" let itemPath2 = subDirs1 + "itemFile2" let itemPath3 = subDirs1 + "itemFile3.ext" #else - let subDirs2 = basePath + "subdir1/subdir2/subdir4.app/subdir5./.subdir6.ext/subdir7.ext./" + let hiddenDir2 = basePath + "subdir1/subdir2/subdir4.app/subdir5./.subdir6.ext/" + let subDirs2 = hiddenDir2 + "subdir7.ext./" let itemPath2 = subDirs1 + "itemFile2." let itemPath3 = subDirs1 + "itemFile3.ext." #endif @@ -555,10 +558,35 @@ class TestFileManager : XCTestCase { XCTAssertNotNil(try? fm.createDirectory(atPath: subDirs1, withIntermediateDirectories: true, attributes: nil)) XCTAssertNotNil(try? fm.createDirectory(atPath: subDirs2, withIntermediateDirectories: true, attributes: nil)) - for filename in [itemPath1, itemPath2, itemPath3, hiddenItem1, hiddenItem2, hiddenItem3, hiddenItem4] { + for filename in [itemPath1, itemPath2, itemPath3] { XCTAssertTrue(fm.createFile(atPath: filename, contents: Data(), attributes: nil), "Cant create file '\(filename)'") } + var resourceValues = URLResourceValues() + resourceValues.isHidden = true + for filename in [ hiddenItem1, hiddenItem2, hiddenItem3, hiddenItem4] { + XCTAssertTrue(fm.createFile(atPath: filename, contents: Data(), attributes: nil), "Cant create file '\(filename)'") +#if os(Windows) + do { + var url = URL(fileURLWithPath: filename) + try url.setResourceValues(resourceValues) + } catch { + XCTFail("Couldn't make \(filename) a hidden file") + } +#endif + } + +#if os(Windows) + do { + var hiddenURL1 = URL(fileURLWithPath: hiddenDir1) + var hiddenURL2 = URL(fileURLWithPath: hiddenDir2) + try hiddenURL1.setResourceValues(resourceValues) + try hiddenURL2.setResourceValues(resourceValues) + } catch { + XCTFail("Couldn't make \(hiddenDir1) and \(hiddenDir2) hidden directories") + } +#endif + if let foundItems = directoryItems(options: []) { XCTAssertEqual(foundItems.count, fileLevels.count) for (name, level) in foundItems {