Access contents of SKDownload after processing in-app purchase using SKPaymentQueue - swift

I've been trying to access a downloaded SKDownload zip file after a successful in-app purchase as such:
func paymentQueue(_ queue: SKPaymentQueue, updatedDownloads downloads: [SKDownload]) {
downloads.forEach ({ (download) -> Void in
switch download.state {
...
case .finished:
self.processDownload(download: download)
...
break
...
})
}
Here's the function that processes the downloaded SKDownload file:
func procesessDownload(download: SKDownload) {
guard let hostedContentPath = download.contentURL else {
return
}
do {
// THIS LINE OF CODE THROWS WITH THE ERROR POSTED BELOW
let files = try FileManager.default.contentsOfDirectory(atPath: hostedContentPath.relativePath)
} catch {
//catch error
}
}
When I inspect the download.contentURL it is there:
file:///private/var/mobile/Containers/Data/Application/D755EC3C-2BA6-43A8-BB21-938B38EBFCB7/Library/Caches/EB11461D-71C8-42C7-9C99-E6F8B81161EE.zip/
- _url : file:///private/var/mobile/Containers/Data/Application/D755EC3C-2BA6-43A8-BB21-938B38EBFCB7/Library/Caches/EB11461D-71C8-42C7-9C99-E6F8B81161EE.zip/
But upon trying to access the file using FileManager using this line of code above:
let files = try FileManager.default.contentsOfDirectory(atPath: hostedContentPath.relativePath)
I keep getting the following error stating that the file doesn't exist:
Error Domain=NSCocoaErrorDomain Code=260 "The file “EB11461D-71C8-42C7-9C99-E6F8B81161EE.zip” couldn’t be opened because there is no such file." UserInfo={NSFilePath=/private/var/mobile/Containers/Data/Application/D755EC3C-2BA6-43A8-BB21-938B38EBFCB7/Library/Caches/EB11461D-71C8-42C7-9C99-E6F8B81161EE.zip, NSUnderlyingError=0x28320d500 {Error Domain=NSPOSIXErrorDomain Code=2 "No such file or directory"}}
I tried adding / at the end to treat the zip file as a directory and many other things to no avail.
I even tried to access the directory containing that file (up to the zip file):
/private/var/mobile/Containers/Data/Application/D755EC3C-2BA6-43A8-BB21-938B38EBFCB7/Library/Caches/
The error says that "Caches" directory not found.
I've also tried adding "Contents" to the path like one of the example codes I found by a random person.
I've been banging my head for more than a day straight and I couldn't find any documentation on the matter. Any help would be greatly appreciated, thanks!

Related

Permission denied in XCode debugging run

I want to read all files in a folder:
let fileManager = FileManager.default
let directoryEnumerator = fileManager.enumerator(at: self.url,
includingPropertiesForKeys: resourceKeys,
options: skipHiddenFiles ? [.skipsHiddenFiles] : [],
errorHandler: { (url, error) -> Bool in
print("directoryEnumerator error at \(url): ", error)
accessError = true
return true
})!
In that folder there are two files. But I get an Error, that I have not the permission to do this (to read it):
directoryEnumerator error at
file:///Users/Shared/MeinDesktop/fasttemp/tmp/src: Error
Domain=NSCocoaErrorDomain Code=257 "The file “src” couldn’t be opened
because you don’t have permission to view it."
UserInfo={NSURL=file:///Users/Shared/MeinDesktop/fasttemp/tmp/src,
NSFilePath=/Users/Shared/MeinDesktop/fasttemp/tmp/src,
NSUnderlyingError=0x600000a94240 {Error Domain=NSPOSIXErrorDomain
Code=1 "Operation not permitted"}}
I added Xcode itself as well as the generated app file to security section with full access. Nothing changed! The code worked for years, so I don't believe that this is wrong. Do I need some settings in XCode?
I got it:
There is a file in the project named "YourProjectName.entitlements".
Within that file there is a option to sandbox the app. Set this to "NO" and it works fine! Hurray

Firebase Storage and Swift - download task handling

I have a download task to get the video file. I checked the documentation about the download task but I don't see anything about handling the existing files.
Does the download task checks the existing file, then checks if it need to be resumed or leaves it alone?
If the download task fails in the middle of downloading, will the incomplete file still there?
If it don't overwrite the file, how do I compare the files in local and remote (file size? or MD5) to verify that I don't need to download the file?
Scenario - I have AppDelegateManager to download all of the videos beforehand. I don't want to download the videos each time the app is opened and wasn't sure if the videos were overwritten or not.
func getVideo(fileName : String, localUrl : URL, completion : #escaping (_ success : Bool) -> Void) {
let storage = Storage.storage()
let pathReference = storage.reference(withPath: FirebaseDirectoryNames.videos.rawValue + "/" + fileName)
let downloadTask = pathReference.write(toFile: localUrl) { url, error in
if let error = error {
os_log("error : ", log: self.tag, type: .debug, error.localizedDescription)
completion(false)
} else {
completion(true)
}
}
}

"Interrupted system call" when reading test data in XCTestCase

Using Xcode (11.4.1) on Catalina (10.15.4, only updated from Mojave 5 days ago), with a tiny SPM-based project opened in Xcode in "folder mode" (i.e. not an actual .xcodeproj), I have a test which should read some input data from a sample file. Following recommendations for constructing a URL to those files, Data(contentsOf:) cannot read it, although the generated URL is correct.
The code is:
func testCanReadConfigFromFile() {
let thisDirectory = URL(fileURLWithPath: #file)
.deletingLastPathComponent()
let url = thisDirectory
.appendingPathComponent("Test_data", isDirectory: true)
.appendingPathComponent("Config.json", isDirectory: false)
print(url.absoluteString)
do {
let _ = try Data(contentsOf: url)
} catch let error {
print(error)
print(error.localizedDescription)
}
}
The exception is:
Error Domain=NSCocoaErrorDomain Code=256 "The file “Config.json” couldn’t be opened."
UserInfo={NSFilePath=/full/path/to/project/Tests/ProjectTests/Test_data/Config.json,
NSUnderlyingError=0x100b0aa30
{Error Domain=NSPOSIXErrorDomain Code=4 "Interrupted system call"}}
If I take the string output by print(url.absoluteString) and in a terminal execute file <absoulteString>, it confirms that my file does exist at that path (and that it is JSON).
"Interrupted system call" makes me think Catalina's stricter sand-boxing rules are to blame, but I have not been shown a permissions dialog. This project is within my ~/Documents folder, which Xcode does have permission to read.
Edited to add:
swift test at the command line works. It's only running tests in Xcode that exhibits the problem.

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:

Swift 4 / Xcode 9.3 / iOS - Catch The file "x" couldn’t be opened because there is no such file with String(contentsOf: url)

Swift 4 / Xcode 9.3 / iOS
I'm using String(contentsOf: url) and in some cases I get an error because the file referenced by the URL doesn't exist. I want to handle that specific error case.
I know I can check to see if the file exists first, but I want to handle the error case instead.
Right now I am doing:
let data = try String(contentsOf: url)
Up until now I've been catching all errors, with something like:
do {
let data = try String(contentsOf: url)
//<other code here>
} catch let error {
fatalError("bad error: \(error)")
}
When the specific file I'm trying to pull from (from the give URL) doesn't exist, I get an error like this:
bad error: Error Domain=NSCocoaErrorDomain Code=260 "The file “whatever.json” couldn’t be opened because there is no such file." UserInfo={NSFilePath=/Users/myname/Library/Developer/CoreSimulator/Devices/6529F22A-5234-49D7-9BB3-B9C71474CC53/data/Containers/Data/Application/005C1F55-07E8-4CF4-86FD-2F00B1144FD4/Documents/whatever.json, NSUnderlyingError=0x608000058180 {Error Domain=NSPOSIXErrorDomain Code=2 "No such file or directory"}}
How do I catch this error separately so I can handle it separately from other errors? I've tried CocoaError.fileNoSuchFile and several in the POSIX error domain.
Thanks!
Okay, so I've been fighting this for awhile, and as soon as I posted, I ended up solving it.
the answer is CocoaError.fileReadNoSuchFile. I kept trying CocoaError.fileNoSuchFile.
So here's the answer:
do {
let data = try String(contentsOf: url)
//<other code here>
} catch CocoaError.fileReadNoSuchFile {
print("CAUGHT IT!")
} catch let error {
fatalError("bad error: \(error)")
}
Thanks!