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