A modern Music Player Daemon based on Rockbox open source high quality audio player
libadwaita
audio
rust
zig
deno
mpris
rockbox
mpd
1use anyhow::Error;
2use clap::Command;
3use owo_colors::OwoColorize;
4use rockbox_library::audio_scan::scan_audio_files;
5use rockbox_library::{create_connection_pool, repo};
6use rockbox_search::album::Album;
7use rockbox_search::artist::Artist;
8use rockbox_search::track::Track;
9use rockbox_search::{create_indexes, delete_all_documents, index_entity};
10use std::thread::sleep;
11use std::time::Duration;
12use std::{env, ffi::CStr};
13use std::{fs, thread};
14
15#[no_mangle]
16pub extern "C" fn parse_args(argc: usize, argv: *const *const u8) -> i32 {
17 let string_array = unsafe { std::slice::from_raw_parts(argv, argc) };
18 let args: Vec<&str> = string_array
19 .iter()
20 .map(|&ptr| {
21 let c_str = unsafe { CStr::from_ptr(ptr as *const std::ffi::c_char) };
22 c_str
23 .to_str()
24 .unwrap_or("[Invalid UTF-8 or Non Null-Terminated String]")
25 })
26 .collect();
27
28 const VERSION: &str = match option_env!("TAG") {
29 Some(tag) => tag,
30 None => env!("CARGO_PKG_VERSION"),
31 };
32
33 let banner = format!(
34 "{}\nA fork of the original Rockbox project, with a focus on modernization and more features.",
35 r#"
36 __________ __ ___.
37 Open \______ \ ____ ____ | | _\_ |__ _______ ___
38 Source | _// _ \_/ ___\| |/ /| __ \ / _ \ \/ /
39 Jukebox | | ( <_> ) \___| < | \_\ ( <_> > < <
40 Firmware |____|_ /\____/ \___ >__|_ \|___ /\____/__/\_ \
41 \/ \/ \/ \/ \/
42 "#
43 .yellow()
44 );
45 let cli = Command::new("rockboxd").version(VERSION).about(&banner);
46
47 cli.get_matches_from(args);
48
49 thread::spawn(move || {
50 let home = env::var("HOME").unwrap();
51
52 match fs::create_dir_all(format!("{}/Music", home)) {
53 Ok(_) => {}
54 Err(e) => {
55 eprintln!("Failed to create Music directory: {}", e);
56 }
57 }
58
59 let update_library = match env::var("ROCKBOX_UPDATE_LIBRARY")
60 .as_ref()
61 .map(|s| s.as_str())
62 {
63 Ok("1") => true,
64 Ok("true") => true,
65 Ok(_) => false,
66 Err(_) => false,
67 };
68 let path = rockbox_settings::get_music_dir().unwrap_or(format!("{}/Music", home));
69 let rt = tokio::runtime::Runtime::new().unwrap();
70 rt.block_on(async {
71 let pool = create_connection_pool().await?;
72 let tracks = repo::track::all(pool.clone()).await?;
73 if tracks.is_empty() || update_library {
74 scan_audio_files(pool.clone(), path.into()).await?;
75 let tracks = repo::track::all(pool.clone()).await?;
76 let albums = repo::album::all(pool.clone()).await?;
77 let artists = repo::artist::all(pool.clone()).await?;
78 let indexes = create_indexes()?;
79 let tracks_index = indexes.tracks.clone();
80 let albums_index = indexes.albums.clone();
81 let artists_index = indexes.artists.clone();
82
83 thread::spawn(move || {
84 match delete_all_documents(&tracks_index) {
85 Ok(_) => {}
86 Err(e) => eprintln!("Error deleting all documents: {:?}", e),
87 }
88 for track in tracks {
89 index_entity::<Track>(&tracks_index, &track.into()).unwrap();
90 }
91 });
92
93 thread::spawn(move || {
94 match delete_all_documents(&albums_index) {
95 Ok(_) => {}
96 Err(e) => eprintln!("Error deleting all documents: {:?}", e),
97 }
98 for album in albums {
99 index_entity::<Album>(&albums_index, &album.into()).unwrap();
100 }
101 });
102
103 thread::spawn(move || {
104 match delete_all_documents(&artists_index) {
105 Ok(_) => {}
106 Err(e) => eprintln!("Error deleting all documents: {:?}", e),
107 }
108 for artist in artists {
109 index_entity::<Artist>(&artists_index, &artist.into()).unwrap();
110 }
111 });
112 }
113 Ok::<(), Error>(())
114 })
115 .unwrap();
116
117 thread::spawn(move || {
118 sleep(Duration::from_secs(5));
119 match rockbox_rocksky::register_rockbox() {
120 Ok(_) => println!("Successfully registered Rockbox with Rocksky server"),
121 Err(e) => eprintln!("Failed to register Rockbox with Rocksky server: {}", e),
122 };
123 });
124
125 const BANNER: &str = r#"
126 __________ __ ___.
127Open \______ \ ____ ____ | | _\_ |__ _______ ___
128Source | _// _ \_/ ___\| |/ /| __ \ / _ \ \/ /
129Jukebox | | ( <_> ) \___| < | \_\ ( <_> > < <
130Firmware |____|_ /\____/ \___ >__|_ \|___ /\____/__/\_ \
131 \/ \/ \/ \/ \/
132"#;
133
134 println!("{}", BANNER.yellow());
135
136 let port = std::env::var("ROCKBOX_TCP_PORT").unwrap_or_else(|_| "6063".to_string());
137 let addr = format!("0.0.0.0:{}", port);
138
139 println!(
140 "{} server is running on {}",
141 "Rockbox TCP".bright_purple(),
142 addr.bright_green()
143 );
144
145 let graphql_port = env::var("ROCKBOX_GRAPHQL_PORT").unwrap_or("6062".to_string());
146 let addr = format!("{}:{}", "0.0.0.0", graphql_port);
147
148 println!(
149 "{} server is running on {}",
150 "Rockbox GraphQL".bright_purple(),
151 addr.bright_green()
152 );
153
154 let rockbox_port: u16 = std::env::var("ROCKBOX_PORT")
155 .unwrap_or_else(|_| "6061".to_string())
156 .parse()
157 .expect("ROCKBOX_PORT must be a number");
158
159 let host_and_port = format!("0.0.0.0:{}", rockbox_port);
160
161 println!(
162 "{} server is running on {}",
163 "Rockbox gRPC".bright_purple(),
164 host_and_port.bright_green()
165 );
166
167 println!(
168 "Rockbox Web UI is running on {} ⚡",
169 "http://localhost:6062".bright_green()
170 );
171 });
172
173 return 0;
174}