NSUrlSessionDownloadDelegate - DidFinishDownloading checking for "File Not Found" - ios

I have setup a background downloader on my app for iOS. The only problem I am having is checking if the file exists server side (as it is possible that it may not).
public override void DidFinishDownloading(NSUrlSession session, NSUrlSessionDownloadTask downloadTask, NSUrl location)
This is always returning even if there is no file to grab but then will save the tmp file as if it has downloaded successfully. Like so.
// Create the filename
var documentsFolderPath = Environment.GetFolderPath(Environment.SpecialFolder.MyDocuments);
NSUrl destinationURL = NSUrl.FromFilename(Path.Combine(documentsFolderPath, destFile));
// Remove any existing file in our destination
NSError error;
fileManager.Remove(destinationURL, out error);
bool success = fileManager.Copy(sourceFile, documentsFolderPath + "/" + destFile, out error);
if (!success)
{
Console.WriteLine("Error during the copy: {0}", error.LocalizedDescription);
}
Now how can I check the response status from here? As I am downloading in the background I cannot use a completion block and that is the only place I can find where I can possibly get the response status.
The only other way I can see to handle this is to "ping" the file before I setup a task to check it is there, is this correct or can I just allow this download delegate to handle it?

Related

Download Zip file in Xamarin iOS Native

I have to download a Zip File in Xamarin iOS.
URL - https://osdn.net/projects/sfnet_fotohound/downloads/sample-pictures/Sample/Sample-Pictures.zip/
As soon as i hit this URL, the download should start and the same should get saved in a particular folder in the documents directory.
How should I implement the same in Xamarin Native iOS.
You can use NSURLSession to download the zip file.
Firstly, you should find the real download link of this download site.
You can refer to this.
In Chrome - run download as normal - then go to Menu - Downloads - and you should see the direct link which was used.
Actually, your file link is https://mirrors.netix.net/sourceforge/f/fo/fotohound/sample-pictures/Sample/Sample-Pictures.zip.
Now start to code. Create download task via NSURLSession:
public void downloadTask()
{
// Your file link.
NSUrl url = NSUrl.FromString("https://mirrors.netix.net/sourceforge/f/fo/fotohound/sample-pictures/Sample/Sample-Pictures.zip");
// Configure your download session.
var config = NSUrlSessionConfiguration.DefaultSessionConfiguration;
NSUrlSession session = NSUrlSession.FromConfiguration(config, new SimpleSessionDelegate(), new NSOperationQueue());
var downloadTask = session.CreateDownloadTask(NSUrlRequest.FromUrl(url));
// Start the session.
downloadTask.Resume();
Console.WriteLine("Start DownloadTask!!!");
}
Configure the callback DidFinishDownloading:
class SimpleSessionDelegate : NSUrlSessionDownloadDelegate
{
public override void DidFinishDownloading(NSUrlSession session, NSUrlSessionDownloadTask downloadTask, NSUrl location)
{
// Configure your destination path. Here's saved to /Documents/ folder.
var documents = Environment.GetFolderPath(Environment.SpecialFolder.MyDocuments);
var destinationPath = Path.Combine(documents, "Sample.zip");
if (File.Exists(location.Path))
{
NSFileManager fileManager = NSFileManager.DefaultManager;
NSError error;
// Remove the same name file in destination path.
fileManager.Remove(destinationPath, out error);
// Copy the file from the tmp directory to your destination path. The tmp file will be removed when this delegate finishes.
bool success = fileManager.Copy(location.Path, destinationPath, out error);
if (!success)
{
Console.WriteLine("Error during the copy: {0}", error.LocalizedDescription);
}
}
}
}
Now the file has been saved at the documents directory and named Sample.zip.

How to clean nsurlsession downloadTask generate tmp File?

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 !!!");
}

Swift: downloadTaskWithURL sometimes "succeeds" with non-nil location even though file does not exist

The downloadTaskWithURL function sometimes returns a non-nil location for a file that does not exist.
There is no file at http://192.168.0.102:3000/p/1461224691.mp4 in the test environment.
Most of the time, invoking downloadTaskWithURL on this URL results in the expected error message:
Error downloading message during network operation. Download URL:
http://192.168.0.102:3000/p/1461224691.mp4. Location: nil. Error:
Optional(Error Domain=NSURLErrorDomain Code=-1100 "The requested URL
was not found on this server." UserInfo=0x17547b640
{NSErrorFailingURLKey=http://192.168.0.102:3000/p/1461224691.mp4,
NSLocalizedDescription=The requested URL was not found on this
server.,
NSErrorFailingURLStringKey=http://192.168.0.102:3000/p/1461224691.mp4})
Occasionally, and in a non-deterministic way, downloadTaskWithURL believes the file exists and writes something to the location variable. As a result, the guard condition does not fail, and the code continues to execute ... which it should not.
The permanent file created by fileManager.moveItemAtURL(location!, toURL: fileURL) is only 1 byte, confirming that the network file never existed in the first place.
Why does downloadTaskWithURL behave like this?
func download() {
// Verify <networkURL>
guard let downloadURL = NSURL(string: networkURL) where !networkURL.isEmpty else {
print("Error downloading message: invalid download URL. URL: \(networkURL)")
return
}
// Generate filename for storing locally
let suffix = (networkURL as NSString).pathExtension
let localFilename = getUniqueFilename("." + suffix)
// Create download request
let task = NSURLSession.sharedSession().downloadTaskWithURL(downloadURL) { location, response, error in
guard location != nil && error == nil else {
print("Error downloading message during network operation. Download URL: \(downloadURL). Location: \(location). Error: \(error)")
return
}
// If here, no errors so save message to permanent location
let fileManager = NSFileManager.defaultManager()
do {
let documents = try fileManager.URLForDirectory(.DocumentDirectory, inDomain: .UserDomainMask, appropriateForURL: nil, create: false)
let fileURL = documents.URLByAppendingPathComponent(localFilename)
try fileManager.moveItemAtURL(location!, toURL: fileURL)
self.doFileDownloaded(fileURL, localFilename: localFilename)
print("Downloaded file # \(localFilename). Download URL: \(downloadURL)")
} catch {
print("Error downloading message during save operation. Network URL: \(self.networkURL). Filename: \(localFilename). Error: \(error)")
}
}
// Start download
print("Starting download # \(downloadURL)")
task.resume()
}
Just to clarify, the server is returning a 404, but the download task is returning a basically-empty file? And you're certain that the server actually returned an error code (by verifying the server logs)?
Either way, I would suggest checking the status code in the response object. If it isn't a 200, then the download task probably just downloaded the response body of an error page. Or, if the status code is 0, the connection timed out. Either way, treat it as a failure.
You might also try forcing this to all happen on a single thread and see if the nondeterminism goes away.

SoundCloud API iOS Fails to upload audio with a HTTP 422 error

I'm trying to put a super basic Soundcloud feature in my app to upload single .wav files using their UI. I followed their guide and I didn't really need anything outside of the bare bones share menu so I assumed this code would work:
NSURL *trackURL = [[NSURL alloc] initWithString:[docsDir stringByAppendingPathComponent:fileToShare]];
SCShareViewController *shareViewController;
shareViewController = [SCShareViewController shareViewControllerWithFileURL:trackURL
completionHandler:^(NSDictionary *trackInfo, NSError *error){
if (SC_CANCELED(error)) {
NSLog(#"Canceled!");
} else if (error) {
NSLog(#"Ooops, something went wrong: %#", [error localizedDescription
]);
} else {
// If you want to do something with the uploaded
// track this is the right place for that.
NSLog(#"Uploaded track: %#", trackInfo);
}
}];
// If your app is a registered foursquare app, you can set the client id and secret.
// The user will then see a place picker where a location can be selected.
// If you don't set them, the user sees a plain plain text filed for the place.
[shareViewController setFoursquareClientID:#"<foursquare client id>"
clientSecret:#"<foursquare client secret>"];
// We can preset the title ...
[shareViewController setTitle:#"Funny sounds"];
// ... and other options like the private flag.
[shareViewController setPrivate:NO];
// Now present the share view controller.
[self presentModalViewController:shareViewController animated:YES];
[trackURL release];
However I get an HTTP 422 error with this appearing in my debug console:
2016-02-29 11:04:47.129 synthQ[801:443840] parameters: {
"track[asset_data]" = "/var/mobile/Containers/Data/Application/EE58E5CA-B30C-44EB-B207-EB3368263319/Documents/bb.wav";
"track[downloadable]" = 1;
"track[post_to][]" = "";
"track[sharing]" = public;
"track[tag_list]" = "\"soundcloud:source=synthQ\"";
"track[title]" = "Funny sounds";
"track[track_type]" = recording;
}
2016-02-29 11:04:47.164 synthQ[801:444011] -[NXOAuth2PostBodyStream open] Stream has been reopened after close
2016-02-29 11:04:47.373 synthQ[801:443840] Upload failed with error: HTTP Error: 422 Error Domain=NXOAuth2HTTPErrorDomain Code=422 "HTTP Error: 422" UserInfo={NSLocalizedDescription=HTTP Error: 422}
Does anyone have any ideas what could be going wrong here?
Thanks!
One of the reason to get HTTP 422 error in SoundCloud is:
If you are trying to upload a file for a first time with a new account, you need to verify your email address to complete your SoundCloud registration in order to upload your files.
There might be other reasons for this error, however for my case it was the case and that solved the problem.
You cannot reference external resources while uploading tracks. You
therefor need to download the track to your computer and then perform
the actual upload to SoundCloud.
Source
Soundcloud file uploading issue HTTP 442
I managed to solve this by using a different constructor that passes the audio file as an NSData object:
shareViewController = [SCShareViewController shareViewControllerWithFileData:[NSData dataWithContentsOfFile:[docsDir stringByAppendingPathComponent:fileToShare]]
completionHandler:^(NSDictionary *trackInfo, NSError *error){
if (SC_CANCELED(error)) {
NSLog(#"Canceled!");
} else if (error) {
NSLog(#"Ooops, something went wrong: %#", [error localizedDescription
]);
} else {
// If you want to do something with the uploaded
// track this is the right place for that.
NSLog(#"Uploaded track: %#", trackInfo);
}
}];

Why am I not getting an error when sending data through the Game Center without internet?

-(BOOL)sendMessage:(NSMutableDictionary *)_message {
//*** Process _message and convert it to NSData here ***//
NSError *error;
BOOL success = [currentMatch sendDataToAllPlayers:data withDataMode:GKMatchSendDataReliable error:&error];
if (error != nil) {
NSLog(#"Sending data ERROR");
}
return success;
}
I started a match (stored in currentMatch) with another player and continuously sent data using the above method. Then I turned off the wifi.
However, I am not getting the "Sending data ERROR" log message at all.
Why? I turned off the internet connection, so why is there no error here? What could possibly lead to this scenario?
I've also confirmed that the dictionary I am sending is properly encoded into an NSData object, and success is returning YES.
As per the documentation
Return Value
YES if the data was successfully queued for transmission; NO if the match was unable to queue the data.
The method only enqueues the data for transmission, which happens asynchronously.
If you want to monitor the state of the transmission, implement the proper GKMatchDelegate delegate methods, such as match:didFailWithError:.
However, as stated in the documentation:
The match queues the data and transmits it when the network becomes available.
so if you try to perform the method with no network, the transfer just won't happen until the network is back, meaning that you won't see it failing.
By the way you should check the return value, instead of the error, since the error might be nil despite the operation being unsuccessful.
NSError *error;
BOOL success = [currentMatch sendDataToAllPlayers:data withDataMode:GKMatchSendDataReliable error:&error];
if (!success) {
NSLog(#"Sending data ERROR\n%#", error);
}
return success;
You need to check the return value of the method to know whether or not an error occurred. You cannot test the error parameter directly.
if (!success) {
NSLog(#"Sending data ERROR -- %#", error);
}
return success;
As to why you don't get an error, that send method is asynchronous. It simply enqueues the data for transmission and immediately returns. You have to catch the error through some other means (I'm not steeped in GameKit to know what that other means might be).

Resources