Finalize SourceForge provider install and update flows
This commit is contained in:
parent
eaa9a3b52d
commit
f260790d91
11 changed files with 718 additions and 65 deletions
|
|
@ -8,11 +8,23 @@ pub struct SourceForgeAdapter;
|
|||
|
||||
impl SourceForgeAdapter {
|
||||
pub fn artifact_url(source: &SourceRef) -> Option<String> {
|
||||
if is_resolved_download_locator(&source.locator) {
|
||||
Some(source.locator.clone())
|
||||
} else {
|
||||
None
|
||||
if let Some(asset_name) = source.requested_asset_name.as_deref()
|
||||
&& is_sourceforge_releases_root_locator(&source.locator)
|
||||
{
|
||||
return Some(format!("{}/{asset_name}/download", source.locator));
|
||||
}
|
||||
|
||||
if is_latest_download_locator(&source.locator)
|
||||
|| is_sourceforge_release_folder_download_locator(&source.locator)
|
||||
{
|
||||
return Some(source.locator.clone());
|
||||
}
|
||||
|
||||
if is_sourceforge_releases_root_locator(&source.locator) {
|
||||
return sourceforge_latest_download_url(&source.locator);
|
||||
}
|
||||
|
||||
None
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -85,7 +97,14 @@ impl SourceAdapter for SourceForgeAdapter {
|
|||
|
||||
fn resolved_source(source: &SourceRef) -> SourceRef {
|
||||
let mut resolved = source.clone();
|
||||
if is_sourceforge_stable_download_locator(&resolved.locator) {
|
||||
if is_sourceforge_file_like_release_download_locator(&resolved.locator) {
|
||||
resolved.locator = sourceforge_releases_root_url(&resolved.locator)
|
||||
.unwrap_or_else(|| resolved.locator.clone());
|
||||
resolved.normalized_kind = NormalizedSourceKind::SourceForge;
|
||||
resolved.tracks_latest = true;
|
||||
} else if is_sourceforge_release_folder_download_locator(&resolved.locator)
|
||||
|| is_sourceforge_releases_root_locator(&resolved.locator)
|
||||
{
|
||||
resolved.normalized_kind = NormalizedSourceKind::SourceForge;
|
||||
resolved.tracks_latest = true;
|
||||
}
|
||||
|
|
@ -94,7 +113,9 @@ fn resolved_source(source: &SourceRef) -> SourceRef {
|
|||
}
|
||||
|
||||
fn is_resolved_download_locator(locator: &str) -> bool {
|
||||
is_latest_download_locator(locator) || is_sourceforge_stable_download_locator(locator)
|
||||
is_latest_download_locator(locator)
|
||||
|| is_sourceforge_release_folder_download_locator(locator)
|
||||
|| is_sourceforge_releases_root_locator(locator)
|
||||
}
|
||||
|
||||
fn is_latest_download_locator(locator: &str) -> bool {
|
||||
|
|
@ -106,11 +127,140 @@ fn is_latest_download_locator(locator: &str) -> bool {
|
|||
trimmed.ends_with("/files/latest/download")
|
||||
}
|
||||
|
||||
fn is_sourceforge_stable_download_locator(locator: &str) -> bool {
|
||||
fn is_sourceforge_release_folder_download_locator(locator: &str) -> bool {
|
||||
let trimmed = locator
|
||||
.split(['?', '#'])
|
||||
.next()
|
||||
.unwrap_or(locator)
|
||||
.trim_end_matches('/');
|
||||
trimmed.ends_with("/files/releases/stable/download")
|
||||
|
||||
let parts = trimmed
|
||||
.trim_start_matches("https://sourceforge.net/projects/")
|
||||
.trim_start_matches("http://sourceforge.net/projects/")
|
||||
.split('/')
|
||||
.filter(|segment| !segment.is_empty())
|
||||
.collect::<Vec<_>>();
|
||||
|
||||
parts.len() == 5 && parts[1] == "files" && parts[2] == "releases" && parts[4] == "download"
|
||||
}
|
||||
|
||||
fn is_sourceforge_file_like_release_download_locator(locator: &str) -> bool {
|
||||
let trimmed = locator
|
||||
.split(['?', '#'])
|
||||
.next()
|
||||
.unwrap_or(locator)
|
||||
.trim_end_matches('/');
|
||||
|
||||
let parts = trimmed
|
||||
.trim_start_matches("https://sourceforge.net/projects/")
|
||||
.trim_start_matches("http://sourceforge.net/projects/")
|
||||
.split('/')
|
||||
.filter(|segment| !segment.is_empty())
|
||||
.collect::<Vec<_>>();
|
||||
|
||||
parts.len() == 5
|
||||
&& parts[1] == "files"
|
||||
&& parts[2] == "releases"
|
||||
&& is_sourceforge_artifact_name(parts[3])
|
||||
&& parts[4] == "download"
|
||||
}
|
||||
|
||||
fn is_sourceforge_artifact_name(segment: &str) -> bool {
|
||||
let lower = segment.to_ascii_lowercase();
|
||||
|
||||
[
|
||||
".appimage",
|
||||
".tar.gz",
|
||||
".tar.xz",
|
||||
".tar.bz2",
|
||||
".zip",
|
||||
".deb",
|
||||
".rpm",
|
||||
".exe",
|
||||
".msi",
|
||||
".dmg",
|
||||
".pkg",
|
||||
".apk",
|
||||
".tgz",
|
||||
".whl",
|
||||
".jar",
|
||||
".nupkg",
|
||||
]
|
||||
.iter()
|
||||
.any(|suffix| lower.ends_with(suffix))
|
||||
}
|
||||
|
||||
fn is_sourceforge_releases_root_locator(locator: &str) -> bool {
|
||||
let trimmed = locator
|
||||
.split(['?', '#'])
|
||||
.next()
|
||||
.unwrap_or(locator)
|
||||
.trim_end_matches('/');
|
||||
|
||||
let parts = trimmed
|
||||
.trim_start_matches("https://sourceforge.net/projects/")
|
||||
.trim_start_matches("http://sourceforge.net/projects/")
|
||||
.split('/')
|
||||
.filter(|segment| !segment.is_empty())
|
||||
.collect::<Vec<_>>();
|
||||
|
||||
parts.len() == 3 && parts[1] == "files" && parts[2] == "releases"
|
||||
}
|
||||
|
||||
fn sourceforge_releases_root_url(locator: &str) -> Option<String> {
|
||||
let trimmed = locator
|
||||
.split(['?', '#'])
|
||||
.next()
|
||||
.unwrap_or(locator)
|
||||
.trim_end_matches('/');
|
||||
|
||||
let prefix = if trimmed.starts_with("https://sourceforge.net/projects/") {
|
||||
"https://sourceforge.net/projects/"
|
||||
} else if trimmed.starts_with("http://sourceforge.net/projects/") {
|
||||
"http://sourceforge.net/projects/"
|
||||
} else {
|
||||
return None;
|
||||
};
|
||||
|
||||
let path = trimmed
|
||||
.trim_start_matches("https://sourceforge.net/projects/")
|
||||
.trim_start_matches("http://sourceforge.net/projects/")
|
||||
.split('/')
|
||||
.filter(|segment| !segment.is_empty())
|
||||
.collect::<Vec<_>>();
|
||||
|
||||
if path.is_empty() {
|
||||
return None;
|
||||
}
|
||||
|
||||
Some(format!("{}{}/files/releases", prefix, path[0]))
|
||||
}
|
||||
|
||||
fn sourceforge_latest_download_url(locator: &str) -> Option<String> {
|
||||
let trimmed = locator
|
||||
.split(['?', '#'])
|
||||
.next()
|
||||
.unwrap_or(locator)
|
||||
.trim_end_matches('/');
|
||||
|
||||
let prefix = if trimmed.starts_with("https://sourceforge.net/projects/") {
|
||||
"https://sourceforge.net/projects/"
|
||||
} else if trimmed.starts_with("http://sourceforge.net/projects/") {
|
||||
"http://sourceforge.net/projects/"
|
||||
} else {
|
||||
return None;
|
||||
};
|
||||
|
||||
let path = trimmed
|
||||
.trim_start_matches("https://sourceforge.net/projects/")
|
||||
.trim_start_matches("http://sourceforge.net/projects/")
|
||||
.split('/')
|
||||
.filter(|segment| !segment.is_empty())
|
||||
.collect::<Vec<_>>();
|
||||
|
||||
if path.is_empty() {
|
||||
return None;
|
||||
}
|
||||
|
||||
Some(format!("{}{}/files/latest/download", prefix, path[0]))
|
||||
}
|
||||
|
|
|
|||
|
|
@ -215,7 +215,7 @@ pub fn build_add_plan_with_reporter<T: GitHubTransport + ?Sized>(
|
|||
let strategy = UpdateStrategy {
|
||||
preferred: crate::domain::update::ChannelPreference {
|
||||
kind: crate::domain::update::UpdateChannelKind::DirectAsset,
|
||||
locator: artifact_url,
|
||||
locator: resolution.source.locator.clone(),
|
||||
reason: "provider-release".to_owned(),
|
||||
},
|
||||
alternates: Vec::new(),
|
||||
|
|
|
|||
|
|
@ -203,6 +203,12 @@ fn execute_update(
|
|||
}
|
||||
|
||||
fn update_query(app: &AppRecord) -> Option<String> {
|
||||
if let Some(source) = app.source.as_ref()
|
||||
&& source.kind == SourceKind::SourceForge
|
||||
{
|
||||
return Some(source.locator.clone());
|
||||
}
|
||||
|
||||
app.source_input.clone().or_else(|| {
|
||||
app.source.as_ref().map(|source| {
|
||||
source
|
||||
|
|
|
|||
|
|
@ -171,19 +171,21 @@ fn classify_sourceforge_http(query: &str) -> Option<Result<ClassifiedInput, Clas
|
|||
};
|
||||
|
||||
let is_project_url = parts.len() == 1;
|
||||
let is_releases_root_url = parts.len() == 3 && parts[1] == "files" && parts[2] == "releases";
|
||||
let is_latest_download_url =
|
||||
parts.len() == 4 && parts[1] == "files" && parts[2] == "latest" && parts[3] == "download";
|
||||
let is_root_file_download_url = parts.len() == 4
|
||||
&& parts[1] == "files"
|
||||
&& parts[3] == "download"
|
||||
&& !matches!(parts[2], "latest" | "releases");
|
||||
let is_nested_file_download_url = parts.len() > 4
|
||||
let is_nested_file_download_url = parts.len() > 5
|
||||
&& parts[1] == "files"
|
||||
&& parts.last() == Some(&"download")
|
||||
&& parts
|
||||
.get(parts.len().saturating_sub(2))
|
||||
.is_some_and(|segment| segment.contains('.'));
|
||||
let is_ambiguous_candidate = is_ambiguous_sourceforge_candidate_path(&parts);
|
||||
let requested_asset_name = sourceforge_requested_asset_name(&parts);
|
||||
let is_concrete_download_url =
|
||||
!is_latest_download_url && (is_root_file_download_url || is_nested_file_download_url);
|
||||
if is_concrete_download_url {
|
||||
|
|
@ -198,7 +200,11 @@ fn classify_sourceforge_http(query: &str) -> Option<Result<ClassifiedInput, Clas
|
|||
tracks_latest: false,
|
||||
}));
|
||||
}
|
||||
if !is_project_url && !is_latest_download_url && !is_ambiguous_candidate {
|
||||
if !is_project_url
|
||||
&& !is_releases_root_url
|
||||
&& !is_latest_download_url
|
||||
&& !is_ambiguous_candidate
|
||||
{
|
||||
return Some(Err(ClassifyInputError::Unsupported));
|
||||
}
|
||||
|
||||
|
|
@ -213,8 +219,8 @@ fn classify_sourceforge_http(query: &str) -> Option<Result<ClassifiedInput, Clas
|
|||
locator: query.to_owned(),
|
||||
canonical_locator: Some((*project).to_owned()),
|
||||
requested_tag: None,
|
||||
requested_asset_name: None,
|
||||
tracks_latest: is_project_url || is_latest_download_url,
|
||||
requested_asset_name,
|
||||
tracks_latest: is_project_url || is_releases_root_url || is_latest_download_url,
|
||||
}))
|
||||
}
|
||||
|
||||
|
|
@ -271,15 +277,45 @@ fn is_ambiguous_gitlab_candidate_path(parts: &[&str]) -> bool {
|
|||
}
|
||||
|
||||
fn is_ambiguous_sourceforge_candidate_path(parts: &[&str]) -> bool {
|
||||
parts.len() == 5
|
||||
&& parts[1] == "files"
|
||||
&& parts[2] == "releases"
|
||||
&& (parts[3] == "stable" || is_version_like_sourceforge_folder(parts[3]))
|
||||
&& parts[4] == "download"
|
||||
parts.len() == 5 && parts[1] == "files" && parts[2] == "releases" && parts[4] == "download"
|
||||
}
|
||||
|
||||
fn is_version_like_sourceforge_folder(segment: &str) -> bool {
|
||||
segment.starts_with('v') && segment.chars().any(|character| character.is_ascii_digit())
|
||||
fn sourceforge_requested_asset_name(parts: &[&str]) -> Option<String> {
|
||||
if parts.len() == 5
|
||||
&& parts[1] == "files"
|
||||
&& parts[2] == "releases"
|
||||
&& parts[4] == "download"
|
||||
&& is_sourceforge_artifact_name(parts[3])
|
||||
{
|
||||
return Some(parts[3].to_owned());
|
||||
}
|
||||
|
||||
None
|
||||
}
|
||||
|
||||
fn is_sourceforge_artifact_name(segment: &str) -> bool {
|
||||
let lower = segment.to_ascii_lowercase();
|
||||
|
||||
[
|
||||
".appimage",
|
||||
".tar.gz",
|
||||
".tar.xz",
|
||||
".tar.bz2",
|
||||
".zip",
|
||||
".deb",
|
||||
".rpm",
|
||||
".exe",
|
||||
".msi",
|
||||
".dmg",
|
||||
".pkg",
|
||||
".apk",
|
||||
".tgz",
|
||||
".whl",
|
||||
".jar",
|
||||
".nupkg",
|
||||
]
|
||||
.iter()
|
||||
.any(|suffix| lower.ends_with(suffix))
|
||||
}
|
||||
|
||||
fn classify_github_http(query: &str) -> Option<ClassifiedInput> {
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue