fetchThread :: ImageCache -> Manager -> (forall a. IO a -> IO a) -> IO ()
fetchThread ic manager unmask =
  handle (\ThreadKilled -> -- Handle this exception here so we exit cleanly
             -- traceT TLInfo "Fetch thread received 'ThreadKilled', exiting"
             return ()
         ) . forever . unmask $ do
    -- The inner bracket takes care of cleanup, here we decide if the exception is
    -- recoverable or if we should stop the thread
    catches
      ( do
        -- Once we pop a request from the stack we own it, either fill it with valid
        -- image data or mark it as a cache error. Note that we never want to call
        -- LBM.insert on the cache directory, only LBM.update. Our entry might have
        -- been removed while we're fetching it, don't add it back
        bracketOnError
            (popRequestStack ic)
            (\(uriUncached, retryAttempt) -> do
                time <- (+ (retryDelay retryAttempt)) <$> getTick
                modifyCacheEntries ic . LBM.update uriUncached $ CacheError time (retryAttempt + 1)
            )
            (\(uriUncached, retryAttempt) -> do
                when (retryAttempt > 0) .
                    traceS TLWarn $ printf
                        "Now attempting retry no. %i of failed URI fetch after >=%.1fsec delay: %s"
                        retryAttempt
                        (retryDelay $ retryAttempt - 1)
                        (B8.unpack uriUncached)
                let cacheFn = B8.unpack $ mkURICacheFn ic uriUncached
                imgBS <- fetchDiskCache ic manager uriUncached cacheFn
                -- Decompress and convert
                di <- {-# SCC decompressAndConvert #-}                     -- TODO: toStrict, nasty
                      case toImageRes =<< dynImgToRGBA8 =<< (JP.decodeImage $ BL.toStrict imgBS) of
                          Left  err -> throwIO $ DecodeException
                                           { deError   = err
                                           , deURI     = (B8.unpack uriUncached)
                                           , deCacheFn = cacheFn
                                           }
                          Right x   -> return x
                -- Update cache with image, make sure we actually decompress /
                -- covert it here instead of just storing a thunk
                di `seq` modifyCacheEntries ic $! LBM.update uriUncached (Fetched di)
            )
      )
      [ Handler $ \(ex :: IOException    ) -> reportEx ex
      , Handler $ \(ex :: HttpException  ) -> reportEx ex
      , Handler $ \(ex :: DecodeException) -> reportEx ex
      ]
    where reportEx ex             = traceS TLError $ "Image Cache Exception: " ++ show ex
          retryDelay retryAttempt = ([2, 10, 30, 60, 120] ++ repeat 600) !! retryAttempt :: Double

-- Return the image at the given URI from the cache, or schedule fetching if not present
fetchThread :: ImageCache -> Manager -> (forall a. IO a -> IO a) -> IO ()
fetchThread ic manager unmask =
  handle (\ThreadKilled -> -- Handle this exception here so we exit cleanly
             -- traceT TLInfo "Fetch thread received 'ThreadKilled', exiting"
             return ()
         ) . forever . unmask $ do
    -- The inner bracket takes care of cleanup, here we decide if the exception is
    -- recoverable or if we should stop the thread
    catches
      ( do
        -- Once we pop a request from the stack we own it, either fill it with valid
        -- image data or mark it as a cache error. Note that we never want to call
        -- LBM.insert on the cache directory, only LBM.update. Our entry might have
        -- been removed while we're fetching it, don't add it back
        bracketOnError
            (popRequestStack ic)
            (\(uriUncached, retryAttempt) -> do
                time <- (+ (retryDelay retryAttempt)) <$> getTick
                modifyCacheEntries ic . LBM.update uriUncached $ CacheError time (retryAttempt + 1)
            )
            (\(uriUncached, retryAttempt) -> do
                when (retryAttempt > 0) .
                    traceS TLWarn $ printf
                        "Now attempting retry no. %i of failed URI fetch after >=%.1fsec delay: %s"
                        retryAttempt
                        (retryDelay $ retryAttempt - 1)
                        (B8.unpack uriUncached)
                let cacheFn = B8.unpack $ mkURICacheFn ic uriUncached
                imgBS <- fetchDiskCache ic manager uriUncached cacheFn
                -- Decompress and convert
                di <- {-# SCC decompressAndConvert #-}                     -- TODO: toStrict, nasty
                      case toImageRes =<< dynImgToRGBA8 =<< (JP.decodeImage $ BL.toStrict imgBS) of
                          Left  err -> throwIO $ DecodeException
                                           { deError   = err
                                           , deURI     = (B8.unpack uriUncached)
                                           , deCacheFn = cacheFn
                                           }
                          Right x   -> return x
                -- Update cache with image, make sure we actually decompress /
                -- covert it here instead of just storing a thunk
                di `seq` modifyCacheEntries ic $! LBM.update uriUncached (Fetched di)
            )
      )
      [ Handler $ \(ex :: IOException    ) -> reportEx ex
      , Handler $ \(ex :: HttpException  ) -> reportEx ex
      , Handler $ \(ex :: DecodeException) -> reportEx ex
      ]
    where reportEx ex             = traceS TLError $ "Image Cache Exception: " ++ show ex
          retryDelay retryAttempt = ([2, 10, 30, 60, 120] ++ repeat 600) !! retryAttempt :: Double

-- Return the image at the given URI from the cache, or schedule fetching if not present
fetchThread ic manager unmask =
  handle (\ThreadKilled -> -- Handle this exception here so we exit cleanly
             -- traceT TLInfo "Fetch thread received 'ThreadKilled', exiting"
             return ()
         ) . forever . unmask $ do
    -- The inner bracket takes care of cleanup, here we decide if the exception is
    -- recoverable or if we should stop the thread
    catches
      ( do
        -- Once we pop a request from the stack we own it, either fill it with valid
        -- image data or mark it as a cache error. Note that we never want to call
        -- LBM.insert on the cache directory, only LBM.update. Our entry might have
        -- been removed while we're fetching it, don't add it back
        bracketOnError
            (popRequestStack ic)
            (\(uriUncached, retryAttempt) -> do
                time <- (+ (retryDelay retryAttempt)) <$> getTick
                modifyCacheEntries ic . LBM.update uriUncached $ CacheError time (retryAttempt + 1)
            )
            (\(uriUncached, retryAttempt) -> do
                when (retryAttempt > 0) .
                    traceS TLWarn $ printf
                        "Now attempting retry no. %i of failed URI fetch after >=%.1fsec delay: %s"
                        retryAttempt
                        (retryDelay $ retryAttempt - 1)
                        (B8.unpack uriUncached)
                let cacheFn = B8.unpack $ mkURICacheFn ic uriUncached
                imgBS <- fetchDiskCache ic manager uriUncached cacheFn
                -- Decompress and convert
                di <- {-# SCC decompressAndConvert #-}                     -- TODO: toStrict, nasty
                      case toImageRes =<< dynImgToRGBA8 =<< (JP.decodeImage $ BL.toStrict imgBS) of
                          Left  err -> throwIO $ DecodeException
                                           { deError   = err
                                           , deURI     = (B8.unpack uriUncached)
                                           , deCacheFn = cacheFn
                                           }
                          Right x   -> return x
                -- Update cache with image, make sure we actually decompress /
                -- covert it here instead of just storing a thunk
                di `seq` modifyCacheEntries ic $! LBM.update uriUncached (Fetched di)
            )
      )
      [ Handler $ \(ex :: IOException    ) -> reportEx ex
      , Handler $ \(ex :: HttpException  ) -> reportEx ex
      , Handler $ \(ex :: DecodeException) -> reportEx ex
      ]
    where reportEx ex             = traceS TLError $ "Image Cache Exception: " ++ show ex
          retryDelay retryAttempt = ([2, 10, 30, 60, 120] ++ repeat 600) !! retryAttempt :: Double

-- Return the image at the given URI from the cache, or schedule fetching if not present