Connexion Inscription
Accueil › Développement › Sources PureBasic › PureDownloader – Module de téléchargement HTTP pour PureBasic
Avancé PureBasic 6.x

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

module_downloader.pbiPureBasic · 441 lignes
; +======================================================+
; |                                                      |
; +------------------------------------------------------+
; | 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
exemple.pbPureBasic · 106 lignes
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)
‹ Retour à Sources PureBasic