Измените атрибут метаданных файла kMDItemDateAdded («Дата добавления») с помощью кода Swift

Мне нужно изменить атрибут метаданных файла kMDItemDateAdded («Дата добавления»).

Такая возможность отсутствует через

var attrs: [FileAttributeKey: Date] = [:]
//no ability to change FileAttributeKey.dateAdded - it is absent
attrs[FileAttributeKey.modificationDate] = Date.now 

try FileManager.default.setAttributes(attrs, ofItemAtPath: file.path)

Все, что я нашел, это код языка C:

#include <stdlib.h>
#include <string.h>
#include <sys/attr.h>
#include <unistd.h>

/*
 * Set kMDItemDateAdded of path.
 *
 * Returns:
 *   • 0 on success
 *   • 1 if a system call failed: check errno
 */
int set_date_added(const char* path, struct timespec in) {
    attrgroup_t request_attrs = ATTR_CMN_ADDEDTIME;

    struct attrlist request;
    memset(&request, 0, sizeof(request));
    request.bitmapcount = ATTR_BIT_MAP_COUNT;
    request.commonattr = request_attrs;

    typedef struct {
        struct timespec added;
    } __attribute__((aligned(4), packed)) request_buf_t;

    request_buf_t request_buf;
    request_buf.added.tv_sec = in.tv_sec;
    request_buf.added.tv_nsec = in.tv_nsec;

    int err = setattrlist(path, &request, &request_buf, sizeof(request_buf), 0);
    if (err != 0) {
        return 1;
    }

    return 0;
}

Код взят из: https://gist.github.com/tbussmann/b6473db60dc622233ec8720f65c9b450

Мне нужна помощь в переносе функции «set_date_added» в быстрый код.

Все, что у меня есть на данный момент, это:

import Darwin

func setDateAdded(path: String, toDate date: Date) -> Bool {
    guard FileManager.default.fileExists(atPath: path) else { return false }
    
    var attrLst = attrlist()
    attrLst.bitmapcount = u_short(ATTR_BIT_MAP_COUNT)
    attrLst.commonattr  = attrgroup_t(ATTR_CMN_ADDEDTIME)
    
    struct attr_buf_t {
        var added: timespec
    }
    
    var attr_buf = attr_buf_t(added: timespec(tv_sec: Int(date.timeIntervalSince1970), tv_nsec: 0) )
    
    let memSize = MemoryLayout.size(ofValue: attr_buf)
    
    let err = setattrlist(path, &attrLst, &attr_buf, memSize, 0)
    
    return err == 0
}

( setattrlist() возвращает -1 )

Возможно, это будет полезно: документация для setattrlist

Я попробовал ваш код Swift, и он у меня работает. Это изолированное приложение? Вы пробовали код C и Swift в инструменте командной строки?

Willeke 29.05.2024 10:27

@Willeke, это в песочнице. Но доступ к каталогу дан. Я могу удалить файл, например

Andrew_STOP_RU_WAR_IN_UA 30.05.2024 04:34

Как получить доступ к каталогу? Как удалить файл по пути?

Willeke 30.05.2024 06:14

Где находится файл, на внутреннем устройстве?

Willeke 30.05.2024 06:42

локальный рабочий стол. Доступ на запись есть. По пути.

Andrew_STOP_RU_WAR_IN_UA 30.05.2024 08:02

Из любопытства, как получить разрешение на доступ к файлу по пути из изолированного приложения?

Willeke 30.05.2024 09:15

1) проект приложения -> Подписание и возможности -> настройки песочницы - чтение/запись для всех типов доступа к файлам; 2) выбор доступа к папке каталога пользователя через MacOS OpenFileDialog при первом запуске приложения (создается вручную); Для других папок будет показан диалог с кнопками «да/нет» для доступа (система MacOS по умолчанию — код не требуется) 3) сохранение настроек доступа к жесткому диску — «Bookmarks.dict» — что-то вроде этого: stackoverflow.com/ вопросы/31278869/…

Andrew_STOP_RU_WAR_IN_UA 30.05.2024 09:44

Очень вероятно, что проблема связана с правами доступа к файлам в изолированном приложении. Я обновил свой ответ, добавив немного больше информации о доступе к файлам из изолированного приложения, но, вероятно, эта информация вам уже известна.

ConfusedByCode 30.05.2024 20:15

Я только что заметил кое-что, чего не заметил, когда тестировал это: атрибут kMDItemDateAdded может быть изменен только владельцем файла. Если вы пытаетесь изменить файл, принадлежащий другому пользователю, обновление может завершиться неудачно, независимо от других разрешений, которые вы установили ранее. Я обновил свой (довольно длинный) ответ, включив в него эту информацию.

ConfusedByCode 30.05.2024 22:57
Как запланировать звонок iOS в точное время в Swift? показывает мне, что есть struct timespec в Swift и как с ним работать. Я также нашел несколько упоминаний о timespec здесь: Apple Developer . Как мне создать упакованную структуру данных в Swift? уже спрашивали.
Harith 22.04.2024 15:23
Стоит ли изучать PHP в 2023-2024 годах?
Стоит ли изучать PHP в 2023-2024 годах?
Привет всем, сегодня я хочу высказать свои соображения по поводу вопроса, который я уже много раз получал в своем сообществе: "Стоит ли изучать PHP в...
Поведение ключевого слова "this" в стрелочной функции в сравнении с нормальной функцией
Поведение ключевого слова "this" в стрелочной функции в сравнении с нормальной функцией
В JavaScript одним из самых запутанных понятий является поведение ключевого слова "this" в стрелочной и обычной функциях.
Приемы CSS-макетирования - floats и Flexbox
Приемы CSS-макетирования - floats и Flexbox
Здравствуйте, друзья-студенты! Готовы совершенствовать свои навыки веб-дизайна? Сегодня в нашем путешествии мы рассмотрим приемы CSS-верстки - в...
Тестирование функциональных ngrx-эффектов в Angular 16 с помощью Jest
В системе управления состояниями ngrx, совместимой с Angular 16, появились функциональные эффекты. Это здорово и делает код определенно легче для...
Концепция локализации и ее применение в приложениях React ⚡️
Концепция локализации и ее применение в приложениях React ⚡️
Локализация - это процесс адаптации приложения к различным языкам и культурным требованиям. Это позволяет пользователям получить опыт, соответствующий...
Пользовательский скаляр GraphQL
Пользовательский скаляр GraphQL
Листовые узлы системы типов GraphQL называются скалярами. Достигнув скалярного типа, невозможно спуститься дальше по иерархии типов. Скалярный тип...
3
10
181
2
Перейти к ответу Данный вопрос помечен как решенный

Ответы 2

Есть несколько способов установить/изменить kMDItemDateAdded; С точки зрения эффективности и мобильности выбранный вами подход находится на правильном пути. По сути, ключевыми шагами будут инициализация структуры attrlist и правильная установка bitmapcount и commonattr. Затем вам нужно правильно рассчитать timespec для данной даты. Другим ключевым компонентом является обеспечение правильного размера структуры памяти для буфера атрибутов.

import Foundation
import CoreServices

func setDateAdded(_ path: String, _ date: Date) throws {
    guard FileManager.default.fileExists(atPath: path) else {
        throw DateErr.fileNotFound
    }
    
    var attr = attrlist()
    attr.bitmapcount = UInt16(ATTR_BIT_MAP_COUNT)
    attr.commonattr = UInt32(ATTR_CMN_ADDEDTIME)
    
    var buf = AttrBuf(added: (Int(date.timeIntervalSince1970),
                              Int(date.timeIntervalSince1970.truncatingRemainder(dividingBy: 1) * 1_000_000_000)))
    
    guard setattrlist(path, &attr, &buf, MemoryLayout<AttrBuf>.size, 0) == 0 else {
        throw DateErr.updateFailed
    }
    
    // There are multiple ways to do this perhaps, here I'm updating the file's metadata using NSURL and CFURLSetResourcePropertyForKey (it seemed faster)
    let nsurl = NSURL(fileURLWithPath: path)
    
    let success = CFURLSetResourcePropertyForKey(nsurl, kCFURLAddedToDirectoryDateKey, date as CFDate, nil)
    guard success else {
        throw DateErr.metadataUpdateFailed
    }
}

struct AttrBuf {
    var added: (tv_sec: Int, tv_nsec: Int)
}

enum DateErr: Error {
    case fileNotFound
    case updateFailed
    case metadataUpdateFailed
}

Вы могли бы вызвать функцию с чем-то вроде:

let path = "/path/to/file"
let newDate = DateComponents(calendar: Calendar.current, year: 2024, month: 7, day: 1,
                                                         hour: 0, minute: 0, second: 0)
                                                         .date!

Вы можете проверить это:

$  mdls -name kMDItemDateAdded -raw /path/to/file

О настройке kMDItem следует знать одну вещь: вы можете не сразу увидеть изменения из-за кэширования в macOS. Я полагаю, вы можете принудительно обновить кеш, хотя я бы не советовал этого делать просто потому, что это дорого.

Ответ принят как подходящий

Интересно, что ваш Swift-код у меня отлично сработал. Он успешно обновил атрибут kMDItemDateAdded и не выдал никаких ошибок.

Если вы прокрутите документацию setattrlist, на которую вы ссылаетесь, вы увидите, что существует несколько возможных ошибок. Вы можете увидеть, какой именно возвращается, импортировав System в свой файл Swift, а затем в строке после вызова setattrlist добавьте:

if err != 0, let msg = strerror(errno) {
    print(String(cString: msg))
}

Сравните сообщение об ошибке с ошибками, перечисленными в документации, и вы быстро поймете, что происходит не так.

Поскольку у меня код работает, возможно, проблема связана с вашими разрешениями или именем пути. Я тестировал это в изолированном приложении SwiftUI, и оно работает при условии, что вы импортируете URL-адрес файла с модификатором .fileImporter, а затем выполняете операцию между вызовами startAccessingSecurityScopedResource и startAccessingSecurityScopedResource. Независимо от того, используете ли вы SwiftUI или AppKit, вам необходимо сначала получить URL-адрес из соответствующего интерфейса выбора документов. Непосредственное создание экземпляра URL-адреса пути, а затем вызов методов ...SecurintScopedResource не работает. А если вам нужен более длительный доступ, вы можете использовать закладки URL, чего я никогда раньше не делал, но вы можете прочитать об этом здесь: https://developer.apple.com/documentation/security/app_sandbox/accessing_files_from_the_macos_app_sandbox?language= объект .

Также стоит отметить, что согласно документации setattlist только владелец файла может изменить атрибут «Дата добавления»:

You must own the file system object in order to set any of the following attributes:

ATTR_CMN_GRPID
ATTR_CMN_ACCESSMASK
ATTR_CMN_FLAGS
ATTR_CMN_CRTIME
ATTR_CMN_MODTIME
ATTR_CMN_ACCTIME
ATTR_CMN_ADDEDTIME

Единственный способ изменить эти атрибуты файла, которым вы не владеете, — это изменить владельца файла на текущего пользователя, для чего требуются права root.

Помимо разрешений, в вашем коде есть некоторые потенциальные проблемы, которые могут вызвать проблемы при работе с функциями getattrlist и setattrlist в Swift в целом.

Во-первых, структуры Swift не обязательно имеют ту же структуру памяти, что и структуры C, поэтому передача указателя на вашу структуру может не работать со структурой с большим количеством свойств. Кроме того, если вы покопаетесь в документации getattrlist, вы увидите, что структуры C должны быть правильно упакованы (именно поэтому в переведенном вами коде C использовалась упакованная структура). Вот что говорится в документации getattrlist:

  1. Первый элемент буфера — это u_int32_t, который содержит общую длину возвращаемых атрибутов в байтах. Этот размер
    включает само поле длины.

  2. После поля длины находится список атрибутов. Каждый атрибут представлен полем своего типа, где тип указывается как часть описания атрибута (ниже).

  3. Атрибуты помещаются в буфер атрибутов в том порядке, в котором они описаны ниже.

  4. Каждый атрибут выравнивается по 4-байтовой границе (включая 64-битные типы данных).

(См.: https://www.unix.com/man-page/mojave/2/getattrlist/)

Во-вторых, в документации setattrlist пример показывает, что буфер сначала заполняется вызовом getattrlist, что позволяет обновлять существующие свойства.

Есть два способа справиться с этим безопасно. Во-первых, если возможно, просто определите структуру для вашего буфера в заголовке C и добавьте ее в заголовок моста вашего проекта (или создайте цель C в проекте SPM и импортируйте ее).

Однако если вы хотите использовать чистый Swift, возможно, вы не сможете использовать структуру, но нет причин, по которым буфер должен быть структурой. Вы можете сложить размеры всех полей, выделить UnsafeMutableRawPointer соответствующего размера и выравнивания, а затем заполнить буфер. Чтобы опробовать это, я создал класс-оболочку, который использует новые пакеты параметров Swift. Вы можете посмотреть эту суть: https://gist.github.com/elopinto/95c46981098003aa58cb9a8344c66ab8

И используя это, мы можем обновить вашу функцию, улучшив обработку ошибок и немного увеличив гибкость буфера:

func setDateAdded(path: String, toDate date: Date) -> Bool {
    guard FileManager.default.fileExists(atPath: path) else { return false }
    
    var attrLst = attrlist()
    attrLst.bitmapcount = u_short(ATTR_BIT_MAP_COUNT)
    attrLst.commonattr  = attrgroup_t(ATTR_CMN_ADDEDTIME)
    
    //Always start with UInt32 for the length field.
    let attrBuff = AttrBuffer(attrTypes: UInt32.self, timespec.self)
    var err = getattrlist(path, &attrLst, attrBuff.buffer, attrBuff.size, 0)
    
    if err != 0, let msg = strerror(errno) {
        print(String(cString: msg))
        return false
    }
    
    let tsPtr = attrBuff.attribs.1
    tsPtr.pointee = timespec(tv_sec: Int(date.timeIntervalSince1970), tv_nsec: 0)
    
    // Alternatively, we can update the existing value, by, e.g., one minute
    // tsPtr.pointee.tv_sec += 60
    
    // We use the pointer to the field to update, not the whole buffer here.
    err = setattrlist(path, &attrLst, tsPtr, MemoryLayout.size(ofValue: tsPtr.pointee), 0)
    
    if err != 0, let msg = strerror(errno) {
        print(String(cString: msg))
        return false
    }
    
    return true
}

Другие вопросы по теме