├── static
├── robots.txt
├── favicon.ico
├── stats.hbs
└── main.css
├── .dockerignore
├── init.sql
├── ansible
├── playbook.yml
├── inventory
└── roles
│ └── radiobrowser
│ ├── templates
│ └── radio-browser.conf.j2
│ └── tasks
│ └── main.yml
├── install_from_source.sh
├── src
├── check
│ ├── mod.rs
│ ├── diff_calc.rs
│ └── favicon.rs
├── db
│ ├── mod.rs
│ ├── models
│ │ ├── station_click_item_new.rs
│ │ ├── station_click_item.rs
│ │ ├── db_country.rs
│ │ ├── station_check_step_item_new.rs
│ │ ├── station_check_step_item.rs
│ │ ├── streaming_server_new.rs
│ │ ├── station_change_item_new.rs
│ │ ├── streaming_server.rs
│ │ ├── station_history_item.rs
│ │ ├── mod.rs
│ │ ├── station_check_item.rs
│ │ ├── extra_info.rs
│ │ ├── state.rs
│ │ ├── station_check_item_new.rs
│ │ └── station_item.rs
│ ├── db_error.rs
│ ├── db_mysql
│ │ ├── simple_migrate.rs
│ │ └── conversions.rs
│ └── db.rs
├── api
│ ├── api_response.rs
│ ├── api_error.rs
│ ├── data
│ │ ├── result_message.rs
│ │ ├── mod.rs
│ │ ├── station_add_result.rs
│ │ ├── status.rs
│ │ ├── api_streaming_server.rs
│ │ ├── api_language.rs
│ │ ├── station_check_step.rs
│ │ ├── api_country.rs
│ │ ├── station_click.rs
│ │ ├── api_config.rs
│ │ ├── station_history.rs
│ │ └── station_check.rs
│ ├── cache
│ │ ├── memcached.rs
│ │ ├── redis.rs
│ │ ├── builtin.rs
│ │ └── mod.rs
│ ├── all_params.rs
│ ├── prometheus_exporter.rs
│ └── parameters.rs
├── pull
│ ├── uuid_with_time.rs
│ └── pull_error.rs
├── config
│ ├── config_error.rs
│ ├── data_mapping_item.rs
│ └── config.rs
├── cleanup
│ └── mod.rs
├── checkserver
│ └── mod.rs
├── cli.rs
├── refresh
│ └── mod.rs
├── logger.rs
└── main.rs
├── .gitignore
├── etc
├── tag-replace.csv
├── logrotate
├── language-replace.csv
├── language-to-code.csv
└── config-example.toml
├── start.sh
├── uninstall.sh
├── debian
├── postinst
└── radiobrowser.service
├── deployment
├── backup.sh
└── HEADER.html
├── traefik-dyn-config.toml
├── .github
└── workflows
│ └── rust.yml
├── Dockerfile
├── docker-compose.yml
├── install_from_dist.sh
├── .gitlab-ci.yml
├── docker-compose-traefik.yml
├── Cargo.toml
├── radiobrowser-dev.toml
├── CODE_OF_CONDUCT.md
└── CHANGELOG.md
/static/robots.txt:
--------------------------------------------------------------------------------
1 | User-agent: *
2 | Disallow: /
--------------------------------------------------------------------------------
/.dockerignore:
--------------------------------------------------------------------------------
1 | target/
2 | .git
3 | .gitignore
4 | Dockerfile
5 | dist/
--------------------------------------------------------------------------------
/init.sql:
--------------------------------------------------------------------------------
1 | CREATE DATABASE radio;
2 | GRANT all ON radio.* TO radiouser IDENTIFIED BY "password";
--------------------------------------------------------------------------------
/ansible/playbook.yml:
--------------------------------------------------------------------------------
1 | ---
2 | - hosts: all
3 | remote_user: root
4 | roles:
5 | - radiobrowser
--------------------------------------------------------------------------------
/install_from_source.sh:
--------------------------------------------------------------------------------
1 | #!/bin/bash
2 |
3 | set -e
4 |
5 | ./builddist.sh
6 | cd dist
7 | ./install.sh
--------------------------------------------------------------------------------
/src/check/mod.rs:
--------------------------------------------------------------------------------
1 | mod check;
2 | mod favicon;
3 | mod diff_calc;
4 |
5 | pub use check::dbcheck;
6 |
--------------------------------------------------------------------------------
/static/favicon.ico:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/segler-alex/radiobrowser-api-rust/HEAD/static/favicon.ico
--------------------------------------------------------------------------------
/ansible/inventory:
--------------------------------------------------------------------------------
1 | [servers]
2 | test.api.radio-browser.info
3 |
4 | [servers:vars]
5 | ansible_python_interpreter=auto
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 |
2 | /target/
3 | **/*.rs.bk
4 |
5 | dbdata/
6 |
7 | radio\.sql\.gz
8 |
9 | access\.log
10 | dist/
11 | *.log
12 |
--------------------------------------------------------------------------------
/etc/tag-replace.csv:
--------------------------------------------------------------------------------
1 | # replace tags in the database in the first columen
2 | # with tags in the second column
3 | # empty strings in the second column does remove the item
4 | from;to
5 |
--------------------------------------------------------------------------------
/etc/logrotate:
--------------------------------------------------------------------------------
1 | /var/log/radiobrowser/*.log {
2 | daily
3 | rotate 10
4 | copytruncate
5 | compress
6 | delaycompress
7 | notifempty
8 | missingok
9 | }
10 |
--------------------------------------------------------------------------------
/etc/language-replace.csv:
--------------------------------------------------------------------------------
1 | # replace languages in the database in the first columen
2 | # with languages in the second column
3 | # empty strings in the second column does remove the item
4 | from;to
5 |
--------------------------------------------------------------------------------
/src/db/mod.rs:
--------------------------------------------------------------------------------
1 | mod db;
2 | mod db_mysql;
3 | mod db_error;
4 |
5 | pub mod models;
6 |
7 | pub use self::db::DbConnection;
8 | pub use self::db_mysql::MysqlConnection;
9 | pub use self::db_error::DbError;
--------------------------------------------------------------------------------
/start.sh:
--------------------------------------------------------------------------------
1 | #!/bin/bash
2 |
3 | rm radio.sql.gz
4 | wget http://www.radio-browser.info/backups/latest.sql.gz -O radio.sql.gz
5 | mkdir -p dbdata
6 | docker stack deploy -c docker-compose.yml radiobrowser
7 |
--------------------------------------------------------------------------------
/src/db/models/station_click_item_new.rs:
--------------------------------------------------------------------------------
1 | #[derive(Clone,Debug)]
2 | pub struct StationClickItemNew {
3 | pub stationuuid: String,
4 | pub ip: String,
5 | pub clickuuid: String,
6 | pub clicktimestamp: String,
7 | }
--------------------------------------------------------------------------------
/static/stats.hbs:
--------------------------------------------------------------------------------
1 |
2 |
3 | RadioBrowser Server Information
4 |
5 |
6 | RadioBrowser Server Information
7 | Status: {{ status.status }}
8 |
9 |
--------------------------------------------------------------------------------
/src/api/api_response.rs:
--------------------------------------------------------------------------------
1 | use std::fs::File;
2 |
3 | pub enum ApiResponse {
4 | Text(String),
5 | File(String, File),
6 | ServerError(String),
7 | NotFound,
8 | UnknownContentType,
9 | //ParameterError(String),
10 | Locked(String),
11 | }
12 |
--------------------------------------------------------------------------------
/uninstall.sh:
--------------------------------------------------------------------------------
1 | #!/bin/bash
2 | sudo rm /usr/bin/radiobrowser
3 | sudo userdel radiobrowser
4 | sudo groupdel radiobrowser
5 |
6 | sudo rm /etc/systemd/system/radiobrowser.service
7 | sudo systemctl daemon-reload
8 | sudo rm -rf /usr/share/radiobrowser
9 | sudo rm -rf /var/log/radiobrowser
10 |
11 | sudo rm /etc/logrotate.d/radiobrowser
--------------------------------------------------------------------------------
/src/pull/uuid_with_time.rs:
--------------------------------------------------------------------------------
1 | use std::time::{Instant};
2 | pub struct UuidWithTime {
3 | pub uuid: String,
4 | pub instant: Instant,
5 | }
6 |
7 | impl UuidWithTime {
8 | pub fn new(uuid: &str) -> Self{
9 | UuidWithTime {
10 | uuid: uuid.to_string(),
11 | instant: Instant::now(),
12 | }
13 | }
14 | }
--------------------------------------------------------------------------------
/src/db/models/station_click_item.rs:
--------------------------------------------------------------------------------
1 | use chrono::DateTime;
2 | use chrono::Utc;
3 |
4 | #[derive(Clone, Debug)]
5 | pub struct StationClickItem {
6 | pub id: i32,
7 | pub stationuuid: String,
8 | pub ip: String,
9 | pub clickuuid: String,
10 | pub clicktimestamp_iso8601: Option>,
11 | pub clicktimestamp: String,
12 | }
13 |
--------------------------------------------------------------------------------
/debian/postinst:
--------------------------------------------------------------------------------
1 | # Add user
2 | groupadd --system radiobrowser
3 | useradd --system --no-create-home --home-dir /var/lib/radiobrowser --gid radiobrowser radiobrowser
4 |
5 | # Create log dir
6 | mkdir -p /var/log/radiobrowser
7 | chown radiobrowser:radiobrowser /var/log/radiobrowser
8 |
9 | # Create home dir
10 | mkdir -p /var/lib/radiobrowser
11 | chown radiobrowser:radiobrowser /var/lib/radiobrowser
12 |
13 | #DEBHELPER#
--------------------------------------------------------------------------------
/deployment/backup.sh:
--------------------------------------------------------------------------------
1 | #!/bin/sh
2 | DBNAME="radio"
3 | NAME="/var/www/backups/backup_$(hostname -s)_$(date +"%Y-%m-%d").sql"
4 | mysqldump ${DBNAME} --ignore-table=${DBNAME}.StationCheckHistory --ignore-table=${DBNAME}.StationClick > ${NAME}
5 | mysqldump --no-data ${DBNAME} StationCheckHistory StationClick >> ${NAME}
6 | gzip -f ${NAME}
7 | rm /var/www/backups/latest.sql.gz
8 | ln -s "${NAME}.gz" /var/www/backups/latest.sql.gz
9 |
--------------------------------------------------------------------------------
/src/check/diff_calc.rs:
--------------------------------------------------------------------------------
1 | #[derive(Clone, Debug)]
2 | pub struct DiffCalc {
3 | pub old: T,
4 | pub new: T,
5 | }
6 |
7 | impl DiffCalc {
8 | pub fn new(current: T) -> Self {
9 | DiffCalc {
10 | old: current.clone(),
11 | new: current,
12 | }
13 | }
14 |
15 | pub fn changed(&self) -> bool {
16 | self.new != self.old
17 | }
18 | }
19 |
--------------------------------------------------------------------------------
/src/db/models/db_country.rs:
--------------------------------------------------------------------------------
1 | use serde::{Deserialize, Serialize};
2 |
3 | #[derive(PartialEq, Eq, Serialize, Deserialize)]
4 | pub struct DBCountry {
5 | pub iso_3166_1: String,
6 | pub stationcount: u32,
7 | }
8 |
9 | impl DBCountry {
10 | pub fn new(iso_3166_1: String, stationcount: u32) -> Self {
11 | DBCountry {
12 | iso_3166_1,
13 | stationcount,
14 | }
15 | }
16 | }
17 |
--------------------------------------------------------------------------------
/src/db/models/station_check_step_item_new.rs:
--------------------------------------------------------------------------------
1 | use serde::{Serialize,Deserialize};
2 |
3 | #[derive(PartialEq, Eq, Serialize, Deserialize, Debug, Clone)]
4 | pub struct StationCheckStepItemNew {
5 | pub stepuuid: String,
6 | pub parent_stepuuid: Option,
7 | pub checkuuid: String,
8 | pub stationuuid: String,
9 | pub url: String,
10 | pub urltype: Option,
11 | pub error: Option,
12 | }
--------------------------------------------------------------------------------
/traefik-dyn-config.toml:
--------------------------------------------------------------------------------
1 | [tls.options]
2 | [tls.options.default]
3 | minVersion = "VersionTLS12"
4 | cipherSuites = [
5 | "TLS_ECDHE_ECDSA_WITH_AES_128_GCM_SHA256",
6 | "TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256",
7 | "TLS_ECDHE_ECDSA_WITH_AES_256_GCM_SHA384",
8 | "TLS_ECDHE_RSA_WITH_AES_256_GCM_SHA384",
9 | "TLS_ECDHE_ECDSA_WITH_CHACHA20_POLY1305",
10 | "TLS_ECDHE_RSA_WITH_CHACHA20_POLY1305"
11 | ]
--------------------------------------------------------------------------------
/src/api/api_error.rs:
--------------------------------------------------------------------------------
1 | use std::error::Error;
2 | use std::fmt::Display;
3 | use std::fmt::Formatter;
4 | use std::fmt::Result;
5 |
6 | #[derive(Debug, Clone)]
7 | pub enum ApiError {
8 | InternalError(String),
9 | }
10 |
11 | impl Display for ApiError {
12 | fn fmt(&self, f: &mut Formatter) -> Result {
13 | match *self {
14 | ApiError::InternalError(ref v) => write!(f, "InternalError '{}'", v),
15 | }
16 | }
17 | }
18 |
19 | impl Error for ApiError {}
20 |
--------------------------------------------------------------------------------
/src/pull/pull_error.rs:
--------------------------------------------------------------------------------
1 | use std::error::Error;
2 | use std::fmt::Display;
3 | use std::fmt::Formatter;
4 | use std::fmt::Result;
5 |
6 | #[derive(Debug, Clone)]
7 | pub enum PullError {
8 | UnknownApiVersion(u32),
9 | }
10 |
11 | impl Display for PullError {
12 | fn fmt(&self, f: &mut Formatter) -> Result {
13 | match *self {
14 | PullError::UnknownApiVersion(ref v) => write!(f, "UnknownApiVersion {}", v),
15 | }
16 | }
17 | }
18 |
19 | impl Error for PullError {}
20 |
--------------------------------------------------------------------------------
/static/main.css:
--------------------------------------------------------------------------------
1 | /*
2 | #558C89 dark
3 | #74AFAD light
4 | #D9853B orange
5 | #ECECEA grey
6 | j*/
7 |
8 | body {
9 | background-color: #ede7df;
10 | font-family: "Segoe UI", Helvetica, Arial, sans-serif;
11 | }
12 | .syntax {
13 | line-height: 1.4;
14 | }
15 | .dropdown-item:hover{
16 | background-color: #007bff;
17 | color: white;
18 | }
19 | .format, .searchTerm{
20 | font-style: italic;
21 | font-weight: bold;
22 | color: rgb(116, 116, 116);
23 | }
24 |
25 | .result {
26 | margin: 20px;
27 | }
--------------------------------------------------------------------------------
/src/db/models/station_check_step_item.rs:
--------------------------------------------------------------------------------
1 | use chrono::DateTime;
2 | use chrono::Utc;
3 | use serde::{Serialize,Deserialize};
4 |
5 | #[derive(PartialEq, Eq, Serialize, Deserialize, Debug, Clone)]
6 | pub struct StationCheckStepItem {
7 | pub id: u32,
8 | pub stepuuid: String,
9 | pub parent_stepuuid: Option,
10 | pub checkuuid: String,
11 | pub stationuuid: String,
12 | pub url: String,
13 | pub urltype: Option,
14 | pub error: Option,
15 | pub inserttime: DateTime,
16 | }
--------------------------------------------------------------------------------
/deployment/HEADER.html:
--------------------------------------------------------------------------------
1 | Radio-browser database backups
2 | The backups are done with the following commands on a daily basis:
3 |
4 |
5 | #!/bin/sh
6 | DBNAME="radio"
7 | NAME="/var/www/backups/backup_$(hostname -s)_$(date +"%Y-%m-%d").sql"
8 | mysqldump ${DBNAME} --ignore-table=${DBNAME}.StationCheckHistory --ignore-table=${DBNAME}.StationClick > ${NAME}
9 | mysqldump --no-data ${DBNAME} StationCheckHistory StationClick >> ${NAME}
10 | gzip -f ${NAME}
11 | rm /var/www/backups/latest.sql.gz
12 | ln -s "${NAME}.gz" /var/www/backups/latest.sql.gz
13 |
14 |
15 |
--------------------------------------------------------------------------------
/src/db/models/streaming_server_new.rs:
--------------------------------------------------------------------------------
1 | use serde::{Deserialize, Serialize};
2 |
3 | #[derive(PartialEq, Eq, Serialize, Deserialize, Debug)]
4 | pub struct DbStreamingServerNew {
5 | pub url: String,
6 | pub statusurl: Option,
7 | pub status: Option,
8 | pub error: Option,
9 | }
10 |
11 | impl DbStreamingServerNew {
12 | pub fn new(url: String, statusurl: Option, status: Option, error: Option) -> Self {
13 | DbStreamingServerNew {
14 | url,
15 | statusurl,
16 | status,
17 | error,
18 | }
19 | }
20 | }
21 |
--------------------------------------------------------------------------------
/src/db/models/station_change_item_new.rs:
--------------------------------------------------------------------------------
1 | use serde::{Serialize,Deserialize};
2 |
3 | #[derive(PartialEq, Serialize, Deserialize, Debug, Clone)]
4 | pub struct StationChangeItemNew {
5 | pub name: String,
6 | pub url: String,
7 | pub homepage: String,
8 | pub favicon: String,
9 | pub country: String,
10 | pub state: String,
11 | pub countrycode: String,
12 | pub language: String,
13 | pub languagecodes: String,
14 | pub tags: String,
15 | pub votes: i32,
16 |
17 | pub changeuuid: String,
18 | pub stationuuid: String,
19 | pub geo_lat: Option,
20 | pub geo_long: Option,
21 | }
--------------------------------------------------------------------------------
/ansible/roles/radiobrowser/templates/radio-browser.conf.j2:
--------------------------------------------------------------------------------
1 |
2 | ServerName {{ inventory_hostname }}
3 |
4 | ServerAdmin {{ email }}
5 | DocumentRoot /var/www/radio
6 |
7 | #ErrorLog ${APACHE_LOG_DIR}/error.radio.log
8 | #CustomLog ${APACHE_LOG_DIR}/access.radio.log combined
9 |
10 | ProxyPass "/" "http://localhost:8080/"
11 | ProxyPassReverse "/" "http://localhost:8080/"
12 |
13 | AddOutputFilterByType DEFLATE application/xml text/xml
14 | AddOutputFilterByType DEFLATE application/json
15 |
16 |
17 | AllowOverride All
18 | Order allow,deny
19 | allow from all
20 |
21 |
--------------------------------------------------------------------------------
/src/config/config_error.rs:
--------------------------------------------------------------------------------
1 | use std::error::Error;
2 | use std::fmt::Display;
3 | use std::fmt::Formatter;
4 | use std::fmt::Result;
5 |
6 | #[derive(Debug, Clone)]
7 | pub enum ConfigError {
8 | TypeError(String, String),
9 | }
10 |
11 | impl Display for ConfigError {
12 | fn fmt(&self, f: &mut Formatter) -> Result {
13 | match *self {
14 | ConfigError::TypeError(ref field_name, ref field_value) => write!(f, "Value {} for field {} has wrong type", field_name, field_value),
15 | }
16 | }
17 | }
18 |
19 | impl Error for ConfigError {
20 | fn description(&self) -> &str {
21 | "NO DESCRIPTION"
22 | }
23 |
24 | fn cause(&self) -> Option<&dyn Error> {
25 | None
26 | }
27 | }
--------------------------------------------------------------------------------
/src/db/models/streaming_server.rs:
--------------------------------------------------------------------------------
1 | use serde::{Deserialize, Serialize};
2 |
3 | #[derive(PartialEq, Eq, Serialize, Deserialize, Debug)]
4 | pub struct DbStreamingServer {
5 | pub id: u32,
6 | pub uuid: String,
7 | pub url: String,
8 | pub statusurl: Option,
9 | pub status: Option,
10 | pub error: Option,
11 | }
12 |
13 | impl DbStreamingServer {
14 | pub fn new(id: u32, uuid: String, url: String, statusurl: Option, status: Option, error: Option) -> Self {
15 | DbStreamingServer {
16 | id,
17 | uuid,
18 | url,
19 | statusurl,
20 | status,
21 | error,
22 | }
23 | }
24 | }
25 |
--------------------------------------------------------------------------------
/src/db/models/station_history_item.rs:
--------------------------------------------------------------------------------
1 | use chrono::Utc;
2 | use chrono::DateTime;
3 | use serde::{Serialize,Deserialize};
4 |
5 | #[derive(PartialEq, Serialize, Deserialize, Debug)]
6 | pub struct StationHistoryItem {
7 | pub id: i32,
8 | pub changeuuid: String,
9 | pub stationuuid: String,
10 | pub name: String,
11 | pub url: String,
12 | pub homepage: String,
13 | pub favicon: String,
14 | pub tags: String,
15 | pub countrycode: String,
16 | pub state: String,
17 | pub language: String,
18 | pub languagecodes: String,
19 | pub votes: i32,
20 | pub lastchangetime: String,
21 | pub lastchangetime_iso8601: Option>,
22 | pub geo_lat: Option,
23 | pub geo_long: Option,
24 | }
25 |
--------------------------------------------------------------------------------
/debian/radiobrowser.service:
--------------------------------------------------------------------------------
1 | [Unit]
2 | Description=RadioBrowserAPI
3 | After=network.target
4 |
5 | [Install]
6 | WantedBy=multi-user.target
7 |
8 | [Service]
9 |
10 | ##############################################################################
11 | ## Core requirements
12 | ##
13 |
14 | Type=simple
15 |
16 | ##############################################################################
17 | ## Package maintainers
18 | ##
19 |
20 | User=radiobrowser
21 | Group=radiobrowser
22 |
23 | # Prevent writes to /usr, /boot, and /etc
24 | ProtectSystem=full
25 |
26 | # Prevent accessing /home, /root and /run/user
27 | ProtectHome=true
28 |
29 | # Start main service
30 | ExecStart=/usr/bin/radiobrowser -f /etc/radiobrowser/config.toml
31 |
32 | Restart=always
33 | RestartSec=5s
34 |
--------------------------------------------------------------------------------
/.github/workflows/rust.yml:
--------------------------------------------------------------------------------
1 | on: [push, pull_request]
2 |
3 | name: Continuous integration
4 |
5 | jobs:
6 | check:
7 | name: Check
8 | runs-on: ubuntu-latest
9 | steps:
10 | - uses: actions/checkout@v2
11 | - uses: actions-rs/toolchain@v1
12 | with:
13 | profile: minimal
14 | toolchain: stable
15 | override: true
16 | - uses: actions-rs/cargo@v1
17 | with:
18 | command: check
19 |
20 | test:
21 | name: Test Suite
22 | runs-on: ubuntu-latest
23 | steps:
24 | - uses: actions/checkout@v2
25 | - uses: actions-rs/toolchain@v1
26 | with:
27 | profile: minimal
28 | toolchain: stable
29 | override: true
30 | - uses: actions-rs/cargo@v1
31 | with:
32 | command: test
33 |
--------------------------------------------------------------------------------
/src/db/db_error.rs:
--------------------------------------------------------------------------------
1 | use std::error::Error;
2 | use std::fmt::Display;
3 | use std::fmt::Formatter;
4 | use std::fmt::Result;
5 |
6 | #[derive(Debug, Clone)]
7 | pub enum DbError {
8 | //ConnectionError(String),
9 | VoteError(String),
10 | AddStationError(String),
11 | IllegalOrderError(String),
12 | }
13 |
14 | impl Display for DbError {
15 | fn fmt(&self, f: &mut Formatter) -> Result {
16 | match *self {
17 | //DbError::ConnectionError(ref v) => write!(f, "ConnectionError '{}'", v),
18 | DbError::VoteError(ref v) => write!(f, "VoteError '{}'", v),
19 | DbError::AddStationError(ref v) => write!(f, "AddStationError '{}'", v),
20 | DbError::IllegalOrderError(ref v) => write!(f, "IllegalOrderError '{}'", v),
21 | }
22 | }
23 | }
24 |
25 | impl Error for DbError {}
26 |
--------------------------------------------------------------------------------
/src/api/data/result_message.rs:
--------------------------------------------------------------------------------
1 | use serde::{Serialize,Deserialize};
2 |
3 | #[derive(Serialize, Deserialize)]
4 | pub struct ResultMessage {
5 | ok: bool,
6 | message: String,
7 | }
8 |
9 | impl ResultMessage {
10 | pub fn new(ok: bool, message: String) -> Self {
11 | ResultMessage{
12 | ok,
13 | message
14 | }
15 | }
16 |
17 | pub fn serialize_xml(&self) -> std::io::Result {
18 | let mut xml = xml_writer::XmlWriter::new(Vec::new());
19 | xml.begin_elem("result")?;
20 | xml.begin_elem("status")?;
21 | xml.attr_esc("ok", &self.ok.to_string())?;
22 | xml.attr_esc("message", &self.message)?;
23 | xml.end_elem()?;
24 | xml.end_elem()?;
25 | xml.close()?;
26 | xml.flush()?;
27 | Ok(String::from_utf8(xml.into_inner()).unwrap_or("encoding error".to_string()))
28 | }
29 | }
--------------------------------------------------------------------------------
/Dockerfile:
--------------------------------------------------------------------------------
1 | FROM alpine:3.14
2 | ADD . /app
3 | WORKDIR /app
4 | RUN apk update
5 | RUN apk add rustup openssl-dev gcc g++
6 | RUN rustup-init -y
7 | ENV PATH="/root/.cargo/bin:$PATH"
8 | RUN cargo build --release
9 |
10 | FROM alpine:3.14
11 | EXPOSE 8080
12 | COPY --from=0 /app/target/release/radiobrowser-api-rust /usr/bin/
13 | COPY --from=0 /app/static/ /usr/lib/radiobrowser/static/
14 | COPY --from=0 /app/etc/config-example.toml /etc/radiobrowser/config.toml
15 | COPY --from=0 /app/etc/*.csv /etc/radiobrowser/
16 | RUN addgroup -S radiobrowser && \
17 | adduser -S -G radiobrowser radiobrowser && \
18 | apk add libgcc && \
19 | mkdir -p /var/log/radiobrowser/ && \
20 | chown -R radiobrowser:radiobrowser /var/log/radiobrowser/ && \
21 | chmod go+r /etc/radiobrowser/config.toml
22 | ENV STATIC_FILES_DIR=/usr/lib/radiobrowser/static/
23 | USER radiobrowser:radiobrowser
24 | CMD [ "radiobrowser-api-rust", "-f", "/etc/radiobrowser/config.toml", "-vvv"]
25 |
--------------------------------------------------------------------------------
/src/check/favicon.rs:
--------------------------------------------------------------------------------
1 | use website_icon_extract::ImageLink;
2 |
3 | fn proximity(optimal: i32, link: &ImageLink) -> i32
4 | {
5 | let width: i32 = link.width as i32;
6 | let height: i32 = link.height as i32;
7 | (optimal - (width + height) / 2).abs()
8 | }
9 |
10 | pub fn get_best_icon(
11 | mut list: Vec,
12 | optimal: usize,
13 | minsize: usize,
14 | maxsize: usize,
15 | ) -> Option {
16 | if list.len() > 0 {
17 | let mut new_list: Vec = list
18 | .drain(..)
19 | .filter(|image| {
20 | image.width >= minsize
21 | && image.width <= maxsize
22 | && image.height >= minsize
23 | && image.height <= maxsize
24 | })
25 | .collect();
26 | new_list.sort_unstable_by(|a, b| {
27 | proximity(optimal as i32, b).cmp(&proximity(optimal as i32, a))
28 | });
29 | new_list.pop()
30 | } else {
31 | None
32 | }
33 | }
34 |
--------------------------------------------------------------------------------
/docker-compose.yml:
--------------------------------------------------------------------------------
1 | version: "3.0"
2 | services:
3 | api:
4 | build: ./
5 | image: segleralex/radiobrowser-api-rust:0.7.24
6 | deploy:
7 | replicas: 1
8 | networks:
9 | - mynet
10 | ports:
11 | - 8080:8080
12 | environment:
13 | - DATABASE_URL=mysql://radiouser:password@dbserver/radio
14 | - CACHETYPE=redis
15 | - CACHETTL=60sec
16 | - CACHEURL=redis://cacheserver:6379
17 | - HOST=0.0.0.0
18 | cacheserver:
19 | image: redis
20 | networks:
21 | - mynet
22 | dbserver:
23 | image: mariadb:10.4
24 | deploy:
25 | replicas: 1
26 | environment:
27 | - MYSQL_ROOT_PASSWORD=12345678
28 | - MYSQL_USER=radiouser
29 | - MYSQL_PASSWORD=password
30 | - MYSQL_DATABASE=radio
31 | networks:
32 | - mynet
33 | volumes:
34 | - ./dbdata:/var/lib/mysql
35 | command: ["mysqld","--character-set-server=utf8mb4","--collation-server=utf8mb4_unicode_ci"]
36 | networks:
37 | mynet:
38 | driver: "overlay"
39 | driver_opts:
40 | encrypted: ""
41 |
--------------------------------------------------------------------------------
/src/db/models/mod.rs:
--------------------------------------------------------------------------------
1 | mod extra_info;
2 | mod station_check_item;
3 | mod station_check_item_new;
4 | mod station_item;
5 | mod state;
6 | mod station_change_item_new;
7 | mod station_history_item;
8 | mod station_click_item;
9 | mod station_click_item_new;
10 | mod station_check_step_item;
11 | mod station_check_step_item_new;
12 | mod streaming_server;
13 | mod streaming_server_new;
14 | mod db_country;
15 |
16 | pub use db_country::DBCountry;
17 | pub use station_click_item::StationClickItem;
18 | pub use station_click_item_new::StationClickItemNew;
19 | pub use station_history_item::StationHistoryItem;
20 | pub use station_change_item_new::StationChangeItemNew;
21 | pub use station_check_item::StationCheckItem;
22 | pub use station_check_item_new::StationCheckItemNew;
23 | pub use station_item::DbStationItem;
24 | pub use extra_info::ExtraInfo;
25 | pub use state::State;
26 | pub use station_check_step_item::StationCheckStepItem;
27 | pub use station_check_step_item_new::StationCheckStepItemNew;
28 | pub use streaming_server::DbStreamingServer;
29 | pub use streaming_server_new::DbStreamingServerNew;
--------------------------------------------------------------------------------
/src/api/data/mod.rs:
--------------------------------------------------------------------------------
1 | mod api_config;
2 | mod api_language;
3 | mod api_streaming_server;
4 | mod api_country;
5 | mod result_message;
6 | mod station_add_result;
7 | mod station_check_step;
8 | mod station_check;
9 | mod station_click;
10 | mod station_history;
11 | mod station;
12 | mod status;
13 |
14 | pub use self::api_config::ApiConfig as ApiConfig;
15 | pub use self::api_language::ApiLanguage as ApiLanguage;
16 | pub use self::api_streaming_server::ApiStreamingServer as ApiStreamingServer;
17 | pub use self::api_country::ApiCountry as ApiCountry;
18 | pub use self::result_message::ResultMessage;
19 | pub use self::station_add_result::StationAddResult;
20 | pub use self::station_check_step::StationCheckStep;
21 | pub use self::station_check::StationCheck;
22 | pub use self::station_check::StationCheckV0;
23 | pub use self::station_click::StationClick;
24 | pub use self::station_click::StationClickV0;
25 | pub use self::station_history::StationHistoryCurrent;
26 | pub use self::station_history::StationHistoryV0;
27 | pub use self::station::Station;
28 | pub use self::station::StationCachedInfo;
29 | pub use self::station::StationV0;
30 | pub use self::status::Status;
--------------------------------------------------------------------------------
/src/db/models/station_check_item.rs:
--------------------------------------------------------------------------------
1 | use chrono::DateTime;
2 | use chrono::Utc;
3 |
4 | #[derive(Clone, Debug)]
5 | pub struct StationCheckItem {
6 | pub check_id: i32,
7 | pub check_time_iso8601: Option>,
8 | pub check_time: String,
9 | pub check_uuid: String,
10 |
11 | pub station_uuid: String,
12 | pub source: String,
13 | pub codec: String,
14 | pub bitrate: u32,
15 | pub hls: bool,
16 | pub check_ok: bool,
17 | pub url: String,
18 |
19 | pub metainfo_overrides_database: bool,
20 | pub public: Option,
21 | pub name: Option,
22 | pub description: Option,
23 | pub tags: Option,
24 | pub countrycode: Option,
25 | pub homepage: Option,
26 | pub favicon: Option,
27 | pub loadbalancer: Option,
28 | pub do_not_index: Option,
29 |
30 | pub countrysubdivisioncode: Option,
31 | pub server_software: Option,
32 | pub sampling: Option,
33 | pub timing_ms: u128,
34 | pub languagecodes: Option,
35 | pub ssl_error: bool,
36 | pub geo_lat: Option,
37 | pub geo_long: Option,
38 | }
39 |
--------------------------------------------------------------------------------
/src/db/models/extra_info.rs:
--------------------------------------------------------------------------------
1 | use serde::{Serialize,Deserialize};
2 | use std::error::Error;
3 |
4 | #[derive(PartialEq, Eq, Serialize, Deserialize)]
5 | pub struct ExtraInfo {
6 | pub name: String,
7 | pub stationcount: u32,
8 | }
9 |
10 | impl ExtraInfo {
11 | pub fn new(name: String, stationcount:u32) -> Self {
12 | return ExtraInfo{
13 | name,
14 | stationcount,
15 | };
16 | }
17 |
18 | pub fn serialize_extra_list_csv(entries: Vec) -> Result> {
19 | let mut wtr = csv::Writer::from_writer(Vec::new());
20 |
21 | for entry in entries {
22 | wtr.serialize(entry)?;
23 | }
24 |
25 | wtr.flush()?;
26 | let x: Vec = wtr.into_inner()?;
27 | Ok(String::from_utf8(x).unwrap_or("encoding error".to_string()))
28 | }
29 |
30 | pub fn serialize_extra_list(entries: Vec, tag_name: &str) -> std::io::Result {
31 | let mut xml = xml_writer::XmlWriter::new(Vec::new());
32 | xml.begin_elem("result")?;
33 | for entry in entries {
34 | xml.begin_elem(tag_name)?;
35 | xml.attr_esc("name", &entry.name)?;
36 | xml.attr_esc("stationcount", &entry.stationcount.to_string())?;
37 | xml.end_elem()?;
38 | }
39 | xml.end_elem()?;
40 | xml.close()?;
41 | xml.flush()?;
42 | Ok(String::from_utf8(xml.into_inner()).unwrap_or("encoding error".to_string()))
43 | }
44 | }
--------------------------------------------------------------------------------
/src/api/cache/memcached.rs:
--------------------------------------------------------------------------------
1 | //use super::generic_cache::GenericCache;
2 | use memcache;
3 | use std::error::Error;
4 |
5 | #[derive(Debug, Clone)]
6 | pub struct MemcachedCache {
7 | ttl: u16,
8 | cache_url: String,
9 | }
10 |
11 | impl MemcachedCache {
12 | pub fn new(cache_url: String, ttl: u16) -> Self {
13 | MemcachedCache { cache_url, ttl }
14 | }
15 | fn get_internal(&self, key: &str) -> Result