code cleanup & fixed compatibility for non-JS users & fixed fullscreen images being in low resolution

This commit is contained in:
partisan 2024-11-19 10:36:33 +01:00
parent 0d083f53e7
commit db89f9c781
13 changed files with 474 additions and 300 deletions

View file

@ -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"
}
}