Swift: FileManager().fileExists(atPath: (fileURL.path)) without knowing extension - swift

)
today I have a problem and I can't find an easy solution.
With:
FileManager().fileExists(atPath:(fileURL.path))
it's simple to find out if a file exist. Actually I have the file name but don't know the extension. How can I use FileManager() to find a file without the extension. Something like .deletingPathExtension() for FileManger().fileExists?
Something like
ls filename.*

You could create a FileManager extension that retrieves the contents of the directory and filters for files as well as the expected filename.
It might look something like this:
extension FileManager {
func urls(of filename: String, in directory: URL) -> [URL]? {
guard let urls = try? contentsOfDirectory(at: directory, includingPropertiesForKeys: nil, options: [])
else { return nil }
return urls.filter { url in
!url.hasDirectoryPath && url.deletingPathExtension().lastPathComponent == filename
}
}
}
Finally, one would call it something like this:
let directory = URL(string: "file:///Users/stephan/tmp")!
if let urls = FileManager.default.urls(of: "test", in: directory) {
for url in urls {
print("do something with url: \(url)")
}
}

Related

How do I add an Images folder to my FileWrapper

I need a FileWrapper which contains a file and contains a folder.
The file is a single file, and the folder is used to write images to.
The folder also can contain some subfolders. I have a working code, but the issue is that when the Document gets saved, the folder gets re-written which deletes my images and subfolders.
I'm quite sure it has something to do with func fileWrapper(configuration: WriteConfiguration) throws -> FileWrapper but I need some help from somebody with more experience with FileWrappers.
This is my code:
struct MyProject: FileDocument {
var myFile: MyFile
static var readableContentTypes: [UTType] { [.myf] }
init(myFile: MyFile = MyFile() {
self.myFile = myFile
}
init(configuration: ReadConfiguration) throws {
let decoder = JSONDecoder()
guard let data = configuration.file.fileWrappers?["MYFProject"]?.regularFileContents else {
throw CocoaError(.fileReadCorruptFile)
}
do {
self.myFile = try decoder.decode(MyFile.self, from: data)
} catch {
throw error
}
}
func fileWrapper(configuration: WriteConfiguration) throws -> FileWrapper {
let encoder = JSONEncoder()
do {
let data = try encoder.encode(myFile)
let mainDirectory = FileWrapper(directoryWithFileWrappers: [:])
let myfWrapper = FileWrapper(regularFileWithContents: data)
let imagesWrapper = FileWrapper(directoryWithFileWrappers: [:])
let imageSubFolder = FileWrapper(directoryWithFileWrappers: [:])
for numberString in myFile.numbers {
imageSubFolder.preferredFilename = numberString
imagesWrapper.addFileWrapper(imageSubFolder)
}
myfWrapper.preferredFilename = "MYFProject"
mainDirectory.addFileWrapper(myfWrapper)
imagesWrapper.preferredFilename = "MYFImages"
mainDirectory.addFileWrapper(imagesWrapper)
return mainDirectory
} catch {
throw error
}
}
}
I use this path to write images to.
func getSubFolderImageFolder(documentPath: URL, subFolder: String) -> URL {
let sfProjectPath = documentPath.appendingPathComponent("MYFImages").appendingPathComponent(subFolder)
if !FileManager.default.fileExists(atPath: sfProjectPath.path) {
do {
try FileManager.default.createDirectory(atPath: sfProjectPath.path, withIntermediateDirectories: false, attributes: nil)
return sfProjectPath
} catch {
fatalError(error.localizedDescription)
}
}
else {
return sfProjectPath
}
}
Thanks in advance!
Your getSubFolderImageFolder function is not going to work well with file wrappers. You must use the FileWrapper methods to create the folders and files in the file wrapper.
To add a subfolder to your images folder, create a directory file wrapper the same way you created the imagesWrapper folder for the images. Add the subfolder as a child of the images folder.
let imageSubFolder = FileWrapper(directoryWithFileWrappers: [:])
imagesWrapper.addFileWrapper(imageSubFolder)
You must create a directory file wrapper for each subfolder. I notice in your updated code, you have only one subfolder file wrapper. With only one subfolder file wrapper, you have no way to store an image file in the correct subfolder.
To add the images, start by converting each image into a Data object. Create a regular file wrapper for each image, passing the image data as the argument to regularFileWithContents. Call addFileWrapper to add the image file to the appropriate folder.
let imageFile = FileWrapper(regularFileWithContents: imageData)
imageFile.preferredFilename = "ImageFilename" // Replace with your filename.
imagesWrapper.addFileWrapper(imageFile)
In your case the image subfolders will call addFileWrapper to add the images. The destination for the image file calls addFileWrapper.
You can find more detailed information about file wrappers in the following article:
Using File Wrappers in a SwiftUI App

Removing structure of empty folders

I am trying to delete any empty folders in a directory (sometimes folders within folders).
Here's how I'm trying to do it (see below), however it still leaves the last folder.
If there is a better way to approach this?
func removeEmptyFoldersAt(url: URL) {
let folderContents = try? fm.contentsOfDirectory(at: url, includingPropertiesForKeys: [.isDirectoryKey], options: [.skipsPackageDescendants, .skipsHiddenFiles]).filter({u in
let attr = try? u.resourceValues(forKeys: [.isDirectoryKey])
return attr!.isDirectory!
})
for folder in folderContents! {
let attr = try? folder.resourceValues(forKeys: [.isDirectoryKey])
let contents = try? fm.contentsOfDirectory(at: folder, includingPropertiesForKeys: [], options: [.skipsHiddenFiles,.skipsPackageDescendants])
if attr!.isDirectory! && contents!.count > 0 {
removeEmptyFoldersAt(url: folder)
}
if attr!.isDirectory! && contents?.count == 0 {
try? fm.removeItem(at: folder)
}
}
}
I'd follow a slightly different workflow, I'd have the removeEmptyFoldersAt be more generic in its workflow.
So you can pass it URL and if:
it's a directory, list the contents and recursively call itself with each item
it's a file, just remove it
For example...
extension URL {
var isDirectory: Bool {
return (try? resourceValues(forKeys: [.isDirectoryKey]))?.isDirectory ?? false
}
}
func removeItem(at url: URL) throws {
let fm = FileManager.default
if url.isDirectory {
for item in try fm.contentsOfDirectory(at: url, includingPropertiesForKeys: nil, options: []) {
try removeItem(at: item)
}
}
try fm.removeItem(at: url)
}
nb: I've not tested this, but this is a common workflow I've used in the past

Auto create directory when writing a file to a directory that does not exist

I am using File Manager to write files to the users documents directory. Every file is downloaded to a users device via a URL and then I create a local URL by doing the following:
extension URL {
func getLocalUrl() -> URL {
let directoryURL = FileManager.default.getDocumentsDirectory()
let filePath = pathComponents.dropFirst().joined(separator: "-")
return directoryURL.appendingPathComponent(filePath)
}
}
This works perfectly fine. However, when I try to create a local URL by using slashes instead of dashes via the following:
extension URL {
func getLocalUrl() -> URL {
let directoryURL = FileManager.default.getDocumentsDirectory()
let filePath = pathComponents.dropFirst().joined(separator: "/")
return directoryURL.appendingPathComponent(filePath)
}
}
I get the following error when this code runs:
func save(url: URL, fileUrl: URL) {
do {
// fileUrl is a url in the temporary directory from a URLSession.downloadTask
try FileManager.default.moveItem(at: fileUrl, to: url.getLocalUrl())
} catch {
print("download.service.write.error: \(error)")
}
}
CFNetworkDownload_sKaBto.tmp” couldn’t be moved to “user-data” because either the former doesn’t exist, or the folder containing the latter doesn’t exist."
As you can see the error is because I am trying to write to a directory that does not exist. Is there a way to auto create the directory if it does not exist?

How to check if multiple files exist in documents directory? (Swift)

I can check if one file exists with this method:
let fileNameOne = "savedpicture1"
let fileURLOne = documentsDirectoryURL.appendingPathComponent(fileNameOne)
if !FileManager.default.fileExists(atPath: fileURLOne.path) {
removeImage(itemName: "savedpicture1", fileExtension: "jpg")
} else {
print("There was no image to remove")
}
My problem is having to repeat the same lines of code for multiple files. For instance, I would like to check if the files exist in an array of paths, but I would have to repeat the code from above for each file, and it seems too redundant. I'm wondering if there's a way to check multiple files instead of repeating the code for each single path. ".fileExists" only enables me to check one path:
let filePaths = [fileURLOne.path, fileURLTwo.path, fileURLThree.path,
fileURLFour.path]
Write a method for example
func checkFiles(with fileNames: [String] {
for fileName in fileNames {
let fileURL = documentsDirectoryURL.appendingPathComponent(fileName)
if !FileManager.default.fileExists(atPath: fileURL.path) {
removeImage(itemName: fileName, fileExtension: "jpg")
} else {
print("There was no image to remove at", fileURL)
}
}
}
and call it
let fileNames = ["savedpicture1", "savedpicture2", "savedpicture3", "savedpicture4"]
checkFiles(with: fileNames)

FileHandle not accepting my URLs for write access

I'd like to open a uniquely named output file for writing either plist or data, but not having any luck in getting a handle using either URL routine of init(fileURLWithPath:) or init(string:)
func NewFileHandleForWritingFile(path: String, name: String, type: String, outFile: inout String?) -> FileHandle? {
let fm = FileManager.default
var file: String? = nil
var uniqueNum = 0
while true {
let tag = (uniqueNum > 0 ? String(format: "-%d", uniqueNum) : "")
let unique = String(format: "%#%#.%#", name, tag, type)
file = String(format: "%#/%#", path, unique)
if false == fm.fileExists(atPath: file!) { break }
// Try another tag.
uniqueNum += 1;
}
outFile = file!
do {
let fileURL = URL.init(fileURLWithPath: file!)
let fileHandle = try FileHandle.init(forWritingTo: fileURL)
print("\(file!) was opened for writing")
//set the file extension hidden attribute to YES
try fm.setAttributes([FileAttributeKey.extensionHidden: true], ofItemAtPath: file!)
return fileHandle
} catch let error {
NSApp.presentError(error)
return nil;
}
}
debugger shows
which for this URL init routine adds the scheme (file://) but otherwise the same as the other, and I'd like to prefer the newer methods which throw reutrning (-1) when just using paths. The error thrown (2) is an ENOENT (no such entity!?) as I need a handle to write to I'm confused how else to get one? The sample path is a new folder created at desktop to triage.
Unlike the previous answer, I recommend using Data's write(to:options:) API instead of FileManager's createFile(atPath:contents:attributes:), because it is a URL-based API, which is generally to be preferred over path-based ones. The Data method also throws an error instead of just returning false if it fails, so if something goes wrong, you can tell the user why.
try Data().write(to: fileURL, options: [])
I would also suggesting replacing the path-based FileManager.fileExists(atPath:) with the URL-based checkResourceIsReachable():
if false == ((try? fileURL.checkResourceIsReachable()) ?? false)
You can't create a file handle to a non-existent file. That is what is causing the ENOENT error.
Use FileManager createFile(atPath:contents:attributes:) to create the file just before creating the file handle.
do {
fm.createFile(atPath: file!, contents: nil, attributes: [FileAttributeKey.extensionHidden: true])
let fileURL = URL(fileURLWithPath: file!)
let fileHandle = try FileHandle(forWritingTo: fileURL)
print("\(file!) was opened for writing")
return fileHandle
} catch let error {
NSApp.presentError(error)
return nil;
}