Write Extend File Attributes Swift Example

Write extend file attributes swift example

Here is a possible implementation in Swift 5 as an extension for URL,
with methods to get, set, list, and remove extended attributes of
a file. (Swift 2, 3, and 4 code can be found in the edit history.)

extension URL {

/// Get extended attribute.
func extendedAttribute(forName name: String) throws -> Data {

let data = try self.withUnsafeFileSystemRepresentation { fileSystemPath -> Data in

// Determine attribute size:
let length = getxattr(fileSystemPath, name, nil, 0, 0, 0)
guard length >= 0 else { throw URL.posixError(errno) }

// Create buffer with required size:
var data = Data(count: length)

// Retrieve attribute:
let result = data.withUnsafeMutableBytes { [count = data.count] in
getxattr(fileSystemPath, name, $0.baseAddress, count, 0, 0)
}
guard result >= 0 else { throw URL.posixError(errno) }
return data
}
return data
}

/// Set extended attribute.
func setExtendedAttribute(data: Data, forName name: String) throws {

try self.withUnsafeFileSystemRepresentation { fileSystemPath in
let result = data.withUnsafeBytes {
setxattr(fileSystemPath, name, $0.baseAddress, data.count, 0, 0)
}
guard result >= 0 else { throw URL.posixError(errno) }
}
}

/// Remove extended attribute.
func removeExtendedAttribute(forName name: String) throws {

try self.withUnsafeFileSystemRepresentation { fileSystemPath in
let result = removexattr(fileSystemPath, name, 0)
guard result >= 0 else { throw URL.posixError(errno) }
}
}

/// Get list of all extended attributes.
func listExtendedAttributes() throws -> [String] {

let list = try self.withUnsafeFileSystemRepresentation { fileSystemPath -> [String] in
let length = listxattr(fileSystemPath, nil, 0, 0)
guard length >= 0 else { throw URL.posixError(errno) }

// Create buffer with required size:
var namebuf = Array<CChar>(repeating: 0, count: length)

// Retrieve attribute list:
let result = listxattr(fileSystemPath, &namebuf, namebuf.count, 0)
guard result >= 0 else { throw URL.posixError(errno) }

// Extract attribute names:
let list = namebuf.split(separator: 0).compactMap {
$0.withUnsafeBufferPointer {
$0.withMemoryRebound(to: UInt8.self) {
String(bytes: $0, encoding: .utf8)
}
}
}
return list
}
return list
}

/// Helper function to create an NSError from a Unix errno.
private static func posixError(_ err: Int32) -> NSError {
return NSError(domain: NSPOSIXErrorDomain, code: Int(err),
userInfo: [NSLocalizedDescriptionKey: String(cString: strerror(err))])
}
}

Example usage:

let fileURL = URL(fileURLWithPath: "/path/to/file")

let attr1 = "com.myCompany.myAttribute"
let attr2 = "com.myCompany.otherAttribute"

let data1 = Data([1, 2, 3, 4])
let data2 = Data([5, 6, 7, 8, 9])

do {
// Set attributes:
try fileURL.setExtendedAttribute(data: data1, forName: attr1)
try fileURL.setExtendedAttribute(data: data2, forName: attr2)

// List attributes:
let list = try fileURL.listExtendedAttributes()
print(list)
// ["com.myCompany.myAttribute", "com.myCompany.otherAttribute", "other"]

let data1a = try fileURL.extendedAttribute(forName: attr1)
print(data1a as NSData)
// <01020304>

// Remove attributes
for attr in list {
try fileURL.removeExtendedAttribute(forName: attr)
}

} catch let error {
print(error.localizedDescription)
}

Write extended file attributes

The problem is with your call to setxattr. The sizeof call can't be used. You want:

int result = setxattr(path, attrName, myDataBytes, [myData length], 0, 0);

The call to sizeof(myDataBytes) will return the size of the pointer, not the length of the data.

Swift - How to modify file metadata like kMDItemDisplayName?

Not all metadata can be changed. Much of it is not stored directly, it's derived or computed based on other metadata.

The display name for a simple file is derived from its name on disk and the system settings, like whether extensions are hidden or shown. The display name for a bundle (like an app) is slightly more complicated, but, assuming you don't find changing the contents of the bundle (which would break its code signature) acceptable, amounts to the same thing. Those are subject to the system language(s).

There are also certain folders whose names can be localized for display, but that's still based on their on-disk name.

So, to change a file's display name, change its actual name on disk.

For other properties, you can look at URL.setResourceValues(_:) and URLResourceValues to see which properties are settable. You can also look at URLResourceKey to see which are documented as "read-write".

Swift set some string value to file's attribute

The documentation says "You can set the following attributes". That means you can only set those specific attributes via those API's.

What you are really looking for are Extended Attributes, and these use a separate set of (C-style) API's.

Something like:

let directory = NSTemporaryDirectory()
let someExampleText = "some sample text goes here"
do {
try someExampleText.write(toFile: "\(directory)/test.txt", atomically: true, encoding: .utf8)
} catch let error {
print("error while writing is \(error.localizedDescription)")
}
let valueString = "setting some value here"
let result = setxattr("\(directory)/test.txt", "com.stackoverflow.test", valueString, valueString.characters.count, 0, 0)

print("result is \(result) ; errno is \(errno)")
if errno != 0
{
perror("could not save")
}

let sizeOfRetrievedValue = getxattr("\(directory)/test.txt", "com.stackoverflow.test", nil, 0, 0, 0)

var data = Data(count: sizeOfRetrievedValue)

let newResult = data.withUnsafeMutableBytes({
getxattr("\(directory)/test.txt", "com.stackoverflow.test", $0, data.count, 0, 0)
})

if let resultString = String(data: data, encoding: .utf8)
{
print("retrieved string is \(resultString)")
}

Swift extension example

Creating an extension

Add a new swift file with File > New > File... > iOS > Source > Swift File. You can call it what you want.

The general naming convention is to call it TypeName+NewFunctionality.swift.

Sample Image

Example 1 - Double

Double+Conversions.swift

import Swift // or Foundation

extension Double {

func celsiusToFahrenheit() -> Double {
return self * 9 / 5 + 32
}

func fahrenheitToCelsius() -> Double {
return (self - 32) * 5 / 9
}
}

Usage:

let boilingPointCelsius = 100.0
let boilingPointFarenheit = boilingPointCelsius.celsiusToFahrenheit()
print(boilingPointFarenheit) // 212.0

Example 2 - String

String+Shortcuts.swift

import Swift // or Foundation

extension String {

func replace(target: String, withString: String) -> String {
return self.replacingOccurrences(of: target, with: withString)
}
}

Usage:

let newString = "the old bike".replace(target: "old", withString: "new")
print(newString) // "the new bike"

Here are some more common String extensions.

Example 3 - UIColor

UIColor+CustomColor.swift

import UIKit

extension UIColor {

class var customGreen: UIColor {
let darkGreen = 0x008110
return UIColor.rgb(fromHex: darkGreen)
}

class func rgb(fromHex: Int) -> UIColor {

let red = CGFloat((fromHex & 0xFF0000) >> 16) / 0xFF
let green = CGFloat((fromHex & 0x00FF00) >> 8) / 0xFF
let blue = CGFloat(fromHex & 0x0000FF) / 0xFF
let alpha = CGFloat(1.0)

return UIColor(red: red, green: green, blue: blue, alpha: alpha)
}
}

See here also.

Usage:

view.backgroundColor = UIColor.customGreen

Sample Image

Notes

  • Once you define an extension it can be used anywhere in your app just like the built in class functions.
  • If you are not sure of exactly what the function or property syntax should look like, you can Option+click a similar built in method. For example, when I Option+clicked UIColor.greenColor I see the declaration is class func greenColor() -> UIColor. That gives me a good clue for how to set up my custom method.
  • Apple Documentation for Extensions
  • In Objective-C extensions are known as categories.


Related Topics



Leave a reply



Submit