Hi I followed this excellent tutorial on how to download (and resume-download) files to an iPhone.
Everything works, except that the service-provider I am downloading the files from somehow does not allow for download-resume to work.
Apple says that a download can be resumed only if the following conditions are met (see bullets below). Therefore my dwonload.cancel(byProducingResmeData: ..) possibly returns data = nil because of one of these points :
The resource has not changed since you first requested it
The task is an HTTP or HTTPS GET request
The server provides either the ETag or Last-Modified header (or both)
in its response
The server supports byte-range requests
The temporary file hasn’t been deleted by the system in response to
disk space pressure
At least one of the above points unfortunately seems to not be fulfilled by my service-provider (definitive answer from them hanging....).
Anyway - is there another way to resume a background-downloading Task in iOS ?
If I could persistently keep the URLSessionDownloadTask - this would help. But how would I do that ?
Here is my code that normally keeps the resumeData. But unfortunately, in my case, this data is always nil.
func pauseDownload(_ file: File) {
guard let download = activeDownloads[file.previewURL] else { return }
if download.isDownloading {
download.task?.cancel(byProducingResumeData: { data in
download.resumeData = data
// data unfortunately always nil !!!!!!!!!!!!!!!!!!!!!
})
download.isDownloading = false
}
}
Related
I create an http request using PUT to get data from the server. I got this data from the server and transform it on a PDF file.
When I run it for the first time everything runs fine, but after some calls, I start to get timeout errors.
Sometimes, I need to restart the app to be able to receive HTTP requests again.
This is the code that I use.
func callGetPdfFromEndpointUsingNSMutableURLRequest() {
if codigoBarra == "" {
messageError = "Código não localizado"
showingAlert = true
redirectToPdfView = false
showingActivityIndicator = false
return
}
let serviceRepository = ServiceRepository()
// let codigo_barra = "d152d36914313fedfbf36842a7195b723"
let json: [String: Any] = ["codigoBarra":"\(codigoBarra)"]
let request: NSMutableURLRequest = serviceRepository.clientURLRequest(endpointPesquisa, typeAutho: .basic, parms: "", body: json as Dictionary<String, AnyObject>)
print("request: \(request)")
print("request.url: \(String(describing: request.url))")
serviceRepository.put(request, retryLogin: true, completion: {isOk,msgError,httpCode,needLogin, response in
if isOk {
tratarRequisicaoPdf(response)
} else {
print("erro no request - is not ok | - httpCode: \(httpCode)")
var stringResponse:String = ""
if response != nil {
stringResponse = String(data: response as! Data, encoding: .utf8)!
} else {
stringResponse = "Sem resposta do servidor, tempo limite da solicitação foi esgotado."
}
messageError = "\(stringResponse)"
print(messageError)
showingAlert = true
redirectToPdfView = false
semaphore.signal()
}
semaphore.wait()
showingActivityIndicator = false
})
}
This error is unstable, sometimes it shows, sometimes it don't appear.
The people working on backend was not able to detect any problems.
I got the following error:
2022-05-20 15:33:15.442419-0300 CDA[2016:38068] Task <147B6F7F-E46A-47D0-A258-D6F3E5417D7E>.<1> finished with error [-1001] Error Domain=NSURLErrorDomain Code=-1001 "Esgotou-se o tempo limite da solicitação." UserInfo={_kCFStreamErrorCodeKey=-2102, NSUnderlyingError=0x7fd0e5245520 {Error Domain=kCFErrorDomainCFNetwork Code=-1001 "(null)" UserInfo={_kCFStreamErrorCodeKey=-2102, _kCFStreamErrorDomainKey=4}}, _NSURLErrorFailingURLSessionTaskErrorKey=LocalDataTask <147B6F7F-E46A-47D0-A258-D6F3E5417D7E>.<1>, _NSURLErrorRelatedURLSessionTaskErrorKey=(
"LocalDataTask <147B6F7F-E46A-47D0-A258-D6F3E5417D7E>.<1>"
), NSLocalizedDescription=Esgotou-se o tempo limite da solicitação., NSErrorFailingURLStringKey=https://myurl.sp.gov.br/gedave/api/spservicos/v1/buscaRequisicaoExame, NSErrorFailingURLKey=https://myurl.sp.gov.br/gedave/api/spservicos/v1/buscaRequisicaoExame, _kCFStreamErrorDomainKey=4}
response::: nil
erro: response is nil
httpCode: 0
What can I do to try to detect what was causing the timeout errors?
Edit:
I added the source code that can be viewed here https://swiftfiddle.com/mz2dxw6z6bda7a6t44cryncrpi.
NEW EDIT:
Answers to comments created by #Rob
Good to know about the NSUrlMutableRequest
I will try to use the 'finishAndInvalidate' in my URLSession. I didn't know about that.
My problem is unpredictable. Now I start the app and the first call got an timeout, after the second call the app works. Sometimes it starts working, but after some requests, I got a timeout
In the absence of a MCVE, there is not enough here to diagnose or reproduce the problem.
That having been said, there are some interesting clues:
You are calling wait (on a semaphore). Eliminate the DispatchSemaphore and if you want to know when the requests are done, use DispatchGroup. But when you use the dispatch group, use notify when it is done, not wait. Avoid blocking threads unnecessarily. And never block the main thread.
Your network request is performed by ServiceRepository, which you have not shared with us. But in your fiddle link, you show us unrelated URLSession code that is specifying the main queue as its delegate queue. If ServiceRepository is doing something similar, that, combined with the wait, above, could easily deadlock your code.
So, eliminate the semaphore, avoid ever calling wait (whether semaphore or dispatch group), and the deadlock risk is eliminated.
That having been said, that is only one potential timeout risk. The other scenario might be that you are simply issuing too many requests for the URLSession to run them without timing out.
If that is the case, you have a few options:
Increase the timeout threshold (either of the request or the session).
Try bumping up the timeout values and see if that mitigates the issue.
Submit uploads in just-in-time manner.
E.g., you might have a process where, rather than initiating all the uploads at once, that you issue each upload upon the completion of the prior upload. Or you can wrap the upload in a custom, asynchronous, Operation subclass (which is complicated in its own right), and then tell the operation queue to only attempt 4 at a time, or whatever. There are many techniques to tackle this, but the idea is to prevent timeouts by not even submitting the upload until you are confident that the URLSession can perform them.
Use background URLSession with file-based uploads.
If you have uploads that are so slow that they risk timing out, it begs the question of whether you really want to force the user to keep the app running in order to let the uploads finish at all. If you use a background URLSession, the background daemon will not let them timeout, but rather will upload them when it can.
Refactoring this for background URLSession is a non-trivial exercise (the first time you do it, anyway). You have to abandon completion handlers, use delegate-methods API, use file-based uploads, etc. But it is an incredibly powerful way to let uploads proceed in the background, allowing the user to leave the app, etc.
I don't see full code but you should have a look into semaphore usage - probably wrong multithread logic leads to not complete you request and your completion is hovered for a long period and causes URLDataTask to produce the timeout error
In an iOS project, we decided to turn on a feature as a demo purposing scenario to only some white listed users to implement this, after user logs in to the app in LoginController we send user id to Firebase by using this
Analytics.setUserProperty(id, forName: "ID")
and we have setup a remote config flag with multiple conditions which each condition is checking a particular user id if that's equal with the user id sending after login process to Firebase, it returns true otherwise false.
Exactly after we send user id in LoginController we try to fetch the remote config flag
RCUtility.fetchAVFlagValue { result in
UserDefaults.standard.set(result, forKey: DefaultKeys.remoteConfigFlag)
}
and here is the RCUtility class
class RCUtility {
static func fetchAVFlagValue(completion: #escaping (_ flag: Bool) -> Void) {
#if DEBUG
let fetchDuration: TimeInterval = 0
activateDebugMode()
#else
let fetchDuration: TimeInterval = 3600
#endif
let remoteConfig = RemoteConfig.remoteConfig()
remoteConfig.fetch(withExpirationDuration: fetchDuration) { _, error in
if let error = error {
print("Error fetching remote values: \(error)")
return
}
remoteConfig.activateFetched()
completion(remoteConfig
.configValue(forKey: "auto_vision_flag")
.boolValue)
}
}
static func activateDebugMode() {
let debugSettings = RemoteConfigSettings(developerModeEnabled: true)
RemoteConfig.remoteConfig().configSettings = debugSettings
}
}
the problem is as long as I test it in debug mode which time interval is 0 it works properly when for example I login as a whitelisted user I see that specific feature and when I logout and login as another none whitelisted users I won't see the feature but when we publish the app for QAs this isn't happening if they login as a good user firstly they see the feature but if they logout and login again as a bad user they can still see the feature which they suppose not! unless they wait for 1 hr (the specified time interval to fetch a new value for the flag) or if they delete the app and re-install it and login again! It seems FB doesn't return updated value if these two conditions aren't met.
I am sure the flag has been setup in Firebase correctly.
I have tried different ways such as activating the flag after fetching from FB or try to reset the value from UserDefaults after user logs out of the app or save the value in a constant instead of using UserDefaults but none of them worked I am not sure is it the way FB works with remote config flag or I am missing anything?
The Android version doesn't have this issue with same implementation and same time interval and regardless of the debug or production mode or type of user they get the correct value each time from Firebase without having to delete and reinstall the app.
Remote Config's fetch mechanism probably doesn't know anything about the user, which means that it simple waits until the cache expires, which defaults to 12 hours. Also see the Firebase documentation on caching.
I'm inclined to consider this a bug, although I'm not sure if it's working as intended. Either way, it might be worth it to file a bug report.
As a workaround, you could tell fetch to request new values after you detect a fresh login by calling fetchWithExpirationDuration:completionHandler: with a very low expiration duration. Just be sure to only call this variant after a new user signs in, as too frequent calls may result in a server-side throttle being applied.
I've done some tests and I believe it's a Firebase Bug. In the first request I get the old result, and after two requests I got the update.
I'm currently running SailsJS on a Raspberry Pi and all is working well however when I execute a sails.models.nameofmodel.count() when I attempt to respond with the result I end up getting a empty response.
getListCount: function(req,res)
{
var mainsource = req.param("source");
if(mainsource)
{
sails.models.gatherer.find({source: mainsource}).exec(
function(error, found)
{
if(error)
{
return res.serverError("Error in call");
}
else
{
sails.log("Number found "+found.length);
return res.ok({count: found.length});
}
}
);
}
else
{
return res.ok("Error in parameter");
}
},
I am able to see in the logs the number that was found (73689). However when responding I still get an empty response. I am using the default stock ok.js file, however I did stick in additional logging to try to debug and make sure it is going through the correct paths. I was able to confirm that the ok.js was going through this path
if (req.wantsJSON) {
return res.jsonx(data);
}
I also tried adding .populate() to the call before the .exec(), res.status(200) before I sent out a res.send() instead of res.ok(). I've also updated Sails to 11.5 and still getting the same empty response. I've also used a sails.models.gatherer.count() call with the same result.
You can try to add some logging to the beginning of your method to capture the value of mainsource. I do not believe you need to use an explicit return for any response object calls.
If all looks normal there, try to eliminate the model's find method and just evaluate the request parameter and return a simple response:
getListCount: function(req, res) {
var mainsource = req.param("source");
sails.log("Value of mainsource:" + mainsource);
if (mainsource) {
res.send("Hello!");
} else {
res.badRequest("Sorry, missing source.");
}
}
If that does not work, then your model data may not actually be matching on the criteria that you are providing and the problem may lie there; in which case, your response would be null. You mentioned that you do see the resulting count of the query within the log statement. If the res.badRequest is also null, then you may have a problem with the version of express that is installed within sailsjs. You mention that you have 11.5 of sailsjs. I will assume you mean 0.11.5.
This is what is found in package.json of 0.11.5
"express": "^3.21.0",
Check for any possible bugs within the GitHub issues for sailsjs regarding express and response object handling and the above version of express.
It may be worthwhile to perform a clean install using the latest sailsjs version (0.12.0) and see if that fixes your issue.
Another issue may be in how you are handling the response. In this case .exec should execute the query immediately (i.e. a synchronous call) and return the response when complete. So there should be no asynchronous processing there.
If you can show the code that is consuming the response, that would be helpful. I am assuming that there is a view that is showing the response via AJAX or some kind of form POST that is being performed. If that is where you are seeing the null response, then perhaps the problem lies in the view layer rather than the controller/model.
If you are experiencing a true timeout error via HTTP even though your query returns with a result just in time, then you may need to consider using async processing with sailjs. Take a look at this post on using a Promise instead.
below is the code when user uploads a video from mobile application to S3
def uploadVideo = Action(parse.multipartFormData) { implicit request =>
try {
var height = 0
var width = 0
request.body.files.map { mov =>
var videoName = System.currentTimeMillis() + ".mpeg"
amazonS3Client.putObject(bucketVideos, videoName, mov.ref.file)
}
val map = Map("result" -> "success")
Ok(write(map))
} catch {
case e: Exception =>
Ok(write(Map("result" -> "error")))
}
}
the above code work fine but in case user cancel while uploading of video then error occurs
[error] play - Exception caught in RequestBodyHandler
java.nio.channels.ClosedChannelException: null
at org.jboss.netty.channel.socket.nio.AbstractNioWorker.cleanUpWriteBuffer(AbstractNioWorker.java:434) ~[netty.jar:na]
at org.jboss.netty.channel.socket.nio.AbstractNioWorker.writeFromUserCode(AbstractNioWorker.java:129) ~[netty.jar:na]
at org.jboss.netty.channel.socket.nio.NioServerSocketPipelineSink.handleAcceptedSocket(NioServerSocketPipelineSink.java:99) ~[netty.jar:na]
at org.jboss.netty.channel.socket.nio.NioServerSocketPipelineSink.eventSunk(NioServerSocketPipelineSink.java:36) ~[netty.jar:na]
at org.jboss.netty.channel.Channels.write(Channels.java:725) ~[netty.jar:na]
at org.jboss.netty.handler.codec.oneone.OneToOneEncoder.doEncode(OneToOneEncoder.java:71) ~[netty.jar:na]
and this doesn't go to catch block!!
1.can this is harmfull to server or not?(because it is not needed any response if error occours)
2.if yes, how to handle?
This is all happening in Play's internals that are handling parsing the body of the Request. In fact, during the upload to your server, you haven't even reached the try block yet because the file hasn't finished uploading. Only once the upload is complete do you have the TemporaryFile available.
So no, you can't catch this error, and why would you want to? The user closed the connection. They're not even waiting for a response, so why send one? Let Play handle it.
This is also not a good way of handling an upload, though. For small files, it's passable, but if someone is proxying a huge video upload through your server to S3, it's going to:
Take almost twice is long to serve the response (which will cause the user to hang while you upload to S3).
Block one of Play's threads for handling requests for the entire time that file is uploading to S3, and given enough of these uploads (not many at all), you will no longer be able to process requests until an upload has completed.
Consider at least creating a separate ExecutionContext to use for handling uploads, or even better, look into having the user upload directly to S3 via a signed form, which would remove the need to proxy the upload at all.
My task is to display the supported document types on an iPhone with OS 3.x, such as .pdf, .rtf, .doc, .ppt, .png, .tiff etc.
Now, I have stored these files only encrypted on disk. For security reasons, I want to avoid storing them unencrypted on disk.
Hence, I prefer to use loadData:MIMEType:textEncodingName:baseURL: instead of loadRequest: to display the document because loadData allows me to pass the content in a NSData object, i.e. I can decrypt the file in memory and have no need to store it on disk, as it would be required when using loadRequest.
The problem is that loadData does not appear to work with all file types:
Testing shows that all picture types seem to work fine, as well as PDFs, while the more complex types don't. I get a errors such as:
NSURLErrorDomain Code=100
NSURLErrorDomain Code=102
WebView appears to need a truly working URL for accessing the documents as a file, despite me offering all content via the NSData object already.
Here's the code I use to display the content:
[webView loadData:data MIMEType:type textEncodingName:#"utf-8" baseURL:nil];
The mime-type is properly set, e.g. to "application/msword" for .doc files.
Does anyone know how I could get loadData to work with all types that loadRequest supports? Or, alternatively, is there some way I can tell which types do work for sure (i.e. officially sanctioned by Apple) with loadData? Then I can work twofold, creating a temp unencrypted file only for those cases that loadData won't like.
Update
Looks like I'm not the first one running into this. See here:
http://osdir.com/ml/iPhoneSDKDevelopment/2010-03/msg00216.html
So, I guess, that's the status quo, and nothing I can do about it.
Someone suggested a work-around which might work, though:
http://osdir.com/ml/iPhoneSDKDevelopment/2010-03/msg00219.html
Basically, the idea is to provide a tiny http server that serves the file (from memory in my case), and then use loadRequest. This is probably a bit more memory-intensive, though, as both the server and the webview will probably both hold the entire contents in memory as two copies then, as opposed to using loadData, where both would rather share the same data object. (Mind you, I'll have to hold the decrypted data in memory, that's the whole point here).
I experienced a very similar issue (i get my files from a server however) and saw your post and thought it was a dead end and then just by chance started to experiment on the device (iPad, in this instance) and it worked when i gave the baseURL as what i used to get it from the server and it worked but does not work on the simulator. I would try that, otherwise I would submit a bug report to Apple.
Here is solution via NSURLProtocol:
class CoreDataFileURLProtocol : NSURLProtocol {
var connection: NSURLConnection!
override class func canInitWithRequest(request: NSURLRequest) -> Bool {
return (request.URL.scheme == "coredatafile")
}
override class func canonicalRequestForRequest(request: NSURLRequest) -> NSURLRequest {
return request
}
override func startLoading() {
if let file_id = self.request.URL.absoluteString?.lastPathComponent {
if let file = SAFile.MR_findFirstByAttribute("file_id", withValue: file_id) as? SAFile {
let response = NSURLResponse(URL: request.URL, MIMEType: file.mime, expectedContentLength: Int(file.filesize), textEncodingName: "utf-8")
client?.URLProtocol(self, didReceiveResponse: response, cacheStoragePolicy: .NotAllowed)
client?.URLProtocol(self, didLoadData: file.data)
client?.URLProtocolDidFinishLoading(self)
}
}
}
override func stopLoading() {
}
}
Now you need only register class:
NSURLProtocol.registerClass(CoreDataFileURLProtocol.self)
And create a request with file_id:
let url = NSURL(scheme: "coredatafile", host: "myapp.com", path: "/\(file.file_id)")
webView.loadRequest(NSURLRequest(URL: url!))