Version 6.1.7

This commit is contained in:
Chrystian Huot 2022-01-27 10:27:47 -05:00
parent 0534693937
commit 1eebbc1154
34 changed files with 288 additions and 2425 deletions

View file

@ -49,6 +49,14 @@ _v6.1.6_
- Fix webapp not reporting the correct version.
_v6.1.7_
- More concurrency mutexes to resolve SQL_BUSY errors.
- Better internal management of dirwatches.
- Fix SDRTrunk files not being ingested (discussion #108).
- Fix Trunk Recorder talkgroup_tag assign to the wrong property (issue #115).
- Improved the way the talkgroup label and name are autopopulated. If Trunk Recorder sends a talkgroup_tag with an empty value or with a single `-`, it will not overwrite the talkgroup label.
## Version 6.0
- Backend server rewritten in Go for better performance and ease of installation.

View file

@ -24,13 +24,13 @@ Clone the official repository on your computer and start the build process.
When finished, you will find the precompiled versions for various platforms in the `dist` folder.
rdio-scanner-darwin-amd64-v6.1.6.zip
rdio-scanner-darwin-arm64-v6.1.6.zip
rdio-scanner-freebsd-amd64-v6.1.6.zip
rdio-scanner-linux-386-v6.1.6.zip
rdio-scanner-linux-amd64-v6.1.6.zip
rdio-scanner-linux-arm64-v6.1.6.zip
rdio-scanner-linux-arm-v6.1.6.zip
rdio-scanner-windows-amd64-v6.1.6.zip
rdio-scanner-darwin-amd64-v6.1.7.zip
rdio-scanner-darwin-arm64-v6.1.7.zip
rdio-scanner-freebsd-amd64-v6.1.7.zip
rdio-scanner-linux-386-v6.1.7.zip
rdio-scanner-linux-amd64-v6.1.7.zip
rdio-scanner-linux-arm64-v6.1.7.zip
rdio-scanner-linux-arm-v6.1.7.zip
rdio-scanner-windows-amd64-v6.1.7.zip
**Happy Rdio scanning !**

View file

@ -16,8 +16,8 @@
################################################################################
app := rdio-scanner
date := 2022/01/24
ver := 6.1.6
date := 2022/01/27
ver := 6.1.7
client := $(wildcard client/*.json client/*.ts)
server := $(wildcard server/*.go)

2205
client/package-lock.json generated

File diff suppressed because it is too large Load diff

View file

@ -25,7 +25,6 @@
"@typescript-eslint/parser": "^5.10.0",
"ajv-keywords": "^5.1.0",
"eslint": "^8.7.0",
"npm-check-updates": "^12.2.1",
"ts-node": "~10.4.0",
"typescript": "~4.4.4"
},
@ -39,5 +38,5 @@
"build": "ng build --base-href ./ --configuration production",
"start": "ng serve"
},
"version": "6.1.6"
"version": "6.1.7"
}

View file

@ -470,7 +470,7 @@ export class RdioScannerMainComponent implements OnDestroy, OnInit {
this.callFrequency = typeof this.call.frequency === 'number'
? this.formatFrequency(this.call.frequency)
: this.call.audioName?.replace(/\..+$/, '') || '';
: '';
this.callSpike = '';
}

View file

@ -42,8 +42,8 @@ ALWAYS DOWNLOAD THE LATEST VERSION OF [RDIO SCANNER](https://github.com/chuot/rd
rdio@macos ~ % mkdir rdio-scanner
rdio@macos ~ % cd rdio-scanner
rdio@macos rdio-scanner % unzip \
> ~/Downloads/rdio-scanner-darwin-arm64-v6.1.6.zip
Archive: /Users/rdio/Downloads/rdio-scanner-darwin-arm64-v6.1.6.zip
> ~/Downloads/rdio-scanner-darwin-arm64-v6.1.7.zip
Archive: /Users/rdio/Downloads/rdio-scanner-darwin-arm64-v6.1.7.zip
inflating: rdio-scanner
inflating: rdio-scanner.pdf
@ -51,11 +51,11 @@ ALWAYS DOWNLOAD THE LATEST VERSION OF [RDIO SCANNER](https://github.com/chuot/rd
rdio@macos rdio-scanner % ./rdio-scanner
Rdio Scanner v6.1.6
Rdio Scanner v6.1.7
----------------------------------
2022/01/24 08:38:06 server started
2022/01/24 08:38:06 main interface at http://macos.local:3000
2022/01/24 08:38:06 admin interface at http://macos.local:3000/admin
2022/01/27 08:38:06 server started
2022/01/27 08:38:06 main interface at http://macos.local:3000
2022/01/27 08:38:06 admin interface at http://macos.local:3000/admin
4. Access the administrative dashboard to finalize the configuration.
@ -77,11 +77,11 @@ Here we want our [Rdio Scanner](https://guthub.com/chuot/rdio-scanner) instance
rdio@macos rdio-scanner % ./rdio-scanner --listen :80
Rdio Scanner v6.1.6
Rdio Scanner v6.1.7
----------------------------------
2022/01/24 08:48:03 server started
2022/01/24 08:48:03 main interface at http://macos.local
2022/01/24 08:48:03 admin interface at http://macos.local/admin
2022/01/27 08:48:03 server started
2022/01/27 08:48:03 main interface at http://macos.local
2022/01/27 08:48:03 admin interface at http://macos.local/admin
## Listening with SSL with self-signed certificates
@ -90,8 +90,8 @@ It is advised to share your [Rdio Scanner](https://github.com/chuot/rdio-scanner
We can create such self-signed certificates by running [Rdio Scanner](https://github.com/chuot/rdio-scanner) with the **-ssl_create** argument.
rdio@macos rdio-scanner % ./rdio-scanner -ssl_create
2022/01/24 08:49:16 generating ssl certificate files
2022/01/24 08:49:17 ssl files created
2022/01/27 08:49:16 generating ssl certificate files
2022/01/27 08:49:17 ssl files created
This has generated these files in the same folder as your [Rdio Scanner](https://github.com/chuot/rdio-scanner) executable.
@ -108,12 +108,12 @@ Now we use the self-signed server certificate to run our instance.
> -ssl_key_file server.key \
> -ssl_listen :443
Rdio Scanner v6.1.6
Rdio Scanner v6.1.7
----------------------------------
2022/01/24 08:50:58 server started
2022/01/24 08:50:58 main interface at http://macos.local
2022/01/24 08:50:58 main interface at https://macos.local
2022/01/24 08:50:58 admin interface at https://macos.local/admin
2022/01/27 08:50:58 server started
2022/01/27 08:50:58 main interface at http://macos.local
2022/01/27 08:50:58 main interface at https://macos.local
2022/01/27 08:50:58 admin interface at https://macos.local/admin
## Save your advanced configuration to a config file
@ -125,7 +125,7 @@ You don't want to have to type everytime a long list of arguments. No problem, y
> -ssl_key_file server.key \
> -ssl_listen :443 \
> -config_save
2022/01/24 08:52:24 rdio-scanner.ini file created
2022/01/27 08:52:24 rdio-scanner.ini file created
All of your parameters passed as arguments to [Rdio Scanner](https://github.com/chuot/rdio-scanner) have been saved to an INI file which has the same arguments/values list.
@ -140,12 +140,12 @@ Then simply run [Rdio Scanner](https://github.com/chuot/rdio-scanner) without an
rdio@macos rdio-scanner % ./rdio-scanner
Rdio Scanner v6.1.6
Rdio Scanner v6.1.7
----------------------------------
2022/01/24 08:54:08 server started
2022/01/24 08:54:08 main interface at http://macos.local
2022/01/24 08:54:08 main interface at https://macos.local
2022/01/24 08:54:08 admin interface at https://macos.local/admin
2022/01/27 08:54:08 server started
2022/01/27 08:54:08 main interface at http://macos.local
2022/01/27 08:54:08 main interface at https://macos.local
2022/01/27 08:54:08 admin interface at https://macos.local/admin
## Install Rdio Scanner as a service

View file

@ -42,8 +42,8 @@ ALWAYS DOWNLOAD THE LATEST VERSION OF [RDIO SCANNER](https://github.com/chuot/rd
rdio@pc-freebsd:~ $ mkdir rdio-scanner
rdio@pc-freebsd:~ $ cd rdio-scanner
rdio@pc-freebsd:~/rdio-scanner $ unzip \
> ~/rdio-scanner-freebsd-amd64-v6.1.6.zip
Archive: ../rdio-scanner-freebsd-amd64-v6.1.6.zip
> ~/rdio-scanner-freebsd-amd64-v6.1.7.zip
Archive: ../rdio-scanner-freebsd-amd64-v6.1.7.zip
extracting: rdio-scanner
extracting: rdio-scanner.pdf
@ -51,11 +51,11 @@ ALWAYS DOWNLOAD THE LATEST VERSION OF [RDIO SCANNER](https://github.com/chuot/rd
rdio@pc-freebsd:~/rdio-scanner $ ./rdio-scanner
Rdio Scanner v6.1.6
Rdio Scanner v6.1.7
----------------------------------
2022/01/24 08:16:36 server started
2022/01/24 08:16:36 main interface at http://pc-freebsd:3000
2022/01/24 08:16:36 admin interface at http://pc-freebsd:3000/admin
2022/01/27 08:16:36 server started
2022/01/27 08:16:36 main interface at http://pc-freebsd:3000
2022/01/27 08:16:36 admin interface at http://pc-freebsd:3000/admin
4. Access the administrative dashboard to finalize the configuration.
@ -81,11 +81,11 @@ Here we want our [Rdio Scanner](https://guthub.com/chuot/rdio-scanner) instance
Password:
root@pc-freebsd:/home/rdio/rdio-scanner # ./rdio-scanner -listen :80
Rdio Scanner v6.1.6
Rdio Scanner v6.1.7
----------------------------------
2022/01/24 08:19:38 server started
2022/01/24 08:19:38 main interface at http://pc-freebsd
2022/01/24 08:19:38 admin interface at http://pc-freebsd/admin
2022/01/27 08:19:38 server started
2022/01/27 08:19:38 main interface at http://pc-freebsd
2022/01/27 08:19:38 admin interface at http://pc-freebsd/admin
## Listening with SSL with self-signed certificates
@ -94,8 +94,8 @@ It is advised to share your [Rdio Scanner](https://github.com/chuot/rdio-scanner
We can create such self-signed certificates by running [Rdio Scanner](https://github.com/chuot/rdio-scanner) with the **-ssl_create** argument.
rdio@pc-freebsd:~/rdio-scanner $ ./rdio-scanner -ssl_create
2022/01/24 10:32:17 generating ssl certificate files
2022/01/24 10:32:19 ssl files created
2022/01/27 10:32:17 generating ssl certificate files
2022/01/27 10:32:19 ssl files created
This has generated these files in the same folder as your [Rdio Scanner](https://github.com/chuot/rdio-scanner) executable.
@ -114,12 +114,12 @@ Now we use the self-signed server certificate to run our instance.
> -ssl_key_file server.key \
> -ssl_listen :443
Rdio Scanner v6.1.6
Rdio Scanner v6.1.7
----------------------------------
2022/01/24 10:34:29 server started
2022/01/24 10:34:29 main interface at http://pc-freebsd
2022/01/24 10:34:29 main interface at https://pc-freebsd
2022/01/24 10:34:29 admin interface at https://pc-freebsd/admin
2022/01/27 10:34:29 server started
2022/01/27 10:34:29 main interface at http://pc-freebsd
2022/01/27 10:34:29 main interface at https://pc-freebsd
2022/01/27 10:34:29 admin interface at https://pc-freebsd/admin
## Save your advanced configuration to a config file
@ -131,7 +131,7 @@ You don't want to have to type everytime a long list of arguments. No problem, y
> -ssl_key_file server.key \
> -ssl_listen :443 \
> -config_save
2022/01/24 10:37:00 rdio-scanner.ini file created
2022/01/27 10:37:00 rdio-scanner.ini file created
All of your parameters passed as arguments to [Rdio Scanner](https://github.com/chuot/rdio-scanner) have been saved to an INI file which has the same arguments/values list.
@ -148,12 +148,12 @@ Then simply run [Rdio Scanner](https://github.com/chuot/rdio-scanner) without an
Password:
root@pc-freebsd:/home/rdio/rdio-scanner # ./rdio-scanner
Rdio Scanner v6.1.6
Rdio Scanner v6.1.7
----------------------------------
2022/01/24 10:38:28 server started
2022/01/24 10:38:29 main interface at http://pc-freebsd
2022/01/24 10:38:29 main interface at https://pc-freebsd
2022/01/24 10:38:29 admin interface at https://pc-freebsd/admin
2022/01/27 10:38:28 server started
2022/01/27 10:38:29 main interface at http://pc-freebsd
2022/01/27 10:38:29 main interface at https://pc-freebsd
2022/01/27 10:38:29 admin interface at https://pc-freebsd/admin
## Install Rdio Scanner as a service

View file

@ -42,8 +42,8 @@ ALWAYS DOWNLOAD THE LATEST VERSION OF [RDIO SCANNER](https://github.com/chuot/rd
[rdio@pc-linux ~]$ mkdir rdio-scanner
[rdio@pc-linux ~]$ cd rdio-scanner
[rdio@pc-linux rdio-scanner]$ unzip \
> ~/Downloads/rdio-scanner-linux-amd64-v6.1.6.zip
Archive: /home/rdio/Downloads/rdio-scanner-linux-amd64-v6.1.6.zip
> ~/Downloads/rdio-scanner-linux-amd64-v6.1.7.zip
Archive: /home/rdio/Downloads/rdio-scanner-linux-amd64-v6.1.7.zip
inflating: rdio-scanner
inflating: rdio-scanner.pdf
@ -51,11 +51,11 @@ ALWAYS DOWNLOAD THE LATEST VERSION OF [RDIO SCANNER](https://github.com/chuot/rd
[rdio@pc-linux rdio-scanner]$ ./rdio-scanner
Rdio Scanner v6.1.6
Rdio Scanner v6.1.7
----------------------------------
2022/01/24 09:11:48 server started
2022/01/24 09:11:48 main interface at http://pc-linux:3000
2022/01/24 09:11:48 admin interface at http://pc-linux:3000/admin
2022/01/27 09:11:48 server started
2022/01/27 09:11:48 main interface at http://pc-linux:3000
2022/01/27 09:11:48 admin interface at http://pc-linux:3000/admin
4. Access the administrative dashboard to finalize the configuration.
@ -78,11 +78,11 @@ Here we want our [Rdio Scanner](https://guthub.com/chuot/rdio-scanner) instance
[rdio@pc-linux rdio-scanner]$ sudo ./rdio-scanner -listen :80
[sudo] password for rdio:
Rdio Scanner v6.1.6
Rdio Scanner v6.1.7
----------------------------------
2022/01/24 09:14:00 server started
2022/01/24 09:14:00 main interface at http://pc-linux
2022/01/24 09:14:00 admin interface at http://pc-linux/admin
2022/01/27 09:14:00 server started
2022/01/27 09:14:00 main interface at http://pc-linux
2022/01/27 09:14:00 admin interface at http://pc-linux/admin
## Listening with SSL with self-signed certificates
@ -91,8 +91,8 @@ It is advised to share your [Rdio Scanner](https://github.com/chuot/rdio-scanner
We can create such self-signed certificates by running [Rdio Scanner](https://github.com/chuot/rdio-scanner) with the **-ssl_create** argument.
[rdio@pc-linux rdio-scanner]$ ./rdio-scanner -ssl_create
2022/01/24 09:15:03 generating ssl certificate files
2022/01/24 09:15:05 ssl files created
2022/01/27 09:15:03 generating ssl certificate files
2022/01/27 09:15:05 ssl files created
This has generated these files in the same folder as your [Rdio Scanner](https://github.com/chuot/rdio-scanner) executable.
@ -110,12 +110,12 @@ Now we use the self-signed server certificate to run our instance.
> -ssl_listen :443
[sudo] password for rdio:
Rdio Scanner v6.1.6
Rdio Scanner v6.1.7
----------------------------------
2022/01/24 09:16:47 server started
2022/01/24 09:16:47 main interface at http://pc-linux
2022/01/24 09:16:47 main interface at https://pc-linux
2022/01/24 09:16:47 admin interface at https://pc-linux/admin
2022/01/27 09:16:47 server started
2022/01/27 09:16:47 main interface at http://pc-linux
2022/01/27 09:16:47 main interface at https://pc-linux
2022/01/27 09:16:47 admin interface at https://pc-linux/admin
## Save your advanced configuration to a config file
@ -127,7 +127,7 @@ You don't want to have to type everytime a long list of arguments. No problem, y
> -ssl_key_file server.key \
> -ssl_listen :443 \
> -config_save
2022/01/24 09:19:29 rdio-scanner.ini file created
2022/01/27 09:19:29 rdio-scanner.ini file created
All of your parameters passed as arguments to [Rdio Scanner](https://github.com/chuot/rdio-scanner) have been saved to an INI file which has the same arguments/values list.
@ -143,12 +143,12 @@ Then simply run [Rdio Scanner](https://github.com/chuot/rdio-scanner) without an
[rdio@pc-linux rdio-scanner]$ sudo ./rdio-scanner
[sudo] Mot de passe de rdio :
Rdio Scanner v6.1.6
Rdio Scanner v6.1.7
----------------------------------
2022/01/24 09:20:40 server started
2022/01/24 09:20:40 main interface at http://pc-linux
2022/01/24 09:20:40 main interface at https://pc-linux
2022/01/24 09:20:40 admin interface at https://pc-linux/admin
2022/01/27 09:20:40 server started
2022/01/27 09:20:40 main interface at http://pc-linux
2022/01/27 09:20:40 main interface at https://pc-linux
2022/01/27 09:20:40 admin interface at https://pc-linux/admin
## Install Rdio Scanner as a service

View file

@ -44,7 +44,7 @@ ALWAYS DOWNLOAD THE LATEST VERSION OF [RDIO SCANNER](https://github.com/chuot/rd
C:\Users\rdio> cd rdio-scanner
C:\Users\rdio\rdio-scanner> tar -xvf ^
..\downloads\rdio-scanner-windows-amd64-v6.1.6.zip
..\downloads\rdio-scanner-windows-amd64-v6.1.7.zip
x rdio-scanner.exe
x rdio-scanner.pdf
@ -52,11 +52,11 @@ ALWAYS DOWNLOAD THE LATEST VERSION OF [RDIO SCANNER](https://github.com/chuot/rd
C:\Users\rdio\rdio-scanner>rdio-scanner
Rdio Scanner v6.1.6
Rdio Scanner v6.1.7
----------------------------------
2022/01/24 13:48:48 server started
2022/01/24 13:48:48 main interface at http://pc-windows:3000
2022/01/24 13:48:48 admin interface at http://pc-windows:3000/admin
2022/01/27 13:48:48 server started
2022/01/27 13:48:48 main interface at http://pc-windows:3000
2022/01/27 13:48:48 admin interface at http://pc-windows:3000/admin
4. Access the administrative dashboard to finalize the configuration.
@ -78,11 +78,11 @@ Here we want our [Rdio Scanner](https://guthub.com/chuot/rdio-scanner) instance
C:\Users\rdio\rdio-scanner>rdio-scanner -listen :80
Rdio Scanner v6.1.6
Rdio Scanner v6.1.7
----------------------------------
2022/01/24 10:53:31 server started
2022/01/24 10:53:31 main interface at http://pc-windows
2022/01/24 10:53:31 admin interface at http://pc-windows/admin
2022/01/27 10:53:31 server started
2022/01/27 10:53:31 main interface at http://pc-windows
2022/01/27 10:53:31 admin interface at http://pc-windows/admin
## Listening with SSL with self-signed certificates
@ -91,8 +91,8 @@ It is advised to share your [Rdio Scanner](https://github.com/chuot/rdio-scanner
We can create such self-signed certificates by running [Rdio Scanner](https://github.com/chuot/rdio-scanner) with the **-ssl_create** argument.
C:\Users\rdio\rdio-scanner>rdio-scanner -ssl_create
2022/01/24 10:58:51 generating ssl certificate files
2022/01/24 10:58:54 ssl files created
2022/01/27 10:58:51 generating ssl certificate files
2022/01/27 10:58:54 ssl files created
This has generated these files in the same folder as your [Rdio Scanner](https://github.com/chuot/rdio-scanner) executable.
@ -109,12 +109,12 @@ Now we use the self-signed server certificate to run our instance.
-ssl_key_file server.key ^
-ssl_listen :443
Rdio Scanner v6.1.6
Rdio Scanner v6.1.7
----------------------------------
2022/01/24 11:05:43 server started
2022/01/24 11:05:43 main interface at http://pc-windows
2022/01/24 11:05:43 main interface at https://pc-windows
2022/01/24 11:05:43 admin interface at https://pc-windows/admin
2022/01/27 11:05:43 server started
2022/01/27 11:05:43 main interface at http://pc-windows
2022/01/27 11:05:43 main interface at https://pc-windows
2022/01/27 11:05:43 admin interface at https://pc-windows/admin
## Save your advanced configuration to a config file
@ -126,7 +126,7 @@ You don't want to have to type everytime a long list of arguments. No problem, y
-ssl_key_file server.key ^
-ssl_listen :443 ^
-config_save
2022/01/24 11:08:28 rdio-scanner.ini file created
2022/01/27 11:08:28 rdio-scanner.ini file created
All of your parameters passed as arguments to [Rdio Scanner](https://github.com/chuot/rdio-scanner) have been saved to an INI file which has the same arguments/values list.
@ -141,12 +141,12 @@ Then simply run [Rdio Scanner](https://github.com/chuot/rdio-scanner) without an
C:\Users\rdio\rdio-scanner>rdio-scanner
Rdio Scanner v6.1.6
Rdio Scanner v6.1.7
----------------------------------
2022/01/24 11:11:28 server started
2022/01/24 11:11:28 main interface at http://pc-windows
2022/01/24 11:11:28 main interface at https://pc-windows
2022/01/24 11:11:28 admin interface at https://pc-windows/admin
2022/01/27 11:11:28 server started
2022/01/27 11:11:28 main interface at http://pc-windows
2022/01/27 11:11:28 main interface at https://pc-windows
2022/01/27 11:11:28 admin interface at https://pc-windows/admin
\pagebreak{}

6
package-lock.json generated Normal file
View file

@ -0,0 +1,6 @@
{
"name": "rdio-scanner",
"lockfileVersion": 2,
"requires": true,
"packages": {}
}

1
package.json Normal file
View file

@ -0,0 +1 @@
{}

View file

@ -127,13 +127,13 @@ func (access *Access) HasExpired() bool {
type Accesses struct {
List []*Access
mutex sync.RWMutex
mutex sync.Mutex
}
func NewAccesses() *Accesses {
return &Accesses{
List: []*Access{},
mutex: sync.RWMutex{},
mutex: sync.Mutex{},
}
}
@ -154,8 +154,8 @@ func (accesses *Accesses) FromMap(f []interface{}) {
}
func (accesses *Accesses) GetAccess(code string) (access *Access, ok bool) {
accesses.mutex.RLock()
defer accesses.mutex.RUnlock()
accesses.mutex.Lock()
defer accesses.mutex.Unlock()
for _, access := range accesses.List {
if access.Code == code {
@ -167,8 +167,8 @@ func (accesses *Accesses) GetAccess(code string) (access *Access, ok bool) {
}
func (accesses *Accesses) IsRestricted() bool {
accesses.mutex.RLock()
defer accesses.mutex.RUnlock()
accesses.mutex.Lock()
defer accesses.mutex.Unlock()
return len(accesses.List) > 0
}
@ -185,8 +185,8 @@ func (accesses *Accesses) Read(db *Database) error {
t time.Time
)
accesses.mutex.RLock()
defer accesses.mutex.RUnlock()
accesses.mutex.Lock()
defer accesses.mutex.Unlock()
accesses.List = []*Access{}

View file

@ -22,6 +22,7 @@ import (
"net/http"
"os"
"strings"
"sync"
"time"
"github.com/golang-jwt/jwt/v4"
@ -47,6 +48,7 @@ type Admin struct {
Register chan *websocket.Conn
Tokens []string
Unregister chan *websocket.Conn
mutex sync.Mutex
running bool
}
@ -68,6 +70,7 @@ func NewAdmin(controller *Controller) *Admin {
Register: make(chan *websocket.Conn, 100),
Tokens: []string{},
Unregister: make(chan *websocket.Conn, 100),
mutex: sync.Mutex{},
}
}
@ -145,6 +148,11 @@ func (admin *Admin) ConfigHandler(w http.ResponseWriter, r *http.Request) {
}()
} else {
admin.Controller.Calls.mutex.Lock()
admin.mutex.Lock()
defer admin.mutex.Unlock()
defer admin.Controller.Calls.mutex.Unlock()
logError := func(err error) {
admin.Controller.Logs.LogEvent(admin.Controller.Database, LogLevelError, fmt.Sprintf("admin.confighandler.put: %s", err.Error()))
}

View file

@ -110,13 +110,13 @@ func (apikey *Apikey) HasAccess(call *Call) bool {
type Apikeys struct {
List []*Apikey
mutex sync.RWMutex
mutex sync.Mutex
}
func NewApikeys() *Apikeys {
return &Apikeys{
List: []*Apikey{},
mutex: sync.RWMutex{},
mutex: sync.Mutex{},
}
}
@ -137,8 +137,8 @@ func (apikeys *Apikeys) FromMap(f []interface{}) {
}
func (apikeys *Apikeys) GetApikey(key string) (apikey *Apikey, ok bool) {
apikeys.mutex.RLock()
defer apikeys.mutex.RUnlock()
apikeys.mutex.Lock()
defer apikeys.mutex.Unlock()
for _, apikey := range apikeys.List {
if apikey.Key == key && !apikey.Disabled {
@ -157,8 +157,8 @@ func (apikeys *Apikeys) Read(db *Database) error {
systems string
)
apikeys.mutex.RLock()
defer apikeys.mutex.RUnlock()
apikeys.mutex.Lock()
defer apikeys.mutex.Unlock()
apikeys.List = []*Apikey{}

View file

@ -106,20 +106,20 @@ func (call *Call) ToJson() (string, error) {
}
type Calls struct {
mutex sync.RWMutex
mutex sync.Mutex
}
func NewCalls() *Calls {
return &Calls{
mutex: sync.RWMutex{},
mutex: sync.Mutex{},
}
}
func (calls *Calls) CheckDuplicate(call *Call, msTimeFrame uint, db *Database) bool {
var count uint
calls.mutex.RLock()
defer calls.mutex.RUnlock()
calls.mutex.Lock()
defer calls.mutex.Unlock()
d := time.Duration(msTimeFrame) * time.Millisecond
from := call.DateTime.Add(-d).Format(db.DateTimeFormat)
@ -142,8 +142,8 @@ func (calls *Calls) GetCall(id uint, db *Database) (*Call, error) {
t time.Time
)
calls.mutex.RLock()
defer calls.mutex.RUnlock()
calls.mutex.Lock()
defer calls.mutex.Unlock()
call := Call{}
@ -209,8 +209,8 @@ func (calls *Calls) Search(searchOptions *CallsSearchOptions, client *Client) (*
where string = "true"
)
calls.mutex.RLock()
defer calls.mutex.RUnlock()
calls.mutex.Lock()
defer calls.mutex.Unlock()
db := client.Controller.Database

View file

@ -209,7 +209,7 @@ func (config *Config) GetPath(p string) string {
if path.IsAbs(p) {
return p
}
return path.Join(config.BaseDir, p)
return filepath.Join(config.BaseDir, p)
}
func (config *Config) GetSslCaCertFilePath() string {

View file

@ -81,10 +81,6 @@ func NewController(config *Config) *Controller {
return controller
}
func (controller *Controller) CheckDuplicate(call *Call) bool {
return controller.Calls.CheckDuplicate(call, controller.Options.DuplicateDetectionTimeFrame, controller.Database)
}
func (controller *Controller) ConvertAudio(call *Call) {
var (
args = []string{"-i", "-"}
@ -295,7 +291,6 @@ func (controller *Controller) IngestCall(call *Call) {
GroupId: groupId,
Id: call.Talkgroup,
Label: fmt.Sprintf("%d", call.Talkgroup),
Name: fmt.Sprintf("Talkgroup %d", call.Talkgroup),
TagId: tagId,
}
@ -316,6 +311,11 @@ func (controller *Controller) IngestCall(call *Call) {
populated = true
talkgroup.Name = v
}
default:
if len(talkgroup.Name) == 0 {
populated = true
talkgroup.Name = talkgroup.Label
}
}
}
@ -350,7 +350,7 @@ func (controller *Controller) IngestCall(call *Call) {
}
if !controller.Options.DisableDuplicateDetection {
if controller.CheckDuplicate(call) {
if controller.Calls.CheckDuplicate(call, controller.Options.DuplicateDetectionTimeFrame, controller.Database) {
logCall(call, LogLevelWarn, "duplicate call rejected")
return
}

View file

@ -61,9 +61,10 @@ func NewDatabase(config *Config) *Database {
log.Fatalf("unknown database type %s\n", config.DbType)
}
database.Sql.SetConnMaxIdleTime(time.Second * 5)
database.Sql.SetConnMaxLifetime(0)
database.Sql.SetMaxIdleConns(10)
database.Sql.SetMaxOpenConns(10)
database.Sql.SetMaxIdleConns(64)
database.Sql.SetMaxOpenConns(64)
if err = database.migrate(); err != nil {
log.Fatal(err)

View file

@ -85,6 +85,7 @@ var defaults Defaults = Defaults{
"Air",
"EMS",
"Fire",
"Interop",
"Law",
},
keypadBeeps: "uniden",
@ -102,15 +103,13 @@ var defaults Defaults = Defaults{
},
systems: []System{},
tags: []string{
"Air Apron",
"Air Ground",
"Air Traffic",
"EMS Dispatch ",
"EMS Operations ",
"Air Traffic Control",
"Emergency ",
"Fire Dispatch",
"Fire Tac",
"Fire Talk",
"Law Dispatch",
"Law Talk",
"Interop",
"Security",
"Service",
},
}

View file

@ -25,6 +25,7 @@ import (
"mime"
"os"
"path"
"path/filepath"
"regexp"
"sort"
"strconv"
@ -57,6 +58,7 @@ type Dirwatch struct {
UsePolling bool `json:"usePolling"`
controller *Controller
dirs map[string]bool
pending map[string]*time.Timer
running bool
watcher *fsnotify.Watcher
}
@ -465,7 +467,11 @@ func (dirwatch *Dirwatch) Start(controller *Controller) error {
newTimer := func(eventName string) *time.Timer {
return time.AfterFunc(2*time.Second, func() {
dirwatch.Ingest(eventName)
if dirwatch.running {
if _, err := os.Stat(eventName); err == nil {
dirwatch.Ingest(eventName)
}
}
})
}
@ -475,6 +481,7 @@ func (dirwatch *Dirwatch) Start(controller *Controller) error {
if dirwatch.watcher == nil {
break
}
if event, ok := <-dirwatch.watcher.Events; ok {
switch event.Op {
case fsnotify.Create:
@ -482,11 +489,16 @@ func (dirwatch *Dirwatch) Start(controller *Controller) error {
case uint:
time.Sleep(time.Duration(v) * time.Millisecond)
}
if dirwatch.isDir(event.Name) {
if err := dirwatch.walkDir(event.Name); err != nil {
logError(err)
}
} else {
if pending[event.Name] != nil {
pending[event.Name].Stop()
}
pending[event.Name] = newTimer(event.Name)
}
@ -501,16 +513,28 @@ func (dirwatch *Dirwatch) Start(controller *Controller) error {
case fsnotify.Write:
if pending[event.Name] != nil {
pending[event.Name].Stop()
if pending[event.Name] != nil {
pending[event.Name].Stop()
}
pending[event.Name] = newTimer(event.Name)
}
}
} else if dirwatch.running {
dirwatch.watcher.Close()
dirwatch.watcher, _ = fsnotify.NewWatcher()
if dirwatch.watcher != nil {
dirwatch.watcher.Close()
}
time.Sleep(2 * time.Second)
if dirwatch.watcher, err = fsnotify.NewWatcher(); err != nil {
controller.Logs.LogEvent(
controller.Database,
LogLevelError,
fmt.Sprintf("dirwatch.watcher.restart: %s", err.Error()),
)
}
} else {
dirwatch.Stop()
break
@ -521,10 +545,12 @@ func (dirwatch *Dirwatch) Start(controller *Controller) error {
go watcher()
go func() {
time.Sleep(2 * time.Second)
if err := fs.WalkDir(os.DirFS(dirwatch.Directory), ".", func(p string, d fs.DirEntry, err error) error {
if !dirwatch.running {
return nil
}
fs.WalkDir(os.DirFS(dirwatch.Directory), ".", func(p string, d fs.DirEntry, err error) error {
fp := path.Join(dirwatch.Directory, p)
fp := filepath.Join(dirwatch.Directory, p)
if dirwatch.isDir(fp) {
dirwatch.dirs[fp] = true
@ -533,31 +559,42 @@ func (dirwatch *Dirwatch) Start(controller *Controller) error {
} else if dirwatch.DeleteAfter {
dirwatch.Ingest(fp)
}
return err
})
}); err != nil {
controller.Logs.LogEvent(
controller.Database,
LogLevelError,
fmt.Sprintf("dirwatch.walkdir: %s", err.Error()),
)
}
}()
return nil
}
func (dirwatch *Dirwatch) Stop() {
dirwatch.running = false
for k := range dirwatch.pending {
dirwatch.pending[k].Stop()
}
if dirwatch.watcher != nil {
dirwatch.watcher.Close()
dirwatch.watcher = nil
}
dirwatch.running = false
}
type Dirwatches struct {
List []*Dirwatch
mutex sync.RWMutex
mutex sync.Mutex
}
func NewDirwatches() *Dirwatches {
return &Dirwatches{
List: []*Dirwatch{},
mutex: sync.RWMutex{},
mutex: sync.Mutex{},
}
}
@ -594,8 +631,8 @@ func (dirwatches *Dirwatches) Read(db *Database) error {
talkgroupId sql.NullFloat64
)
dirwatches.mutex.RLock()
defer dirwatches.mutex.RUnlock()
dirwatches.mutex.Lock()
defer dirwatches.mutex.Unlock()
dirwatches.Stop()
@ -610,7 +647,7 @@ func (dirwatches *Dirwatches) Read(db *Database) error {
}
for rows.Next() {
dirwatch := &Dirwatch{}
dirwatch := &Dirwatch{pending: map[string]*time.Timer{}}
if err = rows.Scan(&id, &delay, &dirwatch.DeleteAfter, &dirwatch.Directory, &dirwatch.Disabled, &extension, &frequency, &mask, &order, &systemId, &talkgroupId, &kind, &dirwatch.UsePolling); err != nil {
break
@ -770,7 +807,7 @@ func (dirwatch *Dirwatch) walkDir(d string) error {
dfs := os.DirFS(d)
return fs.WalkDir(dfs, ".", func(p string, de fs.DirEntry, err error) error {
fp := path.Join(d, p)
fp := filepath.Join(d, p)
if dirwatch.isDir(fp) {
if !dirwatch.dirs[fp] {
dirwatch.dirs[fp] = true

View file

@ -350,13 +350,13 @@ func (downstream *Downstream) Send(call *Call) error {
type Downstreams struct {
List []*Downstream
mutex sync.RWMutex
mutex sync.Mutex
}
func NewDownstreams() *Downstreams {
return &Downstreams{
List: []*Downstream{},
mutex: sync.RWMutex{},
mutex: sync.Mutex{},
}
}
@ -384,8 +384,8 @@ func (downstreams *Downstreams) Read(db *Database) error {
rows *sql.Rows
)
downstreams.mutex.RLock()
defer downstreams.mutex.RUnlock()
downstreams.mutex.Lock()
defer downstreams.mutex.Unlock()
downstreams.List = []*Downstream{}

View file

@ -28,8 +28,8 @@ require (
golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1 // indirect
lukechampine.com/uint128 v1.2.0 // indirect
modernc.org/cc/v3 v3.35.22 // indirect
modernc.org/ccgo/v3 v3.15.11 // indirect
modernc.org/libc v1.14.4 // indirect
modernc.org/ccgo/v3 v3.15.13 // indirect
modernc.org/libc v1.14.5 // indirect
modernc.org/mathutil v1.4.1 // indirect
modernc.org/memory v1.0.5 // indirect
modernc.org/opt v0.1.1 // indirect

View file

@ -152,10 +152,12 @@ modernc.org/ccgo/v3 v3.14.0/go.mod h1:hBrkiBlUwvr5vV/ZH9YzXIp982jKE8Ek8tR1ytoAL6
modernc.org/ccgo/v3 v3.15.1/go.mod h1:md59wBwDT2LznX/OTCPoVS6KIsdRgY8xqQwBV+hkTH0=
modernc.org/ccgo/v3 v3.15.9/go.mod h1:md59wBwDT2LznX/OTCPoVS6KIsdRgY8xqQwBV+hkTH0=
modernc.org/ccgo/v3 v3.15.10/go.mod h1:wQKxoFn0ynxMuCLfFD09c8XPUCc8obfchoVR9Cn0fI8=
modernc.org/ccgo/v3 v3.15.11 h1:rBUQkkX3u/Sh1ipCDCCwGnaPIjaErgtR23S5mHzuJH4=
modernc.org/ccgo/v3 v3.15.11/go.mod h1:VFePOWoCd8uDGRJpq/zfJ29D0EVzMSyID8LCMWYbX6I=
modernc.org/ccorpus v1.11.1 h1:K0qPfpVG1MJh5BYazccnmhywH4zHuOgJXgbjzyp6dWA=
modernc.org/ccgo/v3 v3.15.12/go.mod h1:VFePOWoCd8uDGRJpq/zfJ29D0EVzMSyID8LCMWYbX6I=
modernc.org/ccgo/v3 v3.15.13 h1:hqlCzNJTXLrhS70y1PqWckrF9x1btSQRC7JFuQcBg5c=
modernc.org/ccgo/v3 v3.15.13/go.mod h1:QHtvdpeODlXjdK3tsbpyK+7U9JV4PQsrPGIbtmc0KfY=
modernc.org/ccorpus v1.11.1/go.mod h1:2gEUTrWqdpH2pXsmTM1ZkjeSrUWDpjMu2T6m29L/ErQ=
modernc.org/ccorpus v1.11.4 h1:YOmQBBzE8GC/puUx76D5j/gJYIZQsydrh6VMJVfXF0M=
modernc.org/ccorpus v1.11.4/go.mod h1:2gEUTrWqdpH2pXsmTM1ZkjeSrUWDpjMu2T6m29L/ErQ=
modernc.org/httpfs v1.0.6 h1:AAgIpFZRXuYnkjftxTAZwMIiwEqAfk8aVB2/oA6nAeM=
modernc.org/httpfs v1.0.6/go.mod h1:7dosgurJGp0sPaRanU53W4xZYKh14wfzX420oZADeHM=
modernc.org/libc v1.9.8/go.mod h1:U1eq8YWr/Kc1RWCMFUWEdkTg8OTcfLw2kY8EDwl039w=
@ -201,8 +203,8 @@ modernc.org/libc v1.13.2/go.mod h1:npFeGWjmZTjFeWALQLrvklVmAxv4m80jnG3+xI8FdJk=
modernc.org/libc v1.14.1/go.mod h1:npFeGWjmZTjFeWALQLrvklVmAxv4m80jnG3+xI8FdJk=
modernc.org/libc v1.14.2/go.mod h1:MX1GBLnRLNdvmK9azU9LCxZ5lMyhrbEMK8rG3X/Fe34=
modernc.org/libc v1.14.3/go.mod h1:GPIvQVOVPizzlqyRX3l756/3ppsAgg1QgPxjr5Q4agQ=
modernc.org/libc v1.14.4 h1:Sw2GDhSQbQF75VKC6RSd5//QH2gj9TgBzjLNZTW6m00=
modernc.org/libc v1.14.4/go.mod h1:GPIvQVOVPizzlqyRX3l756/3ppsAgg1QgPxjr5Q4agQ=
modernc.org/libc v1.14.5 h1:DAHvwGoVRDZs5iJXnX9RJrgXSsorupCWmJ2ac964Owk=
modernc.org/libc v1.14.5/go.mod h1:2PJHINagVxO4QW/5OQdRrvMYo+bm5ClpUFfyXCYl9ak=
modernc.org/mathutil v1.1.1/go.mod h1:mZW8CKdRPY1v87qxC/wUdX5O1qDzXMP5TH3wjfpga6E=
modernc.org/mathutil v1.2.2/go.mod h1:mZW8CKdRPY1v87qxC/wUdX5O1qDzXMP5TH3wjfpga6E=
modernc.org/mathutil v1.4.0/go.mod h1:mZW8CKdRPY1v87qxC/wUdX5O1qDzXMP5TH3wjfpga6E=

View file

@ -42,13 +42,13 @@ func (group *Group) FromMap(m map[string]interface{}) {
type Groups struct {
List []*Group
mutex sync.RWMutex
mutex sync.Mutex
}
func NewGroups() *Groups {
return &Groups{
List: []*Group{},
mutex: sync.RWMutex{},
mutex: sync.Mutex{},
}
}
@ -69,8 +69,8 @@ func (groups *Groups) FromMap(f []interface{}) {
}
func (groups *Groups) GetGroup(f interface{}) (group *Group, ok bool) {
groups.mutex.RLock()
defer groups.mutex.RUnlock()
groups.mutex.Lock()
defer groups.mutex.Unlock()
switch v := f.(type) {
case uint:
@ -165,8 +165,8 @@ func (groups *Groups) Read(db *Database) error {
rows *sql.Rows
)
groups.mutex.RLock()
defer groups.mutex.RUnlock()
groups.mutex.Lock()
defer groups.mutex.Unlock()
groups.List = []*Group{}

View file

@ -22,13 +22,13 @@ import (
type Livefeed struct {
Matrix map[uint]map[uint]bool
mutex sync.RWMutex
mutex sync.Mutex
}
func NewLivefeed() *Livefeed {
return &Livefeed{
Matrix: map[uint]map[uint]bool{},
mutex: sync.RWMutex{},
mutex: sync.Mutex{},
}
}
@ -66,8 +66,8 @@ func (livefeed *Livefeed) FromMap(f interface{}) {
}
func (livefeed *Livefeed) IsAllOff() bool {
livefeed.mutex.RLock()
defer livefeed.mutex.RUnlock()
livefeed.mutex.Lock()
defer livefeed.mutex.Unlock()
for _, sys := range livefeed.Matrix {
for _, tg := range sys {
@ -81,8 +81,8 @@ func (livefeed *Livefeed) IsAllOff() bool {
}
func (livefeed *Livefeed) IsEnabled(call *Call) bool {
livefeed.mutex.RLock()
defer livefeed.mutex.RUnlock()
livefeed.mutex.Lock()
defer livefeed.mutex.Unlock()
if call != nil {
if livefeed.Matrix[call.System][call.Talkgroup] {

View file

@ -38,12 +38,12 @@ type Log struct {
}
type Logs struct {
mutex sync.RWMutex
mutex sync.Mutex
}
func NewLogs() *Logs {
return &Logs{
mutex: sync.RWMutex{},
mutex: sync.Mutex{},
}
}
@ -94,8 +94,8 @@ func (logs *Logs) Search(searchOptions *LogsSearchOptions, db *Database) (*LogsS
where string = "true"
)
logs.mutex.RLock()
defer logs.mutex.RUnlock()
logs.mutex.Lock()
defer logs.mutex.Unlock()
formatError := func(err error) error {
return fmt.Errorf("newLogResults: %v", err)

View file

@ -37,13 +37,13 @@ type Options struct {
TagsToggle bool `json:"tagsToggle"`
adminPassword string
adminPasswordNeedChange bool
mutex sync.RWMutex
mutex sync.Mutex
secret string
}
func NewOptions() *Options {
return &Options{
mutex: sync.RWMutex{},
mutex: sync.Mutex{},
}
}
@ -129,8 +129,8 @@ func (options *Options) Read(db *Database) error {
f interface{}
)
options.mutex.RLock()
defer options.mutex.RUnlock()
options.mutex.Lock()
defer options.mutex.Unlock()
defaultPassword, _ = bcrypt.GenerateFromPassword([]byte(defaults.adminPassword), bcrypt.DefaultCost)

View file

@ -43,7 +43,7 @@ func ParseSdrTrunkMeta(call *Call, controller *Controller) error {
s = regexp.MustCompile(`^([0-9]+)$`).FindStringSubmatch(m.Artist())
if len(s) == 2 {
if i, err = strconv.Atoi(s[1]); err == nil {
if i, err = strconv.Atoi(s[1]); err != nil {
return err
}
if i > 0 {
@ -271,22 +271,22 @@ func ParseMultipartContent(call *Call, p *multipart.Part, b []byte) {
}
case "talkgroupGroup":
if s := string(b); len(s) > 1 {
if s := string(b); len(s) > 0 && s != "-" {
call.talkgroupGroup = s
}
case "talkgroupLabel":
if s := string(b); len(s) > 1 {
if s := string(b); len(s) > 0 && s != "-" {
call.talkgroupLabel = s
}
case "talkgroupName":
if s := string(b); len(s) > 1 {
if s := string(b); len(s) > 0 && s != "-" {
call.talkgroupName = s
}
case "talkgroupTag":
if s := string(b); len(s) > 1 {
if s := string(b); len(s) > 0 && s != "-" {
call.talkgroupTag = s
}
}
@ -425,9 +425,9 @@ func ParseTrunkRecorderMeta(call *Call, b []byte) error {
switch v := m["talkgroup_tag"].(type) {
case string:
call.talkgroupTag = v
call.talkgroupLabel = v
call.talkgroupName = v
if len(v) > 0 && v != "-" {
call.talkgroupLabel = v
}
}
return nil

View file

@ -95,13 +95,13 @@ type SystemMap map[string]interface{}
type Systems struct {
List []*System
mutex sync.RWMutex
mutex sync.Mutex
}
func NewSystems() *Systems {
return &Systems{
List: []*System{},
mutex: sync.RWMutex{},
mutex: sync.Mutex{},
}
}
@ -122,8 +122,8 @@ func (systems *Systems) FromMap(f []interface{}) {
}
func (systems *Systems) GetNewSystemId() uint {
systems.mutex.RLock()
defer systems.mutex.RUnlock()
systems.mutex.Lock()
defer systems.mutex.Unlock()
NextId:
for i := uint(1); i < 65535; i++ {
@ -138,8 +138,8 @@ NextId:
}
func (systems *Systems) GetSystem(f interface{}) (system *System, ok bool) {
systems.mutex.RLock()
defer systems.mutex.RUnlock()
systems.mutex.Lock()
defer systems.mutex.Unlock()
switch v := f.(type) {
case uint:
@ -319,8 +319,8 @@ func (systems *Systems) Read(db *Database) error {
rows *sql.Rows
)
systems.mutex.RLock()
defer systems.mutex.RUnlock()
systems.mutex.Lock()
defer systems.mutex.Unlock()
systems.List = []*System{}

View file

@ -42,13 +42,13 @@ func (tag *Tag) FromMap(m map[string]interface{}) {
type Tags struct {
List []*Tag
mutex sync.RWMutex
mutex sync.Mutex
}
func NewTags() *Tags {
return &Tags{
List: []*Tag{},
mutex: sync.RWMutex{},
mutex: sync.Mutex{},
}
}
@ -69,8 +69,8 @@ func (tags *Tags) FromMap(f []interface{}) {
}
func (tags *Tags) GetTag(f interface{}) (tag *Tag, ok bool) {
tags.mutex.RLock()
defer tags.mutex.RUnlock()
tags.mutex.Lock()
defer tags.mutex.Unlock()
switch v := f.(type) {
case uint:
@ -165,8 +165,8 @@ func (tags *Tags) Read(db *Database) error {
rows *sql.Rows
)
tags.mutex.RLock()
defer tags.mutex.RUnlock()
tags.mutex.Lock()
defer tags.mutex.Unlock()
tags.List = []*Tag{}

View file

@ -93,13 +93,13 @@ type TalkgroupMap map[string]interface{}
type Talkgroups struct {
List []*Talkgroup
mutex sync.RWMutex
mutex sync.Mutex
}
func NewTalkgroups() *Talkgroups {
return &Talkgroups{
List: []*Talkgroup{},
mutex: sync.RWMutex{},
mutex: sync.Mutex{},
}
}
@ -120,8 +120,8 @@ func (talkgroups *Talkgroups) FromMap(f []interface{}) {
}
func (talkgroups *Talkgroups) GetTalkgroup(f interface{}) (system *Talkgroup, ok bool) {
talkgroups.mutex.RLock()
defer talkgroups.mutex.RUnlock()
talkgroups.mutex.Lock()
defer talkgroups.mutex.Unlock()
switch v := f.(type) {
case uint:
@ -149,8 +149,8 @@ func (talkgroups *Talkgroups) Read(db *Database, systemId uint) error {
rows *sql.Rows
)
talkgroups.mutex.RLock()
defer talkgroups.mutex.RUnlock()
talkgroups.mutex.Lock()
defer talkgroups.mutex.Unlock()
talkgroups.List = []*Talkgroup{}

View file

@ -44,13 +44,13 @@ func (unit *Unit) FromMap(m map[string]interface{}) {
type Units struct {
List []*Unit
mutex sync.RWMutex
mutex sync.Mutex
}
func NewUnits() *Units {
return &Units{
List: []*Unit{},
mutex: sync.RWMutex{},
mutex: sync.Mutex{},
}
}
@ -88,6 +88,9 @@ func (units *Units) FromMap(f []interface{}) {
}
func (u *Units) Merge(units *Units) {
units.mutex.Lock()
defer units.mutex.Unlock()
for _, v := range units.List {
u.Add(v.Id, v.Label)
}
@ -99,8 +102,8 @@ func (units *Units) Read(db *Database, systemId uint) error {
rows *sql.Rows
)
units.mutex.RLock()
defer units.mutex.RUnlock()
units.mutex.Lock()
defer units.mutex.Unlock()
units.List = []*Unit{}

View file

@ -15,4 +15,4 @@
package main
const Version = "6.1.6"
const Version = "6.1.7"