bytes_radar/net/providers/
sourceforge.rs1use crate::net::traits::{GitProvider, ParsedRepository, ProviderConfig};
2use async_trait::async_trait;
3use reqwest::Client;
4use std::collections::HashMap;
5
6pub struct SourceForgeProvider {
7 credentials: HashMap<String, String>,
8}
9
10impl SourceForgeProvider {
11 pub fn new() -> Self {
12 Self {
13 credentials: HashMap::new(),
14 }
15 }
16}
17
18#[async_trait]
19impl GitProvider for SourceForgeProvider {
20 fn name(&self) -> &'static str {
21 "sourceforge"
22 }
23
24 fn can_handle(&self, url: &str) -> bool {
25 url.contains("sourceforge.net") || url.contains("sf.net")
26 }
27
28 fn parse_url(&self, url: &str) -> Option<ParsedRepository> {
29 if !self.can_handle(url) {
30 return None;
31 }
32
33 let url = url.trim_end_matches('/');
34
35 if url.contains("/ci/") && url.contains("/tree/") {
36 return self.parse_tree_url(url);
37 }
38
39 if url.contains("/ci/") {
40 return self.parse_commit_url(url);
41 }
42
43 self.parse_basic_url(url)
44 }
45
46 fn build_download_urls(&self, parsed: &ParsedRepository) -> Vec<String> {
47 let mut urls = Vec::new();
48
49 if let Some(ref branch_or_commit) = parsed.branch_or_commit {
50 urls.push(format!(
51 "https://sourceforge.net/p/{}/code/ci/{}/tarball",
52 parsed.repo, branch_or_commit
53 ));
54 }
55
56 urls
57 }
58
59 async fn get_default_branch(
60 &self,
61 _client: &Client,
62 _parsed: &ParsedRepository,
63 ) -> Option<String> {
64 None
65 }
66
67 fn apply_config(&mut self, config: &ProviderConfig) {
68 self.credentials = config.credentials.clone();
69 }
70
71 fn get_project_name(&self, url: &str) -> String {
72 if let Some(parsed) = self.parse_url(url) {
73 return parsed.project_name;
74 }
75
76 if let Some(filename) = url.split('/').next_back() {
77 if filename.ends_with(".tar.gz") {
78 return filename.trim_end_matches(".tar.gz").to_string();
79 }
80 if filename.ends_with(".tgz") {
81 return filename.trim_end_matches(".tgz").to_string();
82 }
83 return filename.to_string();
84 }
85
86 "sourceforge-project".to_string()
87 }
88}
89
90impl SourceForgeProvider {
91 fn parse_commit_url(&self, url: &str) -> Option<ParsedRepository> {
92 let parts: Vec<&str> = url.split('/').collect();
93 if let Some(ci_pos) = parts.iter().position(|&x| x == "ci") {
94 if ci_pos + 1 < parts.len() && ci_pos >= 3 {
95 let project = parts[ci_pos - 1].to_string();
96 let commit = parts[ci_pos + 1].to_string();
97
98 return Some(
99 ParsedRepository::new("sourceforge".to_string(), project)
100 .with_commit(commit)
101 .with_host("sourceforge.net".to_string()),
102 );
103 }
104 }
105 None
106 }
107
108 fn parse_tree_url(&self, url: &str) -> Option<ParsedRepository> {
109 let parts: Vec<&str> = url.split('/').collect();
110 if let Some(tree_pos) = parts.iter().position(|&x| x == "tree") {
111 if tree_pos + 1 < parts.len() {
112 if let Some(ci_pos) = parts.iter().position(|&x| x == "ci") {
113 if ci_pos >= 3 {
114 let project = parts[ci_pos - 1].to_string();
115 let branch = parts[tree_pos + 1].to_string();
116
117 return Some(
118 ParsedRepository::new("sourceforge".to_string(), project)
119 .with_branch(branch)
120 .with_host("sourceforge.net".to_string()),
121 );
122 }
123 }
124 }
125 }
126 None
127 }
128
129 fn parse_basic_url(&self, url: &str) -> Option<ParsedRepository> {
130 if url.contains("/p/") && url.contains("/code") {
131 let parts: Vec<&str> = url.split('/').collect();
132 if let Some(p_pos) = parts.iter().position(|&x| x == "p") {
133 if p_pos + 1 < parts.len() {
134 let project = parts[p_pos + 1].to_string();
135 return Some(
136 ParsedRepository::new("sourceforge".to_string(), project)
137 .with_host("sourceforge.net".to_string()),
138 );
139 }
140 }
141 }
142 None
143 }
144}
145
146impl Default for SourceForgeProvider {
147 fn default() -> Self {
148 Self::new()
149 }
150}
151
152#[cfg(test)]
153mod tests {
154 use super::*;
155
156 #[test]
157 fn test_can_handle() {
158 let provider = SourceForgeProvider::new();
159 assert!(provider.can_handle("https://sourceforge.net/p/project/code/"));
160 assert!(provider.can_handle("https://sf.net/p/project/code/"));
161 assert!(!provider.can_handle("https://github.com/user/repo"));
162 }
163
164 #[test]
165 fn test_parse_basic_url() {
166 let provider = SourceForgeProvider::new();
167
168 let parsed = provider
169 .parse_url("https://sourceforge.net/p/myproject/code/")
170 .unwrap();
171 assert_eq!(parsed.owner, "sourceforge");
172 assert_eq!(parsed.repo, "myproject");
173 assert_eq!(parsed.project_name, "myproject@main");
174 assert_eq!(parsed.branch_or_commit, None);
175 assert!(!parsed.is_commit);
176 assert_eq!(parsed.host.as_ref().unwrap(), "sourceforge.net");
177 }
178
179 #[test]
180 fn test_build_download_urls() {
181 let provider = SourceForgeProvider::new();
182
183 let parsed = ParsedRepository::new("sourceforge".to_string(), "project".to_string())
184 .with_branch("master".to_string());
185
186 let urls = provider.build_download_urls(&parsed);
187 assert!(
188 urls.contains(&"https://sourceforge.net/p/project/code/ci/master/tarball".to_string())
189 );
190 }
191}