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,64 +246,55 @@
|
||||||
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 checkImageStatus() {
|
* Function to handle image load errors with retry logic
|
||||||
if (!hardCacheEnabled) return;
|
* @param {HTMLElement} imgElement - The image element that failed to load
|
||||||
if (imageIds.length === 0) {
|
* @param {number} retryCount - Number of retries left
|
||||||
// No images to check, do nothing
|
* @param {number} retryDelay - Delay between retries in milliseconds
|
||||||
return;
|
*/
|
||||||
|
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';
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Send AJAX request to check image status
|
/**
|
||||||
fetch('/image_status?image_ids=' + imageIds.join(','))
|
* Function to ensure the page is scrollable by loading more images if necessary
|
||||||
.then(response => response.json())
|
*/
|
||||||
.then(statusMap => {
|
function ensureScrollable() {
|
||||||
imageElements = imageElements.filter(img => {
|
if (noMoreImages) return; // Do not attempt if no more images are available
|
||||||
let id = img.getAttribute('data-id');
|
// Check if the page is not scrollable
|
||||||
if (statusMap[id]) {
|
if (document.body.scrollHeight <= window.innerHeight) {
|
||||||
// Image is ready, update src
|
// If not scrollable, fetch the next page
|
||||||
img.src = statusMap[id];
|
fetchNextPage();
|
||||||
// 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
|
|
||||||
});
|
|
||||||
})
|
|
||||||
.catch(error => {
|
|
||||||
console.error('Error checking image status:', error);
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Initialize imageElements and imageIds
|
/**
|
||||||
if (hardCacheEnabled) {
|
* Function to fetch the next page of images
|
||||||
imageElements = Array.from(document.querySelectorAll('img[data-id]'));
|
*/
|
||||||
imageIds = imageElements.map(img => img.getAttribute('data-id'));
|
function fetchNextPage() {
|
||||||
|
if (isFetching || noMoreImages) return;
|
||||||
// Replace images with placeholders
|
|
||||||
imageElements.forEach(img => {
|
|
||||||
img.src = '/static/images/placeholder.svg';
|
|
||||||
});
|
|
||||||
|
|
||||||
// Start checking image status
|
|
||||||
let imageStatusTimer = setInterval(checkImageStatus, imageStatusInterval);
|
|
||||||
checkImageStatus(); // Initial check
|
|
||||||
}
|
|
||||||
|
|
||||||
// Infinite scrolling
|
|
||||||
window.addEventListener('scroll', function() {
|
|
||||||
if (isFetching) return;
|
|
||||||
|
|
||||||
if (window.innerHeight + window.scrollY >= document.body.offsetHeight - scrollThreshold) {
|
|
||||||
// User scrolled near the bottom
|
|
||||||
isFetching = true;
|
isFetching = true;
|
||||||
page += 1;
|
page += 1;
|
||||||
|
|
||||||
fetch('/search?q=' + encodeURIComponent(query) + '&t=image&p=' + page + '&ajax=true')
|
fetch(`/search?q=${encodeURIComponent(query)}&t=image&p=${page}&ajax=true`)
|
||||||
.then(response => response.text())
|
.then(response => response.text())
|
||||||
.then(html => {
|
.then(html => {
|
||||||
// Parse the returned HTML and extract image elements
|
// Parse the returned HTML and extract image elements
|
||||||
|
@ -323,6 +314,9 @@
|
||||||
if (hardCacheEnabled) {
|
if (hardCacheEnabled) {
|
||||||
// Replace image with placeholder
|
// Replace image with placeholder
|
||||||
img.src = '/static/images/placeholder.svg';
|
img.src = '/static/images/placeholder.svg';
|
||||||
|
img.onerror = function() {
|
||||||
|
handleImageError(img);
|
||||||
|
};
|
||||||
|
|
||||||
let id = img.getAttribute('data-id');
|
let id = img.getAttribute('data-id');
|
||||||
imageElements.push(img);
|
imageElements.push(img);
|
||||||
|
@ -333,19 +327,82 @@
|
||||||
if (hardCacheEnabled) {
|
if (hardCacheEnabled) {
|
||||||
checkImageStatus();
|
checkImageStatus();
|
||||||
}
|
}
|
||||||
isFetching = false;
|
// After appending new images, ensure the page is scrollable
|
||||||
|
ensureScrollable();
|
||||||
} else {
|
} else {
|
||||||
// No more images to load
|
// No more images to load
|
||||||
isFetching = false;
|
noMoreImages = true;
|
||||||
// Optionally, remove the scroll event listener if no more pages
|
|
||||||
// window.removeEventListener('scroll', scrollHandler);
|
|
||||||
}
|
}
|
||||||
|
isFetching = false;
|
||||||
})
|
})
|
||||||
.catch(error => {
|
.catch(error => {
|
||||||
console.error('Error fetching next page:', error);
|
console.error('Error fetching next page:', error);
|
||||||
isFetching = false;
|
isFetching = false;
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Function to check image status via AJAX
|
||||||
|
*/
|
||||||
|
function checkImageStatus() {
|
||||||
|
if (!hardCacheEnabled) return;
|
||||||
|
if (imageIds.length === 0) {
|
||||||
|
// No images to check, do nothing
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Send AJAX request to check image status
|
||||||
|
fetch(`/image_status?image_ids=${imageIds.join(',')}`)
|
||||||
|
.then(response => response.json())
|
||||||
|
.then(statusMap => {
|
||||||
|
imageElements = imageElements.filter(img => {
|
||||||
|
let id = img.getAttribute('data-id');
|
||||||
|
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);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
// Initialize imageElements and imageIds
|
||||||
|
if (hardCacheEnabled) {
|
||||||
|
imageElements = Array.from(document.querySelectorAll('img[data-id]'));
|
||||||
|
imageIds = imageElements.map(img => img.getAttribute('data-id'));
|
||||||
|
|
||||||
|
// Replace images with placeholders
|
||||||
|
imageElements.forEach(img => {
|
||||||
|
img.src = '/static/images/placeholder.svg';
|
||||||
|
});
|
||||||
|
|
||||||
|
// 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 || noMoreImages) return;
|
||||||
|
|
||||||
|
if (window.innerHeight + window.scrollY >= document.body.offsetHeight - scrollThreshold) {
|
||||||
|
// User scrolled near the bottom
|
||||||
|
fetchNextPage();
|
||||||
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
// Remove 'js-enabled' class from content
|
// Remove 'js-enabled' class from content
|
||||||
|
|
Loading…
Add table
Reference in a new issue