⬆️ version bump to v0.3.40

- Update crate and project versions from `v0.3.39` to "\""
- Reorder imports in media_server.rs for consistency
  – Move `error_codes` import before other soap modules in pmoupnp
  – Reorder crate imports to group external and local deps logically (soap_client, then core types)
- Improve Browse response formatting: wrap long expressions for readability
  – Add deterministic sort by `+dc:title` in Browse requests (alphabetical, ascending)
  – Preserve track ordering by NOT sorting items (track number preserved), only containers
- Add explicit sort of container list in content_handler.rs for deterministic output
This commit is contained in:
2026-04-06 18:16:39 +02:00
parent abe8c1a4b2
commit 77cfb7b3e3
4 changed files with 27 additions and 10 deletions

View File

@@ -2,8 +2,8 @@ use std::sync::{Arc, Mutex};
use std::time::SystemTime;
use pmodidl::DIDLLite;
use pmoupnp::soap::SoapEnvelope;
use pmoupnp::soap::error_codes;
use pmoupnp::soap::SoapEnvelope;
use tracing::{debug, warn};
use xmltree::{Element, XMLNode};
@@ -11,8 +11,8 @@ use crate::errors::ControlPointError;
use crate::model::TrackMetadata;
use crate::online::{DeviceConnectionState, DeviceOnline};
use crate::queue::PlaybackItem;
use crate::soap_client::{SoapCallResult, invoke_upnp_action_with_timeout};
use crate::{DEFAULT_HTTP_TIMEOUT, DeviceId, DeviceIdentity};
use crate::soap_client::{invoke_upnp_action_with_timeout, SoapCallResult};
use crate::{DeviceId, DeviceIdentity, DEFAULT_HTTP_TIMEOUT};
/// Snapshot of a media server discovered through UPnP SSDP.
#[derive(Clone, Debug)]
@@ -91,7 +91,9 @@ impl UpnpMediaServer {
start: u32,
count: u32,
) -> Result<Vec<MediaEntry>, ControlPointError> {
Ok(self.browse_with_flag_paged(object_id, browse_flag, start, count)?.entries)
Ok(self
.browse_with_flag_paged(object_id, browse_flag, start, count)?
.entries)
}
fn browse_with_flag_paged(
@@ -109,17 +111,23 @@ impl UpnpMediaServer {
("Filter", "*".to_string()),
("StartingIndex", start_str),
("RequestedCount", count_str),
("SortCriteria", String::new()),
// Tri par titre (alphabétique croissant) pour avoir un ordre déterministe
("SortCriteria", "+dc:title".to_string()),
];
let response = self.invoke_content_directory("Browse", None, args)?;
let envelope = response.envelope.ok_or_else(|| {
ControlPointError::MediaServerError("Missing SOAP envelope in Browse response".to_string())
ControlPointError::MediaServerError(
"Missing SOAP envelope in Browse response".to_string(),
)
})?;
let total_count = extract_total_matches(&envelope, "BrowseResponse");
let didl_xml = extract_result_payload(&envelope, "BrowseResponse")?;
let entries = map_didl_entries(&didl_xml)?;
Ok(BrowsePage { entries, total_count })
Ok(BrowsePage {
entries,
total_count,
})
}
fn has_content_directory(&self) -> bool {
@@ -398,7 +406,10 @@ pub trait MediaBrowser {
) -> Result<BrowsePage, ControlPointError> {
let entries = self.browse_children(object_id, start, count)?;
let total_count = entries.len() as u32 + start;
Ok(BrowsePage { entries, total_count })
Ok(BrowsePage {
entries,
total_count,
})
}
fn browse_object(&self, object_id: &str) -> Result<MediaEntry, ControlPointError>;
fn search(