Version 6.0.4

This commit is contained in:
Chrystian Huot 2021-12-08 11:08:39 -05:00
parent 04fda4558e
commit b76567ec19
31 changed files with 983 additions and 960 deletions

View file

@ -37,6 +37,13 @@ _v6.0.3_
- Fix strconv.Atoi: invalid syntax for dirwatch type sdrtrunk.
- Fix the new version available dialog opening more than once.
_v6.0.4_
- Fix wrong time calculation in prune scheduler.
- More fix on the SQL_BUSY error (issue #67).
- Support files (certs, db, ini) are now created in the same folder as the executable, if the folder is writable, or under a `Rdio Scanner` folder in the user's home folder.
- Some code refactoring.
## Version 5.2
- Change to how the server reports version.

35
COMPILING.md Normal file
View file

@ -0,0 +1,35 @@
# How to compile
The official version of [Rdio Scanner](https://github.com/chuot/rdio-scanner) is compiled on a PC using the current version of Fedora Workstation. You should have no problem building on another platform as long as the prerequisites are available and installed.
## Install the prerequisites
Your os distribution may have all of the following prerequisites available in its own package repository. Make sure, however, that they are at the latest version. This is especially not the case with Ubuntu and its Node.js.
- latest version of git ([here](https://git-scm.com/downloads))
- latest version of gnu make ([here](https://www.gnu.org/software/make/))
- latest long-term support version of Node.js ([here](https://nodejs.org/en/))
- latest version of Go ([here](https://go.dev/dl/))
- latest version of Pandoc with pandoc-pdf ([here](https://pandoc.org/installing.html))
- latest version of Info-Zip ([here](http://infozip.sourceforge.net/))
## Compile the app
Clone the official repository on your computer and start the build process.
git clone https://github.com/chuot/rdio-scanner.git
cd rdio-scanner
make
When finished, you will find the precompiled versions for various platforms in the `dist` folder.
rdio-scanner-darwin-amd64-v6.0.4.zip
rdio-scanner-darwin-arm64-v6.0.4.zip
rdio-scanner-freebsd-amd64-v6.0.4.zip
rdio-scanner-linux-386-v6.0.4.zip
rdio-scanner-linux-amd64-v6.0.4.zip
rdio-scanner-linux-arm64-v6.0.4.zip
rdio-scanner-linux-arm-v6.0.4.zip
rdio-scanner-windows-amd64-v6.0.4.zip
**Happy Rdio scanning !**

View file

@ -16,7 +16,8 @@
################################################################################
app := rdio-scanner
ver := 6.0.3
date := 2021/12/08
ver := 6.0.4
client := $(wildcard client/*.json client/*.ts)
server := $(wildcard server/*.go)
@ -25,7 +26,7 @@ build = @cd server && GOOS=$(1) GOARCH=$(2) go build -o ../dist/$(1)-$(2)/$(3)
pandoc = @test -d dist/$(1)-$(2) || mkdir -p dist/$(1)-$(2) && pandoc -f markdown -o dist/$(1)-$(2)/$(3) --resource-path docs:docs/platforms $(4) docs/webapp.md CHANGELOG.md
zip = @cd dist/$(1)-$(2) && zip -q ../$(app)-$(1)-$(2)-v$(ver).zip * && cd .. && rm -fr $(1)-$(2)
.PHONY: all clean dist setver
.PHONY: all clean dist sed
.PHONY: darwin darwin-amd64 darwin-arm64
.PHONY: freebsd freebsd-amd64
.PHONY: linux linux-386 linux-amd64 linux-arm linux-arm64
@ -38,10 +39,11 @@ clean:
dist: darwin freebsd linux windows
setver:
@sed -i -re "s/^(\s*\"version\":).*$$/\1 \"$(ver)\"/" client/package.json
@sed -i -re "s/^(const\s+Version\s+=).*$$/\1 \"$(ver)\"/" server/version.go
@sed -i -re "s/v[0-9]\.[0-9]\.[0-9]/v$(ver)/" docs/platforms/*.md
sed:
@sed -i -re "s|^(\s*\"version\":).*$$|\1 \"$(ver)\"|" client/package.json
@sed -i -re "s|^(const\s+Version\s+=).*$$|\1 \"$(ver)\"|" server/version.go
@sed -i -re "s|v[0-9]\.[0-9]\.[0-9]|v$(ver)|" COMPILING.md docs/platforms/*.md
@sed -i -re "s|[0-9]{4}/[0-9]{2}/[0-9]{2}|$(date)|" docs/platforms/*.md
webapp: server/webapp/index.html

834
client/package-lock.json generated

File diff suppressed because it is too large Load diff

View file

@ -1,31 +1,31 @@
{
"author": "Chrystian Huot <chrystian.huot@saubeo.solutions>",
"dependencies": {
"@angular/animations": "^13.0.2",
"@angular/cdk": "^13.0.2",
"@angular/common": "~13.0.2",
"@angular/compiler": "~13.0.2",
"@angular/core": "~13.0.2",
"@angular/forms": "~13.0.2",
"@angular/material": "^13.0.2",
"@angular/platform-browser": "~13.0.2",
"@angular/platform-browser-dynamic": "~13.0.2",
"@angular/router": "^13.0.2",
"@angular/service-worker": "^13.0.2",
"@angular/animations": "^13.0.3",
"@angular/cdk": "^13.0.3",
"@angular/common": "~13.0.3",
"@angular/compiler": "~13.0.3",
"@angular/core": "~13.0.3",
"@angular/forms": "~13.0.3",
"@angular/material": "^13.0.3",
"@angular/platform-browser": "~13.0.3",
"@angular/platform-browser-dynamic": "~13.0.3",
"@angular/router": "^13.0.3",
"@angular/service-worker": "^13.0.3",
"rxjs": "~7.4.0",
"tslib": "^2.3.1",
"zone.js": "~0.11.4"
},
"devDependencies": {
"@angular-devkit/build-angular": "~13.0.3",
"@angular/cli": "^13.0.3",
"@angular/compiler-cli": "~13.0.2",
"@types/node": "~16.11.11",
"@typescript-eslint/eslint-plugin": "^5.5.0",
"@typescript-eslint/parser": "^5.5.0",
"@angular-devkit/build-angular": "~13.0.4",
"@angular/cli": "^13.0.4",
"@angular/compiler-cli": "~13.0.3",
"@types/node": "~16.11.12",
"@typescript-eslint/eslint-plugin": "^5.6.0",
"@typescript-eslint/parser": "^5.6.0",
"ajv-keywords": "^5.1.0",
"eslint": "^8.3.0",
"npm-check-updates": "^12.0.2",
"eslint": "^8.4.1",
"npm-check-updates": "^12.0.3",
"ts-node": "~10.4.0",
"typescript": "~4.4.4"
},
@ -39,5 +39,5 @@
"build": "ng build --base-href ./ --configuration production",
"start": "ng serve"
},
"version": "6.0.3"
"version": "6.0.4"
}

View file

@ -757,8 +757,6 @@ export class RdioScannerService implements OnDestroy {
}
};
this.websocket.onerror = () => this.event.emit({ linked: false });
this.websocket.onopen = () => {
this.event.emit({ linked: true });

View file

@ -25,8 +25,8 @@ Here is a list of recorders known to work with [Rdio Scanner](https://github.com
You can enjoy your [Rdio Scanner](https://github.com/chuot/rdio-scanner) on the go on your mobile device with the native app.
[![Available on the App Store](./docs/images/app-store-badge.png?raw=true)](https://apps.apple.com/us/app/rdio-scanner/id1563065667#?platform=iphone)
[![Get it on Google Play](./docs/images/google-play-badge.png?raw=true)](https://play.google.com/store/apps/details?id=solutions.saubeo.rdioScanner)
[![Available on the App Store](../images/app-store-badge.png?raw=true)](https://apps.apple.com/us/app/rdio-scanner/id1563065667#?platform=iphone)
[![Get it on Google Play](../images/google-play-badge.png?raw=true)](https://play.google.com/store/apps/details?id=solutions.saubeo.rdioScanner)
\pagebreak{}
@ -41,8 +41,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.0.3.zip
Archive: /Users/rdio/Downloads/rdio-scanner-darwin-arm64-v6.0.3.zip
> ~/Downloads/rdio-scanner-darwin-arm64-v6.0.4.zip
Archive: /Users/rdio/Downloads/rdio-scanner-darwin-arm64-v6.0.4.zip
inflating: rdio-scanner
inflating: rdio-scanner.pdf
@ -50,11 +50,11 @@ ALWAYS DOWNLOAD THE LATEST VERSION OF [RDIO SCANNER](https://github.com/chuot/rd
rdio@macos rdio-scanner % ./rdio-scanner
Rdio Scanner v6.0.3
Rdio Scanner v6.0.4
----------------------------------
2021/11/29 08:38:06 server started
2021/11/29 08:38:06 main interface at http://macos.local:3000
2021/11/29 08:38:06 admin interface at http://macos.local:3000/admin
2021/12/08 08:38:06 server started
2021/12/08 08:38:06 main interface at http://macos.local:3000
2021/12/08 08:38:06 admin interface at http://macos.local:3000/admin
4. Access the administrative dashboard to finalize the configuration.
@ -76,11 +76,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.0.3
Rdio Scanner v6.0.4
----------------------------------
2021/11/29 08:48:03 server started
2021/11/29 08:48:03 main interface at http://macos.local
2021/11/29 08:48:03 admin interface at http://macos.local/admin
2021/12/08 08:48:03 server started
2021/12/08 08:48:03 main interface at http://macos.local
2021/12/08 08:48:03 admin interface at http://macos.local/admin
## Listening with SSL with self-signed certificates
@ -89,8 +89,8 @@ It is advised to share your [Rdio Scanner](https://github.com/chuot/rdio-scanner
We can create such self-signed certificates by running [Rdio Scanner](https://github.com/chuot/rdio-scanner) with the **-ssl_create** argument.
rdio@macos rdio-scanner % ./rdio-scanner -ssl_create
2021/11/29 08:49:16 generating ssl certificate files
2021/11/29 08:49:17 ssl files created
2021/12/08 08:49:16 generating ssl certificate files
2021/12/08 08:49:17 ssl files created
This has generated these files in the same folder as your [Rdio Scanner](https://github.com/chuot/rdio-scanner) executable.
@ -107,12 +107,12 @@ Now we use the self-signed server certificate to run our instance.
> -ssl_key_file server.key \
> -ssl_listen :443
Rdio Scanner v6.0.3
Rdio Scanner v6.0.4
----------------------------------
2021/11/29 08:50:58 server started
2021/11/29 08:50:58 main interface at http://macos.local
2021/11/29 08:50:58 main interface at https://macos.local
2021/11/29 08:50:58 admin interface at https://macos.local/admin
2021/12/08 08:50:58 server started
2021/12/08 08:50:58 main interface at http://macos.local
2021/12/08 08:50:58 main interface at https://macos.local
2021/12/08 08:50:58 admin interface at https://macos.local/admin
## Save your advanced configuration to a config file
@ -124,7 +124,7 @@ You don't want to have to type everytime a long list of arguments. No problem, y
> -ssl_key_file server.key \
> -ssl_listen :443 \
> -config_save
2021/11/29 08:52:24 rdio-scanner.ini file created
2021/12/08 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.
@ -139,12 +139,12 @@ Then simply run [Rdio Scanner](https://github.com/chuot/rdio-scanner) without an
rdio@macos rdio-scanner % ./rdio-scanner
Rdio Scanner v6.0.3
Rdio Scanner v6.0.4
----------------------------------
2021/11/29 08:54:08 server started
2021/11/29 08:54:08 main interface at http://macos.local
2021/11/29 08:54:08 main interface at https://macos.local
2021/11/29 08:54:08 admin interface at https://macos.local/admin
2021/12/08 08:54:08 server started
2021/12/08 08:54:08 main interface at http://macos.local
2021/12/08 08:54:08 main interface at https://macos.local
2021/12/08 08:54:08 admin interface at https://macos.local/admin
## Install Rdio Scanner as a service

View file

@ -25,8 +25,8 @@ Here is a list of recorders known to work with [Rdio Scanner](https://github.com
You can enjoy your [Rdio Scanner](https://github.com/chuot/rdio-scanner) on the go on your mobile device with the native app.
[![Available on the App Store](./docs/images/app-store-badge.png?raw=true)](https://apps.apple.com/us/app/rdio-scanner/id1563065667#?platform=iphone)
[![Get it on Google Play](./docs/images/google-play-badge.png?raw=true)](https://play.google.com/store/apps/details?id=solutions.saubeo.rdioScanner)
[![Available on the App Store](../images/app-store-badge.png?raw=true)](https://apps.apple.com/us/app/rdio-scanner/id1563065667#?platform=iphone)
[![Get it on Google Play](../images/google-play-badge.png?raw=true)](https://play.google.com/store/apps/details?id=solutions.saubeo.rdioScanner)
\pagebreak{}
@ -41,8 +41,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.0.3.zip
Archive: ../rdio-scanner-freebsd-amd64-v6.0.3.zip
> ~/rdio-scanner-freebsd-amd64-v6.0.4.zip
Archive: ../rdio-scanner-freebsd-amd64-v6.0.4.zip
extracting: rdio-scanner
extracting: rdio-scanner.pdf
@ -50,11 +50,11 @@ ALWAYS DOWNLOAD THE LATEST VERSION OF [RDIO SCANNER](https://github.com/chuot/rd
rdio@pc-freebsd:~/rdio-scanner $ ./rdio-scanner
Rdio Scanner v6.0.3
Rdio Scanner v6.0.4
----------------------------------
2021/11/28 08:16:36 server started
2021/11/28 08:16:36 main interface at http://pc-freebsd:3000
2021/11/28 08:16:36 admin interface at http://pc-freebsd:3000/admin
2021/12/08 08:16:36 server started
2021/12/08 08:16:36 main interface at http://pc-freebsd:3000
2021/12/08 08:16:36 admin interface at http://pc-freebsd:3000/admin
4. Access the administrative dashboard to finalize the configuration.
@ -80,11 +80,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.0.3
Rdio Scanner v6.0.4
----------------------------------
2021/11/28 08:19:38 server started
2021/11/28 08:19:38 main interface at http://pc-freebsd
2021/11/28 08:19:38 admin interface at http://pc-freebsd/admin
2021/12/08 08:19:38 server started
2021/12/08 08:19:38 main interface at http://pc-freebsd
2021/12/08 08:19:38 admin interface at http://pc-freebsd/admin
## Listening with SSL with self-signed certificates
@ -93,8 +93,8 @@ It is advised to share your [Rdio Scanner](https://github.com/chuot/rdio-scanner
We can create such self-signed certificates by running [Rdio Scanner](https://github.com/chuot/rdio-scanner) with the **-ssl_create** argument.
rdio@pc-freebsd:~/rdio-scanner $ ./rdio-scanner -ssl_create
2021/11/28 10:32:17 generating ssl certificate files
2021/11/28 10:32:19 ssl files created
2021/12/08 10:32:17 generating ssl certificate files
2021/12/08 10:32:19 ssl files created
This has generated these files in the same folder as your [Rdio Scanner](https://github.com/chuot/rdio-scanner) executable.
@ -113,12 +113,12 @@ Now we use the self-signed server certificate to run our instance.
> -ssl_key_file server.key \
> -ssl_listen :443
Rdio Scanner v6.0.3
Rdio Scanner v6.0.4
----------------------------------
2021/11/28 10:34:29 server started
2021/11/28 10:34:29 main interface at http://pc-freebsd
2021/11/28 10:34:29 main interface at https://pc-freebsd
2021/11/28 10:34:29 admin interface at https://pc-freebsd/admin
2021/12/08 10:34:29 server started
2021/12/08 10:34:29 main interface at http://pc-freebsd
2021/12/08 10:34:29 main interface at https://pc-freebsd
2021/12/08 10:34:29 admin interface at https://pc-freebsd/admin
## Save your advanced configuration to a config file
@ -130,7 +130,7 @@ You don't want to have to type everytime a long list of arguments. No problem, y
> -ssl_key_file server.key \
> -ssl_listen :443 \
> -config_save
2021/11/28 10:37:00 rdio-scanner.ini file created
2021/12/08 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.
@ -147,12 +147,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.0.3
Rdio Scanner v6.0.4
----------------------------------
2021/11/28 10:38:28 server started
2021/11/28 10:38:29 main interface at http://pc-freebsd
2021/11/28 10:38:29 main interface at https://pc-freebsd
2021/11/28 10:38:29 admin interface at https://pc-freebsd/admin
2021/12/08 10:38:28 server started
2021/12/08 10:38:29 main interface at http://pc-freebsd
2021/12/08 10:38:29 main interface at https://pc-freebsd
2021/12/08 10:38:29 admin interface at https://pc-freebsd/admin
## Install Rdio Scanner as a service

View file

@ -25,8 +25,8 @@ Here is a list of recorders known to work with [Rdio Scanner](https://github.com
You can enjoy your [Rdio Scanner](https://github.com/chuot/rdio-scanner) on the go on your mobile device with the native app.
[![Available on the App Store](./docs/images/app-store-badge.png?raw=true)](https://apps.apple.com/us/app/rdio-scanner/id1563065667#?platform=iphone)
[![Get it on Google Play](./docs/images/google-play-badge.png?raw=true)](https://play.google.com/store/apps/details?id=solutions.saubeo.rdioScanner)
[![Available on the App Store](../images/app-store-badge.png?raw=true)](https://apps.apple.com/us/app/rdio-scanner/id1563065667#?platform=iphone)
[![Get it on Google Play](../images/google-play-badge.png?raw=true)](https://play.google.com/store/apps/details?id=solutions.saubeo.rdioScanner)
\pagebreak{}
@ -41,8 +41,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.0.3.zip
Archive: /home/rdio/Downloads/rdio-scanner-linux-amd64-v6.0.3.zip
> ~/Downloads/rdio-scanner-linux-amd64-v6.0.4.zip
Archive: /home/rdio/Downloads/rdio-scanner-linux-amd64-v6.0.4.zip
inflating: rdio-scanner
inflating: rdio-scanner.pdf
@ -50,11 +50,11 @@ ALWAYS DOWNLOAD THE LATEST VERSION OF [RDIO SCANNER](https://github.com/chuot/rd
[rdio@pc-linux rdio-scanner]$ ./rdio-scanner
Rdio Scanner v6.0.3
Rdio Scanner v6.0.4
----------------------------------
2021/11/29 09:11:48 server started
2021/11/29 09:11:48 main interface at http://pc-linux:3000
2021/11/29 09:11:48 admin interface at http://pc-linux:3000/admin
2021/12/08 09:11:48 server started
2021/12/08 09:11:48 main interface at http://pc-linux:3000
2021/12/08 09:11:48 admin interface at http://pc-linux: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@pc-linux rdio-scanner]$ sudo ./rdio-scanner -listen :80
[sudo] password for rdio:
Rdio Scanner v6.0.3
Rdio Scanner v6.0.4
----------------------------------
2021/11/29 09:14:00 server started
2021/11/29 09:14:00 main interface at http://pc-linux
2021/11/29 09:14:00 admin interface at http://pc-linux/admin
2021/12/08 09:14:00 server started
2021/12/08 09:14:00 main interface at http://pc-linux
2021/12/08 09:14:00 admin interface at http://pc-linux/admin
## Listening with SSL with self-signed certificates
@ -90,8 +90,8 @@ It is advised to share your [Rdio Scanner](https://github.com/chuot/rdio-scanner
We can create such self-signed certificates by running [Rdio Scanner](https://github.com/chuot/rdio-scanner) with the **-ssl_create** argument.
[rdio@pc-linux rdio-scanner]$ ./rdio-scanner -ssl_create
2021/11/29 09:15:03 generating ssl certificate files
2021/11/29 09:15:05 ssl files created
2021/12/08 09:15:03 generating ssl certificate files
2021/12/08 09:15:05 ssl files created
This has generated these files in the same folder as your [Rdio Scanner](https://github.com/chuot/rdio-scanner) executable.
@ -109,12 +109,12 @@ Now we use the self-signed server certificate to run our instance.
> -ssl_listen :443
[sudo] password for rdio:
Rdio Scanner v6.0.3
Rdio Scanner v6.0.4
----------------------------------
2021/11/29 09:16:47 server started
2021/11/29 09:16:47 main interface at http://pc-linux
2021/11/29 09:16:47 main interface at https://pc-linux
2021/11/29 09:16:47 admin interface at https://pc-linux/admin
2021/12/08 09:16:47 server started
2021/12/08 09:16:47 main interface at http://pc-linux
2021/12/08 09:16:47 main interface at https://pc-linux
2021/12/08 09:16:47 admin interface at https://pc-linux/admin
## Save your advanced configuration to a config file
@ -126,7 +126,7 @@ You don't want to have to type everytime a long list of arguments. No problem, y
> -ssl_key_file server.key \
> -ssl_listen :443 \
> -config_save
2021/11/29 09:19:29 rdio-scanner.ini file created
2021/12/08 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.
@ -142,12 +142,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.0.3
Rdio Scanner v6.0.4
----------------------------------
2021/11/29 09:20:40 server started
2021/11/29 09:20:40 main interface at http://pc-linux
2021/11/29 09:20:40 main interface at https://pc-linux
2021/11/29 09:20:40 admin interface at https://pc-linux/admin
2021/12/08 09:20:40 server started
2021/12/08 09:20:40 main interface at http://pc-linux
2021/12/08 09:20:40 main interface at https://pc-linux
2021/12/08 09:20:40 admin interface at https://pc-linux/admin
## Install Rdio Scanner as a service

View file

@ -25,8 +25,8 @@ Here is a list of recorders known to work with [Rdio Scanner](https://github.com
You can enjoy your [Rdio Scanner](https://github.com/chuot/rdio-scanner) on the go on your mobile device with the native app.
[![Available on the App Store](./docs/images/app-store-badge.png?raw=true)](https://apps.apple.com/us/app/rdio-scanner/id1563065667#?platform=iphone)
[![Get it on Google Play](./docs/images/google-play-badge.png?raw=true)](https://play.google.com/store/apps/details?id=solutions.saubeo.rdioScanner)
[![Available on the App Store](../images/app-store-badge.png?raw=true)](https://apps.apple.com/us/app/rdio-scanner/id1563065667#?platform=iphone)
[![Get it on Google Play](../images/google-play-badge.png?raw=true)](https://play.google.com/store/apps/details?id=solutions.saubeo.rdioScanner)
\pagebreak{}
@ -43,7 +43,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.0.3.zip
..\downloads\rdio-scanner-windows-amd64-v6.0.4.zip
x rdio-scanner.exe
x rdio-scanner.pdf
@ -51,11 +51,11 @@ ALWAYS DOWNLOAD THE LATEST VERSION OF [RDIO SCANNER](https://github.com/chuot/rd
C:\Users\rdio\rdio-scanner>rdio-scanner
Rdio Scanner v6.0.3
Rdio Scanner v6.0.4
----------------------------------
2021/11/25 13:48:48 server started
2021/11/25 13:48:48 main interface at http://pc-windows:3000
2021/11/25 13:48:48 admin interface at http://pc-windows:3000/admin
2021/12/08 13:48:48 server started
2021/12/08 13:48:48 main interface at http://pc-windows:3000
2021/12/08 13:48:48 admin interface at http://pc-windows: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
C:\Users\rdio\rdio-scanner>rdio-scanner -listen :80
Rdio Scanner v6.0.3
Rdio Scanner v6.0.4
----------------------------------
2021/11/26 10:53:31 server started
2021/11/26 10:53:31 main interface at http://pc-windows
2021/11/26 10:53:31 admin interface at http://pc-windows/admin
2021/12/08 10:53:31 server started
2021/12/08 10:53:31 main interface at http://pc-windows
2021/12/08 10:53:31 admin interface at http://pc-windows/admin
## Listening with SSL with self-signed certificates
@ -90,8 +90,8 @@ It is advised to share your [Rdio Scanner](https://github.com/chuot/rdio-scanner
We can create such self-signed certificates by running [Rdio Scanner](https://github.com/chuot/rdio-scanner) with the **-ssl_create** argument.
C:\Users\rdio\rdio-scanner>rdio-scanner -ssl_create
2021/11/26 10:58:51 generating ssl certificate files
2021/11/26 10:58:54 ssl files created
2021/12/08 10:58:51 generating ssl certificate files
2021/12/08 10:58:54 ssl files created
This has generated these files in the same folder as your [Rdio Scanner](https://github.com/chuot/rdio-scanner) executable.
@ -108,12 +108,12 @@ Now we use the self-signed server certificate to run our instance.
-ssl_key_file server.key ^
-ssl_listen :443
Rdio Scanner v6.0.3
Rdio Scanner v6.0.4
----------------------------------
2021/11/26 11:05:43 server started
2021/11/26 11:05:43 main interface at http://pc-windows
2021/11/26 11:05:43 main interface at https://pc-windows
2021/11/26 11:05:43 admin interface at https://pc-windows/admin
2021/12/08 11:05:43 server started
2021/12/08 11:05:43 main interface at http://pc-windows
2021/12/08 11:05:43 main interface at https://pc-windows
2021/12/08 11:05:43 admin interface at https://pc-windows/admin
## Save your advanced configuration to a config file
@ -125,7 +125,7 @@ You don't want to have to type everytime a long list of arguments. No problem, y
-ssl_key_file server.key ^
-ssl_listen :443 ^
-config_save
2021/11/26 11:08:28 rdio-scanner.ini file created
2021/12/08 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.
@ -140,12 +140,12 @@ Then simply run [Rdio Scanner](https://github.com/chuot/rdio-scanner) without an
C:\Users\rdio\rdio-scanner>rdio-scanner
Rdio Scanner v6.0.3
Rdio Scanner v6.0.4
----------------------------------
2021/11/26 11:11:28 server started
2021/11/26 11:11:28 main interface at http://pc-windows
2021/11/26 11:11:28 main interface at https://pc-windows
2021/11/26 11:11:28 admin interface at https://pc-windows/admin
2021/12/08 11:11:28 server started
2021/12/08 11:11:28 main interface at http://pc-windows
2021/12/08 11:11:28 main interface at https://pc-windows
2021/12/08 11:11:28 admin interface at https://pc-windows/admin
\pagebreak{}

View file

@ -202,11 +202,9 @@ func (accesses *Accesses) Read(db *Database) error {
*accesses = append(*accesses, access)
}
if err != nil {
return formatError(err)
}
rows.Close()
if err = rows.Close(); err != nil {
if err != nil {
return formatError(err)
}
@ -270,12 +268,9 @@ func (accesses *Accesses) Write(db *Database) error {
}
}
if err != nil {
rows.Close()
return formatError(err)
}
if err = rows.Close(); err != nil {
if err != nil {
return formatError(err)
}

View file

@ -37,7 +37,6 @@ const (
)
type Admin struct {
initialized bool
Attempts AdminLoginAttempts
AttemptsMax uint
AttemptsMaxDelay time.Duration
@ -47,6 +46,7 @@ type Admin struct {
Register chan *websocket.Conn
Tokens []string
Unregister chan *websocket.Conn
running bool
}
type AdminLoginAttempt struct {
@ -56,6 +56,20 @@ type AdminLoginAttempt struct {
type AdminLoginAttempts map[string]*AdminLoginAttempt
func NewAdmin(controller *Controller) *Admin {
return &Admin{
Attempts: AdminLoginAttempts{},
AttemptsMax: uint(3),
AttemptsMaxDelay: time.Duration(time.Duration.Minutes(10)),
Broadcast: make(chan *[]byte, 100),
Conns: make(map[*websocket.Conn]bool),
Controller: controller,
Register: make(chan *websocket.Conn, 100),
Tokens: []string{},
Unregister: make(chan *websocket.Conn, 100),
}
}
func (admin *Admin) BroadcastConfig() {
if b, err := json.Marshal(admin.GetConfig()); err == nil {
@ -298,52 +312,6 @@ func (admin *Admin) GetConfig() map[string]interface{} {
}
}
func (admin *Admin) Init(controller *Controller) error {
if admin.initialized {
return errors.New("admin already initialized")
}
admin.initialized = true
admin.Attempts = AdminLoginAttempts{}
admin.AttemptsMax = uint(3)
admin.AttemptsMaxDelay = time.Duration(time.Duration.Minutes(10))
admin.Broadcast = make(chan *[]byte, 100)
admin.Conns = make(map[*websocket.Conn]bool)
admin.Controller = controller
admin.Register = make(chan *websocket.Conn, 100)
admin.Tokens = []string{}
admin.Unregister = make(chan *websocket.Conn, 100)
go func() {
for {
select {
case data, ok := <-admin.Broadcast:
if !ok {
return
}
for conn := range admin.Conns {
err := conn.WriteMessage(websocket.TextMessage, *data)
if err != nil {
admin.Unregister <- conn
}
}
case conn := <-admin.Register:
admin.Conns[conn] = true
case conn := <-admin.Unregister:
if _, ok := admin.Conns[conn]; ok {
delete(admin.Conns, conn)
conn.Close()
}
}
}
}()
return nil
}
func (admin *Admin) LogsHandler(w http.ResponseWriter, r *http.Request) {
t := admin.GetAuthorization(r)
if !admin.ValidateToken(t) {
@ -570,6 +538,43 @@ func (admin *Admin) SendConfig(w http.ResponseWriter) {
}
}
func (admin *Admin) Start() error {
if admin.running {
return errors.New("admin already running")
} else {
admin.running = true
}
go func() {
for {
select {
case data, ok := <-admin.Broadcast:
if !ok {
return
}
for conn := range admin.Conns {
err := conn.WriteMessage(websocket.TextMessage, *data)
if err != nil {
admin.Unregister <- conn
}
}
case conn := <-admin.Register:
admin.Conns[conn] = true
case conn := <-admin.Unregister:
if _, ok := admin.Conns[conn]; ok {
delete(admin.Conns, conn)
conn.Close()
}
}
}
}()
return nil
}
func (admin *Admin) ValidateToken(sToken string) bool {
found := false
for _, t := range admin.Tokens {

View file

@ -17,7 +17,6 @@ package main
import (
"encoding/json"
"errors"
"fmt"
"io"
"mime"
@ -35,18 +34,11 @@ const (
)
type Api struct {
initialized bool
Controller *Controller
}
func (api *Api) Init(controller *Controller) error {
if api.initialized {
return errors.New("api already initialized")
}
api.Controller = controller
return nil
func NewApi(controller *Controller) *Api {
return &Api{Controller: controller}
}
func (api *Api) CallUploadHandler(w http.ResponseWriter, r *http.Request) {

View file

@ -174,10 +174,10 @@ func (apikeys *Apikeys) Read(db *Database) error {
rows.Close()
if err != nil {
err = formatError(err)
return formatError(err)
}
return err
return nil
}
func (apikeys *Apikeys) Write(db *Database) error {
@ -239,12 +239,9 @@ func (apikeys *Apikeys) Write(db *Database) error {
}
}
if err != nil {
rows.Close()
return formatError(err)
}
if err = rows.Close(); err != nil {
if err != nil {
return formatError(err)
}

View file

@ -49,7 +49,7 @@ func (client *Client) Init(controller *Controller, conn *websocket.Conn) error {
client.Access = &Access{}
client.Controller = controller
client.Conn = conn
client.Send = make(chan *Message, 100)
client.Send = make(chan *Message, 8)
client.LivefeedMap = LivefeedMap{}
controller.Register <- client

View file

@ -35,6 +35,7 @@ const (
)
type Config struct {
BaseDir string
ConfigFile string
DbType string
DbFile string
@ -53,43 +54,7 @@ type Config struct {
newAdminPassword string
}
func (config *Config) GetConfigFilePath() string {
return config.GetPath(config.ConfigFile)
}
func (config *Config) GetDbFilePath() string {
return config.GetPath(config.DbFile)
}
func (config *Config) GetPath(p string) string {
if !path.IsAbs(p) {
if exe, err := os.Executable(); err == nil {
if !regexp.MustCompile(`go-build[0-9]+`).Match([]byte(exe)) {
return path.Join(filepath.Dir(exe), p)
}
}
}
return p
}
func (config *Config) GetSslCaCertFilePath() string {
return config.GetPath(config.SslCaCertFile)
}
func (config *Config) GetSslCaKeyFilePath() string {
return config.GetPath(config.SslCaKeyFile)
}
func (config *Config) GetSslCertFilePath() string {
return config.GetPath(config.SslCertFile)
}
func (config *Config) GetSslKeyFilePath() string {
return config.GetPath(config.SslKeyFile)
}
func (config *Config) Init() (ok bool, error error) {
func NewConfig() *Config {
const (
defaultAdminUrl = "/admin"
defaultConfigFile = "rdio-scanner.ini"
@ -101,6 +66,7 @@ func (config *Config) Init() (ok bool, error error) {
)
var (
config = &Config{}
configSave = flag.Bool("config_save", false, fmt.Sprintf("save configuration to %s", defaultConfigFile))
serviceAction = flag.String("service", "", "service command, one of start, stop, restart, install, uninstall")
sslCreate = flag.Bool("ssl_create", false, "create self-signed certificates")
@ -121,33 +87,50 @@ func (config *Config) Init() (ok bool, error error) {
flag.StringVar(&config.SslKeyFile, "ssl_key_file", "", "ssl PEM formated key")
flag.StringVar(&config.SslListen, "ssl_listen", "", "listening address for ssl")
flag.StringVar(&config.newAdminPassword, "admin_password", "", "change admin password")
flag.Parse()
if exe, err := os.Executable(); err == nil {
if !regexp.MustCompile(`go-build[0-9]+`).Match([]byte(exe)) {
config.BaseDir = filepath.Dir(exe)
if !config.isBaseDirWritable() {
if h, err := os.UserHomeDir(); err == nil {
config.BaseDir = filepath.Join(h, "Rdio Scanner")
if _, err := os.Stat(config.BaseDir); os.IsNotExist(err) {
os.MkdirAll(config.BaseDir, 0770)
}
}
}
}
}
if !config.isBaseDirWritable() {
log.Fatalf("no write permissions in %s", config.BaseDir)
}
switch {
case *configSave:
if err := config.saveConfig(); err == nil {
log.Printf("%s file created", config.ConfigFile)
return false, nil
fmt.Printf("%s file created\n", config.ConfigFile)
os.Exit(0)
} else {
return false, err
fmt.Printf("error: %s\n", err.Error())
os.Exit(-1)
}
case *sslCreate:
log.Print("generating ssl certificate files")
fmt.Println("generating ssl certificate files")
if err := CreateSelfSignedCert(config); err == nil {
log.Print("ssl files created")
return false, nil
fmt.Println("ssl files created")
os.Exit(0)
} else {
return false, err
fmt.Printf("error: %s", err.Error())
os.Exit(-1)
}
case *version:
fmt.Println(Version)
return false, nil
os.Exit(0)
default:
if cfg, err := ini.Load(config.GetConfigFilePath()); err == nil {
@ -201,15 +184,56 @@ func (config *Config) Init() (ok bool, error error) {
}
if !(config.DbType == DbTypeMariadb || config.DbType == DbTypeMysql || config.DbType == DbTypeSqlite) {
return false, fmt.Errorf("unknown database type %s", config.DbType)
fmt.Printf("unknown database type %s\n", config.DbType)
return nil
}
}
if *serviceAction != "" {
return NewDaemon().Control(*serviceAction)
NewDaemon().Control(*serviceAction)
}
return true, nil
return config
}
func (config *Config) GetConfigFilePath() string {
return config.GetPath(config.ConfigFile)
}
func (config *Config) GetDbFilePath() string {
return config.GetPath(config.DbFile)
}
func (config *Config) GetPath(p string) string {
if path.IsAbs(p) {
return p
}
return path.Join(config.BaseDir, p)
}
func (config *Config) GetSslCaCertFilePath() string {
return config.GetPath(config.SslCaCertFile)
}
func (config *Config) GetSslCaKeyFilePath() string {
return config.GetPath(config.SslCaKeyFile)
}
func (config *Config) GetSslCertFilePath() string {
return config.GetPath(config.SslCertFile)
}
func (config *Config) GetSslKeyFilePath() string {
return config.GetPath(config.SslKeyFile)
}
func (config *Config) isBaseDirWritable() bool {
if f, err := os.CreateTemp(config.BaseDir, ".tmp*"); err == nil {
f.Close()
os.Remove(f.Name())
return true
}
return false
}
func (config *Config) saveConfig() error {

View file

@ -48,9 +48,34 @@ type Controller struct {
Register chan *Client
Unregister chan *Client
Ingest chan *Call
initialized bool
ffmpeg bool
ffmpegWarned bool
running bool
}
func NewController(config *Config) *Controller {
controller := &Controller{
Config: config,
Accesses: &Accesses{},
Apikeys: &Apikeys{},
Dirwatches: &Dirwatches{},
Downstreams: &Downstreams{},
Groups: &Groups{},
Options: &Options{},
Systems: &Systems{},
Tags: &Tags{},
Clients: make(map[*Client]bool),
Register: make(chan *Client, 64),
Unregister: make(chan *Client, 64),
Ingest: make(chan *Call, 64),
}
controller.Admin = NewAdmin(controller)
controller.Api = NewApi(controller)
controller.Database = NewDatabase(config)
controller.Scheduler = NewScheduler(controller)
return controller
}
func (controller *Controller) CheckDuplicate(call *Call) bool {
@ -68,7 +93,7 @@ func (controller *Controller) CheckDuplicate(call *Call) bool {
return count > 0
}
func (controller *Controller) ConvertAudio(call *Call) error {
func (controller *Controller) ConvertAudio(call *Call) {
var (
args = []string{"-i", "-"}
err error
@ -80,8 +105,7 @@ func (controller *Controller) ConvertAudio(call *Call) error {
LogEvent(controller.Database, LogLevelWarn, "ffmpeg is not accessible, no audio conversion will be performed.")
}
return nil
return
}
if system, ok := controller.Systems.GetSystem(call.System); ok {
@ -106,6 +130,9 @@ func (controller *Controller) ConvertAudio(call *Call) error {
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"
@ -114,9 +141,10 @@ func (controller *Controller) ConvertAudio(call *Call) error {
case string:
call.AudioName = fmt.Sprintf("%v.m4a", strings.TrimSuffix(v, path.Ext((v))))
}
}
return err
} else {
fmt.Println(stderr.String())
}
}
func (controller *Controller) EmitCall(call *Call) {
@ -323,10 +351,7 @@ func (controller *Controller) IngestCall(call *Call) {
}
if !controller.Options.DisableAudioConversion {
if err = controller.ConvertAudio(call); err != nil {
logError(fmt.Errorf("convertaudio: %s", err.Error()))
return
}
controller.ConvertAudio(call)
}
if id, err = call.Write(controller.Database); err == nil {
@ -339,111 +364,6 @@ func (controller *Controller) IngestCall(call *Call) {
}
}
func (controller *Controller) Init(config *Config, database *Database) error {
if controller.initialized {
return errors.New("controller already initialized")
}
var err error
controller.initialized = true
controller.Admin = &Admin{}
controller.Api = &Api{}
controller.Config = config
controller.Database = database
controller.Accesses = &Accesses{}
controller.Apikeys = &Apikeys{}
controller.Dirwatches = &Dirwatches{}
controller.Downstreams = &Downstreams{}
controller.Groups = &Groups{}
controller.Options = &Options{}
controller.Scheduler = &Scheduler{}
controller.Systems = &Systems{}
controller.Tags = &Tags{}
controller.Clients = make(map[*Client]bool)
controller.Register = make(chan *Client, 64)
controller.Unregister = make(chan *Client, 64)
controller.Ingest = make(chan *Call, 64)
if err = controller.Accesses.Read(database); err != nil {
return err
}
if err = controller.Apikeys.Read(database); err != nil {
return err
}
if err = controller.Dirwatches.Read(database); err != nil {
return err
}
if err = controller.Downstreams.Read(database); err != nil {
return err
}
if err = controller.Groups.Read(database); err != nil {
return err
}
if err = controller.Options.Read(database); err != nil {
return err
}
if err = controller.Systems.Read(database); err != nil {
return err
}
if err = controller.Tags.Read(database); err != nil {
return err
}
if err = controller.Admin.Init(controller); err != nil {
return err
}
if err = controller.Api.Init(controller); err != nil {
return err
}
if err = controller.Scheduler.Init(controller); err != nil {
return err
}
cmd := exec.Command("ffmpeg", "-version")
if err := cmd.Run(); err == nil {
controller.ffmpeg = true
} else {
controller.ffmpeg = false
}
if err = controller.Scheduler.Start(); err != nil {
log.Printf("scheduler: %s", err.Error())
}
go func() {
c := make(chan os.Signal, 1)
signal.Notify(c, os.Interrupt)
<-c
controller.Terminate()
}()
go func() {
for {
select {
case call := <-controller.Ingest:
controller.IngestCall(call)
case client := <-controller.Register:
controller.Clients[client] = true
controller.LogClientsCount()
case client := <-controller.Unregister:
if _, ok := controller.Clients[client]; ok {
delete(controller.Clients, client)
close(client.Send)
controller.LogClientsCount()
}
}
}
}()
controller.Dirwatches.Start(controller)
return nil
}
func (controller *Controller) LogClientsCount() {
LogEvent(
controller.Database,
@ -629,6 +549,93 @@ func (controller *Controller) SendClientConfig(client *Client) {
}
}
func (controller *Controller) Start() error {
var err error
if controller.running {
return errors.New("controller already running")
} else {
controller.running = true
}
LogEvent(controller.Database, LogLevelWarn, "server started")
if len(controller.Config.BaseDir) > 0 {
LogEvent(controller.Database, LogLevelWarn, fmt.Sprintf("base folder is %s", controller.Config.BaseDir))
}
if err = controller.Accesses.Read(controller.Database); err != nil {
return err
}
if err = controller.Apikeys.Read(controller.Database); err != nil {
return err
}
if err = controller.Dirwatches.Read(controller.Database); err != nil {
return err
}
if err = controller.Downstreams.Read(controller.Database); err != nil {
return err
}
if err = controller.Groups.Read(controller.Database); err != nil {
return err
}
if err = controller.Options.Read(controller.Database); err != nil {
return err
}
if err = controller.Systems.Read(controller.Database); err != nil {
return err
}
if err = controller.Tags.Read(controller.Database); err != nil {
return err
}
if err = controller.Admin.Start(); err != nil {
return err
}
if err = controller.Scheduler.Start(); err != nil {
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, 1)
signal.Notify(c, os.Interrupt)
<-c
controller.Terminate()
}()
go func() {
for {
select {
case call := <-controller.Ingest:
controller.IngestCall(call)
case client := <-controller.Register:
controller.Clients[client] = true
controller.LogClientsCount()
case client := <-controller.Unregister:
if _, ok := controller.Clients[client]; ok {
delete(controller.Clients, client)
close(client.Send)
controller.LogClientsCount()
}
}
}
}()
controller.Dirwatches.Start(controller)
return nil
}
func (controller *Controller) Terminate() {
if err := controller.Database.Sql.Close(); err != nil {
log.Println(err)
@ -636,6 +643,10 @@ func (controller *Controller) Terminate() {
controller.Dirwatches.Stop()
for c := range controller.Clients {
c.Conn.Close()
}
log.Println("terminated")
os.Exit(0)

View file

@ -62,14 +62,16 @@ func NewDaemon() *Daemon {
return &d
}
func (d *Daemon) Control(action string) (bool, error) {
func (d *Daemon) Control(action string) {
if action == "run" {
go d.Service.Run()
return true, nil
}
} else if err := service.Control(d.Service, action); err == nil {
os.Exit(0)
return false, service.Control(d.Service, action)
} else {
os.Exit(-1)
}
}
type DaemonInterface struct{}

View file

@ -17,7 +17,6 @@ package main
import (
"database/sql"
"errors"
"fmt"
"log"
"time"
@ -27,59 +26,52 @@ import (
)
type Database struct {
initialized bool
Config *Config
DateTimeFormat string
Sql *sql.DB
}
func (db *Database) Init(config *Config) error {
func NewDatabase(config *Config) *Database {
var err error
if db.initialized {
return errors.New("database already initialized")
}
database := &Database{Config: config}
db.initialized = true
db.Config = config
switch db.Config.DbType {
switch config.DbType {
case DbTypeSqlite:
db.DateTimeFormat = "2006-01-02 15:04:05.000 -07:00"
database.DateTimeFormat = "2006-01-02 15:04:05.000 -07:00"
dsn := fmt.Sprintf("file:%s", db.Config.GetDbFilePath())
dsn := fmt.Sprintf("file:%s", config.GetDbFilePath())
if db.Sql, err = sql.Open("sqlite", dsn); err != nil {
return err
if database.Sql, err = sql.Open("sqlite", dsn); err != nil {
log.Fatal(err)
}
case DbTypeMariadb, DbTypeMysql:
db.DateTimeFormat = "2006-01-02 15:04:05"
database.DateTimeFormat = "2006-01-02 15:04:05"
dsn := fmt.Sprintf("%s:%s@tcp(%s:%d)/%s", db.Config.DbUsername, db.Config.DbPassword, db.Config.DbHost, db.Config.DbPort, db.Config.DbName)
dsn := fmt.Sprintf("%s:%s@tcp(%s:%d)/%s", config.DbUsername, config.DbPassword, config.DbHost, config.DbPort, config.DbName)
if db.Sql, err = sql.Open("mysql", dsn); err != nil {
return err
if database.Sql, err = sql.Open("mysql", dsn); err != nil {
log.Fatal(err)
}
default:
return fmt.Errorf("unknown database type %s", db.Config.DbType)
log.Fatalf("unknown database type %s\n", config.DbType)
}
db.Sql.SetConnMaxLifetime(0)
db.Sql.SetMaxIdleConns(10)
db.Sql.SetMaxOpenConns(10)
database.Sql.SetConnMaxLifetime(0)
database.Sql.SetMaxIdleConns(10)
database.Sql.SetMaxOpenConns(10)
if err = db.migrate(); err != nil {
return err
if err = database.migrate(); err != nil {
log.Fatal(err)
}
if err = db.seed(); err != nil {
return err
if err = database.seed(); err != nil {
log.Fatal(err)
}
return nil
return database
}
func (db *Database) ParseDateTime(f interface{}) (time.Time, error) {
@ -102,7 +94,7 @@ func (db *Database) Prune(controller *Controller) error {
LogEvent(controller.Database, LogLevelInfo, "database pruning")
date := time.Now().Truncate(time.Duration(controller.Options.PruneDays*24) * time.Hour).Format(db.DateTimeFormat)
date := time.Now().Add(-24 * time.Hour * time.Duration(controller.Options.PruneDays)).Format(db.DateTimeFormat)
if _, err := controller.Database.Sql.Exec("delete from `rdioScannerCalls` where `dateTime` < ?", date); err != nil {
return err

View file

@ -274,7 +274,7 @@ func (dirwatch *Dirwatch) ingestTrunkRecorder(p string) error {
}
if call.Audio, err = ioutil.ReadFile(audioName); err != nil {
return err
return nil
}
if b, err = ioutil.ReadFile(p); err != nil {
@ -494,9 +494,10 @@ func (dirwatch *Dirwatch) Start(controller *Controller) error {
}
}()
fileSystem := os.DirFS(dirwatch.Directory)
go func() {
time.Sleep(2 * time.Second)
return fs.WalkDir(fileSystem, ".", func(p string, d fs.DirEntry, err error) error {
fs.WalkDir(os.DirFS(dirwatch.Directory), ".", func(p string, d fs.DirEntry, err error) error {
fp := path.Join(dirwatch.Directory, p)
if dirwatch.isDir(fp) {
@ -508,6 +509,9 @@ func (dirwatch *Dirwatch) Start(controller *Controller) error {
}
return err
})
}()
return nil
}
func (dirwatch *Dirwatch) Stop() {
@ -582,11 +586,9 @@ func (dirwatches *Dirwatches) Read(db *Database) error {
*dirwatches = append(*dirwatches, dirwatch)
}
if err != nil {
return formatError(err)
}
rows.Close()
if err = rows.Close(); err != nil {
if err != nil {
return formatError(err)
}
@ -641,8 +643,7 @@ func (dirwatches *Dirwatches) Write(db *Database) error {
return formatError(err)
}
rows, err = db.Sql.Query("select `_id` from `rdioScannerDirWatches`")
if err != nil {
if rows, err = db.Sql.Query("select `_id` from `rdioScannerDirWatches`"); err != nil {
return formatError(err)
}
@ -661,12 +662,9 @@ func (dirwatches *Dirwatches) Write(db *Database) error {
}
}
if err != nil {
rows.Close()
return formatError(err)
}
if err = rows.Close(); err != nil {
if err != nil {
return formatError(err)
}
@ -674,28 +672,13 @@ func (dirwatches *Dirwatches) Write(db *Database) error {
}
func (dirwatch *Dirwatch) isDir(d string) bool {
var isDir bool
logError := func(err error) {
LogEvent(dirwatch.controller.Database, LogLevelError, fmt.Sprintf("dirwatch.isDir: %s", err.Error()))
}
if f, err := os.Open(d); err == nil {
if fi, err := f.Stat(); err == nil {
if fi, err := os.Stat(d); err == nil {
if fi.IsDir() {
isDir = true
return true
}
} else {
logError(err)
}
if err := f.Close(); err != nil {
logError(err)
}
} else {
logError(err)
}
return isDir
return false
}
func (dirwatch *Dirwatch) walkDir(d string) error {

View file

@ -337,23 +337,24 @@ func (downstreams *Downstreams) FromMap(f []interface{}) {
func (downstreams *Downstreams) Read(db *Database) error {
var (
apikey interface{}
err error
id uint
rows *sql.Rows
systems interface{}
)
*downstreams = Downstreams{}
formatError := func(err error) error {
return fmt.Errorf("downstreams.read: %v", err)
}
if rows, err = db.Sql.Query("select `_id`, `apiKey`, `disabled`, `order`, `systems`, `url` from `rdioScannerDownstreams`"); err != nil {
return err
return formatError(err)
}
for rows.Next() {
var (
apikey interface{}
id uint
systems interface{}
)
downstream := Downstream{}
if err = rows.Scan(&id, &apikey, &downstream.Disabled, &downstream.Order, &systems, &downstream.Url); err != nil {
break
@ -383,12 +384,10 @@ func (downstreams *Downstreams) Read(db *Database) error {
*downstreams = append(*downstreams, downstream)
}
if err != nil {
return err
}
rows.Close()
if err = rows.Close(); err != nil {
return err
if err != nil {
return formatError(err)
}
return nil
@ -473,12 +472,9 @@ func (downstreams *Downstreams) Write(db *Database) error {
}
}
if err != nil {
rows.Close()
return formatError(err)
}
if err = rows.Close(); err != nil {
if err != nil {
return formatError(err)
}

View file

@ -21,15 +21,15 @@ require (
github.com/remyoudompheng/bigfft v0.0.0-20200410134404-eec4a21b6bb0 // indirect
github.com/stretchr/testify v1.7.0 // indirect
golang.org/x/mod v0.5.1 // indirect
golang.org/x/net v0.0.0-20211205041911-012df41ee64c // indirect
golang.org/x/sys v0.0.0-20211204120058-94396e421777 // indirect
golang.org/x/net v0.0.0-20211208012354-db4efeb81f4b // indirect
golang.org/x/sys v0.0.0-20211205182925-97ca703d548d // indirect
golang.org/x/text v0.3.7 // indirect
golang.org/x/tools v0.1.8 // indirect
golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1 // indirect
lukechampine.com/uint128 v1.1.1 // indirect
modernc.org/cc/v3 v3.35.18 // indirect
modernc.org/ccgo/v3 v3.12.86 // indirect
modernc.org/libc v1.11.88 // indirect
modernc.org/ccgo/v3 v3.12.87 // indirect
modernc.org/libc v1.11.89 // indirect
modernc.org/mathutil v1.4.1 // indirect
modernc.org/memory v1.0.5 // indirect
modernc.org/opt v0.1.1 // indirect

View file

@ -47,8 +47,8 @@ golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLL
golang.org/x/net v0.0.0-20201021035429-f5854403a974/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU=
golang.org/x/net v0.0.0-20211015210444-4f30a5c0130f/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y=
golang.org/x/net v0.0.0-20211112202133-69e39bad7dc2/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y=
golang.org/x/net v0.0.0-20211205041911-012df41ee64c h1:7SfqwP5fxEtl/P02w5IhKc86ziJ+A25yFrkVgoy2FT8=
golang.org/x/net v0.0.0-20211205041911-012df41ee64c/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y=
golang.org/x/net v0.0.0-20211208012354-db4efeb81f4b h1:MWaHNqZy3KTpuTMAGvv+Kw+ylsEpmyJZizz1dqxnu28=
golang.org/x/net v0.0.0-20211208012354-db4efeb81f4b/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y=
golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.0.0-20201020160332-67f06af15bc9/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.0.0-20210220032951-036812b2e83c/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
@ -65,8 +65,8 @@ golang.org/x/sys v0.0.0-20210630005230-0f9fa26af87c/go.mod h1:oPkhp1MJrh7nUepCBc
golang.org/x/sys v0.0.0-20210902050250-f475640dd07b/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20211007075335-d3039528d8ac/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20211019181941-9d821ace8654/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20211204120058-94396e421777 h1:QAkhGVjOxMa+n4mlsAWeAU+BMZmimQAaNiMu+iUi94E=
golang.org/x/sys v0.0.0-20211204120058-94396e421777/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20211205182925-97ca703d548d h1:FjkYO/PPp4Wi0EAUOVLxePm7qVW4r4ctbWpURyuOD0E=
golang.org/x/sys v0.0.0-20211205182925-97ca703d548d/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
@ -141,8 +141,9 @@ modernc.org/ccgo/v3 v3.12.73/go.mod h1:hngkB+nUUqzOf3iqsM48Gf1FZhY599qzVg1iX+BT3
modernc.org/ccgo/v3 v3.12.81/go.mod h1:p2A1duHoBBg1mFtYvnhAnQyI6vL0uw5PGYLSIgF6rYY=
modernc.org/ccgo/v3 v3.12.82/go.mod h1:ApbflUfa5BKadjHynCficldU1ghjen84tuM5jRynB7w=
modernc.org/ccgo/v3 v3.12.84/go.mod h1:ApbflUfa5BKadjHynCficldU1ghjen84tuM5jRynB7w=
modernc.org/ccgo/v3 v3.12.86 h1:zN0MkWIf1a2SgP6w9V3EG5r30CSrpRunyzJ5wcYRLFM=
modernc.org/ccgo/v3 v3.12.86/go.mod h1:dN7S26DLTgVSni1PVA3KxxHTcykyDurf3OgUzNqTSrU=
modernc.org/ccgo/v3 v3.12.87 h1:d8IVZadjkPWICHmMWEaPAL37zBrZ7rE7/+Plegf0cyw=
modernc.org/ccgo/v3 v3.12.87/go.mod h1:55tmwIw8bqNbqYCZ2DIXGqB1I0goRe6tq2qHqr1FaQU=
modernc.org/ccorpus v1.11.1 h1:K0qPfpVG1MJh5BYazccnmhywH4zHuOgJXgbjzyp6dWA=
modernc.org/ccorpus v1.11.1/go.mod h1:2gEUTrWqdpH2pXsmTM1ZkjeSrUWDpjMu2T6m29L/ErQ=
modernc.org/httpfs v1.0.6 h1:AAgIpFZRXuYnkjftxTAZwMIiwEqAfk8aVB2/oA6nAeM=
@ -182,8 +183,9 @@ modernc.org/libc v1.11.75/go.mod h1:dGRVugT6edz361wmD9gk6ax1AbDSe0x5vji0dGJiPT0=
modernc.org/libc v1.11.82/go.mod h1:NF+Ek1BOl2jeC7lw3a7Jj5PWyHPwWD4aq3wVKxqV1fI=
modernc.org/libc v1.11.86/go.mod h1:ePuYgoQLmvxdNT06RpGnaDKJmDNEkV7ZPKI2jnsvZoE=
modernc.org/libc v1.11.87/go.mod h1:Qvd5iXTeLhI5PS0XSyqMY99282y+3euapQFxM7jYnpY=
modernc.org/libc v1.11.88 h1:ibbHksDVITfJtBTSCENSXRt/pL4P/QJP+EzJUimW268=
modernc.org/libc v1.11.88/go.mod h1:h3oIVe8dxmTcchcFuCcJ4nAWaoiwzKCdv82MM0oiIdQ=
modernc.org/libc v1.11.89 h1:41X0GhYT3+0WbXvsOMDguwnmfY6692gXkSlOuSaO9jc=
modernc.org/libc v1.11.89/go.mod h1:ynK5sbjsU77AP+nn61+k+wxUGRx9rOFcIqWYYMaDZ4c=
modernc.org/mathutil v1.1.1/go.mod h1:mZW8CKdRPY1v87qxC/wUdX5O1qDzXMP5TH3wjfpga6E=
modernc.org/mathutil v1.2.2/go.mod h1:mZW8CKdRPY1v87qxC/wUdX5O1qDzXMP5TH3wjfpga6E=
modernc.org/mathutil v1.4.0/go.mod h1:mZW8CKdRPY1v87qxC/wUdX5O1qDzXMP5TH3wjfpga6E=

View file

@ -166,11 +166,9 @@ func (groups *Groups) Read(db *Database) error {
*groups = append(*groups, group)
}
if err != nil {
return formatError(err)
}
rows.Close()
if err = rows.Close(); err != nil {
if err != nil {
return formatError(err)
}
@ -228,12 +226,9 @@ func (groups *Groups) Write(db *Database) error {
}
}
if err != nil {
rows.Close()
return formatError(err)
}
if err = rows.Close(); err != nil {
if err != nil {
return formatError(err)
}

View file

@ -229,12 +229,10 @@ func NewLogResults(logOptions *LogOptions, db *Database) (*LogResults, error) {
logResults.Logs = append(logResults.Logs, log)
}
if err != nil {
return logResults, formatError(err)
}
rows.Close()
if err = rows.Close(); err != nil {
return logResults, formatError(err)
if err != nil {
return nil, formatError(err)
}
return logResults, nil

View file

@ -40,27 +40,16 @@ func main() {
sslPort string
)
config := &Config{}
ok, err := config.Init()
if err != nil {
log.Fatal(err)
}
if !ok {
os.Exit(0)
}
config := NewConfig()
database := &Database{}
if err = database.Init(config); err != nil {
log.Fatal(err)
}
controller := &Controller{}
if err = controller.Init(config, database); err != nil {
log.Fatal(err)
}
controller := NewController(config)
if config.newAdminPassword != "" {
if hash, err := bcrypt.GenerateFromPassword([]byte(config.newAdminPassword), bcrypt.DefaultCost); err == nil {
if err := controller.Options.Read(controller.Database); err != nil {
log.Fatal(err)
}
controller.Options.adminPassword = string(hash)
controller.Options.adminPasswordNeedChange = config.newAdminPassword == defaults.adminPassword
@ -68,10 +57,6 @@ func main() {
log.Fatal(err)
}
if err := controller.Options.Read(controller.Database); err != nil {
log.Fatal(err)
}
LogEvent(controller.Database, LogLevelInfo, "admin password changed.")
os.Exit(0)
@ -84,7 +69,9 @@ func main() {
fmt.Printf("\nRdio Scanner v%s\n", Version)
fmt.Printf("----------------------------------\n")
LogEvent(controller.Database, LogLevelWarn, "server started")
if err := controller.Start(); err != nil {
log.Fatal(err)
}
if h, err := os.Hostname(); err == nil {
hostname = h

View file

@ -22,22 +22,17 @@ import (
)
type Scheduler struct {
initialized bool
cancel chan interface{}
Controller *Controller
Ticker *time.Ticker
cancel chan interface{}
running bool
}
func (scheduler *Scheduler) Init(controller *Controller) error {
if scheduler.initialized {
return errors.New("scheduler already initialized")
func NewScheduler(controller *Controller) *Scheduler {
return &Scheduler{
Controller: controller,
cancel: make(chan interface{}),
}
scheduler.Controller = controller
scheduler.cancel = make(chan interface{})
scheduler.initialized = true
return nil
}
func (scheduler *Scheduler) run() {
@ -55,14 +50,10 @@ func (scheduler *Scheduler) run() {
}
func (scheduler *Scheduler) Start() error {
if !scheduler.initialized {
return errors.New("scheduler not initialized")
}
if scheduler.Ticker != nil {
if err := scheduler.Stop(); err != nil {
return err
}
if scheduler.running {
return errors.New("scheduler already running")
} else {
scheduler.running = true
}
scheduler.Ticker = time.NewTicker(time.Hour)
@ -83,12 +74,13 @@ func (scheduler *Scheduler) Start() error {
}
func (scheduler *Scheduler) Stop() error {
if !scheduler.initialized {
return errors.New("scheduler not initialized")
if !scheduler.running {
return errors.New("scheduler not running")
}
scheduler.Ticker.Stop()
scheduler.Ticker = nil
scheduler.running = false
return nil
}

View file

@ -283,8 +283,10 @@ func NewSearchResults(searchOptions *SearchOptions, client *Client) (*SearchResu
searchResults.Results = append(searchResults.Results, searchResult)
}
rows.Close()
if err != nil {
err = formatError(err)
return nil, formatError(err)
}
return searchResults, err

View file

@ -358,11 +358,9 @@ func (systems *Systems) Read(db *Database) error {
*systems = append(*systems, system)
}
if err != nil {
return formatError(err)
}
rows.Close()
if err = rows.Close(); err != nil {
if err != nil {
return formatError(err)
}
@ -437,12 +435,9 @@ func (systems *Systems) Write(db *Database) error {
}
}
if err != nil {
rows.Close()
return formatError(err)
}
if err = rows.Close(); err != nil {
if err != nil {
return formatError(err)
}

View file

@ -166,11 +166,9 @@ func (tags *Tags) Read(db *Database) error {
*tags = append(*tags, tag)
}
if err != nil {
return formatError(err)
}
rows.Close()
if err = rows.Close(); err != nil {
if err != nil {
return formatError(err)
}
@ -227,12 +225,9 @@ func (tags *Tags) Write(db *Database) error {
}
}
if err != nil {
rows.Close()
return formatError(err)
}
if err = rows.Close(); err != nil {
if err != nil {
return formatError(err)
}

View file

@ -15,4 +15,4 @@
package main
const Version = "6.0.3"
const Version = "6.0.4"