Tempo aproximado para leitura: 00:03:00 min
Dúvida
É possível realizar um download em partes (chunks) de um arquivo grande?
Ambiente
Cross Segmento - TOTVS Backoffice (Linha Protheus) – ADVPL – Todas as versões
Solução
Um download em partes (chunks) é uma técnica onde um arquivo grande é dividido em partes para formar o arquivo completo. Atualmente o Protheus possui uma limitação em requisições definida pela chave MAXSTRINGSIZE; entretanto é possível dividir o download em partes usando headers Range (se o servidor suportar).
Exemplo:
#include "protheus.ch"
/*/{Protheus.doc} DRange
Função para download de grandes arquivos em chunks com limite de tamanho
@author Evandro Narciso
@since data
@version 1.2
@param nMaxSizeMB Numérico - Tamanho máximo em MB (opcional)
@return logical .T. se sucesso, .F. se erro
/*/
User Function DRange(nMaxSizeMB)
Default nMaxSizeMB:=45
Local cLocalFile := ""
Local cUrl := "http://arquivos.receitafederal.gov.br/dados/cnpj/dados_abertos_cnpj/2025-07/Empresas0.zip"
Local nChunkSize := 1024 * 1024 // 1 MB
Local nStart := 0, nEnd := nChunkSize - 1
Local cBuffer := "", nHandle := 0, lConcluido := .F.
Local aHeaders := {"User-Agent: Mozilla/5.0 (Protheus)"}
Local cHeaderRet := "", nAttempt := 1, nMaxAttempts := 3
Local nFileSize := 0, nTotalRead := 0
Local nMaxBytes
nMaxBytes := If(nMaxSizeMB == Nil, Nil, nMaxSizeMB * 1024 * 1024)
// Nome do arquivo com timestamp
cLocalFile := "\dirdoc\" + StrTran(DToS(Date()) + "_" + StrTran(Time(), ":") + "_DOCUMENT.zip", " ", "_")
nHandle := FCreate(cLocalFile)
If nHandle == -1
ConOut("Erro ao criar arquivo: " + cLocalFile + " - " + ErrorMsg())
Return .F.
EndIf
// Loop principal de download
While !lConcluido .and. nAttempt <= nMaxAttempts
// Verifica limite máximo (se definido)
If nMaxBytes != Nil .and. nTotalRead >= nMaxBytes
ConOut("Limite de " + cValToChar(nMaxSizeMB) + " MB atingido")
lConcluido := .T.
Exit
EndIf
// Ajusta o final do chunk para não ultrapassar o limite
If nMaxBytes != Nil .and. (nEnd - nStart + 1) > (nMaxBytes - nTotalRead)
nEnd := nStart + (nMaxBytes - nTotalRead) - 1
EndIf
// Configura Range
aAdd(aHeaders, "Range:bytes=" + AllTrim(Str(nStart)) + "-" + AllTrim(Str(nEnd)))
// Tentativa com tratamento de timeout
Begin Sequence
cBuffer := HTTPGet(cUrl, "", 60, aHeaders, @cHeaderRet)
Recover
cBuffer := ""
ConOut("Timeout na tentativa " + cValToChar(nAttempt))
End Sequence
// Verifica condições de término
If Empty(cBuffer)
If nAttempt < nMaxAttempts
nAttempt++
ConOut("Tentando novamente (" + cValToChar(nAttempt) + "/" + cValToChar(nMaxAttempts) + ")")
Loop
Else
ConOut("Falha após " + cValToChar(nMaxAttempts) + " tentativas")
lConcluido := .T.
EndIf
ElseIf "416 Range Not Satisfiable" $ cHeaderRet .or. Len(cBuffer) < nChunkSize
lConcluido := .T.
FWrite(nHandle, cBuffer) // Grava último chunk
nTotalRead += Len(cBuffer)
Else
If FWrite(nHandle, cBuffer) != Len(cBuffer)
ConOut("Erro ao gravar chunk no arquivo")
lConcluido := .T.
Else
nTotalRead += Len(cBuffer)
nStart := nEnd + 1
nEnd += nChunkSize
// Progresso em MB com limite (se aplicável)
ConOut("Baixados: " + Str(nTotalRead/1024/1024, 10, 2) + " MB" + ;
If(nMaxBytes != Nil, " de " + cValToChar(nMaxSizeMB) + " MB máximo", ""))
EndIf
EndIf
// Remove header Range para próxima iteração
If Len(aHeaders) > 1
aSize(aHeaders, Len(aHeaders)-1)
EndIf
EndDo
FClose(nHandle)
If nTotalRead == 0
FErase(cLocalFile)
Return .F.
EndIf
ConOut("Download concluído: " + cLocalFile + " - Tamanho: " + Str(nTotalRead/1024/1024, 10, 2) + " MB" + ;
If(nMaxBytes != Nil, " (Limite: " + cValToChar(nMaxSizeMB) + " MB)", ""))
Return .T.
Saiba mais
MAXSTRINGSIZE
HttpGet
0 Comentários