Jump to content

stdoutread status gui without io blocking


Recommended Posts

I'm trying to figure out the best approach to creating a live status gui that is fed from a constant stdoutread while maintaining the ability to use controls, mainly a cancel button. For this example I'm trying to create an alternate gui for clamscan.exe. However, this is applicable to any command line program that constantly produces output to stdout/err.

As documented in the help file, when using stdoutread with no options, when the stream pauses (as most do), the application freezes until the stream starts getting data again. This is obviously undesirable in all situations most likely as the user will sometimes think the entire application has failed as sometime the gui turns white at the top, or will report the 'application not responding' error.

I thought at first the peak option for stdoutread might be the answer, but after working with it, although it seems to prevent the io blocking, it seems that every time it 'peaks' it gets the entire stream. I do not see any way of clearing the stream (which would give me the answer to this problem). So when dealing with a large amount of streaming data, the program would eventually crash after too much data was buffered.

So I came up with this idea using the other option of stdoutread that reads the number of characters available in the stream, compares it to last number of characters in the stream in the previous loop, and if changed, engages the default stdoutread option which actually only reads what is in the stream since the last read(rather than what has been in the stream since the beginning). Although it works better, it still freezes once in a while. Also, my desire was to have it always display just the last file that was scanned, but it seems that the stream outputs so fast that by the time the controls are filled in the gui and the loop comes back around there are several lines (files) in the stream and they all get displayed (thus the reason I have a vertically long label). I suppose I can do a string split on @LF and just get the last line in the stream.

So my question is whether anyone has dealt with this situation and has a more efficient approach for this process. Like I said above, if there was some way to peak at the stream all the time, but clear the stream afterwards, that would fix this type of problem.

If interested in trying this with ClamWin, go to www.clamwin.org. I simply created the AutoIT script in the same dir in which it was installed by default.

Oh, and if there is some ridiculously easy answer to this that I just missed, my apologies for wasting the bandwidth...

Thanks,

Max

#include <Constants.au3>

#include <process.au3>

Local $stdout_stream

$gui = GUICreate("ClamScan",500,200)

$label = GUICtrlCreateLabel("Current File:",20,20)

$cf_label = GUICtrlCreateLabel("",90,20,400,80)

$button = GUICtrlCreateButton("Test",200,170,100)

GUICtrlSetData($cf_label,"Waiting to start..")

GUISetState(@SW_SHOW)

$bufcount = 0

$oldbufcount = 0

$pid = run("clamscan.exe c:\dell -d " & '"' & "C:\Documents and Settings\All Users\.clamwin\db" & '"',@ScriptDir,@SW_HIDE,$STDOUT_CHILD)

While 1

$msg = GUIGetMsg()

if $msg = $button Then

MsgBox(0,"","No IO blocking!")

Endif

$bufcount = StdoutRead($pid,0,1)

if $bufcount <> $oldbufcount Then

$stream = StdoutRead($pid)

$oldbufcount = $bufcount

GUICtrlSetData($cf_label,"")

GUICtrlSetData($cf_label,$stream)

;ConsoleWrite($stream)

; Determine if scan is finished by looking for last line and the phrase 'Time:'

if stringinstr($stream,"Time:") Then

GUICtrlSetData($cf_label,"Scan complete...")

MsgBox(0,"","Scan Complete...")

ExitLoop

Endif

Endif

Wend

Link to comment
Share on other sites

This demo runs a recursive ATTRIB command and catches all STDERR and STDOUT to an edit control on the GUI:

#include <GuiConstants.au3>
#include <Constants.au3>
#include <Array.au3>

Global $stdout_stream, $pid = -1

$gui = GUICreate("ClamScan", 500, 400)
$cf_label = GUICtrlCreateLabel("Waiting to start..", 20, 20, 460, 20)
$edit_1 = GUICtrlCreateEdit("", 20, 60, 460, 290)
$button = GUICtrlCreateButton("Run", 200, 360, 100, 30)
GUISetState(@SW_SHOW)

While 1
    Switch GUIGetMsg()
        Case $button
            If GUICtrlRead($button) = "Run"  Then
                $pid = Run(@ComSpec & " /c ATTRIB *.* /s", @UserProfileDir, @SW_MINIMIZE, $STDOUT_CHILD + $STDERR_CHILD)
                If $pid = -1 Then
                    MsgBox(16, "Error", "Error starting process")
                    Exit
                EndIf
                GUICtrlSetData($cf_label, "Running...")
                GUICtrlSetData($button, "Test")
                AdlibEnable("_CheckIO", 250)
            Else
                MsgBox(0, "", "No IO blocking!")
            EndIf
        Case $GUI_EVENT_CLOSE
            Exit
    EndSwitch
WEnd

Func _CheckIO()
    ; Get output from console
    Local $sStdConsole = "", $iStart = 1
    If StderrRead($pid, 0, 1) Then $sStdConsole = "<STDERR " & @HOUR & ":" & @MIN & ":" & @SEC & "> " & StderrRead($pid) & @LF
    If StdoutRead($pid, 0, 1) Then $sStdConsole &= "<STDOUT " & @HOUR & ":" & @MIN & ":" & @SEC & "> " & StdoutRead($pid) & @LF
    
    ; Test for console closed
    If @error Then
        GUICtrlSetData($cf_label, "Finished")
        GUICtrlSetData($button, "Run")
        AdlibDisable()
    EndIf
    
    ; Add any output to edit box (max 256 lines)
    If $sStdConsole <> "" Then
        $sEditData = GUICtrlRead($edit_1) & $sStdConsole
        $avEditData = StringSplit($sEditData, @LF)
        If $avEditData[0] > 256 Then $iStart = $avEditData[0] - 256
        $sEditData = _ArrayToString($avEditData, @LF, $iStart)
        GUICtrlSetData($edit_1, $sEditData)
    EndIf
EndFunc   ;==>_CheckIO

Just use this demo for learning the basic technique and apply it to your app.

:P

Valuater's AutoIt 1-2-3, Class... Is now in Session!For those who want somebody to write the script for them: RentACoder"Any technology distinguishable from magic is insufficiently advanced." -- Geek's corollary to Clarke's law
Link to comment
Share on other sites

Thanks a bunch...

After reviewing your code, it seems like the one statement that made the difference is:

If StdoutRead($pid, 0, 1) Then $stream = StdoutRead($pid)

So basically, do not read stderr unless by peaking, something appears to be there.

That line should be added to the docs as that seems to prevent the IO blocking.

Max

Link to comment
Share on other sites

Thanks a bunch...

After reviewing your code, it seems like the one statement that made the difference is:

If StdoutRead($pid, 0, 1) Then $stream = StdoutRead($pid)

So basically, do not read stderr unless by peaking, something appears to be there.

That line should be added to the docs as that seems to prevent the IO blocking.

Max

But that's where I learned it... quoth the help file:

Remarks

StdoutRead reads from the console standard output stream of a child process, which is normally used by console applications to write to the screen. During the call to Run for the child process you wish to read from the STD I/O parameter must have included the STDOUT flag value (2) for this function to work properly (see the Run function).

The optional second parameter tells StdoutRead to read count characters from the stream. If fewer than count characters are available, StdoutRead returns a string of all the characters that are available at that time. If the function is called with no second argument or a second argument of less than zero, StdoutRead assumes you that you wish to read the maximum number of characters and returns a string of all the characters that are available (up to 64kb in a single read).

If at any time when this function is called (except for "peeking" as described below) there are no characters to be read from the stream, the StdoutRead function will block (pause) and not return until there are characters to be read from the stream. This means that the AutoIt process will be halted, and there will be no processing of hotkeys, GUI messages, etc. until the child process writes something to the STDOUT stream.

If StdoutRead is called with a third argument other than zero, StdoutRead will "peek at" the stream rather than actually reading from it, and return the characters that could be read from the stream. When run as a "peek" StdoutRead always returns immediately. Note that any characters are still present on the stream after a peek and will be returned on the next read operation.

When called with a second argument of 0 and a third argument that directs the function to "peek", StdoutRead returns a numeric value of the number of characters that could currently be read from the stream.

Perhaps the examples given could show that being used, though...

:P

Valuater's AutoIt 1-2-3, Class... Is now in Session!For those who want somebody to write the script for them: RentACoder"Any technology distinguishable from magic is insufficiently advanced." -- Geek's corollary to Clarke's law
Link to comment
Share on other sites

Create an account or sign in to comment

You need to be a member in order to leave a comment

Create an account

Sign up for a new account in our community. It's easy!

Register a new account

Sign in

Already have an account? Sign in here.

Sign In Now
 Share

  • Recently Browsing   0 members

    • No registered users viewing this page.
×
×
  • Create New...