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.