PureDownloader – Module de téléchargement HTTP pour PureBasic
Module PureBasic de téléchargement HTTP avec gestion des fichiers et de la mémoire, prise en charge des proxys, suivi de progression, vitesse de transfert, estimation du temps restant et annulation.
PureDownloader – Module de téléchargement HTTP pour PureBasic :
PureDownloader est un module développé en PureBasic permettant d'intégrer facilement des fonctionnalités de téléchargement HTTP dans une application.
Il permet de récupérer des fichiers depuis un serveur distant, soit directement sur le disque, soit en mémoire, tout en fournissant les informations nécessaires au suivi du téléchargement.
Fonctionnalités principales :
Téléchargement HTTP vers un fichier ou directement en mémoire.
- Récupération et lecture des en-têtes HTTP.
- Gestion des serveurs proxy avec authentification.
- Suivi de la progression du téléchargement en pourcentage.
- Calcul de la vitesse de transfert en Ko/s.
- Estimation du temps restant.
- Possibilité d'interrompre un téléchargement en cours.
- Vérification des réponses HTTP et gestion des erreurs.
- Suppression des fichiers incomplets en cas d'échec ou d'annulation.
- Protection contre le lancement simultané de plusieurs téléchargements.
Intégration :
Le module expose une structure de paramètres permettant de définir l'URL, le nom du fichier de destination ou le mode mémoire, ainsi que de récupérer l'état et les informations de progression.
Il peut notamment être utilisé dans un thread afin de ne pas bloquer l'interface graphique de l'application.
Le code source
; +======================================================+
; | |
; +------------------------------------------------------+
; | COPYRIGHT(C)2017-2026, ALL RIGHT RESERVED KOAKDESIGN |
; +--------------+---------------------------------------+
; | Program type | PUREBASIC 6.40 (MODULE) |
; +--------------+---------------------------------------+
; | VER & REV | 0.0.2 |
; +--------------+---------------------------------------+
; | Program name | Downloader |
; +==============+=======================================+
; +======================================================+
; | Original Version: 0.0.1 |
; +--------------+---------------------------------------+
; | Created by | GallyHomeCorp |
; | Graphix by | |
; +--------------+---------------------------------------+
; | Comments: | |
; +--------------+ |
; | 0.0.2 : portage PureBasic 6.40 |
; | - requête d'en-têtes en HeadersOnly + FinishHTTP |
; | - FinishHTTP systématique (fuites corrigées) |
; | - verrou "en cours" partagé et toujours libéré |
; | - logique proxy corrigée |
; | - code HTTP (404, 405...) vérifié avant download |
; | - progression bornée, vitesse en Ko/s stable |
; | - fichier partiel supprimé si échec/annulation |
; +======================================================+
; +==========================+===========================+
; | Operating System | Gestion Compilation |
; +----------------------+---+-----------------------+---+
; | Window | X | Gestion Thread | X |
; | Linux | X | Gestion OnError | |
; | MacOS | X | | - |
; +----------------------+---+-----------------------+---+
; | x32 | X | Droit Administrateur | |
; | X64 | X | Droit particulier | |
; +======================+===+=======================+===+
; ****************************************************************************
CompilerIf #PB_Compiler_EnableExplicit
EnableExplicit
CompilerEndIf
; ****************************************************************************
;-DeclareModule
DeclareModule Downloader
EnableExplicit
Structure _STRUCTURE_DOWNLOADER
url.s
filename.s
memory.b
downfile.s
result.l
UpdateExit.b
UpdateTime.l ; secondes restantes estimées
UpdateKbits.l ; vitesse en Ko/s
UpdateStatus.l
UpdateProgress.l ; 0..100
UpdateSize.q ; taille totale (0 si inconnue)
UpdateReceived.q ; octets reçus
EndStructure
Structure _STRUCTURE_PROXY
server.s
port.l
username.s
password.s
used.b
EndStructure
Global _PROXY._STRUCTURE_PROXY
CompilerSelect #PB_Compiler_Processor
CompilerCase #PB_Processor_x86
#PROCESSOR = "x86"
CompilerCase #PB_Processor_x64
#PROCESSOR = "x64"
CompilerDefault
#PROCESSOR = "arm"
CompilerEndSelect
CompilerSelect #PB_Compiler_OS
CompilerCase #PB_OS_Windows
#HTTP_USERAGENT = "PureDownloader/1.0.2.0 (Windows;" + #PROCESSOR + ")"
CompilerCase #PB_OS_Linux
#HTTP_USERAGENT = "PureDownloader/1.0.2.0 (Linux;" + #PROCESSOR + ")"
CompilerCase #PB_OS_MacOS
#HTTP_USERAGENT = "PureDownloader/1.0.2.0 (MacOS;" + #PROCESSOR + ")"
CompilerDefault
#HTTP_USERAGENT = "PureDownloader/1.0.2.0 (Unknown;" + #PROCESSOR + ")"
CompilerEndSelect
#HTTP_KBITS_VALUE = 100
#HTTP_OK = 200
#HTTP_ERROR_CONNECT = 2
#HTTP_ERROR_MEMORY = 3
#HTTP_ERROR_FILE = 4
#HTTP_ERROR_ABORT = 5
#HTTP_ERROR_USED = 6
#HTTP_ERROR_NOT_COMMAND = 7
#HTTP_ERROR_NOFILE = 404
Declare.s _DOWNLOADER_ERROR (error.l, notview.b = #False)
Declare.s _DOWNLOADER_CONVERTSIZE(filesize.d, decimal.l = 1)
Declare _DOWNLOADER_PROXY (server.s, port.l, username.s, password.s)
Declare _DOWNLOADER_PROXY_USED (value.b)
Declare.s _DOWNLOADER_HEADERREAD (*Parameters._STRUCTURE_DOWNLOADER, content.s = "")
Declare.l _DOWNLOADER_DOWNLOAD (*Parameters._STRUCTURE_DOWNLOADER)
EndDeclareModule
; ****************************************************************************
;-Module
Module Downloader
EnableExplicit
Global _DOWNLOADER_INUSE.b = #False
Global _DOWNLOADER_PROXYSET.b = #False
; Message d'erreur (affiché ou retourné)
Procedure.s _DOWNLOADER_ERROR(error.l, notview.b = #False)
Protected.s result
Select error
Case #HTTP_OK
result = #Null$
Case #HTTP_ERROR_CONNECT
result = "Unable to connect to the server."
Case #HTTP_ERROR_MEMORY
result = "Can not allocate memory for the response buffer."
Case #HTTP_ERROR_FILE
result = "Download failed or unable to save the file locally."
Case #HTTP_ERROR_ABORT
result = "Download aborted."
Case #HTTP_ERROR_USED
result = "A download is already in progress."
Case #HTTP_ERROR_NOFILE
result = "File not found (HTTP 404)."
Case #HTTP_ERROR_NOT_COMMAND
result = "Method not allowed on this server."
Default
result = "HTTP protocol error (Error: " + Str(error) + ")."
EndSelect
If result <> #Null$
If notview = #False
MessageRequester("Downloader Error", result, #PB_MessageRequester_Ok | #PB_MessageRequester_Error)
EndIf
EndIf
ProcedureReturn result
EndProcedure
; Conversion d'une taille en o/Ko/Mo...
Procedure.s _DOWNLOADER_CONVERTSIZE(filesize.d, decimal.l = 1)
Protected i.l = 1
Protected j.s = "o,Ko,Mo,Go,To,Po,Eo,Zo,Yo"
Protected k.l = CountString(j, ",") + 1
While filesize >= 1024 And i < k
filesize / 1024
i + 1
Wend
Protected sres.s = StrD(filesize, decimal)
Protected sent.s = StringField(sres, 1, ".")
Protected sdec.s = StringField(sres, 2, ".")
If i = 1
ProcedureReturn sent + " o"
ElseIf Val(sdec) = 0
ProcedureReturn sent + " " + StringField(j, i, ",")
Else
ProcedureReturn sres + " " + StringField(j, i, ",")
EndIf
EndProcedure
; Lecture d'un champ d'en-tête (dernière occurrence = réponse finale après redirections)
Procedure.s _DOWNLOADER_HEADER(HeaderContent.s, Element.s)
Protected.i i, n
Protected.s line, result
If Element = #Null$
ProcedureReturn #Null$
EndIf
If Right(Element, 1) <> ":"
Element + ":"
EndIf
Element = LCase(Element)
HeaderContent = ReplaceString(HeaderContent, #CR$, "")
n = CountString(HeaderContent, #LF$) + 1
For i = 1 To n
line = StringField(HeaderContent, i, #LF$)
If LCase(Left(line, Len(Element))) = Element
result = Trim(Mid(line, Len(Element) + 1))
EndIf
Next
ProcedureReturn result
EndProcedure
; Définition du proxy (active automatiquement son usage)
Procedure _DOWNLOADER_PROXY(server.s, port.l, username.s, password.s)
_PROXY\server = server
_PROXY\port = port
_PROXY\username = username
_PROXY\password = password
_PROXY\used = Bool(server <> #Null$)
EndProcedure
; Active / désactive l'usage du proxy
Procedure _DOWNLOADER_PROXY_USED(value.b)
_PROXY\used = value
EndProcedure
; Applique (ou retire) le proxy avant chaque requête
Procedure _DOWNLOADER_APPLYPROXY()
Protected.s sproxy
If _PROXY\used And _PROXY\server <> #Null$
sproxy = _PROXY\server
If _PROXY\port > 0
sproxy + ":" + Str(_PROXY\port)
EndIf
HTTPProxy(sproxy, _PROXY\username, _PROXY\password)
_DOWNLOADER_PROXYSET = #True
ElseIf _DOWNLOADER_PROXYSET
HTTPProxy("")
_DOWNLOADER_PROXYSET = #False
EndIf
EndProcedure
; Requête des en-têtes seuls. Retourne le code HTTP (0 = pas de connexion)
Procedure.i _DOWNLOADER_GETHEADERS(url.s, *Headers.String)
Protected.i request, istatus
Protected NewMap Headers.s()
Headers("User-Agent") = #HTTP_USERAGENT
*Headers\s = #Null$
request = HTTPRequest(#PB_HTTP_Get, url, "", #PB_HTTP_HeadersOnly, Headers())
If request
istatus = Val(HTTPInfo(request, #PB_HTTP_StatusCode))
*Headers\s = HTTPInfo(request, #PB_HTTP_Headers)
FinishHTTP(request)
EndIf
ProcedureReturn istatus
EndProcedure
; Lecture d'un en-tête distant (content vide = tous les en-têtes)
Procedure.s _DOWNLOADER_HEADERREAD(*Parameters._STRUCTURE_DOWNLOADER, content.s = "")
Protected shead.String
If *Parameters = 0 Or *Parameters\url = #Null$
ProcedureReturn #Null$
EndIf
_DOWNLOADER_APPLYPROXY()
If _DOWNLOADER_GETHEADERS(URLEncoder(*Parameters\url), @shead) = 0
ProcedureReturn #Null$
EndIf
If content = #Null$
ProcedureReturn shead\s
EndIf
ProcedureReturn _DOWNLOADER_HEADER(shead\s, content)
EndProcedure
; Téléchargement (fichier ou mémoire). Utilisable en thread.
Procedure.l _DOWNLOADER_DOWNLOAD(*Parameters._STRUCTURE_DOWNLOADER)
Protected.i idown, ivalue, iquit, istatus, httpcode
Protected.q file_size, received, itime, ielapsed
Protected.i ispeed
Protected.s surl
Protected shead.String
Protected *Buffer
If *Parameters = 0
ProcedureReturn #HTTP_ERROR_MEMORY
EndIf
If _DOWNLOADER_INUSE
*Parameters\UpdateStatus = #HTTP_ERROR_USED
ProcedureReturn #HTTP_ERROR_USED
EndIf
_DOWNLOADER_INUSE = #True
*Parameters\UpdateExit = #False
*Parameters\UpdateProgress = 0
*Parameters\UpdateKbits = 0
*Parameters\UpdateStatus = 0
*Parameters\UpdateTime = 0
*Parameters\UpdateSize = 0
*Parameters\UpdateReceived = 0
*Parameters\downfile = #Null$
_DOWNLOADER_APPLYPROXY()
surl = URLEncoder(*Parameters\url)
; --- 1) Vérification + taille via les en-têtes seuls
httpcode = _DOWNLOADER_GETHEADERS(surl, @shead)
Select httpcode
Case 0
istatus = #HTTP_ERROR_CONNECT
Case 404
istatus = #HTTP_ERROR_NOFILE
Case 405
istatus = #HTTP_ERROR_NOT_COMMAND
Default
If httpcode >= 400
istatus = httpcode
Else
file_size = Val(_DOWNLOADER_HEADER(shead\s, "Content-Length"))
If file_size < 0
file_size = 0
EndIf
*Parameters\UpdateSize = file_size
EndIf
EndSelect
; --- 2) Téléchargement
If istatus = 0
If *Parameters\memory = #False
idown = ReceiveHTTPFile(surl, *Parameters\filename, #PB_HTTP_Asynchronous, #HTTP_USERAGENT)
Else
idown = ReceiveHTTPMemory(surl, #PB_HTTP_Asynchronous, #HTTP_USERAGENT)
EndIf
If idown = 0
istatus = #HTTP_ERROR_CONNECT
Else
itime = ElapsedMilliseconds()
Repeat
If *Parameters\UpdateExit = #True
AbortHTTP(idown)
EndIf
ivalue = HTTPProgress(idown)
Select ivalue
Case #PB_HTTP_Success
istatus = #HTTP_OK
If *Parameters\memory = #True
*Buffer = FinishHTTP(idown)
idown = 0
If *Buffer
*Parameters\downfile = PeekS(*Buffer, MemorySize(*Buffer), #PB_UTF8 | #PB_ByteLength)
*Parameters\UpdateReceived = MemorySize(*Buffer)
FreeMemory(*Buffer)
Else
istatus = #HTTP_ERROR_MEMORY
EndIf
EndIf
If istatus = #HTTP_OK
*Parameters\UpdateProgress = 100
*Parameters\UpdateTime = 0
EndIf
iquit = #True
Case #PB_HTTP_Failed
istatus = #HTTP_ERROR_FILE
iquit = #True
Case #PB_HTTP_Aborted
istatus = #HTTP_ERROR_ABORT
iquit = #True
Default
If ivalue > 0
received = ivalue
*Parameters\UpdateReceived = received
ielapsed = ElapsedMilliseconds() - itime
If ielapsed > 0
ispeed = (received * 1000 / ielapsed) / 1024 ; Ko/s moyen
*Parameters\UpdateKbits = ispeed
EndIf
If file_size > 0
ivalue = (received * 100) / file_size
If ivalue > 99 : ivalue = 99 : EndIf
If ivalue < 0 : ivalue = 0 : EndIf
*Parameters\UpdateProgress = ivalue
If ispeed > 0 And file_size > received
*Parameters\UpdateTime = ((file_size - received) / 1024) / ispeed
EndIf
EndIf
EndIf
EndSelect
If iquit = #False
Delay(50)
EndIf
Until iquit = #True
If idown
FinishHTTP(idown)
EndIf
EndIf
EndIf
; Suppression d'un fichier partiel
If istatus <> #HTTP_OK And *Parameters\memory = #False And *Parameters\filename <> #Null$
If FileSize(*Parameters\filename) >= 0 And httpcode < 400 And httpcode <> 0
DeleteFile(*Parameters\filename)
EndIf
EndIf
*Parameters\UpdateStatus = istatus
_DOWNLOADER_INUSE = #False
ProcedureReturn istatus
EndProcedure
EndModule
XIncludeFile "module_downloader.pbi"
CompilerIf #PB_Compiler_Thread = 0
CompilerError "Activez 'Créer un exécutable threadsafe' dans les options du compilateur."
CompilerEndIf
Define.i event, DefTread, isavbar, iresult
Define.q itime
Define.s UpdateTimer, UpdateTime, UpdateSpeed, stext
Define *Parameters.Downloader::_STRUCTURE_DOWNLOADER = AllocateStructure(Downloader::_STRUCTURE_DOWNLOADER)
; Wrapper local : évite la syntaxe @Module::Procedure() dans CreateThread
Procedure DownloadThread(*Parameters.Downloader::_STRUCTURE_DOWNLOADER)
Downloader::_DOWNLOADER_DOWNLOAD(*Parameters)
EndProcedure
Procedure StartDownload(*Parameters.Downloader::_STRUCTURE_DOWNLOADER, url.s, memory.b)
*Parameters\url = url
*Parameters\memory = memory
If memory = #False
If GetURLPart(url, #PB_URL_Path) = #Null$ Or GetFilePart(GetURLPart(url, #PB_URL_Path)) = #Null$
*Parameters\filename = "test.txt"
Else
*Parameters\filename = GetFilePart(GetURLPart(url, #PB_URL_Path))
EndIf
EndIf
ProcedureReturn CreateThread(@DownloadThread(), *Parameters)
EndProcedure
If OpenWindow(0, 0, 0, 600, 215, "Download Exemple...", #PB_Window_SystemMenu | #PB_Window_ScreenCentered)
TextGadget (1, 10, 10, 580, 20, "--------------------", #PB_Text_Center)
ProgressBarGadget (2, 10, 35, 580, 30, 0, 100)
ButtonGadget (3, 10, 70, 580, 30, "Lancer le téléchargement (disque)")
ButtonGadget (4, 10, 105, 580, 30, "Lancer le téléchargement (mémoire)")
ButtonGadget (6, 10, 140, 580, 30, "Annuler")
StringGadget (5, 10, 182, 580, 22, "https://proof.ovh.net/files/1Gb.dat")
DisableGadget(6, #True)
Repeat
Event = WaitWindowEvent(100)
Select Event
Case #PB_Event_Gadget
Select EventGadget()
Case 3, 4
If Not IsThread(DefTread)
isavbar = 0
SetGadgetState(2, 0)
itime = ElapsedMilliseconds()
DefTread = StartDownload(*Parameters, GetGadgetText(5), Bool(EventGadget() = 4))
If DefTread
DisableGadget(3, #True) : DisableGadget(4, #True) : DisableGadget(6, #False)
EndIf
EndIf
Case 6
*Parameters\UpdateExit = #True
EndSelect
EndSelect
If DefTread And IsThread(DefTread)
If *Parameters\UpdateKbits < 1
UpdateSpeed = "???"
UpdateTime = "??:??:??"
Else
UpdateSpeed = Downloader::_DOWNLOADER_CONVERTSIZE(*Parameters\UpdateKbits * 1024.0) + "/s"
If *Parameters\UpdateSize > 0
UpdateTime = FormatDate("%hh:%ii:%ss", *Parameters\UpdateTime)
Else
UpdateTime = "taille inconnue"
EndIf
EndIf
UpdateTimer = FormatDate("%hh:%ii:%ss", (ElapsedMilliseconds() - itime) / 1000)
stext = "Temps écoulé: " + UpdateTimer + " (" + UpdateSpeed + ") - Reste " + UpdateTime
If GetGadgetText(1) <> stext
SetGadgetText(1, stext)
EndIf
If *Parameters\UpdateProgress > isavbar
isavbar = *Parameters\UpdateProgress
SetGadgetState(2, isavbar)
EndIf
ElseIf DefTread
; Fin du thread : traitement unique
DefTread = 0
DisableGadget(3, #False) : DisableGadget(4, #False) : DisableGadget(6, #True)
iresult = *Parameters\UpdateStatus
If iresult = Downloader::#HTTP_OK
SetGadgetState(2, 100)
SetGadgetText(1, "Terminé : " + Downloader::_DOWNLOADER_CONVERTSIZE(*Parameters\UpdateReceived) + " reçus")
If *Parameters\memory And *Parameters\downfile <> #Null$
Debug Left(*Parameters\downfile, 2000)
*Parameters\downfile = #Null$
EndIf
Else
SetGadgetText(1, Downloader::_DOWNLOADER_ERROR(iresult, #True))
Downloader::_DOWNLOADER_ERROR(iresult)
EndIf
EndIf
Until Event = #PB_Event_CloseWindow
If IsThread(DefTread)
*Parameters\UpdateExit = #True
WaitThread(DefTread, 5000)
EndIf
EndIf
FreeStructure(*Parameters)
