MULTI-SEGMENT FAST FILE DOWNLOADER .."Only windows

Share your advanced PureBasic knowledge/code with the community.
User avatar
CELTIC88
Enthusiast
Enthusiast
Posts: 154
Joined: Thu Sep 17, 2015 3:39 pm

MULTI-SEGMENT FAST FILE DOWNLOADER .."Only windows

Post by CELTIC88 »

Segmented file-transfer (also known as multisource file-transfer or swarming file-transfer) is a software method that intended to improve file download speed. It works by simultaneously downloading different portions of the computer file sourced from either multiple servers or a from a single server, recombining the parts into the single file requested. The majority of Download Manager applications work in this way.
https://en.wikipedia.org/wiki/Segmented_file_transfer

Code: Select all

EnableExplicit

IncludeFile "WINHTTP_API.PBI"

Procedure.s WinHttpQueryHeadersEx(hRequest,QueryOpt.l,*QUERY_Buff,BufferSize.l = 1024)
  If WinHttpQueryHeaders(hRequest,QueryOpt,"",*QUERY_Buff,@BufferSize,#WINHTTP_NO_HEADER_INDEX)
    Protected String.s = PeekS(*QUERY_Buff,BufferSize)
  EndIf
  ProcedureReturn String
EndProcedure

Procedure.i WinHttpSendRequestEx(hConnect.i, HttpVerb.s, ObjectName.s, Scheme.l, Additionalheaders.s)
  Protected iFlagScheme =#WINHTTP_FLAG_REFRESH:If Scheme = #INTERNET_SCHEME_HTTPS_WINHTTP:iFlagScheme | #WINHTTP_FLAG_SECURE:EndIf
  Protected hRequest=WinHttpOpenRequest(hConnect,HttpVerb,ObjectName,"HTTP/1.1",
                                        #WINHTTP_NO_REFERER,#WINHTTP_DEFAULT_ACCEPT_TYPES,iFlagScheme) 
  If hRequest
    WinHttpSendRequest(hRequest,Additionalheaders,-1,#WINHTTP_NO_REQUEST_DATA,0,0,0)
    WinHttpReceiveResponse(hRequest,0)
  EndIf
  ProcedureReturn hRequest
EndProcedure

Procedure.l WinHttpReadDataEx(hRequest,*DownloadMemory,MemorySize.l)
  If MemorySize < 1:ProcedureReturn 0:EndIf
  Protected ReciveSize.l
  If WinHttpQueryDataAvailable(hRequest,@ReciveSize) =0 
    If GetLastError_():ReciveSize = -1:EndIf
  Else
    ReciveSize=0
    If 0= WinHttpReadData(hRequest,*DownloadMemory,MemorySize,@ReciveSize):ReciveSize = -1:EndIf
  EndIf
  ProcedureReturn ReciveSize
EndProcedure

Structure sMultiDownloadSegment
  hRequest.i
  Range1.q
  Range2.q
  DownloadSize.q
  CurrentMemoryPos.q
  *DownloadMemory
EndStructure

Macro Check(Ver,Error)
  If Ver = 0:ErrorPos = Error:Goto Clean:EndIf
EndMacro

#DEFAULT_USERAGENT = "Mozilla/5.0 (WinHTTP/5.1) like Gecko"
#DownloadMaxRate = 1024
#DownloadMaxSegment = 4

Procedure.q MultiDownloadSegment(DownloadLink.s,                    ; Http File Link
                                 SaveName.s,                        ; File to wrtie the download data to
                                 UserAgent.s = #DEFAULT_USERAGENT , ; Set User Agent
                                 ProxyList.s = "",                  ; Use Proxy eg : "http(s)://IP:PORT"
                                 ProxyLogin.s = "",                 ; Set proxy login
                                 ProxyPassword.s = "")              ; Set proxy password
  
  Protected ErrorPos.q = 0
  Protected uc.URL_COMPONENTS\dwStructSize=SizeOf(URL_COMPONENTS)
  uc\dwHostNameLength = -1:uc\dwUrlPathLength = -1:uc\dwExtraInfoLength = -1
  Check(WinHttpCrackUrl(DownloadLink,Len(DownloadLink),0,uc),-1)
  Protected HostName.s = PeekS(uc\lpszHostName,uc\dwHostNameLength)
  Protected File.s = PeekS(uc\lpszUrlPath,uc\dwUrlPathLength)
  Protected Parameters.s = PeekS(uc\lpszExtraInfo,uc\dwExtraInfoLength)
  
  Protected hSession = WinHttpOpen(UserAgent, #WINHTTP_ACCESS_TYPE_DEFAULT_PROXY, 
                                   #WINHTTP_NO_PROXY_NAME,#WINHTTP_NO_PROXY_BYPASS, 0)
  Check(hSession,-2)
    
  If ProxyList <> "" ;Set Proxy Setting
    Protected proxy.WINHTTP_PROXY_INFO\dwAccessType = #WINHTTP_ACCESS_TYPE_NAMED_PROXY
    proxy\lpszProxy = @ProxyList
    Check(WinHttpSetOption(hSession, #WINHTTP_OPTION_PROXY, @proxy, SizeOf(WINHTTP_PROXY_INFO)),-5)
    Check(WinHttpSetOption(hSession, #WINHTTP_OPTION_PROXY_USERNAME, @ProxyLogin, Len(ProxyLogin)),-5)
    Check(WinHttpSetOption(hSession, #WINHTTP_OPTION_PROXY_PASSWORD, @ProxyPassword, Len(ProxyPassword)),-5)
  EndIf
  
  Protected hConnect = WinHttpConnect(hSession,HostName,uc\nPort,0)
  Check(hConnect,-3)
  Protected hRequest= WinHttpSendRequestEx(hConnect, "HEAD", File+Parameters, uc\nScheme, ;Get File Header Info
                                           #WINHTTP_NO_ADDITIONAL_HEADERS)
  Check(hRequest,-4)
  
  Protected *QUERY_Buff = AllocateMemory(1024)  
  Protected iQuery.s = WinHttpQueryHeadersEx(hRequest,#WINHTTP_QUERY_STATUS_CODE,*QUERY_Buff)  ;get Request Status code
  Check(Bool(iQuery = "200"),-6)
  
  Protected FileSize.q = Val(WinHttpQueryHeadersEx(hRequest,#WINHTTP_QUERY_CONTENT_LENGTH,*QUERY_Buff));Get file size
  Check(FileSize,-7)
  
  ErrorPos = FileSize
  Debug "File Size : " + FileSize
  Debug "Mime type : " + WinHttpQueryHeadersEx(hRequest,#WINHTTP_QUERY_CONTENT_TYPE,*QUERY_Buff)
  Debug "DISPOSITION : " + WinHttpQueryHeadersEx(hRequest,#WINHTTP_QUERY_CONTENT_DISPOSITION,*QUERY_Buff) ; *File Name
  
  Protected DownloadMaxPart.l = #DownloadMaxSegment
  Protected p,RangeCal.q = 0
  Dim aMultiDownloadSegment.sMultiDownloadSegment(4)
  Protected *DownloadMemory = AllocateMemory(FileSize)
  
  For p = 1 To DownloadMaxPart
    With aMultiDownloadSegment(p)
      \DownloadSize = FileSize / DownloadMaxPart
      If p = DownloadMaxPart:\DownloadSize + Mod(FileSize , DownloadMaxPart):EndIf; if last chunk set end to last byte
      \DownloadMemory = *DownloadMemory + RangeCal
      \Range1 = RangeCal
      RangeCal + \DownloadSize
      \Range2 = RangeCal-1
      \hRequest = WinHttpSendRequestEx(hConnect, "GET", File+Parameters, uc\nScheme, 
                                       "Range: bytes=" + Str(\Range1) + "-" + Str(\Range2)) ;Request Chunk 
      Debug WinHttpQueryHeadersEx(\hRequest,#WINHTTP_QUERY_STATUS_CODE,*QUERY_Buff) ;206 Partial Content (RFC 7233)
      Check(\hRequest,-7)
    EndWith
  Next
  
  Protected DownSize.l,AllDownSize.q,DownloadRate.q
  Repeat
    For p = 1 To DownloadMaxPart
      With aMultiDownloadSegment(p)
        If \CurrentMemoryPos < \DownloadSize
          DownloadRate = \DownloadSize - \CurrentMemoryPos
          If DownloadRate > #DownloadMaxRate:DownloadRate = #DownloadMaxRate:EndIf
          DownSize = WinHttpReadDataEx(\hRequest, \DownloadMemory + \CurrentMemoryPos, DownloadRate);receiving data
        EndIf
        If DownSize > 0
          \CurrentMemoryPos + DownSize
          AllDownSize + DownSize
        ElseIf DownSize = -1 ; download error
          Check(0,-8)
        EndIf
      Next
    EndWith
  Until AllDownSize = FileSize
  
  Debug AllDownSize
  ; ShowMemoryViewer(*DownloadMemory,FileSize)
  
  CreateFile(0,SaveName)
  WriteData(0,*DownloadMemory,FileSize)
  CloseFile(0)
  
  Clean:
  If *QUERY_Buff:FreeMemory(*QUERY_Buff):EndIf
  If *DownloadMemory:FreeMemory(*DownloadMemory):EndIf
  For p = 1 To DownloadMaxPart
    With aMultiDownloadSegment(p)
      If \hRequest:WinHttpCloseHandle(\hRequest):EndIf
    EndWith
  Next
  If hRequest:WinHttpCloseHandle(hRequest):EndIf
  If hConnect:WinHttpCloseHandle(hConnect):EndIf
  If hSession:WinHttpCloseHandle(hSession):EndIf
  ProcedureReturn ErrorPos
EndProcedure

Debug MultiDownloadSegment("http://www.esa.int/var/esa/storage/images/esa_multimedia/images/2018/02/terrestrial_planet_magnetospheres/17366155-1-eng-GB/Terrestrial_planet_magnetospheres.jpg","Terrestrial_planet_magnetospheres.jpg")
Last edited by CELTIC88 on Sun Feb 11, 2018 11:07 pm, edited 1 time in total.
interested in Cybersecurity..
User avatar
CELTIC88
Enthusiast
Enthusiast
Posts: 154
Joined: Thu Sep 17, 2015 3:39 pm

Re: MULTI-SEGMENT FAST FILE DOWNLOADER .."Only windows

Post by CELTIC88 »

WINHTTP_API.PBI

Code: Select all

Structure WINHTTP_PROXY_INFO Align #PB_Structure_AlignC
  dwAccessType.l;
  *lpszProxy    ;
  *lpszProxyBypass;
EndStructure

#INTERNET_SCHEME_HTTPS_WINHTTP = 2
#WINHTTP_ACCESS_TYPE_DEFAULT_PROXY = 0
#WINHTTP_NO_PROXY_NAME = ""
#WINHTTP_NO_PROXY_BYPASS = ""
#WINHTTP_ACCESS_TYPE_NAMED_PROXY = 3
#WINHTTP_OPTION_PROXY = 38
#WINHTTP_OPTION_CONTEXT_VALUE = 45
#WINHTTP_NO_REFERER = #Null$
#WINHTTP_DEFAULT_ACCEPT_TYPES = #Null
#WINHTTP_FLAG_BYPASS_PROXY_CACHE = $00000100;// add "pragma: no-cache" request header
#WINHTTP_FLAG_REFRESH = #WINHTTP_FLAG_BYPASS_PROXY_CACHE
#WINHTTP_FLAG_SECURE = $00800000
#WINHTTP_NO_ADDITIONAL_HEADERS = #Null$
#WINHTTP_NO_REQUEST_DATA = #Null
#WINHTTP_QUERY_STATUS_CODE = 19
#WINHTTP_NO_HEADER_INDEX = 0
#WINHTTP_QUERY_RAW_HEADERS = 21
#WINHTTP_OPTION_DISABLE_FEATURE = 63
#WINHTTP_DISABLE_KEEP_ALIVE = $00000008
#WINHTTP_DISABLE_REDIRECTS = $00000002
#WINHTTP_QUERY_CONTENT_TYPE = 1
#WINHTTP_QUERY_LAST_MODIFIED = 11
#WINHTTP_QUERY_RAW_HEADERS_CRLF = 22;// special: all headers
#WINHTTP_QUERY_CONTENT_DISPOSITION = 47
#WINHTTP_QUERY_CONTENT_LENGTH = 5
#WINHTTP_QUERY_STATUS_TEXT = 20
#WINHTTP_QUERY_LOCATION = 33
#WINHTTP_QUERY_CONTENT_RANGE = 53
#WINHTTP_QUERY_ETAG = 54
#HTTP_STATUS_DENIED = 401
#HTTP_STATUS_PROXY_AUTH_REQ = 407
#WINHTTP_AUTH_SCHEME_BASIC = $00000001
#WINHTTP_AUTH_SCHEME_NTLM = $00000002
#WINHTTP_AUTH_SCHEME_PASSPORT = $00000004
#WINHTTP_AUTH_SCHEME_DIGEST = $00000008
#WINHTTP_AUTH_SCHEME_NEGOTIATE = $00000010
#WINHTTP_AUTH_TARGET_SERVER = $00000000; // WinHttp supported Authentication Targets
#WINHTTP_AUTH_TARGET_PROXY = $00000001
#WINHTTP_OPTION_PROXY_USERNAME = $1002
#WINHTTP_OPTION_PROXY_PASSWORD = $1003
#WINHTTP_CALLBACK_STATUS_DATA_AVAILABLE = $00040000
#WINHTTP_FLAG_ASYNC              = $10000000

#WINHTTP_CALLBACK_STATUS_RESOLVING_NAME = $00000001
#WINHTTP_CALLBACK_STATUS_NAME_RESOLVED = $00000002
#WINHTTP_CALLBACK_STATUS_CONNECTING_TO_SERVER = $00000004
#WINHTTP_CALLBACK_STATUS_CONNECTED_TO_SERVER = $00000008
#WINHTTP_CALLBACK_STATUS_SENDING_REQUEST = $00000010
#WINHTTP_CALLBACK_STATUS_REQUEST_SENT = $00000020
#WINHTTP_CALLBACK_STATUS_RECEIVING_RESPONSE = $00000040
#WINHTTP_CALLBACK_STATUS_RESPONSE_RECEIVED = $00000080
#WINHTTP_CALLBACK_STATUS_CLOSING_CONNECTION = $00000100
#WINHTTP_CALLBACK_STATUS_CONNECTION_CLOSED = $00000200
#WINHTTP_CALLBACK_STATUS_HANDLE_CREATED = $00000400
#WINHTTP_CALLBACK_STATUS_HANDLE_CLOSING = $00000800
#WINHTTP_CALLBACK_STATUS_DETECTING_PROXY = $00001000
#WINHTTP_CALLBACK_STATUS_REDIRECT = $00004000
#WINHTTP_CALLBACK_STATUS_INTERMEDIATE_RESPONSE = $00008000
#WINHTTP_CALLBACK_STATUS_SECURE_FAILURE = $00010000
#WINHTTP_CALLBACK_STATUS_HEADERS_AVAILABLE = $00020000
#WINHTTP_CALLBACK_STATUS_DATA_AVAILABLE = $00040000
#WINHTTP_CALLBACK_STATUS_READ_COMPLETE = $00080000
#WINHTTP_CALLBACK_STATUS_WRITE_COMPLETE = $00100000
#WINHTTP_CALLBACK_STATUS_REQUEST_ERROR = $00200000
#WINHTTP_CALLBACK_STATUS_SENDREQUEST_COMPLETE = $00400000
#WINHTTP_CALLBACK_FLAG_RESOLVE_NAME = $00000003
#WINHTTP_CALLBACK_FLAG_CONNECT_TO_SERVER = $0000000C
#WINHTTP_CALLBACK_FLAG_SEND_REQUEST = $00000030
#WINHTTP_CALLBACK_FLAG_RECEIVE_RESPONSE = $000000C0
#WINHTTP_CALLBACK_FLAG_CLOSE_CONNECTION = $00000300
#WINHTTP_CALLBACK_FLAG_HANDLES = $00000C00
#WINHTTP_CALLBACK_FLAG_DETECTING_PROXY = #WINHTTP_CALLBACK_STATUS_DETECTING_PROXY
#WINHTTP_CALLBACK_FLAG_REDIRECT = #WINHTTP_CALLBACK_STATUS_REDIRECT
#WINHTTP_CALLBACK_FLAG_INTERMEDIATE_RESPONSE = #WINHTTP_CALLBACK_STATUS_INTERMEDIATE_RESPONSE
#WINHTTP_CALLBACK_FLAG_SECURE_FAILURE = #WINHTTP_CALLBACK_STATUS_SECURE_FAILURE
#WINHTTP_CALLBACK_FLAG_SENDREQUEST_COMPLETE = #WINHTTP_CALLBACK_STATUS_SENDREQUEST_COMPLETE
#WINHTTP_CALLBACK_FLAG_HEADERS_AVAILABLE = #WINHTTP_CALLBACK_STATUS_HEADERS_AVAILABLE
#WINHTTP_CALLBACK_FLAG_DATA_AVAILABLE = #WINHTTP_CALLBACK_STATUS_DATA_AVAILABLE
#WINHTTP_CALLBACK_FLAG_READ_COMPLETE = #WINHTTP_CALLBACK_STATUS_READ_COMPLETE
#WINHTTP_CALLBACK_FLAG_WRITE_COMPLETE = #WINHTTP_CALLBACK_STATUS_WRITE_COMPLETE
#WINHTTP_CALLBACK_FLAG_REQUEST_ERROR = #WINHTTP_CALLBACK_STATUS_REQUEST_ERROR
#WINHTTP_CALLBACK_FLAG_ALL_COMPLETIONS = $007E0000
#WINHTTP_CALLBACK_FLAG_ALL_NOTIFICATIONS = $FFFFFFFF

Global winhttp_dll = OpenLibrary ( #PB_Any , "winhttp.dll" ) 
If Not winhttp_dll : End 0 : EndIf

Prototype.l WinHttpCrackUrl ( pwszUrl.p - unicode , 
                              dwUrlLength.l , 
                              dwFlags.l , 
                              *lpUrlComponents.URL_COMPONENTS ) 

Prototype.i WinHttpOpen ( pwszUserAgent.p - unicode , 
                          dwAccessType.l , 
                          pwszProxyName.p - unicode , 
                          pwszProxyBypass.p - unicode , 
                          dwFlags.l ) 

Prototype.l WinHttpSetOption ( hInternet.i , 
                               dwOption.l , 
                               *lpBuffer , 
                               dwBufferLength.l ) ;

Prototype.i WinHttpConnect ( hSession.i , 
                             pswzServerName.p - unicode , 
                             nServerPort.l , 
                             dwReserved.l ) 

Prototype.i WinHttpOpenRequest ( hConnect.i , 
                                 pwszVerb.p - unicode , 
                                 pwszObjectName.p - unicode , 
                                 pwszVersion.p - unicode , 
                                 pwszReferrer.p - unicode , 
                                 *ppwszAcceptTypes , 
                                 dwFlags.l ) ;

Prototype.l WinHttpSendRequest ( hRequest.i , 
                                 pwszHeaders.p - unicode , 
                                 dwHeadersLength.l , 
                                 *lpOptional , 
                                 dwOptionalLength.l , 
                                 dwTotalLength.l , 
                                 *dwContext ) 

Prototype.l WinHttpReceiveResponse ( hRequest.i , 
                                     *lpReserved ) 

Prototype.l WinHttpQueryHeaders ( hRequest.i , 
                                  dwInfoLevel.l , 
                                  pwszName.p - unicode , 
                                  *lpBuffer , 
                                  *lpdwBufferLength , 
                                  *lpdwIndex ) 

Prototype.l WinHttpQueryDataAvailable ( hRequest.i , 
                                        *lpdwNumberOfBytesAvailable ) 

Prototype.l WinHttpReadData ( hRequest.i , 
                              *lpBuffer , 
                              dwNumberOfBytesToRead.l , 
                              *lpdwNumberOfBytesRead ) ;

Prototype.l WinHttpQueryAuthSchemes ( hRequest.i , 
                                      *lpdwSupportedSchemes , 
                                      *lpdwFirstScheme , 
                                      *pdwAuthTarget ) 

Prototype.l WinHttpSetCredentials ( hRequest.i , 
                                    AuthTargets.l , 
                                    AuthScheme.l , 
                                    pwszUserName.p - unicode , 
                                    pwszPassword.p - unicode , 
                                    *pAuthParams ) 

Prototype.l WinHttpCloseHandle ( hInternet.i ) 

Prototype.i WinHttpSetStatusCallback(hInternet.i,
                                     *lpfnInternetCallback,
                                     dwNotificationFlags.l,
                                     *dwReserved)

Global WinHttpCrackUrl.WinHttpCrackUrl = GetFunction ( winhttp_dll , "WinHttpCrackUrl" ) 
Global WinHttpOpen.WinHttpOpen = GetFunction ( winhttp_dll , "WinHttpOpen" ) 
Global WinHttpSetOption.WinHttpSetOption = GetFunction ( winhttp_dll , "WinHttpSetOption" ) 
Global WinHttpCloseHandle.WinHttpCloseHandle = GetFunction ( winhttp_dll , "WinHttpCloseHandle" ) 
Global WinHttpConnect.WinHttpConnect = GetFunction ( winhttp_dll , "WinHttpConnect" ) 
Global WinHttpOpenRequest.WinHttpOpenRequest = GetFunction ( winhttp_dll , "WinHttpOpenRequest" ) 
Global WinHttpSendRequest.WinHttpSendRequest = GetFunction ( winhttp_dll , "WinHttpSendRequest" ) 
Global WinHttpReceiveResponse.WinHttpReceiveResponse = GetFunction ( winhttp_dll , "WinHttpReceiveResponse" ) 
Global WinHttpQueryHeaders.WinHttpQueryHeaders = GetFunction ( winhttp_dll , "WinHttpQueryHeaders" ) 
Global WinHttpQueryDataAvailable.WinHttpQueryDataAvailable = GetFunction ( winhttp_dll , "WinHttpQueryDataAvailable" ) 
Global WinHttpReadData.WinHttpReadData = GetFunction ( winhttp_dll , "WinHttpReadData" ) 
Global WinHttpQueryAuthSchemes.WinHttpQueryAuthSchemes = GetFunction ( winhttp_dll , "WinHttpQueryAuthSchemes" ) 
Global WinHttpSetCredentials.WinHttpSetCredentials = GetFunction ( winhttp_dll , "WinHttpSetCredentials" ) 
Global WinHttpSetStatusCallback.WinHttpSetStatusCallback = GetFunction ( winhttp_dll , "WinHttpSetStatusCallback" ) 



interested in Cybersecurity..
User avatar
Lord
Addict
Addict
Posts: 907
Joined: Tue May 26, 2009 2:11 pm

Re: MULTI-SEGMENT FAST FILE DOWNLOADER .."Only windows

Post by Lord »

Nice piece of code!

But I've got one question.
The segments are downloaded sequential and not in parellel:

Code: Select all

  Repeat
    For p = 1 To DownloadMaxPart
      With aMultiDownloadSegment(p)
        If \CurrentMemoryPos < \DownloadSize
          DownloadRate = \DownloadSize - \CurrentMemoryPos
          If DownloadRate > #DownloadMaxRate:DownloadRate = #DownloadMaxRate:EndIf
          DownSize = WinHttpReadDataEx(\hRequest, \DownloadMemory + \CurrentMemoryPos, DownloadRate);receiving data
        EndIf
        If DownSize > 0
          \CurrentMemoryPos + DownSize
          AllDownSize + DownSize
        ElseIf DownSize = -1 ; download error
          Check(0,-8)
        EndIf
      Next
    EndWith
  Until AllDownSize = FileSize
So, where is the advantage here?
Can the segments be downloaded in parallel by threads?

(OK. That are two questions. :) )
Image
Post Reply