Hi,

Unfortunately, the version of netman employing multi-threading is the
also the best behaved one, and I think, I have a solution for blocking
the execution of more than one thread, when the user clicks the same
button more than once very quickly. I tried to get rid of threads as
instructed, but it resulted into a serious bug while connecting, as
connecting hangs while data is read from the process' output stream. I
am attaching the Lazarus unit, so that, anyone who can spot the reason
behind this problem can direct me as to what I should do.

In the meantime, as the multi-threading version seems to be the most
possible to finish, I will do the little extra work needed to finish
it.


Edward



On 31/08/2015, Rainer Weikusat <rainerweiku...@virginmedia.com> wrote:
> Edward Bartolo <edb...@gmail.com> writes:
>
>> I tried several ways to use grey-out buttons but there is a bug or
>> something that is preventing me. The only way I found is
>> multi-threading or simply leaving the main thread to wait for the
>> backend to finish its job until it becomes available to the user.
>> Multi-threading can allow the user to click the same button several
>> times in a very short time creating as several concurrent threads
>> attempting to do the same thing. I tried to prevent this by counting
>> the number of thread objects created but there is a bug that is
>> preventing the created object to decrement the variable when their
>> destructor is called. So, I am sort of, stuck. :(
>>
>> The only way that does not allow multiple similar threads is to allow
>> the GUI to become momentarily unresponsive.
>
> Nope. You can still either react to SIGCHLD in order to reap exit status
> information as soon as it becomes available or set the disposition of
> SIGCHLD to SIG_IGN in order to avoid the need to wait for deceased
> process altogether. Considering that the Linux execve leaves this
> SIGCHLD dispostion unchanged in this case, you could even do that with a
> small C program invoking the Pascal program making up the actual GUI.
> _______________________________________________
> Dng mailing list
> Dng@lists.dyne.org
> https://mailinglists.dyne.org/cgi-bin/mailman/listinfo/dng
>
{*
    netman - A Network Connection Manager
    Copyright (C) 2015  Edward Bartolo

    "netman" is free software: you can redistribute it and/or modify
    it under the terms of the GNU General Public License as published by
    the Free Software Foundation, either version 3 of the License, or
    (at your option) any later version.

    "netman" is distributed in the hope that it will be useful,
    but WITHOUT ANY WARRANTY; without even the implied warranty of
    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
    GNU General Public License for more details.

    You should have received a copy of the GNU General Public License
    along with "netman".  If not, see <http://www.gnu.org/licenses/>.
*}

unit backend;

{$mode objfpc}{$H+}
{$define Xin_development}

interface

uses
  Forms, Classes, SysUtils, Buttons, stdctrls, process, unix, baseunix, dialogs;

type
  TExeMode = (emWait, emNoWait);

const
  backend_exe = 'backend';

var
  cliOut: TMemo = nil;


{********************************* Threads Disabled *************

type
  { Threads }
  TConnectWifiThread = class(TThread)
  private
    essid, pw: string;
    _op: char;

  public
    procedure Execute; override;
    Constructor Create(op: char; AnEssid: string; a_pw: string);
    Destructor Destroy; override;
  end;

****************************************************************}


function run_backend(ExeMode: TExeMode; exe: string; params: array of string): string;

procedure Backend_Save(essid, pw: string);
procedure Backend_Save_Connect(essid, pw: string);
function Backend_Query_Connect(connection: string; var pw: string): boolean;
function Backend_Delete_Connect(essid: string): boolean;
function Backend_Connection_Connect(essid: string): string;
function Backend_Disconnect_Active_Connection: string;
function Backend_Scan: string;
function Backend_Load_Existing: string;
function Backend_Detailed_Scan: string;
function Backend_Wired_Connection_Connect(ethX: string): string;

function Connected: boolean;
function Wifi_Connected: boolean;
function BackendRunning: boolean;
function MultipleConnectionsExist: boolean;


implementation


const
  opSave = '0';
  opSaveConnect = '1';
  opQueryConnect = '2';
  opDeleteConnect = '3';
  opConnectionConnect = '4';
  opDisconnectActiveConnection = '5';
  opScan = '6';
  opLoadExisting = '7';
  opScanDetailed = '8';
  opWiredConnectionConnect = '9';


{******************************************************************

  // Threads implementation
Constructor TConnectWifiThread.Create(op: char; AnEssid: string; a_pw: string);
var
  i: integer;
begin
  essid := AnEssid;
  pw := a_pw;
  _op := op;

  FreeOnTerminate := true;
  inherited Create(false); // start thread immediately
end;

Destructor TConnectWifiThread.Destroy;
begin
  inherited;
end;


procedure TConnectWifiThread.Execute;
begin
  if _op = '4'
    then Backend_Connection_Connect(essid)
  else if _op = '5'
    then Backend_Disconnect_Active_Connection
  else if _op = '9'
    then Backend_Wired_Connection_Connect(essid);  // essid is ethx for wired
end;

*********************************************************}


  {********************** Eliminated
  function run_backend(cmd_and_params: ansistring): string;
  var
     f: Text;
     s: ansistring;
     lines: TStringList;
  begin
    lines := TStringList.Create;
    popen (f, cmd_and_params,'r');
    if cliOut <> nil then
      while(not eof(f)) do
      begin
        readln(f, s);
        lines.Add(s);
      end;
    pclose(f);
    result := lines.Text;
    lines.Free;
  end;
  **********************************}

  function MultipleConnectionsExist: boolean;
  var
    s, line : ansistring;
    output: TStringList;
    i: integer;
    conn: integer;
  begin
    result := false;
    conn := 0;
    RunCommand('ip', ['a'], s);

    output := TStringList.Create;
    output.Text := s;
    try
      for i := 0 to output.Count - 1 do
      begin
        line := output.Strings[i];
        if (Pos(' wlan0: ', line) > 0) and (Pos(' state UP ', line) > 0) then
          inc(conn)
        else if (Pos(' eth0: ', line) > 0) and (Pos(' state UP ', line) > 0) then
          inc(conn);
      end;
    finally
      output.Free;
    end;

    result := (conn > 1);
  end;

  function Wifi_Connected: boolean;
  var
    s, line : ansistring;
    output: TStringList;
    i: integer;
  begin
    result := false;
    RunCommand('ip', ['a'], s);

    output := TStringList.Create;
    output.Text := s;
    try
      for i := 0 to output.Count - 1 do
      begin
        line := output.Strings[i];
        if (Pos(' wlan0: ', line) > 0) and (Pos(' state UP ', line) > 0) then
        begin
          result := true;
          break;
        end;
      end;
    finally
      output.Free;
    end;
  end;

  function Connected: boolean;
  var
    s : ansistring;
  begin
    RunCommand('ip', ['a'], s);

    result := (Pos(' state UP ', s) > 0);
    //result := (length(s) > 0);
  end;

  function BackendRunning: boolean;
  var
    s : ansistring;
  begin
    RunCommand('ps', ['-e'], s);
    //showmessage(s);

    if (Pos(' backend', s) > 0)
    then
      begin
        if (Pos('backend <defunct>', s) > 0)
          then result := false
          else result := true;
        end
    else result := false;
  end;

  function run_backend(ExeMode: TExeMode; exe: string; params: array of string): string;
  const
    BUF_SIZE = 2048; // Buffer size for reading the output in chunks

  var
    AProcess     : TProcess;
    OutputStream : TStream;
    BytesRead    : longint;
    Buffer       : array[1..BUF_SIZE] of byte;

    i            : integer;
    aline        : string;
  begin

//  SUDO dependency removed on 26 Aug 2015

{$ifdef in_development}
    if not sysutils.FileExists('/usr/bin/sudo')
    then
      begin
        result := '/usr/bin/sudo is not installed';
        exit;
      end;
{$endif}

    AProcess := TProcess.Create(nil);

{$ifdef in_development}
    AProcess.Executable := '/usr/bin/sudo';
    AProcess.Parameters.Add('--non-interactive');
    AProcess.Parameters.Add(exe);
{$else}
    AProcess.Executable := ExtractFileDir(Application.ExeName)
      + '/' + exe;
{$endif}

    For i := 0 to High(params) do
      AProcess.Parameters.Add(params[i]);

    if ExeMode = emWait then
      AProcess.Options := [poUsePipes, poStderrToOutPut]
      else AProcess.Options := [];

    AProcess.Execute;

    result := '';
    if ExeMode = emWait then
    begin
      OutputStream := TMemoryStream.Create;
      repeat
        BytesRead := AProcess.Output.Read(Buffer, BUF_SIZE);
        OutputStream.Write(Buffer, BytesRead)
      until BytesRead = 0;  // Stop if no more data is available


      with TStringList.Create do
      begin
        OutputStream.Position := 0; // Required to make sure all data is copied from the start
        LoadFromStream(OutputStream);
        result := Text;
        Free
      end;
    end;

    AProcess.WaitOnExit;
    AProcess.Free;

    if ExeMode = emWait
      then OutputStream.Free;
  end;


{********************************************************************
  function run_backend(exe: string; params: array of string): string;
  const
    BUF_SIZE = 2048; // Buffer size for reading the output in chunks

  var
    AProcess     : TProcess;
    OutputStream : TStream;
    BytesRead    : longint;
    Buffer       : array[1..BUF_SIZE] of byte;

    i            : integer;
    aline        : string;
  begin

//  SUDO dependency removed on 26 Aug 2015

{$ifdef in_development}
    if not sysutils.FileExists('/usr/bin/sudo')
    then
      begin
        result := '/usr/bin/sudo is not installed';
        exit;
      end;
{$endif}

    AProcess := TProcess.Create(nil);

{$ifdef in_development}
    AProcess.Executable := '/usr/bin/sudo';
    AProcess.Parameters.Add('--non-interactive');
    AProcess.Parameters.Add(exe);
{$else}
    AProcess.Executable := ExtractFileDir(Application.ExeName)
      + '/' + exe;
{$endif}

    For i := 0 to High(params) do
      AProcess.Parameters.Add(params[i]);

    AProcess.Options := [poUsePipes, poStderrToOutPut, poWaitOnExit];
    AProcess.Execute;
    result := '';

    OutputStream := TMemoryStream.Create;
    repeat
      BytesRead := AProcess.Output.Read(Buffer, BUF_SIZE);
      OutputStream.Write(Buffer, BytesRead)
    until BytesRead = 0;  // Stop if no more data is available


    with TStringList.Create do
    begin
      OutputStream.Position := 0; // Required to make sure all data is copied from the start
      LoadFromStream(OutputStream);
      result := Text;
      Free
    end;


    AProcess.Free;
    OutputStream.Free;
  end;
**************************************************************}

procedure Backend_Save(essid, pw: string);
begin
  // backend opSave essid pw
  run_backend(emWait, backend_exe, [opSave, essid, pw]);
end;

procedure Backend_Save_Connect(essid, pw: string);
begin
  // backend opSaveConnect essid pw
  run_backend(emWait, backend_exe, [opSaveConnect, essid, pw]);
end;

function Backend_Query_Connect(connection: string; var pw:string): boolean;
var
  res, p: string;
  List: TStringList;
  i: integer;

  PasswordFound: boolean;
begin
  // backend opQueryConnect essid pw
  res := run_backend(emWait, backend_exe, [opQueryConnect, connection, pw]);

  List := TStringList.Create;
  List.Text := res;
  pw := '';
  PasswordFound := false;
  If List.Count = 2
  then
    begin
      p := List.Strings[1];
      p := Trim(p);
      for i := 0 to Length(p) - 1 do
      begin
        if (p[i] = '"')
        then
          begin
            PasswordFound := true;
            continue;
          end
        else
          begin
            if not PasswordFound
              then Continue;
          end;

        pw := pw + p[i];
      end;
    end;

  List.Free;
end;

function Backend_Delete_Connect(essid: string): boolean;
begin
  // backend opDeleteConnect essid
  run_backend(emWait, backend_exe, [opDeleteConnect, essid]);
  result := true;
end;

function Backend_Connection_Connect(essid: string): string;
begin
  // backend opConnectionConnect essid

  result := run_backend(emWait, backend_exe, [opConnectionConnect, essid]);
end;

function Backend_Wired_Connection_Connect(ethX: string): string;
begin
  result := run_backend(emWait, backend_exe, [opWiredConnectionConnect, ethX]);
end;

function Backend_Disconnect_Active_Connection: string;
begin
  // backend opDisconnectActiveConnection
  result := run_backend(emWait, backend_exe, [opDisconnectActiveConnection]);
end;

function Backend_Scan: string;
var
  List: TStringList;
  i: integer;

  wifipoints, essid: string;
begin
  // backend opScan stringlist
  wifipoints := run_backend(emWait, backend_exe, [opScan]);

  List := TStringList.Create;
  List.Text := wifipoints;
  //aListBox.Clear;
  for i := 0 to List.Count - 1 do
  begin
    essid := Trim(List.Strings[i]);
    Delete(essid, 1, 7);
    Delete(essid, Length(essid), 1);
    //aListBox.Items.Add(essid);
    List.Strings[i] := essid;
  end;

  result := List.Text;
  List.Free;
end;

function Backend_Load_Existing: string;
begin
  // backend opLoadExisting stringlist
  result := run_backend(emWait, backend_exe, [opLoadExisting]);
end;

function Backend_Detailed_Scan: string;
Begin
  result := run_backend(emWait, backend_exe, [opScanDetailed]);
end;

end.

_______________________________________________
Dng mailing list
Dng@lists.dyne.org
https://mailinglists.dyne.org/cgi-bin/mailman/listinfo/dng

Reply via email to