2025-01-07 22:49 UTC+0100 Aleksander Czajczynski (hb fki.pl)

* contrib/hbcurl/hbcurl.ch
    * updated HB_CURLOPT_SSL_OPTIONS: HB_CURLSSLOPT_*

    + added HB_CURLMSG_RESP_HANDLE, HB_CURLMSG_RESP_HPOS
      mappings of array elements returned by
      curl_multi_info_read( <hMulti>[, <aHandles> ]) -> <aResults>

  * contrib/hbcurl/core.c
    * adapted @emazv72 contribution from:
      https://github.com/vszakats/hb/pull/344
      Many thanks, it could be useful for managing multiple transfers,
      by extending some kind of Inkey() loop or in many other
      scenarios.

    + added curl lib version guards

    * minor code and code formattings

    + extended function
      curl_multi_info_read( <hMulti>[, <aHandles> ]) -> <aResults>
      with a possibility to return real GC pointer of specific
      transfer related to reported event. <aHandles> should be
      specified if this is expected.

  + contrib/hbcurl/tests/multi.prg
    + demo of the additions
This commit is contained in:
Aleksander Czajczynski
2025-01-07 22:49:07 +01:00
parent 60663791e1
commit c2870ac7d8
4 changed files with 252 additions and 32 deletions

View File

@@ -7,6 +7,34 @@
Entries may not always be in chronological/commit order.
See license at the end of file. */
2025-01-07 22:49 UTC+0100 Aleksander Czajczynski (hb fki.pl)
* contrib/hbcurl/hbcurl.ch
* updated HB_CURLOPT_SSL_OPTIONS: HB_CURLSSLOPT_*
+ added HB_CURLMSG_RESP_HANDLE, HB_CURLMSG_RESP_HPOS
mappings of array elements returned by
curl_multi_info_read( <hMulti>[, <aHandles> ]) -> <aResults>
* contrib/hbcurl/core.c
* adapted @emazv72 contribution from:
https://github.com/vszakats/hb/pull/344
Many thanks, it could be useful for managing multiple transfers,
by extending some kind of Inkey() loop or in many other
scenarios.
+ added curl lib version guards
* minor code and code formattings
+ extended function
curl_multi_info_read( <hMulti>[, <aHandles> ]) -> <aResults>
with a possibility to return real GC pointer of specific
transfer related to reported event. <aHandles> should be
specified if this is expected.
+ contrib/hbcurl/tests/multi.prg
+ demo of the additions
2025-01-07 21:43 UTC+0100 Emanuele Zavallone (emanuele.zavallone gmail.com)
* contrib/hbcurl/easy.c
* contrib/hbcurl/hbcurl.ch

View File

@@ -127,6 +127,16 @@ typedef struct _HB_CURL
} HB_CURL, * PHB_CURL;
/* Multi interface */
/* --------------- */
#if LIBCURL_VERSION_NUM >= 0x070906
typedef struct _HB_CURLM
{
CURLM * curlm;
} HB_CURLM, * PHB_CURLM;
#endif
/* functions to keep passed string values accessible even if HVM
* destroy them. It's necessary for old CURL versions which do not
@@ -2499,19 +2509,10 @@ HB_FUNC( CURL_GETDATE )
hb_errRT_BASE( EG_ARG, 2010, NULL, HB_ERR_FUNCNAME, HB_ERR_ARGS_BASEPARAMS );
}
/* Multi interface */
/* ----------------- */
typedef struct _HB_CURLM
{
CURLM * curlm;
} HB_CURLM, * PHB_CURLM;
/* Constructor/Destructor */
/* ---------------------- */
/* Multi interface Constructor/Destructor */
/* -------------------------------------- */
#if LIBCURL_VERSION_NUM >= 0x070906
static void PHB_CURLM_free( PHB_CURLM hb_curlm )
{
curl_multi_cleanup( hb_curlm->curlm );
@@ -2574,18 +2575,21 @@ static PHB_CURLM PHB_CURLM_par( int iParam )
return ph ? ( PHB_CURLM ) *ph : NULL;
}
#endif
/* Harbour interface */
/* ----------------- */
/* Harbour multi interface */
/* ----------------------- */
HB_FUNC( CURL_MULTI_INIT )
{
#if LIBCURL_VERSION_NUM >= 0x070906
PHB_CURLM_ret( );
#endif
}
HB_FUNC( CURL_MULTI_CLEANUP )
{
#if LIBCURL_VERSION_NUM >= 0x070906
if( PHB_CURLM_is( 1 ) )
{
void ** ph = ( void ** ) hb_parptrGC( &s_gcCURLMFuncs, 1 );
@@ -2598,11 +2602,13 @@ HB_FUNC( CURL_MULTI_CLEANUP )
}
}
else
#endif
hb_errRT_BASE( EG_ARG, 2010, NULL, HB_ERR_FUNCNAME, HB_ERR_ARGS_BASEPARAMS );
}
HB_FUNC( CURL_MULTI_ADD_HANDLE )
{
#if LIBCURL_VERSION_NUM >= 0x070906
if( PHB_CURLM_is( 1 ) && PHB_CURL_is( 2 ) )
{
PHB_CURLM hb_curlm = PHB_CURLM_par( 1 );
@@ -2611,11 +2617,13 @@ HB_FUNC( CURL_MULTI_ADD_HANDLE )
hb_retnl( hb_curlm && hb_curl ? ( long ) curl_multi_add_handle( hb_curlm->curlm, hb_curl->curl) : HB_CURLM_INTERNAL_ERROR );
}
else
#endif
hb_errRT_BASE( EG_ARG, 2010, NULL, HB_ERR_FUNCNAME, HB_ERR_ARGS_BASEPARAMS );
}
HB_FUNC( CURL_MULTI_REMOVE_HANDLE )
{
#if LIBCURL_VERSION_NUM >= 0x070906
if( PHB_CURLM_is( 1 ) && PHB_CURL_is( 2 ) )
{
PHB_CURLM hb_curlm = PHB_CURLM_par( 1 );
@@ -2624,18 +2632,20 @@ HB_FUNC( CURL_MULTI_REMOVE_HANDLE )
hb_retnl( hb_curlm && hb_curl ? ( long ) curl_multi_remove_handle( hb_curlm->curlm, hb_curl->curl) : HB_CURLM_INTERNAL_ERROR );
}
else
#endif
hb_errRT_BASE( EG_ARG, 2010, NULL, HB_ERR_FUNCNAME, HB_ERR_ARGS_BASEPARAMS );
}
HB_FUNC( CURL_MULTI_PERFORM )
{
#if LIBCURL_VERSION_NUM >= 0x070906
if( PHB_CURLM_is( 1 ) && HB_ISBYREF( 2 ) )
{
CURLMcode res = ( CURLMcode ) HB_CURLM_INTERNAL_ERROR;
PHB_CURLM hb_curlm = PHB_CURLM_par( 1 );
if ( hb_curlm )
if( hb_curlm )
{
int running_handles = 0;
res = curl_multi_perform( hb_curlm->curlm, &running_handles);
@@ -2646,18 +2656,20 @@ HB_FUNC( CURL_MULTI_PERFORM )
}
else
#endif
hb_errRT_BASE( EG_ARG, 2010, NULL, HB_ERR_FUNCNAME, HB_ERR_ARGS_BASEPARAMS );
}
HB_FUNC( CURL_MULTI_POLL )
{
#if LIBCURL_VERSION_NUM >= 0x074200
if( PHB_CURLM_is( 1 ) && HB_ISNUM( 2 ) )
{
CURLMcode res = ( CURLMcode ) HB_CURLM_INTERNAL_ERROR;
PHB_CURLM hb_curlm = PHB_CURLM_par( 1 );
if ( hb_curlm )
if( hb_curlm )
{
res = curl_multi_poll( hb_curlm->curlm,
NULL,
@@ -2670,41 +2682,60 @@ HB_FUNC( CURL_MULTI_POLL )
}
else
#endif
hb_errRT_BASE( EG_ARG, 2010, NULL, HB_ERR_FUNCNAME, HB_ERR_ARGS_BASEPARAMS );
}
HB_FUNC( CURL_MULTI_INFO_READ )
{
#if LIBCURL_VERSION_NUM >= 0x070906
if( PHB_CURLM_is( 1 ) )
{
PHB_ITEM pReturn = NULL;
PHB_CURLM hb_curlm = PHB_CURLM_par( 1 );
if ( hb_curlm )
if( hb_curlm )
{
int msgs_in_queue = 0;
long response_code = 0;
struct CURLMsg * msg = curl_multi_info_read( hb_curlm->curlm, &msgs_in_queue );
struct CURLMsg *msg = curl_multi_info_read( hb_curlm->curlm, &msgs_in_queue );
if ( msg )
if( msg && curl_easy_getinfo( msg->easy_handle, CURLINFO_RESPONSE_CODE, &response_code ) == CURLE_OK )
{
CURLcode res = ( CURLcode ) HB_CURLE_ERROR;
long response_code = 0;
res = curl_easy_getinfo( msg->easy_handle, CURLINFO_RESPONSE_CODE, &response_code );
PHB_ITEM pHandles = hb_param( 2, HB_IT_ARRAY );
pReturn = hb_itemArrayNew( HB_CURLMSG_RESP_LAST );
hb_arraySetNL( pReturn, HB_CURLMSG_RESP_LEN, ( long ) msgs_in_queue );
hb_arraySetNL( pReturn, HB_CURLMSG_RESP_RESPONSE_CODE, ( long ) response_code );
hb_arraySetNL( pReturn, HB_CURLMSG_RESP_MSG, ( long ) msg->msg );
hb_arraySetNL( pReturn, HB_CURLMSG_RESP_RESULT, ( long ) msg->data.result );
hb_arraySetNI( pReturn, HB_CURLMSG_RESP_HPOS, 0 );
if( pHandles )
{
HB_SIZE handles_count = hb_arrayLen( pHandles );
HB_SIZE i;
for( i = 1; i <= handles_count; i++ )
{
void ** ph = hb_arrayGetPtrGC( pHandles, i, &s_gcCURLFuncs );
if( ph && *ph )
{
PHB_CURL hbcurl = ( PHB_CURL ) *ph;
if( hbcurl->curl == msg->easy_handle )
{
hb_arraySetNL( pReturn, HB_CURLMSG_RESP_HPOS, ( long ) i );
hb_arraySet( pReturn, HB_CURLMSG_RESP_HANDLE, hb_arrayGetItemPtr( pHandles, i ) );
}
}
}
}
}
}
if ( pReturn )
if( pReturn )
hb_itemReturnRelease( pReturn );
else
hb_ret();
@@ -2712,12 +2743,10 @@ HB_FUNC( CURL_MULTI_INFO_READ )
}
else
hb_errRT_BASE( EG_ARG, 2010, NULL, HB_ERR_FUNCNAME, HB_ERR_ARGS_BASEPARAMS );
#endif
}
/* Harbour interface (session independent) */
/* NOTE: Obsolete, superceded by curl_easy_escape() */
/* NOTE: Obsolete, superseded by curl_easy_escape() */
HB_FUNC( CURL_ESCAPE )
{
if( HB_ISCHAR( 1 ) )
@@ -2730,7 +2759,7 @@ HB_FUNC( CURL_ESCAPE )
hb_errRT_BASE( EG_ARG, 2010, NULL, HB_ERR_FUNCNAME, HB_ERR_ARGS_BASEPARAMS );
}
/* NOTE: Obsolete, superceded by curl_easy_unescape() */
/* NOTE: Obsolete, superseded by curl_easy_unescape() */
HB_FUNC( CURL_UNESCAPE )
{
if( HB_ISCHAR( 1 ) )

View File

@@ -295,6 +295,11 @@
/* HB_CURLOPT_SSL_OPTIONS values */
#define HB_CURLSSLOPT_ALLOW_BEAST hb_bitShift( 1, 0 )
#define HB_CURLSSLOPT_NO_REVOKE hb_bitShift( 1, 1 )
#define HB_CURLSSLOPT_NO_PARTIALCHAIN hb_bitShift( 1, 2 )
#define HB_CURLSSLOPT_REVOKE_BEST_EFFORT hb_bitShift( 1, 3 )
#define HB_CURLSSLOPT_NATIVE_CA hb_bitShift( 1, 4 )
#define HB_CURLSSLOPT_AUTO_CLIENT_CERT hb_bitShift( 1, 5 )
/* HB_CURLOPT_HTTPAUTH option */
#define HB_CURLAUTH_NONE 0 /* nothing */
@@ -604,6 +609,8 @@
#define HB_CURLMSG_RESP_RESPONSE_CODE 2 /* curl_easy_getinfo( msg->easy_handle, CURLINFO_RESPONSE_CODE ) */
#define HB_CURLMSG_RESP_MSG 3 /* CURLMSG */
#define HB_CURLMSG_RESP_RESULT 4 /* CURLcode */
#define HB_CURLMSG_RESP_LAST HB_CURLMSG_RESP_RESULT
#define HB_CURLMSG_RESP_HANDLE 5 /* handle to original curl_easy_init */
#define HB_CURLMSG_RESP_HPOS 6 /* position in handle <array> passed to curl_multi_info_read(, <array> ) */
#define HB_CURLMSG_RESP_LAST HB_CURLMSG_RESP_HPOS
#endif /* HBCURL_CH_ */

View File

@@ -0,0 +1,156 @@
#define REMOTE_1 "https://github.com/"
#define REMOTE_2 "https://github.com/harbour/core/"
#define REMOTE_3 "https://raw.githubusercontent.com/harbour/core/refs/heads/master/contrib/hbcurl/hbcurl.hbm"
#require "hbcurl"
#include "inkey.ch"
PROCEDURE Main( cRemote4 )
LOCAL hMulti
LOCAL hEasy, aResult, nKey := 0
LOCAL aUrls := { REMOTE_1, REMOTE_2, REMOTE_3 }
LOCAL aHandles, i, nErr, nRun := 0, nLastRun := 0
/* add a remote from command-line if you want */
IF ! Empty( cRemote4 )
AAdd( aUrls, cRemote4 )
ENDIF
/* add the individual transfers */
aHandles := Array( Len( aUrls ) )
curl_global_init()
hMulti := curl_multi_init()
FOR i := 1 TO Len( aUrls )
hEasy := curl_easy_init()
IF hEasy != NIL
curl_easy_setopt( hEasy, HB_CURLOPT_URL, aUrls[ i ] )
curl_easy_setopt( hEasy, HB_CURLOPT_DOWNLOAD )
curl_easy_setopt( hEasy, HB_CURLOPT_DL_BUFF_SETUP )
// curl_easy_setopt( hEasy, HB_CURLOPT_VERBOSE, .T. )
curl_easy_setopt( hEasy, HB_CURLOPT_SSL_OPTIONS, HB_CURLSSLOPT_NATIVE_CA )
curl_multi_add_handle( hMulti, hEasy )
aHandles[ i ] := hEasy
ENDIF
NEXT
CLS
nErr := curl_multi_perform( hMulti, @nLastRun )
IF nErr != HB_CURLM_OK
? "curl_multi_perform() failed, err=", nErr
Cleanup( aHandles, hMulti )
RETURN
ENDIF
SetPos( 3, 0 )
DO WHILE curl_multi_perform( hMulti, @nRun ) == HB_CURLM_OK
aResult := CurGet()
@ 1, 1 SAY Time()
@ 2, 1 SAY "downloading... (" + hb_ntos( nRun ) + " transfers left)"
CurSet( aResult )
IF nRun < nLastRun
FOR i := 1 TO nLastRun - nRun
/*
* passing possible aHandles is required to receive curl_easy_*
* compatible GC-pointer in aResult[ HB_CURLMSG_RESP_HANDLE ]
*/
aResult := curl_multi_info_read( hMulti, aHandles )
IF aResult # NIL
/*
* instead of FOR loop you could possibly
* DO WHILE HB_IsArray( aResult := curl_multi_info_read( hMulti ) ) .AND. ;
* aResult[ HB_CURLMSG_RESP_LEN ] > 0
*/
? "HB_CURLMSG_RESP_LEN :", aResult[ HB_CURLMSG_RESP_LEN ]
? "HB_CURLMSG_RESP_RESPONSE_CODE:", aResult[ HB_CURLMSG_RESP_RESPONSE_CODE ]
? "HB_CURLMSG_RESP_MSG :", aResult[ HB_CURLMSG_RESP_MSG ]
? "HB_CURLMSG_RESP_RESULT :", aResult[ HB_CURLMSG_RESP_RESULT ]
? "HB_CURLMSG_RESP_HPOS :", aResult[ HB_CURLMSG_RESP_HPOS ]
IF aResult[ HB_CURLMSG_RESP_HPOS ] > 0
? "URL :", aUrls[ aResult[ HB_CURLMSG_RESP_HPOS ] ]
IF ! Empty( curl_easy_getinfo( aResult[ HB_CURLMSG_RESP_HANDLE ], HB_CURLINFO_REDIRECT_URL ) )
? "HB_CURLINFO_REDIRECT_URL :", curl_easy_getinfo( aResult[ HB_CURLMSG_RESP_HANDLE ], HB_CURLINFO_REDIRECT_URL )
ENDIF
ENDIF
? "--"
ENDIF
NEXT
ENDIF
IF nRun == 0 .OR. nKey == K_ESC
EXIT
ENDIF
nLastRun := nRun
nKey := Inkey( 0.1 )
ENDDO
PrintAllDownloaded( aHandles )
Cleanup( aHandles, hMulti )
RETURN
STATIC PROCEDURE PrintAllDownloaded( aHandles )
LOCAL h
FOR EACH h IN aHandles
IF h == NIL
LOOP
ENDIF
? "Transfer #" + hb_ntos( h:__enumIndex ) + " (" + hb_ntos( curl_easy_getinfo( h, HB_CURLINFO_RESPONSE_CODE ) ) + "): "
IF curl_easy_getinfo( h, HB_CURLINFO_RESPONSE_CODE ) == 0
?? "aborted"
ELSE
?? Left( curl_easy_dl_buff_get( h ), 40 ) + IIF( Len( curl_easy_dl_buff_get( h ) ) > 40, "...", "" )
ENDIF
NEXT
RETURN
STATIC PROCEDURE Cleanup( aHandles, hMulti )
LOCAL h
FOR EACH h IN aHandles
IF h == NIL
LOOP
ENDIF
curl_multi_remove_handle( hMulti, h )
curl_easy_cleanup( h )
NEXT
curl_multi_cleanup( hMulti )
curl_global_cleanup()
RETURN
STATIC FUNCTION CurGet()
RETURN { Row(), Col() }
STATIC PROCEDURE CurSet( a )
SetPos( a[ 1 ], a[ 2 ] )
RETURN