Fix invalid image URLs & dynamic scrolling
This commit is contained in:
parent
01c48fd366
commit
b6fd92673c
2 changed files with 129 additions and 56 deletions
|
@ -25,7 +25,7 @@ import (
|
|||
var (
|
||||
cachingImages = make(map[string]*sync.Mutex)
|
||||
cachingImagesMu sync.Mutex
|
||||
cachingSemaphore = make(chan struct{}, 10) // Limit to 10 concurrent downloads
|
||||
cachingSemaphore = make(chan struct{}, 30) // Limit to concurrent downloads
|
||||
|
||||
invalidImageIDs = make(map[string]struct{})
|
||||
invalidImageIDsMu sync.Mutex
|
||||
|
@ -34,6 +34,7 @@ var (
|
|||
func cacheImage(imageURL, filename, imageID string) (string, bool, error) {
|
||||
cacheDir := "image_cache"
|
||||
cachedImagePath := filepath.Join(cacheDir, filename)
|
||||
tempImagePath := cachedImagePath + ".tmp"
|
||||
|
||||
// Check if the image is already cached
|
||||
if _, err := os.Stat(cachedImagePath); err == nil {
|
||||
|
@ -96,8 +97,15 @@ func cacheImage(imageURL, filename, imageID string) (string, bool, error) {
|
|||
os.Mkdir(cacheDir, os.ModePerm)
|
||||
}
|
||||
|
||||
// Save the SVG file as-is
|
||||
err = os.WriteFile(cachedImagePath, data, 0644)
|
||||
// Save the SVG file as-is to the temp path
|
||||
err = os.WriteFile(tempImagePath, data, 0644)
|
||||
if err != nil {
|
||||
recordInvalidImageID(imageID)
|
||||
return "", false, err
|
||||
}
|
||||
|
||||
// Atomically rename the temp file to the final cached image path
|
||||
err = os.Rename(tempImagePath, cachedImagePath)
|
||||
if err != nil {
|
||||
recordInvalidImageID(imageID)
|
||||
return "", false, err
|
||||
|
@ -141,17 +149,25 @@ func cacheImage(imageURL, filename, imageID string) (string, bool, error) {
|
|||
os.Mkdir(cacheDir, os.ModePerm)
|
||||
}
|
||||
|
||||
// Open the cached file for writing
|
||||
outFile, err := os.Create(cachedImagePath)
|
||||
// Open the temp file for writing
|
||||
outFile, err := os.Create(tempImagePath)
|
||||
if err != nil {
|
||||
recordInvalidImageID(imageID)
|
||||
return "", false, err
|
||||
}
|
||||
defer outFile.Close()
|
||||
|
||||
// Encode the image to WebP and save
|
||||
// Encode the image to WebP and save to the temp file
|
||||
options := &webp.Options{Lossless: false, Quality: 80}
|
||||
err = webp.Encode(outFile, img, options)
|
||||
if err != nil {
|
||||
outFile.Close()
|
||||
recordInvalidImageID(imageID)
|
||||
return "", false, err
|
||||
}
|
||||
outFile.Close()
|
||||
|
||||
// Atomically rename the temp file to the final cached image path
|
||||
err = os.Rename(tempImagePath, cachedImagePath)
|
||||
if err != nil {
|
||||
recordInvalidImageID(imageID)
|
||||
return "", false, err
|
||||
|
|
|
@ -246,11 +246,104 @@
|
|||
let page = parseInt(document.getElementById('template-data').getAttribute('data-page')) || 1;
|
||||
let query = document.getElementById('template-data').getAttribute('data-query');
|
||||
let hardCacheEnabled = document.getElementById('template-data').getAttribute('data-hard-cache-enabled') === 'true';
|
||||
let noMoreImages = false; // Flag to indicate if there are no more images to load
|
||||
|
||||
let imageElements = [];
|
||||
let imageIds = [];
|
||||
|
||||
// Function to check image status
|
||||
/**
|
||||
* Function to handle image load errors with retry logic
|
||||
* @param {HTMLElement} imgElement - The image element that failed to load
|
||||
* @param {number} retryCount - Number of retries left
|
||||
* @param {number} retryDelay - Delay between retries in milliseconds
|
||||
*/
|
||||
function handleImageError(imgElement, retryCount = 3, retryDelay = 1000) {
|
||||
if (retryCount > 0) {
|
||||
setTimeout(() => {
|
||||
imgElement.src = imgElement.getAttribute('data-full');
|
||||
imgElement.onerror = function() {
|
||||
handleImageError(imgElement, retryCount - 1, retryDelay);
|
||||
};
|
||||
}, retryDelay);
|
||||
} else {
|
||||
// After retries, hide the image container or set a fallback image
|
||||
console.warn('Image failed to load after retries:', imgElement.getAttribute('data-full'));
|
||||
imgElement.parentElement.style.display = 'none'; // Hide the image container
|
||||
// Alternatively, set a fallback image:
|
||||
// imgElement.src = '/static/images/fallback.svg';
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Function to ensure the page is scrollable by loading more images if necessary
|
||||
*/
|
||||
function ensureScrollable() {
|
||||
if (noMoreImages) return; // Do not attempt if no more images are available
|
||||
// Check if the page is not scrollable
|
||||
if (document.body.scrollHeight <= window.innerHeight) {
|
||||
// If not scrollable, fetch the next page
|
||||
fetchNextPage();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Function to fetch the next page of images
|
||||
*/
|
||||
function fetchNextPage() {
|
||||
if (isFetching || noMoreImages) return;
|
||||
isFetching = true;
|
||||
page += 1;
|
||||
|
||||
fetch(`/search?q=${encodeURIComponent(query)}&t=image&p=${page}&ajax=true`)
|
||||
.then(response => response.text())
|
||||
.then(html => {
|
||||
// Parse the returned HTML and extract image elements
|
||||
let parser = new DOMParser();
|
||||
let doc = parser.parseFromString(html, 'text/html');
|
||||
let newImages = doc.querySelectorAll('.image');
|
||||
|
||||
if (newImages.length > 0) {
|
||||
let resultsContainer = document.querySelector('.images');
|
||||
newImages.forEach(imageDiv => {
|
||||
// Append new images to the container
|
||||
resultsContainer.appendChild(imageDiv);
|
||||
|
||||
// Get the img element
|
||||
let img = imageDiv.querySelector('img');
|
||||
if (img) {
|
||||
if (hardCacheEnabled) {
|
||||
// Replace image with placeholder
|
||||
img.src = '/static/images/placeholder.svg';
|
||||
img.onerror = function() {
|
||||
handleImageError(img);
|
||||
};
|
||||
|
||||
let id = img.getAttribute('data-id');
|
||||
imageElements.push(img);
|
||||
imageIds.push(id);
|
||||
}
|
||||
}
|
||||
});
|
||||
if (hardCacheEnabled) {
|
||||
checkImageStatus();
|
||||
}
|
||||
// After appending new images, ensure the page is scrollable
|
||||
ensureScrollable();
|
||||
} else {
|
||||
// No more images to load
|
||||
noMoreImages = true;
|
||||
}
|
||||
isFetching = false;
|
||||
})
|
||||
.catch(error => {
|
||||
console.error('Error fetching next page:', error);
|
||||
isFetching = false;
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Function to check image status via AJAX
|
||||
*/
|
||||
function checkImageStatus() {
|
||||
if (!hardCacheEnabled) return;
|
||||
if (imageIds.length === 0) {
|
||||
|
@ -259,7 +352,7 @@
|
|||
}
|
||||
|
||||
// Send AJAX request to check image status
|
||||
fetch('/image_status?image_ids=' + imageIds.join(','))
|
||||
fetch(`/image_status?image_ids=${imageIds.join(',')}`)
|
||||
.then(response => response.json())
|
||||
.then(statusMap => {
|
||||
imageElements = imageElements.filter(img => {
|
||||
|
@ -267,12 +360,17 @@
|
|||
if (statusMap[id]) {
|
||||
// Image is ready, update src
|
||||
img.src = statusMap[id];
|
||||
img.onerror = function() {
|
||||
handleImageError(img);
|
||||
};
|
||||
// Remove the image id from the list
|
||||
imageIds = imageIds.filter(imageId => imageId !== id);
|
||||
return false; // Remove img from imageElements
|
||||
}
|
||||
return true; // Keep img in imageElements
|
||||
});
|
||||
// After updating images, ensure the page is scrollable
|
||||
ensureScrollable();
|
||||
})
|
||||
.catch(error => {
|
||||
console.error('Error checking image status:', error);
|
||||
|
@ -292,59 +390,18 @@
|
|||
// Start checking image status
|
||||
let imageStatusTimer = setInterval(checkImageStatus, imageStatusInterval);
|
||||
checkImageStatus(); // Initial check
|
||||
|
||||
// After initial images are loaded, ensure the page is scrollable
|
||||
window.addEventListener('load', ensureScrollable);
|
||||
}
|
||||
|
||||
// Infinite scrolling
|
||||
window.addEventListener('scroll', function() {
|
||||
if (isFetching) return;
|
||||
if (isFetching || noMoreImages) return;
|
||||
|
||||
if (window.innerHeight + window.scrollY >= document.body.offsetHeight - scrollThreshold) {
|
||||
// User scrolled near the bottom
|
||||
isFetching = true;
|
||||
page += 1;
|
||||
|
||||
fetch('/search?q=' + encodeURIComponent(query) + '&t=image&p=' + page + '&ajax=true')
|
||||
.then(response => response.text())
|
||||
.then(html => {
|
||||
// Parse the returned HTML and extract image elements
|
||||
let parser = new DOMParser();
|
||||
let doc = parser.parseFromString(html, 'text/html');
|
||||
let newImages = doc.querySelectorAll('.image');
|
||||
|
||||
if (newImages.length > 0) {
|
||||
let resultsContainer = document.querySelector('.images');
|
||||
newImages.forEach(imageDiv => {
|
||||
// Append new images to the container
|
||||
resultsContainer.appendChild(imageDiv);
|
||||
|
||||
// Get the img element
|
||||
let img = imageDiv.querySelector('img');
|
||||
if (img) {
|
||||
if (hardCacheEnabled) {
|
||||
// Replace image with placeholder
|
||||
img.src = '/static/images/placeholder.svg';
|
||||
|
||||
let id = img.getAttribute('data-id');
|
||||
imageElements.push(img);
|
||||
imageIds.push(id);
|
||||
}
|
||||
}
|
||||
});
|
||||
if (hardCacheEnabled) {
|
||||
checkImageStatus();
|
||||
}
|
||||
isFetching = false;
|
||||
} else {
|
||||
// No more images to load
|
||||
isFetching = false;
|
||||
// Optionally, remove the scroll event listener if no more pages
|
||||
// window.removeEventListener('scroll', scrollHandler);
|
||||
}
|
||||
})
|
||||
.catch(error => {
|
||||
console.error('Error fetching next page:', error);
|
||||
isFetching = false;
|
||||
});
|
||||
fetchNextPage();
|
||||
}
|
||||
});
|
||||
|
||||
|
|
Loading…
Add table
Reference in a new issue