new version of Snapshot Server

Started by kdmoyers, September 15, 2020, 11:16:32 AM

Previous topic - Next topic

kdmoyers

There's an article in the tech database for "Snapshot Server" that sometimes gets looked at.
I've just realized that it has an error, because I think a parameter got added to sRecvBinary.
It is NOT good code, but it does illustrate a cool trick for making your own webserver.
Dunno if this is worth fixing, but in case anyone cares, here's a fixed version below.  Maybe
replace the code in the tech database?

Code (winbatch) Select
; SnapShotServer.wbt
;
; This serves up (via HTTP) a gif image of whats on the screen
;

    portnumber = 17072

    ;;;based on the LISTENER script from the hlp file
    AddExtender("wwwsk34i.dll")
    AddExtender("wwctl44i.dll")
    AddExtender("WWIMG44I.DLL")

    IntControl(12,1+4,0,0,0)  ; exit without complaint

    title = "SnapShotServer"

    GoSub localdefs

    BoxesUp("650 800 999 950",@NORMAL)
    BoxTitle(title)
    BoxButtonDraw(1,1,"Exit","000 500 999 999")

    listensocket=sOpen()
    sListen(listensocket,portnumber)
    Served=0
    While 1
       datasocket=sAccept(listensocket,@FALSE)  ; NO Block for a connection
       If datasocket
           GoSub ProcessRequest
       Else
          err = wxGetLastErr()
          If err != @SERRNOCONN
            sClose(listenSocket)
            Message("Socket Error %err%",wxGetErrDesc(err))
            Exit
          EndIf
      EndIf
      TimeDelay(1.0)
      If BoxButtonStat(1,1) Then Goto CANCEL
    EndWhile
    Exit

    :CANCEL
    sClose(listenSocket)
    BoxText("Normal exit.")
    TimeDelay(0.5)
    Exit


:ProcessRequest
    Served=Served+1

    binbuf = BinaryAlloc( 1000 )
    BinaryEodSet( binbuf, 1000 )
    binaddr = IntControl( 42, binbuf, 0, 0, 0 )

    rqst=sRecvBinary(datasocket,binaddr,1000)
    ; rqst is the requested URL, but we dont
    ; actually use it here.

    GoSub dosnap

    tf2s = FileSize(tf2)
    bb = BinaryAlloc(tf2s)
    BinaryRead(bb,tf2)
    bbh = IntControl(42,bb,0,0,0)
    guy = wxGetInfo(2,datasocket)

    now = udfYmdHmsToHTTPStamp(TimeYmdHms(),1)
    sSendLine(datasocket,"HTTP/1.1 200 OK")
    sSendLine(datasocket,StrCat("Date: ",now))
    sSendLine(datasocket,StrCat("Expires: ",now))
    sSendLine(datasocket,StrCat("Content-Type: ","image/gif"))
    sSendLine(datasocket,StrCat("Content-Length: ",tf2s))
    sSendLine(datasocket,"")

    sSendBinary(datasocket, bbh, tf2s)

    sClose(datasocket)

    BoxText(StrCat("served: ", served, "   last: ", guy,"   at: ",now))
    BoxTitle(StrCat(title,"  ", served))
    FileDelete(tf2)
Return

:dosnap
    tf1 = StrCat(Environment("TEMP"), "\SnapShotServer.bmp")
    tf2 = StrCat(Environment("TEMP"), "\SnapShotServer.gif")

    ;Takes a bitmap snapshot of the screen and pastes it to the clipboard.
    Snapshot(0)
    ;returns the size of buffer needed for a subsequent BinaryAlloc,
    ;but doesn't attempt to place the contents of clipboard into a buffer
    size=BinaryClipGet(0,8)
    ;allocates a data buffer
    bb=BinaryAlloc(size)
    ;read file format type CF_DIB
    BinaryClipGet(bb,8)
    ; need to add first 14 bytes to make it
    ; a BMP file format
    bmpdatasize=14
    bb2=BinaryAlloc(size + bmpdatasize)
    ;The characters identifying the bitmap.'BM'
    BinaryPokeStr(bb2, 0, "BM")
    ;Complete file size in bytes.
    BinaryPoke4(bb2,2,size + bmpdatasize)
    ;Reserved
    BinaryPoke4(bb2,6,0)
    ;Data offset
    headersize=BinaryPeek4(bb,0)
    dataoffset = headersize + bmpdatasize
    BinaryPoke4(bb2,10,dataoffset)
    BinaryCopy(bb2,bmpdatasize,bb,0,size)
    BinaryWrite(bb2,tf1)
    BinaryFree(bb)
    BinaryFree(bb2)

    ImgConvert(tf1,tf2) ; convert bmp to gif
    FileDelete(tf1)

    ; note that imgconvert makes a nonstandard GIF, but
    ; IE and FF and Chrome seem to display it OK

Return


:localdefs
    #DefineFunction udfYmdHmsToHTTPStamp (sYmdHms, iMode)
    iMode  = Min(5,Max(1,iMode)) ; Force iMode=1..5.
    sYear  = ItemExtract(1,sYmdHms,":")
    sMonth = ItemExtract(2,sYmdHms,":")
    sDay   = ItemExtract(3,sYmdHms,":")
    sHms   = StrSub(sYmdHms,12,8)
    sMName = ItemExtract(sMonth,"Jan,Feb,Mar,Apr,May,Jun,Jul,Aug,Sep,Oct,Nov,Dec",",")
    sDName = ItemExtract(1+((TimeJulianDay(sYmdHms)+5) mod 7),"Sunday,Monday,Tuesday,Wednesday,Thursday,Friday,Saturday",",")
    Select iMode
    Case 1
       sHTTPStamp = StrCat(StrSub(sDName,1,3),', ',sDay,' ',sMName,' ',sYear,' ',sHms,' GMT')
       Break
    Case 2
       sHTTPStamp = StrCat(sDName,', ',sDay,'-',sMName,'-',StrSub(sYear,3,2),' ',sHms,' GMT')
       Break
    Case 3
       sHTTPStamp = StrCat(StrSub(sDName,1,3),' ',sMName,' ',StrFixLeft(0+sDay,' ',2),' ',sHms,' ',sYear)
       Break
    Case 4
       sHTTPStamp = StrCat(sYear,'-',sMonth,'-',sDay,'T',sHms)
       Break
    Case 5
       sHTTPStamp = StrCat(sYear,sMonth,sDay,'T',StrReplace(sHms,":",""))
       Break
    EndSelect
    Return (sHTTPStamp)
    ;...
    ; This function udfYmdHmsToHTTP (sYmdHms, iMode) returns a string representation of date/time stamps for HTTP applications.
    ;
    ; iMode=1 : "Sun, 06 Nov 1994 08:49:37 GMT"  ; RFC 822, updated by RFC 1123
    ; iMode=2 : "Sunday, 06-Nov-94 08:49:37 GMT" ; RFC 850, obsoleted by RFC 1036
    ; iMode=3 : "Sun Nov  6 08:49:37 1994"       ; ANSI C's asctime() format
    ; iMode=4 : "1994-11-06T08:49:37"            ; ISO 8601 (European Standard EN 28601) (see altered German standard DIN 5008)
    ; iMode=5 : "19941106T084937"                ; ISO 8601 compacted
    ;
    ; The first format is preferred as an Internet standard and
    ; represents a fixed-length subset of that defined by RFC 1123 (an update to RFC 822).
    ; The second format is in common use, but is based on the obsolete RFC 850 date format and lacks a four-digit year.
    ;
    ; HTTP/1.1 clients and servers that parse the date value MUST accept
    ; all three formats (iMode=1..3) (for compatibility with HTTP/1.0), though they MUST
    ; only generate the RFC 1123 (iMode=1) format for representing HTTP-date values in header fields.
    ;
    ; All HTTP date/time stamps MUST be represented in Greenwich Mean Time (GMT) without exception.
    ; For the purposes of HTTP, GMT is exactly equal to UTC (Coordinated Universal Time).
    ; This is indicated in the first two formats by the inclusion of "GMT"
    ; as the three-letter abbreviation for time zone, and MUST be assumed when reading the asctime format.
    ; HTTP-date is case sensitive and MUST NOT include additional linear white spaces.
    ;...
    #EndFunction

Return
The mind is everything; What you think, you become.

td

Thanks for reporting the problem. The exmaple has several other minor problems.  The Pixie extender has been retired for one and the the time conversion business can be replaced with the TimeFormat function.
"No one who sees a peregrine falcon fly can ever forget the beauty and thrill of that flight."
  - Dr. Tom Cade

kdmoyers

<<  time conversion business can be replaced >>

Yep, I should do that.  That's some ugly code in there.

<<The Pixie extender has been retired>>

Ooch. that one's worse. I may look at it, no promises.
The mind is everything; What you think, you become.

td

Will get the Tech article updated with your posted code in the near future.
"No one who sees a peregrine falcon fly can ever forget the beauty and thrill of that flight."
  - Dr. Tom Cade

td

It's been updated.  Let me know what we screwed up.
"No one who sees a peregrine falcon fly can ever forget the beauty and thrill of that flight."
  - Dr. Tom Cade

kdmoyers

I found the C# below that will supposedly convert BMP to GIF, but my C# is like my German. 
Eins zwei drei... and things get foggy after that. (grin)

I may puzzle on how to turn this into a #definefunction

Code (csharp) Select
static void Main(string[] args)
{
    Bitmap myBitmap=new Bitmap(@"test.bmp");

    ImageCodecInfo myImageCodecInfo = GetEncoderInfo("image/gif"); ;

    EncoderParameter encCompressionrParameter
= new EncoderParameter(System.Drawing.Imaging.Encoder.Compression, (long)EncoderValue.CompressionLZW); ;
    EncoderParameter encQualityParameter
= new EncoderParameter(System.Drawing.Imaging.Encoder.Quality, 0L);
    EncoderParameters myEncoderParameters
= new EncoderParameters(2);
    myEncoderParameters.Param[0] = encCompressionrParameter;
    myEncoderParameters.Param[1] = encQualityParameter;

    myBitmap.Save("Output.gif", myImageCodecInfo, myEncoderParameters);
}

private static ImageCodecInfo GetEncoderInfo(String mimeType)
{
    int j;
    ImageCodecInfo[] encoders;
    encoders = ImageCodecInfo.GetImageEncoders();
    for (j = 0; j < encoders.Length; ++j)
    {
        if (encoders[j].MimeType == mimeType)
            return encoders[j];
    }
    return null;
}
The mind is everything; What you think, you become.

kdmoyers

OK, I'm stuck on this bit of C#:
Code (csharp) Select
    ImageCodecInfo[] encoders;
    encoders = ImageCodecInfo.GetImageEncoders();


I've 100 variations on
Code (winbatch) Select
  myarray = objectclrnew('System.Drawing.GetImageEncoders') but nothing seems to work -- I always get "CLR type name not found"

What is this newbie doing wrong?

(doc seems to be https://docs.microsoft.com/en-us/dotnet/api/system.drawing.imaging.imagecodecinfo.getimageencoders?view=dotnet-plat-ext-3.1#System_Drawing_Imaging_ImageCodecInfo_GetImageEncoders)

-Kirby
The mind is everything; What you think, you become.

ChuckC

GetImageEncoders() is a static class method, not a type.  It returns an array of ImageCodecInfo objects.

https://docs.microsoft.com/en-us/dotnet/api/system.drawing.imaging.imagecodecinfo.getimageencoders?view=dotnet-plat-ext-3.1

When looking to port C# / .NET code, always refer to Microsoft's documentation for the declaration of things like class methods and enum values so that you understand how to make use of them.  Once you have that understanding, porting code to WinBatch is much easier.

kdmoyers

Yeah, I've been staring at exactly that page,
but I just can't figure out what lines of winbatch that would translate to.

I've got this
Code (winbatch) Select
myarray = objectclrnew('System.Drawing.Imaging.ImageCodecInfo','GetImageEncoders()')
but it fails with "CLR type creation failed",   and
Code (winbatch) Select
  myarray = objectclrnew('System.Drawing.ImageCodecInfo','GetImageEncoders()')
which failed with "CLR type name not found"

The mind is everything; What you think, you become.

td

I can get the codec to work but the  'System.Drawing.Imaging.EncoderParameters' business is a bit bulky.  Here is a much simpler approach that lacks the fine-tuning of the compression algorithm:

Code (winbatch) Select
; Load assemblies.
ObjectClrOption('useany', 'System')
ObjectClrOption('useany', 'System.Drawing')
; Bitmap to load.
strIn = 'c:\temp\Hawaii.bmp' ; Big waves on the shores of Kauai.

; Gif file as output.
strOut = 'c:\temp\Output.Gif'

; Load the input bitmap.
objBitmap  = ObjectClrNew('System.Drawing.Bitmap', strIn)

Guid = ObjectClrNew('System.Guid',  'B96B3CB0-0728-11D3-9D7B-0000F81EF32E') ; GIF guid.
objFormat = ObjectClrNew('System.Drawing.Imaging.ImageFormat',Guid)

; Convert and write to disk.
objBitmap.Save(strOut, objFormat.Gif)

exit
"No one who sees a peregrine falcon fly can ever forget the beauty and thrill of that flight."
  - Dr. Tom Cade

kdmoyers

Bless you Tony.

I was thinking that there might be some steps that could be skipped, at the cost of being less-than-official. I bet that GUID isn't going to change much.

I really am going to learn more C# I promise.  Its tough on us old dogs. (grin)

Thanks man!
-Kirby



The mind is everything; What you think, you become.

td

I had to use the GUID because the class's only constructor requires one.   The GIF GUID value returned by the "Gif" property is the same value as the GUID value dropped into the constructor but it is actually defined by the class internally.  You could likely use any valid image mime type GUID in the constructor as still get the correct result.  Confusing I know but it all works out in the end.
"No one who sees a peregrine falcon fly can ever forget the beauty and thrill of that flight."
  - Dr. Tom Cade

kdmoyers

OK, Here's snapshotserver all cleaned up and working.

Code (winbatch) Select
; SnapShotServer.wbt
;
; This serves up (via HTTP) a gif image of whats on the screen
;

    portnumber = 17072

    ;;;based on the LISTENER script from the hlp file
    AddExtender("wwwsk34i.dll")

    ObjectClrOption('useany', 'System')
    ObjectClrOption('useany', 'System.Drawing')

    IntControl(12,1+4,0,0,0)  ; exit without complaint

    ; temp files used for snapshot
    tf1 = StrCat(Environment("TEMP"), "\SnapShotServer.bmp")
    tf2 = StrCat(Environment("TEMP"), "\SnapShotServer.gif")

    title = "SnapShotServer"

    BoxesUp("650 800 999 950",@NORMAL)
    BoxTitle(title)
    BoxButtonDraw(1,1,"Exit","000 500 999 999")
    BoxDataTag(1,"AAA")

    listensocket=sOpen()
    sListen(listensocket,portnumber)
    Served=0
    While 1
       datasocket=sAccept(listensocket,@FALSE)  ; NO Block for a connection
       If datasocket
           GoSub ProcessRequest
       Else
          err = wxGetLastErr()
          If err != @SERRNOCONN
            sClose(listenSocket)
            Message("Socket Error %err%",wxGetErrDesc(err))
            Exit
          EndIf
      EndIf
      TimeDelay(1.0)
      If BoxButtonStat(1,1) Then Goto CANCEL
    EndWhile
    Exit

    :CANCEL
    sClose(listenSocket)
    BoxText("Normal exit.")
    TimeDelay(0.5)
    Exit


:ProcessRequest
    rqst1=sRecvLine(datasocket,250)
    if strlen(rqst1) == 0 || strindexnc(rqst1,"favicon",1,0)
      sClose(datasocket)
      return
    endif

    BoxText("working...":rqst1)
    timedelay(0.4)
    Served=Served+1

    GoSub dosnap

    tf2s = FileSize(tf2)
    bb = BinaryAlloc(tf2s)
    BinaryRead(bb,tf2)
    bbh = IntControl(42,bb,0,0,0)
    guy = wxGetInfo(2,datasocket)

 
    now = timeformat(timeymdhms(),"ddd, dd MMM yyyy HH:mm:ss"):" GMT"
    sSendLine(datasocket,"HTTP/1.1 200 OK")
    sSendLine(datasocket,StrCat("Date: ",now))
    sSendLine(datasocket,StrCat("Expires: ",now))
    sSendLine(datasocket,StrCat("Content-Type: ","image/gif"))
    sSendLine(datasocket,StrCat("Content-Length: ",tf2s))
    sSendLine(datasocket,"")

    sSendBinary(datasocket, bbh, tf2s)

    sClose(datasocket)

    BoxDataClear(1,"AAA")
    BoxText(StrCat("served: ", served, "   last: ", guy,"   at: ",now))
    BoxTitle(StrCat(title,"  ", served))
    FileDelete(tf2)

    Return


:dosnap

    if fileexist(tf1)
      FileDelete(tf1)
    endif

    ;Takes a bitmap snapshot of the screen and pastes it to the clipboard.
    Snapshot(0)
    ;returns the size of buffer needed for a subsequent BinaryAlloc,
    ;but doesn't attempt to place the contents of clipboard into a buffer
    size=BinaryClipGet(0,8)
    ;allocates a data buffer
    bb=BinaryAlloc(size)
    ;read file format type CF_DIB
    BinaryClipGet(bb,8)
    ; need to add first 14 bytes to make it
    ; a BMP file format
    bmpdatasize=14
    bb2=BinaryAlloc(size + bmpdatasize)
    ;The characters identifying the bitmap.'BM'
    BinaryPokeStr(bb2, 0, "BM")
    ;Complete file size in bytes.
    BinaryPoke4(bb2,2,size + bmpdatasize)
    ;Reserved
    BinaryPoke4(bb2,6,0)
    ;Data offset
    headersize=BinaryPeek4(bb,0)
    dataoffset = headersize + bmpdatasize
    BinaryPoke4(bb2,10,dataoffset)
    BinaryCopy(bb2,bmpdatasize,bb,0,size)
    BinaryWrite(bb2,tf1)
    BinaryFree(bb)
    BinaryFree(bb2)

    ;;Replace this old code
    ;;ImgConvert(tf1,tf2) ; convert bmp to gif

    ;; with this:
    oBitmap = ObjectClrNew('System.Drawing.Bitmap',tf1)
    GifGuid = 'B96B3CB0-0728-11D3-9D7B-0000F81EF32E' ; Magic Number for GIF
    oGifGuid = ObjectClrNew('System.Guid',  GifGuid) ; turn into guid object
    oGifFormat = ObjectClrNew('System.Drawing.Imaging.ImageFormat',oGifGuid)
    oBitmap.Save(tf2, oGifFormat.Gif)
    oBitmap.Dispose()

Return

The mind is everything; What you think, you become.

ChuckC

Per Microsoft's documentation, GetImageEncoders() [note the parenthesis] is the name of a static method, not a data type.  You can't use WinBatch's objectclrnew() function with it.

IIRC, there was a discussion on here in a different thread earlier this year regarding a limitation in the WIL support for .NET static class methods.  The work around, IIRC, was to instantiate an object of the class type that implements the static method and then use that object instance to invoke the static method via the usual dot "." notation.


Quote from: kdmoyers on September 17, 2020, 09:10:12 AM
Yeah, I've been staring at exactly that page,
but I just can't figure out what lines of winbatch that would translate to.

I've got this
Code (winbatch) Select
myarray = objectclrnew('System.Drawing.Imaging.ImageCodecInfo','GetImageEncoders()')
but it fails with "CLR type creation failed",   and
Code (winbatch) Select
  myarray = objectclrnew('System.Drawing.ImageCodecInfo','GetImageEncoders()')
which failed with "CLR type name not found"

td

The ImageCodecInfo part of the C# code migth look something like this in a WinBatch script:

Code (winbatch) Select
; Find a codec.
strMimeType = "image/gif"
objEncoderInfo = ObjectClrNew('System.Drawing.Imaging.ImageCodecInfo')
aInfos = objEncoderInfo.GetImageDecoders()
nMax = ArrInfo(aInfos, 1) - 1
for i = 0 to nMax
    if aInfos[i].MimeType == strMimeType then objCodec = aInfos[i]
    then break
next
"No one who sees a peregrine falcon fly can ever forget the beauty and thrill of that flight."
  - Dr. Tom Cade

kdmoyers

Ah. I tried many arrangements close to that, but not quite that. 
Now that I see it, it makes sense.
Thanks for closing the loop!
-Kirby
The mind is everything; What you think, you become.