Version 6.5.4

This commit is contained in:
Chrystian Huot 2022-09-09 11:52:20 -04:00
parent bf75f92f47
commit d03f0b1fef
22 changed files with 400 additions and 258 deletions

View file

@ -31,6 +31,14 @@ _v6.5.3_
- Add filename to dirwatch error messages (issue #248). - Add filename to dirwatch error messages (issue #248).
- Dirwatch.type=default now defaults to the current date and time if none are provided by the metatags (discussion #250). - Dirwatch.type=default now defaults to the current date and time if none are provided by the metatags (discussion #250).
_v6.5.4_
- Fixed some warnings when linting server code.
- New dirwatch type `DSDPlus Fast Lane` (discussion #244).
- Added new error catches on dirwatch (issue 254).
- Fixed search by inaccurate time (issue #258).
- Reverted sync.Map to regular map with sync.Mutex.
## Version 6.4 ## Version 6.4
- New `-cmd` command line options to allow advanced administrative tasks. - New `-cmd` command line options to allow advanced administrative tasks.

View file

@ -27,13 +27,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. When finished, you will find the precompiled versions for various platforms in the `dist` folder.
rdio-scanner-darwin-amd64-v6.5.3.zip rdio-scanner-darwin-amd64-v6.5.4.zip
rdio-scanner-darwin-arm64-v6.5.3.zip rdio-scanner-darwin-arm64-v6.5.4.zip
rdio-scanner-freebsd-amd64-v6.5.3.zip rdio-scanner-freebsd-amd64-v6.5.4.zip
rdio-scanner-linux-386-v6.5.3.zip rdio-scanner-linux-386-v6.5.4.zip
rdio-scanner-linux-amd64-v6.5.3.zip rdio-scanner-linux-amd64-v6.5.4.zip
rdio-scanner-linux-arm64-v6.5.3.zip rdio-scanner-linux-arm64-v6.5.4.zip
rdio-scanner-linux-arm-v6.5.3.zip rdio-scanner-linux-arm-v6.5.4.zip
rdio-scanner-windows-amd64-v6.5.3.zip rdio-scanner-windows-amd64-v6.5.4.zip
**Happy Rdio scanning !** **Happy Rdio scanning !**

View file

@ -16,8 +16,8 @@
################################################################################ ################################################################################
app := rdio-scanner app := rdio-scanner
date := 2022/08/10 date := 2022/09/09
ver := 6.5.3 ver := 6.5.4
client := $(wildcard client/*.json client/*.ts) client := $(wildcard client/*.json client/*.ts)
server := $(wildcard server/*.go) server := $(wildcard server/*.go)

View file

@ -16,9 +16,10 @@ Here is a list of recorders known to work with [Rdio Scanner](https://github.com
| -------------------------------------------------------------- | --- | -------- | | -------------------------------------------------------------- | --- | -------- |
| [Trunk Recorder](https://github.com/robotastic/trunk-recorder) | X | X | | [Trunk Recorder](https://github.com/robotastic/trunk-recorder) | X | X |
| [RTLSDR-Airband](https://github.com/szpajder/RTLSDR-Airband) | | X | | [RTLSDR-Airband](https://github.com/szpajder/RTLSDR-Airband) | | X |
| [sdrtrunk](https://github.com/DSheirer/sdrtrunk) | | X | | [SDRTrunk](https://github.com/DSheirer/sdrtrunk) | | X |
| [voxcall](https://github.com/aaknitt/voxcall) | X | | | [voxcall](https://github.com/aaknitt/voxcall) | X | |
| [ProScan](https://www.proscan.org/) | | X | | [ProScan](https://www.proscan.org/) | | X |
| [DSDPlus Fast Lane](https://https://www.dsdplus.com/) | | X |
# Quick start # Quick start

View file

@ -1,12 +1,12 @@
{ {
"name": "rdio-scanner", "name": "rdio-scanner",
"version": "6.5.3", "version": "6.5.4",
"lockfileVersion": 2, "lockfileVersion": 2,
"requires": true, "requires": true,
"packages": { "packages": {
"": { "": {
"name": "rdio-scanner", "name": "rdio-scanner",
"version": "6.5.2", "version": "6.5.4",
"license": "LICENSE", "license": "LICENSE",
"dependencies": { "dependencies": {
"@angular/animations": "^13.3.10", "@angular/animations": "^13.3.10",

View file

@ -38,5 +38,5 @@
"build": "ng build --base-href ./ --configuration production", "build": "ng build --base-href ./ --configuration production",
"start": "ng serve" "start": "ng serve"
}, },
"version": "6.5.3" "version": "6.5.4"
} }

View file

@ -637,6 +637,10 @@ export class RdioScannerAdminService implements OnDestroy {
return null; return null;
} }
if (control.value.startsWith('\\')) {
return { network: true }
}
const dirWatch: DirWatch[] = control.parent?.parent?.getRawValue() || []; const dirWatch: DirWatch[] = control.parent?.parent?.getRawValue() || [];
const count = dirWatch.reduce((c, a) => c += a.directory === control.value ? 1 : 0, 0); const count = dirWatch.reduce((c, a) => c += a.directory === control.value ? 1 : 0, 0);
@ -653,7 +657,7 @@ export class RdioScannerAdminService implements OnDestroy {
const type = dirwatch.type; const type = dirwatch.type;
return ['sdr-trunk'].includes(type) || control.value !== null || /#SYS/.test(mask) ? null : { required: true }; return ['dsdplus', 'sdr-trunk'].includes(type) || control.value !== null || /#SYS/.test(mask) ? null : { required: true };
}; };
} }
@ -665,7 +669,7 @@ export class RdioScannerAdminService implements OnDestroy {
const type = dirwatch.type; const type = dirwatch.type;
return ['trunk-recorder', 'sdr-trunk'].includes(type) || control.value !== null || /#TG/.test(mask) ? null : { required: true }; return ['dsdplus', 'trunk-recorder', 'sdr-trunk'].includes(type) || control.value !== null || /#TG/.test(mask) ? null : { required: true };
}; };
} }

View file

@ -33,6 +33,28 @@
<mat-slide-toggle color="primary" formControlName="deleteAfter"></mat-slide-toggle> <mat-slide-toggle color="primary" formControlName="deleteAfter"></mat-slide-toggle>
</div> </div>
</div> </div>
<div class="row">
<p>
<span class="mat-body">Type</span><br>
<span class="mat-caption">
Dirwatch type defines how the metadata are obtained.
<ul>
<li><b>Default</b> - Extract the metadata from a custom mask.</li>
<li><b>DSDPlus Fast Lane</b> - Extract the metadata from the file path.</li>
<li><b>Trunk Recorder</b> - Extract the metadata from the json file.</li>
<li><b>SDR Trunk</b> - Extract the metadata from the MP3 tags defined on the SDR Trunk's aliases tab.</li>
</ul>
</span>
</p>
<mat-form-field floatLabel="never">
<mat-select formControlName="type" placeholder="Type">
<mat-option value="default">Default</mat-option>
<mat-option value="dsdplus">DSDPlus Fast Lane</mat-option>
<mat-option value="trunk-recorder">Trunk Recorder</mat-option>
<mat-option value="sdr-trunk">SDR Trunk</mat-option>
</mat-select>
</mat-form-field>
</div>
<div class="row"> <div class="row">
<p> <p>
<span class="mat-body">Directory</span><br> <span class="mat-body">Directory</span><br>
@ -49,9 +71,12 @@
<mat-error *ngIf="dirWatch.get('directory')?.hasError('duplicate')"> <mat-error *ngIf="dirWatch.get('directory')?.hasError('duplicate')">
Directory is already defined Directory is already defined
</mat-error> </mat-error>
<mat-error *ngIf="dirWatch.get('directory')?.hasError('network')">
Incompatible network folder
</mat-error>
</mat-form-field> </mat-form-field>
</div> </div>
<div class="row"> <div class="row" *ngIf="['default','dsdplus','trunk-recorder'].includes(dirWatch.get('type')?.value)">
<p> <p>
<span class="mat-body">Extension</span><br> <span class="mat-body">Extension</span><br>
<span class="mat-caption">The audio call extension to monitor without the period. Ex.: "mp3", <span class="mat-caption">The audio call extension to monitor without the period. Ex.: "mp3",
@ -64,29 +89,14 @@
</mat-error> </mat-error>
</mat-form-field> </mat-form-field>
</div> </div>
<div class="row"> <div class="row" *ngIf="['default','dsdplus'].includes(dirWatch.get('type')?.value)">
<p>
<span class="mat-body">Type</span><br>
<span class="mat-caption">When SDR Trunk, metadata are obtained from the MP3 tags. Note that
the label of the SDR Trunk system must match the label of the Rdio Scanner system label. When
trunk-recorder, metadata are obtained from the JSON file.</span>
</p>
<mat-form-field floatLabel="never">
<mat-select formControlName="type" placeholder="Type">
<mat-option [value]="null">Default</mat-option>
<mat-option value="trunk-recorder">Trunk Recorder</mat-option>
<mat-option value="sdr-trunk">SDR Trunk (aliases tab only)</mat-option>
</mat-select>
</mat-form-field>
</div>
<div class="row">
<p> <p>
<span class="mat-body">System</span><br> <span class="mat-body">System</span><br>
<span class="mat-caption">System to where the audio files should go.</span> <span class="mat-caption">System to where the audio files should go.</span>
</p> </p>
<mat-form-field floatLabel="never"> <mat-form-field floatLabel="never">
<mat-select #sysId formControlName="systemId" placeholder="System"> <mat-select #sysId formControlName="systemId" placeholder="System">
<mat-option [value]="null">Metatag #SYS</mat-option> <mat-option [value]="null"></mat-option>
<mat-option *ngFor="let system of systems" [value]="system.value.id"> <mat-option *ngFor="let system of systems" [value]="system.value.id">
{{ system.value.label }} {{ system.value.label }}
</mat-option> </mat-option>
@ -96,14 +106,14 @@
</mat-error> </mat-error>
</mat-form-field> </mat-form-field>
</div> </div>
<div class="row"> <div class="row" *ngIf="['default'].includes(dirWatch.get('type')?.value)">
<p> <p>
<span class="mat-body">Talkgroup</span><br> <span class="mat-body">Talkgroup</span><br>
<span class="mat-caption">Talkgroup to where the audio files should go.</span> <span class="mat-caption">Talkgroup to where the audio files should go.</span>
</p> </p>
<mat-form-field floatLabel="never"> <mat-form-field floatLabel="never">
<mat-select formControlName="talkgroupId" placeholder="Talkgroup"> <mat-select formControlName="talkgroupId" placeholder="Talkgroup">
<mat-option [value]="null">Metatag #TG</mat-option> <mat-option [value]="null"></mat-option>
<mat-option *ngFor="let talkgroup of talkgroups[dirWatch.value.systemId] || []" <mat-option *ngFor="let talkgroup of talkgroups[dirWatch.value.systemId] || []"
[value]="talkgroup.value.id"> [value]="talkgroup.value.id">
{{ talkgroup.value.label }} {{ talkgroup.value.label }}
@ -114,7 +124,7 @@
</mat-error> </mat-error>
</mat-form-field> </mat-form-field>
</div> </div>
<div class="row"> <div class="row" *ngIf="['default'].includes(dirWatch.get('type')?.value)">
<p> <p>
<span class="mat-body">Mask</span><br> <span class="mat-body">Mask</span><br>
<span class="mat-caption">Some metadata can be extracted from the file name of the audio file using <span class="mat-caption">Some metadata can be extracted from the file name of the audio file using
@ -156,7 +166,7 @@
</mat-error> </mat-error>
</mat-form-field> </mat-form-field>
</div> </div>
<div class="row"> <div class="row" *ngIf="['default'].includes(dirWatch.get('type')?.value)">
<p> <p>
<span class="mat-body">Frequency</span><br> <span class="mat-body">Frequency</span><br>
<span class="mat-caption">Fake frequency in hertz displayed on the main screen.</span> <span class="mat-caption">Fake frequency in hertz displayed on the main screen.</span>
@ -168,7 +178,7 @@
</mat-error> </mat-error>
</mat-form-field> </mat-form-field>
</div> </div>
<div class="row"> <div class="row" *ngIf="['default'].includes(dirWatch.get('type')?.value)">
<p> <p>
<span class="mat-body">Delay</span><br> <span class="mat-body">Delay</span><br>
<span class="mat-caption">Depending on the recorder, audio files can be ingested too soon after the <span class="mat-caption">Depending on the recorder, audio files can be ingested too soon after the

View file

@ -53,12 +53,12 @@ $ docker run --detach --env TZ=America/Toronto --name rdio-scanner --publish 300
520cdbf51fca11d8bacea12d81245f1cb4d984f80d2be2e3039727b59533a6a9 520cdbf51fca11d8bacea12d81245f1cb4d984f80d2be2e3039727b59533a6a9
$ docker logs rdio-scanner $ docker logs rdio-scanner
Rdio Scanner v6.5.3 Rdio Scanner v6.5.4
---------------------------------- ----------------------------------
2022/08/10 08:57:23 server started 2022/09/09 08:57:23 server started
2022/08/10 08:57:23 base folder is /app/data 2022/09/09 08:57:23 base folder is /app/data
2022/08/10 08:57:23 main interface at http://6d87e8dc37a0:3000 2022/09/09 08:57:23 main interface at http://6d87e8dc37a0:3000
2022/08/10 08:57:23 admin interface at http://6d87e8dc37a0:3000/admin 2022/09/09 08:57:23 admin interface at http://6d87e8dc37a0:3000/admin
``` ```
You can also use docker-compose to start the server with this simple `docker-compose.yml`: You can also use docker-compose to start the server with this simple `docker-compose.yml`:

View file

@ -18,9 +18,10 @@ Here is a list of recorders known to work with [Rdio Scanner](https://github.com
| -------------------------------------------------------------- | --- | -------- | | -------------------------------------------------------------- | --- | -------- |
| [Trunk Recorder](https://github.com/robotastic/trunk-recorder) | X | X | | [Trunk Recorder](https://github.com/robotastic/trunk-recorder) | X | X |
| [RTLSDR-Airband](https://github.com/szpajder/RTLSDR-Airband) | | X | | [RTLSDR-Airband](https://github.com/szpajder/RTLSDR-Airband) | | X |
| [sdrtrunk](https://github.com/DSheirer/sdrtrunk) | | X | | [SDRTrunk](https://github.com/DSheirer/sdrtrunk) | | X |
| [voxcall](https://github.com/aaknitt/voxcall) | X | | | [voxcall](https://github.com/aaknitt/voxcall) | X | |
| [ProScan](https://www.proscan.org/) | | X | | [ProScan](https://www.proscan.org/) | | X |
| [DSDPlus Fast Lane](https://https://www.dsdplus.com/) | | X |
# Improve your experience on the go # Improve your experience on the go
@ -42,8 +43,8 @@ ALWAYS DOWNLOAD THE LATEST VERSION OF [RDIO SCANNER](https://github.com/chuot/rd
rdio@macos ~ % mkdir rdio-scanner rdio@macos ~ % mkdir rdio-scanner
rdio@macos ~ % cd rdio-scanner rdio@macos ~ % cd rdio-scanner
rdio@macos rdio-scanner % unzip \ rdio@macos rdio-scanner % unzip \
> ~/Downloads/rdio-scanner-darwin-arm64-v6.5.3.zip > ~/Downloads/rdio-scanner-darwin-arm64-v6.5.4.zip
Archive: /Users/rdio/Downloads/rdio-scanner-darwin-arm64-v6.5.3.zip Archive: /Users/rdio/Downloads/rdio-scanner-darwin-arm64-v6.5.4.zip
inflating: rdio-scanner inflating: rdio-scanner
inflating: rdio-scanner.pdf inflating: rdio-scanner.pdf
@ -51,11 +52,11 @@ ALWAYS DOWNLOAD THE LATEST VERSION OF [RDIO SCANNER](https://github.com/chuot/rd
rdio@macos rdio-scanner % ./rdio-scanner rdio@macos rdio-scanner % ./rdio-scanner
Rdio Scanner v6.5.3 Rdio Scanner v6.5.4
---------------------------------- ----------------------------------
2022/08/10 08:38:06 server started 2022/09/09 08:38:06 server started
2022/08/10 08:38:06 main interface at http://macos.local:3000 2022/09/09 08:38:06 main interface at http://macos.local:3000
2022/08/10 08:38:06 admin interface at http://macos.local:3000/admin 2022/09/09 08:38:06 admin interface at http://macos.local:3000/admin
4. Access the administrative dashboard to finalize the configuration. 4. Access the administrative dashboard to finalize the configuration.
@ -77,11 +78,11 @@ Here we want our [Rdio Scanner](https://guthub.com/chuot/rdio-scanner) instance
rdio@macos rdio-scanner % ./rdio-scanner --listen :80 rdio@macos rdio-scanner % ./rdio-scanner --listen :80
Rdio Scanner v6.5.3 Rdio Scanner v6.5.4
---------------------------------- ----------------------------------
2022/08/10 08:48:03 server started 2022/09/09 08:48:03 server started
2022/08/10 08:48:03 main interface at http://macos.local 2022/09/09 08:48:03 main interface at http://macos.local
2022/08/10 08:48:03 admin interface at http://macos.local/admin 2022/09/09 08:48:03 admin interface at http://macos.local/admin
## Listening on a SSL port ## Listening on a SSL port
@ -95,12 +96,12 @@ You can use your own SSL certificates that match your domain name with `-ssl_cer
> -ssl_key_file mykey.key \ > -ssl_key_file mykey.key \
> -ssl_listen :443 > -ssl_listen :443
Rdio Scanner v6.5.3 Rdio Scanner v6.5.4
---------------------------------- ----------------------------------
2022/08/10 08:50:58 server started 2022/09/09 08:50:58 server started
2022/08/10 08:50:58 main interface at http://macos.local 2022/09/09 08:50:58 main interface at http://macos.local
2022/08/10 08:50:58 main interface at https://macos.local 2022/09/09 08:50:58 main interface at https://macos.local
2022/08/10 08:50:58 admin interface at https://macos.local/admin 2022/09/09 08:50:58 admin interface at https://macos.local/admin
If you don't want to worry about SSL certificates, you can use the built-in Let's Encrypt auto-cert feature. This requires that you have both port 80 (HTTP) and port 443 (HTTPS) open to the world. Also, your domain name should point to your IP address where [Rdio Scanner](https://github.com/chuot/rdio-scanner/) is running. The advantage of this approach is that everything is done automatically, no certificate request, no certificate renewal. If you don't want to worry about SSL certificates, you can use the built-in Let's Encrypt auto-cert feature. This requires that you have both port 80 (HTTP) and port 443 (HTTPS) open to the world. Also, your domain name should point to your IP address where [Rdio Scanner](https://github.com/chuot/rdio-scanner/) is running. The advantage of this approach is that everything is done automatically, no certificate request, no certificate renewal.
@ -118,7 +119,7 @@ You don't want to have to type everytime a long list of arguments. No problem, y
> -ssl_auto_cert mydomain.com \ > -ssl_auto_cert mydomain.com \
> -ssl_listen :443 \ > -ssl_listen :443 \
> -config_save > -config_save
2022/08/10 08:52:24 rdio-scanner.ini file created 2022/09/09 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. 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.
@ -132,12 +133,12 @@ Then simply run [Rdio Scanner](https://github.com/chuot/rdio-scanner) without an
rdio@macos rdio-scanner % ./rdio-scanner rdio@macos rdio-scanner % ./rdio-scanner
Rdio Scanner v6.5.3 Rdio Scanner v6.5.4
---------------------------------- ----------------------------------
2022/08/10 08:54:08 server started 2022/09/09 08:54:08 server started
2022/08/10 08:54:08 main interface at http://macos.local 2022/09/09 08:54:08 main interface at http://macos.local
2022/08/10 08:54:08 main interface at https://macos.local 2022/09/09 08:54:08 main interface at https://macos.local
2022/08/10 08:54:08 admin interface at https://macos.local/admin 2022/09/09 08:54:08 admin interface at https://macos.local/admin
## Install Rdio Scanner as a service ## Install Rdio Scanner as a service

View file

@ -18,9 +18,10 @@ Here is a list of recorders known to work with [Rdio Scanner](https://github.com
| -------------------------------------------------------------- | --- | -------- | | -------------------------------------------------------------- | --- | -------- |
| [Trunk Recorder](https://github.com/robotastic/trunk-recorder) | X | X | | [Trunk Recorder](https://github.com/robotastic/trunk-recorder) | X | X |
| [RTLSDR-Airband](https://github.com/szpajder/RTLSDR-Airband) | | X | | [RTLSDR-Airband](https://github.com/szpajder/RTLSDR-Airband) | | X |
| [sdrtrunk](https://github.com/DSheirer/sdrtrunk) | | X | | [SDRTrunk](https://github.com/DSheirer/sdrtrunk) | | X |
| [voxcall](https://github.com/aaknitt/voxcall) | X | | | [voxcall](https://github.com/aaknitt/voxcall) | X | |
| [ProScan](https://www.proscan.org/) | | X | | [ProScan](https://www.proscan.org/) | | X |
| [DSDPlus Fast Lane](https://https://www.dsdplus.com/) | | X |
# Improve your experience on the go # Improve your experience on the go
@ -42,8 +43,8 @@ ALWAYS DOWNLOAD THE LATEST VERSION OF [RDIO SCANNER](https://github.com/chuot/rd
rdio@pc-freebsd:~ $ mkdir rdio-scanner rdio@pc-freebsd:~ $ mkdir rdio-scanner
rdio@pc-freebsd:~ $ cd rdio-scanner rdio@pc-freebsd:~ $ cd rdio-scanner
rdio@pc-freebsd:~/rdio-scanner $ unzip \ rdio@pc-freebsd:~/rdio-scanner $ unzip \
> ~/rdio-scanner-freebsd-amd64-v6.5.3.zip > ~/rdio-scanner-freebsd-amd64-v6.5.4.zip
Archive: ../rdio-scanner-freebsd-amd64-v6.5.3.zip Archive: ../rdio-scanner-freebsd-amd64-v6.5.4.zip
extracting: rdio-scanner extracting: rdio-scanner
extracting: rdio-scanner.pdf extracting: rdio-scanner.pdf
@ -51,11 +52,11 @@ ALWAYS DOWNLOAD THE LATEST VERSION OF [RDIO SCANNER](https://github.com/chuot/rd
rdio@pc-freebsd:~/rdio-scanner $ ./rdio-scanner rdio@pc-freebsd:~/rdio-scanner $ ./rdio-scanner
Rdio Scanner v6.5.3 Rdio Scanner v6.5.4
---------------------------------- ----------------------------------
2022/08/10 08:16:36 server started 2022/09/09 08:16:36 server started
2022/08/10 08:16:36 main interface at http://pc-freebsd:3000 2022/09/09 08:16:36 main interface at http://pc-freebsd:3000
2022/08/10 08:16:36 admin interface at http://pc-freebsd:3000/admin 2022/09/09 08:16:36 admin interface at http://pc-freebsd:3000/admin
4. Access the administrative dashboard to finalize the configuration. 4. Access the administrative dashboard to finalize the configuration.
@ -81,11 +82,11 @@ Here we want our [Rdio Scanner](https://guthub.com/chuot/rdio-scanner) instance
Password: Password:
root@pc-freebsd:/home/rdio/rdio-scanner # ./rdio-scanner -listen :80 root@pc-freebsd:/home/rdio/rdio-scanner # ./rdio-scanner -listen :80
Rdio Scanner v6.5.3 Rdio Scanner v6.5.4
---------------------------------- ----------------------------------
2022/08/10 08:19:38 server started 2022/09/09 08:19:38 server started
2022/08/10 08:19:38 main interface at http://pc-freebsd 2022/09/09 08:19:38 main interface at http://pc-freebsd
2022/08/10 08:19:38 admin interface at http://pc-freebsd/admin 2022/09/09 08:19:38 admin interface at http://pc-freebsd/admin
## Listening on a SSL port ## Listening on a SSL port
@ -101,12 +102,12 @@ You can use your own SSL certificates that match your domain name with `-ssl_cer
> -ssl_key_file mykey.key \ > -ssl_key_file mykey.key \
> -ssl_listen :443 > -ssl_listen :443
Rdio Scanner v6.5.3 Rdio Scanner v6.5.4
---------------------------------- ----------------------------------
2022/08/10 10:34:29 server started 2022/09/09 10:34:29 server started
2022/08/10 10:34:29 main interface at http://pc-freebsd 2022/09/09 10:34:29 main interface at http://pc-freebsd
2022/08/10 10:34:29 main interface at https://pc-freebsd 2022/09/09 10:34:29 main interface at https://pc-freebsd
2022/08/10 10:34:29 admin interface at https://pc-freebsd/admin 2022/09/09 10:34:29 admin interface at https://pc-freebsd/admin
If you don't want to worry about SSL certificates, you can use the built-in Let's Encrypt auto-cert feature. This requires that you have both port 80 (HTTP) and port 443 (HTTPS) open to the world. Also, your domain name should point to your IP address where [Rdio Scanner](https://github.com/chuot/rdio-scanner/) is running. The advantage of this approach is that everything is done automatically, no certificate request, no certificate renewal. If you don't want to worry about SSL certificates, you can use the built-in Let's Encrypt auto-cert feature. This requires that you have both port 80 (HTTP) and port 443 (HTTPS) open to the world. Also, your domain name should point to your IP address where [Rdio Scanner](https://github.com/chuot/rdio-scanner/) is running. The advantage of this approach is that everything is done automatically, no certificate request, no certificate renewal.
@ -126,7 +127,7 @@ You don't want to have to type everytime a long list of arguments. No problem, y
> -ssl_auto_cert mydomain.com \ > -ssl_auto_cert mydomain.com \
> -ssl_listen :443 \ > -ssl_listen :443 \
> -config_save > -config_save
2022/08/10 10:37:00 rdio-scanner.ini file created 2022/09/09 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. 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.
@ -142,12 +143,12 @@ Then simply run [Rdio Scanner](https://github.com/chuot/rdio-scanner) without an
Password: Password:
root@pc-freebsd:/home/rdio/rdio-scanner # ./rdio-scanner root@pc-freebsd:/home/rdio/rdio-scanner # ./rdio-scanner
Rdio Scanner v6.5.3 Rdio Scanner v6.5.4
---------------------------------- ----------------------------------
2022/08/10 10:38:28 server started 2022/09/09 10:38:28 server started
2022/08/10 10:38:29 main interface at http://pc-freebsd 2022/09/09 10:38:29 main interface at http://pc-freebsd
2022/08/10 10:38:29 main interface at https://pc-freebsd 2022/09/09 10:38:29 main interface at https://pc-freebsd
2022/08/10 10:38:29 admin interface at https://pc-freebsd/admin 2022/09/09 10:38:29 admin interface at https://pc-freebsd/admin
## Install Rdio Scanner as a service ## Install Rdio Scanner as a service

View file

@ -18,9 +18,10 @@ Here is a list of recorders known to work with [Rdio Scanner](https://github.com
| -------------------------------------------------------------- | --- | -------- | | -------------------------------------------------------------- | --- | -------- |
| [Trunk Recorder](https://github.com/robotastic/trunk-recorder) | X | X | | [Trunk Recorder](https://github.com/robotastic/trunk-recorder) | X | X |
| [RTLSDR-Airband](https://github.com/szpajder/RTLSDR-Airband) | | X | | [RTLSDR-Airband](https://github.com/szpajder/RTLSDR-Airband) | | X |
| [sdrtrunk](https://github.com/DSheirer/sdrtrunk) | | X | | [SDRTrunk](https://github.com/DSheirer/sdrtrunk) | | X |
| [voxcall](https://github.com/aaknitt/voxcall) | X | | | [voxcall](https://github.com/aaknitt/voxcall) | X | |
| [ProScan](https://www.proscan.org/) | | X | | [ProScan](https://www.proscan.org/) | | X |
| [DSDPlus Fast Lane](https://https://www.dsdplus.com/) | | X |
# Improve your experience on the go # Improve your experience on the go
@ -42,8 +43,8 @@ ALWAYS DOWNLOAD THE LATEST VERSION OF [RDIO SCANNER](https://github.com/chuot/rd
[rdio@pc-linux ~]$ mkdir rdio-scanner [rdio@pc-linux ~]$ mkdir rdio-scanner
[rdio@pc-linux ~]$ cd rdio-scanner [rdio@pc-linux ~]$ cd rdio-scanner
[rdio@pc-linux rdio-scanner]$ unzip \ [rdio@pc-linux rdio-scanner]$ unzip \
> ~/Downloads/rdio-scanner-linux-amd64-v6.5.3.zip > ~/Downloads/rdio-scanner-linux-amd64-v6.5.4.zip
Archive: /home/rdio/Downloads/rdio-scanner-linux-amd64-v6.5.3.zip Archive: /home/rdio/Downloads/rdio-scanner-linux-amd64-v6.5.4.zip
inflating: rdio-scanner inflating: rdio-scanner
inflating: rdio-scanner.pdf inflating: rdio-scanner.pdf
@ -51,11 +52,11 @@ ALWAYS DOWNLOAD THE LATEST VERSION OF [RDIO SCANNER](https://github.com/chuot/rd
[rdio@pc-linux rdio-scanner]$ ./rdio-scanner [rdio@pc-linux rdio-scanner]$ ./rdio-scanner
Rdio Scanner v6.5.3 Rdio Scanner v6.5.4
---------------------------------- ----------------------------------
2022/08/10 09:11:48 server started 2022/09/09 09:11:48 server started
2022/08/10 09:11:48 main interface at http://pc-linux:3000 2022/09/09 09:11:48 main interface at http://pc-linux:3000
2022/08/10 09:11:48 admin interface at http://pc-linux:3000/admin 2022/09/09 09:11:48 admin interface at http://pc-linux:3000/admin
4. Access the administrative dashboard to finalize the configuration. 4. Access the administrative dashboard to finalize the configuration.
@ -78,11 +79,11 @@ Here we want our [Rdio Scanner](https://guthub.com/chuot/rdio-scanner) instance
[rdio@pc-linux rdio-scanner]$ sudo ./rdio-scanner -listen :80 [rdio@pc-linux rdio-scanner]$ sudo ./rdio-scanner -listen :80
[sudo] password for rdio: [sudo] password for rdio:
Rdio Scanner v6.5.3 Rdio Scanner v6.5.4
---------------------------------- ----------------------------------
2022/08/10 09:14:00 server started 2022/09/09 09:14:00 server started
2022/08/10 09:14:00 main interface at http://pc-linux 2022/09/09 09:14:00 main interface at http://pc-linux
2022/08/10 09:14:00 admin interface at http://pc-linux/admin 2022/09/09 09:14:00 admin interface at http://pc-linux/admin
## Listening on a SSL port ## Listening on a SSL port
@ -97,12 +98,12 @@ You can use your own SSL certificates that match your domain name with `-ssl_cer
> -ssl_listen :443 > -ssl_listen :443
[sudo] password for rdio: [sudo] password for rdio:
Rdio Scanner v6.5.3 Rdio Scanner v6.5.4
---------------------------------- ----------------------------------
2022/08/10 09:16:47 server started 2022/09/09 09:16:47 server started
2022/08/10 09:16:47 main interface at http://pc-linux 2022/09/09 09:16:47 main interface at http://pc-linux
2022/08/10 09:16:47 main interface at https://pc-linux 2022/09/09 09:16:47 main interface at https://pc-linux
2022/08/10 09:16:47 admin interface at https://pc-linux/admin 2022/09/09 09:16:47 admin interface at https://pc-linux/admin
If you don't want to worry about SSL certificates, you can use the built-in Let's Encrypt auto-cert feature. This requires that you have both port 80 (HTTP) and port 443 (HTTPS) open to the world. Also, your domain name should point to your IP address where [Rdio Scanner](https://github.com/chuot/rdio-scanner/) is running. The advantage of this approach is that everything is done automatically, no certificate request, no certificate renewal. If you don't want to worry about SSL certificates, you can use the built-in Let's Encrypt auto-cert feature. This requires that you have both port 80 (HTTP) and port 443 (HTTPS) open to the world. Also, your domain name should point to your IP address where [Rdio Scanner](https://github.com/chuot/rdio-scanner/) is running. The advantage of this approach is that everything is done automatically, no certificate request, no certificate renewal.
@ -120,7 +121,7 @@ You don't want to have to type everytime a long list of arguments. No problem, y
> -ssl_auto_cert mydomain.com \ > -ssl_auto_cert mydomain.com \
> -ssl_listen :443 \ > -ssl_listen :443 \
> -config_save > -config_save
2022/08/10 09:19:29 rdio-scanner.ini file created 2022/09/09 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. 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.
@ -135,12 +136,12 @@ Then simply run [Rdio Scanner](https://github.com/chuot/rdio-scanner) without an
[rdio@pc-linux rdio-scanner]$ sudo ./rdio-scanner [rdio@pc-linux rdio-scanner]$ sudo ./rdio-scanner
[sudo] Mot de passe de rdio : [sudo] Mot de passe de rdio :
Rdio Scanner v6.5.3 Rdio Scanner v6.5.4
---------------------------------- ----------------------------------
2022/08/10 09:20:40 server started 2022/09/09 09:20:40 server started
2022/08/10 09:20:40 main interface at http://pc-linux 2022/09/09 09:20:40 main interface at http://pc-linux
2022/08/10 09:20:40 main interface at https://pc-linux 2022/09/09 09:20:40 main interface at https://pc-linux
2022/08/10 09:20:40 admin interface at https://pc-linux/admin 2022/09/09 09:20:40 admin interface at https://pc-linux/admin
## Install Rdio Scanner as a service ## Install Rdio Scanner as a service

View file

@ -18,9 +18,10 @@ Here is a list of recorders known to work with [Rdio Scanner](https://github.com
| -------------------------------------------------------------- | --- | -------- | | -------------------------------------------------------------- | --- | -------- |
| [Trunk Recorder](https://github.com/robotastic/trunk-recorder) | X | X | | [Trunk Recorder](https://github.com/robotastic/trunk-recorder) | X | X |
| [RTLSDR-Airband](https://github.com/szpajder/RTLSDR-Airband) | | X | | [RTLSDR-Airband](https://github.com/szpajder/RTLSDR-Airband) | | X |
| [sdrtrunk](https://github.com/DSheirer/sdrtrunk) | | X | | [SDRTrunk](https://github.com/DSheirer/sdrtrunk) | | X |
| [voxcall](https://github.com/aaknitt/voxcall) | X | | | [voxcall](https://github.com/aaknitt/voxcall) | X | |
| [ProScan](https://www.proscan.org/) | | X | | [ProScan](https://www.proscan.org/) | | X |
| [DSDPlus Fast Lane](https://https://www.dsdplus.com/) | | X |
# Improve your experience on the go # Improve your experience on the go
@ -44,7 +45,7 @@ ALWAYS DOWNLOAD THE LATEST VERSION OF [RDIO SCANNER](https://github.com/chuot/rd
C:\Users\rdio> cd rdio-scanner C:\Users\rdio> cd rdio-scanner
C:\Users\rdio\rdio-scanner> tar -xvf ^ C:\Users\rdio\rdio-scanner> tar -xvf ^
..\downloads\rdio-scanner-windows-amd64-v6.5.3.zip ..\downloads\rdio-scanner-windows-amd64-v6.5.4.zip
x rdio-scanner.exe x rdio-scanner.exe
x rdio-scanner.pdf x rdio-scanner.pdf
@ -52,11 +53,11 @@ ALWAYS DOWNLOAD THE LATEST VERSION OF [RDIO SCANNER](https://github.com/chuot/rd
C:\Users\rdio\rdio-scanner>rdio-scanner C:\Users\rdio\rdio-scanner>rdio-scanner
Rdio Scanner v6.5.3 Rdio Scanner v6.5.4
---------------------------------- ----------------------------------
2022/08/10 13:48:48 server started 2022/09/09 13:48:48 server started
2022/08/10 13:48:48 main interface at http://pc-windows:3000 2022/09/09 13:48:48 main interface at http://pc-windows:3000
2022/08/10 13:48:48 admin interface at http://pc-windows:3000/admin 2022/09/09 13:48:48 admin interface at http://pc-windows:3000/admin
4. Access the administrative dashboard to finalize the configuration. 4. Access the administrative dashboard to finalize the configuration.
@ -78,11 +79,11 @@ Here we want our [Rdio Scanner](https://guthub.com/chuot/rdio-scanner) instance
C:\Users\rdio\rdio-scanner>rdio-scanner -listen :80 C:\Users\rdio\rdio-scanner>rdio-scanner -listen :80
Rdio Scanner v6.5.3 Rdio Scanner v6.5.4
---------------------------------- ----------------------------------
2022/08/10 10:53:31 server started 2022/09/09 10:53:31 server started
2022/08/10 10:53:31 main interface at http://pc-windows 2022/09/09 10:53:31 main interface at http://pc-windows
2022/08/10 10:53:31 admin interface at http://pc-windows/admin 2022/09/09 10:53:31 admin interface at http://pc-windows/admin
## Listening on a SSL port ## Listening on a SSL port
@ -96,12 +97,12 @@ You can use your own SSL certificates that match your domain name with `-ssl_cer
-ssl_key_file meykey.key ^ -ssl_key_file meykey.key ^
-ssl_listen :443 -ssl_listen :443
Rdio Scanner v6.5.3 Rdio Scanner v6.5.4
---------------------------------- ----------------------------------
2022/08/10 11:05:43 server started 2022/09/09 11:05:43 server started
2022/08/10 11:05:43 main interface at http://pc-windows 2022/09/09 11:05:43 main interface at http://pc-windows
2022/08/10 11:05:43 main interface at https://pc-windows 2022/09/09 11:05:43 main interface at https://pc-windows
2022/08/10 11:05:43 admin interface at https://pc-windows/admin 2022/09/09 11:05:43 admin interface at https://pc-windows/admin
If you don't want to worry about SSL certificates, you can use the built-in Let's Encrypt auto-cert feature. This requires that you have both port 80 (HTTP) and port 443 (HTTPS) open to the world. Also, your domain name should point to your IP address where [Rdio Scanner](https://github.com/chuot/rdio-scanner/) is running. The advantage of this approach is that everything is done automatically, no certificate request, no certificate renewal. If you don't want to worry about SSL certificates, you can use the built-in Let's Encrypt auto-cert feature. This requires that you have both port 80 (HTTP) and port 443 (HTTPS) open to the world. Also, your domain name should point to your IP address where [Rdio Scanner](https://github.com/chuot/rdio-scanner/) is running. The advantage of this approach is that everything is done automatically, no certificate request, no certificate renewal.
@ -119,7 +120,7 @@ You don't want to have to type everytime a long list of arguments. No problem, y
-ssl_auto_cert mydomain.com ^ -ssl_auto_cert mydomain.com ^
-ssl_listen :443 ^ -ssl_listen :443 ^
-config_save -config_save
2022/08/10 11:08:28 rdio-scanner.ini file created 2022/09/09 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. 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.
@ -133,12 +134,12 @@ Then simply run [Rdio Scanner](https://github.com/chuot/rdio-scanner) without an
C:\Users\rdio\rdio-scanner>rdio-scanner C:\Users\rdio\rdio-scanner>rdio-scanner
Rdio Scanner v6.5.3 Rdio Scanner v6.5.4
---------------------------------- ----------------------------------
2022/08/10 11:11:28 server started 2022/09/09 11:11:28 server started
2022/08/10 11:11:28 main interface at http://pc-windows 2022/09/09 11:11:28 main interface at http://pc-windows
2022/08/10 11:11:28 main interface at https://pc-windows 2022/09/09 11:11:28 main interface at https://pc-windows
2022/08/10 11:11:28 admin interface at https://pc-windows/admin 2022/09/09 11:11:28 admin interface at https://pc-windows/admin
\pagebreak{} \pagebreak{}

View file

@ -338,6 +338,8 @@ func (calls *Calls) Search(searchOptions *CallsSearchOptions, client *Client) (*
if t, err = db.ParseDateTime(dateTime); err == nil { if t, err = db.ParseDateTime(dateTime); err == nil {
searchResults.DateStop = t searchResults.DateStop = t
} else {
searchResults.DateStop = time.Now()
} }
switch v := searchOptions.Sort.(type) { switch v := searchOptions.Sort.(type) {
@ -364,8 +366,8 @@ func (calls *Calls) Search(searchOptions *CallsSearchOptions, client *Client) (*
stop = start.Add(time.Hour*24 - time.Millisecond) stop = start.Add(time.Hour*24 - time.Millisecond)
} else { } else {
start = time.Date(v.Year(), v.Month(), v.Day(), v.Hour(), v.Minute(), 0, 0, time.UTC).Add(time.Hour*-24 - time.Duration(v.Hour())).Add(time.Minute * time.Duration(-v.Minute())) start = time.Date(v.Year(), v.Month(), v.Day(), v.Hour(), v.Minute(), 0, 0, time.UTC).Add(time.Hour*-24 + time.Millisecond)
stop = start.Add(time.Hour*24 - time.Millisecond - time.Duration(v.Hour())).Add(time.Minute * time.Duration(-v.Minute())) stop = time.Date(v.Year(), v.Month(), v.Day(), v.Hour(), v.Minute(), 0, 0, time.UTC)
} }
where += fmt.Sprintf(" and (`dateTime` between '%v' and '%v')", start.Format(df), stop.Format(df)) where += fmt.Sprintf(" and (`dateTime` between '%v' and '%v')", start.Format(df), stop.Format(df))

View file

@ -206,61 +206,64 @@ func (client *Client) SendListenersCount(count int) {
} }
type Clients struct { type Clients struct {
Map sync.Map Map map[*Client]bool
count int mutex sync.Mutex
} }
func NewClients() *Clients { func NewClients() *Clients {
return &Clients{ return &Clients{
Map: sync.Map{}, Map: map[*Client]bool{},
count: 0, mutex: sync.Mutex{},
} }
} }
func (clients *Clients) AccessCount(client *Client) int { func (clients *Clients) AccessCount(client *Client) int {
clients.mutex.Lock()
defer clients.mutex.Unlock()
count := 0 count := 0
clients.Map.Range(func(k any, _ any) bool { for c := range clients.Map {
switch c := k.(type) {
case *Client:
if c.Access == client.Access { if c.Access == client.Access {
count++ count++
} }
} }
return true
})
return count return count
} }
func (clients *Clients) Add(client *Client) { func (clients *Clients) Add(client *Client) {
clients.count = clients.count + 1 clients.mutex.Lock()
clients.Map.Store(client, true) defer clients.mutex.Unlock()
clients.Map[client] = true
} }
func (clients *Clients) Count() int { func (clients *Clients) Count() int {
return clients.count clients.mutex.Lock()
defer clients.mutex.Unlock()
return len(clients.Map)
} }
func (clients *Clients) EmitCall(call *Call, restricted bool) { func (clients *Clients) EmitCall(call *Call, restricted bool) {
clients.Map.Range(func(k any, _ any) bool { clients.mutex.Lock()
switch c := k.(type) { defer clients.mutex.Unlock()
case *Client:
for c := range clients.Map {
if (!restricted || c.Access.HasAccess(call)) && c.Livefeed.IsEnabled(call) { if (!restricted || c.Access.HasAccess(call)) && c.Livefeed.IsEnabled(call) {
c.Send <- &Message{Command: MessageCommandCall, Payload: call} c.Send <- &Message{Command: MessageCommandCall, Payload: call}
} }
} }
return true
})
} }
func (clients *Clients) EmitConfig(groups *Groups, options *Options, systems *Systems, tags *Tags, restricted bool) { func (clients *Clients) EmitConfig(groups *Groups, options *Options, systems *Systems, tags *Tags, restricted bool) {
count := clients.Count() clients.mutex.Lock()
defer clients.mutex.Unlock()
clients.Map.Range(func(k any, _ any) bool { count := len(clients.Map)
switch c := k.(type) {
case *Client: for c := range clients.Map {
if restricted { if restricted {
c.Send <- &Message{Command: MessageCommandPin} c.Send <- &Message{Command: MessageCommandPin}
} else { } else {
@ -271,26 +274,22 @@ func (clients *Clients) EmitConfig(groups *Groups, options *Options, systems *Sy
c.SendListenersCount(count) c.SendListenersCount(count)
} }
} }
return true
})
} }
func (clients *Clients) EmitListenersCount() { func (clients *Clients) EmitListenersCount() {
count := clients.Count() clients.mutex.Lock()
defer clients.mutex.Unlock()
clients.Map.Range(func(k any, _ any) bool { count := len(clients.Map)
switch c := k.(type) {
case *Client: for c := range clients.Map {
c.SendListenersCount(count) c.SendListenersCount(count)
} }
return true
})
} }
func (clients *Clients) Remove(client *Client) { func (clients *Clients) Remove(client *Client) {
if _, loaded := clients.Map.LoadAndDelete(client); loaded { clients.mutex.Lock()
clients.count = clients.count - 1 defer clients.mutex.Unlock()
}
delete(clients.Map, client)
} }

View file

@ -138,7 +138,7 @@ func (command *Command) Do(action string) {
command.url = readVal() command.url = readVal()
if err != nil || !regexp.MustCompile(`^https?://`).Match([]byte(command.url)) { if err != nil || !regexp.MustCompile(`^https?://`).Match([]byte(command.url)) {
command.exitWithError(errors.New("Invalid URL")) command.exitWithError(errors.New("invalid URL"))
} }
} }
@ -259,7 +259,7 @@ func (command *Command) configGet() {
} }
} }
default: default:
command.exitWithError(errors.New("Invalid response")) command.exitWithError(errors.New("invalid response"))
} }
} else { } else {
command.exitWithError(err) command.exitWithError(err)
@ -314,10 +314,10 @@ func (command *Command) login() {
} }
fmt.Println("Logged in.") fmt.Println("Logged in.")
default: default:
command.exitWithError(errors.New("No token in response")) command.exitWithError(errors.New("no token in response"))
} }
default: default:
command.exitWithError(errors.New("Invalid response")) command.exitWithError(errors.New("invalid response"))
} }
} else { } else {
command.exitWithError(err) command.exitWithError(err)

View file

@ -23,7 +23,6 @@ import (
"os" "os"
"os/signal" "os/signal"
"strconv" "strconv"
"sync"
"time" "time"
) )
@ -48,7 +47,6 @@ type Controller struct {
Register chan *Client Register chan *Client
Unregister chan *Client Unregister chan *Client
Ingest chan *Call Ingest chan *Call
ingestMutex sync.Mutex
running bool running bool
} }
@ -271,7 +269,7 @@ func (controller *Controller) IngestCall(call *Call) {
} }
if system == nil || talkgroup == nil { if system == nil || talkgroup == nil {
logCall(call, LogLevelInfo, "no matching system/talkgroup") logCall(call, LogLevelWarn, "no matching system/talkgroup")
return return
} }
@ -516,7 +514,7 @@ func (controller *Controller) Start() error {
} }
go func() { go func() {
c := make(chan os.Signal) c := make(chan os.Signal, 8)
signal.Notify(c, os.Interrupt) signal.Notify(c, os.Interrupt)
<-c <-c
controller.Terminate() controller.Terminate()
@ -536,7 +534,10 @@ func (controller *Controller) Start() error {
if timer != nil { if timer != nil {
timer.Stop() timer.Stop()
} }
timer = time.AfterFunc(time.Second, func() { timer = time.AfterFunc(time.Second, func() {
timer = nil
controller.LogClientsCount() controller.LogClientsCount()
if controller.Options.ShowListenersCount { if controller.Options.ShowListenersCount {

View file

@ -21,7 +21,6 @@ import (
"errors" "errors"
"fmt" "fmt"
"io/fs" "io/fs"
"io/ioutil"
"math" "math"
"mime" "mime"
"os" "os"
@ -38,9 +37,10 @@ import (
) )
const ( const (
DirwatchKindDefault = "default" DirwatchTypeDefault = "default"
DirwatchKindSdrTrunk = "sdr-trunk" DirwatchTypeDSDPlus = "dsdplus"
DirwatchKindTrunkRecorder = "trunk-recorder" DirwatchTypeSdrTrunk = "sdr-trunk"
DirwatchTypeTrunkRecorder = "trunk-recorder"
) )
type Dirwatch struct { type Dirwatch struct {
@ -59,9 +59,19 @@ type Dirwatch struct {
UsePolling bool `json:"usePolling"` UsePolling bool `json:"usePolling"`
controller *Controller controller *Controller
dirs map[string]bool dirs map[string]bool
mutex sync.Mutex
timers map[string]*time.Timer
watcher *fsnotify.Watcher watcher *fsnotify.Watcher
} }
func NewDirwatch() *Dirwatch {
return &Dirwatch{
dirs: map[string]bool{},
mutex: sync.Mutex{},
timers: map[string]*time.Timer{},
}
}
func (dirwatch *Dirwatch) FromMap(m map[string]any) *Dirwatch { func (dirwatch *Dirwatch) FromMap(m map[string]any) *Dirwatch {
switch v := m["_id"].(type) { switch v := m["_id"].(type) {
case float64: case float64:
@ -135,16 +145,18 @@ func (dirwatch *Dirwatch) Ingest(p string) {
var err error var err error
switch dirwatch.Kind { switch dirwatch.Kind {
case DirwatchKindTrunkRecorder: case DirwatchTypeDSDPlus:
err = dirwatch.ingestDSDPlus(p)
case DirwatchTypeTrunkRecorder:
err = dirwatch.ingestTrunkRecorder(p) err = dirwatch.ingestTrunkRecorder(p)
case DirwatchKindSdrTrunk: case DirwatchTypeSdrTrunk:
err = dirwatch.ingestSdrTrunk(p) err = dirwatch.ingestSdrTrunk(p)
default: default:
err = dirwatch.ingestDefault(p) err = dirwatch.ingestDefault(p)
} }
if err != nil { if err != nil {
dirwatch.controller.Logs.LogEvent(LogLevelError, fmt.Sprintf("dirwatch.ingest: %s, %s", err.Error(), p)) dirwatch.controller.Logs.LogEvent(LogLevelWarn, fmt.Sprintf("dirwatch.ingest: %s, %s", err.Error(), p))
} }
} }
@ -156,7 +168,11 @@ func (dirwatch *Dirwatch) ingestDefault(p string) error {
switch v := dirwatch.Extension.(type) { switch v := dirwatch.Extension.(type) {
case string: case string:
if len(v) > 0 {
ext = fmt.Sprintf(".%s", v) ext = fmt.Sprintf(".%s", v)
} else {
ext = ".wav"
}
default: default:
ext = ".wav" ext = ".wav"
} }
@ -202,7 +218,7 @@ func (dirwatch *Dirwatch) ingestDefault(p string) error {
return err return err
} }
func (dirwatch *Dirwatch) ingestSdrTrunk(p string) error { func (dirwatch *Dirwatch) ingestDSDPlus(p string) error {
var ( var (
err error err error
ext string ext string
@ -229,6 +245,48 @@ func (dirwatch *Dirwatch) ingestSdrTrunk(p string) error {
call.AudioType = mime.TypeByExtension(path.Ext(p)) call.AudioType = mime.TypeByExtension(path.Ext(p))
call.Frequency = dirwatch.Frequency call.Frequency = dirwatch.Frequency
switch v := dirwatch.SystemId.(type) {
case uint:
call.System = v
}
if call.Audio, err = os.ReadFile(p); err != nil {
return err
}
if err = ParseDSDPlusMeta(call, p); err != nil {
return err
}
if ok, err := call.IsValid(); ok {
dirwatch.controller.Ingest <- call
if dirwatch.DeleteAfter {
if err = os.Remove(p); err != nil {
return err
}
}
} else {
return err
}
return nil
}
func (dirwatch *Dirwatch) ingestSdrTrunk(p string) error {
var err error
if !strings.EqualFold(path.Ext(p), ".mp3") {
return nil
}
call := NewCall()
call.AudioName = filepath.Base(p)
call.AudioType = mime.TypeByExtension(path.Ext(p))
call.Frequency = dirwatch.Frequency
if call.Audio, err = os.ReadFile(p); err != nil { if call.Audio, err = os.ReadFile(p); err != nil {
return err return err
} }
@ -290,11 +348,11 @@ func (dirwatch *Dirwatch) ingestTrunkRecorder(p string) error {
call.System = v call.System = v
} }
if call.Audio, err = ioutil.ReadFile(audioName); err != nil { if call.Audio, err = os.ReadFile(audioName); err != nil {
return nil return nil
} }
if b, err = ioutil.ReadFile(p); err != nil { if b, err = os.ReadFile(p); err != nil {
return err return err
} }
@ -550,15 +608,16 @@ func (dirwatch *Dirwatch) Start(controller *Controller) error {
} }
go func() { go func() {
var timers = sync.Map{}
logError := func(err error) { logError := func(err error) {
controller.Logs.LogEvent(LogLevelError, fmt.Sprintf("dirwatch.watcher: %v", err.Error())) controller.Logs.LogEvent(LogLevelError, fmt.Sprintf("dirwatch.watcher: %v", err.Error()))
} }
newTimer := func(eventName string) *time.Timer { newTimer := func(eventName string) *time.Timer {
return time.AfterFunc(delay, func() { return time.AfterFunc(delay, func() {
timers.Delete(eventName) dirwatch.mutex.Lock()
defer dirwatch.mutex.Unlock()
delete(dirwatch.timers, eventName)
if _, err := os.Stat(eventName); err == nil { if _, err := os.Stat(eventName); err == nil {
dirwatch.Ingest(eventName) dirwatch.Ingest(eventName)
@ -567,31 +626,35 @@ func (dirwatch *Dirwatch) Start(controller *Controller) error {
} }
defer func() { defer func() {
timers.Range(func(k any, t any) bool {
switch v := t.(type) {
case *time.Timer:
v.Stop()
}
timers.Delete(k)
return true
})
switch v := recover().(type) { switch v := recover().(type) {
case error: case error:
controller.Logs.LogEvent(LogLevelError, v.Error()) controller.Logs.LogEvent(LogLevelError, v.Error())
} }
dirwatch.mutex.Lock()
defer dirwatch.mutex.Unlock()
for e, t := range dirwatch.timers {
t.Stop()
delete(dirwatch.timers, e)
}
if dirwatch.watcher != nil {
dirwatch.Start(controller)
}
}() }()
for { for {
if dirwatch.watcher == nil { if dirwatch.watcher == nil {
break return
} }
select { select {
case event, ok := <-dirwatch.watcher.Events: case event, ok := <-dirwatch.watcher.Events:
if ok { if !ok {
return
}
switch event.Op { switch event.Op {
case fsnotify.Create: case fsnotify.Create:
if dirwatch.isDir(event.Name) { if dirwatch.isDir(event.Name) {
@ -600,13 +663,10 @@ func (dirwatch *Dirwatch) Start(controller *Controller) error {
} }
} else { } else {
if f, ok := timers.LoadAndDelete(event.Name); ok { if dirwatch.timers[event.Name] != nil {
switch v := f.(type) { dirwatch.timers[event.Name].Stop()
case *time.Timer:
v.Stop()
} }
} dirwatch.timers[event.Name] = newTimer(event.Name)
timers.Store(event.Name, newTimer(event.Name))
} }
case fsnotify.Remove: case fsnotify.Remove:
@ -619,24 +679,20 @@ func (dirwatch *Dirwatch) Start(controller *Controller) error {
} }
case fsnotify.Write: case fsnotify.Write:
if f, ok := timers.LoadAndDelete(event.Name); ok { if dirwatch.timers[event.Name] != nil {
switch v := f.(type) { dirwatch.timers[event.Name].Stop()
case *time.Timer:
v.Stop()
}
}
timers.Store(event.Name, newTimer(event.Name))
} }
dirwatch.timers[event.Name] = newTimer(event.Name)
} }
case err, ok := <-dirwatch.watcher.Errors: case err, ok := <-dirwatch.watcher.Errors:
if ok { if ok {
logError(err) logError(err)
}
return return
} }
} }
}
}() }()
go func() { go func() {
@ -671,8 +727,9 @@ func (dirwatch *Dirwatch) Start(controller *Controller) error {
func (dirwatch *Dirwatch) Stop() { func (dirwatch *Dirwatch) Stop() {
if dirwatch.watcher != nil { if dirwatch.watcher != nil {
dirwatch.watcher.Close() w := dirwatch.watcher
dirwatch.watcher = nil dirwatch.watcher = nil
w.Close()
} }
} }
@ -699,8 +756,7 @@ func (dirwatches *Dirwatches) FromMap(f []any) *Dirwatches {
for _, f := range f { for _, f := range f {
switch v := f.(type) { switch v := f.(type) {
case map[string]any: case map[string]any:
dirwatch := &Dirwatch{} dirwatch := NewDirwatch().FromMap(v)
dirwatch.FromMap(v)
dirwatches.List = append(dirwatches.List, dirwatch) dirwatches.List = append(dirwatches.List, dirwatch)
} }
} }
@ -739,7 +795,7 @@ func (dirwatches *Dirwatches) Read(db *Database) error {
} }
for rows.Next() { for rows.Next() {
dirwatch := &Dirwatch{} dirwatch := NewDirwatch()
if err = rows.Scan(&id, &delay, &dirwatch.DeleteAfter, &dirwatch.Directory, &dirwatch.Disabled, &extension, &frequency, &mask, &order, &systemId, &talkgroupId, &kind, &dirwatch.UsePolling); err != nil { if err = rows.Scan(&id, &delay, &dirwatch.DeleteAfter, &dirwatch.Directory, &dirwatch.Disabled, &extension, &frequency, &mask, &order, &systemId, &talkgroupId, &kind, &dirwatch.UsePolling); err != nil {
break break

View file

@ -75,7 +75,7 @@ func (ffmpeg *FFMpeg) Convert(call *Call, systems *Systems, tags *Tags, mode uin
if !ffmpeg.warned { if !ffmpeg.warned {
ffmpeg.warned = true ffmpeg.warned = true
return errors.New("ffmpeg is not available, no audio conversion will be performed.") return errors.New("ffmpeg is not available, no audio conversion will be performed")
} }
return nil return nil
} }

View file

@ -18,7 +18,7 @@ package main
import ( import (
"crypto/tls" "crypto/tls"
"fmt" "fmt"
"io/ioutil" "io"
"log" "log"
"mime" "mime"
"net/http" "net/http"
@ -198,7 +198,7 @@ func main() {
TLSConfig: tlsConfig, TLSConfig: tlsConfig,
ReadTimeout: 30 * time.Second, ReadTimeout: 30 * time.Second,
WriteTimeout: 30 * time.Second, WriteTimeout: 30 * time.Second,
ErrorLog: log.New(ioutil.Discard, "", 0), ErrorLog: log.New(io.Discard, "", 0),
} }
s.SetKeepAlivesEnabled(true) s.SetKeepAlivesEnabled(true)

View file

@ -21,13 +21,70 @@ import (
"mime" "mime"
"mime/multipart" "mime/multipart"
"path" "path"
"path/filepath"
"regexp" "regexp"
"strconv" "strconv"
"strings"
"time" "time"
"github.com/dhowden/tag" "github.com/dhowden/tag"
) )
func ParseDSDPlusMeta(call *Call, fp string) error {
dir := filepath.Dir(fp)
base := strings.TrimSuffix(filepath.Base(fp), filepath.Ext(fp))
if d := regexp.MustCompile(`([0-9]+)$`).FindStringSubmatch(dir); len(d) == 2 && len(d[1]) == 8 {
if t := regexp.MustCompile(`^([0-9]+)`).FindStringSubmatch(base); len(t) == 2 && len(t[1]) == 6 {
if dy, err := strconv.Atoi(d[1][0:4]); err == nil {
if dm, err := strconv.Atoi(d[1][4:6]); err == nil {
if dd, err := strconv.Atoi(d[1][6:8]); err == nil {
if th, err := strconv.Atoi(t[1][0:2]); err == nil {
if tm, err := strconv.Atoi(t[1][2:4]); err == nil {
if ts, err := strconv.Atoi(t[1][4:6]); err == nil {
call.DateTime = time.Date(dy, time.Month(dm), dd, th, tm, ts, 0, time.Now().Location()).UTC()
}
}
}
}
}
}
}
}
if s := regexp.MustCompile(`^[0-9]+_[0-9]+_[^_]+_([0-9]+)-([0-9]+)_`).FindStringSubmatch(base); len(s) == 3 {
if sys, err := strconv.Atoi(s[1]); err == nil && sys > 0 {
call.System = uint(sys)
}
}
tu := regexp.MustCompile(`([0-9]+)_([0-9]+)$|([0-9]+)_([0-9]+)\[([^\]]*)\]$`).FindStringSubmatch(base)
if len(tu[3]) > 0 {
tu[1] = tu[3]
}
if len(tu[4]) > 0 {
tu[2] = tu[4]
}
if tg, err := strconv.Atoi(tu[1]); err == nil && tg > 0 {
call.Talkgroup = uint(tg)
if src, err := strconv.Atoi(tu[2]); err == nil && src > 0 {
call.Source = uint(src)
if len(tu[5]) > 0 && strings.TrimSpace(tu[5]) != strings.TrimSpace(tu[2]) {
if b := regexp.MustCompile(`^([\.\-\ ,_]+)$`).MatchString(tu[5]); !b {
units := NewUnits()
units.Add(uint(src), tu[5])
call.units = units
}
}
}
}
return nil
}
func ParseSdrTrunkMeta(call *Call, controller *Controller) error { func ParseSdrTrunkMeta(call *Call, controller *Controller) error {
var ( var (
s []string s []string
@ -43,7 +100,7 @@ func ParseSdrTrunkMeta(call *Call, controller *Controller) error {
s = regexp.MustCompile(`^([0-9]+) ?(.*)$`).FindStringSubmatch(m.Artist()) s = regexp.MustCompile(`^([0-9]+) ?(.*)$`).FindStringSubmatch(m.Artist())
if len(s) >= 2 { if len(s) >= 2 {
if i, err = strconv.Atoi(s[1]); err != nil { if i, err = strconv.Atoi(s[1][0:1]); err != nil {
return err return err
} }
if i > 0 { if i > 0 {

View file

@ -15,4 +15,4 @@
package main package main
const Version = "6.5.3" const Version = "6.5.4"