Page 2 of 2
Re: OT: Brainstorming: How to handle sw-updates
Posted:
Mon Dec 14, 2009 5:51 pm
by Otto
Hello James
>I mean if the exe is in use by a workstation which does not log out.
OK, I assume you mean this would be a problem when trying to update the EXE? If so, then you have a worse problem than when using local copies. You have no way of knowing if local copies are in use. You could update the server copy, but then you have the situation that users running the local version during the update would prevent DBF modifications.
I have a control.dbf on the network data drive where every workstation logs in and locks ‘his’ record.
The station which executes the dbf-file update must first try to lock the control.dbf successfully (flock - oControl:flock() ).
Or, if there were no DBF modifications, you still could have some users running the old version while users running the app after the server update would be running the new version.
We have a version field in the control.dbf. Every exe checks against this control.dbf. If the version number does not correspond the exe does not start.
Many users will leave a major app running for days so some users may not get the update for days.
If all users are running the server EXE, then everyone must be logged out before the server EXE can be updated. This also allows you to make DBF modifications. You can also be sure all users are using the same version at all times.
I would like to have the exe on the server. But my design is not made for that.
I have a system with many exe files. I have a design that I use for nearly every main task (clients, bills, plan, etc. ) an own exe file.
Therefore and if it is only some seconds on start this is a problem because I open and close very much the exe files.
Otto
Re: OT: Brainstorming: How to handle sw-updates
Posted:
Mon Dec 14, 2009 5:57 pm
by José Luis Sánchez
Hello,
See this two post fron Biem Maimo. They describe an automatic update using an FTP system.
http://bielsys.blogspot.com/2009/02/act ... ca-de.htmlhttp://bielsys.blogspot.com/2009/04/act ... ca-de.htmlRegards,
José Luis
Re: OT: Brainstorming: How to handle sw-updates
Posted:
Mon Dec 14, 2009 6:02 pm
by Otto
Otto,
>What if a user does not switch his PC on before 20.1.?
This is solved by running all EXEs from the server.
James
Hello James,
and if nobody - because they are all on holidays – enters before the 20.1.
This is not a joke.
You must keep in mind that most of my clients have only 1 or 2 workstations and sometimes not always a dedicated fileserver.
It could happen that some users don’t operate the PC for some times.
Some of my clients have their business only open during the seasons.
Best regards,
Otto
Re: OT: Brainstorming: How to handle sw-updates
Posted:
Mon Dec 14, 2009 6:07 pm
by Otto
Hello José,
thank you.
I started today with the coding of the download client.
I found the nearly ready solution published by Biel.
But there are some open questions, too.
Best regards,
Otto
Re: OT: Brainstorming: How to handle sw-updates
Posted:
Mon Dec 14, 2009 6:09 pm
by James Bott
Otto,
Why so many EXEs? There was a time in the way past, when memory was such an issue that we had to resort to such things, but now you can run very large EXEs without problems.
>You must keep in mind that most of my clients have only 1 or 2 workstations and sometimes not always a dedicated fileserver.
Even without a dedicated server, you have to store the DBFs somewhere. If you store the EXEs in the same location and everyone runs the same EXEs then you don't have version problems.
To answer your question about insuring multiple EXE updates, you will need to check the date and time of all EXEs whenever the user starts any app EXE. This just seems like a lot of extra overhead and headache when using a single set of EXEs on a server (or workstation) solves the problem.
James
Re: OT: Brainstorming: How to handle sw-updates
Posted:
Mon Dec 14, 2009 6:31 pm
by Otto
Hello James,
Why so many EXEs?
I think it is more clearly arranged.
In my case what does a DMS have to do with billing or reservation of rooms?
And if there is an error the whole system is blocked. If you have for every task
different exe files maybe only a report or something not so important is broken.
This is only my personal view and also only valid for my special situation.
when using a single set of EXEs on a server (or workstation) solves the problem
This would be really an advantage but as I said I am to fare away from that.
Best regards,
Otto
Re: OT: Brainstorming: How to handle sw-updates
Posted:
Mon Dec 14, 2009 6:37 pm
by Otto
Hello James,
May I ask what size such a unique exe more or less would have.
A total update of all my files is about 60 MB.
Best regards,
Otto
Re: OT: Brainstorming: How to handle sw-updates
Posted:
Mon Dec 14, 2009 8:39 pm
by James Bott
Otto,
Wow, 60MB!
I have some very large apps that are less than 2MB. They contain complete business systems: sales orders, purchase orders, invoicing, inventory, customers, vendors, and about 25 reports or so.
Of course, all my apps are written OOP style. This means I use a lot less code. E.G. an invoice is a subclass of a sales order. The sales order class is about 2800 lines of code (it is a very complex sales order), but the invoice is only 150 lines of code.
Also, keep in mind that there is a lot of overhead code in every EXE, so if you combine two 1MB EXEs you might get an 11MB EXE.
Resources in the EXEs will, of course, also create much larger EXEs.
How many EXEs do you have? You might (just as a test) try compiling a couple of them together just to see what the combined size is.
Regards,
James
Re: OT: Brainstorming: How to handle sw-updates
Posted:
Mon Dec 14, 2009 9:32 pm
by Otto
Hello James,
I see. My single exe’s are between 2 and 3 MB of size. But then there are word-templates with pictures, FastReport.dll, VRD and some bmp files.
Best regards,
Otto
Re: OT: Brainstorming: How to handle sw-updates
Posted:
Tue Dec 15, 2009 1:51 am
by TimStone
Otto,
I have three files that I want to update. One stays on the server ( to manage data ), and two go to the workstations ( the client .exe and the .chm file ). The files, individually, are 6.7 MB, 8.1 MB and 5.5 MB. However, when they are zipped for upload to my server, they are a total of 9.0 MB.
My client management software takes the latest version of the software, and puts into the one update file, then copies it to my host server.
My update system downloads the latest copy, unzips it, and places it in the proper locations.
I do not try to support multiple updates /version. I make the latest version available and everyone who is eligible gets it. If they don't pay for support, they won't get it. How much they use the system isn't important. If they shut it off for a few weeks, when they get back online, it will grab the latest version. I deserve to get paid because I'm still working on their behalf even if they didn't get an update or two ... everything is cumulative.
Also, I don't want to be supporting various versions. If a client is running an older version, and has a problem, the update will surely have the fix unless it is 100% user error. So the answer to their need is to install the update.
Tim
Re: OT: Brainstorming: How to handle sw-updates
Posted:
Tue Dec 15, 2009 9:13 pm
by Otto
Flowchart “Update”[*]Version- Check
[*]Check for and remove non finished updates
[*]Check if all files to update are closed
[*]Create sign "Update Running"
[*]Create download folder
[*]Download ZIP file
[*]extract / unzip
[*]move current files to archive folder and copy new files into current folder
[*]retry function with msg which error occurs and how to handle it
[*]remove temp files
[*]remove sign "Update Running”
This way I plan to make my new update client.
I post what I have so far.
Best regards,
Otto
- Code: Select all Expand view
/*
Updating applications via FTP
(c) 2008 Biel Maimo bmaimo@gmail.com - bielsys.blogspot.com
FileTimes. basado sobre fuente publicacdo en foro FiveWin por Manuel Mercado
Time2Sec De contrib de Harbour HbCt
*/#include "FiveWin.ch"#INCLUDE "BOX.CH"#INCLUDE "FILEIO.CH"#INCLUDE "directry.ch"STATIC cDirlocal
//Directorio local del ejecutableSTATIC cDstZip :=
""STATIC cDirTempInstall :=
""STATIC cDirArchiv :=
""//----------------------------------------------------------------------------//FUNCTION Main
() local cIp :=
"" local cUser :=
"" local cPW :=
"" local cFTPFolder :=
"" //'/UpdFtp/' local cZIPFile :=
"" *----------------------------------------------------------
//FTP - Setup cIp :=
'ftp.asdfa-software.com' cUser :=
"so" cPW :=
"adfasd" cFTPFolder :=
"/asdfaso/downloads/" cZIPFile :=
"xCrmUpdate.zip" *----------------------------------------------------------
//local Setup cDstZip :=
"c:\x_zipUpdate" cDirTempInstall :=
"c:\x_tempInstall" cDirLocal :=
"c:\x_test" cDirArchiv :=
"c:\x_archiv" if lIsDir
( cDirLocal
) =.F.
lMKDir
( cDirLocal
) endif memowrit
( cDirLocal +
"\" + "upDateRunning.txt
", dtoc(date()) + " " + time() )
if lIsDir( cDstZip ) =.F.
lMKDir( cDstZip )
endif
if lIsDir( cDirTempInstall ) =.F.
lMKDir( cDirTempInstall )
endif
ChkUpdFtp( cIp, cFTPFolder , cUser, cPW ,cZIPFile )
Unzip( cDstZip, cDirTempInstall, cZIPFile )
InstallFiles( cDirTempInstall )
DELETEDIR( cDirTempInstall )
// DELETEDIR( cDirArchiv )
ferase (cDirLocal + "\
" + "upDateRunning.txt
")
MsgInfo('Update succesful')
RETURN NIL
//----------------------------------------------------------------------------//
function InstallFiles( cDirTempInstall )
LOCAL oDlg, oSay, lOk:=.F.
DEFINE DIALOG oDlg TITLE "Updating application
" FROM 0,0 TO 10,50
@ 1.5,03 SAY oSay PROMPT "Copied bytes:
" OF oDlg
oDlg:bStart := { || lOk := dir_recurs2( cDirTempInstall, oSay ), oDlg:END() }
ACTIVATE DIALOG oDlg CENTERED
RETURN NIL
//----------------------------------------------------------------------------//
function Unzip( cSrc, cDst, cZIPFile)
local DCOM
cSrc := cSrc + "\
" + cZIPFile
DCOM := '7Z.exe x ' + cSrc + " -o
" + cDst + " *.* -r
"
WAITRUN(DCOM,1)
SYSREFRESH()
return nil
//----------------------------------------------------------------------------//
STATIC FUNCTION ChkUpdFtp( cIp, cFTPFolder, cUser, cPW, cZIPFile )
LOCAL oInternet, oFtp
LOCAL nSize2
LOCAL aFiles := {}
IF !Empty(cIP) .AND. !Empty(cFTPFolder) //.AND. File( cDirLocal + 'ActVer.exe' )
oInternet := tInternet():New()
oFTP := TFTP():New( cIp, oInternet,cUser, cPW )
IF !Empty( oFtp:hFtp)
FtpSetCurrentDirectory(oFTP:hFTP, cFTPFolder )
aFiles = oFTP:Directory( cZIPFile )
nSize2:= aFiles[1,2]
IF Len(aFiles)>0
IF MsgYesno( 'A new version is available, Download it?' )
IF fileUpdate(oFtp,Lower(aFiles[1,1]),aFiles[1,1],nSize2)
oFtp:END()
oInternet:END()
ENDIF
ENDIF
ENDIF
ENDIF
oFtp:END()
oInternet:END()
ENDIF
RETURN NIL
//----------------------------------------------------------------------------//
STATIC FUNCTION fileUpdate(oFtp,cSource,cFileName,nSize)
LOCAL oDlg, oSay, oBtnCancel, oMeter, lEnd:=.F., nAmount, lOk:=.F., lValRet:=.F.
DEFINE DIALOG oDlg TITLE "Updating application
" FROM 0,0 TO 10,50
@ 0.5,03 SAY oSay PROMPT "Copied bytes:
" OF oDlg
@ 02,01 METER oMeter VAR nAMount SIZE 180,20 TOTAL nSize OF oDlg
@ 03,12 BUTTON oBtnCancel PROMPT "&Cancelar
" ACTION ( lEnd := .t., SysRefresh(), oDlg:End() )
oDlg:bStart := { || lOk := GetFile( cSource, nSize, oSay, oMeter, @lEnd, oDlg, oFTP ),;
oBtnCancel:SetText( "&Ok
" ) ,oDlg:END() }
ACTIVATE DIALOG oDlg CENTERED
IF !lEnd .AND. lOk
lValRet:=.T.
ENDIF
RETURN lValRet
//----------------------------------------------------------------------------------------
STATIC FUNCTION GetFile( cSource, nSize, oSay, oMeter, lEnd, oDlg, oFtp )
LOCAL oFile, hTarget, lValRet:=.F.
LOCAL nBufSize,cBuffer,nBytes, nTotal:=0,nFile:=0
*----------------------------------------------------------
nBufSize := 4096
cBuffer := Space( nBufSize )
* oMeter:nTotal := nSize
hTarget := FCreate( cDstZip + '' + cSource )
oFile := tFtpFile():New( cSource, oFtp )
oFile:OpenRead()
SysRefresh()
WHILE ( nBytes := Len( cBuffer := oFile:Read( nBufSize ) ) ) > 0 .and. ! lEnd
FWrite( hTarget, cBuffer, nBytes )
oSay:SetText( "Bytes copiados:
" + ;
AllTrim( Str( nTotal += nBytes ) ) )
oMeter:Set( nTotal )
SysRefresh()
END
FClose( hTarget )
oFile:End()
RETURN nTotal == nSize
//----------------------------------------------------------------------------//
static function dir_recurs2( cPath, oSay )
local x
local aFiles := directory( cPath + '\*.*', 'D' )
local nFilCount := len( aFiles )
local cZiel := ""
local cTarget := ""
*----------------------------------------------------------
for x := 1 to nFilCount
if aFiles[ X, F_NAME ] <> '..'
if 'D' $ aFiles[ X, F_ATTR ]
makeDirectory2( cPath )
endif
if aFiles[ X, F_ATTR ] <> 'D'
cZiel := STRTRAN( cPath + "\
" + aFiles[ X, F_NAME ], cDirTempInstall, cDirLocal )
cTarget := STRTRAN( cPath + "\
" + aFiles[ X, F_NAME ], cDirTempInstall, cDirArchiv )
oSay:SetText( "Bytes copiados:
" + aFiles[ X, F_NAME ] )
if file( cZiel ) = .t. // only if a file is present
MOVEFILE( cZiel , cTarget )
endif
IF filcopyraw( cPath + "\
" + aFiles[ X, F_NAME ], cZiel )= .f.
Msginfo( "Error beim
Update" + CRLF + ;
"Source
" + cPath + "\
" + aFiles[ X, F_NAME ]+ CRLF + ;
"Destination
" + cZiel )
endif
endif
endif
if 'D' $ aFiles[ X, F_ATTR ]
if aFiles[ X, F_NAME ] <> '.'
dir_recurs2( cPath + '' + aFiles[ X, F_NAME ], oSay )
endif
endif
next
return NIL
//----------------------------------------------------------------------------//
function makeDirectory2( cPfad )
local n, cResult := "", nTimes := StrCharCount( cPfad, "\
" ) + 1
local cDst := ""
if lIsDir( cDirArchiv) = .F.
lMKDir(cDirArchiv)
endif
if lIsDir( cDirLocal) = .F.
lMKDir(cDirLocal)
endif
for n = 1 to nTimes
cResult := StrToken( cPfad, n, "\
" )
if n > 2 //ohne Wurzel
cDst := cDst + "\
" + cResult
if lIsDir( cDirArchiv + cDst) = .F.
lMKDir(cDirArchiv + cDst)
endif
if lIsDir( cDirLocal + cDst) = .F.
lMKDir( cDirLocal + cDst)
endif
endif
next
return nil
//----------------------------------------------------------------------------//
function filcopyraw( cSrc, cDst ) //from NagesWaraRao
local hSrc, hDst, nBytes
local nBuf := 64000
local cBuf := Space( nBuf )
local lCopied := .f.
if ( hSrc := FOpen( cSrc, 64 ) ) >= 0
if ( hDst := FCreate( cDst, 0 ) ) >= 0
do while .t.
nBytes := FRead( hSrc, @cBuf, nBuf )
if nBytes > 0
FWrite( hDst, cBuf, nBytes )
endif
if nBytes < nBuf
lCopied := .t.
exit
endif
enddo
fClose( hDst )
endif
fClose( hSrc )
endif
return lCopied
//----------------------------------------------------------------------------//
FUNCTION DELETEDIR( cDir )
LOCAL aDir, cName
LOCAL i
aDir = DIRECTORY( cDir + "\*.*
", "HRD
" )
FOR i = 1 TO LEN( aDir )
cName = aDir[ i, F_NAME ]
IF cName == ".
"; LOOP; ENDIF
IF cName == "..
"; LOOP; ENDIF
cName = cDir + "\
" + cName
IF "D
" $ aDir[ i, F_ATTR ]
IF !DELETEDIR( cName )
RETURN .F.
ENDIF
ELSE
IF FERASE( cName ) = -1
? "File cannot be deleted
" + cName + ".
"
RETURN .F.
ENDIF
ENDIF
NEXT
IF !LRMDIR( cDir )
? "Folder cannot be deleted
" + cDir + ".
"
RETURN .F.
ENDIF
RETURN .T.
//----------------------------------------------------------------------------//
DLL32 FUNCTION MOVEFILE( cExistingFileName AS LPSTR, cNewFileName AS LPSTR ) AS BOOL PASCAL FROM "MoveFileA
" LIB "kernel32.dll
"
//----------------------------------------------------------------------------//
#pragma BEGINDUMP
#include <Windows.h>
#include <mapiwin.h>
#include <hbApi.h>
//nTime 1=Last Update, 2=Last Acces, 3=Creation, defecto last update
HB_FUNC( FILETIMES ) // params cFileName, nTime --> { nYear, nMonth, nDay, nHour, nMin, nSec }
{
LPSTR cFileName = hb_parc( 1 ) ;
int nTime = ( ISNUM( 2 ) ? hb_parni( 2 ) : 1 ) ; // defaults to 1
FILETIME ftCreate, ftAccess, ftWrite ;
SYSTEMTIME stTime ;
BOOL bRet ;
HANDLE hFile = CreateFile( cFileName, GENERIC_READ, FILE_SHARE_READ, 0, OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, 0 ) ;
if( ! hFile )
return ;
GetFileTime( (HANDLE) hFile, &ftCreate, &ftAccess, &ftWrite ) ;
switch( nTime )
{
case 1 : // last update
FileTimeToSystemTime( &ftWrite, &stTime ) ;
break ;
case 2 : // last access
FileTimeToSystemTime( &ftAccess, &stTime ) ;
break ;
case 3 : // creation
FileTimeToSystemTime( &ftCreate, &stTime ) ;
break ;
default : // last update
FileTimeToSystemTime( &ftWrite, &stTime ) ;
break ;
}
SystemTimeToTzSpecificLocalTime( NULL, &stTime, &stTime ) ;
CloseHandle( hFile ) ;
hb_reta( 6 ) ;
hb_storni( stTime.wYear, -1, 1 ) ;
hb_storni( stTime.wMonth, -1, 2 ) ;
hb_storni( stTime.wDay, -1, 3 ) ;
hb_storni( stTime.wHour, -1, 4 ) ;
hb_storni( stTime.wMinute, -1, 5 ) ;
hb_storni( stTime.wSecond, -1, 6 ) ;
}
#define FA_RDONLY 1 /* R */
#define FA_HIDDEN 2 /* H */
#define FA_SYSTEM 4 /* S */
#define FA_LABEL 8 /* V */
#define FA_DIREC 16 /* D */
#define FA_ARCH 32 /* A */
#define FA_NORMAL 0
HB_FUNC(FILESIZE)
{
LPCTSTR szFile;
DWORD dwFlags=FILE_ATTRIBUTE_ARCHIVE;
HANDLE hFind;
WIN32_FIND_DATA hFilesFind;
int iAttr;
if (hb_pcount() >=1){
szFile=hb_parc(1);
if (ISNUM(2)) {
iAttr=hb_parnl(2);
}
else{
iAttr=63;
}
if( iAttr & FA_RDONLY )
dwFlags |= FILE_ATTRIBUTE_READONLY;
if( iAttr & FA_HIDDEN )
dwFlags |= FILE_ATTRIBUTE_HIDDEN;
if( iAttr & FA_SYSTEM )
dwFlags |= FILE_ATTRIBUTE_SYSTEM;
if( iAttr & FA_NORMAL )
dwFlags |= FILE_ATTRIBUTE_NORMAL;
hFind = FindFirstFile(szFile,&hFilesFind);
if (hFind != INVALID_HANDLE_VALUE){
if (dwFlags & hFilesFind.dwFileAttributes) {
if(hFilesFind.nFileSizeHigh>0)
hb_retnl((hFilesFind.nFileSizeHigh*MAXDWORD)+hFilesFind.nFileSizeLow);
else
hb_retnl(hFilesFind.nFileSizeLow);
}
else
hb_retnl(-1);
}
}
}
#pragma ENDDUMP