Skip to content

Commit 90e3d1f

Browse files
author
APT37
committed
Avoid repeated heap allocations when formatting strings
1 parent be9ebde commit 90e3d1f

File tree

13 files changed

+316
-200
lines changed

13 files changed

+316
-200
lines changed

Cargo.lock

Lines changed: 3 additions & 3 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

Cargo.toml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
[package]
22
name = "kumono"
3-
version = "0.65.8"
3+
version = "0.66.0"
44
edition = "2024"
55
description = "Media ripper for coomer and kemono"
66
#homepage = "https://github.com/APT37/kumono"

README.md

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -8,8 +8,8 @@ Support is provided in the [discussions][discussions] section.
88

99
- [Installation](#installation)
1010
- [Binaries](#binaries)
11-
- [Packages (Arch)](#packages-arch)
12-
- [Cargo (Debian)](#cargo-debian)
11+
- [Packages](#packages)
12+
- [Cargo](#cargo)
1313
- [Command Line](#command-line)
1414
- [Available Options](#available-options)
1515
- [Target Selection](#target-selection)
@@ -27,13 +27,13 @@ Support is provided in the [discussions][discussions] section.
2727

2828
[![][linux-x64-badge]][linux-x64-dl] [![][linux-arm-badge]][linux-arm-dl]
2929

30-
### Packages (Arch)
30+
### Packages
3131

3232
You may use an AUR helper like [**paru**][paru] to install one of these packages.
3333

3434
[![][kmn-src-aur-ver]][kmn-src-aur] [![][kmn-bin-aur-ver]][kmn-bin-aur] [![][kmn-git-aur-ver]][kmn-git-aur]
3535

36-
### Cargo (Debian)
36+
### Cargo
3737

3838
[![][crate-ver]][crate-url] [![][crate-deps]][crate-deps-url]
3939

src/api.rs

Lines changed: 64 additions & 42 deletions
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@ use anyhow::{ Result, format_err };
33
use regex::Regex;
44
use reqwest::StatusCode;
55
use serde::{ Deserialize, de::DeserializeOwned };
6-
use std::{ mem, sync::LazyLock };
6+
use std::{ fmt::Write, mem, sync::LazyLock };
77
use thiserror::Error;
88
use tokio::time::{ Duration, sleep };
99

@@ -25,9 +25,7 @@ async fn try_fetch<D: DeserializeOwned>(url: &str) -> Result<D, ApiError> {
2525

2626
if status == StatusCode::BAD_REQUEST && RE_OUT_OF_BOUNDS.is_match(&text) {
2727
return Ok(serde_json::from_str("[]").unwrap());
28-
}
29-
30-
if status != StatusCode::OK {
28+
} else if status != StatusCode::OK {
3129
return Err(ApiError::Status(status));
3230
}
3331

@@ -38,7 +36,7 @@ pub trait Post {
3836
fn files(&mut self) -> Vec<PostFile>;
3937
}
4038

41-
#[derive(Debug, Clone, Error, PartialEq, Eq, PartialOrd, Ord)]
39+
#[derive(Debug, Error)]
4240
pub enum ApiError {
4341
#[error("connection error")] Connect(String),
4442
#[error("non-success status code")] Status(StatusCode),
@@ -73,32 +71,30 @@ impl ApiError {
7371
}
7472
}
7573

76-
#[derive(Debug, Clone, Deserialize, PartialEq, Eq, PartialOrd, Ord)]
74+
#[derive(Deserialize)]
7775
pub struct SinglePost {
7876
post: SinglePostInner,
7977
}
8078

81-
#[derive(Debug, Clone, Deserialize, PartialEq, Eq, PartialOrd, Ord, Default)]
79+
#[derive(Deserialize, Default)]
8280
struct SinglePostInner {
8381
file: Option<PostFileRaw>,
8482
attachments: Vec<PostFileRaw>,
8583
}
8684

8785
impl Post for SinglePost {
8886
fn files(&mut self) -> Vec<PostFile> {
89-
let post = mem::take(&mut self.post);
87+
self.post.attachments.retain(|file| file.path.is_some());
9088

91-
let mut files = Vec::with_capacity(post.attachments.len() + 1);
89+
let attachments = mem::take(&mut self.post.attachments);
9290

93-
files.append(
94-
&mut post.attachments
95-
.into_iter()
96-
.filter_map(|raw| raw.path)
97-
.map(PostFile::new)
98-
.collect()
99-
);
91+
let mut files = Vec::with_capacity(attachments.len() + 1);
10092

101-
if let Some(file) = post.file && let Some(path) = file.path {
93+
for raw in attachments {
94+
files.push(PostFile::new(raw.path.unwrap()));
95+
}
96+
97+
if let Some(raw) = self.post.file.take() && let Some(path) = raw.path {
10298
files.push(PostFile::new(path));
10399
}
104100

@@ -111,68 +107,94 @@ pub async fn try_fetch_page(
111107
user: &str,
112108
offset: usize
113109
) -> Result<Vec<PagePost>, ApiError> {
114-
try_fetch(
115-
&format!(
116-
"https://{site}/api/v1/{service}/user/{user}/posts?o={offset}",
117-
site = target.as_service().site(),
118-
service = target.as_service()
119-
)
120-
).await
110+
let (host, service, offset) = (
111+
target.as_service().host(),
112+
target.as_service().as_static_str(),
113+
offset.to_string(),
114+
);
115+
116+
let mut url = String::with_capacity(
117+
8 + host.len() + 8 + service.len() + 6 + user.len() + 9 + offset.len()
118+
);
119+
120+
write!(url, "https://{host}/api/v1/{service}/user/{user}/posts?o={offset}").unwrap();
121+
122+
drop(offset);
123+
124+
try_fetch(&url).await
121125
}
122126

123-
#[derive(Debug, Clone, Deserialize, PartialEq, Eq, PartialOrd, Ord, Default)]
127+
#[derive(Deserialize)]
124128
pub struct PagePost {
125129
file: Option<PostFileRaw>,
126130
attachments: Vec<PostFileRaw>,
127131
}
128132

129133
impl Post for PagePost {
130134
fn files(&mut self) -> Vec<PostFile> {
135+
self.attachments.retain(|file| file.path.is_some());
136+
131137
let attachments = mem::take(&mut self.attachments);
132138

133139
let mut files = Vec::with_capacity(attachments.len() + 1);
134140

135-
files.append(
136-
&mut attachments
137-
.into_iter()
138-
.filter_map(|raw| raw.path)
139-
.map(PostFile::new)
140-
.collect()
141-
);
141+
for raw in attachments {
142+
if let Some(path) = raw.path {
143+
files.push(PostFile::new(path));
144+
}
145+
}
142146

143-
if let Some(file) = self.file.take() && let Some(path) = file.path {
147+
if let Some(raw) = self.file.take() && let Some(path) = raw.path {
144148
files.push(PostFile::new(path));
145149
}
146150

147151
files
148152
}
149153
}
150154

151-
#[derive(Debug, Clone, Deserialize, PartialEq, Eq, PartialOrd, Ord)]
155+
#[derive(Deserialize)]
152156
pub struct DiscordChannel {
153157
pub id: String, // "455285536341491716",
154158
// name: String, // "news"
155159
}
156160

157161
pub async fn try_discord_server(server: &str) -> Result<Vec<DiscordChannel>, ApiError> {
158-
try_fetch(&format!("https://kemono.cr/api/v1/discord/channel/lookup/{server}")).await
162+
let mut url = String::with_capacity(48 + server.len());
163+
164+
write!(url, "https://kemono.cr/api/v1/discord/channel/lookup/{server}").unwrap();
165+
166+
try_fetch(&url).await
159167
}
160168

161-
#[derive(Debug, Clone, Deserialize, PartialEq, Eq, PartialOrd, Ord, Default)]
169+
#[derive(Deserialize)]
162170
pub struct DiscordPost {
163171
attachments: Vec<PostFileRaw>,
164172
}
165173

166174
impl Post for DiscordPost {
167175
fn files(&mut self) -> Vec<PostFile> {
168-
self.attachments
169-
.drain(..)
170-
.filter_map(|file| file.path)
171-
.map(PostFile::new)
172-
.collect()
176+
self.attachments.retain(|file| file.path.is_some());
177+
178+
let attachments = mem::take(&mut self.attachments);
179+
180+
let mut files = Vec::with_capacity(attachments.len());
181+
182+
for raw in attachments {
183+
files.push(PostFile::new(raw.path.unwrap()));
184+
}
185+
186+
files
173187
}
174188
}
175189

176190
pub async fn try_discord_page(channel: &str, offset: usize) -> Result<Vec<DiscordPost>, ApiError> {
177-
try_fetch(&format!("https://kemono.cr/api/v1/discord/channel/{channel}?o={offset}")).await
191+
let offset = offset.to_string();
192+
193+
let mut url = String::with_capacity(41 + channel.len() + 3 + offset.len());
194+
195+
write!(url, "https://kemono.cr/api/v1/discord/channel/lookup/{channel}?o={offset}").unwrap();
196+
197+
drop(offset);
198+
199+
try_fetch(&url).await
178200
}

src/cli.rs

Lines changed: 1 addition & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -122,9 +122,7 @@ impl Args {
122122

123123
impl Display for Args {
124124
fn fmt(&self, f: &mut Formatter<'_>) -> Result {
125-
fn pd(d: &Duration) -> String {
126-
pretty_duration(d, None)
127-
}
125+
let pd = |d: &Duration| pretty_duration(d, None);
128126

129127
write!(
130128
f,

src/ext.rs

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@ use crate::{ file::PostFile, target::Target };
22
use itertools::Itertools;
33
use std::{ collections::{ HashMap, HashSet }, fmt::{ Display, Formatter, Result } };
44

5-
#[derive(Debug, Clone, Default)]
5+
#[derive(Default)]
66
pub struct ExtensionList {
77
extensions: HashSet<String>,
88
without_extension: usize,
@@ -41,7 +41,7 @@ impl Display for ExtensionList {
4141
if !self.extensions.is_empty() {
4242
writeln!(f)?;
4343
}
44-
44+
4545
write!(f, "{} files do not have an extension", self.without_extension)?;
4646
}
4747

0 commit comments

Comments
 (0)