code cleanup & fixed compatibility for non-JS users & fixed fullscreen images being in low resolution
This commit is contained in:
parent
0d083f53e7
commit
db89f9c781
13 changed files with 474 additions and 300 deletions
222
cache-images.go
222
cache-images.go
|
@ -25,14 +25,31 @@ import (
|
|||
var (
|
||||
cachingImages = make(map[string]*sync.Mutex)
|
||||
cachingImagesMu sync.Mutex
|
||||
cachingSemaphore = make(chan struct{}, 30) // Limit to concurrent downloads
|
||||
cachingSemaphore = make(chan struct{}, 100) // Limit to concurrent downloads
|
||||
|
||||
invalidImageIDs = make(map[string]struct{})
|
||||
invalidImageIDsMu sync.Mutex
|
||||
|
||||
imageURLMap = make(map[string]string) // mapping from imageID_type to imageURL
|
||||
imageURLMapMu sync.RWMutex // mutex for thread-safe access
|
||||
)
|
||||
|
||||
func cacheImage(imageURL, filename, imageID string) (string, bool, error) {
|
||||
func cacheImage(imageURL, imageID string, isThumbnail bool) (string, bool, error) {
|
||||
cacheDir := "image_cache"
|
||||
|
||||
if imageURL == "" {
|
||||
recordInvalidImageID(imageID)
|
||||
return "", false, fmt.Errorf("empty image URL for image ID %s", imageID)
|
||||
}
|
||||
|
||||
// Construct the filename based on the image ID and type
|
||||
var filename string
|
||||
if isThumbnail {
|
||||
filename = fmt.Sprintf("%s_thumb.webp", imageID)
|
||||
} else {
|
||||
filename = fmt.Sprintf("%s_full.webp", imageID)
|
||||
}
|
||||
|
||||
cachedImagePath := filepath.Join(cacheDir, filename)
|
||||
tempImagePath := cachedImagePath + ".tmp"
|
||||
|
||||
|
@ -181,46 +198,105 @@ func cacheImage(imageURL, filename, imageID string) (string, bool, error) {
|
|||
return cachedImagePath, true, nil
|
||||
}
|
||||
|
||||
func handleCachedImages(w http.ResponseWriter, r *http.Request) {
|
||||
func handleImageServe(w http.ResponseWriter, r *http.Request) {
|
||||
// Extract the image ID and type from the URL
|
||||
imageName := filepath.Base(r.URL.Path)
|
||||
idType := imageName
|
||||
|
||||
var imageID, imageType string
|
||||
|
||||
hasExtension := false
|
||||
if strings.HasSuffix(idType, ".webp") {
|
||||
// Cached image, remove extension
|
||||
idType = strings.TrimSuffix(idType, ".webp")
|
||||
hasExtension = true
|
||||
}
|
||||
|
||||
parts := strings.SplitN(idType, "_", 2)
|
||||
if len(parts) != 2 {
|
||||
http.NotFound(w, r)
|
||||
return
|
||||
}
|
||||
imageID = parts[0]
|
||||
imageType = parts[1]
|
||||
|
||||
cacheDir := "image_cache"
|
||||
cachedImagePath := filepath.Join(cacheDir, imageName)
|
||||
filename := fmt.Sprintf("%s_%s.webp", imageID, imageType)
|
||||
cachedImagePath := filepath.Join(cacheDir, filename)
|
||||
|
||||
if _, err := os.Stat(cachedImagePath); os.IsNotExist(err) {
|
||||
printDebug("Cached image not found: %s, serving missing.svg", cachedImagePath)
|
||||
// Serve missing image
|
||||
missingImagePath := filepath.Join("static", "images", "missing.svg")
|
||||
w.Header().Set("Content-Type", "image/svg+xml")
|
||||
http.ServeFile(w, r, missingImagePath)
|
||||
return
|
||||
} else if err != nil {
|
||||
printWarn("Error checking image file: %v", err)
|
||||
http.Error(w, "Internal Server Error", http.StatusInternalServerError)
|
||||
if hasExtension && imageType == "thumb" {
|
||||
// Requesting cached thumbnail image
|
||||
if _, err := os.Stat(cachedImagePath); err == nil {
|
||||
// Cached image exists, serve it
|
||||
contentType := "image/webp"
|
||||
w.Header().Set("Content-Type", contentType)
|
||||
w.Header().Set("Cache-Control", "public, max-age=31536000")
|
||||
http.ServeFile(w, r, cachedImagePath)
|
||||
return
|
||||
} else {
|
||||
// Cached image not found
|
||||
if config.HardCacheEnabled {
|
||||
// Thumbnail should be cached, but not found
|
||||
serveMissingImage(w, r)
|
||||
return
|
||||
}
|
||||
// Else, proceed to proxy (if HardCacheEnabled is false)
|
||||
}
|
||||
}
|
||||
|
||||
// For full images, proceed to proxy the image
|
||||
|
||||
// Image not cached or caching not enabled
|
||||
imageKey := fmt.Sprintf("%s_%s", imageID, imageType)
|
||||
|
||||
imageURLMapMu.RLock()
|
||||
imageURL, exists := imageURLMap[imageKey]
|
||||
imageURLMapMu.RUnlock()
|
||||
|
||||
if !exists {
|
||||
// Cannot find original URL, serve missing image
|
||||
serveMissingImage(w, r)
|
||||
return
|
||||
}
|
||||
|
||||
// Determine the content type based on the file extension
|
||||
extension := strings.ToLower(filepath.Ext(cachedImagePath))
|
||||
var contentType string
|
||||
switch extension {
|
||||
case ".svg":
|
||||
contentType = "image/svg+xml"
|
||||
case ".jpg", ".jpeg":
|
||||
contentType = "image/jpeg"
|
||||
case ".png":
|
||||
contentType = "image/png"
|
||||
case ".gif":
|
||||
contentType = "image/gif"
|
||||
case ".webp":
|
||||
contentType = "image/webp"
|
||||
default:
|
||||
// Default to binary stream if unknown
|
||||
contentType = "application/octet-stream"
|
||||
// For thumbnails, if HardCacheEnabled is true, and image not cached, serve missing image
|
||||
if imageType == "thumb" && config.HardCacheEnabled {
|
||||
// Thumbnail should be cached, but not found
|
||||
serveMissingImage(w, r)
|
||||
return
|
||||
}
|
||||
|
||||
w.Header().Set("Content-Type", contentType)
|
||||
w.Header().Set("Cache-Control", "public, max-age=31536000") // Cache the image for 1 year
|
||||
http.ServeFile(w, r, cachedImagePath)
|
||||
// For full images, proceed to proxy the image
|
||||
|
||||
// Fetch the image from the original URL
|
||||
resp, err := http.Get(imageURL)
|
||||
if err != nil {
|
||||
printWarn("Error fetching image: %v", err)
|
||||
serveMissingImage(w, r)
|
||||
return
|
||||
}
|
||||
defer resp.Body.Close()
|
||||
|
||||
// Check if the request was successful
|
||||
if resp.StatusCode != http.StatusOK {
|
||||
serveMissingImage(w, r)
|
||||
return
|
||||
}
|
||||
|
||||
// Set the Content-Type header to the type of the fetched image
|
||||
contentType := resp.Header.Get("Content-Type")
|
||||
if contentType != "" && strings.HasPrefix(contentType, "image/") {
|
||||
w.Header().Set("Content-Type", contentType)
|
||||
} else {
|
||||
serveMissingImage(w, r)
|
||||
return
|
||||
}
|
||||
|
||||
// Write the image content to the response
|
||||
if _, err := io.Copy(w, resp.Body); err != nil {
|
||||
printWarn("Error writing image to response: %v", err)
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
func handleImageStatus(w http.ResponseWriter, r *http.Request) {
|
||||
|
@ -228,46 +304,55 @@ func handleImageStatus(w http.ResponseWriter, r *http.Request) {
|
|||
ids := strings.Split(imageIDs, ",")
|
||||
|
||||
statusMap := make(map[string]string)
|
||||
cacheDir := "image_cache"
|
||||
|
||||
printDebug("Received image status request for IDs: %v", ids)
|
||||
|
||||
invalidImageIDsMu.Lock()
|
||||
defer invalidImageIDsMu.Unlock()
|
||||
|
||||
for _, id := range ids {
|
||||
// Check if the image ID is in the invalidImageIDs map
|
||||
if _, invalid := invalidImageIDs[id]; invalid {
|
||||
// Image is invalid, set status to "missing"
|
||||
statusMap[id] = "/static/images/missing.svg"
|
||||
if id == "" {
|
||||
continue
|
||||
}
|
||||
|
||||
// Check for different possible extensions
|
||||
extensions := []string{".webp", ".svg"}
|
||||
var cachedImagePath string
|
||||
var found bool
|
||||
// Check for cached full or thumbnail images
|
||||
cacheDir := "image_cache"
|
||||
extensions := []string{"webp", "svg"} // Extensions without leading dots
|
||||
imageReady := false
|
||||
|
||||
// Check thumbnail first
|
||||
for _, ext := range extensions {
|
||||
filename := id + ext
|
||||
path := filepath.Join(cacheDir, filename)
|
||||
if _, err := os.Stat(path); err == nil {
|
||||
cachedImagePath = "/image_cache/" + filename
|
||||
found = true
|
||||
thumbFilename := fmt.Sprintf("%s_thumb.%s", id, ext)
|
||||
thumbPath := filepath.Join(cacheDir, thumbFilename)
|
||||
|
||||
if _, err := os.Stat(thumbPath); err == nil {
|
||||
statusMap[id] = fmt.Sprintf("/image/%s_thumb.%s", id, ext)
|
||||
imageReady = true
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
if found {
|
||||
statusMap[id] = cachedImagePath
|
||||
} else {
|
||||
// Image is not ready yet
|
||||
statusMap[id] = ""
|
||||
// If no thumbnail, check full image
|
||||
if !imageReady {
|
||||
for _, ext := range extensions {
|
||||
fullFilename := fmt.Sprintf("%s_full.%s", id, ext)
|
||||
fullPath := filepath.Join(cacheDir, fullFilename)
|
||||
|
||||
if _, err := os.Stat(fullPath); err == nil {
|
||||
statusMap[id] = fmt.Sprintf("/image/%s_full.%s", id, ext)
|
||||
imageReady = true
|
||||
break
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// If neither is ready
|
||||
if !imageReady {
|
||||
if !config.HardCacheEnabled {
|
||||
// Hard cache is disabled; use the proxy URL
|
||||
statusMap[id] = fmt.Sprintf("/image/%s_thumb", id)
|
||||
} else {
|
||||
// Hard cache is enabled; image is not yet cached
|
||||
// Do not set statusMap[id]; the frontend will keep checking
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
printDebug("Status map: %v", statusMap)
|
||||
|
||||
w.Header().Set("Content-Type", "application/json")
|
||||
json.NewEncoder(w).Encode(statusMap)
|
||||
}
|
||||
|
@ -338,3 +423,20 @@ func removeImageResultFromCache(query string, page int, safe bool, lang string,
|
|||
delete(rc.results, keyStr)
|
||||
}
|
||||
}
|
||||
|
||||
func getContentType(ext string) string {
|
||||
switch strings.ToLower(ext) {
|
||||
case "svg":
|
||||
return "image/svg+xml"
|
||||
case "jpg", "jpeg":
|
||||
return "image/jpeg"
|
||||
case "png":
|
||||
return "image/png"
|
||||
case "gif":
|
||||
return "image/gif"
|
||||
case "webp":
|
||||
return "image/webp"
|
||||
default:
|
||||
return "application/octet-stream"
|
||||
}
|
||||
}
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue