Compare commits
3 Commits
a0c3f5b502
...
123a409e14
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
123a409e14 | ||
|
|
fcaf70b717 | ||
|
|
9bf9a2296e |
2
.gitignore
vendored
Normal file
2
.gitignore
vendored
Normal file
@@ -0,0 +1,2 @@
|
||||
/target
|
||||
.env
|
||||
1091
Cargo.lock
generated
Normal file
1091
Cargo.lock
generated
Normal file
File diff suppressed because it is too large
Load Diff
16
Cargo.toml
Normal file
16
Cargo.toml
Normal file
@@ -0,0 +1,16 @@
|
||||
[package]
|
||||
name = "soundsonic"
|
||||
version = "0.1.0"
|
||||
edition = "2024"
|
||||
|
||||
[dependencies]
|
||||
diesel = { version = "2.3.6", features = ["sqlite", "r2d2", "returning_clauses_for_sqlite_3_35"] }
|
||||
# build libsqlite3 as part of the build process
|
||||
# uncomment this line if you run into setup issues
|
||||
# libsqlite3-sys = { version = "0.36", features = ["bundled"] }
|
||||
dotenvy = "0.15.7"
|
||||
axum = "0.8.8"
|
||||
serde = { version = "1.0.228", features = ["derive"] }
|
||||
serde_json = "1.0.149"
|
||||
tokio = { version = "1.49.0", features = ["full"] }
|
||||
tracing-subscriber = "0.3.22"
|
||||
150
README.md
150
README.md
@@ -1,3 +1,149 @@
|
||||
# soundsonic
|
||||
# SoundSonic
|
||||
|
||||
open subsonic api implementation
|
||||
A Rust-based music streaming server implementing the OpenSubsonic API protocol.
|
||||
|
||||
## Overview
|
||||
|
||||
SoundSonic is a lightweight music streaming server built with Rust that implements the [OpenSubsonic](https://opensubsonic.netlify.app/) API specification. It allows you to stream your music collection to any OpenSubsonic-compatible client.
|
||||
|
||||
## Features
|
||||
|
||||
- **OpenSubsonic API Support**: Compatible with any client supporting the OpenSubsonic protocol
|
||||
- **User Management**: Role-based access control with customizable user permissions
|
||||
- **SQLite Database**: Lightweight local storage with Diesel ORM
|
||||
- **RESTful API**: Clean, well-structured API endpoints
|
||||
- **Async Runtime**: Built on Tokio for high performance
|
||||
|
||||
## Technologies
|
||||
|
||||
- **Rust** (Edition 2024)
|
||||
- **Axum** - Web framework
|
||||
- **Tokio** - Async runtime
|
||||
- **Diesel** - ORM with SQLite
|
||||
- **Serde** - Serialization/deserialization
|
||||
|
||||
## Prerequisites
|
||||
|
||||
- Rust (latest stable version)
|
||||
- SQLite
|
||||
- Diesel CLI (for database migrations)
|
||||
|
||||
## Installation
|
||||
|
||||
1. Clone the repository:
|
||||
```bash
|
||||
git clone <repository-url>
|
||||
cd soundsonic
|
||||
```
|
||||
|
||||
2. Set up the database URL:
|
||||
```bash
|
||||
export DATABASE_URL=database.db
|
||||
```
|
||||
|
||||
Or create a `.env` file:
|
||||
```
|
||||
DATABASE_URL=database.db
|
||||
```
|
||||
|
||||
3. Run database migrations:
|
||||
```bash
|
||||
diesel migration run
|
||||
```
|
||||
|
||||
4. Build and run the server:
|
||||
```bash
|
||||
cargo run
|
||||
```
|
||||
|
||||
The server will start on `http://0.0.0.0:3311`
|
||||
|
||||
## API Endpoints
|
||||
|
||||
### System
|
||||
|
||||
- `GET/POST /rest/getOpenSubsonicExtensions` - Get supported OpenSubsonic extensions
|
||||
|
||||
### User Management
|
||||
|
||||
- `GET/POST /rest/createUser.view` - Create a new user with specified permissions
|
||||
|
||||
### Request/Response Format
|
||||
|
||||
All API responses follow the OpenSubsonic response format:
|
||||
|
||||
```json
|
||||
{
|
||||
"subsonic-response": {
|
||||
"status": "ok",
|
||||
"version": "1.16.1",
|
||||
"type": "SoundSonic",
|
||||
"serverVersion": "1.16.1",
|
||||
"openSubsonic": true
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
## User Roles
|
||||
|
||||
Users can have the following permissions:
|
||||
|
||||
- `adminRole` - Administrative privileges
|
||||
- `streamRole` - Stream music
|
||||
- `downloadRole` - Download music
|
||||
- `uploadRole` - Upload music
|
||||
- `playlistRole` - Manage playlists
|
||||
- `coverArtRole` - Manage cover art
|
||||
- `commentRole` - Add comments
|
||||
- `podcastRole` - Access podcasts
|
||||
- `shareRole` - Share content
|
||||
- `jukeboxRole` - Jukebox control
|
||||
- `videoConversionRole` - Video conversion
|
||||
- `settingsRole` - Change settings
|
||||
- `ldapAuthenticated` - LDAP authentication
|
||||
|
||||
## Configuration
|
||||
|
||||
The server can be configured via environment variables:
|
||||
|
||||
- `DATABASE_URL` - Path to the SQLite database file (required)
|
||||
|
||||
## Development
|
||||
|
||||
### Running in development mode:
|
||||
```bash
|
||||
cargo run
|
||||
```
|
||||
|
||||
### Building for production:
|
||||
```bash
|
||||
cargo build --release
|
||||
```
|
||||
|
||||
### Database schema changes:
|
||||
```bash
|
||||
diesel migration generate <migration_name>
|
||||
diesel migration run
|
||||
```
|
||||
|
||||
## Supported OpenSubsonic Extensions
|
||||
|
||||
- `apiKeyAuthentication` - API key-based authentication
|
||||
- `formPost` - Form-based POST requests
|
||||
- `indexBasedQueue` - Index-based queue management
|
||||
- `songLyrics` - Song lyrics support
|
||||
- `transcodeOffset` - Transcoding offset support
|
||||
- `transcoding` - Audio transcoding capabilities
|
||||
|
||||
## License
|
||||
|
||||
[Your License Here]
|
||||
|
||||
## Contributing
|
||||
|
||||
Contributions are welcome! Please feel free to submit a Pull Request.
|
||||
|
||||
## Acknowledgments
|
||||
|
||||
- [OpenSubsonic](https://opensubsonic.netlify.app/) - The API specification this server implements
|
||||
- [Subsonic](http://www.subsonic.org/) - The original music streaming server
|
||||
|
||||
9
diesel.toml
Normal file
9
diesel.toml
Normal file
@@ -0,0 +1,9 @@
|
||||
# For documentation on how to configure this file,
|
||||
# see https://diesel.rs/guides/configuring-diesel-cli
|
||||
|
||||
[print_schema]
|
||||
file = "src/schema.rs"
|
||||
custom_type_derives = ["diesel::query_builder::QueryId", "Clone"]
|
||||
|
||||
[migrations_directory]
|
||||
dir = "migrations"
|
||||
0
migrations/.diesel_lock
Normal file
0
migrations/.diesel_lock
Normal file
0
migrations/.keep
Normal file
0
migrations/.keep
Normal file
4
migrations/2026-02-09-205759-0000_init/down.sql
Normal file
4
migrations/2026-02-09-205759-0000_init/down.sql
Normal file
@@ -0,0 +1,4 @@
|
||||
-- This file should undo anything in `up.sql`
|
||||
-- down.sql (Diesel)
|
||||
DROP TABLE IF EXISTS user_music_folders;
|
||||
DROP TABLE IF EXISTS users;
|
||||
28
migrations/2026-02-09-205759-0000_init/up.sql
Normal file
28
migrations/2026-02-09-205759-0000_init/up.sql
Normal file
@@ -0,0 +1,28 @@
|
||||
-- Your SQL goes here
|
||||
CREATE TABLE users (
|
||||
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
||||
username TEXT NOT NULL UNIQUE,
|
||||
password TEXT NOT NULL,
|
||||
email TEXT NOT NULL UNIQUE,
|
||||
|
||||
ldapAuthenticated BOOLEAN NOT NULL DEFAULT 0 CHECK (ldapAuthenticated IN (0,1)),
|
||||
adminRole BOOLEAN NOT NULL DEFAULT 0 CHECK (adminRole IN (0,1)),
|
||||
settingsRole BOOLEAN NOT NULL DEFAULT 1 CHECK (settingsRole IN (0,1)),
|
||||
streamRole BOOLEAN NOT NULL DEFAULT 1 CHECK (streamRole IN (0,1)),
|
||||
jukeboxRole BOOLEAN NOT NULL DEFAULT 0 CHECK (jukeboxRole IN (0,1)),
|
||||
downloadRole BOOLEAN NOT NULL DEFAULT 0 CHECK (downloadRole IN (0,1)),
|
||||
uploadRole BOOLEAN NOT NULL DEFAULT 0 CHECK (uploadRole IN (0,1)),
|
||||
playlistRole BOOLEAN NOT NULL DEFAULT 0 CHECK (playlistRole IN (0,1)),
|
||||
coverArtRole BOOLEAN NOT NULL DEFAULT 0 CHECK (coverArtRole IN (0,1)),
|
||||
commentRole BOOLEAN NOT NULL DEFAULT 0 CHECK (commentRole IN (0,1)),
|
||||
podcastRole BOOLEAN NOT NULL DEFAULT 0 CHECK (podcastRole IN (0,1)),
|
||||
shareRole BOOLEAN NOT NULL DEFAULT 0 CHECK (shareRole IN (0,1)),
|
||||
videoConversionRole BOOLEAN NOT NULL DEFAULT 0 CHECK (videoConversionRole IN (0,1))
|
||||
);
|
||||
|
||||
CREATE TABLE user_music_folders (
|
||||
user_id INTEGER NOT NULL,
|
||||
music_folder_id INTEGER NOT NULL,
|
||||
PRIMARY KEY (user_id, music_folder_id),
|
||||
FOREIGN KEY (user_id) REFERENCES users(id) ON DELETE CASCADE
|
||||
);
|
||||
1
rustfmt.toml
Normal file
1
rustfmt.toml
Normal file
@@ -0,0 +1 @@
|
||||
edition = "2024"
|
||||
1
src/db/mod.rs
Normal file
1
src/db/mod.rs
Normal file
@@ -0,0 +1 @@
|
||||
pub mod models;
|
||||
59
src/db/models.rs
Normal file
59
src/db/models.rs
Normal file
@@ -0,0 +1,59 @@
|
||||
use crate::schema::{user_music_folders, users};
|
||||
use diesel::prelude::*;
|
||||
use serde::{Deserialize, Serialize};
|
||||
|
||||
#[derive(Debug, Clone, Queryable, Selectable, Identifiable)]
|
||||
#[diesel(table_name = users)]
|
||||
#[derive(Serialize, Deserialize)]
|
||||
pub struct User {
|
||||
pub id: i32,
|
||||
pub username: String,
|
||||
pub password: String,
|
||||
pub email: String,
|
||||
|
||||
pub ldapAuthenticated: bool,
|
||||
pub adminRole: bool,
|
||||
pub settingsRole: bool,
|
||||
pub streamRole: bool,
|
||||
pub jukeboxRole: bool,
|
||||
pub downloadRole: bool,
|
||||
pub uploadRole: bool,
|
||||
pub playlistRole: bool,
|
||||
pub coverArtRole: bool,
|
||||
pub commentRole: bool,
|
||||
pub podcastRole: bool,
|
||||
pub shareRole: bool,
|
||||
pub videoConversionRole: bool,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Insertable)]
|
||||
#[diesel(table_name = users)]
|
||||
#[diesel(treat_none_as_default_value = true)]
|
||||
#[derive(Serialize, Deserialize)]
|
||||
pub struct NewUser {
|
||||
pub username: String,
|
||||
pub password: String,
|
||||
pub email: String,
|
||||
|
||||
pub ldapAuthenticated: Option<bool>,
|
||||
pub adminRole: Option<bool>,
|
||||
pub settingsRole: Option<bool>,
|
||||
pub streamRole: Option<bool>,
|
||||
pub jukeboxRole: Option<bool>,
|
||||
pub downloadRole: Option<bool>,
|
||||
pub uploadRole: Option<bool>,
|
||||
pub playlistRole: Option<bool>,
|
||||
pub coverArtRole: Option<bool>,
|
||||
pub commentRole: Option<bool>,
|
||||
pub podcastRole: Option<bool>,
|
||||
pub shareRole: Option<bool>,
|
||||
pub videoConversionRole: Option<bool>,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Queryable, Selectable, Insertable)]
|
||||
#[diesel(table_name = user_music_folders)]
|
||||
#[derive(Serialize, Deserialize)]
|
||||
pub struct UserMusicFolder {
|
||||
pub user_id: i32,
|
||||
pub music_folder_id: i32,
|
||||
}
|
||||
28
src/main.rs
Normal file
28
src/main.rs
Normal file
@@ -0,0 +1,28 @@
|
||||
mod db;
|
||||
mod routes;
|
||||
mod schema;
|
||||
mod state;
|
||||
mod types;
|
||||
use std::env;
|
||||
|
||||
use axum::Router;
|
||||
use dotenvy::dotenv;
|
||||
|
||||
use crate::routes::system_router::system_routers;
|
||||
use crate::routes::user_management_router::user_management_routs;
|
||||
use crate::state::AppState;
|
||||
|
||||
#[tokio::main]
|
||||
async fn main() {
|
||||
dotenv().ok();
|
||||
let database_url = env::var("DATABASE_URL").expect("DATABASE_URL must be set");
|
||||
let state = AppState::new(&database_url)
|
||||
.unwrap_or_else(|_| panic!("Error creating pool for {}", database_url));
|
||||
tracing_subscriber::fmt::init();
|
||||
let app = Router::new()
|
||||
.nest("/rest", system_routers().merge(user_management_routs()))
|
||||
.with_state(state);
|
||||
|
||||
let listener = tokio::net::TcpListener::bind("0.0.0.0:3311").await.unwrap();
|
||||
let _ = axum::serve(listener, app).await;
|
||||
}
|
||||
2
src/routes/mod.rs
Normal file
2
src/routes/mod.rs
Normal file
@@ -0,0 +1,2 @@
|
||||
pub mod system_router;
|
||||
pub mod user_management_router;
|
||||
17
src/routes/system_router.rs
Normal file
17
src/routes/system_router.rs
Normal file
@@ -0,0 +1,17 @@
|
||||
use crate::state::AppState;
|
||||
use crate::types::types::OpenSubSonicResponses;
|
||||
use axum::{Json, Router, http::StatusCode, routing::get};
|
||||
|
||||
pub fn system_routers() -> Router<AppState> {
|
||||
Router::new().route(
|
||||
"/getOpenSubsonicExtensions",
|
||||
get(get_opensubsonic_extentions).post(get_opensubsonic_extentions),
|
||||
)
|
||||
}
|
||||
|
||||
async fn get_opensubsonic_extentions() -> (StatusCode, Json<OpenSubSonicResponses>) {
|
||||
(
|
||||
StatusCode::OK,
|
||||
Json(OpenSubSonicResponses::open_subsonic_extentions()),
|
||||
)
|
||||
}
|
||||
56
src/routes/user_management_router.rs
Normal file
56
src/routes/user_management_router.rs
Normal file
@@ -0,0 +1,56 @@
|
||||
use crate::{
|
||||
db::models::NewUser,
|
||||
schema::users,
|
||||
state::AppState,
|
||||
types::types::{OpenSubSonicResponses, OpenSubsonicErrorCode},
|
||||
};
|
||||
use axum::{
|
||||
Json, Router,
|
||||
extract::{Form, State, rejection::FormRejection},
|
||||
http::StatusCode,
|
||||
routing::get,
|
||||
};
|
||||
use diesel::{ExpressionMethods, QueryDsl, RunQueryDsl, insert_into};
|
||||
|
||||
pub fn user_management_routs() -> Router<AppState> {
|
||||
let userm_routs = Router::new().route("/createUser.view", get(create_user).post(create_user));
|
||||
return userm_routs;
|
||||
}
|
||||
|
||||
fn check_if_user_exist(user_name: &String, db_con: &AppState) -> bool {
|
||||
let conn = &mut db_con.pool.get().unwrap();
|
||||
let result = users::table
|
||||
.select(users::id)
|
||||
.filter(users::username.eq(user_name))
|
||||
.first::<Option<i32>>(conn);
|
||||
match result {
|
||||
Ok(_) => true,
|
||||
Err(_) => false,
|
||||
}
|
||||
}
|
||||
|
||||
async fn create_user(
|
||||
State(db_con): State<AppState>,
|
||||
form: Result<Form<NewUser>, FormRejection>,
|
||||
) -> (StatusCode, Json<OpenSubSonicResponses>) {
|
||||
let params = if let Ok(Form(parsed)) = form {
|
||||
parsed
|
||||
} else {
|
||||
return (
|
||||
StatusCode::BAD_REQUEST,
|
||||
Json(OpenSubSonicResponses::open_subsonic_response()),
|
||||
);
|
||||
};
|
||||
if check_if_user_exist(¶ms.username, &db_con) {
|
||||
let response = OpenSubSonicResponses::open_subsonic_response_error(
|
||||
OpenSubsonicErrorCode::WrongUsernameOrPassword,
|
||||
);
|
||||
return (StatusCode::CONFLICT, Json(response));
|
||||
};
|
||||
let conn = &mut db_con.pool.get().unwrap();
|
||||
let _ = insert_into(users::table).values(params).execute(conn);
|
||||
(
|
||||
StatusCode::OK,
|
||||
Json(OpenSubSonicResponses::open_subsonic_response()),
|
||||
)
|
||||
}
|
||||
34
src/schema.rs
Normal file
34
src/schema.rs
Normal file
@@ -0,0 +1,34 @@
|
||||
// @generated automatically by Diesel CLI.
|
||||
|
||||
diesel::table! {
|
||||
user_music_folders (user_id, music_folder_id) {
|
||||
user_id -> Integer,
|
||||
music_folder_id -> Integer,
|
||||
}
|
||||
}
|
||||
|
||||
diesel::table! {
|
||||
users (id) {
|
||||
id -> Nullable<Integer>,
|
||||
username -> Text,
|
||||
password -> Text,
|
||||
email -> Text,
|
||||
ldapAuthenticated -> Bool,
|
||||
adminRole -> Bool,
|
||||
settingsRole -> Bool,
|
||||
streamRole -> Bool,
|
||||
jukeboxRole -> Bool,
|
||||
downloadRole -> Bool,
|
||||
uploadRole -> Bool,
|
||||
playlistRole -> Bool,
|
||||
coverArtRole -> Bool,
|
||||
commentRole -> Bool,
|
||||
podcastRole -> Bool,
|
||||
shareRole -> Bool,
|
||||
videoConversionRole -> Bool,
|
||||
}
|
||||
}
|
||||
|
||||
diesel::joinable!(user_music_folders -> users (user_id));
|
||||
|
||||
diesel::allow_tables_to_appear_in_same_query!(user_music_folders, users,);
|
||||
17
src/state.rs
Normal file
17
src/state.rs
Normal file
@@ -0,0 +1,17 @@
|
||||
use diesel::{
|
||||
SqliteConnection,
|
||||
r2d2::{ConnectionManager, Pool, PoolError},
|
||||
};
|
||||
|
||||
#[derive(Clone)]
|
||||
pub struct AppState {
|
||||
pub pool: Pool<ConnectionManager<SqliteConnection>>,
|
||||
}
|
||||
|
||||
impl AppState {
|
||||
pub fn new(database_url: &str) -> Result<Self, PoolError> {
|
||||
let manager = ConnectionManager::<SqliteConnection>::new(database_url);
|
||||
let pool = Pool::builder().build(manager)?;
|
||||
Ok(Self { pool })
|
||||
}
|
||||
}
|
||||
2
src/types/mod.rs
Normal file
2
src/types/mod.rs
Normal file
@@ -0,0 +1,2 @@
|
||||
pub mod system;
|
||||
pub mod types;
|
||||
12
src/types/system.rs
Normal file
12
src/types/system.rs
Normal file
@@ -0,0 +1,12 @@
|
||||
use serde::{Deserialize, Serialize};
|
||||
|
||||
#[derive(Serialize, Deserialize)]
|
||||
pub struct OpenSubSonicExtension {
|
||||
pub name: String,
|
||||
pub versions: Vec<u8>,
|
||||
}
|
||||
|
||||
#[derive(Serialize, Deserialize)]
|
||||
pub struct OpenSubSonicExtensions {
|
||||
pub openSubsonicExtensions: Vec<OpenSubSonicExtension>,
|
||||
}
|
||||
155
src/types/types.rs
Normal file
155
src/types/types.rs
Normal file
@@ -0,0 +1,155 @@
|
||||
use serde::{Deserialize, Serialize};
|
||||
|
||||
use crate::types::system::{OpenSubSonicExtension, OpenSubSonicExtensions};
|
||||
|
||||
#[derive(Debug, Clone, Copy)]
|
||||
pub enum OpenSubsonicErrorCode {
|
||||
Generic = 0,
|
||||
MissingParameter = 10,
|
||||
ClientMustUpgrade = 20,
|
||||
ServerMustUpgrade = 30,
|
||||
WrongUsernameOrPassword = 40,
|
||||
TokenAuthNotSupportedForLdap = 41,
|
||||
AuthMechanismNotSupported = 42,
|
||||
ConflictingAuthMechanisms = 43,
|
||||
InvalidApiKey = 44,
|
||||
NotAuthorized = 50,
|
||||
TrialExpired = 60,
|
||||
NotFound = 70,
|
||||
}
|
||||
|
||||
impl OpenSubsonicErrorCode {
|
||||
pub const fn description(self) -> &'static str {
|
||||
match self {
|
||||
Self::Generic => "A generic error.",
|
||||
Self::MissingParameter => "Required parameter is missing.",
|
||||
Self::ClientMustUpgrade => {
|
||||
"Incompatible Subsonic REST protocol version. Client must upgrade."
|
||||
}
|
||||
Self::ServerMustUpgrade => {
|
||||
"Incompatible Subsonic REST protocol version. Server must upgrade."
|
||||
}
|
||||
Self::WrongUsernameOrPassword => "Wrong username or password.",
|
||||
Self::TokenAuthNotSupportedForLdap => {
|
||||
"Token authentication not supported for LDAP users."
|
||||
}
|
||||
Self::AuthMechanismNotSupported => "Provided authentication mechanism not supported.",
|
||||
Self::ConflictingAuthMechanisms => {
|
||||
"Multiple conflicting authentication mechanisms provided."
|
||||
}
|
||||
Self::InvalidApiKey => "Invalid API key.",
|
||||
Self::NotAuthorized => "User is not authorized for the given operation.",
|
||||
Self::TrialExpired => {
|
||||
"The trial period for the Subsonic server is over. Please upgrade to Subsonic Premium."
|
||||
}
|
||||
Self::NotFound => "The requested data was not found.",
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Serialize, Deserialize)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
pub struct BaseResponse<T> {
|
||||
status: String,
|
||||
version: String,
|
||||
#[serde(rename = "type")]
|
||||
type_name: String,
|
||||
server_version: String,
|
||||
open_subsonic: bool,
|
||||
#[serde(flatten)]
|
||||
data: Option<T>,
|
||||
}
|
||||
|
||||
#[derive(Serialize, Deserialize)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
pub struct ErrorResponse {
|
||||
code: u8,
|
||||
message: String,
|
||||
}
|
||||
|
||||
#[derive(Serialize, Deserialize)]
|
||||
#[serde(untagged)]
|
||||
pub enum OpenSubSonicResponses {
|
||||
OpenSubSonicResponseError {
|
||||
#[serde(rename = "subsonic-response")]
|
||||
subsonic_response: BaseResponse<ErrorResponse>,
|
||||
},
|
||||
OpenSubSonicResponse {
|
||||
#[serde(rename = "subsonic-response")]
|
||||
subsonic_response: BaseResponse<()>,
|
||||
},
|
||||
OpenSubSonicExtensions {
|
||||
#[serde(rename = "subsonic-response")]
|
||||
subsonic_response: BaseResponse<OpenSubSonicExtensions>,
|
||||
// openSubSonicExtensions: Vec<OpenSubSonicExtension>,
|
||||
},
|
||||
}
|
||||
|
||||
impl OpenSubSonicResponses {
|
||||
fn base_response<T>(inner_response: Option<T>) -> BaseResponse<T> {
|
||||
BaseResponse {
|
||||
status: "ok".to_string(),
|
||||
version: "1.16.1".to_string(),
|
||||
type_name: "SoundSonic".to_string(),
|
||||
server_version: "1.16.1".to_string(),
|
||||
open_subsonic: true,
|
||||
data: inner_response,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn open_subsonic_response_error(error_code: OpenSubsonicErrorCode) -> Self {
|
||||
Self::OpenSubSonicResponseError {
|
||||
subsonic_response: BaseResponse {
|
||||
status: "failed".to_string(),
|
||||
version: "1.16.1".to_string(),
|
||||
type_name: "SoundSonic".to_string(),
|
||||
server_version: "1.16.1".to_string(),
|
||||
open_subsonic: true,
|
||||
data: Some(ErrorResponse {
|
||||
code: error_code as u8,
|
||||
message: error_code.description().to_string(),
|
||||
}),
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
pub fn open_subsonic_response() -> Self {
|
||||
Self::OpenSubSonicResponse {
|
||||
subsonic_response: Self::base_response(None::<()>),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn open_subsonic_extentions() -> Self {
|
||||
let mut extension: Vec<OpenSubSonicExtension> = Vec::new();
|
||||
extension.push(OpenSubSonicExtension {
|
||||
name: "apiKeyAuthentication".to_string(),
|
||||
versions: vec![1],
|
||||
});
|
||||
extension.push(OpenSubSonicExtension {
|
||||
name: "formPost".to_string(),
|
||||
versions: vec![1],
|
||||
});
|
||||
extension.push(OpenSubSonicExtension {
|
||||
name: "indexBasedQueue".to_string(),
|
||||
versions: vec![1],
|
||||
});
|
||||
extension.push(OpenSubSonicExtension {
|
||||
name: "songLyrics".to_string(),
|
||||
versions: vec![1],
|
||||
});
|
||||
extension.push(OpenSubSonicExtension {
|
||||
name: "transcodeOffset".to_string(),
|
||||
versions: vec![1],
|
||||
});
|
||||
extension.push(OpenSubSonicExtension {
|
||||
name: "transcoding".to_string(),
|
||||
versions: vec![1],
|
||||
});
|
||||
|
||||
Self::OpenSubSonicExtensions {
|
||||
subsonic_response: Self::base_response(Some(OpenSubSonicExtensions {
|
||||
openSubsonicExtensions: extension,
|
||||
})),
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user