mirror of
https://github.com/chuot/rdio-scanner.git
synced 2026-08-13 08:33:00 -06:00
Version 6.5.4
This commit is contained in:
parent
bf75f92f47
commit
d03f0b1fef
|
|
@ -31,6 +31,14 @@ _v6.5.3_
|
|||
- 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).
|
||||
|
||||
_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
|
||||
|
||||
- New `-cmd` command line options to allow advanced administrative tasks.
|
||||
|
|
|
|||
16
COMPILING.md
16
COMPILING.md
|
|
@ -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.
|
||||
|
||||
rdio-scanner-darwin-amd64-v6.5.3.zip
|
||||
rdio-scanner-darwin-arm64-v6.5.3.zip
|
||||
rdio-scanner-freebsd-amd64-v6.5.3.zip
|
||||
rdio-scanner-linux-386-v6.5.3.zip
|
||||
rdio-scanner-linux-amd64-v6.5.3.zip
|
||||
rdio-scanner-linux-arm64-v6.5.3.zip
|
||||
rdio-scanner-linux-arm-v6.5.3.zip
|
||||
rdio-scanner-windows-amd64-v6.5.3.zip
|
||||
rdio-scanner-darwin-amd64-v6.5.4.zip
|
||||
rdio-scanner-darwin-arm64-v6.5.4.zip
|
||||
rdio-scanner-freebsd-amd64-v6.5.4.zip
|
||||
rdio-scanner-linux-386-v6.5.4.zip
|
||||
rdio-scanner-linux-amd64-v6.5.4.zip
|
||||
rdio-scanner-linux-arm64-v6.5.4.zip
|
||||
rdio-scanner-linux-arm-v6.5.4.zip
|
||||
rdio-scanner-windows-amd64-v6.5.4.zip
|
||||
|
||||
**Happy Rdio scanning !**
|
||||
4
Makefile
4
Makefile
|
|
@ -16,8 +16,8 @@
|
|||
################################################################################
|
||||
|
||||
app := rdio-scanner
|
||||
date := 2022/08/10
|
||||
ver := 6.5.3
|
||||
date := 2022/09/09
|
||||
ver := 6.5.4
|
||||
|
||||
client := $(wildcard client/*.json client/*.ts)
|
||||
server := $(wildcard server/*.go)
|
||||
|
|
|
|||
|
|
@ -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 |
|
||||
| [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 | |
|
||||
| [ProScan](https://www.proscan.org/) | | X |
|
||||
| [DSDPlus Fast Lane](https://https://www.dsdplus.com/) | | X |
|
||||
|
||||
# Quick start
|
||||
|
||||
|
|
|
|||
4
client/package-lock.json
generated
4
client/package-lock.json
generated
|
|
@ -1,12 +1,12 @@
|
|||
{
|
||||
"name": "rdio-scanner",
|
||||
"version": "6.5.3",
|
||||
"version": "6.5.4",
|
||||
"lockfileVersion": 2,
|
||||
"requires": true,
|
||||
"packages": {
|
||||
"": {
|
||||
"name": "rdio-scanner",
|
||||
"version": "6.5.2",
|
||||
"version": "6.5.4",
|
||||
"license": "LICENSE",
|
||||
"dependencies": {
|
||||
"@angular/animations": "^13.3.10",
|
||||
|
|
|
|||
|
|
@ -38,5 +38,5 @@
|
|||
"build": "ng build --base-href ./ --configuration production",
|
||||
"start": "ng serve"
|
||||
},
|
||||
"version": "6.5.3"
|
||||
"version": "6.5.4"
|
||||
}
|
||||
|
|
|
|||
|
|
@ -637,6 +637,10 @@ export class RdioScannerAdminService implements OnDestroy {
|
|||
return null;
|
||||
}
|
||||
|
||||
if (control.value.startsWith('\\')) {
|
||||
return { network: true }
|
||||
}
|
||||
|
||||
const dirWatch: DirWatch[] = control.parent?.parent?.getRawValue() || [];
|
||||
|
||||
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;
|
||||
|
||||
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;
|
||||
|
||||
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 };
|
||||
};
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -33,6 +33,28 @@
|
|||
<mat-slide-toggle color="primary" formControlName="deleteAfter"></mat-slide-toggle>
|
||||
</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">
|
||||
<p>
|
||||
<span class="mat-body">Directory</span><br>
|
||||
|
|
@ -49,9 +71,12 @@
|
|||
<mat-error *ngIf="dirWatch.get('directory')?.hasError('duplicate')">
|
||||
Directory is already defined
|
||||
</mat-error>
|
||||
<mat-error *ngIf="dirWatch.get('directory')?.hasError('network')">
|
||||
Incompatible network folder
|
||||
</mat-error>
|
||||
</mat-form-field>
|
||||
</div>
|
||||
<div class="row">
|
||||
<div class="row" *ngIf="['default','dsdplus','trunk-recorder'].includes(dirWatch.get('type')?.value)">
|
||||
<p>
|
||||
<span class="mat-body">Extension</span><br>
|
||||
<span class="mat-caption">The audio call extension to monitor without the period. Ex.: "mp3",
|
||||
|
|
@ -64,29 +89,14 @@
|
|||
</mat-error>
|
||||
</mat-form-field>
|
||||
</div>
|
||||
<div class="row">
|
||||
<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">
|
||||
<div class="row" *ngIf="['default','dsdplus'].includes(dirWatch.get('type')?.value)">
|
||||
<p>
|
||||
<span class="mat-body">System</span><br>
|
||||
<span class="mat-caption">System to where the audio files should go.</span>
|
||||
</p>
|
||||
<mat-form-field floatLabel="never">
|
||||
<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">
|
||||
{{ system.value.label }}
|
||||
</mat-option>
|
||||
|
|
@ -96,14 +106,14 @@
|
|||
</mat-error>
|
||||
</mat-form-field>
|
||||
</div>
|
||||
<div class="row">
|
||||
<div class="row" *ngIf="['default'].includes(dirWatch.get('type')?.value)">
|
||||
<p>
|
||||
<span class="mat-body">Talkgroup</span><br>
|
||||
<span class="mat-caption">Talkgroup to where the audio files should go.</span>
|
||||
</p>
|
||||
<mat-form-field floatLabel="never">
|
||||
<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] || []"
|
||||
[value]="talkgroup.value.id">
|
||||
{{ talkgroup.value.label }}
|
||||
|
|
@ -114,7 +124,7 @@
|
|||
</mat-error>
|
||||
</mat-form-field>
|
||||
</div>
|
||||
<div class="row">
|
||||
<div class="row" *ngIf="['default'].includes(dirWatch.get('type')?.value)">
|
||||
<p>
|
||||
<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
|
||||
|
|
@ -156,7 +166,7 @@
|
|||
</mat-error>
|
||||
</mat-form-field>
|
||||
</div>
|
||||
<div class="row">
|
||||
<div class="row" *ngIf="['default'].includes(dirWatch.get('type')?.value)">
|
||||
<p>
|
||||
<span class="mat-body">Frequency</span><br>
|
||||
<span class="mat-caption">Fake frequency in hertz displayed on the main screen.</span>
|
||||
|
|
@ -168,7 +178,7 @@
|
|||
</mat-error>
|
||||
</mat-form-field>
|
||||
</div>
|
||||
<div class="row">
|
||||
<div class="row" *ngIf="['default'].includes(dirWatch.get('type')?.value)">
|
||||
<p>
|
||||
<span class="mat-body">Delay</span><br>
|
||||
<span class="mat-caption">Depending on the recorder, audio files can be ingested too soon after the
|
||||
|
|
|
|||
|
|
@ -53,12 +53,12 @@ $ docker run --detach --env TZ=America/Toronto --name rdio-scanner --publish 300
|
|||
520cdbf51fca11d8bacea12d81245f1cb4d984f80d2be2e3039727b59533a6a9
|
||||
|
||||
$ docker logs rdio-scanner
|
||||
Rdio Scanner v6.5.3
|
||||
Rdio Scanner v6.5.4
|
||||
----------------------------------
|
||||
2022/08/10 08:57:23 server started
|
||||
2022/08/10 08:57:23 base folder is /app/data
|
||||
2022/08/10 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 server started
|
||||
2022/09/09 08:57:23 base folder is /app/data
|
||||
2022/09/09 08:57:23 main interface at http://6d87e8dc37a0:3000
|
||||
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`:
|
||||
|
|
|
|||
|
|
@ -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 |
|
||||
| [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 | |
|
||||
| [ProScan](https://www.proscan.org/) | | X |
|
||||
| [DSDPlus Fast Lane](https://https://www.dsdplus.com/) | | X |
|
||||
|
||||
# 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 ~ % cd rdio-scanner
|
||||
rdio@macos rdio-scanner % unzip \
|
||||
> ~/Downloads/rdio-scanner-darwin-arm64-v6.5.3.zip
|
||||
Archive: /Users/rdio/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.4.zip
|
||||
inflating: rdio-scanner
|
||||
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 Scanner v6.5.3
|
||||
Rdio Scanner v6.5.4
|
||||
----------------------------------
|
||||
2022/08/10 08:38:06 server started
|
||||
2022/08/10 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 server started
|
||||
2022/09/09 08:38:06 main interface at http://macos.local:3000
|
||||
2022/09/09 08:38:06 admin interface at http://macos.local:3000/admin
|
||||
|
||||
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 Scanner v6.5.3
|
||||
Rdio Scanner v6.5.4
|
||||
----------------------------------
|
||||
2022/08/10 08:48:03 server started
|
||||
2022/08/10 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 server started
|
||||
2022/09/09 08:48:03 main interface at http://macos.local
|
||||
2022/09/09 08:48:03 admin interface at http://macos.local/admin
|
||||
|
||||
## 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_listen :443
|
||||
|
||||
Rdio Scanner v6.5.3
|
||||
Rdio Scanner v6.5.4
|
||||
----------------------------------
|
||||
2022/08/10 08:50:58 server started
|
||||
2022/08/10 08:50:58 main interface at http://macos.local
|
||||
2022/08/10 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 server started
|
||||
2022/09/09 08:50:58 main interface at http://macos.local
|
||||
2022/09/09 08:50:58 main interface at https://macos.local
|
||||
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.
|
||||
|
||||
|
|
@ -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_listen :443 \
|
||||
> -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.
|
||||
|
||||
|
|
@ -132,12 +133,12 @@ Then simply run [Rdio Scanner](https://github.com/chuot/rdio-scanner) without an
|
|||
|
||||
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/08/10 08:54:08 main interface at http://macos.local
|
||||
2022/08/10 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 server started
|
||||
2022/09/09 08:54:08 main interface at http://macos.local
|
||||
2022/09/09 08:54:08 main interface at https://macos.local
|
||||
2022/09/09 08:54:08 admin interface at https://macos.local/admin
|
||||
|
||||
## Install Rdio Scanner as a service
|
||||
|
||||
|
|
|
|||
|
|
@ -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 |
|
||||
| [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 | |
|
||||
| [ProScan](https://www.proscan.org/) | | X |
|
||||
| [DSDPlus Fast Lane](https://https://www.dsdplus.com/) | | X |
|
||||
|
||||
# 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:~ $ cd rdio-scanner
|
||||
rdio@pc-freebsd:~/rdio-scanner $ unzip \
|
||||
> ~/rdio-scanner-freebsd-amd64-v6.5.3.zip
|
||||
Archive: ../rdio-scanner-freebsd-amd64-v6.5.3.zip
|
||||
> ~/rdio-scanner-freebsd-amd64-v6.5.4.zip
|
||||
Archive: ../rdio-scanner-freebsd-amd64-v6.5.4.zip
|
||||
extracting: rdio-scanner
|
||||
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 Scanner v6.5.3
|
||||
Rdio Scanner v6.5.4
|
||||
----------------------------------
|
||||
2022/08/10 08:16:36 server started
|
||||
2022/08/10 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 server started
|
||||
2022/09/09 08:16:36 main interface at http://pc-freebsd:3000
|
||||
2022/09/09 08:16:36 admin interface at http://pc-freebsd:3000/admin
|
||||
|
||||
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:
|
||||
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/08/10 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 server started
|
||||
2022/09/09 08:19:38 main interface at http://pc-freebsd
|
||||
2022/09/09 08:19:38 admin interface at http://pc-freebsd/admin
|
||||
|
||||
## 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_listen :443
|
||||
|
||||
Rdio Scanner v6.5.3
|
||||
Rdio Scanner v6.5.4
|
||||
----------------------------------
|
||||
2022/08/10 10:34:29 server started
|
||||
2022/08/10 10:34:29 main interface at http://pc-freebsd
|
||||
2022/08/10 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 server started
|
||||
2022/09/09 10:34:29 main interface at http://pc-freebsd
|
||||
2022/09/09 10:34:29 main interface at https://pc-freebsd
|
||||
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.
|
||||
|
||||
|
|
@ -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_listen :443 \
|
||||
> -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.
|
||||
|
||||
|
|
@ -142,12 +143,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.5.3
|
||||
Rdio Scanner v6.5.4
|
||||
----------------------------------
|
||||
2022/08/10 10:38:28 server started
|
||||
2022/08/10 10:38:29 main interface at http://pc-freebsd
|
||||
2022/08/10 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:28 server started
|
||||
2022/09/09 10:38:29 main interface at http://pc-freebsd
|
||||
2022/09/09 10:38:29 main interface at https://pc-freebsd
|
||||
2022/09/09 10:38:29 admin interface at https://pc-freebsd/admin
|
||||
|
||||
## Install Rdio Scanner as a service
|
||||
|
||||
|
|
|
|||
|
|
@ -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 |
|
||||
| [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 | |
|
||||
| [ProScan](https://www.proscan.org/) | | X |
|
||||
| [DSDPlus Fast Lane](https://https://www.dsdplus.com/) | | X |
|
||||
|
||||
# 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 ~]$ cd rdio-scanner
|
||||
[rdio@pc-linux rdio-scanner]$ unzip \
|
||||
> ~/Downloads/rdio-scanner-linux-amd64-v6.5.3.zip
|
||||
Archive: /home/rdio/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.4.zip
|
||||
inflating: rdio-scanner
|
||||
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 Scanner v6.5.3
|
||||
Rdio Scanner v6.5.4
|
||||
----------------------------------
|
||||
2022/08/10 09:11:48 server started
|
||||
2022/08/10 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 server started
|
||||
2022/09/09 09:11:48 main interface at http://pc-linux:3000
|
||||
2022/09/09 09:11:48 admin interface at http://pc-linux:3000/admin
|
||||
|
||||
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
|
||||
[sudo] password for rdio:
|
||||
|
||||
Rdio Scanner v6.5.3
|
||||
Rdio Scanner v6.5.4
|
||||
----------------------------------
|
||||
2022/08/10 09:14:00 server started
|
||||
2022/08/10 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 server started
|
||||
2022/09/09 09:14:00 main interface at http://pc-linux
|
||||
2022/09/09 09:14:00 admin interface at http://pc-linux/admin
|
||||
|
||||
## 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
|
||||
[sudo] password for rdio:
|
||||
|
||||
Rdio Scanner v6.5.3
|
||||
Rdio Scanner v6.5.4
|
||||
----------------------------------
|
||||
2022/08/10 09:16:47 server started
|
||||
2022/08/10 09:16:47 main interface at http://pc-linux
|
||||
2022/08/10 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 server started
|
||||
2022/09/09 09:16:47 main interface at http://pc-linux
|
||||
2022/09/09 09:16:47 main interface at https://pc-linux
|
||||
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.
|
||||
|
||||
|
|
@ -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_listen :443 \
|
||||
> -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.
|
||||
|
||||
|
|
@ -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
|
||||
[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/08/10 09:20:40 main interface at http://pc-linux
|
||||
2022/08/10 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 server started
|
||||
2022/09/09 09:20:40 main interface at http://pc-linux
|
||||
2022/09/09 09:20:40 main interface at https://pc-linux
|
||||
2022/09/09 09:20:40 admin interface at https://pc-linux/admin
|
||||
|
||||
## Install Rdio Scanner as a service
|
||||
|
||||
|
|
|
|||
|
|
@ -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 |
|
||||
| [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 | |
|
||||
| [ProScan](https://www.proscan.org/) | | X |
|
||||
| [DSDPlus Fast Lane](https://https://www.dsdplus.com/) | | X |
|
||||
|
||||
# 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\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.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
|
||||
|
||||
Rdio Scanner v6.5.3
|
||||
Rdio Scanner v6.5.4
|
||||
----------------------------------
|
||||
2022/08/10 13:48:48 server started
|
||||
2022/08/10 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 server started
|
||||
2022/09/09 13:48:48 main interface at http://pc-windows:3000
|
||||
2022/09/09 13:48:48 admin interface at http://pc-windows:3000/admin
|
||||
|
||||
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
|
||||
|
||||
Rdio Scanner v6.5.3
|
||||
Rdio Scanner v6.5.4
|
||||
----------------------------------
|
||||
2022/08/10 10:53:31 server started
|
||||
2022/08/10 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 server started
|
||||
2022/09/09 10:53:31 main interface at http://pc-windows
|
||||
2022/09/09 10:53:31 admin interface at http://pc-windows/admin
|
||||
|
||||
## 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_listen :443
|
||||
|
||||
Rdio Scanner v6.5.3
|
||||
Rdio Scanner v6.5.4
|
||||
----------------------------------
|
||||
2022/08/10 11:05:43 server started
|
||||
2022/08/10 11:05:43 main interface at http://pc-windows
|
||||
2022/08/10 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 server started
|
||||
2022/09/09 11:05:43 main interface at http://pc-windows
|
||||
2022/09/09 11:05:43 main interface at https://pc-windows
|
||||
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.
|
||||
|
||||
|
|
@ -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_listen :443 ^
|
||||
-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.
|
||||
|
||||
|
|
@ -133,12 +134,12 @@ Then simply run [Rdio Scanner](https://github.com/chuot/rdio-scanner) without an
|
|||
|
||||
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/08/10 11:11:28 main interface at http://pc-windows
|
||||
2022/08/10 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 server started
|
||||
2022/09/09 11:11:28 main interface at http://pc-windows
|
||||
2022/09/09 11:11:28 main interface at https://pc-windows
|
||||
2022/09/09 11:11:28 admin interface at https://pc-windows/admin
|
||||
|
||||
\pagebreak{}
|
||||
|
||||
|
|
|
|||
|
|
@ -338,6 +338,8 @@ func (calls *Calls) Search(searchOptions *CallsSearchOptions, client *Client) (*
|
|||
|
||||
if t, err = db.ParseDateTime(dateTime); err == nil {
|
||||
searchResults.DateStop = t
|
||||
} else {
|
||||
searchResults.DateStop = time.Now()
|
||||
}
|
||||
|
||||
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)
|
||||
|
||||
} 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()))
|
||||
stop = start.Add(time.Hour*24 - time.Millisecond - 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 = 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))
|
||||
|
|
|
|||
|
|
@ -206,91 +206,90 @@ func (client *Client) SendListenersCount(count int) {
|
|||
}
|
||||
|
||||
type Clients struct {
|
||||
Map sync.Map
|
||||
count int
|
||||
Map map[*Client]bool
|
||||
mutex sync.Mutex
|
||||
}
|
||||
|
||||
func NewClients() *Clients {
|
||||
return &Clients{
|
||||
Map: sync.Map{},
|
||||
count: 0,
|
||||
Map: map[*Client]bool{},
|
||||
mutex: sync.Mutex{},
|
||||
}
|
||||
}
|
||||
|
||||
func (clients *Clients) AccessCount(client *Client) int {
|
||||
clients.mutex.Lock()
|
||||
defer clients.mutex.Unlock()
|
||||
|
||||
count := 0
|
||||
|
||||
clients.Map.Range(func(k any, _ any) bool {
|
||||
switch c := k.(type) {
|
||||
case *Client:
|
||||
if c.Access == client.Access {
|
||||
count++
|
||||
}
|
||||
for c := range clients.Map {
|
||||
if c.Access == client.Access {
|
||||
count++
|
||||
}
|
||||
return true
|
||||
})
|
||||
}
|
||||
|
||||
return count
|
||||
}
|
||||
|
||||
func (clients *Clients) Add(client *Client) {
|
||||
clients.count = clients.count + 1
|
||||
clients.Map.Store(client, true)
|
||||
clients.mutex.Lock()
|
||||
defer clients.mutex.Unlock()
|
||||
|
||||
clients.Map[client] = true
|
||||
}
|
||||
|
||||
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) {
|
||||
clients.Map.Range(func(k any, _ any) bool {
|
||||
switch c := k.(type) {
|
||||
case *Client:
|
||||
if (!restricted || c.Access.HasAccess(call)) && c.Livefeed.IsEnabled(call) {
|
||||
c.Send <- &Message{Command: MessageCommandCall, Payload: call}
|
||||
}
|
||||
}
|
||||
clients.mutex.Lock()
|
||||
defer clients.mutex.Unlock()
|
||||
|
||||
return true
|
||||
})
|
||||
for c := range clients.Map {
|
||||
if (!restricted || c.Access.HasAccess(call)) && c.Livefeed.IsEnabled(call) {
|
||||
c.Send <- &Message{Command: MessageCommandCall, Payload: call}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
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 {
|
||||
switch c := k.(type) {
|
||||
case *Client:
|
||||
if restricted {
|
||||
c.Send <- &Message{Command: MessageCommandPin}
|
||||
} else {
|
||||
c.SendConfig(groups, options, systems, tags)
|
||||
}
|
||||
count := len(clients.Map)
|
||||
|
||||
if options.ShowListenersCount {
|
||||
c.SendListenersCount(count)
|
||||
}
|
||||
for c := range clients.Map {
|
||||
if restricted {
|
||||
c.Send <- &Message{Command: MessageCommandPin}
|
||||
} else {
|
||||
c.SendConfig(groups, options, systems, tags)
|
||||
}
|
||||
|
||||
return true
|
||||
})
|
||||
if options.ShowListenersCount {
|
||||
c.SendListenersCount(count)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func (clients *Clients) EmitListenersCount() {
|
||||
count := clients.Count()
|
||||
clients.mutex.Lock()
|
||||
defer clients.mutex.Unlock()
|
||||
|
||||
clients.Map.Range(func(k any, _ any) bool {
|
||||
switch c := k.(type) {
|
||||
case *Client:
|
||||
c.SendListenersCount(count)
|
||||
}
|
||||
count := len(clients.Map)
|
||||
|
||||
return true
|
||||
})
|
||||
for c := range clients.Map {
|
||||
c.SendListenersCount(count)
|
||||
}
|
||||
}
|
||||
|
||||
func (clients *Clients) Remove(client *Client) {
|
||||
if _, loaded := clients.Map.LoadAndDelete(client); loaded {
|
||||
clients.count = clients.count - 1
|
||||
}
|
||||
clients.mutex.Lock()
|
||||
defer clients.mutex.Unlock()
|
||||
|
||||
delete(clients.Map, client)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -138,7 +138,7 @@ func (command *Command) Do(action string) {
|
|||
command.url = readVal()
|
||||
|
||||
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:
|
||||
command.exitWithError(errors.New("Invalid response"))
|
||||
command.exitWithError(errors.New("invalid response"))
|
||||
}
|
||||
} else {
|
||||
command.exitWithError(err)
|
||||
|
|
@ -314,10 +314,10 @@ func (command *Command) login() {
|
|||
}
|
||||
fmt.Println("Logged in.")
|
||||
default:
|
||||
command.exitWithError(errors.New("No token in response"))
|
||||
command.exitWithError(errors.New("no token in response"))
|
||||
}
|
||||
default:
|
||||
command.exitWithError(errors.New("Invalid response"))
|
||||
command.exitWithError(errors.New("invalid response"))
|
||||
}
|
||||
} else {
|
||||
command.exitWithError(err)
|
||||
|
|
|
|||
|
|
@ -23,7 +23,6 @@ import (
|
|||
"os"
|
||||
"os/signal"
|
||||
"strconv"
|
||||
"sync"
|
||||
"time"
|
||||
)
|
||||
|
||||
|
|
@ -48,7 +47,6 @@ type Controller struct {
|
|||
Register chan *Client
|
||||
Unregister chan *Client
|
||||
Ingest chan *Call
|
||||
ingestMutex sync.Mutex
|
||||
running bool
|
||||
}
|
||||
|
||||
|
|
@ -271,7 +269,7 @@ func (controller *Controller) IngestCall(call *Call) {
|
|||
}
|
||||
|
||||
if system == nil || talkgroup == nil {
|
||||
logCall(call, LogLevelInfo, "no matching system/talkgroup")
|
||||
logCall(call, LogLevelWarn, "no matching system/talkgroup")
|
||||
return
|
||||
}
|
||||
|
||||
|
|
@ -516,7 +514,7 @@ func (controller *Controller) Start() error {
|
|||
}
|
||||
|
||||
go func() {
|
||||
c := make(chan os.Signal)
|
||||
c := make(chan os.Signal, 8)
|
||||
signal.Notify(c, os.Interrupt)
|
||||
<-c
|
||||
controller.Terminate()
|
||||
|
|
@ -536,7 +534,10 @@ func (controller *Controller) Start() error {
|
|||
if timer != nil {
|
||||
timer.Stop()
|
||||
}
|
||||
|
||||
timer = time.AfterFunc(time.Second, func() {
|
||||
timer = nil
|
||||
|
||||
controller.LogClientsCount()
|
||||
|
||||
if controller.Options.ShowListenersCount {
|
||||
|
|
|
|||
|
|
@ -21,7 +21,6 @@ import (
|
|||
"errors"
|
||||
"fmt"
|
||||
"io/fs"
|
||||
"io/ioutil"
|
||||
"math"
|
||||
"mime"
|
||||
"os"
|
||||
|
|
@ -38,9 +37,10 @@ import (
|
|||
)
|
||||
|
||||
const (
|
||||
DirwatchKindDefault = "default"
|
||||
DirwatchKindSdrTrunk = "sdr-trunk"
|
||||
DirwatchKindTrunkRecorder = "trunk-recorder"
|
||||
DirwatchTypeDefault = "default"
|
||||
DirwatchTypeDSDPlus = "dsdplus"
|
||||
DirwatchTypeSdrTrunk = "sdr-trunk"
|
||||
DirwatchTypeTrunkRecorder = "trunk-recorder"
|
||||
)
|
||||
|
||||
type Dirwatch struct {
|
||||
|
|
@ -59,9 +59,19 @@ type Dirwatch struct {
|
|||
UsePolling bool `json:"usePolling"`
|
||||
controller *Controller
|
||||
dirs map[string]bool
|
||||
mutex sync.Mutex
|
||||
timers map[string]*time.Timer
|
||||
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 {
|
||||
switch v := m["_id"].(type) {
|
||||
case float64:
|
||||
|
|
@ -135,16 +145,18 @@ func (dirwatch *Dirwatch) Ingest(p string) {
|
|||
var err error
|
||||
|
||||
switch dirwatch.Kind {
|
||||
case DirwatchKindTrunkRecorder:
|
||||
case DirwatchTypeDSDPlus:
|
||||
err = dirwatch.ingestDSDPlus(p)
|
||||
case DirwatchTypeTrunkRecorder:
|
||||
err = dirwatch.ingestTrunkRecorder(p)
|
||||
case DirwatchKindSdrTrunk:
|
||||
case DirwatchTypeSdrTrunk:
|
||||
err = dirwatch.ingestSdrTrunk(p)
|
||||
default:
|
||||
err = dirwatch.ingestDefault(p)
|
||||
}
|
||||
|
||||
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) {
|
||||
case string:
|
||||
ext = fmt.Sprintf(".%s", v)
|
||||
if len(v) > 0 {
|
||||
ext = fmt.Sprintf(".%s", v)
|
||||
} else {
|
||||
ext = ".wav"
|
||||
}
|
||||
default:
|
||||
ext = ".wav"
|
||||
}
|
||||
|
|
@ -202,7 +218,7 @@ func (dirwatch *Dirwatch) ingestDefault(p string) error {
|
|||
return err
|
||||
}
|
||||
|
||||
func (dirwatch *Dirwatch) ingestSdrTrunk(p string) error {
|
||||
func (dirwatch *Dirwatch) ingestDSDPlus(p string) error {
|
||||
var (
|
||||
err error
|
||||
ext string
|
||||
|
|
@ -229,6 +245,48 @@ func (dirwatch *Dirwatch) ingestSdrTrunk(p string) error {
|
|||
call.AudioType = mime.TypeByExtension(path.Ext(p))
|
||||
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 {
|
||||
return err
|
||||
}
|
||||
|
|
@ -290,11 +348,11 @@ func (dirwatch *Dirwatch) ingestTrunkRecorder(p string) error {
|
|||
call.System = v
|
||||
}
|
||||
|
||||
if call.Audio, err = ioutil.ReadFile(audioName); err != nil {
|
||||
if call.Audio, err = os.ReadFile(audioName); err != nil {
|
||||
return nil
|
||||
}
|
||||
|
||||
if b, err = ioutil.ReadFile(p); err != nil {
|
||||
if b, err = os.ReadFile(p); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
|
|
@ -550,15 +608,16 @@ func (dirwatch *Dirwatch) Start(controller *Controller) error {
|
|||
}
|
||||
|
||||
go func() {
|
||||
var timers = sync.Map{}
|
||||
|
||||
logError := func(err error) {
|
||||
controller.Logs.LogEvent(LogLevelError, fmt.Sprintf("dirwatch.watcher: %v", err.Error()))
|
||||
}
|
||||
|
||||
newTimer := func(eventName string) *time.Timer {
|
||||
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 {
|
||||
dirwatch.Ingest(eventName)
|
||||
|
|
@ -567,74 +626,71 @@ func (dirwatch *Dirwatch) Start(controller *Controller) error {
|
|||
}
|
||||
|
||||
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) {
|
||||
case 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 {
|
||||
if dirwatch.watcher == nil {
|
||||
break
|
||||
return
|
||||
}
|
||||
|
||||
select {
|
||||
case event, ok := <-dirwatch.watcher.Events:
|
||||
if ok {
|
||||
switch event.Op {
|
||||
case fsnotify.Create:
|
||||
if dirwatch.isDir(event.Name) {
|
||||
if err := dirwatch.walkDir(event.Name); err != nil {
|
||||
logError(err)
|
||||
}
|
||||
if !ok {
|
||||
return
|
||||
}
|
||||
|
||||
} else {
|
||||
if f, ok := timers.LoadAndDelete(event.Name); ok {
|
||||
switch v := f.(type) {
|
||||
case *time.Timer:
|
||||
v.Stop()
|
||||
}
|
||||
}
|
||||
timers.Store(event.Name, newTimer(event.Name))
|
||||
switch event.Op {
|
||||
case fsnotify.Create:
|
||||
if dirwatch.isDir(event.Name) {
|
||||
if err := dirwatch.walkDir(event.Name); err != nil {
|
||||
logError(err)
|
||||
}
|
||||
|
||||
case fsnotify.Remove:
|
||||
if dirwatch.dirs[event.Name] {
|
||||
if err := dirwatch.watcher.Remove(event.Name); err == nil {
|
||||
delete(dirwatch.dirs, event.Name)
|
||||
} else {
|
||||
logError(err)
|
||||
}
|
||||
} else {
|
||||
if dirwatch.timers[event.Name] != nil {
|
||||
dirwatch.timers[event.Name].Stop()
|
||||
}
|
||||
|
||||
case fsnotify.Write:
|
||||
if f, ok := timers.LoadAndDelete(event.Name); ok {
|
||||
switch v := f.(type) {
|
||||
case *time.Timer:
|
||||
v.Stop()
|
||||
}
|
||||
}
|
||||
timers.Store(event.Name, newTimer(event.Name))
|
||||
dirwatch.timers[event.Name] = newTimer(event.Name)
|
||||
}
|
||||
|
||||
case fsnotify.Remove:
|
||||
if dirwatch.dirs[event.Name] {
|
||||
if err := dirwatch.watcher.Remove(event.Name); err == nil {
|
||||
delete(dirwatch.dirs, event.Name)
|
||||
} else {
|
||||
logError(err)
|
||||
}
|
||||
}
|
||||
|
||||
case fsnotify.Write:
|
||||
if dirwatch.timers[event.Name] != nil {
|
||||
dirwatch.timers[event.Name].Stop()
|
||||
}
|
||||
dirwatch.timers[event.Name] = newTimer(event.Name)
|
||||
}
|
||||
|
||||
case err, ok := <-dirwatch.watcher.Errors:
|
||||
if ok {
|
||||
logError(err)
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
return
|
||||
}
|
||||
}
|
||||
}()
|
||||
|
|
@ -671,8 +727,9 @@ func (dirwatch *Dirwatch) Start(controller *Controller) error {
|
|||
|
||||
func (dirwatch *Dirwatch) Stop() {
|
||||
if dirwatch.watcher != nil {
|
||||
dirwatch.watcher.Close()
|
||||
w := dirwatch.watcher
|
||||
dirwatch.watcher = nil
|
||||
w.Close()
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -699,8 +756,7 @@ func (dirwatches *Dirwatches) FromMap(f []any) *Dirwatches {
|
|||
for _, f := range f {
|
||||
switch v := f.(type) {
|
||||
case map[string]any:
|
||||
dirwatch := &Dirwatch{}
|
||||
dirwatch.FromMap(v)
|
||||
dirwatch := NewDirwatch().FromMap(v)
|
||||
dirwatches.List = append(dirwatches.List, dirwatch)
|
||||
}
|
||||
}
|
||||
|
|
@ -739,7 +795,7 @@ func (dirwatches *Dirwatches) Read(db *Database) error {
|
|||
}
|
||||
|
||||
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 {
|
||||
break
|
||||
|
|
|
|||
|
|
@ -75,7 +75,7 @@ func (ffmpeg *FFMpeg) Convert(call *Call, systems *Systems, tags *Tags, mode uin
|
|||
if !ffmpeg.warned {
|
||||
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
|
||||
}
|
||||
|
|
|
|||
|
|
@ -18,7 +18,7 @@ package main
|
|||
import (
|
||||
"crypto/tls"
|
||||
"fmt"
|
||||
"io/ioutil"
|
||||
"io"
|
||||
"log"
|
||||
"mime"
|
||||
"net/http"
|
||||
|
|
@ -198,7 +198,7 @@ func main() {
|
|||
TLSConfig: tlsConfig,
|
||||
ReadTimeout: 30 * time.Second,
|
||||
WriteTimeout: 30 * time.Second,
|
||||
ErrorLog: log.New(ioutil.Discard, "", 0),
|
||||
ErrorLog: log.New(io.Discard, "", 0),
|
||||
}
|
||||
|
||||
s.SetKeepAlivesEnabled(true)
|
||||
|
|
|
|||
|
|
@ -21,13 +21,70 @@ import (
|
|||
"mime"
|
||||
"mime/multipart"
|
||||
"path"
|
||||
"path/filepath"
|
||||
"regexp"
|
||||
"strconv"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"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 {
|
||||
var (
|
||||
s []string
|
||||
|
|
@ -43,7 +100,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][0:1]); err != nil {
|
||||
return err
|
||||
}
|
||||
if i > 0 {
|
||||
|
|
|
|||
|
|
@ -15,4 +15,4 @@
|
|||
|
||||
package main
|
||||
|
||||
const Version = "6.5.3"
|
||||
const Version = "6.5.4"
|
||||
|
|
|
|||
Loading…
Reference in a new issue