![]() Server : Apache/2 System : Linux server-15-235-50-60 5.15.0-164-generic #174-Ubuntu SMP Fri Nov 14 20:25:16 UTC 2025 x86_64 User : gositeme ( 1004) PHP Version : 8.2.29 Disable Function : exec,system,passthru,shell_exec,proc_close,proc_open,dl,popen,show_source,posix_kill,posix_mkfifo,posix_getpwuid,posix_setpgid,posix_setsid,posix_setuid,posix_setgid,posix_seteuid,posix_setegid,posix_uname Directory : /home/gositeme/domains/soundstudiopro.com/public_html/js/ |
// Track Status Monitor - Automatic Polling System
// This script automatically checks for track status updates every 30 seconds
class TrackMonitor {
constructor() {
this.monitorKey = 'soundstudiopro_monitor_2025';
this.pollingInterval = 60000; // 60 seconds (reduced from 30)
this.isPolling = false;
this.pollingTimer = null;
this.lastCheck = null;
this.onStatusUpdate = null; // Callback function
this.notifiedTrackIds = new Set(); // Track IDs that have already been notified
this.currentUserId = null; // Will be set from session or page data
this.storageKey = 'trackMonitor_notifiedIds'; // localStorage key
// Load persisted notified track IDs from localStorage
this.loadNotifiedTrackIds();
}
// Load notified track IDs from localStorage
loadNotifiedTrackIds() {
try {
const stored = localStorage.getItem(this.storageKey);
if (stored) {
const ids = JSON.parse(stored);
this.notifiedTrackIds = new Set(ids);
console.log(`đĩ Loaded ${this.notifiedTrackIds.size} previously notified track IDs from storage`);
}
} catch (e) {
console.warn('đĩ Could not load notified track IDs from storage:', e);
this.notifiedTrackIds = new Set();
}
}
// Save notified track IDs to localStorage
saveNotifiedTrackIds() {
try {
const ids = Array.from(this.notifiedTrackIds);
localStorage.setItem(this.storageKey, JSON.stringify(ids));
} catch (e) {
console.warn('đĩ Could not save notified track IDs to storage:', e);
}
}
// Clean up notified track IDs for tracks that are no longer in the monitor response
// This is called after processing results to remove IDs for tracks that were deleted
cleanupNotifiedTrackIds(data) {
if (this.notifiedTrackIds.size === 0) return;
try {
// Get all track IDs from current response (all statuses)
const currentTrackIds = new Set();
if (data.fixed_tracks) {
data.fixed_tracks.forEach(track => currentTrackIds.add(String(track.track_id)));
}
if (data.failed_tracks) {
data.failed_tracks.forEach(track => currentTrackIds.add(String(track.track_id)));
}
if (data.still_processing) {
data.still_processing.forEach(track => currentTrackIds.add(String(track.track_id)));
}
// Remove notified IDs for tracks that aren't in the current response
// (they were likely deleted)
const toRemove = [];
this.notifiedTrackIds.forEach(id => {
const trackId = id.replace(/^(failed_|fixed_)/, '');
// Only remove if track is not in current response AND it's been more than 5 minutes
// (to avoid removing IDs for tracks that are just temporarily not showing)
if (!currentTrackIds.has(trackId)) {
toRemove.push(id);
}
});
// Only remove a few at a time to be safe (tracks might be temporarily not in response)
const removed = toRemove.slice(0, 10); // Remove max 10 at a time
removed.forEach(id => this.notifiedTrackIds.delete(id));
if (removed.length > 0) {
console.log(`đĩ Cleaned up ${removed.length} notified track IDs for tracks no longer in monitor`);
this.saveNotifiedTrackIds();
}
} catch (e) {
console.warn('đĩ Could not cleanup notified track IDs:', e);
}
}
// Start automatic polling
start() {
if (this.isPolling) {
console.log('đĩ Track monitor already running');
return;
}
console.log('đĩ Starting track status monitor...');
this.isPolling = true;
this.poll();
}
// Stop automatic polling
stop() {
if (this.pollingTimer) {
clearTimeout(this.pollingTimer);
this.pollingTimer = null;
}
this.isPolling = false;
console.log('đĩ Track monitor stopped');
}
// Perform a single check
async poll() {
if (!this.isPolling) return;
try {
console.log('đĩ Checking for track status updates...');
const response = await fetch(`/api/monitor.php?key=${this.monitorKey}`, {
credentials: 'same-origin' // Include cookies/session
});
const data = await response.json();
if (data.error) {
console.error('đĩ Monitor error:', data.error);
return;
}
this.lastCheck = new Date();
// Process results
this.processResults(data);
// Schedule next poll
this.pollingTimer = setTimeout(() => this.poll(), this.pollingInterval);
} catch (error) {
console.error('đĩ Monitor polling error:', error);
// Retry in 60 seconds on error
this.pollingTimer = setTimeout(() => this.poll(), 60000);
}
}
// Process monitor results
processResults(data) {
const { fixed_tracks, failed_tracks, still_processing } = data;
// Get current user ID from page if available
if (!this.currentUserId) {
const userIdElement = document.querySelector('[data-user-id]');
if (userIdElement) {
this.currentUserId = parseInt(userIdElement.getAttribute('data-user-id'));
}
}
// Filter tracks to only show notifications for current user's tracks
const userFixedTracks = fixed_tracks.filter(track => {
// If user_id is provided, filter by it; otherwise show all (for backward compatibility)
return !this.currentUserId || !track.user_id || track.user_id === this.currentUserId;
});
const userFailedTracks = failed_tracks.filter(track => {
// If user_id is provided, filter by it; otherwise show all (for backward compatibility)
return !this.currentUserId || !track.user_id || track.user_id === this.currentUserId;
});
// Filter out tracks that have already been notified
const newFixedTracks = userFixedTracks.filter(track => {
const key = `fixed_${track.track_id}`;
if (this.notifiedTrackIds.has(key)) {
return false; // Already notified
}
this.notifiedTrackIds.add(key);
this.saveNotifiedTrackIds(); // Persist to localStorage
return true;
});
const newFailedTracks = userFailedTracks.filter(track => {
const key = `failed_${track.track_id}`;
if (this.notifiedTrackIds.has(key)) {
return false; // Already notified
}
this.notifiedTrackIds.add(key);
this.saveNotifiedTrackIds(); // Persist to localStorage
return true;
});
// Show notifications for new fixed tracks
if (newFixedTracks.length > 0) {
console.log(`đĩ ${newFixedTracks.length} track(s) completed!`);
this.showNotification(`${newFixedTracks.length} track(s) are ready to play!`, 'success');
// Trigger page refresh if we're on a relevant page
if (this.shouldRefreshPage()) {
setTimeout(() => {
window.location.reload();
}, 2000);
}
}
// Show notifications for new failed tracks
if (newFailedTracks.length > 0) {
console.log(`đĩ ${newFailedTracks.length} track(s) failed`);
this.showNotification(`${newFailedTracks.length} track(s) failed to process`, 'error');
}
// Call callback if provided
if (this.onStatusUpdate && typeof this.onStatusUpdate === 'function') {
this.onStatusUpdate(data);
}
// Update UI if we're on a page with tracks
this.updateTrackStatusUI(data);
// Cleanup notified track IDs for deleted tracks (after a delay to avoid race conditions)
setTimeout(() => {
this.cleanupNotifiedTrackIds(data);
}, 10000); // Wait 10 seconds after processing
}
// Show notification to user
showNotification(message, type = 'info') {
// Create notification element
const notification = document.createElement('div');
notification.className = `track-notification track-notification-${type}`;
notification.innerHTML = `
<div class="notification-content">
<span class="notification-icon">${type === 'success' ? 'â
' : type === 'error' ? 'â' : 'âšī¸'}</span>
<span class="notification-message">${message}</span>
<button class="notification-close" onclick="this.parentElement.parentElement.remove()">Ã</button>
</div>
`;
// Add styles
notification.style.cssText = `
position: fixed;
top: 20px;
right: 20px;
background: ${type === 'success' ? '#48bb78' : type === 'error' ? '#f56565' : '#4299e1'};
color: white;
padding: 15px 20px;
border-radius: 8px;
box-shadow: 0 4px 12px rgba(0,0,0,0.15);
z-index: 10000;
max-width: 400px;
animation: slideIn 0.3s ease-out;
`;
// Add to page
document.body.appendChild(notification);
// Auto-remove after 5 seconds
setTimeout(() => {
if (notification.parentElement) {
notification.remove();
}
}, 5000);
}
// Check if we should refresh the current page
shouldRefreshPage() {
const currentPage = window.location.pathname;
const relevantPages = ['/library_new.php', '/feed.php', '/dashboard.php', '/'];
return relevantPages.some(page => currentPage.includes(page));
}
// Update track status in UI
updateTrackStatusUI(data) {
const { fixed_tracks, failed_tracks } = data;
// Update processing tracks display
const processingElements = document.querySelectorAll('.track-status-processing');
processingElements.forEach(element => {
const trackId = element.getAttribute('data-track-id');
// Check if this track was fixed
const fixedTrack = fixed_tracks.find(track => track.track_id == trackId);
if (fixedTrack) {
element.innerHTML = '<span class="status-complete">â
Complete</span>';
element.className = 'track-status-complete';
// Update play button if it exists
const playButton = element.closest('.track-card').querySelector('.play-button');
if (playButton) {
playButton.disabled = false;
playButton.innerHTML = '<i class="fas fa-play"></i> Play';
}
}
// Check if this track failed
const failedTrack = failed_tracks.find(track => track.track_id == trackId);
if (failedTrack) {
element.innerHTML = '<span class="status-failed">â Failed</span>';
element.className = 'track-status-failed';
}
});
}
// Manual check (can be called from UI)
async manualCheck() {
console.log('đĩ Manual track status check...');
await this.poll();
}
// Set callback for status updates
setStatusUpdateCallback(callback) {
this.onStatusUpdate = callback;
}
}
// Global instance
window.trackMonitor = new TrackMonitor();
// Auto-start on pages that show tracks
document.addEventListener('DOMContentLoaded', function() {
const currentPage = window.location.pathname;
const trackPages = ['/library_new.php', '/feed.php', '/dashboard.php', '/', '/library.php'];
if (trackPages.some(page => currentPage.includes(page))) {
console.log('đĩ Auto-starting track monitor on track page');
window.trackMonitor.start();
}
});
// Add CSS for notifications
const style = document.createElement('style');
style.textContent = `
@keyframes slideIn {
from { transform: translateX(100%); opacity: 0; }
to { transform: translateX(0); opacity: 1; }
}
.track-notification .notification-content {
display: flex;
align-items: center;
gap: 10px;
}
.track-notification .notification-close {
background: none;
border: none;
color: white;
font-size: 18px;
cursor: pointer;
margin-left: auto;
}
.track-status-processing {
color: #ed8936;
font-weight: 600;
}
.track-status-complete {
color: #48bb78;
font-weight: 600;
}
.track-status-failed {
color: #f56565;
font-weight: 600;
}
`;
document.head.appendChild(style);