Skip to content

Commit 06851d6

Browse files
committed
refactor: split out function for downloading fanpage data
1 parent abc40b4 commit 06851d6

File tree

3 files changed

+69
-46
lines changed

3 files changed

+69
-46
lines changed

src/api/mod.rs

Lines changed: 46 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -102,27 +102,24 @@ impl Api {
102102
unfiltered: Option<DownloadsMap>,
103103
items: &'a Vec<&'a Item>,
104104
album: Option<&String>,
105-
artist: Option<&String>
105+
artist: Option<&String>,
106106
) -> DownloadsMap {
107107
unfiltered
108108
.iter()
109109
.flatten()
110110
.filter_map(|(id, url)| {
111-
items.iter().find(|v| &format!("{}{}", v.sale_item_type, v.sale_item_id) == id)
112-
.filter(|item| {
113-
artist.is_none_or(|v| item.band_name.eq_ignore_ascii_case(v))
114-
})
115-
.filter(|item| {
116-
album.is_none_or(|v| item.item_title.eq_ignore_ascii_case(v))
117-
})
111+
items
112+
.iter()
113+
.find(|v| &format!("{}{}", v.sale_item_type, v.sale_item_id) == id)
114+
.filter(|item| artist.is_none_or(|v| item.band_name.eq_ignore_ascii_case(v)))
115+
.filter(|item| album.is_none_or(|v| item.item_title.eq_ignore_ascii_case(v)))
118116
.map(|_| (id.clone(), url.clone()))
119117
})
120118
.collect::<DownloadsMap>()
121119
}
122120

123-
/// Scrape a user's Bandcamp page to find download urls
124-
pub fn get_download_urls(&self, name: &str, artist: Option<&String>, album: Option<&String>) -> Result<BandcampPage, Box<dyn Error>> {
125-
debug!("`get_download_urls` for Bandcamp page '{name}'");
121+
fn download_fanpage_data(&self, name: &str) -> Result<ParsedFanpageData, Box<dyn Error>> {
122+
debug!("`download_fanpage_data` for Bandcamp page '{name}'");
126123

127124
let body = self.request(Method::GET, &Self::bc_path(name))?.text()?;
128125
let soup = Soup::new(&body);
@@ -138,7 +135,24 @@ impl Api {
138135
.expect("Failed to deserialise collection page data blob.");
139136
debug!("Successfully fetched Bandcamp page, and found + deserialised data blob");
140137

141-
let items = fanpage_data.item_cache.collection.values().collect::<Vec<&Item>>();
138+
Ok(fanpage_data)
139+
}
140+
141+
/// Scrape a user's Bandcamp page to find download urls
142+
pub fn get_download_urls(
143+
&self,
144+
name: &str,
145+
artist: Option<&String>,
146+
album: Option<&String>,
147+
) -> Result<BandcampPage, Box<dyn Error>> {
148+
debug!("`get_download_urls` for Bandcamp page '{name}'");
149+
150+
let fanpage_data = self.download_fanpage_data(&name)?;
151+
let items = fanpage_data
152+
.item_cache
153+
.collection
154+
.values()
155+
.collect::<Vec<&Item>>();
142156

143157
match fanpage_data.fan_data.is_own_page {
144158
Some(true) => (),
@@ -147,8 +161,12 @@ impl Api {
147161
)),
148162
}
149163

150-
// TODO: make sure this exists
151-
let mut collection = Self::filter_download_map(fanpage_data.collection_data.redownload_urls.clone(), &items, album, artist);
164+
let mut collection = Self::filter_download_map(
165+
fanpage_data.collection_data.redownload_urls.clone(),
166+
&items,
167+
album,
168+
artist,
169+
);
152170

153171
let skip_hidden_items = true;
154172
if skip_hidden_items {
@@ -163,7 +181,12 @@ impl Api {
163181
// This should never be `None` thanks to the comparison above.
164182
fanpage_data.collection_data.item_count.unwrap()
165183
);
166-
let rest = self.get_rest_downloads_in_collection(&fanpage_data, "collection_items", album, artist)?;
184+
let rest = self.get_rest_downloads_in_collection(
185+
&fanpage_data,
186+
"collection_items",
187+
album,
188+
artist,
189+
)?;
167190
collection.extend(rest);
168191
}
169192

@@ -174,12 +197,15 @@ impl Api {
174197
"Too many in `hidden_data`, and we're told not to skip, so we need to paginate ({} total)",
175198
fanpage_data.hidden_data.item_count.unwrap()
176199
);
177-
let rest = self.get_rest_downloads_in_collection(&fanpage_data, "hidden_items", album, artist)?;
200+
let rest = self.get_rest_downloads_in_collection(
201+
&fanpage_data,
202+
"hidden_items",
203+
album,
204+
artist,
205+
)?;
178206
collection.extend(rest);
179207
}
180208

181-
// let title = soup.tag("title").find().unwrap().text();
182-
183209
debug!("Successfully retrieved all download URLs");
184210
Ok(BandcampPage {
185211
// page_name: title,
@@ -223,10 +249,10 @@ impl Api {
223249
.json::<ParsedCollectionItems>()?;
224250

225251
let items = body.items.iter().by_ref().collect::<Vec<_>>();
226-
let redownload_urls = Self::filter_download_map(Some(body.redownload_urls), &items, album, artist);
252+
let redownload_urls =
253+
Self::filter_download_map(Some(body.redownload_urls), &items, album, artist);
227254
trace!("Collected {} items", redownload_urls.len());
228255

229-
230256
collection.extend(redownload_urls);
231257
more_available = body.more_available;
232258
last_token = body.last_token;

src/api/structs/mod.rs

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -27,9 +27,18 @@ pub struct ItemCache {
2727

2828
#[derive(Deserialize, Debug)]
2929
pub struct Item {
30+
// /// Used in collection_data.sequence, and tracklist. Probably the most unique field?
31+
// #[serde(deserialize_with = "deserialize_string_from_number")]
32+
// pub item_id: String,
33+
// /// The type of the item: "album" or "track".
34+
// pub item_type: String,
35+
/// Used in `id => download url` mapping.
3036
pub sale_item_id: u64,
37+
/// Used in `id => download url` mapping, as the type of item (no idea what it means).
3138
pub sale_item_type: String,
39+
/// The band or artist who released the item.
3240
pub band_name: String,
41+
/// The name of the item.
3342
pub item_title: String,
3443
}
3544

src/cmds/run.rs

Lines changed: 14 additions & 26 deletions
Original file line numberDiff line numberDiff line change
@@ -82,28 +82,14 @@ pub struct Args {
8282
user: String,
8383
}
8484

85-
pub fn command(
86-
Args {
87-
album,
88-
artist,
89-
audio_format,
90-
cookies,
91-
debug,
92-
dry_run,
93-
force,
94-
jobs,
95-
limit,
96-
output_folder,
97-
user,
98-
}: Args,
99-
) -> Result<(), Box<dyn std::error::Error>> {
100-
let cookies_file = cookies.map(|p| {
85+
pub fn command(args: Args) -> Result<(), Box<dyn std::error::Error>> {
86+
let cookies_file = args.cookies.map(|p| {
10187
let expanded = shellexpand::tilde(&p);
10288
expanded.into_owned()
10389
});
104-
let root = shellexpand::tilde(&output_folder);
90+
let root = shellexpand::tilde(&args.output_folder);
10591
let root = Path::new(root.as_ref());
106-
let limit = limit.unwrap_or(usize::MAX);
92+
let limit = args.limit.unwrap_or(usize::MAX);
10793

10894
let root_exists = match fs::metadata(root) {
10995
Ok(d) => Some(d.is_dir()),
@@ -125,19 +111,21 @@ pub fn command(
125111
root.join("bandcamp-collection-downloader.cache"),
126112
)));
127113

128-
let download_urls = api.get_download_urls(&user, artist.as_ref(), album.as_ref())?.download_urls;
114+
let download_urls = api
115+
.get_download_urls(&args.user, args.artist.as_ref(), args.album.as_ref())?
116+
.download_urls;
129117
let items = {
130118
// Lock gets freed after this block.
131119
let cache_content = cache.lock().unwrap().content()?;
132120

133121
download_urls
134122
.into_iter()
135-
.filter(|(x, _)| force || !cache_content.contains(x))
123+
.filter(|(x, _)| args.force || !cache_content.contains(x))
136124
.take(limit)
137125
.collect::<Vec<_>>()
138126
};
139127

140-
if dry_run {
128+
if args.dry_run {
141129
println!("Fetching information for {} found releases", items.len());
142130
} else {
143131
println!("Trying to download {} releases", items.len());
@@ -148,12 +136,12 @@ pub fn command(
148136
let dry_run_results = Arc::new(Mutex::new(Vec::<String>::new()));
149137

150138
thread::scope(|scope| {
151-
for i in 0..jobs {
139+
for i in 0..args.jobs {
152140
let api = api.clone();
153141
let cache = cache.clone();
154142
let m = m.clone();
155143
let queue = queue.clone();
156-
let audio_format = audio_format.clone();
144+
let audio_format = args.audio_format.clone();
157145
let dry_run_results = dry_run_results.clone();
158146

159147
// somehow re-create thread if it panics
@@ -162,7 +150,7 @@ pub fn command(
162150
m.suspend(|| debug!("thread {i} taking {id}"));
163151

164152
// skip_err!
165-
let item = match api.get_digital_item(&url, &debug) {
153+
let item = match api.get_digital_item(&url, &args.debug) {
166154
Ok(Some(item)) => item,
167155
Ok(None) => {
168156
let cache = cache.lock().unwrap();
@@ -180,7 +168,7 @@ pub fn command(
180168
continue;
181169
}
182170

183-
if dry_run {
171+
if args.dry_run {
184172
let results_lock = dry_run_results.lock();
185173
if let Ok(mut results) = results_lock {
186174
results.push(format!("{id}, {} - {}", item.title, item.artist))
@@ -224,7 +212,7 @@ pub fn command(
224212
})
225213
.unwrap();
226214

227-
if dry_run {
215+
if args.dry_run {
228216
println!("{}", dry_run_results.lock().unwrap().join("\n"));
229217
return Ok(());
230218
}

0 commit comments

Comments
 (0)