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 (
|
var (
|
||||||
cachingImages = make(map[string]*sync.Mutex)
|
cachingImages = make(map[string]*sync.Mutex)
|
||||||
cachingImagesMu 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{})
|
invalidImageIDs = make(map[string]struct{})
|
||||||
invalidImageIDsMu sync.Mutex
|
invalidImageIDsMu sync.Mutex
|
||||||
|
@ -34,6 +34,7 @@ var (
|
||||||
func cacheImage(imageURL, filename, imageID string) (string, bool, error) {
|
func cacheImage(imageURL, filename, imageID string) (string, bool, error) {
|
||||||
cacheDir := "image_cache"
|
cacheDir := "image_cache"
|
||||||
cachedImagePath := filepath.Join(cacheDir, filename)
|
cachedImagePath := filepath.Join(cacheDir, filename)
|
||||||
|
tempImagePath := cachedImagePath + ".tmp"
|
||||||
|
|
||||||
// Check if the image is already cached
|
// Check if the image is already cached
|
||||||
if _, err := os.Stat(cachedImagePath); err == nil {
|
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)
|
os.Mkdir(cacheDir, os.ModePerm)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Save the SVG file as-is
|
// Save the SVG file as-is to the temp path
|
||||||
err = os.WriteFile(cachedImagePath, data, 0644)
|
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 {
|
if err != nil {
|
||||||
recordInvalidImageID(imageID)
|
recordInvalidImageID(imageID)
|
||||||
return "", false, err
|
return "", false, err
|
||||||
|
@ -141,17 +149,25 @@ func cacheImage(imageURL, filename, imageID string) (string, bool, error) {
|
||||||
os.Mkdir(cacheDir, os.ModePerm)
|
os.Mkdir(cacheDir, os.ModePerm)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Open the cached file for writing
|
// Open the temp file for writing
|
||||||
outFile, err := os.Create(cachedImagePath)
|
outFile, err := os.Create(tempImagePath)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
recordInvalidImageID(imageID)
|
recordInvalidImageID(imageID)
|
||||||
return "", false, err
|
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}
|
options := &webp.Options{Lossless: false, Quality: 80}
|
||||||
err = webp.Encode(outFile, img, options)
|
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 {
|
if err != nil {
|
||||||
recordInvalidImageID(imageID)
|
recordInvalidImageID(imageID)
|
||||||
return "", false, err
|
return "", false, err
|
||||||
|
|
|
@ -246,11 +246,104 @@
|
||||||
let page = parseInt(document.getElementById('template-data').getAttribute('data-page')) || 1;
|
let page = parseInt(document.getElementById('template-data').getAttribute('data-page')) || 1;
|
||||||
let query = document.getElementById('template-data').getAttribute('data-query');
|
let query = document.getElementById('template-data').getAttribute('data-query');
|
||||||
let hardCacheEnabled = document.getElementById('template-data').getAttribute('data-hard-cache-enabled') === 'true';
|
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 imageElements = [];
|
||||||
let imageIds = [];
|
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() {
|
function checkImageStatus() {
|
||||||
if (!hardCacheEnabled) return;
|
if (!hardCacheEnabled) return;
|
||||||
if (imageIds.length === 0) {
|
if (imageIds.length === 0) {
|
||||||
|
@ -259,7 +352,7 @@
|
||||||
}
|
}
|
||||||
|
|
||||||
// Send AJAX request to check image status
|
// 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(response => response.json())
|
||||||
.then(statusMap => {
|
.then(statusMap => {
|
||||||
imageElements = imageElements.filter(img => {
|
imageElements = imageElements.filter(img => {
|
||||||
|
@ -267,12 +360,17 @@
|
||||||
if (statusMap[id]) {
|
if (statusMap[id]) {
|
||||||
// Image is ready, update src
|
// Image is ready, update src
|
||||||
img.src = statusMap[id];
|
img.src = statusMap[id];
|
||||||
|
img.onerror = function() {
|
||||||
|
handleImageError(img);
|
||||||
|
};
|
||||||
// Remove the image id from the list
|
// Remove the image id from the list
|
||||||
imageIds = imageIds.filter(imageId => imageId !== id);
|
imageIds = imageIds.filter(imageId => imageId !== id);
|
||||||
return false; // Remove img from imageElements
|
return false; // Remove img from imageElements
|
||||||
}
|
}
|
||||||
return true; // Keep img in imageElements
|
return true; // Keep img in imageElements
|
||||||
});
|
});
|
||||||
|
// After updating images, ensure the page is scrollable
|
||||||
|
ensureScrollable();
|
||||||
})
|
})
|
||||||
.catch(error => {
|
.catch(error => {
|
||||||
console.error('Error checking image status:', error);
|
console.error('Error checking image status:', error);
|
||||||
|
@ -292,59 +390,18 @@
|
||||||
// Start checking image status
|
// Start checking image status
|
||||||
let imageStatusTimer = setInterval(checkImageStatus, imageStatusInterval);
|
let imageStatusTimer = setInterval(checkImageStatus, imageStatusInterval);
|
||||||
checkImageStatus(); // Initial check
|
checkImageStatus(); // Initial check
|
||||||
|
|
||||||
|
// After initial images are loaded, ensure the page is scrollable
|
||||||
|
window.addEventListener('load', ensureScrollable);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Infinite scrolling
|
// Infinite scrolling
|
||||||
window.addEventListener('scroll', function() {
|
window.addEventListener('scroll', function() {
|
||||||
if (isFetching) return;
|
if (isFetching || noMoreImages) return;
|
||||||
|
|
||||||
if (window.innerHeight + window.scrollY >= document.body.offsetHeight - scrollThreshold) {
|
if (window.innerHeight + window.scrollY >= document.body.offsetHeight - scrollThreshold) {
|
||||||
// User scrolled near the bottom
|
// User scrolled near the bottom
|
||||||
isFetching = true;
|
fetchNextPage();
|
||||||
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;
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|
Loading…
Add table
Reference in a new issue