Fix invalid image URLs & dynamic scrolling

This commit is contained in:
partisan 2024-11-14 16:33:04 +01:00
parent 01c48fd366
commit b6fd92673c
2 changed files with 129 additions and 56 deletions

View file

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

View file

@ -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,66 +390,25 @@
// 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();
}
});
// Remove 'js-enabled' class from content
document.getElementById('content').classList.remove('js-enabled');
})();
</script>
</script>
<script type="text/javascript">
// Check if 'js_enabled' is not present in the URL