[PHP, JS] Téléchargement dynamique comme MEGA

Rédigé par beubeulone - - Aucun commentaire

Pour un projet personnel, je me suis lancé dans la création d'un système de téléchargement dynamique comme peut le proposer le célèbre site MEGA. L'objectif étant de pouvoir lancer plusieurs téléchargements tout en les traitant un à un et ceux de façon plus conviviale.

Fonctionnement

Il s'agit à partir d'un lien pointant vers un fichier, de récupérer ce dernier en mémoire avec javascript, d'afficher sa progression et enfin de lancer le téléchargement classique une fois son chargement complet. Le fichier étant en mémoire, le téléchargement sera local et presque instantané (dépendra de la vitesse d'écriture du disque de l'utilisateur).

Nous avons un fichier html pour les liens, un fichier javascript pour la gestion, un petit peu de css pour la mise en forme et un ou plusieurs fichiers à télécharger à la racine.

index.html

<html>
	<head>
		<meta http-equiv="content-type" content="text/html; charset=utf-8" />
		<link rel="stylesheet" type="text/css" href="site.css" />
	</head>
	<body>
		<div id="down"><div id="downencours" class="listedown" ></div></div>
		<div id="global">
			<a class="download" href="exemple.zip" download="fichier.zip">Lancer le téléchargement du fichier d'exemple #1</a><br />
			<a class="download" href="exemple2.bin" download="fichier.bin">Lancer le téléchargement du fichier d'exemple #2</a><br />
			<a class="download" href="exemple3.txt" download="fichier.txt">Lancer le téléchargement du fichier d'exemple #3</a>
		</div>
	<script type="text/javascript" src="telechargements.js"></script>
	</body>
</html>

Les liens de téléchargement ont la classe download. Ici, si javascript est désactivé, les liens se comporteront normalement.

site.css

body {
	margin: 0;
	font-family: Arial;
	cursor: default;
	}
a {
	text-decoration: none;
	}
/*-------------------------------------------------------*/
#down {													/* div des téléchargements */
	font-size: 85%;
	background: rgb(255,255,255);
	color: rgb(50,50,50);
	}
.listedown, .listewait {									/* élément(s) en téléchargement */
	padding: 5px;
	background: rgba(0,0,0,.1);
	}
.listedown>div, .listewait>div {							/* nom du fichier */
	overflow: hidden;
	text-align: left;
	}
#down a {
	color: rgb(50,50,50);
	}
#down .del {												/* croix de suppression */
	color: darkred;
	}
.listedown>div>span, .listewait>div>span {					/* taille du fichier en Mo */
	float: right;
	text-align: right;
	}
.listedown>div.prog {									/* barre de progression */
	height: 3px;
	background: black;
	margin-bottom: 5px;
	}

La mise-en-forme est minimale et concerne essentiellement l'aspect des div de téléchargement (nom, taille et barre de progression).

telechargements.js

/*==========================================================================================*/
/*	TELECHARGEMENT INTERACTIF AVEC FILE D'ATTENTE											*/
/*==========================================================================================*/
var down_id = 0;																			// identifiant du téléchargement
var down_nb = 0;																			// downs en cours (pour ne permettre qu'un téléchargement à la fois)
var div_telech = document.getElementById("down");											// div contenant la liste les téléchargements
var div_down = document.getElementsByClassName("download");									// liste les liens "download"
window.URL = window.URL || window.webkitURL;
/*------------------------------------------------------------------------------------------*/
setInterval(checkfile, 3000);																// vérifie la file d'attente toutes les x secondes
check_clic_down();
var del = document.getElementsByClassName('del');											// liens permettant de supprimer les élements de la liste
deletefile();
/*==========================================================================================*/
/*	FONCTIONS																				*/
/*==========================================================================================*/
function check_clic_down () {
	for (var i = 0; i < div_down.length; i++) {												// pour tous les liens "download"
		div_down[i].addEventListener('click', addfile, false);								// detecte le clique sur l'un et l'ajoute à la file
		}
	}
/*==========================================================================================*/
function addfile (u) {																		// au clique, ajoute une div de file d'attente
	down_id++;																				// nouvel ID
	var nom_fichier = this.getAttribute("download");										// nom du fichier (de l'attribut download)
	var div_fichier = document.createElement('div');										// nouvelle div (div#waitxx)
	div_fichier.id = "wait"+down_id;														// son ID
	div_fichier.className = "listewait";													// et sa classe
	var div_nom = document.createElement('div');											// nouvelle div pour le nom ou lien et span
	var span_lien = document.createElement('a');											// lien pour le nom
	span_lien.href = this.getAttribute("href");
	span_lien.id = 'await'+down_id;
	span_lien.download = nom_fichier;
	span_lien.innerHTML = nom_fichier;
	var span_wait = document.createElement('span');											// ---------- span wait...
	span_wait.innerHTML = '<span><a href="#" title="téléchargement en attente, cliquer pour annuler" rel="'+div_fichier.id+'" class="del">X</a></span>';
	div_telech.appendChild(div_fichier);													// ajoute la div du down dans la liste
	div_fichier.appendChild(div_nom);														// ajoute la div nom/span
	div_nom.appendChild(span_lien);															// ajoute le nom en lien
	div_nom.appendChild(span_wait);															// ajoute les 3 p'tits points
	u.preventDefault();																		// désactive le lien avec JS
	}
/*==========================================================================================*/
function checkfile () {																		// toutes les x sec check la file
	var div_wait = document.getElementsByClassName("listewait");							// liste les liens "en attente"
	if (down_nb == 0) {																		// s'il n'y a pas de téléchargement en cours
		if (div_wait[0])																	// et s'il y a un élément en attente
			startfile(div_wait[0]);															// lance le téléchargement (via xhr) du prochain fichier
		}
	}
/*==========================================================================================*/
function startfile (u) {																	// lance le téléchargement
	down_nb = 1;																			// indique que le téléchargement est en cours
	var yy = document.getElementById("a"+u.getAttribute('id'));								// a#awaitxx à traiter
	var nom_fichier = yy.getAttribute("download");											// nom du fichier
	var div_fichier = document.getElementById("downencours");
	var div_nom = document.createElement('div');											// div du nom ou lien et span
	div_nom.id = "down"+u.getAttribute('id');
	var div_prog = document.createElement('div');											// div de la barre de progression
	div_prog.id = "progdown"+u.getAttribute('id');											// son ID
	div_prog.className = "prog";															// et sa classe
	div_fichier.appendChild(div_nom);														// ajoute le nom
	div_fichier.appendChild(div_prog);														// et la barre de progression
	u.className = '';																		// vire la class (à defaut de la supprimer) pour enchainer les autres
	u.innerHTML = '';																		// vide la div du fichier en attente
	del = document.getElementsByClassName('del');
	deletefile();
/*------------------------------------------------------------------------------------------*/
	var xhr = new XMLHttpRequest();															// REQUETE
	xhr.open('GET', yy.getAttribute("href"), true);											// lien du fichier cliqué
	xhr.responseType = 'arraybuffer';
/*------------------------------------------------------------------------------------------*/
	xhr.onprogress = function (e) {															// EN COURS
		div_prog.style.width = Math.ceil(100 / e.total * e.loaded)+"%";						// pourcentage
		div_nom.innerHTML = nom_fichier+'<span><b>'+Math.ceil(e.loaded / 1000000)+' Mo</b><a href="#" title="téléchargement en cours, cliquer pour annuler" rel="'+div_nom.id+'" class="del">X</a></span>';	// nom + progression
		del = document.getElementsByClassName('del');
		deletefile();
		};
/*------------------------------------------------------------------------------------------*/
	xhr.onloadend = function(e) {															// TERMINE
		if (this.status == 200) {
			var blob = new Blob([xhr.response]);											// nouveau blob avec requête
			var span_lien = document.createElement('a');									// lien pour le nom
			span_lien.innerHTML = nom_fichier;
			span_lien.href = window.URL.createObjectURL(blob);								// met le blob en lien
			span_lien.download = nom_fichier;
			div_nom.innerHTML = '<span><b>'+Math.ceil(blob.size / 1000000)+' Mo</b><a href="#" title="téléchargement terminé, cliquer pour supprimer" rel="'+div_nom.id+'" hreflang="'+span_lien.href+'" class="del">X</a></span>';
			del = document.getElementsByClassName('del');
			deletefile();
			div_nom.appendChild(span_lien);
			span_lien.click();																// final, lance le téléchargement du blob
			down_nb = 0;																	// plus de down, permet le suivant
			blob = '';
			}
		};
/*------------------------------------------------------------------------------------------*/
	xhr.send();
	}
/*==========================================================================================*/
function deletefile() {																		// supprime un telechargement (en attente, en cours ou terminé)
	for (var i = 0; i < del.length; i++) {
		var wa = del[i];
		wa.onclick = function (u) {															// détecte le clic
			var rel = this.getAttribute("rel");
			if (rel.search(/downwait/))														// rel contient "downwait", vire la div en attente
				document.getElementById("down").removeChild(document.getElementById(rel));
			else {																			// rel contient "downwait", vire la div en cours ou terminée
				if (this.getAttribute("hreflang"))
					window.URL.revokeObjectURL(this.getAttribute("hreflang"));				// supprime l'adresse du blob
				document.getElementById("downencours").removeChild(document.getElementById(rel));
				document.getElementById("downencours").removeChild(document.getElementById('prog'+rel));
				}
			u.preventDefault();																// empêche l'exécution du lien
			}
		}
	}

Le gros du travail : le script va vérifier toutes les 3 secondes s'il y a un fichier à traiter dans la liste d'attente. S'il n'y a pas de téléchargement en cours, il traite le suivant.

L'utilisateur peut à tout moment supprimer un élement. Si le fichier est déjà téléchargé et existe sous forme de blob, il est supprimé pour libérer de la mémoire.

Conclusion

Il ne s'agit pas d'une solution miracle, les petites configurations par exemple auront bien du mal à charger des fichiers de plusieurs giga puisque ces derniers sont stockés dans la mémoire du navigateur.

Dans cet exemple, il revient à l'utilisateur de supprimer les éléments déjà téléchargés pour libérer de la place. Ce qui est inutile si le script est dédié à un seul fichier puisque le blob sera détruit à la fermeture de la page.

Fil RSS des articles de ce mot clé