FileManager and accessing app group error - swift

I've been having problem with using share extension to get a file and unzip it into my app group folder.
Here is the code:
for attachment in content.attachments as! [NSItemProvider] {
if attachment.hasItemConformingToTypeIdentifier(contentType){
attachment.loadItem(forTypeIdentifier: contentType, options: nil) { (data, error) in
let fileManager = FileManager()
let url = data as! URL
let destinationURL = FileManager.default.containerURL(forSecurityApplicationGroupIdentifier: "group.KakaoAnalyzer")?.appendingPathComponent("KakaoFilesTemp")
do {
try fileManager.createDirectory(at: destinationURL!, withIntermediateDirectories: true, attributes: nil)
try fileManager.unzipItem(at: url, to: destinationURL!)
self.mergeFiles()
print("SUCCESSFULLY UNZIPPED")
} catch {
print(error.localizedDescription)
print("UNZIP FAILED")
}
}
}
}
This is part of my didSelectPost()
I'm using share extension to get a ZIP file via share, and ZIPFoundation to unzip it into my destination folder which is my app group with this extension and my main project.
This code runs fine and unzips successfully in a simulator, but when I run it on my phone, I get an error:
The file "FILENAME" couldn't be opened because there is no such file
when there clearly should be one, and
Failed to determine whether URL "FILEURL" is managed by a file provider.
I've tried moving the file at the url to my documentDirectory, but it produces error:
"FILE" couldn't be moved because you don't have permission to access "appGroupFolder"
Does anyone how to solve this? Any help would be greatly appreciated!

Related

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?

ZipFoundation cannot unarchive the file because it can't be found

I'm trying to unzip some archives in a macOS app using the ZipFoundation library. The files are located on the desktop, I've confirmed the paths are correct and that the file exists.
I have this code:
let fileManager = FileManager()
let sourceURL = URL(fileURLWithPath: monetizeZiPFile.absoluteString)
var destinationURL = URL(fileURLWithPath: dirBuildURL.absoluteString)
destinationURL.appendPathComponent("directory")
do {
try fileManager.createDirectory(at: destinationURL, withIntermediateDirectories: true, attributes: nil)
try fileManager.unzipItem(at: monetizeZiPFile, to: dirBuildURL)
} catch {
print("Extraction of ZIP archive failed with error:\(error)")
}
I receive this error for the unzipItem method:
CFURLResourceIsReachable failed because it was passed an URL which has no scheme
Extraction of ZIP archive failed with error:Error Domain=NSCocoaErrorDomain Code=260
"The file “00-monetize.zip” couldn’t be opened because there is no such file." UserInfo={NSFilePath=/Users/xxxxxxxx/Desktop/Componize-Builds/DOC-8621/Monetize/00-monetize.zip}
Do I have to be within the project directories for ZipFoundation to work?
For paths, do not use absoluteString, use path.

Unzip the zip file saved into the app in xcode with swift 5.0

I have saved one zip file in my app, which is having .json files in it.
I have to unzip this zip file at some path and read the file after unzip.
I tried using package managers like ZipFoundation
If I run the app on my mac with OS 10.15.2 (macOS Catalina) I get the following output
"Extraction of ZIP archive failed with error:Error Domain=NSCocoaErrorDomain Code=513 "You don’t have permission to save the file “directory” in the folder “Macintosh HD”." UserInfo={NSFilePath=/directory, NSUnderlyingError=0x600002f39530 {Error Domain=NSPOSIXErrorDomain Code=13 "Permission denied"}}"
if I run the app on device (iPhone XS Max iOS 13.3.1) I get the following error,
Extraction of ZIP archive failed with error:Error Domain=NSCocoaErrorDomain Code=513 "You don’t have permission to save the file “directory” in the folder “System#snap-8290536”." UserInfo={NSFilePath=/directory, NSUnderlyingError=0x2811d54d0 {Error Domain=NSPOSIXErrorDomain Code=1 "Operation not permitted"}}
This is my code
import ZIPFoundation
func unarchiveFile(){
let fileManager = FileManager()
let currentWorkingPath = fileManager.currentDirectoryPath
var sourceURL = URL(fileURLWithPath: "/Users/sagarchavan/Documents/Project/Swift-POC/iOS-CD-POCs/CDSwiftDemo/")
//var sourceURL = URL(fileURLWithPath:currentWorkingPath)
sourceURL.appendPathComponent("countryList.zip")
print("source url is :- :\(sourceURL)")
var destinationURL = URL(fileURLWithPath: currentWorkingPath)
destinationURL.appendPathComponent("directory")
print("destinationURL is :- :\(destinationURL)")
do {
try fileManager.createDirectory(at: destinationURL, withIntermediateDirectories: true, attributes: nil)
try fileManager.unzipItem(at: sourceURL, to: destinationURL)
} catch {
print("Extraction of ZIP archive failed with error:\(error)")
}
}
Can you please help me out to get out from this issue.
I need to unzip the zip file.
for sourceURL I tried both, by giving currentWorkingPath and by giving actual path to file. but both the time i get same error.
#note :- I don't want to use any pods or Carthage. I want default code or external dependancies are from package managers only.
Any help will be appreciated.
Thank you.
All Thanks to #vadian
I used URL(fileURLWithPath: currentWorkingPath)
Here is my sample code which is working well now,
func unarchiveFile(){
let fileManager = FileManager()
let currentWorkingPath = "/Users/sagarchavan/Documents/Project/Swift-POC/iOS-CD-POCs/CDSwiftDemo/CDSwiftDemo/Zipfile/"
var sourceURL = URL(fileURLWithPath:currentWorkingPath)
sourceURL.appendPathComponent("countryList.zip")
var destinationURL = URL(fileURLWithPath: currentWorkingPath)
destinationURL.appendPathComponent("unzipData")
do {
try fileManager.createDirectory(at: destinationURL, withIntermediateDirectories: true, attributes: nil)
try fileManager.unzipItem(at: sourceURL, to: destinationURL)
parseJSONFromFile(destinationPath: destinationURL)
} catch {
print("Extraction of ZIP archive failed with error:\(error)")
}
}

How do I write to a local file in Swift/XCTest?

My ultimate question is about saving a screenshot from an AppleTV application using XCTest and Swift4 (running on a MacBook paired to the TV device), but I'm having trouble even writing a simple text string to a local file. If I can get this simple file-save working, I'm hoping I can resolve the screenshot issue. (Apologies for making this look like two questions but they appear to be related and resulted from my troubleshooting efforts.)
First, here's what I'm trying to do with a screenshot, based on sample code I found somewhere online:
let appshot = XCUIApplication().windows.firstMatch.screenshot()
let shotpath = FileManager.default.urls(for: .downloadsDirectory, in: .userDomainMask)[0].appendingPathComponent("appshot.png")
let shotpathUrl = URL(string: "file://\(shotpath)")
print("Saving to: \(shotpath)")
do {
try appshot.pngRepresentation.write(to: shotpathUrl!)
} catch {
print("Failed saving screenshot due to \(error)")
}
This gives me the following output:
Saving to: file:///var/mobile/Containers/Data/Application/77D52C66-353B-4029-97D5-48E6BAE35C92/Downloads/appshot.png
Failed saving screenshot due to Error Domain=NSCocoaErrorDomain Code=4 "The file “appshot.png” doesn’t exist." UserInfo={NSFilePath=///var/mobile/Containers/Data/Application/77D52C66-353B-4029-97D5-48E6BAE35C92/Downloads/appshot.png, NSUnderlyingError=0x1c405bc60 {Error Domain=NSPOSIXErrorDomain Code=2 "No such file or directory"}}
Of course, the file doesn't exist because that's the file I'm trying to create. But /var/mobile doesn't exist on my laptop either -- it looks like the path FileManager is building may exist on the AppleTV device, but I want it on my laptop where my test script is executing.
So I backed out to a much more simple case, and even this is giving me problems:
let str = "This is a test"
let path = "file:///Users/haljor/foo.txt"
let pathUrl = URL(string: path)!
print("Path: \(path)")
print("URL: \(pathUrl)")
do {
try str.write(to: pathUrl, atomically: true, encoding: .utf8)
} catch {
print("Caught error writing to \(pathUrl): \(error)")
}
And here's the output:
Path: file:///Users/haljor/foo.txt
URL: file:///Users/haljor/foo.txt
Caught error writing to file:///Users/haljor/foo.txt: Error Domain=NSCocoaErrorDomain Code=4 "The folder “foo.txt” doesn’t exist." UserInfo={NSURL=file:///Users/haljor/foo.txt, NSUserStringVariant=Folder, NSUnderlyingError=0x1c40553f0 {Error Domain=NSPOSIXErrorDomain Code=2 "No such file or directory"}}
Here, it looks like it's trying to write to a folder at the path I specified, not a file. Clearly there's something I'm not understanding in each of these cases.
I don't really have a preference for whether I use a fully-specified path or something using FileManager -- it just needs to land somewhere on my laptop (not the TV device). What am I missing?
You can add an attachment to the test case and save it to disk too. The problem was that Downloads folder may not exist in the container yet. The best way to handle this is via init-once property:
var downloadsFolder: URL = {
let fm = FileManager.default
let folder = fm.urls(for: .downloadsDirectory, in: .userDomainMask)[0]
var isDirectory: ObjCBool = false
if !(fm.fileExists(atPath: folder.path, isDirectory: &isDirectory) && isDirectory.boolValue) {
try! fm.createDirectory(at: folder, withIntermediateDirectories: false, attributes: nil)
}
return folder
}()
func test() {
let appshot = XCUIScreen.main.screenshot()
let attachment = XCTAttachment(screenshot: appshot)
attachment.lifetime = .keepAlways
self.add(attachment)
// Save to container
let url = downloadsFolder.appendingPathComponent("appshot.png")
try! appshot.pngRepresentation.write(to: url)
}
If you want to view the attachment, right-click on the test case, select Jump to Report and expand the tree. You will see the screenshot eventually:

How to Use SwiftyDropbox's "destination" with a Download

In reviewing the SwiftyDropbox tutorial in the v2 Dropbox API, it shows how to perform a download:
// Download a file
let destination : (NSURL, NSHTTPURLResponse) -> NSURL = { temporaryURL, response in
let fileManager = NSFileManager.defaultManager()
let directoryURL = fileManager.URLsForDirectory(.DocumentDirectory, inDomains: .UserDomainMask)[0]
// generate a unique name for this file in case we've seen it before
let UUID = NSUUID().UUIDString
let pathComponent = "\(UUID)-\(response.suggestedFilename!)"
return directoryURL.URLByAppendingPathComponent(pathComponent)
}
client.files.download(path: "/MyFile.db", destination: destination).response { response, error in
if let (metadata, url) = response {
print("*** Download file ***")
let data = NSData(contentsOfURL: url)
print("Downloaded file name: \(metadata.name)")
print("Downloaded file url: \(url)")
print("Downloaded file data: \(data)")
} else {
print(error!)
}
}
I'm unclear what's going on with the destination part. Why do I need to generate a random string for the filename?
When I try to specify my own filename, the download doesn't seem to work:
let destination : (NSURL, NSHTTPURLResponse) -> NSURL = { temporaryURL, response in
let directoryURL = NSFileManager.defaultManager().URLsForDirectory(.DocumentDirectory, inDomains: .UserDomainMask)[0]
return directoryURL.URLByAppendingPathComponent("MyFile.db")
}
I want to download a file from Dropbox named MyFile.db and I want to put it in my device's documents directory with the name MyFile.db and overwrite it if it's already there.
How can I do that?
When you say it doesn't seem to work, I expect you mean you get an error like this:
Error Domain=NSCocoaErrorDomain Code=516 "“CFNetworkDownload_bPYhu1.tmp” couldn’t be moved to “Documents” because an item with the same name already exists." UserInfo={NSSourceFilePathErrorKey=..., NSUserStringVariant=(
Move
), NSDestinationFilePath=..., NSUnderlyingError=0x7fda0a67cea0 {Error Domain=NSPOSIXErrorDomain Code=17 "File exists"}}
SwiftyDropbox, by virtue of using AlamoFire, doesn't currently let you overwrite files using the download function.
Specifically, SwiftyDropbox calls download in AlamoFire, and AlamoFire calls NSFileManager.moveItemAtURL. The documentation for NSFileManager.moveItemAtURL says:
If an item with the same name already exists at dstURL, this method aborts the move attempt and returns an appropriate error.
So, it seems like it's just being cautious, and making it hard for your app to accidentally overwrite (ad potentially lose) data. If you definitely know you want to overwrite a particular file, you'll need to do so explicitly, after the Dropbox API call. We'll consider this a feature request though.
Update: SwiftyDropbox now offers the ability to overwrite the files directly as of version 3.1.0, e.g., using download(path:rev:overwrite:destination:).