1619 lines
71 KiB
JavaScript
1619 lines
71 KiB
JavaScript
/**
|
|
* FavMasToKey - Script JavaScript principal (version améliorée)
|
|
*/
|
|
|
|
// Attendre que le DOM soit chargé
|
|
document.addEventListener('DOMContentLoaded', function() {
|
|
// Éléments DOM
|
|
const uploadForm = document.getElementById('upload-form');
|
|
const jsonFileInput = document.getElementById('json-file');
|
|
const step1 = document.getElementById('step1');
|
|
const step2 = document.getElementById('step2');
|
|
const step3 = document.getElementById('step3');
|
|
const fileSummary = document.getElementById('file-summary');
|
|
const backToStep1 = document.getElementById('back-to-step1');
|
|
const startMigration = document.getElementById('start-migration');
|
|
const pauseMigration = document.getElementById('pause-migration');
|
|
const cancelMigration = document.getElementById('cancel-migration');
|
|
const globalProgress = document.getElementById('global-progress');
|
|
const currentProgress = document.getElementById('current-progress');
|
|
const operationLog = document.getElementById('operation-log');
|
|
const slowModeCheckbox = document.getElementById('slow-mode');
|
|
const slowModeWarning = document.getElementById('slow-mode-warning');
|
|
const slowModeOptions = document.getElementById('slow-mode-options');
|
|
const slowModeDelay = document.getElementById('slow-mode-delay');
|
|
const delayValue = document.getElementById('delay-value');
|
|
|
|
// Éléments pour le mode tortue
|
|
const tortoiseCheckbox = document.getElementById('tortoise-mode');
|
|
const tortoiseWarning = document.getElementById('tortoise-mode-warning');
|
|
const tortoiseOptions = document.getElementById('tortoise-mode-options');
|
|
const tortoiseDelay = document.getElementById('tortoise-mode-delay');
|
|
const tortoiseDelayValue = document.getElementById('tortoise-delay-value');
|
|
const autoPauseEnabled = document.getElementById('auto-pause-enabled');
|
|
|
|
// Variables globales
|
|
let favoritesList = [];
|
|
let currentIndex = 0;
|
|
let totalItems = 0;
|
|
let isProcessing = false;
|
|
let isPaused = false;
|
|
let successCount = 0;
|
|
let errorCount = 0;
|
|
let skippedCount = 0;
|
|
let warningCount = 0;
|
|
let consecutiveRateLimitErrors = 0;
|
|
let adaptiveDelayIncreases = 0;
|
|
let autoRestartTimer = null;
|
|
let rateLimitPauseActive = false;
|
|
let migration = {
|
|
status: 'not_started', // not_started, in_progress, paused, completed, error, auto_paused
|
|
startTime: null,
|
|
lastUpdateTime: null,
|
|
progress: {
|
|
current: 0,
|
|
total: 0,
|
|
percentage: 0
|
|
},
|
|
stats: {
|
|
success: 0,
|
|
error: 0,
|
|
skipped: 0,
|
|
warning: 0
|
|
},
|
|
options: {
|
|
slowMode: false,
|
|
delaySeconds: 30,
|
|
tortoiseMode: false,
|
|
tortoiseDelaySeconds: 120,
|
|
autoPauseEnabled: true
|
|
}
|
|
};
|
|
|
|
// AMÉLIORATION 1: File d'attente pour les favoris en échec de rate-limit
|
|
let rateLimitQueue = [];
|
|
|
|
// AMÉLIORATION 3: Cache pour les résultats de recherche fédérée
|
|
const federatedCache = {
|
|
// Structure: { "mastodon_url": { id: "misskey_id", timestamp: Date.now() } }
|
|
cache: {},
|
|
|
|
// Ajouter une entrée au cache
|
|
add: function(mastodonUrl, misskeyId) {
|
|
this.cache[mastodonUrl] = {
|
|
id: misskeyId,
|
|
timestamp: Date.now()
|
|
};
|
|
this.saveToStorage();
|
|
},
|
|
|
|
// Récupérer une entrée du cache
|
|
get: function(mastodonUrl) {
|
|
if (this.cache[mastodonUrl]) {
|
|
return this.cache[mastodonUrl].id;
|
|
}
|
|
return null;
|
|
},
|
|
|
|
// Vérifier si une URL est dans le cache
|
|
has: function(mastodonUrl) {
|
|
return this.cache.hasOwnProperty(mastodonUrl);
|
|
},
|
|
|
|
// Sauvegarder le cache dans localStorage
|
|
saveToStorage: function() {
|
|
try {
|
|
localStorage.setItem('favmastokey_federated_cache', JSON.stringify(this.cache));
|
|
} catch (e) {
|
|
// En cas d'erreur (ex: quota dépassé), nettoyer le cache ancien
|
|
this.cleanup(180); // Nettoyer les entrées plus anciennes que 3 heures
|
|
try {
|
|
localStorage.setItem('favmastokey_federated_cache', JSON.stringify(this.cache));
|
|
} catch (e) {
|
|
console.error("Impossible de sauvegarder le cache même après nettoyage", e);
|
|
}
|
|
}
|
|
},
|
|
|
|
// Charger le cache depuis localStorage
|
|
loadFromStorage: function() {
|
|
const savedCache = localStorage.getItem('favmastokey_federated_cache');
|
|
if (savedCache) {
|
|
try {
|
|
this.cache = JSON.parse(savedCache);
|
|
// Nettoyer les entrées trop anciennes au chargement
|
|
this.cleanup(1440); // 24 heures
|
|
} catch (e) {
|
|
console.error("Erreur lors du chargement du cache", e);
|
|
this.cache = {};
|
|
}
|
|
}
|
|
},
|
|
|
|
// Nettoyer les entrées trop anciennes (en minutes)
|
|
cleanup: function(maxAgeMinutes) {
|
|
const now = Date.now();
|
|
const maxAge = maxAgeMinutes * 60 * 1000;
|
|
|
|
Object.keys(this.cache).forEach(url => {
|
|
if (now - this.cache[url].timestamp > maxAge) {
|
|
delete this.cache[url];
|
|
}
|
|
});
|
|
}
|
|
};
|
|
|
|
// AMÉLIORATION 4: Suivi des performances API
|
|
const apiPerformance = {
|
|
// Statistiques par domaine
|
|
domainStats: {},
|
|
|
|
// Ajouter une mesure de temps de réponse
|
|
addResponseTime: function(domain, responseTimeMs, success) {
|
|
if (!this.domainStats[domain]) {
|
|
this.domainStats[domain] = {
|
|
requestCount: 0,
|
|
successCount: 0,
|
|
failureCount: 0,
|
|
rateLimitCount: 0,
|
|
totalResponseTime: 0,
|
|
avgResponseTime: 0,
|
|
lastRateLimitTime: null,
|
|
recommendedDelay: null
|
|
};
|
|
}
|
|
|
|
const stats = this.domainStats[domain];
|
|
stats.requestCount++;
|
|
stats.totalResponseTime += responseTimeMs;
|
|
stats.avgResponseTime = stats.totalResponseTime / stats.requestCount;
|
|
|
|
if (success === true) {
|
|
stats.successCount++;
|
|
} else if (success === false) {
|
|
stats.failureCount++;
|
|
} else if (success === 'rate_limit') {
|
|
stats.rateLimitCount++;
|
|
stats.lastRateLimitTime = Date.now();
|
|
|
|
// Calculer un délai recommandé basé sur les échecs de rate-limit
|
|
// Plus de rate-limits = délai plus long
|
|
const baseDelay = 30; // Délai de base en secondes
|
|
stats.recommendedDelay = baseDelay + (Math.min(5, stats.rateLimitCount) * 30);
|
|
}
|
|
|
|
this.saveToStorage();
|
|
return stats;
|
|
},
|
|
|
|
// Obtenir les statistiques pour un domaine
|
|
getDomainStats: function(domain) {
|
|
return this.domainStats[domain] || null;
|
|
},
|
|
|
|
// Obtenir le délai recommandé pour un domaine
|
|
getRecommendedDelay: function(domain) {
|
|
const stats = this.getDomainStats(domain);
|
|
if (stats && stats.recommendedDelay) {
|
|
return stats.recommendedDelay;
|
|
}
|
|
return null;
|
|
},
|
|
|
|
// Sauvegarder dans localStorage
|
|
saveToStorage: function() {
|
|
try {
|
|
localStorage.setItem('favmastokey_api_performance', JSON.stringify(this.domainStats));
|
|
} catch (e) {
|
|
console.error("Erreur lors de la sauvegarde des performances API", e);
|
|
}
|
|
},
|
|
|
|
// Charger depuis localStorage
|
|
loadFromStorage: function() {
|
|
const saved = localStorage.getItem('favmastokey_api_performance');
|
|
if (saved) {
|
|
try {
|
|
this.domainStats = JSON.parse(saved);
|
|
} catch (e) {
|
|
console.error("Erreur lors du chargement des performances API", e);
|
|
this.domainStats = {};
|
|
}
|
|
}
|
|
}
|
|
};
|
|
|
|
// AMÉLIORATION 5: Points de sauvegarde intermédiaires
|
|
const saveCheckpoints = {
|
|
// Intervalle entre les sauvegardes en millisecondes
|
|
saveInterval: 5 * 60 * 1000, // 5 minutes par défaut
|
|
lastSaveTime: null,
|
|
timer: null,
|
|
|
|
// Démarrer les sauvegardes automatiques
|
|
start: function() {
|
|
this.lastSaveTime = Date.now();
|
|
|
|
// Nettoyer l'ancien timer si existant
|
|
if (this.timer) {
|
|
clearInterval(this.timer);
|
|
}
|
|
|
|
// Configurer un nouveau timer
|
|
this.timer = setInterval(() => {
|
|
if (isProcessing && !isPaused) {
|
|
this.createCheckpoint();
|
|
}
|
|
}, this.saveInterval);
|
|
},
|
|
|
|
// Arrêter les sauvegardes automatiques
|
|
stop: function() {
|
|
if (this.timer) {
|
|
clearInterval(this.timer);
|
|
this.timer = null;
|
|
}
|
|
},
|
|
|
|
// Créer un point de sauvegarde
|
|
createCheckpoint: function() {
|
|
if (!isProcessing) return;
|
|
|
|
this.lastSaveTime = Date.now();
|
|
|
|
// Sauvegarder l'état actuel de la migration
|
|
updateMigrationData(migration.status);
|
|
|
|
// Sauvegarder également le cache de fédération et les performances API
|
|
federatedCache.saveToStorage();
|
|
apiPerformance.saveToStorage();
|
|
|
|
// Sauvegarder la file d'attente rate-limit si non vide
|
|
if (rateLimitQueue.length > 0) {
|
|
try {
|
|
localStorage.setItem('favmastokey_ratelimit_queue', JSON.stringify(rateLimitQueue));
|
|
} catch (e) {
|
|
console.error("Erreur lors de la sauvegarde de la file d'attente rate-limit", e);
|
|
}
|
|
}
|
|
|
|
addLogEntry("Point de sauvegarde créé automatiquement", "info");
|
|
},
|
|
|
|
// Restaurer depuis un point de sauvegarde
|
|
restore: function() {
|
|
// La restauration principale est déjà gérée dans le code de démarrage
|
|
// Restaurer la file d'attente rate-limit si existante
|
|
const savedQueue = localStorage.getItem('favmastokey_ratelimit_queue');
|
|
if (savedQueue) {
|
|
try {
|
|
rateLimitQueue = JSON.parse(savedQueue);
|
|
if (rateLimitQueue.length > 0) {
|
|
addLogEntry(`Restauration de ${rateLimitQueue.length} favoris en attente après rate-limit`, "info");
|
|
}
|
|
} catch (e) {
|
|
console.error("Erreur lors de la restauration de la file d'attente rate-limit", e);
|
|
rateLimitQueue = [];
|
|
}
|
|
}
|
|
}
|
|
};
|
|
|
|
console.log('Éléments DOM initialisés:', {
|
|
tortoiseCheckbox: !!tortoiseCheckbox,
|
|
tortoiseWarning: !!tortoiseWarning,
|
|
tortoiseOptions: !!tortoiseOptions,
|
|
slowModeCheckbox: !!slowModeCheckbox
|
|
});
|
|
|
|
// Initialisation du cache et des statistiques au chargement
|
|
federatedCache.loadFromStorage();
|
|
apiPerformance.loadFromStorage();
|
|
|
|
// Gérer l'affichage du message d'avertissement pour le mode lent
|
|
if (slowModeCheckbox) {
|
|
slowModeCheckbox.addEventListener('change', function() {
|
|
if (this.checked) {
|
|
console.log('Mode lent activé');
|
|
slowModeWarning.classList.remove('d-none');
|
|
if (slowModeOptions) {
|
|
slowModeOptions.classList.remove('d-none');
|
|
}
|
|
|
|
// Désactiver le mode tortue s'il est activé
|
|
if (tortoiseCheckbox && tortoiseCheckbox.checked) {
|
|
console.log('Désactivation du mode tortue (conflit)');
|
|
tortoiseCheckbox.checked = false;
|
|
if (tortoiseWarning) {
|
|
tortoiseWarning.classList.add('d-none');
|
|
}
|
|
if (tortoiseOptions) {
|
|
tortoiseOptions.classList.add('d-none');
|
|
}
|
|
}
|
|
} else {
|
|
console.log('Mode lent désactivé');
|
|
slowModeWarning.classList.add('d-none');
|
|
if (slowModeOptions) {
|
|
slowModeOptions.classList.add('d-none');
|
|
}
|
|
}
|
|
|
|
// Mettre à jour les options de migration
|
|
migration.options.slowMode = this.checked;
|
|
|
|
// Sauvegarder dans localStorage
|
|
if (localStorage.getItem('favmastokey_migration')) {
|
|
const savedMigration = JSON.parse(localStorage.getItem('favmastokey_migration'));
|
|
savedMigration.options.slowMode = this.checked;
|
|
localStorage.setItem('favmastokey_migration', JSON.stringify(savedMigration));
|
|
}
|
|
});
|
|
}
|
|
|
|
// Gérer le curseur de délai pour le mode lent
|
|
if (slowModeDelay && delayValue) {
|
|
slowModeDelay.addEventListener('input', function() {
|
|
delayValue.textContent = this.value;
|
|
|
|
// Mettre à jour les options de migration
|
|
migration.options.delaySeconds = parseInt(this.value);
|
|
|
|
// Sauvegarder dans localStorage
|
|
if (localStorage.getItem('favmastokey_migration')) {
|
|
const savedMigration = JSON.parse(localStorage.getItem('favmastokey_migration'));
|
|
if (savedMigration.options) {
|
|
savedMigration.options.delaySeconds = parseInt(this.value);
|
|
localStorage.setItem('favmastokey_migration', JSON.stringify(savedMigration));
|
|
}
|
|
}
|
|
});
|
|
}
|
|
|
|
// Gérer l'affichage du message d'avertissement pour le mode tortue
|
|
if (tortoiseCheckbox) {
|
|
tortoiseCheckbox.addEventListener('change', function() {
|
|
console.log('Changement du mode tortue:', this.checked);
|
|
|
|
if (this.checked) {
|
|
// Désactiver le mode lent s'il est activé
|
|
if (slowModeCheckbox && slowModeCheckbox.checked) {
|
|
console.log('Désactivation du mode lent (conflit)');
|
|
slowModeCheckbox.checked = false;
|
|
if (slowModeWarning) {
|
|
slowModeWarning.classList.add('d-none');
|
|
}
|
|
if (slowModeOptions) {
|
|
slowModeOptions.classList.add('d-none');
|
|
}
|
|
}
|
|
|
|
// Activer le mode tortue
|
|
if (tortoiseWarning) {
|
|
tortoiseWarning.classList.remove('d-none');
|
|
}
|
|
if (tortoiseOptions) {
|
|
tortoiseOptions.classList.remove('d-none');
|
|
}
|
|
|
|
console.log('Mode tortue activé avec succès');
|
|
} else {
|
|
// Désactiver le mode tortue
|
|
if (tortoiseWarning) {
|
|
tortoiseWarning.classList.add('d-none');
|
|
}
|
|
if (tortoiseOptions) {
|
|
tortoiseOptions.classList.add('d-none');
|
|
}
|
|
|
|
console.log('Mode tortue désactivé avec succès');
|
|
}
|
|
|
|
// Mettre à jour les options de migration
|
|
migration.options.tortoiseMode = this.checked;
|
|
|
|
// Sauvegarder dans localStorage
|
|
if (localStorage.getItem('favmastokey_migration')) {
|
|
const savedMigration = JSON.parse(localStorage.getItem('favmastokey_migration'));
|
|
savedMigration.options.tortoiseMode = this.checked;
|
|
localStorage.setItem('favmastokey_migration', JSON.stringify(savedMigration));
|
|
}
|
|
});
|
|
}
|
|
|
|
// Gérer le curseur de délai du mode tortue
|
|
if (tortoiseDelay && tortoiseDelayValue) {
|
|
tortoiseDelay.addEventListener('input', function() {
|
|
tortoiseDelayValue.textContent = this.value;
|
|
|
|
// Mettre à jour les options de migration
|
|
migration.options.tortoiseDelaySeconds = parseInt(this.value);
|
|
|
|
// Sauvegarder dans localStorage
|
|
if (localStorage.getItem('favmastokey_migration')) {
|
|
const savedMigration = JSON.parse(localStorage.getItem('favmastokey_migration'));
|
|
if (savedMigration.options) {
|
|
savedMigration.options.tortoiseDelaySeconds = parseInt(this.value);
|
|
localStorage.setItem('favmastokey_migration', JSON.stringify(savedMigration));
|
|
}
|
|
}
|
|
});
|
|
}
|
|
|
|
// Gérer l'option de pause automatique
|
|
if (autoPauseEnabled) {
|
|
autoPauseEnabled.addEventListener('change', function() {
|
|
// Mettre à jour les options de migration
|
|
migration.options.autoPauseEnabled = this.checked;
|
|
|
|
// Sauvegarder dans localStorage
|
|
if (localStorage.getItem('favmastokey_migration')) {
|
|
const savedMigration = JSON.parse(localStorage.getItem('favmastokey_migration'));
|
|
if (savedMigration.options) {
|
|
savedMigration.options.autoPauseEnabled = this.checked;
|
|
localStorage.setItem('favmastokey_migration', JSON.stringify(savedMigration));
|
|
}
|
|
}
|
|
});
|
|
}
|
|
|
|
// Gérer le téléchargement et l'analyse du fichier JSON
|
|
if (uploadForm) {
|
|
uploadForm.addEventListener('submit', function(e) {
|
|
e.preventDefault();
|
|
|
|
const file = jsonFileInput.files[0];
|
|
if (!file) {
|
|
alert('Veuillez sélectionner un fichier JSON.');
|
|
return;
|
|
}
|
|
|
|
// Vérifier l'extension du fichier
|
|
if (!file.name.endsWith('.json')) {
|
|
alert('Le fichier doit être au format JSON.');
|
|
return;
|
|
}
|
|
|
|
// Lire le fichier
|
|
const reader = new FileReader();
|
|
reader.onload = function(event) {
|
|
try {
|
|
const json = JSON.parse(event.target.result);
|
|
|
|
// Vérifier la structure du fichier
|
|
if (!json['@context'] || !json.type || !json.orderedItems) {
|
|
alert('Le format du fichier JSON n\'est pas celui attendu pour un export de favoris Mastodon.');
|
|
return;
|
|
}
|
|
|
|
favoritesList = json.orderedItems;
|
|
totalItems = favoritesList.length;
|
|
|
|
// Afficher un résumé
|
|
fileSummary.innerHTML = `
|
|
<strong>${totalItems}</strong> favoris trouvés dans votre fichier Mastodon.
|
|
`;
|
|
|
|
// Passer à l'étape 2
|
|
step1.classList.add('d-none');
|
|
step2.classList.remove('d-none');
|
|
|
|
// Stocker les données dans localStorage pour les conserver
|
|
localStorage.setItem('favmastokey_favorites', JSON.stringify(favoritesList));
|
|
|
|
// Initialiser les données de migration
|
|
migration = {
|
|
status: 'not_started',
|
|
startTime: null,
|
|
lastUpdateTime: null,
|
|
progress: {
|
|
current: 0,
|
|
total: totalItems,
|
|
percentage: 0
|
|
},
|
|
stats: {
|
|
success: 0,
|
|
error: 0,
|
|
skipped: 0,
|
|
warning: 0
|
|
},
|
|
options: {
|
|
slowMode: slowModeCheckbox ? slowModeCheckbox.checked : false,
|
|
delaySeconds: slowModeDelay ? parseInt(slowModeDelay.value) : 30,
|
|
tortoiseMode: tortoiseCheckbox ? tortoiseCheckbox.checked : false,
|
|
tortoiseDelaySeconds: tortoiseDelay ? parseInt(tortoiseDelay.value) : 120,
|
|
autoPauseEnabled: autoPauseEnabled ? autoPauseEnabled.checked : true
|
|
}
|
|
};
|
|
|
|
// Sauvegarder les données de migration
|
|
localStorage.setItem('favmastokey_migration', JSON.stringify(migration));
|
|
|
|
} catch (error) {
|
|
alert('Erreur lors de l\'analyse du fichier JSON: ' + error.message);
|
|
}
|
|
};
|
|
|
|
reader.onerror = function() {
|
|
alert('Erreur lors de la lecture du fichier.');
|
|
};
|
|
|
|
reader.readAsText(file);
|
|
});
|
|
}
|
|
|
|
// Gestion du retour à l'étape 1
|
|
if (backToStep1) {
|
|
backToStep1.addEventListener('click', function() {
|
|
step2.classList.add('d-none');
|
|
step1.classList.remove('d-none');
|
|
});
|
|
}
|
|
|
|
// Vérifier si nous sommes à l'étape 3 (basé sur l'ancre dans l'URL)
|
|
if (window.location.hash === '#step3' && document.getElementById('step3')) {
|
|
// Récupérer les favoris du localStorage
|
|
if (localStorage.getItem('favmastokey_favorites')) {
|
|
favoritesList = JSON.parse(localStorage.getItem('favmastokey_favorites'));
|
|
totalItems = favoritesList.length;
|
|
|
|
// Montrer l'étape 3
|
|
step1.classList.add('d-none');
|
|
step2.classList.add('d-none');
|
|
step3.classList.remove('d-none');
|
|
|
|
// Récupérer les données de migration du localStorage
|
|
if (localStorage.getItem('favmastokey_migration')) {
|
|
migration = JSON.parse(localStorage.getItem('favmastokey_migration'));
|
|
currentIndex = migration.progress.current;
|
|
updateProgress(migration.progress.percentage);
|
|
|
|
// Restaurer les statistiques
|
|
successCount = migration.stats.success || 0;
|
|
errorCount = migration.stats.error || 0;
|
|
skippedCount = migration.stats.skipped || 0;
|
|
warningCount = migration.stats.warning || 0;
|
|
|
|
// Restaurer l'état du mode lent si disponible
|
|
if (migration.options) {
|
|
// Restaurer l'état du mode lent
|
|
if (migration.options.slowMode !== undefined && slowModeCheckbox) {
|
|
slowModeCheckbox.checked = migration.options.slowMode;
|
|
|
|
// Mettre à jour l'affichage de l'avertissement
|
|
if (slowModeWarning) {
|
|
if (migration.options.slowMode) {
|
|
slowModeWarning.classList.remove('d-none');
|
|
if (slowModeOptions) {
|
|
slowModeOptions.classList.remove('d-none');
|
|
}
|
|
} else {
|
|
slowModeWarning.classList.add('d-none');
|
|
if (slowModeOptions) {
|
|
slowModeOptions.classList.add('d-none');
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
// Restaurer la valeur du délai
|
|
if (migration.options.delaySeconds && slowModeDelay && delayValue) {
|
|
slowModeDelay.value = migration.options.delaySeconds;
|
|
delayValue.textContent = migration.options.delaySeconds;
|
|
}
|
|
|
|
// Restaurer l'état du mode tortue
|
|
if (migration.options.tortoiseMode !== undefined && tortoiseCheckbox) {
|
|
tortoiseCheckbox.checked = migration.options.tortoiseMode;
|
|
console.log('État du mode tortue restauré:', migration.options.tortoiseMode);
|
|
|
|
// Mettre à jour l'affichage de l'avertissement
|
|
if (tortoiseWarning) {
|
|
if (migration.options.tortoiseMode) {
|
|
tortoiseWarning.classList.remove('d-none');
|
|
if (tortoiseOptions) {
|
|
tortoiseOptions.classList.remove('d-none');
|
|
}
|
|
} else {
|
|
tortoiseWarning.classList.add('d-none');
|
|
if (tortoiseOptions) {
|
|
tortoiseOptions.classList.add('d-none');
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
// Restaurer la valeur du délai tortue
|
|
if (migration.options.tortoiseDelaySeconds && tortoiseDelay && tortoiseDelayValue) {
|
|
tortoiseDelay.value = migration.options.tortoiseDelaySeconds;
|
|
tortoiseDelayValue.textContent = migration.options.tortoiseDelaySeconds;
|
|
}
|
|
|
|
// Restaurer l'état de pause automatique
|
|
if (migration.options.autoPauseEnabled !== undefined && autoPauseEnabled) {
|
|
autoPauseEnabled.checked = migration.options.autoPauseEnabled;
|
|
}
|
|
}
|
|
|
|
// AMÉLIORATION 5: Restaurer depuis les points de sauvegarde
|
|
saveCheckpoints.restore();
|
|
}
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Met à jour les données de migration dans localStorage
|
|
*/
|
|
function updateMigrationData(status, progress = null) {
|
|
migration.status = status;
|
|
migration.lastUpdateTime = Date.now();
|
|
|
|
if (progress) {
|
|
migration.progress = progress;
|
|
} else {
|
|
migration.progress = {
|
|
current: currentIndex,
|
|
total: totalItems,
|
|
percentage: (currentIndex / totalItems) * 100
|
|
};
|
|
}
|
|
|
|
migration.stats = {
|
|
success: successCount,
|
|
error: errorCount,
|
|
skipped: skippedCount,
|
|
warning: warningCount
|
|
};
|
|
|
|
migration.options = {
|
|
slowMode: slowModeCheckbox ? slowModeCheckbox.checked : false,
|
|
delaySeconds: slowModeDelay ? parseInt(slowModeDelay.value) : 30,
|
|
tortoiseMode: tortoiseCheckbox ? tortoiseCheckbox.checked : false,
|
|
tortoiseDelaySeconds: tortoiseDelay ? parseInt(tortoiseDelay.value) : 120,
|
|
autoPauseEnabled: autoPauseEnabled ? autoPauseEnabled.checked : true
|
|
};
|
|
|
|
// Sauvegarder dans localStorage
|
|
localStorage.setItem('favmastokey_migration', JSON.stringify(migration));
|
|
}
|
|
|
|
/**
|
|
* Réinitialise les données de migration
|
|
*/
|
|
function resetMigration() {
|
|
currentIndex = 0;
|
|
successCount = 0;
|
|
errorCount = 0;
|
|
skippedCount = 0;
|
|
warningCount = 0;
|
|
consecutiveRateLimitErrors = 0;
|
|
adaptiveDelayIncreases = 0;
|
|
|
|
// Réinitialiser la file d'attente rate-limit
|
|
rateLimitQueue = [];
|
|
localStorage.removeItem('favmastokey_ratelimit_queue');
|
|
|
|
migration = {
|
|
status: 'not_started',
|
|
startTime: null,
|
|
lastUpdateTime: null,
|
|
progress: {
|
|
current: 0,
|
|
total: totalItems,
|
|
percentage: 0
|
|
},
|
|
stats: {
|
|
success: 0,
|
|
error: 0,
|
|
skipped: 0,
|
|
warning: 0
|
|
},
|
|
options: {
|
|
slowMode: slowModeCheckbox ? slowModeCheckbox.checked : false,
|
|
delaySeconds: slowModeDelay ? parseInt(slowModeDelay.value) : 30,
|
|
tortoiseMode: tortoiseCheckbox ? tortoiseCheckbox.checked : false,
|
|
tortoiseDelaySeconds: tortoiseDelay ? parseInt(tortoiseDelay.value) : 120,
|
|
autoPauseEnabled: autoPauseEnabled ? autoPauseEnabled.checked : true
|
|
}
|
|
};
|
|
|
|
localStorage.setItem('favmastokey_migration', JSON.stringify(migration));
|
|
updateProgress(0);
|
|
}
|
|
|
|
/**
|
|
* Augmente le délai en cas de détection de rate limit
|
|
*/
|
|
function increaseAdaptiveDelay() {
|
|
if (adaptiveDelayIncreases < 3) {
|
|
if (tortoiseCheckbox && tortoiseCheckbox.checked && tortoiseDelay && tortoiseDelayValue) {
|
|
// Augmenter le délai du mode tortue
|
|
const currentDelay = parseInt(tortoiseDelay.value);
|
|
const newDelay = Math.min(300, currentDelay + 30); // Augmenter de 30 secondes jusqu'à 5 min max
|
|
|
|
tortoiseDelay.value = newDelay;
|
|
tortoiseDelayValue.textContent = newDelay;
|
|
|
|
// Mettre à jour les options de migration
|
|
migration.options.tortoiseDelaySeconds = newDelay;
|
|
|
|
// Sauvegarder dans localStorage
|
|
if (localStorage.getItem('favmastokey_migration')) {
|
|
const savedMigration = JSON.parse(localStorage.getItem('favmastokey_migration'));
|
|
savedMigration.options.tortoiseDelaySeconds = newDelay;
|
|
localStorage.setItem('favmastokey_migration', JSON.stringify(savedMigration));
|
|
}
|
|
|
|
adaptiveDelayIncreases++;
|
|
|
|
addLogEntry(`Délai du mode tortue automatiquement augmenté à ${newDelay} secondes pour éviter les limitations d'API.`, 'warning');
|
|
return true;
|
|
} else if (slowModeCheckbox && slowModeCheckbox.checked && slowModeDelay && delayValue) {
|
|
// Augmenter le délai du mode lent
|
|
const currentDelay = parseInt(slowModeDelay.value);
|
|
const newDelay = Math.min(300, currentDelay + 30); // Augmenter de 30 secondes jusqu'à 5 min max
|
|
|
|
slowModeDelay.value = newDelay;
|
|
delayValue.textContent = newDelay;
|
|
|
|
// Mettre à jour les options de migration
|
|
migration.options.delaySeconds = newDelay;
|
|
|
|
// Sauvegarder dans localStorage
|
|
if (localStorage.getItem('favmastokey_migration')) {
|
|
const savedMigration = JSON.parse(localStorage.getItem('favmastokey_migration'));
|
|
savedMigration.options.delaySeconds = newDelay;
|
|
localStorage.setItem('favmastokey_migration', JSON.stringify(savedMigration));
|
|
}
|
|
|
|
adaptiveDelayIncreases++;
|
|
|
|
addLogEntry(`Délai du mode lent automatiquement augmenté à ${newDelay} secondes pour éviter les limitations d'API.`, 'warning');
|
|
return true;
|
|
}
|
|
}
|
|
return false;
|
|
}
|
|
|
|
/**
|
|
* Déclenche une pause automatique due aux limites d'API
|
|
*/
|
|
function triggerRateLimitPause() {
|
|
if (rateLimitPauseActive) return; // Éviter les pauses multiples
|
|
|
|
// Vérifier si la pause automatique est activée
|
|
if (autoPauseEnabled && !autoPauseEnabled.checked) {
|
|
addLogEntry("Pause automatique désactivée. Considérez activer cette option ou augmenter le délai.", 'warning');
|
|
return;
|
|
}
|
|
|
|
rateLimitPauseActive = true;
|
|
isPaused = true;
|
|
|
|
// Durée de pause en minutes (15 minutes)
|
|
const pauseDurationMinutes = 15;
|
|
const pauseDurationMs = pauseDurationMinutes * 60 * 1000;
|
|
|
|
addLogEntry(`Trop de limitations d'API détectées! Pause automatique de ${pauseDurationMinutes} minutes pour permettre la réinitialisation des limites.`, 'error');
|
|
|
|
if (pauseMigration) {
|
|
pauseMigration.textContent = 'Reprise automatique en attente...';
|
|
pauseMigration.disabled = true;
|
|
}
|
|
|
|
// Mise à jour du statut
|
|
updateMigrationData('auto_paused');
|
|
|
|
// Programmer la reprise automatique
|
|
autoRestartTimer = setTimeout(() => {
|
|
if (isProcessing && isPaused) {
|
|
isPaused = false;
|
|
rateLimitPauseActive = false;
|
|
|
|
if (pauseMigration) {
|
|
pauseMigration.textContent = 'Pause';
|
|
pauseMigration.disabled = false;
|
|
}
|
|
|
|
addLogEntry(`Reprise automatique après pause de ${pauseDurationMinutes} minutes.`, 'info');
|
|
updateMigrationData('in_progress');
|
|
|
|
// Redémarrer avec un délai réduit pour tester si les limites sont réinitialisées
|
|
if (tortoiseCheckbox && tortoiseCheckbox.checked && tortoiseDelay && tortoiseDelayValue) {
|
|
const currentDelay = parseInt(tortoiseDelay.value);
|
|
// Réduire le délai mais pas en dessous de 60 secondes
|
|
const newDelay = Math.max(60, currentDelay - 60);
|
|
tortoiseDelay.value = newDelay;
|
|
tortoiseDelayValue.textContent = newDelay;
|
|
migration.options.tortoiseDelaySeconds = newDelay;
|
|
} else if (slowModeCheckbox && slowModeCheckbox.checked && slowModeDelay && delayValue) {
|
|
const currentDelay = parseInt(slowModeDelay.value);
|
|
// Réduire le délai mais pas en dessous de 30 secondes
|
|
const newDelay = Math.max(30, currentDelay - 10);
|
|
slowModeDelay.value = newDelay;
|
|
delayValue.textContent = newDelay;
|
|
migration.options.delaySeconds = newDelay;
|
|
}
|
|
|
|
processBatch();
|
|
}
|
|
}, pauseDurationMs);
|
|
|
|
// Afficher un compte à rebours
|
|
let remainingMinutes = pauseDurationMinutes;
|
|
const countdownInterval = setInterval(() => {
|
|
remainingMinutes--;
|
|
if (remainingMinutes <= 0) {
|
|
clearInterval(countdownInterval);
|
|
return;
|
|
}
|
|
|
|
if (pauseMigration) {
|
|
pauseMigration.textContent = `Reprise dans ${remainingMinutes}m...`;
|
|
}
|
|
}, 60000); // Mise à jour toutes les minutes
|
|
}
|
|
|
|
// Gérer le processus de migration
|
|
if (startMigration) {
|
|
startMigration.addEventListener('click', function() {
|
|
if (isProcessing) return;
|
|
|
|
// Récupérer les favoris et les données de migration depuis localStorage si nécessaire
|
|
if (favoritesList.length === 0 && localStorage.getItem('favmastokey_favorites')) {
|
|
favoritesList = JSON.parse(localStorage.getItem('favmastokey_favorites'));
|
|
totalItems = favoritesList.length;
|
|
}
|
|
|
|
// Vérifier s'il y a une migration en cours à reprendre
|
|
if (localStorage.getItem('favmastokey_migration')) {
|
|
const savedMigration = JSON.parse(localStorage.getItem('favmastokey_migration'));
|
|
|
|
// Si la migration était en cours ou en pause, proposer de la reprendre
|
|
if (savedMigration.status === 'in_progress' || savedMigration.status === 'paused' || savedMigration.status === 'auto_paused') {
|
|
const resumeConfirm = confirm(`Une migration précédente a été trouvée (${savedMigration.progress.percentage.toFixed(1)}% terminée). Voulez-vous la reprendre?`);
|
|
|
|
if (resumeConfirm) {
|
|
// Restaurer l'état de la migration
|
|
migration = savedMigration;
|
|
currentIndex = migration.progress.current;
|
|
|
|
// Mettre à jour l'interface avec les données sauvegardées
|
|
updateProgress(migration.progress.percentage);
|
|
|
|
// Restaurer les statistiques
|
|
successCount = migration.stats.success || 0;
|
|
errorCount = migration.stats.error || 0;
|
|
skippedCount = migration.stats.skipped || 0;
|
|
warningCount = migration.stats.warning || 0;
|
|
|
|
// Restaurer les options
|
|
if (migration.options) {
|
|
// Restaurer l'état du mode lent
|
|
if (migration.options.slowMode !== undefined && slowModeCheckbox) {
|
|
slowModeCheckbox.checked = migration.options.slowMode;
|
|
|
|
// Mettre à jour l'affichage de l'avertissement
|
|
if (slowModeWarning) {
|
|
if (migration.options.slowMode) {
|
|
slowModeWarning.classList.remove('d-none');
|
|
if (slowModeOptions) {
|
|
slowModeOptions.classList.remove('d-none');
|
|
}
|
|
} else {
|
|
slowModeWarning.classList.add('d-none');
|
|
if (slowModeOptions) {
|
|
slowModeOptions.classList.add('d-none');
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
// Restaurer la valeur du délai
|
|
if (migration.options.delaySeconds && slowModeDelay && delayValue) {
|
|
slowModeDelay.value = migration.options.delaySeconds;
|
|
delayValue.textContent = migration.options.delaySeconds;
|
|
}
|
|
|
|
// Restaurer l'état du mode tortue
|
|
if (migration.options.tortoiseMode !== undefined && tortoiseCheckbox) {
|
|
tortoiseCheckbox.checked = migration.options.tortoiseMode;
|
|
console.log('État du mode tortue restauré:', migration.options.tortoiseMode);
|
|
|
|
// Mettre à jour l'affichage de l'avertissement
|
|
if (tortoiseWarning) {
|
|
if (migration.options.tortoiseMode) {
|
|
tortoiseWarning.classList.remove('d-none');
|
|
if (tortoiseOptions) {
|
|
tortoiseOptions.classList.remove('d-none');
|
|
}
|
|
} else {
|
|
tortoiseWarning.classList.add('d-none');
|
|
if (tortoiseOptions) {
|
|
tortoiseOptions.classList.add('d-none');
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
// Restaurer la valeur du délai tortue
|
|
if (migration.options.tortoiseDelaySeconds && tortoiseDelay && tortoiseDelayValue) {
|
|
tortoiseDelay.value = migration.options.tortoiseDelaySeconds;
|
|
tortoiseDelayValue.textContent = migration.options.tortoiseDelaySeconds;
|
|
}
|
|
|
|
// Restaurer l'état de pause automatique
|
|
if (migration.options.autoPauseEnabled !== undefined && autoPauseEnabled) {
|
|
autoPauseEnabled.checked = migration.options.autoPauseEnabled;
|
|
}
|
|
}
|
|
|
|
// AMÉLIORATION 5: Restaurer depuis les points de sauvegarde
|
|
saveCheckpoints.restore();
|
|
|
|
// Afficher un résumé
|
|
addLogEntry(`Reprise de la migration: ${successCount} réussis, ${errorCount} échecs, ${skippedCount} ignorés, ${warningCount} avertissements.`, 'info');
|
|
} else {
|
|
// Réinitialiser la migration
|
|
resetMigration();
|
|
}
|
|
} else {
|
|
// Réinitialiser la migration
|
|
resetMigration();
|
|
}
|
|
} else {
|
|
// Initialiser une nouvelle migration
|
|
resetMigration();
|
|
}
|
|
|
|
if (favoritesList.length === 0) {
|
|
addLogEntry('Aucun favori à migrer. Veuillez d\'abord télécharger votre fichier JSON.', 'error');
|
|
return;
|
|
}
|
|
|
|
// Démarrer la migration
|
|
isProcessing = true;
|
|
isPaused = false;
|
|
rateLimitPauseActive = false;
|
|
|
|
// Annuler le timer de reprise automatique si en cours
|
|
if (autoRestartTimer) {
|
|
clearTimeout(autoRestartTimer);
|
|
autoRestartTimer = null;
|
|
}
|
|
|
|
startMigration.classList.add('d-none');
|
|
pauseMigration.classList.remove('d-none');
|
|
pauseMigration.textContent = 'Pause';
|
|
pauseMigration.disabled = false;
|
|
|
|
// Initialiser le temps de démarrage si c'est une nouvelle migration
|
|
if (migration.status === 'not_started' || migration.startTime === null) {
|
|
migration.startTime = Date.now();
|
|
}
|
|
|
|
// Démarrer les sauvegardes automatiques
|
|
saveCheckpoints.start();
|
|
|
|
// Mettre à jour le statut de la migration
|
|
updateMigrationData('in_progress');
|
|
|
|
// Mettre à jour les options en fonction des dernières valeurs des cases à cocher
|
|
let isTortoiseMode = tortoiseCheckbox && tortoiseCheckbox.checked;
|
|
let isSlowMode = slowModeCheckbox && slowModeCheckbox.checked;
|
|
|
|
// S'assurer que les deux modes ne sont pas activés en même temps
|
|
if (isTortoiseMode && isSlowMode) {
|
|
console.log("CONFLIT DE MODES: les deux modes sont activés, désactivation du mode lent");
|
|
if (slowModeCheckbox) {
|
|
slowModeCheckbox.checked = false;
|
|
isSlowMode = false;
|
|
if (slowModeWarning) {
|
|
slowModeWarning.classList.add('d-none');
|
|
}
|
|
if (slowModeOptions) {
|
|
slowModeOptions.classList.add('d-none');
|
|
}
|
|
}
|
|
}
|
|
|
|
// Ajouter une entrée pour le mode utilisé
|
|
let modeName = 'Mode normal';
|
|
let delayValue = 3;
|
|
|
|
if (isTortoiseMode) {
|
|
delayValue = tortoiseDelay ? parseInt(tortoiseDelay.value) : 120;
|
|
modeName = `Mode tortue activé (${delayValue}s de délai)`;
|
|
console.log('Mode tortue détecté pour la migration');
|
|
} else if (isSlowMode) {
|
|
delayValue = slowModeDelay ? parseInt(slowModeDelay.value) : 30;
|
|
modeName = `Mode ultra-lent activé (${delayValue}s de délai)`;
|
|
console.log('Mode lent détecté pour la migration');
|
|
} else {
|
|
console.log('Mode normal détecté pour la migration');
|
|
}
|
|
|
|
addLogEntry(`Démarrage de la migration... (${modeName})`, 'info');
|
|
|
|
// Lancer le processus de migration
|
|
processNextBatch();
|
|
});
|
|
}
|
|
|
|
// Gérer la pause de la migration
|
|
if (pauseMigration) {
|
|
pauseMigration.addEventListener('click', function() {
|
|
if (!isProcessing || pauseMigration.disabled) return;
|
|
|
|
isPaused = !isPaused;
|
|
|
|
if (isPaused) {
|
|
pauseMigration.textContent = 'Reprendre';
|
|
addLogEntry('Migration en pause.', 'warning');
|
|
currentProgress.classList.remove('active');
|
|
|
|
// Mettre à jour le statut de la migration
|
|
updateMigrationData('paused');
|
|
|
|
// Arrêter les sauvegardes automatiques
|
|
saveCheckpoints.stop();
|
|
} else {
|
|
pauseMigration.textContent = 'Pause';
|
|
addLogEntry('Reprise de la migration...', 'info');
|
|
currentProgress.classList.add('active');
|
|
|
|
// Mettre à jour le statut de la migration
|
|
updateMigrationData('in_progress');
|
|
|
|
// Redémarrer les sauvegardes automatiques
|
|
saveCheckpoints.start();
|
|
|
|
// Reprendre le traitement
|
|
processNextBatch();
|
|
}
|
|
});
|
|
}
|
|
|
|
// Gérer l'annulation de la migration
|
|
if (cancelMigration) {
|
|
cancelMigration.addEventListener('click', function() {
|
|
if (!isProcessing && currentIndex === 0) {
|
|
// Retour à l'étape 1 si rien n'a commencé
|
|
step3.classList.add('d-none');
|
|
step1.classList.remove('d-none');
|
|
return;
|
|
}
|
|
|
|
const confirmCancel = confirm('Êtes-vous sûr de vouloir annuler la migration en cours ?');
|
|
if (confirmCancel) {
|
|
isProcessing = false;
|
|
isPaused = false;
|
|
rateLimitPauseActive = false;
|
|
|
|
// Arrêter les sauvegardes automatiques
|
|
saveCheckpoints.stop();
|
|
|
|
// Annuler le timer de reprise automatique si en cours
|
|
if (autoRestartTimer) {
|
|
clearTimeout(autoRestartTimer);
|
|
autoRestartTimer = null;
|
|
}
|
|
|
|
addLogEntry('Migration annulée.', 'error');
|
|
|
|
// Réinitialiser l'interface
|
|
startMigration.classList.remove('d-none');
|
|
pauseMigration.classList.add('d-none');
|
|
pauseMigration.textContent = 'Pause';
|
|
pauseMigration.disabled = false;
|
|
currentProgress.classList.remove('active');
|
|
|
|
// Réinitialiser les données de migration
|
|
resetMigration();
|
|
}
|
|
});
|
|
}
|
|
|
|
/**
|
|
* Point d'entrée principal du processus de traitement des favoris
|
|
* Gère à la fois les favoris normaux et ceux en file d'attente après rate-limit
|
|
*/
|
|
function processNextBatch() {
|
|
if (!isProcessing || isPaused) {
|
|
return;
|
|
}
|
|
|
|
// AMÉLIORATION 1: Vérifier d'abord la file d'attente des favoris en rate-limit
|
|
if (rateLimitQueue.length > 0) {
|
|
addLogEntry(`Traitement des favoris en attente après rate-limit (${rateLimitQueue.length} restants)`, 'info');
|
|
const urlToRetry = rateLimitQueue.shift();
|
|
|
|
// Enregistrer l'état de la file d'attente dans localStorage
|
|
if (rateLimitQueue.length > 0) {
|
|
localStorage.setItem('favmastokey_ratelimit_queue', JSON.stringify(rateLimitQueue));
|
|
} else {
|
|
localStorage.removeItem('favmastokey_ratelimit_queue');
|
|
}
|
|
|
|
// Traiter cette URL spécifique
|
|
processBatch([urlToRetry], true);
|
|
return;
|
|
}
|
|
|
|
// Vérifier si nous avons terminé
|
|
if (currentIndex >= totalItems) {
|
|
// Migration terminée
|
|
finishMigration();
|
|
return;
|
|
}
|
|
|
|
// Vérification des modes et débogage
|
|
let isTortoiseMode = tortoiseCheckbox && tortoiseCheckbox.checked;
|
|
let isSlowMode = slowModeCheckbox && slowModeCheckbox.checked;
|
|
|
|
// Nombre d'éléments à traiter dans ce lot
|
|
const batchSize = (isTortoiseMode || isSlowMode) ? 1 : 2;
|
|
const endIndex = Math.min(currentIndex + batchSize, totalItems);
|
|
|
|
// Préparer les éléments du lot
|
|
const batch = favoritesList.slice(currentIndex, endIndex);
|
|
|
|
// Traiter le lot normal
|
|
processBatch(batch, false);
|
|
}
|
|
|
|
/**
|
|
* Traite un lot de favoris
|
|
* @param {Array} batch - URLs à traiter
|
|
* @param {boolean} isRetry - Indique si c'est une tentative après rate-limit
|
|
*/
|
|
function processBatch(batch, isRetry = false) {
|
|
if (!isProcessing || isPaused) {
|
|
return;
|
|
}
|
|
|
|
// Mettre à jour la progression actuelle
|
|
currentProgress.classList.add('active');
|
|
updateProgress();
|
|
|
|
// Ajouter une entrée dans le journal si ce n'est pas une tentative après rate-limit
|
|
if (!isRetry) {
|
|
const endIndex = Math.min(currentIndex + batch.length, totalItems);
|
|
addLogEntry(`Traitement du lot ${currentIndex + 1} à ${endIndex}...`, 'info');
|
|
}
|
|
|
|
// Compteur de tentatives pour cette requête
|
|
let retryAttempt = 0;
|
|
const maxRetries = 3;
|
|
|
|
// Variables pour mesurer les performances
|
|
const requestStartTime = Date.now();
|
|
|
|
// Fonction pour envoyer la requête avec retry automatique
|
|
function sendRequest() {
|
|
// Déterminer le délai à utiliser - Assurez-vous que les valeurs sont à jour
|
|
let isTortoiseMode = tortoiseCheckbox && tortoiseCheckbox.checked;
|
|
let isSlowMode = slowModeCheckbox && slowModeCheckbox.checked;
|
|
|
|
let delaySeconds = 3;
|
|
if (isTortoiseMode && tortoiseDelay) {
|
|
delaySeconds = parseInt(tortoiseDelay.value);
|
|
} else if (isSlowMode && slowModeDelay) {
|
|
delaySeconds = parseInt(slowModeDelay.value);
|
|
}
|
|
|
|
// AMÉLIORATION 3: Vérifier le cache pour les URLs du lot
|
|
// Préparer les données avec le cache quand disponible
|
|
const batchData = batch.map(url => {
|
|
if (federatedCache.has(url)) {
|
|
return {
|
|
url: url,
|
|
cached: true,
|
|
cachedId: federatedCache.get(url)
|
|
};
|
|
}
|
|
return {
|
|
url: url,
|
|
cached: false
|
|
};
|
|
});
|
|
|
|
// Journaliser les URLs mises en cache
|
|
const cachedCount = batchData.filter(item => item.cached).length;
|
|
if (cachedCount > 0) {
|
|
console.log(`${cachedCount}/${batchData.length} URLs trouvées dans le cache de fédération`, batchData);
|
|
|
|
// Ajouter une entrée dans le journal si toutes les URLs sont cachées
|
|
if (cachedCount === batchData.length) {
|
|
addLogEntry(`Traitement accéléré: tous les IDs déjà connus pour ce lot`, 'info');
|
|
}
|
|
}
|
|
|
|
console.log('Envoi de la requête avec paramètres:', {
|
|
tortoiseMode: isTortoiseMode,
|
|
slowMode: isSlowMode,
|
|
delaySeconds: delaySeconds,
|
|
batchWithCache: batchData
|
|
});
|
|
|
|
fetch('process.php', {
|
|
method: 'POST',
|
|
headers: {
|
|
'Content-Type': 'application/json'
|
|
},
|
|
body: JSON.stringify({
|
|
batch: batch,
|
|
batchData: batchData, // Inclure les données du cache
|
|
currentIndex: currentIndex,
|
|
totalItems: totalItems,
|
|
slowMode: isSlowMode,
|
|
tortoiseMode: isTortoiseMode,
|
|
delaySeconds: delaySeconds,
|
|
useCachedIds: true // Indiquer au serveur d'utiliser les IDs en cache quand disponibles
|
|
}),
|
|
// Augmenter le timeout pour éviter les erreurs de limite de temps
|
|
timeout: 60000
|
|
})
|
|
.then(response => {
|
|
// Calculer le temps de réponse
|
|
const responseTime = Date.now() - requestStartTime;
|
|
|
|
// Vérifier si la réponse est au format JSON
|
|
const contentType = response.headers.get('content-type');
|
|
if (!contentType || !contentType.includes('application/json')) {
|
|
// Si ce n'est pas du JSON, récupérer le texte pour déboguer
|
|
return response.text().then(text => {
|
|
throw new Error(`Réponse non-JSON reçue: ${text.substring(0, 100)}${text.length > 100 ? '...' : ''}`);
|
|
});
|
|
}
|
|
return response.json().then(data => ({ data, responseTime }));
|
|
})
|
|
.then(({ data, responseTime }) => {
|
|
if (data.success) {
|
|
// Traiter les résultats
|
|
let hasRateLimitError = false;
|
|
let allAlreadyFavorited = true; // Pour AMÉLIORATION 2
|
|
|
|
if (data.results && data.results.length) {
|
|
data.results.forEach((result, index) => {
|
|
const url = batch[index];
|
|
|
|
// Construction du message avec détails si disponibles
|
|
let message = result.message;
|
|
if (result.details) {
|
|
message += ` (${result.details})`;
|
|
}
|
|
|
|
addLogEntry(message, result.status);
|
|
|
|
// AMÉLIORATION 3: Mettre à jour le cache avec les résultats réussis
|
|
if (result.status === 'success' && result.misskey_id) {
|
|
federatedCache.add(url, result.misskey_id);
|
|
}
|
|
|
|
// AMÉLIORATION 4: Mettre à jour les statistiques de performance API
|
|
const domain = extractDomain(url);
|
|
let apiSuccess = result.status === 'success' ? true :
|
|
(result.status === 'error' || result.status === 'warning') ? false : null;
|
|
|
|
if (result.error_type === 'rate_limit') {
|
|
apiSuccess = 'rate_limit';
|
|
}
|
|
|
|
if (domain) {
|
|
apiPerformance.addResponseTime(domain, responseTime, apiSuccess);
|
|
}
|
|
|
|
// AMÉLIORATION 2: Vérifier si c'est déjà favoris pour accélérer le traitement
|
|
if (result.status !== 'info' || result.error_type !== 'already_favorited') {
|
|
allAlreadyFavorited = false;
|
|
}
|
|
|
|
// Mettre à jour les compteurs
|
|
if (result.status === 'success') {
|
|
successCount++;
|
|
consecutiveRateLimitErrors = 0; // Réinitialiser le compteur en cas de succès
|
|
} else if (result.status === 'error') {
|
|
errorCount++;
|
|
} else if (result.status === 'info') {
|
|
skippedCount++;
|
|
} else if (result.status === 'warning') {
|
|
warningCount++;
|
|
|
|
// AMÉLIORATION 1: Gérer les erreurs de rate limit
|
|
if (result.error_type === 'rate_limit') {
|
|
hasRateLimitError = true;
|
|
|
|
// Ajouter à la file d'attente pour réessayer plus tard
|
|
if (!rateLimitQueue.includes(url)) {
|
|
rateLimitQueue.push(url);
|
|
addLogEntry(`URL ajoutée à la file d'attente pour réessai après rate-limit: ${url}`, 'info');
|
|
|
|
// Sauvegarder la file d'attente
|
|
localStorage.setItem('favmastokey_ratelimit_queue', JSON.stringify(rateLimitQueue));
|
|
}
|
|
}
|
|
}
|
|
});
|
|
}
|
|
|
|
// Mettre à jour l'index si ce n'est pas un réessai après rate-limit
|
|
if (!isRetry) {
|
|
currentIndex = Math.min(currentIndex + batch.length, totalItems);
|
|
}
|
|
|
|
// Mettre à jour la progression
|
|
updateProgress(data.progress.percentage);
|
|
|
|
// Mettre à jour les données de migration
|
|
updateMigrationData('in_progress', data.progress);
|
|
|
|
// Si nous avons détecté une erreur de rate limit
|
|
if (hasRateLimitError) {
|
|
consecutiveRateLimitErrors++;
|
|
|
|
if (consecutiveRateLimitErrors >= 3) {
|
|
// Trop d'erreurs consécutives, déclencher une pause longue
|
|
triggerRateLimitPause();
|
|
return;
|
|
} else if (consecutiveRateLimitErrors >= 2) {
|
|
// Si 2 erreurs consécutives, augmenter le délai automatiquement
|
|
// AMÉLIORATION 4: Utiliser les statistiques pour ajuster le délai
|
|
adjustDelayBasedOnPerformance();
|
|
|
|
// Traiter le lot suivant après un délai plus long
|
|
setTimeout(processNextBatch, 30000); // 30 secondes de pause supplémentaire
|
|
return;
|
|
}
|
|
} else {
|
|
// Réduire le compteur de rate limit si la requête s'est bien passée
|
|
consecutiveRateLimitErrors = Math.max(0, consecutiveRateLimitErrors - 1);
|
|
}
|
|
|
|
// Si le serveur suggère d'utiliser un mode plus lent
|
|
if (data.suggestions) {
|
|
if (data.suggestions.use_tortoise_mode && !isTortoiseMode) {
|
|
addLogEntry("L'API semble limiter les requêtes. Il est recommandé d'activer le 'Mode tortue'.", 'warning');
|
|
} else if (data.suggestions.use_slow_mode && !isSlowMode && !isTortoiseMode) {
|
|
addLogEntry("L'API semble limiter les requêtes. Il est recommandé d'activer le 'Mode ultra-lent'.", 'warning');
|
|
} else if (data.suggestions.increase_delay) {
|
|
if (isTortoiseMode) {
|
|
addLogEntry("Considérez augmenter le délai du mode tortue pour réduire les limitations d'API.", 'warning');
|
|
} else if (isSlowMode) {
|
|
addLogEntry("Considérez augmenter le délai du mode ultra-lent pour réduire les limitations d'API.", 'warning');
|
|
}
|
|
}
|
|
}
|
|
|
|
// AMÉLIORATION 2: Accélérer quand tous les favoris sont déjà traités
|
|
if (allAlreadyFavorited) {
|
|
// Pas besoin d'attendre le délai complet pour les notes déjà dans les favoris
|
|
addLogEntry(`Tous les favoris de ce lot sont déjà présents, traitement immédiat du lot suivant`, 'info');
|
|
processNextBatch();
|
|
return;
|
|
}
|
|
|
|
// Traiter le lot suivant après un délai adapté au mode
|
|
let nextBatchDelay = 3000; // Délai par défaut
|
|
|
|
// Mettre à jour les états des modes
|
|
isTortoiseMode = tortoiseCheckbox && tortoiseCheckbox.checked;
|
|
isSlowMode = slowModeCheckbox && slowModeCheckbox.checked;
|
|
|
|
if (isTortoiseMode) {
|
|
// Utiliser le délai du mode tortue
|
|
nextBatchDelay = delaySeconds * 1000;
|
|
console.log('Utilisation du délai tortue:', nextBatchDelay);
|
|
} else if (isSlowMode) {
|
|
// Utiliser le délai du mode ultra-lent
|
|
nextBatchDelay = delaySeconds * 1000;
|
|
console.log('Utilisation du délai lent:', nextBatchDelay);
|
|
} else {
|
|
console.log('Utilisation du délai normal:', nextBatchDelay);
|
|
}
|
|
|
|
setTimeout(processNextBatch, nextBatchDelay);
|
|
} else {
|
|
// Gérer l'erreur
|
|
addLogEntry('Erreur: ' + data.message, 'error');
|
|
|
|
// Pause en cas d'erreur
|
|
isPaused = true;
|
|
pauseMigration.textContent = 'Reprendre';
|
|
currentProgress.classList.remove('active');
|
|
|
|
// Mettre à jour le statut de la migration
|
|
updateMigrationData('error');
|
|
}
|
|
})
|
|
.catch(error => {
|
|
// Afficher l'erreur détaillée dans la console pour le débogage
|
|
console.error('Erreur complète:', error);
|
|
|
|
// Ajouter l'erreur au journal
|
|
addLogEntry('Erreur de connexion: ' + error.message, 'error');
|
|
|
|
// Retenter si nous n'avons pas atteint le nombre maximum de tentatives
|
|
if (retryAttempt < maxRetries) {
|
|
retryAttempt++;
|
|
const waitTime = Math.pow(2, retryAttempt) * 1000; // Backoff exponentiel
|
|
|
|
addLogEntry(`Nouvelle tentative (${retryAttempt}/${maxRetries}) dans ${waitTime/1000} secondes...`, 'warning');
|
|
|
|
setTimeout(sendRequest, waitTime);
|
|
} else {
|
|
// Pause après plusieurs échecs
|
|
isPaused = true;
|
|
pauseMigration.textContent = 'Reprendre';
|
|
currentProgress.classList.remove('active');
|
|
|
|
// Mettre à jour le statut de la migration
|
|
updateMigrationData('error');
|
|
|
|
addLogEntry(`Échec après ${maxRetries} tentatives. Veuillez reprendre manuellement.`, 'error');
|
|
}
|
|
});
|
|
}
|
|
|
|
// Lancer la requête
|
|
sendRequest();
|
|
}
|
|
|
|
/**
|
|
* Terminer la migration avec succès
|
|
*/
|
|
function finishMigration() {
|
|
isProcessing = false;
|
|
|
|
// Arrêter les sauvegardes automatiques
|
|
saveCheckpoints.stop();
|
|
|
|
const summary = `Migration terminée ! ${successCount} publications ajoutées aux favoris, ${errorCount} échecs, ${skippedCount} déjà présentes, ${warningCount} avertissements.`;
|
|
addLogEntry(summary, 'success');
|
|
|
|
// Si des URLs sont encore dans la file d'attente rate-limit, avertir l'utilisateur
|
|
if (rateLimitQueue.length > 0) {
|
|
addLogEntry(`ATTENTION: ${rateLimitQueue.length} favoris n'ont pas pu être traités à cause de limitations d'API. Vous pouvez redémarrer la migration plus tard pour réessayer.`, 'warning');
|
|
}
|
|
|
|
startMigration.classList.remove('d-none');
|
|
startMigration.textContent = 'Terminer';
|
|
startMigration.addEventListener('click', function() {
|
|
// Nettoyer localStorage et retourner à l'étape 1
|
|
localStorage.removeItem('favmastokey_favorites');
|
|
localStorage.removeItem('favmastokey_migration');
|
|
localStorage.removeItem('favmastokey_ratelimit_queue');
|
|
|
|
// Conserver le cache de fédération et les performances API pour accélérer les futures migrations
|
|
federatedCache.cleanup(1440); // Nettoyer les entrées plus vieilles de 24h
|
|
federatedCache.saveToStorage();
|
|
|
|
step3.classList.add('d-none');
|
|
step1.classList.remove('d-none');
|
|
});
|
|
pauseMigration.classList.add('d-none');
|
|
|
|
// Mettre à jour la progression à 100%
|
|
updateProgress(100);
|
|
|
|
// Mettre à jour le statut de la migration
|
|
updateMigrationData('completed');
|
|
}
|
|
|
|
/**
|
|
* Ajuste le délai en fonction des performances observées
|
|
* AMÉLIORATION 4: Utiliser les statistiques pour ajuster le délai de manière intelligente
|
|
*/
|
|
function adjustDelayBasedOnPerformance() {
|
|
// Identifier les domaines principaux
|
|
const domains = [];
|
|
|
|
// Explorer les URLs récentes pour identifier les domaines
|
|
const startIndex = Math.max(0, currentIndex - 10);
|
|
const recentUrls = favoritesList.slice(startIndex, currentIndex);
|
|
|
|
recentUrls.forEach(url => {
|
|
const domain = extractDomain(url);
|
|
if (domain && !domains.includes(domain)) {
|
|
domains.push(domain);
|
|
}
|
|
});
|
|
|
|
// Trouver le domaine le plus problématique
|
|
let worstDomain = null;
|
|
let maxRateLimits = 0;
|
|
|
|
domains.forEach(domain => {
|
|
const stats = apiPerformance.getDomainStats(domain);
|
|
if (stats && stats.rateLimitCount > maxRateLimits) {
|
|
maxRateLimits = stats.rateLimitCount;
|
|
worstDomain = domain;
|
|
}
|
|
});
|
|
|
|
if (worstDomain) {
|
|
const stats = apiPerformance.getDomainStats(worstDomain);
|
|
const recommendedDelay = stats.recommendedDelay;
|
|
|
|
if (recommendedDelay) {
|
|
if (tortoiseCheckbox && tortoiseCheckbox.checked && tortoiseDelay && tortoiseDelayValue) {
|
|
// Vérifier si le délai recommandé est supérieur au délai actuel
|
|
const currentDelay = parseInt(tortoiseDelay.value);
|
|
if (recommendedDelay > currentDelay) {
|
|
const newDelay = Math.min(300, recommendedDelay);
|
|
tortoiseDelay.value = newDelay;
|
|
tortoiseDelayValue.textContent = newDelay;
|
|
migration.options.tortoiseDelaySeconds = newDelay;
|
|
addLogEntry(`Délai du mode tortue ajusté à ${newDelay}s basé sur les performances avec ${worstDomain}`, 'warning');
|
|
}
|
|
} else if (slowModeCheckbox && slowModeCheckbox.checked && slowModeDelay && delayValue) {
|
|
// Vérifier si le délai recommandé est supérieur au délai actuel
|
|
const currentDelay = parseInt(slowModeDelay.value);
|
|
if (recommendedDelay > currentDelay) {
|
|
const newDelay = Math.min(300, recommendedDelay);
|
|
slowModeDelay.value = newDelay;
|
|
delayValue.textContent = newDelay;
|
|
migration.options.delaySeconds = newDelay;
|
|
addLogEntry(`Délai du mode lent ajusté à ${newDelay}s basé sur les performances avec ${worstDomain}`, 'warning');
|
|
}
|
|
} else {
|
|
// Suggérer d'activer le mode lent avec le délai recommandé
|
|
addLogEntry(`Activez le mode lent avec un délai d'au moins ${recommendedDelay}s pour éviter les limitations avec ${worstDomain}`, 'warning');
|
|
}
|
|
}
|
|
} else {
|
|
// Méthode de secours si aucun domaine problématique identifié
|
|
increaseAdaptiveDelay();
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Extrait le nom de domaine d'une URL
|
|
*/
|
|
function extractDomain(url) {
|
|
try {
|
|
const parsedUrl = new URL(url);
|
|
return parsedUrl.hostname;
|
|
} catch (e) {
|
|
console.error("URL invalide:", url);
|
|
return null;
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Met à jour la barre de progression
|
|
*/
|
|
function updateProgress(forcedValue = null) {
|
|
const progress = forcedValue !== null ? forcedValue : (currentIndex / totalItems) * 100;
|
|
globalProgress.style.width = progress + '%';
|
|
globalProgress.textContent = Math.round(progress) + '%';
|
|
globalProgress.setAttribute('aria-valuenow', progress);
|
|
|
|
if (forcedValue === null) {
|
|
// Mettre à jour la progression actuelle
|
|
const batchProgress = ((currentIndex % 5) / 5) * 100;
|
|
currentProgress.style.width = batchProgress + '%';
|
|
currentProgress.setAttribute('aria-valuenow', batchProgress);
|
|
} else {
|
|
currentProgress.style.width = '100%';
|
|
currentProgress.setAttribute('aria-valuenow', 100);
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Ajoute une entrée dans le journal des opérations
|
|
*/
|
|
function addLogEntry(message, status = 'info') {
|
|
const entry = document.createElement('div');
|
|
entry.className = `log-entry ${status}`;
|
|
|
|
const timestamp = new Date().toLocaleTimeString();
|
|
entry.textContent = `[${timestamp}] ${message}`;
|
|
|
|
operationLog.appendChild(entry);
|
|
|
|
// Scroll vers le bas pour voir la dernière entrée
|
|
const logContainer = document.getElementById('log-container');
|
|
logContainer.scrollTop = logContainer.scrollHeight;
|
|
}
|
|
}); |