CopyFileEx

CopyFileEx

Postby thefull » Wed Jul 25, 2007 4:01 pm

Usando CopyFileEx, con fivewin , por ejemplo.

Bueno, en un proyecto he tenido que copiar un fichero de un sitio a otro,
si bueno teniamos la funcion MyCopyFile() que teniamos implementada en
Fivewin de la siguiente manera:
Code: Select all  Expand view
DLL32 FUNCTION MYCOPYFILE( lpExistingFileName AS STRING, lpNewFileName AS STRING, bFailIfExists AS LONG) AS LONG;
PASCAL FROM "CopyFileA" LIB "kernel32.dll"


Dicha funcion, para copiar ficheros de 1 o 2 megas, funciona muy bien, porque
da tiempo a nuestra aplicación que no se vuelva con un '( No responde )'.

Si no lo crees, muy simple, intenta copiar un fichero de 100 Megas, y me cuentas ;-)

Básicamente, es porque nosotros HEMOS PERDIDO TOTALMENTE el control sobre nuestra
aplicacion, y no podemos recibir/enviar mensajes a Windows en que punto estamos.

Esto tiene fácil solución, nos hacemos nuestra propia función CopyFile().
El mayor inconveniente es que tenemos que hacerla ;-), y eso, es un paso atrás.

¿ Como podemos hacerlo entonces ? Muy simple usando la funcion CopyFileEx(), disponible en el API win32.

Dicha función no funciona bajo los Windows 98 o similares, estáis advertidos, o eso dicen las malas lenguas.

Bien, sin más dilaciones vamos a realizar la implementación, y eso si,
aqui vamos a usar C, por cuestión de velocidad.

Nuestro prototipo
CopyFileEx( Fichero_Origen, Fichero_Destino, bCodeblock ) --> nResult
Fichero_Origen y Fichero_Destino no necesitan explicación.
bCodeblock , es un bloque de código que nos va a pasar ;
{ |nPorcentaje, nTotal, nTransferido | MyFunction( nPorcentaje, nTotal, nTransferido ) }

Un ejemplo de llamada;
Code: Select all  Expand view
if ( COPYFILEEX ( cFile_Path_MDB , cFile_Path_Repara+"\TPVMAL.MDB", {|x,y,z| pasa(x,y,z) } ) == 0 )
    ? "MAL"
endif


Ah!, Ahora puedes ver, que encima vamos a poder montar una progressbar ,
por ejemplo, para ir mostrando el % que va quedando, es el primer parámetro,
que simplemente resulta de ;
nPorcentaje = nTransferido * 100 / nTotal

Lo he pasado como parámetro al codeblock, porque de esta manera, ya lo tienes
calculado a nivel de C, no perdiendo más tiempo.

Lo importante es que puedes crear una funcion como;

Code: Select all  Expand view
STATIC s_lCancel := .F.

#define PROGRESS_CONTINUE 0
#define PROGRESS_CANCEL   1

STATIC FUNCTION PASA( nPorcentaje, nTotal, nTransferido )
    static nPasa := 0
   
    if nPasa > 10
       SysRefresh()
       nPasa := 0
    endif
    nPasa++
    oProgress:SetPos( nPorcentaje )
   
return( if( s_lCancel, PROGRESS_CANCEL, PROGRESS_CONTINUE  ) )


Uy! ¿ Pero que ven tus ojitos!! ? Si , PUEDES CANCELAR también lo que
estas copiando, ;-) , muy útil si quieres cancelar un archivo de 500 Megas,
por ejemplo, créeme.

En este caso, por ejemplo, puedes poner un botón similar a esto;
REDEFINE BUTTON oBtn ACTION ( s_lCancel := .T. ) ID 110 OF oWnd

El cambiar el estado de la variable static s_lCancel, simplemente hará
que cuando el codeblock se vuelva a ejecutar, lo cancelará.

Ahora bien, todo esto no es posible sin el codigo fuente de C, asi
que dejo paso al codigo fuente;
Code: Select all  Expand view
#pragma BEGINDUMP
#include <windows.h>
#include <stdio.h>
#include "hbapi.h"
#include "item.api"
#include "hbapiitm.h"
#include "hbvm.h"
#include "hbapiitm.h"

/*
Convertimos un valor LARGE_INTEGER a double
*/
double clarge2int( DWORD Lo, DWORD Hi )
{
  double dblLo, dblHi;
  double ret;

  if( Lo < 0  ){
     dblLo = 2 ^ 32 + Lo ;
  } else {
     dblLo = Lo;
  }

  if( Hi < 0 ) {
    dblHi = 2 ^ 32 + Hi;
  } else {
    dblHi = Hi;
  }
 
  ret = ( dblLo + dblHi);

  return( ret );
}

DWORD CALLBACK CopyProgressRoutine(
  LARGE_INTEGER TotalFileSize,
  LARGE_INTEGER TotalBytesTransferred,
  LARGE_INTEGER StreamSize,
  LARGE_INTEGER StreamBytesTransferred,
  DWORD dwStreamNumber,
  DWORD dwCallbackReason,
  HANDLE hSourceFile,
  HANDLE hDestinationFile,
  LPVOID pCallback_Progress
)
{
   double TotalSize = clarge2int( TotalFileSize.u.LowPart, TotalFileSize.u.HighPart );
   double TotalBytesTrans = clarge2int( TotalBytesTransferred.u.LowPart, TotalBytesTransferred.u.HighPart );
   double percent =  TotalBytesTrans * 100 / TotalSize ;
                                           
    if( pCallback_Progress ) {
        hb_vmPushSymbol( &hb_symEval );
        hb_vmPush( pCallback_Progress );
        hb_vmPushDouble( percent,1 );
        hb_vmPushDouble( TotalSize,1 );
        hb_vmPushDouble( TotalBytesTrans,1 );
        hb_vmSend( 3 );     
        return( hb_parni( -1 ) );
     }

return PROGRESS_CONTINUE;
}


HB_FUNC( COPYFILEEX )
{
  LPCTSTR lpExistingFileName = hb_parc( 1 );
  LPCTSTR lpNewFileName = hb_parc( 2 );
  LPPROGRESS_ROUTINE lpProgressRoutine = ( void * )CopyProgressRoutine ;
  LPBOOL pbCancel = NULL;
  DWORD dwCopyFlags;
  BOOL ret;
  PHB_ITEM pCallback_Progress;
 
  if( ! ISNIL( 3 ) ) {
     pCallback_Progress = hb_itemNew( hb_param( 3, HB_IT_ANY ) );
     }

  ret = CopyFileEx( lpExistingFileName, lpNewFileName, lpProgressRoutine, pCallback_Progress, pbCancel, NULL );
  hb_retni( ret );
  hb_itemRelease( (PHB_ITEM) pCallback_Progress );

}

#pragma ENDDUMP

Lo que más me a costado no a sido la implementación de la funcion en si
misma, si no, convertir un LARGE_INTEGER en un valor double.

No es una función portable del API de Windows para Harbour, es una adaptación
a una necesidad en concreto, si lo quereis hacer compatible con el API de
Windows, ahi tenéis un punto de partida, por mi parte no pienso mejorarla
más.

Antonio, seria interesante portar esta funcion a Fivewin.
Los warnings de C, son porque no se usan ciertos parametros de la callback, no se como decirle al compilador que no los muestre.
Saludos
Rafa Carmona ( rafa.thefullARROBAgmail.com___quitalineas__)
User avatar
thefull
 
Posts: 729
Joined: Fri Oct 07, 2005 7:42 am
Location: Barcelona

Re: CopyFileEx

Postby JoseLuis » Mon Feb 02, 2009 11:48 am

Buenos dias

Me gustaria saber si se ha implementado ya algo parecido a éste post un poco ya antiguo de The Full, ya que necesito copiar ficheros de unos 50 megas aproximadamente.

De todas las maneras, he hecho lo que pone en el post y copia los ficheros correctamente, pero se me queda colgado en la barra de progreso.

Será que estoy construyendo el dialogo de progreso mal, ya que en el ejemplo que pone The full:
Code: Select all  Expand view
    STATIC FUNCTION PASA( nPorcentaje, nTotal, nTransferido )
        static nPasa := 0
       
        if nPasa > 10
           SysRefresh()
           nPasa := 0
        endif
        nPasa++
        oProgress:SetPos( nPorcentaje )
       
    return( if( s_lCancel, PROGRESS_CANCEL, PROGRESS_CONTINUE  ) )




Da un error en oProgress, y lo que deduzco es que hay que hacer un dialogo progress, y hago esto:
Code: Select all  Expand view
STATIC FUNCTION PASA( nPorcentaje, nTotal, nTransferido )
    static nPasa := 0
    local oDlg1,oprogress
    DEFINE DIALOG oDlg1 TITLE "Espera por favor" SIZE 250,50 PIXEL STYLE (WS_CAPTION)
    @ 5, 3 PROGRESS oProgress POSITION 0 SIZE 120, 10 PIXEL
    if nPasa > 10
       SysRefresh()
       nPasa := 0
    endif
    nPasa++
    oProgress:SetPos( nPorcentaje )

    ACTIVATE DIALOG oDlg1 CENTERED

 
return( if( s_lCancel, PROGRESS_CANCEL, PROGRESS_CONTINUE  ) )


Hago algo mal?

Saludos
--------------------------
Saludos

Jose Luis
JoseLuis
 
Posts: 426
Joined: Thu Oct 19, 2006 12:28 pm
Location: Toledo

Re: CopyFileEx

Postby FranciscoA » Mon Feb 02, 2009 2:41 pm

Esta funcion la he usado durante años, desde FW 2.0, y bueno... hasta el momento me ha funcionado.
Talvez quieran probarla (si aun no lo han hecho). No es mia, venia como ejemplo en FW
//----------------------------------------------------------------------------//

function CopyFiles( aSource, aTarget, nBufSize )

local oDlg, oSay1, oSay2, oSay3, oBtnCancel
local oMeter1, oMeter2
local nAmount1, nAmount2
local lEnd := .f.


DEFAULT nBufSize := 4000

DEFINE DIALOG oDlg RESOURCE "CopyFiles"

REDEFINE SAY oSay1 ID 110 OF oDlg
REDEFINE SAY oSay2 ID 120 OF oDlg

REDEFINE METER oMeter1 VAR nAmount1 ID 130 OF oDlg

REDEFINE SAY oSay3 ID 140 OF oDlg
REDEFINE METER oMeter2 VAR nAmount2 ID 150 OF oDlg

REDEFINE BUTTON oBtnCancel ID 2 OF oDlg ;
ACTION ( lEnd := .t., SysRefresh(), oDlg:End() )

oDlg:bStart := { || StartCopy( aSource, aTarget, nBufSize,;
oSay1, oSay2, oMeter1, oSay3, oMeter2,;
@lEnd, oDlg ),;
oBtnCancel:SetText( "&Ok" ) }

ACTIVATE DIALOG oDlg CENTERED
return nil

//----------------------------------------------------------------------------//

static function StartCopy( aSource, aTarget, nBufSize, oSay1, oSay2,;
oMeter1, oSay3, oMeter2, lEnd, oDlg )

local n
local hSource, hTarget
local cBuffer := Space( nBufSize )
local nBytes, nFile := 0, nTotal := 0
local nTotSize := 0

for n = 1 to Len( aSource )
if ! File( aSource[ n ] )
MsgInfo( "Fichero no encontrado: " + aSource[ n ], "Advertencia" )
else
hSource = FOpen( aSource[ n ] )
nTotSize += FSeek( hSource, 0, 2 )
FClose( hSource )
endif
SysRefresh()
next

oMeter2:nTotal = nTotSize

for n = 1 to Len( aSource )
IF File( aSource[ n ] )
hSource = FOpen( aSource[ n ] )
hTarget = FCreate( aTarget[ n ] )
oSay1:SetText( "Fuente : " + aSource[ n ] )
oSay2:SetText( "Destino: " + aTarget[ n ] )
oMeter1:Set( 0 )
oMeter1:nTotal = FSeek( hSource, 0, 2 )
FSeek( hSource, 0, 0 )
nFile := 0
SysRefresh()
while ( nBytes := FRead( hSource, @cBuffer, nBufSize ) ) > 0
FWrite( hTarget, cBuffer, nBytes )
oSay3:SetText( "Bytes copiados: " + ;
AllTrim( Str( nTotal += nBytes ) ) )
oMeter1:Set( nFile += nBytes )
oMeter2:Set( nTotal )
SysRefresh()
end
FClose( hSource )
FClose( hTarget )
if lEnd
exit
endif
ENDIF
next

oDlg:End()

return nil

Saludos.
Francisco J. Alegría P.
Chinandega, Nicaragua.

Fwxh-MySql-TMySql
User avatar
FranciscoA
 
Posts: 2110
Joined: Fri Jul 18, 2008 1:24 am
Location: Chinandega, Nicaragua, C.A.

Re: CopyFileEx

Postby thefull » Mon Feb 02, 2009 4:09 pm

Es que estas llamando cada vez a la creación del dialogo.

Lo que tienes que hacer es lo siguiente, seudo codigo a piñon;

STATIC oProgressBar // Porque la función PASA va a actualizartelo.

FUNCTION DlgMio()
Local oDlg
DEFINE DIALOG oDlg
@1,1 PROGRESSBAR oProgressBar
@2,1 BUTTON CopyDlg()
@3,1 BUTTON CancelDlg()

ACTIVATE DIALOG
RETURN NIL

FUNCTION CopyDlg()
if ( COPYFILEEX ( cFile_Path_MDB , cFile_Path_Repara+"\TPVMAL.MDB", {|x,y,z| pasa(x,y,z) } ) == 0 )
? "MAL"
endif
return nil

STATIC FUNCTION PASA( nPorcentaje, nTotal, nTransferido )
static nPasa := 0

if nPasa > 10
SysRefresh()
nPasa := 0
endif
nPasa++
oProgress:SetPos( nPorcentaje ) // Esto es una variable static

return( if( s_lCancel, PROGRESS_CANCEL, PROGRESS_CONTINUE ) )

Lo pillas ahora mejor ?
Saludos
Rafa Carmona ( rafa.thefullARROBAgmail.com___quitalineas__)
User avatar
thefull
 
Posts: 729
Joined: Fri Oct 07, 2005 7:42 am
Location: Barcelona

Re: CopyFileEx

Postby Antonio Linares » Mon Feb 02, 2009 4:58 pm

Usando FOpen(), FCreate(), FRead(), FWrite() y FClose() tenemos control total y podemos hacer lo que queramos :-)
Ambas soluciones son validas:

1. Pedirle al API de Windows que lo haga, usando una funcion tipo callback como ha hecho Rafa.

2. Hacerlo a "mano" usando estas funciones mencionadas de Clipper de toda la vida :-)
regards, saludos

Antonio Linares
www.fivetechsoft.com
User avatar
Antonio Linares
Site Admin
 
Posts: 41314
Joined: Thu Oct 06, 2005 5:47 pm
Location: Spain

Re: CopyFileEx

Postby JoseLuis » Mon Feb 02, 2009 7:37 pm

Hola Rafa

Gracias por responder

Tenia encima la empanadilla de Móstoles

Solucionado

Gracias

Jose Luis
--------------------------
Saludos

Jose Luis
JoseLuis
 
Posts: 426
Joined: Thu Oct 19, 2006 12:28 pm
Location: Toledo


Return to FiveWin para Harbour/xHarbour

Who is online

Users browsing this forum: Google [Bot] and 95 guests