:: Re: [DNG] netman GIT project
Góra strony
Delete this message
Reply to this message
Autor: Edward Bartolo
Data:  
Dla: Rainer Weikusat, tilt!, dng
Temat: Re: [DNG] netman GIT project
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 <rainerweikusat@???> wrote:
> Edward Bartolo <edbarx@???> 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@???
> 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.