Send the position of each channel, they are now sorted when rendered in HTML.
authorGreg Burri <greg.burri@gmail.com>
Thu, 20 Jun 2024 10:37:26 +0000 (12:37 +0200)
committerGreg Burri <greg.burri@gmail.com>
Thu, 20 Jun 2024 10:37:26 +0000 (12:37 +0200)
Cargo.toml
ice/ice.py
src/ice.rs
src/main.rs
templates/main.html

index bd438a9..6616b52 100644 (file)
@@ -30,7 +30,7 @@ askama_axum = "0.4"
 shared_child = "1"
 rsevents = "0.3"
 
-
 [profile.release]
 codegen-units = 1
 lto = true
+strip = true
index 34cba11..faa309c 100644 (file)
@@ -4,30 +4,36 @@ initdata = Ice.InitializationData()
 initdata.properties = Ice.createProperties(None, initdata.properties)
 initdata.properties.setProperty('Ice.ImplicitContext', 'Shared')
 
+# Return a tuple (path: string, positions: int list)
+# where 'positions' is the position of each path level.
 def getChannelPath(channels, channelId, first=True):
     if channelId == 0:
-        return "/"
+        return ("/", [0])
     else:
         channel = channels[channelId]
-        return getChannelPath(channels, channel.parent, False) + channel.name + \
-            ("" if first  else "/")
+        (path, positions) = getChannelPath(channels, channel.parent, False)
+        return (path + channel.name + ("" if first  else "/"), positions + [channel.position])
 
 def getUsers(server):
     return map(lambda u: (u.name, u.channel, u.selfMute, u.selfDeaf), server.getUsers().values())
 
+def userToJson(channels, user):
+    (username, channelId, selfMute, selfDeaf) = user
+    (channelPath, positions) = getChannelPath(channels, channelId)
+    return {
+        'username': username,
+        'channelPath': channelPath,
+        'channelPathPositions': positions,
+        'selfMute': selfMute,
+        'selfDeaf': selfDeaf
+    }
+
 def getUsersAsJson(server):
     channels = server.getChannels()
     users = getUsers(server)
+
     return json.dumps(
-        [
-            {
-                'username': username,
-                'channel': getChannelPath(channels, channel),
-                'selfMute': selfMute,
-                'selfDeaf': selfDeaf
-            }
-            for (username, channel, selfMute, selfDeaf) in users
-        ]
+        list(map(lambda user: userToJson(channels, user), users))
     )
 
 with Ice.initialize(sys.argv, initdata) as commmunicator:
@@ -36,7 +42,10 @@ with Ice.initialize(sys.argv, initdata) as commmunicator:
     meta = MumbleServer.MetaPrx.uncheckedCast(proxy)
     server = meta.getServer(1)
 
+    # print(getUsersAsJson(server))
+
     with socket.socket(socket.AF_INET, socket.SOCK_STREAM) as s:
+        s.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
         s.bind(("127.0.0.1", 4978))
         s.listen()
         while True:
index c772ddc..2a791d2 100644 (file)
@@ -11,7 +11,8 @@ use std::{
     time::{self, Duration},
 };
 
-use json;
+use itertools::Itertools;
+use json::{self, JsonValue};
 use rsevents::{AutoResetEvent, Awaitable, EventState};
 use shared_child::SharedChild;
 
@@ -21,9 +22,10 @@ const CACHED_VALUES_DURATION: Duration = Duration::from_secs(5);
 #[derive(Debug, Clone)]
 pub struct User {
     pub name: String,
-    pub channel: String,
+    pub channel_path: String,
+    pub channel_path_positions: Vec<i32>,
     pub self_mute: bool,
-    pub self_deaf: bool, // ðŸ”‡
+    pub self_deaf: bool,
 }
 
 pub struct Ice {
@@ -63,30 +65,36 @@ impl Ice {
             let mut command = Command::new(PYTHON_PATH);
             command.arg("./ice/ice.py");
 
-            let child = SharedChild::spawn(&mut command).unwrap();
-            let child_arc = Arc::new(child);
-            let child_arc_cloned = child_arc.clone();
-
-            thread::spawn(move || {
-                kill_event_arc_clone1.wait();
-                child_arc_cloned.kill()
-            });
-
-            println!("ice.py launched!");
-
-            child_arc.wait().unwrap();
-
-            // if kill_event is set then quit loop.
-            if dropped_arc_clone.load(Ordering::Relaxed) {
-                println!("Stopping main python loop...");
-                kill_event_arc_clone2.set();
-                return;
-            } else {
-                kill_event_arc_clone2.set();
+            match SharedChild::spawn(&mut command) {
+                Ok(child) => {
+                    let child_arc = Arc::new(child);
+                    let child_arc_cloned = child_arc.clone();
+
+                    thread::spawn(move || {
+                        kill_event_arc_clone1.wait();
+                        child_arc_cloned.kill()
+                    });
+
+                    println!("ice.py launched!");
+
+                    if let Err(err) = child_arc.wait() {
+                        println!("Error when waiting python process: {}", err);
+                    }
+
+                    // if kill_event is set then quit loop.
+                    if dropped_arc_clone.load(Ordering::Relaxed) {
+                        println!("Stopping main python loop...");
+                        kill_event_arc_clone2.set();
+                        return;
+                    } else {
+                        kill_event_arc_clone2.set();
+                    }
+
+                    print!("Python stopped!");
+                }
+                Err(err) => print!("Can't lauch python: {}, retrying in a while...", err),
             }
 
-            print!("Python stopped!");
-
             let elapsed = time::Instant::now() - time_beginning_loop;
             if elapsed < RELAUNCH_PERIOD {
                 let to_wait = RELAUNCH_PERIOD - elapsed;
@@ -136,13 +144,24 @@ impl Ice {
             let mut users: Vec<User> = vec![];
             for i in 0..json.len() {
                 let user_json = &json[i];
+
                 users.push(User {
                     name: user_json["username"].as_str().unwrap().to_string(),
-                    channel: user_json["channel"].as_str().unwrap().to_string(),
+                    channel_path: user_json["channelPath"].as_str().unwrap().to_string(),
+                    // TODO: a bit ugly.
+                    channel_path_positions: match &user_json["channelPathPositions"] {
+                        JsonValue::Array(array) => {
+                            array.iter().map(|v| v.as_i32().unwrap()).collect_vec()
+                        }
+                        _ => vec![],
+                    },
                     self_mute: user_json["selfMute"].as_bool().unwrap(),
                     self_deaf: user_json["selfDeaf"].as_bool().unwrap(),
                 });
             }
+
+            // dbg!(&users);
+
             self.cached_users = users.clone();
             self.cached_users_timestamp = time::Instant::now();
             users
index 2055ed5..075f57f 100644 (file)
@@ -1,5 +1,5 @@
 use std::{
-    cmp::Ordering,
+    cmp::{min, Ordering},
     net::SocketAddr,
     sync::{Arc, Mutex},
 };
@@ -7,12 +7,13 @@ use std::{
 use askama::Template;
 use askama_axum::IntoResponse;
 use axum::{extract::State, routing::get, Router};
+use itertools::Itertools;
 use tokio::signal;
 use tower_http::services::ServeDir;
 
 mod ice;
 
-#[derive(Eq)]
+#[derive(Eq, Debug)]
 struct User {
     name: String,
     self_mute: bool,
@@ -47,11 +48,49 @@ impl Ord for User {
     }
 }
 
+#[derive(Eq, Debug)]
 struct Channel {
-    name: String,
+    path: String,
+    positions: Vec<i32>,
     users: Vec<User>,
 }
 
+impl PartialEq for Channel {
+    fn eq(&self, other: &Self) -> bool {
+        self.path.eq(&other.path)
+    }
+}
+
+impl PartialOrd for Channel {
+    fn partial_cmp(&self, other: &Self) -> Option<Ordering> {
+        Some(self.cmp(&other))
+    }
+}
+
+impl Ord for Channel {
+    fn cmp(&self, other: &Self) -> Ordering {
+        let self_path = self.path.split('/').collect_vec();
+        let other_path = other.path.split('/').collect_vec();
+        let l = min(self.positions.len(), other.positions.len());
+        for i in 0..l {
+            if let order @ (Ordering::Greater | Ordering::Less) =
+                self.positions[i].cmp(&other.positions[i])
+            {
+                return order;
+            }
+
+            if let order @ (Ordering::Greater | Ordering::Less) = self_path[i].cmp(other_path[i]) {
+                return order;
+            }
+
+            if i == l - 1 {
+                return self.positions.len().cmp(&other.positions.len());
+            }
+        }
+        Ordering::Equal
+    }
+}
+
 #[derive(Template)]
 #[template(path = "main.html")]
 struct MainTemplate {
@@ -86,13 +125,14 @@ async fn root(State(ice): State<Arc<Mutex<ice::Ice>>>) -> impl IntoResponse {
     let mut channels = Vec::<Channel>::new();
     'next_user: for u in &users {
         for c in &mut channels {
-            if c.name == u.channel {
+            if c.path == u.channel_path {
                 c.users.push(User::from_ice_user(u));
                 continue 'next_user;
             }
         }
         let channel = Channel {
-            name: u.channel.clone(),
+            path: u.channel_path.clone(),
+            positions: u.channel_path_positions.clone(),
             users: vec![User::from_ice_user(u)],
         };
         channels.push(channel);
@@ -102,6 +142,8 @@ async fn root(State(ice): State<Arc<Mutex<ice::Ice>>>) -> impl IntoResponse {
         c.users.sort();
     }
 
+    channels.sort();
+
     MainTemplate { channels }
 }
 
index 5bc6fba..16a3c99 100644 (file)
@@ -15,7 +15,7 @@
         <p>No user connected</p>
         {% else %}
         {% for channel in channels %}
-        <h2 class="channel">{{ channel.name }}</h2>
+        <h2 class="channel">{{ channel.path }}</h2>
         <ul>
             {% for user in channel.users %}
             <li>{{ user.name }}