From 60fcf1e0669b40b13e50574fc12cc9e1e9bb2f4a Mon Sep 17 00:00:00 2001 From: Chrystian Huot Date: Wed, 25 May 2022 11:06:22 -0400 Subject: [PATCH] Version 6.3.4 --- CHANGELOG.md | 6 + COMPILING.md | 16 +- Makefile | 4 +- client/package-lock.json | 4 +- client/package.json | 2 +- .../rdio-scanner/main/main.component.ts | 2 +- docs/platforms/darwin.md | 42 ++--- docs/platforms/freebsd.md | 42 ++--- docs/platforms/linux.md | 42 ++--- docs/platforms/windows.md | 40 ++--- server/admin.go | 20 +-- server/config.go | 3 +- server/controller.go | 159 ++++-------------- server/daemon.go | 11 +- server/dirwatch.go | 39 ++--- server/downstream.go | 6 +- server/ffmpeg.go | 122 ++++++++++++++ server/log.go | 42 ++++- server/main.go | 13 +- server/scheduler.go | 8 +- server/version.go | 2 +- 21 files changed, 345 insertions(+), 280 deletions(-) create mode 100644 server/ffmpeg.go diff --git a/CHANGELOG.md b/CHANGELOG.md index 09fe03c..9082939 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -28,6 +28,12 @@ _v6.3.3_ - Show the real IP address in the logs taking into account if behind a proxy. - Fix panic when emitting a call to clients. +_v6.3.4_ + +- Fix ffmpeg audio filter not available on older version (issue #189). +- Improved logging when run as a service, Windows users can now see these logs in the events viewer. +- Dirwatch now catches panic errors and logs them. + ## Version 6.2 - New max clients options which is 200 by default. diff --git a/COMPILING.md b/COMPILING.md index 5044671..222e2d1 100644 --- a/COMPILING.md +++ b/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.3.3.zip - rdio-scanner-darwin-arm64-v6.3.3.zip - rdio-scanner-freebsd-amd64-v6.3.3.zip - rdio-scanner-linux-386-v6.3.3.zip - rdio-scanner-linux-amd64-v6.3.3.zip - rdio-scanner-linux-arm64-v6.3.3.zip - rdio-scanner-linux-arm-v6.3.3.zip - rdio-scanner-windows-amd64-v6.3.3.zip + rdio-scanner-darwin-amd64-v6.3.4.zip + rdio-scanner-darwin-arm64-v6.3.4.zip + rdio-scanner-freebsd-amd64-v6.3.4.zip + rdio-scanner-linux-386-v6.3.4.zip + rdio-scanner-linux-amd64-v6.3.4.zip + rdio-scanner-linux-arm64-v6.3.4.zip + rdio-scanner-linux-arm-v6.3.4.zip + rdio-scanner-windows-amd64-v6.3.4.zip **Happy Rdio scanning !** \ No newline at end of file diff --git a/Makefile b/Makefile index f609644..04faad3 100644 --- a/Makefile +++ b/Makefile @@ -16,8 +16,8 @@ ################################################################################ app := rdio-scanner -date := 2022/05/24 -ver := 6.3.3 +date := 2022/05/25 +ver := 6.3.4 client := $(wildcard client/*.json client/*.ts) server := $(wildcard server/*.go) diff --git a/client/package-lock.json b/client/package-lock.json index 3ab15f8..55d98c9 100644 --- a/client/package-lock.json +++ b/client/package-lock.json @@ -1,12 +1,12 @@ { "name": "rdio-scanner", - "version": "6.3.3", + "version": "6.3.4", "lockfileVersion": 2, "requires": true, "packages": { "": { "name": "rdio-scanner", - "version": "6.3.3", + "version": "6.3.4", "license": "LICENSE", "dependencies": { "@angular/animations": "^13.3.9", diff --git a/client/package.json b/client/package.json index 4d75467..bc8160c 100644 --- a/client/package.json +++ b/client/package.json @@ -38,5 +38,5 @@ "build": "ng build --base-href ./ --configuration production", "start": "ng serve" }, - "version": "6.3.3" + "version": "6.3.4" } diff --git a/client/src/app/components/rdio-scanner/main/main.component.ts b/client/src/app/components/rdio-scanner/main/main.component.ts index ae6f0d3..0abd0b9 100644 --- a/client/src/app/components/rdio-scanner/main/main.component.ts +++ b/client/src/app/components/rdio-scanner/main/main.component.ts @@ -272,7 +272,7 @@ export class RdioScannerMainComponent implements OnDestroy, OnInit { } else { this.rdioScannerService.replay() } - } else { + } else if (this.replayOffset < this.callHistory.length) { this.rdioScannerService.play(this.callHistory[this.replayOffset]); } diff --git a/docs/platforms/darwin.md b/docs/platforms/darwin.md index c8088ed..76559c4 100644 --- a/docs/platforms/darwin.md +++ b/docs/platforms/darwin.md @@ -42,8 +42,8 @@ ALWAYS DOWNLOAD THE LATEST VERSION OF [RDIO SCANNER](https://github.com/chuot/rd rdio@macos ~ % mkdir rdio-scanner rdio@macos ~ % cd rdio-scanner rdio@macos rdio-scanner % unzip \ - > ~/Downloads/rdio-scanner-darwin-arm64-v6.3.3.zip - Archive: /Users/rdio/Downloads/rdio-scanner-darwin-arm64-v6.3.3.zip + > ~/Downloads/rdio-scanner-darwin-arm64-v6.3.4.zip + Archive: /Users/rdio/Downloads/rdio-scanner-darwin-arm64-v6.3.4.zip inflating: rdio-scanner inflating: rdio-scanner.pdf @@ -51,11 +51,11 @@ ALWAYS DOWNLOAD THE LATEST VERSION OF [RDIO SCANNER](https://github.com/chuot/rd rdio@macos rdio-scanner % ./rdio-scanner - Rdio Scanner v6.3.3 + Rdio Scanner v6.3.4 ---------------------------------- - 2022/05/24 08:38:06 server started - 2022/05/24 08:38:06 main interface at http://macos.local:3000 - 2022/05/24 08:38:06 admin interface at http://macos.local:3000/admin + 2022/05/25 08:38:06 server started + 2022/05/25 08:38:06 main interface at http://macos.local:3000 + 2022/05/25 08:38:06 admin interface at http://macos.local:3000/admin 4. Access the administrative dashboard to finalize the configuration. @@ -77,11 +77,11 @@ Here we want our [Rdio Scanner](https://guthub.com/chuot/rdio-scanner) instance rdio@macos rdio-scanner % ./rdio-scanner --listen :80 - Rdio Scanner v6.3.3 + Rdio Scanner v6.3.4 ---------------------------------- - 2022/05/24 08:48:03 server started - 2022/05/24 08:48:03 main interface at http://macos.local - 2022/05/24 08:48:03 admin interface at http://macos.local/admin + 2022/05/25 08:48:03 server started + 2022/05/25 08:48:03 main interface at http://macos.local + 2022/05/25 08:48:03 admin interface at http://macos.local/admin ## Listening on a SSL port @@ -95,12 +95,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.3.3 + Rdio Scanner v6.3.4 ---------------------------------- - 2022/05/24 08:50:58 server started - 2022/05/24 08:50:58 main interface at http://macos.local - 2022/05/24 08:50:58 main interface at https://macos.local - 2022/05/24 08:50:58 admin interface at https://macos.local/admin + 2022/05/25 08:50:58 server started + 2022/05/25 08:50:58 main interface at http://macos.local + 2022/05/25 08:50:58 main interface at https://macos.local + 2022/05/25 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 +118,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/05/24 08:52:24 rdio-scanner.ini file created + 2022/05/25 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 +132,12 @@ Then simply run [Rdio Scanner](https://github.com/chuot/rdio-scanner) without an rdio@macos rdio-scanner % ./rdio-scanner - Rdio Scanner v6.3.3 + Rdio Scanner v6.3.4 ---------------------------------- - 2022/05/24 08:54:08 server started - 2022/05/24 08:54:08 main interface at http://macos.local - 2022/05/24 08:54:08 main interface at https://macos.local - 2022/05/24 08:54:08 admin interface at https://macos.local/admin + 2022/05/25 08:54:08 server started + 2022/05/25 08:54:08 main interface at http://macos.local + 2022/05/25 08:54:08 main interface at https://macos.local + 2022/05/25 08:54:08 admin interface at https://macos.local/admin ## Install Rdio Scanner as a service diff --git a/docs/platforms/freebsd.md b/docs/platforms/freebsd.md index 064e51b..cb05f83 100644 --- a/docs/platforms/freebsd.md +++ b/docs/platforms/freebsd.md @@ -42,8 +42,8 @@ ALWAYS DOWNLOAD THE LATEST VERSION OF [RDIO SCANNER](https://github.com/chuot/rd rdio@pc-freebsd:~ $ mkdir rdio-scanner rdio@pc-freebsd:~ $ cd rdio-scanner rdio@pc-freebsd:~/rdio-scanner $ unzip \ - > ~/rdio-scanner-freebsd-amd64-v6.3.3.zip - Archive: ../rdio-scanner-freebsd-amd64-v6.3.3.zip + > ~/rdio-scanner-freebsd-amd64-v6.3.4.zip + Archive: ../rdio-scanner-freebsd-amd64-v6.3.4.zip extracting: rdio-scanner extracting: rdio-scanner.pdf @@ -51,11 +51,11 @@ ALWAYS DOWNLOAD THE LATEST VERSION OF [RDIO SCANNER](https://github.com/chuot/rd rdio@pc-freebsd:~/rdio-scanner $ ./rdio-scanner - Rdio Scanner v6.3.3 + Rdio Scanner v6.3.4 ---------------------------------- - 2022/05/24 08:16:36 server started - 2022/05/24 08:16:36 main interface at http://pc-freebsd:3000 - 2022/05/24 08:16:36 admin interface at http://pc-freebsd:3000/admin + 2022/05/25 08:16:36 server started + 2022/05/25 08:16:36 main interface at http://pc-freebsd:3000 + 2022/05/25 08:16:36 admin interface at http://pc-freebsd:3000/admin 4. Access the administrative dashboard to finalize the configuration. @@ -81,11 +81,11 @@ Here we want our [Rdio Scanner](https://guthub.com/chuot/rdio-scanner) instance Password: root@pc-freebsd:/home/rdio/rdio-scanner # ./rdio-scanner -listen :80 - Rdio Scanner v6.3.3 + Rdio Scanner v6.3.4 ---------------------------------- - 2022/05/24 08:19:38 server started - 2022/05/24 08:19:38 main interface at http://pc-freebsd - 2022/05/24 08:19:38 admin interface at http://pc-freebsd/admin + 2022/05/25 08:19:38 server started + 2022/05/25 08:19:38 main interface at http://pc-freebsd + 2022/05/25 08:19:38 admin interface at http://pc-freebsd/admin ## Listening on a SSL port @@ -101,12 +101,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.3.3 + Rdio Scanner v6.3.4 ---------------------------------- - 2022/05/24 10:34:29 server started - 2022/05/24 10:34:29 main interface at http://pc-freebsd - 2022/05/24 10:34:29 main interface at https://pc-freebsd - 2022/05/24 10:34:29 admin interface at https://pc-freebsd/admin + 2022/05/25 10:34:29 server started + 2022/05/25 10:34:29 main interface at http://pc-freebsd + 2022/05/25 10:34:29 main interface at https://pc-freebsd + 2022/05/25 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 +126,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/05/24 10:37:00 rdio-scanner.ini file created + 2022/05/25 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 +142,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.3.3 + Rdio Scanner v6.3.4 ---------------------------------- - 2022/05/24 10:38:28 server started - 2022/05/24 10:38:29 main interface at http://pc-freebsd - 2022/05/24 10:38:29 main interface at https://pc-freebsd - 2022/05/24 10:38:29 admin interface at https://pc-freebsd/admin + 2022/05/25 10:38:28 server started + 2022/05/25 10:38:29 main interface at http://pc-freebsd + 2022/05/25 10:38:29 main interface at https://pc-freebsd + 2022/05/25 10:38:29 admin interface at https://pc-freebsd/admin ## Install Rdio Scanner as a service diff --git a/docs/platforms/linux.md b/docs/platforms/linux.md index 6a978d5..bb14bcd 100644 --- a/docs/platforms/linux.md +++ b/docs/platforms/linux.md @@ -42,8 +42,8 @@ ALWAYS DOWNLOAD THE LATEST VERSION OF [RDIO SCANNER](https://github.com/chuot/rd [rdio@pc-linux ~]$ mkdir rdio-scanner [rdio@pc-linux ~]$ cd rdio-scanner [rdio@pc-linux rdio-scanner]$ unzip \ - > ~/Downloads/rdio-scanner-linux-amd64-v6.3.3.zip - Archive: /home/rdio/Downloads/rdio-scanner-linux-amd64-v6.3.3.zip + > ~/Downloads/rdio-scanner-linux-amd64-v6.3.4.zip + Archive: /home/rdio/Downloads/rdio-scanner-linux-amd64-v6.3.4.zip inflating: rdio-scanner inflating: rdio-scanner.pdf @@ -51,11 +51,11 @@ ALWAYS DOWNLOAD THE LATEST VERSION OF [RDIO SCANNER](https://github.com/chuot/rd [rdio@pc-linux rdio-scanner]$ ./rdio-scanner - Rdio Scanner v6.3.3 + Rdio Scanner v6.3.4 ---------------------------------- - 2022/05/24 09:11:48 server started - 2022/05/24 09:11:48 main interface at http://pc-linux:3000 - 2022/05/24 09:11:48 admin interface at http://pc-linux:3000/admin + 2022/05/25 09:11:48 server started + 2022/05/25 09:11:48 main interface at http://pc-linux:3000 + 2022/05/25 09:11:48 admin interface at http://pc-linux:3000/admin 4. Access the administrative dashboard to finalize the configuration. @@ -78,11 +78,11 @@ Here we want our [Rdio Scanner](https://guthub.com/chuot/rdio-scanner) instance [rdio@pc-linux rdio-scanner]$ sudo ./rdio-scanner -listen :80 [sudo] password for rdio: - Rdio Scanner v6.3.3 + Rdio Scanner v6.3.4 ---------------------------------- - 2022/05/24 09:14:00 server started - 2022/05/24 09:14:00 main interface at http://pc-linux - 2022/05/24 09:14:00 admin interface at http://pc-linux/admin + 2022/05/25 09:14:00 server started + 2022/05/25 09:14:00 main interface at http://pc-linux + 2022/05/25 09:14:00 admin interface at http://pc-linux/admin ## Listening on a SSL port @@ -97,12 +97,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.3.3 + Rdio Scanner v6.3.4 ---------------------------------- - 2022/05/24 09:16:47 server started - 2022/05/24 09:16:47 main interface at http://pc-linux - 2022/05/24 09:16:47 main interface at https://pc-linux - 2022/05/24 09:16:47 admin interface at https://pc-linux/admin + 2022/05/25 09:16:47 server started + 2022/05/25 09:16:47 main interface at http://pc-linux + 2022/05/25 09:16:47 main interface at https://pc-linux + 2022/05/25 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 +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/05/24 09:19:29 rdio-scanner.ini file created + 2022/05/25 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 +135,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.3.3 + Rdio Scanner v6.3.4 ---------------------------------- - 2022/05/24 09:20:40 server started - 2022/05/24 09:20:40 main interface at http://pc-linux - 2022/05/24 09:20:40 main interface at https://pc-linux - 2022/05/24 09:20:40 admin interface at https://pc-linux/admin + 2022/05/25 09:20:40 server started + 2022/05/25 09:20:40 main interface at http://pc-linux + 2022/05/25 09:20:40 main interface at https://pc-linux + 2022/05/25 09:20:40 admin interface at https://pc-linux/admin ## Install Rdio Scanner as a service diff --git a/docs/platforms/windows.md b/docs/platforms/windows.md index a58f7da..6e05cce 100644 --- a/docs/platforms/windows.md +++ b/docs/platforms/windows.md @@ -44,7 +44,7 @@ ALWAYS DOWNLOAD THE LATEST VERSION OF [RDIO SCANNER](https://github.com/chuot/rd C:\Users\rdio> cd rdio-scanner C:\Users\rdio\rdio-scanner> tar -xvf ^ - ..\downloads\rdio-scanner-windows-amd64-v6.3.3.zip + ..\downloads\rdio-scanner-windows-amd64-v6.3.4.zip x rdio-scanner.exe x rdio-scanner.pdf @@ -52,11 +52,11 @@ ALWAYS DOWNLOAD THE LATEST VERSION OF [RDIO SCANNER](https://github.com/chuot/rd C:\Users\rdio\rdio-scanner>rdio-scanner - Rdio Scanner v6.3.3 + Rdio Scanner v6.3.4 ---------------------------------- - 2022/05/24 13:48:48 server started - 2022/05/24 13:48:48 main interface at http://pc-windows:3000 - 2022/05/24 13:48:48 admin interface at http://pc-windows:3000/admin + 2022/05/25 13:48:48 server started + 2022/05/25 13:48:48 main interface at http://pc-windows:3000 + 2022/05/25 13:48:48 admin interface at http://pc-windows:3000/admin 4. Access the administrative dashboard to finalize the configuration. @@ -78,11 +78,11 @@ Here we want our [Rdio Scanner](https://guthub.com/chuot/rdio-scanner) instance C:\Users\rdio\rdio-scanner>rdio-scanner -listen :80 - Rdio Scanner v6.3.3 + Rdio Scanner v6.3.4 ---------------------------------- - 2022/05/24 10:53:31 server started - 2022/05/24 10:53:31 main interface at http://pc-windows - 2022/05/24 10:53:31 admin interface at http://pc-windows/admin + 2022/05/25 10:53:31 server started + 2022/05/25 10:53:31 main interface at http://pc-windows + 2022/05/25 10:53:31 admin interface at http://pc-windows/admin ## Listening on a SSL port @@ -96,12 +96,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.3.3 + Rdio Scanner v6.3.4 ---------------------------------- - 2022/05/24 11:05:43 server started - 2022/05/24 11:05:43 main interface at http://pc-windows - 2022/05/24 11:05:43 main interface at https://pc-windows - 2022/05/24 11:05:43 admin interface at https://pc-windows/admin + 2022/05/25 11:05:43 server started + 2022/05/25 11:05:43 main interface at http://pc-windows + 2022/05/25 11:05:43 main interface at https://pc-windows + 2022/05/25 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 +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/05/24 11:08:28 rdio-scanner.ini file created + 2022/05/25 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 +133,12 @@ Then simply run [Rdio Scanner](https://github.com/chuot/rdio-scanner) without an C:\Users\rdio\rdio-scanner>rdio-scanner - Rdio Scanner v6.3.3 + Rdio Scanner v6.3.4 ---------------------------------- - 2022/05/24 11:11:28 server started - 2022/05/24 11:11:28 main interface at http://pc-windows - 2022/05/24 11:11:28 main interface at https://pc-windows - 2022/05/24 11:11:28 admin interface at https://pc-windows/admin + 2022/05/25 11:11:28 server started + 2022/05/25 11:11:28 main interface at http://pc-windows + 2022/05/25 11:11:28 main interface at https://pc-windows + 2022/05/25 11:11:28 admin interface at https://pc-windows/admin \pagebreak{} diff --git a/server/admin.go b/server/admin.go index 74255e9..53f309f 100644 --- a/server/admin.go +++ b/server/admin.go @@ -105,7 +105,7 @@ func (admin *Admin) ChangePassword(currentPassword string, newPassword string) e return err } - admin.Controller.Logs.LogEvent(admin.Controller.Database, LogLevelWarn, "admin password changed.") + admin.Controller.Logs.LogEvent(LogLevelWarn, "admin password changed.") return nil } @@ -149,7 +149,7 @@ func (admin *Admin) ConfigHandler(w http.ResponseWriter, r *http.Request) { }() logError := func(err error) { - admin.Controller.Logs.LogEvent(admin.Controller.Database, LogLevelError, fmt.Sprintf("admin.confighandler.put: %s", err.Error())) + admin.Controller.Logs.LogEvent(LogLevelError, fmt.Sprintf("admin.confighandler.put: %s", err.Error())) } t := admin.GetAuthorization(r) @@ -284,7 +284,7 @@ func (admin *Admin) ConfigHandler(w http.ResponseWriter, r *http.Request) { admin.SendConfig(w) - admin.Controller.Logs.LogEvent(admin.Controller.Database, LogLevelWarn, "configuration changed") + admin.Controller.Logs.LogEvent(LogLevelWarn, "configuration changed") default: w.WriteHeader(http.StatusMethodNotAllowed) @@ -394,11 +394,7 @@ func (admin *Admin) LoginHandler(w http.ResponseWriter, r *http.Request) { if attempt.Count > admin.AttemptsMax || time.Since(attempt.Date) < admin.AttemptsMaxDelay { if attempt.Count == admin.AttemptsMax+1 { - admin.Controller.Logs.LogEvent( - admin.Controller.Database, - LogLevelWarn, - fmt.Sprintf("too many login attempts for ip=\"%v\"", remoteAddr), - ) + admin.Controller.Logs.LogEvent(LogLevelWarn, fmt.Sprintf("too many login attempts for ip=\"%v\"", remoteAddr)) } w.WriteHeader(http.StatusUnauthorized) @@ -417,11 +413,7 @@ func (admin *Admin) LoginHandler(w http.ResponseWriter, r *http.Request) { } if !ok { - admin.Controller.Logs.LogEvent( - admin.Controller.Database, - LogLevelWarn, - fmt.Sprintf("invalid login attempt for ip=%v", remoteAddr), - ) + admin.Controller.Logs.LogEvent(LogLevelWarn, fmt.Sprintf("invalid login attempt for ip=%v", remoteAddr)) w.WriteHeader(http.StatusUnauthorized) return } @@ -499,7 +491,7 @@ func (admin *Admin) PasswordHandler(w http.ResponseWriter, r *http.Request) { ) logError := func(err error) { - admin.Controller.Logs.LogEvent(admin.Controller.Database, LogLevelError, fmt.Sprintf("admin.passwordhandler.post: %s", err.Error())) + admin.Controller.Logs.LogEvent(LogLevelError, fmt.Sprintf("admin.passwordhandler.post: %s", err.Error())) } t := admin.GetAuthorization(r) diff --git a/server/config.go b/server/config.go index aae77fc..8cbfecd 100644 --- a/server/config.go +++ b/server/config.go @@ -51,6 +51,7 @@ type Config struct { SslCertFile string SslKeyFile string SslListen string + daemon *Daemon newAdminPassword string } @@ -179,7 +180,7 @@ func NewConfig() *Config { } if *serviceAction != "" { - NewDaemon().Control(*serviceAction) + config.daemon = NewDaemon().Control(*serviceAction) } return config diff --git a/server/controller.go b/server/controller.go index 8a67368..f543e82 100644 --- a/server/controller.go +++ b/server/controller.go @@ -16,45 +16,40 @@ package main import ( - "bytes" "encoding/base64" "errors" "fmt" "log" "os" - "os/exec" "os/signal" - "path" "strconv" - "strings" "sync" "time" ) type Controller struct { - Admin *Admin - Api *Api - Calls *Calls - Config *Config - Database *Database - Accesses *Accesses - Apikeys *Apikeys - Dirwatches *Dirwatches - Downstreams *Downstreams - Groups *Groups - Logs *Logs - Options *Options - Scheduler *Scheduler - Systems *Systems - Tags *Tags - Clients *Clients - Register chan *Client - Unregister chan *Client - Ingest chan *Call - ffmpeg bool - ffmpegWarned bool - ingestMutex sync.Mutex - running bool + Admin *Admin + Api *Api + Calls *Calls + Config *Config + Database *Database + Accesses *Accesses + Apikeys *Apikeys + Dirwatches *Dirwatches + Downstreams *Downstreams + FFMpeg *FFMpeg + Groups *Groups + Logs *Logs + Options *Options + Scheduler *Scheduler + Systems *Systems + Tags *Tags + Clients *Clients + Register chan *Client + Unregister chan *Client + Ingest chan *Call + ingestMutex sync.Mutex + running bool } func NewController(config *Config) *Controller { @@ -65,6 +60,7 @@ func NewController(config *Config) *Controller { Calls: NewCalls(), Dirwatches: NewDirwatches(), Downstreams: NewDownstreams(), + FFMpeg: NewFFMpeg(), Groups: NewGroups(), Logs: NewLogs(), Options: NewOptions(), @@ -82,63 +78,12 @@ func NewController(config *Config) *Controller { controller.Database = NewDatabase(config) controller.Scheduler = NewScheduler(controller) + controller.Logs.setDaemon(config.daemon) + controller.Logs.setDatabase(controller.Database) + return controller } -func (controller *Controller) ConvertAudio(call *Call) { - var ( - args = []string{"-i", "-"} - err error - ) - - if !controller.ffmpeg { - if !controller.ffmpegWarned { - controller.ffmpegWarned = true - - controller.Logs.LogEvent(controller.Database, LogLevelWarn, "ffmpeg is not available, no audio conversion will be performed.") - } - return - } - - if system, ok := controller.Systems.GetSystem(call.System); ok { - if talkgroup, ok := system.Talkgroups.GetTalkgroup(call.Talkgroup); ok { - if tag, ok := controller.Tags.GetTag(talkgroup.TagId); ok { - args = append(args, - "-metadata", fmt.Sprintf("album=%v", talkgroup.Label), - "-metadata", fmt.Sprintf("artist=%v", system.Label), - "-metadata", fmt.Sprintf("date=%v", call.DateTime), - "-metadata", fmt.Sprintf("genre=%v", tag), - "-metadata", fmt.Sprintf("title=%v", talkgroup.Name), - ) - } - } - } - - args = append(args, "-af", "apad=whole_dur=3s,loudnorm=I=-16:TP=-1.5:LRA=11", "-c:a", "aac", "-b:a", "32k", "-movflags", "frag_keyframe+empty_moov", "-f", "ipod", "-") - - cmd := exec.Command("ffmpeg", args...) - cmd.Stdin = bytes.NewReader(call.Audio) - - stdout := bytes.NewBuffer([]byte(nil)) - cmd.Stdout = stdout - - stderr := bytes.NewBuffer([]byte(nil)) - cmd.Stderr = stderr - - if err = cmd.Run(); err == nil { - call.Audio = stdout.Bytes() - call.AudioType = "audio/mp4" - - switch v := call.AudioName.(type) { - case string: - call.AudioName = fmt.Sprintf("%v.m4a", strings.TrimSuffix(v, path.Ext((v)))) - } - - } else { - fmt.Println(stderr.String()) - } -} - func (controller *Controller) EmitCall(call *Call) { controller.Clients.EmitCall(call, controller.Accesses.IsRestricted()) controller.Downstreams.Send(controller, call) @@ -169,15 +114,11 @@ func (controller *Controller) IngestCall(call *Call) { defer controller.IngestUnlock() logCall := func(call *Call, level string, message string) { - controller.Logs.LogEvent( - controller.Database, - level, - fmt.Sprintf("newcall: system=%v talkgroup=%v file=%v %v", call.System, call.Talkgroup, call.AudioName, message), - ) + controller.Logs.LogEvent(level, fmt.Sprintf("newcall: system=%v talkgroup=%v file=%v %v", call.System, call.Talkgroup, call.AudioName, message)) } logError := func(err error) { - controller.Logs.LogEvent(controller.Database, LogLevelError, fmt.Sprintf("controller.ingestcall: %v", err.Error())) + controller.Logs.LogEvent(LogLevelError, fmt.Sprintf("controller.ingestcall: %v", err.Error())) } if system, ok = controller.Systems.GetSystem(call.System); ok { @@ -346,7 +287,9 @@ func (controller *Controller) IngestCall(call *Call) { } if !controller.Options.DisableAudioConversion { - controller.ConvertAudio(call) + if err := controller.FFMpeg.Convert(call, controller.Systems, controller.Tags); err != nil { + controller.Logs.LogEvent(LogLevelWarn, err.Error()) + } } if id, err = controller.Calls.WriteCall(call, controller.Database); err == nil { @@ -385,11 +328,7 @@ func (controller *Controller) IngestUnlock() { } func (controller *Controller) LogClientsCount() { - controller.Logs.LogEvent( - controller.Database, - LogLevelInfo, - fmt.Sprintf("listeners count is %v", controller.Clients.Count()), - ) + controller.Logs.LogEvent(LogLevelInfo, fmt.Sprintf("listeners count is %v", controller.Clients.Count())) } func (controller *Controller) ProcessMessage(client *Client, message *Message) error { @@ -494,31 +433,19 @@ func (controller *Controller) ProcessMessageCommandPin(client *Client, message * if access, ok := controller.Accesses.GetAccess(code); ok { client.Access = access } else { - controller.Logs.LogEvent( - controller.Database, - LogLevelWarn, - fmt.Sprintf("invalid access code=\"%s\" address=\"%s\"", code, client.GetRemoteAddr()), - ) + controller.Logs.LogEvent(LogLevelWarn, fmt.Sprintf("invalid access code=\"%s\" address=\"%s\"", code, client.GetRemoteAddr())) client.Send <- &Message{Command: MessageCommandPin} return nil } if client.AuthCount == maxAuthCount { - controller.Logs.LogEvent( - controller.Database, - LogLevelWarn, - fmt.Sprintf("access ident=\"%s\" locked", client.Access.Ident), - ) + controller.Logs.LogEvent(LogLevelWarn, fmt.Sprintf("access ident=\"%s\" locked", client.Access.Ident)) client.Send <- &Message{Command: MessageCommandPin} return nil } if client.Access.HasExpired() { - controller.Logs.LogEvent( - controller.Database, - LogLevelWarn, - fmt.Sprintf("access ident=\"%s\" expired", client.Access.Ident), - ) + controller.Logs.LogEvent(LogLevelWarn, fmt.Sprintf("access ident=\"%s\" expired", client.Access.Ident)) client.Send <- &Message{Command: MessageCommandExpired} return nil } @@ -526,11 +453,7 @@ func (controller *Controller) ProcessMessageCommandPin(client *Client, message * switch v := client.Access.Limit.(type) { case uint: if controller.Clients.AccessCount(client) > int(v) { - controller.Logs.LogEvent( - controller.Database, - LogLevelWarn, - fmt.Sprintf("access ident=\"%s\" too many concurrent connections, limit is %d", client.Access.Ident, client.Access.Limit), - ) + controller.Logs.LogEvent(LogLevelWarn, fmt.Sprintf("access ident=\"%s\" too many concurrent connections, limit is %d", client.Access.Ident, client.Access.Limit)) client.Send <- &Message{Command: MessageCommandMax} return nil } @@ -554,7 +477,7 @@ func (controller *Controller) Start() error { controller.running = true } - controller.Logs.LogEvent(controller.Database, LogLevelWarn, "server started") + controller.Logs.LogEvent(LogLevelWarn, "server started") if len(controller.Config.BaseDir) > 0 { log.Printf("base folder is %s\n", controller.Config.BaseDir) @@ -592,14 +515,6 @@ func (controller *Controller) Start() error { return err } - cmd := exec.Command("ffmpeg", "-version") - if err := cmd.Run(); err == nil { - controller.ffmpeg = true - - } else { - controller.ffmpeg = false - } - go func() { c := make(chan os.Signal) signal.Notify(c, os.Interrupt) diff --git a/server/daemon.go b/server/daemon.go index d85be5e..d21f976 100644 --- a/server/daemon.go +++ b/server/daemon.go @@ -28,6 +28,7 @@ type Daemon struct { Errors chan error Interface service.Interface Service service.Service + Logger service.Logger } func NewDaemon() *Daemon { @@ -61,12 +62,20 @@ func NewDaemon() *Daemon { log.Fatal(err) } + if d.Logger, err = d.Service.Logger(nil); err != nil { + log.Fatal(err) + } + return &d } func (d *Daemon) Control(action string) *Daemon { if action == "run" { - go d.Service.Run() + go func() { + if err := d.Service.Run(); err != nil { + d.Logger.Error(err) + } + }() } else if err := service.Control(d.Service, action); err == nil { os.Exit(0) diff --git a/server/dirwatch.go b/server/dirwatch.go index fc34343..4b574a3 100644 --- a/server/dirwatch.go +++ b/server/dirwatch.go @@ -142,11 +142,7 @@ func (dirwatch *Dirwatch) Ingest(p string) { } if err != nil { - dirwatch.controller.Logs.LogEvent( - dirwatch.controller.Database, - LogLevelError, - fmt.Sprintf("dirwatch.ingest: %v", err.Error()), - ) + dirwatch.controller.Logs.LogEvent(LogLevelError, fmt.Sprintf("dirwatch.ingest: %v", err.Error())) } } @@ -524,9 +520,8 @@ func (dirwatch *Dirwatch) parseMask(call *Call) { func (dirwatch *Dirwatch) Start(controller *Controller) error { var ( - delay time.Duration - err error - timers = map[string]*time.Timer{} + delay time.Duration + err error ) if dirwatch.Disabled { @@ -552,8 +547,10 @@ func (dirwatch *Dirwatch) Start(controller *Controller) error { } go func() { + var timers = map[string]*time.Timer{} + logError := func(err error) { - controller.Logs.LogEvent(controller.Database, 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 { @@ -571,6 +568,11 @@ func (dirwatch *Dirwatch) Start(controller *Controller) error { timers[k].Stop() delete(timers, k) } + + switch v := recover().(type) { + case error: + controller.Logs.LogEvent(LogLevelError, v.Error()) + } }() for { @@ -625,6 +627,13 @@ func (dirwatch *Dirwatch) Start(controller *Controller) error { }() go func() { + defer func() { + switch v := recover().(type) { + case error: + controller.Logs.LogEvent(LogLevelError, v.Error()) + } + }() + time.Sleep(delay) if err := fs.WalkDir(os.DirFS(dirwatch.Directory), ".", func(p string, d fs.DirEntry, err error) error { @@ -640,11 +649,7 @@ func (dirwatch *Dirwatch) Start(controller *Controller) error { return err }); err != nil { - controller.Logs.LogEvent( - controller.Database, - LogLevelError, - fmt.Sprintf("dirwatch.walkdir: %s", err.Error()), - ) + controller.Logs.LogEvent(LogLevelError, fmt.Sprintf("dirwatch.walkdir: %s", err.Error())) } }() @@ -776,11 +781,7 @@ func (dirwatches *Dirwatches) Read(db *Database) error { func (dirwatches *Dirwatches) Start(controller *Controller) { for i := range dirwatches.List { if err := dirwatches.List[i].Start(controller); err != nil { - controller.Logs.LogEvent( - controller.Database, - LogLevelError, - fmt.Sprintf("dirwatches.start: %s", err.Error()), - ) + controller.Logs.LogEvent(LogLevelError, fmt.Sprintf("dirwatches.start: %s", err.Error())) } } } diff --git a/server/downstream.go b/server/downstream.go index 1d9fa4b..cd8b879 100644 --- a/server/downstream.go +++ b/server/downstream.go @@ -440,11 +440,7 @@ func (downstreams *Downstreams) Read(db *Database) error { func (downstreams *Downstreams) Send(controller *Controller, call *Call) { for _, downstream := range downstreams.List { logEvent := func(logLevel string, message string) { - controller.Logs.LogEvent( - controller.Database, - logLevel, - fmt.Sprintf("downstream: system=%v talkgroup=%v file=%v to %v %v", call.System, call.Talkgroup, call.AudioName, downstream.Url, message), - ) + controller.Logs.LogEvent(logLevel, fmt.Sprintf("downstream: system=%v talkgroup=%v file=%v to %v %v", call.System, call.Talkgroup, call.AudioName, downstream.Url, message)) } if downstream.HasAccess(call) { diff --git a/server/ffmpeg.go b/server/ffmpeg.go new file mode 100644 index 0000000..6195242 --- /dev/null +++ b/server/ffmpeg.go @@ -0,0 +1,122 @@ +// Copyright (C) 2019-2022 Chrystian Huot +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with this program. If not, see + +package main + +import ( + "bytes" + "errors" + "fmt" + "os/exec" + "path" + "regexp" + "strconv" + "strings" +) + +type FFMpeg struct { + available bool + version43 bool + warned bool +} + +func NewFFMpeg() *FFMpeg { + ffmpeg := &FFMpeg{} + + stdout := bytes.NewBuffer([]byte(nil)) + + cmd := exec.Command("ffmpeg", "-version") + cmd.Stdout = stdout + + if err := cmd.Run(); err == nil { + ffmpeg.available = true + + if l, err := stdout.ReadString('\n'); err == nil { + s := regexp.MustCompile(`.*ffmpeg version .{0,1}([0-9])\.([0-9])\.[0-9].*`).ReplaceAllString(strings.TrimSuffix(l, "\n"), "$1.$2") + v := strings.Split(s, ".") + if len(v) > 1 { + if major, err := strconv.Atoi(v[0]); err == nil { + if minor, err := strconv.Atoi(v[1]); err == nil { + if major >= 4 || (major == 4 && minor >= 3) { + ffmpeg.version43 = true + } + } + } + } + } + } + + return ffmpeg +} + +func (ffmpeg *FFMpeg) Convert(call *Call, systems *Systems, tags *Tags) error { + var ( + args = []string{"-i", "-"} + err error + ) + + if !ffmpeg.available { + if !ffmpeg.warned { + ffmpeg.warned = true + + return errors.New("ffmpeg is not available, no audio conversion will be performed.") + } + return nil + } + + if system, ok := systems.GetSystem(call.System); ok { + if talkgroup, ok := system.Talkgroups.GetTalkgroup(call.Talkgroup); ok { + if tag, ok := tags.GetTag(talkgroup.TagId); ok { + args = append(args, + "-metadata", fmt.Sprintf("album=%v", talkgroup.Label), + "-metadata", fmt.Sprintf("artist=%v", system.Label), + "-metadata", fmt.Sprintf("date=%v", call.DateTime), + "-metadata", fmt.Sprintf("genre=%v", tag), + "-metadata", fmt.Sprintf("title=%v", talkgroup.Name), + ) + } + } + } + + if ffmpeg.version43 { + args = append(args, "-af", "apad=whole_dur=3s,loudnorm=I=-16:TP=-1.5:LRA=11") + } + + args = append(args, "-c:a", "aac", "-b:a", "32k", "-movflags", "frag_keyframe+empty_moov", "-f", "ipod", "-") + + cmd := exec.Command("ffmpeg", args...) + cmd.Stdin = bytes.NewReader(call.Audio) + + stdout := bytes.NewBuffer([]byte(nil)) + cmd.Stdout = stdout + + stderr := bytes.NewBuffer([]byte(nil)) + cmd.Stderr = stderr + + if err = cmd.Run(); err == nil { + call.Audio = stdout.Bytes() + call.AudioType = "audio/mp4" + + switch v := call.AudioName.(type) { + case string: + call.AudioName = fmt.Sprintf("%v.m4a", strings.TrimSuffix(v, path.Ext((v)))) + } + + } else { + fmt.Println(stderr.String()) + } + + return nil +} diff --git a/server/log.go b/server/log.go index 72c6a62..79a1852 100644 --- a/server/log.go +++ b/server/log.go @@ -38,7 +38,9 @@ type Log struct { } type Logs struct { - mutex sync.Mutex + database *Database + mutex sync.Mutex + daemon *Daemon } func NewLogs() *Logs { @@ -47,20 +49,34 @@ func NewLogs() *Logs { } } -func (logs *Logs) LogEvent(db *Database, level string, message string) error { +func (logs *Logs) LogEvent(level string, message string) error { logs.mutex.Lock() defer logs.mutex.Unlock() - log.Println(message) + if logs.daemon != nil { + switch level { + case LogLevelError: + logs.daemon.Logger.Error(message) + case LogLevelWarn: + logs.daemon.Logger.Warning(message) + case LogLevelInfo: + logs.daemon.Logger.Info(message) + } - l := Log{ - DateTime: time.Now().UTC(), - Level: level, - Message: message, + } else { + log.Println(message) } - if _, err := db.Sql.Exec("insert into `rdioScannerLogs` (`dateTime`, `level`, `message`) values (?, ?, ?)", l.DateTime, l.Level, l.Message); err != nil { - return fmt.Errorf("logs.logevent: %v", err) + if logs.database != nil { + l := Log{ + DateTime: time.Now().UTC(), + Level: level, + Message: message, + } + + if _, err := logs.database.Sql.Exec("insert into `rdioScannerLogs` (`dateTime`, `level`, `message`) values (?, ?, ?)", l.DateTime, l.Level, l.Message); err != nil { + return fmt.Errorf("logs.logevent: %v", err) + } } return nil @@ -219,6 +235,14 @@ func (logs *Logs) Search(searchOptions *LogsSearchOptions, db *Database) (*LogsS return logResults, nil } +func (logs *Logs) setDaemon(d *Daemon) { + logs.daemon = d +} + +func (logs *Logs) setDatabase(d *Database) { + logs.database = d +} + type LogsSearchOptions struct { Date interface{} `json:"date,omitempty"` Level interface{} `json:"level,omitempty"` diff --git a/server/main.go b/server/main.go index ad34cd5..205225a 100644 --- a/server/main.go +++ b/server/main.go @@ -24,6 +24,7 @@ import ( "net/http" "os" "path" + "regexp" "strings" "time" @@ -60,7 +61,7 @@ func main() { log.Fatal(err) } - controller.Logs.LogEvent(controller.Database, LogLevelInfo, "admin password changed.") + controller.Logs.LogEvent(LogLevelInfo, "admin password changed.") os.Exit(0) @@ -247,14 +248,16 @@ func main() { } func GetRemoteAddr(r *http.Request) string { + re := regexp.MustCompile(`(\[[^\]]+\]|[^:]+):.*`) + for _, addr := range strings.Split(r.Header.Get("X-Forwarded-For"), ",") { - if ip := strings.Split(addr, ":"); len(ip[0]) > 0 { - return ip[0] + if ip := re.ReplaceAllString(addr, "$1"); len(ip) > 0 { + return ip } } - if ip := strings.Split(r.RemoteAddr, ":"); len(ip[0]) > 0 { - return ip[0] + if ip := re.ReplaceAllString(r.RemoteAddr, "$1"); len(ip) > 0 { + return ip } return "unknown" diff --git a/server/scheduler.go b/server/scheduler.go index 8ee6138..43e5ac0 100644 --- a/server/scheduler.go +++ b/server/scheduler.go @@ -45,7 +45,7 @@ func (scheduler *Scheduler) pruneDatabase() error { scheduler.Controller.IngestLock() defer scheduler.Controller.IngestUnlock() - scheduler.Controller.Logs.LogEvent(scheduler.Controller.Database, LogLevelInfo, "database pruning") + scheduler.Controller.Logs.LogEvent(LogLevelInfo, "database pruning") if err := scheduler.Controller.Calls.Prune(scheduler.Controller.Database, scheduler.Controller.Options.PruneDays); err != nil { return err @@ -63,11 +63,7 @@ func (scheduler *Scheduler) run() { defer scheduler.mutex.Unlock() logError := func(err error) { - scheduler.Controller.Logs.LogEvent( - scheduler.Controller.Database, - LogLevelError, - fmt.Sprintf("scheduler.run: %s", err.Error()), - ) + scheduler.Controller.Logs.LogEvent(LogLevelError, fmt.Sprintf("scheduler.run: %s", err.Error())) } if err := scheduler.pruneDatabase(); err != nil { diff --git a/server/version.go b/server/version.go index 4f5d303..02f8122 100644 --- a/server/version.go +++ b/server/version.go @@ -15,4 +15,4 @@ package main -const Version = "6.3.3" +const Version = "6.3.4"