How to move a file and create missing directories in OS X?
Asked Answered
F

3

5

I want to move a file, in OSX, to another directory:

func moveFile(currentPath currentPath: String, targetPath: String) {

let fileManager = NSFileManager.defaultManager()

do { try fileManager.moveItemAtPath(currentPath, toPath: targetPath) }
catch let error as NSError { print(error.description) }

}

Everything is working fine, except the case when the target-directory doesn't exist. I figured out that .isWritableFileAtPath could be helpful.

However, in my declared function I use the full file path (including the filename).

How can I split the filename from the path or more in general: how can I force Swift to create the directory before moving the file if needed?

Flint answered 2/10, 2015 at 18:10 Comment(0)
P
5

In the past I have solved this problem with code similar to the code below. Basically you just check to see if a file exists at the path representing the parent directory of the file you want to create. If it does not exist you create it and all folders above it in the path that don't exist as well.

func moveFile(currentPath currentPath: String, targetPath: String) {
    let fileManager = NSFileManager.defaultManager()
    let parentPath = (targetPath as NSString).stringByDeletingLastPathComponent()
    var isDirectory: ObjCBool = false
    if !fileManager.fileExistsAtPath(parentPath, isDirectory:&isDirectory) {
        fileManager.createDirectoryAtPath(parentPath, withIntermediateDirectories: true, attributes: nil)

        // Check to see if file exists, move file, error handling
    }
    else if isDirectory {
        // Check to see if parent path is writable, move file, error handling
    }
    else {
        // Parent path exists and is a file, error handling
    }
}

You may also want to use the fileExistsAtPath:isDirectory: variant so you can handle other error cases. Same is true for

Phototopography answered 2/10, 2015 at 18:17 Comment(3)
The var currentPath is misleading, it String contains something like: "/Documents/test.plist", it includes the full path and the filename. That's why createDirectoryAtPath isn't working for me, because in this case it creates a directory named "test.plist".Flint
I'm not sure what currentPath var you're referring to. You'll notice in my example code I use a variable named parentPath, which is what you need to pass to fileExistsAtPath: and createDirectoryAtPath:withIntermediateDirectories:attributes:. With the path you mention /Documents/test.plist, parentPath would need to be /Documents. You can accomplish this easily with the stringByDeletingLastPathComponent method on NSString. I will add these details to the answer.Phototopography
I was referring to the function in my question. However, stringByDeletingLastPathComponent did the job. Thank you for your answer!Flint
E
2

I've added this extension to FileManager to achieve this

extension FileManager {

    func moveItemCreatingIntermediaryDirectories(at: URL, to: URL) throws {
        let parentPath = to.deletingLastPathComponent()
        if !fileExists(atPath: parentPath.path) {
            try createDirectory(at: parentPath, withIntermediateDirectories: true, attributes: nil)
        }
        try moveItem(at: at, to: to)
    }

}
Eolande answered 1/9, 2018 at 18:30 Comment(1)
Thank you, this is a really helpful extension!Spam
V
0

Adding this because this question pops up in Google and the other answers use an API that they perhaps shouldn't in this context.

It's important to note this in the FileExists(atPath:) docs:

Attempting to predicate behavior based on the current state of the file system or a particular file on the file system is not recommended. Doing so can cause odd behavior or race conditions. It’s far better to attempt an operation (such as loading a file or creating a directory), check for errors, and handle those errors gracefully than it is to try to figure out ahead of time whether the operation will succeed. For more information on file-system race conditions, see Race Conditions and Secure File Operations in Secure Coding Guide.

Also, from the createDirectory(atPath:withIntermediateDirectories:attributes:) docs:

Return Value

true if the directory was created, true if createIntermediates is set and the directory already exists, or false if an error occurred.

Attempting to create a new directory with the withIntermediateDirectories: parameter set to true will not throw an error if that directory already exists, so you can safely use it even if the directory does already exist.

Skip the existence check, try to write the directory, then try to move the file:


func moveFile(from currentURL: URL, to targetURL: URL) {

    // Get the target directory by removing the file component
    let targetDir = targetURL.deletingLastPathComponent()

    do {
        try FileManager.default.createDirectory(at:targetDir, withIntermediateDirectories: true, attributes: nil)
    } catch {
        // Handle errors
    }
    do { 
        try FileManager.default.moveItem(at:currentURL, to: targetURL) }
    catch { 
        // Handle errors
    }
}
Vouge answered 17/10, 2021 at 21:18 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.