How to clean nsurlsession downloadTask generate tmp File? - ios

If I create a DownloadTask by nsurlsession, there was a tmp file named like 'CFNetworkDownload_1vY41L.tmp' in /Developer/tmp/ folder.
Then how to delete the tmp file when I delete downloadTask?
Moreover, I don't want to delete all tmp file because there are other downloadTask cache file.

Apple's documentation says that the file will be deleted once the download block finishes, check location explanation. And yes it is deleted, at least in iOS 12, you have to move it before it completes, no need to free space.
Example:
let task = self.session.downloadTask(with: request) { [weak self] url, response, error in
if let error = error {
...
}
guard let httpResponse = response as? HTTPURLResponse else {
fatalError("Couldn't get HTTP response")
}
if 200..<300 ~= httpResponse.statusCode, let downloadedPath = url {
// Move file in downloadedPath to a documents or other location
}
}
downloadPath will have the location of the file.

You can delete the file using removeItemAtPath:error: method of NSFileManager
if ([[NSFileManager defaultManager] removeItemAtPath:[NSString stringWithFormat:#"%#%#", NSTemporaryDirectory(), #"Your File Name"] error:NULL])
{
NSLog(#"File deleted !!!");
}
else
{
NSLog(#"Couldn't delete the file !!!");
}

Related

Get the names of files in an iCloud Drive folder that haven't been downloaded yet

I’m trying to get the names of all files and folders in an iCloud Drive directory:
import Foundation
let fileManager = FileManager.default
let directoryURL = URL(string: "folderPathHere")!
do {
let directoryContents = try fileManager.contentsOfDirectory(at: directoryURL, includingPropertiesForKeys: nil, options: [.skipsSubdirectoryDescendants, .skipsHiddenFiles])
for url in directoryContents {
let fileName = fileManager.displayName(atPath: url.absoluteString)
print(fileName)
}
} catch let error {
let directoryName = fileManager.displayName(atPath: directoryURL.absoluteString)
print("Couldnt get contents of \(directoryName): \(error.localizedDescription)")
}
It appears that any iCloud files that haven’t been downloaded to the device don’t return URLs.
I know I can check if a path contains a ubiquitous item when I already know the path with the code below (even if it isn’t downloaded):
fileManager.isUbiquitousItem(at: writePath)
Is there a way to get the URLs & names of those iCloud files without downloading them first?
The directory URL is a security-scoped URL constructed from bookmark data in case that makes any difference (omitted that code here for clarity).
Thanks
Found the answer. I was skipping hidden files with ".skipsHiddenFiles", but the non-downloaded files are actually hidden files, named: ".fileName.ext.iCloud".
Remove the skips hidden files option now works as expected.
You need to use a NSFileCoordinator to access the directory in iCloud Storage, and then normalize placeholder file names for items that haven't been downloaded yet:
let iCloudDirectoryURL = URL(...)
let fileCoordinator = NSFileCoordinator(filePresenter: nil)
fileCoordinator.coordinate(
readingItemAt: iCloudDirectoryURL,
options: NSFileCoordinator.ReadingOptions(),
error: nil
) { readingURL in
do {
let contents = try FileManager.default.contentsOfDirectory(
at: readingURL, includingPropertiesForKeys: nil
)
for url in contents {
print("\(canonicalURL(url))")
}
} catch {
print("Error listing iCloud directory: '\(error)'")
}
}
func canonicalURL(_ url: URL) -> URL {
let prefix = "."
let suffix = ".icloud"
var fileName = url.lastPathComponent
if fileName.hasPrefix(prefix), fileName.hasSuffix(suffix) {
fileName.removeFirst(prefix.count)
fileName.removeLast(suffix.count)
var result = url.deletingLastPathComponent()
result.append(path: fileName)
return result
} else {
return url
}
}

Swift File Download Issue

I am trying to download a plist file from a remote location and use it in the iOS app I am creating. The file is going to be used for calendar details within the app's calendar. The goal is obviously that I can update the remote file instead of having to push updates to the app itself every time we need to make changes to calendar details.
I started with the code used in this example: Download File From A Remote URL
Here is my modified version:
// Create destination URL
let documentsUrl:URL = FileManager.default.urls(for: .documentDirectory, in: .userDomainMask).first as URL!
let destinationFileUrl = documentsUrl.appendingPathComponent("2017.plist")
//let destinationFileUrl = URL(string: Bundle.main.path(forResource: String(currentYear), ofType: "plist")!)
//Create URL to the source file you want to download
let fileURL = URL(string: "https://drive.google.com/open?id=0BwHDQFwaL9DuLThNYWwtQ1VXblk")
let sessionConfig = URLSessionConfiguration.default
let session = URLSession(configuration: sessionConfig)
let request = URLRequest(url:fileURL!)
let task = session.downloadTask(with: request) { (tempLocalUrl, response, error) in
if let tempLocalUrl = tempLocalUrl, error == nil {
// Success
if let statusCode = (response as? HTTPURLResponse)?.statusCode {
print("Successfully downloaded. Status code: \(statusCode)")
}
do {
try FileManager.default.removeItem(at: destinationFileUrl)
try FileManager.default.moveItem(at: tempLocalUrl, to: destinationFileUrl)
print("File was replaced")
print(NSArray(contentsOf: tempLocalUrl))
//print(tempLocalUrl)
} catch (let writeError) {
print("Error creating a file \(String(describing: destinationFileUrl)) : \(writeError)")
}
} else {
print("Error took place while downloading a file. Error description: %#", error?.localizedDescription as Any);
}
}
task.resume()
I originally tried to overwrite the file that is bundled with the app to being with, that resulted in errors. So I instead tried to just save it in the app's documents folder and that removed that error. I had to make sure and remove any previous version of the file because it was giving me a file already exists error after the first run.
While it says everything is working (The outputs for both successful download and replaced file happen) when I print the contents of the array from the downloaded URL it just gives me nil.
This is my first attempt to use any kind of external resources in an app. Before I have always kept everything internal, so I am sure there is something glaringly obvious I am missing.
Update 1:
I realized I didn't have the correct URL to use to download a file from a Google drive. That line of code has been changed to:
let fileURL = URL(string: "https://drive.google.com/uc?export=download&id=0BwHDQFwaL9DuLThNYWwtQ1VXblk")
So now I actually am downloading the plist like I originally thought I was. Even removing the deletion issue mentioned in the first comment, I still can't get the downloaded file to actually replace the existing one.
Update 2:
I have reduced the actual file manipulation down to the following:
do {
try FileManager.default.replaceItemAt(destinationFileUrl, withItemAt: tempLocalUrl)
print("File was replaced")
print(NSArray(contentsOf: destinationFileUrl))
} catch (let writeError) {
print("Error creating a file \(String(describing: destinationFileUrl)) : \(writeError)")
}
} else {
print("Error took place while downloading a file. Error description: %#", error?.localizedDescription as Any);
}
After the replacement is performed the output of the file shows the correct new contents that were downloaded from the internet.
Later in the code when I try and access the file it seems to be nil in content again.
Look at your download completion code. You:
Delete the file at the destination URL (in case there was one
leftover)
MOVE the temp file to the destination URL (removing it from the temp
URL)
Try to load the file from the temp URL.
What's wrong with this picture?
You are trying to get the contents of the moved file. You already moved the file to destination url and then you are trying to get the contents of the file from temporary location.
For getting file data, Please try the following :
let fileData = try! String(contentsOf: destinationFileUrl, encoding: String.Encoding.utf8)
print(fileData)

How to handle errors with Swift (FileManager and others in general) [closed]

Closed. This question is opinion-based. It is not currently accepting answers.
Want to improve this question? Update the question so it can be answered with facts and citations by editing this post.
Closed 6 years ago.
Improve this question
Note: I posted a lazy question before for converting code to Swift 3 (deleted it)
Apple has some sample code for managing files. It is an old guide and is all in Objective-C. I converted the snippet to Swift 3. My concern is over the error handling part. I'm nesting multiple do/catch blocks... just want to know if this is the optimal way of doing things??
There is a similar question/amswer to this here.
The document is: Apple File System Programming Guide, under section "Managing Files and Directories".
This is my code (converted to Swift 3):
func backupMyApplicationData() {
// Get the application's main data directory
let directories = FileManager.default.urls(for: .applicationSupportDirectory, in: .userDomainMask)
guard directories.count > 0,
let appSupportDir = directories.first,
let bundleID = Bundle.main.bundleIdentifier else {
return
}
// Build a path to ~/Library/Application Support/<bundle_ID>/Data
// where <bundleID> is the actual bundle ID of the application.
let appDataDir = appSupportDir.appendingPathComponent(bundleID).appendingPathComponent("Data")
// Copy the data to ~/Library/Application Support/<bundle_ID>/Data.backup
let backupDir = appDataDir.appendingPathExtension("backup")
// Perform the copy asynchronously.
DispatchQueue.global(qos: .default).async { _ in
// It's good habit to alloc/init the file manager for move/copy operations,
// just in case you decide to add a delegate later.
let fileManager = FileManager()
do {
// Just try to copy the directory.
try fileManager.copyItem(at: appDataDir, to: backupDir)
} catch CocoaError.fileWriteFileExists {
// If an error occurs, it's probably because a previous backup directory
// already exists. Delete the old directory and try again.
do {
try fileManager.removeItem(at: backupDir)
} catch let error {
// If the operation failed again, abort for real.
print("Operation failed again, abort with error: \(error)")
}
} catch let error {
// If the operation failed again, abort for real.
print("Other error: \(error)")
}
}
}
This is Apple's code in their docs which I converted:
- (void)backupMyApplicationData {
// Get the application's main data directory
NSArray* theDirs = [[NSFileManager defaultManager] URLsForDirectory:NSApplicationSupportDirectory
inDomains:NSUserDomainMask];
if ([theDirs count] > 0)
{
// Build a path to ~/Library/Application Support/<bundle_ID>/Data
// where <bundleID> is the actual bundle ID of the application.
NSURL* appSupportDir = (NSURL*)[theDirs objectAtIndex:0];
NSString* appBundleID = [[NSBundle mainBundle] bundleIdentifier];
NSURL* appDataDir = [[appSupportDir URLByAppendingPathComponent:appBundleID]
URLByAppendingPathComponent:#"Data"];
// Copy the data to ~/Library/Application Support/<bundle_ID>/Data.backup
NSURL* backupDir = [appDataDir URLByAppendingPathExtension:#"backup"];
// Perform the copy asynchronously.
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
// It's good habit to alloc/init the file manager for move/copy operations,
// just in case you decide to add a delegate later.
NSFileManager* theFM = [[NSFileManager alloc] init];
NSError* anError;
// Just try to copy the directory.
if (![theFM copyItemAtURL:appDataDir toURL:backupDir error:&anError]) {
// If an error occurs, it's probably because a previous backup directory
// already exists. Delete the old directory and try again.
if ([theFM removeItemAtURL:backupDir error:&anError]) {
// If the operation failed again, abort for real.
if (![theFM copyItemAtURL:appDataDir toURL:backupDir error:&anError]) {
// Report the error....
}
}
}
});
}
}
Any thoughts?
You forgot to retry the copy operation after deleting an existing backup. Also, "catch let error" can be written as just "catch" because the error will be automatically assigned to a constant named "error" if you don't specify a catch pattern. Here is your code with these changes:
func backupMyApplicationData() {
// Get the application's main data directory
let directories = FileManager.default.urls(for: .applicationSupportDirectory, in: .userDomainMask)
guard
directories.count > 0,
let appSupportDir = directories.first,
let bundleID = Bundle.main.bundleIdentifier
else {
return
}
// Build a path to ~/Library/Application Support/<bundle_ID>/Data
// where <bundleID> is the actual bundle ID of the application.
let appDataDir = appSupportDir.appendingPathComponent(bundleID).appendingPathComponent("Data")
// Copy the data to ~/Library/Application Support/<bundle_ID>/Data.backup
let backupDir = appDataDir.appendingPathExtension("backup")
// Perform the copy asynchronously.
DispatchQueue.global(qos: .default).async { _ in
// It's good habit to alloc/init the file manager for move/copy operations,
// just in case you decide to add a delegate later.
let fileManager = FileManager()
do {
// Just try to copy the directory.
try fileManager.copyItem(at: appDataDir, to: backupDir)
} catch CocoaError.fileWriteFileExists {
// Error occurred because a previous backup directory
// already exists. Delete the old directory and try again.
do {
try fileManager.removeItem(at: backupDir)
} catch {
// The delete operation failed, abort.
print("Deletion of existing backup failed. Abort with error: \(error)")
return
}
do {
try fileManager.copyItem(at: appDataDir, to: backupDir)
} catch {
// The copy operation failed again, abort.
print("Copy operation failed again. Abort with error: \(error)")
}
} catch {
// The copy operation failed for some other reason, abort.
print("Copy operation failed for other reason. Abort with error: \(error)")
}
}
}
If you want a translation that's closer to the Objective-C original, where there's only one error output, try this:
func backupMyApplicationData() {
// Get the application's main data directory
let directories = FileManager.default.urls(for: .applicationSupportDirectory, in: .userDomainMask)
guard
directories.count > 0,
let appSupportDir = directories.first,
let bundleID = Bundle.main.bundleIdentifier
else {
return
}
// Build a path to ~/Library/Application Support/<bundle_ID>/Data
// where <bundleID> is the actual bundle ID of the application.
let appDataDir = appSupportDir.appendingPathComponent(bundleID).appendingPathComponent("Data")
// Copy the data to ~/Library/Application Support/<bundle_ID>/Data.backup
let backupDir = appDataDir.appendingPathExtension("backup")
// Perform the copy asynchronously.
DispatchQueue.global(qos: .default).async { _ in
// It's good habit to alloc/init the file manager for move/copy operations,
// just in case you decide to add a delegate later.
let fileManager = FileManager()
// Just try to copy the directory.
if (try? fileManager.copyItem(at: appDataDir, to: backupDir)) == nil {
// If an error occurs, it's probably because a previous backup directory
// already exists. Delete the old directory and try again.
if (try? fileManager.removeItem(at: backupDir)) != nil {
do {
try fileManager.copyItem(at: appDataDir, to: backupDir)
} catch {
// The copy retry failed.
print("Failed to backup with error: \(error)")
}
}
}
}
}
Your translation of the Objective-C original is incorrect, as has been pointed out in another answer. However, it seems that that is not really your question. It seems that your question is about the nesting.
To answer it, just look at the original that you are trying to imitate:
NSFileManager* theFM = [[NSFileManager alloc] init];
NSError* anError;
if (![theFM copyItemAtURL:appDataDir toURL:backupDir error:&anError]) {
if ([theFM removeItemAtURL:backupDir error:&anError]) {
if (![theFM copyItemAtURL:appDataDir toURL:backupDir error:&anError]) {
}
}
}
Notice anything? Nesting. So the only difference between the structure of your code and the structure of the original is what is nested. The Objective-C original makes a nest of if clauses. Your Swift translation makes a nest of do/catch blocks — as it must do, since e.g. Objective-C copyItemAtURL returns a BOOL whereas Swift copyItem(at:) does not — if there's a problem, it throws.
So I think we may conclude that nesting is exactly the right thing to do. Indeed, what's wrong with your code (the reason it isn't an accurate translation of the original) is that it doesn't nest deep enough!
You could try to wrestle at least one of the catch blocks out of existence by substituting an if block testing what kind of error this is, but you would still be nesting, so why bother? You would just be throwing away all the elegance and clarity of the do/catch construct.
In Swift 2 and 3, there have 3 way to use a method that may generate errors.
If error happened, tell me.
do {
try something()
try somethingElse()
print("No error.")
} catch {
print("Error:", error)
}
I don't care about error. If error happen, just return nil.
try? something()
I don't believe this will have any error. If error happen, crash my app please.
try! something()

Uploading a file from AVCapture using AFNetworking

I have a video that is captured with AVCapture, and I'm trying to upload with AFNetworking with Swift.
Code:
let manager = AFHTTPRequestOperationManager()
let url = "http://localhost/test/upload.php"
var fileURL = NSURL.fileURLWithPath(string: ViewControllerVideoPath)
var params = [
"familyId":locationd,
"contentBody" : "Some body content for the test application",
"name" : "the name/title",
"typeOfContent":"photo"
]
manager.POST( url, parameters: params,
constructingBodyWithBlock: { (data: AFMultipartFormData!) in
println("")
var res = data.appendPartWithFileURL(fileURL, name: "fileToUpload", error: nil)
println("was file added properly to the body? \(res)")
},
success: { (operation: AFHTTPRequestOperation!, responseObject: AnyObject!) in
println("Yes thies was a success")
},
failure: { (operation: AFHTTPRequestOperation!, error: NSError!) in
println("We got an error here.. \(error.localizedDescription)")
})
The code above fails, I keep getting
was file added properly to the body? false"
note that ViewControllerVideoPath is a string containing the location of the video which is:
"/private/var/mobile/Containers/Data/Application/1110EE7A-7572-4092-8045-6EEE1B62949/tmp/movie.mov"
using println().... The code above works when I'm uploading a file included in the directory and using:
var fileURL = NSURL.fileURLWithPath(NSBundle.mainBundle().pathForResource("test_1", ofType: "mov")!)
So definitely my PHP code is fine, and the problem lies with uploading that file saved on the device, what am I doing wrong here?
Comments don't allow a full explanation so here is more info;
NSBundle.mainBundle() refers to a path in the bundle file The path in the simulator differs from that of the application ... this is not what you want. There are a number of "folders" you can access based on your needs (private or sharable/files that can get backed up to the cloud). NSPathUtils.h gives a breakdown of the paths available. In keeping with conventions used by most, you should probably create a private path under your application path by doing something like;
- (NSURL *) applicationPrivateDocumentsDirectory{
NSURL *pathURL = [[self applicationLibraryDirecory]URLByAppendingPathComponent:#"MyApplicationName"];
return pathURL;
}
- (NSURL *) applicationLibraryDirecory{
return [[[NSFileManager defaultManager] URLsForDirectory:NSLibraryDirectory inDomains:NSUserDomainMask] lastObject];
}
You can test if it exists, if not, create it ... then store your video files in this path, and pass this to your AVCapture as the location to store the file.
Here are the code that can do following functionality in swift.
1 : Check weather directory exist or not. if not exist then create directory(Directory has given application name) in document directory folder.
2 : Now we have application directory. so all file that from application will write/read in/from this directory.
let file = "file.txt"
let directoryName = “XYZ” // Here “XYZ” is project name.
var error : NSError?
let filemgr = NSFileManager.defaultManager()
let dirPaths = NSSearchPathForDirectoriesInDomains(.DocumentDirectory,
.UserDomainMask, true)
let documentsDirectory = dirPaths[0] as! String
var dataPath = documentsDirectory.stringByAppendingPathComponent(directoryName)
if !NSFileManager.defaultManager().fileExistsAtPath(dataPath) {
NSFileManager.defaultManager().createDirectoryAtPath(dataPath, withIntermediateDirectories: false, attributes: nil, error: &error)
} else {
println("not creted or exist")
}
Now we have Directory so only need to write/read data from directory.
how to write file in document directory in swift
let filePath = dataPath.stringByAppendingPathComponent(file);
let text = "some text"
//writing
text.writeToFile(filePath, atomically: false, encoding: NSUTF8StringEncoding, error: nil);
How to read file from document directory.
let filePath = dataPath.stringByAppendingPathComponent(file);
// Read file
let text2 = String(contentsOfFile: filePath, encoding: NSUTF8StringEncoding, error: nil)
Output :
Hope this will help you.

How to Clear PFFIle Cache (Parse.com)

I have an app that downloads and displays a lot of images from parse. An image is added to the database almost every minute. Since PFFile is automatically cached with no expiration date, even though I only need to display recent images, the older images still stay in cache thus occupying a lot of storage space. Because of this now the app takes about 5GB of storage on my iPhone. I have been doing a lot of research on this issue and found out that Parse does not have a public api for cleaning up PFFile Cache and also it doesn't allow setting expiration date on the cached files. Is there a workaround on this where I could manually delete older cache data?
Thank you in advance.
Here is a method you can use to clean up the PFFile Cache. If you call it when your app starts, before initializing Parse, I think it should be safe. You can check the file creation date in the for loop if you don't want to remove everything.
+ (void)cleanUpPFFileCacheDirectory
{
NSError *error = nil;
NSFileManager *fileManager = [NSFileManager defaultManager];
NSURL *cacheDirectoryURL = [[fileManager URLsForDirectory:NSCachesDirectory inDomains:NSUserDomainMask] lastObject];
NSURL *PFFileCacheDirectoryURL = [cacheDirectoryURL URLByAppendingPathComponent:#"Parse/PFFileCache" isDirectory:YES];
NSArray *PFFileCacheDirectory = [fileManager contentsOfDirectoryAtURL:PFFileCacheDirectoryURL includingPropertiesForKeys:nil options:0 error:&error];
if (!PFFileCacheDirectory || error) {
if (error && error.code != NSFileReadNoSuchFileError) {
NSLog(#"Error : Retrieving content of directory at URL %# failed with error : %#", PFFileCacheDirectoryURL, error);
}
return;
}
for (NSURL *fileURL in PFFileCacheDirectory) {
BOOL success = [fileManager removeItemAtURL:fileURL error:&error];
if (!success || error) {
NSLog(#"Error : Removing item at URL %# failed with error : %#", fileURL, error);
error = nil;
}
}
}
TimWhiting's answer translated to Swift 2.1:
Note: I have to say thought that is better to use file urls and use your own cache system as Matt S says, I'm using this just for testing purposes. I wish also that Parse could provide us with the correct path instead of having to hardcode it, that's why I think is better to use URLs.
func cleanUpParseDirectory(){
let parseFilesCache = "Parse/PFFileCache"
let fileManager = NSFileManager.defaultManager()
let cacheDirectoryURL = fileManager.URLsForDirectory(NSSearchPathDirectory.CachesDirectory, inDomains: NSSearchPathDomainMask.UserDomainMask)
let PFFileCacheDirectoryURL = cacheDirectoryURL[0].URLByAppendingPathComponent(parseFilesCache, isDirectory: true)
do {
let PFFileCacheDirectory = try fileManager.contentsOfDirectoryAtURL(PFFileCacheDirectoryURL, includingPropertiesForKeys: nil, options: [])
print("number of cached files: \(PFFileCacheDirectory.count)")
for fileURL in PFFileCacheDirectory {
try fileManager.removeItemAtURL(fileURL)
}
print("Success removing items")
} catch let error {
print("Error: \(error)")
}
}
Per this answer from Hector: https://www.parse.com/questions/pffile-cache-size you can manually clear your applications ~/Library/Caches folder if you are insistent upon it. However, I'm fairly certain this will also impact things like NSURL/AFNetworking caches, amongst others.
My suggestion? Don't use PFFile to download the file. PFFile gives you back the remote URL to where it's hosted on Parse's site, so you can pass that to something like AFNetworking or NSURLSession to actually download the image for you, and then you can then assign cache lifetimes (or manage it yourself) since those systems actually support that, unlike PFFile.
deadbeef's answer translated to Swift for anyone that needs it.
func cleanUpParseDirectory(){
var error: NSError?
var fileManager: NSFileManager = NSFileManager.defaultManager()
var cacheDirectoryURL: [NSURL] = fileManager.URLsForDirectory(NSSearchPathDirectory.CachesDirectory, inDomains: NSSearchPathDomainMask.UserDomainMask) as! [NSURL]
var PFFileCacheDirectoryURL: NSURL = cacheDirectoryURL[0].URLByAppendingPathComponent("Parse/PFFileCache", isDirectory: true)
var PFFileCacheDirectory: [AnyObject]? = fileManager.contentsOfDirectoryAtURL(PFFileCacheDirectoryURL, includingPropertiesForKeys: nil, options: NSDirectoryEnumerationOptions.allZeros, error: &error)// [fileManager contentsOfDirectoryAtURL:PFFileCacheDirectoryURL includingPropertiesForKeys:nil options:0 error:&error];
if (PFFileCacheDirectory == nil || error != nil) {
if ((error != NSFileReadNoSuchFileError && error!.code != NSFileReadNoSuchFileError)) {
println("error finding path")
} else {
println("no error finding path")
}
return
}
println("number of cached files: \(PFFileCacheDirectory!.count)")
for fileURL in PFFileCacheDirectory! {
var success: Bool = fileManager.removeItemAtURL(fileURL as! NSURL, error: &error)
if ((!success != false || error != nil) ) {
println("error removing item")
error = nil
} else {
println("success removing item")
}
}
}

Resources