6 Replies
      Latest reply on Aug 10, 2019 8:15 PM by nikitadragonski
      Pendragon Level 1 Level 1 (0 points)

        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)

        • Re: COPYFILE with Swift ?
          eskimo Apple Staff Apple Staff (11,835 points)

          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 (`copyfilestatefree).

          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:

          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:

          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:

          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:

          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.

          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(…).

          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:

          $ 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:

          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!”
          Apple Developer Relations, Developer Technical Support, Core OS/Hardware
          let myEmail = "eskimo" + "1" + "@apple.com"


          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)