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)

Answered by DTS Engineer in 366200022
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)

Accepted Answer
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

COPYFILE with Swift ?
 
 
Q