Skip to content
Projects
Groups
Snippets
Help
This project
Loading...
Sign in / Register
Toggle navigation
E
emiMobile
Overview
Overview
Details
Activity
Cycle Analytics
Repository
Repository
Files
Commits
Branches
Tags
Contributors
Graph
Compare
Charts
Issues
0
Issues
0
List
Board
Labels
Milestones
Merge Requests
0
Merge Requests
0
CI / CD
CI / CD
Pipelines
Jobs
Schedules
Charts
Wiki
Wiki
Snippets
Snippets
Members
Members
Collapse sidebar
Close sidebar
Activity
Graph
Charts
Create a new issue
Jobs
Commits
Issue Boards
Open sidebar
Mac Stephens
emiMobile
Commits
89533881
Commit
89533881
authored
Sep 16, 2026
by
Michael Brachmann
Browse files
Options
Browse Files
Download
Plain Diff
merge in main branch changes
parents
0efe52ee
3171db77
Show whitespace changes
Inline
Side-by-side
Showing
10 changed files
with
209 additions
and
52 deletions
+209
-52
Api.Database.dfm
emiMobileServer/Source/Api.Database.dfm
+1
-0
Api.Database.pas
emiMobileServer/Source/Api.Database.pas
+63
-10
Api.ServiceImpl.pas
emiMobileServer/Source/Api.ServiceImpl.pas
+1
-1
Common.Config.pas
emiMobileServer/Source/Common.Config.pas
+9
-4
WebSocket.Manager.pas
emiMobileServer/Source/WebSocket.Manager.pas
+55
-14
Ws.DataModel.pas
emiMobileServer/Source/Ws.DataModel.pas
+66
-17
emiMobileServer.ini
emiMobileServer/bin/emiMobileServer.ini
+1
-1
emiMobileServer.json
emiMobileServer/bin/emiMobileServer.json
+1
-3
emiMobileServer.dpr
emiMobileServer/emiMobileServer.dpr
+11
-2
Module.Websocket.pas
webEMIMobile/Module.Websocket.pas
+1
-0
No files found.
emiMobileServer/Source/Api.Database.dfm
View file @
89533881
...
...
@@ -1363,6 +1363,7 @@ object ApiDatabaseModule: TApiDatabaseModule
Connection = ucENTCAD
Events = 'disupdate'
OnEvent = UniAlerter1Event
OnError = UniAlerter1Error
Left = 324
Top = 382
end
...
...
emiMobileServer/Source/Api.Database.pas
View file @
89533881
...
...
@@ -6,6 +6,7 @@ uses
System
.
SysUtils
,
System
.
Classes
,
Data
.
DB
,
MemDS
,
DBAccess
,
Uni
,
UniProvider
,
PostgreSQLUniProvider
,
System
.
Variants
,
System
.
Generics
.
Collections
,
System
.
IniFiles
,
Common
.
Logging
,
Vcl
.
Forms
,
System
.
Character
,
Common
.
Ini
,
DAAlerter
,
System
.
SyncObjs
,
UniAlerter
;
type
...
...
@@ -210,14 +211,17 @@ type
procedure
DataModuleCreate
(
Sender
:
TObject
);
procedure
UniAlerter1Event
(
Sender
:
TDAAlerter
;
const
EventName
,
Message
:
string
);
procedure
UniAlerter1Error
(
Sender
:
TDAAlerter
;
E
:
Exception
);
private
{ Private declarations }
FCADUpdate
:
Integer
;
FUpdateListenerFailed
:
Integer
;
public
CADUpdate
:
Boolean
;
function
HandleUniqueFilenames
(
const
category
:
string
):
string
;
function
BadgeCounts
(
const
BaseQuery
:
TUniQuery
):
Integer
;
function
EnsureConnected
:
Boolean
;
function
StartUpdateListener
:
Boolean
;
procedure
MarkCADUpdate
;
function
ConsumeCADUpdate
:
Boolean
;
end
;
var
...
...
@@ -232,7 +236,8 @@ implementation
procedure
TApiDatabaseModule
.
DataModuleCreate
(
Sender
:
TObject
);
begin
CADUpdate
:=
False
;
FCADUpdate
:=
0
;
FUpdateListenerFailed
:=
0
;
ucENTCAD
.
ProviderName
:=
'PostgreSQL'
;
ucENTCAD
.
Server
:=
IniEntries
.
DatabaseServer
;
...
...
@@ -241,8 +246,6 @@ begin
ucENTCAD
.
Username
:=
IniEntries
.
DatabaseUsername
;
ucENTCAD
.
Password
:=
IniEntries
.
DatabasePassword
;
ucENTCAD
.
LoginPrompt
:=
False
;
EnsureConnected
;
end
;
...
...
@@ -258,21 +261,64 @@ begin
ucENTCAD
.
ExecSQL
(
'set search_path to lems, avl, entcad, public'
);
Logger
.
Log
(
2
,
'PostgreSQL API search_path set to lems, avl, entcad, public'
);
end
;
Result
:=
True
;
except
on
E
:
Exception
do
Logger
.
Log
(
1
,
'PostgreSQL API database unavailable: '
+
E
.
Message
);
end
;
end
;
function
TApiDatabaseModule
.
StartUpdateListener
:
Boolean
;
begin
Result
:=
False
;
if
TInterlocked
.
Exchange
(
FUpdateListenerFailed
,
0
)
<>
0
then
begin
try
if
UniAlerter1
.
Active
then
UniAlerter1
.
Stop
;
ucENTCAD
.
Disconnect
;
except
on
E
:
Exception
do
begin
Logger
.
Log
(
1
,
'PostgreSQL disupdate listener reset failed: '
+
E
.
Message
);
TInterlocked
.
Exchange
(
FUpdateListenerFailed
,
1
);
Exit
;
end
;
end
;
end
;
if
not
EnsureConnected
then
Exit
;
try
if
not
UniAlerter1
.
Active
then
begin
Logger
.
Log
(
1
,
'Starting PostgreSQL disupdate listener'
);
UniAlerter1
.
Start
;
Logger
.
Log
(
1
,
'PostgreSQL disupdate listener started'
);
CADUpdate
:=
True
;
MarkCADUpdate
;
end
;
Result
:=
True
;
except
on
E
:
Exception
do
Logger
.
Log
(
1
,
'PostgreSQL
API database
unavailable: '
+
E
.
Message
);
Logger
.
Log
(
1
,
'PostgreSQL
disupdate listener
unavailable: '
+
E
.
Message
);
end
;
end
;
procedure
TApiDatabaseModule
.
MarkCADUpdate
;
begin
TInterlocked
.
Exchange
(
FCADUpdate
,
1
);
end
;
function
TApiDatabaseModule
.
ConsumeCADUpdate
:
Boolean
;
begin
Result
:=
TInterlocked
.
Exchange
(
FCADUpdate
,
0
)
<>
0
;
end
;
procedure
TApiDatabaseModule
.
uqComplaintListCalcFields
(
DataSet
:
TDataSet
);
var
raw
:
string
;
...
...
@@ -343,7 +389,14 @@ end;
procedure
TApiDatabaseModule
.
UniAlerter1Event
(
Sender
:
TDAAlerter
;
const
EventName
,
Message
:
string
);
begin
if
SameText
(
EventName
,
'disupdate'
)
then
CADUpdate
:=
True
;
MarkCADUpdate
;
end
;
procedure
TApiDatabaseModule
.
UniAlerter1Error
(
Sender
:
TDAAlerter
;
E
:
Exception
);
begin
Logger
.
Log
(
1
,
'PostgreSQL disupdate listener error: '
+
E
.
Message
);
TInterlocked
.
Exchange
(
FUpdateListenerFailed
,
1
);
MarkCADUpdate
;
end
;
function
TApiDatabaseModule
.
BadgeCounts
(
const
BaseQuery
:
TUniQuery
):
Integer
;
...
...
emiMobileServer/Source/Api.ServiceImpl.pas
View file @
89533881
...
...
@@ -59,7 +59,7 @@ begin
ApiDB
:=
TApiDatabaseModule
.
Create
(
nil
);
if
not
ApiDB
.
ucENTCAD
.
Connected
then
if
not
ApiDB
.
Ensure
Connected
then
begin
Logger
.
Log
(
1
,
'Unable to connect to API database'
);
raise
EXDataHttpException
.
Create
(
...
...
emiMobileServer/Source/Common.Config.pas
View file @
89533881
...
...
@@ -13,8 +13,6 @@ type
FAdminPassword
:
string
;
FWebAppFolder
:
string
;
FReportsFolder
:
string
;
FMemoLogLevel
:
Integer
;
FFileLogLevel
:
Integer
;
FAuditEnabled
:
Boolean
;
FRpId
:
string
;
FRpName
:
string
;
...
...
@@ -91,6 +89,15 @@ begin
end
;
Logger
.
Log
(
1
,
'-- Config file found.'
);
Logger
.
Log
(
1
,
''
);
Logger
.
Log
(
1
,
'--- Server Config Values ---'
);
Logger
.
Log
(
1
,
'-- url: '
+
serverConfig
.
url
+
IfThen
(
serverConfig
.
url
=
defaultServerUrl
,
' [default]'
,
' [from config]'
));
Logger
.
Log
(
1
,
'-- adminPassword: '
+
serverConfig
.
adminPassword
+
IfThen
(
serverConfig
.
adminPassword
=
'whatisthisusedfor'
,
' [default]'
,
' [from config]'
));
Logger
.
Log
(
1
,
'-- jwtTokenSecret: '
+
serverConfig
.
jwtTokenSecret
+
IfThen
(
serverConfig
.
jwtTokenSecret
=
'super_secret0123super_secret4567'
,
' [default]'
,
' [from config]'
));
Logger
.
Log
(
1
,
'-- webAppFolder: '
+
serverConfig
.
webAppFolder
+
IfThen
(
serverConfig
.
webAppFolder
=
'static'
,
' [default]'
,
' [from config]'
));
Logger
.
Log
(
1
,
'-- auditEnabled: '
+
BoolToStr
(
serverConfig
.
auditEnabled
,
True
));
end
else
jsonObj
:=
TJSONObject
.
ParseJSONValue
(
TFile
.
ReadAllText
(
configFile
))
as
TJSONObject
;
if
not
Assigned
(
jsonObj
)
then
...
...
@@ -143,8 +150,6 @@ begin
jwtTokenSecret
:=
'super_secret0123super_secret4567'
;
webAppFolder
:=
'static'
;
reportsFolder
:=
'reports'
;
memoLogLevel
:=
3
;
fileLogLevel
:=
4
;
auditEnabled
:=
False
;
rpId
:=
'wcemimobile.em-sys.net'
;
rpName
:=
'emiMobile'
;
...
...
emiMobileServer/Source/WebSocket.Manager.pas
View file @
89533881
...
...
@@ -7,7 +7,7 @@ uses
System
.
SysUtils
,
System
.
JSON
,
System
.
Generics
.
Collections
,
Vcl
.
ExtCtrl
s
,
System
.
SyncObj
s
,
VCL
.
TMSFNCWebSocketServer
,
VCL
.
TMSFNCWebSocketCommon
;
...
...
@@ -54,13 +54,15 @@ type
FClients
:
TObjectList
<
TConnectedClient
>;
FClientsLock
:
TObject
;
FSendLock
:
TObject
;
FHeartbeatTimer
:
TTimer
;
FHeartbeatThread
:
TThread
;
FHeartbeatStopEvent
:
TEvent
;
FStarted
:
Integer
;
FOnClientsChanged
:
TClientsChangedEvent
;
function
TrySendTextToClient
(
const
AConnectionId
,
AMessage
:
string
;
ALogFailure
:
Boolean
=
True
):
Boolean
;
function
TryCloseClient
(
const
AConnectionId
:
string
):
Boolean
;
procedure
Heartbeat
Timer
(
Sender
:
TObject
)
;
procedure
Heartbeat
Sweep
;
procedure
NotifyClientsChanged
;
procedure
HandshakeResponseSent
(
Sender
:
TObject
;
AConnection
:
TTMSFNCWebSocketServerConnection
);
procedure
MessageReceived
(
Sender
:
TObject
;
AConnection
:
TTMSFNCWebSocketConnection
;
const
AMessage
:
string
);
...
...
@@ -104,17 +106,32 @@ begin
FServer
.
OnMessageReceived
:=
MessageReceived
;
FServer
.
OnDisconnect
:=
ClientDisconnected
;
FHeartbeatTimer
:=
TTimer
.
Create
(
nil
);
FHeartbeatTimer
.
Enabled
:=
False
;
FHeartbeatTimer
.
Interval
:=
HEARTBEAT_SWEEP_INTERVAL_MS
;
FHeartbeatTimer
.
OnTimer
:=
HeartbeatTimer
;
FStarted
:=
0
;
FHeartbeatStopEvent
:=
TEvent
.
Create
(
nil
,
False
,
False
,
''
);
FHeartbeatThread
:=
TThread
.
CreateAnonymousThread
(
procedure
begin
while
not
TThread
.
CurrentThread
.
CheckTerminated
do
begin
if
FHeartbeatStopEvent
.
WaitFor
(
HEARTBEAT_SWEEP_INTERVAL_MS
)
=
wrSignaled
then
Break
;
if
TInterlocked
.
CompareExchange
(
FStarted
,
0
,
0
)
<>
0
then
HeartbeatSweep
;
end
;
end
);
FHeartbeatThread
.
FreeOnTerminate
:=
False
;
FHeartbeatThread
.
Start
;
end
;
destructor
TWebSocketManager
.
Destroy
;
begin
FHeartbeatTimer
.
Enabled
:=
False
;
Stop
;
FHeartbeatTimer
.
Free
;
FHeartbeatThread
.
Terminate
;
FHeartbeatStopEvent
.
SetEvent
;
FHeartbeatThread
.
WaitFor
;
FHeartbeatThread
.
Free
;
FHeartbeatStopEvent
.
Free
;
FServer
.
Free
;
FClients
.
Free
;
FSendLock
.
Free
;
...
...
@@ -126,13 +143,13 @@ end;
procedure
TWebSocketManager
.
Start
;
begin
FServer
.
Active
:=
True
;
FHeartbeatTimer
.
Enabled
:=
True
;
TInterlocked
.
Exchange
(
FStarted
,
1
)
;
end
;
procedure
TWebSocketManager
.
Stop
;
begin
if
Assigned
(
FHeartbeatTimer
)
then
FHeartbeatTimer
.
Enabled
:=
False
;
TInterlocked
.
Exchange
(
FStarted
,
0
);
if
Assigned
(
FServer
)
then
FServer
.
Active
:=
False
;
end
;
...
...
@@ -141,9 +158,11 @@ function TWebSocketManager.TrySendTextToClient(const AConnectionId,
var
client
:
TConnectedClient
;
connection
:
TTMSFNCWebSocketServerConnection
;
sendFailed
:
Boolean
;
begin
Result
:=
False
;
connection
:=
nil
;
sendFailed
:=
False
;
// TMS owns and frees the connection immediately after its disconnect
// callback returns. Holding FSendLock makes that callback wait until the
...
...
@@ -174,13 +193,35 @@ begin
except
on
E
:
Exception
do
begin
sendFailed
:=
True
;
if
ALogFailure
then
Logger
.
Log
(
2
,
'WebSocket send failed: '
+
E
.
Message
);
Logger
.
Log
(
2
,
'WebSocket send failed for '
+
AConnectionId
+
': '
+
E
.
Message
);
end
;
end
;
if
sendFailed
then
begin
TMonitor
.
Enter
(
FClientsLock
);
try
for
client
in
FClients
do
if
SameText
(
client
.
ConnectionId
,
AConnectionId
)
then
begin
client
.
Closing
:=
True
;
Break
;
end
;
finally
TMonitor
.
Exit
(
FClientsLock
);
end
;
end
;
finally
TMonitor
.
Exit
(
FSendLock
);
end
;
if
sendFailed
then
begin
NotifyClientsChanged
;
TryCloseClient
(
AConnectionId
);
end
;
end
;
function
TWebSocketManager
.
TryCloseClient
(
...
...
@@ -224,7 +265,7 @@ begin
end
;
end
;
procedure
TWebSocketManager
.
Heartbeat
Timer
(
Sender
:
TObject
)
;
procedure
TWebSocketManager
.
Heartbeat
Sweep
;
var
staleConnectionIds
:
TList
<
string
>;
client
:
TConnectedClient
;
...
...
emiMobileServer/Source/Ws.DataModel.pas
View file @
89533881
unit
Ws
.
DataModel
;
// Server-side WebSocket data model.
// Owns a
VCL timer that fire
s every FIntervalMs milliseconds, queries the
// Owns a
worker thread that check
s every FIntervalMs milliseconds, queries the
// database for the five data sets used by the polling timers that existed in
// each connected browser client, and broadcasts the results to every
// handshaked WebSocket connection via the supplied broadcast callback.
...
...
@@ -14,9 +14,8 @@ interface
uses
System
.
SysUtils
,
System
.
Classes
,
System
.
JSON
,
System
.
Generics
.
Collections
,
System
.
Generics
.
Collections
,
System
.
SyncObjs
,
Data
.
DB
,
Vcl
.
ExtCtrls
,
Api
.
Database
,
WsMessages
,
Common
.
Logging
;
...
...
@@ -27,10 +26,13 @@ type
TWsDataModel
=
class
private
FDb
:
TApiDatabaseModule
;
FTimer
:
TTimer
;
FThread
:
TThread
;
FStopEvent
:
TEvent
;
FIntervalMs
:
Integer
;
FBroadcast
:
TWsBroadcastProc
;
procedure
TimerFire
(
Sender
:
TObject
);
procedure
Execute
;
procedure
ConfigureQueryTimeouts
;
procedure
BroadcastAll
;
function
BuildBadgeCountsJson
:
string
;
...
...
@@ -48,37 +50,84 @@ implementation
uses
System
.
StrUtils
,
System
.
DateUtils
;
const
UNIT_MAP_REFRESH_INTERVAL_MS
=
15000
;
{ TWsDataModel }
constructor
TWsDataModel
.
Create
(
ABroadcast
:
TWsBroadcastProc
;
AIntervalMs
:
Integer
);
begin
inherited
Create
;
FBroadcast
:=
ABroadcast
;
FIntervalMs
:=
AIntervalMs
;
FStopEvent
:=
TEvent
.
Create
(
nil
,
False
,
False
,
''
);
FDb
:=
TApiDatabaseModule
.
Create
(
nil
);
FTimer
:=
TTimer
.
Create
(
nil
)
;
FT
imer
.
Interval
:=
AIntervalMs
;
FT
imer
.
OnTimer
:=
TimerFir
e
;
FT
imer
.
Enabled
:=
True
;
ConfigureQueryTimeouts
;
FT
hread
:=
TThread
.
CreateAnonymousThread
(
Execute
)
;
FT
hread
.
FreeOnTerminate
:=
Fals
e
;
FT
hread
.
Start
;
end
;
destructor
TWsDataModel
.
Destroy
;
begin
FTimer
.
Enabled
:=
False
;
FTimer
.
Free
;
FThread
.
Terminate
;
FStopEvent
.
SetEvent
;
FThread
.
WaitFor
;
FThread
.
Free
;
FDb
.
Free
;
FStopEvent
.
Free
;
inherited
;
end
;
procedure
TWsDataModel
.
TimerFire
(
Sender
:
TObject
);
procedure
TWsDataModel
.
Execute
;
var
unitMapElapsedMs
:
Integer
;
begin
if
not
FDb
.
ucENTCAD
.
Connected
then
Exit
;
unitMapElapsedMs
:=
0
;
try
while
not
TThread
.
CurrentThread
.
CheckTerminated
do
begin
if
FStopEvent
.
WaitFor
(
FIntervalMs
)
=
wrSignaled
then
Break
;
if
not
FDb
.
CADUpdate
then
Exit
;
Inc
(
unitMapElapsedMs
,
FIntervalMs
);
FDb
.
CADUpdate
:=
False
;
if
not
FDb
.
StartUpdateListener
then
Continue
;
if
FDb
.
ConsumeCADUpdate
then
begin
BroadcastAll
;
unitMapElapsedMs
:=
0
;
end
else
if
unitMapElapsedMs
>=
UNIT_MAP_REFRESH_INTERVAL_MS
then
begin
try
FBroadcast
(
BuildUnitMapJson
);
except
on
E
:
Exception
do
Logger
.
Log
(
2
,
'WsDataModel UNIT_MAP refresh error: '
+
E
.
Message
);
end
;
unitMapElapsedMs
:=
0
;
end
;
end
;
except
on
E
:
Exception
do
Logger
.
Log
(
1
,
'WsDataModel worker stopped: '
+
E
.
Message
);
end
;
end
;
procedure
TWsDataModel
.
ConfigureQueryTimeouts
;
const
COMMAND_TIMEOUT_SECONDS
=
15
;
begin
FDb
.
uqBadgeCounts
.
SpecificOptions
.
Values
[
'CommandTimeout'
]
:=
IntToStr
(
COMMAND_TIMEOUT_SECONDS
);
FDb
.
uqMapUnits
.
SpecificOptions
.
Values
[
'CommandTimeout'
]
:=
IntToStr
(
COMMAND_TIMEOUT_SECONDS
);
FDb
.
uqMapComplaintUnitsList
.
SpecificOptions
.
Values
[
'CommandTimeout'
]
:=
IntToStr
(
COMMAND_TIMEOUT_SECONDS
);
FDb
.
uqMapComplaints
.
SpecificOptions
.
Values
[
'CommandTimeout'
]
:=
IntToStr
(
COMMAND_TIMEOUT_SECONDS
);
FDb
.
uqUnitList
.
SpecificOptions
.
Values
[
'CommandTimeout'
]
:=
IntToStr
(
COMMAND_TIMEOUT_SECONDS
);
FDb
.
uqComplaintList
.
SpecificOptions
.
Values
[
'CommandTimeout'
]
:=
IntToStr
(
COMMAND_TIMEOUT_SECONDS
);
end
;
procedure
TWsDataModel
.
BroadcastAll
;
...
...
emiMobileServer/bin/emiMobileServer.ini
View file @
89533881
[Settings]
LogFileNum
=
10
7
LogFileNum
=
10
9
webClientVersion
=
0.9.4.3
[Database]
...
...
emiMobileServer/bin/emiMobileServer.json
View file @
89533881
...
...
@@ -2,7 +2,5 @@
"url"
:
"http://localhost:2001/emsys/emiMobile/"
,
"jwtTokenSecret"
:
"super_secret0123super_secret4567"
,
"adminPassword"
:
"whatisthisusedfor?"
,
"webAppFolder"
:
"static"
,
"memoLogLevel"
:
5
,
"fileLogLevel"
:
5
"webAppFolder"
:
"static"
}
emiMobileServer/emiMobileServer.dpr
View file @
89533881
...
...
@@ -2,6 +2,7 @@ program emiMobileServer;
uses
FastMM4,
System.Classes,
System.SyncObjs,
System.SysUtils,
Vcl.StdCtrls,
...
...
@@ -81,6 +82,9 @@ var
LogMsg: string;
begin
if logLevel > FLogLevel then
Exit;
FCriticalSection.Acquire;
try
LogTime := Now;
...
...
@@ -92,11 +96,16 @@ begin
else
FormattedMessage := FormattedMessage + '[' + IntToStr(logLevel) +'] ' + LogMsg;
if logLevel <= FLogLevel then
FLogMemo.Lines.Add( FormattedMessage );
finally
FCriticalSection.Release;
end;
TThread.Queue(nil,
procedure
begin
if Assigned(FLogMemo) and not (csDestroying in FLogMemo.ComponentState) then
FLogMemo.Lines.Add(FormattedMessage);
end);
end;
{ TFileLogAppender }
...
...
webEMIMobile/Module.Websocket.pas
View file @
89533881
...
...
@@ -825,6 +825,7 @@ begin
FHeartbeatOutstanding
:=
False
;
CancelHeartbeatTimeout
;
if
FHeartbeatTimerId
=
0
then
ScheduleHeartbeat
(
HEARTBEAT_INTERVAL_MS
);
end
;
...
...
Write
Preview
Markdown
is supported
0%
Try again
or
attach a new file
Attach a file
Cancel
You are about to add
0
people
to the discussion. Proceed with caution.
Finish editing this message first!
Cancel
Please
register
or
sign in
to comment