Мне нужно изменить атрибут метаданных файла 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
@Willeke, это в песочнице. Но доступ к каталогу дан. Я могу удалить файл, например
Как получить доступ к каталогу? Как удалить файл по пути?
Где находится файл, на внутреннем устройстве?
локальный рабочий стол. Доступ на запись есть. По пути.
Из любопытства, как получить разрешение на доступ к файлу по пути из изолированного приложения?
1) проект приложения -> Подписание и возможности -> настройки песочницы - чтение/запись для всех типов доступа к файлам; 2) выбор доступа к папке каталога пользователя через MacOS OpenFileDialog при первом запуске приложения (создается вручную); Для других папок будет показан диалог с кнопками «да/нет» для доступа (система MacOS по умолчанию — код не требуется) 3) сохранение настроек доступа к жесткому диску — «Bookmarks.dict» — что-то вроде этого: stackoverflow.com/ вопросы/31278869/…
Очень вероятно, что проблема связана с правами доступа к файлам в изолированном приложении. Я обновил свой ответ, добавив немного больше информации о доступе к файлам из изолированного приложения, но, вероятно, эта информация вам уже известна.
Я только что заметил кое-что, чего не заметил, когда тестировал это: атрибут kMDItemDateAdded может быть изменен только владельцем файла. Если вы пытаетесь изменить файл, принадлежащий другому пользователю, обновление может завершиться неудачно, независимо от других разрешений, которые вы установили ранее. Я обновил свой (довольно длинный) ответ, включив в него эту информацию.
struct timespec
в Swift и как с ним работать. Я также нашел несколько упоминаний о timespec
здесь: Apple Developer . Как мне создать упакованную структуру данных в Swift? уже спрашивали.
Есть несколько способов установить/изменить 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
:
Первый элемент буфера — это u_int32_t, который содержит общую длину возвращаемых атрибутов в байтах. Этот размер
включает само поле длины.После поля длины находится список атрибутов. Каждый атрибут представлен полем своего типа, где тип указывается как часть описания атрибута (ниже).
Атрибуты помещаются в буфер атрибутов в том порядке, в котором они описаны ниже.
Каждый атрибут выравнивается по 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
}
Я попробовал ваш код Swift, и он у меня работает. Это изолированное приложение? Вы пробовали код C и Swift в инструменте командной строки?