COPYFILE with Swift ?

I've looked for a while and maybe it's my search parameters but can't find doing this with Swift (5). An example I've looked at I need to translate somehow ... https://searchcode.com/file/101621017/OpenEmu/OEFileManager.m


So far I've only managed to copy a file,

let from_cchar = from.path.cString(using: .utf8)!
let to_cchar = to.path.cString(using: .utf8)!
let flags: copyfile_flags_t = UInt32(bitPattern: COPYFILE_DATA)
let success = copyfile(from_cchar, to_cchar, nil, flags)


I need to implement COPY_QUIT but no idea how to reference the file copy ? (will be async)

Accepted Reply

The third parameter to copyfile is a copy state ‘object’ (copyfile_state_t). To implement COPY_QUIT, you’ll need to allocate one of these (copyfile_state_alloc), configure it (copyfile_state_set), pass it in to the copyfile call, and then deallocate it when you’re done (copyfile_state_free).

Once you have a copy state object, the specific state you need to set is COPYFILE_STATE_STATUS_CB. This takes a C function pointer (of type copyfile_callback_t) as a parameter.

Getting this all right is going to be tricky. It would be tricky even from a C-based language, and Swift doesn’t make it any easier.



The first thing I’d do is wrap the copyfile function so that it takes URL parameters directly:

Code Block
private func copyfileQ(_ from: URL, _ to: URL, _ state: copyfile_state_t?, _ flags: copyfile_flags_t) -> Int32 {
return from.withUnsafeFileSystemRepresentation { fromPtr -> Int32 in
to.withUnsafeFileSystemRepresentation { toPtr -> Int32 in
copyfile(fromPtr, toPtr, state, flags)
}
}
}


You can then call it like so:

Code Block
let success = copyfileQ(self.sourceURL, self.destinationURL, nil, copyfile_flags_t(COPYFILE_DATA)) >= 0
guard success else {
throw NSError(domain: NSPOSIXErrorDomain, code: Int(errno), userInfo: nil)
}


Now you have to deal with the copy state object. You can allocate this like so:

Code Block
let state = copyfile_state_alloc()
defer {
copyfile_state_free(state)
}


Note that this uses a defer statement to guarantee that it gets deallocated. Then pass it in to copyfile like so:

Code Block
let success = copyfileQ(self.sourceURL, self.destinationURL, state, copyfile_flags_t(COPYFILE_DATA)) >= 0


Now you need to set up the callback. This is where things get tricky. The problem is that the type of the third parameter to copyfile_state_set depends on the selector you pass it. For example:
  • For COPYFILE_STATE_STATUS_CB, it is a C function pointer of type copyfile_callback_t.

  • For COPYFILE_STATE_STATUS_CTX, it is a C void * parameter.

These two cases have to be handled differently. For COPYFILE_STATE_STATUS_CTX, the desired parameter is an object (self) and thus you can use the standard Unmanaged tricks.

Code Block
let context = Unmanaged.passUnretained(self)
copyfile_state_set(state, UInt32(COPYFILE_STATE_STATUS_CTX), context.toOpaque())


For COPYFILE_STATE_STATUS_CB, the desired parameter is a C function pointer, and that requires an ugly unsafeBitCast(…).

Code Block
let callback: copyfile_callback_t = { what, stage, state, src, dst, context in
copyfile_state_set(state, UInt32(COPYFILE_STATE_STATUS_CB), unsafeBitCast(callback, to: UnsafeRawPointer.self))


Putting this all together, you’ll find pasted in below the source for a command-line tool that copies from a source to a destination. If the source looks like this:

Code Block
$ find /Users/quinn/Test/source
/Users/quinn/Test/source
/Users/quinn/Test/source/.DS_Store
/Users/quinn/Test/source/f1.txt
/Users/quinn/Test/source/nested
/Users/quinn/Test/source/nested/.DS_Store
/Users/quinn/Test/source/nested/f2.txt


it prints:

Code Block
what: 2
stage: 1
state: 0x000000010183e680
src: /Users/quinn/Test/source
dst: /Users/quinn/Test/destination/source
what: 2
stage: 2
state: 0x000000010183e680
src: /Users/quinn/Test/source
dst: /Users/quinn/Test/destination/source
what: 1
stage: 1
state: 0x000000010050aa30
src: /Users/quinn/Test/source/.DS_Store
dst: /Users/quinn/Test/destination/source/.DS_Store
what: 4
stage: 4
state: 0x000000010050aa30
src: /Users/quinn/Test/source/.DS_Store
dst: /Users/quinn/Test/destination/source/.DS_Store
what: 1
stage: 2
state: 0x000000010050aa30
src: /Users/quinn/Test/source/.DS_Store
dst: /Users/quinn/Test/destination/source/.DS_Store
what: 1
stage: 1
state: 0x00000001006186e0
src: /Users/quinn/Test/source/f1.txt
dst: /Users/quinn/Test/destination/source/f1.txt
what: 4
stage: 4
state: 0x00000001006186e0
src: /Users/quinn/Test/source/f1.txt
dst: /Users/quinn/Test/destination/source/f1.txt
what: 1
stage: 2
state: 0x00000001006186e0
src: /Users/quinn/Test/source/f1.txt
dst: /Users/quinn/Test/destination/source/f1.txt
what: 2
stage: 1
state: 0x000000010183e680
src: /Users/quinn/Test/source/nested
dst: /Users/quinn/Test/destination/source/nested
what: 2
stage: 2
state: 0x000000010183e680
src: /Users/quinn/Test/source/nested
dst: /Users/quinn/Test/destination/source/nested
what: 1
stage: 1
state: 0x0000000101839d00
src: /Users/quinn/Test/source/nested/.DS_Store
dst: /Users/quinn/Test/destination/source/nested/.DS_Store
what: 4
stage: 4
state: 0x0000000101839d00
src: /Users/quinn/Test/source/nested/.DS_Store
dst: /Users/quinn/Test/destination/source/nested/.DS_Store
what: 1
stage: 2
state: 0x0000000101839d00
src: /Users/quinn/Test/source/nested/.DS_Store
dst: /Users/quinn/Test/destination/source/nested/.DS_Store
what: 1
stage: 1
state: 0x0000000101900540
src: /Users/quinn/Test/source/nested/f2.txt
dst: /Users/quinn/Test/destination/source/nested/f2.txt
what: 4
stage: 4
state: 0x0000000101900540
src: /Users/quinn/Test/source/nested/f2.txt
dst: /Users/quinn/Test/destination/source/nested/f2.txt
what: 1
stage: 2
state: 0x0000000101900540
src: /Users/quinn/Test/source/nested/f2.txt
dst: /Users/quinn/Test/destination/source/nested/f2.txt
what: 3
stage: 1
state: 0x0000000100736830
src: /Users/quinn/Test/source/nested
dst: /Users/quinn/Test/destination/source/nested
what: 3
stage: 2
state: 0x0000000100736830
src: /Users/quinn/Test/source/nested
dst: /Users/quinn/Test/destination/source/nested
what: 3
stage: 1
state: 0x0000000100736830
src: /Users/quinn/Test/source
dst: /Users/quinn/Test/destination/source
what: 3
stage: 2
state: 0x0000000100736830
src: /Users/quinn/Test/source
dst: /Users/quinn/Test/destination/source
callbackCount: 20


Share and Enjoy

Quinn “The Eskimo!” @ Developer Technical Support @ Apple
let myEmail = "eskimo" + "1" + "@" + "apple.com"



Code Block
import Foundation
private func copyfileQ(_ from: URL, _ to: URL, _ state: copyfile_state_t?, _ flags: copyfile_flags_t) -> Int32 {
return from.withUnsafeFileSystemRepresentation { fromPtr -> Int32 in
to.withUnsafeFileSystemRepresentation { toPtr -> Int32 in
copyfile(fromPtr, toPtr, state, flags)
}
}
}
class Copier {
init(from sourceURL: URL, to destinationURL: URL) {
self.sourceURL = sourceURL
self.destinationURL = destinationURL
}
let sourceURL: URL
let destinationURL: URL
func run() throws {
// Allocate our state, arranging to deallocate it when we return.
let state = copyfile_state_alloc()
defer {
copyfile_state_free(state)
}
// Create a C function point that calls our `callback(…)` method. Note
// that we can’t use `self` here because this is a C function pointer.
// Rather, we have to recover `self` (here called `obj`) from the
// `context` parameter.
let callback: copyfile_callback_t = { what, stage, state, src, dst, context in
let obj = Unmanaged<Copier>.fromOpaque(context!).takeUnretainedValue()
return obj.callback(what: what, stage: stage, state: state!, src: src, dst: dst)
}
// Apply that to the state. Note the use of `unsafeBitCast(…)` here.
// This is the best of a bad set of options for converting a C function
// pointer to a C `void *`.
copyfile_state_set(state, UInt32(COPYFILE_STATE_STATUS_CB), unsafeBitCast(callback, to: UnsafeRawPointer.self))
// We pass an unretained reference because the reference only needs to
// persist as long as `state`, and we deallocate `state` before we return
// from this function.
let context = Unmanaged.passUnretained(self)
copyfile_state_set(state, UInt32(COPYFILE_STATE_STATUS_CTX), context.toOpaque())
// We’re all set up, so let’s call `copyfile`.
let success = copyfileQ(self.sourceURL, self.destinationURL, state, copyfile_flags_t(COPYFILE_DATA | COPYFILE_RECURSIVE)) >= 0
guard success else {
throw NSError(domain: NSPOSIXErrorDomain, code: Int(errno), userInfo: nil)
}
print("callbackCount: \(self.callbackCount)")
}
var callbackCount = 0
private func callback(what: Int32, stage: Int32, state: copyfile_state_t, src: UnsafePointer<CChar>?, dst: UnsafePointer<CChar>?) -> Int32 {
print("what: \(what)")
print("stage: \(stage)")
print("state: \(state)")
print("src: \(src.flatMap { String(cString: $0) } ?? "nil")")
print("dst: \(dst.flatMap { String(cString: $0) } ?? "nil")")
print()
// Increment the callback count to prove that `self` is functioning.
self.callbackCount += 1
return COPYFILE_CONTINUE
}
}
func main() {
let src = URL(fileURLWithPath: "/Users/quinn/Test/source")
let dst = URL(fileURLWithPath: "/Users/quinn/Test/destination")
let copier = Copier(from: src, to: dst)
try! copier.run()
}
main()
exit(EXIT_SUCCESS)

Replies

The third parameter to copyfile is a copy state ‘object’ (copyfile_state_t). To implement COPY_QUIT, you’ll need to allocate one of these (copyfile_state_alloc), configure it (copyfile_state_set), pass it in to the copyfile call, and then deallocate it when you’re done (copyfile_state_free).

Once you have a copy state object, the specific state you need to set is COPYFILE_STATE_STATUS_CB. This takes a C function pointer (of type copyfile_callback_t) as a parameter.

Getting this all right is going to be tricky. It would be tricky even from a C-based language, and Swift doesn’t make it any easier.



The first thing I’d do is wrap the copyfile function so that it takes URL parameters directly:

Code Block
private func copyfileQ(_ from: URL, _ to: URL, _ state: copyfile_state_t?, _ flags: copyfile_flags_t) -> Int32 {
return from.withUnsafeFileSystemRepresentation { fromPtr -> Int32 in
to.withUnsafeFileSystemRepresentation { toPtr -> Int32 in
copyfile(fromPtr, toPtr, state, flags)
}
}
}


You can then call it like so:

Code Block
let success = copyfileQ(self.sourceURL, self.destinationURL, nil, copyfile_flags_t(COPYFILE_DATA)) >= 0
guard success else {
throw NSError(domain: NSPOSIXErrorDomain, code: Int(errno), userInfo: nil)
}


Now you have to deal with the copy state object. You can allocate this like so:

Code Block
let state = copyfile_state_alloc()
defer {
copyfile_state_free(state)
}


Note that this uses a defer statement to guarantee that it gets deallocated. Then pass it in to copyfile like so:

Code Block
let success = copyfileQ(self.sourceURL, self.destinationURL, state, copyfile_flags_t(COPYFILE_DATA)) >= 0


Now you need to set up the callback. This is where things get tricky. The problem is that the type of the third parameter to copyfile_state_set depends on the selector you pass it. For example:
  • For COPYFILE_STATE_STATUS_CB, it is a C function pointer of type copyfile_callback_t.

  • For COPYFILE_STATE_STATUS_CTX, it is a C void * parameter.

These two cases have to be handled differently. For COPYFILE_STATE_STATUS_CTX, the desired parameter is an object (self) and thus you can use the standard Unmanaged tricks.

Code Block
let context = Unmanaged.passUnretained(self)
copyfile_state_set(state, UInt32(COPYFILE_STATE_STATUS_CTX), context.toOpaque())


For COPYFILE_STATE_STATUS_CB, the desired parameter is a C function pointer, and that requires an ugly unsafeBitCast(…).

Code Block
let callback: copyfile_callback_t = { what, stage, state, src, dst, context in
copyfile_state_set(state, UInt32(COPYFILE_STATE_STATUS_CB), unsafeBitCast(callback, to: UnsafeRawPointer.self))


Putting this all together, you’ll find pasted in below the source for a command-line tool that copies from a source to a destination. If the source looks like this:

Code Block
$ find /Users/quinn/Test/source
/Users/quinn/Test/source
/Users/quinn/Test/source/.DS_Store
/Users/quinn/Test/source/f1.txt
/Users/quinn/Test/source/nested
/Users/quinn/Test/source/nested/.DS_Store
/Users/quinn/Test/source/nested/f2.txt


it prints:

Code Block
what: 2
stage: 1
state: 0x000000010183e680
src: /Users/quinn/Test/source
dst: /Users/quinn/Test/destination/source
what: 2
stage: 2
state: 0x000000010183e680
src: /Users/quinn/Test/source
dst: /Users/quinn/Test/destination/source
what: 1
stage: 1
state: 0x000000010050aa30
src: /Users/quinn/Test/source/.DS_Store
dst: /Users/quinn/Test/destination/source/.DS_Store
what: 4
stage: 4
state: 0x000000010050aa30
src: /Users/quinn/Test/source/.DS_Store
dst: /Users/quinn/Test/destination/source/.DS_Store
what: 1
stage: 2
state: 0x000000010050aa30
src: /Users/quinn/Test/source/.DS_Store
dst: /Users/quinn/Test/destination/source/.DS_Store
what: 1
stage: 1
state: 0x00000001006186e0
src: /Users/quinn/Test/source/f1.txt
dst: /Users/quinn/Test/destination/source/f1.txt
what: 4
stage: 4
state: 0x00000001006186e0
src: /Users/quinn/Test/source/f1.txt
dst: /Users/quinn/Test/destination/source/f1.txt
what: 1
stage: 2
state: 0x00000001006186e0
src: /Users/quinn/Test/source/f1.txt
dst: /Users/quinn/Test/destination/source/f1.txt
what: 2
stage: 1
state: 0x000000010183e680
src: /Users/quinn/Test/source/nested
dst: /Users/quinn/Test/destination/source/nested
what: 2
stage: 2
state: 0x000000010183e680
src: /Users/quinn/Test/source/nested
dst: /Users/quinn/Test/destination/source/nested
what: 1
stage: 1
state: 0x0000000101839d00
src: /Users/quinn/Test/source/nested/.DS_Store
dst: /Users/quinn/Test/destination/source/nested/.DS_Store
what: 4
stage: 4
state: 0x0000000101839d00
src: /Users/quinn/Test/source/nested/.DS_Store
dst: /Users/quinn/Test/destination/source/nested/.DS_Store
what: 1
stage: 2
state: 0x0000000101839d00
src: /Users/quinn/Test/source/nested/.DS_Store
dst: /Users/quinn/Test/destination/source/nested/.DS_Store
what: 1
stage: 1
state: 0x0000000101900540
src: /Users/quinn/Test/source/nested/f2.txt
dst: /Users/quinn/Test/destination/source/nested/f2.txt
what: 4
stage: 4
state: 0x0000000101900540
src: /Users/quinn/Test/source/nested/f2.txt
dst: /Users/quinn/Test/destination/source/nested/f2.txt
what: 1
stage: 2
state: 0x0000000101900540
src: /Users/quinn/Test/source/nested/f2.txt
dst: /Users/quinn/Test/destination/source/nested/f2.txt
what: 3
stage: 1
state: 0x0000000100736830
src: /Users/quinn/Test/source/nested
dst: /Users/quinn/Test/destination/source/nested
what: 3
stage: 2
state: 0x0000000100736830
src: /Users/quinn/Test/source/nested
dst: /Users/quinn/Test/destination/source/nested
what: 3
stage: 1
state: 0x0000000100736830
src: /Users/quinn/Test/source
dst: /Users/quinn/Test/destination/source
what: 3
stage: 2
state: 0x0000000100736830
src: /Users/quinn/Test/source
dst: /Users/quinn/Test/destination/source
callbackCount: 20


Share and Enjoy

Quinn “The Eskimo!” @ Developer Technical Support @ Apple
let myEmail = "eskimo" + "1" + "@" + "apple.com"



Code Block
import Foundation
private func copyfileQ(_ from: URL, _ to: URL, _ state: copyfile_state_t?, _ flags: copyfile_flags_t) -> Int32 {
return from.withUnsafeFileSystemRepresentation { fromPtr -> Int32 in
to.withUnsafeFileSystemRepresentation { toPtr -> Int32 in
copyfile(fromPtr, toPtr, state, flags)
}
}
}
class Copier {
init(from sourceURL: URL, to destinationURL: URL) {
self.sourceURL = sourceURL
self.destinationURL = destinationURL
}
let sourceURL: URL
let destinationURL: URL
func run() throws {
// Allocate our state, arranging to deallocate it when we return.
let state = copyfile_state_alloc()
defer {
copyfile_state_free(state)
}
// Create a C function point that calls our `callback(…)` method. Note
// that we can’t use `self` here because this is a C function pointer.
// Rather, we have to recover `self` (here called `obj`) from the
// `context` parameter.
let callback: copyfile_callback_t = { what, stage, state, src, dst, context in
let obj = Unmanaged<Copier>.fromOpaque(context!).takeUnretainedValue()
return obj.callback(what: what, stage: stage, state: state!, src: src, dst: dst)
}
// Apply that to the state. Note the use of `unsafeBitCast(…)` here.
// This is the best of a bad set of options for converting a C function
// pointer to a C `void *`.
copyfile_state_set(state, UInt32(COPYFILE_STATE_STATUS_CB), unsafeBitCast(callback, to: UnsafeRawPointer.self))
// We pass an unretained reference because the reference only needs to
// persist as long as `state`, and we deallocate `state` before we return
// from this function.
let context = Unmanaged.passUnretained(self)
copyfile_state_set(state, UInt32(COPYFILE_STATE_STATUS_CTX), context.toOpaque())
// We’re all set up, so let’s call `copyfile`.
let success = copyfileQ(self.sourceURL, self.destinationURL, state, copyfile_flags_t(COPYFILE_DATA | COPYFILE_RECURSIVE)) >= 0
guard success else {
throw NSError(domain: NSPOSIXErrorDomain, code: Int(errno), userInfo: nil)
}
print("callbackCount: \(self.callbackCount)")
}
var callbackCount = 0
private func callback(what: Int32, stage: Int32, state: copyfile_state_t, src: UnsafePointer<CChar>?, dst: UnsafePointer<CChar>?) -> Int32 {
print("what: \(what)")
print("stage: \(stage)")
print("state: \(state)")
print("src: \(src.flatMap { String(cString: $0) } ?? "nil")")
print("dst: \(dst.flatMap { String(cString: $0) } ?? "nil")")
print()
// Increment the callback count to prove that `self` is functioning.
self.callbackCount += 1
return COPYFILE_CONTINUE
}
}
func main() {
let src = URL(fileURLWithPath: "/Users/quinn/Test/source")
let dst = URL(fileURLWithPath: "/Users/quinn/Test/destination")
let copier = Copier(from: src, to: dst)
try! copier.run()
}
main()
exit(EXIT_SUCCESS)

Thanks soo much, shall give it a try.

Do I need "exit(EXIT_SUCCESS)" ? Also if I use COPYFILE_QUIT this will be in the callback ?

Found an example of copyfile ... https://github.com/rustle/RSTLCopyOperation/blob/master/Copy/RSTLCopyOperation.m


Within the callback which I set up ok, having trouble specifically within the NSLog is %@ and %s no idea how it gets the total ?


case COPYFILE_COPY_DATA:
    switch (stage) {
    case COPYFILE_PROGRESS:
    {
        off_t copiedBytes;
        const int returnCode = copyfile_state_get(state, COPYFILE_STATE_COPIED, &copiedBytes);
        if (returnCode == 0)
        {
        NSLog(@"Copied %@ of %s so far", [NSByteCountFormatter stringFromByteCount:copiedBytes countStyle:NSByteCountFormatterCountStyleFile], fromPath);
        }

I tried in the callback "if isCancelled { return COPYQUIT }" which seems to work ok, however shows an error "error processing data: Invalid argument", but does carry on. Should I ignore this ? It's not the error thrown as specified, only happens when interrupting the flow.

Something went wrong to my account. Please help me fix the things that need to be fix