Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Is it possible to preserve images in cache even in case of a HTTP 300/400/500 response from server? #223

Open
adams-family opened this issue Aug 11, 2022 · 11 comments
Labels

Comments

@adams-family
Copy link

I'm using SDWebImage in order to preserve data bandwidth with .refreshCached in order to update images from time to time:

WebImage(url: url, options: .refreshCached, options: .refreshCached)

This works great even if the network is down, images are served from cache.

However, if the URL of the image returns 300/400/500, etc... - which may happen - SDWebImage erased the image from cache and no image is displayed. This is not a desirable behaviour as 1) servers can be down temporarily for maintenance, 2) public WiFi networks return 302 Moved for unauthenticated users, etc - I would prefer keeping images in cache until a new valid image is downloaded.

Is that possible?

@dreampiggy
Copy link
Collaborator

dreampiggy commented Aug 12, 2022

Why not use SDWebImageDownloadConfig.acceptableStatusCodes ?

https://sdwebimage.github.io/documentation/sdwebimage/sdwebimagedownloaderconfig/acceptablestatuscodes

You can read documentation firstly and search for the result

@dreampiggy
Copy link
Collaborator

dreampiggy commented Aug 12, 2022

When you config acceptableStatusCodes to nil, behaves like below:

  1. When server down and return error HTTP response, although we don't mark URLSession fail and continue to run
  2. Because of the returned HTTP body data, can not decode to any image format, then the DownloadOperation mark as failed with error code .badimagedata
  3. From top level, sd_setImage API get the NSError and you can do logic on it

@adams-family
Copy link
Author

@dreampiggy Thanks for your response. Interestingly, although I added the following code to my app:

import SDWebImage
import SDWebImageSwiftUI

class AppDelegate: NSObject, UIApplicationDelegate {
    func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey : Any]? = nil) -> Bool {
        
        SDWebImageDownloader.shared.config.acceptableStatusCodes = [200]
        
        return true
    }
}

Still an HTTP 403 status on a resource causes this image to show up from cache on load, but disappear in about 5 seconds (I guess when the HTTP call is unsuccessful after a few attempts):

struct ContentView: View {
    var body: some View {
        WebImage(url: URL(string: "<any_http_403_resource>")!, options: .refreshCached)
            .resizable()
    }
}

Any ideas what is wrong with my setup? Thanks!

@dreampiggy
Copy link
Collaborator

dreampiggy commented Aug 12, 2022

This is NSIndexSet. Your "[200]" menas only HTTP 200 will treat as success.

I menas you provide a IndexSet with range like [200, 600) or something

@dreampiggy
Copy link
Collaborator

Maybe you misunderstand that refreshCache means.

That strange option behave:

  1. First read image from cache and callback to set image on View
  2. When network finished, check whether the download image is equal to previous callback image
  3. If same, do nothing and no second callback
  4. If not same, callback second with the new image (note new image will be nil if download failed)

@dreampiggy
Copy link
Collaborator

dreampiggy commented Aug 12, 2022

Maybe you misunderstand that refreshCache means.

That strange option behave:

  1. First read image from cache and callback to set image on View
  2. When network finished, check whether the download image is equal to previous callback image (we use context option dict to keep a strong reference to that previous image🥲 see hack:https://sdwebimage.github.io/documentation/sdwebimage/sdwebimagecontextoption/loadercachedimage )
  3. If same, do nothing and no second callback
  4. If not same, callback second with the new image (note new image will be nil if download failed)

@dreampiggy
Copy link
Collaborator

dreampiggy commented Aug 12, 2022

For you case, you can either:

  1. Use another feature called .avoidAutoSetImage and manual set image and filter this second callback
  2. Use responseModifier API, to read the previous image from cache again (ugly hack) and return, so the new image and previous inage will be the same and no second callback triggered (...)
  3. Feature request: Hack that context option (.loaderCachedImage) which keep the previous image, set it to nil when network download failed. Or just write another wrapper SDImageDownloader. Pseudocode
class MyImageLoader : SDImageLoaderProtocol {
  func loadImage(with: url, callback) {
    let loadedImage = context[.loaderCachedImage]
    SDImageDownloader.shared.loadImage(with: url) { image, data, error in 
      if error && loadedImage {
          callback(loadedImage, nil, NSError(domain: SDWebImageErrorDomain code: .cacheNotModified))
          return
      }
      callback(image, data, error)
    }
  }
}

Is this feature really useful in general ?

@adams-family
Copy link
Author

I think that in general it would be useful to have a built-in option to keep cached images as long as there is a valid new version of them. If the remote server containing the images is broken for whatever reason (developer mistake, hardware issue) and returns invalid response codes such as 403, 404, 500, ... the user of the mobile app would still see the correct image.

@dreampiggy
Copy link
Collaborator

Seems a feature request. Maybe we can create a issue in https://github.com/SDWebImage/SDWebImage/issues and reference this.

It's not hard to implements, but need a new options like SDWebImageRefreshCachedIgnoreError, or another global config to avoid huge codebase changes ?

@adams-family
Copy link
Author

I like the .refreshCachedIgnoreError option. Should we let people vote before I create the new request?

@dreampiggy
Copy link
Collaborator

It's OK to fire issue first. The detail API name actually we can talk in PR.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
Projects
None yet
Development

No branches or pull requests

2 participants