'
'  PROJECT:  cliSocket.exe
'            In conjunction with svrSocket, test socket comms.
'            (Uses my QSocketEx.inc for additional fns)
'
'  COPYME:
'            This code is provided "free" to be used by anyone
'            provided that it, or any derivative works, is NOT SOLD
'            OR TRADED FOR FINANCIAL GAIN OF ANY KIND.
'            This source code, AND any derivative works based on this
'            source code, may be modified and/or distributed freely
'            providing that this notice is included in, or with,
'            such distribution.
'  DISCLAIMER:
'            As this is "free" code, NO liabilty of ANY KIND whatsoever,
'            can be placed on the original author.
' 
'  CREDITS:  EL SUPREMO ... William YU
'            AND a heap of others?
'                   
'  AUTHOR:   d_homans@yahoo.com.au
'  DATE:     05/06/2007
'  VERSION:  0.0003
'
'  NOTES:    The QTIMER is ONLY used to delay the client requesting
'            service from the server, it is not used for any comms fns.
'            The code not dealing with actual comms has been moved to
'            the end of the file - to get it out of the way.
'
'            The application layer protocol for this demo has been kept ultra simple.
'            Client alternates with one of two requests:
'                  "GET TEXT" or "GET DATA" (8 bytes)
'            Server responds with a header block (14 bytes)
'                  ascii file size (6 bytes) + ascii timestamp (8 bytes)
'            Then sends either the text or data (bmp) file.  
'
$APPTYPE GUI
$TYPECHECK ON
$OPTIMIZE ON
$ESCAPECHARS OFF

$INCLUDE "Rapidq2.inc"
$INCLUDE "QSocketEx.inc"

CONST VERS="v0.0003"                                           ' Nearly there!
CONST CRLF=CHR$(13)+CHR$(10)
CONST DEFAULTSERVER="127.0.0.1"
CONST DEFAULTPORT=2000                                         ' Anything above 1024?
CONST PACKETMAXLENGTH=1024                                     ' An arbitrary value
CONST T_INTERVAL=250                                           ' Timer interval
'
' Forward declarations
DECLARE SUB Initialise
DECLARE SUB Control
DECLARE FUNCTION ConnectToServer() AS integer                  ' Returns True/False
DECLARE FUNCTION WaitForResponse() AS integer
DECLARE SUB DoComms
DECLARE SUB PeriodChange                                       ' Set interval to poll server
DECLARE SUB CfgMe
DECLARE SUB ExitCfg
DECLARE FUNCTION CheckIP(ip AS string) AS integer
DECLARE SUB Cleanup
'
DIM MyTimer AS QTimer
DIM Period AS integer
DIM Sock AS QSocketEx

DIM mySock AS integer
DIM ServerIP AS string
DIM ServerPort AS integer
DIM LastError AS long                                          ' Result of last comms fn
DIM Keepgoing AS integer                                       ' GP Flag
DIM Toggle AS integer                                          ' Flag for type of file to request

CREATE Form AS QFORM
    Height=350
    Width=400
    Center
    DelBorderIcons(2)                                          ' Disable maximize button
    BorderStyle=1                                              ' Fixed size window
    Caption=Application.Title+" "+VERS
    OnClose=Cleanup
    CREATE TxtBox AS QRICHEDIT
        Width=280
        Height=260
        Left=5
        Top=20
        PlainText=True
        ReadOnly=True
        ScrollBars=ssVertical
        HideSelection=False
    END CREATE
    CREATE but1 AS QBUTTON                                     ' START/STOP button
        Width=80
        Height=50
        Left=300
        Top=40
        Caption="START"
        OnClick=Control
    END CREATE
    CREATE but2 AS QBUTTON                                     ' Configure button
        Width=60
        Left=310
        Top=140
        Caption="Configure"
        OnClick=CfgMe
    END CREATE
    CREATE tBar AS QTRACKBAR
        Width=100
        Height=20
        Top=215
        Left=290
        Min=0
        Max=40                                                 ' 40xT_INTERVAL mSecs (range 10 secs)
        Frequency=4
        PageSize=1
        SelStart=0
        SelEnd=40
        Position=4                                             ' 4xT_INTERVAL mSecs (default 1 sec)
        onChange=PeriodChange
    END CREATE
    CREATE lbl1 AS QLABEL
        Width=100
        Height=20
        Top=200
        Left=310
        Caption="Polling Rate"
    END CREATE
    CREATE lbl2 AS QLABEL
        Height=20
        Top=238
        Left=310
        Autosize=True
        Caption="1 Second"                                     ' Default as above
    END CREATE
    CREATE exitBut AS QBUTTON
        Width=60
        Left=310
        Top=270
        Caption="EXIT"
        OnClick=Cleanup
    END CREATE
    CREATE CfgPan AS QPANEL
        Width=200
        Height=150
        Left=(Form.ClientWidth-CfgPan.Width)\2
        Top=(Form.ClientHeight-CfgPan.Height)\2
        BevelWidth=2
        Visible=False
        CREATE Lbl3 AS QLABEL
            Width=50
            Left=10
            Top=35
            Alignment=1                                        ' Right justify
            Caption="Server IP"
        END CREATE
        CREATE Txt1 AS QEDIT
            Width=100
            Left=70
            Top=30
            EditText=DEFAULTSERVER
        END CREATE
        CREATE Lbl4 AS QLABEL
            Width=50
            Left=10
            Top=65
            Alignment=1                                        ' Right justify
            Caption="Port"
        END CREATE
        CREATE Txt2 AS QEDIT
            Width=100
            Left=70
            Top=60
            EditText=str$(DEFAULTPORT)
        END CREATE
        CREATE but3 AS QBUTTON
            Left=(CfgPan.Width-but3.Width)\2
            Top=110
            Caption="Done"
            OnClick=ExitCfg
        END CREATE
    END CREATE
END CREATE

Initialise

Form.ShowModal

'
'**********************   COMMS stuff *******************************************
'
SUB Control
    IF but1.Caption="START" THEN
        but1.Caption="STOP"
        but2.Enabled=False                                     ' Disable configure button
        TxtBox.Addstring TIME$+" STATUS: Client polling server"
        MyTimer.Interval=Period
        MyTimer.Enabled=True
        Keepgoing=True
    ELSE
        but1.Caption="START"
        but2.Enabled=True                                      ' Allow re-config
        Keepgoing=False
    END IF
END SUB

FUNCTION ConnectToServer()
DIM retries AS integer

    FOR retries=1 TO 3
        mySock=Sock.Connect(ServerIP,ServerPort)               ' Inbuilt delay waiting connect
        IF mySock=-1 THEN
            TxtBox.Addstring TIME$+" STATUS: Retrying connection to server"
        ELSE
            EXIT FOR
        END IF
    NEXT Retries
    IF mySock=-1 THEN
        TxtBox.Addstring TIME$+" STATUS: Failed connection to server!"
        Result=False
    ELSE
        TxtBox.Addstring TIME$+" STATUS: Connected to server "+Sock.GetHostName
        Result=True
    END IF
    
END FUNCTION

FUNCTION WaitForResponse()
DIM i AS integer

    Result=False                                           ' Default return fail
    FOR i=1 to 1000                                        ' Wait MIN of 10 secs for response
        IF Sock.IsServerReady(mySock) THEN EXIT FOR
        DoEvents                                           ' Could use a timer but prefer ..
        Sleep.MS 10                                        ' .. Yield to CPU
    NEXT i
    IF i<1000 THEN Result=True

END FUNCTION

SUB DoComms                                                    ' Called from Qtimer event
DIM HdrCount AS long                                           ' Count expected
DIM RxCount AS long                                            ' Count received
DIM ChkCount AS long                                           ' Check we Rx all data
DIM tmpstr AS string
DIM mem AS QMEMORYSTREAM

    MyTimer.Enabled=False
    IF ConnectToServer() THEN
        IF Toggle=True THEN                                    ' Toggle between 2 files
            Toggle=False
            tmpstr="GET TEXT"                                  ' Request text file
        ELSE
            Toggle=True
            tmpstr="GET DATA"                                  ' Request data file
        END IF        
        TxtBox.Addstring TIME$+" TX: "+tmpstr
        LastError=Sock.WriteEx(mySock,tmpstr,8)                ' Send request for file
        IF LastError>0 THEN
            TxtBox.Addstring " STATUS: Failed send request to server!"
            Control                                            ' MY test code halts .. yours???
            EXIT SUB
        END IF
        
        DoEvents
        TxtBox.Addstring TIME$+" RX: Waiting for response"
        IF WaitForResponse()=False THEN
            TxtBox.Addstring TIME$+" STATUS: NO response from server!"
            Control                                             ' MY test code halts .. yours???
            EXIT SUB                                            ' Get outa here
        END IF
        
        ' Server has responded, now check the hdr data.
	    ' Format of hdr (in this test code) is:
        ' 6 ascii chars (length of data) + 8 ascii chars (timestamp) = 14 data bytes
        tmpstr=Sock.Read(mySock,14)                            ' Get hdr info - SHOULD ..
        DoEvents                                               ' .. parse the hdr for sanity?
        HdrCount=VAL(Left$(tmpstr,6))
        IF HdrCount>0 THEN
            ChkCount=HdrCount
            TxtBox.Addstring TIME$+" RX: Hdr "+STR$(HdrCount)+" bytes - Time "+Right$(tmpstr,8)
            mem.Position=0                                     ' Reset mem
            RxCount=0
            WHILE HdrCount>0
                tmpstr=Sock.Read(mySock,PACKETMAXLENGTH)       ' Get available data from queue 
                mem.WriteStr(tmpstr,Sock.Transferred)          ' Store in memorystream
                HdrCount-=Sock.Transferred                     ' actual bytes taken from queue
                RxCount+=Sock.Transferred                      '         ditto
                DoEvents                                       ' Check for other events
                Sleep.MS 10                                    ' Yield CPU for a bit 
            WEND
            TxtBox.AddString TIME$+" RX: Captured "+STR$(RxCount)+" data bytes"
            IF ChkCount<>RxCount THEN
                TxtBox.Addstring TIME$+" RX: Download FAILED byte count check!"
            END IF
        ELSE
            TxtBox.Addstring TIME$+" RX: NO response from server"
        END IF
        Sock.Close(mySock)
        mySock=-1
        TxtBox.Addstring TIME$+" STATUS: Socket closed"
    END IF

    IF Keepgoing=True THEN
        MyTimer.Interval=Period                                    ' Reload - in case Delay period ..
        MyTimer.Enabled=True                                       ' .. changed on the fly!
    ELSE
        TxtBox.Addstring TIME$+" STATUS: Client Stopped!"
    END IF
    
END SUB

SUB CleanUp
    MyTimer.Enabled=False
    IF mySock <> -1 THEN
        Sock.Close(mySock)
    END IF
    Application.Terminate
END SUB
'
'*************************** Init stuff ***********************************
'
SUB Initialise

    TxtBox.Clear
    TxtBox.Addstring TIME$+" STATUS: Initialising"

    ServerIP=DEFAULTSERVER
    ServerPort=DEFAULTPORT
    mySock=-1
    Period=tBar.Position*T_INTERVAL

    MyTimer.Enabled=False
    MyTimer.OnTimer=DoComms

    'This makes the form correctly minimise
    SetWindowLong Form.Handle, -8, 0
    SetWindowLong Application.Handle, -8, Form.Handle

END SUB

SUB PeriodChange
DIM str AS string

    Period=tBar.Position*T_INTERVAL                            ' 0-10 seconds in 250 mSecs inc
    str=str$(Period)
    SELECT CASE Period
        CASE 0
            Period=20                                          ' FUDGE my logic wont work with 0
            Lbl2.Caption="  No Delay"
        CASE 250 TO 750
            Lbl2.Caption="0."+left$(str,2)+" Seconds"
        CASE 1000
            Lbl2.Caption="1.00 Second"
        CASE 10000
            Lbl2.Caption="10.00 Seconds"
        CASE ELSE
            Lbl2.Caption=left$(str,1)+"."+mid$(str,2,2)+" Seconds"
    END SELECT

END SUB
'
'*************************  Config stuff ****************************************
'
SUB CfgMe
DIM tmpstr AS string

    but1.Enabled=False
    but2.Enabled=False
    CfgPan.Visible=True
    Keepgoing=True
    WHILE Keepgoing=True
        DoEvents
    WEND
    CfgPan.Visible=False
    but1.Enabled=True
    but2.Enabled=True
    TxtBox.Clear
    tmpstr="SETTINGS:"+CRLF+"    Server IP: "+ServerIP+CRLF+"    Port: "+STR$(ServerPort)
    tmpstr=tmpstr+CRLF+"    Poll Interval: "+Lbl2.Caption
    TxtBox.AddString tmpstr

END SUB

SUB ExitCfg
DIM port AS integer

    IF checkIP(Txt1.EditText)=True THEN
        port=VAL(LTRIM$(Txt2.EditText))
        IF port>1023 AND port<65536 THEN
            ServerPort=port
            ServerIP=LTRIM$(RTRIM$(Txt1.EditText))
            Keepgoing=False                                    ' Get me outa here!
        ELSE
            MessageBox "Invalid port (Range: 1024-65535)","CamClient "+VERS,&H40
        END IF
    ELSE
        MessageBox "Invalid IP - Bad format","CamClient "+VERS,&H40
    END IF

END SUB

FUNCTION CheckIP(ip AS string) AS integer
' Check formatting of input IP address (can't trust some users!)
' NB This is not a "foolproof" test of ALL possible input!
DIM i AS integer, j AS integer
DIM st AS integer, nd AS integer
DIM tmpstr AS string

    tmpstr=LTRIM$(RTRIM$(ip))
    i=LEN(tmpstr)
    IF i<7 OR i>15 THEN                                        ' A quick check first
        CheckIP=False
        EXIT FUNCTION
    END IF
    tmpstr=tmpstr+"."                                          ' Cheat a little!
    nd=0
    FOR i=1 TO 4                                               ' Do all 4 grps
        st=nd+1
        nd=INSTR(st,tmpstr,".")
        IF (nd-st<1) OR (nd-st>3) THEN                         ' Bad format?
            CheckIP=False
            EXIT FUNCTION
        END IF
        FOR j=st TO (nd-1)
            SELECT CASE MID$(tmpstr,j,1)
                CASE "0" TO "9"
                    ' ok
                CASE ELSE
                    CheckIP=False
                    EXIT FUNCTION
            END SELECT
        NEXT j
        j=VAL(MID$(tmpstr,st,(nd-st)))
        IF j>255 THEN
            CheckIP=False
            EXIT FUNCTION
        END IF
    NEXT i
    CheckIP=True

END FUNCTION
